feat(services): map backend service data to UI components, add stats & logs parsing, fetch service stats, and fix logs request param
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as shared from './shared/index.js';
|
||||
import * as appstate from '../appstate.js';
|
||||
import * as interfaces from '../../ts_interfaces/index.js';
|
||||
import {
|
||||
DeesElement,
|
||||
customElement,
|
||||
@@ -11,6 +12,91 @@ import {
|
||||
type TemplateResult,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
// ============================================================================
|
||||
// Data transformation helpers
|
||||
// Maps backend data shapes to @serve.zone/catalog component interfaces
|
||||
// ============================================================================
|
||||
|
||||
function formatBytes(bytes: number): string {
|
||||
if (!bytes || bytes === 0) return '0 B';
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
const k = 1024;
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
const value = bytes / Math.pow(k, i);
|
||||
return `${value.toFixed(1)} ${units[i]}`;
|
||||
}
|
||||
|
||||
function parseImageString(image: string): { repository: string; tag: string } {
|
||||
const lastColon = image.lastIndexOf(':');
|
||||
const lastSlash = image.lastIndexOf('/');
|
||||
if (lastColon > lastSlash && lastColon > 0) {
|
||||
return {
|
||||
repository: image.substring(0, lastColon),
|
||||
tag: image.substring(lastColon + 1),
|
||||
};
|
||||
}
|
||||
return { repository: image, tag: 'latest' };
|
||||
}
|
||||
|
||||
function mapStatus(status: string): 'running' | 'stopped' | 'starting' | 'error' {
|
||||
switch (status) {
|
||||
case 'running': return 'running';
|
||||
case 'starting': return 'starting';
|
||||
case 'failed': return 'error';
|
||||
case 'stopped':
|
||||
case 'stopping':
|
||||
default: return 'stopped';
|
||||
}
|
||||
}
|
||||
|
||||
function toServiceDetail(service: interfaces.data.IService) {
|
||||
const parsed = parseImageString(service.image);
|
||||
return {
|
||||
name: service.name,
|
||||
status: mapStatus(service.status),
|
||||
image: service.image,
|
||||
port: service.port,
|
||||
domain: service.domain || null,
|
||||
containerId: service.containerID || '',
|
||||
created: service.createdAt ? new Date(service.createdAt).toLocaleString() : '-',
|
||||
updated: service.updatedAt ? new Date(service.updatedAt).toLocaleString() : '-',
|
||||
registry: service.useOneboxRegistry ? 'Onebox Registry' : (service.registry || 'Docker Hub'),
|
||||
repository: service.registryRepository || parsed.repository,
|
||||
tag: service.registryImageTag || parsed.tag,
|
||||
};
|
||||
}
|
||||
|
||||
function toServiceStats(stats: interfaces.data.IContainerStats) {
|
||||
return {
|
||||
cpu: stats.cpuPercent,
|
||||
memory: formatBytes(stats.memoryUsed),
|
||||
memoryLimit: formatBytes(stats.memoryLimit),
|
||||
networkIn: formatBytes(stats.networkRx),
|
||||
networkOut: formatBytes(stats.networkTx),
|
||||
};
|
||||
}
|
||||
|
||||
function parseLogs(logs: any): Array<{ timestamp: string; message: string }> {
|
||||
if (Array.isArray(logs)) {
|
||||
return logs.map((entry: any) => ({
|
||||
timestamp: entry.timestamp ? String(entry.timestamp) : '',
|
||||
message: entry.message || String(entry),
|
||||
}));
|
||||
}
|
||||
if (typeof logs === 'string' && logs.trim()) {
|
||||
return logs.split('\n').filter((line: string) => line.trim()).map((line: string) => {
|
||||
const match = line.match(/^(\d{4}-\d{2}-\d{2}T[\d:.]+Z?)\s+(.*)/);
|
||||
if (match) {
|
||||
return { timestamp: match[1], message: match[2] };
|
||||
}
|
||||
return { timestamp: '', message: line };
|
||||
});
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
const defaultStats = { cpu: 0, memory: '0 B', memoryLimit: '0 B', networkIn: '0 B', networkOut: '0 B' };
|
||||
|
||||
@customElement('ob-view-services')
|
||||
export class ObViewServices extends DeesElement {
|
||||
@state()
|
||||
@@ -86,10 +172,16 @@ export class ObViewServices extends DeesElement {
|
||||
}
|
||||
|
||||
private renderListView(): TemplateResult {
|
||||
const mappedServices = this.servicesState.services.map((s) => ({
|
||||
name: s.name,
|
||||
image: s.image,
|
||||
domain: s.domain || null,
|
||||
status: mapStatus(s.status),
|
||||
}));
|
||||
return html`
|
||||
<ob-sectionheading>Services</ob-sectionheading>
|
||||
<sz-services-list-view
|
||||
.services=${this.servicesState.services}
|
||||
.services=${mappedServices}
|
||||
@service-click=${(e: CustomEvent) => {
|
||||
this.selectedServiceName = e.detail.name || e.detail.service?.name;
|
||||
appstate.servicesStatePart.dispatchAction(appstate.fetchServiceAction, {
|
||||
@@ -98,6 +190,9 @@ export class ObViewServices extends DeesElement {
|
||||
appstate.servicesStatePart.dispatchAction(appstate.fetchServiceLogsAction, {
|
||||
name: this.selectedServiceName,
|
||||
});
|
||||
appstate.servicesStatePart.dispatchAction(appstate.fetchServiceStatsAction, {
|
||||
name: this.selectedServiceName,
|
||||
});
|
||||
this.currentView = 'detail';
|
||||
}}
|
||||
@service-action=${(e: CustomEvent) => this.handleServiceAction(e)}
|
||||
@@ -124,12 +219,19 @@ export class ObViewServices extends DeesElement {
|
||||
}
|
||||
|
||||
private renderDetailView(): TemplateResult {
|
||||
const service = this.servicesState.currentService;
|
||||
const transformedService = service ? toServiceDetail(service) : null;
|
||||
const transformedStats = this.servicesState.currentServiceStats
|
||||
? toServiceStats(this.servicesState.currentServiceStats)
|
||||
: defaultStats;
|
||||
const transformedLogs = parseLogs(this.servicesState.currentServiceLogs);
|
||||
|
||||
return html`
|
||||
<ob-sectionheading>Service Details</ob-sectionheading>
|
||||
<sz-service-detail-view
|
||||
.service=${this.servicesState.currentService}
|
||||
.logs=${this.servicesState.currentServiceLogs}
|
||||
.stats=${this.servicesState.currentServiceStats}
|
||||
.service=${transformedService}
|
||||
.logs=${transformedLogs}
|
||||
.stats=${transformedStats}
|
||||
@back=${() => {
|
||||
this.currentView = 'list';
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user