From ab08d745a111a34510911202ac696a291c32148a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Mon, 23 Feb 2026 11:20:16 +0100 Subject: [PATCH] Looks nicer --- dns.go | 96 ++++++++++++++++++++++-------- static/css/index.css | 72 ++++++++++++++++++---- static/images/icon_folder.svg | 56 +++++++++++++++++ static/images/icon_folder_open.svg | 58 ++++++++++++++++++ static/js/dns.mjs | 65 ++++++++++---------- webserver.go | 11 +--- 6 files changed, 279 insertions(+), 79 deletions(-) create mode 100644 static/images/icon_folder.svg create mode 100644 static/images/icon_folder_open.svg diff --git a/dns.go b/dns.go index a88ded2..9b86886 100644 --- a/dns.go +++ b/dns.go @@ -10,15 +10,18 @@ import ( type DNSRecord struct { ID string `json:".id"` - Address string Disabled string Dynamic string Name string TTL string Type string + + Address string + CNAME string } type DomainPart struct { + Record DNSRecord Subparts map[string]*DomainPart `json:",omitempty"` } @@ -53,6 +56,17 @@ func SortDNSRecord(a, b DNSRecord) int { return 0 } +func (r DNSRecord) String() string { + switch r.Type { + case "A", "AAAA": + return r.Address + case "CNAME": + return r.CNAME + } + + return fmt.Sprintf("Implement type '%s'", r.Type) +} + func (r DNSRecord) Parts() int { return len(strings.Split(r.Name, ".")) } @@ -92,6 +106,7 @@ func BuildRecordsTree(records []DNSRecord) *DomainPart { for _, part := range currentDomainNameSplit { if nextDomainPart, found := curPart.Subparts[part]; !found { newPart := new(DomainPart) + newPart.Record = record newPart.Subparts = make(map[string]*DomainPart) curPart.Subparts[strings.ToLower(part)] = newPart curPart = newPart @@ -109,12 +124,12 @@ type HTMLElement struct { HTML string } -func (dp *DomainPart) ToHTMLElements(parts []string, idFrom int) []HTMLElement { +func (dp *DomainPart) ToHTMLElements(parts []string) []HTMLElement { var lines []HTMLElement sortedParts := slices.Sorted(maps.Keys(dp.Subparts)) - for i, part := range sortedParts { + for _, part := range sortedParts { subpart := dp.Subparts[part] newParts := append(parts, part) @@ -130,38 +145,67 @@ func (dp *DomainPart) ToHTMLElements(parts []string, idFrom int) []HTMLElement { mostSpecificPart += "." } + if len(subpart.Subparts) != 0 { + topmost := "" + if len(newParts) == 1 { + topmost = "top-most" + } + html := fmt.Sprintf(` +
+ + + %s%s +
+
+
+ `, + topmost, // .top-most + restPart, // data-top + fqdn, // data-self + (len(newParts)-1)*32, // margin-left + VERSION, // images/ + VERSION, // images/ + mostSpecificPart, // innerText + restPart, + ) + lines = append(lines, HTMLElement{Header: true, HTML: html}) + subLines := subpart.ToHTMLElements(newParts) + lines = append(lines, subLines...) + } + + } + + for _, part := range sortedParts { + subpart := dp.Subparts[part] + newParts := append(parts, part) + + reversedParts := make([]string, len(newParts)) + copy(reversedParts, newParts) + slices.Reverse(reversedParts) + //fqdn := strings.Join(reversedParts, ".") + + mostSpecificPart := reversedParts[0] + restPart := "" + if len(reversedParts) > 1 { + restPart = strings.Join(reversedParts[1:], ".") + mostSpecificPart += "." + } + if len(subpart.Subparts) == 0 { html := fmt.Sprintf( ` -
%s%s
-
hum
+
%s%s
+
%s
+
%s
`, - len(newParts)*32, - idFrom + i, // data-i + (len(newParts)-1)*32, restPart, // data-top mostSpecificPart, restPart, - idFrom + i, + subpart.Record.Type, + subpart.Record.String(), ) lines = append(lines, HTMLElement{Header: false, HTML: html}) - } else { - html := fmt.Sprintf(` -
- %s%s -
-
- `, - idFrom + i, // data-i - restPart, // data-top - fqdn, // data-self - len(newParts)*32, // margin-left - mostSpecificPart, // innerText - restPart, - idFrom + i, - ) - lines = append(lines, HTMLElement{Header: true, HTML: html}) - subLines := subpart.ToHTMLElements(newParts, idFrom + i) - lines = append(lines, subLines...) } } diff --git a/static/css/index.css b/static/css/index.css index f51925b..7e921e4 100644 --- a/static/css/index.css +++ b/static/css/index.css @@ -18,10 +18,18 @@ body { .records-tree { display: grid; - grid-template-columns: min-content 1fr; + grid-template-columns: min-content min-content 1fr; white-space: nowrap; - .top, .record, .type { + .show { + display: block !important; + } + + .top, + .record, + .type, + .value { + display: none; border-bottom: 1px solid #ccc; padding: 4px 0px; } @@ -29,9 +37,38 @@ body { .top { font-weight: bold; background-color: #f8f8f8; + user-select: none; + grid-template-columns: repeat(3, min-content); + align-items: center; + + &.open { + .folder.open { + display: inline-block; + } + .folder.closed { + display: none; + } + } + + &:not(.open) { + .folder.open { + display: none; + } + .folder.closed { + display: inline-block; + } + } + + img { + + height: 16px; + display: none; + margin-right: 4px; + } + span:first-child { - /*color: #004680;*/ + color: #800033; } span:last-child { @@ -39,19 +76,24 @@ body { font-weight: normal; } } - .top + .type { - background-color: #f8f; + + .top+.type, + .top+.type+.value { + background-color: #f8f8f8; + } + + .top-most, + .top-most + .type, + .top-most + .type + .value + { + display:grid; } .record { - /*display: none;*/ + display: none; font-weight: normal; color: #444; - &.show { - display: block; - } - span:first-child { color: #800033; } @@ -61,7 +103,13 @@ body { } } - .record + .type { - color: #f0f; + .record+.type, + .record+.type+.value { + display: none; + padding-left: 16px; + } + + .record+.type+.value { + color: #800033; } } diff --git a/static/images/icon_folder.svg b/static/images/icon_folder.svg new file mode 100644 index 0000000..3ab4b80 --- /dev/null +++ b/static/images/icon_folder.svg @@ -0,0 +1,56 @@ + + + + + + + + folder-outline + + + + diff --git a/static/images/icon_folder_open.svg b/static/images/icon_folder_open.svg new file mode 100644 index 0000000..be1e573 --- /dev/null +++ b/static/images/icon_folder_open.svg @@ -0,0 +1,58 @@ + + + + + + + + folder-outline + folder-open-outline + + + + diff --git a/static/js/dns.mjs b/static/js/dns.mjs index b3dc270..91187d5 100644 --- a/static/js/dns.mjs +++ b/static/js/dns.mjs @@ -1,35 +1,6 @@ export class Application { constructor() { this.addTopHandlers() - this.moveRecords() - } - - moveRecords() { - const records = Array.from(document.querySelectorAll('.record')) - - records.sort((a, b) => { - if (a.innerText < b.innerText) return 1 - if (a.innerText > b.innerText) return -1 - return 0 - }) - - for (const r of records) { - const subTops = document.querySelectorAll(`.top[data-top="${r.dataset.top}"] + .type`) - if (subTops === null) { - continue - } - - let lastSubTop = subTops.item(subTops.length - 1) - if (lastSubTop === null) { - lastSubTop = document.querySelector(`.top[data-self="${r.dataset.top}"] + .type`) - } - - if (lastSubTop !== null) { - const other = Array.from(document.querySelectorAll(`[data-i="${r.dataset.i}"]`)) - console.log(other) - lastSubTop.after(...other) - } - } } addTopHandlers() { @@ -39,10 +10,40 @@ export class Application { handlerTop(event) { const topEl = event.target.closest('.top') + console.log(topEl.dataset.self) - const records = document.querySelectorAll(`.record[data-top="${topEl.dataset.self}"]`) - for (const r of records) { - r.classList.toggle('show') + let records, types, values + if (topEl.classList.contains('open')) { + records = document.querySelectorAll(`[data-top$="${topEl.dataset.self}"]`) + types = document.querySelectorAll(`[data-top$="${topEl.dataset.self}"] + .type`) + values = document.querySelectorAll(`[data-top$="${topEl.dataset.self}"] + .type + .value`) + + for (const r of records) { + r.classList.remove('show') + r.classList.remove('open') + } + + for (const r of types) + r.classList.remove('show') + for (const r of values) + r.classList.remove('show') + + topEl.classList.remove('open') + } else { + records = document.querySelectorAll(`[data-top="${topEl.dataset.self}"]`) + types = document.querySelectorAll(`[data-top="${topEl.dataset.self}"] + .type`) + values = document.querySelectorAll(`[data-top="${topEl.dataset.self}"] + .type + .value`) + + for (const r of records) + r.classList.add('show') + for (const r of types) + r.classList.add('show') + for (const r of values) + r.classList.add('show') + + topEl.classList.add('open') } + + } } diff --git a/webserver.go b/webserver.go index e555f39..d4fb454 100644 --- a/webserver.go +++ b/webserver.go @@ -67,18 +67,11 @@ func rootHandler(w http.ResponseWriter, r *http.Request) { data["DNSRecords"] = entries tree := BuildRecordsTree(entries) - htmlElements := tree.ToHTMLElements([]string{}, 0) + htmlElements := tree.ToHTMLElements([]string{}) var html string for _, el := range htmlElements { - if el.Header { - html += el.HTML - } - } - for _, el := range htmlElements { - if !el.Header { - html += el.HTML - } + html += el.HTML } data["Tree"] = template.HTML(html)