Codebase Audit Report
Exhaustive analysis of the Prometheus Mantine UI codebase β every component, token, pattern, and inconsistency.
Audited path: prometheus-source/web/ui/mantine-ui/src/ Β· Package version: 0.310.0 Β· Audit date: 2026-03-30
1. Tech Stack & Dependencies
Core Framework
- React
^19.2.4with TypeScript - Vite
^6.4.1(build tool) - PostCSS with
postcss-preset-mantine ^1.18.0andpostcss-simple-vars ^7.0.1
UI Layer
- @mantine/core
^8.3.18β primary component library - @mantine/hooks
^8.3.18 - @mantine/notifications
^8.3.18 - @mantine/dates
^8.3.18 - @mantine/code-highlight
^8.3.18 - @tabler/icons-react
^3.40.0β icon set
State Management
- @reduxjs/toolkit
^2.11.2 - react-redux
^9.2.0 - Redux slices:
settings,queryPage,targetsPage,serviceDiscoveryPage - Settings persisted to
localStoragevia custom middleware
Data Fetching
- @tanstack/react-query
^5.95.2 - Custom
useAPIQueryanduseSuspenseAPIQuerywrappers - @microsoft/fetch-event-source
^2.0.1β for SSE notifications
Routing & URL State
- react-router-dom
^7.13.2 - use-query-params
^2.2.2(URL params for filters, pagination, etc.)
Code Editor
- @uiw/react-codemirror
^4.25.9 - @codemirror/autocomplete, @codemirror/language, @codemirror/lint, @codemirror/view, @codemirror/state
- @prometheus-io/codemirror-promql
0.310.0β PromQL language support - @lezer/highlight
^1.2.3
Charting
- uplot
^1.6.32β time series chart library - uplot-react
^1.2.4
Search/Filtering
- @nexucis/kvsearch
^0.9.1β fuzzy key-value search for targets, alerts, rules - @nexucis/fuzzy
^0.5.1
Utilities
- dayjs
^1.11.20β date formatting - lodash
^4.17.23 - sanitize-html
^2.17.2 - clsx
^2.1.1 - highlight.js
^11.11.1β YAML syntax highlighting in ConfigPage
2. Design Tokens Currently in Use
2.1 Color Values
Navigation Header β Hardcoded RGB
rgb(65, 73, 81) β AppShell.Header bg and AppShell.Navbar bg (App.tsx)
#fff β Header text color (c="#fff")
white β Logo link text color (style={{ color: "white" }})
Custom Mantine Color Palette (App.tsx, createTheme)
"codebox-bg": [
"#f5f5f5", "#e7e7e7", "#cdcdcd", "#b2b2b2", "#9a9a9a",
"#8b8b8b", "#848484", "#717171", "#656565", "#575757"
]
This 10-step gray scale is registered but only referenced for codebox backgrounds.
Badge Health Colors (Badge.module.css) β light-dark() aware
| Class | Light bg | Light text | Dark bg | Dark text |
|---|---|---|---|---|
.statsBadge | gray-1 | gray-7 | gray-8 | gray-5 |
.labelBadge | gray-1 | gray-7 | gray-8 | gray-5 |
.healthOk | green-1 | green-9 | green-9 | green-1 |
.healthErr | red-1 | red-9 | darken(red-9, 0.25) | red-1 |
.healthWarn | yellow-1 | yellow-9 | yellow-9 | yellow-1 |
.healthInfo | blue-1 | blue-9 | blue-9 | blue-1 |
.healthUnknown | gray-2 | gray-7 | gray-7 | gray-4 |
Panel Health Border Colors (Panel.module.css) β light-dark() aware
| Class | Light | Dark |
|---|---|---|
.panelHealthOk | green-3 | green-8 |
.panelHealthErr | red-3 | red-9 |
.panelHealthWarn | orange-3 | yellow-9 |
.panelHealthUnknown | gray-3 | gray-6 |
All borders are 5px solid, applied with !important.
RulesPage β Hardcoded Inline Border Colors
// RulesPage.tsx (hardcoded, not using Panel.module.css)
err: "5px solid var(--mantine-color-red-4)"
unknown: "5px solid var(--mantine-color-gray-5)"
ok: "5px solid var(--mantine-color-green-4)"
These use CSS variables but are inline styles β inconsistent with the Panel.module.css approach used by AlertsPage.
CodeMirror Editor Theme Colors (codemirror/theme.ts)
Base theme (mode-independent):
.cm-completionDetail: #999
.cm-selectionMatch background: #e6f3ff
.cm-diagnostic-error border: #e65013
.cm-completionIcon-text: #ee9d28
Light theme:
.cm-tooltip bg: #f8f8f8
.cm-tooltip border: rgba(52, 79, 113, 0.2)
autocomplete hover: #ddd
selected item: #d6ebff
completionInfo bg: #d6ebff
selection bg: #add6ff
matchingBracket: color #000, bg #dedede
matched text: #0066bf
completionIcon: #007acc
completionIcon-constant: #007acc
completionIcon-function/method: #652d90
completionIcon-keyword: #616161
Dark theme:
caret: #fff
completionInfo bg: #333338
selection bg: #767676
matchingBracket bg: #616161
matched text: #7dd3fc
completionIcon/constant: #7dd3fc
completionIcon-function/method: #d8b4fe
completionIcon-keyword: #cbd5e1
PromQL Syntax Highlighting Colors (codemirror/theme.ts)
Light mode (promqlHighlighter):
number: #09885a
string: #a31515
keyword: #008080
function/var: #008080
labelName: #800000
modifier: #008080
invalid: red
comment: #888 (italic)
Dark mode (darkPromqlHighlighter):
number: #22c55e
string: #fca5a5
keyword: #14bfad
function/var: #14bfad
labelName: #ff8585
modifier: #14bfad
invalid: #ff3d3d
comment: #9ca3af (italic)
PromQL CSS Classes (promql.css)
.promql-code font-family: "DejaVu Sans Mono", monospace
.promql-keyword: light-dark(#008080, #14bfad)
.promql-label-name: light-dark(#800000, #ff8585)
.promql-string: light-dark(#a31515, #fca5a5)
.promql-ellipsis: light-dark(rgb(170,170,170), rgb(170,170,170))
.promql-duration: light-dark(#09885a, #22c55e)
.promql-number: light-dark(#09885a, #22c55e)
Chart Series Color Pool (colorPool.ts)
- Size: 942 entries (hex, 6-digit, no alpha)
- Palette strategy: Algorithmically generated grid of RGB values using multiples of
0x20(32) in each channel β covering the full visible spectrum at mediumβhigh saturation. - First 8 entries:
#008000,#008080,#800000,#800080,#808000,#808080,#0000c0,#008040 - Dark mode adaptation:
lighten(color, 0.4)via Mantine'slighten()utility (colorPool.ts,getSeriesColor) - No warm palette start β starts with green/teal/maroon, no orange/red gradient entry early.
uPlot Chart CSS Colors (uplot.css)
.u-under background (dark): #1f1f1f
.u-over box-shadow: 0px 0px 0px 0.5px #ccc
.u-select background: rgba(255, 200, 150, 0.2)
cursor lines: light-dark(#607d8b, #90adbc) β dashed border
.u-tooltip bg: light-dark(rgba(255,255,255,0.95), rgba(25,25,25,0.95))
.u-tooltip color: light-dark(gray-9, gray-5)
.u-tooltip border: 2px solid light-dark(gray-6, gray-6)
.u-tooltip border-radius: 4px
SeriesName Hover (SeriesName.module.css)
.labelPair:hover background: #add6ffa0
.labelPair:hover color: #495057
.labelPair:hover border-radius: 3px
TreeNode State Colors (TreeNode.module.css β light-dark() aware)
.nodeText bg: light-dark(gray-1, dark-5)
.nodeTextSelected bg: light-dark(gray-4, gray-7)
.nodeTextSelected border: light-dark(gray-5, dark-2)
.nodeText:hover bg: light-dark(gray-2, dark-4)
.nodeTextError bg: light-dark(red-1, darken(red-5, 70%))
.errorText color: light-dark(red-9, red-3)
Highlight.js Theme (highlightjs.css)
Light: text: gray-7, bg: gray-0, comment: gray-6, keyword: violet-8, tag: red-9, literal: blue-6, string: blue-9, variable: lime-9, class: orange-9
Dark: text: dark-1, bg: dark-8, comment: dark-3, keyword: violet-3, tag: yellow-4, literal: blue-4, string: green-6, variable: blue-2, class: orange-5
App.module.css Nav Link
.link color: gray-0
.link:hover bg: gray-6
.link[aria-current="page"] bg: blue-filled, color: white
.link border-radius: radius-sm
.link padding: rem(8px) rem(12px)
2.2 Font Families
| Context | Font |
|---|---|
| App body (Mantine default) | System UI stack (-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, ...) |
| CodeMirror scroller / autocomplete | "DejaVu Sans Mono", monospace |
| CodeMirror placeholder | -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, ... |
| CodeMirror completionInfo tooltip | 'Open Sans', 'Lucida Sans Unicode', 'Lucida Grande', sans-serif |
| PromQL code display | "DejaVu Sans Mono", monospace (via .promql-code) |
| Codicon icon font | "codicon" from ./fonts/codicon.ttf |
2.3 Font Sizes
| Token | Value | Usage |
|---|---|---|
fz="xl" | Mantine xl | Group name headings (AlertsPage, RulesPage), InfoPageCard titles |
fz="sm" | Mantine sm | File path labels, Anchor links |
fz="xs" | Mantine xs | DataTable, graph legend (--mantine-font-size-xs), notification timestamps |
fz={20} | 20px (unitless) | Prometheus logo text (App.tsx, hardcoded number) |
fz="1em" | relative | Alert action anchors |
size="1em" | relative | DataTable timestamp Text |
0.8em | relative | uPlot tooltip, legend |
0.9em | relative | uPlot labels |
12px | absolute | uPlot .u-label |
0.9rem | β | RangeInput, TimeInput, QueryPanel icon sizes |
font-size: 16px | absolute | CodeMirror .cm-completionIcon |
0.6875rem * var(--mantine-scale) | token | Manual labelBadge font size |
2.4 Font Weights
| Weight | Usage |
|---|---|
| 400 | Accordion label default |
| 500 | Nav link (.link), Table icon header text, notifications text |
| 600 | InfoPageCard title (fw={600}), group name (fw={600}), label badge (font-weight: 700) |
| 700 | Notifications header text (fw={700}), .hljs-strong, histogram table headers |
| bold | uPlot legend .series-label, uPlot tooltip headers |
2.5 Spacing Values
All spacing uses Mantine's token system (xs, sm, md, lg, xl) except where noted.
Hardcoded numeric values found:
gap={40}β logo-to-nav gap in header (App.tsx)gap={12}β nav links gap in header (App.tsx)gap={10}β logo group gap (App.tsx)height={40},mb={15},width={1000}β Skeleton fallback dimensions (App.tsx, TargetsPage.tsx)height={30},mb={15}β QueryPanel/Graph Skeleton sizesp={10}β histogram chart Group padding (DataTable.tsx)gap="1rem"β histogram detail Group (DataTable.tsx)gap={5}β RangeInput/TimeInput Group gappx={10},py={4}β TreeNode inner node boxmaw={500}β Alert max-width in ErrorBoundary, ReadinessWrappermaw={250}β NotificationsIcon card max-widthmaw={1000}β InfoPageStack (mx="auto")h={570}β LoadingOverlay height on graph (Graph.tsx, hardcoded)size={rem(32)}β Documentation ActionIcon size (App.tsx)size={32}β Most other ActionIcon sizestop={7},right={7}β RuleDefinition query button positionnodeIndent = 20β TreeNode indent per level
Mantine spacing tokens used: p="md", p="xs", px="md", padding="md", py="md", mt="xs", mt="lg", mt="xl", mb="sm", mb="md", mb="xl", gap="xs", gap="sm", gap="lg", gap="xl", ml="xs", ml="md"
2.6 Border Radii
| Context | Value |
|---|---|
| Nav link | var(--mantine-radius-sm) |
| AppShell content | var(--mantine-radius-default) |
| Accordion root | var(--mantine-radius-default) |
| Badge label | calc(62.5rem * var(--mantine-scale)) β pill shape |
| uPlot tooltip | 4px (hardcoded) |
| TreeNode box | 4 (unitless, hardcoded β likely pixels) |
| SeriesName hover | 3px (hardcoded) |
| LoadingOverlay | "sm" (via overlayProps) |
| FlagsPage sort icon | rem(21px) |
2.7 Shadows / Elevations
| Value | Usage |
|---|---|
shadow="xs" | InfoPageCard, AlertsPage card, RulesPage card |
shadow="md" | Status menu, settings popover, notifications popover |
shadow="none" | RuleDefinition code card |
2.8 Transition / Animation Values
| Value | Usage |
|---|---|
opacity 0.1s ease-in-out | RuleDefinition query button hover reveal |
transition: transform var(--accordion-transition-duration, 200ms) ease | Accordion chevron rotation |
background-color 150ms ease | Accordion item contained/separated variants |
2.9 Breakpoints
Mantine defaults are used exclusively (no custom breakpoints defined):
hiddenFrom="sm"β hides logo text on small+visibleFrom="md"β shows logo text on medium+visibleFrom="xs"β shows action icons on xs+hiddenFrom="sm"β hides burger on sm+breakpoint: "sm"β AppShell navbar collapse breakpointnavbar.width: 300β mobile navbar width
No custom breakpoints are defined in createTheme().
3. Component Inventory
3.1 Custom Components
| Component | File | Props | Description |
|---|---|---|---|
InfoPageCard | components/InfoPageCard.tsx | children, title?, icon? | Wrapper Card with icon+title header for status pages |
InfoPageStack | components/InfoPageStack.tsx | children | Centered max-1000px Stack for info pages |
LabelBadges | components/LabelBadges.tsx | labels, wrapper?, style? | Performance-optimized label pill display (single DOM span, not Mantine Badge) |
EndpointLink | components/EndpointLink.tsx | endpoint, globalUrl | Parses and renders scrape endpoint URL with query params as badges |
ErrorBoundary | components/ErrorBoundary.tsx | children, title? | Class-based error boundary, resets on route change |
SettingsMenu | components/SettingsMenu.tsx | β | Popover with all user-configurable settings |
ThemeSelector | components/ThemeSelector.tsx | β | ActionIcon cycling between light/dark/auto color schemes |
StateMultiSelect | components/StateMultiSelect.tsx | options, optionClass, optionCount?, placeholder, values, onChange | Custom multi-select using Mantine Combobox primitives with colored Pill display |
StatePill | components/StateMultiSelect.tsx | value, onRemove? | Uppercase bold Pill for state display |
NotificationsIcon | components/NotificationsIcon.tsx | β | Indicator+ActionIcon+Popover for live notifications |
NotificationsProvider | components/NotificationsProvider.tsx | children | SSE-based notification context provider |
ReadinessWrapper | components/ReadinessWrapper.tsx | children | Guards pages behind Prometheus readiness check; shows WAL replay progress |
RuleDefinition | components/RuleDefinition.tsx | rule: Rule | CodeMirror read-only PromQL display + badges for duration/labels/annotations |
Accordion | components/Accordion/Accordion.tsx | Full Mantine-compatible Accordion API | Fork of Mantine Accordion with overflow-wrap: break-word on panel |
SeriesName | pages/query/SeriesName.tsx | labels, format | Renders metric label set; formatted mode adds clickable label pairs that copy matchers |
TreeNode | pages/query/TreeNode.tsx | node, selectedNode, setSelectedNode, parentEl?, reportNodeState?, reverse, childIdx | Recursive PromQL AST tree node with live query execution and connector lines |
ExpressionInput | pages/query/ExpressionInput.tsx | initialExpr, metricNames, executeQuery, treeShown, setShowTree, duplicatePanel, removePanel | CodeMirror-based PromQL input with full extensions, metrics explorer modal |
Graph | pages/query/Graph.tsx | expr, node, endTime, range, resolution, showExemplars, displayMode, yAxisMin, retriggerIdx, onSelectRange | Range query executor + LoadingOverlay + UPlotChart wrapper |
UPlotChart | pages/query/UPlotChart.tsx | data, range, width, showExemplars, displayMode, yAxisMin, onSelectRange | uPlot chart wrapper translating Prometheus data to uPlot format |
DataTable | pages/query/DataTable.tsx | data, limitResults, setLimitResults | Table display for instant query results |
RangeInput | pages/query/RangeInput.tsx | range, onChangeRange | TextInput with +/- ActionIcons and stepped range values |
TimeInput | pages/query/TimeInput.tsx | time, range, description, onChangeTime | DateTimePicker with prev/next ActionIcons |
ResolutionInput | pages/query/ResolutionInput.tsx | resolution, range, onChangeResolution | Select for auto/fixed/custom resolution + conditional TextInput |
TargetLabels | pages/targets/TargetLabels.tsx | labels, discoveredLabels | Collapsible label display with ActionIcon toggle for discovered labels |
AlertsPage | pages/AlertsPage.tsx | β | Paged, filtered alert rules grouped by rule group |
RulesPage | pages/RulesPage.tsx | β | Paged, filtered recording+alerting rules |
TargetsPage | pages/targets/TargetsPage.tsx | β | Scrape pool selector + filter bar + ScrapePoolList |
FlagsPage | pages/FlagsPage.tsx | β | Sortable flags table with search |
StatusPage | pages/StatusPage.tsx | β | Build + runtime info tables |
ConfigPage | pages/ConfigPage.tsx | β | YAML config via CodeHighlight |
TSDBStatusPage | pages/TSDBStatusPage.tsx | β | TSDB head stats tables |
AgentPage | pages/AgentPage.tsx | β | Static informational page for agent mode |
3.2 Mantine Components Used β Full Inventory with Frequency
| Mantine Component | Files Used In | Notes |
|---|---|---|
ActionIcon | App.tsx, ThemeSelector, NotificationsIcon, SettingsMenu, RuleDefinition, ExpressionInput, QueryPanel, RangeInput, TimeInput, TargetsPage, TargetLabels, SD pages | Most common interactive element |
Alert | ErrorBoundary, ReadinessWrapper, QueryPage, Graph, DataTable, AlertsPage, RulesPage, ScrapePoolsList, SD lists, QueryPanel | Used for all error, warning, info states |
Anchor | DataTable, EndpointLink, AlertsPage, RulesPage, ScrapePoolsList, SD pages | Links within content |
AppShell + subcomponents | App.tsx | Core layout |
Badge | EndpointLink, RuleDefinition, AlertsPage, RulesPage, ScrapePoolsList | Health and state indicators |
Box | App.tsx, NotificationsIcon, TreeNode, Graph, RuleDefinition, QueryPanel, DataTable | Generic container/layout |
Burger | App.tsx | Mobile menu toggle |
Button | App.tsx (nav links), QueryPage, ExpressionInput | CTA and nav |
Card | NotificationsIcon, AlertsPage, RulesPage, RuleDefinition | Content containers |
Center | FlagsPage, QueryPanel | Centering wrapper |
Checkbox | SettingsMenu, QueryPanel | Settings toggles |
Code | TreeNode | Inline code display |
CodeHighlight | ConfigPage | YAML display |
Collapse | TargetLabels | Expandable discovered labels |
Combobox (+ subcomponents) | StateMultiSelect | Custom multi-select |
DateTimePicker | TimeInput | Graph end time picker |
Divider | AlertsPage | Section separator |
Fieldset | SettingsMenu | Settings grouping |
Group | Nearly all files | Most common layout primitive |
Indicator | NotificationsIcon | Notification count badge |
InputBase | ExpressionInput | CodeMirror host element |
List / List.Item | TreeNode | Label example lists in tooltips |
Loader | ExpressionInput, TreeNode | Inline loading indicators |
LoadingOverlay | Graph.tsx | Graph refetch overlay |
Menu + subcomponents | App.tsx (status nav), ExpressionInput | Dropdowns |
Modal | ExpressionInput (metrics explorer), SD lists (relabeling) | Full-screen modals |
Notifications | App.tsx (provider), ExpressionInput (show), SeriesName (show) | Toast notifications |
NumberInput | SettingsMenu | Pagination settings |
Pagination | AlertsPage, RulesPage | Group pagination |
Pill / PillsInput | StateMultiSelect | Multi-select pills |
Popover + subcomponents | SettingsMenu, QueryPanel, NotificationsIcon | Overlay panels |
Progress | ReadinessWrapper | WAL replay progress |
RingProgress | ScrapePoolsList, SD lists | Pool health ring |
ScrollArea | NotificationsIcon, DataTable | Scrollable regions |
SegmentedControl | QueryPanel (graph mode), DataTable (histogram scale) | Segmented toggles |
Select | TargetsPage, SD page, ResolutionInput | Dropdown selects |
Skeleton | App.tsx (Suspense fallback), QueryPanel, Graph, TargetsPage, SD page | Loading skeletons |
Space | QueryPanel | Explicit spacing |
Stack | Many files | Vertical layout |
Table + subcomponents | DataTable, FlagsPage, StatusPage, TSDBStatusPage, AlertsPage, RulesPage, ScrapePoolsList, RuleDefinition | Data display |
Tabs + subcomponents | QueryPanel | Table/Graph/Explain tabs |
Text | Nearly all files | Typography |
TextInput | AlertsPage, RulesPage, TargetsPage, SD page, FlagsPage, RangeInput, ResolutionInput | Search/filter inputs |
Tooltip | AlertsPage, RulesPage, RuleDefinition, TreeNode, ScrapePoolsList | Hover info |
UnstyledButton | FlagsPage | Sortable column header |
3.3 Mantine Hooks Used
| Hook | Usage |
|---|---|
useDisclosure | App.tsx (mobile nav), TargetLabels (label toggle) |
useCombobox | StateMultiSelect |
useMantineColorScheme | ThemeSelector |
useComputedColorScheme | ExpressionInput, UPlotChart, RuleDefinition |
useElementSize | Graph.tsx (width for uPlot) |
useDebouncedValue | AlertsPage, RulesPage, TargetsPage (search debounce 250ms) |
useLocalStorage | AlertsPage, RulesPage, ScrapePoolsList, SD lists |
useId | TreeNode, Accordion |
useUncontrolled | Accordion |
3.4 Tabler Icons Used
Navigation: IconSearch, IconBellFilled, IconBell, IconBook, IconChevronDown, IconChevronRight, IconCloudDataConnection, IconDatabase, IconDeviceDesktopAnalytics, IconFlag, IconHeartRateMonitor, IconInfoCircle, IconServer, IconServerCog, IconTable
Actions: IconSettings, IconMoonFilled, IconSunFilled, IconBrightnessFilled, IconDotsVertical, IconPlus, IconMinus, IconTrash, IconCopy, IconAlignJustified, IconBinaryTree, IconTerminal, IconAdjustmentsHorizontal, IconLayoutNavbarCollapse, IconLayoutNavbarExpand, IconChevronLeft, IconChevronRight, IconChevronUp, IconSelector
Status: IconAlertTriangle, IconAlertCircle, IconNetworkOff, IconMessageExclamation, IconPointFilled
Domain: IconClockPause, IconClockPlay, IconRefresh, IconHourglass, IconRepeat, IconTimeline, IconBell (rules), IconSpy, IconRun, IconWall, IconChartLine, IconChartAreaFilled, IconGraph
4. Layout Patterns
4.1 App Shell Structure
BrowserRouter > QueryParamProvider > MantineProvider > CodeHighlightAdapterProvider
ββ QueryClientProvider
ββ AppShell (header height=56, navbar width=300, breakpoint=sm, padding=md)
ββ AppShell.Header (bg=rgb(65,73,81), c=#fff)
β ββ Group h="100%" px="md" wrap="nowrap"
β ββ Group style={{flex:1}} justify="space-between"
β β ββ Group gap={40} (logo + nav links) [desktop]
β β ββ Group visibleFrom="xs" (action icons) [desktop]
β ββ Burger hiddenFrom="sm" [mobile]
ββ AppShell.Navbar py="md" px={4} bg=... c=... [mobile only]
β ββ navLinks + Group hiddenFrom="xs" (action icons)
ββ AppShell.Main
ββ ErrorBoundary > Suspense > Routes
4.2 Status/Info Page Pattern
Used by: StatusPage, TSDBStatusPage, AgentPage, FlagsPage
InfoPageStack (Stack gap="lg" maw={1000} mx="auto" mt="xs")
ββ InfoPageCard (Card shadow="xs" withBorder p="md")
ββ Group (icon + title) [optional]
ββ {children} (usually Table)
4.3 Accordion-Based List Pattern
Used by: AlertsPage, RulesPage, TargetsPage, ServiceDiscoveryPage
Stack mt="xs"
ββ Group (filters: StateMultiSelect + TextInput)
ββ Pagination (hideWithOnePage)
ββ [Card (group header)]
ββ Accordion multiple variant="separated"
ββ Accordion.Item (health border class)
ββ Accordion.Control
β ββ Group justify="space-between" (name + badges)
ββ Accordion.Panel
ββ CustomInfiniteScroll > Table/RuleDefinition
4.4 Query Page Panel Pattern
Stack gap="xl" (outer, for multiple panels)
ββ Stack gap="lg" (per QueryPanel)
ββ ExpressionInput (Group align="flex-start" wrap="nowrap" gap="xs")
β ββ InputBase (CodeMirror host, flex=auto)
β ββ Button "Execute"
ββ [TreeView β conditional Suspense]
ββ Tabs (Table | Graph | Explain)
ββ Tabs.Panel "table"
β ββ TableTab > DataTable > Table
ββ Tabs.Panel "graph"
β ββ Group (RangeInput + TimeInput + ResolutionInput + controls)
β ββ Graph (Box > LoadingOverlay + UPlotChart)
ββ Tabs.Panel "explain"
ββ ExplainView
4.5 Common Layout Compositions
Filter bar (reused across AlertsPage, RulesPage, TargetsPage, SD page):
<Group>
<StateMultiSelect ... />
<TextInput flex={1} leftSection={<IconSearch />} ... />
[<ActionIcon /> // TargetsPage only β collapse/expand]
</Group>
Card group header (AlertsPage, RulesPage):
<Group mb="sm" justify="space-between">
<Group align="baseline">
<Text fz="xl" fw={600} c="var(--mantine-primary-color-filled)">{name}</Text>
<Text fz="sm" c="gray.6">{file}</Text>
</Group>
<Group>{badges}</Group>
</Group>
4.6 Responsive Behavior
- Breakpoint
sm: Nav collapses from horizontal buttons to mobile drawer - Breakpoint
xs: Action icons (theme/notifications/settings) move from header to drawer - Breakpoint
md: "Prometheus" logo text switches β there is a gap window where the text is invisible betweensmandmd - Tables do not have explicit responsive handling β horizontal scrolling on narrow viewports is implicit
- Graph width uses
useElementSizehook to measure the actual container and pass pixel width to uPlot
5. Interaction Patterns
5.1 Form Controls
Text search: TextInput with IconSearch left section. Debounced at 250ms via useDebouncedValue. Search state persisted to URL via use-query-params.
State/health filter: StateMultiSelect (custom Combobox) with colored pills. Persisted to URL.
Pagination: Mantine Pagination with hideWithOnePage. Page number in URL (?page=N). Active page clamped to valid range on filter changes.
Settings: Mantine Checkbox and NumberInput inside Fieldset groups in a Popover. Settings written to localStorage via Redux middleware.
Graph time range: RangeInput (TextInput + +/- ActionIcons with stepped values), TimeInput (DateTimePicker + prev/next ActionIcons), ResolutionInput (Select + optional TextInput for custom).
Sorting (FlagsPage only): Client-side sort via UnstyledButton column headers with icon state. Does not persist to URL.
5.2 PromQL Expression Input
- Editor: CodeMirror 6 via
@uiw/react-codemirror, embedded asInputBasecomponent - Extensions: history, indentOnInput, bracketMatching, closeBrackets, autocompletion, highlightSelectionMatches, lineWrapping, lintKeymap
- Autocomplete: PromQL-aware via
@prometheus-io/codemirror-promqlwith remote metric names and optional query history overlay - Execution:
Enterruns query;Shift+Enterinserts newline;Escapeblurs editor - Right section:
Menu(dots icon) with options: Explore metrics, Format expression, Show/Hide tree, Duplicate, Remove - Format expression: calls
/format_queryAPI; shows Mantine toast notifications for success/error - Metrics Explorer:
Modal size="95%"withMetricsExplorercomponent (Suspense-wrapped) - Syntax highlighting: Toggled per user setting; dual theme via
promqlHighlighter/darkPromqlHighlighter - Linting: Toggled per user setting via
promqlExtension.activateLinter()
Read-only mode (RuleDefinition): Uses CodeMirror directly with editable={false}, only baseTheme + lightTheme + syntaxHighlighting + promqlExtension + lineWrapping β no autocomplete, no history.
5.3 Filtering / Search
Client-side fuzzy search: KVSearch from @nexucis/kvsearch used in:
- AlertsPage β indexes
["name", "labels", ["labels", /.*/]] - RulesPage β same indexes
- TargetsPage β indexes
["labels", "scrapePool", ["labels", /.*/]] - ServiceDiscoveryPage β separate searches for active and dropped targets
All searches are debounced at 250ms; results are useMemo-ized.
5.4 Navigation
- Primary nav:
Button component={NavLink}β NavLink handles active state viaaria-current="page" - Status dropdown:
Menucomponent withRoutes-based active target detection - Routing:
BrowserRouterwithbasename={pathPrefix}for reverse-proxy support - Agent mode: Conditionally filters nav pages; redirects root to
/agentvs/query - URL state: Query params via
use-query-paramsadapter for React Router 6
5.5 Data Loading
Pattern: useSuspenseAPIQuery for page-level data (surfaces errors/loading to Suspense/ErrorBoundary above). useAPIQuery for non-blocking fetches (graph data, format query, metric names).
Suspense fallbacks: Skeleton arrays β typically Array.from(Array(10/20), ...) generating stacked Skeleton components with fixed heights (40px for pools, 30px for content).
Loading states:
- Initial load β Skeleton fallback via Suspense
- Subsequent fetches β
LoadingOverlay(graph),Loader size="xs"(format button) - Tree view nodes β
Loader size={14}inline
Notifications: useSuspenseQuery re-polls readiness every 1 second for WAL replay. Notifications use SSE (fetchEventSource) with fallback to 10-second polling if SSE returns 204.
Error handling: ErrorBoundary class component wrapping routes and Suspense blocks. Displays Alert color="red" with error message. Resets on route change.
5.6 Infinite Scroll
CustomInfiniteScroll component (using react-infinite-scroll-component) wraps target/alert/rule lists within Accordion panels to prevent rendering all items at once. Used in AlertsPage, RulesPage, ScrapePoolsList, SD lists.
6. Inconsistencies & Issues
6.1 Hardcoded Color Values Bypassing Theme
High severity:
- Navigation header background is hardcoded as
rgb(65, 73, 81)in bothAppShell.HeaderandAppShell.Navbar. This color does not adapt to light/dark mode β the header always appears dark regardless of user theme preference. - Navigation header text is hardcoded as
c="#fff"andstyle={{ color: "white" }}for the logo link. Always white, won't adapt. - Logo text
fz={20}uses a bare number. Mantine interprets bare numbers as pixel values, so this may not scale with user font preferences. - SeriesName hover uses
#add6ffa0(hardcoded RGBA hex) and#495057β light-mode specific values that will look wrong in dark mode. - RulesPage accordion borders use inline
stylewithvar(--mantine-color-red-4/gray-5/green-4)β slightly different shades thanPanel.module.css(red-3,gray-3,green-3in light). - uPlot chart elements (
.u-over,.u-select,.u-tooltipborder) have hardcoded#cccandrgba(255, 200, 150, 0.2)that only work well in light mode. - CodeMirror
baseThemecontainscolor: "#999"for completion detail and#e6f3fffor selection match β light-mode-only hardcoded values in a supposedly mode-agnostic block. - TreeNode connector line uses an inline style string with
light-dark()applied via JavaScript style object rather than CSS β may not be processed correctly by all browsers.
6.2 Inconsistent Health Status Borders
AlertsPage uses panelClasses.panelHealthErr/Warn/Ok/Unknown (from Panel.module.css), which applies 5px solid borders with green-3/red-3/orange-3/gray-3 shades.
RulesPage uses inline styles with var(--mantine-color-red-4), var(--mantine-color-gray-5), var(--mantine-color-green-4) β different shade numbers (-4 vs -3). Same visual pattern, different implementations.
6.3 Duplicate localStorageKey Between AlertsPage and RulesPage
Both AlertsPage and RulesPage use:
useLocalStorage({ key: "alertsPage.showEmptyGroups", defaultValue: false })
alertsPage.showEmptyGroups key β the two pages share the same toggle state.
6.4 Duplicate Page Structure Code (AlertsPage / RulesPage)
AlertsPage and RulesPage share nearly identical structure:
- Same filter bar (StateMultiSelect + TextInput)
- Same pagination pattern
- Same group Card header structure
- Same Accordion.Item with CustomInfiniteScroll
Similarly, TargetsPage and ServiceDiscoveryPage share almost identical filter bar/collapse button structure and pool-selection-with-limit logic. targetPoolDisplayLimit = 20 is defined in both pages independently.
6.5 Missing Accessibility Attributes
- Nav links use
Button component={NavLink}β the parentclassNamedoesn't semantically communicate the nav role. - StateMultiSelect uses
Combobox.DropdownTargetwithoutroleoraria-*attributes on the trigger. - Burger has no accessible label (
aria-labelnot set). - TreeNode clickable boxes have
onClickbut norole="button"ortabIndex, making them keyboard-inaccessible. - SeriesName
labelPairspan hasonDoubleClickfor copy but no keyboard alternative. - uPlot chart has no
aria-labelor accessible description. - LoadingOverlay has
zIndex={1000}andh={570}(hardcoded pixel height).
6.6 Icon Style Inconsistencies
Three separate local icon style objects are defined in different files for the same size:
// QueryPanel.tsx
const iconStyle = { width: "0.9rem", height: "0.9rem" };
// RangeInput.tsx
const iconStyle = { width: "0.9rem", height: "0.9rem" };
// TimeInput.tsx
const iconStyle = { width: "0.9rem", height: "0.9rem" };
// TODO: This is duplicated everywhere, unify it.
Meanwhile styles.ts exports many similar constants: navIconStyle (rem(16) Γ rem(16)), menuIconStyle (rem(14) Γ rem(14)), badgeIconStyle (em(17) Γ em(17)), actionIconStyle (70% Γ 70%), inputIconStyle (em(16) Γ em(16)), buttonIconStyle (em(20) Γ em(20)), infoPageCardTitleIconStyle (em(17.5) Γ em(17.5)), expandIconStyle (em(16) Γ em(16)), themeSwitcherIconStyle (rem(20) Γ rem(20)). Mixed use of rem(), em(), %, and string literals.
6.7 Mantine-Overrides.css Scope
mantine-overrides.css contains a single global override:
.mantine-Badge-label {
overflow: unset;
text-overflow: unset;
}
This affects all Badge components throughout the app. The LabelBadges component avoids Mantine's Badge entirely and uses plain spans β so this override may be for other Badge usages that need to overflow.
6.8 CSS Module vs Inline Style vs Mantine Props β Inconsistent Patterns
| Approach | Examples |
|---|---|
| CSS module classes | Panel.module.css, Badge.module.css, TreeNode.module.css |
Mantine c= / bg= props | c="gray.6", c="dimmed", c="var(--mantine-primary-color-filled)" |
style={} inline | style={{ whiteSpace: "nowrap" }}, connector line, TreeNode box border |
styles={} prop | Accordion.Control label padding, Badge label textTransform |
| CSS variables in props | c="light-dark(var(...), var(...))" in Table.Th |
The pattern of passing raw CSS light-dark() strings into Mantine's c prop is particularly unconventional and may break in future Mantine versions.
6.9 DataTable.tsx Inline Flexbox Styles
The histogram table uses inline style objects for flexbox layout:
<Table.Tbody style={{ display: "flex", flexDirection: "column", justifyContent: "space-between" }}>
<Table.Tr style={{ display: "flex", flexDirection: "row", justifyContent: "space-between" }}>
This overrides the default table display model globally. It works but is semantically unusual (flex on <tbody> and <tr>).
6.10 Navbar Duplicate Text Visibility
In App.tsx:
<Text hiddenFrom="sm" fz={20}>Prometheus</Text> // visible: xs, hidden: sm+
<Text visibleFrom="md" fz={20}>Prometheus</Text> // visible: md+
Between breakpoints sm and md, neither text is visible β the "Prometheus" label disappears on medium-small windows.
6.11 Commented-Out Code
Several significant pieces of commented-out code exist:
- Exemplars button in
QueryPanel.tsx(Show/Hide exemplars toggle β entire Button block commented) - Heatmap mode in
QueryPanel.tsx(SegmentedControl option commented) ReactQueryDevtoolsinApp.tsx- Several CSS properties in
ExpressionInput.module.css(border, padding, font) - uPlot tooltip background alternatives in
uplot.css LoadingOverlayalternative loader styles inGraph.tsx
6.12 Missing key Props and TODO Comments
The codebase contains multiple // TODO: Find a stable and definitely unique key. comments in ScrapePoolsList and ServiceDiscoveryPoolsList where list items use array index (key={i}) β acknowledged as a potential reconciliation issue.
7. Domain-Specific Patterns
7.1 PromQL Expression Input Architecture
The expression input uses CodeMirror 6 embedded inside a Mantine InputBase component:
<InputBase
component={CodeMirror}
className={classes.input}
basicSetup={false}
value={expr}
onChange={setExpr}
extensions={[
baseTheme,
highlightSpecialChars(),
history(),
EditorState.allowMultipleSelections.of(true),
indentOnInput(),
bracketMatching(),
closeBrackets(),
autocompletion(),
highlightSelectionMatches(),
EditorView.lineWrapping,
keymap.of([...closeBracketsKeymap, ...defaultKeymap, ...historyKeymap, ...completionKeymap, ...lintKeymap]),
placeholder("Enter expression (press Shift+Enter for newlines)"),
enableSyntaxHighlighting ? syntaxHighlighting(theme === "light" ? promqlHighlighter : darkPromqlHighlighter) : [],
promqlExtension.asExtension(),
theme === "light" ? lightTheme : darkTheme,
]}
multiline
/>
Key behaviors: Enter β execute query; Shift+Enter β insert newline; Escape β blur editor. Query history overlay via HistoryCompleteStrategy. Autocomplete and linting are each independently togglable.
7.2 Time Series Graph Rendering
The graph pipeline:
Graph.tsx: Executes/query_rangeAPI with effective resolution and time range- Data is held in
dataAndRangestate β old data kept displayed while new query is in-flight (no flash-to-empty) UPlotChart.tsx: TranslatesRangeSamples[]touPlot.AlignedDataviagetUPlotData(). CallsgetUPlotOptions()for theme-aware options.- Width is measured via
useElementSize()hook (requires two renders to get valid width) - Colors sourced from
colorPool.tsviagetSeriesColor(idx, isLight) - Graph height: 570px hardcoded via
LoadingOverlay h={570} - Chart wrapper: 1px solid border using theme-aware CSS variable in
Graph.module.css
uPlot legend is displayed below the chart with left-aligned text. Custom CSS overrides u-legend to remove default centering.
Range selection: uPlot's built-in select fires onSelectRange(start, end) callback which updates the visualizer state and re-executes the query.
Display modes: GraphDisplayMode.Lines (unstacked) and GraphDisplayMode.Stacked. Heatmap is commented out.
7.3 Metric Display Formatting
SeriesName component renders label sets as metricName{label="value", ...}:
- Formatted mode (
format=true): Renders individual<span>elements per label with CSS classes. Double-click on any label pair copies the matcher to clipboard via Mantine notification. - Unformatted mode (
format=false): Renders a single text string viaformatSeries()β used whenresult.length > 1000to improve scrolling performance. - Extended charset handling: If metric name requires quoting (new UTF-8 charset), it's placed inside the braces as
"escaped_name".
Number display in DataTable: Raw string values from API, no formatting applied.
Duration formatting: Uses custom formatPrometheusDuration() (e.g., "1h30m") throughout RangeInput, RuleDefinition badges.
Timestamp formatting: formatTimestamp(unix, useLocalTime) used in DataTable, StatusPage, TSDBStatusPage, NotificationsIcon. Respects the useLocalTime user setting.
7.4 Label/Badge Display
LabelBadges component is a performance-critical custom implementation. Comment in code explains: "We build our own Mantine-style badges here for performance reasons. We have pages with thousands of labels and care about their performance more than other badges."
Implementation: Plain <span> elements with CSS class .labelBadge that manually replicates Mantine Badge dimensions using var(--mantine-scale) for scaling. Single DOM node vs Mantine's two-node structure.
EndpointLink component parses URL query parameters and renders them as Mantine Badge size="sm" variant="light" color="gray" pills. IPv6 with Zone ID causes URL parse failure β handled with synthetic URL construction.
TargetLabels component shows active labels by default, with a collapse toggle to reveal discovered (pre-relabeling) labels in blue styling.
7.5 PromQL AST Tree View
TreeNode is a recursive component that:
- Traverses the PromQL AST (from
@prometheus-io/codemirror-promql's AST types) - For each node, executes an instant query to get result stats (series count, label cardinalities, examples)
- Uses a bottom-up execution order: child nodes report their state to parents; a node only queries when all children report
success - Matrix selectors are wrapped in
last_over_time()to get cheaper stats - Connector lines between nodes are positioned absolutely using
getBoundingClientRect()comparisons inuseLayoutEffect - Binary expression nodes render children above and below; other nodes render children below
7.6 Health State System
Three overlapping health state vocabularies are used:
| Context | States |
|---|---|
| Alert rules | inactive, pending, firing, unknown |
| Rule health | ok, err, unknown |
| Target health | up, down, unknown |
| Service discovery | active, dropped |
Each uses the same badge CSS classes but the mapping function is repeated in every page file rather than centralized:
// AlertsPage.tsx
const alertStateToClass = (state) =>
state === "inactive" ? healthOk : state === "pending" ? healthWarn : ...
// RulesPage.tsx
const healthBadgeClass = (state) =>
state === "ok" ? healthOk : state === "err" ? healthErr : ...
// ScrapePoolsList.tsx
const healthBadgeClass = (state) =>
state.toLowerCase() === "up" ? healthOk : state.toLowerCase() === "down" ? healthErr : ...
No shared stateToClass(state: string, vocabulary: 'alert'|'rule'|'target') function exists.
8. Summary of Key Findings
Critical Issues
- Header is permanently dark β
rgb(65, 73, 81)hardcoded, ignores user's light/dark theme preference. The Prometheus logo area never adapts. This is the most visually jarring inconsistency. - localStorage key collision β AlertsPage and RulesPage share
"alertsPage.showEmptyGroups". This is a functional bug where showing/hiding empty groups in one page affects the other. - TreeNode accessibility β Clickable boxes with no keyboard support (
onClickonly, norole/tabIndex/onKeyDown).
High-Priority Inconsistencies
- Dual health-border implementation β AlertsPage uses CSS module classes; RulesPage uses inline styles with different color scale steps.
- Icon size constants duplicated β
{ width: "0.9rem", height: "0.9rem" }defined locally in 3 files despite acknowledgedTODO: duplicated everywhere. targetPoolDisplayLimit = 20defined in two files β TargetsPage.tsx and ServiceDiscoveryPage.tsx independently.- Duplicate page structure β AlertsPage/RulesPage and TargetsPage/ServiceDiscoveryPage share near-identical structure without abstraction.
Design Token Observations
- No custom typography β No font family, font scale, or heading style is configured in
createTheme(). The app fully relies on Mantine defaults. - No custom spacing scale β All spacing uses Mantine defaults plus scattered numeric literals.
- The color pool is vast but blunt β 942 algorithmically-generated colors with no perceptual ordering. First entries are mid-saturation greens/teals which may not be visually distinctive at small chart sizes.
codebox-bgcustom color registered but barely used β Defined increateTheme()but not referenced in component code.
Positive Patterns
- Consistent
light-dark()usage in CSS modules β badge, panel, accordion, and chart styles all properly handle both modes vialight-dark(). - Performance-conscious design β
LabelBadgesavoids Mantine Badge overhead,SeriesNamefalls back to plain text for >1000 series,CustomInfiniteScrolllimits DOM nodes,useMemoon filtered/rendered lists. - URL-persistent filter state β All major filters (search, state/health filter, pagination, active tab) are persisted in URL via
use-query-params, enabling bookmarkable and shareable views. - Suspense + ErrorBoundary composition is consistent throughout β all async page data uses
useSuspenseAPIQueryand is wrapped in coordinated Suspense/ErrorBoundary pairs that reset on route change. - Settings architecture β Redux + localStorage middleware cleanly separates UI settings with good TypeScript typing. Per-setting localStorage keys are exported as named constants.