Compare commits
19 Commits
windows-in
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a84c767f2 | |||
| 2bfe8f6196 | |||
| bea61b0c95 | |||
| ce2eea5f43 | |||
| bdeabaad2e | |||
| 5c13a34bbf | |||
| d85181e797 | |||
| b860571f40 | |||
| 1dfa0c9b32 | |||
| 0488e4ec47 | |||
| 5b03441b4d | |||
| 49c3ece8f5 | |||
| 12655429ac | |||
| 9f49a1b865 | |||
| 774e3c953f | |||
| 5bfc380a37 | |||
| b6888ca5da | |||
| 2f4920cc27 | |||
| 64222fb7a8 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -46,3 +46,5 @@ Thumbs.db
|
|||||||
# Temporary files
|
# Temporary files
|
||||||
tmp/
|
tmp/
|
||||||
temp/
|
temp/
|
||||||
|
|
||||||
|
dist/
|
||||||
|
|||||||
130
AUTOSTART_README.md
Normal file
130
AUTOSTART_README.md
Normal 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.
|
||||||
110
LOGGING_README.md
Normal file
110
LOGGING_README.md
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
# Medi-WOL Logging-System
|
||||||
|
|
||||||
|
## Übersicht
|
||||||
|
|
||||||
|
Das Medi-WOL Logging-System protokolliert alle Wake-on-LAN (WOL) Ereignisse in der Datenbank und bietet verschiedene Möglichkeiten, diese Logs anzuzeigen und zu verwalten.
|
||||||
|
|
||||||
|
## Funktionen
|
||||||
|
|
||||||
|
### 1. Automatisches Logging
|
||||||
|
- **WOL-Ereignisse**: Jedes Mal, wenn ein PC über die Web-Oberfläche aufgeweckt wird, wird automatisch ein Log-Eintrag erstellt
|
||||||
|
- **Auslöser-Tracking**: Unterscheidung zwischen manuellen Button-Klicks und automatischen Cron-Jobs
|
||||||
|
- **Vollständige Protokollierung**: Timestamp, PC-Name, MAC-Adresse und Auslöser werden gespeichert
|
||||||
|
|
||||||
|
### 2. Log-Anzeige
|
||||||
|
- **Dedizierte Logs-Seite**: Übersicht aller WOL-Ereignisse unter `/logs`
|
||||||
|
- **Tooltips**: Zeigt die letzten 5 WOL-Ereignisse für jeden PC als Tooltip über der Tabellenzeile
|
||||||
|
- **Sortierung**: Logs werden nach Zeitstempel sortiert (neueste zuerst)
|
||||||
|
|
||||||
|
### 3. API-Endpunkte
|
||||||
|
- `GET /api/logs` - Alle Log-Einträge abrufen
|
||||||
|
- `GET /api/logs/pc/:id` - Alle Logs für einen bestimmten PC
|
||||||
|
- `GET /api/logs/pc/:id/recent` - Die letzten 5 Logs für einen bestimmten PC
|
||||||
|
|
||||||
|
## Datenbankstruktur
|
||||||
|
|
||||||
|
### Tabelle: `wol_logs`
|
||||||
|
```sql
|
||||||
|
CREATE TABLE 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
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Felder
|
||||||
|
- **id**: Eindeutige ID des Log-Eintrags
|
||||||
|
- **timestamp**: Zeitstempel des WOL-Ereignisses
|
||||||
|
- **pc_id**: Referenz auf den PC (Foreign Key)
|
||||||
|
- **pc_name**: Name des PCs (für bessere Lesbarkeit)
|
||||||
|
- **mac**: MAC-Adresse des PCs
|
||||||
|
- **trigger**: Auslöser des WOL-Ereignisses ("button" oder "cron")
|
||||||
|
|
||||||
|
## Verwendung
|
||||||
|
|
||||||
|
### 1. Logs-Seite aufrufen
|
||||||
|
Navigieren Sie zu `http://localhost:8080/logs` um alle WOL-Logs anzuzeigen.
|
||||||
|
|
||||||
|
### 2. Tooltips verwenden
|
||||||
|
- Bewegen Sie den Mauszeiger über eine PC-Zeile in der Haupttabelle
|
||||||
|
- Ein Tooltip zeigt die letzten 5 WOL-Ereignisse für diesen PC an
|
||||||
|
|
||||||
|
### 3. API verwenden
|
||||||
|
```javascript
|
||||||
|
// Alle Logs abrufen
|
||||||
|
const response = await fetch('/api/logs');
|
||||||
|
const logs = await response.json();
|
||||||
|
|
||||||
|
// Logs für einen bestimmten PC abrufen
|
||||||
|
const pcLogs = await fetch(`/api/logs/pc/${pcId}`);
|
||||||
|
const logs = await pcLogs.json();
|
||||||
|
|
||||||
|
// Letzte 5 Logs für einen PC abrufen
|
||||||
|
const recentLogs = await fetch(`/api/logs/pc/${pcId}/recent`);
|
||||||
|
const logs = await recentLogs.json();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Erweiterungen
|
||||||
|
|
||||||
|
### Cron-Job Logging
|
||||||
|
Das System ist vorbereitet für automatische WOL-Ereignisse über Cron-Jobs. Diese würden mit dem Trigger "cron" protokolliert werden.
|
||||||
|
|
||||||
|
### Log-Bereinigung
|
||||||
|
Für Produktionsumgebungen sollte eine Log-Bereinigung implementiert werden, um alte Log-Einträge nach einem bestimmten Zeitraum zu löschen.
|
||||||
|
|
||||||
|
### Export-Funktionalität
|
||||||
|
Zukünftige Versionen könnten CSV- oder PDF-Export der Logs bieten.
|
||||||
|
|
||||||
|
## Technische Details
|
||||||
|
|
||||||
|
### Implementierung
|
||||||
|
- **Backend**: Go mit Gin-Framework
|
||||||
|
- **Datenbank**: SQLite mit Foreign Key Constraints
|
||||||
|
- **Frontend**: HTML, CSS, JavaScript mit Bootstrap
|
||||||
|
- **Tooltips**: Bootstrap Tooltip-System mit HTML-Inhalt
|
||||||
|
|
||||||
|
### Sicherheit
|
||||||
|
- HTML-Escaping für alle Benutzereingaben
|
||||||
|
- Validierung der API-Parameter
|
||||||
|
- Keine sensiblen Daten in den Logs
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
- Indizierte Datenbankabfragen
|
||||||
|
- Lazy Loading der Tooltips
|
||||||
|
- Begrenzte Anzahl von Log-Einträgen pro Tooltip (5)
|
||||||
|
|
||||||
|
## Fehlerbehebung
|
||||||
|
|
||||||
|
### Häufige Probleme
|
||||||
|
1. **Tooltips werden nicht angezeigt**: Überprüfen Sie, ob Bootstrap korrekt geladen ist
|
||||||
|
2. **Logs werden nicht gespeichert**: Überprüfen Sie die Datenbankverbindung und -berechtigungen
|
||||||
|
3. **Fehler beim Laden der Logs**: Überprüfen Sie die API-Endpunkte und die Datenbankstruktur
|
||||||
|
|
||||||
|
### Debugging
|
||||||
|
- Überprüfen Sie die Browser-Konsole auf JavaScript-Fehler
|
||||||
|
- Überprüfen Sie die Server-Logs auf Backend-Fehler
|
||||||
|
- Testen Sie die API-Endpunkte direkt mit einem HTTP-Client
|
||||||
220
README.md
220
README.md
@ -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
|
- **Wake-on-LAN**: Ein-Klick-Aufwecken von Computern über MAC-Adressen
|
||||||
- **IP-Adressverwaltung**: Pro Gerät wird eine IP-Adresse gespeichert
|
- **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
|
- **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
|
- **Moderne Web-Oberfläche**: Responsive Design mit Bootstrap und FontAwesome
|
||||||
- **SQLite-Datenbank**: Einfache lokale Datenspeicherung
|
- **SQLite-Datenbank**: Einfache lokale Datenspeicherung
|
||||||
- **Cross-Platform**: Läuft auf Windows und Linux
|
- **Cross-Platform**: Läuft auf Windows und Linux
|
||||||
@ -15,7 +18,7 @@ Ein moderner Wake-on-LAN Manager, entwickelt mit Go und einer schönen Web-Oberf
|
|||||||
|
|
||||||
## Voraussetzungen
|
## Voraussetzungen
|
||||||
|
|
||||||
- Go 1.21 oder höher
|
- Go 1.18 oder höher
|
||||||
- Git
|
- Git
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
@ -114,8 +117,62 @@ Falls weder Kommandozeilenparameter noch Umgebungsvariable gesetzt sind, wird Po
|
|||||||
- Klicken Sie auf den "Löschen"-Button neben dem gewünschten PC
|
- Klicken Sie auf den "Löschen"-Button neben dem gewünschten PC
|
||||||
- Bestätigen Sie die Löschung
|
- 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
|
||||||
|
Der Windows Installer (`medi-wol-setup.exe`) bietet:
|
||||||
|
- **Automatische Service-Installation** mit NSSM
|
||||||
|
- **Port-Konfiguration** während der Installation (Standard: 9000)
|
||||||
|
- **Windows Firewall-Regeln** werden automatisch erstellt:
|
||||||
|
- Eingehende Verbindungen zur Anwendung
|
||||||
|
- Ausgehende Verbindungen (für Wake-on-LAN)
|
||||||
|
- Port-spezifische TCP-Regeln
|
||||||
|
- **Service-Start-Option** (Checkbox für automatischen Start)
|
||||||
|
- **Saubere Deinstallation** mit Entfernung aller Firewall-Regeln
|
||||||
|
|
||||||
|
### Voraussetzungen für den Build
|
||||||
|
```bash
|
||||||
|
# NSSM in das Installer-Verzeichnis kopieren
|
||||||
|
copy "I:\MARKUS\beszel\nssm.exe" installer\nssm.exe
|
||||||
|
|
||||||
|
# Oder von der offiziellen Quelle herunterladen
|
||||||
|
# https://nssm.cc/download
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build des Installers
|
||||||
|
```bash
|
||||||
|
# Mit build.bat (automatisch)
|
||||||
|
.\build.bat
|
||||||
|
|
||||||
|
# Oder manuell mit Inno Setup Compiler
|
||||||
|
ISCC.exe installer\medi-wol-setup.iss
|
||||||
|
```
|
||||||
|
|
||||||
## Build-Anweisungen
|
## Build-Anweisungen
|
||||||
|
|
||||||
|
> **Wichtig**: Das Projekt verwendet Go 1.18 und kompatible Abhängigkeiten. Für neuere Go-Versionen müssen die Abhängigkeiten entsprechend angepasst werden.
|
||||||
|
|
||||||
### Windows Build
|
### Windows Build
|
||||||
|
|
||||||
#### Lokaler Build (Windows)
|
#### Lokaler Build (Windows)
|
||||||
@ -251,18 +308,30 @@ medi-wol/
|
|||||||
│ ├── database/ # Datenbanklogik
|
│ ├── database/ # Datenbanklogik
|
||||||
│ ├── handlers/ # HTTP-Handler
|
│ ├── handlers/ # HTTP-Handler
|
||||||
│ ├── models/ # Datenmodelle
|
│ ├── models/ # Datenmodelle
|
||||||
|
│ ├── scheduler/ # Autostart-Scheduler für WOL-Ereignisse
|
||||||
│ └── wol/ # Wake-on-LAN Service
|
│ └── wol/ # Wake-on-LAN Service
|
||||||
├── web/ # Web-Oberfläche
|
├── web/ # Web-Oberfläche
|
||||||
│ ├── static/ # CSS, JavaScript
|
│ ├── static/ # CSS, JavaScript
|
||||||
│ └── templates/ # HTML-Templates
|
│ └── templates/ # HTML-Templates
|
||||||
|
├── installer/ # Windows Installer
|
||||||
|
│ ├── medi-wol-setup.iss # Inno Setup Script
|
||||||
|
│ └── nssm.exe # NSSM für Service-Management
|
||||||
├── go.mod # Go-Module
|
├── go.mod # Go-Module
|
||||||
├── build.bat # Windows Build-Skript
|
├── build.bat # Windows Build-Skript (inkl. Installer)
|
||||||
├── build.sh # Linux Build-Skript
|
├── build.sh # Linux Build-Skript
|
||||||
|
├── AUTOSTART_README.md # Dokumentation des Autostart-Systems
|
||||||
|
├── LOGGING_README.md # Dokumentation des Logging-Systems
|
||||||
└── README.md # Diese Datei
|
└── README.md # Diese Datei
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Installer-Verzeichnis
|
||||||
|
Das `installer/` Verzeichnis enthält alle Dateien für den Windows Installer:
|
||||||
|
- **`medi-wol-setup.iss`** - Inno Setup Konfigurationsdatei
|
||||||
|
- **`nssm.exe`** - NSSM (Non-Sucking Service Manager) für Windows Services
|
||||||
|
|
||||||
## API-Endpunkte
|
## API-Endpunkte
|
||||||
|
|
||||||
|
### PC-Management
|
||||||
- `GET /` - Hauptseite
|
- `GET /` - Hauptseite
|
||||||
- `GET /api/pcs` - Alle PCs abrufen
|
- `GET /api/pcs` - Alle PCs abrufen
|
||||||
- `POST /api/pcs` - Neuen PC erstellen
|
- `POST /api/pcs` - Neuen PC erstellen
|
||||||
@ -271,19 +340,53 @@ medi-wol/
|
|||||||
- `POST /api/pcs/:id/wake` - PC aufwecken
|
- `POST /api/pcs/:id/wake` - PC aufwecken
|
||||||
- `GET /api/pcs/status` - Online-Status aller PCs abrufen (Ping)
|
- `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
|
## Datenbank
|
||||||
|
|
||||||
Die Anwendung verwendet SQLite als lokale Datenbank. Die Datenbankdatei `medi-wol.db` wird automatisch im Projektverzeichnis erstellt.
|
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`
|
### Tabellenstruktur `pcs`
|
||||||
| Spalte | Typ | Hinweis |
|
| Spalte | Typ | Hinweis |
|
||||||
|-------------|----------|------------------------|
|
|------------------|----------|--------------------------------------------|
|
||||||
| id | INTEGER | Primärschlüssel |
|
| id | INTEGER | Primärschlüssel |
|
||||||
| name | TEXT | Pflichtfeld |
|
| name | TEXT | Pflichtfeld |
|
||||||
| mac | TEXT | Pflichtfeld, eindeutig |
|
| mac | TEXT | Pflichtfeld, eindeutig |
|
||||||
| ip | TEXT | Pflichtfeld |
|
| ip | TEXT | Pflichtfeld |
|
||||||
| created_at | DATETIME | Automatisch |
|
| autostart_cron | TEXT | Crontab-Syntax für Autostart (Standard: `30 7 * * Mon-Fri`) |
|
||||||
| updated_at | DATETIME | Automatisch |
|
| 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
|
## Wake-on-LAN
|
||||||
|
|
||||||
@ -317,6 +420,11 @@ go build -ldflags="-s -w" -o medi-wol.exe cmd/server/main.go
|
|||||||
|
|
||||||
# Linux
|
# Linux
|
||||||
go build -ldflags="-s -w" -o medi-wol cmd/server/main.go
|
go build -ldflags="-s -w" -o medi-wol cmd/server/main.go
|
||||||
|
|
||||||
|
# Multi-Platform Build (empfohlen)
|
||||||
|
./build.sh # Linux/macOS
|
||||||
|
# oder
|
||||||
|
.\build.bat # Windows
|
||||||
```
|
```
|
||||||
|
|
||||||
### Tests ausführen
|
### Tests ausführen
|
||||||
@ -336,17 +444,61 @@ golint ./...
|
|||||||
go vet ./...
|
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
|
## Deployment
|
||||||
|
|
||||||
### Windows Service
|
### Windows Service
|
||||||
|
|
||||||
|
#### Mit NSSM (Non-Sucking Service Manager)
|
||||||
```bash
|
```bash
|
||||||
# Mit NSSM (Non-Sucking Service Manager)
|
# Service installieren
|
||||||
nssm install Medi-WOL "C:\path\to\medi-wol.exe"
|
nssm install Medi-WOL "C:\path\to\medi-wol.exe"
|
||||||
nssm set Medi-WOL AppDirectory "C:\path\to\medi-wol"
|
nssm set Medi-WOL AppDirectory "C:\path\to\medi-wol"
|
||||||
nssm set Medi-WOL AppParameters "-port 9090" # Optional: Port setzen
|
nssm set Medi-WOL AppParameters "-port 9090" # Optional: Port setzen
|
||||||
nssm start Medi-WOL
|
nssm start Medi-WOL
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Windows Installer (Empfohlen)
|
||||||
|
Der Windows Installer konfiguriert den Service automatisch mit NSSM:
|
||||||
|
- **Automatische Service-Installation** mit NSSM
|
||||||
|
- **Windows Firewall-Regeln** werden automatisch erstellt
|
||||||
|
- **Port-Konfiguration** während der Installation
|
||||||
|
- **Service-Start-Option** (Checkbox für automatischen Start)
|
||||||
|
|
||||||
|
**Wichtig:** Für den Windows Installer muss `nssm.exe` im `installer/` Verzeichnis verfügbar sein:
|
||||||
|
```bash
|
||||||
|
# NSSM in das Installer-Verzeichnis kopieren
|
||||||
|
copy "I:\MARKUS\beszel\nssm.exe" installer\nssm.exe
|
||||||
|
|
||||||
|
# Oder von der offiziellen Quelle herunterladen
|
||||||
|
# https://nssm.cc/download
|
||||||
|
```
|
||||||
|
|
||||||
### Linux Systemd Service
|
### Linux Systemd Service
|
||||||
```ini
|
```ini
|
||||||
# /etc/systemd/system/medi-wol.service
|
# /etc/systemd/system/medi-wol.service
|
||||||
@ -391,49 +543,3 @@ docker run -d -p 9090:9090 -e PORT=9090 medi-wol:latest
|
|||||||
|
|
||||||
Dieses Projekt ist für den internen Gebrauch bestimmt.
|
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
|
|
||||||
```
|
|
||||||
|
|||||||
@ -6,9 +6,12 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"medi-wol/internal/database"
|
"medi-wol/internal/database"
|
||||||
"medi-wol/internal/handlers"
|
"medi-wol/internal/handlers"
|
||||||
|
"medi-wol/internal/scheduler"
|
||||||
"medi-wol/internal/wol"
|
"medi-wol/internal/wol"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@ -45,21 +48,45 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer db.Close()
|
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
|
// Wake-on-LAN Service initialisieren
|
||||||
wolService := wol.NewService()
|
wolService := wol.NewService()
|
||||||
|
|
||||||
|
// Scheduler initialisieren und starten
|
||||||
|
scheduler := scheduler.NewScheduler(db, wolService)
|
||||||
|
scheduler.Start()
|
||||||
|
defer scheduler.Stop()
|
||||||
|
|
||||||
// Handler initialisieren
|
// Handler initialisieren
|
||||||
pcHandler := handlers.NewPCHandler(db, wolService)
|
pcHandler := handlers.NewPCHandler(db, wolService)
|
||||||
|
|
||||||
// Gin Router konfigurieren
|
// Gin Router konfigurieren
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
|
|
||||||
// Statische Dateien bereitstellen
|
// HTML-Templates laden
|
||||||
r.Static("/static", "./web/static")
|
|
||||||
r.LoadHTMLGlob("web/templates/*")
|
r.LoadHTMLGlob("web/templates/*")
|
||||||
|
|
||||||
// Routen definieren
|
// Routen definieren
|
||||||
r.GET("/", pcHandler.Index)
|
r.GET("/", pcHandler.Index)
|
||||||
|
r.GET("/logs", pcHandler.Logs)
|
||||||
r.GET("/api/pcs", pcHandler.GetAllPCs)
|
r.GET("/api/pcs", pcHandler.GetAllPCs)
|
||||||
r.POST("/api/pcs", pcHandler.CreatePC)
|
r.POST("/api/pcs", pcHandler.CreatePC)
|
||||||
r.PUT("/api/pcs/:id", pcHandler.UpdatePC)
|
r.PUT("/api/pcs/:id", pcHandler.UpdatePC)
|
||||||
@ -67,12 +94,34 @@ func main() {
|
|||||||
r.POST("/api/pcs/:id/wake", pcHandler.WakePC)
|
r.POST("/api/pcs/:id/wake", pcHandler.WakePC)
|
||||||
r.GET("/api/pcs/status", pcHandler.GetPCStatus)
|
r.GET("/api/pcs/status", pcHandler.GetPCStatus)
|
||||||
|
|
||||||
// Server starten
|
// Log-Routen
|
||||||
serverAddr := fmt.Sprintf(":%d", port)
|
r.GET("/api/logs", pcHandler.GetAllLogs)
|
||||||
log.Printf("Medi-WOL startet auf Port %d...", port)
|
r.GET("/api/logs/pc/:id", pcHandler.GetLogsByPCID)
|
||||||
log.Printf("Web-Oberfläche verfügbar unter: http://localhost%s", serverAddr)
|
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)
|
||||||
|
|
||||||
if err := r.Run(serverAddr); err != nil {
|
// Statische Dateien bereitstellen (nach den spezifischen Routen)
|
||||||
log.Fatal("Fehler beim Starten des Servers:", err)
|
r.Static("/static", "./web/static")
|
||||||
}
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
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...")
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
dist/medi-wol-linux-amd64
vendored
BIN
dist/medi-wol-linux-amd64
vendored
Binary file not shown.
BIN
dist/medi-wol-linux-arm64
vendored
BIN
dist/medi-wol-linux-arm64
vendored
Binary file not shown.
4
go.mod
4
go.mod
@ -1,10 +1,10 @@
|
|||||||
module medi-wol
|
module medi-wol
|
||||||
|
|
||||||
go 1.23.0
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
golang.org/x/sys v0.35.0
|
golang.org/x/sys v0.15.0
|
||||||
modernc.org/sqlite v1.28.0
|
modernc.org/sqlite v1.28.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
13
go.sum
13
go.sum
@ -16,7 +16,6 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm
|
|||||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
@ -28,10 +27,8 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
|
|||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
@ -46,7 +43,6 @@ github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNa
|
|||||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@ -83,11 +79,10 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|||||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||||
@ -110,9 +105,7 @@ modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
|||||||
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
||||||
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
||||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
||||||
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
|
||||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
|
||||||
modernc.org/libc v1.29.0 h1:tTFRFq69YKCF2QyGNuRUQxKBm1uZZLubf6Cjh/pVHXs=
|
modernc.org/libc v1.29.0 h1:tTFRFq69YKCF2QyGNuRUQxKBm1uZZLubf6Cjh/pVHXs=
|
||||||
modernc.org/libc v1.29.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ=
|
modernc.org/libc v1.29.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ=
|
||||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||||
@ -126,9 +119,7 @@ modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0
|
|||||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||||
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
|
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
|
||||||
modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c=
|
|
||||||
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
|
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
|
||||||
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
|
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
|
||||||
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
|
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
|
|||||||
@ -21,20 +21,20 @@ AllowNoIcons=yes
|
|||||||
LicenseFile=..\LICENSE
|
LicenseFile=..\LICENSE
|
||||||
OutputDir=..\dist
|
OutputDir=..\dist
|
||||||
OutputBaseFilename=medi-wol-setup
|
OutputBaseFilename=medi-wol-setup
|
||||||
|
SetupIconFile=medi-wol.ico
|
||||||
|
UninstallDisplayIcon={app}\{#MyAppExeName}
|
||||||
Compression=lzma
|
Compression=lzma
|
||||||
SolidCompression=yes
|
SolidCompression=yes
|
||||||
WizardStyle=modern
|
WizardStyle=modern
|
||||||
PrivilegesRequired=admin
|
PrivilegesRequired=admin
|
||||||
ArchitecturesAllowed=x64
|
ArchitecturesAllowed=x64os
|
||||||
ArchitecturesInstallIn64BitMode=x64
|
ArchitecturesInstallIn64BitMode=x64os
|
||||||
|
|
||||||
[Languages]
|
[Languages]
|
||||||
Name: "german"; MessagesFile: "compiler:Languages\German.isl"
|
Name: "german"; MessagesFile: "compiler:Languages\German.isl"
|
||||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
|
||||||
[Tasks]
|
[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}"
|
Name: "startservice"; Description: "{cm:StartServiceAfterInstall}"; GroupDescription: "{cm:ServiceOptions}"
|
||||||
|
|
||||||
[Files]
|
[Files]
|
||||||
@ -46,10 +46,8 @@ Source: "..\LICENSE"; DestDir: "{app}"; Flags: ignoreversion
|
|||||||
Source: "..\README.md"; DestDir: "{app}"; Flags: ignoreversion
|
Source: "..\README.md"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
|
||||||
[Icons]
|
[Icons]
|
||||||
Name: "{group}\{#MyAppName}"; Filename: "{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: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
|
||||||
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
|
||||||
Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon
|
|
||||||
|
|
||||||
[Run]
|
[Run]
|
||||||
; Installiere Medi-WOL als Windows-Dienst mit NSSM
|
; Installiere Medi-WOL als Windows-Dienst mit NSSM
|
||||||
@ -74,6 +72,8 @@ Filename: "{app}\nssm.exe"; Parameters: "set ""{#MyAppServiceName}"" AppStopMeth
|
|||||||
Filename: "{app}\nssm.exe"; Parameters: "set ""{#MyAppServiceName}"" AppStopMethodThreads 1500"; Flags: runhidden
|
Filename: "{app}\nssm.exe"; Parameters: "set ""{#MyAppServiceName}"" AppStopMethodThreads 1500"; Flags: runhidden
|
||||||
; Service nach Installation starten mit Verzögerung
|
; Service nach Installation starten mit Verzögerung
|
||||||
Filename: "{app}\nssm.exe"; Parameters: "start ""{#MyAppServiceName}"""; StatusMsg: "Starte Medi-WOL Dienst..."; Flags: runhidden waituntilterminated; Check: ShouldStartService
|
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
|
; 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} - 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
|
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
|
||||||
@ -81,13 +81,13 @@ Filename: "netsh.exe"; Parameters: "advfirewall firewall add rule name=""{#MyApp
|
|||||||
|
|
||||||
[UninstallRun]
|
[UninstallRun]
|
||||||
; Stoppe den Windows-Dienst vor der Deinstallation
|
; 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
|
; 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
|
; 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} - Inbound"""; Flags: runhidden; RunOnceId: "RemoveFirewallInbound"
|
||||||
Filename: "netsh.exe"; Parameters: "advfirewall firewall delete rule name=""{#MyAppServiceName} - Outbound"""; Flags: runhidden
|
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
|
Filename: "netsh.exe"; Parameters: "advfirewall firewall delete rule name=""{#MyAppServiceName} - Port {code:GetPort}"""; Flags: runhidden; RunOnceId: "RemoveFirewallPort"
|
||||||
|
|
||||||
[Code]
|
[Code]
|
||||||
var
|
var
|
||||||
@ -148,9 +148,6 @@ end;
|
|||||||
|
|
||||||
|
|
||||||
[CustomMessages]
|
[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.ServiceOptions=Service-Optionen:
|
||||||
german.StartServiceAfterInstall=Service nach der Installation starten
|
german.StartServiceAfterInstall=Service nach der Installation starten
|
||||||
german.UninstallProgram=Medi-WOL entfernen
|
german.UninstallProgram=Medi-WOL entfernen
|
||||||
|
|||||||
BIN
installer/medi-wol.ico
Normal file
BIN
installer/medi-wol.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@ -21,22 +21,94 @@ func InitDB() (*DB, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tabelle erstellen
|
// PC-Tabelle erstellen
|
||||||
createTableSQL := `
|
createPCTableSQL := `
|
||||||
CREATE TABLE IF NOT EXISTS pcs (
|
CREATE TABLE IF NOT EXISTS pcs (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
mac TEXT NOT NULL UNIQUE,
|
mac TEXT NOT NULL UNIQUE,
|
||||||
ip TEXT NOT NULL,
|
ip TEXT NOT NULL,
|
||||||
|
autostart_cron TEXT DEFAULT '30 7 * * Mon-Fri',
|
||||||
|
autostart_enabled BOOLEAN DEFAULT 0,
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
);`
|
);`
|
||||||
|
|
||||||
_, err = db.Exec(createTableSQL)
|
_, err = db.Exec(createPCTableSQL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 (
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// Füge IP-Spalte hinzu, falls sie nicht existiert
|
||||||
_, err = db.Exec("ALTER TABLE pcs ADD COLUMN ip TEXT DEFAULT ''")
|
_, err = db.Exec("ALTER TABLE pcs ADD COLUMN ip TEXT DEFAULT ''")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -49,7 +121,7 @@ func InitDB() (*DB, error) {
|
|||||||
|
|
||||||
// GetAllPCs holt alle PCs aus der Datenbank
|
// GetAllPCs holt alle PCs aus der Datenbank
|
||||||
func (db *DB) GetAllPCs() ([]models.PC, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -58,7 +130,7 @@ func (db *DB) GetAllPCs() ([]models.PC, error) {
|
|||||||
var pcs []models.PC
|
var pcs []models.PC
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var pc models.PC
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -69,11 +141,11 @@ func (db *DB) GetAllPCs() ([]models.PC, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreatePC erstellt einen neuen PC-Eintrag
|
// 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()
|
now := time.Now()
|
||||||
result, err := db.Exec(
|
result, err := db.Exec(
|
||||||
"INSERT INTO pcs (name, mac, ip, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
|
"INSERT INTO pcs (name, mac, ip, autostart_cron, autostart_enabled, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||||
name, mac, ip, now, now,
|
name, mac, ip, autostartCron, autostartEnabled, now, now,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -85,32 +157,36 @@ func (db *DB) CreatePC(name, mac, ip string) (*models.PC, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &models.PC{
|
return &models.PC{
|
||||||
ID: int(id),
|
ID: int(id),
|
||||||
Name: name,
|
Name: name,
|
||||||
MAC: mac,
|
MAC: mac,
|
||||||
IP: ip,
|
IP: ip,
|
||||||
CreatedAt: now,
|
AutostartCron: autostartCron,
|
||||||
UpdatedAt: now,
|
AutostartEnabled: autostartEnabled,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePC aktualisiert einen bestehenden PC-Eintrag
|
// 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()
|
now := time.Now()
|
||||||
_, err := db.Exec(
|
_, err := db.Exec(
|
||||||
"UPDATE pcs SET name = ?, mac = ?, ip = ?, updated_at = ? WHERE id = ?",
|
"UPDATE pcs SET name = ?, mac = ?, ip = ?, autostart_cron = ?, autostart_enabled = ?, updated_at = ? WHERE id = ?",
|
||||||
name, mac, ip, now, id,
|
name, mac, ip, autostartCron, autostartEnabled, now, id,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &models.PC{
|
return &models.PC{
|
||||||
ID: id,
|
ID: id,
|
||||||
Name: name,
|
Name: name,
|
||||||
MAC: mac,
|
MAC: mac,
|
||||||
IP: ip,
|
IP: ip,
|
||||||
UpdatedAt: now,
|
AutostartCron: autostartCron,
|
||||||
|
AutostartEnabled: autostartEnabled,
|
||||||
|
UpdatedAt: now,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,9 +200,9 @@ func (db *DB) DeletePC(id int) error {
|
|||||||
func (db *DB) GetPCByID(id int) (*models.PC, error) {
|
func (db *DB) GetPCByID(id int) (*models.PC, error) {
|
||||||
var pc models.PC
|
var pc models.PC
|
||||||
err := db.QueryRow(
|
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,
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -134,3 +210,142 @@ func (db *DB) GetPCByID(id int) (*models.PC, error) {
|
|||||||
|
|
||||||
return &pc, nil
|
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()
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"medi-wol/internal/database"
|
"medi-wol/internal/database"
|
||||||
"medi-wol/internal/models"
|
"medi-wol/internal/models"
|
||||||
"medi-wol/internal/ping"
|
"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
|
// GetAllPCs gibt alle gespeicherten PCs zurück
|
||||||
func (h *PCHandler) GetAllPCs(c *gin.Context) {
|
func (h *PCHandler) GetAllPCs(c *gin.Context) {
|
||||||
pcs, err := h.db.GetAllPCs()
|
pcs, err := h.db.GetAllPCs()
|
||||||
@ -71,8 +79,13 @@ func (h *PCHandler) CreatePC(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Standardwert für Autostart-Cron setzen, falls nicht angegeben
|
||||||
|
if req.AutostartCron == "" {
|
||||||
|
req.AutostartCron = "30 7 * * Mon-Fri"
|
||||||
|
}
|
||||||
|
|
||||||
// PC erstellen
|
// 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 {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, models.PCResponse{
|
c.JSON(http.StatusInternalServerError, models.PCResponse{
|
||||||
Success: false,
|
Success: false,
|
||||||
@ -118,8 +131,13 @@ func (h *PCHandler) UpdatePC(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Standardwert für Autostart-Cron setzen, falls nicht angegeben
|
||||||
|
if req.AutostartCron == "" {
|
||||||
|
req.AutostartCron = "30 7 * * Mon-Fri"
|
||||||
|
}
|
||||||
|
|
||||||
// PC aktualisieren
|
// 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 {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, models.PCResponse{
|
c.JSON(http.StatusInternalServerError, models.PCResponse{
|
||||||
Success: false,
|
Success: false,
|
||||||
@ -194,6 +212,13 @@ func (h *PCHandler) WakePC(c *gin.Context) {
|
|||||||
return
|
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{
|
c.JSON(http.StatusOK, models.PCResponse{
|
||||||
Success: true,
|
Success: true,
|
||||||
Message: "Wake-on-LAN Paket erfolgreich gesendet an " + pc.Name,
|
Message: "Wake-on-LAN Paket erfolgreich gesendet an " + pc.Name,
|
||||||
@ -216,11 +241,13 @@ func (h *PCHandler) GetPCStatus(c *gin.Context) {
|
|||||||
for _, pc := range pcs {
|
for _, pc := range pcs {
|
||||||
online := h.pingService.IsOnline(pc.IP)
|
online := h.pingService.IsOnline(pc.IP)
|
||||||
statusList = append(statusList, models.PCStatus{
|
statusList = append(statusList, models.PCStatus{
|
||||||
ID: pc.ID,
|
ID: pc.ID,
|
||||||
Name: pc.Name,
|
Name: pc.Name,
|
||||||
MAC: pc.MAC,
|
MAC: pc.MAC,
|
||||||
IP: pc.IP,
|
IP: pc.IP,
|
||||||
Online: online,
|
Online: online,
|
||||||
|
AutostartCron: pc.AutostartCron,
|
||||||
|
AutostartEnabled: pc.AutostartEnabled,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,3 +256,126 @@ func (h *PCHandler) GetPCStatus(c *gin.Context) {
|
|||||||
"status": statusList,
|
"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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
31
internal/models/log.go
Normal file
31
internal/models/log.go
Normal 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"`
|
||||||
|
}
|
||||||
@ -6,26 +6,32 @@ import (
|
|||||||
|
|
||||||
// PC repräsentiert einen Computer in der Datenbank
|
// PC repräsentiert einen Computer in der Datenbank
|
||||||
type PC struct {
|
type PC struct {
|
||||||
ID int `json:"id" db:"id"`
|
ID int `json:"id" db:"id"`
|
||||||
Name string `json:"name" db:"name"`
|
Name string `json:"name" db:"name"`
|
||||||
MAC string `json:"mac" db:"mac"`
|
MAC string `json:"mac" db:"mac"`
|
||||||
IP string `json:"ip" db:"ip"`
|
IP string `json:"ip" db:"ip"`
|
||||||
CreatedAt time.Time `json:"created_at" db:"updated_at"`
|
AutostartCron string `json:"autostart_cron" db:"autostart_cron"`
|
||||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatePCRequest repräsentiert die Anfrage zum Erstellen eines neuen PCs
|
// CreatePCRequest repräsentiert die Anfrage zum Erstellen eines neuen PCs
|
||||||
type CreatePCRequest struct {
|
type CreatePCRequest struct {
|
||||||
Name string `json:"name" binding:"required"`
|
Name string `json:"name" binding:"required"`
|
||||||
MAC string `json:"mac" binding:"required"`
|
MAC string `json:"mac" binding:"required"`
|
||||||
IP string `json:"ip" 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
|
// UpdatePCRequest repräsentiert die Anfrage zum Aktualisieren eines PCs
|
||||||
type UpdatePCRequest struct {
|
type UpdatePCRequest struct {
|
||||||
Name string `json:"name" binding:"required"`
|
Name string `json:"name" binding:"required"`
|
||||||
MAC string `json:"mac" binding:"required"`
|
MAC string `json:"mac" binding:"required"`
|
||||||
IP string `json:"ip" 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
|
// PCResponse repräsentiert die Antwort für PC-Operationen
|
||||||
@ -38,9 +44,11 @@ type PCResponse struct {
|
|||||||
|
|
||||||
// PCStatus repräsentiert den Online-Status eines PCs
|
// PCStatus repräsentiert den Online-Status eines PCs
|
||||||
type PCStatus struct {
|
type PCStatus struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
MAC string `json:"mac"`
|
MAC string `json:"mac"`
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
Online bool `json:"online"`
|
Online bool `json:"online"`
|
||||||
|
AutostartCron string `json:"autostart_cron"`
|
||||||
|
AutostartEnabled bool `json:"autostart_enabled"`
|
||||||
}
|
}
|
||||||
|
|||||||
185
internal/scheduler/scheduler.go
Normal file
185
internal/scheduler/scheduler.go
Normal 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)
|
||||||
|
}
|
||||||
BIN
web/static/favicon-16x16.png
Normal file
BIN
web/static/favicon-16x16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 374 B |
BIN
web/static/favicon.ico
Normal file
BIN
web/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 630 B |
BIN
web/static/favicon.png
Normal file
BIN
web/static/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 630 B |
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 24 KiB |
80
web/static/logs.js
Normal file
80
web/static/logs.js
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// Logs-Seite JavaScript
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
loadLogs();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Alle Logs laden
|
||||||
|
async function loadLogs() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/logs');
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
displayLogs(data.logs);
|
||||||
|
} else {
|
||||||
|
showError('Fehler beim Laden der Logs: ' + data.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showError('Fehler beim Laden der Logs: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logs in der Tabelle anzeigen
|
||||||
|
function displayLogs(logs) {
|
||||||
|
const tableBody = document.getElementById('logsTableBody');
|
||||||
|
const loading = document.getElementById('loading');
|
||||||
|
const noLogs = document.getElementById('noLogs');
|
||||||
|
|
||||||
|
// Loading ausblenden
|
||||||
|
loading.style.display = 'none';
|
||||||
|
|
||||||
|
if (!logs || logs.length === 0) {
|
||||||
|
noLogs.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tabelle leeren
|
||||||
|
tableBody.innerHTML = '';
|
||||||
|
|
||||||
|
// Logs hinzufügen
|
||||||
|
logs.forEach(log => {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
|
||||||
|
// Zeitstempel formatieren
|
||||||
|
const timestamp = new Date(log.timestamp);
|
||||||
|
const formattedTime = timestamp.toLocaleString('de-DE', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
year: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auslöser übersetzen
|
||||||
|
const triggerText = log.trigger === 'button' ? 'Button-Klick' :
|
||||||
|
log.trigger === 'cron' ? 'Automatisch' : log.trigger;
|
||||||
|
|
||||||
|
row.innerHTML = `
|
||||||
|
<td>${formattedTime}</td>
|
||||||
|
<td>${escapeHtml(log.pc_name)}</td>
|
||||||
|
<td>${escapeHtml(log.mac)}</td>
|
||||||
|
<td>${escapeHtml(triggerText)}</td>
|
||||||
|
`;
|
||||||
|
|
||||||
|
tableBody.appendChild(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fehler anzeigen
|
||||||
|
function showError(message) {
|
||||||
|
const loading = document.getElementById('loading');
|
||||||
|
loading.innerHTML = `<p class="error">${escapeHtml(message)}</p>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML-Escaping für Sicherheit
|
||||||
|
function escapeHtml(text) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
@ -12,6 +12,7 @@ class PCManager {
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.loadPCs();
|
this.loadPCs();
|
||||||
|
this.loadVacationMode();
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
this.startAutoStatus();
|
this.startAutoStatus();
|
||||||
}
|
}
|
||||||
@ -47,6 +48,11 @@ class PCManager {
|
|||||||
document.getElementById('refreshStatusBtn').addEventListener('click', () => {
|
document.getElementById('refreshStatusBtn').addEventListener('click', () => {
|
||||||
this.refreshStatus();
|
this.refreshStatus();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Urlaubsmodus Checkbox
|
||||||
|
document.getElementById('vacationMode').addEventListener('change', (e) => {
|
||||||
|
this.setVacationMode(e.target.checked);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadPCs() {
|
async loadPCs() {
|
||||||
@ -145,7 +151,7 @@ class PCManager {
|
|||||||
pcList.style.display = 'block';
|
pcList.style.display = 'block';
|
||||||
|
|
||||||
tableBody.innerHTML = this.filteredPCs.map(pc => `
|
tableBody.innerHTML = this.filteredPCs.map(pc => `
|
||||||
<tr>
|
<tr data-pc-id="${pc.id}">
|
||||||
<td><strong>${this.escapeHtml(pc.name)}</strong></td>
|
<td><strong>${this.escapeHtml(pc.name)}</strong></td>
|
||||||
<td><code>${this.escapeHtml(pc.mac)}</code></td>
|
<td><code>${this.escapeHtml(pc.mac)}</code></td>
|
||||||
<td><code>${this.escapeHtml(pc.ip || 'N/A')}</code></td>
|
<td><code>${this.escapeHtml(pc.ip || 'N/A')}</code></td>
|
||||||
@ -157,12 +163,21 @@ class PCManager {
|
|||||||
</td>
|
</td>
|
||||||
<td>${new Date(pc.created_at).toLocaleDateString('de-DE')}</td>
|
<td>${new Date(pc.created_at).toLocaleDateString('de-DE')}</td>
|
||||||
<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">
|
<div class="btn-group" role="group">
|
||||||
<button class="btn btn-success btn-sm" onclick="pcManager.wakePC(${pc.id})"
|
<button class="btn btn-success btn-sm" onclick="pcManager.wakePC(${pc.id})"
|
||||||
title="PC aufwecken">
|
title="PC aufwecken">
|
||||||
<i class="fas fa-power-off"></i> Aufwecken
|
<i class="fas fa-power-off"></i> Aufwecken
|
||||||
</button>
|
</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">
|
title="PC bearbeiten">
|
||||||
<i class="fas fa-edit"></i> Bearbeiten
|
<i class="fas fa-edit"></i> Bearbeiten
|
||||||
</button>
|
</button>
|
||||||
@ -174,15 +189,20 @@ class PCManager {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|
||||||
|
// Tooltips für alle PC-Zeilen laden
|
||||||
|
this.loadTooltipsForAllPCs();
|
||||||
}
|
}
|
||||||
|
|
||||||
async addPC() {
|
async addPC() {
|
||||||
const name = document.getElementById('pcName').value.trim();
|
const name = document.getElementById('pcName').value.trim();
|
||||||
const mac = document.getElementById('macAddress').value.trim();
|
const mac = document.getElementById('macAddress').value.trim();
|
||||||
const ip = document.getElementById('ipAddress').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) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,7 +212,13 @@ class PCManager {
|
|||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'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();
|
const data = await response.json();
|
||||||
@ -209,12 +235,14 @@ class PCManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
editPC(id, name, mac, ip) {
|
editPC(id, name, mac, ip, autostartCron, autostartEnabled) {
|
||||||
// Modal mit PC-Daten füllen
|
// Modal mit PC-Daten füllen
|
||||||
document.getElementById('editPCId').value = id;
|
document.getElementById('editPCId').value = id;
|
||||||
document.getElementById('editPCName').value = name;
|
document.getElementById('editPCName').value = name;
|
||||||
document.getElementById('editMACAddress').value = mac;
|
document.getElementById('editMACAddress').value = mac;
|
||||||
document.getElementById('editIPAddress').value = ip;
|
document.getElementById('editIPAddress').value = ip;
|
||||||
|
document.getElementById('editAutostartCron').value = autostartCron || '30 7 * * Mon-Fri';
|
||||||
|
document.getElementById('editAutostartEnabled').checked = autostartEnabled || false;
|
||||||
|
|
||||||
// Modal öffnen
|
// Modal öffnen
|
||||||
const editModal = new bootstrap.Modal(document.getElementById('editPCModal'));
|
const editModal = new bootstrap.Modal(document.getElementById('editPCModal'));
|
||||||
@ -226,9 +254,11 @@ class PCManager {
|
|||||||
const name = document.getElementById('editPCName').value.trim();
|
const name = document.getElementById('editPCName').value.trim();
|
||||||
const mac = document.getElementById('editMACAddress').value.trim();
|
const mac = document.getElementById('editMACAddress').value.trim();
|
||||||
const ip = document.getElementById('editIPAddress').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) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,7 +268,13 @@ class PCManager {
|
|||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'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();
|
const data = await response.json();
|
||||||
@ -368,6 +404,122 @@ class PCManager {
|
|||||||
|
|
||||||
input.value = value.toUpperCase();
|
input.value = value.toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tooltip für alle PCs laden
|
||||||
|
async loadTooltipsForAllPCs() {
|
||||||
|
for (const pc of this.filteredPCs) {
|
||||||
|
await this.loadTooltipForPC(pc.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tooltip für einen bestimmten PC laden
|
||||||
|
async loadTooltipForPC(pcId) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/logs/pc/${pcId}/recent`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
const tooltipContent = this.createTooltipContent(data.logs);
|
||||||
|
const row = document.querySelector(`tr[data-pc-id="${pcId}"]`);
|
||||||
|
|
||||||
|
if (row) {
|
||||||
|
// Tooltip-Attribut setzen
|
||||||
|
row.setAttribute('data-bs-toggle', 'tooltip');
|
||||||
|
row.setAttribute('data-bs-html', 'true');
|
||||||
|
row.setAttribute('title', tooltipContent);
|
||||||
|
|
||||||
|
// Bootstrap Tooltip initialisieren
|
||||||
|
new bootstrap.Tooltip(row, {
|
||||||
|
placement: 'top',
|
||||||
|
trigger: 'hover',
|
||||||
|
html: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Fehler beim Laden des Tooltips für PC ${pcId}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tooltip-Inhalt erstellen
|
||||||
|
createTooltipContent(logs) {
|
||||||
|
if (!logs || logs.length === 0) {
|
||||||
|
return '<div class="text-muted">Keine WOL-Ereignisse</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = '<div class="text-start"><strong>Letzte WOL-Ereignisse:</strong><br>';
|
||||||
|
|
||||||
|
logs.forEach(log => {
|
||||||
|
const timestamp = new Date(log.timestamp).toLocaleString('de-DE', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
|
||||||
|
const triggerText = log.trigger === 'button' ? 'Button' :
|
||||||
|
log.trigger === 'cron' ? 'Auto' : log.trigger;
|
||||||
|
|
||||||
|
content += `• ${timestamp} (${triggerText})<br>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
// PC Manager initialisieren, wenn die Seite geladen ist
|
||||||
|
|||||||
@ -172,3 +172,150 @@ body {
|
|||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%);
|
background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Navigation */
|
||||||
|
.navbar {
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-link {
|
||||||
|
color: var(--brand-primary) !important;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 25px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-link:hover,
|
||||||
|
.navbar-nav .nav-link.active {
|
||||||
|
background: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-accent) 100%);
|
||||||
|
color: white !important;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Logs-Seite */
|
||||||
|
.logs-container {
|
||||||
|
position: relative;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-table-container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 15px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-table {
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-table th {
|
||||||
|
background: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-accent) 100%);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-table td {
|
||||||
|
padding: 12px 15px;
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-table tbody tr:hover {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px;
|
||||||
|
color: var(--brand-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
border: 4px solid #f3f3f3;
|
||||||
|
border-top: 4px solid var(--brand-primary);
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin: 0 auto 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-logs {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: #dc3545;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Verbesserte Tooltips */
|
||||||
|
.tooltip {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-inner {
|
||||||
|
background: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-accent) 100%);
|
||||||
|
color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
max-width: 300px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-arrow::before {
|
||||||
|
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;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-header h2 {
|
||||||
|
color: var(--brand-primary);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-header p {
|
||||||
|
color: #6c757d;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|||||||
@ -4,6 +4,11 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{{.title}}</title>
|
<title>{{.title}}</title>
|
||||||
|
<!-- Favicon -->
|
||||||
|
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicon.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png">
|
||||||
|
<!-- Stylesheets -->
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||||
<link href="/static/style.css" rel="stylesheet">
|
<link href="/static/style.css" rel="stylesheet">
|
||||||
@ -16,6 +21,40 @@
|
|||||||
<img src="/static/logo.png" alt="medisoftware Logo" />
|
<img src="/static/logo.png" alt="medisoftware Logo" />
|
||||||
<h1>{{.title}}</h1>
|
<h1>{{.title}}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-4">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" href="/">
|
||||||
|
<i class="fas fa-desktop"></i> PCs
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/logs">
|
||||||
|
<i class="fas fa-list-alt"></i> Logs
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -52,6 +91,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<button type="submit" class="btn btn-primary">
|
||||||
<i class="fas fa-save"></i> PC hinzufügen
|
<i class="fas fa-save"></i> PC hinzufügen
|
||||||
</button>
|
</button>
|
||||||
@ -153,6 +217,25 @@
|
|||||||
<input type="text" class="form-control" id="editIPAddress"
|
<input type="text" class="form-control" id="editIPAddress"
|
||||||
placeholder="192.168.1.100" required>
|
placeholder="192.168.1.100" required>
|
||||||
</div>
|
</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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
|||||||
89
web/templates/logs.html
Normal file
89
web/templates/logs.html
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Medi-WOL - Logs</title>
|
||||||
|
<!-- Favicon -->
|
||||||
|
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicon.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png">
|
||||||
|
<!-- Stylesheets -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||||
|
<link href="/static/style.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container mt-4">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="app-header mb-4">
|
||||||
|
<img src="/static/logo.png" alt="medisoftware Logo" />
|
||||||
|
<h1>Medi-WOL</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-4">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/">
|
||||||
|
<i class="fas fa-desktop"></i> PCs
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" href="/logs">
|
||||||
|
<i class="fas fa-list-alt"></i> Logs
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fas fa-list-alt"></i> Wake-on-LAN Logs
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="logs-container">
|
||||||
|
<div class="logs-table-container">
|
||||||
|
<table class="logs-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Zeitstempel</th>
|
||||||
|
<th>PC-Name</th>
|
||||||
|
<th>MAC-Adresse</th>
|
||||||
|
<th>Auslöser</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="logsTableBody">
|
||||||
|
<!-- Log-Einträge werden hier dynamisch eingefügt -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="loading" id="loading">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<p>Lade Logs...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="no-logs" id="noLogs" style="display: none;">
|
||||||
|
<p>Keine Log-Einträge gefunden.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/static/logs.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user