From f015e845a92ec1a4675d4bb438dbb26f957356af Mon Sep 17 00:00:00 2001 From: Markus Date: Thu, 21 Aug 2025 13:36:43 +0200 Subject: [PATCH] =?UTF-8?q?Initial=20commit:=20Wake-on-LAN=20Manager=20mit?= =?UTF-8?q?=20Go=20und=20Web-Oberfl=C3=A4che?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 48 ++++++ README.md | 116 ++++++++++++++ cmd/server/main.go | 46 ++++++ go.mod | 50 ++++++ go.sum | 132 ++++++++++++++++ idea.txt | 6 + internal/database/database.go | 127 +++++++++++++++ internal/handlers/pc_handler.go | 198 ++++++++++++++++++++++++ internal/models/pc.go | 34 ++++ internal/wol/wol.go | 96 ++++++++++++ web/static/script.js | 265 ++++++++++++++++++++++++++++++++ web/static/style.css | 148 ++++++++++++++++++ web/templates/index.html | 141 +++++++++++++++++ 13 files changed, 1407 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 cmd/server/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 idea.txt create mode 100644 internal/database/database.go create mode 100644 internal/handlers/pc_handler.go create mode 100644 internal/models/pc.go create mode 100644 internal/wol/wol.go create mode 100644 web/static/script.js create mode 100644 web/static/style.css create mode 100644 web/templates/index.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..519e0c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,48 @@ +# Go binaries +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Go test binary, built with `go test -c` +*.test + +# Go coverage tool, specifically for Go 1.10+ +*.out + +# Go workspace file +go.work + +# Database files +*.db +*.sqlite +*.sqlite3 + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Log files +*.log + +# Environment files +.env +.env.local +.env.*.local + +# Temporary files +tmp/ +temp/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..8294b01 --- /dev/null +++ b/README.md @@ -0,0 +1,116 @@ +# Medi-WOL - Wake-on-LAN Manager + +Ein moderner Wake-on-LAN Manager, entwickelt mit Go und einer schönen Web-Oberfläche. + +## Features + +- **PC-Verwaltung**: Hinzufügen, Anzeigen und Löschen von PC-Einträgen +- **Wake-on-LAN**: Ein-Klick-Aufwecken von Computern über MAC-Adressen +- **Moderne Web-Oberfläche**: Responsive Design mit Bootstrap und FontAwesome +- **SQLite-Datenbank**: Einfache lokale Datenspeicherung +- **Cross-Platform**: Läuft auf Windows und Linux + +## Voraussetzungen + +- Go 1.21 oder höher +- Git + +## Installation + +1. **Repository klonen:** + ```bash + git clone + cd medi-wol + ``` + +2. **Abhängigkeiten installieren:** + ```bash + go mod tidy + ``` + +3. **Anwendung starten:** + ```bash + go run cmd/server/main.go + ``` + +4. **Im Browser öffnen:** + ``` + http://localhost:8080 + ``` + +## Verwendung + +### PC hinzufügen +1. Geben Sie den Namen des PCs ein +2. Geben Sie die MAC-Adresse im Format `XX:XX:XX:XX:XX:XX` ein +3. Klicken Sie auf "PC hinzufügen" + +### PC aufwecken +- Klicken Sie auf den "Aufwecken"-Button neben dem gewünschten PC +- Das System sendet automatisch ein Wake-on-LAN Paket + +### PC löschen +- Klicken Sie auf den "Löschen"-Button neben dem gewünschten PC +- Bestätigen Sie die Löschung + +## Projektstruktur + +``` +medi-wol/ +├── cmd/server/ # Hauptanwendung +├── internal/ # Interne Pakete +│ ├── database/ # Datenbanklogik +│ ├── handlers/ # HTTP-Handler +│ ├── models/ # Datenmodelle +│ └── wol/ # Wake-on-LAN Service +├── web/ # Web-Oberfläche +│ ├── static/ # CSS, JavaScript +│ └── templates/ # HTML-Templates +├── go.mod # Go-Module +└── README.md # Diese Datei +``` + +## API-Endpunkte + +- `GET /` - Hauptseite +- `GET /api/pcs` - Alle PCs abrufen +- `POST /api/pcs` - Neuen PC erstellen +- `DELETE /api/pcs/:id` - PC löschen +- `POST /api/pcs/:id/wake` - PC aufwecken + +## Datenbank + +Die Anwendung verwendet SQLite als lokale Datenbank. Die Datenbankdatei `medi-wol.db` wird automatisch im Projektverzeichnis erstellt. + +## Wake-on-LAN + +Das System sendet Magic Packets an die gespeicherten MAC-Adressen. Stellen Sie sicher, dass: + +1. Die Zielcomputer Wake-on-LAN unterstützen +2. Die Netzwerkkarte für Wake-on-LAN konfiguriert ist +3. Die Computer im gleichen Netzwerksegment sind + +## Entwicklung + +### Lokale Entwicklung +```bash +go run cmd/server/main.go +``` + +### Build für Produktion +```bash +go build -o medi-wol cmd/server/main.go +``` + +### Tests ausführen +```bash +go test ./... +``` + +## Lizenz + +Dieses Projekt ist für den internen Gebrauch bestimmt. + +## Support + +Bei Fragen oder Problemen wenden Sie sich an das Entwicklungsteam. diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 0000000..16f7ea8 --- /dev/null +++ b/cmd/server/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "log" + "medi-wol/internal/database" + "medi-wol/internal/handlers" + "medi-wol/internal/wol" + + "github.com/gin-gonic/gin" +) + +func main() { + // Datenbank initialisieren + db, err := database.InitDB() + if err != nil { + log.Fatal("Fehler beim Initialisieren der Datenbank:", err) + } + defer db.Close() + + // Wake-on-LAN Service initialisieren + wolService := wol.NewService() + + // Handler initialisieren + pcHandler := handlers.NewPCHandler(db, wolService) + + // Gin Router konfigurieren + r := gin.Default() + + // Statische Dateien bereitstellen + r.Static("/static", "./web/static") + r.LoadHTMLGlob("web/templates/*") + + // Routen definieren + r.GET("/", pcHandler.Index) + r.GET("/api/pcs", pcHandler.GetAllPCs) + r.POST("/api/pcs", pcHandler.CreatePC) + r.PUT("/api/pcs/:id", pcHandler.UpdatePC) + r.DELETE("/api/pcs/:id", pcHandler.DeletePC) + r.POST("/api/pcs/:id/wake", pcHandler.WakePC) + + // Server starten + log.Println("Server startet auf Port 8080...") + if err := r.Run(":8080"); err != nil { + log.Fatal("Fehler beim Starten des Servers:", err) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..09a1214 --- /dev/null +++ b/go.mod @@ -0,0 +1,50 @@ +module medi-wol + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + modernc.org/sqlite v1.28.0 +) + +require ( + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.9.0 // indirect + golang.org/x/text v0.9.0 // indirect + golang.org/x/tools v0.6.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + lukechampine.com/uint128 v1.2.0 // indirect + modernc.org/cc/v3 v3.40.0 // indirect + modernc.org/ccgo/v3 v3.16.13 // indirect + modernc.org/libc v1.29.0 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.7.2 // indirect + modernc.org/opt v0.1.3 // indirect + modernc.org/strutil v1.1.3 // indirect + modernc.org/token v1.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..fb2cf96 --- /dev/null +++ b/go.sum @@ -0,0 +1,132 @@ +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +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/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/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/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +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.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/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/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/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +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-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-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +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/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= +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/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= +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/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v1.29.0 h1:tTFRFq69YKCF2QyGNuRUQxKBm1uZZLubf6Cjh/pVHXs= +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/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= +modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= +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/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +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/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +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= diff --git a/idea.txt b/idea.txt new file mode 100644 index 0000000..8f0ecc5 --- /dev/null +++ b/idea.txt @@ -0,0 +1,6 @@ +erstelle ein projekt mit golang: +- soll unter Windows entwickelt werden, aber auch unter Linux laufen +- web Oberfläche, mit der man + - einen neuen PC (Name/MAC Adresse) anlegen kann + - Bestehende einträge löschen kann + - ein klick auf einen pc löst das versenden eines wake-on-lan pakets an die mac Adresse des PCs aus diff --git a/internal/database/database.go b/internal/database/database.go new file mode 100644 index 0000000..4c2d72b --- /dev/null +++ b/internal/database/database.go @@ -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 +} diff --git a/internal/handlers/pc_handler.go b/internal/handlers/pc_handler.go new file mode 100644 index 0000000..837d491 --- /dev/null +++ b/internal/handlers/pc_handler.go @@ -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, + }) +} diff --git a/internal/models/pc.go b/internal/models/pc.go new file mode 100644 index 0000000..539ca68 --- /dev/null +++ b/internal/models/pc.go @@ -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"` +} diff --git a/internal/wol/wol.go b/internal/wol/wol.go new file mode 100644 index 0000000..25d5c25 --- /dev/null +++ b/internal/wol/wol.go @@ -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 +} diff --git a/web/static/script.js b/web/static/script.js new file mode 100644 index 0000000..9c4d80a --- /dev/null +++ b/web/static/script.js @@ -0,0 +1,265 @@ +// JavaScript für Medi-WOL Web-Oberfläche + +class PCManager { + constructor() { + this.init(); + } + + init() { + this.loadPCs(); + this.setupEventListeners(); + } + + setupEventListeners() { + // Formular für neuen PC + document.getElementById('addPCForm').addEventListener('submit', (e) => { + e.preventDefault(); + this.addPC(); + }); + + // Edit PC Modal speichern Button + document.getElementById('saveEditBtn').addEventListener('click', () => { + this.saveEditPC(); + }); + + // MAC-Adresse Formatierung für Edit-Formular + document.getElementById('editMACAddress')?.addEventListener('input', (e) => { + this.formatMACAddress(e.target); + }); + } + + async loadPCs() { + try { + const response = await fetch('/api/pcs'); + const data = await response.json(); + + if (data.success) { + this.displayPCs(data.pcs); + } else { + this.showNotification('Fehler', data.message, 'danger'); + } + } catch (error) { + this.showNotification('Fehler', 'Fehler beim Laden der PCs', 'danger'); + } + } + + displayPCs(pcs) { + const tableBody = document.getElementById('pcTableBody'); + const noPCs = document.getElementById('noPCs'); + const pcList = document.getElementById('pcList'); + + if (pcs.length === 0) { + tableBody.innerHTML = ''; + noPCs.style.display = 'block'; + pcList.style.display = 'none'; + return; + } + + noPCs.style.display = 'none'; + pcList.style.display = 'block'; + + tableBody.innerHTML = pcs.map(pc => ` + + ${this.escapeHtml(pc.name)} + ${this.escapeHtml(pc.mac)} + ${new Date(pc.created_at).toLocaleDateString('de-DE')} + +
+ + + +
+ + + `).join(''); + } + + async addPC() { + const name = document.getElementById('pcName').value.trim(); + const mac = document.getElementById('macAddress').value.trim(); + + if (!name || !mac) { + this.showNotification('Warnung', 'Bitte füllen Sie alle Felder aus', 'warning'); + return; + } + + try { + const response = await fetch('/api/pcs', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ name, mac }) + }); + + const data = await response.json(); + + if (data.success) { + this.showNotification('Erfolg', 'PC erfolgreich hinzugefügt', 'success'); + document.getElementById('addPCForm').reset(); + this.loadPCs(); + } else { + this.showNotification('Fehler', data.message, 'danger'); + } + } catch (error) { + this.showNotification('Fehler', 'Fehler beim Hinzufügen des PCs', 'danger'); + } + } + + editPC(id, name, mac) { + // Modal mit PC-Daten füllen + document.getElementById('editPCId').value = id; + document.getElementById('editPCName').value = name; + document.getElementById('editMACAddress').value = mac; + + // Modal öffnen + const editModal = new bootstrap.Modal(document.getElementById('editPCModal')); + editModal.show(); + } + + async saveEditPC() { + const id = document.getElementById('editPCId').value; + const name = document.getElementById('editPCName').value.trim(); + const mac = document.getElementById('editMACAddress').value.trim(); + + if (!name || !mac) { + this.showNotification('Warnung', 'Bitte füllen Sie alle Felder aus', 'warning'); + return; + } + + try { + const response = await fetch(`/api/pcs/${id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ name, mac }) + }); + + const data = await response.json(); + + if (data.success) { + this.showNotification('Erfolg', 'PC erfolgreich aktualisiert', 'success'); + + // Modal schließen + const editModal = bootstrap.Modal.getInstance(document.getElementById('editPCModal')); + editModal.hide(); + + // PC-Liste neu laden + this.loadPCs(); + } else { + this.showNotification('Fehler', data.message, 'danger'); + } + } catch (error) { + this.showNotification('Fehler', 'Fehler beim Aktualisieren des PCs', 'danger'); + } + } + + async deletePC(id) { + if (!confirm('Sind Sie sicher, dass Sie diesen PC löschen möchten?')) { + return; + } + + try { + const response = await fetch(`/api/pcs/${id}`, { + method: 'DELETE' + }); + + const data = await response.json(); + + if (data.success) { + this.showNotification('Erfolg', 'PC erfolgreich gelöscht', 'success'); + this.loadPCs(); + } else { + this.showNotification('Fehler', data.message, 'danger'); + } + } catch (error) { + this.showNotification('Fehler', 'Fehler beim Löschen des PCs', 'danger'); + } + } + + async wakePC(id) { + try { + const response = await fetch(`/api/pcs/${id}/wake`, { + method: 'POST' + }); + + const data = await response.json(); + + if (data.success) { + this.showNotification('Erfolg', data.message, 'success'); + } else { + this.showNotification('Fehler', data.message, 'danger'); + } + } catch (error) { + this.showNotification('Fehler', 'Fehler beim Senden des Wake-on-LAN Pakets', 'danger'); + } + } + + showNotification(title, message, type = 'info') { + const toast = document.getElementById('notificationToast'); + const toastTitle = document.getElementById('toastTitle'); + const toastMessage = document.getElementById('toastMessage'); + + // Toast-Typ setzen + toast.className = `toast ${type === 'success' ? 'bg-success' : type === 'danger' ? 'bg-danger' : type === 'warning' ? 'bg-warning' : 'bg-info'} text-white`; + + toastTitle.textContent = title; + toastMessage.textContent = message; + + // Toast anzeigen + const bsToast = new bootstrap.Toast(toast); + bsToast.show(); + } + + escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } + + formatMACAddress(input) { + let value = input.value.replace(/[^0-9A-Fa-f]/g, ''); + + if (value.length > 12) { + value = value.substring(0, 12); + } + + // MAC-Adresse mit Doppelpunkten formatieren + if (value.length >= 2) { + value = value.match(/.{1,2}/g).join(':'); + } + + input.value = value.toUpperCase(); + } +} + +// PC Manager initialisieren, wenn die Seite geladen ist +document.addEventListener('DOMContentLoaded', () => { + window.pcManager = new PCManager(); +}); + +// MAC-Adresse Formatierung für das Hauptformular +document.getElementById('macAddress')?.addEventListener('input', (e) => { + let value = e.target.value.replace(/[^0-9A-Fa-f]/g, ''); + + if (value.length > 12) { + value = value.substring(0, 12); + } + + // MAC-Adresse mit Doppelpunkten formatieren + if (value.length >= 2) { + value = value.match(/.{1,2}/g).join(':'); + } + + e.target.value = value.toUpperCase(); +}); diff --git a/web/static/style.css b/web/static/style.css new file mode 100644 index 0000000..7936bb1 --- /dev/null +++ b/web/static/style.css @@ -0,0 +1,148 @@ +/* Custom Styles für Medi-WOL */ + +body { + background-color: #f8f9fa; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; +} + +.card { + border: none; + border-radius: 15px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + transition: transform 0.2s ease-in-out; +} + +.card:hover { + transform: translateY(-2px); +} + +.card-header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border-radius: 15px 15px 0 0 !important; + border: none; +} + +.btn { + border-radius: 25px; + padding: 8px 20px; + font-weight: 500; + transition: all 0.3s ease; +} + +.btn:hover { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); +} + +.btn-primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border: none; +} + +.btn-success { + background: linear-gradient(135deg, #56ab2f 0%, #a8e6cf 100%); + border: none; +} + +.btn-danger { + background: linear-gradient(135deg, #ff416c 0%, #ff4b2b 100%); + border: none; +} + +.btn-warning { + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); + border: none; +} + +.table { + border-radius: 10px; + overflow: hidden; +} + +.table thead th { + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); + color: white; + border: none; + font-weight: 600; +} + +.table tbody tr { + transition: background-color 0.2s ease; +} + +.table tbody tr:hover { + background-color: #f8f9fa; +} + +.form-control { + border-radius: 10px; + border: 2px solid #e9ecef; + transition: border-color 0.3s ease; +} + +.form-control:focus { + border-color: #667eea; + box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25); +} + +.toast { + border-radius: 15px; + border: none; + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); +} + +.toast-header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border-radius: 15px 15px 0 0; +} + +.btn-close { + filter: invert(1); +} + +/* Animationen */ +@keyframes fadeIn { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } +} + +.card { + animation: fadeIn 0.5s ease-out; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .container { + padding: 0 15px; + } + + .btn { + width: 100%; + margin-bottom: 10px; + } + + .table-responsive { + font-size: 0.9rem; + } +} + +/* Custom Scrollbar */ +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 10px; +} + +::-webkit-scrollbar-thumb { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-radius: 10px; +} + +::-webkit-scrollbar-thumb:hover { + background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%); +} diff --git a/web/templates/index.html b/web/templates/index.html new file mode 100644 index 0000000..5be4369 --- /dev/null +++ b/web/templates/index.html @@ -0,0 +1,141 @@ + + + + + + {{.title}} + + + + + +
+
+
+

+ + {{.title}} +

+
+
+ + +
+
+
+
+
+ Neuen PC hinzufügen +
+
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+
+
+
+ + +
+
+
+
+
+ Gespeicherte PCs +
+
+
+
+ + + + + + + + + + + + +
NameMAC-AdresseErstellt amAktionen
+
+ +
+
+
+
+
+ + + + + +
+ +
+ + + + +