ydb-embedded-ui 4.15.1 → 4.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/components/DiagnosticCard/DiagnosticCard.scss +5 -0
  3. package/dist/components/DiagnosticCard/DiagnosticCard.tsx +17 -0
  4. package/dist/components/EntityStatus/EntityStatus.js +2 -2
  5. package/dist/components/EntityStatus/EntityStatus.scss +15 -0
  6. package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.tsx +3 -11
  7. package/dist/containers/Tenant/Diagnostics/Healthcheck/Details/Details.tsx +20 -11
  8. package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.scss +14 -1
  9. package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.tsx +26 -37
  10. package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/Preview.tsx +37 -25
  11. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +154 -0
  12. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json +7 -0
  13. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/index.ts +11 -0
  14. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/ru.json +7 -0
  15. package/dist/containers/Tenant/Query/QueriesHistory/QueriesHistory.scss +4 -1
  16. package/dist/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx +42 -11
  17. package/dist/containers/Tenant/Query/QueryEditor/QueryEditor.js +1 -1
  18. package/dist/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.tsx +11 -7
  19. package/dist/containers/Tenant/Query/i18n/en.json +3 -0
  20. package/dist/containers/Tenant/Query/i18n/ru.json +3 -0
  21. package/dist/containers/Tenants/Tenants.tsx +3 -1
  22. package/dist/containers/UserSettings/Setting.tsx +9 -2
  23. package/dist/containers/UserSettings/i18n/en.json +5 -1
  24. package/dist/containers/UserSettings/i18n/ru.json +5 -1
  25. package/dist/containers/UserSettings/settings.ts +25 -0
  26. package/dist/services/api.ts +16 -16
  27. package/dist/store/reducers/executeQuery.ts +33 -7
  28. package/dist/store/reducers/explainQuery.ts +12 -4
  29. package/dist/store/reducers/healthcheckInfo.ts +27 -11
  30. package/dist/store/reducers/settings/settings.ts +4 -10
  31. package/dist/store/reducers/tenant/tenant.ts +19 -1
  32. package/dist/store/reducers/tenant/types.ts +3 -1
  33. package/dist/store/reducers/tenants/selectors.ts +1 -1
  34. package/dist/store/reducers/tenants/utils.ts +2 -2
  35. package/dist/types/api/tenant.ts +19 -20
  36. package/dist/types/store/executeQuery.ts +11 -1
  37. package/dist/types/store/query.ts +2 -1
  38. package/dist/utils/autofetcher.ts +7 -7
  39. package/dist/utils/constants.ts +2 -1
  40. package/dist/utils/hooks/i18n/en.json +1 -1
  41. package/dist/utils/hooks/i18n/ru.json +1 -1
  42. package/dist/utils/hooks/useQueryModes.ts +4 -2
  43. package/dist/utils/i18n/i18n.ts +10 -4
  44. package/dist/utils/query.ts +14 -0
  45. package/dist/utils/settings.ts +10 -0
  46. package/package.json +1 -1
  47. package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/PreviewItem/PreviewItem.tsx +0 -33
  48. package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/PreviewItem/index.ts +0 -1
  49. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.js +0 -213
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.16.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.15.1...v4.16.0) (2023-08-25)
4
+
5
+
6
+ ### Features
7
+
8
+ * add language setting ([#520](https://github.com/ydb-platform/ydb-embedded-ui/issues/520)) ([425c9ae](https://github.com/ydb-platform/ydb-embedded-ui/commit/425c9ae1fed83d7695d2a9288c2ef24c2807d8da))
9
+ * **Diagnostics:** update Healthcheck design ([#509](https://github.com/ydb-platform/ydb-embedded-ui/issues/509)) ([e315ca4](https://github.com/ydb-platform/ydb-embedded-ui/commit/e315ca42ac6c9d1736aaa25e2dd90afc2bcb9a8e))
10
+ * **Query:** support PostgreSQL syntax ([#515](https://github.com/ydb-platform/ydb-embedded-ui/issues/515)) ([0c8346e](https://github.com/ydb-platform/ydb-embedded-ui/commit/0c8346efc3643a8d201137901880f985dc100458))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **UserSettings:** update query mode setting description ([#521](https://github.com/ydb-platform/ydb-embedded-ui/issues/521)) ([c526471](https://github.com/ydb-platform/ydb-embedded-ui/commit/c52647192ff95d8fb9961479a85cc4d5a639d4e6))
16
+
3
17
  ## [4.15.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.15.0...v4.15.1) (2023-08-21)
4
18
 
5
19
 
@@ -0,0 +1,5 @@
1
+ .diagnostic-card {
2
+ min-width: 200px;
3
+ max-width: 350px;
4
+ padding: 16px;
5
+ }
@@ -0,0 +1,17 @@
1
+ import {ReactNode} from 'react';
2
+ import cn from 'bem-cn-lite';
3
+
4
+ import {Card} from '@gravity-ui/uikit';
5
+
6
+ import './DiagnosticCard.scss';
7
+
8
+ const b = cn('diagnostic-card');
9
+
10
+ interface DiagnosticCardProps {
11
+ children?: ReactNode;
12
+ className?: string;
13
+ }
14
+
15
+ export function DiagnosticCard({children, className}: DiagnosticCardProps) {
16
+ return <Card className={b(null, className)}>{children}</Card>;
17
+ }
@@ -107,13 +107,13 @@ class EntityStatus extends React.Component {
107
107
  );
108
108
  }
109
109
  render() {
110
- const {name, label, iconPath, hasClipboardButton, className} = this.props;
110
+ const {name, label, iconPath, hasClipboardButton, className, size, status} = this.props;
111
111
 
112
112
  return (
113
113
  <div className={b(null, className)} title={name}>
114
114
  {iconPath ? this.renderStatusLink() : this.renderIcon()}
115
115
  {label && (
116
- <span title={label} className={b('label')}>
116
+ <span title={label} className={b('label', {size, state: status.toLowerCase()})}>
117
117
  {label}
118
118
  </span>
119
119
  )}
@@ -47,6 +47,15 @@
47
47
  line-height: var(--yc-text-body-2-line-height);
48
48
 
49
49
  color: var(--yc-color-text-complementary);
50
+
51
+ &_size_m {
52
+ font-size: var(--yc-text-body-2-font-size);
53
+ line-height: var(--yc-text-body-2-line-height);
54
+ }
55
+
56
+ &_size_l {
57
+ font-size: var(--yc-text-header-2-font-size);
58
+ }
50
59
  }
51
60
 
52
61
  &__link {
@@ -89,6 +98,11 @@
89
98
  width: 18px;
90
99
  height: 18px;
91
100
  }
101
+
102
+ &_size_l {
103
+ width: 27px;
104
+ height: 27px;
105
+ }
92
106
  }
93
107
 
94
108
  &__status-color {
@@ -115,6 +129,7 @@
115
129
  }
116
130
  }
117
131
 
132
+ &__label,
118
133
  &__status-icon {
119
134
  &_state_blue {
120
135
  color: var(--yc-color-infographics-info-heavy);
@@ -8,8 +8,7 @@ import type {EPathType} from '../../../../types/api/schema';
8
8
  import {Icon} from '../../../../components/Icon';
9
9
  import Overview from '../Overview/Overview';
10
10
  import {Healthcheck} from '../Healthcheck';
11
- //@ts-ignore
12
- import TenantOverview from '../TenantOverview/TenantOverview';
11
+ import {TenantOverview} from '../TenantOverview/TenantOverview';
13
12
 
14
13
  import './DetailedOverview.scss';
15
14
 
@@ -25,12 +24,9 @@ const b = cn('kv-detailed-overview');
25
24
  function DetailedOverview(props: DetailedOverviewProps) {
26
25
  const [isModalVisible, setIsModalVisible] = useState(false);
27
26
 
28
- const [expandedIssueId, setExpandedIssueId] = useState<string>();
29
-
30
27
  const {currentSchemaPath} = useSelector((state: any) => state.schema);
31
28
 
32
- const openModalHandler = (id: string) => {
33
- setExpandedIssueId(id);
29
+ const openModalHandler = () => {
34
30
  setIsModalVisible(true);
35
31
  };
36
32
 
@@ -41,11 +37,7 @@ function DetailedOverview(props: DetailedOverviewProps) {
41
37
  const renderModal = () => {
42
38
  return (
43
39
  <Modal open={isModalVisible} onClose={closeModalHandler} className={b('modal')}>
44
- <Healthcheck
45
- tenant={props.tenantName}
46
- fetchData={false}
47
- expandedIssueId={expandedIssueId}
48
- />
40
+ <Healthcheck tenant={props.tenantName} fetchData={false} />
49
41
  <Button
50
42
  className={b('close-modal-button')}
51
43
  onClick={closeModalHandler}
@@ -4,7 +4,9 @@ import {Button, Icon} from '@gravity-ui/uikit';
4
4
 
5
5
  import updateArrow from '../../../../../assets/icons/update-arrow.svg';
6
6
 
7
+ import type {IResponseError} from '../../../../../types/api/error';
7
8
  import type {IIssuesTree} from '../../../../../types/store/healthcheck';
9
+ import {ResponseError} from '../../../../../components/Errors/ResponseError';
8
10
 
9
11
  import IssueTree from '../IssuesViewer/IssueTree';
10
12
 
@@ -13,17 +15,14 @@ import i18n from '../i18n';
13
15
  const b = cn('healthcheck');
14
16
 
15
17
  interface DetailsProps {
16
- issueTree?: IIssuesTree;
18
+ issueTrees?: IIssuesTree[];
17
19
  loading?: boolean;
18
20
  onUpdate: VoidFunction;
21
+ error?: IResponseError;
19
22
  }
20
23
 
21
24
  export const Details = (props: DetailsProps) => {
22
- const {loading, onUpdate, issueTree} = props;
23
-
24
- if (!issueTree) {
25
- return null;
26
- }
25
+ const {loading, onUpdate, issueTrees, error} = props;
27
26
 
28
27
  const renderHealthcheckHeader = () => {
29
28
  return (
@@ -38,18 +37,28 @@ export const Details = (props: DetailsProps) => {
38
37
  );
39
38
  };
40
39
 
41
- const renderHealthcheckIssues = () => {
40
+ const renderContent = () => {
41
+ if (error) {
42
+ return <ResponseError error={error} defaultMessage={i18n('no-data')} />;
43
+ }
44
+
45
+ if (!issueTrees || !issueTrees.length) {
46
+ return i18n('status_message.ok');
47
+ }
48
+
42
49
  return (
43
- <div className={b('issues-wrapper')}>
44
- <IssueTree issueTree={issueTree} />
45
- </div>
50
+ <>
51
+ {issueTrees.map((issueTree) => (
52
+ <IssueTree key={issueTree.id} issueTree={issueTree} />
53
+ ))}
54
+ </>
46
55
  );
47
56
  };
48
57
 
49
58
  return (
50
59
  <div className={b('details')}>
51
60
  {renderHealthcheckHeader()}
52
- {renderHealthcheckIssues()}
61
+ <div className={b('details-content-wrapper')}>{renderContent()}</div>
53
62
  </div>
54
63
  );
55
64
  };
@@ -3,6 +3,8 @@
3
3
  @import '@gravity-ui/uikit/styles/mixins.scss';
4
4
 
5
5
  .healthcheck {
6
+ display: flex;
7
+
6
8
  &_expanded {
7
9
  // Since most of the inner containers have fixed width, we can set fixed width here as well
8
10
  // Thus we will get rid of unneeded layout shift when scrollbar appear
@@ -17,7 +19,7 @@
17
19
  margin-bottom: 15px;
18
20
  }
19
21
 
20
- &__issues-wrapper {
22
+ &__details-content-wrapper {
21
23
  overflow-x: hidden;
22
24
  overflow-y: auto;
23
25
 
@@ -67,6 +69,17 @@
67
69
  line-height: 24px;
68
70
  }
69
71
 
72
+ &__issues-statistics {
73
+ display: flex;
74
+ flex-wrap: wrap;
75
+ align-items: center;
76
+
77
+ margin: 10px 0;
78
+
79
+ column-gap: 26px;
80
+ row-gap: 16px;
81
+ }
82
+
70
83
  &__self-check-status-indicator {
71
84
  padding: 0 8px;
72
85
 
@@ -8,40 +8,36 @@ import {SelfCheckResult} from '../../../../types/api/healthcheck';
8
8
  import {useTypedSelector, useAutofetcher} from '../../../../utils/hooks';
9
9
  import {
10
10
  getHealthcheckInfo,
11
- selectIssuesTreeById,
12
- selectIssuesTreesRoots,
11
+ selectIssuesStatistics,
12
+ selectIssuesTrees,
13
13
  setDataWasNotLoaded,
14
14
  } from '../../../../store/reducers/healthcheckInfo';
15
+ import {DiagnosticCard} from '../../../../components/DiagnosticCard/DiagnosticCard';
15
16
 
16
17
  import {Details} from './Details';
17
18
  import {Preview} from './Preview';
18
19
 
19
- import i18n from './i18n';
20
20
  import './Healthcheck.scss';
21
21
 
22
22
  interface HealthcheckProps {
23
23
  tenant: string;
24
24
  preview?: boolean;
25
25
  fetchData?: boolean;
26
- expandedIssueId?: string;
27
- showMoreHandler?: (id: string) => void;
26
+ showMoreHandler?: VoidFunction;
28
27
  }
29
28
 
30
29
  const b = cn('healthcheck');
31
30
 
32
31
  export const Healthcheck = (props: HealthcheckProps) => {
33
- const {tenant, preview, fetchData = true, showMoreHandler, expandedIssueId} = props;
32
+ const {tenant, preview, fetchData = true, showMoreHandler} = props;
34
33
 
35
34
  const dispatch = useDispatch();
36
35
 
37
36
  const {data, loading, wasLoaded, error} = useTypedSelector((state) => state.healthcheckInfo);
38
37
  const selfCheckResult = data?.self_check_result || SelfCheckResult.UNSPECIFIED;
39
38
 
40
- const issuesTreesRoots = useTypedSelector(selectIssuesTreesRoots);
41
- const expandedIssueTree = useTypedSelector((state) =>
42
- selectIssuesTreeById(state, expandedIssueId),
43
- );
44
-
39
+ const issuesStatistics = useTypedSelector(selectIssuesStatistics);
40
+ const issueTrees = useTypedSelector(selectIssuesTrees);
45
41
  const {autorefresh} = useTypedSelector((state) => state.schema);
46
42
 
47
43
  const fetchHealthcheck = useCallback(
@@ -66,37 +62,30 @@ export const Healthcheck = (props: HealthcheckProps) => {
66
62
  );
67
63
 
68
64
  const renderContent = () => {
69
- if (error) {
70
- return error.statusText;
71
- }
72
-
73
65
  if (loading && !wasLoaded) {
74
66
  return (
75
- <div className={b('loader')}>
67
+ <DiagnosticCard className={b('loader')}>
76
68
  <Loader size="m" />
77
- </div>
69
+ </DiagnosticCard>
78
70
  );
79
71
  }
80
-
81
- if (data && data['self_check_result']) {
82
- return preview ? (
83
- <Preview
84
- issuesTrees={issuesTreesRoots}
85
- selfCheckResult={selfCheckResult}
86
- loading={loading}
87
- onShowMore={showMoreHandler}
88
- onUpdate={fetchHealthcheck}
89
- />
90
- ) : (
91
- <Details
92
- issueTree={expandedIssueTree}
93
- loading={loading}
94
- onUpdate={fetchHealthcheck}
95
- />
96
- );
97
- }
98
-
99
- return <div className="error">{i18n('no-data')}</div>;
72
+ return preview ? (
73
+ <Preview
74
+ issuesStatistics={issuesStatistics}
75
+ selfCheckResult={selfCheckResult}
76
+ loading={loading}
77
+ onShowMore={showMoreHandler}
78
+ onUpdate={fetchHealthcheck}
79
+ error={error}
80
+ />
81
+ ) : (
82
+ <Details
83
+ loading={loading}
84
+ onUpdate={fetchHealthcheck}
85
+ issueTrees={issueTrees}
86
+ error={error}
87
+ />
88
+ );
100
89
  };
101
90
 
102
91
  return <div className={b({expanded: !preview})}>{renderContent()}</div>;
@@ -1,13 +1,14 @@
1
1
  import cn from 'bem-cn-lite';
2
2
 
3
- import {Button, Icon} from '@gravity-ui/uikit';
3
+ import {Button, Icon, Link} from '@gravity-ui/uikit';
4
4
 
5
5
  import updateArrow from '../../../../../assets/icons/update-arrow.svg';
6
6
 
7
- import {SelfCheckResult} from '../../../../../types/api/healthcheck';
8
- import type {IIssuesTree} from '../../../../../types/store/healthcheck';
9
-
10
- import {PreviewItem} from './PreviewItem';
7
+ import {SelfCheckResult, type StatusFlag} from '../../../../../types/api/healthcheck';
8
+ import type {IResponseError} from '../../../../../types/api/error';
9
+ import {DiagnosticCard} from '../../../../../components/DiagnosticCard/DiagnosticCard';
10
+ import EntityStatus from '../../../../../components/EntityStatus/EntityStatus';
11
+ import {ResponseError} from '../../../../../components/Errors/ResponseError';
11
12
 
12
13
  import i18n from '../i18n';
13
14
 
@@ -15,21 +16,18 @@ const b = cn('healthcheck');
15
16
 
16
17
  interface PreviewProps {
17
18
  selfCheckResult: SelfCheckResult;
18
- issuesTrees?: IIssuesTree[];
19
+ issuesStatistics?: [StatusFlag, number][];
19
20
  loading?: boolean;
20
- onShowMore?: (id: string) => void;
21
+ onShowMore?: VoidFunction;
21
22
  onUpdate: VoidFunction;
23
+ error?: IResponseError;
22
24
  }
23
25
 
24
26
  export const Preview = (props: PreviewProps) => {
25
- const {selfCheckResult, issuesTrees, loading, onShowMore, onUpdate} = props;
27
+ const {selfCheckResult, issuesStatistics, loading, onShowMore, onUpdate, error} = props;
26
28
 
27
29
  const isStatusOK = selfCheckResult === SelfCheckResult.GOOD;
28
30
 
29
- if (!issuesTrees) {
30
- return null;
31
- }
32
-
33
31
  const renderStatus = () => {
34
32
  const modifier = selfCheckResult.toLowerCase();
35
33
 
@@ -46,26 +44,40 @@ export const Preview = (props: PreviewProps) => {
46
44
  );
47
45
  };
48
46
 
49
- const renderFirstLevelIssues = () => {
47
+ const renderContent = () => {
48
+ if (error) {
49
+ return <ResponseError error={error} defaultMessage={i18n('no-data')} />;
50
+ }
51
+
50
52
  return (
51
53
  <div className={b('preview-content')}>
52
- {isStatusOK
53
- ? i18n('status_message.ok')
54
- : issuesTrees?.map((issueTree) => (
55
- <PreviewItem
56
- key={issueTree.id}
57
- data={issueTree}
58
- onShowMore={onShowMore}
59
- />
60
- ))}
54
+ {isStatusOK || !issuesStatistics || !issuesStatistics.length ? (
55
+ i18n('status_message.ok')
56
+ ) : (
57
+ <>
58
+ <div>Issues:</div>
59
+ <div className={b('issues-statistics')}>
60
+ {issuesStatistics.map(([status, count]) => (
61
+ <EntityStatus
62
+ key={status}
63
+ mode="icons"
64
+ status={status}
65
+ label={count.toString()}
66
+ size="l"
67
+ />
68
+ ))}
69
+ </div>
70
+ <Link onClick={() => onShowMore?.()}>{i18n('label.show-details')}</Link>
71
+ </>
72
+ )}
61
73
  </div>
62
74
  );
63
75
  };
64
76
 
65
77
  return (
66
- <div className={b('preview')}>
78
+ <DiagnosticCard className={b('preview')}>
67
79
  {renderStatus()}
68
- {renderFirstLevelIssues()}
69
- </div>
80
+ {renderContent()}
81
+ </DiagnosticCard>
70
82
  );
71
83
  };
@@ -0,0 +1,154 @@
1
+ import cn from 'bem-cn-lite';
2
+ import {useCallback} from 'react';
3
+ import {useDispatch} from 'react-redux';
4
+
5
+ import {Loader} from '@gravity-ui/uikit';
6
+
7
+ import {InfoViewer} from '../../../../components/InfoViewer';
8
+ import {PoolUsage} from '../../../../components/PoolUsage/PoolUsage';
9
+ import {Tablet} from '../../../../components/Tablet';
10
+ import EntityStatus from '../../../../components/EntityStatus/EntityStatus';
11
+ import {formatCPU} from '../../../../utils';
12
+ import {TABLET_STATES, TENANT_DEFAULT_TITLE} from '../../../../utils/constants';
13
+ import {bytesToGB} from '../../../../utils/utils';
14
+ import {mapDatabaseTypeToDBName} from '../../utils/schema';
15
+ import {useAutofetcher, useTypedSelector} from '../../../../utils/hooks';
16
+ import {ETabletVolatileState} from '../../../../types/api/tenant';
17
+ import {getTenantInfo, setDataWasNotLoaded} from '../../../../store/reducers/tenant/tenant';
18
+
19
+ import i18n from './i18n';
20
+ import './TenantOverview.scss';
21
+
22
+ const b = cn('tenant-overview');
23
+
24
+ interface TenantOverviewProps {
25
+ tenantName: string;
26
+ additionalTenantInfo?: any;
27
+ }
28
+
29
+ export function TenantOverview({tenantName, additionalTenantInfo}: TenantOverviewProps) {
30
+ const {tenant, loading, wasLoaded} = useTypedSelector((state) => state.tenant);
31
+ const {autorefresh} = useTypedSelector((state) => state.schema);
32
+ const dispatch = useDispatch();
33
+ const fetchTenant = useCallback(
34
+ (isBackground = true) => {
35
+ if (!isBackground) {
36
+ dispatch(setDataWasNotLoaded());
37
+ }
38
+ dispatch(getTenantInfo({path: tenantName}));
39
+ },
40
+ [dispatch, tenantName],
41
+ );
42
+
43
+ useAutofetcher(fetchTenant, [fetchTenant], autorefresh);
44
+
45
+ const {
46
+ Metrics = {},
47
+ PoolStats,
48
+ StateStats = [],
49
+ MemoryUsed,
50
+ Name,
51
+ State,
52
+ CoresUsed,
53
+ StorageGroups,
54
+ StorageAllocatedSize,
55
+ Type,
56
+ SystemTablets,
57
+ } = tenant || {};
58
+
59
+ const tenantType = mapDatabaseTypeToDBName(Type);
60
+ const memoryRaw = MemoryUsed ?? Metrics.Memory;
61
+
62
+ const memory = (memoryRaw && bytesToGB(memoryRaw)) || i18n('no-data');
63
+ const storage = (Metrics.Storage && bytesToGB(Metrics.Storage)) || i18n('no-data');
64
+ const storageGroups = StorageGroups ?? i18n('no-data');
65
+ const blobStorage =
66
+ (StorageAllocatedSize && bytesToGB(StorageAllocatedSize)) || i18n('no-data');
67
+ const storageEfficiency =
68
+ Metrics.Storage && StorageAllocatedSize
69
+ ? `${((parseInt(Metrics.Storage) * 100) / parseInt(StorageAllocatedSize)).toFixed(2)}%`
70
+ : i18n('no-data');
71
+
72
+ const cpuRaw = CoresUsed !== undefined ? Number(CoresUsed) * 1_000_000 : Metrics.CPU;
73
+
74
+ const cpu = formatCPU(cpuRaw);
75
+
76
+ const metricsInfo = [
77
+ {label: 'Type', value: Type},
78
+ {label: 'Memory', value: memory},
79
+ {label: 'CPU', value: cpu},
80
+ {label: 'Tablet storage', value: storage},
81
+ {label: 'Storage groups', value: storageGroups},
82
+ {label: 'Blob storage', value: blobStorage},
83
+ {label: 'Storage efficiency', value: storageEfficiency},
84
+ ];
85
+
86
+ const tabletsInfo = StateStats.filter(
87
+ (item): item is {VolatileState: ETabletVolatileState; Count: number} => {
88
+ return item.VolatileState !== undefined && item.Count !== undefined;
89
+ },
90
+ ).map((info) => {
91
+ return {label: TABLET_STATES[info.VolatileState], value: info.Count};
92
+ });
93
+
94
+ const renderName = () => {
95
+ return (
96
+ <div className={b('tenant-name-wrapper')}>
97
+ <EntityStatus
98
+ status={State}
99
+ name={Name || TENANT_DEFAULT_TITLE}
100
+ withLeftTrim
101
+ hasClipboardButton={Boolean(tenant)}
102
+ clipboardButtonAlwaysVisible
103
+ />
104
+ </div>
105
+ );
106
+ };
107
+
108
+ if (loading && !wasLoaded) {
109
+ return (
110
+ <div className={b('loader')}>
111
+ <Loader size="m" />
112
+ </div>
113
+ );
114
+ }
115
+
116
+ return (
117
+ <div className={b()}>
118
+ <div className={b('top-label')}>{tenantType}</div>
119
+ <div className={b('top')}>
120
+ {renderName()}
121
+ {tenant && additionalTenantInfo && additionalTenantInfo(tenant.Name, tenant.Type)}
122
+ </div>
123
+ <div className={b('system-tablets')}>
124
+ {SystemTablets &&
125
+ SystemTablets.map((tablet, tabletIndex) => (
126
+ <Tablet key={tabletIndex} tablet={tablet} tenantName={Name} />
127
+ ))}
128
+ </div>
129
+ <div className={b('common-info')}>
130
+ <div>
131
+ <div className={b('section-title')}>{i18n('title.pools')}</div>
132
+ {PoolStats ? (
133
+ <div className={b('section', {pools: true})}>
134
+ {PoolStats.map((pool, poolIndex) => (
135
+ <PoolUsage key={poolIndex} data={pool} />
136
+ ))}
137
+ </div>
138
+ ) : (
139
+ <div className="error">{i18n('no-pools-data')}</div>
140
+ )}
141
+ </div>
142
+ <InfoViewer
143
+ title={i18n('title.metrics')}
144
+ className={b('section', {metrics: true})}
145
+ info={metricsInfo}
146
+ />
147
+
148
+ <div className={b('section')}>
149
+ <InfoViewer info={tabletsInfo} title="Tablets" />
150
+ </div>
151
+ </div>
152
+ </div>
153
+ );
154
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "no-data": "No data",
3
+ "no-pools-data": "No pools data",
4
+
5
+ "title.pools": "Pools",
6
+ "title.metrics": "Metrics"
7
+ }
@@ -0,0 +1,11 @@
1
+ import {i18n, Lang} from '../../../../../utils/i18n';
2
+
3
+ import en from './en.json';
4
+ import ru from './ru.json';
5
+
6
+ const COMPONENT = 'ydb-diagnostics-tenant-overview';
7
+
8
+ i18n.registerKeyset(Lang.En, COMPONENT, en);
9
+ i18n.registerKeyset(Lang.Ru, COMPONENT, ru);
10
+
11
+ export default i18n.keyset(COMPONENT);
@@ -0,0 +1,7 @@
1
+ {
2
+ "no-data": "Нет данных",
3
+ "no-pools-data": "Нет данных о пулах",
4
+
5
+ "title.pools": "Пулы",
6
+ "title.metrics": "Метрики"
7
+ }
@@ -9,11 +9,14 @@
9
9
  @include flex-container();
10
10
  @include table-styles;
11
11
 
12
+ &__table-row {
13
+ cursor: pointer;
14
+ }
15
+
12
16
  &__query {
13
17
  overflow: hidden;
14
18
  flex-grow: 1;
15
19
 
16
- cursor: pointer;
17
20
  white-space: pre;
18
21
  text-overflow: ellipsis;
19
22
  }