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 8 px ; border - radius : 4px ; font - size : 0.85em ; font - weight : 500 ; color : white ; }
. status - active { background : # 4 CAF50 ; }
. status - pending { background : # FF9800 ; }
. status - expired { background : # f44336 ; }
. status - suspended { background : # 9 E9E9E ; }
. status - transferred { background : # 607 D8B ; }
. verification - badge { display : inline - block ; padding : 2px 8 px ; border - radius : 4px ; font - size : 0.85em ; font - weight : 500 ; }
. verification - verified { background : # 4 CAF50 ; 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 6 px ; border - radius : 3px ; font - size : 0.8em ; }
. ssl - active { color : # 4 CAF50 ; }
. ssl - pending { color : # FF9800 ; }
. ssl - expired { color : # f44336 ; }
. ssl - none { color : # 9 E9E9E ; }
. nameserver - list { font - size : 0.85em ; color : # 666 ; }
2025-09-14 17:38:16 +00:00
. activation - badge { display : inline - block ; padding : 2px 8 px ; border - radius : 4px ; font - size : 0.85em ; font - weight : 500 ; }
. activation - available { background : # 2 b2b2b ; color : # bbb ; border : 1px solid # 444 ; }
. activation - activated { background : # 4 CAF50 ; color : # fff ; }
. activation - ignored { background : # 9 E9E9E ; 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 < / c l o u d l y - s e c t i o n h e a d i n g >
< 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 ) : [ ] ;
await appstate . dataState . dispatchAction ( appstate . updateDomainAction , { domainId : domain.id , updates : { name : formData.name , description : formData.description || undefined , status : formData.status , autoRenew : formData.autoRenew , dnssecEnabled : formData.dnssecEnabled , tags } , } ) ;
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 [ ] }
> < / d e e s - t a b l e >
` ;
}
}
declare global {
interface HTMLElementTagNameMap { 'cloudly-view-domains' : CloudlyViewDomains ; }
}