mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-26 06:04:05 -08:00
Show rules page as collapsed accordion instead of rules table
Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
parent
8165a6e337
commit
719b31f1b5
|
@ -1,7 +1,7 @@
|
||||||
import { Badge, Card, Group, useComputedColorScheme } from "@mantine/core";
|
import { Badge, Box, Card, Group, useComputedColorScheme } from "@mantine/core";
|
||||||
import { IconClockPause, IconClockPlay } from "@tabler/icons-react";
|
import { IconClockPause, IconClockPlay } from "@tabler/icons-react";
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { formatDuration } from "./lib/formatTime";
|
import { formatPrometheusDuration } from "./lib/formatTime";
|
||||||
import codeboxClasses from "./codebox.module.css";
|
import codeboxClasses from "./codebox.module.css";
|
||||||
import badgeClasses from "./Badge.module.css";
|
import badgeClasses from "./Badge.module.css";
|
||||||
import { Rule } from "./api/responseTypes/rules";
|
import { Rule } from "./api/responseTypes/rules";
|
||||||
|
@ -14,6 +14,7 @@ import {
|
||||||
promqlHighlighter,
|
promqlHighlighter,
|
||||||
} from "./codemirror/theme";
|
} from "./codemirror/theme";
|
||||||
import { PromQLExtension } from "@prometheus-io/codemirror-promql";
|
import { PromQLExtension } from "@prometheus-io/codemirror-promql";
|
||||||
|
import { LabelBadges } from "./LabelBadges";
|
||||||
|
|
||||||
const promqlExtension = new PromQLExtension();
|
const promqlExtension = new PromQLExtension();
|
||||||
|
|
||||||
|
@ -46,7 +47,7 @@ const RuleDefinition: FC<{ rule: Rule }> = ({ rule }) => {
|
||||||
styles={{ label: { textTransform: "none" } }}
|
styles={{ label: { textTransform: "none" } }}
|
||||||
leftSection={<IconClockPause size={12} />}
|
leftSection={<IconClockPause size={12} />}
|
||||||
>
|
>
|
||||||
for: {formatDuration(rule.duration * 1000)}
|
for: {formatPrometheusDuration(rule.duration * 1000)}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{rule.keepFiringFor && (
|
{rule.keepFiringFor && (
|
||||||
|
@ -55,24 +56,15 @@ const RuleDefinition: FC<{ rule: Rule }> = ({ rule }) => {
|
||||||
styles={{ label: { textTransform: "none" } }}
|
styles={{ label: { textTransform: "none" } }}
|
||||||
leftSection={<IconClockPlay size={12} />}
|
leftSection={<IconClockPlay size={12} />}
|
||||||
>
|
>
|
||||||
keep_firing_for: {formatDuration(rule.duration * 1000)}
|
keep_firing_for: {formatPrometheusDuration(rule.duration * 1000)}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
)}
|
)}
|
||||||
{rule.labels && Object.keys(rule.labels).length > 0 && (
|
{rule.labels && Object.keys(rule.labels).length > 0 && (
|
||||||
<Group mt="md" gap="xs">
|
<Box mt="md">
|
||||||
{Object.entries(rule.labels).map(([k, v]) => (
|
<LabelBadges labels={rule.labels} />
|
||||||
<Badge
|
</Box>
|
||||||
variant="light"
|
|
||||||
className={badgeClasses.labelBadge}
|
|
||||||
styles={{ label: { textTransform: "none" } }}
|
|
||||||
key={k}
|
|
||||||
>
|
|
||||||
{k}: {v}
|
|
||||||
</Badge>
|
|
||||||
))}
|
|
||||||
</Group>
|
|
||||||
)}
|
)}
|
||||||
{/* {Object.keys(r.annotations).length > 0 && (
|
{/* {Object.keys(r.annotations).length > 0 && (
|
||||||
<Group mt="md" gap="xs">
|
<Group mt="md" gap="xs">
|
||||||
|
|
|
@ -13,7 +13,7 @@ type CommonRuleFields = {
|
||||||
name: string;
|
name: string;
|
||||||
query: string;
|
query: string;
|
||||||
evaluationTime: string;
|
evaluationTime: string;
|
||||||
health: string;
|
health: "ok" | "unknown" | "err";
|
||||||
lastError?: string;
|
lastError?: string;
|
||||||
lastEvaluation: string;
|
lastEvaluation: string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
Accordion,
|
||||||
Alert,
|
Alert,
|
||||||
Badge,
|
Badge,
|
||||||
Card,
|
Card,
|
||||||
|
@ -9,12 +10,17 @@ import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
// import { useQuery } from "react-query";
|
// import { useQuery } from "react-query";
|
||||||
import { formatRelative, humanizeDuration, now } from "../lib/formatTime";
|
import {
|
||||||
|
humanizeDurationRelative,
|
||||||
|
humanizeDuration,
|
||||||
|
now,
|
||||||
|
} from "../lib/formatTime";
|
||||||
import {
|
import {
|
||||||
IconAlertTriangle,
|
IconAlertTriangle,
|
||||||
IconBell,
|
IconBell,
|
||||||
IconDatabaseImport,
|
IconDatabaseImport,
|
||||||
IconHourglass,
|
IconHourglass,
|
||||||
|
IconInfoCircle,
|
||||||
IconRefresh,
|
IconRefresh,
|
||||||
IconRepeat,
|
IconRepeat,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
|
@ -32,7 +38,7 @@ const healthBadgeClass = (state: string) => {
|
||||||
case "unknown":
|
case "unknown":
|
||||||
return badgeClasses.healthUnknown;
|
return badgeClasses.healthUnknown;
|
||||||
default:
|
default:
|
||||||
return "orange";
|
throw new Error("Unknown rule health state");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -41,6 +47,11 @@ export default function RulesPage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack mt="xs">
|
<Stack mt="xs">
|
||||||
|
{data.data.groups.length === 0 && (
|
||||||
|
<Alert title="No rule groups" icon={<IconInfoCircle size={14} />}>
|
||||||
|
No rule groups configured.
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
{data.data.groups.map((g, i) => (
|
{data.data.groups.map((g, i) => (
|
||||||
<Card
|
<Card
|
||||||
shadow="xs"
|
shadow="xs"
|
||||||
|
@ -66,7 +77,7 @@ export default function RulesPage() {
|
||||||
styles={{ label: { textTransform: "none" } }}
|
styles={{ label: { textTransform: "none" } }}
|
||||||
leftSection={<IconRefresh size={12} />}
|
leftSection={<IconRefresh size={12} />}
|
||||||
>
|
>
|
||||||
last run {formatRelative(g.lastEvaluation, now())}
|
last run {humanizeDurationRelative(g.lastEvaluation, now())}
|
||||||
</Badge>
|
</Badge>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label="Duration of last group evaluation" withArrow>
|
<Tooltip label="Duration of last group evaluation" withArrow>
|
||||||
|
@ -91,21 +102,34 @@ export default function RulesPage() {
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
<Table>
|
{g.rules.length === 0 && (
|
||||||
<Table.Tbody>
|
<Alert title="No rules" icon={<IconInfoCircle size={14} />}>
|
||||||
{g.rules.map((r) => (
|
No rules in rule group.
|
||||||
// TODO: Find a stable and definitely unique key.
|
</Alert>
|
||||||
<Table.Tr key={r.name}>
|
)}
|
||||||
<Table.Td p="md" py="xl" valign="top">
|
<Accordion multiple variant="separated">
|
||||||
|
{g.rules.map((r, j) => (
|
||||||
|
<Accordion.Item
|
||||||
|
key={j}
|
||||||
|
value={j.toString()}
|
||||||
|
style={{
|
||||||
|
borderLeft:
|
||||||
|
r.health === "err"
|
||||||
|
? "5px solid var(--mantine-color-red-4)"
|
||||||
|
: r.health === "unknown"
|
||||||
|
? "5px solid var(--mantine-color-gray-5)"
|
||||||
|
: "5px solid var(--mantine-color-green-4)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Accordion.Control>
|
||||||
|
<Group wrap="nowrap" justify="space-between" mr="lg">
|
||||||
<Group gap="xs" wrap="nowrap">
|
<Group gap="xs" wrap="nowrap">
|
||||||
{r.type === "alerting" ? (
|
{r.type === "alerting" ? (
|
||||||
<IconBell size={14} />
|
<IconBell size={15} />
|
||||||
) : (
|
) : (
|
||||||
<IconDatabaseImport size={14} />
|
<IconDatabaseImport size={15} />
|
||||||
)}
|
)}
|
||||||
<Text fz="sm" fw={600}>
|
<Text>{r.name}</Text>
|
||||||
{r.name}
|
|
||||||
</Text>
|
|
||||||
</Group>
|
</Group>
|
||||||
<Group mt="md" gap="xs">
|
<Group mt="md" gap="xs">
|
||||||
<Badge className={healthBadgeClass(r.health)}>
|
<Badge className={healthBadgeClass(r.health)}>
|
||||||
|
@ -120,7 +144,7 @@ export default function RulesPage() {
|
||||||
styles={{ label: { textTransform: "none" } }}
|
styles={{ label: { textTransform: "none" } }}
|
||||||
leftSection={<IconRefresh size={12} />}
|
leftSection={<IconRefresh size={12} />}
|
||||||
>
|
>
|
||||||
{formatRelative(r.lastEvaluation, now())}
|
{humanizeDurationRelative(r.lastEvaluation, now())}
|
||||||
</Badge>
|
</Badge>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
|
@ -141,24 +165,24 @@ export default function RulesPage() {
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
</Table.Td>
|
</Group>
|
||||||
<Table.Td p="md" py="xl">
|
</Accordion.Control>
|
||||||
<RuleDefinition rule={r} />
|
<Accordion.Panel>
|
||||||
{r.lastError && (
|
<RuleDefinition rule={r} />
|
||||||
<Alert
|
{r.lastError && (
|
||||||
color="red"
|
<Alert
|
||||||
mt="sm"
|
color="red"
|
||||||
title="Rule failed to evaluate"
|
mt="sm"
|
||||||
icon={<IconAlertTriangle size={14} />}
|
title="Rule failed to evaluate"
|
||||||
>
|
icon={<IconAlertTriangle size={14} />}
|
||||||
<strong>Error:</strong> {r.lastError}
|
>
|
||||||
</Alert>
|
<strong>Error:</strong> {r.lastError}
|
||||||
)}
|
</Alert>
|
||||||
</Table.Td>
|
)}
|
||||||
</Table.Tr>
|
</Accordion.Panel>
|
||||||
))}
|
</Accordion.Item>
|
||||||
</Table.Tbody>
|
))}
|
||||||
</Table>
|
</Accordion>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
Loading…
Reference in a new issue