Merge branch 'prometheus:main' into patch-impl2

This commit is contained in:
Vanshika 2024-09-16 21:54:00 +05:30 committed by GitHub
commit b4513c9fac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 338 additions and 305 deletions

View file

@ -30,6 +30,11 @@ include Makefile.common
DOCKER_IMAGE_NAME ?= prometheus
# Only build UI if PREBUILT_ASSETS_STATIC_DIR is not set
ifdef PREBUILT_ASSETS_STATIC_DIR
SKIP_UI_BUILD = true
endif
.PHONY: update-npm-deps
update-npm-deps:
@echo ">> updating npm dependencies"
@ -75,8 +80,24 @@ ui-lint:
cd $(UI_PATH)/react-app && npm run lint
.PHONY: assets
ifndef SKIP_UI_BUILD
assets: ui-install ui-build
.PHONY: npm_licenses
npm_licenses: ui-install
@echo ">> bundling npm licenses"
rm -f $(REACT_APP_NPM_LICENSES_TARBALL) npm_licenses
ln -s . npm_licenses
find npm_licenses/$(UI_NODE_MODULES_PATH) -iname "license*" | tar cfj $(REACT_APP_NPM_LICENSES_TARBALL) --files-from=-
rm -f npm_licenses
else
assets:
@echo '>> skipping assets build, pre-built assets provided'
npm_licenses:
@echo '>> skipping assets npm licenses, pre-built assets provided'
endif
.PHONY: assets-compress
assets-compress: assets
@echo '>> compressing assets'
@ -125,14 +146,6 @@ else
test: check-generated-parser common-test ui-build-module ui-test ui-lint check-go-mod-version
endif
.PHONY: npm_licenses
npm_licenses: ui-install
@echo ">> bundling npm licenses"
rm -f $(REACT_APP_NPM_LICENSES_TARBALL) npm_licenses
ln -s . npm_licenses
find npm_licenses/$(UI_NODE_MODULES_PATH) -iname "license*" | tar cfj $(REACT_APP_NPM_LICENSES_TARBALL) --files-from=-
rm -f npm_licenses
.PHONY: tarball
tarball: npm_licenses common-tarball

View file

@ -471,7 +471,7 @@ func main() {
a.Flag("scrape.discovery-reload-interval", "Interval used by scrape manager to throttle target groups updates.").
Hidden().Default("5s").SetValue(&cfg.scrape.DiscoveryReloadInterval)
a.Flag("enable-feature", "Comma separated feature names to enable. Valid options: auto-gomemlimit, exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-per-step-stats, promql-experimental-functions, extra-scrape-metrics, auto-gomaxprocs, no-default-scrape-port, native-histograms, otlp-write-receiver, created-timestamp-zero-ingestion, concurrent-rule-eval. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details.").
a.Flag("enable-feature", "Comma separated feature names to enable. Valid options: auto-gomemlimit, exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-per-step-stats, promql-experimental-functions, extra-scrape-metrics, auto-gomaxprocs, no-default-scrape-port, native-histograms, otlp-write-receiver, created-timestamp-zero-ingestion, concurrent-rule-eval, delayed-compaction, old-ui. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details.").
Default("").StringsVar(&cfg.featureList)
a.Flag("agent", "Run Prometheus in 'Agent mode'.").BoolVar(&agentMode)

View file

@ -56,7 +56,7 @@ The Prometheus monitoring server
| <code class="text-nowrap">--query.timeout</code> | Maximum time a query may take before being aborted. Use with server mode only. | `2m` |
| <code class="text-nowrap">--query.max-concurrency</code> | Maximum number of queries executed concurrently. Use with server mode only. | `20` |
| <code class="text-nowrap">--query.max-samples</code> | Maximum number of samples a single query can load into memory. Note that queries will fail if they try to load more samples than this into memory, so this also limits the number of samples a query can return. Use with server mode only. | `50000000` |
| <code class="text-nowrap">--enable-feature</code> <code class="text-nowrap">...<code class="text-nowrap"> | Comma separated feature names to enable. Valid options: auto-gomemlimit, exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-per-step-stats, promql-experimental-functions, extra-scrape-metrics, auto-gomaxprocs, no-default-scrape-port, native-histograms, otlp-write-receiver, created-timestamp-zero-ingestion, concurrent-rule-eval. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details. | |
| <code class="text-nowrap">--enable-feature</code> <code class="text-nowrap">...<code class="text-nowrap"> | Comma separated feature names to enable. Valid options: auto-gomemlimit, exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-per-step-stats, promql-experimental-functions, extra-scrape-metrics, auto-gomaxprocs, no-default-scrape-port, native-histograms, otlp-write-receiver, created-timestamp-zero-ingestion, concurrent-rule-eval, delayed-compaction, old-ui. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details. | |
| <code class="text-nowrap">--agent</code> | Run Prometheus in 'Agent mode'. | |
| <code class="text-nowrap">--log.level</code> | Only log messages with the given severity or above. One of: [debug, info, warn, error] | `info` |
| <code class="text-nowrap">--log.format</code> | Output format of log messages. One of: [logfmt, json] | `logfmt` |

View file

@ -47,16 +47,6 @@ When enabled, for each instance scrape, Prometheus stores a sample in the follow
to find out how close they are to reaching the limit with `scrape_samples_post_metric_relabeling / scrape_sample_limit`. Note that `scrape_sample_limit` can be zero if there is no limit configured, which means that the query above can return `+Inf` for targets with no limit (as we divide by zero). If you want to query only for targets that do have a sample limit use this query: `scrape_samples_post_metric_relabeling / (scrape_sample_limit > 0)`.
- `scrape_body_size_bytes`. The uncompressed size of the most recent scrape response, if successful. Scrapes failing because `body_size_limit` is exceeded report `-1`, other scrape failures report `0`.
## Prometheus agent
`--enable-feature=agent`
When enabled, Prometheus runs in agent mode. The agent mode is limited to
discovery, scrape and remote write.
This is useful when you do not need to query the Prometheus data locally, but
only from a central [remote endpoint](https://prometheus.io/docs/operating/integrations/#remote-endpoints-and-storage).
## Per-step stats
`--enable-feature=promql-per-step-stats`

2
go.mod
View file

@ -27,7 +27,7 @@ require (
github.com/go-kit/log v0.2.1
github.com/go-logfmt/logfmt v0.6.0
github.com/go-openapi/strfmt v0.23.0
github.com/go-zookeeper/zk v1.0.3
github.com/go-zookeeper/zk v1.0.4
github.com/gogo/protobuf v1.3.2
github.com/golang/snappy v0.0.4
github.com/google/go-cmp v0.6.0

4
go.sum
View file

@ -238,8 +238,8 @@ github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg=
github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
github.com/go-zookeeper/zk v1.0.4 h1:DPzxraQx7OrPyXq2phlGlNSIyWEsAox0RJmjTseMV6I=
github.com/go-zookeeper/zk v1.0.4/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=

View file

@ -4,6 +4,12 @@
set -euo pipefail
export STATIC_DIR=static
PREBUILT_ASSETS_STATIC_DIR=${PREBUILT_ASSETS_STATIC_DIR:-}
if [ -n "$PREBUILT_ASSETS_STATIC_DIR" ]; then
STATIC_DIR=$(realpath $PREBUILT_ASSETS_STATIC_DIR)
fi
cd web/ui
cp embed.go.tmpl embed.go
@ -11,6 +17,19 @@ GZIP_OPTS="-fk"
# gzip option '-k' may not always exist in the latest gzip available on different distros.
if ! gzip -k -h &>/dev/null; then GZIP_OPTS="-f"; fi
mkdir -p static
find static -type f -name '*.gz' -delete
find static -type f -exec gzip $GZIP_OPTS '{}' \; -print0 | xargs -0 -I % echo %.gz | sort | xargs echo //go:embed >> embed.go
# Compress files from the prebuilt static directory and replicate the structure in the current static directory
find "${STATIC_DIR}" -type f ! -name '*.gz' -exec bash -c '
for file; do
dest="${file#${STATIC_DIR}}"
mkdir -p "static/$(dirname "$dest")"
gzip '"$GZIP_OPTS"' "$file" -c > "static/${dest}.gz"
done
' bash {} +
# Append the paths of gzipped files to embed.go
find static -type f -name '*.gz' -print0 | sort -z | xargs -0 echo //go:embed >> embed.go
echo var EmbedFS embed.FS >> embed.go

View file

@ -1484,13 +1484,13 @@ func (s *memSeries) mmapCurrentOOOHeadChunk(chunkDiskMapper *chunks.ChunkDiskMap
handleChunkWriteError(err)
return nil
}
chunkRefs := make([]chunks.ChunkDiskMapperRef, 0, 1)
chunkRefs := make([]chunks.ChunkDiskMapperRef, 0, len(chks))
for _, memchunk := range chks {
if len(s.ooo.oooMmappedChunks) >= (oooChunkIDMask - 1) {
level.Error(logger).Log("msg", "Too many OOO chunks, dropping data", "series", s.lset.String())
break
}
chunkRef := chunkDiskMapper.WriteChunk(s.ref, s.ooo.oooHeadChunk.minTime, s.ooo.oooHeadChunk.maxTime, memchunk.chunk, true, handleChunkWriteError)
chunkRef := chunkDiskMapper.WriteChunk(s.ref, memchunk.minTime, memchunk.maxTime, memchunk.chunk, true, handleChunkWriteError)
chunkRefs = append(chunkRefs, chunkRef)
s.ooo.oooMmappedChunks = append(s.ooo.oooMmappedChunks, &mmappedChunk{
ref: chunkRef,

View file

@ -111,7 +111,7 @@ func getOOOSeriesChunks(s *memSeries, mint, maxt int64, lastGarbageCollectedMmap
return nil
}
for _, chk := range chks {
addChunk(c.minTime, c.maxTime, ref, chk.chunk)
addChunk(chk.minTime, chk.maxTime, ref, chk.chunk)
}
} else {
var emptyChunk chunkenc.Chunk

View file

@ -1,30 +0,0 @@
# React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
- Configure the top-level `parserOptions` property like this:
```js
export default {
// other rules...
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: ['./tsconfig.json', './tsconfig.node.json'],
tsconfigRootDir: __dirname,
},
}
```
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list

View file

@ -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>
</>
);

View file

@ -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"

View 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;

View 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;

View file

@ -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>

View file

@ -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>

View file

@ -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([])} />

View file

@ -0,0 +1,4 @@
.mantine-Badge-label {
overflow: unset;
text-overflow: unset;
}

View file

@ -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>
);
};

View file

@ -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>
);
}

View file

@ -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>

View file

@ -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>
);
}

View file

@ -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>

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -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>

View file

@ -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>

View file

@ -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

View file

@ -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.

View file

@ -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>

View file

@ -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

View file

@ -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>

View file

@ -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}

View file

@ -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>

View file

@ -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))}
>

View file

@ -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[];
@ -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}

View file

@ -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>

View file

@ -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>

View file

@ -8,7 +8,7 @@ import {
vectorMatchCardinality,
VectorMatching,
} from "./ast";
import { isComparisonOperator } from "./utils";
import { isComparisonOperator, isSetOperator } from "./utils";
// We use a special (otherwise invalid) sample value to indicate that
// a sample has been filtered away by a comparison operator.
@ -340,6 +340,11 @@ export const computeVectorVectorBinOp = (
// Annotate the match groups with errors (if any) and populate the results.
Object.values(groups).forEach((mg) => {
// Do not populate results for set operators.
if (isSetOperator(op)) {
return;
}
if (matching.card === vectorMatchCardinality.oneToOne) {
if (mg.lhs.length > 1 && mg.rhs.length > 1) {
mg.error = { type: MatchErrorType.multipleMatchesOnBothSides };

View 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",
};