ydb-embedded-ui 1.0.0 → 1.0.3

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 CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ### [1.0.3](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.0.2...v1.0.3) (2022-03-21)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * query status should not be shown when query is loading ([d214eee](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/d214eee575b63341082f0be33163e3fce520df88))
9
+ * should set correct initial current index in queries history ([c3228d7](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/c3228d7a6a0c810982db1bdbec7762889ac44ffa))
10
+ * **Storage:** wording fixed [YDB-1552] ([3f487ff](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/3f487ff01117963760b676d14281e93e5f3002c0))
11
+
12
+ ### [1.0.2](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.0.1...v1.0.2) (2022-03-11)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * **Header:** add link to internal viewer ([64af24f](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/64af24f8d78cf0d34466ac129be10c0764cce3d4))
18
+
19
+ ### [1.0.1](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v1.0.0...v1.0.1) (2022-03-05)
20
+
21
+
22
+ ### Bug Fixes
23
+
24
+ * **QueriesHistory:** should save history to local storage ([#8](https://www.github.com/ydb-platform/ydb-embedded-ui/issues/8)) ([57031ab](https://www.github.com/ydb-platform/ydb-embedded-ui/commit/57031ab16900e9d1112bbf506d5c777f94f883bb))
25
+
3
26
  ## [1.0.0](https://www.github.com/ydb-platform/ydb-embedded-ui/compare/v0.2.0...v1.0.0) (2022-03-01)
4
27
 
5
28
 
@@ -25,4 +25,16 @@
25
25
  font-size: var(--yc-text-body2-font-size);
26
26
  font-weight: 500;
27
27
  }
28
+
29
+ &__cluster-name-wrapper {
30
+ display: flex;
31
+ align-items: center;
32
+
33
+ height: 100%;
34
+ gap: 5px;
35
+ }
36
+
37
+ &__divider {
38
+ height: 80%;
39
+ }
28
40
  }
@@ -1,15 +1,19 @@
1
- import {useEffect} from 'react';
1
+ import React, {useEffect} from 'react';
2
2
  import {useDispatch, useSelector} from 'react-redux';
3
3
  import cn from 'bem-cn-lite';
4
4
  import {useHistory, useLocation} from 'react-router';
5
+ import {Breadcrumbs, BreadcrumbsItem, Link} from '@yandex-cloud/uikit';
5
6
 
6
- import {clusterName as clusterNameLocation} from '../../store';
7
+ import Divider from '../../components/Divider/Divider';
8
+ //@ts-ignore
9
+ import Icon from '../../components/Icon/Icon';
10
+
11
+ import {clusterName as clusterNameLocation, backend, customBackend} from '../../store';
7
12
  import {getClusterInfo} from '../../store/reducers/cluster';
8
13
  import {getHostInfo} from '../../store/reducers/host';
14
+ import {HeaderItemType} from '../../store/reducers/header';
9
15
 
10
16
  import './Header.scss';
11
- import {Breadcrumbs, BreadcrumbsItem} from '@yandex-cloud/uikit';
12
- import {HeaderItemType} from '../../store/reducers/header';
13
17
 
14
18
  const b = cn('header');
15
19
 
@@ -49,6 +53,12 @@ function Header() {
49
53
  const renderHeader = () => {
50
54
  const clusterNameFinal = singleClusterMode ? host.ClusterName : clusterName;
51
55
 
56
+ let link = backend + '/internal';
57
+
58
+ if (singleClusterMode && !customBackend) {
59
+ link = `/internal`;
60
+ }
61
+
52
62
  const breadcrumbItems = header.reduce((acc, el) => {
53
63
  acc.push({text: el.text, action: () => history.push(el.link)});
54
64
  return acc;
@@ -63,8 +73,20 @@ function Header() {
63
73
  firstDisplayedItemsCount={1}
64
74
  />
65
75
  </div>
66
- <div>
67
- {clusterNameFinal && <ClusterName name={clusterNameFinal} />}
76
+
77
+ <div className={b('cluster-name-wrapper')}>
78
+ <Link href={link} target="_blank">
79
+ Internal viewer{' '}
80
+ <Icon name="external" viewBox={'0 0 16 16'} width={16} height={16} />
81
+ </Link>
82
+ {clusterNameFinal && (
83
+ <React.Fragment>
84
+ <div className={b('divider')}>
85
+ <Divider />
86
+ </div>
87
+ <ClusterName name={clusterNameFinal} />
88
+ </React.Fragment>
89
+ )}
68
90
  </div>
69
91
  </header>
70
92
  );
@@ -210,13 +210,16 @@ function StorageGroups({data, tableSettings, visibleEntities, nodes}: StorageGro
210
210
  ];
211
211
 
212
212
  let columns = allColumns;
213
+ let emptyMessage = 'No such groups.';
213
214
 
214
215
  if (visibleEntities === VisibleEntities.Space) {
215
216
  columns = allColumns.filter((col) => col.name !== TableColumnsIds.Missing);
217
+ emptyMessage = 'No storage groups with space problems.';
216
218
  }
217
219
 
218
220
  if (visibleEntities === VisibleEntities.Missing) {
219
221
  columns = allColumns.filter((col) => col.name !== TableColumnsIds.UsedSpaceFlag);
222
+ emptyMessage = 'No degraded groups.';
220
223
  }
221
224
  return data ? (
222
225
  <DataTable
@@ -226,7 +229,7 @@ function StorageGroups({data, tableSettings, visibleEntities, nodes}: StorageGro
226
229
  columns={columns}
227
230
  settings={tableSettings}
228
231
  initialSortOrder={setSortOrder(visibleEntities)}
229
- emptyDataMessage="No such groups."
232
+ emptyDataMessage={emptyMessage}
230
233
  />
231
234
  ) : null;
232
235
  }
@@ -114,9 +114,14 @@ function StorageNodes({data, tableSettings, visibleEntities}: StorageGroupsProps
114
114
  ];
115
115
 
116
116
  let columns = allColumns;
117
+ let emptyMessage = 'No such nodes.';
117
118
 
118
119
  if (visibleEntities === VisibleEntities.Space) {
119
120
  columns = allColumns.filter((col) => col.name !== TableColumnsIds.Missing);
121
+ emptyMessage = 'No nodes with space problems.';
122
+ }
123
+ if (visibleEntities === VisibleEntities.Missing) {
124
+ emptyMessage = 'No degraded nodes.';
120
125
  }
121
126
 
122
127
  return data ? (
@@ -127,7 +132,7 @@ function StorageNodes({data, tableSettings, visibleEntities}: StorageGroupsProps
127
132
  columns={columns}
128
133
  settings={tableSettings}
129
134
  initialSortOrder={setSortOrder(visibleEntities)}
130
- emptyDataMessage="No such nodes."
135
+ emptyDataMessage={emptyMessage}
131
136
  />
132
137
  ) : null;
133
138
  }
@@ -1,6 +1,10 @@
1
1
  @import '../../../styles/mixins.scss';
2
2
 
3
3
  .kv-acl {
4
+ display: flex;
5
+ overflow: auto;
6
+ flex-grow: 1;
7
+
4
8
  padding: 0 12px 16px;
5
9
  @include query-data-table;
6
10
  &__message-container {
@@ -38,8 +42,4 @@
38
42
  color: var(--yc-color-text-danger);
39
43
  }
40
44
  }
41
-
42
- &__result {
43
- overflow: auto;
44
- }
45
45
  }
@@ -13,7 +13,9 @@
13
13
  max-height: 100%;
14
14
 
15
15
  &__overview-wrapper {
16
+ display: flex;
16
17
  overflow: auto;
18
+ flex-grow: 1;
17
19
 
18
20
  padding: 0 12px 16px;
19
21
  }
@@ -91,7 +93,15 @@
91
93
  }
92
94
 
93
95
  &__info {
96
+ display: flex;
94
97
  overflow: hidden;
98
+ flex-direction: column;
99
+ }
100
+
101
+ &__schema {
102
+ display: flex;
103
+ overflow: auto;
104
+ flex-grow: 1;
95
105
  }
96
106
 
97
107
  &__info-controls {
@@ -78,7 +78,7 @@ interface ObjectSummaryProps {
78
78
  onCollapseSummary: VoidFunction;
79
79
  onExpandSummary: VoidFunction;
80
80
  isCollapsed: boolean;
81
- additionalTenantInfo?: any
81
+ additionalTenantInfo?: any;
82
82
  }
83
83
 
84
84
  function ObjectSummary(props: ObjectSummaryProps) {
@@ -168,10 +168,16 @@ function ObjectSummary(props: ObjectSummaryProps) {
168
168
  const renderTabContent = () => {
169
169
  switch (infoTab) {
170
170
  case TenantInfoTabsIds.acl: {
171
- return <Acl additionalTenantInfo={props.additionalTenantInfo}/>;
171
+ return <Acl additionalTenantInfo={props.additionalTenantInfo} />;
172
172
  }
173
173
  case TenantInfoTabsIds.schema: {
174
- return loadingSchema ? renderLoader() : <SchemaViewer data={schema} />;
174
+ return loadingSchema ? (
175
+ renderLoader()
176
+ ) : (
177
+ <div className={b('schema')}>
178
+ <SchemaViewer data={schema} />
179
+ </div>
180
+ );
175
181
  }
176
182
  default: {
177
183
  return renderObjectOverview();
@@ -194,7 +200,9 @@ function ObjectSummary(props: ObjectSummaryProps) {
194
200
  <div className={b('tree-title')}>Navigation</div>
195
201
  </div>
196
202
  <div className={b('tree')}>
197
- {tenantData && <SchemaNode fullPath={tenantName as string} data={tenantData} isRoot />}
203
+ {tenantData && (
204
+ <SchemaNode fullPath={tenantName as string} data={tenantData} isRoot />
205
+ )}
198
206
  </div>
199
207
  </div>
200
208
  );
@@ -38,6 +38,7 @@ function QueriesHistory(props: QueriesHistoryProps) {
38
38
  };
39
39
 
40
40
  const renderSavedQueries = () => {
41
+ const reversedHistory = ([] as string[]).concat(history).reverse();
41
42
  return (
42
43
  <Popup
43
44
  className={b('popup-wrapper')}
@@ -47,7 +48,7 @@ function QueriesHistory(props: QueriesHistoryProps) {
47
48
  onClose={onCloseHistory}
48
49
  >
49
50
  <div className={b()}>
50
- {history.length === 0 ? (
51
+ {reversedHistory.length === 0 ? (
51
52
  <div className={b('empty')}>History is empty</div>
52
53
  ) : (
53
54
  <React.Fragment>
@@ -57,7 +58,7 @@ function QueriesHistory(props: QueriesHistoryProps) {
57
58
  </div>
58
59
  </div>
59
60
  <div>
60
- {history?.reverse().map((query, index) => {
61
+ {reversedHistory.map((query, index) => {
61
62
  return (
62
63
  <div
63
64
  className={b('saved-queries-row')}
@@ -154,7 +154,7 @@ function QueryEditor(props) {
154
154
  useEffect(() => {
155
155
  const {monacoHotKey, setMonacoHotKey} = props;
156
156
  if (monacoHotKey === null) {
157
- return
157
+ return;
158
158
  }
159
159
  setMonacoHotKey(null);
160
160
  switch (monacoHotKey) {
@@ -464,19 +464,12 @@ function QueryEditor(props) {
464
464
  };
465
465
 
466
466
  const getExecuteResult = () => {
467
- const {
468
- data = [],
469
- error,
470
- loading,
471
- history: {queries},
472
- } = props.executeQuery;
467
+ const {data = [], error} = props.executeQuery;
473
468
 
474
469
  if (error) {
475
470
  return error.data || error;
476
471
  } else if (data.length > 0) {
477
472
  return data;
478
- } else if (!loading && queries.length) {
479
- return 'The request was successful';
480
473
  } else {
481
474
  return '';
482
475
  }
@@ -252,28 +252,32 @@ function QueryExplain(props) {
252
252
  return (
253
253
  <React.Fragment>
254
254
  <div className={b('controls')}>
255
- <div className={b('controls-right')}>
256
- <QueryExecutionStatus hasError={Boolean(props.error)} />
257
- {!props.error && (
258
- <React.Fragment>
259
- <Divider />
260
- <RadioButton
261
- options={explainOptions}
262
- value={activeOption}
263
- onUpdate={onSelectOption}
255
+ {!props.loading && (
256
+ <React.Fragment>
257
+ <div className={b('controls-right')}>
258
+ <QueryExecutionStatus hasError={Boolean(props.error)} />
259
+ {!props.error && (
260
+ <React.Fragment>
261
+ <Divider />
262
+ <RadioButton
263
+ options={explainOptions}
264
+ value={activeOption}
265
+ onUpdate={onSelectOption}
266
+ />
267
+ </React.Fragment>
268
+ )}
269
+ </div>
270
+ <div className={b('controls-left')}>
271
+ <EnableFullscreenButton disabled={Boolean(props.error)} />
272
+ <PaneVisibilityToggleButtons
273
+ onCollapse={props.onCollapseResults}
274
+ onExpand={props.onExpandResults}
275
+ isCollapsed={props.isResultsCollapsed}
276
+ initialDirection="bottom"
264
277
  />
265
- </React.Fragment>
266
- )}
267
- </div>
268
- <div className={b('controls-left')}>
269
- <EnableFullscreenButton disabled={Boolean(props.error)} />
270
- <PaneVisibilityToggleButtons
271
- onCollapse={props.onCollapseResults}
272
- onExpand={props.onExpandResults}
273
- isCollapsed={props.isResultsCollapsed}
274
- initialDirection="bottom"
275
- />
276
- </div>
278
+ </div>
279
+ </React.Fragment>
280
+ )}
277
281
  </div>
278
282
  <div className={b('result')}>{renderContent()}</div>
279
283
  </React.Fragment>
@@ -19,6 +19,7 @@
19
19
  justify-content: space-between;
20
20
  align-items: center;
21
21
 
22
+ height: 53px;
22
23
  padding: 12px 20px;
23
24
 
24
25
  border-bottom: 1px solid var(--yc-color-line-generic);
@@ -28,6 +28,7 @@
28
28
  justify-content: space-between;
29
29
  align-items: center;
30
30
 
31
+ height: 53px;
31
32
  padding: 12px 20px;
32
33
 
33
34
  border-bottom: 1px solid var(--yc-color-line-generic);
@@ -67,7 +67,7 @@ class SchemaViewer extends React.Component {
67
67
  theme="yandex-cloud"
68
68
  data={tableData}
69
69
  columns={columns}
70
- settings={{...DEFAULT_TABLE_SETTINGS, stickyTop: 107}}
70
+ settings={DEFAULT_TABLE_SETTINGS}
71
71
  dynamicRender={true}
72
72
  initialSortOrder={{columnId: SchemaViewerColumns.key, order: DataTable.DESCENDING}}
73
73
  />
@@ -1,5 +1,9 @@
1
1
  import {createRequestActionTypes, createApiRequest} from '../utils';
2
2
  import '../../services/api';
3
+ import {getValueFromLS, parseJson} from '../../utils/utils';
4
+ import {QUERIES_HISTORY_KEY} from '../../utils/constants';
5
+
6
+ const MAXIMUM_QUERIES_IN_HISTORY = 20;
3
7
 
4
8
  const SEND_QUERY = createRequestActionTypes('query', 'SEND_QUERY');
5
9
  const CHANGE_USER_INPUT = 'query/CHANGE_USER_INPUT';
@@ -9,6 +13,10 @@ const GO_TO_NEXT_QUERY = 'query/GO_TO_NEXT_QUERY';
9
13
  const SELECT_RUN_ACTION = 'query/SELECT_RUN_ACTION';
10
14
  const MONACO_HOT_KEY = 'query/MONACO_HOT_KEY';
11
15
 
16
+ const queriesHistoryInitial = parseJson(getValueFromLS(QUERIES_HISTORY_KEY, []));
17
+
18
+ const sliceLimit = queriesHistoryInitial.length - MAXIMUM_QUERIES_IN_HISTORY;
19
+
12
20
  export const RUN_ACTIONS_VALUES = {
13
21
  script: 'execute-script',
14
22
  scan: 'execute-scan',
@@ -25,8 +33,11 @@ const initialState = {
25
33
  loading: false,
26
34
  input: '',
27
35
  history: {
28
- queries: [],
29
- currentIndex: -1,
36
+ queries: queriesHistoryInitial.slice(sliceLimit < 0 ? 0 : sliceLimit),
37
+ currentIndex:
38
+ queriesHistoryInitial.length > MAXIMUM_QUERIES_IN_HISTORY
39
+ ? MAXIMUM_QUERIES_IN_HISTORY - 1
40
+ : queriesHistoryInitial.length - 1,
30
41
  },
31
42
  runAction: RUN_ACTIONS_VALUES.script,
32
43
  monacoHotKey: null,
@@ -76,7 +87,10 @@ const executeQuery = (state = initialState, action) => {
76
87
 
77
88
  case SAVE_QUERY_TO_HISTORY: {
78
89
  const query = action.data;
79
- const newQueries = [...state.history.queries, query];
90
+ const newQueries = [...state.history.queries, query].slice(
91
+ state.history.queries.length >= MAXIMUM_QUERIES_IN_HISTORY ? 1 : 0,
92
+ );
93
+ window.localStorage.setItem(QUERIES_HISTORY_KEY, JSON.stringify(newQueries));
80
94
  const currentIndex = newQueries.length - 1;
81
95
 
82
96
  return {
@@ -6,8 +6,8 @@ import {calcUptime} from '../../utils';
6
6
 
7
7
  export const VisibleEntities = {
8
8
  All: 'All',
9
- Missing: 'Missing',
10
- Space: 'Space',
9
+ Missing: 'Degraded',
10
+ Space: 'Out of space',
11
11
  };
12
12
 
13
13
  export const StorageTypes = {
@@ -120,6 +120,7 @@ export const PROBLEMS = 'With problems';
120
120
 
121
121
  export const THEME_KEY = 'theme';
122
122
  export const SAVED_QUERIES_KEY = 'saved_queries';
123
+ export const QUERIES_HISTORY_KEY = 'queries_history';
123
124
  export const DATA_QA_TUNE_COLUMNS_POPUP = 'tune-columns-popup';
124
125
 
125
126
  export const defaultUserSettings = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ydb-embedded-ui",
3
- "version": "1.0.0",
3
+ "version": "1.0.3",
4
4
  "files": [
5
5
  "dist"
6
6
  ],