Files
3x-ui/frontend/src/lib/panel-version.ts
MHSanaei 971843f669 feat(nodes): bulk panel self-update with live online indicator
Adds the ability to update node panels to the latest release from the Nodes
page: select online, enabled nodes (checkboxes) and trigger their official
self-updater, or use the per-row Update action. A node whose reported panel
version trails the latest GitHub release is flagged with an 'update available'
tag (compared via lib/panel-version, mirroring the Go isNewerVersion).

Backend: Remote.UpdatePanel calls the node's existing
POST /panel/api/server/updatePanel; NodeService.UpdatePanels fans out over the
selected ids, skipping disabled/offline nodes with a per-node reason; exposed
as POST /panel/api/nodes/updatePanel (documented in endpoints.ts + openapi.json).

The bulk request sends a JSON body, so it sets Content-Type: application/json
explicitly — axios defaults POST to form-urlencoded, which made ShouldBindJSON
fail with 'invalid character i'.

Also reuses the clients-page online cue on the Nodes page: a pulsing green dot
plus green label for an online node. The .online-dot style moved to the shared
styles/utils.css so both pages load it.

Translations for all new node keys added across every language file.
2026-06-01 07:03:06 +02:00

30 lines
1.1 KiB
TypeScript

// Mirror of web/service/panel.go isNewerVersion: parse a vMAJOR.MINOR.PATCH tag
// and report whether `latest` is ahead of `current`. When either side isn't a
// clean three-part numeric tag, fall back to a normalized string inequality —
// the same heuristic the Go side uses so the node "update available" badge
// agrees with what the server would decide.
function parseVersionParts(version: string): [number, number, number] | null {
const parts = version.trim().replace(/^v/, '').split('.');
if (parts.length !== 3) return null;
const out: number[] = [];
for (const part of parts) {
if (!/^\d+$/.test(part)) return null;
out.push(Number(part));
}
return [out[0], out[1], out[2]];
}
export function isPanelUpdateAvailable(latest: string, current: string): boolean {
if (!latest || !current) return false;
const a = parseVersionParts(latest);
const b = parseVersionParts(current);
if (!a || !b) {
return latest.trim().replace(/^v/, '') !== current.trim().replace(/^v/, '');
}
for (let i = 0; i < 3; i++) {
if (a[i] > b[i]) return true;
if (a[i] < b[i]) return false;
}
return false;
}