未验证 提交 1cffda5d 编写于 作者: J James Ranson 提交者: GitHub

react updates for pathPrefix (#7979)

* dynamically determine path prefix
Signed-off-by: NJames Ranson <james_ranson@cable.comcast.com>

* minor changes per PR review
Signed-off-by: NJames Ranson <james_ranson@cable.comcast.com>

* use Context for apiPath and pathPrefix
Signed-off-by: NJames Ranson <james_ranson@cable.comcast.com>

* remove unhandled "/version" path
Signed-off-by: NJames Ranson <james_ranson@cable.comcast.com>

* only process index once instead of on every req
Signed-off-by: NJames Ranson <james_ranson@cable.comcast.com>

* remove unneeded tag fragment
Signed-off-by: NJames Ranson <james_ranson@cable.comcast.com>

* switch api path to const
Signed-off-by: NJames Ranson <james_ranson@cable.comcast.com>

* revert
Signed-off-by: NJames Ranson <james_ranson@cable.comcast.com>

* update tests
Signed-off-by: NJames Ranson <james_ranson@cable.comcast.com>

* linter updates
Signed-off-by: NJames Ranson <james_ranson@cable.comcast.com>

* simplify
Signed-off-by: NJames Ranson <james_ranson@cable.comcast.com>

* updates per peer review
Signed-off-by: NJames Ranson <james_ranson@cable.comcast.com>
上级 74775d73
......@@ -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 }) => {
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 (
<>
<Navigation pathPrefix={pathPrefix} consolesLink={consolesLink} />
<PathPrefixContext.Provider value={basePath}>
<Navigation consolesLink={consolesLink} />
<Container fluid style={{ paddingTop: 70 }}>
<Router basepath={`${pathPrefix}/new`}>
<Redirect from="/" to={`${pathPrefix}/new/graph`} />
<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>
......
export const API_PATH = '../api/v1';
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'));
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}
......
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} />;
};
......
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
......
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
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册