feat(cluster): Add cluster setupMode (manual|hetzner|aws|digitalocean) with conditional Hetzner auto-provisioning; UI and dashboard improvements; dependency upgrades

This commit is contained in:
2025-09-05 16:07:46 +00:00
parent 330797ab1a
commit eefaa55e13
13 changed files with 392 additions and 314 deletions

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/cloudly',
version: '5.0.6',
version: '5.1.0',
description: 'A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.'
}

View File

@@ -245,6 +245,7 @@ export const addClusterAction = dataState.createAction(
statePartArg,
payloadArg: {
clusterName: string;
setupMode?: 'manual' | 'hetzner' | 'aws' | 'digitalocean';
}
) => {
let currentState = statePartArg.getState();

View File

@@ -76,66 +76,82 @@ export class CloudlyDashboard extends DeesElement {
.viewTabs=${[
{
name: 'Overview',
iconName: 'lucide:LayoutDashboard',
element: CloudlyViewOverview,
},
{
name: 'SecretGroups',
iconName: 'lucide:ShieldCheck',
element: CloudlyViewSecretGroups,
},
{
name: 'SecretBundles',
iconName: 'lucide:LockKeyhole',
element: CloudlyViewSecretBundles,
},
{
name: 'Clusters',
iconName: 'lucide:Network',
element: CloudlyViewClusters,
},
{
name: 'ExternalRegistries',
iconName: 'lucide:Package',
element: CloudlyViewExternalRegistries,
},
{
name: 'Images',
iconName: 'lucide:Image',
element: CloudlyViewImages,
},
{
name: 'Services',
iconName: 'lucide:Layers',
element: CloudlyViewServices,
},
{
name: 'Testing & Building',
iconName: 'lucide:HardHat',
element: CloudlyViewServices,
},
{
name: 'Deployments',
iconName: 'lucide:Rocket',
element: CloudlyViewDeployments,
},
{
name: 'DNS',
iconName: 'lucide:Globe',
element: CloudlyViewDns,
},
{
name: 'Mails',
iconName: 'lucide:Mail',
element: CloudlyViewMails,
},
{
name: 'Logs',
iconName: 'lucide:FileText',
element: CloudlyViewLogs,
},
{
name: 's3',
iconName: 'lucide:Cloud',
element: CloudlyViewS3,
},
{
name: 'DBs',
iconName: 'lucide:Database',
element: CloudlyViewDbs,
},
{
name: 'Backups',
iconName: 'lucide:Save',
element: CloudlyViewBackups,
},
{
name: 'Fleet',
iconName: 'lucide:Truck',
element: CloudlyViewBackups,
}
] as plugins.deesCatalog.IView[]}

View File

@@ -68,6 +68,18 @@ export class CloudlyViewClusters extends DeesElement {
.description=${'a descriptive name for the cluster'}
.value=${''}
></dees-input-text>
<dees-input-dropdown
.key=${'setupMode'}
.label=${'Setup Mode'}
.description=${'How the cluster infrastructure should be managed'}
.options=${[
{option: 'manual', key: 'manual', description: 'Manual Setup - Add your own servers manually'},
{option: 'hetzner', key: 'hetzner', description: 'Hetzner Cloud - Auto-provision servers on Hetzner'},
{option: 'aws', key: 'aws', description: 'AWS - Auto-provision on Amazon Web Services (coming soon)', disabled: true},
{option: 'digitalocean', key: 'digitalocean', description: 'DigitalOcean - Auto-provision on DigitalOcean (coming soon)', disabled: true}
]}
.selectedOption=${'manual'}
></dees-input-dropdown>
</dees-form>
`,
menuOptions: [
@@ -76,6 +88,7 @@ export class CloudlyViewClusters extends DeesElement {
action: async (modalArg) => {
const data: {
clusterName: string;
setupMode: 'manual' | 'hetzner' | 'aws' | 'digitalocean';
} = (await modalArg.shadowRoot
.querySelector('dees-form')
.collectFormData()) as any;

View File

@@ -1,4 +1,3 @@
import * as plugins from '../plugins.js';
import * as shared from '../elements/shared/index.js';
import {
@@ -34,34 +33,124 @@ export class CloudlyViewOverview extends DeesElement {
cssManager.defaultStyles,
shared.viewHostCss,
css`
.clusterGrid {
display: grid;
grid-template-columns: ${cssManager.cssGridColumns(3, 8)};
grid-gap: 16px;
margin-bottom: 40px;
dees-statsgrid {
margin-top: 24px;
}
`,
];
public render() {
// Calculate total servers across all clusters
const totalServers = this.data.clusters?.reduce((sum, cluster) =>
sum + (cluster.data.servers?.length || 0), 0) || 0;
// Create tiles for the stats grid
const statsTiles = [
{
id: 'clusters',
title: 'Total Clusters',
value: this.data.clusters?.length || 0,
type: 'number' as const,
iconName: 'lucide:Network',
description: 'Active clusters'
},
{
id: 'servers',
title: 'Total Servers',
value: totalServers,
type: 'number' as const,
iconName: 'lucide:Server',
description: 'Connected servers'
},
{
id: 'services',
title: 'Services',
value: this.data.services?.length || 0,
type: 'number' as const,
iconName: 'lucide:Layers',
description: 'Deployed services'
},
{
id: 'deployments',
title: 'Deployments',
value: this.data.deployments?.length || 0,
type: 'number' as const,
iconName: 'lucide:Rocket',
description: 'Active deployments'
},
{
id: 'secretGroups',
title: 'Secret Groups',
value: this.data.secretGroups?.length || 0,
type: 'number' as const,
iconName: 'lucide:ShieldCheck',
description: 'Configured secret groups'
},
{
id: 'secretBundles',
title: 'Secret Bundles',
value: this.data.secretBundles?.length || 0,
type: 'number' as const,
iconName: 'lucide:LockKeyhole',
description: 'Available secret bundles'
},
{
id: 'images',
title: 'Images',
value: this.data.images?.length || 0,
type: 'number' as const,
iconName: 'lucide:Image',
description: 'Container images'
},
{
id: 'dns',
title: 'DNS Zones',
value: this.data.dns?.length || 0,
type: 'number' as const,
iconName: 'lucide:Globe',
description: 'Managed DNS zones'
},
{
id: 'databases',
title: 'Databases',
value: this.data.dbs?.length || 0,
type: 'number' as const,
iconName: 'lucide:Database',
description: 'Database instances'
},
{
id: 'backups',
title: 'Backups',
value: this.data.backups?.length || 0,
type: 'number' as const,
iconName: 'lucide:Save',
description: 'Available backups'
},
{
id: 'mails',
title: 'Mail Domains',
value: this.data.mails?.length || 0,
type: 'number' as const,
iconName: 'lucide:Mail',
description: 'Mail configurations'
},
{
id: 's3',
title: 'S3 Buckets',
value: this.data.s3?.length || 0,
type: 'number' as const,
iconName: 'lucide:Cloud',
description: 'Storage buckets'
}
];
return html`
<cloudly-sectionheading>Overview</cloudly-sectionheading>
${this.data.clusters.length === 0 ? html`
You need to create at least one cluster to see an overview.
`: html``}
${this.data.clusters.map(
(clusterArg) => html`
<dees-label .label=${'cluster: ' + clusterArg.data.name}></dees-label>
<div class="clusterGrid">
<dees-chart-area .label=${'System Usage'}></dees-chart-area>
<dees-chart-area .label=${'Internet Traffic'}></dees-chart-area>
<dees-chart-area .label=${'Requests'}></dees-chart-area>
<dees-chart-area .label=${'WebSocket Connections'}></dees-chart-area>
<dees-chart-log class="services" .label=${'Deployed Services'}></dees-chart-log>
<dees-chart-log class="eventLog" .label=${'Event Log'}></dees-chart-log>
</div>
`
)}
<dees-statsgrid
.tiles=${statsTiles}
.minTileWidth=${250}
.gap=${16}
></dees-statsgrid>
`;
}
}