Merge branch 'develop' of github.com:bluewave-labs/Checkmate into enhancement/uptime-graph-avg-max-response
@@ -3,4 +3,5 @@
|
||||
.VSCodeCounter
|
||||
*.sh
|
||||
mongo
|
||||
node_modules/
|
||||
node_modules/
|
||||
docs/architecture
|
||||
@@ -2472,11 +2472,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.13.4",
|
||||
"version": "1.13.5",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
|
||||
"integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
"follow-redirects": "^1.15.11",
|
||||
"form-data": "^4.0.5",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, type CSSProperties } from "react";
|
||||
import { type CSSProperties } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
@@ -6,7 +6,6 @@ import { ThemeProvider } from "@emotion/react";
|
||||
import lightTheme from "./Utils/Theme/lightTheme";
|
||||
import darkTheme from "./Utils/Theme/darkTheme";
|
||||
import { CssBaseline, GlobalStyles } from "@mui/material";
|
||||
import { logger } from "./Utils/Logger"; // Import the logger
|
||||
import { Routes } from "./Routes";
|
||||
import AppLayout from "@/Components/v2/layout/AppLayout";
|
||||
import type { RootState } from "@/Types/state";
|
||||
@@ -14,13 +13,6 @@ import type { RootState } from "@/Types/state";
|
||||
function App() {
|
||||
const mode = useSelector((state: RootState) => state.ui.mode);
|
||||
|
||||
// Cleanup
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
logger.cleanup();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const theme = mode === "light" ? lightTheme : darkTheme;
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
.wallet-adapter-dropdown {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: var(--env-var-spacing-1);
|
||||
}
|
||||
|
||||
.wallet-adapter-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: auto;
|
||||
height: var(--env-var-height-2) !important;
|
||||
font-size: var(--env-var-font-size-medium-plus) !important;
|
||||
font-weight: 500 !important;
|
||||
margin: 0;
|
||||
padding: calc(
|
||||
(var(--env-var-height-2) - var(--env-var-font-size-medium-plus) * 1.2) / 2
|
||||
)
|
||||
var(--env-var-spacing-1);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.wallet-adapter-modal-title {
|
||||
font-size: var(--font-size-h5) !important;
|
||||
}
|
||||
|
||||
/* Separator styling */
|
||||
.wallet-adapter-modal-divider {
|
||||
background-color: var(--border-color) !important;
|
||||
margin: var(--spacing-md) 0 !important;
|
||||
}
|
||||
|
||||
/* Responsive fixes */
|
||||
@media (max-width: 1200px) {
|
||||
.wallet-adapter-button {
|
||||
font-size: var(--env-var-font-size-medium) !important;
|
||||
padding: calc((var(--env-var-height-2) - var(--env-var-font-size-medium) * 1.2) / 2)
|
||||
var(--env-var-spacing-1-minus) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.wallet-adapter-modal-wrapper {
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
.wallet-adapter-modal-divider {
|
||||
margin: var(--spacing-sm) 0 !important;
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
// import { useMemo } from "react";
|
||||
// import { ConnectionProvider, WalletProvider } from "@solana/wallet-adapter-react";
|
||||
// import { WalletAdapterNetwork } from "@solana/wallet-adapter-base";
|
||||
// import {
|
||||
// UnsafeBurnerWalletAdapter,
|
||||
// PhantomWalletAdapter,
|
||||
// } from "@solana/wallet-adapter-wallets";
|
||||
|
||||
// import { WalletModalProvider } from "@solana/wallet-adapter-react-ui";
|
||||
// import { clusterApiUrl } from "@solana/web3.js";
|
||||
// import PropTypes from "prop-types";
|
||||
// import "./index.css";
|
||||
|
||||
// // Default styles that can be overridden by your app
|
||||
// import "@solana/wallet-adapter-react-ui/styles.css";
|
||||
|
||||
// export const Wallet = ({ children }) => {
|
||||
// // The network can be set to 'devnet', 'testnet', or 'mainnet-beta'.
|
||||
// const network = WalletAdapterNetwork.Mainnet;
|
||||
|
||||
// // You can also provide a custom RPC endpoint.
|
||||
// const endpoint = useMemo(() => clusterApiUrl(network), [network]);
|
||||
|
||||
// const wallets = useMemo(
|
||||
// () => [new PhantomWalletAdapter()],
|
||||
// // eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// [network]
|
||||
// );
|
||||
|
||||
// return (
|
||||
// <ConnectionProvider endpoint={endpoint}>
|
||||
// <WalletProvider
|
||||
// wallets={wallets}
|
||||
// autoConnect
|
||||
// >
|
||||
// <WalletModalProvider>{children}</WalletModalProvider>
|
||||
// </WalletProvider>
|
||||
// </ConnectionProvider>
|
||||
// );
|
||||
// };
|
||||
|
||||
// Wallet.propTypes = {
|
||||
// children: PropTypes.node,
|
||||
// };
|
||||
|
||||
const Wallet = ({ children }) => {
|
||||
return children;
|
||||
};
|
||||
|
||||
export default Wallet;
|
||||
@@ -1,9 +0,0 @@
|
||||
.alert {
|
||||
margin: 0;
|
||||
width: fit-content;
|
||||
}
|
||||
.alert,
|
||||
.alert button,
|
||||
.alert .MuiTypography-root {
|
||||
font-size: var(--env-var-font-size-medium);
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Box, Button, IconButton, Stack, Typography } from "@mui/material";
|
||||
import Icon from "../Icon";
|
||||
import "./index.css";
|
||||
|
||||
/**
|
||||
* Icons mapping for different alert variants.
|
||||
* @type {Object<string, JSX.Element>}
|
||||
*/
|
||||
|
||||
const icons = {
|
||||
info: (
|
||||
<Icon
|
||||
name="Info"
|
||||
size={24}
|
||||
/>
|
||||
),
|
||||
error: (
|
||||
<Icon
|
||||
name="AlertCircle"
|
||||
size={24}
|
||||
/>
|
||||
),
|
||||
warning: (
|
||||
<Icon
|
||||
name="AlertTriangle"
|
||||
size={24}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Object} props
|
||||
* @param {'info' | 'error' | 'warning'} props.variant - The type of alert.
|
||||
* @param {string} [props.title] - The title of the alert.
|
||||
* @param {string} [props.body] - The body text of the alert.
|
||||
* @param {boolean} [props.isToast] - Indicates if the alert is used as a toast notification.
|
||||
* @param {boolean} [props.hasIcon] - Whether to display an icon in the alert.
|
||||
* @param {function} props.onClick - Toast dismiss function.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
|
||||
const Alert = ({ variant, title, body, isToast, hasIcon = true, onClick }) => {
|
||||
const theme = useTheme();
|
||||
/* TODO
|
||||
Do we need other variants for alert?
|
||||
*/
|
||||
|
||||
const text = theme.palette.secondary.contrastText;
|
||||
const border = theme.palette.alert.contrastText;
|
||||
const bg = theme.palette.alert.main;
|
||||
const icon = icons[variant];
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="flex-start"
|
||||
alignItems={hasIcon ? "" : "center"}
|
||||
className="alert row-stack"
|
||||
gap={theme.spacing(8)}
|
||||
sx={{
|
||||
padding: hasIcon ? theme.spacing(8) : `${theme.spacing(4)} ${theme.spacing(8)}`,
|
||||
backgroundColor: bg,
|
||||
border: `solid 1px ${border}`,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
}}
|
||||
>
|
||||
{hasIcon && <Box sx={{ color: text }}>{icon}</Box>}
|
||||
<Stack
|
||||
direction="column"
|
||||
gap="2px"
|
||||
sx={{ flex: 1 }}
|
||||
>
|
||||
{title && (
|
||||
<Typography sx={{ fontWeight: "700", color: `${text}` }}>{title}</Typography>
|
||||
)}
|
||||
{body && (
|
||||
<Typography sx={{ fontWeight: "400", color: `${text}` }}>{body}</Typography>
|
||||
)}
|
||||
{hasIcon && isToast && (
|
||||
<Button
|
||||
variant="text"
|
||||
color="info"
|
||||
onClick={onClick}
|
||||
sx={{
|
||||
fontWeight: "600",
|
||||
width: "fit-content",
|
||||
mt: theme.spacing(4),
|
||||
padding: 0,
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
Dismiss
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
{isToast && (
|
||||
<IconButton
|
||||
onClick={onClick}
|
||||
sx={{
|
||||
alignSelf: "flex-start",
|
||||
ml: "auto",
|
||||
mr: "-5px",
|
||||
mt: hasIcon ? "-5px" : 0,
|
||||
padding: "5px",
|
||||
"&:focus": {
|
||||
outline: "none",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name="X"
|
||||
size={20}
|
||||
/>
|
||||
</IconButton>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
Alert.propTypes = {
|
||||
variant: PropTypes.oneOf(["info", "error", "warning"]).isRequired,
|
||||
title: PropTypes.string,
|
||||
body: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
isToast: PropTypes.bool,
|
||||
hasIcon: PropTypes.bool,
|
||||
onClick: function (props, propName, componentName) {
|
||||
if (props.isToast && !props[propName]) {
|
||||
return new Error(
|
||||
`Prop '${propName}' is required when 'isToast' is true in '${componentName}'.`
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
export default Alert;
|
||||
@@ -6,7 +6,6 @@
|
||||
min-height: 16px;
|
||||
}
|
||||
.MuiBreadcrumbs-root .MuiBreadcrumbs-li a {
|
||||
font-size: var(--env-var-font-size-medium);
|
||||
font-weight: 400;
|
||||
}
|
||||
.MuiBreadcrumbs-root .MuiBreadcrumbs-li:not(:last-child) {
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { logger } from "../../../Utils/Logger.js";
|
||||
import { useLazyGet } from "@/Hooks/UseApi";
|
||||
|
||||
const withAdminCheck = (WrappedComponent) => {
|
||||
const WithAdminCheck = (props) => {
|
||||
const navigate = useNavigate();
|
||||
const [superAdminExists, setSuperAdminExists] = useState(false);
|
||||
const [hasChecked, setHasChecked] = useState(false);
|
||||
const { get: checkSuperAdmin, loading: isChecking } = useLazyGet();
|
||||
|
||||
useEffect(() => {
|
||||
checkSuperAdmin("/auth/users/superadmin")
|
||||
.then((response) => {
|
||||
if (response?.data === true) {
|
||||
navigate("/login");
|
||||
} else {
|
||||
setSuperAdminExists(false);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error(error);
|
||||
})
|
||||
.finally(() => {
|
||||
setHasChecked(true);
|
||||
});
|
||||
}, [navigate, checkSuperAdmin]);
|
||||
|
||||
if (!hasChecked || isChecking) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<WrappedComponent
|
||||
{...props}
|
||||
superAdminExists={superAdminExists}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const wrappedComponentName =
|
||||
WrappedComponent.displayName || WrappedComponent.name || "Component";
|
||||
WithAdminCheck.displayName = `WithAdminCheck(${wrappedComponentName})`;
|
||||
|
||||
return WithAdminCheck;
|
||||
};
|
||||
|
||||
export default withAdminCheck;
|
||||
@@ -64,7 +64,6 @@ const Select = ({
|
||||
const theme = useTheme();
|
||||
const getItemValue = (item) => item?._id ?? item?.id;
|
||||
const itemStyles = {
|
||||
fontSize: "var(--env-var-font-size-medium)",
|
||||
color: theme.palette.primary.contrastTextTertiary,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
margin: theme.spacing(2),
|
||||
|
||||
@@ -18,7 +18,6 @@ export const HttpAdornment = ({ https }) => {
|
||||
>
|
||||
<Typography
|
||||
component="h5"
|
||||
paddingRight={"var(--env-var-spacing-1-minus)"}
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
sx={{ lineHeight: 1, opacity: 0.8 }}
|
||||
>
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import { Navigate } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
/**
|
||||
* ProtectedRoute is a wrapper component that ensures only authenticated users
|
||||
* can access the wrapped content. It checks authentication status (e.g., from Redux or Context).
|
||||
* If the user is authenticated, it renders the children; otherwise, it redirects to the login page.
|
||||
*
|
||||
* @param {Object} props - The props passed to the ProtectedRoute component.
|
||||
* @param {React.ReactNode} props.children - The children to render if the user is authenticated.
|
||||
* @returns {React.ReactElement} The children wrapped in a protected route or a redirect to the login page.
|
||||
*/
|
||||
|
||||
const ProtectedRoute = ({ children }) => {
|
||||
const authState = useSelector((state) => state.auth);
|
||||
|
||||
return authState.authToken ? (
|
||||
children
|
||||
) : (
|
||||
<Navigate
|
||||
to="/login"
|
||||
replace
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
ProtectedRoute.propTypes = {
|
||||
children: PropTypes.element.isRequired,
|
||||
};
|
||||
|
||||
export default ProtectedRoute;
|
||||
@@ -1,35 +0,0 @@
|
||||
import { Navigate } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
/**
|
||||
* ProtectedRoute is a wrapper component that ensures only authenticated users
|
||||
* can access the wrapped content. It checks authentication status (e.g., from Redux or Context).
|
||||
* If the user is authenticated, it renders the children; otherwise, it redirects to the login page.
|
||||
*
|
||||
* @param {Object} props - The props passed to the ProtectedRoute component.
|
||||
* @param {React.ReactNode} props.children - The children to render if the user is authenticated.
|
||||
* @returns {React.ReactElement} The children wrapped in a protected route or a redirect to the login page.
|
||||
*/
|
||||
|
||||
const RoleProtectedRoute = ({ roles, children }) => {
|
||||
const authState = useSelector((state) => state.auth);
|
||||
const userRoles = authState?.user?.role || [];
|
||||
const canAccess = userRoles.some((role) => roles.includes(role));
|
||||
|
||||
return canAccess ? (
|
||||
children
|
||||
) : (
|
||||
<Navigate
|
||||
to="/uptime"
|
||||
replace
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
RoleProtectedRoute.propTypes = {
|
||||
children: PropTypes.element.isRequired,
|
||||
roles: PropTypes.array,
|
||||
};
|
||||
|
||||
export default RoleProtectedRoute;
|
||||
@@ -1,98 +0,0 @@
|
||||
import { useTheme } from "@mui/material";
|
||||
import "./index.css";
|
||||
|
||||
const SunAndMoonIcon = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<svg
|
||||
className="sun-and-moon"
|
||||
aria-hidden="true"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<mask
|
||||
className="moon"
|
||||
id="moon-mask"
|
||||
>
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
fill="#fff"
|
||||
/>
|
||||
<circle
|
||||
cx="24"
|
||||
cy="10"
|
||||
r="6"
|
||||
fill="#000"
|
||||
/>
|
||||
</mask>
|
||||
<circle
|
||||
className="sun"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="6"
|
||||
fill={theme.palette.primary.contrastTextSecondary}
|
||||
mask="url(#moon-mask)"
|
||||
/>
|
||||
<g
|
||||
className="sun-beams"
|
||||
stroke={theme.palette.primary.contrastTextSecondary}
|
||||
>
|
||||
<line
|
||||
x1="12"
|
||||
y1="1"
|
||||
x2="12"
|
||||
y2="3"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
y1="21"
|
||||
x2="12"
|
||||
y2="23"
|
||||
/>
|
||||
<line
|
||||
x1="4.22"
|
||||
y1="4.22"
|
||||
x2="5.64"
|
||||
y2="5.64"
|
||||
/>
|
||||
<line
|
||||
x1="18.36"
|
||||
y1="18.36"
|
||||
x2="19.78"
|
||||
y2="19.78"
|
||||
/>
|
||||
<line
|
||||
x1="1"
|
||||
y1="12"
|
||||
x2="3"
|
||||
y2="12"
|
||||
/>
|
||||
<line
|
||||
x1="21"
|
||||
y1="12"
|
||||
x2="23"
|
||||
y2="12"
|
||||
/>
|
||||
<line
|
||||
x1="4.22"
|
||||
y1="19.78"
|
||||
x2="5.64"
|
||||
y2="18.36"
|
||||
/>
|
||||
<line
|
||||
x1="18.36"
|
||||
y1="5.64"
|
||||
x2="19.78"
|
||||
y2="4.22"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default SunAndMoonIcon;
|
||||
@@ -1,64 +0,0 @@
|
||||
.sun-and-moon > :is(.moon, .sun, .sun-beams) {
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.theme-toggle .sun-and-moon > .sun-beams {
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.theme-dark .sun-and-moon > .sun {
|
||||
transform: scale(1.75);
|
||||
}
|
||||
|
||||
.theme-dark .sun-and-moon > .sun-beams {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.theme-dark .sun-and-moon > .moon > circle {
|
||||
transform: translateX(-7px);
|
||||
}
|
||||
|
||||
@supports (cx: 1) {
|
||||
.theme-dark .sun-and-moon > .moon > circle {
|
||||
cx: 17;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.sun-and-moon > .sun {
|
||||
transition: transform 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55);
|
||||
}
|
||||
|
||||
.sun-and-moon > .sun-beams {
|
||||
transition:
|
||||
transform 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55),
|
||||
opacity 0.5s cubic-bezier(0.25, 0.1, 0.25, 1);
|
||||
}
|
||||
|
||||
.sun-and-moon .moon > circle {
|
||||
transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
@supports (cx: 1) {
|
||||
.sun-and-moon .moon > circle {
|
||||
transition: cx 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-dark .sun-and-moon > .sun {
|
||||
transition-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);
|
||||
transition-duration: 0.25s;
|
||||
transform: scale(1.75);
|
||||
}
|
||||
|
||||
.theme-dark .sun-and-moon > .sun-beams {
|
||||
transition-duration: 0.15s;
|
||||
transform: rotateZ(-25deg);
|
||||
}
|
||||
|
||||
.theme-dark .sun-and-moon > .moon > circle {
|
||||
transition-duration: 0.5s;
|
||||
transition-delay: 0.25s;
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* ThemeSwitch Component
|
||||
* Dark and Light Theme Switch
|
||||
* Original Code: https://web.dev/patterns/theming/theme-switch
|
||||
* License: Apache License 2.0
|
||||
* Copyright © Google LLC
|
||||
*
|
||||
* This code has been adapted for use in this project.
|
||||
* Apache License: https://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
|
||||
import { IconButton } from "@mui/material";
|
||||
import SunAndMoonIcon from "./SunAndMoonIcon.jsx";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { setMode } from "../../../Features/UI/uiSlice.js";
|
||||
import "./index.css";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const ThemeSwitch = ({ width = 48, height = 48, color }) => {
|
||||
const mode = useSelector((state) => state.ui.mode);
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const toggleTheme = () => {
|
||||
dispatch(setMode(mode === "light" ? "dark" : "light"));
|
||||
};
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
id="theme-toggle"
|
||||
title={t("common.buttons.toggleTheme")}
|
||||
className={`theme-${mode}`}
|
||||
aria-label="auto"
|
||||
aria-live="polite"
|
||||
onClick={toggleTheme}
|
||||
sx={{
|
||||
width,
|
||||
height,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
"& svg >:is(circle, g)": {
|
||||
fill: color,
|
||||
stroke: color,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SunAndMoonIcon />
|
||||
</IconButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeSwitch;
|
||||
@@ -31,6 +31,7 @@ export const Avatar = ({ src, small, sx, onClick = () => {} }: AvatarProps) => {
|
||||
alt={`${user?.firstName} ${user?.lastName}`}
|
||||
src={src ? src : user?.avatarImage ? image : undefined}
|
||||
sx={{
|
||||
color: theme.palette.primary.contrastText,
|
||||
fontSize: small ? "16px" : "22px",
|
||||
fontWeight: 400,
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
|
||||
@@ -23,7 +23,6 @@ export const MonitorStatus = ({ monitor }: { monitor: Monitor }) => {
|
||||
overflow={"hidden"}
|
||||
textOverflow={"ellipsis"}
|
||||
whiteSpace={"nowrap"}
|
||||
maxWidth={isSmall ? "100%" : "calc((100vw - var(--env-var-width-2)) / 2)"}
|
||||
>
|
||||
{monitor.name}
|
||||
</Typography>
|
||||
@@ -40,7 +39,6 @@ export const MonitorStatus = ({ monitor }: { monitor: Monitor }) => {
|
||||
overflow={"hidden"}
|
||||
textOverflow={"ellipsis"}
|
||||
whiteSpace={"nowrap"}
|
||||
maxWidth={isSmall ? "100%" : "calc((100vw - var(--env-var-width-2)) / 2)"}
|
||||
>
|
||||
{formatUrl(monitor?.url)}
|
||||
</Typography>
|
||||
|
||||
@@ -1,4 +1,17 @@
|
||||
import { Box, Stack, useTheme } from "@mui/material";
|
||||
import Box from "@mui/material/Box";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { useTheme, keyframes } from "@mui/material";
|
||||
|
||||
const ripple = keyframes`
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: scale(0);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(2);
|
||||
}
|
||||
`;
|
||||
|
||||
interface PulseDotProps {
|
||||
color: string;
|
||||
@@ -28,7 +41,7 @@ export const PulseDot = ({ color }: PulseDotProps) => {
|
||||
height: "100%",
|
||||
backgroundColor: "inherit",
|
||||
borderRadius: "50%",
|
||||
animation: "ripple 1.8s ease-out infinite",
|
||||
animation: `${ripple} 1.8s ease-out infinite`,
|
||||
},
|
||||
"&::after": {
|
||||
content: `""`,
|
||||
|
||||
@@ -2,7 +2,6 @@ import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Box from "@mui/material/Box";
|
||||
import { BaseBox } from "@/Components/v2/design-elements";
|
||||
import Background from "@/assets/Images/background-grid.svg?react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { SxProps } from "@mui/material";
|
||||
|
||||
@@ -18,6 +17,7 @@ export const BGBox = ({ children, sx }: StatusBoxProps) => {
|
||||
return (
|
||||
<BaseBox
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.default,
|
||||
overflow: "hidden",
|
||||
position: "relative",
|
||||
flex: 1,
|
||||
@@ -27,11 +27,23 @@ export const BGBox = ({ children, sx }: StatusBoxProps) => {
|
||||
>
|
||||
<Box
|
||||
position="absolute"
|
||||
top="-10%"
|
||||
left="5%"
|
||||
>
|
||||
<Background />
|
||||
</Box>
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
sx={{
|
||||
pointerEvents: "none",
|
||||
backgroundImage: `
|
||||
linear-gradient(${theme.palette.divider} 1px, transparent 1px),
|
||||
linear-gradient(90deg, ${theme.palette.divider} 1px, transparent 1px)
|
||||
`,
|
||||
backgroundSize: "24px 24px",
|
||||
maskImage:
|
||||
"linear-gradient(135deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 30%, rgba(0,0,0,0.4) 100%)",
|
||||
WebkitMaskImage:
|
||||
"linear-gradient(135deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 30%, rgba(0,0,0,0.4) 100%)",
|
||||
}}
|
||||
/>
|
||||
{children}
|
||||
</BaseBox>
|
||||
);
|
||||
@@ -105,6 +117,40 @@ export const PausedStatusBox = ({ n }: { n: number }) => {
|
||||
/>
|
||||
);
|
||||
};
|
||||
export const MaintenanceStatusBox = ({ n }: { n: number }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<StatusBox
|
||||
label={t("pages.common.monitors.status.maintenance")}
|
||||
n={n}
|
||||
color={theme.palette.warning.light}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export const InitializingStatusBox = ({ n }: { n: number }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<StatusBox
|
||||
label={t("pages.common.monitors.status.initializing")}
|
||||
n={n}
|
||||
color={theme.palette.warning.light}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export const BreachedStatusBox = ({ n }: { n: number }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<StatusBox
|
||||
label={t("pages.common.monitors.status.breached")}
|
||||
n={n}
|
||||
color={theme.palette.warning.main}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const TotalChecksBox = ({ n }: { n: number }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
@@ -138,15 +184,3 @@ export const UpChecksBox = ({ n }: { n: number }) => {
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const InitializingStatusBox = ({ n }: { n: number }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<StatusBox
|
||||
label={t("pages.common.monitors.status.initializing")}
|
||||
n={n}
|
||||
color={theme.palette.warning.light}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -11,33 +11,26 @@ import { useTranslation } from "react-i18next";
|
||||
export const ValueTypes = ["positive", "negative", "neutral"] as const;
|
||||
export type ValueType = (typeof ValueTypes)[number];
|
||||
|
||||
export const StatusLabel = ({
|
||||
status,
|
||||
isActive,
|
||||
sx,
|
||||
}: {
|
||||
status: MonitorStatus;
|
||||
isActive?: boolean;
|
||||
sx?: SxProps;
|
||||
}) => {
|
||||
export const StatusLabel = ({ status, sx }: { status: MonitorStatus; sx?: SxProps }) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const palette = getStatusPalette(status);
|
||||
|
||||
const determineStatus = (
|
||||
isActive: boolean | undefined,
|
||||
status: MonitorStatus
|
||||
): string => {
|
||||
if (isActive === false) {
|
||||
const determineStatus = (status: MonitorStatus): string => {
|
||||
if (status === "up") {
|
||||
return t("pages.common.monitors.status.up");
|
||||
} else if (status === "down") {
|
||||
return t("pages.common.monitors.status.down");
|
||||
} else if (status === "breached") {
|
||||
return t("pages.common.monitors.status.breached");
|
||||
} else if (status === "maintenance") {
|
||||
return t("pages.common.monitors.status.maintenance");
|
||||
} else if (status === "paused") {
|
||||
return t("pages.common.monitors.status.paused");
|
||||
} else if (status === "initializing") {
|
||||
return t("pages.common.monitors.status.initializing");
|
||||
}
|
||||
|
||||
if (status === true) {
|
||||
return t("pages.common.monitors.status.up");
|
||||
}
|
||||
if (status === false) {
|
||||
return t("pages.common.monitors.status.down");
|
||||
}
|
||||
return t("pages.common.monitors.status.initializing");
|
||||
};
|
||||
|
||||
@@ -64,9 +57,7 @@ export const StatusLabel = ({
|
||||
borderRadius="50%"
|
||||
marginRight="5px"
|
||||
/>
|
||||
<Typography textTransform={"capitalize"}>
|
||||
{determineStatus(isActive, status)}
|
||||
</Typography>
|
||||
<Typography textTransform={"capitalize"}>{determineStatus(status)}</Typography>
|
||||
</BaseBox>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import i18n from "../../../Utils/i18n.js";
|
||||
import i18n from "@/Utils/i18n.js";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useEffect } from "react";
|
||||
import type { RootState } from "@/store";
|
||||
const I18nLoader = () => {
|
||||
const language = useSelector((state) => state.ui.language ?? "en");
|
||||
const language = useSelector((state: RootState) => state.ui.language ?? "en");
|
||||
|
||||
useEffect(() => {
|
||||
if (language && i18n.language !== language) {
|
||||
@@ -38,11 +38,11 @@ export const ImageUpload = ({
|
||||
const isValidType = accept.some((type) => file.type.includes(type));
|
||||
const isValidSize = file.size <= maxSize;
|
||||
if (!isValidType) {
|
||||
setLocalError(t("common.errors.invalidFileFormat"));
|
||||
setLocalError(t("components.imageUpload.errors.invalidFileFormat"));
|
||||
return;
|
||||
}
|
||||
if (!isValidSize) {
|
||||
setLocalError(t("common.errors.invalidFileSize"));
|
||||
setLocalError(t("components.imageUpload.errors.invalidFileSize"));
|
||||
return;
|
||||
}
|
||||
setLocalError(null);
|
||||
@@ -137,15 +137,15 @@ export const ImageUpload = ({
|
||||
color="primary"
|
||||
fontWeight={500}
|
||||
>
|
||||
{t("common.imageUpload.clickToUpload")}
|
||||
{t("components.imageUpload.clickToUpload")}
|
||||
</Typography>{" "}
|
||||
{t("common.imageUpload.orDragAndDrop")}
|
||||
{t("components.imageUpload.orDragAndDrop")}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="text.disabled"
|
||||
>
|
||||
{accept.join(", ").toUpperCase()} • {t("common.imageUpload.maxSize")}{" "}
|
||||
{accept.join(", ").toUpperCase()} • {t("components.imageUpload.maxSize")}{" "}
|
||||
{Math.round(maxSize / 1024 / 1024)}MB
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
@@ -41,7 +41,10 @@ export const SliderInput = forwardRef<HTMLSpanElement, SliderInputProps>(
|
||||
"& .MuiSlider-thumb": {
|
||||
backgroundColor: "#fff",
|
||||
"&:hover, &.Mui-focusVisible": {
|
||||
boxShadow: `0 0 0 8px ${theme.palette.primary.main}20`,
|
||||
boxShadow: "none",
|
||||
},
|
||||
"&:active": {
|
||||
boxShadow: "none",
|
||||
},
|
||||
},
|
||||
"& .MuiSlider-valueLabel": {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
UpStatusBox,
|
||||
DownStatusBox,
|
||||
PausedStatusBox,
|
||||
InitializingStatusBox,
|
||||
BreachedStatusBox,
|
||||
} from "@/Components/v2/design-elements";
|
||||
import Stack from "@mui/material/Stack";
|
||||
|
||||
import type { MonitorsSummary } from "@/Types/Monitor";
|
||||
import { useTheme } from "@mui/material";
|
||||
|
||||
interface MonitorsSummaryProps {
|
||||
summary: MonitorsSummary | null;
|
||||
showBreached?: boolean;
|
||||
}
|
||||
|
||||
export const HeaderMonitorsSummary = ({
|
||||
summary,
|
||||
showBreached = false,
|
||||
}: MonitorsSummaryProps) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Stack
|
||||
direction={{ xs: "column", md: "row" }}
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<UpStatusBox n={summary?.upMonitors || 0} />
|
||||
<DownStatusBox n={summary?.downMonitors || 0} />
|
||||
{showBreached && <BreachedStatusBox n={summary?.breachedMonitors || 0} />}
|
||||
<PausedStatusBox n={summary?.pausedMonitors || 0} />
|
||||
<InitializingStatusBox n={summary?.initializingMonitors || 0} />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -3,7 +3,7 @@ import Typography from "@mui/material/Typography";
|
||||
import { BaseChart } from "@/Components/v2/design-elements";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useSelector } from "react-redux";
|
||||
import { formatDateWithTz } from "@/Utils/timeUtilsLegacy";
|
||||
import { formatDateWithTz } from "@/Utils/TimeUtils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ResponsiveContainer, BarChart, XAxis, Bar, Cell, Tooltip } from "recharts";
|
||||
import { getResponseTimeColor } from "@/Utils/MonitorUtils";
|
||||
|
||||
@@ -11,3 +11,4 @@ export * from "./charts/PiePageSpeedLegend";
|
||||
export * from "./charts/HistogramPageSpeedDetails";
|
||||
export * from "./charts/HistogramPageSpeedDetailsTooltip";
|
||||
export * from "./charts/HistogramInfrastructure";
|
||||
export * from "./HeaderMonitorsSummary";
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Navigate } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
import type { RootState } from "@/Types/state";
|
||||
import type { UserRole } from "@/Types/User";
|
||||
|
||||
interface ProtectedRouteProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
|
||||
const authState = useSelector((state: RootState) => state.auth);
|
||||
|
||||
return authState.authToken ? (
|
||||
children
|
||||
) : (
|
||||
<Navigate
|
||||
to="/login"
|
||||
replace
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface RoleProtectedRouteProps {
|
||||
roles: UserRole[];
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const RoleProtectedRoute = ({ roles, children }: RoleProtectedRouteProps) => {
|
||||
const authState = useSelector((state: RootState) => state.auth);
|
||||
const userRoles = authState?.user?.role || [];
|
||||
const canAccess = userRoles.some((role) => roles.includes(role));
|
||||
|
||||
return canAccess ? (
|
||||
children
|
||||
) : (
|
||||
<Navigate
|
||||
to="/uptime"
|
||||
replace
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -76,7 +76,7 @@ export const AuthFooter = ({ collapsed, accountMenuItems }: AuthFooterProps) =>
|
||||
alignItems="center"
|
||||
py={theme.spacing(4)}
|
||||
px={theme.spacing(8)}
|
||||
gap={theme.spacing(2)}
|
||||
gap={theme.spacing(4)}
|
||||
>
|
||||
<Avatar
|
||||
small
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useEffect } from "react";
|
||||
import Backdrop from "@mui/material/Backdrop";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import List from "@mui/material/List";
|
||||
import Divider from "@mui/material/Divider";
|
||||
@@ -51,71 +52,80 @@ export const Sidebar = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack
|
||||
component="aside"
|
||||
position={isSmall ? "fixed" : "sticky"}
|
||||
top={0}
|
||||
left={0}
|
||||
minHeight={"100vh"}
|
||||
maxHeight={"100vh"}
|
||||
paddingTop={theme.spacing(6)}
|
||||
paddingBottom={theme.spacing(6)}
|
||||
gap={theme.spacing(6)}
|
||||
borderRight={`1px solid ${theme.palette.divider}`}
|
||||
width={width}
|
||||
sx={{
|
||||
transition: transition,
|
||||
zIndex: isSmall ? (t) => t.zIndex.drawer : "auto",
|
||||
}}
|
||||
>
|
||||
<List
|
||||
component="nav"
|
||||
disablePadding
|
||||
<>
|
||||
<Backdrop
|
||||
open={!collapsed && isSmall}
|
||||
onClick={() => dispatch(setCollapsed({ collapsed: true }))}
|
||||
sx={{ zIndex: 999 }}
|
||||
/>
|
||||
<Stack
|
||||
component="aside"
|
||||
position={isSmall ? "fixed" : "sticky"}
|
||||
top={0}
|
||||
left={0}
|
||||
minHeight={"100vh"}
|
||||
maxHeight={"100vh"}
|
||||
paddingTop={theme.spacing(6)}
|
||||
paddingBottom={theme.spacing(6)}
|
||||
gap={theme.spacing(6)}
|
||||
borderRight={`1px solid ${theme.palette.divider}`}
|
||||
width={width}
|
||||
sx={{
|
||||
px: theme.spacing(6),
|
||||
flex: 1,
|
||||
touchAction: "none",
|
||||
transition: transition,
|
||||
zIndex: 1000,
|
||||
backdropFilter: "blur(8px)",
|
||||
}}
|
||||
>
|
||||
<Logo
|
||||
pt={theme.spacing(8)}
|
||||
pb={theme.spacing(10)}
|
||||
/>
|
||||
{menu.map((item) => {
|
||||
const selected = location.pathname.startsWith(`/${item.path}`);
|
||||
return (
|
||||
<NavItem
|
||||
key={item.path}
|
||||
item={item}
|
||||
selected={selected}
|
||||
onClick={() => handleNavClick(item.path)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
<StarPrompt />
|
||||
<List
|
||||
component="nav"
|
||||
disablePadding
|
||||
sx={{ px: theme.spacing(6) }}
|
||||
>
|
||||
{bottomMenu.map((item) => {
|
||||
const selected = location.pathname.startsWith(`/${item.path}`);
|
||||
return (
|
||||
<NavItem
|
||||
key={item.path}
|
||||
item={item}
|
||||
selected={selected}
|
||||
onClick={() => handleNavClick(item.path)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
<Divider sx={{ borderColor: theme.palette.divider }} />
|
||||
<List
|
||||
component="nav"
|
||||
disablePadding
|
||||
sx={{
|
||||
px: theme.spacing(6),
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
<Logo
|
||||
pt={theme.spacing(8)}
|
||||
pb={theme.spacing(10)}
|
||||
/>
|
||||
{menu.map((item) => {
|
||||
const selected = location.pathname.startsWith(`/${item.path}`);
|
||||
return (
|
||||
<NavItem
|
||||
key={item.path}
|
||||
item={item}
|
||||
selected={selected}
|
||||
onClick={() => handleNavClick(item.path)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
<StarPrompt />
|
||||
<List
|
||||
component="nav"
|
||||
disablePadding
|
||||
sx={{ px: theme.spacing(6) }}
|
||||
>
|
||||
{bottomMenu.map((item) => {
|
||||
const selected = location.pathname.startsWith(`/${item.path}`);
|
||||
return (
|
||||
<NavItem
|
||||
key={item.path}
|
||||
item={item}
|
||||
selected={selected}
|
||||
onClick={() => handleNavClick(item.path)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
<Divider sx={{ borderColor: theme.palette.divider }} />
|
||||
|
||||
<AuthFooter
|
||||
collapsed={collapsed}
|
||||
accountMenuItems={accountMenu}
|
||||
/>
|
||||
</Stack>
|
||||
<AuthFooter
|
||||
collapsed={collapsed}
|
||||
accountMenuItems={accountMenu}
|
||||
/>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
const initialMode = window?.matchMedia?.("(prefers-color-scheme: dark)")?.matches
|
||||
? "dark"
|
||||
: "light";
|
||||
|
||||
const initialState = {
|
||||
monitors: {
|
||||
rowsPerPage: 10,
|
||||
},
|
||||
team: {
|
||||
rowsPerPage: 5,
|
||||
},
|
||||
maintenance: {
|
||||
rowsPerPage: 5,
|
||||
},
|
||||
infrastructure: {
|
||||
rowsPerPage: 5,
|
||||
},
|
||||
logs: {
|
||||
rowsPerPage: 15,
|
||||
},
|
||||
sidebar: {
|
||||
collapsed: false,
|
||||
},
|
||||
mode: initialMode,
|
||||
showURL: false,
|
||||
greeting: { index: 0, lastUpdate: null },
|
||||
timezone: "America/Toronto",
|
||||
distributedUptimeEnabled: false,
|
||||
language: "en",
|
||||
starPromptOpen: true,
|
||||
chartType: "histogram",
|
||||
};
|
||||
|
||||
const uiSlice = createSlice({
|
||||
name: "ui",
|
||||
initialState,
|
||||
reducers: {
|
||||
setDistributedUptimeEnabled: (state, action) => {
|
||||
state.distributedUptimeEnabled = action.payload;
|
||||
},
|
||||
setRowsPerPage: (state, action) => {
|
||||
const { table, value } = action.payload;
|
||||
if (!state[table]) {
|
||||
state[table] = {};
|
||||
}
|
||||
state[table].rowsPerPage = value;
|
||||
},
|
||||
toggleSidebar: (state) => {
|
||||
state.sidebar.collapsed = !state.sidebar.collapsed;
|
||||
},
|
||||
setCollapsed: (state, action) => {
|
||||
const { collapsed } = action.payload;
|
||||
state.sidebar.collapsed = collapsed;
|
||||
},
|
||||
setMode: (state, action) => {
|
||||
state.mode = action.payload;
|
||||
},
|
||||
setShowURL: (state, action) => {
|
||||
state.showURL = action.payload;
|
||||
},
|
||||
setGreeting(state, action) {
|
||||
if (!state.greeting) {
|
||||
state.greeting = { index: 0, lastUpdate: null };
|
||||
}
|
||||
state.greeting.index = action.payload.index;
|
||||
state.greeting.lastUpdate = action.payload.lastUpdate;
|
||||
},
|
||||
setTimezone(state, action) {
|
||||
state.timezone = action.payload.timezone;
|
||||
},
|
||||
setLanguage: (state, action) => {
|
||||
state.language = action.payload;
|
||||
},
|
||||
setStarPromptOpen: (state, action) => {
|
||||
state.starPromptOpen = action.payload;
|
||||
},
|
||||
setChartType: (state, action) => {
|
||||
state.chartType = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default uiSlice.reducer;
|
||||
export const {
|
||||
setRowsPerPage,
|
||||
toggleSidebar,
|
||||
setCollapsed,
|
||||
setMode,
|
||||
setShowURL,
|
||||
setGreeting,
|
||||
setTimezone,
|
||||
setDistributedUptimeEnabled,
|
||||
setLanguage,
|
||||
setStarPromptOpen,
|
||||
setChartType,
|
||||
} = uiSlice.actions;
|
||||
@@ -0,0 +1,119 @@
|
||||
import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
type ThemeMode = "light" | "dark";
|
||||
type ChartType = "histogram" | "line";
|
||||
type TableName = "monitors" | "team" | "maintenance" | "infrastructure" | "logs";
|
||||
|
||||
interface TableState {
|
||||
rowsPerPage: number;
|
||||
}
|
||||
|
||||
interface SidebarState {
|
||||
collapsed: boolean;
|
||||
}
|
||||
|
||||
interface UIState {
|
||||
monitors: TableState;
|
||||
team: TableState;
|
||||
maintenance: TableState;
|
||||
infrastructure: TableState;
|
||||
logs: TableState;
|
||||
sidebar: SidebarState;
|
||||
mode: ThemeMode;
|
||||
showURL: boolean;
|
||||
timezone: string;
|
||||
distributedUptimeEnabled: boolean;
|
||||
language: string;
|
||||
starPromptOpen: boolean;
|
||||
chartType: ChartType;
|
||||
}
|
||||
|
||||
const initialMode: ThemeMode = window?.matchMedia?.("(prefers-color-scheme: dark)")
|
||||
?.matches
|
||||
? "dark"
|
||||
: "light";
|
||||
|
||||
const initialState: UIState = {
|
||||
monitors: {
|
||||
rowsPerPage: 10,
|
||||
},
|
||||
team: {
|
||||
rowsPerPage: 5,
|
||||
},
|
||||
maintenance: {
|
||||
rowsPerPage: 5,
|
||||
},
|
||||
infrastructure: {
|
||||
rowsPerPage: 5,
|
||||
},
|
||||
logs: {
|
||||
rowsPerPage: 15,
|
||||
},
|
||||
sidebar: {
|
||||
collapsed: false,
|
||||
},
|
||||
mode: initialMode,
|
||||
showURL: false,
|
||||
timezone: "America/Toronto",
|
||||
distributedUptimeEnabled: false,
|
||||
language: "en",
|
||||
starPromptOpen: true,
|
||||
chartType: "histogram",
|
||||
};
|
||||
|
||||
const uiSlice = createSlice({
|
||||
name: "ui",
|
||||
initialState,
|
||||
reducers: {
|
||||
setDistributedUptimeEnabled: (state, action: PayloadAction<boolean>) => {
|
||||
state.distributedUptimeEnabled = action.payload;
|
||||
},
|
||||
setRowsPerPage: (
|
||||
state,
|
||||
action: PayloadAction<{ table: TableName; value: number }>
|
||||
) => {
|
||||
const { table, value } = action.payload;
|
||||
state[table].rowsPerPage = value;
|
||||
},
|
||||
toggleSidebar: (state) => {
|
||||
state.sidebar.collapsed = !state.sidebar.collapsed;
|
||||
},
|
||||
setCollapsed: (state, action: PayloadAction<{ collapsed: boolean }>) => {
|
||||
state.sidebar.collapsed = action.payload.collapsed;
|
||||
},
|
||||
setMode: (state, action: PayloadAction<ThemeMode>) => {
|
||||
state.mode = action.payload;
|
||||
},
|
||||
setShowURL: (state, action: PayloadAction<boolean>) => {
|
||||
state.showURL = action.payload;
|
||||
},
|
||||
|
||||
setTimezone: (state, action: PayloadAction<{ timezone: string }>) => {
|
||||
state.timezone = action.payload.timezone;
|
||||
},
|
||||
setLanguage: (state, action: PayloadAction<string>) => {
|
||||
state.language = action.payload;
|
||||
},
|
||||
setStarPromptOpen: (state, action: PayloadAction<boolean>) => {
|
||||
state.starPromptOpen = action.payload;
|
||||
},
|
||||
setChartType: (state, action: PayloadAction<ChartType>) => {
|
||||
state.chartType = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export type { UIState, ThemeMode, ChartType, TableName };
|
||||
export default uiSlice.reducer;
|
||||
export const {
|
||||
setRowsPerPage,
|
||||
toggleSidebar,
|
||||
setCollapsed,
|
||||
setMode,
|
||||
setShowURL,
|
||||
setTimezone,
|
||||
setDistributedUptimeEnabled,
|
||||
setLanguage,
|
||||
setStarPromptOpen,
|
||||
setChartType,
|
||||
} = uiSlice.actions;
|
||||
@@ -83,10 +83,10 @@ export const useMonitorForm = ({
|
||||
type: "hardware",
|
||||
url: data?.url || "",
|
||||
secret: data?.secret || "",
|
||||
cpuAlertThreshold: data?.cpuAlertThreshold ?? 80,
|
||||
memoryAlertThreshold: data?.memoryAlertThreshold ?? 80,
|
||||
diskAlertThreshold: data?.diskAlertThreshold ?? 80,
|
||||
tempAlertThreshold: data?.tempAlertThreshold ?? 80,
|
||||
cpuAlertThreshold: data?.cpuAlertThreshold ?? 100,
|
||||
memoryAlertThreshold: data?.memoryAlertThreshold ?? 100,
|
||||
diskAlertThreshold: data?.diskAlertThreshold ?? 100,
|
||||
tempAlertThreshold: data?.tempAlertThreshold ?? 100,
|
||||
selectedDisks: data?.selectedDisks || [],
|
||||
};
|
||||
break;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { zodResolver } from "@hookform/resolvers/zod/dist/zod.js";
|
||||
import { useRegisterForm } from "@/Hooks/useRegisterForm";
|
||||
import type { RegisterFormData } from "@/Validation/register";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { usePost } from "@/Hooks/UseApi";
|
||||
import { usePost, useGet } from "@/Hooks/UseApi";
|
||||
import { setAuthState } from "@/Features/Auth/authSlice";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
@@ -32,11 +32,21 @@ const RegisterPage = () => {
|
||||
const { post: verifyToken } = usePost<{ token: string }, InviteVerifyResponse>();
|
||||
const hasVerified = useRef(false);
|
||||
|
||||
const { data: superAdminExists, isLoading: isCheckingAdmin } = useGet<boolean>(
|
||||
token ? null : "/auth/users/superadmin"
|
||||
);
|
||||
|
||||
const { control, handleSubmit, setError, reset } = useForm<RegisterFormData>({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: defaults,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (superAdminExists === true) {
|
||||
navigate("/login", { replace: true });
|
||||
}
|
||||
}, [superAdminExists, navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!token || hasVerified.current) return;
|
||||
hasVerified.current = true;
|
||||
@@ -53,6 +63,8 @@ const RegisterPage = () => {
|
||||
});
|
||||
}, [token]);
|
||||
|
||||
if (isCheckingAdmin) return null;
|
||||
|
||||
const onSubmit = async (data: RegisterFormData) => {
|
||||
if (loading) return;
|
||||
|
||||
|
||||
@@ -97,27 +97,27 @@ const SetNewPasswordPage = () => {
|
||||
/>
|
||||
<Stack gap={theme.spacing(4)}>
|
||||
<BulletPointCheck
|
||||
text={t("auth.common.passwordRules.length")}
|
||||
text={t("pages.auth.common.passwordRules.length")}
|
||||
variant={getVariant(hasLength)}
|
||||
/>
|
||||
<BulletPointCheck
|
||||
text={t("auth.common.passwordRules.special")}
|
||||
text={t("pages.auth.common.passwordRules.special")}
|
||||
variant={getVariant(hasSpecial)}
|
||||
/>
|
||||
<BulletPointCheck
|
||||
text={t("auth.common.passwordRules.number")}
|
||||
text={t("pages.auth.common.passwordRules.number")}
|
||||
variant={getVariant(hasNumber)}
|
||||
/>
|
||||
<BulletPointCheck
|
||||
text={t("auth.common.passwordRules.uppercase")}
|
||||
text={t("pages.auth.common.passwordRules.uppercase")}
|
||||
variant={getVariant(hasUppercase)}
|
||||
/>
|
||||
<BulletPointCheck
|
||||
text={t("auth.common.passwordRules.lowercase")}
|
||||
text={t("pages.auth.common.passwordRules.lowercase")}
|
||||
variant={getVariant(hasLowercase)}
|
||||
/>
|
||||
<BulletPointCheck
|
||||
text={t("auth.common.passwordRules.match")}
|
||||
text={t("pages.auth.common.passwordRules.match")}
|
||||
variant={getVariant(passwordsMatch)}
|
||||
/>
|
||||
</Stack>
|
||||
@@ -127,7 +127,7 @@ const SetNewPasswordPage = () => {
|
||||
fullWidth
|
||||
loading={loading}
|
||||
>
|
||||
{t("auth.forgotPassword.buttons.resetPassword")}
|
||||
{t("common.buttons.resetPassword")}
|
||||
</Button>
|
||||
<TextLink
|
||||
alignSelf="center"
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from "@/Components/v2/design-elements";
|
||||
import Box from "@mui/material/Box";
|
||||
import type { Header } from "@/Components/v2/design-elements/Table";
|
||||
import type { Monitor, MonitorStatus } from "@/Types/Monitor";
|
||||
import type { Monitor } from "@/Types/Monitor";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { formatDateWithTz } from "@/Utils/TimeUtils";
|
||||
@@ -52,7 +52,7 @@ export const ChecksTable = ({
|
||||
id: "status",
|
||||
content: "Status",
|
||||
render: (row) => {
|
||||
return <StatusLabel status={row.status as MonitorStatus} />;
|
||||
return <StatusLabel status={row.status === true ? "up" : "down"} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -329,11 +329,16 @@ const CreateMonitorPage = () => {
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
onChange={(e) => field.onChange(Number(e.target.value) || 0)}
|
||||
value={field.value === 0 ? "" : field.value}
|
||||
onChange={(e) => {
|
||||
const val = e.target.value;
|
||||
field.onChange(val === "" ? 0 : Number(val));
|
||||
}}
|
||||
type="number"
|
||||
fieldLabel={t("portToMonitor")}
|
||||
placeholder="5173"
|
||||
fieldLabel={t("pages.createMonitor.form.general.option.port.label")}
|
||||
placeholder={t(
|
||||
"pages.createMonitor.form.general.option.port.placeholder"
|
||||
)}
|
||||
fullWidth
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message ?? ""}
|
||||
@@ -351,10 +356,12 @@ const CreateMonitorPage = () => {
|
||||
<Select
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
fieldLabel={t("chooseGame")}
|
||||
fieldLabel={t("pages.createMonitor.form.general.option.game.label")}
|
||||
error={!!fieldState.error}
|
||||
>
|
||||
<MenuItem value="">Select a game</MenuItem>
|
||||
<MenuItem value="">
|
||||
{t("pages.createMonitor.form.general.option.game.placeholder")}{" "}
|
||||
</MenuItem>
|
||||
{games &&
|
||||
Object.entries(games).map(([key, game]) => (
|
||||
<MenuItem
|
||||
@@ -426,22 +433,146 @@ const CreateMonitorPage = () => {
|
||||
)}
|
||||
error={!!fieldState.error}
|
||||
>
|
||||
<MenuItem value={15000}>{t("time.fifteenSeconds")}</MenuItem>
|
||||
<MenuItem value={30000}>{t("time.thirtySeconds")}</MenuItem>
|
||||
<MenuItem value={60000}>{t("time.oneMinute")}</MenuItem>
|
||||
<MenuItem value={120000}>{t("time.twoMinutes")}</MenuItem>
|
||||
<MenuItem value={180000}>{t("time.threeMinutes")}</MenuItem>
|
||||
<MenuItem value={240000}>{t("time.fourMinutes")}</MenuItem>
|
||||
<MenuItem value={300000}>{t("time.fiveMinutes")}</MenuItem>
|
||||
<MenuItem value={600000}>{t("time.tenMinutes")}</MenuItem>
|
||||
<MenuItem value={900000}>{t("time.fifteenMinutes")}</MenuItem>
|
||||
<MenuItem value={1800000}>{t("time.thirtyMinutes")}</MenuItem>
|
||||
<MenuItem value={15000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.frequency.option.frequency.value.fifteenSeconds"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={30000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.frequency.option.frequency.value.thirtySeconds"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={60000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.frequency.option.frequency.value.oneMinute"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={120000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.frequency.option.frequency.value.twoMinutes"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={180000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.frequency.option.frequency.value.threeMinutes"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={240000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.frequency.option.frequency.value.fourMinutes"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={300000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.frequency.option.frequency.value.fiveMinutes"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={600000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.frequency.option.frequency.value.tenMinutes"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={900000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.frequency.option.frequency.value.fifteenMinutes"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={1800000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.frequency.option.frequency.value.thirtyMinutes"
|
||||
)}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Alert Thresholds - only for hardware type */}
|
||||
{generalSettingsConfig.showSecret && (
|
||||
<ConfigBox
|
||||
title={t("pages.createMonitor.form.thresholds.title")}
|
||||
subtitle={t("pages.createMonitor.form.thresholds.description")}
|
||||
rightContent={
|
||||
<Stack spacing={theme.spacing(8)}>
|
||||
<Controller
|
||||
name="cpuAlertThreshold"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<SliderWithLabel
|
||||
{...field}
|
||||
sliderMaxWidth={{ xs: "100%", md: "50%" }}
|
||||
fieldLabel={t(
|
||||
"pages.createMonitor.form.thresholds.option.cpuThreshold.label"
|
||||
)}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
valueLabelDisplay="auto"
|
||||
valueLabelFormat={(value) => `${value}%`}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="memoryAlertThreshold"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<SliderWithLabel
|
||||
{...field}
|
||||
sliderMaxWidth={{ xs: "100%", md: "50%" }}
|
||||
fieldLabel={t(
|
||||
"pages.createMonitor.form.thresholds.option.memoryThreshold.label"
|
||||
)}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
valueLabelDisplay="auto"
|
||||
valueLabelFormat={(value) => `${value}%`}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="diskAlertThreshold"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<SliderWithLabel
|
||||
{...field}
|
||||
sliderMaxWidth={{ xs: "100%", md: "50%" }}
|
||||
fieldLabel={t(
|
||||
"pages.createMonitor.form.thresholds.option.diskThreshold.label"
|
||||
)}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
valueLabelDisplay="auto"
|
||||
valueLabelFormat={(value) => `${value}%`}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="tempAlertThreshold"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<SliderWithLabel
|
||||
{...field}
|
||||
sliderMaxWidth={{ xs: "100%", md: "50%" }}
|
||||
fieldLabel={t(
|
||||
"pages.createMonitor.form.thresholds.option.tempThreshold.label"
|
||||
)}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
valueLabelDisplay="auto"
|
||||
valueLabelFormat={(value) => `${value}°C`}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ConfigBox
|
||||
title={t("pages.createMonitor.form.incidents.title")}
|
||||
subtitle={t("pages.createMonitor.form.incidents.description")}
|
||||
@@ -617,11 +748,21 @@ const CreateMonitorPage = () => {
|
||||
"pages.createMonitor.form.advanced.option.matchMethod.label"
|
||||
)}
|
||||
>
|
||||
<MenuItem value="equal">{t("matchMethodOptions.equal")}</MenuItem>
|
||||
<MenuItem value="include">
|
||||
{t("matchMethodOptions.include")}
|
||||
<MenuItem value="equal">
|
||||
{t(
|
||||
"pages.createMonitor.form.advanced.option.matchMethod.equal"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value="include">
|
||||
{t(
|
||||
"pages.createMonitor.form.advanced.option.matchMethod.include"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value="regex">
|
||||
{t(
|
||||
"pages.createMonitor.form.advanced.option.matchMethod.regex"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value="regex">{t("matchMethodOptions.regex")}</MenuItem>
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -46,10 +46,9 @@ export const DialogResolution = ({
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
title={t("incidentsPage.resolveIncidentDialogTitle")}
|
||||
title={t("pages.incidents.dialog.resolveIncident.title")}
|
||||
onCancel={handleCancel}
|
||||
onConfirm={handleConfirm}
|
||||
confirmText={t("incidentsPage.resolveIncidentDialogConfirm")}
|
||||
confirmColor="error"
|
||||
cancelColor="primary"
|
||||
loading={isResolving}
|
||||
@@ -58,8 +57,10 @@ export const DialogResolution = ({
|
||||
>
|
||||
<Box sx={{ mt: theme.spacing(4) }}>
|
||||
<TextField
|
||||
fieldLabel={t("incidentsPage.resolveIncidentDialogCommentLabel")}
|
||||
placeholder={t("incidentsPage.resolveIncidentDialogCommentPlaceholder")}
|
||||
fieldLabel={t("pages.incidents.dialog.resolveIncident.option.comment.label")}
|
||||
placeholder={t(
|
||||
"pages.incidents.dialog.resolveIncident.option.comment.placeholder"
|
||||
)}
|
||||
value={comment}
|
||||
onChange={(e) => setComment(e.target.value)}
|
||||
fullWidth
|
||||
|
||||
@@ -9,13 +9,13 @@ import type { Monitor } from "@/Types/Monitor";
|
||||
import type { ActionMenuItem } from "@/Components/v2/actions-menu";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { TypeToPathMap } from "@/Utils/monitorUtilsLegacy.js";
|
||||
import { formatDateWithTz } from "@/Utils/TimeUtils";
|
||||
import { useSelector } from "react-redux";
|
||||
import type { RootState } from "@/Types/state";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import Box from "@mui/material/Box";
|
||||
import { getMonitorPath } from "@/Utils/MonitorUtils";
|
||||
|
||||
interface IncidentsTableProps {
|
||||
title?: string;
|
||||
@@ -62,7 +62,7 @@ export const IncidentsTable = ({
|
||||
label: t("pages.incidents.table.actions.goToMonitor"),
|
||||
action: () => {
|
||||
if (monitor) {
|
||||
const path = TypeToPathMap[monitor.type as keyof typeof TypeToPathMap];
|
||||
const path = getMonitorPath(monitor.type);
|
||||
if (path && monitor.id) {
|
||||
navigate(`/${path}/${monitor.id}`);
|
||||
}
|
||||
|
||||
@@ -195,12 +195,7 @@ export const InfraMonitorsTable = ({
|
||||
</Typography>
|
||||
),
|
||||
render: (row) => {
|
||||
return (
|
||||
<StatusLabel
|
||||
status={row.status}
|
||||
isActive={row.isActive}
|
||||
/>
|
||||
);
|
||||
return <StatusLabel status={row.status} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
import {
|
||||
MonitorBasePageWithStates,
|
||||
UpStatusBox,
|
||||
DownStatusBox,
|
||||
PausedStatusBox,
|
||||
} from "@/Components/v2/design-elements";
|
||||
import { MonitorBasePageWithStates } from "@/Components/v2/design-elements";
|
||||
import { HeaderCreate } from "@/Components/v2/common";
|
||||
import { ControlsFilter } from "@/Components/v2/monitors";
|
||||
import { ControlsFilter, HeaderMonitorsSummary } from "@/Components/v2/monitors";
|
||||
import { TextField, Dialog } from "@/Components/v2/inputs";
|
||||
|
||||
import { useGet, useDelete } from "@/Hooks/UseApi";
|
||||
@@ -99,7 +94,7 @@ const InfrastructureMonitors = () => {
|
||||
{ refreshInterval: 5000, keepPreviousData: true }
|
||||
);
|
||||
|
||||
const { summary, count } = monitorsWithChecksData ?? {};
|
||||
const { summary, count } = monitorsWithChecksData ?? { summary: null, count: 0 };
|
||||
const isLoading = monitorsWithChecksLoading;
|
||||
|
||||
// Check if any filters are active
|
||||
@@ -138,14 +133,10 @@ const InfrastructureMonitors = () => {
|
||||
isLoading={isLoading}
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
<Stack
|
||||
direction={isSmall ? "column" : "row"}
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<UpStatusBox n={summary?.upMonitors || 0} />
|
||||
<DownStatusBox n={summary?.downMonitors || 0} />
|
||||
<PausedStatusBox n={summary?.pausedMonitors || 0} />
|
||||
</Stack>
|
||||
<HeaderMonitorsSummary
|
||||
summary={summary}
|
||||
showBreached={true}
|
||||
/>
|
||||
<Stack
|
||||
direction={isSmall ? "column" : "row"}
|
||||
justifyContent={isSmall ? "flex-start" : "space-between"}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
.integrations h1.MuiTypography-root {
|
||||
font-size: var(--env-var-font-size-large);
|
||||
font-weight: 600;
|
||||
}
|
||||
.integrations p.MuiTypography-root {
|
||||
font-size: var(--env-var-font-size-medium);
|
||||
}
|
||||
.integrations button {
|
||||
height: var(--env-var-height-2);
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { Stack, Typography, Grid, Button } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import Discord from "../../../assets/icons/discord-icon.svg?react";
|
||||
import Slack from "../../../assets/icons/slack-icon.svg?react";
|
||||
import Zapier from "../../../assets/icons/zapier-icon.svg?react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import "./index.css";
|
||||
|
||||
/**
|
||||
* Integrations component
|
||||
* @param {Object} props - Props for the IntegrationsComponent.
|
||||
* @param {string} props.icon - The icon for the integration image.
|
||||
* @param {string} props.header - The header for the integration.
|
||||
* @param {string} props.info - Information about the integration.
|
||||
* @param {Function} props.onClick - The onClick handler for the integration button.
|
||||
* @returns {JSX.Element} The JSX representation of the IntegrationsComponent.
|
||||
*/
|
||||
const IntegrationsComponent = ({ icon, header, info, onClick }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Grid
|
||||
item
|
||||
lg={6}
|
||||
flexGrow={1}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
gap={theme.spacing(12)}
|
||||
p={theme.spacing(8)}
|
||||
pl={theme.spacing(12)}
|
||||
height="100%"
|
||||
border={1}
|
||||
borderColor={theme.palette.primary.lowContrast}
|
||||
borderRadius={theme.shape.borderRadius}
|
||||
backgroundColor={theme.palette.primary.main}
|
||||
>
|
||||
{icon}
|
||||
<Stack
|
||||
gap={theme.spacing(2)}
|
||||
flex={1}
|
||||
>
|
||||
<Typography component="h1">{header}</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
maxWidth: "300px",
|
||||
wordWrap: "break-word",
|
||||
}}
|
||||
>
|
||||
{info}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={onClick}
|
||||
sx={{ alignSelf: "center" }}
|
||||
disabled={true}
|
||||
>
|
||||
{t("add")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
// PropTypes for IntegrationsComponent
|
||||
IntegrationsComponent.propTypes = {
|
||||
icon: PropTypes.object.isRequired,
|
||||
header: PropTypes.string.isRequired,
|
||||
info: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
/**
|
||||
* Integrations Page Component
|
||||
* @returns {JSX.Element} The JSX representation of the Integrations page.
|
||||
*/
|
||||
|
||||
const Integrations = () => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const integrations = [
|
||||
{
|
||||
icon: (
|
||||
<Slack
|
||||
alt="slack integration"
|
||||
style={{ width: "45px", height: "45px", alignSelf: "center" }}
|
||||
/>
|
||||
),
|
||||
header: t("integrationsSlack"),
|
||||
info: t("integrationsSlackInfo"),
|
||||
onClick: () => {},
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<Discord
|
||||
alt="discord integration"
|
||||
style={{ width: "42px", height: "42px", alignSelf: "center" }}
|
||||
/>
|
||||
),
|
||||
header: t("integrationsDiscord"),
|
||||
info: t("integrationsDiscordInfo"),
|
||||
onClick: () => {},
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<Zapier
|
||||
alt="zapier integration"
|
||||
style={{ width: "42px", height: "42px", alignSelf: "center" }}
|
||||
/>
|
||||
),
|
||||
header: t("integrationsZapier"),
|
||||
info: t("integrationsZapierInfo"),
|
||||
onClick: () => {},
|
||||
},
|
||||
// Add more integrations as needed
|
||||
];
|
||||
|
||||
return (
|
||||
<Stack
|
||||
className="integrations"
|
||||
pt={theme.spacing(20)}
|
||||
gap={theme.spacing(2)}
|
||||
sx={{
|
||||
"& h1, & p": {
|
||||
color: theme.palette.primary.contrastTextSecondary,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Typography component="h1">{t("integrations")}</Typography>
|
||||
<Typography mb={theme.spacing(12)}>{t("integrationsPrism")}</Typography>
|
||||
<Grid
|
||||
container
|
||||
spacing={theme.spacing(12)}
|
||||
>
|
||||
{integrations.map((integration, index) => (
|
||||
<IntegrationsComponent
|
||||
key={index}
|
||||
icon={integration.icon}
|
||||
header={integration.header}
|
||||
info={integration.info}
|
||||
onClick={integration.onClick}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default Integrations;
|
||||
@@ -196,13 +196,13 @@ export const MaintenanceWindowTable = ({
|
||||
/>
|
||||
<DialogInput
|
||||
open={deleteDialogOpen}
|
||||
title={t("maintenanceTableActionMenuDialogTitle")}
|
||||
title={t("common.dialogs.delete.title")}
|
||||
content={t("common.dialogs.delete.description")}
|
||||
onCancel={() => {
|
||||
setDeleteDialogOpen(false);
|
||||
setSelectedWindow(null);
|
||||
}}
|
||||
onConfirm={handleDelete}
|
||||
confirmText={t("delete")}
|
||||
loading={deleteLoading}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@@ -9,8 +9,8 @@ import { useTheme } from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface NotFoundProps {
|
||||
title: string;
|
||||
desc: string;
|
||||
title?: string;
|
||||
desc?: string;
|
||||
}
|
||||
|
||||
const NotFoundPage = ({ title, desc }: NotFoundProps) => {
|
||||
|
||||
@@ -1,27 +1,22 @@
|
||||
import {
|
||||
MonitorBasePageWithStates,
|
||||
UpStatusBox,
|
||||
DownStatusBox,
|
||||
PausedStatusBox,
|
||||
PageSpeedKeyPriorityFallback,
|
||||
} from "@/Components/v2/design-elements";
|
||||
import { Dialog } from "@/Components/v2/inputs";
|
||||
import { HeaderCreate } from "@/Components/v2/common";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { PageSpeedMonitorsTable } from "@/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable";
|
||||
import type { Monitor } from "@/Types/Monitor";
|
||||
|
||||
import { useTheme } from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useState } from "react";
|
||||
import { useIsAdmin } from "@/Hooks/useIsAdmin";
|
||||
import { useGet, useDelete } from "@/Hooks/UseApi";
|
||||
import type { MonitorsWithChecksResponse } from "@/Types/Monitor";
|
||||
import type { AppSettingsResponse } from "@/Types/Settings";
|
||||
import { HeaderMonitorsSummary } from "@/Components/v2/monitors";
|
||||
|
||||
const PageSpeedMonitorsPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const isAdmin = useIsAdmin();
|
||||
const { deleteFn, loading: isDeleting } = useDelete();
|
||||
|
||||
@@ -48,8 +43,8 @@ const PageSpeedMonitorsPage = () => {
|
||||
const [rowsPerPage, setRowsPerPage] = useState(10);
|
||||
|
||||
const monitors = monitorsData?.monitors;
|
||||
const monitorsCount = monitorsData?.count;
|
||||
const summary = monitorsData?.summary;
|
||||
const monitorsCount = monitorsData?.count ?? 0;
|
||||
const summary = monitorsData?.summary ?? null;
|
||||
|
||||
const isLoading = monitorsIsLoading || settingsIsLoading;
|
||||
|
||||
@@ -80,14 +75,7 @@ const PageSpeedMonitorsPage = () => {
|
||||
isLoading={isLoading}
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
<Stack
|
||||
direction={{ xs: "column", md: "row" }}
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<UpStatusBox n={summary?.upMonitors || 0} />
|
||||
<DownStatusBox n={summary?.downMonitors || 0} />
|
||||
<PausedStatusBox n={summary?.pausedMonitors || 0} />
|
||||
</Stack>
|
||||
<HeaderMonitorsSummary summary={summary} />
|
||||
<PageSpeedMonitorsTable
|
||||
monitors={monitors || []}
|
||||
refetch={refetch}
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
import SettingsStats from "./SettingsStats.jsx";
|
||||
|
||||
import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
|
||||
import { useGet, usePost, useDelete, useLazyGet, usePut } from "@/Hooks/UseApi";
|
||||
import { useGet, usePost, useDelete, useLazyGet, usePatch } from "@/Hooks/UseApi";
|
||||
// Constants
|
||||
const BREADCRUMBS = [{ name: `Settings`, path: "/settings" }];
|
||||
|
||||
@@ -62,10 +62,10 @@ const Settings = () => {
|
||||
}
|
||||
}, [fetchedSettings]);
|
||||
|
||||
const { put: saveSettingsFn, loading: isSaving } = usePut();
|
||||
const { patch: saveSettingsFn, loading: isSaving } = usePatch();
|
||||
|
||||
const saveSettings = async (settings) => {
|
||||
const response = await saveSettingsFn("/settings", { settings });
|
||||
const response = await saveSettingsFn("/settings", settings);
|
||||
if (response?.data) {
|
||||
const data = response.data;
|
||||
setIsApiKeySet(data.pagespeedKeySet);
|
||||
|
||||
@@ -64,7 +64,7 @@ export const HeaderStatusPageControls = ({
|
||||
},
|
||||
}}
|
||||
>
|
||||
{t("publicLink")}
|
||||
{t("components.headerStatusPageControls.publicLink")}
|
||||
</Typography>
|
||||
<Box>
|
||||
<ExternalLink size={14} />
|
||||
|
||||
@@ -7,7 +7,6 @@ import { StatusLabel, BaseBox } from "@/Components/v2/design-elements";
|
||||
import { SwitchComponent } from "@/Components/v2/inputs";
|
||||
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { determineState } from "@/Utils/MonitorUtils";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useState } from "react";
|
||||
import type { Monitor } from "@/Types/Monitor";
|
||||
@@ -54,7 +53,6 @@ export const MonitorsList = ({ statusPage, monitors }: MonitorsListProps) => {
|
||||
)}
|
||||
|
||||
{monitors?.map((monitor) => {
|
||||
const status = determineState(monitor);
|
||||
return (
|
||||
<BaseBox
|
||||
key={monitor.id}
|
||||
@@ -87,10 +85,7 @@ export const MonitorsList = ({ statusPage, monitors }: MonitorsListProps) => {
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<StatusLabel
|
||||
status={status === "up"}
|
||||
isActive={monitor.isActive}
|
||||
/>
|
||||
<StatusLabel status={monitor.status} />
|
||||
</Stack>
|
||||
{statusPage.showCharts !== false && (
|
||||
<Box sx={{ overflow: "hidden", minWidth: 0, flex: 1 }}>
|
||||
|
||||
@@ -12,16 +12,16 @@ const getMonitorStatus = (monitors: Monitor[], theme: Theme, t: Function) => {
|
||||
icon: <AlertTriangle size={24} />,
|
||||
};
|
||||
|
||||
if (monitors.every((monitor) => monitor.status === true)) {
|
||||
if (monitors.every((monitor) => monitor.status === "up")) {
|
||||
monitorsStatus.msg = t("pages.statusPages.statusBar.allUp");
|
||||
monitorsStatus.color = theme.palette.success.main;
|
||||
monitorsStatus.icon = <CircleCheck size={24} />;
|
||||
return monitorsStatus;
|
||||
} else if (monitors.every((monitor) => monitor.status === false)) {
|
||||
} else if (monitors.every((monitor) => monitor.status === "down")) {
|
||||
monitorsStatus.msg = t("pages.statusPages.statusBar.allDown");
|
||||
monitorsStatus.color = theme.palette.error.main;
|
||||
return monitorsStatus;
|
||||
} else if (monitors.some((monitor) => monitor.status === false)) {
|
||||
} else if (monitors.some((monitor) => monitor.status === "down")) {
|
||||
monitorsStatus.msg = t("pages.statusPages.statusBar.degraded");
|
||||
monitorsStatus.color = theme.palette.warning.main;
|
||||
return monitorsStatus;
|
||||
|
||||
@@ -96,7 +96,6 @@ const StatusPageView = () => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Typography variant="h2">{t("statusPageStatusServiceStatus")}</Typography>
|
||||
<StatusBar monitors={monitors} />
|
||||
<MonitorsList
|
||||
statusPage={statusPage}
|
||||
|
||||
@@ -15,7 +15,7 @@ const getHeaders = (t: Function, uiTimezone: string) => {
|
||||
id: "status",
|
||||
content: t("common.table.headers.status"),
|
||||
render: (row) => {
|
||||
return <StatusLabel status={row.status} />;
|
||||
return <StatusLabel status={row.status === true ? "up" : "down"} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -186,12 +186,7 @@ export const MonitorTable = ({
|
||||
</Stack>
|
||||
),
|
||||
render: (row) => {
|
||||
return (
|
||||
<StatusLabel
|
||||
status={row.status}
|
||||
isActive={row.isActive}
|
||||
/>
|
||||
);
|
||||
return <StatusLabel status={row.status} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { ControlsFilter } from "@/Components/v2/monitors";
|
||||
import {
|
||||
MonitorBasePageWithStates,
|
||||
UpStatusBox,
|
||||
DownStatusBox,
|
||||
PausedStatusBox,
|
||||
} from "@/Components/v2/design-elements";
|
||||
import { ControlsFilter, HeaderMonitorsSummary } from "@/Components/v2/monitors";
|
||||
import { MonitorBasePageWithStates } from "@/Components/v2/design-elements";
|
||||
import { TextField, Dialog } from "@/Components/v2/inputs";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { MonitorTable } from "@/Pages/Uptime/Monitors/Components/UptimeMonitorsTable";
|
||||
@@ -99,7 +94,11 @@ const UptimeMonitorsPage = () => {
|
||||
{ refreshInterval: 5000, keepPreviousData: true }
|
||||
);
|
||||
|
||||
const { monitors: monitorsWithChecks, summary, count } = monitorsWithChecksData ?? {};
|
||||
const {
|
||||
monitors: monitorsWithChecks,
|
||||
summary,
|
||||
count,
|
||||
} = monitorsWithChecksData ?? { monitors: null, summary: null, count: 0 };
|
||||
|
||||
// Delete hook
|
||||
const { deleteFn, loading: isDeleting } = useDelete();
|
||||
@@ -150,14 +149,8 @@ const UptimeMonitorsPage = () => {
|
||||
isLoading={isLoading}
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
<Stack
|
||||
direction={isSmall ? "column" : "row"}
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<UpStatusBox n={summary?.upMonitors || 0} />
|
||||
<DownStatusBox n={summary?.downMonitors || 0} />
|
||||
<PausedStatusBox n={summary?.pausedMonitors || 0} />
|
||||
</Stack>
|
||||
|
||||
<HeaderMonitorsSummary summary={summary} />
|
||||
|
||||
<Stack
|
||||
direction={isSmall ? "column" : "row"}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ThemeProvider } from "@mui/material";
|
||||
import { lightTheme, darkTheme } from "@/Utils/Theme/v2Theme";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
import type { RootState } from "@/store";
|
||||
import { Navigate, Route, Routes as LibRoutes } from "react-router";
|
||||
import RootLayout from "@/Components/v2/layout/RootLayout";
|
||||
import NotFound from "@/Pages/NotFound";
|
||||
@@ -11,7 +12,7 @@ import NotFound from "@/Pages/NotFound";
|
||||
import AuthLogin from "@/Pages/Auth/Login";
|
||||
import AuthRegister from "@/Pages/Auth/Register";
|
||||
import AuthForgotPassword from "@/Pages/Auth/Recovery";
|
||||
import AuthSetNewPassword from "../Pages/Auth/SetNewPassword";
|
||||
import AuthSetNewPassword from "@/Pages/Auth/SetNewPassword";
|
||||
|
||||
// Uptime
|
||||
import Uptime from "@/Pages/Uptime/Monitors";
|
||||
@@ -23,40 +24,43 @@ import PageSpeedDetails from "@/Pages/PageSpeed/Details/";
|
||||
|
||||
// Infrastructure
|
||||
import Infrastructure from "@/Pages/Infrastructure/Monitors";
|
||||
import InfrastructureDetails from "@/Pages/Infrastructure/Details/index";
|
||||
import InfrastructureDetails from "@/Pages/Infrastructure/Details";
|
||||
|
||||
// Checks
|
||||
import Checks from "../Pages/Checks/index";
|
||||
import Checks from "@/Pages/Checks";
|
||||
|
||||
// Incidents
|
||||
import Incidents from "../Pages/Incidents/";
|
||||
import Incidents from "@/Pages/Incidents";
|
||||
|
||||
// Status pages
|
||||
import CreateStatus from "../Pages/StatusPage/Create/";
|
||||
import StatusPages from "../Pages/StatusPage/StatusPages";
|
||||
import Status from "../Pages/StatusPage/Status";
|
||||
import CreateStatus from "@/Pages/StatusPage/Create/";
|
||||
import StatusPages from "@/Pages/StatusPage/StatusPages";
|
||||
import Status from "@/Pages/StatusPage/Status";
|
||||
|
||||
import Notifications from "../Pages/Notifications";
|
||||
import CreateNotifications from "../Pages/Notifications/create";
|
||||
import Notifications from "@/Pages/Notifications";
|
||||
import CreateNotifications from "@/Pages/Notifications/create";
|
||||
|
||||
// Settings
|
||||
import Account from "@/Pages/Account";
|
||||
import EditUser from "../Pages/Account/EditUser";
|
||||
import Settings from "../Pages/Settings";
|
||||
import EditUser from "@/Pages/Account/EditUser";
|
||||
import Settings from "@/Pages/Settings";
|
||||
|
||||
import Maintenance from "../Pages/Maintenance";
|
||||
import Maintenance from "@/Pages/Maintenance";
|
||||
import CreateNewMaintenanceWindow from "@/Pages/Maintenance/create";
|
||||
|
||||
import ProtectedRoute from "../Components/v1/ProtectedRoute";
|
||||
import RoleProtectedRoute from "../Components/v1/RoleProtectedRoute";
|
||||
import withAdminCheck from "@/Components/v1/HOC/withAdminCheck";
|
||||
import Logs from "../Pages/Logs";
|
||||
// Logs & Diagnostics
|
||||
import Logs from "@/Pages/Logs";
|
||||
|
||||
// Routing
|
||||
import {
|
||||
ProtectedRoute,
|
||||
RoleProtectedRoute,
|
||||
} from "@/Components/v2/routing/RouteProtected";
|
||||
|
||||
import CreateMonitor from "@/Pages/CreateMonitor";
|
||||
|
||||
const Routes = () => {
|
||||
const mode = useSelector((state) => state.ui.mode);
|
||||
const AdminCheckedRegister = withAdminCheck(AuthRegister);
|
||||
const mode = useSelector((state: RootState) => state.ui.mode);
|
||||
const v2theme = mode === "light" ? lightTheme : darkTheme;
|
||||
return (
|
||||
<LibRoutes>
|
||||
@@ -380,19 +384,18 @@ const Routes = () => {
|
||||
element={
|
||||
<>
|
||||
<ThemeProvider theme={v2theme}>
|
||||
<AdminCheckedRegister />
|
||||
<AuthRegister />
|
||||
</ThemeProvider>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
exact
|
||||
path="/register/:token"
|
||||
element={
|
||||
<>
|
||||
<ThemeProvider theme={v2theme}>
|
||||
<AuthRegister superAdminExists={true} />
|
||||
<AuthRegister />
|
||||
</ThemeProvider>
|
||||
</>
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { GroupedCheck, CheckSnapshot } from "@/Types/Check";
|
||||
export type MonitorStatus = boolean | undefined;
|
||||
|
||||
export const MonitorTypes = [
|
||||
"http",
|
||||
@@ -13,12 +12,15 @@ export const MonitorTypes = [
|
||||
] as const;
|
||||
export type MonitorType = (typeof MonitorTypes)[number];
|
||||
|
||||
export interface MonitorThresholds {
|
||||
usage_cpu?: number;
|
||||
usage_memory?: number;
|
||||
usage_disk?: number;
|
||||
usage_temperature?: number;
|
||||
}
|
||||
export const MonitorStatuses = [
|
||||
"up",
|
||||
"down",
|
||||
"paused",
|
||||
"initializing",
|
||||
"maintenance",
|
||||
"breached",
|
||||
] as const;
|
||||
export type MonitorStatus = (typeof MonitorStatuses)[number];
|
||||
|
||||
export type MonitorMatchMethod = "equal" | "include" | "regex" | "";
|
||||
|
||||
@@ -28,7 +30,7 @@ export interface Monitor {
|
||||
teamId: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
status?: boolean;
|
||||
status: MonitorStatus;
|
||||
statusWindow: boolean[];
|
||||
statusWindowSize: number;
|
||||
statusWindowThreshold: number;
|
||||
@@ -45,12 +47,14 @@ export interface Monitor {
|
||||
uptimePercentage?: number;
|
||||
notifications: string[];
|
||||
secret?: string;
|
||||
thresholds?: MonitorThresholds;
|
||||
alertThreshold: number;
|
||||
cpuAlertThreshold: number;
|
||||
cpuAlertCounter: number;
|
||||
memoryAlertThreshold: number;
|
||||
memoryAlertCounter: number;
|
||||
diskAlertThreshold: number;
|
||||
diskAlertCounter: number;
|
||||
tempAlertThreshold: number;
|
||||
tempAlertCounter: number;
|
||||
selectedDisks: string[];
|
||||
gameId?: string;
|
||||
group: string | null;
|
||||
@@ -66,6 +70,9 @@ export interface MonitorsSummary {
|
||||
upMonitors: number;
|
||||
downMonitors: number;
|
||||
pausedMonitors: number;
|
||||
initializingMonitors: number;
|
||||
maintenanceMonitors: number;
|
||||
breachedMonitors: number;
|
||||
}
|
||||
|
||||
export interface MonitorsWithChecksResponse {
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
const LOG_LEVEL = import.meta.env.VITE_APP_LOG_LEVEL || "debug";
|
||||
const NO_OP = () => {};
|
||||
|
||||
class Logger {
|
||||
constructor() {
|
||||
let logLevel = LOG_LEVEL;
|
||||
this.updateLogLevel(logLevel);
|
||||
// Defer store subscription to avoid circular dependency during HMR
|
||||
setTimeout(() => {
|
||||
try {
|
||||
import("../store").then(({ default: store }) => {
|
||||
if (store) {
|
||||
this.unsubscribe = store.subscribe(() => {
|
||||
logLevel = "debug";
|
||||
this.updateLogLevel(logLevel);
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
// Store not ready yet, ignore
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
updateLogLevel(logLevel) {
|
||||
if (logLevel === "none") {
|
||||
this.info = NO_OP;
|
||||
this.error = NO_OP;
|
||||
this.warn = NO_OP;
|
||||
this.log = NO_OP;
|
||||
return;
|
||||
}
|
||||
|
||||
if (logLevel === "error") {
|
||||
this.error = console.error.bind(console);
|
||||
this.info = NO_OP;
|
||||
this.warn = NO_OP;
|
||||
this.log = NO_OP;
|
||||
return;
|
||||
}
|
||||
|
||||
if (logLevel === "warn") {
|
||||
this.error = console.error.bind(console);
|
||||
this.warn = console.warn.bind(console);
|
||||
this.info = NO_OP;
|
||||
this.log = NO_OP;
|
||||
return;
|
||||
}
|
||||
|
||||
if (logLevel === "info") {
|
||||
this.error = console.error.bind(console);
|
||||
this.warn = console.warn.bind(console);
|
||||
this.info = console.info.bind(console);
|
||||
this.log = NO_OP;
|
||||
return;
|
||||
}
|
||||
|
||||
if (logLevel === "debug") {
|
||||
this.error = console.error.bind(console);
|
||||
this.warn = console.warn.bind(console);
|
||||
this.info = console.info.bind(console);
|
||||
this.log = console.log.bind(console);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if (this.unsubscribe) {
|
||||
this.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const logger = new Logger();
|
||||
@@ -1,33 +1,29 @@
|
||||
import type { Monitor, MonitorStatus, MonitorType } from "@/Types/Monitor";
|
||||
import type { MonitorStatus, MonitorType } from "@/Types/Monitor";
|
||||
import type { PaletteKey } from "@/Utils/Theme/v2Theme";
|
||||
import type { ValueType } from "@/Components/v2/design-elements/StatusLabel";
|
||||
|
||||
export const determineState = (monitor: Monitor) => {
|
||||
if (typeof monitor === "undefined") return "pending";
|
||||
if (monitor?.isActive === false) return "paused";
|
||||
if (monitor?.status === undefined) return "pending";
|
||||
return monitor?.status == true ? "up" : "down";
|
||||
};
|
||||
|
||||
export const getMonitorPath = (type: MonitorType): string => {
|
||||
const pathMap: Record<MonitorType, string> = {
|
||||
http: "uptime",
|
||||
port: "uptime",
|
||||
ping: "uptime",
|
||||
hardware: "hardware",
|
||||
pagespeed: "pagespeed",
|
||||
docker: "docker",
|
||||
game: "game-servers",
|
||||
game: "uptime",
|
||||
unknown: "uptime",
|
||||
docker: "uptime",
|
||||
hardware: "infrastructure",
|
||||
pagespeed: "pagespeed",
|
||||
};
|
||||
return pathMap[type];
|
||||
};
|
||||
|
||||
export const getStatusPalette = (status: MonitorStatus): PaletteKey => {
|
||||
if (status === true) {
|
||||
if (status === "up") {
|
||||
return "success";
|
||||
}
|
||||
if (status === false) {
|
||||
if (status === "down") {
|
||||
return "error";
|
||||
}
|
||||
if (status === "breached") {
|
||||
return "error";
|
||||
}
|
||||
return "warning";
|
||||
@@ -43,11 +39,11 @@ export const getValuePalette = (value: ValueType): PaletteKey => {
|
||||
};
|
||||
|
||||
export const getStatusColor = (status: MonitorStatus, theme: any): string => {
|
||||
if (status === true) {
|
||||
if (status === "up") {
|
||||
return theme.palette.success.light;
|
||||
}
|
||||
|
||||
if (status === false) {
|
||||
if (status === "down") {
|
||||
return theme.palette.error.light;
|
||||
}
|
||||
|
||||
@@ -88,40 +84,3 @@ export const formatUrl = (url: string, maxLength: number = 55) => {
|
||||
? `${strippedUrl.slice(0, maxLength)}…`
|
||||
: strippedUrl;
|
||||
};
|
||||
|
||||
export interface IStatusPageHeaderConfig {
|
||||
paletteKey: PaletteKey;
|
||||
message: string;
|
||||
}
|
||||
export const getStatusPageHeaderConfig = (
|
||||
monitors: Monitor[],
|
||||
t: any
|
||||
): IStatusPageHeaderConfig => {
|
||||
if (!monitors || monitors.length === 0) {
|
||||
return { paletteKey: "error", message: "No monitors available" };
|
||||
}
|
||||
|
||||
const allUp = monitors.every((monitor) => monitor.status === true);
|
||||
const anyDown = monitors.some((monitor) => monitor.status === false);
|
||||
const allDown = monitors.every((monitor) => monitor.status === false);
|
||||
|
||||
if (allUp)
|
||||
return {
|
||||
paletteKey: "success",
|
||||
message: t("statusPage.details.statusHeader.allUp"),
|
||||
};
|
||||
if (allDown)
|
||||
return {
|
||||
paletteKey: "error",
|
||||
message: t("statusPage.details.statusHeader.allDown"),
|
||||
};
|
||||
if (anyDown)
|
||||
return {
|
||||
paletteKey: "warning",
|
||||
message: t("statusPage.details.statusHeader.anyDown"),
|
||||
};
|
||||
return {
|
||||
paletteKey: "warning",
|
||||
message: t("statusPage.details.statusHeader.anyDown"),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
#Utils folder
|
||||
@@ -38,7 +38,6 @@ const baseTheme = (palette) => ({
|
||||
fontWeight: 400,
|
||||
},
|
||||
label: {
|
||||
fontSize: "var(--env-var-font-size-medium)",
|
||||
color: palette.primary.contrastTextSecondary,
|
||||
fontWeight: 500,
|
||||
},
|
||||
@@ -332,8 +331,6 @@ const baseTheme = (palette) => ({
|
||||
|
||||
"& .MuiInputBase-input": {
|
||||
padding: ".75em",
|
||||
minHeight: "var(--env-var-height-2)",
|
||||
fontSize: "var(--env-var-font-size-medium)",
|
||||
fontWeight: 400,
|
||||
color: palette.primary.contrastTextSecondary,
|
||||
"&.Mui-disabled": {
|
||||
@@ -368,13 +365,12 @@ const baseTheme = (palette) => ({
|
||||
"& .MuiFormHelperText-root": {
|
||||
color: palette.error.main,
|
||||
opacity: 0.8,
|
||||
fontSize: "var(--env-var-font-size-medium)",
|
||||
fontSize: "var()",
|
||||
marginLeft: 0,
|
||||
},
|
||||
|
||||
"& .MuiFormHelperText-root.Mui-error": {
|
||||
opacity: 0.8,
|
||||
fontSize: "var(--env-var-font-size-medium)",
|
||||
color: palette.error.main,
|
||||
whiteSpace: "nowrap",
|
||||
},
|
||||
@@ -762,7 +758,6 @@ const baseTheme = (palette) => ({
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
maxWidth: "calc((100vw - var(--env-var-width-2)) / 2)",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -775,7 +770,6 @@ const baseTheme = (palette) => ({
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
maxWidth: "calc((100vw - var(--env-var-width-2)) / 2)",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -49,6 +49,13 @@ export const theme = (mode: string, palette: any) =>
|
||||
},
|
||||
|
||||
components: {
|
||||
MuiTouchRipple: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
display: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiButtonBase: {
|
||||
defaultProps: {
|
||||
disableRipple: true,
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
export const formatBytes = (bytes) => {
|
||||
if (bytes === 0) return "0 Bytes";
|
||||
const megabytes = bytes / (1024 * 1024);
|
||||
return megabytes.toFixed(2) + " MB";
|
||||
};
|
||||
|
||||
export const checkImage = (url) => {
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
return img.naturalWidth !== 0;
|
||||
};
|
||||
@@ -1,197 +0,0 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { setGreeting } from "../Features/UI/uiSlice";
|
||||
|
||||
const early = [
|
||||
{
|
||||
prepend: "Rise and shine",
|
||||
append: "If you’re up this early, you might as well be a legend!",
|
||||
emoji: "☕",
|
||||
},
|
||||
{
|
||||
prepend: "Good morning",
|
||||
append: "The world’s still asleep, but you’re already awesome!",
|
||||
emoji: "🦉",
|
||||
},
|
||||
{
|
||||
prepend: "Good morning",
|
||||
append: "Are you a wizard? Only magical people are up at this hour!",
|
||||
emoji: "🌄",
|
||||
},
|
||||
{
|
||||
prepend: "Up before the roosters",
|
||||
append: "Ready to tackle the day before it even starts?",
|
||||
emoji: "🐓",
|
||||
},
|
||||
{
|
||||
prepend: "Early bird special",
|
||||
append: "Let’s get things done while everyone else is snoozing!",
|
||||
emoji: "🌟",
|
||||
},
|
||||
];
|
||||
|
||||
const morning = [
|
||||
{
|
||||
prepend: "Good morning",
|
||||
append: "Is it coffee o’clock yet, or should we start with high fives?",
|
||||
emoji: "☕",
|
||||
},
|
||||
{
|
||||
prepend: "Morning",
|
||||
append: "The sun is up, and so are you—time to be amazing!",
|
||||
emoji: "🌞",
|
||||
},
|
||||
{
|
||||
prepend: "Good morning",
|
||||
append: "Time to make today the best thing since sliced bread!",
|
||||
emoji: "🥐",
|
||||
},
|
||||
{
|
||||
prepend: "Morning",
|
||||
append: "Let’s kick off the day with more energy than a double espresso!",
|
||||
emoji: "🚀",
|
||||
},
|
||||
{
|
||||
prepend: "Rise and shine",
|
||||
append: "You’re about to make today so great, even Monday will be jealous!",
|
||||
emoji: "🌟",
|
||||
},
|
||||
];
|
||||
|
||||
const afternoon = [
|
||||
{
|
||||
prepend: "Good afternoon",
|
||||
append: "How about a break to celebrate how awesome you’re doing?",
|
||||
emoji: "🥪",
|
||||
},
|
||||
{
|
||||
prepend: "Afternoon",
|
||||
append: "If you’re still going strong, you’re officially a rockstar!",
|
||||
emoji: "🌞",
|
||||
},
|
||||
{
|
||||
prepend: "Hey there",
|
||||
append: "The afternoon is your playground—let’s make it epic!",
|
||||
emoji: "🍕",
|
||||
},
|
||||
{
|
||||
prepend: "Good afternoon",
|
||||
append: "Time to crush the rest of the day like a pro!",
|
||||
emoji: "🏆",
|
||||
},
|
||||
{
|
||||
prepend: "Afternoon",
|
||||
append: "Time to turn those afternoon slumps into afternoon triumphs!",
|
||||
emoji: "🎉",
|
||||
},
|
||||
];
|
||||
|
||||
const evening = [
|
||||
{
|
||||
prepend: "Good evening",
|
||||
append: "Time to wind down and think about how you crushed today!",
|
||||
emoji: "🌇",
|
||||
},
|
||||
{
|
||||
prepend: "Evening",
|
||||
append: "You’ve earned a break—let’s make the most of these evening vibes!",
|
||||
emoji: "🍹",
|
||||
},
|
||||
{
|
||||
prepend: "Hey there",
|
||||
append: "Time to relax and bask in the glow of your day’s awesomeness!",
|
||||
emoji: "🌙",
|
||||
},
|
||||
{
|
||||
prepend: "Good evening",
|
||||
append: "Ready to trade productivity for chill mode?",
|
||||
emoji: "🛋️ ",
|
||||
},
|
||||
{
|
||||
prepend: "Evening",
|
||||
append: "Let’s call it a day and toast to your success!",
|
||||
emoji: "🕶️",
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Greeting component that displays a personalized greeting message
|
||||
* based on the time of day and the user's first name.
|
||||
*
|
||||
* @component
|
||||
* @example
|
||||
* return <Greeting type={"pagespeed"} />;
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {string} props.type - The type of monitor to be displayed in the message
|
||||
* @returns {JSX.Element} The rendered Greeting component
|
||||
*/
|
||||
|
||||
const Greeting = ({ type = "" }) => {
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const { firstName } = useSelector((state) => state.auth.user);
|
||||
const index = useSelector((state) => state.ui.greeting?.index ?? 0);
|
||||
const lastUpdate = useSelector((state) => state.ui.greeting?.lastUpdate ?? null);
|
||||
|
||||
const now = new Date();
|
||||
const hour = now.getHours();
|
||||
|
||||
useEffect(() => {
|
||||
const hourDiff = lastUpdate ? hour - lastUpdate : null;
|
||||
|
||||
if (!lastUpdate || hourDiff >= 1) {
|
||||
let random = Math.floor(Math.random() * 5);
|
||||
dispatch(setGreeting({ index: random, lastUpdate: hour }));
|
||||
}
|
||||
}, [dispatch, hour, lastUpdate]);
|
||||
|
||||
let greetingArray =
|
||||
hour < 6 ? early : hour < 12 ? morning : hour < 18 ? afternoon : evening;
|
||||
const { prepend, append, emoji } = greetingArray[index];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="h1"
|
||||
mb={theme.spacing(1)}
|
||||
>
|
||||
<Typography
|
||||
component="span"
|
||||
fontSize="inherit"
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{t("greeting.prepend", { defaultValue: prepend })},{" "}
|
||||
</Typography>
|
||||
<Typography
|
||||
component="span"
|
||||
fontSize="inherit"
|
||||
fontWeight="inherit"
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
>
|
||||
{firstName} {emoji}
|
||||
</Typography>
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h2"
|
||||
lineHeight={1}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{t("greeting.append", { defaultValue: append })} —{" "}
|
||||
{t("greeting.overview", { type: t(`menu.${type}`) })}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
Greeting.propTypes = {
|
||||
type: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Greeting;
|
||||
@@ -1,16 +1,26 @@
|
||||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import type { Resource } from "i18next";
|
||||
|
||||
const primaryLanguage = "en";
|
||||
|
||||
// Load all translation files eagerly
|
||||
const translations = import.meta.glob("../locales/*.json", { eager: true });
|
||||
interface TranslationModule {
|
||||
default?: Record<string, unknown>;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
const resources = {};
|
||||
// Load all translation files eagerly
|
||||
const translations = import.meta.glob<TranslationModule>("../locales/*.json", {
|
||||
eager: true,
|
||||
});
|
||||
|
||||
const resources: Resource = {};
|
||||
Object.keys(translations).forEach((path) => {
|
||||
const langCode = path.match(/\/([^/]+)\.json$/)[1];
|
||||
const match = path.match(/\/([^/]+)\.json$/);
|
||||
if (!match) return;
|
||||
const langCode = match[1];
|
||||
resources[langCode] = {
|
||||
translation: translations[path].default || translations[path],
|
||||
translation: translations[path].default ?? translations[path],
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import { capitalizeFirstLetter } from "./stringUtils";
|
||||
|
||||
/**
|
||||
* Helper function to get duration since last check or the last date checked
|
||||
* @param {Array} checks Array of check objects.
|
||||
* @param {boolean} duration Whether the function should return the duration since last checked or the date itself
|
||||
* @returns {number} Timestamp of the most recent check.
|
||||
*/
|
||||
export const getLastChecked = (checks, duration = true) => {
|
||||
if (!checks || checks.length === 0) {
|
||||
return 0; // Handle case when no checks are available
|
||||
}
|
||||
|
||||
// Data is sorted newest -> oldest, so newest check is the most recent
|
||||
if (!duration) {
|
||||
return new Date(checks[0].createdAt);
|
||||
}
|
||||
return new Date() - new Date(checks[0].createdAt);
|
||||
};
|
||||
|
||||
export const parseDomainName = (url) => {
|
||||
url = url.replace(/^https?:\/\//, "");
|
||||
// Remove leading/trailing dots
|
||||
url = url.replace(/^\.+|\.+$/g, "");
|
||||
// Split by dots
|
||||
const parts = url.split(".");
|
||||
// Remove common prefixes and empty parts and exclude the last element of the array (the last element should be the TLD)
|
||||
const cleanParts = parts.filter((part) => part !== "www" && part !== "").slice(0, -1);
|
||||
// If there's more than one part, append the two words and capitalize the first letters (e.g. ["api", "test"] -> "Api Test")
|
||||
const domainPart =
|
||||
cleanParts.length > 1
|
||||
? cleanParts.map((part) => capitalizeFirstLetter(part)).join(" ")
|
||||
: capitalizeFirstLetter(cleanParts[0]);
|
||||
|
||||
if (domainPart) return domainPart;
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
export const TypeToPathMap = {
|
||||
http: "uptime",
|
||||
port: "uptime",
|
||||
docker: "uptime",
|
||||
ping: "uptime",
|
||||
hardware: "infrastructure",
|
||||
pagespeed: "pagespeed",
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
export const ROLES = {
|
||||
SUPERADMIN: "superadmin",
|
||||
ADMIN: "admin",
|
||||
USER: "user",
|
||||
DEMO: "demo",
|
||||
};
|
||||
|
||||
export const VALID_ROLES = [ROLES.ADMIN, ROLES.USER, ROLES.DEMO];
|
||||
|
||||
export const EDITABLE_ROLES = [
|
||||
{ role: ROLES.ADMIN, _id: ROLES.ADMIN },
|
||||
{ role: ROLES.USER, _id: ROLES.USER },
|
||||
];
|
||||
@@ -1,46 +0,0 @@
|
||||
/**
|
||||
* Helper function to get first letter capitalized string
|
||||
* @param {string} str String whose first letter is to be capitalized
|
||||
* @returns A string with first letter capitalized
|
||||
*/
|
||||
export const capitalizeFirstLetter = (str) => {
|
||||
if (str === null || str === undefined) {
|
||||
return "";
|
||||
}
|
||||
if (typeof str !== "string") {
|
||||
throw new TypeError("Input must be a string");
|
||||
}
|
||||
if (str.length === 0) {
|
||||
return "";
|
||||
}
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to get first letter as a lower case string
|
||||
* @param {string} str String whose first letter is to be lower cased
|
||||
* @returns A string with first letter lower cased
|
||||
*/
|
||||
|
||||
export const toLowerCaseFirstLetter = (str) => {
|
||||
if (str === null || str === undefined) {
|
||||
return "";
|
||||
}
|
||||
if (typeof str !== "string") {
|
||||
throw new TypeError("Input must be a string");
|
||||
}
|
||||
if (str.length === 0) {
|
||||
return "";
|
||||
}
|
||||
return str.charAt(0).toLowerCase() + str.slice(1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a string is null, undefined, or empty (including strings with only whitespace).
|
||||
* @param {string} str - The string to check.
|
||||
* @returns {boolean} - Returns true if the string is null, undefined, or empty.
|
||||
*/
|
||||
export const isEmpty = (str) => {
|
||||
// Check if string is null, undefined, or empty (including whitespace only)
|
||||
return str === null || typeof str === "undefined" || str.trim().length === 0;
|
||||
};
|
||||
@@ -1,167 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
import duration from "dayjs/plugin/duration";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import customParseFormat from "dayjs/plugin/customParseFormat";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(customParseFormat);
|
||||
dayjs.extend(duration);
|
||||
|
||||
export const MS_PER_SECOND = 1000;
|
||||
export const MS_PER_MINUTE = 60 * MS_PER_SECOND;
|
||||
export const MS_PER_HOUR = 60 * MS_PER_MINUTE;
|
||||
export const MS_PER_DAY = 24 * MS_PER_HOUR;
|
||||
export const MS_PER_WEEK = MS_PER_DAY * 7;
|
||||
|
||||
export const formatDuration = (ms) => {
|
||||
const seconds = Math.floor(ms / 1000);
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const days = Math.floor(hours / 24);
|
||||
|
||||
let dateStr = "";
|
||||
|
||||
days && (dateStr += `${days}d `);
|
||||
hours && (dateStr += `${hours % 24}h `);
|
||||
minutes && (dateStr += `${minutes % 60}m `);
|
||||
seconds && (dateStr += `${seconds % 60}s `);
|
||||
|
||||
dateStr === "" && (dateStr = "0s");
|
||||
|
||||
return dateStr;
|
||||
};
|
||||
|
||||
export const formatDurationRounded = (ms) => {
|
||||
const seconds = Math.floor(ms / 1000);
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const days = Math.floor(hours / 24);
|
||||
|
||||
let time = "";
|
||||
if (days > 0) {
|
||||
time += `${days} day${days !== 1 ? "s" : ""}`;
|
||||
return time;
|
||||
}
|
||||
if (hours > 0) {
|
||||
time += `${hours} hour${hours !== 1 ? "s" : ""}`;
|
||||
return time;
|
||||
}
|
||||
if (minutes > 0) {
|
||||
time += `${minutes} minute${minutes !== 1 ? "s" : ""}`;
|
||||
return time;
|
||||
}
|
||||
if (seconds > 0) {
|
||||
time += `${seconds} second${seconds !== 1 ? "s" : ""}`;
|
||||
return time;
|
||||
}
|
||||
|
||||
return time;
|
||||
};
|
||||
|
||||
export const formatDurationSplit = (ms) => {
|
||||
const seconds = Math.floor(ms / 1000);
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const days = Math.floor(hours / 24);
|
||||
|
||||
return days > 0
|
||||
? { time: days, format: days === 1 ? "day" : "days" }
|
||||
: hours > 0
|
||||
? { time: hours, format: hours === 1 ? "hour" : "hours" }
|
||||
: minutes > 0
|
||||
? { time: minutes, format: minutes === 1 ? "minute" : "minutes" }
|
||||
: seconds > 0
|
||||
? { time: seconds, format: seconds === 1 ? "second" : "seconds" }
|
||||
: { time: 0, format: "seconds" };
|
||||
};
|
||||
|
||||
export const getHumanReadableDuration = (ms) => {
|
||||
const durationObj = dayjs.duration(ms);
|
||||
|
||||
const parts = {
|
||||
days: Math.floor(durationObj.asDays()),
|
||||
hours: durationObj.hours(),
|
||||
minutes: durationObj.minutes(),
|
||||
seconds: durationObj.seconds(),
|
||||
milliseconds: durationObj.milliseconds(),
|
||||
};
|
||||
|
||||
const result = [];
|
||||
|
||||
if (parts.days > 0) {
|
||||
result.push(`${parts.days}d`);
|
||||
}
|
||||
if (parts.hours > 0) {
|
||||
result.push(`${parts.hours}h`);
|
||||
}
|
||||
if (result.length < 2 && parts.minutes > 0) {
|
||||
result.push(`${parts.minutes}m`);
|
||||
}
|
||||
if (result.length < 2 && parts.seconds > 0) {
|
||||
result.push(`${parts.seconds}s`);
|
||||
}
|
||||
if (result.length < 2 && parts.milliseconds > 0 && parts.seconds < 1) {
|
||||
result.push(`${parts.milliseconds.toFixed(2)}ms`);
|
||||
}
|
||||
|
||||
if (result.length === 0) {
|
||||
// fallback for durations < 1s
|
||||
return "0s";
|
||||
}
|
||||
|
||||
return result.join(" ");
|
||||
};
|
||||
|
||||
export const formatDate = (date, customOptions) => {
|
||||
const options = {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
hour12: true,
|
||||
...customOptions,
|
||||
};
|
||||
|
||||
// Return the date using the specified options
|
||||
return date
|
||||
.toLocaleString("en-US", options)
|
||||
.replace(/\b(AM|PM)\b/g, (match) => match.toLowerCase());
|
||||
};
|
||||
|
||||
export const formatDateWithTz = (timestamp, format, timezone) => {
|
||||
const formattedDate = dayjs(timestamp).tz(timezone).format(format);
|
||||
return formattedDate;
|
||||
};
|
||||
|
||||
export const tickDateFormatLookup = (range) => {
|
||||
switch (range) {
|
||||
case "recent":
|
||||
return "h:mm A";
|
||||
case "day":
|
||||
return "h A";
|
||||
case "week":
|
||||
return "MMM D";
|
||||
case "month":
|
||||
return "MMM D";
|
||||
default:
|
||||
return "MMM D, h A";
|
||||
}
|
||||
};
|
||||
|
||||
export const tooltipDateFormatLookup = (range) => {
|
||||
switch (range) {
|
||||
case "recent":
|
||||
return "MMM D, h:mm A";
|
||||
case "day":
|
||||
return "MMM D, h:mm A";
|
||||
case "week":
|
||||
return "ddd, MMM D";
|
||||
case "month":
|
||||
return "ddd, MMM D";
|
||||
default:
|
||||
return "MMM D, h:mm A";
|
||||
}
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
export const safelyParseFloat = (value) => {
|
||||
const parsedValue = parseFloat(value);
|
||||
if (isNaN(parsedValue)) {
|
||||
return 0;
|
||||
}
|
||||
return parsedValue;
|
||||
};
|
||||
|
||||
export const formatMonitorUrl = (url, maxLength = 55) => {
|
||||
if (!url) return "";
|
||||
const strippedUrl = url.replace(/^https?:\/\//, "");
|
||||
return strippedUrl.length > maxLength
|
||||
? `${strippedUrl.slice(0, maxLength)}…`
|
||||
: strippedUrl;
|
||||
};
|
||||
@@ -1,101 +0,0 @@
|
||||
/**
|
||||
* Update errors if passed id matches the error.details[0].path, otherwise remove
|
||||
* the error for the id
|
||||
* @param {*} prev Previous errors *
|
||||
* @param {*} id ID of the field whose error is to be either updated or removed
|
||||
* @param {*} error the error object
|
||||
* @returns the Update Errors with the specific field with id being either removed or updated
|
||||
*/
|
||||
|
||||
const buildErrors = (prev, id, error) => {
|
||||
const updatedErrors = { ...prev };
|
||||
if (error && id == error.details[0].path) {
|
||||
updatedErrors[id] = error.details[0].message ?? "Validation error";
|
||||
} else {
|
||||
delete updatedErrors[id];
|
||||
}
|
||||
return updatedErrors;
|
||||
};
|
||||
|
||||
/**
|
||||
* Processes Joi validation errors and returns a filtered object of error messages for fields that have been touched.
|
||||
*
|
||||
* @param {Object} validation - The Joi validation result object.
|
||||
* @param {Object} validation.error - The error property of the validation result containing details of validation failures.
|
||||
* @param {Object[]} validation.error.details - An array of error details from the Joi validation. Each item contains information about the path and the message.
|
||||
* @param {Object} touchedErrors - An object representing which fields have been interacted with. Keys are field IDs (field names), and values are booleans indicating whether the field has been touched.
|
||||
* @returns {Object} - An object where keys are the field IDs (if they exist in `touchedErrors` and are in the error details) and values are their corresponding error messages.
|
||||
*/
|
||||
const getTouchedFieldErrors = (validation, touchedErrors) => {
|
||||
let newErrors = {};
|
||||
|
||||
if (validation?.error) {
|
||||
newErrors = validation.error.details.reduce((errors, detail) => {
|
||||
const fieldId = detail.path[0];
|
||||
if (touchedErrors[fieldId] && !(fieldId in errors)) {
|
||||
errors[fieldId] = detail.message;
|
||||
}
|
||||
return errors;
|
||||
}, {});
|
||||
}
|
||||
|
||||
return newErrors;
|
||||
};
|
||||
/**
|
||||
*
|
||||
* @param {*} form The form object of the submitted form data
|
||||
* @param {*} validation The Joi validation rules
|
||||
* @param {*} setErrors The function used to set the local errors
|
||||
* @returns true if there is no error or false if there is error after validating the form
|
||||
* the error will be reset to {} if returns false; otherwise the errors object will be set with
|
||||
* the new value
|
||||
*/
|
||||
const hasValidationErrors = (form, validation, setErrors) => {
|
||||
const { error } = validation.validate(form, {
|
||||
abortEarly: false,
|
||||
});
|
||||
if (error) {
|
||||
const newErrors = {};
|
||||
error.details.forEach((err) => {
|
||||
if (
|
||||
![
|
||||
"clientHost",
|
||||
"refreshTokenSecret",
|
||||
"dbConnectionString",
|
||||
"refreshTokenTTL",
|
||||
"jwtTTL",
|
||||
"notify-email-list",
|
||||
"_id",
|
||||
"__v",
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
].includes(err.path[0])
|
||||
) {
|
||||
newErrors[err.path[0]] = err.message ?? "Validation error";
|
||||
}
|
||||
// Handle conditionally usage number required cases
|
||||
if (!form.cpu || form.usage_cpu) {
|
||||
newErrors["usage_cpu"] = null;
|
||||
}
|
||||
if (!form.memory || form.usage_memory) {
|
||||
newErrors["usage_memory"] = null;
|
||||
}
|
||||
if (!form.disk || form.usage_disk) {
|
||||
newErrors["usage_disk"] = null;
|
||||
}
|
||||
if (!form.temperature || form.usage_temperature) {
|
||||
newErrors["usage_temperature"] = null;
|
||||
}
|
||||
});
|
||||
if (Object.values(newErrors).some((v) => v)) {
|
||||
setErrors(newErrors);
|
||||
return true;
|
||||
} else {
|
||||
setErrors({});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
setErrors({});
|
||||
return false;
|
||||
};
|
||||
export { buildErrors, hasValidationErrors, getTouchedFieldErrors };
|
||||
@@ -2,11 +2,10 @@ import { z } from "zod";
|
||||
|
||||
export const loginSchema = z.object({
|
||||
email: z
|
||||
.string()
|
||||
.min(1, "auth.common.inputs.email.errors.empty")
|
||||
.email("auth.common.inputs.email.errors.invalid")
|
||||
.email("Please enter a valid email address")
|
||||
.min(1, "Please enter your email address")
|
||||
.transform((val) => val.toLowerCase().trim()),
|
||||
password: z.string().min(1, "auth.common.inputs.password.errors.empty"),
|
||||
password: z.string().min(1, "Please enter your password"),
|
||||
});
|
||||
|
||||
export type LoginFormData = z.infer<typeof loginSchema>;
|
||||
|
||||
@@ -2,9 +2,8 @@ import { z } from "zod";
|
||||
|
||||
export const recoverySchema = z.object({
|
||||
email: z
|
||||
.string()
|
||||
.min(1, "auth.common.inputs.email.errors.empty")
|
||||
.email("auth.common.inputs.email.errors.invalid")
|
||||
.email("Please enter a valid email address")
|
||||
.min(1, "Please enter your email address")
|
||||
.transform((val) => val.toLowerCase().trim()),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
import joi from "joi";
|
||||
import dayjs from "dayjs";
|
||||
import { ROLES } from "../Utils/roleUtils";
|
||||
export const ROLES = {
|
||||
SUPERADMIN: "superadmin",
|
||||
ADMIN: "admin",
|
||||
USER: "user",
|
||||
DEMO: "demo",
|
||||
};
|
||||
|
||||
export const VALID_ROLES = [ROLES.ADMIN, ROLES.USER, ROLES.DEMO];
|
||||
|
||||
export const EDITABLE_ROLES = [
|
||||
{ role: ROLES.ADMIN, _id: ROLES.ADMIN },
|
||||
{ role: ROLES.USER, _id: ROLES.USER },
|
||||
];
|
||||
|
||||
const THRESHOLD_COMMON_BASE_MSG = "Threshold must be a number.";
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 853 B |
|
Before Width: | Height: | Size: 5.4 KiB |
@@ -1,67 +0,0 @@
|
||||
<svg width="768" height="768" viewBox="0 0 768 768" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_14_2)">
|
||||
<mask id="mask0_14_2" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="768" height="768">
|
||||
<path d="M768 0H0V768H768V0Z" fill="url(#paint0_radial_14_2)"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_14_2)">
|
||||
<mask id="mask1_14_2" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="768" height="768">
|
||||
<path d="M768 0H0V768H768V0Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask1_14_2)">
|
||||
<mask id="mask2_14_2" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="768" height="768">
|
||||
<path d="M768 0H0V768H768V0Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask2_14_2)">
|
||||
<path d="M0.5 0V768" stroke="#b8b9bd"/>
|
||||
<path d="M48.5 0V768" stroke="#b8b9bd"/>
|
||||
<path d="M96.5 0V768" stroke="#b8b9bd"/>
|
||||
<path d="M144.5 0V768" stroke="#b8b9bd"/>
|
||||
<path d="M192.5 0V768" stroke="#b8b9bd"/>
|
||||
<path d="M240.5 0V768" stroke="#b8b9bd"/>
|
||||
<path d="M288.5 0V768" stroke="#b8b9bd"/>
|
||||
<path d="M336.5 0V768" stroke="#b8b9bd"/>
|
||||
<path d="M384.5 0V768" stroke="#b8b9bd"/>
|
||||
<path d="M432.5 0V768" stroke="#b8b9bd"/>
|
||||
<path d="M480.5 0V768" stroke="#b8b9bd"/>
|
||||
<path d="M528.5 0V768" stroke="#b8b9bd"/>
|
||||
<path d="M576.5 0V768" stroke="#b8b9bd"/>
|
||||
<path d="M624.5 0V768" stroke="#b8b9bd"/>
|
||||
<path d="M672.5 0V768" stroke="#b8b9bd"/>
|
||||
<path d="M720.5 0V768" stroke="#b8b9bd"/>
|
||||
</g>
|
||||
<path d="M767.5 0.5H0.5V767.5H767.5V0.5Z" stroke="#b8b9bd"/>
|
||||
<mask id="mask3_14_2" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="768" height="768">
|
||||
<path d="M768 0H0V768H768V0Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask3_14_2)">
|
||||
<path d="M0 47.5H768" stroke="#b8b9bd"/>
|
||||
<path d="M0 95.5H768" stroke="#b8b9bd"/>
|
||||
<path d="M0 143.5H768" stroke="#b8b9bd"/>
|
||||
<path d="M0 191.5H768" stroke="#b8b9bd"/>
|
||||
<path d="M0 239.5H768" stroke="#b8b9bd"/>
|
||||
<path d="M0 287.5H768" stroke="#b8b9bd"/>
|
||||
<path d="M0 335.5H768" stroke="#b8b9bd"/>
|
||||
<path d="M0 383.5H768" stroke="#b8b9bd"/>
|
||||
<path d="M0 431.5H768" stroke="#b8b9bd"/>
|
||||
<path d="M0 479.5H768" stroke="#b8b9bd"/>
|
||||
<path d="M0 527.5H768" stroke="#b8b9bd"/>
|
||||
<path d="M0 575.5H768" stroke="#b8b9bd"/>
|
||||
<path d="M0 623.5H768" stroke="#b8b9bd"/>
|
||||
<path d="M0 671.5H768" stroke="#b8b9bd"/>
|
||||
<path d="M0 719.5H768" stroke="#b8b9bd"/>
|
||||
<path d="M0 767.5H768" stroke="#b8b9bd"/>
|
||||
</g>
|
||||
<path d="M767.5 0.5H0.5V767.5H767.5V0.5Z" stroke="#b8b9bd"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<radialGradient id="paint0_radial_14_2" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(384 384) rotate(90) scale(384)">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<clipPath id="clip0_14_2">
|
||||
<rect width="768" height="768" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.7 KiB |
@@ -1,36 +0,0 @@
|
||||
<svg width="256" height="170" viewBox="0 0 256 170" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_24_2)">
|
||||
<path d="M248.196 0.390244H7.80489C3.7099 0.390244 0.390259 3.70989 0.390259 7.80487V161.561C0.390259 165.656 3.7099 168.975 7.80489 168.975H248.196C252.291 168.975 255.61 165.656 255.61 161.561V7.80487C255.61 3.70989 252.291 0.390244 248.196 0.390244Z" fill="#151518"/>
|
||||
<path d="M248.196 0.390244H7.80489C3.7099 0.390244 0.390259 3.70989 0.390259 7.80487V161.561C0.390259 165.656 3.7099 168.975 7.80489 168.975H248.196C252.291 168.975 255.61 165.656 255.61 161.561V7.80487C255.61 3.70989 252.291 0.390244 248.196 0.390244Z" stroke="#27272A" stroke-width="0.780488"/>
|
||||
<path d="M234.976 18H21.122C19.3977 18 18 19.3977 18 21.122V69.5122C18 71.2364 19.3977 72.6341 21.122 72.6341H234.976C236.7 72.6341 238.098 71.2364 238.098 69.5122V21.122C238.098 19.3977 236.7 18 234.976 18Z" fill="#27272A" fill-opacity="0.8"/>
|
||||
<path d="M99.9025 78.8293H21.0732C19.3489 78.8293 17.9512 80.227 17.9512 81.9512V85.0732C17.9512 86.7974 19.3489 88.1952 21.0732 88.1952H99.9025C101.627 88.1952 103.024 86.7974 103.024 85.0732V81.9512C103.024 80.227 101.627 78.8293 99.9025 78.8293Z" fill="#27272A" fill-opacity="0.6"/>
|
||||
<path d="M234.927 93.6584H21.0732C19.3489 93.6584 17.9512 95.0561 17.9512 96.7803V99.9023C17.9512 101.627 19.3489 103.024 21.0732 103.024H234.927C236.651 103.024 238.049 101.627 238.049 99.9023V96.7803C238.049 95.0561 236.651 93.6584 234.927 93.6584Z" fill="#27272A" fill-opacity="0.3"/>
|
||||
<path d="M234.927 108.488H21.0732C19.3489 108.488 17.9512 109.886 17.9512 111.61V114.732C17.9512 116.456 19.3489 117.854 21.0732 117.854H234.927C236.651 117.854 238.049 116.456 238.049 114.732V111.61C238.049 109.886 236.651 108.488 234.927 108.488Z" fill="#27272A" fill-opacity="0.3"/>
|
||||
<g filter="url(#filter0_d_24_2)">
|
||||
<mask id="mask0_24_2" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="174" y="129" width="65" height="21">
|
||||
<path d="M234.927 129.561H177.171C175.447 129.561 174.049 130.959 174.049 132.683V146.732C174.049 148.456 175.447 149.854 177.171 149.854H234.927C236.651 149.854 238.049 148.456 238.049 146.732V132.683C238.049 130.959 236.651 129.561 234.927 129.561Z" fill="white"/>
|
||||
<path d="M177.171 130.061H234.927C236.375 130.061 237.549 131.235 237.549 132.683V146.732C237.549 148.18 236.375 149.354 234.927 149.354H177.171C175.723 149.354 174.549 148.18 174.549 146.732V132.683C174.549 131.235 175.723 130.061 177.171 130.061Z" stroke="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_24_2)">
|
||||
<path d="M234.927 129.561H177.171C175.447 129.561 174.049 130.959 174.049 132.683V146.732C174.049 148.456 175.447 149.854 177.171 149.854H234.927C236.651 149.854 238.049 148.456 238.049 146.732V132.683C238.049 130.959 236.651 129.561 234.927 129.561Z" fill="#27272A" fill-opacity="0.2"/>
|
||||
<path d="M177.171 130.061H234.927C236.375 130.061 237.549 131.235 237.549 132.683V146.732C237.549 148.18 236.375 149.354 234.927 149.354H177.171C175.723 149.354 174.549 148.18 174.549 146.732V132.683C174.549 131.235 175.723 130.061 177.171 130.061Z" stroke="#27272A" stroke-opacity="0.8"/>
|
||||
</g>
|
||||
<path d="M234.927 129.951H177.171C175.662 129.951 174.439 131.174 174.439 132.683V146.731C174.439 148.24 175.662 149.463 177.171 149.463H234.927C236.435 149.463 237.658 148.24 237.658 146.731V132.683C237.658 131.174 236.435 129.951 234.927 129.951Z" stroke="#27272A" stroke-opacity="0.8" stroke-width="0.780488"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_24_2" x="172.049" y="128.561" width="68.0002" height="24.2929" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="1"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.0627451 0 0 0 0 0.0941176 0 0 0 0 0.156863 0 0 0 0.05 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_24_2"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_24_2" result="shape"/>
|
||||
</filter>
|
||||
<clipPath id="clip0_24_2">
|
||||
<rect width="256" height="170" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -1,29 +0,0 @@
|
||||
<svg width="256" height="170" viewBox="0 0 256 170" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0.390244" y="0.390244" width="255.22" height="168.585" rx="7.41463" fill="white"/>
|
||||
<rect x="0.390244" y="0.390244" width="255.22" height="168.585" rx="7.41463" stroke="#EBEBEB" stroke-width="0.780488"/>
|
||||
<rect x="17.9512" y="16.3901" width="220.098" height="54.6341" rx="3.12195" fill="#EFF2FF"/>
|
||||
<rect x="17.9512" y="78.8293" width="85.0732" height="9.36585" rx="3.12195" fill="#D9D9D9"/>
|
||||
<rect x="17.9512" y="93.6584" width="220.098" height="9.36585" rx="3.12195" fill="#F3F3F3"/>
|
||||
<rect x="17.9512" y="108.488" width="220.098" height="9.36585" rx="3.12195" fill="#F3F3F3"/>
|
||||
<g filter="url(#filter0_d_28_7483)">
|
||||
<g clip-path="url(#clip0_28_7483)">
|
||||
<rect x="174.049" y="129.561" width="64" height="20.2927" rx="3.12195" fill="#E0F2FE"/>
|
||||
</g>
|
||||
<rect x="174.439" y="129.951" width="63.2195" height="19.5122" rx="2.73171" stroke="#D5D9EB" stroke-width="0.780488"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_28_7483" x="172.049" y="128.561" width="68" height="24.2927" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="1"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.0627451 0 0 0 0 0.0941176 0 0 0 0 0.156863 0 0 0 0.05 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_28_7483"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_28_7483" result="shape"/>
|
||||
</filter>
|
||||
<clipPath id="clip0_28_7483">
|
||||
<rect x="174.049" y="129.561" width="64" height="20.2927" rx="3.12195" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -1,19 +0,0 @@
|
||||
<svg width="250" height="176" viewBox="0 0 250 176" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M246 72.5H4C2.067 72.5 0.5 74.067 0.5 76V100C0.5 101.933 2.067 103.5 4 103.5H246C247.933 103.5 249.5 101.933 249.5 100V76C249.5 74.067 247.933 72.5 246 72.5Z" fill="white" stroke="#E1E1E1"/>
|
||||
<path d="M112.5 81H42.5C38.3579 81 35 84.3579 35 88.5C35 92.6421 38.3579 96 42.5 96H112.5C116.642 96 120 92.6421 120 88.5C120 84.3579 116.642 81 112.5 81Z" fill="#F1F1F1"/>
|
||||
<path d="M25 88.5C25 84.3579 21.6421 81 17.5 81C13.3579 81 10 84.3579 10 88.5C10 92.6421 13.3579 96 17.5 96C21.6421 96 25 92.6421 25 88.5Z" fill="#F1F1F1"/>
|
||||
<path d="M219 34H31C28.7909 34 27 35.7909 27 38V56C27 58.2091 28.7909 60 31 60H219C221.209 60 223 58.2091 223 56V38C223 35.7909 221.209 34 219 34Z" fill="white"/>
|
||||
<path d="M219 34.5H31C29.067 34.5 27.5 36.067 27.5 38V56C27.5 57.933 29.067 59.5 31 59.5H219C220.933 59.5 222.5 57.933 222.5 56V38C222.5 36.067 220.933 34.5 219 34.5Z" stroke="#E1E1E1" stroke-opacity="0.71"/>
|
||||
<path d="M156.5 42H60.5C57.4624 42 55 44.4624 55 47.5C55 50.5376 57.4624 53 60.5 53H156.5C159.538 53 162 50.5376 162 47.5C162 44.4624 159.538 42 156.5 42Z" fill="#F4F4F4"/>
|
||||
<path d="M41.5 42H40.5C37.4624 42 35 44.4624 35 47.5C35 50.5376 37.4624 53 40.5 53H41.5C44.5376 53 47 50.5376 47 47.5C47 44.4624 44.5376 42 41.5 42Z" fill="#F4F4F4"/>
|
||||
<path d="M219 116H31C28.7909 116 27 117.791 27 120V138C27 140.209 28.7909 142 31 142H219C221.209 142 223 140.209 223 138V120C223 117.791 221.209 116 219 116Z" fill="white"/>
|
||||
<path d="M219 116.5H31C29.067 116.5 27.5 118.067 27.5 120V138C27.5 139.933 29.067 141.5 31 141.5H219C220.933 141.5 222.5 139.933 222.5 138V120C222.5 118.067 220.933 116.5 219 116.5Z" stroke="#E1E1E1" stroke-opacity="0.71"/>
|
||||
<path d="M156.5 124H60.5C57.4624 124 55 126.462 55 129.5C55 132.538 57.4624 135 60.5 135H156.5C159.538 135 162 132.538 162 129.5C162 126.462 159.538 124 156.5 124Z" fill="#F4F4F4"/>
|
||||
<path d="M41.5 124H40.5C37.4624 124 35 126.462 35 129.5C35 132.538 37.4624 135 40.5 135H41.5C44.5376 135 47 132.538 47 129.5C47 126.462 44.5376 124 41.5 124Z" fill="#F4F4F4"/>
|
||||
<path d="M176 0.5H59C57.067 0.5 55.5 2.067 55.5 4V18C55.5 19.933 57.067 21.5 59 21.5H176C177.933 21.5 179.5 19.933 179.5 18V4C179.5 2.067 177.933 0.5 176 0.5Z" fill="white" stroke="#F6F6F6"/>
|
||||
<path d="M136.5 7H77.5C75.0147 7 73 9.01472 73 11.5C73 13.9853 75.0147 16 77.5 16H136.5C138.985 16 141 13.9853 141 11.5C141 9.01472 138.985 7 136.5 7Z" fill="#F6F6F6"/>
|
||||
<path d="M68 11C68 8.79086 66.2091 7 64 7C61.7909 7 60 8.79086 60 11V12C60 14.2091 61.7909 16 64 16C66.2091 16 68 14.2091 68 12V11Z" fill="#F6F6F6"/>
|
||||
<path d="M176 154.5H59C57.067 154.5 55.5 156.067 55.5 158V172C55.5 173.933 57.067 175.5 59 175.5H176C177.933 175.5 179.5 173.933 179.5 172V158C179.5 156.067 177.933 154.5 176 154.5Z" fill="white" stroke="#F6F6F6"/>
|
||||
<path d="M136.5 161H77.5C75.0147 161 73 163.015 73 165.5C73 167.985 75.0147 170 77.5 170H136.5C138.985 170 141 167.985 141 165.5C141 163.015 138.985 161 136.5 161Z" fill="#F6F6F6"/>
|
||||
<path d="M68 165C68 162.791 66.2091 161 64 161C61.7909 161 60 162.791 60 165V166C60 168.209 61.7909 170 64 170C66.2091 170 68 168.209 68 166V165Z" fill="#F6F6F6"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -1,19 +0,0 @@
|
||||
<svg width="250" height="176" viewBox="0 0 250 176" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M246 72.5H4C2.067 72.5 0.5 74.067 0.5 76V100C0.5 101.933 2.067 103.5 4 103.5H246C247.933 103.5 249.5 101.933 249.5 100V76C249.5 74.067 247.933 72.5 246 72.5Z" fill="#151518" stroke="#1F1F23"/>
|
||||
<path d="M112.5 81H42.5C38.3579 81 35 84.3579 35 88.5C35 92.6421 38.3579 96 42.5 96H112.5C116.642 96 120 92.6421 120 88.5C120 84.3579 116.642 81 112.5 81Z" fill="#1F1F23"/>
|
||||
<path d="M25 88.5C25 84.3579 21.6421 81 17.5 81C13.3579 81 10 84.3579 10 88.5C10 92.6421 13.3579 96 17.5 96C21.6421 96 25 92.6421 25 88.5Z" fill="#1F1F23"/>
|
||||
<path d="M219 34H31C28.7909 34 27 35.7909 27 38V56C27 58.2091 28.7909 60 31 60H219C221.209 60 223 58.2091 223 56V38C223 35.7909 221.209 34 219 34Z" fill="#151518"/>
|
||||
<path d="M219 34.5H31C29.067 34.5 27.5 36.067 27.5 38V56C27.5 57.933 29.067 59.5 31 59.5H219C220.933 59.5 222.5 57.933 222.5 56V38C222.5 36.067 220.933 34.5 219 34.5Z" stroke="#1F1F23" stroke-opacity="0.71"/>
|
||||
<path d="M156.5 42H60.5C57.4624 42 55 44.4624 55 47.5C55 50.5376 57.4624 53 60.5 53H156.5C159.538 53 162 50.5376 162 47.5C162 44.4624 159.538 42 156.5 42Z" fill="#252529"/>
|
||||
<path d="M41.5 42H40.5C37.4624 42 35 44.4624 35 47.5C35 50.5376 37.4624 53 40.5 53H41.5C44.5376 53 47 50.5376 47 47.5C47 44.4624 44.5376 42 41.5 42Z" fill="#252529"/>
|
||||
<path d="M219 116H31C28.7909 116 27 117.791 27 120V138C27 140.209 28.7909 142 31 142H219C221.209 142 223 140.209 223 138V120C223 117.791 221.209 116 219 116Z" fill="#151518"/>
|
||||
<path d="M219 116.5H31C29.067 116.5 27.5 118.067 27.5 120V138C27.5 139.933 29.067 141.5 31 141.5H219C220.933 141.5 222.5 139.933 222.5 138V120C222.5 118.067 220.933 116.5 219 116.5Z" stroke="#1F1F23" stroke-opacity="0.71"/>
|
||||
<path d="M156.5 124H60.5C57.4624 124 55 126.462 55 129.5C55 132.538 57.4624 135 60.5 135H156.5C159.538 135 162 132.538 162 129.5C162 126.462 159.538 124 156.5 124Z" fill="#252529"/>
|
||||
<path d="M41.5 124H40.5C37.4624 124 35 126.462 35 129.5C35 132.538 37.4624 135 40.5 135H41.5C44.5376 135 47 132.538 47 129.5C47 126.462 44.5376 124 41.5 124Z" fill="#252529"/>
|
||||
<path d="M176 0.5H59C57.067 0.5 55.5 2.067 55.5 4V18C55.5 19.933 57.067 21.5 59 21.5H176C177.933 21.5 179.5 19.933 179.5 18V4C179.5 2.067 177.933 0.5 176 0.5Z" fill="#151518" stroke="#202023"/>
|
||||
<path d="M136.5 7H77.5C75.0147 7 73 9.01472 73 11.5C73 13.9853 75.0147 16 77.5 16H136.5C138.985 16 141 13.9853 141 11.5C141 9.01472 138.985 7 136.5 7Z" fill="#202023"/>
|
||||
<path d="M68 11C68 8.79086 66.2091 7 64 7C61.7909 7 60 8.79086 60 11V12C60 14.2091 61.7909 16 64 16C66.2091 16 68 14.2091 68 12V11Z" fill="#202023"/>
|
||||
<path d="M176 154.5H59C57.067 154.5 55.5 156.067 55.5 158V172C55.5 173.933 57.067 175.5 59 175.5H176C177.933 175.5 179.5 173.933 179.5 172V158C179.5 156.067 177.933 154.5 176 154.5Z" fill="#151518" stroke="#202023"/>
|
||||
<path d="M136.5 161H77.5C75.0147 161 73 163.015 73 165.5C73 167.985 75.0147 170 77.5 170H136.5C138.985 170 141 167.985 141 165.5C141 163.015 138.985 161 136.5 161Z" fill="#202023"/>
|
||||
<path d="M68 165C68 162.791 66.2091 161 64 161C61.7909 161 60 162.791 60 165V166C60 168.209 61.7909 170 64 170C66.2091 170 68 168.209 68 166V165Z" fill="#202023"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 9.2 KiB |
@@ -1,5 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="100" viewBox="0 0 300 100">
|
||||
<rect width="100%" height="100%" fill="#DDDDDD" />
|
||||
<path fill="#999999"
|
||||
d="m76.795 37.635-7.38 12.4v7.84h-3.75v-7.84l-7.38-12.4h3.32q.49 0 .78.23.29.24.48.61l3.7 6.76q.32.6.57 1.13.24.53.44 1.07.18-.54.42-1.07.23-.53.54-1.13l3.68-6.76q.16-.31.47-.58.3-.26.78-.26zm7.88 5.65q1.6 0 2.91.52t2.24 1.47 1.43 2.32q.51 1.38.51 3.07 0 1.71-.51 3.08-.5 1.37-1.43 2.34-.93.96-2.24 1.48t-2.91.52q-1.61 0-2.92-.52-1.32-.52-2.25-1.48-.93-.97-1.44-2.34t-.51-3.08q0-1.69.51-3.07.51-1.37 1.44-2.32t2.25-1.47q1.31-.52 2.92-.52m0 12.14q1.8 0 2.66-1.2.86-1.21.86-3.53 0-2.33-.86-3.54-.86-1.22-2.66-1.22-1.82 0-2.69 1.22-.88 1.23-.88 3.54t.88 3.52q.87 1.21 2.69 1.21m18.65-11.91h3.46v14.36h-2.11q-.69 0-.87-.63l-.24-1.15q-.88.9-1.95 1.45-1.06.55-2.5.55-1.18 0-2.08-.4-.9-.39-1.52-1.12t-.93-1.73q-.32-1-.32-2.21v-9.12h3.46v9.12q0 1.32.61 2.04t1.83.72q.89 0 1.68-.4.78-.4 1.48-1.1zm9.93.86.21 1.63q.67-1.29 1.59-2.02.93-.74 2.19-.74.99 0 1.59.43l-.22 2.59q-.07.26-.2.36-.14.11-.36.11-.21 0-.62-.07-.42-.07-.81-.07-.57 0-1.02.16-.45.17-.8.49-.36.31-.63.76-.28.45-.52 1.02v8.85h-3.45v-14.36h2.03q.53 0 .74.18.21.19.28.68m14.24-7.3h3.45v20.8h-3.45zm13.27 6.21q1.59 0 2.9.52t2.24 1.47 1.44 2.32q.5 1.38.5 3.07 0 1.71-.5 3.08-.51 1.37-1.44 2.34-.93.96-2.24 1.48t-2.9.52q-1.61 0-2.93-.52-1.31-.52-2.24-1.48-.94-.97-1.45-2.34t-.51-3.08q0-1.69.51-3.07.51-1.37 1.45-2.32.93-.95 2.24-1.47 1.32-.52 2.93-.52m0 12.14q1.79 0 2.65-1.2.86-1.21.86-3.53 0-2.33-.86-3.54-.86-1.22-2.65-1.22-1.82 0-2.7 1.22-.87 1.23-.87 3.54t.87 3.52q.88 1.21 2.7 1.21m14.98-5.08q.64 0 1.12-.18.47-.17.79-.48.31-.31.48-.74.16-.44.16-.95 0-1.07-.64-1.69t-1.91-.62q-1.28 0-1.91.62-.64.62-.64 1.69 0 .5.16.93.16.44.48.75.31.32.8.49.48.18 1.11.18m3.9 8.17q0-.42-.25-.68-.25-.27-.68-.42-.44-.14-1.02-.21t-1.23-.11l-1.34-.06q-.7-.03-1.36-.11-.57.32-.93.75-.35.44-.35 1.01 0 .38.19.71.18.33.6.57.41.23 1.07.37.66.13 1.61.13.96 0 1.66-.15.7-.14 1.16-.4.45-.26.66-.62t.21-.78m-.68-14.51h4.13v1.28q0 .62-.74.76l-1.29.24q.29.74.29 1.62 0 1.07-.42 1.93-.43.86-1.19 1.46-.75.6-1.78.93t-2.22.33q-.42 0-.81-.04-.4-.04-.77-.11-.68.4-.68.91 0 .43.4.63.4.21 1.06.29t1.5.1q.84.03 1.72.1t1.72.24q.84.18 1.5.55.66.38 1.06 1.03t.4 1.68q0 .95-.47 1.84-.47.9-1.36 1.6t-2.18 1.13q-1.3.42-2.95.42-1.63 0-2.83-.31-1.2-.32-2-.84-.8-.53-1.19-1.21-.39-.69-.39-1.43 0-1.01.61-1.69.6-.68 1.67-1.08-.58-.3-.91-.79-.34-.49-.34-1.28 0-.33.12-.67t.35-.68q.23-.33.58-.64.35-.3.83-.53-1.09-.59-1.72-1.57-.62-.98-.62-2.3 0-1.06.43-1.92.42-.86 1.19-1.47.76-.61 1.8-.93 1.05-.33 2.28-.33.92 0 1.73.19.82.19 1.49.56m12.67-.72q1.59 0 2.9.52t2.24 1.47 1.44 2.32q.5 1.38.5 3.07 0 1.71-.5 3.08-.51 1.37-1.44 2.34-.93.96-2.24 1.48t-2.9.52q-1.61 0-2.93-.52-1.31-.52-2.24-1.48-.94-.97-1.45-2.34t-.51-3.08q0-1.69.51-3.07.51-1.37 1.45-2.32.93-.95 2.24-1.47 1.32-.52 2.93-.52m0 12.14q1.79 0 2.65-1.2.86-1.21.86-3.53 0-2.33-.86-3.54-.86-1.22-2.65-1.22-1.82 0-2.7 1.22-.87 1.23-.87 3.54t.87 3.52q.88 1.21 2.7 1.21m19.99-18.35v7.98q.84-.79 1.85-1.28t2.36-.49q1.18 0 2.09.4t1.52 1.12.92 1.72q.32 1 .32 2.21v9.14h-3.46v-9.14q0-1.32-.6-2.04t-1.84-.72q-.89 0-1.68.41-.78.4-1.48 1.1v10.39h-3.46v-20.8zm15.06 11.97h6.53q0-.68-.19-1.27-.19-.6-.57-1.04-.38-.45-.96-.71t-1.35-.26q-1.5 0-2.36.85-.86.86-1.1 2.43m8.82 2.08h-8.89q.09 1.11.4 1.91.3.81.81 1.33.5.53 1.2.79.69.26 1.53.26t1.45-.2 1.06-.43q.46-.24.8-.44.34-.19.66-.19.44 0 .65.32l.99 1.26q-.57.67-1.29 1.12-.71.46-1.49.73-.77.28-1.58.39-.8.11-1.56.11-1.5 0-2.79-.5-1.28-.49-2.24-1.47-.95-.97-1.49-2.41-.55-1.43-.55-3.32 0-1.47.48-2.77.47-1.29 1.36-2.25t2.17-1.52 2.89-.56q1.36 0 2.51.44 1.15.43 1.97 1.26.83.84 1.3 2.05t.47 2.76q0 .79-.17 1.06t-.65.27m6.46-6.75.21 1.63q.67-1.29 1.59-2.02.93-.74 2.19-.74.99 0 1.59.43l-.22 2.59q-.07.26-.2.36-.14.11-.36.11-.21 0-.62-.07-.42-.07-.81-.07-.57 0-1.02.16-.45.17-.8.49-.36.31-.63.76-.28.45-.52 1.02v8.85h-3.45v-14.36h2.03q.53 0 .74.18.21.19.28.68m10.12 4.67h6.52q0-.68-.18-1.27-.19-.6-.57-1.04-.38-.45-.96-.71t-1.35-.26q-1.5 0-2.36.85-.86.86-1.1 2.43m8.82 2.08h-8.89q.08 1.11.39 1.91.31.81.81 1.33.51.53 1.2.79t1.53.26 1.45-.2 1.07-.43q.45-.24.8-.44.34-.19.66-.19.43 0 .64.32l1 1.26q-.58.67-1.29 1.12-.71.46-1.49.73-.78.28-1.58.39-.81.11-1.56.11-1.5 0-2.79-.5-1.29-.49-2.24-1.47-.95-.97-1.5-2.41-.54-1.43-.54-3.32 0-1.47.47-2.77.48-1.29 1.37-2.25t2.17-1.52 2.89-.56q1.36 0 2.5.44 1.15.43 1.98 1.26.82.84 1.29 2.05t.47 2.76q0 .79-.17 1.06-.16.27-.64.27" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.3 KiB |
@@ -1,24 +0,0 @@
|
||||
<svg width="646" height="96" viewBox="0 0 646 96" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1064_606)">
|
||||
<path d="M108.53 75.6899L90.81 94.6899C90.4267 95.1026 89.9626 95.432 89.4464 95.6573C88.9303 95.8827 88.3732 95.9994 87.81 95.9999H3.81C3.40937 95.9997 3.01749 95.8827 2.68235 95.6631C2.34722 95.4436 2.08338 95.1311 1.92313 94.7639C1.76288 94.3967 1.71318 93.9908 1.78012 93.5958C1.84706 93.2008 2.02772 92.8338 2.3 92.5399L20 73.5399C20.3833 73.1273 20.8474 72.7979 21.3636 72.5725C21.8797 72.3472 22.4368 72.2305 23 72.2299H107C107.404 72.2216 107.802 72.333 108.143 72.5502C108.484 72.7674 108.754 73.0806 108.917 73.4504C109.081 73.8203 109.131 74.2303 109.062 74.6288C108.993 75.0273 108.808 75.3965 108.53 75.6899ZM90.81 37.4199C90.4253 37.0091 89.9608 36.6811 89.445 36.4558C88.9292 36.2306 88.3728 36.1129 87.81 36.11H3.81C3.40937 36.1102 3.01749 36.2272 2.68235 36.4468C2.34722 36.6663 2.08338 36.9788 1.92313 37.346C1.76288 37.7132 1.71318 38.1191 1.78012 38.5141C1.84706 38.9091 2.02772 39.2761 2.3 39.57L20 58.58C20.3847 58.9908 20.8492 59.3188 21.365 59.5441C21.8808 59.7693 22.4372 59.887 23 59.8899H107C107.4 59.8878 107.79 59.7693 108.124 59.5491C108.458 59.3288 108.72 59.0162 108.879 58.6494C109.038 58.2826 109.087 57.8774 109.019 57.4833C108.952 57.0892 108.772 56.7232 108.5 56.43L90.81 37.4199ZM3.81 23.7699H87.81C88.3732 23.7694 88.9303 23.6527 89.4464 23.4273C89.9626 23.202 90.4267 22.8726 90.81 22.4599L108.53 3.45995C108.808 3.16647 108.993 2.79726 109.062 2.39877C109.131 2.00028 109.081 1.59031 108.917 1.22045C108.754 0.850591 108.484 0.537368 108.143 0.320195C107.802 0.103021 107.404 -0.0084012 107 -5.10783e-05H23C22.4368 0.000541762 21.8797 0.117167 21.3636 0.342553C20.8474 0.567938 20.3833 0.897249 20 1.30995L2.3 20.3099C2.02772 20.6038 1.84706 20.9708 1.78012 21.3658C1.71318 21.7608 1.76288 22.1667 1.92313 22.5339C2.08338 22.9011 2.34722 23.2136 2.68235 23.4331C3.01749 23.6527 3.40937 23.7697 3.81 23.7699Z" fill="url(#paint0_linear_1064_606)"/>
|
||||
<path d="M210.94 40.6002H166V25.8002H222.62V11.0002H165.85C163.91 10.9897 161.988 11.3613 160.192 12.0938C158.396 12.8264 156.761 13.9055 155.383 15.2696C154.004 16.6337 152.907 18.2561 152.155 20.044C151.403 21.832 151.01 23.7506 151 25.6902V40.6902C151.008 42.6315 151.398 44.5523 152.149 46.3425C152.9 48.1328 153.996 49.7575 155.375 51.1237C156.755 52.49 158.39 53.5709 160.187 54.3047C161.984 55.0385 163.909 55.4108 165.85 55.4002H210.85V70.2002H152.07V85.0002H210.94C212.88 85.0108 214.802 84.6391 216.598 83.9066C218.394 83.174 220.029 82.0949 221.407 80.7308C222.786 79.3667 223.883 77.7444 224.635 75.9564C225.387 74.1684 225.78 72.2498 225.79 70.3102V55.3102C225.782 53.3689 225.392 51.4482 224.641 49.6579C223.89 47.8676 222.794 46.2429 221.415 44.8767C220.035 43.5105 218.4 42.4296 216.603 41.6958C214.806 40.962 212.881 40.5897 210.94 40.6002Z" fill="white"/>
|
||||
<path d="M298 11H252.89C250.947 10.9842 249.02 11.3519 247.219 12.0821C245.419 12.8123 243.78 13.8905 242.397 15.2552C241.013 16.6198 239.913 18.2439 239.159 20.0345C238.404 21.8251 238.01 23.747 238 25.69V70.31C238.01 72.253 238.404 74.1749 239.159 75.9655C239.913 77.7561 241.013 79.3802 242.397 80.7448C243.78 82.1095 245.419 83.1877 247.219 83.9179C249.02 84.6481 250.947 85.0158 252.89 85H298C299.94 85.0105 301.862 84.6389 303.658 83.9064C305.454 83.1738 307.089 82.0947 308.467 80.7306C309.846 79.3665 310.943 77.7441 311.695 75.9562C312.447 74.1682 312.84 72.2496 312.85 70.31V25.69C312.84 23.7504 312.447 21.8318 311.695 20.0438C310.943 18.2559 309.846 16.6335 308.467 15.2694C307.089 13.9053 305.454 12.8262 303.658 12.0936C301.862 11.3611 299.94 10.9895 298 11ZM297.89 70.2H253V25.8H297.87L297.89 70.2Z" fill="white"/>
|
||||
<path d="M456 11.0001H412C410.06 10.9896 408.138 11.3612 406.342 12.0937C404.546 12.8263 402.911 13.9054 401.533 15.2695C400.154 16.6336 399.057 18.256 398.305 20.0439C397.553 21.8319 397.16 23.7505 397.15 25.6901V85.0001H412.15V60.6901H455.95V85.0001H470.95V25.6901C470.94 23.742 470.544 21.8152 469.786 20.0206C469.027 18.2261 467.922 16.5993 466.532 15.2338C465.143 13.8684 463.497 12.7914 461.689 12.0648C459.881 11.3382 457.948 10.9764 456 11.0001ZM455.89 45.8901H412.09V25.8001H455.89V45.8901Z" fill="white"/>
|
||||
<path d="M631.15 11.0002H587.15C585.21 10.9897 583.288 11.3613 581.492 12.0938C579.696 12.8264 578.062 13.9055 576.683 15.2696C575.304 16.6337 574.207 18.2561 573.455 20.044C572.703 21.832 572.31 23.7506 572.3 25.6902V85.0002H587.3V60.6902H631V85.0002H646V25.6902C645.99 23.7506 645.597 21.832 644.845 20.044C644.093 18.2561 642.996 16.6337 641.617 15.2696C640.238 13.9055 638.604 12.8264 636.808 12.0938C635.012 11.3613 633.09 10.9897 631.15 11.0002ZM631 45.8902H587.2V25.8002H631V45.8902Z" fill="white"/>
|
||||
<path d="M544 70.2001H538L516.55 17.2001C515.815 15.3716 514.55 13.8045 512.918 12.6999C511.286 11.5952 509.361 11.0033 507.39 11.0001H494.08C492.786 10.9935 491.504 11.2418 490.307 11.7307C489.109 12.2197 488.02 12.9397 487.1 13.8497C486.181 14.7598 485.45 15.8419 484.949 17.0345C484.448 18.227 484.187 19.5066 484.18 20.8001V85.0001H499.18V25.8001H505.18L526.62 78.8001C527.367 80.6251 528.642 82.1858 530.281 83.283C531.919 84.3803 533.848 84.9641 535.82 84.9601H549.13C550.424 84.9667 551.706 84.7185 552.903 84.2295C554.101 83.7406 555.19 83.0205 556.11 82.1105C557.029 81.2005 557.76 80.1183 558.261 78.9258C558.762 77.7332 559.023 76.4537 559.03 75.1601V11.0001H544V70.2001Z" fill="white"/>
|
||||
<path d="M341.1 11H326.1V70.31C326.11 72.2539 326.505 74.1766 327.26 75.9678C328.015 77.7591 329.116 79.3836 330.5 80.7484C331.884 82.1132 333.525 83.1912 335.326 83.9208C337.128 84.6504 339.056 85.0171 341 85H386V70.2H341.1V11Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_1064_606" x1="10.81" y1="98.29" x2="98.89" y2="-1.01005" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.08" stop-color="#9945FF"/>
|
||||
<stop offset="0.3" stop-color="#8752F3"/>
|
||||
<stop offset="0.5" stop-color="#5497D5"/>
|
||||
<stop offset="0.6" stop-color="#43B4CA"/>
|
||||
<stop offset="0.72" stop-color="#28E0B9"/>
|
||||
<stop offset="0.97" stop-color="#19FB9B"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_1064_606">
|
||||
<rect width="646" height="96" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 6.1 KiB |
@@ -1,49 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="646" height="96" viewBox="0 0 646 96" fill="none" version="1.1" id="svg36"
|
||||
sodipodi:docname="solana_logo_banner_dark.svg" inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview id="namedview38" pagecolor="#ffffff" bordercolor="#000000" borderopacity="0.25"
|
||||
inkscape:showpageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false" inkscape:zoom="2.0820433" inkscape:cx="324.68104" inkscape:cy="48.02974"
|
||||
inkscape:window-width="1854" inkscape:window-height="1131" inkscape:window-x="0" inkscape:window-y="0"
|
||||
inkscape:window-maximized="1" inkscape:current-layer="svg36" />
|
||||
<g clip-path="url(#clip0_1064_606)" id="g16">
|
||||
<path
|
||||
d="M108.53 75.6899L90.81 94.6899C90.4267 95.1026 89.9626 95.432 89.4464 95.6573C88.9303 95.8827 88.3732 95.9994 87.81 95.9999H3.81C3.40937 95.9997 3.01749 95.8827 2.68235 95.6631C2.34722 95.4436 2.08338 95.1311 1.92313 94.7639C1.76288 94.3967 1.71318 93.9908 1.78012 93.5958C1.84706 93.2008 2.02772 92.8338 2.3 92.5399L20 73.5399C20.3833 73.1273 20.8474 72.7979 21.3636 72.5725C21.8797 72.3472 22.4368 72.2305 23 72.2299H107C107.404 72.2216 107.802 72.333 108.143 72.5502C108.484 72.7674 108.754 73.0806 108.917 73.4504C109.081 73.8203 109.131 74.2303 109.062 74.6288C108.993 75.0273 108.808 75.3965 108.53 75.6899ZM90.81 37.4199C90.4253 37.0091 89.9608 36.6811 89.445 36.4558C88.9292 36.2306 88.3728 36.1129 87.81 36.11H3.81C3.40937 36.1102 3.01749 36.2272 2.68235 36.4468C2.34722 36.6663 2.08338 36.9788 1.92313 37.346C1.76288 37.7132 1.71318 38.1191 1.78012 38.5141C1.84706 38.9091 2.02772 39.2761 2.3 39.57L20 58.58C20.3847 58.9908 20.8492 59.3188 21.365 59.5441C21.8808 59.7693 22.4372 59.887 23 59.8899H107C107.4 59.8878 107.79 59.7693 108.124 59.5491C108.458 59.3288 108.72 59.0162 108.879 58.6494C109.038 58.2826 109.087 57.8774 109.019 57.4833C108.952 57.0892 108.772 56.7232 108.5 56.43L90.81 37.4199ZM3.81 23.7699H87.81C88.3732 23.7694 88.9303 23.6527 89.4464 23.4273C89.9626 23.202 90.4267 22.8726 90.81 22.4599L108.53 3.45995C108.808 3.16647 108.993 2.79726 109.062 2.39877C109.131 2.00028 109.081 1.59031 108.917 1.22045C108.754 0.850591 108.484 0.537368 108.143 0.320195C107.802 0.103021 107.404 -0.0084012 107 -5.10783e-05H23C22.4368 0.000541762 21.8797 0.117167 21.3636 0.342553C20.8474 0.567938 20.3833 0.897249 20 1.30995L2.3 20.3099C2.02772 20.6038 1.84706 20.9708 1.78012 21.3658C1.71318 21.7608 1.76288 22.1667 1.92313 22.5339C2.08338 22.9011 2.34722 23.2136 2.68235 23.4331C3.01749 23.6527 3.40937 23.7697 3.81 23.7699Z"
|
||||
fill="url(#paint0_linear_1064_606)" id="path2" />
|
||||
<path
|
||||
d="M210.94 40.6002H166V25.8002H222.62V11.0002H165.85C163.91 10.9897 161.988 11.3613 160.192 12.0938C158.396 12.8264 156.761 13.9055 155.383 15.2696C154.004 16.6337 152.907 18.2561 152.155 20.044C151.403 21.832 151.01 23.7506 151 25.6902V40.6902C151.008 42.6315 151.398 44.5523 152.149 46.3425C152.9 48.1328 153.996 49.7575 155.375 51.1237C156.755 52.49 158.39 53.5709 160.187 54.3047C161.984 55.0385 163.909 55.4108 165.85 55.4002H210.85V70.2002H152.07V85.0002H210.94C212.88 85.0108 214.802 84.6391 216.598 83.9066C218.394 83.174 220.029 82.0949 221.407 80.7308C222.786 79.3667 223.883 77.7444 224.635 75.9564C225.387 74.1684 225.78 72.2498 225.79 70.3102V55.3102C225.782 53.3689 225.392 51.4482 224.641 49.6579C223.89 47.8676 222.794 46.2429 221.415 44.8767C220.035 43.5105 218.4 42.4296 216.603 41.6958C214.806 40.962 212.881 40.5897 210.94 40.6002Z"
|
||||
fill="white" id="path4" style="fill:#000000;fill-opacity:1" />
|
||||
<path
|
||||
d="M298 11H252.89C250.947 10.9842 249.02 11.3519 247.219 12.0821C245.419 12.8123 243.78 13.8905 242.397 15.2552C241.013 16.6198 239.913 18.2439 239.159 20.0345C238.404 21.8251 238.01 23.747 238 25.69V70.31C238.01 72.253 238.404 74.1749 239.159 75.9655C239.913 77.7561 241.013 79.3802 242.397 80.7448C243.78 82.1095 245.419 83.1877 247.219 83.9179C249.02 84.6481 250.947 85.0158 252.89 85H298C299.94 85.0105 301.862 84.6389 303.658 83.9064C305.454 83.1738 307.089 82.0947 308.467 80.7306C309.846 79.3665 310.943 77.7441 311.695 75.9562C312.447 74.1682 312.84 72.2496 312.85 70.31V25.69C312.84 23.7504 312.447 21.8318 311.695 20.0438C310.943 18.2559 309.846 16.6335 308.467 15.2694C307.089 13.9053 305.454 12.8262 303.658 12.0936C301.862 11.3611 299.94 10.9895 298 11ZM297.89 70.2H253V25.8H297.87L297.89 70.2Z"
|
||||
fill="white" id="path6" style="fill:#000000" />
|
||||
<path
|
||||
d="M456 11.0001H412C410.06 10.9896 408.138 11.3612 406.342 12.0937C404.546 12.8263 402.911 13.9054 401.533 15.2695C400.154 16.6336 399.057 18.256 398.305 20.0439C397.553 21.8319 397.16 23.7505 397.15 25.6901V85.0001H412.15V60.6901H455.95V85.0001H470.95V25.6901C470.94 23.742 470.544 21.8152 469.786 20.0206C469.027 18.2261 467.922 16.5993 466.532 15.2338C465.143 13.8684 463.497 12.7914 461.689 12.0648C459.881 11.3382 457.948 10.9764 456 11.0001ZM455.89 45.8901H412.09V25.8001H455.89V45.8901Z"
|
||||
fill="white" id="path8" style="fill:#000000" />
|
||||
<path
|
||||
d="M631.15 11.0002H587.15C585.21 10.9897 583.288 11.3613 581.492 12.0938C579.696 12.8264 578.062 13.9055 576.683 15.2696C575.304 16.6337 574.207 18.2561 573.455 20.044C572.703 21.832 572.31 23.7506 572.3 25.6902V85.0002H587.3V60.6902H631V85.0002H646V25.6902C645.99 23.7506 645.597 21.832 644.845 20.044C644.093 18.2561 642.996 16.6337 641.617 15.2696C640.238 13.9055 638.604 12.8264 636.808 12.0938C635.012 11.3613 633.09 10.9897 631.15 11.0002ZM631 45.8902H587.2V25.8002H631V45.8902Z"
|
||||
fill="white" id="path10" style="fill:#000000" />
|
||||
<path
|
||||
d="M544 70.2001H538L516.55 17.2001C515.815 15.3716 514.55 13.8045 512.918 12.6999C511.286 11.5952 509.361 11.0033 507.39 11.0001H494.08C492.786 10.9935 491.504 11.2418 490.307 11.7307C489.109 12.2197 488.02 12.9397 487.1 13.8497C486.181 14.7598 485.45 15.8419 484.949 17.0345C484.448 18.227 484.187 19.5066 484.18 20.8001V85.0001H499.18V25.8001H505.18L526.62 78.8001C527.367 80.6251 528.642 82.1858 530.281 83.283C531.919 84.3803 533.848 84.9641 535.82 84.9601H549.13C550.424 84.9667 551.706 84.7185 552.903 84.2295C554.101 83.7406 555.19 83.0205 556.11 82.1105C557.029 81.2005 557.76 80.1183 558.261 78.9258C558.762 77.7332 559.023 76.4537 559.03 75.1601V11.0001H544V70.2001Z"
|
||||
fill="white" id="path12" style="fill:#000000" />
|
||||
<path
|
||||
d="M341.1 11H326.1V70.31C326.11 72.2539 326.505 74.1766 327.26 75.9678C328.015 77.7591 329.116 79.3836 330.5 80.7484C331.884 82.1132 333.525 83.1912 335.326 83.9208C337.128 84.6504 339.056 85.0171 341 85H386V70.2H341.1V11Z"
|
||||
fill="white" id="path14" style="fill:#000000" />
|
||||
</g>
|
||||
<defs id="defs34">
|
||||
<linearGradient id="paint0_linear_1064_606" x1="10.81" y1="98.29" x2="98.89" y2="-1.01005"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.08" stop-color="#9945FF" id="stop18" />
|
||||
<stop offset="0.3" stop-color="#8752F3" id="stop20" />
|
||||
<stop offset="0.5" stop-color="#5497D5" id="stop22" />
|
||||
<stop offset="0.6" stop-color="#43B4CA" id="stop24" />
|
||||
<stop offset="0.72" stop-color="#28E0B9" id="stop26" />
|
||||
<stop offset="0.97" stop-color="#19FB9B" id="stop28" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_1064_606">
|
||||
<rect width="646" height="96" fill="white" id="rect31" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 7.5 KiB |
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 -28.5 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g>
|
||||
<path d="M216.856339,16.5966031 C200.285002,8.84328665 182.566144,3.2084988 164.041564,0 C161.766523,4.11318106 159.108624,9.64549908 157.276099,14.0464379 C137.583995,11.0849896 118.072967,11.0849896 98.7430163,14.0464379 C96.9108417,9.64549908 94.1925838,4.11318106 91.8971895,0 C73.3526068,3.2084988 55.6133949,8.86399117 39.0420583,16.6376612 C5.61752293,67.146514 -3.4433191,116.400813 1.08711069,164.955721 C23.2560196,181.510915 44.7403634,191.567697 65.8621325,198.148576 C71.0772151,190.971126 75.7283628,183.341335 79.7352139,175.300261 C72.104019,172.400575 64.7949724,168.822202 57.8887866,164.667963 C59.7209612,163.310589 61.5131304,161.891452 63.2445898,160.431257 C105.36741,180.133187 151.134928,180.133187 192.754523,160.431257 C194.506336,161.891452 196.298154,163.310589 198.110326,164.667963 C191.183787,168.842556 183.854737,172.420929 176.223542,175.320965 C180.230393,183.341335 184.861538,190.991831 190.096624,198.16893 C211.238746,191.588051 232.743023,181.531619 254.911949,164.955721 C260.227747,108.668201 245.831087,59.8662432 216.856339,16.5966031 Z M85.4738752,135.09489 C72.8290281,135.09489 62.4592217,123.290155 62.4592217,108.914901 C62.4592217,94.5396472 72.607595,82.7145587 85.4738752,82.7145587 C98.3405064,82.7145587 108.709962,94.5189427 108.488529,108.914901 C108.508531,123.290155 98.3405064,135.09489 85.4738752,135.09489 Z M170.525237,135.09489 C157.88039,135.09489 147.510584,123.290155 147.510584,108.914901 C147.510584,94.5396472 157.658606,82.7145587 170.525237,82.7145587 C183.391518,82.7145587 193.761324,94.5189427 193.539891,108.914901 C193.539891,123.290155 183.391518,135.09489 170.525237,135.09489 Z" fill="#5865F2" fill-rule="nonzero">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="none">
|
||||
<g fill-rule="evenodd" clip-rule="evenodd">
|
||||
<path fill="#E01E5A" d="M2.471 11.318a1.474 1.474 0 001.47-1.471v-1.47h-1.47A1.474 1.474 0 001 9.846c.001.811.659 1.469 1.47 1.47zm3.682-2.942a1.474 1.474 0 00-1.47 1.471v3.683c.002.811.66 1.468 1.47 1.47a1.474 1.474 0 001.47-1.47V9.846a1.474 1.474 0 00-1.47-1.47z"/>
|
||||
<path fill="#36C5F0" d="M4.683 2.471c.001.811.659 1.469 1.47 1.47h1.47v-1.47A1.474 1.474 0 006.154 1a1.474 1.474 0 00-1.47 1.47zm2.94 3.682a1.474 1.474 0 00-1.47-1.47H2.47A1.474 1.474 0 001 6.153c.002.812.66 1.469 1.47 1.47h3.684a1.474 1.474 0 001.47-1.47z"/>
|
||||
<path fill="#2EB67D" d="M9.847 7.624a1.474 1.474 0 001.47-1.47V2.47A1.474 1.474 0 009.848 1a1.474 1.474 0 00-1.47 1.47v3.684c.002.81.659 1.468 1.47 1.47zm3.682-2.941a1.474 1.474 0 00-1.47 1.47v1.47h1.47A1.474 1.474 0 0015 6.154a1.474 1.474 0 00-1.47-1.47z"/>
|
||||
<path fill="#ECB22E" d="M8.377 9.847c.002.811.659 1.469 1.47 1.47h3.683A1.474 1.474 0 0015 9.848a1.474 1.474 0 00-1.47-1.47H9.847a1.474 1.474 0 00-1.47 1.47zm2.94 3.682a1.474 1.474 0 00-1.47-1.47h-1.47v1.47c.002.812.659 1.469 1.47 1.47a1.474 1.474 0 001.47-1.47z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 428 KiB |
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<path d="M128.080089,-0.000183105 C135.311053,0.0131003068 142.422517,0.624138494 149.335663,1.77979593 L149.335663,1.77979593 L149.335663,76.2997796 L202.166953,23.6044907 C208.002065,27.7488446 213.460883,32.3582023 218.507811,37.3926715 C223.557281,42.4271407 228.192318,47.8867213 232.346817,53.7047992 L232.346817,53.7047992 L179.512985,106.400063 L254.227854,106.400063 C255.387249,113.29414 256,120.36111 256,127.587243 L256,127.587243 L256,127.759881 C256,134.986013 255.387249,142.066204 254.227854,148.960282 L254.227854,148.960282 L179.500273,148.960282 L232.346817,201.642324 C228.192318,207.460402 223.557281,212.919983 218.523066,217.954452 L218.523066,217.954452 L218.507811,217.954452 C213.460883,222.988921 208.002065,227.6115 202.182208,231.742607 L202.182208,231.742607 L149.335663,179.04709 L149.335663,253.5672 C142.435229,254.723036 135.323765,255.333244 128.092802,255.348499 L128.092802,255.348499 L127.907197,255.348499 C120.673691,255.333244 113.590195,254.723036 106.677048,253.5672 L106.677048,253.5672 L106.677048,179.04709 L53.8457596,231.742607 C42.1780766,223.466917 31.977435,213.278734 23.6658953,201.642324 L23.6658953,201.642324 L76.4997269,148.960282 L1.78485803,148.960282 C0.612750404,142.052729 0,134.946095 0,127.719963 L0,127.719963 L0,127.349037 C0.0121454869,125.473817 0.134939797,123.182933 0.311311815,120.812834 L0.36577283,120.099764 C0.887996182,113.428547 1.78485803,106.400063 1.78485803,106.400063 L1.78485803,106.400063 L76.4997269,106.400063 L23.6658953,53.7047992 C27.8076812,47.8867213 32.4300059,42.4403618 37.4769335,37.4193681 L37.4769335,37.4193681 L37.5023588,37.3926715 C42.5391163,32.3582023 48.0106469,27.7488446 53.8457596,23.6044907 L53.8457596,23.6044907 L106.677048,76.2997796 L106.677048,1.77979593 C113.590195,0.624138494 120.688946,0.0131003068 127.932622,-0.000183105 L127.932622,-0.000183105 L128.080089,-0.000183105 Z M128.067377,95.7600714 L127.945335,95.7600714 C118.436262,95.7600714 109.32891,97.5001809 100.910584,100.661566 C97.7553011,109.043534 96.0085811,118.129275 95.9958684,127.613685 L95.9958684,127.733184 C96.0085811,137.217594 97.7553011,146.303589 100.923296,154.685303 C109.32891,157.846943 118.436262,159.587052 127.945335,159.587052 L128.067377,159.587052 C137.576449,159.587052 146.683802,157.846943 155.089415,154.685303 C158.257411,146.290368 160.004131,137.217594 160.004131,127.733184 L160.004131,127.613685 C160.004131,118.129275 158.257411,109.043534 155.089415,100.661566 C146.683802,97.5001809 137.576449,95.7600714 128.067377,95.7600714 Z" fill="#FF4A00" fill-rule="nonzero">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.8 KiB |
@@ -18,70 +18,4 @@ html {
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
/* Generalized Stylings */
|
||||
--env-var-radius-2: 8px;
|
||||
|
||||
--env-var-width-2: 360px;
|
||||
--env-var-width-4: 100px;
|
||||
|
||||
--env-var-height-2: 34px;
|
||||
|
||||
--env-var-nav-bar-height: 70px;
|
||||
--env-var-side-bar-width: 250px;
|
||||
--env-var-side-bar-collapsed-width: 64px;
|
||||
--env-var-side-bar-auth-footer-height: 50px;
|
||||
|
||||
--env-var-spacing-1: 12px;
|
||||
--env-var-spacing-1-plus: 16px;
|
||||
--env-var-spacing-1-minus: 10px;
|
||||
--env-var-spacing-2: 24px;
|
||||
|
||||
--env-var-font-size-small: 11px;
|
||||
--env-var-font-size-small-plus: 12px;
|
||||
--env-var-font-size-medium: 13px;
|
||||
--env-var-font-size-medium-plus: 14px;
|
||||
--env-var-font-size-large: 16px;
|
||||
--env-var-font-size-large-plus: 22px;
|
||||
--env-var-font-size-xlarge: 30px;
|
||||
}
|
||||
|
||||
.MuiInputBase-root.Mui-disabled input {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* .Toastify__toast-container {
|
||||
min-width: 300px;
|
||||
width: auto;
|
||||
}
|
||||
.Toastify__toast-body .alert {
|
||||
min-width: 150px;
|
||||
padding: 5px 10px;
|
||||
align-items: center;
|
||||
}
|
||||
.Toastify [class^="Toastify__toast"] {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.Toastify__toast {
|
||||
min-height: 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.Toastify [class*="Toastify__toast-theme"] {
|
||||
background-color: transparent;
|
||||
} */
|
||||
|
||||
.MuiTouchRipple-root {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@keyframes ripple {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: scale(0);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,189 +1,4 @@
|
||||
{
|
||||
"aboutus": "About Us",
|
||||
"access": "Access",
|
||||
"actions": "Actions",
|
||||
"add": "Add",
|
||||
"addMonitors": "Add monitors",
|
||||
"advancedMatching": "Advanced matching",
|
||||
"area": "Area",
|
||||
"auth": {
|
||||
"common": {
|
||||
"errors": {
|
||||
"validation": "Error validating data."
|
||||
},
|
||||
"fields": {
|
||||
"role": {
|
||||
"errors": {
|
||||
"min": "At least one role is required"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"email": {
|
||||
"errors": {
|
||||
"empty": "To continue, please enter your email address",
|
||||
"invalid": "Please recheck validity of entered email address"
|
||||
},
|
||||
"label": "Email",
|
||||
"placeholder": "jordan.ellis@domain.com"
|
||||
},
|
||||
"firstName": {
|
||||
"errors": {
|
||||
"empty": "Please enter your name",
|
||||
"length": "Name must be less than 50 characters",
|
||||
"pattern": "Name must contain only letters, spaces, apostrophes, or hyphens"
|
||||
},
|
||||
"label": "Name",
|
||||
"placeholder": "Jordan"
|
||||
},
|
||||
"lastName": {
|
||||
"errors": {
|
||||
"empty": "Please enter your surname",
|
||||
"length": "Surname must be less than 50 characters",
|
||||
"pattern": "Surname must contain only letters, spaces, apostrophes, or hyphens"
|
||||
},
|
||||
"label": "Surname",
|
||||
"placeholder": "Ellis"
|
||||
},
|
||||
"password": {
|
||||
"errors": {
|
||||
"empty": "Please enter your password",
|
||||
"length": "Password must be at least 8 characters long",
|
||||
"lowercase": "Password must contain at least 1 lowercase letter",
|
||||
"number": "Password must contain at least 1 number",
|
||||
"special": "Password must contain at least 1 special character (!?@#$%^&*()-_=+[]{}|;:'\",./\\~`)",
|
||||
"uppercase": "Password must contain at least 1 uppercase letter"
|
||||
},
|
||||
"label": "Password",
|
||||
"rules": {
|
||||
"length": {
|
||||
"beginning": "Must be at least",
|
||||
"highlighted": "8 characters long"
|
||||
},
|
||||
"lowercase": {
|
||||
"beginning": "Must contain at least",
|
||||
"highlighted": "one lower character"
|
||||
},
|
||||
"match": {
|
||||
"beginning": "Passwords",
|
||||
"highlighted": "must match"
|
||||
},
|
||||
"number": {
|
||||
"beginning": "Must contain at least",
|
||||
"highlighted": "one number"
|
||||
},
|
||||
"special": {
|
||||
"beginning": "Must contain at least",
|
||||
"highlighted": "one special character (!?@#$%^&*()-_=+[]{}|;:'\",./\\~`)"
|
||||
},
|
||||
"uppercase": {
|
||||
"beginning": "Must contain at least",
|
||||
"highlighted": "one upper character"
|
||||
}
|
||||
}
|
||||
},
|
||||
"passwordConfirm": {
|
||||
"errors": {
|
||||
"different": "Entered passwords don't match, so one of them is probably mistyped",
|
||||
"empty": "Please enter your password again for confirmation (helps with typos)"
|
||||
},
|
||||
"label": "Confirm password",
|
||||
"placeholder": "Re-enter password to confirm"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"continue": "Continue"
|
||||
},
|
||||
"passwordRules": {
|
||||
"length": "Must be at least 8 characters long",
|
||||
"special": "Must contain at least one special character (!?@#$%^&*()-_=+[]{}|;:'\",./\\~`)",
|
||||
"number": "Must contain at least one number",
|
||||
"uppercase": "Must contain at least one upper character",
|
||||
"lowercase": "Must contain at least one lower character",
|
||||
"match": "Passwords must match"
|
||||
}
|
||||
},
|
||||
"forgotPassword": {
|
||||
"buttons": {
|
||||
"openEmail": "Open email app",
|
||||
"resetPassword": "Reset password"
|
||||
},
|
||||
"heading": "Forgot password?",
|
||||
"links": {
|
||||
"login": "Go back to <a>Log In</a>",
|
||||
"resend": "Didn't receive the email? <a>Click to resend</a>"
|
||||
},
|
||||
"subheadings": {
|
||||
"stepFour": "Your password has been successfully reset. Click below to log in magically.",
|
||||
"stepOne": "No worries, we'll send you reset instructions.",
|
||||
"stepThree": "Your new password must be different from previously used passwords.",
|
||||
"stepTwo": "We sent a password reset link to <email/>"
|
||||
},
|
||||
"toasts": {
|
||||
"emailNotFound": "Email not found.",
|
||||
"error": "Unable to reset password. Please try again later or contact support.",
|
||||
"redirect": "Redirecting in <seconds/>...",
|
||||
"sent": "Instructions sent to <email/>.",
|
||||
"success": "Your password was reset successfully."
|
||||
}
|
||||
},
|
||||
"login": {
|
||||
"errors": {
|
||||
"password": {
|
||||
"incorrect": "The password you provided does not match our records"
|
||||
}
|
||||
},
|
||||
"heading": "Log in to continue",
|
||||
"links": {
|
||||
"forgotPassword": "Forgot password?",
|
||||
"forgotPasswordLink": "Reset password",
|
||||
"register": "Do not have an account?",
|
||||
"registerLink": "Register here"
|
||||
},
|
||||
"toasts": {
|
||||
"incorrectPassword": "Incorrect password",
|
||||
"success": "Welcome back! You're successfully logged in."
|
||||
},
|
||||
"welcome": "Welcome back to Checkmate!"
|
||||
},
|
||||
"registration": {
|
||||
"description": {
|
||||
"superAdmin": "Create your super admin account to get started",
|
||||
"user": "Sign up as a user and ask super admin for access to your monitors"
|
||||
},
|
||||
"heading": {
|
||||
"user": "Sign Up"
|
||||
},
|
||||
"toasts": {
|
||||
"success": "Welcome! Your account was created successfully."
|
||||
},
|
||||
"welcome": "Welcome to Checkmate!"
|
||||
}
|
||||
},
|
||||
"avgCpuTemperature": "Average CPU Temperature",
|
||||
"bar": "Bar",
|
||||
"basicInformation": "Basic Information",
|
||||
"bulkImport": {
|
||||
"fallbackPage": "Import a file to upload a list of servers in bulk",
|
||||
"invalidFileType": "Invalid file type",
|
||||
"noFileSelected": "No file selected",
|
||||
"selectFile": "Select File",
|
||||
"selectFileDescription": "You can download our <template>template</template> or <sample>sample</sample>",
|
||||
"selectFileTips": "Select CSV file to upload",
|
||||
"title": "Bulk Import",
|
||||
"uploadFailed": "Upload failed",
|
||||
"uploadSuccess": "Monitors created successfully!"
|
||||
},
|
||||
"bytesSent": "Bytes Sent",
|
||||
"addMember": "Add member",
|
||||
"cancel": "Cancel",
|
||||
"checkHooks": {
|
||||
"failureResolveOne": "Failed to resolve incident."
|
||||
},
|
||||
"chooseGame": "Choose game",
|
||||
"city": "CITY",
|
||||
"ClickUpload": "Click to upload",
|
||||
"close": "Close",
|
||||
"common": {
|
||||
"auth": {
|
||||
"roles": {
|
||||
@@ -222,7 +37,8 @@
|
||||
"testNotifications": "Test notifications",
|
||||
"toggleTheme": "Toggles light & dark",
|
||||
"flushQueue": "Flush queue",
|
||||
"notFound": "Go to the main dashboard"
|
||||
"notFound": "Go to the main dashboard",
|
||||
"resetPassword": "Reset password"
|
||||
},
|
||||
"charts": {
|
||||
"labels": {
|
||||
@@ -294,28 +110,23 @@
|
||||
"interval": "Interval",
|
||||
"active": "Active"
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
"checkConnection": "Please check your connection",
|
||||
"networkError": "Network error",
|
||||
"unknownError": "Unknown error"
|
||||
},
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"imageUpload": {
|
||||
"clickToUpload": "Click to upload",
|
||||
"dragAndDrop": "or drag and drop",
|
||||
"supportedFormats": "Supported formats",
|
||||
"maxSize": "Max size",
|
||||
"orDragAndDrop": "or drag and drop"
|
||||
"orDragAndDrop": "or drag and drop",
|
||||
"errors": {
|
||||
"invalidFileSize": "File size is too large!",
|
||||
"invalidFileFormat": "Unsupported file format!"
|
||||
}
|
||||
},
|
||||
"headerStatusPageControls": {
|
||||
"publicLink": "Public link"
|
||||
},
|
||||
"errors": {
|
||||
"invalidFileType": "Invalid file type",
|
||||
"fileTooLarge": "File too large"
|
||||
}
|
||||
},
|
||||
"commonSave": "Save",
|
||||
"commonSaving": "Saving...",
|
||||
"companyName": "Company name",
|
||||
"components": {
|
||||
"headerTimeRange": {
|
||||
"labels": {
|
||||
"day": "Day",
|
||||
@@ -380,229 +191,6 @@
|
||||
"description": "See the latest releases and help grow the community on GitHub"
|
||||
}
|
||||
},
|
||||
"configure": "Configure",
|
||||
"confirmPassword": "Confirm password",
|
||||
"cores": "Cores",
|
||||
"cpu": "CPU",
|
||||
"cpuFrequency": "CPU Frequency",
|
||||
"cpuLogical": "CPU (Logical)",
|
||||
"cpuPhysical": "CPU (Physical)",
|
||||
"cpuTemperature": "CPU Temperature",
|
||||
"cpuUsage": "CPU usage",
|
||||
"createA": "Create a",
|
||||
"createMaintenance": "Create maintenance",
|
||||
"createMaintenanceWindow": "Create maintenance window",
|
||||
"createMonitor": "Create monitor",
|
||||
"createNew": "Create new",
|
||||
"delete": "Delete",
|
||||
"generateToken": "Generate token",
|
||||
"DeleteAccountButton": "Remove account",
|
||||
"DeleteAccountTitle": "Remove account",
|
||||
"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.",
|
||||
"DeleteDescriptionText": "This will remove the account and all associated data from the server. This isn't reversible.",
|
||||
"deleteStatusPage": "Do you want to delete this status page?",
|
||||
"deleteStatusPageConfirm": "Yes, delete status page",
|
||||
"deleteStatusPageDescription": "Once deleted, your status page cannot be retrieved.",
|
||||
"DeleteWarningTitle": "Really remove this account?",
|
||||
"details": "Details",
|
||||
"device": "Device",
|
||||
"diagnosticsPage": {
|
||||
"diagnosticDescription": "System diagnostics",
|
||||
"gauges": {
|
||||
"heapAllocationSubtitle": "% of available memory",
|
||||
"heapAllocationTitle": "Heap allocation",
|
||||
"heapUsageSubtitle": "% of available memory",
|
||||
"heapUsageTitle": "Heap usage",
|
||||
"heapUtilizationSubtitle": "% of allocated",
|
||||
"heapUtilizationTitle": "Heap utilization",
|
||||
"instantCpuUsageSubtitle": "% of 1s used by CPU",
|
||||
"instantCpuUsageTitle": "Instant CPU usage"
|
||||
},
|
||||
"stats": {
|
||||
"eventLoopDelayTitle": "Event loop delay",
|
||||
"osMemoryLimitTitle": "OS Memory Limit",
|
||||
"totalHeapSizeTitle": "Total heap size",
|
||||
"uptimeTitle": "Uptime",
|
||||
"usedHeapSizeTitle": "Used heap size"
|
||||
}
|
||||
},
|
||||
"disk": "Disk",
|
||||
"diskUsage": "Disk Usage",
|
||||
"displayName": "Display name",
|
||||
"download": "Download",
|
||||
"DragandDrop": "drag and drop",
|
||||
"duration": "Duration",
|
||||
"edit": "Edit",
|
||||
"editing": "Editing...",
|
||||
"editMaintenance": "Edit maintenance",
|
||||
"editUserPage": {
|
||||
"form": {
|
||||
"email": "Email",
|
||||
"firstName": "First name",
|
||||
"lastName": "Last name",
|
||||
"role": "Roles",
|
||||
"save": "Save"
|
||||
},
|
||||
"table": {
|
||||
"actionHeader": "Action",
|
||||
"roleHeader": "Role"
|
||||
},
|
||||
"title": "Edit user",
|
||||
"toast": {
|
||||
"successUserUpdate": "User updated successfully",
|
||||
"validationErrors": "Validation errors"
|
||||
}
|
||||
},
|
||||
"EmailDescriptionText": "This is your current email address — it cannot be changed.",
|
||||
"errorPages": {
|
||||
"serverUnreachable": {
|
||||
"alertBox": "Server Connection Error",
|
||||
"description": "We're unable to connect to the server. Please check your internet connection or verify your deployment configuration if the problem persists.",
|
||||
"retryButton": {
|
||||
"default": "Retry connection",
|
||||
"processing": "Connecting..."
|
||||
},
|
||||
"toasts": {
|
||||
"reconnected": "Successfully reconnected to the server.",
|
||||
"stillUnreachable": "Server is still unreachable. Please try again later."
|
||||
}
|
||||
}
|
||||
},
|
||||
"errors": "Errors",
|
||||
"expectedValue": "Expected value",
|
||||
"failedToSendEmail": "Failed to send email",
|
||||
"FirstName": "First name",
|
||||
"frequency": "Frequency",
|
||||
"friendlyNameInput": "Friendly name",
|
||||
"friendlyNamePlaceholder": "Maintenance at __ : __ for ___ minutes",
|
||||
"gb": "GB",
|
||||
"general": {
|
||||
"noOptionsFound": "No {{unit}} found"
|
||||
},
|
||||
"greeting": {
|
||||
"append": "The afternoon is your playground—let's make it epic!",
|
||||
"overview": "Here's an overview of your {{type}} monitors.",
|
||||
"prepend": "Hey there"
|
||||
},
|
||||
"high": "high",
|
||||
"host": "Host",
|
||||
"http": "HTTP",
|
||||
"https": "HTTPS",
|
||||
"incidentsPage": {
|
||||
"resolveIncidentDialogCommentLabel": "Comment (optional)",
|
||||
"resolveIncidentDialogCommentPlaceholder": "Add a comment about the resolution...",
|
||||
"resolveIncidentDialogConfirm": "Resolve",
|
||||
"resolveIncidentDialogTitle": "Resolve Incident"
|
||||
},
|
||||
"integrations": "Integrations",
|
||||
"integrationsDiscord": "Discord",
|
||||
"integrationsDiscordInfo": "Connect with Discord and view incidents directly in a channel",
|
||||
"integrationsPrism": "Connect Prism to your favorite service.",
|
||||
"integrationsSlack": "Slack",
|
||||
"integrationsSlackInfo": "Connect with Slack and see incidents in a channel",
|
||||
"integrationsZapier": "Zapier",
|
||||
"integrationsZapierInfo": "Send all incidents to Zapier, and then see them everywhere",
|
||||
"invalidFileFormat": "Unsupported file format!",
|
||||
"invalidFileSize": "File size is too large!",
|
||||
"inviteNoTokenFound": "No invite token found",
|
||||
"LastName": "Last name",
|
||||
"logsPage": {
|
||||
"logLevelSelect": {
|
||||
"title": "Log level",
|
||||
"values": {
|
||||
"all": "All",
|
||||
"debug": "Debug",
|
||||
"error": "Error",
|
||||
"info": "Info",
|
||||
"warn": "Warn"
|
||||
}
|
||||
},
|
||||
"noLogs": "No logs found",
|
||||
"table": {
|
||||
"level": "Level",
|
||||
"logs": "logs",
|
||||
"message": "Message",
|
||||
"method": "Method",
|
||||
"service": "Service",
|
||||
"timestamp": "Timestamp"
|
||||
},
|
||||
"tabs": {
|
||||
"diagnostics": "Diagnostics",
|
||||
"logs": "Server logs",
|
||||
"queue": "Job queue"
|
||||
},
|
||||
"title": "Logs"
|
||||
},
|
||||
"low": "low",
|
||||
"maintenance": "maintenance",
|
||||
"maintenanceRepeat": "Maintenance Repeat",
|
||||
"maintenanceTableActionMenuDialogTitle": "Do you really want to remove this maintenance window?",
|
||||
"maintenanceWindowDescription": "During maintenance windows, all monitoring is suspended for selected monitors. No network checks will be performed, preventing any status updates or notifications from being triggered. Your monitors will appear frozen at their last known status, and status pages will display a maintenance indicator. Once the maintenance window ends, monitoring automatically resumes, and alerts will trigger if issues are detected. Maintenance periods do not count against uptime calculations.",
|
||||
"maintenanceWindowName": "Maintenance Window Name",
|
||||
"matchMethod": "Match Method",
|
||||
"matchMethodOptions": {
|
||||
"equal": "Equal",
|
||||
"include": "Include",
|
||||
"regex": "Regex"
|
||||
},
|
||||
"MaxSize": "Maximum Size",
|
||||
"mb": "MB",
|
||||
"mem": "Mem",
|
||||
"memory": "Memory",
|
||||
"memoryUsage": "Memory usage",
|
||||
"menu": {
|
||||
"changelog": "Changelog",
|
||||
"checks": "Checks",
|
||||
"discussions": "Discussions",
|
||||
"docs": "Docs",
|
||||
"incidents": "Incidents",
|
||||
"inviteMember": "Invite member",
|
||||
"infrastructure": "Infrastructure",
|
||||
"logOut": "Log out",
|
||||
"logs": "Logs",
|
||||
"maintenance": "Maintenance",
|
||||
"notifications": "Notifications",
|
||||
"pagespeed": "Pagespeed",
|
||||
"password": "Password",
|
||||
"profile": "Profile",
|
||||
"settings": "Settings",
|
||||
"statusPages": "Status pages",
|
||||
"support": "Support",
|
||||
"team": "Team",
|
||||
"uptime": "Uptime"
|
||||
},
|
||||
"message": "Message",
|
||||
"monitor": "monitor",
|
||||
"monitorHooks": {
|
||||
"failureAddDemoMonitors": "Failed to add demo monitors",
|
||||
"successAddDemoMonitors": "Successfully added demo monitors"
|
||||
},
|
||||
"monitors": "monitors",
|
||||
"monitorsToApply": "Monitors to apply maintenance window to",
|
||||
"ms": "ms",
|
||||
"navControls": "Controls",
|
||||
"network": "Network",
|
||||
"nextWindow": "Next window",
|
||||
"notFoundButton": "Go to the main dashboard",
|
||||
"notifications": {
|
||||
"fallback": {
|
||||
"actionButton": "Create notification channel!",
|
||||
"checks": [
|
||||
"Alert teams about downtime or performance issues",
|
||||
"Let engineers know when incidents happen",
|
||||
"Keep administrators informed of system changes"
|
||||
],
|
||||
"title": "A notification channel is used to:"
|
||||
},
|
||||
"fetch": {
|
||||
"failed": "Failed to fetch notifications"
|
||||
},
|
||||
"test": {
|
||||
"success": "Test notification sent successfully"
|
||||
}
|
||||
},
|
||||
"now": "Now",
|
||||
"os": "OS",
|
||||
"pages": {
|
||||
"notFound": {
|
||||
"title": "Oh no! You dropped your sushi!",
|
||||
@@ -685,7 +273,32 @@
|
||||
},
|
||||
"auth": {
|
||||
"common": {
|
||||
"passwordRules": {},
|
||||
"passwordRules": {
|
||||
"length": {
|
||||
"beginning": "Must be at least",
|
||||
"highlighted": "8 characters long"
|
||||
},
|
||||
"lowercase": {
|
||||
"beginning": "Must contain at least",
|
||||
"highlighted": "one lower character"
|
||||
},
|
||||
"match": {
|
||||
"beginning": "Passwords",
|
||||
"highlighted": "must match"
|
||||
},
|
||||
"number": {
|
||||
"beginning": "Must contain at least",
|
||||
"highlighted": "one number"
|
||||
},
|
||||
"special": {
|
||||
"beginning": "Must contain at least",
|
||||
"highlighted": "one special character (!?@#$%^&*()-_=+[]{}|;:'\",./\\~`)"
|
||||
},
|
||||
"uppercase": {
|
||||
"beginning": "Must contain at least",
|
||||
"highlighted": "one upper character"
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"option": {
|
||||
"email": {
|
||||
@@ -778,7 +391,9 @@
|
||||
},
|
||||
"status": {
|
||||
"down": "down",
|
||||
"breached": "breached",
|
||||
"initializing": "initializing",
|
||||
"maintenance": "maintenance",
|
||||
"paused": "paused",
|
||||
"total": "total",
|
||||
"up": "up"
|
||||
@@ -801,7 +416,10 @@
|
||||
"label": "JSONPath expression"
|
||||
},
|
||||
"matchMethod": {
|
||||
"label": "Match method"
|
||||
"label": "Match method",
|
||||
"equal": "Equal",
|
||||
"include": "Include",
|
||||
"regex": "Regex"
|
||||
}
|
||||
},
|
||||
"title": "Advanced settings"
|
||||
@@ -810,7 +428,19 @@
|
||||
"description": "How often do you want to check the status of this monitor?",
|
||||
"option": {
|
||||
"frequency": {
|
||||
"label": "Check frequency"
|
||||
"label": "Check frequency",
|
||||
"value": {
|
||||
"fifteenMinutes": "15 minutes",
|
||||
"fifteenSeconds": "15 seconds",
|
||||
"fiveMinutes": "5 minutes",
|
||||
"fourMinutes": "4 minutes",
|
||||
"oneMinute": "1 minute",
|
||||
"tenMinutes": "10 minutes",
|
||||
"thirtyMinutes": "30 minutes",
|
||||
"thirtySeconds": "30 seconds",
|
||||
"threeMinutes": "3 minutes",
|
||||
"twoMinutes": "2 minutes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Check frequency"
|
||||
@@ -836,6 +466,14 @@
|
||||
"url": {
|
||||
"label": "URL",
|
||||
"placeholder": "https://www.google.com"
|
||||
},
|
||||
"game": {
|
||||
"label": "Choose game",
|
||||
"placeholder": "Select a game"
|
||||
},
|
||||
"port": {
|
||||
"label": "Port to monitor",
|
||||
"placeholder": 80
|
||||
}
|
||||
},
|
||||
"title": "General settings",
|
||||
@@ -887,6 +525,24 @@
|
||||
"optionPort": "Port",
|
||||
"optionPortDescription": "Monitor if a specific port on a server is open.",
|
||||
"title": "Type"
|
||||
},
|
||||
"thresholds": {
|
||||
"title": "Alert thresholds",
|
||||
"description": "Define the thresholds at which alerts should be triggered for this hardware monitor.",
|
||||
"option": {
|
||||
"cpuThreshold": {
|
||||
"label": "CPU alert threshold (%)"
|
||||
},
|
||||
"memoryThreshold": {
|
||||
"label": "Memory alert threshold (%)"
|
||||
},
|
||||
"diskThreshold": {
|
||||
"label": "Disk alert threshold (%)"
|
||||
},
|
||||
"tempThreshold": {
|
||||
"label": "Temperature alert threshold (°C)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -900,6 +556,15 @@
|
||||
},
|
||||
"incidents": {
|
||||
"dialog": {
|
||||
"resolveIncident": {
|
||||
"title": "Resolve incident",
|
||||
"option": {
|
||||
"comment": {
|
||||
"label": "Comment (optional)",
|
||||
"placeholder": "Add a comment about the resolution..."
|
||||
}
|
||||
}
|
||||
},
|
||||
"details": {
|
||||
"analysis": "Incident analysis",
|
||||
"comment": "Comment:",
|
||||
@@ -1374,272 +1039,5 @@
|
||||
"title": "An uptime monitor is used to:"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pageSpeedAddApiKey": "to add your API key.",
|
||||
"pageSpeedLearnMoreLink": "Click here",
|
||||
"pageSpeedWarning": "Warning: You haven't added a Google PageSpeed API key yet. Without it, the PageSpeed monitor won't function.",
|
||||
"passwordPanel": {
|
||||
"confirmNewPassword": "Confirm new password",
|
||||
"currentPassword": "Current password",
|
||||
"enterCurrentPassword": "Enter your current password",
|
||||
"enterNewPassword": "Enter your new password",
|
||||
"newPassword": "New password",
|
||||
"passwordChangedSuccess": "Your password was changed successfully.",
|
||||
"passwordInputIncorrect": "Your password input was incorrect.",
|
||||
"passwordRequirements": "New password must contain at least 8 characters and must have at least one uppercase letter, one lowercase letter, one number and one special character (!?@#$%^&*()-_=+[]{}|;:'\",./\\~`)."
|
||||
},
|
||||
"pause": "Pause",
|
||||
"PhotoDescriptionText": "This photo will be displayed in your profile page.",
|
||||
"portToMonitor": "Port to monitor",
|
||||
"publicLink": "Public link",
|
||||
"queuePage": {
|
||||
"failedJobTable": {
|
||||
"failCountHeader": "Fail count",
|
||||
"failedAtHeader": "Last failed at",
|
||||
"failReasonHeader": "Fail reason",
|
||||
"monitorIdHeader": "Monitor ID",
|
||||
"monitorUrlHeader": "Monitor URL",
|
||||
"title": "Failed jobs"
|
||||
},
|
||||
"flushButton": "Flush queue",
|
||||
"jobTable": {
|
||||
"activeHeader": "Active",
|
||||
"failCountHeader": "Fail count",
|
||||
"idHeader": "Monitor ID",
|
||||
"intervalHeader": "Interval",
|
||||
"lastFinishedAtHeader": "Last finished at",
|
||||
"lastRunHeader": "Last run at",
|
||||
"lastRunTookHeader": "Last run took",
|
||||
"lockedAtHeader": "Locked at",
|
||||
"runCountHeader": "Run count",
|
||||
"title": "Jobs currently in queue",
|
||||
"typeHeader": "Type",
|
||||
"urlHeader": "URL"
|
||||
},
|
||||
"metricsTable": {
|
||||
"metricHeader": "Metric",
|
||||
"title": "Queue metrics",
|
||||
"valueHeader": "Value"
|
||||
},
|
||||
"refreshButton": "Refresh"
|
||||
},
|
||||
"rate": "Rate",
|
||||
"remove": "Remove",
|
||||
"repeat": "Repeat",
|
||||
"reset": "Reset",
|
||||
"response": "RESPONSE",
|
||||
"responseTime": "Response time",
|
||||
"resume": "Resume",
|
||||
"roles": {
|
||||
"admin": "Admin",
|
||||
"demoUser": "Demo user",
|
||||
"superAdmin": "Super admin",
|
||||
"teamMember": "Team member"
|
||||
},
|
||||
"save": "Save",
|
||||
"sendInvite": "Send invite",
|
||||
"selectAll": "Select all",
|
||||
"settingsFailedToClearStats": "Failed to clear stats",
|
||||
"settingsFailedToDeleteMonitors": "Failed to delete all monitors",
|
||||
"settingsFailedToSave": "Failed to save settings",
|
||||
"settingsGeneralSettings": "General settings",
|
||||
"settingsMonitorsDeleted": "Successfully deleted all monitors",
|
||||
"settingsPage": {
|
||||
"aboutSettings": {
|
||||
"labelDevelopedBy": "Developed by Bluewave Labs",
|
||||
"title": "About"
|
||||
},
|
||||
"demoMonitorsSettings": {
|
||||
"buttonAddMonitors": "Add demo monitors",
|
||||
"description": "Add sample monitors for demonstration purposes.",
|
||||
"title": "Demo monitors"
|
||||
},
|
||||
"emailSettings": {
|
||||
"buttonSendTestEmail": "Send test e-mail",
|
||||
"description": "Configure the email settings for your system. This is used to send notifications and alerts.",
|
||||
"descriptionTransport": "This builds an SMTP transport for NodeMailer",
|
||||
"labelAddress": "Email address - Used for authentication",
|
||||
"labelConnectionHost": "Email connection host - Hostname to use in the HELO/EHLO greeting",
|
||||
"labelHost": "Email host - Hostname or IP address to connect to",
|
||||
"labelIgnoreTLS": "Disable STARTTLS: Don't use TLS even if the server supports it",
|
||||
"labelPassword": "Email password - Password for authentication",
|
||||
"labelPasswordSet": "Password is set. Click Reset to change it.",
|
||||
"labelPool": "Enable connection pooling: Reuse existing connections to improve performance",
|
||||
"labelPort": "Email port - Port to connect to",
|
||||
"labelRejectUnauthorized": "Reject invalid certificates: Reject connections with self-signed or untrusted certificates",
|
||||
"labelRequireTLS": "Force STARTTLS: Require TLS upgrade, fail if not supported",
|
||||
"labelSecure": "Use SSL (recommended): Encrypt the connection using SSL/TLS",
|
||||
"labelTLSServername": "TLS Servername - Optional Hostname for TLS Validation when host is an IP",
|
||||
"labelUser": "Email user - Username for authentication, overrides email address if specified",
|
||||
"linkTransport": "See specifications here",
|
||||
"placeholderUser": "Leave empty if not required",
|
||||
"title": "Email",
|
||||
"toastEmailRequiredFieldsError": "Email address, host, port and password are required"
|
||||
},
|
||||
"globalThresholds": {
|
||||
"description": "Configure global CPU, Memory, Disk, and Temperature thresholds. If a value is provided, it will automatically be enabled for monitoring.",
|
||||
"title": "Global Thresholds"
|
||||
},
|
||||
"pageSpeedSettings": {
|
||||
"description": "Enter your Google PageSpeed API key to enable Google PageSpeed monitoring. Click Reset to update the key.",
|
||||
"labelApiKey": "PageSpeed API key",
|
||||
"labelApiKeySet": "API key is set. Click Reset to change it.",
|
||||
"title": "Google PageSpeed API key"
|
||||
},
|
||||
"saveButtonLabel": "Save",
|
||||
"statsSettings": {
|
||||
"clearAllStatsButton": "Clear all stats",
|
||||
"clearAllStatsDescription": "Clear all stats. This is irreversible.",
|
||||
"clearAllStatsDialogConfirm": "Yes, clear all stats",
|
||||
"clearAllStatsDialogDescription": "Once removed, the monitoring history and stats cannot be retrieved.",
|
||||
"clearAllStatsDialogTitle": "Do you want to clear all stats?",
|
||||
"description": "Define how long you want to retain historical data. You can also clear all existing data.",
|
||||
"title": "Monitor history"
|
||||
},
|
||||
"systemResetSettings": {
|
||||
"buttonRemoveAllMonitors": "Remove all monitors",
|
||||
"description": "Remove all monitors from your system.",
|
||||
"dialogConfirm": "Yes, remove all monitors",
|
||||
"dialogTitle": "Do you want to remove all monitors?",
|
||||
"title": "System reset"
|
||||
},
|
||||
"timezoneSettings": {
|
||||
"description": "Select the timezone used to display dates and times throughout the application.",
|
||||
"title": "Display timezone"
|
||||
},
|
||||
"title": "Settings",
|
||||
"uiSettings": {
|
||||
"chartTypeHeatmap": "Heatmap",
|
||||
"chartTypeHistogram": "Histogram",
|
||||
"description": "Switch between light and dark mode, or change user interface language.",
|
||||
"labelChartType": "Chart type",
|
||||
"labelLanguage": "Language",
|
||||
"labelTheme": "Theme mode",
|
||||
"title": "Appearance"
|
||||
},
|
||||
"urlSettings": {
|
||||
"description": "Display the IP address or URL of monitor on the public Status page. If it's disabled, only the monitor name will be shown to protect sensitive information.",
|
||||
"label": "Display IP/URL on status page",
|
||||
"selectDisabled": "Disabled",
|
||||
"selectEnabled": "Enabled",
|
||||
"title": "Monitor IP/URL on Status Page"
|
||||
}
|
||||
},
|
||||
"settingsStatsCleared": "Stats cleared successfully",
|
||||
"settingsSuccessSaved": "Settings saved successfully",
|
||||
"settingsTestEmailFailed": "Failed to send test email",
|
||||
"settingsTestEmailFailedWithReason": "Failed to send test email: {{reason}}",
|
||||
"settingsTestEmailSuccess": "Test email sent successfully",
|
||||
"settingsTestEmailUnknownError": "Unknown error",
|
||||
"shown": "Shown",
|
||||
"starPromptDescription": "See the latest releases and help grow the community on GitHub",
|
||||
"starPromptTitle": "Star Checkmate",
|
||||
"startTime": "Start time",
|
||||
"state": "State",
|
||||
"status": "Status",
|
||||
"statusCode": "Status code",
|
||||
"statusPage": {
|
||||
"contents": "Contents",
|
||||
"deleteFailed": "Failed to delete status page",
|
||||
"deleteSuccess": "Status page deleted successfully",
|
||||
"generalSettings": "General settings",
|
||||
"details": {
|
||||
"statusHeader": {
|
||||
"allUp": "All systems operational",
|
||||
"allDown": "All systems are experiencing issues",
|
||||
"anyDown": "Some systems are experiencing issues"
|
||||
}
|
||||
}
|
||||
},
|
||||
"statusPageCreate": {
|
||||
"buttonSave": "Save"
|
||||
},
|
||||
"statusPageStatusServiceStatus": "Service status",
|
||||
"submit": "Submit",
|
||||
"SupportedFormats": "Supported formats",
|
||||
"teamPanel": {
|
||||
"addTeamMember": {
|
||||
"addButton": "Add Member",
|
||||
"addMemberMenu": "Add Team Member",
|
||||
"description": "Create a new user and share the credentials with them. This method gives the member immediate access to all monitors.",
|
||||
"title": "Register new team member"
|
||||
},
|
||||
"addMember": "Add member",
|
||||
"cancel": "Cancel",
|
||||
"changeTeamPassword": {
|
||||
"changePasswordMenu": "Reset Password",
|
||||
"description": "Create a new password for this team member. You will need to share the password with them securely.",
|
||||
"success": "Password successfully reset. Make sure to provide the credentials to the member in a secure way.",
|
||||
"title": "Reset team member password"
|
||||
},
|
||||
"email": "Email",
|
||||
"emailToken": "E-mail token",
|
||||
"filter": {
|
||||
"all": "All",
|
||||
"member": "Member"
|
||||
},
|
||||
"getToken": "Get token",
|
||||
"inviteDescription": "When you add a new team member, they will get access to all monitors.",
|
||||
"inviteLink": "Invite link",
|
||||
"inviteNewTeamMember": "Invite new team member",
|
||||
"inviteTeamMember": "Invite a team member",
|
||||
"noMembers": "There are no team members with this role",
|
||||
"register": "Register a team member",
|
||||
"registerTeamMember": {
|
||||
"auth": {
|
||||
"common": {
|
||||
"inputs": {
|
||||
"role": {
|
||||
"errors": {
|
||||
"empty": "Role is required"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"registerToast": {
|
||||
"success": "User created, share credentials with the member securely."
|
||||
},
|
||||
"role": "Role",
|
||||
"selectRole": "Select role",
|
||||
"table": {
|
||||
"created": "Created",
|
||||
"email": "Email",
|
||||
"name": "Name",
|
||||
"role": "Role"
|
||||
},
|
||||
"teamMembers": "Team members"
|
||||
},
|
||||
"time": {
|
||||
"fifteenMinutes": "15 minutes",
|
||||
"fifteenSeconds": "15 seconds",
|
||||
"fiveMinutes": "5 minutes",
|
||||
"fourMinutes": "4 minutes",
|
||||
"oneMinute": "1 minute",
|
||||
"tenMinutes": "10 minutes",
|
||||
"thirtyMinutes": "30 minutes",
|
||||
"thirtySeconds": "30 seconds",
|
||||
"threeMinutes": "3 minutes",
|
||||
"twoMinutes": "2 minutes"
|
||||
},
|
||||
"timezone": "Timezone",
|
||||
"timeZoneInfo": "All dates and times are in GMT+0 time zone.",
|
||||
"title": "Title",
|
||||
"total": "Total",
|
||||
"type": "Type",
|
||||
"update": "Update",
|
||||
"uptime": "Uptime",
|
||||
"url": "URL",
|
||||
"used": "Used",
|
||||
"window": "window",
|
||||
"YourPhoto": "Profile photo",
|
||||
"failedDeleteMonitor": "Failed to delete monitor",
|
||||
"failedPauseMonitor": "Failed to pause monitor",
|
||||
"hour": "hour",
|
||||
"minute": "minute",
|
||||
"monitorDeleted": "Monitor deleted successfully",
|
||||
"monitorPaused": "Monitor paused successfully",
|
||||
"monitorResumed": "Monitor resumed successfully",
|
||||
"name": "Name"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ import App from "./App.jsx";
|
||||
import "./index.css";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
import { Provider } from "react-redux";
|
||||
import { persistor, store } from "./store.js";
|
||||
import { persistor, store } from "@/store.js";
|
||||
import { PersistGate } from "redux-persist/integration/react";
|
||||
import I18nLoader from "./Components/v1/I18nLoader/index.jsx";
|
||||
import I18nLoader from "./Components/v2/i18nLoader";
|
||||
import { initApiClient } from "./Utils/ApiClient.js";
|
||||
|
||||
initApiClient(store);
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import { configureStore, combineReducers } from "@reduxjs/toolkit";
|
||||
|
||||
import authReducer from "./Features/Auth/authSlice";
|
||||
import uiReducer from "./Features/UI/uiSlice";
|
||||
import authReducer from "@/Features/Auth/authSlice";
|
||||
import uiReducer from "@/Features/UI/uiSlice";
|
||||
import storage from "redux-persist/lib/storage";
|
||||
import { persistReducer, persistStore, createTransform } from "redux-persist";
|
||||
import {
|
||||
persistReducer,
|
||||
persistStore,
|
||||
createTransform,
|
||||
PERSIST,
|
||||
REHYDRATE,
|
||||
} from "redux-persist";
|
||||
|
||||
const authTransform = createTransform(
|
||||
(inboundState) => {
|
||||
(inboundState: Record<string, unknown>) => {
|
||||
const { profileImage, ...rest } = inboundState;
|
||||
return rest;
|
||||
},
|
||||
// No transformation on rehydration
|
||||
null,
|
||||
// Only applies to auth
|
||||
undefined,
|
||||
{ whitelist: ["auth"] }
|
||||
);
|
||||
|
||||
@@ -28,6 +31,7 @@ const rootReducer = combineReducers({
|
||||
ui: uiReducer,
|
||||
});
|
||||
|
||||
// @ts-expect-error - redux-persist types don't align perfectly with redux-toolkit
|
||||
const persistedReducer = persistReducer(persistConfig, rootReducer);
|
||||
|
||||
export const store = configureStore({
|
||||
@@ -35,10 +39,13 @@ export const store = configureStore({
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware({
|
||||
serializableCheck: {
|
||||
ignoredActions: ["persist/PERSIST", "persist/REHYDRATE", "persist/REGISTER"],
|
||||
ignoredActions: [PERSIST, REHYDRATE, "persist/REGISTER"],
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
export type RootState = ReturnType<typeof rootReducer>;
|
||||
export type AppDispatch = typeof store.dispatch;
|
||||
|
||||
export const persistor = persistStore(store);
|
||||
export default store;
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
function flattenKeys(obj, prefix = "", result = []) {
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
const newKey = prefix ? `${prefix}.${key}` : key;
|
||||
|
||||
if (
|
||||
typeof obj[key] === "object" &&
|
||||
obj[key] !== null &&
|
||||
!Array.isArray(obj[key])
|
||||
) {
|
||||
flattenKeys(obj[key], newKey, result);
|
||||
} else {
|
||||
result.push(newKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Paths
|
||||
const localesDir = path.join(__dirname, "../client/src/locales");
|
||||
const inputPath = path.join(localesDir, "en.json");
|
||||
const outputPath = path.join(localesDir, "keys.json");
|
||||
|
||||
try {
|
||||
const jsonContent = fs.readFileSync(inputPath, "utf8");
|
||||
const jsonData = JSON.parse(jsonContent);
|
||||
|
||||
const flattenedKeys = flattenKeys(jsonData);
|
||||
flattenedKeys.sort();
|
||||
|
||||
fs.writeFileSync(outputPath, JSON.stringify(flattenedKeys, null, 2), "utf8");
|
||||
|
||||
console.log(`Keys written to: ${outputPath}`);
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -30,7 +30,7 @@ export const initializeControllers = (services: InitializedServices): Initialize
|
||||
return {
|
||||
authController: new AuthController(services.userService),
|
||||
monitorController: new MonitorController(services.monitorService),
|
||||
settingsController: new SettingsController(services.settingsService, services.emailService, services.db),
|
||||
settingsController: new SettingsController(services.settingsService, services.emailService),
|
||||
checkController: new CheckController(services.checkService),
|
||||
inviteController: new InviteController(services.inviteService),
|
||||
maintenanceWindowController: new MaintenanceWindowController(services.maintenanceWindowService),
|
||||
|
||||
@@ -41,18 +41,6 @@ import crypto from "crypto";
|
||||
import { games, GameDig } from "gamedig";
|
||||
import jmespath from "jmespath";
|
||||
|
||||
// DB Modules
|
||||
import { GenerateAvatarImage } from "../utils/imageProcessing.js";
|
||||
import { ParseBoolean } from "../utils/utils.js";
|
||||
|
||||
// Models
|
||||
import InviteToken from "../db/models/Invite.js";
|
||||
import Team from "../db/models/Team.js";
|
||||
import MaintenanceWindow from "../db/models/MaintenanceWindow.js";
|
||||
import MonitorStats from "../db/models/MonitorStats.js";
|
||||
import NotificationModel from "../db/models/Notification.js";
|
||||
import RecoveryToken from "../db/models/RecoveryToken.js";
|
||||
|
||||
// repositories
|
||||
import {
|
||||
MongoMonitorsRepository,
|
||||
@@ -176,7 +164,7 @@ export const initializeServices = async ({
|
||||
|
||||
const bufferService = new BufferService({ logger, checkService, settingsService });
|
||||
|
||||
const statusService = new StatusService({ logger, buffer: bufferService, monitorsRepository });
|
||||
const statusService = new StatusService(logger, bufferService, monitorsRepository, monitorStatsRepository, checksRepository);
|
||||
|
||||
const webhookProvider = new WebhookProvider(logger);
|
||||
const slackProvider = new SlackProvider(logger);
|
||||
@@ -194,6 +182,7 @@ export const initializeServices = async ({
|
||||
discordProvider,
|
||||
pagerDutyProvider,
|
||||
matrixProvider,
|
||||
settingsService,
|
||||
logger
|
||||
);
|
||||
|
||||
@@ -206,6 +195,7 @@ export const initializeServices = async ({
|
||||
buffer: bufferService,
|
||||
incidentService,
|
||||
maintenanceWindowsRepository,
|
||||
monitorsRepository,
|
||||
});
|
||||
|
||||
const superSimpleQueue = await SuperSimpleQueue.create({
|
||||
|
||||
@@ -8,11 +8,9 @@ class SettingsController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
private settingsService: any;
|
||||
private emailService: any;
|
||||
private db: any;
|
||||
constructor(settingsService: any, emailService: any, db: any) {
|
||||
constructor(settingsService: any, emailService: any) {
|
||||
this.settingsService = settingsService;
|
||||
this.emailService = emailService;
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { Schema, model, Types, type UpdateQuery } from "mongoose";
|
||||
import type { Monitor, MonitorMatchMethod, MonitorThresholds, CheckSnapshot } from "@/types/monitor.js";
|
||||
import { MonitorTypes } from "@/types/monitor.js";
|
||||
import Check from "./Check.js";
|
||||
import MonitorStats from "./MonitorStats.js";
|
||||
import StatusPage from "./StatusPage.js";
|
||||
import type { Monitor, MonitorMatchMethod, CheckSnapshot } from "@/types/monitor.js";
|
||||
import { MonitorTypes, MonitorStatuses } from "@/types/monitor.js";
|
||||
|
||||
type CheckSnapshotDocument = Omit<CheckSnapshot, "createdAt"> & { createdAt: Date };
|
||||
|
||||
@@ -16,7 +13,6 @@ type MonitorDocumentBase = Omit<
|
||||
notifications: Types.ObjectId[];
|
||||
selectedDisks: string[];
|
||||
matchMethod?: MonitorMatchMethod;
|
||||
thresholds?: MonitorThresholds;
|
||||
};
|
||||
|
||||
interface MonitorDocument extends MonitorDocumentBase {
|
||||
@@ -27,16 +23,6 @@ interface MonitorDocument extends MonitorDocumentBase {
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
const thresholdsSchema = new Schema<MonitorThresholds>(
|
||||
{
|
||||
usage_cpu: { type: Number },
|
||||
usage_memory: { type: Number },
|
||||
usage_disk: { type: Number },
|
||||
usage_temperature: { type: Number },
|
||||
},
|
||||
{ _id: false }
|
||||
);
|
||||
|
||||
const checkSnapshotSchema = new Schema<CheckSnapshotDocument>(
|
||||
{
|
||||
id: { type: String, required: true },
|
||||
@@ -68,8 +54,9 @@ const MonitorSchema = new Schema<MonitorDocument>(
|
||||
type: String,
|
||||
},
|
||||
status: {
|
||||
type: Boolean,
|
||||
default: undefined,
|
||||
type: String,
|
||||
enum: MonitorStatuses,
|
||||
default: "initializing",
|
||||
},
|
||||
statusWindow: {
|
||||
type: [Boolean],
|
||||
@@ -134,36 +121,37 @@ const MonitorSchema = new Schema<MonitorDocument>(
|
||||
secret: {
|
||||
type: String,
|
||||
},
|
||||
thresholds: {
|
||||
type: thresholdsSchema,
|
||||
cpuAlertThreshold: {
|
||||
type: Number,
|
||||
default: 100,
|
||||
},
|
||||
alertThreshold: {
|
||||
cpuAlertCounter: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
},
|
||||
cpuAlertThreshold: {
|
||||
type: Number,
|
||||
default: function () {
|
||||
return this.alertThreshold;
|
||||
},
|
||||
},
|
||||
memoryAlertThreshold: {
|
||||
type: Number,
|
||||
default: function () {
|
||||
return this.alertThreshold;
|
||||
},
|
||||
default: 100,
|
||||
},
|
||||
memoryAlertCounter: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
},
|
||||
diskAlertThreshold: {
|
||||
type: Number,
|
||||
default: function () {
|
||||
return this.alertThreshold;
|
||||
},
|
||||
default: 100,
|
||||
},
|
||||
diskAlertCounter: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
},
|
||||
tempAlertThreshold: {
|
||||
type: Number,
|
||||
default: function () {
|
||||
return this.alertThreshold;
|
||||
},
|
||||
default: 100,
|
||||
},
|
||||
tempAlertCounter: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
},
|
||||
selectedDisks: {
|
||||
type: [String],
|
||||
@@ -191,33 +179,6 @@ const MonitorSchema = new Schema<MonitorDocument>(
|
||||
}
|
||||
);
|
||||
|
||||
MonitorSchema.pre("save", function (next) {
|
||||
if (!this.cpuAlertThreshold || this.isModified("alertThreshold")) {
|
||||
this.cpuAlertThreshold = this.alertThreshold;
|
||||
}
|
||||
if (!this.memoryAlertThreshold || this.isModified("alertThreshold")) {
|
||||
this.memoryAlertThreshold = this.alertThreshold;
|
||||
}
|
||||
if (!this.diskAlertThreshold || this.isModified("alertThreshold")) {
|
||||
this.diskAlertThreshold = this.alertThreshold;
|
||||
}
|
||||
if (!this.tempAlertThreshold || this.isModified("alertThreshold")) {
|
||||
this.tempAlertThreshold = this.alertThreshold;
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
MonitorSchema.pre("findOneAndUpdate", function (next) {
|
||||
const update = this.getUpdate() as UpdateQuery<MonitorDocument> | null;
|
||||
if (update && !Array.isArray(update) && update.alertThreshold !== undefined) {
|
||||
update.cpuAlertThreshold = update.alertThreshold;
|
||||
update.memoryAlertThreshold = update.alertThreshold;
|
||||
update.diskAlertThreshold = update.alertThreshold;
|
||||
update.tempAlertThreshold = update.alertThreshold;
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
MonitorSchema.index({ teamId: 1, type: 1 });
|
||||
|
||||
const MonitorModel = model<MonitorDocument>("Monitor", MonitorSchema);
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { LatestChecksMap } from "@/repositories/checks/MongoChecksRepistory
|
||||
|
||||
export interface IChecksRepository {
|
||||
// create
|
||||
create(check: Check): Promise<Check>;
|
||||
createChecks(checks: Check[]): Promise<Check[]>;
|
||||
|
||||
// single fetch
|
||||
|
||||
@@ -227,6 +227,11 @@ class MongoChecksRepository implements IChecksRepository {
|
||||
} as unknown as CheckDocument;
|
||||
};
|
||||
|
||||
create = async (check: Check) => {
|
||||
const savedCheck = await CheckModel.create(check);
|
||||
return this.toEntity(savedCheck);
|
||||
};
|
||||
|
||||
createChecks = async (checks: Check[]) => {
|
||||
const docs = checks.map((check) => this.toDocument(check));
|
||||
const inserted = await CheckModel.insertMany(docs);
|
||||
|
||||