From 99a35fc7d66b74961bb0d7a4bbf969300c097ab8 Mon Sep 17 00:00:00 2001 From: Kim Doberstein Date: Sat, 28 Aug 2021 21:43:09 -0500 Subject: [PATCH 1/3] Add cluster in url --- .../src/components/detect-cluster/cluster.ts | 43 ++- .../src/utils/__tests__/user-settings.spec.ts | 6 +- frontend/public/actions/ui.ts | 19 +- frontend/public/components/app-contents.tsx | 248 ++++++++++++++++++ frontend/public/components/app.jsx | 12 +- frontend/public/components/nav/nav-header.tsx | 2 +- frontend/public/components/utils/link.tsx | 10 + .../public/components/utils/resource-link.tsx | 13 +- 8 files changed, 336 insertions(+), 17 deletions(-) diff --git a/frontend/packages/console-app/src/components/detect-cluster/cluster.ts b/frontend/packages/console-app/src/components/detect-cluster/cluster.ts index 76cd7ec96f9..a089784575d 100644 --- a/frontend/packages/console-app/src/components/detect-cluster/cluster.ts +++ b/frontend/packages/console-app/src/components/detect-cluster/cluster.ts @@ -2,9 +2,17 @@ import * as React from 'react'; // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore: FIXME missing exports due to out-of-sync @types/react-redux version import { useDispatch } from 'react-redux'; -import { setActiveCluster } from '@console/internal/actions/ui'; +import { useLocation } from 'react-router-dom'; +import isMultiClusterEnabled from '@console/app/src/utils/isMultiClusterEnabled'; +import { setActiveCluster, formatNamespaceRoute } from '@console/internal/actions/ui'; +import { getCluster } from '@console/internal/components/utils/link'; +import { history } from '@console/internal/components/utils/router'; +// import { useActiveNamespace } from '@console/shared'; import { LAST_CLUSTER_USER_SETTINGS_KEY } from '@console/shared/src/constants'; import { useUserSettings } from '@console/shared/src/hooks/useUserSettings'; +import store from '../../../../../public/redux'; + +export const multiClusterRoutePrefixes = ['/k8s/all-namespaces', '/k8s/cluster', '/k8s/ns']; type ClusterContextType = { cluster?: string; @@ -28,14 +36,39 @@ export const useValuesForClusterContext = () => { [dispatch, setLastCluster], ); + // const [activeNamespace] = useActiveNamespace(); + const urlCluster = getCluster(useLocation().pathname); React.useEffect(() => { - // TODO: Detect cluster from URL. - if (lastClusterLoaded && lastCluster) { + if (urlCluster) { + setLastCluster(urlCluster); + dispatch(setActiveCluster(urlCluster)); + } else if (lastClusterLoaded && lastCluster) { dispatch(setActiveCluster(lastCluster)); } - // Only run this hook after last cluster is loaded. + + if ( + isMultiClusterEnabled() && + lastClusterLoaded && + lastCluster && + !urlCluster && + multiClusterRoutePrefixes.some((pattern) => window.location.pathname.startsWith(pattern)) + ) { + const activeNamespace = store.getState().UI.get('activeNamespace'); + const newPath = formatNamespaceRoute( + activeNamespace, + window.location.pathname, + window.location, + false, + lastCluster, + ); + + if (newPath !== window.location.pathname) { + history.pushPath(newPath); + } + } + // Only run this hook after last cluster is loaded or window path changes. // eslint-disable-next-line react-hooks/exhaustive-deps - }, [lastClusterLoaded]); + }, [lastClusterLoaded, urlCluster, window.location.pathname]); return { cluster: lastCluster, diff --git a/frontend/packages/console-shared/src/utils/__tests__/user-settings.spec.ts b/frontend/packages/console-shared/src/utils/__tests__/user-settings.spec.ts index 91d1bb2e94d..28e2ee8b68f 100644 --- a/frontend/packages/console-shared/src/utils/__tests__/user-settings.spec.ts +++ b/frontend/packages/console-shared/src/utils/__tests__/user-settings.spec.ts @@ -40,7 +40,10 @@ describe('createConfigMap', () => { expect(actual).toEqual(configMap); expect(coFetchMock).toHaveBeenCalledTimes(1); - expect(coFetchMock).lastCalledWith('/api/console/user-settings', { method: 'POST' }); + expect(coFetchMock).lastCalledWith('/api/console/user-settings', { + headers: { 'X-Cluster': 'local-cluster' }, + method: 'POST', + }); }); }); @@ -61,6 +64,7 @@ describe('updateConfigMap', () => { headers: { Accept: 'application/json', 'Content-Type': 'application/merge-patch+json;charset=UTF-8', + 'X-Cluster': 'local-cluster', }, body: '{"data":{"key":"value"}}', }, diff --git a/frontend/public/actions/ui.ts b/frontend/public/actions/ui.ts index 9b4d351212e..a373b898c07 100644 --- a/frontend/public/actions/ui.ts +++ b/frontend/public/actions/ui.ts @@ -10,6 +10,7 @@ import { ALL_NAMESPACES_KEY, LAST_NAMESPACE_NAME_LOCAL_STORAGE_KEY, } from '@console/shared/src/constants'; +// import { multiClusterRoutePrefixes } from '@console/app/src/components/detect-cluster/cluster' import { K8sResourceKind, PodKind, NodeKind } from '../module/k8s'; import { allModels } from '../module/k8s/k8s-models'; import { detectFeatures, clearSSARFlags } from './features'; @@ -152,10 +153,23 @@ export const formatNamespaceRoute = ( originalPath, location?, forceList?: boolean, + activeCluster?: string, ) => { let path = originalPath.substr(window.SERVER_FLAGS.basePath.length); let parts = path.split('/').filter((p) => p); + + if (parts[0] === 'cluster') { + // The url pattern that includes the cluster name starts with /cluster/:clusterName + parts.splice(0, 2); + } + + const multiClusterRoutePrefixes = ['/k8s/all-namespaces', '/k8s/cluster', '/k8s/ns']; + const clusterPathPart = + activeCluster && multiClusterRoutePrefixes.includes(`/${parts[0]}/${parts[1]}`) + ? `/cluster/${activeCluster}` + : ''; + const prefix = parts.shift(); let previousNS; @@ -168,7 +182,7 @@ export const formatNamespaceRoute = ( } if (!previousNS) { - return originalPath; + return `${clusterPathPart}${originalPath}`; } if ( @@ -193,7 +207,7 @@ export const formatNamespaceRoute = ( path += `${location.search}${location.hash}`; } - return path; + return `${clusterPathPart}${path}`; }; export const setCurrentLocation = (location: string) => @@ -211,6 +225,7 @@ export const setActiveNamespace = (namespace: string = '') => { // make it noop when new active namespace is the same // otherwise users will get page refresh and cry about // broken direct links and bookmarks + if (namespace !== getActiveNamespace()) { const oldPath = window.location.pathname; const newPath = formatNamespaceRoute(namespace, oldPath, window.location); diff --git a/frontend/public/components/app-contents.tsx b/frontend/public/components/app-contents.tsx index a0c94f2948e..4d9a78a2e00 100644 --- a/frontend/public/components/app-contents.tsx +++ b/frontend/public/components/app-contents.tsx @@ -291,6 +291,17 @@ const AppContents: React.FC<{}> = () => { /> )} /> + ( + + )} + /> = () => { ) } /> + + import('./events' /* webpackChunkName: "events" */).then((m) => + NamespaceFromURL(m.EventStreamPage), + ) + } + /> + = () => { ) } /> + + import('./events' /* webpackChunkName: "events" */).then((m) => + NamespaceFromURL(m.EventStreamPage), + ) + } + /> + @@ -323,6 +354,16 @@ const AppContents: React.FC<{}> = () => { ) } /> + + import('./import-yaml' /* webpackChunkName: "import-yaml" */).then((m) => + NamespaceFromURL(m.ImportYamlPage), + ) + } + /> + = () => { ) } /> + + import('./import-yaml' /* webpackChunkName: "import-yaml" */).then((m) => + NamespaceFromURL(m.ImportYamlPage), + ) + } + /> { // These pages are temporarily disabled. We need to update the safe resources list. @@ -354,6 +404,17 @@ const AppContents: React.FC<{}> = () => { ) } /> + + import('./secrets/create-secret' /* webpackChunkName: "create-secret" */).then( + (m) => m.CreateSecret, + ) + } + /> + = () => { ) } /> + + import('./secrets/create-secret' /* webpackChunkName: "create-secret" */).then( + (m) => m.EditSecret, + ) + } + /> + import('./create-yaml').then((m) => m.EditYAMLPage)} /> + import('./create-yaml').then((m) => m.EditYAMLPage)} + /> = () => { ).then((m) => m.CreateNetworkPolicy) } /> + + import( + '@console/app/src/components/network-policies/create-network-policy' /* webpackChunkName: "create-network-policy" */ + ).then((m) => m.CreateNetworkPolicy) + } + /> = () => { ) } /> + + import('./routes/create-route' /* webpackChunkName: "create-route" */).then( + (m) => m.CreateRoute, + ) + } + /> = () => { } kind="RoleBinding" /> + + import('./RBAC' /* webpackChunkName: "rbac" */).then((m) => m.CreateRoleBinding) + } + kind="RoleBinding" + /> + = () => { } kind="RoleBinding" /> + + import('./RBAC' /* webpackChunkName: "rbac" */).then((m) => m.CreateRoleBinding) + } + kind="RoleBinding" + /> + = () => { import('./RBAC' /* webpackChunkName: "rbac" */).then((m) => m.CopyRoleBinding) } /> + + import('./RBAC' /* webpackChunkName: "rbac" */).then((m) => m.CopyRoleBinding) + } + /> + = () => { import('./RBAC' /* webpackChunkName: "rbac" */).then((m) => m.EditRoleBinding) } /> + + import('./RBAC' /* webpackChunkName: "rbac" */).then((m) => m.EditRoleBinding) + } + /> + = () => { import('./RBAC' /* webpackChunkName: "rbac" */).then((m) => m.CopyRoleBinding) } /> + + import('./RBAC' /* webpackChunkName: "rbac" */).then((m) => m.CopyRoleBinding) + } + /> + = () => { import('./RBAC' /* webpackChunkName: "rbac" */).then((m) => m.EditRoleBinding) } /> + + import('./RBAC' /* webpackChunkName: "rbac" */).then((m) => m.EditRoleBinding) + } + /> + = () => { ) } /> + + import('./storage/attach-storage' /* webpackChunkName: "attach-storage" */).then( + (m) => m.default, + ) + } + /> = () => { ) } /> + + import('./storage/create-pvc' /* webpackChunkName: "create-pvc" */).then( + (m) => m.CreatePVC, + ) + } + /> = () => { ).then((m) => m.VolumeSnapshot) } /> + + import( + '@console/app/src/components/volume-snapshot/create-volume-snapshot/create-volume-snapshot' /* webpackChunkName: "create-volume-snapshot" */ + ).then((m) => m.VolumeSnapshot) + } + /> = () => { ).then((m) => m.VolumeSnapshot) } /> + + import( + '@console/app/src/components/volume-snapshot/create-volume-snapshot/create-volume-snapshot' /* webpackChunkName: "create-volume-snapshot" */ + ).then((m) => m.VolumeSnapshot) + } + /> = () => { ) } /> + + import('./storage-class-form' /* webpackChunkName: "storage-class-form" */).then( + (m) => m.StorageClassForm, + ) + } + /> + {/* START of new links */} + + + + + + import('./container').then((m) => m.ContainersDetailsPage)} /> + import('./container').then((m) => m.ContainersDetailsPage)} + /> + + + + + + + + + + {/* END of new links */} {inactivePluginPageRoutes} diff --git a/frontend/public/components/app.jsx b/frontend/public/components/app.jsx index 3a061b75c9b..7b06490cfce 100644 --- a/frontend/public/components/app.jsx +++ b/frontend/public/components/app.jsx @@ -204,9 +204,9 @@ class App_ extends React.PureComponent { ); return ( - - - + + + {contextProviderExtensions.reduce( (children, e) => ( @@ -215,9 +215,9 @@ class App_ extends React.PureComponent { ), content, )} - - - + + + ); } } diff --git a/frontend/public/components/nav/nav-header.tsx b/frontend/public/components/nav/nav-header.tsx index 2b0ca6ce513..e349675b49c 100644 --- a/frontend/public/components/nav/nav-header.tsx +++ b/frontend/public/components/nav/nav-header.tsx @@ -58,7 +58,7 @@ const NavHeader: React.FC = ({ onPerspectiveSelected }) => { dispatch(clearSSARFlags()); dispatch(detectFeatures()); const oldPath = window.location.pathname; - const newPath = formatNamespaceRoute(activeNamespace, oldPath, window.location, true); + const newPath = formatNamespaceRoute(activeNamespace, oldPath, window.location, true, cluster); if (newPath !== oldPath) { history.pushPath(newPath); } diff --git a/frontend/public/components/utils/link.tsx b/frontend/public/components/utils/link.tsx index ed82e6bd2a5..89498b3fa25 100644 --- a/frontend/public/components/utils/link.tsx +++ b/frontend/public/components/utils/link.tsx @@ -29,10 +29,18 @@ export const namespacedPrefixes = [ '/provisionedservices', '/search', '/status', + '/cluster/:cluster/k8s', ]; export const stripBasePath = (path: string): string => path.replace(basePathPattern, '/'); +export const getCluster = (path: string): string => { + const strippedPath = stripBasePath(path); + const split = strippedPath.split('/').filter((x) => x); + + return split[0] === 'cluster' ? split[1] : null; +}; + export const getNamespace = (path: string): string => { path = stripBasePath(path); const split = path.split('/').filter((x) => x); @@ -46,6 +54,8 @@ export const getNamespace = (path: string): string => { ns = split[3]; } else if (split[1] === 'ns' && split[2]) { ns = split[2]; + } else if (split[0] === 'cluster' && split[3] === 'ns' && split[4]) { + ns = split[4]; } else { return; } diff --git a/frontend/public/components/utils/resource-link.tsx b/frontend/public/components/utils/resource-link.tsx index 48a9cef62df..036764fc26d 100644 --- a/frontend/public/components/utils/resource-link.tsx +++ b/frontend/public/components/utils/resource-link.tsx @@ -2,8 +2,9 @@ import * as _ from 'lodash-es'; import * as React from 'react'; import { Link } from 'react-router-dom'; import * as classNames from 'classnames'; - import { FLAGS } from '@console/shared/src/constants'; +// import { getActiveCluster } from '@console/internal/actions/ui'; + import { ResourceIcon } from './resource-icon'; import { modelFor, @@ -18,10 +19,18 @@ import { FlagsObject } from '../../reducers/features'; const unknownKinds = new Set(); +// import { getActiveCluster } from '@console/internal/reducers/ui'; + export const resourcePathFromModel = (model: K8sKind, name?: string, namespace?: string) => { const { plural, namespaced, crd } = model; + // const activeCluster = getActiveCluster(); + + let url = ''; + // if (activeCluster) { + // url += `/cluster/${activeCluster}`; + // } - let url = '/k8s/'; + url += '/k8s/'; if (!namespaced) { url += 'cluster/'; From 788d60f09c1481ca6c61bb59edeb6d86c47fb057 Mon Sep 17 00:00:00 2001 From: Kim Doberstein Date: Tue, 28 Sep 2021 15:16:02 -0500 Subject: [PATCH 2/3] Feedback from PR - If isMultiClusterEnabled, add the cluster name to '/k8s/ ' based urls - Change import path from store to not be relative --- .../src/components/detect-cluster/cluster.ts | 4 +++- .../public/components/utils/resource-link.tsx | 20 +++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/frontend/packages/console-app/src/components/detect-cluster/cluster.ts b/frontend/packages/console-app/src/components/detect-cluster/cluster.ts index a089784575d..3e4e1c85a88 100644 --- a/frontend/packages/console-app/src/components/detect-cluster/cluster.ts +++ b/frontend/packages/console-app/src/components/detect-cluster/cluster.ts @@ -8,9 +8,9 @@ import { setActiveCluster, formatNamespaceRoute } from '@console/internal/action import { getCluster } from '@console/internal/components/utils/link'; import { history } from '@console/internal/components/utils/router'; // import { useActiveNamespace } from '@console/shared'; +import store from '@console/internal/redux'; import { LAST_CLUSTER_USER_SETTINGS_KEY } from '@console/shared/src/constants'; import { useUserSettings } from '@console/shared/src/hooks/useUserSettings'; -import store from '../../../../../public/redux'; export const multiClusterRoutePrefixes = ['/k8s/all-namespaces', '/k8s/cluster', '/k8s/ns']; @@ -37,6 +37,8 @@ export const useValuesForClusterContext = () => { ); // const [activeNamespace] = useActiveNamespace(); + // const activeNamespace = useSelector(({ UI }) => UI.get('activeNamespace')); + const urlCluster = getCluster(useLocation().pathname); React.useEffect(() => { if (urlCluster) { diff --git a/frontend/public/components/utils/resource-link.tsx b/frontend/public/components/utils/resource-link.tsx index 036764fc26d..f81627b21d9 100644 --- a/frontend/public/components/utils/resource-link.tsx +++ b/frontend/public/components/utils/resource-link.tsx @@ -3,7 +3,8 @@ import * as React from 'react'; import { Link } from 'react-router-dom'; import * as classNames from 'classnames'; import { FLAGS } from '@console/shared/src/constants'; -// import { getActiveCluster } from '@console/internal/actions/ui'; +import { getActiveCluster } from '@console/internal/actions/ui'; +import isMultiClusterEnabled from '@console/app/src/utils/isMultiClusterEnabled'; import { ResourceIcon } from './resource-icon'; import { @@ -19,16 +20,19 @@ import { FlagsObject } from '../../reducers/features'; const unknownKinds = new Set(); -// import { getActiveCluster } from '@console/internal/reducers/ui'; - -export const resourcePathFromModel = (model: K8sKind, name?: string, namespace?: string) => { +export const resourcePathFromModel = ( + model: K8sKind, + name?: string, + namespace?: string, + cluster?: string, +) => { const { plural, namespaced, crd } = model; - // const activeCluster = getActiveCluster(); + const activeCluster = cluster || getActiveCluster(); let url = ''; - // if (activeCluster) { - // url += `/cluster/${activeCluster}`; - // } + if (activeCluster && isMultiClusterEnabled()) { + url += `/cluster/${activeCluster}`; + } url += '/k8s/'; From 64cb38e5759b3ab245d519ab7db9efaa77ef001c Mon Sep 17 00:00:00 2001 From: Kim Doberstein Date: Tue, 5 Oct 2021 11:28:14 -0500 Subject: [PATCH 3/3] Feedback from PR Includes: - renaming "activeCluster" variable in resource-link.tsx - forwarding from a /cluster route to another /cluster route --- frontend/public/components/app-contents.tsx | 6 +++--- frontend/public/components/utils/resource-link.tsx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/public/components/app-contents.tsx b/frontend/public/components/app-contents.tsx index 4d9a78a2e00..7e06063750d 100644 --- a/frontend/public/components/app-contents.tsx +++ b/frontend/public/components/app-contents.tsx @@ -296,9 +296,9 @@ const AppContents: React.FC<{}> = () => { exact render={({ match }) => ( )} /> diff --git a/frontend/public/components/utils/resource-link.tsx b/frontend/public/components/utils/resource-link.tsx index f81627b21d9..c281ab0ee08 100644 --- a/frontend/public/components/utils/resource-link.tsx +++ b/frontend/public/components/utils/resource-link.tsx @@ -27,11 +27,11 @@ export const resourcePathFromModel = ( cluster?: string, ) => { const { plural, namespaced, crd } = model; - const activeCluster = cluster || getActiveCluster(); + const targetCluster = cluster || getActiveCluster(); let url = ''; - if (activeCluster && isMultiClusterEnabled()) { - url += `/cluster/${activeCluster}`; + if (targetCluster && isMultiClusterEnabled()) { + url += `/cluster/${targetCluster}`; } url += '/k8s/';