2025-09-14 17:28:21 +00:00
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-domains' )
export class CloudlyViewDomains extends DeesElement {
@state ( )
private data : appstate.IDataState = { secretGroups : [ ] , secretBundles : [ ] , domains : [ ] , dnsEntries : [ ] } as any ;
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; }
2025-09-14 17:38:16 +00:00
.activation-badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 0.85em; font-weight: 500; }
.activation-available { background: #2b2b2b; color: #bbb; border: 1px solid #444; }
.activation-activated { background: #4CAF50; color: #fff; }
.activation-ignored { background: #9E9E9E; color: #fff; }
2025-09-14 17:28:21 +00:00
.expiry-warning { color: #FF9800; font-weight: 500; }
.expiry-critical { color: #f44336; font-weight: bold; }
` ,
] ;
private getStatusBadge ( status : string ) { return html ` <span class="status-badge status- ${ status } "> ${ status . toUpperCase ( ) } </span> ` ; }
private getVerificationBadge ( status : string ) { const displayText = status === 'not_required' ? 'Not Required' : status . replace ( '_' , ' ' ) . toUpperCase ( ) ; return html ` <span class="verification-badge verification- ${ status } "> ${ displayText } </span> ` ; }
private getSslBadge ( sslStatus? : string ) { if ( ! sslStatus ) return html ` <span class="ssl-badge ssl-none">—</span> ` ; const icon = sslStatus === 'active' ? '🔒' : sslStatus === 'expired' ? '⚠️' : '🔓' ; return html ` <span class="ssl-badge ssl- ${ sslStatus } "> ${ icon } ${ sslStatus . toUpperCase ( ) } </span> ` ; }
2025-09-14 17:38:16 +00:00
private getActivationBadge ( state ? : 'available' | 'activated' | 'ignored' ) { const s = state || 'available' ; return html ` <span class="activation-badge activation- ${ s } "> ${ s . toUpperCase ( ) } </span> ` ; }
2025-09-14 17:28:21 +00:00
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 ` <span class="expiry-critical">Expired ${ Math . abs ( days ) } days ago</span> ` ; } else if ( days <= 30 ) { return html ` <span class="expiry-warning">Expires in ${ days } days</span> ` ; } else { return ` ${ days } days ` ; } }
public render() {
return html `
<cloudly-sectionheading>Domain Management</cloudly-sectionheading>
<dees-table
.heading1= ${ 'Domains' }
.heading2= ${ 'Manage your domains and DNS zones' }
.data= ${ this . data . domains || [ ] }
.displayFunction= ${ ( itemArg : plugins.interfaces.data.IDomain ) = > {
const dnsCount = this . data . dnsEntries ? . filter ( dns = > dns . data . zone === itemArg . data . name ) . length || 0 ;
return {
Domain : html ` <div><div style="font-weight: 500;"> ${ itemArg . data . name } </div> ${ itemArg . data . description ? html ` <div style="font-size: 0.85em; color: #666; margin-top: 2px;"> ${ itemArg . data . description } </div> ` : '' } </div> ` ,
Status : this.getStatusBadge ( itemArg . data . status ) ,
Verification : this.getVerificationBadge ( itemArg . data . verificationStatus ) ,
SSL : this.getSslBadge ( itemArg . data . sslStatus ) ,
2025-09-14 17:38:16 +00:00
Activation : this.getActivationBadge ( ( itemArg . data as any ) . activationState ) ,
2025-09-14 17:28:21 +00:00
'DNS Records' : dnsCount ,
Registrar : itemArg.data.registrar?.name || '—' ,
Expires : this.getExpiryDisplay ( itemArg . data . expiresAt ) ,
'Auto-Renew' : itemArg . data . autoRenew ? '✓' : '✗' ,
Nameservers : html ` <div class="nameserver-list"> ${ itemArg . data . nameservers ? . join ( ', ' ) || '—' } </div> ` ,
} ;
}}
.dataActions= ${ [
2025-09-14 17:38:16 +00:00
{ name : 'Sync from Cloudflare' , iconName : 'cloud' , type : [ 'header' ] , actionFunc : async ( ) = > { await appstate . dataState . dispatchAction ( appstate . taskActions . triggerTask , { taskName : 'cloudflare-domain-sync' } as any); await appstate.dataState.dispatchAction(appstate.getAllDataAction, null); plugins.deesCatalog.DeesToast.createAndShow({ message: 'Triggered Cloudflare sync', type: 'success' }); } },
2025-09-14 17:28:21 +00:00
{ name: 'Add Domain', iconName: 'plus', type: ['header', 'footer'], actionFunc: async () => {
const modal = await plugins.deesCatalog.DeesModal.createAndShow({
heading: 'Add Domain',
content: html `
< dees - form >
< dees - input - text .key = $ { 'name' } .label = $ { 'Domain Name' } .placeholder = $ { 'example.com' } .required = $ { true } > < / d e e s - i n p u t - t e x t >
< dees - input - text .key = $ { 'description' } .label = $ { 'Description' } .placeholder = $ { 'Main company domain' } > < / d e e s - i n p u t - t e x t >
< dees - input - dropdown .key = $ { 'status' } .label = $ { 'Status' } .options = $ { [ { key : 'active' , option : 'Active' } , { key : 'pending' , option : 'Pending' } , { key : 'expired' , option : 'Expired' } , { key : 'suspended' , option : 'Suspended' } ] } .value = $ { 'pending' } .required = $ { true } > < / d e e s - i n p u t - d r o p d o w n >
< dees - input - text .key = $ { 'nameservers' } .label = $ { 'Nameservers (comma separated)' } .placeholder = $ { 'ns1.example.com, ns2.example.com' } > < / d e e s - i n p u t - t e x t >
< dees - input - text .key = $ { 'registrarName' } .label = $ { 'Registrar Name' } .placeholder = $ { 'GoDaddy, Namecheap, etc.' } > < / d e e s - i n p u t - t e x t >
< dees - input - text .key = $ { 'registrarUrl' } .label = $ { 'Registrar URL' } .placeholder = $ { 'https://registrar.com' } > < / d e e s - i n p u t - t e x t >
< dees - input - text .key = $ { 'expiresAt' } .label = $ { 'Expiration Date' } .type = $ { 'date' } > < / d e e s - i n p u t - t e x t >
< dees - input - checkbox .key = $ { 'autoRenew' } .label = $ { 'Auto-Renew Enabled' } .value = $ { true } > < / d e e s - i n p u t - c h e c k b o x >
< dees - input - checkbox .key = $ { 'dnssecEnabled' } .label = $ { 'DNSSEC Enabled' } .value = $ { false } > < / d e e s - i n p u t - c h e c k b o x >
< dees - input - checkbox .key = $ { 'isPrimary' } .label = $ { 'Primary Domain' } .value = $ { false } > < / d e e s - i n p u t - c h e c k b o x >
< dees - input - text .key = $ { 'tags' } .label = $ { 'Tags (comma separated)' } .placeholder = $ { 'production, critical' } > < / d e e s - i n p u t - t e x t >
< / d e e s - f o r m >
` ,
menuOptions: [
{ name: 'Create Domain', action: async (modalArg: any) => {
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: any) => modalArg.destroy() },
],
});
} },
{ name: 'Edit', iconName: 'edit', type: ['contextmenu', 'inRow'], actionFunc: async (actionDataArg: any) => {
const domain = actionDataArg.item as plugins.interfaces.data.IDomain;
const modal = await plugins.deesCatalog.DeesModal.createAndShow({
heading: ` Edit Domain : $ { domain . data . name } ` ,
content: html `
< dees - form >
< dees - input - text .key = $ { 'name' } .label = $ { 'Domain Name' } .value = $ { domain.data.name } .required = $ { true } > < / d e e s - i n p u t - t e x t >
< dees - input - text .key = $ { 'description' } .label = $ { 'Description' } .value = $ { domain.data.description | | '' } > < / d e e s - i n p u t - t e x t >
< dees - input - dropdown .key = $ { 'status' } .label = $ { 'Status' } .options = $ { [ { key : 'active' , option : 'Active' } , { key : 'pending' , option : 'Pending' } , { key : 'expired' , option : 'Expired' } , { key : 'suspended' , option : 'Suspended' } , { key : 'transferred' , option : 'Transferred' } ] } .value = $ { domain.data.status } .required = $ { true } > < / d e e s - i n p u t - d r o p d o w n >
< dees - input - checkbox .key = $ { 'autoRenew' } .label = $ { 'Auto-Renew Enabled' } .value = $ { domain.data.autoRenew } > < / d e e s - i n p u t - c h e c k b o x >
< dees - input - checkbox .key = $ { 'dnssecEnabled' } .label = $ { 'DNSSEC Enabled' } .value = $ { domain.data.dnssecEnabled } > < / d e e s - i n p u t - c h e c k b o x >
< dees - input - text .key = $ { 'tags' } .label = $ { 'Tags (comma separated)' } .value = $ { ( domain.data.tags | | [ ] ) .join ( ', ' ) } > < / d e e s - i n p u t - t e x t >
< / d e e s - f o r m >
` ,
menuOptions: [
{ name: 'Save Changes', action: async (modalArg: any) => {
const form = modalArg.shadowRoot.querySelector('dees-form') as any;
const formData = await form.gatherData();
const tags = formData.tags ? formData.tags.split(',').map((tag: string) => tag.trim()).filter((tag: string) => tag) : [];
2026-04-25 13:57:59 +00:00
await appstate.dataState.dispatchAction(appstate.updateDomainAction, { domainId: domain.id, domainData: { ...domain.data, name: formData.name, description: formData.description || undefined, status: formData.status, autoRenew: formData.autoRenew, dnssecEnabled: formData.dnssecEnabled, tags }, });
2025-09-14 17:28:21 +00:00
await modalArg.destroy();
}},
{ name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
],
});
} },
{ name: 'Verify Ownership', iconName: 'check-circle', type: ['contextmenu', 'inRow'], actionFunc: async (actionDataArg: any) => {
const domain = actionDataArg.item as plugins.interfaces.data.IDomain;
await plugins.deesCatalog.DeesModal.createAndShow({
heading: ` Verify Domain : $ { domain . data . name } ` ,
content: html `
< div style = "text-align:center; padding: 20px;" >
< p > Choose a verification method for < strong > $ { domain . data . name } < / strong > < / p >
< dees - form >
< dees - input - dropdown .key = $ { 'method' } .label = $ { 'Verification Method' } .options = $ { [ { key : 'dns' , option : 'DNS TXT Record' } , { key : 'http' , option : 'HTTP File Upload' } , { key : 'email' , option : 'Email Verification' } , { key : 'manual' , option : 'Manual Verification' } ] } .value = $ { 'dns' } .required = $ { true } > < / d e e s - i n p u t - d r o p d o w n >
< / d e e s - f o r m >
$ { domain . data . verificationToken ? html ` <div style="margin-top: 20px; padding: 15px; background: #333; border-radius: 8px;"><div style="color: #aaa; font-size: 0.9em;">Verification Token:</div><code style="color: #4CAF50; word-break: break-all;"> ${ domain . data . verificationToken } </code></div> ` : '' }
< / div >
` ,
menuOptions: [
{ name: 'Start Verification', action: async (modalArg: any) => { 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: any) => modalArg.destroy() },
],
});
} },
{ name: 'View DNS Records', iconName: 'list', type: ['contextmenu', 'inRow'], actionFunc: async (actionDataArg: any) => { const domain = actionDataArg.item as plugins.interfaces.data.IDomain; console.log('View DNS records for domain:', domain.data.name); } },
{ name: 'Delete', iconName: 'trash', type: ['contextmenu', 'inRow'], actionFunc: async (actionDataArg: any) => {
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 `
< div style = "text-align:center" > Are you sure you want to delete this domain ? < / div >
< div style = "margin-top: 16px; padding: 16px; background: #333; border-radius: 8px;" >
< div style = "color: #fff; font-weight: bold; font-size: 1.1em;" > $ { domain . data . name } < / div >
$ { domain . data . description ? html ` <div style="color: #aaa; margin-top: 4px;"> ${ domain . data . description } </div> ` : '' }
$ { dnsCount > 0 ? html ` <div style="color: #f44336; margin-top: 12px; padding: 8px; background: #1a1a1a; border-radius: 4px;">⚠️ This domain has ${ dnsCount } DNS record ${ dnsCount > 1 ? 's' : '' } that will also be deleted</div> ` : '' }
< / div >
` ,
menuOptions: [ { name: 'Cancel', action: async (modalArg: any) => { await modalArg.destroy(); } }, { name: 'Delete', action: async (modalArg: any) => { await appstate.dataState.dispatchAction(appstate.deleteDomainAction, { domainId: domain.id, }); await modalArg.destroy(); } }, ],
});
} },
2025-09-14 17:38:16 +00:00
{ name: 'Activate', iconName: 'check', type: ['contextmenu', 'inRow'], actionFunc: async (actionDataArg: any) => { const domain = actionDataArg.item as plugins.interfaces.data.IDomain; await appstate.dataState.dispatchAction(appstate.updateDomainAction, { domainId: domain.id, domainData: { ...(domain.data as any), activationState: 'activated' } as any }); } },
{ name: 'Deactivate', iconName: 'slash', type: ['contextmenu', 'inRow'], actionFunc: async (actionDataArg: any) => { const domain = actionDataArg.item as plugins.interfaces.data.IDomain; await appstate.dataState.dispatchAction(appstate.updateDomainAction, { domainId: domain.id, domainData: { ...(domain.data as any), activationState: 'available' } as any }); } },
{ name: 'Ignore', iconName: 'ban', type: ['contextmenu', 'inRow'], actionFunc: async (actionDataArg: any) => { const domain = actionDataArg.item as plugins.interfaces.data.IDomain; await appstate.dataState.dispatchAction(appstate.updateDomainAction, { domainId: domain.id, domainData: { ...(domain.data as any), activationState: 'ignored' } as any }); } },
2025-09-14 17:28:21 +00:00
] as plugins.deesCatalog.ITableAction[]}
></dees-table>
` ;
}
}
declare global {
interface HTMLElementTagNameMap { 'cloudly-view-domains' : CloudlyViewDomains ; }
}