13 Commits

16 changed files with 873 additions and 127 deletions

2
.gitignore vendored
View File

@@ -46,3 +46,5 @@ Thumbs.db
# Temporary files
tmp/
temp/
dist/

130
AUTOSTART_README.md Normal file
View File

@@ -0,0 +1,130 @@
# Medi-WOL Autostart-System
## Übersicht
Das Medi-WOL Autostart-System ermöglicht es, PCs automatisch zu bestimmten Zeitpunkten über Wake-on-LAN zu starten. Es verwendet die Standard-Crontab-Syntax für die Zeitplanung und integriert sich nahtlos in das bestehende Logging-System.
## Funktionen
### 1. **Autostart-Konfiguration pro PC**
- **Crontab-Syntax**: Unterstützt alle Standard-Crontab-Ausdrücke
- **Aktivierung/Deaktivierung**: Einzelne PCs können unabhängig voneinander konfiguriert werden
- **Standardwert**: "30 7 * * Mon-Fri" (Mo-Fr um 7:30 Uhr)
### 2. **Crontab-Syntax-Unterstützung**
- **Minute**: 0-59
- **Stunde**: 0-23
- **Tag**: 1-31
- **Monat**: 1-12
- **Wochentag**: 0-6 (0=Sonntag) oder Mon, Tue, Wed, Thu, Fri, Sat, Sun
### 3. **Automatische Ausführung**
- **Scheduler**: Läuft im Hintergrund und prüft jede Minute
- **Logging**: Alle automatischen WOL-Ereignisse werden mit Trigger "cron" protokolliert
- **Fehlerbehandlung**: Robuste Fehlerbehandlung bei WOL-Ausführung
## Verwendung
### 1. **PC mit Autostart hinzufügen**
1. Navigieren Sie zur Hauptseite
2. Füllen Sie alle Pflichtfelder aus (Name, MAC, IP)
3. Geben Sie den gewünschten Crontab-Ausdruck ein
4. Aktivieren Sie die Checkbox "Autostart aktiv"
5. Klicken Sie auf "PC hinzufügen"
### 2. **Autostart bearbeiten**
1. Klicken Sie auf "Bearbeiten" bei dem gewünschten PC
2. Ändern Sie den Crontab-Ausdruck oder die Aktivierung
3. Klicken Sie auf "Speichern"
### 3. **Crontab-Ausdrücke verstehen**
- **Hilfe**: Klicken Sie auf den Link zu [Crontab-Guru](https://crontab.guru/)
- **Beispiele**:
- `30 7 * * Mon-Fri` = Mo-Fr um 7:30 Uhr
- `0 8 * * 1-5` = Mo-Fr um 8:00 Uhr
- `0 9 * * 0,6` = Sa und So um 9:00 Uhr
- `*/15 * * * *` = Alle 15 Minuten
## Technische Details
### 1. **Datenbankstruktur**
```sql
ALTER TABLE pcs ADD COLUMN autostart_cron TEXT DEFAULT '30 7 * * Mon-Fri';
ALTER TABLE pcs ADD COLUMN autostart_enabled BOOLEAN DEFAULT 0;
```
### 2. **Scheduler-Implementierung**
- **Go-Routine**: Läuft parallel zum Hauptserver
- **Ticker**: Prüft jede Minute auf auszuführende Aufgaben
- **Crontab-Parser**: Eigene Implementierung für häufige Anwendungsfälle
- **Graceful Shutdown**: Sauberes Beenden bei Server-Herunterfahrt
### 3. **Crontab-Parser-Features**
- **Wildcards**: `*` für "alle"
- **Bereiche**: `1-5` für "1 bis 5"
- **Wochentage**: `Mon-Fri` für "Montag bis Freitag"
- **Einzelwerte**: `30` für "nur 30"
## Beispiele
### **Geschäftliche Anwendungen**
- **Büro-PCs**: `30 7 * * Mon-Fri` - Startet alle Büro-PCs Mo-Fr um 7:30
- **Server**: `0 6 * * *` - Startet Server täglich um 6:00
- **Backup-Systeme**: `0 2 * * 0` - Startet Backup-Systeme sonntags um 2:00
### **Private Anwendungen**
- **Home-Office**: `0 8 * * Mon-Fri` - Startet Arbeits-PC Mo-Fr um 8:00
- **Gaming**: `0 18 * * Fri-Sun` - Startet Gaming-PC Fr-So um 18:00
- **Medien-Server**: `0 20 * * *` - Startet Medien-Server täglich um 20:00
## Sicherheit und Monitoring
### 1. **Logging**
- Alle automatischen WOL-Ereignisse werden protokolliert
- Trigger wird als "cron" gekennzeichnet
- Vollständige Protokollierung in der Logs-Seite
### 2. **Fehlerbehandlung**
- WOL-Fehler werden geloggt, aber nicht an den Benutzer weitergegeben
- Scheduler läuft weiter, auch wenn einzelne WOL-Befehle fehlschlagen
- Robuste Behandlung ungültiger Crontab-Ausdrücke
### 3. **Performance**
- Scheduler prüft nur alle PCs mit aktiviertem Autostart
- Effiziente Datenbankabfragen
- Minimale Server-Belastung
## Erweiterungen
### **Zukünftige Features**
- **Webhook-Integration**: Benachrichtigungen bei erfolgreichen/fehlgeschlagenen Autostarts
- **Erweiterte Crontab-Syntax**: Unterstützung für komplexere Ausdrücke
- **Zeitzonen**: Lokale Zeitzonen-Unterstützung
- **Bedingte Ausführung**: Nur starten wenn PC offline ist
### **Monitoring und Alerting**
- **E-Mail-Benachrichtigungen**: Bei fehlgeschlagenen Autostarts
- **Dashboard**: Übersicht aller geplanten Autostarts
- **Statistiken**: Erfolgsrate und Ausführungszeiten
## Fehlerbehebung
### **Häufige Probleme**
1. **PC startet nicht**: Überprüfen Sie MAC-Adresse und Netzwerk-Konfiguration
2. **Falsche Zeit**: Überprüfen Sie den Crontab-Ausdruck mit Crontab-Guru
3. **Scheduler läuft nicht**: Überprüfen Sie die Server-Logs
### **Debugging**
- **Server-Logs**: Zeigen alle Scheduler-Aktivitäten
- **Logs-Seite**: Zeigt alle WOL-Ereignisse (auch automatische)
- **Crontab-Validierung**: Verwenden Sie Crontab-Guru für Tests
## Support
### **Hilfreiche Links**
- [Crontab-Guru](https://crontab.guru/) - Online-Crontab-Editor und Validator
- [Cron-Wiki](https://en.wikipedia.org/wiki/Cron) - Detaillierte Crontab-Dokumentation
- [Crontab-Examples](https://crontab.guru/examples.html) - Häufige Crontab-Beispiele
### **Kontakt**
Bei Fragen oder Problemen wenden Sie sich an das Medi-WOL-Entwicklungsteam.

136
README.md
View File

@@ -8,6 +8,9 @@ Ein moderner Wake-on-LAN Manager, entwickelt mit Go und einer schönen Web-Oberf
- **Wake-on-LAN**: Ein-Klick-Aufwecken von Computern über MAC-Adressen
- **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
- **Cross-Platform**: Läuft auf Windows und Linux
@@ -114,6 +117,27 @@ Falls weder Kommandozeilenparameter noch Umgebungsvariable gesetzt sind, wird Po
- Klicken Sie auf den "Löschen"-Button neben dem gewünschten PC
- Bestätigen Sie die Löschung
### Autostart konfigurieren
1. **Autostart aktivieren**: Aktivieren Sie die Checkbox "Autostart aktiv"
2. **Crontab-Syntax eingeben**: Verwenden Sie das Format `Minute Stunde Tag Monat Wochentag`
- **Standard**: `30 7 * * Mon-Fri` (Mo-Fr um 7:30 Uhr)
- **Beispiele**:
- `0 8 * * *` = Täglich um 8:00 Uhr
- `0 9 * * Mon,Wed,Fri` = Mo, Mi, Fr um 9:00 Uhr
- `30 7 * * 1-5` = Mo-Fr um 7:30 Uhr
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`
- **Log-Details**: Jeder Eintrag enthält Timestamp, PC-Name, MAC-Adresse und Auslöser (Button/Scheduler)
## Windows Installer
### Features
@@ -282,6 +306,7 @@ medi-wol/
│ ├── database/ # Datenbanklogik
│ ├── handlers/ # HTTP-Handler
│ ├── models/ # Datenmodelle
│ ├── scheduler/ # Autostart-Scheduler für WOL-Ereignisse
│ └── wol/ # Wake-on-LAN Service
├── web/ # Web-Oberfläche
│ ├── static/ # CSS, JavaScript
@@ -292,6 +317,8 @@ medi-wol/
├── go.mod # Go-Module
├── build.bat # Windows Build-Skript (inkl. Installer)
├── build.sh # Linux Build-Skript
├── AUTOSTART_README.md # Dokumentation des Autostart-Systems
├── LOGGING_README.md # Dokumentation des Logging-Systems
└── README.md # Diese Datei
```
@@ -302,6 +329,7 @@ Das `installer/` Verzeichnis enthält alle Dateien für den Windows Installer:
## API-Endpunkte
### PC-Management
- `GET /` - Hauptseite
- `GET /api/pcs` - Alle PCs abrufen
- `POST /api/pcs` - Neuen PC erstellen
@@ -310,20 +338,54 @@ 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
- `GET /api/logs/pc/:id` - Logs für einen spezifischen PC abrufen
- `GET /api/logs/pc/:id/recent` - Die letzten 5 Logs für einen PC abrufen
## Datenbank
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 |
|-------------|----------|------------------------|
|------------------|----------|--------------------------------------------|
| id | INTEGER | Primärschlüssel |
| name | TEXT | Pflichtfeld |
| mac | TEXT | Pflichtfeld, eindeutig |
| ip | TEXT | Pflichtfeld |
| autostart_cron | TEXT | Crontab-Syntax für Autostart (Standard: `30 7 * * Mon-Fri`) |
| autostart_enabled| BOOLEAN | Autostart aktiviert (Standard: false) |
| 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 |
|-------------|----------|--------------------------------------------|
| id | INTEGER | Primärschlüssel |
| timestamp | DATETIME | Zeitstempel des WOL-Ereignisses |
| pc_id | INTEGER | Referenz auf PC (Foreign Key) |
| pc_name | TEXT | Name des PCs zum Zeitpunkt des Ereignisses |
| mac | TEXT | MAC-Adresse des PCs |
| trigger | TEXT | Auslöser: "button" oder "cron" |
## Wake-on-LAN
Das System sendet Magic Packets an die gespeicherten MAC-Adressen. Stellen Sie sicher, dass:
@@ -375,6 +437,32 @@ golint ./...
go vet ./...
```
## Scheduler-System
### Übersicht
Der integrierte Scheduler ermöglicht es, Wake-on-LAN Ereignisse automatisch zu geplanten Zeiten auszuführen. Dies ist ideal für:
- **Bürozeiten**: PCs starten automatisch vor Arbeitsbeginn
- **Wartungsfenster**: Regelmäßige Systemstarts für Updates
- **Energiesparen**: PCs werden nur bei Bedarf gestartet
### Funktionsweise
1. **Crontab-Parser**: Unterstützt Standard Crontab-Syntax
2. **Minuten-Intervall**: Prüft jede Minute auf fällige Aufgaben
3. **Automatische Ausführung**: Sendet WOL-Pakete ohne Benutzerinteraktion
4. **Logging**: Alle Scheduler-Ereignisse werden protokolliert
### Unterstützte Crontab-Formate
- **Einfache Werte**: `30 7 * * Mon-Fri` (Mo-Fr um 7:30 Uhr)
- **Bereiche**: `0 8-18 * * *` (8:00-18:00 Uhr stündlich)
- **Wochentage**: `0 9 * * Mon,Wed,Fri` (Mo, Mi, Fr um 9:00 Uhr)
- **Wochentag-Bereiche**: `0 8 * * Mon-Fri` (Mo-Fr um 8:00 Uhr)
### Konfiguration
- **Standard**: `30 7 * * Mon-Fri` (Mo-Fr um 7:30 Uhr)
- **Anpassung**: Über die Web-Oberfläche bei jedem PC individuell
- **Aktivierung**: Checkbox "Autostart aktiv" pro PC
- **Sofortige Übernahme**: Änderungen werden sofort vom Scheduler berücksichtigt
## Deployment
### Windows Service
@@ -448,49 +536,3 @@ docker run -d -p 9090:9090 -e PORT=9090 medi-wol:latest
Dieses Projekt ist für den internen Gebrauch bestimmt.
## Support
Bei Fragen oder Problemen wenden Sie sich an das Entwicklungsteam.
Hier sind drei einfache Wege nimm Variante A (empfohlen) für die neueste Version.
- A) Offizielles Tarball (empfohlen)
1) Alte Installation entfernen (falls vorhanden):
```bash
sudo rm -rf /usr/local/go
```
2) Tarball laden und installieren (ersetze 1.22.6 und Arch bei Bedarf: amd64/arm64):
```bash
wget https://go.dev/dl/go1.22.6.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.22.6.linux-amd64.tar.gz
```
3) PATH setzen (dauerhaft):
```bash
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.profile
source ~/.profile
```
4) Prüfen:
```bash
go version
```
- B) Snap (schnell, meist aktuell)
```bash
sudo snap install go --classic
go version
```
- C) Ubuntu-Paket (einfach, aber oft älter)
```bash
sudo apt update
sudo apt install -y golang-go
go version
```
Hinweise:
- Standard-GOPATH ist ~/go. Optional:
```bash
echo 'export GOPATH=$HOME/go' >> ~/.profile
echo 'export PATH=$PATH:$GOPATH/bin' >> ~/.profile
source ~/.profile
```

View File

@@ -6,9 +6,12 @@ import (
"log"
"medi-wol/internal/database"
"medi-wol/internal/handlers"
"medi-wol/internal/scheduler"
"medi-wol/internal/wol"
"os"
"os/signal"
"strconv"
"syscall"
"github.com/gin-gonic/gin"
)
@@ -45,9 +48,33 @@ 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()
// Scheduler initialisieren und starten
scheduler := scheduler.NewScheduler(db, wolService)
scheduler.Start()
defer scheduler.Stop()
// Handler initialisieren
pcHandler := handlers.NewPCHandler(db, wolService)
@@ -72,10 +99,19 @@ func main() {
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")
// Server starten
// Graceful Shutdown konfigurieren
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
// Server in Goroutine starten
go func() {
serverAddr := fmt.Sprintf(":%d", port)
log.Printf("Medi-WOL startet auf Port %d...", port)
log.Printf("Web-Oberfläche verfügbar unter: http://localhost%s", serverAddr)
@@ -83,4 +119,9 @@ func main() {
if err := r.Run(serverAddr); err != nil {
log.Fatal("Fehler beim Starten des Servers:", err)
}
}()
// Auf Shutdown-Signal warten
<-quit
log.Println("Server wird heruntergefahren...")
}

Binary file not shown.

Binary file not shown.

View File

@@ -27,16 +27,14 @@ Compression=lzma
SolidCompression=yes
WizardStyle=modern
PrivilegesRequired=admin
ArchitecturesAllowed=x64
ArchitecturesInstallIn64BitMode=x64
ArchitecturesAllowed=x64os
ArchitecturesInstallIn64BitMode=x64os
[Languages]
Name: "german"; MessagesFile: "compiler:Languages\German.isl"
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 6.1; Check: not IsAdminInstallMode
Name: "startservice"; Description: "{cm:StartServiceAfterInstall}"; GroupDescription: "{cm:ServiceOptions}"
[Files]
@@ -48,10 +46,8 @@ Source: "..\LICENSE"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\README.md"; DestDir: "{app}"; Flags: ignoreversion
[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{app}\{#MyAppExeName}"
Name: "{group}\mediWOL WEB UI"; Filename: "cmd.exe"; Parameters: "/c start http://localhost:{code:GetPort}"; IconFilename: "medi-wol.ico"
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon
[Run]
; Installiere Medi-WOL als Windows-Dienst mit NSSM
@@ -76,6 +72,8 @@ Filename: "{app}\nssm.exe"; Parameters: "set ""{#MyAppServiceName}"" AppStopMeth
Filename: "{app}\nssm.exe"; Parameters: "set ""{#MyAppServiceName}"" AppStopMethodThreads 1500"; Flags: runhidden
; Service nach Installation starten mit Verzögerung
Filename: "{app}\nssm.exe"; Parameters: "start ""{#MyAppServiceName}"""; StatusMsg: "Starte Medi-WOL Dienst..."; Flags: runhidden waituntilterminated; Check: ShouldStartService
; Warte auf Service-Start und öffne dann die Web-Oberfläche
Filename: "cmd.exe"; Parameters: "/c timeout /t 3 /nobreak >nul && start http://localhost:{code:GetPort}"; StatusMsg: "Öffne Medi-WOL Web-Oberfläche..."; Flags: runhidden; Check: ShouldStartService
; Windows Firewall-Ausnahmen für den Service hinzufügen
Filename: "netsh.exe"; Parameters: "advfirewall firewall add rule name=""{#MyAppServiceName} - Inbound"" dir=in action=allow program=""{app}\{#MyAppExeName}"" enable=yes"; StatusMsg: "Erstelle Windows Firewall-Ausnahme (Eingehend)..."; Flags: runhidden
Filename: "netsh.exe"; Parameters: "advfirewall firewall add rule name=""{#MyAppServiceName} - Outbound"" dir=out action=allow program=""{app}\{#MyAppExeName}"" enable=yes"; StatusMsg: "Erstelle Windows Firewall-Ausnahme (Ausgehend)..."; Flags: runhidden
@@ -83,13 +81,13 @@ Filename: "netsh.exe"; Parameters: "advfirewall firewall add rule name=""{#MyApp
[UninstallRun]
; Stoppe den Windows-Dienst vor der Deinstallation
Filename: "{app}\nssm.exe"; Parameters: "stop ""{#MyAppServiceName}"""; Flags: runhidden
Filename: "{app}\nssm.exe"; Parameters: "stop ""{#MyAppServiceName}"""; Flags: runhidden; RunOnceId: "StopService"
; Entferne den Windows-Dienst
Filename: "{app}\nssm.exe"; Parameters: "remove ""{#MyAppServiceName}"" confirm"; StatusMsg: "Entferne Medi-WOL Dienst..."; Flags: runhidden
Filename: "{app}\nssm.exe"; Parameters: "remove ""{#MyAppServiceName}"" confirm"; StatusMsg: "Entferne Medi-WOL Dienst..."; Flags: runhidden; RunOnceId: "RemoveService"
; Entferne Windows Firewall-Regeln
Filename: "netsh.exe"; Parameters: "advfirewall firewall delete rule name=""{#MyAppServiceName} - Inbound"""; Flags: runhidden
Filename: "netsh.exe"; Parameters: "advfirewall firewall delete rule name=""{#MyAppServiceName} - Outbound"""; Flags: runhidden
Filename: "netsh.exe"; Parameters: "advfirewall firewall delete rule name=""{#MyAppServiceName} - Port {code:GetPort}"""; Flags: runhidden
Filename: "netsh.exe"; Parameters: "advfirewall firewall delete rule name=""{#MyAppServiceName} - Inbound"""; Flags: runhidden; RunOnceId: "RemoveFirewallInbound"
Filename: "netsh.exe"; Parameters: "advfirewall firewall delete rule name=""{#MyAppServiceName} - Outbound"""; Flags: runhidden; RunOnceId: "RemoveFirewallOutbound"
Filename: "netsh.exe"; Parameters: "advfirewall firewall delete rule name=""{#MyAppServiceName} - Port {code:GetPort}"""; Flags: runhidden; RunOnceId: "RemoveFirewallPort"
[Code]
var
@@ -150,9 +148,6 @@ end;
[CustomMessages]
german.CreateDesktopIcon=Desktop-Verknüpfung erstellen
german.CreateQuickLaunchIcon=Quick Launch-Verknüpfung erstellen
german.AdditionalIcons=Zusätzliche Verknüpfungen:
german.ServiceOptions=Service-Optionen:
german.StartServiceAfterInstall=Service nach der Installation starten
german.UninstallProgram=Medi-WOL entfernen

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -28,6 +28,8 @@ func InitDB() (*DB, error) {
name TEXT NOT NULL,
mac TEXT NOT NULL UNIQUE,
ip TEXT NOT NULL,
autostart_cron TEXT DEFAULT '30 7 * * Mon-Fri',
autostart_enabled BOOLEAN DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);`
@@ -37,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 (
@@ -54,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 {
@@ -66,7 +121,7 @@ func InitDB() (*DB, error) {
// GetAllPCs holt alle PCs aus der Datenbank
func (db *DB) GetAllPCs() ([]models.PC, error) {
rows, err := db.Query("SELECT id, name, mac, ip, created_at, updated_at FROM pcs ORDER BY name")
rows, err := db.Query("SELECT id, name, mac, ip, autostart_cron, autostart_enabled, created_at, updated_at FROM pcs ORDER BY name")
if err != nil {
return nil, err
}
@@ -75,7 +130,7 @@ func (db *DB) GetAllPCs() ([]models.PC, error) {
var pcs []models.PC
for rows.Next() {
var pc models.PC
err := rows.Scan(&pc.ID, &pc.Name, &pc.MAC, &pc.IP, &pc.CreatedAt, &pc.UpdatedAt)
err := rows.Scan(&pc.ID, &pc.Name, &pc.MAC, &pc.IP, &pc.AutostartCron, &pc.AutostartEnabled, &pc.CreatedAt, &pc.UpdatedAt)
if err != nil {
return nil, err
}
@@ -86,11 +141,11 @@ func (db *DB) GetAllPCs() ([]models.PC, error) {
}
// CreatePC erstellt einen neuen PC-Eintrag
func (db *DB) CreatePC(name, mac, ip string) (*models.PC, error) {
func (db *DB) CreatePC(name, mac, ip, autostartCron string, autostartEnabled bool) (*models.PC, error) {
now := time.Now()
result, err := db.Exec(
"INSERT INTO pcs (name, mac, ip, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
name, mac, ip, now, now,
"INSERT INTO pcs (name, mac, ip, autostart_cron, autostart_enabled, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)",
name, mac, ip, autostartCron, autostartEnabled, now, now,
)
if err != nil {
return nil, err
@@ -106,17 +161,19 @@ func (db *DB) CreatePC(name, mac, ip string) (*models.PC, error) {
Name: name,
MAC: mac,
IP: ip,
AutostartCron: autostartCron,
AutostartEnabled: autostartEnabled,
CreatedAt: now,
UpdatedAt: now,
}, nil
}
// UpdatePC aktualisiert einen bestehenden PC-Eintrag
func (db *DB) UpdatePC(id int, name, mac, ip string) (*models.PC, error) {
func (db *DB) UpdatePC(id int, name, mac, ip, autostartCron string, autostartEnabled bool) (*models.PC, error) {
now := time.Now()
_, err := db.Exec(
"UPDATE pcs SET name = ?, mac = ?, ip = ?, updated_at = ? WHERE id = ?",
name, mac, ip, now, id,
"UPDATE pcs SET name = ?, mac = ?, ip = ?, autostart_cron = ?, autostart_enabled = ?, updated_at = ? WHERE id = ?",
name, mac, ip, autostartCron, autostartEnabled, now, id,
)
if err != nil {
return nil, err
@@ -127,6 +184,8 @@ func (db *DB) UpdatePC(id int, name, mac, ip string) (*models.PC, error) {
Name: name,
MAC: mac,
IP: ip,
AutostartCron: autostartCron,
AutostartEnabled: autostartEnabled,
UpdatedAt: now,
}, nil
}
@@ -141,9 +200,9 @@ func (db *DB) DeletePC(id int) error {
func (db *DB) GetPCByID(id int) (*models.PC, error) {
var pc models.PC
err := db.QueryRow(
"SELECT id, name, mac, ip, created_at, updated_at FROM pcs WHERE id = ?",
"SELECT id, name, mac, ip, autostart_cron, autostart_enabled, created_at, updated_at FROM pcs WHERE id = ?",
id,
).Scan(&pc.ID, &pc.Name, &pc.MAC, &pc.IP, &pc.CreatedAt, &pc.UpdatedAt)
).Scan(&pc.ID, &pc.Name, &pc.MAC, &pc.IP, &pc.AutostartCron, &pc.AutostartEnabled, &pc.CreatedAt, &pc.UpdatedAt)
if err != nil {
return nil, err
@@ -152,6 +211,27 @@ func (db *DB) GetPCByID(id int) (*models.PC, error) {
return &pc, nil
}
// GetPCsWithAutostart holt alle PCs mit aktiviertem Autostart
func (db *DB) GetPCsWithAutostart() ([]models.PC, error) {
rows, err := db.Query("SELECT id, name, mac, ip, autostart_cron, autostart_enabled, created_at, updated_at FROM pcs WHERE autostart_enabled = 1")
if err != nil {
return nil, err
}
defer rows.Close()
var pcs []models.PC
for rows.Next() {
var pc models.PC
err := rows.Scan(&pc.ID, &pc.Name, &pc.MAC, &pc.IP, &pc.AutostartCron, &pc.AutostartEnabled, &pc.CreatedAt, &pc.UpdatedAt)
if err != nil {
return nil, err
}
pcs = append(pcs, pc)
}
return pcs, nil
}
// CreateLog erstellt einen neuen Log-Eintrag
func (db *DB) CreateLog(pcID int, pcName, mac, trigger string) (*models.LogEvent, error) {
now := time.Now()
@@ -240,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
}

View File

@@ -79,8 +79,13 @@ func (h *PCHandler) CreatePC(c *gin.Context) {
return
}
// Standardwert für Autostart-Cron setzen, falls nicht angegeben
if req.AutostartCron == "" {
req.AutostartCron = "30 7 * * Mon-Fri"
}
// PC erstellen
pc, err := h.db.CreatePC(req.Name, req.MAC, req.IP)
pc, err := h.db.CreatePC(req.Name, req.MAC, req.IP, req.AutostartCron, req.AutostartEnabled)
if err != nil {
c.JSON(http.StatusInternalServerError, models.PCResponse{
Success: false,
@@ -126,8 +131,13 @@ func (h *PCHandler) UpdatePC(c *gin.Context) {
return
}
// Standardwert für Autostart-Cron setzen, falls nicht angegeben
if req.AutostartCron == "" {
req.AutostartCron = "30 7 * * Mon-Fri"
}
// PC aktualisieren
pc, err := h.db.UpdatePC(id, req.Name, req.MAC, req.IP)
pc, err := h.db.UpdatePC(id, req.Name, req.MAC, req.IP, req.AutostartCron, req.AutostartEnabled)
if err != nil {
c.JSON(http.StatusInternalServerError, models.PCResponse{
Success: false,
@@ -236,6 +246,8 @@ func (h *PCHandler) GetPCStatus(c *gin.Context) {
MAC: pc.MAC,
IP: pc.IP,
Online: online,
AutostartCron: pc.AutostartCron,
AutostartEnabled: pc.AutostartEnabled,
})
}
@@ -315,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,
})
}

View File

@@ -10,6 +10,8 @@ type PC struct {
Name string `json:"name" db:"name"`
MAC string `json:"mac" db:"mac"`
IP string `json:"ip" db:"ip"`
AutostartCron string `json:"autostart_cron" db:"autostart_cron"`
AutostartEnabled bool `json:"autostart_enabled" db:"autostart_enabled"`
CreatedAt time.Time `json:"created_at" db:"updated_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
@@ -19,6 +21,8 @@ type CreatePCRequest struct {
Name string `json:"name" binding:"required"`
MAC string `json:"mac" binding:"required"`
IP string `json:"ip" binding:"required"`
AutostartCron string `json:"autostart_cron"`
AutostartEnabled bool `json:"autostart_enabled"`
}
// UpdatePCRequest repräsentiert die Anfrage zum Aktualisieren eines PCs
@@ -26,6 +30,8 @@ type UpdatePCRequest struct {
Name string `json:"name" binding:"required"`
MAC string `json:"mac" binding:"required"`
IP string `json:"ip" binding:"required"`
AutostartCron string `json:"autostart_cron"`
AutostartEnabled bool `json:"autostart_enabled"`
}
// PCResponse repräsentiert die Antwort für PC-Operationen
@@ -43,4 +49,6 @@ type PCStatus struct {
MAC string `json:"mac"`
IP string `json:"ip"`
Online bool `json:"online"`
AutostartCron string `json:"autostart_cron"`
AutostartEnabled bool `json:"autostart_enabled"`
}

View File

@@ -0,0 +1,185 @@
package scheduler
import (
"log"
"medi-wol/internal/database"
"medi-wol/internal/models"
"medi-wol/internal/wol"
"strconv"
"strings"
"time"
)
// Scheduler verwaltet geplante WOL-Ereignisse
type Scheduler struct {
db *database.DB
wolService *wol.Service
ticker *time.Ticker
stopChan chan bool
}
// NewScheduler erstellt einen neuen Scheduler
func NewScheduler(db *database.DB, wolService *wol.Service) *Scheduler {
return &Scheduler{
db: db,
wolService: wolService,
stopChan: make(chan bool),
}
}
// Start startet den Scheduler
func (s *Scheduler) Start() {
s.ticker = time.NewTicker(1 * time.Minute) // Jede Minute prüfen
go s.run()
log.Println("Autostart-Scheduler gestartet")
}
// Stop stoppt den Scheduler
func (s *Scheduler) Stop() {
if s.ticker != nil {
s.ticker.Stop()
}
close(s.stopChan)
log.Println("Autostart-Scheduler gestoppt")
}
// run ist die Hauptschleife des Schedulers
func (s *Scheduler) run() {
for {
select {
case <-s.ticker.C:
s.checkAndExecuteScheduledTasks()
case <-s.stopChan:
return
}
}
}
// checkAndExecuteScheduledTasks prüft alle geplanten Aufgaben
func (s *Scheduler) checkAndExecuteScheduledTasks() {
// 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 {
log.Printf("Fehler beim Laden der PCs mit Autostart: %v", err)
return
}
for _, pc := range pcs {
if s.shouldExecuteNow(pc.AutostartCron, now) {
s.executeWOL(pc)
}
}
}
// shouldExecuteNow prüft, ob ein Crontab-Ausdruck jetzt ausgeführt werden soll
func (s *Scheduler) shouldExecuteNow(cronExpr string, now time.Time) bool {
parts := strings.Fields(cronExpr)
if len(parts) != 5 {
log.Printf("Ungültiger Crontab-Ausdruck: %s", cronExpr)
return false
}
// Einfache Crontab-Parser-Implementierung
// Format: Minute Stunde Tag Monat Wochentag
minute := s.parseCronField(parts[0], 0, 59, now.Minute())
hour := s.parseCronField(parts[1], 0, 23, now.Hour())
day := s.parseCronField(parts[2], 1, 31, now.Day())
month := s.parseCronField(parts[3], 1, 12, int(now.Month()))
weekday := s.parseCronField(parts[4], 0, 6, int(now.Weekday()))
return minute && hour && day && month && weekday
}
// parseCronField parst ein einzelnes Crontab-Feld
func (s *Scheduler) parseCronField(field string, min, max, current int) bool {
// Einfache Implementierung für häufige Fälle
if field == "*" {
return true
}
// Einzelne Zahl
if num, err := strconv.Atoi(field); err == nil {
return num == current
}
// Bereich (z.B. "1-5")
if strings.Contains(field, "-") {
parts := strings.Split(field, "-")
if len(parts) == 2 {
start, err1 := strconv.Atoi(parts[0])
end, err2 := strconv.Atoi(parts[1])
if err1 == nil && err2 == nil {
return current >= start && current <= end
}
}
}
// Wochentag-Namen (z.B. "Mon-Fri")
if strings.Contains(field, "-") && (strings.Contains(field, "Mon") || strings.Contains(field, "Tue") ||
strings.Contains(field, "Wed") || strings.Contains(field, "Thu") ||
strings.Contains(field, "Fri") || strings.Contains(field, "Sat") || strings.Contains(field, "Sun")) {
return s.parseWeekdayRange(field, current)
}
return false
}
// parseWeekdayRange parst Wochentag-Bereiche
func (s *Scheduler) parseWeekdayRange(field string, current int) bool {
// Wochentag-Mapping (0=Sonntag, 1=Montag, ..., 6=Samstag)
weekdayMap := map[string]int{
"Sun": 0, "Mon": 1, "Tue": 2, "Wed": 3, "Thu": 4, "Fri": 5, "Sat": 6,
}
if strings.Contains(field, "-") {
parts := strings.Split(field, "-")
if len(parts) == 2 {
start, ok1 := weekdayMap[parts[0]]
end, ok2 := weekdayMap[parts[1]]
if ok1 && ok2 {
// Spezielle Behandlung für Wochentag-Bereiche
if start <= end {
return current >= start && current <= end
} else {
// Bereich über Mitternacht (z.B. Fri-Mon)
return current >= start || current <= end
}
}
}
}
return false
}
// executeWOL führt einen geplanten WOL-Befehl aus
func (s *Scheduler) executeWOL(pc models.PC) {
log.Printf("Führe geplanten WOL für PC %s (%s) aus", pc.Name, pc.MAC)
// WOL-Paket senden
err := s.wolService.WakePC(pc.MAC)
if err != nil {
log.Printf("Fehler beim Senden des WOL-Pakets für PC %s: %v", pc.Name, err)
return
}
// Log-Eintrag erstellen
_, logErr := s.db.CreateLog(pc.ID, pc.Name, pc.MAC, "cron")
if logErr != nil {
log.Printf("Fehler beim Erstellen des Log-Eintrags: %v", logErr)
}
log.Printf("Geplanter WOL für PC %s erfolgreich ausgeführt", pc.Name)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -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() {
@@ -157,12 +163,21 @@ class PCManager {
</td>
<td>${new Date(pc.created_at).toLocaleDateString('de-DE')}</td>
<td>
<div class="mb-1">
<small class="text-muted">
<i class="fas fa-clock"></i> Autostart:
${pc.autostart_enabled ?
`<span class="text-success">${this.escapeHtml(pc.autostart_cron || '30 7 * * Mon-Fri')}</span>` :
'<span class="text-muted">Deaktiviert</span>'
}
</small>
</div>
<div class="btn-group" role="group">
<button class="btn btn-success btn-sm" onclick="pcManager.wakePC(${pc.id})"
title="PC aufwecken">
<i class="fas fa-power-off"></i> Aufwecken
</button>
<button class="btn btn-warning btn-sm" onclick="pcManager.editPC(${pc.id}, '${this.escapeHtml(pc.name)}', '${this.escapeHtml(pc.mac)}', '${this.escapeHtml(pc.ip || '')}')"
<button class="btn btn-warning btn-sm" onclick="pcManager.editPC(${pc.id}, '${this.escapeHtml(pc.name)}', '${this.escapeHtml(pc.mac)}', '${this.escapeHtml(pc.ip || '')}', '${this.escapeHtml(pc.autostart_cron || '')}', ${pc.autostart_enabled})"
title="PC bearbeiten">
<i class="fas fa-edit"></i> Bearbeiten
</button>
@@ -183,9 +198,11 @@ class PCManager {
const name = document.getElementById('pcName').value.trim();
const mac = document.getElementById('macAddress').value.trim();
const ip = document.getElementById('ipAddress').value.trim();
const autostartCron = document.getElementById('autostartCron').value.trim();
const autostartEnabled = document.getElementById('autostartEnabled').checked;
if (!name || !mac || !ip) {
this.showNotification('Warnung', 'Bitte füllen Sie alle Felder aus', 'warning');
this.showNotification('Warnung', 'Bitte füllen Sie alle Pflichtfelder aus', 'warning');
return;
}
@@ -195,7 +212,13 @@ class PCManager {
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name, mac, ip })
body: JSON.stringify({
name,
mac,
ip,
autostart_cron: autostartCron || '30 7 * * Mon-Fri',
autostart_enabled: autostartEnabled
})
});
const data = await response.json();
@@ -212,12 +235,14 @@ class PCManager {
}
}
editPC(id, name, mac, ip) {
editPC(id, name, mac, ip, autostartCron, autostartEnabled) {
// Modal mit PC-Daten füllen
document.getElementById('editPCId').value = id;
document.getElementById('editPCName').value = name;
document.getElementById('editMACAddress').value = mac;
document.getElementById('editIPAddress').value = ip;
document.getElementById('editAutostartCron').value = autostartCron || '30 7 * * Mon-Fri';
document.getElementById('editAutostartEnabled').checked = autostartEnabled || false;
// Modal öffnen
const editModal = new bootstrap.Modal(document.getElementById('editPCModal'));
@@ -229,9 +254,11 @@ class PCManager {
const name = document.getElementById('editPCName').value.trim();
const mac = document.getElementById('editMACAddress').value.trim();
const ip = document.getElementById('editIPAddress').value.trim();
const autostartCron = document.getElementById('editAutostartCron').value.trim();
const autostartEnabled = document.getElementById('editAutostartEnabled').checked;
if (!name || !mac || !ip) {
this.showNotification('Warnung', 'Bitte füllen Sie alle Felder aus', 'warning');
this.showNotification('Warnung', 'Bitte füllen Sie alle Pflichtfelder aus', 'warning');
return;
}
@@ -241,7 +268,13 @@ class PCManager {
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name, mac, ip })
body: JSON.stringify({
name,
mac,
ip,
autostart_cron: autostartCron || '30 7 * * Mon-Fri',
autostart_enabled: autostartEnabled
})
});
const data = await response.json();
@@ -433,6 +466,60 @@ class PCManager {
content += '</div>';
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

View File

@@ -281,6 +281,29 @@ body {
border-top-color: var(--brand-primary) !important;
}
/* Autostart-Checkbox Styling - vereinfacht */
.form-check {
display: flex;
align-items: center;
min-height: 38px; /* Gleiche Höhe wie form-control */
}
/* Spezifische Ausrichtung für die Autostart-Zeile */
.row .col-md-6 .form-check {
height: 38px;
display: flex;
align-items: center;
}
/* Mehr Abstand zwischen Checkbox und Label */
.form-check-input[type="checkbox"] {
margin-right: 0.5em; /* Erhöhter Abstand zur Checkbox */
}
.form-check-label {
margin-left: 0.5em; /* Zusätzlicher Abstand zum Label */
}
/* Content Header */
.content-header {
text-align: center;

View File

@@ -42,6 +42,22 @@
</div>
</div>
<!-- Urlaubsmodus-Checkbox -->
<div class="row mb-3">
<div class="col-12 text-center">
<div class="form-check d-inline-block">
<input class="form-check-input" type="checkbox" id="vacationMode" style="transform: scale(1.2);">
<label class="form-check-label ms-2" for="vacationMode">
<strong>Urlaubsmodus</strong>
</label>
<i class="fas fa-info-circle ms-2 text-muted"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="Der Urlaubsmodus deaktiviert den geplanten Autostart für alle Geräte"></i>
</div>
</div>
</div>
<!-- Neuen PC hinzufügen -->
<div class="row mb-4">
<div class="col-12">
@@ -75,6 +91,31 @@
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="autostartCron" class="form-label">Autostart (Crontab)</label>
<input type="text" class="form-control" id="autostartCron"
placeholder="30 7 * * Mon-Fri"
title="Format: Minute Stunde Tag Monat Wochentag">
<div class="form-text">
<a href="https://crontab.guru/" target="_blank" class="text-decoration-none">
<i class="fas fa-external-link-alt"></i> Crontab-Guru für Hilfe
</a>
</div>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<div class="form-check mt-4">
<input class="form-check-input" type="checkbox" id="autostartEnabled">
<label class="form-check-label" for="autostartEnabled">
Autostart aktiv
</label>
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> PC hinzufügen
</button>
@@ -176,6 +217,25 @@
<input type="text" class="form-control" id="editIPAddress"
placeholder="192.168.1.100" required>
</div>
<div class="mb-3">
<label for="editAutostartCron" class="form-label">Autostart (Crontab)</label>
<input type="text" class="form-control" id="editAutostartCron"
placeholder="30 7 * * Mon-Fri"
title="Format: Minute Stunde Tag Monat Wochentag">
<div class="form-text">
<a href="https://crontab.guru/" target="_blank" class="text-decoration-none">
<i class="fas fa-external-link-alt"></i> Crontab-Guru für Hilfe
</a>
</div>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="editAutostartEnabled">
<label class="form-check-label" for="editAutostartEnabled">
Autostart aktiv
</label>
</div>
</div>
</form>
</div>
<div class="modal-footer">