import { DeesElement, html, customElement, type TemplateResult, css, state, cssManager, } from '@design.estate/dees-element'; import * as appstate from '../../appstate.js'; import * as interfaces from '../../../dist_ts_interfaces/index.js'; import { viewHostCss } from '../shared/css.js'; import { type IStatsTile } from '@design.estate/dees-catalog'; declare global { interface HTMLElementTagNameMap { 'ops-view-email-domains': OpsViewEmailDomains; } } @customElement('ops-view-email-domains') export class OpsViewEmailDomains extends DeesElement { @state() accessor emailDomainsState: appstate.IEmailDomainsState = appstate.emailDomainsStatePart.getState()!; @state() accessor domainsState: appstate.IDomainsState = appstate.domainsStatePart.getState()!; constructor() { super(); const sub = appstate.emailDomainsStatePart.select().subscribe((s) => { this.emailDomainsState = s; }); this.rxSubscriptions.push(sub); const domSub = appstate.domainsStatePart.select().subscribe((s) => { this.domainsState = s; }); this.rxSubscriptions.push(domSub); } async connectedCallback() { await super.connectedCallback(); await appstate.emailDomainsStatePart.dispatchAction(appstate.fetchEmailDomainsAction, null); await appstate.domainsStatePart.dispatchAction(appstate.fetchDomainsAndProvidersAction, null); } public static styles = [ cssManager.defaultStyles, viewHostCss, css` .emailDomainsContainer { display: flex; flex-direction: column; gap: 24px; } .statusBadge { display: inline-flex; align-items: center; padding: 3px 10px; border-radius: 12px; font-size: 12px; font-weight: 600; letter-spacing: 0.02em; text-transform: uppercase; } .statusBadge.valid { background: ${cssManager.bdTheme('#dcfce7', '#14532d')}; color: ${cssManager.bdTheme('#166534', '#4ade80')}; } .statusBadge.missing { background: ${cssManager.bdTheme('#fef2f2', '#450a0a')}; color: ${cssManager.bdTheme('#991b1b', '#f87171')}; } .statusBadge.invalid { background: ${cssManager.bdTheme('#fff7ed', '#431407')}; color: ${cssManager.bdTheme('#9a3412', '#fb923c')}; } .statusBadge.unchecked { background: ${cssManager.bdTheme('#f3f4f6', '#1f2937')}; color: ${cssManager.bdTheme('#4b5563', '#9ca3af')}; } .sourceBadge { display: inline-flex; align-items: center; padding: 3px 8px; border-radius: 4px; font-size: 11px; font-weight: 500; background: ${cssManager.bdTheme('#f3f4f6', '#1f2937')}; color: ${cssManager.bdTheme('#374151', '#d1d5db')}; } `, ]; public render(): TemplateResult { const domains = this.emailDomainsState.domains; const validCount = domains.filter( (d) => d.dnsStatus.mx === 'valid' && d.dnsStatus.spf === 'valid' && d.dnsStatus.dkim === 'valid' && d.dnsStatus.dmarc === 'valid', ).length; const issueCount = domains.length - validCount; const tiles: IStatsTile[] = [ { id: 'total', title: 'Total Domains', value: domains.length, type: 'number', icon: 'lucide:globe', color: '#3b82f6', }, { id: 'valid', title: 'Valid DNS', value: validCount, type: 'number', icon: 'lucide:Check', color: '#22c55e', }, { id: 'issues', title: 'Issues', value: issueCount, type: 'number', icon: 'lucide:TriangleAlert', color: issueCount > 0 ? '#ef4444' : '#22c55e', }, { id: 'dkim', title: 'DKIM Active', value: domains.filter((d) => d.dkim.publicKey).length, type: 'number', icon: 'lucide:KeyRound', color: '#8b5cf6', }, ]; return html` Email Domains
{ await appstate.emailDomainsStatePart.dispatchAction( appstate.fetchEmailDomainsAction, null, ); }, }, ]} > ({ Domain: d.domain, Source: this.renderSourceBadge(d.linkedDomainId), MX: this.renderDnsStatus(d.dnsStatus.mx), SPF: this.renderDnsStatus(d.dnsStatus.spf), DKIM: this.renderDnsStatus(d.dnsStatus.dkim), DMARC: this.renderDnsStatus(d.dnsStatus.dmarc), })} .dataActions=${[ { name: 'Add Email Domain', iconName: 'lucide:plus', type: ['header'] as any, actionFunc: async () => { await this.showCreateDialog(); }, }, { name: 'Validate DNS', iconName: 'lucide:search-check', type: ['inRow', 'contextmenu'] as any, actionFunc: async (actionData: any) => { const d = actionData.item as interfaces.data.IEmailDomain; await appstate.emailDomainsStatePart.dispatchAction( appstate.validateEmailDomainAction, d.id, ); const { DeesToast } = await import('@design.estate/dees-catalog'); DeesToast.show({ message: `DNS validated for ${d.domain}`, type: 'success', duration: 2500 }); }, }, { name: 'Provision DNS', iconName: 'lucide:wand-sparkles', type: ['inRow', 'contextmenu'] as any, actionFunc: async (actionData: any) => { const d = actionData.item as interfaces.data.IEmailDomain; await appstate.emailDomainsStatePart.dispatchAction( appstate.provisionEmailDomainDnsAction, d.id, ); const { DeesToast } = await import('@design.estate/dees-catalog'); DeesToast.show({ message: `DNS records provisioned for ${d.domain}`, type: 'success', duration: 2500 }); }, }, { name: 'View DNS Records', iconName: 'lucide:list', type: ['inRow', 'contextmenu'] as any, actionFunc: async (actionData: any) => { const d = actionData.item as interfaces.data.IEmailDomain; await this.showDnsRecordsDialog(d); }, }, { name: 'Delete', iconName: 'lucide:trash2', type: ['inRow', 'contextmenu'] as any, actionFunc: async (actionData: any) => { const d = actionData.item as interfaces.data.IEmailDomain; await appstate.emailDomainsStatePart.dispatchAction( appstate.deleteEmailDomainAction, d.id, ); }, }, ]} dataName="email domain" >
`; } private renderDnsStatus(status: interfaces.data.TDnsRecordStatus): TemplateResult { return html`${status}`; } private renderSourceBadge(linkedDomainId: string): TemplateResult { const domain = this.domainsState.domains.find((d) => d.id === linkedDomainId); if (!domain) return html`unknown`; const label = domain.source === 'dcrouter' ? 'dcrouter' : this.domainsState.providers.find((p) => p.id === domain.providerId)?.name || 'provider'; return html`${label}`; } private async showCreateDialog() { const { DeesModal } = await import('@design.estate/dees-catalog'); const domainOptions = this.domainsState.domains.map((d) => ({ option: `${d.name} (${d.source})`, key: d.id, })); DeesModal.createAndShow({ heading: 'Add Email Domain', content: html` `, menuOptions: [ { name: 'Cancel', action: async (m: any) => m.destroy() }, { name: 'Create', action: async (m: any) => { const form = m.shadowRoot?.querySelector('.content')?.querySelector('dees-form'); if (!form) return; const data = await form.collectFormData(); const linkedDomainId = typeof data.linkedDomainId === 'object' ? data.linkedDomainId.key : data.linkedDomainId; const keySize = typeof data.dkimKeySize === 'object' ? parseInt(data.dkimKeySize.key, 10) : parseInt(data.dkimKeySize || '2048', 10); const subdomain = data.subdomain?.trim() || undefined; await appstate.emailDomainsStatePart.dispatchAction( appstate.createEmailDomainAction, { linkedDomainId, subdomain, dkimSelector: data.dkimSelector || 'default', dkimKeySize: keySize, rotateKeys: Boolean(data.rotateKeys), }, ); m.destroy(); }, }, ], }); } private async showDnsRecordsDialog(emailDomain: interfaces.data.IEmailDomain) { const { DeesModal, DeesToast } = await import('@design.estate/dees-catalog'); // Fetch required DNS records let records: interfaces.data.IEmailDnsRecord[] = []; try { const response = await appstate.fetchEmailDomainDnsRecords(emailDomain.id); records = response.records; } catch { records = []; } DeesModal.createAndShow({ heading: `DNS Records: ${emailDomain.domain}`, content: html` ({ Type: r.type, Name: r.name, Value: r.value, Status: html`${r.status}`, })} .dataActions=${[ { name: 'Copy Value', iconName: 'lucide:copy', type: ['inRow'] as any, actionFunc: async (actionData: any) => { const rec = actionData.item as interfaces.data.IEmailDnsRecord; await navigator.clipboard.writeText(rec.value); DeesToast.show({ message: 'Copied to clipboard', type: 'success', duration: 1500 }); }, }, ]} dataName="DNS record" > `, menuOptions: [ { name: 'Auto-Provision All', action: async (m: any) => { await appstate.emailDomainsStatePart.dispatchAction( appstate.provisionEmailDomainDnsAction, emailDomain.id, ); DeesToast.show({ message: 'DNS records provisioned', type: 'success', duration: 2500 }); m.destroy(); }, }, { name: 'Close', action: async (m: any) => m.destroy() }, ], }); } }