Added settings, proper record renaming
This commit is contained in:
parent
2ae93b6fd4
commit
7d7c0c9570
10 changed files with 444 additions and 85 deletions
|
|
@ -1,28 +1,56 @@
|
|||
import { MessageBus } from '@mbus'
|
||||
|
||||
export class Application {
|
||||
constructor(records) {// {{{
|
||||
window._mbus = new MessageBus()
|
||||
this.settings = new Settings()
|
||||
this.records = this.parseRecords(records)
|
||||
this.folders = this.createFolders()
|
||||
this.topFolder = new Folder(this, 'root')
|
||||
this.recordsTree = null
|
||||
this.settingsIcon = null
|
||||
|
||||
this.renderFolders()
|
||||
this.render()
|
||||
}// }}}
|
||||
parseRecords(recordsData) {// {{{
|
||||
const records = recordsData.map(d => new Record(d))
|
||||
return records
|
||||
}// }}}
|
||||
createFolders() {// {{{
|
||||
this.records.sort(this.sortRecords)
|
||||
const topFolder = new Folder(this, 'root')
|
||||
|
||||
// cleanFolders removes all records from all folders.
|
||||
// renderFolders can then put moved records in the correct
|
||||
// (or newly created) folders again when record names are updated.
|
||||
cleanFolders(folder) {// {{{
|
||||
if (folder === undefined)
|
||||
folder = this.topFolder
|
||||
|
||||
folder.records = []
|
||||
folder.subfolders.forEach((folder, _label) => {
|
||||
this.cleanFolders(folder)
|
||||
})
|
||||
}// }}}
|
||||
renderFolders() {// {{{
|
||||
this.records.sort(this.sortRecords)
|
||||
|
||||
|
||||
// rec: for example www.google.com
|
||||
for (const rec of this.records) {
|
||||
// com.google (reverse and remove wwww)
|
||||
const labels = rec.labels().reverse().slice(0, -1)
|
||||
|
||||
let currFolder = topFolder
|
||||
// Start each record from the top and iterate through all its labels
|
||||
// except the first one since that would be the actual record.
|
||||
let currFolder = this.topFolder
|
||||
let accFolderLabels = []
|
||||
for (const i in labels) {
|
||||
const label = labels[i]
|
||||
|
||||
// The accumulated name is used to create each folder as the record progresses.
|
||||
accFolderLabels.push(label)
|
||||
const accFolderName = accFolderLabels.map(v => v).reverse().join('.')
|
||||
|
||||
// A new folder is created only when it doesn't exist
|
||||
// to be able to update them when necessary.
|
||||
let folder = currFolder.subfolders.get(label)
|
||||
if (folder === undefined) {
|
||||
folder = new Folder(this, accFolderName)
|
||||
|
|
@ -30,12 +58,10 @@ export class Application {
|
|||
}
|
||||
currFolder = folder
|
||||
|
||||
// Add the record to the innermost folder
|
||||
}
|
||||
// Add the record to the innermost folder
|
||||
currFolder.addRecord(rec)
|
||||
}
|
||||
|
||||
return topFolder
|
||||
}// }}}
|
||||
sortFolders(a, b) {// {{{
|
||||
const aLabels = a.labels().reverse()
|
||||
|
|
@ -67,15 +93,31 @@ export class Application {
|
|||
return 0
|
||||
}// }}}
|
||||
render() {// {{{
|
||||
const tree = document.getElementById('records-tree')
|
||||
tree.replaceChildren()
|
||||
if (this.recordsTree == null) {
|
||||
this.recordsTree = document.createElement('div')
|
||||
this.recordsTree.id = 'records-tree'
|
||||
|
||||
this.settingsIcon = document.createElement('img')
|
||||
this.settingsIcon.id = 'settings-icon'
|
||||
this.settingsIcon.src = `/images/${_VERSION}/icon_settings.svg`
|
||||
this.settingsIcon.addEventListener('click', () => new SettingsDialog(this).show())
|
||||
|
||||
document.body.appendChild(this.recordsTree)
|
||||
document.body.appendChild(this.settingsIcon)
|
||||
}
|
||||
//this.recordsTree.replaceChildren()
|
||||
|
||||
// Top root folder doesn't have to be shown.
|
||||
const folders = Array.from(this.folders.subfolders.values())
|
||||
const folders = Array.from(this.topFolder.subfolders.values())
|
||||
folders.sort(this.sortFolders)
|
||||
|
||||
for (const folder of folders)
|
||||
tree.append(folder.toElement())
|
||||
this.recordsTree.append(folder.toElement())
|
||||
|
||||
// Subscribe to settings update since the elements they will change
|
||||
// exists now.
|
||||
_mbus.subscribe('settings_updated', event => this.handlerSettingsUpdated(event.detail))
|
||||
this.setBoxedFolders(this.settings.get('boxed_folders'))
|
||||
|
||||
}// }}}
|
||||
handlerTop(event) {// {{{
|
||||
|
|
@ -125,6 +167,17 @@ export class Application {
|
|||
|
||||
|
||||
}// }}}
|
||||
handlerSettingsUpdated({ key, value }) {// {{{
|
||||
if (key == 'boxed_folders') {
|
||||
this.setBoxedFolders(value)
|
||||
}
|
||||
}// }}}
|
||||
setBoxedFolders(state) {// {{{
|
||||
if (state)
|
||||
document.body.classList.add('boxed-folders')
|
||||
else
|
||||
document.body.classList.remove('boxed-folders')
|
||||
}// }}}
|
||||
}
|
||||
|
||||
class Folder {
|
||||
|
|
@ -250,6 +303,9 @@ class Record {
|
|||
value() {// {{{
|
||||
return this.data.ParsedValue
|
||||
}// }}}
|
||||
matchSubdomain() {// {{{
|
||||
return this.data.MatchSubdomain === 'true'
|
||||
}// }}}
|
||||
labels() {// {{{
|
||||
return this.name().split('.')
|
||||
}// }}}
|
||||
|
|
@ -263,6 +319,17 @@ class Record {
|
|||
new RecordDialog(this).show()
|
||||
}// }}}
|
||||
set(key, value) {// {{{
|
||||
if (key == 'Name') {
|
||||
if (value.slice(0, 2) == '*.') {
|
||||
this.data['Name'] = value.slice(2)
|
||||
this.data['MatchSubdomain'] = 'true'
|
||||
} else {
|
||||
this.data['Name'] = value
|
||||
this.data['MatchSubdomain'] = 'false'
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
this.data[key] = value
|
||||
}// }}}
|
||||
render() {// {{{
|
||||
|
|
@ -280,8 +347,9 @@ class Record {
|
|||
this.divSeparator.classList.add('separator')
|
||||
|
||||
this.divFQDN.innerHTML = `
|
||||
<span class="first-label">${this.labels()[0]}</span>
|
||||
<span class="rest-label">${this.labels().slice(1).join('.')}</span>
|
||||
<span class="subdomains">*.</span>
|
||||
<span class="first-label"></span>
|
||||
<span class="rest-label"></span>
|
||||
`
|
||||
|
||||
this.divFQDN.addEventListener('click', event => {
|
||||
|
|
@ -299,6 +367,17 @@ class Record {
|
|||
})
|
||||
}
|
||||
|
||||
// FQDN is updated.
|
||||
if (this.matchSubdomain())
|
||||
this.divFQDN.classList.add('match-subdomains')
|
||||
else
|
||||
this.divFQDN.classList.remove('match-subdomains')
|
||||
|
||||
const fl = this.labels()[0]
|
||||
const rl = this.labels().slice(1).join('.')
|
||||
this.divFQDN.querySelector('.first-label').innerText = fl
|
||||
this.divFQDN.querySelector('.rest-label').innerText = rl != '' ? `.${rl}` : ''
|
||||
|
||||
this.divType.innerText = this.type()
|
||||
this.divValue.innerText = this.value()
|
||||
|
||||
|
|
@ -315,6 +394,9 @@ class Record {
|
|||
alert(json.Error)
|
||||
return
|
||||
}
|
||||
_app.cleanFolders()
|
||||
_app.renderFolders()
|
||||
_app.render()
|
||||
})
|
||||
}// }}}
|
||||
}
|
||||
|
|
@ -336,6 +418,10 @@ class RecordDialog {
|
|||
<option>A</option>
|
||||
<option>AAAA</option>
|
||||
<option>CNAME</option>
|
||||
<option>FWD</option>
|
||||
<option>NS</option>
|
||||
<option>NXDOMAIN</option>
|
||||
<option>TXT</option>
|
||||
</select>
|
||||
|
||||
<div>Value</div>
|
||||
|
|
@ -350,7 +436,11 @@ class RecordDialog {
|
|||
</div>
|
||||
`
|
||||
|
||||
this.dlg.querySelector('.name').value = this.record.name()
|
||||
if (this.record.matchSubdomain())
|
||||
this.dlg.querySelector('.name').value = '*.' + this.record.name()
|
||||
else
|
||||
this.dlg.querySelector('.name').value = this.record.name()
|
||||
|
||||
this.dlg.querySelector('.type').value = this.record.type()
|
||||
this.dlg.querySelector('.value').value = this.record.value()
|
||||
this.dlg.querySelector('.ttl').value = this.record.ttl();
|
||||
|
|
@ -380,3 +470,63 @@ class RecordDialog {
|
|||
this.dlg.close()
|
||||
}// }}}
|
||||
}
|
||||
|
||||
class Settings {
|
||||
constructor() {// {{{
|
||||
this.settings = new Map([
|
||||
['boxed_folders', true],
|
||||
])
|
||||
|
||||
// Read any configured settings from local storage, but keeping default value
|
||||
// if not set.
|
||||
this.settings.forEach((_v, key) => {
|
||||
const configuredValue = localStorage.getItem(key)
|
||||
if (configuredValue !== null)
|
||||
this.settings.set(key, JSON.parse(configuredValue))
|
||||
})
|
||||
}// }}}
|
||||
set(key, value) {// {{{
|
||||
this.settings.set(key, value)
|
||||
localStorage.setItem(key, JSON.stringify(value))
|
||||
_mbus.dispatch('settings_updated', { key, value })
|
||||
}// }}}
|
||||
get(key) {// {{{
|
||||
return this.settings.get(key)
|
||||
}// }}}
|
||||
}
|
||||
|
||||
class SettingsDialog {
|
||||
constructor(app) {// {{{
|
||||
this.application = app
|
||||
|
||||
this.dlg = null
|
||||
this.elBoxedFolders = null
|
||||
}// }}}
|
||||
show() {// {{{
|
||||
this.dlg = document.createElement('dialog')
|
||||
this.dlg.id = 'settings-dialog'
|
||||
|
||||
this.dlg.innerHTML = `
|
||||
<input type="checkbox" id="boxed-folders"> <label for="boxed-folders">Boxed folders</label>
|
||||
<div class="buttons">
|
||||
<button class="save">Save</button>
|
||||
</div>
|
||||
`
|
||||
|
||||
const boxedFolders = this.application.settings.get('boxed_folders')
|
||||
this.elBoxedFolders = this.dlg.querySelector('#boxed-folders')
|
||||
this.elBoxedFolders.checked = boxedFolders
|
||||
|
||||
// Event listeners are connected.
|
||||
this.dlg.querySelector('.save').addEventListener('click', () => this.save())
|
||||
this.dlg.addEventListener('close', () => this.dlg.remove())
|
||||
|
||||
// Can't show a dialog that doesn't exist in DOM.
|
||||
document.body.appendChild(this.dlg)
|
||||
this.dlg.showModal()
|
||||
}// }}}
|
||||
save() {// {{{
|
||||
this.application.settings.set('boxed_folders', this.elBoxedFolders.checked)
|
||||
this.dlg.close()
|
||||
}// }}}
|
||||
}
|
||||
|
|
|
|||
29
static/js/mbus.mjs
Normal file
29
static/js/mbus.mjs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
export class MessageBus {
|
||||
constructor() {
|
||||
this.bus = new EventTarget()
|
||||
this.counter = 0
|
||||
this.fnmap = new Map()
|
||||
}
|
||||
|
||||
subscribe(eventName, fn) {
|
||||
this.counter++
|
||||
this.bus.addEventListener(eventName, fn)
|
||||
this.fnmap.set(this.counter, { eventName, fn })
|
||||
return this.counter
|
||||
}
|
||||
|
||||
unsubscribe(mappedID) {
|
||||
const mapped = this.fnmap.get(mappedID)
|
||||
if (mapped === undefined) {
|
||||
console.warn('unsubscribe, no such mapped ID', mappedID)
|
||||
return
|
||||
}
|
||||
this.fnmap.delete(mappedID)
|
||||
this.bus.removeEventListener(mapped.eventName, mapped.fn)
|
||||
}
|
||||
|
||||
dispatch(eventName, data) {
|
||||
console.debug('mbus', eventName, data)
|
||||
this.bus.dispatchEvent(new CustomEvent(eventName, { detail: data }))
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue