Merge pull request #3944 from bluewave-labs/feat/refined-dashboard
feat: create dashboard of updates
This commit is contained in:
12 files changed
+294
-1
No files matched your search
Binary file not shown.
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
@@ -20,12 +20,18 @@ import {
|
||||
Tag,
|
||||
Waypoints,
|
||||
Container,
|
||||
// LayoutDashboard,
|
||||
} from "lucide-react";
|
||||
export const getMenu = (t: Function) => {
|
||||
return [
|
||||
{
|
||||
group: t("components.sidebar.menu.groups.monitoring"),
|
||||
items: [
|
||||
// {
|
||||
// name: t("components.sidebar.menu.dashboard"),
|
||||
// path: "dashboard",
|
||||
// icon: <Icon icon={LayoutDashboard} />,
|
||||
// },
|
||||
{
|
||||
name: t("components.sidebar.menu.uptime"),
|
||||
path: "uptime",
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import Box from "@mui/material/Box";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { LAYOUT } from "@/Utils/Theme/constants";
|
||||
import { typographyLevels } from "@/Utils/Theme/Palette";
|
||||
|
||||
interface DashboardCardProps {
|
||||
title: string;
|
||||
topRight?: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const DashboardCard = ({ title, topRight, children }: DashboardCardProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
bgcolor={theme.palette.background.paper}
|
||||
border={1}
|
||||
borderColor={theme.palette.divider}
|
||||
borderRadius={`${theme.shape.borderRadius}px`}
|
||||
padding={theme.spacing(LAYOUT.MD)}
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
gap={theme.spacing(LAYOUT.SM)}
|
||||
height="100%"
|
||||
boxSizing="border-box"
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Typography
|
||||
fontSize={typographyLevels.m}
|
||||
fontWeight={500}
|
||||
color={theme.palette.text.primary}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
{topRight && (
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
{topRight}
|
||||
</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import { LAYOUT } from "@/Utils/Theme/constants";
|
||||
import Box from "@mui/material/Box";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
|
||||
interface ProgressBarProps {
|
||||
fillRatio: number;
|
||||
}
|
||||
|
||||
export const ProgressBar = ({ fillRatio }: ProgressBarProps) => {
|
||||
const theme = useTheme();
|
||||
const clamped = Math.min(1, Math.max(0, fillRatio));
|
||||
|
||||
return (
|
||||
<Box
|
||||
height={theme.spacing(LAYOUT.XS)}
|
||||
borderRadius={theme.shape.borderRadius}
|
||||
bgcolor={theme.palette.action.disabledBackground}
|
||||
overflow="hidden"
|
||||
>
|
||||
<Box
|
||||
height="100%"
|
||||
width={`${clamped * 100}%`}
|
||||
borderRadius={theme.shape.borderRadius}
|
||||
bgcolor={theme.palette.primary.main}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { typographyLevels } from "@/Utils/Theme/Palette";
|
||||
import { SPACING } from "@/Utils/Theme/constants";
|
||||
import { ProgressBar } from "@/Pages/Dashboard/components/ProgressBar";
|
||||
|
||||
interface BarRowProps {
|
||||
label: string;
|
||||
value: string;
|
||||
fillRatio: number;
|
||||
valueColor?: string;
|
||||
}
|
||||
|
||||
export const BarRow = ({ label, value, fillRatio, valueColor }: BarRowProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(SPACING.LG)}>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<Typography
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.text.primary}
|
||||
>
|
||||
{label}
|
||||
</Typography>
|
||||
<Typography
|
||||
fontSize={typographyLevels.m}
|
||||
color={valueColor ?? theme.palette.primary.main}
|
||||
fontWeight={500}
|
||||
>
|
||||
{value}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<ProgressBar fillRatio={fillRatio} />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { DashboardCard } from "@/Pages/Dashboard/components/DashboardCard";
|
||||
import { ProgressBar } from "@/Pages/Dashboard/components/ProgressBar";
|
||||
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;
|
||||
}
|
||||
|
||||
export const MonitorStatusCard = ({ summary }: MonitorStatusCardProps) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const up = summary.upMonitors;
|
||||
const total = summary.totalMonitors;
|
||||
const ratio = total > 0 ? up / total : 0;
|
||||
|
||||
return (
|
||||
<DashboardCard title={t("pages.dashboard.cards.monitorStatus")}>
|
||||
<Stack gap={theme.spacing(SPACING.SM)}>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="baseline"
|
||||
gap={theme.spacing(SPACING.XS)}
|
||||
>
|
||||
<Typography
|
||||
fontSize={typographyLevels.xxl}
|
||||
fontWeight={500}
|
||||
color={theme.palette.text.primary}
|
||||
>
|
||||
{up}
|
||||
</Typography>
|
||||
<Typography
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
{t("pages.dashboard.monitorStatus.ofUp", { total })}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<ProgressBar fillRatio={ratio} />
|
||||
</Stack>
|
||||
</DashboardCard>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { DashboardCard } from "@/Pages/Dashboard/components/DashboardCard";
|
||||
import { BarRow } from "@/Pages/Dashboard/components/cards/BarRow";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import type { DashboardTypeCount } from "@/Pages/Dashboard/types";
|
||||
import { getMonitorTypeLabel } from "@/Types/Monitor";
|
||||
import { LAYOUT } from "@/Utils/Theme/constants";
|
||||
|
||||
interface MonitorsByTypeCardProps {
|
||||
monitorsByType: DashboardTypeCount[];
|
||||
}
|
||||
|
||||
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")}>
|
||||
<Stack gap={theme.spacing(LAYOUT.SM)}>
|
||||
{monitorsByType.map(({ type, count }) => (
|
||||
<BarRow
|
||||
key={type}
|
||||
label={getMonitorTypeLabel(type, t)}
|
||||
value={String(count)}
|
||||
fillRatio={count / maxCount}
|
||||
valueColor={theme.palette.text.primary}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</DashboardCard>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import Box from "@mui/material/Box";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
|
||||
import { BasePage } from "@/Components/design-elements/BasePage";
|
||||
import { LAYOUT } from "@/Utils/Theme/constants";
|
||||
|
||||
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();
|
||||
|
||||
return (
|
||||
<BasePage headerKey="dashboard">
|
||||
<Box
|
||||
display="grid"
|
||||
gridTemplateColumns={{ xs: "1fr", md: "1fr 1fr" }}
|
||||
gap={theme.spacing(LAYOUT.MD)}
|
||||
>
|
||||
<MonitorStatusCard summary={MOCK_SUMMARY} />
|
||||
<MonitorsByTypeCard monitorsByType={MOCK_MONITORS_BY_TYPE} />
|
||||
</Box>
|
||||
</BasePage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { MonitorType } from "@/Types/Monitor";
|
||||
|
||||
export interface DashboardTypeCount {
|
||||
type: MonitorType;
|
||||
count: number;
|
||||
}
|
||||
@@ -8,6 +8,9 @@ import AuthRegister from "@/Pages/Auth/Register";
|
||||
import AuthForgotPassword from "@/Pages/Auth/Recovery";
|
||||
import AuthSetNewPassword from "@/Pages/Auth/SetNewPassword";
|
||||
|
||||
// Dashboard
|
||||
import Dashboard from "@/Pages/Dashboard";
|
||||
|
||||
// Uptime
|
||||
import Uptime from "@/Pages/Uptime/Monitors";
|
||||
import UptimeDetails from "@/Pages/Uptime/Details";
|
||||
@@ -92,6 +95,10 @@ const Routes = () => {
|
||||
path="/"
|
||||
element={<Navigate to="/uptime" />}
|
||||
/>
|
||||
<Route
|
||||
path="dashboard"
|
||||
element={<Dashboard />}
|
||||
/>
|
||||
<Route
|
||||
path="/uptime"
|
||||
element={<Uptime />}
|
||||
|
||||
@@ -60,6 +60,13 @@ export const monitorTypeLabelKey: Record<SelectableMonitorType, string> &
|
||||
pagespeed: "optionPagespeed",
|
||||
};
|
||||
|
||||
export const getMonitorTypeLabel = (
|
||||
type: MonitorType,
|
||||
t: (key: string) => string
|
||||
): string => {
|
||||
const monitorLabel = monitorTypeLabelKey[type];
|
||||
return monitorLabel ? t(`pages.common.monitors.monitorTypes.${monitorLabel}`) : type;
|
||||
};
|
||||
export const MIN_MONITOR_INTERVAL_MS = 10000;
|
||||
|
||||
export const MonitorIntervalOptions = [
|
||||
|
||||
@@ -226,7 +226,8 @@
|
||||
"alerting": "Alerting",
|
||||
"reporting": "Reporting",
|
||||
"configuration": "Configuration"
|
||||
}
|
||||
},
|
||||
"dashboard": "Dashboard"
|
||||
},
|
||||
"bottomMenu": {
|
||||
"support": "Support",
|
||||
@@ -1908,6 +1909,19 @@
|
||||
"title": "Uptime monitors",
|
||||
"description": "Watch HTTP endpoints, pings, containers, and ports — and get alerted the moment something goes down."
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"monitorStatus": {
|
||||
"ofUp": "of {{total}} up"
|
||||
},
|
||||
"cards": {
|
||||
"monitorStatus": "Monitor status",
|
||||
"monitorsByType": "Monitors by type"
|
||||
},
|
||||
"header": {
|
||||
"title": "Dashboard",
|
||||
"description": "See every monitor type in one place and spot what needs attention."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user