mirror of
https://github.com/prometheus/prometheus.git
synced 2025-08-05 05:37:10 +02:00
* Initial commit from Create React App Signed-off-by: Julius Volz <julius.volz@gmail.com> * Initial Prometheus expression browser code Signed-off-by: Julius Volz <julius.volz@gmail.com> * Grpahing, try out echarts Signed-off-by: Julius Volz <julius.volz@gmail.com> * Switch to flot Signed-off-by: Julius Volz <julius.volz@gmail.com> * Add metrics fetching and stuff Signed-off-by: Julius Volz <julius.volz@gmail.com> * Autosuggest and graph improvements Signed-off-by: Julius Volz <julius.volz@gmail.com> * Start implementing graph controls, add loading spinner Signed-off-by: Julius Volz <julius.volz@gmail.com> * So many new features and fixes Signed-off-by: Julius Volz <julius.volz@gmail.com> * Fixed and built more features Signed-off-by: Julius Volz <julius.volz@gmail.com> * Make datetimepicker clear work Signed-off-by: Julius Volz <julius.volz@gmail.com> * Don't abort when executing empty expression Signed-off-by: Julius Volz <julius.volz@gmail.com> * Remove TabPaneAlert Signed-off-by: Julius Volz <julius.volz@gmail.com> * Split components into separate files Signed-off-by: Julius Volz <julius.volz@gmail.com> * Add table time input Signed-off-by: Julius Volz <julius.volz@gmail.com> * Move first files to TypeScript! Signed-off-by: Julius Volz <julius.volz@gmail.com> * More TypeScript conversions Signed-off-by: Julius Volz <julius.volz@gmail.com> * More TS conversions Signed-off-by: Julius Volz <julius.volz@gmail.com> * More TS conversions Signed-off-by: Julius Volz <julius.volz@gmail.com> * More TS conversions Signed-off-by: Julius Volz <julius.volz@gmail.com> * More TS conversions Signed-off-by: Julius Volz <julius.volz@gmail.com> * More TS fixes Signed-off-by: Julius Volz <julius.volz@gmail.com> * Convert Graph to TS Signed-off-by: Julius Volz <julius.volz@gmail.com> * Changes Signed-off-by: Julius Volz <julius.volz@gmail.com> * Resize detector, start building legend, axis font colors Signed-off-by: Julius Volz <julius.volz@gmail.com> * Make graph legend work Signed-off-by: Julius Volz <julius.volz@gmail.com> * Add URL params support and much more Signed-off-by: Julius Volz <julius.volz@gmail.com> * Put panel state into panel list, write URL options Signed-off-by: Julius Volz <julius.volz@gmail.com> * Change order of Graph and Table tabs Signed-off-by: Julius Volz <julius.volz@gmail.com> * Generalize time input naming more Signed-off-by: Julius Volz <julius.volz@gmail.com> * Work on history functionality Signed-off-by: Julius Volz <julius.volz@gmail.com> * npm updates Signed-off-by: Julius Volz <julius.volz@gmail.com> * Move loading indicator into "Execute" button Signed-off-by: Julius Volz <julius.volz@gmail.com> * Fix typo Signed-off-by: Julius Volz <julius.volz@gmail.com> * Revert "Move loading indicator into "Execute" button" This reverts commit ce7daee1f1af35da6c0d8b5517272839285ccfec. Signed-off-by: Julius Volz <julius.volz@gmail.com> * Improve error message when failing to fetch server time Signed-off-by: Julius Volz <julius.volz@gmail.com> * Move all code to Prometheus repo target dir Signed-off-by: Julius Volz <julius.volz@gmail.com> * Add react-app Makefile step and check in generated assets Signed-off-by: Julius Volz <julius.volz@gmail.com> * Add preliminary npm packages notice to NOTICE file Signed-off-by: Julius Volz <julius.volz@gmail.com> * Update React app's favicon and metadata Signed-off-by: Julius Volz <julius.volz@gmail.com> * Remove RP server refs, cleanups Signed-off-by: Julius Volz <julius.volz@gmail.com> * Use CircleCI image that includes NodeJS Signed-off-by: Julius Volz <julius.volz@gmail.com> * Add some missing React output assets Signed-off-by: Julius Volz <julius.volz@gmail.com> * Preserve CRLF in generated React files Signed-off-by: Julius Volz <julius.volz@gmail.com> * Switch from npm to yarn for React UI Signed-off-by: Julius Volz <julius.volz@gmail.com> * Save npm licenses and include them in release tarball Signed-off-by: Julius Volz <julius.volz@gmail.com> * Install npm on Travis Signed-off-by: Julius Volz <julius.volz@gmail.com> * Remove npm license tarball from source Signed-off-by: Julius Volz <julius.volz@gmail.com> * Remove React graph bundle from source Signed-off-by: Julius Volz <julius.volz@gmail.com> * Don't check in any compiled web assets Signed-off-by: Julius Volz <julius.volz@gmail.com> * Update README.md with node/yarn/React UI info Signed-off-by: Julius Volz <julius.volz@gmail.com> * Fix asset build step on CircleCI promu crossbuild Signed-off-by: Julius Volz <julius.volz@gmail.com> * Try to fix multi-arch go generate Signed-off-by: Julius Volz <julius.volz@gmail.com> * Remove check_assets from Travis CI build Signed-off-by: Julius Volz <julius.volz@gmail.com> * Prevent rebuilding of unchanged React app parts Signed-off-by: Julius Volz <julius.volz@gmail.com> * Fix npm license tarball path for promu Signed-off-by: Julius Volz <julius.volz@gmail.com> * Simplify Makefile Signed-off-by: Julius Volz <julius.volz@gmail.com> * Clarify build instructions in README.md Signed-off-by: Julius Volz <julius.volz@gmail.com> * Make minimal JS test pass Signed-off-by: Julius Volz <julius.volz@gmail.com> * Integrate React app tests into Makefile Signed-off-by: Julius Volz <julius.volz@gmail.com> * Separate react-app-tests target, but run it from CI Signed-off-by: Julius Volz <julius.volz@gmail.com> * Fix working directory for React app tests Signed-off-by: Julius Volz <julius.volz@gmail.com> * Remove local modifications to Makefile.common This means that CircleCI will not run the React app tests, but at least Travis still will... Signed-off-by: Julius Volz <julius.volz@gmail.com> * Depend on node_modules path for npm_licenses target Signed-off-by: Julius Volz <julius.volz@gmail.com> * Simplify tarball/docker/build Makefile targets Signed-off-by: Julius Volz <julius.volz@gmail.com> * Include React tests in "test" target Signed-off-by: Julius Volz <julius.volz@gmail.com> * Remove reference to removed "check_assets" target Signed-off-by: Julius Volz <julius.volz@gmail.com> * Do initial resize of expression input field Signed-off-by: Julius Volz <julius.volz@gmail.com> * Add React app proxying to local Prometheus in dev mode Signed-off-by: Julius Volz <julius.volz@gmail.com>
298 lines
8.2 KiB
TypeScript
298 lines
8.2 KiB
TypeScript
import React, { Component } from 'react';
|
|
|
|
import {
|
|
Alert,
|
|
Button,
|
|
Col,
|
|
Nav,
|
|
NavItem,
|
|
NavLink,
|
|
Row,
|
|
TabContent,
|
|
TabPane,
|
|
} from 'reactstrap';
|
|
|
|
import moment from 'moment-timezone';
|
|
|
|
import ExpressionInput from './ExpressionInput';
|
|
import GraphControls from './GraphControls';
|
|
import Graph from './Graph';
|
|
import DataTable from './DataTable';
|
|
import TimeInput from './TimeInput';
|
|
|
|
interface PanelProps {
|
|
options: PanelOptions;
|
|
onOptionsChanged: (opts: PanelOptions) => void;
|
|
metricNames: string[];
|
|
removePanel: () => void;
|
|
}
|
|
|
|
interface PanelState {
|
|
data: any; // TODO: Type data.
|
|
lastQueryParams: { // TODO: Share these with Graph.tsx in a file.
|
|
startTime: number,
|
|
endTime: number,
|
|
resolution: number,
|
|
} | null;
|
|
loading: boolean;
|
|
error: string | null;
|
|
stats: null; // TODO: Stats.
|
|
}
|
|
|
|
export interface PanelOptions {
|
|
expr: string;
|
|
type: PanelType;
|
|
range: number; // Range in seconds.
|
|
endTime: number | null; // Timestamp in milliseconds.
|
|
resolution: number | null; // Resolution in seconds.
|
|
stacked: boolean;
|
|
}
|
|
|
|
export enum PanelType {
|
|
Graph = 'graph',
|
|
Table = 'table',
|
|
}
|
|
|
|
export const PanelDefaultOptions: PanelOptions = {
|
|
type: PanelType.Table,
|
|
expr: '',
|
|
range: 3600,
|
|
endTime: null,
|
|
resolution: null,
|
|
stacked: false,
|
|
}
|
|
|
|
class Panel extends Component<PanelProps, PanelState> {
|
|
private abortInFlightFetch: (() => void) | null = null;
|
|
|
|
constructor(props: PanelProps) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
data: null,
|
|
lastQueryParams: null,
|
|
loading: false,
|
|
error: null,
|
|
stats: null,
|
|
};
|
|
}
|
|
|
|
componentDidUpdate(prevProps: PanelProps, prevState: PanelState) {
|
|
const prevOpts = prevProps.options;
|
|
const opts = this.props.options;
|
|
if (prevOpts.type !== opts.type ||
|
|
prevOpts.range !== opts.range ||
|
|
prevOpts.endTime !== opts.endTime ||
|
|
prevOpts.resolution !== opts.resolution) {
|
|
|
|
if (prevOpts.type !== opts.type) {
|
|
// If the other options change, we still want to show the old data until the new
|
|
// query completes, but this is not a good idea when we actually change between
|
|
// table and graph view, since not all queries work well in both.
|
|
this.setState({data: null});
|
|
}
|
|
this.executeQuery(opts.expr);
|
|
}
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.executeQuery(this.props.options.expr);
|
|
}
|
|
|
|
executeQuery = (expr: string): void => {
|
|
if (this.props.options.expr !== expr) {
|
|
this.setOptions({expr: expr});
|
|
}
|
|
if (expr === '') {
|
|
return;
|
|
}
|
|
|
|
if (this.abortInFlightFetch) {
|
|
this.abortInFlightFetch();
|
|
this.abortInFlightFetch = null;
|
|
}
|
|
|
|
const abortController = new AbortController();
|
|
this.abortInFlightFetch = () => abortController.abort();
|
|
this.setState({loading: true});
|
|
|
|
const endTime = this.getEndTime().valueOf() / 1000; // TODO: shouldn't valueof only work when it's a moment?
|
|
const startTime = endTime - this.props.options.range;
|
|
const resolution = this.props.options.resolution || Math.max(Math.floor(this.props.options.range / 250), 1);
|
|
|
|
const url = new URL(window.location.href);
|
|
const params: {[key: string]: string} = {
|
|
'query': expr,
|
|
};
|
|
|
|
switch (this.props.options.type) {
|
|
case 'graph':
|
|
url.pathname = '../../api/v1/query_range'
|
|
Object.assign(params, {
|
|
start: startTime,
|
|
end: endTime,
|
|
step: resolution,
|
|
})
|
|
// TODO path prefix here and elsewhere.
|
|
break;
|
|
case 'table':
|
|
url.pathname = '../../api/v1/query'
|
|
Object.assign(params, {
|
|
time: endTime,
|
|
})
|
|
break;
|
|
default:
|
|
throw new Error('Invalid panel type "' + this.props.options.type + '"');
|
|
}
|
|
Object.keys(params).forEach(key => url.searchParams.append(key, params[key]))
|
|
|
|
fetch(url.toString(), {cache: 'no-store', signal: abortController.signal})
|
|
.then(resp => resp.json())
|
|
.then(json => {
|
|
if (json.status !== 'success') {
|
|
throw new Error(json.error || 'invalid response JSON');
|
|
}
|
|
|
|
this.setState({
|
|
error: null,
|
|
data: json.data,
|
|
lastQueryParams: {
|
|
startTime: startTime,
|
|
endTime: endTime,
|
|
resolution: resolution,
|
|
},
|
|
loading: false,
|
|
});
|
|
this.abortInFlightFetch = null;
|
|
})
|
|
.catch(error => {
|
|
if (error.name === 'AbortError') {
|
|
// Aborts are expected, don't show an error for them.
|
|
return
|
|
}
|
|
this.setState({
|
|
error: 'Error executing query: ' + error.message,
|
|
loading: false,
|
|
})
|
|
});
|
|
}
|
|
|
|
setOptions(opts: object): void {
|
|
const newOpts = {...this.props.options, ...opts};
|
|
this.props.onOptionsChanged(newOpts);
|
|
}
|
|
|
|
handleExpressionChange = (expr: string): void => {
|
|
this.setOptions({expr: expr});
|
|
}
|
|
|
|
handleChangeRange = (range: number): void => {
|
|
this.setOptions({range: range});
|
|
}
|
|
|
|
getEndTime = (): number | moment.Moment => {
|
|
if (this.props.options.endTime === null) {
|
|
return moment();
|
|
}
|
|
return this.props.options.endTime;
|
|
}
|
|
|
|
handleChangeEndTime = (endTime: number | null) => {
|
|
this.setOptions({endTime: endTime});
|
|
}
|
|
|
|
handleChangeResolution = (resolution: number | null) => {
|
|
this.setOptions({resolution: resolution});
|
|
}
|
|
|
|
handleChangeStacking = (stacked: boolean) => {
|
|
this.setOptions({stacked: stacked});
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<div className="panel">
|
|
<Row>
|
|
<Col>
|
|
<ExpressionInput
|
|
value={this.props.options.expr}
|
|
executeQuery={this.executeQuery}
|
|
loading={this.state.loading}
|
|
metricNames={this.props.metricNames}
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
<Row>
|
|
<Col>
|
|
{this.state.error && <Alert color="danger">{this.state.error}</Alert>}
|
|
</Col>
|
|
</Row>
|
|
<Row>
|
|
<Col>
|
|
<Nav tabs>
|
|
<NavItem>
|
|
<NavLink
|
|
className={this.props.options.type === 'table' ? 'active' : ''}
|
|
onClick={() => { this.setOptions({type: 'table'}); }}
|
|
>
|
|
Table
|
|
</NavLink>
|
|
</NavItem>
|
|
<NavItem>
|
|
<NavLink
|
|
className={this.props.options.type === 'graph' ? 'active' : ''}
|
|
onClick={() => { this.setOptions({type: 'graph'}); }}
|
|
>
|
|
Graph
|
|
</NavLink>
|
|
</NavItem>
|
|
</Nav>
|
|
<TabContent activeTab={this.props.options.type}>
|
|
<TabPane tabId="table">
|
|
{this.props.options.type === 'table' &&
|
|
<>
|
|
<div className="table-controls">
|
|
<TimeInput
|
|
time={this.props.options.endTime}
|
|
range={this.props.options.range}
|
|
placeholder="Evaluation time"
|
|
onChangeTime={this.handleChangeEndTime}
|
|
/>
|
|
</div>
|
|
<DataTable data={this.state.data} />
|
|
</>
|
|
}
|
|
</TabPane>
|
|
<TabPane tabId="graph">
|
|
{this.props.options.type === 'graph' &&
|
|
<>
|
|
<GraphControls
|
|
range={this.props.options.range}
|
|
endTime={this.props.options.endTime}
|
|
resolution={this.props.options.resolution}
|
|
stacked={this.props.options.stacked}
|
|
|
|
onChangeRange={this.handleChangeRange}
|
|
onChangeEndTime={this.handleChangeEndTime}
|
|
onChangeResolution={this.handleChangeResolution}
|
|
onChangeStacking={this.handleChangeStacking}
|
|
/>
|
|
<Graph data={this.state.data} stacked={this.props.options.stacked} queryParams={this.state.lastQueryParams} />
|
|
</>
|
|
}
|
|
</TabPane>
|
|
</TabContent>
|
|
</Col>
|
|
</Row>
|
|
<Row>
|
|
<Col>
|
|
<Button className="float-right" color="link" onClick={this.props.removePanel} size="sm">Remove Panel</Button>
|
|
</Col>
|
|
</Row>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default Panel;
|