diff --git a/README.md b/README.md index 78bdae5..f80b74a 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Ein moderner Wake-on-LAN Manager, entwickelt mit Go und einer schönen Web-Oberf - **IP-Adressverwaltung**: Pro Gerät wird eine IP-Adresse gespeichert - **Online-Status (Ping)**: Geräte können per Ping geprüft und im UI als Online/Offline angezeigt werden - **Automatischer Start**: Scheduler für geplante Wake-on-LAN Ereignisse mit Crontab-Syntax +- **Urlaubsmodus**: Globaler Schalter, der den geplanten Autostart für alle Geräte deaktiviert - **Logging-System**: Vollständige Protokollierung aller WOL-Ereignisse (Button-Klick und Scheduler) - **Moderne Web-Oberfläche**: Responsive Design mit Bootstrap und FontAwesome - **SQLite-Datenbank**: Einfache lokale Datenspeicherung @@ -127,6 +128,11 @@ Falls weder Kommandozeilenparameter noch Umgebungsvariable gesetzt sind, wird Po 3. **Hilfe**: Link zu [crontab.guru](https://crontab.guru/) für Crontab-Syntax 4. **Speichern**: Änderungen werden automatisch vom Scheduler übernommen +### Urlaubsmodus +- Aktivieren/Deaktivieren über die Checkbox "Urlaubsmodus" auf der Startseite +- Tooltip: "Der Urlaubsmodus deaktiviert den geplanten Autostart für alle Geräte" +- Wirkung: Solange aktiv, führt der Scheduler keine geplanten WOL-Tasks aus + ### Logs einsehen - **Tooltips**: Die letzten 5 Log-Einträge werden als Tooltip über jeder PC-Zeile angezeigt - **Logs-Seite**: Vollständige Log-Übersicht unter `/logs` @@ -332,6 +338,10 @@ Das `installer/` Verzeichnis enthält alle Dateien für den Windows Installer: - `POST /api/pcs/:id/wake` - PC aufwecken - `GET /api/pcs/status` - Online-Status aller PCs abrufen (Ping) +### Settings +- `GET /api/settings/vacation-mode` - Status des Urlaubsmodus abrufen +- `POST /api/settings/vacation-mode` - Urlaubsmodus setzen (`{ "vacation_mode": true|false }`) + ### Logging - `GET /logs` - Logs-Seite anzeigen - `GET /api/logs` - Alle Log-Einträge abrufen @@ -342,6 +352,9 @@ Das `installer/` Verzeichnis enthält alle Dateien für den Windows Installer: Die Anwendung verwendet SQLite als lokale Datenbank. Die Datenbankdatei `medi-wol.db` wird automatisch im Projektverzeichnis erstellt. +Hinweis: Beim ersten Start bzw. wenn die Tabelle `pcs` leer ist, wird automatisch ein Dummy-PC angelegt: +- Name: `Test`, IP: `192.168.0.1`, MAC: `00:11:22:33:AA:FF`, Cron: `30 7 * * Mon-Fri`, Autostart: deaktiviert + ### Tabellenstruktur `pcs` | Spalte | Typ | Hinweis | |------------------|----------|--------------------------------------------| @@ -354,6 +367,15 @@ Die Anwendung verwendet SQLite als lokale Datenbank. Die Datenbankdatei `medi-wo | created_at | DATETIME | Automatisch | | updated_at | DATETIME | Automatisch | +### Tabellenstruktur `settings` +| Spalte | Typ | Hinweis | +|-----------|----------|---------------------------------| +| id | INTEGER | Primärschlüssel | +| key | TEXT | Eindeutiger Schlüssel | +| value | TEXT | Wert (z. B. "true"/"false") | +| created_at| DATETIME | Automatisch | +| updated_at| DATETIME | Automatisch | + ### Tabellenstruktur `wol_logs` | Spalte | Typ | Hinweis | |-------------|----------|--------------------------------------------| diff --git a/cmd/server/main.go b/cmd/server/main.go index f5cc109..e0f13ad 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -48,6 +48,25 @@ func main() { } defer db.Close() + // Dummy-PC anlegen, falls keine Geräte vorhanden sind + pcs, err := db.GetAllPCs() + if err != nil { + log.Printf("Warnung: Konnte PCs nicht laden: %v", err) + } else if len(pcs) == 0 { + _, createErr := db.CreatePC( + "Test", + "00:11:22:33:AA:FF", + "192.168.0.1", + "30 7 * * Mon-Fri", + false, + ) + if createErr != nil { + log.Printf("Warnung: Konnte Dummy-PC nicht anlegen: %v", createErr) + } else { + log.Println("Dummy-PC 'Test' automatisch angelegt (leere Datenbank)") + } + } + // Wake-on-LAN Service initialisieren wolService := wol.NewService() @@ -79,6 +98,10 @@ func main() { r.GET("/api/logs", pcHandler.GetAllLogs) r.GET("/api/logs/pc/:id", pcHandler.GetLogsByPCID) r.GET("/api/logs/pc/:id/recent", pcHandler.GetRecentLogsByPCID) + + // Settings-API + r.GET("/api/settings/vacation-mode", pcHandler.GetVacationMode) + r.POST("/api/settings/vacation-mode", pcHandler.SetVacationMode) // Statische Dateien bereitstellen (nach den spezifischen Routen) r.Static("/static", "./web/static") diff --git a/internal/database/database.go b/internal/database/database.go index 3db3f38..28c89c2 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -39,6 +39,21 @@ func InitDB() (*DB, error) { return nil, err } + // Migration: fehlende Spalten hinzufügen (für bestehende DBs) + // autostart_cron + _, err = db.Exec("ALTER TABLE pcs ADD COLUMN autostart_cron TEXT DEFAULT '30 7 * * Mon-Fri'") + if err != nil { + // Spalte existiert bereits, ignorieren + } + // autostart_enabled + _, err = db.Exec("ALTER TABLE pcs ADD COLUMN autostart_enabled BOOLEAN DEFAULT 0") + if err != nil { + // Spalte existiert bereits, ignorieren + } + // Standardwerte für evtl. NULL-Einträge setzen + _, _ = db.Exec("UPDATE pcs SET autostart_cron = '30 7 * * Mon-Fri' WHERE autostart_cron IS NULL") + _, _ = db.Exec("UPDATE pcs SET autostart_enabled = 0 WHERE autostart_enabled IS NULL") + // Log-Tabelle erstellen createLogTableSQL := ` CREATE TABLE IF NOT EXISTS wol_logs ( @@ -56,6 +71,44 @@ func InitDB() (*DB, error) { return nil, err } + // Settings-Tabelle erstellen + createSettingsTableSQL := ` + CREATE TABLE IF NOT EXISTS settings ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + key TEXT NOT NULL UNIQUE, + value TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP + );` + + _, err = db.Exec(createSettingsTableSQL) + if err != nil { + return nil, err + } + + // Standard-Einstellungen setzen + _, err = db.Exec("INSERT OR IGNORE INTO settings (key, value) VALUES ('vacation_mode', 'false')") + if err != nil { + return nil, err + } + + // Falls keine PCs vorhanden sind, Dummy-PC anlegen + var pcCount int + err = db.QueryRow("SELECT COUNT(*) FROM pcs").Scan(&pcCount) + if err != nil { + log.Printf("Warnung: Konnte Anzahl der PCs nicht ermitteln: %v", err) + } else if pcCount == 0 { + _, insErr := db.Exec( + "INSERT INTO pcs (name, mac, ip, autostart_cron, autostart_enabled, created_at, updated_at) VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)", + "Test", "00:11:22:33:AA:FF", "192.168.0.1", "30 7 * * Mon-Fri", 0, + ) + if insErr != nil { + log.Printf("Warnung: Konnte Dummy-PC nicht anlegen: %v", insErr) + } else { + log.Println("Dummy-PC 'Test' in InitDB automatisch angelegt (leere Datenbank)") + } + } + // Füge IP-Spalte hinzu, falls sie nicht existiert _, err = db.Exec("ALTER TABLE pcs ADD COLUMN ip TEXT DEFAULT ''") if err != nil { @@ -267,3 +320,32 @@ func (db *DB) GetRecentLogsByPCID(pcID int) ([]models.LogEvent, error) { return logs, nil } + +// GetSetting holt eine Einstellung aus der Datenbank +func (db *DB) GetSetting(key string) (string, error) { + var value string + err := db.QueryRow("SELECT value FROM settings WHERE key = ?", key).Scan(&value) + if err != nil { + return "", err + } + return value, nil +} + +// SetSetting setzt eine Einstellung in der Datenbank +func (db *DB) SetSetting(key, value string) error { + now := time.Now() + _, err := db.Exec( + "INSERT OR REPLACE INTO settings (key, value, updated_at) VALUES (?, ?, ?)", + key, value, now, + ) + return err +} + +// IsVacationModeEnabled prüft, ob der Urlaubsmodus aktiviert ist +func (db *DB) IsVacationModeEnabled() (bool, error) { + value, err := db.GetSetting("vacation_mode") + if err != nil { + return false, err + } + return value == "true", nil +} diff --git a/internal/handlers/pc_handler.go b/internal/handlers/pc_handler.go index 167e41e..a490292 100644 --- a/internal/handlers/pc_handler.go +++ b/internal/handlers/pc_handler.go @@ -327,3 +327,55 @@ func (h *PCHandler) GetRecentLogsByPCID(c *gin.Context) { Logs: logs, }) } + +// GetVacationMode gibt den aktuellen Urlaubsmodus-Status zurück +func (h *PCHandler) GetVacationMode(c *gin.Context) { + enabled, err := h.db.IsVacationModeEnabled() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "success": false, + "message": "Fehler beim Laden des Urlaubsmodus: " + err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "success": true, + "vacation_mode": enabled, + }) +} + +// SetVacationMode setzt den Urlaubsmodus +func (h *PCHandler) SetVacationMode(c *gin.Context) { + var req struct { + VacationMode bool `json:"vacation_mode" binding:"required"` + } + + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "success": false, + "message": "Ungültige Anfrage: " + err.Error(), + }) + return + } + + value := "false" + if req.VacationMode { + value = "true" + } + + err := h.db.SetSetting("vacation_mode", value) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "success": false, + "message": "Fehler beim Speichern des Urlaubsmodus: " + err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "Urlaubsmodus erfolgreich aktualisiert", + "vacation_mode": req.VacationMode, + }) +} diff --git a/internal/scheduler/scheduler.go b/internal/scheduler/scheduler.go index 5d0abaf..0d3ae09 100644 --- a/internal/scheduler/scheduler.go +++ b/internal/scheduler/scheduler.go @@ -57,8 +57,19 @@ func (s *Scheduler) run() { // checkAndExecuteScheduledTasks prüft alle geplanten Aufgaben func (s *Scheduler) checkAndExecuteScheduledTasks() { - now := time.Now() + // Prüfe zuerst, ob der Urlaubsmodus aktiviert ist + vacationMode, err := s.db.IsVacationModeEnabled() + if err != nil { + log.Printf("Fehler beim Prüfen des Urlaubsmodus: %v", err) + return + } + + if vacationMode { + log.Println("Urlaubsmodus aktiviert - Autostart deaktiviert") + return + } + now := time.Now() // Alle PCs mit aktiviertem Autostart holen pcs, err := s.db.GetPCsWithAutostart() if err != nil { diff --git a/web/static/logo.png b/web/static/logo.png index 5211f8e..f322c20 100644 Binary files a/web/static/logo.png and b/web/static/logo.png differ diff --git a/web/static/script.js b/web/static/script.js index c0eb2c7..8d062bb 100644 --- a/web/static/script.js +++ b/web/static/script.js @@ -12,6 +12,7 @@ class PCManager { init() { this.loadPCs(); + this.loadVacationMode(); this.setupEventListeners(); this.startAutoStatus(); } @@ -47,6 +48,11 @@ class PCManager { document.getElementById('refreshStatusBtn').addEventListener('click', () => { this.refreshStatus(); }); + + // Urlaubsmodus Checkbox + document.getElementById('vacationMode').addEventListener('change', (e) => { + this.setVacationMode(e.target.checked); + }); } async loadPCs() { @@ -460,6 +466,60 @@ class PCManager { content += ''; return content; } + + // Urlaubsmodus laden + async loadVacationMode() { + try { + const response = await fetch('/api/settings/vacation-mode'); + const data = await response.json(); + + if (data.success) { + const vacationModeCheckbox = document.getElementById('vacationMode'); + if (vacationModeCheckbox) { + vacationModeCheckbox.checked = data.vacation_mode; + } + } + } catch (error) { + console.error('Fehler beim Laden des Urlaubsmodus:', error); + } + } + + // Urlaubsmodus setzen + async setVacationMode(enabled) { + try { + const response = await fetch('/api/settings/vacation-mode', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + vacation_mode: enabled + }) + }); + + const data = await response.json(); + + if (data.success) { + const status = enabled ? 'aktiviert' : 'deaktiviert'; + this.showNotification('Erfolg', `Urlaubsmodus ${status}`, 'success'); + } else { + this.showNotification('Fehler', data.message, 'danger'); + // Checkbox zurücksetzen bei Fehler + const vacationModeCheckbox = document.getElementById('vacationMode'); + if (vacationModeCheckbox) { + vacationModeCheckbox.checked = !enabled; + } + } + } catch (error) { + console.error('Fehler beim Setzen des Urlaubsmodus:', error); + this.showNotification('Fehler', 'Fehler beim Setzen des Urlaubsmodus', 'danger'); + // Checkbox zurücksetzen bei Fehler + const vacationModeCheckbox = document.getElementById('vacationMode'); + if (vacationModeCheckbox) { + vacationModeCheckbox.checked = !enabled; + } + } + } } // PC Manager initialisieren, wenn die Seite geladen ist diff --git a/web/templates/index.html b/web/templates/index.html index f0aec79..2cd7b3d 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -42,6 +42,22 @@ + +