CI / go-test (push) Waiting to run
CI / postgres-durable-first (push) Waiting to run
CI / codegen (push) Waiting to run
CI / govulncheck (push) Waiting to run
CI / race (push) Waiting to run
CI / fuzz-smoke (push) Waiting to run
CI / golangci (push) Waiting to run
CI / frontend (push) Waiting to run
Docs CI / build (push) Waiting to run
Docs Deploy (GitHub Pages) / build (push) Waiting to run
Docs Deploy (GitHub Pages) / deploy (push) Blocked by required conditions
Release 3X-UI / build (386) (push) Waiting to run
Release 3X-UI / build (amd64) (push) Waiting to run
Release 3X-UI / build (arm64) (push) Waiting to run
Release 3X-UI / build (armv5) (push) Waiting to run
Release 3X-UI / build (armv6) (push) Waiting to run
Release 3X-UI / build (armv7) (push) Waiting to run
Release 3X-UI / build (s390x) (push) Waiting to run
Release 3X-UI / Build for Windows (amd64) (push) Waiting to run
Release 3X-UI / Publish rolling dev release (push) Blocked by required conditions
CodeQL Advanced / Analyze (go) (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
- 0: sing-box gen overrides fragment listen/port from the inbound row (empty listen -> "::"), Update/Add recompute the stored fragment; non-loopback E2E against the real core binary. - 1: agent fetches the sing-box core from the Gitea release when missing, self-checks it, and only then latches the switch. - 2: node list shows the sing-box core version (heartbeat field, persisted on the node row), xray version as fallback. - 3: public-page fallback returns 404 to anonymous visitors when the page is disabled. - 4: settings restart hint bar removed, dead i18n keys cleaned. - 5: two PUT calls missing JSON headers fixed (audit: only those two), batch probe-task creation endpoint + UI. - 6: cf-probe retirement card and coverage pipeline removed. - 7: generated vless links no longer carry spx (panel export + sub); parser still accepts spx; golden fixtures updated. - probe batch: dedicated probe settings tab (public-page settings, tasks, per-node meta), retention_days editable, per-node traffic calibration + stat-interface pinning, runtime metrics split into a lightweight fast report (default 3s, 1-60s adjustable). - 8: sing-box core processes start with GOMEMLIMIT=32MiB, GOGC=50. - 9: dead /getTwoFactorEnable docs entries removed (frontend call and contract entry already gone at base; verified by grep).
124 lines
6.5 KiB
Go
124 lines
6.5 KiB
Go
package model
|
|
|
|
// AgentNode binds a lightweight agent (token-registered, no web panel) to a
|
|
// 3x-ui node row and carries the per-agent egress configuration.
|
|
//
|
|
// Agents are the only writers of their own LastSeen; the panel owns everything
|
|
// else. Authentication uses only the SHA-256 hex of the token; since the
|
|
// next-core batch (item 19) the raw token is additionally kept recoverably
|
|
// in TokenPlain — an operator-accepted tradeoff so the node edit page can
|
|
// re-display the full install command.
|
|
type AgentNode struct {
|
|
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
|
TokenHash string `json:"-" gorm:"size:64;uniqueIndex"`
|
|
TokenPrefix string `json:"tokenPrefix" gorm:"size:16;index"`
|
|
// TokenPlain is a recoverable copy of the raw token: stored at
|
|
// issuance, and backfilled from the first authenticated agent request
|
|
// for rows created before this field existed (item 19). Existing
|
|
// values are never overwritten by requests; only issuance (create /
|
|
// panel-side re-issue) writes a fresh one. Never serialized.
|
|
TokenPlain string `json:"-" gorm:"type:text"`
|
|
Name string `json:"name"`
|
|
// NodeID is the 3x-ui node whose inbounds this agent serves. Nil means the
|
|
// token was issued but not yet bound to a node.
|
|
NodeID *int `json:"nodeId" gorm:"index"`
|
|
// Outbounds is the full xray "outbounds" JSON array pushed to this agent,
|
|
// replacing the template defaults. Empty means "use template defaults".
|
|
Outbounds string `json:"outbounds" gorm:"type:text"`
|
|
// RoutingRules is the full xray "routing.rules" JSON array pushed to this
|
|
// agent, replacing the template defaults. Empty means "use template defaults".
|
|
RoutingRules string `json:"routingRules" gorm:"type:text"`
|
|
// SingboxOutbounds / SingboxRoutingRules are the sing-box conversions
|
|
// of the two override columns above, produced by the B1 one-shot
|
|
// migration (ITEM6 §5.6). Entries that cannot be mapped stay only in
|
|
// the legacy columns and are listed in the migration report. The
|
|
// legacy columns stay the read source until B2.
|
|
SingboxOutbounds string `json:"-" gorm:"column:singbox_outbounds;type:text"`
|
|
SingboxRoutingRules string `json:"-" gorm:"column:singbox_routing_rules;type:text"`
|
|
LastSeen int64 `json:"lastSeen"`
|
|
CreatedAt int64 `json:"createdAt" gorm:"autoCreateTime:milli"`
|
|
UpdatedAt int64 `json:"updatedAt" gorm:"autoUpdateTime:milli"`
|
|
|
|
// Heartbeat telemetry (P13): the agent piggybacks host/runtime facts on
|
|
// its 60s heartbeat and the panel overlays them onto the node list row.
|
|
// All fields are best-effort: an agent that never reported telemetry has
|
|
// TelemetryAt == 0 and the UI keeps showing "-" for its row.
|
|
AgentVersion string `json:"agentVersion"`
|
|
XrayVersion string `json:"xrayVersion"`
|
|
// SingboxVersion is the agent's sing-box core version (V2.0.1 ②):
|
|
// reported on the same telemetry since ITEM6 B3, persisted only
|
|
// from V2.0.1 on — the node list shows it in place of the xray
|
|
// version for agents that report one.
|
|
SingboxVersion string `json:"singboxVersion"`
|
|
UptimeSecs uint64 `json:"uptimeSecs"`
|
|
LatencyMs int `json:"latencyMs"`
|
|
CpuPct float64 `json:"cpuPct"`
|
|
MemPct float64 `json:"memPct"`
|
|
IPv4 string `json:"ipv4"`
|
|
IPv6 string `json:"ipv6"`
|
|
// TelemetryAt is the unix-milli time of the last heartbeat that carried
|
|
// a telemetry object.
|
|
TelemetryAt int64 `json:"telemetryAt"`
|
|
|
|
// Probe snapshot (item 7, B1): the monitor-probe-style host Facts and
|
|
// latest Metrics the agent piggybacks on register + heartbeat. Only the
|
|
// latest values live here; history lands in B2's probe tables. Agents
|
|
// that never reported a probe block keep ProbeAt == 0 and zero values.
|
|
ProbeHostname string `json:"probeHostname"`
|
|
ProbeOS string `json:"probeOs"`
|
|
ProbeKernel string `json:"probeKernel"`
|
|
ProbeArch string `json:"probeArch"`
|
|
ProbeVirt string `json:"probeVirt"`
|
|
ProbeCPUName string `json:"probeCpuName"`
|
|
ProbeCPUCores uint32 `json:"probeCpuCores"`
|
|
ProbeBootID string `json:"probeBootId"`
|
|
ProbeIface string `json:"probeIface"`
|
|
ProbeLoad1 float32 `json:"probeLoad1"`
|
|
ProbeLoad5 float32 `json:"probeLoad5"`
|
|
ProbeLoad15 float32 `json:"probeLoad15"`
|
|
ProbeMemTotal uint64 `json:"probeMemTotal"`
|
|
ProbeMemUsed uint64 `json:"probeMemUsed"`
|
|
ProbeSwapTotal uint64 `json:"probeSwapTotal"`
|
|
ProbeSwapUsed uint64 `json:"probeSwapUsed"`
|
|
ProbeDiskTotal uint64 `json:"probeDiskTotal"`
|
|
ProbeDiskUsed uint64 `json:"probeDiskUsed"`
|
|
ProbeNetRxTot uint64 `json:"probeNetRxTotal"`
|
|
ProbeNetTxTot uint64 `json:"probeNetTxTotal"`
|
|
ProbeNetRx uint64 `json:"probeNetRx"`
|
|
ProbeNetTx uint64 `json:"probeNetTx"`
|
|
ProbeTCP uint32 `json:"probeTcp"`
|
|
ProbeUDP uint32 `json:"probeUdp"`
|
|
ProbeProcs uint32 `json:"probeProcs"`
|
|
ProbeCPU float32 `json:"probeCpu"`
|
|
// ProbeAt is the unix-milli time of the last heartbeat that carried a
|
|
// probe metrics block.
|
|
ProbeAt int64 `json:"probeAt"`
|
|
|
|
// Seen source addresses (public-address resolution): the source IP of
|
|
// the agent's own authenticated requests, as observed by the master —
|
|
// the CF-Connecting-IP header when the agent comes through Cloudflare,
|
|
// else the direct peer address. An agent behind 1:1 NAT reports only
|
|
// its private interface address in telemetry (its public address is
|
|
// not assigned to any local interface, so enumeration can never see
|
|
// it); the observed source address is the master's view of the
|
|
// agent's public side. Writes are change-only (see
|
|
// AgentController.captureSeenIP); SeenIPAt is the unix-milli time of
|
|
// the last change, not of the last request.
|
|
SeenIPv4 string `json:"seenIpv4"`
|
|
SeenIPv6 string `json:"seenIpv6"`
|
|
SeenIPAt int64 `json:"seenIpAt"`
|
|
|
|
// One-click update (item 20): the master records an update intent here;
|
|
// the agent picks it up from its config poll, downloads + verifies +
|
|
// swaps its binary, restarts itself and reports the outcome back on
|
|
// the heartbeat. UpdateRequestedAt == 0 means no intent is pending.
|
|
UpdateRequestedAt int64 `json:"updateRequestedAt"`
|
|
UpdateTarget string `json:"updateTarget"` // A-form display target, e.g. "A1.0.1"
|
|
UpdateTag string `json:"updateTag"` // release tag to download, e.g. "V1.0.1"
|
|
// UpdateState: "" | pending | downloading | swapped | failed |
|
|
// current | success (success = new A version observed by the master).
|
|
UpdateState string `json:"updateState"`
|
|
UpdateError string `json:"updateError"`
|
|
UpdateStateAt int64 `json:"updateStateAt"`
|
|
}
|