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;