Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a924227805 | ||
|
|
e4e75632c5 | ||
|
|
ddb7c24832 | ||
|
|
2144d3f8c0 | ||
|
|
4a824dec81 | ||
|
|
b35fee1be8 | ||
|
|
1b167af12b | ||
|
|
f2962e34ae | ||
|
|
386db5029a | ||
|
|
0e6e54a124 | ||
|
|
c666cb3879 | ||
|
|
4039517683 | ||
|
|
00fc7b8038 | ||
|
|
81d283ec8d | ||
|
|
105100e349 | ||
|
|
4598690dbf | ||
|
|
f68341c315 | ||
|
|
e8a8cea909 | ||
|
|
bf53f107e3 | ||
|
|
00de974528 | ||
|
|
0445398e94 | ||
|
|
272e2ba955 | ||
|
|
0a2adc2d92 | ||
|
|
7008fc1770 | ||
|
|
041a261540 | ||
|
|
1008d819f7 | ||
|
|
fb84a79a04 | ||
|
|
fcd6786149 | ||
|
|
45bfdbdce0 | ||
|
|
1537c2141f | ||
|
|
ea968ebf3c | ||
|
|
3ba1a52644 | ||
|
|
a015cc6706 | ||
|
|
5bad362793 | ||
|
|
5520756edd | ||
|
|
ba954a1d6e | ||
|
|
2944ff298c | ||
|
|
80d121e87a | ||
|
|
8a7b852974 | ||
|
|
85f00eb7e8 | ||
|
|
8e13855b66 | ||
|
|
c8d3e3b70c | ||
|
|
7a90a6f9f4 |
No files matched your search
@@ -7,7 +7,7 @@ import { URLSearchParams } from "url";
|
||||
const API_TOKEN = process.env.POEDITOR_API;
|
||||
const PROJECT_ID = process.env.POEDITOR_PROJECT_ID;
|
||||
const LANGUAGES = (
|
||||
process.env.LANGUAGES || "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn"
|
||||
process.env.LANGUAGES || "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn,th"
|
||||
).split(",");
|
||||
const EXPORT_FORMAT = process.env.EXPORT_FORMAT || "key_value_json";
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
languages:
|
||||
description: "Languages to synchronize (comma separated, e.g.: tr,en,es)"
|
||||
required: false
|
||||
default: "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn"
|
||||
default: "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn,th"
|
||||
format:
|
||||
description: "Export format (key_value_json or json)"
|
||||
required: false
|
||||
|
||||
@@ -52,7 +52,7 @@ Usage instructions can be found [here](https://docs.checkmate.so/checkmate-2.1).
|
||||
|
||||
See installation instructions in [Checkmate documentation portal](https://docs.checkmate.so/checkmate-2.1/users-guide/quickstart).
|
||||
|
||||
Alternatively, you can also use [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md) or [Pikapods](https://www.pikapods.com/) to quickly spin off a Checkmate instance. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions.
|
||||
Alternatively, you can also use [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (South Africa) or [Pikapods](https://www.pikapods.com/) to quickly spin off a Checkmate instance. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions.
|
||||
|
||||
### Using a Custom CA
|
||||
|
||||
|
||||
Generated
+22
-8
@@ -45,7 +45,7 @@
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.6",
|
||||
"prettier": "^3.3.3",
|
||||
"vite": "6.3.5"
|
||||
"vite": "6.3.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
@@ -2422,13 +2422,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
|
||||
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
|
||||
"version": "1.12.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
|
||||
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
@@ -3756,6 +3756,20 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
@@ -6375,9 +6389,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.3.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
|
||||
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
|
||||
"version": "6.3.6",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
|
||||
"integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
|
||||
+1
-1
@@ -58,6 +58,6 @@
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.6",
|
||||
"prettier": "^3.3.3",
|
||||
"vite": "6.3.5"
|
||||
"vite": "6.3.6"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import GenericFallback from "../GenericFallback";
|
||||
import Fallback from "../Fallback";
|
||||
import { Typography } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useIsAdmin } from "../../Hooks/useIsAdmin";
|
||||
import PropTypes from "prop-types";
|
||||
const PageStateWrapper = ({
|
||||
networkError,
|
||||
isLoading,
|
||||
items,
|
||||
type,
|
||||
fallbackLink,
|
||||
fallbackChildren,
|
||||
children,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const isAdmin = useIsAdmin();
|
||||
const isEmpty = items === null || (Array.isArray(items) && items.length === 0);
|
||||
const hasInitialized = items !== undefined && !(isLoading && isEmpty);
|
||||
const showEmpty = !isLoading && isEmpty;
|
||||
if (networkError) {
|
||||
return (
|
||||
<GenericFallback>
|
||||
<Typography
|
||||
variant="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{t("common.toasts.networkError")}
|
||||
</Typography>
|
||||
<Typography>{t("common.toasts.checkConnection")}</Typography>
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
if (!hasInitialized) {
|
||||
return null;
|
||||
}
|
||||
if (showEmpty) {
|
||||
return (
|
||||
<Fallback
|
||||
type={type}
|
||||
title={t(`${type}.fallback.title`)}
|
||||
checks={t(`${type}.fallback.checks`, { returnObjects: true })}
|
||||
link={fallbackLink}
|
||||
isAdmin={isAdmin}
|
||||
>
|
||||
{fallbackChildren}
|
||||
</Fallback>
|
||||
);
|
||||
}
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
PageStateWrapper.propTypes = {
|
||||
networkError: PropTypes.any,
|
||||
isLoading: PropTypes.bool,
|
||||
items: PropTypes.oneOfType([PropTypes.array, PropTypes.oneOf([null])]),
|
||||
type: PropTypes.string,
|
||||
fallbackLink: PropTypes.string,
|
||||
fallbackChildren: PropTypes.node,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
export default PageStateWrapper;
|
||||
@@ -34,7 +34,7 @@ const useCreateNotification = () => {
|
||||
|
||||
const useGetNotificationsByTeamId = (updateTrigger) => {
|
||||
const [notifications, setNotifications] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useState, useCallback } from "react";
|
||||
const useInfrastructureMonitorForm = () => {
|
||||
const [infrastructureMonitor, setInfrastructureMonitor] = useState({
|
||||
url: "",
|
||||
@@ -6,6 +6,8 @@ const useInfrastructureMonitorForm = () => {
|
||||
notifications: [],
|
||||
notify_email: false,
|
||||
interval: 0.25,
|
||||
statusWindowSize: 5,
|
||||
statusWindowThreshold: 60,
|
||||
cpu: false,
|
||||
usage_cpu: "",
|
||||
memory: false,
|
||||
@@ -29,9 +31,10 @@ const useInfrastructureMonitorForm = () => {
|
||||
[event.target.name]: event.target.checked,
|
||||
});
|
||||
};
|
||||
const initializeInfrastructureMonitorForCreate = (globalSettings) => {
|
||||
const initializeInfrastructureMonitorForCreate = useCallback((globalSettings) => {
|
||||
const gt = globalSettings?.data?.settings?.globalThresholds || {};
|
||||
setInfrastructureMonitor({
|
||||
setInfrastructureMonitor((prev) => ({
|
||||
...prev,
|
||||
url: "",
|
||||
name: "",
|
||||
notifications: [],
|
||||
@@ -45,17 +48,20 @@ const useInfrastructureMonitorForm = () => {
|
||||
temperature: gt.temperature !== undefined,
|
||||
usage_temperature: gt.temperature !== undefined ? gt.temperature.toString() : "",
|
||||
secret: "",
|
||||
});
|
||||
};
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const initializeInfrastructureMonitorForUpdate = (monitor) => {
|
||||
const initializeInfrastructureMonitorForUpdate = useCallback((monitor) => {
|
||||
const MS_PER_MINUTE = 60000;
|
||||
const { thresholds = {} } = monitor;
|
||||
setInfrastructureMonitor({
|
||||
setInfrastructureMonitor((prev) => ({
|
||||
...prev,
|
||||
url: monitor.url.replace(/^https?:\/\//, ""),
|
||||
name: monitor.name || "",
|
||||
notifications: monitor.notifications || [],
|
||||
interval: monitor.interval / MS_PER_MINUTE,
|
||||
statusWindowSize: monitor.statusWindowSize,
|
||||
statusWindowThreshold: monitor.statusWindowThreshold,
|
||||
cpu: thresholds.usage_cpu !== undefined,
|
||||
usage_cpu:
|
||||
thresholds.usage_cpu !== undefined ? (thresholds.usage_cpu * 100).toString() : "",
|
||||
@@ -75,8 +81,8 @@ const useInfrastructureMonitorForm = () => {
|
||||
? (thresholds.usage_temperature * 100).toString()
|
||||
: "",
|
||||
secret: monitor.secret || "",
|
||||
});
|
||||
};
|
||||
}));
|
||||
}, []);
|
||||
return {
|
||||
infrastructureMonitor,
|
||||
setInfrastructureMonitor,
|
||||
|
||||
@@ -12,6 +12,8 @@ const useInfrastructureSubmit = () => {
|
||||
? infrastructureMonitor.url
|
||||
: infrastructureMonitor.name,
|
||||
interval: infrastructureMonitor.interval * MS_PER_MINUTE,
|
||||
statusWindowSize: infrastructureMonitor.statusWindowSize,
|
||||
statusWindowThreshold: infrastructureMonitor.statusWindowThreshold,
|
||||
cpu: infrastructureMonitor.cpu,
|
||||
...(infrastructureMonitor.cpu
|
||||
? { usage_cpu: infrastructureMonitor.usage_cpu }
|
||||
|
||||
@@ -86,7 +86,14 @@ const CreateInfrastructureMonitor = () => {
|
||||
setHttps(monitor.url.startsWith("https"));
|
||||
initializeInfrastructureMonitorForUpdate(monitor);
|
||||
}
|
||||
}, [isCreate, monitor, globalSettings, globalSettingsLoading]);
|
||||
}, [
|
||||
isCreate,
|
||||
monitor,
|
||||
globalSettings,
|
||||
globalSettingsLoading,
|
||||
initializeInfrastructureMonitorForCreate,
|
||||
initializeInfrastructureMonitorForUpdate,
|
||||
]);
|
||||
|
||||
// Handlers
|
||||
const onSubmit = async (event) => {
|
||||
@@ -281,6 +288,39 @@ const CreateInfrastructureMonitor = () => {
|
||||
setNotifications={infrastructureMonitor.notifications}
|
||||
/>
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="h2"
|
||||
>
|
||||
{t("createMonitorPage.incidentConfigTitle")}
|
||||
</Typography>
|
||||
<Typography component="p">
|
||||
{t("createMonitorPage.incidentConfigDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(20)}>
|
||||
<TextInput
|
||||
name="statusWindowSize"
|
||||
label={t("createMonitorPage.incidentConfigStatusWindowLabel")}
|
||||
type="number"
|
||||
value={infrastructureMonitor.statusWindowSize}
|
||||
onChange={onChange}
|
||||
error={errors["statusWindowSize"] ? true : false}
|
||||
helperText={errors["statusWindowSize"]}
|
||||
/>
|
||||
<TextInput
|
||||
name="statusWindowThreshold"
|
||||
label={t("createMonitorPage.incidentConfigStatusWindowThresholdLabel")}
|
||||
type="number"
|
||||
value={infrastructureMonitor.statusWindowThreshold}
|
||||
onChange={onChange}
|
||||
error={errors["statusWindowThreshold"] ? true : false}
|
||||
helperText={errors["statusWindowThreshold"]}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
<CustomAlertsSection
|
||||
errors={errors}
|
||||
onChange={onChange}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
// Components
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import { Stack } from "@mui/material";
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
import MonitorCountHeader from "../../../Components/MonitorCountHeader";
|
||||
import MonitorCreateHeader from "../../../Components/MonitorCreateHeader";
|
||||
import MonitorsTable from "./Components/MonitorsTable";
|
||||
import Pagination from "../../..//Components/Table/TablePagination";
|
||||
import GenericFallback from "../../../Components/GenericFallback";
|
||||
import Fallback from "../../../Components/Fallback";
|
||||
import PageStateWrapper from "../../../Components/PageStateWrapper";
|
||||
import Filter from "./Components/Filters";
|
||||
import SearchComponent from "../../Uptime/Monitors/Components/SearchComponent";
|
||||
// Utils
|
||||
@@ -81,75 +80,58 @@ const InfrastructureMonitors = () => {
|
||||
updateTrigger,
|
||||
});
|
||||
|
||||
if (networkError === true) {
|
||||
return (
|
||||
<GenericFallback>
|
||||
<Typography
|
||||
variant="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{t("common.toasts.networkError")}
|
||||
</Typography>
|
||||
<Typography>{t("common.toasts.checkConnection")}</Typography>
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isLoading && typeof summary?.totalMonitors === "undefined") {
|
||||
return (
|
||||
<Fallback
|
||||
type="infrastructureMonitor"
|
||||
title={t("infrastructureMonitor.fallback.title")}
|
||||
checks={t("infrastructureMonitor.fallback.checks", { returnObjects: true })}
|
||||
link="/infrastructure/create"
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<MonitorCreateHeader
|
||||
isAdmin={isAdmin}
|
||||
<>
|
||||
<PageStateWrapper
|
||||
networkError={networkError}
|
||||
isLoading={isLoading}
|
||||
path="/infrastructure/create"
|
||||
/>
|
||||
<Stack direction={"row"}>
|
||||
<MonitorCountHeader
|
||||
isLoading={isLoading}
|
||||
monitorCount={summary?.totalMonitors ?? 0}
|
||||
/>
|
||||
<Filter
|
||||
selectedStatus={selectedStatus}
|
||||
setSelectedStatus={setSelectedStatus}
|
||||
setToFilterStatus={setToFilterStatus}
|
||||
handleReset={handleReset}
|
||||
/>
|
||||
<SearchComponent
|
||||
monitors={monitors}
|
||||
onSearchChange={setSearch}
|
||||
setIsSearching={setIsSearching}
|
||||
/>
|
||||
</Stack>
|
||||
items={monitors}
|
||||
type="infrastructureMonitor"
|
||||
fallbackLink="/infrastructure/create"
|
||||
>
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<MonitorCreateHeader
|
||||
isAdmin={isAdmin}
|
||||
isLoading={isLoading}
|
||||
path="/infrastructure/create"
|
||||
/>
|
||||
<Stack direction={"row"}>
|
||||
<MonitorCountHeader
|
||||
isLoading={isLoading}
|
||||
monitorCount={summary?.totalMonitors ?? 0}
|
||||
/>
|
||||
<Filter
|
||||
selectedStatus={selectedStatus}
|
||||
setSelectedStatus={setSelectedStatus}
|
||||
setToFilterStatus={setToFilterStatus}
|
||||
handleReset={handleReset}
|
||||
/>
|
||||
<SearchComponent
|
||||
monitors={monitors}
|
||||
onSearchChange={setSearch}
|
||||
setIsSearching={setIsSearching}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<MonitorsTable
|
||||
isLoading={isLoading}
|
||||
monitors={monitors}
|
||||
isAdmin={isAdmin}
|
||||
handleActionMenuDelete={handleActionMenuDelete}
|
||||
isSearching={isSearching}
|
||||
/>
|
||||
<Pagination
|
||||
itemCount={summary?.totalMonitors}
|
||||
paginationLabel={t("monitors")}
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
handleChangePage={handleChangePage}
|
||||
handleChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
</Stack>
|
||||
<MonitorsTable
|
||||
isLoading={isLoading}
|
||||
monitors={monitors}
|
||||
isAdmin={isAdmin}
|
||||
handleActionMenuDelete={handleActionMenuDelete}
|
||||
isSearching={isSearching}
|
||||
/>
|
||||
<Pagination
|
||||
itemCount={summary?.totalMonitors}
|
||||
paginationLabel={t("monitors")}
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
handleChangePage={handleChangePage}
|
||||
handleChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
</Stack>
|
||||
</PageStateWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Stack, Button } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import Fallback from "../../Components/Fallback";
|
||||
import { useState, useEffect } from "react";
|
||||
import "./index.css";
|
||||
import MaintenanceTable from "./MaintenanceTable";
|
||||
@@ -8,24 +7,21 @@ import { useSelector } from "react-redux";
|
||||
import { networkService } from "../../main";
|
||||
import Breadcrumbs from "../../Components/Breadcrumbs";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useIsAdmin } from "../../Hooks/useIsAdmin";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Typography } from "@mui/material";
|
||||
import GenericFallback from "../../Components/GenericFallback";
|
||||
import PageStateWrapper from "../../Components/PageStateWrapper";
|
||||
|
||||
const Maintenance = () => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const rowsPerPage = useSelector((state) => state?.ui?.maintenance?.rowsPerPage ?? 5);
|
||||
const isAdmin = useIsAdmin();
|
||||
const [maintenanceWindows, setMaintenanceWindows] = useState([]);
|
||||
const [maintenanceWindows, setMaintenanceWindows] = useState(undefined);
|
||||
const [maintenanceWindowCount, setMaintenanceWindowCount] = useState(0);
|
||||
const [page, setPage] = useState(0);
|
||||
const [sort, setSort] = useState({});
|
||||
const [updateTrigger, setUpdateTrigger] = useState(false);
|
||||
const [networkError, setNetworkError] = useState(false);
|
||||
const [isDataFetched, setIsDataFetched] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const handleActionMenuDelete = () => {
|
||||
setUpdateTrigger((prev) => !prev);
|
||||
@@ -34,80 +30,65 @@ const Maintenance = () => {
|
||||
useEffect(() => {
|
||||
const fetchMaintenanceWindows = async () => {
|
||||
try {
|
||||
setNetworkError(false);
|
||||
setIsLoading(true);
|
||||
const response = await networkService.getMaintenanceWindowsByTeamId({
|
||||
page: page,
|
||||
rowsPerPage: rowsPerPage,
|
||||
});
|
||||
const { maintenanceWindows, maintenanceWindowCount } = response.data.data;
|
||||
setMaintenanceWindows(maintenanceWindows);
|
||||
setMaintenanceWindowCount(maintenanceWindowCount);
|
||||
setMaintenanceWindows(maintenanceWindows ?? []);
|
||||
setMaintenanceWindowCount(maintenanceWindowCount ?? 0);
|
||||
} catch (error) {
|
||||
setNetworkError(true);
|
||||
} finally {
|
||||
setIsDataFetched(true);
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
fetchMaintenanceWindows();
|
||||
}, [page, rowsPerPage, updateTrigger]);
|
||||
|
||||
if (networkError) {
|
||||
return (
|
||||
<GenericFallback>
|
||||
<Typography
|
||||
variant="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{t("common.toasts.networkError")}
|
||||
</Typography>
|
||||
<Typography>{t("common.toasts.checkConnection")}</Typography>
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
// Only show the fallback if we've fetched data and there are no maintenance windows
|
||||
if (isDataFetched && maintenanceWindows.length === 0) {
|
||||
return (
|
||||
<Fallback
|
||||
type="maintenanceWindow"
|
||||
title={t("maintenanceWindow.fallback.title")}
|
||||
checks={t("maintenanceWindow.fallback.checks", { returnObjects: true })}
|
||||
link="/maintenance/create"
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
mt={theme.spacing(5)}
|
||||
<>
|
||||
<PageStateWrapper
|
||||
networkError={networkError}
|
||||
isLoading={isLoading}
|
||||
items={maintenanceWindows}
|
||||
type="maintenanceWindow"
|
||||
fallbackLink="/maintenance/create"
|
||||
>
|
||||
<Breadcrumbs list={[{ name: "maintenance", path: "/maintenance" }]} />
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={() => {
|
||||
navigate("/maintenance/create");
|
||||
}}
|
||||
sx={{ fontWeight: 500 }}
|
||||
>
|
||||
{t("createMaintenanceWindow")}
|
||||
</Button>
|
||||
</Stack>
|
||||
<MaintenanceTable
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
rowsPerPage={rowsPerPage}
|
||||
sort={sort}
|
||||
setSort={setSort}
|
||||
maintenanceWindows={maintenanceWindows}
|
||||
maintenanceWindowCount={maintenanceWindowCount}
|
||||
updateCallback={handleActionMenuDelete}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
mt={theme.spacing(5)}
|
||||
>
|
||||
<Breadcrumbs list={[{ name: "maintenance", path: "/maintenance" }]} />
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={() => {
|
||||
navigate("/maintenance/create");
|
||||
}}
|
||||
sx={{ fontWeight: 500 }}
|
||||
>
|
||||
{t("createMaintenanceWindow")}
|
||||
</Button>
|
||||
</Stack>
|
||||
<MaintenanceTable
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
rowsPerPage={rowsPerPage}
|
||||
sort={sort}
|
||||
setSort={setSort}
|
||||
maintenanceWindows={maintenanceWindows}
|
||||
maintenanceWindowCount={maintenanceWindowCount}
|
||||
updateCallback={handleActionMenuDelete}
|
||||
/>
|
||||
</Stack>
|
||||
</PageStateWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -4,11 +4,10 @@ import Typography from "@mui/material/Typography";
|
||||
import Breadcrumbs from "../../Components/Breadcrumbs";
|
||||
import Button from "@mui/material/Button";
|
||||
import DataTable from "../../Components/Table";
|
||||
import Fallback from "../../Components/Fallback";
|
||||
import ActionMenu from "./components/ActionMenu";
|
||||
import PageStateWrapper from "../../Components/PageStateWrapper";
|
||||
|
||||
// Utils
|
||||
import { useIsAdmin } from "../../Hooks/useIsAdmin";
|
||||
import { useState } from "react";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@@ -23,7 +22,6 @@ const Notifications = () => {
|
||||
const theme = useTheme();
|
||||
const BREADCRUMBS = [{ name: "notifications", path: "/notifications" }];
|
||||
const [updateTrigger, setUpdateTrigger] = useState(false);
|
||||
const isAdmin = useIsAdmin();
|
||||
const [notifications, isLoading, error] = useGetNotificationsByTeamId(updateTrigger);
|
||||
const [deleteNotification, isDeleting, deleteError] = useDeleteNotification();
|
||||
const { t } = useTranslation();
|
||||
@@ -76,49 +74,47 @@ const Notifications = () => {
|
||||
},
|
||||
];
|
||||
|
||||
if (notifications?.length === 0) {
|
||||
return (
|
||||
<Fallback
|
||||
type="notifications"
|
||||
title={t("notifications.fallback.title")}
|
||||
checks={t("notifications.fallback.checks", { returnObjects: true })}
|
||||
link="/notifications/create"
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="flex-end"
|
||||
<>
|
||||
<PageStateWrapper
|
||||
networkError={error}
|
||||
isLoading={isLoading}
|
||||
items={notifications}
|
||||
type="notifications"
|
||||
fallbackLink="/notifications/create"
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={() => navigate("/notifications/create")}
|
||||
>
|
||||
{t("notifications.createButton")}
|
||||
</Button>
|
||||
</Stack>
|
||||
<Typography variant="h1">{t("notifications.createTitle")}</Typography>
|
||||
<DataTable
|
||||
config={{
|
||||
onRowClick: (row) => navigate(`/notifications/${row._id}`),
|
||||
rowSX: {
|
||||
cursor: "pointer",
|
||||
"&:hover td": {
|
||||
backgroundColor: theme.palette.tertiary.main,
|
||||
transition: "background-color .3s ease",
|
||||
},
|
||||
},
|
||||
}}
|
||||
headers={headers}
|
||||
data={notifications}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={() => navigate("/notifications/create")}
|
||||
>
|
||||
{t("notifications.createButton")}
|
||||
</Button>
|
||||
</Stack>
|
||||
<Typography variant="h1">{t("notifications.createTitle")}</Typography>
|
||||
<DataTable
|
||||
config={{
|
||||
onRowClick: (row) => navigate(`/notifications/${row._id}`),
|
||||
rowSX: {
|
||||
cursor: "pointer",
|
||||
"&:hover td": {
|
||||
backgroundColor: theme.palette.tertiary.main,
|
||||
transition: "background-color .3s ease",
|
||||
},
|
||||
},
|
||||
}}
|
||||
headers={headers}
|
||||
data={notifications}
|
||||
/>
|
||||
</Stack>
|
||||
</PageStateWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
// Components
|
||||
import { useState } from "react";
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import { Stack } from "@mui/material";
|
||||
import CreateMonitorHeader from "../../../Components/MonitorCreateHeader";
|
||||
import MonitorCountHeader from "../../../Components/MonitorCountHeader";
|
||||
import MonitorGrid from "./Components/MonitorGrid";
|
||||
import Fallback from "../../../Components/Fallback";
|
||||
import GenericFallback from "../../../Components/GenericFallback";
|
||||
import PageStateWrapper from "../../../Components/PageStateWrapper";
|
||||
import FallbackPageSpeedWarning from "../../../Components/Fallback/FallbackPageSpeedWarning";
|
||||
|
||||
// Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFetchMonitorsByTeamId } from "../../../Hooks/monitorHooks";
|
||||
import { useFetchSettings } from "../../../Hooks/settingsHooks";
|
||||
// Constants
|
||||
@@ -20,7 +18,6 @@ const BREADCRUMBS = [{ name: `pagespeed`, path: "/pagespeed" }];
|
||||
const TYPES = ["pagespeed"];
|
||||
const PageSpeed = () => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const isAdmin = useIsAdmin();
|
||||
|
||||
const [monitors, monitorsSummary, isLoading, networkError] = useFetchMonitorsByTeamId({
|
||||
@@ -40,56 +37,42 @@ const PageSpeed = () => {
|
||||
setIsEmailPasswordSet: () => {},
|
||||
});
|
||||
|
||||
if (networkError === true) {
|
||||
return (
|
||||
<GenericFallback>
|
||||
<Typography
|
||||
variant="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{t("common.toasts.networkError")}
|
||||
</Typography>
|
||||
<Typography>{t("common.toasts.checkConnection")}</Typography>
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isLoading && monitors?.length === 0) {
|
||||
return (
|
||||
<Fallback
|
||||
type="pageSpeed"
|
||||
title={t("pageSpeed.fallback.title")}
|
||||
checks={t("pageSpeed.fallback.checks", { returnObjects: true })}
|
||||
link="/pagespeed/create"
|
||||
isAdmin={isAdmin}
|
||||
>
|
||||
{isAdmin && settingsData && !settingsData.pagespeedApiKey && (
|
||||
<FallbackPageSpeedWarning settingsData={settingsData} />
|
||||
)}
|
||||
</Fallback>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<CreateMonitorHeader
|
||||
isAdmin={isAdmin}
|
||||
<>
|
||||
<PageStateWrapper
|
||||
networkError={networkError}
|
||||
isLoading={isLoading}
|
||||
path="/pagespeed/create"
|
||||
/>
|
||||
<MonitorCountHeader
|
||||
isLoading={isLoading}
|
||||
monitorCount={monitorsSummary?.totalMonitors}
|
||||
sx={{ mb: theme.spacing(8) }}
|
||||
/>
|
||||
<MonitorGrid
|
||||
size={6}
|
||||
shouldRender={!isLoading}
|
||||
monitors={monitors}
|
||||
/>
|
||||
</Stack>
|
||||
items={monitors}
|
||||
type="pageSpeed"
|
||||
fallbackLink="/pagespeed/create"
|
||||
fallbackChildren={
|
||||
isAdmin &&
|
||||
settingsData &&
|
||||
!settingsData.pagespeedApiKey && (
|
||||
<FallbackPageSpeedWarning settingsData={settingsData} />
|
||||
)
|
||||
}
|
||||
>
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<CreateMonitorHeader
|
||||
isAdmin={isAdmin}
|
||||
isLoading={isLoading}
|
||||
path="/pagespeed/create"
|
||||
/>
|
||||
<MonitorCountHeader
|
||||
isLoading={isLoading}
|
||||
monitorCount={monitorsSummary?.totalMonitors}
|
||||
sx={{ mb: theme.spacing(8) }}
|
||||
/>
|
||||
<MonitorGrid
|
||||
size={6}
|
||||
shouldRender={!isLoading}
|
||||
monitors={monitors}
|
||||
/>
|
||||
</Stack>
|
||||
</PageStateWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
// Components
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import { Stack } from "@mui/material";
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
import Fallback from "../../../Components/Fallback";
|
||||
import MonitorCreateHeader from "../../../Components/MonitorCreateHeader";
|
||||
import GenericFallback from "../../../Components/GenericFallback";
|
||||
import StatusPagesTable from "./Components/StatusPagesTable";
|
||||
import SkeletonLayout from "../../../Components/Skeletons/FullPage";
|
||||
import PageStateWrapper from "../../../Components/PageStateWrapper";
|
||||
// Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useStatusPagesFetch } from "./Hooks/useStatusPagesFetch";
|
||||
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
|
||||
const BREADCRUMBS = [{ name: `Status Pages`, path: "" }];
|
||||
@@ -16,52 +13,30 @@ const BREADCRUMBS = [{ name: `Status Pages`, path: "" }];
|
||||
const StatusPages = () => {
|
||||
// Utils
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const isAdmin = useIsAdmin();
|
||||
const [isLoading, networkError, statusPages] = useStatusPagesFetch();
|
||||
|
||||
if (isLoading) {
|
||||
return <SkeletonLayout />;
|
||||
}
|
||||
|
||||
if (networkError === true) {
|
||||
return (
|
||||
<GenericFallback>
|
||||
<Typography
|
||||
variant="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{t("common.toasts.networkError")}
|
||||
</Typography>
|
||||
<Typography>{t("common.toasts.checkConnection")}</Typography>
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isLoading && typeof statusPages !== "undefined" && statusPages.length === 0) {
|
||||
return (
|
||||
<Fallback
|
||||
title={t("statusPage.fallback.title")}
|
||||
checks={t("statusPage.fallback.checks", { returnObjects: true })}
|
||||
type="statusPage"
|
||||
link="/status/uptime/create"
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<MonitorCreateHeader
|
||||
label="Create status page"
|
||||
isAdmin={isAdmin}
|
||||
path="/status/uptime/create"
|
||||
<>
|
||||
<PageStateWrapper
|
||||
networkError={networkError}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<StatusPagesTable data={statusPages} />
|
||||
</Stack>
|
||||
items={statusPages}
|
||||
type="statusPage"
|
||||
fallbackLink="/status/uptime/create"
|
||||
>
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<MonitorCreateHeader
|
||||
label="Create status page"
|
||||
isAdmin={isAdmin}
|
||||
path="/status/uptime/create"
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<StatusPagesTable data={statusPages} />
|
||||
</Stack>
|
||||
</PageStateWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -10,16 +10,14 @@ import StatusBoxes from "./Components/StatusBoxes";
|
||||
import UptimeDataTable from "./Components/UptimeDataTable";
|
||||
import Pagination from "../../../Components/Table/TablePagination";
|
||||
import CreateMonitorHeader from "../../../Components/MonitorCreateHeader";
|
||||
import Fallback from "../../../Components/Fallback";
|
||||
import GenericFallback from "../../../Components/GenericFallback";
|
||||
import SearchComponent from "./Components/SearchComponent";
|
||||
import Filter from "./Components/Filter";
|
||||
import PageStateWrapper from "../../../Components/PageStateWrapper";
|
||||
|
||||
import MonitorCountHeader from "../../../Components/MonitorCountHeader";
|
||||
|
||||
// MUI Components
|
||||
import { Stack, Box, Button, Typography } from "@mui/material";
|
||||
|
||||
import { Stack, Box, Button } from "@mui/material";
|
||||
// Utils
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
|
||||
@@ -153,93 +151,75 @@ const UptimeMonitors = () => {
|
||||
}, [isSearching]);
|
||||
|
||||
const isLoading = monitorsWithSummaryIsLoading || monitorsWithChecksIsLoading;
|
||||
if (networkError) {
|
||||
return (
|
||||
<GenericFallback>
|
||||
<Typography
|
||||
variant="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{t("common.toasts.networkError")}
|
||||
</Typography>
|
||||
<Typography>{t("common.toasts.checkConnection")}</Typography>
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
if (
|
||||
!isLoading &&
|
||||
(monitorsSummary?.totalMonitors === 0 ||
|
||||
typeof monitorsSummary?.totalMonitors === "undefined")
|
||||
) {
|
||||
return (
|
||||
<Fallback
|
||||
type="uptimeMonitor"
|
||||
title={t("uptimeMonitor.fallback.title")}
|
||||
checks={t("uptimeMonitor.fallback.checks", { returnObjects: true })}
|
||||
link="/uptime/create"
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Stack
|
||||
className="monitors"
|
||||
gap={theme.spacing(10)}
|
||||
>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<CreateMonitorHeader
|
||||
isAdmin={isAdmin}
|
||||
isLoading={isLoading}
|
||||
path="/uptime/create"
|
||||
bulkPath="/uptime/bulk-import"
|
||||
/>
|
||||
<Greeting type="uptime" />
|
||||
<StatusBoxes
|
||||
monitorsSummary={monitorsSummary}
|
||||
shouldRender={!monitorsWithSummaryIsLoading}
|
||||
/>
|
||||
|
||||
<Stack direction={"row"}>
|
||||
<MonitorCountHeader
|
||||
isLoading={monitorsWithSummaryIsLoading}
|
||||
monitorCount={monitorsSummary?.totalMonitors}
|
||||
/>
|
||||
<Filter
|
||||
selectedTypes={selectedTypes}
|
||||
setSelectedTypes={setSelectedTypes}
|
||||
selectedStatus={selectedStatus}
|
||||
setSelectedStatus={setSelectedStatus}
|
||||
selectedState={selectedState}
|
||||
setSelectedState={setSelectedState}
|
||||
setToFilterStatus={setToFilterStatus}
|
||||
setToFilterActive={setToFilterActive}
|
||||
handleReset={handleReset}
|
||||
/>
|
||||
<SearchComponent
|
||||
monitors={monitors}
|
||||
onSearchChange={setSearch}
|
||||
setIsSearching={setIsSearching}
|
||||
/>
|
||||
</Stack>
|
||||
<UptimeDataTable
|
||||
isAdmin={isAdmin}
|
||||
isSearching={isSearching}
|
||||
filteredMonitors={monitorsWithChecks}
|
||||
sort={sort}
|
||||
setSort={setSort}
|
||||
monitorsAreLoading={monitorsWithChecksIsLoading}
|
||||
triggerUpdate={triggerUpdate}
|
||||
/>
|
||||
<Pagination
|
||||
itemCount={monitorsWithChecksCount}
|
||||
paginationLabel="monitors"
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
handleChangePage={handleChangePage}
|
||||
handleChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
</Stack>
|
||||
return (
|
||||
<>
|
||||
<PageStateWrapper
|
||||
networkError={networkError}
|
||||
isLoading={isLoading}
|
||||
items={monitors}
|
||||
type="uptimeMonitor"
|
||||
fallbackLink="/uptime/create"
|
||||
>
|
||||
<Stack
|
||||
className="monitors"
|
||||
gap={theme.spacing(10)}
|
||||
>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<CreateMonitorHeader
|
||||
isAdmin={isAdmin}
|
||||
isLoading={isLoading}
|
||||
path="/uptime/create"
|
||||
bulkPath="/uptime/bulk-import"
|
||||
/>
|
||||
<Greeting type="uptime" />
|
||||
<StatusBoxes
|
||||
monitorsSummary={monitorsSummary}
|
||||
shouldRender={!monitorsWithSummaryIsLoading}
|
||||
/>
|
||||
|
||||
<Stack direction={"row"}>
|
||||
<MonitorCountHeader
|
||||
isLoading={monitorsWithSummaryIsLoading}
|
||||
monitorCount={monitorsSummary?.totalMonitors}
|
||||
/>
|
||||
<Filter
|
||||
selectedTypes={selectedTypes}
|
||||
setSelectedTypes={setSelectedTypes}
|
||||
selectedStatus={selectedStatus}
|
||||
setSelectedStatus={setSelectedStatus}
|
||||
selectedState={selectedState}
|
||||
setSelectedState={setSelectedState}
|
||||
setToFilterStatus={setToFilterStatus}
|
||||
setToFilterActive={setToFilterActive}
|
||||
handleReset={handleReset}
|
||||
/>
|
||||
<SearchComponent
|
||||
monitors={monitors}
|
||||
onSearchChange={setSearch}
|
||||
setIsSearching={setIsSearching}
|
||||
/>
|
||||
</Stack>
|
||||
<UptimeDataTable
|
||||
isAdmin={isAdmin}
|
||||
isSearching={isSearching}
|
||||
filteredMonitors={monitorsWithChecks}
|
||||
sort={sort}
|
||||
setSort={setSort}
|
||||
monitorsAreLoading={monitorsWithChecksIsLoading}
|
||||
triggerUpdate={triggerUpdate}
|
||||
/>
|
||||
<Pagination
|
||||
itemCount={monitorsWithChecksCount}
|
||||
paginationLabel="monitors"
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
handleChangePage={handleChangePage}
|
||||
handleChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
</Stack>
|
||||
</PageStateWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -437,6 +437,16 @@ const infrastructureMonitorValidation = joi.object({
|
||||
"number.base": "Frequency must be a number.",
|
||||
"any.required": "Frequency is required.",
|
||||
}),
|
||||
statusWindowSize: joi.number().min(1).max(20).messages({
|
||||
"number.base": "Status window size must be a number.",
|
||||
"number.min": "Status window size must be at least 1.",
|
||||
"number.max": "Status window size cannot exceed 20.",
|
||||
}),
|
||||
statusWindowThreshold: joi.number().min(1).max(100).messages({
|
||||
"number.base": "Status window threshold must be a number.",
|
||||
"number.min": "Status window threshold must be at least 1%.",
|
||||
"number.max": "Status window threshold cannot exceed 100%.",
|
||||
}),
|
||||
notifications: joi.array().items(joi.string()),
|
||||
});
|
||||
|
||||
|
||||
@@ -376,7 +376,52 @@
|
||||
"email": "",
|
||||
"role": "",
|
||||
"created": ""
|
||||
}
|
||||
},
|
||||
"addTeamMember": {
|
||||
"addMemberMenu": "",
|
||||
"title": "",
|
||||
"description": "",
|
||||
"addButton": ""
|
||||
},
|
||||
"register": "",
|
||||
"registerToast": {
|
||||
"success": "",
|
||||
"dbUserExists": "",
|
||||
"unknownError": ""
|
||||
},
|
||||
"registerTeamMember": {
|
||||
"title": "",
|
||||
"auth": {
|
||||
"common": {
|
||||
"inputs": {
|
||||
"firstName": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
"lastName": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
"email": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"invalid": ""
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"errors": {
|
||||
"empty": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"role": ""
|
||||
},
|
||||
"monitorState": {
|
||||
"paused": "",
|
||||
@@ -742,7 +787,8 @@
|
||||
"failCountHeader": "",
|
||||
"lastRunHeader": "",
|
||||
"lastFinishedAtHeader": "",
|
||||
"lastRunTookHeader": ""
|
||||
"lastRunTookHeader": "",
|
||||
"intervalHeader": ""
|
||||
},
|
||||
"metricsTable": {
|
||||
"title": "",
|
||||
|
||||
@@ -376,7 +376,52 @@
|
||||
"email": "",
|
||||
"role": "",
|
||||
"created": ""
|
||||
}
|
||||
},
|
||||
"addTeamMember": {
|
||||
"addMemberMenu": "",
|
||||
"title": "",
|
||||
"description": "",
|
||||
"addButton": ""
|
||||
},
|
||||
"register": "",
|
||||
"registerToast": {
|
||||
"success": "",
|
||||
"dbUserExists": "",
|
||||
"unknownError": ""
|
||||
},
|
||||
"registerTeamMember": {
|
||||
"title": "",
|
||||
"auth": {
|
||||
"common": {
|
||||
"inputs": {
|
||||
"firstName": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
"lastName": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
"email": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"invalid": ""
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"errors": {
|
||||
"empty": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"role": ""
|
||||
},
|
||||
"monitorState": {
|
||||
"paused": "",
|
||||
@@ -742,7 +787,8 @@
|
||||
"failCountHeader": "",
|
||||
"lastRunHeader": "",
|
||||
"lastFinishedAtHeader": "",
|
||||
"lastRunTookHeader": ""
|
||||
"lastRunTookHeader": "",
|
||||
"intervalHeader": ""
|
||||
},
|
||||
"metricsTable": {
|
||||
"title": "",
|
||||
|
||||
@@ -380,7 +380,52 @@
|
||||
"email": "Email",
|
||||
"role": "Rolle",
|
||||
"created": "Angelegt"
|
||||
}
|
||||
},
|
||||
"addTeamMember": {
|
||||
"addMemberMenu": "",
|
||||
"title": "",
|
||||
"description": "",
|
||||
"addButton": ""
|
||||
},
|
||||
"register": "",
|
||||
"registerToast": {
|
||||
"success": "",
|
||||
"dbUserExists": "",
|
||||
"unknownError": ""
|
||||
},
|
||||
"registerTeamMember": {
|
||||
"title": "",
|
||||
"auth": {
|
||||
"common": {
|
||||
"inputs": {
|
||||
"firstName": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
"lastName": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
"email": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"invalid": ""
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"errors": {
|
||||
"empty": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"role": ""
|
||||
},
|
||||
"monitorState": {
|
||||
"paused": "Pausiert",
|
||||
@@ -746,7 +791,8 @@
|
||||
"failCountHeader": "Anzahl Fehler",
|
||||
"lastRunHeader": "Letzter Lauf um",
|
||||
"lastFinishedAtHeader": "Zuletzt abgeschlossen um",
|
||||
"lastRunTookHeader": "Letzter Lauf dauerte"
|
||||
"lastRunTookHeader": "Letzter Lauf dauerte",
|
||||
"intervalHeader": ""
|
||||
},
|
||||
"metricsTable": {
|
||||
"title": "Warteschlangenmetriken",
|
||||
|
||||
+15
-15
@@ -360,12 +360,6 @@
|
||||
"demoUser": "Demo user"
|
||||
},
|
||||
"teamPanel": {
|
||||
"addTeamMember": {
|
||||
"addMemberMenu": "Add Team Member",
|
||||
"title": "Register new team member",
|
||||
"description": "Create a new user and share the credentials with them. This method gives the member immediate access to all monitors.",
|
||||
"addButton": "Add Member"
|
||||
},
|
||||
"teamMembers": "Team members",
|
||||
"filter": {
|
||||
"all": "All",
|
||||
@@ -381,6 +375,18 @@
|
||||
"noMembers": "There are no team members with this role",
|
||||
"getToken": "Get token",
|
||||
"emailToken": "E-mail token",
|
||||
"table": {
|
||||
"name": "Name",
|
||||
"email": "Email",
|
||||
"role": "Role",
|
||||
"created": "Created"
|
||||
},
|
||||
"addTeamMember": {
|
||||
"addMemberMenu": "Add Team Member",
|
||||
"title": "Register new team member",
|
||||
"description": "Create a new user and share the credentials with them. This method gives the member immediate access to all monitors.",
|
||||
"addButton": "Add Member"
|
||||
},
|
||||
"register": "Register a team member",
|
||||
"registerToast": {
|
||||
"success": "User created, share credentials with the member securely.",
|
||||
@@ -419,13 +425,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"role": "Role",
|
||||
"table": {
|
||||
"name": "Name",
|
||||
"email": "Email",
|
||||
"role": "Role",
|
||||
"created": "Created"
|
||||
}
|
||||
"role": "Role"
|
||||
},
|
||||
"monitorState": {
|
||||
"paused": "Paused",
|
||||
@@ -785,14 +785,14 @@
|
||||
"idHeader": "Monitor ID",
|
||||
"urlHeader": "URL",
|
||||
"typeHeader": "Type",
|
||||
"intervalHeader": "Interval",
|
||||
"activeHeader": "Active",
|
||||
"lockedAtHeader": "Locked at",
|
||||
"runCountHeader": "Run count",
|
||||
"failCountHeader": "Fail count",
|
||||
"lastRunHeader": "Last run at",
|
||||
"lastFinishedAtHeader": "Last finished at",
|
||||
"lastRunTookHeader": "Last run took"
|
||||
"lastRunTookHeader": "Last run took",
|
||||
"intervalHeader": "Interval"
|
||||
},
|
||||
"metricsTable": {
|
||||
"title": "Queue metrics",
|
||||
|
||||
@@ -376,7 +376,52 @@
|
||||
"email": "",
|
||||
"role": "",
|
||||
"created": ""
|
||||
}
|
||||
},
|
||||
"addTeamMember": {
|
||||
"addMemberMenu": "",
|
||||
"title": "",
|
||||
"description": "",
|
||||
"addButton": ""
|
||||
},
|
||||
"register": "",
|
||||
"registerToast": {
|
||||
"success": "",
|
||||
"dbUserExists": "",
|
||||
"unknownError": ""
|
||||
},
|
||||
"registerTeamMember": {
|
||||
"title": "",
|
||||
"auth": {
|
||||
"common": {
|
||||
"inputs": {
|
||||
"firstName": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
"lastName": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
"email": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"invalid": ""
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"errors": {
|
||||
"empty": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"role": ""
|
||||
},
|
||||
"monitorState": {
|
||||
"paused": "",
|
||||
@@ -742,7 +787,8 @@
|
||||
"failCountHeader": "",
|
||||
"lastRunHeader": "",
|
||||
"lastFinishedAtHeader": "",
|
||||
"lastRunTookHeader": ""
|
||||
"lastRunTookHeader": "",
|
||||
"intervalHeader": ""
|
||||
},
|
||||
"metricsTable": {
|
||||
"title": "",
|
||||
|
||||
@@ -376,7 +376,52 @@
|
||||
"email": "Sähköposti",
|
||||
"role": "Rooli",
|
||||
"created": "Luotu"
|
||||
}
|
||||
},
|
||||
"addTeamMember": {
|
||||
"addMemberMenu": "",
|
||||
"title": "",
|
||||
"description": "",
|
||||
"addButton": ""
|
||||
},
|
||||
"register": "",
|
||||
"registerToast": {
|
||||
"success": "",
|
||||
"dbUserExists": "",
|
||||
"unknownError": ""
|
||||
},
|
||||
"registerTeamMember": {
|
||||
"title": "",
|
||||
"auth": {
|
||||
"common": {
|
||||
"inputs": {
|
||||
"firstName": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
"lastName": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
"email": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"invalid": ""
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"errors": {
|
||||
"empty": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"role": ""
|
||||
},
|
||||
"monitorState": {
|
||||
"paused": "Tauota",
|
||||
@@ -742,7 +787,8 @@
|
||||
"failCountHeader": "",
|
||||
"lastRunHeader": "",
|
||||
"lastFinishedAtHeader": "",
|
||||
"lastRunTookHeader": ""
|
||||
"lastRunTookHeader": "",
|
||||
"intervalHeader": ""
|
||||
},
|
||||
"metricsTable": {
|
||||
"title": "",
|
||||
|
||||
@@ -380,7 +380,52 @@
|
||||
"email": "Email",
|
||||
"role": "Rôle",
|
||||
"created": "Créée"
|
||||
}
|
||||
},
|
||||
"addTeamMember": {
|
||||
"addMemberMenu": "",
|
||||
"title": "",
|
||||
"description": "",
|
||||
"addButton": ""
|
||||
},
|
||||
"register": "",
|
||||
"registerToast": {
|
||||
"success": "",
|
||||
"dbUserExists": "",
|
||||
"unknownError": ""
|
||||
},
|
||||
"registerTeamMember": {
|
||||
"title": "",
|
||||
"auth": {
|
||||
"common": {
|
||||
"inputs": {
|
||||
"firstName": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
"lastName": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
"email": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"invalid": ""
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"errors": {
|
||||
"empty": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"role": ""
|
||||
},
|
||||
"monitorState": {
|
||||
"paused": "Pause",
|
||||
@@ -746,7 +791,8 @@
|
||||
"failCountHeader": "Nombre d'échecs",
|
||||
"lastRunHeader": "Dernière tentative le",
|
||||
"lastFinishedAtHeader": "Dernière finalisation à",
|
||||
"lastRunTookHeader": "Dernière exécution effectuée"
|
||||
"lastRunTookHeader": "Dernière exécution effectuée",
|
||||
"intervalHeader": ""
|
||||
},
|
||||
"metricsTable": {
|
||||
"title": "Métriques des files",
|
||||
|
||||
@@ -380,7 +380,52 @@
|
||||
"email": "メール",
|
||||
"role": "役割",
|
||||
"created": "作成日"
|
||||
}
|
||||
},
|
||||
"addTeamMember": {
|
||||
"addMemberMenu": "",
|
||||
"title": "",
|
||||
"description": "",
|
||||
"addButton": ""
|
||||
},
|
||||
"register": "",
|
||||
"registerToast": {
|
||||
"success": "",
|
||||
"dbUserExists": "",
|
||||
"unknownError": ""
|
||||
},
|
||||
"registerTeamMember": {
|
||||
"title": "",
|
||||
"auth": {
|
||||
"common": {
|
||||
"inputs": {
|
||||
"firstName": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
"lastName": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
"email": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"invalid": ""
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"errors": {
|
||||
"empty": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"role": ""
|
||||
},
|
||||
"monitorState": {
|
||||
"paused": "一時停止",
|
||||
@@ -746,7 +791,8 @@
|
||||
"failCountHeader": "失敗回数",
|
||||
"lastRunHeader": "最終実行日時",
|
||||
"lastFinishedAtHeader": "最終完了日時",
|
||||
"lastRunTookHeader": "最終実行時間"
|
||||
"lastRunTookHeader": "最終実行時間",
|
||||
"intervalHeader": ""
|
||||
},
|
||||
"metricsTable": {
|
||||
"title": "キューメトリック",
|
||||
|
||||
@@ -380,7 +380,52 @@
|
||||
"email": "E-mail",
|
||||
"role": "Cargo",
|
||||
"created": "Criado"
|
||||
}
|
||||
},
|
||||
"addTeamMember": {
|
||||
"addMemberMenu": "",
|
||||
"title": "",
|
||||
"description": "",
|
||||
"addButton": ""
|
||||
},
|
||||
"register": "",
|
||||
"registerToast": {
|
||||
"success": "",
|
||||
"dbUserExists": "",
|
||||
"unknownError": ""
|
||||
},
|
||||
"registerTeamMember": {
|
||||
"title": "",
|
||||
"auth": {
|
||||
"common": {
|
||||
"inputs": {
|
||||
"firstName": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
"lastName": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
"email": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"invalid": ""
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"errors": {
|
||||
"empty": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"role": ""
|
||||
},
|
||||
"monitorState": {
|
||||
"paused": "Pausado",
|
||||
@@ -746,7 +791,8 @@
|
||||
"failCountHeader": "Contagem de falhas",
|
||||
"lastRunHeader": "Última corrida em",
|
||||
"lastFinishedAtHeader": "Última conclusão em",
|
||||
"lastRunTookHeader": "A última corrida aconteceu"
|
||||
"lastRunTookHeader": "A última corrida aconteceu",
|
||||
"intervalHeader": ""
|
||||
},
|
||||
"metricsTable": {
|
||||
"title": "Métricas de fila",
|
||||
|
||||
+111
-65
@@ -376,7 +376,52 @@
|
||||
"email": "Email",
|
||||
"role": "Роль",
|
||||
"created": "Создано"
|
||||
}
|
||||
},
|
||||
"addTeamMember": {
|
||||
"addMemberMenu": "",
|
||||
"title": "",
|
||||
"description": "",
|
||||
"addButton": ""
|
||||
},
|
||||
"register": "",
|
||||
"registerToast": {
|
||||
"success": "",
|
||||
"dbUserExists": "",
|
||||
"unknownError": ""
|
||||
},
|
||||
"registerTeamMember": {
|
||||
"title": "",
|
||||
"auth": {
|
||||
"common": {
|
||||
"inputs": {
|
||||
"firstName": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
"lastName": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
"email": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"invalid": ""
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"errors": {
|
||||
"empty": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"role": ""
|
||||
},
|
||||
"monitorState": {
|
||||
"paused": "Остановлено",
|
||||
@@ -421,23 +466,23 @@
|
||||
"passwordRequirements": "Новый пароль должен содержать не менее 8 символов и содержать как минимум одну заглавную букву, одну строчную букву, одну цифру и один специальный символ.",
|
||||
"saving": "Сохранение..."
|
||||
},
|
||||
"emailSent": "",
|
||||
"failedToSendEmail": "",
|
||||
"settingsTestEmailSuccess": "",
|
||||
"settingsTestEmailFailed": "",
|
||||
"settingsTestEmailFailedWithReason": "",
|
||||
"emailSent": "Письмо успешно отправлено",
|
||||
"failedToSendEmail": "Не удалось отправить письмо",
|
||||
"settingsTestEmailSuccess": "Тестовое письмо успешно отправлено",
|
||||
"settingsTestEmailFailed": "Не удалось отправить тестовое письмо",
|
||||
"settingsTestEmailFailedWithReason": "Не удалось отправить тестовое письмо: {{reason}}",
|
||||
"settingsTestEmailUnknownError": "Неизвестная ошибка",
|
||||
"statusMsg": {
|
||||
"paused": "",
|
||||
"paused": "Мониторинг приостановлен",
|
||||
"up": "",
|
||||
"down": "",
|
||||
"pending": ""
|
||||
"pending": "Ожидание..."
|
||||
},
|
||||
"uptimeGeneralInstructions": {
|
||||
"http": "",
|
||||
"ping": "",
|
||||
"docker": "",
|
||||
"port": "",
|
||||
"http": "Введите URL-адрес или IP-адрес для мониторинга (например, https://example.com/ или 192.168.1.100) и добавьте понятное имя, которое будет отображаться на панели инструментов.",
|
||||
"ping": "Ведите IP-адрес или имя хоста для пинга (например, 192.168.1.100 или example.com) и добавьте понятное имя, которое будет отображаться на панели инструментов.",
|
||||
"docker": "Введите Docker ID вашего контейнера. Docker ID должен быть полным 64-символьным Docker ID. Вы можете запустить docker inspect <short_id>, чтобы получить полный ID контейнера.",
|
||||
"port": "Введите URL-адрес или IP-адрес сервера, номер порта и понятное имя, которое будет отображаться на панели инструментов.",
|
||||
"game": ""
|
||||
},
|
||||
"common": {
|
||||
@@ -447,24 +492,24 @@
|
||||
"toggleTheme": ""
|
||||
},
|
||||
"toasts": {
|
||||
"networkError": "",
|
||||
"checkConnection": "",
|
||||
"networkError": "Ошибка сети",
|
||||
"checkConnection": "Пожалуйста, проверьте свое сетевое соединение",
|
||||
"unknownError": "Неизвестная ошибка"
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
"common": {
|
||||
"navigation": {
|
||||
"continue": "",
|
||||
"continue": "Продолжить",
|
||||
"back": "Назад"
|
||||
},
|
||||
"inputs": {
|
||||
"email": {
|
||||
"label": "",
|
||||
"label": "Электронная почта",
|
||||
"placeholder": "",
|
||||
"errors": {
|
||||
"empty": "Для продолжения введите свой адрес электронной почты",
|
||||
"invalid": ""
|
||||
"invalid": "Пожалуйста, проверьте правильность введённого адреса электронной почты"
|
||||
}
|
||||
},
|
||||
"password": {
|
||||
@@ -491,49 +536,49 @@
|
||||
"highlighted": "одну прописную букву"
|
||||
},
|
||||
"match": {
|
||||
"beginning": "",
|
||||
"highlighted": ""
|
||||
"beginning": "Пароли",
|
||||
"highlighted": "должны совпадать"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"length": "",
|
||||
"uppercase": "",
|
||||
"lowercase": "",
|
||||
"number": "",
|
||||
"special": "",
|
||||
"incorrect": ""
|
||||
"empty": "Пожалуйста, введите пароль",
|
||||
"length": "Пароль должен быть длиной не менее 8 символов",
|
||||
"uppercase": "Пароль должен содержать минимум 1 прописную букву",
|
||||
"lowercase": "Пароль должен содержать минимум 1 строчную букву",
|
||||
"number": "Пароль должен содержать минимум 1 цифру",
|
||||
"special": "Пароль должен содержать минимум 1 спецсимвол",
|
||||
"incorrect": "Предоставленный вами пароль не соответствует нашим записям"
|
||||
}
|
||||
},
|
||||
"passwordConfirm": {
|
||||
"label": "",
|
||||
"placeholder": "",
|
||||
"label": "Подтвердите пароль",
|
||||
"placeholder": "Введите пароль еще раз для подтверждения",
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"different": ""
|
||||
"empty": "Пожалуйста, введите свой пароль еще раз для подтверждения (помогает избежать опечаток)",
|
||||
"different": "Введённые пароли не совпадают, вероятно, один из них напечатан с ошибкой"
|
||||
}
|
||||
},
|
||||
"firstName": {
|
||||
"label": "",
|
||||
"label": "Имя",
|
||||
"placeholder": "",
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"length": "",
|
||||
"pattern": ""
|
||||
"empty": "Пожалуйста, введите свое имя",
|
||||
"length": "Имя должно быть короче 50 символов",
|
||||
"pattern": "Имя должно содержать только буквы, пробелы, апострофы или дефисы"
|
||||
}
|
||||
},
|
||||
"lastName": {
|
||||
"label": "",
|
||||
"label": "Фамилия",
|
||||
"placeholder": "",
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"length": "",
|
||||
"pattern": ""
|
||||
"empty": "Пожалуйста, введите свою фамилию",
|
||||
"length": "Фамилия должна быть короче 50 символов",
|
||||
"pattern": "Фамилия должна содержать только буквы, пробелы, апострофы или дефисы"
|
||||
}
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"validation": ""
|
||||
"validation": "Ошибка при проверке данных."
|
||||
},
|
||||
"fields": {
|
||||
"password": {
|
||||
@@ -549,20 +594,20 @@
|
||||
}
|
||||
},
|
||||
"login": {
|
||||
"heading": "",
|
||||
"heading": "Войдите, чтобы продолжить",
|
||||
"subheadings": {
|
||||
"stepOne": "",
|
||||
"stepTwo": ""
|
||||
"stepOne": "Введите свою электронную почту",
|
||||
"stepTwo": "Введите свой пароль"
|
||||
},
|
||||
"links": {
|
||||
"forgotPassword": "",
|
||||
"register": "",
|
||||
"forgotPassword": "Забыли пароль?",
|
||||
"register": "У вас нет учетной записи?",
|
||||
"forgotPasswordLink": "",
|
||||
"registerLink": ""
|
||||
},
|
||||
"toasts": {
|
||||
"success": "",
|
||||
"incorrectPassword": ""
|
||||
"success": "С возвращением! Вы успешно вошли в систему.",
|
||||
"incorrectPassword": "Неверный пароль"
|
||||
},
|
||||
"errors": {
|
||||
"password": {
|
||||
@@ -573,42 +618,42 @@
|
||||
},
|
||||
"registration": {
|
||||
"heading": {
|
||||
"superAdmin": "",
|
||||
"user": ""
|
||||
"superAdmin": "Создать супер-администратора",
|
||||
"user": "Зарегистрироваться"
|
||||
},
|
||||
"subheadings": {
|
||||
"stepOne": "",
|
||||
"stepTwo": "",
|
||||
"stepThree": ""
|
||||
"stepOne": "Введите свои личные данные",
|
||||
"stepTwo": "Введите вашу электронную почту",
|
||||
"stepThree": "Создайте свой пароль"
|
||||
},
|
||||
"description": {
|
||||
"superAdmin": "",
|
||||
"user": ""
|
||||
"superAdmin": "Создайте свой аккаунт супер-администратора, чтобы начать",
|
||||
"user": "Зарегистрируйтесь как пользователь и обратитесь к супер-администратору для доступа к вашим мониторам"
|
||||
},
|
||||
"gettingStartedButton": {
|
||||
"superAdmin": "",
|
||||
"user": ""
|
||||
"superAdmin": "Создать учетную запись супер-администратора",
|
||||
"user": "Зарегистрироваться как обычный пользователь"
|
||||
},
|
||||
"termsAndPolicies": "",
|
||||
"termsAndPolicies": "Создавая учетную запись, вы соглашаетесь с нашими <a1>Условиями предоставления услуг</a1> и <a2>Политикой конфиденциальности</a2>.",
|
||||
"links": {
|
||||
"login": ""
|
||||
"login": "Уже есть учетная запись? <a>Войти</a>"
|
||||
},
|
||||
"toasts": {
|
||||
"success": ""
|
||||
"success": "Добро пожаловать! Ваша учетная запись была создана успешно."
|
||||
},
|
||||
"welcome": ""
|
||||
},
|
||||
"forgotPassword": {
|
||||
"heading": "",
|
||||
"heading": "Забыли пароль?",
|
||||
"subheadings": {
|
||||
"stepOne": "",
|
||||
"stepTwo": "",
|
||||
"stepThree": "",
|
||||
"stepFour": ""
|
||||
"stepOne": "Не волнуйтесь, мы вышлем вам инструкции для сброса.",
|
||||
"stepTwo": "Мы выслали ссылку для сброса пароля на <email/>",
|
||||
"stepThree": "Ваш новый пароль должен отличаться от ваших предыдущих пароей.",
|
||||
"stepFour": "Ваш пароль был успешно сброшен. Нажмите ниже, чтобы волшебным образом войти в систему."
|
||||
},
|
||||
"buttons": {
|
||||
"openEmail": "",
|
||||
"resetPassword": ""
|
||||
"resetPassword": "Сбросить пароль"
|
||||
},
|
||||
"imageAlts": {
|
||||
"passwordKey": "",
|
||||
@@ -742,7 +787,8 @@
|
||||
"failCountHeader": "",
|
||||
"lastRunHeader": "",
|
||||
"lastFinishedAtHeader": "",
|
||||
"lastRunTookHeader": ""
|
||||
"lastRunTookHeader": "",
|
||||
"intervalHeader": ""
|
||||
},
|
||||
"metricsTable": {
|
||||
"title": "",
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -376,7 +376,52 @@
|
||||
"email": "Eposta",
|
||||
"role": "Rol",
|
||||
"created": "Oluşturulma"
|
||||
}
|
||||
},
|
||||
"addTeamMember": {
|
||||
"addMemberMenu": "",
|
||||
"title": "",
|
||||
"description": "",
|
||||
"addButton": ""
|
||||
},
|
||||
"register": "",
|
||||
"registerToast": {
|
||||
"success": "",
|
||||
"dbUserExists": "",
|
||||
"unknownError": ""
|
||||
},
|
||||
"registerTeamMember": {
|
||||
"title": "",
|
||||
"auth": {
|
||||
"common": {
|
||||
"inputs": {
|
||||
"firstName": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
"lastName": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
"email": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"invalid": ""
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"errors": {
|
||||
"empty": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"role": ""
|
||||
},
|
||||
"monitorState": {
|
||||
"paused": "Durduruldu",
|
||||
@@ -742,7 +787,8 @@
|
||||
"failCountHeader": "",
|
||||
"lastRunHeader": "",
|
||||
"lastFinishedAtHeader": "Son tamamlama",
|
||||
"lastRunTookHeader": ""
|
||||
"lastRunTookHeader": "",
|
||||
"intervalHeader": ""
|
||||
},
|
||||
"metricsTable": {
|
||||
"title": "",
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -380,7 +380,52 @@
|
||||
"email": "",
|
||||
"role": "",
|
||||
"created": ""
|
||||
}
|
||||
},
|
||||
"addTeamMember": {
|
||||
"addMemberMenu": "",
|
||||
"title": "",
|
||||
"description": "",
|
||||
"addButton": ""
|
||||
},
|
||||
"register": "",
|
||||
"registerToast": {
|
||||
"success": "",
|
||||
"dbUserExists": "",
|
||||
"unknownError": ""
|
||||
},
|
||||
"registerTeamMember": {
|
||||
"title": "",
|
||||
"auth": {
|
||||
"common": {
|
||||
"inputs": {
|
||||
"firstName": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
"lastName": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
"email": {
|
||||
"errors": {
|
||||
"empty": "",
|
||||
"invalid": ""
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"errors": {
|
||||
"empty": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"role": ""
|
||||
},
|
||||
"monitorState": {
|
||||
"paused": "",
|
||||
@@ -746,7 +791,8 @@
|
||||
"failCountHeader": "",
|
||||
"lastRunHeader": "",
|
||||
"lastFinishedAtHeader": "",
|
||||
"lastRunTookHeader": ""
|
||||
"lastRunTookHeader": "",
|
||||
"intervalHeader": ""
|
||||
},
|
||||
"metricsTable": {
|
||||
"title": "",
|
||||
|
||||
@@ -5,7 +5,7 @@ import { execSync } from "child_process";
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), "");
|
||||
let version = "3.1.7";
|
||||
let version = "3.2.0";
|
||||
|
||||
return {
|
||||
base: "/",
|
||||
|
||||
@@ -3,10 +3,7 @@
|
||||
# ---------------------
|
||||
FROM node:24-slim AS frontend-build
|
||||
|
||||
# Install ping
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y iputils-ping \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
WORKDIR /app/client
|
||||
|
||||
@@ -25,6 +22,11 @@ RUN npm run build
|
||||
# ---------------------
|
||||
FROM node:24-slim AS backend
|
||||
|
||||
# Install ping
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y iputils-ping \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app/server
|
||||
|
||||
COPY server/package.json ./
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
services:
|
||||
server:
|
||||
image: ghcr.io/bluewave-labs/checkmate-backend-mono:latest
|
||||
pull_policy: always
|
||||
restart: always
|
||||
ports:
|
||||
- "52345:52345"
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
FROM node:20-slim AS frontend-build
|
||||
|
||||
# Install ping
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y iputils-ping \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app/client
|
||||
|
||||
COPY client/package*.json ./
|
||||
@@ -15,6 +10,11 @@ RUN npm run build
|
||||
|
||||
FROM node:20-slim AS app
|
||||
|
||||
# Install ping
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y iputils-ping \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app/server
|
||||
|
||||
COPY server ./
|
||||
|
||||
@@ -149,10 +149,12 @@
|
||||
"statusPageByTeamId": "Got status pages by team id successfully",
|
||||
"httpNetworkError": "Network error",
|
||||
"httpNotJson": "Response data is not json",
|
||||
"httpExpectedValueFail": "Expected value did not match",
|
||||
"httpJsonPathError": "Failed to parse json data",
|
||||
"httpEmptyResult": "Result is empty",
|
||||
"httpMatchSuccess": "Response data match successfully",
|
||||
"httpMatchFail": "Failed to match response data",
|
||||
"httpJsonPathFail": "JSONPath did not match expected value",
|
||||
"webhookSendSuccess": "Webhook notification sent successfully",
|
||||
"telegramRequiresBotTokenAndChatId": "Telegram notifications require both botToken and chatId",
|
||||
"webhookUrlRequired": "Webhook URL is required",
|
||||
@@ -162,5 +164,18 @@
|
||||
"monitorDownAlert": "Downtime Alert: One of your monitors went offline.\n📌 Monitor: {monitorName}\n📅 Time: {time}\n⚠️ Status: DOWN\n📟 Status Code: {code}\n\u200B\n",
|
||||
"sendTestEmail": "Test email sent successfully",
|
||||
"errorForValidEmailAddress": "A valid recipient email address is required.",
|
||||
"testEmailSubject": "Test E-mail from Checkmate"
|
||||
"testEmailSubject": "Test E-mail from Checkmate",
|
||||
"discordNotification": {
|
||||
"monitor": "Monitor",
|
||||
"status": "Status",
|
||||
"statusCode": "Status Code",
|
||||
"time": "Time",
|
||||
"up": "UP",
|
||||
"down": "DOWN",
|
||||
"checkmate": "Checkmate",
|
||||
"url": "URL",
|
||||
"unknown": "Unknown",
|
||||
"uptimeAlert": "Uptime Alert: One of your monitors is back online",
|
||||
"downtimeAlert": "Downtime Alert: One of your monitors went offline"
|
||||
}
|
||||
}
|
||||
@@ -28,11 +28,12 @@ class SuperSimpleQueueHelper {
|
||||
return async (monitor) => {
|
||||
try {
|
||||
const monitorId = monitor._id;
|
||||
const teamId = monitor.teamId;
|
||||
if (!monitorId) {
|
||||
throw new Error("No monitor id");
|
||||
}
|
||||
|
||||
const maintenanceWindowActive = await this.isInMaintenanceWindow(monitorId);
|
||||
const maintenanceWindowActive = await this.isInMaintenanceWindow(monitorId, teamId);
|
||||
if (maintenanceWindowActive) {
|
||||
this.logger.info({
|
||||
message: `Monitor ${monitorId} is in maintenance window`,
|
||||
@@ -77,8 +78,11 @@ class SuperSimpleQueueHelper {
|
||||
};
|
||||
};
|
||||
|
||||
async isInMaintenanceWindow(monitorId) {
|
||||
const maintenanceWindows = await this.db.maintenanceWindowModule.getMaintenanceWindowsByMonitorId(monitorId);
|
||||
async isInMaintenanceWindow(monitorId, teamId) {
|
||||
const maintenanceWindows = await this.db.maintenanceWindowModule.getMaintenanceWindowsByMonitorId({
|
||||
monitorId: monitorId.toString(),
|
||||
teamId: teamId.toString(),
|
||||
});
|
||||
// Check for active maintenance window:
|
||||
const maintenanceWindowIsActive = maintenanceWindows.reduce((acc, window) => {
|
||||
if (window.active) {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { match } from "assert";
|
||||
|
||||
const SERVICE_NAME = "NetworkService";
|
||||
|
||||
class NetworkService {
|
||||
@@ -107,7 +105,7 @@ class NetworkService {
|
||||
}
|
||||
|
||||
async requestHttp(monitor) {
|
||||
const { url, secret, _id, name, teamId, type, ignoreTlsErrors, jsonPath, matchMethod, expectedValue } = monitor;
|
||||
const { url, secret, _id, teamId, type, ignoreTlsErrors, jsonPath, matchMethod, expectedValue } = monitor;
|
||||
const httpResponse = {
|
||||
monitorId: _id,
|
||||
teamId: teamId,
|
||||
@@ -156,33 +154,61 @@ class NetworkService {
|
||||
return httpResponse;
|
||||
}
|
||||
|
||||
if (expectedValue) {
|
||||
if (expectedValue && !jsonPath) {
|
||||
let ok = false;
|
||||
if (matchMethod === "equal") ok = payload === expectedValue;
|
||||
if (matchMethod === "include") ok = payload.includes(expectedValue);
|
||||
if (matchMethod === "regex") ok = new RegExp(expectedValue).test(payload);
|
||||
if (matchMethod === "include" && typeof payload === "string") ok = payload.includes(expectedValue);
|
||||
if (matchMethod === "regex" && typeof payload === "string") ok = new RegExp(expectedValue).test(payload);
|
||||
|
||||
if (ok === true) {
|
||||
return httpResponse;
|
||||
} else {
|
||||
httpResponse.code = 500;
|
||||
httpResponse.status = false;
|
||||
httpResponse.message = this.stringService.httpExpectedValueFail;
|
||||
return httpResponse;
|
||||
}
|
||||
}
|
||||
|
||||
if (jsonPath) {
|
||||
const contentType = response.headers["content-type"];
|
||||
|
||||
const isJson = contentType?.includes("application/json");
|
||||
if (!isJson) {
|
||||
httpResponse.status = false;
|
||||
httpResponse.message = this.stringService.httpNotJson;
|
||||
return httpResponse;
|
||||
}
|
||||
|
||||
try {
|
||||
this.jmespath.search(payload, jsonPath);
|
||||
const extracted = this.jmespath.search(payload, jsonPath);
|
||||
if (expectedValue) {
|
||||
let ok = false;
|
||||
if (matchMethod === "equal") ok = extracted === expectedValue;
|
||||
if (matchMethod === "include" && typeof extracted === "string") ok = extracted.includes(expectedValue);
|
||||
if (matchMethod === "regex" && typeof extracted === "string") ok = new RegExp(expectedValue).test(extracted);
|
||||
|
||||
if (ok) {
|
||||
httpResponse.extracted = extracted;
|
||||
return httpResponse;
|
||||
} else {
|
||||
httpResponse.status = false;
|
||||
httpResponse.code = 500;
|
||||
httpResponse.message = this.stringService.httpJsonPathFail;
|
||||
httpResponse.extracted = extracted;
|
||||
return httpResponse;
|
||||
}
|
||||
} else {
|
||||
const isFalsey = extracted === false || extracted === "false" || extracted === undefined || extracted === null;
|
||||
if (!isFalsey) {
|
||||
httpResponse.extracted = extracted;
|
||||
return httpResponse;
|
||||
} else {
|
||||
httpResponse.status = false;
|
||||
httpResponse.code = 500;
|
||||
httpResponse.message = this.stringService.httpJsonPathFail;
|
||||
httpResponse.extracted = extracted;
|
||||
return httpResponse;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
httpResponse.status = false;
|
||||
httpResponse.message = this.stringService.httpJsonPathError;
|
||||
|
||||
@@ -16,7 +16,7 @@ class NotificationService {
|
||||
return NotificationService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
sendNotification = async ({ notification, subject, content, html }) => {
|
||||
sendNotification = async ({ notification, subject, content, html, discordContent = null, webhookBody = null }) => {
|
||||
const { type, address } = notification;
|
||||
|
||||
if (type === "email") {
|
||||
@@ -28,9 +28,11 @@ class NotificationService {
|
||||
// Create a body for webhooks
|
||||
let body = { text: content };
|
||||
if (type === "discord") {
|
||||
body = { content };
|
||||
body = !discordContent ? { content } : discordContent;
|
||||
}
|
||||
if (type === "webhook") {
|
||||
body = !webhookBody ? { content } : webhookBody;
|
||||
}
|
||||
|
||||
if (type === "slack" || type === "discord" || type === "webhook") {
|
||||
const response = await this.networkService.requestWebhook(type, address, body);
|
||||
return response.status;
|
||||
@@ -55,7 +57,6 @@ class NotificationService {
|
||||
|
||||
const notificationIDs = networkResponse.monitor?.notifications ?? [];
|
||||
if (notificationIDs.length === 0) return false;
|
||||
|
||||
if (networkResponse.monitor.type === "hardware") {
|
||||
const thresholds = networkResponse?.monitor?.thresholds;
|
||||
|
||||
@@ -63,30 +64,29 @@ class NotificationService {
|
||||
const metrics = networkResponse?.payload?.data ?? null;
|
||||
if (metrics === null) return false; // No metrics, we're done
|
||||
|
||||
const alerts = await this.notificationUtils.buildHardwareAlerts(networkResponse);
|
||||
const [alerts, discordContent] = await this.notificationUtils.buildHardwareAlerts(networkResponse);
|
||||
if (alerts.length === 0) return false;
|
||||
|
||||
const { subject, html } = await this.notificationUtils.buildHardwareEmail(networkResponse, alerts);
|
||||
const content = await this.notificationUtils.buildHardwareNotificationMessage(alerts);
|
||||
|
||||
const success = await this.notifyAll({ notificationIDs, subject, html, content });
|
||||
const content = await this.notificationUtils.buildHardwareNotificationMessage(alerts, monitor);
|
||||
const webhookBody = await this.notificationUtils.buildHardwareWebhookBody(alerts, monitor);
|
||||
const success = await this.notifyAll({ notificationIDs, subject, html, content, discordContent, webhookBody });
|
||||
return success;
|
||||
}
|
||||
|
||||
// Status monitors
|
||||
const { subject, html } = await this.notificationUtils.buildStatusEmail(networkResponse);
|
||||
const content = await this.notificationUtils.buildWebhookMessage(networkResponse);
|
||||
const success = this.notifyAll({ notificationIDs, subject, html, content });
|
||||
const [content, discordContent] = await this.notificationUtils.buildWebhookMessage(networkResponse);
|
||||
const success = this.notifyAll({ notificationIDs, subject, html, content, discordContent });
|
||||
return success;
|
||||
}
|
||||
|
||||
async notifyAll({ notificationIDs, subject, html, content }) {
|
||||
async notifyAll({ notificationIDs, subject, html, content, discordContent = null, webhookBody = null }) {
|
||||
const notifications = await this.db.notificationModule.getNotificationsByIds(notificationIDs);
|
||||
|
||||
// Map each notification to a test promise
|
||||
const promises = notifications.map(async (notification) => {
|
||||
try {
|
||||
await this.sendNotification({ notification, subject, content, html });
|
||||
await this.sendNotification({ notification, subject, content, html, discordContent, webhookBody });
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
|
||||
@@ -70,7 +70,25 @@ class NotificationUtils {
|
||||
.replace("{time}", formattedTime)
|
||||
.replace("{code}", code || "Unknown");
|
||||
}
|
||||
return messageText;
|
||||
const dn = this.stringService.discordNotification;
|
||||
let discordMessageText = {
|
||||
embeds: [
|
||||
{
|
||||
title: status ? dn.uptimeAlert : dn.downtimeAlert,
|
||||
color: status ? 5763719 : 15548997,
|
||||
|
||||
fields: [
|
||||
{ name: dn.monitor, value: monitor?.name ?? dn.unknown, inline: true },
|
||||
{ name: dn.status, value: status ? dn.up : dn.down, inline: true },
|
||||
{ name: dn.statusCode, value: String(code ?? dn.unknown), inline: true },
|
||||
{ name: dn.time, value: formattedTime, inline: true },
|
||||
{ name: dn.url, value: monitor?.url ?? dn.unknown, inline: false },
|
||||
],
|
||||
footer: { text: dn.checkmate },
|
||||
},
|
||||
],
|
||||
};
|
||||
return [messageText, discordMessageText];
|
||||
};
|
||||
|
||||
buildHardwareAlerts = async (networkResponse) => {
|
||||
@@ -88,6 +106,43 @@ class NotificationUtils {
|
||||
};
|
||||
|
||||
const alertsToSend = [];
|
||||
const discordEmbeds = [];
|
||||
const monitorInfoFields = [
|
||||
{ name: "Monitor", value: monitor.name, inline: true },
|
||||
{ name: "URL", value: monitor.url, inline: false },
|
||||
];
|
||||
const formatDiscordAlert = {
|
||||
cpu: () => ({
|
||||
title: "CPU alert",
|
||||
description: `Your current CPU usage (${(cpuUsage * 100).toFixed(0)}%) is above your threshold (${(cpuThreshold * 100).toFixed(0)}%)`,
|
||||
color: 15548997,
|
||||
fields: monitorInfoFields,
|
||||
footer: { text: "Checkmate" },
|
||||
}),
|
||||
|
||||
memory: () => ({
|
||||
title: "Memory alert",
|
||||
description: `Your current memory usage (${(memoryUsage * 100).toFixed(0)}%) is above your threshold (${(memoryThreshold * 100).toFixed(0)}%)`,
|
||||
color: 15548997,
|
||||
fields: monitorInfoFields,
|
||||
footer: { text: "Checkmate" },
|
||||
}),
|
||||
|
||||
disk: () => ({
|
||||
title: "Disk alert",
|
||||
description: `Your current disk usage is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`,
|
||||
color: 15548997,
|
||||
footer: { text: "Checkmate" },
|
||||
fields: [
|
||||
...monitorInfoFields,
|
||||
...(Array.isArray(disk) ? disk : []).map((d, idx) => ({
|
||||
name: `Disk ${idx}`,
|
||||
value: `${(d?.usage_percent * 100).toFixed(0)}%`,
|
||||
inline: true,
|
||||
})),
|
||||
],
|
||||
}),
|
||||
};
|
||||
const alertTypes = ["cpu", "memory", "disk"];
|
||||
for (const type of alertTypes) {
|
||||
// Iterate over each alert type to see if any need to be decremented
|
||||
@@ -108,11 +163,13 @@ class NotificationUtils {
|
||||
.join(", ")} is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`,
|
||||
};
|
||||
alertsToSend.push(formatAlert[type]());
|
||||
discordEmbeds.push(formatDiscordAlert[type]());
|
||||
}
|
||||
}
|
||||
}
|
||||
await monitor.save();
|
||||
return alertsToSend;
|
||||
const discordPayload = discordEmbeds.length ? { embeds: discordEmbeds } : null;
|
||||
return [alertsToSend, discordPayload];
|
||||
};
|
||||
|
||||
buildHardwareEmail = async (networkResponse, alerts) => {
|
||||
@@ -124,8 +181,15 @@ class NotificationUtils {
|
||||
return { subject, html };
|
||||
};
|
||||
|
||||
buildHardwareNotificationMessage = (alerts) => {
|
||||
return alerts.map((alert) => alert).join("\n");
|
||||
buildHardwareNotificationMessage = (alerts, monitor) => {
|
||||
const alertsHeader = [`Monitor: ${monitor.name}`, `URL: ${monitor.url}`];
|
||||
const alertText = alerts.length > 0 ? [...alertsHeader, ...alerts] : [];
|
||||
return alertText.map((alert) => alert).join("\n");
|
||||
};
|
||||
buildHardwareWebhookBody = (alerts, monitor) => {
|
||||
const content = alerts.map((alert) => alert).join("\n");
|
||||
const body = { text: content, name: monitor.name, url: monitor.url };
|
||||
return body;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -216,6 +216,10 @@ class StringService {
|
||||
return this.translationService.getTranslation("monitorDownAlert");
|
||||
}
|
||||
|
||||
get discordNotification() {
|
||||
return this.translationService.getTranslation("discordNotification");
|
||||
}
|
||||
|
||||
getWebhookUnsupportedPlatform(platform) {
|
||||
return this.translationService.getTranslation("webhookUnsupportedPlatform").replace("{platform}", platform);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user