mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-25 05:34:05 -08:00
WIP: status page - API and UI (#6243)
* status page initial commit Signed-off-by: Boyko Lalov <boyskila@gmail.com> Signed-off-by: blalov <boyko.lalov@tick42.com> * refactor useFetch Signed-off-by: Boyko Lalov <boyskila@gmail.com> Signed-off-by: blalov <boyko.lalov@tick42.com> * refactoring Signed-off-by: Boyko Lalov <boyskila@gmail.com> Signed-off-by: blalov <boyko.lalov@tick42.com> * adding tests Signed-off-by: Boyko Lalov <boyskila@gmail.com> Signed-off-by: blalov <boyko.lalov@tick42.com> * snapshot testing Signed-off-by: Boyko Lalov <boyskila@gmail.com> Signed-off-by: blalov <boyko.lalov@tick42.com> * fix wrong go files formatting Signed-off-by: Boyko Lalov <boyskila@gmail.com> Signed-off-by: blalov <boyko.lalov@tick42.com> * change the snapshot library Signed-off-by: Boyko Lalov <boyskila@gmail.com> Signed-off-by: blalov <boyko.lalov@tick42.com> * update api paths Signed-off-by: Boyko Lalov <boyskila@gmail.com> Signed-off-by: blalov <boyko.lalov@tick42.com> * move test folder outside src Signed-off-by: Boyko Lalov <boyskila@gmail.com> Signed-off-by: blalov <boyko.lalov@tick42.com> * useFetches tests Signed-off-by: blalov <boyko.lalov@tick42.com> * sticky navbar Signed-off-by: Boyko Lalov <boyskila@gmail.com> Signed-off-by: blalov <boyko.lalov@tick42.com> * handle runtimeInfo error on Gather() and add json tags to RuntimeInfo struct Signed-off-by: blalov <boyko.lalov@tick42.com> * refactor alert managers section Signed-off-by: blalov <boyko.lalov@tick42.com>
This commit is contained in:
parent
ca9fce46a3
commit
cb7cbad5f9
|
@ -108,6 +108,32 @@ type rulesRetriever interface {
|
||||||
AlertingRules() []*rules.AlertingRule
|
AlertingRules() []*rules.AlertingRule
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PrometheusVersion contains build information about Prometheus.
|
||||||
|
type PrometheusVersion struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Revision string `json:"revision"`
|
||||||
|
Branch string `json:"branch"`
|
||||||
|
BuildUser string `json:"buildUser"`
|
||||||
|
BuildDate string `json:"buildDate"`
|
||||||
|
GoVersion string `json:"goVersion"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RuntimeInfo contains runtime information about Prometheus.
|
||||||
|
type RuntimeInfo struct {
|
||||||
|
StartTime time.Time `json:"startTime"`
|
||||||
|
CWD string `json:"CWD"`
|
||||||
|
ReloadConfigSuccess bool `json:"reloadConfigSuccess"`
|
||||||
|
LastConfigTime time.Time `json:"lastConfigTime"`
|
||||||
|
ChunkCount int64 `json:"chunkCount"`
|
||||||
|
TimeSeriesCount int64 `json:"timeSeriesCount"`
|
||||||
|
CorruptionCount int64 `json:"corruptionCount"`
|
||||||
|
GoroutineCount int `json:"goroutineCount"`
|
||||||
|
GOMAXPROCS int `json:"GOMAXPROCS"`
|
||||||
|
GOGC string `json:"GOGC"`
|
||||||
|
GODEBUG string `json:"GODEBUG"`
|
||||||
|
StorageRetention string `json:"storageRetention"`
|
||||||
|
}
|
||||||
|
|
||||||
type response struct {
|
type response struct {
|
||||||
Status status `json:"status"`
|
Status status `json:"status"`
|
||||||
Data interface{} `json:"data,omitempty"`
|
Data interface{} `json:"data,omitempty"`
|
||||||
|
@ -154,6 +180,8 @@ type API struct {
|
||||||
remoteReadMaxBytesInFrame int
|
remoteReadMaxBytesInFrame int
|
||||||
remoteReadGate *gate.Gate
|
remoteReadGate *gate.Gate
|
||||||
CORSOrigin *regexp.Regexp
|
CORSOrigin *regexp.Regexp
|
||||||
|
buildInfo *PrometheusVersion
|
||||||
|
runtimeInfo func() (RuntimeInfo, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -178,6 +206,8 @@ func NewAPI(
|
||||||
remoteReadConcurrencyLimit int,
|
remoteReadConcurrencyLimit int,
|
||||||
remoteReadMaxBytesInFrame int,
|
remoteReadMaxBytesInFrame int,
|
||||||
CORSOrigin *regexp.Regexp,
|
CORSOrigin *regexp.Regexp,
|
||||||
|
runtimeInfo func() (RuntimeInfo, error),
|
||||||
|
buildInfo *PrometheusVersion,
|
||||||
) *API {
|
) *API {
|
||||||
return &API{
|
return &API{
|
||||||
QueryEngine: qe,
|
QueryEngine: qe,
|
||||||
|
@ -197,6 +227,8 @@ func NewAPI(
|
||||||
remoteReadMaxBytesInFrame: remoteReadMaxBytesInFrame,
|
remoteReadMaxBytesInFrame: remoteReadMaxBytesInFrame,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
CORSOrigin: CORSOrigin,
|
CORSOrigin: CORSOrigin,
|
||||||
|
runtimeInfo: runtimeInfo,
|
||||||
|
buildInfo: buildInfo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,6 +274,8 @@ func (api *API) Register(r *route.Router) {
|
||||||
r.Get("/alertmanagers", wrap(api.alertmanagers))
|
r.Get("/alertmanagers", wrap(api.alertmanagers))
|
||||||
|
|
||||||
r.Get("/status/config", wrap(api.serveConfig))
|
r.Get("/status/config", wrap(api.serveConfig))
|
||||||
|
r.Get("/status/runtimeinfo", wrap(api.serveRuntimeInfo))
|
||||||
|
r.Get("/status/buildinfo", wrap(api.serveBuildInfo))
|
||||||
r.Get("/status/flags", wrap(api.serveFlags))
|
r.Get("/status/flags", wrap(api.serveFlags))
|
||||||
r.Post("/read", api.ready(http.HandlerFunc(api.remoteRead)))
|
r.Post("/read", api.ready(http.HandlerFunc(api.remoteRead)))
|
||||||
|
|
||||||
|
@ -832,6 +866,18 @@ type prometheusConfig struct {
|
||||||
YAML string `json:"yaml"`
|
YAML string `json:"yaml"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api *API) serveRuntimeInfo(r *http.Request) apiFuncResult {
|
||||||
|
status, err := api.runtimeInfo()
|
||||||
|
if err != nil {
|
||||||
|
return apiFuncResult{status, &apiError{errorInternal, err}, nil, nil}
|
||||||
|
}
|
||||||
|
return apiFuncResult{status, nil, nil, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) serveBuildInfo(r *http.Request) apiFuncResult {
|
||||||
|
return apiFuncResult{api.buildInfo, nil, nil, nil}
|
||||||
|
}
|
||||||
|
|
||||||
func (api *API) serveConfig(r *http.Request) apiFuncResult {
|
func (api *API) serveConfig(r *http.Request) apiFuncResult {
|
||||||
cfg := &prometheusConfig{
|
cfg := &prometheusConfig{
|
||||||
YAML: api.config().String(),
|
YAML: api.config().String(),
|
||||||
|
@ -1176,6 +1222,7 @@ func (api *API) respondError(w http.ResponseWriter, apiErr *apiError, data inter
|
||||||
Error: apiErr.err.Error(),
|
Error: apiErr.err.Error(),
|
||||||
Data: data,
|
Data: data,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
level.Error(api.logger).Log("msg", "error marshaling json response", "err", err)
|
level.Error(api.logger).Log("msg", "error marshaling json response", "err", err)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.7.1",
|
"@fortawesome/free-solid-svg-icons": "^5.7.1",
|
||||||
"@fortawesome/react-fontawesome": "^0.1.4",
|
"@fortawesome/react-fontawesome": "^0.1.4",
|
||||||
"@reach/router": "^1.2.1",
|
"@reach/router": "^1.2.1",
|
||||||
|
"@testing-library/react-hooks": "^3.1.1",
|
||||||
"@types/jest": "^24.0.20",
|
"@types/jest": "^24.0.20",
|
||||||
"@types/jquery": "^3.3.29",
|
"@types/jquery": "^3.3.29",
|
||||||
"@types/node": "^12.11.1",
|
"@types/node": "^12.11.1",
|
||||||
|
@ -18,9 +19,11 @@
|
||||||
"@types/sanitize-html": "^1.20.2",
|
"@types/sanitize-html": "^1.20.2",
|
||||||
"bootstrap": "^4.2.1",
|
"bootstrap": "^4.2.1",
|
||||||
"downshift": "^3.2.2",
|
"downshift": "^3.2.2",
|
||||||
|
"enzyme-to-json": "^3.4.3",
|
||||||
"flot": "^3.2.13",
|
"flot": "^3.2.13",
|
||||||
"fuzzy": "^0.1.3",
|
"fuzzy": "^0.1.3",
|
||||||
"i": "^0.3.6",
|
"i": "^0.3.6",
|
||||||
|
"jest-fetch-mock": "^2.1.2",
|
||||||
"jquery": "^3.3.1",
|
"jquery": "^3.3.1",
|
||||||
"jquery.flot.tooltip": "^0.9.0",
|
"jquery.flot.tooltip": "^0.9.0",
|
||||||
"jsdom": "^15.2.0",
|
"jsdom": "^15.2.0",
|
||||||
|
@ -32,6 +35,7 @@
|
||||||
"react-dom": "^16.7.0",
|
"react-dom": "^16.7.0",
|
||||||
"react-resize-detector": "^4.2.1",
|
"react-resize-detector": "^4.2.1",
|
||||||
"react-scripts": "^3.2.0",
|
"react-scripts": "^3.2.0",
|
||||||
|
"react-test-renderer": "^16.9.0",
|
||||||
"reactstrap": "^8.0.1",
|
"reactstrap": "^8.0.1",
|
||||||
"sanitize-html": "^1.20.1",
|
"sanitize-html": "^1.20.1",
|
||||||
"tempusdominus-bootstrap-4": "^5.1.2",
|
"tempusdominus-bootstrap-4": "^5.1.2",
|
||||||
|
@ -83,5 +87,10 @@
|
||||||
"prettier": "^1.18.2",
|
"prettier": "^1.18.2",
|
||||||
"sinon": "^7.5.0"
|
"sinon": "^7.5.0"
|
||||||
},
|
},
|
||||||
"proxy": "http://localhost:9090"
|
"proxy": "http://localhost:9090",
|
||||||
|
"jest": {
|
||||||
|
"snapshotSerializers": [
|
||||||
|
"enzyme-to-json/serializer"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,10 @@ input[type='checkbox']:checked + label {
|
||||||
line-height: 1.8;
|
line-height: 1.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.capitalize-title::first-letter {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
.expression-input {
|
.expression-input {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ class App extends Component {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Navigation />
|
<Navigation />
|
||||||
<Container fluid>
|
<Container fluid style={{ paddingTop: 70 }}>
|
||||||
<Router basepath="/new">
|
<Router basepath="/new">
|
||||||
<PanelList path="/graph" />
|
<PanelList path="/graph" />
|
||||||
<Alerts path="/alerts" />
|
<Alerts path="/alerts" />
|
||||||
|
|
|
@ -34,7 +34,7 @@ class Graph extends PureComponent<GraphProps> {
|
||||||
private chartRef = React.createRef<HTMLDivElement>();
|
private chartRef = React.createRef<HTMLDivElement>();
|
||||||
|
|
||||||
renderLabels(labels: { [key: string]: string }) {
|
renderLabels(labels: { [key: string]: string }) {
|
||||||
let labelStrings: string[] = [];
|
const labelStrings: string[] = [];
|
||||||
for (const label in labels) {
|
for (const label in labels) {
|
||||||
if (label !== '__name__') {
|
if (label !== '__name__') {
|
||||||
labelStrings.push('<strong>' + label + '</strong>: ' + escapeHTML(labels[label]));
|
labelStrings.push('<strong>' + label + '</strong>: ' + escapeHTML(labels[label]));
|
||||||
|
|
|
@ -17,7 +17,7 @@ const Navigation = () => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const toggle = () => setIsOpen(!isOpen);
|
const toggle = () => setIsOpen(!isOpen);
|
||||||
return (
|
return (
|
||||||
<Navbar className="mb-3" dark color="dark" expand="md">
|
<Navbar className="mb-3" dark color="dark" expand="md" fixed="top">
|
||||||
<NavbarToggler onClick={toggle} />
|
<NavbarToggler onClick={toggle} />
|
||||||
<Link className="pt-0 navbar-brand" to="/new/graph">
|
<Link className="pt-0 navbar-brand" to="/new/graph">
|
||||||
Prometheus
|
Prometheus
|
||||||
|
|
55
web/ui/react-app/src/hooks/useFetches.test.tsx
Normal file
55
web/ui/react-app/src/hooks/useFetches.test.tsx
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import useFetches from './useFetches';
|
||||||
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
|
|
||||||
|
describe('useFetches', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
fetchMock.resetMocks();
|
||||||
|
});
|
||||||
|
it('should can handle multiple requests', async done => {
|
||||||
|
fetchMock.mockResponse(JSON.stringify({ satus: 'success', data: { id: 1 } }));
|
||||||
|
const { result, waitForNextUpdate } = renderHook(useFetches, { initialProps: ['/foo/bar', '/foo/bar', '/foo/bar'] });
|
||||||
|
await waitForNextUpdate();
|
||||||
|
expect(result.current.response).toHaveLength(3);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
it('should can handle success flow -> isLoading=true, response=[data, data], isLoading=false', async done => {
|
||||||
|
fetchMock.mockResponse(JSON.stringify({ satus: 'success', data: { id: 1 } }));
|
||||||
|
const { result, waitForNextUpdate } = renderHook(useFetches, { initialProps: ['/foo/bar'] });
|
||||||
|
expect(result.current.isLoading).toEqual(true);
|
||||||
|
await waitForNextUpdate();
|
||||||
|
expect(result.current.response).toHaveLength(1);
|
||||||
|
expect(result.current.isLoading).toEqual(false);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
it('should isLoading remains true on empty response', async done => {
|
||||||
|
fetchMock.mockResponse(jest.fn());
|
||||||
|
const { result, waitForNextUpdate } = renderHook(useFetches, { initialProps: ['/foo/bar'] });
|
||||||
|
expect(result.current.isLoading).toEqual(true);
|
||||||
|
await waitForNextUpdate();
|
||||||
|
setTimeout(() => {
|
||||||
|
expect(result.current.isLoading).toEqual(true);
|
||||||
|
done();
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
it('should set error message when response fail', async done => {
|
||||||
|
fetchMock.mockReject(new Error('errr'));
|
||||||
|
const { result, waitForNextUpdate } = renderHook(useFetches, { initialProps: ['/foo/bar'] });
|
||||||
|
expect(result.current.isLoading).toEqual(true);
|
||||||
|
await waitForNextUpdate();
|
||||||
|
expect(result.current.error!.message).toEqual('errr');
|
||||||
|
expect(result.current.isLoading).toEqual(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
it('should throw an error if array is empty', async done => {
|
||||||
|
try {
|
||||||
|
useFetches([]);
|
||||||
|
const { result, waitForNextUpdate } = renderHook(useFetches, { initialProps: [] });
|
||||||
|
await waitForNextUpdate().then(done);
|
||||||
|
expect(result.error.message).toEqual("Doesn't have url to fetch.");
|
||||||
|
done();
|
||||||
|
} catch (e) {
|
||||||
|
} finally {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
36
web/ui/react-app/src/hooks/useFetches.tsx
Normal file
36
web/ui/react-app/src/hooks/useFetches.tsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
const useFetches = <R extends any>(urls: string[], options?: RequestInit) => {
|
||||||
|
if (!urls.length) {
|
||||||
|
throw new Error("Doesn't have url to fetch.");
|
||||||
|
}
|
||||||
|
const [response, setResponse] = useState<R[]>();
|
||||||
|
const [error, setError] = useState<Error>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
try {
|
||||||
|
const responses: R[] = await Promise.all(
|
||||||
|
urls
|
||||||
|
.map(async url => {
|
||||||
|
const res = await fetch(url, options);
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(res.statusText);
|
||||||
|
}
|
||||||
|
const result = await res.json();
|
||||||
|
return result.data;
|
||||||
|
})
|
||||||
|
.filter(Boolean) // Remove falsy values
|
||||||
|
);
|
||||||
|
setResponse(responses);
|
||||||
|
} catch (error) {
|
||||||
|
setError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchData();
|
||||||
|
}, [urls, options]);
|
||||||
|
|
||||||
|
return { response, error, isLoading: !response || !response.length };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useFetches;
|
72
web/ui/react-app/src/pages/Status.test.tsx
Normal file
72
web/ui/react-app/src/pages/Status.test.tsx
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import { Status } from '.';
|
||||||
|
import { Alert } from 'reactstrap';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import * as useFetch from '../hooks/useFetches';
|
||||||
|
import toJson from 'enzyme-to-json';
|
||||||
|
|
||||||
|
describe('Status', () => {
|
||||||
|
afterEach(() => jest.restoreAllMocks());
|
||||||
|
it('should render spinner while waiting data', () => {
|
||||||
|
const wrapper = shallow(<Status />);
|
||||||
|
expect(wrapper.find(FontAwesomeIcon)).toHaveLength(1);
|
||||||
|
});
|
||||||
|
it('should render Alert on error', () => {
|
||||||
|
(useFetch as any).default = jest.fn().mockImplementation(() => ({ error: new Error('foo') }));
|
||||||
|
const wrapper = shallow(<Status />);
|
||||||
|
expect(wrapper.find(Alert)).toHaveLength(1);
|
||||||
|
});
|
||||||
|
it('should fetch proper API endpoints', () => {
|
||||||
|
const useFetchSpy = jest.spyOn(useFetch, 'default');
|
||||||
|
shallow(<Status />);
|
||||||
|
expect(useFetchSpy).toHaveBeenCalledWith([
|
||||||
|
'../api/v1/status/runtimeinfo',
|
||||||
|
'../api/v1/status/buildinfo',
|
||||||
|
'../api/v1/alertmanagers',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
describe('Snapshot testing', () => {
|
||||||
|
const response = [
|
||||||
|
{
|
||||||
|
startTime: '2019-10-30T22:03:23.247913868+02:00',
|
||||||
|
CWD: '/home/boyskila/Desktop/prometheus',
|
||||||
|
reloadConfigSuccess: true,
|
||||||
|
lastConfigTime: '2019-10-30T22:03:23+02:00',
|
||||||
|
chunkCount: 1383,
|
||||||
|
timeSeriesCount: 461,
|
||||||
|
corruptionCount: 0,
|
||||||
|
goroutineCount: 37,
|
||||||
|
GOMAXPROCS: 4,
|
||||||
|
GOGC: '',
|
||||||
|
GODEBUG: '',
|
||||||
|
storageRetention: '15d',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: '',
|
||||||
|
revision: '',
|
||||||
|
branch: '',
|
||||||
|
buildUser: '',
|
||||||
|
buildDate: '',
|
||||||
|
goVersion: 'go1.13.3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
activeAlertmanagers: [
|
||||||
|
{ url: 'https://1.2.3.4:9093/api/v1/alerts' },
|
||||||
|
{ url: 'https://1.2.3.5:9093/api/v1/alerts' },
|
||||||
|
{ url: 'https://1.2.3.6:9093/api/v1/alerts' },
|
||||||
|
{ url: 'https://1.2.3.7:9093/api/v1/alerts' },
|
||||||
|
{ url: 'https://1.2.3.8:9093/api/v1/alerts' },
|
||||||
|
{ url: 'https://1.2.3.9:9093/api/v1/alerts' },
|
||||||
|
],
|
||||||
|
droppedAlertmanagers: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
it('should match table snapshot', () => {
|
||||||
|
(useFetch as any).default = jest.fn().mockImplementation(() => ({ response }));
|
||||||
|
const wrapper = shallow(<Status />);
|
||||||
|
expect(toJson(wrapper)).toMatchSnapshot();
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,6 +1,108 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC, Fragment } from 'react';
|
||||||
import { RouteComponentProps } from '@reach/router';
|
import { RouteComponentProps } from '@reach/router';
|
||||||
|
import { Table, Alert } from 'reactstrap';
|
||||||
|
import useFetches from '../hooks/useFetches';
|
||||||
|
|
||||||
const Status: FC<RouteComponentProps> = () => <div>Status page</div>;
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
export default Status;
|
const ENDPOINTS = ['../api/v1/status/runtimeinfo', '../api/v1/status/buildinfo', '../api/v1/alertmanagers'];
|
||||||
|
const sectionTitles = ['Runtime Information', 'Build Information', 'Alertmanagers'];
|
||||||
|
|
||||||
|
interface StatusConfig {
|
||||||
|
[k: string]: { title?: string; customizeValue?: (v: any) => any; customRow?: boolean; skip?: boolean };
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatusPageState = Array<{ [k: string]: string }>;
|
||||||
|
|
||||||
|
export const statusConfig: StatusConfig = {
|
||||||
|
startTime: { title: 'Start time', customizeValue: (v: string) => new Date(v).toUTCString() },
|
||||||
|
CWD: { title: 'Working directory' },
|
||||||
|
reloadConfigSuccess: {
|
||||||
|
title: 'Configuration reload',
|
||||||
|
customizeValue: (v: boolean) => (v ? 'Successful' : 'Unsuccessful'),
|
||||||
|
},
|
||||||
|
lastConfigTime: { title: 'Last successful configuration reload' },
|
||||||
|
chunkCount: { title: 'Head chunks' },
|
||||||
|
timeSeriesCount: { title: 'Head time series' },
|
||||||
|
corruptionCount: { title: 'WAL corruptions' },
|
||||||
|
goroutineCount: { title: 'Goroutines' },
|
||||||
|
storageRetention: { title: 'Storage retention' },
|
||||||
|
activeAlertmanagers: {
|
||||||
|
customRow: true,
|
||||||
|
customizeValue: (alertMgrs: { url: string }[]) => {
|
||||||
|
return (
|
||||||
|
<Fragment key="alert-managers">
|
||||||
|
<tr>
|
||||||
|
<th>Endpoint</th>
|
||||||
|
</tr>
|
||||||
|
{alertMgrs.map(({ url }) => {
|
||||||
|
const { origin, pathname } = new URL(url);
|
||||||
|
return (
|
||||||
|
<tr key={url}>
|
||||||
|
<td>
|
||||||
|
<a href={url}>{origin}</a>
|
||||||
|
{pathname}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
droppedAlertmanagers: { skip: true },
|
||||||
|
};
|
||||||
|
|
||||||
|
const Status = () => {
|
||||||
|
const { response: data, error, isLoading } = useFetches<StatusPageState[]>(ENDPOINTS);
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<Alert color="danger">
|
||||||
|
<strong>Error:</strong> Error fetching status: {error.message}
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
} else if (isLoading) {
|
||||||
|
return (
|
||||||
|
<FontAwesomeIcon
|
||||||
|
size="3x"
|
||||||
|
icon={faSpinner}
|
||||||
|
spin
|
||||||
|
className="position-absolute"
|
||||||
|
style={{ transform: 'translate(-50%, -50%)', top: '50%', left: '50%' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
? data.map((statuses, i) => {
|
||||||
|
return (
|
||||||
|
<Fragment key={i}>
|
||||||
|
<h2>{sectionTitles[i]}</h2>
|
||||||
|
<Table className="h-auto" size="sm" bordered striped>
|
||||||
|
<tbody>
|
||||||
|
{Object.entries(statuses).map(([k, v]) => {
|
||||||
|
const { title = k, customizeValue = (val: any) => val, customRow, skip } = statusConfig[k] || {};
|
||||||
|
if (skip) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (customRow) {
|
||||||
|
return customizeValue(v);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<tr key={k}>
|
||||||
|
<th className="capitalize-title" style={{ width: '35%' }}>
|
||||||
|
{title}
|
||||||
|
</th>
|
||||||
|
<td className="text-break">{customizeValue(v)}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Status as FC<RouteComponentProps>;
|
||||||
|
|
465
web/ui/react-app/src/pages/__snapshots__/Status.test.tsx.snap
Normal file
465
web/ui/react-app/src/pages/__snapshots__/Status.test.tsx.snap
Normal file
|
@ -0,0 +1,465 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Status Snapshot testing should match table snapshot 1`] = `
|
||||||
|
Array [
|
||||||
|
<Fragment
|
||||||
|
key="0"
|
||||||
|
>
|
||||||
|
<h2>
|
||||||
|
Runtime Information
|
||||||
|
</h2>
|
||||||
|
<Table
|
||||||
|
bordered={true}
|
||||||
|
className="h-auto"
|
||||||
|
responsiveTag="div"
|
||||||
|
size="sm"
|
||||||
|
striped={true}
|
||||||
|
tag="table"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
key="startTime"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
className="capitalize-title"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"width": "35%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Start time
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
className="text-break"
|
||||||
|
>
|
||||||
|
Wed, 30 Oct 2019 20:03:23 GMT
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
key="CWD"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
className="capitalize-title"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"width": "35%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Working directory
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
className="text-break"
|
||||||
|
>
|
||||||
|
/home/boyskila/Desktop/prometheus
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
key="reloadConfigSuccess"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
className="capitalize-title"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"width": "35%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Configuration reload
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
className="text-break"
|
||||||
|
>
|
||||||
|
Successful
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
key="lastConfigTime"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
className="capitalize-title"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"width": "35%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Last successful configuration reload
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
className="text-break"
|
||||||
|
>
|
||||||
|
2019-10-30T22:03:23+02:00
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
key="chunkCount"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
className="capitalize-title"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"width": "35%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Head chunks
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
className="text-break"
|
||||||
|
>
|
||||||
|
1383
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
key="timeSeriesCount"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
className="capitalize-title"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"width": "35%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Head time series
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
className="text-break"
|
||||||
|
>
|
||||||
|
461
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
key="corruptionCount"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
className="capitalize-title"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"width": "35%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
WAL corruptions
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
className="text-break"
|
||||||
|
>
|
||||||
|
0
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
key="goroutineCount"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
className="capitalize-title"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"width": "35%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Goroutines
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
className="text-break"
|
||||||
|
>
|
||||||
|
37
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
key="GOMAXPROCS"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
className="capitalize-title"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"width": "35%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
GOMAXPROCS
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
className="text-break"
|
||||||
|
>
|
||||||
|
4
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
key="GOGC"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
className="capitalize-title"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"width": "35%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
GOGC
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
className="text-break"
|
||||||
|
/>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
key="GODEBUG"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
className="capitalize-title"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"width": "35%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
GODEBUG
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
className="text-break"
|
||||||
|
/>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
key="storageRetention"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
className="capitalize-title"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"width": "35%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Storage retention
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
className="text-break"
|
||||||
|
>
|
||||||
|
15d
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
</Fragment>,
|
||||||
|
<Fragment
|
||||||
|
key="1"
|
||||||
|
>
|
||||||
|
<h2>
|
||||||
|
Build Information
|
||||||
|
</h2>
|
||||||
|
<Table
|
||||||
|
bordered={true}
|
||||||
|
className="h-auto"
|
||||||
|
responsiveTag="div"
|
||||||
|
size="sm"
|
||||||
|
striped={true}
|
||||||
|
tag="table"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
key="version"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
className="capitalize-title"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"width": "35%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
version
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
className="text-break"
|
||||||
|
/>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
key="revision"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
className="capitalize-title"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"width": "35%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
revision
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
className="text-break"
|
||||||
|
/>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
key="branch"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
className="capitalize-title"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"width": "35%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
branch
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
className="text-break"
|
||||||
|
/>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
key="buildUser"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
className="capitalize-title"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"width": "35%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
buildUser
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
className="text-break"
|
||||||
|
/>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
key="buildDate"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
className="capitalize-title"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"width": "35%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
buildDate
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
className="text-break"
|
||||||
|
/>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
key="goVersion"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
className="capitalize-title"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"width": "35%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
goVersion
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
className="text-break"
|
||||||
|
>
|
||||||
|
go1.13.3
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
</Fragment>,
|
||||||
|
<Fragment
|
||||||
|
key="2"
|
||||||
|
>
|
||||||
|
<h2>
|
||||||
|
Alertmanagers
|
||||||
|
</h2>
|
||||||
|
<Table
|
||||||
|
bordered={true}
|
||||||
|
className="h-auto"
|
||||||
|
responsiveTag="div"
|
||||||
|
size="sm"
|
||||||
|
striped={true}
|
||||||
|
tag="table"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
Endpoint
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
key="https://1.2.3.4:9093/api/v1/alerts"
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
<a
|
||||||
|
href="https://1.2.3.4:9093/api/v1/alerts"
|
||||||
|
>
|
||||||
|
https://1.2.3.4:9093
|
||||||
|
</a>
|
||||||
|
/api/v1/alerts
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
key="https://1.2.3.5:9093/api/v1/alerts"
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
<a
|
||||||
|
href="https://1.2.3.5:9093/api/v1/alerts"
|
||||||
|
>
|
||||||
|
https://1.2.3.5:9093
|
||||||
|
</a>
|
||||||
|
/api/v1/alerts
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
key="https://1.2.3.6:9093/api/v1/alerts"
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
<a
|
||||||
|
href="https://1.2.3.6:9093/api/v1/alerts"
|
||||||
|
>
|
||||||
|
https://1.2.3.6:9093
|
||||||
|
</a>
|
||||||
|
/api/v1/alerts
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
key="https://1.2.3.7:9093/api/v1/alerts"
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
<a
|
||||||
|
href="https://1.2.3.7:9093/api/v1/alerts"
|
||||||
|
>
|
||||||
|
https://1.2.3.7:9093
|
||||||
|
</a>
|
||||||
|
/api/v1/alerts
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
key="https://1.2.3.8:9093/api/v1/alerts"
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
<a
|
||||||
|
href="https://1.2.3.8:9093/api/v1/alerts"
|
||||||
|
>
|
||||||
|
https://1.2.3.8:9093
|
||||||
|
</a>
|
||||||
|
/api/v1/alerts
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
key="https://1.2.3.9:9093/api/v1/alerts"
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
<a
|
||||||
|
href="https://1.2.3.9:9093/api/v1/alerts"
|
||||||
|
>
|
||||||
|
https://1.2.3.9:9093
|
||||||
|
</a>
|
||||||
|
/api/v1/alerts
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
</Fragment>,
|
||||||
|
]
|
||||||
|
`;
|
|
@ -20,6 +20,6 @@
|
||||||
"jsx": "preserve"
|
"jsx": "preserve"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src"
|
"src", "test"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -857,7 +857,7 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.2"
|
regenerator-runtime "^0.13.2"
|
||||||
|
|
||||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.5":
|
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.4":
|
||||||
version "7.6.3"
|
version "7.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.6.3.tgz#935122c74c73d2240cafd32ddb5fc2a6cd35cf1f"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.6.3.tgz#935122c74c73d2240cafd32ddb5fc2a6cd35cf1f"
|
||||||
integrity sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA==
|
integrity sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA==
|
||||||
|
@ -1282,6 +1282,14 @@
|
||||||
"@svgr/plugin-svgo" "^4.3.1"
|
"@svgr/plugin-svgo" "^4.3.1"
|
||||||
loader-utils "^1.2.3"
|
loader-utils "^1.2.3"
|
||||||
|
|
||||||
|
"@testing-library/react-hooks@^3.1.1":
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-3.1.1.tgz#5c93e463c0252bea6ac237ec8d9c982c27d67208"
|
||||||
|
integrity sha512-HANnmA68/i6RwZn9j7pcbAg438PoDToftRQ1CH0j893WuQGtENFm57GKTagtmXXDN5gKh3rVbN1GH6HDvHbk6A==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.5.4"
|
||||||
|
"@types/testing-library__react-hooks" "^2.0.0"
|
||||||
|
|
||||||
"@types/babel__core@^7.1.0":
|
"@types/babel__core@^7.1.0":
|
||||||
version "7.1.3"
|
version "7.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.3.tgz#e441ea7df63cd080dfcd02ab199e6d16a735fc30"
|
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.3.tgz#e441ea7df63cd080dfcd02ab199e6d16a735fc30"
|
||||||
|
@ -1470,6 +1478,13 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
|
"@types/react-test-renderer@*":
|
||||||
|
version "16.9.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.9.1.tgz#9d432c46c515ebe50c45fa92c6fb5acdc22e39c4"
|
||||||
|
integrity sha512-nCXQokZN1jp+QkoDNmDZwoWpKY8HDczqevIDO4Uv9/s9rbGPbSpy8Uaxa5ixHKkcm/Wt0Y9C3wCxZivh4Al+rQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react@*", "@types/react@^16.8.2":
|
"@types/react@*", "@types/react@^16.8.2":
|
||||||
version "16.9.9"
|
version "16.9.9"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.9.tgz#a62c6f40f04bc7681be5e20975503a64fe783c3a"
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.9.tgz#a62c6f40f04bc7681be5e20975503a64fe783c3a"
|
||||||
|
@ -1508,6 +1523,14 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
|
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
|
||||||
integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
|
integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
|
||||||
|
|
||||||
|
"@types/testing-library__react-hooks@^2.0.0":
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/testing-library__react-hooks/-/testing-library__react-hooks-2.0.0.tgz#7b289d64945517ae8ba9cbcb0c5b282432aaeffa"
|
||||||
|
integrity sha512-YUVqXGCChJKEJ4aAnMXqPCq0NfPAFVsJeGIb2y/iiMjxwyu+45+vR+AHOwjJHHKEHeC0ZhOGrZ5gSEmaJe4tyQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/react" "*"
|
||||||
|
"@types/react-test-renderer" "*"
|
||||||
|
|
||||||
"@types/yargs-parser@*":
|
"@types/yargs-parser@*":
|
||||||
version "13.1.0"
|
version "13.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.1.0.tgz#c563aa192f39350a1d18da36c5a8da382bbd8228"
|
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.1.0.tgz#c563aa192f39350a1d18da36c5a8da382bbd8228"
|
||||||
|
@ -3968,6 +3991,13 @@ enzyme-shallow-equal@^1.0.0:
|
||||||
has "^1.0.3"
|
has "^1.0.3"
|
||||||
object-is "^1.0.1"
|
object-is "^1.0.1"
|
||||||
|
|
||||||
|
enzyme-to-json@^3.4.3:
|
||||||
|
version "3.4.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/enzyme-to-json/-/enzyme-to-json-3.4.3.tgz#ed4386f48768ed29e2d1a2910893542c34e7e0af"
|
||||||
|
integrity sha512-jqNEZlHqLdz7OTpXSzzghArSS3vigj67IU/fWkPyl1c0TCj9P5s6Ze0kRkYZWNEoCqCR79xlQbigYlMx5erh8A==
|
||||||
|
dependencies:
|
||||||
|
lodash "^4.17.15"
|
||||||
|
|
||||||
enzyme@^3.10.0:
|
enzyme@^3.10.0:
|
||||||
version "3.10.0"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.10.0.tgz#7218e347c4a7746e133f8e964aada4a3523452f6"
|
resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.10.0.tgz#7218e347c4a7746e133f8e964aada4a3523452f6"
|
||||||
|
@ -9104,7 +9134,7 @@ react-scripts@^3.2.0:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "2.0.7"
|
fsevents "2.0.7"
|
||||||
|
|
||||||
react-test-renderer@^16.0.0-0:
|
react-test-renderer@^16.0.0-0, react-test-renderer@^16.9.0:
|
||||||
version "16.11.0"
|
version "16.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.11.0.tgz#72574566496462c808ac449b0287a4c0a1a7d8f8"
|
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.11.0.tgz#72574566496462c808ac449b0287a4c0a1a7d8f8"
|
||||||
integrity sha512-nh9gDl8R4ut+ZNNb2EeKO5VMvTKxwzurbSMuGBoKtjpjbg8JK/u3eVPVNi1h1Ue+eYK9oSzJjb+K3lzLxyA4ag==
|
integrity sha512-nh9gDl8R4ut+ZNNb2EeKO5VMvTKxwzurbSMuGBoKtjpjbg8JK/u3eVPVNi1h1Ue+eYK9oSzJjb+K3lzLxyA4ag==
|
||||||
|
|
56
web/web.go
56
web/web.go
|
@ -163,6 +163,9 @@ func (m *metrics) instrumentHandler(handlerName string, handler http.HandlerFunc
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PrometheusVersion contains build information about Prometheus.
|
||||||
|
type PrometheusVersion = api_v1.PrometheusVersion
|
||||||
|
|
||||||
// Handler serves various HTTP endpoints of the Prometheus server
|
// Handler serves various HTTP endpoints of the Prometheus server
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
|
@ -206,16 +209,6 @@ func (h *Handler) ApplyConfig(conf *config.Config) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrometheusVersion contains build information about Prometheus.
|
|
||||||
type PrometheusVersion struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
Revision string `json:"revision"`
|
|
||||||
Branch string `json:"branch"`
|
|
||||||
BuildUser string `json:"buildUser"`
|
|
||||||
BuildDate string `json:"buildDate"`
|
|
||||||
GoVersion string `json:"goVersion"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Options for the web Handler.
|
// Options for the web Handler.
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Context context.Context
|
Context context.Context
|
||||||
|
@ -310,6 +303,8 @@ func New(logger log.Logger, o *Options) *Handler {
|
||||||
h.options.RemoteReadConcurrencyLimit,
|
h.options.RemoteReadConcurrencyLimit,
|
||||||
h.options.RemoteReadBytesInFrame,
|
h.options.RemoteReadBytesInFrame,
|
||||||
h.options.CORSOrigin,
|
h.options.CORSOrigin,
|
||||||
|
h.runtimeInfo,
|
||||||
|
h.versionInfo,
|
||||||
)
|
)
|
||||||
|
|
||||||
if o.RoutePrefix != "/" {
|
if o.RoutePrefix != "/" {
|
||||||
|
@ -744,6 +739,47 @@ func (h *Handler) status(w http.ResponseWriter, r *http.Request) {
|
||||||
h.executeTemplate(w, "status.html", status)
|
h.executeTemplate(w, "status.html", status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) runtimeInfo() (api_v1.RuntimeInfo, error) {
|
||||||
|
status := api_v1.RuntimeInfo{
|
||||||
|
StartTime: h.birth,
|
||||||
|
CWD: h.cwd,
|
||||||
|
GoroutineCount: runtime.NumGoroutine(),
|
||||||
|
GOMAXPROCS: runtime.GOMAXPROCS(0),
|
||||||
|
GOGC: os.Getenv("GOGC"),
|
||||||
|
GODEBUG: os.Getenv("GODEBUG"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.options.TSDBCfg.RetentionDuration != 0 {
|
||||||
|
status.StorageRetention = h.options.TSDBCfg.RetentionDuration.String()
|
||||||
|
}
|
||||||
|
if h.options.TSDBCfg.MaxBytes != 0 {
|
||||||
|
if status.StorageRetention != "" {
|
||||||
|
status.StorageRetention = status.StorageRetention + " or "
|
||||||
|
}
|
||||||
|
status.StorageRetention = status.StorageRetention + h.options.TSDBCfg.MaxBytes.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics, err := h.gatherer.Gather()
|
||||||
|
if err != nil {
|
||||||
|
return status, errors.Errorf("error gathering runtime status: %s", err)
|
||||||
|
}
|
||||||
|
for _, mF := range metrics {
|
||||||
|
switch *mF.Name {
|
||||||
|
case "prometheus_tsdb_head_chunks":
|
||||||
|
status.ChunkCount = int64(toFloat64(mF))
|
||||||
|
case "prometheus_tsdb_head_series":
|
||||||
|
status.TimeSeriesCount = int64(toFloat64(mF))
|
||||||
|
case "prometheus_tsdb_wal_corruptions_total":
|
||||||
|
status.CorruptionCount = int64(toFloat64(mF))
|
||||||
|
case "prometheus_config_last_reload_successful":
|
||||||
|
status.ReloadConfigSuccess = toFloat64(mF) != 0
|
||||||
|
case "prometheus_config_last_reload_success_timestamp_seconds":
|
||||||
|
status.LastConfigTime = time.Unix(int64(toFloat64(mF)), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return status, nil
|
||||||
|
}
|
||||||
|
|
||||||
func toFloat64(f *io_prometheus_client.MetricFamily) float64 {
|
func toFloat64(f *io_prometheus_client.MetricFamily) float64 {
|
||||||
m := *f.Metric[0]
|
m := *f.Metric[0]
|
||||||
if m.Gauge != nil {
|
if m.Gauge != nil {
|
||||||
|
|
Loading…
Reference in a new issue