feat: leave only ui changes
This commit is contained in:
16 files changed
+29
-286
No files matched your search
@@ -102,6 +102,7 @@ const uiSlice = createSlice({
|
||||
setShowURL: (state, action: PayloadAction<boolean>) => {
|
||||
state.showURL = action.payload;
|
||||
},
|
||||
|
||||
setTimezone: (state, action: PayloadAction<{ timezone: string }>) => {
|
||||
if (action.payload.timezone) {
|
||||
state.timezone = action.payload.timezone;
|
||||
|
||||
@@ -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 (
|
||||
<Stack gap={theme.spacing(LAYOUT.XS)}>
|
||||
{[80, 60, 90, 50, 70].map((w, i) => (
|
||||
<Skeleton
|
||||
key={i}
|
||||
variant="rounded"
|
||||
height={14}
|
||||
width={`${w}%`}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
if (error) {
|
||||
return (
|
||||
<Typography
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.error.main}
|
||||
>
|
||||
{t("pages.dashboard.error")}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
return children;
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -87,7 +48,7 @@ export const DashboardCard = ({
|
||||
</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
{renderContent()}
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -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 (
|
||||
<DashboardCard
|
||||
title={t("pages.dashboard.cards.monitorStatus")}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
>
|
||||
<DashboardCard title={t("pages.dashboard.cards.monitorStatus")}>
|
||||
<Stack gap={theme.spacing(SPACING.SM)}>
|
||||
<Stack
|
||||
direction="row"
|
||||
|
||||
@@ -8,26 +8,16 @@ import { LAYOUT } from "@/Utils/Theme/constants";
|
||||
|
||||
interface MonitorsByTypeCardProps {
|
||||
monitorsByType: DashboardTypeCount[];
|
||||
isLoading?: boolean;
|
||||
error?: unknown;
|
||||
}
|
||||
|
||||
export const MonitorsByTypeCard = ({
|
||||
monitorsByType,
|
||||
isLoading,
|
||||
error,
|
||||
}: MonitorsByTypeCardProps) => {
|
||||
export const MonitorsByTypeCard = ({ monitorsByType }: MonitorsByTypeCardProps) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
|
||||
const maxCount = monitorsByType[0]?.count ?? 1;
|
||||
|
||||
return (
|
||||
<DashboardCard
|
||||
title={t("pages.dashboard.cards.monitorsByType")}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
>
|
||||
<DashboardCard title={t("pages.dashboard.cards.monitorsByType")}>
|
||||
<Stack gap={LAYOUT.SM}>
|
||||
{monitorsByType.map(({ type, count }) => (
|
||||
<BarRow
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { useGet } from "@/Hooks/UseApi";
|
||||
import type { DashboardSummaryResult } from "@/Pages/Dashboard/types";
|
||||
import type { MonitorsSummary } from "@/Types/Monitor";
|
||||
|
||||
const EMPTY_SUMMARY: MonitorsSummary = {
|
||||
totalMonitors: 0,
|
||||
upMonitors: 0,
|
||||
downMonitors: 0,
|
||||
pausedMonitors: 0,
|
||||
initializingMonitors: 0,
|
||||
maintenanceMonitors: 0,
|
||||
breachedMonitors: 0,
|
||||
};
|
||||
|
||||
export const useDashboardData = () => {
|
||||
const { data, isLoading, error } = useGet<DashboardSummaryResult>(
|
||||
"/dashboard/summary",
|
||||
undefined,
|
||||
{
|
||||
refreshInterval: 30000,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
error,
|
||||
summary: data?.summary ?? EMPTY_SUMMARY,
|
||||
monitorsByType: data?.monitorsByType ?? [],
|
||||
};
|
||||
};
|
||||
@@ -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 (
|
||||
<BasePage headerKey="dashboard">
|
||||
<Box
|
||||
@@ -22,16 +37,8 @@ const Dashboard = () => {
|
||||
gridTemplateColumns={isSmall ? "1fr" : "1fr 1fr"}
|
||||
gap={theme.spacing(LAYOUT.MD)}
|
||||
>
|
||||
<MonitorStatusCard
|
||||
summary={summary}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
/>
|
||||
<MonitorsByTypeCard
|
||||
monitorsByType={monitorsByType}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
/>
|
||||
<MonitorStatusCard summary={MOCK_SUMMARY} />
|
||||
<MonitorsByTypeCard monitorsByType={MOCK_MONITORS_BY_TYPE} />
|
||||
</Box>
|
||||
</BasePage>
|
||||
);
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
@@ -1887,7 +1887,6 @@
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"error": "Failed to load data",
|
||||
"monitorStatus": {
|
||||
"ofUp": "of {{total}} up"
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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),
|
||||
};
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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<DashboardSummaryResult>;
|
||||
}
|
||||
|
||||
export class DashboardService implements IDashboardService {
|
||||
private monitorsRepository: IMonitorsRepository;
|
||||
|
||||
constructor(monitorsRepository: IMonitorsRepository) {
|
||||
this.monitorsRepository = monitorsRepository;
|
||||
}
|
||||
|
||||
getSummary = async (teamId: string): Promise<DashboardSummaryResult> => {
|
||||
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<string, number>();
|
||||
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<string, number>();
|
||||
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,
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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[];
|
||||
}
|
||||
Reference in new issue
Block a user