diff --git a/src/containers/Clusters/Clusters.scss b/src/containers/Clusters/Clusters.scss index 101fb8f80e..cb73b6ab99 100644 --- a/src/containers/Clusters/Clusters.scss +++ b/src/containers/Clusters/Clusters.scss @@ -80,10 +80,6 @@ color: var(--g-color-text-secondary); } - &__remove-cluster { - color: var(--ydb-color-status-red); - } - &__progress { --g-progress-filled-background-color: var(--ydb-color-status-green); } diff --git a/src/containers/Clusters/columns.tsx b/src/containers/Clusters/columns.tsx index dc980a7f4f..121fd8da2d 100644 --- a/src/containers/Clusters/columns.tsx +++ b/src/containers/Clusters/columns.tsx @@ -79,7 +79,7 @@ function getTitleColumn({isEditClusterAvailable, isDeleteClusterAvailable}: Clus action: () => { onDeleteCluster({clusterData: row}); }, - className: b('remove-cluster'), + theme: 'danger', }); } diff --git a/src/containers/Header/Header.tsx b/src/containers/Header/Header.tsx index e6a4a59394..0bb9f0e616 100644 --- a/src/containers/Header/Header.tsx +++ b/src/containers/Header/Header.tsx @@ -1,23 +1,41 @@ import React from 'react'; -import {ArrowUpRightFromSquare, CirclePlus, PlugConnection} from '@gravity-ui/icons'; -import {Breadcrumbs, Button, Divider, Flex, Icon} from '@gravity-ui/uikit'; -import {useLocation} from 'react-router-dom'; +import { + ArrowUpRightFromSquare, + ChevronDown, + CirclePlus, + Pencil, + PlugConnection, + TrashBin, +} from '@gravity-ui/icons'; +import type {DropdownMenuItem, DropdownMenuProps} from '@gravity-ui/uikit'; +import {Breadcrumbs, Button, Divider, DropdownMenu, Flex, Icon} from '@gravity-ui/uikit'; +import {skipToken} from '@reduxjs/toolkit/query'; +import {useHistory, useLocation} from 'react-router-dom'; import {getConnectToDBDialog} from '../../components/ConnectToDB/ConnectToDBDialog'; import {InternalLink} from '../../components/InternalLink'; -import {useAddClusterFeatureAvailable} from '../../store/reducers/capabilities/hooks'; +import { + useAddClusterFeatureAvailable, + useDeleteDatabaseFeatureAvailable, + useEditDatabaseFeatureAvailable, +} from '../../store/reducers/capabilities/hooks'; import {useClusterBaseInfo} from '../../store/reducers/cluster/cluster'; +import {tenantApi} from '../../store/reducers/tenant/tenant'; import {uiFactory} from '../../uiFactory/uiFactory'; import {cn} from '../../utils/cn'; import {DEVELOPER_UI_TITLE} from '../../utils/constants'; import {createDeveloperUIInternalPageHref} from '../../utils/developerUI/developerUI'; import {useTypedSelector} from '../../utils/hooks'; -import {useDatabaseFromQuery} from '../../utils/hooks/useDatabaseFromQuery'; +import { + useClusterNameFromQuery, + useDatabaseFromQuery, +} from '../../utils/hooks/useDatabaseFromQuery'; import { useIsUserAllowedToMakeChanges, useIsViewerUser, } from '../../utils/hooks/useIsUserAllowedToMakeChanges'; +import {getClusterPath} from '../Cluster/utils'; import {getBreadcrumbs} from './breadcrumbs'; import {headerKeyset} from './i18n'; @@ -35,13 +53,29 @@ function Header() { const {title: clusterTitle} = useClusterBaseInfo(); const database = useDatabaseFromQuery(); + const clusterName = useClusterNameFromQuery(); + const location = useLocation(); + const history = useHistory(); + const isDatabasePage = location.pathname === '/tenant'; const isClustersPage = location.pathname === '/clusters'; const isAddClusterAvailable = useAddClusterFeatureAvailable() && uiFactory.onAddCluster !== undefined; + const isEditDBAvailable = useEditDatabaseFeatureAvailable() && uiFactory.onEditDB !== undefined; + const isDeleteDBAvailable = + useDeleteDatabaseFeatureAvailable() && uiFactory.onDeleteDB !== undefined; + + const shouldRequestTenantData = + database && isDatabasePage && (isEditDBAvailable || isDeleteDBAvailable); + + const params = shouldRequestTenantData ? {path: database, clusterName} : skipToken; + + const {currentData: databaseData, isLoading: isDatabaseDataLoading} = + tenantApi.useGetTenantInfoQuery(params); + const breadcrumbItems = React.useMemo(() => { let options = { ...pageBreadcrumbsOptions, @@ -82,6 +116,57 @@ function Header() { {headerKeyset('connect')} , ); + + const menuItems: DropdownMenuItem[] = []; + + const {onEditDB, onDeleteDB} = uiFactory; + + const isEnoughData = clusterName && databaseData; + + if (isEditDBAvailable && onEditDB && isEnoughData) { + menuItems.push({ + text: headerKeyset('action_edit-db'), + iconStart: , + action: () => { + onEditDB({clusterName, databaseData}); + }, + }); + } + if (isDeleteDBAvailable && onDeleteDB && isEnoughData) { + menuItems.push({ + text: headerKeyset('action_delete-db'), + iconStart: , + action: () => { + onDeleteDB({clusterName, databaseData}).then((isDeleted) => { + if (isDeleted) { + const path = getClusterPath('tenants'); + history.push(path); + } + }); + }, + theme: 'danger', + }); + } + + if (menuItems.length) { + const renderSwitcher: DropdownMenuProps['renderSwitcher'] = (props) => { + return ( + + ); + }; + + elements.push( + , + ); + } } if (!isClustersPage && isUserAllowedToMakeChanges) { diff --git a/src/containers/Header/i18n/en.json b/src/containers/Header/i18n/en.json index a78cae8d83..5f45efdf4a 100644 --- a/src/containers/Header/i18n/en.json +++ b/src/containers/Header/i18n/en.json @@ -9,5 +9,9 @@ "breadcrumbs.storageGroup": "Storage Group", "connect": "Connect", - "add-cluster": "Add Cluster" + "add-cluster": "Add Cluster", + + "action_edit-db": "Edit database", + "action_delete-db": "Delete database", + "action_manage": "Manage" } diff --git a/src/containers/Tenants/Tenants.scss b/src/containers/Tenants/Tenants.scss index 87d9b6bd58..b51d3874e9 100644 --- a/src/containers/Tenants/Tenants.scss +++ b/src/containers/Tenants/Tenants.scss @@ -65,8 +65,4 @@ margin: 0 0 0 auto; } - - &__remove-db { - color: var(--ydb-color-status-red); - } } diff --git a/src/containers/Tenants/Tenants.tsx b/src/containers/Tenants/Tenants.tsx index a4ca80b397..1648234ec6 100644 --- a/src/containers/Tenants/Tenants.tsx +++ b/src/containers/Tenants/Tenants.tsx @@ -374,7 +374,7 @@ function getDBActionsColumn({ databaseData: row, }); }, - className: b('remove-db'), + theme: 'danger', }); } diff --git a/src/store/reducers/tenant/tenant.ts b/src/store/reducers/tenant/tenant.ts index e01b02d51f..44ef0092df 100644 --- a/src/store/reducers/tenant/tenant.ts +++ b/src/store/reducers/tenant/tenant.ts @@ -5,6 +5,7 @@ import {DEFAULT_USER_SETTINGS, settingsManager} from '../../../services/settings import type {TTenantInfo} from '../../../types/api/tenant'; import {TENANT_INITIAL_PAGE_KEY} from '../../../utils/constants'; import {api} from '../api'; +import {prepareTenants} from '../tenants/utils'; import {TENANT_DIAGNOSTICS_TABS_IDS, TENANT_METRICS_TABS_IDS} from './constants'; import {tenantPageSchema} from './types'; @@ -73,7 +74,7 @@ export const tenantApi = api.injectEndpoints({ } else { tenantData = await window.api.viewer.getTenantInfo({path}, {signal}); } - const databases = tenantData.TenantInfo || []; + const databases = prepareTenants(tenantData.TenantInfo || []); // previous meta versions do not support filtering databases by name const data = databases.find((tenant) => tenant.Name === path) ?? databases[0] ?? null;