Implementiere umfassendes Logging-System für WOL-Ereignisse

- Neue Log-Tabelle in der Datenbank
- Automatisches Logging bei WOL-Button-Klicks
- Dedizierte Logs-Seite mit Bootstrap-Design
- Tooltips mit letzten 5 WOL-Ereignissen pro PC
- API-Endpunkte für Log-Verwaltung
- Einheitliches Design zwischen Haupt- und Logs-Seite
- Vollständige Dokumentation des Logging-Systems
This commit is contained in:
2025-08-22 07:16:14 +02:00
parent 2f4920cc27
commit b6888ca5da
10 changed files with 723 additions and 6 deletions

View File

@ -21,8 +21,8 @@ func InitDB() (*DB, error) {
return nil, err
}
// Tabelle erstellen
createTableSQL := `
// PC-Tabelle erstellen
createPCTableSQL := `
CREATE TABLE IF NOT EXISTS pcs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
@ -32,7 +32,24 @@ func InitDB() (*DB, error) {
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);`
_, err = db.Exec(createTableSQL)
_, err = db.Exec(createPCTableSQL)
if err != nil {
return nil, err
}
// Log-Tabelle erstellen
createLogTableSQL := `
CREATE TABLE IF NOT EXISTS wol_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
pc_id INTEGER NOT NULL,
pc_name TEXT NOT NULL,
mac TEXT NOT NULL,
trigger TEXT NOT NULL,
FOREIGN KEY (pc_id) REFERENCES pcs (id) ON DELETE CASCADE
);`
_, err = db.Exec(createLogTableSQL)
if err != nil {
return nil, err
}
@ -134,3 +151,92 @@ func (db *DB) GetPCByID(id int) (*models.PC, error) {
return &pc, nil
}
// CreateLog erstellt einen neuen Log-Eintrag
func (db *DB) CreateLog(pcID int, pcName, mac, trigger string) (*models.LogEvent, error) {
now := time.Now()
result, err := db.Exec(
"INSERT INTO wol_logs (timestamp, pc_id, pc_name, mac, trigger) VALUES (?, ?, ?, ?, ?)",
now, pcID, pcName, mac, trigger,
)
if err != nil {
return nil, err
}
id, err := result.LastInsertId()
if err != nil {
return nil, err
}
return &models.LogEvent{
ID: int(id),
Timestamp: now,
PCID: pcID,
PCName: pcName,
MAC: mac,
Trigger: trigger,
}, nil
}
// GetAllLogs holt alle Log-Einträge aus der Datenbank
func (db *DB) GetAllLogs() ([]models.LogEvent, error) {
rows, err := db.Query("SELECT id, timestamp, pc_id, pc_name, mac, trigger FROM wol_logs ORDER BY timestamp DESC")
if err != nil {
return nil, err
}
defer rows.Close()
var logs []models.LogEvent
for rows.Next() {
var log models.LogEvent
err := rows.Scan(&log.ID, &log.Timestamp, &log.PCID, &log.PCName, &log.MAC, &log.Trigger)
if err != nil {
return nil, err
}
logs = append(logs, log)
}
return logs, nil
}
// GetLogsByPCID holt alle Log-Einträge für einen bestimmten PC
func (db *DB) GetLogsByPCID(pcID int) ([]models.LogEvent, error) {
rows, err := db.Query("SELECT id, timestamp, pc_id, pc_name, mac, trigger FROM wol_logs WHERE pc_id = ? ORDER BY timestamp DESC", pcID)
if err != nil {
return nil, err
}
defer rows.Close()
var logs []models.LogEvent
for rows.Next() {
var log models.LogEvent
err := rows.Scan(&log.ID, &log.Timestamp, &log.PCID, &log.PCName, &log.MAC, &log.Trigger)
if err != nil {
return nil, err
}
logs = append(logs, log)
}
return logs, nil
}
// GetRecentLogsByPCID holt die letzten 5 Log-Einträge für einen bestimmten PC
func (db *DB) GetRecentLogsByPCID(pcID int) ([]models.LogEvent, error) {
rows, err := db.Query("SELECT id, timestamp, pc_id, pc_name, mac, trigger FROM wol_logs WHERE pc_id = ? ORDER BY timestamp DESC LIMIT 5", pcID)
if err != nil {
return nil, err
}
defer rows.Close()
var logs []models.LogEvent
for rows.Next() {
var log models.LogEvent
err := rows.Scan(&log.ID, &log.Timestamp, &log.PCID, &log.PCName, &log.MAC, &log.Trigger)
if err != nil {
return nil, err
}
logs = append(logs, log)
}
return logs, nil
}

View File

@ -1,6 +1,7 @@
package handlers
import (
"log"
"medi-wol/internal/database"
"medi-wol/internal/models"
"medi-wol/internal/ping"
@ -34,6 +35,13 @@ func (h *PCHandler) Index(c *gin.Context) {
})
}
// Logs zeigt die Logs-Seite an
func (h *PCHandler) Logs(c *gin.Context) {
c.HTML(http.StatusOK, "logs.html", gin.H{
"title": "Medi-WOL - Logs",
})
}
// GetAllPCs gibt alle gespeicherten PCs zurück
func (h *PCHandler) GetAllPCs(c *gin.Context) {
pcs, err := h.db.GetAllPCs()
@ -194,6 +202,13 @@ func (h *PCHandler) WakePC(c *gin.Context) {
return
}
// Log-Eintrag erstellen
_, logErr := h.db.CreateLog(pc.ID, pc.Name, pc.MAC, "button")
if logErr != nil {
// Log-Fehler nicht an den Benutzer weitergeben, nur intern protokollieren
log.Printf("Fehler beim Erstellen des Log-Eintrags: %v", logErr)
}
c.JSON(http.StatusOK, models.PCResponse{
Success: true,
Message: "Wake-on-LAN Paket erfolgreich gesendet an " + pc.Name,
@ -229,3 +244,74 @@ func (h *PCHandler) GetPCStatus(c *gin.Context) {
"status": statusList,
})
}
// GetAllLogs gibt alle Log-Einträge zurück
func (h *PCHandler) GetAllLogs(c *gin.Context) {
logs, err := h.db.GetAllLogs()
if err != nil {
c.JSON(http.StatusInternalServerError, models.LogResponse{
Success: false,
Message: "Fehler beim Laden der Logs: " + err.Error(),
})
return
}
c.JSON(http.StatusOK, models.LogResponse{
Success: true,
Logs: logs,
})
}
// GetLogsByPCID gibt alle Log-Einträge für einen bestimmten PC zurück
func (h *PCHandler) GetLogsByPCID(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.Atoi(idStr)
if err != nil {
c.JSON(http.StatusBadRequest, models.LogResponse{
Success: false,
Message: "Ungültige PC-ID",
})
return
}
logs, err := h.db.GetLogsByPCID(id)
if err != nil {
c.JSON(http.StatusInternalServerError, models.LogResponse{
Success: false,
Message: "Fehler beim Laden der Logs: " + err.Error(),
})
return
}
c.JSON(http.StatusOK, models.LogResponse{
Success: true,
Logs: logs,
})
}
// GetRecentLogsByPCID gibt die letzten 5 Log-Einträge für einen bestimmten PC zurück
func (h *PCHandler) GetRecentLogsByPCID(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.Atoi(idStr)
if err != nil {
c.JSON(http.StatusBadRequest, models.LogResponse{
Success: false,
Message: "Ungültige PC-ID",
})
return
}
logs, err := h.db.GetRecentLogsByPCID(id)
if err != nil {
c.JSON(http.StatusInternalServerError, models.LogResponse{
Success: false,
Message: "Fehler beim Laden der Logs: " + err.Error(),
})
return
}
c.JSON(http.StatusOK, models.LogResponse{
Success: true,
Logs: logs,
})
}

31
internal/models/log.go Normal file
View File

@ -0,0 +1,31 @@
package models
import (
"time"
)
// LogEvent repräsentiert ein Log-Ereignis in der Datenbank
type LogEvent struct {
ID int `json:"id" db:"id"`
Timestamp time.Time `json:"timestamp" db:"timestamp"`
PCID int `json:"pc_id" db:"pc_id"`
PCName string `json:"pc_name" db:"pc_name"`
MAC string `json:"mac" db:"mac"`
Trigger string `json:"trigger" db:"trigger"` // "button" oder "cron"
}
// CreateLogRequest repräsentiert die Anfrage zum Erstellen eines neuen Log-Eintrags
type CreateLogRequest struct {
PCID int `json:"pc_id" binding:"required"`
PCName string `json:"pc_name" binding:"required"`
MAC string `json:"mac" binding:"required"`
Trigger string `json:"trigger" binding:"required"`
}
// LogResponse repräsentiert die Antwort für Log-Operationen
type LogResponse struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
Log *LogEvent `json:"log,omitempty"`
Logs []LogEvent `json:"logs,omitempty"`
}