models

package
v1.0.5 Latest Latest
Warning

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

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

Documentation

Index

Constants

View Source
const (
	ConfigScopeKindFolder          = "folder"
	ConfigScopeKindSite            = "site"
	ConfigScopeKindLocation        = "location"
	ConfigScopeKindDevice          = "device"
	ConfigScopeKindInterface       = "interface"
	ConfigScopeKindParameter       = "parameter"
	ConfigScopeKindCLI             = "cli"
	ConfigScopeKindService         = "service"
	ConfigScopeKindServiceEndpoint = "service_endpoint"
	// ConfigScopeKindServiceRef is virtual: ScopeTree injects it, it is not stored.
	ConfigScopeKindServiceRef = "service_ref"

	ConfigRootName            = "global"
	ConfigCatalogName         = "_catalog"
	ConfigCatalogCLIName      = "cli"
	ConfigServicesFolderName  = "_services"
	ConfigParametersChildName = "parameters"

	VarTypeString       = "string"
	VarTypeInt          = "int"
	VarTypeBool         = "bool"
	VarTypeEnum         = "enum"
	VarTypeIP           = "ip"
	VarTypePrefix       = "prefix"
	VarTypeVLAN         = "vlan"
	VarTypeInterfaceRef = "interface_ref"
	VarTypeSecret       = "secret"
	// List is a JSON array. Type entries with constraints.items
	// ("ip" or {"type":"int","min":1}); min/max are length.
	VarTypeList = "list"
	// Map is a JSON object (hash/dictionary). Type keys and values with
	// constraints.keys and constraints.values; min/max are size.
	VarTypeMap = "map"

	PayloadKindCLI      = "cli"
	PayloadKindNETCONF  = "netconf"
	PayloadKindRESTCONF = "restconf"

	// SyncSource* is which parsed DeviceConfig collection device-sync
	// reads for a service type. Empty means device-sync ignores the type.
	SyncSourceELINE = "eline"
	SyncSourceELAN  = "elan"
	SyncSourceL3VPN = "l3vpn"

	// NetboxType* is the NetBox object kind device-sync upserts for a
	// SyncSource. L2VPN sources use the L2VPN type slug; L3VPN uses VRF.
	NetboxTypeEVPL = "evpl"
	NetboxTypeVPLS = "vpls"
	NetboxTypeVRF  = "vrf"
)
View Source
const (
	SchemaFieldBandwidthMbps   = "bandwidth_mbps"
	SchemaFieldMaxMacAddresses = "max_mac_addresses"
)

Well-known ServiceType.Schema field names that are also copied onto dedicated Service columns so list views and older API clients can read them without parsing Fields.

View Source
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.

View Source
const (
	PortTXPClient    = "txp_client"
	PortTXPLine      = "txp_line"
	PortROADMAddDrop = "roadm_adddrop"
	PortROADMDegree  = "roadm_degree"
	PortFiber        = "fiber_port"
)

Optical port roles.

View Source
const (
	XCTributary   = "tributary"
	XCAddDrop     = "roadm_adddrop"
	XCExpress     = "roadm_express"
	XCPassthrough = "passthrough"

	// XCSourceOpenROADM marks xconnects written by optical inventory sync.
	// Operator-created rows keep Source empty; sync only deletes its own.
	XCSourceOpenROADM = "openroadm"
)

Optical xconnect kinds.

View Source
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.

View Source
const (
	MaintResourceConnection = "connection"
	MaintResourceDevice     = "device"
	MaintResourceInterface  = "interface"
	MaintResourceWavelength = "wavelength"
	MaintResourceFiber      = "fiber"

	MaintDraft      = "draft"
	MaintPlanned    = "planned"
	MaintNotified   = "notified"
	MaintInProgress = "in_progress"
	MaintCompleted  = "completed"
	MaintCancelled  = "cancelled"

	MaintNotifyPending = "pending"
	MaintNotifySent    = "sent"
	MaintNotifyFailed  = "failed"
	MaintNotifySkipped = "skipped"
)

Maintenance constants.

Variables

AllowedOpticalKinds is the stored set (aliases are normalized first).

AllowedOpticalPortRoles is the stored set for OpticalPort.Role / the NetBox optical_role custom field on dcim.interface.

View Source
var OpticalKindAliases = map[string]string{
	"transponder": OpticalKindWDMShelf,
	"muxponder":   OpticalKindWDMShelf,
}

OpticalKindAliases maps legacy / operator-facing names onto stored kinds.

View Source
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

func CategoryFromServiceID(serviceID string) string

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 IsOpticalKind(kind string) bool

func NetboxModeToSwitchportMode

func NetboxModeToSwitchportMode(mode string) string

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

func SwitchportModeToNetboxMode(mode string) any

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 CLIContext added in v1.0.5

type CLIContext struct {
	Pattern  string            `json:"pattern"`
	Enter    string            `json:"enter"`
	Exit     string            `json:"exit"`
	Captures map[string]string `json:"captures,omitempty"`
}

CLIContext is the optional CLI mode wrapping for a kind=cli object.

type ConfigAssignment added in v1.0.2

type ConfigAssignment struct {
	FactumModel
	VariableDefID uint            `json:"variable_def_id" gorm:"uniqueIndex:idx_cfg_assign_var_scope;not null"`
	ScopeID       uint            `json:"scope_id" gorm:"uniqueIndex:idx_cfg_assign_var_scope;not null"`
	Value         json.RawMessage `json:"value" gorm:"serializer:json"`
}

ConfigAssignment binds a variable def to a value at one scope.

func (ConfigAssignment) TableName added in v1.0.2

func (ConfigAssignment) TableName() string

type ConfigAssignmentDTO added in v1.0.2

type ConfigAssignmentDTO struct {
	ID            uint            `json:"id"`
	VariableDefID uint            `json:"variable_def_id"`
	ScopeID       uint            `json:"scope_id"`
	Value         json.RawMessage `json:"value"`
}

type ConfigCLIFeature added in v1.0.5

type ConfigCLIFeature struct {
	FactumModel
	ScopeID        uint   `json:"scope_id" gorm:"uniqueIndex:idx_cfg_feat_scope_name;not null"`
	Name           string `json:"name" gorm:"uniqueIndex:idx_cfg_feat_scope_name;not null;type:varchar(128)"`
	SortOrder      int    `json:"sort_order"`
	AddCommands    string `json:"add_commands" gorm:"type:text"`
	UpdateCommands string `json:"update_commands" gorm:"type:text"`
	RemoveCommands string `json:"remove_commands" gorm:"type:text"`
	RemoveAtRoot   bool   `json:"remove_at_root"`
}

ConfigCLIFeature is one ordered command-blob set on a kind=cli scope.

func (ConfigCLIFeature) TableName added in v1.0.5

func (ConfigCLIFeature) TableName() string

type ConfigCLIFeatureDTO added in v1.0.5

type ConfigCLIFeatureDTO struct {
	ID             uint   `json:"id"`
	ScopeID        uint   `json:"scope_id"`
	Name           string `json:"name"`
	SortOrder      int    `json:"sort_order"`
	AddCommands    string `json:"add_commands"`
	UpdateCommands string `json:"update_commands"`
	RemoveCommands string `json:"remove_commands"`
	RemoveAtRoot   bool   `json:"remove_at_root"`
}

type ConfigMacro added in v1.0.2

type ConfigMacro struct {
	FactumModel
	Name string `json:"name" gorm:"uniqueIndex;not null;type:varchar(255)"`
	Body string `json:"body" gorm:"type:text"`
}

ConfigMacro is a named snippet templates can {{include}}.

func (ConfigMacro) TableName added in v1.0.2

func (ConfigMacro) TableName() string

type ConfigMacroDTO added in v1.0.2

type ConfigMacroDTO struct {
	ID   uint   `json:"id"`
	Name string `json:"name"`
	Body string `json:"body"`
}

type ConfigScope added in v1.0.2

type ConfigScope struct {
	FactumModel
	ParentID      *uint              `json:"parent_id"`
	Name          string             `json:"name" gorm:"not null;type:varchar(255)"`
	Kind          string             `json:"kind" gorm:"not null;type:varchar(32)"`
	SiteID        *uint              `json:"site_id" gorm:"index"`
	DeviceID      *uint              `json:"device_id" gorm:"index"`
	InterfaceID   *uint              `json:"interface_id" gorm:"index"`
	ServiceID     *uint              `json:"service_id" gorm:"index"`
	ServiceTypeID *uint              `json:"service_type_id" gorm:"index"`
	Platform      string             `json:"platform" gorm:"type:varchar(64)"`
	PayloadKind   string             `json:"payload_kind" gorm:"type:varchar(32)"`
	Enabled       bool               `json:"enabled" gorm:"not null;default:true"`
	SortOrder     int                `json:"sort_order"`
	Payload       ConfigScopePayload `json:"payload" gorm:"serializer:json"`
	SeedChecksum  string             `json:"-" gorm:"type:varchar(64)"`
}

ConfigScope is one node in an arbitrary configuration hierarchy.

func (ConfigScope) TableName added in v1.0.2

func (ConfigScope) TableName() string

type ConfigScopeDTO added in v1.0.2

type ConfigScopeDTO struct {
	ID            uint                `json:"id"`
	ParentID      *uint               `json:"parent_id"`
	Name          *string             `json:"name"`
	Kind          *string             `json:"kind"`
	SiteID        *uint               `json:"site_id"`
	DeviceID      *uint               `json:"device_id"`
	InterfaceID   *uint               `json:"interface_id"`
	ServiceID     *uint               `json:"service_id"`
	ServiceTypeID *uint               `json:"service_type_id"`
	Platform      *string             `json:"platform"`
	PayloadKind   *string             `json:"payload_kind"`
	Enabled       *bool               `json:"enabled"`
	SortOrder     *int                `json:"sort_order"`
	Payload       *ConfigScopePayload `json:"payload"`
	// Attach creates a new CN/CI inventory row plus a canonical service
	// node with zero endpoints. Mutually exclusive with ServiceID (attach existing).
	Attach *ServiceDTO `json:"attach,omitempty"`
}

type ConfigScopePayload added in v1.0.5

type ConfigScopePayload struct {
	Description string         `json:"description,omitempty"`
	Platforms   []string       `json:"platforms,omitempty"`
	Context     *CLIContext    `json:"context,omitempty"`
	Role        string         `json:"role,omitempty"`
	Fields      map[string]any `json:"fields,omitempty"`
}

ConfigScopePayload is kind-specific data stored as JSON on ConfigScope.

type ConfigVariableDef added in v1.0.2

type ConfigVariableDef struct {
	FactumModel
	Name         string          `json:"name" gorm:"uniqueIndex;not null;type:varchar(255)"`
	Type         string          `json:"type" gorm:"not null;type:varchar(32)"`
	Description  string          `json:"description" gorm:"type:varchar(255)"`
	DefaultValue json.RawMessage `json:"default_value" gorm:"serializer:json"`
	Constraints  json.RawMessage `json:"constraints" gorm:"serializer:json"`
	Secret       bool            `json:"secret"`
	Required     bool            `json:"required"`
	Platforms    json.RawMessage `json:"platforms" gorm:"serializer:json"`
}

ConfigVariableDef is a typed variable that can be assigned on any scope.

func (ConfigVariableDef) TableName added in v1.0.2

func (ConfigVariableDef) TableName() string

type ConfigVariableDefDTO added in v1.0.2

type ConfigVariableDefDTO struct {
	ID           uint            `json:"id"`
	Name         string          `json:"name"`
	Type         string          `json:"type"`
	Description  string          `json:"description"`
	DefaultValue json.RawMessage `json:"default_value"`
	Constraints  json.RawMessage `json:"constraints"`
	Secret       bool            `json:"secret"`
	Required     bool            `json:"required"`
	Platforms    json.RawMessage `json:"platforms"`
}

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

func (c *Contact) BeforeCreate(tx *gorm.DB) error

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

func (c *Customer) BeforeCreate(tx *gorm.DB) error

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"`
}

func (*Device) IsTag

func (d *Device) IsTag(name string) bool

IsTag reports whether the device has a tag with the given name.

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 DeviceSyncAuthDTO struct {
	ID       uint   `json:"id"`
	Name     string `json:"name"`
	Username string `json:"username"`
	// Password is omitempty so the frontend can leave it blank on Update to
	// keep the existing value - same pattern as WorkerNodeDTO.Token.
	Password string `json:"password,omitempty"`
}

type EndpointRole added in v1.0.2

type EndpointRole struct {
	Name   string        `json:"name"`
	Min    int           `json:"min"`
	Max    int           `json:"max"`
	Fields []FieldSchema `json:"fields"`
}

EndpointRole describes how many endpoints of a role a service may have.

type FactumModel

type FactumModel struct {
	ID        uint      `json:"id" gorm:"primarykey"`
	CreatedAt time.Time `json:"-"`
	UpdatedAt time.Time `json:"-"`
}

type FieldSchema added in v1.0.2

type FieldSchema struct {
	Name        string `json:"name"`
	Type        string `json:"type"`
	Required    bool   `json:"required"`
	Description string `json:"description"`
}

FieldSchema is one typed field on a service type or endpoint role.

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"`
	// VLANNames maps VID -> Netbox VLAN name for this interface's untagged
	// and tagged VLANs, copied from netboxtool.NBInterface.VLANNames on
	// sync. Used by the VLAN matrix to label columns.
	VLANNames map[int]string `json:"vlan_names,omitempty" 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. Terminations come from service_endpoints; when
	// a per-VLAN subinterface exists, the service is attached to that
	// subinterface row instead of the physical port.
	Services []InterfaceServiceRef `json:"services,omitempty" gorm:"-"`
	// Optical is assembled by fetchDevices — not a DB relation on Interface.
	Optical *OpticalPort `json:"optical,omitempty" gorm:"-"`
}

func (*Interface) IsTag

func (i *Interface) IsTag(name string) bool

IsTag reports whether the interface has a tag with the given name.

type InterfaceServiceRef

type InterfaceServiceRef struct {
	ID        uint   `json:"id"`
	ServiceID string `json:"service_id"`
}

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 IpamNamespaceDTO struct {
	ID          uint   `json:"id"`
	Name        string `json:"name"`
	Description string `json:"description"`
}

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 IpamNamespacePrefixDTO struct {
	ID          uint   `json:"id"`
	NamespaceID uint   `json:"namespace_id"`
	Prefix      string `json:"prefix"`
}

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 IpamPrefixDTO struct {
	ID          uint   `json:"id"`
	NamespaceID uint   `json:"namespace_id"`
	VRFID       uint   `json:"vrf_id"`
	Prefix      string `json:"prefix"`
	Description string `json:"description"`
}

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.

func (IpamVRF) TableName

func (IpamVRF) TableName() string

type IpamVRFIDTO

type IpamVRFIDTO struct {
	ID          uint   `json:"id"`
	NamespaceID uint   `json:"namespace_id"`
	Name        string `json:"name"`
	Description string `json:"description"`
}

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 job, the in-app replacement for a crontab entry on the primary. Target is either one worker.IsValidJobTarget name (a single-target StartJob, same as clicking one tile on Job overview - sync targets plus "housekeeping"), or the sentinel "all" (SequencedSyncAllTargets + StartJob, same as "Sync all"; housekeeping is not included). 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 "factum2-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 LdapRoleMappingDTO struct {
	ID      uint   `json:"id"`
	GroupDN string `json:"group_dn"`
	RoleID  uint   `json:"role_id"`
}

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 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 LogEntry

type LogEntry struct {
	FactumModel
}

Loosely based on RFC5424

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 MaintenanceResource added in v1.0.5

type MaintenanceResource struct {
	FactumModel
	WindowID     uint   `json:"window_id" gorm:"uniqueIndex:idx_maint_resources;not null;index"`
	ResourceType string `json:"resource_type" gorm:"uniqueIndex:idx_maint_resources;type:varchar(16);not null"`
	ResourceID   uint   `json:"resource_id" gorm:"uniqueIndex:idx_maint_resources;not null"`
}

MaintenanceResource is one device, fiber, wavelength, or interface attached to a window. Impact is the union across all rows for that 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"`
	Resources    []MaintenanceResource `json:"resources,omitempty" gorm:"foreignKey:WindowID"`
}

MaintenanceWindow is a scheduled outage on one or more devices, fibers (cables or LF/LI services), wavelengths (VL/VI), or interfaces. ResourceType/ResourceID denormalize the first attached resource so list queries and pre-join rows keep working; Resources is the full set.

type MoveScopeRequest added in v1.0.5

type MoveScopeRequest struct {
	ParentID  uint `json:"parent_id"`
	SortOrder *int `json:"sort_order"`
}

MoveScopeRequest is POST /api/config/scopes/:id/move. SortOrder nil = last sibling.

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 OpticalKindMapDTO struct {
	ID             uint   `json:"id"`
	NetboxRoleName string `json:"netbox_role_name"`
	OpticalKind    string `json:"optical_kind"`
}

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"`
	Source       string `json:"source" gorm:"type:varchar(32);index"`
}

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 RoleDTO

type RoleDTO struct {
	ID          uint   `json:"id"`
	Name        string `json:"name"`
	Description string `json:"description"`
}

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"`    // from type schema field bandwidth_mbps, else 0
	MaxMacAddresses int    `json:"max_mac_addresses"` // from type schema field max_mac_addresses (ELAN), 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

	// Leftover ELINE columns. New code stores terminations in
	// service_endpoints (roles a/b, vlan + netbox ids in Fields). These
	// columns are no longer written; they remain so AutoMigrate does not
	// drop them.
	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"`

	// Fields holds instance-level values for a service type's schema.
	Fields json.RawMessage `json:"fields" gorm:"serializer:json"`

	Source   string `json:"source"`
	SourceID string `json:"source_id"`
}

func (*Service) BeforeCreate

func (s *Service) BeforeCreate(tx *gorm.DB) error

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"`
	Fields          json.RawMessage `json:"fields"`
}

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 ServiceEndpoint added in v1.0.2

type ServiceEndpoint struct {
	FactumModel
	ServiceID   uint            `json:"service_id" gorm:"index;not null"`
	Role        string          `json:"role" gorm:"type:varchar(64);not null"`
	DeviceID    uint            `json:"device_id" gorm:"index;not null"`
	InterfaceID uint            `json:"interface_id" gorm:"index;not null"`
	Fields      json.RawMessage `json:"fields" gorm:"serializer:json"`
}

ServiceEndpoint is a service termination (including ELINE a/b).

func (ServiceEndpoint) TableName added in v1.0.2

func (ServiceEndpoint) TableName() string

type ServiceEndpointDTO added in v1.0.2

type ServiceEndpointDTO struct {
	ID          uint            `json:"id"`
	ServiceID   uint            `json:"service_id"`
	Role        string          `json:"role"`
	DeviceID    uint            `json:"device_id"`
	InterfaceID uint            `json:"interface_id"`
	Fields      json.RawMessage `json:"fields"`
}

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 ServiceType added in v1.0.2

type ServiceType struct {
	FactumModel
	Name          string         `json:"name" gorm:"uniqueIndex;not null;type:varchar(64)"`
	Description   string         `json:"description" gorm:"type:varchar(255)"`
	Schema        []FieldSchema  `json:"schema" gorm:"serializer:json"`
	EndpointRoles []EndpointRole `json:"endpoint_roles" gorm:"serializer:json"`
	Builtin       bool           `json:"builtin"`
	// SyncSource names the on-device collection device-sync reads
	// (eline / elan / l3vpn). Empty means the type is GUI-only.
	SyncSource string `json:"sync_source" gorm:"type:varchar(32)"`
	// NetboxType is the NetBox object to upsert for SyncSource
	// (evpl / vpls / vrf).
	NetboxType string `json:"netbox_type" gorm:"type:varchar(32)"`
}

ServiceType is a vendor-agnostic service class (ELINE, ELAN, …).

func (ServiceType) TableName added in v1.0.2

func (ServiceType) TableName() string

type ServiceTypeDTO added in v1.0.2

type ServiceTypeDTO struct {
	ID            uint           `json:"id"`
	Name          string         `json:"name"`
	Description   string         `json:"description"`
	Schema        []FieldSchema  `json:"schema"`
	EndpointRoles []EndpointRole `json:"endpoint_roles"`
	SyncSource    string         `json:"sync_source"`
	NetboxType    string         `json:"netbox_type"`
}

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"`
	// OrganizationEnabled gates the Organization menu (Customers, Contacts)
	// in the web GUI. Off (nil/false) is the default. Turning it off only
	// hides the menu — customer and contact rows stay in the database, and
	// services may still reference them.
	OrganizationEnabled *bool `gorm:"column:organization_enabled" form:"organization_enabled" json:"organization_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"`
	PrometheusEnabled   *bool `gorm:"column:prometheus_enabled" form:"prometheus_enabled" json:"prometheus_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/Prometheus), 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/Prometheus 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"`
	// JobHistoryKeep is how many newest finished Jobs (and their
	// JobTask/JobTaskEvent rows) the housekeeping target keeps. Values < 1
	// are treated as 50 by housekeeping.Trim (the same cap GET /api/jobs
	// lists), so an unset column cannot wipe history on the first run.
	// Unfinished jobs are never deleted. The task does not run on its
	// own - schedule or trigger "housekeeping" like any other job target.
	JobHistoryKeep int `gorm:"column:job_history_keep" form:"job_history_keep" json:"job_history_keep"`

	// 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 (factum2-icinga-notifications is the first
	// consumer, via util.CommonConfig, but any tool that needs to send
	// mail can fetch the same settings). Edited on the admin UI's
	// Factum > Email tab.
	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 factum2-icinga's Update() skips entirely.
	IcingaIgnoreDevices string `gorm:"column:icinga_ignore_devices;type:text" form:"icinga_ignore_devices" json:"icinga_ignore_devices"`
	// IcingaDefaultNotification is Go text/template executed with .Device
	// for a host that has no cf_alarm_destination, e.g.
	// `  vars.pe_notify_default = true`. Literal Icinga with no {{ }} is
	// fine. The rendered lines are inserted into the host object (see
	// internal/icinga/factum2-icinga.go).
	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/factum2-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"`
	// NetboxSyncContactsEnabled, when set, makes FactumSyncNetbox also
	// push factum contacts to Netbox as contacts (same source/source_id
	// custom fields as tenants). CustomerContact links become contact
	// assignments on the matching tenant when that tenant exists.
	NetboxSyncContactsEnabled *bool `gorm:"column:netbox_sync_contacts_enabled" form:"netbox_sync_contacts_enabled" json:"netbox_sync_contacts_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:ip:model per line).
	// Oxidized's CSV source must map name: 0, ip: 1, model: 2.
	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"`

	// Prometheus + snmp_exporter. factum2-prometheus writes a Prometheus
	// file_sd JSON of SNMP targets (Settings.PrometheusDestFile) from
	// devices flagged CfMonitorGrafana, then POSTs PrometheusReloadURL
	// if the file changed and a URL is set. Module/Auth are snmp_exporter
	// names (snmp.yml), not community strings.
	PrometheusDestFile string `gorm:"column:prometheus_dest_file" form:"prometheus_dest_file" json:"prometheus_dest_file"`
	// PrometheusReloadURL is the full URL POSTed after a file change
	// (typically http://127.0.0.1:9090/-/reload, which needs Prometheus
	// --web.enable-lifecycle). Empty skips the reload - file_sd still
	// re-reads the dest file on its own refresh interval.
	PrometheusReloadURL string `gorm:"column:prometheus_reload_url" form:"prometheus_reload_url" json:"prometheus_reload_url"`
	// PrometheusModule is the snmp_exporter module name stamped on every
	// target (e.g. "if_mib"). Empty is treated as "if_mib" at write time.
	PrometheusModule string `gorm:"column:prometheus_module" form:"prometheus_module" json:"prometheus_module"`
	// PrometheusAuth is the snmp_exporter auth name stamped on every
	// target (an auths: key in snmp.yml, e.g. "public_v2"). Empty is
	// treated as "public_v2" at write time.
	PrometheusAuth string `gorm:"column:prometheus_auth" form:"prometheus_auth" json:"prometheus_auth"`
	// PrometheusIgnoreDevices/Manufacturers/Models/Platforms are
	// newline-separated lists, same convention as Oxidized's Ignore* -
	// a device matching any one of them is skipped by
	// FactumPrometheusClient.Sync.
	PrometheusIgnoreDevices       string `gorm:"column:prometheus_ignore_devices;type:text" form:"prometheus_ignore_devices" json:"prometheus_ignore_devices"`
	PrometheusIgnoreManufacturers string `` /* 133-byte string literal not displayed */
	PrometheusIgnoreModels        string `gorm:"column:prometheus_ignore_models;type:text" form:"prometheus_ignore_models" json:"prometheus_ignore_models"`
	PrometheusIgnorePlatforms     string `gorm:"column:prometheus_ignore_platforms;type:text" form:"prometheus_ignore_platforms" json:"prometheus_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"`
	// LdapHost2/LdapPort2 are an optional second LDAP/AD server for
	// redundancy. Directory operations try LdapHost first and fall back to
	// LdapHost2 if it is unreachable. LdapPort2 of 0 means "same as
	// LdapPort". TLS, bind DN, base DN and filters are shared - both
	// servers are assumed to be replicas of the same directory.
	LdapHost2 string `gorm:"column:ldap_host2" form:"ldap_host2" json:"ldap_host2"`
	LdapPort2 uint16 `gorm:"column:ldap_port2" form:"ldap_port2" json:"ldap_port2"`
	// 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 search identity used for the
	// search+bind flow (see internal/ldapauth.Authenticate) - stored in
	// plaintext like every other integration credential in this struct.
	// Both may be blank for an anonymous bind, which requires the
	// directory to allow anonymous search under LdapBaseDN.
	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

func (*User) HasRole

func (u *User) HasRole(roleName string) bool

HasRole checks if the user has a specific role by name

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 wss://<Address><worker.HubPath>
	Token   string `json:"-"`       // shared secret sent as "Authorization: Bearer <Token>"; never serialized
	Enabled bool   `json:"enabled"`
	// TLSSkipVerify disables hub certificate verification for this node.
	// The channel is still encrypted, but a MITM with any cert is accepted;
	// prefer TLSCA. Intended for lab/self-signed certs whose SAN does not
	// match Address.
	TLSSkipVerify bool `json:"tls_skip_verify" gorm:"column:tls_skip_verify"`
	// TLSCA is optional PEM used as the TLS trust root when verifying this
	// node's hub certificate. Empty uses the system CA pool. Typically the
	// worker's self-signed hub.crt, or an internal CA. Not a secret.
	TLSCA string `json:"tls_ca" gorm:"column:tls_ca;type:text"`
}

WorkerNode is a remote factum2-worker host the primary dials out to over a WSS 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"`
	TLSSkipVerify bool   `json:"tls_skip_verify"`
	TLSCA         string `json:"tls_ca"`
}

Jump to

Keyboard shortcuts

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