homeio
PublicForked from doctor-io/homeio
monitor-utils.ts
import type { InstalledApp } from "@/lib/shared/contracts/apps";
import type { ContainerStats } from "@/lib/shared/contracts/docker";
/**
* The installed app a container belongs to: the app's own container, or one of
* its Compose stack's (<stack>-<service>-1 or <stack>_<service>_1). With
* several matching stacks the longest name wins, so nextcloud-aio-… goes to
* the nextcloud-aio stack rather than nextcloud.
*/
export function findAppForContainer(
containerName: string,
apps: Pick<InstalledApp, "id" | "stackName" | "containerName" | "logoUrl">[],
) {
const name = containerName.replace(/^\//, "");
const own = apps.find((app) => app.containerName === name);
if (own) return own;
let best: (typeof apps)[number] | null = null;
for (const app of apps) {
const stack = app.stackName;
const matches = name === stack || name.startsWith(`${stack}-`) || name.startsWith(`${stack}_`);
if (matches && (!best || stack.length > best.stackName.length)) best = app;
}
return best;
}
/**
* Calculate total stats from Docker containers
*/
export function calculateDockerTotals(containers: ContainerStats[]) {
if (containers.length === 0) {
return {
totalCpu: 0,
totalMemoryMB: 0,
totalNetworkRx: 0,
totalNetworkTx: 0,
totalDiskRead: 0,
totalDiskWrite: 0,
runningCount: 0,
};
}
return containers.reduce(
(acc, container) => ({
totalCpu: acc.totalCpu + container.cpuPercent,
totalMemoryMB: acc.totalMemoryMB + container.memoryUsed / 1024 / 1024,
totalNetworkRx: acc.totalNetworkRx + container.networkRx,
totalNetworkTx: acc.totalNetworkTx + container.networkTx,
totalDiskRead: acc.totalDiskRead + container.blockRead,
totalDiskWrite: acc.totalDiskWrite + container.blockWrite,
runningCount:
acc.runningCount + (container.state === "running" ? 1 : 0),
}),
{
totalCpu: 0,
totalMemoryMB: 0,
totalNetworkRx: 0,
totalNetworkTx: 0,
totalDiskRead: 0,
totalDiskWrite: 0,
runningCount: 0,
},
);
}
/**
* Convert Docker container to process-like format for table display
*/
export function containerToProcess(container: ContainerStats) {
return {
name: container.name,
status: (container.state === "running"
? "Running"
: container.state === "exited"
? "Sleeping"
: "Background") as "Running" | "Sleeping" | "Background",
pid: parseInt(container.id.slice(0, 8), 16) % 99999, // Pseudo PID from container ID
cpu: container.cpuPercent,
memory: container.memoryUsed / 1024 / 1024, // Convert to MB
network: (container.networkRx + container.networkTx) / 1024 / 1024 / 2, // Average RX+TX in MB
disk: (container.blockRead + container.blockWrite) / 1024 / 1024 / 2, // Average read+write in MB
};
}
/**
* Nudge a value randomly within min/max bounds (for animations)
*/
export function nudge(
value: number,
min: number,
max: number,
delta = 2.2,
): number {
const next = value + (Math.random() * 2 - 1) * delta;
return Math.min(max, Math.max(min, Number(next.toFixed(1))));
}
/**
* Get status badge color based on container/process state
*/
export function getStatusBadgeColor(
status: "Running" | "Sleeping" | "Background" | string,
): string {
switch (status) {
case "Running":
case "running":
return "bg-status-green/15 text-status-green";
case "Sleeping":
case "exited":
return "bg-status-amber/15 text-status-amber";
default:
return "bg-secondary/60 text-muted-foreground";
}
}