Commit graph

37 commits

Author SHA1 Message Date
c9ba6755b8 fix: migration 004 — force enable logs and services modules
INSERT OR IGNORE in migration 003 was silently skipped because
logs/services rows already existed in the DB with is_enabled=0.
Migration 004 uses ON CONFLICT DO UPDATE to ensure is_enabled=1.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 02:45:32 +01:00
6666d931c9 fix: use valid LineIcons class names for services and logs nav items
lnid-gear-loading and lnid-scroll-document-1 don't exist in the font.
Replace with lnid-gear-2 (services) and lnid-scroll-angular-1 (logs).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 02:34:57 +01:00
5836f2201a feat: add Services and Logs modules (systemctl + journalctl via SSH)
Backend:
- modules/services: list, status, start/stop/restart systemctl services
  with pct exec support for LXC targets
- modules/logs: journalctl unit listing + WebSocket live streaming
  (direct SSH connection, journalctl -f, graceful teardown on WS close)
- migrations/003: seed services and logs modules in DB
- main.go: register services.New() and logs.New() in module loader

Frontend:
- services.html: target selector, search/filter, services table with
  active state indicators and start/stop/restart buttons
- logs.html: target + unit selectors, live follow toggle, scrollable
  terminal output with 3000-line cap
- app.js: servicePage() and logsPage() Alpine components + navItems
- locales: services and logs i18n keys (fr + en)
- pages.css: services table, state dots, logs output styles

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 02:03:55 +01:00
98cdabf3e1 feat: label session actuelle + fix bouton révoquer
- GetSessions: retourne is_current=true pour la session correspondant au cookie courant
- GetSessions: select token_hash pour la comparaison (non exposé dans le JSON)
- profile.html: badge "Session actuelle" + désactive révoquer pour la session courante
  (utiliser le bouton Déconnexion à la place)
- app.js: revokeSession utilise finally pour reset + isRevoking() helper
- pages.css: styles .badge-current + .session-current

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 01:45:09 +01:00
1cbd7e9d17 fix: GetSessions scan robuste (sql.NullString) + formatDate adaptatif
- Scan des colonnes datetime via sql.NullString au lieu de time.Time pour éviter
  les échecs silencieux dus aux formats mixtes SQLite (CURRENT_TIMESTAMP vs RFC3339)
- Log explicite si un scan échoue (au lieu du continue silencieux)
- formatDate frontend adapte le format SQLite "YYYY-MM-DD HH:MM:SS" en ISO pour new Date()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 01:39:37 +01:00
95757124de fix: corriger bug multi-sessions (upsertUser wrong ID + schema repair + logs refresh)
- auth.go: upsertUser utilise toujours SELECT explicite au lieu de LastInsertId()
  qui retournait un rowid obsolète pour ON CONFLICT DO UPDATE sur ligne existante
- auth.go: vérifier l'erreur de l'INSERT refresh_tokens (était silencieusement ignorée)
- auth.go: logs détaillés dans Refresh handler pour diagnostiquer les 401
- db.go: repairSchema() ajoute les colonnes manquantes (ip, last_used_at) dans les
  bases où migration 002 était partiellement appliquée (ancien bug multi-statements)
- app.js: tryRefresh et fetchMe affichent le vrai message d'erreur du backend

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 01:32:01 +01:00
dc0c67b89c fix: multi-sessions + système de toasts pour erreurs visibles
Sessions:
- Cookie pxp_refresh path: /api/auth/refresh → /api/auth/ pour être envoyé au logout
- Logout supprime uniquement le token de la session courante (via cookie hash)
  si pas de cookie = fallback suppression globale (rétro-compat)

Toasts:
- Store Alpine.store('toasts') avec error/success/warn/info + auto-dismiss
- Conteneur #pxp-toasts injecté dans <body>, persiste à travers les navigations Swup
- fetchMe(): HTTP non-401 → toast explicite (ex: HTTP 404 backend down)
- tryRefresh(): session expirée → sessionStorage → toast orange sur la page login
- logout(): message "Déconnexion réussie" sur la page login
- proxmoxPage.action(): toast succès/erreur pour start/stop LXC
- profilePage.revokeSession(): toast confirmation révocation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 01:04:37 +01:00
21e1e0ed1e feat: sync DB prefs, update history tab, configurable dashboard shortcuts
- auth store fetchMe(): sync theme/sidebar_position/lang from DB to localStorage+stores on login/refresh
- profilePage setters: PATCH /api/auth/preferences on every preference change
- updatesPage: add history tab (GET /api/updates/history) with job list, click to view output
- dashboardPage: load shortcuts from settings API, fall back to defaults if none configured
- settingsPage: new Raccourcis tab to add/remove/configure dashboard shortcuts (saved as JSON)
- settings.go: expose dashboard_shortcuts in publicSettings for GET/PUT access
- pages.css: add .history-table, .shortcut-row styles

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 00:35:24 +01:00
780e5ec81d fix: auth redirect bug + cookie Secure + migration multi-statements
- fetchMe: handle ALL non-ok responses (not just 401) by calling tryRefresh
  → avoids user=null when backend returns 404/500/any error
- DOMContentLoaded guard: check isAuthenticated instead of localStorage token
  → immediate redirect if fetchMe+tryRefresh both fail, no more flash of dashboard
- Cookie Secure flag: check X-Forwarded-Proto header for Traefik/proxy setup
  → cookie gets Secure=true when behind TLS-terminating reverse proxy
- db.go migrate(): split SQL by ; and exec each statement separately
  → fixes SQLite multi-statement limitation (only first stmt was executed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 22:29:22 +01:00
97212b7ffa feat: sessions management, web manifest, square icon-only buttons, remove lang select
- Backend: migration 002 adds user_agent/ip/last_used_at to refresh_tokens
- Backend: GET /api/auth/sessions + DELETE /api/auth/sessions/{id} endpoints
- Frontend: profile page — sessions section (browser, IP, datetime, revoke)
- Frontend: web manifest + SVG icon for PWA support
- Frontend: remove language selector from all navbars (moved to profile page)
- Frontend: neu-btn--icon-sm class for square icon-only buttons (theme/logout/edit)
- Frontend: manifest link added to all 9 HTML pages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 20:14:11 +01:00
b851dc61af fix: couleurs icônes, boutons carrés, sidebar collapsée, langue, SW scope, LXC arrêté
Icônes :
- Sidebar navItems : couleur distincte par section (iconStyle via data-binding)
- Sidebar footer user : couleur primary
- Navbar : logout → danger, soleil → amber, lune → blue
- Panel widgets : œil visible → success, caché → muted

Boutons :
- `.neu-btn--sm:has(> i:only-child)` → carré 2rem×2rem automatiquement
  (theme, logout, mode édition) sans modifier le HTML

Sidebar :
- --sidebar-width-collapsed : 64px → 52px
- Sidebar réduite : icônes centrées (justify-content center)

Langue :
- Setter vide sur `lang` dans navbar() pour corriger x-model avec getter
  (le @change gère la vraie mise à jour du store)

Service Worker :
- Enregistrement depuis /ws.sw.js (scope /) au lieu de /js/ws.sw.js (scope /js/)
- build.mjs : copie ws.sw.js vers dist/ root en plus de dist/js/

LXC arrêté :
- checkTarget() : skip si target.status !== 'running' → évite les 502 SSH

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 19:50:58 +01:00
7c57b0ff84 feat(dashboard): resize souris, DnD live, panel widgets, icônes corrigées
- Resize widget via drag sur le coin bas-droit (mousedown → mousemove →
  snap à taille 1 ou 2 colonnes selon distance)
- DnD live : les autres widgets se déplacent pendant le drag (onDragEnter
  réordonne le tableau + onDragEnd restaure si annulé)
- Mode édition : panel latéral avec tous les widgets (œil toggle visible/masqué),
  survol d'une entrée met en avant (outline) le widget correspondant dans la grille
- Bouton mode édition : icône seule (lnid-pencil-1)
- Correction noms d'icônes LineIcons (lnid-pencil-1, lnid-eye, lnid-eye-closed,
  lnid-cross → supprimé en faveur de toggles dans le panel)
- Suppression des classes CSS obsolètes (edit-mode-banner, widget-add-btn, etc.)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 19:30:35 +01:00
cbfb20505d feat(frontend): Service Worker WS, mode édition dashboard, sidebar click-to-toggle
- WebSocket via Service Worker (ws.sw.js) : connexions persistantes entre navigations
  Swup, reconnexion exponentielle, protocole WS_SUBSCRIBE/WS_UNSUBSCRIBE/WS_SEND
- WsProxy dans app.js : abstraction SW + fallback WebSocket direct
- proxmoxPage migré vers WsProxy (identique au dashboardPage)
- Dashboard : mode édition toggle — DnD, resize (x1/x2), masquer/afficher widget
  uniquement actifs en mode édition ; preview drag (is-dragging/drag-over)
- Sidebar : suppression bouton hamburger, clic sur sidebar-header pour replier
- pages.css : targets-grid 350px, styles edit mode, widget-size-2, drag preview
- neu.css : sidebar-header cursor pointer, suppression .sidebar-toggle

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 19:09:58 +01:00
b6d6355c6c fix: correction sélecteur CSS LineIcons Duotone (position absolute sur éléments)
Le CSS vendor avait un bug de sélecteur : [class^="lnid-"] sans pseudo-élément
recevait position:absolute + width/height:100%, faisant que les icônes
recouvraient tout l'écran. Correction : sélecteurs ::before et ::after explicites.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 18:49:56 +01:00
5f6681dd17 feat: LineIcons Duotone, page profil, widgets dashboard, sidebar gauche/droite
- Intégration LineIcons Duotone (css/ + toutes les pages)
- Remplacement de tous les symboles Unicode par des icônes lnid-*
- Page profile.html : préférences thème, position sidebar, langue
- Dashboard : système de widgets add/remove + drag-and-drop natif
- Sidebar gauche/droite configurable per-user (data-sidebar CSS + FOUC script)
- Store ui : sidebarPosition, applySidebarPosition(), setSidebarPosition()
- Composant profilePage() dans app.js
- nav.profile ajouté dans fr.json et en.json
- SUIVI.md mis à jour

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 18:38:48 +01:00
9739dbaee8 Correction CSS Swup, types WS et création fichier de suivi
- Extraction de tous les styles inline en css/pages.css (chargé globalement)
  pour corriger le CSS cassé lors des navigations Swup
- Correction types WebSocket : proxmox_resources → resources_update
  et msg.data → msg.payload (format réel du hub Go)
- Ajout d'un fetch HTTP immédiat dans dashboardPage/proxmoxPage
  pour éviter l'attente du premier tick (10s) du polling WS
- Correction msg.payload pour les updates (update_output/done/error)
- Ajout class terminal-wrapper sur .main-layout de terminal.html
  pour le fullscreen height sans affecter les autres pages
- Création SUIVI.md : état d'implémentation vs instruction.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:49:33 +01:00
a4b5b06f04 fix: CSS reset, settings API, modules champs, proxmox token
- CSS: ajout reset (box-sizing, margin, font-family, body background)
- Settings: save par PUT /api/settings/{key} (pas bulk), un appel par clé
- Settings: proxmox_token champ unique (format user@realm!id=secret)
- Modules: is_enabled/is_core (champs backend réels, pas enabled/core)
- Proxmox: supprime bouton reboot (route inexistante)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:28:55 +01:00
65c8bf332f fix: access_token (pas token) dans la réponse login/refresh
Le backend retourne { access_token: "...", user: {...} } pas { token: "..." }.
Le store Alpine lisait data.token → undefined → stockait "undefined" en localStorage
→ toutes les requêtes API échouaient avec 401.

Corrigé dans login() et tryRefresh().
Ajout d'un guard synchrone immédiat (pas de token → redirect login sans attendre fetchMe).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 16:50:52 +01:00
562eff8863 fix: CSS variables neu-*, WebSocket token, thème initial
- CSS: remplace var(--bg-*)/var(--text-*)/var(--accent-*)/var(--color-*)
  par les vraies variables --neu-* (neu-bg, neu-surface, neu-text, neu-primary…)
- CSS: supprime body{overflow:hidden} qui bloquait le scroll
- CSS: .auth-layout déplacé dans neu.css pour login/install
- WS: ajoute ?token= aux connexions /ws/proxmox (dashboardPage + proxmoxPage)
- HTML: script inline pour appliquer data-theme avant Alpine (évite FOUC)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 16:39:23 +01:00
2098c80ec1 feat: réécriture frontend Alpine.js + HTMX + Swup (branche frontend/alpine)
Remplace Vue 3 / Vite / TypeScript par une stack légère statique :
- Alpine.js v3 : réactivité inline, stores auth/ui/i18n, composants par page
- HTMX v2 : interactions serveur via attributs HTML
- Swup v4 : transitions de page (bundlé via esbuild, IIFE browser-loadable)
- xterm.js v5 : terminal PTY (bundlé via esbuild)

Structure : HTML statiques + js/app.js + js/terminal.js + css/ + locales/
Build : esbuild (bundle Swup + xterm seulement) → dist/ → Nginx
Dockerfile simplifié : node:22-alpine build → nginx:1.27-alpine serve

Pages : index, install, login, dashboard, proxmox, updates, terminal, settings, modules
URLs propres via nginx try_files $uri.html

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 16:19:24 +01:00
7ba0ff143c fix: sudo -n pour pct exec/list (permissions root requises)
Tous les appels pct passent par sudo -n pour les sessions SSH non-root.
GetPackages est résilient : utilise l'output même si exit code != 0.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 01:29:07 +01:00
8ff6fb0e8c fix: chemin complet /usr/sbin/pct pour les sessions SSH non-interactives
pct n'est pas dans le PATH SSH par défaut (exit 127).
Corrigé dans GetPackages, GetTargets et executeUpdate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 01:23:29 +01:00
8019b1475d fix: utiliser /api/proxmox/resources pour les LXC (même endpoint que Proxmox.vue)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 01:20:17 +01:00
53c535844e fix: utiliser l'API Proxmox pour la liste des LXC + check auto au chargement
- Remplace pct list SSH (permissions aléatoires) par /api/proxmox/lxc
- Lance checkAll() automatiquement à l'arrivée sur la page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 01:16:37 +01:00
82e3b850d0 feat: page mises à jour avec liste des paquets par cible
- Backend: GET /api/updates/targets (pct list via SSH)
- Backend: GET /api/updates/packages?target= (apt list --upgradable)
- Frontend: grille de cards par cible (host + chaque LXC)
- Bouton Check/Update par card, liste paquets dépliable (version actuelle → nouvelle)
- Boutons globaux "Tout vérifier" et "Tout mettre à jour"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 01:10:47 +01:00
c6028b6951 fix: proxmox client relit la config DB à chaque requête (token update sans redémarrage)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 00:39:19 +01:00
c0dff6d86f fix: échapper @ dans proxmoxTokenIdPlaceholder (vue-i18n SyntaxError 10)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 00:28:42 +01:00
233f690214 ux: token Proxmox en deux champs séparés (ID + Secret)
Install.vue et Settings.vue : remplace le champ unique "PVEAPIToken=..."
par deux inputs distincts — Token ID (ex: enzo@pam!panel) et Secret
(uuid). L'assemblage PVEAPIToken=ID=Secret se fait côté frontend avant
envoi. Plus besoin de connaître le format interne.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 00:24:57 +01:00
d55ecdcd97 fix: session F5 + token/password modifiables dans les paramètres
Session F5 :
- auth.store: restoreSession() essaie fetchMe() avec le token existant
  (< 15 min → fonctionne sans cookie), puis tryRefresh() en fallback
- router: appelle restoreSession() au premier chargement au lieu de tryRefresh()

Paramètres infrastructure :
- Champs ssh_password et proxmox_token en write-only (vide = pas de changement)
- SettingsHandler: accepte les clés chiffrées, chiffre avant stockage
- Permet de corriger le token Proxmox invalide sans réinstallation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 00:17:12 +01:00
1886071922 fix: session F5, terminal module core, logs proxmox
- router: tryRefresh() au premier chargement → plus besoin de se
  reconnecter après F5 (user restauré depuis le cookie refresh)
- migration 003: terminal marqué is_core=1 + is_enabled=1
- proxmox.go: logs pour diagnostiquer l'erreur 502 (visible dans
  Paramètres → Logs)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 00:07:02 +01:00
88831e3967 feat: nettoyage menu + suppression modules inexistants + log viewer
- Sidebar : retrait des liens files, logs, services (non implémentés)
- Migration 001 : suppression des inserts files/logs/services
- Migration 002 : DELETE des modules inexistants en DB existante
- logbuffer : ring buffer mémoire branché sur log.SetOutput
- GET /api/settings/logs : retourne les 300 dernières lignes de log
- Settings : onglet Logs avec auto-refresh (5s/10s/30s/60s/désactivé, défaut 10s)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 23:57:07 +01:00
07af66ad81 fix: SSHAuthenticator vide après installation + logs debug
Bug principal : l'SSHAuthenticator est créé au démarrage avec host=""
(DB vide avant installation). Après configure, il gardait host vide.
Le login lisait maintenant le ssh_host depuis la DB à chaque requête.

Logs ajoutés :
- ssh_auth.go : dial SSH, succès, échec avec détail d'erreur
- auth.go : host SSH utilisé, résultat auth à chaque login
- updates.go : credentials SSH, démarrage/fin de job
- terminal.go : ouverture/échec session SSH

Frontend :
- auth.store.ts : gère les réponses non-JSON sur erreur HTTP

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 23:39:52 +01:00
15965082ce fix: détection HTTPS et crash i18n étape 3 installation
- install.go : detectPublicURL utilise https pour tout domaine public
  même si Traefik envoie X-Forwarded-Proto: http en interne
- fr.json / en.json : échappe le @ dans proxmoxTokenHint avec {'@'}
  (vue-i18n interprétait @realm comme un linked message → SyntaxError)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 23:03:17 +01:00
a1090db802 fix: correction decodeJSON nil body + logs debug test-ssh
- helpers.go : corrige le cas r.Body == nil (panic → erreur explicite)
- install.go : ajout logs étape par étape pour TestSSH (TCP, auth SSH)
  sans jamais logger le mot de passe (longueur uniquement)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 22:54:47 +01:00
f6b3761d53 fix: supprimer nginx -t du Dockerfile frontend
nginx -t échoue au build car le hostname 'backend' n'existe
pas encore — il est résolu au runtime par le DNS Docker.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 21:57:11 +01:00
ad046219af fix: golang 1.23 → 1.26 dans le Dockerfile backend
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 21:55:04 +01:00
5dbcb1df07 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>
2026-03-20 21:08:53 +01:00