From cafea2bf5dd058770e80891d3181f3fe5cd34c78 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 12 Feb 2026 19:13:53 +0000 Subject: [PATCH] remove unused utils, convert utils to TS --- client/src/App.tsx | 8 - .../index.jsx => v2/i18nLoader/index.tsx} | 5 +- .../v2/monitors/charts/HistogramStatus.tsx | 2 +- client/src/Features/UI/uiSlice.ts | 16 +- .../Incidents/Components/IncidentTable.tsx | 4 +- client/src/Utils/Logger.js | 74 ------- client/src/Utils/MonitorUtils.ts | 8 +- client/src/Utils/ReadMe.md | 1 - client/src/Utils/fileUtils.js | 11 - client/src/Utils/greeting.jsx | 197 ------------------ client/src/Utils/{i18n.js => i18n.ts} | 20 +- client/src/Utils/monitorUtilsLegacy.js | 47 ----- client/src/Utils/roleUtils.js | 13 -- client/src/Utils/stringUtils.js | 46 ---- client/src/Utils/timeUtilsLegacy.js | 167 --------------- client/src/Utils/utils.js | 15 -- client/src/Validation/error.js | 101 --------- client/src/Validation/validation.js | 14 +- client/src/main.tsx | 2 +- 19 files changed, 40 insertions(+), 711 deletions(-) rename client/src/Components/{v1/I18nLoader/index.jsx => v2/i18nLoader/index.tsx} (63%) delete mode 100644 client/src/Utils/Logger.js delete mode 100644 client/src/Utils/ReadMe.md delete mode 100644 client/src/Utils/fileUtils.js delete mode 100644 client/src/Utils/greeting.jsx rename client/src/Utils/{i18n.js => i18n.ts} (55%) delete mode 100644 client/src/Utils/monitorUtilsLegacy.js delete mode 100644 client/src/Utils/roleUtils.js delete mode 100644 client/src/Utils/stringUtils.js delete mode 100644 client/src/Utils/timeUtilsLegacy.js delete mode 100644 client/src/Utils/utils.js delete mode 100644 client/src/Validation/error.js diff --git a/client/src/App.tsx b/client/src/App.tsx index 0a6b364f8..1004131e3 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -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 ( diff --git a/client/src/Components/v1/I18nLoader/index.jsx b/client/src/Components/v2/i18nLoader/index.tsx similarity index 63% rename from client/src/Components/v1/I18nLoader/index.jsx rename to client/src/Components/v2/i18nLoader/index.tsx index 676e63d7b..f0555afed 100644 --- a/client/src/Components/v1/I18nLoader/index.jsx +++ b/client/src/Components/v2/i18nLoader/index.tsx @@ -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) { diff --git a/client/src/Components/v2/monitors/charts/HistogramStatus.tsx b/client/src/Components/v2/monitors/charts/HistogramStatus.tsx index 1c25c339e..d3aa62a69 100644 --- a/client/src/Components/v2/monitors/charts/HistogramStatus.tsx +++ b/client/src/Components/v2/monitors/charts/HistogramStatus.tsx @@ -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"; diff --git a/client/src/Features/UI/uiSlice.ts b/client/src/Features/UI/uiSlice.ts index 0675784b9..84e8248db 100644 --- a/client/src/Features/UI/uiSlice.ts +++ b/client/src/Features/UI/uiSlice.ts @@ -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) => { 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, diff --git a/client/src/Pages/Incidents/Components/IncidentTable.tsx b/client/src/Pages/Incidents/Components/IncidentTable.tsx index 121f33123..a9b4bc104 100644 --- a/client/src/Pages/Incidents/Components/IncidentTable.tsx +++ b/client/src/Pages/Incidents/Components/IncidentTable.tsx @@ -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}`); } diff --git a/client/src/Utils/Logger.js b/client/src/Utils/Logger.js deleted file mode 100644 index 849e10854..000000000 --- a/client/src/Utils/Logger.js +++ /dev/null @@ -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(); diff --git a/client/src/Utils/MonitorUtils.ts b/client/src/Utils/MonitorUtils.ts index 703579fc6..7d7149c9d 100644 --- a/client/src/Utils/MonitorUtils.ts +++ b/client/src/Utils/MonitorUtils.ts @@ -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]; }; diff --git a/client/src/Utils/ReadMe.md b/client/src/Utils/ReadMe.md deleted file mode 100644 index 6488752d1..000000000 --- a/client/src/Utils/ReadMe.md +++ /dev/null @@ -1 +0,0 @@ -#Utils folder diff --git a/client/src/Utils/fileUtils.js b/client/src/Utils/fileUtils.js deleted file mode 100644 index 1c35738dd..000000000 --- a/client/src/Utils/fileUtils.js +++ /dev/null @@ -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; -}; diff --git a/client/src/Utils/greeting.jsx b/client/src/Utils/greeting.jsx deleted file mode 100644 index c4afbf88c..000000000 --- a/client/src/Utils/greeting.jsx +++ /dev/null @@ -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 ; - * - * @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 ( - - - - {t("greeting.prepend", { defaultValue: prepend })},{" "} - - - {firstName} {emoji} - - - - {t("greeting.append", { defaultValue: append })} —{" "} - {t("greeting.overview", { type: t(`menu.${type}`) })} - - - ); -}; - -Greeting.propTypes = { - type: PropTypes.string, -}; - -export default Greeting; diff --git a/client/src/Utils/i18n.js b/client/src/Utils/i18n.ts similarity index 55% rename from client/src/Utils/i18n.js rename to client/src/Utils/i18n.ts index 030091d48..db448a065 100644 --- a/client/src/Utils/i18n.js +++ b/client/src/Utils/i18n.ts @@ -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; + [key: string]: unknown; +} -const resources = {}; +// Load all translation files eagerly +const translations = import.meta.glob("../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], }; }); diff --git a/client/src/Utils/monitorUtilsLegacy.js b/client/src/Utils/monitorUtilsLegacy.js deleted file mode 100644 index e3645b0d0..000000000 --- a/client/src/Utils/monitorUtilsLegacy.js +++ /dev/null @@ -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", -}; diff --git a/client/src/Utils/roleUtils.js b/client/src/Utils/roleUtils.js deleted file mode 100644 index f359246b2..000000000 --- a/client/src/Utils/roleUtils.js +++ /dev/null @@ -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 }, -]; diff --git a/client/src/Utils/stringUtils.js b/client/src/Utils/stringUtils.js deleted file mode 100644 index bb62d0169..000000000 --- a/client/src/Utils/stringUtils.js +++ /dev/null @@ -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; -}; diff --git a/client/src/Utils/timeUtilsLegacy.js b/client/src/Utils/timeUtilsLegacy.js deleted file mode 100644 index 365572f6b..000000000 --- a/client/src/Utils/timeUtilsLegacy.js +++ /dev/null @@ -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"; - } -}; diff --git a/client/src/Utils/utils.js b/client/src/Utils/utils.js deleted file mode 100644 index 81ec44b06..000000000 --- a/client/src/Utils/utils.js +++ /dev/null @@ -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; -}; diff --git a/client/src/Validation/error.js b/client/src/Validation/error.js deleted file mode 100644 index 5738edf40..000000000 --- a/client/src/Validation/error.js +++ /dev/null @@ -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 }; diff --git a/client/src/Validation/validation.js b/client/src/Validation/validation.js index 27d585074..fa92f2e99 100644 --- a/client/src/Validation/validation.js +++ b/client/src/Validation/validation.js @@ -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."; diff --git a/client/src/main.tsx b/client/src/main.tsx index 1e339f87b..f075886d3 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -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);