Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 95 additions & 11 deletions pkg/ui/react-app/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pkg/ui/react-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"eject": "react-scripts eject",
"lint:ci": "eslint --quiet \"src/**/*.{ts,tsx}\"",
"lint": "eslint --fix \"src/**/*.{ts,tsx}\"",
"prettier-format": "prettier 'src/**/*.ts' --write"
"prettier-format": "prettier 'src/**/*.{ts,tsx}' --write"
},
"prettier": {
"singleQuote": true,
Expand Down
18 changes: 12 additions & 6 deletions pkg/ui/react-app/src/thanos/pages/blocks/BlockFilterCompaction.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { FC, ChangeEvent } from 'react';
import Checkbox from '../../../components/Checkbox';
import { Input } from 'reactstrap';
import styles from './blocks.module.css';

interface BlockFilterCompactionProps {
id: string;
Expand All @@ -19,16 +18,23 @@ export const BlockFilterCompaction: FC<BlockFilterCompactionProps> = ({
defaultValue,
}) => {
return (
<div className={styles.blockFilter} style={{ marginLeft: '24px' }}>
<Checkbox style={{ marginRight: '4px' }} id={id} defaultChecked={defaultChecked} onChange={onChangeCheckbox} />
<p style={{ marginRight: '4px' }}>Filter by compaction level</p>
<>
<Checkbox
id={id}
defaultChecked={defaultChecked}
onChange={onChangeCheckbox}
wrapperStyles={{ marginBottom: 0, display: 'inline-flex', alignItems: 'center' }}
>
Filter by compaction level
</Checkbox>
<Input
type="number"
style={{ width: '80px', marginBottom: '1rem' }}
style={{ width: '80px', marginLeft: '10px' }}
onChange={onChangeInput}
defaultValue={defaultValue}
min={0}
bsSize="sm"
/>
</div>
</>
);
};
144 changes: 121 additions & 23 deletions pkg/ui/react-app/src/thanos/pages/blocks/Blocks.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { ChangeEvent, FC, useMemo, useState } from 'react';
import { RouteComponentProps } from '@reach/router';
import { UncontrolledAlert } from 'reactstrap';
import { useQueryParams, withDefault, NumberParam, StringParam, BooleanParam } from 'use-query-params';
import { Button, Form, Input, InputGroup, InputGroupAddon, UncontrolledAlert } from 'reactstrap';
import { BooleanParam, NumberParam, StringParam, useQueryParams, withDefault } from 'use-query-params';
import { withStatusIndicator } from '../../../components/withStatusIndicator';
import { useFetch } from '../../../hooks/useFetch';
import PathPrefixProps from '../../../types/PathPrefixProps';
Expand All @@ -10,11 +10,19 @@ import { SourceView } from './SourceView';
import { BlockDetails } from './BlockDetails';
import { BlockSearchInput } from './BlockSearchInput';
import { BlockFilterCompaction } from './BlockFilterCompaction';
import { sortBlocks, getBlockByUlid, getFilteredBlockPools } from './helpers';
import { getBlockByUlid, getFilteredBlockPools, sortBlocks } from './helpers';
import styles from './blocks.module.css';
import TimeRange from './TimeRange';
import Checkbox from '../../../components/Checkbox';
import { FlagMap } from '../../../pages/flags/Flags';
import TimeInput from '../../../pages/graph/TimeInput';
import { formatDuration, parseDuration } from '../../../utils';
import { faMinus, faPlus } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

const daySeconds = 24 * 60 * 60;

const rangeSteps = [1, 7, 30, 180, 360, 720, 1440].map((s) => s * daySeconds * 1000);

export interface BlockListProps {
blocks: Block[];
Expand All @@ -30,26 +38,22 @@ export const BlocksContent: FC<{ data: BlockListProps } & PathPrefixProps> = ({
const { blocks, label, err } = data;

const [gridMinTime, gridMaxTime] = useMemo(() => {
if (!err && blocks.length > 0) {
let gridMinTime = blocks[0].minTime;
let gridMaxTime = blocks[0].maxTime;
blocks.forEach((block) => {
if (block.minTime < gridMinTime) {
gridMinTime = block.minTime;
}
if (block.maxTime > gridMaxTime) {
gridMaxTime = block.maxTime;
}
});
return [gridMinTime, gridMaxTime];
}
return [0, 0];
}, [blocks, err]);
if (!blocks || blocks.length === 0) return [0, 0];

let gridMinTime = blocks[0].minTime;
let gridMaxTime = blocks[0].maxTime;

blocks.forEach((block) => {
gridMinTime = Math.min(gridMinTime, block.minTime);
gridMaxTime = Math.max(gridMaxTime, block.maxTime);
});
return [gridMinTime, gridMaxTime];
}, [blocks]);

const [
{
'min-time': viewMinTime,
'max-time': viewMaxTime,
'min-time': queryViewMinTime,
'max-time': queryViewMaxTime,
ulid: blockSearchParam,
'find-overlapping': findOverlappingParam,
'filter-compaction': filterCompactionParam,
Expand All @@ -65,6 +69,14 @@ export const BlocksContent: FC<{ data: BlockListProps } & PathPrefixProps> = ({
'compaction-level': withDefault(NumberParam, 0),
});

const viewMinTime = queryViewMinTime;
const viewMaxTime = queryViewMaxTime;

// Initialize time controls from query parameters
const [endTime, setEndTime] = useState<number>(viewMaxTime);
const [range, setRange] = useState<number>(rangeSteps[rangeSteps.length - 1]);
const [rangeInput, setRangeInput] = useState<string>(formatDuration(rangeSteps[rangeSteps.length - 1]));

const [filterCompaction, setFilterCompaction] = useState<boolean>(filterCompactionParam);
const [findOverlappingBlocks, setFindOverlappingBlocks] = useState<boolean>(findOverlappingParam);
const [compactionLevel, setCompactionLevel] = useState<number>(compactionLevelParam);
Expand All @@ -85,6 +97,62 @@ export const BlocksContent: FC<{ data: BlockListProps } & PathPrefixProps> = ({
});
};

const handleTimeRangeChange = (times: number[]): void => {
const [newMinTime, newMaxTime] = times;
const newRange = newMaxTime - newMinTime;

setEndTime(newMaxTime);
setRange(newRange);
setRangeInput(formatDuration(newRange));

setViewTime(times);
};

const updateTimeRange = (currentEndTime: number, currentRange: number) => {
setViewTime([currentEndTime - currentRange, currentEndTime]);
};

const onChangeRange = (newRange: number) => {
setRange(newRange);
setRangeInput(formatDuration(newRange));
updateTimeRange(endTime, newRange);
};

const onChangeEndTime = (newEndTime: number | null) => {
if (newEndTime == null) {
newEndTime = gridMaxTime;
}
// Cap the end time to gridMaxTime.
const cappedEndTime = Math.min(newEndTime, gridMaxTime);
setEndTime(cappedEndTime);
updateTimeRange(cappedEndTime, range);
};

const onChangeRangeInput = (rangeText: string): void => {
const newRange = parseDuration(rangeText);
if (newRange !== null) {
onChangeRange(newRange);
}
};

const increaseRange = (): void => {
for (const step of rangeSteps) {
if (range < step) {
onChangeRange(step);
return;
}
}
};

const decreaseRange = (): void => {
for (const step of rangeSteps.slice().reverse()) {
if (range > step) {
onChangeRange(step);
return;
}
}
};

const setBlockSearchInput = (searchState: string): void => {
setQuery({
ulid: searchState,
Expand Down Expand Up @@ -131,7 +199,35 @@ export const BlocksContent: FC<{ data: BlockListProps } & PathPrefixProps> = ({
onClick={() => setBlockSearchInput(searchState)}
defaultValue={blockSearchParam}
/>
<div className={styles.blockFilter}>
<Form inline className="graph-controls" onSubmit={(e) => e.preventDefault()}>
<InputGroup className="range-input" size="sm">
<InputGroupAddon addonType="prepend">
<Button title="Decrease range" onClick={decreaseRange}>
<FontAwesomeIcon icon={faMinus} fixedWidth />
</Button>
</InputGroupAddon>

<Input
value={rangeInput}
onChange={(e) => setRangeInput(e.target.value)}
onBlur={(e) => onChangeRangeInput(e.target.value)}
/>

<InputGroupAddon addonType="append">
<Button title="Increase range" onClick={increaseRange}>
<FontAwesomeIcon icon={faPlus} fixedWidth />
</Button>
</InputGroupAddon>
</InputGroup>

<TimeInput
time={endTime}
useLocalTime={true}
range={range}
placeholder="End time"
onChangeTime={onChangeEndTime}
/>

<Checkbox
id="find-overlap-block-checkbox"
onChange={({ target }) => {
Expand All @@ -141,9 +237,11 @@ export const BlocksContent: FC<{ data: BlockListProps } & PathPrefixProps> = ({
setFindOverlappingBlocks(target.checked);
}}
defaultChecked={findOverlappingBlocks}
wrapperStyles={{ marginBottom: 0 }}
>
Enable finding overlapping blocks
</Checkbox>

<BlockFilterCompaction
id="filter-compaction-checkbox"
defaultChecked={filterCompaction}
Expand All @@ -153,7 +251,7 @@ export const BlocksContent: FC<{ data: BlockListProps } & PathPrefixProps> = ({
}}
defaultValue={compactionLevelInput}
/>
</div>
</Form>
<div className={styles.container}>
<div className={styles.grid}>
<div className={styles.sources}>
Expand Down Expand Up @@ -181,7 +279,7 @@ export const BlocksContent: FC<{ data: BlockListProps } & PathPrefixProps> = ({
gridMaxTime={gridMaxTime}
viewMinTime={viewMinTime}
viewMaxTime={viewMaxTime}
onChange={setViewTime}
onChange={handleTimeRangeChange}
/>
</div>
<BlockDetails
Expand Down
2 changes: 1 addition & 1 deletion pkg/ui/react-app/src/thanos/pages/blocks/TimeRange.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const TimeRange: FC<TimeRangeProps> = ({ viewMinTime, viewMaxTime, gridMinTime,
min={gridMinTime}
max={gridMaxTime}
marks={marks}
defaultValue={[viewMinTime, viewMaxTime]}
value={[viewMinTime, viewMaxTime]}
onChange={onChange}
/>
<div className={styles.timeRange}>
Expand Down
6 changes: 3 additions & 3 deletions pkg/ui/static/react/asset-manifest.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"files": {
"main.css": "./static/css/main.17d667f4.css",
"main.js": "./static/js/main.ed1f5447.js",
"main.js": "./static/js/main.2b15849a.js",
"static/media/codicon.ttf": "./static/media/codicon.b3726f0165bf67ac6849.ttf",
"index.html": "./index.html",
"static/media/index.cjs": "./static/media/index.cd351d7c31d0d3fccf96.cjs",
"main.17d667f4.css.map": "./static/css/main.17d667f4.css.map",
"main.ed1f5447.js.map": "./static/js/main.ed1f5447.js.map"
"main.2b15849a.js.map": "./static/js/main.2b15849a.js.map"
},
"entrypoints": [
"static/css/main.17d667f4.css",
"static/js/main.ed1f5447.js"
"static/js/main.2b15849a.js"
]
}
2 changes: 1 addition & 1 deletion pkg/ui/static/react/index.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><meta name="theme-color" content="#000000"/><script>const GLOBAL_PATH_PREFIX="{{ pathPrefix }}"</script><script>const THANOS_COMPONENT="{{ .Component }}",THANOS_QUERY_URL="{{ .queryURL }}",THANOS_TENANT_HEADER="{{ .tenantHeader }}",THANOS_DEFAULT_TENANT="{{ .defaultTenant }}",THANOS_DISPLAY_TENANT_BOX="{{ .displayTenantBox }}"</script><link rel="manifest" href="./manifest.json"/><title>Thanos | Highly available Prometheus setup</title><script defer="defer" src="./static/js/main.ed1f5447.js"></script><link href="./static/css/main.17d667f4.css" rel="stylesheet"></head><body class="bootstrap"><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><meta name="theme-color" content="#000000"/><script>const GLOBAL_PATH_PREFIX="{{ pathPrefix }}"</script><script>const THANOS_COMPONENT="{{ .Component }}",THANOS_QUERY_URL="{{ .queryURL }}",THANOS_TENANT_HEADER="{{ .tenantHeader }}",THANOS_DEFAULT_TENANT="{{ .defaultTenant }}",THANOS_DISPLAY_TENANT_BOX="{{ .displayTenantBox }}"</script><link rel="manifest" href="./manifest.json"/><title>Thanos | Highly available Prometheus setup</title><script defer="defer" src="./static/js/main.2b15849a.js"></script><link href="./static/css/main.17d667f4.css" rel="stylesheet"></head><body class="bootstrap"><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Loading