>] {
4 | const [value, setValue] = useState(() =>
5 | JSON.parse(localStorage.getItem(localStorageKey) || JSON.stringify(initialState))
6 | );
7 |
8 | useEffect(() => {
9 | const serializedState = JSON.stringify(value);
10 | localStorage.setItem(localStorageKey, serializedState);
11 | }, [localStorageKey, value]);
12 |
13 | return [value, setValue];
14 | }
15 |
--------------------------------------------------------------------------------
/web/ui/react-app/src/hooks/useMedia.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | // A hook to determine whether a CSS media query finds any matches.
4 | const useMedia = (query: string): boolean => {
5 | const mediaQuery = window.matchMedia(query);
6 | const [matches, setMatches] = useState(mediaQuery.matches);
7 |
8 | useEffect(() => {
9 | const handler = () => setMatches(mediaQuery.matches);
10 | mediaQuery.addEventListener('change', handler);
11 | return () => mediaQuery.removeEventListener('change', handler);
12 | }, [mediaQuery]);
13 |
14 | return matches;
15 | };
16 |
17 | export default useMedia;
18 |
--------------------------------------------------------------------------------
/web/ui/react-app/src/pages/agent/Agent.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 |
3 | const Agent: FC = () => {
4 | return (
5 | <>
6 | Prometheus Agent
7 |
8 | This Prometheus instance is running in agent mode. In this mode, Prometheus is only used to scrape
9 | discovered targets and forward the scraped metrics to remote write endpoints.
10 |
11 | Some features are not available in this mode, such as querying and alerting.
12 | >
13 | );
14 | };
15 |
16 | export default Agent;
17 |
--------------------------------------------------------------------------------
/web/ui/react-app/src/pages/graph/QueryStatsView.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { shallow } from 'enzyme';
3 | import QueryStatsView from './QueryStatsView';
4 |
5 | describe('QueryStatsView', () => {
6 | it('renders props as query stats', () => {
7 | const queryStatsProps = {
8 | loadTime: 100,
9 | resolution: 5,
10 | resultSeries: 10000,
11 | };
12 | const queryStatsView = shallow();
13 | expect(queryStatsView.prop('className')).toEqual('query-stats');
14 | expect(queryStatsView.children().prop('className')).toEqual('float-right');
15 | expect(queryStatsView.children().text()).toEqual('Load time: 100ms Resolution: 5s Result series: 10000');
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/web/ui/react-app/src/pages/graph/QueryStatsView.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 |
3 | export interface QueryStats {
4 | loadTime: number;
5 | resolution: number;
6 | resultSeries: number;
7 | }
8 |
9 | const QueryStatsView: FC = (props) => {
10 | const { loadTime, resolution, resultSeries } = props;
11 |
12 | return (
13 |
14 |
15 | Load time: {loadTime}ms Resolution: {resolution}s Result series: {resultSeries}
16 |
17 |
18 | );
19 | };
20 |
21 | export default QueryStatsView;
22 |
--------------------------------------------------------------------------------
/web/ui/react-app/src/pages/rules/Rules.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import { useFetch } from '../../hooks/useFetch';
3 | import { withStatusIndicator } from '../../components/withStatusIndicator';
4 | import { RulesMap, RulesContent } from './RulesContent';
5 | import { usePathPrefix } from '../../contexts/PathPrefixContext';
6 | import { API_PATH } from '../../constants/constants';
7 |
8 | const RulesWithStatusIndicator = withStatusIndicator(RulesContent);
9 |
10 | const Rules: FC = () => {
11 | const pathPrefix = usePathPrefix();
12 | const { response, error, isLoading } = useFetch(`${pathPrefix}/${API_PATH}/rules`);
13 |
14 | return ;
15 | };
16 |
17 | export default Rules;
18 |
--------------------------------------------------------------------------------
/web/ui/react-app/src/pages/targets/ScrapePoolPanel.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | margin-top: -12px;
3 | }
4 |
5 | .title {
6 | font-size: 20px;
7 | font-weight: bold;
8 | cursor: pointer;
9 | }
10 |
11 | .normal {
12 | composes: title;
13 | }
14 |
15 | .danger {
16 | composes: title;
17 | color: rgb(242, 65, 65);
18 | }
19 |
20 | .table {
21 | width: 100%;
22 | }
23 |
24 | .cell {
25 | height: auto;
26 | word-wrap: break-word;
27 | word-break: break-all;
28 | }
29 |
30 | .endpoint, .labels {
31 | composes: cell;
32 | width: 25%;
33 | }
34 |
35 | .state, .last-scrape {
36 | composes: cell;
37 | width: 10%;
38 | }
39 |
40 | .errors {
41 | composes: cell;
42 | width: 30%;
43 | }
44 |
--------------------------------------------------------------------------------
/web/ui/react-app/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/web/ui/react-app/src/setupProxy.js:
--------------------------------------------------------------------------------
1 | const { createProxyMiddleware } = require('http-proxy-middleware');
2 |
3 | module.exports = function(app) {
4 | app.use(
5 | '/api',
6 | createProxyMiddleware({
7 | target: 'http://localhost:9090',
8 | changeOrigin: true,
9 | })
10 | );
11 | };
12 |
--------------------------------------------------------------------------------
/web/ui/react-app/src/themes/app.scss:
--------------------------------------------------------------------------------
1 | /* This file contains styles that are applied to root document, which cannot be
2 | nested under theme selectors. */
3 |
4 | html {
5 | /* https://github.com/prometheus/prometheus/issues/7434 */
6 | /* Scroll to hash-fragment-links counting the fixed navbar 40px tall with 16px padding */
7 | scroll-padding-top: 56px;
8 | }
9 |
10 | /* Font used for autocompletion item icons. */
11 | @font-face {
12 | font-family: 'codicon';
13 | src: local('codicon'), url(../fonts/codicon.ttf) format('truetype');
14 | }
15 |
--------------------------------------------------------------------------------
/web/ui/react-app/src/themes/dark.scss:
--------------------------------------------------------------------------------
1 | @import 'bootstrap_dark';
2 |
3 | @import '~bootstrap/scss/functions';
4 | @import '~bootstrap/scss/variables';
5 | @import '~@forevolve/bootstrap-dark/scss/_dark-variables.scss';
6 |
7 | $alert-cell-color: $white;
8 | $rule-cell-bg: $gray-900;
9 |
10 | $config-yaml-color: $black;
11 | $config-yaml-bg: $gray-500;
12 | $config-yaml-border: $gray-700;
13 |
14 | $query-stats-color: lighten($secondary, 20%);
15 |
16 | $metrics-explorer-bg: $dropdown-link-hover-bg;
17 |
18 | $clear-time-btn-bg: $secondary;
19 |
20 | $checked-checkbox-color: #60a5fa;
21 |
22 | $histogram-chart-axis-color: $gray-700;
23 | $histogram-chart-grid-color: $gray-600;
24 | $histogram-chart-hover-color: $gray-400;
25 |
26 | .bootstrap-dark {
27 | @import './shared';
28 | }
29 |
--------------------------------------------------------------------------------
/web/ui/react-app/src/themes/light.scss:
--------------------------------------------------------------------------------
1 | @import 'bootstrap_light';
2 |
3 | @import '~bootstrap/scss/functions';
4 | @import '~bootstrap/scss/variables';
5 |
6 | $alert-cell-color: inherit;
7 | $rule-cell-bg: #f5f5f5;
8 |
9 | $config-yaml-color: #333;
10 | $config-yaml-bg: #f5f5f5;
11 | $config-yaml-border: #ccc;
12 |
13 | $query-stats-color: #71808e;
14 |
15 | $metrics-explorer-bg: #efefef;
16 |
17 | $clear-time-btn-bg: $white;
18 |
19 | $checked-checkbox-color: #286090;
20 |
21 | $histogram-chart-axis-color: $gray-700;
22 | $histogram-chart-grid-color: $gray-600;
23 | $histogram-chart-hover-color: $gray-400;
24 |
25 | .bootstrap {
26 | @import './shared';
27 | }
28 |
--------------------------------------------------------------------------------
/web/ui/react-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "noEmit": true,
20 | "jsx": "react-jsx",
21 | "noFallthroughCasesInSwitch": true
22 | },
23 | "include": [
24 | "src",
25 | "test",
26 | "react-app-env.d.ts"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------