ydb-embedded-ui 1.1.1 → 1.2.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 +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/Tenant/ObjectSummary/ObjectSummary.tsx +1 -1
- 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.0](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.1.3...v1.2.0) (2022-04-26)
|
4
|
+
|
5
|
+
|
6
|
+
### Features
|
7
|
+
|
8
|
+
* **Storage:** smoother loading state for storage table ([f7f38c4](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/f7f38c455dd9abc3f898048081e90af9b460f922))
|
9
|
+
|
10
|
+
|
11
|
+
### Bug Fixes
|
12
|
+
|
13
|
+
* prevent ghost autofetch ([153d829](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/153d8291d315f1dab001a69981a12e30d3d2aea9))
|
14
|
+
|
15
|
+
### [1.1.3](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.1.2...v1.1.3) (2022-04-20)
|
16
|
+
|
17
|
+
|
18
|
+
### Bug Fixes
|
19
|
+
|
20
|
+
* should prepare internal link correctly ([3da36e2](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/3da36e22f6adbce6a1b14ac1afb0fb4aa46bb75f))
|
21
|
+
|
22
|
+
### [1.1.2](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.1.1...v1.1.2) (2022-04-19)
|
23
|
+
|
24
|
+
|
25
|
+
### Bug Fixes
|
26
|
+
|
27
|
+
* **ObjectSummary:** should correctly parse table creation time ([c9887dd](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/c9887dd162720667dcbe3b4834b3b0ba5a9f3f6e))
|
28
|
+
|
3
29
|
### [1.1.1](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.1.0...v1.1.1) (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
|
|
@@ -153,7 +153,7 @@ function ObjectSummary(props: ObjectSummaryProps) {
|
|
153
153
|
};
|
154
154
|
|
155
155
|
const renderObjectOverview = () => {
|
156
|
-
const startTimeInMilliseconds = currentSchemaData?.CreateStep
|
156
|
+
const startTimeInMilliseconds = Number(currentSchemaData?.CreateStep);
|
157
157
|
let createTime = '';
|
158
158
|
if (startTimeInMilliseconds) {
|
159
159
|
createTime = new Date(startTimeInMilliseconds).toUTCString();
|
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
|
}
|