react updates for pathPrefix (#7979)
* dynamically determine path prefix Signed-off-by: James Ranson <james_ranson@cable.comcast.com> * minor changes per PR review Signed-off-by: James Ranson <james_ranson@cable.comcast.com> * use Context for apiPath and pathPrefix Signed-off-by: James Ranson <james_ranson@cable.comcast.com> * remove unhandled "/version" path Signed-off-by: James Ranson <james_ranson@cable.comcast.com> * only process index once instead of on every req Signed-off-by: James Ranson <james_ranson@cable.comcast.com> * remove unneeded tag fragment Signed-off-by: James Ranson <james_ranson@cable.comcast.com> * switch api path to const Signed-off-by: James Ranson <james_ranson@cable.comcast.com> * revert Signed-off-by: James Ranson <james_ranson@cable.comcast.com> * update tests Signed-off-by: James Ranson <james_ranson@cable.comcast.com> * linter updates Signed-off-by: James Ranson <james_ranson@cable.comcast.com> * simplify Signed-off-by: James Ranson <james_ranson@cable.comcast.com> * updates per peer review Signed-off-by: James Ranson <james_ranson@cable.comcast.com>
This commit is contained in:
parent
74775d7324
commit
1cffda5de7
|
@ -10,16 +10,11 @@
|
|||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
<!--
|
||||
The GLOBAL_PATH_PREFIX placeholder magic value is replaced during serving by Prometheus
|
||||
and set to Prometheus's external URL path. It gets prepended to all links back
|
||||
to Prometheus, both for asset loading as well as API accesses.
|
||||
|
||||
The GLOBAL_CONSOLES_LINK placeholder magic value is replaced during serving by Prometheus
|
||||
and set to the consoles link if it exists. It will render a "Consoles" link in the navbar when
|
||||
it is non-empty.
|
||||
-->
|
||||
<script>
|
||||
const GLOBAL_PATH_PREFIX='PATH_PREFIX_PLACEHOLDER';
|
||||
const GLOBAL_CONSOLES_LINK='CONSOLES_LINK_PLACEHOLDER';
|
||||
</script>
|
||||
|
||||
|
|
|
@ -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(<App pathPrefix="/path/prefix" />);
|
||||
const app = shallow(<App />);
|
||||
|
||||
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);
|
||||
|
|
|
@ -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<PathPrefixProps & AppProps> = ({ pathPrefix, consolesLink }) => {
|
||||
return (
|
||||
<>
|
||||
<Navigation pathPrefix={pathPrefix} consolesLink={consolesLink} />
|
||||
<Container fluid style={{ paddingTop: 70 }}>
|
||||
<Router basepath={`${pathPrefix}/new`}>
|
||||
<Redirect from="/" to={`${pathPrefix}/new/graph`} />
|
||||
const App: FC<AppProps> = ({ 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 (
|
||||
<PathPrefixContext.Provider value={basePath}>
|
||||
<Navigation consolesLink={consolesLink} />
|
||||
<Container fluid style={{ paddingTop: 70 }}>
|
||||
<Router basepath={`${basePath}`}>
|
||||
<Redirect from="/" to={`graph`} noThrow />
|
||||
{/*
|
||||
NOTE: Any route added here needs to also be added to the list of
|
||||
React-handled router paths ("reactRouterPaths") in /web/web.go.
|
||||
*/}
|
||||
<PanelList path="/graph" pathPrefix={pathPrefix} />
|
||||
<Alerts path="/alerts" pathPrefix={pathPrefix} />
|
||||
<Config path="/config" pathPrefix={pathPrefix} />
|
||||
<Flags path="/flags" pathPrefix={pathPrefix} />
|
||||
<Rules path="/rules" pathPrefix={pathPrefix} />
|
||||
<ServiceDiscovery path="/service-discovery" pathPrefix={pathPrefix} />
|
||||
<Status path="/status" pathPrefix={pathPrefix} />
|
||||
<TSDBStatus path="/tsdb-status" pathPrefix={pathPrefix} />
|
||||
<Targets path="/targets" pathPrefix={pathPrefix} />
|
||||
NOTE: Any route added here needs to also be added to the list of
|
||||
React-handled router paths ("reactRouterPaths") in /web/web.go.
|
||||
*/}
|
||||
<PanelList path="/graph" />
|
||||
<Alerts path="/alerts" />
|
||||
<Config path="/config" />
|
||||
<Flags path="/flags" />
|
||||
<Rules path="/rules" />
|
||||
<ServiceDiscovery path="/service-discovery" />
|
||||
<Status path="/status" />
|
||||
<TSDBStatus path="/tsdb-status" />
|
||||
<Targets path="/targets" />
|
||||
</Router>
|
||||
</Container>
|
||||
</>
|
||||
</PathPrefixContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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<PathPrefixProps & NavbarProps> = ({ pathPrefix, consolesLink }) => {
|
||||
const Navigation: FC<NavbarProps> = ({ consolesLink }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const toggle = () => setIsOpen(!isOpen);
|
||||
const pathPrefix = usePathPrefix();
|
||||
return (
|
||||
<Navbar className="mb-3" dark color="dark" expand="md" fixed="top">
|
||||
<NavbarToggler onClick={toggle} />
|
||||
<Link className="pt-0 navbar-brand" to={`${pathPrefix}/new/graph`}>
|
||||
<Link className="pt-0 navbar-brand" to={`${pathPrefix}/graph`}>
|
||||
Prometheus
|
||||
</Link>
|
||||
<Collapse isOpen={isOpen} navbar style={{ justifyContent: 'space-between' }}>
|
||||
|
@ -35,12 +36,12 @@ const Navigation: FC<PathPrefixProps & NavbarProps> = ({ pathPrefix, consolesLin
|
|||
</NavItem>
|
||||
)}
|
||||
<NavItem>
|
||||
<NavLink tag={Link} to={`${pathPrefix}/new/alerts`}>
|
||||
<NavLink tag={Link} to={`${pathPrefix}/alerts`}>
|
||||
Alerts
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink tag={Link} to={`${pathPrefix}/new/graph`}>
|
||||
<NavLink tag={Link} to={`${pathPrefix}/graph`}>
|
||||
Graph
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
|
@ -49,25 +50,25 @@ const Navigation: FC<PathPrefixProps & NavbarProps> = ({ pathPrefix, consolesLin
|
|||
Status
|
||||
</DropdownToggle>
|
||||
<DropdownMenu>
|
||||
<DropdownItem tag={Link} to={`${pathPrefix}/new/status`}>
|
||||
<DropdownItem tag={Link} to={`${pathPrefix}/status`}>
|
||||
Runtime & Build Information
|
||||
</DropdownItem>
|
||||
<DropdownItem tag={Link} to={`${pathPrefix}/new/tsdb-status`}>
|
||||
<DropdownItem tag={Link} to={`${pathPrefix}/tsdb-status`}>
|
||||
TSDB Status
|
||||
</DropdownItem>
|
||||
<DropdownItem tag={Link} to={`${pathPrefix}/new/flags`}>
|
||||
<DropdownItem tag={Link} to={`${pathPrefix}/flags`}>
|
||||
Command-Line Flags
|
||||
</DropdownItem>
|
||||
<DropdownItem tag={Link} to={`${pathPrefix}/new/config`}>
|
||||
<DropdownItem tag={Link} to={`${pathPrefix}/config`}>
|
||||
Configuration
|
||||
</DropdownItem>
|
||||
<DropdownItem tag={Link} to={`${pathPrefix}/new/rules`}>
|
||||
<DropdownItem tag={Link} to={`${pathPrefix}/rules`}>
|
||||
Rules
|
||||
</DropdownItem>
|
||||
<DropdownItem tag={Link} to={`${pathPrefix}/new/targets`}>
|
||||
<DropdownItem tag={Link} to={`${pathPrefix}/targets`}>
|
||||
Targets
|
||||
</DropdownItem>
|
||||
<DropdownItem tag={Link} to={`${pathPrefix}/new/service-discovery`}>
|
||||
<DropdownItem tag={Link} to={`${pathPrefix}/service-discovery`}>
|
||||
Service Discovery
|
||||
</DropdownItem>
|
||||
</DropdownMenu>
|
||||
|
@ -76,7 +77,7 @@ const Navigation: FC<PathPrefixProps & NavbarProps> = ({ pathPrefix, consolesLin
|
|||
<NavLink href="https://prometheus.io/docs/prometheus/latest/getting_started/">Help</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink href={`${pathPrefix}/graph${window.location.search}`}>Classic UI</NavLink>
|
||||
<NavLink href={`${pathPrefix}/../graph${window.location.search}`}>Classic UI</NavLink>
|
||||
</NavItem>
|
||||
</Nav>
|
||||
</Collapse>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export const API_PATH = '../api/v1';
|
|
@ -0,0 +1,9 @@
|
|||
import React from 'react';
|
||||
|
||||
const PathPrefixContext = React.createContext('');
|
||||
|
||||
function usePathPrefix() {
|
||||
return React.useContext(PathPrefixContext);
|
||||
}
|
||||
|
||||
export { usePathPrefix, PathPrefixContext };
|
|
@ -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(<App pathPrefix={prefix} consolesLink={consolesLink} />, document.getElementById('root'));
|
||||
ReactDOM.render(<App consolesLink={consolesLink} />, document.getElementById('root'));
|
||||
|
|
|
@ -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<RouteComponentProps & PathPrefixProps> = ({ pathPrefix = '' }) => {
|
||||
const { response, error, isLoading } = useFetch<AlertsProps>(`${pathPrefix}/api/v1/rules?type=alert`);
|
||||
const Alerts: FC<RouteComponentProps> = () => {
|
||||
const pathPrefix = usePathPrefix();
|
||||
const { response, error, isLoading } = useFetch<AlertsProps>(`${pathPrefix}/${API_PATH}/rules?type=alert`);
|
||||
|
||||
const ruleStatsCount: RuleStatus<number> = {
|
||||
inactive: 0,
|
||||
|
|
|
@ -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<ConfigContentProps> = ({ error, data }) => {
|
|||
);
|
||||
};
|
||||
|
||||
const Config: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix }) => {
|
||||
const { response, error } = useFetch<YamlConfig>(`${pathPrefix}/api/v1/status/config`);
|
||||
const Config: FC<RouteComponentProps> = () => {
|
||||
const pathPrefix = usePathPrefix();
|
||||
const { response, error } = useFetch<YamlConfig>(`${pathPrefix}/${API_PATH}/status/config`);
|
||||
return <ConfigContent error={error} data={response.data} />;
|
||||
};
|
||||
|
||||
|
|
|
@ -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<RouteComponentProps & PathPrefixProps> = ({ pathPrefix = '' }) => {
|
||||
const { response, error, isLoading } = useFetch<FlagMap>(`${pathPrefix}/api/v1/status/flags`);
|
||||
const Flags: FC<RouteComponentProps> = () => {
|
||||
const pathPrefix = usePathPrefix();
|
||||
const { response, error, isLoading } = useFetch<FlagMap>(`${pathPrefix}/${API_PATH}/status/flags`);
|
||||
return <FlagsWithStatusIndicator data={response.data} error={error} isLoading={isLoading} />;
|
||||
};
|
||||
|
||||
|
|
|
@ -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<PanelProps & PathPrefixProps, PanelState> {
|
||||
class Panel extends Component<PanelProps, PanelState> {
|
||||
private abortInFlightFetch: (() => void) | null = null;
|
||||
|
||||
constructor(props: PanelProps) {
|
||||
|
@ -117,21 +118,20 @@ class Panel extends Component<PanelProps & PathPrefixProps, PanelState> {
|
|||
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,
|
||||
|
|
|
@ -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<PanelListProps> = ({
|
||||
metrics = [],
|
||||
useLocalTime,
|
||||
pathPrefix,
|
||||
queryHistoryEnabled,
|
||||
...rest
|
||||
}) => {
|
||||
export const PanelListContent: FC<PanelListProps> = ({ metrics = [], useLocalTime, queryHistoryEnabled, ...rest }) => {
|
||||
const [panels, setPanels] = useState(rest.panels);
|
||||
const [historyItems, setLocalStorageHistoryItems] = useLocalStorage<string[]>('history', []);
|
||||
|
||||
|
@ -73,10 +68,13 @@ export const PanelListContent: FC<PanelListProps> = ({
|
|||
]);
|
||||
};
|
||||
|
||||
const pathPrefix = usePathPrefix();
|
||||
|
||||
return (
|
||||
<>
|
||||
{panels.map(({ id, options }) => (
|
||||
<Panel
|
||||
pathPrefix={pathPrefix}
|
||||
onExecuteQuery={handleExecuteQuery}
|
||||
key={id}
|
||||
options={options}
|
||||
|
@ -97,7 +95,6 @@ export const PanelListContent: FC<PanelListProps> = ({
|
|||
useLocalTime={useLocalTime}
|
||||
metricNames={metrics}
|
||||
pastQueries={queryHistoryEnabled ? historyItems : []}
|
||||
pathPrefix={pathPrefix}
|
||||
/>
|
||||
))}
|
||||
<Button className="mb-3" color="primary" onClick={addPanel}>
|
||||
|
@ -107,15 +104,18 @@ export const PanelListContent: FC<PanelListProps> = ({
|
|||
);
|
||||
};
|
||||
|
||||
const PanelList: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix = '' }) => {
|
||||
const PanelList: FC<RouteComponentProps> = () => {
|
||||
const [delta, setDelta] = useState(0);
|
||||
const [useLocalTime, setUseLocalTime] = useLocalStorage('use-local-time', false);
|
||||
const [enableQueryHistory, setEnableQueryHistory] = useLocalStorage('enable-query-history', false);
|
||||
|
||||
const { response: metricsRes, error: metricsErr } = useFetch<string[]>(`${pathPrefix}/api/v1/label/__name__/values`);
|
||||
const pathPrefix = usePathPrefix();
|
||||
const { response: metricsRes, error: metricsErr } = useFetch<string[]>(`${pathPrefix}/${API_PATH}/label/__name__/values`);
|
||||
|
||||
const browserTime = new Date().getTime() / 1000;
|
||||
const { response: timeRes, error: timeErr } = useFetch<{ result: number[] }>(`${pathPrefix}/api/v1/query?query=time()`);
|
||||
const { response: timeRes, error: timeErr } = useFetch<{ result: number[] }>(
|
||||
`${pathPrefix}/${API_PATH}/query?query=time()`
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (timeRes.data) {
|
||||
|
@ -164,7 +164,6 @@ const PanelList: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix = ''
|
|||
)}
|
||||
<PanelListContent
|
||||
panels={decodePanelOptionsFromQueryString(window.location.search)}
|
||||
pathPrefix={pathPrefix}
|
||||
useLocalTime={useLocalTime}
|
||||
metrics={metricsRes.data}
|
||||
queryHistoryEnabled={enableQueryHistory}
|
||||
|
|
|
@ -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 { RulesMap, RulesContent } from './RulesContent';
|
||||
import { usePathPrefix } from '../../contexts/PathPrefixContext';
|
||||
import { API_PATH } from '../../constants/constants';
|
||||
|
||||
const RulesWithStatusIndicator = withStatusIndicator(RulesContent);
|
||||
|
||||
const Rules: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix }) => {
|
||||
const { response, error, isLoading } = useFetch<RulesMap>(`${pathPrefix}/api/v1/rules`);
|
||||
const Rules: FC<RouteComponentProps> = () => {
|
||||
const pathPrefix = usePathPrefix();
|
||||
const { response, error, isLoading } = useFetch<RulesMap>(`${pathPrefix}/${API_PATH}/rules`);
|
||||
|
||||
return <RulesWithStatusIndicator response={response} error={error} isLoading={isLoading} />;
|
||||
};
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import React, { FC } from 'react';
|
||||
import { RouteComponentProps } from '@reach/router';
|
||||
import PathPrefixProps from '../../types/PathPrefixProps';
|
||||
import { useFetch } from '../../hooks/useFetch';
|
||||
import { LabelsTable } from './LabelsTable';
|
||||
import { Target, Labels, DroppedTarget } from '../targets/target';
|
||||
|
||||
import { withStatusIndicator } from '../../components/withStatusIndicator';
|
||||
import { mapObjEntries } from '../../utils';
|
||||
import { usePathPrefix } from '../../contexts/PathPrefixContext';
|
||||
import { API_PATH } from '../../constants/constants';
|
||||
|
||||
interface ServiceMap {
|
||||
activeTargets: Target[];
|
||||
|
@ -105,8 +106,9 @@ ServiceDiscoveryContent.displayName = 'ServiceDiscoveryContent';
|
|||
|
||||
const ServicesWithStatusIndicator = withStatusIndicator(ServiceDiscoveryContent);
|
||||
|
||||
const ServiceDiscovery: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix }) => {
|
||||
const { response, error, isLoading } = useFetch<ServiceMap>(`${pathPrefix}/api/v1/targets`);
|
||||
const ServiceDiscovery: FC<RouteComponentProps> = () => {
|
||||
const pathPrefix = usePathPrefix();
|
||||
const { response, error, isLoading } = useFetch<ServiceMap>(`${pathPrefix}/${API_PATH}/targets`);
|
||||
return (
|
||||
<ServicesWithStatusIndicator
|
||||
{...response.data}
|
||||
|
|
|
@ -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 StatusPageProps {
|
||||
data: Record<string, string>;
|
||||
|
@ -82,8 +83,9 @@ const StatusWithStatusIndicator = withStatusIndicator(StatusContent);
|
|||
|
||||
StatusContent.displayName = 'Status';
|
||||
|
||||
const Status: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix = '' }) => {
|
||||
const path = `${pathPrefix}/api/v1`;
|
||||
const Status: FC<RouteComponentProps> = () => {
|
||||
const pathPrefix = usePathPrefix();
|
||||
const path = `${pathPrefix}/${API_PATH}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -7,11 +7,11 @@ import ScrapePoolList from './ScrapePoolList';
|
|||
import ScrapePoolPanel from './ScrapePoolPanel';
|
||||
import { Target } from './target';
|
||||
import { FetchMock } from 'jest-fetch-mock/types';
|
||||
import { PathPrefixContext } from '../../contexts/PathPrefixContext';
|
||||
|
||||
describe('ScrapePoolList', () => {
|
||||
const defaultProps = {
|
||||
filter: { showHealthy: true, showUnhealthy: true },
|
||||
pathPrefix: '..',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -36,10 +36,17 @@ describe('ScrapePoolList', () => {
|
|||
|
||||
it('renders a table', async () => {
|
||||
await act(async () => {
|
||||
scrapePoolList = mount(<ScrapePoolList {...defaultProps} />);
|
||||
scrapePoolList = mount(
|
||||
<PathPrefixContext.Provider value="/path/prefix">
|
||||
<ScrapePoolList {...defaultProps} />
|
||||
</PathPrefixContext.Provider>
|
||||
);
|
||||
});
|
||||
scrapePoolList.update();
|
||||
expect(mock).toHaveBeenCalledWith('../api/v1/targets?state=active', { cache: 'no-store', credentials: 'same-origin' });
|
||||
expect(mock).toHaveBeenCalledWith('/path/prefix/../api/v1/targets?state=active', {
|
||||
cache: 'no-store',
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
const panels = scrapePoolList.find(ScrapePoolPanel);
|
||||
expect(panels).toHaveLength(3);
|
||||
const activeTargets: Target[] = sampleApiResponse.data.activeTargets as Target[];
|
||||
|
@ -55,10 +62,17 @@ describe('ScrapePoolList', () => {
|
|||
filter: { showHealthy: false, showUnhealthy: true },
|
||||
};
|
||||
await act(async () => {
|
||||
scrapePoolList = mount(<ScrapePoolList {...props} />);
|
||||
scrapePoolList = mount(
|
||||
<PathPrefixContext.Provider value="/path/prefix">
|
||||
<ScrapePoolList {...props} />
|
||||
</PathPrefixContext.Provider>
|
||||
);
|
||||
});
|
||||
scrapePoolList.update();
|
||||
expect(mock).toHaveBeenCalledWith('../api/v1/targets?state=active', { cache: 'no-store', credentials: 'same-origin' });
|
||||
expect(mock).toHaveBeenCalledWith('/path/prefix/../api/v1/targets?state=active', {
|
||||
cache: 'no-store',
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
const panels = scrapePoolList.find(ScrapePoolPanel);
|
||||
expect(panels).toHaveLength(0);
|
||||
});
|
||||
|
@ -70,11 +84,18 @@ describe('ScrapePoolList', () => {
|
|||
|
||||
let scrapePoolList: any;
|
||||
await act(async () => {
|
||||
scrapePoolList = mount(<ScrapePoolList {...defaultProps} />);
|
||||
scrapePoolList = mount(
|
||||
<PathPrefixContext.Provider value="/path/prefix">
|
||||
<ScrapePoolList {...defaultProps} />
|
||||
</PathPrefixContext.Provider>
|
||||
);
|
||||
});
|
||||
scrapePoolList.update();
|
||||
|
||||
expect(mock).toHaveBeenCalledWith('../api/v1/targets?state=active', { cache: 'no-store', credentials: 'same-origin' });
|
||||
expect(mock).toHaveBeenCalledWith('/path/prefix/../api/v1/targets?state=active', {
|
||||
cache: 'no-store',
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
const alert = scrapePoolList.find(Alert);
|
||||
expect(alert.prop('color')).toBe('danger');
|
||||
expect(alert.text()).toContain('Error fetching targets');
|
||||
|
|
|
@ -3,8 +3,9 @@ import { FilterData } from './Filter';
|
|||
import { useFetch } from '../../hooks/useFetch';
|
||||
import { groupTargets, Target } from './target';
|
||||
import ScrapePoolPanel from './ScrapePoolPanel';
|
||||
import PathPrefixProps from '../../types/PathPrefixProps';
|
||||
import { withStatusIndicator } from '../../components/withStatusIndicator';
|
||||
import { usePathPrefix } from '../../contexts/PathPrefixContext';
|
||||
import { API_PATH } from '../../constants/constants';
|
||||
|
||||
interface ScrapePoolListProps {
|
||||
filter: FilterData;
|
||||
|
@ -30,8 +31,9 @@ ScrapePoolContent.displayName = 'ScrapePoolContent';
|
|||
|
||||
const ScrapePoolListWithStatusIndicator = withStatusIndicator(ScrapePoolContent);
|
||||
|
||||
const ScrapePoolList: FC<{ filter: FilterData } & PathPrefixProps> = ({ pathPrefix, filter }) => {
|
||||
const { response, error, isLoading } = useFetch<ScrapePoolListProps>(`${pathPrefix}/api/v1/targets?state=active`);
|
||||
const ScrapePoolList: FC<{ filter: FilterData }> = ({ filter }) => {
|
||||
const pathPrefix = usePathPrefix();
|
||||
const { response, error, isLoading } = useFetch<ScrapePoolListProps>(`${pathPrefix}/${API_PATH}/targets?state=active`);
|
||||
const { status: responseStatus } = response;
|
||||
const badResponse = responseStatus !== 'success' && responseStatus !== 'start fetching';
|
||||
return (
|
||||
|
|
|
@ -28,6 +28,5 @@ describe('Targets', () => {
|
|||
const scrapePoolList = targets.find(ScrapePoolList);
|
||||
expect(scrapePoolList).toHaveLength(1);
|
||||
expect(scrapePoolList.prop('filter')).toEqual({ showHealthy: true, showUnhealthy: true });
|
||||
expect(scrapePoolList.prop('pathPrefix')).toEqual(defaultProps.pathPrefix);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,13 +2,15 @@ import React, { FC } from 'react';
|
|||
import { RouteComponentProps } from '@reach/router';
|
||||
import Filter from './Filter';
|
||||
import ScrapePoolList from './ScrapePoolList';
|
||||
import PathPrefixProps from '../../types/PathPrefixProps';
|
||||
import { useLocalStorage } from '../../hooks/useLocalStorage';
|
||||
import { usePathPrefix } from '../../contexts/PathPrefixContext';
|
||||
import { API_PATH } from '../../constants/constants';
|
||||
|
||||
const Targets: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix }) => {
|
||||
const Targets: FC<RouteComponentProps> = () => {
|
||||
const pathPrefix = usePathPrefix();
|
||||
const [filter, setFilter] = useLocalStorage('targets-page-filter', { showHealthy: true, showUnhealthy: true });
|
||||
const filterProps = { filter, setFilter };
|
||||
const scrapePoolListProps = { filter, pathPrefix };
|
||||
const scrapePoolListProps = { filter, pathPrefix, API_PATH };
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -5,6 +5,7 @@ import { Table } from 'reactstrap';
|
|||
|
||||
import TSDBStatus from './TSDBStatus';
|
||||
import { TSDBMap } from './TSDBStatus';
|
||||
import { PathPrefixContext } from '../../contexts/PathPrefixContext';
|
||||
|
||||
const fakeTSDBStatusResponse: {
|
||||
status: string;
|
||||
|
@ -66,11 +67,15 @@ describe('TSDB Stats', () => {
|
|||
const mock = fetchMock.mockResponse(JSON.stringify(fakeTSDBStatusResponse));
|
||||
let page: any;
|
||||
await act(async () => {
|
||||
page = mount(<TSDBStatus pathPrefix="/path/prefix" />);
|
||||
page = mount(
|
||||
<PathPrefixContext.Provider value="/path/prefix">
|
||||
<TSDBStatus />
|
||||
</PathPrefixContext.Provider>
|
||||
);
|
||||
});
|
||||
page.update();
|
||||
|
||||
expect(mock).toHaveBeenCalledWith('/path/prefix/api/v1/status/tsdb', {
|
||||
expect(mock).toHaveBeenCalledWith('/path/prefix/../api/v1/status/tsdb', {
|
||||
cache: 'no-store',
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
|
|
|
@ -3,8 +3,9 @@ import { RouteComponentProps } from '@reach/router';
|
|||
import { Table } from 'reactstrap';
|
||||
|
||||
import { useFetch } from '../../hooks/useFetch';
|
||||
import PathPrefixProps from '../../types/PathPrefixProps';
|
||||
import { withStatusIndicator } from '../../components/withStatusIndicator';
|
||||
import { usePathPrefix } from '../../contexts/PathPrefixContext';
|
||||
import { API_PATH } from '../../constants/constants';
|
||||
|
||||
interface Stats {
|
||||
name: string;
|
||||
|
@ -101,8 +102,9 @@ TSDBStatusContent.displayName = 'TSDBStatusContent';
|
|||
|
||||
const TSDBStatusContentWithStatusIndicator = withStatusIndicator(TSDBStatusContent);
|
||||
|
||||
const TSDBStatus: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix }) => {
|
||||
const { response, error, isLoading } = useFetch<TSDBMap>(`${pathPrefix}/api/v1/status/tsdb`);
|
||||
const TSDBStatus: FC<RouteComponentProps> = () => {
|
||||
const pathPrefix = usePathPrefix();
|
||||
const { response, error, isLoading } = useFetch<TSDBMap>(`${pathPrefix}/${API_PATH}/status/tsdb`);
|
||||
|
||||
return (
|
||||
<TSDBStatusContentWithStatusIndicator
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
interface PathPrefixProps {
|
||||
pathPrefix?: string;
|
||||
}
|
||||
|
||||
export default PathPrefixProps;
|
|
@ -79,7 +79,6 @@ var reactRouterPaths = []string{
|
|||
"/status",
|
||||
"/targets",
|
||||
"/tsdb-status",
|
||||
"/version",
|
||||
}
|
||||
|
||||
// withStackTrace logs the stack trace in case the request panics. The function
|
||||
|
@ -393,8 +392,7 @@ func New(logger log.Logger, o *Options) *Handler {
|
|||
fmt.Fprintf(w, "Error reading React index.html: %v", err)
|
||||
return
|
||||
}
|
||||
replacedIdx := bytes.ReplaceAll(idx, []byte("PATH_PREFIX_PLACEHOLDER"), []byte(o.ExternalURL.Path))
|
||||
replacedIdx = bytes.ReplaceAll(replacedIdx, []byte("CONSOLES_LINK_PLACEHOLDER"), []byte(h.consolesPath()))
|
||||
replacedIdx := bytes.ReplaceAll(idx, []byte("CONSOLES_LINK_PLACEHOLDER"), []byte(h.consolesPath()))
|
||||
replacedIdx = bytes.ReplaceAll(replacedIdx, []byte("TITLE_PLACEHOLDER"), []byte(h.options.PageTitle))
|
||||
w.Write(replacedIdx)
|
||||
return
|
||||
|
|
Loading…
Reference in New Issue