feat(web): Add deployments API typings and web UI improvements: services & deployments management with CRUD and actions
This commit is contained in:
@@ -1,5 +1,14 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-09-08 - 5.3.0 - feat(web)
|
||||||
|
Add deployments API typings and web UI improvements: services & deployments management with CRUD and actions
|
||||||
|
|
||||||
|
- Add deployment request interfaces (ts_interfaces/requests/deployment.ts) to define typed API for create/read/update/delete/scale/restart operations.
|
||||||
|
- Extend web app state (ts_web/appstate.ts) to include typed services and deployments, and add actions for create/update/delete of services and deployments.
|
||||||
|
- Enhance web views (ts_web/elements/*): CloudlyViewServices and CloudlyViewDeployments now include richer display, styling, and UI actions (create, edit, deploy, restart, stop, delete).
|
||||||
|
- Fix subscription variable naming in several web components (subecription -> subscription) and improve table display functions to handle missing data safely.
|
||||||
|
- Add .claude/settings.local.json (tooling/permissions) used for local development/test tooling.
|
||||||
|
|
||||||
## 2025-09-07 - 5.2.0 - feat(settings)
|
## 2025-09-07 - 5.2.0 - feat(settings)
|
||||||
Add runtime settings management, node & baremetal managers, and settings UI
|
Add runtime settings management, node & baremetal managers, and settings UI
|
||||||
|
|
||||||
|
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/cloudly',
|
name: '@serve.zone/cloudly',
|
||||||
version: '5.2.0',
|
version: '5.3.0',
|
||||||
description: 'A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.'
|
description: 'A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.'
|
||||||
}
|
}
|
||||||
|
141
ts_interfaces/requests/deployment.ts
Normal file
141
ts_interfaces/requests/deployment.ts
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import * as plugins from '../plugins.js';
|
||||||
|
import type { IDeployment } from '../data/deployment.js';
|
||||||
|
import type { IIdentity } from '../data/user.js';
|
||||||
|
|
||||||
|
export interface IReq_Any_Cloudly_GetDeploymentById
|
||||||
|
extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_Any_Cloudly_GetDeploymentById
|
||||||
|
> {
|
||||||
|
method: 'getDeploymentById';
|
||||||
|
request: {
|
||||||
|
identity: IIdentity;
|
||||||
|
deploymentId: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
deployment: IDeployment;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_Any_Cloudly_GetDeployments
|
||||||
|
extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_Any_Cloudly_GetDeployments
|
||||||
|
> {
|
||||||
|
method: 'getDeployments';
|
||||||
|
request: {
|
||||||
|
identity: IIdentity;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
deployments: IDeployment[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_Any_Cloudly_GetDeploymentsByService
|
||||||
|
extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_Any_Cloudly_GetDeploymentsByService
|
||||||
|
> {
|
||||||
|
method: 'getDeploymentsByService';
|
||||||
|
request: {
|
||||||
|
identity: IIdentity;
|
||||||
|
serviceId: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
deployments: IDeployment[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_Any_Cloudly_GetDeploymentsByNode
|
||||||
|
extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_Any_Cloudly_GetDeploymentsByNode
|
||||||
|
> {
|
||||||
|
method: 'getDeploymentsByNode';
|
||||||
|
request: {
|
||||||
|
identity: IIdentity;
|
||||||
|
nodeId: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
deployments: IDeployment[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_Any_Cloudly_CreateDeployment
|
||||||
|
extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_Any_Cloudly_CreateDeployment
|
||||||
|
> {
|
||||||
|
method: 'createDeployment';
|
||||||
|
request: {
|
||||||
|
identity: IIdentity;
|
||||||
|
deploymentData: Partial<IDeployment>;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
deployment: IDeployment;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_Any_Cloudly_UpdateDeployment
|
||||||
|
extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_Any_Cloudly_UpdateDeployment
|
||||||
|
> {
|
||||||
|
method: 'updateDeployment';
|
||||||
|
request: {
|
||||||
|
identity: IIdentity;
|
||||||
|
deploymentId: string;
|
||||||
|
deploymentData: Partial<IDeployment>;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
deployment: IDeployment;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_Any_Cloudly_DeleteDeploymentById
|
||||||
|
extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_Any_Cloudly_DeleteDeploymentById
|
||||||
|
> {
|
||||||
|
method: 'deleteDeploymentById';
|
||||||
|
request: {
|
||||||
|
identity: IIdentity;
|
||||||
|
deploymentId: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
success: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_Any_Cloudly_RestartDeployment
|
||||||
|
extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_Any_Cloudly_RestartDeployment
|
||||||
|
> {
|
||||||
|
method: 'restartDeployment';
|
||||||
|
request: {
|
||||||
|
identity: IIdentity;
|
||||||
|
deploymentId: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
success: boolean;
|
||||||
|
deployment: IDeployment;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_Any_Cloudly_ScaleDeployment
|
||||||
|
extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_Any_Cloudly_ScaleDeployment
|
||||||
|
> {
|
||||||
|
method: 'scaleDeployment';
|
||||||
|
request: {
|
||||||
|
identity: IIdentity;
|
||||||
|
deploymentId: string;
|
||||||
|
replicas: number;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
success: boolean;
|
||||||
|
deployment: IDeployment;
|
||||||
|
};
|
||||||
|
}
|
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/cloudly',
|
name: '@serve.zone/cloudly',
|
||||||
version: '5.2.0',
|
version: '5.3.0',
|
||||||
description: 'A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.'
|
description: 'A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.'
|
||||||
}
|
}
|
||||||
|
@@ -48,8 +48,8 @@ export interface IDataState {
|
|||||||
secretBundles?: plugins.interfaces.data.ISecretBundle[];
|
secretBundles?: plugins.interfaces.data.ISecretBundle[];
|
||||||
clusters?: plugins.interfaces.data.ICluster[];
|
clusters?: plugins.interfaces.data.ICluster[];
|
||||||
images?: any[];
|
images?: any[];
|
||||||
services?: any[];
|
services?: plugins.interfaces.data.IService[];
|
||||||
deployments?: any[];
|
deployments?: plugins.interfaces.data.IDeployment[];
|
||||||
dns?: any[];
|
dns?: any[];
|
||||||
mails?: any[];
|
mails?: any[];
|
||||||
logs?: any[];
|
logs?: any[];
|
||||||
@@ -136,9 +136,90 @@ export const getAllDataAction = dataState.createAction(async (statePartArg) => {
|
|||||||
clusters: responseClusters.clusters,
|
clusters: responseClusters.clusters,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Services
|
||||||
|
const trGetServices =
|
||||||
|
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.service.IRequest_Any_Cloudly_GetServices>(
|
||||||
|
'/typedrequest',
|
||||||
|
'getServices'
|
||||||
|
);
|
||||||
|
const responseServices = await trGetServices.fire({
|
||||||
|
identity: loginStatePart.getState().identity,
|
||||||
|
});
|
||||||
|
currentState = {
|
||||||
|
...currentState,
|
||||||
|
services: responseServices.services,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Deployments
|
||||||
|
const trGetDeployments =
|
||||||
|
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.deployment.IReq_Any_Cloudly_GetDeployments>(
|
||||||
|
'/typedrequest',
|
||||||
|
'getDeployments'
|
||||||
|
);
|
||||||
|
const responseDeployments = await trGetDeployments.fire({
|
||||||
|
identity: loginStatePart.getState().identity,
|
||||||
|
});
|
||||||
|
currentState = {
|
||||||
|
...currentState,
|
||||||
|
deployments: responseDeployments.deployments,
|
||||||
|
};
|
||||||
|
|
||||||
return currentState;
|
return currentState;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Service Actions
|
||||||
|
export const createServiceAction = dataState.createAction(
|
||||||
|
async (statePartArg, payloadArg: { serviceData: plugins.interfaces.data.IService['data'] }) => {
|
||||||
|
let currentState = statePartArg.getState();
|
||||||
|
const trCreateService =
|
||||||
|
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.service.IRequest_Any_Cloudly_CreateService>(
|
||||||
|
'/typedrequest',
|
||||||
|
'createService'
|
||||||
|
);
|
||||||
|
const response = await trCreateService.fire({
|
||||||
|
identity: loginStatePart.getState().identity,
|
||||||
|
serviceData: payloadArg.serviceData,
|
||||||
|
});
|
||||||
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const updateServiceAction = dataState.createAction(
|
||||||
|
async (statePartArg, payloadArg: { serviceId: string; serviceData: plugins.interfaces.data.IService['data'] }) => {
|
||||||
|
let currentState = statePartArg.getState();
|
||||||
|
const trUpdateService =
|
||||||
|
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.service.IRequest_Any_Cloudly_UpdateService>(
|
||||||
|
'/typedrequest',
|
||||||
|
'updateService'
|
||||||
|
);
|
||||||
|
const response = await trUpdateService.fire({
|
||||||
|
identity: loginStatePart.getState().identity,
|
||||||
|
serviceId: payloadArg.serviceId,
|
||||||
|
serviceData: payloadArg.serviceData,
|
||||||
|
});
|
||||||
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const deleteServiceAction = dataState.createAction(
|
||||||
|
async (statePartArg, payloadArg: { serviceId: string }) => {
|
||||||
|
let currentState = statePartArg.getState();
|
||||||
|
const trDeleteService =
|
||||||
|
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.service.IRequest_Any_Cloudly_DeleteServiceById>(
|
||||||
|
'/typedrequest',
|
||||||
|
'deleteServiceById'
|
||||||
|
);
|
||||||
|
const response = await trDeleteService.fire({
|
||||||
|
identity: loginStatePart.getState().identity,
|
||||||
|
serviceId: payloadArg.serviceId,
|
||||||
|
});
|
||||||
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// SecretGroup Actions
|
// SecretGroup Actions
|
||||||
export const createSecretGroupAction = dataState.createAction(
|
export const createSecretGroupAction = dataState.createAction(
|
||||||
async (statePartArg, payloadArg: plugins.interfaces.data.ISecretGroup) => {
|
async (statePartArg, payloadArg: plugins.interfaces.data.ISecretGroup) => {
|
||||||
@@ -239,6 +320,59 @@ export const deleteImageAction = dataState.createAction(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Deployment Actions
|
||||||
|
export const createDeploymentAction = dataState.createAction(
|
||||||
|
async (statePartArg, payloadArg: { deploymentData: Partial<plugins.interfaces.data.IDeployment> }) => {
|
||||||
|
let currentState = statePartArg.getState();
|
||||||
|
const trCreateDeployment =
|
||||||
|
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.deployment.IReq_Any_Cloudly_CreateDeployment>(
|
||||||
|
'/typedrequest',
|
||||||
|
'createDeployment'
|
||||||
|
);
|
||||||
|
const response = await trCreateDeployment.fire({
|
||||||
|
identity: loginStatePart.getState().identity,
|
||||||
|
deploymentData: payloadArg.deploymentData,
|
||||||
|
});
|
||||||
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const updateDeploymentAction = dataState.createAction(
|
||||||
|
async (statePartArg, payloadArg: { deploymentId: string; deploymentData: Partial<plugins.interfaces.data.IDeployment> }) => {
|
||||||
|
let currentState = statePartArg.getState();
|
||||||
|
const trUpdateDeployment =
|
||||||
|
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.deployment.IReq_Any_Cloudly_UpdateDeployment>(
|
||||||
|
'/typedrequest',
|
||||||
|
'updateDeployment'
|
||||||
|
);
|
||||||
|
const response = await trUpdateDeployment.fire({
|
||||||
|
identity: loginStatePart.getState().identity,
|
||||||
|
deploymentId: payloadArg.deploymentId,
|
||||||
|
deploymentData: payloadArg.deploymentData,
|
||||||
|
});
|
||||||
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const deleteDeploymentAction = dataState.createAction(
|
||||||
|
async (statePartArg, payloadArg: { deploymentId: string }) => {
|
||||||
|
let currentState = statePartArg.getState();
|
||||||
|
const trDeleteDeployment =
|
||||||
|
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.deployment.IReq_Any_Cloudly_DeleteDeploymentById>(
|
||||||
|
'/typedrequest',
|
||||||
|
'deleteDeploymentById'
|
||||||
|
);
|
||||||
|
const response = await trDeleteDeployment.fire({
|
||||||
|
identity: loginStatePart.getState().identity,
|
||||||
|
deploymentId: payloadArg.deploymentId,
|
||||||
|
});
|
||||||
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// cluster
|
// cluster
|
||||||
export const addClusterAction = dataState.createAction(
|
export const addClusterAction = dataState.createAction(
|
||||||
async (
|
async (
|
||||||
|
@@ -22,62 +22,222 @@ export class CloudlyViewDeployments extends DeesElement {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
const subecription = appstate.dataState
|
const subscription = appstate.dataState
|
||||||
.select((stateArg) => stateArg)
|
.select((stateArg) => stateArg)
|
||||||
.subscribe((dataArg) => {
|
.subscribe((dataArg) => {
|
||||||
this.data = dataArg;
|
this.data = dataArg;
|
||||||
});
|
});
|
||||||
this.rxSubscriptions.push(subecription);
|
this.rxSubscriptions.push(subscription);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
shared.viewHostCss,
|
shared.viewHostCss,
|
||||||
css`
|
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`<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() {
|
public render() {
|
||||||
return html`
|
return html`
|
||||||
<cloudly-sectionheading>Deployments</cloudly-sectionheading>
|
<cloudly-sectionheading>Deployments</cloudly-sectionheading>
|
||||||
<dees-table
|
<dees-table
|
||||||
.heading1=${'Deployments'}
|
.heading1=${'Deployments'}
|
||||||
.heading2=${'decoded in client'}
|
.heading2=${'Service deployments running on cluster nodes'}
|
||||||
.data=${this.data.deployments}
|
.data=${this.data.deployments || []}
|
||||||
.displayFunction=${(itemArg: plugins.interfaces.data.ICluster) => {
|
.displayFunction=${(itemArg: plugins.interfaces.data.IDeployment) => {
|
||||||
return {
|
return {
|
||||||
id: itemArg.id,
|
Service: this.getServiceName(itemArg.serviceId),
|
||||||
serverAmount: itemArg.data.servers.length,
|
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=${[
|
.dataActions=${[
|
||||||
{
|
{
|
||||||
name: 'add configBundle',
|
name: 'Deploy Service',
|
||||||
iconName: 'plus',
|
iconName: 'plus',
|
||||||
type: ['header', 'footer'],
|
type: ['header', 'footer'],
|
||||||
actionFunc: async (dataActionArg) => {
|
actionFunc: async (dataActionArg) => {
|
||||||
|
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) => {
|
||||||
|
await modalArg.destroy();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const modal = await plugins.deesCatalog.DeesModal.createAndShow({
|
const modal = await plugins.deesCatalog.DeesModal.createAndShow({
|
||||||
heading: 'Add ConfigBundle',
|
heading: 'Deploy Service',
|
||||||
content: html`
|
content: html`
|
||||||
<dees-form>
|
<dees-form>
|
||||||
<dees-input-text .key=${'id'} .label=${'ID'} .value=${''}></dees-input-text>
|
<dees-input-dropdown
|
||||||
|
.key=${'serviceId'}
|
||||||
|
.label=${'Service'}
|
||||||
|
.options=${availableServices.map(s => ({ key: s.id, value: s.data.name }))}
|
||||||
|
.required=${true}>
|
||||||
|
</dees-input-dropdown>
|
||||||
<dees-input-text
|
<dees-input-text
|
||||||
.key=${'data.secretGroupIds'}
|
.key=${'nodeId'}
|
||||||
.label=${'secretGroupIds'}
|
.label=${'Target Node ID'}
|
||||||
.value=${''}
|
.required=${true}
|
||||||
></dees-input-text>
|
.description=${'Enter the cluster node ID where this service should be deployed'}>
|
||||||
|
</dees-input-text>
|
||||||
<dees-input-text
|
<dees-input-text
|
||||||
.key=${'data.includedTags'}
|
.key=${'version'}
|
||||||
.label=${'includedTags'}
|
.label=${'Version'}
|
||||||
.value=${''}
|
.value=${'latest'}
|
||||||
></dees-input-text>
|
.required=${true}>
|
||||||
|
</dees-input-text>
|
||||||
|
<dees-input-dropdown
|
||||||
|
.key=${'status'}
|
||||||
|
.label=${'Initial Status'}
|
||||||
|
.options=${['deploying', 'running']}
|
||||||
|
.value=${'deploying'}
|
||||||
|
.required=${true}>
|
||||||
|
</dees-input-dropdown>
|
||||||
</dees-form>
|
</dees-form>
|
||||||
`,
|
`,
|
||||||
menuOptions: [
|
menuOptions: [
|
||||||
{ name: 'create', action: async (modalArg) => {} },
|
|
||||||
{
|
{
|
||||||
name: 'cancel',
|
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) => {
|
action: async (modalArg) => {
|
||||||
modalArg.destroy();
|
modalArg.destroy();
|
||||||
},
|
},
|
||||||
@@ -87,34 +247,96 @@ export class CloudlyViewDeployments extends DeesElement {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'delete',
|
name: 'Restart',
|
||||||
iconName: 'trash',
|
iconName: 'refresh-cw',
|
||||||
type: ['contextmenu', 'inRow'],
|
type: ['contextmenu', 'inRow'],
|
||||||
actionFunc: async (actionDataArg) => {
|
actionFunc: async (actionDataArg) => {
|
||||||
|
const deployment = actionDataArg.item as plugins.interfaces.data.IDeployment;
|
||||||
plugins.deesCatalog.DeesModal.createAndShow({
|
plugins.deesCatalog.DeesModal.createAndShow({
|
||||||
heading: `Delete ConfigBundle ${actionDataArg.item.id}`,
|
heading: `Restart Deployment`,
|
||||||
content: html`
|
content: html`
|
||||||
<div style="text-align:center">
|
<div style="text-align:center">
|
||||||
Do you really want to delete the ConfigBundle?
|
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>
|
||||||
<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>
|
</div>
|
||||||
`,
|
`,
|
||||||
menuOptions: [
|
menuOptions: [
|
||||||
{
|
{
|
||||||
name: 'cancel',
|
name: 'Cancel',
|
||||||
action: async (modalArg) => {
|
action: async (modalArg) => {
|
||||||
await modalArg.destroy();
|
await modalArg.destroy();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'delete',
|
name: 'Restart',
|
||||||
action: async (modalArg) => {
|
action: async (modalArg) => {
|
||||||
appstate.dataState.dispatchAction(appstate.deleteSecretBundleAction, {
|
// TODO: Implement restart action
|
||||||
configBundleId: actionDataArg.item.id,
|
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`
|
||||||
|
<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) => {
|
||||||
|
await modalArg.destroy();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
action: async (modalArg) => {
|
||||||
|
await appstate.dataState.dispatchAction(appstate.deleteDeploymentAction, {
|
||||||
|
deploymentId: deployment.id,
|
||||||
});
|
});
|
||||||
await modalArg.destroy();
|
await modalArg.destroy();
|
||||||
},
|
},
|
||||||
|
@@ -22,62 +22,187 @@ export class CloudlyViewServices extends DeesElement {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
const subecription = appstate.dataState
|
const subscription = appstate.dataState
|
||||||
.select((stateArg) => stateArg)
|
.select((stateArg) => stateArg)
|
||||||
.subscribe((dataArg) => {
|
.subscribe((dataArg) => {
|
||||||
this.data = dataArg;
|
this.data = dataArg;
|
||||||
});
|
});
|
||||||
this.rxSubscriptions.push(subecription);
|
this.rxSubscriptions.push(subscription);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
shared.viewHostCss,
|
shared.viewHostCss,
|
||||||
css`
|
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() {
|
public render() {
|
||||||
return html`
|
return html`
|
||||||
<cloudly-sectionheading>Services</cloudly-sectionheading>
|
<cloudly-sectionheading>Services</cloudly-sectionheading>
|
||||||
<dees-table
|
<dees-table
|
||||||
.heading1=${'Services'}
|
.heading1=${'Services'}
|
||||||
.heading2=${'decoded in client'}
|
.heading2=${'Service configuration and deployment management'}
|
||||||
.data=${this.data.services}
|
.data=${this.data.services || []}
|
||||||
.displayFunction=${(itemArg: plugins.interfaces.data.ICluster) => {
|
.displayFunction=${(itemArg: plugins.interfaces.data.IService) => {
|
||||||
return {
|
return {
|
||||||
id: itemArg.id,
|
Name: itemArg.data.name,
|
||||||
serverAmount: itemArg.data.servers.length,
|
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=${[
|
.dataActions=${[
|
||||||
{
|
{
|
||||||
name: 'add configBundle',
|
name: 'Add Service',
|
||||||
iconName: 'plus',
|
iconName: 'plus',
|
||||||
type: ['header', 'footer'],
|
type: ['header', 'footer'],
|
||||||
actionFunc: async (dataActionArg) => {
|
actionFunc: async (dataActionArg) => {
|
||||||
const modal = await plugins.deesCatalog.DeesModal.createAndShow({
|
const modal = await plugins.deesCatalog.DeesModal.createAndShow({
|
||||||
heading: 'Add ConfigBundle',
|
heading: 'Add Service',
|
||||||
content: html`
|
content: html`
|
||||||
<dees-form>
|
<dees-form>
|
||||||
<dees-input-text .key=${'id'} .label=${'ID'} .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
|
<dees-input-text
|
||||||
.key=${'data.secretGroupIds'}
|
.key=${'maxReplicas'}
|
||||||
.label=${'secretGroupIds'}
|
.label=${'Max Replicas (for distributed services)'}
|
||||||
.value=${''}
|
.value=${'3'}
|
||||||
></dees-input-text>
|
.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
|
<dees-input-text
|
||||||
.key=${'data.includedTags'}
|
.key=${'scaleFactor'}
|
||||||
.label=${'includedTags'}
|
.label=${'Scale Factor'}
|
||||||
.value=${''}
|
.value=${'1'}
|
||||||
></dees-input-text>
|
.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>
|
</dees-form>
|
||||||
`,
|
`,
|
||||||
menuOptions: [
|
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) => {
|
action: async (modalArg) => {
|
||||||
modalArg.destroy();
|
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',
|
iconName: 'trash',
|
||||||
type: ['contextmenu', 'inRow'],
|
type: ['contextmenu', 'inRow'],
|
||||||
actionFunc: async (actionDataArg) => {
|
actionFunc: async (actionDataArg) => {
|
||||||
|
const service = actionDataArg.item as plugins.interfaces.data.IService;
|
||||||
plugins.deesCatalog.DeesModal.createAndShow({
|
plugins.deesCatalog.DeesModal.createAndShow({
|
||||||
heading: `Delete ConfigBundle ${actionDataArg.item.id}`,
|
heading: `Delete Service: ${service.data.name}`,
|
||||||
content: html`
|
content: html`
|
||||||
<div style="text-align:center">
|
<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="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>
|
||||||
<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>
|
</div>
|
||||||
`,
|
`,
|
||||||
menuOptions: [
|
menuOptions: [
|
||||||
{
|
{
|
||||||
name: 'cancel',
|
name: 'Cancel',
|
||||||
action: async (modalArg) => {
|
action: async (modalArg) => {
|
||||||
await modalArg.destroy();
|
await modalArg.destroy();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'delete',
|
name: 'Delete',
|
||||||
action: async (modalArg) => {
|
action: async (modalArg) => {
|
||||||
appstate.dataState.dispatchAction(appstate.deleteSecretBundleAction, {
|
await appstate.dataState.dispatchAction(appstate.deleteServiceAction, {
|
||||||
configBundleId: actionDataArg.item.id,
|
serviceId: service.id,
|
||||||
});
|
});
|
||||||
await modalArg.destroy();
|
await modalArg.destroy();
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user