diff --git a/app/django_wol/celery.py b/app/django_wol/celery.py index bed70158..f55ec86c 100644 --- a/app/django_wol/celery.py +++ b/app/django_wol/celery.py @@ -11,6 +11,10 @@ app.conf.beat_schedule = { "ping_devices_5s": { "task": "wol.tasks.status", "schedule": 5 + }, + "scheduled_wakes_1s": { + "task": "wol.tasks.scheduled_wakes", + "schedule": 1 } } diff --git a/app/wol/admin.py b/app/wol/admin.py index d71ec372..a929965b 100644 --- a/app/wol/admin.py +++ b/app/wol/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from .models import Device +from wol.models import Device class DeviceAdmin(admin.ModelAdmin): diff --git a/app/wol/consumers.py b/app/wol/consumers.py index a0e65b79..9ca5f5f2 100644 --- a/app/wol/consumers.py +++ b/app/wol/consumers.py @@ -1,12 +1,13 @@ -import ipaddress import json - -import wakeonlan +from datetime import datetime, tzinfo from channels.db import database_sync_to_async from channels.generic.websocket import AsyncWebsocketConsumer from django.core import serializers +from django.utils.dateparse import parse_datetime +from django.utils.timezone import make_aware -from .models import Device, Websocket +from wol.models import Device, Websocket +from wol.wake import wake class WSConsumer(AsyncWebsocketConsumer): @@ -36,20 +37,56 @@ class WSConsumer(AsyncWebsocketConsumer): ) async def receive(self, text_data=None, bytes_data=None): - dev = await self.get_json_from_device_id(text_data) + received = json.loads(text_data) - subnet = ipaddress.ip_network( - f"{dev['fields']['ip']}/{dev['fields']['netmask']}", strict=False).broadcast_address - wakeonlan.send_magic_packet(dev['fields']["mac"], ip_address=str(subnet)) + if received["message"] == "wake": + dev = await self.get_json_from_device_id(received["id"]) + wake(dev["fields"]["mac"], dev["fields"] + ["ip"], dev["fields"]["netmask"]) - await self.channel_layer.group_send( - "wol", { - "type": "send_group", - "message": { - "wake": dev + await self.channel_layer.group_send( + "wol", { + "type": "send_group", + "message": { + "wake": { + "id": dev["pk"], + "name": dev["fields"]["name"] + } + } } - } - ) + ) + elif received["message"] == "add_schedule": + if not received["datetime"]: + return + d = make_aware(parse_datetime(received["datetime"])) + print(d.isoformat()) + await self.add_schedule(received["id"], d) + await self.channel_layer.group_send( + "wol", { + "type": "send_group", + "message": { + "add_schedule": { + "id": received["id"], + "name": received["name"], + "datetime": str(d.isoformat()) + } + } + } + ) + elif received["message"] == "delete_schedule": + await self.delete_schedule(received["id"]) + await self.channel_layer.group_send( + "wol", { + "type": "send_group", + "message": { + "delete_schedule": { + "id": received["id"], + "name": received["name"] + } + } + } + ) + async def send_status(self, event): await self.send(event["status"]) @@ -77,3 +114,15 @@ class WSConsumer(AsyncWebsocketConsumer): def get_json_from_device_id(self, id): dev = Device.objects.filter(id=id) return serializers.serialize("python", dev)[0] + + @database_sync_to_async + def add_schedule(self, id, datetime): + dev = Device.objects.filter(id=id).get() + dev.scheduled_wake = datetime + dev.save() + + @database_sync_to_async + def delete_schedule(self, id): + dev = Device.objects.filter(id=id).get() + dev.scheduled_wake = None + dev.save() diff --git a/app/wol/migrations/0007_device_scheduled_wake.py b/app/wol/migrations/0007_device_scheduled_wake.py new file mode 100644 index 00000000..4835656b --- /dev/null +++ b/app/wol/migrations/0007_device_scheduled_wake.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.7 on 2021-09-23 16:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wol', '0006_websocket'), + ] + + operations = [ + migrations.AddField( + model_name='device', + name='scheduled_wake', + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/app/wol/models.py b/app/wol/models.py index acdc1623..26c2b99e 100644 --- a/app/wol/models.py +++ b/app/wol/models.py @@ -7,6 +7,7 @@ class Device(models.Model): ip = models.GenericIPAddressField() mac = models.CharField(max_length=17) netmask = models.CharField(max_length=15, default="255.255.255.0", blank=False, null=False) + scheduled_wake = models.DateTimeField(blank=True, null=True) class Websocket(models.Model): visitors = models.PositiveSmallIntegerField(blank=False, null=False, default=0) diff --git a/app/wol/routing.py b/app/wol/routing.py index 3927f9f4..bdc89a69 100644 --- a/app/wol/routing.py +++ b/app/wol/routing.py @@ -1,7 +1,7 @@ from django.urls import path -from . consumers import WSConsumer +from wol.consumers import WSConsumer ws_urlpatterns = [ path("wol/", WSConsumer.as_asgi()) -] \ No newline at end of file +] diff --git a/app/wol/static/css/style.css b/app/wol/static/css/style.css index ed47a4cd..fbd06ba0 100644 --- a/app/wol/static/css/style.css +++ b/app/wol/static/css/style.css @@ -49,6 +49,7 @@ .button.is-static { border-color: transparent; + color: inherit; } .notification { @@ -61,6 +62,10 @@ box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.3); } +.modal-card, .modal-content { + margin: 0 auto; +} + @media (prefers-color-scheme: dark) { .box { background-color: var(--box-bg); @@ -69,7 +74,11 @@ .button.is-static { background-color: #dbdbdb; color: #363636; - } + } + + .modal-card-body { + background-color: black; + } } @keyframes green-pulse { diff --git a/app/wol/static/js/main.js b/app/wol/static/js/main.js index 5afa690b..432217be 100644 --- a/app/wol/static/js/main.js +++ b/app/wol/static/js/main.js @@ -88,6 +88,25 @@ class BulmaNotification { let notif; window.onload = () => { notif = new BulmaNotification(); + var now = new Date(); + var utcString = now.toISOString().substring(0, 19); + var year = now.getFullYear(); + var month = now.getMonth() + 1; + var day = now.getDate(); + var hour = now.getHours(); + var minute = now.getMinutes(); + var second = now.getSeconds(); + var localDatetime = year + "-" + + (month < 10 ? "0" + month.toString() : month) + "-" + + (day < 10 ? "0" + day.toString() : day) + "T" + + (hour < 10 ? "0" + hour.toString() : hour) + ":" + + (minute < 10 ? "0" + minute.toString() : minute) + + utcString.substring(16, 19); + var datetimeFields = document.querySelectorAll('[id*=-input]'); + for (let index = 0; index < datetimeFields.length; index++) { + const element = datetimeFields[index]; + element.value = localDatetime; + } }; // @@ -100,7 +119,8 @@ function setDeviceUp(device) { var statusDot = document.getElementById(device.id + "-dot"); var statusPorts = document.getElementById(device.id + "-ports"); var wakeButton = document.getElementById(device.id + "-btn-wake"); - + var scheduleModalButton = document.getElementById(device.id + "-btn-schedule"); + // check if device was down before if (statusDot.classList.contains("dot-down")) { notif.show("Device now up!", device.name + " is now up.", "is-success", 5000); @@ -141,6 +161,8 @@ function setDeviceUp(device) { // set wake btn wakeButton.classList.remove("is-loading"); wakeButton.disabled = true; + // set schedule button + scheduleModalButton.disabled = false; } function setDeviceDown(device) { @@ -149,12 +171,13 @@ function setDeviceDown(device) { var statusDot = document.getElementById(device.id + "-dot"); var statusPorts = document.getElementById(device.id + "-ports"); var wakeButton = document.getElementById(device.id + "-btn-wake"); + var scheduleModalButton = document.getElementById(device.id + "-btn-schedule"); // check if device was up before if (statusDot.classList.contains("dot-up")) { notif.show("Device now down!", device.name + " is now down.", "is-danger", 5000); } - + // clear current animation statusDot.style.animation = "none"; statusDot.offsetWidth; @@ -177,13 +200,36 @@ function setDeviceDown(device) { statusPorts.innerHTML = '
Scheduled wake set:
${message.add_schedule.datetime}
- -
+ +
+ Scheduled wake set:
{{ dev.scheduled_wake|date:"c" }}
+