models

package
v1.1.1 Latest Latest
Warning

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

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

Documentation

Index

Constants

View Source
const (
	CertChallengeKindDNS01 = "dns-01"
	CertProviderRFC2136    = "rfc2136"
)
View Source
const (
	ConfigScopeKindFolder          = "folder"
	ConfigScopeKindSite            = "site"
	ConfigScopeKindLocation        = "location"
	ConfigScopeKindDevice          = "device"
	ConfigScopeKindInterface       = "interface"
	ConfigScopeKindParameter       = "parameter"
	ConfigScopeKindCLI             = "cli"
	ConfigScopeKindService         = "service"
	ConfigScopeKindServiceEndpoint = "service_endpoint"
	ConfigScopeKindResource        = "resource"
	// 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 (
	FieldTypeString     = VarTypeString
	FieldTypeInt        = VarTypeInt
	FieldTypeBool       = VarTypeBool
	FieldTypeEnum       = VarTypeEnum
	FieldTypeVLAN       = VarTypeVLAN
	FieldTypeMAC        = "mac"
	FieldTypeSNPA       = "snpa" // alias of mac; stored as mac
	FieldTypeIPv4       = "ipv4"
	FieldTypeIPv6       = "ipv6"
	FieldTypeIP         = VarTypeIP
	FieldTypeIPv4Prefix = "ipv4_prefix"
	FieldTypeIPv6Prefix = "ipv6_prefix"
	FieldTypePrefix     = VarTypePrefix
	FieldTypeServiceID  = "service_id"
	FieldTypeList       = VarTypeList
)
View Source
const (
	DCIMSourceFactum = "factum"
	DCIMSourceNetbox = "netbox"

	RackNumberingAscending  = "ascending"
	RackNumberingDescending = "descending"

	DeviceFaceFront = "front"
	DeviceFaceRear  = "rear"

	FloorAnnotationLabel = "label"
	FloorAnnotationAisle = "aisle"
	FloorAnnotationZone  = "zone"
)
View Source
const (
	SiteSourceFactum = "factum"
	SiteSourceNetbox = "netbox"

	// SiteNetboxKind* is the NetBox object a synced row came from.
	// Factum-created sites leave this empty. Region/site/location IDs
	// occupy separate NetBox sequences, so uniqueness is (kind, id).
	SiteNetboxKindRegion   = "region"
	SiteNetboxKindSite     = "site"
	SiteNetboxKindLocation = "location"
)
View Source
const (
	DnsZoneTypeForward  = "forward"
	DnsZoneTypeReverse4 = "reverse4"
	DnsZoneTypeReverse6 = "reverse6"
)
View Source
const (
	VRFSourceFactum = "factum"
	VRFSourceNetbox = "netbox"
)
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.

View Source
const EndpointRoleInterface = "interface"

EndpointRoleInterface is the sentinel stored in ServiceEndpoint.Role.

View Source
const TicksPerU = 2

TicksPerU is the internal occupancy resolution: two ticks equal 1U.

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 AutoMigrateAll added in v1.0.6

func AutoMigrateAll(db *gorm.DB) error

AutoMigrateAll creates the application schema via GORM. Used only by in-memory SQLite tests (util.MigrateDatabase's non-postgres branch). Production schema is internal/dbmigrate/sql, not this list.

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 Slugify added in v1.1.0

func Slugify(s string) string

Slugify turns a catalog name into a URL/NetBox-style slug.

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)"`
	// PrefixID is the most-specific allocated ipam_prefixes row that
	// contains this address. Prefix length and VRF are read from that
	// row rather than parsed from Address / the denormalized VRF name.
	PrefixID *uint `json:"prefix_id" gorm:"index"`
	// DNSName is Netbox ipam.IPAddress.dns_name, validated at Netbox sync.
	DNSName string `json:"dns_name" gorm:"type:varchar(255)"`
	// VRF is the name of the VRF this address belongs to, "" for
	// global/default VRF - mirrors netboxtool.NBAddress.VRF. Kept in
	// sync with the related prefix's VRF when PrefixID is set.
	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 AddressCreateDTO added in v1.1.0

type AddressCreateDTO struct {
	InterfaceID uint   `json:"interface_id"`
	Address     string `json:"address"`
	DNSName     string `json:"dns_name"`
	VRF         string `json:"vrf"`
	Role        string `json:"role"`
	PrefixID    *uint  `json:"prefix_id"`
	// Management, when set, assigns or clears this address as the device's
	// primary IPv4 or IPv6 (Device.PrimaryIPv4ID / PrimaryIPv6ID). nil
	// leaves the existing primary assignment unchanged (aside from
	// keeping the denormalized address string in sync).
	Management *bool `json:"management"`
}

AddressCreateDTO is the writable subset of Address. NetboxID is never accepted from the client — Factum-created rows stay NetboxID=0.

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 CertAccount added in v1.1.0

type CertAccount struct {
	FactumModel
	Name                  string `json:"name" gorm:"uniqueIndex;not null;type:varchar(255)"`
	Email                 string `json:"email" gorm:"type:varchar(255)"`
	Server                string `json:"server" gorm:"type:varchar(512)"`
	KeyType               string `json:"key_type" gorm:"type:varchar(32)"`
	AcceptsTermsOfService bool   `json:"accepts_terms_of_service"`
	EABKID                string `json:"eab_kid" gorm:"column:eab_kid;type:varchar(255)"`
	EABHMACKey            string `json:"eab_hmac_key" gorm:"column:eab_hmac_key;type:text"`
}

CertAccount is a lego ACME account (email, server, key type).

func (CertAccount) TableName added in v1.1.0

func (CertAccount) TableName() string

type CertAccountDTO added in v1.1.0

type CertAccountDTO struct {
	ID                    uint   `json:"id"`
	Name                  string `json:"name"`
	Email                 string `json:"email"`
	Server                string `json:"server"`
	KeyType               string `json:"key_type"`
	AcceptsTermsOfService bool   `json:"accepts_terms_of_service"`
	EABKID                string `json:"eab_kid"`
	EABHMACKey            string `json:"eab_hmac_key"`
}

type CertChallenge added in v1.1.0

type CertChallenge struct {
	FactumModel
	Name               string `json:"name" gorm:"uniqueIndex;not null;type:varchar(255)"`
	Kind               string `json:"kind" gorm:"type:varchar(32);not null;default:'dns-01'"`
	Provider           string `json:"provider" gorm:"type:varchar(64);not null;default:'rfc2136'"`
	DNSTimeout         int    `json:"dns_timeout"`
	Resolvers          string `json:"resolvers" gorm:"type:text"`
	DisableAuthNS      bool   `json:"disable_authoritative_nameservers" gorm:"column:disable_authoritative_nameservers"`
	DisableRecursiveNS bool   `json:"disable_recursive_nameservers" gorm:"column:disable_recursive_nameservers"`
	PropagationWait    string `json:"propagation_wait" gorm:"type:varchar(32)"`

	RFC2136Nameserver   string `json:"rfc2136_nameserver" gorm:"type:varchar(255)"`
	RFC2136TSIGAlgo     string `json:"rfc2136_tsig_algorithm" gorm:"column:rfc2136_tsig_algorithm;type:varchar(128)"`
	RFC2136TSIGKey      string `json:"rfc2136_tsig_key" gorm:"type:varchar(255)"`
	RFC2136TSIGSecret   string `json:"rfc2136_tsig_secret" gorm:"type:text"`
	RFC2136TSIGFile     string `json:"rfc2136_tsig_file" gorm:"type:varchar(512)"`
	RFC2136TTL          int    `json:"rfc2136_ttl"`
	RFC2136PropTimeout  string `json:"rfc2136_propagation_timeout" gorm:"column:rfc2136_propagation_timeout;type:varchar(32)"`
	RFC2136PollInterval string `json:"rfc2136_polling_interval" gorm:"column:rfc2136_polling_interval;type:varchar(32)"`

	// ExtraEnv is KEY=value lines appended to the challenge dotenv.
	ExtraEnv string `json:"extra_env" gorm:"type:text"`
}

CertChallenge is a named DNS-01 solver, currently RFC2136 (dynamic update).

func (CertChallenge) TableName added in v1.1.0

func (CertChallenge) TableName() string

type CertChallengeDTO added in v1.1.0

type CertChallengeDTO struct {
	ID                  uint   `json:"id"`
	Name                string `json:"name"`
	Kind                string `json:"kind"`
	Provider            string `json:"provider"`
	DNSTimeout          int    `json:"dns_timeout"`
	Resolvers           string `json:"resolvers"`
	DisableAuthNS       bool   `json:"disable_authoritative_nameservers"`
	DisableRecursiveNS  bool   `json:"disable_recursive_nameservers"`
	PropagationWait     string `json:"propagation_wait"`
	RFC2136Nameserver   string `json:"rfc2136_nameserver"`
	RFC2136TSIGAlgo     string `json:"rfc2136_tsig_algorithm"`
	RFC2136TSIGKey      string `json:"rfc2136_tsig_key"`
	RFC2136TSIGSecret   string `json:"rfc2136_tsig_secret"`
	RFC2136TSIGFile     string `json:"rfc2136_tsig_file"`
	RFC2136TTL          int    `json:"rfc2136_ttl"`
	RFC2136PropTimeout  string `json:"rfc2136_propagation_timeout"`
	RFC2136PollInterval string `json:"rfc2136_polling_interval"`
	ExtraEnv            string `json:"extra_env"`
}

type Certificate added in v1.1.0

type Certificate struct {
	FactumModel
	Name             string        `json:"name" gorm:"uniqueIndex;not null;type:varchar(255)"`
	AccountID        uint          `json:"account_id"`
	Account          CertAccount   `json:"account,omitempty" gorm:"constraint:OnUpdate:CASCADE,OnDelete:RESTRICT;"`
	ChallengeID      uint          `json:"challenge_id"`
	Challenge        CertChallenge `json:"challenge,omitempty" gorm:"constraint:OnUpdate:CASCADE,OnDelete:RESTRICT;"`
	KeyType          string        `json:"key_type" gorm:"type:varchar(32)"`
	EnableCommonName *bool         `json:"enable_common_name"`
	// Host is the IPv4/IPv6/hostname Icinga connects to when checking
	// this certificate. Empty skips Icinga cert checks for this cert.
	Host    string              `json:"host" gorm:"type:varchar(255)"`
	Domains []CertificateDomain `json:"domains" gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}

Certificate is one lego certificate: name, domains, account, challenge. KeyType and EnableCommonName nil/empty inherit Settings.CertsDefault*.

func (Certificate) TableName added in v1.1.0

func (Certificate) TableName() string

type CertificateDTO added in v1.1.0

type CertificateDTO struct {
	ID               uint     `json:"id"`
	Name             string   `json:"name"`
	AccountID        uint     `json:"account_id"`
	Account          string   `json:"account"`
	ChallengeID      uint     `json:"challenge_id"`
	Challenge        string   `json:"challenge"`
	KeyType          string   `json:"key_type"`
	EnableCommonName *bool    `json:"enable_common_name"`
	Host             string   `json:"host"`
	Domains          []string `json:"domains"`
}

type CertificateDomain added in v1.1.0

type CertificateDomain struct {
	ID            uint   `json:"id" gorm:"primaryKey"`
	CertificateID uint   `json:"certificate_id" gorm:"not null;uniqueIndex:idx_cert_domain_rank"`
	Rank          uint   `json:"rank" gorm:"not null;uniqueIndex:idx_cert_domain_rank"`
	Name          string `json:"name" gorm:"type:varchar(255);not null"`
}

func (CertificateDomain) TableName added in v1.1.0

func (CertificateDomain) TableName() string

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"`
	// CIDRs is the prefix pool for kind=resource (canonical Masked strings).
	CIDRs []string `json:"cidrs,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 uniqueness is a partial index WHERE netbox_id <> 0 so many
	// Factum-local cables (netbox_id=0) can coexist.
	NetboxID     uint   `json:"netbox_id"`
	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 direct interface-to-interface cable. NetBox-synced rows keep NetboxID set (partial unique index WHERE netbox_id <> 0) and are read-only here. Factum-created cables keep NetboxID=0 and can be created, retimed, and deleted from the Connections page.

type ConnectionViewLayout added in v1.1.1

type ConnectionViewLayout struct {
	FactumModel
	UserID   uint   `json:"user_id" gorm:"uniqueIndex:idx_connection_view_layouts_user_scope;not null"`
	Scope    string `json:"scope" gorm:"uniqueIndex:idx_connection_view_layouts_user_scope;type:varchar(64);not null"`
	Revision int    `json:"revision"`
	Nodes    string `json:"nodes" gorm:"type:text"`
}

ConnectionViewLayout stores per-user graph node coordinates for a scope. Nodes JSON is keyed by local device IDs. It never stores cable inventory.

func (*ConnectionViewLayout) BeforeCreate added in v1.1.1

func (l *ConnectionViewLayout) BeforeCreate(tx *gorm.DB) error

type ConnectionWriteDTO added in v1.1.1

type ConnectionWriteDTO struct {
	InterfaceAID uint   `json:"interface_a_id"`
	InterfaceBID uint   `json:"interface_b_id"`
	Label        string `json:"label"`
}

ConnectionWriteDTO is the body for POST/PUT /api/dcim/connections. Device IDs are taken from the chosen interfaces, not the client.

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.
	// Local (Factum-created) devices keep NetboxID=0; uniqueness is a
	// partial index WHERE netbox_id <> 0 so many local rows can coexist.
	VM             bool   `json:"vm"`
	NetboxID       uint   `json:"netbox_id"`
	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"`
	// DeviceTypeID is the Factum catalog row (models.DeviceType), not
	// NetBox's device-type id (that is ModelID). 0 if unset.
	DeviceTypeID  uint   `json:"device_type_id" gorm:"index"`
	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 DeviceCreateDTO added in v1.1.0

type DeviceCreateDTO struct {
	Name         string `json:"name"`
	DeviceTypeID uint   `json:"device_type_id"`
	PlatformID   uint   `json:"platform_id"`
	SiteID       uint   `json:"site_id"`
	Site         string `json:"site"`
	Role         string `json:"role"`
	Status       string `json:"status"`
	// PrimaryIPv4/PrimaryIPv6 on this DTO are ignored. Primary addresses
	// are assigned by referencing an interface address (management IP).
	PrimaryIPv4       string `json:"primary_ipv4"`
	PrimaryIPv6       string `json:"primary_ipv6"`
	Comments          string `json:"comments"`
	Enabled           *bool  `json:"enabled"`
	CfLocation        string `json:"cf_location"`
	CfMonitorIcinga   *bool  `json:"cf_monitor_icinga"`
	CfMonitorLibrenms *bool  `json:"cf_monitor_librenms"`
	CfMonitorGrafana  *bool  `json:"cf_monitor_grafana"`
	CfBackupOxidized  *bool  `json:"cf_backup_oxidized"`
	CfAlarmInterfaces *bool  `json:"cf_alarm_interfaces"`
	OpticalKind       string `json:"optical_kind"`
}

DeviceCreateDTO is the POST/PUT /api/device body for a Factum-local device (NetboxID stays 0, CfSource is "factum"). Manufacturer/model/platform strings on Device are copied from the catalog rows at create time. Pointer bools distinguish omitted (leave existing / default) from false.

type DevicePlacement added in v1.1.1

type DevicePlacement struct {
	FactumModel
	DeviceID    uint   `json:"device_id" gorm:"uniqueIndex;not null"`
	RackID      uint   `json:"rack_id" gorm:"index;not null"`
	OffsetTicks int    `json:"offset_ticks" gorm:"column:offset_ticks"`
	Face        string `json:"face" gorm:"type:varchar(16)"`
	Source      string `json:"source" gorm:"type:varchar(32)"`
	Version     int    `json:"version"`
	// Conflict is set when imported occupancy is inconsistent with other
	// imported rows. Local writers treat the occupied interval conservatively.
	Conflict string `json:"conflict,omitempty" gorm:"type:varchar(64)"`
}

DevicePlacement is the one authoritative rack mount for a physical device.

func (*DevicePlacement) BeforeCreate added in v1.1.1

func (p *DevicePlacement) BeforeCreate(tx *gorm.DB) error

func (DevicePlacement) IsLocal added in v1.1.1

func (p DevicePlacement) IsLocal() bool

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 DeviceType added in v1.1.0

type DeviceType struct {
	FactumModel
	ManufacturerID uint   `json:"manufacturer_id" gorm:"index;not null"`
	Model          string `json:"model" gorm:"type:varchar(255);not null"`
	Slug           string `json:"slug" gorm:"type:varchar(255);not null"`
	PlatformID     uint   `json:"platform_id" gorm:"index"`
	Source         string `json:"source" gorm:"type:varchar(32)"`
	NetboxID       uint   `json:"netbox_id"`
	// HeightTicks is physical height in half-U ticks (2 ticks = 1U).
	// nil means unknown — never treat as 1U.
	HeightTicks *int   `json:"height_ticks"`
	FullDepth   *bool  `json:"full_depth"`
	FrontImage  string `json:"front_image" gorm:"type:varchar(512)"`
	RearImage   string `json:"rear_image" gorm:"type:varchar(512)"`
}

DeviceType is the shared DCIM catalog (NetBox dcim.DeviceType).

func (*DeviceType) BeforeCreate added in v1.1.0

func (d *DeviceType) BeforeCreate(tx *gorm.DB) error

func (*DeviceType) BeforeDelete added in v1.1.0

func (d *DeviceType) BeforeDelete(tx *gorm.DB) error

func (*DeviceType) BeforeUpdate added in v1.1.0

func (d *DeviceType) BeforeUpdate(tx *gorm.DB) error

type DeviceTypeDTO added in v1.1.0

type DeviceTypeDTO struct {
	ID             uint   `json:"id"`
	ManufacturerID uint   `json:"manufacturer_id"`
	Model          string `json:"model"`
	Slug           string `json:"slug"`
	PlatformID     uint   `json:"platform_id"`
	HeightTicks    *int   `json:"height_ticks"`
	FullDepth      *bool  `json:"full_depth"`
	FrontImage     string `json:"front_image"`
	RearImage      string `json:"rear_image"`
}

type DnsDNSSECPolicy added in v1.0.6

type DnsDNSSECPolicy struct {
	FactumModel
	Name                     string `json:"name" gorm:"uniqueIndex;not null;type:varchar(255)"`
	KSKLifetime              string `json:"ksk_lifetime" gorm:"type:varchar(64)"`
	KSKAlgorithm             string `json:"ksk_algorithm" gorm:"type:varchar(64)"`
	ZSKLifetime              string `json:"zsk_lifetime" gorm:"type:varchar(64)"`
	ZSKAlgorithm             string `json:"zsk_algorithm" gorm:"type:varchar(64)"`
	PurgeKeys                string `json:"purge_keys" gorm:"type:varchar(64)"`
	SignaturesValidity       string `json:"signatures_validity" gorm:"type:varchar(64)"`
	SignaturesValidityDNSKEY string `json:"signatures_validity_dnskey" gorm:"column:signatures_validity_dnskey;type:varchar(64)"`
	SignaturesRefresh        string `json:"signatures_refresh" gorm:"type:varchar(64)"`
}

DnsDNSSECPolicy is a BIND dnssec-policy (name, KSK/ZSK, signature timings). dnsmgr2 currently consumes only Name (as zone_templates.dnssec_policy); the rest is stored so the GUI can round-trip a full policy.

func (DnsDNSSECPolicy) TableName added in v1.0.6

func (DnsDNSSECPolicy) TableName() string

type DnsDNSSECPolicyDTO added in v1.0.6

type DnsDNSSECPolicyDTO struct {
	ID                       uint   `json:"id"`
	Name                     string `json:"name"`
	KSKLifetime              string `json:"ksk_lifetime"`
	KSKAlgorithm             string `json:"ksk_algorithm"`
	ZSKLifetime              string `json:"zsk_lifetime"`
	ZSKAlgorithm             string `json:"zsk_algorithm"`
	PurgeKeys                string `json:"purge_keys"`
	SignaturesValidity       string `json:"signatures_validity"`
	SignaturesValidityDNSKEY string `json:"signatures_validity_dnskey"`
	SignaturesRefresh        string `json:"signatures_refresh"`
}

type DnsSOATemplate added in v1.0.6

type DnsSOATemplate struct {
	FactumModel
	Name    string `json:"name" gorm:"uniqueIndex;not null;type:varchar(255)"`
	Mname   string `json:"mname" gorm:"type:varchar(255)"`
	Rname   string `json:"rname" gorm:"type:varchar(255)"`
	Refresh uint   `json:"refresh"`
	Retry   uint   `json:"retry"`
	Expire  uint   `json:"expire"`
	TTL     uint   `json:"ttl"` // SOA minimum
}

DnsSOATemplate is a named SOA used by DNS templates when dnsmgr2 writes zone files. Serial is not stored — dnsmgr2 assigns YYYYMMDDnn itself.

func (DnsSOATemplate) TableName added in v1.0.6

func (DnsSOATemplate) TableName() string

type DnsSOATemplateDTO added in v1.0.6

type DnsSOATemplateDTO struct {
	ID      uint   `json:"id"`
	Name    string `json:"name"`
	Mname   string `json:"mname"`
	Rname   string `json:"rname"`
	Refresh uint   `json:"refresh"`
	Retry   uint   `json:"retry"`
	Expire  uint   `json:"expire"`
	TTL     uint   `json:"ttl"`
}

type DnsTemplate added in v1.0.6

type DnsTemplate struct {
	FactumModel
	Name           string                  `json:"name" gorm:"uniqueIndex;not null;type:varchar(255)"`
	SOATemplateID  uint                    `json:"soa_template_id"`
	SOATemplate    DnsSOATemplate          `json:"soa_template,omitempty" gorm:"constraint:OnUpdate:CASCADE,OnDelete:RESTRICT;"`
	DefaultTTL     uint                    `json:"default_ttl"`
	DNSSECPolicyID *uint                   `json:"dnssec_policy_id"`
	DNSSECPolicy   *DnsDNSSECPolicy        `json:"dnssec_policy,omitempty" gorm:"constraint:OnUpdate:CASCADE,OnDelete:RESTRICT;"`
	Nameservers    []DnsTemplateNameserver `json:"nameservers" gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}

DnsTemplate is a zone template (dns_template in dnsmgr2): SOA, default TTL, optional DNSSEC policy, and the NS list written into every zone that uses it.

func (DnsTemplate) TableName added in v1.0.6

func (DnsTemplate) TableName() string

type DnsTemplateDTO added in v1.0.6

type DnsTemplateDTO struct {
	ID             uint     `json:"id"`
	Name           string   `json:"name"`
	SOATemplateID  uint     `json:"soa_template_id"`
	SOATemplate    string   `json:"soa_template"`
	DefaultTTL     uint     `json:"default_ttl"`
	DNSSECPolicyID *uint    `json:"dnssec_policy_id"`
	DNSSECPolicy   string   `json:"dnssec_policy"`
	Nameservers    []string `json:"nameservers"`
}

type DnsTemplateNameserver added in v1.0.6

type DnsTemplateNameserver struct {
	ID            uint   `json:"id" gorm:"primaryKey"`
	DnsTemplateID uint   `json:"dns_template_id" gorm:"not null;uniqueIndex:idx_dns_template_ns_rank"`
	Rank          uint   `json:"rank" gorm:"not null;uniqueIndex:idx_dns_template_ns_rank"`
	Hostname      string `json:"hostname" gorm:"type:varchar(255);not null"`
}

DnsTemplateNameserver is one NS hostname on a DNS template, in display order.

func (DnsTemplateNameserver) TableName added in v1.0.6

func (DnsTemplateNameserver) TableName() string

type DnsZone added in v1.0.6

type DnsZone struct {
	FactumModel
	Name          string          `json:"name" gorm:"uniqueIndex;not null;type:varchar(255)"`
	Type          string          `json:"type" gorm:"type:varchar(16);not null;default:'forward'"`
	DnsTemplateID uint            `json:"dns_template_id"`
	DnsTemplate   DnsTemplate     `json:"dns_template,omitempty" gorm:"constraint:OnUpdate:CASCADE,OnDelete:RESTRICT;"`
	Comment       string          `json:"comment" gorm:"type:text"`
	Records       []DnsZoneRecord `json:"records" gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}

DnsZone is one managed zone. Type is forward / reverse4 / reverse6, matching dnsmgr2. Records are extra RRs; SOA and apex NS come from DnsTemplate.

func (DnsZone) TableName added in v1.0.6

func (DnsZone) TableName() string

type DnsZoneDTO added in v1.0.6

type DnsZoneDTO struct {
	ID            uint               `json:"id"`
	Name          string             `json:"name"`
	Type          string             `json:"type"`
	DnsTemplateID uint               `json:"dns_template_id"`
	DnsTemplate   string             `json:"dns_template"`
	Comment       string             `json:"comment"`
	Records       []DnsZoneRecordDTO `json:"records,omitempty"`
}

type DnsZoneRecord added in v1.0.6

type DnsZoneRecord struct {
	ID          uint   `json:"id" gorm:"primaryKey"`
	DnsZoneID   uint   `json:"dns_zone_id" gorm:"not null;uniqueIndex:idx_dns_zone_record_rank"`
	Rank        uint   `json:"rank" gorm:"not null;uniqueIndex:idx_dns_zone_record_rank"`
	Name        string `json:"name" gorm:"type:varchar(255);not null"`
	TTL         *uint  `json:"ttl"`
	Type        string `json:"type" gorm:"column:record_type;type:varchar(16);not null"`
	Value       string `json:"value" gorm:"type:text;not null"`
	Description string `json:"description" gorm:"type:text"`
	MAC         string `json:"mac" gorm:"type:varchar(32)"`
}

DnsZoneRecord is one DNS resource record in a zone. Rank is the display / records-file order (0-based). TTL is seconds; nil means unspecified (use the template default). A stored 0 is treated as unspecified — DNS TTL 0 is not used. Description is an optional operator note; it is not part of the DNS RDATA. MAC is a DHCP host reservation (A/AAAA only). It is its own column, not a comment; factum2-dns writes it as a `mac` field on the JSON A/AAAA record so dnsmgr2 can emit a Kea reservation.

func (DnsZoneRecord) TableName added in v1.0.6

func (DnsZoneRecord) TableName() string

type DnsZoneRecordDTO added in v1.0.6

type DnsZoneRecordDTO struct {
	Name        string `json:"name"`
	TTL         *uint  `json:"ttl"`
	Type        string `json:"type"`
	Value       string `json:"value"`
	Description string `json:"description"`
	MAC         string `json:"mac"`
}

type EnumChoice added in v1.1.0

type EnumChoice struct {
	Label string `json:"label"`
	Value string `json:"value"`
}

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

	// Inclusive bounds for int/vlan values and list length.
	// VLAN defaults unset Min/Max to 1 and 4094 and clamps custom bounds to that range; int is unbounded.
	Min  *float64 `json:"min,omitempty"`
	Max  *float64 `json:"max,omitempty"`
	Unit string   `json:"unit,omitempty"`

	BoolTrueLabel  string `json:"bool_true_label,omitempty"`
	BoolFalseLabel string `json:"bool_false_label,omitempty"`

	Enum []EnumChoice `json:"enum,omitempty"`

	Items *FieldSchema `json:"items,omitempty"`

	// Resource names a kind=resource pool. Prefix-typed nodes only, including items.
	Resource string `json:"resource,omitempty"`

	// Default is used when the field is empty on a service or endpoint.
	// Named fields only; list items cannot set default.
	Default json.RawMessage `json:"default,omitempty"`
}

FieldSchema is one typed field on a service type or interfaces spec. Nested Items is the same shape; list items are nameless.

type FloorPlan added in v1.1.1

type FloorPlan struct {
	FactumModel
	SiteID   uint   `json:"site_id" gorm:"index;not null"`
	Name     string `json:"name" gorm:"type:varchar(255);not null"`
	WidthMM  int    `json:"width_mm" gorm:"column:width_mm"`
	HeightMM int    `json:"height_mm" gorm:"column:height_mm"`
	GridMM   int    `json:"grid_mm" gorm:"column:grid_mm"`
	Revision int    `json:"revision"`
}

FloorPlan is a Factum drawing of a room. SiteID is a local sites.id.

func (*FloorPlan) BeforeCreate added in v1.1.1

func (p *FloorPlan) BeforeCreate(tx *gorm.DB) error

type FloorPlanAnnotation added in v1.1.1

type FloorPlanAnnotation struct {
	FactumModel
	FloorPlanID uint   `json:"floor_plan_id" gorm:"index;not null"`
	Kind        string `json:"kind" gorm:"type:varchar(32)"`
	Text        string `json:"text" gorm:"type:varchar(255)"`
	XMM         int    `json:"x_mm" gorm:"column:x_mm"`
	YMM         int    `json:"y_mm" gorm:"column:y_mm"`
	WidthMM     int    `json:"width_mm" gorm:"column:width_mm"`
	HeightMM    int    `json:"height_mm" gorm:"column:height_mm"`
	Rotation    int    `json:"rotation"`
}

FloorPlanAnnotation is a label, aisle or zone on a floor plan.

type FloorPlanRack added in v1.1.1

type FloorPlanRack struct {
	FactumModel
	FloorPlanID uint `json:"floor_plan_id" gorm:"uniqueIndex:idx_floor_plan_racks_plan_rack;not null"`
	RackID      uint `json:"rack_id" gorm:"uniqueIndex:idx_floor_plan_racks_plan_rack;index;not null"`
	XMM         int  `json:"x_mm" gorm:"column:x_mm"`
	YMM         int  `json:"y_mm" gorm:"column:y_mm"`
	Rotation    int  `json:"rotation"`
}

FloorPlanRack places a rack on a floor plan. X/Y are mm from the plan origin.

type Interface

type Interface struct {
	FactumModel
	DeviceID uint `json:"device_id" gorm:"index"`
	// NetboxID uniqueness is a partial index WHERE netbox_id <> 0 so many
	// Factum-local interfaces (netbox_id=0) can coexist on one device.
	NetboxID    uint   `json:"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, Cisco SMB), "" 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 InterfaceCreateDTO added in v1.1.0

type InterfaceCreateDTO struct {
	ID          uint   `json:"id"`
	DeviceID    uint   `json:"device_id"`
	Name        string `json:"name"`
	Type        string `json:"type"`
	Label       string `json:"label"`
	Description string `json:"description"`
	VRF         string `json:"vrf"`
	Enabled     *bool  `json:"enabled"`
}

InterfaceCreateDTO is the POST/PUT /api/dcim/interfaces body for a Factum-local device interface (NetboxID stays 0).

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 InterfaceTemplate added in v1.1.0

type InterfaceTemplate struct {
	FactumModel
	DeviceTypeID uint   `json:"device_type_id" gorm:"uniqueIndex:idx_interface_templates_type_name;not null"`
	Name         string `json:"name" gorm:"uniqueIndex:idx_interface_templates_type_name;type:varchar(255);not null"`
	Type         string `json:"type" gorm:"type:varchar(255)"`
	Label        string `json:"label" gorm:"type:varchar(255)"`
	Description  string `json:"description" gorm:"type:varchar(255)"`
	Source       string `json:"source" gorm:"type:varchar(32)"`
	NetboxID     uint   `json:"netbox_id"`
}

InterfaceTemplate is a port defined on a DeviceType (NetBox dcim.InterfaceTemplate). Copied onto a device when that device is created locally, and shown under DCIM → Device types.

func (*InterfaceTemplate) BeforeCreate added in v1.1.0

func (t *InterfaceTemplate) BeforeCreate(tx *gorm.DB) error

type InterfaceTemplateDTO added in v1.1.0

type InterfaceTemplateDTO struct {
	ID           uint   `json:"id"`
	DeviceTypeID uint   `json:"device_type_id"`
	Name         string `json:"name"`
	Type         string `json:"type"`
	Label        string `json:"label"`
	Description  string `json:"description"`
}

type InterfaceType added in v1.1.1

type InterfaceType struct {
	FactumModel
	Value     string `json:"value" gorm:"type:varchar(255);uniqueIndex;not null"`
	Label     string `json:"label" gorm:"type:varchar(255);not null"`
	Source    string `json:"source" gorm:"type:varchar(32)"`
	SortOrder int    `json:"sort_order"`
}

InterfaceType is a DCIM catalog row for NetBox interface.type choices (e.g. "1000base-t"). NetBox has no interface-types resource; full sync fills this from OPTIONS /api/dcim/interfaces/. Factum-local rows can be added in the GUI. Value is what interfaces and templates store.

func (*InterfaceType) BeforeCreate added in v1.1.1

func (t *InterfaceType) BeforeCreate(tx *gorm.DB) error

func (*InterfaceType) BeforeUpdate added in v1.1.1

func (t *InterfaceType) BeforeUpdate(tx *gorm.DB) error

type InterfaceTypeDTO added in v1.1.1

type InterfaceTypeDTO struct {
	ID        uint   `json:"id"`
	Value     string `json:"value"`
	Label     string `json:"label"`
	SortOrder int    `json:"sort_order"`
}

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. The empty-name namespace is implicit: its prefixes and extra VRFs appear at the forest root so operators who do not use namespaces can allocate there directly.

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)"`
	// DhcpEnabled is the per-prefix "run a DHCP server here" checkbox.
	DhcpEnabled bool `json:"dhcp_enabled"`
	// DhcpRangeStart/End are the dynamic pool. Both empty is static-only.
	// When set they must sit inside Prefix.
	DhcpRangeStart string `json:"dhcp_range_start" gorm:"type:varchar(80)"`
	DhcpRangeEnd   string `json:"dhcp_range_end" gorm:"type:varchar(80)"`
	// DhcpGateway empty means "first usable address in the prefix"
	// (network + 1 for IPv4 /30 or shorter).
	DhcpGateway string `json:"dhcp_gateway" gorm:"type:varchar(80)"`
	// DhcpDnsServers is newline-separated. Empty means use the global
	// default (Settings.DhcpDnsServers).
	DhcpDnsServers string `json:"dhcp_dns_servers" gorm:"type:text"`
}

IpamPrefix is a CIDR allocated to exactly one VRF in a namespace. Parent/child relationships are computed from containment, not stored. DHCP fields are used when Settings.DhcpEnabled is on; turning that flag off only hides the UI — values stay on the row.

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"`
	DhcpEnabled    bool   `json:"dhcp_enabled"`
	DhcpRangeStart string `json:"dhcp_range_start"`
	DhcpRangeEnd   string `json:"dhcp_range_end"`
	DhcpGateway    string `json:"dhcp_gateway"`
	DhcpDnsServers string `json:"dhcp_dns_servers"`
}

type IpamVRF

type IpamVRF struct {
	FactumModel
	NamespaceID uint   `json:"namespace_id" gorm:"index;not null"`
	Name        string `json:"name" gorm:"not null;type:varchar(255)"`
	Description string `json:"description" gorm:"type:varchar(255)"`
	IsDefault   bool   `json:"is_default"`
	// RD is the BGP route distinguisher (e.g. "65000:1").
	RD string `json:"rd" gorm:"type:varchar(255)"`
	// ImportRT / ExportRT are route-targets, comma-separated when there
	// are several (NetBox import_targets / export_targets).
	ImportRT string `json:"import_rt" gorm:"type:text"`
	ExportRT string `json:"export_rt" gorm:"type:text"`
	// Source is "netbox" when upserted from sync, "factum" when created
	// in the UI. NetBox-synced rows are read-only.
	Source   string `json:"source" gorm:"type:varchar(32)"`
	NetboxID uint   `json:"netbox_id"`
}

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. Extra VRF names are unique across all namespaces; each namespace still has its own default VRF (named "default"), which is created with the namespace and cannot be deleted.

func (*IpamVRF) BeforeCreate added in v1.1.1

func (v *IpamVRF) BeforeCreate(tx *gorm.DB) error

func (IpamVRF) IsLocal added in v1.1.1

func (v IpamVRF) IsLocal() bool

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"`
	RD          string `json:"rd"`
	ImportRT    string `json:"import_rt"`
	ExportRT    string `json:"export_rt"`
}

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 Manufacturer added in v1.1.0

type Manufacturer struct {
	FactumModel
	Name     string `json:"name" gorm:"type:varchar(255);uniqueIndex;not null"`
	Slug     string `json:"slug" gorm:"type:varchar(255);uniqueIndex;not null"`
	Source   string `json:"source" gorm:"type:varchar(32)"`
	NetboxID uint   `json:"netbox_id"`
}

Manufacturer is the shared DCIM catalog (NetBox dcim.Manufacturer). Source is "netbox" when upserted from sync, "factum" when created in the UI.

func (*Manufacturer) BeforeCreate added in v1.1.0

func (m *Manufacturer) BeforeCreate(tx *gorm.DB) error

func (*Manufacturer) BeforeUpdate added in v1.1.0

func (m *Manufacturer) BeforeUpdate(tx *gorm.DB) error

type ManufacturerDTO added in v1.1.0

type ManufacturerDTO struct {
	ID   uint   `json:"id"`
	Name string `json:"name"`
	Slug string `json:"slug"`
}

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 Platform added in v1.1.0

type Platform struct {
	FactumModel
	Name           string `json:"name" gorm:"type:varchar(255);uniqueIndex;not null"`
	Slug           string `json:"slug" gorm:"type:varchar(255);uniqueIndex;not null"`
	ManufacturerID uint   `json:"manufacturer_id"`
	Source         string `json:"source" gorm:"type:varchar(32)"`
	NetboxID       uint   `json:"netbox_id"`
}

Platform is the shared DCIM catalog (NetBox dcim.Platform). Slug is what drivers match on (eos, sros, vrp, …).

func (*Platform) BeforeCreate added in v1.1.0

func (p *Platform) BeforeCreate(tx *gorm.DB) error

func (*Platform) BeforeUpdate added in v1.1.0

func (p *Platform) BeforeUpdate(tx *gorm.DB) error

type PlatformDTO added in v1.1.0

type PlatformDTO struct {
	ID             uint   `json:"id"`
	Name           string `json:"name"`
	Slug           string `json:"slug"`
	ManufacturerID uint   `json:"manufacturer_id"`
}

type Product

type Product struct {
	FactumModel
	LastSync uint   `json:"-"`
	Name     string `json:"name"`
}

type Rack added in v1.1.1

type Rack struct {
	FactumModel
	SiteID    uint   `json:"site_id" gorm:"index;not null"`
	Name      string `json:"name" gorm:"type:varchar(255);not null"`
	Source    string `json:"source" gorm:"type:varchar(32)"`
	NetboxID  uint   `json:"netbox_id"`
	HeightU   int    `json:"height_u" gorm:"column:height_u;not null"`
	WidthMM   int    `json:"width_mm" gorm:"column:width_mm"`
	DepthMM   int    `json:"depth_mm" gorm:"column:depth_mm"`
	StartUnit int    `json:"start_unit"`
	Numbering string `json:"numbering" gorm:"type:varchar(32)"`
	Version   int    `json:"version"`
}

Rack is a physical equipment rack. SiteID is a local sites.id.

func (*Rack) BeforeCreate added in v1.1.1

func (r *Rack) BeforeCreate(tx *gorm.DB) error

func (Rack) HeightTicks added in v1.1.1

func (r Rack) HeightTicks() int

func (Rack) IsLocal added in v1.1.1

func (r Rack) IsLocal() bool

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

	// ConnectionTypeID is the chosen service_connection_types row, if any.
	ConnectionTypeID *uint `json:"connection_type_id"`

	// 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 ServiceConnectionType added in v1.1.0

type ServiceConnectionType struct {
	FactumModel
	ServiceTypeID uint   `json:"service_type_id" gorm:"uniqueIndex:idx_svc_ct_type_name;not null"`
	Name          string `json:"name" gorm:"uniqueIndex:idx_svc_ct_type_name;not null;type:varchar(64)"`
	SortOrder     int    `json:"sort_order"`
	Image         []byte `json:"-" gorm:"type:bytea"`
	ContentType   string `json:"content_type" gorm:"type:varchar(64)"`
	// HasImage is filled by list/get loaders from a length check; not a column.
	HasImage bool `json:"-" gorm:"-"`
}

ServiceConnectionType is a named connection choice on a definition.

func (ServiceConnectionType) TableName added in v1.1.0

func (ServiceConnectionType) TableName() string

type ServiceConnectionTypeDTO added in v1.1.0

type ServiceConnectionTypeDTO struct {
	ID          uint   `json:"id"`
	Name        string `json:"name"`
	SortOrder   int    `json:"sort_order"`
	ContentType string `json:"content_type,omitempty"`
	HasImage    bool   `json:"has_image"`
	ImageURL    string `json:"image_url,omitempty"`
}

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"`
	ConnectionTypeID *uint           `json:"connection_type_id"`
}

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"`
	// Applied* is the last successful push snapshot for this binding.
	AppliedDeviceID uint            `json:"-"`
	AppliedIface    string          `json:"-"`
	AppliedPlatform string          `json:"-"`
	AppliedFields   json.RawMessage `json:"-" gorm:"serializer:json"`
}

ServiceEndpoint is a service termination. Role is always "interface".

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 ServiceInterfacesSpec added in v1.1.0

type ServiceInterfacesSpec struct {
	Min    int           `json:"min"`
	Max    int           `json:"max"`
	Unique bool          `json:"unique"`
	Fields []FieldSchema `json:"fields"`
}

ServiceInterfacesSpec is the homogeneous UNI spec for a service type. Max == 0 means unlimited.

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"`
	Interfaces  ServiceInterfacesSpec `json:"interfaces" 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)"`
	ConnectionTypes []ServiceConnectionType `json:"connection_types" gorm:"foreignKey:ServiceTypeID"`
}

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"`
	Interfaces      ServiceInterfacesSpec      `json:"interfaces"`
	SyncSource      string                     `json:"sync_source"`
	NetboxType      string                     `json:"netbox_type"`
	ConnectionTypes []ServiceConnectionTypeDTO `json:"connection_types"`
}

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,
	// Sites) in the web GUI. Off (nil/false) is the default. Turning it off
	// only hides the menu — customer, contact and site rows stay in the
	// database, and services may still reference them.
	OrganizationEnabled *bool `gorm:"column:organization_enabled" form:"organization_enabled" json:"organization_enabled"`
	// DnsZonesEnabled gates the DNS zone editor UI and /api/dns/* zone
	// routes (SOA templates, DNS templates, DNSSEC policies, zones).
	// Off (nil/false) is the default. Distinct from DnsEnabled, which is
	// the Destinations toggle for device-record sync. Turning this off
	// only hides the UI — rows stay in the database.
	DnsZonesEnabled *bool `gorm:"column:dns_zones_enabled" form:"dns_zones_enabled" json:"dns_zones_enabled"`
	// DhcpEnabled gates DHCP server management: per-prefix DHCP in IPAM
	// and the MAC column on DNS zone records. Off (nil/false) is the
	// default. Turning it off only hides the UI — prefix DHCP fields and
	// record MACs stay in the database. factum2-dns writes Kea prefixes
	// into the dnsmgr2 prefix include when this is on.
	DhcpEnabled       *bool `gorm:"column:dhcp_enabled" form:"dhcp_enabled" json:"dhcp_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"`
	// CertsEnabled gates the Certificates GUI, /api/certs/*, and the
	// dest "certs" sync (factum2-certs writes .lego.yaml/.env and runs lego).
	CertsEnabled *bool `gorm:"column:certs_enabled" form:"certs_enabled" json:"certs_enabled"`
	// StorageEnabled gates the Software GUI and /api/software/*. Off
	// (nil/false) is the default. Turning it off only hides the UI — files
	// on the storage host are not deleted.
	StorageEnabled *bool `gorm:"column:storage_enabled" form:"storage_enabled" json:"storage_enabled"`
	// StorageRoot is the repository directory on the storage host
	// (factum2-storage start). Empty is /var/lib/factum2/storage.
	StorageRoot string `gorm:"column:storage_root" form:"storage_root" json:"storage_root"`
	// StorageHTTPListen is the device-facing HTTP bind (e.g. ":8088").
	// Empty disables the HTTP server.
	StorageHTTPListen string `gorm:"column:storage_http_listen" form:"storage_http_listen" json:"storage_http_listen"`
	// StorageHTTPURL is the origin devices use in copy http://… commands
	// (e.g. http://10.0.0.5:8088). Not the listen address.
	StorageHTTPURL      string `gorm:"column:storage_http_url" form:"storage_http_url" json:"storage_http_url"`
	StorageTFTPListen   string `gorm:"column:storage_tftp_listen" form:"storage_tftp_listen" json:"storage_tftp_listen"`
	StorageTFTPHost     string `gorm:"column:storage_tftp_host" form:"storage_tftp_host" json:"storage_tftp_host"`
	StorageSFTPListen   string `gorm:"column:storage_sftp_listen" form:"storage_sftp_listen" json:"storage_sftp_listen"`
	StorageSFTPHost     string `gorm:"column:storage_sftp_host" form:"storage_sftp_host" json:"storage_sftp_host"`
	StorageSFTPUser     string `gorm:"column:storage_sftp_user" form:"storage_sftp_user" json:"storage_sftp_user"`
	StorageSFTPPassword string `gorm:"column:storage_sftp_password" form:"storage_sftp_password" json:"storage_sftp_password"`

	// factum
	FactumApiToken string `gorm:"column:factum_api_token" form:"factum_api_token" json:"factum_api_token"`
	// wordmark (data URL or http(s) URL). Empty hides it.
	BrandLogo string `gorm:"column:brand_logo;type:text" form:"brand_logo" json:"brand_logo"`
	// BrandText is optional label shown next to BrandLogo.
	BrandText string `gorm:"column:brand_text" form:"brand_text" json:"brand_text"`
	// 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"`
	// DnsZonesFile is the path factum2-dns writes the dnsmgr2 zone-include
	// YAML to when DnsZonesEnabled is on. Empty skips writing (the
	// operator lists zones in the main dnsmgr2.yaml).
	DnsZonesFile string `gorm:"column:dns_zones_file" form:"dns_zones_file" json:"dns_zones_file"`
	// Unused by current factum2-dns (the administrator-managed
	// dnsmgr2.yaml holds BIND paths and the sqlite serial DB). Kept so
	// existing Settings rows continue to load.
	DnsConfigFile        string `gorm:"column:dns_config_file" form:"dns_config_file" json:"dns_config_file"`
	DnsDbFile            string `gorm:"column:dns_db_file" form:"dns_db_file" json:"dns_db_file"`
	DnsHostTemplate      string `gorm:"column:dns_host_template" form:"dns_host_template" json:"dns_host_template"`
	DnsBindType          string `gorm:"column:dns_bind_type" form:"dns_bind_type" json:"dns_bind_type"`
	DnsBindConfigDir     string `gorm:"column:dns_bind_config_dir" form:"dns_bind_config_dir" json:"dns_bind_config_dir"`
	DnsBindIncludeFile   string `gorm:"column:dns_bind_include_file" form:"dns_bind_include_file" json:"dns_bind_include_file"`
	DnsBindZonesDir      string `gorm:"column:dns_bind_zones_dir" form:"dns_bind_zones_dir" json:"dns_bind_zones_dir"`
	DnsBindZonesFile     string `gorm:"column:dns_bind_zones_file" form:"dns_bind_zones_file" json:"dns_bind_zones_file"`
	DnsBindTmpDir        string `gorm:"column:dns_bind_tmp_dir" form:"dns_bind_tmp_dir" json:"dns_bind_tmp_dir"`
	DnsBindCmdReloadAll  string `gorm:"column:dns_bind_cmd_reload_all" form:"dns_bind_cmd_reload_all" json:"dns_bind_cmd_reload_all"`
	DnsBindCmdReloadZone string `gorm:"column:dns_bind_cmd_reload_zone" form:"dns_bind_cmd_reload_zone" json:"dns_bind_cmd_reload_zone"`
	DnsBindCmdRestart    string `gorm:"column:dns_bind_cmd_restart" form:"dns_bind_cmd_restart" json:"dns_bind_cmd_restart"`
	// DhcpDnsServers is the default DNS server list offered to DHCP
	// clients (newline-separated). A prefix may override this. Edited on
	// the Factum tab next to the DHCP feature switch.
	DhcpDnsServers string `gorm:"column:dhcp_dns_servers;type:text" form:"dhcp_dns_servers" json:"dhcp_dns_servers"`
	// DhcpPrefixesFile is the path factum2-dns writes the dnsmgr2
	// prefix-include YAML to when DhcpEnabled is on. Empty skips writing.
	DhcpPrefixesFile string `gorm:"column:dhcp_prefixes_file" form:"dhcp_prefixes_file" json:"dhcp_prefixes_file"`
	// Kea host-template fields. Paths belong in the administrator-managed
	// dnsmgr2.yaml; these Settings columns remain for existing rows.
	DhcpHostTemplate    string `gorm:"column:dhcp_host_template" form:"dhcp_host_template" json:"dhcp_host_template"`
	DhcpKeaType         string `gorm:"column:dhcp_kea_type" form:"dhcp_kea_type" json:"dhcp_kea_type"`
	DhcpKea4ConfigDir   string `gorm:"column:dhcp_kea4_config_dir" form:"dhcp_kea4_config_dir" json:"dhcp_kea4_config_dir"`
	DhcpKea4IncludeFile string `gorm:"column:dhcp_kea4_include_file" form:"dhcp_kea4_include_file" json:"dhcp_kea4_include_file"`
	DhcpKea4TmpDir      string `gorm:"column:dhcp_kea4_tmp_dir" form:"dhcp_kea4_tmp_dir" json:"dhcp_kea4_tmp_dir"`
	DhcpKea4CmdRestart  string `gorm:"column:dhcp_kea4_cmd_restart" form:"dhcp_kea4_cmd_restart" json:"dhcp_kea4_cmd_restart"`
	DhcpKea6ConfigDir   string `gorm:"column:dhcp_kea6_config_dir" form:"dhcp_kea6_config_dir" json:"dhcp_kea6_config_dir"`
	DhcpKea6IncludeFile string `gorm:"column:dhcp_kea6_include_file" form:"dhcp_kea6_include_file" json:"dhcp_kea6_include_file"`
	DhcpKea6TmpDir      string `gorm:"column:dhcp_kea6_tmp_dir" form:"dhcp_kea6_tmp_dir" json:"dhcp_kea6_tmp_dir"`
	DhcpKea6CmdRestart  string `gorm:"column:dhcp_kea6_cmd_restart" form:"dhcp_kea6_cmd_restart" json:"dhcp_kea6_cmd_restart"`

	// Certificates / lego (https://github.com/go-acme/lego). Paths are
	// written by factum2-certs on sync. Empty yaml/env paths skip writing.
	CertsLegoYaml                string `gorm:"column:certs_lego_yaml" form:"certs_lego_yaml" json:"certs_lego_yaml"`
	CertsEnvFile                 string `gorm:"column:certs_env_file" form:"certs_env_file" json:"certs_env_file"`
	CertsLegoBin                 string `gorm:"column:certs_lego_bin" form:"certs_lego_bin" json:"certs_lego_bin"`
	CertsLegoStorage             string `gorm:"column:certs_lego_storage" form:"certs_lego_storage" json:"certs_lego_storage"`
	CertsDefaultKeyType          string `gorm:"column:certs_default_key_type" form:"certs_default_key_type" json:"certs_default_key_type"`
	CertsDefaultEnableCommonName *bool  `` /* 126-byte string literal not displayed */

	// 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"`
	// IcingaCertsFile is the Icinga 2 conf written by factum2-icinga for
	// HTTPS certificate checks (one Service per certificate name).
	IcingaCertsFile string `gorm:"column:icinga_certs_file" form:"icinga_certs_file" json:"icinga_certs_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 a Jet 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
	// Jet templates (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"`
	// IcingaCertTemplate is a Jet template executed once per Icinga sync
	// with .Checks (Host, Domain, CertName). Typically an Icinga
	// `template Service` plus one `object Service` per check.
	IcingaCertTemplate string `gorm:"column:icinga_cert_template;type:text" form:"icinga_cert_template" json:"icinga_cert_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
	ParentID   *uint   `json:"parent_id" gorm:"index"`
	Name       string  `json:"name" gorm:"type:varchar(255);not null"`
	Slug       string  `json:"slug" gorm:"type:varchar(255)"`
	Source     string  `json:"source" gorm:"type:varchar(32)"`
	NetboxKind string  `json:"netbox_kind" gorm:"type:varchar(32)"`
	NetboxID   uint    `json:"netbox_id"`
	Latitude   float64 `json:"latitude"`
	Longitude  float64 `json:"longitude"`
}

Site is one node in the Organization sites tree. NetBox regions, sites and locations all map onto this table (parented the same way they nest in NetBox); operators can also create sites here (source=factum). GPS is optional — the network map only plots rows that have coordinates.

func (*Site) BeforeCreate added in v1.1.0

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

func (*Site) BeforeUpdate added in v1.1.0

func (s *Site) BeforeUpdate(tx *gorm.DB) error

func (Site) HasCoordinates added in v1.1.0

func (s Site) HasCoordinates() bool

func (Site) IsLocal added in v1.1.0

func (s Site) IsLocal() bool

type SiteDTO added in v1.1.0

type SiteDTO struct {
	ID        uint    `json:"id"`
	ParentID  *uint   `json:"parent_id"`
	Name      string  `json:"name"`
	Latitude  float64 `json:"latitude"`
	Longitude float64 `json:"longitude"`
}

SiteDTO is the create/update body for /api/sites. Source/NetboxKind/ NetboxID are sync-managed and excluded so a caller cannot fake a NetBox origin or rewrite the import key.

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