Skip to content

feat: 2dc #2699

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
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
12 changes: 12 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# Copilot Instructions

## Internationalization (i18n)

- Do NOT hardcode user-facing strings.
- ALWAYS use the component i18n keysets.
- For key naming, see `i18n-naming-ruleset.md` in the repo root.

# GitHub Copilot Instructions for YDB Embedded UI

> **Note**: This file contains project-specific instructions for GitHub Copilot code review and assistance.
Expand Down Expand Up @@ -82,6 +90,10 @@ const handleInputChange = useCallback(
- Follow key format: `<context>_<content>` (e.g., `action_save`, `field_name`)
- Register keysets with `registerKeysets()` using unique component name

### Display Placeholders (MANDATORY)

- ALWAYS use `EMPTY_DATA_PLACEHOLDER` for empty UI values. Do not hardcode em or en dashes (`—`, `–`) as placeholders. Hyphen `-`/dashes may be used as separators in titles/ranges. Before submitting a PR, grep for `—` and `–` and ensure placeholder usages use `EMPTY_DATA_PLACEHOLDER` from `src/utils/constants.ts`.

### State Management

- Use Redux Toolkit with domain-based organization
Expand Down
17 changes: 2 additions & 15 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,21 +232,7 @@ Uses BEM naming convention with `cn()` utility from `utils/cn`. Create a block f

### Internationalization (i18n)

All user-facing text must be internationalized using the i18n system. Follow the naming rules from `i18n-naming-ruleset.md`:

- **Component Structure**: Each component has an `i18n/` folder with `en.json` and `index.ts`
- **Registration**: Use `registerKeysets()` with a unique component name
- **Key Format**: Follow `<context>_<content>` pattern (e.g., `action_save`, `field_name`, `alert_error`)
- **Context Prefixes**:
- `action_` - buttons, links, menu items
- `field_` - form fields, table columns
- `title_` - page/section titles
- `alert_` - notifications, errors
- `context_` - descriptions, hints
- `confirm_` - confirmation dialogs
- `value_` - status values, options
- **NEVER** use hardcoded strings in UI components
- **ALWAYS** create i18n entries for all user-visible text
See `i18n-naming-ruleset.md` in the repo root for all i18n conventions (naming and usage).

### Performance Considerations

Expand Down Expand Up @@ -297,6 +283,7 @@ const [urlParam, setUrlParam] = useQueryParam('sort', SortOrderParam);
- **NEVER** call APIs directly - use `window.api.module.method()`
- **NEVER** mutate state in RTK Query - return new objects/arrays
- **NEVER** hardcode user-facing strings - use i18n
- **ALWAYS** use `EMPTY_DATA_PLACEHOLDER` for empty UI values. Do not hardcode em dashes `—` or en dashes `–` as placeholders. Hyphen `-` and dashes may be used as separators in titles/ranges. Before submitting, grep the code for `—`/`–` and ensure placeholders use `EMPTY_DATA_PLACEHOLDER` from `src/utils/constants.ts`.
- **ALWAYS** use `cn()` for classNames: `const b = cn('component-name')`
- **ALWAYS** clear errors on user input
- **ALWAYS** handle loading states in UI
Expand Down
17 changes: 2 additions & 15 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,21 +232,7 @@ Uses BEM naming convention with `cn()` utility from `utils/cn`. Create a block f

### Internationalization (i18n)

All user-facing text must be internationalized using the i18n system. Follow the naming rules from `i18n-naming-ruleset.md`:

- **Component Structure**: Each component has an `i18n/` folder with `en.json` and `index.ts`
- **Registration**: Use `registerKeysets()` with a unique component name
- **Key Format**: Follow `<context>_<content>` pattern (e.g., `action_save`, `field_name`, `alert_error`)
- **Context Prefixes**:
- `action_` - buttons, links, menu items
- `field_` - form fields, table columns
- `title_` - page/section titles
- `alert_` - notifications, errors
- `context_` - descriptions, hints
- `confirm_` - confirmation dialogs
- `value_` - status values, options
- **NEVER** use hardcoded strings in UI components
- **ALWAYS** create i18n entries for all user-visible text
See `i18n-naming-ruleset.md` in the repo root for all i18n conventions (naming and usage).

### Performance Considerations

Expand Down Expand Up @@ -297,6 +283,7 @@ const [urlParam, setUrlParam] = useQueryParam('sort', SortOrderParam);
- **NEVER** call APIs directly - use `window.api.module.method()`
- **NEVER** mutate state in RTK Query - return new objects/arrays
- **NEVER** hardcode user-facing strings - use i18n
- **ALWAYS** use `EMPTY_DATA_PLACEHOLDER` for empty UI values. Do not hardcode em dashes `—` or en dashes `–` as placeholders. Hyphen `-` and dashes may be used as separators in titles/ranges. Before submitting, grep the code for `—`/`–` and ensure placeholders use `EMPTY_DATA_PLACEHOLDER` from `src/utils/constants.ts`.
- **ALWAYS** use `cn()` for classNames: `const b = cn('component-name')`
- **ALWAYS** clear errors on user input
- **ALWAYS** handle loading states in UI
Expand Down
3 changes: 2 additions & 1 deletion src/components/NodeHostWrapper/NodeHostWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {getDefaultNodePath} from '../../containers/Node/NodePages';
import type {GetNodeRefFunc, NodeAddress} from '../../types/additionalProps';
import type {TNodeInfo, TSystemStateInfo} from '../../types/api/nodes';
import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants';
import {
createDeveloperUIInternalPageHref,
createDeveloperUILinkWithNodeId,
Expand Down Expand Up @@ -30,7 +31,7 @@ export const NodeHostWrapper = ({
statusForIcon = 'SystemState',
}: NodeHostWrapperProps) => {
if (!node.Host) {
return <span>—</span>;
return EMPTY_DATA_PLACEHOLDER;
}

const status = statusForIcon === 'ConnectStatus' ? node.ConnectStatus : node.SystemState;
Expand Down
10 changes: 10 additions & 0 deletions src/components/nodesColumns/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,16 @@ export function getRackColumn<T extends {Rack?: string}>(): Column<T> {
width: 100,
};
}

export function getPileNameColumn<T extends {PileName?: string}>(): Column<T> {
return {
name: NODES_COLUMNS_IDS.PileName,
header: i18n('field_pile-name'),
align: DataTable.LEFT,
render: ({row}) => row.PileName || EMPTY_DATA_PLACEHOLDER,
width: 100,
};
}
export function getVersionColumn<T extends {Version?: string}>(): Column<T> {
return {
name: NODES_COLUMNS_IDS.Version,
Expand Down
9 changes: 9 additions & 0 deletions src/components/nodesColumns/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const NODES_COLUMNS_IDS = {
Missing: 'Missing',
Tablets: 'Tablets',
PDisks: 'PDisks',
PileName: 'PileName',
} as const;

export type NodesColumnId = ValueOf<typeof NODES_COLUMNS_IDS>;
Expand Down Expand Up @@ -130,6 +131,9 @@ export const NODES_COLUMNS_TITLES = {
get PDisks() {
return i18n('pdisks');
},
get PileName() {
return i18n('field_pile-name');
},
} as const satisfies Record<NodesColumnId, string>;

const NODES_COLUMNS_GROUP_BY_TITLES = {
Expand Down Expand Up @@ -178,6 +182,9 @@ const NODES_COLUMNS_GROUP_BY_TITLES = {
get PingTime() {
return i18n('ping-time');
},
get PileName() {
return i18n('field_pile-name');
},
} as const satisfies Record<NodesGroupByField, string>;

export function getNodesGroupByFieldTitle(groupByField: NodesGroupByField) {
Expand Down Expand Up @@ -213,6 +220,7 @@ export const NODES_COLUMNS_TO_DATA_FIELDS: Record<NodesColumnId, NodesRequiredFi
Missing: ['Missing'],
Tablets: ['Tablets', 'Database'],
PDisks: ['PDisks'],
PileName: ['PileName'],
};

const NODES_COLUMNS_TO_SORT_FIELDS: Record<NodesColumnId, NodesSortValue | undefined> = {
Expand Down Expand Up @@ -242,6 +250,7 @@ const NODES_COLUMNS_TO_SORT_FIELDS: Record<NodesColumnId, NodesSortValue | undef
Missing: 'Missing',
Tablets: undefined,
PDisks: undefined,
PileName: undefined,
};

export function getNodesColumnSortField(columnId?: string) {
Expand Down
4 changes: 1 addition & 3 deletions src/components/nodesColumns/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@
"sessions": "Sessions",
"missing": "Missing",
"pdisks": "PDisks",

"field_memory-used": "Memory used",
"field_memory-limit": "Memory limit",

"field_pile-name": "Pile Name",
"system-state": "System State",
"connect-status": "Connect Status",
"utilization": "Utilization",
Expand All @@ -33,7 +32,6 @@
"ping": "Ping",
"send": "Send",
"receive": "Receive",

"max": "Max",
"min": "Min",
"avg": "Avg",
Expand Down
34 changes: 26 additions & 8 deletions src/containers/Cluster/ClusterInfo/ClusterInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import {InfoViewerSkeleton} from '../../../components/InfoViewerSkeleton/InfoVie
import {LinkWithIcon} from '../../../components/LinkWithIcon/LinkWithIcon';
import type {ClusterGroupsStats} from '../../../store/reducers/cluster/types';
import type {AdditionalClusterProps} from '../../../types/additionalProps';
import type {TClusterInfo} from '../../../types/api/cluster';
import type {TBridgePile, TClusterInfo} from '../../../types/api/cluster';
import type {IResponseError} from '../../../types/api/error';
import {formatNumber} from '../../../utils/dataFormatters/dataFormatters';
import {BridgeInfoTable} from '../ClusterOverview/components/BridgeInfoTable';
import i18n from '../i18n';
import {getTotalStorageGroupsUsed} from '../utils';

Expand All @@ -25,6 +26,7 @@ interface ClusterInfoProps {
error?: IResponseError | string;
additionalClusterProps?: AdditionalClusterProps;
groupStats?: ClusterGroupsStats;
bridgePiles?: TBridgePile[];
}

export const ClusterInfo = ({
Expand All @@ -33,6 +35,7 @@ export const ClusterInfo = ({
error,
additionalClusterProps = {},
groupStats = {},
bridgePiles,
}: ClusterInfoProps) => {
const {info = [], links = []} = additionalClusterProps;

Expand Down Expand Up @@ -96,13 +99,28 @@ export const ClusterInfo = ({
}
return (
<InfoSection>
<Text as="div" variant="subheader-2" className={b('section-title')}>
{i18n('title_storage-groups')}{' '}
<Text variant="subheader-2" color="secondary">
{formatNumber(total)}
</Text>
</Text>
<Flex gap={2}>{stats}</Flex>
<Flex gap={6} width="full">
<Flex direction="column" gap={2}>
<Text as="div" variant="subheader-2" className={b('section-title')}>
{i18n('title_storage-groups')}{' '}
<Text variant="subheader-2" color="secondary">
{formatNumber(total)}
</Text>
</Text>
<Flex gap={2}>{stats}</Flex>
</Flex>
{bridgePiles?.length ? (
<Flex direction="column" gap={2} className={b('bridge-table')}>
<Text as="div" variant="subheader-2" className={b('section-title')}>
{i18n('title_bridge')}{' '}
<Text variant="subheader-2" color="secondary">
{formatNumber(bridgePiles.length)}
</Text>
</Text>
<BridgeInfoTable piles={bridgePiles} />
</Flex>
) : null}
</Flex>
</InfoSection>
);
};
Expand Down
21 changes: 18 additions & 3 deletions src/containers/Cluster/ClusterOverview/ClusterOverview.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import React from 'react';

import {ArrowToggle, Disclosure, Flex, Icon, Text} from '@gravity-ui/uikit';

import {ResponseError} from '../../../components/Errors/ResponseError';
import {useClusterDashboardAvailable} from '../../../store/reducers/capabilities/hooks';
import {
useBridgeModeEnabled,
useClusterDashboardAvailable,
} from '../../../store/reducers/capabilities/hooks';
import type {ClusterGroupsStats} from '../../../store/reducers/cluster/types';
import type {AdditionalClusterProps} from '../../../types/additionalProps';
import {isClusterInfoV2, isClusterInfoV5} from '../../../types/api/cluster';
Expand Down Expand Up @@ -36,6 +41,16 @@ interface ClusterOverviewProps {

export function ClusterOverview(props: ClusterOverviewProps) {
const [expandDashboard, setExpandDashboard] = useSetting<boolean>(EXPAND_CLUSTER_DASHBOARD);
const bridgeModeEnabled = useBridgeModeEnabled();

const bridgePiles = React.useMemo(() => {
if (!bridgeModeEnabled || !isClusterInfoV5(props.cluster)) {
return undefined;
}

const {BridgeInfo} = props.cluster;
return BridgeInfo?.Piles?.length ? BridgeInfo.Piles : undefined;
}, [props.cluster, bridgeModeEnabled]);
if (props.error) {
return <ResponseError error={props.error} className={b('error')} />;
}
Expand Down Expand Up @@ -67,7 +82,7 @@ export function ClusterOverview(props: ClusterOverviewProps) {
)}
</Disclosure.Summary>
<ClusterDashboard {...props} />
<ClusterInfo {...props} />
<ClusterInfo {...props} bridgePiles={bridgePiles} />
</Disclosure>
</Flex>
);
Expand All @@ -93,7 +108,7 @@ function ClusterDoughnuts({cluster, groupStats = {}, loading, collapsed}: Cluste
if (loading) {
return <ClusterDashboardSkeleton collapsed={collapsed} />;
}
const metricsCards = [];
const metricsCards: React.ReactNode[] = [];
if (isClusterInfoV2(cluster)) {
const {CoresUsed, NumberOfCpus, CoresTotal} = cluster;
const total = CoresTotal ?? NumberOfCpus;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@use '../../../../styles/mixins.scss';

.bridge-info-table {
height: 100%;

&__pile-card {
--g-definition-list-item-gap: var(--g-spacing-3);

width: 347px;
padding: var(--g-spacing-3) var(--g-spacing-4);

border-radius: var(--g-border-radius-s);
background-color: var(--g-color-base-generic-ultralight);

@include mixins.body-2-typography();
}

&__status-icon {
&_primary {
color: var(--g-color-text-positive);
}

&:not(&_primary) {
color: var(--g-color-private-white-250);
}
}
}
Loading
Loading