mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-19 16:08:39 -05:00
remove unused utils, convert utils to TS
This commit is contained in:
@@ -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 (
|
||||
|
||||
+3
-2
@@ -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) {
|
||||
@@ -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";
|
||||
|
||||
@@ -12,11 +12,6 @@ interface SidebarState {
|
||||
collapsed: boolean;
|
||||
}
|
||||
|
||||
interface GreetingState {
|
||||
index: number;
|
||||
lastUpdate: string | null;
|
||||
}
|
||||
|
||||
interface UIState {
|
||||
monitors: TableState;
|
||||
team: TableState;
|
||||
@@ -26,7 +21,6 @@ interface UIState {
|
||||
sidebar: SidebarState;
|
||||
mode: ThemeMode;
|
||||
showURL: boolean;
|
||||
greeting: GreetingState;
|
||||
timezone: string;
|
||||
distributedUptimeEnabled: boolean;
|
||||
language: string;
|
||||
@@ -60,7 +54,6 @@ const initialState: UIState = {
|
||||
},
|
||||
mode: initialMode,
|
||||
showURL: false,
|
||||
greeting: { index: 0, lastUpdate: null },
|
||||
timezone: "America/Toronto",
|
||||
distributedUptimeEnabled: false,
|
||||
language: "en",
|
||||
@@ -94,13 +87,7 @@ const uiSlice = createSlice({
|
||||
setShowURL: (state, action: PayloadAction<boolean>) => {
|
||||
state.showURL = action.payload;
|
||||
},
|
||||
setGreeting: (
|
||||
state,
|
||||
action: PayloadAction<{ index: number; lastUpdate: string | null }>
|
||||
) => {
|
||||
state.greeting.index = action.payload.index;
|
||||
state.greeting.lastUpdate = action.payload.lastUpdate;
|
||||
},
|
||||
|
||||
setTimezone: (state, action: PayloadAction<{ timezone: string }>) => {
|
||||
state.timezone = action.payload.timezone;
|
||||
},
|
||||
@@ -124,7 +111,6 @@ export const {
|
||||
setCollapsed,
|
||||
setMode,
|
||||
setShowURL,
|
||||
setGreeting,
|
||||
setTimezone,
|
||||
setDistributedUptimeEnabled,
|
||||
setLanguage,
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
@@ -7,11 +7,11 @@ export const getMonitorPath = (type: 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];
|
||||
};
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
#Utils folder
|
||||
@@ -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 };
|
||||
@@ -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.";
|
||||
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import { BrowserRouter as Router } from "react-router-dom";
|
||||
import { Provider } from "react-redux";
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user