prometheus

Форк
0
240 строк · 8.4 Кб
1
import { FC, useEffect, useState } from 'react';
2
import { Alert, Button, Toast, ToastBody } from 'reactstrap';
3

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';
12

13
export type PanelMeta = { key: string; options: PanelOptions; id: string };
14

15
export const updateURL = (nextPanels: PanelMeta[]): void => {
16
  const query = encodePanelOptionsToQueryString(nextPanels);
17
  window.history.pushState({}, '', query);
18
};
19

20
interface PanelListContentProps {
21
  panels: PanelMeta[];
22
  metrics: string[];
23
  useLocalTime: boolean;
24
  queryHistoryEnabled: boolean;
25
  enableAutocomplete: boolean;
26
  enableHighlighting: boolean;
27
  enableLinter: boolean;
28
}
29

30
export const PanelListContent: FC<PanelListContentProps> = ({
31
  metrics = [],
32
  useLocalTime,
33
  queryHistoryEnabled,
34
  enableAutocomplete,
35
  enableHighlighting,
36
  enableLinter,
37
  ...rest
38
}) => {
39
  const [panels, setPanels] = useState(rest.panels);
40
  const [historyItems, setLocalStorageHistoryItems] = useLocalStorage<string[]>('history', []);
41

42
  useEffect(() => {
43
    !panels.length && addPanel();
44
    window.onpopstate = () => {
45
      const panels = decodePanelOptionsFromQueryString(window.location.search);
46
      if (panels.length > 0) {
47
        setPanels(panels);
48
      }
49
    };
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
52
  }, []);
53

54
  const handleExecuteQuery = (query: string) => {
55
    const isSimpleMetric = metrics.indexOf(query) !== -1;
56
    if (isSimpleMetric || !query.length) {
57
      return;
58
    }
59
    const extendedItems = historyItems.reduce(
60
      (acc, metric) => {
61
        return metric === query ? acc : [...acc, metric]; // Prevent adding query twice.
62
      },
63
      [query]
64
    );
65
    setLocalStorageHistoryItems(extendedItems.slice(0, 50));
66
  };
67

68
  const addPanel = () => {
69
    callAll(
70
      setPanels,
71
      updateURL
72
    )([
73
      ...panels,
74
      {
75
        id: generateID(),
76
        key: `${panels.length}`,
77
        options: PanelDefaultOptions,
78
      },
79
    ]);
80
  };
81

82
  const pathPrefix = usePathPrefix();
83

84
  return (
85
    <>
86
      {panels.map(({ id, options }) => (
87
        <Panel
88
          pathPrefix={pathPrefix}
89
          onExecuteQuery={handleExecuteQuery}
90
          key={id}
91
          id={id}
92
          options={options}
93
          onOptionsChanged={(opts) =>
94
            callAll(setPanels, updateURL)(panels.map((p) => (id === p.id ? { ...p, options: opts } : p)))
95
          }
96
          removePanel={() =>
97
            callAll(
98
              setPanels,
99
              updateURL
100
            )(
101
              panels.reduce<PanelMeta[]>(
102
                (acc, panel) => (panel.id !== id ? [...acc, { ...panel, key: `${acc.length}` }] : acc),
103
                []
104
              )
105
            )
106
          }
107
          useLocalTime={useLocalTime}
108
          metricNames={metrics}
109
          pastQueries={queryHistoryEnabled ? historyItems : []}
110
          enableAutocomplete={enableAutocomplete}
111
          enableHighlighting={enableHighlighting}
112
          enableLinter={enableLinter}
113
        />
114
      ))}
115
      <Button className="d-block mb-3" color="primary" onClick={addPanel}>
116
        Add Panel
117
      </Button>
118
    </>
119
  );
120
};
121

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);
130

131
  const pathPrefix = usePathPrefix();
132
  const { response: metricsRes, error: metricsErr } = useFetch<string[]>(`${pathPrefix}/${API_PATH}/label/__name__/values`);
133

134
  const browserTime = new Date().getTime() / 1000;
135
  const { response: timeRes, error: timeErr } = useFetch<{ result: number[] }>(
136
    `${pathPrefix}/${API_PATH}/query?query=time()`
137
  );
138

139
  const onClipboardMsg = (msg: string) => {
140
    setClipboardMsg(msg);
141
    setTimeout(() => {
142
      setClipboardMsg(null);
143
    }, 1500);
144
  };
145

146
  useEffect(() => {
147
    if (timeRes.data) {
148
      const serverTime = timeRes.data.result[0];
149
      setDelta(Math.abs(browserTime - serverTime));
150
    }
151
    /**
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.
155
     **/
156
    // eslint-disable-next-line react-hooks/exhaustive-deps
157
  }, [timeRes.data]);
158

159
  return (
160
    <>
161
      <ToastContext.Provider value={onClipboardMsg}>
162
        <div className="clearfix">
163
          <Toast
164
            isOpen={clipboardMsg != null}
165
            style={{ position: 'fixed', zIndex: 1000, left: '50%', transform: 'translateX(-50%)' }}
166
          >
167
            <ToastBody>Label matcher copied to clipboard</ToastBody>
168
          </Toast>
169
          <div className="float-left">
170
            <Checkbox
171
              wrapperStyles={{ display: 'inline-block' }}
172
              id="use-local-time-checkbox"
173
              onChange={({ target }) => setUseLocalTime(target.checked)}
174
              defaultChecked={useLocalTime}
175
            >
176
              Use local time
177
            </Checkbox>
178
            <Checkbox
179
              wrapperStyles={{ marginLeft: 20, display: 'inline-block' }}
180
              id="query-history-checkbox"
181
              onChange={({ target }) => setEnableQueryHistory(target.checked)}
182
              defaultChecked={enableQueryHistory}
183
            >
184
              Enable query history
185
            </Checkbox>
186
            <Checkbox
187
              wrapperStyles={{ marginLeft: 20, display: 'inline-block' }}
188
              id="autocomplete-checkbox"
189
              onChange={({ target }) => setEnableAutocomplete(target.checked)}
190
              defaultChecked={enableAutocomplete}
191
            >
192
              Enable autocomplete
193
            </Checkbox>
194
          </div>
195
          <Checkbox
196
            wrapperStyles={{ marginLeft: 20, display: 'inline-block' }}
197
            id="highlighting-checkbox"
198
            onChange={({ target }) => setEnableHighlighting(target.checked)}
199
            defaultChecked={enableHighlighting}
200
          >
201
            Enable highlighting
202
          </Checkbox>
203
          <Checkbox
204
            wrapperStyles={{ marginLeft: 20, display: 'inline-block' }}
205
            id="linter-checkbox"
206
            onChange={({ target }) => setEnableLinter(target.checked)}
207
            defaultChecked={enableLinter}
208
          >
209
            Enable linter
210
          </Checkbox>
211
        </div>
212
        {(delta > 30 || timeErr) && (
213
          <Alert color="danger">
214
            <strong>Warning: </strong>
215
            {timeErr && `Unexpected response status when fetching server time: ${timeErr.message}`}
216
            {delta >= 30 &&
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.`}
218
          </Alert>
219
        )}
220
        {metricsErr && (
221
          <Alert color="danger">
222
            <strong>Warning: </strong>
223
            Error fetching metrics list: Unexpected response status when fetching metric names: {metricsErr.message}
224
          </Alert>
225
        )}
226
        <PanelListContent
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}
234
        />
235
      </ToastContext.Provider>
236
    </>
237
  );
238
};
239

240
export default PanelList;
241

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.