mirror of
https://github.com/seriousm4x/UpSnap.git
synced 2026-07-22 21:43:42 -04:00
add wake/shutdown cronjob
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
28
backend/networking/shutdown.go
Normal file
28
backend/networking/shutdown.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
<script>
|
||||
import { dev } from '$app/environment';
|
||||
import { parseISO, formatDistance } from 'date-fns';
|
||||
import { faPowerOff, faEllipsisVertical, faCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import {
|
||||
faPowerOff,
|
||||
faEllipsisVertical,
|
||||
faCircle,
|
||||
faCircleUp,
|
||||
faCircleDown,
|
||||
faLock
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import Fa from 'svelte-fa';
|
||||
|
||||
export let device;
|
||||
@@ -96,6 +103,39 @@
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if device.wake_cron !== ''}
|
||||
<span
|
||||
class="badge rounded-pill {device.wake_cron_enabled
|
||||
? 'text-bg-success'
|
||||
: 'text-bg-danger'}"
|
||||
data-toggle="tooltip"
|
||||
title="Wake cron {device.wake_cron_enabled ? 'enabled' : 'disabled'}"
|
||||
>
|
||||
<Fa icon={faCircleUp} class="me-1" />
|
||||
{device.wake_cron}
|
||||
</span>
|
||||
{/if}
|
||||
{#if device.shutdown_cron !== ''}
|
||||
<span
|
||||
class="badge rounded-pill {device.shutdown_cron_enabled
|
||||
? 'text-bg-success'
|
||||
: 'text-bg-danger'}"
|
||||
data-toggle="tooltip"
|
||||
title="Shutdown cron {device.shutdown_cron_enabled ? 'enabled' : 'disabled'}"
|
||||
>
|
||||
<Fa icon={faCircleDown} class="me-1" />
|
||||
{device.shutdown_cron}
|
||||
</span>
|
||||
{/if}
|
||||
{#if device.password !== ''}
|
||||
<span
|
||||
class="badge rounded-pill text-bg-secondary"
|
||||
data-toggle="tooltip"
|
||||
title="Wake password set"
|
||||
>
|
||||
<Fa icon={faLock} class="mx-1" />
|
||||
</span>
|
||||
{/if}
|
||||
<p class="text-muted m-0">
|
||||
{formatDistance(parseISO(device.updated), now, {
|
||||
includeSeconds: true,
|
||||
|
||||
@@ -252,6 +252,17 @@
|
||||
type="text"
|
||||
bind:value={device.wake_cron}
|
||||
/>
|
||||
<div class="input-group-text form-switch">
|
||||
<label class="form-check-label" for="wake-cron-enabled">Enable</label>
|
||||
<input
|
||||
id="wake-cron-enabled"
|
||||
class="form-check-input mt-0 ms-1"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
aria-label="Enable/Disable wake cron"
|
||||
bind:checked={device.wake_cron_enabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group mb-1">
|
||||
<span class="input-group-text">Shutdown Cron<sup>(1)</sup></span>
|
||||
@@ -263,6 +274,17 @@
|
||||
type="text"
|
||||
bind:value={device.shutdown_cron}
|
||||
/>
|
||||
<div class="input-group-text form-switch">
|
||||
<label class="form-check-label" for="wake-shutdown-enabled">Enable</label>
|
||||
<input
|
||||
id="wake-shutdown-enabled"
|
||||
class="form-check-input mt-0 ms-1"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
aria-label="Enable/Disable wake cron"
|
||||
bind:checked={device.shutdown_cron_enabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text">Shutdown Cmd<sup>(2)</sup></span>
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
const result = await pb.collection('devices').getOne(id, {
|
||||
expand: 'ports'
|
||||
});
|
||||
if (!result.expand.ports) {
|
||||
result.expand.ports = [];
|
||||
}
|
||||
device = result;
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user