diff --git a/web/ui/react-app/public/index.html b/web/ui/react-app/public/index.html
index cc3336fa25..c19bb569bf 100755
--- a/web/ui/react-app/public/index.html
+++ b/web/ui/react-app/public/index.html
@@ -10,16 +10,11 @@
diff --git a/web/ui/react-app/src/App.test.tsx b/web/ui/react-app/src/App.test.tsx
index 705e0bc6b8..e96bbf7a40 100755
--- a/web/ui/react-app/src/App.test.tsx
+++ b/web/ui/react-app/src/App.test.tsx
@@ -7,7 +7,7 @@ import { Router } from '@reach/router';
import { Alerts, Config, Flags, Rules, ServiceDiscovery, Status, Targets, TSDBStatus, PanelList } from './pages';
describe('App', () => {
- const app = shallow();
+ const app = shallow();
it('navigates', () => {
expect(app.find(Navigation)).toHaveLength(1);
@@ -16,7 +16,6 @@ describe('App', () => {
[Alerts, Config, Flags, Rules, ServiceDiscovery, Status, Targets, TSDBStatus, PanelList].forEach(component => {
const c = app.find(component);
expect(c).toHaveLength(1);
- expect(c.prop('pathPrefix')).toBe('/path/prefix');
});
expect(app.find(Router)).toHaveLength(1);
expect(app.find(Container)).toHaveLength(1);
diff --git a/web/ui/react-app/src/App.tsx b/web/ui/react-app/src/App.tsx
index d172773e0d..29adb54b6f 100755
--- a/web/ui/react-app/src/App.tsx
+++ b/web/ui/react-app/src/App.tsx
@@ -5,36 +5,62 @@ import { Container } from 'reactstrap';
import './App.css';
import { Router, Redirect } from '@reach/router';
import { Alerts, Config, Flags, Rules, ServiceDiscovery, Status, Targets, TSDBStatus, PanelList } from './pages';
-import PathPrefixProps from './types/PathPrefixProps';
+import { PathPrefixContext } from './contexts/PathPrefixContext';
interface AppProps {
consolesLink: string | null;
}
-const App: FC = ({ pathPrefix, consolesLink }) => {
- return (
- <>
-
-
-
-
+const App: FC = ({ consolesLink }) => {
+ // This dynamically/generically determines the pathPrefix by stripping the first known
+ // endpoint suffix from the window location path. It works out of the box for both direct
+ // hosting and reverse proxy deployments with no additional configurations required.
+ let basePath = window.location.pathname;
+ const paths = [
+ '/graph',
+ '/alerts',
+ '/status',
+ '/tsdb-status',
+ '/flags',
+ '/config',
+ '/rules',
+ '/targets',
+ '/service-discovery',
+ ];
+ if (basePath.endsWith('/')) {
+ basePath = basePath.slice(0, -1);
+ }
+ if (basePath.length > 1) {
+ for (let i = 0; i < paths.length; i++) {
+ if (basePath.endsWith(paths[i])) {
+ basePath = basePath.slice(0, basePath.length - paths[i].length);
+ break;
+ }
+ }
+ }
+ return (
+
+
+
+
+
{/*
- NOTE: Any route added here needs to also be added to the list of
- React-handled router paths ("reactRouterPaths") in /web/web.go.
- */}
-
-
-
-
-
-
-
-
-
+ NOTE: Any route added here needs to also be added to the list of
+ React-handled router paths ("reactRouterPaths") in /web/web.go.
+ */}
+
+
+
+
+
+
+
+
+
- >
+
);
};
diff --git a/web/ui/react-app/src/Navbar.tsx b/web/ui/react-app/src/Navbar.tsx
index 51c072add4..a425c33cb2 100644
--- a/web/ui/react-app/src/Navbar.tsx
+++ b/web/ui/react-app/src/Navbar.tsx
@@ -12,19 +12,20 @@ import {
DropdownMenu,
DropdownItem,
} from 'reactstrap';
-import PathPrefixProps from './types/PathPrefixProps';
+import { usePathPrefix } from './contexts/PathPrefixContext';
interface NavbarProps {
consolesLink: string | null;
}
-const Navigation: FC = ({ pathPrefix, consolesLink }) => {
+const Navigation: FC = ({ consolesLink }) => {
const [isOpen, setIsOpen] = useState(false);
const toggle = () => setIsOpen(!isOpen);
+ const pathPrefix = usePathPrefix();
return (
-
+
Prometheus
@@ -35,12 +36,12 @@ const Navigation: FC = ({ pathPrefix, consolesLin
)}
-
+
Alerts
-
+
Graph
@@ -49,25 +50,25 @@ const Navigation: FC = ({ pathPrefix, consolesLin
Status
-
+
Runtime & Build Information
-
+
TSDB Status
-
+
Command-Line Flags
-
+
Configuration
-
+
Rules
-
+
Targets
-
+
Service Discovery
@@ -76,7 +77,7 @@ const Navigation: FC = ({ pathPrefix, consolesLin
Help
- Classic UI
+ Classic UI
diff --git a/web/ui/react-app/src/constants/constants.tsx b/web/ui/react-app/src/constants/constants.tsx
new file mode 100644
index 0000000000..5f0d8b9b4a
--- /dev/null
+++ b/web/ui/react-app/src/constants/constants.tsx
@@ -0,0 +1 @@
+export const API_PATH = '../api/v1';
diff --git a/web/ui/react-app/src/contexts/PathPrefixContext.tsx b/web/ui/react-app/src/contexts/PathPrefixContext.tsx
new file mode 100644
index 0000000000..38e4eec096
--- /dev/null
+++ b/web/ui/react-app/src/contexts/PathPrefixContext.tsx
@@ -0,0 +1,9 @@
+import React from 'react';
+
+const PathPrefixContext = React.createContext('');
+
+function usePathPrefix() {
+ return React.useContext(PathPrefixContext);
+}
+
+export { usePathPrefix, PathPrefixContext };
diff --git a/web/ui/react-app/src/index.tsx b/web/ui/react-app/src/index.tsx
index f6e672829a..9398bc8983 100755
--- a/web/ui/react-app/src/index.tsx
+++ b/web/ui/react-app/src/index.tsx
@@ -6,20 +6,10 @@ import 'bootstrap/dist/css/bootstrap.min.css';
import { isPresent } from './utils';
// Declared/defined in public/index.html, value replaced by Prometheus when serving bundle.
-declare const GLOBAL_PATH_PREFIX: string;
declare const GLOBAL_CONSOLES_LINK: string;
-let prefix = GLOBAL_PATH_PREFIX;
let consolesLink: string | null = GLOBAL_CONSOLES_LINK;
-if (GLOBAL_PATH_PREFIX === 'PATH_PREFIX_PLACEHOLDER' || GLOBAL_PATH_PREFIX === '/' || !isPresent(GLOBAL_PATH_PREFIX)) {
- // Either we are running the app outside of Prometheus, so the placeholder value in
- // the index.html didn't get replaced, or we have a '/' prefix, which we also need to
- // normalize to '' to make concatenations work (prefixes like '/foo/bar/' already get
- // their trailing slash stripped by Prometheus).
- prefix = '';
-}
-
if (
GLOBAL_CONSOLES_LINK === 'CONSOLES_LINK_PLACEHOLDER' ||
GLOBAL_CONSOLES_LINK === '' ||
@@ -28,4 +18,4 @@ if (
consolesLink = null;
}
-ReactDOM.render(, document.getElementById('root'));
+ReactDOM.render(, document.getElementById('root'));
diff --git a/web/ui/react-app/src/pages/alerts/Alerts.tsx b/web/ui/react-app/src/pages/alerts/Alerts.tsx
index c6fef5b767..a1624d7a87 100644
--- a/web/ui/react-app/src/pages/alerts/Alerts.tsx
+++ b/web/ui/react-app/src/pages/alerts/Alerts.tsx
@@ -1,14 +1,16 @@
import React, { FC } from 'react';
import { RouteComponentProps } from '@reach/router';
-import PathPrefixProps from '../../types/PathPrefixProps';
import { useFetch } from '../../hooks/useFetch';
import { withStatusIndicator } from '../../components/withStatusIndicator';
import AlertsContent, { RuleStatus, AlertsProps } from './AlertContents';
+import { usePathPrefix } from '../../contexts/PathPrefixContext';
+import { API_PATH } from '../../constants/constants';
const AlertsWithStatusIndicator = withStatusIndicator(AlertsContent);
-const Alerts: FC = ({ pathPrefix = '' }) => {
- const { response, error, isLoading } = useFetch(`${pathPrefix}/api/v1/rules?type=alert`);
+const Alerts: FC = () => {
+ const pathPrefix = usePathPrefix();
+ const { response, error, isLoading } = useFetch(`${pathPrefix}/${API_PATH}/rules?type=alert`);
const ruleStatsCount: RuleStatus = {
inactive: 0,
diff --git a/web/ui/react-app/src/pages/config/Config.tsx b/web/ui/react-app/src/pages/config/Config.tsx
index 1636668e6c..00bb56d19c 100644
--- a/web/ui/react-app/src/pages/config/Config.tsx
+++ b/web/ui/react-app/src/pages/config/Config.tsx
@@ -2,11 +2,12 @@ import React, { useState, FC } from 'react';
import { RouteComponentProps } from '@reach/router';
import { Button } from 'reactstrap';
import CopyToClipboard from 'react-copy-to-clipboard';
-import PathPrefixProps from '../../types/PathPrefixProps';
import './Config.css';
import { withStatusIndicator } from '../../components/withStatusIndicator';
import { useFetch } from '../../hooks/useFetch';
+import { usePathPrefix } from '../../contexts/PathPrefixContext';
+import { API_PATH } from '../../constants/constants';
type YamlConfig = { yaml?: string };
@@ -44,8 +45,9 @@ export const ConfigContent: FC = ({ error, data }) => {
);
};
-const Config: FC = ({ pathPrefix }) => {
- const { response, error } = useFetch(`${pathPrefix}/api/v1/status/config`);
+const Config: FC = () => {
+ const pathPrefix = usePathPrefix();
+ const { response, error } = useFetch(`${pathPrefix}/${API_PATH}/status/config`);
return ;
};
diff --git a/web/ui/react-app/src/pages/flags/Flags.tsx b/web/ui/react-app/src/pages/flags/Flags.tsx
index 5f0a409650..60b67ae771 100644
--- a/web/ui/react-app/src/pages/flags/Flags.tsx
+++ b/web/ui/react-app/src/pages/flags/Flags.tsx
@@ -3,7 +3,8 @@ import { RouteComponentProps } from '@reach/router';
import { Table } from 'reactstrap';
import { withStatusIndicator } from '../../components/withStatusIndicator';
import { useFetch } from '../../hooks/useFetch';
-import PathPrefixProps from '../../types/PathPrefixProps';
+import { usePathPrefix } from '../../contexts/PathPrefixContext';
+import { API_PATH } from '../../constants/constants';
interface FlagMap {
[key: string]: string;
@@ -34,8 +35,9 @@ const FlagsWithStatusIndicator = withStatusIndicator(FlagsContent);
FlagsContent.displayName = 'Flags';
-const Flags: FC = ({ pathPrefix = '' }) => {
- const { response, error, isLoading } = useFetch(`${pathPrefix}/api/v1/status/flags`);
+const Flags: FC = () => {
+ const pathPrefix = usePathPrefix();
+ const { response, error, isLoading } = useFetch(`${pathPrefix}/${API_PATH}/status/flags`);
return ;
};
diff --git a/web/ui/react-app/src/pages/graph/Panel.tsx b/web/ui/react-app/src/pages/graph/Panel.tsx
index 29dbbc4dc7..bd103de4d2 100644
--- a/web/ui/react-app/src/pages/graph/Panel.tsx
+++ b/web/ui/react-app/src/pages/graph/Panel.tsx
@@ -10,8 +10,8 @@ import { GraphTabContent } from './GraphTabContent';
import DataTable from './DataTable';
import TimeInput from './TimeInput';
import QueryStatsView, { QueryStats } from './QueryStatsView';
-import PathPrefixProps from '../../types/PathPrefixProps';
import { QueryParams } from '../../types/types';
+import { API_PATH } from '../../constants/constants';
interface PanelProps {
options: PanelOptions;
@@ -21,6 +21,7 @@ interface PanelProps {
metricNames: string[];
removePanel: () => void;
onExecuteQuery: (query: string) => void;
+ pathPrefix: string;
}
interface PanelState {
@@ -55,7 +56,7 @@ export const PanelDefaultOptions: PanelOptions = {
stacked: false,
};
-class Panel extends Component {
+class Panel extends Component {
private abortInFlightFetch: (() => void) | null = null;
constructor(props: PanelProps) {
@@ -117,21 +118,20 @@ class Panel extends Component {
let path: string;
switch (this.props.options.type) {
case 'graph':
- path = '/api/v1/query_range';
+ path = 'query_range';
params.append('start', startTime.toString());
params.append('end', endTime.toString());
params.append('step', resolution.toString());
- // TODO path prefix here and elsewhere.
break;
case 'table':
- path = '/api/v1/query';
+ path = 'query';
params.append('time', endTime.toString());
break;
default:
throw new Error('Invalid panel type "' + this.props.options.type + '"');
}
- fetch(`${this.props.pathPrefix}${path}?${params}`, {
+ fetch(`${this.props.pathPrefix}/${API_PATH}/${path}?${params}`, {
cache: 'no-store',
credentials: 'same-origin',
signal: abortController.signal,
diff --git a/web/ui/react-app/src/pages/graph/PanelList.tsx b/web/ui/react-app/src/pages/graph/PanelList.tsx
index 8444b09872..b8e29bcb0f 100644
--- a/web/ui/react-app/src/pages/graph/PanelList.tsx
+++ b/web/ui/react-app/src/pages/graph/PanelList.tsx
@@ -4,10 +4,11 @@ import { Alert, Button } from 'reactstrap';
import Panel, { PanelOptions, PanelDefaultOptions } from './Panel';
import Checkbox from '../../components/Checkbox';
-import PathPrefixProps from '../../types/PathPrefixProps';
import { generateID, decodePanelOptionsFromQueryString, encodePanelOptionsToQueryString, callAll } from '../../utils';
import { useFetch } from '../../hooks/useFetch';
import { useLocalStorage } from '../../hooks/useLocalStorage';
+import { usePathPrefix } from '../../contexts/PathPrefixContext';
+import { API_PATH } from '../../constants/constants';
export type PanelMeta = { key: string; options: PanelOptions; id: string };
@@ -16,20 +17,14 @@ export const updateURL = (nextPanels: PanelMeta[]) => {
window.history.pushState({}, '', query);
};
-interface PanelListProps extends PathPrefixProps, RouteComponentProps {
+interface PanelListProps extends RouteComponentProps {
panels: PanelMeta[];
metrics: string[];
useLocalTime: boolean;
queryHistoryEnabled: boolean;
}
-export const PanelListContent: FC = ({
- metrics = [],
- useLocalTime,
- pathPrefix,
- queryHistoryEnabled,
- ...rest
-}) => {
+export const PanelListContent: FC = ({ metrics = [], useLocalTime, queryHistoryEnabled, ...rest }) => {
const [panels, setPanels] = useState(rest.panels);
const [historyItems, setLocalStorageHistoryItems] = useLocalStorage('history', []);
@@ -73,10 +68,13 @@ export const PanelListContent: FC = ({
]);
};
+ const pathPrefix = usePathPrefix();
+
return (
<>
{panels.map(({ id, options }) => (
= ({
useLocalTime={useLocalTime}
metricNames={metrics}
pastQueries={queryHistoryEnabled ? historyItems : []}
- pathPrefix={pathPrefix}
/>
))}