140 lines
3.3 KiB
Svelte
140 lines
3.3 KiB
Svelte
<script lang="ts">
|
|
import { formatMetricValue } from "../format";
|
|
import type { UiTelemetryCard } from "../types";
|
|
import IconGlyph from "./IconGlyph.svelte";
|
|
import LineChart from "./LineChart.svelte";
|
|
|
|
let { card }: { card: UiTelemetryCard } = $props();
|
|
|
|
let progress = $derived(resolveProgress(card));
|
|
let value = $derived(formatMetricValue(card.value));
|
|
</script>
|
|
|
|
<article class="telemetry-card" data-severity={card.severity} data-model-id={card.id}>
|
|
<header>
|
|
{#if card.icon}
|
|
<IconGlyph name={card.icon} size="sm" />
|
|
{/if}
|
|
<h3>{card.label}</h3>
|
|
</header>
|
|
<strong>{value}</strong>
|
|
{#if progress !== null}
|
|
<div class="telemetry-card__bar" aria-hidden="true">
|
|
<span style={`--metric-progress: ${progress}%`}></span>
|
|
</div>
|
|
{/if}
|
|
{#if card.sparkline?.length}
|
|
<LineChart values={card.sparkline} severity={card.severity} label={`${card.label} trend`} />
|
|
{/if}
|
|
{#if card.detail || card.description}
|
|
<p>{card.detail || card.description}</p>
|
|
{/if}
|
|
</article>
|
|
|
|
<style>
|
|
.telemetry-card {
|
|
display: grid;
|
|
min-height: 5.15rem;
|
|
border: var(--ui-border);
|
|
background: linear-gradient(180deg, rgba(13, 15, 14, 0.82), rgba(2, 3, 2, 0.92));
|
|
padding: 0.45rem 0.55rem 0.42rem;
|
|
color: var(--ui-color-text);
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
.telemetry-card[data-severity="warning"] {
|
|
border-color: color-mix(in srgb, var(--ui-color-warning), transparent 42%);
|
|
}
|
|
|
|
.telemetry-card[data-severity="danger"] {
|
|
border-color: color-mix(in srgb, var(--ui-color-danger), transparent 22%);
|
|
color: var(--ui-color-danger);
|
|
}
|
|
|
|
.telemetry-card[data-severity="stale"],
|
|
.telemetry-card[data-severity="unavailable"] {
|
|
color: var(--ui-color-stale);
|
|
}
|
|
|
|
.telemetry-card[data-severity="loading"] {
|
|
color: var(--ui-color-accent);
|
|
}
|
|
|
|
.telemetry-card[data-severity="loading"] .telemetry-card__bar span {
|
|
min-width: 1.25rem;
|
|
}
|
|
|
|
header {
|
|
display: flex;
|
|
min-width: 0;
|
|
align-items: center;
|
|
gap: 0.35rem;
|
|
}
|
|
|
|
header :global(.icon-glyph) {
|
|
display: none;
|
|
}
|
|
|
|
h3,
|
|
p {
|
|
margin: 0;
|
|
}
|
|
|
|
h3 {
|
|
overflow-wrap: anywhere;
|
|
color: inherit;
|
|
font-size: 0.58rem;
|
|
font-weight: 800;
|
|
letter-spacing: 0;
|
|
line-height: 1.02;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
strong {
|
|
font-family: var(--ui-font-display);
|
|
font-size: clamp(1.55rem, 2.45vw, 2.22rem);
|
|
font-weight: 800;
|
|
letter-spacing: 0;
|
|
line-height: 0.76;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
p {
|
|
overflow: hidden;
|
|
color: var(--ui-color-muted);
|
|
font-size: 0.49rem;
|
|
line-height: 1.05;
|
|
text-overflow: ellipsis;
|
|
text-transform: uppercase;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.telemetry-card__bar {
|
|
height: 0.25rem;
|
|
border: var(--ui-border);
|
|
background: #030403;
|
|
}
|
|
|
|
.telemetry-card__bar span {
|
|
display: block;
|
|
width: var(--metric-progress);
|
|
height: 100%;
|
|
background: currentColor;
|
|
}
|
|
|
|
</style>
|
|
|
|
<script lang="ts" module>
|
|
import { clampPercent } from "../format";
|
|
|
|
function resolveProgress(card: {
|
|
progress?: number;
|
|
value: { kind: string; value: number | string };
|
|
}): number | null {
|
|
if (typeof card.progress === "number") return clampPercent(card.progress);
|
|
if (card.value.kind !== "percent") return null;
|
|
|
|
const progress = Number(card.value.value);
|
|
return Number.isFinite(progress) ? clampPercent(progress) : null;
|
|
}
|
|
</script>
|