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()
private data: appstate.IDataState = {} as any;
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`${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 () => {
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: any) => { await modalArg.destroy(); } } ],
});
return;
}
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: 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`
Are you sure you want to restart this deployment?
${this.getServiceName(deployment.serviceId)}
Node: ${this.getNodeName(deployment.nodeId)}
`,
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`
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: 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[]}
>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
'cloudly-view-deployments': CloudlyViewDeployments;
}
}