2025-09-14 17:28:21 +00:00
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-deployments' )
export class CloudlyViewDeployments extends DeesElement {
@state ( )
2026-05-08 13:56:20 +00:00
private accessor data : appstate.IDataState = { } as any ;
2025-09-14 17:28:21 +00:00
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 {
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= ${ 'Service deployments running on cluster nodes' }
.data= ${ this . data . deployments || [ ] }
.displayFunction= ${ ( itemArg : plugins.interfaces.data.IDeployment ) = > {
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 ` <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 : 'Deploy Service' ,
iconName : 'plus' ,
type : [ 'header' , 'footer' ] ,
actionFunc : async ( ) = > {
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 : any ) = > { await modalArg . destroy ( ) ; } } ],
});
return;
}
await plugins.deesCatalog.DeesModal.createAndShow({
heading: 'Deploy Service',
content: html `
< dees - form >
< dees - input - dropdown .key = $ { 'serviceId' } .label = $ { 'Service' } .options = $ { availableServices.map ( s = > ( { key : s.id , value : s.data.name } ) ) } . required = $ { true } > < / d e e s - i n p u t - d r o p d o w n >
< dees - input - text .key = $ { 'nodeId' } .label = $ { 'Target Node ID' } .required = $ { true } .description = $ { 'Enter the cluster node ID where this service should be deployed' } > < / d e e s - i n p u t - t e x t >
< dees - input - text .key = $ { 'version' } .label = $ { 'Version' } .value = $ { 'latest' } .required = $ { true } > < / d e e s - i n p u t - t e x t >
< dees - input - dropdown .key = $ { 'status' } .label = $ { 'Initial Status' } .options = $ { [ 'deploying' , 'running' ] } .value = $ { 'deploying' } .required = $ { true } > < / d e e s - i n p u t - d r o p d o w n >
< / d e e s - f o r m >
` ,
menuOptions: [
{ name: 'Deploy', action: async (modalArg: any) => {
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',
deploymentLog: [],
},
});
await modalArg.destroy();
}},
{ name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
],
});
},
},
{
name: 'Restart',
iconName: 'refresh-cw',
type: ['contextmenu', 'inRow'],
actionFunc: async (actionDataArg: any) => {
const deployment = actionDataArg.item as plugins.interfaces.data.IDeployment;
plugins.deesCatalog.DeesModal.createAndShow({
heading: ` Restart Deployment ` ,
content: html `
< div style = "text-align:center" > Are you sure you want to restart 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 >
` ,
menuOptions: [
{ name: 'Cancel', action: async (modalArg: any) => { await modalArg.destroy(); } },
{ name: 'Restart', action: async (modalArg: any) => { console.log('Restart deployment:', deployment); await modalArg.destroy(); } },
],
});
},
},
{
name: 'Stop',
iconName: 'square',
type: ['contextmenu', 'inRow'],
actionFunc: async (actionDataArg: any) => {
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: any) => {
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: any) => { await modalArg.destroy(); } },
{ name: 'Delete', action: async (modalArg: any) => { await appstate.dataState.dispatchAction(appstate.deleteDeploymentAction, { deploymentId: deployment.id, }); await modalArg.destroy(); } },
],
});
},
},
] as plugins.deesCatalog.ITableAction[]}
></dees-table>
` ;
}
}
declare global {
interface HTMLElementTagNameMap {
'cloudly-view-deployments' : CloudlyViewDeployments ;
}
}