P7: restore agent node type, agent heartbeat expiry, build-version injection, remove log/history/xray-metrics modules, optional sub port
- nodes: port node-type (traditional/agent) picker back from web-src into the embedded frontend; backend annotates NodeView.nodeType from agent_nodes bindings; fix agent-node update path (skip reachability probe and apiToken requirement for agent-managed nodes); restore i18n keys in all 13 locales; install command now uses the Gitea mirror - jobs: expire agent-bound nodes whose agent_nodes.last_seen is older than max(3x heartbeat interval, 120s) = 180s; last_seen=0 (never registered) is left untouched; transitions reuse UpdateHeartbeat + node event broadcast - config: GetBaseVersion honors ldflags-injected buildVersion; release builds inject 3.7.0-agent.1 so the panel no longer reports a phantom update against the Gitea release tag - logger/server: remove the three log viewers (panel/xray/amneziawg), their API endpoints and the 10240-entry in-memory log buffer; file logging via lumberjack unchanged; tests assert via a new optional logger hook instead of the removed buffer - server: remove system history and xray metrics modules end to end (endpoints, sampling appends in RefreshStatus, per-minute persist cron, gob snapshot/restore, XrayMetricsService); realtime status and per-node history are untouched - settings: subPort may be 0/unset - subscription links are generated without a port while the subscription service keeps binding the default 2096; the settings form declares the cleared state via onNumber's new onClear option instead of snapping back to 1
This commit is contained in:
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/util/common"
|
||||
)
|
||||
@@ -25,32 +26,38 @@ type NodeView struct {
|
||||
InboundSyncMode string `json:"inboundSyncMode" example:"all"`
|
||||
InboundTags []string `json:"inboundTags" example:"[\"in-443-tcp\"]"`
|
||||
OutboundTag string `json:"outboundTag" example:"direct"`
|
||||
Guid string `json:"guid" example:"node-guid"`
|
||||
Status string `json:"status" example:"online"`
|
||||
LastHeartbeat int64 `json:"lastHeartbeat" example:"1700000000"`
|
||||
LatencyMs int `json:"latencyMs" example:"42"`
|
||||
XrayVersion string `json:"xrayVersion" example:"25.10.31"`
|
||||
PanelVersion string `json:"panelVersion" example:"v3.x.x"`
|
||||
CpuPct float64 `json:"cpuPct" example:"12.5"`
|
||||
MemPct float64 `json:"memPct" example:"45.2"`
|
||||
UptimeSecs uint64 `json:"uptimeSecs" example:"86400"`
|
||||
NetUp uint64 `json:"netUp" example:"2097152"`
|
||||
NetDown uint64 `json:"netDown" example:"1048576"`
|
||||
LastError string `json:"lastError" example:""`
|
||||
XrayState string `json:"xrayState" example:"running"`
|
||||
XrayError string `json:"xrayError" example:""`
|
||||
ConfigDirty bool `json:"configDirty" example:"false"`
|
||||
ConfigDirtyAt int64 `json:"configDirtyAt" example:"0"`
|
||||
InboundCount int `json:"inboundCount" example:"3"`
|
||||
ClientCount int `json:"clientCount" example:"25"`
|
||||
OnlineCount int `json:"onlineCount" example:"5"`
|
||||
ActiveCount int `json:"activeCount" example:"20"`
|
||||
DisabledCount int `json:"disabledCount" example:"2"`
|
||||
DepletedCount int `json:"depletedCount" example:"1"`
|
||||
ParentGuid string `json:"parentGuid,omitempty" example:""`
|
||||
Transitive bool `json:"transitive,omitempty" example:"false"`
|
||||
CreatedAt int64 `json:"createdAt" example:"1700000000"`
|
||||
UpdatedAt int64 `json:"updatedAt" example:"1700003600"`
|
||||
// NodeType is "agent" for lightweight agent-managed nodes (an agent is
|
||||
// bound to the node) and "traditional" otherwise. It is derived from the
|
||||
// agent binding, not stored on the node row; read paths that serve the
|
||||
// panel UI annotate it via annotateNodeTypes. Empty on views built
|
||||
// outside those paths.
|
||||
NodeType string `json:"nodeType,omitempty" example:"traditional"`
|
||||
Guid string `json:"guid" example:"node-guid"`
|
||||
Status string `json:"status" example:"online"`
|
||||
LastHeartbeat int64 `json:"lastHeartbeat" example:"1700000000"`
|
||||
LatencyMs int `json:"latencyMs" example:"42"`
|
||||
XrayVersion string `json:"xrayVersion" example:"25.10.31"`
|
||||
PanelVersion string `json:"panelVersion" example:"v3.x.x"`
|
||||
CpuPct float64 `json:"cpuPct" example:"12.5"`
|
||||
MemPct float64 `json:"memPct" example:"45.2"`
|
||||
UptimeSecs uint64 `json:"uptimeSecs" example:"86400"`
|
||||
NetUp uint64 `json:"netUp" example:"2097152"`
|
||||
NetDown uint64 `json:"netDown" example:"1048576"`
|
||||
LastError string `json:"lastError" example:""`
|
||||
XrayState string `json:"xrayState" example:"running"`
|
||||
XrayError string `json:"xrayError" example:""`
|
||||
ConfigDirty bool `json:"configDirty" example:"false"`
|
||||
ConfigDirtyAt int64 `json:"configDirtyAt" example:"0"`
|
||||
InboundCount int `json:"inboundCount" example:"3"`
|
||||
ClientCount int `json:"clientCount" example:"25"`
|
||||
OnlineCount int `json:"onlineCount" example:"5"`
|
||||
ActiveCount int `json:"activeCount" example:"20"`
|
||||
DisabledCount int `json:"disabledCount" example:"2"`
|
||||
DepletedCount int `json:"depletedCount" example:"1"`
|
||||
ParentGuid string `json:"parentGuid,omitempty" example:""`
|
||||
Transitive bool `json:"transitive,omitempty" example:"false"`
|
||||
CreatedAt int64 `json:"createdAt" example:"1700000000"`
|
||||
UpdatedAt int64 `json:"updatedAt" example:"1700003600"`
|
||||
}
|
||||
|
||||
func toNodeView(n *model.Node) *NodeView {
|
||||
@@ -110,6 +117,45 @@ func toNodeViews(nodes []*model.Node) []*NodeView {
|
||||
return views
|
||||
}
|
||||
|
||||
// agentManagedNodeIDs returns the set of node ids with at least one agent
|
||||
// bound to them — the definition of an "agent node" in this codebase (the
|
||||
// heartbeat job's probe skip uses the same test).
|
||||
func agentManagedNodeIDs() map[int]bool {
|
||||
ids := map[int]bool{}
|
||||
var rows []int
|
||||
_ = database.GetDB().Model(&model.AgentNode{}).
|
||||
Where("node_id IS NOT NULL").
|
||||
Distinct().
|
||||
Pluck("node_id", &rows).Error
|
||||
for _, id := range rows {
|
||||
ids[id] = true
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// IsAgentManaged reports whether the node has an agent bound to it.
|
||||
func (s *NodeService) IsAgentManaged(id int) bool {
|
||||
var count int64
|
||||
_ = database.GetDB().Model(&model.AgentNode{}).Where("node_id = ?", id).Count(&count).Error
|
||||
return count > 0
|
||||
}
|
||||
|
||||
// annotateNodeTypes fills NodeType on views from the agent bindings so the
|
||||
// UI can tell agent nodes apart from traditional panel nodes when editing.
|
||||
func annotateNodeTypes(views []*NodeView) {
|
||||
if len(views) == 0 {
|
||||
return
|
||||
}
|
||||
agents := agentManagedNodeIDs()
|
||||
for _, v := range views {
|
||||
if agents[v.Id] {
|
||||
v.NodeType = "agent"
|
||||
} else {
|
||||
v.NodeType = "traditional"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NodeMutationRequest is the node write/probe contract. ApiToken is accepted
|
||||
// only as input. On update, nil means keep the stored token; replacement and
|
||||
// clearing are explicit and mutually exclusive.
|
||||
|
||||
Reference in New Issue
Block a user