diff --git a/client/src/Features/UI/uiSlice.ts b/client/src/Features/UI/uiSlice.ts index 198ed8389..a07b629f8 100644 --- a/client/src/Features/UI/uiSlice.ts +++ b/client/src/Features/UI/uiSlice.ts @@ -102,6 +102,7 @@ const uiSlice = createSlice({ setShowURL: (state, action: PayloadAction) => { state.showURL = action.payload; }, + setTimezone: (state, action: PayloadAction<{ timezone: string }>) => { if (action.payload.timezone) { state.timezone = action.payload.timezone; diff --git a/client/src/Pages/Dashboard/components/DashboardCard.tsx b/client/src/Pages/Dashboard/components/DashboardCard.tsx index f638e110b..b075ca0f3 100644 --- a/client/src/Pages/Dashboard/components/DashboardCard.tsx +++ b/client/src/Pages/Dashboard/components/DashboardCard.tsx @@ -1,57 +1,18 @@ import Box from "@mui/material/Box"; -import Skeleton from "@mui/material/Skeleton"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; import { useTheme } from "@mui/material/styles"; -import { useTranslation } from "react-i18next"; import { LAYOUT } from "@/Utils/Theme/constants"; import { typographyLevels } from "@/Utils/Theme/Palette"; interface DashboardCardProps { title: string; topRight?: React.ReactNode; - isLoading?: boolean; - error?: unknown; children: React.ReactNode; } -export const DashboardCard = ({ - title, - topRight, - isLoading, - error, - children, -}: DashboardCardProps) => { +export const DashboardCard = ({ title, topRight, children }: DashboardCardProps) => { const theme = useTheme(); - const { t } = useTranslation(); - - const renderContent = () => { - if (isLoading) { - return ( - - {[80, 60, 90, 50, 70].map((w, i) => ( - - ))} - - ); - } - if (error) { - return ( - - {t("pages.dashboard.error")} - - ); - } - return children; - }; return ( )} - {renderContent()} + {children} ); }; diff --git a/client/src/Pages/Dashboard/components/cards/MonitorStatusCard.tsx b/client/src/Pages/Dashboard/components/cards/MonitorStatusCard.tsx index 16eb9bceb..6ef28301e 100644 --- a/client/src/Pages/Dashboard/components/cards/MonitorStatusCard.tsx +++ b/client/src/Pages/Dashboard/components/cards/MonitorStatusCard.tsx @@ -7,17 +7,12 @@ import { typographyLevels } from "@/Utils/Theme/Palette"; import { SPACING } from "@/Utils/Theme/constants"; import { useTranslation } from "react-i18next"; import type { MonitorsSummary } from "@/Types/Monitor"; + interface MonitorStatusCardProps { summary: MonitorsSummary; - isLoading?: boolean; - error?: unknown; } -export const MonitorStatusCard = ({ - summary, - isLoading, - error, -}: MonitorStatusCardProps) => { +export const MonitorStatusCard = ({ summary }: MonitorStatusCardProps) => { const theme = useTheme(); const { t } = useTranslation(); @@ -26,11 +21,7 @@ export const MonitorStatusCard = ({ const ratio = total > 0 ? up / total : 0; return ( - + { +export const MonitorsByTypeCard = ({ monitorsByType }: MonitorsByTypeCardProps) => { const { t } = useTranslation(); const theme = useTheme(); const maxCount = monitorsByType[0]?.count ?? 1; return ( - + {monitorsByType.map(({ type, count }) => ( { - const { data, isLoading, error } = useGet( - "/dashboard/summary", - undefined, - { - refreshInterval: 30000, - } - ); - - return { - isLoading, - error, - summary: data?.summary ?? EMPTY_SUMMARY, - monitorsByType: data?.monitorsByType ?? [], - }; -}; diff --git a/client/src/Pages/Dashboard/index.tsx b/client/src/Pages/Dashboard/index.tsx index 4cb9045ab..9f89ead27 100644 --- a/client/src/Pages/Dashboard/index.tsx +++ b/client/src/Pages/Dashboard/index.tsx @@ -5,16 +5,31 @@ import useMediaQuery from "@mui/material/useMediaQuery"; import { BasePage } from "@/Components/design-elements/BasePage"; import { LAYOUT } from "@/Utils/Theme/constants"; -import { useDashboardData } from "@/Pages/Dashboard/hooks/useDashboardData"; import { MonitorStatusCard } from "@/Pages/Dashboard/components/cards/MonitorStatusCard"; import { MonitorsByTypeCard } from "@/Pages/Dashboard/components/cards/MonitorsByTypeCard"; +import type { MonitorsSummary } from "@/Types/Monitor"; +import type { DashboardTypeCount } from "@/Pages/Dashboard/types"; + +const MOCK_SUMMARY: MonitorsSummary = { + totalMonitors: 12, + upMonitors: 10, + downMonitors: 1, + pausedMonitors: 1, + initializingMonitors: 0, + maintenanceMonitors: 0, + breachedMonitors: 0, +}; + +const MOCK_MONITORS_BY_TYPE: DashboardTypeCount[] = [ + { type: "http", count: 7 }, + { type: "ping", count: 3 }, + { type: "port", count: 2 }, +]; const Dashboard = () => { const theme = useTheme(); const isSmall = useMediaQuery(theme.breakpoints.down("md")); - const { isLoading, error, summary, monitorsByType } = useDashboardData(); - return ( { gridTemplateColumns={isSmall ? "1fr" : "1fr 1fr"} gap={theme.spacing(LAYOUT.MD)} > - - + + ); diff --git a/client/src/Pages/Dashboard/types.ts b/client/src/Pages/Dashboard/types.ts index f9d4c34fb..bbb5c3924 100644 --- a/client/src/Pages/Dashboard/types.ts +++ b/client/src/Pages/Dashboard/types.ts @@ -1,31 +1,4 @@ -import type { MonitorsSummary } from "@/Types/Monitor"; - -export interface DashboardMonitorRow { - id: string; - name: string; - status: string; - type: string; - group: string | null; - uptimePercentage: number; - avgResponseTime: number; -} - export interface DashboardTypeCount { type: string; count: number; } - -export interface DashboardGroupCount { - group: string; - count: number; -} - -export interface DashboardSummaryResult { - summary: MonitorsSummary; - monitors: DashboardMonitorRow[]; - slowestMonitors: DashboardMonitorRow[]; - lowestUptimeMonitors: DashboardMonitorRow[]; - downMonitors: DashboardMonitorRow[]; - monitorsByType: DashboardTypeCount[]; - monitorsByGroup: DashboardGroupCount[]; -} diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 32c7a07d4..b9705f6a7 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -1887,7 +1887,6 @@ } }, "dashboard": { - "error": "Failed to load data", "monitorStatus": { "ofUp": "of {{total}} up" }, diff --git a/client/src/store.ts b/client/src/store.ts index 706a58da2..d2a1b96d8 100644 --- a/client/src/store.ts +++ b/client/src/store.ts @@ -25,6 +25,7 @@ const persistConfig = { storage, whitelist: ["auth", "ui"], transforms: [authTransform], + // Merge two levels deep on rehydrate so a slice key added in a new release keeps initial value stateReconciler: autoMergeLevel2, }; diff --git a/server/src/api/controllers/dashboardController.ts b/server/src/api/controllers/dashboardController.ts deleted file mode 100644 index 2c2d66791..000000000 --- a/server/src/api/controllers/dashboardController.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Request, Response, RequestHandler } from "express"; -import { catchAsync } from "@/utils/catchAsync.js"; -import { requireTeamId } from "@/api/controllers/controllerUtils.js"; -import type { IDashboardService } from "@/domain/dashboard/dashboard.service.js"; - -export interface IDashboardController { - getSummary: RequestHandler; -} - -class DashboardController implements IDashboardController { - private dashboardService: IDashboardService; - - constructor(dashboardService: IDashboardService) { - this.dashboardService = dashboardService; - } - - getSummary = catchAsync(async (req: Request, res: Response) => { - const teamId = requireTeamId(req.user?.teamId); - const data = await this.dashboardService.getSummary(teamId); - return res.status(200).json({ - success: true, - msg: "Dashboard summary retrieved successfully", - data, - }); - }); -} - -export default DashboardController; diff --git a/server/src/api/routes/dashboardRoutes.ts b/server/src/api/routes/dashboardRoutes.ts deleted file mode 100644 index 385e46c17..000000000 --- a/server/src/api/routes/dashboardRoutes.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Router } from "express"; -import type { IDashboardController } from "@/api/controllers/dashboardController.js"; - -export const createDashboardRoutes = (dashboardController: IDashboardController): Router => { - const router = Router(); - router.get("/summary", dashboardController.getSummary); - return router; -}; diff --git a/server/src/config/controllers.ts b/server/src/config/controllers.ts index 406e2c18d..7787a389e 100644 --- a/server/src/config/controllers.ts +++ b/server/src/config/controllers.ts @@ -13,8 +13,6 @@ import TagsController from "../api/controllers/tagController.js"; import DiagnosticController from "../api/controllers/diagnosticController.js"; import IncidentController from "../api/controllers/incidentController.js"; import ProxyController from "@/api/controllers/proxyController.js"; -import DashboardController from "@/api/controllers/dashboardController.js"; -import type { IDashboardController } from "@/api/controllers/dashboardController.js"; import { ApiServices } from "@/config/services.api.js"; export interface InitializedControllers { @@ -33,7 +31,6 @@ export interface InitializedControllers { diagnosticController: DiagnosticController; incidentController: IncidentController; proxyController: ProxyController; - dashboardController: IDashboardController; } export const initializeControllers = (apiServices: ApiServices): InitializedControllers => { return { @@ -52,6 +49,5 @@ export const initializeControllers = (apiServices: ApiServices): InitializedCont diagnosticController: new DiagnosticController(apiServices.diagnosticService), incidentController: new IncidentController(apiServices.incidentService), proxyController: new ProxyController(apiServices.proxiesService), - dashboardController: new DashboardController(apiServices.dashboardService), }; }; diff --git a/server/src/config/routes.ts b/server/src/config/routes.ts index 5a89f9bc7..6704fa83b 100644 --- a/server/src/config/routes.ts +++ b/server/src/config/routes.ts @@ -20,7 +20,6 @@ import { createNotificationRoutes } from "@/api/routes/notificationRoutes.js"; import { createTagRoutes } from "@/api/routes/tagRoutes.js"; import { createIncidentRoutes } from "@/api/routes/incidentRoutes.js"; import { createProxyRoutes } from "@/api/routes/proxyRoutes.js"; -import { createDashboardRoutes } from "@/api/routes/dashboardRoutes.js"; export const setupRoutes = (app: Application, controllers: InitializedControllers, apiServices: ApiServices) => { const verifyJWT = createVerifyJWT(apiServices.settingsService); @@ -40,7 +39,6 @@ export const setupRoutes = (app: Application, controllers: InitializedController const diagnosticRoutes = createDiagnosticRoutes(controllers.diagnosticController, verifyJWT); const incidentRoutes = createIncidentRoutes(controllers.incidentController); const proxyRoutes = createProxyRoutes(controllers.proxyController); - const dashboardRoutes = createDashboardRoutes(controllers.dashboardController); app.use("/api/v1/auth", authApiLimiter, authRoutes); app.use("/api/v1/monitors", verifyJWT, monitorRoutes); @@ -57,5 +55,4 @@ export const setupRoutes = (app: Application, controllers: InitializedController app.use("/api/v1/diagnostic", verifyJWT, diagnosticRoutes); app.use("/api/v1/incidents", verifyJWT, incidentRoutes); app.use("/api/v1/proxies", verifyJWT, proxyRoutes); - app.use("/api/v1/dashboard", verifyJWT, dashboardRoutes); }; diff --git a/server/src/config/services.api.ts b/server/src/config/services.api.ts index a8feec846..65607107c 100644 --- a/server/src/config/services.api.ts +++ b/server/src/config/services.api.ts @@ -7,7 +7,6 @@ import { ITagsService, TagsService } from "@/domain/tags/tag.service.js"; import { IUserService, UserService } from "@/domain/users/user.service.js"; import { IJobScheduler } from "@/worker/worker.interface.js"; import { ProxiesService, IProxiesService } from "@/domain/proxies/proxy.service.js"; -import { DashboardService, IDashboardService } from "@/domain/dashboard/dashboard.service.js"; import { SharedServices } from "@/config/services.shared.js"; // Third-party @@ -25,7 +24,6 @@ export interface ApiServices extends SharedServices { tagsService: ITagsService; diagnosticService: IDiagnosticService; proxiesService: IProxiesService; - dashboardService: IDashboardService; } export const buildApi = (shared: SharedServices, jobScheduler: IJobScheduler): ApiServices => { @@ -101,7 +99,6 @@ export const buildApi = (shared: SharedServices, jobScheduler: IJobScheduler): A const tagsService = new TagsService(tagsRepository, monitorsRepository); const diagnosticService = new DiagnosticService(db); const proxiesService = new ProxiesService(proxiesRepository, monitorsRepository, settingsService); - const dashboardService = new DashboardService(monitorsRepository); return { ...shared, worker: jobScheduler, @@ -113,6 +110,5 @@ export const buildApi = (shared: SharedServices, jobScheduler: IJobScheduler): A tagsService, diagnosticService, proxiesService, - dashboardService, }; }; diff --git a/server/src/domain/dashboard/dashboard.service.ts b/server/src/domain/dashboard/dashboard.service.ts deleted file mode 100644 index aca10d54c..000000000 --- a/server/src/domain/dashboard/dashboard.service.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js"; -import type { Monitor } from "@/domain/monitors/monitor.type.js"; -import type { DashboardMonitorRow, DashboardSummaryResult, DashboardTypeCount, DashboardGroupCount } from "@/domain/dashboard/dashboard.type.js"; - -const TOP_N = 5; - -const avgResponseTime = (monitor: Monitor): number => { - const times = (monitor.recentChecks ?? []).map((c) => c.responseTime).filter((t): t is number => t != null && t > 0); - return times.length ? times.reduce((a, b) => a + b, 0) / times.length : 0; -}; - -const toRow = (monitor: Monitor): DashboardMonitorRow => ({ - id: monitor.id, - name: monitor.name, - status: monitor.status, - type: monitor.type, - group: monitor.group ?? null, - uptimePercentage: (monitor.uptimePercentage ?? 1) * 100, - avgResponseTime: avgResponseTime(monitor), -}); - -export interface IDashboardService { - getSummary(teamId: string): Promise; -} - -export class DashboardService implements IDashboardService { - private monitorsRepository: IMonitorsRepository; - - constructor(monitorsRepository: IMonitorsRepository) { - this.monitorsRepository = monitorsRepository; - } - - getSummary = async (teamId: string): Promise => { - const [summary, monitors] = await Promise.all([ - this.monitorsRepository.findMonitorsSummaryByTeamId(teamId), - this.monitorsRepository.findByTeamIdWithStats(teamId, { limit: 200 }), - ]); - - const rows = monitors.map(toRow); - - const slowestMonitors = [...rows] - .filter((r) => r.avgResponseTime > 0) - .sort((a, b) => b.avgResponseTime - a.avgResponseTime) - .slice(0, TOP_N); - - const lowestUptimeMonitors = [...rows].sort((a, b) => a.uptimePercentage - b.uptimePercentage).slice(0, TOP_N); - - const downMonitors = rows.filter((r) => r.status === "down"); - - const typeMap = new Map(); - rows.forEach((r) => typeMap.set(r.type, (typeMap.get(r.type) ?? 0) + 1)); - const monitorsByType: DashboardTypeCount[] = [...typeMap.entries()].map(([type, count]) => ({ type, count })).sort((a, b) => b.count - a.count); - - const groupMap = new Map(); - rows.forEach((r) => { - if (r.group) groupMap.set(r.group, (groupMap.get(r.group) ?? 0) + 1); - }); - const monitorsByGroup: DashboardGroupCount[] = [...groupMap.entries()] - .map(([group, count]) => ({ group, count })) - .sort((a, b) => b.count - a.count); - - return { - summary, - monitors: rows, - slowestMonitors, - lowestUptimeMonitors, - downMonitors, - monitorsByType, - monitorsByGroup, - }; - }; -} diff --git a/server/src/domain/dashboard/dashboard.type.ts b/server/src/domain/dashboard/dashboard.type.ts deleted file mode 100644 index c5e445ebc..000000000 --- a/server/src/domain/dashboard/dashboard.type.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { MonitorsSummary } from "@/domain/monitors/monitor.type.js"; - -export interface DashboardMonitorRow { - id: string; - name: string; - status: string; - type: string; - group: string | null; - uptimePercentage: number; - avgResponseTime: number; -} - -export interface DashboardTypeCount { - type: string; - count: number; -} - -export interface DashboardGroupCount { - group: string; - count: number; -} - -export interface DashboardSummaryResult { - summary: MonitorsSummary; - monitors: DashboardMonitorRow[]; - slowestMonitors: DashboardMonitorRow[]; - lowestUptimeMonitors: DashboardMonitorRow[]; - downMonitors: DashboardMonitorRow[]; - monitorsByType: DashboardTypeCount[]; - monitorsByGroup: DashboardGroupCount[]; -}