ydb-embedded-ui 1.1.3 → 1.2.2
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 +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;
|