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:
		
							
								
								
									
										11
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								changelog.md
									
									
									
									
									
								
							| @@ -1,5 +1,16 @@ | |||||||
| # Changelog | # Changelog | ||||||
|  |  | ||||||
|  | ## 2025-09-05 - 5.1.0 - feat(cluster) | ||||||
|  | Add cluster setupMode (manual|hetzner|aws|digitalocean) with conditional Hetzner auto-provisioning; UI and dashboard improvements; dependency upgrades | ||||||
|  |  | ||||||
|  | - Introduce optional setupMode on cluster configs and requests (ICluster.data.setupMode, createCluster request) to allow 'manual' | 'hetzner' | 'aws' | 'digitalocean'. | ||||||
|  | - ClusterManager: default setupMode to 'manual' when creating clusters and only trigger serverManager.ensureServerInfrastructure() for 'hetzner' clusters. | ||||||
|  | - ServerManager: skip provisioning for clusters not configured with setupMode 'hetzner' and log skipped clusters. | ||||||
|  | - Web UI: add a 'Setup Mode' dropdown when creating a cluster so users can choose auto-provisioning provider; ensure the add-cluster action passes setupMode. | ||||||
|  | - Web UI: dashboard enhancements — add icons to view tabs and replace cluster overview with a stats grid (including total clusters, total servers, images, services, deployments, secret groups/bundles, DNS, DBs, backups, mails, s3). The overview now computes total servers across clusters. | ||||||
|  | - Package dependency bumps (devDependencies and dependencies) to keep libs up-to-date (examples: @git.zone/tsbuild, @git.zone/tstest, @api.global/typedserver, @apiclient.xyz/docker, @design.estate/dees-catalog, @push.rocks/smartlog, @push.rocks/smartrequest, @push.rocks/taskbuffer, etc.). | ||||||
|  | - Add .claude/settings.local.json with local Claude permissions (editor/automation config). | ||||||
|  |  | ||||||
| ## 2025-08-18 - 5.0.6 - fix(connector.letsencrypt) | ## 2025-08-18 - 5.0.6 - fix(connector.letsencrypt) | ||||||
| Improve Let's Encrypt integration and certificate handling; fix coreflow certificate response; add local assistant permissions config | Improve Let's Encrypt integration and certificate handling; fix coreflow certificate response; add local assistant permissions config | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								package.json
									
									
									
									
									
								
							| @@ -22,24 +22,24 @@ | |||||||
|     "docs": "tsdoc aidoc" |     "docs": "tsdoc aidoc" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@git.zone/tsbuild": "^2.6.7", |     "@git.zone/tsbuild": "^2.6.8", | ||||||
|     "@git.zone/tsbundle": "^2.5.1", |     "@git.zone/tsbundle": "^2.5.1", | ||||||
|     "@git.zone/tsdoc": "^1.5.1", |     "@git.zone/tsdoc": "^1.5.1", | ||||||
|     "@git.zone/tspublish": "^1.10.3", |     "@git.zone/tspublish": "^1.10.3", | ||||||
|     "@git.zone/tstest": "^2.3.5", |     "@git.zone/tstest": "^2.3.6", | ||||||
|     "@git.zone/tswatch": "^2.2.1", |     "@git.zone/tswatch": "^2.2.1", | ||||||
|     "@types/node": "^22.0.0" |     "@types/node": "^22.0.0" | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@api.global/typedrequest": "3.1.10", |     "@api.global/typedrequest": "3.1.10", | ||||||
|     "@api.global/typedrequest-interfaces": "^3.0.19", |     "@api.global/typedrequest-interfaces": "^3.0.19", | ||||||
|     "@api.global/typedserver": "^3.0.77", |     "@api.global/typedserver": "^3.0.79", | ||||||
|     "@api.global/typedsocket": "^3.0.1", |     "@api.global/typedsocket": "^3.0.1", | ||||||
|     "@apiclient.xyz/cloudflare": "^6.4.1", |     "@apiclient.xyz/cloudflare": "^6.4.1", | ||||||
|     "@apiclient.xyz/docker": "^1.3.0", |     "@apiclient.xyz/docker": "^1.3.5", | ||||||
|     "@apiclient.xyz/hetznercloud": "^1.2.0", |     "@apiclient.xyz/hetznercloud": "^1.2.0", | ||||||
|     "@apiclient.xyz/slack": "^3.0.9", |     "@apiclient.xyz/slack": "^3.0.9", | ||||||
|     "@design.estate/dees-catalog": "^1.10.10", |     "@design.estate/dees-catalog": "^1.10.12", | ||||||
|     "@design.estate/dees-domtools": "^2.3.3", |     "@design.estate/dees-domtools": "^2.3.3", | ||||||
|     "@design.estate/dees-element": "^2.1.2", |     "@design.estate/dees-element": "^2.1.2", | ||||||
|     "@git.zone/tsrun": "^1.3.3", |     "@git.zone/tsrun": "^1.3.3", | ||||||
| @@ -59,19 +59,19 @@ | |||||||
|     "@push.rocks/smartguard": "^3.1.0", |     "@push.rocks/smartguard": "^3.1.0", | ||||||
|     "@push.rocks/smartjson": "^5.0.19", |     "@push.rocks/smartjson": "^5.0.19", | ||||||
|     "@push.rocks/smartjwt": "^2.2.1", |     "@push.rocks/smartjwt": "^2.2.1", | ||||||
|     "@push.rocks/smartlog": "^3.1.8", |     "@push.rocks/smartlog": "^3.1.9", | ||||||
|     "@push.rocks/smartlog-destination-clickhouse": "^1.0.13", |     "@push.rocks/smartlog-destination-clickhouse": "^1.0.13", | ||||||
|     "@push.rocks/smartlog-interfaces": "^3.0.2", |     "@push.rocks/smartlog-interfaces": "^3.0.2", | ||||||
|     "@push.rocks/smartpath": "^6.0.0", |     "@push.rocks/smartpath": "^6.0.0", | ||||||
|     "@push.rocks/smartpromise": "^4.2.3", |     "@push.rocks/smartpromise": "^4.2.3", | ||||||
|     "@push.rocks/smartrequest": "^4.2.2", |     "@push.rocks/smartrequest": "^4.3.1", | ||||||
|     "@push.rocks/smartrx": "^3.0.10", |     "@push.rocks/smartrx": "^3.0.10", | ||||||
|     "@push.rocks/smartssh": "^2.0.1", |     "@push.rocks/smartssh": "^2.0.1", | ||||||
|     "@push.rocks/smartstate": "^2.0.26", |     "@push.rocks/smartstate": "^2.0.26", | ||||||
|     "@push.rocks/smartstream": "^3.2.5", |     "@push.rocks/smartstream": "^3.2.5", | ||||||
|     "@push.rocks/smartstring": "^4.0.15", |     "@push.rocks/smartstring": "^4.0.15", | ||||||
|     "@push.rocks/smartunique": "^3.0.9", |     "@push.rocks/smartunique": "^3.0.9", | ||||||
|     "@push.rocks/taskbuffer": "^3.0.2", |     "@push.rocks/taskbuffer": "^3.1.10", | ||||||
|     "@push.rocks/webjwt": "^1.0.9", |     "@push.rocks/webjwt": "^1.0.9", | ||||||
|     "@tsclass/tsclass": "^9.2.0" |     "@tsclass/tsclass": "^9.2.0" | ||||||
|   }, |   }, | ||||||
|   | |||||||
							
								
								
									
										491
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										491
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -3,6 +3,6 @@ | |||||||
|  */ |  */ | ||||||
| export const commitinfo = { | export const commitinfo = { | ||||||
|   name: '@serve.zone/cloudly', |   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.' |   description: 'A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.' | ||||||
| } | } | ||||||
|   | |||||||
| @@ -24,11 +24,13 @@ export class ClusterManager { | |||||||
|     this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.cluster.IRequest_CreateCluster>( |     this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.cluster.IRequest_CreateCluster>( | ||||||
|       new plugins.typedrequest.TypedHandler('createCluster', async (dataArg) => { |       new plugins.typedrequest.TypedHandler('createCluster', async (dataArg) => { | ||||||
|         // TODO: guards |         // TODO: guards | ||||||
|  |         const setupMode = dataArg.setupMode || 'manual'; // Default to manual if not specified | ||||||
|         const cluster = await this.createCluster({ |         const cluster = await this.createCluster({ | ||||||
|           id: plugins.smartunique.uniSimple('cluster'), |           id: plugins.smartunique.uniSimple('cluster'), | ||||||
|           data: { |           data: { | ||||||
|             userId: null, // this is created by the createCluster method |             userId: null, // this is created by the createCluster method | ||||||
|             name: dataArg.clusterName, |             name: dataArg.clusterName, | ||||||
|  |             setupMode: setupMode, | ||||||
|             acmeInfo: null, |             acmeInfo: null, | ||||||
|             cloudlyUrl: `https://${this.cloudlyRef.config.data.publicUrl}:${this.cloudlyRef.config.data.publicPort}/`, |             cloudlyUrl: `https://${this.cloudlyRef.config.data.publicUrl}:${this.cloudlyRef.config.data.publicPort}/`, | ||||||
|             servers: [], |             servers: [], | ||||||
| @@ -36,7 +38,12 @@ export class ClusterManager { | |||||||
|           }, |           }, | ||||||
|         }); |         }); | ||||||
|         console.log(await cluster.createSavableObject()); |         console.log(await cluster.createSavableObject()); | ||||||
|  |          | ||||||
|  |         // Only auto-provision servers if setupMode is 'hetzner' | ||||||
|  |         if (setupMode === 'hetzner') { | ||||||
|           this.cloudlyRef.serverManager.ensureServerInfrastructure(); |           this.cloudlyRef.serverManager.ensureServerInfrastructure(); | ||||||
|  |         } | ||||||
|  |          | ||||||
|         return { |         return { | ||||||
|           cluster: await cluster.createSavableObject(), |           cluster: await cluster.createSavableObject(), | ||||||
|         }; |         }; | ||||||
|   | |||||||
| @@ -55,6 +55,12 @@ export class CloudlyServerManager { | |||||||
|     // get all clusters |     // get all clusters | ||||||
|     const allClusters = await this.cloudlyRef.clusterManager.getAllClusters(); |     const allClusters = await this.cloudlyRef.clusterManager.getAllClusters(); | ||||||
|     for (const cluster of allClusters) { |     for (const cluster of allClusters) { | ||||||
|  |       // Skip clusters that are not set up for Hetzner auto-provisioning | ||||||
|  |       if (cluster.data.setupMode !== 'hetzner') { | ||||||
|  |         console.log(`Skipping server provisioning for cluster ${cluster.id} - setupMode is ${cluster.data.setupMode || 'manual'}`); | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |  | ||||||
|       // get existing servers |       // get existing servers | ||||||
|       const servers = await this.getServersByCluster(cluster); |       const servers = await this.getServersByCluster(cluster); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -18,6 +18,11 @@ export interface ICluster { | |||||||
|      */ |      */ | ||||||
|     cloudlyUrl?: string; |     cloudlyUrl?: string; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Cluster setup mode - manual by default, or auto-provision with cloud provider | ||||||
|  |      */ | ||||||
|  |     setupMode?: 'manual' | 'hetzner' | 'aws' | 'digitalocean'; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * what servers are expected to be part of the cluster |      * what servers are expected to be part of the cluster | ||||||
|      */ |      */ | ||||||
|   | |||||||
| @@ -41,6 +41,7 @@ export interface IRequest_CreateCluster extends plugins.typedrequestInterfaces.i | |||||||
|   request: { |   request: { | ||||||
|     identity: userInterfaces.IIdentity; |     identity: userInterfaces.IIdentity; | ||||||
|     clusterName: string; |     clusterName: string; | ||||||
|  |     setupMode?: 'manual' | 'hetzner' | 'aws' | 'digitalocean'; | ||||||
|   }; |   }; | ||||||
|   response: { |   response: { | ||||||
|     cluster: clusterInterfaces.ICluster; |     cluster: clusterInterfaces.ICluster; | ||||||
|   | |||||||
| @@ -3,6 +3,6 @@ | |||||||
|  */ |  */ | ||||||
| export const commitinfo = { | export const commitinfo = { | ||||||
|   name: '@serve.zone/cloudly', |   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.' |   description: 'A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.' | ||||||
| } | } | ||||||
|   | |||||||
| @@ -245,6 +245,7 @@ export const addClusterAction = dataState.createAction( | |||||||
|     statePartArg, |     statePartArg, | ||||||
|     payloadArg: { |     payloadArg: { | ||||||
|       clusterName: string; |       clusterName: string; | ||||||
|  |       setupMode?: 'manual' | 'hetzner' | 'aws' | 'digitalocean'; | ||||||
|     } |     } | ||||||
|   ) => { |   ) => { | ||||||
|     let currentState = statePartArg.getState(); |     let currentState = statePartArg.getState(); | ||||||
|   | |||||||
| @@ -76,66 +76,82 @@ export class CloudlyDashboard extends DeesElement { | |||||||
|             .viewTabs=${[ |             .viewTabs=${[ | ||||||
|               { |               { | ||||||
|                 name: 'Overview', |                 name: 'Overview', | ||||||
|  |                 iconName: 'lucide:LayoutDashboard', | ||||||
|                 element: CloudlyViewOverview, |                 element: CloudlyViewOverview, | ||||||
|               }, |               }, | ||||||
|               { |               { | ||||||
|                 name: 'SecretGroups', |                 name: 'SecretGroups', | ||||||
|  |                 iconName: 'lucide:ShieldCheck', | ||||||
|                 element: CloudlyViewSecretGroups, |                 element: CloudlyViewSecretGroups, | ||||||
|               }, |               }, | ||||||
|               { |               { | ||||||
|                 name: 'SecretBundles', |                 name: 'SecretBundles', | ||||||
|  |                 iconName: 'lucide:LockKeyhole', | ||||||
|                 element: CloudlyViewSecretBundles, |                 element: CloudlyViewSecretBundles, | ||||||
|               }, |               }, | ||||||
|               { |               { | ||||||
|                 name: 'Clusters', |                 name: 'Clusters', | ||||||
|  |                 iconName: 'lucide:Network', | ||||||
|                 element: CloudlyViewClusters, |                 element: CloudlyViewClusters, | ||||||
|               }, |               }, | ||||||
|               { |               { | ||||||
|                 name: 'ExternalRegistries', |                 name: 'ExternalRegistries', | ||||||
|  |                 iconName: 'lucide:Package', | ||||||
|                 element: CloudlyViewExternalRegistries, |                 element: CloudlyViewExternalRegistries, | ||||||
|               }, |               }, | ||||||
|               { |               { | ||||||
|                 name: 'Images', |                 name: 'Images', | ||||||
|  |                 iconName: 'lucide:Image', | ||||||
|                 element: CloudlyViewImages, |                 element: CloudlyViewImages, | ||||||
|               }, |               }, | ||||||
|               { |               { | ||||||
|                 name: 'Services', |                 name: 'Services', | ||||||
|  |                 iconName: 'lucide:Layers', | ||||||
|                 element: CloudlyViewServices, |                 element: CloudlyViewServices, | ||||||
|               }, |               }, | ||||||
|               { |               { | ||||||
|                 name: 'Testing & Building', |                 name: 'Testing & Building', | ||||||
|  |                 iconName: 'lucide:HardHat', | ||||||
|                 element: CloudlyViewServices, |                 element: CloudlyViewServices, | ||||||
|               }, |               }, | ||||||
|               { |               { | ||||||
|                 name: 'Deployments', |                 name: 'Deployments', | ||||||
|  |                 iconName: 'lucide:Rocket', | ||||||
|                 element: CloudlyViewDeployments, |                 element: CloudlyViewDeployments, | ||||||
|               }, |               }, | ||||||
|               { |               { | ||||||
|                 name: 'DNS', |                 name: 'DNS', | ||||||
|  |                 iconName: 'lucide:Globe', | ||||||
|                 element: CloudlyViewDns, |                 element: CloudlyViewDns, | ||||||
|               }, |               }, | ||||||
|               { |               { | ||||||
|                 name: 'Mails', |                 name: 'Mails', | ||||||
|  |                 iconName: 'lucide:Mail', | ||||||
|                 element: CloudlyViewMails, |                 element: CloudlyViewMails, | ||||||
|               }, |               }, | ||||||
|               { |               { | ||||||
|                 name: 'Logs', |                 name: 'Logs', | ||||||
|  |                 iconName: 'lucide:FileText', | ||||||
|                 element: CloudlyViewLogs, |                 element: CloudlyViewLogs, | ||||||
|               }, |               }, | ||||||
|               { |               { | ||||||
|                 name: 's3', |                 name: 's3', | ||||||
|  |                 iconName: 'lucide:Cloud', | ||||||
|                 element: CloudlyViewS3, |                 element: CloudlyViewS3, | ||||||
|               }, |               }, | ||||||
|               { |               { | ||||||
|                 name: 'DBs', |                 name: 'DBs', | ||||||
|  |                 iconName: 'lucide:Database', | ||||||
|                 element: CloudlyViewDbs, |                 element: CloudlyViewDbs, | ||||||
|               }, |               }, | ||||||
|               { |               { | ||||||
|                 name: 'Backups', |                 name: 'Backups', | ||||||
|  |                 iconName: 'lucide:Save', | ||||||
|                 element: CloudlyViewBackups, |                 element: CloudlyViewBackups, | ||||||
|               }, |               }, | ||||||
|               { |               { | ||||||
|                 name: 'Fleet', |                 name: 'Fleet', | ||||||
|  |                 iconName: 'lucide:Truck', | ||||||
|                 element: CloudlyViewBackups, |                 element: CloudlyViewBackups, | ||||||
|               } |               } | ||||||
|             ] as plugins.deesCatalog.IView[]} |             ] as plugins.deesCatalog.IView[]} | ||||||
|   | |||||||
| @@ -68,6 +68,18 @@ export class CloudlyViewClusters extends DeesElement { | |||||||
|                       .description=${'a descriptive name for the cluster'} |                       .description=${'a descriptive name for the cluster'} | ||||||
|                       .value=${''} |                       .value=${''} | ||||||
|                     ></dees-input-text> |                     ></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> |                   </dees-form> | ||||||
|                 `, |                 `, | ||||||
|                 menuOptions: [ |                 menuOptions: [ | ||||||
| @@ -76,6 +88,7 @@ export class CloudlyViewClusters extends DeesElement { | |||||||
|                     action: async (modalArg) => { |                     action: async (modalArg) => { | ||||||
|                       const data: { |                       const data: { | ||||||
|                         clusterName: string; |                         clusterName: string; | ||||||
|  |                         setupMode: 'manual' | 'hetzner' | 'aws' | 'digitalocean'; | ||||||
|                       } = (await modalArg.shadowRoot |                       } = (await modalArg.shadowRoot | ||||||
|                         .querySelector('dees-form') |                         .querySelector('dees-form') | ||||||
|                         .collectFormData()) as any; |                         .collectFormData()) as any; | ||||||
|   | |||||||
| @@ -1,4 +1,3 @@ | |||||||
| import * as plugins from '../plugins.js'; |  | ||||||
| import * as shared from '../elements/shared/index.js'; | import * as shared from '../elements/shared/index.js'; | ||||||
|  |  | ||||||
| import { | import { | ||||||
| @@ -34,34 +33,124 @@ export class CloudlyViewOverview extends DeesElement { | |||||||
|     cssManager.defaultStyles, |     cssManager.defaultStyles, | ||||||
|     shared.viewHostCss, |     shared.viewHostCss, | ||||||
|     css` |     css` | ||||||
|       .clusterGrid { |       dees-statsgrid { | ||||||
|         display: grid; |         margin-top: 24px; | ||||||
|         grid-template-columns: ${cssManager.cssGridColumns(3, 8)}; |  | ||||||
|         grid-gap: 16px; |  | ||||||
|         margin-bottom: 40px; |  | ||||||
|       } |       } | ||||||
|     `, |     `, | ||||||
|   ]; |   ]; | ||||||
|  |  | ||||||
|   public render() { |   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` |     return html` | ||||||
|       <cloudly-sectionheading>Overview</cloudly-sectionheading> |       <cloudly-sectionheading>Overview</cloudly-sectionheading> | ||||||
|       ${this.data.clusters.length === 0 ? html` |       <dees-statsgrid | ||||||
|         You need to create at least one cluster to see an overview. |         .tiles=${statsTiles} | ||||||
|       `: html``} |         .minTileWidth=${250} | ||||||
|       ${this.data.clusters.map( |         .gap=${16} | ||||||
|         (clusterArg) => html` |       ></dees-statsgrid> | ||||||
|           <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> |  | ||||||
|         ` |  | ||||||
|       )} |  | ||||||
|     `; |     `; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user