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:
enzo 2026-03-20 21:08:53 +01:00
commit 5dbcb1df07
66 changed files with 10370 additions and 0 deletions

View 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)

View 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
View 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
}

View 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
View 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

View 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)

View 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)

View 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)