import React, { Component, ChangeEvent } from 'react'; import { Alert, Button, Col, Row } from 'reactstrap'; import Panel, { PanelOptions, PanelDefaultOptions } from './Panel'; import { decodePanelOptionsFromQueryString, encodePanelOptionsToQueryString } from './utils/urlParams'; import Checkbox from './Checkbox'; export type MetricGroup = { title: string; items: string[] }; interface PanelListState { panels: { key: string; options: PanelOptions; }[]; pastQueries: string[]; metricNames: string[]; fetchMetricsError: string | null; timeDriftError: string | null; } class PanelList extends Component { private key: number = 0; constructor(props: any) { super(props); const urlPanels = decodePanelOptionsFromQueryString(window.location.search); this.state = { panels: urlPanels.length !== 0 ? urlPanels : [ { key: this.getKey(), options: PanelDefaultOptions, }, ], pastQueries: [], metricNames: [], fetchMetricsError: null, timeDriftError: null, }; } componentDidMount() { fetch("../../api/v1/label/__name__/values", {cache: "no-store"}) .then(resp => { if (resp.ok) { return resp.json(); } else { throw new Error('Unexpected response status when fetching metric names: ' + resp.statusText); // TODO extract error } }) .then(json => { this.setMetrics(json.data); }) .catch(error => this.setState({ fetchMetricsError: error.message })); const browserTime = new Date().getTime() / 1000; fetch("../../api/v1/query?query=time()", {cache: "no-store"}) .then(resp => { if (resp.ok) { return resp.json(); } else { throw new Error('Unexpected response status when fetching metric names: ' + resp.statusText); // TODO extract error } }) .then(json => { const serverTime = json.data.result[0]; const delta = Math.abs(browserTime - serverTime); if (delta >= 30) { throw new Error('Detected ' + delta + ' seconds time difference between your browser and the server. Prometheus relies on accurate time and time drift might cause unexpected query results.'); } }) .catch(error => this.setState({ timeDriftError: error.message })); window.onpopstate = () => { const panels = decodePanelOptionsFromQueryString(window.location.search); if (panels.length !== 0) { this.setState({panels: panels}); } } } isHistoryEnabled = () => JSON.parse(localStorage.getItem('enable-query-history') || 'false') as boolean; getHistoryItems = () => JSON.parse(localStorage.getItem('history') || '[]') as string[]; toggleQueryHistory = (e: ChangeEvent) => { localStorage.setItem('enable-query-history', `${e.target.checked}`); this.setMetrics(); } setMetrics = (metricNames: string[] = this.state.metricNames) => { this.setState({ pastQueries: this.isHistoryEnabled() ? this.getHistoryItems() : [], metricNames }); } handleQueryHistory = (query: string) => { const isSimpleMetric = this.state.metricNames.indexOf(query) !== -1; if (isSimpleMetric || !query.length) { return; } const historyItems = this.getHistoryItems(); const extendedItems = historyItems.reduce((acc, metric) => { return metric === query ? acc : [...acc, metric]; // Prevent adding query twice. }, [query]); localStorage.setItem('history', JSON.stringify(extendedItems.slice(0, 50))); this.setMetrics(); } getKey(): string { return (this.key++).toString(); } handleOptionsChanged(key: string, opts: PanelOptions): void { const newPanels = this.state.panels.map(p => { if (key === p.key) { return { key: key, options: opts, } } return p; }); this.setState({panels: newPanels}, this.updateURL) } updateURL(): void { const query = encodePanelOptionsToQueryString(this.state.panels); window.history.pushState({}, '', query); } addPanel = (): void => { const panels = this.state.panels.slice(); panels.push({ key: this.getKey(), options: PanelDefaultOptions, }); this.setState({panels: panels}, this.updateURL); } removePanel = (key: string): void => { const panels = this.state.panels.filter(panel => { return panel.key !== key; }); this.setState({panels: panels}, this.updateURL); } render() { const { metricNames, pastQueries, timeDriftError, fetchMetricsError } = this.state; return ( <> Enable query history {timeDriftError && Warning: Error fetching server time: {this.state.timeDriftError}} {fetchMetricsError && Warning: Error fetching metrics list: {this.state.fetchMetricsError}} {this.state.panels.map(p => this.handleOptionsChanged(p.key, opts)} removePanel={() => this.removePanel(p.key)} metricNames={metricNames} pastQueries={pastQueries} /> )} ); } } export default PanelList;