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>
This commit is contained in:
enzo 2026-03-20 23:57:07 +01:00
parent 07af66ad81
commit 88831e3967
9 changed files with 200 additions and 27 deletions

View file

@ -3,9 +3,11 @@ package api
import (
"net/http"
"strconv"
"git.geronzi.fr/proxmoxPanel/core/backend/internal/audit"
"git.geronzi.fr/proxmoxPanel/core/backend/internal/db"
"git.geronzi.fr/proxmoxPanel/core/backend/internal/logbuffer"
"github.com/go-chi/chi/v5"
)
@ -168,6 +170,22 @@ func (h *SettingsHandler) DisableModule(w http.ResponseWriter, r *http.Request)
JSONResponse(w, http.StatusOK, map[string]string{"message": "Module désactivé"})
}
// GetLogs retourne les dernières lignes du log applicatif (tampon mémoire).
// GET /api/settings/logs?lines=200
func (h *SettingsHandler) GetLogs(w http.ResponseWriter, r *http.Request) {
n := 200
if s := r.URL.Query().Get("lines"); s != "" {
if v, err := strconv.Atoi(s); err == nil && v > 0 {
n = v
}
}
lines := logbuffer.Global.Lines(n)
if lines == nil {
lines = []string{}
}
JSONResponse(w, http.StatusOK, lines)
}
// GetAuditLog retourne le journal d'audit paginé.
// GET /api/settings/audit
func (h *SettingsHandler) GetAuditLog(w http.ResponseWriter, r *http.Request) {

View file

@ -93,8 +93,5 @@ INSERT OR IGNORE INTO modules (id, name, description, version, is_core, is_enabl
('dashboard', 'Dashboard', 'Tableau de bord avec widgets configurables', '1.0.0', 1, 1),
('proxmox', 'Proxmox', 'Gestion des LXC et VM Proxmox', '1.0.0', 1, 1),
('updates', 'Mises à jour', 'Mises à jour de paquets apt avec streaming', '1.0.0', 1, 1),
('settings', 'Paramètres', 'Configuration de l''application', '1.0.0', 1, 1),
('files', 'Fichiers', 'Navigateur de fichiers SFTP', '1.0.0', 0, 0),
('terminal', 'Terminal', 'Terminal SSH interactif', '1.0.0', 0, 0),
('logs', 'Logs', 'Streaming de logs en temps réel', '1.0.0', 0, 0),
('services', 'Services', 'Gestion des services systemd', '1.0.0', 0, 0);
('settings', 'Paramètres', 'Configuration de l''application', '1.0.0', 1, 1);

View file

@ -0,0 +1,3 @@
-- Migration 002 : Suppression des modules non implémentés
-- Les modules files, logs et services n'ont pas d'implémentation backend.
DELETE FROM modules WHERE id IN ('files', 'logs', 'services');

View file

@ -0,0 +1,53 @@
// Package logbuffer maintient un tampon circulaire en mémoire des lignes de log.
// Il implémente io.Writer pour être branché sur log.SetOutput via io.MultiWriter.
package logbuffer
import (
"bytes"
"sync"
)
const maxLines = 500
// Buffer est un tampon circulaire thread-safe de lignes de log.
type Buffer struct {
mu sync.RWMutex
lines []string
}
// Write implémente io.Writer. Découpe p en lignes et les ajoute au tampon.
func (b *Buffer) Write(p []byte) (int, error) {
b.mu.Lock()
defer b.mu.Unlock()
for _, line := range bytes.Split(p, []byte("\n")) {
s := string(bytes.TrimRight(line, "\r"))
if s == "" {
continue
}
b.lines = append(b.lines, s)
if len(b.lines) > maxLines {
b.lines = b.lines[len(b.lines)-maxLines:]
}
}
return len(p), nil
}
// Lines retourne les n dernières lignes (ou toutes si n <= 0 ou n > taille).
func (b *Buffer) Lines(n int) []string {
b.mu.RLock()
defer b.mu.RUnlock()
total := len(b.lines)
if n <= 0 || n >= total {
result := make([]string, total)
copy(result, b.lines)
return result
}
result := make([]string, n)
copy(result, b.lines[total-n:])
return result
}
// Global est l'instance partagée utilisée par main.go et les handlers.
var Global = &Buffer{}