feat(web): Add deployments API typings and web UI improvements: services & deployments management with CRUD and actions
This commit is contained in:
		| @@ -22,62 +22,222 @@ export class CloudlyViewDeployments extends DeesElement { | ||||
|  | ||||
|   constructor() { | ||||
|     super(); | ||||
|     const subecription = appstate.dataState | ||||
|     const subscription = appstate.dataState | ||||
|       .select((stateArg) => stateArg) | ||||
|       .subscribe((dataArg) => { | ||||
|         this.data = dataArg; | ||||
|       }); | ||||
|     this.rxSubscriptions.push(subecription); | ||||
|     this.rxSubscriptions.push(subscription); | ||||
|   } | ||||
|  | ||||
|   public static styles = [ | ||||
|     cssManager.defaultStyles, | ||||
|     shared.viewHostCss, | ||||
|     css` | ||||
|       | ||||
|       .status-badge { | ||||
|         padding: 2px 8px; | ||||
|         border-radius: 4px; | ||||
|         font-size: 0.85em; | ||||
|         font-weight: 500; | ||||
|       } | ||||
|       .status-running { | ||||
|         background: #4caf50; | ||||
|         color: white; | ||||
|       } | ||||
|       .status-stopped { | ||||
|         background: #f44336; | ||||
|         color: white; | ||||
|       } | ||||
|       .status-paused { | ||||
|         background: #ff9800; | ||||
|         color: white; | ||||
|       } | ||||
|       .status-deploying { | ||||
|         background: #2196f3; | ||||
|         color: white; | ||||
|       } | ||||
|       .health-indicator { | ||||
|         display: inline-flex; | ||||
|         align-items: center; | ||||
|         gap: 4px; | ||||
|         padding: 2px 8px; | ||||
|         border-radius: 4px; | ||||
|         font-size: 0.85em; | ||||
|       } | ||||
|       .health-healthy { | ||||
|         background: #e8f5e9; | ||||
|         color: #2e7d32; | ||||
|       } | ||||
|       .health-unhealthy { | ||||
|         background: #ffebee; | ||||
|         color: #c62828; | ||||
|       } | ||||
|       .health-unknown { | ||||
|         background: #f5f5f5; | ||||
|         color: #666; | ||||
|       } | ||||
|       .resource-usage { | ||||
|         display: flex; | ||||
|         gap: 12px; | ||||
|         font-size: 0.9em; | ||||
|         color: #888; | ||||
|       } | ||||
|       .resource-item { | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         gap: 4px; | ||||
|       } | ||||
|     `, | ||||
|   ]; | ||||
|  | ||||
|   private getServiceName(serviceId: string): string { | ||||
|     const service = this.data.services?.find(s => s.id === serviceId); | ||||
|     return service?.data?.name || serviceId; | ||||
|   } | ||||
|  | ||||
|   private getNodeName(nodeId: string): string { | ||||
|     // This would ideally look up the cluster node name | ||||
|     // For now just return the ID shortened | ||||
|     return nodeId.substring(0, 8); | ||||
|   } | ||||
|  | ||||
|   private getStatusBadgeHtml(status: string): any { | ||||
|     const className = `status-badge status-${status}`; | ||||
|     return html`<span class="${className}">${status}</span>`; | ||||
|   } | ||||
|  | ||||
|   private getHealthIndicatorHtml(health?: string): any { | ||||
|     if (!health) health = 'unknown'; | ||||
|     const className = `health-indicator health-${health}`; | ||||
|     const icon = health === 'healthy' ? '✓' : health === 'unhealthy' ? '✗' : '?'; | ||||
|     return html`<span class="${className}">${icon} ${health}</span>`; | ||||
|   } | ||||
|  | ||||
|   private getResourceUsageHtml(deployment: plugins.interfaces.data.IDeployment): any { | ||||
|     if (!deployment.resourceUsage) { | ||||
|       return html`<span style="color: #aaa;">N/A</span>`; | ||||
|     } | ||||
|     const { cpuUsagePercent, memoryUsedMB } = deployment.resourceUsage; | ||||
|     return html` | ||||
|       <div class="resource-usage"> | ||||
|         <div class="resource-item"> | ||||
|           <lucide-icon name="Cpu" size="14"></lucide-icon> | ||||
|           ${cpuUsagePercent?.toFixed(1) || 0}% | ||||
|         </div> | ||||
|         <div class="resource-item"> | ||||
|           <lucide-icon name="MemoryStick" size="14"></lucide-icon> | ||||
|           ${memoryUsedMB || 0} MB | ||||
|         </div> | ||||
|       </div> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   public render() { | ||||
|     return html` | ||||
|       <cloudly-sectionheading>Deployments</cloudly-sectionheading> | ||||
|       <dees-table | ||||
|         .heading1=${'Deployments'} | ||||
|         .heading2=${'decoded in client'} | ||||
|         .data=${this.data.deployments} | ||||
|         .displayFunction=${(itemArg: plugins.interfaces.data.ICluster) => { | ||||
|         .heading2=${'Service deployments running on cluster nodes'} | ||||
|         .data=${this.data.deployments || []} | ||||
|         .displayFunction=${(itemArg: plugins.interfaces.data.IDeployment) => { | ||||
|           return { | ||||
|             id: itemArg.id, | ||||
|             serverAmount: itemArg.data.servers.length, | ||||
|             Service: this.getServiceName(itemArg.serviceId), | ||||
|             Node: this.getNodeName(itemArg.nodeId), | ||||
|             Status: this.getStatusBadgeHtml(itemArg.status), | ||||
|             Health: this.getHealthIndicatorHtml(itemArg.healthStatus), | ||||
|             'Container ID': itemArg.containerId ?  | ||||
|               html`<span style="font-family: monospace; font-size: 0.9em;">${itemArg.containerId.substring(0, 12)}</span>` :  | ||||
|               html`<span style="color: #aaa;">N/A</span>`, | ||||
|             Version: itemArg.version || 'latest', | ||||
|             'Resource Usage': this.getResourceUsageHtml(itemArg), | ||||
|             'Last Updated': itemArg.deployedAt ?  | ||||
|               new Date(itemArg.deployedAt).toLocaleString() :  | ||||
|               'Never', | ||||
|           }; | ||||
|         }} | ||||
|         .dataActions=${[ | ||||
|           { | ||||
|             name: 'add configBundle', | ||||
|             name: 'Deploy Service', | ||||
|             iconName: 'plus', | ||||
|             type: ['header', 'footer'], | ||||
|             actionFunc: async (dataActionArg) => { | ||||
|               const availableServices = this.data.services || []; | ||||
|               if (availableServices.length === 0) { | ||||
|                 plugins.deesCatalog.DeesModal.createAndShow({ | ||||
|                   heading: 'No Services Available', | ||||
|                   content: html` | ||||
|                     <div style="text-align: center; padding: 24px;"> | ||||
|                       <lucide-icon name="AlertCircle" size="48" style="color: #ff9800; margin-bottom: 16px;"></lucide-icon> | ||||
|                       <div>Please create a service first before creating deployments.</div> | ||||
|                     </div> | ||||
|                   `, | ||||
|                   menuOptions: [ | ||||
|                     { | ||||
|                       name: 'OK', | ||||
|                       action: async (modalArg) => { | ||||
|                         await modalArg.destroy(); | ||||
|                       }, | ||||
|                     }, | ||||
|                   ], | ||||
|                 }); | ||||
|                 return; | ||||
|               } | ||||
|  | ||||
|               const modal = await plugins.deesCatalog.DeesModal.createAndShow({ | ||||
|                 heading: 'Add ConfigBundle', | ||||
|                 heading: 'Deploy Service', | ||||
|                 content: html` | ||||
|                   <dees-form> | ||||
|                     <dees-input-text .key=${'id'} .label=${'ID'} .value=${''}></dees-input-text> | ||||
|                     <dees-input-text | ||||
|                       .key=${'data.secretGroupIds'} | ||||
|                       .label=${'secretGroupIds'} | ||||
|                       .value=${''} | ||||
|                     ></dees-input-text> | ||||
|                     <dees-input-text | ||||
|                       .key=${'data.includedTags'} | ||||
|                       .label=${'includedTags'} | ||||
|                       .value=${''} | ||||
|                     ></dees-input-text> | ||||
|                     <dees-input-dropdown  | ||||
|                       .key=${'serviceId'}  | ||||
|                       .label=${'Service'}  | ||||
|                       .options=${availableServices.map(s => ({ key: s.id, value: s.data.name }))} | ||||
|                       .required=${true}> | ||||
|                     </dees-input-dropdown> | ||||
|                     <dees-input-text  | ||||
|                       .key=${'nodeId'}  | ||||
|                       .label=${'Target Node ID'}  | ||||
|                       .required=${true} | ||||
|                       .description=${'Enter the cluster node ID where this service should be deployed'}> | ||||
|                     </dees-input-text> | ||||
|                     <dees-input-text  | ||||
|                       .key=${'version'}  | ||||
|                       .label=${'Version'}  | ||||
|                       .value=${'latest'} | ||||
|                       .required=${true}> | ||||
|                     </dees-input-text> | ||||
|                     <dees-input-dropdown  | ||||
|                       .key=${'status'}  | ||||
|                       .label=${'Initial Status'}  | ||||
|                       .options=${['deploying', 'running']} | ||||
|                       .value=${'deploying'} | ||||
|                       .required=${true}> | ||||
|                     </dees-input-dropdown> | ||||
|                   </dees-form> | ||||
|                 `, | ||||
|                 menuOptions: [ | ||||
|                   { name: 'create', action: async (modalArg) => {} }, | ||||
|                   { | ||||
|                     name: 'cancel', | ||||
|                     name: 'Deploy', | ||||
|                     action: async (modalArg) => { | ||||
|                       const form = modalArg.shadowRoot.querySelector('dees-form') as any; | ||||
|                       const formData = await form.gatherData(); | ||||
|                        | ||||
|                       await appstate.dataState.dispatchAction(appstate.createDeploymentAction, { | ||||
|                         deploymentData: { | ||||
|                           serviceId: formData.serviceId, | ||||
|                           nodeId: formData.nodeId, | ||||
|                           status: formData.status, | ||||
|                           version: formData.version, | ||||
|                           deployedAt: Date.now(), | ||||
|                           usedImageId: 'placeholder', // This would come from the service | ||||
|                           deploymentLog: [], | ||||
|                         }, | ||||
|                       }); | ||||
|                        | ||||
|                       await modalArg.destroy(); | ||||
|                     }, | ||||
|                   }, | ||||
|                   { | ||||
|                     name: 'Cancel', | ||||
|                     action: async (modalArg) => { | ||||
|                       modalArg.destroy(); | ||||
|                     }, | ||||
| @@ -87,34 +247,96 @@ export class CloudlyViewDeployments extends DeesElement { | ||||
|             }, | ||||
|           }, | ||||
|           { | ||||
|             name: 'delete', | ||||
|             iconName: 'trash', | ||||
|             name: 'Restart', | ||||
|             iconName: 'refresh-cw', | ||||
|             type: ['contextmenu', 'inRow'], | ||||
|             actionFunc: async (actionDataArg) => { | ||||
|               const deployment = actionDataArg.item as plugins.interfaces.data.IDeployment; | ||||
|               plugins.deesCatalog.DeesModal.createAndShow({ | ||||
|                 heading: `Delete ConfigBundle ${actionDataArg.item.id}`, | ||||
|                 heading: `Restart Deployment`, | ||||
|                 content: html` | ||||
|                   <div style="text-align:center"> | ||||
|                     Do you really want to delete the ConfigBundle? | ||||
|                     Are you sure you want to restart this deployment? | ||||
|                   </div> | ||||
|                   <div | ||||
|                     style="font-size: 0.8em; color: red; text-align:center; padding: 16px; margin-top: 24px; border: 1px solid #444; font-family: Intel One Mono; font-size: 16px;" | ||||
|                   > | ||||
|                     ${actionDataArg.item.id} | ||||
|                   <div style="margin-top: 16px; padding: 16px; background: #333; border-radius: 8px;"> | ||||
|                     <div style="color: #fff; font-weight: bold;"> | ||||
|                       ${this.getServiceName(deployment.serviceId)} | ||||
|                     </div> | ||||
|                     <div style="color: #aaa; font-size: 0.9em; margin-top: 4px;"> | ||||
|                       Node: ${this.getNodeName(deployment.nodeId)} | ||||
|                     </div> | ||||
|                   </div> | ||||
|                 `, | ||||
|                 menuOptions: [ | ||||
|                   { | ||||
|                     name: 'cancel', | ||||
|                     name: 'Cancel', | ||||
|                     action: async (modalArg) => { | ||||
|                       await modalArg.destroy(); | ||||
|                     }, | ||||
|                   }, | ||||
|                   { | ||||
|                     name: 'delete', | ||||
|                     name: 'Restart', | ||||
|                     action: async (modalArg) => { | ||||
|                       appstate.dataState.dispatchAction(appstate.deleteSecretBundleAction, { | ||||
|                         configBundleId: actionDataArg.item.id, | ||||
|                       // TODO: Implement restart action | ||||
|                       console.log('Restart deployment:', deployment); | ||||
|                       await modalArg.destroy(); | ||||
|                     }, | ||||
|                   }, | ||||
|                 ], | ||||
|               }); | ||||
|             }, | ||||
|           }, | ||||
|           { | ||||
|             name: 'Stop', | ||||
|             iconName: 'square', | ||||
|             type: ['contextmenu', 'inRow'], | ||||
|             actionFunc: async (actionDataArg) => { | ||||
|               const deployment = actionDataArg.item as plugins.interfaces.data.IDeployment; | ||||
|               await appstate.dataState.dispatchAction(appstate.updateDeploymentAction, { | ||||
|                 deploymentId: deployment.id, | ||||
|                 deploymentData: { | ||||
|                   ...deployment, | ||||
|                   status: 'stopped', | ||||
|                 }, | ||||
|               }); | ||||
|             }, | ||||
|           }, | ||||
|           { | ||||
|             name: 'Delete', | ||||
|             iconName: 'trash', | ||||
|             type: ['contextmenu', 'inRow'], | ||||
|             actionFunc: async (actionDataArg) => { | ||||
|               const deployment = actionDataArg.item as plugins.interfaces.data.IDeployment; | ||||
|               plugins.deesCatalog.DeesModal.createAndShow({ | ||||
|                 heading: `Delete Deployment`, | ||||
|                 content: html` | ||||
|                   <div style="text-align:center"> | ||||
|                     Are you sure you want to delete this deployment? | ||||
|                   </div> | ||||
|                   <div style="margin-top: 16px; padding: 16px; background: #333; border-radius: 8px;"> | ||||
|                     <div style="color: #fff; font-weight: bold;"> | ||||
|                       ${this.getServiceName(deployment.serviceId)} | ||||
|                     </div> | ||||
|                     <div style="color: #aaa; font-size: 0.9em; margin-top: 4px;"> | ||||
|                       Node: ${this.getNodeName(deployment.nodeId)} | ||||
|                     </div> | ||||
|                     <div style="color: #f44336; margin-top: 8px;"> | ||||
|                       This action cannot be undone. | ||||
|                     </div> | ||||
|                   </div> | ||||
|                 `, | ||||
|                 menuOptions: [ | ||||
|                   { | ||||
|                     name: 'Cancel', | ||||
|                     action: async (modalArg) => { | ||||
|                       await modalArg.destroy(); | ||||
|                     }, | ||||
|                   }, | ||||
|                   { | ||||
|                     name: 'Delete', | ||||
|                     action: async (modalArg) => { | ||||
|                       await appstate.dataState.dispatchAction(appstate.deleteDeploymentAction, { | ||||
|                         deploymentId: deployment.id, | ||||
|                       }); | ||||
|                       await modalArg.destroy(); | ||||
|                     }, | ||||
| @@ -127,4 +349,4 @@ export class CloudlyViewDeployments extends DeesElement { | ||||
|       ></dees-table> | ||||
|     `; | ||||
|   } | ||||
| } | ||||
| } | ||||
| @@ -22,62 +22,187 @@ export class CloudlyViewServices extends DeesElement { | ||||
|  | ||||
|   constructor() { | ||||
|     super(); | ||||
|     const subecription = appstate.dataState | ||||
|     const subscription = appstate.dataState | ||||
|       .select((stateArg) => stateArg) | ||||
|       .subscribe((dataArg) => { | ||||
|         this.data = dataArg; | ||||
|       }); | ||||
|     this.rxSubscriptions.push(subecription); | ||||
|     this.rxSubscriptions.push(subscription); | ||||
|   } | ||||
|  | ||||
|   public static styles = [ | ||||
|     cssManager.defaultStyles, | ||||
|     shared.viewHostCss, | ||||
|     css` | ||||
|        | ||||
|       .category-badge { | ||||
|         padding: 2px 8px; | ||||
|         border-radius: 4px; | ||||
|         font-size: 0.9em; | ||||
|         font-weight: 500; | ||||
|       } | ||||
|       .category-base { | ||||
|         background: #2196f3; | ||||
|         color: white; | ||||
|       } | ||||
|       .category-distributed { | ||||
|         background: #9c27b0; | ||||
|         color: white; | ||||
|       } | ||||
|       .category-workload { | ||||
|         background: #4caf50; | ||||
|         color: white; | ||||
|       } | ||||
|       .strategy-badge { | ||||
|         padding: 2px 8px; | ||||
|         border-radius: 4px; | ||||
|         font-size: 0.85em; | ||||
|         background: #444; | ||||
|         color: #ccc; | ||||
|         margin-left: 4px; | ||||
|       } | ||||
|     `, | ||||
|   ]; | ||||
|  | ||||
|   private getCategoryIcon(category: string): string { | ||||
|     switch (category) { | ||||
|       case 'base': | ||||
|         return 'lucide:ServerCog'; | ||||
|       case 'distributed': | ||||
|         return 'lucide:Network'; | ||||
|       case 'workload': | ||||
|         return 'lucide:Container'; | ||||
|       default: | ||||
|         return 'lucide:Box'; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private getCategoryBadgeHtml(category: string): any { | ||||
|     const className = `category-badge category-${category}`; | ||||
|     return html`<span class="${className}">${category}</span>`; | ||||
|   } | ||||
|  | ||||
|   private getStrategyBadgeHtml(strategy: string): any { | ||||
|     return html`<span class="strategy-badge">${strategy}</span>`; | ||||
|   } | ||||
|  | ||||
|   public render() { | ||||
|     return html` | ||||
|       <cloudly-sectionheading>Services</cloudly-sectionheading> | ||||
|       <dees-table | ||||
|         .heading1=${'Services'} | ||||
|         .heading2=${'decoded in client'} | ||||
|         .data=${this.data.services} | ||||
|         .displayFunction=${(itemArg: plugins.interfaces.data.ICluster) => { | ||||
|         .heading2=${'Service configuration and deployment management'} | ||||
|         .data=${this.data.services || []} | ||||
|         .displayFunction=${(itemArg: plugins.interfaces.data.IService) => { | ||||
|           return { | ||||
|             id: itemArg.id, | ||||
|             serverAmount: itemArg.data.servers.length, | ||||
|             Name: itemArg.data.name, | ||||
|             Description: itemArg.data.description, | ||||
|             Category: this.getCategoryBadgeHtml(itemArg.data.serviceCategory || 'workload'), | ||||
|             'Deployment Strategy': html` | ||||
|               ${this.getStrategyBadgeHtml(itemArg.data.deploymentStrategy || 'custom')} | ||||
|               ${itemArg.data.maxReplicas ? html`<span style="color: #888; margin-left: 8px;">Max: ${itemArg.data.maxReplicas}</span>` : ''} | ||||
|               ${itemArg.data.antiAffinity ? html`<span style="color: #f44336; margin-left: 8px;">⚡ Anti-affinity</span>` : ''} | ||||
|             `, | ||||
|             'Image': `${itemArg.data.imageId}:${itemArg.data.imageVersion}`, | ||||
|             'Scale Factor': itemArg.data.scaleFactor, | ||||
|             'Balancing': itemArg.data.balancingStrategy, | ||||
|             'Deployments': itemArg.data.deploymentIds?.length || 0, | ||||
|           }; | ||||
|         }} | ||||
|         .dataActions=${[ | ||||
|           { | ||||
|             name: 'add configBundle', | ||||
|             name: 'Add Service', | ||||
|             iconName: 'plus', | ||||
|             type: ['header', 'footer'], | ||||
|             actionFunc: async (dataActionArg) => { | ||||
|               const modal = await plugins.deesCatalog.DeesModal.createAndShow({ | ||||
|                 heading: 'Add ConfigBundle', | ||||
|                 heading: 'Add Service', | ||||
|                 content: html` | ||||
|                   <dees-form> | ||||
|                     <dees-input-text .key=${'id'} .label=${'ID'} .value=${''}></dees-input-text> | ||||
|                     <dees-input-text | ||||
|                       .key=${'data.secretGroupIds'} | ||||
|                       .label=${'secretGroupIds'} | ||||
|                       .value=${''} | ||||
|                     ></dees-input-text> | ||||
|                     <dees-input-text | ||||
|                       .key=${'data.includedTags'} | ||||
|                       .label=${'includedTags'} | ||||
|                       .value=${''} | ||||
|                     ></dees-input-text> | ||||
|                     <dees-input-text .key=${'name'} .label=${'Service Name'} .required=${true}></dees-input-text> | ||||
|                     <dees-input-text .key=${'description'} .label=${'Description'} .required=${true}></dees-input-text> | ||||
|                     <dees-input-dropdown  | ||||
|                       .key=${'serviceCategory'}  | ||||
|                       .label=${'Service Category'}  | ||||
|                       .options=${['base', 'distributed', 'workload']} | ||||
|                       .value=${'workload'} | ||||
|                       .required=${true}> | ||||
|                     </dees-input-dropdown> | ||||
|                     <dees-input-dropdown  | ||||
|                       .key=${'deploymentStrategy'}  | ||||
|                       .label=${'Deployment Strategy'}  | ||||
|                       .options=${['all-nodes', 'limited-replicas', 'custom']} | ||||
|                       .value=${'custom'} | ||||
|                       .required=${true}> | ||||
|                     </dees-input-dropdown> | ||||
|                     <dees-input-text  | ||||
|                       .key=${'maxReplicas'}  | ||||
|                       .label=${'Max Replicas (for distributed services)'}  | ||||
|                       .value=${'3'} | ||||
|                       .type=${'number'}> | ||||
|                     </dees-input-text> | ||||
|                     <dees-input-checkbox  | ||||
|                       .key=${'antiAffinity'}  | ||||
|                       .label=${'Enable Anti-Affinity'} | ||||
|                       .value=${false}> | ||||
|                     </dees-input-checkbox> | ||||
|                     <dees-input-text .key=${'imageId'} .label=${'Image ID'} .required=${true}></dees-input-text> | ||||
|                     <dees-input-text .key=${'imageVersion'} .label=${'Image Version'} .value=${'latest'} .required=${true}></dees-input-text> | ||||
|                     <dees-input-text  | ||||
|                       .key=${'scaleFactor'}  | ||||
|                       .label=${'Scale Factor'}  | ||||
|                       .value=${'1'} | ||||
|                       .type=${'number'} | ||||
|                       .required=${true}> | ||||
|                     </dees-input-text> | ||||
|                     <dees-input-dropdown  | ||||
|                       .key=${'balancingStrategy'}  | ||||
|                       .label=${'Balancing Strategy'}  | ||||
|                       .options=${['round-robin', 'least-connections']} | ||||
|                       .value=${'round-robin'} | ||||
|                       .required=${true}> | ||||
|                     </dees-input-dropdown> | ||||
|                     <dees-input-text  | ||||
|                       .key=${'webPort'}  | ||||
|                       .label=${'Web Port'}  | ||||
|                       .value=${'80'} | ||||
|                       .type=${'number'} | ||||
|                       .required=${true}> | ||||
|                     </dees-input-text> | ||||
|                   </dees-form> | ||||
|                 `, | ||||
|                 menuOptions: [ | ||||
|                   { name: 'create', action: async (modalArg) => {} }, | ||||
|                   { | ||||
|                     name: 'cancel', | ||||
|                     name: 'Create Service', | ||||
|                     action: async (modalArg) => { | ||||
|                       const form = modalArg.shadowRoot.querySelector('dees-form') as any; | ||||
|                       const formData = await form.gatherData(); | ||||
|                        | ||||
|                       await appstate.dataState.dispatchAction(appstate.createServiceAction, { | ||||
|                         serviceData: { | ||||
|                           name: formData.name, | ||||
|                           description: formData.description, | ||||
|                           serviceCategory: formData.serviceCategory, | ||||
|                           deploymentStrategy: formData.deploymentStrategy, | ||||
|                           maxReplicas: formData.maxReplicas ? parseInt(formData.maxReplicas) : undefined, | ||||
|                           antiAffinity: formData.antiAffinity, | ||||
|                           imageId: formData.imageId, | ||||
|                           imageVersion: formData.imageVersion, | ||||
|                           scaleFactor: parseInt(formData.scaleFactor), | ||||
|                           balancingStrategy: formData.balancingStrategy, | ||||
|                           ports: { | ||||
|                             web: parseInt(formData.webPort), | ||||
|                           }, | ||||
|                           environment: {}, | ||||
|                           domains: [], | ||||
|                           deploymentIds: [], | ||||
|                         }, | ||||
|                       }); | ||||
|                        | ||||
|                       await modalArg.destroy(); | ||||
|                     }, | ||||
|                   }, | ||||
|                   { | ||||
|                     name: 'Cancel', | ||||
|                     action: async (modalArg) => { | ||||
|                       modalArg.destroy(); | ||||
|                     }, | ||||
| @@ -87,34 +212,137 @@ export class CloudlyViewServices extends DeesElement { | ||||
|             }, | ||||
|           }, | ||||
|           { | ||||
|             name: 'delete', | ||||
|             name: 'Edit', | ||||
|             iconName: 'edit', | ||||
|             type: ['contextmenu', 'inRow'], | ||||
|             actionFunc: async (actionDataArg) => { | ||||
|               const service = actionDataArg.item as plugins.interfaces.data.IService; | ||||
|               const modal = await plugins.deesCatalog.DeesModal.createAndShow({ | ||||
|                 heading: `Edit Service: ${service.data.name}`, | ||||
|                 content: html` | ||||
|                   <dees-form> | ||||
|                     <dees-input-text .key=${'name'} .label=${'Service Name'} .value=${service.data.name} .required=${true}></dees-input-text> | ||||
|                     <dees-input-text .key=${'description'} .label=${'Description'} .value=${service.data.description} .required=${true}></dees-input-text> | ||||
|                     <dees-input-dropdown  | ||||
|                       .key=${'serviceCategory'}  | ||||
|                       .label=${'Service Category'}  | ||||
|                       .options=${['base', 'distributed', 'workload']} | ||||
|                       .value=${service.data.serviceCategory || 'workload'} | ||||
|                       .required=${true}> | ||||
|                     </dees-input-dropdown> | ||||
|                     <dees-input-dropdown  | ||||
|                       .key=${'deploymentStrategy'}  | ||||
|                       .label=${'Deployment Strategy'}  | ||||
|                       .options=${['all-nodes', 'limited-replicas', 'custom']} | ||||
|                       .value=${service.data.deploymentStrategy || 'custom'} | ||||
|                       .required=${true}> | ||||
|                     </dees-input-dropdown> | ||||
|                     <dees-input-text  | ||||
|                       .key=${'maxReplicas'}  | ||||
|                       .label=${'Max Replicas (for distributed services)'}  | ||||
|                       .value=${service.data.maxReplicas || ''} | ||||
|                       .type=${'number'}> | ||||
|                     </dees-input-text> | ||||
|                     <dees-input-checkbox  | ||||
|                       .key=${'antiAffinity'}  | ||||
|                       .label=${'Enable Anti-Affinity'} | ||||
|                       .value=${service.data.antiAffinity || false}> | ||||
|                     </dees-input-checkbox> | ||||
|                     <dees-input-text .key=${'imageVersion'} .label=${'Image Version'} .value=${service.data.imageVersion} .required=${true}></dees-input-text> | ||||
|                     <dees-input-text  | ||||
|                       .key=${'scaleFactor'}  | ||||
|                       .label=${'Scale Factor'}  | ||||
|                       .value=${service.data.scaleFactor} | ||||
|                       .type=${'number'} | ||||
|                       .required=${true}> | ||||
|                     </dees-input-text> | ||||
|                     <dees-input-dropdown  | ||||
|                       .key=${'balancingStrategy'}  | ||||
|                       .label=${'Balancing Strategy'}  | ||||
|                       .options=${['round-robin', 'least-connections']} | ||||
|                       .value=${service.data.balancingStrategy} | ||||
|                       .required=${true}> | ||||
|                     </dees-input-dropdown> | ||||
|                   </dees-form> | ||||
|                 `, | ||||
|                 menuOptions: [ | ||||
|                   { | ||||
|                     name: 'Update Service', | ||||
|                     action: async (modalArg) => { | ||||
|                       const form = modalArg.shadowRoot.querySelector('dees-form') as any; | ||||
|                       const formData = await form.gatherData(); | ||||
|                        | ||||
|                       await appstate.dataState.dispatchAction(appstate.updateServiceAction, { | ||||
|                         serviceId: service.id, | ||||
|                         serviceData: { | ||||
|                           ...service.data, | ||||
|                           name: formData.name, | ||||
|                           description: formData.description, | ||||
|                           serviceCategory: formData.serviceCategory, | ||||
|                           deploymentStrategy: formData.deploymentStrategy, | ||||
|                           maxReplicas: formData.maxReplicas ? parseInt(formData.maxReplicas) : undefined, | ||||
|                           antiAffinity: formData.antiAffinity, | ||||
|                           imageVersion: formData.imageVersion, | ||||
|                           scaleFactor: parseInt(formData.scaleFactor), | ||||
|                           balancingStrategy: formData.balancingStrategy, | ||||
|                         }, | ||||
|                       }); | ||||
|                        | ||||
|                       await modalArg.destroy(); | ||||
|                     }, | ||||
|                   }, | ||||
|                   { | ||||
|                     name: 'Cancel', | ||||
|                     action: async (modalArg) => { | ||||
|                       modalArg.destroy(); | ||||
|                     }, | ||||
|                   }, | ||||
|                 ], | ||||
|               }); | ||||
|             }, | ||||
|           }, | ||||
|           { | ||||
|             name: 'Deploy', | ||||
|             iconName: 'rocket', | ||||
|             type: ['contextmenu', 'inRow'], | ||||
|             actionFunc: async (actionDataArg) => { | ||||
|               const service = actionDataArg.item as plugins.interfaces.data.IService; | ||||
|               // TODO: Implement deployment action | ||||
|               console.log('Deploy service:', service); | ||||
|             }, | ||||
|           }, | ||||
|           { | ||||
|             name: 'Delete', | ||||
|             iconName: 'trash', | ||||
|             type: ['contextmenu', 'inRow'], | ||||
|             actionFunc: async (actionDataArg) => { | ||||
|               const service = actionDataArg.item as plugins.interfaces.data.IService; | ||||
|               plugins.deesCatalog.DeesModal.createAndShow({ | ||||
|                 heading: `Delete ConfigBundle ${actionDataArg.item.id}`, | ||||
|                 heading: `Delete Service: ${service.data.name}`, | ||||
|                 content: html` | ||||
|                   <div style="text-align:center"> | ||||
|                     Do you really want to delete the ConfigBundle? | ||||
|                     Are you sure you want to delete this service? | ||||
|                   </div> | ||||
|                   <div | ||||
|                     style="font-size: 0.8em; color: red; text-align:center; padding: 16px; margin-top: 24px; border: 1px solid #444; font-family: Intel One Mono; font-size: 16px;" | ||||
|                   > | ||||
|                     ${actionDataArg.item.id} | ||||
|                   <div style="margin-top: 16px; padding: 16px; background: #333; border-radius: 8px;"> | ||||
|                     <div style="color: #fff; font-weight: bold;">${service.data.name}</div> | ||||
|                     <div style="color: #aaa; font-size: 0.9em; margin-top: 4px;">${service.data.description}</div> | ||||
|                     <div style="color: #f44336; margin-top: 8px;"> | ||||
|                       This will also delete ${service.data.deploymentIds?.length || 0} deployment(s) | ||||
|                     </div> | ||||
|                   </div> | ||||
|                 `, | ||||
|                 menuOptions: [ | ||||
|                   { | ||||
|                     name: 'cancel', | ||||
|                     name: 'Cancel', | ||||
|                     action: async (modalArg) => { | ||||
|                       await modalArg.destroy(); | ||||
|                     }, | ||||
|                   }, | ||||
|                   { | ||||
|                     name: 'delete', | ||||
|                     name: 'Delete', | ||||
|                     action: async (modalArg) => { | ||||
|                       appstate.dataState.dispatchAction(appstate.deleteSecretBundleAction, { | ||||
|                         configBundleId: actionDataArg.item.id, | ||||
|                       await appstate.dataState.dispatchAction(appstate.deleteServiceAction, { | ||||
|                         serviceId: service.id, | ||||
|                       }); | ||||
|                       await modalArg.destroy(); | ||||
|                     }, | ||||
| @@ -127,4 +355,4 @@ export class CloudlyViewServices extends DeesElement { | ||||
|       ></dees-table> | ||||
|     `; | ||||
|   } | ||||
| } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user