mirror of
https://github.com/prometheus/prometheus.git
synced 2024-11-09 23:24:05 -08:00
Merge pull request #14907 from prometheus/julius/new-ui-improvements
UI improvements: Factor out common styles, fix tree node line rendering, always show full badge contents (no ellipsis)
This commit is contained in:
commit
62bd893b80
|
@ -2,6 +2,7 @@ import "@mantine/core/styles.css";
|
|||
import "@mantine/code-highlight/styles.css";
|
||||
import "@mantine/notifications/styles.css";
|
||||
import "@mantine/dates/styles.css";
|
||||
import "./mantine-overrides.css";
|
||||
import classes from "./App.module.css";
|
||||
import PrometheusLogo from "./images/prometheus-logo.svg";
|
||||
|
||||
|
@ -67,11 +68,10 @@ import { QueryParamProvider } from "use-query-params";
|
|||
import { ReactRouter6Adapter } from "use-query-params/adapters/react-router-6";
|
||||
import ServiceDiscoveryPage from "./pages/service-discovery/ServiceDiscoveryPage";
|
||||
import AlertmanagerDiscoveryPage from "./pages/AlertmanagerDiscoveryPage";
|
||||
import { actionIconStyle, navIconStyle } from "./styles";
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const navIconStyle = { width: rem(16), height: rem(16) };
|
||||
|
||||
const mainNavPages = [
|
||||
{
|
||||
title: "Query",
|
||||
|
@ -322,9 +322,9 @@ function App() {
|
|||
color="gray"
|
||||
title="Documentation"
|
||||
aria-label="Documentation"
|
||||
size={32}
|
||||
size={rem(32)}
|
||||
>
|
||||
<IconBook size={20} />
|
||||
<IconBook style={actionIconStyle} />
|
||||
</ActionIcon>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -32,7 +32,7 @@ class ErrorBoundary extends Component<Props, State> {
|
|||
<Alert
|
||||
color="red"
|
||||
title={this.props.title || "Error querying page data"}
|
||||
icon={<IconAlertTriangle size={14} />}
|
||||
icon={<IconAlertTriangle />}
|
||||
maw={500}
|
||||
mx="auto"
|
||||
mt="lg"
|
||||
|
|
32
web/ui/mantine-ui/src/components/InfoPageCard.tsx
Normal file
32
web/ui/mantine-ui/src/components/InfoPageCard.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { Card, Group } from "@mantine/core";
|
||||
import { TablerIconsProps } from "@tabler/icons-react";
|
||||
import { FC, ReactNode } from "react";
|
||||
import { infoPageCardTitleIconStyle } from "../styles";
|
||||
|
||||
const InfoPageCard: FC<{
|
||||
children: ReactNode;
|
||||
title?: string;
|
||||
icon?: React.ComponentType<TablerIconsProps>;
|
||||
}> = ({ children, title, icon: Icon }) => {
|
||||
return (
|
||||
<Card shadow="xs" withBorder p="md">
|
||||
{title && (
|
||||
<Group
|
||||
wrap="nowrap"
|
||||
align="center"
|
||||
ml="xs"
|
||||
mb="sm"
|
||||
gap="xs"
|
||||
fz="xl"
|
||||
fw={600}
|
||||
>
|
||||
{Icon && <Icon style={infoPageCardTitleIconStyle} />}
|
||||
{title}
|
||||
</Group>
|
||||
)}
|
||||
{children}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfoPageCard;
|
12
web/ui/mantine-ui/src/components/InfoPageStack.tsx
Normal file
12
web/ui/mantine-ui/src/components/InfoPageStack.tsx
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { Stack } from "@mantine/core";
|
||||
import { FC, ReactNode } from "react";
|
||||
|
||||
const InfoPageStack: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
return (
|
||||
<Stack gap="lg" maw={1000} mx="auto" mt="xs">
|
||||
{children}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfoPageStack;
|
|
@ -4,7 +4,6 @@ import {
|
|||
Box,
|
||||
Card,
|
||||
Group,
|
||||
rem,
|
||||
Table,
|
||||
Tooltip,
|
||||
useComputedColorScheme,
|
||||
|
@ -25,6 +24,7 @@ import {
|
|||
import { PromQLExtension } from "@prometheus-io/codemirror-promql";
|
||||
import { LabelBadges } from "./LabelBadges";
|
||||
import { useSettings } from "../state/settingsSlice";
|
||||
import { actionIconStyle, badgeIconStyle } from "../styles";
|
||||
|
||||
const promqlExtension = new PromQLExtension();
|
||||
|
||||
|
@ -64,7 +64,7 @@ const RuleDefinition: FC<{ rule: Rule }> = ({ rule }) => {
|
|||
}}
|
||||
className={codeboxClasses.queryButton}
|
||||
>
|
||||
<IconSearch style={{ width: rem(14) }} />
|
||||
<IconSearch style={actionIconStyle} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Card>
|
||||
|
@ -74,7 +74,7 @@ const RuleDefinition: FC<{ rule: Rule }> = ({ rule }) => {
|
|||
<Badge
|
||||
variant="light"
|
||||
styles={{ label: { textTransform: "none" } }}
|
||||
leftSection={<IconClockPause size={12} />}
|
||||
leftSection={<IconClockPause style={badgeIconStyle} />}
|
||||
>
|
||||
for: {formatPrometheusDuration(rule.duration * 1000)}
|
||||
</Badge>
|
||||
|
@ -83,7 +83,7 @@ const RuleDefinition: FC<{ rule: Rule }> = ({ rule }) => {
|
|||
<Badge
|
||||
variant="light"
|
||||
styles={{ label: { textTransform: "none" } }}
|
||||
leftSection={<IconClockPlay size={12} />}
|
||||
leftSection={<IconClockPlay style={badgeIconStyle} />}
|
||||
>
|
||||
keep_firing_for: {formatPrometheusDuration(rule.duration * 1000)}
|
||||
</Badge>
|
||||
|
|
|
@ -3,6 +3,7 @@ import { IconSettings } from "@tabler/icons-react";
|
|||
import { FC } from "react";
|
||||
import { useAppDispatch } from "../state/hooks";
|
||||
import { updateSettings, useSettings } from "../state/settingsSlice";
|
||||
import { actionIconStyle } from "../styles";
|
||||
|
||||
const SettingsMenu: FC = () => {
|
||||
const {
|
||||
|
@ -24,7 +25,7 @@ const SettingsMenu: FC = () => {
|
|||
aria-label="Settings"
|
||||
size={32}
|
||||
>
|
||||
<IconSettings size={20} />
|
||||
<IconSettings style={actionIconStyle} />
|
||||
</ActionIcon>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown>
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
useCombobox,
|
||||
} from "@mantine/core";
|
||||
import { IconHeartRateMonitor } from "@tabler/icons-react";
|
||||
import { inputIconStyle } from "../styles";
|
||||
|
||||
interface StatePillProps extends React.ComponentPropsWithoutRef<"div"> {
|
||||
value: string;
|
||||
|
@ -80,7 +81,7 @@ export const StateMultiSelect: FC<StateMultiSelectProps> = ({
|
|||
pointer
|
||||
onClick={() => combobox.toggleDropdown()}
|
||||
miw={200}
|
||||
leftSection={<IconHeartRateMonitor size={14} />}
|
||||
leftSection={<IconHeartRateMonitor style={inputIconStyle} />}
|
||||
rightSection={
|
||||
values.length > 0 ? (
|
||||
<ComboboxClearButton onClear={() => onChange([])} />
|
||||
|
|
4
web/ui/mantine-ui/src/mantine-overrides.css
Normal file
4
web/ui/mantine-ui/src/mantine-overrides.css
Normal file
|
@ -0,0 +1,4 @@
|
|||
.mantine-Badge-label {
|
||||
overflow: unset;
|
||||
text-overflow: unset;
|
||||
}
|
|
@ -1,16 +1,16 @@
|
|||
import { Card, Group, Text } from "@mantine/core";
|
||||
import { Text } from "@mantine/core";
|
||||
import { IconSpy } from "@tabler/icons-react";
|
||||
import { FC } from "react";
|
||||
import InfoPageStack from "../components/InfoPageStack";
|
||||
import InfoPageCard from "../components/InfoPageCard";
|
||||
|
||||
const AgentPage: FC = () => {
|
||||
return (
|
||||
<Card shadow="xs" withBorder p="md" mt="xs">
|
||||
<Group wrap="nowrap" align="center" ml="xs" mb="sm" gap="xs">
|
||||
<IconSpy size={22} />
|
||||
<Text fz="xl" fw={600}>
|
||||
Prometheus Agent
|
||||
</Text>
|
||||
</Group>
|
||||
<InfoPageStack>
|
||||
<InfoPageCard
|
||||
title="Prometheus Agent"
|
||||
icon={IconSpy}
|
||||
>
|
||||
<Text p="md">
|
||||
This Prometheus instance is running in <strong>agent mode</strong>. In
|
||||
this mode, Prometheus is only used to scrape discovered targets and
|
||||
|
@ -18,9 +18,10 @@ const AgentPage: FC = () => {
|
|||
</Text>
|
||||
<Text p="md">
|
||||
Some features are not available in this mode, such as querying and
|
||||
alerting.
|
||||
</Text>
|
||||
</Card>
|
||||
alerting.
|
||||
</Text>
|
||||
</InfoPageCard>
|
||||
</InfoPageStack>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { Alert, Card, Group, Stack, Table, Text } from "@mantine/core";
|
||||
import { Alert, Table } from "@mantine/core";
|
||||
import { IconBell, IconBellOff, IconInfoCircle } from "@tabler/icons-react";
|
||||
|
||||
import { useSuspenseAPIQuery } from "../api/api";
|
||||
import { AlertmanagersResult } from "../api/responseTypes/alertmanagers";
|
||||
import EndpointLink from "../components/EndpointLink";
|
||||
import InfoPageCard from "../components/InfoPageCard";
|
||||
import InfoPageStack from "../components/InfoPageStack";
|
||||
|
||||
export const targetPoolDisplayLimit = 20;
|
||||
|
||||
|
@ -18,14 +20,8 @@ export default function AlertmanagerDiscoveryPage() {
|
|||
});
|
||||
|
||||
return (
|
||||
<Stack gap="lg" maw={1000} mx="auto" mt="xs">
|
||||
<Card shadow="xs" withBorder p="md">
|
||||
<Group wrap="nowrap" align="center" ml="xs" mb="sm" gap="xs">
|
||||
<IconBell size={22} />
|
||||
<Text fz="xl" fw={600}>
|
||||
Active Alertmanagers
|
||||
</Text>
|
||||
</Group>
|
||||
<InfoPageStack>
|
||||
<InfoPageCard title="Active Alertmanagers" icon={IconBell}>
|
||||
{activeAlertmanagers.length === 0 ? (
|
||||
<Alert title="No active alertmanagers" icon={<IconInfoCircle />}>
|
||||
No active alertmanagers found.
|
||||
|
@ -46,14 +42,8 @@ export default function AlertmanagerDiscoveryPage() {
|
|||
</Table.Tbody>
|
||||
</Table>
|
||||
)}
|
||||
</Card>
|
||||
<Card shadow="xs" withBorder p="md">
|
||||
<Group wrap="nowrap" align="center" ml="xs" mb="sm" gap="xs">
|
||||
<IconBellOff size={22} />
|
||||
<Text fz="xl" fw={600}>
|
||||
Dropped Alertmanagers
|
||||
</Text>
|
||||
</Group>
|
||||
</InfoPageCard>
|
||||
<InfoPageCard title="Dropped Alertmanagers" icon={IconBellOff}>
|
||||
{droppedAlertmanagers.length === 0 ? (
|
||||
<Alert title="No dropped alertmanagers" icon={<IconInfoCircle />}>
|
||||
No dropped alertmanagers found.
|
||||
|
@ -74,7 +64,7 @@ export default function AlertmanagerDiscoveryPage() {
|
|||
</Table.Tbody>
|
||||
</Table>
|
||||
)}
|
||||
</Card>
|
||||
</Stack>
|
||||
</InfoPageCard>
|
||||
</InfoPageStack>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import {
|
|||
} from "use-query-params";
|
||||
import { useDebouncedValue } from "@mantine/hooks";
|
||||
import { KVSearch } from "@nexucis/kvsearch";
|
||||
import { inputIconStyle } from "../styles";
|
||||
|
||||
type AlertsPageData = {
|
||||
// How many rules are in each state across all groups.
|
||||
|
@ -190,7 +191,7 @@ export default function AlertsPage() {
|
|||
/>
|
||||
<TextInput
|
||||
flex={1}
|
||||
leftSection={<IconSearch size={14} />}
|
||||
leftSection={<IconSearch style={inputIconStyle} />}
|
||||
placeholder="Filter by rule name or labels"
|
||||
value={searchFilter || ""}
|
||||
onChange={(event) =>
|
||||
|
@ -199,7 +200,7 @@ export default function AlertsPage() {
|
|||
></TextInput>
|
||||
</Group>
|
||||
{alertsPageData.groups.length === 0 ? (
|
||||
<Alert title="No rules found" icon={<IconInfoCircle size={14} />}>
|
||||
<Alert title="No rules found" icon={<IconInfoCircle />}>
|
||||
No rules found.
|
||||
</Alert>
|
||||
) : (
|
||||
|
@ -207,7 +208,7 @@ export default function AlertsPage() {
|
|||
alertsPageData.groups.length !== shownGroups.length && (
|
||||
<Alert
|
||||
title="Hiding groups with no matching rules"
|
||||
icon={<IconInfoCircle size={14} />}
|
||||
icon={<IconInfoCircle/>}
|
||||
>
|
||||
Hiding {alertsPageData.groups.length - shownGroups.length} empty
|
||||
groups due to filters or no rules.
|
||||
|
@ -326,7 +327,7 @@ export default function AlertsPage() {
|
|||
{r.rule.alerts.length > 0 && (
|
||||
<Table mt="lg">
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Tr style={{whiteSpace: "nowrap"}}>
|
||||
<Table.Th>Alert labels</Table.Th>
|
||||
<Table.Th>State</Table.Th>
|
||||
<Table.Th>Active Since</Table.Th>
|
||||
|
|
|
@ -8,7 +8,6 @@ import {
|
|||
TextInput,
|
||||
rem,
|
||||
keys,
|
||||
Card,
|
||||
} from "@mantine/core";
|
||||
import {
|
||||
IconSelector,
|
||||
|
@ -18,6 +17,9 @@ import {
|
|||
} from "@tabler/icons-react";
|
||||
import classes from "./FlagsPage.module.css";
|
||||
import { useSuspenseAPIQuery } from "../api/api";
|
||||
import InfoPageStack from "../components/InfoPageStack";
|
||||
import InfoPageCard from "../components/InfoPageCard";
|
||||
import { inputIconStyle } from "../styles";
|
||||
|
||||
interface RowData {
|
||||
flag: string;
|
||||
|
@ -124,59 +126,56 @@ export default function FlagsPage() {
|
|||
));
|
||||
|
||||
return (
|
||||
<Card shadow="xs" maw={1000} mx="auto" mt="xs" withBorder>
|
||||
<TextInput
|
||||
placeholder="Filter by flag name or value"
|
||||
mb="md"
|
||||
autoFocus
|
||||
leftSection={
|
||||
<IconSearch
|
||||
style={{ width: rem(16), height: rem(16) }}
|
||||
stroke={1.5}
|
||||
/>
|
||||
}
|
||||
value={search}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
<Table
|
||||
horizontalSpacing="md"
|
||||
verticalSpacing="xs"
|
||||
miw={700}
|
||||
layout="fixed"
|
||||
>
|
||||
<Table.Tbody>
|
||||
<Table.Tr>
|
||||
<Th
|
||||
sorted={sortBy === "flag"}
|
||||
reversed={reverseSortDirection}
|
||||
onSort={() => setSorting("flag")}
|
||||
>
|
||||
Flag
|
||||
</Th>
|
||||
|
||||
<Th
|
||||
sorted={sortBy === "value"}
|
||||
reversed={reverseSortDirection}
|
||||
onSort={() => setSorting("value")}
|
||||
>
|
||||
Value
|
||||
</Th>
|
||||
</Table.Tr>
|
||||
</Table.Tbody>
|
||||
<Table.Tbody>
|
||||
{rows.length > 0 ? (
|
||||
rows
|
||||
) : (
|
||||
<InfoPageStack>
|
||||
<InfoPageCard>
|
||||
<TextInput
|
||||
placeholder="Filter by flag name or value"
|
||||
mb="md"
|
||||
autoFocus
|
||||
leftSection={<IconSearch style={inputIconStyle} />}
|
||||
value={search}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
<Table
|
||||
horizontalSpacing="md"
|
||||
verticalSpacing="xs"
|
||||
miw={700}
|
||||
layout="fixed"
|
||||
>
|
||||
<Table.Tbody>
|
||||
<Table.Tr>
|
||||
<Table.Td colSpan={2}>
|
||||
<Text fw={500} ta="center">
|
||||
Nothing found
|
||||
</Text>
|
||||
</Table.Td>
|
||||
<Th
|
||||
sorted={sortBy === "flag"}
|
||||
reversed={reverseSortDirection}
|
||||
onSort={() => setSorting("flag")}
|
||||
>
|
||||
Flag
|
||||
</Th>
|
||||
|
||||
<Th
|
||||
sorted={sortBy === "value"}
|
||||
reversed={reverseSortDirection}
|
||||
onSort={() => setSorting("value")}
|
||||
>
|
||||
Value
|
||||
</Th>
|
||||
</Table.Tr>
|
||||
)}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</Card>
|
||||
</Table.Tbody>
|
||||
<Table.Tbody>
|
||||
{rows.length > 0 ? (
|
||||
rows
|
||||
) : (
|
||||
<Table.Tr>
|
||||
<Table.Td colSpan={2}>
|
||||
<Text fw={500} ta="center">
|
||||
Nothing found
|
||||
</Text>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
)}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</InfoPageCard>
|
||||
</InfoPageStack>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import { useSuspenseAPIQuery } from "../api/api";
|
|||
import { RulesResult } from "../api/responseTypes/rules";
|
||||
import badgeClasses from "../Badge.module.css";
|
||||
import RuleDefinition from "../components/RuleDefinition";
|
||||
import { badgeIconStyle } from "../styles";
|
||||
|
||||
const healthBadgeClass = (state: string) => {
|
||||
switch (state) {
|
||||
|
@ -47,7 +48,7 @@ export default function RulesPage() {
|
|||
return (
|
||||
<Stack mt="xs">
|
||||
{data.data.groups.length === 0 && (
|
||||
<Alert title="No rule groups" icon={<IconInfoCircle size={14} />}>
|
||||
<Alert title="No rule groups" icon={<IconInfoCircle />}>
|
||||
No rule groups configured.
|
||||
</Alert>
|
||||
)}
|
||||
|
@ -74,7 +75,7 @@ export default function RulesPage() {
|
|||
variant="light"
|
||||
className={badgeClasses.statsBadge}
|
||||
styles={{ label: { textTransform: "none" } }}
|
||||
leftSection={<IconRefresh size={12} />}
|
||||
leftSection={<IconRefresh style={badgeIconStyle} />}
|
||||
>
|
||||
last run {humanizeDurationRelative(g.lastEvaluation, now())}
|
||||
</Badge>
|
||||
|
@ -84,7 +85,7 @@ export default function RulesPage() {
|
|||
variant="light"
|
||||
className={badgeClasses.statsBadge}
|
||||
styles={{ label: { textTransform: "none" } }}
|
||||
leftSection={<IconHourglass size={12} />}
|
||||
leftSection={<IconHourglass style={badgeIconStyle} />}
|
||||
>
|
||||
took {humanizeDuration(parseFloat(g.evaluationTime) * 1000)}
|
||||
</Badge>
|
||||
|
@ -94,7 +95,7 @@ export default function RulesPage() {
|
|||
variant="transparent"
|
||||
className={badgeClasses.statsBadge}
|
||||
styles={{ label: { textTransform: "none" } }}
|
||||
leftSection={<IconRepeat size={12} />}
|
||||
leftSection={<IconRepeat style={badgeIconStyle} />}
|
||||
>
|
||||
every {humanizeDuration(parseFloat(g.interval) * 1000)}{" "}
|
||||
</Badge>
|
||||
|
@ -102,7 +103,7 @@ export default function RulesPage() {
|
|||
</Group>
|
||||
</Group>
|
||||
{g.rules.length === 0 && (
|
||||
<Alert title="No rules" icon={<IconInfoCircle size={14} />}>
|
||||
<Alert title="No rules" icon={<IconInfoCircle />}>
|
||||
No rules in rule group.
|
||||
</Alert>
|
||||
)}
|
||||
|
@ -150,7 +151,7 @@ export default function RulesPage() {
|
|||
variant="light"
|
||||
className={badgeClasses.statsBadge}
|
||||
styles={{ label: { textTransform: "none" } }}
|
||||
leftSection={<IconRefresh size={12} />}
|
||||
leftSection={<IconRefresh style={badgeIconStyle} />}
|
||||
>
|
||||
{humanizeDurationRelative(r.lastEvaluation, now())}
|
||||
</Badge>
|
||||
|
@ -164,7 +165,9 @@ export default function RulesPage() {
|
|||
variant="light"
|
||||
className={badgeClasses.statsBadge}
|
||||
styles={{ label: { textTransform: "none" } }}
|
||||
leftSection={<IconHourglass size={12} />}
|
||||
leftSection={
|
||||
<IconHourglass style={badgeIconStyle} />
|
||||
}
|
||||
>
|
||||
{humanizeDuration(
|
||||
parseFloat(r.evaluationTime) * 1000
|
||||
|
@ -185,7 +188,7 @@ export default function RulesPage() {
|
|||
color="red"
|
||||
mt="sm"
|
||||
title="Rule failed to evaluate"
|
||||
icon={<IconAlertTriangle size={14} />}
|
||||
icon={<IconAlertTriangle />}
|
||||
>
|
||||
<strong>Error:</strong> {r.lastError}
|
||||
</Alert>
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { Card, Group, Stack, Table, Text } from "@mantine/core";
|
||||
import { Table } from "@mantine/core";
|
||||
import { useSuspenseAPIQuery } from "../api/api";
|
||||
import { IconRun, IconWall } from "@tabler/icons-react";
|
||||
import { formatTimestamp } from "../lib/formatTime";
|
||||
import { useSettings } from "../state/settingsSlice";
|
||||
import InfoPageCard from "../components/InfoPageCard";
|
||||
import InfoPageStack from "../components/InfoPageStack";
|
||||
|
||||
export default function StatusPage() {
|
||||
const { data: buildinfo } = useSuspenseAPIQuery<Record<string, string>>({
|
||||
|
@ -42,14 +44,8 @@ export default function StatusPage() {
|
|||
};
|
||||
|
||||
return (
|
||||
<Stack gap="lg" maw={1000} mx="auto" mt="xs">
|
||||
<Card shadow="xs" withBorder p="md">
|
||||
<Group wrap="nowrap" align="center" ml="xs" mb="sm" gap="xs">
|
||||
<IconWall size={22} />
|
||||
<Text fz="xl" fw={600}>
|
||||
Build information
|
||||
</Text>
|
||||
</Group>
|
||||
<InfoPageStack>
|
||||
<InfoPageCard title="Build information" icon={IconWall}>
|
||||
<Table layout="fixed">
|
||||
<Table.Tbody>
|
||||
{Object.entries(buildinfo.data).map(([k, v]) => (
|
||||
|
@ -60,14 +56,8 @@ export default function StatusPage() {
|
|||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</Card>
|
||||
<Card shadow="xs" withBorder p="md">
|
||||
<Group wrap="nowrap" align="center" ml="xs" mb="sm" gap="xs">
|
||||
<IconRun size={22} />
|
||||
<Text fz="xl" fw={600}>
|
||||
Runtime information
|
||||
</Text>
|
||||
</Group>
|
||||
</InfoPageCard>
|
||||
<InfoPageCard title="Runtime information" icon={IconRun}>
|
||||
<Table layout="fixed">
|
||||
<Table.Tbody>
|
||||
{Object.entries(runtimeinfo.data).map(([k, v]) => {
|
||||
|
@ -84,7 +74,7 @@ export default function StatusPage() {
|
|||
})}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</Card>
|
||||
</Stack>
|
||||
</InfoPageCard>
|
||||
</InfoPageStack>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { Stack, Card, Table, Text } from "@mantine/core";
|
||||
import { Table } from "@mantine/core";
|
||||
import { useSuspenseAPIQuery } from "../api/api";
|
||||
import { TSDBStatusResult } from "../api/responseTypes/tsdbStatus";
|
||||
import { formatTimestamp } from "../lib/formatTime";
|
||||
import { useSettings } from "../state/settingsSlice";
|
||||
import InfoPageStack from "../components/InfoPageStack";
|
||||
import InfoPageCard from "../components/InfoPageCard";
|
||||
|
||||
export default function TSDBStatusPage() {
|
||||
const {
|
||||
|
@ -41,7 +43,7 @@ export default function TSDBStatusPage() {
|
|||
];
|
||||
|
||||
return (
|
||||
<Stack gap="lg" maw={1000} mx="auto" mt="xs">
|
||||
<InfoPageStack>
|
||||
{[
|
||||
{
|
||||
title: "TSDB Head Status",
|
||||
|
@ -70,10 +72,7 @@ export default function TSDBStatusPage() {
|
|||
formatAsCode: true,
|
||||
},
|
||||
].map(({ title, unit = "Count", stats, formatAsCode }) => (
|
||||
<Card shadow="xs" withBorder p="md">
|
||||
<Text fz="xl" fw={600} ml="xs" mb="sm">
|
||||
{title}
|
||||
</Text>
|
||||
<InfoPageCard title={title}>
|
||||
<Table layout="fixed">
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
|
@ -82,24 +81,22 @@ export default function TSDBStatusPage() {
|
|||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{stats.map(({ name, value }) => {
|
||||
return (
|
||||
<Table.Tr key={name}>
|
||||
<Table.Td
|
||||
style={{
|
||||
wordBreak: "break-all",
|
||||
}}
|
||||
>
|
||||
{formatAsCode ? <code>{name}</code> : name}
|
||||
</Table.Td>
|
||||
<Table.Td>{value}</Table.Td>
|
||||
</Table.Tr>
|
||||
);
|
||||
})}
|
||||
{stats.map(({ name, value }) => (
|
||||
<Table.Tr key={name}>
|
||||
<Table.Td
|
||||
style={{
|
||||
wordBreak: "break-all",
|
||||
}}
|
||||
>
|
||||
{formatAsCode ? <code>{name}</code> : name}
|
||||
</Table.Td>
|
||||
<Table.Td>{value}</Table.Td>
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</Card>
|
||||
</InfoPageCard>
|
||||
))}
|
||||
</Stack>
|
||||
</InfoPageStack>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ const DataTable: FC<DataTableProps> = ({
|
|||
result.length > maxDisplayableSeries && (
|
||||
<Alert
|
||||
color="orange"
|
||||
icon={<IconAlertTriangle size={14} />}
|
||||
icon={<IconAlertTriangle />}
|
||||
title="Showing limited results"
|
||||
>
|
||||
Fetched {data.result.length} metrics, only displaying first{" "}
|
||||
|
@ -76,10 +76,7 @@ const DataTable: FC<DataTableProps> = ({
|
|||
)}
|
||||
|
||||
{!doFormat && (
|
||||
<Alert
|
||||
title="Formatting turned off"
|
||||
icon={<IconInfoCircle size={14} />}
|
||||
>
|
||||
<Alert title="Formatting turned off" icon={<IconInfoCircle />}>
|
||||
Showing more than {maxFormattableSeries} series, turning off label
|
||||
formatting to improve rendering performance.
|
||||
</Alert>
|
||||
|
@ -166,7 +163,7 @@ const DataTable: FC<DataTableProps> = ({
|
|||
<Alert
|
||||
color="red"
|
||||
title="Invalid query response"
|
||||
icon={<IconAlertTriangle size={14} />}
|
||||
icon={<IconAlertTriangle />}
|
||||
>
|
||||
Invalid result value type
|
||||
</Alert>
|
||||
|
|
|
@ -381,11 +381,7 @@ const VectorVectorBinaryExprExplainView: FC<
|
|||
</Group>
|
||||
|
||||
{numGroups > Object.keys(matchGroups).length && (
|
||||
<Alert
|
||||
color="yellow"
|
||||
mb="md"
|
||||
icon={<IconAlertTriangle size={14} />}
|
||||
>
|
||||
<Alert color="yellow" mb="md" icon={<IconAlertTriangle />}>
|
||||
Too many match groups to display, only showing{" "}
|
||||
{Object.keys(matchGroups).length} out of {numGroups} groups.
|
||||
<br />
|
||||
|
@ -397,11 +393,7 @@ const VectorVectorBinaryExprExplainView: FC<
|
|||
)}
|
||||
|
||||
{errCount > 0 && (
|
||||
<Alert
|
||||
color="yellow"
|
||||
mb="md"
|
||||
icon={<IconAlertTriangle size={14} />}
|
||||
>
|
||||
<Alert color="yellow" mb="md" icon={<IconAlertTriangle />}>
|
||||
Found matching issues in {errCount} match group
|
||||
{errCount > 1 ? "s" : ""}. See below for per-group error details.
|
||||
</Alert>
|
||||
|
@ -642,7 +634,7 @@ const VectorVectorBinaryExprExplainView: FC<
|
|||
color="red"
|
||||
mb="md"
|
||||
title="Error in match group below"
|
||||
icon={<IconAlertTriangle size={14} />}
|
||||
icon={<IconAlertTriangle />}
|
||||
>
|
||||
{explainError(node, mg, error)}
|
||||
</Alert>
|
||||
|
|
|
@ -7,7 +7,6 @@ import {
|
|||
Loader,
|
||||
Menu,
|
||||
Modal,
|
||||
rem,
|
||||
Skeleton,
|
||||
useComputedColorScheme,
|
||||
} from "@mantine/core";
|
||||
|
@ -70,6 +69,7 @@ import { useSettings } from "../../state/settingsSlice";
|
|||
import MetricsExplorer from "./MetricsExplorer/MetricsExplorer";
|
||||
import ErrorBoundary from "../../components/ErrorBoundary";
|
||||
import { useAppSelector } from "../../state/hooks";
|
||||
import { inputIconStyle, menuIconStyle } from "../../styles";
|
||||
|
||||
const promqlExtension = new PromQLExtension();
|
||||
|
||||
|
@ -224,25 +224,19 @@ const ExpressionInput: FC<ExpressionInputProps> = ({
|
|||
color="gray"
|
||||
aria-label="Show query options"
|
||||
>
|
||||
<IconDotsVertical style={{ width: "1rem", height: "1rem" }} />
|
||||
<IconDotsVertical style={inputIconStyle} />
|
||||
</ActionIcon>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Label>Query options</Menu.Label>
|
||||
<Menu.Item
|
||||
leftSection={
|
||||
<IconSearch style={{ width: rem(14), height: rem(14) }} />
|
||||
}
|
||||
leftSection={<IconSearch style={menuIconStyle} />}
|
||||
onClick={() => setShowMetricsExplorer(true)}
|
||||
>
|
||||
Explore metrics
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
leftSection={
|
||||
<IconAlignJustified
|
||||
style={{ width: rem(14), height: rem(14) }}
|
||||
/>
|
||||
}
|
||||
leftSection={<IconAlignJustified style={menuIconStyle} />}
|
||||
onClick={() => formatQuery()}
|
||||
disabled={
|
||||
isFormatting || expr === "" || expr === formatResult?.data
|
||||
|
@ -251,18 +245,14 @@ const ExpressionInput: FC<ExpressionInputProps> = ({
|
|||
Format expression
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
leftSection={
|
||||
<IconBinaryTree style={{ width: rem(14), height: rem(14) }} />
|
||||
}
|
||||
leftSection={<IconBinaryTree style={menuIconStyle} />}
|
||||
onClick={() => setShowTree(!treeShown)}
|
||||
>
|
||||
{treeShown ? "Hide" : "Show"} tree view
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
color="red"
|
||||
leftSection={
|
||||
<IconTrash style={{ width: rem(14), height: rem(14) }} />
|
||||
}
|
||||
leftSection={<IconTrash style={menuIconStyle} />}
|
||||
onClick={removePanel}
|
||||
>
|
||||
Remove query
|
||||
|
|
|
@ -131,7 +131,7 @@ const Graph: FC<GraphProps> = ({
|
|||
<Alert
|
||||
color="red"
|
||||
title="Error executing query"
|
||||
icon={<IconAlertTriangle size={14} />}
|
||||
icon={<IconAlertTriangle />}
|
||||
>
|
||||
{error.message}
|
||||
</Alert>
|
||||
|
@ -146,7 +146,7 @@ const Graph: FC<GraphProps> = ({
|
|||
|
||||
if (result.length === 0) {
|
||||
return (
|
||||
<Alert title="Empty query result" icon={<IconInfoCircle size={14} />}>
|
||||
<Alert title="Empty query result" icon={<IconInfoCircle />}>
|
||||
This query returned no data.
|
||||
</Alert>
|
||||
);
|
||||
|
@ -158,7 +158,7 @@ const Graph: FC<GraphProps> = ({
|
|||
<Alert
|
||||
color="orange"
|
||||
title="Graphing modified expression"
|
||||
icon={<IconAlertTriangle size={14} />}
|
||||
icon={<IconAlertTriangle />}
|
||||
>
|
||||
<strong>Note:</strong> Range vector selectors can't be graphed, so
|
||||
graphing the equivalent instant vector selector instead.
|
||||
|
|
|
@ -37,6 +37,7 @@ import {
|
|||
} from "@tabler/icons-react";
|
||||
import { formatNode } from "../../../promql/format";
|
||||
import classes from "./LabelsExplorer.module.css";
|
||||
import { buttonIconStyle } from "../../../styles";
|
||||
|
||||
type LabelsExplorerProps = {
|
||||
metricName: string;
|
||||
|
@ -150,7 +151,7 @@ const LabelsExplorer: FC<LabelsExplorerProps> = ({
|
|||
<Alert
|
||||
color="red"
|
||||
title="Error querying series"
|
||||
icon={<IconAlertTriangle size={14} />}
|
||||
icon={<IconAlertTriangle />}
|
||||
>
|
||||
<strong>Error:</strong> {error.message}
|
||||
</Alert>
|
||||
|
@ -177,7 +178,7 @@ const LabelsExplorer: FC<LabelsExplorerProps> = ({
|
|||
variant="light"
|
||||
size="xs"
|
||||
onClick={() => insertText(serializeNode(selector))}
|
||||
leftSection={<IconCodePlus size={18} />}
|
||||
leftSection={<IconCodePlus style={buttonIconStyle} />}
|
||||
title="Insert selector at cursor and close explorer"
|
||||
>
|
||||
Insert
|
||||
|
@ -188,7 +189,11 @@ const LabelsExplorer: FC<LabelsExplorerProps> = ({
|
|||
variant="light"
|
||||
size="xs"
|
||||
leftSection={
|
||||
copied ? <IconCheck size={18} /> : <IconCopy size={18} />
|
||||
copied ? (
|
||||
<IconCheck style={buttonIconStyle} />
|
||||
) : (
|
||||
<IconCopy style={buttonIconStyle} />
|
||||
)
|
||||
}
|
||||
onClick={copy}
|
||||
title="Copy selector to clipboard"
|
||||
|
@ -228,7 +233,7 @@ const LabelsExplorer: FC<LabelsExplorerProps> = ({
|
|||
variant="light"
|
||||
size="xs"
|
||||
onClick={hideLabelsExplorer}
|
||||
leftSection={<IconArrowLeft size={18} />}
|
||||
leftSection={<IconArrowLeft style={buttonIconStyle} />}
|
||||
>
|
||||
Back to all metrics
|
||||
</Button>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Alert, Box, Button, Stack, rem } from "@mantine/core";
|
||||
import { Alert, Box, Button, Stack } from "@mantine/core";
|
||||
import {
|
||||
IconAlertCircle,
|
||||
IconAlertTriangle,
|
||||
|
@ -17,6 +17,7 @@ import { useEffect, useState } from "react";
|
|||
import { InstantQueryResult } from "../../api/responseTypes/query";
|
||||
import { humanizeDuration } from "../../lib/formatTime";
|
||||
import { decodePanelOptionsFromURLParams } from "./urlStateEncoding";
|
||||
import { buttonIconStyle } from "../../styles";
|
||||
|
||||
export default function QueryPage() {
|
||||
const panels = useAppSelector((state) => state.queryPage.panels);
|
||||
|
@ -80,9 +81,7 @@ export default function QueryPage() {
|
|||
{metricNamesError && (
|
||||
<Alert
|
||||
mb="sm"
|
||||
icon={
|
||||
<IconAlertTriangle style={{ width: rem(14), height: rem(14) }} />
|
||||
}
|
||||
icon={<IconAlertTriangle />}
|
||||
color="red"
|
||||
title="Error fetching metrics list"
|
||||
withCloseButton
|
||||
|
@ -93,9 +92,7 @@ export default function QueryPage() {
|
|||
{timeError && (
|
||||
<Alert
|
||||
mb="sm"
|
||||
icon={
|
||||
<IconAlertTriangle style={{ width: rem(14), height: rem(14) }} />
|
||||
}
|
||||
icon={<IconAlertTriangle />}
|
||||
color="red"
|
||||
title="Error fetching server time"
|
||||
withCloseButton
|
||||
|
@ -108,7 +105,7 @@ export default function QueryPage() {
|
|||
mb="sm"
|
||||
title="Server time is out of sync"
|
||||
color="red"
|
||||
icon={<IconAlertCircle style={{ width: rem(14), height: rem(14) }} />}
|
||||
icon={<IconAlertCircle />}
|
||||
onClose={() => setTimeDelta(0)}
|
||||
>
|
||||
Detected a time difference of{" "}
|
||||
|
@ -131,7 +128,7 @@ export default function QueryPage() {
|
|||
<Button
|
||||
variant="light"
|
||||
mt="xl"
|
||||
leftSection={<IconPlus size={18} />}
|
||||
leftSection={<IconPlus style={buttonIconStyle} />}
|
||||
onClick={() => dispatch(addPanel())}
|
||||
>
|
||||
Add query
|
||||
|
|
|
@ -80,7 +80,7 @@ const TableTab: FC<TableTabProps> = ({ panelIdx, retriggerIdx, expr }) => {
|
|||
<Alert
|
||||
color="red"
|
||||
title="Error executing query"
|
||||
icon={<IconAlertTriangle size={14} />}
|
||||
icon={<IconAlertTriangle />}
|
||||
>
|
||||
{error.message}
|
||||
</Alert>
|
||||
|
@ -89,10 +89,7 @@ const TableTab: FC<TableTabProps> = ({ panelIdx, retriggerIdx, expr }) => {
|
|||
) : (
|
||||
<>
|
||||
{data.data.result.length === 0 && (
|
||||
<Alert
|
||||
title="Empty query result"
|
||||
icon={<IconInfoCircle size={14} />}
|
||||
>
|
||||
<Alert title="Empty query result" icon={<IconInfoCircle />}>
|
||||
This query returned no data.
|
||||
</Alert>
|
||||
)}
|
||||
|
@ -102,7 +99,7 @@ const TableTab: FC<TableTabProps> = ({ panelIdx, retriggerIdx, expr }) => {
|
|||
key={idx}
|
||||
color="red"
|
||||
title="Query warning"
|
||||
icon={<IconAlertTriangle size={14} />}
|
||||
icon={<IconAlertTriangle />}
|
||||
>
|
||||
{w}
|
||||
</Alert>
|
||||
|
@ -113,7 +110,7 @@ const TableTab: FC<TableTabProps> = ({ panelIdx, retriggerIdx, expr }) => {
|
|||
key={idx}
|
||||
color="yellow"
|
||||
title="Query notice"
|
||||
icon={<IconInfoCircle size={14} />}
|
||||
icon={<IconInfoCircle />}
|
||||
>
|
||||
{w}
|
||||
</Alert>
|
||||
|
|
|
@ -4,7 +4,6 @@ import {
|
|||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import ASTNode, { nodeType } from "../../promql/ast";
|
||||
|
@ -17,6 +16,7 @@ import {
|
|||
Group,
|
||||
List,
|
||||
Loader,
|
||||
rem,
|
||||
Text,
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
|
@ -37,6 +37,8 @@ const nodeIndent = 20;
|
|||
const maxLabelNames = 10;
|
||||
const maxLabelValues = 10;
|
||||
|
||||
const nodeIndicatorIconStyle = { width: rem(18), height: rem(18) };
|
||||
|
||||
type NodeState = "waiting" | "running" | "error" | "success";
|
||||
|
||||
const mergeChildStates = (states: NodeState[]): NodeState => {
|
||||
|
@ -57,7 +59,7 @@ const TreeNode: FC<{
|
|||
node: ASTNode;
|
||||
selectedNode: { id: string; node: ASTNode } | null;
|
||||
setSelectedNode: (Node: { id: string; node: ASTNode } | null) => void;
|
||||
parentRef?: React.RefObject<HTMLDivElement>;
|
||||
parentEl?: HTMLDivElement | null;
|
||||
reportNodeState?: (childIdx: number, state: NodeState) => void;
|
||||
reverse: boolean;
|
||||
// The index of this node in its parent's children.
|
||||
|
@ -66,13 +68,21 @@ const TreeNode: FC<{
|
|||
node,
|
||||
selectedNode,
|
||||
setSelectedNode,
|
||||
parentRef,
|
||||
parentEl,
|
||||
reportNodeState,
|
||||
reverse,
|
||||
childIdx,
|
||||
}) => {
|
||||
const nodeID = useId();
|
||||
const nodeRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// A normal ref won't work properly here because the ref's `current` property
|
||||
// going from `null` to defined won't trigger a re-render of the child
|
||||
// component, since it's not a React state update. So we manually have to
|
||||
// create a state update using a callback ref. See also
|
||||
// https://tkdodo.eu/blog/avoiding-use-effect-with-callback-refs
|
||||
const [nodeEl, setNodeEl] = useState<HTMLDivElement | null>(null);
|
||||
const nodeRef = useCallback((node: HTMLDivElement) => setNodeEl(node), []);
|
||||
|
||||
const [connectorStyle, setConnectorStyle] = useState<CSSProperties>({
|
||||
borderColor:
|
||||
"light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-3))",
|
||||
|
@ -94,10 +104,10 @@ const TreeNode: FC<{
|
|||
|
||||
// Select the node when it is mounted and it is the root of the tree.
|
||||
useEffect(() => {
|
||||
if (parentRef === undefined) {
|
||||
if (parentEl === undefined) {
|
||||
setSelectedNode({ id: nodeID, node: node });
|
||||
}
|
||||
}, [parentRef, setSelectedNode, nodeID, node]);
|
||||
}, [parentEl, setSelectedNode, nodeID, node]);
|
||||
|
||||
// Deselect node when node is unmounted.
|
||||
useEffect(() => {
|
||||
|
@ -170,16 +180,18 @@ const TreeNode: FC<{
|
|||
|
||||
// Update the size and position of tree connector lines based on the node's and its parent's position.
|
||||
useLayoutEffect(() => {
|
||||
if (parentRef === undefined) {
|
||||
if (parentEl === undefined) {
|
||||
// We're the root node.
|
||||
return;
|
||||
}
|
||||
|
||||
if (parentRef.current === null || nodeRef.current === null) {
|
||||
if (parentEl === null || nodeEl === null) {
|
||||
// Either of the two connected nodes hasn't been rendered yet.
|
||||
return;
|
||||
}
|
||||
const parentRect = parentRef.current.getBoundingClientRect();
|
||||
const nodeRect = nodeRef.current.getBoundingClientRect();
|
||||
|
||||
const parentRect = parentEl.getBoundingClientRect();
|
||||
const nodeRect = nodeEl.getBoundingClientRect();
|
||||
if (reverse) {
|
||||
setConnectorStyle((prevStyle) => ({
|
||||
...prevStyle,
|
||||
|
@ -199,7 +211,7 @@ const TreeNode: FC<{
|
|||
borderTopLeftRadius: undefined,
|
||||
}));
|
||||
}
|
||||
}, [parentRef, reverse, nodeRef, setConnectorStyle]);
|
||||
}, [parentEl, nodeEl, reverse, nodeRef, setConnectorStyle]);
|
||||
|
||||
// Update the node info state based on the query result.
|
||||
useEffect(() => {
|
||||
|
@ -261,7 +273,7 @@ const TreeNode: FC<{
|
|||
pos="relative"
|
||||
align="center"
|
||||
>
|
||||
{parentRef && (
|
||||
{parentEl !== undefined && (
|
||||
// Connector line between this node and its parent.
|
||||
<Box pos="absolute" display="inline-block" style={connectorStyle} />
|
||||
)}
|
||||
|
@ -288,13 +300,14 @@ const TreeNode: FC<{
|
|||
</Box>
|
||||
{mergedChildState === "waiting" ? (
|
||||
<Group c="gray">
|
||||
<IconPointFilled size={18} />
|
||||
<IconPointFilled style={nodeIndicatorIconStyle} />
|
||||
</Group>
|
||||
) : mergedChildState === "running" ? (
|
||||
<Loader size={14} color="gray" type="dots" />
|
||||
) : mergedChildState === "error" ? (
|
||||
<Group c="orange.7" gap={5} fz="xs" wrap="nowrap">
|
||||
<IconPointFilled size={18} /> Blocked on child query error
|
||||
<IconPointFilled style={nodeIndicatorIconStyle} /> Blocked on child
|
||||
query error
|
||||
</Group>
|
||||
) : isFetching ? (
|
||||
<Loader size={14} color="gray" />
|
||||
|
@ -305,7 +318,7 @@ const TreeNode: FC<{
|
|||
style={{ flexShrink: 0 }}
|
||||
className={classes.errorText}
|
||||
>
|
||||
<IconPointFilled size={18} />
|
||||
<IconPointFilled style={nodeIndicatorIconStyle} />
|
||||
<Text fz="xs">
|
||||
<strong>Error executing query:</strong> {error.message}
|
||||
</Text>
|
||||
|
@ -387,7 +400,7 @@ const TreeNode: FC<{
|
|||
node={children[0]}
|
||||
selectedNode={selectedNode}
|
||||
setSelectedNode={setSelectedNode}
|
||||
parentRef={nodeRef}
|
||||
parentEl={nodeEl}
|
||||
reverse={true}
|
||||
childIdx={0}
|
||||
reportNodeState={childReportNodeState}
|
||||
|
@ -399,7 +412,7 @@ const TreeNode: FC<{
|
|||
node={children[1]}
|
||||
selectedNode={selectedNode}
|
||||
setSelectedNode={setSelectedNode}
|
||||
parentRef={nodeRef}
|
||||
parentEl={nodeEl}
|
||||
reverse={false}
|
||||
childIdx={1}
|
||||
reportNodeState={childReportNodeState}
|
||||
|
@ -418,7 +431,7 @@ const TreeNode: FC<{
|
|||
node={child}
|
||||
selectedNode={selectedNode}
|
||||
setSelectedNode={setSelectedNode}
|
||||
parentRef={nodeRef}
|
||||
parentEl={nodeEl}
|
||||
reverse={false}
|
||||
childIdx={idx}
|
||||
reportNodeState={childReportNodeState}
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
} from "../../state/serviceDiscoveryPageSlice";
|
||||
import { StateMultiSelect } from "../../components/StateMultiSelect";
|
||||
import badgeClasses from "../../Badge.module.css";
|
||||
import { expandIconStyle, inputIconStyle } from "../../styles";
|
||||
|
||||
export const targetPoolDisplayLimit = 20;
|
||||
|
||||
|
@ -98,7 +99,7 @@ export default function ServiceDiscoveryPage() {
|
|||
/>
|
||||
<TextInput
|
||||
flex={1}
|
||||
leftSection={<IconSearch size={14} />}
|
||||
leftSection={<IconSearch style={inputIconStyle} />}
|
||||
placeholder="Filter by labels"
|
||||
value={searchFilter || ""}
|
||||
onChange={(event) => setSearchFilter(event.currentTarget.value)}
|
||||
|
@ -118,9 +119,9 @@ export default function ServiceDiscoveryPage() {
|
|||
}
|
||||
>
|
||||
{collapsedPools.length > 0 ? (
|
||||
<IconLayoutNavbarExpand size={16} />
|
||||
<IconLayoutNavbarExpand style={expandIconStyle} />
|
||||
) : (
|
||||
<IconLayoutNavbarCollapse size={16} />
|
||||
<IconLayoutNavbarCollapse style={expandIconStyle} />
|
||||
)}
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
|
|
|
@ -204,10 +204,7 @@ const ScrapePoolList: FC<ScrapePoolListProp> = ({
|
|||
return (
|
||||
<Stack>
|
||||
{allPoolNames.length === 0 ? (
|
||||
<Alert
|
||||
title="No scrape pools found"
|
||||
icon={<IconInfoCircle size={14} />}
|
||||
>
|
||||
<Alert title="No scrape pools found" icon={<IconInfoCircle />}>
|
||||
No scrape pools found.
|
||||
</Alert>
|
||||
) : (
|
||||
|
@ -215,7 +212,7 @@ const ScrapePoolList: FC<ScrapePoolListProp> = ({
|
|||
allPoolNames.length !== shownPoolNames.length && (
|
||||
<Alert
|
||||
title="Hiding pools with no matching targets"
|
||||
icon={<IconInfoCircle size={14} />}
|
||||
icon={<IconInfoCircle />}
|
||||
>
|
||||
Hiding {allPoolNames.length - shownPoolNames.length} empty pools due
|
||||
to filters or no targets.
|
||||
|
@ -228,7 +225,7 @@ const ScrapePoolList: FC<ScrapePoolListProp> = ({
|
|||
{showLimitAlert && (
|
||||
<Alert
|
||||
title="Found many pools, showing only one"
|
||||
icon={<IconInfoCircle size={14} />}
|
||||
icon={<IconInfoCircle />}
|
||||
withCloseButton
|
||||
onClose={() => dispatch(setShowLimitAlert(false))}
|
||||
>
|
||||
|
|
|
@ -39,6 +39,7 @@ import TargetLabels from "./TargetLabels";
|
|||
import { useDebouncedValue } from "@mantine/hooks";
|
||||
import { targetPoolDisplayLimit } from "./TargetsPage";
|
||||
import { BooleanParam, useQueryParam, withDefault } from "use-query-params";
|
||||
import { badgeIconStyle } from "../../styles";
|
||||
|
||||
type ScrapePool = {
|
||||
targets: Target[];
|
||||
|
@ -53,7 +54,7 @@ type ScrapePools = {
|
|||
};
|
||||
|
||||
const poolPanelHealthClass = (pool: ScrapePool) =>
|
||||
pool.count > 0 && pool.downCount === pool.count
|
||||
pool.count > 1 && pool.downCount === pool.count
|
||||
? panelClasses.panelHealthErr
|
||||
: pool.downCount >= 1
|
||||
? panelClasses.panelHealthWarn
|
||||
|
@ -194,10 +195,7 @@ const ScrapePoolList: FC<ScrapePoolListProp> = ({
|
|||
return (
|
||||
<Stack>
|
||||
{allPoolNames.length === 0 ? (
|
||||
<Alert
|
||||
title="No scrape pools found"
|
||||
icon={<IconInfoCircle size={14} />}
|
||||
>
|
||||
<Alert title="No scrape pools found" icon={<IconInfoCircle />}>
|
||||
No scrape pools found.
|
||||
</Alert>
|
||||
) : (
|
||||
|
@ -205,7 +203,7 @@ const ScrapePoolList: FC<ScrapePoolListProp> = ({
|
|||
allPoolNames.length !== shownPoolNames.length && (
|
||||
<Alert
|
||||
title="Hiding pools with no matching targets"
|
||||
icon={<IconInfoCircle size={14} />}
|
||||
icon={<IconInfoCircle />}
|
||||
>
|
||||
Hiding {allPoolNames.length - shownPoolNames.length} empty pools due
|
||||
to filters or no targets.
|
||||
|
@ -218,7 +216,7 @@ const ScrapePoolList: FC<ScrapePoolListProp> = ({
|
|||
{showLimitAlert && (
|
||||
<Alert
|
||||
title="Found many pools, showing only one"
|
||||
icon={<IconInfoCircle size={14} />}
|
||||
icon={<IconInfoCircle />}
|
||||
withCloseButton
|
||||
onClose={() => dispatch(setShowLimitAlert(false))}
|
||||
>
|
||||
|
@ -355,7 +353,9 @@ const ScrapePoolList: FC<ScrapePoolListProp> = ({
|
|||
styles={{
|
||||
label: { textTransform: "none" },
|
||||
}}
|
||||
leftSection={<IconRefresh size={12} />}
|
||||
leftSection={
|
||||
<IconRefresh style={badgeIconStyle} />
|
||||
}
|
||||
>
|
||||
{humanizeDurationRelative(
|
||||
target.lastScrape,
|
||||
|
@ -376,7 +376,9 @@ const ScrapePoolList: FC<ScrapePoolListProp> = ({
|
|||
label: { textTransform: "none" },
|
||||
}}
|
||||
leftSection={
|
||||
<IconHourglass size={12} />
|
||||
<IconHourglass
|
||||
style={badgeIconStyle}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{humanizeDuration(
|
||||
|
@ -401,7 +403,7 @@ const ScrapePoolList: FC<ScrapePoolListProp> = ({
|
|||
<Alert
|
||||
color="red"
|
||||
mb="sm"
|
||||
icon={<IconAlertTriangle size={14} />}
|
||||
icon={<IconAlertTriangle />}
|
||||
>
|
||||
<strong>Error scraping target:</strong>{" "}
|
||||
{target.lastError}
|
||||
|
|
|
@ -4,6 +4,7 @@ import { LabelBadges } from "../../components/LabelBadges";
|
|||
import { ActionIcon, Collapse, Group, Stack, Text } from "@mantine/core";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import { IconChevronDown, IconChevronUp } from "@tabler/icons-react";
|
||||
import { actionIconStyle } from "../../styles";
|
||||
|
||||
type TargetLabelsProps = {
|
||||
labels: Labels;
|
||||
|
@ -26,12 +27,9 @@ const TargetLabels: FC<TargetLabelsProps> = ({ discoveredLabels, labels }) => {
|
|||
title={`${showDiscovered ? "Hide" : "Show"} discovered (pre-relabeling) labels`}
|
||||
>
|
||||
{showDiscovered ? (
|
||||
<IconChevronUp
|
||||
style={{ width: "70%", height: "70%" }}
|
||||
stroke={1.5}
|
||||
/>
|
||||
<IconChevronUp style={actionIconStyle} />
|
||||
) : (
|
||||
<IconChevronDown style={{ width: "70%", height: "70%" }} />
|
||||
<IconChevronDown style={actionIconStyle} />
|
||||
)}
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
|
|
|
@ -29,6 +29,7 @@ import ErrorBoundary from "../../components/ErrorBoundary";
|
|||
import ScrapePoolList from "./ScrapePoolsList";
|
||||
import { useSuspenseAPIQuery } from "../../api/api";
|
||||
import { ScrapePoolsResult } from "../../api/responseTypes/scrapePools";
|
||||
import { expandIconStyle, inputIconStyle } from "../../styles";
|
||||
|
||||
export const targetPoolDisplayLimit = 20;
|
||||
|
||||
|
@ -101,7 +102,7 @@ export default function TargetsPage() {
|
|||
/>
|
||||
<TextInput
|
||||
flex={1}
|
||||
leftSection={<IconSearch size={14} />}
|
||||
leftSection={<IconSearch style={inputIconStyle} />}
|
||||
placeholder="Filter by endpoint or labels"
|
||||
value={searchFilter || ""}
|
||||
onChange={(event) =>
|
||||
|
@ -123,9 +124,9 @@ export default function TargetsPage() {
|
|||
}
|
||||
>
|
||||
{collapsedPools.length > 0 ? (
|
||||
<IconLayoutNavbarExpand size={16} />
|
||||
<IconLayoutNavbarExpand style={expandIconStyle} />
|
||||
) : (
|
||||
<IconLayoutNavbarCollapse size={16} />
|
||||
<IconLayoutNavbarCollapse style={expandIconStyle} />
|
||||
)}
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
|
|
15
web/ui/mantine-ui/src/styles.ts
Normal file
15
web/ui/mantine-ui/src/styles.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { em, rem } from "@mantine/core";
|
||||
|
||||
export const navIconStyle = { width: rem(16), height: rem(16) };
|
||||
export const menuIconStyle = { width: rem(14), height: rem(14) };
|
||||
export const badgeIconStyle = { width: em(17), height: em(17) };
|
||||
export const actionIconStyle = { width: "70%", height: "70%" };
|
||||
export const inputIconStyle = { width: em(16), height: em(16) };
|
||||
export const buttonIconStyle = { width: em(20), height: em(20) };
|
||||
export const infoPageCardTitleIconStyle = { width: em(17.5), height: em(17.5) };
|
||||
export const expandIconStyle = { width: em(16), height: em(16) };
|
||||
export const themeSwitcherIconStyle = {
|
||||
width: rem(20),
|
||||
height: rem(20),
|
||||
display: "block",
|
||||
};
|
Loading…
Reference in a new issue