mirror of
https://github.com/prometheus/prometheus.git
synced 2025-03-05 20:59:13 -08:00
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" />
|
<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
|
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
|
and set to the consoles link if it exists. It will render a "Consoles" link in the navbar when
|
||||||
it is non-empty.
|
it is non-empty.
|
||||||
-->
|
-->
|
||||||
<script>
|
<script>
|
||||||
const GLOBAL_PATH_PREFIX='PATH_PREFIX_PLACEHOLDER';
|
|
||||||
const GLOBAL_CONSOLES_LINK='CONSOLES_LINK_PLACEHOLDER';
|
const GLOBAL_CONSOLES_LINK='CONSOLES_LINK_PLACEHOLDER';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { Router } from '@reach/router';
|
||||||
import { Alerts, Config, Flags, Rules, ServiceDiscovery, Status, Targets, TSDBStatus, PanelList } from './pages';
|
import { Alerts, Config, Flags, Rules, ServiceDiscovery, Status, Targets, TSDBStatus, PanelList } from './pages';
|
||||||
|
|
||||||
describe('App', () => {
|
describe('App', () => {
|
||||||
const app = shallow(<App pathPrefix="/path/prefix" />);
|
const app = shallow(<App />);
|
||||||
|
|
||||||
it('navigates', () => {
|
it('navigates', () => {
|
||||||
expect(app.find(Navigation)).toHaveLength(1);
|
expect(app.find(Navigation)).toHaveLength(1);
|
||||||
|
@ -16,7 +16,6 @@ describe('App', () => {
|
||||||
[Alerts, Config, Flags, Rules, ServiceDiscovery, Status, Targets, TSDBStatus, PanelList].forEach(component => {
|
[Alerts, Config, Flags, Rules, ServiceDiscovery, Status, Targets, TSDBStatus, PanelList].forEach(component => {
|
||||||
const c = app.find(component);
|
const c = app.find(component);
|
||||||
expect(c).toHaveLength(1);
|
expect(c).toHaveLength(1);
|
||||||
expect(c.prop('pathPrefix')).toBe('/path/prefix');
|
|
||||||
});
|
});
|
||||||
expect(app.find(Router)).toHaveLength(1);
|
expect(app.find(Router)).toHaveLength(1);
|
||||||
expect(app.find(Container)).toHaveLength(1);
|
expect(app.find(Container)).toHaveLength(1);
|
||||||
|
|
|
@ -5,36 +5,62 @@ import { Container } from 'reactstrap';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import { Router, Redirect } from '@reach/router';
|
import { Router, Redirect } from '@reach/router';
|
||||||
import { Alerts, Config, Flags, Rules, ServiceDiscovery, Status, Targets, TSDBStatus, PanelList } from './pages';
|
import { Alerts, Config, Flags, Rules, ServiceDiscovery, Status, Targets, TSDBStatus, PanelList } from './pages';
|
||||||
import PathPrefixProps from './types/PathPrefixProps';
|
import { PathPrefixContext } from './contexts/PathPrefixContext';
|
||||||
|
|
||||||
interface AppProps {
|
interface AppProps {
|
||||||
consolesLink: string | null;
|
consolesLink: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const App: FC<PathPrefixProps & AppProps> = ({ pathPrefix, consolesLink }) => {
|
const App: FC<AppProps> = ({ consolesLink }) => {
|
||||||
return (
|
// 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
|
||||||
<Navigation pathPrefix={pathPrefix} consolesLink={consolesLink} />
|
// hosting and reverse proxy deployments with no additional configurations required.
|
||||||
<Container fluid style={{ paddingTop: 70 }}>
|
let basePath = window.location.pathname;
|
||||||
<Router basepath={`${pathPrefix}/new`}>
|
const paths = [
|
||||||
<Redirect from="/" to={`${pathPrefix}/new/graph`} />
|
'/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
|
NOTE: Any route added here needs to also be added to the list of
|
||||||
React-handled router paths ("reactRouterPaths") in /web/web.go.
|
React-handled router paths ("reactRouterPaths") in /web/web.go.
|
||||||
*/}
|
*/}
|
||||||
<PanelList path="/graph" pathPrefix={pathPrefix} />
|
<PanelList path="/graph" />
|
||||||
<Alerts path="/alerts" pathPrefix={pathPrefix} />
|
<Alerts path="/alerts" />
|
||||||
<Config path="/config" pathPrefix={pathPrefix} />
|
<Config path="/config" />
|
||||||
<Flags path="/flags" pathPrefix={pathPrefix} />
|
<Flags path="/flags" />
|
||||||
<Rules path="/rules" pathPrefix={pathPrefix} />
|
<Rules path="/rules" />
|
||||||
<ServiceDiscovery path="/service-discovery" pathPrefix={pathPrefix} />
|
<ServiceDiscovery path="/service-discovery" />
|
||||||
<Status path="/status" pathPrefix={pathPrefix} />
|
<Status path="/status" />
|
||||||
<TSDBStatus path="/tsdb-status" pathPrefix={pathPrefix} />
|
<TSDBStatus path="/tsdb-status" />
|
||||||
<Targets path="/targets" pathPrefix={pathPrefix} />
|
<Targets path="/targets" />
|
||||||
</Router>
|
</Router>
|
||||||
</Container>
|
</Container>
|
||||||
</>
|
</PathPrefixContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -12,19 +12,20 @@ import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownItem,
|
DropdownItem,
|
||||||
} from 'reactstrap';
|
} from 'reactstrap';
|
||||||
import PathPrefixProps from './types/PathPrefixProps';
|
import { usePathPrefix } from './contexts/PathPrefixContext';
|
||||||
|
|
||||||
interface NavbarProps {
|
interface NavbarProps {
|
||||||
consolesLink: string | null;
|
consolesLink: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Navigation: FC<PathPrefixProps & NavbarProps> = ({ pathPrefix, consolesLink }) => {
|
const Navigation: FC<NavbarProps> = ({ consolesLink }) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const toggle = () => setIsOpen(!isOpen);
|
const toggle = () => setIsOpen(!isOpen);
|
||||||
|
const pathPrefix = usePathPrefix();
|
||||||
return (
|
return (
|
||||||
<Navbar className="mb-3" dark color="dark" expand="md" fixed="top">
|
<Navbar className="mb-3" dark color="dark" expand="md" fixed="top">
|
||||||
<NavbarToggler onClick={toggle} />
|
<NavbarToggler onClick={toggle} />
|
||||||
<Link className="pt-0 navbar-brand" to={`${pathPrefix}/new/graph`}>
|
<Link className="pt-0 navbar-brand" to={`${pathPrefix}/graph`}>
|
||||||
Prometheus
|
Prometheus
|
||||||
</Link>
|
</Link>
|
||||||
<Collapse isOpen={isOpen} navbar style={{ justifyContent: 'space-between' }}>
|
<Collapse isOpen={isOpen} navbar style={{ justifyContent: 'space-between' }}>
|
||||||
|
@ -35,12 +36,12 @@ const Navigation: FC<PathPrefixProps & NavbarProps> = ({ pathPrefix, consolesLin
|
||||||
</NavItem>
|
</NavItem>
|
||||||
)}
|
)}
|
||||||
<NavItem>
|
<NavItem>
|
||||||
<NavLink tag={Link} to={`${pathPrefix}/new/alerts`}>
|
<NavLink tag={Link} to={`${pathPrefix}/alerts`}>
|
||||||
Alerts
|
Alerts
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</NavItem>
|
</NavItem>
|
||||||
<NavItem>
|
<NavItem>
|
||||||
<NavLink tag={Link} to={`${pathPrefix}/new/graph`}>
|
<NavLink tag={Link} to={`${pathPrefix}/graph`}>
|
||||||
Graph
|
Graph
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</NavItem>
|
</NavItem>
|
||||||
|
@ -49,25 +50,25 @@ const Navigation: FC<PathPrefixProps & NavbarProps> = ({ pathPrefix, consolesLin
|
||||||
Status
|
Status
|
||||||
</DropdownToggle>
|
</DropdownToggle>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownItem tag={Link} to={`${pathPrefix}/new/status`}>
|
<DropdownItem tag={Link} to={`${pathPrefix}/status`}>
|
||||||
Runtime & Build Information
|
Runtime & Build Information
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DropdownItem tag={Link} to={`${pathPrefix}/new/tsdb-status`}>
|
<DropdownItem tag={Link} to={`${pathPrefix}/tsdb-status`}>
|
||||||
TSDB Status
|
TSDB Status
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DropdownItem tag={Link} to={`${pathPrefix}/new/flags`}>
|
<DropdownItem tag={Link} to={`${pathPrefix}/flags`}>
|
||||||
Command-Line Flags
|
Command-Line Flags
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DropdownItem tag={Link} to={`${pathPrefix}/new/config`}>
|
<DropdownItem tag={Link} to={`${pathPrefix}/config`}>
|
||||||
Configuration
|
Configuration
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DropdownItem tag={Link} to={`${pathPrefix}/new/rules`}>
|
<DropdownItem tag={Link} to={`${pathPrefix}/rules`}>
|
||||||
Rules
|
Rules
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DropdownItem tag={Link} to={`${pathPrefix}/new/targets`}>
|
<DropdownItem tag={Link} to={`${pathPrefix}/targets`}>
|
||||||
Targets
|
Targets
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DropdownItem tag={Link} to={`${pathPrefix}/new/service-discovery`}>
|
<DropdownItem tag={Link} to={`${pathPrefix}/service-discovery`}>
|
||||||
Service Discovery
|
Service Discovery
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
@ -76,7 +77,7 @@ const Navigation: FC<PathPrefixProps & NavbarProps> = ({ pathPrefix, consolesLin
|
||||||
<NavLink href="https://prometheus.io/docs/prometheus/latest/getting_started/">Help</NavLink>
|
<NavLink href="https://prometheus.io/docs/prometheus/latest/getting_started/">Help</NavLink>
|
||||||
</NavItem>
|
</NavItem>
|
||||||
<NavItem>
|
<NavItem>
|
||||||
<NavLink href={`${pathPrefix}/graph${window.location.search}`}>Classic UI</NavLink>
|
<NavLink href={`${pathPrefix}/../graph${window.location.search}`}>Classic UI</NavLink>
|
||||||
</NavItem>
|
</NavItem>
|
||||||
</Nav>
|
</Nav>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
|
1
web/ui/react-app/src/constants/constants.tsx
Normal file
1
web/ui/react-app/src/constants/constants.tsx
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const API_PATH = '../api/v1';
|
9
web/ui/react-app/src/contexts/PathPrefixContext.tsx
Normal file
9
web/ui/react-app/src/contexts/PathPrefixContext.tsx
Normal file
|
@ -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';
|
import { isPresent } from './utils';
|
||||||
|
|
||||||
// Declared/defined in public/index.html, value replaced by Prometheus when serving bundle.
|
// 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;
|
declare const GLOBAL_CONSOLES_LINK: string;
|
||||||
|
|
||||||
let prefix = GLOBAL_PATH_PREFIX;
|
|
||||||
let consolesLink: string | null = GLOBAL_CONSOLES_LINK;
|
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 (
|
if (
|
||||||
GLOBAL_CONSOLES_LINK === 'CONSOLES_LINK_PLACEHOLDER' ||
|
GLOBAL_CONSOLES_LINK === 'CONSOLES_LINK_PLACEHOLDER' ||
|
||||||
GLOBAL_CONSOLES_LINK === '' ||
|
GLOBAL_CONSOLES_LINK === '' ||
|
||||||
|
@ -28,4 +18,4 @@ if (
|
||||||
consolesLink = null;
|
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 React, { FC } from 'react';
|
||||||
import { RouteComponentProps } from '@reach/router';
|
import { RouteComponentProps } from '@reach/router';
|
||||||
import PathPrefixProps from '../../types/PathPrefixProps';
|
|
||||||
import { useFetch } from '../../hooks/useFetch';
|
import { useFetch } from '../../hooks/useFetch';
|
||||||
import { withStatusIndicator } from '../../components/withStatusIndicator';
|
import { withStatusIndicator } from '../../components/withStatusIndicator';
|
||||||
import AlertsContent, { RuleStatus, AlertsProps } from './AlertContents';
|
import AlertsContent, { RuleStatus, AlertsProps } from './AlertContents';
|
||||||
|
import { usePathPrefix } from '../../contexts/PathPrefixContext';
|
||||||
|
import { API_PATH } from '../../constants/constants';
|
||||||
|
|
||||||
const AlertsWithStatusIndicator = withStatusIndicator(AlertsContent);
|
const AlertsWithStatusIndicator = withStatusIndicator(AlertsContent);
|
||||||
|
|
||||||
const Alerts: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix = '' }) => {
|
const Alerts: FC<RouteComponentProps> = () => {
|
||||||
const { response, error, isLoading } = useFetch<AlertsProps>(`${pathPrefix}/api/v1/rules?type=alert`);
|
const pathPrefix = usePathPrefix();
|
||||||
|
const { response, error, isLoading } = useFetch<AlertsProps>(`${pathPrefix}/${API_PATH}/rules?type=alert`);
|
||||||
|
|
||||||
const ruleStatsCount: RuleStatus<number> = {
|
const ruleStatsCount: RuleStatus<number> = {
|
||||||
inactive: 0,
|
inactive: 0,
|
||||||
|
|
|
@ -2,11 +2,12 @@ import React, { useState, FC } from 'react';
|
||||||
import { RouteComponentProps } from '@reach/router';
|
import { RouteComponentProps } from '@reach/router';
|
||||||
import { Button } from 'reactstrap';
|
import { Button } from 'reactstrap';
|
||||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||||
import PathPrefixProps from '../../types/PathPrefixProps';
|
|
||||||
|
|
||||||
import './Config.css';
|
import './Config.css';
|
||||||
import { withStatusIndicator } from '../../components/withStatusIndicator';
|
import { withStatusIndicator } from '../../components/withStatusIndicator';
|
||||||
import { useFetch } from '../../hooks/useFetch';
|
import { useFetch } from '../../hooks/useFetch';
|
||||||
|
import { usePathPrefix } from '../../contexts/PathPrefixContext';
|
||||||
|
import { API_PATH } from '../../constants/constants';
|
||||||
|
|
||||||
type YamlConfig = { yaml?: string };
|
type YamlConfig = { yaml?: string };
|
||||||
|
|
||||||
|
@ -44,8 +45,9 @@ export const ConfigContent: FC<ConfigContentProps> = ({ error, data }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Config: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix }) => {
|
const Config: FC<RouteComponentProps> = () => {
|
||||||
const { response, error } = useFetch<YamlConfig>(`${pathPrefix}/api/v1/status/config`);
|
const pathPrefix = usePathPrefix();
|
||||||
|
const { response, error } = useFetch<YamlConfig>(`${pathPrefix}/${API_PATH}/status/config`);
|
||||||
return <ConfigContent error={error} data={response.data} />;
|
return <ConfigContent error={error} data={response.data} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,8 @@ import { RouteComponentProps } from '@reach/router';
|
||||||
import { Table } from 'reactstrap';
|
import { Table } from 'reactstrap';
|
||||||
import { withStatusIndicator } from '../../components/withStatusIndicator';
|
import { withStatusIndicator } from '../../components/withStatusIndicator';
|
||||||
import { useFetch } from '../../hooks/useFetch';
|
import { useFetch } from '../../hooks/useFetch';
|
||||||
import PathPrefixProps from '../../types/PathPrefixProps';
|
import { usePathPrefix } from '../../contexts/PathPrefixContext';
|
||||||
|
import { API_PATH } from '../../constants/constants';
|
||||||
|
|
||||||
interface FlagMap {
|
interface FlagMap {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
|
@ -34,8 +35,9 @@ const FlagsWithStatusIndicator = withStatusIndicator(FlagsContent);
|
||||||
|
|
||||||
FlagsContent.displayName = 'Flags';
|
FlagsContent.displayName = 'Flags';
|
||||||
|
|
||||||
const Flags: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix = '' }) => {
|
const Flags: FC<RouteComponentProps> = () => {
|
||||||
const { response, error, isLoading } = useFetch<FlagMap>(`${pathPrefix}/api/v1/status/flags`);
|
const pathPrefix = usePathPrefix();
|
||||||
|
const { response, error, isLoading } = useFetch<FlagMap>(`${pathPrefix}/${API_PATH}/status/flags`);
|
||||||
return <FlagsWithStatusIndicator data={response.data} error={error} isLoading={isLoading} />;
|
return <FlagsWithStatusIndicator data={response.data} error={error} isLoading={isLoading} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,8 @@ import { GraphTabContent } from './GraphTabContent';
|
||||||
import DataTable from './DataTable';
|
import DataTable from './DataTable';
|
||||||
import TimeInput from './TimeInput';
|
import TimeInput from './TimeInput';
|
||||||
import QueryStatsView, { QueryStats } from './QueryStatsView';
|
import QueryStatsView, { QueryStats } from './QueryStatsView';
|
||||||
import PathPrefixProps from '../../types/PathPrefixProps';
|
|
||||||
import { QueryParams } from '../../types/types';
|
import { QueryParams } from '../../types/types';
|
||||||
|
import { API_PATH } from '../../constants/constants';
|
||||||
|
|
||||||
interface PanelProps {
|
interface PanelProps {
|
||||||
options: PanelOptions;
|
options: PanelOptions;
|
||||||
|
@ -21,6 +21,7 @@ interface PanelProps {
|
||||||
metricNames: string[];
|
metricNames: string[];
|
||||||
removePanel: () => void;
|
removePanel: () => void;
|
||||||
onExecuteQuery: (query: string) => void;
|
onExecuteQuery: (query: string) => void;
|
||||||
|
pathPrefix: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PanelState {
|
interface PanelState {
|
||||||
|
@ -55,7 +56,7 @@ export const PanelDefaultOptions: PanelOptions = {
|
||||||
stacked: false,
|
stacked: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
class Panel extends Component<PanelProps & PathPrefixProps, PanelState> {
|
class Panel extends Component<PanelProps, PanelState> {
|
||||||
private abortInFlightFetch: (() => void) | null = null;
|
private abortInFlightFetch: (() => void) | null = null;
|
||||||
|
|
||||||
constructor(props: PanelProps) {
|
constructor(props: PanelProps) {
|
||||||
|
@ -117,21 +118,20 @@ class Panel extends Component<PanelProps & PathPrefixProps, PanelState> {
|
||||||
let path: string;
|
let path: string;
|
||||||
switch (this.props.options.type) {
|
switch (this.props.options.type) {
|
||||||
case 'graph':
|
case 'graph':
|
||||||
path = '/api/v1/query_range';
|
path = 'query_range';
|
||||||
params.append('start', startTime.toString());
|
params.append('start', startTime.toString());
|
||||||
params.append('end', endTime.toString());
|
params.append('end', endTime.toString());
|
||||||
params.append('step', resolution.toString());
|
params.append('step', resolution.toString());
|
||||||
// TODO path prefix here and elsewhere.
|
|
||||||
break;
|
break;
|
||||||
case 'table':
|
case 'table':
|
||||||
path = '/api/v1/query';
|
path = 'query';
|
||||||
params.append('time', endTime.toString());
|
params.append('time', endTime.toString());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error('Invalid panel type "' + this.props.options.type + '"');
|
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',
|
cache: 'no-store',
|
||||||
credentials: 'same-origin',
|
credentials: 'same-origin',
|
||||||
signal: abortController.signal,
|
signal: abortController.signal,
|
||||||
|
|
|
@ -4,10 +4,11 @@ import { Alert, Button } from 'reactstrap';
|
||||||
|
|
||||||
import Panel, { PanelOptions, PanelDefaultOptions } from './Panel';
|
import Panel, { PanelOptions, PanelDefaultOptions } from './Panel';
|
||||||
import Checkbox from '../../components/Checkbox';
|
import Checkbox from '../../components/Checkbox';
|
||||||
import PathPrefixProps from '../../types/PathPrefixProps';
|
|
||||||
import { generateID, decodePanelOptionsFromQueryString, encodePanelOptionsToQueryString, callAll } from '../../utils';
|
import { generateID, decodePanelOptionsFromQueryString, encodePanelOptionsToQueryString, callAll } from '../../utils';
|
||||||
import { useFetch } from '../../hooks/useFetch';
|
import { useFetch } from '../../hooks/useFetch';
|
||||||
import { useLocalStorage } from '../../hooks/useLocalStorage';
|
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 };
|
export type PanelMeta = { key: string; options: PanelOptions; id: string };
|
||||||
|
|
||||||
|
@ -16,20 +17,14 @@ export const updateURL = (nextPanels: PanelMeta[]) => {
|
||||||
window.history.pushState({}, '', query);
|
window.history.pushState({}, '', query);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface PanelListProps extends PathPrefixProps, RouteComponentProps {
|
interface PanelListProps extends RouteComponentProps {
|
||||||
panels: PanelMeta[];
|
panels: PanelMeta[];
|
||||||
metrics: string[];
|
metrics: string[];
|
||||||
useLocalTime: boolean;
|
useLocalTime: boolean;
|
||||||
queryHistoryEnabled: boolean;
|
queryHistoryEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PanelListContent: FC<PanelListProps> = ({
|
export const PanelListContent: FC<PanelListProps> = ({ metrics = [], useLocalTime, queryHistoryEnabled, ...rest }) => {
|
||||||
metrics = [],
|
|
||||||
useLocalTime,
|
|
||||||
pathPrefix,
|
|
||||||
queryHistoryEnabled,
|
|
||||||
...rest
|
|
||||||
}) => {
|
|
||||||
const [panels, setPanels] = useState(rest.panels);
|
const [panels, setPanels] = useState(rest.panels);
|
||||||
const [historyItems, setLocalStorageHistoryItems] = useLocalStorage<string[]>('history', []);
|
const [historyItems, setLocalStorageHistoryItems] = useLocalStorage<string[]>('history', []);
|
||||||
|
|
||||||
|
@ -73,10 +68,13 @@ export const PanelListContent: FC<PanelListProps> = ({
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const pathPrefix = usePathPrefix();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{panels.map(({ id, options }) => (
|
{panels.map(({ id, options }) => (
|
||||||
<Panel
|
<Panel
|
||||||
|
pathPrefix={pathPrefix}
|
||||||
onExecuteQuery={handleExecuteQuery}
|
onExecuteQuery={handleExecuteQuery}
|
||||||
key={id}
|
key={id}
|
||||||
options={options}
|
options={options}
|
||||||
|
@ -97,7 +95,6 @@ export const PanelListContent: FC<PanelListProps> = ({
|
||||||
useLocalTime={useLocalTime}
|
useLocalTime={useLocalTime}
|
||||||
metricNames={metrics}
|
metricNames={metrics}
|
||||||
pastQueries={queryHistoryEnabled ? historyItems : []}
|
pastQueries={queryHistoryEnabled ? historyItems : []}
|
||||||
pathPrefix={pathPrefix}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<Button className="mb-3" color="primary" onClick={addPanel}>
|
<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 [delta, setDelta] = useState(0);
|
||||||
const [useLocalTime, setUseLocalTime] = useLocalStorage('use-local-time', false);
|
const [useLocalTime, setUseLocalTime] = useLocalStorage('use-local-time', false);
|
||||||
const [enableQueryHistory, setEnableQueryHistory] = useLocalStorage('enable-query-history', 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 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(() => {
|
useEffect(() => {
|
||||||
if (timeRes.data) {
|
if (timeRes.data) {
|
||||||
|
@ -164,7 +164,6 @@ const PanelList: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix = ''
|
||||||
)}
|
)}
|
||||||
<PanelListContent
|
<PanelListContent
|
||||||
panels={decodePanelOptionsFromQueryString(window.location.search)}
|
panels={decodePanelOptionsFromQueryString(window.location.search)}
|
||||||
pathPrefix={pathPrefix}
|
|
||||||
useLocalTime={useLocalTime}
|
useLocalTime={useLocalTime}
|
||||||
metrics={metricsRes.data}
|
metrics={metricsRes.data}
|
||||||
queryHistoryEnabled={enableQueryHistory}
|
queryHistoryEnabled={enableQueryHistory}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { RouteComponentProps } from '@reach/router';
|
import { RouteComponentProps } from '@reach/router';
|
||||||
import PathPrefixProps from '../../types/PathPrefixProps';
|
|
||||||
import { useFetch } from '../../hooks/useFetch';
|
import { useFetch } from '../../hooks/useFetch';
|
||||||
import { withStatusIndicator } from '../../components/withStatusIndicator';
|
import { withStatusIndicator } from '../../components/withStatusIndicator';
|
||||||
import { RulesMap, RulesContent } from './RulesContent';
|
import { RulesMap, RulesContent } from './RulesContent';
|
||||||
|
import { usePathPrefix } from '../../contexts/PathPrefixContext';
|
||||||
|
import { API_PATH } from '../../constants/constants';
|
||||||
|
|
||||||
const RulesWithStatusIndicator = withStatusIndicator(RulesContent);
|
const RulesWithStatusIndicator = withStatusIndicator(RulesContent);
|
||||||
|
|
||||||
const Rules: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix }) => {
|
const Rules: FC<RouteComponentProps> = () => {
|
||||||
const { response, error, isLoading } = useFetch<RulesMap>(`${pathPrefix}/api/v1/rules`);
|
const pathPrefix = usePathPrefix();
|
||||||
|
const { response, error, isLoading } = useFetch<RulesMap>(`${pathPrefix}/${API_PATH}/rules`);
|
||||||
|
|
||||||
return <RulesWithStatusIndicator response={response} error={error} isLoading={isLoading} />;
|
return <RulesWithStatusIndicator response={response} error={error} isLoading={isLoading} />;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { RouteComponentProps } from '@reach/router';
|
import { RouteComponentProps } from '@reach/router';
|
||||||
import PathPrefixProps from '../../types/PathPrefixProps';
|
|
||||||
import { useFetch } from '../../hooks/useFetch';
|
import { useFetch } from '../../hooks/useFetch';
|
||||||
import { LabelsTable } from './LabelsTable';
|
import { LabelsTable } from './LabelsTable';
|
||||||
import { Target, Labels, DroppedTarget } from '../targets/target';
|
import { Target, Labels, DroppedTarget } from '../targets/target';
|
||||||
|
|
||||||
import { withStatusIndicator } from '../../components/withStatusIndicator';
|
import { withStatusIndicator } from '../../components/withStatusIndicator';
|
||||||
import { mapObjEntries } from '../../utils';
|
import { mapObjEntries } from '../../utils';
|
||||||
|
import { usePathPrefix } from '../../contexts/PathPrefixContext';
|
||||||
|
import { API_PATH } from '../../constants/constants';
|
||||||
|
|
||||||
interface ServiceMap {
|
interface ServiceMap {
|
||||||
activeTargets: Target[];
|
activeTargets: Target[];
|
||||||
|
@ -105,8 +106,9 @@ ServiceDiscoveryContent.displayName = 'ServiceDiscoveryContent';
|
||||||
|
|
||||||
const ServicesWithStatusIndicator = withStatusIndicator(ServiceDiscoveryContent);
|
const ServicesWithStatusIndicator = withStatusIndicator(ServiceDiscoveryContent);
|
||||||
|
|
||||||
const ServiceDiscovery: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix }) => {
|
const ServiceDiscovery: FC<RouteComponentProps> = () => {
|
||||||
const { response, error, isLoading } = useFetch<ServiceMap>(`${pathPrefix}/api/v1/targets`);
|
const pathPrefix = usePathPrefix();
|
||||||
|
const { response, error, isLoading } = useFetch<ServiceMap>(`${pathPrefix}/${API_PATH}/targets`);
|
||||||
return (
|
return (
|
||||||
<ServicesWithStatusIndicator
|
<ServicesWithStatusIndicator
|
||||||
{...response.data}
|
{...response.data}
|
||||||
|
|
|
@ -3,7 +3,8 @@ import { RouteComponentProps } from '@reach/router';
|
||||||
import { Table } from 'reactstrap';
|
import { Table } from 'reactstrap';
|
||||||
import { withStatusIndicator } from '../../components/withStatusIndicator';
|
import { withStatusIndicator } from '../../components/withStatusIndicator';
|
||||||
import { useFetch } from '../../hooks/useFetch';
|
import { useFetch } from '../../hooks/useFetch';
|
||||||
import PathPrefixProps from '../../types/PathPrefixProps';
|
import { usePathPrefix } from '../../contexts/PathPrefixContext';
|
||||||
|
import { API_PATH } from '../../constants/constants';
|
||||||
|
|
||||||
interface StatusPageProps {
|
interface StatusPageProps {
|
||||||
data: Record<string, string>;
|
data: Record<string, string>;
|
||||||
|
@ -82,8 +83,9 @@ const StatusWithStatusIndicator = withStatusIndicator(StatusContent);
|
||||||
|
|
||||||
StatusContent.displayName = 'Status';
|
StatusContent.displayName = 'Status';
|
||||||
|
|
||||||
const Status: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix = '' }) => {
|
const Status: FC<RouteComponentProps> = () => {
|
||||||
const path = `${pathPrefix}/api/v1`;
|
const pathPrefix = usePathPrefix();
|
||||||
|
const path = `${pathPrefix}/${API_PATH}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -7,11 +7,11 @@ import ScrapePoolList from './ScrapePoolList';
|
||||||
import ScrapePoolPanel from './ScrapePoolPanel';
|
import ScrapePoolPanel from './ScrapePoolPanel';
|
||||||
import { Target } from './target';
|
import { Target } from './target';
|
||||||
import { FetchMock } from 'jest-fetch-mock/types';
|
import { FetchMock } from 'jest-fetch-mock/types';
|
||||||
|
import { PathPrefixContext } from '../../contexts/PathPrefixContext';
|
||||||
|
|
||||||
describe('ScrapePoolList', () => {
|
describe('ScrapePoolList', () => {
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
filter: { showHealthy: true, showUnhealthy: true },
|
filter: { showHealthy: true, showUnhealthy: true },
|
||||||
pathPrefix: '..',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -36,10 +36,17 @@ describe('ScrapePoolList', () => {
|
||||||
|
|
||||||
it('renders a table', async () => {
|
it('renders a table', async () => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
scrapePoolList = mount(<ScrapePoolList {...defaultProps} />);
|
scrapePoolList = mount(
|
||||||
|
<PathPrefixContext.Provider value="/path/prefix">
|
||||||
|
<ScrapePoolList {...defaultProps} />
|
||||||
|
</PathPrefixContext.Provider>
|
||||||
|
);
|
||||||
});
|
});
|
||||||
scrapePoolList.update();
|
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);
|
const panels = scrapePoolList.find(ScrapePoolPanel);
|
||||||
expect(panels).toHaveLength(3);
|
expect(panels).toHaveLength(3);
|
||||||
const activeTargets: Target[] = sampleApiResponse.data.activeTargets as Target[];
|
const activeTargets: Target[] = sampleApiResponse.data.activeTargets as Target[];
|
||||||
|
@ -55,10 +62,17 @@ describe('ScrapePoolList', () => {
|
||||||
filter: { showHealthy: false, showUnhealthy: true },
|
filter: { showHealthy: false, showUnhealthy: true },
|
||||||
};
|
};
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
scrapePoolList = mount(<ScrapePoolList {...props} />);
|
scrapePoolList = mount(
|
||||||
|
<PathPrefixContext.Provider value="/path/prefix">
|
||||||
|
<ScrapePoolList {...props} />
|
||||||
|
</PathPrefixContext.Provider>
|
||||||
|
);
|
||||||
});
|
});
|
||||||
scrapePoolList.update();
|
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);
|
const panels = scrapePoolList.find(ScrapePoolPanel);
|
||||||
expect(panels).toHaveLength(0);
|
expect(panels).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
@ -70,11 +84,18 @@ describe('ScrapePoolList', () => {
|
||||||
|
|
||||||
let scrapePoolList: any;
|
let scrapePoolList: any;
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
scrapePoolList = mount(<ScrapePoolList {...defaultProps} />);
|
scrapePoolList = mount(
|
||||||
|
<PathPrefixContext.Provider value="/path/prefix">
|
||||||
|
<ScrapePoolList {...defaultProps} />
|
||||||
|
</PathPrefixContext.Provider>
|
||||||
|
);
|
||||||
});
|
});
|
||||||
scrapePoolList.update();
|
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);
|
const alert = scrapePoolList.find(Alert);
|
||||||
expect(alert.prop('color')).toBe('danger');
|
expect(alert.prop('color')).toBe('danger');
|
||||||
expect(alert.text()).toContain('Error fetching targets');
|
expect(alert.text()).toContain('Error fetching targets');
|
||||||
|
|
|
@ -3,8 +3,9 @@ import { FilterData } from './Filter';
|
||||||
import { useFetch } from '../../hooks/useFetch';
|
import { useFetch } from '../../hooks/useFetch';
|
||||||
import { groupTargets, Target } from './target';
|
import { groupTargets, Target } from './target';
|
||||||
import ScrapePoolPanel from './ScrapePoolPanel';
|
import ScrapePoolPanel from './ScrapePoolPanel';
|
||||||
import PathPrefixProps from '../../types/PathPrefixProps';
|
|
||||||
import { withStatusIndicator } from '../../components/withStatusIndicator';
|
import { withStatusIndicator } from '../../components/withStatusIndicator';
|
||||||
|
import { usePathPrefix } from '../../contexts/PathPrefixContext';
|
||||||
|
import { API_PATH } from '../../constants/constants';
|
||||||
|
|
||||||
interface ScrapePoolListProps {
|
interface ScrapePoolListProps {
|
||||||
filter: FilterData;
|
filter: FilterData;
|
||||||
|
@ -30,8 +31,9 @@ ScrapePoolContent.displayName = 'ScrapePoolContent';
|
||||||
|
|
||||||
const ScrapePoolListWithStatusIndicator = withStatusIndicator(ScrapePoolContent);
|
const ScrapePoolListWithStatusIndicator = withStatusIndicator(ScrapePoolContent);
|
||||||
|
|
||||||
const ScrapePoolList: FC<{ filter: FilterData } & PathPrefixProps> = ({ pathPrefix, filter }) => {
|
const ScrapePoolList: FC<{ filter: FilterData }> = ({ filter }) => {
|
||||||
const { response, error, isLoading } = useFetch<ScrapePoolListProps>(`${pathPrefix}/api/v1/targets?state=active`);
|
const pathPrefix = usePathPrefix();
|
||||||
|
const { response, error, isLoading } = useFetch<ScrapePoolListProps>(`${pathPrefix}/${API_PATH}/targets?state=active`);
|
||||||
const { status: responseStatus } = response;
|
const { status: responseStatus } = response;
|
||||||
const badResponse = responseStatus !== 'success' && responseStatus !== 'start fetching';
|
const badResponse = responseStatus !== 'success' && responseStatus !== 'start fetching';
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -28,6 +28,5 @@ describe('Targets', () => {
|
||||||
const scrapePoolList = targets.find(ScrapePoolList);
|
const scrapePoolList = targets.find(ScrapePoolList);
|
||||||
expect(scrapePoolList).toHaveLength(1);
|
expect(scrapePoolList).toHaveLength(1);
|
||||||
expect(scrapePoolList.prop('filter')).toEqual({ showHealthy: true, showUnhealthy: true });
|
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 { RouteComponentProps } from '@reach/router';
|
||||||
import Filter from './Filter';
|
import Filter from './Filter';
|
||||||
import ScrapePoolList from './ScrapePoolList';
|
import ScrapePoolList from './ScrapePoolList';
|
||||||
import PathPrefixProps from '../../types/PathPrefixProps';
|
|
||||||
import { useLocalStorage } from '../../hooks/useLocalStorage';
|
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 [filter, setFilter] = useLocalStorage('targets-page-filter', { showHealthy: true, showUnhealthy: true });
|
||||||
const filterProps = { filter, setFilter };
|
const filterProps = { filter, setFilter };
|
||||||
const scrapePoolListProps = { filter, pathPrefix };
|
const scrapePoolListProps = { filter, pathPrefix, API_PATH };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { Table } from 'reactstrap';
|
||||||
|
|
||||||
import TSDBStatus from './TSDBStatus';
|
import TSDBStatus from './TSDBStatus';
|
||||||
import { TSDBMap } from './TSDBStatus';
|
import { TSDBMap } from './TSDBStatus';
|
||||||
|
import { PathPrefixContext } from '../../contexts/PathPrefixContext';
|
||||||
|
|
||||||
const fakeTSDBStatusResponse: {
|
const fakeTSDBStatusResponse: {
|
||||||
status: string;
|
status: string;
|
||||||
|
@ -66,11 +67,15 @@ describe('TSDB Stats', () => {
|
||||||
const mock = fetchMock.mockResponse(JSON.stringify(fakeTSDBStatusResponse));
|
const mock = fetchMock.mockResponse(JSON.stringify(fakeTSDBStatusResponse));
|
||||||
let page: any;
|
let page: any;
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
page = mount(<TSDBStatus pathPrefix="/path/prefix" />);
|
page = mount(
|
||||||
|
<PathPrefixContext.Provider value="/path/prefix">
|
||||||
|
<TSDBStatus />
|
||||||
|
</PathPrefixContext.Provider>
|
||||||
|
);
|
||||||
});
|
});
|
||||||
page.update();
|
page.update();
|
||||||
|
|
||||||
expect(mock).toHaveBeenCalledWith('/path/prefix/api/v1/status/tsdb', {
|
expect(mock).toHaveBeenCalledWith('/path/prefix/../api/v1/status/tsdb', {
|
||||||
cache: 'no-store',
|
cache: 'no-store',
|
||||||
credentials: 'same-origin',
|
credentials: 'same-origin',
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,8 +3,9 @@ import { RouteComponentProps } from '@reach/router';
|
||||||
import { Table } from 'reactstrap';
|
import { Table } from 'reactstrap';
|
||||||
|
|
||||||
import { useFetch } from '../../hooks/useFetch';
|
import { useFetch } from '../../hooks/useFetch';
|
||||||
import PathPrefixProps from '../../types/PathPrefixProps';
|
|
||||||
import { withStatusIndicator } from '../../components/withStatusIndicator';
|
import { withStatusIndicator } from '../../components/withStatusIndicator';
|
||||||
|
import { usePathPrefix } from '../../contexts/PathPrefixContext';
|
||||||
|
import { API_PATH } from '../../constants/constants';
|
||||||
|
|
||||||
interface Stats {
|
interface Stats {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -101,8 +102,9 @@ TSDBStatusContent.displayName = 'TSDBStatusContent';
|
||||||
|
|
||||||
const TSDBStatusContentWithStatusIndicator = withStatusIndicator(TSDBStatusContent);
|
const TSDBStatusContentWithStatusIndicator = withStatusIndicator(TSDBStatusContent);
|
||||||
|
|
||||||
const TSDBStatus: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix }) => {
|
const TSDBStatus: FC<RouteComponentProps> = () => {
|
||||||
const { response, error, isLoading } = useFetch<TSDBMap>(`${pathPrefix}/api/v1/status/tsdb`);
|
const pathPrefix = usePathPrefix();
|
||||||
|
const { response, error, isLoading } = useFetch<TSDBMap>(`${pathPrefix}/${API_PATH}/status/tsdb`);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TSDBStatusContentWithStatusIndicator
|
<TSDBStatusContentWithStatusIndicator
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
interface PathPrefixProps {
|
|
||||||
pathPrefix?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PathPrefixProps;
|
|
|
@ -79,7 +79,6 @@ var reactRouterPaths = []string{
|
||||||
"/status",
|
"/status",
|
||||||
"/targets",
|
"/targets",
|
||||||
"/tsdb-status",
|
"/tsdb-status",
|
||||||
"/version",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// withStackTrace logs the stack trace in case the request panics. The function
|
// 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)
|
fmt.Fprintf(w, "Error reading React index.html: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
replacedIdx := bytes.ReplaceAll(idx, []byte("PATH_PREFIX_PLACEHOLDER"), []byte(o.ExternalURL.Path))
|
replacedIdx := bytes.ReplaceAll(idx, []byte("CONSOLES_LINK_PLACEHOLDER"), []byte(h.consolesPath()))
|
||||||
replacedIdx = bytes.ReplaceAll(replacedIdx, []byte("CONSOLES_LINK_PLACEHOLDER"), []byte(h.consolesPath()))
|
|
||||||
replacedIdx = bytes.ReplaceAll(replacedIdx, []byte("TITLE_PLACEHOLDER"), []byte(h.options.PageTitle))
|
replacedIdx = bytes.ReplaceAll(replacedIdx, []byte("TITLE_PLACEHOLDER"), []byte(h.options.PageTitle))
|
||||||
w.Write(replacedIdx)
|
w.Write(replacedIdx)
|
||||||
return
|
return
|
||||||
|
|
Loading…
Reference in a new issue