mirror of
https://github.com/rajnandan1/kener.git
synced 2026-08-07 07:14:56 +00:00
refactor: enhance ping and tcp monitoring logic, improve error handling, and introduce shared types
This commit is contained in:
+51
-3
@@ -1,4 +1,5 @@
|
||||
import net from "net"; // Use import instead of require
|
||||
import dns from "node:dns/promises";
|
||||
import ping from "ping";
|
||||
|
||||
interface TCPResult {
|
||||
@@ -59,6 +60,30 @@ interface PingResult {
|
||||
type: string;
|
||||
}
|
||||
|
||||
function ToLatencyNumber(value: string | number | null | undefined): number {
|
||||
if (typeof value === "number") return Number.isFinite(value) ? value : 0;
|
||||
if (typeof value === "string") {
|
||||
const n = Number(value);
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function IsSpawnRestrictionError(error: unknown): error is NodeJS.ErrnoException {
|
||||
if (!(error instanceof Error)) return false;
|
||||
const err = error as NodeJS.ErrnoException;
|
||||
const message = (err.message || "").toLowerCase();
|
||||
const code = err.code || "";
|
||||
return (
|
||||
err.syscall === "spawn" ||
|
||||
code === "EPERM" ||
|
||||
code === "EACCES" ||
|
||||
code === "ENOENT" ||
|
||||
message.includes("spawn eperm") ||
|
||||
message.includes("operation not permitted")
|
||||
);
|
||||
}
|
||||
|
||||
const Ping = async function (type: string, host: string, timeout: number, count: number): Promise<PingResult> {
|
||||
let output: PingResult = {
|
||||
alive: false,
|
||||
@@ -82,9 +107,32 @@ const Ping = async function (type: string, host: string, timeout: number, count:
|
||||
output.max = res.max;
|
||||
output.avg = res.avg;
|
||||
output.latencies = (res as unknown as { times?: string[] }).times ?? []; //sv5-verify
|
||||
output.latency = res.time;
|
||||
} catch (error) {
|
||||
console.log(`Error in pingCall IP4 for ${host}`, error);
|
||||
output.latency = ToLatencyNumber(res.time as string | number | null | undefined);
|
||||
} catch (error: unknown) {
|
||||
if (IsSpawnRestrictionError(error)) {
|
||||
const start = process.hrtime.bigint();
|
||||
try {
|
||||
await dns.lookup(host, { family: type === "IP6" ? 6 : 4 });
|
||||
const end = process.hrtime.bigint();
|
||||
const latency = Number(end - start) / 1e6;
|
||||
const latencyStr = latency.toFixed(3);
|
||||
|
||||
output.alive = true;
|
||||
output.min = latencyStr;
|
||||
output.max = latencyStr;
|
||||
output.avg = latencyStr;
|
||||
output.latencies = [latencyStr];
|
||||
output.latency = latency;
|
||||
|
||||
console.warn(
|
||||
`[Ping] ICMP unavailable for ${host} (${(error as Error).message}). Falling back to DNS lookup reachability.`,
|
||||
);
|
||||
} catch (lookupError) {
|
||||
console.log(`Error in ping fallback DNS for ${host}`, lookupError);
|
||||
}
|
||||
} else {
|
||||
console.log(`Error in pingCall ${type} for ${host}`, error);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
@@ -15,22 +15,25 @@ class PingCall {
|
||||
let pingEval = !!this.monitor.type_data.pingEval ? this.monitor.type_data.pingEval : DefaultPingEval;
|
||||
let tag = this.monitor.tag;
|
||||
if (hosts === undefined) {
|
||||
console.log(
|
||||
"Hosts is undefined. The ping monitor has changed in version 3.0.10. Please update your monitor with tag",
|
||||
tag,
|
||||
);
|
||||
return {
|
||||
status: GC.DOWN,
|
||||
latency: 0,
|
||||
type: GC.ERROR,
|
||||
error_message:
|
||||
"Hosts is undefined. The ping monitor has changed in version 3.0.10. Please update your monitor with tag " +
|
||||
tag,
|
||||
};
|
||||
}
|
||||
let arrayOfPings = [];
|
||||
let errorMessages: string[] = [];
|
||||
for (let i = 0; i < hosts.length; i++) {
|
||||
const host = hosts[i];
|
||||
arrayOfPings.push(await Ping(host.type, host.host, host.timeout, host.count));
|
||||
const result = await Ping(host.type, host.host, host.timeout, host.count);
|
||||
if (!result.alive) {
|
||||
errorMessages.push(`Host ${host.host} is unreachable.`);
|
||||
}
|
||||
arrayOfPings.push(result);
|
||||
}
|
||||
|
||||
let evalResp: EvalResponse | undefined = undefined;
|
||||
|
||||
try {
|
||||
@@ -42,13 +45,16 @@ class PingCall {
|
||||
status: GC.DOWN,
|
||||
latency: 0,
|
||||
type: GC.ERROR,
|
||||
error_message: `Error in pingEval: ${(error as Error).message}`,
|
||||
};
|
||||
}
|
||||
|
||||
//reduce to get the status
|
||||
return {
|
||||
status: evalResp?.status || GC.DOWN,
|
||||
latency: evalResp?.latency || 0,
|
||||
type: GC.REALTIME,
|
||||
error_message: errorMessages.length > 0 ? errorMessages.join("; ") : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,20 +16,28 @@ class TcpCall {
|
||||
let tag = this.monitor.tag;
|
||||
|
||||
if (hosts === undefined) {
|
||||
const message =
|
||||
"Hosts is undefined. The TCP monitor has changed in version 3.0.10. Please update your monitor with tag " + tag;
|
||||
console.log(
|
||||
"Hosts is undefined. The ping monitor has changed in version 3.0.10. Please update your monitor with tag",
|
||||
"Hosts is undefined. The TCP monitor has changed in version 3.0.10. Please update your monitor with tag",
|
||||
tag,
|
||||
);
|
||||
return {
|
||||
status: GC.DOWN,
|
||||
latency: 0,
|
||||
type: GC.ERROR,
|
||||
error_message: message,
|
||||
};
|
||||
}
|
||||
let arrayOfPings = [];
|
||||
let errorMessages: string[] = [];
|
||||
for (let i = 0; i < hosts.length; i++) {
|
||||
const host = hosts[i];
|
||||
arrayOfPings.push(await TCP(host.type, host.host, host.port, host.timeout));
|
||||
const result = await TCP(host.type, host.host, host.port, host.timeout);
|
||||
if (result.status !== "open") {
|
||||
errorMessages.push(`Host ${host.host}:${host.port} is ${result.status}`);
|
||||
}
|
||||
arrayOfPings.push(result);
|
||||
}
|
||||
|
||||
let evalResp: EvalResponse | undefined = undefined;
|
||||
@@ -38,11 +46,23 @@ class TcpCall {
|
||||
const evalFunction = new Function("arrayOfPings", `return (${tcpEval})(arrayOfPings);`);
|
||||
evalResp = await evalFunction(arrayOfPings);
|
||||
} catch (error: unknown) {
|
||||
console.log(`Error in tcpEval for ${tag}`, (error as Error).message);
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.log(`Error in tcpEval for ${tag}`, message);
|
||||
return {
|
||||
status: GC.DOWN,
|
||||
latency: 0,
|
||||
type: GC.ERROR,
|
||||
error_message: `Error in tcpEval: ${message}`,
|
||||
};
|
||||
}
|
||||
if (!!!evalResp) {
|
||||
const message = "tcpEval did not return a valid response.";
|
||||
console.log(`Error in tcpEval for ${tag}:`, message);
|
||||
return {
|
||||
status: GC.DOWN,
|
||||
latency: 0,
|
||||
type: GC.ERROR,
|
||||
error_message: `Error in tcpEval: ${message}`,
|
||||
};
|
||||
}
|
||||
//reduce to get the status
|
||||
@@ -50,6 +70,7 @@ class TcpCall {
|
||||
status: evalResp?.status || GC.DOWN,
|
||||
latency: evalResp?.latency || 0,
|
||||
type: GC.REALTIME,
|
||||
error_message: errorMessages.length > 0 ? errorMessages.join("; ") : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// Server-only monitor types (internal representation with all fields).
|
||||
|
||||
import type { PingHost, PingMonitorTypeData } from "$lib/types/ping.js";
|
||||
import type { TcpHost, TcpMonitorTypeData } from "$lib/types/tcp.js";
|
||||
|
||||
export interface MonitoringResult {
|
||||
status: string;
|
||||
latency: number;
|
||||
@@ -30,29 +33,8 @@ export interface DnsMonitorTypeData {
|
||||
values: string[];
|
||||
}
|
||||
|
||||
export interface PingHost {
|
||||
type: string;
|
||||
host: string;
|
||||
timeout: number;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface PingMonitorTypeData {
|
||||
hosts: PingHost[];
|
||||
pingEval?: string;
|
||||
}
|
||||
|
||||
export interface TcpHost {
|
||||
type: string;
|
||||
host: string;
|
||||
port: number;
|
||||
timeout: number;
|
||||
}
|
||||
|
||||
export interface TcpMonitorTypeData {
|
||||
hosts: TcpHost[];
|
||||
tcpEval?: string;
|
||||
}
|
||||
export type { PingHost, PingMonitorTypeData };
|
||||
export type { TcpHost, TcpMonitorTypeData };
|
||||
|
||||
export interface SslMonitorTypeData {
|
||||
host: string;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// Shared ping monitor types: safe to import from both server and client.
|
||||
|
||||
export const PING_HOST_TYPES = ["IP4", "IP6", "DOMAIN"] as const;
|
||||
|
||||
export type PingHostType = (typeof PING_HOST_TYPES)[number];
|
||||
|
||||
export interface PingHost {
|
||||
type: PingHostType;
|
||||
host: string;
|
||||
timeout: number;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface PingMonitorTypeData {
|
||||
hosts: PingHost[];
|
||||
pingEval?: string;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Shared TCP monitor types: safe to import from both server and client.
|
||||
|
||||
import type { PingHostType } from "$lib/types/ping.js";
|
||||
|
||||
export interface TcpHost {
|
||||
type: PingHostType;
|
||||
host: string;
|
||||
port: number;
|
||||
timeout: number;
|
||||
}
|
||||
|
||||
export interface TcpMonitorTypeData {
|
||||
hosts: TcpHost[];
|
||||
tcpEval?: string;
|
||||
}
|
||||
@@ -190,7 +190,7 @@
|
||||
fetchPages();
|
||||
});
|
||||
|
||||
let activeAccordionItem = $state<string>("");
|
||||
let activeAccordionItem = $derived<string>(isNew ? "general" : "configuration");
|
||||
let cloneDialogOpen = $state(false);
|
||||
let cloneTag = $state("");
|
||||
let cloneName = $state("");
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { Input } from "$lib/components/ui/input/index.js";
|
||||
import { Label } from "$lib/components/ui/label/index.js";
|
||||
import { Textarea } from "$lib/components/ui/textarea/index.js";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as Select from "$lib/components/ui/select/index.js";
|
||||
import { ValidateIpAddress } from "$lib/clientTools";
|
||||
import Plus from "@lucide/svelte/icons/plus";
|
||||
import X from "@lucide/svelte/icons/x";
|
||||
import { DefaultPingEval } from "$lib/anywhere.js";
|
||||
@@ -11,21 +11,55 @@
|
||||
import { javascript } from "@codemirror/lang-javascript";
|
||||
import { githubLight, githubDark } from "@uiw/codemirror-theme-github";
|
||||
import { mode } from "mode-watcher";
|
||||
import type { PingHost, PingHostType, PingMonitorTypeData } from "$lib/types/ping.js";
|
||||
import { PING_HOST_TYPES } from "$lib/types/ping.js";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let { data = $bindable() }: { data: any } = $props();
|
||||
let { data = $bindable({ hosts: [], pingEval: DefaultPingEval }) }: { data: PingMonitorTypeData } = $props();
|
||||
|
||||
function normalizeHostType(value: unknown): PingHostType {
|
||||
return typeof value === "string" && PING_HOST_TYPES.includes(value as PingHostType)
|
||||
? (value as PingHostType)
|
||||
: "IP4";
|
||||
}
|
||||
|
||||
function inferHostType(host: string): PingHostType | null {
|
||||
const inferred = ValidateIpAddress((host || "").trim());
|
||||
return inferred === "Invalid" ? null : inferred;
|
||||
}
|
||||
|
||||
// Initialize defaults if not set
|
||||
if (!data.hosts) data.hosts = [];
|
||||
if (!Array.isArray(data.hosts) || data.hosts.length === 0)
|
||||
data.hosts = [
|
||||
{
|
||||
type: "IP4",
|
||||
host: "",
|
||||
timeout: 1000,
|
||||
count: 3
|
||||
}
|
||||
];
|
||||
else {
|
||||
data.hosts = data.hosts.map((host) => ({
|
||||
...host,
|
||||
type: normalizeHostType(host?.type)
|
||||
}));
|
||||
}
|
||||
|
||||
if (!data.pingEval) data.pingEval = DefaultPingEval;
|
||||
|
||||
function addHost() {
|
||||
data.hosts = [...data.hosts, { type: "cmd", host: "", timeout: 1000, count: 3 }];
|
||||
data.hosts = [...data.hosts, { type: "IP4", host: "", timeout: 1000, count: 3 }];
|
||||
}
|
||||
|
||||
function removeHost(index: number) {
|
||||
data.hosts = data.hosts.filter((_: unknown, i: number) => i !== index);
|
||||
}
|
||||
|
||||
function onHostChange(host: PingHost) {
|
||||
const inferredType = inferHostType(host.host);
|
||||
if (inferredType) {
|
||||
host.type = inferredType;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="space-y-4">
|
||||
@@ -39,7 +73,7 @@
|
||||
</div>
|
||||
{#if data.hosts.length > 0}
|
||||
<div class="space-y-3">
|
||||
{#each data.hosts as host, index}
|
||||
{#each data.hosts as host, index (index)}
|
||||
<div class="bg-muted/50 rounded-lg border p-3">
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<span class="text-sm font-medium">Host {index + 1}</span>
|
||||
@@ -47,10 +81,32 @@
|
||||
<X class="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 gap-2">
|
||||
<div class="grid grid-cols-5 gap-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label for="ping-type-{index}">Type</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={normalizeHostType(host.type)}
|
||||
onValueChange={(v) => (host.type = normalizeHostType(v))}
|
||||
>
|
||||
<Select.Trigger id="ping-type-{index}" class="w-full">
|
||||
{normalizeHostType(host.type)}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each PING_HOST_TYPES as typeOption (typeOption)}
|
||||
<Select.Item value={typeOption}>{typeOption}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
<div class="col-span-2 flex flex-col gap-2">
|
||||
<Label for="ping-host-{index}">Host</Label>
|
||||
<Input id="ping-host-{index}" bind:value={host.host} placeholder="8.8.8.8 or example.com" />
|
||||
<Input
|
||||
id="ping-host-{index}"
|
||||
bind:value={host.host}
|
||||
oninput={() => onHostChange(host)}
|
||||
placeholder="8.8.8.8, 2001:db8::1, or example.com"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label for="ping-timeout-{index}">Timeout (ms)</Label>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { Input } from "$lib/components/ui/input/index.js";
|
||||
import { Label } from "$lib/components/ui/label/index.js";
|
||||
import { Textarea } from "$lib/components/ui/textarea/index.js";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as Select from "$lib/components/ui/select/index.js";
|
||||
import { ValidateIpAddress } from "$lib/clientTools";
|
||||
import Plus from "@lucide/svelte/icons/plus";
|
||||
import X from "@lucide/svelte/icons/x";
|
||||
import { DefaultTCPEval } from "$lib/anywhere.js";
|
||||
@@ -10,21 +11,49 @@
|
||||
import { javascript } from "@codemirror/lang-javascript";
|
||||
import { githubLight, githubDark } from "@uiw/codemirror-theme-github";
|
||||
import { mode } from "mode-watcher";
|
||||
import type { PingHostType } from "$lib/types/ping.js";
|
||||
import { PING_HOST_TYPES } from "$lib/types/ping.js";
|
||||
import type { TcpHost, TcpMonitorTypeData } from "$lib/types/tcp.js";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let { data = $bindable() }: { data: any } = $props();
|
||||
let { data = $bindable({ hosts: [], tcpEval: DefaultTCPEval }) }: { data: TcpMonitorTypeData } = $props();
|
||||
|
||||
function normalizeHostType(value: unknown): PingHostType {
|
||||
return typeof value === "string" && PING_HOST_TYPES.includes(value as PingHostType)
|
||||
? (value as PingHostType)
|
||||
: "IP4";
|
||||
}
|
||||
|
||||
function inferHostType(host: string): PingHostType | null {
|
||||
const inferred = ValidateIpAddress((host || "").trim());
|
||||
return inferred === "Invalid" ? null : inferred;
|
||||
}
|
||||
|
||||
// Initialize defaults if not set
|
||||
if (!data.hosts) data.hosts = [];
|
||||
if (!Array.isArray(data.hosts) || data.hosts.length === 0) {
|
||||
data.hosts = [{ type: "IP4", host: "", port: 80, timeout: 1000 }];
|
||||
} else {
|
||||
data.hosts = data.hosts.map((host) => ({
|
||||
...host,
|
||||
type: normalizeHostType(host?.type)
|
||||
}));
|
||||
}
|
||||
|
||||
if (!data.tcpEval) data.tcpEval = DefaultTCPEval;
|
||||
|
||||
function addHost() {
|
||||
data.hosts = [...data.hosts, { type: "tcp", host: "", port: 80, timeout: 1000 }];
|
||||
data.hosts = [...data.hosts, { type: "IP4", host: "", port: 80, timeout: 1000 }];
|
||||
}
|
||||
|
||||
function removeHost(index: number) {
|
||||
data.hosts = data.hosts.filter((_: unknown, i: number) => i !== index);
|
||||
}
|
||||
|
||||
function onHostChange(host: TcpHost) {
|
||||
const inferredType = inferHostType(host.host);
|
||||
if (inferredType) {
|
||||
host.type = inferredType;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="space-y-4">
|
||||
@@ -38,7 +67,7 @@
|
||||
</div>
|
||||
{#if data.hosts.length > 0}
|
||||
<div class="space-y-3">
|
||||
{#each data.hosts as host, index}
|
||||
{#each data.hosts as host, index (index)}
|
||||
<div class="bg-muted/50 rounded-lg border p-3">
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<span class="text-sm font-medium">Host {index + 1}</span>
|
||||
@@ -46,10 +75,32 @@
|
||||
<X class="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div class="col-span-1 flex flex-col gap-2">
|
||||
<div class="grid grid-cols-5 gap-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label for="tcp-type-{index}">Type</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={normalizeHostType(host.type)}
|
||||
onValueChange={(v) => (host.type = normalizeHostType(v))}
|
||||
>
|
||||
<Select.Trigger id="tcp-type-{index}" class="w-full">
|
||||
{normalizeHostType(host.type)}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each PING_HOST_TYPES as typeOption (typeOption)}
|
||||
<Select.Item value={typeOption}>{typeOption}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
<div class="col-span-2 flex flex-col gap-2">
|
||||
<Label for="tcp-host-{index}">Host</Label>
|
||||
<Input id="tcp-host-{index}" bind:value={host.host} placeholder="example.com" />
|
||||
<Input
|
||||
id="tcp-host-{index}"
|
||||
bind:value={host.host}
|
||||
oninput={() => onHostChange(host)}
|
||||
placeholder="example.com"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label for="tcp-port-{index}">Port</Label>
|
||||
|
||||
Reference in New Issue
Block a user