Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/containers/Header/Header.scss
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,9 @@
display: flex;
align-items: center;
}

&__remove-db {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it may be removed after using theme: 'danger', for menu item

// !important to prevent color from .g-menu__item
color: var(--ydb-color-status-red) !important;
}
}
95 changes: 90 additions & 5 deletions src/containers/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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,
Expand Down Expand Up @@ -82,6 +116,57 @@ function Header() {
{headerKeyset('connect')}
</Button>,
);

const menuItems: DropdownMenuItem[] = [];

const {onEditDB, onDeleteDB} = uiFactory;

const isEnoughtData = clusterName && databaseData;

if (isEditDBAvailable && onEditDB && isEnoughtData) {
Copy link

Copilot AI Aug 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name 'isEnoughtData' contains a spelling error. It should be 'isEnoughData' (missing 'u' in 'enough').

Suggested change
const isEnoughtData = clusterName && databaseData;
if (isEditDBAvailable && onEditDB && isEnoughtData) {
const isEnoughData = clusterName && databaseData;
if (isEditDBAvailable && onEditDB && isEnoughData) {

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Aug 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable 'isEnoughtData' contains a spelling error. It should be 'isEnoughData' (missing 'u' in 'enough').

Suggested change
const isEnoughtData = clusterName && databaseData;
if (isEditDBAvailable && onEditDB && isEnoughtData) {
const isEnoughData = clusterName && databaseData;
if (isEditDBAvailable && onEditDB && isEnoughData) {

Copilot uses AI. Check for mistakes.
menuItems.push({
text: headerKeyset('action_edit-db'),
iconStart: <Pencil />,
action: () => {
onEditDB({clusterName, databaseData});
},
});
}
if (isDeleteDBAvailable && onDeleteDB && isEnoughtData) {
Copy link

Copilot AI Aug 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable 'isEnoughtData' contains a spelling error. It should be 'isEnoughData' (missing 'u' in 'enough').

Suggested change
if (isDeleteDBAvailable && onDeleteDB && isEnoughtData) {
if (isDeleteDBAvailable && onDeleteDB && isEnoughData) {

Copilot uses AI. Check for mistakes.
menuItems.push({
text: headerKeyset('action_delete-db'),
iconStart: <TrashBin />,
action: () => {
onDeleteDB({clusterName, databaseData}).then((isDeleted) => {
if (isDeleted) {
const path = getClusterPath('tenants');
history.push(path);
}
});
},
className: b('remove-db'),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to define className, just use theme: 'danger',

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, there wasn't such option previously, fixed

});
}

if (menuItems.length) {
const renderSwitcher: DropdownMenuProps<unknown>['renderSwitcher'] = (props) => {
return (
<Button {...props} loading={isDatabaseDataLoading} view="flat" size="m">
{headerKeyset('action_manage')}
<Icon data={ChevronDown} />
</Button>
);
};

elements.push(
<DropdownMenu
items={menuItems}
renderSwitcher={renderSwitcher}
menuProps={{size: 'l'}}
popupProps={{placement: 'bottom-end'}}
/>,
);
}
}

if (!isClustersPage && isUserAllowedToMakeChanges) {
Expand Down
6 changes: 5 additions & 1 deletion src/containers/Header/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
3 changes: 2 additions & 1 deletion src/store/reducers/tenant/tenant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down
Loading