mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-21 17:19:00 -05:00
resolve merge conflicts
This commit is contained in:
@@ -13,9 +13,6 @@ on:
|
||||
required: false
|
||||
default: "key_value_json"
|
||||
|
||||
# For automatic execution at a specific time (every day at midnight)
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
VITE_APP_API_BASE_URL=UPTIME_APP_API_BASE_URL
|
||||
VITE_STATUS_PAGE_SUBDOMAIN_PREFIX=UPTIME_STATUS_PAGE_SUBDOMAIN_PREFIX
|
||||
VITE_APP_CLIENT_HOST=UPTIME_APP_CLIENT_HOST
|
||||
VITE_APP_LOG_LEVEL=UPTIME_APP_LOG_LEVEL
|
||||
Generated
+71
-13627
File diff suppressed because it is too large
Load Diff
+8
-6
@@ -22,12 +22,6 @@
|
||||
"@mui/lab": "6.0.0-dev.240424162023-9968b4889d",
|
||||
"@mui/material": "6.4.11",
|
||||
"@reduxjs/toolkit": "2.7.0",
|
||||
"@solana/wallet-adapter-base": "0.9.25",
|
||||
"@solana/wallet-adapter-material-ui": "0.16.35",
|
||||
"@solana/wallet-adapter-react": "0.15.37",
|
||||
"@solana/wallet-adapter-react-ui": "0.9.37",
|
||||
"@solana/wallet-adapter-wallets": "0.19.34",
|
||||
"@solana/web3.js": "1.98.0",
|
||||
"axios": "^1.7.4",
|
||||
"dayjs": "1.11.13",
|
||||
"flag-icons": "7.3.2",
|
||||
@@ -52,6 +46,14 @@
|
||||
"redux-persist": "6.0.0",
|
||||
"vite-plugin-svgr": "^4.2.0"
|
||||
},
|
||||
"unusedDepencies": {
|
||||
"@solana/wallet-adapter-base": "0.9.25",
|
||||
"@solana/wallet-adapter-material-ui": "0.16.35",
|
||||
"@solana/wallet-adapter-react": "0.15.37",
|
||||
"@solana/wallet-adapter-react-ui": "0.9.37",
|
||||
"@solana/wallet-adapter-wallets": "0.19.34",
|
||||
"@solana/web3.js": "1.98.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.66",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
|
||||
@@ -1,43 +1,18 @@
|
||||
import { useEffect } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useDispatch } from "react-redux";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import { ThemeProvider } from "@emotion/react";
|
||||
import lightTheme from "./Utils/Theme/lightTheme";
|
||||
import darkTheme from "./Utils/Theme/darkTheme";
|
||||
import { CssBaseline, GlobalStyles } from "@mui/material";
|
||||
import { getAppSettings } from "./Features/Settings/settingsSlice";
|
||||
import { logger } from "./Utils/Logger"; // Import the logger
|
||||
import { networkService } from "./main";
|
||||
import { Routes } from "./Routes";
|
||||
import WalletProvider from "./Components/WalletProvider";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { setLanguage } from "./Features/UI/uiSlice";
|
||||
|
||||
function App() {
|
||||
const mode = useSelector((state) => state.ui.mode);
|
||||
const { authToken } = useSelector((state) => state.auth);
|
||||
const dispatch = useDispatch();
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (authToken) {
|
||||
dispatch(getAppSettings({ authToken })).then((action) => {
|
||||
if (action.payload && action.payload.success) {
|
||||
const { language } = action.payload.data;
|
||||
const availableLanguages = Object.keys(i18n.options.resources || {});
|
||||
if (language && availableLanguages.includes(language)) {
|
||||
dispatch(setLanguage(language));
|
||||
i18n.changeLanguage(language);
|
||||
} else {
|
||||
dispatch(setLanguage(availableLanguages[0]));
|
||||
i18n.changeLanguage(availableLanguages[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [dispatch, authToken, i18n]);
|
||||
|
||||
// Cleanup
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { Stack, Button } from "@mui/material";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import PropTypes from "prop-types";
|
||||
import SkeletonLayout from "./skeleton";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTheme } from "@emotion/react";
|
||||
|
||||
const CreateMonitorHeader = ({
|
||||
isAdmin,
|
||||
label = "Create new",
|
||||
shouldRender = true,
|
||||
isLoading = true,
|
||||
path,
|
||||
bulkPath,
|
||||
}) => {
|
||||
@@ -17,7 +16,6 @@ const CreateMonitorHeader = ({
|
||||
const theme = useTheme();
|
||||
|
||||
if (!isAdmin) return null;
|
||||
if (!shouldRender) return <SkeletonLayout />;
|
||||
|
||||
return (
|
||||
<Stack
|
||||
@@ -27,6 +25,7 @@ const CreateMonitorHeader = ({
|
||||
gap={theme.spacing(6)}
|
||||
>
|
||||
<Button
|
||||
loading={isLoading}
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={() => navigate(path)}
|
||||
@@ -35,6 +34,7 @@ const CreateMonitorHeader = ({
|
||||
</Button>
|
||||
{bulkPath && (
|
||||
<Button
|
||||
loading={isLoading}
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={() => {
|
||||
@@ -52,7 +52,7 @@ export default CreateMonitorHeader;
|
||||
|
||||
CreateMonitorHeader.propTypes = {
|
||||
isAdmin: PropTypes.bool.isRequired,
|
||||
shouldRender: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
path: PropTypes.string.isRequired,
|
||||
label: PropTypes.string,
|
||||
bulkPath: PropTypes.string,
|
||||
|
||||
@@ -55,10 +55,9 @@ const MonitorTimeFrameHeader = ({
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="flex-end"
|
||||
justifyContent="flex-end"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(4)}
|
||||
mb={theme.spacing(8)}
|
||||
>
|
||||
<Typography variant="body2">
|
||||
Showing statistics for past{" "}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { update } from "../../../Features/Auth/authSlice";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { createToast } from "../../../Utils/toastUtils";
|
||||
import { getTouchedFieldErrors } from "../../../Validation/error";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const defaultPasswordsState = {
|
||||
password: "",
|
||||
@@ -26,6 +27,7 @@ const defaultPasswordsState = {
|
||||
const PasswordPanel = () => {
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const SPACING_GAP = theme.spacing(12);
|
||||
|
||||
@@ -203,7 +205,7 @@ const PasswordPanel = () => {
|
||||
<TextInput
|
||||
type="password"
|
||||
id="edit-confirm-password"
|
||||
placeholder="Reenter your new password"
|
||||
placeholder={t("confirmPassword")}
|
||||
autoComplete="new-password"
|
||||
value={localData.confirm}
|
||||
onChange={handleChange}
|
||||
|
||||
@@ -344,7 +344,7 @@ const ProfilePanel = () => {
|
||||
spellCheck="false"
|
||||
>
|
||||
<Box mb={theme.spacing(6)}>
|
||||
<Typography component="h1">{t('DeleteAccount')}</Typography>
|
||||
<Typography component="h1">{t('DeleteAccountTitle')}</Typography>
|
||||
<Typography
|
||||
component="p"
|
||||
sx={{ opacity: 0.6 }}
|
||||
@@ -357,7 +357,7 @@ const ProfilePanel = () => {
|
||||
color="error"
|
||||
onClick={() => setIsOpen("delete")}
|
||||
>
|
||||
{t('DeleteAccount')}
|
||||
{t('DeleteAccountButton')}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
@@ -367,7 +367,7 @@ const ProfilePanel = () => {
|
||||
title={t('DeleteWarningTitle')}
|
||||
description={t('DeleteAccountWarning')}
|
||||
onCancel={() => setIsOpen("")}
|
||||
confirmationButtonLabel={t('DeleteAccount')}
|
||||
confirmationButtonLabel={t('DeleteAccountButton')}
|
||||
onConfirm={handleDeleteAccount}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
|
||||
@@ -1,46 +1,50 @@
|
||||
import { useMemo } from "react";
|
||||
import { ConnectionProvider, WalletProvider } from "@solana/wallet-adapter-react";
|
||||
import { WalletAdapterNetwork } from "@solana/wallet-adapter-base";
|
||||
import {
|
||||
UnsafeBurnerWalletAdapter,
|
||||
PhantomWalletAdapter,
|
||||
} from "@solana/wallet-adapter-wallets";
|
||||
// import { useMemo } from "react";
|
||||
// import { ConnectionProvider, WalletProvider } from "@solana/wallet-adapter-react";
|
||||
// import { WalletAdapterNetwork } from "@solana/wallet-adapter-base";
|
||||
// import {
|
||||
// UnsafeBurnerWalletAdapter,
|
||||
// PhantomWalletAdapter,
|
||||
// } from "@solana/wallet-adapter-wallets";
|
||||
|
||||
import { WalletModalProvider } from "@solana/wallet-adapter-react-ui";
|
||||
import { clusterApiUrl } from "@solana/web3.js";
|
||||
import PropTypes from "prop-types";
|
||||
import "./index.css";
|
||||
// import { WalletModalProvider } from "@solana/wallet-adapter-react-ui";
|
||||
// import { clusterApiUrl } from "@solana/web3.js";
|
||||
// import PropTypes from "prop-types";
|
||||
// import "./index.css";
|
||||
|
||||
// Default styles that can be overridden by your app
|
||||
import "@solana/wallet-adapter-react-ui/styles.css";
|
||||
// // Default styles that can be overridden by your app
|
||||
// import "@solana/wallet-adapter-react-ui/styles.css";
|
||||
|
||||
export const Wallet = ({ children }) => {
|
||||
// The network can be set to 'devnet', 'testnet', or 'mainnet-beta'.
|
||||
const network = WalletAdapterNetwork.Mainnet;
|
||||
// export const Wallet = ({ children }) => {
|
||||
// // The network can be set to 'devnet', 'testnet', or 'mainnet-beta'.
|
||||
// const network = WalletAdapterNetwork.Mainnet;
|
||||
|
||||
// You can also provide a custom RPC endpoint.
|
||||
const endpoint = useMemo(() => clusterApiUrl(network), [network]);
|
||||
// // You can also provide a custom RPC endpoint.
|
||||
// const endpoint = useMemo(() => clusterApiUrl(network), [network]);
|
||||
|
||||
const wallets = useMemo(
|
||||
() => [new PhantomWalletAdapter()],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[network]
|
||||
);
|
||||
// const wallets = useMemo(
|
||||
// () => [new PhantomWalletAdapter()],
|
||||
// // eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// [network]
|
||||
// );
|
||||
|
||||
return (
|
||||
<ConnectionProvider endpoint={endpoint}>
|
||||
<WalletProvider
|
||||
wallets={wallets}
|
||||
autoConnect
|
||||
>
|
||||
<WalletModalProvider>{children}</WalletModalProvider>
|
||||
</WalletProvider>
|
||||
</ConnectionProvider>
|
||||
);
|
||||
};
|
||||
// return (
|
||||
// <ConnectionProvider endpoint={endpoint}>
|
||||
// <WalletProvider
|
||||
// wallets={wallets}
|
||||
// autoConnect
|
||||
// >
|
||||
// <WalletModalProvider>{children}</WalletModalProvider>
|
||||
// </WalletProvider>
|
||||
// </ConnectionProvider>
|
||||
// );
|
||||
// };
|
||||
|
||||
Wallet.propTypes = {
|
||||
children: PropTypes.node,
|
||||
// Wallet.propTypes = {
|
||||
// children: PropTypes.node,
|
||||
// };
|
||||
|
||||
const Wallet = ({ children }) => {
|
||||
return children;
|
||||
};
|
||||
|
||||
export default Wallet;
|
||||
|
||||
@@ -101,7 +101,7 @@ export const updatePageSpeed = createAsyncThunk(
|
||||
name: monitor.name,
|
||||
description: monitor.description,
|
||||
interval: monitor.interval,
|
||||
// notifications: monitor.notifications,
|
||||
notifications: monitor.notifications,
|
||||
};
|
||||
const res = await networkService.updateMonitor({
|
||||
monitorId: monitor._id,
|
||||
|
||||
@@ -5,7 +5,7 @@ const initialState = {
|
||||
isLoading: false,
|
||||
apiBaseUrl: "",
|
||||
logLevel: "debug",
|
||||
language: "",
|
||||
language: "gb",
|
||||
pagespeedApiKey: "",
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState } from "react";
|
||||
import { networkService } from "../main";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const CLIENT_HOST = import.meta.env.VITE_CLIENT_HOST;
|
||||
const CLIENT_HOST = import.meta.env.VITE_APP_CLIENT_HOST;
|
||||
|
||||
const useGetInviteToken = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -58,7 +58,7 @@ const EmailStep = ({ form, errors, onSubmit, onChange }) => {
|
||||
onInput={(e) => (e.target.value = e.target.value.toLowerCase())}
|
||||
onChange={onChange}
|
||||
error={errors.email ? true : false}
|
||||
helperText={errors.email}
|
||||
helperText={errors.email ? t(errors.email) : ""}
|
||||
ref={inputRef}
|
||||
/>
|
||||
<Stack
|
||||
@@ -70,8 +70,17 @@ const EmailStep = ({ form, errors, onSubmit, onChange }) => {
|
||||
color="accent"
|
||||
type="submit"
|
||||
disabled={errors.email && true}
|
||||
className="dashboard-style-button"
|
||||
sx={{
|
||||
width: "30%",
|
||||
px: theme.spacing(6),
|
||||
borderRadius: `${theme.shape.borderRadius}px !important`,
|
||||
'&.MuiButtonBase-root': {
|
||||
borderRadius: `${theme.shape.borderRadius}px !important`
|
||||
},
|
||||
'&.MuiButton-root': {
|
||||
borderRadius: `${theme.shape.borderRadius}px !important`
|
||||
},
|
||||
"&.Mui-focusVisible": {
|
||||
outline: `2px solid ${theme.palette.primary.main}`,
|
||||
outlineOffset: `2px`,
|
||||
|
||||
@@ -75,8 +75,16 @@ const PasswordStep = ({ form, errors, onSubmit, onChange, onBack }) => {
|
||||
variant="outlined"
|
||||
color="info"
|
||||
onClick={onBack}
|
||||
className="dashboard-style-button"
|
||||
sx={{
|
||||
px: theme.spacing(5),
|
||||
borderRadius: `${theme.shape.borderRadius}px !important`,
|
||||
'&.MuiButtonBase-root': {
|
||||
borderRadius: `${theme.shape.borderRadius}px !important`
|
||||
},
|
||||
'&.MuiButton-root': {
|
||||
borderRadius: `${theme.shape.borderRadius}px !important`
|
||||
},
|
||||
"& svg.MuiSvgIcon-root": {
|
||||
mr: theme.spacing(3),
|
||||
},
|
||||
@@ -95,13 +103,22 @@ const PasswordStep = ({ form, errors, onSubmit, onChange, onBack }) => {
|
||||
type="submit"
|
||||
loading={authState.isLoading}
|
||||
disabled={errors.password && true}
|
||||
className="dashboard-style-button"
|
||||
sx={{
|
||||
width: "30%",
|
||||
px: theme.spacing(4),
|
||||
borderRadius: `${theme.shape.borderRadius}px !important`,
|
||||
'&.MuiButtonBase-root': {
|
||||
borderRadius: `${theme.shape.borderRadius}px !important`
|
||||
},
|
||||
'&.MuiButton-root': {
|
||||
borderRadius: `${theme.shape.borderRadius}px !important`
|
||||
},
|
||||
"&.Mui-focusVisible": {
|
||||
outline: `2px solid ${theme.palette.primary.main}`,
|
||||
outlineOffset: `2px`,
|
||||
boxShadow: `none`,
|
||||
},
|
||||
boxShadow: `none`,
|
||||
}}
|
||||
>
|
||||
{t("continue")}
|
||||
|
||||
@@ -9,13 +9,13 @@ import { createToast } from "../../../Utils/toastUtils";
|
||||
import { networkService } from "../../../main";
|
||||
import Background from "../../../assets/Images/background-grid.svg?react";
|
||||
import Logo from "../../../assets/icons/checkmate-icon.svg?react";
|
||||
import { logger } from "../../../Utils/Logger";
|
||||
import "../index.css";
|
||||
import EmailStep from "./Components/EmailStep";
|
||||
import PasswordStep from "./Components/PasswordStep";
|
||||
import ThemeSwitch from "../../../Components/ThemeSwitch";
|
||||
import ForgotPasswordLabel from "./Components/ForgotPasswordLabel";
|
||||
import LanguageSelector from "../../../Components/LanguageSelector";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const DEMO = import.meta.env.VITE_APP_DEMO;
|
||||
|
||||
@@ -24,10 +24,10 @@ const DEMO = import.meta.env.VITE_APP_DEMO;
|
||||
*/
|
||||
|
||||
const Login = () => {
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const authState = useSelector((state) => state.auth);
|
||||
const { authToken } = authState;
|
||||
|
||||
@@ -43,33 +43,28 @@ const Login = () => {
|
||||
const [errors, setErrors] = useState({});
|
||||
const [step, setStep] = useState(0);
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (authToken) {
|
||||
navigate("/uptime");
|
||||
return;
|
||||
}
|
||||
networkService
|
||||
.doesSuperAdminExist()
|
||||
.then((response) => {
|
||||
if (response.data.data === false) {
|
||||
navigate("/register");
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error(error);
|
||||
});
|
||||
}, [authToken, navigate]);
|
||||
|
||||
const handleChange = (event) => {
|
||||
const { value, id } = event.target;
|
||||
const name = idMap[id];
|
||||
const lowerCasedValue = name === idMap["login-email-input"]? value?.toLowerCase()||value : value
|
||||
const lowerCasedValue =
|
||||
name === idMap["login-email-input"] ? value?.toLowerCase() || value : value;
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
[name]: lowerCasedValue,
|
||||
}));
|
||||
|
||||
const { error } = credentials.validate({ [name]: lowerCasedValue }, { abortEarly: false });
|
||||
const { error } = credentials.validate(
|
||||
{ [name]: lowerCasedValue },
|
||||
{ abortEarly: false }
|
||||
);
|
||||
|
||||
setErrors((prev) => {
|
||||
const prevErrors = { ...prev };
|
||||
@@ -88,8 +83,10 @@ const Login = () => {
|
||||
{ abortEarly: false }
|
||||
);
|
||||
if (error) {
|
||||
setErrors((prev) => ({ ...prev, email: error.details[0].message }));
|
||||
createToast({ body: error.details[0].message });
|
||||
const errorMessage = error.details[0].message;
|
||||
const translatedMessage = errorMessage.startsWith('auth') ? t(errorMessage) : errorMessage;
|
||||
setErrors((prev) => ({ ...prev, email: translatedMessage }));
|
||||
createToast({ body: translatedMessage });
|
||||
} else {
|
||||
setStep(1);
|
||||
}
|
||||
@@ -106,15 +103,15 @@ const Login = () => {
|
||||
createToast({
|
||||
body:
|
||||
error.details && error.details.length > 0
|
||||
? error.details[0].message
|
||||
: "Error validating data.",
|
||||
? (error.details[0].message.startsWith('auth') ? t(error.details[0].message) : error.details[0].message)
|
||||
: t("Error validating data."),
|
||||
});
|
||||
} else {
|
||||
const action = await dispatch(login(form));
|
||||
if (action.payload.success) {
|
||||
navigate("/uptime");
|
||||
createToast({
|
||||
body: "Welcome back! You're successfully logged in.",
|
||||
body: t("welcomeBack"),
|
||||
});
|
||||
} else {
|
||||
if (action.payload) {
|
||||
@@ -230,6 +227,31 @@ const Login = () => {
|
||||
email={form.email}
|
||||
errorEmail={errors.email}
|
||||
/>
|
||||
|
||||
{/* Registration link */}
|
||||
<Box textAlign="center" >
|
||||
<Typography
|
||||
className="forgot-p"
|
||||
display="inline-block"
|
||||
color={theme.palette.primary.main}
|
||||
>
|
||||
{t("doNotHaveAccount")}
|
||||
</Typography>
|
||||
<Typography
|
||||
component="span"
|
||||
color={theme.palette.accent.main}
|
||||
ml={theme.spacing(2)}
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
color: theme.palette.accent.darker
|
||||
}
|
||||
}}
|
||||
onClick={() => navigate("/register")}
|
||||
>
|
||||
{t("registerHere")}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -387,7 +387,7 @@ const Register = ({ isSuperAdmin }) => {
|
||||
}}
|
||||
sx={{ userSelect: "none", color: theme.palette.accent.main }}
|
||||
>
|
||||
{t("authLoginTitle")}
|
||||
{t("authRegisterLoginLink")}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
@@ -68,7 +68,11 @@ function StepTwo({ form, errors, onSubmit, onChange, onBack }) {
|
||||
onInput={(e) => (e.target.value = e.target.value.toLowerCase())}
|
||||
onChange={onChange}
|
||||
error={errors.email ? true : false}
|
||||
helperText={errors.email && t("authRegisterEmailRequired")}
|
||||
helperText={errors.email && (
|
||||
errors.email.includes("required") ? t("authRegisterEmailRequired") :
|
||||
errors.email.includes("valid email") ? t("authRegisterEmailInvalid") :
|
||||
errors.email
|
||||
)}
|
||||
ref={inputRef}
|
||||
/>
|
||||
<Stack
|
||||
|
||||
@@ -75,6 +75,7 @@ const InfrastructureMenu = ({ monitor, isAdmin, updateCallback }) => {
|
||||
<IconButton
|
||||
aria-label="monitor actions"
|
||||
onClick={openMenu}
|
||||
disabled={!isAdmin}
|
||||
sx={{
|
||||
"&:focus": {
|
||||
outline: "none",
|
||||
|
||||
@@ -94,7 +94,7 @@ const InfrastructureMonitors = () => {
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<MonitorCreateHeader
|
||||
isAdmin={isAdmin}
|
||||
shouldRender={!isLoading}
|
||||
isLoading={isLoading}
|
||||
path="/infrastructure/create"
|
||||
/>
|
||||
<Stack direction={"row"}>
|
||||
|
||||
@@ -62,7 +62,7 @@ const PageSpeed = () => {
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<CreateMonitorHeader
|
||||
isAdmin={isAdmin}
|
||||
shouldRender={!isLoading}
|
||||
isLoading={isLoading}
|
||||
path="/pagespeed/create"
|
||||
/>
|
||||
<MonitorCountHeader
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
import React, { useState } from "react";
|
||||
import { Box, Typography, Button, Stack } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useNavigate } from "react-router";
|
||||
import { networkService } from "../Utils/NetworkService";
|
||||
import Alert from "../Components/Alert";
|
||||
import { createToast } from "../Utils/toastUtils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Background from "../assets/Images/background-grid.svg?react";
|
||||
import Logo from "../assets/icons/checkmate-icon.svg?react";
|
||||
import ThemeSwitch from "../Components/ThemeSwitch";
|
||||
import LanguageSelector from "../Components/LanguageSelector";
|
||||
|
||||
const ServerUnreachable = () => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
// State for tracking connection check status
|
||||
const [isCheckingConnection, setIsCheckingConnection] = useState(false);
|
||||
|
||||
const handleRetry = React.useCallback(async () => {
|
||||
setIsCheckingConnection(true);
|
||||
try {
|
||||
// Try to connect to the backend with a simple API call
|
||||
// We'll use any lightweight endpoint that doesn't require authentication
|
||||
await networkService.axiosInstance.get('/health', { timeout: 5000 });
|
||||
|
||||
// If successful, show toast and navigate to login page
|
||||
createToast({
|
||||
body: t("backendReconnected", "Connection to server restored"),
|
||||
});
|
||||
navigate("/login");
|
||||
} catch (error) {
|
||||
// If still unreachable, stay on this page and show toast
|
||||
createToast({
|
||||
body: t("backendStillUnreachable", "Server is still unreachable"),
|
||||
});
|
||||
} finally {
|
||||
setIsCheckingConnection(false);
|
||||
}
|
||||
}, [navigate, t]);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
className="login-page auth"
|
||||
overflow="hidden"
|
||||
>
|
||||
<Box
|
||||
className="background-pattern-svg"
|
||||
sx={{
|
||||
"& svg g g:last-of-type path": {
|
||||
stroke: theme.palette.primary.lowContrast,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Background style={{ width: "100%" }} />
|
||||
</Box>
|
||||
|
||||
{/* Header with logo */}
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
px={theme.spacing(12)}
|
||||
gap={theme.spacing(4)}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(4)}
|
||||
>
|
||||
<Logo style={{ borderRadius: theme.shape.borderRadius }} />
|
||||
<Typography sx={{ userSelect: "none" }}>Checkmate</Typography>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={2}
|
||||
alignItems="center"
|
||||
>
|
||||
<LanguageSelector />
|
||||
<ThemeSwitch />
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack
|
||||
width="100%"
|
||||
maxWidth={600}
|
||||
flex={1}
|
||||
justifyContent="center"
|
||||
px={{ xs: theme.spacing(12), lg: theme.spacing(20) }}
|
||||
pb={theme.spacing(20)}
|
||||
mx="auto"
|
||||
rowGap={theme.spacing(8)}
|
||||
sx={{
|
||||
"& > .MuiStack-root": {
|
||||
border: 1,
|
||||
borderRadius: theme.spacing(5),
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
padding: {
|
||||
xs: theme.spacing(12),
|
||||
sm: theme.spacing(20),
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Stack spacing={theme.spacing(6)} alignItems="center">
|
||||
<Box sx={{
|
||||
width: theme.spacing(220),
|
||||
mx: 'auto',
|
||||
'& .alert.row-stack': {
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing(3)
|
||||
}
|
||||
}}>
|
||||
<Alert
|
||||
variant="error"
|
||||
body={t("backendUnreachable", "Server Unreachable")}
|
||||
hasIcon={true}
|
||||
/>
|
||||
</Box>
|
||||
<Box mt={theme.spacing(2)}>
|
||||
<Typography
|
||||
variant="body1"
|
||||
align="center"
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
>
|
||||
{t("backendUnreachableMessage", "The Checkmate server is not responding. Please check your deployment configuration or try again later.")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ mt: theme.spacing(4) }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={handleRetry}
|
||||
disabled={isCheckingConnection}
|
||||
className="dashboard-style-button"
|
||||
sx={{
|
||||
px: theme.spacing(6),
|
||||
borderRadius: `${theme.shape.borderRadius}px !important`,
|
||||
'&.MuiButtonBase-root': {
|
||||
borderRadius: `${theme.shape.borderRadius}px !important`
|
||||
},
|
||||
'&.MuiButton-root': {
|
||||
borderRadius: `${theme.shape.borderRadius}px !important`
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isCheckingConnection ?
|
||||
t("retryingConnection", "Retrying Connection...") :
|
||||
t("retryConnection", "Retry Connection")}
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServerUnreachable;
|
||||
@@ -8,10 +8,10 @@ import Dialog from "../../Components/Dialog";
|
||||
import ConfigBox from "../../Components/ConfigBox";
|
||||
import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments";
|
||||
import { getAppSettings } from "../../Features/Settings/settingsSlice";
|
||||
import {
|
||||
WalletMultiButton,
|
||||
WalletDisconnectButton,
|
||||
} from "@solana/wallet-adapter-react-ui";
|
||||
// import {
|
||||
// WalletMultiButton,
|
||||
// WalletDisconnectButton,
|
||||
// } from "@solana/wallet-adapter-react-ui";
|
||||
|
||||
//Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
@@ -290,7 +290,7 @@ const Settings = () => {
|
||||
></Select>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
{isAdmin && (
|
||||
{/* {isAdmin && (
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">{t("settingsDistributedUptime")}</Typography>
|
||||
@@ -312,7 +312,7 @@ const Settings = () => {
|
||||
: t("settingsDisabled")}
|
||||
</Box>
|
||||
</ConfigBox>
|
||||
)}
|
||||
)} */}
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">{t("pageSpeedApiKeyFieldTitle")}</Typography>
|
||||
@@ -350,7 +350,7 @@ const Settings = () => {
|
||||
)}
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
{isAdmin && (
|
||||
{/* {isAdmin && (
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">{t("settingsWallet")}</Typography>
|
||||
@@ -375,7 +375,7 @@ const Settings = () => {
|
||||
</Stack>
|
||||
</Box>
|
||||
</ConfigBox>
|
||||
)}
|
||||
)} */}
|
||||
{isAdmin && (
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
@@ -421,14 +421,15 @@ const Settings = () => {
|
||||
</ConfigBox>
|
||||
)}
|
||||
{isAdmin && (
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">{t("settingsDemoMonitors")}</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2) }}>
|
||||
{t("settingsDemoMonitorsDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(20)}>
|
||||
<>
|
||||
{/* Demo Monitors Section */}
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">{t("settingsDemoMonitors")}</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2) }}>
|
||||
{t("settingsDemoMonitorsDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography>{t("settingsAddDemoMonitors")}</Typography>
|
||||
<Button
|
||||
@@ -441,6 +442,16 @@ const Settings = () => {
|
||||
{t("settingsAddDemoMonitorsButton")}
|
||||
</Button>
|
||||
</Box>
|
||||
</ConfigBox>
|
||||
|
||||
{/* System Reset Section */}
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">{t("settingsSystemReset")}</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2) }}>
|
||||
{t("settingsSystemResetDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography>{t("settingsRemoveAllMonitors")}</Typography>
|
||||
<Button
|
||||
@@ -455,17 +466,17 @@ const Settings = () => {
|
||||
{t("settingsRemoveAllMonitorsButton")}
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Dialog
|
||||
open={isOpen.deleteMonitors}
|
||||
theme={theme}
|
||||
title={t("settingsRemoveAllMonitorsDialogTitle")}
|
||||
onCancel={() => setIsOpen(deleteStatsMonitorsInitState)}
|
||||
confirmationButtonLabel={t("settingsRemoveAllMonitorsDialogConfirm")}
|
||||
onConfirm={handleDeleteAllMonitors}
|
||||
isLoading={isLoading || authIsLoading || checksIsLoading}
|
||||
/>
|
||||
</ConfigBox>
|
||||
<Dialog
|
||||
open={isOpen.deleteMonitors}
|
||||
theme={theme}
|
||||
title={t("settingsRemoveAllMonitorsDialogTitle")}
|
||||
onCancel={() => setIsOpen(deleteStatsMonitorsInitState)}
|
||||
confirmationButtonLabel={t("settingsRemoveAllMonitorsDialogConfirm")}
|
||||
onConfirm={handleDeleteAllMonitors}
|
||||
isLoading={isLoading || authIsLoading || checksIsLoading}
|
||||
/>
|
||||
</ConfigBox>
|
||||
</>
|
||||
)}
|
||||
|
||||
<ConfigBox>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Box, Stack, Typography, Button } from "@mui/material";
|
||||
import Image from "../../../../../Components/Image";
|
||||
import SettingsIcon from "../../../../../assets/icons/settings-bold.svg?react";
|
||||
import ThemeSwitch from "../../../../../Components/ThemeSwitch";
|
||||
import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward";
|
||||
//Utils
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@@ -86,6 +87,8 @@ const ControlsHeader = ({
|
||||
type = "uptime",
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const publicUrl = `/status/uptime/public/${url}`;
|
||||
|
||||
return (
|
||||
<Stack
|
||||
@@ -118,6 +121,26 @@ const ControlsHeader = ({
|
||||
>
|
||||
{statusPage?.companyName}
|
||||
</Typography>
|
||||
{statusPage?.isPublished && !isPublic && (
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
onClick={() => {
|
||||
window.open(publicUrl, "_blank", "noopener,noreferrer");
|
||||
}}
|
||||
sx={{
|
||||
display: "inline-flex",
|
||||
":hover": {
|
||||
cursor: "pointer",
|
||||
borderBottom: 1,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Typography>{t("publicLink")}</Typography>
|
||||
<ArrowOutwardIcon />
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
<Controls
|
||||
isDeleting={isDeleting}
|
||||
|
||||
@@ -139,6 +139,7 @@ const PublicStatus = () => {
|
||||
isDeleteOpen={isDeleteOpen}
|
||||
setIsDeleteOpen={setIsDeleteOpen}
|
||||
url={url}
|
||||
isPublic={isPublic}
|
||||
/>
|
||||
<Typography variant="h2">{t("statusPageStatusServiceStatus")}</Typography>
|
||||
<StatusBar monitors={monitors} />
|
||||
|
||||
@@ -61,6 +61,7 @@ const StatusPages = () => {
|
||||
label="Create status page"
|
||||
isAdmin={isAdmin}
|
||||
path="/status/uptime/create"
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<StatusPagesTable data={statusPages} />
|
||||
</Stack>
|
||||
|
||||
@@ -61,18 +61,20 @@ const UptimeStatusBoxes = ({
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<StatBox
|
||||
heading="certificate expiry"
|
||||
subHeading={
|
||||
<Typography
|
||||
component="span"
|
||||
fontSize={13}
|
||||
color={theme.palette.primary.contrastText}
|
||||
>
|
||||
{certificateExpiry}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
{monitor?.type === "http" && (
|
||||
<StatBox
|
||||
heading="certificate expiry"
|
||||
subHeading={
|
||||
<Typography
|
||||
component="span"
|
||||
fontSize={13}
|
||||
color={theme.palette.primary.contrastText}
|
||||
>
|
||||
{certificateExpiry}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</StatusBoxes>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -189,7 +189,7 @@ const UptimeMonitors = () => {
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<CreateMonitorHeader
|
||||
isAdmin={isAdmin}
|
||||
shouldRender={!isLoading}
|
||||
isLoading={isLoading}
|
||||
path="/uptime/create"
|
||||
bulkPath="/uptime/bulk-import"
|
||||
/>
|
||||
|
||||
+24
-17
@@ -36,6 +36,9 @@ import DistributedUptimeDetails from "../Pages/DistributedUptime/Details";
|
||||
import CreateDistributedUptimeStatus from "../Pages/DistributedUptimeStatus/Create";
|
||||
import DistributedUptimeStatus from "../Pages/DistributedUptimeStatus/Status";
|
||||
|
||||
// Server Status
|
||||
import ServerUnreachable from "../Pages/ServerUnreachable";
|
||||
|
||||
// Incidents
|
||||
import Incidents from "../Pages/Incidents";
|
||||
|
||||
@@ -96,16 +99,16 @@ const Routes = () => {
|
||||
path="/uptime/configure/:monitorId/"
|
||||
element={<UptimeConfigure />}
|
||||
/>
|
||||
<Route
|
||||
{/* <Route
|
||||
path="/distributed-uptime"
|
||||
element={
|
||||
<ProtectedDistributedUptimeRoute>
|
||||
<DistributedUptimeMonitors />{" "}
|
||||
</ProtectedDistributedUptimeRoute>
|
||||
}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
<Route
|
||||
{/* <Route
|
||||
path="/distributed-uptime/create"
|
||||
element={
|
||||
<ProtectedDistributedUptimeRoute>
|
||||
@@ -120,15 +123,15 @@ const Routes = () => {
|
||||
<CreateDistributedUptime />
|
||||
</ProtectedDistributedUptimeRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
/> */}
|
||||
{/* <Route
|
||||
path="/distributed-uptime/:monitorId"
|
||||
element={
|
||||
<ProtectedDistributedUptimeRoute>
|
||||
<DistributedUptimeDetails />
|
||||
</ProtectedDistributedUptimeRoute>
|
||||
}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
<Route
|
||||
path="pagespeed"
|
||||
@@ -154,9 +157,9 @@ const Routes = () => {
|
||||
path="infrastructure/create"
|
||||
element={<InfrastructureCreate />}
|
||||
/>
|
||||
<Route
|
||||
path="/infrastructure/configure/:monitorId"
|
||||
element={<InfrastructureCreate />}
|
||||
<Route
|
||||
path="/infrastructure/configure/:monitorId"
|
||||
element={<InfrastructureCreate />}
|
||||
/>
|
||||
<Route
|
||||
path="infrastructure/:monitorId"
|
||||
@@ -177,42 +180,42 @@ const Routes = () => {
|
||||
element={<Status />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
{/* <Route
|
||||
path="/status/distributed/:url"
|
||||
element={
|
||||
<ProtectedDistributedUptimeRoute>
|
||||
<DistributedUptimeStatus />
|
||||
</ProtectedDistributedUptimeRoute>
|
||||
}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
<Route
|
||||
path="status/uptime/create"
|
||||
element={<CreateStatus />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
{/* <Route
|
||||
path="/status/distributed/create/:monitorId"
|
||||
element={
|
||||
<ProtectedDistributedUptimeRoute>
|
||||
<CreateDistributedUptimeStatus />
|
||||
</ProtectedDistributedUptimeRoute>
|
||||
}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
<Route
|
||||
path="status/uptime/configure/:url"
|
||||
element={<CreateStatus />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
{/* <Route
|
||||
path="/status/distributed/configure/:url"
|
||||
element={
|
||||
<ProtectedDistributedUptimeRoute>
|
||||
<CreateDistributedUptimeStatus />
|
||||
</ProtectedDistributedUptimeRoute>
|
||||
}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
<Route
|
||||
path="integrations"
|
||||
@@ -280,11 +283,15 @@ const Routes = () => {
|
||||
path="/status/uptime/public/:url"
|
||||
element={<Status />}
|
||||
/>
|
||||
<Route
|
||||
{/* <Route
|
||||
path="/status/distributed/public/:url"
|
||||
element={<DistributedUptimeStatus />}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
<Route
|
||||
path="/server-unreachable"
|
||||
element={<ServerUnreachable />}
|
||||
/>
|
||||
<Route
|
||||
path="*"
|
||||
element={<NotFound />}
|
||||
|
||||
@@ -32,7 +32,7 @@ class NetworkService {
|
||||
|
||||
config.headers = {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
"Accept-Language": currentLanguage,
|
||||
"Accept-Language": currentLanguage === "gb" ? "en" : currentLanguage,
|
||||
...config.headers,
|
||||
};
|
||||
|
||||
@@ -45,6 +45,16 @@ class NetworkService {
|
||||
this.axiosInstance.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
// Handle network errors (server unreachable)
|
||||
if (error.code === "ERR_NETWORK") {
|
||||
// Navigate to server unreachable page
|
||||
navigate("/server-unreachable");
|
||||
// Return an empty resolved promise to stop the error propagation
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
// Handle authentication errors
|
||||
|
||||
if (error.response && error.response.status === 401) {
|
||||
dispatch(clearAuthState());
|
||||
dispatch(clearUptimeMonitorState());
|
||||
@@ -505,6 +515,8 @@ class NetworkService {
|
||||
return this.axiosInstance.get("/auth/users/superadmin");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* ************************************
|
||||
* Get all users
|
||||
@@ -921,7 +933,7 @@ class NetworkService {
|
||||
onOpen?.();
|
||||
};
|
||||
|
||||
this.eventSource.addEventListener("open", (e) => {});
|
||||
this.eventSource.addEventListener("open", (e) => { });
|
||||
|
||||
this.eventSource.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
@@ -16,7 +16,7 @@ Object.keys(translations).forEach((path) => {
|
||||
});
|
||||
|
||||
const savedLanguage = store.getState()?.ui?.language;
|
||||
const initialLanguage = savedLanguage || primaryLanguage;
|
||||
const initialLanguage = savedLanguage;
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
resources,
|
||||
|
||||
@@ -66,8 +66,8 @@ const credentials = joi.object({
|
||||
return lowercasedValue;
|
||||
})
|
||||
.messages({
|
||||
"string.empty": "Email is required",
|
||||
"string.email": "Must be a valid email address",
|
||||
"string.empty": "authRegisterEmailRequired",
|
||||
"string.email": "authRegisterEmailInvalid",
|
||||
}),
|
||||
password: passwordSchema,
|
||||
newPassword: passwordSchema,
|
||||
@@ -253,7 +253,9 @@ const statusPageValidation = joi.object({
|
||||
});
|
||||
const settingsValidation = joi.object({
|
||||
ttl: joi.number().required().messages({
|
||||
"string.empty": "TTL is required",
|
||||
"string.empty": "Please enter a value",
|
||||
"number.base": "Please enter a valid number",
|
||||
"any.required": "Please enter a value"
|
||||
}),
|
||||
pagespeedApiKey: joi.string().allow("").optional(),
|
||||
})
|
||||
|
||||
+45
-24
@@ -1,9 +1,11 @@
|
||||
{
|
||||
"dontHaveAccount": "Don't have account",
|
||||
"doNotHaveAccount": "Do not have an account?",
|
||||
"registerHere": "Register here",
|
||||
"email": "E-mail",
|
||||
"forgotPassword": "Forgot Password",
|
||||
"password": "password",
|
||||
"signUp": "Sign up",
|
||||
"password": "Password",
|
||||
"signUp": "Sign Up",
|
||||
"submit": "Submit",
|
||||
"title": "Title",
|
||||
"continue": "Continue",
|
||||
@@ -15,9 +17,11 @@
|
||||
"authForgotPasswordTitle": "Forgot password?",
|
||||
"authForgotPasswordResetPassword": "Reset password",
|
||||
"createPassword": "Create your password",
|
||||
"createAPassword": "Create a password",
|
||||
"createAPassword": "Password",
|
||||
"authRegisterAlreadyHaveAccount": "Already have an account?",
|
||||
"authRegisterLoginLink": "Log In",
|
||||
"commonAppName": "Checkmate",
|
||||
"welcomeBack": "Welcome back! You're successfully logged in.",
|
||||
"authLoginEnterEmail": "Enter your email",
|
||||
"authRegisterTitle": "Create an account",
|
||||
"authRegisterStepOneTitle": "Create your account",
|
||||
@@ -40,7 +44,7 @@
|
||||
"authSetNewPasswordDescription": "Your new password must be different from previously used passwords.",
|
||||
"authSetNewPasswordNewPassword": "New password",
|
||||
"authSetNewPasswordConfirmPassword": "Confirm password",
|
||||
"confirmPassword": "Confirm your password",
|
||||
"confirmPassword": "Re-enter password to confirm",
|
||||
"authSetNewPasswordResetPassword": "Reset password",
|
||||
"authSetNewPasswordBackTo": "Back to",
|
||||
"authPasswordMustBeAtLeast": "Must be at least",
|
||||
@@ -51,7 +55,10 @@
|
||||
"authPasswordUpperCharacter": "one upper character",
|
||||
"authPasswordLowerCharacter": "one lower character",
|
||||
"authPasswordConfirmAndPassword": "Confirm password and password",
|
||||
"authPasswordMustMatch": "must match",
|
||||
"authPasswordMustMatch": "Passwords must match",
|
||||
"validationNameRequired": "Please enter your name",
|
||||
"validationNameTooLong": "Name should be less than 50 characters",
|
||||
"validationNameInvalidCharacters": "Please use only letters, spaces, apostrophes, or hyphens",
|
||||
"authRegisterCreateAccount": "Create your account to get started",
|
||||
"authRegisterCreateSuperAdminAccount": "Create your super admin account to get started",
|
||||
"authRegisterSignUpWithEmail": "Create super admin account",
|
||||
@@ -60,7 +67,7 @@
|
||||
"distributedStatusSubHeaderText": "Powered by millions devices worldwide, view a system performance by global region, country or city",
|
||||
"settingsGeneralSettings": "General settings",
|
||||
"settingsDisplayTimezone": "Display timezone",
|
||||
"settingsDisplayTimezoneDescription": "The timezone of the dashboard you publicly display.",
|
||||
"settingsDisplayTimezoneDescription": "Select the timezone used to display dates and times throughout the application.",
|
||||
"settingsAppearance": "Appearance",
|
||||
"settingsAppearanceDescription": "Switch between light and dark mode, or change user interface language",
|
||||
"settingsThemeMode": "Theme Mode",
|
||||
@@ -69,23 +76,25 @@
|
||||
"settingsDistributedUptimeDescription": "Enable/disable distributed uptime monitoring.",
|
||||
"settingsEnabled": "Enabled",
|
||||
"settingsDisabled": "Disabled",
|
||||
"settingsHistoryAndMonitoring": "History and monitoring",
|
||||
"settingsHistoryAndMonitoringDescription": "Define here for how long you want to keep the data. You can also remove all past data.",
|
||||
"settingsHistoryAndMonitoring": "History of monitoring",
|
||||
"settingsHistoryAndMonitoringDescription": "Define how long you want to retain historical data. You can also clear all existing data.",
|
||||
"settingsTTLLabel": "The days you want to keep monitoring history.",
|
||||
"settingsTTLOptionalLabel": "0 for infinite",
|
||||
"settingsClearAllStats": "Clear all stats. This is irreversible.",
|
||||
"settingsClearAllStatsButton": "Clear all stats",
|
||||
"settingsClearAllStatsDialogTitle": "Do you want to clear all stats?",
|
||||
"settingsClearAllStatsDialogDescription": "Once deleted, your monitors cannot be retrieved.",
|
||||
"settingsClearAllStatsDialogDescription": "Once removed, the monitoring history and stats cannot be retrieved.",
|
||||
"settingsClearAllStatsDialogConfirm": "Yes, clear all stats",
|
||||
"settingsDemoMonitors": "Demo monitors",
|
||||
"settingsDemoMonitorsDescription": "Here you can add and remove demo monitors.",
|
||||
"settingsAddDemoMonitors": "Add demo monitors",
|
||||
"settingsDemoMonitorsDescription": "Add sample monitors for demonstration purposes.",
|
||||
"settingsAddDemoMonitors": "Adding demo monitors",
|
||||
"settingsAddDemoMonitorsButton": "Add demo monitors",
|
||||
"settingsRemoveAllMonitors": "Remove all monitors",
|
||||
"settingsSystemReset": "System reset",
|
||||
"settingsSystemResetDescription": "Remove all monitors from your system.",
|
||||
"settingsRemoveAllMonitors": "Removing all monitors",
|
||||
"settingsRemoveAllMonitorsButton": "Remove all monitors",
|
||||
"settingsRemoveAllMonitorsDialogTitle": "Do you want to remove all monitors?",
|
||||
"settingsRemoveAllMonitorsDialogConfirm": "Yes, clear all monitors",
|
||||
"settingsRemoveAllMonitorsDialogConfirm": "Yes, remove all monitors",
|
||||
"settingsWallet": "Wallet",
|
||||
"settingsWalletDescription": "Connect your wallet here. This is required for the Distributed Uptime monitor to connect to multiple nodes globally.",
|
||||
"settingsAbout": "About",
|
||||
@@ -99,18 +108,26 @@
|
||||
"settingsFailedToAddDemoMonitors": "Failed to add demo monitors",
|
||||
"settingsMonitorsDeleted": "Successfully deleted all monitors",
|
||||
"settingsFailedToDeleteMonitors": "Failed to delete all monitors",
|
||||
"backendUnreachable": "Server Connection Error",
|
||||
"backendUnreachableMessage": "We're unable to connect to the server. Please check your internet connection or verify your deployment configuration if the problem persists.",
|
||||
"backendUnreachableError": "Cannot connect to the server. Please try again later.",
|
||||
"retryConnection": "Retry connection",
|
||||
"retryingConnection": "Connecting...",
|
||||
"backendReconnected": "Successfully reconnected to the server.",
|
||||
"backendStillUnreachable": "Server is still unreachable. Please try again later.",
|
||||
"backendConnectionError": "Error connecting to the server. Please check your network connection.",
|
||||
"starPromptTitle": "Star Checkmate",
|
||||
"starPromptDescription": "See the latest releases and help grow the community on GitHub",
|
||||
"https": "HTTPS",
|
||||
"http": "HTTP",
|
||||
"monitor": "monitor",
|
||||
"aboutus": "About Us",
|
||||
"signUP": "Sign Up",
|
||||
|
||||
"now": "Now",
|
||||
"delete": "Delete",
|
||||
"configure": "Configure",
|
||||
"networkError": "Network error",
|
||||
"responseTime": "Response time:",
|
||||
"responseTime": "Response time",
|
||||
"ms": "ms",
|
||||
"bar": "Bar",
|
||||
"area": "Area",
|
||||
@@ -386,20 +403,22 @@
|
||||
"DragandDrop": "drag and drop",
|
||||
"MaxSize": "Maximum Size",
|
||||
"SupportedFormats": "Supported formats",
|
||||
"FirstName": "First name",
|
||||
"LastName": "Last name",
|
||||
"EmailDescriptionText": "This is your current email address — it cannot be changed.",
|
||||
"YourPhoto": "Your photo",
|
||||
"FirstName": "Name",
|
||||
"LastName": "Surname",
|
||||
"EmailDescriptionText": "Your current email—it cannot be changed.",
|
||||
"YourPhoto": "Profile photo",
|
||||
"PhotoDescriptionText": "This photo will be displayed in your profile page.",
|
||||
"save": "Save",
|
||||
"DeleteAccount": "Delete account",
|
||||
"DeleteDescriptionText": "Note that deleting your account will remove all data from the server. This is permanent and non-recoverable.",
|
||||
"DeleteAccountWarning": "If you delete your account, you will no longer be able to sign in, and all of your data will be deleted. Deleting your account is permanent and non-recoverable action.",
|
||||
"DeleteWarningTitle": "Really delete this account?",
|
||||
"DeleteAccountTitle": "Remove account",
|
||||
"DeleteAccountButton": "Remove account",
|
||||
"DeleteDescriptionText": "This will remove the account and all associated data from the server. This isn't reversible.",
|
||||
"DeleteAccountWarning": "Removing your account means you won't be able to sign in again and all your data will be removed. This isn't reversible.",
|
||||
"DeleteWarningTitle": "Really remove this account?",
|
||||
"authRegisterFirstName": "Name",
|
||||
"authRegisterLastName": "Surname",
|
||||
"authRegisterEmail": "Email",
|
||||
"authRegisterEmailRequired": "Email is required",
|
||||
"authRegisterEmailRequired": "To continue, please enter your email address",
|
||||
"authRegisterEmailInvalid": "Please enter a valid email address",
|
||||
"bulkImport": {
|
||||
"title": "Bulk Import",
|
||||
"selectFileTips": "Select CSV file to upload",
|
||||
@@ -411,6 +430,7 @@
|
||||
"noFileSelected": "No file selected",
|
||||
"fallbackPage": "Import a file to upload a list of servers in bulk"
|
||||
},
|
||||
"publicLink": "Public link",
|
||||
"maskedPageSpeedKeyPlaceholder": "*************************************",
|
||||
"pageSpeedApiKeyFieldTitle": "Google PageSpeed API key",
|
||||
"pageSpeedApiKeyFieldLabel": "PageSpeed API key",
|
||||
@@ -418,3 +438,4 @@
|
||||
"pageSpeedApiKeyFieldResetLabel": "API key is set. Click Reset to change it.",
|
||||
"reset": "Reset"
|
||||
}
|
||||
|
||||
|
||||
+43
-17
@@ -1,8 +1,10 @@
|
||||
{
|
||||
"dontHaveAccount": "Нет аккаунта",
|
||||
"doNotHaveAccount": "У вас нет учетной записи?",
|
||||
"registerHere": "Зарегистрироваться здесь",
|
||||
"email": "Почта",
|
||||
"forgotPassword": "Забыли пароль",
|
||||
"password": "пароль",
|
||||
"password": "Пароль",
|
||||
"signUp": "Зарегистрироваться",
|
||||
"submit": "Подтвердить",
|
||||
"title": "Название",
|
||||
@@ -15,9 +17,11 @@
|
||||
"authForgotPasswordTitle": "Забыли пароль?",
|
||||
"authForgotPasswordResetPassword": "Сбросить пароль",
|
||||
"createPassword": "Создайте свой пароль",
|
||||
"createAPassword": "Создайте пароль",
|
||||
"createAPassword": "Пароль",
|
||||
"authRegisterAlreadyHaveAccount": "Уже есть аккаунт?",
|
||||
"authRegisterLoginLink": "Войти",
|
||||
"commonAppName": "Checkmate",
|
||||
"welcomeBack": "Добро пожаловать обратно! Вы успешно вошли в систему.",
|
||||
"authLoginEnterEmail": "Введите свой email",
|
||||
"authRegisterTitle": "Создать аккаунт",
|
||||
"authRegisterStepOneTitle": "Создайте свой аккаут",
|
||||
@@ -40,7 +44,8 @@
|
||||
"authSetNewPasswordDescription": "Ваш новый пароль должен отличаться от ранее использованных паролей.",
|
||||
"authSetNewPasswordNewPassword": "Новый пароль",
|
||||
"authSetNewPasswordConfirmPassword": "Подтвердите пароль",
|
||||
"confirmPassword": "Подтвердите ваш пароль",
|
||||
"confirmPassword": "Введите пароль еще раз для подтверждения",
|
||||
"confirmNewPasswordPlaceholder": "Подтвердите ваш новый пароль",
|
||||
"authSetNewPasswordResetPassword": "Сбросить пароль",
|
||||
"authSetNewPasswordBackTo": "Назад к",
|
||||
"authPasswordMustBeAtLeast": "Должно быть как минимум",
|
||||
@@ -51,7 +56,7 @@
|
||||
"authPasswordUpperCharacter": "один верхний символ",
|
||||
"authPasswordLowerCharacter": "один нижний символ",
|
||||
"authPasswordConfirmAndPassword": "Подтвердите пароль и пароль",
|
||||
"authPasswordMustMatch": "должен совпадать",
|
||||
"authPasswordMustMatch": "Пароли должны совпадать",
|
||||
"authRegisterCreateAccount": "Создайте свою учетную запись, чтобы начать",
|
||||
"authRegisterCreateSuperAdminAccount": "Создайте учетную запись суперадминистратора, чтобы начать работу",
|
||||
"authRegisterSignUpWithEmail": "Создать учетную запись суперадминистратора",
|
||||
@@ -59,12 +64,13 @@
|
||||
"authRegisterFirstName": "Имя",
|
||||
"authRegisterLastName": "Фамилия",
|
||||
"authRegisterEmail": "Эл. почта",
|
||||
"authRegisterEmailRequired": "Эл. почта обязательна",
|
||||
"authRegisterEmailRequired": "Чтобы продолжить, пожалуйста, введите ваш адрес электронной почты",
|
||||
"authRegisterEmailInvalid": "Пожалуйста, введите корректный адрес электронной почты",
|
||||
"distributedStatusHeaderText": "Охват реального времени и реального устройства",
|
||||
"distributedStatusSubHeaderText": "Работает на миллионах устройств по всему миру, просматривайте производительность системы по глобальному региону, стране или городу",
|
||||
"settingsGeneralSettings": "Общие настройки",
|
||||
"settingsDisplayTimezone": "Отображать часовой пояс",
|
||||
"settingsDisplayTimezoneDescription": "Часовой пояс панели мониторинга, которую вы публично отображаете.",
|
||||
"settingsDisplayTimezoneDescription": "Выберите часовой пояс, используемый для отображения дат и времени в приложении.",
|
||||
"settingsAppearance": "Внешний вид",
|
||||
"settingsAppearanceDescription": "Переключение между светлым и темным режимом или изменение языка пользовательского интерфейса",
|
||||
"settingsThemeMode": "Тема",
|
||||
@@ -73,23 +79,25 @@
|
||||
"settingsDistributedUptimeDescription": "Включить/выключить distributed uptime monitoring.",
|
||||
"settingsEnabled": "Включено",
|
||||
"settingsDisabled": "Выключено",
|
||||
"settingsHistoryAndMonitoring": "История и мониторинг",
|
||||
"settingsHistoryAndMonitoringDescription": "Определите здесь, как долго вы хотите хранить данные. Вы также можете удалить все прошлые данные.",
|
||||
"settingsHistoryAndMonitoring": "История мониторинга",
|
||||
"settingsHistoryAndMonitoringDescription": "Определите, как долго вы хотите хранить исторические данные. Вы также можете очистить все существующие данные.",
|
||||
"settingsTTLLabel": "Дни, за которыми вы хотите следить.",
|
||||
"settingsTTLOptionalLabel": "0 для бесконечности",
|
||||
"settingsClearAllStats": "Очистить всю статистику. Это необратимо.",
|
||||
"settingsClearAllStatsButton": "Очистить всю статистику",
|
||||
"settingsClearAllStatsDialogTitle": "Хотите очистить всю статистику?",
|
||||
"settingsClearAllStatsDialogDescription": "После удаления ваши мониторы не могут быть восстановлены.",
|
||||
"settingsClearAllStatsDialogDescription": "После удаления история мониторинга и статистика не могут быть восстановлены.",
|
||||
"settingsClearAllStatsDialogConfirm": "Да, очистить всю статистику",
|
||||
"settingsDemoMonitors": "Демо мониторы",
|
||||
"settingsDemoMonitorsDescription": "Здесь вы можете добавлять и удалять демонстрационные мониторы.",
|
||||
"settingsAddDemoMonitors": "Добавьте демонстрационные мониторы",
|
||||
"settingsDemoMonitorsDescription": "Добавьте примеры мониторов для демонстрации.",
|
||||
"settingsAddDemoMonitors": "Добавление демонстрационных мониторов",
|
||||
"settingsAddDemoMonitorsButton": "Добавьте демонстрационные мониторы",
|
||||
"settingsRemoveAllMonitors": "Удалить все демонстрационные мониторы",
|
||||
"settingsRemoveAllMonitorsButton": "Удалить все демонстрационные мониторы",
|
||||
"settingsSystemReset": "Сброс системы",
|
||||
"settingsSystemResetDescription": "Удалить все мониторы из вашей системы.",
|
||||
"settingsRemoveAllMonitors": "Удаление всех мониторов",
|
||||
"settingsRemoveAllMonitorsButton": "Удалить все мониторы",
|
||||
"settingsRemoveAllMonitorsDialogTitle": "Хотите удалить все мониторы?",
|
||||
"settingsRemoveAllMonitorsDialogConfirm": "Да, очистить все мониторы",
|
||||
"settingsRemoveAllMonitorsDialogConfirm": "Да, удалить все мониторы",
|
||||
"settingsWallet": "Кошелёк",
|
||||
"settingsWalletDescription": "Подключите свой кошелек здесь. Это необходимо для того, чтобы монитор Distributed Uptime мог подключиться к нескольким узлам по всему миру.",
|
||||
"settingsAbout": "О",
|
||||
@@ -99,22 +107,40 @@
|
||||
"settingsFailedToSave": "Не удалось сохранить настройки",
|
||||
"settingsStatsCleared": "Статистика успешно очищена",
|
||||
"settingsFailedToClearStats": "Не удалось очистить статистику",
|
||||
"FirstName": "Имя",
|
||||
"LastName": "Фамилия",
|
||||
"YourPhoto": "Фото профиля",
|
||||
"PhotoDescriptionText": "Это фото будет отображаться на странице вашего профиля.",
|
||||
"EmailDescriptionText": "Ваш текущий email—его нельзя изменить.",
|
||||
"DeleteAccountTitle": "Удалить аккаунт",
|
||||
"DeleteAccountButton": "Удалить аккаунт",
|
||||
"DeleteDescriptionText": "Это удалит аккаунт и все связанные данные с сервера. Это необратимо.",
|
||||
"DeleteAccountWarning": "Удаление аккаунта означает, что вы не сможете снова войти в систему, и все ваши данные будут удалены. Это необратимо.",
|
||||
"DeleteWarningTitle": "Действительно удалить этот аккаунт?",
|
||||
"settingsDemoMonitorsAdded": "Успешно добавлены демонстрационные мониторы",
|
||||
"settingsFailedToAddDemoMonitors": "Не удалось добавить демонстрационные мониторы",
|
||||
"settingsMonitorsDeleted": "Успешно удалены все мониторы",
|
||||
"settingsFailedToDeleteMonitors": "Не удалось удалить все мониторы",
|
||||
"backendUnreachable": "Ошибка подключения к серверу",
|
||||
"backendUnreachableMessage": "Мы не можем подключиться к серверу. Пожалуйста, проверьте ваше интернет-соединение или проверьте конфигурацию развертывания, если проблема не устраняется.",
|
||||
"backendUnreachableError": "Невозможно подключиться к серверу. Пожалуйста, повторите попытку позже.",
|
||||
"retryConnection": "Повторить подключение",
|
||||
"retryingConnection": "Подключение...",
|
||||
"backendReconnected": "Успешно восстановлено подключение к серверу.",
|
||||
"backendStillUnreachable": "Сервер по-прежнему недоступен. Пожалуйста, повторите попытку позже.",
|
||||
"backendConnectionError": "Ошибка подключения к серверу. Пожалуйста, проверьте ваше сетевое подключение.",
|
||||
"starPromptTitle": "Star Checkmate",
|
||||
"starPromptDescription": "Ознакомьтесь с последними релизами и помогите развить сообщество на GitHub",
|
||||
"https": "HTTPS",
|
||||
"http": "HTTP",
|
||||
"monitor": "монитор",
|
||||
"aboutus": "О Нас",
|
||||
"signUP": "Зарегистрироваться",
|
||||
|
||||
"now": "Сейчас",
|
||||
"delete": "Удалить",
|
||||
"configure": "Настроить",
|
||||
"networkError": "Ошибка сети",
|
||||
"responseTime": "Время ответа:",
|
||||
"responseTime": "Время ответа",
|
||||
"ms": "мс",
|
||||
"bar": "Bar",
|
||||
"area": "Area",
|
||||
@@ -368,4 +394,4 @@
|
||||
"pageSpeedWarning": "Предупреждение: Вы не добавили ключ API Google PageSpeed. Без него монитор PageSpeed не будет работать.",
|
||||
"pageSpeedLearnMoreLink": "Нажмите здесь, чтобы узнать",
|
||||
"pageSpeedAddApiKey": "как добавить ваш ключ API."
|
||||
}
|
||||
}
|
||||
+43
-26
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"dontHaveAccount": "Hesabınız yok mu",
|
||||
"doNotHaveAccount": "Hesabınız yok mu?",
|
||||
"registerHere": "Buradan kayıt olun",
|
||||
"email": "E-posta",
|
||||
"forgotPassword": "Parolamı unuttum",
|
||||
"password": "Parola",
|
||||
@@ -15,9 +17,11 @@
|
||||
"authForgotPasswordTitle": "Parolanızı mı unuttunuz?",
|
||||
"authForgotPasswordResetPassword": "Parola sıfırla",
|
||||
"createPassword": "Parolanızı oluşturun",
|
||||
"createAPassword": "Bir parola oluşturun",
|
||||
"createAPassword": "Parola",
|
||||
"authRegisterAlreadyHaveAccount": "Zaten hesabınız var mı?",
|
||||
"authRegisterLoginLink": "Giriş Yap",
|
||||
"commonAppName": "Checkmate",
|
||||
"welcomeBack": "Tekrar hoş geldiniz! Başarıyla giriş yaptınız.",
|
||||
"authLoginEnterEmail": "E-posta adresinizi girin",
|
||||
"authRegisterTitle": "Hesap oluştur",
|
||||
"authRegisterStepOneTitle": "Hesabınızı oluşturun",
|
||||
@@ -40,7 +44,8 @@
|
||||
"authSetNewPasswordDescription": "Yeni şifreniz daha önce kullanılan şifrelerden farklı olmalıdır.",
|
||||
"authSetNewPasswordNewPassword": "Yeni şifre",
|
||||
"authSetNewPasswordConfirmPassword": "Parolayı onayla",
|
||||
"confirmPassword": "Parolanızı onaylayın",
|
||||
"confirmPassword": "Onaylamak için parolayı tekrar girin",
|
||||
"confirmNewPasswordPlaceholder": "Yeni parolanızı onaylayın",
|
||||
"authSetNewPasswordResetPassword": "Parola sıfırla",
|
||||
"authSetNewPasswordBackTo": "Geri dön",
|
||||
"authPasswordMustBeAtLeast": "En az",
|
||||
@@ -51,7 +56,7 @@
|
||||
"authPasswordUpperCharacter": "bir büyük harf",
|
||||
"authPasswordLowerCharacter": "bir küçük harf",
|
||||
"authPasswordConfirmAndPassword": "Onay şifresi ve şifre",
|
||||
"authPasswordMustMatch": "eşleşmelidir",
|
||||
"authPasswordMustMatch": "Parolalar eşleşmelidir",
|
||||
"authRegisterCreateAccount": "Hesap oluşturmak için devam et",
|
||||
"authRegisterCreateSuperAdminAccount": "Super admin hesabınızı oluşturmak için devam edin",
|
||||
"authRegisterSignUpWithEmail": "E-posta ile kayıt ol",
|
||||
@@ -60,7 +65,7 @@
|
||||
"distributedStatusSubHeaderText": "Dünya çapında milyonlarca cihaz tarafından desteklenen sistem performansını küresel bölgeye, ülkeye veya şehre göre görüntüleyin",
|
||||
"settingsGeneralSettings": "Genel ayarlar",
|
||||
"settingsDisplayTimezone": "Görüntüleme saat dilimi",
|
||||
"settingsDisplayTimezoneDescription": "Herkese açık olarak görüntülediğiniz kontrol panelinin saat dilimi.",
|
||||
"settingsDisplayTimezoneDescription": "Uygulama genelinde tarih ve saatlerin görüntülenmesi için kullanılacak saat dilimini seçin.",
|
||||
"settingsAppearance": "Görünüm",
|
||||
"settingsAppearanceDescription": "Açık ve koyu mod arasında geçiş yapın veya kullanıcı arayüzü dilini değiştirin",
|
||||
"settingsThemeMode": "Tema",
|
||||
@@ -69,23 +74,25 @@
|
||||
"settingsDistributedUptimeDescription": "Dağıtılmış çalışma süresi izlemeyi etkinleştirin/devre dışı bırakın.",
|
||||
"settingsEnabled": "Etkin",
|
||||
"settingsDisabled": "Devre dışı",
|
||||
"settingsHistoryAndMonitoring": "Geçmiş ve izleme",
|
||||
"settingsHistoryAndMonitoringDescription": "Verileri ne kadar süreyle saklamak istediğinizi burada tanımlayın. Ayrıca tüm geçmiş verileri kaldırabilirsiniz.",
|
||||
"settingsHistoryAndMonitoring": "İzleme geçmişi",
|
||||
"settingsHistoryAndMonitoringDescription": "Geçmiş verileri ne kadar süre saklamak istediğinizi tanımlayın. Ayrıca mevcut tüm verileri temizleyebilirsiniz.",
|
||||
"settingsTTLLabel": "İzleme geçmişini saklamak istediğiniz gün sayısı.",
|
||||
"settingsTTLOptionalLabel": "Sınırsız için 0",
|
||||
"settingsClearAllStats": "Tüm istatistikleri temizle. Bu geri alınamaz.",
|
||||
"settingsClearAllStatsButton": "Tüm istatistikleri temizle",
|
||||
"settingsClearAllStatsDialogTitle": "Tüm istatistikleri temizlemek istiyor musunuz?",
|
||||
"settingsClearAllStatsDialogDescription": "Silindikten sonra, monitörleriniz geri alınamaz.",
|
||||
"settingsClearAllStatsDialogDescription": "Kaldırıldıktan sonra, izleme geçmişi ve istatistikleri geri alınamaz.",
|
||||
"settingsClearAllStatsDialogConfirm": "Evet, tüm istatistikleri temizle",
|
||||
"settingsDemoMonitors": "Demo monitörler",
|
||||
"settingsDemoMonitorsDescription": "Burada demo monitörler ekleyebilir ve kaldırabilirsiniz.",
|
||||
"settingsAddDemoMonitors": "Demo monitörler ekle",
|
||||
"settingsDemoMonitorsDescription": "Gösterim amaçlı örnek monitörler ekleyin.",
|
||||
"settingsAddDemoMonitors": "Demo monitörler ekleniyor",
|
||||
"settingsAddDemoMonitorsButton": "Demo monitörler ekle",
|
||||
"settingsRemoveAllMonitors": "Tüm monitörleri kaldır",
|
||||
"settingsSystemReset": "Sistem sıfırlama",
|
||||
"settingsSystemResetDescription": "Sisteminizden tüm monitörleri kaldırın.",
|
||||
"settingsRemoveAllMonitors": "Tüm monitörler kaldırılıyor",
|
||||
"settingsRemoveAllMonitorsButton": "Tüm monitörleri kaldır",
|
||||
"settingsRemoveAllMonitorsDialogTitle": "Tüm monitörleri kaldırmak istiyor musunuz?",
|
||||
"settingsRemoveAllMonitorsDialogConfirm": "Evet, tüm monitörleri temizle",
|
||||
"settingsRemoveAllMonitorsDialogConfirm": "Evet, tüm monitörleri kaldır",
|
||||
"settingsWallet": "Cüzdan",
|
||||
"settingsWalletDescription": "Cüzdanınızı buradan bağlayın. Bu, Dağıtılmış Çalışma Süresi monitörünün küresel olarak birden çok düğüme bağlanması için gereklidir.",
|
||||
"settingsAbout": "Hakkında",
|
||||
@@ -99,18 +106,26 @@
|
||||
"settingsFailedToAddDemoMonitors": "Demo monitörler eklenemedi",
|
||||
"settingsMonitorsDeleted": "Tüm monitörler başarıyla silindi",
|
||||
"settingsFailedToDeleteMonitors": "Monitörler silinemedi",
|
||||
"backendUnreachable": "Sunucu Bağlantı Hatası",
|
||||
"backendUnreachableMessage": "Sunucuya bağlanamıyoruz. Lütfen internet bağlantınızı kontrol edin veya sorun devam ederse dağıtım yapılandırmanızı doğrulayın.",
|
||||
"backendUnreachableError": "Sunucuya bağlanılamıyor. Lütfen daha sonra tekrar deneyin.",
|
||||
"retryConnection": "Bağlantıyı Yeniden Dene",
|
||||
"retryingConnection": "Bağlanıyor...",
|
||||
"backendReconnected": "Sunucuya başarıyla yeniden bağlandı.",
|
||||
"backendStillUnreachable": "Sunucu hala erişilemez durumda. Lütfen daha sonra tekrar deneyin.",
|
||||
"backendConnectionError": "Sunucuya bağlanırken hata oluştu. Lütfen ağ bağlantınızı kontrol edin.",
|
||||
"starPromptTitle": "Checkmate yıldızla değerlendirin",
|
||||
"starPromptDescription": "En son sürümleri görün ve GitHub'daki topluluğun büyümesine yardımcı olun",
|
||||
"https": "HTTPS",
|
||||
"http": "HTTP",
|
||||
"monitor": "monitör",
|
||||
"aboutus": "Hakkımızda",
|
||||
"signUP": "Hesap Oluştur",
|
||||
|
||||
"now": "Şimdi",
|
||||
"delete": "Sil",
|
||||
"configure": "Yapılandır",
|
||||
"networkError": "Ağ hatası",
|
||||
"responseTime": "Yanıt süresi:",
|
||||
"responseTime": "Yanıt süresi",
|
||||
"ms": "ms",
|
||||
"bar": "Çubuk",
|
||||
"area": "Alan",
|
||||
@@ -386,20 +401,22 @@
|
||||
"DragandDrop": "",
|
||||
"MaxSize": "",
|
||||
"SupportedFormats": "",
|
||||
"FirstName": "",
|
||||
"LastName": "",
|
||||
"EmailDescriptionText": "",
|
||||
"YourPhoto": "",
|
||||
"PhotoDescriptionText": "",
|
||||
"FirstName": "Ad",
|
||||
"LastName": "Soyad",
|
||||
"EmailDescriptionText": "Mevcut e-postanız—değiştirilemez.",
|
||||
"YourPhoto": "Profil fotoğrafı",
|
||||
"PhotoDescriptionText": "Bu fotoğraf profil sayfanızda görüntülenecektir.",
|
||||
"save": "",
|
||||
"DeleteAccount": "",
|
||||
"DeleteDescriptionText": "",
|
||||
"DeleteAccountWarning": "",
|
||||
"DeleteWarningTitle": "",
|
||||
"authRegisterFirstName": "",
|
||||
"authRegisterLastName": "",
|
||||
"authRegisterEmail": "",
|
||||
"authRegisterEmailRequired": "",
|
||||
"DeleteAccountTitle": "Hesabı kaldır",
|
||||
"DeleteAccountButton": "Hesabı kaldır",
|
||||
"DeleteDescriptionText": "Bu, hesabı ve tüm ilişkili verileri sunucudan kaldıracaktır. Bu geri alınamaz.",
|
||||
"DeleteAccountWarning": "Hesabınızı kaldırmak, tekrar oturum açamayacağınız ve tüm verilerinizin kaldırılacağı anlamına gelir. Bu geri alınamaz.",
|
||||
"DeleteWarningTitle": "Bu hesabı gerçekten kaldırmak istiyor musunuz?",
|
||||
"authRegisterFirstName": "Ad",
|
||||
"authRegisterLastName": "Soyad",
|
||||
"authRegisterEmail": "E-posta",
|
||||
"authRegisterEmailRequired": "Devam etmek için lütfen e-posta adresinizi girin",
|
||||
"authRegisterEmailInvalid": "Lütfen geçerli bir e-posta adresi girin",
|
||||
"bulkImport": {
|
||||
"title": "",
|
||||
"selectFileTips": "",
|
||||
|
||||
@@ -5,7 +5,7 @@ cd "$(dirname "$0")"
|
||||
cd ../..
|
||||
|
||||
# Define service names and their corresponding Dockerfiles in parallel arrays
|
||||
services=("uptime_client" "uptime_database_mongo" "uptime_redis" "uptime_server")
|
||||
services=("uptime_client" "uptime_mongo" "uptime_redis" "uptime_server")
|
||||
dockerfiles=(
|
||||
"./docker/dev/client.Dockerfile"
|
||||
"./docker/dev/mongoDB.Dockerfile"
|
||||
|
||||
@@ -18,10 +18,13 @@ COPY ./client/package*.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY ./client .
|
||||
COPY ./client ./
|
||||
|
||||
RUN npm run build-dev
|
||||
RUN npm run build
|
||||
|
||||
RUN npm install -g serve
|
||||
FROM nginx:1.27.1-alpine
|
||||
|
||||
CMD ["serve","-s", "dist", "-l", "5173"]
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
COPY --from=build /app/env.sh /docker-entrypoint.d/env.sh
|
||||
RUN chmod +x /docker-entrypoint.d/env.sh
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -3,8 +3,12 @@ services:
|
||||
image: uptime_client:latest
|
||||
restart: always
|
||||
ports:
|
||||
- "5173:5173"
|
||||
|
||||
- "80:80"
|
||||
environment:
|
||||
UPTIME_APP_API_BASE_URL: "http://localhost:5000/api/v1"
|
||||
UPTIME_APP_CLIENT_HOST: "http://localhost"
|
||||
volumes:
|
||||
- ./nginx/conf.d:/etc/nginx/conf.d/
|
||||
depends_on:
|
||||
- server
|
||||
server:
|
||||
@@ -31,10 +35,17 @@ services:
|
||||
retries: 5
|
||||
start_period: 5s
|
||||
mongodb:
|
||||
image: uptime_database_mongo:latest
|
||||
image: uptime_mongo:latest
|
||||
restart: always
|
||||
command: ["mongod", "--quiet"]
|
||||
command: ["mongod", "--quiet", "--replSet", "rs0", "--bind_ip_all"]
|
||||
ports:
|
||||
- "27017:27017"
|
||||
volumes:
|
||||
- ./mongo/data:/data/db
|
||||
healthcheck:
|
||||
test: echo "try { rs.status() } catch (err) { rs.initiate({_id:'rs0',members:[{_id:0,host:'mongodb:27017'}]}) }" | mongosh --port 27017 --quiet
|
||||
interval: 5s
|
||||
timeout: 30s
|
||||
start_period: 0s
|
||||
start_interval: 1s
|
||||
retries: 30
|
||||
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
|
||||
server_name localhost; # Set server name to localhost
|
||||
server_tokens off;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# location /api/ {
|
||||
# proxy_pass http://server:5000/api/;
|
||||
# proxy_http_version 1.1;
|
||||
# proxy_set_header Host $host;
|
||||
# proxy_set_header X-Real-IP $remote_addr;
|
||||
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
# proxy_set_header X-Forwarded-Proto $scheme;
|
||||
# proxy_set_header Connection '';
|
||||
# chunked_transfer_encoding off;
|
||||
# proxy_buffering off;
|
||||
# proxy_cache off;
|
||||
# }
|
||||
|
||||
location /api-docs/ {
|
||||
proxy_pass http://server:5000/api-docs/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -4,6 +4,7 @@ services:
|
||||
restart: always
|
||||
environment:
|
||||
UPTIME_APP_API_BASE_URL: "http://localhost:5000/api/v1"
|
||||
UPTIME_APP_CLIENT_HOST: "http://localhost"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
|
||||
@@ -4,7 +4,7 @@ services:
|
||||
restart: always
|
||||
environment:
|
||||
UPTIME_APP_API_BASE_URL: "https://checkmate-demo.bluewavelabs.ca/api/v1"
|
||||
UPTIME_STATUS_PAGE_SUBDOMAIN_PREFIX: "http://uptimegenie.com/"
|
||||
UPTIME_APP_CLIENT_HOST: "https://checkmate-demo.bluewavelabs.ca"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
|
||||
@@ -4,7 +4,7 @@ services:
|
||||
restart: always
|
||||
environment:
|
||||
UPTIME_APP_API_BASE_URL: "https://checkmate-test.bluewavelabs.ca/api/v1"
|
||||
UPTIME_STATUS_PAGE_SUBDOMAIN_PREFIX: "http://uptimegenie.com/"
|
||||
UPTIME_APP_CLIENT_HOST: "https://checkmate-test.bluewavelabs.ca"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
|
||||
@@ -27,11 +27,12 @@ const SERVICE_NAME = "monitorController";
|
||||
import pkg from "papaparse";
|
||||
|
||||
class MonitorController {
|
||||
constructor(db, settingsService, jobQueue, stringService) {
|
||||
constructor(db, settingsService, jobQueue, stringService, emailService) {
|
||||
this.db = db;
|
||||
this.settingsService = settingsService;
|
||||
this.jobQueue = jobQueue;
|
||||
this.stringService = stringService;
|
||||
this.emailService = emailService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -537,18 +538,16 @@ class MonitorController {
|
||||
const monitorBeforeEdit = await this.db.getMonitorById(monitorId);
|
||||
|
||||
// Get notifications from the request body
|
||||
const notifications = req.body.notifications;
|
||||
|
||||
const notifications = req.body.notifications ?? [];
|
||||
const editedMonitor = await this.db.editMonitor(monitorId, req.body);
|
||||
|
||||
await this.db.deleteNotificationsByMonitorId(editedMonitor._id);
|
||||
|
||||
await Promise.all(
|
||||
notifications &&
|
||||
notifications.map(async (notification) => {
|
||||
notification.monitorId = editedMonitor._id;
|
||||
await this.db.createNotification(notification);
|
||||
})
|
||||
notifications.map(async (notification) => {
|
||||
notification.monitorId = editedMonitor._id;
|
||||
await this.db.createNotification(notification);
|
||||
})
|
||||
);
|
||||
|
||||
// Delete the old job(editedMonitor has the same ID as the old monitor)
|
||||
@@ -636,6 +635,50 @@ class MonitorController {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a test email to verify email delivery functionality.
|
||||
* @async
|
||||
* @param {Object} req - The Express request object.
|
||||
* @property {Object} req.body - The body of the request.
|
||||
* @property {string} req.body.to - The email address to send the test email to.
|
||||
* @param {Object} res - The Express response object.
|
||||
* @param {function} next - The next middleware function.
|
||||
* @returns {Object} The response object with a success status and the email delivery message ID.
|
||||
* @throws {Error} If there is an error while sending the test email.
|
||||
*/
|
||||
sendTestEmail = async (req, res, next) => {
|
||||
try {
|
||||
const { to } = req.body;
|
||||
|
||||
if (!to || typeof to !== "string") {
|
||||
return res.error({ msg: this.stringService.errorForValidEmailAddress });
|
||||
}
|
||||
|
||||
const subject = this.stringService.testEmailSubject;
|
||||
const context = { testName: "Monitoring System" };
|
||||
|
||||
const messageId = await this.emailService.buildAndSendEmail(
|
||||
"testEmailTemplate",
|
||||
context,
|
||||
to,
|
||||
subject
|
||||
);
|
||||
|
||||
if (!messageId) {
|
||||
return res.error({
|
||||
msg: "Failed to send test email.",
|
||||
});
|
||||
}
|
||||
|
||||
return res.success({
|
||||
msg: this.stringService.sendTestEmail,
|
||||
data: { messageId },
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "sendTestEmail"));
|
||||
}
|
||||
};
|
||||
|
||||
getMonitorsByTeamId = async (req, res, next) => {
|
||||
try {
|
||||
await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
|
||||
|
||||
@@ -7,10 +7,6 @@ const AppSettingsSchema = mongoose.Schema(
|
||||
required: true,
|
||||
default: "http://localhost:5000/api/v1",
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
default: "en",
|
||||
},
|
||||
logLevel: {
|
||||
type: String,
|
||||
default: "debug",
|
||||
|
||||
@@ -39,6 +39,10 @@ const MonitorSchema = mongoose.Schema(
|
||||
"distributed_test",
|
||||
],
|
||||
},
|
||||
ignoreTlsErrors: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
jsonPath: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
+19
-13
@@ -81,7 +81,9 @@ import ServiceRegistry from "./service/serviceRegistry.js";
|
||||
|
||||
import MongoDB from "./db/mongo/MongoDB.js";
|
||||
|
||||
// Redis Service and dependencies
|
||||
import IORedis from "ioredis";
|
||||
import RedisService from "./service/redisService.js";
|
||||
|
||||
import TranslationService from "./service/translationService.js";
|
||||
import languageMiddleware from "./middleware/languageMiddleware.js";
|
||||
@@ -113,21 +115,16 @@ const shutdown = async () => {
|
||||
method: "shutdown",
|
||||
});
|
||||
// flush Redis
|
||||
const settings =
|
||||
ServiceRegistry.get(SettingsService.SERVICE_NAME).getSettings() || {};
|
||||
const redisService = ServiceRegistry.get(RedisService.SERVICE_NAME);
|
||||
await redisService.flushall();
|
||||
|
||||
const { redisUrl } = settings;
|
||||
const redis = new IORedis(redisUrl, { maxRetriesPerRequest: null });
|
||||
|
||||
logger.info({ message: "Flushing Redis" });
|
||||
await redis.flushall();
|
||||
logger.info({ message: "Redis flushed" });
|
||||
process.exit(1);
|
||||
}, SHUTDOWN_TIMEOUT);
|
||||
try {
|
||||
server.close();
|
||||
await ServiceRegistry.get(JobQueue.SERVICE_NAME).obliterate();
|
||||
await ServiceRegistry.get(MongoDB.SERVICE_NAME).disconnect();
|
||||
await ServiceRegistry.get(RedisService.SERVICE_NAME).flushall();
|
||||
logger.info({ message: "Graceful shutdown complete" });
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
@@ -185,7 +182,13 @@ const startApp = async () => {
|
||||
stringService
|
||||
);
|
||||
|
||||
const jobQueue = new JobQueue(
|
||||
const redisService = await RedisService.createInstance({
|
||||
logger,
|
||||
IORedis,
|
||||
SettingsService: settingsService,
|
||||
});
|
||||
|
||||
const jobQueue = new JobQueue({
|
||||
db,
|
||||
statusService,
|
||||
networkService,
|
||||
@@ -194,8 +197,9 @@ const startApp = async () => {
|
||||
stringService,
|
||||
logger,
|
||||
Queue,
|
||||
Worker
|
||||
);
|
||||
Worker,
|
||||
redisService,
|
||||
});
|
||||
|
||||
// Register services
|
||||
ServiceRegistry.register(JobQueue.SERVICE_NAME, jobQueue);
|
||||
@@ -207,6 +211,7 @@ const startApp = async () => {
|
||||
ServiceRegistry.register(StatusService.SERVICE_NAME, statusService);
|
||||
ServiceRegistry.register(NotificationService.SERVICE_NAME, notificationService);
|
||||
ServiceRegistry.register(TranslationService.SERVICE_NAME, translationService);
|
||||
ServiceRegistry.register(RedisService.SERVICE_NAME, redisService);
|
||||
|
||||
await translationService.initialize();
|
||||
|
||||
@@ -231,7 +236,8 @@ const startApp = async () => {
|
||||
ServiceRegistry.get(MongoDB.SERVICE_NAME),
|
||||
ServiceRegistry.get(SettingsService.SERVICE_NAME),
|
||||
ServiceRegistry.get(JobQueue.SERVICE_NAME),
|
||||
ServiceRegistry.get(StringService.SERVICE_NAME)
|
||||
ServiceRegistry.get(StringService.SERVICE_NAME),
|
||||
ServiceRegistry.get(EmailService.SERVICE_NAME),
|
||||
);
|
||||
|
||||
const settingsController = new SettingsController(
|
||||
@@ -343,7 +349,7 @@ const startApp = async () => {
|
||||
app.use("/api/v1/queue", verifyJWT, queueRoutes.getRouter());
|
||||
app.use("/api/v1/distributed-uptime", distributedUptimeRoutes.getRouter());
|
||||
app.use("/api/v1/status-page", statusPageRoutes.getRouter());
|
||||
app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter());
|
||||
app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter());
|
||||
app.use("/api/v1/diagnostic", verifyJWT, diagnosticRoutes.getRouter());
|
||||
app.use("/api/v1/health", (req, res) => {
|
||||
res.json({
|
||||
|
||||
@@ -158,5 +158,8 @@
|
||||
"platformRequired": "Platform is required",
|
||||
"testNotificationFailed": "Failed to send test notification",
|
||||
"monitorUpAlert": "Uptime Alert: One of your monitors is back online.\n📌 Monitor: {monitorName}\n📅 Time: {time}\n⚠️ Status: UP\n📟 Status Code: {code}\n\u200B\n",
|
||||
"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"
|
||||
"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 Email from Monitoring System"
|
||||
}
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
import logger from "../utils/logger.js";
|
||||
|
||||
const languageMiddleware =
|
||||
(stringService, translationService, settingsService) => async (req, res, next) => {
|
||||
(stringService, translationService) => async (req, res, next) => {
|
||||
try {
|
||||
const settings = await settingsService.getSettings();
|
||||
|
||||
let language = settings && settings.language ? settings.language : null;
|
||||
|
||||
if (!language) {
|
||||
const acceptLanguage = req.headers["accept-language"] || "en";
|
||||
language = acceptLanguage.split(",")[0].slice(0, 2).toLowerCase();
|
||||
}
|
||||
const acceptLanguage = req.headers["accept-language"] || "en";
|
||||
const language = acceptLanguage.split(",")[0].slice(0, 2).toLowerCase();
|
||||
|
||||
translationService.setLanguage(language);
|
||||
stringService.setLanguage(language);
|
||||
|
||||
@@ -99,6 +99,12 @@ class MonitorRoutes {
|
||||
);
|
||||
|
||||
this.router.post("/seed", isAllowed(["superadmin"]), this.monitorController.seedDb);
|
||||
|
||||
this.router.post(
|
||||
"/test-email",
|
||||
isAllowed(["admin", "superadmin"]),
|
||||
this.monitorController.sendTestEmail
|
||||
);
|
||||
}
|
||||
|
||||
getRouter() {
|
||||
|
||||
@@ -67,6 +67,7 @@ class EmailService {
|
||||
serverIsUpTemplate: this.loadTemplate("serverIsUp"),
|
||||
passwordResetTemplate: this.loadTemplate("passwordReset"),
|
||||
hardwareIncidentTemplate: this.loadTemplate("hardwareIncident"),
|
||||
testEmailTemplate: this.loadTemplate("testEmailTemplate")
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import IORedis from "ioredis";
|
||||
|
||||
const QUEUE_NAMES = ["uptime", "pagespeed", "hardware", "distributed"];
|
||||
const SERVICE_NAME = "JobQueue";
|
||||
const JOBS_PER_WORKER = 5;
|
||||
@@ -18,7 +16,7 @@ const getSchedulerId = (monitor) => `scheduler:${monitor.type}:${monitor._id}`;
|
||||
class NewJobQueue {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
constructor(
|
||||
constructor({
|
||||
db,
|
||||
statusService,
|
||||
networkService,
|
||||
@@ -27,16 +25,14 @@ class NewJobQueue {
|
||||
stringService,
|
||||
logger,
|
||||
Queue,
|
||||
Worker
|
||||
) {
|
||||
const settings = settingsService.getSettings() || {};
|
||||
const { redisUrl } = settings;
|
||||
const connection = new IORedis(redisUrl, { maxRetriesPerRequest: null });
|
||||
Worker,
|
||||
redisService,
|
||||
}) {
|
||||
this.connection = redisService.getConnection();
|
||||
this.queues = {};
|
||||
this.workers = {};
|
||||
this.lastJobProcessedTime = {};
|
||||
|
||||
this.connection = connection;
|
||||
this.db = db;
|
||||
this.networkService = networkService;
|
||||
this.statusService = statusService;
|
||||
@@ -47,7 +43,7 @@ class NewJobQueue {
|
||||
this.stringService = stringService;
|
||||
|
||||
QUEUE_NAMES.forEach((name) => {
|
||||
const q = new Queue(name, { connection });
|
||||
const q = new Queue(name, { connection: this.connection });
|
||||
this.lastJobProcessedTime[q.name] = Date.now();
|
||||
q.on("error", (error) => {
|
||||
this.logger.error({
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import jmespath from "jmespath";
|
||||
import https from "https";
|
||||
|
||||
const SERVICE_NAME = "NetworkService";
|
||||
const UPROCK_ENDPOINT = "https://api.uprock.com/checkmate/push";
|
||||
|
||||
@@ -133,6 +135,7 @@ class NetworkService {
|
||||
name,
|
||||
teamId,
|
||||
type,
|
||||
ignoreTlsErrors,
|
||||
jsonPath,
|
||||
matchMethod,
|
||||
expectedValue,
|
||||
@@ -141,6 +144,12 @@ class NetworkService {
|
||||
|
||||
secret !== undefined && (config.headers = { Authorization: `Bearer ${secret}` });
|
||||
|
||||
if (ignoreTlsErrors === true) {
|
||||
config.httpsAgent = new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
}
|
||||
|
||||
const { response, responseTime, error } = await this.timeRequest(() =>
|
||||
this.axios.get(url, config)
|
||||
);
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
class RedisService {
|
||||
static SERVICE_NAME = "RedisService";
|
||||
|
||||
constructor({ logger, IORedis, SettingsService }) {
|
||||
this.logger = logger;
|
||||
this.IORedis = IORedis;
|
||||
this.SettingsService = SettingsService;
|
||||
this.connection = null;
|
||||
}
|
||||
|
||||
static async createInstance({ logger, IORedis, SettingsService }) {
|
||||
const instance = new RedisService({ logger, IORedis, SettingsService });
|
||||
await instance.connect();
|
||||
return instance;
|
||||
}
|
||||
|
||||
async connect() {
|
||||
const settings = this.SettingsService.getSettings();
|
||||
const { redisUrl } = settings;
|
||||
this.connection = new this.IORedis(redisUrl, {
|
||||
maxRetriesPerRequest: null,
|
||||
retryStrategy: (times) => {
|
||||
if (times >= 5) {
|
||||
throw new Error("Failed to connect to Redis");
|
||||
}
|
||||
this.logger.debug({
|
||||
message: "Retrying Redis connection",
|
||||
service: RedisService.SERVICE_NAME,
|
||||
details: { times },
|
||||
});
|
||||
return Math.min(times * 100, 2000);
|
||||
},
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
let errorOccurred = false;
|
||||
|
||||
this.connection.on("ready", () => {
|
||||
if (!errorOccurred) {
|
||||
this.logger.info({
|
||||
message: "Redis connection established",
|
||||
service: RedisService.SERVICE_NAME,
|
||||
});
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
this.connection.on("error", (err) => {
|
||||
errorOccurred = true;
|
||||
this.logger.error({
|
||||
message: "Redis connection error",
|
||||
service: RedisService.SERVICE_NAME,
|
||||
error: err,
|
||||
});
|
||||
setTimeout(() => reject(err), 5000);
|
||||
});
|
||||
});
|
||||
}
|
||||
async flushall() {
|
||||
this.logger.debug({
|
||||
message: "Flushing all Redis data",
|
||||
service: RedisService.SERVICE_NAME,
|
||||
});
|
||||
await this.connection.flushall();
|
||||
}
|
||||
|
||||
getConnection() {
|
||||
return this.connection;
|
||||
}
|
||||
}
|
||||
|
||||
export default RedisService;
|
||||
@@ -3,7 +3,6 @@ import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
const envConfig = {
|
||||
logLevel: process.env.LOG_LEVEL,
|
||||
language: process.env.LANGUAGE,
|
||||
clientHost: process.env.CLIENT_HOST,
|
||||
jwtSecret: process.env.JWT_SECRET,
|
||||
dbType: process.env.DB_TYPE,
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<mjml>
|
||||
<mj-body>
|
||||
<mj-section>
|
||||
<mj-column>
|
||||
<mj-text font-size="20px" font-family="Helvetica Neue">
|
||||
Hello!
|
||||
</mj-text>
|
||||
<mj-text font-size="16px" font-family="Helvetica Neue">
|
||||
This is a test email from the Monitoring System.
|
||||
</mj-text>
|
||||
<mj-text font-size="14px" font-family="Helvetica Neue">
|
||||
If you're receiving this, your email configuration is working correctly.
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
</mj-body>
|
||||
</mjml>
|
||||
+21
-1
@@ -1,4 +1,6 @@
|
||||
import { createLogger, format, transports } from "winston";
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
class Logger {
|
||||
constructor() {
|
||||
@@ -40,8 +42,10 @@ class Logger {
|
||||
}
|
||||
);
|
||||
|
||||
const logLevel = process.env.LOG_LEVEL || "info";
|
||||
|
||||
this.logger = createLogger({
|
||||
level: "info",
|
||||
level: logLevel,
|
||||
format: format.combine(format.timestamp()),
|
||||
transports: [
|
||||
new transports.Console({
|
||||
@@ -107,6 +111,22 @@ class Logger {
|
||||
stack: config.stack,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Logs a debug message.
|
||||
* @param {Object} config - The configuration object.
|
||||
* @param {string} config.message - The message to log.
|
||||
* @param {string} config.service - The service name.
|
||||
* @param {string} config.method - The method name.
|
||||
* @param {Object} config.details - Additional details.
|
||||
*/
|
||||
debug(config) {
|
||||
this.logger.debug(config.message, {
|
||||
service: config.service,
|
||||
method: config.method,
|
||||
details: config.details,
|
||||
stack: config.stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const logger = new Logger();
|
||||
|
||||
@@ -183,6 +183,7 @@ const createMonitorBodyValidation = joi.object({
|
||||
description: joi.string().required(),
|
||||
type: joi.string().required(),
|
||||
url: joi.string().required(),
|
||||
ignoreTlsErrors: joi.boolean().default(false),
|
||||
port: joi.number(),
|
||||
isActive: joi.boolean(),
|
||||
interval: joi.number(),
|
||||
@@ -207,6 +208,7 @@ const editMonitorBodyValidation = joi.object({
|
||||
interval: joi.number(),
|
||||
notifications: joi.array().items(joi.object()),
|
||||
secret: joi.string(),
|
||||
ignoreTlsErrors: joi.boolean(),
|
||||
jsonPath: joi.string().allow(""),
|
||||
expectedValue: joi.string().allow(""),
|
||||
matchMethod: joi.string(),
|
||||
|
||||
Reference in New Issue
Block a user