mirror of https://github.com/prometheus/prometheus
React UI: Add Starting Screen to Individual Pages (#8909)
* Fix/removed forwarding Signed-off-by: Levi Harrison <git@leviharrison.dev> * Added global 'wasReady' and 'wasUnexpected' Signed-off-by: Levi Harrison <git@leviharrison.dev> * Eslint fixes Signed-off-by: Levi Harrison <git@leviharrison.dev> * Added withStartingIndicator wrapper Signed-off-by: Levi Harrison <git@leviharrison.dev> * Fixed condition Signed-off-by: Levi Harrison <git@leviharrison.dev> * Removed unused import Signed-off-by: Levi Harrison <git@leviharrison.dev> * Moved withStartingIndicator calls to pages index Signed-off-by: Levi Harrison <git@leviharrison.dev> * Fixed withStartingIndicator tests Signed-off-by: Levi Harrison <git@leviharrison.dev> * Fixed eslint (maybe?) Signed-off-by: Levi Harrison <git@leviharrison.dev> * Trailing comma Signed-off-by: Levi Harrison <git@leviharrison.dev> * Added prettier ignore Signed-off-by: Levi Harrison <git@leviharrison.dev> * Fix eslint (pt. 2) Signed-off-by: Levi Harrison <git@leviharrison.dev>pull/8946/head
parent
ea3728bcac
commit
eb8ca06885
|
@ -4,7 +4,17 @@ import App from './App';
|
||||||
import Navigation from './Navbar';
|
import Navigation from './Navbar';
|
||||||
import { Container } from 'reactstrap';
|
import { Container } from 'reactstrap';
|
||||||
import { Router } from '@reach/router';
|
import { Router } from '@reach/router';
|
||||||
import { Alerts, Config, Flags, Rules, ServiceDiscovery, Status, Targets, TSDBStatus, PanelList } from './pages';
|
import {
|
||||||
|
AlertsPage,
|
||||||
|
ConfigPage,
|
||||||
|
FlagsPage,
|
||||||
|
RulesPage,
|
||||||
|
ServiceDiscoveryPage,
|
||||||
|
StatusPage,
|
||||||
|
TargetsPage,
|
||||||
|
TSDBStatusPage,
|
||||||
|
PanelListPage,
|
||||||
|
} from './pages';
|
||||||
|
|
||||||
describe('App', () => {
|
describe('App', () => {
|
||||||
const app = shallow(<App />);
|
const app = shallow(<App />);
|
||||||
|
@ -13,7 +23,17 @@ describe('App', () => {
|
||||||
expect(app.find(Navigation)).toHaveLength(1);
|
expect(app.find(Navigation)).toHaveLength(1);
|
||||||
});
|
});
|
||||||
it('routes', () => {
|
it('routes', () => {
|
||||||
[Alerts, Config, Flags, Rules, ServiceDiscovery, Status, Targets, TSDBStatus, PanelList].forEach(component => {
|
[
|
||||||
|
AlertsPage,
|
||||||
|
ConfigPage,
|
||||||
|
FlagsPage,
|
||||||
|
RulesPage,
|
||||||
|
ServiceDiscoveryPage,
|
||||||
|
StatusPage,
|
||||||
|
TargetsPage,
|
||||||
|
TSDBStatusPage,
|
||||||
|
PanelListPage,
|
||||||
|
].forEach(component => {
|
||||||
const c = app.find(component);
|
const c = app.find(component);
|
||||||
expect(c).toHaveLength(1);
|
expect(c).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,15 +2,23 @@ import React, { FC } from 'react';
|
||||||
import Navigation from './Navbar';
|
import Navigation from './Navbar';
|
||||||
import { Container } from 'reactstrap';
|
import { Container } from 'reactstrap';
|
||||||
|
|
||||||
import { Router, Redirect, navigate } from '@reach/router';
|
import { Router, Redirect } from '@reach/router';
|
||||||
import useMedia from 'use-media';
|
import useMedia from 'use-media';
|
||||||
import { Alerts, Config, Flags, Rules, ServiceDiscovery, Status, Targets, TSDBStatus, PanelList, Starting } from './pages';
|
import {
|
||||||
|
AlertsPage,
|
||||||
|
ConfigPage,
|
||||||
|
FlagsPage,
|
||||||
|
RulesPage,
|
||||||
|
ServiceDiscoveryPage,
|
||||||
|
StatusPage,
|
||||||
|
TargetsPage,
|
||||||
|
TSDBStatusPage,
|
||||||
|
PanelListPage,
|
||||||
|
} from './pages';
|
||||||
import { PathPrefixContext } from './contexts/PathPrefixContext';
|
import { PathPrefixContext } from './contexts/PathPrefixContext';
|
||||||
import { ThemeContext, themeName, themeSetting } from './contexts/ThemeContext';
|
import { ThemeContext, themeName, themeSetting } from './contexts/ThemeContext';
|
||||||
import { Theme, themeLocalStorageKey } from './Theme';
|
import { Theme, themeLocalStorageKey } from './Theme';
|
||||||
import { useLocalStorage } from './hooks/useLocalStorage';
|
import { useLocalStorage } from './hooks/useLocalStorage';
|
||||||
import { useFetchReady } from './hooks/useFetch';
|
|
||||||
import { usePathPrefix } from './contexts/PathPrefixContext';
|
|
||||||
|
|
||||||
interface AppProps {
|
interface AppProps {
|
||||||
consolesLink: string | null;
|
consolesLink: string | null;
|
||||||
|
@ -31,7 +39,6 @@ const App: FC<AppProps> = ({ consolesLink }) => {
|
||||||
'/rules',
|
'/rules',
|
||||||
'/targets',
|
'/targets',
|
||||||
'/service-discovery',
|
'/service-discovery',
|
||||||
'/starting',
|
|
||||||
];
|
];
|
||||||
if (basePath.endsWith('/')) {
|
if (basePath.endsWith('/')) {
|
||||||
basePath = basePath.slice(0, -1);
|
basePath = basePath.slice(0, -1);
|
||||||
|
@ -45,14 +52,6 @@ const App: FC<AppProps> = ({ consolesLink }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const pathPrefix = usePathPrefix();
|
|
||||||
const { ready, isLoading, isUnexpected } = useFetchReady(pathPrefix);
|
|
||||||
if (basePath !== '/starting') {
|
|
||||||
if (!ready && !isLoading && !isUnexpected) {
|
|
||||||
navigate('/starting');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [userTheme, setUserTheme] = useLocalStorage<themeSetting>(themeLocalStorageKey, 'auto');
|
const [userTheme, setUserTheme] = useLocalStorage<themeSetting>(themeLocalStorageKey, 'auto');
|
||||||
const browserHasThemes = useMedia('(prefers-color-scheme)');
|
const browserHasThemes = useMedia('(prefers-color-scheme)');
|
||||||
const browserWantsDarkTheme = useMedia('(prefers-color-scheme: dark)');
|
const browserWantsDarkTheme = useMedia('(prefers-color-scheme: dark)');
|
||||||
|
@ -78,16 +77,15 @@ const App: FC<AppProps> = ({ consolesLink }) => {
|
||||||
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" />
|
<PanelListPage path="/graph" />
|
||||||
<Alerts path="/alerts" />
|
<AlertsPage path="/alerts" />
|
||||||
<Config path="/config" />
|
<ConfigPage path="/config" />
|
||||||
<Flags path="/flags" />
|
<FlagsPage path="/flags" />
|
||||||
<Rules path="/rules" />
|
<RulesPage path="/rules" />
|
||||||
<ServiceDiscovery path="/service-discovery" />
|
<ServiceDiscoveryPage path="/service-discovery" />
|
||||||
<Status path="/status" />
|
<StatusPage path="/status" />
|
||||||
<TSDBStatus path="/tsdb-status" />
|
<TSDBStatusPage path="/tsdb-status" />
|
||||||
<Targets path="/targets" />
|
<TargetsPage path="/targets" />
|
||||||
<Starting path="/starting" />
|
|
||||||
</Router>
|
</Router>
|
||||||
</Container>
|
</Container>
|
||||||
</PathPrefixContext.Provider>
|
</PathPrefixContext.Provider>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import { WALReplayData } from '../../types/types';
|
import { WALReplayData } from '../types/types';
|
||||||
import { StartingContent } from './Starting';
|
import { StartingContent } from './withStartingIndicator';
|
||||||
import { Progress } from 'reactstrap';
|
import { Progress } from 'reactstrap';
|
||||||
|
|
||||||
describe('Starting', () => {
|
describe('Starting', () => {
|
|
@ -1,10 +1,9 @@
|
||||||
import React, { FC, useEffect } from 'react';
|
import React, { FC, ComponentType } from 'react';
|
||||||
import { RouteComponentProps, navigate } from '@reach/router';
|
|
||||||
import { Progress, Alert } from 'reactstrap';
|
import { Progress, Alert } from 'reactstrap';
|
||||||
|
|
||||||
import { useFetchReadyInterval } from '../../hooks/useFetch';
|
import { useFetchReadyInterval } from '../hooks/useFetch';
|
||||||
import { WALReplayData } from '../../types/types';
|
import { WALReplayData } from '../types/types';
|
||||||
import { usePathPrefix } from '../../contexts/PathPrefixContext';
|
import { usePathPrefix } from '../contexts/PathPrefixContext';
|
||||||
|
|
||||||
interface StartingContentProps {
|
interface StartingContentProps {
|
||||||
isUnexpected: boolean;
|
isUnexpected: boolean;
|
||||||
|
@ -44,17 +43,13 @@ export const StartingContent: FC<StartingContentProps> = ({ status, isUnexpected
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Starting: FC<RouteComponentProps> = () => {
|
export const withStartingIndicator = <T extends {}>(Page: ComponentType<T>): FC<T> => ({ ...rest }) => {
|
||||||
const pathPrefix = usePathPrefix();
|
const pathPrefix = usePathPrefix();
|
||||||
const { ready, walReplayStatus, isUnexpected } = useFetchReadyInterval(pathPrefix);
|
const { ready, walReplayStatus, isUnexpected } = useFetchReadyInterval(pathPrefix);
|
||||||
|
|
||||||
useEffect(() => {
|
if (ready || isUnexpected) {
|
||||||
if (ready) {
|
return <Page {...(rest as T)} />;
|
||||||
navigate('/');
|
}
|
||||||
}
|
|
||||||
}, [ready]);
|
|
||||||
|
|
||||||
return <StartingContent isUnexpected={isUnexpected} status={walReplayStatus.data} />;
|
return <StartingContent isUnexpected={isUnexpected} status={walReplayStatus.data} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Starting;
|
|
|
@ -47,36 +47,7 @@ export const useFetch = <T extends {}>(url: string, options?: RequestInit): Fetc
|
||||||
return { response, error, isLoading };
|
return { response, error, isLoading };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useFetchReady = (pathPrefix: string, options?: RequestInit): FetchStateReady => {
|
let wasReady = false;
|
||||||
const [ready, setReady] = useState<boolean>(false);
|
|
||||||
const [isUnexpected, setIsUnexpected] = useState<boolean>(false);
|
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchData = async () => {
|
|
||||||
setIsLoading(true);
|
|
||||||
try {
|
|
||||||
const res = await fetch(`${pathPrefix}/-/ready`, { cache: 'no-store', credentials: 'same-origin', ...options });
|
|
||||||
if (res.status === 200) {
|
|
||||||
setReady(true);
|
|
||||||
}
|
|
||||||
// The server sends back a 503 if it isn't ready,
|
|
||||||
// if we get back anything else that means something has gone wrong.
|
|
||||||
if (res.status !== 503) {
|
|
||||||
setIsUnexpected(true);
|
|
||||||
} else {
|
|
||||||
setIsUnexpected(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsLoading(false);
|
|
||||||
} catch (error) {
|
|
||||||
setIsUnexpected(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fetchData();
|
|
||||||
}, [pathPrefix, options]);
|
|
||||||
return { ready, isUnexpected, isLoading };
|
|
||||||
};
|
|
||||||
|
|
||||||
// This is used on the starting page to periodically check if the server is ready yet,
|
// This is used on the starting page to periodically check if the server is ready yet,
|
||||||
// and check the status of the WAL replay.
|
// and check the status of the WAL replay.
|
||||||
|
@ -86,33 +57,60 @@ export const useFetchReadyInterval = (pathPrefix: string, options?: RequestInit)
|
||||||
const [walReplayStatus, setWALReplayStatus] = useState<WALReplayStatus>({} as any);
|
const [walReplayStatus, setWALReplayStatus] = useState<WALReplayStatus>({} as any);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const interval = setInterval(async () => {
|
if (wasReady) {
|
||||||
try {
|
setReady(true);
|
||||||
let res = await fetch(`${pathPrefix}/-/ready`, { cache: 'no-store', credentials: 'same-origin', ...options });
|
} else {
|
||||||
if (res.status === 200) {
|
// This helps avoid a memory leak.
|
||||||
setReady(true);
|
let mounted = true;
|
||||||
|
|
||||||
|
const fetchStatus = async () => {
|
||||||
|
try {
|
||||||
|
let res = await fetch(`${pathPrefix}/-/ready`, { cache: 'no-store', credentials: 'same-origin', ...options });
|
||||||
|
if (res.status === 200) {
|
||||||
|
if (mounted) {
|
||||||
|
setReady(true);
|
||||||
|
}
|
||||||
|
wasReady = true;
|
||||||
|
clearInterval(interval);
|
||||||
|
} else if (res.status !== 503) {
|
||||||
|
if (mounted) {
|
||||||
|
setIsUnexpected(true);
|
||||||
|
}
|
||||||
|
clearInterval(interval);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
if (mounted) {
|
||||||
|
setIsUnexpected(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
res = await fetch(`${pathPrefix}/${API_PATH}/status/walreplay`, {
|
||||||
|
cache: 'no-store',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
const data = (await res.json()) as WALReplayStatus;
|
||||||
|
if (mounted) {
|
||||||
|
setWALReplayStatus(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (mounted) {
|
||||||
|
setIsUnexpected(true);
|
||||||
|
}
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (res.status !== 503) {
|
};
|
||||||
setIsUnexpected(true);
|
|
||||||
setWALReplayStatus({ data: { last: 0, first: 0 } } as any);
|
|
||||||
} else {
|
|
||||||
setIsUnexpected(false);
|
|
||||||
|
|
||||||
res = await fetch(`${pathPrefix}/${API_PATH}/status/walreplay`, { cache: 'no-store', credentials: 'same-origin' });
|
fetchStatus();
|
||||||
if (res.ok) {
|
const interval = setInterval(fetchStatus, 1000);
|
||||||
const data = (await res.json()) as WALReplayStatus;
|
return () => {
|
||||||
setWALReplayStatus(data);
|
clearInterval(interval);
|
||||||
}
|
mounted = false;
|
||||||
}
|
};
|
||||||
} catch (error) {
|
}
|
||||||
setIsUnexpected(true);
|
|
||||||
setWALReplayStatus({ data: { last: 0, first: 0 } } as any);
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, [pathPrefix, options]);
|
}, [pathPrefix, options]);
|
||||||
|
|
||||||
return { ready, isUnexpected, walReplayStatus };
|
return { ready, isUnexpected, walReplayStatus };
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,27 @@ import Status from './status/Status';
|
||||||
import Targets from './targets/Targets';
|
import Targets from './targets/Targets';
|
||||||
import PanelList from './graph/PanelList';
|
import PanelList from './graph/PanelList';
|
||||||
import TSDBStatus from './tsdbStatus/TSDBStatus';
|
import TSDBStatus from './tsdbStatus/TSDBStatus';
|
||||||
import Starting from './starting/Starting';
|
import { withStartingIndicator } from '../components/withStartingIndicator';
|
||||||
|
|
||||||
export { Alerts, Config, Flags, Rules, ServiceDiscovery, Status, Targets, TSDBStatus, PanelList, Starting };
|
const AlertsPage = withStartingIndicator(Alerts);
|
||||||
|
const ConfigPage = withStartingIndicator(Config);
|
||||||
|
const FlagsPage = withStartingIndicator(Flags);
|
||||||
|
const RulesPage = withStartingIndicator(Rules);
|
||||||
|
const ServiceDiscoveryPage = withStartingIndicator(ServiceDiscovery);
|
||||||
|
const StatusPage = withStartingIndicator(Status);
|
||||||
|
const TSDBStatusPage = withStartingIndicator(TSDBStatus);
|
||||||
|
const TargetsPage = withStartingIndicator(Targets);
|
||||||
|
const PanelListPage = withStartingIndicator(PanelList);
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
export {
|
||||||
|
AlertsPage,
|
||||||
|
ConfigPage,
|
||||||
|
FlagsPage,
|
||||||
|
RulesPage,
|
||||||
|
ServiceDiscoveryPage,
|
||||||
|
StatusPage,
|
||||||
|
TSDBStatusPage,
|
||||||
|
TargetsPage,
|
||||||
|
PanelListPage
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue