diff --git a/backend/run.sh b/backend/run.sh index eb6b7712..026e0ff9 100644 --- a/backend/run.sh +++ b/backend/run.sh @@ -7,12 +7,6 @@ if [ "${DB_TYPE}" != "sqlite" ]; then fi /usr/bin/env bash ./wait-for-it.sh "${REDIS_HOST}":"${REDIS_PORT}" -t 300 -s -# init django -python manage.py makemigrations -python manage.py migrate -python manage.py collectstatic --noinput -python manage shell < setup.py - # set ping interval if [ -z "$PING_INTERVAL" ]; then PING_INTERVAL=5 @@ -23,6 +17,12 @@ elif [ "$PING_INTERVAL" -lt 5 ]; then PING_INTERVAL=5 fi +# init django +python manage.py makemigrations +python manage.py migrate +python manage.py collectstatic --noinput +python manage shell < setup.py + celery -A backend worker & celery -A backend beat & gunicorn --bind 0.0.0.0:"$DJANGO_PORT" --workers 4 backend.asgi:application -k uvicorn.workers.UvicornWorker diff --git a/backend/setup.py b/backend/setup.py index fccada81..ee7573e1 100644 --- a/backend/setup.py +++ b/backend/setup.py @@ -20,7 +20,8 @@ Websocket.objects.create(visitors=0) Settings.objects.update_or_create( id=1, defaults={ - "enable_notifications": os.getenv("ENABLE_NOTIFICATIONS") + "enable_notifications": os.getenv("ENABLE_NOTIFICATIONS"), + "interval": os.getenv("PING_INTERVAL") } ) diff --git a/backend/wol/admin.py b/backend/wol/admin.py index e88962a2..e659c4c8 100644 --- a/backend/wol/admin.py +++ b/backend/wol/admin.py @@ -11,17 +11,13 @@ class DeviceAdmin(admin.ModelAdmin): def ports(self, obj): return ", ".join([p.name for p in obj.port.all()]) - class PortAdmin(admin.ModelAdmin): list_display = ["number", "name"] search_fields = ["number", "name"] list_filter = ["number", "name"] class SettingsAdmin(admin.ModelAdmin): - list_display = ["enable_notifications", - "enable_console_logging", "sort_by", - "scan_address"] - + list_display = ["enable_notifications", "sort_by", "scan_address", "interval"] admin.site.register(Device, DeviceAdmin) diff --git a/backend/wol/consumers.py b/backend/wol/consumers.py index a0a56365..a8216882 100644 --- a/backend/wol/consumers.py +++ b/backend/wol/consumers.py @@ -1,14 +1,17 @@ +from array import array +import ipaddress import json +import subprocess 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 django_celery_beat.models import (ClockedSchedule, CrontabSchedule, + PeriodicTask) -from wol.models import Device, Port, Websocket +from wol.models import Device, Port, Settings, Websocket from wol.wake import wake -from django_celery_beat.models import PeriodicTask, ClockedSchedule, CrontabSchedule class WSConsumer(AsyncWebsocketConsumer): @@ -17,10 +20,12 @@ class WSConsumer(AsyncWebsocketConsumer): await self.accept() await self.send(text_data=json.dumps({ "type": "init", - "message": await self.get_all_devices() + "message": { + "devices": await self.get_all_devices(), + "settings": await self.get_settings() + } })) await self.add_visitor() - await self.channel_layer.group_send( "wol", { "type": "send_group", @@ -47,9 +52,7 @@ class WSConsumer(AsyncWebsocketConsumer): async def receive(self, text_data=None): received = json.loads(text_data) if received["type"] == "wake": - dev = await self.get_device(received["id"]) - wake(dev.mac, dev.ip, dev.netmask) - + await self.wake_device(received["id"]) await self.channel_layer.group_send( "wol", { "type": "send_group", @@ -74,9 +77,15 @@ class WSConsumer(AsyncWebsocketConsumer): await self.update_device(received["data"]) elif received["type"] == "update_port": await self.update_port(received["data"]) + elif received["type"] == "update_settings": + await self.update_settings(received["data"]) elif received["type"] == "celery": await self.celery_create_scheduled_wake(received["data"]) - + elif received["type"] == "scan_network": + await self.send(text_data=json.dumps({ + "type": "scan_network", + "message": await self.scan_network() + })) async def send_group(self, event): await self.send(json.dumps(event["message"])) @@ -98,10 +107,9 @@ class WSConsumer(AsyncWebsocketConsumer): return Websocket.objects.first().visitors @database_sync_to_async - def get_device(self, id): + def wake_device(self, id): dev = Device.objects.filter(id=id).first() - return dev - + wake(dev.mac, dev.ip, dev.netmask) @database_sync_to_async def get_all_devices(self): @@ -188,7 +196,6 @@ class WSConsumer(AsyncWebsocketConsumer): task.enabled = False task.save() - @database_sync_to_async def update_port(self, data): if data.get("name"): @@ -201,6 +208,28 @@ class WSConsumer(AsyncWebsocketConsumer): else: Port.objects.filter(number=data["number"]).delete() + @database_sync_to_async + def get_settings(self): + conf = Settings.objects.get(id=1) + data = { + "notifications": conf.enable_notifications, + "discovery": conf.scan_address, + "interval": conf.interval, + "scan_network": [] + } + return data + + @database_sync_to_async + def update_settings(self, data): + Settings.objects.update_or_create( + id=1, + defaults={ + "enable_notifications": data["notifications"], + "scan_address": data["discovery"], + "interval": data["interval"] + } + ) + @database_sync_to_async def celery_create_scheduled_wake(self, data): aware_time = make_aware(parse_datetime(data["time"])) @@ -213,3 +242,40 @@ class WSConsumer(AsyncWebsocketConsumer): task="wol.tasks.scheduled_wake", args=json.dumps([data["id"]]) ) + + @database_sync_to_async + def scan_network(self): + data = [] + conf = Settings.objects.get(id=1) + netmask = str(ipaddress.ip_network(conf.scan_address).netmask) + + if not conf.scan_address: + return + + p = subprocess.Popen( + ["nmap", "-sP", conf.scan_address], stdout=subprocess.PIPE) + out = p.communicate()[0].decode("utf-8") + ip_line = "Nmap scan report for" + mac_line = "MAC Address:" + + for line in out.splitlines(): + if line.startswith(ip_line): + line_splitted = line.split() + if len(line_splitted) == 6: + name = line_splitted[4] + ip = line_splitted[5] + ip = ip.replace("(", "") + ip = ip.replace(")", "") + else: + name = "Unknown" + ip = line_splitted[4] + elif line.startswith(mac_line): + line_splitted = line.split() + mac = line_splitted[2] + data.append({ + "name": name, + "ip": ip, + "netmask": netmask, + "mac": mac + }) + return data diff --git a/backend/wol/migrations/0001_initial.py b/backend/wol/migrations/0001_initial.py index cf084e13..afc50341 100644 --- a/backend/wol/migrations/0001_initial.py +++ b/backend/wol/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0.2 on 2022-02-16 20:54 +# Generated by Django 3.2.12 on 2022-02-20 18:24 from django.db import migrations, models @@ -12,14 +12,11 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='Device', + name='Port', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.SlugField(default='Unknown', max_length=100)), - ('ip', models.GenericIPAddressField()), - ('mac', models.CharField(max_length=17)), - ('netmask', models.CharField(default='255.255.255.0', max_length=15)), - ('scheduled_wake', models.DateTimeField(blank=True, null=True)), + ('number', models.PositiveIntegerField()), + ('name', models.SlugField()), ], ), migrations.CreateModel( @@ -39,4 +36,16 @@ class Migration(migrations.Migration): ('visitors', models.PositiveSmallIntegerField(default=0)), ], ), + migrations.CreateModel( + name='Device', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.SlugField(default='Unknown', max_length=100)), + ('ip', models.GenericIPAddressField()), + ('mac', models.CharField(max_length=17)), + ('netmask', models.CharField(default='255.255.255.0', max_length=15)), + ('scheduled_wake', models.DateTimeField(blank=True, null=True)), + ('port', models.ManyToManyField(blank=True, to='wol.Port')), + ], + ), ] diff --git a/backend/wol/migrations/0002_port.py b/backend/wol/migrations/0002_port.py deleted file mode 100644 index 586e9da7..00000000 --- a/backend/wol/migrations/0002_port.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 4.0.2 on 2022-02-18 18:20 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('wol', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Port', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('number', models.PositiveIntegerField()), - ('name', models.SlugField()), - ], - ), - ] diff --git a/backend/wol/migrations/0002_remove_settings_enable_console_logging.py b/backend/wol/migrations/0002_remove_settings_enable_console_logging.py new file mode 100644 index 00000000..31696609 --- /dev/null +++ b/backend/wol/migrations/0002_remove_settings_enable_console_logging.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.12 on 2022-02-20 19:41 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('wol', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='settings', + name='enable_console_logging', + ), + ] diff --git a/backend/wol/migrations/0003_device_port.py b/backend/wol/migrations/0003_device_port.py deleted file mode 100644 index e03fa9bf..00000000 --- a/backend/wol/migrations/0003_device_port.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 4.0.2 on 2022-02-18 18:28 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('wol', '0002_port'), - ] - - operations = [ - migrations.AddField( - model_name='device', - name='port', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='wol.port'), - ), - ] diff --git a/backend/wol/migrations/0003_settings_interval.py b/backend/wol/migrations/0003_settings_interval.py new file mode 100644 index 00000000..7db62a5c --- /dev/null +++ b/backend/wol/migrations/0003_settings_interval.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.12 on 2022-02-20 19:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wol', '0002_remove_settings_enable_console_logging'), + ] + + operations = [ + migrations.AddField( + model_name='settings', + name='interval', + field=models.PositiveSmallIntegerField(blank=True, null=True), + ), + ] diff --git a/backend/wol/migrations/0004_remove_device_port_device_port.py b/backend/wol/migrations/0004_remove_device_port_device_port.py deleted file mode 100644 index 17a0766e..00000000 --- a/backend/wol/migrations/0004_remove_device_port_device_port.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.0.2 on 2022-02-18 18:31 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('wol', '0003_device_port'), - ] - - operations = [ - migrations.RemoveField( - model_name='device', - name='port', - ), - migrations.AddField( - model_name='device', - name='port', - field=models.ManyToManyField(blank=True, null=True, to='wol.Port'), - ), - ] diff --git a/backend/wol/migrations/0005_alter_port_unique_together.py b/backend/wol/migrations/0005_alter_port_unique_together.py deleted file mode 100644 index 45cb441c..00000000 --- a/backend/wol/migrations/0005_alter_port_unique_together.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.0.2 on 2022-02-18 19:06 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('wol', '0004_remove_device_port_device_port'), - ] - - operations = [ - migrations.AlterUniqueTogether( - name='port', - unique_together={('number', 'name')}, - ), - ] diff --git a/backend/wol/migrations/0006_alter_port_unique_together_alter_device_port.py b/backend/wol/migrations/0006_alter_port_unique_together_alter_device_port.py deleted file mode 100644 index 4a059b53..00000000 --- a/backend/wol/migrations/0006_alter_port_unique_together_alter_device_port.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.0.2 on 2022-02-18 19:14 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('wol', '0005_alter_port_unique_together'), - ] - - operations = [ - migrations.AlterUniqueTogether( - name='port', - unique_together=set(), - ), - migrations.AlterField( - model_name='device', - name='port', - field=models.ManyToManyField(to='wol.Port'), - ), - ] diff --git a/backend/wol/migrations/0007_alter_device_port.py b/backend/wol/migrations/0007_alter_device_port.py deleted file mode 100644 index 6f7a0584..00000000 --- a/backend/wol/migrations/0007_alter_device_port.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.0.2 on 2022-02-19 11:32 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('wol', '0006_alter_port_unique_together_alter_device_port'), - ] - - operations = [ - migrations.AlterField( - model_name='device', - name='port', - field=models.ManyToManyField(blank=True, to='wol.Port'), - ), - ] diff --git a/backend/wol/models.py b/backend/wol/models.py index 1d9419e5..04c2a5de 100644 --- a/backend/wol/models.py +++ b/backend/wol/models.py @@ -21,6 +21,6 @@ class Websocket(models.Model): class Settings(models.Model): enable_notifications = models.BooleanField(default=True) - enable_console_logging = models.BooleanField(default=False) sort_by = models.SlugField(default="name") scan_address = models.GenericIPAddressField(null=True, blank=True) + interval = models.PositiveSmallIntegerField(null=True, blank=True) diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 66cdcfad..3ff754e5 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -6,17 +6,19 @@ let visitors = 0; let devices = []; + let settings = {} onMount(() => { store.subscribe(currentMessage => { if (currentMessage.type == "init") { - // create placeholder devices - for (let index = 0; index < currentMessage.message.length; index++) { - const element = currentMessage.message[index]; + // create devices + for (let index = 0; index < currentMessage.message.devices.length; index++) { + const element = currentMessage.message.devices[index]; devices.push(element); devices = devices; - devices.sort(compare) } + devices.sort(compare); + settings = currentMessage.message.settings; } else if (currentMessage.type == "status") { // set device array and sort const index = devices.findIndex(x => x.id == currentMessage.message.id); @@ -45,17 +47,22 @@ // delete device const devCol = document.querySelector(`#device-col-${currentMessage.message}`); devCol.remove(); + } else if (currentMessage.type == "scan_network") { + // set scanned network devices + if (!currentMessage.message) { + return + } + settings["scan_network"] = currentMessage.message; + const btnScan = document.querySelector("#btnScan"); + const btnScanSpinner = document.querySelector("#btnScanSpinner"); + const btnScanText = document.querySelector("#btnScanText"); + btnScan.disabled = false; + btnScanSpinner.classList.add("d-none"); + btnScanText.innerText = "Scan"; } }) }) - function onSendMessage() { - if (message.length > 0) { - store.sendMessage(message); - message = ""; - } - } - function setUp(id) { const dot = document.querySelector(`#dot-${id}`) const spinner = document.querySelector(`#spinner-${id}`); @@ -112,7 +119,7 @@
- +
{#each devices as device} diff --git a/frontend/src/components/DeviceCard.svelte b/frontend/src/components/DeviceCard.svelte index 7385cdcb..131b988e 100644 --- a/frontend/src/components/DeviceCard.svelte +++ b/frontend/src/components/DeviceCard.svelte @@ -1,7 +1,6 @@