UI: Speed up /alerts for many firing alerts via virtual scrolling (#17486)

We already have infinite scrolling for all the rules within a group, but this
adds a second (nested) level of infinite scrolling for the firing alerts within
a rule.

This should further improve the rendering speed for the /alerts page for
Prometheus setups with many firing alerts and when you expand the individual
rule panels that contain those firing alerts.

Fixes https://github.com/prometheus/prometheus/issues/17254

Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
Julius Volz 2025-11-06 15:39:42 +01:00 committed by GitHub
parent 2607d9259d
commit 12c9b0b9d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -13,6 +13,7 @@ import {
Anchor,
Pagination,
rem,
Divider,
} from "@mantine/core";
import { useSuspenseAPIQuery } from "../api/api";
import { AlertingRule, AlertingRulesResult } from "../api/responseTypes/rules";
@ -325,79 +326,88 @@ export default function AlertsPage() {
<Accordion.Panel>
<RuleDefinition rule={r.rule} />
{r.rule.alerts.length > 0 && (
<Table mt="lg">
<Table.Thead>
<Table.Tr style={{ whiteSpace: "nowrap" }}>
<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.rule.type === "alerting" &&
r.rule.alerts.map((a, k) => (
<Fragment key={k}>
<Table.Tr>
<Table.Td>
<LabelBadges labels={a.labels} />
</Table.Td>
<Table.Td>
<Badge
className={
a.state === "firing"
? badgeClasses.healthErr
: badgeClasses.healthWarn
}
>
{a.state}
</Badge>
</Table.Td>
<Table.Td
style={{ whiteSpace: "nowrap" }}
>
<Tooltip label={a.activeAt}>
<Box>
{humanizeDurationRelative(
a.activeAt,
now(),
""
)}
</Box>
</Tooltip>
</Table.Td>
<Table.Td
style={{ whiteSpace: "nowrap" }}
>
{isNaN(Number(a.value))
? a.value
: Number(a.value)}
</Table.Td>
<>
<Divider my="md" />
<CustomInfiniteScroll
allItems={r.rule.alerts}
child={({ items }) => (
<Table mt="lg">
<Table.Thead>
<Table.Tr
style={{ whiteSpace: "nowrap" }}
>
<Table.Th>Alert labels</Table.Th>
<Table.Th>State</Table.Th>
<Table.Th>Active Since</Table.Th>
<Table.Th>Value</Table.Th>
</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 c="light-dark(var(--mantine-color-gray-7), var(--mantine-color-gray-4))">
{k}
</Table.Th>
<Table.Td>{v}</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</Table.Td>
</Table.Tr>
)}
</Fragment>
))}
</Table.Tbody>
</Table>
</Table.Thead>
<Table.Tbody>
{items.map((a, k) => (
<Fragment key={k}>
<Table.Tr>
<Table.Td>
<LabelBadges labels={a.labels} />
</Table.Td>
<Table.Td>
<Badge
className={
a.state === "firing"
? badgeClasses.healthErr
: badgeClasses.healthWarn
}
>
{a.state}
</Badge>
</Table.Td>
<Table.Td
style={{ whiteSpace: "nowrap" }}
>
<Tooltip label={a.activeAt}>
<Box>
{humanizeDurationRelative(
a.activeAt,
now(),
""
)}
</Box>
</Tooltip>
</Table.Td>
<Table.Td
style={{ whiteSpace: "nowrap" }}
>
{isNaN(Number(a.value))
? a.value
: Number(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 c="light-dark(var(--mantine-color-gray-7), var(--mantine-color-gray-4))">
{k}
</Table.Th>
<Table.Td>{v}</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</Table.Td>
</Table.Tr>
)}
</Fragment>
))}
</Table.Tbody>
</Table>
)}
></CustomInfiniteScroll>
</>
)}
</Accordion.Panel>
</Accordion.Item>