diff --git a/client/.env.production b/client/.env.production
index 2ae6d13cd..7a11848c0 100644
--- a/client/.env.production
+++ b/client/.env.production
@@ -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
\ No newline at end of file
diff --git a/client/src/Components/Fallback/index.jsx b/client/src/Components/Fallback/index.jsx
index 18fecd3dd..5b6da7062 100644
--- a/client/src/Components/Fallback/index.jsx
+++ b/client/src/Components/Fallback/index.jsx
@@ -1,6 +1,7 @@
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { Box, Button, Stack, Typography, Link } from "@mui/material";
+import { Link as RouterLink } from "react-router-dom";
import Skeleton from "../../assets/Images/create-placeholder.svg?react";
import SkeletonDark from "../../assets/Images/create-placeholder-dark.svg?react";
import Background from "../../assets/Images/background-grid.svg?react";
@@ -35,9 +36,8 @@ const Fallback = ({ title, checks, link = "/", isAdmin, vowelStart = false, show
<>
{t("pageSpeedWarning")} {" "}
{
const sx = {
maxWidth: maxWidth,
- "& .MuiOutlinedInput-root ": {
- "&:hover .MuiOutlinedInput-notchedOutline": {
- borderColor: theme.palette.primary.contrastText, // Adjust hover border color
- },
- "&.Mui-focused .MuiOutlinedInput-notchedOutline": {
- borderColor: theme.palette.accent.main, // Adjust focus border color
- },
- "&.Mui-disabled .MuiOutlinedInput-notchedOutline": {
- borderColor: theme.palette.primary.contrastText, // CAIO_REVIEW
- opacity: 0.5,
- },
- },
"& .MuiFormHelperText-root": {
position: "absolute",
@@ -116,6 +104,7 @@ const TextInput = forwardRef(
fontSize={"var(--env-var-font-size-medium)"}
color={theme.palette.primary.contrastTextSecondary}
fontWeight={500}
+ mb={theme.spacing(2)}
>
{label}
{isRequired && }
diff --git a/client/src/Components/StatusBoxes/index.jsx b/client/src/Components/StatusBoxes/index.jsx
index fedc74011..40ea2d182 100644
--- a/client/src/Components/StatusBoxes/index.jsx
+++ b/client/src/Components/StatusBoxes/index.jsx
@@ -20,8 +20,8 @@ const StatusBoxes = ({ shouldRender, flexWrap = "nowrap", children }) => {
direction="row"
flexWrap={flexWrap}
gap={theme.spacing(8)}
- justifyContent="space-between"
- display="flex"
+ justifyContent="flex-start"
+ display="flex"
>
{children}
@@ -30,6 +30,7 @@ const StatusBoxes = ({ shouldRender, flexWrap = "nowrap", children }) => {
StatusBoxes.propTypes = {
shouldRender: PropTypes.bool,
+ flexWrap: PropTypes.string,
children: PropTypes.node,
};
diff --git a/client/src/Components/TabPanels/Account/PasswordPanel.jsx b/client/src/Components/TabPanels/Account/PasswordPanel.jsx
index 6f2775cf1..474058e66 100644
--- a/client/src/Components/TabPanels/Account/PasswordPanel.jsx
+++ b/client/src/Components/TabPanels/Account/PasswordPanel.jsx
@@ -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 = () => {
{
spellCheck="false"
>
- {t('DeleteAccount')}
+ {t('DeleteAccountTitle')}
{
color="error"
onClick={() => setIsOpen("delete")}
>
- {t('DeleteAccount')}
+ {t('DeleteAccountButton')}
)}
@@ -367,7 +367,7 @@ const ProfilePanel = () => {
title={t('DeleteWarningTitle')}
description={t('DeleteAccountWarning')}
onCancel={() => setIsOpen("")}
- confirmationButtonLabel={t('DeleteAccount')}
+ confirmationButtonLabel={t('DeleteAccountButton')}
onConfirm={handleDeleteAccount}
isLoading={isLoading}
/>
diff --git a/client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js b/client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js
index 11cbcb36c..3ed2da13f 100644
--- a/client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js
+++ b/client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js
@@ -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,
diff --git a/client/src/Features/Settings/settingsSlice.js b/client/src/Features/Settings/settingsSlice.js
index 967858901..02724af02 100644
--- a/client/src/Features/Settings/settingsSlice.js
+++ b/client/src/Features/Settings/settingsSlice.js
@@ -6,6 +6,7 @@ const initialState = {
apiBaseUrl: "",
logLevel: "debug",
language: "gb",
+ pagespeedApiKey: "",
};
export const getAppSettings = createAsyncThunk(
@@ -33,6 +34,7 @@ export const updateAppSettings = createAsyncThunk(
try {
const parsedSettings = {
language: settings.language,
+ pagespeedApiKey: settings.pagespeedApiKey,
};
const res = await networkService.updateAppSettings({ settings: parsedSettings });
return res.data;
@@ -56,6 +58,7 @@ const handleGetSettingsFulfilled = (state, action) => {
state.apiBaseUrl = action.payload.data.apiBaseUrl;
state.logLevel = action.payload.data.logLevel;
state.language = action.payload.data.language;
+ state.pagespeedApiKey = action.payload.data.pagespeedApiKey;
};
const handleGetSettingsRejected = (state, action) => {
state.isLoading = false;
diff --git a/client/src/Hooks/inviteHooks.js b/client/src/Hooks/inviteHooks.js
index f8b44f0fd..705906efa 100644
--- a/client/src/Hooks/inviteHooks.js
+++ b/client/src/Hooks/inviteHooks.js
@@ -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();
diff --git a/client/src/Pages/Auth/Login/Components/EmailStep.jsx b/client/src/Pages/Auth/Login/Components/EmailStep.jsx
index ec5a4b2db..cea175da3 100644
--- a/client/src/Pages/Auth/Login/Components/EmailStep.jsx
+++ b/client/src/Pages/Auth/Login/Components/EmailStep.jsx
@@ -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}
/>
{
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`,
diff --git a/client/src/Pages/Auth/Login/Components/PasswordStep.jsx b/client/src/Pages/Auth/Login/Components/PasswordStep.jsx
index 0b8142161..a57246e23 100644
--- a/client/src/Pages/Auth/Login/Components/PasswordStep.jsx
+++ b/client/src/Pages/Auth/Login/Components/PasswordStep.jsx
@@ -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")}
diff --git a/client/src/Pages/Auth/Login/Login.jsx b/client/src/Pages/Auth/Login/Login.jsx
index 1337a8723..8ac831a40 100644
--- a/client/src/Pages/Auth/Login/Login.jsx
+++ b/client/src/Pages/Auth/Login/Login.jsx
@@ -9,7 +9,6 @@ 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";
@@ -25,11 +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;
@@ -45,21 +43,12 @@ 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) => {
@@ -94,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);
}
@@ -112,8 +103,8 @@ 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));
@@ -236,6 +227,31 @@ const Login = () => {
email={form.email}
errorEmail={errors.email}
/>
+
+ {/* Registration link */}
+
+
+ {t("doNotHaveAccount")}
+
+ navigate("/register")}
+ >
+ {t("registerHere")}
+
+
);
diff --git a/client/src/Pages/Auth/Register/Register.jsx b/client/src/Pages/Auth/Register/Register.jsx
index 45c866797..8017f306d 100644
--- a/client/src/Pages/Auth/Register/Register.jsx
+++ b/client/src/Pages/Auth/Register/Register.jsx
@@ -387,7 +387,7 @@ const Register = ({ isSuperAdmin }) => {
}}
sx={{ userSelect: "none", color: theme.palette.accent.main }}
>
- {t("authLoginTitle")}
+ {t("authRegisterLoginLink")}
diff --git a/client/src/Pages/Auth/Register/StepTwo/index.jsx b/client/src/Pages/Auth/Register/StepTwo/index.jsx
index d40ff161f..5f563b303 100644
--- a/client/src/Pages/Auth/Register/StepTwo/index.jsx
+++ b/client/src/Pages/Auth/Register/StepTwo/index.jsx
@@ -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}
/>
{
subHeading={
<>
{physicalCores}
- cores
+
+ {physicalCores === 1 ? "core" : "cores"}
+
>
}
/>
@@ -61,7 +63,9 @@ const InfraStatBoxes = ({ shouldRender, monitor }) => {
subHeading={
<>
{logicalCores}
- cores
+
+ {logicalCores === 1 ? "core" : "cores"}
+
>
}
/>
@@ -79,7 +83,7 @@ const InfraStatBoxes = ({ shouldRender, monitor }) => {
subHeading={
<>
{cpuTemperature.toFixed(2)}
- C
+ °C
>
}
/>
diff --git a/client/src/Pages/PageSpeed/Monitors/index.jsx b/client/src/Pages/PageSpeed/Monitors/index.jsx
index f10ba73d0..bd40aedff 100644
--- a/client/src/Pages/PageSpeed/Monitors/index.jsx
+++ b/client/src/Pages/PageSpeed/Monitors/index.jsx
@@ -21,6 +21,7 @@ const PageSpeed = () => {
const { t } = useTranslation();
const isAdmin = useIsAdmin();
const { user } = useSelector((state) => state.auth);
+ const { pagespeedApiKey } = useSelector((state) => state.settings);
const { isLoading, monitors, summary, networkError } = useMonitorsFetch({
teamId: user.teamId,
@@ -52,7 +53,7 @@ const PageSpeed = () => {
]}
link="/pagespeed/create"
isAdmin={isAdmin}
- showPageSpeedWarning={true}
+ showPageSpeedWarning={isAdmin && !pagespeedApiKey}
/>
);
}
@@ -62,7 +63,7 @@ const PageSpeed = () => {
{
+ 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 (
+
+
+
+
+
+ {/* Header with logo */}
+
+
+
+ Checkmate
+
+
+
+
+
+
+ .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),
+ },
+ },
+ }}
+ >
+
+
+
+
+
+
+ {t("backendUnreachableMessage", "The Checkmate server is not responding. Please check your deployment configuration or try again later.")}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default ServerUnreachable;
diff --git a/client/src/Pages/Settings/index.jsx b/client/src/Pages/Settings/index.jsx
index 8ffbd5fa1..554ee6857 100644
--- a/client/src/Pages/Settings/index.jsx
+++ b/client/src/Pages/Settings/index.jsx
@@ -6,6 +6,8 @@ import Select from "../../Components/Inputs/Select";
import { useIsAdmin } from "../../Hooks/useIsAdmin";
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,
@@ -54,8 +56,11 @@ const Settings = () => {
const [form, setForm] = useState({
enableDistributedUptime: distributedUptimeEnabled,
ttl: checkTTL ? (checkTTL / SECONDS_PER_DAY).toString() : 0,
+ pagespeedApiKey: "",
});
const [version, setVersion] = useState("unknown");
+ const [apiKeyFieldType, setApiKeyFieldType] = useState("password");
+ const [isApiKeySet, setIsApiKeySet] = useState(false);
const [errors, setErrors] = useState({});
const deleteStatsMonitorsInitState = { deleteMonitors: false, deleteStats: false };
const [isOpen, setIsOpen] = useState(deleteStatsMonitorsInitState);
@@ -80,6 +85,28 @@ const Settings = () => {
fetchLatestVersion();
}, []);
+ useEffect(() => {
+ dispatch(getAppSettings());
+ }, []);
+
+ const { pagespeedApiKey } = useSelector((state) => state.settings);
+
+ useEffect(() => {
+ if (pagespeedApiKey) {
+ setIsApiKeySet(true);
+ setForm((prev) => ({
+ ...prev,
+ pagespeedApiKey: t("maskedPageSpeedKeyPlaceholder"),
+ }));
+ } else {
+ setIsApiKeySet(false);
+ setForm((prev) => ({
+ ...prev,
+ pagespeedApiKey: "",
+ }));
+ }
+ }, [pagespeedApiKey]);
+
const handleChange = (event) => {
const { type, checked, value, id } = event.target;
@@ -91,12 +118,17 @@ const Settings = () => {
return;
}
+ let inputValue = value;
+ if (id === "ttl") {
+ inputValue = value.replace(/[^0-9]/g, "");
+ }
+
+ const updatedForm = { ...form, [id]: inputValue };
const { error } = settingsValidation.validate(
- { [id]: value },
- {
- abortEarly: false,
- }
+ updatedForm,
+ { abortEarly: false }
);
+
if (!error || error.details.length === 0) {
setErrors({});
} else {
@@ -107,12 +139,8 @@ const Settings = () => {
setErrors(newErrors);
logger.error("Validation errors:", error.details);
}
- let inputValue = value;
- id === "ttl" && (inputValue = value.replace(/[^0-9]/g, ""));
- setForm((prev) => ({
- ...prev,
- [id]: inputValue,
- }));
+
+ setForm(updatedForm);
};
// TODO Handle saving
@@ -125,7 +153,7 @@ const Settings = () => {
const updatedUser = { ...user, checkTTL: form.ttl };
const [userAction, settingsAction] = await Promise.all([
dispatch(update({ localData: updatedUser })),
- dispatch(updateAppSettings({ settings: { language: language } })),
+ dispatch(updateAppSettings({ settings: { language: language, pagespeedApiKey: form.pagespeedApiKey } })),
]);
if (userAction.payload.success && settingsAction.payload.success) {
@@ -187,6 +215,14 @@ const Settings = () => {
}
};
+ const handleResetApiKey = () => {
+ setIsApiKeySet(false);
+ setForm((prev) => ({
+ ...prev,
+ pagespeedApiKey: "",
+ }));
+ };
+
const languages = Object.keys(i18n.options.resources || {});
return (
@@ -277,6 +313,43 @@ const Settings = () => {
)} */}
+
+
+ {t("pageSpeedApiKeyFieldTitle")}
+
+ {t("pageSpeedApiKeyFieldDescription")}
+
+
+
+
+ )
+ }
+ />
+ {isApiKeySet && (
+
+ {t("pageSpeedApiKeyFieldResetLabel")}
+
+
+ )}
+
+
{/* {isAdmin && (
@@ -348,14 +421,15 @@ const Settings = () => {
)}
{isAdmin && (
-
-
- {t("settingsDemoMonitors")}
-
- {t("settingsDemoMonitorsDescription")}
-
-
-
+ <>
+ {/* Demo Monitors Section */}
+
+
+ {t("settingsDemoMonitors")}
+
+ {t("settingsDemoMonitorsDescription")}
+
+
{t("settingsAddDemoMonitors")}
+
+
+ {/* System Reset Section */}
+
+
+ {t("settingsSystemReset")}
+
+ {t("settingsSystemResetDescription")}
+
+
{t("settingsRemoveAllMonitors")}
-
-
+
diff --git a/client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx b/client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx
index 66c021aa2..ddc7e223b 100644
--- a/client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx
+++ b/client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx
@@ -61,18 +61,20 @@ const UptimeStatusBoxes = ({
>
}
/>
-
- {certificateExpiry}
-
- }
- />
+ {monitor?.type === "http" && (
+
+ {certificateExpiry}
+
+ }
+ />
+ )}
);
};
diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx
index f5a1adcae..e334af912 100644
--- a/client/src/Routes/index.jsx
+++ b/client/src/Routes/index.jsx
@@ -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";
@@ -285,6 +288,10 @@ const Routes = () => {
element={}
/> */}
+ }
+ />
}
diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js
index 76ad659ec..3be13bff9 100644
--- a/client/src/Utils/NetworkService.js
+++ b/client/src/Utils/NetworkService.js
@@ -45,9 +45,16 @@ class NetworkService {
this.axiosInstance.interceptors.response.use(
(response) => response,
(error) => {
+ // Handle network errors (server unreachable)
if (error.code === "ERR_NETWORK") {
- // Do error handling here
+ // 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());
@@ -508,6 +515,8 @@ class NetworkService {
return this.axiosInstance.get("/auth/users/superadmin");
}
+
+
/**
* ************************************
* Get all users
diff --git a/client/src/Utils/Theme/constants.js b/client/src/Utils/Theme/constants.js
index 8c4127c3d..920a55f69 100644
--- a/client/src/Utils/Theme/constants.js
+++ b/client/src/Utils/Theme/constants.js
@@ -63,7 +63,6 @@ const paletteColors = {
orange500: "#E88C30",
orange600: "#DC6803",
orange800: "#624711",
-
};
const semanticColors = {
@@ -155,7 +154,6 @@ const newSemanticColors = {
light: newColors.blueGray600,
dark: newColors.gray200,
},
- // CAIO_REVIEW, need a brighter color for dark bg
contrastTextSecondaryDarkBg: {
light: newColors.gray200,
dark: newColors.gray200,
@@ -164,6 +162,14 @@ const newSemanticColors = {
light: newColors.blueGray500,
dark: newColors.gray500,
},
+ contrastBorder: {
+ light: newColors.gray500,
+ dark: newColors.blueGray600,
+ },
+ contrastBorderDisabled: {
+ light: newColors.gray100,
+ dark: newColors.blueGray800,
+ },
lowContrast: {
light: newColors.gray200,
dark: newColors.blueGray600,
@@ -264,7 +270,7 @@ const newSemanticColors = {
dark: newColors.red600,
},
},
-
+
/* These are temporary, just for everything not to break */
gradient: {
color1: {
diff --git a/client/src/Utils/Theme/globalTheme.js b/client/src/Utils/Theme/globalTheme.js
index 08a1edca1..6a6724150 100644
--- a/client/src/Utils/Theme/globalTheme.js
+++ b/client/src/Utils/Theme/globalTheme.js
@@ -1,5 +1,4 @@
import { typographyLevels } from "./constants";
-
const fontFamilyPrimary = '"Inter" , sans-serif';
// const fontFamilySecondary = '"Avenir", sans-serif';
@@ -300,13 +299,15 @@ const baseTheme = (palette) => ({
}),
},
},
+
MuiTextField: {
styleOverrides: {
root: ({ theme }) => ({
"& fieldset": {
- borderColor: theme.palette.primary.lowContrast,
+ borderColor: theme.palette.primary.contrastBorder,
borderRadius: theme.shape.borderRadius,
},
+
"& .MuiInputBase-input": {
padding: ".75em",
minHeight: "var(--env-var-height-2)",
@@ -356,10 +357,19 @@ const baseTheme = (palette) => ({
MuiOutlinedInput: {
styleOverrides: {
root: {
- color: palette.primary.contrastTextTertiary,
- "&.Mui-focused .MuiOutlinedInput-notchedOutline": {
- borderColor: palette.primary.contrastTextTertiary,
+ "&.Mui-disabled .MuiOutlinedInput-notchedOutline": {
+ borderColor: palette.primary.contrastBorderDisabled,
},
+ "&.Mui-disabled:hover .MuiOutlinedInput-notchedOutline": {
+ borderColor: palette.primary.contrastBorderDisabled,
+ },
+ "&:hover .MuiOutlinedInput-notchedOutline": {
+ borderColor: palette.primary.contrastText, // Adjust hover border color
+ },
+ "&.Mui-focused .MuiOutlinedInput-notchedOutline": {
+ borderColor: palette.accent.main, // Adjust focus border color
+ },
+ color: palette.primary.contrastTextTertiary,
},
},
},
@@ -510,7 +520,7 @@ const baseTheme = (palette) => ({
root: ({ theme }) => ({
ml: "auto",
"& .MuiButtonBase-root, & .MuiButtonBase-root:hover": {
- borderColor: theme.palette.primary.lowContrast,
+ borderColor: theme.palette.primary.contrastBorder,
width: "auto",
whiteSpace: "nowrap",
},
diff --git a/client/src/Validation/validation.js b/client/src/Validation/validation.js
index 94c820ef6..0ace7b02c 100644
--- a/client/src/Validation/validation.js
+++ b/client/src/Validation/validation.js
@@ -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,
@@ -254,9 +254,13 @@ 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(),
+})
+.unknown(true);
const dayjsValidator = (value, helpers) => {
if (!dayjs(value).isValid()) {
diff --git a/client/src/locales/gb.json b/client/src/locales/gb.json
index 1670873b8..49993eb89 100644
--- a/client/src/locales/gb.json
+++ b/client/src/locales/gb.json
@@ -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,8 +17,9 @@
"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",
@@ -41,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",
@@ -52,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",
@@ -61,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",
@@ -70,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",
@@ -100,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",
@@ -377,9 +393,9 @@
"errorInvalidTypeId": "Invalid notification type provided",
"errorInvalidFieldId": "Invalid field ID provided",
"inviteNoTokenFound": "No invite token found",
- "pageSpeedWarning": "Warning: You haven't added a Google PageSpeed API key. Without it, the PageSpeed monitor won't function.",
- "pageSpeedLearnMoreLink": "Click here to learn",
- "pageSpeedAddApiKey": "how to add your API key.",
+ "pageSpeedWarning": "Warning: You haven't added a Google PageSpeed API key yet. Without it, the PageSpeed monitor won't function.",
+ "pageSpeedLearnMoreLink": "Click here",
+ "pageSpeedAddApiKey": "to add your API key.",
"update": "Update",
"invalidFileFormat": "Unsupported file format!",
"invalidFileSize": "File size is too large!",
@@ -393,17 +409,19 @@
"ignoreTLSError": "Ignore TLS/SSL error",
"tlsErrorIgnored": "TLS/SSL errors ignored",
"ignoreTLSErrorDescription": "Ignore TLS/SSL errors and continue checking the website's availability",
- "YourPhoto": "Your photo",
+ "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",
@@ -415,6 +433,12 @@
"noFileSelected": "No file selected",
"fallbackPage": "Import a file to upload a list of servers in bulk"
},
- "publicLink": "Public link"
+ "publicLink": "Public link",
+ "maskedPageSpeedKeyPlaceholder": "*************************************",
+ "pageSpeedApiKeyFieldTitle": "Google PageSpeed API key",
+ "pageSpeedApiKeyFieldLabel": "PageSpeed API key",
+ "pageSpeedApiKeyFieldDescription": "Enter your Google PageSpeed API key to enable pagespeed monitoring. Click Reset to update the key.",
+ "pageSpeedApiKeyFieldResetLabel": "API key is set. Click Reset to change it.",
+ "reset": "Reset"
}
diff --git a/client/src/locales/ru.json b/client/src/locales/ru.json
index 4ee3f249e..5536b5e45 100644
--- a/client/src/locales/ru.json
+++ b/client/src/locales/ru.json
@@ -1,8 +1,10 @@
{
"dontHaveAccount": "Нет аккаунта",
+ "doNotHaveAccount": "У вас нет учетной записи?",
+ "registerHere": "Зарегистрироваться здесь",
"email": "Почта",
"forgotPassword": "Забыли пароль",
- "password": "пароль",
+ "password": "Пароль",
"signUp": "Зарегистрироваться",
"submit": "Подтвердить",
"title": "Название",
@@ -15,8 +17,9 @@
"authForgotPasswordTitle": "Забыли пароль?",
"authForgotPasswordResetPassword": "Сбросить пароль",
"createPassword": "Создайте свой пароль",
- "createAPassword": "Создайте пароль",
+ "createAPassword": "Пароль",
"authRegisterAlreadyHaveAccount": "Уже есть аккаунт?",
+ "authRegisterLoginLink": "Войти",
"commonAppName": "Checkmate",
"welcomeBack": "Добро пожаловать обратно! Вы успешно вошли в систему.",
"authLoginEnterEmail": "Введите свой email",
@@ -41,7 +44,8 @@
"authSetNewPasswordDescription": "Ваш новый пароль должен отличаться от ранее использованных паролей.",
"authSetNewPasswordNewPassword": "Новый пароль",
"authSetNewPasswordConfirmPassword": "Подтвердите пароль",
- "confirmPassword": "Подтвердите ваш пароль",
+ "confirmPassword": "Введите пароль еще раз для подтверждения",
+ "confirmNewPasswordPlaceholder": "Подтвердите ваш новый пароль",
"authSetNewPasswordResetPassword": "Сбросить пароль",
"authSetNewPasswordBackTo": "Назад к",
"authPasswordMustBeAtLeast": "Должно быть как минимум",
@@ -52,7 +56,7 @@
"authPasswordUpperCharacter": "один верхний символ",
"authPasswordLowerCharacter": "один нижний символ",
"authPasswordConfirmAndPassword": "Подтвердите пароль и пароль",
- "authPasswordMustMatch": "должен совпадать",
+ "authPasswordMustMatch": "Пароли должны совпадать",
"authRegisterCreateAccount": "Создайте свою учетную запись, чтобы начать",
"authRegisterCreateSuperAdminAccount": "Создайте учетную запись суперадминистратора, чтобы начать работу",
"authRegisterSignUpWithEmail": "Создать учетную запись суперадминистратора",
@@ -60,12 +64,13 @@
"authRegisterFirstName": "Имя",
"authRegisterLastName": "Фамилия",
"authRegisterEmail": "Эл. почта",
- "authRegisterEmailRequired": "Эл. почта обязательна",
+ "authRegisterEmailRequired": "Чтобы продолжить, пожалуйста, введите ваш адрес электронной почты",
+ "authRegisterEmailInvalid": "Пожалуйста, введите корректный адрес электронной почты",
"distributedStatusHeaderText": "Охват реального времени и реального устройства",
"distributedStatusSubHeaderText": "Работает на миллионах устройств по всему миру, просматривайте производительность системы по глобальному региону, стране или городу",
"settingsGeneralSettings": "Общие настройки",
"settingsDisplayTimezone": "Отображать часовой пояс",
- "settingsDisplayTimezoneDescription": "Часовой пояс панели мониторинга, которую вы публично отображаете.",
+ "settingsDisplayTimezoneDescription": "Выберите часовой пояс, используемый для отображения дат и времени в приложении.",
"settingsAppearance": "Внешний вид",
"settingsAppearanceDescription": "Переключение между светлым и темным режимом или изменение языка пользовательского интерфейса",
"settingsThemeMode": "Тема",
@@ -74,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": "О",
@@ -100,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",
diff --git a/client/src/locales/tr.json b/client/src/locales/tr.json
index e942a2263..5df644fdd 100644
--- a/client/src/locales/tr.json
+++ b/client/src/locales/tr.json
@@ -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,8 +17,9 @@
"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",
@@ -41,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",
@@ -52,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",
@@ -61,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",
@@ -70,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",
@@ -100,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",
@@ -387,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": "",
diff --git a/docker/dev/build_images.sh b/docker/dev/build_images.sh
index 4baf442b8..e94be9fa5 100755
--- a/docker/dev/build_images.sh
+++ b/docker/dev/build_images.sh
@@ -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"
diff --git a/docker/dev/client.Dockerfile b/docker/dev/client.Dockerfile
index fd60f47a2..aa4d228fc 100755
--- a/docker/dev/client.Dockerfile
+++ b/docker/dev/client.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;"]
\ No newline at end of file
diff --git a/docker/dev/docker-compose.yaml b/docker/dev/docker-compose.yaml
index f8b2c6936..73904da37 100755
--- a/docker/dev/docker-compose.yaml
+++ b/docker/dev/docker-compose.yaml
@@ -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
diff --git a/docker/dev/nginx/conf.d/default.conf b/docker/dev/nginx/conf.d/default.conf
new file mode 100755
index 000000000..9a7690aa5
--- /dev/null
+++ b/docker/dev/nginx/conf.d/default.conf
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/docker/dist/docker-compose.yaml b/docker/dist/docker-compose.yaml
index 230623c8d..ca8e24061 100755
--- a/docker/dist/docker-compose.yaml
+++ b/docker/dist/docker-compose.yaml
@@ -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"
diff --git a/docker/prod/docker-compose.yaml b/docker/prod/docker-compose.yaml
index 770aaad9c..bc720862f 100755
--- a/docker/prod/docker-compose.yaml
+++ b/docker/prod/docker-compose.yaml
@@ -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"
diff --git a/docker/staging/docker-compose.yaml b/docker/staging/docker-compose.yaml
index 8117dc01b..d491a729e 100755
--- a/docker/staging/docker-compose.yaml
+++ b/docker/staging/docker-compose.yaml
@@ -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"
diff --git a/server/controllers/monitorController.js b/server/controllers/monitorController.js
index 6295a9aba..5ab654375 100755
--- a/server/controllers/monitorController.js
+++ b/server/controllers/monitorController.js
@@ -538,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)
diff --git a/server/validation/joi.js b/server/validation/joi.js
index fdd51b8d2..bd402fb8a 100755
--- a/server/validation/joi.js
+++ b/server/validation/joi.js
@@ -183,7 +183,7 @@ const createMonitorBodyValidation = joi.object({
description: joi.string().required(),
type: joi.string().required(),
url: joi.string().required(),
- ignoreTlsErrors: joi.boolean().required(),
+ ignoreTlsErrors: joi.boolean().default(false),
port: joi.number(),
isActive: joi.boolean(),
interval: joi.number(),