ydb-embedded-ui 4.6.0 → 4.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/assets/icons/versions.svg +3 -0
  3. package/dist/components/NodeHostWrapper/NodeHostWrapper.tsx +6 -1
  4. package/dist/components/Tablet/Tablet.tsx +17 -3
  5. package/dist/components/TabletsStatistic/TabletsStatistic.tsx +23 -16
  6. package/dist/containers/App/Content.js +5 -2
  7. package/dist/containers/AsideNavigation/AsideNavigation.tsx +50 -18
  8. package/dist/containers/Cluster/Cluster.tsx +6 -13
  9. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.tsx +3 -3
  10. package/dist/containers/Cluster/{utils.ts → utils.tsx} +11 -0
  11. package/dist/containers/Header/Header.scss +9 -0
  12. package/dist/containers/Header/Header.tsx +70 -14
  13. package/dist/containers/Header/breadcrumbs.ts +146 -0
  14. package/dist/containers/Node/Node.tsx +21 -27
  15. package/dist/containers/Node/NodePages.ts +10 -6
  16. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +11 -3
  17. package/dist/containers/Tablet/Tablet.tsx +35 -27
  18. package/dist/containers/Tablet/TabletInfo/TabletInfo.tsx +2 -2
  19. package/dist/containers/TabletsFilters/TabletsFilters.js +13 -15
  20. package/dist/containers/Tenant/Diagnostics/Consumers/columns/columns.tsx +1 -1
  21. package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.scss +5 -1
  22. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +4 -14
  23. package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.scss +5 -3
  24. package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.tsx +1 -1
  25. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.js +2 -1
  26. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +9 -16
  27. package/dist/containers/Tenant/ObjectGeneral/ObjectGeneral.tsx +11 -11
  28. package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +7 -3
  29. package/dist/containers/Tenant/{QueryEditor/QueryResult/QueryResult.js → Query/ExecuteResult/ExecuteResult.js} +3 -5
  30. package/dist/containers/Tenant/{QueryEditor/QueryResult/QueryResult.scss → Query/ExecuteResult/ExecuteResult.scss} +1 -1
  31. package/dist/containers/Tenant/{QueryEditor/QueryExplain/QueryExplain.js → Query/ExplainResult/ExplainResult.js} +3 -5
  32. package/dist/containers/Tenant/{QueryEditor/QueryExplain/QueryExplain.scss → Query/ExplainResult/ExplainResult.scss} +1 -1
  33. package/dist/containers/Tenant/Query/QueriesHistory/QueriesHistory.scss +20 -0
  34. package/dist/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx +60 -0
  35. package/dist/containers/Tenant/Query/Query.scss +16 -0
  36. package/dist/containers/Tenant/Query/Query.tsx +73 -0
  37. package/dist/containers/Tenant/{QueryEditor → Query/QueryEditor}/QueryEditor.js +39 -88
  38. package/dist/containers/Tenant/{QueryEditor → Query/QueryEditor}/QueryEditor.scss +7 -23
  39. package/dist/containers/Tenant/{QueryEditor → Query}/QueryEditorControls/QueryEditorControls.scss +1 -4
  40. package/dist/containers/Tenant/Query/QueryTabs/QueryTabs.tsx +59 -0
  41. package/dist/containers/Tenant/{QueryEditor → Query}/SaveQuery/SaveQuery.js +5 -5
  42. package/dist/containers/Tenant/Query/SavedQueries/SavedQueries.scss +55 -0
  43. package/dist/containers/Tenant/Query/SavedQueries/SavedQueries.tsx +150 -0
  44. package/dist/containers/Tenant/Query/i18n/en.json +12 -0
  45. package/dist/containers/Tenant/Query/i18n/ru.json +12 -0
  46. package/dist/containers/Tenant/Query/utils/getPreparedResult.ts +30 -0
  47. package/dist/containers/Tenant/Tenant.scss +0 -8
  48. package/dist/containers/Tenant/Tenant.tsx +24 -46
  49. package/dist/containers/Tenant/TenantPages.tsx +7 -16
  50. package/dist/containers/Tenant/utils/constants.ts +10 -0
  51. package/dist/containers/Tenant/utils/schemaActions.ts +9 -4
  52. package/dist/containers/Tenants/Tenants.js +26 -13
  53. package/dist/routes.ts +21 -7
  54. package/dist/services/api.ts +9 -5
  55. package/dist/store/reducers/executeQuery.ts +18 -4
  56. package/dist/store/reducers/header/header.ts +31 -0
  57. package/dist/store/reducers/header/types.ts +54 -0
  58. package/dist/store/reducers/index.ts +1 -1
  59. package/dist/store/reducers/node/types.ts +2 -0
  60. package/dist/store/reducers/settings/settings.ts +8 -3
  61. package/dist/store/reducers/tablet.ts +18 -1
  62. package/dist/store/reducers/tenant/constants.ts +9 -1
  63. package/dist/store/reducers/tenant/tenant.ts +23 -4
  64. package/dist/store/reducers/tenant/types.ts +9 -5
  65. package/dist/store/reducers/topic.ts +1 -1
  66. package/dist/store/state-url-mapping.js +6 -3
  67. package/dist/types/store/executeQuery.ts +4 -1
  68. package/dist/types/store/query.ts +5 -0
  69. package/dist/types/store/tablet.ts +7 -4
  70. package/dist/utils/constants.ts +5 -1
  71. package/package.json +2 -1
  72. package/dist/containers/Tenant/ObjectGeneralTabs/ObjectGeneralTabs.scss +0 -9
  73. package/dist/containers/Tenant/ObjectGeneralTabs/ObjectGeneralTabs.tsx +0 -68
  74. package/dist/containers/Tenant/QueryEditor/QueriesHistory/QueriesHistory.scss +0 -85
  75. package/dist/containers/Tenant/QueryEditor/QueriesHistory/QueriesHistory.tsx +0 -95
  76. package/dist/containers/Tenant/QueryEditor/SavedQueries/SavedQueries.js +0 -161
  77. package/dist/containers/Tenant/QueryEditor/SavedQueries/SavedQueries.scss +0 -93
  78. package/dist/containers/Tenant/QueryEditor/i18n/en.json +0 -3
  79. package/dist/containers/Tenant/QueryEditor/i18n/ru.json +0 -3
  80. package/dist/store/reducers/header.ts +0 -26
  81. /package/dist/containers/Tenant/{QueryEditor → Query}/Issues/Issues.scss +0 -0
  82. /package/dist/containers/Tenant/{QueryEditor → Query}/Issues/Issues.tsx +0 -0
  83. /package/dist/containers/Tenant/{QueryEditor → Query}/Issues/models.ts +0 -0
  84. /package/dist/containers/Tenant/{QueryEditor → Query}/QueryDuration/QueryDuration.scss +0 -0
  85. /package/dist/containers/Tenant/{QueryEditor → Query}/QueryDuration/QueryDuration.tsx +0 -0
  86. /package/dist/containers/Tenant/{QueryEditor → Query}/QueryEditorControls/OldQueryEditorControls.tsx +0 -0
  87. /package/dist/containers/Tenant/{QueryEditor → Query}/QueryEditorControls/QueryEditorControls.tsx +0 -0
  88. /package/dist/containers/Tenant/{QueryEditor → Query}/QueryEditorControls/shared.ts +0 -0
  89. /package/dist/containers/Tenant/{QueryEditor → Query}/SaveQuery/SaveQuery.scss +0 -0
  90. /package/dist/containers/Tenant/{QueryEditor → Query}/i18n/index.ts +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.8.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.7.0...v4.8.0) (2023-06-26)
4
+
5
+
6
+ ### Features
7
+
8
+ * **Tenant:** transform general tabs into left navigation items ([#431](https://github.com/ydb-platform/ydb-embedded-ui/issues/431)) ([7117b96](https://github.com/ydb-platform/ydb-embedded-ui/commit/7117b9622d5f6469dcc2bcc1c0d5cb71d4f94c0b))
9
+
10
+ ## [4.7.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.6.0...v4.7.0) (2023-06-23)
11
+
12
+
13
+ ### Features
14
+
15
+ * **QueryEditor:** transform history and saved to tabs ([#427](https://github.com/ydb-platform/ydb-embedded-ui/issues/427)) ([6378ca7](https://github.com/ydb-platform/ydb-embedded-ui/commit/6378ca7013239b33e55c1f88fdde7cab3a102df6))
16
+ * update breadcrumbs ([#432](https://github.com/ydb-platform/ydb-embedded-ui/issues/432)) ([e583a03](https://github.com/ydb-platform/ydb-embedded-ui/commit/e583a03fe0d77698f29c924e611133f015c3f7ad))
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+ * **Cluster:** add icons to tabs ([#430](https://github.com/ydb-platform/ydb-embedded-ui/issues/430)) ([e9e649f](https://github.com/ydb-platform/ydb-embedded-ui/commit/e9e649f614691e44172c9b93dd3119066c145413))
22
+ * **ClusterInfo:** hide by default ([#435](https://github.com/ydb-platform/ydb-embedded-ui/issues/435)) ([ef2b353](https://github.com/ydb-platform/ydb-embedded-ui/commit/ef2b3535f2c6324a34c4386680f5050655a04eb4))
23
+ * **Cluster:** use counter from uikit for tabs ([#428](https://github.com/ydb-platform/ydb-embedded-ui/issues/428)) ([19ca3bd](https://github.com/ydb-platform/ydb-embedded-ui/commit/19ca3bd14b15bdab1a9621939ddceee6d23b08ac))
24
+ * **DetailedOverview:** prevent tenant info scroll on overflow ([#434](https://github.com/ydb-platform/ydb-embedded-ui/issues/434)) ([8ed6076](https://github.com/ydb-platform/ydb-embedded-ui/commit/8ed60760d54913d05f39d35d00a34c8b1d7d9738))
25
+ * rename Internal Viewer to Developer UI ([#423](https://github.com/ydb-platform/ydb-embedded-ui/issues/423)) ([3eb21f3](https://github.com/ydb-platform/ydb-embedded-ui/commit/3eb21f35a230cc591f02ef9b195f99031f832e8a))
26
+ * **Storage:** update columns ([#437](https://github.com/ydb-platform/ydb-embedded-ui/issues/437)) ([264fbc9](https://github.com/ydb-platform/ydb-embedded-ui/commit/264fbc984cd9ef1467110d3e2f5ed9b29a526c2b))
27
+ * **Tablet:** clear tablet data on unmount ([#425](https://github.com/ydb-platform/ydb-embedded-ui/issues/425)) ([5d308cd](https://github.com/ydb-platform/ydb-embedded-ui/commit/5d308cdded342d7a40cbc6a91431d3f286c39b8a))
28
+ * **TabletsStatistic:** use tenant backend ([#429](https://github.com/ydb-platform/ydb-embedded-ui/issues/429)) ([d290684](https://github.com/ydb-platform/ydb-embedded-ui/commit/d290684ba08aec8b66c0492ba571a5337b5b896c))
29
+
3
30
  ## [4.6.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.5.2...v4.6.0) (2023-06-13)
4
31
 
5
32
 
@@ -0,0 +1,3 @@
1
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M8.625 13.0156C8.625 13.2695 8.47266 13.498 8.24414 13.5742C8.04102 13.6758 7.76172 13.6504 7.58398 13.4727L5.55273 11.6445C5.42578 11.543 5.375 11.3652 5.375 11.1875C5.375 11.0352 5.42578 10.8574 5.55273 10.7559L7.58398 8.92773C7.76172 8.75 8.04102 8.72461 8.24414 8.82617C8.47266 8.90234 8.625 9.13086 8.625 9.35938V10.5781H9.03125C10.0215 10.5781 10.8594 9.76562 10.8594 8.75V4.61133C10.0215 4.35742 9.4375 3.57031 9.4375 2.65625C9.4375 1.53906 10.3262 0.625 11.4688 0.625C12.5859 0.625 13.5 1.53906 13.5 2.65625C13.5 3.57031 12.8906 4.35742 12.0781 4.61133V8.75C12.0781 10.4512 10.707 11.7969 9.03125 11.7969H8.625V13.0156ZM12.2812 2.65625C12.2812 2.22461 11.9004 1.84375 11.4688 1.84375C11.0117 1.84375 10.6562 2.22461 10.6562 2.65625C10.6562 3.11328 11.0117 3.46875 11.4688 3.46875C11.9004 3.46875 12.2812 3.11328 12.2812 2.65625ZM5.375 1.23438C5.375 1.00586 5.50195 0.777344 5.73047 0.701172C5.93359 0.599609 6.21289 0.625 6.39062 0.802734L8.42188 2.63086C8.54883 2.73242 8.625 2.91016 8.625 3.0625C8.625 3.24023 8.54883 3.41797 8.42188 3.51953L6.39062 5.34766C6.21289 5.52539 5.93359 5.55078 5.73047 5.44922C5.50195 5.37305 5.375 5.14453 5.375 4.89062V3.67188H4.96875C3.95312 3.67188 3.14062 4.50977 3.14062 5.5V9.66406C3.95312 9.91797 4.5625 10.7051 4.5625 11.5938C4.5625 12.7363 3.64844 13.625 2.53125 13.625C1.38867 13.625 0.5 12.7363 0.5 11.5938C0.5 10.7051 1.08398 9.91797 1.92188 9.66406V5.5C1.92188 3.82422 3.26758 2.45312 4.96875 2.45312H5.375V1.23438ZM1.71875 11.5938C1.71875 12.0508 2.07422 12.4062 2.53125 12.4062C2.96289 12.4062 3.34375 12.0508 3.34375 11.5938C3.34375 11.1621 2.96289 10.7812 2.53125 10.7812C2.07422 10.7812 1.71875 11.1621 1.71875 11.5938Z"/>
3
+ </svg>
@@ -26,6 +26,11 @@ export const NodeHostWrapper = ({node, getNodeRef}: NodeHostWrapperProps) => {
26
26
 
27
27
  const isNodeAvailable = !isUnavailableNode(node);
28
28
  const nodeRef = isNodeAvailable && getNodeRef ? getNodeRef(node) + 'internal' : undefined;
29
+ const nodePath = isNodeAvailable
30
+ ? getDefaultNodePath(node.NodeId, {
31
+ tenantName: node.TenantName,
32
+ })
33
+ : undefined;
29
34
 
30
35
  return (
31
36
  <div className={b()}>
@@ -39,7 +44,7 @@ export const NodeHostWrapper = ({node, getNodeRef}: NodeHostWrapperProps) => {
39
44
  <EntityStatus
40
45
  name={node.Host}
41
46
  status={node.SystemState}
42
- path={isNodeAvailable ? getDefaultNodePath(node.NodeId) : undefined}
47
+ path={nodePath}
43
48
  hasClipboardButton
44
49
  className={b('host')}
45
50
  />
@@ -14,18 +14,32 @@ const b = cn('tablet');
14
14
 
15
15
  interface TabletProps {
16
16
  tablet?: TTabletStateInfo;
17
+ tenantName?: string;
17
18
  }
18
19
 
19
- export const Tablet = ({tablet = {}}: TabletProps) => {
20
- const {TabletId: id} = tablet;
20
+ export const Tablet = ({tablet = {}, tenantName}: TabletProps) => {
21
+ const {TabletId: id, NodeId, Type, State} = tablet;
21
22
  const status = tablet.Overall?.toLowerCase();
22
23
 
24
+ const tabletPath =
25
+ id &&
26
+ createHref(
27
+ routes.tablet,
28
+ {id},
29
+ {
30
+ nodeId: NodeId,
31
+ type: Type,
32
+ state: State,
33
+ tenantName,
34
+ },
35
+ );
36
+
23
37
  return (
24
38
  <ContentWithPopup
25
39
  className={b('wrapper')}
26
40
  content={<TabletTooltipContent data={tablet} className={b('popup-content')} />}
27
41
  >
28
- <InternalLink to={id && createHref(routes.tablet, {id})}>
42
+ <InternalLink to={tabletPath}>
29
43
  <div className={b({status})}>
30
44
  <div className={b('type')}>{[getTabletLabel(tablet.Type)]}</div>
31
45
  </div>
@@ -12,9 +12,9 @@ import './TabletsStatistic.scss';
12
12
 
13
13
  const b = cn('tablets-statistic');
14
14
 
15
- type ITablets = TFullTabletStateInfo[] | TComputeTabletStateInfo[];
15
+ type Tablets = TFullTabletStateInfo[] | TComputeTabletStateInfo[];
16
16
 
17
- const prepareTablets = (tablets: ITablets) => {
17
+ const prepareTablets = (tablets: Tablets) => {
18
18
  const res = tablets.map((tablet) => {
19
19
  return {
20
20
  label: getTabletLabel(tablet.Type),
@@ -28,25 +28,32 @@ const prepareTablets = (tablets: ITablets) => {
28
28
  };
29
29
 
30
30
  interface TabletsStatisticProps {
31
- tablets: ITablets;
31
+ tablets: Tablets;
32
32
  path: string | undefined;
33
33
  nodeIds: string[] | number[];
34
+ backend?: string;
34
35
  }
35
36
 
36
- export const TabletsStatistic = ({tablets = [], path, nodeIds}: TabletsStatisticProps) => {
37
+ export const TabletsStatistic = ({tablets = [], path, nodeIds, backend}: TabletsStatisticProps) => {
37
38
  const renderTabletInfo = (item: ReturnType<typeof prepareTablets>[number], index: number) => {
38
- return (
39
- <Link
40
- to={createHref(routes.tabletsFilters, undefined, {
41
- nodeIds,
42
- state: item.state,
43
- type: item.type,
44
- path,
45
- })}
46
- key={index}
47
- className={b('tablet', {state: item.state?.toLowerCase()})}
48
- >
49
- {item.label}: {item.count}
39
+ const tabletsPath = createHref(routes.tabletsFilters, undefined, {
40
+ nodeIds,
41
+ state: item.state,
42
+ type: item.type,
43
+ path,
44
+ backend,
45
+ });
46
+
47
+ const label = `${item.label}: ${item.count}`;
48
+ const className = b('tablet', {state: item.state?.toLowerCase()});
49
+
50
+ return backend ? (
51
+ <a href={tabletsPath} key={index} className={className}>
52
+ {label}
53
+ </a>
54
+ ) : (
55
+ <Link to={tabletsPath} key={index} className={className}>
56
+ {label}
50
57
  </Link>
51
58
  );
52
59
  };
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import {Switch, Route, Redirect, Router} from 'react-router-dom';
2
+ import {Switch, Route, Redirect, Router, useLocation} from 'react-router-dom';
3
3
  import cn from 'bem-cn-lite';
4
4
  import {connect} from 'react-redux';
5
5
 
@@ -28,6 +28,8 @@ import {clusterTabsIds} from '../Cluster/utils';
28
28
  const b = cn('app');
29
29
 
30
30
  export function Content(props) {
31
+ const location = useLocation();
32
+
31
33
  const {singleClusterMode} = props;
32
34
  const isClustersPage =
33
35
  location.pathname.includes('/clusters') ||
@@ -54,7 +56,7 @@ export function Content(props) {
54
56
  };
55
57
  return (
56
58
  <React.Fragment>
57
- {!isClustersPage && <Header clusterName={props.clusterName} />}
59
+ {!isClustersPage && <Header mainPage={props.mainPage} />}
58
60
  <main className={b('main')}>{renderRoute()}</main>
59
61
  <ReduxTooltip />
60
62
  <AppIcons />
@@ -66,6 +68,7 @@ Content.propTypes = {
66
68
  singleClusterMode: PropTypes.bool,
67
69
  children: PropTypes.node,
68
70
  clusterName: PropTypes.string,
71
+ mainPage: PropTypes.object,
69
72
  };
70
73
 
71
74
  function ContentWrapper(props) {
@@ -7,6 +7,9 @@ import cn from 'bem-cn-lite';
7
7
  import {Icon, Button} from '@gravity-ui/uikit';
8
8
  import {AsideHeader, MenuItem as AsideHeaderMenuItem, FooterItem} from '@gravity-ui/navigation';
9
9
 
10
+ import squareChartBarIcon from '@gravity-ui/icons/svgs/square-chart-bar.svg';
11
+ import pulseIcon from '@gravity-ui/icons/svgs/pulse.svg';
12
+
10
13
  import signOutIcon from '../../assets/icons/signOut.svg';
11
14
  import signInIcon from '../../assets/icons/signIn.svg';
12
15
  import ydbLogoIcon from '../../assets/icons/ydb.svg';
@@ -15,18 +18,20 @@ import userChecked from '../../assets/icons/user-check.svg';
15
18
  import settingsIcon from '../../assets/icons/settings.svg';
16
19
  import supportIcon from '../../assets/icons/support.svg';
17
20
 
18
- import {UserSettings} from '../UserSettings/UserSettings';
19
-
20
- import routes, {createHref} from '../../routes';
21
-
22
21
  import {logout} from '../../store/reducers/authentication';
23
22
  import {getParsedSettingValue, setSettingValue} from '../../store/reducers/settings/settings';
23
+ import {TENANT_PAGE, TENANT_PAGES_IDS} from '../../store/reducers/tenant/constants';
24
+ import routes, {TENANT, createHref, parseQuery} from '../../routes';
25
+ import {useSetting, useTypedSelector} from '../../utils/hooks';
26
+ import {ASIDE_HEADER_COMPACT_KEY, TENANT_INITIAL_PAGE_KEY} from '../../utils/constants';
24
27
 
25
- import {ASIDE_HEADER_COMPACT_KEY} from '../../utils/constants';
28
+ import {getTenantPath} from '../Tenant/TenantPages';
29
+ import {UserSettings} from '../UserSettings/UserSettings';
26
30
 
27
31
  import './AsideNavigation.scss';
28
32
 
29
33
  const b = cn('kv-navigation');
34
+
30
35
  interface MenuItem {
31
36
  id: string;
32
37
  title: string;
@@ -111,9 +116,6 @@ interface AsideNavigationProps {
111
116
  setSettingValue: (name: string, value: string) => void;
112
117
  }
113
118
 
114
- // FIXME: add items or delete
115
- const items: MenuItem[] = [];
116
-
117
119
  enum Panel {
118
120
  UserSettings = 'UserSettings',
119
121
  }
@@ -124,19 +126,49 @@ function AsideNavigation(props: AsideNavigationProps) {
124
126
 
125
127
  const [visiblePanel, setVisiblePanel] = useState<Panel>();
126
128
 
129
+ const [initialTenantPage, setInitialTenantPage] = useSetting<string>(TENANT_INITIAL_PAGE_KEY);
130
+ const {tenantPage = initialTenantPage} = useTypedSelector((state) => state.tenant);
131
+
127
132
  const setIsCompact = (compact: boolean) => {
128
133
  props.setSettingValue(ASIDE_HEADER_COMPACT_KEY, JSON.stringify(compact));
129
134
  };
130
135
 
136
+ const {pathname} = location;
137
+ const queryParams = parseQuery(location);
138
+
139
+ const isTenantPage = pathname === `/${TENANT}`;
140
+
131
141
  const menuItems: AsideHeaderMenuItem[] = React.useMemo(() => {
132
- const {pathname} = location;
133
- const menuItems: AsideHeaderMenuItem[] = items.map((item) => {
134
- const locationKeysCoincidence = item.locationKeys?.filter((key) =>
135
- pathname.startsWith(key),
136
- );
137
- const current =
138
- (locationKeysCoincidence && locationKeysCoincidence.length > 0) ||
139
- item.location.startsWith(pathname);
142
+ if (!isTenantPage) {
143
+ return [];
144
+ }
145
+
146
+ const items: MenuItem[] = [
147
+ {
148
+ id: TENANT_PAGES_IDS.diagnostics,
149
+ title: 'Diagnostics',
150
+ icon: squareChartBarIcon,
151
+ iconSize: 20,
152
+ location: getTenantPath({
153
+ ...queryParams,
154
+ [TENANT_PAGE]: TENANT_PAGES_IDS.diagnostics,
155
+ }),
156
+ },
157
+ {
158
+ id: TENANT_PAGES_IDS.query,
159
+ title: 'Query',
160
+ icon: pulseIcon,
161
+ iconSize: 20,
162
+ location: getTenantPath({
163
+ ...queryParams,
164
+ [TENANT_PAGE]: TENANT_PAGES_IDS.query,
165
+ }),
166
+ },
167
+ ];
168
+
169
+ return items.map((item) => {
170
+ const current = item.id === tenantPage;
171
+
140
172
  return {
141
173
  id: item.id,
142
174
  title: item.title,
@@ -144,12 +176,12 @@ function AsideNavigation(props: AsideNavigationProps) {
144
176
  iconSize: item.iconSize,
145
177
  current,
146
178
  onItemClick: () => {
179
+ setInitialTenantPage(item.id);
147
180
  history.push(item.location);
148
181
  },
149
182
  };
150
183
  });
151
- return menuItems;
152
- }, [location, history]);
184
+ }, [tenantPage, isTenantPage, setInitialTenantPage, history, queryParams]);
153
185
 
154
186
  return (
155
187
  <React.Fragment>
@@ -10,7 +10,7 @@ import type {AdditionalClusterProps, AdditionalVersionsProps} from '../../types/
10
10
  import type {AdditionalNodesInfo} from '../../utils/nodes';
11
11
  import routes from '../../routes';
12
12
 
13
- import {setHeader} from '../../store/reducers/header';
13
+ import {setHeaderBreadcrumbs} from '../../store/reducers/header/header';
14
14
  import {getClusterInfo} from '../../store/reducers/cluster/cluster';
15
15
  import {getClusterNodes} from '../../store/reducers/clusterNodes/clusterNodes';
16
16
  import {parseNodesToVersionsValues, parseVersionsToVersionToColorMap} from '../../utils/versions';
@@ -80,14 +80,7 @@ function Cluster({
80
80
  );
81
81
 
82
82
  useEffect(() => {
83
- dispatch(
84
- setHeader([
85
- {
86
- text: Name || 'Cluster',
87
- link: getClusterPath(),
88
- },
89
- ]),
90
- );
83
+ dispatch(setHeaderBreadcrumbs('cluster', {}));
91
84
  }, [dispatch, Name]);
92
85
 
93
86
  const versionToColor = useMemo(() => {
@@ -124,13 +117,13 @@ function Cluster({
124
117
  const getTabEntityCount = (tabId: ClusterTab) => {
125
118
  switch (tabId) {
126
119
  case clusterTabsIds.tenants: {
127
- return cluster?.Tenants;
120
+ return cluster?.Tenants ? Number(cluster.Tenants) : undefined;
128
121
  }
129
122
  case clusterTabsIds.nodes: {
130
- return cluster?.NodesTotal;
123
+ return cluster?.NodesTotal ? Number(cluster.NodesTotal) : undefined;
131
124
  }
132
125
  default: {
133
- return null;
126
+ return undefined;
134
127
  }
135
128
  }
136
129
  };
@@ -153,7 +146,7 @@ function Cluster({
153
146
  items={clusterTabs.map((item) => {
154
147
  return {
155
148
  ...item,
156
- title: `${item.title} ${getTabEntityCount(item.id) || ''}`,
149
+ counter: getTabEntityCount(item.id),
157
150
  };
158
151
  })}
159
152
  wrapTo={({id}, node) => {
@@ -18,7 +18,7 @@ import type {TClusterInfo} from '../../../types/api/cluster';
18
18
  import {backend, customBackend} from '../../../store';
19
19
  import {formatStorageValues} from '../../../utils';
20
20
  import {useSetting, useTypedSelector} from '../../../utils/hooks';
21
- import {CLUSTER_INFO_HIDDEN_KEY} from '../../../utils/constants';
21
+ import {CLUSTER_INFO_HIDDEN_KEY, DEVELOPER_UI} from '../../../utils/constants';
22
22
 
23
23
  import {VersionsBar} from '../VersionsBar/VersionsBar';
24
24
  import {ClusterInfoSkeleton} from '../ClusterInfoSkeleton/ClusterInfoSkeleton';
@@ -126,7 +126,7 @@ export const ClusterInfo = ({
126
126
  }: ClusterInfoProps) => {
127
127
  const singleClusterMode = useTypedSelector((state) => state.singleClusterMode);
128
128
 
129
- const [clusterInfoHidden, setClusterInfoHidden] = useSetting(CLUSTER_INFO_HIDDEN_KEY, false);
129
+ const [clusterInfoHidden, setClusterInfoHidden] = useSetting<boolean>(CLUSTER_INFO_HIDDEN_KEY);
130
130
 
131
131
  const togleClusterInfoVisibility = () => {
132
132
  setClusterInfoHidden(!clusterInfoHidden);
@@ -141,7 +141,7 @@ export const ClusterInfo = ({
141
141
  const {info = [], links = []} = additionalClusterProps;
142
142
 
143
143
  const clusterInfo = getInfo(cluster, versionsValues, info, [
144
- {title: 'Internal Viewer', url: internalLink},
144
+ {title: DEVELOPER_UI, url: internalLink},
145
145
  ...links,
146
146
  ]);
147
147
 
@@ -1,3 +1,10 @@
1
+ import {Icon} from '@gravity-ui/uikit';
2
+ import cubes3Icon from '@gravity-ui/icons/svgs/cubes-3.svg';
3
+ import databasesIcon from '@gravity-ui/icons/svgs/databases.svg';
4
+ import hardDriveIcon from '@gravity-ui/icons/svgs/hard-drive.svg';
5
+
6
+ import versionsIcon from '../../assets/icons/versions.svg';
7
+
1
8
  import type {ValueOf} from '../../types/common';
2
9
  import routes, {createHref} from '../../routes';
3
10
 
@@ -13,18 +20,22 @@ export type ClusterTab = ValueOf<typeof clusterTabsIds>;
13
20
  const tenants = {
14
21
  id: clusterTabsIds.tenants,
15
22
  title: 'Databases',
23
+ icon: <Icon data={databasesIcon} />,
16
24
  };
17
25
  const nodes = {
18
26
  id: clusterTabsIds.nodes,
19
27
  title: 'Nodes',
28
+ icon: <Icon data={cubes3Icon} />,
20
29
  };
21
30
  const storage = {
22
31
  id: clusterTabsIds.storage,
23
32
  title: 'Storage',
33
+ icon: <Icon data={hardDriveIcon} />,
24
34
  };
25
35
  const versions = {
26
36
  id: clusterTabsIds.versions,
27
37
  title: 'Versions',
38
+ icon: <Icon data={versionsIcon} />,
28
39
  };
29
40
 
30
41
  export const clusterTabs = [tenants, nodes, storage, versions];
@@ -13,4 +13,13 @@
13
13
  border-bottom: 1px solid var(--yc-color-line-generic);
14
14
 
15
15
  @include body2-typography;
16
+
17
+ &__breadcrumb {
18
+ display: flex;
19
+ align-items: center;
20
+
21
+ &__icon {
22
+ margin-right: 3px;
23
+ }
24
+ }
16
25
  }
@@ -1,13 +1,19 @@
1
+ import {useEffect, useMemo} from 'react';
2
+ import {useHistory, useLocation} from 'react-router';
3
+ import {useDispatch} from 'react-redux';
1
4
  import block from 'bem-cn-lite';
2
- import {useHistory} from 'react-router';
3
5
 
4
- import {Breadcrumbs, BreadcrumbsItem} from '@gravity-ui/uikit';
6
+ import {Breadcrumbs, Icon} from '@gravity-ui/uikit';
5
7
 
6
8
  import {ExternalLinkWithIcon} from '../../components/ExternalLinkWithIcon/ExternalLinkWithIcon';
7
9
 
8
10
  import {backend, customBackend} from '../../store';
9
- import {HeaderItemType} from '../../store/reducers/header';
11
+ import {getClusterInfo} from '../../store/reducers/cluster/cluster';
10
12
  import {useTypedSelector} from '../../utils/hooks';
13
+ import {DEVELOPER_UI} from '../../utils/constants';
14
+ import {parseQuery} from '../../routes';
15
+
16
+ import {RawBreadcrumbItem, getBreadcrumbs} from './breadcrumbs';
11
17
 
12
18
  import './Header.scss';
13
19
 
@@ -21,23 +27,57 @@ const getInternalLink = (singleClusterMode: boolean) => {
21
27
  return backend + '/internal';
22
28
  };
23
29
 
24
- function Header() {
25
- const {singleClusterMode, header}: {singleClusterMode: boolean; header: HeaderItemType[]} =
26
- useTypedSelector((state) => state);
30
+ interface HeaderProps {
31
+ mainPage?: RawBreadcrumbItem;
32
+ }
27
33
 
34
+ function Header({mainPage}: HeaderProps) {
35
+ const dispatch = useDispatch();
28
36
  const history = useHistory();
37
+ const location = useLocation();
29
38
 
30
- const renderHeader = () => {
31
- const breadcrumbItems = header.reduce((acc, el) => {
39
+ const singleClusterMode = useTypedSelector((state) => state.singleClusterMode);
40
+ const {page, pageBreadcrumbsOptions} = useTypedSelector((state) => state.header);
41
+ const {data} = useTypedSelector((state) => state.cluster);
42
+
43
+ const queryParams = parseQuery(location);
44
+
45
+ const clusterNameFromQuery = queryParams.clusterName?.toString();
46
+
47
+ const clusterNameFinal = data?.Name || clusterNameFromQuery;
48
+
49
+ useEffect(() => {
50
+ dispatch(getClusterInfo(clusterNameFromQuery));
51
+ }, [dispatch, clusterNameFromQuery]);
52
+
53
+ const breadcrumbItems = useMemo(() => {
54
+ const rawBreadcrumbs: RawBreadcrumbItem[] = [];
55
+ let options = pageBreadcrumbsOptions;
56
+
57
+ if (mainPage) {
58
+ rawBreadcrumbs.push(mainPage);
59
+ }
60
+
61
+ if (clusterNameFinal) {
62
+ options = {
63
+ ...pageBreadcrumbsOptions,
64
+ clusterName: clusterNameFinal,
65
+ };
66
+ }
67
+
68
+ const breadcrumbs = getBreadcrumbs(page, options, rawBreadcrumbs, queryParams);
69
+
70
+ return breadcrumbs.map((item) => {
32
71
  const action = () => {
33
- if (el.link) {
34
- history.push(el.link);
72
+ if (item.link) {
73
+ history.push(item.link);
35
74
  }
36
75
  };
37
- acc.push({text: el.text, action});
38
- return acc;
39
- }, [] as BreadcrumbsItem[]);
76
+ return {...item, action};
77
+ });
78
+ }, [clusterNameFinal, mainPage, history, queryParams, page, pageBreadcrumbsOptions]);
40
79
 
80
+ const renderHeader = () => {
41
81
  return (
42
82
  <header className={b()}>
43
83
  <div>
@@ -45,11 +85,27 @@ function Header() {
45
85
  items={breadcrumbItems}
46
86
  lastDisplayedItemsCount={1}
47
87
  firstDisplayedItemsCount={1}
88
+ renderItemContent={({icon, text}) => {
89
+ if (!icon) {
90
+ return text;
91
+ }
92
+ return (
93
+ <span className={b('breadcrumb')}>
94
+ <Icon
95
+ width={16}
96
+ height={16}
97
+ data={icon}
98
+ className={b('breadcrumb__icon')}
99
+ />
100
+ {text}
101
+ </span>
102
+ );
103
+ }}
48
104
  />
49
105
  </div>
50
106
 
51
107
  <ExternalLinkWithIcon
52
- title={'Internal Viewer'}
108
+ title={DEVELOPER_UI}
53
109
  url={getInternalLink(singleClusterMode)}
54
110
  />
55
111
  </header>