156 lines
12 KiB
TypeScript
156 lines
12 KiB
TypeScript
import * as plugins from '../../../plugins.js';
|
|
import * as shared from '../../shared/index.js';
|
|
|
|
import {
|
|
DeesElement,
|
|
customElement,
|
|
html,
|
|
state,
|
|
css,
|
|
cssManager,
|
|
} from '@design.estate/dees-element';
|
|
|
|
import * as appstate from '../../../appstate.js';
|
|
|
|
@customElement('cloudly-view-dns')
|
|
export class CloudlyViewDns extends DeesElement {
|
|
@state()
|
|
private data: appstate.IDataState = { secretGroups: [], secretBundles: [], dnsEntries: [], domains: [] } as any;
|
|
|
|
constructor() {
|
|
super();
|
|
const subscription = appstate.dataState.select((stateArg) => stateArg).subscribe((dataArg) => { this.data = dataArg; });
|
|
this.rxSubscriptions.push(subscription);
|
|
}
|
|
|
|
async connectedCallback() {
|
|
super.connectedCallback();
|
|
await appstate.dataState.dispatchAction(appstate.getAllDataAction, {});
|
|
}
|
|
|
|
public static styles = [
|
|
cssManager.defaultStyles,
|
|
shared.viewHostCss,
|
|
css`
|
|
.dns-type-badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 0.85em; font-weight: 500; color: white; }
|
|
.type-A { background: #4CAF50; }
|
|
.type-AAAA { background: #45a049; }
|
|
.type-CNAME { background: #2196F3; }
|
|
.type-MX { background: #FF9800; }
|
|
.type-TXT { background: #9C27B0; }
|
|
.type-NS { background: #795548; }
|
|
.type-SOA { background: #607D8B; }
|
|
.type-SRV { background: #E91E63; }
|
|
.type-CAA { background: #00BCD4; }
|
|
.type-PTR { background: #673AB7; }
|
|
.status-active { color: #4CAF50; }
|
|
.status-inactive { color: #f44336; }
|
|
`,
|
|
];
|
|
|
|
private getRecordTypeBadge(type: string) { return html`<span class="dns-type-badge type-${type}">${type}</span>`; }
|
|
private getStatusBadge(active: boolean) { return html`<span class="${active ? 'status-active' : 'status-inactive'}">${active ? '✓ Active' : '✗ Inactive'}</span>`; }
|
|
|
|
public render() {
|
|
return html`
|
|
<cloudly-sectionheading>DNS Management</cloudly-sectionheading>
|
|
<dees-table
|
|
.heading1=${'DNS Entries'}
|
|
.heading2=${'Manage DNS records for your domains'}
|
|
.data=${this.data.dnsEntries || []}
|
|
.displayFunction=${(itemArg: plugins.interfaces.data.IDnsEntry) => {
|
|
return {
|
|
Type: this.getRecordTypeBadge(itemArg.data.type),
|
|
Name: itemArg.data.name === '@' ? '<root>' : itemArg.data.name,
|
|
Value: itemArg.data.value,
|
|
TTL: `${itemArg.data.ttl}s`,
|
|
Priority: itemArg.data.priority || '-',
|
|
Zone: itemArg.data.zone,
|
|
Status: this.getStatusBadge(itemArg.data.active),
|
|
Description: itemArg.data.description || '-',
|
|
};
|
|
}}
|
|
.dataActions=${[
|
|
{ name: 'Add DNS Entry', iconName: 'plus', type: ['header', 'footer'], actionFunc: async () => {
|
|
const modal = await plugins.deesCatalog.DeesModal.createAndShow({
|
|
heading: 'Add DNS Entry',
|
|
content: html`
|
|
<dees-form>
|
|
<dees-input-dropdown .key=${'type'} .label=${'Record Type'} .options=${[
|
|
{key: 'A', option: 'A - IPv4 Address'}, {key: 'AAAA', option: 'AAAA - IPv6 Address'}, {key: 'CNAME', option: 'CNAME - Canonical Name'}, {key: 'MX', option: 'MX - Mail Exchange'}, {key: 'TXT', option: 'TXT - Text Record'}, {key: 'NS', option: 'NS - Name Server'}, {key: 'SOA', option: 'SOA - Start of Authority'}, {key: 'SRV', option: 'SRV - Service'}, {key: 'CAA', option: 'CAA - Certification Authority'}, {key: 'PTR', option: 'PTR - Pointer'}, ]} .value=${'A'} .required=${true}></dees-input-dropdown>
|
|
<dees-input-dropdown .key=${'domainId'} .label=${'Domain'} .options=${this.data.domains?.map(domain => ({ key: domain.id, option: domain.data.name })) || []} .required=${true}></dees-input-dropdown>
|
|
<dees-input-text .key=${'name'} .label=${'Name'} .placeholder=${'@ for root, www, mail, etc.'} .value=${'@'} .required=${true}></dees-input-text>
|
|
<dees-input-text .key=${'value'} .label=${'Value'} .placeholder=${'IP address, domain, or text value'} .required=${true}></dees-input-text>
|
|
<dees-input-text .key=${'ttl'} .label=${'TTL (seconds)'} .value=${'3600'} .type=${'number'} .required=${true}></dees-input-text>
|
|
<dees-input-text .key=${'priority'} .label=${'Priority (MX/SRV only)'} .type=${'number'} .placeholder=${'10'}></dees-input-text>
|
|
<dees-input-text .key=${'weight'} .label=${'Weight (SRV only)'} .type=${'number'} .placeholder=${'0'}></dees-input-text>
|
|
<dees-input-text .key=${'port'} .label=${'Port (SRV only)'} .type=${'number'} .placeholder=${'443'}></dees-input-text>
|
|
<dees-input-checkbox .key=${'active'} .label=${'Active'} .value=${true}></dees-input-checkbox>
|
|
<dees-input-text .key=${'description'} .label=${'Description (optional)'} .placeholder=${'What is this record for?'}></dees-input-text>
|
|
</dees-form>
|
|
`,
|
|
menuOptions: [
|
|
{ name: 'Create DNS Entry', action: async (modalArg: any) => {
|
|
const form = modalArg.shadowRoot.querySelector('dees-form') as any;
|
|
const formData = await form.gatherData();
|
|
// Guard: only allow on activated domains
|
|
const domain = (this.data.domains || []).find((d: any) => d.id === formData.domainId);
|
|
if (!domain || (domain.data as any).activationState !== 'activated') {
|
|
plugins.deesCatalog.DeesToast.createAndShow({ message: 'Selected domain is not activated. Activate it first.', type: 'error' });
|
|
return;
|
|
}
|
|
await appstate.dataState.dispatchAction(appstate.createDnsEntryAction, { dnsEntryData: { type: formData.type, domainId: formData.domainId, zone: '', name: formData.name || '@', value: formData.value, ttl: parseInt(formData.ttl) || 3600, priority: formData.priority ? parseInt(formData.priority) : undefined, weight: formData.weight ? parseInt(formData.weight) : undefined, port: formData.port ? parseInt(formData.port) : undefined, active: formData.active, description: formData.description || undefined, }, });
|
|
await modalArg.destroy();
|
|
} },
|
|
{ name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
|
|
],
|
|
});
|
|
} },
|
|
{ name: 'Edit', iconName: 'edit', type: ['contextmenu', 'inRow'], actionFunc: async (actionDataArg: any) => {
|
|
const dnsEntry = actionDataArg.item as plugins.interfaces.data.IDnsEntry;
|
|
const modal = await plugins.deesCatalog.DeesModal.createAndShow({
|
|
heading: `Edit DNS Entry`,
|
|
content: html`
|
|
<dees-form>
|
|
<dees-input-dropdown .key=${'type'} .label=${'Record Type'} .options=${[
|
|
{key: 'A', option: 'A - IPv4 Address'}, {key: 'AAAA', option: 'AAAA - IPv6 Address'}, {key: 'CNAME', option: 'CNAME - Canonical Name'}, {key: 'MX', option: 'MX - Mail Exchange'}, {key: 'TXT', option: 'TXT - Text Record'}, {key: 'NS', option: 'NS - Name Server'}, {key: 'SOA', option: 'SOA - Start of Authority'}, {key: 'SRV', option: 'SRV - Service'}, {key: 'CAA', option: 'CAA - Certification Authority'}, {key: 'PTR', option: 'PTR - Pointer'}, ]} .value=${dnsEntry.data.type} .required=${true}></dees-input-dropdown>
|
|
<dees-input-dropdown .key=${'domainId'} .label=${'Domain'} .options=${this.data.domains?.map(domain => ({ key: domain.id, option: domain.data.name })) || []} .value=${dnsEntry.data.domainId || ''} .required=${true}></dees-input-dropdown>
|
|
<dees-input-text .key=${'name'} .label=${'Name'} .value=${dnsEntry.data.name} .required=${true}></dees-input-text>
|
|
<dees-input-text .key=${'value'} .label=${'Value'} .value=${dnsEntry.data.value} .required=${true}></dees-input-text>
|
|
<dees-input-text .key=${'ttl'} .label=${'TTL (seconds)'} .value=${dnsEntry.data.ttl} .type=${'number'} .required=${true}></dees-input-text>
|
|
<dees-input-text .key=${'priority'} .label=${'Priority (MX/SRV only)'} .value=${dnsEntry.data.priority || ''} .type=${'number'}></dees-input-text>
|
|
<dees-input-text .key=${'weight'} .label=${'Weight (SRV only)'} .value=${dnsEntry.data.weight || ''} .type=${'number'}></dees-input-text>
|
|
<dees-input-text .key=${'port'} .label=${'Port (SRV only)'} .value=${dnsEntry.data.port || ''} .type=${'number'}></dees-input-text>
|
|
<dees-input-checkbox .key=${'active'} .label=${'Active'} .value=${dnsEntry.data.active}></dees-input-checkbox>
|
|
<dees-input-text .key=${'description'} .label=${'Description (optional)'} .value=${dnsEntry.data.description || ''}></dees-input-text>
|
|
</dees-form>
|
|
`,
|
|
menuOptions: [
|
|
{ name: 'Update DNS Entry', action: async (modalArg: any) => {
|
|
const form = modalArg.shadowRoot.querySelector('dees-form') as any;
|
|
const formData = await form.gatherData();
|
|
if (formData.domainId) {
|
|
const domain = (this.data.domains || []).find((d: any) => d.id === formData.domainId);
|
|
if (!domain || (domain.data as any).activationState !== 'activated') {
|
|
plugins.deesCatalog.DeesToast.createAndShow({ message: 'Selected domain is not activated. Activate it first.', type: 'error' });
|
|
return;
|
|
}
|
|
}
|
|
await appstate.dataState.dispatchAction(appstate.updateDnsEntryAction, { dnsEntryId: dnsEntry.id, dnsEntryData: { ...dnsEntry.data, type: formData.type, domainId: formData.domainId, zone: '', name: formData.name || '@', value: formData.value, ttl: parseInt(formData.ttl) || 3600, priority: formData.priority ? parseInt(formData.priority) : undefined, weight: formData.weight ? parseInt(formData.weight) : undefined, port: formData.port ? parseInt(formData.port) : undefined, active: formData.active, description: formData.description || undefined, }, });
|
|
await modalArg.destroy();
|
|
} },
|
|
{ name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
|
|
],
|
|
});
|
|
} },
|
|
{ name: 'Duplicate', iconName: 'copy', type: ['contextmenu', 'inRow'], actionFunc: async (actionDataArg: any) => { const dnsEntry = actionDataArg.item as plugins.interfaces.data.IDnsEntry; await appstate.dataState.dispatchAction(appstate.createDnsEntryAction, { dnsEntryData: { ...dnsEntry.data, description: `Copy of ${dnsEntry.data.description || dnsEntry.data.name}`, }, }); } },
|
|
{ name: 'Toggle Active', iconName: 'power', type: ['contextmenu', 'inRow'], actionFunc: async (actionDataArg: any) => { const dnsEntry = actionDataArg.item as plugins.interfaces.data.IDnsEntry; await appstate.dataState.dispatchAction(appstate.updateDnsEntryAction, { dnsEntryId: dnsEntry.id, dnsEntryData: { ...dnsEntry.data, active: !dnsEntry.data.active, }, }); } },
|
|
{ name: 'Delete', iconName: 'trash', type: ['contextmenu', 'inRow'], actionFunc: async (actionDataArg: any) => { const dnsEntry = actionDataArg.item as plugins.interfaces.data.IDnsEntry; plugins.deesCatalog.DeesModal.createAndShow({ heading: `Delete DNS Entry`, content: html`<div style="text-align:center">Are you sure you want to delete this DNS entry?</div><div style="margin-top: 16px; padding: 16px; background: #333; border-radius: 8px;"><div style="color: #fff; font-weight: bold;">${dnsEntry.data.type} - ${dnsEntry.data.name}.${dnsEntry.data.zone}</div><div style="color: #aaa; font-size: 0.9em; margin-top: 4px;">${dnsEntry.data.value}</div>${dnsEntry.data.description ? html`<div style=\"color: #888; font-size: 0.85em; margin-top: 8px;\">${dnsEntry.data.description}</div>` : ''}</div>`, menuOptions: [ { name: 'Cancel', action: async (modalArg: any) => { await modalArg.destroy(); } }, { name: 'Delete', action: async (modalArg: any) => { await appstate.dataState.dispatchAction(appstate.deleteDnsEntryAction, { dnsEntryId: dnsEntry.id, }); await modalArg.destroy(); } }, ], }); } },
|
|
] as plugins.deesCatalog.ITableAction[]}
|
|
></dees-table>
|
|
`;
|
|
}
|
|
}
|
|
|
|
declare global { interface HTMLElementTagNameMap { 'cloudly-view-dns': CloudlyViewDns; } }
|