From b43cd7fe99ffa9574f8583fcb895359db417b43e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxi=20Quo=C3=9F?= Date: Sat, 28 Jan 2023 12:52:20 +0100 Subject: [PATCH] add wake/shutdown cronjob --- README.md | 6 ++- backend/cronjobs/cronjobs.go | 37 +++++++++++++--- ...vices.go => 1674902070_created_devices.go} | 22 +++++++++- ...d_ports.go => 1674902070_created_ports.go} | 4 +- ...ings.go => 1674902070_created_settings.go} | 4 +- ...d_users.go => 1674902070_deleted_users.go} | 4 +- backend/networking/shutdown.go | 28 +++++++++++++ backend/networking/wake.go | 12 +++--- backend/pb/handlers.go | 17 +++----- backend/pb/pb.go | 20 +++++---- frontend/src/components/DeviceCard.svelte | 42 ++++++++++++++++++- frontend/src/components/DeviceForm.svelte | 22 ++++++++++ frontend/src/routes/device/[id]/+page.svelte | 3 ++ frontend/src/routes/settings/+page.svelte | 9 +++- frontend/src/scss/main.scss | 1 + 15 files changed, 188 insertions(+), 43 deletions(-) rename backend/migrations/{1674853772_created_devices.go => 1674902070_created_devices.go} (88%) rename backend/migrations/{1674853772_created_ports.go => 1674902070_created_ports.go} (94%) rename backend/migrations/{1674853772_created_settings.go => 1674902070_created_settings.go} (94%) rename backend/migrations/{1674853772_deleted_users.go => 1674902070_deleted_users.go} (95%) create mode 100644 backend/networking/shutdown.go diff --git a/README.md b/README.md index d7a8e6db..5ecc0976 100644 --- a/README.md +++ b/README.md @@ -37,14 +37,16 @@ Open up [http://localhost:5173/](http://localhost:5173/) - [x] ~~add per device settings~~ - [x] ~~theme toggle button~~ - [x] ~~add device ports to cards~~ + - [ ] settings: add network scan + - [ ] settings: add option for website title - backend - [x] ~~make sure ping works~~ - [x] ~~make sure arp works~~ - [x] ~~make sure wake works~~ - - [ ] add shutdown command - - [ ] add scheduled wake + - [x] ~~add shutdown command~~ + - [x] ~~add scheduled wake~~ - [x] [~~#34 Add support for WOL passwords~~](https://github.com/seriousm4x/UpSnap/issues/34) - [x] [~~#33 Seemingly High Ram usage~~](https://github.com/seriousm4x/UpSnap/issues/33) - [x] [~~#32 API available?~~](https://github.com/seriousm4x/UpSnap/issues/32) diff --git a/backend/cronjobs/cronjobs.go b/backend/cronjobs/cronjobs.go index 9234e1a5..4d564730 100644 --- a/backend/cronjobs/cronjobs.go +++ b/backend/cronjobs/cronjobs.go @@ -9,17 +9,18 @@ import ( ) var Devices []*models.Record -var Jobs *cron.Cron +var CronPing *cron.Cron +var CronWakeShutdown *cron.Cron -func RunCron(app *pocketbase.PocketBase) { +func RunPing(app *pocketbase.PocketBase) { settingsRecords, err := app.Dao().FindRecordsByExpr("settings") if err != nil { logger.Error.Println(err) } // init cronjob - Jobs = cron.New() - Jobs.AddFunc(settingsRecords[0].GetString("interval"), func() { + CronPing = cron.New() + CronPing.AddFunc(settingsRecords[0].GetString("interval"), func() { // skip cron if no realtime clients connected realtimeClients := len(app.SubscriptionsBroker().Clients()) if realtimeClients == 0 { @@ -70,5 +71,31 @@ func RunCron(app *pocketbase.PocketBase) { }(device) } }) - Jobs.Run() + CronPing.Run() +} + +func RunWakeShutdown() { + CronWakeShutdown = cron.New() + for _, device := range Devices { + wake_cron := device.GetString("wake_cron") + wake_cron_enabled := device.GetBool("wake_cron_enabled") + shutdown_cron := device.GetString("shutdown_cron") + shutdown_cron_enabled := device.GetBool("wake_cron_enabled") + + if wake_cron_enabled && wake_cron != "" { + CronWakeShutdown.AddFunc(wake_cron, func() { + if err := networking.WakeDevice(device); err != nil { + logger.Error.Println(err) + } + }) + } + + if shutdown_cron_enabled && shutdown_cron != "" { + CronWakeShutdown.AddFunc(shutdown_cron, func() { + if err := networking.ShutdownDevice(device); err != nil { + logger.Error.Println(err) + } + }) + } + } } diff --git a/backend/migrations/1674853772_created_devices.go b/backend/migrations/1674902070_created_devices.go similarity index 88% rename from backend/migrations/1674853772_created_devices.go rename to backend/migrations/1674902070_created_devices.go index c998417b..4785f353 100644 --- a/backend/migrations/1674853772_created_devices.go +++ b/backend/migrations/1674902070_created_devices.go @@ -13,8 +13,8 @@ func init() { m.Register(func(db dbx.Builder) error { jsonData := `{ "id": "z5lghx2r3tm45n1", - "created": "2023-01-27 21:09:32.861Z", - "updated": "2023-01-27 21:09:32.861Z", + "created": "2023-01-28 10:34:30.781Z", + "updated": "2023-01-28 10:34:30.781Z", "name": "devices", "type": "base", "system": false, @@ -122,6 +122,15 @@ func init() { "pattern": "" } }, + { + "system": false, + "id": "dcd3vuc3", + "name": "wake_cron_enabled", + "type": "bool", + "required": false, + "unique": false, + "options": {} + }, { "system": false, "id": "91bs6clw", @@ -135,6 +144,15 @@ func init() { "pattern": "" } }, + { + "system": false, + "id": "vnlymcuw", + "name": "shutdown_cron_enabled", + "type": "bool", + "required": false, + "unique": false, + "options": {} + }, { "system": false, "id": "1a7yrwo9", diff --git a/backend/migrations/1674853772_created_ports.go b/backend/migrations/1674902070_created_ports.go similarity index 94% rename from backend/migrations/1674853772_created_ports.go rename to backend/migrations/1674902070_created_ports.go index 7150af74..f51f430a 100644 --- a/backend/migrations/1674853772_created_ports.go +++ b/backend/migrations/1674902070_created_ports.go @@ -13,8 +13,8 @@ func init() { m.Register(func(db dbx.Builder) error { jsonData := `{ "id": "cti4l8f4mz8df3r", - "created": "2023-01-27 21:09:32.861Z", - "updated": "2023-01-27 21:09:32.861Z", + "created": "2023-01-28 10:34:30.781Z", + "updated": "2023-01-28 10:34:30.781Z", "name": "ports", "type": "base", "system": false, diff --git a/backend/migrations/1674853772_created_settings.go b/backend/migrations/1674902070_created_settings.go similarity index 94% rename from backend/migrations/1674853772_created_settings.go rename to backend/migrations/1674902070_created_settings.go index 9277fbce..0438cfe4 100644 --- a/backend/migrations/1674853772_created_settings.go +++ b/backend/migrations/1674902070_created_settings.go @@ -13,8 +13,8 @@ func init() { m.Register(func(db dbx.Builder) error { jsonData := `{ "id": "nmj3ko20gzkg8n3", - "created": "2023-01-27 21:09:32.861Z", - "updated": "2023-01-27 21:09:32.861Z", + "created": "2023-01-28 10:34:30.781Z", + "updated": "2023-01-28 10:34:30.781Z", "name": "settings", "type": "base", "system": false, diff --git a/backend/migrations/1674853772_deleted_users.go b/backend/migrations/1674902070_deleted_users.go similarity index 95% rename from backend/migrations/1674853772_deleted_users.go rename to backend/migrations/1674902070_deleted_users.go index 2cd8e332..f856f5b5 100644 --- a/backend/migrations/1674853772_deleted_users.go +++ b/backend/migrations/1674902070_deleted_users.go @@ -22,8 +22,8 @@ func init() { }, func(db dbx.Builder) error { jsonData := `{ "id": "_pb_users_auth_", - "created": "2023-01-27 21:08:42.303Z", - "updated": "2023-01-27 21:08:42.304Z", + "created": "2023-01-28 10:33:57.177Z", + "updated": "2023-01-28 10:33:57.178Z", "name": "users", "type": "auth", "system": false, diff --git a/backend/networking/shutdown.go b/backend/networking/shutdown.go new file mode 100644 index 00000000..50dd599b --- /dev/null +++ b/backend/networking/shutdown.go @@ -0,0 +1,28 @@ +package networking + +import ( + "errors" + "os/exec" + "time" + + "github.com/pocketbase/pocketbase/models" +) + +func ShutdownDevice(device *models.Record) error { + shutdown_cmd := device.GetString("shutdown_cmd") + if shutdown_cmd != "" { + cmd := exec.Command(shutdown_cmd) + if err := cmd.Run(); err != nil { + return err + } + } + + // we wait 1 minute for the device to come up + // after that, we check the state + time.Sleep(1 * time.Minute) + isOnline := PingDevice(device) + if isOnline { + return errors.New("device not offline after 1 min") + } + return nil +} diff --git a/backend/networking/wake.go b/backend/networking/wake.go index 5c8e78ac..e5a3560d 100644 --- a/backend/networking/wake.go +++ b/backend/networking/wake.go @@ -1,17 +1,16 @@ package networking import ( + "errors" "time" "github.com/pocketbase/pocketbase/models" - "github.com/seriousm4x/upsnap/backend/logger" ) -func WakeDevice(device *models.Record) bool { +func WakeDevice(device *models.Record) error { err := SendMagicPacket(device) if err != nil { - logger.Error.Println(err) - return false + return err } // we wait 1 minute for the device to come up @@ -19,8 +18,7 @@ func WakeDevice(device *models.Record) bool { time.Sleep(1 * time.Minute) isOnline := PingDevice(device) if isOnline { - return true - } else { - return false + return nil } + return errors.New("device not online after 1 min") } diff --git a/backend/pb/handlers.go b/backend/pb/handlers.go index 03139b4d..1aee3d6d 100644 --- a/backend/pb/handlers.go +++ b/backend/pb/handlers.go @@ -24,11 +24,10 @@ func HandlerWake(c echo.Context) error { go func(*models.Record) { record.Set("status", "pending") App.Dao().SaveRecord(record) - isOnline := networking.WakeDevice(record) - if isOnline { - record.Set("status", "online") - } else { + if err := networking.WakeDevice(record); err != nil { record.Set("status", "offline") + } else { + record.Set("status", "online") } App.Dao().SaveRecord(record) }(record) @@ -40,13 +39,9 @@ func HandlerShutdown(c echo.Context) error { if err != nil { return apis.NewNotFoundError("The device does not exist.", err) } - shutdown_cmd := record.GetString("shutdown_cmd") - if shutdown_cmd != "" { - cmd := exec.Command(shutdown_cmd) - if err := cmd.Run(); err != nil { - logger.Error.Println(err) - return apis.NewBadRequestError(err.Error(), record) - } + if err := networking.ShutdownDevice(record); err != nil { + logger.Error.Println(err) + return apis.NewBadRequestError(err.Error(), record) } return nil } diff --git a/backend/pb/pb.go b/backend/pb/pb.go index 5432c0a6..11f64a09 100644 --- a/backend/pb/pb.go +++ b/backend/pb/pb.go @@ -74,23 +74,29 @@ func StartPocketBase() { return err } - // run ping cronjob - go cronjobs.RunCron(App) + // run cronjobs + go cronjobs.RunPing(App) + go cronjobs.RunWakeShutdown() + // restart ping cronjobs or wake/shutdown cronjobs on model update // add event hook before starting server. // using this outside App.OnBeforeServe() would not work App.OnModelAfterUpdate().Add(func(e *core.ModelEvent) error { if e.Model.TableName() == "settings" { - for _, job := range cronjobs.Jobs.Entries() { - cronjobs.Jobs.Remove(job.ID) + for _, job := range cronjobs.CronPing.Entries() { + cronjobs.CronPing.Remove(job.ID) } - go cronjobs.RunCron(App) - } else { + go cronjobs.RunPing(App) + } else if e.Model.TableName() == "devices" { + logger.Debug.Println(e.Model) refreshDeviceList() + for _, job := range cronjobs.CronWakeShutdown.Entries() { + cronjobs.CronWakeShutdown.Remove(job.ID) + } + go cronjobs.RunWakeShutdown() } return nil }) - return nil }) diff --git a/frontend/src/components/DeviceCard.svelte b/frontend/src/components/DeviceCard.svelte index 5efa8899..da512cd3 100644 --- a/frontend/src/components/DeviceCard.svelte +++ b/frontend/src/components/DeviceCard.svelte @@ -1,7 +1,14 @@ diff --git a/frontend/src/routes/settings/+page.svelte b/frontend/src/routes/settings/+page.svelte index 6acc3b3e..55832ba2 100644 --- a/frontend/src/routes/settings/+page.svelte +++ b/frontend/src/routes/settings/+page.svelte @@ -28,7 +28,9 @@ ports: [], link: '', wake_cron: '', + wake_cron_enabled: false, shutdown_cron: '', + shutdown_cron_enabled: false, shutdown_cmd: '', password: '' }; @@ -115,8 +117,11 @@ netmask: device.netmask, ports: thisDevicePorts, link: device.link, - wake: device.wake, - shutdown: device.shutdown + wake_cron: device.wake.cron, + wake_cron_enabled: device.wake.enabled, + shutdown_cron: device.shutdown.cron, + shutdown_cron_enabled: device.shutdown.cron.enabled, + shutdown_cmd: device.shutdown.command }); } }; diff --git a/frontend/src/scss/main.scss b/frontend/src/scss/main.scss index 00460b0e..8a7676e8 100644 --- a/frontend/src/scss/main.scss +++ b/frontend/src/scss/main.scss @@ -40,6 +40,7 @@ $dropdown-min-width: 0; @import '../../node_modules/bootstrap/scss/dropdown'; @import '../../node_modules/bootstrap/scss/buttons'; @import '../../node_modules/bootstrap/scss/spinners'; +@import '../../node_modules/bootstrap/scss/badge'; @import '../../node_modules/bootstrap/scss/utilities/api';