- Added typedRequestInterfaces import to plugins.ts for better type handling. - Updated CLI client to utilize environment variables for Cloudly API credentials and improved authentication flow. - Refactored appstate.ts to use a shared API client instance, reducing redundancy in API calls for various actions. - Simplified external registry actions in appstate.ts by leveraging the shared API client. - Updated CloudlyDashboard and CloudlyViewSettings components to utilize the shared API client for fetching settings and managing connections. - Removed redundant TypedRequest instances in favor of direct API client calls for improved performance and maintainability. - Exposed the API client in plugins.ts for easier access in UI components.
462 lines
15 KiB
TypeScript
462 lines
15 KiB
TypeScript
import * as plugins from '../plugins.js';
|
|
import * as shared from '../elements/shared/index.js';
|
|
|
|
import {
|
|
DeesElement,
|
|
customElement,
|
|
html,
|
|
state,
|
|
css,
|
|
cssManager,
|
|
property,
|
|
} 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 = {};
|
|
|
|
@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 {
|
|
// Use shared API client
|
|
const response = await appstate.apiClient.settings.getSettings();
|
|
this.settings = response.settings;
|
|
} catch (error) {
|
|
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) {
|
|
console.log('saveSettings called with formData:', formData);
|
|
this.isLoading = true;
|
|
try {
|
|
const updates: Partial<plugins.interfaces.data.ICloudlySettings> = {};
|
|
|
|
// Process form data
|
|
for (const [key, value] of Object.entries(formData)) {
|
|
console.log(`Processing ${key}:`, value);
|
|
if (value !== undefined && value !== '****' && !value?.toString().endsWith('****')) {
|
|
// Only update if value changed (not masked)
|
|
updates[key as keyof plugins.interfaces.data.ICloudlySettings] = value as string;
|
|
}
|
|
}
|
|
console.log('Updates to send:', updates);
|
|
|
|
const response = await appstate.apiClient.settings.updateSettings(updates);
|
|
|
|
if (response.success) {
|
|
plugins.deesCatalog.DeesToast.createAndShow({
|
|
message: 'Settings saved successfully',
|
|
type: 'success',
|
|
});
|
|
await this.loadSettings(); // Reload to get masked values
|
|
} else {
|
|
throw new Error(response.message);
|
|
}
|
|
} catch (error) {
|
|
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
|
|
}
|
|
};
|
|
|
|
// Show toast notification
|
|
plugins.deesCatalog.DeesToast.createAndShow({
|
|
message: response.message,
|
|
type: response.connectionValid ? 'success' : 'error',
|
|
});
|
|
} catch (error) {
|
|
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 '';
|
|
|
|
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) => {
|
|
console.log('formData event received:', e);
|
|
console.log('Event detail:', e.detail);
|
|
console.log('Event detail.data:', e.detail.data);
|
|
this.saveSettings(e.detail.data);
|
|
}}>
|
|
|
|
<!-- Hetzner Cloud -->
|
|
<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>
|
|
|
|
<!-- Cloudflare -->
|
|
<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>
|
|
|
|
<!-- AWS -->
|
|
<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>
|
|
|
|
<!-- DigitalOcean -->
|
|
<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>
|
|
|
|
<!-- Azure -->
|
|
<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>
|
|
|
|
<!-- Google Cloud -->
|
|
<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>
|
|
`;
|
|
}
|
|
}
|