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.
- package/CHANGELOG.md +14 -0
- package/dist/components/DiagnosticCard/DiagnosticCard.scss +5 -0
- package/dist/components/DiagnosticCard/DiagnosticCard.tsx +17 -0
- package/dist/components/EntityStatus/EntityStatus.js +2 -2
- package/dist/components/EntityStatus/EntityStatus.scss +15 -0
- package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.tsx +3 -11
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Details/Details.tsx +20 -11
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.scss +14 -1
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.tsx +26 -37
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/Preview.tsx +37 -25
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +154 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json +7 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/index.ts +11 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/ru.json +7 -0
- package/dist/containers/Tenant/Query/QueriesHistory/QueriesHistory.scss +4 -1
- package/dist/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx +42 -11
- package/dist/containers/Tenant/Query/QueryEditor/QueryEditor.js +1 -1
- package/dist/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.tsx +11 -7
- package/dist/containers/Tenant/Query/i18n/en.json +3 -0
- package/dist/containers/Tenant/Query/i18n/ru.json +3 -0
- package/dist/containers/Tenants/Tenants.tsx +3 -1
- package/dist/containers/UserSettings/Setting.tsx +9 -2
- package/dist/containers/UserSettings/i18n/en.json +5 -1
- package/dist/containers/UserSettings/i18n/ru.json +5 -1
- package/dist/containers/UserSettings/settings.ts +25 -0
- package/dist/services/api.ts +16 -16
- package/dist/store/reducers/executeQuery.ts +33 -7
- package/dist/store/reducers/explainQuery.ts +12 -4
- package/dist/store/reducers/healthcheckInfo.ts +27 -11
- package/dist/store/reducers/settings/settings.ts +4 -10
- package/dist/store/reducers/tenant/tenant.ts +19 -1
- package/dist/store/reducers/tenant/types.ts +3 -1
- package/dist/store/reducers/tenants/selectors.ts +1 -1
- package/dist/store/reducers/tenants/utils.ts +2 -2
- package/dist/types/api/tenant.ts +19 -20
- package/dist/types/store/executeQuery.ts +11 -1
- package/dist/types/store/query.ts +2 -1
- package/dist/utils/autofetcher.ts +7 -7
- package/dist/utils/constants.ts +2 -1
- package/dist/utils/hooks/i18n/en.json +1 -1
- package/dist/utils/hooks/i18n/ru.json +1 -1
- package/dist/utils/hooks/useQueryModes.ts +4 -2
- package/dist/utils/i18n/i18n.ts +10 -4
- package/dist/utils/query.ts +14 -0
- package/dist/utils/settings.ts +10 -0
- package/package.json +1 -1
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/PreviewItem/PreviewItem.tsx +0 -33
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/PreviewItem/index.ts +0 -1
- 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,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
|
-
|
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 = (
|
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
|
-
|
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,
|
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
|
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
|
-
|
44
|
-
|
45
|
-
|
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
|
-
{
|
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
|
-
&
|
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
|
-
|
12
|
-
|
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
|
-
|
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
|
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
|
41
|
-
const
|
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
|
-
<
|
67
|
+
<DiagnosticCard className={b('loader')}>
|
76
68
|
<Loader size="m" />
|
77
|
-
</
|
69
|
+
</DiagnosticCard>
|
78
70
|
);
|
79
71
|
}
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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 {
|
9
|
-
|
10
|
-
import
|
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
|
-
|
19
|
+
issuesStatistics?: [StatusFlag, number][];
|
19
20
|
loading?: boolean;
|
20
|
-
onShowMore?:
|
21
|
+
onShowMore?: VoidFunction;
|
21
22
|
onUpdate: VoidFunction;
|
23
|
+
error?: IResponseError;
|
22
24
|
}
|
23
25
|
|
24
26
|
export const Preview = (props: PreviewProps) => {
|
25
|
-
const {selfCheckResult,
|
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
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
<
|
78
|
+
<DiagnosticCard className={b('preview')}>
|
67
79
|
{renderStatus()}
|
68
|
-
{
|
69
|
-
</
|
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,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);
|