mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-20 10:00:58 +00:00
feat(nodes): add distinct purple indicator when panel is online but Xray core failed (#5040)
* feat(nodes): add distinct purple indicator when panel is online but Xray core failed Currently nodes only show binary online/offline based on panel API reachability. This adds a third state: - Green: panel reachable + Xray healthy - Purple pulsing dot + "Online (Xray Error)": panel API works (management actions still available) but the node Xray process is in error or stopped. Tooltip shows the remote xrayError. - Red: unreachable (unchanged) Backend now captures xray.state + xray.errorMsg from /panel/api/server/status heartbeats and probes. New fields on Node + NodeSummary, forwarded for transitive nodes. Frontend Zod + NodeList rendering + dedicated .xray-error-dot CSS (color #722ED1) + i18n key. Color chosen purple per feedback after initial implementation. Refs: worktree xray-failed-in-nodes * fix: remove invalid JSON comment causing CI failures * chore: regenerate OpenAPI schemas and types for xray error indicators * chore: regenerate examples and schemas for xray error indicators * chore: regenerate missing openapi.json examples * fix --------- Co-authored-by: Rqzbeh <rqzbeh@users.noreply.github.com> Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
This commit is contained in:
+18
-1
@@ -35,6 +35,11 @@ type HeartbeatPatch struct {
|
||||
MemPct float64
|
||||
UptimeSecs uint64
|
||||
LastError string
|
||||
// XrayState and XrayError come from the remote /panel/api/server/status when the
|
||||
// panel API is reachable. They allow distinguishing panel connectivity from
|
||||
// Xray core health on the node.
|
||||
XrayState string
|
||||
XrayError string
|
||||
}
|
||||
|
||||
type NodeService struct{}
|
||||
@@ -474,6 +479,8 @@ func (s *NodeService) UpdateHeartbeat(id int, p HeartbeatPatch) error {
|
||||
"mem_pct": p.MemPct,
|
||||
"uptime_secs": p.UptimeSecs,
|
||||
"last_error": p.LastError,
|
||||
"xray_state": p.XrayState,
|
||||
"xray_error": p.XrayError,
|
||||
}
|
||||
// Only learn the GUID; never clear a known one if an old-build node (or a
|
||||
// failed probe) reports none, so the stable identity survives blips.
|
||||
@@ -607,7 +614,9 @@ func (s *NodeService) Probe(ctx context.Context, n *model.Node) (HeartbeatPatch,
|
||||
Total uint64 `json:"total"`
|
||||
} `json:"mem"`
|
||||
Xray struct {
|
||||
Version string `json:"version"`
|
||||
Version string `json:"version"`
|
||||
State string `json:"state"`
|
||||
ErrorMsg string `json:"errorMsg"`
|
||||
} `json:"xray"`
|
||||
PanelVersion string `json:"panelVersion"`
|
||||
PanelGuid string `json:"panelGuid"`
|
||||
@@ -628,6 +637,8 @@ func (s *NodeService) Probe(ctx context.Context, n *model.Node) (HeartbeatPatch,
|
||||
patch.MemPct = float64(o.Mem.Current) * 100.0 / float64(o.Mem.Total)
|
||||
}
|
||||
patch.XrayVersion = o.Xray.Version
|
||||
patch.XrayState = o.Xray.State
|
||||
patch.XrayError = o.Xray.ErrorMsg
|
||||
patch.PanelVersion = o.PanelVersion
|
||||
patch.Guid = o.PanelGuid
|
||||
patch.UptimeSecs = o.Uptime
|
||||
@@ -643,6 +654,10 @@ type ProbeResultUI struct {
|
||||
MemPct float64 `json:"memPct" example:"45.2"`
|
||||
UptimeSecs uint64 `json:"uptimeSecs" example:"86400"`
|
||||
Error string `json:"error"`
|
||||
// XrayState/XrayError are populated on successful probes even when the node's
|
||||
// Xray core is not healthy. The UI uses them for a distinct "panel ok, xray failed" indicator.
|
||||
XrayState string `json:"xrayState"`
|
||||
XrayError string `json:"xrayError"`
|
||||
}
|
||||
|
||||
func (p HeartbeatPatch) ToUI(ok bool) ProbeResultUI {
|
||||
@@ -654,6 +669,8 @@ func (p HeartbeatPatch) ToUI(ok bool) ProbeResultUI {
|
||||
MemPct: p.MemPct,
|
||||
UptimeSecs: p.UptimeSecs,
|
||||
Error: FriendlyProbeError(p.LastError),
|
||||
XrayState: p.XrayState,
|
||||
XrayError: p.XrayError,
|
||||
}
|
||||
if ok {
|
||||
r.Status = "online"
|
||||
|
||||
@@ -40,6 +40,8 @@ func (s *NodeService) LocalDescendants() ([]model.NodeSummary, error) {
|
||||
LatencyMs: n.LatencyMs,
|
||||
PanelVersion: n.PanelVersion,
|
||||
XrayVersion: n.XrayVersion,
|
||||
XrayState: n.XrayState,
|
||||
XrayError: n.XrayError,
|
||||
})
|
||||
}
|
||||
return out, nil
|
||||
@@ -140,6 +142,8 @@ func (s *NodeService) GetNodeTree() ([]*model.Node, error) {
|
||||
LatencyMs: sum.LatencyMs,
|
||||
PanelVersion: sum.PanelVersion,
|
||||
XrayVersion: sum.XrayVersion,
|
||||
XrayState: sum.XrayState,
|
||||
XrayError: sum.XrayError,
|
||||
Transitive: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -896,7 +896,9 @@
|
||||
"statusValues": {
|
||||
"online": "متصل",
|
||||
"offline": "غير متصل",
|
||||
"unknown": "غير معروف"
|
||||
"unknown": "غير معروف",
|
||||
"xrayError": "خطأ Xray",
|
||||
"xrayStopped": "متوقف"
|
||||
},
|
||||
"toasts": {
|
||||
"list": "فشل تحميل النودز",
|
||||
|
||||
@@ -897,7 +897,9 @@
|
||||
"statusValues": {
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"unknown": "Unknown"
|
||||
"unknown": "Unknown",
|
||||
"xrayError": "Xray Error",
|
||||
"xrayStopped": "Stopped"
|
||||
},
|
||||
"toasts": {
|
||||
"list": "Failed to load nodes",
|
||||
|
||||
@@ -896,7 +896,9 @@
|
||||
"statusValues": {
|
||||
"online": "En línea",
|
||||
"offline": "Sin conexión",
|
||||
"unknown": "Desconocido"
|
||||
"unknown": "Desconocido",
|
||||
"xrayError": "Error de Xray",
|
||||
"xrayStopped": "Detenido"
|
||||
},
|
||||
"toasts": {
|
||||
"list": "Error al cargar los nodos",
|
||||
|
||||
@@ -896,7 +896,9 @@
|
||||
"statusValues": {
|
||||
"online": "آنلاین",
|
||||
"offline": "آفلاین",
|
||||
"unknown": "نامشخص"
|
||||
"unknown": "نامشخص",
|
||||
"xrayError": "خطای Xray",
|
||||
"xrayStopped": "متوقف"
|
||||
},
|
||||
"toasts": {
|
||||
"list": "بارگذاری نودها ناموفق",
|
||||
|
||||
@@ -896,7 +896,9 @@
|
||||
"statusValues": {
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"unknown": "Tidak diketahui"
|
||||
"unknown": "Tidak diketahui",
|
||||
"xrayError": "Kesalahan Xray",
|
||||
"xrayStopped": "Berhenti"
|
||||
},
|
||||
"toasts": {
|
||||
"list": "Gagal memuat node",
|
||||
|
||||
@@ -896,7 +896,9 @@
|
||||
"statusValues": {
|
||||
"online": "オンライン",
|
||||
"offline": "オフライン",
|
||||
"unknown": "不明"
|
||||
"unknown": "不明",
|
||||
"xrayError": "Xray エラー",
|
||||
"xrayStopped": "停止"
|
||||
},
|
||||
"toasts": {
|
||||
"list": "ノードの読み込みに失敗しました",
|
||||
|
||||
@@ -896,7 +896,9 @@
|
||||
"statusValues": {
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"unknown": "Desconhecido"
|
||||
"unknown": "Desconhecido",
|
||||
"xrayError": "Erro do Xray",
|
||||
"xrayStopped": "Parado"
|
||||
},
|
||||
"toasts": {
|
||||
"list": "Falha ao carregar os nós",
|
||||
|
||||
@@ -896,7 +896,9 @@
|
||||
"statusValues": {
|
||||
"online": "В сети",
|
||||
"offline": "Не в сети",
|
||||
"unknown": "Неизвестно"
|
||||
"unknown": "Неизвестно",
|
||||
"xrayError": "Ошибка Xray",
|
||||
"xrayStopped": "Остановлен"
|
||||
},
|
||||
"toasts": {
|
||||
"list": "Не удалось загрузить узлы",
|
||||
|
||||
@@ -896,7 +896,9 @@
|
||||
"statusValues": {
|
||||
"online": "Çevrimiçi",
|
||||
"offline": "Çevrimdışı",
|
||||
"unknown": "Bilinmiyor"
|
||||
"unknown": "Bilinmiyor",
|
||||
"xrayError": "Xray Hatası",
|
||||
"xrayStopped": "Durduruldu"
|
||||
},
|
||||
"toasts": {
|
||||
"list": "Düğümler yüklenemedi",
|
||||
|
||||
@@ -896,7 +896,9 @@
|
||||
"statusValues": {
|
||||
"online": "У мережі",
|
||||
"offline": "Не в мережі",
|
||||
"unknown": "Невідомо"
|
||||
"unknown": "Невідомо",
|
||||
"xrayError": "Помилка Xray",
|
||||
"xrayStopped": "Зупинено"
|
||||
},
|
||||
"toasts": {
|
||||
"list": "Не вдалося завантажити вузли",
|
||||
|
||||
@@ -896,7 +896,9 @@
|
||||
"statusValues": {
|
||||
"online": "Trực tuyến",
|
||||
"offline": "Ngoại tuyến",
|
||||
"unknown": "Không xác định"
|
||||
"unknown": "Không xác định",
|
||||
"xrayError": "Lỗi Xray",
|
||||
"xrayStopped": "Đã dừng"
|
||||
},
|
||||
"toasts": {
|
||||
"list": "Không tải được danh sách nút",
|
||||
|
||||
@@ -896,7 +896,9 @@
|
||||
"statusValues": {
|
||||
"online": "在线",
|
||||
"offline": "离线",
|
||||
"unknown": "未知"
|
||||
"unknown": "未知",
|
||||
"xrayError": "Xray 错误",
|
||||
"xrayStopped": "已停止"
|
||||
},
|
||||
"toasts": {
|
||||
"list": "加载节点失败",
|
||||
|
||||
@@ -896,7 +896,9 @@
|
||||
"statusValues": {
|
||||
"online": "上線",
|
||||
"offline": "離線",
|
||||
"unknown": "未知"
|
||||
"unknown": "未知",
|
||||
"xrayError": "Xray 錯誤",
|
||||
"xrayStopped": "已停止"
|
||||
},
|
||||
"toasts": {
|
||||
"list": "載入節點失敗",
|
||||
|
||||
Reference in New Issue
Block a user