mirror of
https://github.com/prometheus/prometheus.git
synced 2025-01-03 18:07:27 -08:00
d22e721d39
Signed-off-by: Julius Volz <julius.volz@gmail.com>
435 lines
13 KiB
TypeScript
435 lines
13 KiB
TypeScript
import "@mantine/core/styles.css";
|
|
import "@mantine/code-highlight/styles.css";
|
|
import "@mantine/notifications/styles.css";
|
|
import "@mantine/dates/styles.css";
|
|
import classes from "./App.module.css";
|
|
import PrometheusLogo from "./images/prometheus-logo.svg";
|
|
|
|
import {
|
|
AppShell,
|
|
Box,
|
|
Burger,
|
|
Button,
|
|
Group,
|
|
MantineProvider,
|
|
Menu,
|
|
Skeleton,
|
|
Text,
|
|
createTheme,
|
|
rem,
|
|
} from "@mantine/core";
|
|
import { useDisclosure } from "@mantine/hooks";
|
|
import {
|
|
IconBellFilled,
|
|
IconChevronDown,
|
|
IconChevronRight,
|
|
IconCloudDataConnection,
|
|
IconDatabase,
|
|
IconFlag,
|
|
IconHeartRateMonitor,
|
|
IconInfoCircle,
|
|
IconSearch,
|
|
IconServer,
|
|
IconServerCog,
|
|
} from "@tabler/icons-react";
|
|
import {
|
|
BrowserRouter,
|
|
Link,
|
|
NavLink,
|
|
Navigate,
|
|
Route,
|
|
Routes,
|
|
} from "react-router-dom";
|
|
import { IconTable } from "@tabler/icons-react";
|
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
import QueryPage from "./pages/query/QueryPage";
|
|
import AlertsPage from "./pages/AlertsPage";
|
|
import RulesPage from "./pages/RulesPage";
|
|
import TargetsPage from "./pages/targets/TargetsPage";
|
|
import StatusPage from "./pages/StatusPage";
|
|
import TSDBStatusPage from "./pages/TSDBStatusPage";
|
|
import FlagsPage from "./pages/FlagsPage";
|
|
import ConfigPage from "./pages/ConfigPage";
|
|
import AgentPage from "./pages/AgentPage";
|
|
import { Suspense } from "react";
|
|
import ErrorBoundary from "./components/ErrorBoundary";
|
|
import { ThemeSelector } from "./components/ThemeSelector";
|
|
import { Notifications } from "@mantine/notifications";
|
|
import { useAppDispatch } from "./state/hooks";
|
|
import { updateSettings, useSettings } from "./state/settingsSlice";
|
|
import SettingsMenu from "./components/SettingsMenu";
|
|
import ReadinessWrapper from "./components/ReadinessWrapper";
|
|
import { QueryParamProvider } from "use-query-params";
|
|
import { ReactRouter6Adapter } from "use-query-params/adapters/react-router-6";
|
|
import ServiceDiscoveryPage from "./pages/service-discovery/ServiceDiscoveryPage";
|
|
|
|
const queryClient = new QueryClient();
|
|
|
|
const navIconStyle = { width: rem(16), height: rem(16) };
|
|
|
|
const mainNavPages = [
|
|
{
|
|
title: "Query",
|
|
path: "/query",
|
|
icon: <IconSearch style={navIconStyle} />,
|
|
element: <QueryPage />,
|
|
inAgentMode: false,
|
|
},
|
|
{
|
|
title: "Alerts",
|
|
path: "/alerts",
|
|
icon: <IconBellFilled style={navIconStyle} />,
|
|
element: <AlertsPage />,
|
|
inAgentMode: false,
|
|
},
|
|
];
|
|
|
|
const monitoringStatusPages = [
|
|
{
|
|
title: "Target health",
|
|
path: "/targets",
|
|
icon: <IconHeartRateMonitor style={navIconStyle} />,
|
|
element: <TargetsPage />,
|
|
inAgentMode: true,
|
|
},
|
|
{
|
|
title: "Rule health",
|
|
path: "/rules",
|
|
icon: <IconTable style={navIconStyle} />,
|
|
element: <RulesPage />,
|
|
inAgentMode: false,
|
|
},
|
|
{
|
|
title: "Service discovery",
|
|
path: "/service-discovery",
|
|
icon: <IconCloudDataConnection style={navIconStyle} />,
|
|
element: <ServiceDiscoveryPage />,
|
|
inAgentMode: true,
|
|
},
|
|
];
|
|
|
|
const serverStatusPages = [
|
|
{
|
|
title: "Runtime & build information",
|
|
path: "/status",
|
|
icon: <IconInfoCircle style={navIconStyle} />,
|
|
element: <StatusPage />,
|
|
inAgentMode: true,
|
|
},
|
|
{
|
|
title: "TSDB status",
|
|
path: "/tsdb-status",
|
|
icon: <IconDatabase style={navIconStyle} />,
|
|
element: <TSDBStatusPage />,
|
|
inAgentMode: false,
|
|
},
|
|
{
|
|
title: "Command-line flags",
|
|
path: "/flags",
|
|
icon: <IconFlag style={navIconStyle} />,
|
|
element: <FlagsPage />,
|
|
inAgentMode: true,
|
|
},
|
|
{
|
|
title: "Configuration",
|
|
path: "/config",
|
|
icon: <IconServerCog style={navIconStyle} />,
|
|
element: <ConfigPage />,
|
|
inAgentMode: true,
|
|
},
|
|
];
|
|
|
|
const allStatusPages = [...monitoringStatusPages, ...serverStatusPages];
|
|
|
|
const theme = createTheme({
|
|
colors: {
|
|
"codebox-bg": [
|
|
"#f5f5f5",
|
|
"#e7e7e7",
|
|
"#cdcdcd",
|
|
"#b2b2b2",
|
|
"#9a9a9a",
|
|
"#8b8b8b",
|
|
"#848484",
|
|
"#717171",
|
|
"#656565",
|
|
"#575757",
|
|
],
|
|
},
|
|
});
|
|
|
|
// This dynamically/generically determines the pathPrefix by stripping the first known
|
|
// endpoint suffix from the window location path. It works out of the box for both direct
|
|
// hosting and reverse proxy deployments with no additional configurations required.
|
|
const getPathPrefix = (path: string) => {
|
|
if (path.endsWith("/")) {
|
|
path = path.slice(0, -1);
|
|
}
|
|
|
|
const pagePaths = [
|
|
...mainNavPages,
|
|
...allStatusPages,
|
|
{ path: "/agent" },
|
|
].map((p) => p.path);
|
|
|
|
const pagePath = pagePaths.find((p) => path.endsWith(p));
|
|
return path.slice(0, path.length - (pagePath || "").length);
|
|
};
|
|
|
|
const navLinkXPadding = "md";
|
|
|
|
function App() {
|
|
const [opened, { toggle }] = useDisclosure();
|
|
|
|
const pathPrefix = getPathPrefix(window.location.pathname);
|
|
const dispatch = useAppDispatch();
|
|
dispatch(updateSettings({ pathPrefix }));
|
|
|
|
const { agentMode } = useSettings();
|
|
|
|
const navLinks = (
|
|
<>
|
|
{mainNavPages
|
|
.filter((p) => !agentMode || p.inAgentMode)
|
|
.map((p) => (
|
|
<Button
|
|
key={p.path}
|
|
component={NavLink}
|
|
to={p.path}
|
|
className={classes.link}
|
|
leftSection={p.icon}
|
|
px={navLinkXPadding}
|
|
>
|
|
{p.title}
|
|
</Button>
|
|
))}
|
|
|
|
<Menu shadow="md" width={240}>
|
|
<Routes>
|
|
{allStatusPages
|
|
.filter((p) => !agentMode || p.inAgentMode)
|
|
.map((p) => (
|
|
<Route
|
|
key={p.path}
|
|
path={p.path}
|
|
element={
|
|
<Menu.Target>
|
|
<Button
|
|
component={NavLink}
|
|
to={p.path}
|
|
className={classes.link}
|
|
leftSection={p.icon}
|
|
rightSection={<IconChevronDown style={navIconStyle} />}
|
|
px={navLinkXPadding}
|
|
>
|
|
Status <IconChevronRight style={navIconStyle} /> {p.title}
|
|
</Button>
|
|
</Menu.Target>
|
|
}
|
|
/>
|
|
))}
|
|
<Route
|
|
path="*"
|
|
element={
|
|
<Menu.Target>
|
|
<Button
|
|
component={NavLink}
|
|
to="/"
|
|
className={classes.link}
|
|
leftSection={<IconServer style={navIconStyle} />}
|
|
rightSection={<IconChevronDown style={navIconStyle} />}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
}}
|
|
px={navLinkXPadding}
|
|
>
|
|
Status
|
|
</Button>
|
|
</Menu.Target>
|
|
}
|
|
/>
|
|
</Routes>
|
|
|
|
<Menu.Dropdown>
|
|
<Menu.Label>Monitoring status</Menu.Label>
|
|
{monitoringStatusPages
|
|
.filter((p) => !agentMode || p.inAgentMode)
|
|
.map((p) => (
|
|
<Menu.Item
|
|
key={p.path}
|
|
component={NavLink}
|
|
to={p.path}
|
|
leftSection={p.icon}
|
|
>
|
|
{p.title}
|
|
</Menu.Item>
|
|
))}
|
|
|
|
<Menu.Divider />
|
|
<Menu.Label>Server status</Menu.Label>
|
|
{serverStatusPages
|
|
.filter((p) => !agentMode || p.inAgentMode)
|
|
.map((p) => (
|
|
<Menu.Item
|
|
key={p.path}
|
|
component={NavLink}
|
|
to={p.path}
|
|
leftSection={p.icon}
|
|
>
|
|
{p.title}
|
|
</Menu.Item>
|
|
))}
|
|
</Menu.Dropdown>
|
|
</Menu>
|
|
|
|
{/* <Button
|
|
component="a"
|
|
href="https://prometheus.io/docs/prometheus/latest/getting_started/"
|
|
className={classes.link}
|
|
leftSection={<IconHelp style={navIconStyle} />}
|
|
target="_blank"
|
|
px={navLinkXPadding}
|
|
>
|
|
Help
|
|
</Button> */}
|
|
</>
|
|
);
|
|
|
|
return (
|
|
<BrowserRouter basename={pathPrefix}>
|
|
<QueryParamProvider adapter={ReactRouter6Adapter}>
|
|
<MantineProvider defaultColorScheme="auto" theme={theme}>
|
|
<Notifications position="top-right" />
|
|
|
|
<QueryClientProvider client={queryClient}>
|
|
<AppShell
|
|
header={{ height: 56 }}
|
|
navbar={{
|
|
width: 300,
|
|
// TODO: On pages with a long title like "/status", the navbar
|
|
// breaks in an ugly way for narrow windows. Fix this.
|
|
breakpoint: "sm",
|
|
collapsed: { desktop: true, mobile: !opened },
|
|
}}
|
|
padding="md"
|
|
>
|
|
<AppShell.Header bg="rgb(65, 73, 81)" c="#fff">
|
|
<Group h="100%" px="md">
|
|
<Group style={{ flex: 1 }} justify="space-between">
|
|
<Group gap={65}>
|
|
<Link
|
|
to="/"
|
|
style={{ textDecoration: "none", color: "white" }}
|
|
>
|
|
<Group gap={10}>
|
|
<img src={PrometheusLogo} height={30} />
|
|
<Text fz={20}>Prometheus{agentMode && " Agent"}</Text>
|
|
</Group>
|
|
</Link>
|
|
<Group gap={12} visibleFrom="sm">
|
|
{navLinks}
|
|
</Group>
|
|
</Group>
|
|
<Group visibleFrom="xs">
|
|
<ThemeSelector />
|
|
<SettingsMenu />
|
|
</Group>
|
|
</Group>
|
|
<Burger
|
|
opened={opened}
|
|
onClick={toggle}
|
|
hiddenFrom="sm"
|
|
size="sm"
|
|
color="gray.2"
|
|
/>
|
|
</Group>
|
|
</AppShell.Header>
|
|
|
|
<AppShell.Navbar py="md" px={4} bg="rgb(65, 73, 81)" c="#fff">
|
|
{navLinks}
|
|
<Group mt="md" hiddenFrom="xs" justify="center">
|
|
<ThemeSelector />
|
|
<SettingsMenu />
|
|
</Group>
|
|
</AppShell.Navbar>
|
|
|
|
<AppShell.Main>
|
|
<ErrorBoundary key={location.pathname}>
|
|
<Suspense
|
|
fallback={
|
|
<Box mt="lg">
|
|
{Array.from(Array(10), (_, i) => (
|
|
<Skeleton
|
|
key={i}
|
|
height={40}
|
|
mb={15}
|
|
width={1000}
|
|
mx="auto"
|
|
/>
|
|
))}
|
|
</Box>
|
|
}
|
|
>
|
|
<Routes>
|
|
<Route
|
|
path="/"
|
|
element={
|
|
<Navigate
|
|
to={agentMode ? "/agent" : "/query"}
|
|
replace
|
|
/>
|
|
}
|
|
/>
|
|
{agentMode ? (
|
|
<Route
|
|
path="/agent"
|
|
element={
|
|
<ReadinessWrapper>
|
|
<AgentPage />
|
|
</ReadinessWrapper>
|
|
}
|
|
/>
|
|
) : (
|
|
<>
|
|
<Route
|
|
path="/query"
|
|
element={
|
|
<ReadinessWrapper>
|
|
<QueryPage />
|
|
</ReadinessWrapper>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/alerts"
|
|
element={
|
|
<ReadinessWrapper>
|
|
<AlertsPage />
|
|
</ReadinessWrapper>
|
|
}
|
|
/>
|
|
</>
|
|
)}
|
|
{allStatusPages.map((p) => (
|
|
<Route
|
|
key={p.path}
|
|
path={p.path}
|
|
element={
|
|
<ReadinessWrapper>{p.element}</ReadinessWrapper>
|
|
}
|
|
/>
|
|
))}
|
|
</Routes>
|
|
</Suspense>
|
|
</ErrorBoundary>
|
|
</AppShell.Main>
|
|
</AppShell>
|
|
{/* <ReactQueryDevtools initialIsOpen={false} /> */}
|
|
</QueryClientProvider>
|
|
</MantineProvider>
|
|
</QueryParamProvider>
|
|
</BrowserRouter>
|
|
);
|
|
}
|
|
|
|
export default App;
|