From ca9986a4cdff3f65fe5b442441d18ab6e7851f41 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Thu, 4 Sep 2025 16:15:13 +0300 Subject: [PATCH 01/13] feat: serverless database view --- .../Tenant/Diagnostics/Diagnostics.tsx | 7 +- .../Tenant/Diagnostics/DiagnosticsPages.ts | 85 +++++++---- .../MetricsTabs/MetricsTabs.scss | 12 ++ .../MetricsTabs/MetricsTabs.tsx | 137 ++++++++++++------ .../TenantOverview/TabCard/TabCard.tsx | 40 ++++- .../TenantOverview/TenantCpu/TenantCpu.tsx | 41 ++++-- .../TenantOverview/TenantOverview.scss | 14 ++ .../TenantOverview/TenantOverview.tsx | 34 ++++- .../TenantStorage/TenantStorage.tsx | 19 ++- .../Diagnostics/TenantOverview/i18n/en.json | 6 +- src/store/reducers/tenant/tenant.ts | 4 +- 11 files changed, 306 insertions(+), 93 deletions(-) diff --git a/src/containers/Tenant/Diagnostics/Diagnostics.tsx b/src/containers/Tenant/Diagnostics/Diagnostics.tsx index a7ea19eaa7..bb1128cfe2 100644 --- a/src/containers/Tenant/Diagnostics/Diagnostics.tsx +++ b/src/containers/Tenant/Diagnostics/Diagnostics.tsx @@ -60,7 +60,7 @@ function Diagnostics(props: DiagnosticsProps) { const tenantName = isDatabaseEntityType(type) ? path : database; - const {controlPlane} = useTenantBaseInfo(isDatabaseEntityType(type) ? path : ''); + const {controlPlane, databaseType} = useTenantBaseInfo(isDatabaseEntityType(type) ? path : ''); const hasFeatureFlags = useFeatureFlagsAvailable(); const hasTopicData = useTopicDataAvailable(); @@ -72,6 +72,7 @@ function Diagnostics(props: DiagnosticsProps) { hasBackups: typeof uiFactory.renderBackups === 'function' && Boolean(controlPlane), hasConfigs: isViewerUser, hasAccess: uiFactory.hasAccess, + isServerless: databaseType === 'Serverless', }); let activeTab = pages.find((el) => el.id === diagnosticsTab); if (!activeTab) { @@ -187,10 +188,10 @@ function Diagnostics(props: DiagnosticsProps) { {pages.map(({id, title}) => { - const path = getDiagnosticsPageLink(id); + const linkPath = getDiagnosticsPageLink(id); return ( - + {title} diff --git a/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts b/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts index a27170502f..bcb1ba064f 100644 --- a/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts +++ b/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts @@ -14,6 +14,16 @@ type Page = { title: string; }; +interface GetPagesOptions { + hasFeatureFlags?: boolean; + hasTopicData?: boolean; + isTopLevel?: boolean; + hasBackups?: boolean; + hasConfigs?: boolean; + hasAccess?: boolean; + isServerless?: boolean; +} + const overview = { id: TENANT_DIAGNOSTICS_TABS_IDS.overview, title: 'Info', @@ -118,6 +128,16 @@ const DATABASE_PAGES = [ backups, ]; +const SERVERLESS_DATABASE_PAGES = [ + overview, + topQueries, + topShards, + tablets, + describe, + configs, + operations, +]; + const TABLE_PAGES = [overview, schema, topShards, nodes, graph, tablets, hotKeys, describe, access]; const COLUMN_TABLE_PAGES = [overview, schema, topShards, nodes, tablets, describe, access]; @@ -168,41 +188,52 @@ const pathSubTypeToPages: Record = { [EPathSubType.EPathSubTypeEmpty]: undefined, }; -export const getPagesByType = ( - type?: EPathType, - subType?: EPathSubType, - options?: { - hasFeatureFlags?: boolean; - hasTopicData?: boolean; - isTopLevel?: boolean; - hasBackups?: boolean; - hasConfigs?: boolean; - hasAccess?: boolean; - }, -) => { +function computeInitialPages(type?: EPathType, subType?: EPathSubType) { const subTypePages = subType ? pathSubTypeToPages[subType] : undefined; const typePages = type ? pathTypeToPages[type] : undefined; - let pages = subTypePages || typePages || DIR_PAGES; + return subTypePages || typePages || DIR_PAGES; +} + +function getDatabasePages(isServerless?: boolean) { + return isServerless ? SERVERLESS_DATABASE_PAGES : DATABASE_PAGES; +} + +function applyFilters(pages: Page[], type?: EPathType, options: GetPagesOptions = {}) { + let result = pages; - if (isTopicEntityType(type) && !options?.hasTopicData) { - pages = pages?.filter((item) => item.id !== TENANT_DIAGNOSTICS_TABS_IDS.topicData); + if (isTopicEntityType(type) && !options.hasTopicData) { + result = result.filter((p) => p.id !== TENANT_DIAGNOSTICS_TABS_IDS.topicData); } - if (isDatabaseEntityType(type) || options?.isTopLevel) { - pages = DATABASE_PAGES; - if (!options?.hasFeatureFlags) { - pages = pages.filter((item) => item.id !== TENANT_DIAGNOSTICS_TABS_IDS.configs); - } + + const removals: TenantDiagnosticsTab[] = []; + if (!options.hasBackups) { + removals.push(TENANT_DIAGNOSTICS_TABS_IDS.backups); } - if (!options?.hasBackups) { - pages = pages.filter((item) => item.id !== TENANT_DIAGNOSTICS_TABS_IDS.backups); + if (!options.hasConfigs) { + removals.push(TENANT_DIAGNOSTICS_TABS_IDS.configs); } - if (!options?.hasConfigs) { - pages = pages.filter((item) => item.id !== TENANT_DIAGNOSTICS_TABS_IDS.configs); + if (!options.hasAccess) { + removals.push(TENANT_DIAGNOSTICS_TABS_IDS.access); } - if (!options?.hasAccess) { - pages = pages.filter((item) => item.id !== TENANT_DIAGNOSTICS_TABS_IDS.access); + + return result.filter((p) => !removals.includes(p.id)); +} + +export const getPagesByType = ( + type?: EPathType, + subType?: EPathSubType, + options?: GetPagesOptions, +) => { + const base = computeInitialPages(type, subType); + const dbContext = isDatabaseEntityType(type) || options?.isTopLevel; + const seeded = dbContext ? getDatabasePages(options?.isServerless) : base; + + let withFlags = seeded; + if (!options?.hasFeatureFlags) { + withFlags = seeded.filter((p) => p.id !== TENANT_DIAGNOSTICS_TABS_IDS.configs); } - return pages; + + return applyFilters(withFlags, type, options); }; export const useDiagnosticsPageLinkGetter = () => { diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.scss b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.scss index fe6b55b43c..f3b2ff6144 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.scss +++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.scss @@ -224,3 +224,15 @@ padding-right: 0; } } + +// Serverless variant: exactly two tabs (CPU, Storage) at 50% width each + +.tenant-metrics-tabs_serverless { + .tenant-metrics-tabs__link-container_placeholder { + pointer-events: none; + + .tenant-tab-card__card-container { + opacity: 0; + } + } +} diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx index 0cedc6b33d..9a85526bc6 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx @@ -33,6 +33,8 @@ interface MetricsTabsProps { tabletStorageStats?: TenantStorageStats[]; networkStats?: TenantMetricStats[]; storageGroupsCount?: number; + isServerless?: boolean; + activeTab?: TenantMetricsTab; } export function MetricsTabs({ @@ -42,6 +44,8 @@ export function MetricsTabs({ tabletStorageStats, networkStats, storageGroupsCount, + isServerless, + activeTab, }: MetricsTabsProps) { const location = useLocation(); const {metricsTab} = useTypedSelector((state) => state.tenant); @@ -83,11 +87,13 @@ export function MetricsTabs({ const [showNetworkUtilization] = useSetting(SHOW_NETWORK_UTILIZATION); const networkMetrics = networkStats ? calculateMetricAggregates(networkStats) : null; + const active = activeTab ?? metricsTab; + return ( - +
@@ -96,64 +102,109 @@ export function MetricsTabs({ value={cpuMetrics.totalUsed} limit={cpuMetrics.totalLimit} legendFormatter={formatCoresLegend} - active={metricsTab === TENANT_METRICS_TABS_IDS.cpu} + active={active === TENANT_METRICS_TABS_IDS.cpu} helpText={i18n('context_cpu-description')} + variant={isServerless ? 'serverless' : 'default'} + subtitle={isServerless ? i18n('serverless.autoscaled') : undefined} />
-
- - - -
- {showNetworkUtilization && networkStats && networkMetrics && ( -
- - - -
+ {!isServerless && ( + <> +
+ + + +
+ {showNetworkUtilization && networkStats && networkMetrics && ( +
+ + + +
+ )} + + )} + + {isServerless && ( + <> +
+
+ +
+
+
+
+ +
+
+ )}
); diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TabCard/TabCard.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TabCard/TabCard.tsx index 6c5f138bc2..4af3bedb9c 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TabCard/TabCard.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TabCard/TabCard.tsx @@ -15,15 +15,53 @@ interface TabCardProps { active?: boolean; helpText?: string; legendFormatter: (params: {value: number; capacity: number}) => string; + variant?: 'default' | 'serverless'; + subtitle?: string; } -export function TabCard({text, value, limit, active, helpText, legendFormatter}: TabCardProps) { +export function TabCard({ + text, + value, + limit, + active, + helpText, + legendFormatter, + variant = 'default', + subtitle, +}: TabCardProps) { const {status, percents, legend, fill} = getDiagramValues({ value, capacity: limit, legendFormatter, }); + if (variant === 'serverless') { + return ( +
+ +
+ + {text} + + {subtitle ? ( + + {subtitle} + + ) : null} +
+
+
+ ); + } + return (
- - - - - - - + {!isServerless && ( + <> + + + + + + + + + )} diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss index 1256fafc68..15b249fe4d 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss @@ -24,6 +24,20 @@ line-height: 24px; } + &__serverless-tag { + margin-left: var(--g-spacing-2); + padding: 2px var(--g-spacing-2); + + border: 1px solid var(--g-color-line-generic); + border-radius: var(--g-border-radius-s); + } + + &__serverless-tag-label { + display: flex; + align-items: center; + gap: var(--g-spacing-1); + } + &__info { position: sticky; left: 0; diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx index 1638a3033d..45611ee32e 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx @@ -1,6 +1,7 @@ import {Button, Flex, Icon} from '@gravity-ui/uikit'; import {EntityStatus} from '../../../../components/EntityStatus/EntityStatus'; +import {LabelWithPopover} from '../../../../components/LabelWithPopover/LabelWithPopover'; import {LoaderWrapper} from '../../../../components/LoaderWrapper/LoaderWrapper'; import {QueriesActivityBar} from '../../../../components/QueriesActivityBar/QueriesActivityBar'; import {useDatabasesAvailable} from '../../../../store/reducers/capabilities/hooks'; @@ -21,6 +22,7 @@ import {TenantCpu} from './TenantCpu/TenantCpu'; import {TenantMemory} from './TenantMemory/TenantMemory'; import {TenantNetwork} from './TenantNetwork/TenantNetwork'; import {TenantStorage} from './TenantStorage/TenantStorage'; +import i18n from './i18n'; import {b} from './utils'; import './TenantOverview.scss'; @@ -48,6 +50,13 @@ export function TenantOverview({ ); const tenantLoading = isFetching && tenant === undefined; const {Name, Type, Overall} = tenant || {}; + const isServerless = Type === 'Serverless'; + const effectiveMetricsTab = + isServerless && + metricsTab !== TENANT_METRICS_TABS_IDS.cpu && + metricsTab !== TENANT_METRICS_TABS_IDS.storage + ? TENANT_METRICS_TABS_IDS.cpu + : metricsTab; const tenantType = mapDatabaseTypeToDBName(Type); // FIXME: remove after correct data is added to tenantInfo @@ -111,22 +120,38 @@ export function TenantOverview({ hasClipboardButton={Boolean(tenant)} clipboardButtonAlwaysVisible /> + {isServerless ? ( +
+ +
+ ) : null} ); }; const renderTabContent = () => { - switch (metricsTab) { + switch (effectiveMetricsTab) { case TENANT_METRICS_TABS_IDS.cpu: { return ( ); } case TENANT_METRICS_TABS_IDS.storage: { - return ; + return ( + + ); } case TENANT_METRICS_TABS_IDS.memory: { return ( @@ -146,6 +171,9 @@ export function TenantOverview({ /> ); } + default: { + return null; + } } }; @@ -186,6 +214,8 @@ export function TenantOverview({ ? Number(tenantData.StorageGroups) : undefined } + isServerless={isServerless} + activeTab={effectiveMetricsTab} />
diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx index 4df7995609..bbee827bcd 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx @@ -25,9 +25,10 @@ export interface TenantStorageMetrics { interface TenantStorageProps { tenantName: string; metrics: TenantStorageMetrics; + mode?: 'regular' | 'serverless'; } -export function TenantStorage({tenantName, metrics}: TenantStorageProps) { +export function TenantStorage({tenantName, metrics, mode = 'regular'}: TenantStorageProps) { const {blobStorageUsed, tabletStorageUsed, blobStorageLimit, tabletStorageLimit} = metrics; const query = useSearchQuery(); @@ -66,6 +67,22 @@ export function TenantStorage({tenantName, metrics}: TenantStorageProps) { }, ]; + if (mode === 'serverless') { + return ( + + + + + + ); + } + return ( diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json b/src/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json index 44fe1650f5..4a376dbc9e 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json +++ b/src/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json @@ -37,5 +37,9 @@ "field_memory-usage": "Memory usage", "context_capacity-usage": "{{value}} of {{capacity}}", "network-stats-unavailable.title": "Network Statistics Unavailable", - "network-stats-unavailable.description": "Network ping and clock skew statistics require a newer backend version. Please upgrade your YDB installation to access these features." + "network-stats-unavailable.description": "Network ping and clock skew statistics require a newer backend version. Please upgrade your YDB installation to access these features.", + "serverless.label": "Serverless", + "serverless.tooltip": "Some metrics are hidden in Serverless mode — resources scale automatically based on workload", + "serverless.autoscaled": "Auto-Scaled Resources", + "serverless.storage-subtitle": "{{legend}} | {{groups}} groups" } diff --git a/src/store/reducers/tenant/tenant.ts b/src/store/reducers/tenant/tenant.ts index 2da7ace466..abf18a72fe 100644 --- a/src/store/reducers/tenant/tenant.ts +++ b/src/store/reducers/tenant/tenant.ts @@ -135,10 +135,12 @@ export function useTenantBaseInfo(path: string) { {skip: !path}, ); - const {ControlPlane, Name} = currentData || {}; + const {ControlPlane, Name, Id, Type} = currentData || {}; return { controlPlane: ControlPlane, name: Name, + id: Id, + databaseType: Type, }; } From 5947cade23b0b62c6ec9ac6a120e9049fb704387 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Thu, 4 Sep 2025 17:01:38 +0300 Subject: [PATCH 02/13] fix: some review fixes --- .../Tenant/Diagnostics/Diagnostics.tsx | 3 +- .../MetricsTabs/MetricsTabs.tsx | 46 ++++++++++++------- .../Tenant/Diagnostics/i18n/en.json | 3 ++ .../Tenant/Diagnostics/i18n/index.ts | 7 +++ src/utils/constants.ts | 1 + 5 files changed, 43 insertions(+), 17 deletions(-) create mode 100644 src/containers/Tenant/Diagnostics/i18n/en.json create mode 100644 src/containers/Tenant/Diagnostics/i18n/index.ts diff --git a/src/containers/Tenant/Diagnostics/Diagnostics.tsx b/src/containers/Tenant/Diagnostics/Diagnostics.tsx index bb1128cfe2..d959964502 100644 --- a/src/containers/Tenant/Diagnostics/Diagnostics.tsx +++ b/src/containers/Tenant/Diagnostics/Diagnostics.tsx @@ -38,6 +38,7 @@ import {Partitions} from './Partitions/Partitions'; import {TopQueries} from './TopQueries'; import {TopShards} from './TopShards'; import {TopicData} from './TopicData/TopicData'; +import i18n from './i18n'; import './Diagnostics.scss'; @@ -177,7 +178,7 @@ function Diagnostics(props: DiagnosticsProps) { }); } default: { - return
No data...
; + return
{i18n('no-data')}
; } } }; diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx index 9a85526bc6..4765610b7a 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx @@ -1,3 +1,5 @@ +import {useMemo} from 'react'; + import {Flex} from '@gravity-ui/uikit'; import {Link, useLocation} from 'react-router-dom'; @@ -10,7 +12,7 @@ import type { TenantStorageStats, } from '../../../../../store/reducers/tenants/utils'; import {cn} from '../../../../../utils/cn'; -import {SHOW_NETWORK_UTILIZATION} from '../../../../../utils/constants'; +import {NON_BREAKING_SPACE, SHOW_NETWORK_UTILIZATION} from '../../../../../utils/constants'; import {useSetting, useTypedSelector} from '../../../../../utils/hooks'; import {calculateMetricAggregates} from '../../../../../utils/metrics'; import { @@ -26,6 +28,10 @@ import './MetricsTabs.scss'; const b = cn('tenant-metrics-tabs'); +// Placeholder values used for serverless layout filler cards +const PLACEHOLDER_VALUE = 0; +const PLACEHOLDER_LIMIT = 1; + interface MetricsTabsProps { poolsCpuStats?: TenantPoolsStats[]; memoryStats?: TenantMetricStats[]; @@ -71,21 +77,29 @@ export function MetricsTabs({ }; // Use only pools that directly indicate resources available to perform user queries - const cpuPools = (poolsCpuStats || []).filter( - (pool) => !(pool.name === 'Batch' || pool.name === 'IO'), + const cpuPools = useMemo( + () => + (poolsCpuStats || []).filter((pool) => !(pool.name === 'Batch' || pool.name === 'IO')), + [poolsCpuStats], ); - const cpuMetrics = calculateMetricAggregates(cpuPools); + const cpuMetrics = useMemo(() => calculateMetricAggregates(cpuPools), [cpuPools]); // Calculate storage metrics using utility - const storageStats = tabletStorageStats || blobStorageStats || []; - const storageMetrics = calculateMetricAggregates(storageStats); + const storageStats = useMemo( + () => tabletStorageStats || blobStorageStats || [], + [tabletStorageStats, blobStorageStats], + ); + const storageMetrics = useMemo(() => calculateMetricAggregates(storageStats), [storageStats]); // Calculate memory metrics using utility - const memoryMetrics = calculateMetricAggregates(memoryStats); + const memoryMetrics = useMemo(() => calculateMetricAggregates(memoryStats), [memoryStats]); // Calculate network metrics using utility const [showNetworkUtilization] = useSetting(SHOW_NETWORK_UTILIZATION); - const networkMetrics = networkStats ? calculateMetricAggregates(networkStats) : null; + const networkMetrics = useMemo( + () => (networkStats ? calculateMetricAggregates(networkStats) : null), + [networkStats], + ); const active = activeTab ?? metricsTab; @@ -181,26 +195,26 @@ export function MetricsTabs({
diff --git a/src/containers/Tenant/Diagnostics/i18n/en.json b/src/containers/Tenant/Diagnostics/i18n/en.json new file mode 100644 index 0000000000..839fe16e58 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/i18n/en.json @@ -0,0 +1,3 @@ +{ + "no-data": "No data" +} diff --git a/src/containers/Tenant/Diagnostics/i18n/index.ts b/src/containers/Tenant/Diagnostics/i18n/index.ts new file mode 100644 index 0000000000..dfddf69419 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/i18n/index.ts @@ -0,0 +1,7 @@ +import {registerKeysets} from '../../../../utils/i18n'; + +import en from './en.json'; + +const COMPONENT = 'ydb-diagnostics'; + +export default registerKeysets(COMPONENT, {en}); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index c4ae821be0..ab3a8dec61 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -100,6 +100,7 @@ export const SECTION_IDS = { export const TENANT_OVERVIEW_TABLES_LIMIT = 3; export const EMPTY_DATA_PLACEHOLDER = '—'; +export const NON_BREAKING_SPACE = '\u00A0'; export const QUERY_TECHNICAL_MARK = '/*UI-QUERY-EXCLUDE*/'; From f0b8ec972d70a077cb0c7278a7a1fdd4b7250a45 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Thu, 4 Sep 2025 18:24:34 +0300 Subject: [PATCH 03/13] fix: review fixes --- .../MetricsTabs/MetricsTabs.scss | 12 ------ .../MetricsTabs/MetricsTabs.tsx | 39 ++--------------- .../ServerlessPlaceholderTabs.scss | 7 +++ .../MetricsTabs/ServerlessPlaceholderTabs.tsx | 43 +++++++++++++++++++ 4 files changed, 54 insertions(+), 47 deletions(-) create mode 100644 src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.scss create mode 100644 src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.tsx diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.scss b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.scss index f3b2ff6144..fe6b55b43c 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.scss +++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.scss @@ -224,15 +224,3 @@ padding-right: 0; } } - -// Serverless variant: exactly two tabs (CPU, Storage) at 50% width each - -.tenant-metrics-tabs_serverless { - .tenant-metrics-tabs__link-container_placeholder { - pointer-events: none; - - .tenant-tab-card__card-container { - opacity: 0; - } - } -} diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx index 4765610b7a..78f66efd98 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx @@ -12,7 +12,7 @@ import type { TenantStorageStats, } from '../../../../../store/reducers/tenants/utils'; import {cn} from '../../../../../utils/cn'; -import {NON_BREAKING_SPACE, SHOW_NETWORK_UTILIZATION} from '../../../../../utils/constants'; +import {SHOW_NETWORK_UTILIZATION} from '../../../../../utils/constants'; import {useSetting, useTypedSelector} from '../../../../../utils/hooks'; import {calculateMetricAggregates} from '../../../../../utils/metrics'; import { @@ -24,14 +24,12 @@ import {TenantTabsGroups, getTenantPath} from '../../../TenantPages'; import {TabCard} from '../TabCard/TabCard'; import i18n from '../i18n'; +import {ServerlessPlaceholderTabs} from './ServerlessPlaceholderTabs'; + import './MetricsTabs.scss'; const b = cn('tenant-metrics-tabs'); -// Placeholder values used for serverless layout filler cards -const PLACEHOLDER_VALUE = 0; -const PLACEHOLDER_LIMIT = 1; - interface MetricsTabsProps { poolsCpuStats?: TenantPoolsStats[]; memoryStats?: TenantMetricStats[]; @@ -190,36 +188,7 @@ export function MetricsTabs({ )} - {isServerless && ( - <> -
-
- -
-
-
-
- -
-
- - )} + {isServerless && }
); } diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.scss b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.scss new file mode 100644 index 0000000000..ef9c9a7257 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.scss @@ -0,0 +1,7 @@ +.tenant-metrics-tabs_serverless { + pointer-events: none; + + .tenant-tab-card__card-container { + opacity: 0; + } +} diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.tsx new file mode 100644 index 0000000000..c879b5c8e9 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.tsx @@ -0,0 +1,43 @@ +import React from 'react'; + +import {cn} from '../../../../../utils/cn'; +import {NON_BREAKING_SPACE} from '../../../../../utils/constants'; +import {formatCoresLegend} from '../../../../../utils/metrics/formatMetricLegend'; +import {TabCard} from '../TabCard/TabCard'; + +import './ServerlessPlaceholderTabs.scss'; + +const b = cn('tenant-metrics-tabs'); + +const PLACEHOLDER_VALUE = 0; +const PLACEHOLDER_LIMIT = 1; + +interface ServerlessPlaceholderTabsProps { + count?: number; +} + +export const ServerlessPlaceholderTabs: React.FC = React.memo( + ({count = 2}) => { + const items = React.useMemo(() => Array.from({length: count}, (_, i) => i), [count]); + + return ( + <> + {items.map((idx) => ( +
+
+ +
+
+ ))} + + ); + }, +); From a336c9d9caf9f60cf41354b33d4b10d27e19b047 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Thu, 4 Sep 2025 19:27:01 +0300 Subject: [PATCH 04/13] fix: code --- .../TenantOverview/MetricsTabs/MetricsTabs.tsx | 5 +++-- .../MetricsTabs/ServerlessPlaceholderTabs.scss | 8 +++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx index 78f66efd98..8500e5f7c2 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx @@ -100,6 +100,7 @@ export function MetricsTabs({ ); const active = activeTab ?? metricsTab; + const cardVariant = isServerless ? 'serverless' : 'default'; return ( @@ -116,7 +117,7 @@ export function MetricsTabs({ legendFormatter={formatCoresLegend} active={active === TENANT_METRICS_TABS_IDS.cpu} helpText={i18n('context_cpu-description')} - variant={isServerless ? 'serverless' : 'default'} + variant={cardVariant} subtitle={isServerless ? i18n('serverless.autoscaled') : undefined} /> @@ -134,7 +135,7 @@ export function MetricsTabs({ legendFormatter={formatStorageLegend} active={active === TENANT_METRICS_TABS_IDS.storage} helpText={i18n('context_storage-description')} - variant={isServerless ? 'serverless' : 'default'} + variant={cardVariant} subtitle={ isServerless && storageMetrics.totalLimit ? i18n('serverless.storage-subtitle', { diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.scss b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.scss index ef9c9a7257..000a3b3c1b 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.scss +++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.scss @@ -1,7 +1,9 @@ .tenant-metrics-tabs_serverless { - pointer-events: none; + .tenant-metrics-tabs__link-container_placeholder { + pointer-events: none; - .tenant-tab-card__card-container { - opacity: 0; + .tenant-tab-card__card-container { + opacity: 0; + } } } From 9c92d12e1377239a80770aab2378e0b290329215 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Thu, 4 Sep 2025 19:44:28 +0300 Subject: [PATCH 05/13] fix: better code --- .../MetricsTabs/MetricsTabs.tsx | 22 +++++++++---------- .../TenantOverview/TenantOverview.tsx | 6 ++--- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx index 8500e5f7c2..0ba8d0475b 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx @@ -13,7 +13,7 @@ import type { } from '../../../../../store/reducers/tenants/utils'; import {cn} from '../../../../../utils/cn'; import {SHOW_NETWORK_UTILIZATION} from '../../../../../utils/constants'; -import {useSetting, useTypedSelector} from '../../../../../utils/hooks'; +import {useSetting} from '../../../../../utils/hooks'; import {calculateMetricAggregates} from '../../../../../utils/metrics'; import { formatCoresLegend, @@ -38,7 +38,7 @@ interface MetricsTabsProps { networkStats?: TenantMetricStats[]; storageGroupsCount?: number; isServerless?: boolean; - activeTab?: TenantMetricsTab; + activeTab: TenantMetricsTab; } export function MetricsTabs({ @@ -52,7 +52,6 @@ export function MetricsTabs({ activeTab, }: MetricsTabsProps) { const location = useLocation(); - const {metricsTab} = useTypedSelector((state) => state.tenant); const queryParams = parseQuery(location); const tabLinks: Record = { @@ -99,14 +98,13 @@ export function MetricsTabs({ [networkStats], ); - const active = activeTab ?? metricsTab; const cardVariant = isServerless ? 'serverless' : 'default'; return (
@@ -115,7 +113,7 @@ export function MetricsTabs({ value={cpuMetrics.totalUsed} limit={cpuMetrics.totalLimit} legendFormatter={formatCoresLegend} - active={active === TENANT_METRICS_TABS_IDS.cpu} + active={activeTab === TENANT_METRICS_TABS_IDS.cpu} helpText={i18n('context_cpu-description')} variant={cardVariant} subtitle={isServerless ? i18n('serverless.autoscaled') : undefined} @@ -124,7 +122,7 @@ export function MetricsTabs({
@@ -133,7 +131,7 @@ export function MetricsTabs({ value={storageMetrics.totalUsed} limit={storageMetrics.totalLimit} legendFormatter={formatStorageLegend} - active={active === TENANT_METRICS_TABS_IDS.storage} + active={activeTab === TENANT_METRICS_TABS_IDS.storage} helpText={i18n('context_storage-description')} variant={cardVariant} subtitle={ @@ -154,7 +152,7 @@ export function MetricsTabs({ <>
@@ -163,7 +161,7 @@ export function MetricsTabs({ value={memoryMetrics.totalUsed} limit={memoryMetrics.totalLimit} legendFormatter={formatStorageLegend} - active={active === TENANT_METRICS_TABS_IDS.memory} + active={activeTab === TENANT_METRICS_TABS_IDS.memory} helpText={i18n('context_memory-description')} /> @@ -171,7 +169,7 @@ export function MetricsTabs({ {showNetworkUtilization && networkStats && networkMetrics && (
@@ -180,7 +178,7 @@ export function MetricsTabs({ value={networkMetrics.totalUsed} limit={networkMetrics.totalLimit} legendFormatter={formatSpeedLegend} - active={active === TENANT_METRICS_TABS_IDS.network} + active={activeTab === TENANT_METRICS_TABS_IDS.network} helpText={i18n('context_network-description')} /> diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx index 45611ee32e..9ab973fa77 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx @@ -51,7 +51,7 @@ export function TenantOverview({ const tenantLoading = isFetching && tenant === undefined; const {Name, Type, Overall} = tenant || {}; const isServerless = Type === 'Serverless'; - const effectiveMetricsTab = + const activeMetricsTab = isServerless && metricsTab !== TENANT_METRICS_TABS_IDS.cpu && metricsTab !== TENANT_METRICS_TABS_IDS.storage @@ -134,7 +134,7 @@ export function TenantOverview({ }; const renderTabContent = () => { - switch (effectiveMetricsTab) { + switch (activeMetricsTab) { case TENANT_METRICS_TABS_IDS.cpu: { return (
From 65574d056cb164dfed2331d25cbcd85596db9fd8 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Thu, 4 Sep 2025 20:07:53 +0300 Subject: [PATCH 06/13] fix: better code --- .../Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx index 0ba8d0475b..94cf3afc43 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx @@ -148,7 +148,9 @@ export function MetricsTabs({ />
- {!isServerless && ( + {isServerless ? ( + + ) : ( <>
)} - - {isServerless && } ); } From e78f7ec7b64d10d7deea177a51df45c31adc5a54 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Fri, 5 Sep 2025 11:27:36 +0300 Subject: [PATCH 07/13] fix: label --- .../TenantOverview/MetricsTabs/MetricsTabs.tsx | 4 ++-- .../TenantOverview/TenantOverview.scss | 12 +----------- .../TenantOverview/TenantOverview.tsx | 16 +++++++++------- .../Diagnostics/TenantOverview/i18n/en.json | 12 ++++++------ 4 files changed, 18 insertions(+), 26 deletions(-) diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx index 94cf3afc43..c0c0aa1ba0 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx @@ -116,7 +116,7 @@ export function MetricsTabs({ active={activeTab === TENANT_METRICS_TABS_IDS.cpu} helpText={i18n('context_cpu-description')} variant={cardVariant} - subtitle={isServerless ? i18n('serverless.autoscaled') : undefined} + subtitle={isServerless ? i18n('context_serverless-autoscaled') : undefined} />
@@ -136,7 +136,7 @@ export function MetricsTabs({ variant={cardVariant} subtitle={ isServerless && storageMetrics.totalLimit - ? i18n('serverless.storage-subtitle', { + ? i18n('context_serverless-storage-subtitle', { groups: String(storageGroupsCount ?? 0), legend: formatStorageLegend({ value: storageMetrics.totalUsed, diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss index 15b249fe4d..7c511feee3 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss @@ -24,18 +24,8 @@ line-height: 24px; } - &__serverless-tag { - margin-left: var(--g-spacing-2); - padding: 2px var(--g-spacing-2); - - border: 1px solid var(--g-color-line-generic); - border-radius: var(--g-border-radius-s); - } - &__serverless-tag-label { - display: flex; - align-items: center; - gap: var(--g-spacing-1); + margin-left: var(--g-spacing-2); } &__info { diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx index 9ab973fa77..1d46e4d8d8 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx @@ -1,7 +1,6 @@ -import {Button, Flex, Icon} from '@gravity-ui/uikit'; +import {Button, Flex, HelpMark, Icon, Label} from '@gravity-ui/uikit'; import {EntityStatus} from '../../../../components/EntityStatus/EntityStatus'; -import {LabelWithPopover} from '../../../../components/LabelWithPopover/LabelWithPopover'; import {LoaderWrapper} from '../../../../components/LoaderWrapper/LoaderWrapper'; import {QueriesActivityBar} from '../../../../components/QueriesActivityBar/QueriesActivityBar'; import {useDatabasesAvailable} from '../../../../store/reducers/capabilities/hooks'; @@ -122,11 +121,14 @@ export function TenantOverview({ /> {isServerless ? (
- +
) : null} diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json b/src/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json index 4a376dbc9e..9ec4807cc0 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json +++ b/src/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json @@ -36,10 +36,10 @@ "title_memory-details": "Memory Details", "field_memory-usage": "Memory usage", "context_capacity-usage": "{{value}} of {{capacity}}", - "network-stats-unavailable.title": "Network Statistics Unavailable", - "network-stats-unavailable.description": "Network ping and clock skew statistics require a newer backend version. Please upgrade your YDB installation to access these features.", - "serverless.label": "Serverless", - "serverless.tooltip": "Some metrics are hidden in Serverless mode — resources scale automatically based on workload", - "serverless.autoscaled": "Auto-Scaled Resources", - "serverless.storage-subtitle": "{{legend}} | {{groups}} groups" + "title_network-stats-unavailable": "Network Statistics Unavailable", + "context_network-stats-unavailable": "Network ping and clock skew statistics require a newer backend version. Please upgrade your YDB installation to access these features.", + "value_serverless": "Serverless", + "context_serverless-tooltip": "Some metrics are hidden in Serverless mode — resources scale automatically based on workload", + "context_serverless-autoscaled": "Auto-Scaled Resources", + "context_serverless-storage-subtitle": "{{legend}} | {{groups}} groups" } From 610df88155337c183ee693b45197e431a97e5be9 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Fri, 5 Sep 2025 11:38:32 +0300 Subject: [PATCH 08/13] fix: nanofix --- .../Tenant/Diagnostics/TenantOverview/TenantOverview.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss index 7c511feee3..90512455bd 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss @@ -25,7 +25,7 @@ } &__serverless-tag-label { - margin-left: var(--g-spacing-2); + margin-left: var(--g-spacing-1); } &__info { From cfb0d28a0b5f8f42c0a30550e6e645389cd879fa Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Fri, 5 Sep 2025 12:47:07 +0300 Subject: [PATCH 09/13] fix: better code --- .../MetricsTabs/MetricsTabs.scss | 10 + .../ServerlessPlaceholderTabs.scss | 9 - .../MetricsTabs/ServerlessPlaceholderTabs.tsx | 11 +- .../TenantOverview/TabCard/TabCard.tsx | 187 +++++++++++------- 4 files changed, 126 insertions(+), 91 deletions(-) delete mode 100644 src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.scss diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.scss b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.scss index fe6b55b43c..c174af3667 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.scss +++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.scss @@ -224,3 +224,13 @@ padding-right: 0; } } + +.tenant-metrics-tabs_serverless { + .tenant-metrics-tabs__link-container_placeholder { + pointer-events: none; + + .tenant-tab-card__card-container { + opacity: 0; + } + } +} diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.scss b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.scss deleted file mode 100644 index 000a3b3c1b..0000000000 --- a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.scss +++ /dev/null @@ -1,9 +0,0 @@ -.tenant-metrics-tabs_serverless { - .tenant-metrics-tabs__link-container_placeholder { - pointer-events: none; - - .tenant-tab-card__card-container { - opacity: 0; - } - } -} diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.tsx index c879b5c8e9..9851105b97 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.tsx @@ -2,16 +2,10 @@ import React from 'react'; import {cn} from '../../../../../utils/cn'; import {NON_BREAKING_SPACE} from '../../../../../utils/constants'; -import {formatCoresLegend} from '../../../../../utils/metrics/formatMetricLegend'; import {TabCard} from '../TabCard/TabCard'; -import './ServerlessPlaceholderTabs.scss'; - const b = cn('tenant-metrics-tabs'); - -const PLACEHOLDER_VALUE = 0; -const PLACEHOLDER_LIMIT = 1; - +import './MetricsTabs.scss'; interface ServerlessPlaceholderTabsProps { count?: number; } @@ -27,9 +21,6 @@ export const ServerlessPlaceholderTabs: React.FC
string; - variant?: 'default' | 'serverless'; subtitle?: string; } -export function TabCard({ - text, - value, - limit, - active, - helpText, - legendFormatter, - variant = 'default', - subtitle, -}: TabCardProps) { - const {status, percents, legend, fill} = getDiagramValues({ - value, - capacity: limit, - legendFormatter, - }); - - if (variant === 'serverless') { - return ( -
- -
- - {text} - - {subtitle ? ( - - {subtitle} - - ) : null} -
-
-
- ); - } +interface TabCardDefaultProps extends TabCardBaseProps { + variant?: Extract; + value: number; + limit: number; + legendFormatter: (params: {value: number; capacity: number}) => string; +} + +interface TabCardServerlessProps extends TabCardBaseProps { + variant: Extract; +} + +type TabCardProps = TabCardDefaultProps | TabCardServerlessProps; + +function isServerlessProps(props: TabCardProps): props is TabCardServerlessProps { + return props.variant === 'serverless'; +} +function TabCardContainer({active, children}: {active?: boolean; children: React.ReactNode}) { return (
- - - - {percents} - - -
- - {legend} - - - {text} - -
-
+ {children}
); } + +const TabCardDefaultContent = React.memo(function TabCardDefaultContent({ + text, + value, + limit, + helpText, + legendFormatter, +}: { + text: string; + value: number; + limit: number; + helpText?: string; + legendFormatter: (params: {value: number; capacity: number}) => string; +}) { + const diagram = useMemo( + () => getDiagramValues({value, capacity: limit, legendFormatter}), + [value, limit, legendFormatter], + ); + + const {status, percents, legend, fill} = diagram; + + return ( + + + {percents} + +
+ {legend} + + {text} + +
+
+ ); +}); + +const TabCardServerlessContent = React.memo(function TabCardServerlessContent({ + text, + helpText, + subtitle, +}: { + text: string; + helpText?: string; + subtitle?: string; +}) { + return ( +
+ + {text} + + {subtitle ? ( + + {subtitle} + + ) : null} +
+ ); +}); + +export function TabCard(props: TabCardProps) { + const {active} = props; + + return ( + + {isServerlessProps(props) ? ( + + ) : ( + + )} + + ); +} From 044b80e2b8d26773eeb63dcef31ccf1a1f26d7cc Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Fri, 5 Sep 2025 13:17:04 +0300 Subject: [PATCH 10/13] fix: code --- .../MetricsTabs/ServerlessPlaceholderTabs.tsx | 28 ++++++++----------- .../TenantOverview/TenantCpu/TenantCpu.tsx | 4 +-- .../TenantOverview/TenantOverview.tsx | 4 +-- .../TenantStorage/TenantStorage.tsx | 4 +-- 4 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.tsx index 9851105b97..ac4d925016 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.tsx @@ -14,21 +14,17 @@ export const ServerlessPlaceholderTabs: React.FC ({count = 2}) => { const items = React.useMemo(() => Array.from({length: count}, (_, i) => i), [count]); - return ( - <> - {items.map((idx) => ( -
-
- -
-
- ))} - - ); + return items.map((idx) => ( +
+
+ +
+
+ )); }, ); diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx index f061c8e0e9..f7c17dbc12 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx @@ -18,10 +18,10 @@ import {cpuDashboardConfig} from './cpuDashboardConfig'; interface TenantCpuProps { tenantName: string; additionalNodesProps?: AdditionalNodesProps; - mode?: 'regular' | 'serverless'; + mode?: 'default' | 'serverless'; } -export function TenantCpu({tenantName, additionalNodesProps, mode = 'regular'}: TenantCpuProps) { +export function TenantCpu({tenantName, additionalNodesProps, mode = 'default'}: TenantCpuProps) { const dispatch = useTypedDispatch(); const getDiagnosticsPageLink = useDiagnosticsPageLinkGetter(); diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx index 1d46e4d8d8..d6b8c64283 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx @@ -142,7 +142,7 @@ export function TenantOverview({ ); } @@ -151,7 +151,7 @@ export function TenantOverview({ ); } diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx index bbee827bcd..b71af9f9cc 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx @@ -25,10 +25,10 @@ export interface TenantStorageMetrics { interface TenantStorageProps { tenantName: string; metrics: TenantStorageMetrics; - mode?: 'regular' | 'serverless'; + mode?: 'default' | 'serverless'; } -export function TenantStorage({tenantName, metrics, mode = 'regular'}: TenantStorageProps) { +export function TenantStorage({tenantName, metrics, mode = 'default'}: TenantStorageProps) { const {blobStorageUsed, tabletStorageUsed, blobStorageLimit, tabletStorageLimit} = metrics; const query = useSearchQuery(); From d75bd92425ca75aa478460a15add40f93b0d5889 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Fri, 5 Sep 2025 14:05:49 +0300 Subject: [PATCH 11/13] fix: storage groups --- .../Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx index c0c0aa1ba0..db3326aed0 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx @@ -127,7 +127,11 @@ export function MetricsTabs({ > Date: Fri, 5 Sep 2025 14:22:51 +0300 Subject: [PATCH 12/13] fix: title --- .../Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx index db3326aed0..64ff6689d5 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx @@ -128,7 +128,7 @@ export function MetricsTabs({ Date: Fri, 5 Sep 2025 16:09:00 +0300 Subject: [PATCH 13/13] fix: refactoring --- .../Tenant/Diagnostics/Diagnostics.tsx | 2 +- .../Tenant/Diagnostics/DiagnosticsPages.ts | 9 +- .../MetricsTabs/CommonMetricsTabs.tsx | 89 ++++++++++++ .../MetricsTabs/DedicatedMetricsTabs.tsx | 70 ++++++++++ .../MetricsTabs/MetricsTabs.tsx | 128 +++++------------- .../MetricsTabs/ServerlessPlaceholderTabs.tsx | 6 +- .../TenantOverview/TabCard/TabCard.tsx | 9 +- .../TenantOverview/TenantCpu/TenantCpu.tsx | 7 +- .../TenantOverview/TenantOverview.tsx | 6 +- .../TenantStorage/TenantStorage.tsx | 7 +- 10 files changed, 215 insertions(+), 118 deletions(-) create mode 100644 src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/CommonMetricsTabs.tsx create mode 100644 src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/DedicatedMetricsTabs.tsx diff --git a/src/containers/Tenant/Diagnostics/Diagnostics.tsx b/src/containers/Tenant/Diagnostics/Diagnostics.tsx index d959964502..00d8b9cdde 100644 --- a/src/containers/Tenant/Diagnostics/Diagnostics.tsx +++ b/src/containers/Tenant/Diagnostics/Diagnostics.tsx @@ -73,7 +73,7 @@ function Diagnostics(props: DiagnosticsProps) { hasBackups: typeof uiFactory.renderBackups === 'function' && Boolean(controlPlane), hasConfigs: isViewerUser, hasAccess: uiFactory.hasAccess, - isServerless: databaseType === 'Serverless', + databaseType, }); let activeTab = pages.find((el) => el.id === diagnosticsTab); if (!activeTab) { diff --git a/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts b/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts index bcb1ba064f..ead594afe2 100644 --- a/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts +++ b/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts @@ -5,6 +5,7 @@ import {StringParam, useQueryParams} from 'use-query-params'; import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../store/reducers/tenant/constants'; import type {TenantDiagnosticsTab} from '../../../store/reducers/tenant/types'; import {EPathSubType, EPathType} from '../../../types/api/schema'; +import type {ETenantType} from '../../../types/api/tenant'; import type {TenantQuery} from '../TenantPages'; import {TenantTabsGroups, getTenantPath} from '../TenantPages'; import {isDatabaseEntityType, isTopicEntityType} from '../utils/schema'; @@ -21,7 +22,7 @@ interface GetPagesOptions { hasBackups?: boolean; hasConfigs?: boolean; hasAccess?: boolean; - isServerless?: boolean; + databaseType?: ETenantType; } const overview = { @@ -194,8 +195,8 @@ function computeInitialPages(type?: EPathType, subType?: EPathSubType) { return subTypePages || typePages || DIR_PAGES; } -function getDatabasePages(isServerless?: boolean) { - return isServerless ? SERVERLESS_DATABASE_PAGES : DATABASE_PAGES; +function getDatabasePages(databaseType?: ETenantType) { + return databaseType === 'Serverless' ? SERVERLESS_DATABASE_PAGES : DATABASE_PAGES; } function applyFilters(pages: Page[], type?: EPathType, options: GetPagesOptions = {}) { @@ -226,7 +227,7 @@ export const getPagesByType = ( ) => { const base = computeInitialPages(type, subType); const dbContext = isDatabaseEntityType(type) || options?.isTopLevel; - const seeded = dbContext ? getDatabasePages(options?.isServerless) : base; + const seeded = dbContext ? getDatabasePages(options?.databaseType) : base; let withFlags = seeded; if (!options?.hasFeatureFlags) { diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/CommonMetricsTabs.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/CommonMetricsTabs.tsx new file mode 100644 index 0000000000..39cf5213bc --- /dev/null +++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/CommonMetricsTabs.tsx @@ -0,0 +1,89 @@ +import {Link} from 'react-router-dom'; + +import {TENANT_METRICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants'; +import type {TenantMetricsTab} from '../../../../../store/reducers/tenant/types'; +import type {ETenantType} from '../../../../../types/api/tenant'; +import {cn} from '../../../../../utils/cn'; +import { + formatCoresLegend, + formatStorageLegend, +} from '../../../../../utils/metrics/formatMetricLegend'; +import {TabCard} from '../TabCard/TabCard'; +import i18n from '../i18n'; + +const b = cn('tenant-metrics-tabs'); + +interface CommonMetricsTabsProps { + activeTab: TenantMetricsTab; + tabLinks: Record; + cpu: {totalUsed: number; totalLimit: number}; + storage: {totalUsed: number; totalLimit: number}; + storageGroupsCount?: number; + databaseType?: ETenantType; +} + +export function CommonMetricsTabs({ + activeTab, + tabLinks, + cpu, + storage, + storageGroupsCount, + databaseType, +}: CommonMetricsTabsProps) { + const isServerless = databaseType === 'Serverless'; + + return ( + <> +
+ + + +
+
+ + + +
+ + ); +} diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/DedicatedMetricsTabs.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/DedicatedMetricsTabs.tsx new file mode 100644 index 0000000000..21e0da39bb --- /dev/null +++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/DedicatedMetricsTabs.tsx @@ -0,0 +1,70 @@ +import {Link} from 'react-router-dom'; + +import {TENANT_METRICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants'; +import type {TenantMetricsTab} from '../../../../../store/reducers/tenant/types'; +import {cn} from '../../../../../utils/cn'; +import { + formatSpeedLegend, + formatStorageLegend, +} from '../../../../../utils/metrics/formatMetricLegend'; +import {TabCard} from '../TabCard/TabCard'; +import i18n from '../i18n'; + +const b = cn('tenant-metrics-tabs'); + +interface DedicatedMetricsTabsProps { + activeTab: TenantMetricsTab; + tabLinks: Record; + memory: {totalUsed: number; totalLimit: number}; + network: {totalUsed: number; totalLimit: number} | null; + showNetwork: boolean; +} + +export function DedicatedMetricsTabs({ + activeTab, + tabLinks, + memory, + network, + showNetwork, +}: DedicatedMetricsTabsProps) { + return ( + <> +
+ + + +
+ {showNetwork && network && ( +
+ + + +
+ )} + + ); +} diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx index 64ff6689d5..8a780224e5 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx @@ -1,7 +1,7 @@ import {useMemo} from 'react'; import {Flex} from '@gravity-ui/uikit'; -import {Link, useLocation} from 'react-router-dom'; +import {useLocation} from 'react-router-dom'; import {parseQuery} from '../../../../../routes'; import {TENANT_METRICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants'; @@ -11,19 +11,16 @@ import type { TenantPoolsStats, TenantStorageStats, } from '../../../../../store/reducers/tenants/utils'; +import type {ETenantType} from '../../../../../types/api/tenant'; import {cn} from '../../../../../utils/cn'; import {SHOW_NETWORK_UTILIZATION} from '../../../../../utils/constants'; import {useSetting} from '../../../../../utils/hooks'; import {calculateMetricAggregates} from '../../../../../utils/metrics'; -import { - formatCoresLegend, - formatSpeedLegend, - formatStorageLegend, -} from '../../../../../utils/metrics/formatMetricLegend'; +// no direct legend formatters needed here – handled in subcomponents import {TenantTabsGroups, getTenantPath} from '../../../TenantPages'; -import {TabCard} from '../TabCard/TabCard'; -import i18n from '../i18n'; +import {CommonMetricsTabs} from './CommonMetricsTabs'; +import {DedicatedMetricsTabs} from './DedicatedMetricsTabs'; import {ServerlessPlaceholderTabs} from './ServerlessPlaceholderTabs'; import './MetricsTabs.scss'; @@ -37,7 +34,7 @@ interface MetricsTabsProps { tabletStorageStats?: TenantStorageStats[]; networkStats?: TenantMetricStats[]; storageGroupsCount?: number; - isServerless?: boolean; + databaseType?: ETenantType; activeTab: TenantMetricsTab; } @@ -48,7 +45,7 @@ export function MetricsTabs({ tabletStorageStats, networkStats, storageGroupsCount, - isServerless, + databaseType, activeTab, }: MetricsTabsProps) { const location = useLocation(); @@ -98,99 +95,36 @@ export function MetricsTabs({ [networkStats], ); - const cardVariant = isServerless ? 'serverless' : 'default'; + // card variant is handled within subcomponents + + const isServerless = databaseType === 'Serverless'; return ( -
- - - -
-
- - - -
+ {isServerless ? ( ) : ( - <> -
- - - -
- {showNetworkUtilization && networkStats && networkMetrics && ( -
- - - -
- )} - + )}
); diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.tsx index ac4d925016..6eb3428e3c 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.tsx @@ -4,8 +4,10 @@ import {cn} from '../../../../../utils/cn'; import {NON_BREAKING_SPACE} from '../../../../../utils/constants'; import {TabCard} from '../TabCard/TabCard'; -const b = cn('tenant-metrics-tabs'); import './MetricsTabs.scss'; + +const b = cn('tenant-metrics-tabs'); + interface ServerlessPlaceholderTabsProps { count?: number; } @@ -20,7 +22,7 @@ export const ServerlessPlaceholderTabs: React.FC
diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TabCard/TabCard.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TabCard/TabCard.tsx index ea5b9a1caa..ee30d0f347 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TabCard/TabCard.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TabCard/TabCard.tsx @@ -4,14 +4,13 @@ import {Card, Flex} from '@gravity-ui/uikit'; import {DoughnutMetrics} from '../../../../../components/DoughnutMetrics/DoughnutMetrics'; import {getDiagramValues} from '../../../../../containers/Cluster/ClusterOverview/utils'; +import type {ETenantType} from '../../../../../types/api/tenant'; import {cn} from '../../../../../utils/cn'; import './TabCard.scss'; const b = cn('tenant-tab-card'); -type TabCardVariant = 'default' | 'serverless'; - interface TabCardBaseProps { text: string; active?: boolean; @@ -20,20 +19,20 @@ interface TabCardBaseProps { } interface TabCardDefaultProps extends TabCardBaseProps { - variant?: Extract; + databaseType?: Exclude; value: number; limit: number; legendFormatter: (params: {value: number; capacity: number}) => string; } interface TabCardServerlessProps extends TabCardBaseProps { - variant: Extract; + databaseType: 'Serverless'; } type TabCardProps = TabCardDefaultProps | TabCardServerlessProps; function isServerlessProps(props: TabCardProps): props is TabCardServerlessProps { - return props.variant === 'serverless'; + return props.databaseType === 'Serverless'; } function TabCardContainer({active, children}: {active?: boolean; children: React.ReactNode}) { diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx index f7c17dbc12..7aeba979dd 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx @@ -3,6 +3,7 @@ import {Flex} from '@gravity-ui/uikit'; import {setTopQueriesFilters} from '../../../../../store/reducers/executeTopQueries/executeTopQueries'; import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants'; import type {AdditionalNodesProps} from '../../../../../types/additionalProps'; +import type {ETenantType} from '../../../../../types/api/tenant'; import {useTypedDispatch} from '../../../../../utils/hooks'; import {useDiagnosticsPageLinkGetter} from '../../../Diagnostics/DiagnosticsPages'; import {StatsWrapper} from '../StatsWrapper/StatsWrapper'; @@ -18,10 +19,10 @@ import {cpuDashboardConfig} from './cpuDashboardConfig'; interface TenantCpuProps { tenantName: string; additionalNodesProps?: AdditionalNodesProps; - mode?: 'default' | 'serverless'; + databaseType?: ETenantType; } -export function TenantCpu({tenantName, additionalNodesProps, mode = 'default'}: TenantCpuProps) { +export function TenantCpu({tenantName, additionalNodesProps, databaseType}: TenantCpuProps) { const dispatch = useTypedDispatch(); const getDiagnosticsPageLink = useDiagnosticsPageLinkGetter(); @@ -29,7 +30,7 @@ export function TenantCpu({tenantName, additionalNodesProps, mode = 'default'}: const topShardsLink = getDiagnosticsPageLink(TENANT_DIAGNOSTICS_TABS_IDS.topShards); const topQueriesLink = getDiagnosticsPageLink(TENANT_DIAGNOSTICS_TABS_IDS.topQueries); - const isServerless = mode === 'serverless'; + const isServerless = databaseType === 'Serverless'; return ( diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx index d6b8c64283..9f70bb2da6 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx @@ -142,7 +142,7 @@ export function TenantOverview({ ); } @@ -151,7 +151,7 @@ export function TenantOverview({ ); } @@ -216,7 +216,7 @@ export function TenantOverview({ ? Number(tenantData.StorageGroups) : undefined } - isServerless={isServerless} + databaseType={Type} activeTab={activeMetricsTab} /> diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx index b71af9f9cc..b057707dae 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx @@ -4,6 +4,7 @@ import {InfoViewer} from '../../../../../components/InfoViewer/InfoViewer'; import {LabelWithPopover} from '../../../../../components/LabelWithPopover'; import {ProgressWrapper} from '../../../../../components/ProgressWrapper'; import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants'; +import type {ETenantType} from '../../../../../types/api/tenant'; import {formatStorageValues} from '../../../../../utils/dataFormatters/dataFormatters'; import {useSearchQuery} from '../../../../../utils/hooks'; import {TenantTabsGroups, getTenantPath} from '../../../TenantPages'; @@ -25,10 +26,10 @@ export interface TenantStorageMetrics { interface TenantStorageProps { tenantName: string; metrics: TenantStorageMetrics; - mode?: 'default' | 'serverless'; + databaseType?: ETenantType; } -export function TenantStorage({tenantName, metrics, mode = 'default'}: TenantStorageProps) { +export function TenantStorage({tenantName, metrics, databaseType}: TenantStorageProps) { const {blobStorageUsed, tabletStorageUsed, blobStorageLimit, tabletStorageLimit} = metrics; const query = useSearchQuery(); @@ -67,7 +68,7 @@ export function TenantStorage({tenantName, metrics, mode = 'default'}: TenantSto }, ]; - if (mode === 'serverless') { + if (databaseType === 'Serverless') { return (