2026-06-16 15:59:53 -05:00
|
|
|
import type { WidgetType } from "./stats-widgets.js";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Host Metrics layout model. Shared by the frontend tab and the backend
|
|
|
|
|
* preferences endpoint. Metric card ids reuse the existing `WidgetType` values
|
|
|
|
|
* verbatim (so `enabledWidgets` stays valid for the mobile app); manager card
|
|
|
|
|
* ids are new and never enter `statsConfig.enabledWidgets`.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
export type HostMetricCardId = WidgetType;
|
|
|
|
|
|
|
|
|
|
export type HostMetricManagerId =
|
|
|
|
|
| "service_manager"
|
|
|
|
|
| "process_inspector"
|
|
|
|
|
| "log_viewer"
|
|
|
|
|
| "cron_manager"
|
|
|
|
|
| "package_manager"
|
|
|
|
|
| "ssl_manager"
|
|
|
|
|
| "firewall_manager"
|
|
|
|
|
| "user_manager"
|
|
|
|
|
| "health_check"
|
|
|
|
|
| "disk_breakdown"
|
|
|
|
|
| "systemd_timers"
|
2026-06-29 13:28:26 -05:00
|
|
|
| "top_memory"
|
|
|
|
|
| "wireguard_manager"
|
|
|
|
|
| "tailscale_manager";
|
2026-06-16 15:59:53 -05:00
|
|
|
|
|
|
|
|
export type HostMetricsCardId = HostMetricCardId | HostMetricManagerId;
|
|
|
|
|
|
|
|
|
|
export type HostMetricsColSpan = 1 | 2 | 3;
|
|
|
|
|
|
|
|
|
|
export interface HostMetricsSlot {
|
|
|
|
|
id: HostMetricsCardId;
|
|
|
|
|
order: number;
|
|
|
|
|
colSpan: HostMetricsColSpan;
|
|
|
|
|
height: number | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface HostMetricsLayout {
|
|
|
|
|
slots: HostMetricsSlot[];
|
|
|
|
|
columns: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Canonical order of metric cards (also the canonical `enabledWidgets` order). */
|
|
|
|
|
export const METRIC_CARD_IDS: HostMetricCardId[] = [
|
|
|
|
|
"cpu",
|
|
|
|
|
"memory",
|
|
|
|
|
"disk",
|
|
|
|
|
"network",
|
|
|
|
|
"uptime",
|
|
|
|
|
"system",
|
|
|
|
|
"login_stats",
|
|
|
|
|
"processes",
|
|
|
|
|
"ports",
|
|
|
|
|
"firewall",
|
2026-06-29 13:28:26 -05:00
|
|
|
"temperature",
|
2026-06-16 15:59:53 -05:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
export const MANAGER_CARD_IDS: HostMetricManagerId[] = [
|
|
|
|
|
"service_manager",
|
|
|
|
|
"process_inspector",
|
|
|
|
|
"log_viewer",
|
|
|
|
|
"cron_manager",
|
|
|
|
|
"package_manager",
|
|
|
|
|
"ssl_manager",
|
|
|
|
|
"firewall_manager",
|
|
|
|
|
"user_manager",
|
|
|
|
|
"health_check",
|
|
|
|
|
"disk_breakdown",
|
|
|
|
|
"systemd_timers",
|
|
|
|
|
"top_memory",
|
2026-06-29 13:28:26 -05:00
|
|
|
"wireguard_manager",
|
|
|
|
|
"tailscale_manager",
|
2026-06-16 15:59:53 -05:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
export function isMetricCardId(id: string): id is HostMetricCardId {
|
|
|
|
|
return (METRIC_CARD_IDS as string[]).includes(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function isManagerCardId(id: string): id is HostMetricManagerId {
|
|
|
|
|
return (MANAGER_CARD_IDS as string[]).includes(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Derive the flat `enabledWidgets` array (canonical order, deduped, metric ids
|
|
|
|
|
* only) from a set of layout slots. This is the single source of truth keeping
|
|
|
|
|
* `statsConfig.enabledWidgets` valid for the mobile app.
|
|
|
|
|
*/
|
|
|
|
|
export function deriveEnabledWidgets(
|
|
|
|
|
slots: Array<{ id: string }>,
|
|
|
|
|
): WidgetType[] {
|
|
|
|
|
const present = new Set(slots.map((s) => s.id));
|
|
|
|
|
return METRIC_CARD_IDS.filter((id) => present.has(id));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const DEFAULT_COLSPAN: Partial<Record<HostMetricsCardId, HostMetricsColSpan>> =
|
|
|
|
|
{
|
|
|
|
|
cpu: 1,
|
|
|
|
|
memory: 1,
|
|
|
|
|
disk: 1,
|
|
|
|
|
uptime: 1,
|
|
|
|
|
system: 1,
|
|
|
|
|
network: 1,
|
2026-06-29 13:28:26 -05:00
|
|
|
temperature: 1,
|
2026-06-16 15:59:53 -05:00
|
|
|
processes: 2,
|
|
|
|
|
ports: 2,
|
|
|
|
|
firewall: 2,
|
|
|
|
|
login_stats: 2,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// All cards default to content/auto height (null); the masonry packs them
|
|
|
|
|
// tetris-style and scrollable cards cap their own body height. Users can still
|
|
|
|
|
// pin an explicit height by dragging a card's resize handle.
|
|
|
|
|
const DEFAULT_HEIGHT: Partial<Record<HostMetricsCardId, number | null>> = {};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Build a default layout from a list of enabled metric-card ids (e.g. the host's
|
|
|
|
|
* current `statsConfig.enabledWidgets`), preserving canonical order. Manager
|
|
|
|
|
* cards are not added by default.
|
|
|
|
|
*/
|
|
|
|
|
export function defaultLayoutFromWidgets(
|
|
|
|
|
enabledWidgets: string[],
|
|
|
|
|
columns = 3,
|
|
|
|
|
): HostMetricsLayout {
|
|
|
|
|
const enabled = new Set(enabledWidgets);
|
|
|
|
|
const ids = METRIC_CARD_IDS.filter((id) => enabled.has(id));
|
|
|
|
|
const slots: HostMetricsSlot[] = ids.map((id, i) => ({
|
|
|
|
|
id,
|
|
|
|
|
order: i,
|
|
|
|
|
colSpan: DEFAULT_COLSPAN[id] ?? 1,
|
|
|
|
|
height: DEFAULT_HEIGHT[id] ?? null,
|
|
|
|
|
}));
|
|
|
|
|
return { slots, columns };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function defaultColSpanFor(id: HostMetricsCardId): HostMetricsColSpan {
|
|
|
|
|
return DEFAULT_COLSPAN[id] ?? (isManagerCardId(id) ? 2 : 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function defaultHeightFor(id: HostMetricsCardId): number | null {
|
|
|
|
|
if (id in DEFAULT_HEIGHT) return DEFAULT_HEIGHT[id] ?? null;
|
|
|
|
|
return null;
|
|
|
|
|
}
|