mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-30 15:15:24 +00:00
155 lines
3.9 KiB
TypeScript
155 lines
3.9 KiB
TypeScript
import type { Client } from "ssh2";
|
|
import { execCommand } from "../../../../src/backend/hosts/metrics-shared/common-utils.js";
|
|
import type {
|
|
PortsMetrics,
|
|
ListeningPort,
|
|
} from "../../../../src/types/stats-widgets.js";
|
|
|
|
function parseSsOutput(output: string): ListeningPort[] {
|
|
const ports: ListeningPort[] = [];
|
|
const lines = output.split("\n").slice(1);
|
|
|
|
for (const line of lines) {
|
|
const trimmed = line.trim();
|
|
if (!trimmed) continue;
|
|
|
|
const parts = trimmed.split(/\s+/);
|
|
if (parts.length < 5) continue;
|
|
|
|
const protocol = parts[0]?.toLowerCase();
|
|
if (protocol !== "tcp" && protocol !== "udp") continue;
|
|
|
|
const state = parts[1];
|
|
const localAddr = parts[4];
|
|
|
|
if (!localAddr) continue;
|
|
|
|
const lastColon = localAddr.lastIndexOf(":");
|
|
if (lastColon === -1) continue;
|
|
|
|
const address = localAddr.substring(0, lastColon);
|
|
const portStr = localAddr.substring(lastColon + 1);
|
|
const port = parseInt(portStr, 10);
|
|
|
|
if (isNaN(port)) continue;
|
|
|
|
const portEntry: ListeningPort = {
|
|
protocol: protocol as "tcp" | "udp",
|
|
localAddress: address.replace(/^\[|\]$/g, ""),
|
|
localPort: port,
|
|
state: protocol === "tcp" ? state : undefined,
|
|
};
|
|
|
|
const processInfo = parts[6];
|
|
if (processInfo && processInfo.startsWith("users:")) {
|
|
const pidMatch = processInfo.match(/pid=(\d+)/);
|
|
const nameMatch = processInfo.match(/\("([^"]+)"/);
|
|
if (pidMatch) portEntry.pid = parseInt(pidMatch[1], 10);
|
|
if (nameMatch) portEntry.process = nameMatch[1];
|
|
}
|
|
|
|
ports.push(portEntry);
|
|
}
|
|
|
|
return ports;
|
|
}
|
|
|
|
function parseNetstatOutput(output: string): ListeningPort[] {
|
|
const ports: ListeningPort[] = [];
|
|
const lines = output.split("\n");
|
|
|
|
for (const line of lines) {
|
|
const trimmed = line.trim();
|
|
if (!trimmed) continue;
|
|
|
|
const parts = trimmed.split(/\s+/);
|
|
if (parts.length < 4) continue;
|
|
|
|
const proto = parts[0]?.toLowerCase();
|
|
if (!proto) continue;
|
|
|
|
let protocol: "tcp" | "udp";
|
|
if (proto.startsWith("tcp")) {
|
|
protocol = "tcp";
|
|
} else if (proto.startsWith("udp")) {
|
|
protocol = "udp";
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
const localAddr = parts[3];
|
|
if (!localAddr) continue;
|
|
|
|
const lastColon = localAddr.lastIndexOf(":");
|
|
if (lastColon === -1) continue;
|
|
|
|
const address = localAddr.substring(0, lastColon);
|
|
const portStr = localAddr.substring(lastColon + 1);
|
|
const port = parseInt(portStr, 10);
|
|
|
|
if (isNaN(port)) continue;
|
|
|
|
const portEntry: ListeningPort = {
|
|
protocol,
|
|
localAddress: address,
|
|
localPort: port,
|
|
};
|
|
|
|
if (protocol === "tcp" && parts.length >= 6) {
|
|
portEntry.state = parts[5];
|
|
}
|
|
|
|
const pidProgram = parts[parts.length - 1];
|
|
if (pidProgram && pidProgram.includes("/")) {
|
|
const [pidStr, process] = pidProgram.split("/");
|
|
const pid = parseInt(pidStr, 10);
|
|
if (!isNaN(pid)) portEntry.pid = pid;
|
|
if (process) portEntry.process = process;
|
|
}
|
|
|
|
ports.push(portEntry);
|
|
}
|
|
|
|
return ports;
|
|
}
|
|
|
|
export async function collectPortsMetrics(
|
|
client: Client,
|
|
): Promise<PortsMetrics> {
|
|
try {
|
|
const ssResult = await execCommand(client, "ss -tulnp 2>/dev/null", 15000);
|
|
|
|
if (ssResult.stdout && ssResult.stdout.includes("Local")) {
|
|
const ports = parseSsOutput(ssResult.stdout);
|
|
return {
|
|
source: "ss",
|
|
ports: ports.sort((a, b) => a.localPort - b.localPort),
|
|
};
|
|
}
|
|
|
|
const netstatResult = await execCommand(
|
|
client,
|
|
"netstat -tulnp 2>/dev/null",
|
|
15000,
|
|
);
|
|
|
|
if (netstatResult.stdout && netstatResult.stdout.includes("Local")) {
|
|
const ports = parseNetstatOutput(netstatResult.stdout);
|
|
return {
|
|
source: "netstat",
|
|
ports: ports.sort((a, b) => a.localPort - b.localPort),
|
|
};
|
|
}
|
|
|
|
return {
|
|
source: "none",
|
|
ports: [],
|
|
};
|
|
} catch {
|
|
return {
|
|
source: "none",
|
|
ports: [],
|
|
};
|
|
}
|
|
}
|