Clarify dashboard workload and trend states

Refs #1429
This commit is contained in:
rcourtman
2026-04-19 19:18:18 +01:00
parent 29d96aec33
commit 9de093725f
6 changed files with 45 additions and 11 deletions
@@ -85,6 +85,9 @@ export function KPIStrip(props: KPIStripProps) {
<p class="text-2xl font-mono font-semibold text-base-content mt-1">
{props.workloads.total}
</p>
<p class="text-[11px] text-muted mt-0.5">
{workloadsPresentation.supportingText}
</p>
<p class="text-xs text-muted mt-0.5">
<span class="font-mono font-medium text-emerald-600 dark:text-emerald-400">
{props.workloads.running}
@@ -40,6 +40,14 @@ function normalizeRange(range: HistoryTimeRange): SummaryTimeRange {
export function TrendCharts(props: TrendChartsProps) {
const selectedRange = createMemo(() => normalizeRange(props.trendRange()));
const emptyStateTitle = createMemo(() =>
props.overview.infrastructure.total > 0 ? 'History is warming up' : 'No infrastructure trends yet',
);
const emptyStateBody = createMemo(() =>
props.overview.infrastructure.total > 0
? 'Pulse needs a couple of CPU and memory samples from connected infrastructure before these charts render.'
: 'These charts appear after connected systems start reporting infrastructure CPU and memory history.',
);
const cpuSeries = createMemo<InteractiveSparklineSeries[]>(() => {
const resources = props.overview.infrastructure.topCPU.slice(0, 5);
@@ -125,8 +133,12 @@ export function TrendCharts(props: TrendChartsProps) {
</div>
</Show>
<Show when={!props.trends.error && props.trends.infrastructure.emptyMessage}>
<div class="mb-2 rounded border border-border bg-surface px-2 py-1.5 text-[11px] text-muted">
{props.trends.infrastructure.emptyMessage}
<div class="mb-2 rounded border border-border bg-surface px-3 py-2 text-[11px] text-muted">
<p class="font-medium text-base-content">{emptyStateTitle()}</p>
<p class="mt-1">{emptyStateBody()}</p>
<p class="mt-1 text-[10px] uppercase tracking-wide text-muted/80">
{props.trends.infrastructure.emptyMessage}
</p>
</div>
</Show>
@@ -3,18 +3,20 @@ import { render, screen } from '@solidjs/testing-library';
import { KPIStrip } from '../KPIStrip';
describe('KPIStrip', () => {
it('renders alert summary counts', () => {
it('keeps the workloads card explicitly discoverable for containers, VMs, and pods', () => {
render(() => (
<KPIStrip
infrastructure={{ total: 8, online: 7 }}
workloads={{ total: 12, running: 10 }}
storage={{ capacityPercent: 63, totalUsed: 630, totalCapacity: 1000 }}
alerts={{ activeCritical: 2, activeWarning: 3, total: 5 }}
infrastructure={{ total: 5, online: 5 }}
workloads={{ total: 33, running: 28 }}
storage={{ capacityPercent: 27, totalUsed: 20, totalCapacity: 75 }}
alerts={{ activeCritical: 1, activeWarning: 16, total: 17 }}
/>
));
expect(screen.getByText('Alerts')).toBeInTheDocument();
expect(screen.getByText('5')).toBeInTheDocument();
expect(screen.getByText(/critical ·/)).toBeInTheDocument();
const workloadsLink = screen.getByRole('link', { name: /workloads/i });
expect(workloadsLink.getAttribute('href')).toBe('/workloads');
expect(screen.getByText('VMs, containers, and pods')).toBeInTheDocument();
expect(screen.getByText('28')).toBeInTheDocument();
expect(screen.getByText(/running/i)).toBeInTheDocument();
});
});
@@ -61,6 +61,15 @@ describe('TrendCharts', () => {
const [range, setRange] = createSignal<HistoryTimeRange>('1h');
render(() => (
<TrendCharts
overview={makeOverview({
infrastructure: {
total: 2,
byStatus: {},
byType: {},
topCPU: [],
topMemory: [],
},
})}
trends={makeTrends({
infrastructure: {
cpu: new Map(),
@@ -68,12 +77,17 @@ describe('TrendCharts', () => {
emptyMessage: 'Gathering first sample…',
},
})}
overview={makeOverview()}
trendRange={range}
setTrendRange={setRange}
/>
));
expect(screen.getByText('History is warming up')).toBeTruthy();
expect(
screen.getByText(
'Pulse needs a couple of CPU and memory samples from connected infrastructure before these charts render.',
),
).toBeTruthy();
expect(screen.getByText('Gathering first sample…')).toBeTruthy();
});
});
@@ -8,6 +8,7 @@ describe('dashboardKpiPresentation', () => {
});
expect(getDashboardKpiPresentation('workloads')).toMatchObject({
label: 'Workloads',
supportingText: 'VMs, containers, and pods',
});
expect(getDashboardKpiPresentation('storage')).toMatchObject({
label: 'Storage',
@@ -8,6 +8,7 @@ export type DashboardKpiKey = 'infrastructure' | 'workloads' | 'storage' | 'aler
export interface DashboardKpiPresentation {
label: string;
supportingText?: string;
cardClassName: string;
iconClassName: string;
icon: Component<{ class?: string }>;
@@ -23,6 +24,7 @@ const DASHBOARD_KPI_PRESENTATION: Record<DashboardKpiKey, DashboardKpiPresentati
},
workloads: {
label: 'Workloads',
supportingText: 'VMs, containers, and pods',
cardClassName:
'h-full border-l-[3px] border-l-violet-500 dark:border-l-violet-400 bg-surface group-hover:bg-surface-hover transition-colors',
iconClassName: 'w-3.5 h-3.5 text-violet-500/50 dark:text-violet-400/50',