Device management

This commit is contained in:
Magnus Åhall 2026-02-26 21:37:26 +01:00
parent 5635c2af1a
commit b9a5437909
6 changed files with 294 additions and 9 deletions

View file

@ -1,5 +1,5 @@
:root {
--header-background: #acbb78;
--header-background: #e1eabe;
--header-border: 1px solid #869a41;
--line-color: #ccc;
@ -120,12 +120,21 @@ button {
background-color: var(--header-background);
.device-select {
display: grid;
grid-template-columns: min-content min-content;
align-items: center;
padding: 16px;
border-right: var(--header-border);
.device-name {
.label {
grid-column: 1 / -1;
font-weight: bold;
}
img {
margin-left: 8px;
}
}
#search {
@ -465,3 +474,49 @@ button {
margin-top: 8px;
}
}
#device-dialog {
display: grid;
grid-template-columns: min-content min-content;
align-items: top;
grid-gap: 16px;
.devices {
display: flex;
flex-direction:column;
gap: 8px;
align-items: flex-start;
.header {
font-weight: bold;
}
.device {
border: 1px solid #aaa;
background-color: #f0f0f0;
padding: 8px 16px;
border-radius: 8px;
cursor: pointer;
user-select: none;
&.selected {
background-color: var(--header-background);
}
}
}
.fields {
display: grid;
grid-template-columns: min-content 1fr;
grid-gap: 8px 16px;
align-items: center;
border: 1px solid #aaa;
padding: 16px;
.actions {
grid-column: 1 / -1;
justify-self: right;
}
}
}

View file

@ -25,9 +25,9 @@
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.1646825"
inkscape:cx="-71.693356"
inkscape:cy="-55.809199"
inkscape:zoom="5.6568542"
inkscape:cx="-20.948038"
inkscape:cy="9.0156115"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Before After
Before After

View file

@ -303,6 +303,10 @@ export class Application {
this.searchWidget.searchField.focus()
break
case 'd':
new DeviceDialog(this.devices).render()
break
default:
handled = false
}
@ -385,6 +389,21 @@ class Device {
name() {// {{{
return this.data.Name?.toLowerCase() || ''
}// }}}
address() {// {{{
return this.data.Address || ''
}// }}}
port() {// {{{
return parseInt(this.data.Port || 0)
}// }}}
username() {// {{{
return this.data.Username || ''
}// }}}
password() {// {{{
return this.data.Password || ''
}// }}}
name() {// {{{
return this.data.Name?.toLowerCase() || ''
}// }}}
async retrieveRecords() {// {{{
const data = await fetch(`/device/${this.name()}/dns_records`)
const json = await data.json()
@ -410,12 +429,14 @@ class DeviceSelectWidget {
this.div = document.createElement('div')
this.div.classList.add('device-select')
this.div.innerHTML = `
<div class="device-name">Select device</div>
<div class="label">Select device</div>
<select></select>
<img src="/images/${_VERSION}/icon_device_edit.svg">
`
this.elDeviceSelect = this.div.querySelector('select')
this.elDeviceSelect.addEventListener('change', () => this.notifyDeviceSelect())
this.div.querySelector('img').addEventListener('click', ()=>new DeviceDialog(this.devices).render())
}
this.restockDeviceSelect()
return this.div
@ -976,3 +997,117 @@ class SettingsDialog {
this.dlg.close()
}// }}}
}
class DeviceDialog {
constructor(devices) {// {{{
this.devices = devices
this.device = null
}// }}}
render() {// {{{
// Only one open at any one time.
if (document.getElementById('device-dialog'))
return
this.dlg = document.createElement('dialog')
this.dlg.id = 'device-dialog'
this.dlg.innerHTML = `
<div class="devices">
<div class="header">Devices</div>
</div>
<div class="fields">
<div>Name</div>
<input type="text" class="name">
<div>Address</div>
<input type="text" class="address">
<div>Port</div>
<input type="number" class="port">
<div>Username</div>
<input type="text" class="username">
<div>Password</div>
<input type="text" class="password">
<div class="actions">
<button class="delete">Delete</button>
<button class="update">Update</button>
</div>
</div>
`
this.elDevices = this.dlg.querySelector('.devices')
this.elFields = this.dlg.querySelector('.fields')
this.elName = this.dlg.querySelector('.fields .name')
this.elAddress = this.dlg.querySelector('.fields .address')
this.elPort = this.dlg.querySelector('.fields .port')
this.elUsername = this.dlg.querySelector('.fields .username')
this.elPassword = this.dlg.querySelector('.fields .password')
const devices = Array.from(this.devices.values())
devices.sort((a, b)=>{
if (a.name() < b.name()) return -1
if (a.name() > b.name()) return 1
return 0
})
devices.forEach(dev=>{
const devEl = document.createElement('div')
devEl.classList.add('device')
devEl.innerText = dev.name()
devEl.addEventListener('click', ()=>this.editDevice(dev, devEl))
this.elDevices.appendChild(devEl)
})
this.dlg.querySelector('.update').addEventListener('click', ()=>this.updateDevice())
this.dlg.addEventListener('close', ()=>this.dlg.remove())
document.body.appendChild(this.dlg)
this.dlg.showModal()
}// }}}
editDevice(dev, devEl) {// {{{
this.device = dev
this.elDevices.querySelectorAll('.device.selected').forEach(el=>el.classList.remove('selected'))
devEl.classList.add('selected')
this.elName.value = dev.name()
this.elAddress.value = dev.address()
this.elPort.value = dev.port()
this.elUsername.value = dev.username()
this.elPassword.value = dev.password()
}// }}}
async updateDevice() {// {{{
const req = {
CurrentName: this.device.name(),
Device: {
Name: this.elName.value,
Address: this.elAddress.value,
Port: parseInt(this.elPort.value),
Username: this.elUsername.value,
Password: this.elPassword.value,
}
}
try {
const data = await fetch('/device', {
method: 'POST',
body: JSON.stringify(req),
})
const json = await data.json()
this.device.data.Name = json.Device.Name
this.device.data.Address = json.Device.Address
this.device.data.Port = json.Device.Port
this.device.data.Username = json.Device.Username
_mbus.dispatch('device_updated', { device: this.device })
this.dlg.close()
} catch(err) {
console.error(err)
alert(err)
}
}// }}}
}