feat(appstore): add service volumes and published ports
This commit is contained in:
@@ -12,6 +12,20 @@ import {
|
||||
type TemplateResult,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
const byteUnits = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
|
||||
function getByteUnitIndex(bytes: number): number {
|
||||
if (!bytes || bytes === 0) return 0;
|
||||
return Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), byteUnits.length - 1);
|
||||
}
|
||||
|
||||
function formatBytes(bytes: number, forcedUnitIndex?: number): string {
|
||||
if ((!bytes || bytes === 0) && forcedUnitIndex === undefined) return '0 B';
|
||||
const unitIndex = forcedUnitIndex ?? getByteUnitIndex(bytes);
|
||||
const value = bytes / Math.pow(1024, unitIndex);
|
||||
return `${value.toFixed(1)} ${byteUnits[unitIndex]}`;
|
||||
}
|
||||
|
||||
@customElement('ob-view-dashboard')
|
||||
export class ObViewDashboard extends DeesElement {
|
||||
@state()
|
||||
@@ -69,7 +83,42 @@ export class ObViewDashboard extends DeesElement {
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
shared.viewHostCss,
|
||||
css``,
|
||||
css`
|
||||
.dashboard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
.services-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.services-grid > * {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.services-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
async connectedCallback() {
|
||||
@@ -79,6 +128,7 @@ export class ObViewDashboard extends DeesElement {
|
||||
appstate.servicesStatePart.dispatchAction(appstate.fetchServicesAction, null),
|
||||
appstate.servicesStatePart.dispatchAction(appstate.fetchPlatformServicesAction, null),
|
||||
appstate.networkStatePart.dispatchAction(appstate.fetchNetworkStatsAction, null),
|
||||
appstate.networkStatePart.dispatchAction(appstate.fetchTrafficStatsAction, null),
|
||||
appstate.networkStatePart.dispatchAction(appstate.fetchCertificatesAction, null),
|
||||
]);
|
||||
}
|
||||
@@ -88,10 +138,15 @@ export class ObViewDashboard extends DeesElement {
|
||||
const services = this.servicesState.services;
|
||||
const platformServices = this.servicesState.platformServices;
|
||||
const networkStats = this.networkState.stats;
|
||||
const trafficStats = this.networkState.trafficStats;
|
||||
const certificates = this.networkState.certificates;
|
||||
const statusCounts = trafficStats?.statusCounts || {};
|
||||
|
||||
const runningServices = services.filter((s) => s.status === 'running').length;
|
||||
const stoppedServices = services.filter((s) => s.status === 'stopped').length;
|
||||
const memoryUnitIndex = getByteUnitIndex(
|
||||
status?.docker?.memoryTotal || status?.docker?.memoryUsage || 0,
|
||||
);
|
||||
|
||||
const validCerts = certificates.filter((c) => c.isValid).length;
|
||||
const expiringCerts = certificates.filter(
|
||||
@@ -99,65 +154,98 @@ export class ObViewDashboard extends DeesElement {
|
||||
).length;
|
||||
const expiredCerts = certificates.filter((c) => !c.isValid).length;
|
||||
|
||||
const dashboardData = {
|
||||
cluster: {
|
||||
totalServices: services.length,
|
||||
running: runningServices,
|
||||
stopped: stoppedServices,
|
||||
dockerStatus: status?.docker?.running ? 'running' as const : 'stopped' as const,
|
||||
},
|
||||
resourceUsage: {
|
||||
cpu: status?.docker?.cpuUsage || 0,
|
||||
memoryUsed: formatBytes(status?.docker?.memoryUsage || 0, memoryUnitIndex),
|
||||
memoryTotal: formatBytes(status?.docker?.memoryTotal || 0, memoryUnitIndex),
|
||||
networkIn: formatBytes(status?.docker?.networkIn || 0),
|
||||
networkOut: formatBytes(status?.docker?.networkOut || 0),
|
||||
topConsumers: [],
|
||||
},
|
||||
platformServices: platformServices
|
||||
.filter((ps) => ps.status === 'running' || ps.status === 'starting' || ps.status === 'stopping' || ps.isCore)
|
||||
.map((ps) => ({
|
||||
name: ps.displayName,
|
||||
status: ps.status === 'running' ? 'Running' : ps.status === 'starting' ? 'Starting...' : ps.status === 'stopping' ? 'Stopping...' : 'Stopped',
|
||||
running: ps.status === 'running',
|
||||
})),
|
||||
traffic: {
|
||||
requests: trafficStats?.requestCount || 0,
|
||||
errors: trafficStats?.errorCount || 0,
|
||||
errorPercent: trafficStats?.errorRate || 0,
|
||||
avgResponse: trafficStats?.avgResponseTime || 0,
|
||||
reqPerMin: trafficStats?.requestsPerMinute || 0,
|
||||
status2xx: statusCounts['2xx'] || 0,
|
||||
status3xx: statusCounts['3xx'] || 0,
|
||||
status4xx: statusCounts['4xx'] || 0,
|
||||
status5xx: statusCounts['5xx'] || 0,
|
||||
},
|
||||
proxy: {
|
||||
httpPort: String(networkStats?.proxy?.httpPort || 80),
|
||||
httpsPort: String(networkStats?.proxy?.httpsPort || 443),
|
||||
httpActive: networkStats?.proxy?.running || false,
|
||||
httpsActive: networkStats?.proxy?.running || false,
|
||||
routeCount: String(networkStats?.proxy?.routes || 0),
|
||||
},
|
||||
certificates: {
|
||||
valid: validCerts,
|
||||
expiring: expiringCerts,
|
||||
expired: expiredCerts,
|
||||
},
|
||||
dnsConfigured: status?.dns?.configured || false,
|
||||
acmeConfigured: status?.ssl?.configured || false,
|
||||
quickActions: [
|
||||
{ label: 'Deploy Service', icon: 'lucide:Plus', primary: true },
|
||||
{ label: 'Add Domain', icon: 'lucide:Globe' },
|
||||
{ label: 'View Logs', icon: 'lucide:FileText' },
|
||||
],
|
||||
};
|
||||
|
||||
return html`
|
||||
<ob-sectionheading>Dashboard</ob-sectionheading>
|
||||
<sz-dashboard-view
|
||||
.data=${{
|
||||
cluster: {
|
||||
totalServices: services.length,
|
||||
running: runningServices,
|
||||
stopped: stoppedServices,
|
||||
dockerStatus: status?.docker?.running ? 'running' : 'stopped',
|
||||
},
|
||||
resourceUsage: {
|
||||
cpu: status?.docker?.cpuUsage || 0,
|
||||
memoryUsed: status?.docker?.memoryUsage || 0,
|
||||
memoryTotal: status?.docker?.memoryTotal || 0,
|
||||
networkIn: status?.docker?.networkIn || 0,
|
||||
networkOut: status?.docker?.networkOut || 0,
|
||||
topConsumers: [],
|
||||
},
|
||||
platformServices: platformServices
|
||||
.filter((ps) => ps.status === 'running' || ps.status === 'starting' || ps.status === 'stopping' || ps.isCore)
|
||||
.map((ps) => ({
|
||||
name: ps.displayName,
|
||||
status: ps.status === 'running' ? 'Running' : ps.status === 'starting' ? 'Starting...' : ps.status === 'stopping' ? 'Stopping...' : 'Stopped',
|
||||
running: ps.status === 'running',
|
||||
})),
|
||||
traffic: {
|
||||
requests: 0,
|
||||
errors: 0,
|
||||
errorPercent: 0,
|
||||
avgResponse: 0,
|
||||
reqPerMin: 0,
|
||||
status2xx: 0,
|
||||
status3xx: 0,
|
||||
status4xx: 0,
|
||||
status5xx: 0,
|
||||
},
|
||||
proxy: {
|
||||
httpPort: networkStats?.proxy?.httpPort || 80,
|
||||
httpsPort: networkStats?.proxy?.httpsPort || 443,
|
||||
httpActive: networkStats?.proxy?.running || false,
|
||||
httpsActive: networkStats?.proxy?.running || false,
|
||||
routeCount: networkStats?.proxy?.routes || 0,
|
||||
},
|
||||
certificates: {
|
||||
valid: validCerts,
|
||||
expiring: expiringCerts,
|
||||
expired: expiredCerts,
|
||||
},
|
||||
dnsConfigured: true,
|
||||
acmeConfigured: true,
|
||||
quickActions: [
|
||||
{ label: 'Deploy Service', icon: 'lucide:Plus', primary: true },
|
||||
{ label: 'Add Domain', icon: 'lucide:Globe' },
|
||||
{ label: 'View Logs', icon: 'lucide:FileText' },
|
||||
],
|
||||
}}
|
||||
@action-click=${(e: CustomEvent) => this.handleQuickAction(e)}
|
||||
@service-click=${(e: CustomEvent) => this.handlePlatformServiceClick(e)}
|
||||
></sz-dashboard-view>
|
||||
<div class="dashboard">
|
||||
<section class="section">
|
||||
<h2 class="section-title">Cluster Overview</h2>
|
||||
<sz-status-grid-cluster .stats=${dashboardData.cluster}></sz-status-grid-cluster>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<h2 class="section-title">Services & Resources</h2>
|
||||
<div class="services-grid">
|
||||
<sz-resource-usage-card .data=${dashboardData.resourceUsage}></sz-resource-usage-card>
|
||||
<sz-platform-services-card
|
||||
.services=${dashboardData.platformServices}
|
||||
@service-click=${(e: CustomEvent) => this.handlePlatformServiceClick(e)}
|
||||
></sz-platform-services-card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<h2 class="section-title">Network & Traffic</h2>
|
||||
<sz-status-grid-network
|
||||
.traffic=${dashboardData.traffic}
|
||||
.proxy=${dashboardData.proxy}
|
||||
.certificates=${dashboardData.certificates}
|
||||
></sz-status-grid-network>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<h2 class="section-title">Infrastructure</h2>
|
||||
<sz-status-grid-infra
|
||||
?dnsConfigured=${dashboardData.dnsConfigured}
|
||||
?acmeConfigured=${dashboardData.acmeConfigured}
|
||||
.actions=${dashboardData.quickActions}
|
||||
@action-click=${(e: CustomEvent) => this.handleQuickAction(e)}
|
||||
></sz-status-grid-infra>
|
||||
</section>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user