import * as plugins from '../plugins.js'; import * as shared from '../elements/shared/index.js'; import { DeesElement, customElement, html, state, css, cssManager, } from '@design.estate/dees-element'; import * as appstate from '../appstate.js'; @customElement('cloudly-view-domains') export class CloudlyViewDomains extends DeesElement { @state() private data: appstate.IDataState = { secretGroups: [], secretBundles: [], domains: [], dnsEntries: [], }; constructor() { super(); const subscription = appstate.dataState .select((stateArg) => stateArg) .subscribe((dataArg) => { this.data = dataArg; }); this.rxSubscriptions.push(subscription); } public static styles = [ cssManager.defaultStyles, shared.viewHostCss, css` .status-badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 0.85em; font-weight: 500; color: white; } .status-active { background: #4CAF50; } .status-pending { background: #FF9800; } .status-expired { background: #f44336; } .status-suspended { background: #9E9E9E; } .status-transferred { background: #607D8B; } .verification-badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 0.85em; font-weight: 500; } .verification-verified { background: #4CAF50; color: white; } .verification-pending { background: #FF9800; color: white; } .verification-failed { background: #f44336; color: white; } .verification-not_required { background: #E0E0E0; color: #333; } .ssl-badge { display: inline-block; padding: 2px 6px; border-radius: 3px; font-size: 0.8em; } .ssl-active { color: #4CAF50; } .ssl-pending { color: #FF9800; } .ssl-expired { color: #f44336; } .ssl-none { color: #9E9E9E; } .nameserver-list { font-size: 0.85em; color: #666; } .expiry-warning { color: #FF9800; font-weight: 500; } .expiry-critical { color: #f44336; font-weight: bold; } `, ]; private getStatusBadge(status: string) { return html`${status.toUpperCase()}`; } private getVerificationBadge(status: string) { const displayText = status === 'not_required' ? 'Not Required' : status.replace('_', ' ').toUpperCase(); return html`${displayText}`; } private getSslBadge(sslStatus?: string) { if (!sslStatus) return html``; const icon = sslStatus === 'active' ? '🔒' : sslStatus === 'expired' ? '⚠️' : '🔓'; return html`${icon} ${sslStatus.toUpperCase()}`; } private formatDate(timestamp?: number) { if (!timestamp) return '—'; const date = new Date(timestamp); return date.toLocaleDateString(); } private getDaysUntilExpiry(expiresAt?: number) { if (!expiresAt) return null; const days = Math.floor((expiresAt - Date.now()) / (1000 * 60 * 60 * 24)); return days; } private getExpiryDisplay(expiresAt?: number) { const days = this.getDaysUntilExpiry(expiresAt); if (days === null) return '—'; if (days < 0) { return html`Expired ${Math.abs(days)} days ago`; } else if (days <= 30) { return html`Expires in ${days} days`; } else { return `${days} days`; } } public render() { return html` Domain Management { const dnsCount = this.data.dnsEntries?.filter(dns => dns.data.zone === itemArg.data.name).length || 0; return { Domain: html`
${itemArg.data.name}
${itemArg.data.description ? html`
${itemArg.data.description}
` : ''}
`, Status: this.getStatusBadge(itemArg.data.status), Verification: this.getVerificationBadge(itemArg.data.verificationStatus), SSL: this.getSslBadge(itemArg.data.sslStatus), 'DNS Records': dnsCount, Registrar: itemArg.data.registrar?.name || '—', Expires: this.getExpiryDisplay(itemArg.data.expiresAt), 'Auto-Renew': itemArg.data.autoRenew ? '✓' : '✗', Nameservers: html`
${itemArg.data.nameservers?.join(', ') || '—'}
`, }; }} .dataActions=${[ { name: 'Add Domain', iconName: 'plus', type: ['header', 'footer'], actionFunc: async (dataActionArg) => { const modal = await plugins.deesCatalog.DeesModal.createAndShow({ heading: 'Add Domain', content: html` `, menuOptions: [ { name: 'Create Domain', action: async (modalArg) => { const form = modalArg.shadowRoot.querySelector('dees-form') as any; const formData = await form.gatherData(); const nameservers = formData.nameservers ? formData.nameservers.split(',').map((ns: string) => ns.trim()).filter((ns: string) => ns) : []; const tags = formData.tags ? formData.tags.split(',').map((tag: string) => tag.trim()).filter((tag: string) => tag) : []; await appstate.dataState.dispatchAction(appstate.createDomainAction, { domainData: { name: formData.name, description: formData.description || undefined, status: formData.status, verificationStatus: 'pending', nameservers, registrar: formData.registrarName ? { name: formData.registrarName, url: formData.registrarUrl || undefined, } : undefined, expiresAt: formData.expiresAt ? new Date(formData.expiresAt).getTime() : undefined, autoRenew: formData.autoRenew, dnssecEnabled: formData.dnssecEnabled, isPrimary: formData.isPrimary, tags: tags.length > 0 ? tags : undefined, }, }); await modalArg.destroy(); }, }, { name: 'Cancel', action: async (modalArg) => { modalArg.destroy(); }, }, ], }); }, }, { name: 'Edit', iconName: 'edit', type: ['contextmenu', 'inRow'], actionFunc: async (actionDataArg) => { const domain = actionDataArg.item as plugins.interfaces.data.IDomain; const modal = await plugins.deesCatalog.DeesModal.createAndShow({ heading: `Edit Domain: ${domain.data.name}`, content: html` `, menuOptions: [ { name: 'Update Domain', action: async (modalArg) => { const form = modalArg.shadowRoot.querySelector('dees-form') as any; const formData = await form.gatherData(); const nameservers = formData.nameservers ? formData.nameservers.split(',').map((ns: string) => ns.trim()).filter((ns: string) => ns) : []; const tags = formData.tags ? formData.tags.split(',').map((tag: string) => tag.trim()).filter((tag: string) => tag) : []; await appstate.dataState.dispatchAction(appstate.updateDomainAction, { domainId: domain.id, domainData: { ...domain.data, name: formData.name, description: formData.description || undefined, status: formData.status, nameservers, registrar: formData.registrarName ? { name: formData.registrarName, url: formData.registrarUrl || undefined, } : undefined, expiresAt: formData.expiresAt ? new Date(formData.expiresAt).getTime() : undefined, autoRenew: formData.autoRenew, dnssecEnabled: formData.dnssecEnabled, isPrimary: formData.isPrimary, tags: tags.length > 0 ? tags : undefined, }, }); await modalArg.destroy(); }, }, { name: 'Cancel', action: async (modalArg) => { modalArg.destroy(); }, }, ], }); }, }, { name: 'Verify', iconName: 'check-circle', type: ['contextmenu', 'inRow'], actionFunc: async (actionDataArg) => { const domain = actionDataArg.item as plugins.interfaces.data.IDomain; const modal = await plugins.deesCatalog.DeesModal.createAndShow({ heading: `Verify Domain: ${domain.data.name}`, content: html`

Choose a verification method for ${domain.data.name}

${domain.data.verificationToken ? html`
Verification Token:
${domain.data.verificationToken}
` : ''}
`, menuOptions: [ { name: 'Start Verification', action: async (modalArg) => { const form = modalArg.shadowRoot.querySelector('dees-form') as any; const formData = await form.gatherData(); await appstate.dataState.dispatchAction(appstate.verifyDomainAction, { domainId: domain.id, verificationMethod: formData.method, }); await modalArg.destroy(); }, }, { name: 'Cancel', action: async (modalArg) => { modalArg.destroy(); }, }, ], }); }, }, { name: 'View DNS Records', iconName: 'list', type: ['contextmenu', 'inRow'], actionFunc: async (actionDataArg) => { const domain = actionDataArg.item as plugins.interfaces.data.IDomain; // Navigate to DNS view with filter for this domain // TODO: Implement navigation with filter console.log('View DNS records for domain:', domain.data.name); }, }, { name: 'Delete', iconName: 'trash', type: ['contextmenu', 'inRow'], actionFunc: async (actionDataArg) => { const domain = actionDataArg.item as plugins.interfaces.data.IDomain; const dnsCount = this.data.dnsEntries?.filter(dns => dns.data.zone === domain.data.name).length || 0; plugins.deesCatalog.DeesModal.createAndShow({ heading: `Delete Domain`, content: html`
Are you sure you want to delete this domain?
${domain.data.name}
${domain.data.description ? html`
${domain.data.description}
` : ''} ${dnsCount > 0 ? html`
⚠️ This domain has ${dnsCount} DNS record${dnsCount > 1 ? 's' : ''} that will also be deleted
` : ''}
`, menuOptions: [ { name: 'Cancel', action: async (modalArg) => { await modalArg.destroy(); }, }, { name: 'Delete', action: async (modalArg) => { await appstate.dataState.dispatchAction(appstate.deleteDomainAction, { domainId: domain.id, }); await modalArg.destroy(); }, }, ], }); }, }, ] as plugins.deesCatalog.ITableAction[]} >
`; } }