added scheduled wake events #3

This commit is contained in:
Maxi Quoß
2021-09-24 00:20:58 +02:00
parent 14692a0d9d
commit 2ff17c11ca
14 changed files with 331 additions and 57 deletions

View File

@@ -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
}
}

View File

@@ -1,6 +1,6 @@
from django.contrib import admin
from .models import Device
from wol.models import Device
class DeviceAdmin(admin.ModelAdmin):

View File

@@ -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()

View File

@@ -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),
),
]

View File

@@ -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)

View File

@@ -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())
]
]

View File

@@ -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 {

View File

@@ -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 = '<ul>' + openPorts.join('') + '</ul>';
// set wake btn
wakeButton.disabled = false;
wakeButton.addEventListener("click", function() {
setDeviceWake(device.id);
});
// set schedule button
scheduleModalButton.disabled = false;
}
function setDeviceWake(id) {
socket.send(id);
function wakeDevice(id) {
socket.send(JSON.stringify({
"message": "wake",
"id": id
}));
}
function addSchedule(id, name, datetime) {
if (!(datetime)) {
return;
}
console.log(datetime);
socket.send(JSON.stringify({
"message": "add_schedule",
"id": id,
"name": name,
"datetime": datetime
}));
}
function deleteSchedule(id, name) {
socket.send(JSON.stringify({
"message": "delete_schedule",
"id": id,
"name": name
}));
}
//
@@ -195,22 +241,6 @@ socket.onmessage = function (event) {
var message = JSON.parse(event.data);
console.log(message);
// set visitors element
if ("visitors" in message) {
if (message.visitors == 1) {
document.getElementById("visitors").innerHTML = message.visitors + ' visitor';
} else {
document.getElementById("visitors").innerHTML = message.visitors + ' visitors';
notif.show("Visitors updated", "There are currently " + message.visitors + " visitors", "is-info", 50000);
}
}
// set wake button
if ("wake" in message) {
document.getElementById(message.wake.pk + "-btn-wake").classList.add("is-loading");
notif.show("Wake started", message.wake.fields.name + " has been started.", "is-info", 5000);
}
// set devices up or down
if ("device" in message) {
if (message.device.up == true) {
@@ -219,7 +249,42 @@ socket.onmessage = function (event) {
setDeviceDown(message.device);
}
}
// set visitors element
if ("visitors" in message) {
if (message.visitors == 1) {
document.getElementById("visitors").innerHTML = message.visitors + ' visitor';
} else {
document.getElementById("visitors").innerHTML = message.visitors + ' visitors';
notif.show("Visitors updated", "There are currently " + message.visitors + " visitors", "is-info", 5000);
}
}
// set wake by client
if ("wake" in message) {
document.getElementById(message.wake.id + "-btn-wake").classList.add("is-loading");
notif.show("Wake started", message.wake.name + " has been started.", "is-info", 5000);
}
// set wake by schedule
if ("wake_schedule" in message) {
document.getElementById(message.wake_schedule.id + "-btn-wake").classList.add("is-loading");
document.getElementById(message.wake_schedule.id + "-schedule-notice").innerHTML = "";
notif.show("Scheduled wake started", message.wake_schedule.name + " has been started.", "is-info", 5000);
}
// add schedule
if ("add_schedule" in message) {
document.getElementById(message.add_schedule.id + "-schedule-notice").innerHTML = `<p>Scheduled wake set:<br>${message.add_schedule.datetime}</p>`;
notif.show("Schedule added", "A wake up event has been scheduled for " + message.add_schedule.name, "is-info", 5000);
}
// delete schedule
if ("delete_schedule" in message) {
document.getElementById(message.delete_schedule.id + "-schedule-notice").innerHTML = "";
notif.show("Schedule deleted", "A wake up event has been deleted for " + message.delete_schedule.name, "is-info", 5000);
}
}
socket.onclose = function (event) {
notif.show("Connection closed", "Websocket connection has closed", "is-danger", 5000);
}
}

View File

@@ -0,0 +1,57 @@
'use strict';
document.addEventListener('DOMContentLoaded', function () {
// Modals
var rootEl = document.documentElement;
var $modals = getAll('.modal');
var $modalButtons = getAll('.modal-button');
var $modalCloses = getAll('.modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button');
if ($modalButtons.length > 0) {
$modalButtons.forEach(function ($el) {
$el.addEventListener('click', function () {
var target = $el.dataset.target;
openModal(target);
});
});
}
if ($modalCloses.length > 0) {
$modalCloses.forEach(function ($el) {
$el.addEventListener('click', function () {
closeModals();
});
});
}
function openModal(target) {
var $target = document.getElementById(target);
rootEl.classList.add('is-clipped');
$target.classList.add('is-active');
}
function closeModals() {
rootEl.classList.remove('is-clipped');
$modals.forEach(function ($el) {
$el.classList.remove('is-active');
});
}
document.addEventListener('keydown', function (event) {
var e = event || window.event;
if (e.keyCode === 27) {
closeModals();
closeDropdowns();
}
});
// Utils
function getAll(selector) {
var parent = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : document;
return Array.prototype.slice.call(parent.querySelectorAll(selector), 0);
}
})

View File

@@ -6,8 +6,11 @@ import threading
from asgiref.sync import async_to_sync
from celery import shared_task
from channels.layers import get_channel_layer
from django.core import serializers
from django.utils import timezone
from .models import Device, Websocket
from wol.models import Device, Websocket
from wol.wake import wake
channel_layer = get_channel_layer()
@@ -65,3 +68,28 @@ def status():
d = WolDevice()
t = threading.Thread(target=d.start, args=(dev,))
t.start()
@shared_task
def scheduled_wakes():
if Websocket.objects.first().visitors == 0:
return
devices = Device.objects.all()
for dev in devices:
if dev.scheduled_wake and dev.scheduled_wake <= timezone.now():
wake(dev.mac, dev.ip, dev.netmask)
dev.scheduled_wake = None
dev.save()
async_to_sync(channel_layer.group_send)(
"wol", {
"type": "send_group",
"message": {
"wake_schedule": {
"id": dev.id,
"name": dev.name
}
}
}
)

View File

@@ -15,7 +15,7 @@
<link rel="stylesheet" href="{% static 'css/bulma-notifications.css' %}">
</head>
<body class="px-6">
<body class="px-4">
<section class="section">
<div class="container">
<div class="columns is-vcentered">
@@ -94,22 +94,55 @@
<h1 class="title">
Actions
</h1>
<div class="field is-grouped">
<p class="control">
<button class="button is-success" id="{{ dev.id }}-btn-wake" name="wake"
aria-label="wake" disabled>
<span>Wake</span>
<span class="icon is-small">
<i class="fas fa-play"></i>
</span>
</button>
</p>
<div class="buttons">
<button class="button is-success" id="{{ dev.id }}-btn-wake" name="wake"
aria-label="wake" onclick="wakeDevice({{ dev.id }})" disabled>
<span>Wake</span>
<span class="icon is-small">
<i class="fas fa-play"></i>
</span>
</button>
<button id="{{ dev.id }}-btn-schedule" class="button is-light modal-button" data-target="{{ dev.id }}-modal" aria-haspopup="true" disabled>
<span class="icon">
<i class="fas fa-clock"></i>
</span>
</button>
</div>
<div id="{{ dev.id }}-schedule-notice">
{% if dev.scheduled_wake %}
<p>
Scheduled wake set:<br>{{ dev.scheduled_wake|date:"c" }}
</p>
{% endif %}
</div>
<p></p>
</div>
</div>
</div>
</div>
</div>
<div id="{{ dev.id }}-modal" class="modal">
<div class="modal-background"></div>
<div class="modal-content">
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Schedule wake up for {{ dev.name }}</p>
<button class="delete" aria-label="close"></button>
</header>
<section class="modal-card-body">
<label for="{{ dev.id }}-input">Choose date and time:</label>
<input type="datetime-local" id="{{ dev.id }}-input">
</section>
<footer class="modal-card-foot">
<button class="button is-success" id="{{ dev.id }}-btn-schedule-save"
onclick="addSchedule({{ dev.id }}, '{{ dev.name }}', document.getElementById('{{ dev.id }}-input').value)">Save changes</button>
<button class="button is-light-dark">Cancel</button>
<button class="button is-danger is-pulled-right" onclick="deleteSchedule({{ dev.id }}, '{{ dev.name }}')">Delete</button>
</footer>
</div>
</div>
<button class="modal-close is-large" aria-label="close"></button>
</div>
{% endfor %}
</div>
</body>
@@ -117,6 +150,7 @@
<footer>
<script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
<script src="{% static 'js/main.js' %}"></script>
<script src="{% static 'js/modal.js' %}"></script>
</footer>
</html>

View File

@@ -1,6 +1,6 @@
from django.urls import path
from . import views
from wol import views
urlpatterns = [
path("", views.index, name="index")

View File

@@ -1,6 +1,6 @@
from django.shortcuts import render
from .models import Device, Websocket
from wol.models import Device, Websocket
def index(request):

9
app/wol/wake.py Normal file
View File

@@ -0,0 +1,9 @@
import ipaddress
import wakeonlan
def wake(mac, ip, netmask):
subnet = ipaddress.ip_network(
f"{ip}/{netmask}", strict=False).broadcast_address
wakeonlan.send_magic_packet(mac, ip_address=str(subnet))