');
diff --git a/frontend-modern/src/features/proxmox/ProxmoxCephClusterDrawer.tsx b/frontend-modern/src/features/proxmox/ProxmoxCephClusterDrawer.tsx
index b3bf0db42e..63baca9b1e 100644
--- a/frontend-modern/src/features/proxmox/ProxmoxCephClusterDrawer.tsx
+++ b/frontend-modern/src/features/proxmox/ProxmoxCephClusterDrawer.tsx
@@ -3,6 +3,20 @@ import XIcon from 'lucide-solid/icons/x';
import { Card } from '@/components/shared/Card';
import { ProgressBar } from '@/components/shared/ProgressBar';
import { StatusDot } from '@/components/shared/StatusDot';
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from '@/components/shared/Table';
+import {
+ PLATFORM_TABLE_BODY_CLASS,
+ PLATFORM_TABLE_HEADER_ROW_CLASS,
+ getPlatformTableCellClassForKind,
+ getPlatformTableHeadClassForKind,
+} from '@/features/platformPage/sharedPlatformPage';
import type { StatusIndicatorVariant } from '@/utils/status';
import { formatBytes } from '@/utils/format';
import { getMetricColorClass } from '@/utils/metricThresholds';
@@ -48,11 +62,7 @@ function capacityToneFor(percent: number): string {
return getMetricColorClass(percent, 'disk');
}
-function CapacityBar(props: {
- used: number;
- total: number;
- percent: number;
-}) {
+function CapacityBar(props: { used: number; total: number; percent: number }) {
const clamped = Math.max(0, Math.min(100, props.percent));
return (
-
-
+
@@ -146,40 +151,58 @@ export const ProxmoxCephClusterDrawer: Component<{
when={pools().length > 0}
fallback={No pools reported.
}
>
-
-
-
- | Pool |
- Objects |
- Stored |
- Avail |
- Used |
-
-
-
+
+
+
+
+
{(pool) => (
-
- | {pool.name || '—'} |
-
+
+
+ {pool.name || '—'}
+
+
{pool.objects.toLocaleString()}
- |
-
+
+
{formatBytes(pool.storedBytes)}
- |
-
+
+
{formatBytes(pool.availableBytes)}
- |
-
+
+
- |
-
+
+
)}
-
-
+
+
@@ -194,38 +217,59 @@ export const ProxmoxCephClusterDrawer: Component<{
when={services().length > 0}
fallback={
No services reported.
}
>
-
-
-
- | Service |
- Status |
- Running |
- Total |
-
-
-
+
+
+
+
+
{(svc) => {
const cls = classifyService(svc);
return (
-
- |
+
+
{svc.type}
- |
-
+
+
-
- {cls.label}
+
+
+ {cls.label}
+
- |
- {svc.running} |
- {svc.total} |
-
+
+
+ {svc.running}
+
+
+ {svc.total}
+
+
);
}}
-
-
+
+
diff --git a/frontend-modern/src/features/proxmox/ProxmoxMailGatewayDrawer.tsx b/frontend-modern/src/features/proxmox/ProxmoxMailGatewayDrawer.tsx
index 18d4b969b4..cc88005856 100644
--- a/frontend-modern/src/features/proxmox/ProxmoxMailGatewayDrawer.tsx
+++ b/frontend-modern/src/features/proxmox/ProxmoxMailGatewayDrawer.tsx
@@ -1,23 +1,26 @@
-import {
- For,
- Show,
- createMemo,
- createResource,
- type Component,
-} from 'solid-js';
+import { For, Show, createMemo, createResource, type Component } from 'solid-js';
import XIcon from 'lucide-solid/icons/x';
import { Card } from '@/components/shared/Card';
import { StatusDot } from '@/components/shared/StatusDot';
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from '@/components/shared/Table';
+import {
+ PLATFORM_TABLE_BODY_CLASS,
+ PLATFORM_TABLE_HEADER_ROW_CLASS,
+ getPlatformTableCellClassForKind,
+ getPlatformTableHeadClassForKind,
+} from '@/features/platformPage/sharedPlatformPage';
import type { StatusIndicatorVariant } from '@/utils/status';
import { formatBytes } from '@/utils/format';
import { asTrimmedString } from '@/utils/stringUtils';
import { apiFetch } from '@/utils/apiClient';
-import type {
- PMGInstance,
- PMGNodeStatus,
- PMGQueueStatus,
- PMGSpamBucket,
-} from '@/types/api';
+import type { PMGInstance, PMGNodeStatus, PMGQueueStatus, PMGSpamBucket } from '@/types/api';
import type { Resource } from '@/types/resource';
// Inline drawer for a single Proxmox Mail Gateway instance. Drops the
@@ -113,9 +116,7 @@ function StackedBar(props: { segments: StackedSegment[]; ariaLabel: string }) {
0}
- fallback={
-
- }
+ fallback={}
>
- Math.max(0, ...props.buckets.map((b) => Math.max(0, b.count))),
- );
+ const maxCount = createMemo(() => Math.max(0, ...props.buckets.map((b) => Math.max(0, b.count))));
return (
-
+
{(bucket) => {
const heightPct = maxCount() > 0 ? (bucket.count / maxCount()) * 100 : 0;
@@ -219,15 +214,9 @@ function InOutBar(props: {
class="relative flex h-4 w-full overflow-hidden rounded bg-surface-hover"
title={`In ${format(props.inValue)} · Out ${format(props.outValue)}`}
>
-
+
-
+
In {format(props.inValue)}
Out {format(props.outValue)}
@@ -245,10 +234,7 @@ export const ProxmoxMailGatewayDrawer: Component<{
const meta = props.instanceRow.pmg;
return asTrimmedString(meta?.instanceId) || props.instanceRow.id;
};
- const [instance, { refetch }] = createResource(
- id,
- fetchPMGInstance,
- );
+ const [instance, { refetch }] = createResource(id, fetchPMGInstance);
const stats = createMemo(() => instance()?.mailStats);
const quarantine = createMemo(() => instance()?.quarantine);
@@ -268,9 +254,7 @@ export const ProxmoxMailGatewayDrawer: Component<{
asTrimmedString(props.instanceRow.name) ||
props.instanceRow.id;
const version = () =>
- asTrimmedString(instance()?.version) ||
- asTrimmedString(props.instanceRow.pmg?.version) ||
- '—';
+ asTrimmedString(instance()?.version) || asTrimmedString(props.instanceRow.pmg?.version) || '—';
const hostname = () => asTrimmedString(instance()?.host);
const inboundSegments = createMemo(() => {
@@ -487,25 +471,33 @@ export const ProxmoxMailGatewayDrawer: Component<{
when={nodes().length > 0}
fallback={No cluster nodes reported.
}
>
-
-
-
- | Node |
- Role |
- Uptime |
- Load |
- Queue |
- Oldest |
-
-
-
+
+
+
+
+
{(node) => {
const cls = classifyNode(node);
const queue = queueCell(node.queueStatus);
return (
-
- |
+
+
- |
-
+
+
{asTrimmedString(node.role) || '—'}
- |
-
+
+
{formatUptime(node.uptime)}
- |
-
+
+
{asTrimmedString(node.loadAvg) || '—'}
- |
-
+
+
{queue.count}
{queue.label}
- |
-
+
+
{node.queueStatus?.oldestAge
? formatAge(node.queueStatus.oldestAge)
: '—'}
- |
-
+
+
);
}}
-
-
+
+
@@ -555,51 +557,66 @@ export const ProxmoxMailGatewayDrawer: Component<{
Top domains
-
- top {topDomains().length}
-
+ top {topDomains().length}
0}
fallback={No domain stats reported.
}
>
-
-
-
- | Domain |
- Mail |
- Spam |
- Virus |
- Bytes |
-
-
-
+
+
+
+
+
{(domain) => (
-
- |
+
+
+ {domain.domain || '—'}
+
+
+
- {domain.domain || '—'}
- |
-
{formatNumber(domain.mailCount)}
- |
-
+
+
{formatNumber(domain.spamCount)}
- |
-
+
+
{formatNumber(domain.virusCount)}
- |
-
+
+
{domain.bytes && domain.bytes > 0 ? formatBytes(domain.bytes) : '—'}
- |
-
+
+
)}
-
-
+
+