Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d25ce9e498 | ||
|
|
b7c9e2710a | ||
|
|
bfdf28a2cc | ||
|
|
4ca24c794e | ||
|
|
7f9d2f275e | ||
|
|
1966134647 | ||
|
|
7646d11ea0 | ||
|
|
c10d9be0f0 | ||
|
|
27c3a9e12b | ||
|
|
134c2ebbf4 | ||
|
|
e39b44db6c |
No files matched your search
@@ -37,6 +37,7 @@ export const useMonitorForm = ({
|
|||||||
matchMethod: data?.matchMethod || "",
|
matchMethod: data?.matchMethod || "",
|
||||||
expectedValue: data?.expectedValue || "",
|
expectedValue: data?.expectedValue || "",
|
||||||
jsonPath: data?.jsonPath || "",
|
jsonPath: data?.jsonPath || "",
|
||||||
|
globalpingEnabled: data?.globalpingEnabled || false,
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case "ping":
|
case "ping":
|
||||||
@@ -44,6 +45,7 @@ export const useMonitorForm = ({
|
|||||||
...base,
|
...base,
|
||||||
type: "ping",
|
type: "ping",
|
||||||
url: data?.url || "",
|
url: data?.url || "",
|
||||||
|
globalpingEnabled: data?.globalpingEnabled || false,
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case "port":
|
case "port":
|
||||||
@@ -100,6 +102,7 @@ export const useMonitorForm = ({
|
|||||||
matchMethod: "",
|
matchMethod: "",
|
||||||
expectedValue: "",
|
expectedValue: "",
|
||||||
jsonPath: "",
|
jsonPath: "",
|
||||||
|
globalpingEnabled: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ export const useSettingsForm = ({ data = null }: UseSettingsFormOptions = {}) =>
|
|||||||
},
|
},
|
||||||
checkTTL: data?.checkTTL ?? 30,
|
checkTTL: data?.checkTTL ?? 30,
|
||||||
pagespeedApiKey: "",
|
pagespeedApiKey: "",
|
||||||
|
globalpingApiKey: "",
|
||||||
|
globalpingLocationsTier: data?.globalpingLocationsTier ?? 6,
|
||||||
systemEmailPassword: "",
|
systemEmailPassword: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -146,7 +146,6 @@ const CreateMonitorPage = () => {
|
|||||||
|
|
||||||
const { data: notifications } = useGet<Notification[]>("/notifications/team");
|
const { data: notifications } = useGet<Notification[]>("/notifications/team");
|
||||||
const { data: games } = useGet<GamesMap>("/monitors/games");
|
const { data: games } = useGet<GamesMap>("/monitors/games");
|
||||||
|
|
||||||
const { schema, defaults } = useMonitorForm({
|
const { schema, defaults } = useMonitorForm({
|
||||||
data: existingMonitor ?? null,
|
data: existingMonitor ?? null,
|
||||||
defaultType,
|
defaultType,
|
||||||
@@ -223,8 +222,8 @@ const CreateMonitorPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onError = (errors: unknown) => {
|
const onError = (_errors: unknown) => {
|
||||||
console.log(errors);
|
// Validation errors are shown inline via react-hook-form field errors
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -489,6 +488,27 @@ const CreateMonitorPage = () => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Global monitoring - only for http and ping types */}
|
||||||
|
{(watchedType === "http" || watchedType === "ping") && (
|
||||||
|
<ConfigBox
|
||||||
|
title={t("pages.createMonitor.form.globalping.title")}
|
||||||
|
subtitle={t("pages.createMonitor.form.globalping.description")}
|
||||||
|
rightContent={
|
||||||
|
<Controller
|
||||||
|
name="globalpingEnabled"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Switch
|
||||||
|
id="globalpingEnabled"
|
||||||
|
checked={field.value ?? false}
|
||||||
|
onChange={(e) => field.onChange(e.target.checked)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Alert Thresholds - only for hardware type */}
|
{/* Alert Thresholds - only for hardware type */}
|
||||||
{generalSettingsConfig.showSecret && (
|
{generalSettingsConfig.showSecret && (
|
||||||
<ConfigBox
|
<ConfigBox
|
||||||
|
|||||||
@@ -0,0 +1,197 @@
|
|||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import Stack from "@mui/material/Stack";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import Button from "@mui/material/Button";
|
||||||
|
import RadioGroup from "@mui/material/RadioGroup";
|
||||||
|
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||||
|
import Radio from "@mui/material/Radio";
|
||||||
|
import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
|
||||||
|
import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
|
||||||
|
import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx";
|
||||||
|
import { useTheme } from "@emotion/react";
|
||||||
|
import { PropTypes } from "prop-types";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Controller } from "react-hook-form";
|
||||||
|
|
||||||
|
const SettingsGlobalping = ({
|
||||||
|
isAdmin,
|
||||||
|
HEADING_SX,
|
||||||
|
isGlobalpingKeySet,
|
||||||
|
globalpingKeyHasBeenReset,
|
||||||
|
setGlobalpingKeyHasBeenReset,
|
||||||
|
defaults,
|
||||||
|
control,
|
||||||
|
setValue,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
if (!isAdmin) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfigBox>
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
component="h1"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("pages.settings.globalpingSettings.title")}
|
||||||
|
</Typography>
|
||||||
|
<Typography sx={HEADING_SX}>
|
||||||
|
{t("pages.settings.globalpingSettings.description")}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Stack gap={theme.spacing(20)}>
|
||||||
|
{(isGlobalpingKeySet === false || globalpingKeyHasBeenReset === true) && (
|
||||||
|
<Controller
|
||||||
|
name="globalpingApiKey"
|
||||||
|
control={control}
|
||||||
|
defaultValue={defaults.globalpingApiKey}
|
||||||
|
render={({ field, fieldState }) => (
|
||||||
|
<TextInput
|
||||||
|
{...field}
|
||||||
|
label={t("pages.settings.globalpingSettings.labelApiKey")}
|
||||||
|
type={"password"}
|
||||||
|
optionalLabel={t("pages.settings.globalpingSettings.optionalLabel")}
|
||||||
|
endAdornment={<PasswordEndAdornment />}
|
||||||
|
error={!!fieldState.error}
|
||||||
|
helperText={fieldState.error?.message}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isGlobalpingKeySet === true && globalpingKeyHasBeenReset === false && (
|
||||||
|
<Box>
|
||||||
|
<Typography>
|
||||||
|
{t("pages.settings.globalpingSettings.labelApiKeySet")}
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setValue("globalpingApiKey", "");
|
||||||
|
setGlobalpingKeyHasBeenReset(true);
|
||||||
|
}}
|
||||||
|
variant="contained"
|
||||||
|
color="error"
|
||||||
|
sx={{ mt: theme.spacing(4) }}
|
||||||
|
>
|
||||||
|
{t("reset")}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
variant="h2"
|
||||||
|
sx={{ mb: theme.spacing(2) }}
|
||||||
|
>
|
||||||
|
{t("pages.settings.globalpingSettings.locationsTierTitle")}
|
||||||
|
</Typography>
|
||||||
|
<Typography sx={{ mb: theme.spacing(8), color: theme.palette.text.secondary }}>
|
||||||
|
{t("pages.settings.globalpingSettings.locationsTierDescription")}
|
||||||
|
</Typography>
|
||||||
|
<Controller
|
||||||
|
name="globalpingLocationsTier"
|
||||||
|
control={control}
|
||||||
|
defaultValue={defaults.globalpingLocationsTier}
|
||||||
|
render={({ field }) => (
|
||||||
|
<RadioGroup
|
||||||
|
value={String(field.value ?? 6)}
|
||||||
|
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||||
|
sx={{ gap: theme.spacing(4) }}
|
||||||
|
>
|
||||||
|
{[
|
||||||
|
{ value: "3", labelKey: "tier3Label", descKey: "tier3Description" },
|
||||||
|
{ value: "6", labelKey: "tier6Label", descKey: "tier6Description" },
|
||||||
|
{ value: "15", labelKey: "tier15Label", descKey: "tier15Description" },
|
||||||
|
].map((tier) => (
|
||||||
|
<FormControlLabel
|
||||||
|
key={tier.value}
|
||||||
|
value={tier.value}
|
||||||
|
control={<Radio sx={{ p: 0, mr: theme.spacing(4) }} />}
|
||||||
|
label={
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
fontWeight={500}
|
||||||
|
lineHeight={1.5}
|
||||||
|
>
|
||||||
|
{t(`pages.settings.globalpingSettings.${tier.labelKey}`)}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="text.secondary"
|
||||||
|
>
|
||||||
|
{t(`pages.settings.globalpingSettings.${tier.descKey}`)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
sx={{ alignItems: "flex-start", m: 0 }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</RadioGroup>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
p: theme.spacing(8),
|
||||||
|
borderRadius: theme.spacing(2),
|
||||||
|
border: `1px solid ${theme.palette.primary.lowContrast}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="h2"
|
||||||
|
sx={{ mb: theme.spacing(4) }}
|
||||||
|
>
|
||||||
|
{t("pages.settings.globalpingSettings.rateLimitsTitle")}
|
||||||
|
</Typography>
|
||||||
|
<Typography sx={{ mb: theme.spacing(4) }}>
|
||||||
|
{t("pages.settings.globalpingSettings.rateLimitsDescription")}
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
component="ul"
|
||||||
|
sx={{ m: 0, pl: theme.spacing(8) }}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
component="li"
|
||||||
|
sx={{ mb: theme.spacing(2) }}
|
||||||
|
>
|
||||||
|
{t("pages.settings.globalpingSettings.rateLimitNoAuth")}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
component="li"
|
||||||
|
sx={{ mb: theme.spacing(2) }}
|
||||||
|
>
|
||||||
|
{t("pages.settings.globalpingSettings.rateLimitAuth")}
|
||||||
|
</Typography>
|
||||||
|
<Typography component="li">
|
||||||
|
{t("pages.settings.globalpingSettings.rateLimitSponsor")}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Typography
|
||||||
|
color="text.secondary"
|
||||||
|
sx={{ mt: theme.spacing(4), fontStyle: "italic" }}
|
||||||
|
>
|
||||||
|
{t("pages.settings.globalpingSettings.rateLimitsNote")}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</ConfigBox>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
SettingsGlobalping.propTypes = {
|
||||||
|
isAdmin: PropTypes.bool,
|
||||||
|
HEADING_SX: PropTypes.object,
|
||||||
|
isGlobalpingKeySet: PropTypes.bool,
|
||||||
|
globalpingKeyHasBeenReset: PropTypes.bool,
|
||||||
|
setGlobalpingKeyHasBeenReset: PropTypes.func,
|
||||||
|
defaults: PropTypes.object,
|
||||||
|
control: PropTypes.object,
|
||||||
|
setValue: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SettingsGlobalping;
|
||||||
@@ -5,6 +5,7 @@ import SettingsTimeZone from "./SettingsTimeZone.jsx";
|
|||||||
import SettingsUI from "./SettingsUI.jsx";
|
import SettingsUI from "./SettingsUI.jsx";
|
||||||
import SettingsURL from "./SettingsURL.jsx";
|
import SettingsURL from "./SettingsURL.jsx";
|
||||||
import SettingsPagespeed from "./SettingsPagespeed.jsx";
|
import SettingsPagespeed from "./SettingsPagespeed.jsx";
|
||||||
|
import SettingsGlobalping from "./SettingsGlobalping.jsx";
|
||||||
import SettingsDemoMonitors from "./SettingsDemoMonitors.jsx";
|
import SettingsDemoMonitors from "./SettingsDemoMonitors.jsx";
|
||||||
import SettingsAbout from "./SettingsAbout.jsx";
|
import SettingsAbout from "./SettingsAbout.jsx";
|
||||||
import SettingsEmail from "./SettingsEmail.jsx";
|
import SettingsEmail from "./SettingsEmail.jsx";
|
||||||
@@ -34,6 +35,10 @@ const Settings = () => {
|
|||||||
fetchedSettings?.pagespeedKeySet ?? false
|
fetchedSettings?.pagespeedKeySet ?? false
|
||||||
);
|
);
|
||||||
const [apiKeyHasBeenReset, setApiKeyHasBeenReset] = useState(false);
|
const [apiKeyHasBeenReset, setApiKeyHasBeenReset] = useState(false);
|
||||||
|
const [isGlobalpingKeySet, setIsGlobalpingKeySet] = useState(
|
||||||
|
fetchedSettings?.globalpingKeySet ?? false
|
||||||
|
);
|
||||||
|
const [globalpingKeyHasBeenReset, setGlobalpingKeyHasBeenReset] = useState(false);
|
||||||
const [isEmailPasswordSet, setIsEmailPasswordSet] = useState(
|
const [isEmailPasswordSet, setIsEmailPasswordSet] = useState(
|
||||||
fetchedSettings?.emailPasswordSet ?? false
|
fetchedSettings?.emailPasswordSet ?? false
|
||||||
);
|
);
|
||||||
@@ -63,6 +68,7 @@ const Settings = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (fetchedSettings) {
|
if (fetchedSettings) {
|
||||||
setIsApiKeySet(fetchedSettings?.pagespeedKeySet);
|
setIsApiKeySet(fetchedSettings?.pagespeedKeySet);
|
||||||
|
setIsGlobalpingKeySet(fetchedSettings?.globalpingKeySet);
|
||||||
setIsEmailPasswordSet(fetchedSettings?.emailPasswordSet);
|
setIsEmailPasswordSet(fetchedSettings?.emailPasswordSet);
|
||||||
}
|
}
|
||||||
}, [fetchedSettings]);
|
}, [fetchedSettings]);
|
||||||
@@ -74,10 +80,14 @@ const Settings = () => {
|
|||||||
if (response?.data) {
|
if (response?.data) {
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
setIsApiKeySet(data.pagespeedKeySet);
|
setIsApiKeySet(data.pagespeedKeySet);
|
||||||
|
setIsGlobalpingKeySet(data.globalpingKeySet);
|
||||||
setIsEmailPasswordSet(data.emailPasswordSet);
|
setIsEmailPasswordSet(data.emailPasswordSet);
|
||||||
if (data.pagespeedKeySet === true) {
|
if (data.pagespeedKeySet === true) {
|
||||||
setApiKeyHasBeenReset(false);
|
setApiKeyHasBeenReset(false);
|
||||||
}
|
}
|
||||||
|
if (data.globalpingKeySet === true) {
|
||||||
|
setGlobalpingKeyHasBeenReset(false);
|
||||||
|
}
|
||||||
if (data.emailPasswordSet === true) {
|
if (data.emailPasswordSet === true) {
|
||||||
setEmailPasswordHasBeenReset(false);
|
setEmailPasswordHasBeenReset(false);
|
||||||
}
|
}
|
||||||
@@ -102,6 +112,22 @@ const Settings = () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Don't send empty sensitive fields when the key is already set and
|
||||||
|
// hasn't been explicitly reset — otherwise the backend $unsets them
|
||||||
|
if (isApiKeySet && !apiKeyHasBeenReset && !toSubmit.pagespeedApiKey) {
|
||||||
|
delete toSubmit.pagespeedApiKey;
|
||||||
|
}
|
||||||
|
if (isGlobalpingKeySet && !globalpingKeyHasBeenReset && !toSubmit.globalpingApiKey) {
|
||||||
|
delete toSubmit.globalpingApiKey;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isEmailPasswordSet &&
|
||||||
|
!emailPasswordHasBeenReset &&
|
||||||
|
!toSubmit.systemEmailPassword
|
||||||
|
) {
|
||||||
|
delete toSubmit.systemEmailPassword;
|
||||||
|
}
|
||||||
|
|
||||||
saveSettings(toSubmit);
|
saveSettings(toSubmit);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -134,6 +160,16 @@ const Settings = () => {
|
|||||||
defaults={defaults}
|
defaults={defaults}
|
||||||
setValue={setValue}
|
setValue={setValue}
|
||||||
/>
|
/>
|
||||||
|
<SettingsGlobalping
|
||||||
|
isAdmin={isAdmin}
|
||||||
|
HEADING_SX={HEADING_SX}
|
||||||
|
isGlobalpingKeySet={isGlobalpingKeySet}
|
||||||
|
globalpingKeyHasBeenReset={globalpingKeyHasBeenReset}
|
||||||
|
setGlobalpingKeyHasBeenReset={setGlobalpingKeyHasBeenReset}
|
||||||
|
control={control}
|
||||||
|
defaults={defaults}
|
||||||
|
setValue={setValue}
|
||||||
|
/>
|
||||||
<SettingsURL
|
<SettingsURL
|
||||||
HEADING_SX={HEADING_SX}
|
HEADING_SX={HEADING_SX}
|
||||||
control={control}
|
control={control}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { BasePage } from "@/Components/v2/design-elements";
|
import { BasePage } from "@/Components/v2/design-elements";
|
||||||
import { HeaderTimeRange } from "@/Components/v2/common";
|
import { HeaderTimeRange } from "@/Components/v2/common";
|
||||||
import Stack from "@mui/material/Stack";
|
import Stack from "@mui/material/Stack";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import Tabs from "@mui/material/Tabs";
|
||||||
|
import Tab from "@mui/material/Tab";
|
||||||
import {
|
import {
|
||||||
HistogramStatus,
|
HistogramStatus,
|
||||||
RadialAvgResponse,
|
RadialAvgResponse,
|
||||||
@@ -13,11 +17,11 @@ import { MonitorStatBoxes } from "@/Components/v2/monitors";
|
|||||||
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
import { useTheme } from "@mui/material/styles";
|
||||||
import { useIsAdmin } from "@/Hooks/useIsAdmin";
|
import { useIsAdmin } from "@/Hooks/useIsAdmin";
|
||||||
import { useState, useMemo } from "react";
|
import { useState, useMemo, useEffect } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { useGet } from "@/Hooks/UseApi";
|
import { useGet } from "@/Hooks/UseApi";
|
||||||
import type { MonitorDetailsResponse } from "@/Types/Monitor";
|
import type { MonitorDetailsResponse, LocationCheckData } from "@/Types/Monitor";
|
||||||
import type { ChecksResponse } from "@/Types/Check";
|
import type { ChecksResponse } from "@/Types/Check";
|
||||||
import type { RootState } from "@/Types/state";
|
import type { RootState } from "@/Types/state";
|
||||||
import { formatDateWithTz } from "@/Utils/TimeUtils";
|
import { formatDateWithTz } from "@/Utils/TimeUtils";
|
||||||
@@ -38,6 +42,7 @@ const UptimeDetailsPage = () => {
|
|||||||
const [page, setPage] = useState<number>(0);
|
const [page, setPage] = useState<number>(0);
|
||||||
const [rowsPerPage, setRowsPerPage] = useState<number>(5);
|
const [rowsPerPage, setRowsPerPage] = useState<number>(5);
|
||||||
const [dateRange, setDateRange] = useState<string>("recent");
|
const [dateRange, setDateRange] = useState<string>("recent");
|
||||||
|
const [selectedLocationTab, setSelectedLocationTab] = useState<number>(0);
|
||||||
|
|
||||||
const monitorDetailsUrl = useMemo(() => {
|
const monitorDetailsUrl = useMemo(() => {
|
||||||
if (!monitorId) {
|
if (!monitorId) {
|
||||||
@@ -108,6 +113,30 @@ const UptimeDetailsPage = () => {
|
|||||||
const checks = checksData?.checks ?? [];
|
const checks = checksData?.checks ?? [];
|
||||||
const checksCount = checksData?.checksCount ?? 0;
|
const checksCount = checksData?.checksCount ?? 0;
|
||||||
|
|
||||||
|
const globalpingLocationsUrl = monitor?.globalpingEnabled
|
||||||
|
? "/monitors/globalping/locations"
|
||||||
|
: null;
|
||||||
|
const { data: globalpingData } = useGet<{
|
||||||
|
locations: { id: string; label: string }[];
|
||||||
|
labels: Record<string, string>;
|
||||||
|
}>(globalpingLocationsUrl);
|
||||||
|
const locationLabels = globalpingData?.labels ?? {};
|
||||||
|
|
||||||
|
const locationChecks = monitorData?.locationChecks;
|
||||||
|
const locationKeys = useMemo(
|
||||||
|
() => (locationChecks ? Object.keys(locationChecks).sort() : []),
|
||||||
|
[locationChecks]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reset tab index when location keys change to prevent out-of-bounds
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedLocationTab(0);
|
||||||
|
}, [locationKeys.length]);
|
||||||
|
|
||||||
|
const selectedLocation = locationKeys[selectedLocationTab] ?? null;
|
||||||
|
const selectedLocationData: LocationCheckData | null =
|
||||||
|
selectedLocation && locationChecks ? locationChecks[selectedLocation] : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BasePage>
|
<BasePage>
|
||||||
<HeaderMonitorControls
|
<HeaderMonitorControls
|
||||||
@@ -152,6 +181,63 @@ const UptimeDetailsPage = () => {
|
|||||||
checks={monitorData?.groupedChecks || []}
|
checks={monitorData?.groupedChecks || []}
|
||||||
range={dateRange}
|
range={dateRange}
|
||||||
/>
|
/>
|
||||||
|
{locationKeys.length > 0 && (
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
variant="h2"
|
||||||
|
sx={{ mb: theme.spacing(4) }}
|
||||||
|
>
|
||||||
|
{t("pages.uptime.details.locations.title")}
|
||||||
|
</Typography>
|
||||||
|
<Tabs
|
||||||
|
value={selectedLocationTab}
|
||||||
|
onChange={(_e, newVal) => setSelectedLocationTab(newVal)}
|
||||||
|
sx={{ mb: theme.spacing(4) }}
|
||||||
|
>
|
||||||
|
{locationKeys.map((loc) => (
|
||||||
|
<Tab
|
||||||
|
key={loc}
|
||||||
|
label={locationLabels[loc] ?? loc}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
{selectedLocationData && (
|
||||||
|
<Stack gap={theme.spacing(4)}>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
gap={theme.spacing(8)}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="text.secondary"
|
||||||
|
>
|
||||||
|
{t("pages.uptime.details.locations.avgResponseTime")}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h3">
|
||||||
|
{Math.round(selectedLocationData.avgResponseTime)} ms
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="text.secondary"
|
||||||
|
>
|
||||||
|
{t("pages.uptime.details.locations.uptime")}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h3">
|
||||||
|
{(selectedLocationData.uptimePercentage * 100).toFixed(1)}%
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
<HistogramDetails
|
||||||
|
checks={selectedLocationData.groupedChecks || []}
|
||||||
|
range={dateRange}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
<ChecksTable
|
<ChecksTable
|
||||||
checks={checks}
|
checks={checks}
|
||||||
count={checksCount}
|
count={checksCount}
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import {
|
|||||||
} from "@/Components/v2/design-elements";
|
} from "@/Components/v2/design-elements";
|
||||||
import { HeatmapResponseTime, HistogramResponseTime } from "@/Components/v2/common";
|
import { HeatmapResponseTime, HistogramResponseTime } from "@/Components/v2/common";
|
||||||
import { ActionsMenu } from "@/Components/v2/actions-menu";
|
import { ActionsMenu } from "@/Components/v2/actions-menu";
|
||||||
import { ArrowDown, ArrowUp } from "lucide-react";
|
import { ArrowDown, ArrowUp, Globe } from "lucide-react";
|
||||||
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useTheme } from "@mui/material/styles";
|
import { useTheme } from "@mui/material/styles";
|
||||||
@@ -167,7 +168,23 @@ export const MonitorTable = ({
|
|||||||
),
|
),
|
||||||
|
|
||||||
render: (row) => {
|
render: (row) => {
|
||||||
return row?.name;
|
return (
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
alignItems="center"
|
||||||
|
gap={theme.spacing(2)}
|
||||||
|
>
|
||||||
|
{row?.name}
|
||||||
|
{row.globalpingEnabled && (
|
||||||
|
<Tooltip title={t("pages.uptime.table.headers.globalpingEnabled")}>
|
||||||
|
<Globe
|
||||||
|
size={14}
|
||||||
|
aria-label={t("pages.uptime.table.headers.globalpingEnabled")}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ export interface Monitor {
|
|||||||
selectedDisks: string[];
|
selectedDisks: string[];
|
||||||
gameId?: string;
|
gameId?: string;
|
||||||
group: string | null;
|
group: string | null;
|
||||||
|
globalpingEnabled: boolean;
|
||||||
recentChecks: CheckSnapshot[];
|
recentChecks: CheckSnapshot[];
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
@@ -97,6 +98,12 @@ export interface MonitorStats {
|
|||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LocationCheckData {
|
||||||
|
groupedChecks: GroupedCheck[];
|
||||||
|
uptimePercentage: number;
|
||||||
|
avgResponseTime: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface MonitorData {
|
export interface MonitorData {
|
||||||
monitor: Monitor;
|
monitor: Monitor;
|
||||||
groupedChecks: GroupedCheck[];
|
groupedChecks: GroupedCheck[];
|
||||||
@@ -104,6 +111,7 @@ export interface MonitorData {
|
|||||||
groupedDownChecks: GroupedCheck[];
|
groupedDownChecks: GroupedCheck[];
|
||||||
groupedAvgResponseTime: number;
|
groupedAvgResponseTime: number;
|
||||||
groupedUptimePercentage: number;
|
groupedUptimePercentage: number;
|
||||||
|
locationChecks?: Record<string, LocationCheckData>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MonitorDetailsResponse {
|
export interface MonitorDetailsResponse {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export interface Settings {
|
|||||||
systemEmailRequireTLS: boolean;
|
systemEmailRequireTLS: boolean;
|
||||||
systemEmailRejectUnauthorized: boolean;
|
systemEmailRejectUnauthorized: boolean;
|
||||||
showURL: boolean;
|
showURL: boolean;
|
||||||
|
globalpingLocationsTier?: number;
|
||||||
singleton: boolean;
|
singleton: boolean;
|
||||||
globalThresholds?: SettingsThresholds;
|
globalThresholds?: SettingsThresholds;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
@@ -29,6 +30,7 @@ export interface Settings {
|
|||||||
|
|
||||||
export interface AppSettingsResponse {
|
export interface AppSettingsResponse {
|
||||||
pagespeedKeySet: boolean;
|
pagespeedKeySet: boolean;
|
||||||
|
globalpingKeySet: boolean;
|
||||||
emailPasswordSet: boolean;
|
emailPasswordSet: boolean;
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
}
|
}
|
||||||
@@ -31,12 +31,14 @@ const httpSchema = baseSchema.extend({
|
|||||||
matchMethod: z.enum(["equal", "include", "regex", ""]).optional(),
|
matchMethod: z.enum(["equal", "include", "regex", ""]).optional(),
|
||||||
expectedValue: z.string().optional(),
|
expectedValue: z.string().optional(),
|
||||||
jsonPath: z.string().optional(),
|
jsonPath: z.string().optional(),
|
||||||
|
globalpingEnabled: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ping monitor schema
|
// Ping monitor schema
|
||||||
const pingSchema = baseSchema.extend({
|
const pingSchema = baseSchema.extend({
|
||||||
type: z.literal("ping"),
|
type: z.literal("ping"),
|
||||||
url: z.string().min(1, "Host is required"),
|
url: z.string().min(1, "Host is required"),
|
||||||
|
globalpingEnabled: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Port monitor schema
|
// Port monitor schema
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ export const settingsSchema = z.object({
|
|||||||
.min(1, "Please enter a value")
|
.min(1, "Please enter a value")
|
||||||
.max(365, "Maximum 365 days"),
|
.max(365, "Maximum 365 days"),
|
||||||
pagespeedApiKey: optionalString(),
|
pagespeedApiKey: optionalString(),
|
||||||
|
globalpingApiKey: optionalString(),
|
||||||
|
globalpingLocationsTier: z.coerce.number().optional(),
|
||||||
systemEmailHost: z.preprocess(
|
systemEmailHost: z.preprocess(
|
||||||
(val) =>
|
(val) =>
|
||||||
val === "" || val === null || val === undefined ? undefined : String(val).trim(),
|
val === "" || val === null || val === undefined ? undefined : String(val).trim(),
|
||||||
|
|||||||
@@ -445,6 +445,15 @@
|
|||||||
},
|
},
|
||||||
"title": "Check frequency"
|
"title": "Check frequency"
|
||||||
},
|
},
|
||||||
|
"globalping": {
|
||||||
|
"title": "Global monitoring",
|
||||||
|
"description": "Run checks from multiple locations around the world using the Globalping network. Configure the API key and location count in settings.",
|
||||||
|
"option": {
|
||||||
|
"enabled": {
|
||||||
|
"label": "Enable Globalping"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"option": {
|
"option": {
|
||||||
"container": {
|
"container": {
|
||||||
@@ -950,6 +959,27 @@
|
|||||||
"labelApiKey": "PageSpeed API key",
|
"labelApiKey": "PageSpeed API key",
|
||||||
"title": "Google PageSpeed API key"
|
"title": "Google PageSpeed API key"
|
||||||
},
|
},
|
||||||
|
"globalpingSettings": {
|
||||||
|
"description": "Configure Globalping to run uptime checks from multiple geographic regions worldwide.",
|
||||||
|
"labelApiKeySet": "API key is set. Click Reset to change it.",
|
||||||
|
"labelApiKey": "Globalping API key",
|
||||||
|
"optionalLabel": "(Optional)",
|
||||||
|
"title": "Globalping",
|
||||||
|
"locationsTierTitle": "Global locations",
|
||||||
|
"locationsTierDescription": "Choose how many probe locations are available when creating monitors.",
|
||||||
|
"tier3Label": "3 locations — core regions",
|
||||||
|
"tier3Description": "North America, Europe, Asia",
|
||||||
|
"tier6Label": "6 locations — all continents",
|
||||||
|
"tier6Description": "North America, Europe, Asia, South America, Oceania, Africa",
|
||||||
|
"tier15Label": "15 locations — city level",
|
||||||
|
"tier15Description": "US East, US West, Canada, UK, Germany, Netherlands, Poland, Japan, Singapore, India, Australia, Brazil, Chile, South Africa, UAE",
|
||||||
|
"rateLimitsTitle": "Rate limits",
|
||||||
|
"rateLimitsDescription": "Globalping limits how many tests you can run per hour. Each location check on each monitor uses 1 credit per interval.",
|
||||||
|
"rateLimitNoAuth": "Without a token: 250 tests per hour (no authentication needed)",
|
||||||
|
"rateLimitAuth": "With a free token: 500 tests per hour (sign in at dash.globalping.io)",
|
||||||
|
"rateLimitSponsor": "GitHub sponsors: higher limits based on sponsorship tier + hosting probes",
|
||||||
|
"rateLimitsNote": "For example, 3 monitors with 5 locations each, checked every minute, uses 15 credits per minute (900/hour). Choose your tier accordingly."
|
||||||
|
},
|
||||||
"saveButtonLabel": "Save",
|
"saveButtonLabel": "Save",
|
||||||
"statsSettings": {
|
"statsSettings": {
|
||||||
"clearAllStatsButton": "Clear all stats",
|
"clearAllStatsButton": "Clear all stats",
|
||||||
@@ -1106,6 +1136,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uptime": {
|
"uptime": {
|
||||||
|
"details": {
|
||||||
|
"locations": {
|
||||||
|
"title": "Location breakdown",
|
||||||
|
"label": "Location",
|
||||||
|
"avgResponseTime": "Avg response time",
|
||||||
|
"uptime": "Uptime"
|
||||||
|
}
|
||||||
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"search": {
|
"search": {
|
||||||
"placeholder": "Search monitors..."
|
"placeholder": "Search monitors..."
|
||||||
@@ -1113,7 +1151,8 @@
|
|||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
"headers": {
|
"headers": {
|
||||||
"responseTime": "Response time"
|
"responseTime": "Response time",
|
||||||
|
"globalpingEnabled": "Global monitoring enabled"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fallback": {
|
"fallback": {
|
||||||
|
|||||||
Generated
+580
-17
@@ -22,6 +22,7 @@
|
|||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"express-rate-limit": "8.0.1",
|
"express-rate-limit": "8.0.1",
|
||||||
"gamedig": "^5.3.1",
|
"gamedig": "^5.3.1",
|
||||||
|
"globalping": "^0.2.0",
|
||||||
"got": "14.4.7",
|
"got": "14.4.7",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
"helmet": "^8.0.0",
|
"helmet": "^8.0.0",
|
||||||
@@ -874,7 +875,6 @@
|
|||||||
"integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
|
"integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.28.6",
|
"@babel/code-frame": "^7.28.6",
|
||||||
"@babel/generator": "^7.28.6",
|
"@babel/generator": "^7.28.6",
|
||||||
@@ -1504,7 +1504,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
@@ -1549,7 +1548,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
@@ -2261,6 +2259,132 @@
|
|||||||
"@hapi/hoek": "^9.0.0"
|
"@hapi/hoek": "^9.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@hey-api/client-fetch": {
|
||||||
|
"version": "0.10.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.10.2.tgz",
|
||||||
|
"integrity": "sha512-AGiFYDx+y8VT1wlQ3EbzzZtfU8EfV+hLLRTtr8Y/tjYZaxIECwJagVZf24YzNbtEBXONFV50bwcU1wLVGXe1ow==",
|
||||||
|
"deprecated": "Starting with v0.73.0, this package is bundled directly inside @hey-api/openapi-ts.",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/hey-api"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@hey-api/openapi-ts": "< 2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@hey-api/codegen-core": {
|
||||||
|
"version": "0.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hey-api/codegen-core/-/codegen-core-0.7.0.tgz",
|
||||||
|
"integrity": "sha512-HglL4B4QwpzocE+c8qDU6XK8zMf8W8Pcv0RpFDYxHuYALWLTnpDUuEsglC7NQ4vC1maoXsBpMbmwpco0N4QviA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@hey-api/types": "0.1.3",
|
||||||
|
"ansi-colors": "4.1.3",
|
||||||
|
"c12": "3.3.3",
|
||||||
|
"color-support": "1.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.19.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/hey-api"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": ">=5.5.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@hey-api/json-schema-ref-parser": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hey-api/json-schema-ref-parser/-/json-schema-ref-parser-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-3tQJ8N2egHXZjQWUeceoWrl88APWjo7gRrQ/L4HWJKnh6HowczCv7yNNFeSusPoWGV6HGdoFiCvq6UsLkrwKhg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@jsdevtools/ono": "7.1.3",
|
||||||
|
"@types/json-schema": "7.0.15",
|
||||||
|
"js-yaml": "4.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.19.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/hey-api"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@hey-api/openapi-ts": {
|
||||||
|
"version": "0.92.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.92.4.tgz",
|
||||||
|
"integrity": "sha512-RA3wnL7Odr5xczuS3xpvnPClgJ/K8jivK3hvD8J0m5GBuvJFkZ1A1xp+6Ve1G0BV8p4LwxwgN1Qhb+4BFsLfMg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@hey-api/codegen-core": "0.7.0",
|
||||||
|
"@hey-api/json-schema-ref-parser": "1.3.0",
|
||||||
|
"@hey-api/shared": "0.2.0",
|
||||||
|
"@hey-api/types": "0.1.3",
|
||||||
|
"ansi-colors": "4.1.3",
|
||||||
|
"color-support": "1.1.3",
|
||||||
|
"commander": "14.0.3"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"openapi-ts": "bin/run.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.19.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/hey-api"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": ">=5.5.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@hey-api/openapi-ts/node_modules/commander": {
|
||||||
|
"version": "14.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
|
||||||
|
"integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@hey-api/shared": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hey-api/shared/-/shared-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-t7C+65ES12OqAE5k6DB/y5nDuTjydtqdxf/Qe4zflVn2AzGs7hO/7KjXvGXZYnpNVF7QISAcj0LEObASU9I53Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@hey-api/codegen-core": "0.7.0",
|
||||||
|
"@hey-api/json-schema-ref-parser": "1.3.0",
|
||||||
|
"@hey-api/types": "0.1.3",
|
||||||
|
"ansi-colors": "4.1.3",
|
||||||
|
"cross-spawn": "7.0.6",
|
||||||
|
"open": "11.0.0",
|
||||||
|
"semver": "7.7.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.19.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/hey-api"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": ">=5.5.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@hey-api/types": {
|
||||||
|
"version": "0.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hey-api/types/-/types-0.1.3.tgz",
|
||||||
|
"integrity": "sha512-mZaiPOWH761yD4GjDQvtjS2ZYLu5o5pI1TVSvV/u7cmbybv51/FVtinFBeaE1kFQCKZ8OQpn2ezjLBJrKsGATw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": ">=5.5.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanfs/core": {
|
"node_modules/@humanfs/core": {
|
||||||
"version": "0.19.1",
|
"version": "0.19.1",
|
||||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||||
@@ -3223,6 +3347,13 @@
|
|||||||
"url": "https://opencollective.com/js-sdsl"
|
"url": "https://opencollective.com/js-sdsl"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@jsdevtools/ono": {
|
||||||
|
"version": "7.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
|
||||||
|
"integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/@mongodb-js/saslprep": {
|
"node_modules/@mongodb-js/saslprep": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz",
|
||||||
@@ -4229,6 +4360,7 @@
|
|||||||
"integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==",
|
"integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
}
|
}
|
||||||
@@ -4405,7 +4537,6 @@
|
|||||||
"integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==",
|
"integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/body-parser": "*",
|
"@types/body-parser": "*",
|
||||||
"@types/express-serve-static-core": "^5.0.0",
|
"@types/express-serve-static-core": "^5.0.0",
|
||||||
@@ -4506,7 +4637,6 @@
|
|||||||
"version": "7.0.15",
|
"version": "7.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/jsonwebtoken": {
|
"node_modules/@types/jsonwebtoken": {
|
||||||
@@ -4566,7 +4696,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz",
|
||||||
"integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==",
|
"integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.16.0"
|
"undici-types": "~7.16.0"
|
||||||
}
|
}
|
||||||
@@ -5053,7 +5182,6 @@
|
|||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -5632,7 +5760,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.8.19",
|
"baseline-browser-mapping": "^2.8.19",
|
||||||
"caniuse-lite": "^1.0.30001751",
|
"caniuse-lite": "^1.0.30001751",
|
||||||
@@ -5739,6 +5866,22 @@
|
|||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bundle-name": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"run-applescript": "^7.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/busboy": {
|
"node_modules/busboy": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||||
@@ -5759,6 +5902,78 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/c12": {
|
||||||
|
"version": "3.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/c12/-/c12-3.3.3.tgz",
|
||||||
|
"integrity": "sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"chokidar": "^5.0.0",
|
||||||
|
"confbox": "^0.2.2",
|
||||||
|
"defu": "^6.1.4",
|
||||||
|
"dotenv": "^17.2.3",
|
||||||
|
"exsolve": "^1.0.8",
|
||||||
|
"giget": "^2.0.0",
|
||||||
|
"jiti": "^2.6.1",
|
||||||
|
"ohash": "^2.0.11",
|
||||||
|
"pathe": "^2.0.3",
|
||||||
|
"perfect-debounce": "^2.0.0",
|
||||||
|
"pkg-types": "^2.3.0",
|
||||||
|
"rc9": "^2.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"magicast": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"magicast": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/c12/node_modules/chokidar": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"readdirp": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20.19.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/c12/node_modules/dotenv": {
|
||||||
|
"version": "17.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz",
|
||||||
|
"integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://dotenvx.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/c12/node_modules/readdirp": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20.19.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/c8": {
|
"node_modules/c8": {
|
||||||
"version": "10.1.3",
|
"version": "10.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz",
|
||||||
@@ -6023,6 +6238,16 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/citty": {
|
||||||
|
"version": "0.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz",
|
||||||
|
"integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"consola": "^3.2.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cjs-module-lexer": {
|
"node_modules/cjs-module-lexer": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz",
|
||||||
@@ -6220,6 +6445,16 @@
|
|||||||
"simple-swizzle": "^0.2.2"
|
"simple-swizzle": "^0.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/color-support": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"peer": true,
|
||||||
|
"bin": {
|
||||||
|
"color-support": "bin.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/colord": {
|
"node_modules/colord": {
|
||||||
"version": "2.9.3",
|
"version": "2.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
|
||||||
@@ -6357,6 +6592,23 @@
|
|||||||
"safe-buffer": "~5.1.0"
|
"safe-buffer": "~5.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/confbox": {
|
||||||
|
"version": "0.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
|
||||||
|
"integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/consola": {
|
||||||
|
"version": "3.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
|
||||||
|
"integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.18.0 || >=16.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/content-disposition": {
|
"node_modules/content-disposition": {
|
||||||
"version": "0.5.4",
|
"version": "0.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||||
@@ -6575,7 +6827,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.1.tgz",
|
||||||
"integrity": "sha512-fm4D8ti0dQmFPeF8DXSAA//btEmqCOgAc/9Oa3C1LW94h5usNrJEfrON7b4FkPZgnDEn6OUs5NdxiJZmAtGOpQ==",
|
"integrity": "sha512-fm4D8ti0dQmFPeF8DXSAA//btEmqCOgAc/9Oa3C1LW94h5usNrJEfrON7b4FkPZgnDEn6OUs5NdxiJZmAtGOpQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cssnano-preset-default": "^7.0.9",
|
"cssnano-preset-default": "^7.0.9",
|
||||||
"lilconfig": "^3.1.3"
|
"lilconfig": "^3.1.3"
|
||||||
@@ -6821,6 +7072,36 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/default-browser": {
|
||||||
|
"version": "5.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz",
|
||||||
|
"integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"bundle-name": "^4.1.0",
|
||||||
|
"default-browser-id": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/default-browser-id": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/defer-to-connect": {
|
"node_modules/defer-to-connect": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
|
||||||
@@ -6830,6 +7111,26 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/define-lazy-prop": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/defu": {
|
||||||
|
"version": "6.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
||||||
|
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/delayed-stream": {
|
"node_modules/delayed-stream": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
@@ -6857,6 +7158,13 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/destr": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/destroy": {
|
"node_modules/destroy": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||||
@@ -7330,7 +7638,6 @@
|
|||||||
"integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
|
"integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -7692,7 +7999,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
|
||||||
"integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
|
"integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "~1.3.8",
|
"accepts": "~1.3.8",
|
||||||
"array-flatten": "1.1.1",
|
"array-flatten": "1.1.1",
|
||||||
@@ -7767,6 +8073,13 @@
|
|||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/exsolve": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/extend": {
|
"node_modules/extend": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||||
@@ -8395,6 +8708,24 @@
|
|||||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/giget": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"citty": "^0.1.6",
|
||||||
|
"consola": "^3.4.0",
|
||||||
|
"defu": "^6.1.4",
|
||||||
|
"node-fetch-native": "^1.6.6",
|
||||||
|
"nypm": "^0.6.0",
|
||||||
|
"pathe": "^2.0.3"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"giget": "dist/cli.mjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/glob": {
|
"node_modules/glob": {
|
||||||
"version": "10.5.0",
|
"version": "10.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
||||||
@@ -8452,6 +8783,15 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/globalping": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/globalping/-/globalping-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-rxyvqXF/oz5yBGJHR58TEsCkcbc4WQVmdKC+uZ8LXfIt4KwTdTvbzkfwhhjuyUmuQr8U/b8m/ZicdrA8ZIroKw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@hey-api/client-fetch": "^0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/globals": {
|
"node_modules/globals": {
|
||||||
"version": "15.15.0",
|
"version": "15.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz",
|
||||||
@@ -8947,6 +9287,22 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-docker": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"bin": {
|
||||||
|
"is-docker": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-extglob": {
|
"node_modules/is-extglob": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
@@ -8987,6 +9343,38 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-in-ssh": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-inside-container": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"is-docker": "^3.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"is-inside-container": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-json": {
|
"node_modules/is-json": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz",
|
||||||
@@ -9020,6 +9408,22 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-wsl": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"is-inside-container": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/isarray": {
|
"node_modules/isarray": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||||
@@ -9341,7 +9745,6 @@
|
|||||||
"integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==",
|
"integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jest/core": "30.2.0",
|
"@jest/core": "30.2.0",
|
||||||
"@jest/types": "30.2.0",
|
"@jest/types": "30.2.0",
|
||||||
@@ -9929,6 +10332,16 @@
|
|||||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jiti": {
|
||||||
|
"version": "2.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
||||||
|
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"bin": {
|
||||||
|
"jiti": "lib/jiti-cli.mjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jmespath": {
|
"node_modules/jmespath": {
|
||||||
"version": "0.16.0",
|
"version": "0.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz",
|
||||||
@@ -11611,6 +12024,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/node-fetch-native": {
|
||||||
|
"version": "1.6.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz",
|
||||||
|
"integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/node-fetch/node_modules/tr46": {
|
"node_modules/node-fetch/node_modules/tr46": {
|
||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
@@ -11780,6 +12200,31 @@
|
|||||||
"integrity": "sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==",
|
"integrity": "sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/nypm": {
|
||||||
|
"version": "0.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.5.tgz",
|
||||||
|
"integrity": "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"citty": "^0.2.0",
|
||||||
|
"pathe": "^2.0.3",
|
||||||
|
"tinyexec": "^1.0.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"nypm": "dist/cli.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/nypm/node_modules/citty": {
|
||||||
|
"version": "0.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/citty/-/citty-0.2.1.tgz",
|
||||||
|
"integrity": "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/object-assign": {
|
"node_modules/object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
@@ -11801,6 +12246,13 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ohash": {
|
||||||
|
"version": "2.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
|
||||||
|
"integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/on-finished": {
|
"node_modules/on-finished": {
|
||||||
"version": "2.4.1",
|
"version": "2.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||||
@@ -11856,6 +12308,27 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/open": {
|
||||||
|
"version": "11.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz",
|
||||||
|
"integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"default-browser": "^5.4.0",
|
||||||
|
"define-lazy-prop": "^3.0.0",
|
||||||
|
"is-in-ssh": "^1.0.0",
|
||||||
|
"is-inside-container": "^1.0.0",
|
||||||
|
"powershell-utils": "^0.1.0",
|
||||||
|
"wsl-utils": "^0.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/optionator": {
|
"node_modules/optionator": {
|
||||||
"version": "0.9.4",
|
"version": "0.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||||
@@ -12080,6 +12553,20 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pathe": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/perfect-debounce": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
@@ -12199,6 +12686,18 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pkg-types": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"confbox": "^0.2.2",
|
||||||
|
"exsolve": "^1.0.7",
|
||||||
|
"pathe": "^2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/plimit-lit": {
|
"node_modules/plimit-lit": {
|
||||||
"version": "1.6.1",
|
"version": "1.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz",
|
||||||
@@ -12231,7 +12730,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.11",
|
"nanoid": "^3.3.11",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
@@ -12825,6 +13323,19 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/powershell-utils": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz",
|
||||||
|
"integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prelude-ls": {
|
"node_modules/prelude-ls": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||||
@@ -13088,6 +13599,17 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rc9": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"defu": "^6.1.4",
|
||||||
|
"destr": "^2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||||
@@ -13280,6 +13802,19 @@
|
|||||||
"integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
|
"integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/run-applescript": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/run-parallel": {
|
"node_modules/run-parallel": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||||
@@ -14179,6 +14714,7 @@
|
|||||||
"integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==",
|
"integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@trysound/sax": "0.2.0",
|
"@trysound/sax": "0.2.0",
|
||||||
"commander": "^7.2.0",
|
"commander": "^7.2.0",
|
||||||
@@ -14205,6 +14741,7 @@
|
|||||||
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
|
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
@@ -14215,6 +14752,7 @@
|
|||||||
"integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
|
"integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mdn-data": "2.0.30",
|
"mdn-data": "2.0.30",
|
||||||
"source-map-js": "^1.0.1"
|
"source-map-js": "^1.0.1"
|
||||||
@@ -14228,7 +14766,8 @@
|
|||||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
|
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
|
||||||
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
|
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
|
||||||
"license": "CC0-1.0",
|
"license": "CC0-1.0",
|
||||||
"optional": true
|
"optional": true,
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/swagger-ui-dist": {
|
"node_modules/swagger-ui-dist": {
|
||||||
"version": "5.30.0",
|
"version": "5.30.0",
|
||||||
@@ -14365,6 +14904,16 @@
|
|||||||
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==",
|
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/tinyexec": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tldts": {
|
"node_modules/tldts": {
|
||||||
"version": "6.1.86",
|
"version": "6.1.86",
|
||||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
|
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
|
||||||
@@ -14513,7 +15062,6 @@
|
|||||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cspotcode/source-map-support": "^0.8.0",
|
"@cspotcode/source-map-support": "^0.8.0",
|
||||||
"@tsconfig/node10": "^1.0.7",
|
"@tsconfig/node10": "^1.0.7",
|
||||||
@@ -14674,9 +15222,7 @@
|
|||||||
"version": "5.9.2",
|
"version": "5.9.2",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
||||||
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
||||||
"devOptional": true,
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -15306,6 +15852,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/wsl-utils": {
|
||||||
|
"version": "0.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz",
|
||||||
|
"integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"is-wsl": "^3.1.0",
|
||||||
|
"powershell-utils": "^0.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/xml-name-validator": {
|
"node_modules/xml-name-validator": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"express-rate-limit": "8.0.1",
|
"express-rate-limit": "8.0.1",
|
||||||
"gamedig": "^5.3.1",
|
"gamedig": "^5.3.1",
|
||||||
|
"globalping": "^0.2.0",
|
||||||
"got": "14.4.7",
|
"got": "14.4.7",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
"helmet": "^8.0.0",
|
"helmet": "^8.0.0",
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export interface InitializedControllers {
|
|||||||
export const initializeControllers = (services: InitializedServices): InitializedControllers => {
|
export const initializeControllers = (services: InitializedServices): InitializedControllers => {
|
||||||
return {
|
return {
|
||||||
authController: new AuthController(services.userService),
|
authController: new AuthController(services.userService),
|
||||||
monitorController: new MonitorController(services.monitorService),
|
monitorController: new MonitorController(services.monitorService, services.settingsService),
|
||||||
settingsController: new SettingsController(services.settingsService, services.emailService),
|
settingsController: new SettingsController(services.settingsService, services.emailService),
|
||||||
checkController: new CheckController(services.checkService),
|
checkController: new CheckController(services.checkService),
|
||||||
inviteController: new InviteController(services.inviteService),
|
inviteController: new InviteController(services.inviteService),
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
INotificationsService,
|
INotificationsService,
|
||||||
} from "@/service/index.js";
|
} from "@/service/index.js";
|
||||||
import SuperSimpleQueueHelper from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js";
|
import SuperSimpleQueueHelper from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js";
|
||||||
|
import GlobalpingService from "../service/infrastructure/globalpingService.js";
|
||||||
import SuperSimpleQueue from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js";
|
import SuperSimpleQueue from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js";
|
||||||
import UserService from "../service/business/userService.js";
|
import UserService from "../service/business/userService.js";
|
||||||
import CheckService from "../service/business/checkService.js";
|
import CheckService from "../service/business/checkService.js";
|
||||||
@@ -192,6 +193,8 @@ export const initializeServices = async ({
|
|||||||
notificationMessageBuilder
|
notificationMessageBuilder
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const globalpingService = new GlobalpingService({ settingsService, logger });
|
||||||
|
|
||||||
const superSimpleQueueHelper = new SuperSimpleQueueHelper({
|
const superSimpleQueueHelper = new SuperSimpleQueueHelper({
|
||||||
logger,
|
logger,
|
||||||
networkService,
|
networkService,
|
||||||
@@ -200,6 +203,7 @@ export const initializeServices = async ({
|
|||||||
checkService,
|
checkService,
|
||||||
buffer: bufferService,
|
buffer: bufferService,
|
||||||
incidentService,
|
incidentService,
|
||||||
|
globalpingService,
|
||||||
maintenanceWindowsRepository,
|
maintenanceWindowsRepository,
|
||||||
monitorsRepository,
|
monitorsRepository,
|
||||||
teamsRepository,
|
teamsRepository,
|
||||||
|
|||||||
@@ -26,15 +26,19 @@ import {
|
|||||||
} from "./controllerUtils.js";
|
} from "./controllerUtils.js";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import { IMonitorService } from "@/service/index.js";
|
import { IMonitorService } from "@/service/index.js";
|
||||||
|
import { getGlobalpingLocationsByTier, LOCATION_LABELS } from "@/service/infrastructure/globalpingService.js";
|
||||||
|
import type { ISettingsService } from "@/service/system/settingsService.js";
|
||||||
|
|
||||||
const SERVICE_NAME = "monitorController";
|
const SERVICE_NAME = "monitorController";
|
||||||
class MonitorController {
|
class MonitorController {
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
|
|
||||||
private monitorService: IMonitorService;
|
private monitorService: IMonitorService;
|
||||||
|
private settingsService: ISettingsService;
|
||||||
|
|
||||||
constructor(monitorService: IMonitorService) {
|
constructor(monitorService: IMonitorService, settingsService: ISettingsService) {
|
||||||
this.monitorService = monitorService;
|
this.monitorService = monitorService;
|
||||||
|
this.settingsService = settingsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
get serviceName() {
|
||||||
@@ -387,6 +391,21 @@ class MonitorController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getGlobalpingLocations = async (_req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const settings = await this.settingsService.getDBSettings();
|
||||||
|
const tier = settings.globalpingLocationsTier ?? 6;
|
||||||
|
const locations = getGlobalpingLocationsByTier(tier);
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
msg: "Globalping locations retrieved successfully",
|
||||||
|
data: { locations, labels: LOCATION_LABELS },
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
getGroupsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
getGroupsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const teamId = req?.user?.teamId;
|
const teamId = req?.user?.teamId;
|
||||||
|
|||||||
@@ -21,8 +21,9 @@ class SettingsController {
|
|||||||
const sanitizedSettings = { ...dbSettings };
|
const sanitizedSettings = { ...dbSettings };
|
||||||
delete sanitizedSettings.version;
|
delete sanitizedSettings.version;
|
||||||
delete sanitizedSettings.jwtSecret;
|
delete sanitizedSettings.jwtSecret;
|
||||||
const returnSettings = {
|
const returnSettings: any = {
|
||||||
pagespeedKeySet: false,
|
pagespeedKeySet: false,
|
||||||
|
globalpingKeySet: false,
|
||||||
emailPasswordSet: false,
|
emailPasswordSet: false,
|
||||||
settings: null,
|
settings: null,
|
||||||
};
|
};
|
||||||
@@ -31,6 +32,10 @@ class SettingsController {
|
|||||||
returnSettings.pagespeedKeySet = true;
|
returnSettings.pagespeedKeySet = true;
|
||||||
delete sanitizedSettings.pagespeedApiKey;
|
delete sanitizedSettings.pagespeedApiKey;
|
||||||
}
|
}
|
||||||
|
if (typeof sanitizedSettings.globalpingApiKey !== "undefined") {
|
||||||
|
returnSettings.globalpingKeySet = true;
|
||||||
|
delete sanitizedSettings.globalpingApiKey;
|
||||||
|
}
|
||||||
if (typeof sanitizedSettings.systemEmailPassword !== "undefined") {
|
if (typeof sanitizedSettings.systemEmailPassword !== "undefined") {
|
||||||
returnSettings.emailPasswordSet = true;
|
returnSettings.emailPasswordSet = true;
|
||||||
delete sanitizedSettings.systemEmailPassword;
|
delete sanitizedSettings.systemEmailPassword;
|
||||||
@@ -55,15 +60,19 @@ class SettingsController {
|
|||||||
};
|
};
|
||||||
|
|
||||||
updateAppSettings = async (req: Request, res: Response, next: NextFunction) => {
|
updateAppSettings = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
await updateAppSettingsBodyValidation.validateAsync(req.body);
|
try {
|
||||||
|
await updateAppSettingsBodyValidation.validateAsync(req.body);
|
||||||
|
|
||||||
const updatedSettings = await this.settingsService.updateDbSettings(req.body);
|
const updatedSettings = await this.settingsService.updateDbSettings(req.body);
|
||||||
const returnSettings = this.buildAppSettings(updatedSettings);
|
const returnSettings = this.buildAppSettings(updatedSettings);
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
msg: "App settings updated successfully",
|
msg: "App settings updated successfully",
|
||||||
data: returnSettings,
|
data: returnSettings,
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
sendTestEmail = async (req: Request, res: Response, next: NextFunction) => {
|
sendTestEmail = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ const AppSettingsSchema = new Schema<AppSettingsDocument>(
|
|||||||
language: { type: String, default: "gb" },
|
language: { type: String, default: "gb" },
|
||||||
jwtSecret: { type: String },
|
jwtSecret: { type: String },
|
||||||
pagespeedApiKey: { type: String },
|
pagespeedApiKey: { type: String },
|
||||||
|
globalpingApiKey: { type: String },
|
||||||
|
globalpingLocationsTier: { type: Number, default: 6 },
|
||||||
systemEmailHost: { type: String },
|
systemEmailHost: { type: String },
|
||||||
systemEmailPort: { type: Number },
|
systemEmailPort: { type: Number },
|
||||||
systemEmailAddress: { type: String },
|
systemEmailAddress: { type: String },
|
||||||
|
|||||||
@@ -13,12 +13,14 @@ import type {
|
|||||||
CheckNetworkInterfaceInfo,
|
CheckNetworkInterfaceInfo,
|
||||||
GotTimings,
|
GotTimings,
|
||||||
ILighthouseAudit,
|
ILighthouseAudit,
|
||||||
|
LocationResult,
|
||||||
} from "@/types/check.js";
|
} from "@/types/check.js";
|
||||||
|
|
||||||
type CheckMetadataDocument = Omit<CheckMetadata, "monitorId" | "teamId"> & {
|
type CheckMetadataDocument = Omit<CheckMetadata, "monitorId" | "teamId"> & {
|
||||||
monitorId: Types.ObjectId;
|
monitorId: Types.ObjectId;
|
||||||
teamId: Types.ObjectId;
|
teamId: Types.ObjectId;
|
||||||
type: MonitorType;
|
type: MonitorType;
|
||||||
|
location?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type CheckDocumentBase = Omit<Check, "id" | "metadata" | "expiry" | "ackAt" | "createdAt" | "updatedAt"> & {
|
type CheckDocumentBase = Omit<Check, "id" | "metadata" | "expiry" | "ackAt" | "createdAt" | "updatedAt"> & {
|
||||||
@@ -175,6 +177,17 @@ const auditsSchema = new Schema<CheckAudits>(
|
|||||||
{ _id: false }
|
{ _id: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const locationResultSchema = new Schema<LocationResult>(
|
||||||
|
{
|
||||||
|
location: { type: String, required: true },
|
||||||
|
status: { type: Boolean, required: true },
|
||||||
|
responseTime: { type: Number, required: true },
|
||||||
|
statusCode: { type: Number, required: true },
|
||||||
|
message: { type: String, default: "" },
|
||||||
|
},
|
||||||
|
{ _id: false }
|
||||||
|
);
|
||||||
|
|
||||||
const metadataSchema = new Schema<CheckMetadataDocument>(
|
const metadataSchema = new Schema<CheckMetadataDocument>(
|
||||||
{
|
{
|
||||||
monitorId: {
|
monitorId: {
|
||||||
@@ -197,6 +210,9 @@ const metadataSchema = new Schema<CheckMetadataDocument>(
|
|||||||
required: true,
|
required: true,
|
||||||
index: true,
|
index: true,
|
||||||
},
|
},
|
||||||
|
location: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{ _id: false }
|
{ _id: false }
|
||||||
);
|
);
|
||||||
@@ -281,6 +297,10 @@ const CheckSchema = new Schema<CheckDocument>(
|
|||||||
type: auditsSchema,
|
type: auditsSchema,
|
||||||
default: undefined,
|
default: undefined,
|
||||||
},
|
},
|
||||||
|
locationResults: {
|
||||||
|
type: [locationResultSchema],
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
timestamps: true,
|
timestamps: true,
|
||||||
@@ -299,6 +319,7 @@ CheckSchema.index({ createdAt: 1 });
|
|||||||
CheckSchema.index({ "metadata.teamId": 1, createdAt: -1 });
|
CheckSchema.index({ "metadata.teamId": 1, createdAt: -1 });
|
||||||
CheckSchema.index({ "metadata.monitorId": 1, "metadata.type": 1, createdAt: -1 });
|
CheckSchema.index({ "metadata.monitorId": 1, "metadata.type": 1, createdAt: -1 });
|
||||||
CheckSchema.index({ "metadata.teamId": 1, status: 1, createdAt: -1 });
|
CheckSchema.index({ "metadata.teamId": 1, status: 1, createdAt: -1 });
|
||||||
|
CheckSchema.index({ "metadata.monitorId": 1, "metadata.location": 1, createdAt: -1 });
|
||||||
|
|
||||||
const CheckModel = model<CheckDocument>("Check", CheckSchema);
|
const CheckModel = model<CheckDocument>("Check", CheckSchema);
|
||||||
|
|
||||||
|
|||||||
@@ -160,6 +160,10 @@ const MonitorSchema = new Schema<MonitorDocument>(
|
|||||||
gameId: {
|
gameId: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
|
globalpingEnabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
group: {
|
group: {
|
||||||
type: String,
|
type: String,
|
||||||
trim: true,
|
trim: true,
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export interface IChecksRepository {
|
|||||||
options?: { type?: MonitorType }
|
options?: { type?: MonitorType }
|
||||||
): Promise<UptimeChecksResult | HardwareChecksResult | PageSpeedChecksResult>;
|
): Promise<UptimeChecksResult | HardwareChecksResult | PageSpeedChecksResult>;
|
||||||
findSummaryByTeamId(teamId: string, dateRange: string): Promise<ChecksSummary>;
|
findSummaryByTeamId(teamId: string, dateRange: string): Promise<ChecksSummary>;
|
||||||
|
findLocationChecksByMonitorId(monitorId: string, startDate: Date, endDate: Date, dateString: string): Promise<Record<string, UptimeChecksResult>>;
|
||||||
// update
|
// update
|
||||||
//delete
|
//delete
|
||||||
deleteByMonitorId(monitorId: string): Promise<number>;
|
deleteByMonitorId(monitorId: string): Promise<number>;
|
||||||
|
|||||||
@@ -169,6 +169,7 @@ class MongoChecksRepository implements IChecksRepository {
|
|||||||
monitorId: toStringId(metadata.monitorId),
|
monitorId: toStringId(metadata.monitorId),
|
||||||
teamId: toStringId(metadata.teamId),
|
teamId: toStringId(metadata.teamId),
|
||||||
type: metadata.type,
|
type: metadata.type,
|
||||||
|
...(metadata.location ? { location: metadata.location } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -219,6 +220,7 @@ class MongoChecksRepository implements IChecksRepository {
|
|||||||
monitorId: new mongoose.Types.ObjectId(metadata.monitorId),
|
monitorId: new mongoose.Types.ObjectId(metadata.monitorId),
|
||||||
teamId: new mongoose.Types.ObjectId(metadata.teamId),
|
teamId: new mongoose.Types.ObjectId(metadata.teamId),
|
||||||
type: metadata.type,
|
type: metadata.type,
|
||||||
|
...(metadata.location ? { location: metadata.location } : {}),
|
||||||
},
|
},
|
||||||
...rest,
|
...rest,
|
||||||
} as unknown as CheckDocument;
|
} as unknown as CheckDocument;
|
||||||
@@ -420,6 +422,59 @@ class MongoChecksRepository implements IChecksRepository {
|
|||||||
return result.deletedCount ?? 0;
|
return result.deletedCount ?? 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
findLocationChecksByMonitorId = async (monitorId: string, startDate: Date, endDate: Date, dateString: string): Promise<Record<string, any>> => {
|
||||||
|
const monitorObjectId = new mongoose.Types.ObjectId(monitorId);
|
||||||
|
const matchStage = {
|
||||||
|
"metadata.monitorId": monitorObjectId,
|
||||||
|
"metadata.location": { $exists: true, $ne: null },
|
||||||
|
createdAt: { $gte: startDate, $lte: endDate },
|
||||||
|
};
|
||||||
|
|
||||||
|
const results = await CheckModel.aggregate([
|
||||||
|
{ $match: matchStage },
|
||||||
|
{ $sort: { createdAt: 1 } },
|
||||||
|
{
|
||||||
|
$group: {
|
||||||
|
_id: {
|
||||||
|
location: "$metadata.location",
|
||||||
|
bucket: { $dateToString: { format: dateString, date: "$createdAt" } },
|
||||||
|
},
|
||||||
|
avgResponseTime: { $avg: "$responseTime" },
|
||||||
|
totalChecks: { $sum: 1 },
|
||||||
|
upChecks: { $sum: { $cond: [{ $eq: ["$status", true] }, 1, 0] } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ $sort: { "_id.bucket": 1 } },
|
||||||
|
{
|
||||||
|
$group: {
|
||||||
|
_id: "$_id.location",
|
||||||
|
groupedChecks: {
|
||||||
|
$push: {
|
||||||
|
bucketDate: "$_id.bucket",
|
||||||
|
avgResponseTime: "$avgResponseTime",
|
||||||
|
totalChecks: "$totalChecks",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
totalUpChecks: { $sum: "$upChecks" },
|
||||||
|
totalAllChecks: { $sum: "$totalChecks" },
|
||||||
|
overallAvgResponseTime: { $avg: "$avgResponseTime" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const locationChecks: Record<string, any> = {};
|
||||||
|
for (const loc of results) {
|
||||||
|
const uptimePercentage = loc.totalAllChecks > 0 ? loc.totalUpChecks / loc.totalAllChecks : 0;
|
||||||
|
locationChecks[loc._id] = {
|
||||||
|
groupedChecks: loc.groupedChecks,
|
||||||
|
uptimePercentage,
|
||||||
|
avgResponseTime: loc.overallAvgResponseTime ?? 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return locationChecks;
|
||||||
|
};
|
||||||
|
|
||||||
private findUptimeDateRangeChecks = async (
|
private findUptimeDateRangeChecks = async (
|
||||||
monitorType: Exclude<MonitorType, "hardware" | "pagespeed">,
|
monitorType: Exclude<MonitorType, "hardware" | "pagespeed">,
|
||||||
monitorObjectId: mongoose.Types.ObjectId,
|
monitorObjectId: mongoose.Types.ObjectId,
|
||||||
@@ -427,9 +482,10 @@ class MongoChecksRepository implements IChecksRepository {
|
|||||||
endDate: Date,
|
endDate: Date,
|
||||||
dateString: string
|
dateString: string
|
||||||
) => {
|
) => {
|
||||||
const matchStage = {
|
const matchStage: Record<string, any> = {
|
||||||
"metadata.monitorId": monitorObjectId,
|
"metadata.monitorId": monitorObjectId,
|
||||||
createdAt: { $gte: startDate, $lte: endDate },
|
createdAt: { $gte: startDate, $lte: endDate },
|
||||||
|
"metadata.location": { $exists: false },
|
||||||
};
|
};
|
||||||
const [result] = await CheckModel.aggregate([
|
const [result] = await CheckModel.aggregate([
|
||||||
{ $match: matchStage },
|
{ $match: matchStage },
|
||||||
|
|||||||
@@ -343,6 +343,7 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
|||||||
selectedDisks: doc.selectedDisks ?? [],
|
selectedDisks: doc.selectedDisks ?? [],
|
||||||
gameId: doc.gameId ?? undefined,
|
gameId: doc.gameId ?? undefined,
|
||||||
group: doc.group ?? null,
|
group: doc.group ?? null,
|
||||||
|
globalpingEnabled: doc.globalpingEnabled ?? false,
|
||||||
recentChecks: (doc.recentChecks ?? []).map((check: any) => this.toCheckSnapshot(check)),
|
recentChecks: (doc.recentChecks ?? []).map((check: any) => this.toCheckSnapshot(check)),
|
||||||
createdAt: toDateString(doc.createdAt),
|
createdAt: toDateString(doc.createdAt),
|
||||||
updatedAt: toDateString(doc.updatedAt),
|
updatedAt: toDateString(doc.updatedAt),
|
||||||
@@ -398,6 +399,7 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
|||||||
selectedDisks: doc.selectedDisks ?? [],
|
selectedDisks: doc.selectedDisks ?? [],
|
||||||
gameId: doc.gameId ?? undefined,
|
gameId: doc.gameId ?? undefined,
|
||||||
group: doc.group ?? null,
|
group: doc.group ?? null,
|
||||||
|
globalpingEnabled: doc.globalpingEnabled ?? false,
|
||||||
recentChecks: (doc.recentChecks ?? []).map((check: any) => this.toCheckSnapshot(check)),
|
recentChecks: (doc.recentChecks ?? []).map((check: any) => this.toCheckSnapshot(check)),
|
||||||
createdAt: toDateString(doc.createdAt),
|
createdAt: toDateString(doc.createdAt),
|
||||||
updatedAt: toDateString(doc.updatedAt),
|
updatedAt: toDateString(doc.updatedAt),
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ class MongoSettingsRepository implements ISettingsRepository {
|
|||||||
systemEmailRequireTLS: doc.systemEmailRequireTLS ?? false,
|
systemEmailRequireTLS: doc.systemEmailRequireTLS ?? false,
|
||||||
systemEmailRejectUnauthorized: doc.systemEmailRejectUnauthorized ?? true,
|
systemEmailRejectUnauthorized: doc.systemEmailRejectUnauthorized ?? true,
|
||||||
showURL: doc.showURL ?? false,
|
showURL: doc.showURL ?? false,
|
||||||
|
globalpingApiKey: doc.globalpingApiKey ?? undefined,
|
||||||
|
globalpingLocationsTier: doc.globalpingLocationsTier ?? 6,
|
||||||
singleton: doc.singleton,
|
singleton: doc.singleton,
|
||||||
version: doc.version ?? 1,
|
version: doc.version ?? 1,
|
||||||
globalThresholds: doc.globalThresholds ?? undefined,
|
globalThresholds: doc.globalThresholds ?? undefined,
|
||||||
@@ -81,10 +83,6 @@ class MongoSettingsRepository implements ISettingsRepository {
|
|||||||
delete update.$unset;
|
delete update.$unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
await AppSettingsModel.findOneAndUpdate({}, update, {
|
|
||||||
upsert: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const updatedSettings = await AppSettingsModel.findOneAndUpdate({}, update, {
|
const updatedSettings = await AppSettingsModel.findOneAndUpdate({}, update, {
|
||||||
upsert: true,
|
upsert: true,
|
||||||
new: true,
|
new: true,
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ class MonitorRoutes {
|
|||||||
this.router.get("/team/with-checks", this.monitorController.getMonitorsWithChecksByTeamId);
|
this.router.get("/team/with-checks", this.monitorController.getMonitorsWithChecksByTeamId);
|
||||||
this.router.get("/team/groups", this.monitorController.getGroupsByTeamId);
|
this.router.get("/team/groups", this.monitorController.getGroupsByTeamId);
|
||||||
|
|
||||||
|
// Globalping locations
|
||||||
|
this.router.get("/globalping/locations", this.monitorController.getGlobalpingLocations);
|
||||||
|
|
||||||
// Uptime routes
|
// Uptime routes
|
||||||
this.router.get("/uptime/details/:monitorId", this.monitorController.getUptimeDetailsById);
|
this.router.get("/uptime/details/:monitorId", this.monitorController.getUptimeDetailsById);
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class CheckService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
buildCheck = (statusResponse: MonitorStatusResponse<PageSpeedStatusPayload | HardwareStatusPayload | undefined>) => {
|
buildCheck = (statusResponse: MonitorStatusResponse<PageSpeedStatusPayload | HardwareStatusPayload | undefined>) => {
|
||||||
const { monitorId, teamId, type, status, responseTime, code, message, payload, timings } = statusResponse;
|
const { monitorId, teamId, type, status, responseTime, code, message, payload, timings, location } = statusResponse;
|
||||||
|
|
||||||
const check: Partial<Check> = {
|
const check: Partial<Check> = {
|
||||||
id: new Types.ObjectId().toString(),
|
id: new Types.ObjectId().toString(),
|
||||||
@@ -46,6 +46,7 @@ class CheckService {
|
|||||||
monitorId,
|
monitorId,
|
||||||
teamId,
|
teamId,
|
||||||
type,
|
type,
|
||||||
|
...(location ? { location } : {}),
|
||||||
},
|
},
|
||||||
status,
|
status,
|
||||||
statusCode: code,
|
statusCode: code,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type {
|
|||||||
UptimeDetailsResult,
|
UptimeDetailsResult,
|
||||||
HardwareDetailsResult,
|
HardwareDetailsResult,
|
||||||
PageSpeedDetailsResult,
|
PageSpeedDetailsResult,
|
||||||
|
LocationCheckData,
|
||||||
GamesMap,
|
GamesMap,
|
||||||
} from "@/types/monitor.js";
|
} from "@/types/monitor.js";
|
||||||
import type {
|
import type {
|
||||||
@@ -222,6 +223,15 @@ export class MonitorService implements IMonitorService {
|
|||||||
throw new AppError({ message: `${monitor.type} monitors are not supported for uptime details`, status: 400 });
|
throw new AppError({ message: `${monitor.type} monitors are not supported for uptime details`, status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch location checks if Globalping is enabled for this monitor
|
||||||
|
let locationChecks: Record<string, LocationCheckData> | undefined;
|
||||||
|
if (monitor.globalpingEnabled && (monitor.type === "http" || monitor.type === "ping")) {
|
||||||
|
const locData = await this.checksRepository.findLocationChecksByMonitorId(monitor.id, start, end, this.getDateFormat(rangeKey));
|
||||||
|
if (Object.keys(locData).length > 0) {
|
||||||
|
locationChecks = locData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
monitorData: {
|
monitorData: {
|
||||||
monitor,
|
monitor,
|
||||||
@@ -230,6 +240,7 @@ export class MonitorService implements IMonitorService {
|
|||||||
groupedDownChecks: NormalizeDataUptimeDetails(checksData.groupedDownChecks, 10, 100),
|
groupedDownChecks: NormalizeDataUptimeDetails(checksData.groupedDownChecks, 10, 100),
|
||||||
groupedAvgResponseTime: checksData.avgResponseTime,
|
groupedAvgResponseTime: checksData.avgResponseTime,
|
||||||
groupedUptimePercentage: checksData.uptimePercentage,
|
groupedUptimePercentage: checksData.uptimePercentage,
|
||||||
|
...(locationChecks ? { locationChecks } : {}),
|
||||||
},
|
},
|
||||||
monitorStats,
|
monitorStats,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { AppError } from "@/utils/AppError.js";
|
|||||||
import { INetworkService, INotificationsService, IStatusService } from "@/service/index.js";
|
import { INetworkService, INotificationsService, IStatusService } from "@/service/index.js";
|
||||||
import type { StatusChangeResult, MonitorStatusResponse, HardwareStatusPayload, MonitorStatus } from "@/types/index.js";
|
import type { StatusChangeResult, MonitorStatusResponse, HardwareStatusPayload, MonitorStatus } from "@/types/index.js";
|
||||||
import IncidentService from "@/service/business/incidentService.js";
|
import IncidentService from "@/service/business/incidentService.js";
|
||||||
|
import type GlobalpingService from "@/service/infrastructure/globalpingService.js";
|
||||||
import {
|
import {
|
||||||
IMaintenanceWindowsRepository,
|
IMaintenanceWindowsRepository,
|
||||||
IMonitorsRepository,
|
IMonitorsRepository,
|
||||||
@@ -37,6 +38,7 @@ class SuperSimpleQueueHelper {
|
|||||||
private checkService: any;
|
private checkService: any;
|
||||||
private buffer: any;
|
private buffer: any;
|
||||||
private incidentService: IncidentService;
|
private incidentService: IncidentService;
|
||||||
|
private globalpingService: GlobalpingService;
|
||||||
private maintenanceWindowsRepository: IMaintenanceWindowsRepository;
|
private maintenanceWindowsRepository: IMaintenanceWindowsRepository;
|
||||||
private monitorsRepository: IMonitorsRepository;
|
private monitorsRepository: IMonitorsRepository;
|
||||||
private teamsRepository: ITeamsRepository;
|
private teamsRepository: ITeamsRepository;
|
||||||
@@ -52,6 +54,7 @@ class SuperSimpleQueueHelper {
|
|||||||
checkService,
|
checkService,
|
||||||
buffer,
|
buffer,
|
||||||
incidentService,
|
incidentService,
|
||||||
|
globalpingService,
|
||||||
maintenanceWindowsRepository,
|
maintenanceWindowsRepository,
|
||||||
monitorsRepository,
|
monitorsRepository,
|
||||||
teamsRepository,
|
teamsRepository,
|
||||||
@@ -66,6 +69,7 @@ class SuperSimpleQueueHelper {
|
|||||||
checkService: any;
|
checkService: any;
|
||||||
buffer: any;
|
buffer: any;
|
||||||
incidentService: IncidentService;
|
incidentService: IncidentService;
|
||||||
|
globalpingService: GlobalpingService;
|
||||||
maintenanceWindowsRepository: IMaintenanceWindowsRepository;
|
maintenanceWindowsRepository: IMaintenanceWindowsRepository;
|
||||||
monitorsRepository: IMonitorsRepository;
|
monitorsRepository: IMonitorsRepository;
|
||||||
teamsRepository: ITeamsRepository;
|
teamsRepository: ITeamsRepository;
|
||||||
@@ -80,6 +84,7 @@ class SuperSimpleQueueHelper {
|
|||||||
this.buffer = buffer;
|
this.buffer = buffer;
|
||||||
this.notificationsService = notificationsService;
|
this.notificationsService = notificationsService;
|
||||||
this.incidentService = incidentService;
|
this.incidentService = incidentService;
|
||||||
|
this.globalpingService = globalpingService;
|
||||||
this.maintenanceWindowsRepository = maintenanceWindowsRepository;
|
this.maintenanceWindowsRepository = maintenanceWindowsRepository;
|
||||||
this.monitorsRepository = monitorsRepository;
|
this.monitorsRepository = monitorsRepository;
|
||||||
this.teamsRepository = teamsRepository;
|
this.teamsRepository = teamsRepository;
|
||||||
@@ -122,7 +127,34 @@ class SuperSimpleQueueHelper {
|
|||||||
throw new Error("No network response");
|
throw new Error("No network response");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 3. Build check
|
// Step 2b. Run Globalping checks (fire-and-forget, doesn't affect local check pipeline)
|
||||||
|
if (monitor.globalpingEnabled && (monitor.type === "http" || monitor.type === "ping")) {
|
||||||
|
this.globalpingService
|
||||||
|
.runChecks(monitor)
|
||||||
|
.then((locationStatuses) => {
|
||||||
|
for (const locStatus of locationStatuses) {
|
||||||
|
const locCheck = this.checkService.buildCheck(locStatus);
|
||||||
|
if (locCheck) {
|
||||||
|
this.buffer.addToBuffer(locCheck);
|
||||||
|
} else {
|
||||||
|
this.logger.warn({
|
||||||
|
message: `Failed to build Globalping check for monitor ${monitorId}, location: ${locStatus.location}`,
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "getMonitorJob",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
this.logger.warn({
|
||||||
|
message: `Globalping checks failed for monitor ${monitorId}: ${err.message}`,
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "getMonitorJob",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3. Build check
|
||||||
const check = await this.checkService.buildCheck(status);
|
const check = await this.checkService.buildCheck(status);
|
||||||
if (!check) {
|
if (!check) {
|
||||||
this.logger.warn({
|
this.logger.warn({
|
||||||
@@ -133,15 +165,15 @@ class SuperSimpleQueueHelper {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Step 4 Add check to buffer
|
// Step 4. Add check to buffer
|
||||||
this.buffer.addToBuffer(check);
|
this.buffer.addToBuffer(check);
|
||||||
// Step 4. Update monitor status
|
// Step 5. Update monitor status
|
||||||
const statusChangeResult = await this.statusService.updateMonitorStatus(status, check);
|
const statusChangeResult = await this.statusService.updateMonitorStatus(status, check);
|
||||||
|
|
||||||
// Step 5. Get decisions
|
// Step 6. Get decisions
|
||||||
const decision = this.evaluateMonitorAction(statusChangeResult);
|
const decision = this.evaluateMonitorAction(statusChangeResult);
|
||||||
|
|
||||||
// Step 6. Handle notifications (best effort, continue even in event of failure, don't wait)
|
// Step 7. Handle notifications (best effort, continue even in event of failure, don't wait)
|
||||||
if (decision.shouldSendNotification) {
|
if (decision.shouldSendNotification) {
|
||||||
this.notificationsService.handleNotifications(statusChangeResult.monitor, status, decision).catch((error: any) => {
|
this.notificationsService.handleNotifications(statusChangeResult.monitor, status, decision).catch((error: any) => {
|
||||||
this.logger.error({
|
this.logger.error({
|
||||||
@@ -154,7 +186,7 @@ class SuperSimpleQueueHelper {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 7. Handle incidents (best effort, don't wait)
|
// Step 8. Handle incidents (best effort, don't wait)
|
||||||
this.incidentService.handleIncident(statusChangeResult.monitor, statusChangeResult.code, decision, status).catch((error: any) => {
|
this.incidentService.handleIncident(statusChangeResult.monitor, statusChangeResult.code, decision, status).catch((error: any) => {
|
||||||
this.logger.warn({
|
this.logger.warn({
|
||||||
message: error.message,
|
message: error.message,
|
||||||
|
|||||||
@@ -0,0 +1,308 @@
|
|||||||
|
import { Globalping } from "globalping";
|
||||||
|
import type { ISettingsService } from "@/service/system/settingsService.js";
|
||||||
|
import type { Monitor } from "@/types/monitor.js";
|
||||||
|
import type { MonitorStatusResponse } from "@/types/network.js";
|
||||||
|
|
||||||
|
const SERVICE_NAME = "GlobalpingService";
|
||||||
|
|
||||||
|
export interface GlobalpingLocationOption {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maps location IDs to Globalping API location objects
|
||||||
|
const LOCATION_MAP: Record<string, Record<string, string>> = {
|
||||||
|
"north-america": { continent: "NA" },
|
||||||
|
europe: { continent: "EU" },
|
||||||
|
asia: { continent: "AS" },
|
||||||
|
"south-america": { continent: "SA" },
|
||||||
|
oceania: { continent: "OC" },
|
||||||
|
africa: { continent: "AF" },
|
||||||
|
"us-east": { country: "US", state: "VA" },
|
||||||
|
"us-west": { country: "US", state: "CA" },
|
||||||
|
canada: { country: "CA", city: "Toronto" },
|
||||||
|
uk: { country: "GB", city: "London" },
|
||||||
|
germany: { country: "DE", city: "Frankfurt" },
|
||||||
|
netherlands: { country: "NL", city: "Amsterdam" },
|
||||||
|
poland: { country: "PL", city: "Warsaw" },
|
||||||
|
japan: { country: "JP", city: "Tokyo" },
|
||||||
|
singapore: { country: "SG", city: "Singapore" },
|
||||||
|
india: { country: "IN", city: "Mumbai" },
|
||||||
|
australia: { country: "AU", city: "Sydney" },
|
||||||
|
brazil: { country: "BR" },
|
||||||
|
chile: { country: "CL", city: "Santiago" },
|
||||||
|
"south-africa": { country: "ZA", city: "Johannesburg" },
|
||||||
|
uae: { country: "AE", city: "Dubai" },
|
||||||
|
};
|
||||||
|
|
||||||
|
const LOCATIONS_TIER_3: GlobalpingLocationOption[] = [
|
||||||
|
{ id: "north-america", label: "North America" },
|
||||||
|
{ id: "europe", label: "Europe" },
|
||||||
|
{ id: "asia", label: "Asia" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const LOCATIONS_TIER_6: GlobalpingLocationOption[] = [
|
||||||
|
...LOCATIONS_TIER_3,
|
||||||
|
{ id: "south-america", label: "South America" },
|
||||||
|
{ id: "oceania", label: "Oceania" },
|
||||||
|
{ id: "africa", label: "Africa" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const LOCATIONS_TIER_15: GlobalpingLocationOption[] = [
|
||||||
|
{ id: "us-east", label: "US East (Virginia)" },
|
||||||
|
{ id: "us-west", label: "US West (California)" },
|
||||||
|
{ id: "canada", label: "Canada (Toronto)" },
|
||||||
|
{ id: "uk", label: "UK (London)" },
|
||||||
|
{ id: "germany", label: "Germany (Frankfurt)" },
|
||||||
|
{ id: "netherlands", label: "Netherlands (Amsterdam)" },
|
||||||
|
{ id: "poland", label: "Poland (Warsaw)" },
|
||||||
|
{ id: "japan", label: "Japan (Tokyo)" },
|
||||||
|
{ id: "singapore", label: "Singapore" },
|
||||||
|
{ id: "india", label: "India (Mumbai)" },
|
||||||
|
{ id: "australia", label: "Australia (Sydney)" },
|
||||||
|
{ id: "brazil", label: "Brazil" },
|
||||||
|
{ id: "chile", label: "Chile (Santiago)" },
|
||||||
|
{ id: "south-africa", label: "South Africa (Johannesburg)" },
|
||||||
|
{ id: "uae", label: "UAE (Dubai)" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const LOCATIONS_BY_TIER: Record<number, GlobalpingLocationOption[]> = {
|
||||||
|
3: LOCATIONS_TIER_3,
|
||||||
|
6: LOCATIONS_TIER_6,
|
||||||
|
15: LOCATIONS_TIER_15,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build a lookup from location ID to human-readable label
|
||||||
|
const LOCATION_LABELS: Record<string, string> = {};
|
||||||
|
for (const tier of Object.values(LOCATIONS_BY_TIER)) {
|
||||||
|
for (const loc of tier) {
|
||||||
|
LOCATION_LABELS[loc.id] = loc.label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const HOSTNAME_REGEX = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*$/;
|
||||||
|
|
||||||
|
export const getGlobalpingLocationsByTier = (tier: number): GlobalpingLocationOption[] => {
|
||||||
|
return LOCATIONS_BY_TIER[tier] ?? LOCATIONS_TIER_6;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GlobalpingService {
|
||||||
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
|
|
||||||
|
private logger: any;
|
||||||
|
private settingsService: ISettingsService;
|
||||||
|
private client: Globalping<false> | null = null;
|
||||||
|
private lastApiKey: string | undefined;
|
||||||
|
private inFlightChecks = new Set<string>();
|
||||||
|
|
||||||
|
constructor({ settingsService, logger }: { settingsService: ISettingsService; logger: any }) {
|
||||||
|
this.settingsService = settingsService;
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
get serviceName() {
|
||||||
|
return GlobalpingService.SERVICE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads settings once, returns both the SDK client and configured location IDs.
|
||||||
|
* Returns null client if the API key is not configured.
|
||||||
|
*/
|
||||||
|
private async getClientAndLocations(): Promise<{
|
||||||
|
client: Globalping<false> | null;
|
||||||
|
locationIds: string[];
|
||||||
|
}> {
|
||||||
|
const dbSettings = await this.settingsService.getDBSettings();
|
||||||
|
const apiKey = dbSettings.globalpingApiKey;
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
return { client: null, locationIds: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.client || this.lastApiKey !== apiKey) {
|
||||||
|
this.client = new Globalping({ auth: apiKey, throwApiErrors: false, timeout: 30000 });
|
||||||
|
this.lastApiKey = apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tier = dbSettings.globalpingLocationsTier ?? 6;
|
||||||
|
const locationIds = getGlobalpingLocationsByTier(tier).map((loc) => loc.id);
|
||||||
|
|
||||||
|
return { client: this.client, locationIds };
|
||||||
|
}
|
||||||
|
|
||||||
|
async runChecks(monitor: Monitor): Promise<MonitorStatusResponse[]> {
|
||||||
|
if (monitor.type !== "http" && monitor.type !== "ping") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent overlapping checks for the same monitor
|
||||||
|
if (this.inFlightChecks.has(monitor.id)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.inFlightChecks.add(monitor.id);
|
||||||
|
try {
|
||||||
|
return await this.executeChecks(monitor);
|
||||||
|
} finally {
|
||||||
|
this.inFlightChecks.delete(monitor.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async executeChecks(monitor: Monitor): Promise<MonitorStatusResponse[]> {
|
||||||
|
const { client, locationIds } = await this.getClientAndLocations();
|
||||||
|
if (!client || locationIds.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const measurementType = monitor.type === "ping" ? "ping" : "http";
|
||||||
|
const monitorUrl = monitor.url ?? "";
|
||||||
|
|
||||||
|
let target: string;
|
||||||
|
try {
|
||||||
|
if (measurementType === "http") {
|
||||||
|
const url = new URL(monitorUrl);
|
||||||
|
target = url.hostname;
|
||||||
|
} else {
|
||||||
|
target = monitorUrl.replace(/^https?:\/\//, "").split("/")[0] ?? monitorUrl;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
target = monitorUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!target || !HOSTNAME_REGEX.test(target)) {
|
||||||
|
this.logger.warn({
|
||||||
|
message: `Invalid target hostname for Globalping: "${target}"`,
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "executeChecks",
|
||||||
|
monitorId: monitor.id,
|
||||||
|
});
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const locations = locationIds.map((loc) => LOCATION_MAP[loc]).filter(Boolean);
|
||||||
|
|
||||||
|
if (locations.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const createResult =
|
||||||
|
measurementType === "http"
|
||||||
|
? await client.createMeasurement({
|
||||||
|
type: "http" as const,
|
||||||
|
target,
|
||||||
|
locations: locations as any,
|
||||||
|
measurementOptions: { request: { method: "HEAD" }, protocol: "HTTPS" },
|
||||||
|
})
|
||||||
|
: await client.createMeasurement({
|
||||||
|
type: "ping" as const,
|
||||||
|
target,
|
||||||
|
locations: locations as any,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!createResult.ok) {
|
||||||
|
this.logger.warn({
|
||||||
|
message: "Failed to create Globalping measurement",
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "executeChecks",
|
||||||
|
details: JSON.stringify(createResult.data),
|
||||||
|
});
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const measurementId = (createResult.data as any).id;
|
||||||
|
const awaitResult = await client.awaitMeasurement(measurementId);
|
||||||
|
|
||||||
|
if (!awaitResult.ok) {
|
||||||
|
this.logger.warn({
|
||||||
|
message: "Failed to await Globalping measurement",
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "executeChecks",
|
||||||
|
details: JSON.stringify(awaitResult.data),
|
||||||
|
});
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const measurement = awaitResult.data as any;
|
||||||
|
const results: MonitorStatusResponse[] = [];
|
||||||
|
|
||||||
|
for (const item of measurement.results || []) {
|
||||||
|
const probe = item.probe;
|
||||||
|
const result = item.result;
|
||||||
|
const locationId = this.resolveLocationId(probe?.continent, probe?.country, locationIds);
|
||||||
|
|
||||||
|
if (measurementType === "http") {
|
||||||
|
results.push({
|
||||||
|
monitorId: monitor.id,
|
||||||
|
teamId: monitor.teamId,
|
||||||
|
type: monitor.type,
|
||||||
|
status: result.statusCode >= 200 && result.statusCode < 400,
|
||||||
|
code: result.statusCode || 0,
|
||||||
|
message: result.statusCodeName || result.rawOutput || "",
|
||||||
|
responseTime: result.timings?.total ?? 0,
|
||||||
|
location: locationId,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Ping: loss < 100% is considered up (consistent with local ping checks)
|
||||||
|
const stats = result.stats;
|
||||||
|
results.push({
|
||||||
|
monitorId: monitor.id,
|
||||||
|
teamId: monitor.teamId,
|
||||||
|
type: monitor.type,
|
||||||
|
status: stats?.loss !== undefined ? stats.loss < 100 : false,
|
||||||
|
code: stats?.loss === 0 ? 200 : 5000,
|
||||||
|
message: stats?.loss === 0 ? "Ping successful" : `Packet loss: ${stats?.loss}%`,
|
||||||
|
responseTime: stats?.avg ?? 0,
|
||||||
|
location: locationId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
} catch (error: any) {
|
||||||
|
this.logger.warn({
|
||||||
|
message: `Globalping check failed: ${error.message}`,
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "executeChecks",
|
||||||
|
stack: error.stack,
|
||||||
|
});
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a probe's continent/country back to a configured location ID.
|
||||||
|
* For city-level tiers, matches by country code.
|
||||||
|
* For continent-level tiers, matches by continent code.
|
||||||
|
*/
|
||||||
|
private resolveLocationId(continent?: string, country?: string, configuredIds?: string[]): string {
|
||||||
|
if (!configuredIds || configuredIds.length === 0) {
|
||||||
|
return continent && country ? `${continent}-${country}` : continent || country || "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try country-level match first (for tier 15)
|
||||||
|
if (country) {
|
||||||
|
for (const id of configuredIds) {
|
||||||
|
const loc = LOCATION_MAP[id];
|
||||||
|
if (loc?.country === country) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to continent-level match (for tier 3/6)
|
||||||
|
if (continent) {
|
||||||
|
for (const id of configuredIds) {
|
||||||
|
const loc = LOCATION_MAP[id];
|
||||||
|
if (loc?.continent === continent) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return continent && country ? `${continent}-${country}` : continent || country || "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { LOCATION_LABELS };
|
||||||
|
export default GlobalpingService;
|
||||||
@@ -6,6 +6,15 @@ export interface CheckMetadata {
|
|||||||
monitorId: string;
|
monitorId: string;
|
||||||
teamId: string;
|
teamId: string;
|
||||||
type: MonitorType;
|
type: MonitorType;
|
||||||
|
location?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LocationResult {
|
||||||
|
location: string;
|
||||||
|
status: boolean;
|
||||||
|
responseTime: number;
|
||||||
|
statusCode: number;
|
||||||
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CheckCpuInfo {
|
export interface CheckCpuInfo {
|
||||||
@@ -113,6 +122,7 @@ export interface Check {
|
|||||||
seo?: number;
|
seo?: number;
|
||||||
performance?: number;
|
performance?: number;
|
||||||
audits?: CheckAudits;
|
audits?: CheckAudits;
|
||||||
|
locationResults?: LocationResult[];
|
||||||
__v: number;
|
__v: number;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export interface Monitor {
|
|||||||
selectedDisks: string[];
|
selectedDisks: string[];
|
||||||
gameId?: string;
|
gameId?: string;
|
||||||
group: string | null;
|
group: string | null;
|
||||||
|
globalpingEnabled: boolean;
|
||||||
recentChecks: CheckSnapshot[];
|
recentChecks: CheckSnapshot[];
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
@@ -64,6 +65,12 @@ export interface MonitorsWithChecksByTeamIdResult {
|
|||||||
monitors: Monitor[];
|
monitors: Monitor[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LocationCheckData {
|
||||||
|
groupedChecks: import("./check.js").GroupedCheck[];
|
||||||
|
uptimePercentage: number;
|
||||||
|
avgResponseTime: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface UptimeDetailsResult {
|
export interface UptimeDetailsResult {
|
||||||
monitorData: {
|
monitorData: {
|
||||||
monitor: Monitor;
|
monitor: Monitor;
|
||||||
@@ -72,6 +79,7 @@ export interface UptimeDetailsResult {
|
|||||||
groupedDownChecks: import("./check.js").GroupedCheck[];
|
groupedDownChecks: import("./check.js").GroupedCheck[];
|
||||||
groupedAvgResponseTime: number;
|
groupedAvgResponseTime: number;
|
||||||
groupedUptimePercentage: number;
|
groupedUptimePercentage: number;
|
||||||
|
locationChecks?: Record<string, LocationCheckData>;
|
||||||
};
|
};
|
||||||
monitorStats: import("./monitorStats.js").MonitorStats | null;
|
monitorStats: import("./monitorStats.js").MonitorStats | null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,15 @@ export interface MonitorStatusResponse<T = any> {
|
|||||||
matchMethod?: MonitorMatchMethod;
|
matchMethod?: MonitorMatchMethod;
|
||||||
expectedValue?: string;
|
expectedValue?: string;
|
||||||
extracted?: unknown;
|
extracted?: unknown;
|
||||||
|
location?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GlobalpingCheckResult {
|
||||||
|
location: string;
|
||||||
|
status: boolean;
|
||||||
|
responseTime: number;
|
||||||
|
statusCode: number;
|
||||||
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PingStatusPayload {
|
export interface PingStatusPayload {
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ export interface Settings {
|
|||||||
systemEmailRequireTLS: boolean;
|
systemEmailRequireTLS: boolean;
|
||||||
systemEmailRejectUnauthorized: boolean;
|
systemEmailRejectUnauthorized: boolean;
|
||||||
showURL: boolean;
|
showURL: boolean;
|
||||||
|
globalpingApiKey?: string;
|
||||||
|
globalpingLocationsTier?: number;
|
||||||
singleton: boolean;
|
singleton: boolean;
|
||||||
version: number;
|
version: number;
|
||||||
globalThresholds?: SettingsThresholds;
|
globalThresholds?: SettingsThresholds;
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ const createMonitorBodyValidation = joi.object({
|
|||||||
gameId: joi.string().allow(""),
|
gameId: joi.string().allow(""),
|
||||||
selectedDisks: joi.array().items(joi.string()).optional(),
|
selectedDisks: joi.array().items(joi.string()).optional(),
|
||||||
group: joi.string().max(50).trim().allow(null, "").optional(),
|
group: joi.string().max(50).trim().allow(null, "").optional(),
|
||||||
|
globalpingEnabled: joi.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const createMonitorsBodyValidation = joi.array().items(
|
const createMonitorsBodyValidation = joi.array().items(
|
||||||
@@ -203,6 +204,7 @@ const editMonitorBodyValidation = joi
|
|||||||
gameId: joi.string().allow(""),
|
gameId: joi.string().allow(""),
|
||||||
selectedDisks: joi.array().items(joi.string()).optional(),
|
selectedDisks: joi.array().items(joi.string()).optional(),
|
||||||
group: joi.string().max(50).trim().allow(null, "").optional(),
|
group: joi.string().max(50).trim().allow(null, "").optional(),
|
||||||
|
globalpingEnabled: joi.boolean().optional(),
|
||||||
})
|
})
|
||||||
.options({ stripUnknown: true });
|
.options({ stripUnknown: true });
|
||||||
|
|
||||||
@@ -428,6 +430,8 @@ const updateAppSettingsBodyValidation = joi.object({
|
|||||||
systemEmailRequireTLS: joi.boolean(),
|
systemEmailRequireTLS: joi.boolean(),
|
||||||
systemEmailRejectUnauthorized: joi.boolean(),
|
systemEmailRejectUnauthorized: joi.boolean(),
|
||||||
|
|
||||||
|
globalpingApiKey: joi.string().allow(""),
|
||||||
|
globalpingLocationsTier: joi.number().valid(3, 6, 15).optional(),
|
||||||
globalThresholds: joi
|
globalThresholds: joi
|
||||||
.object({
|
.object({
|
||||||
cpu: joi.number().min(1).max(100).allow(""),
|
cpu: joi.number().min(1).max(100).allow(""),
|
||||||
|
|||||||
Reference in new issue
Block a user