mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-07 21:54:10 +00:00
157 lines
4.8 KiB
Markdown
157 lines
4.8 KiB
Markdown
# 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)
|