mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 14:15:24 +00:00
feat: finish the host-metrics plugin, move host status into core (B16)
This commit is contained in:
1 parent
14eb558b85
commit
0a3f05d99d
308 files changed
+53073
-8985
No files matched your search
@@ -1,145 +0,0 @@
|
||||
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"
|
||||
| "top_memory"
|
||||
| "wireguard_manager"
|
||||
| "tailscale_manager";
|
||||
|
||||
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",
|
||||
"temperature",
|
||||
"gpu",
|
||||
];
|
||||
|
||||
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",
|
||||
"wireguard_manager",
|
||||
"tailscale_manager",
|
||||
];
|
||||
|
||||
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,
|
||||
temperature: 1,
|
||||
processes: 2,
|
||||
ports: 2,
|
||||
firewall: 2,
|
||||
login_stats: 2,
|
||||
gpu: 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;
|
||||
}
|
||||
+6
-3
@@ -1,4 +1,3 @@
|
||||
import type { StatsConfig } from "./stats-widgets.js";
|
||||
import type { Request } from "express";
|
||||
import type { RefObject } from "react";
|
||||
import type { HostAuthOverrides } from "./auth-protocols.js";
|
||||
@@ -230,7 +229,9 @@ export type Host = {
|
||||
tunnelConnections: TunnelConnection[];
|
||||
jumpHosts?: JumpHost[];
|
||||
quickActions?: QuickAction[];
|
||||
statsConfig?: string | StatsConfig;
|
||||
statusCheckEnabled?: boolean;
|
||||
/** Seconds between status checks; null follows the global setting. */
|
||||
statusCheckInterval?: number | null;
|
||||
terminalConfig?: Partial<TerminalConfig>;
|
||||
notes?: string;
|
||||
|
||||
@@ -385,7 +386,9 @@ export interface HostData {
|
||||
tunnelConnections?: TunnelConnection[];
|
||||
jumpHosts?: JumpHostData[];
|
||||
quickActions?: QuickActionData[];
|
||||
statsConfig?: string | StatsConfig;
|
||||
statusCheckEnabled?: boolean;
|
||||
/** Seconds between status checks; null follows the global setting. */
|
||||
statusCheckInterval?: number | null;
|
||||
terminalConfig?: Partial<TerminalConfig>;
|
||||
notes?: string;
|
||||
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
export type WidgetType =
|
||||
| "cpu"
|
||||
| "memory"
|
||||
| "disk"
|
||||
| "network"
|
||||
| "uptime"
|
||||
| "processes"
|
||||
| "system"
|
||||
| "login_stats"
|
||||
| "ports"
|
||||
| "firewall"
|
||||
| "temperature"
|
||||
| "gpu";
|
||||
|
||||
export interface ListeningPort {
|
||||
protocol: "tcp" | "udp";
|
||||
localAddress: string;
|
||||
localPort: number;
|
||||
state?: string;
|
||||
pid?: number;
|
||||
process?: string;
|
||||
}
|
||||
|
||||
export interface PortsMetrics {
|
||||
source: "ss" | "netstat" | "none";
|
||||
ports: ListeningPort[];
|
||||
}
|
||||
|
||||
export interface FirewallRule {
|
||||
chain: string;
|
||||
target: string;
|
||||
protocol: string;
|
||||
source: string;
|
||||
destination: string;
|
||||
dport?: string;
|
||||
sport?: string;
|
||||
state?: string;
|
||||
interface?: string;
|
||||
extra?: string;
|
||||
}
|
||||
|
||||
export interface FirewallChain {
|
||||
name: string;
|
||||
policy: string;
|
||||
rules: FirewallRule[];
|
||||
}
|
||||
|
||||
export interface FirewallMetrics {
|
||||
type: "iptables" | "nftables" | "none";
|
||||
status: "active" | "inactive" | "unknown";
|
||||
chains: FirewallChain[];
|
||||
}
|
||||
|
||||
export interface TemperatureSensor {
|
||||
label: string;
|
||||
celsius: number;
|
||||
}
|
||||
|
||||
export interface TemperatureMetrics {
|
||||
source: "sysfs" | "sensors" | "none";
|
||||
highestCelsius: number | null;
|
||||
sensors: TemperatureSensor[];
|
||||
}
|
||||
|
||||
export interface GpuDevice {
|
||||
index: number;
|
||||
uuid: string;
|
||||
name: string;
|
||||
driverVersion: string | null;
|
||||
utilizationPercent: number | null;
|
||||
memoryUsedMiB: number | null;
|
||||
memoryTotalMiB: number | null;
|
||||
memoryPercent: number | null;
|
||||
temperatureCelsius: number | null;
|
||||
powerDrawWatts: number | null;
|
||||
powerLimitWatts: number | null;
|
||||
fanPercent: number | null;
|
||||
}
|
||||
|
||||
export interface GpuProcess {
|
||||
gpuIndex: number | null;
|
||||
pid: number;
|
||||
name: string;
|
||||
memoryUsedMiB: number | null;
|
||||
}
|
||||
|
||||
export interface GpuMetrics {
|
||||
source: "nvidia-smi" | "none";
|
||||
gpus: GpuDevice[];
|
||||
processes: GpuProcess[];
|
||||
}
|
||||
|
||||
export interface StatsConfig {
|
||||
enabledWidgets: WidgetType[];
|
||||
statusCheckEnabled: boolean;
|
||||
statusCheckInterval: number;
|
||||
useGlobalStatusInterval?: boolean;
|
||||
metricsEnabled: boolean;
|
||||
metricsInterval: number;
|
||||
useGlobalMetricsInterval?: boolean;
|
||||
disableTcpPing?: boolean;
|
||||
/** Filesystem mount points to leave out of the disk widget. */
|
||||
excludedMounts?: string[];
|
||||
/** Extra paths to monitor, including paths inside bind-mounted containers. */
|
||||
monitoredMounts?: Array<{ path: string; label?: string }>;
|
||||
}
|
||||
|
||||
export const DEFAULT_STATS_CONFIG: StatsConfig = {
|
||||
enabledWidgets: [
|
||||
"cpu",
|
||||
"memory",
|
||||
"disk",
|
||||
"network",
|
||||
"uptime",
|
||||
"system",
|
||||
"login_stats",
|
||||
"processes",
|
||||
"ports",
|
||||
"firewall",
|
||||
"temperature",
|
||||
],
|
||||
statusCheckEnabled: true,
|
||||
statusCheckInterval: 30,
|
||||
useGlobalStatusInterval: true,
|
||||
metricsEnabled: true,
|
||||
metricsInterval: 30,
|
||||
useGlobalMetricsInterval: true,
|
||||
disableTcpPing: false,
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { TerminalConfig } from "./index.js";
|
||||
import type { StatsConfig } from "./stats-widgets.js";
|
||||
import type { HostAuthOverrides } from "./auth-protocols.js";
|
||||
|
||||
export type Host = {
|
||||
@@ -147,7 +146,9 @@ export type Host = {
|
||||
enabledCards?: string[];
|
||||
} | null;
|
||||
|
||||
statsConfig?: StatsConfig;
|
||||
statusCheckEnabled?: boolean;
|
||||
/** Seconds between status checks; null follows the global setting. */
|
||||
statusCheckInterval?: number | null;
|
||||
quickActions: { name: string; snippetId: string }[];
|
||||
|
||||
enableSsh: boolean;
|
||||
|
||||
Reference in new issue
Block a user