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,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