ydb-embedded-ui 4.28.0 → 4.30.0

Sign up to get free protection for your applications and to get access to all the features.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.30.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.29.0...v4.30.0) (2024-01-16)
4
+
5
+
6
+ ### Features
7
+
8
+ * add clipboard button to nodes tree titles ([#648](https://github.com/ydb-platform/ydb-embedded-ui/issues/648)) ([1411651](https://github.com/ydb-platform/ydb-embedded-ui/commit/141165173189be064e9e9314b42aa3eb7fce9c69))
9
+
10
+ ## [4.29.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.28.0...v4.29.0) (2024-01-12)
11
+
12
+
13
+ ### Features
14
+
15
+ * add ErrorBoundary ([#549](https://github.com/ydb-platform/ydb-embedded-ui/issues/549)) ([f5ad224](https://github.com/ydb-platform/ydb-embedded-ui/commit/f5ad224b342e0fa25b1bafa3f5e2202ce165ef80))
16
+
3
17
  ## [4.28.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.27.1...v4.28.0) (2024-01-10)
4
18
 
5
19
 
package/README.md CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  Local viewer for YDB clusters
4
4
 
5
- [Docs for users](https://ydb.tech/en/docs/maintenance/embedded_monitoring/ydb_monitoring)
5
+ * [Docs for users](https://ydb.tech/en/docs/maintenance/embedded_monitoring/ydb_monitoring)
6
+ * [Project Roadmap](ROADMAP.md)
6
7
 
7
8
  ## Preview
8
9
 
@@ -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>
@@ -0,0 +1,52 @@
1
+ import {
2
+ Button,
3
+ ButtonProps,
4
+ ClipboardIcon,
5
+ CopyToClipboard as CopyToClipboardUiKit,
6
+ CopyToClipboardStatus,
7
+ Tooltip,
8
+ } from '@gravity-ui/uikit';
9
+ import cn from 'bem-cn-lite';
10
+
11
+ const b = cn('clipboard-button');
12
+
13
+ interface ClipboardButtonProps extends Pick<ButtonProps, 'disabled' | 'size' | 'title' | 'view'> {
14
+ className?: string;
15
+ text: string;
16
+ }
17
+
18
+ /**
19
+ * An inner component required
20
+ * because `react-copy-to-clipboard` doesn't work with `Tooltip` otherwise.
21
+ */
22
+ function InnerButton({
23
+ className,
24
+ status,
25
+ title,
26
+ ...props
27
+ }: Omit<ClipboardButtonProps, 'text'> & {status: CopyToClipboardStatus}) {
28
+ return (
29
+ <Tooltip
30
+ content={status === CopyToClipboardStatus.Success ? 'Copied!' : title || 'Copy'}
31
+ /**
32
+ * Auto-placement has a bug with text changing.
33
+ * @link https://github.com/ydb-platform/ydb-embedded-ui/pull/648#discussion_r1453530092
34
+ */
35
+ placement="bottom-start"
36
+ >
37
+ <Button {...props} className={b(null, className)}>
38
+ <Button.Icon>
39
+ <ClipboardIcon status={status} size={16} />
40
+ </Button.Icon>
41
+ </Button>
42
+ </Tooltip>
43
+ );
44
+ }
45
+
46
+ export function ClipboardButton({text, ...props}: ClipboardButtonProps) {
47
+ return (
48
+ <CopyToClipboardUiKit text={text} timeout={1000}>
49
+ {(status) => <InnerButton {...props} status={status} />}
50
+ </CopyToClipboardUiKit>
51
+ );
52
+ }
@@ -0,0 +1 @@
1
+ export * from './ClipboardButton';
@@ -1,14 +1,13 @@
1
- import React from 'react';
2
- import PropTypes from 'prop-types';
1
+ import {Icon, Link as UIKitLink} from '@gravity-ui/uikit';
3
2
  import cn from 'bem-cn-lite';
3
+ import PropTypes from 'prop-types';
4
+ import React from 'react';
4
5
  import {Link} from 'react-router-dom';
5
- import {ClipboardButton, Link as UIKitLink, Button, Icon} from '@gravity-ui/uikit';
6
-
7
- import circleInfoIcon from '../../assets/icons/circle-info.svg';
8
6
  import circleExclamationIcon from '../../assets/icons/circle-exclamation.svg';
9
- import triangleExclamationIcon from '../../assets/icons/triangle-exclamation.svg';
7
+ import circleInfoIcon from '../../assets/icons/circle-info.svg';
10
8
  import circleTimesIcon from '../../assets/icons/circle-xmark.svg';
11
-
9
+ import triangleExclamationIcon from '../../assets/icons/triangle-exclamation.svg';
10
+ import {ClipboardButton} from '../ClipboardButton';
12
11
  import './EntityStatus.scss';
13
12
 
14
13
  const icons = {
@@ -121,15 +120,13 @@ class EntityStatus extends React.Component {
121
120
  {this.renderLink()}
122
121
  </span>
123
122
  {hasClipboardButton && (
124
- <Button
125
- component="span"
123
+ <ClipboardButton
124
+ text={name}
126
125
  size="s"
127
126
  className={b('clipboard-button', {
128
127
  visible: this.props.clipboardButtonAlwaysVisible,
129
128
  })}
130
- >
131
- <ClipboardButton text={name} size={16} />
132
- </Button>
129
+ />
133
130
  )}
134
131
  </div>
135
132
  );
@@ -10,25 +10,14 @@
10
10
  @include body-2-typography();
11
11
 
12
12
  &__clipboard-button {
13
- display: none;
14
- justify-content: center;
15
- align-items: center;
13
+ visibility: hidden;
16
14
 
17
15
  margin-left: 8px;
18
16
 
19
17
  color: var(--yc-color-text-secondary);
20
18
 
21
- .yc-button__text {
22
- margin: 0;
23
- }
24
-
25
- .yc-clipboard-button {
26
- width: 24px;
27
- height: 24px;
28
- }
29
-
30
19
  &_visible {
31
- display: inline-flex;
20
+ visibility: visible;
32
21
  }
33
22
  }
34
23
 
@@ -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
+ }
@@ -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
 
@@ -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
  }
@@ -107,21 +107,21 @@ body,
107
107
  border-right: unset;
108
108
  border-left: unset;
109
109
  }
110
-
111
- .yc-clipboard-button {
112
- display: inline-flex;
113
- justify-content: center;
114
- align-items: center;
115
- }
116
110
  }
117
111
 
118
112
  .error {
119
113
  color: var(--g-color-text-danger);
120
114
  }
121
115
 
122
- .data-table__row:hover .entity-status__clipboard-button,
123
- .ydb-virtual-table__row:hover .entity-status__clipboard-button {
124
- display: flex;
116
+ .data-table__row,
117
+ .ydb-virtual-table__row,
118
+ .ydb-tree-view__item {
119
+ &:hover,
120
+ &:focus-within {
121
+ & .clipboard-button {
122
+ visibility: visible;
123
+ }
124
+ }
125
125
  }
126
126
 
127
127
  .g-root .data-table_highlight-rows .data-table__row:hover {
@@ -1,60 +1,53 @@
1
+ import {HelpPopover} from '@gravity-ui/components';
2
+ import {Button, Tabs} from '@gravity-ui/uikit';
3
+ import cn from 'bem-cn-lite';
4
+ import qs from 'qs';
1
5
  import React, {ReactNode, useEffect, useReducer} from 'react';
2
6
  import {useDispatch} from 'react-redux';
3
7
  import {useLocation} from 'react-router';
4
8
  import {Link} from 'react-router-dom';
5
- import qs from 'qs';
6
- import cn from 'bem-cn-lite';
7
-
8
- import {Button, Tabs} from '@gravity-ui/uikit';
9
- import {HelpPopover} from '@gravity-ui/components';
10
-
11
- import SplitPane from '../../../components/SplitPane';
12
- import CopyToClipboard from '../../../components/CopyToClipboard/CopyToClipboard';
9
+ import {ClipboardButton} from '../../../components/ClipboardButton';
10
+ import {Icon} from '../../../components/Icon';
13
11
  import InfoViewer from '../../../components/InfoViewer/InfoViewer';
14
12
  import {
15
13
  CDCStreamOverview,
16
14
  PersQueueGroupOverview,
17
15
  } from '../../../components/InfoViewer/schemaOverview';
18
- import {Icon} from '../../../components/Icon';
19
16
  import {Loader} from '../../../components/Loader';
20
-
17
+ import SplitPane from '../../../components/SplitPane';
18
+ import routes, {createHref} from '../../../routes';
19
+ import {setShowPreview} from '../../../store/reducers/schema/schema';
20
+ import {
21
+ TENANT_PAGES_IDS,
22
+ TENANT_QUERY_TABS_ID,
23
+ TENANT_SUMMARY_TABS_IDS,
24
+ } from '../../../store/reducers/tenant/constants';
25
+ import {setQueryTab, setSummaryTab, setTenantPage} from '../../../store/reducers/tenant/tenant';
21
26
  import {
22
27
  EPathSubType,
23
28
  EPathType,
24
29
  TColumnDescription,
25
30
  TColumnTableDescription,
26
31
  } from '../../../types/api/schema';
27
- import routes, {createHref} from '../../../routes';
28
- import {formatDateTime} from '../../../utils/dataFormatters/dataFormatters';
29
- import {useTypedSelector} from '../../../utils/hooks';
30
32
  import {
31
33
  DEFAULT_IS_TENANT_COMMON_INFO_COLLAPSED,
32
34
  DEFAULT_SIZE_TENANT_SUMMARY_KEY,
33
35
  } from '../../../utils/constants';
34
- import {setShowPreview} from '../../../store/reducers/schema/schema';
35
- import {setQueryTab, setSummaryTab, setTenantPage} from '../../../store/reducers/tenant/tenant';
36
- import {
37
- TENANT_PAGES_IDS,
38
- TENANT_QUERY_TABS_ID,
39
- TENANT_SUMMARY_TABS_IDS,
40
- } from '../../../store/reducers/tenant/constants';
41
-
42
- import {SchemaTree} from '../Schema/SchemaTree/SchemaTree';
43
- import {SchemaViewer} from '../Schema/SchemaViewer/SchemaViewer';
36
+ import {formatDateTime} from '../../../utils/dataFormatters/dataFormatters';
37
+ import {useTypedSelector} from '../../../utils/hooks';
44
38
  import {Acl} from '../Acl/Acl';
45
- import {ExternalTableSummary} from '../Info/ExternalTable/ExternalTable';
39
+ import i18n from '../i18n';
46
40
  import {ExternalDataSourceSummary} from '../Info/ExternalDataSource/ExternalDataSource';
47
-
41
+ import {ExternalTableSummary} from '../Info/ExternalTable/ExternalTable';
42
+ import {SchemaTree} from '../Schema/SchemaTree/SchemaTree';
43
+ import {SchemaViewer} from '../Schema/SchemaViewer/SchemaViewer';
48
44
  import {TenantTabsGroups, TENANT_INFO_TABS, TENANT_SCHEMA_TAB} from '../TenantPages';
49
45
  import {
50
46
  PaneVisibilityActionTypes,
51
- paneVisibilityToggleReducerCreator,
52
47
  PaneVisibilityToggleButtons,
48
+ paneVisibilityToggleReducerCreator,
53
49
  } from '../utils/paneVisibilityToggleHelpers';
54
50
  import {isColumnEntityType, isExternalTable, isIndexTable, isTableType} from '../utils/schema';
55
-
56
- import i18n from '../i18n';
57
-
58
51
  import './ObjectSummary.scss';
59
52
 
60
53
  const b = cn('object-summary');
@@ -306,8 +299,9 @@ export function ObjectSummary({
306
299
  </Button>
307
300
  )}
308
301
  {currentSchemaPath && (
309
- <CopyToClipboard
302
+ <ClipboardButton
310
303
  text={currentSchemaPath}
304
+ view="flat-secondary"
311
305
  title={i18n('summary.copySchemaPath')}
312
306
  />
313
307
  )}
@@ -1,31 +1,25 @@
1
- import React, {useEffect, useState} from 'react';
2
- import {useDispatch} from 'react-redux';
1
+ import {RadioButton, Tabs} from '@gravity-ui/uikit';
3
2
  import cn from 'bem-cn-lite';
3
+ import React, {useEffect, useState} from 'react';
4
4
  import JSONTree from 'react-json-inspector';
5
-
6
- import {RadioButton, Tabs} from '@gravity-ui/uikit';
7
-
8
- import CopyToClipboard from '../../../../components/CopyToClipboard/CopyToClipboard';
5
+ import {useDispatch} from 'react-redux';
6
+ import {ClipboardButton} from '../../../../components/ClipboardButton';
9
7
  import Divider from '../../../../components/Divider/Divider';
10
8
  import EnableFullscreenButton from '../../../../components/EnableFullscreenButton/EnableFullscreenButton';
11
9
  import Fullscreen from '../../../../components/Fullscreen/Fullscreen';
12
10
  import {QueryExecutionStatus} from '../../../../components/QueryExecutionStatus';
13
11
  import {QueryResultTable} from '../../../../components/QueryResultTable/QueryResultTable';
14
-
12
+ import {disableFullscreen} from '../../../../store/reducers/fullscreen';
13
+ import type {ColumnType, KeyValueRow} from '../../../../types/api/query';
15
14
  import type {ValueOf} from '../../../../types/common';
16
15
  import type {IQueryResult, QueryErrorResponse} from '../../../../types/store/query';
17
- import type {ColumnType, KeyValueRow} from '../../../../types/api/query';
18
- import {disableFullscreen} from '../../../../store/reducers/fullscreen';
19
- import {prepareQueryError} from '../../../../utils/query';
20
- import {useTypedSelector} from '../../../../utils/hooks';
21
16
  import {getArray} from '../../../../utils';
22
-
17
+ import {useTypedSelector} from '../../../../utils/hooks';
18
+ import {prepareQueryError} from '../../../../utils/query';
23
19
  import {PaneVisibilityToggleButtons} from '../../utils/paneVisibilityToggleHelpers';
24
-
25
20
  import {ResultIssues} from '../Issues/Issues';
26
21
  import {QueryDuration} from '../QueryDuration/QueryDuration';
27
22
  import {getPreparedResult} from '../utils/getPreparedResult';
28
-
29
23
  import './ExecuteResult.scss';
30
24
 
31
25
  const b = cn('ydb-query-execute-result');
@@ -115,10 +109,10 @@ export function ExecuteResult({
115
109
 
116
110
  const renderClipboardButton = () => {
117
111
  return (
118
- <CopyToClipboard
112
+ <ClipboardButton
119
113
  text={textResults}
114
+ view="flat-secondary"
120
115
  title="Copy results"
121
- toastText="Results were copied to clipboard successfully"
122
116
  disabled={copyDisabled}
123
117
  />
124
118
  );
@@ -17,10 +17,12 @@
17
17
  margin-left: 30px;
18
18
  }
19
19
  }
20
+
20
21
  &__overview-container {
21
22
  display: flex;
22
23
  align-items: center;
23
24
  }
25
+
24
26
  &__info-label {
25
27
  font-weight: 200;
26
28
 
@@ -52,4 +54,17 @@
52
54
  width: 200px;
53
55
  }
54
56
  }
57
+
58
+ &__overview-title {
59
+ display: flex;
60
+ align-items: center;
61
+ }
62
+
63
+ &__clipboard-button {
64
+ visibility: hidden;
65
+
66
+ margin-left: 8px;
67
+
68
+ color: var(--yc-color-text-secondary);
69
+ }
55
70
  }
@@ -1,11 +1,9 @@
1
- import block from 'bem-cn-lite';
2
-
3
1
  import {Progress} from '@gravity-ui/uikit';
4
-
5
- import type {VersionValue} from '../../../types/versions';
2
+ import block from 'bem-cn-lite';
3
+ import {ClipboardButton} from '../../../components/ClipboardButton';
6
4
  import type {PreparedClusterNode} from '../../../store/reducers/clusterNodes/types';
5
+ import type {VersionValue} from '../../../types/versions';
7
6
  import type {GroupedNodesItem} from '../types';
8
-
9
7
  import './NodesTreeTitle.scss';
10
8
 
11
9
  const b = block('ydb-versions-nodes-tree-title');
@@ -43,7 +41,12 @@ export const NodesTreeTitle = ({
43
41
  {versionColor ? (
44
42
  <div className={b('version-color')} style={{background: versionColor}} />
45
43
  ) : null}
46
- <span className={b('overview-title')}>{title}</span>
44
+ {title ? (
45
+ <span className={b('overview-title')}>
46
+ {title}
47
+ <ClipboardButton text={title} size="s" className={b('clipboard-button')} />
48
+ </span>
49
+ ) : null}
47
50
  </div>
48
51
  <div className={b('overview-info')}>
49
52
  <div>
package/dist/index.tsx CHANGED
@@ -8,6 +8,7 @@ import App from './containers/App/App';
8
8
  import configureStore from './store';
9
9
  import reportWebVitals from './reportWebVitals';
10
10
  import HistoryContext from './contexts/HistoryContext';
11
+ import {ErrorBoundary} from './components/ErrorBoundary/ErrorBoundary';
11
12
 
12
13
  import './styles/themes.scss';
13
14
  import './styles/constants.scss';
@@ -18,11 +19,13 @@ window.store = store;
18
19
 
19
20
  ReactDOM.render(
20
21
  <React.StrictMode>
21
- <Provider store={store}>
22
- <HistoryContext.Provider value={history}>
23
- <App />
24
- </HistoryContext.Provider>
25
- </Provider>
22
+ <ErrorBoundary>
23
+ <Provider store={store}>
24
+ <HistoryContext.Provider value={history}>
25
+ <App />
26
+ </HistoryContext.Provider>
27
+ </Provider>
28
+ </ErrorBoundary>
26
29
  </React.StrictMode>,
27
30
  document.getElementById('root'),
28
31
  );
@@ -0,0 +1,18 @@
1
+ export function registerError(error: Error, message?: string, type = 'error') {
2
+ if (typeof window !== 'undefined' && window.Ya?.Rum) {
3
+ window.Ya.Rum.logError(
4
+ {
5
+ additional: {
6
+ url: window.location.href,
7
+ },
8
+ type,
9
+ message,
10
+ level: window.Ya.Rum.ERROR_LEVEL.ERROR,
11
+ },
12
+ error,
13
+ );
14
+ } else {
15
+ // eslint-disable-next-line no-console
16
+ console.error(error);
17
+ }
18
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ydb-embedded-ui",
3
- "version": "4.28.0",
3
+ "version": "4.30.0",
4
4
  "files": [
5
5
  "dist"
6
6
  ],
@@ -30,6 +30,7 @@
30
30
  "numeral": "2.0.6",
31
31
  "path-to-regexp": "3.0.0",
32
32
  "qs": "^6.11.0",
33
+ "react-error-boundary": "^4.0.12",
33
34
  "react-json-inspector": "7.1.1",
34
35
  "react-list": "0.8.11",
35
36
  "react-monaco-editor": "0.30.1",
@@ -45,7 +46,7 @@
45
46
  "sass": "1.32.8",
46
47
  "url": "^0.11.0",
47
48
  "web-vitals": "1.1.2",
48
- "ydb-ui-components": "^3.5.0"
49
+ "ydb-ui-components": "^3.6.0"
49
50
  },
50
51
  "scripts": {
51
52
  "start": "react-app-rewired start",
@@ -1,38 +0,0 @@
1
- import {Button, CopyToClipboard as CopyToClipboardUiKit} from '@gravity-ui/uikit';
2
- import createToast from '../../utils/createToast';
3
- import {Icon} from '../Icon';
4
-
5
- interface CopyToClipboardProps {
6
- text: string;
7
- title?: string;
8
- disabled?: boolean;
9
- toastText?: string;
10
- }
11
-
12
- function CopyToClipboard(props: CopyToClipboardProps) {
13
- return (
14
- <CopyToClipboardUiKit text={props.text} timeout={1000}>
15
- {(state) => {
16
- if (state === 'success') {
17
- createToast({
18
- name: 'Copied',
19
- title: props.toastText ?? 'Data was copied to clipboard successfully',
20
- type: state,
21
- });
22
- }
23
-
24
- return (
25
- <Button
26
- disabled={props.disabled}
27
- title={props.title ?? 'Copy'}
28
- view="flat-secondary"
29
- >
30
- <Icon name="copy" viewBox={'0 0 16 16'} width={16} height={16} />
31
- </Button>
32
- );
33
- }}
34
- </CopyToClipboardUiKit>
35
- );
36
- }
37
-
38
- export default CopyToClipboard;