Start working on domain management tab

This commit is contained in:
Melon 2024-07-19 17:59:14 +01:00
parent f55e6c798a
commit 2f6d043b63
Signed by: melon
GPG Key ID: 6C9D970C50D26A25
9 changed files with 657 additions and 291 deletions

View File

@ -0,0 +1,53 @@
<script lang="ts">
import {isAaaaRecord, isARecord, type AaaaRecord, type ARecord} from "../../stores/records";
import type {RestItem} from "../../utils/rest-table";
import ActionMenu from "../ActionMenu.svelte";
import ActionPopup from "../ActionPopup.svelte";
export let value: RestItem<ARecord | AaaaRecord>;
let editItem: ARecord & AaaaRecord = {
Hdr: {
Name: "",
Rrtype: 0,
Class: 0,
Ttl: 0,
},
A: "",
AAAA: "",
};
let editPopup: boolean = false;
function save() {
value.update(editItem);
}
</script>
<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>
<ActionMenu
data={value}
edit={() => {
editItem = JSON.parse(JSON.stringify(value.data));
editPopup = true;
}}
remove={() => value.remove()}
/>
<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}
</ActionPopup>
</td>
</tr>

View File

@ -0,0 +1,45 @@
<script lang="ts">
import type {CnameRecord} from "../../stores/records";
import type {RestItem} from "../../utils/rest-table";
import ActionMenu from "../ActionMenu.svelte";
import ActionPopup from "../ActionPopup.svelte";
export let value: RestItem<CnameRecord>;
let editItem: CnameRecord = {
Hdr: {
Name: "",
Rrtype: 0,
Class: 0,
Ttl: 0,
},
Target: "",
};
let editPopup: boolean = false;
function save() {
value.update(editItem);
}
</script>
<tr>
<td class="code-font">{value.data.Hdr.Name}</td>
<td class="code-font">{value.data.Target}</td>
<td>
<ActionMenu
data={value}
edit={() => {
editItem = JSON.parse(JSON.stringify(value.data));
editPopup = true;
}}
remove={() => value.remove()}
/>
<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>
</ActionPopup>
</td>
</tr>

View File

@ -0,0 +1,49 @@
<script lang="ts">
import type {MxRecord} from "../../stores/records";
import type {RestItem} from "../../utils/rest-table";
import ActionMenu from "../ActionMenu.svelte";
import ActionPopup from "../ActionPopup.svelte";
export let value: RestItem<MxRecord>;
let editItem: MxRecord = {
Hdr: {
Name: "",
Rrtype: 0,
Class: 0,
Ttl: 0,
},
Mx: "",
Preference: 0,
};
let editPopup: boolean = false;
function save() {
value.update(editItem);
}
</script>
<tr>
<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>
<ActionMenu
data={value}
edit={() => {
editItem = JSON.parse(JSON.stringify(value.data));
editPopup = true;
}}
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>
</td>
</tr>

View File

@ -0,0 +1,45 @@
<script lang="ts">
import type {NsRecord} from "../../stores/records";
import type {RestItem} from "../../utils/rest-table";
import ActionMenu from "../ActionMenu.svelte";
import ActionPopup from "../ActionPopup.svelte";
export let value: RestItem<NsRecord>;
let editItem: NsRecord = {
Hdr: {
Name: "",
Rrtype: 0,
Class: 0,
Ttl: 0,
},
Ns: "",
};
let editPopup: boolean = false;
function save() {
value.update(editItem);
}
</script>
<tr>
<td class="code-font">{value.data.Hdr.Name}</td>
<td class="code-font">{value.data.Ns}</td>
<td>
<ActionMenu
data={value}
edit={() => {
editItem = JSON.parse(JSON.stringify(value.data));
editPopup = true;
}}
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>Nameserver</div>
<div><input type="text" class="code-font" bind:value={editItem.Ns} size={Math.max(20, editItem.Ns.length + 2)} /></div>
</ActionPopup>
</td>
</tr>

View File

@ -0,0 +1,63 @@
<script lang="ts">
import type {SoaRecord} from "../../stores/records";
import type {RestItem} from "../../utils/rest-table";
import ActionMenu from "../ActionMenu.svelte";
import ActionPopup from "../ActionPopup.svelte";
export let value: RestItem<SoaRecord>;
let editItem: SoaRecord = {
Hdr: {
Name: "",
Rrtype: 0,
Class: 0,
Ttl: 0,
},
Ns: "",
Mbox: "",
Serial: 0,
Refresh: 0,
Retry: 0,
Expire: 0,
Minttl: 0,
};
let editPopup: boolean = false;
function save() {
value.update(editItem);
}
</script>
<tr>
<td class="code-font">{value.data.Hdr.Name}</td>
<td class="code-font">{value.data.Mbox}</td>
<td class="code-font">{value.data.Minttl}</td>
<td class="code-font">{value.data.Refresh}</td>
<td class="code-font">{value.data.Retry}</td>
<td class="code-font">{value.data.Expire}</td>
<td>
<ActionMenu
data={value}
edit={() => {
editItem = JSON.parse(JSON.stringify(value.data));
editPopup = true;
}}
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>Mailbox</div>
<div><input type="text" class="code-font" bind:value={editItem.Mbox} size={Math.max(20, editItem.Mbox.length + 2)} /></div>
<div>Minimum Time-to-Live</div>
<div><input type="number" class="code-font" bind:value={editItem.Minttl} size={Math.max(20, editItem.Minttl.length + 2)} /></div>
<div>Refresh</div>
<div><input type="number" class="code-font" bind:value={editItem.Refresh} size={Math.max(20, editItem.Refresh.length + 2)} /></div>
<div>Retry</div>
<div><input type="number" class="code-font" bind:value={editItem.Retry} size={Math.max(20, editItem.Retry.length + 2)} /></div>
<div>Expire</div>
<div><input type="number" class="code-font" bind:value={editItem.Expire} size={Math.max(20, editItem.Expire.length + 2)} /></div>
</ActionPopup>
</td>
</tr>

View File

@ -0,0 +1,47 @@
<script lang="ts">
import type {TxtRecord} from "../../stores/records";
import type {RestItem} from "../../utils/rest-table";
import ActionMenu from "../ActionMenu.svelte";
import ActionPopup from "../ActionPopup.svelte";
export let value: RestItem<TxtRecord>;
let editItem: TxtRecord = {
Hdr: {
Name: "",
Rrtype: 0,
Class: 0,
Ttl: 0,
},
Txt: [""],
};
let editPopup: boolean = false;
function save() {
value.update(editItem);
}
</script>
<tr>
<td class="code-font">{value.data.Hdr.Name}</td>
<td class="code-font">
<span class="cutoff">{value.data.Txt.join("\n")}</span>
</td>
<td>
<ActionMenu
data={value}
edit={() => {
editItem = JSON.parse(JSON.stringify(value.data));
editPopup = true;
}}
remove={() => value.remove()}
/>
<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>
</ActionPopup>
</td>
</tr>

View File

@ -1,4 +1,12 @@
import {writable} from "svelte/store";
export const DnsTypeSOA = 6;
export const DnsTypeNS = 2;
export const DnsTypeMX = 15;
export const DnsTypeA = 1;
export const DnsTypeAAAA = 28;
export const DnsTypeCNAME = 5;
export const DnsTypeTXT = 16;
export const DnsTypeSRV = 33;
export const DnsTypeCAA = 257;
export interface RecordHeader {
Name: string;
@ -22,72 +30,58 @@ export interface SoaRecord extends UnknownRecord {
}
export function isSoaRecord(x: UnknownRecord): x is SoaRecord {
return x.Hdr.Rrtype === 6;
return x.Hdr.Rrtype === DnsTypeSOA;
}
export const soaRecords = writable<Array<SoaRecord>>([]);
export interface NsRecord extends UnknownRecord {
Ns: string;
}
export function isNsRecord(x: UnknownRecord): x is NsRecord {
return x.Hdr.Rrtype === 2;
return x.Hdr.Rrtype === DnsTypeNS;
}
export const nsRecords = writable<Array<NsRecord>>([]);
export interface MxRecord extends UnknownRecord {
Preference: number;
Mx: string;
}
export function isMxRecord(x: UnknownRecord): x is MxRecord {
return x.Hdr.Rrtype === 15;
return x.Hdr.Rrtype === DnsTypeMX;
}
export const mxRecords = writable<Array<MxRecord>>([]);
export interface ARecord extends UnknownRecord {
A: string;
}
export function isARecord(x: UnknownRecord): x is ARecord {
return x.Hdr.Rrtype === 1;
return x.Hdr.Rrtype === DnsTypeA;
}
export const aRecords = writable<Array<ARecord>>([]);
export interface AaaaRecord extends UnknownRecord {
AAAA: string;
}
export function isAaaaRecord(x: UnknownRecord): x is AaaaRecord {
return x.Hdr.Rrtype === 28;
return x.Hdr.Rrtype === DnsTypeAAAA;
}
export const aaaaRecords = writable<Array<AaaaRecord>>([]);
export interface CnameRecord extends UnknownRecord {
Target: string;
}
export function isCnameRecord(x: UnknownRecord): x is CnameRecord {
return x.Hdr.Rrtype === 5;
return x.Hdr.Rrtype === DnsTypeCNAME;
}
export const cnameRecords = writable<Array<CnameRecord>>([]);
export interface TxtRecord extends UnknownRecord {
Txt: Array<string>;
}
export function isTxtRecord(x: UnknownRecord): x is TxtRecord {
return x.Hdr.Rrtype === 16;
return x.Hdr.Rrtype === DnsTypeTXT;
}
export const txtRecords = writable<Array<TxtRecord>>([]);
export interface SrvRecord extends UnknownRecord {
Priority: number;
Weight: number;
@ -96,11 +90,9 @@ export interface SrvRecord extends UnknownRecord {
}
export function isSrvRecord(x: UnknownRecord): x is SrvRecord {
return x.Hdr.Rrtype === 33;
return x.Hdr.Rrtype === DnsTypeSRV;
}
export const srvRecords = writable<Array<SrvRecord>>([]);
export interface CaaRecord extends UnknownRecord {
Flag: number;
Tag: string;
@ -108,7 +100,5 @@ export interface CaaRecord extends UnknownRecord {
}
export function isCaaRecord(x: UnknownRecord): x is CaaRecord {
return x.Hdr.Rrtype === 257;
return x.Hdr.Rrtype === DnsTypeCAA;
}
export const caaRecords = writable<Array<CaaRecord>>([]);

View File

@ -2,10 +2,7 @@
import {LOGIN} from "../utils/login";
import {domainOption} from "../stores/domain-option";
import {
aRecords,
aaaaRecords,
caaRecords,
cnameRecords,
DnsTypeSOA,
isARecord,
isAaaaRecord,
isCaaRecord,
@ -15,38 +12,50 @@
isSoaRecord,
isSrvRecord,
isTxtRecord,
mxRecords,
nsRecords,
soaRecords,
srvRecords,
txtRecords,
type ARecord,
type AaaaRecord,
type CaaRecord,
type CnameRecord,
type MxRecord,
type NsRecord,
type SoaRecord,
type SrvRecord,
type TxtRecord,
type UnknownRecord,
} from "../stores/records";
import ActionMenu from "../components/ActionMenu.svelte";
import PromiseTable from "../components/PromiseTable.svelte";
import {RestItem, RestTable} from "../utils/rest-table";
import PromiseLike from "../components/PromiseLike.svelte";
import SoaRow from "../components/domains/SoaRow.svelte";
const apiAzalea = import.meta.env.VITE_API_AZALEA;
let promiseForTable: Promise<void> = reloadTable();
type AllRecords = SoaRecord | NsRecord | MxRecord | ARecord | AaaaRecord | CnameRecord | TxtRecord | SrvRecord | CaaRecord;
async function reloadTable(): Promise<void> {
let f = await LOGIN.clientRequest(apiAzalea + "/domains/" + $domainOption + "/records", {});
if (f.status != 200) throw new Error("Unexpected status code: " + f.status);
let fJson = await f.json();
let rows = fJson as Array<UnknownRecord>;
$soaRecords = rows.filter(isSoaRecord);
$nsRecords = rows.filter(isNsRecord);
$mxRecords = rows.filter(isMxRecord);
$aRecords = rows.filter(isARecord);
$aaaaRecords = rows.filter(isAaaaRecord);
$cnameRecords = rows.filter(isCnameRecord);
$txtRecords = rows.filter(isTxtRecord);
$srvRecords = rows.filter(isSrvRecord);
$caaRecords = rows.filter(isCaaRecord);
const table = new RestTable<AllRecords>(apiAzalea + "/domains/" + $domainOption + "/records", (item: AllRecords) => item.Hdr.Name);
function rowOrdering<T extends UnknownRecord>(
rows: RestItem<UnknownRecord>[],
domain: string,
isTRecord: (t: UnknownRecord) => t is T,
): RestItem<T>[] {
return rows
.filter(x => isTRecord(x.data))
.filter(x => domainFilter(x.data.Hdr.Name, domain))
.sort((a, b) => a.data.Hdr.Name.localeCompare(b.data.Hdr.Name)) as unknown as RestItem<T>[];
}
domainOption.subscribe(x => {
promiseForTable = reloadTable();
});
function domainFilter(src: string, domain: string) {
if (domain == "*") return true;
let n = src.indexOf("/");
if (n == -1) n = src.length;
let p = src.slice(0, n);
if (p == domain) return true;
return p.endsWith(domain);
}
domainOption.subscribe(() => table.reload());
function getTitleDomain(name: string): string {
if (name.endsWith(".")) {
@ -54,266 +63,309 @@
}
return name;
}
let soaRecords: SoaRecord[] = [
{
Hdr: {
Name: "example.com.",
Rrtype: DnsTypeSOA,
Class: 1,
Ttl: 300,
},
Ns: "ns1.example.com.",
Mbox: "postmaster.example.com.",
Serial: 0,
Refresh: 0,
Retry: 0,
Expire: 0,
Minttl: 0,
},
];
</script>
{#await promiseForTable}
<div class="text-padding">
<div>Loading...</div>
{#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>
</div>
{:then}
{#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>
</div>
{/if}
{/if}
<h2>SOA Record</h2>
<table class="action-table" aria-label="List of Domains SOA Record">
<thead>
<tr>
<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>
</thead>
<tbody>
{#if $soaRecords.length === 0}
<tr class="empty-row"><td colspan="7">No items to display</td></tr>
{/if}
{#each $soaRecords as record}
<tr>
<td>{record.Hdr.Name}</td>
<td>{record.Mbox}</td>
<td>{record.Minttl}</td>
<td>{record.Refresh}</td>
<td>{record.Retry}</td>
<td>{record.Expire}</td>
<td>
<ActionMenu data={record} edit={t => console.log(t)} remove={null} />
<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>
<svelte:fragment slot="rows" let:value>
{#each rowOrdering(value.rows, $domainOption, DnsTypeSOA) as item}
<PromiseLike value={item}>
<tr slot="loading" class="empty-row">
<td colspan="100">
<div>Loading...</div>
</td>
</tr>
{/each}
</tbody>
</table>
<h2>NS Record</h2>
<table class="action-table" aria-label="List of Domains NS Record">
<thead>
<tr>
<th>Name Server</th>
<th>Subdomain</th>
<th>TTL</th>
<th></th>
</tr>
</thead>
<tbody>
{#if $nsRecords.length === 0}
<tr class="empty-row"><td colspan="7">No items to display</td></tr>
{/if}
{#each $nsRecords as record}
<tr>
<td>{record.Ns}</td>
<td>{record.Hdr.Name}</td>
<td>{record.Hdr.Ttl}</td>
<td></td>
<tr slot="error" let:reason class="empty-row">
<td colspan="100">Error loading row for {item.data.Hdr.Name}: {reason}</td>
</tr>
{/each}
</tbody>
</table>
<h2>MX Record</h2>
<table class="action-table" aria-label="List of Domains MX Record">
<thead>
<SoaRow slot="ok" let:value {value} />
</PromiseLike>
{/each}
</svelte:fragment>
</PromiseTable>
<table class="action-table" aria-label="List of Domains SOA Record">
<thead>
<tr>
<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>
</thead>
<tbody>
{#if $soaRecords.length === 0}
<tr class="empty-row"><td colspan="7">No items to display</td></tr>
{/if}
{#each $soaRecords as record}
<tr>
<th>Mail Server</th>
<th>Preference</th>
<th>Subdomain</th>
<th>TTL</th>
<th></th>
<td>{record.Hdr.Name}</td>
<td>{record.Mbox}</td>
<td>{record.Minttl}</td>
<td>{record.Refresh}</td>
<td>{record.Retry}</td>
<td>{record.Expire}</td>
<td>
<ActionMenu data={record} edit={t => console.log(t)} remove={null} />
</td>
</tr>
</thead>
<tbody>
{#if $mxRecords.length === 0}
<tr class="empty-row"><td colspan="7">No items to display</td></tr>
{/if}
{#each $mxRecords as record}
<tr>
<td>{record.Mx}</td>
<td>{record.Preference}</td>
<td>{record.Hdr.Name}</td>
<td>{record.Hdr.Ttl}</td>
<td>
<ActionMenu data={record} edit={t => console.log(t)} remove={t => console.log(t)} />
</td>
</tr>
{/each}
</tbody>
</table>
{/each}
</tbody>
</table>
<h2>A/AAAA Record</h2>
<table class="action-table" aria-label="List of Domains A/AAAA Record">
<thead>
<h2>NS Record</h2>
<table class="action-table" aria-label="List of Domains NS Record">
<thead>
<tr>
<th>Name Server</th>
<th>Subdomain</th>
<th>TTL</th>
<th></th>
</tr>
</thead>
<tbody>
{#if $nsRecords.length === 0}
<tr class="empty-row"><td colspan="7">No items to display</td></tr>
{/if}
{#each $nsRecords as record}
<tr>
<th>Hostname</th>
<th>IP Address</th>
<th>TTL</th>
<th></th>
<td>{record.Ns}</td>
<td>{record.Hdr.Name}</td>
<td>{record.Hdr.Ttl}</td>
<td></td>
</tr>
</thead>
<tbody>
{#if $aRecords.length === 0 && $aaaaRecords.length === 0}
<tr class="empty-row"><td colspan="7">No items to display</td></tr>
{/if}
{#each $aRecords as record}
<tr>
<td>{record.Hdr.Name}</td>
<td>{record.A}</td>
<td>{record.Hdr.Ttl}</td>
<td>
<ActionMenu data={record} edit={t => console.log(t)} remove={t => console.log(t)} />
</td>
</tr>
{/each}
{#each $aaaaRecords as record}
<tr>
<td>{record.Hdr.Name}</td>
<td>{record.AAAA}</td>
<td>{record.Hdr.Ttl}</td>
<td>
<ActionMenu data={record} edit={t => console.log(t)} remove={t => console.log(t)} />
</td>
</tr>
{/each}
</tbody>
</table>
{/each}
</tbody>
</table>
<h2>CNAME Record</h2>
<table class="action-table" aria-label="List of Domains CNAME Record">
<thead>
<h2>MX Record</h2>
<table class="action-table" aria-label="List of Domains MX Record">
<thead>
<tr>
<th>Mail Server</th>
<th>Preference</th>
<th>Subdomain</th>
<th>TTL</th>
<th></th>
</tr>
</thead>
<tbody>
{#if $mxRecords.length === 0}
<tr class="empty-row"><td colspan="7">No items to display</td></tr>
{/if}
{#each $mxRecords as record}
<tr>
<th>Hostname</th>
<th>Aliases to</th>
<th>TTL</th>
<th></th>
<td>{record.Mx}</td>
<td>{record.Preference}</td>
<td>{record.Hdr.Name}</td>
<td>{record.Hdr.Ttl}</td>
<td>
<ActionMenu data={record} edit={t => console.log(t)} remove={t => console.log(t)} />
</td>
</tr>
</thead>
<tbody>
{#if $cnameRecords.length === 0}
<tr class="empty-row"><td colspan="7">No items to display</td></tr>
{/if}
{#each $cnameRecords as record}
<tr>
<td>{record.Hdr.Name}</td>
<td>{record.Target}</td>
<td>{record.Hdr.Ttl}</td>
<td>
<ActionMenu data={record} edit={t => console.log(t)} remove={t => console.log(t)} />
</td>
</tr>
{/each}
</tbody>
</table>
{/each}
</tbody>
</table>
<h2>TXT Record</h2>
<table class="action-table" aria-label="List of Domains TXT Record">
<thead>
<h2>A/AAAA Record</h2>
<table class="action-table" aria-label="List of Domains A/AAAA Record">
<thead>
<tr>
<th>Hostname</th>
<th>IP Address</th>
<th>TTL</th>
<th></th>
</tr>
</thead>
<tbody>
{#if $aRecords.length === 0 && $aaaaRecords.length === 0}
<tr class="empty-row"><td colspan="7">No items to display</td></tr>
{/if}
{#each $aRecords as record}
<tr>
<th>Hostname</th>
<th>Value</th>
<th>TTL</th>
<th></th>
<td>{record.Hdr.Name}</td>
<td>{record.A}</td>
<td>{record.Hdr.Ttl}</td>
<td>
<ActionMenu data={record} edit={t => console.log(t)} remove={t => console.log(t)} />
</td>
</tr>
</thead>
<tbody>
{#if $txtRecords.length === 0}
<tr class="empty-row"><td colspan="7">No items to display</td></tr>
{/if}
{#each $txtRecords as record}
<tr>
<td>{record.Hdr.Name}</td>
<td>{record.Txt.join("\n")}</td>
<td>{record.Hdr.Ttl}</td>
<td>
<ActionMenu data={record} edit={t => console.log(t)} remove={t => console.log(t)} />
</td>
</tr>
{/each}
</tbody>
</table>
{/each}
{#each $aaaaRecords as record}
<tr>
<td>{record.Hdr.Name}</td>
<td>{record.AAAA}</td>
<td>{record.Hdr.Ttl}</td>
<td>
<ActionMenu data={record} edit={t => console.log(t)} remove={t => console.log(t)} />
</td>
</tr>
{/each}
</tbody>
</table>
<h2>SRV Record</h2>
<table class="action-table" aria-label="List of Domains SRV Record">
<thead>
<h2>CNAME Record</h2>
<table class="action-table" aria-label="List of Domains CNAME Record">
<thead>
<tr>
<th>Hostname</th>
<th>Aliases to</th>
<th>TTL</th>
<th></th>
</tr>
</thead>
<tbody>
{#if $cnameRecords.length === 0}
<tr class="empty-row"><td colspan="7">No items to display</td></tr>
{/if}
{#each $cnameRecords as record}
<tr>
<th>Name</th>
<th>Priority</th>
<th>Weight</th>
<th>Port</th>
<th>Target</th>
<th>TTL</th>
<th></th>
<td>{record.Hdr.Name}</td>
<td>{record.Target}</td>
<td>{record.Hdr.Ttl}</td>
<td>
<ActionMenu data={record} edit={t => console.log(t)} remove={t => console.log(t)} />
</td>
</tr>
</thead>
<tbody>
{#if $srvRecords.length === 0}
<tr class="empty-row"><td colspan="7">No items to display</td></tr>
{/if}
{#each $srvRecords as record}
<tr>
<td>{record.Hdr.Name}</td>
<td>{record.Priority}</td>
<td>{record.Weight}</td>
<td>{record.Port}</td>
<td>{record.Target}</td>
<td>{record.Hdr.Ttl}</td>
<td>
<ActionMenu data={record} edit={t => console.log(t)} remove={t => console.log(t)} />
</td>
</tr>
{/each}
</tbody>
</table>
{/each}
</tbody>
</table>
<h2>CAA Record</h2>
<table class="action-table" aria-label="List of Domains CAA Record">
<thead>
<h2>TXT Record</h2>
<table class="action-table" aria-label="List of Domains TXT Record">
<thead>
<tr>
<th>Hostname</th>
<th>Value</th>
<th>TTL</th>
<th></th>
</tr>
</thead>
<tbody>
{#if $txtRecords.length === 0}
<tr class="empty-row"><td colspan="7">No items to display</td></tr>
{/if}
{#each $txtRecords as record}
<tr>
<th>Name</th>
<th>Tag</th>
<th>Value</th>
<th>TTL</th>
<th></th>
<td>{record.Hdr.Name}</td>
<td>
<span class="cutoff">{record.Txt.join("\n")}</span>
</td>
<td>{record.Hdr.Ttl}</td>
<td>
<ActionMenu data={record} edit={t => console.log(t)} remove={t => console.log(t)} />
</td>
</tr>
</thead>
<tbody>
{#if $caaRecords.length === 0}
<tr class="empty-row"><td colspan="7">No items to display</td></tr>
{/if}
{#each $caaRecords as record}
<tr>
<td>{record.Hdr.Name}</td>
<td>{record.Tag}</td>
<td>{record.Value}</td>
<td>{record.Hdr.Ttl}</td>
<td>
<ActionMenu data={record} edit={t => console.log(t)} remove={t => console.log(t)} />
</td>
</tr>
{/each}
</tbody>
</table>
{/await}
{/each}
</tbody>
</table>
<h2>SRV Record</h2>
<table class="action-table" aria-label="List of Domains SRV Record">
<thead>
<tr>
<th>Name</th>
<th>Priority</th>
<th>Weight</th>
<th>Port</th>
<th>Target</th>
<th>TTL</th>
<th></th>
</tr>
</thead>
<tbody>
{#if $srvRecords.length === 0}
<tr class="empty-row"><td colspan="7">No items to display</td></tr>
{/if}
{#each $srvRecords as record}
<tr>
<td>{record.Hdr.Name}</td>
<td>{record.Priority}</td>
<td>{record.Weight}</td>
<td>{record.Port}</td>
<td>{record.Target}</td>
<td>{record.Hdr.Ttl}</td>
<td>
<ActionMenu data={record} edit={t => console.log(t)} remove={t => console.log(t)} />
</td>
</tr>
{/each}
</tbody>
</table>
<h2>CAA Record</h2>
<table class="action-table" aria-label="List of Domains CAA Record">
<thead>
<tr>
<th>Name</th>
<th>Tag</th>
<th>Value</th>
<th>TTL</th>
<th></th>
</tr>
</thead>
<tbody>
{#if $caaRecords.length === 0}
<tr class="empty-row"><td colspan="7">No items to display</td></tr>
{/if}
{#each $caaRecords as record}
<tr>
<td>{record.Hdr.Name}</td>
<td>{record.Tag}</td>
<td>{record.Value}</td>
<td>{record.Hdr.Ttl}</td>
<td>
<ActionMenu data={record} edit={t => console.log(t)} remove={t => console.log(t)} />
</td>
</tr>
{/each}
</tbody>
</table>
<style lang="scss">
@import "../values.scss";
@ -322,8 +374,29 @@
@include button-green-highlight;
}
table tbody tr.empty-row td {
text-align: center;
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 {

View File

@ -52,6 +52,7 @@ func ssoServer(signer mjwt.Signer) {
ps := claims.NewPermStorage()
ps.Set("violet:route")
ps.Set("violet:redirect")
ps.Set("azalea:domains")
ps.Set("domain:owns=example.com")
ps.Set("domain:owns=example.org")
accessToken, err := signer.GenerateJwt("81b99bd7-bf74-4cc2-9133-80ed2393dfe6", uuid.NewString(), jwt.ClaimStrings{"b5a9a8df-827c-4925-b1c1-1940abcf356b"}, 15*time.Minute, auth.AccessTokenClaims{