- Implemented CloudlyViewSettings component for managing cloud provider settings including Hetzner, Cloudflare, AWS, DigitalOcean, Azure, and Google Cloud. - Added functionality to load, save, and test connections for each provider. - Enhanced UI with loading states and success/error notifications. feat: Create tasks view with execution history - Developed CloudlyViewTasks component to display and manage tasks and their executions. - Integrated auto-refresh functionality for task executions. - Added filtering and searching capabilities for tasks. feat: Implement execution details and task panel components - Created CloudlyExecutionDetails component to show detailed information about task executions including logs and metrics. - Developed CloudlyTaskPanel component to display individual tasks with execution status and actions to run or cancel tasks. feat: Utility functions for formatting and categorization - Added utility functions for formatting dates, durations, and cron expressions. - Implemented functions to retrieve category icons and hues for task categorization.
207 lines
11 KiB
TypeScript
207 lines
11 KiB
TypeScript
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)) {
|
|
if (value !== undefined && value !== '****' && !value?.toString().endsWith('****')) {
|
|
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>
|
|
|
|
<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;
|
|
}
|
|
}
|
|
|