Documentation
¶
Index ¶
- Constants
- Variables
- func CategoryFromServiceID(serviceID string) string
- func IsOpticalKind(kind string) bool
- func NetboxModeToSwitchportMode(mode string) string
- func SwitchportModeToNetboxMode(mode string) any
- type Address
- type Agreement
- type AgreementQoS
- type Connection
- type Contact
- type ContactDTO
- type Control
- type Customer
- type CustomerContact
- type CustomerDTO
- type Device
- type DeviceSyncAuth
- type DeviceSyncAuthDTO
- type FactumModel
- type Interface
- type InterfaceServiceRef
- type IpamNamespace
- type IpamNamespaceDTO
- type IpamNamespacePrefix
- type IpamNamespacePrefixDTO
- type IpamPrefix
- type IpamPrefixDTO
- type IpamVRF
- type IpamVRFIDTO
- type Job
- type JobSchedule
- type JobScheduleDTO
- type JobTask
- type JobTaskEvent
- type LdapRoleMapping
- type LdapRoleMappingDTO
- type LibrenmsPendingDelete
- type Link
- type LinkDTO
- type LogEntry
- type MaintenanceNotification
- type MaintenanceWindow
- type OpticalKindMap
- type OpticalKindMapDTO
- type OpticalPort
- type OpticalXConnect
- type PasswordResetToken
- type Product
- type Role
- type RoleDTO
- type Service
- type ServiceDTO
- type ServiceHop
- type ServicePath
- type Settings
- type Site
- type Tag
- type User
- type UserDTO
- type WorkerNode
- type WorkerNodeDTO
Constants ¶
const ( OpticalKindNone = "" OpticalKindROADM = "roadm" OpticalKindWDMShelf = "wdm_shelf" OpticalKindILA = "ila" OpticalKindPassive = "passive" )
Optical chassis kinds. Transponder vs muxponder is not a device kind — it is derived from tributary xconnect cardinality on a line port.
const ( PortTXPClient = "txp_client" PortTXPLine = "txp_line" PortROADMAddDrop = "roadm_adddrop" PortROADMDegree = "roadm_degree" PortFiber = "fiber_port" )
Optical port roles.
const ( XCTributary = "tributary" XCAddDrop = "roadm_adddrop" XCExpress = "roadm_express" XCPassthrough = "passthrough" )
Optical xconnect kinds.
const ( HopInterface = "interface" HopDevice = "device" HopConnection = "connection" HopXConnect = "xconnect" PathNone = "none" PathComplete = "complete" PathIncomplete = "incomplete" PathStale = "stale" PathConflict = "conflict" TraceModeWDM = "wdm" TraceModeFiber = "fiber" StartCustomerPort = "customer_port" StartTXPClient = "txp_client" StartFiberPort = "fiber_port" )
Service path / hop kinds and statuses.
const ( MaintResourceConnection = "connection" MaintResourceDevice = "device" MaintResourceInterface = "interface" MaintDraft = "draft" MaintPlanned = "planned" MaintNotified = "notified" MaintInProgress = "in_progress" MaintCompleted = "completed" MaintCancelled = "cancelled" MaintNotifyPending = "pending" MaintNotifySent = "sent" MaintNotifyFailed = "failed" MaintNotifySkipped = "skipped" )
Maintenance constants.
Variables ¶
var AllowedOpticalKinds = map[string]bool{ OpticalKindROADM: true, OpticalKindWDMShelf: true, OpticalKindILA: true, OpticalKindPassive: true, }
AllowedOpticalKinds is the stored set (aliases are normalized first).
var AllowedOpticalPortRoles = map[string]bool{ PortTXPClient: true, PortTXPLine: true, PortROADMAddDrop: true, PortROADMDegree: true, PortFiber: true, }
AllowedOpticalPortRoles is the stored set for OpticalPort.Role / the NetBox optical_role custom field on dcim.interface.
var OpticalKindAliases = map[string]string{ "transponder": OpticalKindWDMShelf, "muxponder": OpticalKindWDMShelf, }
OpticalKindAliases maps legacy / operator-facing names onto stored kinds.
var OpticalServiceCategories = map[string]bool{ "VL": true, "VI": true, "LF": true, "LI": true, }
OpticalServiceCategories are the prefixes that participate in fiber / wavelength impact. CN/CI are excluded.
Functions ¶
func CategoryFromServiceID ¶
CategoryFromServiceID derives CN/CI/VL/VI/LF/LI from ServiceID's prefix. Returns "" for free-text / Lime IDs that don't match the shape.
func IsOpticalKind ¶
func NetboxModeToSwitchportMode ¶
NetboxModeToSwitchportMode is the inverse of SwitchportModeToNetboxMode, used when syncing Netbox's interface "mode" into factum's own Interface.SwitchportMode (internal/netbox's syncInterfaces). Netbox's "tagged-all" (an untagged VLAN plus every VLAN in the assigned group, tagged) has no equivalent in this codebase's switchport vocabulary, so it maps to "trunk" like "tagged" does - both write "switchport trunk allowed vlan ..." in this codebase's driver-write direction.
func SwitchportModeToNetboxMode ¶
SwitchportModeToNetboxMode maps a driver/factum SwitchportMode value ("access", "trunk", "dot1q-tunnel") to Netbox's interface "mode" field ("access", "tagged", "q-in-q") - used when pushing a device-read switchport config to Netbox (internal/device-sync, and the interfaces/ vlans web endpoint). "" (not a switchport) clears Netbox's mode.
Types ¶
type Address ¶
type Address struct {
FactumModel
AddressID uint `json:"address_id"`
InterfaceID uint `json:"interface_id" gorm:"index"`
NetboxID uint `json:"netbox_id"`
Address string `json:"address" gorm:"type:varchar(80)"`
// VRF is the name of the VRF this address belongs to, "" for
// global/default VRF - mirrors netboxtool.NBAddress.VRF.
VRF string `json:"vrf" gorm:"type:varchar(255)"`
// Role is Netbox's native ipam.IPAddress.role (e.g. "anycast"), not a
// custom field - hence no Cf prefix, unlike Interface.CfRole.
Role string `json:"role" gorm:"type:varchar(255)"`
}
type Agreement ¶
type Agreement struct {
FactumModel
LastSync uint `json:"-"`
Monthly_fee int `json:"montly_fee"`
Onetime_fee int `json:"onetime_fee"`
QOS *AgreementQoS `gorm:"-" json:"qos"`
}
-------
type AgreementQoS ¶
type AgreementQoS struct {
FactumModel
LastSync uint `json:"-"`
Key string `json:"key"`
Text string `json:"text"`
}
type Connection ¶
type Connection struct {
FactumModel
NetboxID uint `json:"netbox_id" gorm:"uniqueIndex"`
DeviceAID uint `json:"device_a_id" gorm:"index"`
InterfaceAID uint `json:"interface_a_id" gorm:"index"`
DeviceBID uint `json:"device_b_id" gorm:"index"`
InterfaceBID uint `json:"interface_b_id" gorm:"index"`
Label string `json:"label" gorm:"type:varchar(255)"`
}
Connection is a Netbox cable directly connecting two device interfaces, synced read-only from Netbox (internal/netbox.syncCables) - covers every interface-to-interface cable Netbox knows about, not just the LLDP-discovered ones internal/device-sync creates there.
type Contact ¶
type Contact struct {
FactumModel
LastSync uint `json:"-"`
Name string `json:"name"`
Email string `json:"email"`
Phone string `json:"phone"`
NotifyMaintenance bool `json:"notify_maintenance"`
Source string `json:"source"`
SourceID string `json:"source_id"`
}
func (*Contact) BeforeCreate ¶
BeforeCreate defaults a contact's Source to "factum" when nothing set it already. The Lime person-sync (internal/lime/lime.go) always sets Source to "lime" itself before saving, so this only ever fires for contacts created through the web API - ContactDTO has no Source field for a caller to set, so without this default every manually-created contact would be indistinguishable (Source == "") from one whose sync origin just hasn't been recorded yet, instead of clearly marked as user-created.
type ContactDTO ¶
type ContactDTO struct {
ID uint `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Phone string `json:"phone"`
NotifyMaintenance bool `json:"notify_maintenance"`
}
ContactDTO is the create/update request shape for the contact API, mirroring the fields the frontend's contact form actually edits (web/frontend/src/views/contact/ContactList.vue). Source/SourceID are deliberately excluded: they're populated by the Lime person-sync (internal/lime/lime.go), not something a caller creating or editing a contact through the web UI should be able to set - excluding them from the DTO means Update leaves a synced contact's Source/SourceID untouched (the JSON merge only overwrites fields present in the body), and Create leaves them at their zero value so BeforeCreate can default Source to "factum" for a manually-created contact. Name/email/phone on a Lime row are also ignored by ApiContactUpdate; only NotifyMaintenance is applied, since the next sync would otherwise discard those edits.
type Control ¶
type Control struct {
FactumModel
}
type Customer ¶
type Customer struct {
FactumModel
LastSync uint `json:"-"`
Name string `json:"name"`
Postaladdress1 string `json:"postal_address1"`
Postaladdress2 string `json:"postal_address2"`
Postalcity string `json:"postalcity"`
Postalzipcode string `json:"postalzipcode"`
Country string `json:"country"`
OrganizationNumber string `json:"organization_number"`
Source string `json:"source"`
SourceID string `json:"source_id"`
}
func (*Customer) BeforeCreate ¶
BeforeCreate defaults a customer's Source to "factum" when nothing set it already. The Lime sync job (internal/lime/lime.go) always sets Source to "lime" itself before saving, so this only ever fires for customers created through the web API - CustomerDTO has no Source field for a caller to set, so without this default every manually-created customer would be indistinguishable (Source == "") from one whose sync origin just hasn't been recorded yet, instead of clearly marked as user-created.
type CustomerContact ¶
type CustomerContact struct {
FactumModel
CustomerID uint `json:"customer_id" gorm:"uniqueIndex:idx_customer_contacts;not null"`
ContactID uint `json:"contact_id" gorm:"uniqueIndex:idx_customer_contacts;not null;index"`
}
CustomerContact joins a contact to a customer for maintenance notify.
type CustomerDTO ¶
type CustomerDTO struct {
ID uint `json:"id"`
Name string `json:"name"`
Postaladdress1 string `json:"postal_address1"`
Postaladdress2 string `json:"postal_address2"`
Postalcity string `json:"postalcity"`
Postalzipcode string `json:"postalzipcode"`
Country string `json:"country"`
OrganizationNumber string `json:"organization_number"`
}
CustomerDTO is the create/update request shape for the customer API, mirroring the fields the frontend's customer form actually edits (web/frontend/src/views/customer/CustomerList.vue). Source/SourceID are deliberately excluded: they're populated by the Lime sync job (internal/lime/lime.go), not something a caller creating or editing a customer through the web UI should be able to set - excluding them from the DTO means Update leaves a synced customer's Source/SourceID untouched (the JSON merge only overwrites fields present in the body), and Create leaves them at their zero value so BeforeCreate can default Source to "factum" for a manually-created customer.
type Device ¶
type Device struct {
FactumModel
// Netbox's dcim.Device and virtualization.VirtualMachine tables have
// independent ID sequences, so NetboxID is only unique per VM value,
// never globally - see internal/netbox.syncDevice.
VM bool `json:"vm" gorm:"uniqueIndex:idx_devices_netbox_id_vm"`
NetboxID uint `json:"netbox_id" gorm:"uniqueIndex:idx_devices_netbox_id_vm"`
Name string `json:"name" gorm:"type:varchar(255)"`
Comments string `json:"comments" gorm:"type:varchar(255)"`
Enabled bool `json:"enabled"`
Manufacturer string `json:"manufacturer" gorm:"type:varchar(255)"`
ManufacturerID uint `json:"manufacturer_id"`
ModelName string `json:"model_name" gorm:"type:varchar(255)"`
ModelID uint `json:"model_id"`
Platform string `json:"platform" gorm:"type:varchar(255)"`
PlatformID uint `json:"platform_id"`
PrimaryIPv4 string `json:"primary_ipv4" gorm:"type:varchar(255)"`
PrimaryIPv4ID uint `json:"primary_ipv4_id"`
PrimaryIPv6 string `json:"primary_ipv6" gorm:"type:varchar(255)"`
PrimaryIPv6ID uint `json:"primary_ipv6_id"`
Role string `json:"role" gorm:"type:varchar(255)"`
RoleID uint `json:"role_id"`
Site string `json:"site" gorm:"type:varchar(255)"`
SiteID uint `json:"site_id"`
Status string `json:"status" gorm:"type:varchar(255)"`
// Latitude/Longitude are the device's own GPS coordinates if Netbox has
// them, else inherited from its site - nil if neither is set. See
// internal/netbox.syncDevice.
Latitude *float64 `json:"latitude"`
Longitude *float64 `json:"longitude"`
// OpticalKind is computed on every NetBox sync (CF then role map).
// Always assigned on the upsert struct so UpdateAll cannot zero it.
OpticalKind string `json:"optical_kind" gorm:"type:varchar(32);index"`
// OpticalKindCF is the normalized NetBox custom-field value from the
// last sync ("" if missing/invalid). Persisted so mapping CRUD can
// re-resolve without calling NetBox.
OpticalKindCF string `json:"optical_kind_cf" gorm:"type:varchar(32)"`
LibrenmsID uint `json:"librenms_id"`
// Custom fields
CfAlarmTimeperiod string `json:"cf_alarm_timeperiod" gorm:"type:varchar(255)"`
CfAlarmDestination string `json:"cf_alarm_destination" gorm:"type:varchar(255)"`
CfAlarmInterfaces bool `json:"cf_alarm_interfaces"`
CfBackupOxidized bool `json:"cf_backup_oxidized"`
CfConnectionMethod string `json:"cf_connection_method" gorm:"type:varchar(255)"`
CfLocation string `json:"cf_location" gorm:"type:varchar(255)"`
CfMonitorGrafana bool `json:"cf_monitor_grafana"`
CfMonitorIcinga bool `json:"cf_monitor_icinga"`
CfMonitorLibrenms bool `json:"cf_monitor_librenms"`
CfSource string `json:"cf_source" gorm:"type:varchar(255)"`
CfSourceID uint `json:"cf_source_id"`
Interfaces []Interface `json:"interfaces"` // gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
Tags []Tag `json:"tags"`
}
type DeviceSyncAuth ¶
type DeviceSyncAuth struct {
FactumModel
Name string `gorm:"uniqueIndex;not null" json:"name"`
Username string `json:"username"`
Password string `json:"-"`
}
DeviceSyncAuth is one set of device-login credentials internal/device-sync uses to connect directly to a device (SSH/NETCONF/eAPI) - distinct from Netbox's own API credentials (Settings.NetboxApi*). Name is either a device name (an override for that one device) or the literal "default", used as the fallback for any device without its own row. Admin-editable list, same shape as WorkerNode (Password is json:"-" for the same reason WorkerNode.Token is - never leak it via GetAll/GetOne).
type DeviceSyncAuthDTO ¶
type FactumModel ¶
type Interface ¶
type Interface struct {
FactumModel
DeviceID uint `json:"device_id" gorm:"uniqueIndex:idx_interfaces_device_id_netbox_id"`
NetboxID uint `json:"netbox_id" gorm:"uniqueIndex:idx_interfaces_device_id_netbox_id"`
Name string `json:"name" gorm:"type:varchar(255)"`
Description string `json:"description" gorm:"type:varchar(255)"`
Enabled bool `json:"enabled"`
VRF string `json:"vrf" gorm:"type:varchar(255)"`
CfRole string `json:"cf_role" gorm:"type:varchar(255)"`
// Type is Netbox's interface type (e.g. "1000base-t") - mirrors
// netboxtool.NBInterface.Type.
Type string `json:"type" gorm:"type:varchar(255)"`
// CableID is the Netbox ID of the cable terminated on this interface,
// 0 if none - mirrors netboxtool.NBInterface.CableID.
CableID uint `json:"cable_id"`
// Label is Netbox's free-text interface label, distinct from Name -
// mirrors netboxtool.NBInterface.Label.
Label string `json:"label" gorm:"type:varchar(255)"`
// ParentID is the Netbox ID of this interface's parent interface, 0 if
// none - mirrors netboxtool.NBInterface.ParentID.
ParentID uint `json:"parent_id"`
// UntaggedVLAN/TaggedVLANs are VIDs (not Netbox IDs), mirroring
// netboxtool.NBInterface.UntaggedVLAN/TaggedVLANs. UntaggedVLAN is 0 if
// unset.
UntaggedVLAN int `json:"untagged_vlan"`
TaggedVLANs []int `json:"tagged_vlans" gorm:"serializer:json"`
// SwitchportMode is "access", "trunk" or "dot1q-tunnel" (Q-in-Q), mirroring
// drivers.Interface.SwitchportMode - only meaningful on global-VLAN
// platforms (EOS, VRP), "" if not a switchport or unknown.
SwitchportMode string `json:"switchport_mode" gorm:"type:varchar(255)"`
LibrenmsID uint `json:"librenms_id"`
// runtime data
LineProtocolStatus string `gorm:"-"`
InterfaceStatus string `gorm:"-"`
Addresses []Address `json:"addresses"` // gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
Tags []Tag `json:"tags"`
// Services lists the factum services terminating on this interface,
// assembled by fetchDevices (web/handle_dcim.go) - not a DB relation
// on Interface itself. For ELINE, EndpointA/BInterfaceID is the
// physical port the operator chose; when a per-VLAN subinterface
// exists (EndpointXSubinterfaceNetboxID / "<parent>.<vlan>"), the
// service is attached to that subinterface row instead.
Services []InterfaceServiceRef `json:"services,omitempty" gorm:"-"`
// Optical is assembled by fetchDevices — not a DB relation on Interface.
Optical *OpticalPort `json:"optical,omitempty" gorm:"-"`
}
type InterfaceServiceRef ¶
InterfaceServiceRef is the minimal service info needed to link to and label a service from an interface listing, without pulling in the whole Service record.
type IpamNamespace ¶
type IpamNamespace struct {
FactumModel
Name string `json:"name" gorm:"uniqueIndex;not null;type:varchar(255)"`
Description string `json:"description" gorm:"type:varchar(255)"`
}
IpamNamespace is one unique address space. Two namespaces may both hold 10.0.0.0/8. Created with a default VRF; allowed prefixes (pools) are added separately.
func (IpamNamespace) TableName ¶
func (IpamNamespace) TableName() string
type IpamNamespaceDTO ¶
type IpamNamespacePrefix ¶
type IpamNamespacePrefix struct {
FactumModel
NamespaceID uint `json:"namespace_id" gorm:"uniqueIndex:idx_ipam_ns_pool;not null"`
Prefix string `json:"prefix" gorm:"uniqueIndex:idx_ipam_ns_pool;type:varchar(80);not null"`
Family int `json:"family"`
}
IpamNamespacePrefix is an allowed pool for a namespace. 0.0.0.0/0 and ::/0 mean any prefix of that family. Prefix is stored masked and canonical (10.1.2.3/24 → 10.1.2.0/24).
func (IpamNamespacePrefix) TableName ¶
func (IpamNamespacePrefix) TableName() string
type IpamNamespacePrefixDTO ¶
type IpamPrefix ¶
type IpamPrefix struct {
FactumModel
NamespaceID uint `json:"namespace_id" gorm:"uniqueIndex:idx_ipam_alloc_ns_pfx;not null"`
VRFID uint `json:"vrf_id" gorm:"index;not null"`
Prefix string `json:"prefix" gorm:"uniqueIndex:idx_ipam_alloc_ns_pfx;type:varchar(80);not null"`
Family int `json:"family"`
Description string `json:"description" gorm:"type:varchar(255)"`
}
IpamPrefix is a CIDR allocated to exactly one VRF in a namespace. Parent/child relationships are computed from containment, not stored.
func (IpamPrefix) TableName ¶
func (IpamPrefix) TableName() string
type IpamPrefixDTO ¶
type IpamVRF ¶
type IpamVRF struct {
FactumModel
NamespaceID uint `json:"namespace_id" gorm:"uniqueIndex:idx_ipam_vrf_ns_name;not null"`
Name string `json:"name" gorm:"uniqueIndex:idx_ipam_vrf_ns_name;not null;type:varchar(255)"`
Description string `json:"description" gorm:"type:varchar(255)"`
IsDefault bool `json:"is_default"`
}
IpamVRF is a routing domain inside a namespace. Address space is unique across VRFs of the same namespace (not classic overlapping-VRF semantics): once a prefix is allocated to one VRF, no other VRF may take it or anything that overlaps it. The default VRF is created with the namespace and cannot be deleted.
type IpamVRFIDTO ¶
type Job ¶
type Job struct {
FactumModel
Type string `gorm:"index;not null;default:sync" json:"type"`
TriggeredBy string `json:"triggered_by"`
StartedAt time.Time `json:"started_at"`
FinishedAt *time.Time `json:"finished_at,omitempty"`
// ExpectedTasks is the total number of JobTasks this job will end up
// with, set once at creation. A sequential batch (StartJob's
// multi-target path) creates its JobTask rows one at a time as each
// target's turn comes up rather than all at once, so
// RemoteManager.resolveTask can't tell "done" from "no rows created
// yet" by counting unfinished rows alone - it also checks the finished
// row count against ExpectedTasks. Internal bookkeeping, not part of
// the REST API surface (json:"-"), like JobTask.TaskID.
ExpectedTasks int `json:"-"`
Tasks []JobTask `gorm:"foreignKey:JobID" json:"tasks,omitempty"`
}
Job is one triggered unit of work (e.g. one click of "Sync DNS", or one "Sync all"), made up of one or more JobTasks - one per target. Type lets future non-sync job kinds reuse this table without another schema change; "sync" is the only value produced today.
type JobSchedule ¶
type JobSchedule struct {
FactumModel
Name string `gorm:"not null" json:"name"`
Enabled bool `json:"enabled"`
Target string `gorm:"not null;index" json:"target"`
Cron string `gorm:"not null" json:"cron"`
// LastRunAt is when the scheduler last claimed a due run - it is
// stamped even if StartJob then fails (LastError), so a busy target
// doesn't get retried every tick.
LastRunAt *time.Time `json:"last_run_at,omitempty"`
NextRunAt *time.Time `gorm:"index" json:"next_run_at,omitempty"`
LastError string `json:"last_error,omitempty"`
CreatedBy string `json:"created_by"`
}
JobSchedule is one user-defined periodic trigger for a sync job, the in-app replacement for a crontab entry on the primary. Target is either one worker.SyncTargets name (a single-target StartJob, same as clicking one tile on Job overview) or the sentinel "all" (SequencedSyncAllTargets + StartJob, same as "Sync all"). Cron is a 5-field expression evaluated in Europe/Stockholm; NextRunAt is computed at create/update and advanced by the scheduler when a run is claimed so a restart only ever catch-up fires once, not once per missed tick.
type JobScheduleDTO ¶
type JobScheduleDTO struct {
ID uint `json:"id"`
Name string `json:"name"`
Enabled bool `json:"enabled"`
Target string `json:"target"`
Cron string `json:"cron"`
}
JobScheduleDTO is the writable subset of JobSchedule - LastRunAt/ NextRunAt/LastError/CreatedBy are server-owned, same reason LinkDTO omits Position from ordinary edits.
type JobTask ¶
type JobTask struct {
FactumModel
JobID uint `gorm:"index;not null" json:"job_id"`
TaskID string `gorm:"uniqueIndex;not null" json:"-"`
Target string `gorm:"index" json:"target"`
StartedAt time.Time `json:"started_at"`
FinishedAt *time.Time `json:"finished_at,omitempty"`
ExitCode int `json:"exit_code"`
Err string `json:"err,omitempty"`
ErrorCount int `gorm:"not null;default:0" json:"error_count"`
WarningCount int `gorm:"not null;default:0" json:"warning_count"`
}
JobTask is one dispatched run of a single target (web.ApiSyncTrigger/ ApiSyncTriggerAll) within a Job - the direct successor of the old SyncJob row, minus TriggeredBy (now only on the parent Job). TaskID is the same ID used for wire-protocol correlation over the hub transport (internal/worker/hub.go's StartCommand/RunAndWait), not a separate ID space - it's an internal transport detail, not part of the REST API surface (json:"-"), which addresses tasks by their ordinary numeric ID like every other resource. FinishedAt is set by the same code path that already detects command completion (internal/worker.LogMsg's StreamExit line) - see RemoteManager.resolveTask. ErrorCount/WarningCount are denormalized, incremented alongside each JobTaskEvent insert (RemoteManager's EnvelopeEvent handler) so the job list can show counts without a live COUNT(*) over events on every poll.
type JobTaskEvent ¶
type JobTaskEvent struct {
FactumModel
JobTaskID *uint `gorm:"index" json:"job_task_id,omitempty"`
TaskID string `gorm:"index;not null" json:"-"`
Target string `json:"target"`
Level string `json:"level"`
Message string `json:"message"`
At time.Time `json:"at"`
}
JobTaskEvent is one info/warning/error line reported by a sync tool via internal/jobevent.Reporter, relayed over the hub transport's "event" envelope and persisted by internal/worker.RemoteManager. JobTaskID is nullable - a --job-flagged command run outside a tracked job (e.g. ad hoc "factum-worker run") still produces rows here with no owning JobTask, they're just never queried since nothing links to them from the UI.
type LdapRoleMapping ¶
type LdapRoleMapping struct {
FactumModel
GroupDN string `gorm:"uniqueIndex;not null" json:"group_dn"`
RoleID uint `gorm:"not null" json:"role_id"`
}
LdapRoleMapping maps one LDAP/AD group DN to one Factum Role. Re-evaluated on every successful LDAP-backed login (see web/auth.go's syncLDAPRoles), so role changes in the directory propagate automatically without local admin action.
type LdapRoleMappingDTO ¶
type LibrenmsPendingDelete ¶
type LibrenmsPendingDelete struct {
FactumModel
DeviceID int `gorm:"uniqueIndex;not null;column:device_id" json:"device_id"`
Hostname string `json:"hostname"`
Display string `json:"display"`
Reason string `json:"reason"`
ScheduledAt time.Time `json:"scheduled_at"`
ForceDelete bool `json:"force_delete"`
}
LibrenmsPendingDelete is one LibreNMS device that sync has quarantined rather than deleted outright. DeviceID is LibreNMS's device_id (not a factum device id) - the row can outlive the factum device, which is the usual reason it was queued. Display is the original LibreNMS display name without the "(scheduled for deletion …)" suffix sync stamps while the device is quarantined.
type Link ¶
type Link struct {
FactumModel
Group string `gorm:"index;not null" json:"group"`
Name string `gorm:"not null" json:"name"`
URL string `gorm:"not null" json:"url"`
OpenInNewTab bool `json:"open_in_new_tab"`
// Icon is an optional "data:<mime>;base64,..." URI, uploaded client-side
// and stored inline rather than as a file on disk - a release build's
// static assets are compiled into the binary (web/fs_release.go) and
// read-only at runtime, so there's nowhere to write an uploaded file to.
Icon string `gorm:"type:text" json:"icon,omitempty"`
Position int `json:"position"`
}
Link is one admin-managed shortcut shown on the dashboard (Admin -> Settings -> Dashboard), grouped under Group (a free-text section heading) and ordered within the whole list by Position - see web/handle_links.go's ApiLinkCreate/ApiLinksReorder for how the two are maintained. Readable by every logged-in user (GET /api/links), editable by admins only (/api/admin/links).
type LinkDTO ¶
type LinkDTO struct {
ID uint `json:"id"`
Group string `json:"group"`
Name string `json:"name"`
URL string `json:"url"`
OpenInNewTab bool `json:"open_in_new_tab"`
Icon string `json:"icon,omitempty"`
// Position is omitempty so the generic Update handler's DTO<->model
// round trip (web/handle_crud.go) leaves the stored value alone when a
// regular edit omits it - only ApiLinkCreate (append) and
// ApiLinksReorder (drag-and-drop) ever set it, same convention as
// WorkerNodeDTO.Token/DeviceSyncAuthDTO.Password leaving those fields
// unchanged when blank.
Position int `json:"position,omitempty"`
}
type MaintenanceNotification ¶
type MaintenanceNotification struct {
FactumModel
WindowID uint `json:"window_id" gorm:"index;not null"`
CustomerID uint `json:"customer_id" gorm:"index;not null"`
ServiceIDs []uint `json:"service_ids" gorm:"serializer:json"`
ContactID *uint `json:"contact_id"`
Email string `json:"email"`
SentAt *time.Time `json:"sent_at"`
Status string `json:"status" gorm:"type:varchar(16)"`
Error string `json:"error" gorm:"type:text"`
}
MaintenanceNotification is one recipient row for a window.
type MaintenanceWindow ¶
type MaintenanceWindow struct {
FactumModel
Title string `json:"title" gorm:"type:varchar(255);not null"`
Description string `json:"description" gorm:"type:text"`
ResourceType string `json:"resource_type" gorm:"type:varchar(16);not null;index"`
ResourceID uint `json:"resource_id" gorm:"index;not null"`
StartsAt time.Time `json:"starts_at" gorm:"index;not null"`
EndsAt time.Time `json:"ends_at"`
Status string `json:"status" gorm:"type:varchar(16);not null;index"`
CreatedBy uint `json:"created_by"`
}
MaintenanceWindow is a scheduled outage on a connection, device, or interface.
type OpticalKindMap ¶
type OpticalKindMap struct {
FactumModel
NetboxRoleName string `json:"netbox_role_name" gorm:"uniqueIndex;type:varchar(255);not null"`
OpticalKind string `json:"optical_kind" gorm:"type:varchar(32);not null"`
}
OpticalKindMap maps a NetBox device role display name (lowercased) to a Factum optical kind. Admin-editable.
type OpticalKindMapDTO ¶
type OpticalPort ¶
type OpticalPort struct {
FactumModel
InterfaceID uint `json:"interface_id" gorm:"uniqueIndex;not null"`
Role string `json:"role" gorm:"type:varchar(32);not null;index"`
FreqHz uint64 `json:"freq_hz"`
ITUChannel *int `json:"itu_channel"`
Notes string `json:"notes" gorm:"type:varchar(255)"`
}
OpticalPort is Factum-owned optical metadata for a NetBox-synced interface. Separate table so syncInterfaces UpdateAll cannot wipe it.
type OpticalXConnect ¶
type OpticalXConnect struct {
FactumModel
DeviceID uint `json:"device_id" gorm:"index;not null"`
Kind string `json:"kind" gorm:"type:varchar(32);not null;index"`
InterfaceAID uint `json:"interface_a_id" gorm:"index;not null"`
InterfaceBID uint `json:"interface_b_id" gorm:"index;not null"`
FreqHz uint64 `json:"freq_hz"`
}
OpticalXConnect is one intra-device optical adjacency.
type PasswordResetToken ¶
type PasswordResetToken struct {
FactumModel
UserID uint `gorm:"index;not null" json:"-"`
TokenHash string `gorm:"uniqueIndex;not null" json:"-"`
CodeHash string `gorm:"not null" json:"-"`
ExpiresAt time.Time `json:"-"`
// Attempts counts failed code redemption tries against this row - the
// code is only 8 digits (looked up by email, not by hash), so this
// caps brute-forcing it before ExpiresAt would otherwise.
Attempts int `json:"-"`
ConsumedAt *time.Time `json:"-"`
}
PasswordResetToken backs the forgot-password flow (web.ApiForgotPassword/ ApiResetPassword). One row per outstanding request: TokenHash matches the long random token embedded in the emailed link, CodeHash matches the short human-typeable code shown alongside it - both derive from the same crypto/rand-generated secrets, so a fast deterministic hash is fine for both (unlike a user-chosen password, there's no dictionary smaller than the keyspace to attack). Either one redeems the request; redeeming consumes it.
type Product ¶
type Product struct {
FactumModel
LastSync uint `json:"-"`
Name string `json:"name"`
}
type Role ¶
type Role struct {
FactumModel
Name string `gorm:"uniqueIndex;not null" json:"name"`
Description string `json:"description"`
Users []*User `gorm:"many2many:user_roles;" json:"-"`
}
Role defines user roles (e.g., admin, user)
type Service ¶
type Service struct {
FactumModel
LastSync uint `json:"-"`
Name string `json:"name"`
CustomerID uint `json:"company"`
Comment string `json:"comment"`
ServiceID string `json:"service_id"` // <category><5-digit>, e.g. CI00001 - the 2-letter prefix is the category (CI, VI, LI, ...), so it isn't stored separately
ServiceType string `json:"service_type"` // ELINE, ELAN, L3VPN, POLARIX - CI/CN only, else ""
BandwidthMbps int `json:"bandwidth_mbps"`
MaxMacAddresses int `json:"max_mac_addresses"` // ELAN only, else 0
DeliveryPoint1 string `json:"deliverypoint1"`
DeliveryPoint2 string `json:"deliverypoint2"`
Product string `json:"product"`
Service string `json:"service"`
AgreementStatus string `json:"agreement_status"` // Lime agreement_status.text (e.g. "Active"); empty for factum-created rows
// ELINE endpoint/provisioning data (ServiceType == "ELINE" only, zero
// otherwise) - set via PUT /service/:id/eline
// (web/handler_service_eline.go), which also creates the matching
// Netbox L2VPN + L2VPNTerminations. EndpointXInterfaceID is the
// physical interface (factum Interface.ID) chosen by the user;
// EndpointXSubinterfaceNetboxID is the Netbox ID of the per-VLAN
// subinterface created to carry the L2VPN termination.
EndpointADeviceID uint `json:"endpoint_a_device_id"`
EndpointAInterfaceID uint `json:"endpoint_a_interface_id"`
EndpointAVlan int `json:"endpoint_a_vlan"`
EndpointASubinterfaceNetboxID uint `json:"endpoint_a_subinterface_netbox_id"`
EndpointATerminationNetboxID uint `json:"endpoint_a_termination_netbox_id"`
EndpointBDeviceID uint `json:"endpoint_b_device_id"`
EndpointBInterfaceID uint `json:"endpoint_b_interface_id"`
EndpointBVlan int `json:"endpoint_b_vlan"`
EndpointBSubinterfaceNetboxID uint `json:"endpoint_b_subinterface_netbox_id"`
EndpointBTerminationNetboxID uint `json:"endpoint_b_termination_netbox_id"`
// AppliedEndpointX* record what's actually live on the devices as of
// the last successful PUT .../eline/push (web/handler_service_eline.go,
// ApiServiceElinePush) - distinct from EndpointX* above, which is the
// desired state Netbox/the DB already reflects the moment PUT
// .../eline runs. A zero AppliedEndpointXDeviceID means this side has
// never been pushed yet. Comparing Applied* against Endpoint* on each
// push is what lets a re-provision (interface/VLAN/device edit) find
// and remove the stale subinterface/pseudowire/patch a previous push
// left behind, on whichever device still has it - see
// elineComputeStale. Only advanced once the corresponding push (and
// any stale cleanup it required) actually succeeds, so a partial
// failure is retried on the next push rather than silently forgotten.
AppliedEndpointADeviceID uint `json:"-"`
AppliedEndpointAIface string `json:"-"`
AppliedEndpointAVlan int `json:"-"`
AppliedEndpointBDeviceID uint `json:"-"`
AppliedEndpointBIface string `json:"-"`
AppliedEndpointBVlan int `json:"-"`
// PseudowireID is derived from ServiceID (pseudowireIDFromServiceID,
// web/handler_service_eline.go) and stored as the Netbox L2VPN's
// identifier.
PseudowireID int `json:"pseudowire_id"`
L2VPNNetboxID uint `json:"l2vpn_netbox_id"`
Source string `json:"source"`
SourceID string `json:"source_id"`
}
func (*Service) BeforeCreate ¶
BeforeCreate defaults a service's Source to "factum" when nothing set it already. The Lime sync job (internal/lime/lime.go) always sets Source to "lime" itself before saving, so this only ever fires for services created through the web API - ServiceDTO has no Source field for a caller to set, so without this default every manually-created service would be indistinguishable (Source == "") from one whose sync origin just hasn't been recorded yet, instead of clearly marked as user-created.
type ServiceDTO ¶
type ServiceDTO struct {
ID uint `json:"id"`
Name string `json:"name"`
CustomerID uint `json:"company"`
Comment string `json:"comment"`
ServiceID string `json:"service_id"`
Category string `json:"category"`
ServiceType string `json:"service_type"`
BandwidthMbps int `json:"bandwidth_mbps"`
MaxMacAddresses int `json:"max_mac_addresses"`
DeliveryPoint1 string `json:"deliverypoint1"`
DeliveryPoint2 string `json:"deliverypoint2"`
Product string `json:"product"`
Service string `json:"service"`
}
ServiceDTO is the create/update request shape for the service API, mirroring the fields the frontend's service form actually edits (web/frontend/src/views/service/ServiceList.vue, web/frontend/src/views/service/ServiceCreateWizard.vue) - it shows "source" and "agreement_status" as disabled/display-only fields, so, like ContactDTO, Source/SourceID/AgreementStatus are excluded here rather than merely disabled client-side: the sync-managed values survive an Update untouched, and a manually-created service gets the zero value instead of a caller being able to fake a Lime origin or agreement status.
On create (ApiServiceCreate), ServiceID is optional: a blank value has the backend auto-assign the next <category><5-digit> number for Category, rather than the wizard reserving one up front.
type ServiceHop ¶
type ServiceHop struct {
FactumModel
ServiceID uint `json:"service_id" gorm:"index;not null"`
Seq int `json:"seq" gorm:"not null"`
Kind string `json:"kind" gorm:"type:varchar(16);not null;index"`
InterfaceID *uint `json:"interface_id" gorm:"index"`
ConnectionID *uint `json:"connection_id" gorm:"index"`
XConnectID *uint `json:"xconnect_id" gorm:"index"`
DeviceID *uint `json:"device_id" gorm:"index"`
FreqHz uint64 `json:"freq_hz"`
Label string `json:"label" gorm:"type:varchar(255)"`
}
ServiceHop is one row of a materialized path. Exactly one Kind / FK.
type ServicePath ¶
type ServicePath struct {
FactumModel
ServiceID uint `json:"service_id" gorm:"uniqueIndex;not null"`
Mode string `json:"mode" gorm:"type:varchar(16);not null"`
Status string `json:"status" gorm:"type:varchar(16);not null;index"`
EndpointAInterfaceID uint `json:"endpoint_a_interface_id" gorm:"index;not null"`
EndpointZInterfaceID uint `json:"endpoint_z_interface_id" gorm:"index;not null"`
StartKindA string `json:"start_kind_a" gorm:"type:varchar(32)"`
StartKindZ string `json:"start_kind_z" gorm:"type:varchar(32)"`
FreqHz uint64 `json:"freq_hz"`
LastTracedAt *time.Time `json:"last_traced_at"`
LastTraceError string `json:"last_trace_error" gorm:"type:text"`
Hops []ServiceHop `json:"hops,omitempty" gorm:"foreignKey:ServiceID;references:ServiceID"`
}
ServicePath is the attached optical/fiber path for one VL/VI/LF/LI service.
type Settings ¶
type Settings struct {
FactumModel
// features activated
BecsEnabled *bool `gorm:"column:becs_enabled" form:"becs_enabled" json:"becs_enabled"`
DnsEnabled *bool `gorm:"column:dns_enabled" form:"dns_enabled" json:"dns_enabled"`
IcingaEnabled *bool `gorm:"column:icinga_enabled" form:"icinga_enabled" json:"icinga_enabled"`
// OpticalEnabled gates WDM / ROADM / wavelength-path UI and APIs.
// Off (nil/false) is the default. Same *bool shape as the other flags.
OpticalEnabled *bool `gorm:"column:optical_enabled" form:"optical_enabled" json:"optical_enabled"`
// IpamEnabled gates the standalone IPAM UI and /api/ipam/* routes.
// Off (nil/false) is the default. Turning it off only hides the
// feature — namespaces, VRFs and prefixes stay in the database.
IpamEnabled *bool `gorm:"column:ipam_enabled" form:"ipam_enabled" json:"ipam_enabled"`
LibrenmsEnabled *bool `gorm:"column:librenms_enabled" form:"librenms_enabled" json:"librenms_enabled"`
LimeEnabled *bool `gorm:"column:lime_enabled" form:"lime_enabled" json:"lime_enabled"`
NetboxEnabled *bool `gorm:"column:netbox_enabled" form:"netbox_enabled" json:"netbox_enabled"`
OxidizedEnabled *bool `gorm:"column:oxidized_enabled" form:"oxidized_enabled" json:"oxidized_enabled"`
DeviceSyncEnabled *bool `gorm:"column:device_sync_enabled" form:"device_sync_enabled" json:"device_sync_enabled"`
// factum
FactumApiToken string `gorm:"column:factum_api_token" form:"factum_api_token" json:"factum_api_token"`
// PublicBaseURL is the externally-reachable origin (e.g.
// "https://factum.example.com") used to build absolute links in
// outgoing email, currently just the password-reset link
// (web.ApiForgotPassword). If left blank, the reset handler falls back
// to deriving an origin from the incoming request instead - fine for
// simple setups, but a deployment behind a reverse proxy should set
// this explicitly rather than trusting Host/X-Forwarded-* headers for
// something that ends up in an email.
PublicBaseURL string `gorm:"column:public_base_url" form:"public_base_url" json:"public_base_url"`
// DefaultDomain is shared by every downstream sync target
// (DNS/Icinga/LibreNMS/Oxidized), not just DNS - it's the $ORIGIN of the
// generated DNS zone file, and also used to match factum device names
// against fully-qualified DNS names during Icinga/LibreNMS/Oxidized
// sync. It lives here rather than in local YAML config so each CLI tool
// can fetch it over REST (via util.CommonConfig) like the rest of its
// settings, since they typically run on a different host than the
// primary.
DefaultDomain string `gorm:"column:default_domain" form:"default_domain" json:"default_domain"`
// BECS
BecsEapiURL string `gorm:"column:becs_eapi_url" form:"becs_eapi_url" json:"becs_eapi_url"`
BecsEapiUser string `gorm:"column:becs_eapi_user" form:"becs_eapi_user" json:"becs_eapi_user"`
BecsEapiPass string `gorm:"column:becs_eapi_pass" form:"becs_eapi_pass" json:"becs_eapi_pass"`
BecsEapiOID uint `gorm:"column:becs_eapi_oid" form:"becs_eapi_oid" json:"becs_eapi_oid"`
// DNS
DnsDestFile string `gorm:"column:dns_dest_file" form:"dns_dest_file" json:"dns_dest_file"`
// IgnoreModels/IgnorePlatforms are newline-separated lists (one model or
// platform name per line), edited as a multiline box in the admin UI -
// internal/dns.filterDevices skips a device matching either.
DnsIgnoreModels string `gorm:"column:dns_ignore_models;type:text" form:"dns_ignore_models" json:"dns_ignore_models"`
DnsIgnorePlatforms string `gorm:"column:dns_ignore_platforms;type:text" form:"dns_ignore_platforms" json:"dns_ignore_platforms"`
// Email / SMTP - a general-purpose outbound mail relay, not tied to
// Icinga specifically (factum-icinga-notifications is the first
// consumer, via util.CommonConfig, but any tool that needs to send
// mail can fetch the same settings).
SmtpHost string `gorm:"column:smtp_host" form:"smtp_host" json:"smtp_host"`
SmtpPort uint16 `gorm:"column:smtp_port" form:"smtp_port" json:"smtp_port"`
SmtpUser string `gorm:"column:smtp_user" form:"smtp_user" json:"smtp_user"`
SmtpPass string `gorm:"column:smtp_pass" form:"smtp_pass" json:"smtp_pass"`
// SmtpTLSMode is "none" | "starttls" | "tls", same convention as LdapTLSMode.
SmtpTLSMode string `gorm:"column:smtp_tls_mode" form:"smtp_tls_mode" json:"smtp_tls_mode"`
// EmailSender is the default From address for outgoing notification email.
EmailSender string `gorm:"column:email_sender" form:"email_sender" json:"email_sender"`
// Icinga
IcingaApiURL string `gorm:"column:icinga_api_url" form:"icinga_api_url" json:"icinga_api_url"`
IcingaApiUser string `gorm:"column:icinga_api_user" form:"icinga_api_user" json:"icinga_api_user"`
IcingaApiPass string `gorm:"column:icinga_api_pass" form:"icinga_api_pass" json:"icinga_api_pass"`
IcingaHostsFile string `gorm:"column:icinga_hosts_file" form:"icinga_hosts_file" json:"icinga_hosts_file"`
IcingaUsersFile string `gorm:"column:icinga_users_file" form:"icinga_users_file" json:"icinga_users_file"`
// IcingaIgnoreDevices is a newline-separated list of device names (one
// per line) that factum-icinga's Update() skips entirely.
IcingaIgnoreDevices string `gorm:"column:icinga_ignore_devices;type:text" form:"icinga_ignore_devices" json:"icinga_ignore_devices"`
// IcingaDefaultNotification is the Icinga config line(s) applied to a
// host that has no cf_alarm_destination set, e.g.
// ` vars.pe_notify_default = true`.
IcingaDefaultNotification string `gorm:"column:icinga_default_notification;type:text" form:"icinga_default_notification" json:"icinga_default_notification"`
// IcingaHostTemplate/IcingaDependencyTemplate/IcingaUserTemplate are
// Go text/template (see internal/icinga/factum-icinga.go for the data
// each is executed with).
IcingaHostTemplate string `gorm:"column:icinga_host_template;type:text" form:"icinga_host_template" json:"icinga_host_template"`
IcingaDependencyTemplate string `gorm:"column:icinga_dependency_template;type:text" form:"icinga_dependency_template" json:"icinga_dependency_template"`
IcingaUserTemplate string `gorm:"column:icinga_user_template;type:text" form:"icinga_user_template" json:"icinga_user_template"`
// Librenms
LibrenmsApiURL string `gorm:"column:librenms_api_url" form:"librenms_api_url" json:"librenms_api_url"`
LibrenmsApiToken string `gorm:"column:librenms_api_token" form:"librenms_api_token" json:"librenms_api_token"`
// LibrenmsPersistentDevices is a newline-separated list, edited as a
// multiline box in the admin UI, same convention as the Ignore* fields
// above. FactumLibrenmsClient.Sync never quarantines or deletes a
// LibreNMS device whose hostname or display name matches a line here.
LibrenmsPersistentDevices string `gorm:"column:librenms_persistent_devices;type:text" form:"librenms_persistent_devices" json:"librenms_persistent_devices"`
// LibrenmsDelayedDeleteEnabled gates the LibreNMS delete path. Off
// (nil/false, the default) means sync never removes a device. On means
// a delete candidate is quarantined (polling and alerts off, display
// name stamped with the scheduled date) and actually deleted on a later
// sync once ScheduledAt has passed, or sooner if the user sets
// LibrenmsPendingDelete.ForceDelete. Same *bool shape as the other flags.
LibrenmsDelayedDeleteEnabled *bool `gorm:"column:librenms_delayed_delete_enabled" form:"librenms_delayed_delete_enabled" json:"librenms_delayed_delete_enabled"`
// LibrenmsDelayedDeleteDays is how long a quarantined device stays in
// LibreNMS before the next sync deletes it. Values < 1 are treated as
// 30 by sync (the documented default), so an unset column cannot
// accidentally delete on the following run.
LibrenmsDelayedDeleteDays int `gorm:"column:librenms_delayed_delete_days" form:"librenms_delayed_delete_days" json:"librenms_delayed_delete_days"`
// LibrenmsRolesEnabled/InterfacesDisabled are newline-separated lists of
// regexes (one per line) - FactumLibrenmsClient.syncInterfaces compiles
// each line and matches it against an interface's factum role
// (RolesEnabled) or name (InterfacesDisabled) to force alerting on/off
// respectively, overriding the device's CfAlarmInterfaces default.
LibrenmsRolesEnabled string `gorm:"column:librenms_roles_enabled;type:text" form:"librenms_roles_enabled" json:"librenms_roles_enabled"`
LibrenmsInterfacesDisabled string `gorm:"column:librenms_interfaces_disabled;type:text" form:"librenms_interfaces_disabled" json:"librenms_interfaces_disabled"`
// LibrenmsSNMPVersion is the SNMP version (e.g. "v1", "v2c", "v3") used
// when creating devices in LibreNMS.
LibrenmsSNMPVersion string `gorm:"column:librenms_snmp_version" form:"librenms_snmp_version" json:"librenms_snmp_version"`
// LibrenmsSNMPCommunities is a newline-separated list of SNMP
// communities (one per line, tried in order), same convention as the
// Ignore* fields above - FactumLibrenmsClient.Sync tries each in turn
// when creating a device in LibreNMS, force-adding with the first one
// if none succeed.
LibrenmsSNMPCommunities string `gorm:"column:librenms_snmp_communities;type:text" form:"librenms_snmp_communities" json:"librenms_snmp_communities"`
// Lime
LimeApiURL string `gorm:"column:lime_api_url" form:"lime_api_url" json:"lime_api_url"`
LimeApiToken string `gorm:"column:lime_api_token" form:"lime_api_token" json:"lime_api_token"`
// Netbox
NetboxApiURL string `gorm:"column:netbox_api_url" form:"netbox_api_url" json:"netbox_api_url"`
NetboxApiToken string `gorm:"column:netbox_api_token" form:"netbox_api_token" json:"netbox_api_token"`
// NetboxWebhookSecret is the shared secret Netbox signs its webhook
// request bodies with (HMAC-SHA512, "X-Hook-Signature" header) - see
// web.ApiNetboxWebhook. Configured the same way on the Netbox side, as
// the webhook's "secret" field.
NetboxWebhookSecret string `gorm:"column:netbox_webhook_secret" form:"netbox_webhook_secret" json:"netbox_webhook_secret"`
// NetboxSyncCustomersEnabled, when set, makes FactumSyncNetbox also
// push factum customers to Netbox as tenants (custom fields
// source/source_id identify which customer a tenant came from).
NetboxSyncCustomersEnabled *bool `gorm:"column:netbox_sync_customers_enabled" form:"netbox_sync_customers_enabled" json:"netbox_sync_customers_enabled"`
// Oxidized
OxidizedApiURL string `gorm:"column:oxidized_api_url" form:"oxidized_api_url" json:"oxidized_api_url"`
OxidizedApiUser string `gorm:"column:oxidized_api_user" form:"oxidized_api_user" json:"oxidized_api_user"`
OxidizedApiPass string `gorm:"column:oxidized_api_pass" form:"oxidized_api_pass" json:"oxidized_api_pass"`
// OxidizedDestFile is oxidized's own router.db - internal/oxidized
// writes the filtered device list here (name:model per line).
OxidizedDestFile string `gorm:"column:oxidized_dest_file" form:"oxidized_dest_file" json:"oxidized_dest_file"`
// OxidizedIgnoreDevices/Manufacturers/Models/Platforms are
// newline-separated lists, edited as multiline boxes in the admin UI -
// a device matching any one of them is skipped by
// FactumOxidizedClient.Sync.
OxidizedIgnoreDevices string `gorm:"column:oxidized_ignore_devices;type:text" form:"oxidized_ignore_devices" json:"oxidized_ignore_devices"`
OxidizedIgnoreManufacturers string `` /* 127-byte string literal not displayed */
OxidizedIgnoreModels string `gorm:"column:oxidized_ignore_models;type:text" form:"oxidized_ignore_models" json:"oxidized_ignore_models"`
OxidizedIgnorePlatforms string `gorm:"column:oxidized_ignore_platforms;type:text" form:"oxidized_ignore_platforms" json:"oxidized_ignore_platforms"`
// Device Sync (internal/device-sync) - VRFInGlobal/DeviceStates/
// DeviceIgnore are newline-separated lists, edited as multiline boxes in
// the admin UI, same convention as Oxidized's Ignore* fields above.
// Per-device login credentials aren't here - see DeviceSyncAuth.
//
// DeviceSyncVRFInGlobal lists VRF names whose addresses are allocated in
// the global routing table on these devices (to avoid duplicate-address
// rejections in Netbox) - device-sync compares/creates addresses in
// those VRFs as if they had no VRF at all.
DeviceSyncVRFInGlobal string `gorm:"column:device_sync_vrf_in_global;type:text" form:"device_sync_vrf_in_global" json:"device_sync_vrf_in_global"`
// DeviceSyncDeviceStates lists the Netbox device status values (e.g.
// "Active") eligible for sync - a device in any other state is skipped.
DeviceSyncDeviceStates string `gorm:"column:device_sync_device_states;type:text" form:"device_sync_device_states" json:"device_sync_device_states"`
// DeviceSyncDeviceIgnore lists device names to always skip.
DeviceSyncDeviceIgnore string `gorm:"column:device_sync_device_ignore;type:text" form:"device_sync_device_ignore" json:"device_sync_device_ignore"`
// DeviceSyncVlanGroupName is the Netbox VLAN Group (global, not scoped
// to a site) that every VLAN discovered across all synced devices is
// created in, along with each interface's untagged/tagged VLAN
// assignment. Empty disables VLAN/interface-VLAN sync entirely.
DeviceSyncVlanGroupName string `` /* 129-byte string literal not displayed */
// LDAP / Active Directory authentication + authorization. Connection
// fields are edited from the admin "Authentication" page,
// LdapDefaultRoleID from the "Authorization" page - both just load/save
// this same Settings singleton, like every other integration here.
LdapEnabled *bool `gorm:"column:ldap_enabled" form:"ldap_enabled" json:"ldap_enabled"`
// LdapServerType is "ad" | "generic" (OpenLDAP-compatible). Drives
// server-specific behavior that differs between the two (e.g. how a
// password change is performed) rather than being purely cosmetic.
LdapServerType string `gorm:"column:ldap_server_type" form:"ldap_server_type" json:"ldap_server_type"`
LdapHost string `gorm:"column:ldap_host" form:"ldap_host" json:"ldap_host"`
LdapPort uint16 `gorm:"column:ldap_port" form:"ldap_port" json:"ldap_port"`
// LdapTLSMode is "none" | "starttls" | "ldaps".
LdapTLSMode string `gorm:"column:ldap_tls_mode" form:"ldap_tls_mode" json:"ldap_tls_mode"`
LdapSkipTLSVerify *bool `gorm:"column:ldap_skip_tls_verify" form:"ldap_skip_tls_verify" json:"ldap_skip_tls_verify"`
// LdapBindDN/LdapBindPassword are the service account used for the
// search+bind flow (see internal/ldapauth.Authenticate) - stored in
// plaintext like every other integration credential in this struct.
LdapBindDN string `gorm:"column:ldap_bind_dn" form:"ldap_bind_dn" json:"ldap_bind_dn"`
LdapBindPassword string `gorm:"column:ldap_bind_password" form:"ldap_bind_password" json:"ldap_bind_password"`
LdapBaseDN string `gorm:"column:ldap_base_dn" form:"ldap_base_dn" json:"ldap_base_dn"`
// LdapUserFilter is a fmt.Sprintf template with one %s for the escaped
// username, e.g. "(sAMAccountName=%s)" for AD or "(uid=%s)" for
// OpenLDAP.
LdapUserFilter string `gorm:"column:ldap_user_filter" form:"ldap_user_filter" json:"ldap_user_filter"`
// LdapAttrUsername is the attribute holding the login-username value
// (matched against LdapUserFilter's %s) - "sAMAccountName" for AD,
// "uid" for OpenLDAP by default. Only used by the forgot-password
// flow's LDAP-by-email lookup to auto-provision a not-yet-provisioned
// LDAP user's local row; ordinary login already knows the username
// from what was typed into the form.
LdapAttrUsername string `gorm:"column:ldap_attr_username" form:"ldap_attr_username" json:"ldap_attr_username"`
LdapAttrEmail string `gorm:"column:ldap_attr_email" form:"ldap_attr_email" json:"ldap_attr_email"`
LdapAttrDisplayName string `gorm:"column:ldap_attr_display_name" form:"ldap_attr_display_name" json:"ldap_attr_display_name"`
// LdapAttrMobile is the attribute holding the user's phone/mobile
// number, e.g. "mobile" for both AD and OpenLDAP.
LdapAttrMobile string `gorm:"column:ldap_attr_mobile" form:"ldap_attr_mobile" json:"ldap_attr_mobile"`
// LdapAttrGroups is the attribute holding group DNs on the user entry,
// e.g. "memberOf" for both AD and OpenLDAP (with the memberOf overlay).
LdapAttrGroups string `gorm:"column:ldap_attr_groups" form:"ldap_attr_groups" json:"ldap_attr_groups"`
// LdapDefaultRoleID is the fallback Role granted to an LDAP-authenticated
// user whose group DNs match no LdapRoleMapping row. Nil = no fallback.
LdapDefaultRoleID *uint `gorm:"column:ldap_default_role_id" form:"ldap_default_role_id" json:"ldap_default_role_id"`
// LdapAllowPasswordChange opts into writing a password reset back to the
// directory for LDAP-backed users (self-service "Change password" on
// the User Settings page, and the forgot-password flow) instead of
// refusing it outright. The elevated credentials that actually perform
// the directory write are NOT here - they're config-file-only
// (util.ConfigRoot.LdapWriteback), deliberately never exposed through
// this Settings row or its admin API, since they're far more powerful
// than the read-only LdapBindDN/LdapBindPassword service account above.
LdapAllowPasswordChange *bool `gorm:"column:ldap_allow_password_change" form:"ldap_allow_password_change" json:"ldap_allow_password_change"`
}
Settings is stored in database as a single row (id=1)
type Site ¶
type Site struct {
FactumModel
NetboxID uint `json:"netbox_id" gorm:"uniqueIndex"`
Name string `json:"name" gorm:"type:varchar(255)"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
}
Site is a Netbox dcim.Site, synced independently of Device (see internal/netbox.syncSites) so a site with GPS coordinates but no devices still has somewhere to be recorded - Device.Site/SiteID alone only ever reference sites that have at least one device.
type Tag ¶
type Tag struct {
FactumModel
DeviceID *uint `json:"device_id"`
InterfaceID *uint `json:"interface_id"`
NetboxID uint `json:"netbox_id"`
Name string `json:"name" gorm:"type:varchar(255)"`
}
Tag is shared by device-tags and interface-tags: exactly one of DeviceID/InterfaceID is set, the other is nil (SQL NULL). They are pointers rather than plain uint so the unused side is stored as NULL, not 0 -- the fk_devices_tags/fk_interfaces_tags constraints reject a literal 0 since no device/interface has id 0.
type User ¶
type User struct {
FactumModel
Username string `gorm:"uniqueIndex;not null" json:"username"`
PasswordHash string `gorm:"not null" json:"-"`
Name string `gorm:"not null" json:"name"`
Email string `json:"email"`
Mobile string `json:"mobile"`
Roles []*Role `gorm:"many2many:user_roles;;constraint:OnDelete:CASCADE;" json:"-"`
}
User represents an application user
type UserDTO ¶
type UserDTO struct {
ID uint `json:"id"`
Username string `json:"username"`
Name string `json:"name"`
Email string `json:"email"`
Mobile string `json:"mobile"`
Roles []string `json:"roles"`
RoleIDs []uint `json:"role_ids"`
// Password is only used as input on create, and as an optional reset on update.
// It is never populated on output (the model's PasswordHash is json:"-").
Password string `json:"password,omitempty"`
}
type WorkerNode ¶
type WorkerNode struct {
FactumModel
Name string `gorm:"uniqueIndex;not null" json:"name"`
Address string `json:"address"` // host:port, dialed as ws://<Address><worker.HubPath>
Token string `json:"-"` // shared secret sent as "Authorization: Bearer <Token>"; never serialized
Enabled bool `json:"enabled"`
}
WorkerNode is a remote factum-worker host the primary dials out to over a WebSocket connection (internal/worker.RemoteManager) - the reverse of a worker dialing in, so the firewall hole is a single narrow rule on the worker host (source = primary) rather than any inbound rule on the primary. Admin-editable list, not local YAML, since it's just "who to dial", not a security boundary tied to the primary host the way an agent's own worker.commands allowlist is.
type WorkerNodeDTO ¶
type WorkerNodeDTO struct {
ID uint `json:"id"`
Name string `json:"name"`
Address string `json:"address"`
// Token is omitempty so the frontend can leave it blank on Update to
// keep the existing value - handle_crud.go's Update loads the row
// before merging the DTO's JSON on top, so an absent key never
// overwrites the stored token (same pattern as UserDTO.Password).
Token string `json:"token,omitempty"`
Enabled bool `json:"enabled"`
}