mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-26 22:19:40 -08:00
Start working on /alerts page, factor out rule definition display
Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
parent
65cc7b058e
commit
128b6461e9
|
@ -46,6 +46,14 @@ interface RuleGroup {
|
||||||
lastEvaluation: string;
|
lastEvaluation: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AlertingRuleGroup = Omit<RuleGroup, "rules"> & {
|
||||||
|
rules: AlertingRule[];
|
||||||
|
};
|
||||||
|
|
||||||
export interface RulesMap {
|
export interface RulesMap {
|
||||||
groups: RuleGroup[];
|
groups: RuleGroup[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AlertingRulesMap {
|
||||||
|
groups: AlertingRuleGroup[];
|
||||||
|
}
|
||||||
|
|
49
web/ui/mantine-ui/src/badge.module.css
Normal file
49
web/ui/mantine-ui/src/badge.module.css
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
.statsBadge {
|
||||||
|
background-color: light-dark(
|
||||||
|
var(--mantine-color-gray-1),
|
||||||
|
var(--mantine-color-gray-9)
|
||||||
|
);
|
||||||
|
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-gray-5));
|
||||||
|
}
|
||||||
|
|
||||||
|
.labelBadge {
|
||||||
|
background-color: light-dark(
|
||||||
|
var(--mantine-color-gray-1),
|
||||||
|
var(--mantine-color-gray-9)
|
||||||
|
);
|
||||||
|
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-gray-5));
|
||||||
|
}
|
||||||
|
|
||||||
|
.healthOk {
|
||||||
|
background-color: light-dark(
|
||||||
|
var(--mantine-color-green-1),
|
||||||
|
var(--mantine-color-green-9)
|
||||||
|
);
|
||||||
|
color: light-dark(var(--mantine-color-green-9), var(--mantine-color-green-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.healthErr {
|
||||||
|
background-color: light-dark(
|
||||||
|
var(--mantine-color-red-1),
|
||||||
|
darken(var(--mantine-color-red-9), 0.25)
|
||||||
|
);
|
||||||
|
color: light-dark(var(--mantine-color-red-9), var(--mantine-color-red-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.healthWarn {
|
||||||
|
background-color: light-dark(
|
||||||
|
var(--mantine-color-yellow-1),
|
||||||
|
var(--mantine-color-yellow-9)
|
||||||
|
);
|
||||||
|
color: light-dark(
|
||||||
|
var(--mantine-color-yellow-9),
|
||||||
|
var(--mantine-color-yellow-1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.healthUnknown {
|
||||||
|
background-color: light-dark(
|
||||||
|
var(--mantine-color-gray-1),
|
||||||
|
var(--mantine-color-gray-9)
|
||||||
|
);
|
||||||
|
}
|
|
@ -4,42 +4,3 @@
|
||||||
var(--mantine-color-gray-9)
|
var(--mantine-color-gray-9)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.statsBadge {
|
|
||||||
background-color: light-dark(
|
|
||||||
var(--mantine-color-gray-1),
|
|
||||||
var(--mantine-color-gray-9)
|
|
||||||
);
|
|
||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-gray-5));
|
|
||||||
}
|
|
||||||
|
|
||||||
.labelBadge {
|
|
||||||
background-color: light-dark(
|
|
||||||
var(--mantine-color-gray-1),
|
|
||||||
var(--mantine-color-gray-9)
|
|
||||||
);
|
|
||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-gray-5));
|
|
||||||
}
|
|
||||||
|
|
||||||
.healthOk {
|
|
||||||
background-color: light-dark(
|
|
||||||
var(--mantine-color-green-1),
|
|
||||||
var(--mantine-color-green-9)
|
|
||||||
);
|
|
||||||
color: light-dark(var(--mantine-color-green-9), var(--mantine-color-green-1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.healthErr {
|
|
||||||
background-color: light-dark(
|
|
||||||
var(--mantine-color-red-1),
|
|
||||||
var(--mantine-color-red-9)
|
|
||||||
);
|
|
||||||
color: light-dark(var(--mantine-color-red-9), var(--mantine-color-red-1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.healthUnknown {
|
|
||||||
background-color: light-dark(
|
|
||||||
var(--mantine-color-gray-1),
|
|
||||||
var(--mantine-color-gray-9)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
@ -109,10 +109,14 @@ export const humanizeDuration = (milliseconds: number): string => {
|
||||||
return "0s";
|
return "0s";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatRelative = (startStr: string, end: number): string => {
|
export const formatRelative = (
|
||||||
|
startStr: string,
|
||||||
|
end: number,
|
||||||
|
suffix: string = " ago"
|
||||||
|
): string => {
|
||||||
const start = parseTime(startStr);
|
const start = parseTime(startStr);
|
||||||
if (start < 0) {
|
if (start < 0) {
|
||||||
return "Never";
|
return "never";
|
||||||
}
|
}
|
||||||
return humanizeDuration(end - start) + " ago";
|
return humanizeDuration(end - start) + suffix;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,187 @@
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
Group,
|
||||||
|
Table,
|
||||||
|
Text,
|
||||||
|
Accordion,
|
||||||
|
Badge,
|
||||||
|
Tooltip,
|
||||||
|
Box,
|
||||||
|
Switch,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { useSuspenseAPIQuery } from "../api/api";
|
||||||
|
import { AlertingRulesMap } from "../api/response-types/rules";
|
||||||
|
import badgeClasses from "../badge.module.css";
|
||||||
|
import RuleDefinition from "../rule-definition";
|
||||||
|
import { formatRelative, now } from "../lib/time-format";
|
||||||
|
import { Fragment, useState } from "react";
|
||||||
|
|
||||||
export default function Alerts() {
|
export default function Alerts() {
|
||||||
return <>Alerts page</>;
|
const { data } = useSuspenseAPIQuery<AlertingRulesMap>(`/rules?type=alert`);
|
||||||
|
const [showAnnotations, setShowAnnotations] = useState(false);
|
||||||
|
|
||||||
|
const ruleStatsCount = {
|
||||||
|
inactive: 0,
|
||||||
|
pending: 0,
|
||||||
|
firing: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
data.data.groups.forEach((el) =>
|
||||||
|
el.rules.forEach((r) => ruleStatsCount[r.state]++)
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Switch
|
||||||
|
checked={showAnnotations}
|
||||||
|
label="Show annotations"
|
||||||
|
onChange={(event) => setShowAnnotations(event.currentTarget.checked)}
|
||||||
|
mb="md"
|
||||||
|
/>
|
||||||
|
{data.data.groups.map((g, i) => (
|
||||||
|
<Card
|
||||||
|
shadow="xs"
|
||||||
|
withBorder
|
||||||
|
radius="md"
|
||||||
|
p="md"
|
||||||
|
mb="md"
|
||||||
|
key={i} // TODO: Find a stable and definitely unique key.
|
||||||
|
>
|
||||||
|
<Group mb="md" mt="xs" ml="xs" justify="space-between">
|
||||||
|
<Group align="baseline">
|
||||||
|
<Text fz="xl" fw={600} c="var(--mantine-primary-color-filled)">
|
||||||
|
{g.name}
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" c="gray.6">
|
||||||
|
{g.file}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
<Accordion multiple variant="separated">
|
||||||
|
{g.rules.map((r, j) => {
|
||||||
|
const numFiring = r.alerts.filter(
|
||||||
|
(a) => a.state === "firing"
|
||||||
|
).length;
|
||||||
|
const numPending = r.alerts.filter(
|
||||||
|
(a) => a.state === "pending"
|
||||||
|
).length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Accordion.Item key={j} value={j.toString()}>
|
||||||
|
<Accordion.Control>
|
||||||
|
<Group wrap="nowrap" justify="space-between" mr="lg">
|
||||||
|
<Text>{r.name}</Text>
|
||||||
|
<Group gap="xs">
|
||||||
|
{numFiring > 0 && (
|
||||||
|
<Badge className={badgeClasses.healthErr}>
|
||||||
|
firing ({numFiring})
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{numPending > 0 && (
|
||||||
|
<Badge className={badgeClasses.healthWarn}>
|
||||||
|
pending ({numPending})
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{/* {numFiring === 0 && numPending === 0 && (
|
||||||
|
<Badge className={badgeClasses.healthOk}>
|
||||||
|
inactive
|
||||||
|
</Badge>
|
||||||
|
)} */}
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</Accordion.Control>
|
||||||
|
<Accordion.Panel>
|
||||||
|
<RuleDefinition rule={r} />
|
||||||
|
{r.alerts.length > 0 && (
|
||||||
|
<Table mt="lg">
|
||||||
|
<Table.Thead>
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Th>Alert labels</Table.Th>
|
||||||
|
<Table.Th>State</Table.Th>
|
||||||
|
<Table.Th>Active Since</Table.Th>
|
||||||
|
<Table.Th>Value</Table.Th>
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>
|
||||||
|
{r.type === "alerting" &&
|
||||||
|
r.alerts.map((a, k) => (
|
||||||
|
<Fragment key={k}>
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Td>
|
||||||
|
<Group gap="xs">
|
||||||
|
{Object.entries(a.labels).map(
|
||||||
|
([k, v]) => {
|
||||||
|
return (
|
||||||
|
<Badge
|
||||||
|
variant="light"
|
||||||
|
className={
|
||||||
|
badgeClasses.labelBadge
|
||||||
|
}
|
||||||
|
styles={{
|
||||||
|
label: {
|
||||||
|
textTransform: "none",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
key={k}
|
||||||
|
>
|
||||||
|
{/* TODO: Proper quote escaping */}
|
||||||
|
{k}="{v}"
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<Badge
|
||||||
|
className={
|
||||||
|
a.state === "firing"
|
||||||
|
? badgeClasses.healthErr
|
||||||
|
: badgeClasses.healthWarn
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{a.state}
|
||||||
|
</Badge>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<Tooltip label={a.activeAt}>
|
||||||
|
<Box>
|
||||||
|
{formatRelative(a.activeAt, now(), "")}
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>{a.value}</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
{showAnnotations && (
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Td colSpan={4}>
|
||||||
|
<Table mt="md" mb="xl">
|
||||||
|
<Table.Tbody>
|
||||||
|
{Object.entries(a.annotations).map(
|
||||||
|
([k, v]) => (
|
||||||
|
<Table.Tr key={k}>
|
||||||
|
<Table.Th>{k}</Table.Th>
|
||||||
|
<Table.Td>{v}</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
)}
|
||||||
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Accordion>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,72 +1,45 @@
|
||||||
import {
|
import { Alert, Badge, Card, Group, Table, Text, Tooltip } from "@mantine/core";
|
||||||
Alert,
|
|
||||||
Badge,
|
|
||||||
Card,
|
|
||||||
Group,
|
|
||||||
Table,
|
|
||||||
Text,
|
|
||||||
Tooltip,
|
|
||||||
useComputedColorScheme,
|
|
||||||
} from "@mantine/core";
|
|
||||||
// import { useQuery } from "react-query";
|
// import { useQuery } from "react-query";
|
||||||
import {
|
import { formatRelative, humanizeDuration, now } from "../lib/time-format";
|
||||||
formatDuration,
|
|
||||||
formatRelative,
|
|
||||||
humanizeDuration,
|
|
||||||
now,
|
|
||||||
} from "../lib/time-format";
|
|
||||||
import {
|
import {
|
||||||
IconAlertTriangle,
|
IconAlertTriangle,
|
||||||
IconBell,
|
IconBell,
|
||||||
IconClockPause,
|
|
||||||
IconClockPlay,
|
|
||||||
IconDatabaseImport,
|
IconDatabaseImport,
|
||||||
IconHourglass,
|
IconHourglass,
|
||||||
IconRefresh,
|
IconRefresh,
|
||||||
IconRepeat,
|
IconRepeat,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import CodeMirror, { EditorView } from "@uiw/react-codemirror";
|
|
||||||
import { useSuspenseAPIQuery } from "../api/api";
|
import { useSuspenseAPIQuery } from "../api/api";
|
||||||
import { RulesMap } from "../api/response-types/rules";
|
import { RulesMap } from "../api/response-types/rules";
|
||||||
import { syntaxHighlighting } from "@codemirror/language";
|
import badgeClasses from "../badge.module.css";
|
||||||
import {
|
import RuleDefinition from "../rule-definition";
|
||||||
baseTheme,
|
|
||||||
darkPromqlHighlighter,
|
|
||||||
lightTheme,
|
|
||||||
promqlHighlighter,
|
|
||||||
} from "../codemirror/theme";
|
|
||||||
import { PromQLExtension } from "@prometheus-io/codemirror-promql";
|
|
||||||
import classes from "../codebox.module.css";
|
|
||||||
|
|
||||||
const healthBadgeClass = (state: string) => {
|
const healthBadgeClass = (state: string) => {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case "ok":
|
case "ok":
|
||||||
return classes.healthOk;
|
return badgeClasses.healthOk;
|
||||||
case "err":
|
case "err":
|
||||||
return classes.healthErr;
|
return badgeClasses.healthErr;
|
||||||
case "unknown":
|
case "unknown":
|
||||||
return classes.healthUnknown;
|
return badgeClasses.healthUnknown;
|
||||||
default:
|
default:
|
||||||
return "orange";
|
return "orange";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const promqlExtension = new PromQLExtension();
|
|
||||||
|
|
||||||
export default function Rules() {
|
export default function Rules() {
|
||||||
const { data } = useSuspenseAPIQuery<RulesMap>(`/rules`);
|
const { data } = useSuspenseAPIQuery<RulesMap>(`/rules`);
|
||||||
const theme = useComputedColorScheme();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{data.data.groups.map((g) => (
|
{data.data.groups.map((g, i) => (
|
||||||
<Card
|
<Card
|
||||||
shadow="xs"
|
shadow="xs"
|
||||||
withBorder
|
withBorder
|
||||||
radius="md"
|
radius="md"
|
||||||
p="md"
|
p="md"
|
||||||
mb="md"
|
mb="md"
|
||||||
key={g.name + ";" + g.file}
|
key={i} // TODO: Find a stable and definitely unique key.
|
||||||
>
|
>
|
||||||
<Group mb="md" mt="xs" ml="xs" justify="space-between">
|
<Group mb="md" mt="xs" ml="xs" justify="space-between">
|
||||||
<Group align="baseline">
|
<Group align="baseline">
|
||||||
|
@ -81,7 +54,7 @@ export default function Rules() {
|
||||||
<Tooltip label="Last group evaluation" withArrow>
|
<Tooltip label="Last group evaluation" withArrow>
|
||||||
<Badge
|
<Badge
|
||||||
variant="light"
|
variant="light"
|
||||||
className={classes.statsBadge}
|
className={badgeClasses.statsBadge}
|
||||||
styles={{ label: { textTransform: "none" } }}
|
styles={{ label: { textTransform: "none" } }}
|
||||||
leftSection={<IconRefresh size={12} />}
|
leftSection={<IconRefresh size={12} />}
|
||||||
>
|
>
|
||||||
|
@ -91,7 +64,7 @@ export default function Rules() {
|
||||||
<Tooltip label="Duration of last group evaluation" withArrow>
|
<Tooltip label="Duration of last group evaluation" withArrow>
|
||||||
<Badge
|
<Badge
|
||||||
variant="light"
|
variant="light"
|
||||||
className={classes.statsBadge}
|
className={badgeClasses.statsBadge}
|
||||||
styles={{ label: { textTransform: "none" } }}
|
styles={{ label: { textTransform: "none" } }}
|
||||||
leftSection={<IconHourglass size={12} />}
|
leftSection={<IconHourglass size={12} />}
|
||||||
>
|
>
|
||||||
|
@ -101,7 +74,7 @@ export default function Rules() {
|
||||||
<Tooltip label="Group evaluation interval" withArrow>
|
<Tooltip label="Group evaluation interval" withArrow>
|
||||||
<Badge
|
<Badge
|
||||||
variant="light"
|
variant="light"
|
||||||
className={classes.statsBadge}
|
className={badgeClasses.statsBadge}
|
||||||
styles={{ label: { textTransform: "none" } }}
|
styles={{ label: { textTransform: "none" } }}
|
||||||
leftSection={<IconRepeat size={12} />}
|
leftSection={<IconRepeat size={12} />}
|
||||||
>
|
>
|
||||||
|
@ -113,8 +86,9 @@ export default function Rules() {
|
||||||
<Table>
|
<Table>
|
||||||
<Table.Tbody>
|
<Table.Tbody>
|
||||||
{g.rules.map((r) => (
|
{g.rules.map((r) => (
|
||||||
|
// TODO: Find a stable and definitely unique key.
|
||||||
<Table.Tr key={r.name}>
|
<Table.Tr key={r.name}>
|
||||||
<Table.Td p="md" valign="top">
|
<Table.Td p="md" py="xl" valign="top">
|
||||||
<Group gap="xs" wrap="nowrap">
|
<Group gap="xs" wrap="nowrap">
|
||||||
{r.type === "alerting" ? (
|
{r.type === "alerting" ? (
|
||||||
<IconBell size={14} />
|
<IconBell size={14} />
|
||||||
|
@ -134,7 +108,7 @@ export default function Rules() {
|
||||||
<Tooltip label="Last rule evaluation" withArrow>
|
<Tooltip label="Last rule evaluation" withArrow>
|
||||||
<Badge
|
<Badge
|
||||||
variant="light"
|
variant="light"
|
||||||
className={classes.statsBadge}
|
className={badgeClasses.statsBadge}
|
||||||
styles={{ label: { textTransform: "none" } }}
|
styles={{ label: { textTransform: "none" } }}
|
||||||
leftSection={<IconRefresh size={12} />}
|
leftSection={<IconRefresh size={12} />}
|
||||||
>
|
>
|
||||||
|
@ -148,7 +122,7 @@ export default function Rules() {
|
||||||
>
|
>
|
||||||
<Badge
|
<Badge
|
||||||
variant="light"
|
variant="light"
|
||||||
className={classes.statsBadge}
|
className={badgeClasses.statsBadge}
|
||||||
styles={{ label: { textTransform: "none" } }}
|
styles={{ label: { textTransform: "none" } }}
|
||||||
leftSection={<IconHourglass size={12} />}
|
leftSection={<IconHourglass size={12} />}
|
||||||
>
|
>
|
||||||
|
@ -160,31 +134,8 @@ export default function Rules() {
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td p="md">
|
<Table.Td p="md" py="xl">
|
||||||
<Card
|
<RuleDefinition rule={r} />
|
||||||
p="xs"
|
|
||||||
className={classes.codebox}
|
|
||||||
radius="sm"
|
|
||||||
shadow="none"
|
|
||||||
>
|
|
||||||
<CodeMirror
|
|
||||||
basicSetup={false}
|
|
||||||
value={r.query}
|
|
||||||
editable={false}
|
|
||||||
extensions={[
|
|
||||||
baseTheme,
|
|
||||||
lightTheme,
|
|
||||||
syntaxHighlighting(
|
|
||||||
theme === "light"
|
|
||||||
? promqlHighlighter
|
|
||||||
: darkPromqlHighlighter
|
|
||||||
),
|
|
||||||
promqlExtension.asExtension(),
|
|
||||||
EditorView.lineWrapping,
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{r.lastError && (
|
{r.lastError && (
|
||||||
<Alert
|
<Alert
|
||||||
color="red"
|
color="red"
|
||||||
|
@ -195,56 +146,6 @@ export default function Rules() {
|
||||||
<strong>Error:</strong> {r.lastError}
|
<strong>Error:</strong> {r.lastError}
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
{r.type === "alerting" && (
|
|
||||||
<Group mt="md" gap="xs">
|
|
||||||
{r.duration && (
|
|
||||||
<Badge
|
|
||||||
variant="light"
|
|
||||||
styles={{ label: { textTransform: "none" } }}
|
|
||||||
leftSection={<IconClockPause size={12} />}
|
|
||||||
>
|
|
||||||
for: {formatDuration(r.duration * 1000)}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
{r.keepFiringFor && (
|
|
||||||
<Badge
|
|
||||||
variant="light"
|
|
||||||
styles={{ label: { textTransform: "none" } }}
|
|
||||||
leftSection={<IconClockPlay size={12} />}
|
|
||||||
>
|
|
||||||
keep_firing_for: {formatDuration(r.duration * 1000)}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</Group>
|
|
||||||
)}
|
|
||||||
{r.labels && Object.keys(r.labels).length > 0 && (
|
|
||||||
<Group mt="md" gap="xs">
|
|
||||||
{Object.entries(r.labels).map(([k, v]) => (
|
|
||||||
<Badge
|
|
||||||
variant="light"
|
|
||||||
className={classes.labelBadge}
|
|
||||||
styles={{ label: { textTransform: "none" } }}
|
|
||||||
key={k}
|
|
||||||
>
|
|
||||||
{k}: {v}
|
|
||||||
</Badge>
|
|
||||||
))}
|
|
||||||
</Group>
|
|
||||||
)}
|
|
||||||
{/* {Object.keys(r.annotations).length > 0 && (
|
|
||||||
<Group mt="md" gap="xs">
|
|
||||||
{Object.entries(r.annotations).map(([k, v]) => (
|
|
||||||
<Badge
|
|
||||||
variant="light"
|
|
||||||
color="orange.9"
|
|
||||||
styles={{ label: { textTransform: "none" } }}
|
|
||||||
key={k}
|
|
||||||
>
|
|
||||||
{k}: {v}
|
|
||||||
</Badge>
|
|
||||||
))}
|
|
||||||
</Group>
|
|
||||||
)} */}
|
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
))}
|
))}
|
||||||
|
|
105
web/ui/mantine-ui/src/rule-definition.tsx
Normal file
105
web/ui/mantine-ui/src/rule-definition.tsx
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
import {
|
||||||
|
Alert,
|
||||||
|
Badge,
|
||||||
|
Card,
|
||||||
|
Group,
|
||||||
|
useComputedColorScheme,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import {
|
||||||
|
IconAlertTriangle,
|
||||||
|
IconClockPause,
|
||||||
|
IconClockPlay,
|
||||||
|
} from "@tabler/icons-react";
|
||||||
|
import { FC } from "react";
|
||||||
|
import { formatDuration } from "./lib/time-format";
|
||||||
|
import codeboxClasses from "./codebox.module.css";
|
||||||
|
import badgeClasses from "./badge.module.css";
|
||||||
|
import { Rule } from "./api/response-types/rules";
|
||||||
|
import CodeMirror, { EditorView } from "@uiw/react-codemirror";
|
||||||
|
import { syntaxHighlighting } from "@codemirror/language";
|
||||||
|
import {
|
||||||
|
baseTheme,
|
||||||
|
darkPromqlHighlighter,
|
||||||
|
lightTheme,
|
||||||
|
promqlHighlighter,
|
||||||
|
} from "./codemirror/theme";
|
||||||
|
import { PromQLExtension } from "@prometheus-io/codemirror-promql";
|
||||||
|
|
||||||
|
const promqlExtension = new PromQLExtension();
|
||||||
|
|
||||||
|
const RuleDefinition: FC<{ rule: Rule }> = ({ rule }) => {
|
||||||
|
const theme = useComputedColorScheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Card p="xs" className={codeboxClasses.codebox} radius="sm" shadow="none">
|
||||||
|
<CodeMirror
|
||||||
|
basicSetup={false}
|
||||||
|
value={rule.query}
|
||||||
|
editable={false}
|
||||||
|
extensions={[
|
||||||
|
baseTheme,
|
||||||
|
lightTheme,
|
||||||
|
syntaxHighlighting(
|
||||||
|
theme === "light" ? promqlHighlighter : darkPromqlHighlighter
|
||||||
|
),
|
||||||
|
promqlExtension.asExtension(),
|
||||||
|
EditorView.lineWrapping,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
{rule.type === "alerting" && (
|
||||||
|
<Group mt="md" gap="xs">
|
||||||
|
{rule.duration && (
|
||||||
|
<Badge
|
||||||
|
variant="light"
|
||||||
|
styles={{ label: { textTransform: "none" } }}
|
||||||
|
leftSection={<IconClockPause size={12} />}
|
||||||
|
>
|
||||||
|
for: {formatDuration(rule.duration * 1000)}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{rule.keepFiringFor && (
|
||||||
|
<Badge
|
||||||
|
variant="light"
|
||||||
|
styles={{ label: { textTransform: "none" } }}
|
||||||
|
leftSection={<IconClockPlay size={12} />}
|
||||||
|
>
|
||||||
|
keep_firing_for: {formatDuration(rule.duration * 1000)}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
{rule.labels && Object.keys(rule.labels).length > 0 && (
|
||||||
|
<Group mt="md" gap="xs">
|
||||||
|
{Object.entries(rule.labels).map(([k, v]) => (
|
||||||
|
<Badge
|
||||||
|
variant="light"
|
||||||
|
className={badgeClasses.labelBadge}
|
||||||
|
styles={{ label: { textTransform: "none" } }}
|
||||||
|
key={k}
|
||||||
|
>
|
||||||
|
{k}: {v}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
{/* {Object.keys(r.annotations).length > 0 && (
|
||||||
|
<Group mt="md" gap="xs">
|
||||||
|
{Object.entries(r.annotations).map(([k, v]) => (
|
||||||
|
<Badge
|
||||||
|
variant="light"
|
||||||
|
color="orange.9"
|
||||||
|
styles={{ label: { textTransform: "none" } }}
|
||||||
|
key={k}
|
||||||
|
>
|
||||||
|
{k}: {v}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
)} */}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RuleDefinition;
|
Loading…
Reference in a new issue