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:
Rouzbeh†
2026-06-08 20:24:00 +02:00
committed by GitHub
parent 0daedd3db9
commit 1c74b995c3
25 changed files with 239 additions and 49 deletions
+4
View File
@@ -364,6 +364,8 @@ export const EXAMPLES: Record<string, unknown> = {
"transitive": false,
"updatedAt": 1700000000,
"uptimeSecs": 86400,
"xrayError": "",
"xrayState": "",
"xrayVersion": "25.10.31"
},
"OutboundTraffics": {
@@ -381,6 +383,8 @@ export const EXAMPLES: Record<string, unknown> = {
"panelVersion": "v3.x.x",
"status": "online",
"uptimeSecs": 86400,
"xrayError": "",
"xrayState": "",
"xrayVersion": "25.10.31"
},
"Setting": {
+18
View File
@@ -1630,6 +1630,13 @@ export const SCHEMAS: Record<string, unknown> = {
"example": 86400,
"type": "integer"
},
"xrayError": {
"type": "string"
},
"xrayState": {
"description": "XrayState and XrayError are captured from the remote node's /panel/api/server/status\nduring heartbeats. They let the central panel distinguish \"panel API reachable\"\n(status=online) from \"Xray core itself has failed on the node\" for monitoring.",
"type": "string"
},
"xrayVersion": {
"example": "25.10.31",
"type": "string"
@@ -1665,6 +1672,8 @@ export const SCHEMAS: Record<string, unknown> = {
"tlsVerifyMode",
"updatedAt",
"uptimeSecs",
"xrayError",
"xrayState",
"xrayVersion"
],
"type": "object"
@@ -1726,6 +1735,13 @@ export const SCHEMAS: Record<string, unknown> = {
"example": 86400,
"type": "integer"
},
"xrayError": {
"type": "string"
},
"xrayState": {
"description": "XrayState/XrayError are populated on successful probes even when the node's\nXray core is not healthy. The UI uses them for a distinct \"panel ok, xray failed\" indicator.",
"type": "string"
},
"xrayVersion": {
"example": "25.10.31",
"type": "string"
@@ -1739,6 +1755,8 @@ export const SCHEMAS: Record<string, unknown> = {
"panelVersion",
"status",
"uptimeSecs",
"xrayError",
"xrayState",
"xrayVersion"
],
"type": "object"
+4
View File
@@ -371,6 +371,8 @@ export interface Node {
transitive?: boolean;
updatedAt: number;
uptimeSecs: number;
xrayError: string;
xrayState: string;
xrayVersion: string;
}
@@ -390,6 +392,8 @@ export interface ProbeResultUI {
panelVersion: string;
status: string;
uptimeSecs: number;
xrayError: string;
xrayState: string;
xrayVersion: string;
}
+4
View File
@@ -398,6 +398,8 @@ export const NodeSchema = z.object({
transitive: z.boolean().optional(),
updatedAt: z.number().int(),
uptimeSecs: z.number().int(),
xrayError: z.string(),
xrayState: z.string(),
xrayVersion: z.string(),
});
export type Node = z.infer<typeof NodeSchema>;
@@ -419,6 +421,8 @@ export const ProbeResultUISchema = z.object({
panelVersion: z.string(),
status: z.string(),
uptimeSecs: z.number().int(),
xrayError: z.string(),
xrayState: z.string(),
xrayVersion: z.string(),
});
export type ProbeResultUI = z.infer<typeof ProbeResultUISchema>;