rework objects/arrays, better modal, remove drf

This commit is contained in:
Maxi Quoß
2022-02-20 00:30:12 +01:00
parent 86d0e0f86b
commit 16c1d5b635
13 changed files with 158 additions and 173 deletions

View File

@@ -39,8 +39,7 @@ INSTALLED_APPS = [
'django.contrib.messages',
'django.contrib.staticfiles',
'wol',
'channels',
'rest_framework'
'channels'
]
MIDDLEWARE = [

View File

@@ -14,9 +14,8 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('wol.urls')),
path('admin/', admin.site.urls)
]

View File

@@ -16,9 +16,10 @@ class WSConsumer(AsyncWebsocketConsumer):
await self.accept()
await self.send(text_data=json.dumps({
"type": "init",
"message": await self.get_device_count()
"message": await self.get_all_devices()
}))
await self.add_visitor()
await self.channel_layer.group_send(
"wol", {
"type": "send_group",
@@ -36,7 +37,8 @@ class WSConsumer(AsyncWebsocketConsumer):
"wol", {
"type": "send_group",
"message": {
"visitors": await self.get_visitors()
"type": "visitor",
"message": await self.get_visitors()
}
}
)
@@ -89,13 +91,7 @@ class WSConsumer(AsyncWebsocketConsumer):
}
}
)
elif received["type"] == "get_ports":
ports = await self.get_ports()
await self.send(text_data=json.dumps({
"type": "get_ports",
"message": ports
}))
elif received["type"] == "delete":
elif received["type"] == "delete_device":
await self.delete_device(received["id"])
await self.channel_layer.group_send(
"wol", {
@@ -106,8 +102,10 @@ class WSConsumer(AsyncWebsocketConsumer):
}
}
)
elif received["type"] == "update":
elif received["type"] == "update_device":
await self.update_device(received["data"])
elif received["type"] == "update_port":
await self.update_port(received["data"])
async def send_group(self, event):
@@ -130,9 +128,27 @@ class WSConsumer(AsyncWebsocketConsumer):
return Websocket.objects.first().visitors
@database_sync_to_async
def get_device_count(self):
count = Device.objects.count()
return count
def get_all_devices(self):
devices = Device.objects.all()
d = []
for dev in devices:
data = {
"id": dev.id,
"name": dev.name,
"ip": dev.ip,
"mac": dev.mac,
"netmask": dev.netmask,
"ports": []
}
for p in Port.objects.all().order_by("number"):
data["ports"].append({
"number": p.number,
"name": p.name,
"checked": False,
"open": False
})
d.append(data)
return d
@database_sync_to_async
def delete_device(self, id):
@@ -150,14 +166,25 @@ class WSConsumer(AsyncWebsocketConsumer):
"netmask": data["netmask"]
}
)
for _, value in data["ports"].items():
if value["checked"]:
p, _ = Port.objects.get_or_create(number=value["number"], name=value["name"])
for port in data["ports"]:
if port["checked"]:
p, _ = Port.objects.get_or_create(number=port["number"], name=port["name"])
obj.port.add(p)
else:
p = Port.objects.get(number=value["number"])
p = Port.objects.get(number=port["number"])
obj.port.remove(p)
@database_sync_to_async
def update_port(self, data):
if data.get("name"):
Port.objects.update_or_create(
number=data["number"],
defaults={
"name": data["name"]
}
)
else:
Port.objects.filter(number=data["number"]).delete()
@database_sync_to_async
def get_json_from_device_id(self, id):
@@ -175,8 +202,3 @@ class WSConsumer(AsyncWebsocketConsumer):
dev = Device.objects.filter(id=id).get()
dev.scheduled_wake = None
dev.save()
@database_sync_to_async
def get_ports(self):
ports = Port.objects.all().order_by("name")
return serializers.serialize("python", ports)

View File

@@ -1,25 +0,0 @@
from rest_framework import serializers, viewsets
from wol.models import Settings, Device, Port
class SettingsSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Settings
fields = "__all__"
class SettingsViewSet(viewsets.ModelViewSet):
def get_queryset(self):
queryset = Settings.objects.first()
return queryset
serializer_class = SettingsSerializer
class PortSerializer(serializers.ModelSerializer):
class Meta:
model = Port
fields = ["number", "name"]
class DeviceSerializer(serializers.ModelSerializer):
port = PortSerializer(many=True, read_only=True)
class Meta:
model = Device
fields = ["name", "ip", "mac", "netmask", "scheduled_wake", "port"]

View File

@@ -31,33 +31,32 @@ class WolDevice:
def start(self, dev):
data = {
"device": {
"id": dev.id,
"name": dev.name,
"ip": dev.ip,
"mac": dev.mac,
"netmask": dev.netmask,
"up": False,
"ports": {}
}
"id": dev.id,
"name": dev.name,
"ip": dev.ip,
"mac": dev.mac,
"netmask": dev.netmask,
"up": False,
"ports": []
}
for p in Port.objects.all():
data["device"]["ports"][str(p.number)] = {
for p in Port.objects.all().order_by("number"):
data["ports"].append({
"number": p.number,
"name": p.name,
"checked": False,
"checked": True if p in dev.port.all() else False,
"open": False
}
})
if self.ping_device(dev.ip):
data["device"]["up"] = True
data["up"] = True
for port in dev.port.all():
index = next(i for i, d in enumerate(data["ports"]) if d["number"] == port.number)
if self.check_port(dev.ip, port.number):
data["device"]["ports"][str(port.number)]["checked"] = True
data["device"]["ports"][str(port.number)]["open"] = True
data["ports"][index]["checked"] = True
data["ports"][index]["open"] = True
else:
data["device"]["ports"][str(port.number)]["checked"] = True
data["device"]["ports"][str(port.number)]["open"] = False
data["ports"][index]["checked"] = True
data["ports"][index]["open"] = False
async_to_sync(channel_layer.group_send)(
"wol", {"type": "send_group", "message": {

View File

@@ -1,11 +0,0 @@
from django.urls import path, include
from rest_framework import routers
from wol.serializers import SettingsViewSet
router = routers.DefaultRouter()
router.register(r'settings', SettingsViewSet, basename="settings")
urlpatterns = [
path('api/', include(router.urls)),
]

View File

@@ -1,3 +0,0 @@
from django.shortcuts import render
# Create your views here.

View File

@@ -5,7 +5,7 @@
"scripts": {
"build": "rollup -c",
"dev": "rollup -c -w",
"start": "sirv public --no-clear"
"start": "sirv public --no-clear --host"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^17.0.0",

View File

@@ -3,35 +3,36 @@
import store from './store.js';
import Navbar from "./components/Navbar.svelte";
import DeviceCard from "./components/DeviceCard.svelte"
import DummyDeviceCard from "./components/DummyDeviceCard.svelte"
let visitors = 0;
let devices = [];
let dummyDevices = 0;
let ports = [];
onMount(() => {
store.subscribe(currentMessage => {
if (currentMessage.type == "init") {
// create placeholder devices
dummyDevices = currentMessage.message;
for (let index = 0; index < currentMessage.message.length; index++) {
const element = currentMessage.message[index];
devices.push(element);
devices = devices;
devices.sort(compare)
}
} else if (currentMessage.type == "status") {
// set device array and sort
dummyDevices -= 1;
const index = devices.findIndex(x => x.id == currentMessage.message.device.id);
const index = devices.findIndex(x => x.id == currentMessage.message.id);
if (devices.length === 0 || index === -1) {
devices.push(currentMessage.message.device);
devices.push(currentMessage.message);
devices = devices;
} else {
devices[index] = currentMessage.message.device;
devices[index] = currentMessage.message;
}
devices.sort(compare)
// set device status
if (currentMessage.message.device.up == true) {
setUp(currentMessage.message.device.id);
if (currentMessage.message.up == true) {
setUp(currentMessage.message.id);
} else {
setDown(currentMessage.message.device.id);
setDown(currentMessage.message.id);
}
} else if (currentMessage.type == "wake") {
// set device waking
@@ -40,9 +41,6 @@
} else if (currentMessage.type == "visitor") {
// update visitor count
visitors = currentMessage.message;
} else if (currentMessage.type == "get_ports") {
// all available ports in db
ports = [...currentMessage.message];
} else if (currentMessage.type == "delete") {
// delete device
const devCol = document.querySelector(`#device-col-${currentMessage.message}`);
@@ -115,15 +113,10 @@
<main>
<Navbar visitors={visitors} />
<div class="container">
<div class="container mb-3">
<div class="row">
{#if dummyDevices >= 1}
{#each Array(dummyDevices) as _, i}
<DummyDeviceCard/>
{/each}
{/if}
{#each devices as device}
<DeviceCard device={device} ports={ports}/>
<DeviceCard device={device}/>
{/each}
</div>
</div>

View File

@@ -1,9 +1,9 @@
<script>
import store from '../store.js';
export let device;
export let ports;
let modalDevice = JSON.parse(JSON.stringify(device));
let customPort = {}
function wake(id) {
store.sendMessage({
@@ -12,28 +12,60 @@
})
}
function openModal() {
modalDevice = Object.assign({}, device)
function deleteDevice(id) {
store.sendMessage({
type: "get_ports"
})
}
function del(id) {
store.sendMessage({
type: "delete",
type: "delete_device",
id: id
})
}
function update(data) {
function updateDevice(data) {
device = data;
store.sendMessage({
type: "update",
type: "update_device",
data: data
})
}
function updatePort() {
if (!customPort.number) {
return
}
const index = modalDevice.ports.findIndex(x => x.number == customPort.number);
if (customPort.name) {
// add port
if (index === -1) {
customPort.checked = true;
modalDevice.ports.push(customPort);
} else {
customPort.checked = modalDevice.ports[index].checked;
modalDevice.ports[index] = JSON.parse(JSON.stringify(customPort));
}
} else {
// delete port
if (index >= 0) {
modalDevice.ports.splice(index, 1)
}
}
modalDevice = modalDevice;
// send to backend
store.sendMessage({
type: "update_port",
data: customPort
})
}
function openModal() {
modalDevice = Object.assign({}, device)
}
function validatePort() {
if (typeof customPort.number != "number") {
customPort.number = 1
} else if (customPort.number > 65535) {
customPort.number = 65535
}
}
</script>
<div id="device-col-{device.id}" class="col-xs-12 col-sm-6 col-md-4 col-lg-3 g-4">
@@ -43,7 +75,11 @@
<div class="col-auto me-auto">
<div id="spinner-{device.id}" class="spinner-border color-warning d-none" role="status"></div>
<div class="hover" on:click="{() => wake(device.id)}" role="button">
<i id="dot-{device.id}" class="fa-solid fa-power-off fa-2x {device.up ? 'success' : 'danger'}"></i>
{#if device.up === true || device.up === false}
<i id="dot-{device.id}" class="fa-solid fa-power-off fa-2x {device.up ? 'success' : 'danger'}"></i>
{:else}
<i id="dot-{device.id}" class="fa-solid fa-power-off fa-2x text-muted"></i>
{/if}
</div>
</div>
<div class="col-auto hover" data-bs-toggle="modal" data-bs-target="#device-modal-{device.id}" role="button" on:click="{() => openModal()}">
@@ -53,7 +89,7 @@
<h5 class="card-title fw-bold my-2">{device.name}</h5>
<h6 class="card-subtitle mb-2 text-muted">{device.ip}</h6>
<ul class="list-group">
{#each Object.entries(device.ports) as [_, port]}
{#each device.ports as port}
{#if port.checked === true}
<li class="list-group-item">
<i class="fa-solid fa-circle align-middle {port.open ? 'success' : 'danger'}"></i>
@@ -66,7 +102,7 @@
</div>
<div class="modal fade" id="device-modal-{device.id}" tabindex="-1" aria-labelledby="device-modal-{device.id}-label" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-dialog modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title fw-bold" id="device-modal-{modalDevice.id}-label">{modalDevice.name}</h5>
@@ -75,9 +111,19 @@
<div class="modal-body">
<form>
<h5 class="fw-bold">General</h5>
<div class="mb-3">
<label for="inputName{modalDevice.id}" class="form-label">Device name</label>
<input type="text" class="form-control" id="inputName{modalDevice.id}" bind:value="{modalDevice.name}">
<div class="row">
<div class="col-sm">
<div class="mb-3">
<label for="inputName{modalDevice.id}" class="form-label">Device name</label>
<input type="text" class="form-control" id="inputName{modalDevice.id}" bind:value="{modalDevice.name}">
</div>
</div>
<div class="col-sm">
<div class="mb-3">
<label for="inputMac{modalDevice.id}" class="form-label">Mac address</label>
<input type="text" class="form-control" id="inputMac{modalDevice.mac}" bind:value="{modalDevice.mac}">
</div>
</div>
</div>
<div class="row">
<div class="col-sm">
@@ -94,17 +140,25 @@
</div>
</div>
<h5 class="fw-bold">Ports</h5>
{#each ports as port}
{#each modalDevice.ports as port}
<div class="form-check">
<input type="checkbox" class="form-check-input" id="{device.id}-port-{port.fields.name}" bind:checked="{modalDevice.ports[port.fields.number]['checked']}" >
<label class="form-check-label" for="{device.id}-port-{port.fields.name}">{port.fields.name}</label>
<input type="checkbox" class="form-check-input" id="{device.id}-port-{port.name}" bind:checked="{port['checked']}">
<label class="form-check-label" for="{device.id}-port-{port.name}">{port.name} <span class="text-muted">({port.number})</span></label>
</div>
{/each}
<label class="form-label mt-3" for="{device.id}-custom-port">Custom port</label>
<div class="input-group mb-2">
<input type="text" id="{device.id}-custom-port" class="form-control" placeholder="Name" aria-label="Name" aria-describedby="button-addon2" bind:value={customPort.name} required>
<input type="number" min="1" max="65535" class="form-control" placeholder="Port" aria-label="Port" aria-describedby="button-addon2" bind:value={customPort.number} on:input={validatePort} required>
<button class="btn btn-outline-secondary" type="button" id="button-addon2" on:click="{updatePort}">Update Port</button>
</div>
<p>Port must be between 1 and 65535. Enter the same port with a differen name to change it. Leave name empty to delete port.</p>
<h5 class="fw-bold">Scheduled wake</h5>
</form>
</div>
<div class="modal-footer justify-content-between">
<button type="button" class="btn btn-outline-danger" data-bs-dismiss="modal" on:click="{() => del(modalDevice.id)}">Delete</button>
<button type="button" class="btn btn-outline-success" on:click="{() => update(modalDevice)}">Save changes</button>
<button type="button" class="btn btn-outline-danger" data-bs-dismiss="modal" on:click="{() => deleteDevice(modalDevice.id)}">Delete</button>
<button type="button" class="btn btn-outline-success" data-bs-dismiss="modal" on:click="{() => updateDevice(modalDevice)}">Save changes</button>
</div>
</div>
</div>

View File

@@ -1,41 +0,0 @@
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 g-4">
<div class="card border-0 p-3 pt-2" >
<div class="card-body">
<div class="row">
<div class="col-auto me-auto">
<div>
<i class="fa-solid fa-power-off fa-2x"></i>
</div>
</div>
<div class="col-auto">
<div>
<i class="fa-solid fa-ellipsis-vertical fa-2x"></i>
</div>
</div>
</div>
<h5 class="card-title fw-bold my-2 placeholder-glow">
<span class="placeholder col-7"></span>
</h5>
<h6 class="card-subtitle mb-2 text-muted placeholder-glow">
<span class="placeholder col-4"></span>
</h6>
</div>
</div>
</div>
<style lang="scss">
@import "../variables.scss";
.card {
border-radius: 2em;
}
.fa-2x {
font-size: 1.5em;
}
.fa-circle {
font-size: 0.8em;
color: $success;
}
</style>

View File

@@ -6,7 +6,7 @@
</script>
<nav class="navbar navbar-expand-lg bg-light">
<nav class="navbar navbar-expand-sm bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="/">
<img src="/favicon.png" alt="Logo" width="24" height="24">

View File

@@ -2,7 +2,6 @@ celery[redis]
channels
channels-redis
django
django-rest-framework
gunicorn
mysqlclient
psycopg2-binary