diff --git a/.github/easypanel-deploy-screenshot.png b/.github/easypanel-deploy-screenshot.png index 271273289..20f76e9e5 100644 Binary files a/.github/easypanel-deploy-screenshot.png and b/.github/easypanel-deploy-screenshot.png differ diff --git a/client/src/Components/sidebar/Menu.tsx b/client/src/Components/sidebar/Menu.tsx index be42fa6e5..300ce0346 100644 --- a/client/src/Components/sidebar/Menu.tsx +++ b/client/src/Components/sidebar/Menu.tsx @@ -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: , + // }, { name: t("components.sidebar.menu.uptime"), path: "uptime", diff --git a/client/src/Pages/Dashboard/components/DashboardCard.tsx b/client/src/Pages/Dashboard/components/DashboardCard.tsx new file mode 100644 index 000000000..b075ca0f3 --- /dev/null +++ b/client/src/Pages/Dashboard/components/DashboardCard.tsx @@ -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 ( + + + + {title} + + {topRight && ( + + {topRight} + + )} + + {children} + + ); +}; diff --git a/client/src/Pages/Dashboard/components/ProgressBar.tsx b/client/src/Pages/Dashboard/components/ProgressBar.tsx new file mode 100644 index 000000000..6e0f86837 --- /dev/null +++ b/client/src/Pages/Dashboard/components/ProgressBar.tsx @@ -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 ( + + + + ); +}; diff --git a/client/src/Pages/Dashboard/components/cards/BarRow.tsx b/client/src/Pages/Dashboard/components/cards/BarRow.tsx new file mode 100644 index 000000000..af87105e4 --- /dev/null +++ b/client/src/Pages/Dashboard/components/cards/BarRow.tsx @@ -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 ( + + + + {label} + + + {value} + + + + + ); +}; diff --git a/client/src/Pages/Dashboard/components/cards/MonitorStatusCard.tsx b/client/src/Pages/Dashboard/components/cards/MonitorStatusCard.tsx new file mode 100644 index 000000000..10016e8f2 --- /dev/null +++ b/client/src/Pages/Dashboard/components/cards/MonitorStatusCard.tsx @@ -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 ( + + + + + {up} + + + {t("pages.dashboard.monitorStatus.ofUp", { total })} + + + + + + ); +}; diff --git a/client/src/Pages/Dashboard/components/cards/MonitorsByTypeCard.tsx b/client/src/Pages/Dashboard/components/cards/MonitorsByTypeCard.tsx new file mode 100644 index 000000000..2922f708f --- /dev/null +++ b/client/src/Pages/Dashboard/components/cards/MonitorsByTypeCard.tsx @@ -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 ( + + + {monitorsByType.map(({ type, count }) => ( + + ))} + + + ); +}; diff --git a/client/src/Pages/Dashboard/index.tsx b/client/src/Pages/Dashboard/index.tsx new file mode 100644 index 000000000..9d48fb5a4 --- /dev/null +++ b/client/src/Pages/Dashboard/index.tsx @@ -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 ( + + + + + + + ); +}; + +export default Dashboard; diff --git a/client/src/Pages/Dashboard/types.ts b/client/src/Pages/Dashboard/types.ts new file mode 100644 index 000000000..1b9b75d60 --- /dev/null +++ b/client/src/Pages/Dashboard/types.ts @@ -0,0 +1,6 @@ +import type { MonitorType } from "@/Types/Monitor"; + +export interface DashboardTypeCount { + type: MonitorType; + count: number; +} diff --git a/client/src/Routes/index.tsx b/client/src/Routes/index.tsx index 37e49d1ee..937fd0f93 100644 --- a/client/src/Routes/index.tsx +++ b/client/src/Routes/index.tsx @@ -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={} /> + } + /> } diff --git a/client/src/Types/Monitor.ts b/client/src/Types/Monitor.ts index 892e2b549..664077ea0 100644 --- a/client/src/Types/Monitor.ts +++ b/client/src/Types/Monitor.ts @@ -60,6 +60,13 @@ export const monitorTypeLabelKey: Record & 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 = [ diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 078722f53..e9ec4110e 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -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." + } } } }