Merge branch 'bluewave-labs:develop' into StatusBarResolutionStyling
This commit is contained in:
23 files changed
+465
-114
No files matched your search
@@ -107,7 +107,7 @@ If you have any questions, suggestions or comments, please use our [Discord chan
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
We pride ourselves on building strong connections with contributors at every level. Despite being a young project, Checkmate has already earned 3700+ stars and attracted 45+ contributors from around the globe.
|
||||
We pride ourselves on building strong connections with contributors at every level. Despite being a young project, Checkmate has already earned 4.4K+ stars and attracted 50+ contributors from around the globe.
|
||||
|
||||
Our repo is starred by employees from **Google, Microsoft, Intel, Cisco, Tencent, Electronic Arts, ByteDance, JP Morgan Chase, Deloitte, Accenture, Foxconn, Broadcom, China Telecom, Barclays, Capgemini, Wipro, Cloudflare, Dassault Systèmes and NEC**, so don’t hold back — jump in, contribute and learn with us!
|
||||
|
||||
|
||||
Generated
+26
-23
@@ -16,7 +16,7 @@
|
||||
"@mui/lab": "6.0.0-beta.29",
|
||||
"@mui/material": "6.4.6",
|
||||
"@mui/x-charts": "^7.5.1",
|
||||
"@mui/x-data-grid": "7.27.1",
|
||||
"@mui/x-data-grid": "7.27.2",
|
||||
"@mui/x-date-pickers": "7.27.1",
|
||||
"@reduxjs/toolkit": "2.6.0",
|
||||
"@solana/wallet-adapter-base": "0.9.23",
|
||||
@@ -2688,10 +2688,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@fontsource/roboto": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.1.1.tgz",
|
||||
"integrity": "sha512-XwVVXtERDQIM7HPUIbyDe0FP4SRovpjF7zMI8M7pbqFp3ahLJsJTd18h+E6pkar6UbV3btbwkKjYARr5M+SQow==",
|
||||
"license": "Apache-2.0"
|
||||
"version": "5.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.2.5.tgz",
|
||||
"integrity": "sha512-70r2UZ0raqLn5W+sPeKhqlf8wGvUXFWlofaDlcbt/S3d06+17gXKr3VNqDODB0I1ASme3dGT5OJj9NABt7OTZQ==",
|
||||
"license": "OFL-1.1",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
}
|
||||
},
|
||||
"node_modules/@fractalwagmi/popup-connection": {
|
||||
"version": "1.1.1",
|
||||
@@ -3858,9 +3861,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-data-grid": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.27.1.tgz",
|
||||
"integrity": "sha512-dDgrCIiIcb3XPxUVxXQsk9yjXFSAXBunqW1m1jjcr0D3jj3qHibBtGslSKnBmmcoT6XLAXdbZOu1fFC+xYhyGA==",
|
||||
"version": "7.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.27.2.tgz",
|
||||
"integrity": "sha512-Nj6PWp6WN0gkps676j1Jai3mP1fQotJbqMTvllU7LwQp09S/RE/uJicYdqfeWQLxtQipWm5dnAY63gzsTiAHGA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.25.7",
|
||||
@@ -4713,9 +4716,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.22.0.tgz",
|
||||
"integrity": "sha512-MBOl8MeOzpK0HQQQshKB7pABXbmyHizdTpqnrIseTbsv0nAepwC2ENZa1aaBExNQcpLoXmWthhak8SABLzvGPw==",
|
||||
"version": "1.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
|
||||
"integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -11412,9 +11415,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react-hooks": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0.tgz",
|
||||
"integrity": "sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==",
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz",
|
||||
"integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -16305,12 +16308,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "6.29.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.29.0.tgz",
|
||||
"integrity": "sha512-DXZJoE0q+KyeVw75Ck6GkPxFak63C4fGqZGNijnWgzB/HzSP1ZfTlBj5COaGWwhrMQ/R8bXiq5Ooy4KG+ReyjQ==",
|
||||
"version": "6.30.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.0.tgz",
|
||||
"integrity": "sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.22.0"
|
||||
"@remix-run/router": "1.23.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -16320,13 +16323,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "6.29.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.29.0.tgz",
|
||||
"integrity": "sha512-pkEbJPATRJ2iotK+wUwHfy0xs2T59YPEN8BQxVCPeBZvK7kfPESRc/nyxzdcxR17hXgUPYx2whMwl+eo9cUdnQ==",
|
||||
"version": "6.30.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.0.tgz",
|
||||
"integrity": "sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.22.0",
|
||||
"react-router": "6.29.0"
|
||||
"@remix-run/router": "1.23.0",
|
||||
"react-router": "6.30.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
"@fontsource/roboto": "^5.0.13",
|
||||
"@hello-pangea/dnd": "^18.0.0",
|
||||
"@mui/x-charts": "^7.5.1",
|
||||
"@mui/x-data-grid": "7.27.1",
|
||||
"@mui/x-data-grid": "7.27.2",
|
||||
"@mui/x-date-pickers": "7.27.1",
|
||||
"@mui/icons-material": "6.4.6",
|
||||
"@mui/lab": "6.0.0-beta.29",
|
||||
|
||||
+13
-2
@@ -12,17 +12,28 @@ import { logger } from "./Utils/Logger"; // Import the logger
|
||||
import { networkService } from "./main";
|
||||
import { Routes } from "./Routes";
|
||||
import WalletProvider from "./Components/WalletProvider";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { setLanguage } from "./Features/UI/uiSlice";
|
||||
|
||||
function App() {
|
||||
const mode = useSelector((state) => state.ui.mode);
|
||||
const { authToken } = useSelector((state) => state.auth);
|
||||
const dispatch = useDispatch();
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (authToken) {
|
||||
dispatch(getAppSettings({ authToken }));
|
||||
dispatch(getAppSettings({ authToken })).then((action) => {
|
||||
if (action.payload && action.payload.success) {
|
||||
const { language } = action.payload.data;
|
||||
if (language) {
|
||||
dispatch(setLanguage(language));
|
||||
i18n.changeLanguage(language);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [dispatch, authToken]);
|
||||
}, [dispatch, authToken, i18n]);
|
||||
|
||||
// Cleanup
|
||||
useEffect(() => {
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
// Components
|
||||
import { Typography, Stack } from "@mui/material";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import IconBox from "../../IconBox";
|
||||
|
||||
/**
|
||||
* `EmptyView` is a functional React component that displays an empty state view with an optional icon, header, and message.
|
||||
*
|
||||
* @component
|
||||
* @param {Object} props - The properties that define the `EmptyView` component.
|
||||
* @param {React.ReactNode} [props.icon] - An optional icon to display at the top of the empty view.
|
||||
* @param {string} [props.header] - An optional header text displayed next to the icon.
|
||||
* @param {string} [props.message="No Data"] - The message to be displayed in the empty view.
|
||||
* @param {'h1' | 'h2' | 'h3'} [props.headingLevel="h2"] - The heading level for the message text.
|
||||
* @param {string} [props.justifyContent="flex-start"] - The CSS `justify-content` value to align elements vertically.
|
||||
* @param {string} [props.height="100%"] - The height of the empty view container.
|
||||
*
|
||||
* @example
|
||||
* // Example usage of EmptyView component:
|
||||
* <EmptyView
|
||||
* icon={<SomeIcon />}
|
||||
* header="Average Response Time"
|
||||
* message="No Response Time Available"
|
||||
* headingLevel="h2"
|
||||
* justifyContent="center"
|
||||
* height="50%"
|
||||
* />
|
||||
*
|
||||
* @returns {React.Element} The `EmptyView` component with customizable icon, header, and message.
|
||||
*/
|
||||
|
||||
const EmptyView = ({
|
||||
icon,
|
||||
header,
|
||||
message = "No Data",
|
||||
headingLevel = "h2",
|
||||
justifyContent = "flex-start",
|
||||
height = "100%"
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Stack
|
||||
flex={1}
|
||||
direction="row"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
|
||||
border: 1,
|
||||
borderStyle: "solid",
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
borderRadius: 2,
|
||||
borderTopRightRadius: 4,
|
||||
borderBottomRightRadius: 4,
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
flex={1}
|
||||
alignItems="center"
|
||||
sx={{
|
||||
padding: theme.spacing(8),
|
||||
justifyContent,
|
||||
gap: theme.spacing(8),
|
||||
height,
|
||||
"& h2": {
|
||||
color: theme.palette.primary.contrastTextSecondary,
|
||||
fontSize: 15,
|
||||
fontWeight: 500,
|
||||
},
|
||||
|
||||
"& tspan, & text": {
|
||||
fill: theme.palette.primary.contrastTextTertiary,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
alignSelf="flex-start"
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(6)}
|
||||
>
|
||||
{icon && <IconBox>{icon}</IconBox>}
|
||||
{header && <Typography component="h2">{header}</Typography>}
|
||||
</Stack>
|
||||
<Stack
|
||||
flex={1}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Typography component={headingLevel}>
|
||||
{message}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
EmptyView.propTypes = {
|
||||
message: PropTypes.string,
|
||||
icon: PropTypes.node,
|
||||
header: PropTypes.string,
|
||||
headingLevel: PropTypes.oneOf(['h1', 'h2', 'h3']),
|
||||
justifyContent: PropTypes.string,
|
||||
height: PropTypes.string
|
||||
};
|
||||
|
||||
export default EmptyView;
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import IconBox from "../../IconBox";
|
||||
import EmptyView from "./EmptyView";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const ChartBox = ({
|
||||
@@ -12,8 +13,13 @@ const ChartBox = ({
|
||||
Legend,
|
||||
borderRadiusRight = 4,
|
||||
sx,
|
||||
noDataMessage,
|
||||
isEmpty = false,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
if (isEmpty) {
|
||||
return <EmptyView icon={icon} header={header} message={noDataMessage} />;
|
||||
}
|
||||
return (
|
||||
<Stack
|
||||
flex={1}
|
||||
@@ -88,4 +94,6 @@ ChartBox.propTypes = {
|
||||
icon: PropTypes.node,
|
||||
header: PropTypes.string,
|
||||
height: PropTypes.string,
|
||||
noDataMessage: PropTypes.string,
|
||||
isEmpty: PropTypes.bool
|
||||
};
|
||||
@@ -14,7 +14,6 @@ const LanguageSelector = () => {
|
||||
i18n.changeLanguage(newLang);
|
||||
};
|
||||
|
||||
// i18n instance'ından mevcut dilleri al
|
||||
const languages = Object.keys(i18n.options.resources || {});
|
||||
|
||||
return (
|
||||
|
||||
@@ -628,10 +628,19 @@ function Sidebar() {
|
||||
) : (
|
||||
<>
|
||||
<Avatar small={true} />
|
||||
<Box ml={theme.spacing(2)}>
|
||||
<Box
|
||||
ml={theme.spacing(2)}
|
||||
sx={{ maxWidth: "50%", overflow: "hidden" }}
|
||||
>
|
||||
<Typography
|
||||
component="span"
|
||||
fontWeight={500}
|
||||
sx={{
|
||||
display: "block",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
>
|
||||
{authState.user?.firstName} {authState.user?.lastName}
|
||||
</Typography>
|
||||
@@ -706,12 +715,21 @@ function Sidebar() {
|
||||
}}
|
||||
>
|
||||
{collapsed && (
|
||||
<MenuItem sx={{ cursor: "default", minWidth: "150px" }}>
|
||||
<Box mb={theme.spacing(2)}>
|
||||
<MenuItem sx={{ cursor: "default", minWidth: "50%" }}>
|
||||
<Box
|
||||
mb={theme.spacing(2)}
|
||||
sx={{ maxWidth: "50%", overflow: "hidden" }}
|
||||
>
|
||||
<Typography
|
||||
component="span"
|
||||
fontWeight={500}
|
||||
fontSize={13}
|
||||
sx={{
|
||||
display: "block",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
>
|
||||
{authState.user?.firstName} {authState.user?.lastName}
|
||||
</Typography>
|
||||
|
||||
@@ -15,6 +15,8 @@ import { useTheme } from "@emotion/react";
|
||||
* @typedef {Object} Header
|
||||
* @property {number|string} id - The unique identifier for the header.
|
||||
* @property {React.ReactNode} content - The content to display in the header cell.
|
||||
* @property {Function} onClick - A function to be called when this cell is clicked, receiving the event and row data as an argument.
|
||||
* @property {Object} getCellSx - Function that takes a row and returns a style object for the table cell.
|
||||
* @property {Function} render - A function to render the cell content for a given row.
|
||||
*/
|
||||
|
||||
@@ -107,6 +109,8 @@ const DataTable = ({
|
||||
<TableCell
|
||||
align={index === 0 ? "left" : "center"}
|
||||
key={header.id}
|
||||
onClick={header.onClick ? (e) => header.onClick(e, row) : null}
|
||||
sx={header.getCellSx ? header.getCellSx(row) : {}}
|
||||
>
|
||||
{header.render(row)}
|
||||
</TableCell>
|
||||
|
||||
@@ -5,6 +5,7 @@ const initialState = {
|
||||
isLoading: false,
|
||||
apiBaseUrl: "",
|
||||
logLevel: "debug",
|
||||
language: "",
|
||||
};
|
||||
|
||||
export const getAppSettings = createAsyncThunk(
|
||||
@@ -34,6 +35,7 @@ export const updateAppSettings = createAsyncThunk(
|
||||
const parsedSettings = {
|
||||
apiBaseUrl: settings.apiBaseUrl,
|
||||
logLevel: settings.logLevel,
|
||||
language: settings.language,
|
||||
clientHost: settings.clientHost,
|
||||
jwtSecret: settings.jwtSecret,
|
||||
dbType: settings.dbType,
|
||||
@@ -68,6 +70,7 @@ const handleGetSettingsFulfilled = (state, action) => {
|
||||
state.msg = action.payload.msg;
|
||||
state.apiBaseUrl = action.payload.data.apiBaseUrl;
|
||||
state.logLevel = action.payload.data.logLevel;
|
||||
state.language = action.payload.data.language;
|
||||
};
|
||||
const handleGetSettingsRejected = (state, action) => {
|
||||
state.isLoading = false;
|
||||
|
||||
@@ -53,8 +53,8 @@ const uiSlice = createSlice({
|
||||
state.timezone = action.payload.timezone;
|
||||
},
|
||||
setLanguage(state, action) {
|
||||
state.language = action.payload;
|
||||
},
|
||||
state.language = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTheme } from "@emotion/react";
|
||||
import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils";
|
||||
import { createToast } from "../../../../Utils/toastUtils";
|
||||
|
||||
const useSubscribeToMonitors = () => {
|
||||
const useSubscribeToMonitors = (page, rowsPerPage) => {
|
||||
// Redux
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
|
||||
@@ -25,8 +25,8 @@ const useSubscribeToMonitors = () => {
|
||||
teamId: user.teamId,
|
||||
limit: 25,
|
||||
types: ["distributed_http"],
|
||||
page: 0,
|
||||
rowsPerPage: 10,
|
||||
page,
|
||||
rowsPerPage,
|
||||
filter: null,
|
||||
field: null,
|
||||
order: null,
|
||||
@@ -56,7 +56,7 @@ const useSubscribeToMonitors = () => {
|
||||
});
|
||||
setNetworkError(true);
|
||||
}
|
||||
}, [user, getMonitorWithPercentage, theme, isLoading]);
|
||||
}, [user, getMonitorWithPercentage, theme, isLoading, page, rowsPerPage]);
|
||||
return [isLoading, networkError, monitors, monitorsSummary, filteredMonitors];
|
||||
};
|
||||
export { useSubscribeToMonitors };
|
||||
@@ -3,10 +3,12 @@ import { Stack, Typography } from "@mui/material";
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
import CreateMonitorHeader from "../../../Components/MonitorCreateHeader";
|
||||
import MonitorTable from "./Components/MonitorTable";
|
||||
import Pagination from "../../../Components/Table/TablePagination";
|
||||
import Fallback from "../../../Components/Fallback";
|
||||
import GenericFallback from "../../../Components/GenericFallback";
|
||||
|
||||
// Utils
|
||||
import { useState } from "react";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
|
||||
import { useSubscribeToMonitors } from "./Hooks/useSubscribeToMonitors";
|
||||
@@ -16,11 +18,23 @@ const BREADCRUMBS = [{ name: `Distributed Uptime`, path: "/distributed-uptime" }
|
||||
|
||||
const DistributedUptimeMonitors = () => {
|
||||
// Local state
|
||||
const [page, setPage] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(10);
|
||||
|
||||
// Utils
|
||||
const theme = useTheme();
|
||||
const isAdmin = useIsAdmin();
|
||||
const [isLoading, networkError, monitors, monitorsSummary, filteredMonitors] =
|
||||
useSubscribeToMonitors();
|
||||
useSubscribeToMonitors(page, rowsPerPage);
|
||||
// Handlers
|
||||
const handleChangePage = (event, newPage) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (event) => {
|
||||
setRowsPerPage(event.target.value);
|
||||
setPage(0);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <SkeletonLayout />;
|
||||
@@ -69,6 +83,14 @@ const DistributedUptimeMonitors = () => {
|
||||
isLoading={isLoading}
|
||||
monitors={filteredMonitors}
|
||||
/>
|
||||
<Pagination
|
||||
itemCount={monitorsSummary?.totalMonitors ?? 0}
|
||||
paginationLabel="monitors"
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
handleChangePage={handleChangePage}
|
||||
handleChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -18,10 +18,11 @@ import InfoBox from "../../../Components/InfoBox";
|
||||
import StatusHeader from "../../DistributedUptime/Details/Components/StatusHeader";
|
||||
import MonitorsList from "./Components/MonitorsList";
|
||||
import { RowContainer } from "../../../Components/StandardContainer";
|
||||
import ThemeSwitch from "../../../Components/ThemeSwitch";
|
||||
|
||||
//Utils
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useState, useRef } from "react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useSubscribeToDetails } from "../../DistributedUptime/Details/Hooks/useSubscribeToDetails";
|
||||
import { useDUStatusPageFetchByUrl } from "./Hooks/useDUStatusPageFetchByUrl";
|
||||
@@ -31,6 +32,8 @@ import SubHeader from "../../../Components/Subheader";
|
||||
import { safelyParseFloat } from "../../../Utils/utils";
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { setMode } from "../../../Features/UI/uiSlice";
|
||||
|
||||
// Responsive
|
||||
import { useMediaQuery } from "@mui/material";
|
||||
@@ -40,8 +43,12 @@ const DistributedUptimeStatus = () => {
|
||||
const location = useLocation();
|
||||
const { t } = useTranslation();
|
||||
const isPublic = location.pathname.startsWith("/status/distributed/public");
|
||||
|
||||
const elementToCapture = useRef(null);
|
||||
|
||||
// Redux state
|
||||
const mode = useSelector((state) => state.ui.mode);
|
||||
const originalModeRef = useRef(null);
|
||||
|
||||
// Local State
|
||||
const [dateRange, setDateRange] = useState("day");
|
||||
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
||||
@@ -50,6 +57,7 @@ const DistributedUptimeStatus = () => {
|
||||
const theme = useTheme();
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [
|
||||
statusPageIsLoading,
|
||||
@@ -85,6 +93,28 @@ const DistributedUptimeStatus = () => {
|
||||
};
|
||||
}
|
||||
|
||||
// Default to dark mode
|
||||
useEffect(() => {
|
||||
const cleanup = () => {
|
||||
if (originalModeRef.current === null) {
|
||||
originalModeRef.current = mode;
|
||||
}
|
||||
|
||||
if (isPublic) {
|
||||
dispatch(setMode(originalModeRef.current));
|
||||
}
|
||||
};
|
||||
|
||||
if (isPublic) {
|
||||
dispatch(setMode("dark"));
|
||||
}
|
||||
|
||||
window.addEventListener("beforeunload", cleanup);
|
||||
return () => {
|
||||
window.removeEventListener("beforeunload", cleanup);
|
||||
};
|
||||
}, [dispatch, isPublic]);
|
||||
|
||||
// Done loading, a status page doesn't exist
|
||||
if (!statusPageIsLoading && typeof statusPage === "undefined") {
|
||||
return (
|
||||
@@ -158,9 +188,10 @@ const DistributedUptimeStatus = () => {
|
||||
}}
|
||||
>
|
||||
{!isPublic && <Breadcrumbs list={BREADCRUMBS} />}
|
||||
|
||||
<ControlsHeader
|
||||
shouldShow={!isPublic}
|
||||
statusPage={statusPage}
|
||||
isPublic={isPublic}
|
||||
isDeleting={isDeleting}
|
||||
isDeleteOpen={isDeleteOpen}
|
||||
setIsDeleteOpen={setIsDeleteOpen}
|
||||
@@ -183,11 +214,9 @@ const DistributedUptimeStatus = () => {
|
||||
<RowContainer>
|
||||
<Stack>
|
||||
<Typography variant={`body2`}>
|
||||
{t("distributedRightCatagoryTitle")}
|
||||
</Typography>
|
||||
<Typography variant={`h2`}>
|
||||
{t("distributedRightCatagoryDescription")}
|
||||
{t("distributedRightCategoryTitle")}
|
||||
</Typography>
|
||||
<Typography variant={`h2`}>{statusPage.companyName}</Typography>
|
||||
</Stack>
|
||||
</RowContainer>
|
||||
</SubHeader>
|
||||
|
||||
@@ -27,19 +27,24 @@ import {
|
||||
setTimezone,
|
||||
setMode,
|
||||
setDistributedUptimeEnabled,
|
||||
setLanguage,
|
||||
} from "../../Features/UI/uiSlice";
|
||||
import timezones from "../../Utils/timezones.json";
|
||||
import { useState, useEffect } from "react";
|
||||
import { networkService } from "../../main";
|
||||
import { settingsValidation } from "../../Validation/validation";
|
||||
import { updateAppSettings } from "../../Features/Settings/settingsSlice";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
// Constants
|
||||
const SECONDS_PER_DAY = 86400;
|
||||
|
||||
const Settings = () => {
|
||||
const theme = useTheme();
|
||||
const { t, i18n } = useTranslation();
|
||||
const isAdmin = useIsAdmin();
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
const { language } = useSelector((state) => state.ui);
|
||||
const { checkTTL } = user;
|
||||
const { isLoading } = useSelector((state) => state.uptimeMonitors);
|
||||
const { isLoading: authIsLoading } = useSelector((state) => state.auth);
|
||||
@@ -119,10 +124,13 @@ const Settings = () => {
|
||||
});
|
||||
const updatedUser = { ...user, checkTTL: form.ttl };
|
||||
const action = await dispatch(update({ localData: updatedUser }));
|
||||
const settingsAction = await dispatch(
|
||||
updateAppSettings({ settings: { language: language } })
|
||||
);
|
||||
|
||||
if (action.payload.success) {
|
||||
if (action.payload.success && settingsAction.payload.success) {
|
||||
createToast({
|
||||
body: "Settings saved successfully",
|
||||
body: t("settingsSuccessSaved"),
|
||||
});
|
||||
} else {
|
||||
if (action.payload) {
|
||||
@@ -139,7 +147,7 @@ const Settings = () => {
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
createToast({ body: "Failed to save settings" });
|
||||
createToast({ body: t("settingsFailedToSave") });
|
||||
} finally {
|
||||
setChecksIsLoading(false);
|
||||
}
|
||||
@@ -147,18 +155,16 @@ const Settings = () => {
|
||||
|
||||
const handleClearStats = async () => {
|
||||
try {
|
||||
const action = await dispatch(
|
||||
deleteMonitorChecksByTeamId({ teamId: user.teamId })
|
||||
);
|
||||
const action = await dispatch(deleteMonitorChecksByTeamId({ teamId: user.teamId }));
|
||||
|
||||
if (deleteMonitorChecksByTeamId.fulfilled.match(action)) {
|
||||
createToast({ body: "Stats cleared successfully" });
|
||||
createToast({ body: t("settingsStatsCleared") });
|
||||
} else {
|
||||
createToast({ body: "Failed to clear stats" });
|
||||
createToast({ body: t("settingsFailedToClearStats") });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
createToast({ body: "Failed to clear stats" });
|
||||
createToast({ body: t("settingsFailedToClearStats") });
|
||||
} finally {
|
||||
setIsOpen(deleteStatsMonitorsInitState);
|
||||
}
|
||||
@@ -168,13 +174,13 @@ const Settings = () => {
|
||||
try {
|
||||
const action = await dispatch(addDemoMonitors());
|
||||
if (addDemoMonitors.fulfilled.match(action)) {
|
||||
createToast({ body: "Successfully added demo monitors" });
|
||||
createToast({ body: t("settingsDemoMonitorsAdded") });
|
||||
} else {
|
||||
createToast({ body: "Failed to add demo monitors" });
|
||||
createToast({ body: t("settingsFailedToAddDemoMonitors") });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
createToast({ Body: "Failed to add demo monitors" });
|
||||
createToast({ Body: t("settingsFailedToAddDemoMonitors") });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -182,18 +188,20 @@ const Settings = () => {
|
||||
try {
|
||||
const action = await dispatch(deleteAllMonitors());
|
||||
if (deleteAllMonitors.fulfilled.match(action)) {
|
||||
createToast({ body: "Successfully deleted all monitors" });
|
||||
createToast({ body: t("settingsMonitorsDeleted") });
|
||||
} else {
|
||||
createToast({ body: "Failed to add demo monitors" });
|
||||
createToast({ body: t("settingsFailedToDeleteMonitors") });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
createToast({ Body: "Failed to delete all monitors" });
|
||||
createToast({ Body: t("settingsFailedToDeleteMonitors") });
|
||||
} finally {
|
||||
setIsOpen(deleteStatsMonitorsInitState);
|
||||
}
|
||||
};
|
||||
|
||||
const languages = Object.keys(i18n.options.resources || {});
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="settings"
|
||||
@@ -209,16 +217,15 @@ const Settings = () => {
|
||||
>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">General settings</Typography>
|
||||
<Typography component="h1">{t("settingsGeneralSettings")}</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2), mb: theme.spacing(2) }}>
|
||||
<Typography component="span">Display timezone</Typography>- The timezone of
|
||||
the dashboard you publicly display.
|
||||
<Typography component="span">{t("settingsDisplayTimezone")}</Typography>- {t("settingsDisplayTimezoneDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(20)}>
|
||||
<Select
|
||||
id="display-timezones"
|
||||
label="Display timezone"
|
||||
label={t("settingsDisplayTimezone")}
|
||||
value={timezone}
|
||||
onChange={(e) => {
|
||||
dispatch(setTimezone({ timezone: e.target.value }));
|
||||
@@ -229,32 +236,42 @@ const Settings = () => {
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">Appearance</Typography>
|
||||
<Typography component="h1">{t("settingsAppearance")}</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2), mb: theme.spacing(2) }}>
|
||||
Switch between light and dark mode.
|
||||
{t("settingsAppearanceDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(20)}>
|
||||
<Select
|
||||
id="theme-mode"
|
||||
label={t("settingsThemeMode")}
|
||||
value={mode}
|
||||
onChange={(e) => {
|
||||
dispatch(setMode(e.target.value));
|
||||
}}
|
||||
items={[
|
||||
{ _id: "light", name: "Light" },
|
||||
{ _id: "dark", name: "Dark" },
|
||||
]}
|
||||
></Select>
|
||||
<Select
|
||||
id="theme-mode"
|
||||
label="Theme Mode"
|
||||
value={mode}
|
||||
id="language"
|
||||
label={t("settingsLanguage")}
|
||||
value={language}
|
||||
onChange={(e) => {
|
||||
dispatch(setMode(e.target.value));
|
||||
dispatch(setLanguage(e.target.value));
|
||||
i18n.changeLanguage(e.target.value);
|
||||
}}
|
||||
items={[
|
||||
{ _id: "light", name: "Light" },
|
||||
{ _id: "dark", name: "Dark" },
|
||||
]}
|
||||
items={languages.map((lang) => ({ _id: lang, name: lang.toUpperCase() }))}
|
||||
></Select>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
{isAdmin && (
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">Distributed uptime</Typography>
|
||||
<Typography component="h1">{t("settingsDistributedUptime")}</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2), mb: theme.spacing(2) }}>
|
||||
Enable/disable distributed uptime monitoring.
|
||||
{t("settingsDistributedUptimeDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
@@ -266,31 +283,30 @@ const Settings = () => {
|
||||
dispatch(setDistributedUptimeEnabled(e.target.checked));
|
||||
}}
|
||||
/>
|
||||
{distributedUptimeEnabled === true ? "Enabled" : "Disabled"}
|
||||
{distributedUptimeEnabled === true ? t("settingsEnabled") : t("settingsDisabled")}
|
||||
</Box>
|
||||
</ConfigBox>
|
||||
)}
|
||||
{isAdmin && (
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">History and monitoring</Typography>
|
||||
<Typography component="h1">{t("settingsHistoryAndMonitoring")}</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2) }}>
|
||||
Define here for how long you want to keep the data. You can also remove
|
||||
all past data.
|
||||
{t("settingsHistoryAndMonitoringDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(20)}>
|
||||
<TextInput
|
||||
id="ttl"
|
||||
label="The days you want to keep monitoring history."
|
||||
optionalLabel="0 for infinite"
|
||||
label={t("settingsTTLLabel")}
|
||||
optionalLabel={t("settingsTTLOptionalLabel")}
|
||||
value={form.ttl}
|
||||
onChange={handleChange}
|
||||
error={errors.ttl ? true : false}
|
||||
helperText={errors.ttl}
|
||||
/>
|
||||
<Box>
|
||||
<Typography>Clear all stats. This is irreversible.</Typography>
|
||||
<Typography>{t("settingsClearAllStats")}</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
@@ -299,17 +315,17 @@ const Settings = () => {
|
||||
}
|
||||
sx={{ mt: theme.spacing(4) }}
|
||||
>
|
||||
Clear all stats
|
||||
{t("settingsClearAllStatsButton")}
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Dialog
|
||||
open={isOpen.deleteStats}
|
||||
theme={theme}
|
||||
title="Do you want to clear all stats?"
|
||||
description="Once deleted, your monitors cannot be retrieved."
|
||||
title={t("settingsClearAllStatsDialogTitle")}
|
||||
description={t("settingsClearAllStatsDialogDescription")}
|
||||
onCancel={() => setIsOpen(deleteStatsMonitorsInitState)}
|
||||
confirmationButtonLabel="Yes, clear all stats"
|
||||
confirmationButtonLabel={t("settingsClearAllStatsDialogConfirm")}
|
||||
onConfirm={handleClearStats}
|
||||
isLoading={isLoading || authIsLoading || checksIsLoading}
|
||||
/>
|
||||
@@ -318,14 +334,14 @@ const Settings = () => {
|
||||
{isAdmin && (
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">Demo monitors</Typography>
|
||||
<Typography component="h1">{t("settingsDemoMonitors")}</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2) }}>
|
||||
Here you can add and remove demo monitors.
|
||||
{t("settingsDemoMonitorsDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(20)}>
|
||||
<Box>
|
||||
<Typography>Add demo monitors</Typography>
|
||||
<Typography>{t("settingsAddDemoMonitors")}</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
@@ -333,11 +349,11 @@ const Settings = () => {
|
||||
onClick={handleInsertDemoMonitors}
|
||||
sx={{ mt: theme.spacing(4) }}
|
||||
>
|
||||
Add demo monitors
|
||||
{t("settingsAddDemoMonitorsButton")}
|
||||
</Button>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography>Remove all monitors</Typography>
|
||||
<Typography>{t("settingsRemoveAllMonitors")}</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
@@ -347,16 +363,16 @@ const Settings = () => {
|
||||
}
|
||||
sx={{ mt: theme.spacing(4) }}
|
||||
>
|
||||
Remove all monitors
|
||||
{t("settingsRemoveAllMonitorsButton")}
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Dialog
|
||||
open={isOpen.deleteMonitors}
|
||||
theme={theme}
|
||||
title="Do you want to remove all monitors?"
|
||||
title={t("settingsRemoveAllMonitorsDialogTitle")}
|
||||
onCancel={() => setIsOpen(deleteStatsMonitorsInitState)}
|
||||
confirmationButtonLabel="Yes, clear all monitors"
|
||||
confirmationButtonLabel={t("settingsRemoveAllMonitorsDialogConfirm")}
|
||||
onConfirm={handleDeleteAllMonitors}
|
||||
isLoading={isLoading || authIsLoading || checksIsLoading}
|
||||
/>
|
||||
@@ -365,10 +381,9 @@ const Settings = () => {
|
||||
{isAdmin && (
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">Wallet</Typography>
|
||||
<Typography component="h1">{t("settingsWallet")}</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2) }}>
|
||||
Connect your wallet here. This is required for the Distributed Uptime
|
||||
monitor to connect to multiple nodes globally.
|
||||
{t("settingsWalletDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
@@ -385,12 +400,12 @@ const Settings = () => {
|
||||
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">About</Typography>
|
||||
<Typography component="h1">{t("settingsAbout")}</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography component="h2">Checkmate {version}</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2), mb: theme.spacing(6), opacity: 0.6 }}>
|
||||
Developed by Bluewave Labs.
|
||||
{t("settingsDevelopedBy")}
|
||||
</Typography>
|
||||
<Link
|
||||
level="secondary"
|
||||
@@ -411,7 +426,7 @@ const Settings = () => {
|
||||
sx={{ px: theme.spacing(12), mt: theme.spacing(20) }}
|
||||
onClick={handleSave}
|
||||
>
|
||||
Save
|
||||
{t("settingsSave")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { Box, Stack, Typography, Button } from "@mui/material";
|
||||
import Image from "../../../../../Components/Image";
|
||||
import SettingsIcon from "../../../../../assets/icons/settings-bold.svg?react";
|
||||
|
||||
import ThemeSwitch from "../../../../../Components/ThemeSwitch";
|
||||
//Utils
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@@ -76,6 +76,7 @@ Controls.propTypes = {
|
||||
|
||||
const ControlsHeader = ({
|
||||
statusPage,
|
||||
isPublic,
|
||||
isDeleting,
|
||||
isDeleteOpen,
|
||||
setIsDeleteOpen,
|
||||
@@ -105,7 +106,16 @@ const ControlsHeader = ({
|
||||
maxHeight={"50px"}
|
||||
base64={statusPage?.logo?.data}
|
||||
/>
|
||||
<Typography variant="h1">{statusPage?.companyName}</Typography>
|
||||
<Typography
|
||||
variant="h1"
|
||||
overflow="hidden"
|
||||
textOverflow="ellipsis"
|
||||
sx={{
|
||||
maxWidth: { xs: "200px", sm: "100%" },
|
||||
}}
|
||||
>
|
||||
{statusPage?.companyName}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Controls
|
||||
isDeleting={isDeleting}
|
||||
@@ -114,6 +124,7 @@ const ControlsHeader = ({
|
||||
url={url}
|
||||
type={type}
|
||||
/>
|
||||
{isPublic && <ThemeSwitch />}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -121,6 +132,7 @@ const ControlsHeader = ({
|
||||
ControlsHeader.propTypes = {
|
||||
url: PropTypes.string,
|
||||
statusPage: PropTypes.object,
|
||||
isPublic: PropTypes.bool,
|
||||
isDeleting: PropTypes.bool,
|
||||
isDeleteOpen: PropTypes.bool.isRequired,
|
||||
setIsDeleteOpen: PropTypes.func.isRequired,
|
||||
|
||||
@@ -17,16 +17,40 @@ const StatusPagesTable = ({ data }) => {
|
||||
},
|
||||
{
|
||||
id: "url",
|
||||
content: "URL",
|
||||
content: "Public URL",
|
||||
onClick: (e, row) => {
|
||||
if (row.isPublished) {
|
||||
e.stopPropagation();
|
||||
const url =
|
||||
row.type === "distributed"
|
||||
? `/status/distributed/public/${row.url}`
|
||||
: `/status/uptime/public/${row.url}`;
|
||||
navigate(url);
|
||||
}
|
||||
},
|
||||
render: (row) => {
|
||||
const content = row.isPublished ? `/${row.url}` : "Unpublished";
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
gap={theme.spacing(2)}
|
||||
paddingLeft={theme.spacing(2)}
|
||||
paddingRight={theme.spacing(2)}
|
||||
borderRadius={theme.spacing(4)}
|
||||
sx={{
|
||||
...(row.isPublished && {
|
||||
":hover": {
|
||||
backgroundColor: `${theme.palette.primary.light}`,
|
||||
cursor: "pointer",
|
||||
borderRadius: 1,
|
||||
},
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<Typography>{`/${row.url}`}</Typography>
|
||||
<ArrowOutwardIcon />
|
||||
<Typography>{content}</Typography>
|
||||
{row.isPublished && <ArrowOutwardIcon />}
|
||||
</Stack>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -51,6 +51,7 @@ const StatusPages = () => {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
|
||||
@@ -35,6 +35,7 @@ const ChartBoxes = ({
|
||||
const denominator =
|
||||
totalUpChecks + totalDownChecks > 0 ? totalUpChecks + totalDownChecks : 1;
|
||||
const groupedUptimePercentage = (totalUpChecks / denominator) * 100;
|
||||
const noIncidentsMessage = "Great. No Incidents, yet!";
|
||||
|
||||
return (
|
||||
<Stack
|
||||
@@ -45,6 +46,7 @@ const ChartBoxes = ({
|
||||
<ChartBox
|
||||
icon={<UptimeIcon />}
|
||||
header="Uptime"
|
||||
isEmpty={monitor?.uptimePercentage === 0 && !monitor?.upChecks?.length}
|
||||
>
|
||||
<Stack
|
||||
width={"100%"}
|
||||
@@ -95,6 +97,8 @@ const ChartBoxes = ({
|
||||
<ChartBox
|
||||
icon={<IncidentsIcon />}
|
||||
header="Incidents"
|
||||
noDataMessage={noIncidentsMessage}
|
||||
isEmpty={monitor?.groupedDownChecks?.length === 0}
|
||||
>
|
||||
<Stack width={"100%"}>
|
||||
<Box position="relative">
|
||||
|
||||
@@ -282,7 +282,7 @@ const newSemanticColors = {
|
||||
},
|
||||
chatbot: {
|
||||
background: {
|
||||
light: "#112B2B",
|
||||
light: newColors.gray200,
|
||||
dark: "#112B2B",
|
||||
},
|
||||
textAccent: {
|
||||
|
||||
@@ -7,11 +7,18 @@ const nameSchema = joi
|
||||
.string()
|
||||
.max(50)
|
||||
.trim()
|
||||
.pattern(/^[A-Za-z]+$/)
|
||||
.pattern(/^(?=.*[\p{L}\p{Sc}])[\p{L}\p{Sc}\s']+$/u, {
|
||||
name: "name.containsLetterOrSymbol",
|
||||
})
|
||||
.pattern(/^[\p{L}\p{Sc}\s']+$/u, {
|
||||
name: "name.validCharacters",
|
||||
})
|
||||
.messages({
|
||||
"string.empty": "Name is required",
|
||||
"string.max": "Name must be less than 50 characters long",
|
||||
"string.pattern.base": "Name must contain only letters",
|
||||
"string.max": "Name must be less than 50 characters",
|
||||
"string.pattern.name": "Name must contain at least 1 letter or currency symbol",
|
||||
"string.pattern.base.validCharacters":
|
||||
"Can only contain letters, spaces, apostrophes, and currency symbols",
|
||||
});
|
||||
|
||||
const passwordSchema = joi
|
||||
|
||||
+44
-3
@@ -63,8 +63,49 @@
|
||||
"webhookUnsupportedPlatform": "Unsupported platform: {platform}",
|
||||
"distributedStatusHeaderText": "Real-time, real-device coverage",
|
||||
"distributedStatusSubHeaderText": "Powered by millions devices worldwide, view a system performance by global region, country or city",
|
||||
"distributedRightCatagoryTitle": "Monitor",
|
||||
"distributedRightCatagoryDescription": "Mainnet Beta Cluster",
|
||||
"distributedRightCategoryTitle": "Monitor",
|
||||
"distributedStatusServerMonitors": "Server Monitors",
|
||||
"distributedStatusServerMonitorsDescription": "Monitor status of related servers"
|
||||
"distributedStatusServerMonitorsDescription": "Monitor status of related servers",
|
||||
|
||||
"settingsGeneralSettings": "General settings",
|
||||
"settingsDisplayTimezone": "Display timezone",
|
||||
"settingsDisplayTimezoneDescription": "The timezone of the dashboard you publicly display.",
|
||||
"settingsAppearance": "Appearance",
|
||||
"settingsAppearanceDescription": "Switch between light and dark mode, or change user interface language",
|
||||
"settingsThemeMode": "Theme Mode",
|
||||
"settingsLanguage": "Language",
|
||||
"settingsDistributedUptime": "Distributed uptime",
|
||||
"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.",
|
||||
"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.",
|
||||
"settingsClearAllStatsDialogConfirm": "Yes, clear all stats",
|
||||
"settingsDemoMonitors": "Demo monitors",
|
||||
"settingsDemoMonitorsDescription": "Here you can add and remove demo monitors.",
|
||||
"settingsAddDemoMonitors": "Add demo monitors",
|
||||
"settingsAddDemoMonitorsButton": "Add demo monitors",
|
||||
"settingsRemoveAllMonitors": "Remove all monitors",
|
||||
"settingsRemoveAllMonitorsButton": "Remove all monitors",
|
||||
"settingsRemoveAllMonitorsDialogTitle": "Do you want to remove all monitors?",
|
||||
"settingsRemoveAllMonitorsDialogConfirm": "Yes, clear 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",
|
||||
"settingsDevelopedBy": "Developed by Bluewave Labs.",
|
||||
"settingsSave": "Save",
|
||||
"settingsSuccessSaved": "Settings saved successfully",
|
||||
"settingsFailedToSave": "Failed to save settings",
|
||||
"settingsStatsCleared": "Stats cleared successfully",
|
||||
"settingsFailedToClearStats": "Failed to clear stats",
|
||||
"settingsDemoMonitorsAdded": "Successfully added demo monitors",
|
||||
"settingsFailedToAddDemoMonitors": "Failed to add demo monitors",
|
||||
"settingsMonitorsDeleted": "Successfully deleted all monitors",
|
||||
"settingsFailedToDeleteMonitors": "Failed to delete all monitors"
|
||||
}
|
||||
+43
-1
@@ -59,5 +59,47 @@
|
||||
"distributedStatusHeaderText": "Gerçek zamanlı, Gerçek cihazlar kapsamı",
|
||||
"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",
|
||||
"distributedRightCatagoryTitle": "Monitör",
|
||||
"distributedRightCatagoryDescription": "Mainnet Beta Kümesi"
|
||||
"distributedRightCatagoryDescription": "Mainnet Beta Kümesi",
|
||||
|
||||
"settingsGeneralSettings": "Genel ayarlar",
|
||||
"settingsDisplayTimezone": "Görüntüleme saat dilimi",
|
||||
"settingsDisplayTimezoneDescription": "Herkese açık olarak görüntülediğiniz kontrol panelinin saat dilimi.",
|
||||
"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",
|
||||
"settingsLanguage": "Dil",
|
||||
"settingsDistributedUptime": "Dağıtılmış çalışma süresi",
|
||||
"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.",
|
||||
"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.",
|
||||
"settingsClearAllStatsDialogConfirm": "Evet, tüm istatistikleri temizle",
|
||||
"settingsDemoMonitors": "Demo monitörler",
|
||||
"settingsDemoMonitorsDescription": "Burada demo monitörler ekleyebilir ve kaldırabilirsiniz.",
|
||||
"settingsAddDemoMonitors": "Demo monitörler ekle",
|
||||
"settingsAddDemoMonitorsButton": "Demo monitörler ekle",
|
||||
"settingsRemoveAllMonitors": "Tüm monitörleri kaldır",
|
||||
"settingsRemoveAllMonitorsButton": "Tüm monitörleri kaldır",
|
||||
"settingsRemoveAllMonitorsDialogTitle": "Tüm monitörleri kaldırmak istiyor musunuz?",
|
||||
"settingsRemoveAllMonitorsDialogConfirm": "Evet, tüm monitörleri temizle",
|
||||
"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",
|
||||
"settingsDevelopedBy": "Bluewave Labs tarafından geliştirilmiştir.",
|
||||
"settingsSave": "Kaydet",
|
||||
"settingsSuccessSaved": "Ayarlar başarıyla kaydedildi",
|
||||
"settingsFailedToSave": "Ayarlar kaydedilemedi",
|
||||
"settingsStatsCleared": "İstatistikler başarıyla temizlendi",
|
||||
"settingsFailedToClearStats": "İstatistikler temizlenemedi",
|
||||
"settingsDemoMonitorsAdded": "Demo monitörler başarıyla eklendi",
|
||||
"settingsFailedToAddDemoMonitors": "Demo monitörler eklenemedi",
|
||||
"settingsMonitorsDeleted": "Tüm monitörler başarıyla silindi",
|
||||
"settingsFailedToDeleteMonitors": "Monitörler silinemedi"
|
||||
}
|
||||
Reference in new issue
Block a user