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")} - - setIsOpen(deleteStatsMonitorsInitState)} - confirmationButtonLabel={t("settingsRemoveAllMonitorsDialogConfirm")} - onConfirm={handleDeleteAllMonitors} - isLoading={isLoading || authIsLoading || checksIsLoading} - /> - + setIsOpen(deleteStatsMonitorsInitState)} + confirmationButtonLabel={t("settingsRemoveAllMonitorsDialogConfirm")} + onConfirm={handleDeleteAllMonitors} + isLoading={isLoading || authIsLoading || checksIsLoading} + /> + + )} diff --git a/client/src/Pages/StatusPage/StatusPages/index.jsx b/client/src/Pages/StatusPage/StatusPages/index.jsx index 6654be38c..095023d76 100644 --- a/client/src/Pages/StatusPage/StatusPages/index.jsx +++ b/client/src/Pages/StatusPage/StatusPages/index.jsx @@ -61,6 +61,7 @@ const StatusPages = () => { label="Create status page" isAdmin={isAdmin} path="/status/uptime/create" + isLoading={isLoading} /> 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(),