Initial commit: Wake-on-LAN Manager mit Go und Web-Oberfläche
This commit is contained in:
127
internal/database/database.go
Normal file
127
internal/database/database.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"medi-wol/internal/models"
|
||||
"time"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
// DB ist die Datenbankverbindung
|
||||
type DB struct {
|
||||
*sql.DB
|
||||
}
|
||||
|
||||
// InitDB initialisiert die Datenbank und erstellt die Tabellen
|
||||
func InitDB() (*DB, error) {
|
||||
db, err := sql.Open("sqlite", "./medi-wol.db")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Tabelle erstellen
|
||||
createTableSQL := `
|
||||
CREATE TABLE IF NOT EXISTS pcs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
mac TEXT NOT NULL UNIQUE,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);`
|
||||
|
||||
_, err = db.Exec(createTableSQL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Println("Datenbank erfolgreich initialisiert")
|
||||
return &DB{db}, nil
|
||||
}
|
||||
|
||||
// GetAllPCs holt alle PCs aus der Datenbank
|
||||
func (db *DB) GetAllPCs() ([]models.PC, error) {
|
||||
rows, err := db.Query("SELECT id, name, mac, created_at, updated_at FROM pcs ORDER BY name")
|
||||
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.CreatedAt, &pc.UpdatedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pcs = append(pcs, pc)
|
||||
}
|
||||
|
||||
return pcs, nil
|
||||
}
|
||||
|
||||
// CreatePC erstellt einen neuen PC-Eintrag
|
||||
func (db *DB) CreatePC(name, mac string) (*models.PC, error) {
|
||||
now := time.Now()
|
||||
result, err := db.Exec(
|
||||
"INSERT INTO pcs (name, mac, created_at, updated_at) VALUES (?, ?, ?, ?)",
|
||||
name, mac, now, now,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &models.PC{
|
||||
ID: int(id),
|
||||
Name: name,
|
||||
MAC: mac,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdatePC aktualisiert einen bestehenden PC-Eintrag
|
||||
func (db *DB) UpdatePC(id int, name, mac string) (*models.PC, error) {
|
||||
now := time.Now()
|
||||
_, err := db.Exec(
|
||||
"UPDATE pcs SET name = ?, mac = ?, updated_at = ? WHERE id = ?",
|
||||
name, mac, now, id,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &models.PC{
|
||||
ID: id,
|
||||
Name: name,
|
||||
MAC: mac,
|
||||
UpdatedAt: now,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeletePC löscht einen PC-Eintrag
|
||||
func (db *DB) DeletePC(id int) error {
|
||||
_, err := db.Exec("DELETE FROM pcs WHERE id = ?", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetPCByID holt einen PC anhand der ID
|
||||
func (db *DB) GetPCByID(id int) (*models.PC, error) {
|
||||
var pc models.PC
|
||||
err := db.QueryRow(
|
||||
"SELECT id, name, mac, created_at, updated_at FROM pcs WHERE id = ?",
|
||||
id,
|
||||
).Scan(&pc.ID, &pc.Name, &pc.MAC, &pc.CreatedAt, &pc.UpdatedAt)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pc, nil
|
||||
}
|
||||
198
internal/handlers/pc_handler.go
Normal file
198
internal/handlers/pc_handler.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"medi-wol/internal/database"
|
||||
"medi-wol/internal/models"
|
||||
"medi-wol/internal/wol"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// PCHandler verwaltet die HTTP-Anfragen für PC-Operationen
|
||||
type PCHandler struct {
|
||||
db *database.DB
|
||||
wolService *wol.Service
|
||||
}
|
||||
|
||||
// NewPCHandler erstellt einen neuen PC-Handler
|
||||
func NewPCHandler(db *database.DB, wolService *wol.Service) *PCHandler {
|
||||
return &PCHandler{
|
||||
db: db,
|
||||
wolService: wolService,
|
||||
}
|
||||
}
|
||||
|
||||
// Index zeigt die Hauptseite an
|
||||
func (h *PCHandler) Index(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "index.html", gin.H{
|
||||
"title": "Medi-WOL - Wake-on-LAN Manager",
|
||||
})
|
||||
}
|
||||
|
||||
// GetAllPCs gibt alle gespeicherten PCs zurück
|
||||
func (h *PCHandler) GetAllPCs(c *gin.Context) {
|
||||
pcs, err := h.db.GetAllPCs()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.PCResponse{
|
||||
Success: false,
|
||||
Message: "Fehler beim Laden der PCs: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.PCResponse{
|
||||
Success: true,
|
||||
PCs: pcs,
|
||||
})
|
||||
}
|
||||
|
||||
// CreatePC erstellt einen neuen PC-Eintrag
|
||||
func (h *PCHandler) CreatePC(c *gin.Context) {
|
||||
var req models.CreatePCRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.PCResponse{
|
||||
Success: false,
|
||||
Message: "Ungültige Anfrage: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// MAC-Adresse validieren
|
||||
if !wol.ValidateMAC(req.MAC) {
|
||||
c.JSON(http.StatusBadRequest, models.PCResponse{
|
||||
Success: false,
|
||||
Message: "Ungültige MAC-Adresse",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// PC erstellen
|
||||
pc, err := h.db.CreatePC(req.Name, req.MAC)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.PCResponse{
|
||||
Success: false,
|
||||
Message: "Fehler beim Erstellen des PCs: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, models.PCResponse{
|
||||
Success: true,
|
||||
Message: "PC erfolgreich erstellt",
|
||||
PC: pc,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdatePC aktualisiert einen bestehenden PC-Eintrag
|
||||
func (h *PCHandler) UpdatePC(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.PCResponse{
|
||||
Success: false,
|
||||
Message: "Ungültige PC-ID",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var req models.UpdatePCRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.PCResponse{
|
||||
Success: false,
|
||||
Message: "Ungültige Anfrage: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// MAC-Adresse validieren
|
||||
if !wol.ValidateMAC(req.MAC) {
|
||||
c.JSON(http.StatusBadRequest, models.PCResponse{
|
||||
Success: false,
|
||||
Message: "Ungültige MAC-Adresse",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// PC aktualisieren
|
||||
pc, err := h.db.UpdatePC(id, req.Name, req.MAC)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.PCResponse{
|
||||
Success: false,
|
||||
Message: "Fehler beim Aktualisieren des PCs: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.PCResponse{
|
||||
Success: true,
|
||||
Message: "PC erfolgreich aktualisiert",
|
||||
PC: pc,
|
||||
})
|
||||
}
|
||||
|
||||
// DeletePC löscht einen PC-Eintrag
|
||||
func (h *PCHandler) DeletePC(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.PCResponse{
|
||||
Success: false,
|
||||
Message: "Ungültige PC-ID",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
err = h.db.DeletePC(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.PCResponse{
|
||||
Success: false,
|
||||
Message: "Fehler beim Löschen des PCs: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.PCResponse{
|
||||
Success: true,
|
||||
Message: "PC erfolgreich gelöscht",
|
||||
})
|
||||
}
|
||||
|
||||
// WakePC sendet ein Wake-on-LAN Paket
|
||||
func (h *PCHandler) WakePC(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.PCResponse{
|
||||
Success: false,
|
||||
Message: "Ungültige PC-ID",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// PC aus der Datenbank holen
|
||||
pc, err := h.db.GetPCByID(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, models.PCResponse{
|
||||
Success: false,
|
||||
Message: "PC nicht gefunden",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Wake-on-LAN Paket senden
|
||||
err = h.wolService.WakePC(pc.MAC)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.PCResponse{
|
||||
Success: false,
|
||||
Message: "Fehler beim Senden des Wake-on-LAN Pakets: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.PCResponse{
|
||||
Success: true,
|
||||
Message: "Wake-on-LAN Paket erfolgreich gesendet an " + pc.Name,
|
||||
})
|
||||
}
|
||||
34
internal/models/pc.go
Normal file
34
internal/models/pc.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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"`
|
||||
CreatedAt time.Time `json:"created_at" db:"created_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"`
|
||||
}
|
||||
|
||||
// UpdatePCRequest repräsentiert die Anfrage zum Aktualisieren eines PCs
|
||||
type UpdatePCRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
MAC string `json:"mac" binding:"required"`
|
||||
}
|
||||
|
||||
// PCResponse repräsentiert die Antwort für PC-Operationen
|
||||
type PCResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message,omitempty"`
|
||||
PC *PC `json:"pc,omitempty"`
|
||||
PCs []PC `json:"pcs,omitempty"`
|
||||
}
|
||||
96
internal/wol/wol.go
Normal file
96
internal/wol/wol.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package wol
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Service ist der Wake-on-LAN Service
|
||||
type Service struct{}
|
||||
|
||||
// NewService erstellt einen neuen Wake-on-LAN Service
|
||||
func NewService() *Service {
|
||||
return &Service{}
|
||||
}
|
||||
|
||||
// WakePC sendet ein Wake-on-LAN Paket an die angegebene MAC-Adresse
|
||||
func (s *Service) WakePC(mac string) error {
|
||||
// MAC-Adresse normalisieren
|
||||
normalizedMAC := strings.ReplaceAll(mac, ":", "")
|
||||
normalizedMAC = strings.ReplaceAll(normalizedMAC, "-", "")
|
||||
|
||||
log.Printf("Versende Wake-on-LAN Paket an MAC: %s", mac)
|
||||
|
||||
// Magic Packet erstellen
|
||||
magicPacket, err := s.createMagicPacket(normalizedMAC)
|
||||
if err != nil {
|
||||
log.Printf("Fehler beim Erstellen des Magic Packets: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// UDP-Paket senden
|
||||
err = s.sendMagicPacket(magicPacket)
|
||||
if err != nil {
|
||||
log.Printf("Fehler beim Senden des Wake-on-LAN Pakets: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Wake-on-LAN Paket erfolgreich an %s gesendet", mac)
|
||||
return nil
|
||||
}
|
||||
|
||||
// createMagicPacket erstellt ein Wake-on-LAN Magic Packet
|
||||
func (s *Service) createMagicPacket(mac string) ([]byte, error) {
|
||||
// MAC-Adresse in Bytes konvertieren
|
||||
macBytes, err := hex.DecodeString(mac)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Magic Packet: 6 Bytes 0xFF gefolgt von 16 Wiederholungen der MAC-Adresse
|
||||
magicPacket := make([]byte, 102)
|
||||
|
||||
// Erste 6 Bytes mit 0xFF füllen
|
||||
for i := 0; i < 6; i++ {
|
||||
magicPacket[i] = 0xFF
|
||||
}
|
||||
|
||||
// MAC-Adresse 16 mal wiederholen
|
||||
for i := 6; i < 102; i += 6 {
|
||||
copy(magicPacket[i:i+6], macBytes)
|
||||
}
|
||||
|
||||
return magicPacket, nil
|
||||
}
|
||||
|
||||
// sendMagicPacket sendet das Magic Packet über UDP
|
||||
func (s *Service) sendMagicPacket(magicPacket []byte) error {
|
||||
// UDP-Verbindung erstellen
|
||||
conn, err := net.Dial("udp", "255.255.255.255:9")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Paket senden
|
||||
_, err = conn.Write(magicPacket)
|
||||
return err
|
||||
}
|
||||
|
||||
// ValidateMAC prüft, ob die MAC-Adresse gültig ist
|
||||
func ValidateMAC(mac string) bool {
|
||||
// MAC-Adresse bereinigen
|
||||
cleanMAC := strings.ReplaceAll(mac, ":", "")
|
||||
cleanMAC = strings.ReplaceAll(cleanMAC, "-", "")
|
||||
|
||||
// Länge prüfen (12 Hex-Zeichen)
|
||||
if len(cleanMAC) != 12 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Prüfen, ob alle Zeichen gültige Hexadezimalziffern sind
|
||||
_, err := hex.DecodeString(cleanMAC)
|
||||
return err == nil
|
||||
}
|
||||
Reference in New Issue
Block a user