diff --git a/src/containers/Tenant/Diagnostics/Diagnostics.tsx b/src/containers/Tenant/Diagnostics/Diagnostics.tsx
index a7ea19eaa7..00d8b9cdde 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';
@@ -60,7 +61,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 +73,7 @@ function Diagnostics(props: DiagnosticsProps) {
hasBackups: typeof uiFactory.renderBackups === 'function' && Boolean(controlPlane),
hasConfigs: isViewerUser,
hasAccess: uiFactory.hasAccess,
+ databaseType,
});
let activeTab = pages.find((el) => el.id === diagnosticsTab);
if (!activeTab) {
@@ -176,7 +178,7 @@ function Diagnostics(props: DiagnosticsProps) {
});
}
default: {
- return
No data...
;
+ return {i18n('no-data')}
;
}
}
};
@@ -187,10 +189,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..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';
@@ -14,6 +15,16 @@ type Page = {
title: string;
};
+interface GetPagesOptions {
+ hasFeatureFlags?: boolean;
+ hasTopicData?: boolean;
+ isTopLevel?: boolean;
+ hasBackups?: boolean;
+ hasConfigs?: boolean;
+ hasAccess?: boolean;
+ databaseType?: ETenantType;
+}
+
const overview = {
id: TENANT_DIAGNOSTICS_TABS_IDS.overview,
title: 'Info',
@@ -118,6 +129,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 +189,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(databaseType?: ETenantType) {
+ return databaseType === 'Serverless' ? 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?.databaseType) : 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/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.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/MetricsTabs.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx
index 0cedc6b33d..8a780224e5 100644
--- a/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx
+++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx
@@ -1,5 +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';
@@ -9,18 +11,17 @@ 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, useTypedSelector} from '../../../../../utils/hooks';
+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';
@@ -33,6 +34,8 @@ interface MetricsTabsProps {
tabletStorageStats?: TenantStorageStats[];
networkStats?: TenantMetricStats[];
storageGroupsCount?: number;
+ databaseType?: ETenantType;
+ activeTab: TenantMetricsTab;
}
export function MetricsTabs({
@@ -42,9 +45,10 @@ export function MetricsTabs({
tabletStorageStats,
networkStats,
storageGroupsCount,
+ databaseType,
+ activeTab,
}: MetricsTabsProps) {
const location = useLocation();
- const {metricsTab} = useTypedSelector((state) => state.tenant);
const queryParams = parseQuery(location);
const tabLinks: Record = {
@@ -67,93 +71,60 @@ 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],
+ );
+
+ // card variant is handled within subcomponents
+
+ const isServerless = databaseType === 'Serverless';
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {showNetworkUtilization && networkStats && networkMetrics && (
-
-
-
-
-
+
+
+ {isServerless ? (
+
+ ) : (
+
)}
);
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..6eb3428e3c
--- /dev/null
+++ b/src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/ServerlessPlaceholderTabs.tsx
@@ -0,0 +1,32 @@
+import React from 'react';
+
+import {cn} from '../../../../../utils/cn';
+import {NON_BREAKING_SPACE} from '../../../../../utils/constants';
+import {TabCard} from '../TabCard/TabCard';
+
+import './MetricsTabs.scss';
+
+const b = cn('tenant-metrics-tabs');
+
+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) => (
+
+ ));
+ },
+);
diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TabCard/TabCard.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TabCard/TabCard.tsx
index 6c5f138bc2..ee30d0f347 100644
--- a/src/containers/Tenant/Diagnostics/TenantOverview/TabCard/TabCard.tsx
+++ b/src/containers/Tenant/Diagnostics/TenantOverview/TabCard/TabCard.tsx
@@ -1,29 +1,41 @@
+import React, {useMemo} from 'react';
+
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');
-interface TabCardProps {
+interface TabCardBaseProps {
text: string;
- value: number;
- limit: number;
active?: boolean;
helpText?: string;
+ subtitle?: string;
+}
+
+interface TabCardDefaultProps extends TabCardBaseProps {
+ databaseType?: Exclude;
+ value: number;
+ limit: number;
legendFormatter: (params: {value: number; capacity: number}) => string;
}
-export function TabCard({text, value, limit, active, helpText, legendFormatter}: TabCardProps) {
- const {status, percents, legend, fill} = getDiagramValues({
- value,
- capacity: limit,
- legendFormatter,
- });
+interface TabCardServerlessProps extends TabCardBaseProps {
+ databaseType: 'Serverless';
+}
+
+type TabCardProps = TabCardDefaultProps | TabCardServerlessProps;
+function isServerlessProps(props: TabCardProps): props is TabCardServerlessProps {
+ return props.databaseType === '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) ? (
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx
index b04026f366..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,9 +19,10 @@ import {cpuDashboardConfig} from './cpuDashboardConfig';
interface TenantCpuProps {
tenantName: string;
additionalNodesProps?: AdditionalNodesProps;
+ databaseType?: ETenantType;
}
-export function TenantCpu({tenantName, additionalNodesProps}: TenantCpuProps) {
+export function TenantCpu({tenantName, additionalNodesProps, databaseType}: TenantCpuProps) {
const dispatch = useTypedDispatch();
const getDiagnosticsPageLink = useDiagnosticsPageLinkGetter();
@@ -28,21 +30,33 @@ export function TenantCpu({tenantName, additionalNodesProps}: TenantCpuProps) {
const topShardsLink = getDiagnosticsPageLink(TENANT_DIAGNOSTICS_TABS_IDS.topShards);
const topQueriesLink = getDiagnosticsPageLink(TENANT_DIAGNOSTICS_TABS_IDS.topQueries);
+ const isServerless = databaseType === 'Serverless';
+
return (
-
-
-
-
-
-
-
+ {!isServerless && (
+ <>
+
+
+
+
+
+
+
+ >
+ )}
diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss
index 1256fafc68..90512455bd 100644
--- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss
+++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss
@@ -24,6 +24,10 @@
line-height: 24px;
}
+ &__serverless-tag-label {
+ margin-left: 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..9f70bb2da6 100644
--- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx
+++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx
@@ -1,4 +1,4 @@
-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 {LoaderWrapper} from '../../../../components/LoaderWrapper/LoaderWrapper';
@@ -21,6 +21,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 +49,13 @@ export function TenantOverview({
);
const tenantLoading = isFetching && tenant === undefined;
const {Name, Type, Overall} = tenant || {};
+ const isServerless = Type === 'Serverless';
+ const activeMetricsTab =
+ 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 +119,41 @@ export function TenantOverview({
hasClipboardButton={Boolean(tenant)}
clipboardButtonAlwaysVisible
/>
+ {isServerless ? (
+
+
+
+ ) : null}
);
};
const renderTabContent = () => {
- switch (metricsTab) {
+ switch (activeMetricsTab) {
case TENANT_METRICS_TABS_IDS.cpu: {
return (
);
}
case TENANT_METRICS_TABS_IDS.storage: {
- return ;
+ return (
+
+ );
}
case TENANT_METRICS_TABS_IDS.memory: {
return (
@@ -146,6 +173,9 @@ export function TenantOverview({
/>
);
}
+ default: {
+ return null;
+ }
}
};
@@ -186,6 +216,8 @@ export function TenantOverview({
? Number(tenantData.StorageGroups)
: undefined
}
+ 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 4df7995609..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,9 +26,10 @@ export interface TenantStorageMetrics {
interface TenantStorageProps {
tenantName: string;
metrics: TenantStorageMetrics;
+ databaseType?: ETenantType;
}
-export function TenantStorage({tenantName, metrics}: TenantStorageProps) {
+export function TenantStorage({tenantName, metrics, databaseType}: TenantStorageProps) {
const {blobStorageUsed, tabletStorageUsed, blobStorageLimit, tabletStorageLimit} = metrics;
const query = useSearchQuery();
@@ -66,6 +68,22 @@ export function TenantStorage({tenantName, metrics}: TenantStorageProps) {
},
];
+ if (databaseType === '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..9ec4807cc0 100644
--- a/src/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json
+++ b/src/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json
@@ -36,6 +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."
+ "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"
}
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/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,
};
}
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*/';