package main import ( // Standard "fmt" "maps" "slices" "strings" ) type DNSRecord struct { ID string `json:".id"` 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"` } type RecordsTree struct { Record DNSRecord Children []RecordsTree } func SortDNSRecord(a, b DNSRecord) int { aComponents := strings.Split(a.Name, ".") bComponents := strings.Split(b.Name, ".") slices.Reverse(aComponents) slices.Reverse(bComponents) for i, aComp := range aComponents { if i >= len(bComponents) { return -1 } bComp := bComponents[i] if aComp > bComp { return 1 } if aComp < bComp { return -1 } } 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, ".")) } func (r DNSRecord) Part(numParts int) string { splitName := strings.Split(r.Name, ".") slices.Reverse(splitName) parts := []string{} for i := range numParts { if i >= len(splitName) { break } parts = append(parts, splitName[i]) } slices.Reverse(parts) return strings.Join(parts, ".") } func (r DNSRecord) NameReversed() string { parts := strings.Split(r.Name, ".") slices.Reverse(parts) return strings.Join(parts, ".") } func BuildRecordsTree(records []DNSRecord) *DomainPart { topPart := new(DomainPart) topPart.Subparts = make(map[string]*DomainPart) for _, record := range records { curPart := topPart currentDomainNameSplit := strings.Split(record.Name, ".") slices.Reverse(currentDomainNameSplit) 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 } else { curPart = nextDomainPart } } } return topPart } type HTMLElement struct { Header bool HTML string } func (dp *DomainPart) ToHTMLElements(parts []string) []HTMLElement { var lines []HTMLElement sortedParts := slices.Sorted(maps.Keys(dp.Subparts)) 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 { 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
%s
%s
`, (len(newParts)-1)*32, restPart, // data-top mostSpecificPart, restPart, subpart.Record.Type, subpart.Record.String(), ) lines = append(lines, HTMLElement{Header: false, HTML: html}) } } return lines }