ydb-embedded-ui 1.1.3 → 1.2.2
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +27 -0
- package/dist/components/TableSkeleton/TableSkeleton.scss +38 -0
- package/dist/components/TableSkeleton/TableSkeleton.tsx +29 -0
- package/dist/containers/App/App.js +5 -1
- package/dist/containers/App/Content.js +2 -1
- package/dist/containers/Header/Header.tsx +5 -5
- 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/index.js +0 -2
- package/dist/store/reducers/storage.js +5 -2
- package/dist/utils/autofetcher.ts +11 -0
- package/package.json +1 -1
- package/dist/store/reducers/clusterInfo.js +0 -48
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,32 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
### [1.2.2](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.2.1...v1.2.2) (2022-05-04)
|
4
|
+
|
5
|
+
|
6
|
+
### Bug Fixes
|
7
|
+
|
8
|
+
* code-review ([288fda3](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/288fda3cd207908e9b5c0486c4d486c6f2e17dd4))
|
9
|
+
* reducer clusterInfo should not be used ([1cafcbf](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/1cafcbfb15f668b100cf6628b540b7cd234f6024))
|
10
|
+
|
11
|
+
### [1.2.1](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.2.0...v1.2.1) (2022-04-27)
|
12
|
+
|
13
|
+
|
14
|
+
### Bug Fixes
|
15
|
+
|
16
|
+
* **Vdisk:** should not fail if no node id passed ([d66686d](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/d66686d0cbd9f61c4e106f6775db2fca226c922f))
|
17
|
+
|
18
|
+
## [1.2.0](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.1.3...v1.2.0) (2022-04-26)
|
19
|
+
|
20
|
+
|
21
|
+
### Features
|
22
|
+
|
23
|
+
* **Storage:** smoother loading state for storage table ([f7f38c4](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/f7f38c455dd9abc3f898048081e90af9b460f922))
|
24
|
+
|
25
|
+
|
26
|
+
### Bug Fixes
|
27
|
+
|
28
|
+
* prevent ghost autofetch ([153d829](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/153d8291d315f1dab001a69981a12e30d3d2aea9))
|
29
|
+
|
3
30
|
### [1.1.3](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.1.2...v1.1.3) (2022-04-20)
|
4
31
|
|
5
32
|
|
@@ -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
|
+
);
|
@@ -17,6 +17,8 @@ registerLanguages();
|
|
17
17
|
class App extends React.Component {
|
18
18
|
static propTypes = {
|
19
19
|
isAuthenticated: PropTypes.bool,
|
20
|
+
singleClusterMode: PropTypes.bool,
|
21
|
+
clusterName: PropTypes.string,
|
20
22
|
children: PropTypes.node,
|
21
23
|
};
|
22
24
|
|
@@ -40,9 +42,10 @@ class App extends React.Component {
|
|
40
42
|
}
|
41
43
|
|
42
44
|
renderContentWithNavigation() {
|
45
|
+
const {singleClusterMode, clusterName} = this.props;
|
43
46
|
return (
|
44
47
|
<AsideNavigation>
|
45
|
-
<Content singleClusterMode={
|
48
|
+
<Content singleClusterMode={singleClusterMode} clusterName={clusterName} />
|
46
49
|
<div id="fullscreen-root"></div>
|
47
50
|
</AsideNavigation>
|
48
51
|
);
|
@@ -58,6 +61,7 @@ function mapStateToProps(state) {
|
|
58
61
|
isAuthenticated: state.authentication.isAuthenticated,
|
59
62
|
internalUser: state.authentication.user,
|
60
63
|
singleClusterMode: state.singleClusterMode,
|
64
|
+
clusterName: state.cluster.data?.Name,
|
61
65
|
};
|
62
66
|
}
|
63
67
|
|
@@ -59,7 +59,7 @@ export function Content(props) {
|
|
59
59
|
};
|
60
60
|
return (
|
61
61
|
<React.Fragment>
|
62
|
-
{!isClustersPage && <Header />}
|
62
|
+
{!isClustersPage && <Header clusterName={props.clusterName} />}
|
63
63
|
<main className={b('main')}>{renderRoute()}</main>
|
64
64
|
<ReduxTooltip />
|
65
65
|
<AppIcons />
|
@@ -70,6 +70,7 @@ export function Content(props) {
|
|
70
70
|
Content.propTypes = {
|
71
71
|
singleClusterMode: PropTypes.bool,
|
72
72
|
children: PropTypes.node,
|
73
|
+
clusterName: PropTypes.string,
|
73
74
|
};
|
74
75
|
|
75
76
|
function ContentWrapper(props) {
|
@@ -26,16 +26,16 @@ function ClusterName({name}: {name: string}) {
|
|
26
26
|
);
|
27
27
|
}
|
28
28
|
|
29
|
-
|
29
|
+
interface HeaderProps {
|
30
|
+
clusterName: string
|
31
|
+
}
|
32
|
+
|
33
|
+
function Header({clusterName}: HeaderProps) {
|
30
34
|
const dispatch = useDispatch();
|
31
35
|
const {data: host}: {data: {ClusterName?: string}} = useSelector((state: any) => state.host);
|
32
36
|
const {singleClusterMode, header}: {singleClusterMode: boolean; header: HeaderItemType[]} =
|
33
37
|
useSelector((state: any) => state);
|
34
38
|
|
35
|
-
const clusterName: string = useSelector(
|
36
|
-
(state: any) => state.cluster.data?.Name || state.clusterInfo.title,
|
37
|
-
);
|
38
|
-
|
39
39
|
const location = useLocation();
|
40
40
|
const history = useHistory();
|
41
41
|
|
@@ -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) {
|
@@ -20,7 +20,6 @@ import tablet from './tablet';
|
|
20
20
|
import executeQuery from './executeQuery';
|
21
21
|
import explainQuery from './explainQuery';
|
22
22
|
import tabletsFilters from './tabletsFilters';
|
23
|
-
import clusterInfo from './clusterInfo';
|
24
23
|
import settings from './settings';
|
25
24
|
import preview from './preview';
|
26
25
|
import nodesList from './clusterNodes';
|
@@ -63,7 +62,6 @@ export const rootReducer = {
|
|
63
62
|
explainQuery,
|
64
63
|
tabletsFilters,
|
65
64
|
heatmap,
|
66
|
-
clusterInfo,
|
67
65
|
settings,
|
68
66
|
preview,
|
69
67
|
nodesList,
|
@@ -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
|
}
|
package/package.json
CHANGED
@@ -1,48 +0,0 @@
|
|
1
|
-
import {createRequestActionTypes, createApiRequest} from '../utils';
|
2
|
-
import '../../services/api';
|
3
|
-
|
4
|
-
const FETCH_CLUSTER = createRequestActionTypes('cluster', 'FETCH_CLUSTER');
|
5
|
-
|
6
|
-
const initialState = {loading: false};
|
7
|
-
|
8
|
-
const clusterInfo = function (state = initialState, action) {
|
9
|
-
switch (action.type) {
|
10
|
-
case FETCH_CLUSTER.REQUEST: {
|
11
|
-
return {
|
12
|
-
...state,
|
13
|
-
loading: true,
|
14
|
-
};
|
15
|
-
}
|
16
|
-
case FETCH_CLUSTER.SUCCESS: {
|
17
|
-
const {data = {}} = action;
|
18
|
-
|
19
|
-
return {
|
20
|
-
...state,
|
21
|
-
...data,
|
22
|
-
loading: false,
|
23
|
-
error: undefined,
|
24
|
-
};
|
25
|
-
}
|
26
|
-
case FETCH_CLUSTER.FAILURE: {
|
27
|
-
return {
|
28
|
-
...state,
|
29
|
-
error: action.error,
|
30
|
-
loading: false,
|
31
|
-
};
|
32
|
-
}
|
33
|
-
default:
|
34
|
-
return state;
|
35
|
-
}
|
36
|
-
};
|
37
|
-
|
38
|
-
export function getCluster(name) {
|
39
|
-
return createApiRequest({
|
40
|
-
request: window.api.getClustersList(),
|
41
|
-
actions: FETCH_CLUSTER,
|
42
|
-
dataHandler: ({clusters = []}) => {
|
43
|
-
return clusters.filter((item) => item.name === name)[0];
|
44
|
-
},
|
45
|
-
});
|
46
|
-
}
|
47
|
-
|
48
|
-
export default clusterInfo;
|