diff --git a/src/actions/GrafanaThunkActions.ts b/src/actions/GrafanaThunkActions.ts deleted file mode 100644 index 957d0abdde..0000000000 --- a/src/actions/GrafanaThunkActions.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ThunkDispatch } from 'redux-thunk'; -import { KialiAppState } from '../store/Store'; -import { GrafanaActions } from './GrafanaActions'; -import { MessageType } from '../types/MessageCenter'; -import { KialiAppAction } from './KialiAppAction'; -import { MessageCenterActions } from './MessageCenterActions'; -import * as API from '../services/Api'; - -const GrafanaThunkActions = { - getInfo: () => { - return (dispatch: ThunkDispatch) => { - API.getGrafanaInfo() - .then(response => { - dispatch(GrafanaActions.setinfo(response.data)); - }) - .catch(error => { - dispatch( - MessageCenterActions.addMessage( - API.getErrorMsg('Error fetching Grafana Info.', error), - 'default', - MessageType.WARNING - ) - ); - }); - }; - } -}; - -export default GrafanaThunkActions; diff --git a/src/actions/GraphDataThunkActions.ts b/src/actions/GraphDataThunkActions.ts index 0aa7956001..a7defe2cc6 100644 --- a/src/actions/GraphDataThunkActions.ts +++ b/src/actions/GraphDataThunkActions.ts @@ -42,7 +42,7 @@ const GraphDataThunkActions = { graphType: graphType, injectServiceNodes: injectServiceNodes }; - if (namespaces.find(namespace => namespace.name === serverConfig().istioNamespace)) { + if (namespaces.find(namespace => namespace.name === serverConfig.istioNamespace)) { restParams['includeIstio'] = true; } diff --git a/src/actions/HelpDropdownThunkActions.ts b/src/actions/HelpDropdownThunkActions.ts deleted file mode 100644 index 776c48c5af..0000000000 --- a/src/actions/HelpDropdownThunkActions.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { ThunkDispatch } from 'redux-thunk'; -import { KialiAppState } from '../store/Store'; -import { MessageType } from '../types/MessageCenter'; -import { HelpDropdownActions } from './HelpDropdownActions'; -import { JaegerActions } from './JaegerActions'; -import { KialiAppAction } from './KialiAppAction'; -import { MessageCenterActions } from './MessageCenterActions'; -import * as API from '../services/Api'; - -const HelpDropdownThunkActions = { - refresh: () => { - return (dispatch: ThunkDispatch) => { - API.getStatus().then( - status => { - dispatch( - HelpDropdownActions.statusRefresh( - status['data']['status'], - status['data']['externalServices'], - status['data']['warningMessages'] - ) - ); - - // Get the jaeger URL - const hasJaeger = status['data']['externalServices'].filter(item => item['name'] === 'Jaeger'); - if (hasJaeger.length === 1) { - dispatch(JaegerActions.setUrl(hasJaeger[0]['url'])); - // If same protocol enable integration - if (hasJaeger[0]['url'].startsWith(window.location.protocol)) { - dispatch(JaegerActions.setEnableIntegration(true)); - } - } - - status['data']['warningMessages'].forEach(wMsg => { - dispatch(MessageCenterActions.addMessage(wMsg, 'systemErrors', MessageType.WARNING)); - }); - }, - error => { - dispatch( - MessageCenterActions.addMessage( - API.getErrorMsg('Error fetching status.', error), - 'default', - MessageType.WARNING - ) - ); - } - ); - }; - } -}; - -export default HelpDropdownThunkActions; diff --git a/src/actions/KialiAppAction.ts b/src/actions/KialiAppAction.ts index 91058cde83..9415978c46 100644 --- a/src/actions/KialiAppAction.ts +++ b/src/actions/KialiAppAction.ts @@ -8,7 +8,6 @@ import { LoginAction } from './LoginActions'; import { MessageCenterAction } from './MessageCenterActions'; import { NamespaceAction } from './NamespaceAction'; import { UserSettingsAction } from './UserSettingsActions'; -import { ServerConfigAction } from './ServerConfigActions'; import { JaegerAction } from './JaegerActions'; export type KialiAppAction = @@ -21,6 +20,5 @@ export type KialiAppAction = | LoginAction | MessageCenterAction | NamespaceAction - | ServerConfigAction | UserSettingsAction | JaegerAction; diff --git a/src/actions/LoginThunkActions.ts b/src/actions/LoginThunkActions.ts index f72f0d62b5..a7c215c2d1 100644 --- a/src/actions/LoginThunkActions.ts +++ b/src/actions/LoginThunkActions.ts @@ -1,12 +1,7 @@ import moment from 'moment'; -import { HTTP_CODES } from '../types/Common'; import { KialiAppState, LoginState, LoginSession } from '../store/Store'; -import HelpDropdownThunkActions from './HelpDropdownThunkActions'; -import GrafanaThunkActions from './GrafanaThunkActions'; import { LoginActions } from './LoginActions'; import * as API from '../services/Api'; -import { ServerConfigActions } from './ServerConfigActions'; - import * as Login from '../services/Login'; import { AuthResult } from '../types/Auth'; import { KialiDispatch } from '../types/Redux'; @@ -21,20 +16,7 @@ const shouldRelogin = (state?: LoginState): boolean => moment(state.uiExpiresOn).diff(moment()) > 0; const loginSuccess = async (dispatch: KialiDispatch, session: LoginSession) => { - try { - dispatch(LoginActions.loginSuccess(session)); - - dispatch(HelpDropdownThunkActions.refresh()); - dispatch(GrafanaThunkActions.getInfo()); - - const response = await API.getServerConfig(); - - dispatch(ServerConfigActions.setServerConfig(response.data)); - } catch (error) { - if (error.response && error.response.status === HTTP_CODES.UNAUTHORIZED) { - dispatch(LoginActions.logoutSuccess()); - } - } + dispatch(LoginActions.loginSuccess(session)); }; // Performs the user login, dispatching to the proper login implementations. @@ -87,7 +69,6 @@ const LoginThunkActions = { if (response.status === 204) { dispatch(LoginActions.logoutSuccess()); - dispatch(LoginThunkActions.checkCredentials()); } } catch (err) { dispatch(MessageCenterActions.addMessage(API.getErrorMsg('Logout failed', err))); diff --git a/src/actions/ServerConfigActions.ts b/src/actions/ServerConfigActions.ts deleted file mode 100644 index d48d4ca507..0000000000 --- a/src/actions/ServerConfigActions.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ActionType, createStandardAction } from 'typesafe-actions'; -import { ServerConfig } from '../store/Store'; - -enum ServerConfigActionKeys { - SET_SERVER_CONFIG = 'SET_SERVER_CONFIG' -} - -// synchronous action creators -export const ServerConfigActions = { - setServerConfig: createStandardAction(ServerConfigActionKeys.SET_SERVER_CONFIG)() -}; - -export type ServerConfigAction = ActionType; diff --git a/src/actions/__tests__/ServerConfigAction.test.ts b/src/actions/__tests__/ServerConfigAction.test.ts deleted file mode 100644 index f1d3be1290..0000000000 --- a/src/actions/__tests__/ServerConfigAction.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ServerConfigActions } from '../ServerConfigActions'; -import { ServerConfig } from '../../store/Store'; -import { serverConfig } from '../../config/serverConfig'; -import { store } from '../../store/ConfigStore'; - -const config: ServerConfig = { - istioNamespace: 'istio-system', - istioLabels: { appLabelName: 'app', versionLabelName: 'version' }, - prometheus: { globalScrapeInterval: 15, storageTsdbRetention: 21600 } -}; - -describe('ServerConfigActions', () => { - it('Set ServerConfig action success', () => { - store.dispatch(ServerConfigActions.setServerConfig(config)); - expect(serverConfig().istioNamespace).toEqual(config.istioNamespace); - expect(serverConfig().istioLabels).toEqual(config.istioLabels); - expect(serverConfig().prometheus.globalScrapeInterval).toEqual(config.prometheus.globalScrapeInterval); - expect(serverConfig().prometheus.storageTsdbRetention).toEqual(config.prometheus.storageTsdbRetention); - }); -}); diff --git a/src/app/App.tsx b/src/app/App.tsx index ada20e07a7..aa746f0b7e 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,16 +1,18 @@ +import axios from 'axios'; import * as React from 'react'; -import { Router, withRouter } from 'react-router-dom'; +import { PersistGate } from 'redux-persist/lib/integration/react'; import { Provider } from 'react-redux'; -import './App.css'; +import { Router, withRouter } from 'react-router-dom'; +import * as Visibility from 'visibilityjs'; +import { GlobalActions } from '../actions/GlobalActions'; import NavigationContainer from '../containers/NavigationContainer'; import { store, persistor } from '../store/ConfigStore'; -import axios from 'axios'; -import { GlobalActions } from '../actions/GlobalActions'; +import './App.css'; +import AuthenticationControllerContainer from './AuthenticationController'; import history from './History'; -import { PersistGate } from 'redux-persist/lib/integration/react'; -import * as Visibility from 'visibilityjs'; import InitializingScreen from './InitializingScreen'; import StartupInitializer from './StartupInitializer'; +import LoginPageConnected from '../containers/LoginPageContainer'; /** * Use the Patternfly RCUE productized css styles if set by the environment @@ -83,26 +85,32 @@ type AppState = { }; class App extends React.Component<{}, AppState> { - private navigator = withRouter(NavigationContainer); + private protectedArea: React.ReactNode; constructor(props: {}) { super(props); this.state = { isInitialized: false }; - loadRcueCssIfNeeded(); + + const Navigator = withRouter(NavigationContainer); + this.protectedArea = ( + + + + ); } render() { - const Navigator = this.navigator; return ( } persistor={persistor}> {this.state.isInitialized ? ( - - - + } + protectedAreaComponent={this.protectedArea} + /> ) : ( )} diff --git a/src/app/AppConfigs.ts b/src/app/AppConfigs.ts deleted file mode 100644 index 601d04afbf..0000000000 --- a/src/app/AppConfigs.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { AuthConfig } from '../types/Auth'; - -interface Configs { - authenticationConfig?: AuthConfig; -} - -const AppConfigs: Configs = {}; - -export default AppConfigs; diff --git a/src/app/AuthenticationController.tsx b/src/app/AuthenticationController.tsx new file mode 100644 index 0000000000..d9919394af --- /dev/null +++ b/src/app/AuthenticationController.tsx @@ -0,0 +1,151 @@ +import * as React from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import { GrafanaInfo, KialiAppState, LoginStatus } from '../store/Store'; +import * as API from '../services/Api'; +import { HelpDropdownActions } from '../actions/HelpDropdownActions'; +import { JaegerActions } from '../actions/JaegerActions'; +import { MessageCenterActions } from '../actions/MessageCenterActions'; +import { MessageType } from '../types/MessageCenter'; +import { KialiDispatch } from '../types/Redux'; +import { ServerStatus } from '../types/ServerStatus'; +import { GrafanaActions } from '../actions/GrafanaActions'; +import InitializingScreen from './InitializingScreen'; +import { isKioskMode } from '../utils/SearchParamUtils'; +import * as MessageCenter from '../utils/MessageCenter'; +import { setServerConfig } from '../config/serverConfig'; + +interface AuthenticationControllerReduxProps { + authenticated: boolean; + setGrafanaInfo: (grafanaInfo: GrafanaInfo) => void; + setServerStatus: (serverStatus: ServerStatus) => void; +} + +type AuthenticationControllerProps = AuthenticationControllerReduxProps & { + protectedAreaComponent: React.ReactNode; + publicAreaComponent: React.ReactNode; +}; + +interface AuthenticationControllerState { + stage: 'login' | 'post-login' | 'logged-in'; + isPostLoginError: boolean; +} + +class AuthenticationController extends React.Component { + static readonly PostLoginErrorMsg = + 'You are logged in, but there was a problem when fetching some required server ' + + 'configurations. Please, try refreshing the page.'; + + constructor(props: AuthenticationControllerProps) { + super(props); + this.state = { + stage: this.props.authenticated ? 'post-login' : 'login', + isPostLoginError: false + }; + } + + componentDidMount(): void { + if (this.state.stage === 'post-login') { + this.doPostLoginActions(); + } + + this.setDocLayout(); + } + + componentDidUpdate( + prevProps: Readonly, + prevState: Readonly + ): void { + if (!prevProps.authenticated && this.props.authenticated) { + this.setState({ stage: 'post-login' }); + this.doPostLoginActions(); + } else if (prevProps.authenticated && !this.props.authenticated) { + this.setState({ stage: 'login' }); + } + + this.setDocLayout(); + } + + render() { + if (this.state.stage === 'logged-in') { + return this.props.protectedAreaComponent; + } else if (this.state.stage === 'post-login') { + return !this.state.isPostLoginError ? ( + + ) : ( + + ); + } else { + return this.props.publicAreaComponent; + } + } + + private doPostLoginActions = async () => { + try { + const getStatusPromise = API.getStatus() + .then(response => this.props.setServerStatus(response.data)) + .catch(error => { + MessageCenter.add(API.getErrorMsg('Error fetching status.', error), 'default', MessageType.WARNING); + }); + const getGrafanaInfoPromise = API.getGrafanaInfo() + .then(response => this.props.setGrafanaInfo(response.data)) + .catch(error => { + MessageCenter.add(API.getErrorMsg('Error fetching Grafana Info.', error), 'default', MessageType.WARNING); + }); + + const configs = await Promise.all([API.getServerConfig(), getStatusPromise, getGrafanaInfoPromise]); + setServerConfig(configs[0].data); + + this.setState({ stage: 'logged-in' }); + } catch (err) { + console.error('Error on post-login actions.', err); + this.setState({ isPostLoginError: true }); + } + }; + + private setDocLayout = () => { + if (document.documentElement) { + document.documentElement.className = this.state.stage === 'logged-in' ? 'layout-pf layout-pf-fixed' : 'login-pf'; + if (isKioskMode()) { + document.documentElement.className += ' kiosk'; + } + } + }; +} + +const processServerStatus = (dispatch: KialiDispatch, serverStatus: ServerStatus) => { + dispatch( + HelpDropdownActions.statusRefresh(serverStatus.status, serverStatus.externalServices, serverStatus.warningMessages) + ); + + // Get the jaeger URL + const hasJaeger = serverStatus.externalServices.filter(item => item['name'] === 'Jaeger'); + if (hasJaeger.length === 1 && hasJaeger[0].url) { + dispatch(JaegerActions.setUrl(hasJaeger[0].url)); + // If same protocol enable integration + if (hasJaeger[0].url.startsWith(window.location.protocol)) { + dispatch(JaegerActions.setEnableIntegration(true)); + } + } + + serverStatus.warningMessages.forEach(wMsg => { + dispatch(MessageCenterActions.addMessage(wMsg, 'systemErrors', MessageType.WARNING)); + }); +}; + +const mapStateToProps = (state: KialiAppState) => ({ + authenticated: state.authentication.status === LoginStatus.loggedIn +}); + +const mapDispatchToProps = (dispatch: KialiDispatch) => { + return { + setGrafanaInfo: bindActionCreators(GrafanaActions.setinfo, dispatch), + setServerStatus: (serverStatus: ServerStatus) => processServerStatus(dispatch, serverStatus) + }; +}; + +const AuthenticationControllerContainer = connect( + mapStateToProps, + mapDispatchToProps +)(AuthenticationController); +export default AuthenticationControllerContainer; diff --git a/src/app/History.ts b/src/app/History.ts index d145c64422..ef1b68bb7c 100644 --- a/src/app/History.ts +++ b/src/app/History.ts @@ -1,5 +1,6 @@ import { createBrowserHistory } from 'history'; import createMemoryHistory from 'history/createMemoryHistory'; +import { toValidDuration } from '../config/serverConfig'; const webRoot = (window as any).WEB_ROOT ? (window as any).WEB_ROOT : undefined; const baseName = webRoot && webRoot !== '/' ? webRoot + '/console' : '/console'; @@ -7,7 +8,7 @@ const history = process.env.TEST_RUNNER ? createMemoryHistory() : createBrowserH export default history; -export enum URLParams { +export enum URLParam { AGGREGATOR = 'aggregator', BY_LABELS = 'bylbl', DIRECTION = 'direction', @@ -28,7 +29,7 @@ export enum URLParams { } export interface URLParamValue { - name: URLParams; + name: URLParam; value: any; } @@ -38,18 +39,31 @@ export enum ParamAction { } export namespace HistoryManager { - export const setParam = (name: URLParams, value: string) => { + export const setParam = (name: URLParam, value: string) => { const urlParams = new URLSearchParams(history.location.search); urlParams.set(name, value); history.replace(history.location.pathname + '?' + urlParams.toString()); }; - export const getParam = (name: URLParams): string | null => { - const urlParams = new URLSearchParams(history.location.search); - return urlParams.get(name); + export const getParam = (name: URLParam, urlParams?: URLSearchParams): string | undefined => { + if (!urlParams) { + urlParams = new URLSearchParams(history.location.search); + } + const p = urlParams.get(name); + return p !== null ? p : undefined; }; - export const deleteParam = (name: URLParams, historyReplace?: boolean) => { + export const getNumericParam = (name: URLParam, urlParams?: URLSearchParams): number | undefined => { + const p = getParam(name, urlParams); + return p !== undefined ? Number(p) : undefined; + }; + + export const getBooleanParam = (name: URLParam, urlParams?: URLSearchParams): boolean | undefined => { + const p = getParam(name, urlParams); + return p !== undefined ? p === 'true' : undefined; + }; + + export const deleteParam = (name: URLParam, historyReplace?: boolean) => { const urlParams = new URLSearchParams(history.location.search); urlParams.delete(name); if (historyReplace) { @@ -82,4 +96,12 @@ export namespace HistoryManager { history.push(history.location.pathname + '?' + urlParams.toString()); } }; + + export const getDuration = (urlParams?: URLSearchParams): number | undefined => { + const duration = getParam(URLParam.DURATION, urlParams); + if (duration) { + return toValidDuration(Number(duration)); + } + return undefined; + }; } diff --git a/src/app/StartupInitializer.tsx b/src/app/StartupInitializer.tsx index e9b9159a95..91041f7968 100644 --- a/src/app/StartupInitializer.tsx +++ b/src/app/StartupInitializer.tsx @@ -5,7 +5,7 @@ import * as API from '../services/Api'; import { LoginSession } from '../store/Store'; import { KialiDispatch } from '../types/Redux'; import InitializingScreen from './InitializingScreen'; -import AppConfigs from './AppConfigs'; +import authenticationConfig from '../config/authenticationConfig'; interface InitializerComponentProps { setInitialAuthentication: (session: LoginSession) => void; @@ -29,10 +29,8 @@ class InitializerComponent extends React.Component { try { const authConfig = await API.getAuthInfo(); - AppConfigs.authenticationConfig = { - authorizationEndpoint: authConfig.data.authorizationEndpoint, - strategy: authConfig.data.strategy - }; + authenticationConfig.authorizationEndpoint = authConfig.data.authorizationEndpoint; + authenticationConfig.strategy = authConfig.data.strategy; if (authConfig.data.sessionInfo.expiresOn && authConfig.data.sessionInfo.username) { this.props.setInitialAuthentication({ diff --git a/src/components/CytoscapeGraph/CytoscapeToolbar.tsx b/src/components/CytoscapeGraph/CytoscapeToolbar.tsx index 5e6ab95765..4e3589b97b 100644 --- a/src/components/CytoscapeGraph/CytoscapeToolbar.tsx +++ b/src/components/CytoscapeGraph/CytoscapeToolbar.tsx @@ -14,8 +14,7 @@ import { CoseGraph } from './graphs/CoseGraph'; import { DagreGraph } from './graphs/DagreGraph'; import { KialiAppAction } from '../../actions/KialiAppAction'; import { GraphActions } from '../../actions/GraphActions'; -import { HistoryManager, URLParams } from '../../app/History'; -import { ListPagesHelper } from '../ListPage/ListPagesHelper'; +import { HistoryManager, URLParam } from '../../app/History'; import * as LayoutDictionary from './graphs/LayoutDictionary'; import { GraphFilterActions } from '../../actions/GraphFilterActions'; @@ -46,19 +45,19 @@ export class CytoscapeToolbar extends React.PureComponent constructor(props: CytoscapeToolbarProps) { super(props); // Let URL override current redux state at construction time. Update URL with unset params. - const urlLayout = ListPagesHelper.getSingleQueryParam(URLParams.GRAPH_LAYOUT); + const urlLayout = HistoryManager.getParam(URLParam.GRAPH_LAYOUT); if (urlLayout) { if (urlLayout !== props.layout.name) { props.setLayout(LayoutDictionary.getLayoutByName(urlLayout)); } } else { - HistoryManager.setParam(URLParams.GRAPH_LAYOUT, props.layout.name); + HistoryManager.setParam(URLParam.GRAPH_LAYOUT, props.layout.name); } } componentDidUpdate() { // ensure redux state and URL are aligned - HistoryManager.setParam(URLParams.GRAPH_LAYOUT, this.props.layout.name); + HistoryManager.setParam(URLParam.GRAPH_LAYOUT, this.props.layout.name); } render() { diff --git a/src/components/GraphFilter/GraphFilter.tsx b/src/components/GraphFilter/GraphFilter.tsx index 073d213248..0daa4b644a 100644 --- a/src/components/GraphFilter/GraphFilter.tsx +++ b/src/components/GraphFilter/GraphFilter.tsx @@ -16,8 +16,7 @@ import { EdgeLabelMode } from '../../types/GraphFilter'; import GraphFindContainer from './GraphFind'; import GraphRefreshContainer from './GraphRefresh'; import GraphSettingsContainer from './GraphSettings'; -import { HistoryManager, URLParams } from '../../app/History'; -import { ListPagesHelper } from '../../components/ListPage/ListPagesHelper'; +import history, { HistoryManager, URLParam } from '../../app/History'; import { ToolbarDropdown } from '../ToolbarDropdown/ToolbarDropdown'; import Namespace, { namespacesToString, namespacesFromString } from '../../types/Namespace'; import { NamespaceActions } from '../../actions/NamespaceAction'; @@ -71,32 +70,33 @@ export class GraphFilter extends React.PureComponent { constructor(props: GraphFilterProps) { super(props); // Let URL override current redux state at construction time. Update URL with unset params. - const urlEdgeLabelMode = ListPagesHelper.getSingleQueryParam(URLParams.GRAPH_EDGES) as EdgeLabelMode; + const urlParams = new URLSearchParams(history.location.search); + const urlEdgeLabelMode = HistoryManager.getParam(URLParam.GRAPH_EDGES, urlParams) as EdgeLabelMode; if (urlEdgeLabelMode) { if (urlEdgeLabelMode !== props.edgeLabelMode) { props.setEdgeLabelMode(urlEdgeLabelMode); } } else { - HistoryManager.setParam(URLParams.GRAPH_EDGES, String(this.props.edgeLabelMode)); + HistoryManager.setParam(URLParam.GRAPH_EDGES, String(this.props.edgeLabelMode)); } - const urlGraphType = ListPagesHelper.getSingleQueryParam(URLParams.GRAPH_TYPE) as GraphType; + const urlGraphType = HistoryManager.getParam(URLParam.GRAPH_TYPE, urlParams) as GraphType; if (urlGraphType) { if (urlGraphType !== props.graphType) { props.setGraphType(urlGraphType); } } else { - HistoryManager.setParam(URLParams.GRAPH_TYPE, String(this.props.graphType)); + HistoryManager.setParam(URLParam.GRAPH_TYPE, String(this.props.graphType)); } - const urlNamespaces = ListPagesHelper.getSingleQueryParam(URLParams.NAMESPACES); + const urlNamespaces = HistoryManager.getParam(URLParam.NAMESPACES, urlParams); if (urlNamespaces) { if (urlNamespaces !== namespacesToString(props.activeNamespaces)) { props.setActiveNamespaces(namespacesFromString(urlNamespaces)); } } else { const activeNamespacesString = namespacesToString(props.activeNamespaces); - HistoryManager.setParam(URLParams.NAMESPACES, activeNamespacesString); + HistoryManager.setParam(URLParam.NAMESPACES, activeNamespacesString); } } @@ -104,12 +104,12 @@ export class GraphFilter extends React.PureComponent { // ensure redux state and URL are aligned const activeNamespacesString = namespacesToString(this.props.activeNamespaces); if (this.props.activeNamespaces.length === 0) { - HistoryManager.deleteParam(URLParams.NAMESPACES, true); + HistoryManager.deleteParam(URLParam.NAMESPACES, true); } else { - HistoryManager.setParam(URLParams.NAMESPACES, activeNamespacesString); + HistoryManager.setParam(URLParam.NAMESPACES, activeNamespacesString); } - HistoryManager.setParam(URLParams.GRAPH_EDGES, String(this.props.edgeLabelMode)); - HistoryManager.setParam(URLParams.GRAPH_TYPE, String(this.props.graphType)); + HistoryManager.setParam(URLParam.GRAPH_EDGES, String(this.props.edgeLabelMode)); + HistoryManager.setParam(URLParam.GRAPH_TYPE, String(this.props.graphType)); } handleRefresh = () => { diff --git a/src/components/GraphFilter/GraphRefresh.tsx b/src/components/GraphFilter/GraphRefresh.tsx index d7e7f19f10..3a5afe3011 100644 --- a/src/components/GraphFilter/GraphRefresh.tsx +++ b/src/components/GraphFilter/GraphRefresh.tsx @@ -5,18 +5,17 @@ import { connect } from 'react-redux'; import { ThunkDispatch } from 'redux-thunk'; import { bindActionCreators } from 'redux'; -import { KialiAppState, ServerConfig } from '../../store/Store'; -import { durationSelector, refreshIntervalSelector, serverConfigSelector } from '../../store/Selectors'; +import { KialiAppState } from '../../store/Store'; +import { durationSelector, refreshIntervalSelector } from '../../store/Selectors'; import { KialiAppAction } from '../../actions/KialiAppAction'; import { UserSettingsActions } from '../../actions/UserSettingsActions'; import { DurationInSeconds, PollIntervalInMs } from '../../types/Common'; import { config } from '../../config/config'; -import { HistoryManager, URLParams } from '../../app/History'; -import { ListPagesHelper } from '../../components/ListPage/ListPagesHelper'; +import { HistoryManager, URLParam } from '../../app/History'; import ToolbarDropdown from '../ToolbarDropdown/ToolbarDropdown'; -import { getValidDurations, getValidDuration } from '../../config/serverConfig'; +import { serverConfig } from '../../config/serverConfig'; // // GraphRefresh actually handles the Duration dropdown, the RefreshInterval dropdown and the Refresh button. @@ -25,7 +24,6 @@ import { getValidDurations, getValidDuration } from '../../config/serverConfig'; type ReduxProps = { duration: DurationInSeconds; refreshInterval: PollIntervalInMs; - serverConfig: ServerConfig; setDuration: (duration: DurationInSeconds) => void; setRefreshInterval: (refreshInterval: PollIntervalInMs) => void; @@ -38,7 +36,6 @@ type GraphRefreshProps = ReduxProps & { }; export class GraphRefresh extends React.PureComponent { - static readonly DURATION_LIST = config.toolbar.intervalDuration; static readonly POLL_INTERVAL_LIST = config.toolbar.pollInterval; static readonly durationLabelStyle = style({ @@ -54,38 +51,34 @@ export class GraphRefresh extends React.PureComponent { super(props); // Let URL override current redux state at construction time - const urlDuration = ListPagesHelper.getSingleIntQueryParam(URLParams.DURATION); - const urlPollInterval = ListPagesHelper.getSingleIntQueryParam(URLParams.POLL_INTERVAL); + const urlDuration = HistoryManager.getDuration(); + const urlPollInterval = HistoryManager.getNumericParam(URLParam.POLL_INTERVAL); if (urlDuration !== undefined && urlDuration !== props.duration) { props.setDuration(urlDuration); } if (urlPollInterval !== undefined && urlPollInterval !== props.refreshInterval) { props.setRefreshInterval(urlPollInterval); } - HistoryManager.setParam(URLParams.DURATION, String(this.props.duration)); - HistoryManager.setParam(URLParams.POLL_INTERVAL, String(this.props.refreshInterval)); + HistoryManager.setParam(URLParam.DURATION, String(this.props.duration)); + HistoryManager.setParam(URLParam.POLL_INTERVAL, String(this.props.refreshInterval)); } componentDidUpdate() { // ensure redux state and URL are aligned - HistoryManager.setParam(URLParams.DURATION, String(this.props.duration)); - HistoryManager.setParam(URLParams.POLL_INTERVAL, String(this.props.refreshInterval)); + HistoryManager.setParam(URLParam.DURATION, String(this.props.duration)); + HistoryManager.setParam(URLParam.POLL_INTERVAL, String(this.props.refreshInterval)); } render() { - const retention = this.props.serverConfig.prometheus.storageTsdbRetention; - const validDurations = getValidDurations(GraphRefresh.DURATION_LIST, retention); - const validDuration = getValidDuration(validDurations, this.props.duration); - return ( <> this.props.setDuration(Number(key))} + value={this.props.duration} + label={String(serverConfig.durations[this.props.duration])} + options={serverConfig.durations} tooltip={'Time range for graph data'} /> { ); } - - private handleDurationChange = (duration: string) => { - this.props.setDuration(Number(duration)); - }; } const mapStateToProps = (state: KialiAppState) => ({ duration: durationSelector(state), - refreshInterval: refreshIntervalSelector(state), - serverConfig: serverConfigSelector(state) + refreshInterval: refreshIntervalSelector(state) }); const mapDispatchToProps = (dispatch: ThunkDispatch) => { diff --git a/src/components/GraphFilter/GraphSettings.tsx b/src/components/GraphFilter/GraphSettings.tsx index f29d16de1e..5bf3021189 100644 --- a/src/components/GraphFilter/GraphSettings.tsx +++ b/src/components/GraphFilter/GraphSettings.tsx @@ -4,8 +4,7 @@ import * as React from 'react'; import { connect } from 'react-redux'; import { ThunkDispatch } from 'redux-thunk'; import { bindActionCreators } from 'redux'; -import { HistoryManager, URLParams } from '../../app/History'; -import { ListPagesHelper } from '../../components/ListPage/ListPagesHelper'; +import { HistoryManager, URLParam } from '../../app/History'; import { GraphFilterState, KialiAppState } from '../../store/Store'; import { KialiAppAction } from '../../actions/KialiAppAction'; import { GraphFilterActions } from '../../actions/GraphFilterActions'; @@ -44,19 +43,19 @@ class GraphSettings extends React.PureComponent { super(props); // Let URL override current redux state at construction time. Update URL with unset params. - const urlInjectServiceNodes = ListPagesHelper.getSingleBooleanQueryParam(URLParams.GRAPH_SERVICE_NODES); + const urlInjectServiceNodes = HistoryManager.getBooleanParam(URLParam.GRAPH_SERVICE_NODES); if (urlInjectServiceNodes !== undefined) { if (urlInjectServiceNodes !== props.showServiceNodes) { props.toggleServiceNodes(); } } else { - HistoryManager.setParam(URLParams.GRAPH_SERVICE_NODES, String(this.props.showServiceNodes)); + HistoryManager.setParam(URLParam.GRAPH_SERVICE_NODES, String(this.props.showServiceNodes)); } } componentDidUpdate(prevProps: GraphSettingsProps) { // ensure redux state and URL are aligned - HistoryManager.setParam(URLParams.GRAPH_SERVICE_NODES, String(this.props.showServiceNodes)); + HistoryManager.setParam(URLParam.GRAPH_SERVICE_NODES, String(this.props.showServiceNodes)); } render() { diff --git a/src/components/IstioWizards/IstioWizard.tsx b/src/components/IstioWizards/IstioWizard.tsx index 31d120e566..e0e8e94fe8 100644 --- a/src/components/IstioWizards/IstioWizard.tsx +++ b/src/components/IstioWizards/IstioWizard.tsx @@ -147,7 +147,7 @@ class IstioWizard extends React.Component { host: this.props.serviceName, subsets: this.props.workloads.map(workload => { // Using version - const versionLabelName = serverConfig().istioLabels.versionLabelName; + const versionLabelName = serverConfig.istioLabels.versionLabelName; const versionValue = workload.labels![versionLabelName]; const labels: { [key: string]: string } = {}; labels[versionLabelName] = versionValue; diff --git a/src/components/IstioWizards/IstioWizardDropdown.tsx b/src/components/IstioWizards/IstioWizardDropdown.tsx index 1bc327b18b..3ab339f78c 100644 --- a/src/components/IstioWizards/IstioWizardDropdown.tsx +++ b/src/components/IstioWizards/IstioWizardDropdown.tsx @@ -146,8 +146,8 @@ class IstioWizardDropdown extends React.Component { namespace={this.props.namespace} serviceName={this.props.serviceName} workloads={this.props.workloads.filter(workload => { - const appLabelName = serverConfig().istioLabels.versionLabelName; - const versionLabelName = serverConfig().istioLabels.versionLabelName; + const appLabelName = serverConfig.istioLabels.versionLabelName; + const versionLabelName = serverConfig.istioLabels.versionLabelName; return workload.labels && workload.labels[appLabelName] && workload.labels[versionLabelName]; })} onClose={this.onClose} diff --git a/src/components/JaegerToolbar/LookBack.tsx b/src/components/JaegerToolbar/LookBack.tsx index 77053bb8db..b9f6b99871 100644 --- a/src/components/JaegerToolbar/LookBack.tsx +++ b/src/components/JaegerToolbar/LookBack.tsx @@ -6,7 +6,7 @@ import ToolbarDropdown from '../../components/ToolbarDropdown/ToolbarDropdown'; import { JaegerActions } from '../../actions/JaegerActions'; import { ThunkDispatch } from 'redux-thunk'; import { KialiAppAction } from '../../actions/KialiAppAction'; -import { config } from '../../config'; +import { serverConfig } from '../../config/serverConfig'; interface LookBackProps { fetching: boolean; @@ -16,7 +16,7 @@ interface LookBackProps { } export class LookBack extends React.PureComponent { - lookBackOptions = { ...config.toolbar.intervalDuration, ...{ 0: 'Custom Time Range' } }; + lookBackOptions = { ...serverConfig.durations, ...{ 0: 'Custom Time Range' } }; constructor(props: LookBackProps) { super(props); diff --git a/src/components/JaegerToolbar/__tests__/LookBack.test.tsx b/src/components/JaegerToolbar/__tests__/LookBack.test.tsx index f3c1048c2f..889bcd90ed 100644 --- a/src/components/JaegerToolbar/__tests__/LookBack.test.tsx +++ b/src/components/JaegerToolbar/__tests__/LookBack.test.tsx @@ -3,9 +3,9 @@ import { shallow } from 'enzyme'; import { LookBack } from '../LookBack'; import { Form, FormGroup } from 'patternfly-react'; import ToolbarDropdown from '../../../components/ToolbarDropdown/ToolbarDropdown'; -import { config } from '../../../config'; +import { serverConfig } from '../../../config/serverConfig'; -const lookBackOptions = { ...config.toolbar.intervalDuration, ...{ 0: 'Custom Time Range' } }; +const lookBackOptions = { ...serverConfig.durations, ...{ 0: 'Custom Time Range' } }; describe('LookBack', () => { let wrapper, onChangeCustom, setLookback; diff --git a/src/components/JaegerToolbar/__tests__/__snapshots__/LookBack.test.tsx.snap b/src/components/JaegerToolbar/__tests__/__snapshots__/LookBack.test.tsx.snap index d547e74ebe..4cf790c00c 100644 --- a/src/components/JaegerToolbar/__tests__/__snapshots__/LookBack.test.tsx.snap +++ b/src/components/JaegerToolbar/__tests__/__snapshots__/LookBack.test.tsx.snap @@ -61,14 +61,10 @@ ShallowWrapper { "10800": "Last 3h", "1800": "Last 30m", "21600": "Last 6h", - "2592000": "Last 30d", "300": "Last 5m", "3600": "Last 1h", - "43200": "Last 12h", "60": "Last 1m", "600": "Last 10m", - "604800": "Last 7d", - "86400": "Last 1d", } } useName={false} @@ -118,14 +114,10 @@ ShallowWrapper { "10800": "Last 3h", "1800": "Last 30m", "21600": "Last 6h", - "2592000": "Last 30d", "300": "Last 5m", "3600": "Last 1h", - "43200": "Last 12h", "60": "Last 1m", "600": "Last 10m", - "604800": "Last 7d", - "86400": "Last 1d", }, "useName": false, "value": 3600, @@ -175,14 +167,10 @@ ShallowWrapper { "10800": "Last 3h", "1800": "Last 30m", "21600": "Last 6h", - "2592000": "Last 30d", "300": "Last 5m", "3600": "Last 1h", - "43200": "Last 12h", "60": "Last 1m", "600": "Last 10m", - "604800": "Last 7d", - "86400": "Last 1d", } } useName={false} @@ -232,14 +220,10 @@ ShallowWrapper { "10800": "Last 3h", "1800": "Last 30m", "21600": "Last 6h", - "2592000": "Last 30d", "300": "Last 5m", "3600": "Last 1h", - "43200": "Last 12h", "60": "Last 1m", "600": "Last 10m", - "604800": "Last 7d", - "86400": "Last 1d", }, "useName": false, "value": 3600, diff --git a/src/components/ListPage/ListComponent.tsx b/src/components/ListPage/ListComponent.tsx index 8336615e66..3b045dab22 100644 --- a/src/components/ListPage/ListComponent.tsx +++ b/src/components/ListPage/ListComponent.tsx @@ -4,7 +4,7 @@ import { ListPagesHelper } from './ListPagesHelper'; import { SortField } from '../../types/SortFilters'; import { Pagination } from '../../types/Pagination'; import * as API from '../../services/Api'; -import { HistoryManager, URLParams } from '../../app/History'; +import { HistoryManager, URLParam } from '../../app/History'; export namespace ListComponent { export interface Props { @@ -33,7 +33,7 @@ export namespace ListComponent { onFilterChange = () => { // Resetting pagination when filters change - HistoryManager.deleteParam(URLParams.PAGE); + HistoryManager.deleteParam(URLParam.PAGE); this.updateListItems(true); }; @@ -58,7 +58,7 @@ export namespace ListComponent { } }; }); - HistoryManager.setParam(URLParams.PAGE, String(page)); + HistoryManager.setParam(URLParam.PAGE, String(page)); }; perPageSelect = (perPage: number) => { @@ -73,8 +73,8 @@ export namespace ListComponent { }; }); HistoryManager.setParams([ - { name: URLParams.PAGE, value: '1' }, - { name: URLParams.PER_PAGE, value: String(perPage) } + { name: URLParam.PAGE, value: '1' }, + { name: URLParam.PER_PAGE, value: String(perPage) } ]); }; @@ -84,7 +84,7 @@ export namespace ListComponent { currentSortField: sortField, listItems: sorted }); - HistoryManager.setParam(URLParams.SORT, sortField.param); + HistoryManager.setParam(URLParam.SORT, sortField.param); }); }; @@ -94,7 +94,7 @@ export namespace ListComponent { isSortAscending: !this.state.isSortAscending, listItems: sorted }); - HistoryManager.setParam(URLParams.DIRECTION, this.state.isSortAscending ? 'asc' : 'desc'); + HistoryManager.setParam(URLParam.DIRECTION, this.state.isSortAscending ? 'asc' : 'desc'); }); }; } diff --git a/src/components/ListPage/ListPagesHelper.ts b/src/components/ListPage/ListPagesHelper.ts index d5fc30c956..57cd653cd1 100644 --- a/src/components/ListPage/ListPagesHelper.ts +++ b/src/components/ListPage/ListPagesHelper.ts @@ -1,4 +1,4 @@ -import history, { URLParams } from '../../app/History'; +import history, { URLParam, HistoryManager } from '../../app/History'; import { config } from '../../config'; import { ActiveFilter, FilterType } from '../../types/Filters'; import { Pagination } from '../../types/Pagination'; @@ -14,32 +14,6 @@ export namespace ListPagesHelper { MessageCenter.add(error); }; - export const getQueryParam = (queryName: string): string[] | undefined => { - const urlParams = new URLSearchParams(history.location.search); - const values = urlParams.getAll(queryName); - - if (values.length === 0) { - return undefined; - } - - return values; - }; - - export const getSingleQueryParam = (queryName: string): string | undefined => { - const p = getQueryParam(queryName); - return p === undefined ? undefined : p[0]; - }; - - export const getSingleBooleanQueryParam = (queryName: string): boolean | undefined => { - const p = getQueryParam(queryName); - return p === undefined ? undefined : p[0] === 'true'; - }; - - export const getSingleIntQueryParam = (queryName: string): number | undefined => { - const p = getQueryParam(queryName); - return p === undefined ? undefined : Number(p[0]); - }; - export const getFiltersFromURL = (filterTypes: FilterType[]): ActiveFilter[] => { const urlParams = new URLSearchParams(history.location.search); const activeFilters: ActiveFilter[] = []; @@ -69,7 +43,7 @@ export namespace ListPagesHelper { urlParams.append(filterType.id, activeFilter.value); }); // Resetting pagination when filters change - urlParams.delete(URLParams.PAGE); + urlParams.delete(URLParam.PAGE); history.push(history.location.pathname + '?' + urlParams.toString()); return cleanFilters; }; @@ -106,27 +80,24 @@ export namespace ListPagesHelper { }; export const currentPagination = (): Pagination => { + const urlParams = new URLSearchParams(history.location.search); return { - page: getSingleIntQueryParam(URLParams.PAGE) || 1, - perPage: getSingleIntQueryParam(URLParams.PER_PAGE) || perPageOptions[1], + page: HistoryManager.getNumericParam(URLParam.PAGE, urlParams) || 1, + perPage: HistoryManager.getNumericParam(URLParam.PER_PAGE, urlParams) || perPageOptions[1], perPageOptions: perPageOptions }; }; export const isCurrentSortAscending = (): boolean => { - return (getSingleQueryParam(URLParams.DIRECTION) || 'asc') === 'asc'; - }; - - export const currentSortFieldId = (): string | undefined => { - return getSingleQueryParam(URLParams.SORT); + return (HistoryManager.getParam(URLParam.DIRECTION) || 'asc') === 'asc'; }; export const currentDuration = (): number => { - return getSingleIntQueryParam(URLParams.DURATION) || defaultDuration; + return HistoryManager.getDuration() || defaultDuration; }; export const currentPollInterval = (): number => { - const pi = getSingleIntQueryParam(URLParams.POLL_INTERVAL); + const pi = HistoryManager.getNumericParam(URLParam.POLL_INTERVAL); if (pi === undefined) { return defaultPollInterval; } @@ -134,7 +105,7 @@ export namespace ListPagesHelper { }; export const currentSortField = (sortFields: SortField[]): SortField => { - const queriedSortedField = getQueryParam(URLParams.SORT) || [sortFields[0].param]; + const queriedSortedField = HistoryManager.getParam(URLParam.SORT) || [sortFields[0].param]; return ( sortFields.find(sortField => { return sortField.param === queriedSortedField[0]; diff --git a/src/components/Metrics/CustomMetrics.tsx b/src/components/Metrics/CustomMetrics.tsx index 8663ee2de8..dde4cca309 100644 --- a/src/components/Metrics/CustomMetrics.tsx +++ b/src/components/Metrics/CustomMetrics.tsx @@ -15,7 +15,7 @@ import { Dashboard } from './Dashboard'; import MetricsHelper from './Helper'; import { MetricsSettingsDropdown, MetricsSettings } from '../MetricsOptions/MetricsSettings'; import MetricsRawAggregation from '../MetricsOptions/MetricsRawAggregation'; -import MetricsDurationContainer from '../MetricsOptions/MetricsDuration'; +import MetricsDuration from '../MetricsOptions/MetricsDuration'; type MetricsState = { dashboard?: M.MonitoringDashboard; @@ -115,12 +115,14 @@ class CustomMetrics extends React.Component { } renderOptionsBar() { - const hasHistograms = this.state.dashboard !== undefined && this.state.dashboard.charts.some(chart => { - if (chart.histogram) { - return Object.keys(chart.histogram).length > 0; - } - return false; - }); + const hasHistograms = + this.state.dashboard !== undefined && + this.state.dashboard.charts.some(chart => { + if (chart.histogram) { + return Object.keys(chart.histogram).length > 0; + } + return false; + }); return ( @@ -135,7 +137,7 @@ class CustomMetrics extends React.Component { - + diff --git a/src/components/Metrics/Helper.ts b/src/components/Metrics/Helper.ts index 317b05a01e..2b00ce3be4 100644 --- a/src/components/Metrics/Helper.ts +++ b/src/components/Metrics/Helper.ts @@ -11,7 +11,7 @@ import { } from '../../types/Metrics'; import { BaseMetricsOptions } from '../../types/MetricsOptions'; import { MetricsSettingsDropdown, MetricsSettings } from '../MetricsOptions/MetricsSettings'; -import { MetricsDuration } from '../MetricsOptions/MetricsDuration'; +import MetricsDuration from '../MetricsOptions/MetricsDuration'; import { DurationInSeconds } from '../../types/Common'; import { computePrometheusRateParams } from '../../services/Prometheus'; diff --git a/src/components/Metrics/IstioMetrics.tsx b/src/components/Metrics/IstioMetrics.tsx index b4e6ace18f..5c7201b70e 100644 --- a/src/components/Metrics/IstioMetrics.tsx +++ b/src/components/Metrics/IstioMetrics.tsx @@ -14,7 +14,7 @@ import { Dashboard } from './Dashboard'; import MetricsHelper from './Helper'; import { MetricsSettings, MetricsSettingsDropdown } from '../MetricsOptions/MetricsSettings'; import MetricsReporter from '../MetricsOptions/MetricsReporter'; -import MetricsDurationContainer from '../MetricsOptions/MetricsDuration'; +import MetricsDuration from '../MetricsOptions/MetricsDuration'; type MetricsState = { dashboard?: M.MonitoringDashboard; @@ -176,7 +176,7 @@ class IstioMetrics extends React.Component { )} - + diff --git a/src/components/Metrics/__tests__/CustomMetrics.test.tsx b/src/components/Metrics/__tests__/CustomMetrics.test.tsx index 7301f2a5ec..75ab3317b4 100644 --- a/src/components/Metrics/__tests__/CustomMetrics.test.tsx +++ b/src/components/Metrics/__tests__/CustomMetrics.test.tsx @@ -7,8 +7,6 @@ import CustomMetrics from '../CustomMetrics'; import * as API from '../../../services/Api'; import { MonitoringDashboard } from '../../../types/Metrics'; import { store } from '../../../store/ConfigStore'; -import { ServerConfig } from '../../../store/Store'; -import { ServerConfigActions } from '../../../actions/ServerConfigActions'; window['SVGPathElement'] = a => a; let mounted: ReactWrapper | null; @@ -34,15 +32,6 @@ const mockCustomDashboard = (dashboard: MonitoringDashboard): Promise => { return mockAPIToPromise('getCustomDashboard', dashboard); }; -const mockServerConfig = () => { - const config: ServerConfig = { - istioNamespace: 'istio-system', - istioLabels: { appLabelName: 'app', versionLabelName: 'version' }, - prometheus: { storageTsdbRetention: 31 * 24 * 60 * 60 } - }; - store.dispatch(ServerConfigActions.setServerConfig(config)); -}; - describe('Custom metrics', () => { beforeEach(() => { mounted = null; @@ -54,7 +43,6 @@ describe('Custom metrics', () => { }); it('mounts and loads empty metrics', done => { - mockServerConfig(); mockCustomDashboard({ title: 'foo', aggregations: [], charts: [] }) .then(() => { mounted!.update(); diff --git a/src/components/Metrics/__tests__/IstioMetrics.test.tsx b/src/components/Metrics/__tests__/IstioMetrics.test.tsx index 7259c93a44..a19e16fc2a 100644 --- a/src/components/Metrics/__tests__/IstioMetrics.test.tsx +++ b/src/components/Metrics/__tests__/IstioMetrics.test.tsx @@ -7,8 +7,6 @@ import IstioMetrics from '../IstioMetrics'; import * as API from '../../../services/Api'; import { MetricsObjectTypes, MonitoringDashboard, Chart } from '../../../types/Metrics'; import { store } from '../../../store/ConfigStore'; -import { ServerConfig } from '../../../store/Store'; -import { ServerConfigActions } from '../../../actions/ServerConfigActions'; window['SVGPathElement'] = a => a; let mounted: ReactWrapper | null; @@ -42,15 +40,6 @@ const mockGrafanaInfo = (info: any): Promise => { return mockAPIToPromise('getGrafanaInfo', info); }; -const mockServerConfig = () => { - const config: ServerConfig = { - istioNamespace: 'istio-system', - istioLabels: { appLabelName: 'app', versionLabelName: 'version' }, - prometheus: { storageTsdbRetention: 31 * 24 * 60 * 60 } - }; - store.dispatch(ServerConfigActions.setServerConfig(config)); -}; - const createMetricChart = (name: string): Chart => { return { name: name, @@ -125,7 +114,6 @@ describe('Metrics for a service', () => { }); it('renders initial layout', () => { - mockServerConfig(); mockGrafanaInfo({}); const wrapper = shallow( @@ -157,7 +145,6 @@ describe('Metrics for a service', () => { it('mounts and loads empty metrics', done => { const allMocksDone = [ - mockServerConfig(), mockServiceDashboard({ title: 'foo', aggregations: [], charts: [] }) .then(() => { mounted!.update(); @@ -196,7 +183,6 @@ describe('Metrics for a service', () => { it('mounts and loads full metrics', done => { const allMocksDone = [ - mockServerConfig(), mockServiceDashboard({ title: 'foo', aggregations: [], @@ -281,7 +267,6 @@ describe('Inbound Metrics for a workload', () => { it('mounts and loads empty metrics', done => { const allMocksDone = [ - mockServerConfig(), mockWorkloadDashboard({ title: 'foo', aggregations: [], charts: [] }) .then(() => { mounted!.update(); @@ -320,7 +305,6 @@ describe('Inbound Metrics for a workload', () => { it('mounts and loads full metrics', done => { const allMocksDone = [ - mockServerConfig(), mockWorkloadDashboard({ title: 'foo', aggregations: [], diff --git a/src/components/MetricsOptions/MetricsDuration.tsx b/src/components/MetricsOptions/MetricsDuration.tsx index ffa1a0ca0f..4bf96f2a4f 100644 --- a/src/components/MetricsOptions/MetricsDuration.tsx +++ b/src/components/MetricsOptions/MetricsDuration.tsx @@ -1,38 +1,28 @@ import * as React from 'react'; -import history, { URLParams, HistoryManager } from '../../app/History'; -import { config } from '../../config'; +import { URLParam, HistoryManager } from '../../app/History'; import { DurationInSeconds } from '../../types/Common'; import { ToolbarDropdown } from '../ToolbarDropdown/ToolbarDropdown'; -import { KialiAppState, ServerConfig } from '../../store/Store'; -import { serverConfigSelector } from '../../store/Selectors'; -import { connect } from 'react-redux'; -import { getValidDurations, getValidDuration } from '../../config/serverConfig'; +import { serverConfig } from '../../config/serverConfig'; -type ReduxProps = { - serverConfig: ServerConfig; -}; - -type Props = ReduxProps & { +type Props = { onChanged: (duration: DurationInSeconds) => void; }; -export class MetricsDuration extends React.Component { - static Durations = config.toolbar.intervalDuration; +export default class MetricsDuration extends React.Component { // Default to 10 minutes. Showing timeseries to only 1 minute doesn't make so much sense. static DefaultDuration = 600; private duration: DurationInSeconds; static initialDuration = (): DurationInSeconds => { - const urlParams = new URLSearchParams(history.location.search); - let d = urlParams.get(URLParams.DURATION); - if (d !== null) { - sessionStorage.setItem(URLParams.DURATION, d); - return Number(d); + const urlDuration = HistoryManager.getDuration(); + if (urlDuration !== undefined) { + sessionStorage.setItem(URLParam.DURATION, String(urlDuration)); + return urlDuration; } - d = sessionStorage.getItem(URLParams.DURATION); - return d !== null ? Number(d) : MetricsDuration.DefaultDuration; + const storageDuration = sessionStorage.getItem(URLParam.DURATION); + return storageDuration !== null ? Number(storageDuration) : MetricsDuration.DefaultDuration; }; constructor(props: Props) { @@ -41,38 +31,23 @@ export class MetricsDuration extends React.Component { } onDurationChanged = (key: string) => { - sessionStorage.setItem(URLParams.DURATION, key); - HistoryManager.setParam(URLParams.DURATION, key); + sessionStorage.setItem(URLParam.DURATION, key); + HistoryManager.setParam(URLParam.DURATION, key); this.duration = Number(key); this.props.onChanged(this.duration); }; render() { - const retention = this.props.serverConfig.prometheus.storageTsdbRetention; - const validDurations = getValidDurations(MetricsDuration.Durations, retention); - const validDuration = getValidDuration(validDurations, this.duration); - return ( ); } } - -const mapStateToProps = (state: KialiAppState) => ({ - serverConfig: serverConfigSelector(state) -}); - -const MetricsDurationContainer = connect( - mapStateToProps, - null -)(MetricsDuration); - -export default MetricsDurationContainer; diff --git a/src/components/MetricsOptions/MetricsRawAggregation.tsx b/src/components/MetricsOptions/MetricsRawAggregation.tsx index 095992183d..2fc16b165c 100644 --- a/src/components/MetricsOptions/MetricsRawAggregation.tsx +++ b/src/components/MetricsOptions/MetricsRawAggregation.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import history, { URLParams, HistoryManager } from '../../app/History'; +import { URLParam, HistoryManager } from '../../app/History'; import { ToolbarDropdown } from '../ToolbarDropdown/ToolbarDropdown'; import { Aggregator } from '../../types/MetricsOptions'; @@ -21,9 +21,8 @@ export default class MetricsRawAggregation extends React.Component { private aggregator: Aggregator; static initialAggregator = (): Aggregator => { - const urlParams = new URLSearchParams(history.location.search); - const opParam = urlParams.get(URLParams.AGGREGATOR); - if (opParam != null) { + const opParam = HistoryManager.getParam(URLParam.AGGREGATOR); + if (opParam !== undefined) { return opParam as Aggregator; } return 'sum'; @@ -35,7 +34,7 @@ export default class MetricsRawAggregation extends React.Component { } onAggregatorChanged = (aggregator: string) => { - HistoryManager.setParam(URLParams.AGGREGATOR, aggregator); + HistoryManager.setParam(URLParam.AGGREGATOR, aggregator); this.aggregator = aggregator as Aggregator; this.props.onChanged(this.aggregator); }; diff --git a/src/components/MetricsOptions/MetricsReporter.tsx b/src/components/MetricsOptions/MetricsReporter.tsx index 764d9325ef..34b78ad18e 100644 --- a/src/components/MetricsOptions/MetricsReporter.tsx +++ b/src/components/MetricsOptions/MetricsReporter.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import history, { URLParams, HistoryManager } from '../../app/History'; +import { URLParam, HistoryManager } from '../../app/History'; import { ToolbarDropdown } from '../ToolbarDropdown/ToolbarDropdown'; import { Reporter, Direction } from '../../types/MetricsOptions'; @@ -18,9 +18,8 @@ export default class MetricsReporter extends React.Component { private reporter: Reporter; static initialReporter = (direction: Direction): Reporter => { - const urlParams = new URLSearchParams(history.location.search); - const reporterParam = urlParams.get(URLParams.REPORTER); - if (reporterParam != null) { + const reporterParam = HistoryManager.getParam(URLParam.REPORTER); + if (reporterParam !== undefined) { return reporterParam as Reporter; } return direction === 'inbound' ? 'destination' : 'source'; @@ -32,7 +31,7 @@ export default class MetricsReporter extends React.Component { } onReporterChanged = (reporter: string) => { - HistoryManager.setParam(URLParams.REPORTER, reporter); + HistoryManager.setParam(URLParam.REPORTER, reporter); this.reporter = reporter as Reporter; this.props.onChanged(this.reporter); }; diff --git a/src/components/MetricsOptions/MetricsSettings.tsx b/src/components/MetricsOptions/MetricsSettings.tsx index 8ad9a87be2..25152271bc 100644 --- a/src/components/MetricsOptions/MetricsSettings.tsx +++ b/src/components/MetricsOptions/MetricsSettings.tsx @@ -3,7 +3,7 @@ import { Button, Icon, OverlayTrigger, Popover } from 'patternfly-react'; import { style } from 'typestyle'; import isEqual from 'lodash/fp/isEqual'; -import history, { URLParams } from '../../app/History'; +import history, { URLParam } from '../../app/History'; import { LabelDisplayName, AllLabelsValues } from '../../types/Metrics'; export type Quantiles = '0.5' | '0.95' | '0.99' | '0.999'; @@ -37,11 +37,11 @@ export class MetricsSettingsDropdown extends React.Component { showQuantiles: ['0.5', '0.95', '0.99'], activeLabels: [] }; - const avg = urlParams.get(URLParams.SHOW_AVERAGE); + const avg = urlParams.get(URLParam.SHOW_AVERAGE); if (avg !== null) { settings.showAverage = avg === 'true'; } - const quantiles = urlParams.get(URLParams.QUANTILES); + const quantiles = urlParams.get(URLParam.QUANTILES); if (quantiles !== null) { if (quantiles.trim().length !== 0) { settings.showQuantiles = quantiles.split(' ').map(val => val.trim() as Quantiles); @@ -49,7 +49,7 @@ export class MetricsSettingsDropdown extends React.Component { settings.showQuantiles = []; } } - const byLabels = urlParams.getAll(URLParams.BY_LABELS); + const byLabels = urlParams.getAll(URLParam.BY_LABELS); if (byLabels.length !== 0) { settings.activeLabels = byLabels as LabelDisplayName[]; } @@ -73,14 +73,14 @@ export class MetricsSettingsDropdown extends React.Component { : this.settings.activeLabels.filter(g => label !== g); const urlParams = new URLSearchParams(history.location.search); - urlParams.delete(URLParams.BY_LABELS); - newLabels.forEach(lbl => urlParams.append(URLParams.BY_LABELS, lbl)); + urlParams.delete(URLParam.BY_LABELS); + newLabels.forEach(lbl => urlParams.append(URLParam.BY_LABELS, lbl)); history.replace(history.location.pathname + '?' + urlParams.toString()); }; onHistogramAverageChanged = (checked: boolean) => { const urlParams = new URLSearchParams(history.location.search); - urlParams.set(URLParams.SHOW_AVERAGE, String(checked)); + urlParams.set(URLParam.SHOW_AVERAGE, String(checked)); history.replace(history.location.pathname + '?' + urlParams.toString()); }; @@ -90,7 +90,7 @@ export class MetricsSettingsDropdown extends React.Component { : this.settings.showQuantiles.filter(q => quantile !== q); const urlParams = new URLSearchParams(history.location.search); - urlParams.set(URLParams.QUANTILES, newQuantiles.join(' ')); + urlParams.set(URLParam.QUANTILES, newQuantiles.join(' ')); history.replace(history.location.pathname + '?' + urlParams.toString()); }; diff --git a/src/components/NamespaceDropdown.tsx b/src/components/NamespaceDropdown.tsx index 4129727807..87d312557f 100644 --- a/src/components/NamespaceDropdown.tsx +++ b/src/components/NamespaceDropdown.tsx @@ -11,7 +11,7 @@ import { NamespaceActions } from '../actions/NamespaceAction'; import NamespaceThunkActions from '../actions/NamespaceThunkActions'; import Namespace from '../types/Namespace'; import { PfColors } from './Pf/PfColors'; -import { HistoryManager, URLParams } from '../app/History'; +import { HistoryManager, URLParam } from '../app/History'; const namespaceButtonColors = { backgroundColor: PfColors.White, @@ -66,21 +66,21 @@ export class NamespaceDropdown extends React.PureComponent item.name).join(',')); + HistoryManager.setParam(URLParam.NAMESPACES, this.props.activeNamespaces.map(item => item.name).join(',')); } } } syncNamespacesURLParam = () => { - const namespaces = (HistoryManager.getParam(URLParams.NAMESPACES) || '').split(',').filter(Boolean); + const namespaces = (HistoryManager.getParam(URLParam.NAMESPACES) || '').split(',').filter(Boolean); if (namespaces.length > 0 && _.difference(namespaces, this.props.activeNamespaces.map(item => item.name))) { // We must change the props of namespaces const items = namespaces.map(ns => ({ name: ns } as Namespace)); this.props.setNamespaces(items); } else if (namespaces.length === 0 && this.props.activeNamespaces.length !== 0) { - HistoryManager.setParam(URLParams.NAMESPACES, this.props.activeNamespaces.map(item => item.name).join(',')); + HistoryManager.setParam(URLParam.NAMESPACES, this.props.activeNamespaces.map(item => item.name).join(',')); } }; diff --git a/src/components/Nav/NavUtils.tsx b/src/components/Nav/NavUtils.tsx index c1a4891343..b3d9185f59 100644 --- a/src/components/Nav/NavUtils.tsx +++ b/src/components/Nav/NavUtils.tsx @@ -2,7 +2,7 @@ import { NodeType, NodeParamsType, GraphType } from '../../types/Graph'; import { Layout, EdgeLabelMode } from '../../types/GraphFilter'; import { DurationInSeconds, PollIntervalInMs } from '../../types/Common'; import Namespace from '../../types/Namespace'; -import { URLParams } from '../../app/History'; +import { URLParam } from '../../app/History'; import { isKioskMode } from '../../utils/SearchParamUtils'; export type GraphUrlParams = { @@ -17,12 +17,12 @@ export type GraphUrlParams = { }; const buildCommonQueryParams = (params: GraphUrlParams): string => { - let q = `&${URLParams.GRAPH_EDGES}=${params.edgeLabelMode}`; - q += `&${URLParams.GRAPH_LAYOUT}=${params.graphLayout.name}`; - q += `&${URLParams.GRAPH_SERVICE_NODES}=${params.showServiceNodes}`; - q += `&${URLParams.GRAPH_TYPE}=${params.graphType}`; - q += `&${URLParams.DURATION}=${params.duration}`; - q += `&${URLParams.POLL_INTERVAL}=${params.refreshInterval}`; + let q = `&${URLParam.GRAPH_EDGES}=${params.edgeLabelMode}`; + q += `&${URLParam.GRAPH_LAYOUT}=${params.graphLayout.name}`; + q += `&${URLParam.GRAPH_SERVICE_NODES}=${params.showServiceNodes}`; + q += `&${URLParam.GRAPH_TYPE}=${params.graphType}`; + q += `&${URLParam.DURATION}=${params.duration}`; + q += `&${URLParam.POLL_INTERVAL}=${params.refreshInterval}`; return q; }; @@ -30,7 +30,7 @@ export const makeNamespacesGraphUrlFromParams = (params: GraphUrlParams): string let queryParams = buildCommonQueryParams(params); if (params.activeNamespaces.length > 0) { const namespaces = params.activeNamespaces.map(namespace => namespace.name).join(','); - queryParams += `&${URLParams.NAMESPACES}=${namespaces}`; + queryParams += `&${URLParam.NAMESPACES}=${namespaces}`; } if (isKioskMode()) { queryParams += '&kiosk=true'; diff --git a/src/components/Nav/Navigation.tsx b/src/components/Nav/Navigation.tsx index 628d177d16..5f0466f216 100644 --- a/src/components/Nav/Navigation.tsx +++ b/src/components/Nav/Navigation.tsx @@ -9,19 +9,14 @@ import { MessageCenterContainer, MessageCenterTriggerContainer } from '../../con import HelpDropdown from '../../containers/HelpDropdownContainer'; import UserDropdown from '../../containers/UserDropdownContainer'; import GlobalMTLSStatus from '../../containers/GlobalMTLSContainer'; -import LoginPage from '../../containers/LoginPageContainer'; -import { store } from '../../store/ConfigStore'; import PfSpinnerContainer from '../../containers/PfSpinnerContainer'; import { kialiLogo } from '../../config'; -import { isKioskMode } from '../../utils/SearchParamUtils'; export const istioConfigTitle = 'Istio Config'; export const servicesTitle = 'Services'; type PropsType = RouteComponentProps & { - authenticated: boolean; navCollapsed: boolean; - checkCredentials: () => void; setNavCollapsed: (collapse: boolean) => void; jaegerUrl: string; enableIntegration: boolean; @@ -36,24 +31,6 @@ class Navigation extends React.Component { super(props); } - setDocLayout = () => { - if (document.documentElement) { - document.documentElement.className = this.props.authenticated ? 'layout-pf layout-pf-fixed' : 'login-pf'; - if (isKioskMode()) { - document.documentElement.className += ' kiosk'; - } - } - }; - - componentDidMount() { - this.setDocLayout(); - - if (!this.props.authenticated) { - // handle initial path from the browser - this.props.checkCredentials(); - } - } - setControlledState = event => { if ('navCollapsed' in event) { this.props.setNavCollapsed(this.props.navCollapsed); @@ -100,11 +77,7 @@ class Navigation extends React.Component { } render() { - store.subscribe(() => { - this.setDocLayout(); - }); - - return this.props.authenticated ? ( + return ( <> { - ) : ( - ); } } diff --git a/src/components/Nav/UserDropdown.tsx b/src/components/Nav/UserDropdown.tsx index f99762d174..f933aa3d0c 100644 --- a/src/components/Nav/UserDropdown.tsx +++ b/src/components/Nav/UserDropdown.tsx @@ -70,11 +70,6 @@ class UserDropdown extends React.Component { handleLogout() { this.props.logout(); - - const el = document.documentElement; - if (el) { - el.className = 'login-pf'; - } } extendSession = () => { diff --git a/src/components/Nav/__tests__/Navigation.test.tsx b/src/components/Nav/__tests__/Navigation.test.tsx index bad85d087a..e0aa03cc74 100644 --- a/src/components/Nav/__tests__/Navigation.test.tsx +++ b/src/components/Nav/__tests__/Navigation.test.tsx @@ -51,8 +51,6 @@ const _tester = (path: string, expectedMenuPath: string) => { const wrapper = shallow( { handleSelect={optionsChanged} nameDropdown={'Duration'} initialValue={config.toolbar.defaultDuration} - initialLabel={config.toolbar.intervalDuration[config.toolbar.defaultDuration]} - options={config.toolbar.intervalDuration} + initialLabel={serverConfig.durations[config.toolbar.defaultDuration]} + options={serverConfig.durations} /> ); const elt = wrapper diff --git a/src/components/ToolbarDropdown/__tests__/__snapshots__/ToolbarDropdown.test.tsx.snap b/src/components/ToolbarDropdown/__tests__/__snapshots__/ToolbarDropdown.test.tsx.snap index 5df9dccb7e..00d0ddded0 100644 --- a/src/components/ToolbarDropdown/__tests__/__snapshots__/ToolbarDropdown.test.tsx.snap +++ b/src/components/ToolbarDropdown/__tests__/__snapshots__/ToolbarDropdown.test.tsx.snap @@ -15,14 +15,10 @@ ShallowWrapper { "10800": "Last 3h", "1800": "Last 30m", "21600": "Last 6h", - "2592000": "Last 30d", "300": "Last 5m", "3600": "Last 1h", - "43200": "Last 12h", "60": "Last 1m", "600": "Last 10m", - "604800": "Last 7d", - "86400": "Last 1d", } } />, @@ -126,46 +122,6 @@ ShallowWrapper { > Last 6h - - Last 12h - - - Last 1d - - - Last 7d - - - Last 30d - , ], }, @@ -261,46 +217,6 @@ ShallowWrapper { > Last 6h , - - Last 12h - , - - Last 1d - , - - Last 7d - , - - Last 30d - , ], "disabled": false, "id": "graph_filter_interval_duration", @@ -429,74 +345,6 @@ ShallowWrapper { "rendered": "Last 6h", "type": [Function], }, - Object { - "instance": null, - "key": "43200", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 12h", - "disabled": false, - "divider": false, - "eventKey": "43200", - "header": false, - }, - "ref": null, - "rendered": "Last 12h", - "type": [Function], - }, - Object { - "instance": null, - "key": "86400", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 1d", - "disabled": false, - "divider": false, - "eventKey": "86400", - "header": false, - }, - "ref": null, - "rendered": "Last 1d", - "type": [Function], - }, - Object { - "instance": null, - "key": "604800", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 7d", - "disabled": false, - "divider": false, - "eventKey": "604800", - "header": false, - }, - "ref": null, - "rendered": "Last 7d", - "type": [Function], - }, - Object { - "instance": null, - "key": "2592000", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 30d", - "disabled": false, - "divider": false, - "eventKey": "2592000", - "header": false, - }, - "ref": null, - "rendered": "Last 30d", - "type": [Function], - }, ], "type": [Function], }, @@ -596,46 +444,6 @@ ShallowWrapper { > Last 6h - - Last 12h - - - Last 1d - - - Last 7d - - - Last 30d - , ], }, @@ -731,46 +539,6 @@ ShallowWrapper { > Last 6h , - - Last 12h - , - - Last 1d - , - - Last 7d - , - - Last 30d - , ], "disabled": false, "id": "graph_filter_interval_duration", @@ -899,74 +667,6 @@ ShallowWrapper { "rendered": "Last 6h", "type": [Function], }, - Object { - "instance": null, - "key": "43200", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 12h", - "disabled": false, - "divider": false, - "eventKey": "43200", - "header": false, - }, - "ref": null, - "rendered": "Last 12h", - "type": [Function], - }, - Object { - "instance": null, - "key": "86400", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 1d", - "disabled": false, - "divider": false, - "eventKey": "86400", - "header": false, - }, - "ref": null, - "rendered": "Last 1d", - "type": [Function], - }, - Object { - "instance": null, - "key": "604800", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 7d", - "disabled": false, - "divider": false, - "eventKey": "604800", - "header": false, - }, - "ref": null, - "rendered": "Last 7d", - "type": [Function], - }, - Object { - "instance": null, - "key": "2592000", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 30d", - "disabled": false, - "divider": false, - "eventKey": "2592000", - "header": false, - }, - "ref": null, - "rendered": "Last 30d", - "type": [Function], - }, ], "type": [Function], }, @@ -2096,14 +1796,10 @@ ShallowWrapper { "10800": "Last 3h", "1800": "Last 30m", "21600": "Last 6h", - "2592000": "Last 30d", "300": "Last 5m", "3600": "Last 1h", - "43200": "Last 12h", "60": "Last 1m", "600": "Last 10m", - "604800": "Last 7d", - "86400": "Last 1d", } } value={60} @@ -2208,46 +1904,6 @@ ShallowWrapper { > Last 6h - - Last 12h - - - Last 1d - - - Last 7d - - - Last 30d - , ], }, @@ -2343,46 +1999,6 @@ ShallowWrapper { > Last 6h , - - Last 12h - , - - Last 1d - , - - Last 7d - , - - Last 30d - , ], "disabled": false, "id": "graph_filter_interval_duration", @@ -2511,74 +2127,6 @@ ShallowWrapper { "rendered": "Last 6h", "type": [Function], }, - Object { - "instance": null, - "key": "43200", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 12h", - "disabled": false, - "divider": false, - "eventKey": "43200", - "header": false, - }, - "ref": null, - "rendered": "Last 12h", - "type": [Function], - }, - Object { - "instance": null, - "key": "86400", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 1d", - "disabled": false, - "divider": false, - "eventKey": "86400", - "header": false, - }, - "ref": null, - "rendered": "Last 1d", - "type": [Function], - }, - Object { - "instance": null, - "key": "604800", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 7d", - "disabled": false, - "divider": false, - "eventKey": "604800", - "header": false, - }, - "ref": null, - "rendered": "Last 7d", - "type": [Function], - }, - Object { - "instance": null, - "key": "2592000", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 30d", - "disabled": false, - "divider": false, - "eventKey": "2592000", - "header": false, - }, - "ref": null, - "rendered": "Last 30d", - "type": [Function], - }, ], "type": [Function], }, @@ -2678,46 +2226,6 @@ ShallowWrapper { > Last 6h - - Last 12h - - - Last 1d - - - Last 7d - - - Last 30d - , ], }, @@ -2813,46 +2321,6 @@ ShallowWrapper { > Last 6h , - - Last 12h - , - - Last 1d - , - - Last 7d - , - - Last 30d - , ], "disabled": false, "id": "graph_filter_interval_duration", @@ -2981,74 +2449,6 @@ ShallowWrapper { "rendered": "Last 6h", "type": [Function], }, - Object { - "instance": null, - "key": "43200", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 12h", - "disabled": false, - "divider": false, - "eventKey": "43200", - "header": false, - }, - "ref": null, - "rendered": "Last 12h", - "type": [Function], - }, - Object { - "instance": null, - "key": "86400", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 1d", - "disabled": false, - "divider": false, - "eventKey": "86400", - "header": false, - }, - "ref": null, - "rendered": "Last 1d", - "type": [Function], - }, - Object { - "instance": null, - "key": "604800", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 7d", - "disabled": false, - "divider": false, - "eventKey": "604800", - "header": false, - }, - "ref": null, - "rendered": "Last 7d", - "type": [Function], - }, - Object { - "instance": null, - "key": "2592000", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 30d", - "disabled": false, - "divider": false, - "eventKey": "2592000", - "header": false, - }, - "ref": null, - "rendered": "Last 30d", - "type": [Function], - }, ], "type": [Function], }, diff --git a/src/config/authenticationConfig.ts b/src/config/authenticationConfig.ts new file mode 100644 index 0000000000..b2a8f999e1 --- /dev/null +++ b/src/config/authenticationConfig.ts @@ -0,0 +1,7 @@ +import { AuthConfig, AuthStrategy } from '../types/Auth'; + +const authenticationConfig: AuthConfig = { + strategy: AuthStrategy.login +}; + +export default authenticationConfig; diff --git a/src/config/config.ts b/src/config/config.ts index a0f96f0086..6e51c1da1b 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -16,20 +16,6 @@ const conf = { toolbar: { /** Duration default in 1 minute */ defaultDuration: 1 * UNIT_TIME.MINUTE, - /** Options in interval duration */ - intervalDuration: { - 60: 'Last 1m', - 300: 'Last 5m', - 600: 'Last 10m', - 1800: 'Last 30m', - 3600: 'Last 1h', - 10800: 'Last 3h', - 21600: 'Last 6h', - 43200: 'Last 12h', - 86400: 'Last 1d', - 604800: 'Last 7d', - 2592000: 'Last 30d' - }, /** By default refresh is 15 seconds */ defaultPollInterval: 15 * MILLISECONDS, /** Options in refresh */ diff --git a/src/config/serverConfig.ts b/src/config/serverConfig.ts index cb6e88ae0f..c9ec2073e7 100644 --- a/src/config/serverConfig.ts +++ b/src/config/serverConfig.ts @@ -1,42 +1,81 @@ -import deepFreeze from 'deep-freeze'; -import { store } from '../store/ConfigStore'; -import { KialiAppState, ServerConfig } from '../store/Store'; -import { PersistPartial } from 'redux-persist'; -import { DurationInSeconds } from '../types/Common'; -import { forOwn, pickBy } from 'lodash'; - -// It's not great to access the store directly for convenience but the alternative is -// a huge code ripple just to access some server config. better to just have this one utility. -export const serverConfig = (): ServerConfig => { - const actualState = store.getState() || ({} as KialiAppState & PersistPartial); - return deepFreeze(actualState.serverConfig); +import { ServerConfig } from '../types/ServerConfig'; + +export type Durations = { [key: number]: string }; + +type ComputedServerConfig = ServerConfig & { + durations: Durations; }; -// getValidDurations returns a new object with only the durations <= retention -export const getValidDurations = ( - durations: { [key: number]: string }, - retention?: DurationInSeconds -): { [key: number]: string } => { - const validDurations = pickBy(durations, (_, key: number) => { - return !retention || key <= retention; +const toDurations = (tupleArray: [number, string][]): Durations => { + const obj = {}; + tupleArray.forEach(tuple => { + obj[tuple[0]] = tuple[1]; }); - return validDurations as { [key: number]: string }; + return obj; }; -// getValidDuration returns duration if it is a valid property key in durations, otherwise it return the first property -// key in durations. It is assumed that durations has at least one property. -export const getValidDuration = ( - durations: { [key: number]: string }, - duration: DurationInSeconds -): DurationInSeconds => { - let validDuration = 0; - forOwn(durations, (_, key) => { - const d = Number(key); - if (d === duration) { - validDuration = d; - } else if (!validDuration) { - validDuration = d; +let durationsTuples: [number, string][] = [ + [60, 'Last 1m'], + [300, 'Last 5m'], + [600, 'Last 10m'], + [1800, 'Last 30m'], + [3600, 'Last 1h'], + [10800, 'Last 3h'], + [21600, 'Last 6h'], + [43200, 'Last 12h'], + [86400, 'Last 1d'], + [604800, 'Last 7d'], + [2592000, 'Last 30d'] +]; + +const computeValidDurations = (cfg: ComputedServerConfig) => { + if (cfg.prometheus.storageTsdbRetention) { + // Make sure we'll keep at least one item + if (cfg.prometheus.storageTsdbRetention <= durationsTuples[0][0]) { + durationsTuples = [durationsTuples[0]]; + } else { + durationsTuples = durationsTuples.filter(d => d[0] <= cfg.prometheus.storageTsdbRetention!); } - }); - return validDuration; + } + cfg.durations = toDurations(durationsTuples); +}; + +// Set some defaults. Mainly used in tests, because +// these will be overwritten on user login. +let serverConfig: ComputedServerConfig = { + istioNamespace: 'istio-system', + istioLabels: { + appLabelName: 'app', + versionLabelName: 'version' + }, + prometheus: { + globalScrapeInterval: 15, + storageTsdbRetention: 21600 + }, + durations: {} +}; +computeValidDurations(serverConfig); +export { serverConfig }; + +export const toValidDuration = (duration: number): number => { + // Check if valid + if (serverConfig.durations[duration]) { + return duration; + } + // Get closest duration + for (let i = durationsTuples.length - 1; i >= 0; i--) { + if (duration > durationsTuples[i][0]) { + return durationsTuples[i][0]; + } + } + return durationsTuples[0][0]; +}; + +export const setServerConfig = (svcConfig: ServerConfig) => { + serverConfig = { + ...svcConfig, + durations: {} + }; + + computeValidDurations(serverConfig); }; diff --git a/src/containers/LoginPageContainer.ts b/src/containers/LoginPageContainer.ts index 1abd1a0f64..609f80b76b 100644 --- a/src/containers/LoginPageContainer.ts +++ b/src/containers/LoginPageContainer.ts @@ -11,7 +11,8 @@ const mapStateToProps = (state: KialiAppState) => ({ }); const mapDispatchToProps = (dispatch: ThunkDispatch) => ({ - authenticate: (username: string, password: string) => dispatch(LoginThunkActions.authenticate(username, password)) + authenticate: (username: string, password: string) => dispatch(LoginThunkActions.authenticate(username, password)), + checkCredentials: () => dispatch(LoginThunkActions.checkCredentials()) }); const LoginPageConnected = connect( diff --git a/src/containers/NavigationContainer.ts b/src/containers/NavigationContainer.ts index d8eead60a2..1f3fac2d88 100644 --- a/src/containers/NavigationContainer.ts +++ b/src/containers/NavigationContainer.ts @@ -1,9 +1,8 @@ import { connect } from 'react-redux'; import { ThunkDispatch } from 'redux-thunk'; import Navigation from '../components/Nav/Navigation'; -import { KialiAppState, Component, LoginStatus } from '../store/Store'; +import { KialiAppState, Component } from '../store/Store'; import { KialiAppAction } from '../actions/KialiAppAction'; -import LoginThunkActions from '../actions/LoginThunkActions'; import UserSettingsThunkActions from '../actions/UserSettingsThunkActions'; const getJaegerUrl = (components: Component[]) => { @@ -12,14 +11,12 @@ const getJaegerUrl = (components: Component[]) => { }; const mapStateToProps = (state: KialiAppState) => ({ - authenticated: state.authentication.status === LoginStatus.loggedIn, navCollapsed: state.userSettings.interface.navCollapse, jaegerUrl: getJaegerUrl(state.statusState.components), enableIntegration: state.jaegerState.enableIntegration }); const mapDispatchToProps = (dispatch: ThunkDispatch) => ({ - checkCredentials: () => dispatch(LoginThunkActions.checkCredentials()), setNavCollapsed: (collapse: boolean) => dispatch(UserSettingsThunkActions.setNavCollapsed(!collapse)) }); diff --git a/src/pages/AppDetails/AppDetailsPage.tsx b/src/pages/AppDetails/AppDetailsPage.tsx index f945192196..155b1ceeb9 100644 --- a/src/pages/AppDetails/AppDetailsPage.tsx +++ b/src/pages/AppDetails/AppDetailsPage.tsx @@ -13,7 +13,7 @@ import BreadcrumbView from '../../components/BreadcrumbView/BreadcrumbView'; import { GraphDefinition, GraphType, NodeParamsType, NodeType } from '../../types/Graph'; import { fetchTrafficDetails } from '../../helpers/TrafficDetailsHelper'; import TrafficDetails from '../../components/Metrics/TrafficDetails'; -import { MetricsDuration } from '../../components/MetricsOptions/MetricsDuration'; +import MetricsDuration from '../../components/MetricsOptions/MetricsDuration'; type AppDetailsState = { app: App; diff --git a/src/pages/AppList/AppListComponent.tsx b/src/pages/AppList/AppListComponent.tsx index fed2b6baa5..cd79306ee8 100644 --- a/src/pages/AppList/AppListComponent.tsx +++ b/src/pages/AppList/AppListComponent.tsx @@ -11,7 +11,6 @@ import { PromisesRegistry } from '../../utils/CancelablePromises'; import { ListPagesHelper } from '../../components/ListPage/ListPagesHelper'; import { SortField } from '../../types/SortFilters'; import { ListComponent } from '../../components/ListPage/ListComponent'; -import { HistoryManager, URLParams } from '../../app/History'; import { AlignRightStyle, ThinStyle } from '../../components/Filters/FilterStyles'; interface AppListComponentState extends ListComponent.State { @@ -68,11 +67,6 @@ class AppListComponent extends ListComponent.Component { - HistoryManager.setParam(URLParams.DURATION, String(key)); - this.setState({ rateInterval: key }); - }; - sortItemList(apps: AppListItem[], sortField: SortField, isAscending: boolean): Promise { // Chain promises, as there may be an ongoing fetch/refresh and sort can be called after UI interaction // This ensures that the list will display the new data with the right sorting diff --git a/src/pages/Graph/SummaryPanelCommon.tsx b/src/pages/Graph/SummaryPanelCommon.tsx index 5542cd4853..f28fd332ee 100644 --- a/src/pages/Graph/SummaryPanelCommon.tsx +++ b/src/pages/Graph/SummaryPanelCommon.tsx @@ -177,7 +177,7 @@ export const renderLabels = (data: NodeData) => { <>
{hasNamespace &&
); diff --git a/src/pages/Graph/SummaryPanelEdge.tsx b/src/pages/Graph/SummaryPanelEdge.tsx index fbc05e87ab..fd49eaecf6 100644 --- a/src/pages/Graph/SummaryPanelEdge.tsx +++ b/src/pages/Graph/SummaryPanelEdge.tsx @@ -233,8 +233,8 @@ export default class SummaryPanelEdge extends React.Component (