mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 06:35:24 +00:00
139 lines
4.1 KiB
TypeScript
139 lines
4.1 KiB
TypeScript
import type { Express } from "express";
|
|
import { execCommand } from "../../../../src/backend/hosts/metrics-shared/common-utils.js";
|
|
import { managerHandler } from "./route-helpers.js";
|
|
import type { ManagerRoutesDeps } from "./types.js";
|
|
|
|
// ─── Top by memory ──────────────────────────────────────────────────────────
|
|
|
|
export interface MemProcessRow {
|
|
pid: number;
|
|
user: string;
|
|
mem: number;
|
|
rss: number;
|
|
command: string;
|
|
}
|
|
|
|
const TOP_MEM_CMD =
|
|
"ps -eo pid,user:20,pmem,rss,comm --sort=-pmem --no-headers 2>/dev/null | head -n 20";
|
|
|
|
export function parseTopMemory(output: string): MemProcessRow[] {
|
|
const rows: MemProcessRow[] = [];
|
|
for (const raw of output.split("\n")) {
|
|
const line = raw.trim();
|
|
if (!line) continue;
|
|
const m = line.match(/^(\d+)\s+(\S+)\s+([\d.]+)\s+(\d+)\s+(.*)$/);
|
|
if (!m) continue;
|
|
rows.push({
|
|
pid: Number(m[1]),
|
|
user: m[2],
|
|
mem: Number(m[3]),
|
|
rss: Number(m[4]),
|
|
command: m[5],
|
|
});
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
// ─── Systemd timers ─────────────────────────────────────────────────────────
|
|
|
|
export interface TimerRow {
|
|
next: string;
|
|
left: string;
|
|
last: string;
|
|
unit: string;
|
|
activates: string;
|
|
}
|
|
|
|
const TIMERS_CMD =
|
|
"systemctl list-timers --all --no-legend --no-pager 2>/dev/null";
|
|
|
|
export function parseTimers(output: string): TimerRow[] {
|
|
const rows: TimerRow[] = [];
|
|
for (const raw of output.split("\n")) {
|
|
const line = raw.trim();
|
|
if (!line || line.startsWith("NEXT")) continue;
|
|
// NEXT(3) LEFT(2) LAST(3) PASSED(2) UNIT ACTIVATES -> columns vary; grab
|
|
// the trailing UNIT + ACTIVATES which always end the line.
|
|
const parts = line.split(/\s+/);
|
|
if (parts.length < 2) continue;
|
|
const activates = parts[parts.length - 1];
|
|
const unit = parts[parts.length - 2];
|
|
if (!unit.endsWith(".timer")) continue;
|
|
rows.push({
|
|
next: parts.slice(0, 3).join(" "),
|
|
left: "",
|
|
last: "",
|
|
unit,
|
|
activates,
|
|
});
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
// ─── Disk breakdown (per-mount) ─────────────────────────────────────────────
|
|
|
|
export interface MountUsage {
|
|
filesystem: string;
|
|
sizeKb: number;
|
|
usedKb: number;
|
|
availKb: number;
|
|
usePct: number;
|
|
mount: string;
|
|
}
|
|
|
|
const DF_CMD = "df -Pk 2>/dev/null | tail -n +2";
|
|
|
|
export function parseDfMounts(output: string): MountUsage[] {
|
|
const mounts: MountUsage[] = [];
|
|
for (const raw of output.split("\n")) {
|
|
const line = raw.trim();
|
|
if (!line) continue;
|
|
const m = line.match(/^(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)%\s+(.+)$/);
|
|
if (!m) continue;
|
|
const fs = m[1];
|
|
// Skip pseudo/virtual filesystems that clutter the view.
|
|
if (/^(tmpfs|devtmpfs|overlay|udev|none|shm)$/.test(fs)) continue;
|
|
mounts.push({
|
|
filesystem: fs,
|
|
sizeKb: Number(m[2]),
|
|
usedKb: Number(m[3]),
|
|
availKb: Number(m[4]),
|
|
usePct: Number(m[5]),
|
|
mount: m[6],
|
|
});
|
|
}
|
|
return mounts;
|
|
}
|
|
|
|
export function registerSimpleReadRoutes(
|
|
app: Express,
|
|
{ validateHostId, runOnHost }: ManagerRoutesDeps,
|
|
): void {
|
|
app.get(
|
|
"/host-metrics/managers/top-memory/:id",
|
|
validateHostId,
|
|
managerHandler(runOnHost, "connect", "top_memory", async (client) => {
|
|
const { stdout } = await execCommand(client, TOP_MEM_CMD, 15000);
|
|
return { processes: parseTopMemory(stdout) };
|
|
}),
|
|
);
|
|
|
|
app.get(
|
|
"/host-metrics/managers/timers/:id",
|
|
validateHostId,
|
|
managerHandler(runOnHost, "connect", "systemd_timers", async (client) => {
|
|
const { stdout } = await execCommand(client, TIMERS_CMD, 15000);
|
|
return { timers: parseTimers(stdout) };
|
|
}),
|
|
);
|
|
|
|
app.get(
|
|
"/host-metrics/managers/disk-breakdown/:id",
|
|
validateHostId,
|
|
managerHandler(runOnHost, "connect", "disk_breakdown", async (client) => {
|
|
const { stdout } = await execCommand(client, DF_CMD, 15000);
|
|
return { mounts: parseDfMounts(stdout) };
|
|
}),
|
|
);
|
|
}
|