Implement device ID

This commit is contained in:
Magnus Åhall 2026-02-27 11:31:19 +01:00
parent 7213ee7a28
commit e0e2d9b164
4 changed files with 83 additions and 79 deletions

View file

@ -28,6 +28,7 @@ type Config struct {
} }
type Device struct { type Device struct {
ID string
Name string Name string
Address string Address string
Port int Port int
@ -48,10 +49,9 @@ func readConfig() (config Config, err error) {
return return
} }
func (cfg *Config) UpdateDevice(currentName string, deviceToUpdate Device) (dev Device, err error) {// {{{ func (cfg *Config) UpdateDevice(deviceToUpdate Device) (dev Device, err error) { // {{{
i := slices.IndexFunc(cfg.Devices, func(d Device) bool { i := slices.IndexFunc(cfg.Devices, func(d Device) bool {
return strings.TrimSpace(strings.ToLower(d.Name)) == strings.TrimSpace(strings.ToLower(currentName)) return d.ID == deviceToUpdate.ID
}) })
if i > -1 { if i > -1 {
@ -75,7 +75,7 @@ func (cfg *Config) UpdateDevice(currentName string, deviceToUpdate Device) (dev
return return
} }
dev.Name = strings.TrimSpace(strings.ToLower(deviceToUpdate.Name)) dev.Name = strings.TrimSpace(deviceToUpdate.Name)
dev.Address = strings.TrimSpace(strings.ToLower(deviceToUpdate.Address)) dev.Address = strings.TrimSpace(strings.ToLower(deviceToUpdate.Address))
dev.Port = deviceToUpdate.Port dev.Port = deviceToUpdate.Port
dev.Username = strings.TrimSpace(deviceToUpdate.Username) dev.Username = strings.TrimSpace(deviceToUpdate.Username)
@ -91,6 +91,7 @@ func (cfg *Config) UpdateDevice(currentName string, deviceToUpdate Device) (dev
err = fmt.Errorf("Password can't be empty") err = fmt.Errorf("Password can't be empty")
return return
} }
dev.ID = deviceToUpdate.ID
dev.Password = strings.TrimSpace(deviceToUpdate.Password) dev.Password = strings.TrimSpace(deviceToUpdate.Password)
cfg.Devices = append(cfg.Devices, dev) cfg.Devices = append(cfg.Devices, dev)
} else { } else {
@ -104,14 +105,14 @@ func (cfg *Config) UpdateDevice(currentName string, deviceToUpdate Device) (dev
err = os.WriteFile(cfg.filename, j, 0600) err = os.WriteFile(cfg.filename, j, 0600)
return return
}// }}} } // }}}
func (cfg *Config) DeleteDevice(devname string) (err error) {// {{{ func (cfg *Config) DeleteDevice(devID string) (err error) { // {{{
cfg.Devices = slices.DeleteFunc(cfg.Devices, func(d Device) bool { cfg.Devices = slices.DeleteFunc(cfg.Devices, func(d Device) bool {
return strings.TrimSpace(strings.ToLower(d.Name)) == strings.TrimSpace(strings.ToLower(devname)) return d.ID == devID
}) })
j, _ := json.Marshal(cfg) j, _ := json.Marshal(cfg)
err = os.WriteFile(cfg.filename, j, 0600) err = os.WriteFile(cfg.filename, j, 0600)
return return
}// }}} } // }}}

16
main.go
View file

@ -6,11 +6,11 @@ import (
// Standard // Standard
"flag" "flag"
"fmt"
"log/slog" "log/slog"
"os" "os"
"path" "path"
"slices" "slices"
"fmt"
) )
const VERSION = "v1" const VERSION = "v1"
@ -89,23 +89,23 @@ func main() {
startWebserver() startWebserver()
} }
func routerosDevice(name string) (dev RouterosDevice, err error) { func routerosDevice(id string) (dev RouterosDevice, err error) {
var found bool var found bool
if dev, found = devices[name]; found { if dev, found = devices[id]; found {
logger.Debug("routeros", "op", "connection", "name", name, "cached", true) logger.Debug("routeros", "op", "connection", "ID", id, "cached", true)
return return
} }
i := slices.IndexFunc(config.Devices, func(d Device) bool { i := slices.IndexFunc(config.Devices, func(d Device) bool {
return d.Name == name return d.ID == id
}) })
if i == -1 { if i == -1 {
err = fmt.Errorf("Unknown device '%s'", name) err = fmt.Errorf("Unknown device '%s'", id)
logger.Error("routeros", "op", "connection", "error", err) logger.Error("routeros", "op", "connection", "error", err)
return return
} }
logger.Debug("routeros", "name", name, "cached", false) logger.Debug("routeros", "name", id, "cached", false)
confDev := config.Devices[i] confDev := config.Devices[i]
dev.Host = confDev.Address dev.Host = confDev.Address
dev.Port = confDev.Port dev.Port = confDev.Port
@ -113,7 +113,7 @@ func routerosDevice(name string) (dev RouterosDevice, err error) {
dev.Password = confDev.Password dev.Password = confDev.Password
dev.Timeout = confDev.Timeout dev.Timeout = confDev.Timeout
dev.Init() dev.Init()
devices[name] = dev devices[id] = dev
return return
} }

View file

@ -25,8 +25,8 @@ export class Application {
alert(err) alert(err)
}) })
_mbus.subscribe('device_selected', event => this.connectDevice(event.detail.devName)) _mbus.subscribe('device_selected', event => this.connectDevice(event.detail.devID))
_mbus.subscribe('device_deleted', event => this.deleteDevice(event.detail.devName)) _mbus.subscribe('device_deleted', event => this.deleteDevice(event.detail.devID))
_mbus.subscribe('search', () => this.search()) _mbus.subscribe('search', () => this.search())
}// }}} }// }}}
@ -34,8 +34,6 @@ export class Application {
resetDeviceState() {// {{{ resetDeviceState() {// {{{
if (this.recordsTree) { if (this.recordsTree) {
this.recordsTree.remove() this.recordsTree.remove()
this.recordsTree = null
return
} }
this.topFolder = new Folder(this, null, 'root') this.topFolder = new Folder(this, null, 'root')
@ -60,7 +58,7 @@ export class Application {
this.devices = new Map() this.devices = new Map()
for (const devData of json.Devices) { for (const devData of json.Devices) {
const dev = new Device(devData) const dev = new Device(devData)
this.devices.set(dev.name(), dev) this.devices.set(dev.id(), dev)
} }
resolve() resolve()
@ -69,17 +67,18 @@ export class Application {
}// }}} }// }}}
// connectDevice resets the device state, retrieves the records and renders everything necessary. // connectDevice resets the device state, retrieves the records and renders everything necessary.
async connectDevice(devName) {// {{{ async connectDevice(devID) {// {{{
this.resetDeviceState() this.resetDeviceState()
if (devName == '-') {
if (devID == '-') {
this.settings.set('last_device', '') this.settings.set('last_device', '')
return return
} }
try { try {
const dev = this.devices.get(devName) const dev = this.devices.get(devID)
if (dev === undefined) { if (dev === undefined) {
alert(`Unknown device '${devName}'`) alert(`Unknown device '${devID}'`)
return return
} }
this.currentDevice = dev this.currentDevice = dev
@ -87,7 +86,7 @@ export class Application {
const records = await this.currentDevice.retrieveRecords() const records = await this.currentDevice.retrieveRecords()
this.records = this.parseRecords(records) this.records = this.parseRecords(records)
this.settings.set('last_device', this.currentDevice.name()) this.settings.set('last_device', this.currentDevice.id())
this.createFolders() this.createFolders()
this.renderDevice() this.renderDevice()
@ -98,9 +97,9 @@ export class Application {
}// }}} }// }}}
// deleteDevice removes it from the list and resets app state if it was connected. // deleteDevice removes it from the list and resets app state if it was connected.
deleteDevice(devName) {// {{{ deleteDevice(devID) {// {{{
this.devices.delete(devName) this.devices.delete(devID)
if (this.currentDevice?.name() == devName) { if (this.currentDevice?.id() == devID) {
this.resetDeviceState() this.resetDeviceState()
this.settings.set('last_device', '') this.settings.set('last_device', '')
} }
@ -396,8 +395,11 @@ class Device {
constructor(data) {// {{{ constructor(data) {// {{{
this.data = data this.data = data
}// }}} }// }}}
id() {// {{{
return this.data.ID || ''
}// }}}
name() {// {{{ name() {// {{{
return this.data.Name?.toLowerCase() || '' return this.data.Name || ''
}// }}} }// }}}
address() {// {{{ address() {// {{{
return this.data.Address || '' return this.data.Address || ''
@ -411,11 +413,8 @@ class Device {
password() {// {{{ password() {// {{{
return this.data.Password || '' return this.data.Password || ''
}// }}} }// }}}
name() {// {{{
return this.data.Name?.toLowerCase() || ''
}// }}}
async retrieveRecords() {// {{{ async retrieveRecords() {// {{{
const data = await fetch(`/device/${this.name()}/dns_records`) const data = await fetch(`/device/${this.id()}/dns_records`)
const json = await data.json() const json = await data.json()
if (!json.OK) if (!json.OK)
throw new Error(json.Error) throw new Error(json.Error)
@ -431,7 +430,7 @@ class DeviceSelectWidget {
this.elDeviceSelect = null this.elDeviceSelect = null
this.currentlySelected = null this.currentlySelected = null
_mbus.subscribe('device_deleted', event => this.deleteDevice(event.detail.devName)) _mbus.subscribe('device_deleted', event => this.deleteDevice(event.detail.devID))
_mbus.subscribe('device_updated', event => this.updateDevice(event.detail.device)) _mbus.subscribe('device_updated', event => this.updateDevice(event.detail.device))
}// }}} }// }}}
setDevices(devices) {// {{{ setDevices(devices) {// {{{
@ -461,12 +460,16 @@ class DeviceSelectWidget {
emptyOption.dataset.devicename = "-" emptyOption.dataset.devicename = "-"
this.elDeviceSelect.appendChild(emptyOption) this.elDeviceSelect.appendChild(emptyOption)
const devNames = Array.from(this.devices.keys()).sort() const devs = Array.from(this.devices.values()).sort((a, b) => {
devNames.forEach(devName => { if (a.name().toLowerCase() < b.name().toLowerCase()) return -1
const dev = this.devices.get(devName) if (a.name().toLowerCase() > b.name().toLowerCase()) return 1
return 0
})
devs.forEach(dev => {
const option = document.createElement('option') const option = document.createElement('option')
option.value = dev.id()
option.innerText = dev.name() option.innerText = dev.name()
option.dataset.devicename = dev.name() option.dataset.id = dev.id()
this.elDeviceSelect.appendChild(option) this.elDeviceSelect.appendChild(option)
}) })
@ -474,21 +477,20 @@ class DeviceSelectWidget {
this.elDeviceSelect.value = this.currentlySelected this.elDeviceSelect.value = this.currentlySelected
}// }}} }// }}}
notifyDeviceSelect() {// {{{ notifyDeviceSelect() {// {{{
const devName = this.elDeviceSelect.value const devID = this.elDeviceSelect.value
this.currentlySelected = devName this.currentlySelected = devID
_mbus.dispatch('device_selected', { devName }) _mbus.dispatch('device_selected', { devID })
}// }}} }// }}}
selectDevice(devname) {// {{{ selectDevice(devID) {// {{{
this.elDeviceSelect.value = devname this.elDeviceSelect.value = devID
this.notifyDeviceSelect() this.notifyDeviceSelect()
}// }}} }// }}}
deleteDevice(devName) {// {{{ deleteDevice(devID) {// {{{
this.devices.delete(devName) this.devices.delete(devID)
this.restockDeviceSelect() this.restockDeviceSelect()
}// }}} }// }}}
updateDevice(dev) {// {{{ updateDevice(dev) {// {{{
console.log(dev) this.devices.set(dev.id(), dev)
this.devices.set(dev.name(), dev)
this.restockDeviceSelect() this.restockDeviceSelect()
}// }}} }// }}}
} }
@ -686,8 +688,6 @@ class Record {
labels.reverse() labels.reverse()
labels = [`${labels[1]}.${labels[0]}`].concat(labels.slice(2)) labels = [`${labels[1]}.${labels[0]}`].concat(labels.slice(2))
labels.reverse() labels.reverse()
} else {
console.log(this, labels)
} }
return labels return labels
@ -817,7 +817,7 @@ class Record {
save() {// {{{ save() {// {{{
const created = (this.id() == '') const created = (this.id() == '')
fetch(`/device/${_app.currentDevice.name()}/record`, { fetch(`/device/${_app.currentDevice.id()}/record`, {
method: 'POST', method: 'POST',
body: JSON.stringify(this.data), body: JSON.stringify(this.data),
}) })
@ -855,7 +855,7 @@ class Record {
if (!confirm(`Are you sure you want to delete ${this.name()}?`)) if (!confirm(`Are you sure you want to delete ${this.name()}?`))
return return
fetch(`/device/${_app.currentDevice.name()}/record/${this.id()}`, { method: 'DELETE' }) fetch(`/device/${_app.currentDevice.id()}/record/${this.id()}`, { method: 'DELETE' })
.then(data => data.json()) .then(data => data.json())
.then(json => { .then(json => {
if (!json.OK) { if (!json.OK) {
@ -1091,7 +1091,7 @@ class DeviceDialog {
filterDevices() {// {{{ filterDevices() {// {{{
const filter = this.dlg.querySelector('.filter').value.toLowerCase() const filter = this.dlg.querySelector('.filter').value.toLowerCase()
this.elDeviceList.querySelectorAll('.device').forEach(dev=>{ this.elDeviceList.querySelectorAll('.device').forEach(dev => {
if (dev.innerText.toLowerCase().includes(filter)) if (dev.innerText.toLowerCase().includes(filter))
dev.classList.remove('filtered') dev.classList.remove('filtered')
else else
@ -1110,17 +1110,18 @@ class DeviceDialog {
devices.forEach(dev => { devices.forEach(dev => {
const devEl = document.createElement('div') const devEl = document.createElement('div')
devEl.classList.add('device') devEl.classList.add('device')
devEl.dataset.name = dev.name() devEl.dataset.id = dev.id()
devEl.innerText = dev.name() devEl.innerText = dev.name()
devEl.addEventListener('click', () => this.editDevice(dev, devEl)) devEl.addEventListener('click', () => this.editDevice(dev))
this.elDeviceList.appendChild(devEl) this.elDeviceList.appendChild(devEl)
}) })
}// }}} }// }}}
editDevice(dev, devEl) {// {{{ editDevice(dev) {// {{{
this.device = dev this.device = dev
this.elDevices.querySelectorAll('.device.selected').forEach(el => el.classList.remove('selected')) this.elDevices.querySelectorAll('.device.selected').forEach(el => el.classList.remove('selected'))
const devEl = this.elDeviceList.querySelector(`[data-id="${dev.id()}"]`)
devEl.classList.add('selected') devEl.classList.add('selected')
this.elName.value = dev.name() this.elName.value = dev.name()
@ -1133,7 +1134,7 @@ class DeviceDialog {
let name = prompt('Name of new device') let name = prompt('Name of new device')
if (name === null || name.trim() === '') if (name === null || name.trim() === '')
return return
name = name.trim().toLowerCase() name = name.trim()
// Make sure it doesn't already exist. // Make sure it doesn't already exist.
if (this.devices.has(name)) { if (this.devices.has(name)) {
@ -1141,18 +1142,20 @@ class DeviceDialog {
return return
} }
const dev = new Device({Name: name, Port: 443}, true) const dev = new Device({
ID: crypto.randomUUID(),
Name: name,
Port: 443
}, true)
this.devices.set(name, dev) this.devices.set(dev.id(), dev)
this.updateDeviceList() this.updateDeviceList()
this.editDevice(dev)
const devEl = this.elDeviceList.querySelector(`[data-name="${dev.name()}"]`)
this.editDevice(dev, devEl)
}// }}} }// }}}
async updateDevice() {// {{{ async updateDevice() {// {{{
const req = { const req = {
CurrentName: this.device.name(),
Device: { Device: {
ID: this.device.id(),
Name: this.elName.value, Name: this.elName.value,
Address: this.elAddress.value, Address: this.elAddress.value,
Port: parseInt(this.elPort.value), Port: parseInt(this.elPort.value),
@ -1186,12 +1189,13 @@ class DeviceDialog {
} }
}// }}} }// }}}
async deleteDevice() {// {{{ async deleteDevice() {// {{{
const devID = this.device.id()
const devname = this.device.name() const devname = this.device.name()
if (!confirm(`Do you want to delete '${devname}'?`)) if (!confirm(`Do you want to delete '${devname}'?`))
return return
try { try {
const data = await fetch(`/device/${devname}`, { method: 'DELETE' }) const data = await fetch(`/device/${devID}`, { method: 'DELETE' })
const json = await data.json() const json = await data.json()
if (!json.OK) { if (!json.OK) {
alert(json.Error) alert(json.Error)
@ -1204,10 +1208,10 @@ class DeviceDialog {
this.elUsername.value = '' this.elUsername.value = ''
this.elPassword.value = '' this.elPassword.value = ''
this.devices.delete(devname) this.devices.delete(devID)
this.device = null this.device = null
this.updateDeviceList() this.updateDeviceList()
_mbus.dispatch('device_deleted', { devName: devname }) _mbus.dispatch('device_deleted', { devID: devID })
} catch (err) { } catch (err) {
console.error(err) console.error(err)
alert(err) alert(err)

View file

@ -100,7 +100,6 @@ func actionDevices(w http.ResponseWriter, r *http.Request) { // {{{
} // }}} } // }}}
func actionDeviceUpdate(w http.ResponseWriter, r *http.Request) { // {{{ func actionDeviceUpdate(w http.ResponseWriter, r *http.Request) { // {{{
var req struct { var req struct {
CurrentName string
Device Device Device Device
} }
@ -111,7 +110,7 @@ func actionDeviceUpdate(w http.ResponseWriter, r *http.Request) { // {{{
return return
} }
device, err := config.UpdateDevice(req.CurrentName, req.Device) device, err := config.UpdateDevice(req.Device)
if err != nil { if err != nil {
httpError(w, err) httpError(w, err)
return return
@ -128,9 +127,9 @@ func actionDeviceUpdate(w http.ResponseWriter, r *http.Request) { // {{{
w.Write(j) w.Write(j)
} // }}} } // }}}
func actionDeviceDelete(w http.ResponseWriter, r *http.Request) { // {{{ func actionDeviceDelete(w http.ResponseWriter, r *http.Request) { // {{{
devname := r.PathValue("dev") devID := r.PathValue("dev")
err := config.DeleteDevice(devname) err := config.DeleteDevice(devID)
if err != nil { if err != nil {
httpError(w, err) httpError(w, err)
return return
@ -141,9 +140,9 @@ func actionDeviceDelete(w http.ResponseWriter, r *http.Request) { // {{{
} // }}} } // }}}
func actionDNSRecords(w http.ResponseWriter, r *http.Request) { // {{{ func actionDNSRecords(w http.ResponseWriter, r *http.Request) { // {{{
devname := r.PathValue("dev") devID := r.PathValue("dev")
device, err := routerosDevice(devname) device, err := routerosDevice(devID)
if err != nil { if err != nil {
httpError(w, err) httpError(w, err)
return return