Documentation
¶
Overview ¶
Package adminapi implements the ToughRADIUS management REST API served under /api/v1. Its handlers back the React Admin frontend and expose CRUD and action endpoints for operators, NAS devices, network nodes, RADIUS profiles and users, online sessions, accounting records, the dashboard, system settings, logs, and backups.
Handlers are plain github.com/labstack/echo/v4 HandlerFuncs registered with the shared web server (see internal/webserver). They read their dependencies — the application context, the GORM database handle, and the configuration manager — from the echo context using GetAppContext, GetDB, and GetConfig, rather than holding injected state, so a handler is a stateless function of its request.
Successful responses use the unified Response envelope (a data object with optional pagination Meta); failures use ErrorResponse with a stable, machine-readable error code. Authorization is enforced per route with the RequireLevel middleware against the operator levels LevelSuper, LevelAdmin, and LevelOperator.
Index ¶
- Constants
- func ChangeOnlineSessionAuthorization(c echo.Context) error
- func CreateCertificate(c echo.Context) error
- func CreateNAS(c echo.Context) error
- func CreateProfile(c echo.Context) error
- func DeleteCertificate(c echo.Context) error
- func DeleteNAS(c echo.Context) error
- func DeleteOnlineSession(c echo.Context) error
- func DeleteProfile(c echo.Context) error
- func DisconnectOnlineSession(c echo.Context) error
- func ExportCertificate(c echo.Context) error
- func GetAccounting(c echo.Context) error
- func GetAppContext(c echo.Context) app.AppContext
- func GetCertificate(c echo.Context) error
- func GetConfig(c echo.Context) *app.ConfigManager
- func GetDB(c echo.Context) *gorm.DB
- func GetDashboardStats(c echo.Context) error
- func GetNAS(c echo.Context) error
- func GetOnlineSession(c echo.Context) error
- func GetProfile(c echo.Context) error
- func Init(appCtx app.AppContext)
- func ListAccounting(c echo.Context) error
- func ListCertificates(c echo.Context) error
- func ListNAS(c echo.Context) error
- func ListOnlineSessions(c echo.Context) error
- func ListProfiles(c echo.Context) error
- func RequireLevel(levels ...string) echo.MiddlewareFunc
- func UpdateCertificate(c echo.Context) error
- func UpdateNAS(c echo.Context) error
- func UpdateProfile(c echo.Context) error
- type DashboardAuthTrendPoint
- type DashboardIPv6Stats
- type DashboardProfileSlice
- type DashboardStats
- type DashboardTrafficPoint
- type ErrorResponse
- type ImportUserError
- type ImportUserResult
- type Meta
- type ProfileRequest
- type ProfileUpdateRequest
- type Response
- type SystemBackup
- type SystemBackupCert
- type SystemRestoreResult
- type UserRequest
- type UserUpdateRequest
Constants ¶
const ( LevelSuper = "super" LevelAdmin = "admin" LevelOperator = "operator" )
Operator privilege levels, ordered from most to least privileged.
Variables ¶
This section is empty.
Functions ¶
func ChangeOnlineSessionAuthorization ¶ added in v9.1.0
ChangeOnlineSessionAuthorization sends a RADIUS CoA-Request (RFC 5176 §2.2) to the session's NAS to change the live session's authorization (for example a new Session-Timeout or Filter-Id) without disconnecting it, returning the structured exchange result. At least one change must be supplied.
As with DisconnectOnlineSession, a NAK or timeout is reported with HTTP 200 and success=false; the structured body carries the Error-Cause and timing.
Authorization: admin/super only (requireAdmin).
@Summary Change a live session's authorization via RADIUS CoA-Request @Tags OnlineSession @Param id path int true "Session ID" @Param body body coaChangePayload true "Authorization changes" @Success 200 {object} Response @Router /api/v1/sessions/{id}/coa [post]
func CreateCertificate ¶ added in v9.2.0
CreateCertificate handles POST /api/v1/system/certificate, importing a certificate from the JSON body bound to certPayload. The local name must be unique; a duplicate is rejected 409 with code NAME_EXISTS. The PEM material is parsed to derive the subject, issuer, serial, SHA-256 fingerprint, and validity window; malformed material is rejected 400 with code INVALID_CERT. Server certificates must include a private key matching the certificate (400 codes KEY_REQUIRED / KEY_MISMATCH). On success it returns the persisted domain.SysCert without the private key. This endpoint requires an admin or super operator (see requireAdmin).
@Summary import a certificate @Tags Certificate @Param certificate body certPayload true "Certificate material" @Success 200 {object} domain.SysCert @Router /api/v1/system/certificate [post]
func CreateNAS ¶
CreateNAS handles POST /api/v1/network/nas, creating a NAS device from the JSON body bound to nasPayload and validated against its struct tags. The IP address must be unique; a duplicate is rejected 409 with code IPADDR_EXISTS. Unset optional fields default to status "enabled" and CoA port 3799. On success it returns the persisted domain.NetNas. This endpoint requires an admin or super operator (see requireAdmin).
@Summary create a NAS device @Tags NAS @Param nas body nasPayload true "NAS device information" @Success 201 {object} domain.NetNas @Router /api/v1/network/nas [post]
func CreateProfile ¶
CreateProfile handles POST /api/v1/radius-profiles, creating a RADIUS profile from the JSON body bound to ProfileRequest and validated against its struct tags. The profile name must be unique; a duplicate is rejected 409 with code NAME_EXISTS. An unset status defaults to "enabled". On success it returns the persisted domain.RadiusProfile. This endpoint requires an admin or super operator (see requireAdmin).
@Summary create a RADIUS profile @Tags RadiusProfile @Param profile body ProfileRequest true "Profile information" @Success 201 {object} domain.RadiusProfile @Router /api/v1/radius-profiles [post]
func DeleteCertificate ¶ added in v9.2.0
DeleteCertificate handles DELETE /api/v1/system/certificate/:id, removing the certificate with the given numeric id. It responds 400 with code INVALID_ID for a non-integer path parameter and returns {"data": {"id": id}} on success. This endpoint requires an admin or super operator (see requireAdmin).
@Summary delete a certificate @Tags Certificate @Param id path int true "Certificate ID" @Success 200 {object} map[string]interface{} @Router /api/v1/system/certificate/{id} [delete]
func DeleteNAS ¶
DeleteNAS handles DELETE /api/v1/network/nas/:id, removing the NAS device with the given id. To protect accounting integrity it refuses deletion while the device still has online sessions, responding 409 with code HAS_ONLINE_SESSIONS and the active count in Details. It responds 400 with code INVALID_ID for a non-integer id. This endpoint requires an admin or super operator (see requireAdmin).
@Summary delete a NAS device @Tags NAS @Param id path int true "NAS ID" @Success 200 {object} SuccessResponse @Router /api/v1/network/nas/{id} [delete]
func DeleteOnlineSession ¶
DeleteOnlineSession handles DELETE /api/v1/sessions/:id, forcing the user of the online session with the given id offline. It removes the local session record and then tears the connection down on the NAS by sending an RFC 5176 Disconnect-Request through the shared, audited CoAService client path — the same path used by POST /sessions/:id/disconnect — so the request carries the full NAS and session identity triplet (NAS-IP-Address, Acct-Session-Id, User-Name, …), honors the NAS CoA port (defaulting to 3799 / DefaultCoAPort), brackets IPv6 addresses correctly, and leaves a durable M2.3 audit record. The Disconnect outcome (ACK/NAK/timeout) is logged and audited, not returned in the HTTP body. When the NAS is not found in the database the record is still deleted and only a warning is logged. A non-integer id responds 400 INVALID_ID, an unknown id 404 NOT_FOUND, and a failed delete 500 DELETE_FAILED. On success it returns a confirmation message.
Authorization: admin/super only (requireAdmin), since forcing a subscriber offline disrupts a live user; this matches the disconnect/coa POST actions.
@Summary Force user offline @Tags OnlineSession @Param id path int true "Session ID" @Success 200 {object} SuccessResponse @Router /api/v1/sessions/{id} [delete]
func DeleteProfile ¶
DeleteProfile handles DELETE /api/v1/radius-profiles/:id, removing the profile with the given id. A profile still referenced by one or more users is not deleted; it is rejected 409 with code IN_USE and the offending user_count in the response details. A non-integer id responds 400 INVALID_ID. On success it invalidates the profile cache and returns a confirmation message. This endpoint requires an admin or super operator (see requireAdmin).
@Summary Delete RADIUS Profile @Tags RadiusProfile @Param id path int true "Profile ID" @Success 200 {object} SuccessResponse @Router /api/v1/radius-profiles/{id} [delete]
func DisconnectOnlineSession ¶ added in v9.1.0
DisconnectOnlineSession sends a RADIUS Disconnect-Request (RFC 5176 §2.1) to the NAS hosting the session, forcing the user offline, and returns the structured exchange result (ACK/NAK/timeout, Error-Cause, attempts, RTT).
Unlike DELETE /sessions/:id — which removes the local record and best-effort notifies the NAS asynchronously — this endpoint performs a synchronous, retry-bounded exchange and reports the NAS's actual response, giving the operator immediate confirmation. The local session record is left untouched; the NAS is expected to emit an Accounting-Stop that clears it.
A NAK or timeout is reported with HTTP 200 and success=false in the body (the exchange genuinely ran; its outcome is the payload). Build/transport setup failures return 4xx/5xx.
Authorization: admin/super only (requireAdmin), since it disrupts live users.
@Summary Force a session offline via RADIUS Disconnect-Request @Tags OnlineSession @Param id path int true "Session ID" @Success 200 {object} Response @Router /api/v1/sessions/{id}/disconnect [post]
func ExportCertificate ¶ added in v9.2.0
ExportCertificate handles GET /api/v1/system/certificate/:id/export, streaming the certificate as a PEM file attachment named "<name>.pem". By default only the public certificate is exported; include_key=true additionally appends the private key, which is a sensitive operation recorded to the application log together with the requesting operator. A request for the key when none is stored is rejected 400 with code NO_KEY. This endpoint requires an admin or super operator (see requireAdmin).
@Summary export a certificate @Tags Certificate @Param id path int true "Certificate ID" @Param include_key query bool false "Include the private key in the export" @Success 200 {string} string "PEM data" @Router /api/v1/system/certificate/{id}/export [get]
func GetAccounting ¶
GetAccounting handles GET /api/v1/accounting/:id and returns one accounting record by numeric id. It responds with INVALID_ID (400) when the path parameter is not an integer and NOT_FOUND (404) when the record does not exist. Any authenticated operator may call this endpoint.
@Summary get accounting record detail @Tags Accounting @Param id path int true "Accounting ID" @Success 200 {object} domain.RadiusAccounting @Router /api/v1/accounting/{id} [get]
func GetAppContext ¶
func GetAppContext(c echo.Context) app.AppContext
GetAppContext returns the application context that request middleware stored on the echo context under the "appCtx" key. It is the entry point handlers use to reach shared services (database, configuration, scheduler).
It panics if no application context is present, which indicates the route was registered without the middleware that injects it — a programming error rather than a runtime condition, so it is surfaced immediately instead of returning a nil context that would fault later.
func GetCertificate ¶ added in v9.2.0
GetCertificate handles GET /api/v1/system/certificate/:id, returning the single certificate with the given numeric id. It responds 400 with code INVALID_ID when the path parameter is not an integer and 404 with code NOT_FOUND when no such certificate exists. The private key is never serialized. Any authenticated operator may call it.
@Summary get certificate detail @Tags Certificate @Param id path int true "Certificate ID" @Success 200 {object} domain.SysCert @Router /api/v1/system/certificate/{id} [get]
func GetConfig ¶
func GetConfig(c echo.Context) *app.ConfigManager
GetConfig returns the configuration manager for the current request, resolved from the application context. It is the handler-facing accessor for reading and updating dynamic system settings.
func GetDB ¶
GetDB returns the GORM database handle for the current request. It prefers a per-request handle stored under the "db" key (used by tests and request-scoped transactions) and otherwise falls back to the shared connection from the application context, so handlers get a usable *gorm.DB either way.
func GetDashboardStats ¶
GetDashboardStats handles GET /api/v1/dashboard/stats, returning a single DashboardStats snapshot that the admin dashboard renders in one request. It aggregates real-time counters (total, online, disabled, and expired users, and total profiles), today's volumes (authentication and accounting record counts plus today's upstream/downstream traffic in GB), and three time series — a 7-day daily authentication trend, a 24-hour hourly upload/download traffic series, and the distribution of online users across profiles — together with an IPv6 adoption summary (DashboardIPv6Stats).
"Today" is measured from the local start of day; the trend and traffic series are zero-filled so every day/hour bucket is present even with no data, and traffic is reported in GB (bytes divided by 1024^3). TodayAuthCount is an estimate derived from sessions that started today, not a dedicated authentication counter. A failure in any individual section is logged and that section falls back to zero/empty rather than failing the whole response. Any authenticated operator may call it.
@Summary get dashboard statistics @Tags Dashboard @Accept json @Produce json @Success 200 {object} DashboardStats @Router /api/v1/dashboard/stats [get]
func GetNAS ¶
GetNAS handles GET /api/v1/network/nas/:id, returning the single NAS device with the given numeric id. It responds 400 with code INVALID_ID when the path parameter is not an integer and 404 with code NOT_FOUND when no such device exists. Any authenticated operator may call it.
@Summary get NAS device detail @Tags NAS @Param id path int true "NAS ID" @Success 200 {object} domain.NetNas @Router /api/v1/network/nas/{id} [get]
func GetOnlineSession ¶
GetOnlineSession handles GET /api/v1/sessions/:id, returning the single online session (domain.RadiusOnline) with the given id. A non-integer id responds 400 INVALID_ID and an unknown id 404 NOT_FOUND. Any authenticated operator may call it.
@Summary Get online session details @Tags OnlineSession @Param id path int true "Session ID" @Success 200 {object} domain.RadiusOnline @Router /api/v1/sessions/{id} [get]
func GetProfile ¶
GetProfile handles GET /api/v1/radius-profiles/:id, returning the single RADIUS profile with the given numeric id. It responds 400 with code INVALID_ID when the path parameter is not an integer and 404 with code NOT_FOUND when no such profile exists. Any authenticated operator may call it.
@Summary get RADIUS profile detail @Tags RadiusProfile @Param id path int true "Profile ID" @Success 200 {object} domain.RadiusProfile @Router /api/v1/radius-profiles/{id} [get]
func Init ¶
func Init(appCtx app.AppContext)
Init registers every admin API route group on the shared web server. It wires the handlers in this package to their paths under /api/v1 and must be called once during startup, after the application context is ready and before the web server begins serving. It is not safe for concurrent use and is not meant to be called more than once.
The appCtx parameter is accepted for symmetry with other subsystems' Init functions; handlers resolve the live application context per request from the echo context (see GetAppContext) rather than capturing it here.
func ListAccounting ¶
ListAccounting handles GET /api/v1/accounting and returns a paginated accounting-record page in the standard Response envelope. It accepts page and perPage (perPage is clamped to 1..100, default 10), optional sort/order (validated against allowedAcctSortFields to protect ORDER BY from injection), and optional fuzzy filters for username, NAS address, session ID, IPv4/IPv6, MAC, and delegated-prefix fields.
Time-window filters are optional: acct_start_time_gte and acct_start_time_lte accept RFC3339, datetime-local ("2006-01-02T15:04"), or date-only ("2006-01-02") values. Unparseable time values are ignored rather than rejected.
Any authenticated operator may call this endpoint.
@Summary get accounting logs table @Tags Accounting @Param page query int false "Page number" @Param perPage query int false "Items per page" @Param sort query string false "Sort field" @Param order query string false "Sort direction" @Param username query string false "Username" @Param nas_addr query string false "NAS address" @Param acct_session_id query string false "Session ID" @Param framed_ipaddr query string false "User IP address" @Param mac_addr query string false "MAC address" @Param framed_ipv6_address query string false "Framed IPv6 address" @Param framed_ipv6_prefix query string false "Framed IPv6 prefix" @Param delegated_ipv6_prefix query string false "Delegated IPv6 prefix" @Param acct_start_time_gte query string false "Start time from (RFC3339 or datetime-local format)" @Param acct_start_time_lte query string false "Start time to (RFC3339 or datetime-local format)" @Success 200 {object} ListResponse @Router /api/v1/accounting [get]
func ListCertificates ¶ added in v9.2.0
ListCertificates handles GET /api/v1/system/certificate, returning a paginated list of locally managed certificates. It accepts the page and perPage query parameters (perPage clamped to 1..100, default 10) and optional name (matched case-insensitively) and cert_type filters. The sort and order parameters are validated against allowedCertSortFields to keep the ORDER BY clause injection-safe. The response body is {"data": []domain.SysCert, "total": int64}; the private key is never serialized and HasKey reports its presence. Any authenticated operator may call it.
@Summary get the certificate list @Tags Certificate @Param page query int false "Page number" @Param perPage query int false "Items per page" @Param sort query string false "Sort field" @Param order query string false "Sort direction" @Param name query string false "Certificate name" @Param cert_type query string false "Certificate type (server|ca)" @Success 200 {object} ListResponse @Router /api/v1/system/certificate [get]
func ListNAS ¶
ListNAS handles GET /api/v1/network/nas, returning a paginated list of NAS devices. It accepts the page and perPage query parameters (perPage is clamped to 1..100, default 10) and optional name, status, and ipaddr filters; name matches case-insensitively and ipaddr matches by prefix. The sort and order parameters are validated against allowedNasSortFields to keep the ORDER BY clause injection-safe. The response body is {"data": []domain.NetNas, "total": int64}. Any authenticated operator may call it.
@Summary get the NAS device list @Tags NAS @Param page query int false "Page number" @Param perPage query int false "Items per page" @Param sort query string false "Sort field" @Param order query string false "Sort direction" @Param name query string false "Device name" @Param status query string false "Device status" @Success 200 {object} ListResponse @Router /api/v1/network/nas [get]
func ListOnlineSessions ¶
ListOnlineSessions handles GET /api/v1/sessions, returning a paginated page of the currently online RADIUS sessions (domain.RadiusOnline). It accepts the page and perPage query parameters (perPage is clamped to 1..100, default 10) and orders by acct_start_time DESC unless overridden by sort and order, which are validated against allowedSessionSortFields so the ORDER BY clause cannot be used for SQL injection. Sessions may be filtered by username, framed_ipv6_address, framed_ipv6_prefix, delegated_ipv6_prefix, mac_addr, and acct_session_id (each a substring match whose LIKE wildcards are escaped), by nas_addr and framed_ipaddr (exact match), and by an acct_start_time_gte / acct_start_time_lte window whose bounds accept RFC 3339, HTML datetime-local, "2006-01-02 15:04:05", or date-only strings (an unparseable bound is ignored rather than erroring). The response is the paginated Response envelope. Any authenticated operator may call it.
@Summary List online sessions @Tags OnlineSession @Param page query int false "Page number" @Param perPage query int false "Items per page" @Param sort query string false "Sort field" @Param order query string false "Sort direction" @Param username query string false "Username" @Param nas_addr query string false "NAS addresses" @Param framed_ipaddr query string false "User IP address" @Param framed_ipv6_address query string false "Framed IPv6 address" @Param framed_ipv6_prefix query string false "Framed IPv6 prefix" @Param delegated_ipv6_prefix query string false "Delegated IPv6 prefix" @Param mac_addr query string false "MAC address" @Param acct_session_id query string false "Session ID" @Param acct_start_time_gte query string false "Start time from (RFC3339 or datetime-local)" @Param acct_start_time_lte query string false "Start time to (RFC3339 or datetime-local)" @Success 200 {object} ListResponse @Router /api/v1/sessions [get]
func ListProfiles ¶
ListProfiles handles GET /api/v1/radius-profiles, returning a paginated page of RADIUS profiles (the rate, address-pool, and binding plans assigned to users). It accepts the page and perPage query parameters (perPage is clamped to 1..100, default 10) and optional name, status, addr_pool, and domain filters; name, addr_pool, and domain match case-insensitively while status matches exactly. The sort and order parameters are validated against allowedProfileSortFields so the ORDER BY clause cannot be used for SQL injection. The response is the paginated Response envelope: the page of domain.RadiusProfile in Data and the total/page/page-size counters in Meta. Any authenticated operator may call it.
@Summary get the RADIUS profile list @Tags RadiusProfile @Param page query int false "Page number" @Param perPage query int false "Items per page" @Param sort query string false "Sort field" @Param order query string false "Sort direction" @Success 200 {object} ListResponse @Router /api/v1/radius-profiles [get]
func RequireLevel ¶ added in v9.1.0
func RequireLevel(levels ...string) echo.MiddlewareFunc
RequireLevel returns middleware that authorizes a request only when the authenticated operator's level is one of the allowed levels.
It must be chained after the JWT middleware so the operator can be resolved from the request context. Requests from operators whose level is not allowed receive HTTP 403; requests without a resolvable operator receive HTTP 401.
func UpdateCertificate ¶ added in v9.2.0
UpdateCertificate handles PUT /api/v1/system/certificate/:id, applying a partial update to an existing certificate from the JSON body bound to certUpdatePayload. Empty cert and private_key fields leave the stored material untouched so the local name and remark can be edited without re-uploading PEM. When new certificate material is supplied it is re-parsed for metadata, and a server certificate's key (new or existing) must match the certificate. A changed name must remain unique. It responds 404 with code NOT_FOUND when the certificate does not exist and returns the updated domain.SysCert on success. This endpoint requires an admin or super operator (see requireAdmin).
@Summary update a certificate @Tags Certificate @Param id path int true "Certificate ID" @Param certificate body certUpdatePayload true "Certificate fields" @Success 200 {object} domain.SysCert @Router /api/v1/system/certificate/{id} [put]
func UpdateNAS ¶
UpdateNAS handles PUT /api/v1/network/nas/:id, applying a partial update to an existing NAS device from the JSON body bound to nasUpdatePayload. Only non-empty fields are written, so omitted fields keep their stored values. A changed IP address must remain unique across other devices; a collision is rejected 409 with code IPADDR_EXISTS. It responds 404 with code NOT_FOUND when the device does not exist and returns the updated domain.NetNas on success. This endpoint requires an admin or super operator (see requireAdmin).
@Summary update a NAS device @Tags NAS @Param id path int true "NAS ID" @Param nas body nasPayload true "NAS device information" @Success 200 {object} domain.NetNas @Router /api/v1/network/nas/{id} [put]
func UpdateProfile ¶
UpdateProfile handles PUT /api/v1/radius-profiles/:id, applying a partial update to the profile with the given id from the JSON body bound to ProfileUpdateRequest. Only the supplied (non-empty, non-negative) fields are written, so omitted fields keep their stored value. A non-integer id responds 400 INVALID_ID and an unknown id 404 NOT_FOUND; changing the name to one already used by another profile is rejected 409 with code NAME_EXISTS. After a successful update it invalidates the profile cache so dynamic users pick up the change, then returns the refreshed domain.RadiusProfile. This endpoint requires an admin or super operator (see requireAdmin).
@Summary update a RADIUS profile @Tags RadiusProfile @Param id path int true "Profile ID" @Param profile body ProfileRequest true "Profile information" @Success 200 {object} domain.RadiusProfile @Router /api/v1/radius-profiles/{id} [put]
Types ¶
type DashboardAuthTrendPoint ¶
type DashboardAuthTrendPoint struct {
Date string `json:"date"` // Date label formatted as YYYY-MM-DD
Count int64 `json:"count"` // Authentication count for the day
}
DashboardAuthTrendPoint is one day in the dashboard's 7-day authentication trend: the per-day count of accounting sessions started on that date.
type DashboardIPv6Stats ¶ added in v9.1.0
type DashboardIPv6Stats struct {
OnlineWithIPv6 int64 `json:"online_with_ipv6"` // Online sessions carrying any IPv6 attribute
OnlineWithIPv6Address int64 `json:"online_with_ipv6_address"` // Online sessions with a Framed-IPv6-Address
OnlineWithFramedPrefix int64 `json:"online_with_framed_prefix"` // Online sessions with a Framed-IPv6-Prefix
OnlineWithDelegatedPrefix int64 `json:"online_with_delegated_prefix"` // Online sessions with a Delegated-IPv6-Prefix
UsersWithStaticAddress int64 `json:"users_with_static_address"` // Users provisioned with a static IPv6 address (RadiusUser.IpV6Addr)
UsersWithDelegatedPrefix int64 `json:"users_with_delegated_prefix"` // Users provisioned with a static Delegated-IPv6-Prefix (RFC 4818)
AdoptionRate float64 `json:"adoption_rate"` // Percentage of online sessions carrying any IPv6 attribute
}
DashboardIPv6Stats summarizes IPv6 usage along two dimensions: live adoption across currently online sessions, and static provisioning across the user base. Together they let operators answer both "who is using IPv6 right now?" and "how many subscribers are provisioned for IPv6?" without inspecting the database manually.
type DashboardProfileSlice ¶
type DashboardProfileSlice struct {
ProfileID int64 `json:"profile_id"` // Profile primary key
ProfileName string `json:"profile_name"` // Profile display name
Value int64 `json:"value"` // Online users assigned to the profile
}
DashboardProfileSlice is one slice of the online-user-by-profile distribution: the profile identified by ProfileID/ProfileName and the number of currently online users assigned to it.
type DashboardStats ¶
type DashboardStats struct {
TotalUsers int64 `json:"total_users"` // Total number of users
OnlineUsers int64 `json:"online_users"` // Currently online users
TodayAuthCount int64 `json:"today_auth_count"` // Authentication count for today
TodayAcctCount int64 `json:"today_acct_count"` // Accounting record count for today
TotalProfiles int64 `json:"total_profiles"` // Total number of profiles
DisabledUsers int64 `json:"disabled_users"` // Disabled users
ExpiredUsers int64 `json:"expired_users"` // Expired users
TodayInputGB float64 `json:"today_input_gb"` // Today's upstream traffic (GB)
TodayOutputGB float64 `json:"today_output_gb"` // Today's downstream traffic (GB)
AuthTrend []DashboardAuthTrendPoint `json:"auth_trend"` // Daily authentication trend (last 7 days)
Traffic24h []DashboardTrafficPoint `json:"traffic_24h"` // Hourly traffic statistics (last 24 hours)
ProfileDistribution []DashboardProfileSlice `json:"profile_distribution"` // Online users grouped by profile
IPv6Stats DashboardIPv6Stats `json:"ipv6_stats"` // IPv6 adoption among currently online sessions
}
DashboardStats is the aggregated snapshot returned by GetDashboardStats. It combines real-time counters, today's volumes, and several time series so the admin dashboard can render its entire overview from a single response.
type DashboardTrafficPoint ¶
type DashboardTrafficPoint struct {
Hour string `json:"hour"` // Hour label formatted as YYYY-MM-DD HH:00
UploadGB float64 `json:"upload_gb"` // Upload traffic in GB within the hour
DownloadGB float64 `json:"download_gb"` // Download traffic in GB within the hour
}
DashboardTrafficPoint is one hour in the dashboard's 24-hour traffic series, holding the upstream and downstream totals (GB) for sessions started in that hour.
type ErrorResponse ¶
type ErrorResponse struct {
Error string `json:"error"`
Message string `json:"message"`
Details interface{} `json:"details,omitempty"`
}
ErrorResponse is the unified failure envelope returned by admin API handlers. Error is a stable, machine-readable code (for example "NOT_FOUND" or "VALIDATION_ERROR") that clients may branch on; Message is a human-readable explanation; Details carries optional structured context and is omitted for server-side (5xx) failures so internal information is never leaked to clients.
type ImportUserError ¶ added in v9.1.0
type ImportUserError struct {
Row int `json:"row"`
Username string `json:"username"`
Message string `json:"message"`
}
ImportUserError describes one failed input row during a batch user import.
Row is 1-based within the parsed payload and Username may be empty when the failure occurs before a username can be resolved (for example missing column).
type ImportUserResult ¶ added in v9.1.0
type ImportUserResult struct {
Total int `json:"total"`
Success int `json:"success"`
Failed int `json:"failed"`
Errors []ImportUserError `json:"errors"`
}
ImportUserResult summarizes the outcome of a batch user import request.
Success and Failed are per-row counts and Errors carries only failed rows so callers can present actionable feedback without re-reading the source file.
type Meta ¶
type Meta struct {
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"pageSize"`
}
Meta carries pagination information for a list response: the total number of matching rows across all pages, the 1-based page index, and the page size that were applied to the query.
type ProfileRequest ¶
type ProfileRequest struct {
Name string `json:"name" validate:"required,min=1,max=100"`
Status interface{} `json:"status"` // Can be string or boolean
AddrPool string `json:"addr_pool" validate:"omitempty,addrpool"`
ActiveNum int `json:"active_num" validate:"gte=0,lte=100"`
UpRate int `json:"up_rate" validate:"gte=0,lte=10000000"`
DownRate int `json:"down_rate" validate:"gte=0,lte=10000000"`
Domain string `json:"domain" validate:"omitempty,max=50"`
IPv6PrefixPool string `json:"ipv6_prefix_pool" validate:"omitempty"`
DelegatedIpv6PrefixPool string `json:"delegated_ipv6_prefix_pool" validate:"omitempty,max=100"` // DHCPv6-PD pool (RFC 6911 §2.4)
BindMac interface{} `json:"bind_mac"` // Can be int or boolean
BindVlan interface{} `json:"bind_vlan"` // Can be int or boolean
Remark string `json:"remark" validate:"omitempty,max=500"`
NodeId interface{} `json:"node_id"` // Can be int64 or string
}
ProfileRequest is the JSON body accepted by CreateProfile. Several fields are typed as interface{} because the frontend may send them as either a string or a boolean/number: Status accepts a string or a boolean (true -> "enabled", false -> "disabled"); BindMac and BindVlan accept a boolean or a number (true -> 1, false -> 0); and NodeId accepts a number or a numeric string. toRadiusProfile normalizes these into a domain.RadiusProfile. The struct tags drive request validation.
type ProfileUpdateRequest ¶
type ProfileUpdateRequest struct {
Name string `json:"name" validate:"omitempty,min=1,max=100"`
Status interface{} `json:"status"` // Can be string or boolean
AddrPool string `json:"addr_pool" validate:"omitempty,addrpool"`
ActiveNum int `json:"active_num" validate:"gte=0,lte=100"`
UpRate int `json:"up_rate" validate:"gte=0,lte=10000000"`
DownRate int `json:"down_rate" validate:"gte=0,lte=10000000"`
Domain string `json:"domain" validate:"omitempty,max=50"`
IPv6PrefixPool string `json:"ipv6_prefix_pool" validate:"omitempty"`
DelegatedIpv6PrefixPool string `json:"delegated_ipv6_prefix_pool" validate:"omitempty,max=100"` // DHCPv6-PD pool (RFC 6911 §2.4)
BindMac interface{} `json:"bind_mac"` // Can be int or boolean
BindVlan interface{} `json:"bind_vlan"` // Can be int or boolean
Remark string `json:"remark" validate:"omitempty,max=500"`
NodeId interface{} `json:"node_id"` // Can be int64 or string
}
ProfileUpdateRequest is the JSON body accepted by UpdateProfile. It mirrors ProfileRequest but treats every field as optional: an empty Name leaves the stored name unchanged, and UpdateProfile applies only the supplied fields. The same mixed-type coercion rules apply, performed by toRadiusProfile.
type Response ¶
type Response struct {
Data interface{} `json:"data,omitempty"`
Meta *Meta `json:"meta,omitempty"`
}
Response is the unified success envelope returned by admin API handlers. Data holds the payload (an object or a slice) and is omitted when nil; Meta is present only for paginated list responses. Handlers populate it through the internal ok and paged helpers rather than constructing it directly.
type SystemBackup ¶ added in v9.1.0
type SystemBackup struct {
// Version is the backup schema version in "major.minor" form.
Version string `json:"version"`
// CreatedAt is the server timestamp when the backup was generated.
CreatedAt time.Time `json:"created_at"`
// Nodes stores exported network node definitions.
Nodes []domain.NetNode `json:"nodes"`
// Nas stores exported NAS device records.
Nas []domain.NetNas `json:"nas"`
// Profiles stores exported RADIUS profile definitions.
Profiles []domain.RadiusProfile `json:"profiles"`
// Users stores exported RADIUS user records.
Users []domain.RadiusUser `json:"users"`
// Configs stores exported dynamic system configuration items.
Configs []domain.SysConfig `json:"configs"`
// Operators stores exported admin operator accounts.
Operators []domain.SysOpr `json:"operators"`
// Certs stores exported managed certificates (sys_cert), including their
// PEM private keys, so certificate-based EAP (EAP-TLS/PEAP/TTLS) keeps
// working after a restore. Restores older backups without this field
// simply skip the table.
Certs []SystemBackupCert `json:"certs,omitempty"`
}
SystemBackup is the on-disk JSON snapshot exchanged by backupSystem and restoreSystem.
The payload is versioned by Version and contains the core configuration tables that define runtime behavior (nodes, NAS, profiles, users, configs, operators, and managed certificates). The snapshot intentionally preserves primary keys so restore can upsert records deterministically.
Sensitive data notice: the payload includes security-relevant credentials (for example RadiusUser passwords, SysOpr password hashes, and SysCert private keys). Callers must treat serialized backups as secrets at rest and in transit.
type SystemBackupCert ¶ added in v9.2.0
type SystemBackupCert struct {
domain.SysCert
// PrivateKey is the PEM-encoded private key of the certificate, exported
// only inside backups.
PrivateKey string `json:"private_key,omitempty"`
}
SystemBackupCert is the backup serialization of a domain.SysCert record.
domain.SysCert deliberately hides PrivateKey from the REST API via json:"-", so marshaling SysCert directly would silently drop key material and a restored server certificate would be unusable for EAP-TLS/PEAP/TTLS. This wrapper re-exposes the private key for the access-controlled backup payload, which is already treated as a secret (it carries user passwords and operator hashes). The list/detail certificate APIs remain unaffected and never disclose the key.
type SystemRestoreResult ¶ added in v9.1.0
type SystemRestoreResult struct {
// Nodes is the number of net_node records restored.
Nodes int `json:"nodes"`
// Nas is the number of net_nas records restored.
Nas int `json:"nas"`
// Profiles is the number of radius_profile records restored.
Profiles int `json:"profiles"`
// Users is the number of radius_user records restored.
Users int `json:"users"`
// Configs is the number of sys_config records restored.
Configs int `json:"configs"`
// Operators is the number of sys_opr records restored.
Operators int `json:"operators"`
// Certs is the number of sys_cert records restored.
Certs int `json:"certs"`
}
SystemRestoreResult reports how many records restoreSystem upserted into each table during a successful restore transaction.
A zero value for a field means either the table was absent from the payload or the payload contained no records for that table.
type UserRequest ¶
type UserRequest struct {
NodeID interface{} `json:"node_id"` // Can be int64 or string
ProfileID interface{} `json:"profile_id" validate:"required"` // Can be int64 or string
LegacyProfileID interface{} `json:"profileid"` // Legacy fallback field from older clients, used when profile_id is missing
Realname string `json:"realname" validate:"omitempty,max=100"` // Real name
Email string `json:"email" validate:"omitempty,email,max=100"` // Email
Mobile string `json:"mobile" validate:"omitempty,max=20"` // Mobile number (optional, max 20 characters)
Address string `json:"address" validate:"omitempty,max=255"` // addresses
Username string `json:"username" validate:"required,min=3,max=50"` // Username
Password string `json:"password" validate:"omitempty,min=6,max=128"` // Password
AddrPool string `json:"addr_pool" validate:"omitempty,max=50"` // Address pool
Vlanid1 int `json:"vlanid1" validate:"gte=0,lte=4096"` // VLAN ID 1
Vlanid2 int `json:"vlanid2" validate:"gte=0,lte=4096"` // VLAN ID 2
IpAddr string `json:"ip_addr" validate:"omitempty,ipv4"` // IPv4addresses
Ipv6Addr string `json:"ipv6_addr" validate:"omitempty"` // IPv6addresses
MacAddr string `json:"mac_addr" validate:"omitempty,mac"` // MACaddresses
BindVlan interface{} `json:"bind_vlan"` // Can be int or boolean
BindMac interface{} `json:"bind_mac"` // Can be int or boolean
ExpireTime string `json:"expire_time" validate:"omitempty"` // Expiration time
Status interface{} `json:"status"` // Can be string or boolean
Remark string `json:"remark" validate:"omitempty,max=500"` // Remark
}
UserRequest represents the request body for creating a RADIUS user via POST /api/v1/users.
It preserves compatibility with older clients that still send legacy keys such as profileid, or mixed JSON types for flags (for example bool vs number for bind_mac/bind_vlan and status). The handler normalizes those variants before persisting the user.
type UserUpdateRequest ¶
type UserUpdateRequest struct {
NodeID interface{} `json:"node_id"` // Can be int64 or string
ProfileID interface{} `json:"profile_id"` // Can be int64 or string
LegacyProfileID interface{} `json:"profileid"` // Legacy fallback field from older clients, used when profile_id is missing
Realname string `json:"realname" validate:"omitempty,max=100"` // Real name
Email string `json:"email" validate:"omitempty,email,max=100"` // Email
Mobile string `json:"mobile" validate:"omitempty,max=20"` // Mobile number (optional, max 20 characters)
Address string `json:"address" validate:"omitempty,max=255"` // addresses
Username string `json:"username" validate:"omitempty,min=3,max=50"` // Username
Password string `json:"password" validate:"omitempty,min=6,max=128"` // Password
AddrPool string `json:"addr_pool" validate:"omitempty,max=50"` // Address pool
Vlanid1 int `json:"vlanid1" validate:"gte=0,lte=4096"` // VLAN ID 1
Vlanid2 int `json:"vlanid2" validate:"gte=0,lte=4096"` // VLAN ID 2
IpAddr string `json:"ip_addr" validate:"omitempty,ipv4"` // IPv4addresses
Ipv6Addr string `json:"ipv6_addr" validate:"omitempty"` // IPv6addresses
MacAddr string `json:"mac_addr" validate:"omitempty,mac"` // MACaddresses
BindVlan interface{} `json:"bind_vlan"` // Can be int or boolean
BindMac interface{} `json:"bind_mac"` // Can be int or boolean
ExpireTime string `json:"expire_time" validate:"omitempty"` // Expiration time
Status interface{} `json:"status"` // Can be string or boolean
Remark string `json:"remark" validate:"omitempty,max=500"` // Remark
IPv6PrefixPool string `json:"ipv6_prefix_pool" validate:"omitempty,max=100"` // IPv6 prefix pool name
DelegatedIpv6Prefix string `json:"delegated_ipv6_prefix" validate:"omitempty,max=100"` // Static Delegated-IPv6-Prefix (RFC 4818)
DelegatedIpv6PrefixPool string `json:"delegated_ipv6_prefix_pool" validate:"omitempty,max=100"` // Delegated-IPv6-Prefix-Pool (RFC 6911 §2.4)
Domain string `json:"domain" validate:"omitempty,max=100"` // User domain
ProfileLinkMode int `json:"profile_link_mode" validate:"gte=0,lte=1"` // Profile link mode (0=static, 1=dynamic)
}
UserUpdateRequest represents the request body for updating a RADIUS user via PUT /api/v1/users/:id.
Most fields are optional and are applied as partial updates. As with UserRequest, legacy and mixed-type frontend payloads are accepted and normalized so older admin clients remain compatible.