mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-15 08:33:03 +00:00
Normalize Proxmox drawer tables
This commit is contained in:
+21
@@ -7,7 +7,9 @@ import kubernetesDeploymentsTableSource from '@/features/kubernetes/KubernetesDe
|
||||
import kubernetesNodesTableSource from '@/features/kubernetes/KubernetesNodesTable.tsx?raw';
|
||||
import kubernetesPageSurfaceSource from '@/features/kubernetes/KubernetesPageSurface.tsx?raw';
|
||||
import proxmoxBackupsTableSource from '@/features/proxmox/ProxmoxBackupsTable.tsx?raw';
|
||||
import proxmoxCephClusterDrawerSource from '@/features/proxmox/ProxmoxCephClusterDrawer.tsx?raw';
|
||||
import proxmoxCephTableSource from '@/features/proxmox/ProxmoxCephTable.tsx?raw';
|
||||
import proxmoxMailGatewayDrawerSource from '@/features/proxmox/ProxmoxMailGatewayDrawer.tsx?raw';
|
||||
import proxmoxMailGatewayTableSource from '@/features/proxmox/ProxmoxMailGatewayTable.tsx?raw';
|
||||
import proxmoxNodesTableSource from '@/features/proxmox/ProxmoxNodesTable.tsx?raw';
|
||||
import proxmoxPageSurfaceSource from '@/features/proxmox/ProxmoxPageSurface.tsx?raw';
|
||||
@@ -60,6 +62,11 @@ const proxmoxBespokeTableSources = [
|
||||
proxmoxReplicationTableSource,
|
||||
];
|
||||
|
||||
const proxmoxInlineDetailTableSources = [
|
||||
proxmoxCephClusterDrawerSource,
|
||||
proxmoxMailGatewayDrawerSource,
|
||||
];
|
||||
|
||||
describe('platform overview layout guardrails', () => {
|
||||
it('keeps platform inventory tables on the shared dense table styling contract', () => {
|
||||
expect(sharedPlatformPageSource).toContain('PLATFORM_TABLE_CARD_CLASS');
|
||||
@@ -111,6 +118,20 @@ describe('platform overview layout guardrails', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps Proxmox inline detail tables on shared platform table primitives', () => {
|
||||
for (const source of proxmoxInlineDetailTableSources) {
|
||||
expect(source).toContain("from '@/components/shared/Table'");
|
||||
expect(source).toContain('PLATFORM_TABLE_HEADER_ROW_CLASS');
|
||||
expect(source).toContain('PLATFORM_TABLE_BODY_CLASS');
|
||||
expect(source).toContain('getPlatformTableHeadClassForKind');
|
||||
expect(source).toContain('getPlatformTableCellClassForKind');
|
||||
expect(source).not.toContain('<table');
|
||||
expect(source).not.toContain('<thead');
|
||||
expect(source).not.toContain('<tbody');
|
||||
expect(source).not.toContain('divide-y divide-border-subtle');
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps provider overview pages in the parent-table plus child-inventory stack', () => {
|
||||
for (const source of overviewSurfaceSources) {
|
||||
expect(source).toContain('<div class="space-y-4">');
|
||||
|
||||
@@ -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 (
|
||||
<ProgressBar
|
||||
@@ -127,12 +137,7 @@ export const ProxmoxCephClusterDrawer: Component<{
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<CapacityBar
|
||||
used={usedCapacity()}
|
||||
total={totalCapacity()}
|
||||
percent={usagePercent()}
|
||||
/>
|
||||
|
||||
<CapacityBar used={usedCapacity()} total={totalCapacity()} percent={usagePercent()} />
|
||||
|
||||
<div class="grid gap-3 lg:grid-cols-2">
|
||||
<Card padding="md">
|
||||
@@ -146,40 +151,58 @@ export const ProxmoxCephClusterDrawer: Component<{
|
||||
when={pools().length > 0}
|
||||
fallback={<p class="text-xs text-muted">No pools reported.</p>}
|
||||
>
|
||||
<table class="w-full text-xs">
|
||||
<thead class="text-[10px] uppercase tracking-wide text-muted">
|
||||
<tr>
|
||||
<th class="pb-2 text-left font-medium">Pool</th>
|
||||
<th class="pb-2 text-right font-medium">Objects</th>
|
||||
<th class="pb-2 text-right font-medium">Stored</th>
|
||||
<th class="pb-2 text-right font-medium">Avail</th>
|
||||
<th class="pb-2 text-right font-medium">Used</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-border-subtle">
|
||||
<Table class="text-xs">
|
||||
<TableHeader>
|
||||
<TableRow class={PLATFORM_TABLE_HEADER_ROW_CLASS}>
|
||||
<TableHead class={getPlatformTableHeadClassForKind('name')}>Pool</TableHead>
|
||||
<TableHead class={getPlatformTableHeadClassForKind('numeric-value')}>
|
||||
Objects
|
||||
</TableHead>
|
||||
<TableHead class={getPlatformTableHeadClassForKind('numeric-value')}>
|
||||
Stored
|
||||
</TableHead>
|
||||
<TableHead class={getPlatformTableHeadClassForKind('numeric-value')}>
|
||||
Avail
|
||||
</TableHead>
|
||||
<TableHead class={getPlatformTableHeadClassForKind('numeric-value')}>
|
||||
Used
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody class={PLATFORM_TABLE_BODY_CLASS}>
|
||||
<For each={pools()}>
|
||||
{(pool) => (
|
||||
<tr>
|
||||
<td class="py-2 font-medium text-base-content">{pool.name || '—'}</td>
|
||||
<td class="py-2 text-right text-base-content tabular-nums">
|
||||
<TableRow>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('name')} font-medium text-base-content`}
|
||||
>
|
||||
{pool.name || '—'}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content tabular-nums`}
|
||||
>
|
||||
{pool.objects.toLocaleString()}
|
||||
</td>
|
||||
<td class="py-2 text-right text-base-content tabular-nums">
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content tabular-nums`}
|
||||
>
|
||||
{formatBytes(pool.storedBytes)}
|
||||
</td>
|
||||
<td class="py-2 text-right text-muted tabular-nums">
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-muted tabular-nums`}
|
||||
>
|
||||
{formatBytes(pool.availableBytes)}
|
||||
</td>
|
||||
<td class="py-2 text-right">
|
||||
</TableCell>
|
||||
<TableCell class={getPlatformTableCellClassForKind('numeric-value')}>
|
||||
<div class="flex items-center justify-end">
|
||||
<PoolUsageBar percent={pool.percentUsed} />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Show>
|
||||
</Card>
|
||||
|
||||
@@ -194,38 +217,59 @@ export const ProxmoxCephClusterDrawer: Component<{
|
||||
when={services().length > 0}
|
||||
fallback={<p class="text-xs text-muted">No services reported.</p>}
|
||||
>
|
||||
<table class="w-full text-xs">
|
||||
<thead class="text-[10px] uppercase tracking-wide text-muted">
|
||||
<tr>
|
||||
<th class="pb-2 text-left font-medium">Service</th>
|
||||
<th class="pb-2 text-left font-medium">Status</th>
|
||||
<th class="pb-2 text-right font-medium">Running</th>
|
||||
<th class="pb-2 text-right font-medium">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-border-subtle">
|
||||
<Table class="text-xs">
|
||||
<TableHeader>
|
||||
<TableRow class={PLATFORM_TABLE_HEADER_ROW_CLASS}>
|
||||
<TableHead class={getPlatformTableHeadClassForKind('name')}>Service</TableHead>
|
||||
<TableHead class={getPlatformTableHeadClassForKind('text')}>Status</TableHead>
|
||||
<TableHead class={getPlatformTableHeadClassForKind('numeric-value')}>
|
||||
Running
|
||||
</TableHead>
|
||||
<TableHead class={getPlatformTableHeadClassForKind('numeric-value')}>
|
||||
Total
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody class={PLATFORM_TABLE_BODY_CLASS}>
|
||||
<For each={services()}>
|
||||
{(svc) => {
|
||||
const cls = classifyService(svc);
|
||||
return (
|
||||
<tr>
|
||||
<td class="py-2 font-mono text-[11px] font-semibold text-base-content uppercase">
|
||||
<TableRow>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('name')} font-mono text-[11px] font-semibold text-base-content uppercase`}
|
||||
>
|
||||
{svc.type}
|
||||
</td>
|
||||
<td class="py-2">
|
||||
</TableCell>
|
||||
<TableCell class={getPlatformTableCellClassForKind('text')}>
|
||||
<div class="flex items-center gap-2">
|
||||
<StatusDot size="sm" variant={cls.variant} title={cls.label} ariaHidden />
|
||||
<span class="text-[11px] font-medium text-base-content">{cls.label}</span>
|
||||
<StatusDot
|
||||
size="sm"
|
||||
variant={cls.variant}
|
||||
title={cls.label}
|
||||
ariaHidden
|
||||
/>
|
||||
<span class="text-[11px] font-medium text-base-content">
|
||||
{cls.label}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-2 text-right text-base-content tabular-nums">{svc.running}</td>
|
||||
<td class="py-2 text-right text-muted tabular-nums">{svc.total}</td>
|
||||
</tr>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content tabular-nums`}
|
||||
>
|
||||
{svc.running}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-muted tabular-nums`}
|
||||
>
|
||||
{svc.total}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Show>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -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 }) {
|
||||
<div class="space-y-2">
|
||||
<Show
|
||||
when={total() > 0}
|
||||
fallback={
|
||||
<div class="relative h-2.5 w-full overflow-hidden rounded-full bg-surface-alt" />
|
||||
}
|
||||
fallback={<div class="relative h-2.5 w-full overflow-hidden rounded-full bg-surface-alt" />}
|
||||
>
|
||||
<div
|
||||
class="relative flex h-2.5 w-full overflow-hidden rounded-full bg-surface-alt"
|
||||
@@ -162,15 +163,9 @@ function StackedBar(props: { segments: StackedSegment[]; ariaLabel: string }) {
|
||||
// metric severity palette: 0 = clean = normal, mid-score = warning,
|
||||
// high-score = critical.
|
||||
function SpamHistogram(props: { buckets: PMGSpamBucket[] }) {
|
||||
const maxCount = createMemo(() =>
|
||||
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 (
|
||||
<div
|
||||
class="flex items-end gap-1 h-16"
|
||||
role="img"
|
||||
aria-label="Spam score distribution"
|
||||
>
|
||||
<div class="flex items-end gap-1 h-16" role="img" aria-label="Spam score distribution">
|
||||
<For each={props.buckets}>
|
||||
{(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)}`}
|
||||
>
|
||||
<div
|
||||
class="relative bg-blue-500/60"
|
||||
style={{ width: `${inWidth}%` }}
|
||||
/>
|
||||
<div class="relative bg-blue-500/60" style={{ width: `${inWidth}%` }} />
|
||||
<div class="w-px bg-surface" />
|
||||
<div
|
||||
class="relative bg-purple-500/60"
|
||||
style={{ width: `${outWidth}%` }}
|
||||
/>
|
||||
<div class="relative bg-purple-500/60" style={{ width: `${outWidth}%` }} />
|
||||
<span class="pointer-events-none absolute inset-0 flex items-center justify-between px-1.5 text-[10px] font-semibold text-base-content leading-none tabular-nums">
|
||||
<span>In {format(props.inValue)}</span>
|
||||
<span>Out {format(props.outValue)}</span>
|
||||
@@ -245,10 +234,7 @@ export const ProxmoxMailGatewayDrawer: Component<{
|
||||
const meta = props.instanceRow.pmg;
|
||||
return asTrimmedString(meta?.instanceId) || props.instanceRow.id;
|
||||
};
|
||||
const [instance, { refetch }] = createResource<PMGInstance | null, string>(
|
||||
id,
|
||||
fetchPMGInstance,
|
||||
);
|
||||
const [instance, { refetch }] = createResource<PMGInstance | null, string>(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<StackedSegment[]>(() => {
|
||||
@@ -487,25 +471,33 @@ export const ProxmoxMailGatewayDrawer: Component<{
|
||||
when={nodes().length > 0}
|
||||
fallback={<p class="text-xs text-muted">No cluster nodes reported.</p>}
|
||||
>
|
||||
<table class="w-full text-xs">
|
||||
<thead class="text-[10px] uppercase tracking-wide text-muted">
|
||||
<tr>
|
||||
<th class="pb-2 text-left font-medium">Node</th>
|
||||
<th class="pb-2 text-left font-medium">Role</th>
|
||||
<th class="pb-2 text-right font-medium">Uptime</th>
|
||||
<th class="pb-2 text-right font-medium">Load</th>
|
||||
<th class="pb-2 text-right font-medium">Queue</th>
|
||||
<th class="pb-2 text-right font-medium">Oldest</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-border-subtle">
|
||||
<Table class="text-xs">
|
||||
<TableHeader>
|
||||
<TableRow class={PLATFORM_TABLE_HEADER_ROW_CLASS}>
|
||||
<TableHead class={getPlatformTableHeadClassForKind('name')}>Node</TableHead>
|
||||
<TableHead class={getPlatformTableHeadClassForKind('text')}>Role</TableHead>
|
||||
<TableHead class={getPlatformTableHeadClassForKind('numeric-value')}>
|
||||
Uptime
|
||||
</TableHead>
|
||||
<TableHead class={getPlatformTableHeadClassForKind('numeric-value')}>
|
||||
Load
|
||||
</TableHead>
|
||||
<TableHead class={getPlatformTableHeadClassForKind('numeric-value')}>
|
||||
Queue
|
||||
</TableHead>
|
||||
<TableHead class={getPlatformTableHeadClassForKind('numeric-value')}>
|
||||
Oldest
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody class={PLATFORM_TABLE_BODY_CLASS}>
|
||||
<For each={nodes()}>
|
||||
{(node) => {
|
||||
const cls = classifyNode(node);
|
||||
const queue = queueCell(node.queueStatus);
|
||||
return (
|
||||
<tr>
|
||||
<td class="py-2">
|
||||
<TableRow>
|
||||
<TableCell class={getPlatformTableCellClassForKind('name')}>
|
||||
<div class="flex items-center gap-2">
|
||||
<StatusDot
|
||||
size="sm"
|
||||
@@ -517,17 +509,25 @@ export const ProxmoxMailGatewayDrawer: Component<{
|
||||
{node.name || '—'}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-2 text-base-content text-[11px]">
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} text-base-content text-[11px]`}
|
||||
>
|
||||
{asTrimmedString(node.role) || '—'}
|
||||
</td>
|
||||
<td class="py-2 text-right text-base-content">
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content`}
|
||||
>
|
||||
{formatUptime(node.uptime)}
|
||||
</td>
|
||||
<td class="py-2 text-right text-base-content tabular-nums text-[11px]">
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content tabular-nums text-[11px]`}
|
||||
>
|
||||
{asTrimmedString(node.loadAvg) || '—'}
|
||||
</td>
|
||||
<td class="py-2 text-right tabular-nums">
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} tabular-nums`}
|
||||
>
|
||||
<span class="text-base-content font-semibold">{queue.count}</span>
|
||||
<span
|
||||
class="ml-1 text-muted text-[10px] font-mono"
|
||||
@@ -535,18 +535,20 @@ export const ProxmoxMailGatewayDrawer: Component<{
|
||||
>
|
||||
{queue.label}
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-2 text-right text-base-content">
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content`}
|
||||
>
|
||||
{node.queueStatus?.oldestAge
|
||||
? formatAge(node.queueStatus.oldestAge)
|
||||
: '—'}
|
||||
</td>
|
||||
</tr>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Show>
|
||||
</Card>
|
||||
|
||||
@@ -555,51 +557,66 @@ export const ProxmoxMailGatewayDrawer: Component<{
|
||||
<h4 class="text-xs font-semibold uppercase tracking-wide text-muted">
|
||||
Top domains
|
||||
</h4>
|
||||
<span class="text-[10px] text-muted tabular-nums">
|
||||
top {topDomains().length}
|
||||
</span>
|
||||
<span class="text-[10px] text-muted tabular-nums">top {topDomains().length}</span>
|
||||
</div>
|
||||
<Show
|
||||
when={topDomains().length > 0}
|
||||
fallback={<p class="text-xs text-muted">No domain stats reported.</p>}
|
||||
>
|
||||
<table class="w-full text-xs">
|
||||
<thead class="text-[10px] uppercase tracking-wide text-muted">
|
||||
<tr>
|
||||
<th class="pb-2 text-left font-medium">Domain</th>
|
||||
<th class="pb-2 text-right font-medium">Mail</th>
|
||||
<th class="pb-2 text-right font-medium">Spam</th>
|
||||
<th class="pb-2 text-right font-medium">Virus</th>
|
||||
<th class="pb-2 text-right font-medium">Bytes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-border-subtle">
|
||||
<Table class="text-xs">
|
||||
<TableHeader>
|
||||
<TableRow class={PLATFORM_TABLE_HEADER_ROW_CLASS}>
|
||||
<TableHead class={getPlatformTableHeadClassForKind('name')}>Domain</TableHead>
|
||||
<TableHead class={getPlatformTableHeadClassForKind('numeric-value')}>
|
||||
Mail
|
||||
</TableHead>
|
||||
<TableHead class={getPlatformTableHeadClassForKind('numeric-value')}>
|
||||
Spam
|
||||
</TableHead>
|
||||
<TableHead class={getPlatformTableHeadClassForKind('numeric-value')}>
|
||||
Virus
|
||||
</TableHead>
|
||||
<TableHead class={getPlatformTableHeadClassForKind('numeric-value')}>
|
||||
Bytes
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody class={PLATFORM_TABLE_BODY_CLASS}>
|
||||
<For each={topDomains()}>
|
||||
{(domain) => (
|
||||
<tr>
|
||||
<td
|
||||
class="py-2 font-mono text-[11px] text-base-content truncate max-w-[14rem]"
|
||||
title={domain.domain}
|
||||
<TableRow>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('name')} font-mono text-[11px] text-base-content`}
|
||||
>
|
||||
<span class="inline-block max-w-[14rem] truncate" title={domain.domain}>
|
||||
{domain.domain || '—'}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content tabular-nums`}
|
||||
>
|
||||
{domain.domain || '—'}
|
||||
</td>
|
||||
<td class="py-2 text-right text-base-content tabular-nums">
|
||||
{formatNumber(domain.mailCount)}
|
||||
</td>
|
||||
<td class="py-2 text-right text-base-content tabular-nums">
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content tabular-nums`}
|
||||
>
|
||||
{formatNumber(domain.spamCount)}
|
||||
</td>
|
||||
<td class="py-2 text-right text-base-content tabular-nums">
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content tabular-nums`}
|
||||
>
|
||||
{formatNumber(domain.virusCount)}
|
||||
</td>
|
||||
<td class="py-2 text-right text-muted tabular-nums">
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-muted tabular-nums`}
|
||||
>
|
||||
{domain.bytes && domain.bytes > 0 ? formatBytes(domain.bytes) : '—'}
|
||||
</td>
|
||||
</tr>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Show>
|
||||
</Card>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user