1
import { FC, useEffect, useState } from 'react';
2
import { Alert, Button, Toast, ToastBody } from 'reactstrap';
4
import Checkbox from '../../components/Checkbox';
5
import { API_PATH } from '../../constants/constants';
6
import { ToastContext } from '../../contexts/ToastContext';
7
import { usePathPrefix } from '../../contexts/PathPrefixContext';
8
import { useFetch } from '../../hooks/useFetch';
9
import { useLocalStorage } from '../../hooks/useLocalStorage';
10
import { callAll, decodePanelOptionsFromQueryString, encodePanelOptionsToQueryString, generateID } from '../../utils';
11
import Panel, { PanelDefaultOptions, PanelOptions } from './Panel';
13
export type PanelMeta = { key: string; options: PanelOptions; id: string };
15
export const updateURL = (nextPanels: PanelMeta[]): void => {
16
const query = encodePanelOptionsToQueryString(nextPanels);
17
window.history.pushState({}, '', query);
20
interface PanelListContentProps {
23
useLocalTime: boolean;
24
queryHistoryEnabled: boolean;
25
enableAutocomplete: boolean;
26
enableHighlighting: boolean;
27
enableLinter: boolean;
30
export const PanelListContent: FC<PanelListContentProps> = ({
39
const [panels, setPanels] = useState(rest.panels);
40
const [historyItems, setLocalStorageHistoryItems] = useLocalStorage<string[]>('history', []);
43
!panels.length && addPanel();
44
window.onpopstate = () => {
45
const panels = decodePanelOptionsFromQueryString(window.location.search);
46
if (panels.length > 0) {
50
// We want useEffect to act only as componentDidMount, but react still complains about the empty dependencies list.
51
// eslint-disable-next-line react-hooks/exhaustive-deps
54
const handleExecuteQuery = (query: string) => {
55
const isSimpleMetric = metrics.indexOf(query) !== -1;
56
if (isSimpleMetric || !query.length) {
59
const extendedItems = historyItems.reduce(
61
return metric === query ? acc : [...acc, metric]; // Prevent adding query twice.
65
setLocalStorageHistoryItems(extendedItems.slice(0, 50));
68
const addPanel = () => {
76
key: `${panels.length}`,
77
options: PanelDefaultOptions,
82
const pathPrefix = usePathPrefix();
86
{panels.map(({ id, options }) => (
88
pathPrefix={pathPrefix}
89
onExecuteQuery={handleExecuteQuery}
93
onOptionsChanged={(opts) =>
94
callAll(setPanels, updateURL)(panels.map((p) => (id === p.id ? { ...p, options: opts } : p)))
101
panels.reduce<PanelMeta[]>(
102
(acc, panel) => (panel.id !== id ? [...acc, { ...panel, key: `${acc.length}` }] : acc),
107
useLocalTime={useLocalTime}
108
metricNames={metrics}
109
pastQueries={queryHistoryEnabled ? historyItems : []}
110
enableAutocomplete={enableAutocomplete}
111
enableHighlighting={enableHighlighting}
112
enableLinter={enableLinter}
115
<Button className="d-block mb-3" color="primary" onClick={addPanel}>
122
const PanelList: FC = () => {
123
const [delta, setDelta] = useState(0);
124
const [useLocalTime, setUseLocalTime] = useLocalStorage('use-local-time', false);
125
const [enableQueryHistory, setEnableQueryHistory] = useLocalStorage('enable-query-history', false);
126
const [enableAutocomplete, setEnableAutocomplete] = useLocalStorage('enable-metric-autocomplete', true);
127
const [enableHighlighting, setEnableHighlighting] = useLocalStorage('enable-syntax-highlighting', true);
128
const [enableLinter, setEnableLinter] = useLocalStorage('enable-linter', true);
129
const [clipboardMsg, setClipboardMsg] = useState<string | null>(null);
131
const pathPrefix = usePathPrefix();
132
const { response: metricsRes, error: metricsErr } = useFetch<string[]>(`${pathPrefix}/${API_PATH}/label/__name__/values`);
134
const browserTime = new Date().getTime() / 1000;
135
const { response: timeRes, error: timeErr } = useFetch<{ result: number[] }>(
136
`${pathPrefix}/${API_PATH}/query?query=time()`
139
const onClipboardMsg = (msg: string) => {
140
setClipboardMsg(msg);
142
setClipboardMsg(null);
148
const serverTime = timeRes.data.result[0];
149
setDelta(Math.abs(browserTime - serverTime));
152
* React wants to include browserTime to useEffect dependencies list which will cause a delta change on every re-render
153
* Basically it's not recommended to disable this rule, but this is the only way to take control over the useEffect
154
* dependencies and to not include the browserTime variable.
156
// eslint-disable-next-line react-hooks/exhaustive-deps
161
<ToastContext.Provider value={onClipboardMsg}>
162
<div className="clearfix">
164
isOpen={clipboardMsg != null}
165
style={{ position: 'fixed', zIndex: 1000, left: '50%', transform: 'translateX(-50%)' }}
167
<ToastBody>Label matcher copied to clipboard</ToastBody>
169
<div className="float-left">
171
wrapperStyles={{ display: 'inline-block' }}
172
id="use-local-time-checkbox"
173
onChange={({ target }) => setUseLocalTime(target.checked)}
174
defaultChecked={useLocalTime}
179
wrapperStyles={{ marginLeft: 20, display: 'inline-block' }}
180
id="query-history-checkbox"
181
onChange={({ target }) => setEnableQueryHistory(target.checked)}
182
defaultChecked={enableQueryHistory}
187
wrapperStyles={{ marginLeft: 20, display: 'inline-block' }}
188
id="autocomplete-checkbox"
189
onChange={({ target }) => setEnableAutocomplete(target.checked)}
190
defaultChecked={enableAutocomplete}
196
wrapperStyles={{ marginLeft: 20, display: 'inline-block' }}
197
id="highlighting-checkbox"
198
onChange={({ target }) => setEnableHighlighting(target.checked)}
199
defaultChecked={enableHighlighting}
204
wrapperStyles={{ marginLeft: 20, display: 'inline-block' }}
206
onChange={({ target }) => setEnableLinter(target.checked)}
207
defaultChecked={enableLinter}
212
{(delta > 30 || timeErr) && (
213
<Alert color="danger">
214
<strong>Warning: </strong>
215
{timeErr && `Unexpected response status when fetching server time: ${timeErr.message}`}
217
`Error fetching server time: Detected ${delta} seconds time difference between your browser and the server. Prometheus relies on accurate time and time drift might cause unexpected query results.`}
221
<Alert color="danger">
222
<strong>Warning: </strong>
223
Error fetching metrics list: Unexpected response status when fetching metric names: {metricsErr.message}
227
panels={decodePanelOptionsFromQueryString(window.location.search)}
228
useLocalTime={useLocalTime}
229
metrics={metricsRes.data}
230
queryHistoryEnabled={enableQueryHistory}
231
enableAutocomplete={enableAutocomplete}
232
enableHighlighting={enableHighlighting}
233
enableLinter={enableLinter}
235
</ToastContext.Provider>
240
export default PanelList;