import * as plugins from '../plugins.js'; import * as shared from '../elements/shared/index.js'; import { DeesElement, customElement, html, state, css, cssManager, } from '@design.estate/dees-element'; import * as appstate from '../appstate.js'; @customElement('cloudly-view-deployments') export class CloudlyViewDeployments extends DeesElement { @state() private data: appstate.IDataState = {}; constructor() { super(); const subscription = appstate.dataState .select((stateArg) => stateArg) .subscribe((dataArg) => { this.data = dataArg; }); 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`${status}`; } private getHealthIndicatorHtml(health?: string): any { if (!health) health = 'unknown'; const className = `health-indicator health-${health}`; const icon = health === 'healthy' ? '✓' : health === 'unhealthy' ? '✗' : '?'; return html`${icon} ${health}`; } private getResourceUsageHtml(deployment: plugins.interfaces.data.IDeployment): any { if (!deployment.resourceUsage) { return html`N/A`; } const { cpuUsagePercent, memoryUsedMB } = deployment.resourceUsage; return html`
${cpuUsagePercent?.toFixed(1) || 0}%
${memoryUsedMB || 0} MB
`; } public render() { return html` Deployments { return { 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`${itemArg.containerId.substring(0, 12)}` : html`N/A`, Version: itemArg.version || 'latest', 'Resource Usage': this.getResourceUsageHtml(itemArg), 'Last Updated': itemArg.deployedAt ? new Date(itemArg.deployedAt).toLocaleString() : 'Never', }; }} .dataActions=${[ { 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`
Please create a service first before creating deployments.
`, menuOptions: [ { name: 'OK', action: async (modalArg) => { await modalArg.destroy(); }, }, ], }); return; } const modal = await plugins.deesCatalog.DeesModal.createAndShow({ heading: 'Deploy Service', content: html` ({ key: s.id, value: s.data.name }))} .required=${true}> `, menuOptions: [ { 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(); }, }, ], }); }, }, { 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: `Restart Deployment`, content: html`
Are you sure you want to restart this deployment?
${this.getServiceName(deployment.serviceId)}
Node: ${this.getNodeName(deployment.nodeId)}
`, menuOptions: [ { name: 'Cancel', action: async (modalArg) => { await modalArg.destroy(); }, }, { name: 'Restart', action: async (modalArg) => { // 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`
Are you sure you want to delete this deployment?
${this.getServiceName(deployment.serviceId)}
Node: ${this.getNodeName(deployment.nodeId)}
This action cannot be undone.
`, 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(); }, }, ], }); }, }, ] as plugins.deesCatalog.ITableAction[]} >
`; } }