ydb-embedded-ui 1.1.2 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +26 -0
- package/dist/components/FullNodeViewer/FullNodeViewer.js +1 -1
- package/dist/components/TableSkeleton/TableSkeleton.scss +38 -0
- package/dist/components/TableSkeleton/TableSkeleton.tsx +29 -0
- package/dist/containers/Node/NodeStructure/Pdisk.tsx +4 -4
- package/dist/containers/Storage/Storage.js +42 -37
- package/dist/containers/Storage/Storage.scss +0 -6
- package/dist/containers/Storage/Vdisk/Vdisk.js +12 -5
- package/dist/services/api.js +4 -1
- package/dist/store/reducers/storage.js +5 -2
- package/dist/utils/autofetcher.ts +11 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,31 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
### [1.2.1](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.2.0...v1.2.1) (2022-04-27)
|
4
|
+
|
5
|
+
|
6
|
+
### Bug Fixes
|
7
|
+
|
8
|
+
* **Vdisk:** should not fail if no node id passed ([d66686d](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/d66686d0cbd9f61c4e106f6775db2fca226c922f))
|
9
|
+
|
10
|
+
## [1.2.0](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.1.3...v1.2.0) (2022-04-26)
|
11
|
+
|
12
|
+
|
13
|
+
### Features
|
14
|
+
|
15
|
+
* **Storage:** smoother loading state for storage table ([f7f38c4](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/f7f38c455dd9abc3f898048081e90af9b460f922))
|
16
|
+
|
17
|
+
|
18
|
+
### Bug Fixes
|
19
|
+
|
20
|
+
* prevent ghost autofetch ([153d829](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/153d8291d315f1dab001a69981a12e30d3d2aea9))
|
21
|
+
|
22
|
+
### [1.1.3](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.1.2...v1.1.3) (2022-04-20)
|
23
|
+
|
24
|
+
|
25
|
+
### Bug Fixes
|
26
|
+
|
27
|
+
* should prepare internal link correctly ([3da36e2](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/3da36e22f6adbce6a1b14ac1afb0fb4aa46bb75f))
|
28
|
+
|
3
29
|
### [1.1.2](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.1.1...v1.1.2) (2022-04-19)
|
4
30
|
|
5
31
|
|
@@ -32,7 +32,7 @@ class FullNodeViewer extends React.Component {
|
|
32
32
|
render() {
|
33
33
|
const {node, className, additionalNodesInfo={}} = this.props;
|
34
34
|
const nodeHref = additionalNodesInfo.getNodeRef
|
35
|
-
? additionalNodesInfo.getNodeRef(node)
|
35
|
+
? additionalNodesInfo.getNodeRef(node) + 'internal'
|
36
36
|
: undefined;
|
37
37
|
|
38
38
|
const commonInfo = [
|
@@ -0,0 +1,38 @@
|
|
1
|
+
.table-skeleton {
|
2
|
+
width: 100%;
|
3
|
+
|
4
|
+
&__row {
|
5
|
+
display: flex;
|
6
|
+
align-items: center;
|
7
|
+
|
8
|
+
height: var(--data-table-row-height);
|
9
|
+
|
10
|
+
.yc-skeleton {
|
11
|
+
height: var(--yc-text-body2-line-height);
|
12
|
+
}
|
13
|
+
}
|
14
|
+
|
15
|
+
&__col-1 {
|
16
|
+
width: 10%;
|
17
|
+
margin-right: 5%;
|
18
|
+
}
|
19
|
+
|
20
|
+
&__col-2 {
|
21
|
+
width: 7%;
|
22
|
+
margin-right: 5%;
|
23
|
+
}
|
24
|
+
|
25
|
+
&__col-3,
|
26
|
+
&__col-4 {
|
27
|
+
width: 5%;
|
28
|
+
margin-right: 5%;
|
29
|
+
}
|
30
|
+
|
31
|
+
&__col-5 {
|
32
|
+
width: 20%;
|
33
|
+
}
|
34
|
+
|
35
|
+
&__col-full {
|
36
|
+
width: 100%;
|
37
|
+
}
|
38
|
+
}
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import { FC } from 'react';
|
2
|
+
import block from 'bem-cn-lite';
|
3
|
+
import { Skeleton } from '@yandex-cloud/uikit';
|
4
|
+
|
5
|
+
import './TableSkeleton.scss';
|
6
|
+
|
7
|
+
const b = block('table-skeleton');
|
8
|
+
|
9
|
+
interface TableSkeletonProps {
|
10
|
+
className?: string;
|
11
|
+
rows?: number;
|
12
|
+
}
|
13
|
+
|
14
|
+
export const TableSkeleton: FC<TableSkeletonProps> = ({ rows = 2, className }) => (
|
15
|
+
<div className={b(null, className)}>
|
16
|
+
<div className={b('row')}>
|
17
|
+
<Skeleton className={b('col-1')} />
|
18
|
+
<Skeleton className={b('col-2')} />
|
19
|
+
<Skeleton className={b('col-3')} />
|
20
|
+
<Skeleton className={b('col-4')} />
|
21
|
+
<Skeleton className={b('col-5')} />
|
22
|
+
</div>
|
23
|
+
{[...new Array(rows)].map((_, index) => (
|
24
|
+
<div className={b('row')} key={`skeleton-row-${index}`}>
|
25
|
+
<Skeleton className={b('col-full')} />
|
26
|
+
</div>
|
27
|
+
))}
|
28
|
+
</div>
|
29
|
+
);
|
@@ -68,11 +68,11 @@ function getColumns({
|
|
68
68
|
header: vDiskTableColumnsNames[VDiskTableColumnsIds.slotId],
|
69
69
|
width: 100,
|
70
70
|
render: ({value, row}) => {
|
71
|
-
let vdiskInternalViewerLink
|
71
|
+
let vdiskInternalViewerLink = '';
|
72
72
|
|
73
73
|
if (nodeHref && value !== undefined) {
|
74
74
|
vdiskInternalViewerLink +=
|
75
|
-
nodeHref + '
|
75
|
+
nodeHref + 'actors/vdisks/vdisk' + pad9(pDiskId) + '_' + pad9(value);
|
76
76
|
}
|
77
77
|
|
78
78
|
return (
|
@@ -195,10 +195,10 @@ export function PDisk(props: PDiskProps) {
|
|
195
195
|
SerialNumber,
|
196
196
|
} = data;
|
197
197
|
|
198
|
-
let pDiskInternalViewerLink
|
198
|
+
let pDiskInternalViewerLink = '';
|
199
199
|
|
200
200
|
if (nodeHref) {
|
201
|
-
pDiskInternalViewerLink += nodeHref + '
|
201
|
+
pDiskInternalViewerLink += nodeHref + 'actors/pdisks/pdisk' + pad9(PDiskId);
|
202
202
|
}
|
203
203
|
|
204
204
|
const pdiskInfo: any = [
|
@@ -3,10 +3,11 @@ import PropTypes from 'prop-types';
|
|
3
3
|
import {connect} from 'react-redux';
|
4
4
|
import cn from 'bem-cn-lite';
|
5
5
|
import DataTable from '@yandex-cloud/react-data-table';
|
6
|
-
import {
|
6
|
+
import {RadioButton, Label} from '@yandex-cloud/uikit';
|
7
7
|
|
8
8
|
import StorageFilter from './StorageFilter/StorageFilter';
|
9
9
|
import {AutoFetcher} from '../../utils/autofetcher';
|
10
|
+
import {TableSkeleton} from '../../components/TableSkeleton/TableSkeleton';
|
10
11
|
|
11
12
|
import {
|
12
13
|
getStorageInfo,
|
@@ -68,17 +69,14 @@ class Storage extends React.Component {
|
|
68
69
|
storageType,
|
69
70
|
setHeader,
|
70
71
|
getNodesList,
|
71
|
-
getStorageInfo,
|
72
72
|
} = this.props;
|
73
73
|
|
74
74
|
this.autofetcher = new AutoFetcher();
|
75
75
|
getNodesList();
|
76
76
|
if (tenant || nodeId) {
|
77
77
|
setVisibleEntities(VisibleEntities.All);
|
78
|
-
getStorageInfo({
|
79
|
-
tenant,
|
78
|
+
this.getStorageInfo({
|
80
79
|
filter: FILTER_OPTIONS.All,
|
81
|
-
nodeId,
|
82
80
|
type: storageType,
|
83
81
|
});
|
84
82
|
} else {
|
@@ -88,16 +86,12 @@ class Storage extends React.Component {
|
|
88
86
|
link: createHref(routes.cluster, {activeTab: CLUSTER_PAGES.storage.id}),
|
89
87
|
},
|
90
88
|
]);
|
91
|
-
getStorageInfo({
|
92
|
-
tenant,
|
93
|
-
nodeId,
|
89
|
+
this.getStorageInfo({
|
94
90
|
filter: FILTER_OPTIONS.Missing,
|
95
91
|
type: storageType,
|
96
92
|
});
|
97
93
|
this.autofetcher.fetch(() =>
|
98
|
-
getStorageInfo({
|
99
|
-
tenant,
|
100
|
-
nodeId,
|
94
|
+
this.getStorageInfo({
|
101
95
|
filter: FILTER_OPTIONS.Missing,
|
102
96
|
type: storageType,
|
103
97
|
}),
|
@@ -107,30 +101,23 @@ class Storage extends React.Component {
|
|
107
101
|
|
108
102
|
componentDidUpdate(prevProps) {
|
109
103
|
const {
|
110
|
-
tenant,
|
111
104
|
visibleEntities,
|
112
|
-
getStorageInfo,
|
113
|
-
nodeId,
|
114
105
|
storageType,
|
115
106
|
autorefresh,
|
116
107
|
database,
|
117
108
|
} = this.props;
|
118
109
|
|
119
110
|
const startFetch = () => {
|
120
|
-
getStorageInfo({
|
121
|
-
tenant,
|
111
|
+
this.getStorageInfo({
|
122
112
|
filter: FILTER_OPTIONS[visibleEntities],
|
123
|
-
nodeId,
|
124
113
|
type: storageType,
|
125
114
|
});
|
126
115
|
|
127
116
|
this.autofetcher.stop();
|
128
117
|
this.autofetcher.start();
|
129
118
|
this.autofetcher.fetch(() =>
|
130
|
-
getStorageInfo({
|
131
|
-
tenant,
|
119
|
+
this.getStorageInfo({
|
132
120
|
filter: FILTER_OPTIONS[visibleEntities],
|
133
|
-
nodeId,
|
134
121
|
type: storageType,
|
135
122
|
}),
|
136
123
|
);
|
@@ -157,11 +144,25 @@ class Storage extends React.Component {
|
|
157
144
|
this.props.setInitialState();
|
158
145
|
}
|
159
146
|
|
147
|
+
getStorageInfo(data) {
|
148
|
+
const {
|
149
|
+
tenant,
|
150
|
+
nodeId,
|
151
|
+
getStorageInfo,
|
152
|
+
} = this.props;
|
153
|
+
|
154
|
+
getStorageInfo({
|
155
|
+
tenant,
|
156
|
+
nodeId,
|
157
|
+
...data,
|
158
|
+
}, {
|
159
|
+
concurrentId: 'getStorageInfo',
|
160
|
+
});
|
161
|
+
}
|
162
|
+
|
160
163
|
renderLoader() {
|
161
164
|
return (
|
162
|
-
<
|
163
|
-
<Loader size="m" />
|
164
|
-
</div>
|
165
|
+
<TableSkeleton className={b('loader')}/>
|
165
166
|
);
|
166
167
|
}
|
167
168
|
|
@@ -200,8 +201,9 @@ class Storage extends React.Component {
|
|
200
201
|
};
|
201
202
|
|
202
203
|
renderControls() {
|
203
|
-
const {setStorageFilter, visibleEntities, storageType, flatListStorageEntities} =
|
204
|
+
const {setStorageFilter, visibleEntities, storageType, flatListStorageEntities, loading, wasLoaded} =
|
204
205
|
this.props;
|
206
|
+
const showLoader = loading && !wasLoaded;
|
205
207
|
return (
|
206
208
|
<div className={b('controls')}>
|
207
209
|
<div className={b('search')}>
|
@@ -232,25 +234,28 @@ class Storage extends React.Component {
|
|
232
234
|
</RadioButton>
|
233
235
|
<Label theme="info" size="m">{`${
|
234
236
|
storageType === StorageTypes.groups ? 'Groups' : 'Nodes'
|
235
|
-
}: ${flatListStorageEntities.length}`}</Label>
|
237
|
+
}: ${(showLoader) ? '...' : flatListStorageEntities.length}`}</Label>
|
236
238
|
</div>
|
237
239
|
);
|
238
240
|
}
|
239
241
|
|
240
242
|
render() {
|
241
243
|
const {loading, wasLoaded, error} = this.props;
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
244
|
+
const showLoader = loading && !wasLoaded;
|
245
|
+
|
246
|
+
return (
|
247
|
+
<div className={b()}>
|
248
|
+
{this.renderControls()}
|
249
|
+
{error && (
|
250
|
+
<div>{error.statusText}</div>
|
251
|
+
)}
|
252
|
+
{showLoader ? (
|
253
|
+
this.renderLoader()
|
254
|
+
) : (
|
255
|
+
this.renderDataTable()
|
256
|
+
)}
|
257
|
+
</div>
|
258
|
+
);
|
254
259
|
}
|
255
260
|
}
|
256
261
|
|
@@ -235,11 +235,18 @@ function Vdisk(props) {
|
|
235
235
|
<DiskStateProgressBar
|
236
236
|
diskAllocatedPercent={vdiskAllocatedPercent}
|
237
237
|
severity={severity}
|
238
|
-
href={
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
238
|
+
href={
|
239
|
+
props.NodeId
|
240
|
+
? createHref(
|
241
|
+
routes.node,
|
242
|
+
{id: props.NodeId, activeTab: STRUCTURE},
|
243
|
+
{
|
244
|
+
pdiskId: props.PDisk?.PDiskId,
|
245
|
+
vdiskId: stringifyVdiskId(props.VDiskId),
|
246
|
+
},
|
247
|
+
)
|
248
|
+
: undefined
|
249
|
+
}
|
243
250
|
/>
|
244
251
|
</div>
|
245
252
|
</React.Fragment>
|
package/dist/services/api.js
CHANGED
@@ -35,7 +35,7 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
|
|
35
35
|
storage: true,
|
36
36
|
});
|
37
37
|
}
|
38
|
-
getStorageInfo({tenant, filter, nodeId, type}) {
|
38
|
+
getStorageInfo({tenant, filter, nodeId, type}, {concurrentId}) {
|
39
39
|
return this.get(
|
40
40
|
this.getPath(
|
41
41
|
`/viewer/json/${type === StorageTypes.nodes ? 'nodes' : 'storage'}?enums=true`,
|
@@ -45,6 +45,9 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
|
|
45
45
|
node_id: nodeId,
|
46
46
|
with: filter,
|
47
47
|
},
|
48
|
+
{
|
49
|
+
concurrentId,
|
50
|
+
},
|
48
51
|
);
|
49
52
|
}
|
50
53
|
getPdiskInfo(nodeId, pdiskId) {
|
@@ -74,6 +74,8 @@ const storage = function z(state = initialState, action) {
|
|
74
74
|
return {
|
75
75
|
...state,
|
76
76
|
visible: action.data,
|
77
|
+
wasLoaded: false,
|
78
|
+
error: undefined,
|
77
79
|
};
|
78
80
|
}
|
79
81
|
case SET_STORAGE_TYPE: {
|
@@ -81,6 +83,7 @@ const storage = function z(state = initialState, action) {
|
|
81
83
|
...state,
|
82
84
|
type: action.data,
|
83
85
|
wasLoaded: false,
|
86
|
+
error: undefined,
|
84
87
|
};
|
85
88
|
}
|
86
89
|
default:
|
@@ -94,9 +97,9 @@ export function setInitialState() {
|
|
94
97
|
};
|
95
98
|
}
|
96
99
|
|
97
|
-
export function getStorageInfo({tenant, filter, nodeId, type}) {
|
100
|
+
export function getStorageInfo({tenant, filter, nodeId, type}, {concurrentId}) {
|
98
101
|
return createApiRequest({
|
99
|
-
request: window.api.getStorageInfo({tenant, filter, nodeId, type}),
|
102
|
+
request: window.api.getStorageInfo({tenant, filter, nodeId, type}, {concurrentId}),
|
100
103
|
actions: FETCH_STORAGE,
|
101
104
|
});
|
102
105
|
}
|
@@ -3,6 +3,7 @@ export class AutoFetcher {
|
|
3
3
|
this.timeout = AutoFetcher.DEFAULT_TIMEOUT;
|
4
4
|
this.active = true;
|
5
5
|
this.timer = undefined;
|
6
|
+
this.launchCounter = 0;
|
6
7
|
}
|
7
8
|
|
8
9
|
wait(ms: number) {
|
@@ -16,6 +17,8 @@ export class AutoFetcher {
|
|
16
17
|
return;
|
17
18
|
}
|
18
19
|
|
20
|
+
const currentLaunch = this.launchCounter;
|
21
|
+
|
19
22
|
await this.wait(this.timeout);
|
20
23
|
|
21
24
|
if (this.active) {
|
@@ -23,6 +26,12 @@ export class AutoFetcher {
|
|
23
26
|
await request();
|
24
27
|
const finishTs = Date.now();
|
25
28
|
|
29
|
+
if (currentLaunch !== this.launchCounter) {
|
30
|
+
// autofetcher was restarted while request was in progress
|
31
|
+
// stop further fetches, we are in deprecated thread
|
32
|
+
return;
|
33
|
+
}
|
34
|
+
|
26
35
|
const responseTime = finishTs - startTs;
|
27
36
|
const nextTimeout =
|
28
37
|
responseTime > AutoFetcher.MIN_TIMEOUT ? responseTime : AutoFetcher.MIN_TIMEOUT;
|
@@ -40,6 +49,7 @@ export class AutoFetcher {
|
|
40
49
|
this.active = false;
|
41
50
|
}
|
42
51
|
start() {
|
52
|
+
this.launchCounter++;
|
43
53
|
this.active = true;
|
44
54
|
}
|
45
55
|
|
@@ -48,4 +58,5 @@ export class AutoFetcher {
|
|
48
58
|
timeout: number;
|
49
59
|
active: boolean;
|
50
60
|
timer: undefined | ReturnType<typeof setTimeout>;
|
61
|
+
launchCounter: number;
|
51
62
|
}
|