feat: initialisation complète du CORE ProxmoxPanel
Backend Go 1.23+ : - API REST + WebSocket (chi, gorilla/websocket) - Authentification PAM via SSH + JWT RS256 - Chiffrement AES-256-GCM pour secrets SQLite - Pool SSH, client Proxmox REST, hub WebSocket pub/sub - Système de modules compilés à initialisation conditionnelle - Audit log, migrations SQLite versionnées Frontend Vue 3 + Vite + TypeScript : - Thème Neumorphism sombre/clair (CSS custom properties) - Wizard d'installation, Dashboard drag-drop, Terminal xterm.js - Toutes les vues CORE + stubs modules optionnels - i18n EN/FR (vue-i18n v11) Infrastructure : - Docker multi-stage (Go → alpine, Node → nginx) - docker-compose.yml, .gitattributes, LICENSE MIT, README Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
5dbcb1df07
66 changed files with 10370 additions and 0 deletions
44
backend/modules/dashboard/README.md
Normal file
44
backend/modules/dashboard/README.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# Module — Dashboard
|
||||
|
||||
**Type**: Core (always enabled)
|
||||
|
||||
Provides the main dashboard with a configurable, per-user widget grid.
|
||||
|
||||
## Features
|
||||
|
||||
- Drag-and-drop widget reordering (saved per user in SQLite)
|
||||
- Add and remove widgets via modal
|
||||
- Widget layout persisted across sessions
|
||||
|
||||
## Widget Types
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `shortcut` | Clickable link card (icon + label + URL) |
|
||||
| `lxc_status` | Live status of a specific LXC container |
|
||||
| `metrics` | Host CPU/RAM/disk summary |
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Method | Path | Auth | Description |
|
||||
|--------|------|------|-------------|
|
||||
| GET | `/api/dashboard/widgets` | JWT | Get current user's widget layout |
|
||||
| PUT | `/api/dashboard/widgets` | JWT | Save widget layout |
|
||||
|
||||
## Widget Layout Format
|
||||
|
||||
```json
|
||||
[
|
||||
{ "id": "w1", "type": "shortcut", "config": { "label": "Proxmox", "url": "https://proxmox.example.com", "icon": "server" } },
|
||||
{ "id": "w2", "type": "lxc_status", "config": { "vmid": 100 } },
|
||||
{ "id": "w3", "type": "metrics", "config": {} }
|
||||
]
|
||||
```
|
||||
|
||||
## Database
|
||||
|
||||
Layouts are stored in the `user_widgets` table, keyed by user ID.
|
||||
|
||||
## License
|
||||
|
||||
MIT — see [LICENSE](../../LICENSE)
|
||||
41
backend/modules/files/README.md
Normal file
41
backend/modules/files/README.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# Module — Files
|
||||
|
||||
**Type**: Optional (disabled by default)
|
||||
|
||||
SFTP-based file browser for the Proxmox host and LXC containers. Navigate, view, edit, upload, and download files directly from the browser.
|
||||
|
||||
## Planned Features
|
||||
|
||||
- Directory listing with permissions, size, modification date
|
||||
- File preview (text, JSON, YAML, shell scripts, logs)
|
||||
- File editing via CodeMirror 6 (syntax highlighting for common formats)
|
||||
- Upload and download
|
||||
- Create/delete files and directories
|
||||
- Navigate into LXC containers via `pct exec` or direct SFTP
|
||||
|
||||
## Planned API Endpoints
|
||||
|
||||
| Method | Path | Auth | Description |
|
||||
|--------|------|------|-------------|
|
||||
| GET | `/api/files/list` | JWT | List directory contents |
|
||||
| GET | `/api/files/read` | JWT | Read file content |
|
||||
| PUT | `/api/files/write` | JWT+Admin | Write file content |
|
||||
| POST | `/api/files/mkdir` | JWT+Admin | Create directory |
|
||||
| DELETE | `/api/files/delete` | JWT+Admin | Delete file or directory |
|
||||
| GET | `/api/files/download` | JWT | Download file |
|
||||
| POST | `/api/files/upload` | JWT+Admin | Upload file |
|
||||
|
||||
Query parameters: `path=<absolute-path>`, `host=<optional-ssh-override>`
|
||||
|
||||
## Status
|
||||
|
||||
> This module is currently a stub. The UI view is implemented (shows a "module not enabled" placeholder). Full SFTP implementation is planned for a future release.
|
||||
|
||||
## Requirements
|
||||
|
||||
- SSH/SFTP access to the target host
|
||||
- The `ssh_host`, `ssh_username`, `ssh_password` settings must be configured
|
||||
|
||||
## License
|
||||
|
||||
MIT — see [LICENSE](../../LICENSE)
|
||||
150
backend/modules/loader.go
Normal file
150
backend/modules/loader.go
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
// Package modules — Loader de modules.
|
||||
// Découvre les modules disponibles, vérifie leur état en DB, et les initialise si activés.
|
||||
// Un module désactivé ne fait appel à aucune de ses méthodes Register().
|
||||
package modules
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Loader charge et gère les modules actifs.
|
||||
type Loader struct {
|
||||
db *sql.DB
|
||||
registry *coreRegistry
|
||||
modules []Module
|
||||
}
|
||||
|
||||
// NewLoader crée un Loader avec le router et la DB fournis.
|
||||
func NewLoader(db *sql.DB) *Loader {
|
||||
return &Loader{
|
||||
db: db,
|
||||
registry: newCoreRegistry(db),
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterModule enregistre un module disponible (appelé à l'init, depuis main.go).
|
||||
// Le module sera initialisé seulement s'il est activé en base.
|
||||
func (l *Loader) RegisterModule(m Module) {
|
||||
l.modules = append(l.modules, m)
|
||||
}
|
||||
|
||||
// LoadActive charge et initialise tous les modules activés en base de données.
|
||||
func (l *Loader) LoadActive() error {
|
||||
for _, m := range l.modules {
|
||||
enabled, err := l.isEnabled(m.ID())
|
||||
if err != nil {
|
||||
return fmt.Errorf("vérification module %s : %w", m.ID(), err)
|
||||
}
|
||||
if !enabled {
|
||||
log.Printf("Module %s : désactivé, ignoré", m.ID())
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("Module %s : chargement...", m.ID())
|
||||
if err := m.Register(l.registry); err != nil {
|
||||
return fmt.Errorf("initialisation module %s : %w", m.ID(), err)
|
||||
}
|
||||
log.Printf("Module %s : chargé avec succès", m.ID())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isEnabled vérifie en base de données si un module est activé.
|
||||
func (l *Loader) isEnabled(id string) (bool, error) {
|
||||
var enabled int
|
||||
err := l.db.QueryRow(`SELECT is_enabled FROM modules WHERE id = ?`, id).Scan(&enabled)
|
||||
if err == sql.ErrNoRows {
|
||||
return false, nil // Module inconnu = désactivé
|
||||
}
|
||||
return enabled == 1, err
|
||||
}
|
||||
|
||||
// Registry retourne le registry partagé (pour accès par le serveur HTTP).
|
||||
func (l *Loader) Registry() *coreRegistry {
|
||||
return l.registry
|
||||
}
|
||||
|
||||
// ---- Implémentation interne du Registry ----
|
||||
|
||||
// RouteEntry décrit une route HTTP enregistrée par un module.
|
||||
type RouteEntry struct {
|
||||
Method string
|
||||
Path string
|
||||
Handler http.HandlerFunc
|
||||
RequireAdmin bool
|
||||
}
|
||||
|
||||
type migrationEntry struct {
|
||||
version int
|
||||
sql string
|
||||
fn MigrationFn
|
||||
}
|
||||
|
||||
type translationEntry struct {
|
||||
lang string
|
||||
keys map[string]string
|
||||
}
|
||||
|
||||
// coreRegistry implémente l'interface Registry.
|
||||
type coreRegistry struct {
|
||||
db *sql.DB
|
||||
routes []RouteEntry
|
||||
wsChannels map[string]WSHandler
|
||||
widgets []WidgetDef
|
||||
settingsTabs []SettingsTabDef
|
||||
migrations []migrationEntry
|
||||
translations []translationEntry
|
||||
}
|
||||
|
||||
func newCoreRegistry(db *sql.DB) *coreRegistry {
|
||||
return &coreRegistry{
|
||||
db: db,
|
||||
wsChannels: make(map[string]WSHandler),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *coreRegistry) RegisterRoute(method, path string, handler http.HandlerFunc, requireAdmin bool) {
|
||||
r.routes = append(r.routes, RouteEntry{method, path, handler, requireAdmin})
|
||||
}
|
||||
|
||||
func (r *coreRegistry) RegisterWSChannel(channel string, handler WSHandler) {
|
||||
r.wsChannels[channel] = handler
|
||||
}
|
||||
|
||||
func (r *coreRegistry) RegisterWidget(widget WidgetDef) {
|
||||
r.widgets = append(r.widgets, widget)
|
||||
}
|
||||
|
||||
func (r *coreRegistry) RegisterSettingsTab(tab SettingsTabDef) {
|
||||
r.settingsTabs = append(r.settingsTabs, tab)
|
||||
}
|
||||
|
||||
func (r *coreRegistry) RegisterTranslations(lang string, keys map[string]string) {
|
||||
r.translations = append(r.translations, translationEntry{lang, keys})
|
||||
}
|
||||
|
||||
func (r *coreRegistry) RegisterMigration(version int, sqlStr string, fn MigrationFn) {
|
||||
r.migrations = append(r.migrations, migrationEntry{version, sqlStr, fn})
|
||||
}
|
||||
|
||||
func (r *coreRegistry) DB() *sql.DB {
|
||||
return r.db
|
||||
}
|
||||
|
||||
// GetRoutes retourne les routes enregistrées par les modules.
|
||||
func (r *coreRegistry) GetRoutes() []RouteEntry {
|
||||
return r.routes
|
||||
}
|
||||
|
||||
// GetWidgets retourne les types de widgets disponibles.
|
||||
func (r *coreRegistry) GetWidgets() []WidgetDef {
|
||||
return r.widgets
|
||||
}
|
||||
|
||||
// GetSettingsTabs retourne les onglets de paramètres des modules.
|
||||
func (r *coreRegistry) GetSettingsTabs() []SettingsTabDef {
|
||||
return r.settingsTabs
|
||||
}
|
||||
45
backend/modules/logs/README.md
Normal file
45
backend/modules/logs/README.md
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# Module — Logs
|
||||
|
||||
**Type**: Optional (disabled by default)
|
||||
|
||||
Stream and browse system logs from the Proxmox host or LXC containers in real time via WebSocket (`tail -f` equivalent).
|
||||
|
||||
## Planned Features
|
||||
|
||||
- Real-time log streaming via WebSocket
|
||||
- Common log sources: `syslog`, `auth.log`, `kern.log`, journald
|
||||
- Filter by log level (error, warning, info)
|
||||
- Stop/start streaming on demand
|
||||
- LXC log access via `pct exec`
|
||||
|
||||
## Planned API Endpoints
|
||||
|
||||
| Method | Path | Auth | Description |
|
||||
|--------|------|------|-------------|
|
||||
| GET | `/api/logs/sources` | JWT | List available log sources |
|
||||
|
||||
## Planned WebSocket Endpoint
|
||||
|
||||
`GET /ws/logs/{source}?token=<jwt>&host=<optional>`
|
||||
|
||||
Where `source` is a log name such as `syslog`, `auth`, or `journal`.
|
||||
|
||||
Message types:
|
||||
|
||||
| Type | Payload | Description |
|
||||
|------|---------|-------------|
|
||||
| `log_line` | `{ "line": "...", "level": "info" }` | New log line |
|
||||
| `log_end` | — | Stream closed (e.g. SSH disconnected) |
|
||||
|
||||
## Status
|
||||
|
||||
> This module is currently a stub. The UI view is implemented (shows a "module not enabled" placeholder). Full implementation is planned for a future release.
|
||||
|
||||
## Requirements
|
||||
|
||||
- SSH access to the target host
|
||||
- Read permissions on the log files (root or appropriate group)
|
||||
|
||||
## License
|
||||
|
||||
MIT — see [LICENSE](../../LICENSE)
|
||||
66
backend/modules/module.go
Normal file
66
backend/modules/module.go
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
// Package modules définit le contrat d'interface pour les modules ProxmoxPanel.
|
||||
// Chaque module implémente l'interface Module et s'enregistre auprès du ModuleRegistry.
|
||||
package modules
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Module est l'interface que chaque module doit implémenter.
|
||||
type Module interface {
|
||||
// ID retourne l'identifiant unique du module (doit correspondre à la table modules en DB).
|
||||
ID() string
|
||||
|
||||
// Register est appelé au chargement du module actif.
|
||||
// Il reçoit le registry pour enregistrer ses routes, widgets, etc.
|
||||
Register(registry Registry) error
|
||||
}
|
||||
|
||||
// Registry est l'interface exposée aux modules pour s'enregistrer dans le CORE.
|
||||
type Registry interface {
|
||||
// RegisterRoute enregistre une route HTTP dans le router principal.
|
||||
RegisterRoute(method, path string, handler http.HandlerFunc, requireAdmin bool)
|
||||
|
||||
// RegisterWSChannel enregistre un handler WebSocket pour un channel nommé.
|
||||
RegisterWSChannel(channel string, handler WSHandler)
|
||||
|
||||
// RegisterWidget déclare un type de widget disponible pour le dashboard.
|
||||
RegisterWidget(widget WidgetDef)
|
||||
|
||||
// RegisterSettingsTab ajoute un onglet dans la page paramètres.
|
||||
RegisterSettingsTab(tab SettingsTabDef)
|
||||
|
||||
// RegisterTranslations fusionne des clés de traduction pour une langue donnée.
|
||||
RegisterTranslations(lang string, keys map[string]string)
|
||||
|
||||
// RegisterMigration déclare une migration de base de données propre au module.
|
||||
RegisterMigration(version int, sql string, fn MigrationFn)
|
||||
|
||||
// DB retourne un accès à SQLite avec isolation par module (préfixe de tables).
|
||||
DB() *sql.DB
|
||||
}
|
||||
|
||||
// WSHandler est un handler WebSocket pour un channel nommé.
|
||||
type WSHandler func(userID int64, send chan<- []byte, recv <-chan []byte)
|
||||
|
||||
// WidgetDef décrit un type de widget disponible pour le dashboard.
|
||||
type WidgetDef struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
DefaultW int `json:"default_width"`
|
||||
DefaultH int `json:"default_height"`
|
||||
}
|
||||
|
||||
// SettingsTabDef décrit un onglet de paramètres fourni par un module.
|
||||
type SettingsTabDef struct {
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label"`
|
||||
Icon string `json:"icon"`
|
||||
// Path est le chemin frontend du composant Vue à charger (lazy import).
|
||||
ComponentPath string `json:"component_path"`
|
||||
}
|
||||
|
||||
// MigrationFn est une fonction de migration optionnelle (pour les migrations non-SQL).
|
||||
type MigrationFn func(db *sql.DB) error
|
||||
55
backend/modules/services/README.md
Normal file
55
backend/modules/services/README.md
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# Module — Services
|
||||
|
||||
**Type**: Optional (disabled by default)
|
||||
|
||||
Manage systemd services on the Proxmox host and LXC containers. Check status, start, stop, and restart services directly from the web interface.
|
||||
|
||||
## Planned Features
|
||||
|
||||
- List systemd services with current status (active/inactive/failed)
|
||||
- Start, stop, restart, reload actions
|
||||
- View service logs (last N lines via `journalctl -u <service>`)
|
||||
- Filter by status or name
|
||||
- LXC service management via `pct exec`
|
||||
|
||||
## Planned API Endpoints
|
||||
|
||||
| Method | Path | Auth | Description |
|
||||
|--------|------|------|-------------|
|
||||
| GET | `/api/services` | JWT | List services and their status |
|
||||
| POST | `/api/services/{name}/start` | JWT+Admin | Start a service |
|
||||
| POST | `/api/services/{name}/stop` | JWT+Admin | Stop a service |
|
||||
| POST | `/api/services/{name}/restart` | JWT+Admin | Restart a service |
|
||||
| POST | `/api/services/{name}/reload` | JWT+Admin | Reload a service |
|
||||
| GET | `/api/services/{name}/logs` | JWT | Last 100 log lines |
|
||||
|
||||
Query parameter: `host=<optional>` to target a specific LXC.
|
||||
|
||||
## How It Works
|
||||
|
||||
Commands are executed over SSH using `systemctl`:
|
||||
|
||||
```bash
|
||||
systemctl status nginx
|
||||
systemctl restart nginx
|
||||
journalctl -u nginx -n 100 --no-pager
|
||||
```
|
||||
|
||||
For LXC containers:
|
||||
|
||||
```bash
|
||||
pct exec <vmid> -- systemctl restart nginx
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
> This module is currently a stub. The UI view is implemented (shows a "module not enabled" placeholder). Full implementation is planned for a future release.
|
||||
|
||||
## Requirements
|
||||
|
||||
- SSH access with sufficient privileges to run `systemctl` commands
|
||||
- `systemd` on the target host/containers
|
||||
|
||||
## License
|
||||
|
||||
MIT — see [LICENSE](../../LICENSE)
|
||||
54
backend/modules/terminal/README.md
Normal file
54
backend/modules/terminal/README.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# Module — Terminal
|
||||
|
||||
**Type**: Optional (disabled by default)
|
||||
|
||||
Interactive SSH terminal in the browser. Connects to the Proxmox host (or any SSH-accessible target) and opens a full PTY session via WebSocket.
|
||||
|
||||
## Features
|
||||
|
||||
- Full PTY support (`xterm-256color`, interactive shell)
|
||||
- Responsive resizing — the terminal adjusts when the browser window is resized
|
||||
- Terminal theme matches the panel's Neumorphism dark/light design
|
||||
- Audit log entry on open and close
|
||||
|
||||
## WebSocket Endpoint
|
||||
|
||||
`GET /ws/terminal?token=<jwt>&host=<optional-override>`
|
||||
|
||||
If `host` is not specified, the SSH host configured during installation is used.
|
||||
|
||||
### Message Format
|
||||
|
||||
**Client → Server** (keyboard input): raw binary bytes
|
||||
|
||||
**Client → Server** (resize event): JSON text frame
|
||||
|
||||
```json
|
||||
{ "type": "resize", "cols": 120, "rows": 40 }
|
||||
```
|
||||
|
||||
**Server → Client** (terminal output): raw binary bytes
|
||||
|
||||
## Frontend
|
||||
|
||||
Uses [xterm.js](https://xtermjs.org/) with the following addons:
|
||||
|
||||
- `@xterm/addon-fit` — auto-resize to container dimensions
|
||||
- `@xterm/addon-attach` — attach xterm directly to a WebSocket
|
||||
|
||||
## How It Works
|
||||
|
||||
1. WebSocket connection is established and JWT is validated
|
||||
2. Backend opens an SSH connection using stored credentials
|
||||
3. A PTY session is requested (`xterm-256color`, initial size 80×24)
|
||||
4. An interactive shell is launched
|
||||
5. All data flows bidirectionally: WebSocket ↔ SSH ↔ PTY
|
||||
|
||||
## Requirements
|
||||
|
||||
- SSH access to the target host (password authentication)
|
||||
- The `ssh_host`, `ssh_username`, `ssh_password` settings must be configured
|
||||
|
||||
## License
|
||||
|
||||
MIT — see [LICENSE](../../LICENSE)
|
||||
68
backend/modules/updates/README.md
Normal file
68
backend/modules/updates/README.md
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# Module — Updates
|
||||
|
||||
**Type**: Core (always enabled)
|
||||
|
||||
Run `apt update && apt full-upgrade` on the Proxmox host or any LXC container, with real-time streaming output via WebSocket.
|
||||
|
||||
## Features
|
||||
|
||||
- Target: host, a specific LXC (`lxc:100`), or all LXC containers at once
|
||||
- Output streamed line-by-line via WebSocket — no polling required
|
||||
- Full output saved to `update_history` table in SQLite
|
||||
- Admin-only action
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Method | Path | Auth | Description |
|
||||
|--------|------|------|-------------|
|
||||
| POST | `/api/updates/run` | JWT+Admin | Start an update job |
|
||||
| GET | `/api/updates/history` | JWT | List past update jobs (last 50) |
|
||||
|
||||
### POST /api/updates/run
|
||||
|
||||
```json
|
||||
{ "target": "host" }
|
||||
```
|
||||
|
||||
```json
|
||||
{ "target": "lxc:100" }
|
||||
```
|
||||
|
||||
```json
|
||||
{ "target": "all" }
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{ "job_id": "1710000000-ab3f9z12", "message": "Mise à jour démarrée" }
|
||||
```
|
||||
|
||||
## WebSocket Streaming
|
||||
|
||||
Connect to `GET /ws/updates/{jobId}?token=<jwt>` to receive output in real time.
|
||||
|
||||
Message types published on the channel:
|
||||
|
||||
| Type | Payload | Description |
|
||||
|------|---------|-------------|
|
||||
| `update_output` | `{ "chunk": "..." }` | Line(s) of apt output |
|
||||
| `update_done` | `{ "job_id": "..." }` | Job completed successfully |
|
||||
| `update_error` | `{ "error": "..." }` | Job failed |
|
||||
|
||||
## How It Works
|
||||
|
||||
Updates run over SSH using the credentials configured during installation:
|
||||
|
||||
- **Host**: runs `DEBIAN_FRONTEND=noninteractive apt-get update && apt-get full-upgrade -y` directly
|
||||
- **LXC**: runs the same command via `pct exec <vmid> -- bash -c '...'`
|
||||
- **All**: iterates over `pct list` output and updates each container
|
||||
|
||||
## Requirements
|
||||
|
||||
- SSH access to the Proxmox host with sudo/root privileges
|
||||
- `pct` available on the host (standard on Proxmox VE)
|
||||
|
||||
## License
|
||||
|
||||
MIT — see [LICENSE](../../LICENSE)
|
||||
Loading…
Add table
Add a link
Reference in a new issue