3x-ui/docs/superpowers/specs/2026-04-24-node-management-sidebar-design.md

158 lines
4.8 KiB
Markdown
Raw Normal View History

2026-04-24 09:25:02 +00:00
# Node Management Sidebar — Design Spec
**Date:** 2026-04-24
**Status:** Approved
## Overview
Add a "Node Management" page accessible from the sidebar, visible only to admin users. The page displays connected node status and allows modifying node configuration.
## Behavior by Role
- **Master node:** Shows a table of all connected worker nodes with detailed status
- **Worker node:** Shows a card with the master node's info
## Backend
### New Controller: `NodeController`
File: `web/controller/node.go`
API endpoints (all admin-only via `checkAdmin` middleware):
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/panel/api/nodes/list` | GET | Node list (master: all workers; worker: master) |
| `/panel/api/nodes/config` | GET | Current node config |
| `/panel/api/nodes/config` | POST | Update current node config |
### New Page Route
In `xui.go`, add:
```go
func (x *XUIController) Nodes(c *gin.Context) {
// render nodes.html
}
```
Route: `GET /panel/nodes``XUIController.Nodes` (admin only)
### Data Sources
- **Node list:** Query `node_states` table via `database.GetNodeStates()` (new function in `database/shared_state.go`)
- **Node config:** Read from `x-ui.json` via existing `config.GetNodeConfigFromJSON()`
- **DB config:** Read from `AllSetting` entity (dbType, dbHost, dbPort, dbUser, dbPass, dbName)
- **Online status:** `LastHeartbeatAt` > 2 × `syncInterval` ago → offline
### Config Update Logic
POST `/panel/api/nodes/config` accepts JSON body with:
- `syncInterval` (int, seconds)
- `trafficFlushInterval` (int, seconds)
- `dbType`, `dbHost`, `dbPort`, `dbUser`, `dbPass`, `dbName`
Writes to `x-ui.json` under `"other"` group. Does NOT allow changing `nodeRole` or `nodeId` at runtime (displayed as read-only).
## Frontend
### New Page: `web/html/xui/nodes.html`
Structure (mirrors settings.html pattern):
- Head section: imports, template includes
- Vue app with two sections:
1. **Node list**`<a-table>` (master) or `<a-card>` (worker)
2. **Node config form**`<a-form>` with save button
### Node List Columns (master view)
| Column | Source |
|--------|--------|
| Node ID | `NodeState.NodeID` |
| Status | Online/Offline (heartbeat check) |
| Last Heartbeat | `NodeState.LastHeartbeatAt` (formatted) |
| Last Sync | `NodeState.LastSyncAt` (formatted) |
| Sync Version | `NodeState.LastSeenVersion` |
| Error | `NodeState.LastError` |
Worker view: same fields in a card layout.
### Config Form Fields
| Field | Type | Editable |
|-------|------|----------|
| Node Role | Text | No (read-only) |
| Node ID | Text | No (read-only) |
| Sync Interval | Number (seconds) | Yes |
| Traffic Flush Interval | Number (seconds) | Yes |
| DB Type | Select (sqlite/mysql) | Yes |
| DB Host | Text | Yes |
| DB Port | Number | Yes |
| DB User | Text | Yes |
| DB Password | Password | Yes |
| DB Name | Text | Yes |
### Auto-Refresh
Node list polls `/panel/api/nodes/list` every 10 seconds via `setInterval`.
### Sidebar Change
In `web/html/component/aSidebar.html`, add between `settings` and `xray`:
```javascript
{{if .is_admin}}
{ key: '{{ .base_path }}panel/nodes', icon: 'cluster', title: '{{ i18n "menu.nodes"}}' },
{{end}}
```
## i18n
Add to `translate.en_US.toml` and `translate.zh_CN.toml`:
```toml
[menu]
"nodes" = "Nodes" # en
"nodes" = "节点管理" # zh
[nodes]
"title" = "Node Management"
"nodeId" = "Node ID"
"role" = "Role"
"status" = "Status"
"online" = "Online"
"offline" = "Offline"
"lastHeartbeat" = "Last Heartbeat"
"lastSync" = "Last Sync"
"syncVersion" = "Sync Version"
"error" = "Error"
"syncInterval" = "Sync Interval"
"trafficFlushInterval" = "Traffic Flush Interval"
"dbType" = "Database Type"
"dbHost" = "Database Host"
"dbPort" = "Database Port"
"dbUser" = "Database User"
"dbPass" = "Database Password"
"dbName" = "Database Name"
"save" = "Save"
"saveSuccess" = "Saved successfully"
"noWorkerNodes" = "No worker nodes connected"
"masterNode" = "Master Node"
"workerNodes" = "Worker Nodes"
```
## Files to Create/Modify
| File | Action |
|------|--------|
| `web/controller/node.go` | **Create** — NodeController with list/config APIs |
| `web/html/xui/nodes.html` | **Create** — Node management page |
| `web/html/component/aSidebar.html` | **Modify** — Add nodes menu item |
| `web/web.go` | **Modify** — Register routes and controller |
| `web/controller/xui.go` | **Modify** — Add Nodes() page method |
| `web/translation/translate.en_US.toml` | **Modify** — Add i18n keys |
| `web/translation/translate.zh_CN.toml` | **Modify** — Add i18n keys |
| `database/shared_state.go` | **Modify** — Add GetNodeStates() query function |
## Scope Boundaries
- **In scope:** View node status, modify node config, sidebar entry
- **Out of scope:** Node registration/removal, restart, adding new nodes, real-time WebSocket updates (uses polling instead)