feat: Add Domain Monitor functionality with WHOIS integration

- Updated package.json to include whois-json dependency.
- Created DomainMonitorCriteria class for evaluating domain monitoring criteria.
- Added DomainMonitorResponse interface to define the structure of domain monitoring responses.
- Introduced MonitorStepDomainMonitor interface and utility for managing domain monitor steps.
- Developed DomainMonitorStepForm component for user input on domain monitoring settings.
- Implemented DomainMonitorView component to display monitoring results and details.
- Added DomainMonitorUtil class for querying domain information using WHOIS data.
- Included parsing methods for name servers and domain status in DomainMonitorUtil.
This commit is contained in:
Nawaz Dhandala
2026-02-16 19:10:44 +00:00
parent fb661126d4
commit 7ab3dfe043
20 changed files with 1272 additions and 21 deletions

View File

@@ -0,0 +1,142 @@
import DataToProcess from "../DataToProcess";
import CompareCriteria from "./CompareCriteria";
import {
CheckOn,
CriteriaFilter,
FilterType,
} from "../../../../Types/Monitor/CriteriaFilter";
import DomainMonitorResponse from "../../../../Types/Monitor/DomainMonitor/DomainMonitorResponse";
import ProbeMonitorResponse from "../../../../Types/Probe/ProbeMonitorResponse";
import CaptureSpan from "../../Telemetry/CaptureSpan";
export default class DomainMonitorCriteria {
@CaptureSpan()
public static async isMonitorInstanceCriteriaFilterMet(input: {
dataToProcess: DataToProcess;
criteriaFilter: CriteriaFilter;
}): Promise<string | null> {
let threshold: number | string | undefined | null =
input.criteriaFilter.value;
const dataToProcess: ProbeMonitorResponse =
input.dataToProcess as ProbeMonitorResponse;
const domainResponse: DomainMonitorResponse | undefined =
dataToProcess.domainResponse;
// Check domain expires in days
if (input.criteriaFilter.checkOn === CheckOn.DomainExpiresDaysIn) {
threshold = CompareCriteria.convertToNumber(threshold);
if (threshold === null || threshold === undefined) {
return null;
}
if (!domainResponse?.expiresDate) {
return null;
}
const expiresDate: Date = new Date(domainResponse.expiresDate);
const now: Date = new Date();
const diffMs: number = expiresDate.getTime() - now.getTime();
const diffDays: number = Math.ceil(diffMs / (1000 * 60 * 60 * 24));
return CompareCriteria.compareCriteriaNumbers({
value: diffDays,
threshold: threshold as number,
criteriaFilter: input.criteriaFilter,
});
}
// Check domain registrar
if (input.criteriaFilter.checkOn === CheckOn.DomainRegistrar) {
if (!domainResponse?.registrar) {
return null;
}
return CompareCriteria.compareCriteriaStrings({
value: domainResponse.registrar,
threshold: String(threshold),
criteriaFilter: input.criteriaFilter,
});
}
// Check domain name server
if (input.criteriaFilter.checkOn === CheckOn.DomainNameServer) {
if (
!domainResponse?.nameServers ||
domainResponse.nameServers.length === 0
) {
return null;
}
// Check if any name server matches the criteria
for (const nameServer of domainResponse.nameServers) {
const result: string | null = CompareCriteria.compareCriteriaStrings({
value: nameServer,
threshold: String(threshold),
criteriaFilter: input.criteriaFilter,
});
if (result) {
return `Domain name server: ${result}`;
}
}
return null;
}
// Check domain status code
if (input.criteriaFilter.checkOn === CheckOn.DomainStatusCode) {
if (
!domainResponse?.domainStatus ||
domainResponse.domainStatus.length === 0
) {
return null;
}
// Check if any status matches the criteria
for (const status of domainResponse.domainStatus) {
const result: string | null = CompareCriteria.compareCriteriaStrings({
value: status,
threshold: String(threshold),
criteriaFilter: input.criteriaFilter,
});
if (result) {
return `Domain status: ${result}`;
}
}
return null;
}
// Check if domain is expired
if (input.criteriaFilter.checkOn === CheckOn.DomainIsExpired) {
const isTrue: boolean =
input.criteriaFilter.filterType === FilterType.True;
const isFalse: boolean =
input.criteriaFilter.filterType === FilterType.False;
if (!domainResponse?.expiresDate) {
return null;
}
const expiresDate: Date = new Date(domainResponse.expiresDate);
const now: Date = new Date();
const isExpired: boolean = expiresDate.getTime() < now.getTime();
if (isExpired && isTrue) {
return `Domain is expired (expired on ${domainResponse.expiresDate}).`;
}
if (!isExpired && isFalse) {
return `Domain is not expired (expires on ${domainResponse.expiresDate}).`;
}
return null;
}
return null;
}
}

View File

@@ -13,6 +13,7 @@ import TraceMonitorCriteria from "./Criteria/TraceMonitorCriteria";
import ExceptionMonitorCriteria from "./Criteria/ExceptionMonitorCriteria";
import SnmpMonitorCriteria from "./Criteria/SnmpMonitorCriteria";
import DnsMonitorCriteria from "./Criteria/DnsMonitorCriteria";
import DomainMonitorCriteria from "./Criteria/DomainMonitorCriteria";
import MonitorCriteriaMessageBuilder from "./MonitorCriteriaMessageBuilder";
import MonitorCriteriaDataExtractor from "./MonitorCriteriaDataExtractor";
import MonitorCriteriaMessageFormatter from "./MonitorCriteriaMessageFormatter";
@@ -506,6 +507,18 @@ ${contextBlock}
}
}
if (input.monitor.monitorType === MonitorType.Domain) {
const domainMonitorResult: string | null =
await DomainMonitorCriteria.isMonitorInstanceCriteriaFilterMet({
dataToProcess: input.dataToProcess,
criteriaFilter: input.criteriaFilter,
});
if (domainMonitorResult) {
return domainMonitorResult;
}
}
return null;
}

View File

@@ -17,6 +17,7 @@ import SnmpMonitorResponse, {
import DnsMonitorResponse, {
DnsRecordResponse,
} from "../../../Types/Monitor/DnsMonitor/DnsMonitorResponse";
import DomainMonitorResponse from "../../../Types/Monitor/DomainMonitor/DomainMonitorResponse";
import Typeof from "../../../Types/Typeof";
import VMUtil from "../VM/VMAPI";
import DataToProcess from "./DataToProcess";
@@ -277,6 +278,26 @@ export default class MonitorTemplateUtil {
);
}
}
if (data.monitorType === MonitorType.Domain) {
const domainResponse: DomainMonitorResponse | undefined = (
data.dataToProcess as ProbeMonitorResponse
).domainResponse;
storageMap = {
isOnline: (data.dataToProcess as ProbeMonitorResponse).isOnline,
responseTimeInMs: domainResponse?.responseTimeInMs,
failureCause: domainResponse?.failureCause,
domainName: domainResponse?.domainName,
registrar: domainResponse?.registrar,
createdDate: domainResponse?.createdDate,
updatedDate: domainResponse?.updatedDate,
expiresDate: domainResponse?.expiresDate,
nameServers: domainResponse?.nameServers,
domainStatus: domainResponse?.domainStatus,
dnssec: domainResponse?.dnssec,
} as JSONObject;
}
} catch (err) {
logger.error(err);
}

View File

@@ -67,6 +67,13 @@ export enum CheckOn {
DnsRecordValue = "DNS Record Value",
DnssecIsValid = "DNSSEC Is Valid",
DnsRecordExists = "DNS Record Exists",
// Domain monitors.
DomainExpiresDaysIn = "Domain Expires In Days",
DomainRegistrar = "Domain Registrar",
DomainNameServer = "Domain Name Server",
DomainStatusCode = "Domain Status Code",
DomainIsExpired = "Domain Is Expired",
}
export interface ServerMonitorOptions {
@@ -151,7 +158,8 @@ export class CriteriaFilterUtil {
if (
checkOn === CheckOn.IsOnline ||
checkOn === CheckOn.SnmpIsOnline ||
checkOn === CheckOn.DnsIsOnline
checkOn === CheckOn.DnsIsOnline ||
checkOn === CheckOn.DomainIsExpired
) {
return false;
}

View File

@@ -0,0 +1,15 @@
export default interface DomainMonitorResponse {
isOnline: boolean;
responseTimeInMs: number;
failureCause: string;
domainName: string;
registrar?: string | undefined;
registrarUrl?: string | undefined;
createdDate?: string | undefined;
updatedDate?: string | undefined;
expiresDate?: string | undefined;
nameServers?: Array<string> | undefined;
dnssec?: string | undefined;
domainStatus?: Array<string> | undefined;
isTimeout?: boolean | undefined;
}

View File

@@ -32,6 +32,9 @@ import MonitorStepSnmpMonitor, {
import MonitorStepDnsMonitor, {
MonitorStepDnsMonitorUtil,
} from "./MonitorStepDnsMonitor";
import MonitorStepDomainMonitor, {
MonitorStepDomainMonitorUtil,
} from "./MonitorStepDomainMonitor";
import Zod, { ZodSchema } from "../../Utils/Schema/Zod";
export interface MonitorStepType {
@@ -78,6 +81,9 @@ export interface MonitorStepType {
// DNS monitor
dnsMonitor?: MonitorStepDnsMonitor | undefined;
// Domain monitor
domainMonitor?: MonitorStepDomainMonitor | undefined;
}
export default class MonitorStep extends DatabaseProperty {
@@ -105,6 +111,7 @@ export default class MonitorStep extends DatabaseProperty {
exceptionMonitor: undefined,
snmpMonitor: undefined,
dnsMonitor: undefined,
domainMonitor: undefined,
};
}
@@ -137,6 +144,7 @@ export default class MonitorStep extends DatabaseProperty {
exceptionMonitor: undefined,
snmpMonitor: undefined,
dnsMonitor: undefined,
domainMonitor: undefined,
};
return monitorStep;
@@ -237,6 +245,13 @@ export default class MonitorStep extends DatabaseProperty {
return this;
}
public setDomainMonitor(
domainMonitor: MonitorStepDomainMonitor,
): MonitorStep {
this.data!.domainMonitor = domainMonitor;
return this;
}
public setCustomCode(customCode: string): MonitorStep {
this.data!.customCode = customCode;
return this;
@@ -355,6 +370,16 @@ export default class MonitorStep extends DatabaseProperty {
}
}
if (monitorType === MonitorType.Domain) {
if (!value.data.domainMonitor) {
return "Domain configuration is required";
}
if (!value.data.domainMonitor.domainName) {
return "Domain name is required";
}
}
return null;
}
@@ -403,6 +428,9 @@ export default class MonitorStep extends DatabaseProperty {
dnsMonitor: this.data.dnsMonitor
? MonitorStepDnsMonitorUtil.toJSON(this.data.dnsMonitor)
: undefined,
domainMonitor: this.data.domainMonitor
? MonitorStepDomainMonitorUtil.toJSON(this.data.domainMonitor)
: undefined,
},
});
}
@@ -511,6 +539,9 @@ export default class MonitorStep extends DatabaseProperty {
dnsMonitor: json["dnsMonitor"]
? (json["dnsMonitor"] as JSONObject)
: undefined,
domainMonitor: json["domainMonitor"]
? (json["domainMonitor"] as JSONObject)
: undefined,
}) as any;
return monitorStep;
@@ -537,6 +568,7 @@ export default class MonitorStep extends DatabaseProperty {
metricMonitor: Zod.any().optional(),
snmpMonitor: Zod.any().optional(),
dnsMonitor: Zod.any().optional(),
domainMonitor: Zod.any().optional(),
}).openapi({
type: "object",
example: {

View File

@@ -0,0 +1,33 @@
import { JSONObject } from "../JSON";
export default interface MonitorStepDomainMonitor {
domainName: string;
timeout: number;
retries: number;
}
export class MonitorStepDomainMonitorUtil {
public static getDefault(): MonitorStepDomainMonitor {
return {
domainName: "",
timeout: 10000,
retries: 3,
};
}
public static fromJSON(json: JSONObject): MonitorStepDomainMonitor {
return {
domainName: (json["domainName"] as string) || "",
timeout: (json["timeout"] as number) || 10000,
retries: (json["retries"] as number) || 3,
};
}
public static toJSON(monitor: MonitorStepDomainMonitor): JSONObject {
return {
domainName: monitor.domainName,
timeout: monitor.timeout,
retries: monitor.retries,
};
}
}

View File

@@ -29,6 +29,9 @@ enum MonitorType {
// DNS monitoring
DNS = "DNS",
// Domain registration monitoring
Domain = "Domain",
}
export default MonitorType;
@@ -58,6 +61,7 @@ export class MonitorTypeHelper {
MonitorType.Port,
MonitorType.DNS,
MonitorType.SSLCertificate,
MonitorType.Domain,
],
},
{
@@ -239,6 +243,13 @@ export class MonitorTypeHelper {
"This monitor type lets you monitor DNS resolution for your domains, verify record values, and check DNSSEC validity.",
icon: IconProp.GlobeAlt,
},
{
monitorType: MonitorType.Domain,
title: "Domain",
description:
"This monitor type lets you monitor domain registration health — expiry dates, registrar info, nameserver delegation, and WHOIS status.",
icon: IconProp.Globe,
},
];
return monitorTypeProps;
@@ -285,7 +296,8 @@ export class MonitorTypeHelper {
monitorType === MonitorType.SyntheticMonitor ||
monitorType === MonitorType.CustomJavaScriptCode ||
monitorType === MonitorType.SNMP ||
monitorType === MonitorType.DNS;
monitorType === MonitorType.DNS ||
monitorType === MonitorType.Domain;
return isProbeableMonitor;
}
@@ -308,6 +320,7 @@ export class MonitorTypeHelper {
MonitorType.Exceptions,
MonitorType.SNMP,
MonitorType.DNS,
MonitorType.Domain,
];
}
@@ -341,7 +354,8 @@ export class MonitorTypeHelper {
monitorType === MonitorType.SyntheticMonitor ||
monitorType === MonitorType.CustomJavaScriptCode ||
monitorType === MonitorType.SNMP ||
monitorType === MonitorType.DNS
monitorType === MonitorType.DNS ||
monitorType === MonitorType.Domain
) {
return true;
}

View File

@@ -8,6 +8,7 @@ import SslMonitorResponse from "../Monitor/SSLMonitor/SslMonitorResponse";
import SyntheticMonitorResponse from "../Monitor/SyntheticMonitors/SyntheticMonitorResponse";
import SnmpMonitorResponse from "../Monitor/SnmpMonitor/SnmpMonitorResponse";
import DnsMonitorResponse from "../Monitor/DnsMonitor/DnsMonitorResponse";
import DomainMonitorResponse from "../Monitor/DomainMonitor/DomainMonitorResponse";
import MonitorEvaluationSummary from "../Monitor/MonitorEvaluationSummary";
import ObjectID from "../ObjectID";
import Port from "../Port";
@@ -32,6 +33,7 @@ export default interface ProbeMonitorResponse {
customCodeMonitorResponse?: CustomCodeMonitorResponse | undefined;
snmpResponse?: SnmpMonitorResponse | undefined;
dnsResponse?: DnsMonitorResponse | undefined;
domainResponse?: DomainMonitorResponse | undefined;
monitoredAt: Date;
isTimeout?: boolean | undefined;
ingestedAt?: Date | undefined;

View File

@@ -0,0 +1,101 @@
import React, { FunctionComponent, ReactElement, useState } from "react";
import MonitorStepDomainMonitor from "Common/Types/Monitor/MonitorStepDomainMonitor";
import Input, { InputType } from "Common/UI/Components/Input/Input";
import FieldLabelElement from "Common/UI/Components/Forms/Fields/FieldLabel";
import Button, { ButtonStyleType } from "Common/UI/Components/Button/Button";
export interface ComponentProps {
monitorStepDomainMonitor: MonitorStepDomainMonitor;
onChange: (value: MonitorStepDomainMonitor) => void;
}
const DomainMonitorStepForm: FunctionComponent<ComponentProps> = (
props: ComponentProps,
): ReactElement => {
const [showAdvancedOptions, setShowAdvancedOptions] =
useState<boolean>(false);
return (
<div className="space-y-5">
<div>
<FieldLabelElement
title="Domain Name"
description="The domain name to monitor (e.g. example.com)"
required={true}
/>
<Input
initialValue={props.monitorStepDomainMonitor.domainName}
placeholder="example.com"
onChange={(value: string) => {
props.onChange({
...props.monitorStepDomainMonitor,
domainName: value,
});
}}
/>
</div>
{!showAdvancedOptions && (
<div className="mt-1 -ml-3">
<Button
title="Advanced: Timeout and Retries"
buttonStyle={ButtonStyleType.SECONDARY_LINK}
onClick={() => {
setShowAdvancedOptions(true);
}}
/>
</div>
)}
{showAdvancedOptions && (
<div className="space-y-4 border p-4 rounded-md bg-gray-50">
<h4 className="font-medium">Advanced Options</h4>
<div>
<FieldLabelElement
title="Timeout (ms)"
description="How long to wait for a WHOIS response before timing out"
required={false}
/>
<Input
initialValue={
props.monitorStepDomainMonitor.timeout?.toString() || "10000"
}
placeholder="10000"
type={InputType.NUMBER}
onChange={(value: string) => {
props.onChange({
...props.monitorStepDomainMonitor,
timeout: parseInt(value) || 10000,
});
}}
/>
</div>
<div>
<FieldLabelElement
title="Retries"
description="Number of times to retry on failure"
required={false}
/>
<Input
initialValue={
props.monitorStepDomainMonitor.retries?.toString() || "3"
}
placeholder="3"
type={InputType.NUMBER}
onChange={(value: string) => {
props.onChange({
...props.monitorStepDomainMonitor,
retries: parseInt(value) || 3,
});
}}
/>
</div>
</div>
)}
</div>
);
};
export default DomainMonitorStepForm;

View File

@@ -79,6 +79,10 @@ import DnsMonitorStepForm from "./DnsMonitor/DnsMonitorStepForm";
import MonitorStepDnsMonitor, {
MonitorStepDnsMonitorUtil,
} from "Common/Types/Monitor/MonitorStepDnsMonitor";
import DomainMonitorStepForm from "./DomainMonitor/DomainMonitorStepForm";
import MonitorStepDomainMonitor, {
MonitorStepDomainMonitorUtil,
} from "Common/Types/Monitor/MonitorStepDomainMonitor";
export interface ComponentProps {
monitorStatusDropdownOptions: Array<DropdownOption>;
@@ -812,6 +816,24 @@ return {
</Card>
)}
{props.monitorType === MonitorType.Domain && (
<Card
title="Domain Monitor Configuration"
description="Configure the domain registration monitoring settings"
>
<DomainMonitorStepForm
monitorStepDomainMonitor={
monitorStep.data?.domainMonitor ||
MonitorStepDomainMonitorUtil.getDefault()
}
onChange={(value: MonitorStepDomainMonitor) => {
monitorStep.setDomainMonitor(value);
props.onChange?.(MonitorStep.clone(monitorStep));
}}
/>
</Card>
)}
{/* Code Monitor Section */}
{isCodeMonitor && (
<Card

View File

@@ -310,6 +310,20 @@ const MonitorStepElement: FunctionComponent<ComponentProps> = (
placeholder: "0",
},
];
} else if (props.monitorType === MonitorType.Domain) {
fields = [
{
key: "domainMonitor",
title: "Domain Name",
description: "The domain name being monitored via WHOIS.",
fieldType: FieldType.Element,
placeholder: "No data entered",
getElement: (item: MonitorStepType): ReactElement => {
const domainMonitor: any = item.domainMonitor;
return <p>{domainMonitor?.domainName || "-"}</p>;
},
},
];
} else if (props.monitorType === MonitorType.Logs) {
logFields = [];

View File

@@ -0,0 +1,172 @@
import OneUptimeDate from "Common/Types/Date";
import ProbeMonitorResponse from "Common/Types/Probe/ProbeMonitorResponse";
import DomainMonitorResponse from "Common/Types/Monitor/DomainMonitor/DomainMonitorResponse";
import InfoCard from "Common/UI/Components/InfoCard/InfoCard";
import React, { FunctionComponent, ReactElement } from "react";
export interface ComponentProps {
probeMonitorResponse: ProbeMonitorResponse;
probeName?: string | undefined;
}
const DomainMonitorView: FunctionComponent<ComponentProps> = (
props: ComponentProps,
): ReactElement => {
const domainResponse: DomainMonitorResponse | undefined =
props.probeMonitorResponse?.domainResponse;
let responseTimeInMs: number = domainResponse?.responseTimeInMs || 0;
if (responseTimeInMs > 0) {
responseTimeInMs = Math.round(responseTimeInMs);
}
type FormatDateText = (dateStr: string | undefined) => string;
const formatDateText: FormatDateText = (
dateStr: string | undefined,
): string => {
if (!dateStr) {
return "-";
}
try {
const date: Date = new Date(dateStr);
return OneUptimeDate.getDateAsUserFriendlyLocalFormattedString(date);
} catch {
return dateStr;
}
};
return (
<div className="space-y-5">
<div className="flex space-x-3">
<InfoCard
className="w-1/5 shadow-none border-2 border-gray-100"
title="Probe"
value={props.probeName || "-"}
/>
<InfoCard
className="w-1/5 shadow-none border-2 border-gray-100"
title="Status"
value={props.probeMonitorResponse.isOnline ? "Online" : "Offline"}
/>
<InfoCard
className="w-1/5 shadow-none border-2 border-gray-100"
title="Response Time"
value={responseTimeInMs ? responseTimeInMs + " ms" : "-"}
/>
<InfoCard
className="w-1/5 shadow-none border-2 border-gray-100"
title="Expires At"
value={formatDateText(domainResponse?.expiresDate)}
/>
<InfoCard
className="w-1/5 shadow-none border-2 border-gray-100"
title="Monitored At"
value={
props.probeMonitorResponse?.monitoredAt
? OneUptimeDate.getDateAsUserFriendlyLocalFormattedString(
props.probeMonitorResponse.monitoredAt,
)
: "-"
}
/>
</div>
<div className="flex space-x-3">
<InfoCard
className="w-1/3 shadow-none border-2 border-gray-100"
title="Registrar"
value={domainResponse?.registrar || "-"}
/>
<InfoCard
className="w-1/3 shadow-none border-2 border-gray-100"
title="Created"
value={formatDateText(domainResponse?.createdDate)}
/>
<InfoCard
className="w-1/3 shadow-none border-2 border-gray-100"
title="DNSSEC"
value={domainResponse?.dnssec || "-"}
/>
</div>
{props.probeMonitorResponse.failureCause && (
<div className="flex space-x-3">
<InfoCard
className="w-full shadow-none border-2 border-gray-100"
title="Error"
value={props.probeMonitorResponse.failureCause?.toString() || "-"}
/>
</div>
)}
{/* Name Servers Section */}
{domainResponse?.nameServers && domainResponse.nameServers.length > 0 && (
<div className="space-y-3">
<h3 className="text-sm font-medium text-gray-700">Name Servers</h3>
<div className="border rounded-md overflow-hidden">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Name Server
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{domainResponse.nameServers.map(
(ns: string, index: number) => {
return (
<tr key={index}>
<td className="px-4 py-2 text-sm text-gray-900 font-mono">
{ns}
</td>
</tr>
);
},
)}
</tbody>
</table>
</div>
</div>
)}
{/* Domain Status Section */}
{domainResponse?.domainStatus &&
domainResponse.domainStatus.length > 0 && (
<div className="space-y-3">
<h3 className="text-sm font-medium text-gray-700">
Domain Status Codes
</h3>
<div className="border rounded-md overflow-hidden">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Status
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{domainResponse.domainStatus.map(
(status: string, index: number) => {
return (
<tr key={index}>
<td className="px-4 py-2 text-sm text-gray-900 font-mono">
{status}
</td>
</tr>
);
},
)}
</tbody>
</table>
</div>
</div>
)}
</div>
);
};
export default DomainMonitorView;

View File

@@ -8,6 +8,7 @@ import SyntheticMonitorView from "./SyntheticMonitorView";
import WebsiteMonitorSummaryView from "./WebsiteMonitorView";
import SnmpMonitorView from "./SnmpMonitorView";
import DnsMonitorView from "./DnsMonitorView";
import DomainMonitorView from "./DomainMonitorView";
import IncomingMonitorRequest from "Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest";
import IncomingEmailMonitorRequest from "Common/Types/Monitor/IncomingEmailMonitor/IncomingEmailMonitorRequest";
import MonitorType, {
@@ -131,6 +132,15 @@ const SummaryInfo: FunctionComponent<ComponentProps> = (
);
}
if (props.monitorType === MonitorType.Domain) {
summaryComponent = (
<DomainMonitorView
probeMonitorResponse={probeMonitorResponse}
probeName={props.probeName}
/>
);
}
return (
<div key={key} className="space-y-6">
{summaryComponent}

View File

@@ -295,6 +295,18 @@ export default class CriteriaFilterUtil {
});
}
if (monitorType === MonitorType.Domain) {
options = options.filter((i: DropdownOption) => {
return (
i.value === CheckOn.DomainExpiresDaysIn ||
i.value === CheckOn.DomainRegistrar ||
i.value === CheckOn.DomainNameServer ||
i.value === CheckOn.DomainStatusCode ||
i.value === CheckOn.DomainIsExpired
);
});
}
return options;
}
@@ -570,6 +582,40 @@ export default class CriteriaFilterUtil {
});
}
if (checkOn === CheckOn.DomainExpiresDaysIn) {
options = options.filter((i: DropdownOption) => {
return (
i.value === FilterType.GreaterThan ||
i.value === FilterType.LessThan ||
i.value === FilterType.LessThanOrEqualTo ||
i.value === FilterType.GreaterThanOrEqualTo
);
});
}
if (
checkOn === CheckOn.DomainRegistrar ||
checkOn === CheckOn.DomainNameServer ||
checkOn === CheckOn.DomainStatusCode
) {
options = options.filter((i: DropdownOption) => {
return (
i.value === FilterType.Contains ||
i.value === FilterType.NotContains ||
i.value === FilterType.EqualTo ||
i.value === FilterType.NotEqualTo ||
i.value === FilterType.StartsWith ||
i.value === FilterType.EndsWith
);
});
}
if (checkOn === CheckOn.DomainIsExpired) {
options = options.filter((i: DropdownOption) => {
return i.value === FilterType.True || i.value === FilterType.False;
});
}
return options;
}
@@ -709,6 +755,22 @@ export default class CriteriaFilterUtil {
return "192.168.1.1";
}
if (checkOn === CheckOn.DomainExpiresDaysIn) {
return "30";
}
if (checkOn === CheckOn.DomainRegistrar) {
return "GoDaddy";
}
if (checkOn === CheckOn.DomainNameServer) {
return "ns1.example.com";
}
if (checkOn === CheckOn.DomainStatusCode) {
return "clientTransferProhibited";
}
return "";
}
}

4
Probe/Types/whois-json.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
declare module "whois-json" {
function whoisJson(domain: string, options?: object): Promise<any>;
export default whoisJson;
}

View File

@@ -16,6 +16,9 @@ import MonitorStepSnmpMonitor from "Common/Types/Monitor/MonitorStepSnmpMonitor"
import DnsMonitorUtil from "./MonitorTypes/DnsMonitor";
import DnsMonitorResponse from "Common/Types/Monitor/DnsMonitor/DnsMonitorResponse";
import MonitorStepDnsMonitor from "Common/Types/Monitor/MonitorStepDnsMonitor";
import DomainMonitorUtil from "./MonitorTypes/DomainMonitor";
import DomainMonitorResponse from "Common/Types/Monitor/DomainMonitor/DomainMonitorResponse";
import MonitorStepDomainMonitor from "Common/Types/Monitor/MonitorStepDomainMonitor";
import HTTPMethod from "Common/Types/API/HTTPMethod";
import URL from "Common/Types/API/URL";
import OneUptimeDate from "Common/Types/Date";
@@ -546,6 +549,38 @@ export default class MonitorUtil {
result.dnsResponse = response;
}
if (monitorType === MonitorType.Domain) {
if (!monitorStep.data?.domainMonitor) {
result.failureCause = "Domain configuration not specified";
return result;
}
const domainConfig: MonitorStepDomainMonitor =
monitorStep.data.domainMonitor;
if (!domainConfig.domainName) {
result.failureCause = "Domain name not specified";
return result;
}
const response: DomainMonitorResponse | null =
await DomainMonitorUtil.query(domainConfig, {
retry: PROBE_MONITOR_RETRY_LIMIT,
monitorId: monitorId,
timeout: domainConfig.timeout || 10000,
});
if (!response) {
return null;
}
result.isOnline = response.isOnline;
result.isTimeout = response.isTimeout;
result.responseTimeInMs = response.responseTimeInMs;
result.failureCause = response.failureCause;
result.domainResponse = response;
}
// update the monitoredAt time to the current time.
result.monitoredAt = OneUptimeDate.getCurrentDate();

View File

@@ -0,0 +1,209 @@
import OnlineCheck from "../../OnlineCheck";
import logger from "Common/Server/Utils/Logger";
import ObjectID from "Common/Types/ObjectID";
import Sleep from "Common/Types/Sleep";
import MonitorStepDomainMonitor from "Common/Types/Monitor/MonitorStepDomainMonitor";
import DomainMonitorResponse from "Common/Types/Monitor/DomainMonitor/DomainMonitorResponse";
import whoisJson from "whois-json";
export interface DomainQueryOptions {
timeout?: number | undefined;
retry?: number | undefined;
currentRetryCount?: number | undefined;
monitorId?: ObjectID | undefined;
isOnlineCheckRequest?: boolean | undefined;
}
export default class DomainMonitorUtil {
public static async query(
config: MonitorStepDomainMonitor,
options?: DomainQueryOptions,
): Promise<DomainMonitorResponse | null> {
if (!options) {
options = {};
}
if (options?.currentRetryCount === undefined) {
options.currentRetryCount = 1;
}
logger.debug(
`Domain Query: ${options?.monitorId?.toString()} ${config.domainName} - Retry: ${options?.currentRetryCount}`,
);
const startTime: [number, number] = process.hrtime();
try {
const result: any = await whoisJson(config.domainName);
const endTime: [number, number] = process.hrtime(startTime);
const responseTimeInMs: number = Math.ceil(
(endTime[0] * 1000000000 + endTime[1]) / 1000000,
);
// Parse WHOIS response
const whoisData: any = Array.isArray(result) ? result[0] : result;
const nameServers: Array<string> = DomainMonitorUtil.parseNameServers(
whoisData?.nameServer || whoisData?.nameServers,
);
const domainStatus: Array<string> = DomainMonitorUtil.parseDomainStatus(
whoisData?.domainStatus || whoisData?.status,
);
logger.debug(
`Domain Query success: ${options?.monitorId?.toString()} ${config.domainName} - Response Time: ${responseTimeInMs}ms`,
);
return {
isOnline: true,
responseTimeInMs: responseTimeInMs,
failureCause: "",
domainName: config.domainName,
registrar: whoisData?.registrar || whoisData?.registrarName || undefined,
registrarUrl:
whoisData?.registrarUrl ||
whoisData?.registrarURL ||
whoisData?.referralUrl ||
undefined,
createdDate:
whoisData?.creationDate ||
whoisData?.createdDate ||
whoisData?.created ||
undefined,
updatedDate:
whoisData?.updatedDate ||
whoisData?.lastUpdated ||
whoisData?.changed ||
undefined,
expiresDate:
whoisData?.registrarRegistrationExpirationDate ||
whoisData?.registryExpiryDate ||
whoisData?.expirationDate ||
whoisData?.expiresDate ||
whoisData?.expires ||
whoisData?.expiryDate ||
undefined,
nameServers: nameServers.length > 0 ? nameServers : undefined,
dnssec: whoisData?.dnssec || whoisData?.DNSSEC || undefined,
domainStatus: domainStatus.length > 0 ? domainStatus : undefined,
};
} catch (err: unknown) {
logger.debug(
`Domain Query error: ${options?.monitorId?.toString()} ${config.domainName}`,
);
logger.debug(err);
if (!options) {
options = {};
}
if (!options.currentRetryCount) {
options.currentRetryCount = 0;
}
if (options.currentRetryCount < (options.retry || config.retries || 3)) {
options.currentRetryCount++;
await Sleep.sleep(1000);
return await DomainMonitorUtil.query(config, options);
}
// Check if the probe is online
if (!options.isOnlineCheckRequest) {
if (!(await OnlineCheck.canProbeMonitorWebsiteMonitors())) {
logger.error(
`DomainMonitor - Probe is not online. Cannot query ${options?.monitorId?.toString()} ${config.domainName} - ERROR: ${err}`,
);
return null;
}
}
const endTime: [number, number] = process.hrtime(startTime);
const responseTimeInMs: number = Math.ceil(
(endTime[0] * 1000000000 + endTime[1]) / 1000000,
);
// Check if timeout
const isTimeout: boolean =
(err as Error).message?.toLowerCase().includes("timeout") ||
(err as Error).message?.toLowerCase().includes("timed out") ||
(err as Error).message?.toLowerCase().includes("etimeout");
if (isTimeout) {
return {
isOnline: false,
isTimeout: true,
responseTimeInMs: responseTimeInMs,
failureCause:
"Request was tried " +
options.currentRetryCount +
" times and it timed out.",
domainName: config.domainName,
};
}
return {
isOnline: false,
isTimeout: false,
responseTimeInMs: responseTimeInMs,
failureCause: (err as Error).message || (err as Error).toString(),
domainName: config.domainName,
};
}
}
private static parseNameServers(
value: string | Array<string> | undefined,
): Array<string> {
if (!value) {
return [];
}
if (Array.isArray(value)) {
return value.map((ns: string) => {
return ns.trim().toLowerCase();
});
}
if (typeof value === "string") {
return value
.split(/[\s,]+/)
.map((ns: string) => {
return ns.trim().toLowerCase();
})
.filter((ns: string) => {
return ns.length > 0;
});
}
return [];
}
private static parseDomainStatus(
value: string | Array<string> | undefined,
): Array<string> {
if (!value) {
return [];
}
if (Array.isArray(value)) {
return value.map((status: string) => {
return status.trim();
});
}
if (typeof value === "string") {
return value
.split(/[\n,]+/)
.map((status: string) => {
return status.trim();
})
.filter((status: string) => {
return status.length > 0;
});
}
return [];
}
}

375
Probe/package-lock.json generated
View File

@@ -18,7 +18,8 @@
"net-snmp": "^3.26.1",
"ping": "^0.4.4",
"playwright": "^1.57.0",
"ts-node": "^10.9.1"
"ts-node": "^10.9.1",
"whois-json": "^2.0.4"
},
"devDependencies": {
"@types/jest": "^27.5.2",
@@ -288,6 +289,7 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.6.tgz",
"integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==",
"dev": true,
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.23.5",
@@ -1279,7 +1281,8 @@
"node_modules/@types/node": {
"version": "17.0.45",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz",
"integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="
"integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==",
"peer": true
},
"node_modules/@types/ping": {
"version": "0.4.4",
@@ -1366,7 +1369,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"engines": {
"node": ">=8"
}
@@ -1587,6 +1589,7 @@
"url": "https://github.com/sponsors/ai"
}
],
"peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001565",
"electron-to-chromium": "^1.4.601",
@@ -1649,11 +1652,20 @@
"node": ">=6"
}
},
"node_modules/camel-case": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
"integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==",
"license": "MIT",
"dependencies": {
"no-case": "^2.2.0",
"upper-case": "^1.1.1"
}
},
"node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true,
"engines": {
"node": ">=6"
}
@@ -1693,6 +1705,32 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/change-case": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/change-case/-/change-case-3.1.0.tgz",
"integrity": "sha512-2AZp7uJZbYEzRPsFoa+ijKdvp9zsrnnt6+yFokfwEpeJm0xuJDVoxiRCAaTzyJND8GJkofo2IcKWaUZ/OECVzw==",
"license": "MIT",
"dependencies": {
"camel-case": "^3.0.0",
"constant-case": "^2.0.0",
"dot-case": "^2.1.0",
"header-case": "^1.0.0",
"is-lower-case": "^1.1.0",
"is-upper-case": "^1.1.0",
"lower-case": "^1.1.1",
"lower-case-first": "^1.0.0",
"no-case": "^2.3.2",
"param-case": "^2.1.0",
"pascal-case": "^2.0.0",
"path-case": "^2.1.0",
"sentence-case": "^2.1.0",
"snake-case": "^2.1.0",
"swap-case": "^1.1.0",
"title-case": "^2.1.0",
"upper-case": "^1.1.1",
"upper-case-first": "^1.1.0"
}
},
"node_modules/char-regex": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
@@ -1817,6 +1855,16 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/constant-case": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/constant-case/-/constant-case-2.0.0.tgz",
"integrity": "sha512-eS0N9WwmjTqrOmR3o83F5vW8Z+9R1HnVz3xmzT2PMFug9ly+Au/fxRWlEBSb6LcZwspSsEn9Xs1uw9YgzAg1EQ==",
"license": "MIT",
"dependencies": {
"snake-case": "^2.1.0",
"upper-case": "^1.1.1"
}
},
"node_modules/convert-source-map": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
@@ -1859,12 +1907,27 @@
}
}
},
"node_modules/decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/dedent": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
"integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==",
"dev": true
},
"node_modules/dedent-js": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dedent-js/-/dedent-js-1.0.1.tgz",
"integrity": "sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==",
"license": "MIT"
},
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
@@ -1910,6 +1973,15 @@
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
}
},
"node_modules/dot-case": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/dot-case/-/dot-case-2.1.1.tgz",
"integrity": "sha512-HnM6ZlFqcajLsyudHq7LeeLDr2rFAVYtDv/hV5qchQEidSck8j9OPUsXY9KwJv/lHMtYlX4DjRQqwFYa+0r8Ug==",
"license": "MIT",
"dependencies": {
"no-case": "^2.2.0"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -1959,8 +2031,7 @@
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/error-ex": {
"version": "1.3.2",
@@ -2227,7 +2298,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dev": true,
"dependencies": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
@@ -2313,7 +2383,6 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true,
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
@@ -2482,6 +2551,22 @@
"node": ">= 0.4"
}
},
"node_modules/header-case": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/header-case/-/header-case-1.0.1.tgz",
"integrity": "sha512-i0q9mkOeSuhXw6bGgiQCCBgY/jlZuV/7dZXyZ9c6LcBrqwvT8eT719E9uxE5LiZftdl+z81Ugbg/VvXV4OJOeQ==",
"license": "MIT",
"dependencies": {
"no-case": "^2.2.0",
"upper-case": "^1.1.3"
}
},
"node_modules/html-entities": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz",
"integrity": "sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==",
"license": "MIT"
},
"node_modules/html-escaper": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
@@ -2573,6 +2658,15 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"node_modules/ip-address": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
"integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@@ -2616,7 +2710,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"engines": {
"node": ">=8"
}
@@ -2642,6 +2735,15 @@
"node": ">=0.10.0"
}
},
"node_modules/is-lower-case": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/is-lower-case/-/is-lower-case-1.1.3.tgz",
"integrity": "sha512-+5A1e/WJpLLXZEDlgz4G//WYSHyQBD32qa4Jd3Lw06qQlv3fJHnp3YIHjTQSGzHMgzmVKz2ZP3rBxTHkPw/lxA==",
"license": "MIT",
"dependencies": {
"lower-case": "^1.1.0"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -2664,6 +2766,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-upper-case": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-upper-case/-/is-upper-case-1.1.2.tgz",
"integrity": "sha512-GQYSJMgfeAmVwh9ixyk888l7OIhNAGKtY6QA+IrWlu9MDTCaXmeozOZ2S9Knj7bQwBO/H6J2kb+pbyTUiMNbsw==",
"license": "MIT",
"dependencies": {
"upper-case": "^1.1.0"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -2758,6 +2869,7 @@
"resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz",
"integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==",
"dev": true,
"peer": true,
"dependencies": {
"@jest/core": "^28.1.3",
"@jest/types": "^28.1.3",
@@ -3747,7 +3859,6 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dev": true,
"dependencies": {
"p-locate": "^4.1.0"
},
@@ -3761,6 +3872,21 @@
"integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
"dev": true
},
"node_modules/lower-case": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
"integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==",
"license": "MIT"
},
"node_modules/lower-case-first": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/lower-case-first/-/lower-case-first-1.0.2.tgz",
"integrity": "sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA==",
"license": "MIT",
"dependencies": {
"lower-case": "^1.1.2"
}
},
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -3923,6 +4049,15 @@
"smart-buffer": "^4.1.0"
}
},
"node_modules/no-case": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
"integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==",
"license": "MIT",
"dependencies": {
"lower-case": "^1.1.1"
}
},
"node_modules/node-int64": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
@@ -4081,7 +4216,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dev": true,
"dependencies": {
"p-limit": "^2.2.0"
},
@@ -4093,7 +4227,6 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"dependencies": {
"p-try": "^2.0.0"
},
@@ -4108,11 +4241,19 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/param-case": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
"integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==",
"license": "MIT",
"dependencies": {
"no-case": "^2.2.0"
}
},
"node_modules/parse-json": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
@@ -4131,11 +4272,29 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/pascal-case": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-2.0.1.tgz",
"integrity": "sha512-qjS4s8rBOJa2Xm0jmxXiyh1+OFf6ekCWOvUaRgAQSktzlTbMotS0nmG9gyYAybCWBcuP4fsBeRCKNwGBnMe2OQ==",
"license": "MIT",
"dependencies": {
"camel-case": "^3.0.0",
"upper-case-first": "^1.1.0"
}
},
"node_modules/path-case": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/path-case/-/path-case-2.1.1.tgz",
"integrity": "sha512-Ou0N05MioItesaLr9q8TtHVWmJ6fxWdqKB2RohFmNWVyJ+2zeKIeDNWAN6B/Pe7wpzWChhZX6nONYmOnMeJQ/Q==",
"license": "MIT",
"dependencies": {
"no-case": "^2.2.0"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true,
"engines": {
"node": ">=8"
}
@@ -4328,11 +4487,16 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"license": "ISC"
},
"node_modules/resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@@ -4404,6 +4568,22 @@
"semver": "bin/semver.js"
}
},
"node_modules/sentence-case": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-2.1.1.tgz",
"integrity": "sha512-ENl7cYHaK/Ktwk5OTD+aDbQ3uC8IByu/6Bkg+HDv8Mm+XnBnppVNalcfJTNsp1ibstKh030/JKQQWglDvtKwEQ==",
"license": "MIT",
"dependencies": {
"no-case": "^2.2.0",
"upper-case-first": "^1.1.2"
}
},
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"license": "ISC"
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -4477,6 +4657,29 @@
"npm": ">= 3.0.0"
}
},
"node_modules/snake-case": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/snake-case/-/snake-case-2.1.0.tgz",
"integrity": "sha512-FMR5YoPFwOLuh4rRz92dywJjyKYZNLpMn1R5ujVpIYkbA9p01fq8RMg0FkO4M+Yobt4MjHeLTJVm5xFFBHSV2Q==",
"license": "MIT",
"dependencies": {
"no-case": "^2.2.0"
}
},
"node_modules/socks": {
"version": "2.8.7",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz",
"integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==",
"license": "MIT",
"dependencies": {
"ip-address": "^10.0.1",
"smart-buffer": "^4.2.0"
},
"engines": {
"node": ">= 10.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -4531,7 +4734,6 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -4545,7 +4747,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -4619,6 +4820,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/swap-case": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/swap-case/-/swap-case-1.1.2.tgz",
"integrity": "sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ==",
"license": "MIT",
"dependencies": {
"lower-case": "^1.1.1",
"upper-case": "^1.1.1"
}
},
"node_modules/terminal-link": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz",
@@ -4649,6 +4860,16 @@
"node": ">=8"
}
},
"node_modules/title-case": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/title-case/-/title-case-2.1.1.tgz",
"integrity": "sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==",
"license": "MIT",
"dependencies": {
"no-case": "^2.2.0",
"upper-case": "^1.0.3"
}
},
"node_modules/tmpl": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
@@ -4760,6 +4981,7 @@
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"peer": true,
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
@@ -4838,6 +5060,12 @@
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
"dev": true
},
"node_modules/underscore": {
"version": "1.13.7",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz",
"integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==",
"license": "MIT"
},
"node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
@@ -4875,6 +5103,21 @@
"browserslist": ">= 4.21.0"
}
},
"node_modules/upper-case": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
"integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==",
"license": "MIT"
},
"node_modules/upper-case-first": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-1.1.2.tgz",
"integrity": "sha512-wINKYvI3Db8dtjikdAqoBbZoP6Q+PZUyfMR7pmwHzjC2quzSkUq5DmPrTtPEqHaz8AGtmsB4TqwapMTM1QAQOQ==",
"license": "MIT",
"dependencies": {
"upper-case": "^1.1.1"
}
},
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
@@ -4924,6 +5167,104 @@
"node": ">= 8"
}
},
"node_modules/which-module": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
"license": "ISC"
},
"node_modules/whois": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/whois/-/whois-2.16.1.tgz",
"integrity": "sha512-gCxr+knAuXzsHJlHaIi8bCZoRU9anysyKbo0mgjAkkanNdOvUhBZShgG/ckcpKryDUVs2cH684MU+wOLjBYlAA==",
"license": "FreeBSD",
"dependencies": {
"socks": "^2.2.2",
"underscore": "^1.9.1",
"yargs": "^15.4.1"
},
"bin": {
"whois": "bin.js"
}
},
"node_modules/whois-json": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/whois-json/-/whois-json-2.0.4.tgz",
"integrity": "sha512-ui9Qe0qS4yWtFAPw0q+QPuuH+m4vDDtoO/YtqmF00YoPMMNplhya7SqTVAxThWIVXhWHPEKajFEulegmysdlwQ==",
"license": "MIT",
"dependencies": {
"change-case": "^3.0.2",
"dedent-js": "^1.0.1",
"html-entities": "^1.2.1",
"whois": "^2.6.0"
}
},
"node_modules/whois/node_modules/cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
}
},
"node_modules/whois/node_modules/wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/whois/node_modules/y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"license": "ISC"
},
"node_modules/whois/node_modules/yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"license": "MIT",
"dependencies": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
},
"engines": {
"node": ">=8"
}
},
"node_modules/whois/node_modules/yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"license": "ISC",
"dependencies": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",

View File

@@ -30,7 +30,8 @@
"net-snmp": "^3.26.1",
"ping": "^0.4.4",
"playwright": "^1.57.0",
"ts-node": "^10.9.1"
"ts-node": "^10.9.1",
"whois-json": "^2.0.4"
},
"devDependencies": {
"@types/jest": "^27.5.2",