mirror of
https://github.com/datahub-project/datahub.git
synced 2025-12-25 08:58:26 +00:00
refactor(ui): Extract searchable page into its own component (perf + ux) (#5318)
This commit is contained in:
parent
cb54629af4
commit
a44167c33d
@ -1,48 +1,21 @@
|
||||
import React from 'react';
|
||||
import { Switch, Route, Redirect } from 'react-router-dom';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
import { Layout } from 'antd';
|
||||
import { BrowseResultsPage } from './browse/BrowseResultsPage';
|
||||
import { EntityPage } from './entity/EntityPage';
|
||||
import { PageRoutes } from '../conf/Global';
|
||||
import { useEntityRegistry } from './useEntityRegistry';
|
||||
import { HomePage } from './home/HomePage';
|
||||
import { SearchPage } from './search/SearchPage';
|
||||
import { AnalyticsPage } from './analyticsDashboard/components/AnalyticsPage';
|
||||
import AppConfigProvider from '../AppConfigProvider';
|
||||
import { ManageIngestionPage } from './ingest/ManageIngestionPage';
|
||||
import { ManageDomainsPage } from './domain/ManageDomainsPage';
|
||||
import BusinessGlossaryPage from './glossary/BusinessGlossaryPage';
|
||||
import { SettingsPage } from './settings/SettingsPage';
|
||||
import { NoPageFound } from './shared/NoPageFound';
|
||||
import { SearchRoutes } from './SearchRoutes';
|
||||
|
||||
/**
|
||||
* Container for all views behind an authentication wall.
|
||||
*/
|
||||
export const ProtectedRoutes = (): JSX.Element => {
|
||||
const entityRegistry = useEntityRegistry();
|
||||
return (
|
||||
<AppConfigProvider>
|
||||
<Layout style={{ height: '100%', width: '100%' }}>
|
||||
<Layout>
|
||||
<Switch>
|
||||
<Route exact path="/" render={() => <HomePage />} />
|
||||
{entityRegistry.getEntities().map((entity) => (
|
||||
<Route
|
||||
key={entity.getPathName()}
|
||||
path={`/${entity.getPathName()}/:urn`}
|
||||
render={() => <EntityPage entityType={entity.type} />}
|
||||
/>
|
||||
))}
|
||||
<Route path={PageRoutes.SEARCH_RESULTS} render={() => <SearchPage />} />
|
||||
<Route path={PageRoutes.BROWSE_RESULTS} render={() => <BrowseResultsPage />} />
|
||||
<Route path={PageRoutes.ANALYTICS} render={() => <AnalyticsPage />} />
|
||||
<Route path={PageRoutes.POLICIES} render={() => <Redirect to="/settings/policies" />} />
|
||||
<Route path={PageRoutes.IDENTITIES} render={() => <Redirect to="/settings/identities" />} />
|
||||
<Route path={PageRoutes.DOMAINS} render={() => <ManageDomainsPage />} />
|
||||
<Route path={PageRoutes.INGESTION} render={() => <ManageIngestionPage />} />
|
||||
<Route path={PageRoutes.SETTINGS} render={() => <SettingsPage />} />
|
||||
<Route path={PageRoutes.GLOSSARY} render={() => <BusinessGlossaryPage />} />
|
||||
<Route path="/*" component={NoPageFound} />
|
||||
<Route path="/*" render={() => <SearchRoutes />} />
|
||||
</Switch>
|
||||
</Layout>
|
||||
</Layout>
|
||||
|
||||
@ -2,8 +2,6 @@ import React, { useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Alert, Divider, Input, Select } from 'antd';
|
||||
import { SearchOutlined } from '@ant-design/icons';
|
||||
|
||||
import { SearchablePage } from '../../search/SearchablePage';
|
||||
import { ChartGroup } from './ChartGroup';
|
||||
import { useGetAnalyticsChartsQuery, useGetMetadataAnalyticsChartsQuery } from '../../../graphql/analytics.generated';
|
||||
import { useGetHighlightsQuery } from '../../../graphql/highlights.generated';
|
||||
@ -85,7 +83,7 @@ export const AnalyticsPage = () => {
|
||||
});
|
||||
|
||||
return (
|
||||
<SearchablePage>
|
||||
<>
|
||||
<HighlightGroup>
|
||||
{highlightLoading && (
|
||||
<Message type="loading" content="Loading Highlights..." style={{ marginTop: '10%' }} />
|
||||
@ -179,6 +177,6 @@ export const AnalyticsPage = () => {
|
||||
</>
|
||||
))}
|
||||
</>
|
||||
</SearchablePage>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import { Result } from 'antd';
|
||||
import React from 'react';
|
||||
import { SearchablePage } from '../search/SearchablePage';
|
||||
|
||||
export const UnauthorizedPage = () => {
|
||||
return (
|
||||
<SearchablePage>
|
||||
<>
|
||||
<Result status="403" title="Unauthorized" subTitle="Sorry, you are not authorized to access this page." />
|
||||
</SearchablePage>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { Affix } from 'antd';
|
||||
import { SearchablePage } from '../search/SearchablePage';
|
||||
import { LegacyBrowsePath } from './LegacyBrowsePath';
|
||||
import { useGetBrowsePathsQuery } from '../../graphql/browse.generated';
|
||||
import { EntityType } from '../../types.generated';
|
||||
@ -26,7 +25,7 @@ export const BrowsableEntityPage = ({
|
||||
const { data } = useGetBrowsePathsQuery({ variables: { input: { urn: _urn, type: _type } } });
|
||||
|
||||
return (
|
||||
<SearchablePage>
|
||||
<>
|
||||
{data && data.browsePaths && data.browsePaths.length > 0 && (
|
||||
<Affix offsetTop={60}>
|
||||
<LegacyBrowsePath
|
||||
@ -39,6 +38,6 @@ export const BrowsableEntityPage = ({
|
||||
</Affix>
|
||||
)}
|
||||
{_children}
|
||||
</SearchablePage>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -4,7 +4,6 @@ import * as QueryString from 'query-string';
|
||||
import { Affix, Alert } from 'antd';
|
||||
import { BrowseCfg } from '../../conf';
|
||||
import { BrowseResults } from './BrowseResults';
|
||||
import { SearchablePage } from '../search/SearchablePage';
|
||||
import { useGetBrowseResultsQuery } from '../../graphql/browse.generated';
|
||||
import { LegacyBrowsePath } from './LegacyBrowsePath';
|
||||
import { PageRoutes } from '../../conf/Global';
|
||||
@ -56,7 +55,7 @@ export const BrowseResultsPage = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<SearchablePage>
|
||||
<>
|
||||
<Affix offsetTop={60}>
|
||||
<LegacyBrowsePath type={entityType} path={path} isBrowsable />
|
||||
</Affix>
|
||||
@ -74,6 +73,6 @@ export const BrowseResultsPage = () => {
|
||||
onChangePage={onChangePage}
|
||||
/>
|
||||
)}
|
||||
</SearchablePage>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { Typography } from 'antd';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { SearchablePage } from '../search/SearchablePage';
|
||||
import { DomainsList } from './DomainsList';
|
||||
|
||||
const PageContainer = styled.div`
|
||||
@ -24,18 +23,16 @@ const ListContainer = styled.div``;
|
||||
|
||||
export const ManageDomainsPage = () => {
|
||||
return (
|
||||
<SearchablePage>
|
||||
<PageContainer>
|
||||
<PageHeaderContainer>
|
||||
<PageTitle level={3}>Domains</PageTitle>
|
||||
<Typography.Paragraph type="secondary">
|
||||
View your DataHub Domains. Take administrative actions.
|
||||
</Typography.Paragraph>
|
||||
</PageHeaderContainer>
|
||||
<ListContainer>
|
||||
<DomainsList />
|
||||
</ListContainer>
|
||||
</PageContainer>
|
||||
</SearchablePage>
|
||||
<PageContainer>
|
||||
<PageHeaderContainer>
|
||||
<PageTitle level={3}>Domains</PageTitle>
|
||||
<Typography.Paragraph type="secondary">
|
||||
View your DataHub Domains. Take administrative actions.
|
||||
</Typography.Paragraph>
|
||||
</PageHeaderContainer>
|
||||
<ListContainer>
|
||||
<DomainsList />
|
||||
</ListContainer>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -5,7 +5,6 @@ import { EntityType } from '../../types.generated';
|
||||
import { BrowsableEntityPage } from '../browse/BrowsableEntityPage';
|
||||
import LineageExplorer from '../lineage/LineageExplorer';
|
||||
import useIsLineageMode from '../lineage/utils/useIsLineageMode';
|
||||
import { SearchablePage } from '../search/SearchablePage';
|
||||
import { useEntityRegistry } from '../useEntityRegistry';
|
||||
import analytics, { EventType } from '../analytics';
|
||||
import { decodeUrn } from './shared/utils';
|
||||
@ -32,7 +31,6 @@ export const EntityPage = ({ entityType }: Props) => {
|
||||
const entity = entityRegistry.getEntity(entityType);
|
||||
const isBrowsable = entity.isBrowseEnabled();
|
||||
const isLineageSupported = entity.isLineageEnabled();
|
||||
const ContainerPage = isBrowsable || isLineageSupported ? BrowsableEntityPage : SearchablePage;
|
||||
const isLineageMode = useIsLineageMode();
|
||||
const authenticatedUserUrn = useGetAuthenticatedUserUrn();
|
||||
const { loading, error, data } = useGetGrantedPrivilegesQuery({
|
||||
@ -74,8 +72,8 @@ export const EntityPage = ({ entityType }: Props) => {
|
||||
{error && <Alert type="error" message={error?.message || `Failed to fetch privileges for user`} />}
|
||||
{data && !canViewEntityPage && <UnauthorizedPage />}
|
||||
{canViewEntityPage &&
|
||||
((showNewPage && <SearchablePage>{entityRegistry.renderProfile(entityType, urn)}</SearchablePage>) || (
|
||||
<ContainerPage
|
||||
((showNewPage && <>{entityRegistry.renderProfile(entityType, urn)}</>) || (
|
||||
<BrowsableEntityPage
|
||||
isBrowsable={isBrowsable}
|
||||
urn={urn}
|
||||
type={entityType}
|
||||
@ -86,7 +84,7 @@ export const EntityPage = ({ entityType }: Props) => {
|
||||
) : (
|
||||
entityRegistry.renderProfile(entityType, urn)
|
||||
)}
|
||||
</ContainerPage>
|
||||
</BrowsableEntityPage>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
@ -5,7 +5,6 @@ import styled from 'styled-components/macro';
|
||||
|
||||
import { useGetRootGlossaryNodesQuery, useGetRootGlossaryTermsQuery } from '../../graphql/glossary.generated';
|
||||
import TabToolbar from '../entity/shared/components/styled/TabToolbar';
|
||||
import { SearchablePage } from '../search/SearchablePage';
|
||||
import GlossaryEntitiesPath from './GlossaryEntitiesPath';
|
||||
import GlossaryEntitiesList from './GlossaryEntitiesList';
|
||||
import GlossaryBrowser from './GlossaryBrowser/GlossaryBrowser';
|
||||
@ -55,39 +54,37 @@ function BusinessGlossaryPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchablePage>
|
||||
<GlossaryWrapper>
|
||||
<BrowserWrapper width={browserWidth}>
|
||||
<GlossarySearch />
|
||||
<GlossaryBrowser rootNodes={nodes} rootTerms={terms} />
|
||||
</BrowserWrapper>
|
||||
<ProfileSidebarResizer
|
||||
setSidePanelWidth={(width) =>
|
||||
setBrowserWidth(Math.min(Math.max(width, MIN_BROWSWER_WIDTH), MAX_BROWSER_WIDTH))
|
||||
}
|
||||
initialSize={browserWidth}
|
||||
isSidebarOnLeft
|
||||
/>
|
||||
<MainContentWrapper>
|
||||
<GlossaryEntitiesPath />
|
||||
<HeaderWrapper>
|
||||
<Typography.Title level={3}>Glossary</Typography.Title>
|
||||
<div>
|
||||
<Button type="text" onClick={() => setIsCreateTermModalVisible(true)}>
|
||||
<PlusOutlined /> Add Term
|
||||
</Button>
|
||||
<Button type="text" onClick={() => setIsCreateNodeModalVisible(true)}>
|
||||
<PlusOutlined /> Add Term Group
|
||||
</Button>
|
||||
</div>
|
||||
</HeaderWrapper>
|
||||
{hasTermsOrNodes && <GlossaryEntitiesList nodes={nodes || []} terms={terms || []} />}
|
||||
{!hasTermsOrNodes && (
|
||||
<EmptyGlossarySection refetchForTerms={refetchForTerms} refetchForNodes={refetchForNodes} />
|
||||
)}
|
||||
</MainContentWrapper>
|
||||
</GlossaryWrapper>
|
||||
</SearchablePage>
|
||||
<GlossaryWrapper>
|
||||
<BrowserWrapper width={browserWidth}>
|
||||
<GlossarySearch />
|
||||
<GlossaryBrowser rootNodes={nodes} rootTerms={terms} />
|
||||
</BrowserWrapper>
|
||||
<ProfileSidebarResizer
|
||||
setSidePanelWidth={(width) =>
|
||||
setBrowserWidth(Math.min(Math.max(width, MIN_BROWSWER_WIDTH), MAX_BROWSER_WIDTH))
|
||||
}
|
||||
initialSize={browserWidth}
|
||||
isSidebarOnLeft
|
||||
/>
|
||||
<MainContentWrapper>
|
||||
<GlossaryEntitiesPath />
|
||||
<HeaderWrapper>
|
||||
<Typography.Title level={3}>Glossary</Typography.Title>
|
||||
<div>
|
||||
<Button type="text" onClick={() => setIsCreateTermModalVisible(true)}>
|
||||
<PlusOutlined /> Add Term
|
||||
</Button>
|
||||
<Button type="text" onClick={() => setIsCreateNodeModalVisible(true)}>
|
||||
<PlusOutlined /> Add Term Group
|
||||
</Button>
|
||||
</div>
|
||||
</HeaderWrapper>
|
||||
{hasTermsOrNodes && <GlossaryEntitiesList nodes={nodes || []} terms={terms || []} />}
|
||||
{!hasTermsOrNodes && (
|
||||
<EmptyGlossarySection refetchForTerms={refetchForTerms} refetchForNodes={refetchForNodes} />
|
||||
)}
|
||||
</MainContentWrapper>
|
||||
</GlossaryWrapper>
|
||||
{isCreateTermModalVisible && (
|
||||
<CreateGlossaryEntityModal
|
||||
entityType={EntityType.GlossaryTerm}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { Tabs, Typography } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { SearchablePage } from '../search/SearchablePage';
|
||||
import { IngestionSourceList } from './source/IngestionSourceList';
|
||||
import { SecretsList } from './secret/SecretsList';
|
||||
|
||||
@ -51,22 +50,18 @@ export const ManageIngestionPage = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SearchablePage>
|
||||
<PageContainer>
|
||||
<PageHeaderContainer>
|
||||
<PageTitle level={3}>Manage Ingestion</PageTitle>
|
||||
<Typography.Paragraph type="secondary">
|
||||
Create, schedule, and run DataHub ingestion pipelines.
|
||||
</Typography.Paragraph>
|
||||
</PageHeaderContainer>
|
||||
<StyledTabs activeKey={selectedTab} size="large" onTabClick={(tab: string) => onClickTab(tab)}>
|
||||
<Tab key={TabType.Sources} tab={TabType.Sources} />
|
||||
<Tab key={TabType.Secrets} tab={TabType.Secrets} />
|
||||
</StyledTabs>
|
||||
<ListContainer>
|
||||
{selectedTab === TabType.Sources ? <IngestionSourceList /> : <SecretsList />}
|
||||
</ListContainer>
|
||||
</PageContainer>
|
||||
</SearchablePage>
|
||||
<PageContainer>
|
||||
<PageHeaderContainer>
|
||||
<PageTitle level={3}>Manage Ingestion</PageTitle>
|
||||
<Typography.Paragraph type="secondary">
|
||||
Create, schedule, and run DataHub ingestion pipelines.
|
||||
</Typography.Paragraph>
|
||||
</PageHeaderContainer>
|
||||
<StyledTabs activeKey={selectedTab} size="large" onTabClick={(tab: string) => onClickTab(tab)}>
|
||||
<Tab key={TabType.Sources} tab={TabType.Sources} />
|
||||
<Tab key={TabType.Secrets} tab={TabType.Secrets} />
|
||||
</StyledTabs>
|
||||
<ListContainer>{selectedTab === TabType.Sources ? <IngestionSourceList /> : <SecretsList />}</ListContainer>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -2,8 +2,6 @@ import React, { useEffect, useState } from 'react';
|
||||
import * as QueryString from 'query-string';
|
||||
import { useHistory, useLocation, useParams } from 'react-router';
|
||||
import { Alert } from 'antd';
|
||||
|
||||
import { SearchablePage } from './SearchablePage';
|
||||
import { useEntityRegistry } from '../useEntityRegistry';
|
||||
import { FacetFilterInput, EntityType } from '../../types.generated';
|
||||
import useFilters from './utils/useFilters';
|
||||
@ -81,20 +79,6 @@ export const SearchPage = () => {
|
||||
}
|
||||
}, [query, data, loading]);
|
||||
|
||||
const onSearch = (q: string, type?: EntityType) => {
|
||||
if (q.trim().length === 0) {
|
||||
return;
|
||||
}
|
||||
analytics.event({
|
||||
type: EventType.SearchEvent,
|
||||
query: q,
|
||||
entityTypeFilter: activeType,
|
||||
pageNumber: 1,
|
||||
originPath: window.location.pathname,
|
||||
});
|
||||
navigateToSearchUrl({ type: type || activeType, query: q, page: 1, history });
|
||||
};
|
||||
|
||||
const onChangeFilters = (newFilters: Array<FacetFilterInput>) => {
|
||||
navigateToSearchUrl({ type: activeType, query, page: 1, filters: newFilters, history });
|
||||
};
|
||||
@ -104,7 +88,7 @@ export const SearchPage = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SearchablePage initialQuery={query} onSearch={onSearch}>
|
||||
<>
|
||||
{!loading && error && (
|
||||
<Alert type="error" message={error?.message || `Search failed to load for query ${query}`} />
|
||||
)}
|
||||
@ -123,6 +107,6 @@ export const SearchPage = () => {
|
||||
numResultsPerPage={numResultsPerPage}
|
||||
setNumResultsPerPage={setNumResultsPerPage}
|
||||
/>
|
||||
</SearchablePage>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
import { useHistory, useLocation } from 'react-router';
|
||||
import * as QueryString from 'query-string';
|
||||
import { useTheme } from 'styled-components';
|
||||
|
||||
import { SearchHeader } from './SearchHeader';
|
||||
import { useEntityRegistry } from '../useEntityRegistry';
|
||||
import { EntityType } from '../../types.generated';
|
||||
@ -21,13 +21,11 @@ const styles = {
|
||||
};
|
||||
|
||||
interface Props extends React.PropsWithChildren<any> {
|
||||
initialQuery?: string;
|
||||
onSearch?: (query: string, type?: EntityType) => void;
|
||||
onAutoComplete?: (query: string) => void;
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
initialQuery: '',
|
||||
onSearch: undefined,
|
||||
onAutoComplete: undefined,
|
||||
};
|
||||
@ -35,7 +33,11 @@ const defaultProps = {
|
||||
/**
|
||||
* A page that includes a sticky search header (nav bar)
|
||||
*/
|
||||
export const SearchablePage = ({ initialQuery, onSearch, onAutoComplete, children }: Props) => {
|
||||
export const SearchablePage = ({ onSearch, onAutoComplete, children }: Props) => {
|
||||
const location = useLocation();
|
||||
const params = QueryString.parse(location.search, { arrayFormat: 'comma' });
|
||||
const currentQuery: string = decodeURIComponent(params.query ? (params.query as string) : '');
|
||||
|
||||
const history = useHistory();
|
||||
const entityRegistry = useEntityRegistry();
|
||||
const themeConfig = useTheme();
|
||||
@ -75,21 +77,21 @@ export const SearchablePage = ({ initialQuery, onSearch, onAutoComplete, childre
|
||||
|
||||
// Load correct autocomplete results on initial page load.
|
||||
useEffect(() => {
|
||||
if (initialQuery && initialQuery.trim() !== '') {
|
||||
if (currentQuery && currentQuery.trim() !== '') {
|
||||
getAutoCompleteResults({
|
||||
variables: {
|
||||
input: {
|
||||
query: initialQuery,
|
||||
query: currentQuery,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [initialQuery, getAutoCompleteResults]);
|
||||
}, [currentQuery, getAutoCompleteResults]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchHeader
|
||||
initialQuery={initialQuery as string}
|
||||
initialQuery={currentQuery as string}
|
||||
placeholderText={themeConfig.content.search.searchbarMessage}
|
||||
suggestions={
|
||||
(suggestionsData &&
|
||||
|
||||
@ -6,7 +6,6 @@ import styled from 'styled-components';
|
||||
import { ANTD_GRAY } from '../entity/shared/constants';
|
||||
import { ManageIdentities } from '../identity/ManageIdentities';
|
||||
import { ManagePolicies } from '../policy/ManagePolicies';
|
||||
import { SearchablePage } from '../search/SearchablePage';
|
||||
import { useAppConfig } from '../useAppConfig';
|
||||
import { useGetAuthenticatedUser } from '../useGetAuthenticatedUser';
|
||||
import { AccessTokens } from './AccessTokens';
|
||||
@ -78,56 +77,54 @@ export const SettingsPage = () => {
|
||||
const showUsersGroups = (isIdentityManagementEnabled && me && me.platformPrivileges.manageIdentities) || false;
|
||||
|
||||
return (
|
||||
<SearchablePage>
|
||||
<PageContainer>
|
||||
<SettingsBarContainer>
|
||||
<SettingsBarHeader>
|
||||
<PageTitle level={3}>Settings</PageTitle>
|
||||
<Typography.Paragraph type="secondary">Manage your DataHub settings.</Typography.Paragraph>
|
||||
</SettingsBarHeader>
|
||||
<ThinDivider />
|
||||
<Menu
|
||||
selectable={false}
|
||||
mode="inline"
|
||||
style={{ width: 256, marginTop: 8 }}
|
||||
selectedKeys={[activePath]}
|
||||
onClick={(newPath) => {
|
||||
history.push(`${url}/${newPath.key}`);
|
||||
}}
|
||||
>
|
||||
<Menu.ItemGroup title="Developer">
|
||||
<Menu.Item key="tokens">
|
||||
<SafetyCertificateOutlined />
|
||||
<ItemTitle>Access Tokens</ItemTitle>
|
||||
</Menu.Item>
|
||||
<PageContainer>
|
||||
<SettingsBarContainer>
|
||||
<SettingsBarHeader>
|
||||
<PageTitle level={3}>Settings</PageTitle>
|
||||
<Typography.Paragraph type="secondary">Manage your DataHub settings.</Typography.Paragraph>
|
||||
</SettingsBarHeader>
|
||||
<ThinDivider />
|
||||
<Menu
|
||||
selectable={false}
|
||||
mode="inline"
|
||||
style={{ width: 256, marginTop: 8 }}
|
||||
selectedKeys={[activePath]}
|
||||
onClick={(newPath) => {
|
||||
history.push(`${url}/${newPath.key}`);
|
||||
}}
|
||||
>
|
||||
<Menu.ItemGroup title="Developer">
|
||||
<Menu.Item key="tokens">
|
||||
<SafetyCertificateOutlined />
|
||||
<ItemTitle>Access Tokens</ItemTitle>
|
||||
</Menu.Item>
|
||||
</Menu.ItemGroup>
|
||||
{(showPolicies || showUsersGroups) && (
|
||||
<Menu.ItemGroup title="Access">
|
||||
{showPolicies && (
|
||||
<Menu.Item key="identities">
|
||||
<UsergroupAddOutlined />
|
||||
<ItemTitle>Users & Groups</ItemTitle>
|
||||
</Menu.Item>
|
||||
)}
|
||||
{showUsersGroups && (
|
||||
<Menu.Item key="policies">
|
||||
<BankOutlined />
|
||||
<ItemTitle>Privileges</ItemTitle>
|
||||
</Menu.Item>
|
||||
)}
|
||||
</Menu.ItemGroup>
|
||||
{(showPolicies || showUsersGroups) && (
|
||||
<Menu.ItemGroup title="Access">
|
||||
{showPolicies && (
|
||||
<Menu.Item key="identities">
|
||||
<UsergroupAddOutlined />
|
||||
<ItemTitle>Users & Groups</ItemTitle>
|
||||
</Menu.Item>
|
||||
)}
|
||||
{showUsersGroups && (
|
||||
<Menu.Item key="policies">
|
||||
<BankOutlined />
|
||||
<ItemTitle>Privileges</ItemTitle>
|
||||
</Menu.Item>
|
||||
)}
|
||||
</Menu.ItemGroup>
|
||||
)}
|
||||
</Menu>
|
||||
</SettingsBarContainer>
|
||||
<Switch>
|
||||
<Route exact path={path}>
|
||||
<Redirect to={`${pathname}${pathname.endsWith('/') ? '' : '/'}${DEFAULT_PATH.path}`} />
|
||||
</Route>
|
||||
{PATHS.map((p) => (
|
||||
<Route path={`${path}/${p.path.replace('/', '')}`} render={() => p.content} key={p.path} />
|
||||
))}
|
||||
</Switch>
|
||||
</PageContainer>
|
||||
</SearchablePage>
|
||||
)}
|
||||
</Menu>
|
||||
</SettingsBarContainer>
|
||||
<Switch>
|
||||
<Route exact path={path}>
|
||||
<Redirect to={`${pathname}${pathname.endsWith('/') ? '' : '/'}${DEFAULT_PATH.path}`} />
|
||||
</Route>
|
||||
{PATHS.map((p) => (
|
||||
<Route path={`${path}/${p.path.replace('/', '')}`} render={() => p.content} key={p.path} />
|
||||
))}
|
||||
</Switch>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user