Rewrite domains page

This commit is contained in:
Melon 2024-07-24 23:54:12 +01:00
parent e27386f4dd
commit 9909f06a40
Signed by: melon
GPG Key ID: 6C9D970C50D26A25
23 changed files with 474 additions and 326 deletions

View File

@ -23,6 +23,7 @@
"vite": "^4.4.5"
},
"dependencies": {
"d3": "^7.9.0"
"d3": "^7.9.0",
"ipaddr.js": "^2.2.0"
}
}

View File

@ -0,0 +1,25 @@
<div class="cutoff-wrapper">
<span class="cutoff"><slot /></span>
</div>
<style lang="scss">
.cutoff-wrapper {
position: relative;
width: 100%;
}
.cutoff {
position: absolute;
top: 50%;
left: 0;
right: 0;
overflow: hidden;
text-wrap: nowrap;
text-overflow: ellipsis;
margin-inline: 15px;
display: inline-block;
vertical-align: middle;
line-height: 1rem;
transform: translateY(-50%);
}
</style>

View File

@ -0,0 +1,13 @@
<script>
import CutOff from "./CutOff.svelte";
</script>
<td {...$$restProps}>
<CutOff><slot /></CutOff>
</td>
<style lang="scss">
td {
width: 100%;
}
</style>

View File

@ -22,7 +22,7 @@
width: 100%;
height: 100%;
cursor: default;
background-color: rgba(0,0,0,0.55);
background-color: rgba(0, 0, 0, 0.55);
display: none;
&.show {
@ -45,6 +45,7 @@
position: fixed;
top: 50vh;
left: 50vw;
max-width: calc(100vw - 72px);
transform: translate(-50%, -50%);
background-color: $theme-header;
cursor: auto;

View File

@ -0,0 +1,40 @@
<script lang="ts">
import {DnsTypeA, DnsTypeAAAA, type AaaaRecord, type ARecord} from "../../types/records";
import {IPv4, IPv6, parse as parseAddr} from "ipaddr.js";
export let editItem: ARecord | AaaaRecord;
export let editMode: boolean;
let value: string = "";
function onChange() {
try {
let addr = parseAddr(value);
if (addr instanceof IPv4) {
(editItem as ARecord).A = addr.toString();
editItem.Hdr.Rrtype = DnsTypeA;
} else if (addr instanceof IPv6) {
(editItem as AaaaRecord).AAAA = addr.toString();
editItem.Hdr.Rrtype = DnsTypeAAAA;
}
} catch {
editItem.Hdr.Rrtype = 0;
console.error("Invalid IP address:", value);
}
}
</script>
<div>Name</div>
{#if editMode}
<div class="code-font">{editItem.Hdr.Name}</div>
{:else}
<div><input type="text" class="code-font" bind:value={editItem.Hdr.Name} size={Math.max(20, editItem.Hdr.Name.length + 2)} /></div>
{/if}
<div>IP Address</div>
<div><input type="text" class="code-font" bind:value on:keyup={onChange} size={Math.max(20, value.length + 2)} /></div>
{#if editItem.Hdr.Rrtype === DnsTypeA}
<div>IP address is IPv4</div>
{:else if editItem.Hdr.Rrtype === DnsTypeAAAA}
<div>IP address is IPv6</div>
{/if}

View File

@ -0,0 +1,17 @@
<script lang="ts">
import type {CaaRecord} from "../../types/records";
export let editItem: CaaRecord;
export let editMode: boolean;
</script>
<div>Name</div>
{#if editMode}
<div class="code-font">{editItem.Hdr.Name}</div>
{:else}
<div><input type="text" class="code-font" bind:value={editItem.Hdr.Name} size={Math.max(20, editItem.Hdr.Name.length + 2)} /></div>
{/if}
<div>Tag</div>
<div><input type="text" class="code-font" bind:value={editItem.Tag} size={Math.max(20, editItem.Tag.length + 2)} /></div>
<div>Value</div>
<div><input type="text" class="code-font" bind:value={editItem.Value} size={Math.max(20, editItem.Value.length + 2)} /></div>

View File

@ -0,0 +1,15 @@
<script lang="ts">
import type {CnameRecord} from "../../types/records";
export let editItem: CnameRecord;
export let editMode: boolean;
</script>
<div>Name</div>
{#if editMode}
<div class="code-font">{editItem.Hdr.Name}</div>
{:else}
<div><input type="text" class="code-font" bind:value={editItem.Hdr.Name} size={Math.max(20, editItem.Hdr.Name.length + 2)} /></div>
{/if}
<div>Target</div>
<div><input type="text" class="code-font" bind:value={editItem.Target} size={Math.max(20, editItem.Target.length + 2)} /></div>

View File

@ -0,0 +1,17 @@
<script lang="ts">
import type {MxRecord} from "../../types/records";
export let editItem: MxRecord;
export let editMode: boolean;
</script>
<div>Name</div>
{#if editMode}
<div class="code-font">{editItem.Hdr.Name}</div>
{:else}
<div><input type="text" class="code-font" bind:value={editItem.Hdr.Name} size={Math.max(20, editItem.Hdr.Name.length + 2)} /></div>
{/if}
<div>Mail Server</div>
<div><input type="text" class="code-font" bind:value={editItem.Mx} size={Math.max(20, editItem.Mx.length + 2)} /></div>
<div>Preference</div>
<div><input type="number" class="code-font" bind:value={editItem.Preference} /></div>

View File

@ -0,0 +1,15 @@
<script lang="ts">
import type {NsRecord} from "../../types/records";
export let editItem: NsRecord;
export let editMode: boolean;
</script>
<div>Name</div>
{#if editMode}
<div class="code-font">{editItem.Hdr.Name}</div>
{:else}
<div><input type="text" class="code-font" bind:value={editItem.Hdr.Name} size={Math.max(20, editItem.Hdr.Name.length + 2)} /></div>
{/if}
<div>Nameserver</div>
<div><input type="text" class="code-font" bind:value={editItem.Ns} size={Math.max(20, editItem.Ns.length + 2)} /></div>

View File

@ -0,0 +1,21 @@
<script lang="ts">
import type {SrvRecord} from "../../types/records";
export let editItem: SrvRecord;
export let editMode: boolean;
</script>
<div>Name</div>
{#if editMode}
<div class="code-font">{editItem.Hdr.Name}</div>
{:else}
<div><input type="text" class="code-font" bind:value={editItem.Hdr.Name} size={Math.max(20, editItem.Hdr.Name.length + 2)} /></div>
{/if}
<div>Priority</div>
<div><input type="number" class="code-font" bind:value={editItem.Priority} /></div>
<div>Weight</div>
<div><input type="number" class="code-font" bind:value={editItem.Weight} /></div>
<div>Port</div>
<div><input type="number" class="code-font" bind:value={editItem.Port} /></div>
<div>Target</div>
<div><input type="text" class="code-font" bind:value={editItem.Target} size={Math.max(20, editItem.Target.length + 2)} /></div>

View File

@ -0,0 +1,19 @@
<script lang="ts">
import type {TxtRecord} from "../../types/records";
export let editItem: TxtRecord;
export let editMode: boolean;
function constrain(min: number, max: number, value: number) {
return Math.min(max, Math.max(min, value));
}
</script>
<div>Name</div>
{#if editMode}
<div class="code-font">{editItem.Hdr.Name}</div>
{:else}
<div><input type="text" class="code-font" bind:value={editItem.Hdr.Name} size={Math.max(20, editItem.Hdr.Name.length + 2)} /></div>
{/if}
<div>Value</div>
<div><input type="text" class="code-font" bind:value={editItem.Txt[0]} size={constrain(20, 100, editItem.Txt[0].length + 2)} /></div>

View File

@ -1,8 +1,9 @@
<script lang="ts">
import {isAaaaRecord, isARecord, type AaaaRecord, type ARecord} from "../../stores/records";
import {isAaaaRecord, isARecord, type AaaaRecord, type ARecord} from "../../types/records";
import type {RestItem} from "../../utils/rest-table";
import ActionMenu from "../ActionMenu.svelte";
import ActionPopup from "../ActionPopup.svelte";
import ACreate from "../create-domains/ACreate.svelte";
export let value: RestItem<ARecord | AaaaRecord>;
let editItem: ARecord & AaaaRecord = {
@ -26,6 +27,7 @@
<tr>
<td class="code-font">{value.data.Hdr.Name}</td>
<td class="code-font">{isARecord(value.data) ? value.data.A : isAaaaRecord(value.data) ? value.data.AAAA : ""}</td>
<td class="code-font">{value.data.Hdr.Ttl}</td>
<td>
<ActionMenu
data={value}
@ -37,17 +39,7 @@
/>
<ActionPopup name="Edit {isARecord(value.data) ? 'A' : 'AAAA'} Record" bind:show={editPopup} on:save={save}>
<div>Name</div>
<div class="code-font">{editItem.Hdr.Name}</div>
{#if isARecord(value.data)}
<div>IPv4 Address</div>
<div><input type="text" class="code-font" bind:value={editItem.A} size={Math.max(20, editItem.A.length + 2)} /></div>
{:else if isAaaaRecord(value.data)}
<div>IPv6 Address</div>
<div><input type="text" class="code-font" bind:value={editItem.AAAA} size={Math.max(20, editItem.AAAA.length + 2)} /></div>
{:else}
<div>Pretty sure something is broken. WOMP WOMP!!</div>
{/if}
<ACreate bind:editItem editMode={true} />
</ActionPopup>
</td>
</tr>

View File

@ -1,8 +1,9 @@
<script lang="ts">
import type {CaaRecord} from "../../stores/records";
import type {CaaRecord} from "../../types/records";
import type {RestItem} from "../../utils/rest-table";
import ActionMenu from "../ActionMenu.svelte";
import ActionPopup from "../ActionPopup.svelte";
import CaaCreate from "../create-domains/CaaCreate.svelte";
export let value: RestItem<CaaRecord>;
let editItem: CaaRecord = {
@ -28,6 +29,7 @@
<td class="code-font">{value.data.Hdr.Name}</td>
<td class="code-font">{value.data.Tag}</td>
<td class="code-font">{value.data.Value}</td>
<td class="code-font">{value.data.Hdr.Ttl}</td>
<td>
<ActionMenu
data={value}
@ -39,12 +41,7 @@
/>
<ActionPopup name="Edit CAA Record" bind:show={editPopup} on:save={save}>
<div>Name</div>
<div class="code-font">{editItem.Hdr.Name}</div>
<div>Tag</div>
<div><input type="text" class="code-font" bind:value={editItem.Tag} size={Math.max(20, editItem.Tag.length + 2)} /></div>
<div>Value</div>
<div><input type="text" class="code-font" bind:value={editItem.Value} size={Math.max(20, editItem.Value.length + 2)} /></div>
<CaaCreate bind:editItem editMode={true} />
</ActionPopup>
</td>
</tr>

View File

@ -1,8 +1,9 @@
<script lang="ts">
import type {CnameRecord} from "../../stores/records";
import type {CnameRecord} from "../../types/records";
import type {RestItem} from "../../utils/rest-table";
import ActionMenu from "../ActionMenu.svelte";
import ActionPopup from "../ActionPopup.svelte";
import CnameCreate from "../create-domains/CnameCreate.svelte";
export let value: RestItem<CnameRecord>;
let editItem: CnameRecord = {
@ -25,6 +26,7 @@
<tr>
<td class="code-font">{value.data.Hdr.Name}</td>
<td class="code-font">{value.data.Target}</td>
<td class="code-font">{value.data.Hdr.Ttl}</td>
<td>
<ActionMenu
data={value}
@ -36,10 +38,7 @@
/>
<ActionPopup name="Edit CNAME Record" bind:show={editPopup} on:save={save}>
<div>Name</div>
<div class="code-font">{editItem.Hdr.Name}</div>
<div>Target</div>
<div><input type="text" class="code-font" bind:value={editItem.Target} size={Math.max(20, editItem.Target.length + 2)} /></div>
<CnameCreate bind:editItem editMode={true} />
</ActionPopup>
</td>
</tr>

View File

@ -1,8 +1,9 @@
<script lang="ts">
import type {MxRecord} from "../../stores/records";
import type {MxRecord} from "../../types/records";
import type {RestItem} from "../../utils/rest-table";
import ActionMenu from "../ActionMenu.svelte";
import ActionPopup from "../ActionPopup.svelte";
import MxCreate from "../create-domains/MxCreate.svelte";
export let value: RestItem<MxRecord>;
let editItem: MxRecord = {
@ -27,6 +28,7 @@
<td class="code-font">{value.data.Hdr.Name}</td>
<td class="code-font">{value.data.Mx}</td>
<td class="code-font">{value.data.Preference}</td>
<td class="code-font">{value.data.Hdr.Ttl}</td>
<td>
<ActionMenu
data={value}
@ -37,13 +39,8 @@
remove={() => value.remove()}
/>
<ActionPopup name="Edit SOA Record" bind:show={editPopup} on:save={save}>
<div>Name</div>
<div class="code-font">{editItem.Hdr.Name}</div>
<div>Mail Server</div>
<div><input type="text" class="code-font" bind:value={editItem.Mx} size={Math.max(20, editItem.Mx.length + 2)} /></div>
<div>Preference</div>
<div><input type="text" class="code-font" bind:value={editItem.Preference} size={Math.max(20, editItem.Preference.length + 2)} /></div>
<ActionPopup name="Edit MX Record" bind:show={editPopup} on:save={save}>
<MxCreate bind:editItem editMode={true} />
</ActionPopup>
</td>
</tr>

View File

@ -1,8 +1,9 @@
<script lang="ts">
import type {NsRecord} from "../../stores/records";
import type {NsRecord} from "../../types/records";
import type {RestItem} from "../../utils/rest-table";
import ActionMenu from "../ActionMenu.svelte";
import ActionPopup from "../ActionPopup.svelte";
import NsCreate from "../create-domains/NsCreate.svelte";
export let value: RestItem<NsRecord>;
let editItem: NsRecord = {
@ -25,6 +26,7 @@
<tr>
<td class="code-font">{value.data.Hdr.Name}</td>
<td class="code-font">{value.data.Ns}</td>
<td class="code-font">{value.data.Hdr.Ttl}</td>
<td>
<ActionMenu
data={value}
@ -36,10 +38,7 @@
/>
<ActionPopup name="Edit SOA Record" bind:show={editPopup} on:save={save}>
<div>Name</div>
<div class="code-font">{editItem.Hdr.Name}</div>
<div>Nameserver</div>
<div><input type="text" class="code-font" bind:value={editItem.Ns} size={Math.max(20, editItem.Ns.length + 2)} /></div>
<NsCreate bind:editItem editMode={true} />
</ActionPopup>
</td>
</tr>

View File

@ -1,5 +1,5 @@
<script lang="ts">
import type {SoaRecord} from "../../stores/records";
import type {SoaRecord} from "../../types/records";
import type {RestItem} from "../../utils/rest-table";
import ActionMenu from "../ActionMenu.svelte";
import ActionPopup from "../ActionPopup.svelte";

View File

@ -1,8 +1,9 @@
<script lang="ts">
import type {CnameRecord, SrvRecord} from "../../stores/records";
import type {SrvRecord} from "../../types/records";
import type {RestItem} from "../../utils/rest-table";
import ActionMenu from "../ActionMenu.svelte";
import ActionPopup from "../ActionPopup.svelte";
import SrvCreate from "../create-domains/SrvCreate.svelte";
export let value: RestItem<SrvRecord>;
let editItem: SrvRecord = {
@ -43,10 +44,7 @@
/>
<ActionPopup name="Edit CNAME Record" bind:show={editPopup} on:save={save}>
<div>Name</div>
<div class="code-font">{editItem.Hdr.Name}</div>
<div>Target</div>
<div><input type="text" class="code-font" bind:value={editItem.Target} size={Math.max(20, editItem.Target.length + 2)} /></div>
<SrvCreate bind:editItem editMode={true} />
</ActionPopup>
</td>
</tr>

View File

@ -1,8 +1,10 @@
<script lang="ts">
import type {TxtRecord} from "../../stores/records";
import type {TxtRecord} from "../../types/records";
import type {RestItem} from "../../utils/rest-table";
import ActionMenu from "../ActionMenu.svelte";
import ActionPopup from "../ActionPopup.svelte";
import TxtCreate from "../create-domains/TxtCreate.svelte";
import TdCutOff from "../CutOffTd.svelte";
export let value: RestItem<TxtRecord>;
let editItem: TxtRecord = {
@ -24,9 +26,8 @@
<tr>
<td class="code-font">{value.data.Hdr.Name}</td>
<td class="code-font">
<span class="cutoff">{value.data.Txt.join("\n")}</span>
</td>
<TdCutOff class="code-font">{value.data.Txt.join("\n")}</TdCutOff>
<td class="code-font">{value.data.Hdr.Ttl}</td>
<td>
<ActionMenu
data={value}
@ -38,10 +39,7 @@
/>
<ActionPopup name="Edit TXT Record" bind:show={editPopup} on:save={save}>
<div>Name</div>
<div class="code-font">{editItem.Hdr.Name}</div>
<div>Value</div>
<div><input type="text" class="code-font" bind:value={editItem.Txt[0]} size={Math.max(20, editItem.Txt[0].length + 2)} /></div>
<TxtCreate bind:editItem editMode={true} />
</ActionPopup>
</td>
</tr>

View File

@ -8,6 +8,8 @@ export const DnsTypeTXT = 16;
export const DnsTypeSRV = 33;
export const DnsTypeCAA = 257;
export type AllRecords = SoaRecord | NsRecord | MxRecord | ARecord | AaaaRecord | CnameRecord | TxtRecord | SrvRecord | CaaRecord;
export interface RecordHeader {
Name: string;
Rrtype: number;

View File

@ -0,0 +1,71 @@
<script lang="ts">
import {domainOption} from "../stores/domain-option";
import type {RestItem, RestTable} from "../utils/rest-table";
import PromiseTable from "../components/PromiseTable.svelte";
import PromiseLike from "../components/PromiseLike.svelte";
import type {AllRecords, UnknownRecord} from "../types/records";
import ActionPopup from "../components/ActionPopup.svelte";
type T = $$Generic<UnknownRecord>;
export let recordName: string;
export let table: RestTable<AllRecords>;
export let emptyRecord: (() => any) | null;
export let rowOrdering: (rows: RestItem<AllRecords>[], domain: string, isTRecord: (t: UnknownRecord) => t is T) => RestItem<T>[];
export let isTRecord: (t: UnknownRecord) => t is T;
let createItem: T | null = emptyRecord == null ? null : emptyRecord();
let createPopup: boolean = false;
function createRecord() {
table.addItem(createItem as any);
}
</script>
<div class="row">
<h2>{recordName} Records</h2>
{#if emptyRecord != null && createItem != null}
<button class="create-button" on:click={() => (createPopup = true)}>Create {recordName} Record</button>
<ActionPopup name="Create {recordName} Record" bind:show={createPopup} on:save={createRecord}>
<slot name="create" editItem={createItem} editMode={false} />
</ActionPopup>
{/if}
</div>
<PromiseTable value={table}>
<slot name="headers" slot="headers" />
<svelte:fragment slot="rows" let:value>
{#each rowOrdering(value.rows, $domainOption, isTRecord) as item}
<PromiseLike value={item}>
<tr slot="loading" class="empty-row">
<td colspan="100">
<div>Loading...</div>
</td>
</tr>
<tr slot="error" let:reason class="empty-row">
<td colspan="100">Error loading row for {item.data.Hdr.Name}: {reason}</td>
</tr>
<slot name="row" slot="ok" let:value {value} />
</PromiseLike>
{/each}
</svelte:fragment>
</PromiseTable>
<style lang="scss">
@import "../values.scss";
.row {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.create-button {
@include button-green-box;
}
</style>

View File

@ -1,8 +1,14 @@
<script lang="ts">
import {LOGIN} from "../utils/login";
import {domainOption} from "../stores/domain-option";
import {
DnsTypeA,
DnsTypeCAA,
DnsTypeCNAME,
DnsTypeMX,
DnsTypeNS,
DnsTypeSOA,
DnsTypeSRV,
DnsTypeTXT,
isARecord,
isAaaaRecord,
isCaaRecord,
@ -14,6 +20,7 @@
isTxtRecord,
type ARecord,
type AaaaRecord,
type AllRecords,
type CaaRecord,
type CnameRecord,
type MxRecord,
@ -22,9 +29,7 @@
type SrvRecord,
type TxtRecord,
type UnknownRecord,
} from "../stores/records";
import ActionMenu from "../components/ActionMenu.svelte";
import PromiseTable from "../components/PromiseTable.svelte";
} from "../types/records";
import {RestItem, RestTable} from "../utils/rest-table";
import PromiseLike from "../components/PromiseLike.svelte";
import SoaRow from "../components/domains/SoaRow.svelte";
@ -35,15 +40,21 @@
import TxtRow from "../components/domains/TxtRow.svelte";
import CaaRow from "../components/domains/CaaRow.svelte";
import SrvRow from "../components/domains/SrvRow.svelte";
import DomainTableView from "./DomainTableView.svelte";
import ActionPopup from "../components/ActionPopup.svelte";
import NsCreate from "../components/create-domains/NsCreate.svelte";
import MxCreate from "../components/create-domains/MxCreate.svelte";
import ACreate from "../components/create-domains/ACreate.svelte";
import CnameCreate from "../components/create-domains/CnameCreate.svelte";
import CaaCreate from "../components/create-domains/CaaCreate.svelte";
import TxtCreate from "../components/create-domains/TxtCreate.svelte";
const apiAzalea = import.meta.env.VITE_API_AZALEA;
type AllRecords = SoaRecord | NsRecord | MxRecord | ARecord | AaaaRecord | CnameRecord | TxtRecord | SrvRecord | CaaRecord;
const table = new RestTable<AllRecords>(apiAzalea + "/domains/" + $domainOption + "/records", (item: AllRecords) => item.Hdr.Name);
function rowOrdering<T extends UnknownRecord>(
rows: RestItem<UnknownRecord>[],
rows: RestItem<AllRecords>[],
domain: string,
isTRecord: (t: UnknownRecord) => t is T,
): RestItem<T>[] {
@ -54,6 +65,7 @@
}
function domainFilter(src: string, domain: string) {
domain = fqdn(domain);
if (domain == "*") return true;
let n = src.indexOf("/");
if (n == -1) n = src.length;
@ -71,6 +83,11 @@
return name;
}
function fqdn(domain: string): string {
if (domain.endsWith(".")) return domain;
return domain + ".";
}
let soaRecords: SoaRecord[] = [
{
Hdr: {
@ -88,285 +105,174 @@
Minttl: 0,
},
];
let domainTitle: string = getTitleDomain(soaRecords[0].Hdr.Name);
let zoneFileUrl: string = `${import.meta.env.VITE_API_AZALEA}/domains/${domainTitle}/zone-file`;
let recordTypes = [
{
name: "SOA",
headers: ["Primary Domain", "Email", "Default TTL", "Refresh Rate", "Retry Rate", "Expire Time"],
filter: isSoaRecord,
render: SoaRow,
create: null,
save: null,
empty: null,
},
{
name: "NS",
headers: ["Subdomain", "Name Server", "TTL"],
filter: isNsRecord,
render: NsRow,
create: NsCreate,
empty: (): NsRecord => ({
Hdr: {
Name: "",
Rrtype: DnsTypeNS,
Class: 1,
Ttl: 0,
},
Ns: "",
}),
},
{
name: "MX",
headers: ["Mail Server", "Preference", "Subdomain", "TTL"],
filter: isMxRecord,
render: MxRow,
create: MxCreate,
empty: (): MxRecord => ({
Hdr: {
Name: "",
Rrtype: DnsTypeMX,
Class: 1,
Ttl: 0,
},
Mx: "",
Preference: 0,
}),
},
{
name: "A/AAAA",
headers: ["Hostname", "IP Address", "TTL"],
filter: (t: UnknownRecord) => isARecord(t) || isAaaaRecord(t),
render: ARow,
create: ACreate,
empty: (): ARecord | AaaaRecord => ({
Hdr: {
Name: "",
Rrtype: 0, // this is on purpose
Class: 1,
Ttl: 0,
},
A: "",
AAAA: "",
}),
},
{
name: "CNAME",
headers: ["Hostname", "Aliases to", "TTL"],
filter: isCnameRecord,
render: CnameRow,
create: CnameCreate,
empty: (): CnameRecord => ({
Hdr: {
Name: "",
Rrtype: DnsTypeCNAME,
Class: 1,
Ttl: 0,
},
Target: "",
}),
},
{
name: "TXT",
headers: ["Hostname", "Value", "TTL"],
filter: isTxtRecord,
render: TxtRow,
create: TxtCreate,
empty: (): TxtRecord => ({
Hdr: {
Name: "",
Rrtype: DnsTypeTXT,
Class: 1,
Ttl: 0,
},
Txt: [""],
}),
},
{
name: "SRV",
headers: ["Name", "Priority", "Weight", "Port", "Target", "TTL"],
filter: isSrvRecord,
render: SrvRow,
create: null,
empty: (): SrvRecord => ({
Hdr: {
Name: "",
Rrtype: DnsTypeSRV,
Class: 1,
Ttl: 0,
},
Priority: 0,
Weight: 0,
Port: 0,
Target: "",
}),
},
{
name: "CAA",
headers: ["Name", "Tag", "Value", "TTL"],
filter: isCaaRecord,
render: CaaRow,
create: CaaCreate,
empty: (): CaaRecord => ({
Hdr: {
Name: "",
Rrtype: DnsTypeCAA,
Class: 1,
Ttl: 0,
},
Flag: 0,
Tag: "",
Value: "",
}),
},
];
function toAny(a: any) {
return a as any;
}
</script>
{#if soaRecords.length >= 1}
<div class="title-row">
<h1>Domains / {getTitleDomain(soaRecords[0].Hdr.Name)}</h1>
<a
class="zone-download"
href="{import.meta.env.VITE_API_AZALEA}/domains/{getTitleDomain(soaRecords[0].Hdr.Name)}/zone-file"
download="{getTitleDomain(soaRecords[0].Hdr.Name)}.zone"
>
Download DNS Zone File
</a>
<h1>Domains / {domainTitle}</h1>
<a class="zone-download" href={zoneFileUrl} download="{domainTitle}.zone">Download DNS Zone File</a>
</div>
{/if}
<h2>SOA Record</h2>
<PromiseTable value={table}>
<tr slot="headers">
<th>Primary Domain</th>
<th>Email</th>
<th>Default TTL</th>
<th>Refresh Rate</th>
<th>Retry Rate</th>
<th>Expire Time</th>
<th></th>
</tr>
{#each recordTypes as recordType}
<DomainTableView recordName={recordType.name} {table} emptyRecord={recordType.empty} {rowOrdering} isTRecord={recordType.filter}>
<tr slot="headers">
{#each recordType.headers as header}
<th>{header}</th>
{/each}
<th></th>
</tr>
<svelte:fragment slot="rows" let:value>
{#each rowOrdering(value.rows, $domainOption, isSoaRecord) as item}
<PromiseLike value={item}>
<tr slot="loading" class="empty-row">
<td colspan="100">
<div>Loading...</div>
</td>
</tr>
<svelte:fragment slot="create" let:editItem let:editMode>
{#if recordType.create != null && recordType.empty != null}
<svelte:component this={recordType.create} editItem={toAny(editItem)} {editMode} />
{/if}
</svelte:fragment>
<tr slot="error" let:reason class="empty-row">
<td colspan="100">Error loading row for {item.data.Hdr.Name}: {reason}</td>
</tr>
<SoaRow slot="ok" let:value {value} />
</PromiseLike>
{/each}
</svelte:fragment>
</PromiseTable>
<h2>NS Record</h2>
<PromiseTable value={table}>
<tr slot="headers">
<th>Name Server</th>
<th>Subdomain</th>
<th>TTL</th>
<th></th>
</tr>
<svelte:fragment slot="rows" let:value>
{#each rowOrdering(value.rows, $domainOption, isNsRecord) as item}
<PromiseLike value={item}>
<tr slot="loading" class="empty-row">
<td colspan="100">
<div>Loading...</div>
</td>
</tr>
<tr slot="error" let:reason class="empty-row">
<td colspan="100">Error loading row for {item.data.Hdr.Name}: {reason}</td>
</tr>
<NsRow slot="ok" let:value {value} />
</PromiseLike>
{/each}
</svelte:fragment>
</PromiseTable>
<h2>MX Record</h2>
<PromiseTable value={table}>
<tr slot="headers">
<th>Mail Server</th>
<th>Preference</th>
<th>Subdomain</th>
<th>TTL</th>
<th></th>
</tr>
<svelte:fragment slot="rows" let:value>
{#each rowOrdering(value.rows, $domainOption, isMxRecord) as item}
<PromiseLike value={item}>
<tr slot="loading" class="empty-row">
<td colspan="100">
<div>Loading...</div>
</td>
</tr>
<tr slot="error" let:reason class="empty-row">
<td colspan="100">Error loading row for {item.data.Hdr.Name}: {reason}</td>
</tr>
<MxRow slot="ok" let:value {value} />
</PromiseLike>
{/each}
</svelte:fragment>
</PromiseTable>
<h2>A/AAAA Record</h2>
<PromiseTable value={table}>
<tr slot="headers">
<th>Hostname</th>
<th>IP Address</th>
<th>TTL</th>
<th></th>
</tr>
<svelte:fragment slot="rows" let:value>
{#each rowOrdering(value.rows, $domainOption, t => isARecord(t) || isAaaaRecord(t)) as item}
<PromiseLike value={item}>
<tr slot="loading" class="empty-row">
<td colspan="100">
<div>Loading...</div>
</td>
</tr>
<tr slot="error" let:reason class="empty-row">
<td colspan="100">Error loading row for {item.data.Hdr.Name}: {reason}</td>
</tr>
<ARow slot="ok" let:value {value} />
</PromiseLike>
{/each}
</svelte:fragment>
</PromiseTable>
<h2>CNAME Record</h2>
<PromiseTable value={table}>
<tr slot="headers">
<th>Hostname</th>
<th>Aliases to</th>
<th>TTL</th>
<th></th>
</tr>
<svelte:fragment slot="rows" let:value>
{#each rowOrdering(value.rows, $domainOption, isCnameRecord) as item}
<PromiseLike value={item}>
<tr slot="loading" class="empty-row">
<td colspan="100">
<div>Loading...</div>
</td>
</tr>
<tr slot="error" let:reason class="empty-row">
<td colspan="100">Error loading row for {item.data.Hdr.Name}: {reason}</td>
</tr>
<CnameRow slot="ok" let:value {value} />
</PromiseLike>
{/each}
</svelte:fragment>
</PromiseTable>
<h2>TXT Record</h2>
<PromiseTable value={table}>
<tr slot="headers">
<th>Hostname</th>
<th>Value</th>
<th>TTL</th>
<th></th>
</tr>
<svelte:fragment slot="rows" let:value>
{#each rowOrdering(value.rows, $domainOption, isTxtRecord) as item}
<PromiseLike value={item}>
<tr slot="loading" class="empty-row">
<td colspan="100">
<div>Loading...</div>
</td>
</tr>
<tr slot="error" let:reason class="empty-row">
<td colspan="100">Error loading row for {item.data.Hdr.Name}: {reason}</td>
</tr>
<TxtRow slot="ok" let:value {value} />
</PromiseLike>
{/each}
</svelte:fragment>
</PromiseTable>
<h2>SRV Record</h2>
<PromiseTable value={table}>
<tr slot="headers">
<th>Name</th>
<th>Priority</th>
<th>Weight</th>
<th>Port</th>
<th>Target</th>
<th>TTL</th>
<th></th>
</tr>
<svelte:fragment slot="rows" let:value>
{#each rowOrdering(value.rows, $domainOption, isSrvRecord) as item}
<PromiseLike value={item}>
<tr slot="loading" class="empty-row">
<td colspan="100">
<div>Loading...</div>
</td>
</tr>
<tr slot="error" let:reason class="empty-row">
<td colspan="100">Error loading row for {item.data.Hdr.Name}: {reason}</td>
</tr>
<SrvRow slot="ok" let:value {value} />
</PromiseLike>
{/each}
</svelte:fragment>
</PromiseTable>
<h2>CAA Record</h2>
<PromiseTable value={table}>
<tr slot="headers">
<th>Name</th>
<th>Tag</th>
<th>Value</th>
<th>TTL</th>
<th></th>
</tr>
<svelte:fragment slot="rows" let:value>
{#each rowOrdering(value.rows, $domainOption, isCaaRecord) as item}
<PromiseLike value={item}>
<tr slot="loading" class="empty-row">
<td colspan="100">
<div>Loading...</div>
</td>
</tr>
<tr slot="error" let:reason class="empty-row">
<td colspan="100">Error loading row for {item.data.Hdr.Name}: {reason}</td>
</tr>
<CaaRow slot="ok" let:value {value} />
</PromiseLike>
{/each}
</svelte:fragment>
</PromiseTable>
<svelte:component this={recordType.render} slot="row" let:value {value} />
</DomainTableView>
{/each}
<style lang="scss">
@import "../values.scss";
button.action-menu {
@include button-green-highlight;
}
table tbody tr {
td {
position: relative;
span.cutoff {
position: absolute;
top: 50%;
left: 0;
right: 0;
overflow: hidden;
text-wrap: nowrap;
text-overflow: ellipsis;
margin-inline: 15px;
display: inline-block;
vertical-align: middle;
line-height: 1rem;
transform: translateY(-50%);
}
}
&.empty-row td {
text-align: center;
}
}
.title-row {
display: flex;
flex-direction: row;

View File

@ -731,6 +731,11 @@ inherits@2:
resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009"
integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
ipaddr.js@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8"
integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz"