f40ef6b7c0
Align Cloudly with the current typedserver, smartconfig, smartstate, and Docker tooling releases so builds and Docker output stay compatible with the upgraded stack.
226 lines
12 KiB
TypeScript
226 lines
12 KiB
TypeScript
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-services')
|
|
export class CloudlyViewServices extends DeesElement {
|
|
@state()
|
|
private accessor 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`
|
|
.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=${'Service configuration and deployment management'}
|
|
.data=${this.data.services || []}
|
|
.displayFunction=${(itemArg: plugins.interfaces.data.IService) => {
|
|
return {
|
|
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 Service',
|
|
iconName: 'plus',
|
|
type: ['header', 'footer'],
|
|
actionFunc: async () => {
|
|
const modal = await plugins.deesCatalog.DeesModal.createAndShow({
|
|
heading: 'Add Service',
|
|
content: html`
|
|
<dees-form>
|
|
<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=${[{key: 'base', option: 'Base'}, {key: 'distributed', option: 'Distributed'}, {key: 'workload', option: 'Workload'}]} .value=${'workload'} .required=${true}></dees-input-dropdown>
|
|
<dees-input-dropdown .key=${'deploymentStrategy'} .label=${'Deployment Strategy'} .options=${[{key: 'all-nodes', option: 'All Nodes'}, {key: 'limited-replicas', option: 'Limited Replicas'}, {key: 'custom', option: 'Custom'}]} .value=${'custom'} .required=${true}></dees-input-dropdown>
|
|
<dees-input-text .key=${'maxReplicas'} .label=${'Max Replicas (for distributed services)'} .value=${'1'} .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=${[{key: 'round-robin', option: 'Round Robin'}, {key: 'least-connections', option: '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 Service', action: async (modalArg: any) => {
|
|
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,
|
|
secretBundleId: '',
|
|
scaleFactor: parseInt(formData.scaleFactor),
|
|
balancingStrategy: formData.balancingStrategy,
|
|
ports: { web: parseInt(formData.webPort) },
|
|
environment: {},
|
|
domains: [],
|
|
deploymentIds: [],
|
|
},
|
|
});
|
|
await modalArg.destroy();
|
|
}},
|
|
{ name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
|
|
],
|
|
});
|
|
},
|
|
},
|
|
{
|
|
name: 'Edit',
|
|
iconName: 'edit',
|
|
type: ['contextmenu', 'inRow'],
|
|
actionFunc: async (actionDataArg: any) => {
|
|
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=${[{key: 'base', option: 'Base'}, {key: 'distributed', option: 'Distributed'}, {key: 'workload', option: 'Workload'}]} .value=${service.data.serviceCategory || 'workload'} .required=${true}></dees-input-dropdown>
|
|
<dees-input-dropdown .key=${'deploymentStrategy'} .label=${'Deployment Strategy'} .options=${[{key: 'all-nodes', option: 'All Nodes'}, {key: 'limited-replicas', option: 'Limited Replicas'}, {key: 'custom', option: '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=${[{key: 'round-robin', option: 'Round Robin'}, {key: 'least-connections', option: 'Least Connections'}]} .value=${service.data.balancingStrategy} .required=${true}></dees-input-dropdown>
|
|
</dees-form>
|
|
`,
|
|
menuOptions: [
|
|
{ name: 'Update Service', action: async (modalArg: any) => {
|
|
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: any) => modalArg.destroy() },
|
|
],
|
|
});
|
|
},
|
|
},
|
|
{
|
|
name: 'Deploy',
|
|
iconName: 'rocket',
|
|
type: ['contextmenu', 'inRow'],
|
|
actionFunc: async (actionDataArg: any) => {
|
|
const service = actionDataArg.item as plugins.interfaces.data.IService;
|
|
console.log('Deploy service:', service);
|
|
},
|
|
},
|
|
{
|
|
name: 'Delete',
|
|
iconName: 'trash',
|
|
type: ['contextmenu', 'inRow'],
|
|
actionFunc: async (actionDataArg: any) => {
|
|
const service = actionDataArg.item as plugins.interfaces.data.IService;
|
|
plugins.deesCatalog.DeesModal.createAndShow({
|
|
heading: `Delete Service: ${service.data.name}`,
|
|
content: html`
|
|
<div style="text-align:center">Are you sure you want to delete this service?</div>
|
|
<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', action: async (modalArg: any) => modalArg.destroy() },
|
|
{ name: 'Delete', action: async (modalArg: any) => { await appstate.dataState.dispatchAction(appstate.deleteServiceAction, { serviceId: service.id }); await modalArg.destroy(); } },
|
|
],
|
|
});
|
|
},
|
|
},
|
|
] as plugins.deesCatalog.ITableAction[]}
|
|
></dees-table>
|
|
`;
|
|
}
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
'cloudly-view-services': CloudlyViewServices;
|
|
}
|
|
}
|