Completes the catalogue at 17 cards and adds the picker that chooses between them. New cards: Slowest monitors, Lowest uptime, Monitors by group, Busiest servers, Incident stats, Notification channels, Maintenance, Status pages, Checks on schedule, Recent errors, Team. Data caveats honoured from the specs: - Rankings exclude paused monitors. A paused ping monitor reads 0% uptime and would otherwise rank beside a genuinely down one; neither is a live problem. Rankings also hide below two entries — a ranking of one is not a ranking. - Busiest servers reads cpu/memory/disk from the newest recentChecks entry (snake_case, 0-1 fractions) and renders a dash for a down server rather than 0%. - Maintenance compares start/end client-side: `active` is a user on/off flag, not 'running now', and repeat > 0 means end may sit in the past for a live window. - Notification channels renders only the type. The response also carries accessToken and accountSid; those must never reach the DOM. - Recent errors is worded 'recent' because /logs is a 1000-entry in-memory ring buffer that rotates fast and is wiped on restart. Also fixes a layout gap the six default cards never exposed: a 6-wide card followed by an 8-wide one left a lone card stretched to 8 in a middle row, with 4 empty columns beside it. Mid-page rows now always fill to 12; only the final row may end short, and the cap applies only there, so a lone trailing 4-wide card does not become a banner. Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
58 lines
1.8 KiB
TypeScript
58 lines
1.8 KiB
TypeScript
import { GRID_COLUMNS, STRETCH_MAX_WIDTH, type CardDefinition } from "./cards";
|
|
|
|
export interface PlacedCard {
|
|
card: CardDefinition;
|
|
/** Columns actually rendered — the declared width, or more if stretched. */
|
|
renderedWidth: number;
|
|
}
|
|
|
|
/**
|
|
* Packs cards into rows of GRID_COLUMNS. Declared width is a minimum; when a
|
|
* row ends short, its last card absorbs the remainder.
|
|
*
|
|
* A row in the middle of the page always fills completely — leaving a hole
|
|
* there reads as a rendering fault. Only the final row may end short, and only
|
|
* when a single card sits in it: stretching a lone trailing card to full width
|
|
* makes a 4-column card look like a banner. There, STRETCH_MAX_WIDTH caps the
|
|
* growth instead.
|
|
*
|
|
* `grid-auto-flow: dense` was tried instead and does not help: no later card is
|
|
* small enough to backfill the gaps that occur in practice.
|
|
*/
|
|
export const layoutCards = (cards: CardDefinition[]): PlacedCard[] => {
|
|
const placed: PlacedCard[] = [];
|
|
let row: PlacedCard[] = [];
|
|
let used = 0;
|
|
|
|
const flush = (isFinalRow: boolean) => {
|
|
if (row.length === 0) {
|
|
return;
|
|
}
|
|
const remainder = GRID_COLUMNS - used;
|
|
if (remainder > 0) {
|
|
const last = row[row.length - 1];
|
|
// A lone card ending the page grows only to the cap; every other short
|
|
// row is filled completely so no gap is left mid-page.
|
|
const ceiling = isFinalRow && row.length === 1 ? STRETCH_MAX_WIDTH : GRID_COLUMNS;
|
|
last.renderedWidth = Math.min(last.renderedWidth + remainder, ceiling);
|
|
}
|
|
placed.push(...row);
|
|
row = [];
|
|
used = 0;
|
|
};
|
|
|
|
for (const card of cards) {
|
|
if (used + card.width > GRID_COLUMNS) {
|
|
flush(false);
|
|
}
|
|
row.push({ card, renderedWidth: card.width });
|
|
used += card.width;
|
|
if (used === GRID_COLUMNS) {
|
|
flush(false);
|
|
}
|
|
}
|
|
flush(true);
|
|
|
|
return placed;
|
|
};
|