feat(appstore): add service volumes and published ports
This commit is contained in:
@@ -288,6 +288,34 @@ export class ObViewAppStore extends DeesElement {
|
||||
text-align: center;
|
||||
color: var(--ci-shade-4, #71717a);
|
||||
}
|
||||
|
||||
.footprint-list {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.footprint-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--ci-shade-2, #27272a);
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
color: var(--ci-shade-6, #d4d4d8);
|
||||
}
|
||||
|
||||
.footprint-meta {
|
||||
color: var(--ci-shade-4, #71717a);
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.exposure-warning {
|
||||
margin-top: 10px;
|
||||
color: #fbbf24;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -410,6 +438,8 @@ export class ObViewAppStore extends DeesElement {
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${this.renderDeploymentFootprint(config)}
|
||||
|
||||
<!-- Version & Image -->
|
||||
<div class="detail-card">
|
||||
<div class="section-label">Version</div>
|
||||
@@ -489,6 +519,8 @@ export class ObViewAppStore extends DeesElement {
|
||||
Onebox routes this domain to the deployed app. Required when the app uses SERVICE_DOMAIN.
|
||||
</div>
|
||||
|
||||
${this.renderDeployConfirmation(config)}
|
||||
|
||||
<div class="actions-row">
|
||||
<button class="btn btn-secondary" @click=${() => { this.currentView = 'grid'; }}>Cancel</button>
|
||||
<button class="btn btn-primary" @click=${() => this.handleDeploy()}>
|
||||
@@ -509,6 +541,73 @@ export class ObViewAppStore extends DeesElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderDeploymentFootprint(config: interfaces.requests.IAppVersionConfig): TemplateResult | '' {
|
||||
const volumes = this.getConfigVolumes(config);
|
||||
const publishedPorts = config.publishedPorts || [];
|
||||
|
||||
if (volumes.length === 0 && publishedPorts.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="detail-card">
|
||||
<div class="section-label">Deployment Footprint</div>
|
||||
<div class="footprint-list">
|
||||
${volumes.map((volume) => html`
|
||||
<div class="footprint-item">
|
||||
<span>Volume mount</span>
|
||||
<span class="footprint-meta">
|
||||
${volume.source || volume.name || 'managed volume'}:${volume.mountPath}${volume.readOnly ? ':ro' : ''}
|
||||
</span>
|
||||
</div>
|
||||
`)}
|
||||
${publishedPorts.map((port) => html`
|
||||
<div class="footprint-item">
|
||||
<span>Published host port</span>
|
||||
<span class="footprint-meta">${this.formatPublishedPort(port)}</span>
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
${publishedPorts.length > 0 ? html`
|
||||
<div class="exposure-warning">
|
||||
This app publishes raw host ports outside the HTTP proxy. Confirm firewall and network policy before deploying.
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderDeployConfirmation(config: interfaces.requests.IAppVersionConfig): TemplateResult | '' {
|
||||
const volumes = this.getConfigVolumes(config);
|
||||
const publishedPorts = config.publishedPorts || [];
|
||||
if (volumes.length === 0 && publishedPorts.length === 0) return '';
|
||||
|
||||
return html`
|
||||
<div class="exposure-warning">
|
||||
Deploying this app will create ${volumes.length} persistent volume(s)
|
||||
${publishedPorts.length > 0 ? html`and expose ${publishedPorts.length} host port declaration(s)` : ''}.
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private getConfigVolumes(config: interfaces.requests.IAppVersionConfig): interfaces.data.IServiceVolume[] {
|
||||
return (config.volumes || []).map((volume) => {
|
||||
if (typeof volume === 'string') {
|
||||
return { mountPath: volume };
|
||||
}
|
||||
return volume;
|
||||
}).filter((volume) => Boolean(volume.mountPath));
|
||||
}
|
||||
|
||||
private formatPublishedPort(port: interfaces.data.IServicePublishedPort): string {
|
||||
const protocol = port.protocol || 'tcp';
|
||||
const target = port.targetPortEnd ? `${port.targetPort}-${port.targetPortEnd}` : String(port.targetPort);
|
||||
const publishedStart = port.publishedPort || port.targetPort;
|
||||
const publishedEnd = port.publishedPortEnd || (port.targetPortEnd ? publishedStart + (port.targetPortEnd - port.targetPort) : undefined);
|
||||
const published = publishedEnd ? `${publishedStart}-${publishedEnd}` : String(publishedStart);
|
||||
return `${port.hostIp || '0.0.0.0'}:${published}/${protocol} -> ${target}/${protocol}`;
|
||||
}
|
||||
|
||||
private async handleViewDetails(e: CustomEvent) {
|
||||
const app = e.detail?.app;
|
||||
if (!app) return;
|
||||
@@ -625,25 +724,21 @@ export class ObViewAppStore extends DeesElement {
|
||||
}
|
||||
}
|
||||
|
||||
const platformReqs = config.platformRequirements || {};
|
||||
const serviceConfig: interfaces.data.IServiceCreate = {
|
||||
name: this.serviceName || app.id,
|
||||
image: config.image,
|
||||
port: config.port || 80,
|
||||
domain: this.serviceDomain || undefined,
|
||||
envVars,
|
||||
enableMongoDB: platformReqs.mongodb || false,
|
||||
enableS3: platformReqs.s3 || false,
|
||||
enableClickHouse: platformReqs.clickhouse || false,
|
||||
enableRedis: platformReqs.redis || false,
|
||||
enableMariaDB: platformReqs.mariadb || false,
|
||||
appTemplateId: app.id,
|
||||
appTemplateVersion: this.selectedVersion,
|
||||
};
|
||||
|
||||
try {
|
||||
await appstate.servicesStatePart.dispatchAction(appstate.createServiceAction, {
|
||||
config: serviceConfig,
|
||||
const identity = appstate.loginStatePart.getState().identity;
|
||||
if (!identity) return;
|
||||
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||
interfaces.requests.IReq_InstallAppTemplate
|
||||
>('/typedrequest', 'installAppTemplate');
|
||||
await typedRequest.fire({
|
||||
identity,
|
||||
install: {
|
||||
appId: app.id,
|
||||
version: this.selectedVersion,
|
||||
serviceName: this.serviceName || app.id,
|
||||
domain: this.serviceDomain || undefined,
|
||||
envVars,
|
||||
},
|
||||
});
|
||||
setTimeout(() => {
|
||||
appRouter.navigateToView('services');
|
||||
|
||||
Reference in New Issue
Block a user