ydb-embedded-ui 4.27.1 → 4.29.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/assets/illustrations/dark/error.svg +32 -0
  3. package/dist/assets/illustrations/light/error.svg +32 -0
  4. package/dist/components/EmptyState/EmptyState.scss +5 -2
  5. package/dist/components/EmptyState/EmptyState.tsx +11 -3
  6. package/dist/components/ErrorBoundary/ErrorBoundary.scss +40 -0
  7. package/dist/components/ErrorBoundary/ErrorBoundary.tsx +62 -0
  8. package/dist/components/ErrorBoundary/i18n/en.json +7 -0
  9. package/dist/components/ErrorBoundary/i18n/index.ts +11 -0
  10. package/dist/components/ErrorBoundary/i18n/ru.json +7 -0
  11. package/dist/components/Errors/403/AccessDenied.tsx +4 -3
  12. package/dist/components/Illustration/Illustration.tsx +3 -1
  13. package/dist/components/ProblemFilter/ProblemFilter.tsx +1 -1
  14. package/dist/components/UptimeFIlter/UptimeFilter.tsx +1 -1
  15. package/dist/components/VirtualTable/VirtualTable.scss +1 -1
  16. package/dist/containers/App/App.js +5 -2
  17. package/dist/containers/Cluster/Cluster.tsx +11 -12
  18. package/dist/containers/Nodes/Nodes.tsx +4 -4
  19. package/dist/containers/Nodes/NodesWrapper.tsx +21 -0
  20. package/dist/containers/Nodes/VirtualNodes.tsx +4 -14
  21. package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.scss +1 -0
  22. package/dist/containers/Storage/EmptyFilter/EmptyFilter.tsx +1 -0
  23. package/dist/containers/Storage/PDisk/PDisk.tsx +5 -7
  24. package/dist/containers/Storage/Storage.tsx +30 -67
  25. package/dist/containers/Storage/StorageControls/StorageControls.tsx +104 -0
  26. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +18 -62
  27. package/dist/containers/Storage/StorageGroups/StorageGroupsEmptyDataMessage.tsx +30 -0
  28. package/dist/containers/Storage/StorageGroups/VirtualStorageGroups.tsx +94 -0
  29. package/dist/containers/Storage/StorageGroups/getGroups.ts +21 -0
  30. package/dist/containers/Storage/StorageGroups/getStorageGroupsColumns.tsx +73 -50
  31. package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +23 -138
  32. package/dist/containers/Storage/StorageNodes/StorageNodesEmptyDataMessage.tsx +44 -0
  33. package/dist/containers/Storage/StorageNodes/VirtualStorageNodes.tsx +105 -0
  34. package/dist/containers/Storage/StorageNodes/getNodes.ts +26 -0
  35. package/dist/containers/Storage/StorageNodes/getStorageNodesColumns.tsx +125 -0
  36. package/dist/containers/Storage/StorageNodes/shared.ts +9 -0
  37. package/dist/containers/Storage/StorageTypeFilter/StorageTypeFilter.tsx +1 -1
  38. package/dist/containers/Storage/StorageVisibleEntitiesFilter/StorageVisibleEntitiesFilter.tsx +1 -1
  39. package/dist/containers/Storage/StorageWrapper.tsx +23 -0
  40. package/dist/containers/Storage/UsageFilter/UsageFilter.tsx +3 -4
  41. package/dist/containers/Storage/VirtualStorage.tsx +112 -0
  42. package/dist/containers/Storage/i18n/en.json +7 -0
  43. package/dist/containers/Storage/i18n/index.ts +11 -0
  44. package/dist/containers/Storage/i18n/ru.json +7 -0
  45. package/dist/containers/Storage/shared.ts +3 -0
  46. package/dist/containers/Tenants/Tenants.tsx +2 -2
  47. package/dist/containers/UserSettings/i18n/en.json +2 -2
  48. package/dist/containers/UserSettings/i18n/ru.json +2 -2
  49. package/dist/containers/UserSettings/settings.ts +4 -4
  50. package/dist/index.tsx +8 -5
  51. package/dist/store/reducers/storage/selectors.ts +0 -20
  52. package/dist/utils/registerError.ts +18 -0
  53. package/package.json +7 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.29.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.28.0...v4.29.0) (2024-01-12)
4
+
5
+
6
+ ### Features
7
+
8
+ * add ErrorBoundary ([#549](https://github.com/ydb-platform/ydb-embedded-ui/issues/549)) ([f5ad224](https://github.com/ydb-platform/ydb-embedded-ui/commit/f5ad224b342e0fa25b1bafa3f5e2202ce165ef80))
9
+
10
+ ## [4.28.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.27.1...v4.28.0) (2024-01-10)
11
+
12
+
13
+ ### Features
14
+
15
+ * **Storage:** use VirtualTable ([#628](https://github.com/ydb-platform/ydb-embedded-ui/issues/628)) ([67fd9b0](https://github.com/ydb-platform/ydb-embedded-ui/commit/67fd9b03653dd28be650094c987451b09fcce858))
16
+
3
17
  ## [4.27.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.27.0...v4.27.1) (2024-01-10)
4
18
 
5
19
 
@@ -0,0 +1,32 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="230" height="230" fill="none">
2
+ <path fill="#BECFE0" fill-opacity=".8" fill-rule="evenodd" d="M169.001 51.666c5.523 0 10 4.477 10 10v21.017l18.197-10.506c4.783-2.762 10.899-1.123 13.66 3.66 2.761 4.783 1.123 10.899-3.66 13.66l-18.197 10.507 18.198 10.506c4.783 2.762 6.421 8.878 3.66 13.661-2.762 4.782-8.877 6.421-13.66 3.66l-18.198-10.506v21.008c0 5.523-4.477 10-10 10-5.522 0-10-4.477-10-10v-21.009l-18.199 10.507c-4.782 2.761-10.898 1.122-13.66-3.66-2.761-4.783-1.122-10.899 3.66-13.661l18.199-10.506-18.198-10.507c-4.783-2.761-6.421-8.877-3.66-13.66 2.762-4.783 8.877-6.422 13.66-3.66l18.198 10.507V61.666c0-5.523 4.478-10 10-10Z" clip-rule="evenodd"/>
3
+ <path fill="#E7E7E7" fill-rule="evenodd" d="M171.523 95.922a11.003 11.003 0 0 1 1.099 8.347l-13.208 49.291c-1.572 5.868-7.604 9.351-13.472 7.778l-25.356-6.794a44.998 44.998 0 0 1-.53 1.929l25.368 6.797c6.935 1.858 14.064-2.257 15.922-9.192l13.207-49.291c.893-3.33.426-6.879-1.298-9.865L155.598 64.34a12.999 12.999 0 0 0-7.894-6.057l-29.972-8.031c-6.935-1.858-14.063 2.257-15.922 9.192l-11.328 42.277c.64.192 1.276.398 1.905.618l11.355-42.377c1.573-5.868 7.604-9.35 13.472-7.778l29.973 8.03a11 11 0 0 1 6.679 5.126l17.657 30.582Z" clip-rule="evenodd"/>
4
+ <path fill="#FF5958" fill-opacity=".8" d="M35.388 155.273c-6.29-23.472 7.64-47.599 31.113-53.889 23.472-6.289 47.599 7.641 53.889 31.113 6.289 23.473-7.641 47.599-31.113 53.889-23.473 6.289-47.6-7.64-53.889-31.113Z"/>
5
+ <path stroke="#E7E7E7" stroke-width="2" d="M60.636 117.734c53.586-33.459-26.868-81.505-36.557-61.318-11.802 24.59 99.395 51.098 128.865-26.3"/>
6
+ <mask id="b" width="89" height="89" x="33" y="99" maskUnits="userSpaceOnUse" style="mask-type:alpha">
7
+ <path fill="#FF5958" fill-opacity=".9" d="M35.388 155.273c-6.29-23.472 7.64-47.599 31.113-53.889 23.472-6.289 47.599 7.641 53.889 31.113 6.289 23.473-7.641 47.599-31.113 53.889-23.473 6.289-47.6-7.64-53.889-31.113Z"/>
8
+ </mask>
9
+ <g filter="url(#a)" mask="url(#b)">
10
+ <path stroke="#E7E7E7" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".6" stroke-width="2" d="M172.389 95.422a12.004 12.004 0 0 1 1.199 9.106l-13.208 49.291c-1.715 6.401-8.295 10.2-14.697 8.485L91.591 147.81c-6.401-1.715-10.2-8.295-8.485-14.697l19.67-73.41c1.716-6.402 8.296-10.2 14.697-8.485l29.972 8.03a11.998 11.998 0 0 1 7.287 5.592l17.657 30.582Z"/>
11
+ </g>
12
+ <g filter="url(#c)">
13
+ <path fill="#fff" fill-opacity=".72" fill-rule="evenodd" d="M80.866 130.432a6.359 6.359 0 1 1-12.284 3.29 6.359 6.359 0 0 1 12.284-3.29Zm4.817-1.291c1.621 6.052-1.97 12.273-8.022 13.894-6.052 1.622-12.273-1.97-13.895-8.022-1.621-6.052 1.97-12.272 8.022-13.894 6.052-1.622 12.273 1.97 13.895 8.022Zm-21.346 32.565c-.154-.577-.009-2.61 2.877-5.555 2.665-2.721 6.917-5.33 12.158-6.734 5.24-1.404 10.227-1.271 13.896-.247 3.971 1.108 5.114 2.796 5.268 3.372a3.116 3.116 0 0 1-2.204 3.817l-28.178 7.55a3.116 3.116 0 0 1-3.817-2.203ZM78.081 144.6c-12.054 3.23-20.238 12.134-18.56 18.396a8.103 8.103 0 0 0 9.924 5.73l28.178-7.55a8.104 8.104 0 0 0 5.73-9.925c-1.678-6.261-13.218-9.881-25.272-6.651Z" clip-rule="evenodd"/>
14
+ </g>
15
+ <defs>
16
+ <filter id="a" width="113.303" height="133.91" x="71.693" y="39.806" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
17
+ <feFlood flood-opacity="0" result="BackgroundImageFix"/>
18
+ <feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
19
+ <feGaussianBlur result="effect1_foregroundBlur_1301_35376" stdDeviation="5"/>
20
+ </filter>
21
+ <filter id="c" width="73.289" height="73.288" x="41.018" y="106.391" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
22
+ <feFlood flood-opacity="0" result="BackgroundImageFix"/>
23
+ <feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
24
+ <feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
25
+ <feOffset/>
26
+ <feGaussianBlur stdDeviation="1.917"/>
27
+ <feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
28
+ <feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.8 0"/>
29
+ <feBlend in2="shape" result="effect1_innerShadow_1301_35376"/>
30
+ </filter>
31
+ </defs>
32
+ </svg>
@@ -0,0 +1,32 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="230" height="230" fill="none">
2
+ <path fill="#BECFE0" fill-opacity=".9" fill-rule="evenodd" d="M169.001 51.666c5.523 0 10 4.477 10 10v21.017l18.197-10.506c4.783-2.762 10.899-1.123 13.66 3.66 2.761 4.783 1.123 10.899-3.66 13.66l-18.197 10.507 18.198 10.506c4.783 2.762 6.421 8.878 3.66 13.661-2.762 4.782-8.877 6.421-13.66 3.66l-18.198-10.506v21.008c0 5.523-4.477 10-10 10-5.522 0-10-4.477-10-10v-21.009l-18.199 10.507c-4.782 2.761-10.898 1.122-13.66-3.66-2.761-4.783-1.122-10.899 3.66-13.661l18.199-10.506-18.198-10.507c-4.783-2.761-6.421-8.877-3.66-13.66 2.762-4.783 8.877-6.422 13.66-3.66l18.198 10.507V61.666c0-5.523 4.478-10 10-10Z" clip-rule="evenodd"/>
3
+ <path fill="#262626" fill-rule="evenodd" d="M171.523 95.922a11.003 11.003 0 0 1 1.099 8.347l-13.208 49.291c-1.572 5.868-7.604 9.351-13.472 7.778l-25.356-6.794a44.998 44.998 0 0 1-.53 1.929l25.368 6.797c6.935 1.858 14.064-2.257 15.922-9.192l13.207-49.291c.893-3.33.426-6.879-1.298-9.865L155.598 64.34a12.999 12.999 0 0 0-7.894-6.057l-29.972-8.031c-6.935-1.858-14.063 2.257-15.922 9.192l-11.328 42.277c.64.192 1.276.398 1.905.618l11.355-42.377c1.573-5.868 7.604-9.35 13.472-7.778l29.973 8.03a11 11 0 0 1 6.679 5.126l17.657 30.582Z" clip-rule="evenodd"/>
4
+ <path fill="#FF5958" fill-opacity=".9" d="M35.388 155.273c-6.29-23.472 7.64-47.599 31.113-53.889 23.472-6.289 47.599 7.641 53.889 31.113 6.289 23.473-7.641 47.599-31.113 53.889-23.473 6.289-47.6-7.64-53.889-31.113Z"/>
5
+ <path stroke="#262626" stroke-width="2" d="M60.636 117.734c53.586-33.459-26.868-81.505-36.557-61.318-11.802 24.59 99.395 51.098 128.865-26.3"/>
6
+ <mask id="b" width="89" height="89" x="33" y="99" maskUnits="userSpaceOnUse" style="mask-type:alpha">
7
+ <path fill="#FF5958" fill-opacity=".9" d="M35.388 155.273c-6.29-23.472 7.64-47.599 31.113-53.889 23.472-6.289 47.599 7.641 53.889 31.113 6.289 23.473-7.641 47.599-31.113 53.889-23.473 6.289-47.6-7.64-53.889-31.113Z"/>
8
+ </mask>
9
+ <g filter="url(#a)" mask="url(#b)">
10
+ <path stroke="#262626" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".6" stroke-width="2" d="M172.389 95.422a12.004 12.004 0 0 1 1.199 9.106l-13.208 49.291c-1.715 6.401-8.295 10.2-14.697 8.485L91.591 147.81c-6.401-1.715-10.2-8.295-8.485-14.697l19.67-73.41c1.716-6.402 8.296-10.2 14.697-8.485l29.972 8.03a11.998 11.998 0 0 1 7.287 5.592l17.657 30.582Z"/>
11
+ </g>
12
+ <g filter="url(#c)">
13
+ <path fill="#fff" fill-opacity=".72" fill-rule="evenodd" d="M80.866 130.432a6.359 6.359 0 1 1-12.284 3.29 6.359 6.359 0 0 1 12.284-3.29Zm4.817-1.291c1.621 6.052-1.97 12.273-8.022 13.894-6.052 1.622-12.273-1.97-13.895-8.022-1.621-6.052 1.97-12.272 8.022-13.894 6.052-1.622 12.273 1.97 13.895 8.022Zm-21.346 32.565c-.154-.577-.009-2.61 2.877-5.555 2.665-2.721 6.917-5.33 12.158-6.734 5.24-1.404 10.227-1.271 13.896-.247 3.971 1.108 5.114 2.796 5.268 3.372a3.116 3.116 0 0 1-2.204 3.817l-28.178 7.55a3.116 3.116 0 0 1-3.817-2.203ZM78.081 144.6c-12.054 3.23-20.238 12.134-18.56 18.396a8.103 8.103 0 0 0 9.924 5.73l28.178-7.55a8.104 8.104 0 0 0 5.73-9.925c-1.678-6.261-13.218-9.881-25.272-6.651Z" clip-rule="evenodd"/>
14
+ </g>
15
+ <defs>
16
+ <filter id="a" width="113.303" height="133.91" x="71.693" y="39.806" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
17
+ <feFlood flood-opacity="0" result="BackgroundImageFix"/>
18
+ <feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
19
+ <feGaussianBlur result="effect1_foregroundBlur_1301_31085" stdDeviation="5"/>
20
+ </filter>
21
+ <filter id="c" width="73.289" height="73.288" x="41.018" y="106.391" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
22
+ <feFlood flood-opacity="0" result="BackgroundImageFix"/>
23
+ <feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
24
+ <feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
25
+ <feOffset/>
26
+ <feGaussianBlur stdDeviation="1.917"/>
27
+ <feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
28
+ <feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.8 0"/>
29
+ <feBlend in2="shape" result="effect1_innerShadow_1301_31085"/>
30
+ </filter>
31
+ </defs>
32
+ </svg>
@@ -19,10 +19,13 @@
19
19
  }
20
20
 
21
21
  &_size_m {
22
- position: relative;
23
-
24
22
  width: 800px;
25
23
  height: 240px;
24
+ }
25
+
26
+ &_position_center {
27
+ position: relative;
28
+
26
29
  margin: 0 auto;
27
30
  }
28
31
  }
@@ -13,18 +13,26 @@ const sizes = {
13
13
  l: 350,
14
14
  };
15
15
 
16
- interface EmptyStateProps {
16
+ export interface EmptyStateProps {
17
17
  title: string;
18
18
  image?: ReactNode;
19
19
  description?: ReactNode;
20
20
  actions?: ReactNode[];
21
21
  size?: keyof typeof sizes;
22
+ position?: 'left' | 'center';
22
23
  }
23
24
 
24
- export const EmptyState = ({image, title, description, actions, size = 'm'}: EmptyStateProps) => {
25
+ export const EmptyState = ({
26
+ image,
27
+ title,
28
+ description,
29
+ actions,
30
+ size = 'm',
31
+ position = 'center',
32
+ }: EmptyStateProps) => {
25
33
  return (
26
34
  <div className={block({size})}>
27
- <div className={block('wrapper', {size})}>
35
+ <div className={block('wrapper', {size, position})}>
28
36
  <div className={block('image')}>
29
37
  {image ? (
30
38
  image
@@ -0,0 +1,40 @@
1
+ @import '../../styles/mixins.scss';
2
+
3
+ .ydb-error-boundary {
4
+ display: flex;
5
+ flex-direction: row;
6
+ align-items: flex-start;
7
+
8
+ padding: 20px;
9
+
10
+ @include body-2-typography();
11
+
12
+ &__illustration {
13
+ width: 230px;
14
+ height: 230px;
15
+ margin-right: 20px;
16
+ }
17
+ &__error-title {
18
+ margin-top: 44px;
19
+ @include lead-typography();
20
+ }
21
+ &__error-description {
22
+ margin-top: 12px;
23
+ }
24
+ &__show-details {
25
+ margin-top: 8px;
26
+ }
27
+ &__error-details {
28
+ padding: 13px 18px;
29
+
30
+ border: 1px solid var(--g-color-line-generic);
31
+ background-color: var(--g-color-base-generic-ultralight);
32
+ }
33
+ &__actions {
34
+ display: flex;
35
+ flex-direction: row;
36
+ gap: 10px;
37
+
38
+ margin-top: 20px;
39
+ }
40
+ }
@@ -0,0 +1,62 @@
1
+ import type {ReactNode} from 'react';
2
+ import {ErrorBoundary as ErrorBoundaryBase} from 'react-error-boundary';
3
+ import cn from 'bem-cn-lite';
4
+
5
+ import {Button, Disclosure} from '@gravity-ui/uikit';
6
+
7
+ import {registerError} from '../../utils/registerError';
8
+ import {Illustration} from '../Illustration';
9
+ import i18n from './i18n';
10
+ import './ErrorBoundary.scss';
11
+
12
+ const b = cn('ydb-error-boundary');
13
+
14
+ interface ErrorBoundaryProps {
15
+ children?: ReactNode;
16
+ useRetry?: boolean;
17
+ onReportProblem?: (error?: Error) => void;
18
+ }
19
+
20
+ export const ErrorBoundary = ({children, useRetry = true, onReportProblem}: ErrorBoundaryProps) => {
21
+ return (
22
+ <ErrorBoundaryBase
23
+ onError={(error, info) => {
24
+ registerError(error, info.componentStack, 'error-boundary');
25
+ }}
26
+ fallbackRender={({error, resetErrorBoundary}) => {
27
+ return (
28
+ <div className={b(null)}>
29
+ <Illustration name="error" className={b('illustration')} />
30
+ <div className={b('content')}>
31
+ <h2 className={b('error-title')}>{i18n('error-title')}</h2>
32
+ <div className={b('error-description')}>
33
+ {i18n('error-description')}
34
+ </div>
35
+ <Disclosure
36
+ summary={i18n('show-details')}
37
+ className={b('show-details')}
38
+ size="m"
39
+ >
40
+ <pre className={b('error-details')}>{error.stack}</pre>
41
+ </Disclosure>
42
+ <div className={b('actions')}>
43
+ {useRetry && (
44
+ <Button view="outlined" onClick={resetErrorBoundary}>
45
+ {i18n('button-reset')}
46
+ </Button>
47
+ )}
48
+ {onReportProblem && (
49
+ <Button view="outlined" onClick={() => onReportProblem(error)}>
50
+ {i18n('report-problem')}
51
+ </Button>
52
+ )}
53
+ </div>
54
+ </div>
55
+ </div>
56
+ );
57
+ }}
58
+ >
59
+ {children}
60
+ </ErrorBoundaryBase>
61
+ );
62
+ };
@@ -0,0 +1,7 @@
1
+ {
2
+ "error-title": "Something went wrong",
3
+ "error-description": "We have something broken, but don't worry, it won't last long",
4
+ "show-details": "Show details",
5
+ "report-problem": "Report a problem",
6
+ "button-reset": "Try again"
7
+ }
@@ -0,0 +1,11 @@
1
+ import {i18n, Lang} from '../../../utils/i18n';
2
+
3
+ import en from './en.json';
4
+ import ru from './ru.json';
5
+
6
+ const COMPONENT = 'ydb-error-boundary';
7
+
8
+ i18n.registerKeyset(Lang.En, COMPONENT, en);
9
+ i18n.registerKeyset(Lang.Ru, COMPONENT, ru);
10
+
11
+ export default i18n.keyset(COMPONENT);
@@ -0,0 +1,7 @@
1
+ {
2
+ "error-title": "Что-то пошло не так",
3
+ "error-description": "У нас что-то сломалось, но не переживайте, это ненадолго",
4
+ "show-details": "Показать детали",
5
+ "report-problem": "Сообщить о проблеме",
6
+ "button-reset": "Попробовать снова"
7
+ }
@@ -1,19 +1,20 @@
1
- import {EmptyState} from '../../EmptyState';
1
+ import {EmptyState, type EmptyStateProps} from '../../EmptyState';
2
2
  import {Illustration} from '../../Illustration';
3
3
 
4
4
  import i18n from '../i18n';
5
5
 
6
- interface AccessDeniedProps {
6
+ interface AccessDeniedProps extends Omit<EmptyStateProps, 'image' | 'title' | 'description'> {
7
7
  title?: string;
8
8
  description?: string;
9
9
  }
10
10
 
11
- export const AccessDenied = ({title, description}: AccessDeniedProps) => {
11
+ export const AccessDenied = ({title, description, ...restProps}: AccessDeniedProps) => {
12
12
  return (
13
13
  <EmptyState
14
14
  image={<Illustration name="403" />}
15
15
  title={title || i18n('403.title')}
16
16
  description={description || i18n('403.description')}
17
+ {...restProps}
17
18
  />
18
19
  );
19
20
  };
@@ -13,10 +13,12 @@ const store: IllustrationStore = {
13
13
  light: {
14
14
  403: () => import('../../assets/illustrations/light/403.svg'),
15
15
  thumbsUp: () => import('../../assets/illustrations/light/thumbsUp.svg'),
16
+ error: () => import('../../assets/illustrations/light/error.svg'),
16
17
  },
17
18
  dark: {
18
19
  403: () => import('../../assets/illustrations/dark/403.svg'),
19
- thumbsUp: () => import('../../assets/illustrations/light/thumbsUp.svg'),
20
+ thumbsUp: () => import('../../assets/illustrations/dark/thumbsUp.svg'),
21
+ error: () => import('../../assets/illustrations/dark/error.svg'),
20
22
  },
21
23
  };
22
24
 
@@ -5,7 +5,7 @@ import {ProblemFilterValues} from '../../store/reducers/settings/settings';
5
5
 
6
6
  interface ProblemFilterProps {
7
7
  value: ProblemFilterValue;
8
- onChange: (value: string) => void;
8
+ onChange: (value: ProblemFilterValue) => void;
9
9
  className?: string;
10
10
  }
11
11
 
@@ -4,7 +4,7 @@ import {NodesUptimeFilterValues, NodesUptimeFilterTitles} from '../../utils/node
4
4
 
5
5
  interface UptimeFilterProps {
6
6
  value: NodesUptimeFilterValues;
7
- onChange: (value: string) => void;
7
+ onChange: (value: NodesUptimeFilterValues) => void;
8
8
  className?: string;
9
9
  }
10
10
 
@@ -15,7 +15,7 @@
15
15
  @include body-2-typography();
16
16
 
17
17
  &__table {
18
- width: 100%;
18
+ width: max-content;
19
19
  max-width: 100%;
20
20
 
21
21
  table-layout: fixed;
@@ -7,6 +7,7 @@ import AsideNavigation from '../AsideNavigation/AsideNavigation';
7
7
 
8
8
  import {getUser} from '../../store/reducers/authentication/authentication';
9
9
  import {registerLanguages} from '../../utils/monaco';
10
+ import {ErrorBoundary} from '../../components/ErrorBoundary/ErrorBoundary';
10
11
 
11
12
  import './App.scss';
12
13
 
@@ -38,8 +39,10 @@ class App extends React.Component {
38
39
  const {singleClusterMode, clusterName} = this.props;
39
40
  return (
40
41
  <AsideNavigation>
41
- <Content singleClusterMode={singleClusterMode} clusterName={clusterName} />
42
- <div id="fullscreen-root"></div>
42
+ <ErrorBoundary>
43
+ <Content singleClusterMode={singleClusterMode} clusterName={clusterName} />
44
+ <div id="fullscreen-root"></div>
45
+ </ErrorBoundary>
43
46
  </AsideNavigation>
44
47
  );
45
48
  }
@@ -18,14 +18,12 @@ import {setHeaderBreadcrumbs} from '../../store/reducers/header/header';
18
18
  import {getClusterInfo} from '../../store/reducers/cluster/cluster';
19
19
  import {getClusterNodes} from '../../store/reducers/clusterNodes/clusterNodes';
20
20
  import {parseNodesToVersionsValues, parseVersionsToVersionToColorMap} from '../../utils/versions';
21
- import {useAutofetcher, useSetting, useTypedSelector} from '../../utils/hooks';
22
- import {USE_BACKEND_PARAMS_FOR_TABLES_KEY} from '../../utils/constants';
21
+ import {useAutofetcher, useTypedSelector} from '../../utils/hooks';
23
22
 
24
23
  import {InternalLink} from '../../components/InternalLink';
25
24
  import {Tenants} from '../Tenants/Tenants';
26
- import {Nodes} from '../Nodes/Nodes';
27
- import {VirtualNodes} from '../Nodes/VirtualNodes';
28
- import {Storage} from '../Storage/Storage';
25
+ import {StorageWrapper} from '../Storage/StorageWrapper';
26
+ import {NodesWrapper} from '../Nodes/NodesWrapper';
29
27
  import {Versions} from '../Versions/Versions';
30
28
 
31
29
  import {ClusterInfo} from './ClusterInfo/ClusterInfo';
@@ -55,8 +53,6 @@ function Cluster({
55
53
  const match = useRouteMatch<{activeTab: string}>(routes.cluster);
56
54
  const {activeTab = clusterTabsIds.tenants} = match?.params || {};
57
55
 
58
- const [useVirtualNodes] = useSetting<boolean>(USE_BACKEND_PARAMS_FOR_TABLES_KEY);
59
-
60
56
  const location = useLocation();
61
57
  const queryParams = qs.parse(location.search, {
62
58
  ignoreQueryPrefix: true,
@@ -111,17 +107,20 @@ function Cluster({
111
107
  return <Tenants additionalTenantsProps={additionalTenantsProps} />;
112
108
  }
113
109
  case clusterTabsIds.nodes: {
114
- return useVirtualNodes ? (
115
- <VirtualNodes
110
+ return (
111
+ <NodesWrapper
116
112
  parentContainer={container.current}
117
113
  additionalNodesProps={additionalNodesProps}
118
114
  />
119
- ) : (
120
- <Nodes additionalNodesProps={additionalNodesProps} />
121
115
  );
122
116
  }
123
117
  case clusterTabsIds.storage: {
124
- return <Storage additionalNodesProps={additionalNodesProps} />;
118
+ return (
119
+ <StorageWrapper
120
+ parentContainer={container.current}
121
+ additionalNodesProps={additionalNodesProps}
122
+ />
123
+ );
125
124
  }
126
125
  case clusterTabsIds.versions: {
127
126
  return <Versions versionToColor={versionToColor} />;
@@ -106,12 +106,12 @@ export const Nodes = ({path, additionalNodesProps = {}}: NodesProps) => {
106
106
  dispatch(setSearchValue(value));
107
107
  };
108
108
 
109
- const handleProblemFilterChange = (value: string) => {
110
- dispatch(changeFilter(value as ProblemFilterValue));
109
+ const handleProblemFilterChange = (value: ProblemFilterValue) => {
110
+ dispatch(changeFilter(value));
111
111
  };
112
112
 
113
- const handleUptimeFilterChange = (value: string) => {
114
- dispatch(setNodesUptimeFilter(value as NodesUptimeFilterValues));
113
+ const handleUptimeFilterChange = (value: NodesUptimeFilterValues) => {
114
+ dispatch(setNodesUptimeFilter(value));
115
115
  };
116
116
 
117
117
  const renderControls = () => {
@@ -0,0 +1,21 @@
1
+ import type {AdditionalNodesProps} from '../../types/additionalProps';
2
+ import {USE_BACKEND_PARAMS_FOR_TABLES_KEY} from '../../utils/constants';
3
+ import {useSetting} from '../../utils/hooks';
4
+
5
+ import {VirtualNodes} from './VirtualNodes';
6
+ import {Nodes} from './Nodes';
7
+
8
+ interface NodesWrapperProps {
9
+ parentContainer?: Element | null;
10
+ additionalNodesProps?: AdditionalNodesProps;
11
+ }
12
+
13
+ export const NodesWrapper = ({parentContainer, ...props}: NodesWrapperProps) => {
14
+ const [useVirtualTable] = useSetting<boolean>(USE_BACKEND_PARAMS_FOR_TABLES_KEY);
15
+
16
+ if (useVirtualTable) {
17
+ return <VirtualNodes parentContainer={parentContainer} {...props} />;
18
+ }
19
+
20
+ return <Nodes {...props} />;
21
+ };
@@ -72,27 +72,17 @@ export const VirtualNodes = ({parentContainer, additionalNodesProps}: NodesProps
72
72
  return b('node', {unavailable: isUnavailableNode(row)});
73
73
  };
74
74
 
75
- const handleSearchQueryChange = (value: string) => {
76
- setSearchValue(value);
77
- };
78
- const handleProblemFilterChange = (value: string) => {
79
- setProblemFilter(value as ProblemFilterValue);
80
- };
81
- const handleUptimeFilterChange = (value: string) => {
82
- setUptimeFilter(value as NodesUptimeFilterValues);
83
- };
84
-
85
75
  const renderControls: RenderControls = ({totalEntities, foundEntities, inited}) => {
86
76
  return (
87
77
  <>
88
78
  <Search
89
- onChange={handleSearchQueryChange}
79
+ onChange={setSearchValue}
90
80
  placeholder="Host name"
91
81
  className={b('search')}
92
82
  value={searchValue}
93
83
  />
94
- <ProblemFilter value={problemFilter} onChange={handleProblemFilterChange} />
95
- <UptimeFilter value={uptimeFilter} onChange={handleUptimeFilterChange} />
84
+ <ProblemFilter value={problemFilter} onChange={setProblemFilter} />
85
+ <UptimeFilter value={uptimeFilter} onChange={setUptimeFilter} />
96
86
  <EntitiesCount
97
87
  total={totalEntities}
98
88
  current={foundEntities}
@@ -116,7 +106,7 @@ export const VirtualNodes = ({parentContainer, additionalNodesProps}: NodesProps
116
106
 
117
107
  const renderErrorMessage: RenderErrorMessage = (error) => {
118
108
  if (error && error.status === 403) {
119
- return <AccessDenied />;
109
+ return <AccessDenied position="left" />;
120
110
  }
121
111
 
122
112
  return <ResponseError error={error} />;
@@ -6,6 +6,7 @@
6
6
  $inner-border-radius: $outer-border-radius - $border-width;
7
7
 
8
8
  position: relative;
9
+ z-index: 0;
9
10
 
10
11
  display: block;
11
12
 
@@ -20,6 +20,7 @@ export const EmptyFilter = ({
20
20
  }: EmptyFilterProps) => (
21
21
  <EmptyState
22
22
  image={<Illustration name="thumbsUp" />}
23
+ position="left"
23
24
  title={title}
24
25
  description={message}
25
26
  actions={
@@ -1,14 +1,13 @@
1
1
  import React, {useEffect, useState, useRef, useMemo} from 'react';
2
2
  import cn from 'bem-cn-lite';
3
3
 
4
+ import type {TVDiskStateInfo} from '../../../types/api/vdisk';
4
5
  import {InternalLink} from '../../../components/InternalLink';
5
6
  import {Stack} from '../../../components/Stack/Stack';
6
7
 
7
8
  import routes, {createHref} from '../../../routes';
8
- import {selectVDisksForPDisk} from '../../../store/reducers/storage/selectors';
9
9
  import {TPDiskStateInfo, TPDiskState} from '../../../types/api/pdisk';
10
10
  import {stringifyVdiskId} from '../../../utils/dataFormatters/dataFormatters';
11
- import {useTypedSelector} from '../../../utils/hooks';
12
11
  import {getPDiskType} from '../../../utils/pdisk';
13
12
  import {isFullVDiskData} from '../../../utils/storage';
14
13
 
@@ -44,6 +43,7 @@ const stateSeverity = {
44
43
  interface PDiskProps {
45
44
  nodeId: number;
46
45
  data?: TPDiskStateInfo;
46
+ vDisks?: TVDiskStateInfo[];
47
47
  }
48
48
 
49
49
  const isSeverityKey = (key?: TPDiskState): key is keyof typeof stateSeverity =>
@@ -53,12 +53,10 @@ const getStateSeverity = (pDiskState?: TPDiskState) => {
53
53
  return isSeverityKey(pDiskState) ? stateSeverity[pDiskState] : NOT_AVAILABLE_SEVERITY;
54
54
  };
55
55
 
56
- export const PDisk = ({nodeId, data: rawData = {}}: PDiskProps) => {
56
+ export const PDisk = ({nodeId, data: rawData = {}, vDisks}: PDiskProps) => {
57
57
  // NodeId in data is required for the popup
58
58
  const data = useMemo(() => ({...rawData, NodeId: nodeId}), [rawData, nodeId]);
59
59
 
60
- const vdisks = useTypedSelector((state) => selectVDisksForPDisk(state, nodeId, data.PDiskId));
61
-
62
60
  const [severity, setSeverity] = useState(getStateSeverity(data.State));
63
61
  const [isPopupVisible, setIsPopupVisible] = useState(false);
64
62
 
@@ -100,13 +98,13 @@ export const PDisk = ({nodeId, data: rawData = {}}: PDiskProps) => {
100
98
  };
101
99
 
102
100
  const renderVDisks = () => {
103
- if (!vdisks?.length) {
101
+ if (!vDisks?.length) {
104
102
  return null;
105
103
  }
106
104
 
107
105
  return (
108
106
  <div className={b('vdisks')}>
109
- {vdisks.map((vdisk) => {
107
+ {vDisks.map((vdisk) => {
110
108
  const donors = vdisk.Donors;
111
109
 
112
110
  return (