Skip to content

Commit e842216

Browse files
committed
feat: redesign Storage section
1 parent dae15e3 commit e842216

29 files changed

+540
-97
lines changed

.github/copilot-instructions.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# GitHub Copilot Instructions for YDB Embedded UI
2+
3+
> **Note**: This file contains project-specific instructions for GitHub Copilot code review and assistance.
4+
> These instructions are derived from AGENTS.md but formatted specifically for Copilot's consumption.
5+
> When updating project conventions, update both AGENTS.md (for human developers) and this file (for Copilot).
6+
7+
## Project Overview
8+
9+
This is a React-based monitoring and management interface for YDB clusters. The codebase follows specific patterns and conventions that must be maintained.
10+
11+
## Tech Stack Requirements
12+
13+
- Use React 18.3 with TypeScript 5.x
14+
- Use Redux Toolkit 2.x with RTK Query for state management
15+
- Use Gravity UI (@gravity-ui/uikit) 7.x for UI components
16+
- Use React Router v5 (NOT v6) for routing
17+
- Use Monaco Editor 0.52 for code editing features
18+
19+
## Critical Coding Rules
20+
21+
### API Architecture
22+
23+
- NEVER call APIs directly - always use `window.api.module.method()` pattern
24+
- Use RTK Query's `injectEndpoints` pattern for API endpoints
25+
- Wrap `window.api` calls in RTK Query for proper state management
26+
27+
### Component Patterns
28+
29+
- Use BEM naming with `cn()` utility: `const b = cn('component-name')`
30+
- Use `PaginatedTable` component for all data tables
31+
- Tables require: columns, fetchData function, and unique tableName
32+
- Use virtual scrolling for large datasets
33+
34+
### Internationalization (MANDATORY)
35+
36+
- NEVER hardcode user-facing strings
37+
- ALWAYS create i18n entries in component's `i18n/` folder
38+
- Follow key format: `<context>_<content>` (e.g., `action_save`, `field_name`)
39+
- Register keysets with `registerKeysets()` using unique component name
40+
41+
### State Management
42+
43+
- Use Redux Toolkit with domain-based organization
44+
- NEVER mutate state in RTK Query - return new objects/arrays
45+
- Clear errors on user input in forms
46+
- Always handle loading states in UI
47+
48+
### UI Components
49+
50+
- Prefer Gravity UI components over custom implementations
51+
- Use `createToast` for notifications
52+
- Use `ResponseError` component for API errors
53+
- Use `Loader` and `TableSkeleton` for loading states
54+
55+
### Form Handling
56+
57+
- Always use controlled components with validation
58+
- Clear errors on user input
59+
- Validate before submission
60+
- Use Gravity UI form components with error states
61+
62+
### Dialog/Modal Patterns
63+
64+
- Use `@ebay/nice-modal-react` for complex modals
65+
- Use Gravity UI `Dialog` for simple dialogs
66+
- Always include loading states
67+
68+
### Type Conventions
69+
70+
- API types prefixed with 'T' (e.g., `TTenantInfo`, `TClusterInfo`)
71+
- Types located in `src/types/api/` directory
72+
73+
### Performance Requirements
74+
75+
- Use React.memo for expensive renders
76+
- Lazy load Monaco Editor
77+
- Batch API requests when possible
78+
- Use virtual scrolling for tables
79+
80+
### Testing Patterns
81+
82+
- Unit tests colocated in `__test__` directories
83+
- E2E tests use Playwright with page objects pattern
84+
- Use CSS class selectors for E2E element selection
85+
86+
### Navigation (React Router v5)
87+
88+
- Use React Router v5 hooks (`useHistory`, `useParams`)
89+
- Always validate route params exist before use
90+
91+
## Common Utilities
92+
93+
- Formatters: `formatBytes()`, `formatDateTime()` from `src/utils/dataFormatters/`
94+
- Time parsing: utilities in `src/utils/timeParsers/`
95+
- Query utilities: `src/utils/query.ts` for SQL/YQL helpers
96+
97+
## Before Making Changes
98+
99+
- Run `npm run lint` and `npm run typecheck` before committing
100+
- Follow conventional commit message format
101+
- Ensure all user-facing text is internationalized
102+
- Test with a local YDB instance when possible
103+
104+
## Debugging Helpers
105+
106+
- `window.api` - Access API methods in browser console
107+
- `window.ydbEditor` - Monaco editor instance
108+
- Enable request tracing with `DEV_ENABLE_TRACING_FOR_ALL_REQUESTS`

CONTRIBUTING.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ that cannot be merged.
1717
To make a contribution you should submit a pull request. There will probably be discussion about the pull request and,
1818
if any changes are needed, we would love to work with you to get your pull request merged.
1919

20+
## Development Guidelines
21+
22+
When making changes to coding conventions or project patterns:
23+
- Update `AGENTS.md` - This is the primary documentation for human developers
24+
- Update `.github/copilot-instructions.md` - This contains the same information formatted for GitHub Copilot
25+
26+
Both files should be kept in sync to ensure consistent code generation and review by both humans and AI assistants.
27+
2028
## Other questions
2129

2230
If you have any questions, please mail us at info@ydb.tech.

src/components/InfoViewer/InfoViewer.scss

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@use '../../styles/mixins.scss';
2+
13
.info-viewer {
24
--ydb-info-viewer-font-size: var(--g-text-body-2-font-size);
35
--ydb-info-viewer-line-height: var(--g-text-body-2-line-height);
@@ -28,6 +30,10 @@
2830

2931
max-width: 100%;
3032
padding-top: 4px;
33+
34+
&:first-child {
35+
padding-top: 0;
36+
}
3137
}
3238

3339
&__label {
@@ -88,4 +94,18 @@
8894
}
8995
}
9096
}
97+
98+
&_variant_storage {
99+
.info-viewer__title {
100+
margin: 0 0 var(--g-spacing-3);
101+
102+
color: var(--g-color-text-primary);
103+
@include mixins.subheader-1-typography();
104+
}
105+
106+
.info-viewer__label {
107+
color: var(--g-color-text-secondary);
108+
@include mixins.body-1-typography();
109+
}
110+
}
91111
}

src/components/InfoViewer/InfoViewer.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export interface InfoViewerProps {
1616
info?: InfoViewerItem[];
1717
dots?: boolean;
1818
size?: 's';
19+
variant?: 'default' | 'storage';
1920
className?: string;
2021
multilineLabels?: boolean;
2122
renderEmptyState?: (props?: Pick<InfoViewerProps, 'title' | 'size'>) => React.ReactNode;
@@ -28,6 +29,7 @@ export const InfoViewer = ({
2829
info,
2930
dots = true,
3031
size,
32+
variant = 'default',
3133
className,
3234
multilineLabels,
3335
renderEmptyState,
@@ -37,7 +39,7 @@ export const InfoViewer = ({
3739
}
3840

3941
return (
40-
<div className={b({size}, className)}>
42+
<div className={b({size, variant}, className)}>
4143
{title && <div className={b('title')}>{title}</div>}
4244
{info && info.length > 0 ? (
4345
<div className={b('items')}>

src/components/ProgressViewer/ProgressViewer.scss

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,31 @@
109109
font-size: var(--g-text-body-1-font-size);
110110
line-height: 36px;
111111
}
112+
113+
&_size_storage {
114+
position: relative;
115+
116+
width: 400px;
117+
min-width: unset;
118+
height: 10px;
119+
padding: 0;
120+
121+
border-radius: var(--g-border-radius-xs);
122+
background: var(--g-color-private-white-100);
123+
124+
#{$block}__progress-container {
125+
position: relative;
126+
127+
width: 100%;
128+
height: 100%;
129+
130+
border-radius: inherit;
131+
background: inherit;
132+
}
133+
134+
#{$block}__line {
135+
border-radius: var(--g-border-radius-xs);
136+
background-color: var(--g-color-private-green-450);
137+
}
138+
}
112139
}

src/components/ProgressViewer/ProgressViewer.tsx

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
import {useTheme} from '@gravity-ui/uikit';
1+
import {Flex, Text, useTheme} from '@gravity-ui/uikit';
22

33
import {cn} from '../../utils/cn';
44
import {formatNumber, roundToPrecision} from '../../utils/dataFormatters/dataFormatters';
55
import {calculateProgressStatus} from '../../utils/progress';
66
import {isNumeric} from '../../utils/utils';
77

8+
import i18n from './i18n';
9+
810
import './ProgressViewer.scss';
911

1012
const b = cn('progress-viewer');
1113

12-
type ProgressViewerSize = 'xs' | 's' | 'ns' | 'm' | 'n' | 'l' | 'head';
14+
type ProgressViewerSize = 'xs' | 's' | 'ns' | 'm' | 'n' | 'l' | 'head' | 'storage';
1315

1416
export type FormatProgressViewerValues = (
1517
value?: number,
@@ -107,7 +109,29 @@ export function ProgressViewer({
107109
return valueText;
108110
};
109111

112+
const isStorageVariant = size === 'storage';
113+
110114
if (isNumeric(value)) {
115+
if (isStorageVariant) {
116+
const storageDisplayText =
117+
isNumeric(capacity) && !hideCapacity
118+
? i18n('value_of_capacity', {value: valueText, capacity: capacityText})
119+
: valueText;
120+
121+
return (
122+
<Flex alignItems="center" gap="2" className={className}>
123+
<div className={b({size, theme, status})}>
124+
<div className={b('progress-container')}>
125+
<div className={b('line')} style={lineStyle}></div>
126+
</div>
127+
</div>
128+
<Text variant="body-2" color="secondary">
129+
{storageDisplayText}
130+
</Text>
131+
</Flex>
132+
);
133+
}
134+
111135
return (
112136
<div className={b({size, theme, status}, className)}>
113137
<div className={b('line')} style={lineStyle}></div>
@@ -116,5 +140,5 @@ export function ProgressViewer({
116140
);
117141
}
118142

119-
return <div className={`${b({size})} ${className} error`}>no data</div>;
143+
return <div className={`${b({size})} ${className} error`}>{i18n('no-data')}</div>;
120144
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"value_of_capacity": "{{value}} of {{capacity}}",
3+
"no-data": "no data"
4+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import {registerKeysets} from '../../../utils/i18n';
2+
3+
import en from './en.json';
4+
5+
const COMPONENT = 'ydb-progress-viewer';
6+
7+
export default registerKeysets(COMPONENT, {en});

src/containers/Tenant/Diagnostics/TenantOverview/MetricsCards/MetricsCards.scss renamed to src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.scss

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,20 +75,20 @@
7575
*/
7676

7777
@mixin tab-edge-filler($side) {
78-
&.tenant-metrics-cards__link-container_active::after {
78+
&.tenant-metrics-tabs__link-container_active::after {
7979
@include pseudo-active-filler($side);
8080
}
8181

82-
&:not(.tenant-metrics-cards__link-container_active)::after {
82+
&:not(.tenant-metrics-tabs__link-container_active)::after {
8383
@include pseudo-inactive-filler($side);
8484
}
8585

86-
&:not(.tenant-metrics-cards__link-container_active)::before {
86+
&:not(.tenant-metrics-tabs__link-container_active)::before {
8787
@include pseudo-background-fill($side);
8888
}
8989
}
9090

91-
.tenant-metrics-cards {
91+
.tenant-metrics-tabs {
9292
// CSS Variables for consistent design system
9393
--tab-border-width: 1px;
9494
--tab-filler-size: 10px;
@@ -198,7 +198,7 @@
198198
}
199199
}
200200

201-
.tenant-metrics-cards__link {
201+
.tenant-metrics-tabs__link {
202202
padding-bottom: var(--tab-border-compensation);
203203
@include tab-border-base(var(--g-color-line-generic));
204204
}

src/containers/Tenant/Diagnostics/TenantOverview/MetricsCards/MetricsCards.tsx renamed to src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,25 @@ import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
2121
import {TabCard} from '../TabCard/TabCard';
2222
import i18n from '../i18n';
2323

24-
import './MetricsCards.scss';
24+
import './MetricsTabs.scss';
2525

26-
const b = cn('tenant-metrics-cards');
26+
const b = cn('tenant-metrics-tabs');
2727

28-
interface MetricsCardsProps {
28+
interface MetricsTabsProps {
2929
poolsCpuStats?: TenantPoolsStats[];
3030
memoryStats?: TenantMetricStats[];
3131
blobStorageStats?: TenantStorageStats[];
3232
tabletStorageStats?: TenantStorageStats[];
3333
networkStats?: TenantMetricStats[];
3434
}
3535

36-
export function MetricsCards({
36+
export function MetricsTabs({
3737
poolsCpuStats,
3838
memoryStats,
3939
blobStorageStats,
4040
tabletStorageStats,
4141
networkStats,
42-
}: MetricsCardsProps) {
42+
}: MetricsTabsProps) {
4343
const location = useLocation();
4444
const {metricsTab} = useTypedSelector((state) => state.tenant);
4545
const queryParams = parseQuery(location);

0 commit comments

Comments
 (0)