ydb-embedded-ui 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/components/EntityStatus/EntityStatus.js +28 -3
- package/dist/components/EntityStatus/EntityStatus.scss +22 -1
- package/dist/components/NodesViewer/NodesViewer.js +1 -0
- package/dist/containers/Nodes/Nodes.js +1 -0
- package/dist/containers/Storage/StorageFilter/StorageFilter.tsx +1 -0
- package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.tsx +2 -3
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.scss +7 -1
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.tsx +86 -0
- package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuePreview/IssuePreview.tsx +34 -0
- package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuePreview/index.ts +1 -0
- package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesList/IssuesList.tsx +69 -0
- package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesList/index.ts +1 -0
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/Preview.tsx +80 -0
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/index.ts +1 -0
- package/dist/containers/Tenant/Diagnostics/Healthcheck/i18n/en.json +11 -0
- package/dist/containers/Tenant/Diagnostics/Healthcheck/i18n/index.ts +11 -0
- package/dist/containers/Tenant/Diagnostics/Healthcheck/i18n/ru.json +11 -0
- package/dist/containers/Tenant/Diagnostics/Healthcheck/index.ts +1 -0
- package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +4 -21
- package/dist/containers/Tenant/QueryEditor/QueryExplain/QueryExplain.js +18 -19
- package/dist/containers/Tenant/QueryEditor/QueryResult/QueryResult.scss +1 -0
- package/dist/containers/Tenants/Tenants.js +1 -0
- package/dist/services/api.d.ts +9 -0
- package/dist/types/api/healthcheck.ts +91 -0
- package/dist/types/store/healthcheck.ts +3 -0
- package/dist/utils/hooks/index.ts +1 -0
- package/dist/utils/hooks/useAutofetcher.ts +29 -0
- package/package.json +1 -1
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.js +0 -195
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,23 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [2.1.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v2.0.0...v2.1.0) (2022-10-04)
|
4
|
+
|
5
|
+
|
6
|
+
### Features
|
7
|
+
|
8
|
+
* autofocus all text search fields ([a38ee84](https://github.com/ydb-platform/ydb-embedded-ui/commit/a38ee84abad4202f5e9b8af897eb68d2c006233a))
|
9
|
+
* **Healthcheck:** display first level issues in overview ([10b4bf5](https://github.com/ydb-platform/ydb-embedded-ui/commit/10b4bf5d15d32f028702ff8cfecca0e06bc5616f))
|
10
|
+
|
11
|
+
|
12
|
+
### Bug Fixes
|
13
|
+
|
14
|
+
* fix production assets paths ([8eaad0f](https://github.com/ydb-platform/ydb-embedded-ui/commit/8eaad0f1db109c4cf3cbf7d11ad32ea335a6b0c1))
|
15
|
+
* **Healthcheck:** add translations ([75f9851](https://github.com/ydb-platform/ydb-embedded-ui/commit/75f9851a35766ef692805a6f154d40340b003487))
|
16
|
+
* move eslint hooks rule extension to src config ([179b81d](https://github.com/ydb-platform/ydb-embedded-ui/commit/179b81d60adf422addc8d72f947800c72bd3e4c5))
|
17
|
+
* **QueryEditor:** disable fullscreen button for empty result ([4825b5b](https://github.com/ydb-platform/ydb-embedded-ui/commit/4825b5b8dcb89fcafd828dabbace521ddc429922))
|
18
|
+
* **QueryEditor:** fix query stats spacings ([b836d72](https://github.com/ydb-platform/ydb-embedded-ui/commit/b836d72824a791b3fde2b9e4585c6c9b42385265))
|
19
|
+
* **useAutofetcher:** private autofetcher instance for each usage ([3f34b7a](https://github.com/ydb-platform/ydb-embedded-ui/commit/3f34b7aee2042562a42e6d1a7daf03ffddd888c0))
|
20
|
+
|
3
21
|
## [2.0.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.14.2...v2.0.0) (2022-09-26)
|
4
22
|
|
5
23
|
|
@@ -2,10 +2,22 @@ import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
3
3
|
import cn from 'bem-cn-lite';
|
4
4
|
import {Link} from 'react-router-dom';
|
5
|
-
import {ClipboardButton, Link as ExternalLink, Button} from '@gravity-ui/uikit';
|
5
|
+
import {ClipboardButton, Link as ExternalLink, Button, Icon} from '@gravity-ui/uikit';
|
6
|
+
|
7
|
+
import circleInfoIcon from '../../assets/icons/circle-info.svg';
|
8
|
+
import circleExclamationIcon from '../../assets/icons/circle-exclamation.svg';
|
9
|
+
import triangleExclamationIcon from '../../assets/icons/triangle-exclamation.svg';
|
10
|
+
import circleTimesIcon from '../../assets/icons/circle-xmark.svg';
|
6
11
|
|
7
12
|
import './EntityStatus.scss';
|
8
13
|
|
14
|
+
const icons = {
|
15
|
+
BLUE: circleInfoIcon,
|
16
|
+
YELLOW: circleExclamationIcon,
|
17
|
+
ORANGE: triangleExclamationIcon,
|
18
|
+
RED: circleTimesIcon,
|
19
|
+
};
|
20
|
+
|
9
21
|
const b = cn('entity-status');
|
10
22
|
|
11
23
|
class EntityStatus extends React.Component {
|
@@ -22,6 +34,7 @@ class EntityStatus extends React.Component {
|
|
22
34
|
showStatus: PropTypes.bool,
|
23
35
|
externalLink: PropTypes.bool,
|
24
36
|
className: PropTypes.string,
|
37
|
+
mode: PropTypes.oneOf(['color', 'icons']),
|
25
38
|
};
|
26
39
|
|
27
40
|
static defaultProps = {
|
@@ -31,15 +44,27 @@ class EntityStatus extends React.Component {
|
|
31
44
|
label: '',
|
32
45
|
showStatus: true,
|
33
46
|
externalLink: false,
|
47
|
+
mode: 'color',
|
34
48
|
};
|
35
49
|
renderIcon() {
|
36
|
-
const {status, size, showStatus} = this.props;
|
50
|
+
const {status, size, showStatus, mode} = this.props;
|
37
51
|
|
38
52
|
if (!showStatus) {
|
39
53
|
return null;
|
40
54
|
}
|
41
55
|
|
42
|
-
|
56
|
+
const modifiers = {state: status.toLowerCase(), size};
|
57
|
+
|
58
|
+
if (mode === 'icons' && icons[status]) {
|
59
|
+
return (
|
60
|
+
<Icon
|
61
|
+
className={b('status-icon', modifiers)}
|
62
|
+
data={icons[status]}
|
63
|
+
/>
|
64
|
+
);
|
65
|
+
}
|
66
|
+
|
67
|
+
return <div className={b('status-color', modifiers)} />;
|
43
68
|
}
|
44
69
|
renderStatusLink() {
|
45
70
|
const {iconPath} = this.props;
|
@@ -57,6 +57,7 @@
|
|
57
57
|
white-space: nowrap;
|
58
58
|
}
|
59
59
|
|
60
|
+
&__status-color,
|
60
61
|
&__status-icon {
|
61
62
|
margin-right: 8px;
|
62
63
|
|
@@ -64,19 +65,24 @@
|
|
64
65
|
&_size_xs {
|
65
66
|
aspect-ratio: 1;
|
66
67
|
|
68
|
+
width: 12px;
|
67
69
|
height: 12px;
|
68
70
|
}
|
69
71
|
&_size_s {
|
70
72
|
aspect-ratio: 1;
|
71
73
|
|
74
|
+
width: 16px;
|
72
75
|
height: 16px;
|
73
76
|
}
|
74
77
|
&_size_m {
|
75
78
|
aspect-ratio: 1;
|
76
79
|
|
80
|
+
width: 18px;
|
77
81
|
height: 18px;
|
78
82
|
}
|
83
|
+
}
|
79
84
|
|
85
|
+
&__status-color {
|
80
86
|
&_state_running,
|
81
87
|
&_state_green {
|
82
88
|
background-color: var(--yc-color-infographics-positive-heavy);
|
@@ -96,7 +102,22 @@
|
|
96
102
|
background: var(--yc-color-text-complementary);
|
97
103
|
}
|
98
104
|
&_state_orange {
|
99
|
-
background: var(--yc-color-
|
105
|
+
background: var(--yc-color-base-warning-orange);
|
106
|
+
}
|
107
|
+
}
|
108
|
+
|
109
|
+
&__status-icon {
|
110
|
+
&_state_blue {
|
111
|
+
color: var(--yc-color-infographics-info-heavy);
|
112
|
+
}
|
113
|
+
&_state_yellow {
|
114
|
+
color: var(--yc-color-infographics-warning-heavy);
|
115
|
+
}
|
116
|
+
&_state_orange {
|
117
|
+
color: var(--yc-color-base-warning-orange);
|
118
|
+
}
|
119
|
+
&_state_red {
|
120
|
+
color: var(--yc-color-infographics-danger-heavy);
|
100
121
|
}
|
101
122
|
}
|
102
123
|
|
@@ -106,6 +106,7 @@ class NodesViewer extends React.PureComponent {
|
|
106
106
|
text={searchQuery}
|
107
107
|
onUpdate={handleSearchQuery}
|
108
108
|
hasClear
|
109
|
+
autoFocus
|
109
110
|
/>
|
110
111
|
<ProblemFilter value={filter} onChange={this.onChangeProblemFilter} />
|
111
112
|
<Label theme="info" size="m">{`Nodes: ${nodesToShow.length}`}</Label>
|
@@ -87,6 +87,7 @@ class Nodes extends React.Component {
|
|
87
87
|
text={searchQuery}
|
88
88
|
onUpdate={this.handleSearchQueryChange}
|
89
89
|
hasClear
|
90
|
+
autoFocus
|
90
91
|
/>
|
91
92
|
<ProblemFilter value={filter} onChange={this.handleFilterChange} />
|
92
93
|
<Label theme="info" size="m">{`Nodes: ${nodes?.length}`}</Label>
|
@@ -8,8 +8,7 @@ import type {EPathType} from '../../../../types/api/schema';
|
|
8
8
|
//@ts-ignore
|
9
9
|
import Icon from '../../../../components/Icon/Icon';
|
10
10
|
import Overview from '../Overview/Overview';
|
11
|
-
|
12
|
-
import Healthcheck from '../Healthcheck/Healthcheck';
|
11
|
+
import {Healthcheck} from '../Healthcheck';
|
13
12
|
//@ts-ignore
|
14
13
|
import TenantOverview from '../TenantOverview/TenantOverview';
|
15
14
|
|
@@ -42,7 +41,7 @@ function DetailedOverview(props: DetailedOverviewProps) {
|
|
42
41
|
const renderModal = () => {
|
43
42
|
return (
|
44
43
|
<Modal open={isModalVisible} onClose={closeModalHandler} className={b('modal')}>
|
45
|
-
<Healthcheck tenant={props.tenantName} />
|
44
|
+
<Healthcheck tenant={props.tenantName} fetchData={false} />
|
46
45
|
<Button
|
47
46
|
className={b('close-modal-button')}
|
48
47
|
onClick={closeModalHandler}
|
@@ -0,0 +1,86 @@
|
|
1
|
+
import {useCallback} from 'react';
|
2
|
+
import {useDispatch, useSelector} from 'react-redux';
|
3
|
+
import cn from 'bem-cn-lite';
|
4
|
+
|
5
|
+
import {Loader} from '@gravity-ui/uikit';
|
6
|
+
|
7
|
+
import {getHealthcheckInfo} from '../../../../store/reducers/healthcheckInfo';
|
8
|
+
import {useAutofetcher} from '../../../../utils/hooks';
|
9
|
+
|
10
|
+
import {IssuesList} from './IssuesList';
|
11
|
+
import {Preview} from './Preview';
|
12
|
+
|
13
|
+
import i18n from './i18n';
|
14
|
+
import './Healthcheck.scss';
|
15
|
+
|
16
|
+
interface HealthcheckProps {
|
17
|
+
tenant: string;
|
18
|
+
preview?: boolean;
|
19
|
+
fetchData?: boolean;
|
20
|
+
showMoreHandler?: VoidFunction;
|
21
|
+
}
|
22
|
+
|
23
|
+
const b = cn('healthcheck');
|
24
|
+
|
25
|
+
export const Healthcheck = (props: HealthcheckProps) => {
|
26
|
+
const {
|
27
|
+
tenant,
|
28
|
+
preview,
|
29
|
+
fetchData = true,
|
30
|
+
showMoreHandler,
|
31
|
+
} = props;
|
32
|
+
|
33
|
+
const dispatch = useDispatch();
|
34
|
+
|
35
|
+
const {data, loading, wasLoaded, error} = useSelector((state: any) => state.healthcheckInfo);
|
36
|
+
const {autorefresh} = useSelector((state: any) => state.schema);
|
37
|
+
|
38
|
+
const fetchHealthcheck = useCallback(() => {
|
39
|
+
if (fetchData) {
|
40
|
+
dispatch(getHealthcheckInfo(tenant));
|
41
|
+
}
|
42
|
+
}, [dispatch, fetchData, tenant]);
|
43
|
+
|
44
|
+
useAutofetcher(fetchHealthcheck, [fetchHealthcheck], autorefresh);
|
45
|
+
|
46
|
+
const renderContent = () => {
|
47
|
+
if (error) {
|
48
|
+
return error.statusText;
|
49
|
+
}
|
50
|
+
|
51
|
+
if (loading && !wasLoaded) {
|
52
|
+
return (
|
53
|
+
<div className={b('loader')}>
|
54
|
+
<Loader size="m" />
|
55
|
+
</div>
|
56
|
+
);
|
57
|
+
}
|
58
|
+
|
59
|
+
if (data && data['self_check_result']) {
|
60
|
+
return preview ? (
|
61
|
+
<Preview
|
62
|
+
data={data}
|
63
|
+
loading={loading}
|
64
|
+
onShowMore={showMoreHandler}
|
65
|
+
onUpdate={fetchHealthcheck}
|
66
|
+
/>
|
67
|
+
) : (
|
68
|
+
<IssuesList
|
69
|
+
data={data}
|
70
|
+
loading={loading}
|
71
|
+
onUpdate={fetchHealthcheck}
|
72
|
+
/>
|
73
|
+
);
|
74
|
+
}
|
75
|
+
|
76
|
+
return (
|
77
|
+
<div className="error">{i18n('no-data')}</div>
|
78
|
+
);
|
79
|
+
};
|
80
|
+
|
81
|
+
return (
|
82
|
+
<div className={b()}>
|
83
|
+
{renderContent()}
|
84
|
+
</div>
|
85
|
+
);
|
86
|
+
};
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import cn from 'bem-cn-lite';
|
2
|
+
|
3
|
+
import {Link, Text} from '@gravity-ui/uikit';
|
4
|
+
|
5
|
+
import EntityStatus from '../../../../../components/EntityStatus/EntityStatus';
|
6
|
+
import {IssueLog} from '../../../../../types/api/healthcheck';
|
7
|
+
|
8
|
+
import i18n from '../i18n';
|
9
|
+
|
10
|
+
const b = cn('healthcheck');
|
11
|
+
|
12
|
+
interface IssuePreviewProps {
|
13
|
+
data?: IssueLog;
|
14
|
+
onShowMore?: VoidFunction;
|
15
|
+
}
|
16
|
+
|
17
|
+
export const IssuePreview = (props: IssuePreviewProps) => {
|
18
|
+
const {
|
19
|
+
data,
|
20
|
+
onShowMore,
|
21
|
+
} = props;
|
22
|
+
|
23
|
+
if (!data) {
|
24
|
+
return null;
|
25
|
+
}
|
26
|
+
|
27
|
+
return (
|
28
|
+
<div className={b('issue-preview')}>
|
29
|
+
<EntityStatus mode="icons" status={data.status} name={data.type} />
|
30
|
+
<Text as="div" color="secondary" variant="body-2">{data.message}</Text>
|
31
|
+
<Link onClick={onShowMore}>{i18n('label.show-details')}</Link>
|
32
|
+
</div>
|
33
|
+
);
|
34
|
+
};
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './IssuePreview';
|
@@ -0,0 +1,69 @@
|
|
1
|
+
import cn from 'bem-cn-lite';
|
2
|
+
|
3
|
+
import {Button} from '@gravity-ui/uikit';
|
4
|
+
|
5
|
+
import type {IHealthCheck} from '../../../../../types/store/healthcheck';
|
6
|
+
|
7
|
+
import IssuesViewer from '../IssuesViewer/IssuesViewer';
|
8
|
+
|
9
|
+
import i18n from '../i18n';
|
10
|
+
|
11
|
+
const b = cn('healthcheck');
|
12
|
+
|
13
|
+
interface IssuesListProps {
|
14
|
+
data?: IHealthCheck;
|
15
|
+
loading?: boolean;
|
16
|
+
onUpdate: VoidFunction;
|
17
|
+
}
|
18
|
+
|
19
|
+
export const IssuesList = (props: IssuesListProps) => {
|
20
|
+
const {
|
21
|
+
data,
|
22
|
+
loading,
|
23
|
+
onUpdate,
|
24
|
+
} = props;
|
25
|
+
|
26
|
+
if (!data) {
|
27
|
+
return null;
|
28
|
+
}
|
29
|
+
|
30
|
+
const renderOverviewStatus = () => {
|
31
|
+
const {self_check_result: selfCheckResult} = data;
|
32
|
+
const modifier = selfCheckResult.toLowerCase();
|
33
|
+
|
34
|
+
return (
|
35
|
+
<div className={b('self-check-status')}>
|
36
|
+
<h3 className={b('self-check-status-label')}>{i18n('title.self-check-status')}</h3>
|
37
|
+
<div className={b('self-check-status-indicator', {[modifier]: true})} />
|
38
|
+
{selfCheckResult}
|
39
|
+
<div className={b('self-check-update')}>
|
40
|
+
<Button size="s" onClick={onUpdate} loading={loading}>
|
41
|
+
{i18n('label.update')}
|
42
|
+
</Button>
|
43
|
+
</div>
|
44
|
+
</div>
|
45
|
+
);
|
46
|
+
};
|
47
|
+
|
48
|
+
const renderHealthcheckIssues = () => {
|
49
|
+
const {issue_log: issueLog} = data;
|
50
|
+
|
51
|
+
if (!issueLog) {
|
52
|
+
return null;
|
53
|
+
}
|
54
|
+
|
55
|
+
return (
|
56
|
+
<div className={b('issues')}>
|
57
|
+
<h3>{i18n('title.issues')}</h3>
|
58
|
+
<IssuesViewer issues={issueLog} />
|
59
|
+
</div>
|
60
|
+
);
|
61
|
+
}
|
62
|
+
|
63
|
+
return (
|
64
|
+
<div className={b('issues-list')}>
|
65
|
+
{renderOverviewStatus()}
|
66
|
+
{renderHealthcheckIssues()}
|
67
|
+
</div>
|
68
|
+
);
|
69
|
+
};
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './IssuesList';
|
@@ -0,0 +1,80 @@
|
|
1
|
+
import {useMemo} from 'react';
|
2
|
+
import cn from 'bem-cn-lite';
|
3
|
+
|
4
|
+
import {Button} from '@gravity-ui/uikit';
|
5
|
+
|
6
|
+
import {SelfCheckResult} from '../../../../../types/api/healthcheck';
|
7
|
+
import type {IHealthCheck} from '../../../../../types/store/healthcheck';
|
8
|
+
|
9
|
+
import {IssuePreview} from '../IssuePreview';
|
10
|
+
|
11
|
+
import i18n from '../i18n';
|
12
|
+
|
13
|
+
const b = cn('healthcheck');
|
14
|
+
|
15
|
+
interface PreviewProps {
|
16
|
+
data?: IHealthCheck;
|
17
|
+
loading?: boolean;
|
18
|
+
onShowMore?: VoidFunction;
|
19
|
+
onUpdate: VoidFunction;
|
20
|
+
}
|
21
|
+
|
22
|
+
export const Preview = (props: PreviewProps) => {
|
23
|
+
const {
|
24
|
+
data,
|
25
|
+
loading,
|
26
|
+
onShowMore,
|
27
|
+
onUpdate,
|
28
|
+
} = props;
|
29
|
+
|
30
|
+
const selfCheckResult = data?.self_check_result || SelfCheckResult.UNSPECIFIED;
|
31
|
+
const isStatusOK = selfCheckResult === SelfCheckResult.GOOD;
|
32
|
+
|
33
|
+
const issuesLog = data?.issue_log;
|
34
|
+
const firstLevelIssues = useMemo(() => issuesLog?.filter(({level}) => level === 1), [issuesLog]);
|
35
|
+
|
36
|
+
if (!data) {
|
37
|
+
return null;
|
38
|
+
}
|
39
|
+
|
40
|
+
const renderStatus = () => {
|
41
|
+
const modifier = selfCheckResult.toLowerCase();
|
42
|
+
|
43
|
+
return (
|
44
|
+
<div className={b('status-wrapper')}>
|
45
|
+
<div className={b('preview-title')}>{i18n('title.healthcheck')}</div>
|
46
|
+
<div className={b('self-check-status-indicator', {[modifier]: true})}>
|
47
|
+
{isStatusOK ? i18n('ok') : i18n('error')}
|
48
|
+
</div>
|
49
|
+
<Button size="s" onClick={onUpdate} loading={loading}>
|
50
|
+
{i18n('label.update')}
|
51
|
+
</Button>
|
52
|
+
</div>
|
53
|
+
);
|
54
|
+
};
|
55
|
+
|
56
|
+
const renderFirstLevelIssues = () => {
|
57
|
+
return (
|
58
|
+
<div className={b('preview-content')}>
|
59
|
+
{
|
60
|
+
isStatusOK ?
|
61
|
+
i18n('status_massage.ok') :
|
62
|
+
firstLevelIssues?.map((issue) => (
|
63
|
+
<IssuePreview
|
64
|
+
key={issue.id}
|
65
|
+
data={issue}
|
66
|
+
onShowMore={onShowMore}
|
67
|
+
/>
|
68
|
+
))
|
69
|
+
}
|
70
|
+
</div>
|
71
|
+
);
|
72
|
+
};
|
73
|
+
|
74
|
+
return (
|
75
|
+
<div className={b('preview')}>
|
76
|
+
{renderStatus()}
|
77
|
+
{renderFirstLevelIssues()}
|
78
|
+
</div>
|
79
|
+
);
|
80
|
+
};
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './Preview';
|
@@ -0,0 +1,11 @@
|
|
1
|
+
{
|
2
|
+
"title.healthcheck": "Healthcheck",
|
3
|
+
"title.issues": "Issues",
|
4
|
+
"title.self-check-status": "Self check status",
|
5
|
+
"label.update": "Update",
|
6
|
+
"label.show-details": "Show details",
|
7
|
+
"status_massage.ok": "No issues have been found on this database",
|
8
|
+
"ok": "Ok",
|
9
|
+
"error": "Error",
|
10
|
+
"no-data": "no healthcheck data"
|
11
|
+
}
|
@@ -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-healthcheck';
|
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,11 @@
|
|
1
|
+
{
|
2
|
+
"title.healthcheck": "Healthcheck",
|
3
|
+
"title.issues": "Проблемы",
|
4
|
+
"title.self-check-status": "Статус самопроверки",
|
5
|
+
"label.update": "Обновить",
|
6
|
+
"label.show-details": "Посмотреть подробности",
|
7
|
+
"status_massage.ok": "В базе данных нет проблем",
|
8
|
+
"ok": "Ok",
|
9
|
+
"error": "Ошибка",
|
10
|
+
"no-data": "нет данных healthcheck"
|
11
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './Healthcheck';
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import {ReactNode,
|
1
|
+
import {ReactNode, useMemo} from 'react';
|
2
2
|
import {useDispatch, useSelector} from 'react-redux';
|
3
3
|
import cn from 'bem-cn-lite';
|
4
4
|
|
@@ -14,11 +14,11 @@ import {
|
|
14
14
|
|
15
15
|
import {EPathType} from '../../../../types/api/schema';
|
16
16
|
import {isColumnEntityType, isTableType} from '../../utils/schema';
|
17
|
-
import {AutoFetcher} from '../../../../utils/autofetcher';
|
18
17
|
//@ts-ignore
|
19
18
|
import {getSchema} from '../../../../store/reducers/schema';
|
20
19
|
//@ts-ignore
|
21
20
|
import {getOlapStats} from '../../../../store/reducers/olapStats';
|
21
|
+
import {useAutofetcher} from '../../../../utils/hooks';
|
22
22
|
|
23
23
|
import './Overview.scss';
|
24
24
|
|
@@ -57,8 +57,6 @@ interface OverviewProps {
|
|
57
57
|
|
58
58
|
const b = cn('kv-tenant-overview');
|
59
59
|
|
60
|
-
const autofetcher = new AutoFetcher();
|
61
|
-
|
62
60
|
function Overview(props: OverviewProps) {
|
63
61
|
const {tenantName, type} = props;
|
64
62
|
|
@@ -76,29 +74,14 @@ function Overview(props: OverviewProps) {
|
|
76
74
|
data: { result: olapStats } = { result: undefined },
|
77
75
|
} = useSelector((state: any) => state.olapStats);
|
78
76
|
|
79
|
-
|
77
|
+
useAutofetcher(() => {
|
80
78
|
const schemaPath = currentSchemaPath || tenantName;
|
81
79
|
dispatch(getSchema({path: schemaPath}));
|
82
80
|
|
83
81
|
if (isTableType(type) && isColumnEntityType(type)) {
|
84
82
|
dispatch(getOlapStats({path: schemaPath}));
|
85
83
|
}
|
86
|
-
}, [currentSchemaPath, dispatch, tenantName, type]);
|
87
|
-
|
88
|
-
useEffect(fetchOverviewData, [fetchOverviewData]);
|
89
|
-
|
90
|
-
useEffect(() => {
|
91
|
-
autofetcher.stop();
|
92
|
-
|
93
|
-
if (autorefresh) {
|
94
|
-
autofetcher.start();
|
95
|
-
autofetcher.fetch(() => fetchOverviewData());
|
96
|
-
}
|
97
|
-
|
98
|
-
return () => {
|
99
|
-
autofetcher.stop();
|
100
|
-
};
|
101
|
-
}, [autorefresh, fetchOverviewData]);
|
84
|
+
}, [currentSchemaPath, dispatch, tenantName, type], autorefresh);
|
102
85
|
|
103
86
|
const tableSchema =
|
104
87
|
currentItem?.PathDescription?.Table || currentItem?.PathDescription?.ColumnTableDescription;
|
@@ -141,16 +141,23 @@ function QueryExplain(props) {
|
|
141
141
|
);
|
142
142
|
};
|
143
143
|
|
144
|
-
const
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
144
|
+
const hasContent = () => {
|
145
|
+
switch (activeOption) {
|
146
|
+
case ExplainOptionIds.schema:
|
147
|
+
return Boolean(props.explain?.nodes?.length);
|
148
|
+
case ExplainOptionIds.json:
|
149
|
+
return Boolean(props.explain);
|
150
|
+
case ExplainOptionIds.ast:
|
151
|
+
return Boolean(props.ast);
|
152
|
+
default:
|
153
|
+
return false;
|
149
154
|
}
|
155
|
+
};
|
150
156
|
|
157
|
+
const renderTextExplain = () => {
|
151
158
|
const content = (
|
152
159
|
<JSONTree
|
153
|
-
data={explain
|
160
|
+
data={props.explain?.pristine}
|
154
161
|
isExpanded={() => true}
|
155
162
|
className={b('inspector')}
|
156
163
|
searchOptions={{
|
@@ -168,13 +175,6 @@ function QueryExplain(props) {
|
|
168
175
|
};
|
169
176
|
|
170
177
|
const renderAstExplain = () => {
|
171
|
-
if (!props.ast) {
|
172
|
-
return (
|
173
|
-
<div className={b('text-message')}>
|
174
|
-
There is no AST explanation for the request
|
175
|
-
</div>
|
176
|
-
);
|
177
|
-
}
|
178
178
|
const content = (
|
179
179
|
<div className={b('ast')}>
|
180
180
|
<MonacoEditor
|
@@ -196,11 +196,6 @@ function QueryExplain(props) {
|
|
196
196
|
const renderGraph = () => {
|
197
197
|
const {explain = {}, theme} = props;
|
198
198
|
const {links, nodes, version} = explain;
|
199
|
-
|
200
|
-
if (!explain.nodes?.length) {
|
201
|
-
return renderStub();
|
202
|
-
}
|
203
|
-
|
204
199
|
const content =
|
205
200
|
links && nodes && nodes.length ? (
|
206
201
|
<div
|
@@ -259,6 +254,10 @@ function QueryExplain(props) {
|
|
259
254
|
return renderError();
|
260
255
|
}
|
261
256
|
|
257
|
+
if (!hasContent()) {
|
258
|
+
return renderStub();
|
259
|
+
}
|
260
|
+
|
262
261
|
switch (activeOption) {
|
263
262
|
case ExplainOptionIds.json: {
|
264
263
|
return renderTextExplain();
|
@@ -293,7 +292,7 @@ function QueryExplain(props) {
|
|
293
292
|
)}
|
294
293
|
</div>
|
295
294
|
<div className={b('controls-left')}>
|
296
|
-
<EnableFullscreenButton disabled={Boolean(props.error)} />
|
295
|
+
<EnableFullscreenButton disabled={Boolean(props.error) || !hasContent()} />
|
297
296
|
<PaneVisibilityToggleButtons
|
298
297
|
onCollapse={props.onCollapseResults}
|
299
298
|
onExpand={props.onExpandResults}
|
package/dist/services/api.d.ts
CHANGED
@@ -30,6 +30,15 @@ interface Window {
|
|
30
30
|
},
|
31
31
|
axiosOptions?: AxiosOptions,
|
32
32
|
) => Promise<import('../types/api/query').QueryAPIResponse<Action, Schema>>;
|
33
|
+
getExplainQuery: (
|
34
|
+
query: string,
|
35
|
+
database: string,
|
36
|
+
) => Promise<import('../types/api/query').QueryAPIExplainResponse<'explain'>>;
|
37
|
+
getExplainQueryAst: (
|
38
|
+
query: string,
|
39
|
+
database: string,
|
40
|
+
) => Promise<import('../types/api/query').QueryAPIExplainResponse<'explain-ast'>>;
|
41
|
+
getHealthcheckInfo: (database: string) => Promise<import('../types/api/healthcheck').HealthCheckAPIResponse>,
|
33
42
|
[method: string]: Function;
|
34
43
|
};
|
35
44
|
}
|
@@ -0,0 +1,91 @@
|
|
1
|
+
export enum SelfCheckResult {
|
2
|
+
UNSPECIFIED = 'UNSPECIFIED',
|
3
|
+
GOOD = 'GOOD',
|
4
|
+
DEGRADED = 'DEGRADED',
|
5
|
+
MAINTENANCE_REQUIRED = 'MAINTENANCE_REQUIRED',
|
6
|
+
EMERGENCY = 'EMERGENCY',
|
7
|
+
}
|
8
|
+
|
9
|
+
enum StatusFlag {
|
10
|
+
UNSPECIFIED = 'UNSPECIFIED',
|
11
|
+
GREY = 'GREY',
|
12
|
+
GREEN = 'GREEN',
|
13
|
+
BLUE = 'BLUE',
|
14
|
+
YELLOW = 'YELLOW',
|
15
|
+
ORANGE = 'ORANGE',
|
16
|
+
RED = 'RED',
|
17
|
+
}
|
18
|
+
|
19
|
+
interface LocationNode {
|
20
|
+
id: number;
|
21
|
+
host: string;
|
22
|
+
port: number;
|
23
|
+
}
|
24
|
+
|
25
|
+
interface LocationStoragePDisk {
|
26
|
+
id: string;
|
27
|
+
path: string;
|
28
|
+
}
|
29
|
+
|
30
|
+
interface LocationStorageVDisk {
|
31
|
+
id: string;
|
32
|
+
pdisk: LocationStoragePDisk;
|
33
|
+
}
|
34
|
+
|
35
|
+
interface LocationStorageGroup {
|
36
|
+
id: string;
|
37
|
+
vdisk: LocationStorageVDisk;
|
38
|
+
}
|
39
|
+
|
40
|
+
interface LocationStoragePool {
|
41
|
+
name: string;
|
42
|
+
group: LocationStorageGroup;
|
43
|
+
}
|
44
|
+
|
45
|
+
interface LocationStorage {
|
46
|
+
node: LocationNode;
|
47
|
+
pool: LocationStoragePool;
|
48
|
+
}
|
49
|
+
|
50
|
+
interface LocationComputePool {
|
51
|
+
name: string;
|
52
|
+
}
|
53
|
+
|
54
|
+
interface LocationComputeTablet {
|
55
|
+
type: string;
|
56
|
+
id?: string[];
|
57
|
+
count: number;
|
58
|
+
}
|
59
|
+
|
60
|
+
interface LocationCompute {
|
61
|
+
node: LocationNode;
|
62
|
+
pool: LocationComputePool;
|
63
|
+
tablet: LocationComputeTablet;
|
64
|
+
}
|
65
|
+
|
66
|
+
interface LocationDatabase {
|
67
|
+
name: string;
|
68
|
+
}
|
69
|
+
|
70
|
+
interface Location {
|
71
|
+
storage: LocationStorage;
|
72
|
+
compute: LocationCompute;
|
73
|
+
database: LocationDatabase;
|
74
|
+
}
|
75
|
+
|
76
|
+
export interface IssueLog {
|
77
|
+
id: string;
|
78
|
+
status: StatusFlag;
|
79
|
+
message: string;
|
80
|
+
location: Location;
|
81
|
+
reason?: string[];
|
82
|
+
type: string;
|
83
|
+
level: number;
|
84
|
+
}
|
85
|
+
|
86
|
+
export interface HealthCheckAPIResponse {
|
87
|
+
// eslint-disable-next-line camelcase
|
88
|
+
self_check_result: SelfCheckResult;
|
89
|
+
// eslint-disable-next-line camelcase
|
90
|
+
issue_log?: IssueLog[];
|
91
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './useAutofetcher';
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import {DependencyList, useEffect, useRef} from 'react';
|
2
|
+
|
3
|
+
import {AutoFetcher} from '../autofetcher';
|
4
|
+
|
5
|
+
export const useAutofetcher = (fetchData: VoidFunction, deps: DependencyList, enabled = true) => {
|
6
|
+
const ref = useRef<AutoFetcher | null>(null);
|
7
|
+
|
8
|
+
if (ref.current === null) {
|
9
|
+
ref.current = new AutoFetcher();
|
10
|
+
}
|
11
|
+
|
12
|
+
const autofetcher = ref.current;
|
13
|
+
|
14
|
+
// initial fetch
|
15
|
+
useEffect(fetchData, deps); // eslint-disable-line react-hooks/exhaustive-deps
|
16
|
+
|
17
|
+
useEffect(() => {
|
18
|
+
autofetcher.stop();
|
19
|
+
|
20
|
+
if (enabled) {
|
21
|
+
autofetcher.start();
|
22
|
+
autofetcher.fetch(fetchData);
|
23
|
+
}
|
24
|
+
|
25
|
+
return () => {
|
26
|
+
autofetcher.stop();
|
27
|
+
};
|
28
|
+
}, [enabled, ...deps]); // eslint-disable-line react-hooks/exhaustive-deps
|
29
|
+
};
|
package/package.json
CHANGED
@@ -1,195 +0,0 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import PropTypes from 'prop-types';
|
3
|
-
import {connect} from 'react-redux';
|
4
|
-
import cn from 'bem-cn-lite';
|
5
|
-
|
6
|
-
import {Loader, Button} from '@gravity-ui/uikit';
|
7
|
-
|
8
|
-
import IssuesViewer from './IssuesViewer/IssuesViewer';
|
9
|
-
|
10
|
-
import {getHealthcheckInfo} from '../../../../store/reducers/healthcheckInfo';
|
11
|
-
import {hideTooltip, showTooltip} from '../../../../store/reducers/tooltip';
|
12
|
-
import {AutoFetcher} from '../../../../utils/autofetcher';
|
13
|
-
|
14
|
-
import './Healthcheck.scss';
|
15
|
-
|
16
|
-
const b = cn('healthcheck');
|
17
|
-
|
18
|
-
class Healthcheck extends React.Component {
|
19
|
-
static propTypes = {
|
20
|
-
data: PropTypes.object,
|
21
|
-
loading: PropTypes.bool,
|
22
|
-
wasLoaded: PropTypes.bool,
|
23
|
-
error: PropTypes.object,
|
24
|
-
getHealthcheckInfo: PropTypes.func,
|
25
|
-
tenant: PropTypes.string,
|
26
|
-
preview: PropTypes.bool,
|
27
|
-
showMoreHandler: PropTypes.func,
|
28
|
-
};
|
29
|
-
|
30
|
-
autofetcher;
|
31
|
-
|
32
|
-
componentDidMount() {
|
33
|
-
this.autofetcher = new AutoFetcher();
|
34
|
-
this.fetchHealthcheck();
|
35
|
-
if (this.props.autorefresh) {
|
36
|
-
this.autofetcher.start();
|
37
|
-
this.autofetcher.fetch(() => this.fetchHealthcheck());
|
38
|
-
}
|
39
|
-
}
|
40
|
-
|
41
|
-
componentDidUpdate(prevProps) {
|
42
|
-
const {autorefresh} = this.props;
|
43
|
-
|
44
|
-
if (autorefresh && !prevProps.autorefresh) {
|
45
|
-
this.fetchHealthcheck();
|
46
|
-
this.autofetcher.stop();
|
47
|
-
this.autofetcher.start();
|
48
|
-
this.autofetcher.fetch(() => this.fetchHealthcheck());
|
49
|
-
}
|
50
|
-
if (!autorefresh && prevProps.autorefresh) {
|
51
|
-
this.autofetcher.stop();
|
52
|
-
}
|
53
|
-
}
|
54
|
-
|
55
|
-
componentWillUnmount() {
|
56
|
-
this.autofetcher.stop();
|
57
|
-
}
|
58
|
-
|
59
|
-
fetchHealthcheck = () => {
|
60
|
-
const {tenant, getHealthcheckInfo} = this.props;
|
61
|
-
getHealthcheckInfo(tenant);
|
62
|
-
};
|
63
|
-
|
64
|
-
renderLoader() {
|
65
|
-
return (
|
66
|
-
<div className={b('loader')}>
|
67
|
-
<Loader size="m" />
|
68
|
-
</div>
|
69
|
-
);
|
70
|
-
}
|
71
|
-
|
72
|
-
renderUpdateButton() {
|
73
|
-
const {loading} = this.props;
|
74
|
-
return (
|
75
|
-
<Button size="s" onClick={this.fetchHealthcheck} loading={loading}>
|
76
|
-
Update
|
77
|
-
</Button>
|
78
|
-
);
|
79
|
-
}
|
80
|
-
|
81
|
-
renderPreview = () => {
|
82
|
-
const {data, showMoreHandler} = this.props;
|
83
|
-
const {self_check_result: selfCheckResult} = data;
|
84
|
-
const modifier = selfCheckResult.toLowerCase();
|
85
|
-
|
86
|
-
const statusOk = selfCheckResult === 'GOOD';
|
87
|
-
const text = statusOk
|
88
|
-
? 'No issues have been found on this database.'
|
89
|
-
: 'Several issues have been found on this database.';
|
90
|
-
|
91
|
-
return (
|
92
|
-
<div>
|
93
|
-
<div className={b('status-wrapper')}>
|
94
|
-
<div className={b('preview-title')}>Healthcheck</div>
|
95
|
-
<div className={b('self-check-status-indicator', {[modifier]: true})}>
|
96
|
-
{statusOk ? 'Ok' : 'Error'}
|
97
|
-
</div>
|
98
|
-
{this.renderUpdateButton()}
|
99
|
-
</div>
|
100
|
-
<div className={b('preview-content')}>
|
101
|
-
{text}
|
102
|
-
{!statusOk && (
|
103
|
-
<Button
|
104
|
-
view="flat-info"
|
105
|
-
onClick={showMoreHandler}
|
106
|
-
size="s"
|
107
|
-
>
|
108
|
-
Show details
|
109
|
-
</Button>
|
110
|
-
)}
|
111
|
-
</div>
|
112
|
-
</div>
|
113
|
-
);
|
114
|
-
};
|
115
|
-
|
116
|
-
renderOverviewStatus = () => {
|
117
|
-
const {data} = this.props;
|
118
|
-
const {self_check_result: selfCheckResult} = data;
|
119
|
-
const modifier = selfCheckResult.toLowerCase();
|
120
|
-
|
121
|
-
return (
|
122
|
-
<div className={b('self-check-status')}>
|
123
|
-
<h3 className={b('self-check-status-label')}>Self check status</h3>
|
124
|
-
<div className={b('self-check-status-indicator', {[modifier]: true})} />
|
125
|
-
{selfCheckResult}
|
126
|
-
<div className={b('self-check-update')}>{this.renderUpdateButton()}</div>
|
127
|
-
</div>
|
128
|
-
);
|
129
|
-
};
|
130
|
-
|
131
|
-
renderHealthcheckIssues() {
|
132
|
-
const {data, showTooltip, hideTooltip} = this.props;
|
133
|
-
const {issue_log: issueLog} = data;
|
134
|
-
|
135
|
-
if (!issueLog) {
|
136
|
-
return null;
|
137
|
-
}
|
138
|
-
|
139
|
-
return (
|
140
|
-
<div className={b('issues')}>
|
141
|
-
<h3>Issues</h3>
|
142
|
-
<IssuesViewer
|
143
|
-
issues={issueLog}
|
144
|
-
showTooltip={showTooltip}
|
145
|
-
hideTooltip={hideTooltip}
|
146
|
-
/>
|
147
|
-
</div>
|
148
|
-
);
|
149
|
-
}
|
150
|
-
|
151
|
-
renderContent = () => {
|
152
|
-
const {preview} = this.props;
|
153
|
-
return preview ? (
|
154
|
-
this.renderPreview()
|
155
|
-
) : (
|
156
|
-
<div className={b()}>
|
157
|
-
{this.renderOverviewStatus()}
|
158
|
-
{this.renderHealthcheckIssues()}
|
159
|
-
</div>
|
160
|
-
);
|
161
|
-
};
|
162
|
-
|
163
|
-
render() {
|
164
|
-
const {error, data, loading, wasLoaded} = this.props;
|
165
|
-
|
166
|
-
if (error) {
|
167
|
-
return <div>{error.statusText}</div>;
|
168
|
-
} else if (data && data['self_check_result']) {
|
169
|
-
return this.renderContent();
|
170
|
-
} else if (loading && !wasLoaded) {
|
171
|
-
return this.renderLoader();
|
172
|
-
} else return <div className="error">no healthcheck data</div>;
|
173
|
-
}
|
174
|
-
}
|
175
|
-
|
176
|
-
function mapStateToProps(state) {
|
177
|
-
const {data, loading, wasLoaded, error} = state.healthcheckInfo;
|
178
|
-
const {autorefresh} = state.schema;
|
179
|
-
|
180
|
-
return {
|
181
|
-
data,
|
182
|
-
loading,
|
183
|
-
wasLoaded,
|
184
|
-
error,
|
185
|
-
autorefresh,
|
186
|
-
};
|
187
|
-
}
|
188
|
-
|
189
|
-
const mapDispatchToProps = {
|
190
|
-
getHealthcheckInfo,
|
191
|
-
hideTooltip,
|
192
|
-
showTooltip,
|
193
|
-
};
|
194
|
-
|
195
|
-
export default connect(mapStateToProps, mapDispatchToProps)(Healthcheck);
|