ydb-embedded-ui 1.11.0 → 1.12.1

Sign up to get free protection for your applications and to get access to all the features.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.12.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.12.0...v1.12.1) (2022-08-26)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **Storage:** properly display usage for 0 storage ([aee67f9](https://github.com/ydb-platform/ydb-embedded-ui/commit/aee67f9314341c995e2c9468f5eedc48fa0a3d35))
9
+
10
+ ## [1.12.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.11.1...v1.12.0) (2022-08-26)
11
+
12
+
13
+ ### Features
14
+
15
+ * **Storage:** show usage column ([73aed5f](https://github.com/ydb-platform/ydb-embedded-ui/commit/73aed5f9ed60b6d2bd77fd315ae514ee7443c489))
16
+ * **Storage:** vividly show degraded disks count ([7315a9c](https://github.com/ydb-platform/ydb-embedded-ui/commit/7315a9cfd98002a7fab85d721712aa82c6dbb552))
17
+
18
+ ## [1.11.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.11.0...v1.11.1) (2022-08-26)
19
+
20
+
21
+ ### Bug Fixes
22
+
23
+ * number type instead of string for uint32 ([e60799e](https://github.com/ydb-platform/ydb-embedded-ui/commit/e60799edec4ef831e8c0d51f4384cde83520541d))
24
+ * **Storage:** expect arbitrary donors data ([09f8e08](https://github.com/ydb-platform/ydb-embedded-ui/commit/09f8e085c94faacd9da502643355e932346502ac))
25
+ * vdisk data contains pdisk data, not id ([bd1ea7f](https://github.com/ydb-platform/ydb-embedded-ui/commit/bd1ea7f59e0461256bb12f146b50470d21ac1ace))
26
+
3
27
  ## [1.11.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.10.3...v1.11.0) (2022-08-23)
4
28
 
5
29
 
@@ -42,6 +42,12 @@
42
42
  vertical-align: top;
43
43
  text-overflow: ellipsis;
44
44
  }
45
+ &__usage-label {
46
+ &_overload {
47
+ color: var(--yc-color-text-light-primary);
48
+ background-color: var(--yc-color-base-danger-heavy);
49
+ }
50
+ }
45
51
  &__group-id {
46
52
  font-weight: 500;
47
53
  }
@@ -1,9 +1,8 @@
1
1
  import _ from 'lodash';
2
2
  import cn from 'bem-cn-lite';
3
3
  import DataTable, {Column, Settings, SortOrder} from '@yandex-cloud/react-data-table';
4
- import {Popover, PopoverBehavior} from '@yandex-cloud/uikit';
4
+ import {Label, Popover, PopoverBehavior} from '@yandex-cloud/uikit';
5
5
 
6
- import Vdisk from '../Vdisk/Vdisk';
7
6
  import {Stack} from '../../../components/Stack/Stack';
8
7
  //@ts-ignore
9
8
  import EntityStatus from '../../../components/EntityStatus/EntityStatus';
@@ -16,6 +15,9 @@ import {bytesToGB, bytesToSpeed} from '../../../utils/utils';
16
15
  //@ts-ignore
17
16
  import {stringifyVdiskId} from '../../../utils';
18
17
 
18
+ import Vdisk from '../Vdisk/Vdisk';
19
+ import {isFullDonorData, getDegradedSeverity, getUsageSeverity, getUsage} from '../utils';
20
+
19
21
  import './StorageGroups.scss';
20
22
 
21
23
  enum TableColumnsIds {
@@ -47,11 +49,11 @@ const tableColumnsNames: Record<TableColumnsIdsValues, string> = {
47
49
  Used: 'Used',
48
50
  Limit: 'Limit',
49
51
  UsedSpaceFlag: 'Space',
50
- UsedPercents: 'Used percents',
52
+ UsedPercents: 'Usage',
51
53
  Read: 'Read',
52
54
  Write: 'Write',
53
55
  VDisks: 'VDisks',
54
- Missing: 'Missing',
56
+ Missing: 'Degraded',
55
57
  };
56
58
 
57
59
  const b = cn('global-storage-groups');
@@ -60,8 +62,8 @@ function setSortOrder(visibleEntities: keyof typeof VisibleEntities): SortOrder
60
62
  switch (visibleEntities) {
61
63
  case VisibleEntities.All: {
62
64
  return {
63
- columnId: TableColumnsIds.GroupID,
64
- order: DataTable.ASCENDING,
65
+ columnId: TableColumnsIds.Missing,
66
+ order: DataTable.DESCENDING,
65
67
  };
66
68
  }
67
69
  case VisibleEntities.Missing: {
@@ -108,6 +110,37 @@ function StorageGroups({data, tableSettings, visibleEntities, nodes}: StorageGro
108
110
  },
109
111
  align: DataTable.LEFT,
110
112
  },
113
+ {
114
+ name: TableColumnsIds.Missing,
115
+ header: tableColumnsNames[TableColumnsIds.Missing],
116
+ width: 100,
117
+ render: ({value, row}) => value ? (
118
+ <Label theme={getDegradedSeverity(row)}>Degraded: {value}</Label>
119
+ ) : '-',
120
+ align: DataTable.LEFT,
121
+ defaultOrder: DataTable.DESCENDING,
122
+ },
123
+ {
124
+ name: TableColumnsIds.UsedPercents,
125
+ header: tableColumnsNames[TableColumnsIds.UsedPercents],
126
+ width: 100,
127
+ render: ({row}) => {
128
+ const usage = getUsage(row, 5);
129
+ // without a limit the usage can be evaluated as 0,
130
+ // but the absence of a value is more clear
131
+ return row.Limit ? (
132
+ <Label
133
+ theme={getUsageSeverity(usage)}
134
+ className={b('usage-label', {overload: usage >= 100})}
135
+ >
136
+ ≥ {usage}%
137
+ </Label>
138
+ ) : '-';
139
+ },
140
+ // without a limit exclude usage from sort to display at the bottom
141
+ sortAccessor: (row) => row.Limit ? getUsage(row) : null,
142
+ align: DataTable.LEFT,
143
+ },
111
144
  {
112
145
  name: TableColumnsIds.GroupID,
113
146
  header: tableColumnsNames[TableColumnsIds.GroupID],
@@ -135,18 +168,6 @@ function StorageGroups({data, tableSettings, visibleEntities, nodes}: StorageGro
135
168
  },
136
169
  align: DataTable.RIGHT,
137
170
  },
138
- // {
139
- // name: tableColumnsIds.UsedPercents,
140
- // header: tableColumnsNames[tableColumnsIds.UsedPercents],
141
- // width: '100px',
142
- // render: ({row}) => {
143
- // return (
144
- // Math.round((row[tableColumnsIds.Used] * 100) / row[tableColumnsIds.Limit]) +
145
- // '%'
146
- // );
147
- // },
148
- // align: DataTable.RIGHT,
149
- // },
150
171
  {
151
172
  name: TableColumnsIds.UsedSpaceFlag,
152
173
  header: tableColumnsNames[TableColumnsIds.UsedSpaceFlag],
@@ -184,47 +205,44 @@ function StorageGroups({data, tableSettings, visibleEntities, nodes}: StorageGro
184
205
  },
185
206
  align: DataTable.RIGHT,
186
207
  },
187
- {
188
- name: TableColumnsIds.Missing,
189
- header: tableColumnsNames[TableColumnsIds.Missing],
190
- width: 100,
191
- align: DataTable.CENTER,
192
- defaultOrder: DataTable.DESCENDING,
193
- },
194
208
  {
195
209
  name: TableColumnsIds.VDisks,
196
210
  className: b('vdisks-column'),
197
211
  header: tableColumnsNames[TableColumnsIds.VDisks],
198
212
  render: ({value, row}) => (
199
213
  <div className={b('vdisks-wrapper')}>
200
- {_.map(value as TVDiskStateInfo[], (el) => (
201
- Array.isArray(el.Donors) && el.Donors.length > 0 ? (
202
- <Stack className={b('vdisks-item')} key={stringifyVdiskId(el.VDiskId)}>
203
- <Vdisk
204
- {...el}
205
- PoolName={row[TableColumnsIds.PoolName]}
206
- nodes={nodes}
207
- />
208
- {el.Donors.map((donor) => (
214
+ {_.map(value as TVDiskStateInfo[], (el) => {
215
+ const donors = Array.isArray(el.Donors) ? el.Donors.filter(isFullDonorData) : [];
216
+
217
+ return (
218
+ donors.length > 0 ? (
219
+ <Stack className={b('vdisks-item')} key={stringifyVdiskId(el.VDiskId)}>
220
+ <Vdisk
221
+ {...el}
222
+ PoolName={row[TableColumnsIds.PoolName]}
223
+ nodes={nodes}
224
+ />
225
+ {donors.map((donor) => (
226
+ <Vdisk
227
+ {...donor}
228
+ // donor and acceptor are always in the same group
229
+ PoolName={row[TableColumnsIds.PoolName]}
230
+ nodes={nodes}
231
+ key={stringifyVdiskId(donor.VDiskId)}
232
+ />
233
+ ))}
234
+ </Stack>
235
+ ) : (
236
+ <div className={b('vdisks-item')} key={stringifyVdiskId(el.VDiskId)}>
209
237
  <Vdisk
210
- {...donor}
211
- // donor and acceptor are always in the same group
238
+ {...el}
212
239
  PoolName={row[TableColumnsIds.PoolName]}
213
240
  nodes={nodes}
214
- key={stringifyVdiskId(donor.VDiskId)}
215
241
  />
216
- ))}
217
- </Stack>
218
- ) : (
219
- <div className={b('vdisks-item')} key={stringifyVdiskId(el.VDiskId)}>
220
- <Vdisk
221
- {...el}
222
- PoolName={row[TableColumnsIds.PoolName]}
223
- nodes={nodes}
224
- />
225
- </div>
226
- )
227
- ))}
242
+ </div>
243
+ )
244
+ );
245
+ })}
228
246
  </div>
229
247
  ),
230
248
  align: DataTable.CENTER,
@@ -1 +1,51 @@
1
+ import type {TVDiskStateInfo, TVSlotId} from '../../../types/api/storage';
2
+ import type {IStoragePoolGroup} from '../../../types/store/storage';
3
+
1
4
  export * from './constants';
5
+
6
+ export const isFullDonorData = (donor: TVDiskStateInfo | TVSlotId): donor is TVDiskStateInfo =>
7
+ 'VDiskId' in donor;
8
+
9
+ const generateEvaluator = (warn: number, crit: number) =>
10
+ (value: number) => {
11
+ if (0 <= value && value < warn) {
12
+ return 'success';
13
+ }
14
+
15
+ if (warn <= value && value < crit) {
16
+ return 'warning';
17
+ }
18
+
19
+ if (crit <= value) {
20
+ return 'danger';
21
+ }
22
+
23
+ return undefined;
24
+ };
25
+
26
+ const defaultDegradationEvaluator = generateEvaluator(1, 2);
27
+
28
+ const degradationEvaluators = {
29
+ 'block-4-2': generateEvaluator(1, 2),
30
+ 'mirror-3-dc': generateEvaluator(1, 3),
31
+ };
32
+
33
+ const canEvaluateErasureSpecies = (value?: string): value is keyof typeof degradationEvaluators =>
34
+ value !== undefined && value in degradationEvaluators;
35
+
36
+ export const getDegradedSeverity = (group: IStoragePoolGroup) => {
37
+ const evaluate = canEvaluateErasureSpecies(group.ErasureSpecies) ?
38
+ degradationEvaluators[group.ErasureSpecies] :
39
+ defaultDegradationEvaluator;
40
+
41
+ return evaluate(group.Missing);
42
+ };
43
+
44
+ export const getUsageSeverity = generateEvaluator(80, 85);
45
+
46
+ export const getUsage = (data: IStoragePoolGroup, step = 1) => {
47
+ // if limit is 0, display 0
48
+ const usage = Math.round((data.Used * 100) / data.Limit) || 0;
49
+
50
+ return Math.floor(usage / step) * step;
51
+ };
@@ -4,6 +4,15 @@ interface Window {
4
4
  params: {path: string},
5
5
  axiosOptions?: {concurrentId?: string},
6
6
  ) => Promise<import('../types/api/schema').TEvDescribeSchemeResult>;
7
+ getStorageInfo: (
8
+ params: {
9
+ tenant: string,
10
+ filter: string,
11
+ nodeId: string,
12
+ type: 'Groups' | 'Nodes',
13
+ },
14
+ axiosOptions?: {concurrentId?: string},
15
+ ) => Promise<import('../types/api/storage').TStorageInfo>;
7
16
  [method: string]: Function;
8
17
  };
9
18
  }
@@ -91,27 +91,19 @@ export interface TTableDescription {
91
91
  export interface TPartitionConfig {
92
92
  /** uint64 */
93
93
  FollowerCount?: string;
94
- /**
95
- * uint32
96
- * @deprecated use FollowerGroups
97
- */
98
- CrossDataCenterFollowerCount?: string;
94
+ /** @deprecated use FollowerGroups */
95
+ CrossDataCenterFollowerCount?: number;
99
96
  /** 0 or 1 items */
100
97
  FollowerGroups?: TFollowerGroup[];
101
98
  }
102
99
 
103
100
  export interface TFollowerGroup {
104
- /** uint32 */
105
- FollowerCount?: string;
101
+ FollowerCount?: number;
106
102
  AllowLeaderPromotion?: boolean;
107
103
  AllowClientRead?: boolean;
108
- /** uint32[] */
109
- AllowedNodeIDs?: string[];
110
- /**
111
- * uint32[]
112
- * @deprecated use AllowedDataCenters
113
- */
114
- AllowedDataCenterNumIDs?: string[];
104
+ AllowedNodeIDs?: number[];
105
+ /** @deprecated use AllowedDataCenters */
106
+ AllowedDataCenterNumIDs?: number[];
115
107
  RequireAllDataCenters?: boolean;
116
108
  LocalNodeOnly?: boolean;
117
109
  RequireDifferentNodes?: boolean;
@@ -64,12 +64,11 @@ export enum EVDiskState {
64
64
 
65
65
  interface TRank {
66
66
  /**
67
- * uint32
68
67
  * Rank in percents; 0-100% is good; >100% is bad.
69
68
  * Formula for rank calculation is the following:
70
69
  * Rank = actual_value / max_allowed_value * 100
71
70
  */
72
- RankPercent?: string;
71
+ RankPercent?: number;
73
72
 
74
73
  /**
75
74
  * Flag is the Rank transformed to something simple
@@ -84,16 +83,17 @@ interface TVDiskSatisfactionRank {
84
83
  }
85
84
 
86
85
  interface TVDiskID {
87
- /** uint32 */
88
- GroupID?: string;
89
- /** uint32 */
90
- GroupGeneration?: string;
91
- /** uint32 */
92
- Ring?: string;
93
- /** uint32 */
94
- Domain?: string;
95
- /** uint32 */
96
- VDisk?: string;
86
+ GroupID?: number;
87
+ GroupGeneration?: number;
88
+ Ring?: number;
89
+ Domain?: number;
90
+ VDisk?: number;
91
+ }
92
+
93
+ export interface TVSlotId {
94
+ NodeId?: number;
95
+ PDiskId?: number;
96
+ VSlotId?: number;
97
97
  }
98
98
 
99
99
  export interface TVDiskStateInfo {
@@ -102,18 +102,14 @@ export interface TVDiskStateInfo {
102
102
  CreateTime?: string;
103
103
  /** uint64 */
104
104
  ChangeTime?: string;
105
- /** uint32 */
106
- PDiskId?: string;
107
- /** uint32 */
108
- VDiskSlotId?: string;
105
+ PDisk?: TPDiskStateInfo;
106
+ VDiskSlotId?: number;
109
107
  /** uint64 */
110
108
  Guid?: string;
111
109
  /** uint64 */
112
110
  Kind?: string;
113
- /** uint32 */
114
- NodeId?: string;
115
- /** uint32 */
116
- Count?: string;
111
+ NodeId?: number;
112
+ Count?: number;
117
113
 
118
114
  Overall?: EFlag;
119
115
 
@@ -154,7 +150,8 @@ export interface TVDiskStateInfo {
154
150
  * VDisk actor instance guid
155
151
  */
156
152
  InstanceGuid?: string;
157
- Donors?: TVDiskStateInfo[];
153
+ // in reality it is `Donors: TVDiskStateInfo[] | TVSlotId[]`, but this way it is more error-proof
154
+ Donors?: Array<TVDiskStateInfo | TVSlotId>;
158
155
 
159
156
  /** VDisk (Skeleton) Front Queue Status */
160
157
  FrontQueues?: EFlag;
@@ -173,3 +170,49 @@ export interface TVDiskStateInfo {
173
170
  */
174
171
  WriteThroughput?: string;
175
172
  }
173
+
174
+ export interface TBSGroupStateInfo {
175
+ /** uint32 */
176
+ GroupID?: string;
177
+ ErasureSpecies?: string;
178
+ VDisks?: TVDiskStateInfo[];
179
+ /** uint64 */
180
+ ChangeTime?: string;
181
+ /** uint32 */
182
+ NodeId?: string; // filled during merge
183
+ /** uint32 */
184
+ GroupGeneration?: string;
185
+ Overall?: EFlag;
186
+ Latency?: EFlag;
187
+ /** uint32 */
188
+ Count?: string; // filled during group count
189
+ StoragePoolName?: string; // from BS_CONTROLLER
190
+ }
191
+
192
+ export interface TStoragePoolInfo {
193
+ Overall?: EFlag;
194
+ Name?: string;
195
+ Kind?: string;
196
+ Groups?: TBSGroupStateInfo[];
197
+ /** uint64 */
198
+ AcquiredUnits?: string;
199
+ AcquiredIOPS?: number;
200
+ /** uint64 */
201
+ AcquiredThroughput?: string;
202
+ /** uint64 */
203
+ AcquiredSize?: string;
204
+ MaximumIOPS?: number;
205
+ /** uint64 */
206
+ MaximumThroughput?: string;
207
+ /** uint64 */
208
+ MaximumSize?: string;
209
+ }
210
+
211
+ export interface TStorageInfo {
212
+ Overall?: EFlag;
213
+ StoragePools?: TStoragePoolInfo[];
214
+ /** uint64 */
215
+ TotalGroups?: string;
216
+ /** uint64 */
217
+ FoundGroups?: string;
218
+ }
@@ -0,0 +1,11 @@
1
+ import type {TBSGroupStateInfo} from '../api/storage';
2
+
3
+ export interface IStoragePoolGroup extends TBSGroupStateInfo {
4
+ Read: number;
5
+ Write: number;
6
+ PoolName?: string;
7
+ Used: number;
8
+ Limit: number;
9
+ Missing: number;
10
+ UsedSpaceFlag: number;
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ydb-embedded-ui",
3
- "version": "1.11.0",
3
+ "version": "1.12.1",
4
4
  "files": [
5
5
  "dist"
6
6
  ],