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-settings' )
export class CloudlyViewSettings extends DeesElement {
@state ( )
private settings : plugins.interfaces.data.ICloudlySettingsMasked = { } as any ;
@state ( )
private isLoading = false ;
@state ( )
private testResults : { [ key : string ] : { success : boolean ; message : string } } = { } ;
constructor ( ) {
super ( ) ;
this . loadSettings ( ) ;
}
public static styles = [
cssManager . defaultStyles ,
shared . viewHostCss ,
css `
.settings-container { padding: 24px 0; display: flex; flex-direction: column; gap: 16px; }
.provider-icon { margin-right: 8px; font-size: 20px; }
.test-status { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
.test-status dees-button { margin-left: auto; }
.loading-container { display: flex; justify-content: center; padding: 48px; }
.actions-container { display: flex; justify-content: center; margin-top: 24px; }
dees-panel { margin-bottom: 16px; }
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.form-grid.single { grid-template-columns: 1fr; }
@media (max-width: 768px) { .form-grid { grid-template-columns: 1fr; } }
` ,
] ;
private async loadSettings() {
this . isLoading = true ;
try {
const response = await appstate . apiClient . settings . getSettings ( ) ;
this . settings = response . settings ;
} catch ( error : any ) {
console . error ( 'Failed to load settings:' , error ) ;
plugins . deesCatalog . DeesToast . createAndShow ( { message : ` Failed to load settings: ${ error . message } ` , type : 'error' } ) ;
} finally {
this . isLoading = false ;
}
}
private async saveSettings ( formData : any ) {
this . isLoading = true ;
try {
const updates : Partial < plugins.interfaces.data.ICloudlySettings > = { } ;
for ( const [ key , value ] of Object . entries ( formData ) ) {
2026-04-29 15:57:02 +00:00
if ( value !== undefined && value !== '****' && ! value ? . toString ( ) . startsWith ( '****' ) ) {
2025-09-14 17:28:21 +00:00
updates [ key as keyof plugins . interfaces . data . ICloudlySettings ] = value as string ;
}
}
const response = await appstate . apiClient . settings . updateSettings ( updates ) ;
if ( response . success ) {
plugins . deesCatalog . DeesToast . createAndShow ( { message : 'Settings saved successfully' , type : 'success' } ) ;
await this . loadSettings ( ) ;
} else {
throw new Error ( response . message ) ;
}
} catch ( error : any ) {
console . error ( 'Failed to save settings:' , error ) ;
plugins . deesCatalog . DeesToast . createAndShow ( { message : ` Failed to save settings: ${ error . message } ` , type : 'error' } ) ;
} finally {
this . isLoading = false ;
}
}
private async testConnection ( provider : string ) {
this . isLoading = true ;
try {
const response = await appstate . apiClient . settings . testProviderConnection ( provider ) ;
this . testResults = { . . . this . testResults , [ provider ] : { success : response.connectionValid , message : response.message } } ;
plugins . deesCatalog . DeesToast . createAndShow ( { message : response.message , type : response . connectionValid ? 'success' : 'error' } ) ;
} catch ( error : any ) {
this . testResults = { . . . this . testResults , [ provider ] : { success : false , message : ` Test failed: ${ error . message } ` } } ;
plugins . deesCatalog . DeesToast . createAndShow ( { message : ` Connection test failed: ${ error . message } ` , type : 'error' } ) ;
} finally {
this . isLoading = false ;
}
}
private renderProviderStatus ( provider : string ) {
const result = this . testResults [ provider ] ;
if ( ! result ) return '' as any ;
return html ` <dees-badge .type= ${ result . success ? 'success' : 'error' } .text= ${ result . success ? 'Connected' : 'Failed' } ></dees-badge> ` ;
}
public render() {
if ( this . isLoading && Object . keys ( this . settings ) . length === 0 ) {
return html ` <div class="loading-container"><dees-spinner></dees-spinner></div> ` ;
}
return html `
<cloudly-sectionheading>Settings</cloudly-sectionheading>
<div class="settings-container">
<dees-form @formData= ${ ( e : CustomEvent ) = > { this . saveSettings ( ( e . detail as any ) . data ) ; } }>
<dees-panel .title= ${ 'Hetzner Cloud' } .subtitle= ${ 'Configure Hetzner Cloud API access' } .variant= ${ 'outline' } >
<div class="test-status">
${ this . renderProviderStatus ( 'hetzner' ) }
<dees-button .text= ${ 'Test Connection' } .type= ${ 'secondary' } @click= ${ ( e : Event ) = > { e . preventDefault ( ) ; e . stopPropagation ( ) ; this . testConnection ( 'hetzner' ) ; } }></dees-button>
</div>
<div class="form-grid single">
<dees-input-text .key= ${ 'hetznerToken' } .label= ${ 'API Token' } .value= ${ this . settings . hetznerToken || '' } .isPasswordBool= ${ true } .description= ${ 'Your Hetzner Cloud API token for managing infrastructure' } .required= ${ false } ></dees-input-text>
</div>
</dees-panel>
<dees-panel .title= ${ 'Cloudflare' } .subtitle= ${ 'Configure Cloudflare API access' } .variant= ${ 'outline' } >
<div class="test-status">
${ this . renderProviderStatus ( 'cloudflare' ) }
<dees-button .text= ${ 'Test Connection' } .type= ${ 'secondary' } @click= ${ ( e : Event ) = > { e . preventDefault ( ) ; e . stopPropagation ( ) ; this . testConnection ( 'cloudflare' ) ; } }></dees-button>
</div>
<div class="form-grid single">
<dees-input-text .key= ${ 'cloudflareToken' } .label= ${ 'API Token' } .value= ${ this . settings . cloudflareToken || '' } .isPasswordBool= ${ true } .description= ${ 'Cloudflare API token with DNS and Zone permissions' } .required= ${ false } ></dees-input-text>
</div>
</dees-panel>
2026-04-29 15:57:02 +00:00
<dees-panel .title= ${ 'External dcrouter Gateway' } .subtitle= ${ 'Route WorkApps through a dcrouter edge authority' } .variant= ${ 'outline' } >
<div class="form-grid">
<dees-input-text .key= ${ 'dcrouterGatewayUrl' } .label= ${ 'Gateway URL' } .value= ${ this . settings . dcrouterGatewayUrl || '' } .description= ${ 'Base URL of the dcrouter OpsServer, for example https://edge.example.com' } .required= ${ false } ></dees-input-text>
<dees-input-text .key= ${ 'dcrouterGatewayApiToken' } .label= ${ 'API Token' } .value= ${ this . settings . dcrouterGatewayApiToken || '' } .isPasswordBool= ${ true } .description= ${ 'dcrouter API token with workhosters and certificates scopes' } .required= ${ false } ></dees-input-text>
</div>
<div class="form-grid">
<dees-input-text .key= ${ 'dcrouterWorkHosterId' } .label= ${ 'WorkHoster ID' } .value= ${ this . settings . dcrouterWorkHosterId || '' } .description= ${ 'Optional stable owner ID; defaults to the cluster ID' } .required= ${ false } ></dees-input-text>
<dees-input-text .key= ${ 'dcrouterTargetHost' } .label= ${ 'Target Host' } .value= ${ this . settings . dcrouterTargetHost || '' } .description= ${ 'Host or IP dcrouter forwards workload traffic to' } .required= ${ false } ></dees-input-text>
</div>
<div class="form-grid single">
<dees-input-text .key= ${ 'dcrouterTargetPort' } .label= ${ 'Target Port' } .value= ${ this . settings . dcrouterTargetPort || '80' } .description= ${ 'Internal HTTP port dcrouter should forward to' } .required= ${ false } ></dees-input-text>
</div>
</dees-panel>
2026-05-07 17:44:31 +00:00
<dees-panel .title= ${ 'CoreBuild Worker' } .subtitle= ${ 'Build BaseOS images on a capable worker node' } .variant= ${ 'outline' } >
<div class="form-grid">
<dees-input-text .key= ${ 'corebuildWorkerUrl' } .label= ${ 'Worker URL' } .value= ${ this . settings . corebuildWorkerUrl || '' } .description= ${ 'Base URL of the corebuild worker, for example http://10.0.0.20:3060' } .required= ${ false } ></dees-input-text>
<dees-input-text .key= ${ 'corebuildWorkerToken' } .label= ${ 'Worker Token' } .value= ${ this . settings . corebuildWorkerToken || '' } .isPasswordBool= ${ true } .description= ${ 'Shared token accepted by the corebuild worker' } .required= ${ false } ></dees-input-text>
</div>
</dees-panel>
2025-09-14 17:28:21 +00:00
<dees-panel .title= ${ 'Amazon Web Services' } .subtitle= ${ 'Configure AWS credentials' } .variant= ${ 'outline' } >
<div class="test-status">
${ this . renderProviderStatus ( 'aws' ) }
<dees-button .text= ${ 'Test Connection' } .type= ${ 'secondary' } @click= ${ ( e : Event ) = > { e . preventDefault ( ) ; e . stopPropagation ( ) ; this . testConnection ( 'aws' ) ; } }></dees-button>
</div>
<div class="form-grid">
<dees-input-text .key= ${ 'awsAccessKey' } .label= ${ 'Access Key ID' } .value= ${ this . settings . awsAccessKey || '' } .isPasswordBool= ${ true } .description= ${ 'AWS IAM access key identifier' } .required= ${ false } ></dees-input-text>
<dees-input-text .key= ${ 'awsSecretKey' } .label= ${ 'Secret Access Key' } .value= ${ this . settings . awsSecretKey || '' } .isPasswordBool= ${ true } .description= ${ 'AWS IAM secret access key' } .required= ${ false } ></dees-input-text>
</div>
<div class="form-grid single">
<dees-input-dropdown .key= ${ 'awsRegion' } .label= ${ 'Default Region' } .selectedOption= ${ this . settings . awsRegion || 'us-east-1' } .options= ${ [
{ key : 'us-east-1' , option : 'US East (N. Virginia)' , payload : null } ,
{ key: 'us-west-2', option: 'US West (Oregon)', payload: null },
{ key: 'eu-west-1', option: 'EU (Ireland)', payload: null },
{ key: 'eu-central-1', option: 'EU (Frankfurt)', payload: null },
{ key: 'ap-southeast-1', option: 'Asia Pacific (Singapore)', payload: null },
{ key: 'ap-northeast-1', option: 'Asia Pacific (Tokyo)', payload: null },
]} .description= ${ 'Default AWS region for resource provisioning' } ></dees-input-dropdown>
</div>
</dees-panel>
<dees-panel .title= ${ 'DigitalOcean' } .subtitle= ${ 'Configure DigitalOcean API access' } .variant= ${ 'outline' } >
<div class="test-status">
${ this . renderProviderStatus ( 'digitalocean' ) }
<dees-button .text= ${ 'Test Connection' } .type= ${ 'secondary' } @click= ${ ( e : Event ) = > { e . preventDefault ( ) ; e . stopPropagation ( ) ; this . testConnection ( 'digitalocean' ) ; } }></dees-button>
</div>
<div class="form-grid single">
<dees-input-text .key= ${ 'digitalOceanToken' } .label= ${ 'Personal Access Token' } .value= ${ this . settings . digitalOceanToken || '' } .isPasswordBool= ${ true } .description= ${ 'DigitalOcean personal access token with read/write scope' } .required= ${ false } ></dees-input-text>
</div>
</dees-panel>
<dees-panel .title= ${ 'Microsoft Azure' } .subtitle= ${ 'Configure Azure service principal' } .variant= ${ 'outline' } >
<div class="test-status">
${ this . renderProviderStatus ( 'azure' ) }
<dees-button .text= ${ 'Test Connection' } .type= ${ 'secondary' } @click= ${ ( e : Event ) = > { e . preventDefault ( ) ; e . stopPropagation ( ) ; this . testConnection ( 'azure' ) ; } }></dees-button>
</div>
<div class="form-grid">
<dees-input-text .key= ${ 'azureClientId' } .label= ${ 'Application (Client) ID' } .value= ${ this . settings . azureClientId || '' } .isPasswordBool= ${ true } .description= ${ 'Azure AD application client ID' } .required= ${ false } ></dees-input-text>
<dees-input-text .key= ${ 'azureClientSecret' } .label= ${ 'Client Secret' } .value= ${ this . settings . azureClientSecret || '' } .isPasswordBool= ${ true } .description= ${ 'Azure AD application client secret' } .required= ${ false } ></dees-input-text>
</div>
<div class="form-grid">
<dees-input-text .key= ${ 'azureTenantId' } .label= ${ 'Directory (Tenant) ID' } .value= ${ this . settings . azureTenantId || '' } .description= ${ 'Azure AD tenant identifier' } .required= ${ false } ></dees-input-text>
<dees-input-text .key= ${ 'azureSubscriptionId' } .label= ${ 'Subscription ID' } .value= ${ this . settings . azureSubscriptionId || '' } .description= ${ 'Azure subscription for resource management' } .required= ${ false } ></dees-input-text>
</div>
</dees-panel>
<dees-panel .title= ${ 'Google Cloud Platform' } .subtitle= ${ 'Configure GCP service account' } .variant= ${ 'outline' } >
<div class="test-status">
${ this . renderProviderStatus ( 'google' ) }
<dees-button .text= ${ 'Test Connection' } .type= ${ 'secondary' } @click= ${ ( e : Event ) = > { e . preventDefault ( ) ; e . stopPropagation ( ) ; this . testConnection ( 'google' ) ; } }></dees-button>
</div>
<div class="form-grid single">
<dees-input-textarea .key= ${ 'googleCloudKeyJson' } .label= ${ 'Service Account Key (JSON)' } .value= ${ this . settings . googleCloudKeyJson || '' } .isPasswordBool= ${ true } .description= ${ 'Complete JSON key file for service account authentication' } .required= ${ false } ></dees-input-textarea>
</div>
<div class="form-grid single">
<dees-input-text .key= ${ 'googleCloudProjectId' } .label= ${ 'Project ID' } .value= ${ this . settings . googleCloudProjectId || '' } .description= ${ 'Google Cloud project identifier' } .required= ${ false } ></dees-input-text>
</div>
</dees-panel>
<div class="actions-container">
<dees-form-submit .text= ${ 'Save All Settings' } .disabled= ${ this . isLoading } ></dees-form-submit>
</div>
</dees-form>
</div>
` ;
}
}
declare global {
interface HTMLElementTagNameMap {
'cloudly-view-settings' : CloudlyViewSettings ;
}
}