mirror of
https://github.com/seriousm4x/UpSnap.git
synced 2026-07-29 17:02:56 -04:00
implement scan network
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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()),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -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',
|
||||
),
|
||||
]
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
18
backend/wol/migrations/0003_settings_interval.py
Normal file
18
backend/wol/migrations/0003_settings_interval.py
Normal file
@@ -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),
|
||||
),
|
||||
]
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
@@ -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')},
|
||||
),
|
||||
]
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
@@ -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)
|
||||
|
||||
@@ -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 @@
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<Navbar visitors={visitors} />
|
||||
<Navbar settings={settings} visitors={visitors} />
|
||||
<div class="container mb-3">
|
||||
<div class="row">
|
||||
{#each devices as device}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script>
|
||||
import store from '../store.js';
|
||||
export let device;
|
||||
console.log(device);
|
||||
|
||||
let modalDevice = JSON.parse(JSON.stringify(device));
|
||||
let customPort = {}
|
||||
|
||||
@@ -1,38 +1,79 @@
|
||||
<script>
|
||||
import store from '../store.js';
|
||||
export let visitors;
|
||||
export let settings;
|
||||
|
||||
let addDevice = {}
|
||||
let addDevice = {
|
||||
cron: {
|
||||
enabled: false,
|
||||
value: ""
|
||||
}
|
||||
}
|
||||
|
||||
function updateDevice() {
|
||||
if (Object.keys(addDevice).length < 4) {
|
||||
function updateDevice(data) {
|
||||
if (Object.keys(data).length < 4) {
|
||||
return
|
||||
}
|
||||
store.sendMessage({
|
||||
type: "update_device",
|
||||
data: addDevice
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
function updateSettings() {
|
||||
store.sendMessage({
|
||||
type: "update_settings",
|
||||
data: settings
|
||||
})
|
||||
}
|
||||
|
||||
function scanNetwork() {
|
||||
store.sendMessage({
|
||||
type: "scan_network"
|
||||
})
|
||||
const btnScan = document.querySelector("#btnScan");
|
||||
const btnScanSpinner = document.querySelector("#btnScanSpinner");
|
||||
const btnScanText = document.querySelector("#btnScanText");
|
||||
btnScan.disabled = true;
|
||||
btnScanSpinner.classList.remove("d-none");
|
||||
btnScanText.innerText = "Scanning...";
|
||||
}
|
||||
|
||||
function addScan(i) {
|
||||
document.querySelector(`#btnAdd${i}`).disabled = true;
|
||||
const dev = settings.scan_network[i];
|
||||
updateDevice(dev);
|
||||
}
|
||||
</script>
|
||||
|
||||
<nav class="navbar navbar-expand-sm navbar-light bg-light">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">
|
||||
<img src="/favicon.png" alt="Logo" width="24" height="24">
|
||||
<img src="/favicon.png" alt="Logo" width="24" height="24" class="me-2">
|
||||
UpSnap
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
|
||||
<div class="navbar-nav me-auto">
|
||||
<a class="nav-link {window.location.pathname == "/" ? 'active' : ''}" href="/">Devices</a>
|
||||
<a class="nav-link {window.location.pathname == "/settings" ? 'active' : ''}" href="/settings">Settings</a>
|
||||
</div>
|
||||
<span class="d-flex">
|
||||
<button type="button" class="btn btn-light py-2 px-3 me-2" data-bs-toggle="modal" data-bs-target="#addDevice">
|
||||
Add device<i class="ms-1 fa-solid fa-plus"></i>
|
||||
</button>
|
||||
<span class="ms-auto d-flex">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-light dropdown-toggle px-3 me-2 py-2 " type="button" id="dropdownMenuButton1" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
More
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1">
|
||||
<li>
|
||||
<button class="dropdown-item" data-bs-toggle="modal" data-bs-target="#addDevice">
|
||||
<i class="fa-solid fa-plus me-2"></i>Add device
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="dropdown-item" data-bs-toggle="modal" data-bs-target="#Settings">
|
||||
<i class="fa-solid fa-sliders me-2"></i>Settings
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card border-0">
|
||||
<div class="card-body py-2 px-3">
|
||||
{visitors} {visitors === 1 ? "Visitor" : "Visitors"}<i class="ms-1 fa-solid {visitors === 1 ? "fa-user" : "fa-user-group"} text-muted"></i>
|
||||
@@ -44,14 +85,14 @@
|
||||
</nav>
|
||||
|
||||
<div class="modal fade" id="addDevice" tabindex="-1" aria-labelledby="addDeviceLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title fw-bold" id="addDeviceLabel">Add device</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="addForm" on:submit|preventDefault={updateDevice}>
|
||||
<form id="addForm" on:submit|preventDefault={() => updateDevice(addDevice)}>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<div class="mb-3">
|
||||
@@ -80,16 +121,110 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-auto ms-auto">
|
||||
<button type="submit" form="addForm" class="btn btn-outline-success">Add device</button>
|
||||
</div>
|
||||
</div>
|
||||
<h5 class="fw-bold">Network discovery</h5>
|
||||
{#if !settings.discovery}
|
||||
<div class="callout callout-danger">
|
||||
<p class="my-0">To enable this option, please enter your network address above.</p>
|
||||
</div>
|
||||
{/if}
|
||||
<button id="btnScan" class="btn btn-secondary" type="button" on:click={scanNetwork} disabled={!settings.discovery}>
|
||||
<span id="btnScanSpinner" class="spinner-grow spinner-grow-sm d-none" role="status" aria-hidden="true"></span>
|
||||
<span id="btnScanText">Scan</span>
|
||||
</button>
|
||||
{#if settings.scan_network?.length}
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>IP</td>
|
||||
<td>Netmask</td>
|
||||
<td>MAC</td>
|
||||
<td>Add</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each settings.scan_network as device, i}
|
||||
<tr>
|
||||
<td>{device.name}</td>
|
||||
<td>{device.ip}</td>
|
||||
<td>{device.netmask}</td>
|
||||
<td>{device.mac}</td>
|
||||
<td>
|
||||
<button type="button" id="btnAdd{i}" class="btn btn-outline-secondary py-0" on:click={() => addScan(i)}>
|
||||
<i class="fa-solid fa-plus fa-sm"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
{/if}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="Settings" tabindex="-1" aria-labelledby="settingsLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title fw-bold" id="settingsLabel">Settings</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="settingsForm" on:submit|preventDefault={updateSettings}>
|
||||
<h5 class="fw-bold">General</h5>
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<div class="mb-3">
|
||||
<label for="inputNetworkDiscovery" class="form-label">Network discovery address</label>
|
||||
<input type="text" class="form-control" id="inputNetworkDiscovery" placeholder="192.168.1.0/24" bind:value="{settings.discovery}" pattern="^([01]?\d\d?|2[0-4]\d|25[0-5])(?:\.(?:[01]?\d\d?|2[0-4]\d|25[0-5])){'{'}2{'}'}(?:\.(?:0))(?:/[0-2]\d|/3[0-2])$" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="mb-3">
|
||||
<label for="inputIntervalSettings" class="form-label">Interval</label>
|
||||
<input type="number" class="form-control" id="inputIntervalSettings" bind:value="{settings.interval}" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<label class="form-check-label" for="flexCheckDefault">
|
||||
Enable notifications
|
||||
</label>
|
||||
<input class="form-check-input" type="checkbox" value="" id="flexCheckDefault" bind:checked={settings.notifications}>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" form="addForm" class="btn btn-outline-success">Add device</button>
|
||||
<button type="submit" form="settingsForm" class="btn btn-outline-success">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../variables.scss";
|
||||
|
||||
.dropdown-item {
|
||||
&:active {
|
||||
color: #1e2125;
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: 1em;
|
||||
}
|
||||
@@ -97,6 +232,18 @@
|
||||
.btn-light {
|
||||
background-color: white;
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
.callout {
|
||||
padding: 1rem;
|
||||
margin-top: 1.25rem;
|
||||
margin-bottom: 1.25rem;
|
||||
border: 1px solid #e9ecef;
|
||||
border-left-width: 0.25rem;
|
||||
border-radius: 0.25rem;
|
||||
|
||||
&.callout-danger {
|
||||
border-left-color: $danger;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -14,6 +14,8 @@ $list-group-bg: transparent;
|
||||
$modal-content-border-radius: 2em;
|
||||
$modal-inner-padding: 1.5rem;
|
||||
$btn-border-radius: 0.5rem;
|
||||
$dropdown-border-radius: 1em;
|
||||
$dropdown-border-width: 0px;
|
||||
|
||||
// font awesome
|
||||
$fa-font-path: "webfonts";
|
||||
|
||||
Reference in New Issue
Block a user