Autostart-Funktionalität implementiert: Crontab-Syntax, Scheduler und UI-Integration
This commit is contained in:
@@ -28,6 +28,8 @@ func InitDB() (*DB, error) {
|
||||
name TEXT NOT NULL,
|
||||
mac TEXT NOT NULL UNIQUE,
|
||||
ip TEXT NOT NULL,
|
||||
autostart_cron TEXT DEFAULT '30 7 * * Mon-Fri',
|
||||
autostart_enabled BOOLEAN DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);`
|
||||
@@ -66,7 +68,7 @@ func InitDB() (*DB, error) {
|
||||
|
||||
// GetAllPCs holt alle PCs aus der Datenbank
|
||||
func (db *DB) GetAllPCs() ([]models.PC, error) {
|
||||
rows, err := db.Query("SELECT id, name, mac, ip, created_at, updated_at FROM pcs ORDER BY name")
|
||||
rows, err := db.Query("SELECT id, name, mac, ip, autostart_cron, autostart_enabled, created_at, updated_at FROM pcs ORDER BY name")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -75,7 +77,7 @@ func (db *DB) GetAllPCs() ([]models.PC, error) {
|
||||
var pcs []models.PC
|
||||
for rows.Next() {
|
||||
var pc models.PC
|
||||
err := rows.Scan(&pc.ID, &pc.Name, &pc.MAC, &pc.IP, &pc.CreatedAt, &pc.UpdatedAt)
|
||||
err := rows.Scan(&pc.ID, &pc.Name, &pc.MAC, &pc.IP, &pc.AutostartCron, &pc.AutostartEnabled, &pc.CreatedAt, &pc.UpdatedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -86,11 +88,11 @@ func (db *DB) GetAllPCs() ([]models.PC, error) {
|
||||
}
|
||||
|
||||
// CreatePC erstellt einen neuen PC-Eintrag
|
||||
func (db *DB) CreatePC(name, mac, ip string) (*models.PC, error) {
|
||||
func (db *DB) CreatePC(name, mac, ip, autostartCron string, autostartEnabled bool) (*models.PC, error) {
|
||||
now := time.Now()
|
||||
result, err := db.Exec(
|
||||
"INSERT INTO pcs (name, mac, ip, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
|
||||
name, mac, ip, now, now,
|
||||
"INSERT INTO pcs (name, mac, ip, autostart_cron, autostart_enabled, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
name, mac, ip, autostartCron, autostartEnabled, now, now,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -102,32 +104,36 @@ func (db *DB) CreatePC(name, mac, ip string) (*models.PC, error) {
|
||||
}
|
||||
|
||||
return &models.PC{
|
||||
ID: int(id),
|
||||
Name: name,
|
||||
MAC: mac,
|
||||
IP: ip,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
ID: int(id),
|
||||
Name: name,
|
||||
MAC: mac,
|
||||
IP: ip,
|
||||
AutostartCron: autostartCron,
|
||||
AutostartEnabled: autostartEnabled,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdatePC aktualisiert einen bestehenden PC-Eintrag
|
||||
func (db *DB) UpdatePC(id int, name, mac, ip string) (*models.PC, error) {
|
||||
func (db *DB) UpdatePC(id int, name, mac, ip, autostartCron string, autostartEnabled bool) (*models.PC, error) {
|
||||
now := time.Now()
|
||||
_, err := db.Exec(
|
||||
"UPDATE pcs SET name = ?, mac = ?, ip = ?, updated_at = ? WHERE id = ?",
|
||||
name, mac, ip, now, id,
|
||||
"UPDATE pcs SET name = ?, mac = ?, ip = ?, autostart_cron = ?, autostart_enabled = ?, updated_at = ? WHERE id = ?",
|
||||
name, mac, ip, autostartCron, autostartEnabled, now, id,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &models.PC{
|
||||
ID: id,
|
||||
Name: name,
|
||||
MAC: mac,
|
||||
IP: ip,
|
||||
UpdatedAt: now,
|
||||
ID: id,
|
||||
Name: name,
|
||||
MAC: mac,
|
||||
IP: ip,
|
||||
AutostartCron: autostartCron,
|
||||
AutostartEnabled: autostartEnabled,
|
||||
UpdatedAt: now,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -141,9 +147,9 @@ func (db *DB) DeletePC(id int) error {
|
||||
func (db *DB) GetPCByID(id int) (*models.PC, error) {
|
||||
var pc models.PC
|
||||
err := db.QueryRow(
|
||||
"SELECT id, name, mac, ip, created_at, updated_at FROM pcs WHERE id = ?",
|
||||
"SELECT id, name, mac, ip, autostart_cron, autostart_enabled, created_at, updated_at FROM pcs WHERE id = ?",
|
||||
id,
|
||||
).Scan(&pc.ID, &pc.Name, &pc.MAC, &pc.IP, &pc.CreatedAt, &pc.UpdatedAt)
|
||||
).Scan(&pc.ID, &pc.Name, &pc.MAC, &pc.IP, &pc.AutostartCron, &pc.AutostartEnabled, &pc.CreatedAt, &pc.UpdatedAt)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -152,6 +158,27 @@ func (db *DB) GetPCByID(id int) (*models.PC, error) {
|
||||
return &pc, nil
|
||||
}
|
||||
|
||||
// GetPCsWithAutostart holt alle PCs mit aktiviertem Autostart
|
||||
func (db *DB) GetPCsWithAutostart() ([]models.PC, error) {
|
||||
rows, err := db.Query("SELECT id, name, mac, ip, autostart_cron, autostart_enabled, created_at, updated_at FROM pcs WHERE autostart_enabled = 1")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var pcs []models.PC
|
||||
for rows.Next() {
|
||||
var pc models.PC
|
||||
err := rows.Scan(&pc.ID, &pc.Name, &pc.MAC, &pc.IP, &pc.AutostartCron, &pc.AutostartEnabled, &pc.CreatedAt, &pc.UpdatedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pcs = append(pcs, pc)
|
||||
}
|
||||
|
||||
return pcs, nil
|
||||
}
|
||||
|
||||
// CreateLog erstellt einen neuen Log-Eintrag
|
||||
func (db *DB) CreateLog(pcID int, pcName, mac, trigger string) (*models.LogEvent, error) {
|
||||
now := time.Now()
|
||||
|
||||
@@ -79,8 +79,13 @@ func (h *PCHandler) CreatePC(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Standardwert für Autostart-Cron setzen, falls nicht angegeben
|
||||
if req.AutostartCron == "" {
|
||||
req.AutostartCron = "30 7 * * Mon-Fri"
|
||||
}
|
||||
|
||||
// PC erstellen
|
||||
pc, err := h.db.CreatePC(req.Name, req.MAC, req.IP)
|
||||
pc, err := h.db.CreatePC(req.Name, req.MAC, req.IP, req.AutostartCron, req.AutostartEnabled)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.PCResponse{
|
||||
Success: false,
|
||||
@@ -126,8 +131,13 @@ func (h *PCHandler) UpdatePC(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Standardwert für Autostart-Cron setzen, falls nicht angegeben
|
||||
if req.AutostartCron == "" {
|
||||
req.AutostartCron = "30 7 * * Mon-Fri"
|
||||
}
|
||||
|
||||
// PC aktualisieren
|
||||
pc, err := h.db.UpdatePC(id, req.Name, req.MAC, req.IP)
|
||||
pc, err := h.db.UpdatePC(id, req.Name, req.MAC, req.IP, req.AutostartCron, req.AutostartEnabled)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.PCResponse{
|
||||
Success: false,
|
||||
@@ -231,11 +241,13 @@ func (h *PCHandler) GetPCStatus(c *gin.Context) {
|
||||
for _, pc := range pcs {
|
||||
online := h.pingService.IsOnline(pc.IP)
|
||||
statusList = append(statusList, models.PCStatus{
|
||||
ID: pc.ID,
|
||||
Name: pc.Name,
|
||||
MAC: pc.MAC,
|
||||
IP: pc.IP,
|
||||
Online: online,
|
||||
ID: pc.ID,
|
||||
Name: pc.Name,
|
||||
MAC: pc.MAC,
|
||||
IP: pc.IP,
|
||||
Online: online,
|
||||
AutostartCron: pc.AutostartCron,
|
||||
AutostartEnabled: pc.AutostartEnabled,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -6,26 +6,32 @@ import (
|
||||
|
||||
// PC repräsentiert einen Computer in der Datenbank
|
||||
type PC struct {
|
||||
ID int `json:"id" db:"id"`
|
||||
Name string `json:"name" db:"name"`
|
||||
MAC string `json:"mac" db:"mac"`
|
||||
IP string `json:"ip" db:"ip"`
|
||||
CreatedAt time.Time `json:"created_at" db:"updated_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||
ID int `json:"id" db:"id"`
|
||||
Name string `json:"name" db:"name"`
|
||||
MAC string `json:"mac" db:"mac"`
|
||||
IP string `json:"ip" db:"ip"`
|
||||
AutostartCron string `json:"autostart_cron" db:"autostart_cron"`
|
||||
AutostartEnabled bool `json:"autostart_enabled" db:"autostart_enabled"`
|
||||
CreatedAt time.Time `json:"created_at" db:"updated_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||
}
|
||||
|
||||
// CreatePCRequest repräsentiert die Anfrage zum Erstellen eines neuen PCs
|
||||
type CreatePCRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
MAC string `json:"mac" binding:"required"`
|
||||
IP string `json:"ip" binding:"required"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
MAC string `json:"mac" binding:"required"`
|
||||
IP string `json:"ip" binding:"required"`
|
||||
AutostartCron string `json:"autostart_cron"`
|
||||
AutostartEnabled bool `json:"autostart_enabled"`
|
||||
}
|
||||
|
||||
// UpdatePCRequest repräsentiert die Anfrage zum Aktualisieren eines PCs
|
||||
type UpdatePCRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
MAC string `json:"mac" binding:"required"`
|
||||
IP string `json:"ip" binding:"required"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
MAC string `json:"mac" binding:"required"`
|
||||
IP string `json:"ip" binding:"required"`
|
||||
AutostartCron string `json:"autostart_cron"`
|
||||
AutostartEnabled bool `json:"autostart_enabled"`
|
||||
}
|
||||
|
||||
// PCResponse repräsentiert die Antwort für PC-Operationen
|
||||
@@ -38,9 +44,11 @@ type PCResponse struct {
|
||||
|
||||
// PCStatus repräsentiert den Online-Status eines PCs
|
||||
type PCStatus struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
MAC string `json:"mac"`
|
||||
IP string `json:"ip"`
|
||||
Online bool `json:"online"`
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
MAC string `json:"mac"`
|
||||
IP string `json:"ip"`
|
||||
Online bool `json:"online"`
|
||||
AutostartCron string `json:"autostart_cron"`
|
||||
AutostartEnabled bool `json:"autostart_enabled"`
|
||||
}
|
||||
|
||||
174
internal/scheduler/scheduler.go
Normal file
174
internal/scheduler/scheduler.go
Normal file
@@ -0,0 +1,174 @@
|
||||
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() {
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user