mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-25 04:33:03 +00:00
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user