web

package
v1.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 1, 2026 License: AGPL-3.0 Imports: 53 Imported by: 0

Documentation

Overview

Dev build: the static Vue frontend is read straight from disk (relative to the process's working directory), so rerunning `npm run build` takes effect without a Go rebuild.

Web backend for factum

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AuthenticateUser

func AuthenticateUser(db *gorm.DB, username string, password string) (*models.User, error)

AuthenticateUser checks username and password, trying local auth first and falling back to LDAP/AD per the rules below. Returns the user object on success, (nil, nil) on any kind of "bad credentials" (mirrors the pre-LDAP behavior other callers already depend on), and (nil, err) only for a real infrastructure problem (DB error, or LDAP server unreachable/misconfigured).

  1. Local user found, PasswordHash == ldapSentinelPassword: authenticate against LDAP/AD instead of bcrypt (authenticateExistingLDAPUser).
  2. Local user found, PasswordHash is a normal bcrypt hash: unchanged bcrypt check.
  3. No local user found: try LDAP/AD; on success, auto-provision a local user with PasswordHash = ldapSentinelPassword so subsequent logins take path 1 (authenticateNewLDAPUser).

func CheckPasswordHash

func CheckPasswordHash(password, hash string) bool

CheckPasswordHash compares a plain text password with a stored hash Returns true if they are the same

func CreateAdmin

func CreateAdmin(p *GuiParams) error

Function to seed initial data (admin user, roles)

func GUI

func GUI(p *GuiParams) error

----- GUI -----

func GenerateJWT

func GenerateJWT(userID uint) (string, error)

func GetCurrentUser

func GetCurrentUser(DB *gorm.DB, c *echo.Context) *models.User

GetCurrentUser retrieves the authenticated user from the context. Returns nil if the user is not authenticated or not found in the context.

func GetUserByID

func GetUserByID(db *gorm.DB, userID uint) (*models.User, error)

GetUserByID fetches a user by ID, preloading roles

func HashPassword

func HashPassword(password string) (string, error)

HashPassword generates a bcrypt hash of the password

Types

type ApiServiceElinePushResult

type ApiServiceElinePushResult struct {
	Device string `json:"device"`
	Error  string `json:"error,omitempty"`
}

ApiServiceElinePushResult is one endpoint device's outcome, returned per-device since a cross-device push can partially fail with no way to roll back the other side.

type ApiWorkerRunRequest

type ApiWorkerRunRequest struct {
	Command string   `json:"command"`
	Args    []string `json:"args,omitempty"`
}

ApiWorkerRunRequest is the body "factum2-worker run" posts - see internal/worker/run_client.go's RunRemote, its client-side counterpart.

type Controller

type Controller struct {
	DB            *gorm.DB
	LogHub        *LogHub
	RemoteManager *worker.RemoteManager
}

func (*Controller) APIServiceList

func (ctrl *Controller) APIServiceList(c *echo.Context) error

func (*Controller) ApiCommonConfig

func (ctrl *Controller) ApiCommonConfig(c *echo.Context) error

ApiCommonConfig returns util.CommonConfig - settings shared by every remote-config-fetching CLI tool, not tied to any one service. Tools that don't need any service-specific config (currently just factum2-worker agents, via internal/worker.FetchRemoteConfig) fetch this directly instead of one of the per-service endpoints (ApiDNSConfig, ApiIcingaConfig, etc.), which already embed the same util.CommonConfig for tools that do need service-specific config too.

func (*Controller) ApiConfigAssignmentDelete added in v1.0.2

func (ctrl *Controller) ApiConfigAssignmentDelete(c *echo.Context) error

func (*Controller) ApiConfigAssignmentList added in v1.0.2

func (ctrl *Controller) ApiConfigAssignmentList(c *echo.Context) error

func (*Controller) ApiConfigAssignmentUpsert added in v1.0.2

func (ctrl *Controller) ApiConfigAssignmentUpsert(c *echo.Context) error

func (*Controller) ApiConfigMacroCreate added in v1.0.2

func (ctrl *Controller) ApiConfigMacroCreate(c *echo.Context) error

func (*Controller) ApiConfigMacroDelete added in v1.0.2

func (ctrl *Controller) ApiConfigMacroDelete(c *echo.Context) error

func (*Controller) ApiConfigMacroGet added in v1.0.2

func (ctrl *Controller) ApiConfigMacroGet(c *echo.Context) error

func (*Controller) ApiConfigMacroList added in v1.0.2

func (ctrl *Controller) ApiConfigMacroList(c *echo.Context) error

func (*Controller) ApiConfigMacroUpdate added in v1.0.2

func (ctrl *Controller) ApiConfigMacroUpdate(c *echo.Context) error

func (*Controller) ApiConfigMatrix added in v1.0.2

func (ctrl *Controller) ApiConfigMatrix(c *echo.Context) error

func (*Controller) ApiConfigPlatformPackCreate added in v1.0.2

func (ctrl *Controller) ApiConfigPlatformPackCreate(c *echo.Context) error

func (*Controller) ApiConfigPlatformPackDelete added in v1.0.2

func (ctrl *Controller) ApiConfigPlatformPackDelete(c *echo.Context) error

func (*Controller) ApiConfigPlatformPackGet added in v1.0.2

func (ctrl *Controller) ApiConfigPlatformPackGet(c *echo.Context) error

func (*Controller) ApiConfigPlatformPackList added in v1.0.2

func (ctrl *Controller) ApiConfigPlatformPackList(c *echo.Context) error

func (*Controller) ApiConfigPlatformPackUpdate added in v1.0.2

func (ctrl *Controller) ApiConfigPlatformPackUpdate(c *echo.Context) error

func (*Controller) ApiConfigRender added in v1.0.2

func (ctrl *Controller) ApiConfigRender(c *echo.Context) error

func (*Controller) ApiConfigResolve added in v1.0.2

func (ctrl *Controller) ApiConfigResolve(c *echo.Context) error

func (*Controller) ApiConfigScopeCreate added in v1.0.2

func (ctrl *Controller) ApiConfigScopeCreate(c *echo.Context) error

func (*Controller) ApiConfigScopeDelete added in v1.0.2

func (ctrl *Controller) ApiConfigScopeDelete(c *echo.Context) error

func (*Controller) ApiConfigScopeList added in v1.0.2

func (ctrl *Controller) ApiConfigScopeList(c *echo.Context) error

func (*Controller) ApiConfigScopeTree added in v1.0.2

func (ctrl *Controller) ApiConfigScopeTree(c *echo.Context) error

func (*Controller) ApiConfigScopeUpdate added in v1.0.2

func (ctrl *Controller) ApiConfigScopeUpdate(c *echo.Context) error

func (*Controller) ApiConfigServiceTypeCreate added in v1.0.2

func (ctrl *Controller) ApiConfigServiceTypeCreate(c *echo.Context) error

func (*Controller) ApiConfigServiceTypeDelete added in v1.0.2

func (ctrl *Controller) ApiConfigServiceTypeDelete(c *echo.Context) error

func (*Controller) ApiConfigServiceTypeGet added in v1.0.2

func (ctrl *Controller) ApiConfigServiceTypeGet(c *echo.Context) error

func (*Controller) ApiConfigServiceTypeList added in v1.0.2

func (ctrl *Controller) ApiConfigServiceTypeList(c *echo.Context) error

func (*Controller) ApiConfigServiceTypeUpdate added in v1.0.2

func (ctrl *Controller) ApiConfigServiceTypeUpdate(c *echo.Context) error

func (*Controller) ApiConfigTemplateCreate added in v1.0.2

func (ctrl *Controller) ApiConfigTemplateCreate(c *echo.Context) error

func (*Controller) ApiConfigTemplateDelete added in v1.0.2

func (ctrl *Controller) ApiConfigTemplateDelete(c *echo.Context) error

func (*Controller) ApiConfigTemplateGet added in v1.0.2

func (ctrl *Controller) ApiConfigTemplateGet(c *echo.Context) error

func (*Controller) ApiConfigTemplateList added in v1.0.2

func (ctrl *Controller) ApiConfigTemplateList(c *echo.Context) error

func (*Controller) ApiConfigTemplateUpdate added in v1.0.2

func (ctrl *Controller) ApiConfigTemplateUpdate(c *echo.Context) error

func (*Controller) ApiConfigVariableCreate added in v1.0.2

func (ctrl *Controller) ApiConfigVariableCreate(c *echo.Context) error

func (*Controller) ApiConfigVariableDelete added in v1.0.2

func (ctrl *Controller) ApiConfigVariableDelete(c *echo.Context) error

func (*Controller) ApiConfigVariableGet added in v1.0.2

func (ctrl *Controller) ApiConfigVariableGet(c *echo.Context) error

func (*Controller) ApiConfigVariableList added in v1.0.2

func (ctrl *Controller) ApiConfigVariableList(c *echo.Context) error

func (*Controller) ApiConfigVariableUpdate added in v1.0.2

func (ctrl *Controller) ApiConfigVariableUpdate(c *echo.Context) error

func (*Controller) ApiContact

func (ctrl *Controller) ApiContact(c *echo.Context) error

Fetch all contacts, with related company names attached for the list view.

func (*Controller) ApiContactByID

func (ctrl *Controller) ApiContactByID(c *echo.Context) error

func (*Controller) ApiContactCustomers

func (ctrl *Controller) ApiContactCustomers(c *echo.Context) error

func (*Controller) ApiContactCustomersPut

func (ctrl *Controller) ApiContactCustomersPut(c *echo.Context) error

func (*Controller) ApiContactDelete

func (ctrl *Controller) ApiContactDelete(c *echo.Context) error

ApiContactDelete removes a locally-created contact. Lime-synced rows are rejected (they'd just reappear on the next person-sync). CustomerContact join rows are not independent records, so they're removed in the same transaction rather than treated as a blocker. MaintenanceNotification rows keep the sent email and have ContactID cleared so the send log is not deleted with the person.

func (*Controller) ApiContactUpdate

func (ctrl *Controller) ApiContactUpdate(contacts *SecureCRUDHandler[models.Contact, models.ContactDTO]) echo.HandlerFunc

ApiContactUpdate rejects Lime-owned field edits on contacts synced from Lime (name/email/phone get overwritten by the next person-sync in internal/lime/lime.go). NotifyMaintenance is factum-owned, so a Lime row may still toggle that flag - same idea as ApiServiceTypeUpdate on a Lime-sourced service. Everything else delegates to the generic CRUD handler.

func (*Controller) ApiCustomer

func (ctrl *Controller) ApiCustomer(c *echo.Context) error

Fetch all companies, with a per-customer service count attached for the list view.

func (*Controller) ApiCustomerByID

func (ctrl *Controller) ApiCustomerByID(c *echo.Context) error

func (*Controller) ApiCustomerContacts

func (ctrl *Controller) ApiCustomerContacts(c *echo.Context) error

func (*Controller) ApiCustomerDelete

func (ctrl *Controller) ApiCustomerDelete(c *echo.Context) error

ApiCustomerDelete removes a locally-created customer. Lime-synced rows are rejected the same way as updates (they'd just reappear on the next Lime sync). A customer that still has services is also rejected: those rows would be left pointing at a missing company. CustomerContact join rows are not independent records, so they're removed in the same transaction rather than treated as a blocker.

func (*Controller) ApiCustomerUpdate

func (ctrl *Controller) ApiCustomerUpdate(customers *SecureCRUDHandler[models.Customer, models.CustomerDTO]) echo.HandlerFunc

ApiCustomerUpdate rejects edits to customers synced from Lime (the existing row's Source == "lime") before delegating to the generic CRUD handler for everything else. Lime-sourced fields get overwritten wholesale on the next sync run (SaveCustomer in internal/lime/lime.go), so letting an edit through the API would just have it silently discarded on the next sync - better to reject it upfront than have an edit mysteriously disappear.

func (*Controller) ApiDNSConfig

func (ctrl *Controller) ApiDNSConfig(c *echo.Context) error

ApiDNSConfig returns the DNS sync settings from the database-backed Settings row, so factum2-dns - which typically runs on a different host than the primary - doesn't need a direct Postgres connection just to read these.

func (*Controller) ApiDeviceImpact

func (ctrl *Controller) ApiDeviceImpact(c *echo.Context) error

func (*Controller) ApiDeviceInterfacesRefresh

func (ctrl *Controller) ApiDeviceInterfacesRefresh(c *echo.Context) error

ApiDeviceInterfacesRefresh fetches the live interface descriptions from the device itself (via the EOS driver) and overwrites both Netbox's and factum's stored interface descriptions with what the device reports.

func (*Controller) ApiDeviceInterfacesUpdate

func (ctrl *Controller) ApiDeviceInterfacesUpdate(c *echo.Context) error

ApiDeviceInterfacesUpdate pushes edited interface descriptions out to the device itself (via the EOS driver), Netbox, and factum's own interface table.

func (*Controller) ApiDeviceInterfacesUpdateVlans

func (ctrl *Controller) ApiDeviceInterfacesUpdateVlans(c *echo.Context) error

ApiDeviceInterfacesUpdateVlans pushes edited switchport/VLAN config out to the device itself (EOS/VRP drivers only - see globalVlanPlatforms) and Netbox (creating any new VLAN in Settings.DeviceSyncVlanGroupName's Netbox VLAN group as needed, via the same NetboxMgr internal/device-sync uses for its own VLAN sync), then refreshes factum's own interface/VLAN cache for the device by running the single-device netbox.SyncDB used by the Netbox webhook. Unlike ApiDeviceInterfacesUpdate's description path, this fails the whole request if any part of it fails - there's no useful "partially applied" VLAN edit to leave in place.

func (*Controller) ApiDeviceOpticalPorts

func (ctrl *Controller) ApiDeviceOpticalPorts(c *echo.Context) error

func (*Controller) ApiDeviceSyncAuthCreate

func (ctrl *Controller) ApiDeviceSyncAuthCreate(c *echo.Context) error

ApiDeviceSyncAuthCreate/ApiDeviceSyncAuthUpdate replace the generic SecureCRUDHandler for DeviceSyncAuth's write side: Password is json:"-" on the model (so GetAll/GetOne never leak it), but that same tag makes it invisible to SecureCRUDHandler's DTO<->model JSON round-trip in both directions - same reasoning as ApiWorkerNodeCreate/Update for WorkerNode.Token.

func (*Controller) ApiDeviceSyncAuthPassword

func (ctrl *Controller) ApiDeviceSyncAuthPassword(c *echo.Context) error

ApiDeviceSyncAuthToken returns one DeviceSyncAuth row's stored password. Kept separate from GetAll/GetOne (models.DeviceSyncAuth.Password is json:"-", so neither ever includes it) so a bulk list/detail fetch never carries secrets - used to prefill the admin UI's password field so its own eye icon can show it, same pattern as ApiWorkerNodeToken.

func (*Controller) ApiDeviceSyncAuthUpdate

func (ctrl *Controller) ApiDeviceSyncAuthUpdate(c *echo.Context) error

func (*Controller) ApiDeviceSyncConfig

func (ctrl *Controller) ApiDeviceSyncConfig(c *echo.Context) error

ApiDeviceSyncConfig returns everything factum2-device-sync-cli needs to run: the default domain and VRFInGlobal/DeviceStates/DeviceIgnore lists from the database-backed Settings row, plus per-device credentials from the models.DeviceSyncAuth table - so factum2-device-sync-cli, which typically runs on a host with network access to the devices rather than the primary, doesn't need any local device-sync config or DB access of its own. The Netbox client itself isn't served here - that's /api/netbox-config (web.ApiNetboxConfig), which device-sync fetches separately.

func (*Controller) ApiForgotPassword

func (ctrl *Controller) ApiForgotPassword(c *echo.Context) error

ApiForgotPassword always responds 200 {"ok":true} regardless of whether the email matches an account, whether that account can actually be reset, or whether sending the email succeeded - anything else would let a caller enumerate valid accounts by probing this endpoint.

func (*Controller) ApiGetDeviceByID

func (ctrl *Controller) ApiGetDeviceByID(c *echo.Context) error

func (*Controller) ApiGetDeviceByName

func (ctrl *Controller) ApiGetDeviceByName(c *echo.Context) error

ApiGetDeviceByName returns the device with a given name as a one-element array (empty if there's no such device), not as a bare object - the same shape as /api/device, so internal/factum.FactumClient parses both with the same []*models.Device. Used by tools that only know a device by name and have no access to the primary's Postgres (currently factum2-driver-cli, via internal/drivers.NewDriverName).

func (*Controller) ApiGetDevices

func (ctrl *Controller) ApiGetDevices(c *echo.Context) error

Fetch all devices. Default is the shallow list (no nested interfaces/addresses) used by the device-list UI. ?include=interfaces loads the full snapshot instead - DNS sync (internal/factum.GetDevicesWithInterfaces) needs per-interface addresses and cannot use the shallow list.

func (*Controller) ApiGetTopology

func (ctrl *Controller) ApiGetTopology(c *echo.Context) error

ApiGetTopology returns every mappable device and the connections between them, for the 3D network map view.

func (*Controller) ApiIcingaConfig

func (ctrl *Controller) ApiIcingaConfig(c *echo.Context) error

ApiIcingaConfig returns the Icinga API connection settings from the database-backed Settings row, so factum2-icinga - which typically runs on a different host than the primary - doesn't need its own copy of these credentials in a local YAML config file.

func (*Controller) ApiIpamForest

func (ctrl *Controller) ApiIpamForest(c *echo.Context) error

ApiIpamForest is the single-tree API: no parent → namespaces; parent is a node key (ns:1, vrf:2, pool:3, pfx:4) → that node's children.

func (*Controller) ApiIpamNamespaceCreate

func (ctrl *Controller) ApiIpamNamespaceCreate(c *echo.Context) error

func (*Controller) ApiIpamNamespaceDelete

func (ctrl *Controller) ApiIpamNamespaceDelete(c *echo.Context) error

func (*Controller) ApiIpamNamespaceGet

func (ctrl *Controller) ApiIpamNamespaceGet(c *echo.Context) error

func (*Controller) ApiIpamNamespaceList

func (ctrl *Controller) ApiIpamNamespaceList(c *echo.Context) error

func (*Controller) ApiIpamNamespaceUpdate

func (ctrl *Controller) ApiIpamNamespaceUpdate(c *echo.Context) error

func (*Controller) ApiIpamPoolCreate

func (ctrl *Controller) ApiIpamPoolCreate(c *echo.Context) error

func (*Controller) ApiIpamPoolDelete

func (ctrl *Controller) ApiIpamPoolDelete(c *echo.Context) error

func (*Controller) ApiIpamPoolList

func (ctrl *Controller) ApiIpamPoolList(c *echo.Context) error

func (*Controller) ApiIpamPrefixCreate

func (ctrl *Controller) ApiIpamPrefixCreate(c *echo.Context) error

func (*Controller) ApiIpamPrefixDelete

func (ctrl *Controller) ApiIpamPrefixDelete(c *echo.Context) error

func (*Controller) ApiIpamPrefixList

func (ctrl *Controller) ApiIpamPrefixList(c *echo.Context) error

func (*Controller) ApiIpamPrefixUpdate

func (ctrl *Controller) ApiIpamPrefixUpdate(c *echo.Context) error

func (*Controller) ApiIpamTree

func (ctrl *Controller) ApiIpamTree(c *echo.Context) error

func (*Controller) ApiIpamVRFCreate

func (ctrl *Controller) ApiIpamVRFCreate(c *echo.Context) error

func (*Controller) ApiIpamVRFDelete

func (ctrl *Controller) ApiIpamVRFDelete(c *echo.Context) error

func (*Controller) ApiIpamVRFList

func (ctrl *Controller) ApiIpamVRFList(c *echo.Context) error

func (*Controller) ApiIpamVRFUpdate

func (ctrl *Controller) ApiIpamVRFUpdate(c *echo.Context) error

func (*Controller) ApiJobTaskEvents

func (ctrl *Controller) ApiJobTaskEvents(c *echo.Context) error

ApiJobTaskEvents lists a single JobTask's JobTaskEvent rows in chronological order - the job-history table's per-subjob drill-in view.

func (*Controller) ApiJobs

func (ctrl *Controller) ApiJobs(c *echo.Context) error

ApiJobs lists the most recent Job rows, newest first, with their JobTasks preloaded in the same query (no N+1 when the frontend opens a job's detail modal) - the job status page's job-history table.

func (*Controller) ApiLdapBrowse

func (ctrl *Controller) ApiLdapBrowse(c *echo.Context) error

ApiLdapBrowse lists the immediate children of the ?dn= query param (or the configured LdapBaseDN if omitted), for the "browse the directory" picker on the LDAP/AD group -> role mapping dialog. Uses the saved Settings connection details (unlike ApiLdapTestConnection, which tests possibly- unsaved form values) since browsing only makes sense once LDAP is actually configured.

func (*Controller) ApiLdapTestConnection

func (ctrl *Controller) ApiLdapTestConnection(c *echo.Context) error

ApiLdapTestConnection dials+binds with the submitted (or saved) settings and reports ok/error as a 200 JSON body rather than a 4xx/5xx status - a failed directory bind isn't an application error, it's the answer to the question the admin is asking. Each configured server is tested on its own (no failover) so a broken secondary isn't hidden by a working primary. `ok` is true only if every configured server succeeded.

func (*Controller) ApiLibrenmsConfig

func (ctrl *Controller) ApiLibrenmsConfig(c *echo.Context) error

ApiLibrenmsConfig returns everything factum2-librenms-cli needs to run - the default domain, LibreNMS REST API settings, and the Sync regex-filter lists, all from the database-backed Settings row - so factum2-librenms-cli, which typically runs on a different host than the primary, doesn't need any local librenms config of its own. LibreNMS's own MySQL credentials aren't included here - factum2-librenms-cli reads those directly from LibreNMS's .env file, since it assumes co-location with the LibreNMS server.

func (*Controller) ApiLibrenmsPendingDeleteList

func (ctrl *Controller) ApiLibrenmsPendingDeleteList(c *echo.Context) error

ApiLibrenmsPendingDeleteList is GET /api/librenms/pending-deletes - the UI's pending-deletion table and factum2-librenms-cli's delete pass.

func (*Controller) ApiLibrenmsPendingDeleteNextSync

func (ctrl *Controller) ApiLibrenmsPendingDeleteNextSync(c *echo.Context) error

ApiLibrenmsPendingDeleteNextSync is POST /api/librenms/pending-deletes/:device_id/delete-next-sync - the UI's "delete on next sync" action. The next factum2-librenms-cli run deletes the LibreNMS device even if the delay has not elapsed (and even if delayed deletion is currently disabled).

func (*Controller) ApiLibrenmsPendingDeleteRemove

func (ctrl *Controller) ApiLibrenmsPendingDeleteRemove(c *echo.Context) error

ApiLibrenmsPendingDeleteRemove is DELETE /api/librenms/pending-deletes/:device_id - factum2-librenms-cli drops the row after a real LibreNMS delete or a restore.

func (*Controller) ApiLibrenmsPendingDeleteUpsert

func (ctrl *Controller) ApiLibrenmsPendingDeleteUpsert(c *echo.Context) error

ApiLibrenmsPendingDeleteUpsert is PUT /api/librenms/pending-deletes/:device_id - factum2-librenms-cli records a newly quarantined device. An existing row keeps its ScheduledAt and ForceDelete so a later sync cannot reset the clock or clear a user's "delete next run" flag.

func (*Controller) ApiLinkCreate

func (ctrl *Controller) ApiLinkCreate(c *echo.Context) error

ApiLinkCreate replaces the generic handler's Create so a new link is always appended after the current highest Position instead of trusting a client-supplied one - drag-and-drop reordering (ApiLinksReorder) is the only path allowed to move an existing row's Position.

func (ctrl *Controller) ApiLinksList(c *echo.Context) error

ApiLinksList returns every dashboard link ordered by Position, then ID as a tiebreaker - shared by the admin CRUD page (GET /api/admin/links) and the dashboard itself (GET /api/links, any authenticated user: the list is meant to be seen by everyone, only edited by admins).

func (*Controller) ApiLinksReorder

func (ctrl *Controller) ApiLinksReorder(c *echo.Context) error

ApiLinksReorder assigns each link's Position from its index in the dropped order.

func (*Controller) ApiListRoles

func (ctrl *Controller) ApiListRoles(c *echo.Context) error

Fetch all roles

func (*Controller) ApiListUsers

func (ctrl *Controller) ApiListUsers(c *echo.Context) error

Fetch all users

func (*Controller) ApiLogin

func (ctrl *Controller) ApiLogin(c *echo.Context) error

ApiLogin authenticates the SPA: on success it issues the "token" cookie via setAuthCookie; on failure it returns a JSON error body with a 401.

func (*Controller) ApiLogout

func (ctrl *Controller) ApiLogout(c *echo.Context) error

func (*Controller) ApiLogsWebSocket

func (ctrl *Controller) ApiLogsWebSocket(c *echo.Context) error

ApiLogsWebSocket streams live slog records (published via hubHandler in logstream.go) to the frontend's log window. Gated by RequireAdmin in web.go: log lines can carry details (hostnames, internal error text) not meant for a non-admin user.

func (*Controller) ApiMaintenanceCreate

func (ctrl *Controller) ApiMaintenanceCreate(c *echo.Context) error

func (*Controller) ApiMaintenanceGet

func (ctrl *Controller) ApiMaintenanceGet(c *echo.Context) error

func (*Controller) ApiMaintenanceImpact

func (ctrl *Controller) ApiMaintenanceImpact(c *echo.Context) error

func (*Controller) ApiMaintenanceList

func (ctrl *Controller) ApiMaintenanceList(c *echo.Context) error

func (*Controller) ApiMaintenanceNotify

func (ctrl *Controller) ApiMaintenanceNotify(c *echo.Context) error

func (*Controller) ApiMaintenanceUpdate

func (ctrl *Controller) ApiMaintenanceUpdate(c *echo.Context) error

func (*Controller) ApiMeGet

func (ctrl *Controller) ApiMeGet(c *echo.Context) error

func (*Controller) ApiMeUpdate

func (ctrl *Controller) ApiMeUpdate(c *echo.Context) error

func (*Controller) ApiNetboxConfig

func (ctrl *Controller) ApiNetboxConfig(c *echo.Context) error

ApiNetboxConfig returns the Netbox API connection settings from the database-backed Settings row, so callers that can't reach the primary's Postgres DB directly - currently factum2-librenms-cli's Sync(), which typically runs on the LibreNMS host, not the primary - can fetch them over REST instead.

func (*Controller) ApiNetboxWebhook

func (ctrl *Controller) ApiNetboxWebhook(c *echo.Context) error

ApiNetboxWebhook receives change-event webhooks from Netbox.

Device / interface / IP: create/update (and interface/IP delete) re-fetches the named device and upserts it. Device delete removes the matching netbox-sourced factum row by the payload's id — GetDevice would return nil once Netbox has already removed the object.

Cable / site: create/update re-fetches that one object and upserts the Connection/Site row; delete removes it by the payload's netbox_id. These are not "resync one named device" — they have no name lookup, and a deleted cable/site cannot be re-fetched.

Tenants are not applied here: customer→tenant sync is factum→Netbox.

Netbox has no session/token auth for outgoing webhooks; instead it signs the request body with HMAC-SHA512 and sends the hex digest in "X-Hook-Signature", keyed by a shared secret configured on both sides (Settings.NetboxWebhookSecret here, the webhook's "secret" field on Netbox) - verified the same constant-time-compare way as checkServiceToken, and likewise fails closed if the secret isn't configured.

func (*Controller) ApiOpticalKindMapCreate

func (ctrl *Controller) ApiOpticalKindMapCreate(c *echo.Context) error

func (*Controller) ApiOpticalKindMapDelete

func (ctrl *Controller) ApiOpticalKindMapDelete(c *echo.Context) error

func (*Controller) ApiOpticalKindMapList

func (ctrl *Controller) ApiOpticalKindMapList(c *echo.Context) error

func (*Controller) ApiOpticalKindMapUpdate

func (ctrl *Controller) ApiOpticalKindMapUpdate(c *echo.Context) error

func (*Controller) ApiOpticalPortDelete

func (ctrl *Controller) ApiOpticalPortDelete(c *echo.Context) error

func (*Controller) ApiOpticalPortGet

func (ctrl *Controller) ApiOpticalPortGet(c *echo.Context) error

func (*Controller) ApiOpticalPortPut

func (ctrl *Controller) ApiOpticalPortPut(c *echo.Context) error

func (*Controller) ApiOpticalRetraceStale

func (ctrl *Controller) ApiOpticalRetraceStale(c *echo.Context) error

func (*Controller) ApiOpticalTrace

func (ctrl *Controller) ApiOpticalTrace(c *echo.Context) error

func (*Controller) ApiOpticalXConnectCreate

func (ctrl *Controller) ApiOpticalXConnectCreate(c *echo.Context) error

func (*Controller) ApiOpticalXConnectDelete

func (ctrl *Controller) ApiOpticalXConnectDelete(c *echo.Context) error

func (*Controller) ApiOpticalXConnectList

func (ctrl *Controller) ApiOpticalXConnectList(c *echo.Context) error

func (*Controller) ApiOxidizedConfig

func (ctrl *Controller) ApiOxidizedConfig(c *echo.Context) error

ApiOxidizedConfig returns the Oxidized API connection settings from the database-backed Settings row, so factum2-oxidized - which typically runs on a different host than the primary - doesn't need its own copy of these credentials in a local YAML config file.

func (*Controller) ApiOxidizedNodeConfig added in v1.0.3

func (ctrl *Controller) ApiOxidizedNodeConfig(c *echo.Context) error

ApiOxidizedNodeConfig is GET /api/oxidized/node/config?node_full=.

func (*Controller) ApiOxidizedNodeDiff added in v1.0.3

func (ctrl *Controller) ApiOxidizedNodeDiff(c *echo.Context) error

ApiOxidizedNodeDiff is GET /api/oxidized/node/diff?node_full=&oid=&oid2=.

func (*Controller) ApiOxidizedNodeVersion added in v1.0.3

func (ctrl *Controller) ApiOxidizedNodeVersion(c *echo.Context) error

ApiOxidizedNodeVersion is GET /api/oxidized/node/version?node_full=&oid=.

func (*Controller) ApiOxidizedNodeVersions added in v1.0.3

func (ctrl *Controller) ApiOxidizedNodeVersions(c *echo.Context) error

ApiOxidizedNodeVersions is GET /api/oxidized/node/versions?node_full=.

func (*Controller) ApiOxidizedNodes added in v1.0.3

func (ctrl *Controller) ApiOxidizedNodes(c *echo.Context) error

ApiOxidizedNodes is GET /api/oxidized/nodes - the GUI device browser. Proxies oxidized-web GET /nodes.json. Deleted devices are not included; see internal/oxidized/api.go.

func (*Controller) ApiPrometheusConfig added in v1.0.3

func (ctrl *Controller) ApiPrometheusConfig(c *echo.Context) error

ApiPrometheusConfig returns the Prometheus/snmp_exporter sync settings from the database-backed Settings row, so factum2-prometheus - which typically runs on a different host than the primary - doesn't need its own copy of these in a local YAML config file.

func (*Controller) ApiResetPassword

func (ctrl *Controller) ApiResetPassword(c *echo.Context) error

func (*Controller) ApiScheduleCreate

func (ctrl *Controller) ApiScheduleCreate(c *echo.Context) error

ApiScheduleCreate inserts a JobSchedule and, if enabled, stamps NextRunAt from the cron expression so the in-process scheduler can pick it up without waiting for an edit.

func (*Controller) ApiScheduleDelete

func (ctrl *Controller) ApiScheduleDelete(c *echo.Context) error

ApiScheduleDelete removes a JobSchedule. In-flight jobs it already started keep running - the row is only the trigger, not the Job.

func (*Controller) ApiScheduleGet

func (ctrl *Controller) ApiScheduleGet(c *echo.Context) error

ApiScheduleGet returns one JobSchedule by ID.

func (*Controller) ApiScheduleList

func (ctrl *Controller) ApiScheduleList(c *echo.Context) error

ApiScheduleList returns every JobSchedule, enabled first then by name - the Scheduler page's table.

func (*Controller) ApiScheduleUpdate

func (ctrl *Controller) ApiScheduleUpdate(c *echo.Context) error

ApiScheduleUpdate replaces the writable fields and recomputes NextRunAt from now (or clears it when disabled). LastRunAt/LastError/CreatedBy stay as the scheduler last left them.

func (*Controller) ApiServiceByID

func (ctrl *Controller) ApiServiceByID(c *echo.Context) error

func (*Controller) ApiServiceCreate

func (ctrl *Controller) ApiServiceCreate(c *echo.Context) error

ApiServiceCreate creates a service in a single step - the create wizard no longer reserves a service ID before the rest of the form is filled in. If the caller leaves ServiceID blank, the next available <category><5-digit> number for Category is auto-assigned (derived from existing rows, max + 1); otherwise the caller-supplied ID is used as-is after checking it isn't already taken.

func (*Controller) ApiServiceDelete

func (ctrl *Controller) ApiServiceDelete(services *SecureCRUDHandler[models.Service, models.ServiceDTO]) echo.HandlerFunc

ApiServiceDelete mirrors ApiServiceUpdate's Lime-source guard, then - unlike a plain generic-CRUD delete - optionally tears down an ELINE service's NetBox/device state first (removeELINEServiceFromDevices/ removeELINEServiceFromNetbox, web/handler_service_eline.go) before hard-deleting the row. Any requested cleanup step failing aborts the whole delete (row is left in place) rather than leaving a deleted local record with orphaned NetBox objects or live device config and no way to find it again - the operator can retry, or uncheck that option and clean up by hand. Used both for an explicit admin delete and for cleaning up an aborted create-wizard draft (which never sets the cleanup flags, so behaves exactly as before).

func (*Controller) ApiServiceElinePush

func (ctrl *Controller) ApiServiceElinePush(c *echo.Context) error

ApiServiceElinePush is kept as a dedicated route; it delegates to the generic pack-based push used by POST /service/:id/push.

func (*Controller) ApiServiceElineUpdate

func (ctrl *Controller) ApiServiceElineUpdate(c *echo.Context) error

ApiServiceElineUpdate is a thin adapter around persistELINEEndpoints: maps the historical A/B DTO onto generic service_endpoints (roles a/b).

func (*Controller) ApiServiceEndpointsGet added in v1.0.2

func (ctrl *Controller) ApiServiceEndpointsGet(c *echo.Context) error

func (*Controller) ApiServiceEndpointsPut added in v1.0.2

func (ctrl *Controller) ApiServiceEndpointsPut(c *echo.Context) error

func (*Controller) ApiServicePathGet

func (ctrl *Controller) ApiServicePathGet(c *echo.Context) error

func (*Controller) ApiServicePathPut

func (ctrl *Controller) ApiServicePathPut(c *echo.Context) error

func (*Controller) ApiServicePush added in v1.0.2

func (ctrl *Controller) ApiServicePush(c *echo.Context) error

ApiServicePush renders the platform pack and applies CLI sessions.

func (*Controller) ApiServiceTypeUpdate

func (ctrl *Controller) ApiServiceTypeUpdate(c *echo.Context) error

ApiServiceTypeUpdate lets the network GUI attach a service type, bandwidth and max MAC address count to a service - including one synced from Lime, which is otherwise read-only (ApiServiceUpdate) since Lime doesn't supply these fields itself and SaveDelivery now preserves whatever's set here across future syncs. Deliberately not routed through the generic SecureCRUDHandler/ServiceDTO update: that DTO also carries Lime-owned fields (company, delivery points, product, service, comment, service_id, agreement_status), which must stay off-limits for a Lime-sourced row.

func (*Controller) ApiServiceUpdate

func (ctrl *Controller) ApiServiceUpdate(services *SecureCRUDHandler[models.Service, models.ServiceDTO]) echo.HandlerFunc

ApiServiceUpdate rejects edits to services synced from Lime (the existing row's Source == "lime") before delegating to the generic CRUD handler for everything else. Lime-sourced fields get overwritten wholesale on the next sync run (SaveDelivery in internal/lime/lime.go), so letting an edit through the API would just have it silently discarded on the next sync - better to reject it upfront than have an edit mysteriously disappear.

func (*Controller) ApiSettingsGet

func (ctrl *Controller) ApiSettingsGet(c *echo.Context) error

func (*Controller) ApiSettingsTestEmail

func (ctrl *Controller) ApiSettingsTestEmail(c *echo.Context) error

ApiSettingsTestEmail sends a test email using the submitted (or saved) SMTP settings and reports ok/error as a 200 JSON body rather than a 4xx/5xx status - a failed send isn't an application error, it's the answer to the question the admin is asking. Same shape as ApiLdapTestConnection.

func (*Controller) ApiSettingsUpdate

func (ctrl *Controller) ApiSettingsUpdate(c *echo.Context) error

func (*Controller) ApiSyncTargets

func (ctrl *Controller) ApiSyncTargets(c *echo.Context) error

ApiSyncTargets returns the systems that can be synced, so the frontend can render one button per target without hardcoding the list - filtered down to those enabled in Settings (worker.EnabledSyncTargets), so a source/ destination an admin hasn't activated doesn't show a Sync button at all.

func (*Controller) ApiSyncTrigger

func (ctrl *Controller) ApiSyncTrigger(c *echo.Context) error

ApiSyncTrigger dispatches :target to exactly one connected worker node activated for it (see internal/worker/hub.go's RemoteManager.StartJob - deliberately single-node per target, not a fan-out like SendCommand/ "factum2-worker run", so a target maps to exactly one execution) and creates a Job row (with one JobTask child) tracking it. Unlike the old rabbitmq-based PublishSync, which silently "succeeded" even with nobody listening, matched==0 is now an observable fact and reported as an error instead - the job/task rows still get created and immediately marked failed, so they show up in history rather than just vanishing. StartJob also refuses to dispatch a target that already has a task in flight (worker.ErrSyncAlreadyRunning), reported here as 409 Conflict - the frontend's Sync buttons are expected to already be disabled in this case, but the server is the actual enforcement point.

func (*Controller) ApiSyncTriggerAll

func (ctrl *Controller) ApiSyncTriggerAll(c *echo.Context) error

ApiSyncTriggerAll dispatches every enabled sync target as one Job with a JobTask per target (RemoteManager.StartJob) - the server-side successor of the old client-side "trigger targets one at a time, waiting for each" loop (SyncOverviewPage.vue's syncAll): targets are dispatched one at a time, each waited on to finish before the next starts (RemoteManager.dispatchRemainingSequentially), in the sources-then- destinations order worker.SequencedSyncAllTargets produces, so a destination sync never runs while (or ahead of) the source sync that feeds it. Unlike ApiSyncTrigger, a single target's conflict/dispatch failure doesn't fail the whole request - it's recorded against that target's JobTask row instead (see worker.RemoteManager.startOneTask), so the response is 200 as long as the parent Job row itself was created. The response's per-target "tasks" only ever has (at most) one entry now - the first target, dispatched synchronously - since the rest are dispatched later from a background goroutine; nothing currently reads that field besides job_id (see SyncOverviewPage.vue's syncAll and factum.FactumClient.TriggerSyncAll), so its shape was kept rather than removed.

func (*Controller) ApiUserCreate

func (ctrl *Controller) ApiUserCreate(c *echo.Context) error

func (*Controller) ApiUserDelete

func (ctrl *Controller) ApiUserDelete(c *echo.Context) error

ApiUserDelete refuses to delete a user holding the "admin" role, so the last admin account can never be removed through this endpoint (there's no other way to grant that role back once nobody holds it).

func (*Controller) ApiUserGetOne

func (ctrl *Controller) ApiUserGetOne(c *echo.Context) error

func (*Controller) ApiUserList

func (ctrl *Controller) ApiUserList(c *echo.Context) error

func (*Controller) ApiUserUpdate

func (ctrl *Controller) ApiUserUpdate(c *echo.Context) error

func (*Controller) ApiVersion added in v1.0.2

func (ctrl *Controller) ApiVersion(c *echo.Context) error

ApiVersion returns the running binary's release identity (ldflags-stamped version/commit/date, plus Go version). Unauthenticated so the login page can show it. `go run` builds stay at the unstamped defaults.

func (*Controller) ApiWorkerNodeCreate

func (ctrl *Controller) ApiWorkerNodeCreate(c *echo.Context) error

ApiWorkerNodeCreate/ApiWorkerNodeUpdate replace the generic SecureCRUDHandler for WorkerNode's write side: Token is json:"-" on the model (so GetAll/GetOne never leak it), but that same tag makes it invisible to SecureCRUDHandler's DTO<->model JSON round-trip in both directions - without a custom handler, a submitted token would silently never be written to the DB. Same reasoning as ApiUserCreate/ApiUserUpdate for PasswordHash (see handle_user_api.go).

func (*Controller) ApiWorkerNodeToken

func (ctrl *Controller) ApiWorkerNodeToken(c *echo.Context) error

ApiWorkerNodeToken returns a single WorkerNode's stored token. Kept separate from GetAll/GetOne (models.WorkerNode.Token is json:"-", so neither ever includes it) so a bulk list/detail fetch never carries secrets - this is the one explicit, single-node reveal path, used to prefill the admin UI's token field so its own eye icon can show it.

func (*Controller) ApiWorkerNodeUpdate

func (ctrl *Controller) ApiWorkerNodeUpdate(c *echo.Context) error

func (*Controller) ApiWorkerRun

func (ctrl *Controller) ApiWorkerRun(c *echo.Context) error

ApiWorkerRun dispatches a predefined command to every connected node handling the given role (ctrl.RemoteManager.RunAndWait) and streams each LogMsg back as one NDJSON line, flushed immediately - the "factum2-worker run" CLI's replacement for dialing rabbitmq directly, now that only the primary holds the hub connections a command can actually be dispatched over.

func (*Controller) ApiWorkerStatus

func (ctrl *Controller) ApiWorkerStatus(c *echo.Context) error

ApiWorkerStatus lists every configured worker node with its live connection status - the sync status page's main table. Replaces ApiWorkerPing's broadcast-and-wait for nodes managed via ctrl.RemoteManager: status here is a plain read of already-known state.

func (*Controller) RequireAPIAuth

func (ctrl *Controller) RequireAPIAuth(next echo.HandlerFunc) echo.HandlerFunc

RequireAPIAuth identifies the caller either from the "token" cookie (sets "user" in the context) or a service token (see checkServiceToken; sets "auth_method" to "token" instead, since there's no user behind it), responding with a 401 JSON body on failure - API clients can't follow a redirect into an HTML page, which is why this doesn't redirect to /login like the legacy form-POST handlers do.

func (*Controller) RequireAdmin

func (ctrl *Controller) RequireAdmin(next echo.HandlerFunc) echo.HandlerFunc

RequireAdmin rejects the request unless the authenticated user (set by RequireAPIAuth) has the "admin" role. Must run after RequireAPIAuth.

func (*Controller) RequireAdminOrServiceToken

func (ctrl *Controller) RequireAdminOrServiceToken(next echo.HandlerFunc) echo.HandlerFunc

RequireAdminOrServiceToken allows either an admin user (session cookie) or a valid service token through - for endpoints a remote CLI needs to reach non-interactively (see checkServiceToken) but which still carry admin-level secrets (DB credentials, API tokens), so a plain logged-in non-admin user mustn't see them either. Must run after RequireAPIAuth.

func (*Controller) RequireIpamEnabled

func (ctrl *Controller) RequireIpamEnabled(next echo.HandlerFunc) echo.HandlerFunc

RequireIpamEnabled 404s IPAM routes when the Factum setting is off. Rows are left untouched — this is a visibility gate, not a delete.

func (*Controller) RequireOpticalEnabled

func (ctrl *Controller) RequireOpticalEnabled(next echo.HandlerFunc) echo.HandlerFunc

RequireOpticalEnabled 404s optical routes when the Factum setting is off.

func (*Controller) RequireRead

func (ctrl *Controller) RequireRead(next echo.HandlerFunc) echo.HandlerFunc

RequireRead allows the "admin", "operator" or "viewer" role - any authenticated user granted at least read access to non-admin resources. A user with no role at all is rejected with 403, leaving them only the routes that don't use RequireRead/RequireWrite: the dashboard (GET /api/links) and their own profile (GET/PUT /api/me). A valid service token is also allowed (same rationale as RequireWrite - e.g. FactumClient.GetDeviceByName). Must run after RequireAPIAuth.

func (*Controller) RequireWrite

func (ctrl *Controller) RequireWrite(next echo.HandlerFunc) echo.HandlerFunc

RequireWrite allows the "admin" or "operator" role - the only roles permitted to create/update/delete non-admin resources (customer, contact, service, device interfaces, sync triggers, ...). A user with no role, or only "viewer", is rejected with 403. A valid service token (auth_method "token", set by RequireAPIAuth) is also allowed - remote CLIs such as factum2-device-sync have no session user. Must run after RequireAPIAuth.

func (*Controller) SaveUserSetings

func (ctrl *Controller) SaveUserSetings(c *echo.Context) error

SaveUserSetings is unrouted (superseded by ApiMeUpdate) but left in place; it doesn't touch templates so it's out of scope for this cleanup.

type DNSConfigResponse

type DNSConfigResponse struct {
	util.CommonConfig
	DestFile        string `json:"dest_file"`
	IgnoreModels    string `json:"ignore_models"`
	IgnorePlatforms string `json:"ignore_platforms"`
}

DNSConfigResponse is what factum2-dns (internal/dns's FetchRemoteConfig) parses - keep the JSON tags in sync with that type.

type DeviceSyncAuthEntry

type DeviceSyncAuthEntry struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

DeviceSyncAuthEntry is one entry of DeviceSyncConfigResponse.Auth - the REST-facing shape of models.DeviceSyncAuth (minus Name, which is the map key instead).

type DeviceSyncConfigResponse

type DeviceSyncConfigResponse struct {
	util.CommonConfig
	VRFInGlobal   []string                       `json:"vrf_in_global"`
	DeviceStates  []string                       `json:"device_states"`
	DeviceIgnore  []string                       `json:"device_ignore"`
	VlanGroupName string                         `json:"vlan_group_name"`
	Auth          map[string]DeviceSyncAuthEntry `json:"auth"`
}

DeviceSyncConfigResponse is what internal/device-sync's FetchRemoteConfig parses - keep the JSON tags in sync with that type.

type ForgotPasswordRequest

type ForgotPasswordRequest struct {
	Email string `json:"email"`
}

type GuiParams

type GuiParams struct {
	cmdbase.Params
	Bind string `default:":8090"`
}

type IcingaConfigResponse

type IcingaConfigResponse struct {
	util.CommonConfig
	URL      string `json:"url"`
	Username string `json:"username"`
	Password string `json:"password"`

	HostsFile           string `json:"hosts_file"`
	UsersFile           string `json:"users_file"`
	IgnoreDevices       string `json:"ignore_devices"`
	DefaultNotification string `json:"default_notification"`
	HostTemplate        string `json:"host_template"`
	DependencyTemplate  string `json:"dependency_template"`
	UserTemplate        string `json:"user_template"`
}

IcingaConfigResponse is what factum2-icinga (internal/icinga's FetchRemoteConfig) parses - keep the JSON tags in sync with that type.

type LdapBrowseEntryDTO

type LdapBrowseEntryDTO struct {
	DN      string `json:"dn"`
	Name    string `json:"name"`
	IsGroup bool   `json:"is_group"`
}

LdapBrowseEntryDTO is one node in the tree-browser response.

type LdapTestRequest

type LdapTestRequest struct {
	Host          string `json:"ldap_host"`
	Port          uint16 `json:"ldap_port"`
	Host2         string `json:"ldap_host2"`
	Port2         uint16 `json:"ldap_port2"`
	TLSMode       string `json:"ldap_tls_mode"`
	SkipTLSVerify bool   `json:"ldap_skip_tls_verify"`
	BindDN        string `json:"ldap_bind_dn"`
	BindPassword  string `json:"ldap_bind_password,omitempty"`
	BaseDN        string `json:"ldap_base_dn"`
}

LdapTestRequest carries the (possibly-unsaved, currently-in-form) LDAP connection fields for the admin "Test Connection" button. BindPassword is optional: if blank, the currently-saved Settings.LdapBindPassword is used instead, so the admin doesn't have to retype a secret just to test other field changes - same "omit to keep existing value" pattern as WorkerNodeDTO.Token/UserDTO.Password.

type LdapTestServerDTO added in v1.0.2

type LdapTestServerDTO struct {
	Host  string `json:"host"`
	Port  uint16 `json:"port"`
	OK    bool   `json:"ok"`
	Error string `json:"error,omitempty"`
}

LdapTestServerDTO is one host's result in ApiLdapTestConnection's response, so a dual-server setup can show which replica answered.

type LibrenmsConfigResponse

type LibrenmsConfigResponse struct {
	util.CommonConfig
	URL                  string `json:"url"`
	Key                  string `json:"key"`
	PersistentDevices    string `json:"persistent_devices"`
	DelayedDeleteEnabled bool   `json:"delayed_delete_enabled"`
	DelayedDeleteDays    int    `json:"delayed_delete_days"`
	RolesEnabled         string `json:"roles_enabled"`
	InterfacesDisabled   string `json:"interfaces_disabled"`
	SNMPVersion          string `json:"snmp_version"`
	SNMPCommunities      string `json:"snmp_communities"`
}

LibrenmsConfigResponse is what factum2-librenms-cli (internal/librenms's FetchRemoteConfig) parses - keep the JSON tags in sync with that type.

type LinksReorderRequest

type LinksReorderRequest struct {
	IDs []uint `json:"ids"`
}

LinksReorderRequest is the drag-and-drop result from the admin page: every link ID in its new display order.

type LogEvent

type LogEvent struct {
	Time    time.Time         `json:"time"`
	Level   string            `json:"level"`
	Message string            `json:"message"`
	Attrs   map[string]string `json:"attrs,omitempty"`
}

LogEvent is the JSON shape streamed to the frontend log window - one per slog record emitted anywhere in the process (see hubHandler below).

type LogHub

type LogHub struct {
	// contains filtered or unexported fields
}

LogHub fans out LogEvents to every connected websocket client and keeps a bounded ring buffer so newly-subscribed clients get recent history.

func NewLogHub

func NewLogHub() *LogHub

func (*LogHub) Publish

func (h *LogHub) Publish(e LogEvent)

Publish fans out an event to all subscribers and appends it to history. A subscriber whose channel is full is skipped rather than blocked on - log production must never stall waiting on a slow/stuck browser tab.

func (*LogHub) Subscribe

func (h *LogHub) Subscribe() (ch chan LogEvent, history []LogEvent, unsubscribe func())

Subscribe registers a new client, returning its event channel, a snapshot of recent history to replay, and an unsubscribe func the caller must run (typically deferred) once it's done reading.

type LoginRequest

type LoginRequest struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

type MeUpdateRequest

type MeUpdateRequest struct {
	Name            string `json:"name"`
	Email           string `json:"email"`
	Mobile          string `json:"mobile"`
	CurrentPassword string `json:"current_password,omitempty"`
	NewPassword     string `json:"new_password,omitempty"`
}

type NetboxConfigResponse

type NetboxConfigResponse struct {
	util.CommonConfig
	URL   string `json:"url"`
	Token string `json:"token"`
}

NetboxConfigResponse is what internal/netbox's FetchRemoteConfig parses - keep the JSON tags in sync with that type.

type NetboxWebhookPayload

type NetboxWebhookPayload struct {
	Event      string         `json:"event"`
	ObjectType string         `json:"object_type"`
	Data       map[string]any `json:"data"`
}

NetboxWebhookPayload is the JSON body Netbox posts for its default webhook body template (https://netboxlabs.com/docs/netbox/integrations/webhooks/): event/object_type/etc plus the changed object's serialized representation in Data - object_type is "app_label.model_name" (e.g. "dcim.device"), per Netbox's send_webhook() (extras/webhooks.py), which builds the context dict from object_type.natural_key(). Only the fields ApiNetboxWebhook needs are declared.

type OxidizedConfigResponse

type OxidizedConfigResponse struct {
	util.CommonConfig
	URL  string `json:"url"`
	User string `json:"user"`
	Pass string `json:"pass"`

	DestFile            string `json:"dest_file"`
	IgnoreDevices       string `json:"ignore_devices"`
	IgnoreManufacturers string `json:"ignore_manufacturers"`
	IgnoreModels        string `json:"ignore_models"`
	IgnorePlatforms     string `json:"ignore_platforms"`
}

OxidizedConfigResponse is what factum2-oxidized (internal/oxidized's FetchRemoteConfig) parses - keep the JSON tags in sync with that type.

type PrometheusConfigResponse added in v1.0.3

type PrometheusConfigResponse struct {
	util.CommonConfig
	DestFile            string `json:"dest_file"`
	ReloadURL           string `json:"reload_url"`
	Module              string `json:"module"`
	Auth                string `json:"auth"`
	IgnoreDevices       string `json:"ignore_devices"`
	IgnoreManufacturers string `json:"ignore_manufacturers"`
	IgnoreModels        string `json:"ignore_models"`
	IgnorePlatforms     string `json:"ignore_platforms"`
}

PrometheusConfigResponse is what factum2-prometheus (internal/prometheus's FetchRemoteConfig) parses - keep the JSON tags in sync with that type.

type ResetPasswordRequest

type ResetPasswordRequest struct {
	Token       string `json:"token,omitempty"`
	Email       string `json:"email,omitempty"`
	Code        string `json:"code,omitempty"`
	NewPassword string `json:"new_password"`
}

type SecureCRUDHandler

type SecureCRUDHandler[M any, Req any] struct {
	DB *gorm.DB
}

M = Database Model, Req = Request DTO

func NewSecureCRUDHandler

func NewSecureCRUDHandler[M any, Req any](db *gorm.DB) *SecureCRUDHandler[M, Req]

func (*SecureCRUDHandler[M, Req]) Create

func (h *SecureCRUDHandler[M, Req]) Create(c *echo.Context) error

3. CREATE: POST /api/resource Safety: Prevents Mass Assignment by binding to the DTO first.

func (*SecureCRUDHandler[M, Req]) Delete

func (h *SecureCRUDHandler[M, Req]) Delete(c *echo.Context) error

5. DELETE: DELETE /api/resource/:id

func (*SecureCRUDHandler[M, Req]) GetAll

func (h *SecureCRUDHandler[M, Req]) GetAll(c *echo.Context) error

1. GET ALL: GET /api/resource

func (*SecureCRUDHandler[M, Req]) GetOne

func (h *SecureCRUDHandler[M, Req]) GetOne(c *echo.Context) error

2. GET ONE: GET /api/resource/:id Safety: Safe by default if your Model utilizes `json:"-"` tags on private fields.

func (*SecureCRUDHandler[M, Req]) Update

func (h *SecureCRUDHandler[M, Req]) Update(c *echo.Context) error

4. UPDATE: PUT /api/resource/:id

type ServiceDTO

type ServiceDTO struct {
	ID       uint   `json:"id"`
	Name     string `json:"name"`
	Customer string `json:"company"`

	ServiceID     string `json:"service_id"`
	Category      string `json:"category"`
	ServiceType   string `json:"service_type"`
	BandwidthMbps int    `json:"bandwidth_mbps"`

	Deliverypoint1  string `json:"deliverypoint1"`
	Deliverypoint2  string `json:"deliverypoint2"`
	Product         string `json:"product"`
	Service         string `json:"service"`
	AgreementStatus string `json:"agreement_status"`
	Source          string `json:"source"`
}

type ServiceDeleteRequest

type ServiceDeleteRequest struct {
	RemoveFromNetbox bool   `json:"remove_from_netbox"`
	RemoveFromDevice bool   `json:"remove_from_device"`
	Username         string `json:"username"`
	Password         string `json:"password"`
}

ServiceDeleteRequest is ApiServiceDelete's optional request body - for an ELINE service, lets the caller ask for the NetBox objects and/or device config a prior eline/update and eline/push left behind to be torn down as part of the delete, rather than orphaned. Ignored entirely for non-ELINE services. Username/Password are only needed when RemoveFromDevice is set (same per-request, never-persisted credentials convention as deviceCredentialsRequest).

type ServiceDetailResponse

type ServiceDetailResponse struct {
	models.Service
	AppliedToDevice bool                     `json:"applied_to_device"`
	Endpoints       []models.ServiceEndpoint `json:"endpoints,omitempty"`
}

ServiceDetailResponse adds AppliedToDevice to the raw Service model - a derived signal for whether the service has live config on a device (models.Service's AppliedEndpointX* fields, which record that, are json:"-" since they're internal bookkeeping, not something a client should ever set directly). Used by the delete dialog (ServiceList.vue) to decide whether to offer a "remove from device" cleanup option.

type ServiceElineDTO

type ServiceElineDTO struct {
	EndpointADeviceID    uint `json:"endpoint_a_device_id"`
	EndpointAInterfaceID uint `json:"endpoint_a_interface_id"`
	EndpointAVlan        int  `json:"endpoint_a_vlan"`

	EndpointBDeviceID    uint `json:"endpoint_b_device_id"`
	EndpointBInterfaceID uint `json:"endpoint_b_interface_id"`
	EndpointBVlan        int  `json:"endpoint_b_vlan"`
}

type ServiceTypeDTO

type ServiceTypeDTO struct {
	ServiceType     string          `json:"service_type"`
	BandwidthMbps   int             `json:"bandwidth_mbps"`
	MaxMacAddresses int             `json:"max_mac_addresses"`
	Fields          json.RawMessage `json:"fields"`
}

ServiceTypeDTO is the request shape for ApiServiceTypeUpdate - the one slice of a service's data that's editable regardless of Source, since SaveDelivery (internal/lime/lime.go) now preserves these specific fields across a Lime resync instead of overwriting them.

type SettingsTestEmailRequest

type SettingsTestEmailRequest struct {
	To          string `json:"to"`
	SmtpHost    string `json:"smtp_host"`
	SmtpPort    uint16 `json:"smtp_port"`
	SmtpUser    string `json:"smtp_user"`
	SmtpPass    string `json:"smtp_pass,omitempty"`
	SmtpTLSMode string `json:"smtp_tls_mode"`
	EmailSender string `json:"email_sender"`
}

SettingsTestEmailRequest carries the (possibly-unsaved, currently-in-form) SMTP fields plus the recipient address(es) for the admin "Send test email" button. To is free-form textarea content, one address (or comma-separated addresses) per line - same "one item per line" convention as the Ignore*/Roles* textareas elsewhere on this page. SmtpPass is optional: if blank, the currently-saved Settings.SmtpPass is used instead, same "omit to keep existing value" pattern as LdapTestRequest.BindPassword.

type TopologyDTO

type TopologyDTO struct {
	Devices []TopologyDeviceDTO `json:"devices"`
	Edges   []TopologyEdgeDTO   `json:"edges"`
	Sites   []TopologySiteDTO   `json:"sites"`
}

type TopologyDeviceDTO

type TopologyDeviceDTO struct {
	ID          uint    `json:"id"`
	Name        string  `json:"name"`
	Site        string  `json:"site"`
	Role        string  `json:"role"`
	Status      string  `json:"status"`
	OpticalKind string  `json:"optical_kind"`
	Latitude    float64 `json:"latitude"`
	Longitude   float64 `json:"longitude"`
}

TopologyDeviceDTO is one device positioned on the network map. Only devices with resolved coordinates (see models.Device.Latitude/Longitude) are ever included - the map has nowhere to place the rest.

type TopologyEdgeDTO

type TopologyEdgeDTO struct {
	ID         uint   `json:"id"`
	DeviceAID  uint   `json:"device_a_id"`
	InterfaceA string `json:"interface_a"`
	DeviceBID  uint   `json:"device_b_id"`
	InterfaceB string `json:"interface_b"`
	Label      string `json:"label"`
}

TopologyEdgeDTO is one Connection (Netbox cable) between two devices already present in the same response's Devices list.

type TopologySiteDTO

type TopologySiteDTO struct {
	ID        uint    `json:"id"`
	Name      string  `json:"name"`
	Latitude  float64 `json:"latitude"`
	Longitude float64 `json:"longitude"`
}

TopologySiteDTO is a Netbox site plotted on the map independently of any device - see models.Site, synced by internal/netbox.syncSites - so a site with no devices of its own still shows up.

type WorkerStatusEntry

type WorkerStatusEntry struct {
	Name      string    `json:"name"`
	Address   string    `json:"address"`
	Enabled   bool      `json:"enabled"`
	Connected bool      `json:"connected"`
	Hostname  string    `json:"hostname"`
	Roles     []string  `json:"roles"`
	LastSeen  time.Time `json:"last_seen"`
	LastError string    `json:"last_error,omitempty"`
}

WorkerStatusEntry is one configured models.WorkerNode merged with its live connection state from ctrl.RemoteManager - unlike ApiWorkerPing, this always lists every configured node (connected or not), since RemoteManager already holds a live-or-retrying connection to each one rather than needing a broadcast round trip to find out.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL