Various query page improvements

Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
Julius Volz 2024-04-03 14:45:35 +02:00
parent 684698b827
commit 2be782df77
6 changed files with 69 additions and 61 deletions

View file

@ -9,6 +9,11 @@ import {
import SeriesName from "./SeriesName"; import SeriesName from "./SeriesName";
import { useAPIQuery } from "../../api/api"; import { useAPIQuery } from "../../api/api";
import classes from "./DataTable.module.css"; import classes from "./DataTable.module.css";
import dayjs from "dayjs";
import timezone from "dayjs/plugin/timezone";
import { useAppSelector } from "../../state/hooks";
import { formatTimestamp } from "../../lib/formatTime";
dayjs.extend(timezone);
const maxFormattableSeries = 1000; const maxFormattableSeries = 1000;
const maxDisplayableSeries = 10000; const maxDisplayableSeries = 10000;
@ -44,6 +49,8 @@ const DataTable: FC<DataTableProps> = ({ expr, evalTime, retriggerIdx }) => {
expr !== "" && refetch(); expr !== "" && refetch();
}, [retriggerIdx, refetch, expr, evalTime]); }, [retriggerIdx, refetch, expr, evalTime]);
const useLocalTime = useAppSelector((state) => state.settings.useLocalTime);
// Show a skeleton only on the first load, not on subsequent ones. // Show a skeleton only on the first load, not on subsequent ones.
if (isLoading) { if (isLoading) {
return ( return (
@ -118,7 +125,12 @@ const DataTable: FC<DataTableProps> = ({ expr, evalTime, retriggerIdx }) => {
{s.values && {s.values &&
s.values.map((v, idx) => ( s.values.map((v, idx) => (
<div key={idx}> <div key={idx}>
{v[1]} @ {v[0]} {v[1]} @{" "}
{
<span title={formatTimestamp(v[0], useLocalTime)}>
{v[0]}
</span>
}
</div> </div>
))} ))}
</Table.Td> </Table.Td>

View file

@ -189,6 +189,8 @@ const ExpressionInput: FC<ExpressionInputProps> = ({
return ( return (
<Group align="flex-start" wrap="nowrap" gap="xs"> <Group align="flex-start" wrap="nowrap" gap="xs">
{/* TODO: For wrapped long lines, the input grows in width more and more, the
longer the line is. Figure out why and fix it. */}
<InputBase<any> <InputBase<any>
leftSection={ leftSection={
isFormatting ? <Loader size="xs" color="gray.5" /> : <IconTerminal /> isFormatting ? <Loader size="xs" color="gray.5" /> : <IconTerminal />

View file

@ -46,47 +46,46 @@ export default function QueryPage() {
return ( return (
<Box mt="xs"> <Box mt="xs">
<Stack gap="sm"> {metricNamesError && (
{metricNamesError && ( <Alert
<Alert mb="sm"
icon={ icon={
<IconAlertTriangle style={{ width: rem(14), height: rem(14) }} /> <IconAlertTriangle style={{ width: rem(14), height: rem(14) }} />
} }
color="red" color="red"
title="Error fetching metrics list" title="Error fetching metrics list"
withCloseButton withCloseButton
> >
Unable to fetch list of metric names: {metricNamesError.message} Unable to fetch list of metric names: {metricNamesError.message}
</Alert> </Alert>
)} )}
{timeError && ( {timeError && (
<Alert <Alert
icon={ mb="sm"
<IconAlertTriangle style={{ width: rem(14), height: rem(14) }} /> icon={
} <IconAlertTriangle style={{ width: rem(14), height: rem(14) }} />
color="red" }
title="Error fetching server time" color="red"
withCloseButton title="Error fetching server time"
> withCloseButton
{timeError.message} >
</Alert> {timeError.message}
)} </Alert>
{timeDelta > 30 && ( )}
<Alert {timeDelta > 30 && (
title="Server time is out of sync" <Alert
color="red" mb="sm"
icon={ title="Server time is out of sync"
<IconAlertCircle style={{ width: rem(14), height: rem(14) }} /> color="red"
} icon={<IconAlertCircle style={{ width: rem(14), height: rem(14) }} />}
onClose={() => setTimeDelta(0)} onClose={() => setTimeDelta(0)}
> >
Detected a time difference of{" "} Detected a time difference of{" "}
<strong>{humanizeDuration(timeDelta * 1000)}</strong> between your <strong>{humanizeDuration(timeDelta * 1000)}</strong> between your
browser and the server. You may see unexpected time-shifted query browser and the server. You may see unexpected time-shifted query
results due to the time drift. results due to the time drift.
</Alert> </Alert>
)} )}
</Stack>
<Stack gap="xl"> <Stack gap="xl">
{panels.map((p, idx) => ( {panels.map((p, idx) => (

View file

@ -214,18 +214,6 @@ const QueryPanel: FC<PanelProps> = ({ idx, metricNames }) => {
/> />
</Tabs.Panel> </Tabs.Panel>
</Tabs> </Tabs>
{/* Link button to remove this panel. */}
{/* <Group justify="right">
<Button
variant="subtle"
size="sm"
fw={500}
// color="red"
onClick={() => dispatch(removePanel(idx))}
>
Remove query
</Button>
</Group> */}
</Stack> </Stack>
); );
}; };

View file

@ -1,7 +1,10 @@
import { FC, useState } from "react"; import { FC, useState } from "react";
import { ActionIcon, Group, Input } from "@mantine/core"; import { ActionIcon, Group, Input } from "@mantine/core";
import { IconMinus, IconPlus } from "@tabler/icons-react"; import { IconMinus, IconPlus } from "@tabler/icons-react";
import { formatDuration, parseDuration } from "../../lib/formatTime"; import {
formatPrometheusDuration,
parsePrometheusDuration,
} from "../../lib/formatTime";
interface RangeInputProps { interface RangeInputProps {
range: number; range: number;
@ -36,12 +39,14 @@ const rangeSteps = [
const RangeInput: FC<RangeInputProps> = ({ range, onChangeRange }) => { const RangeInput: FC<RangeInputProps> = ({ range, onChangeRange }) => {
// TODO: Make sure that when "range" changes externally (like via the URL), // TODO: Make sure that when "range" changes externally (like via the URL),
// the input is updated, either via useEffect() or some better architecture. // the input is updated, either via useEffect() or some better architecture.
const [rangeInput, setRangeInput] = useState<string>(formatDuration(range)); const [rangeInput, setRangeInput] = useState<string>(
formatPrometheusDuration(range)
);
const onChangeRangeInput = (rangeText: string): void => { const onChangeRangeInput = (rangeText: string): void => {
const newRange = parseDuration(rangeText); const newRange = parsePrometheusDuration(rangeText);
if (newRange === null) { if (newRange === null) {
setRangeInput(formatDuration(range)); setRangeInput(formatPrometheusDuration(range));
} else { } else {
onChangeRange(newRange); onChangeRange(newRange);
} }
@ -50,7 +55,7 @@ const RangeInput: FC<RangeInputProps> = ({ range, onChangeRange }) => {
const increaseRange = (): void => { const increaseRange = (): void => {
for (const step of rangeSteps) { for (const step of rangeSteps) {
if (range < step) { if (range < step) {
setRangeInput(formatDuration(step)); setRangeInput(formatPrometheusDuration(step));
onChangeRange(step); onChangeRange(step);
return; return;
} }
@ -60,7 +65,7 @@ const RangeInput: FC<RangeInputProps> = ({ range, onChangeRange }) => {
const decreaseRange = (): void => { const decreaseRange = (): void => {
for (const step of rangeSteps.slice().reverse()) { for (const step of rangeSteps.slice().reverse()) {
if (range > step) { if (range > step) {
setRangeInput(formatDuration(step)); setRangeInput(formatPrometheusDuration(step));
onChangeRange(step); onChangeRange(step);
return; return;
} }

View file

@ -2,6 +2,7 @@ import { Group, ActionIcon, CloseButton } from "@mantine/core";
import { DatesProvider, DateTimePicker } from "@mantine/dates"; import { DatesProvider, DateTimePicker } from "@mantine/dates";
import { IconChevronLeft, IconChevronRight } from "@tabler/icons-react"; import { IconChevronLeft, IconChevronRight } from "@tabler/icons-react";
import { FC } from "react"; import { FC } from "react";
import { useAppSelector } from "../../state/hooks";
interface TimeInputProps { interface TimeInputProps {
time: number | null; // Timestamp in milliseconds. time: number | null; // Timestamp in milliseconds.
@ -19,10 +20,11 @@ const TimeInput: FC<TimeInputProps> = ({
onChangeTime, onChangeTime,
}) => { }) => {
const baseTime = () => (time !== null ? time : Date.now().valueOf()); const baseTime = () => (time !== null ? time : Date.now().valueOf());
const useLocalTime = useAppSelector((state) => state.settings.useLocalTime);
return ( return (
<Group gap={5}> <Group gap={5}>
<DatesProvider settings={{ timezone: "UTC" }}> <DatesProvider settings={{ timezone: useLocalTime ? undefined : "UTC" }}>
<DateTimePicker <DateTimePicker
w={230} w={230}
valueFormat="YYYY-MM-DD HH:mm:ss" valueFormat="YYYY-MM-DD HH:mm:ss"