ydb-embedded-ui 4.1.0 → 4.2.1
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 +23 -0
- package/dist/components/LabelWithPopover/LabelWithPopover.tsx +10 -4
- package/dist/components/TabletsStatistic/TabletsStatistic.scss +1 -1
- package/dist/containers/App/App.scss +7 -4
- package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +3 -12
- package/dist/containers/Storage/VDisk/VDisk.tsx +4 -11
- package/dist/containers/Storage/VDiskPopup/VDiskPopup.tsx +13 -16
- package/dist/containers/Storage/utils/types.ts +2 -1
- package/dist/containers/Tablet/Tablet.scss +4 -0
- package/dist/containers/Tablet/TabletTable/TabletTable.tsx +28 -6
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.scss +1 -1
- package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo.ts +4 -3
- package/dist/containers/Tenant/QueryEditor/Issues/Issues.scss +1 -1
- package/dist/containers/Tenant/QueryEditor/QueryEditorControls/OldQueryEditorControls.tsx +1 -1
- package/dist/containers/Tenant/QueryEditor/QueryEditorControls/QueryEditorControls.tsx +1 -1
- package/dist/containers/Tenant/Schema/SchemaViewer/SchemaViewer.js +12 -0
- package/dist/containers/UserSettings/UserSettings.tsx +19 -17
- package/dist/services/api.ts +9 -9
- package/dist/store/reducers/node.js +5 -1
- package/dist/store/reducers/nodesList.ts +2 -7
- package/dist/store/reducers/storage.js +12 -0
- package/dist/store/reducers/tablet.ts +16 -2
- package/dist/types/store/tablet.ts +1 -0
- package/dist/utils/createToast.tsx +3 -3
- package/dist/utils/nodes.ts +11 -0
- package/package.json +4 -4
    
        package/CHANGELOG.md
    CHANGED
    
    | @@ -1,5 +1,28 @@ | |
| 1 1 | 
             
            # Changelog
         | 
| 2 2 |  | 
| 3 | 
            +
            ## [4.2.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.2.0...v4.2.1) (2023-05-18)
         | 
| 4 | 
            +
             | 
| 5 | 
            +
             | 
| 6 | 
            +
            ### Bug Fixes
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            * export toaster ([b5d12c0](https://github.com/ydb-platform/ydb-embedded-ui/commit/b5d12c0aa39ea3877a9b74071e3124f89a309ca3))
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            ## [4.2.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.1.0...v4.2.0) (2023-05-16)
         | 
| 11 | 
            +
             | 
| 12 | 
            +
             | 
| 13 | 
            +
            ### Features
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            * **Tablet:** display node fqdn in table ([4d8099a](https://github.com/ydb-platform/ydb-embedded-ui/commit/4d8099a454f34fc76886b26ca948895171c57ab8))
         | 
| 16 | 
            +
             | 
| 17 | 
            +
             | 
| 18 | 
            +
            ### Bug Fixes
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            * **api:** change nulls to empty objects ([0ab14e8](https://github.com/ydb-platform/ydb-embedded-ui/commit/0ab14e883a47aeac2f2bab437f2214a32ccb1c9b))
         | 
| 21 | 
            +
            * display storage pool in VDisks popups ([5b5dd8a](https://github.com/ydb-platform/ydb-embedded-ui/commit/5b5dd8a4e6cb4bcc1ead78a7c06d2e80a81424cc))
         | 
| 22 | 
            +
            * fix Select label and values align ([f796730](https://github.com/ydb-platform/ydb-embedded-ui/commit/f7967309fe4a042e7637de212f33b1ebfc6877fc))
         | 
| 23 | 
            +
            * **Overview:** partitioning by size disabled for 0 SizeToSpit ([1028e7d](https://github.com/ydb-platform/ydb-embedded-ui/commit/1028e7d8d3566f5f5e6b2ebe04112ef135d7b55e))
         | 
| 24 | 
            +
            * **Schema:** display NotNull columns ([d61eaa4](https://github.com/ydb-platform/ydb-embedded-ui/commit/d61eaa4ccff357c1e9ca6efde855ec46be24a314))
         | 
| 25 | 
            +
             | 
| 3 26 | 
             
            ## [4.1.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.0.0...v4.1.0) (2023-05-10)
         | 
| 4 27 |  | 
| 5 28 |  | 
| @@ -3,14 +3,20 @@ import type {ReactNode} from 'react'; | |
| 3 3 | 
             
            import {HelpPopover} from '@gravity-ui/uikit';
         | 
| 4 4 |  | 
| 5 5 | 
             
            interface LabelWithPopoverProps {
         | 
| 6 | 
            -
                text:  | 
| 6 | 
            +
                text: ReactNode;
         | 
| 7 7 | 
             
                popoverContent: ReactNode;
         | 
| 8 8 | 
             
                className?: string;
         | 
| 9 | 
            +
                contentClassName?: string;
         | 
| 9 10 | 
             
            }
         | 
| 10 11 |  | 
| 11 | 
            -
            export const LabelWithPopover = ({ | 
| 12 | 
            +
            export const LabelWithPopover = ({
         | 
| 13 | 
            +
                text,
         | 
| 14 | 
            +
                popoverContent,
         | 
| 15 | 
            +
                className,
         | 
| 16 | 
            +
                contentClassName,
         | 
| 17 | 
            +
            }: LabelWithPopoverProps) => (
         | 
| 12 18 | 
             
                <div className={className}>
         | 
| 13 | 
            -
                    {text}
         | 
| 14 | 
            -
                    <HelpPopover content={popoverContent} />
         | 
| 19 | 
            +
                    {text + '\u00a0'}
         | 
| 20 | 
            +
                    <HelpPopover content={popoverContent} contentClassName={contentClassName} />
         | 
| 15 21 | 
             
                </div>
         | 
| 16 22 | 
             
            );
         | 
| @@ -34,10 +34,6 @@ body, | |
| 34 34 | 
             
                --ydb-data-table-color-hover: var(--yc-color-base-float-hover);
         | 
| 35 35 | 
             
            }
         | 
| 36 36 |  | 
| 37 | 
            -
            .yc-select__label {
         | 
| 38 | 
            -
                font-weight: 600;
         | 
| 39 | 
            -
            }
         | 
| 40 | 
            -
             | 
| 41 37 | 
             
            :is(#tab, .yc-tabs-item_active .yc-tabs-item__title) {
         | 
| 42 38 | 
             
                color: var(--yc-color-text-primary) !important;
         | 
| 43 39 | 
             
            }
         | 
| @@ -110,10 +106,17 @@ body, | |
| 110 106 | 
             
                    align-items: center;
         | 
| 111 107 | 
             
                }
         | 
| 112 108 |  | 
| 109 | 
            +
                // Should be removed after https://github.com/ydb-platform/ydb-embedded-ui/issues/344
         | 
| 113 110 | 
             
                .yc-button__text {
         | 
| 114 111 | 
             
                    display: flex;
         | 
| 115 112 | 
             
                    align-items: center;
         | 
| 116 113 | 
             
                }
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                .g-select {
         | 
| 116 | 
            +
                    .yc-button__text {
         | 
| 117 | 
            +
                        align-items: baseline;
         | 
| 118 | 
            +
                    }
         | 
| 119 | 
            +
                }
         | 
| 117 120 | 
             
            }
         | 
| 118 121 |  | 
| 119 122 | 
             
            .error {
         | 
| @@ -256,18 +256,14 @@ function StorageGroups({ | |
| 256 256 | 
             
                        name: TableColumnsIds.VDisks,
         | 
| 257 257 | 
             
                        className: b('vdisks-column'),
         | 
| 258 258 | 
             
                        header: tableColumnsNames[TableColumnsIds.VDisks],
         | 
| 259 | 
            -
                        render: ({value | 
| 259 | 
            +
                        render: ({value}) => (
         | 
| 260 260 | 
             
                            <div className={b('vdisks-wrapper')}>
         | 
| 261 261 | 
             
                                {_.map(value as TVDiskStateInfo[], (el) => {
         | 
| 262 262 | 
             
                                    const donors = el.Donors;
         | 
| 263 263 |  | 
| 264 264 | 
             
                                    return donors && donors.length > 0 ? (
         | 
| 265 265 | 
             
                                        <Stack className={b('vdisks-item')} key={stringifyVdiskId(el.VDiskId)}>
         | 
| 266 | 
            -
                                            <VDisk
         | 
| 267 | 
            -
                                                data={el}
         | 
| 268 | 
            -
                                                poolName={row[TableColumnsIds.PoolName]}
         | 
| 269 | 
            -
                                                nodes={nodes}
         | 
| 270 | 
            -
                                            />
         | 
| 266 | 
            +
                                            <VDisk data={el} nodes={nodes} />
         | 
| 271 267 | 
             
                                            {donors.map((donor) => {
         | 
| 272 268 | 
             
                                                const isFullData = isFullVDiskData(donor);
         | 
| 273 269 |  | 
| @@ -275,7 +271,6 @@ function StorageGroups({ | |
| 275 271 | 
             
                                                    <VDisk
         | 
| 276 272 | 
             
                                                        data={isFullData ? donor : {...donor, DonorMode: true}}
         | 
| 277 273 | 
             
                                                        // donor and acceptor are always in the same group
         | 
| 278 | 
            -
                                                        poolName={row[TableColumnsIds.PoolName]}
         | 
| 279 274 | 
             
                                                        nodes={nodes}
         | 
| 280 275 | 
             
                                                        key={stringifyVdiskId(
         | 
| 281 276 | 
             
                                                            isFullData ? donor.VDiskId : donor,
         | 
| @@ -286,11 +281,7 @@ function StorageGroups({ | |
| 286 281 | 
             
                                        </Stack>
         | 
| 287 282 | 
             
                                    ) : (
         | 
| 288 283 | 
             
                                        <div className={b('vdisks-item')} key={stringifyVdiskId(el.VDiskId)}>
         | 
| 289 | 
            -
                                            <VDisk
         | 
| 290 | 
            -
                                                data={el}
         | 
| 291 | 
            -
                                                poolName={row[TableColumnsIds.PoolName]}
         | 
| 292 | 
            -
                                                nodes={nodes}
         | 
| 293 | 
            -
                                            />
         | 
| 284 | 
            +
                                            <VDisk data={el} nodes={nodes} />
         | 
| 294 285 | 
             
                                        </div>
         | 
| 295 286 | 
             
                                    );
         | 
| 296 287 | 
             
                                })}
         | 
| @@ -16,7 +16,7 @@ import {STRUCTURE} from '../../Node/NodePages'; | |
| 16 16 | 
             
            import {DiskStateProgressBar, EDiskStateSeverity} from '../DiskStateProgressBar';
         | 
| 17 17 | 
             
            import {VDiskPopup} from '../VDiskPopup';
         | 
| 18 18 |  | 
| 19 | 
            -
            import type { | 
| 19 | 
            +
            import type {UnavailableDonor} from '../utils/types';
         | 
| 20 20 | 
             
            import {NOT_AVAILABLE_SEVERITY} from '../utils';
         | 
| 21 21 |  | 
| 22 22 | 
             
            import './VDisk.scss';
         | 
| @@ -49,13 +49,12 @@ const getColorSeverity = (color?: EFlag) => { | |
| 49 49 | 
             
            };
         | 
| 50 50 |  | 
| 51 51 | 
             
            interface VDiskProps {
         | 
| 52 | 
            -
                data?: TVDiskStateInfo |  | 
| 53 | 
            -
                poolName?: string;
         | 
| 52 | 
            +
                data?: TVDiskStateInfo | UnavailableDonor;
         | 
| 54 53 | 
             
                nodes?: NodesMap;
         | 
| 55 54 | 
             
                compact?: boolean;
         | 
| 56 55 | 
             
            }
         | 
| 57 56 |  | 
| 58 | 
            -
            export const VDisk = ({data = {},  | 
| 57 | 
            +
            export const VDisk = ({data = {}, nodes, compact}: VDiskProps) => {
         | 
| 59 58 | 
             
                const isFullData = isFullVDiskData(data);
         | 
| 60 59 |  | 
| 61 60 | 
             
                const [severity, setSeverity] = useState(
         | 
| @@ -125,13 +124,7 @@ export const VDisk = ({data = {}, poolName, nodes, compact}: VDiskProps) => { | |
| 125 124 |  | 
| 126 125 | 
             
                return (
         | 
| 127 126 | 
             
                    <React.Fragment>
         | 
| 128 | 
            -
                        <VDiskPopup
         | 
| 129 | 
            -
                            data={data}
         | 
| 130 | 
            -
                            poolName={poolName}
         | 
| 131 | 
            -
                            nodes={nodes}
         | 
| 132 | 
            -
                            anchorRef={anchor}
         | 
| 133 | 
            -
                            open={isPopupVisible}
         | 
| 134 | 
            -
                        />
         | 
| 127 | 
            +
                        <VDiskPopup data={data} nodes={nodes} anchorRef={anchor} open={isPopupVisible} />
         | 
| 135 128 | 
             
                        <div className={b()} ref={anchor} onMouseEnter={showPopup} onMouseLeave={hidePopup}>
         | 
| 136 129 | 
             
                            {data.NodeId && isFullData ? (
         | 
| 137 130 | 
             
                                <InternalLink
         | 
| @@ -13,7 +13,7 @@ import {stringifyVdiskId} from '../../../utils'; | |
| 13 13 | 
             
            import {bytesToGB, bytesToSpeed} from '../../../utils/utils';
         | 
| 14 14 | 
             
            import {isFullVDiskData} from '../../../utils/storage';
         | 
| 15 15 |  | 
| 16 | 
            -
            import type { | 
| 16 | 
            +
            import type {UnavailableDonor} from '../utils/types';
         | 
| 17 17 |  | 
| 18 18 | 
             
            import {preparePDiskData} from '../PDiskPopup';
         | 
| 19 19 |  | 
| @@ -21,13 +21,13 @@ import './VDiskPopup.scss'; | |
| 21 21 |  | 
| 22 22 | 
             
            const b = cn('vdisk-storage-popup');
         | 
| 23 23 |  | 
| 24 | 
            -
            const prepareUnavailableVDiskData = (data:  | 
| 25 | 
            -
                const {NodeId, PDiskId, VSlotId} = data;
         | 
| 24 | 
            +
            const prepareUnavailableVDiskData = (data: UnavailableDonor) => {
         | 
| 25 | 
            +
                const {NodeId, PDiskId, VSlotId, StoragePoolName} = data;
         | 
| 26 26 |  | 
| 27 27 | 
             
                const vdiskData: InfoViewerItem[] = [{label: 'State', value: 'not available'}];
         | 
| 28 28 |  | 
| 29 | 
            -
                if ( | 
| 30 | 
            -
                    vdiskData.push({label: 'StoragePool', value:  | 
| 29 | 
            +
                if (StoragePoolName) {
         | 
| 30 | 
            +
                    vdiskData.push({label: 'StoragePool', value: StoragePoolName});
         | 
| 31 31 | 
             
                }
         | 
| 32 32 |  | 
| 33 33 | 
             
                vdiskData.push(
         | 
| @@ -39,7 +39,7 @@ const prepareUnavailableVDiskData = (data: IUnavailableDonor, poolName?: string) | |
| 39 39 | 
             
                return vdiskData;
         | 
| 40 40 | 
             
            };
         | 
| 41 41 |  | 
| 42 | 
            -
            const prepareVDiskData = (data: TVDiskStateInfo | 
| 42 | 
            +
            const prepareVDiskData = (data: TVDiskStateInfo) => {
         | 
| 43 43 | 
             
                const {
         | 
| 44 44 | 
             
                    VDiskId,
         | 
| 45 45 | 
             
                    VDiskState,
         | 
| @@ -51,6 +51,7 @@ const prepareVDiskData = (data: TVDiskStateInfo, poolName?: string) => { | |
| 51 51 | 
             
                    AllocatedSize,
         | 
| 52 52 | 
             
                    ReadThroughput,
         | 
| 53 53 | 
             
                    WriteThroughput,
         | 
| 54 | 
            +
                    StoragePoolName,
         | 
| 54 55 | 
             
                } = data;
         | 
| 55 56 |  | 
| 56 57 | 
             
                const vdiskData: InfoViewerItem[] = [
         | 
| @@ -58,8 +59,8 @@ const prepareVDiskData = (data: TVDiskStateInfo, poolName?: string) => { | |
| 58 59 | 
             
                    {label: 'State', value: VDiskState ?? 'not available'},
         | 
| 59 60 | 
             
                ];
         | 
| 60 61 |  | 
| 61 | 
            -
                if ( | 
| 62 | 
            -
                    vdiskData.push({label: 'StoragePool', value:  | 
| 62 | 
            +
                if (StoragePoolName) {
         | 
| 63 | 
            +
                    vdiskData.push({label: 'StoragePool', value: StoragePoolName});
         | 
| 63 64 | 
             
                }
         | 
| 64 65 |  | 
| 65 66 | 
             
                if (SatisfactionRank && SatisfactionRank.FreshRank?.Flag !== EFlag.Green) {
         | 
| @@ -128,20 +129,16 @@ const prepareVDiskData = (data: TVDiskStateInfo, poolName?: string) => { | |
| 128 129 | 
             
            };
         | 
| 129 130 |  | 
| 130 131 | 
             
            interface VDiskPopupProps extends PopupProps {
         | 
| 131 | 
            -
                data: TVDiskStateInfo |  | 
| 132 | 
            -
                poolName?: string;
         | 
| 132 | 
            +
                data: TVDiskStateInfo | UnavailableDonor;
         | 
| 133 133 | 
             
                nodes?: NodesMap;
         | 
| 134 134 | 
             
            }
         | 
| 135 135 |  | 
| 136 | 
            -
            export const VDiskPopup = ({data,  | 
| 136 | 
            +
            export const VDiskPopup = ({data, nodes, ...props}: VDiskPopupProps) => {
         | 
| 137 137 | 
             
                const isFullData = isFullVDiskData(data);
         | 
| 138 138 |  | 
| 139 139 | 
             
                const vdiskInfo = useMemo(
         | 
| 140 | 
            -
                    () =>
         | 
| 141 | 
            -
             | 
| 142 | 
            -
                            ? prepareVDiskData(data, poolName)
         | 
| 143 | 
            -
                            : prepareUnavailableVDiskData(data, poolName),
         | 
| 144 | 
            -
                    [data, poolName, isFullData],
         | 
| 140 | 
            +
                    () => (isFullData ? prepareVDiskData(data) : prepareUnavailableVDiskData(data)),
         | 
| 141 | 
            +
                    [data, isFullData],
         | 
| 145 142 | 
             
                );
         | 
| 146 143 | 
             
                const pdiskInfo = useMemo(
         | 
| 147 144 | 
             
                    () => isFullData && data.PDisk && preparePDiskData(data.PDisk, nodes),
         | 
| @@ -1,7 +1,13 @@ | |
| 1 1 | 
             
            import DataTable, {Column} from '@gravity-ui/react-data-table';
         | 
| 2 2 |  | 
| 3 | 
            +
            import EntityStatus from '../../../components/EntityStatus/EntityStatus';
         | 
| 4 | 
            +
            import {InternalLink} from '../../../components/InternalLink/InternalLink';
         | 
| 5 | 
            +
             | 
| 3 6 | 
             
            import type {ITabletPreparedHistoryItem} from '../../../types/store/tablet';
         | 
| 4 7 | 
             
            import {calcUptime} from '../../../utils';
         | 
| 8 | 
            +
            import {getDefaultNodePath} from '../../Node/NodePages';
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            import {b} from '../Tablet';
         | 
| 5 11 |  | 
| 6 12 | 
             
            const columns: Column<ITabletPreparedHistoryItem>[] = [
         | 
| 7 13 | 
             
                {
         | 
| @@ -9,12 +15,6 @@ const columns: Column<ITabletPreparedHistoryItem>[] = [ | |
| 9 15 | 
             
                    align: DataTable.RIGHT,
         | 
| 10 16 | 
             
                    render: ({row}) => row.generation,
         | 
| 11 17 | 
             
                },
         | 
| 12 | 
            -
                {
         | 
| 13 | 
            -
                    name: 'Node ID',
         | 
| 14 | 
            -
                    align: DataTable.RIGHT,
         | 
| 15 | 
            -
                    sortable: false,
         | 
| 16 | 
            -
                    render: ({row}) => row.nodeId,
         | 
| 17 | 
            -
                },
         | 
| 18 18 | 
             
                {
         | 
| 19 19 | 
             
                    name: 'Change time',
         | 
| 20 20 | 
             
                    align: DataTable.RIGHT,
         | 
| @@ -33,6 +33,28 @@ const columns: Column<ITabletPreparedHistoryItem>[] = [ | |
| 33 33 | 
             
                        return row.leader ? 'leader' : row.followerId;
         | 
| 34 34 | 
             
                    },
         | 
| 35 35 | 
             
                },
         | 
| 36 | 
            +
                {
         | 
| 37 | 
            +
                    name: 'Node ID',
         | 
| 38 | 
            +
                    align: DataTable.RIGHT,
         | 
| 39 | 
            +
                    sortable: false,
         | 
| 40 | 
            +
                    render: ({row}) => {
         | 
| 41 | 
            +
                        return <InternalLink to={getDefaultNodePath(row.nodeId)}>{row.nodeId}</InternalLink>;
         | 
| 42 | 
            +
                    },
         | 
| 43 | 
            +
                },
         | 
| 44 | 
            +
                {
         | 
| 45 | 
            +
                    name: 'Node FQDN',
         | 
| 46 | 
            +
                    sortable: false,
         | 
| 47 | 
            +
                    render: ({row}) => {
         | 
| 48 | 
            +
                        if (!row.fqdn) {
         | 
| 49 | 
            +
                            return <span>—</span>;
         | 
| 50 | 
            +
                        }
         | 
| 51 | 
            +
                        return (
         | 
| 52 | 
            +
                            <div className={b('host')}>
         | 
| 53 | 
            +
                                <EntityStatus name={row.fqdn} showStatus={false} hasClipboardButton />
         | 
| 54 | 
            +
                            </div>
         | 
| 55 | 
            +
                        );
         | 
| 56 | 
            +
                    },
         | 
| 57 | 
            +
                },
         | 
| 36 58 | 
             
            ];
         | 
| 37 59 |  | 
| 38 60 | 
             
            const TABLE_SETTINGS = {
         | 
| @@ -86,9 +86,10 @@ const prepareTableGeneralInfo = (PartitionConfig: TPartitionConfig, TTLSettings? | |
| 86 86 |  | 
| 87 87 | 
             
                const generalTableInfo: InfoViewerItem[] = [];
         | 
| 88 88 |  | 
| 89 | 
            -
                const partitioningBySize = | 
| 90 | 
            -
                     | 
| 91 | 
            -
             | 
| 89 | 
            +
                const partitioningBySize =
         | 
| 90 | 
            +
                    PartitioningPolicy.SizeToSplit && Number(PartitioningPolicy.SizeToSplit) > 0
         | 
| 91 | 
            +
                        ? `Enabled, split size: ${formatBytes(PartitioningPolicy.SizeToSplit)}`
         | 
| 92 | 
            +
                        : 'Disabled';
         | 
| 92 93 |  | 
| 93 94 | 
             
                const partitioningByLoad = PartitioningPolicy.SplitByLoadSettings?.Enabled
         | 
| 94 95 | 
             
                    ? 'Enabled'
         | 
| @@ -61,7 +61,7 @@ export const QueryEditorControls = ({ | |
| 61 61 | 
             
                            </Button>
         | 
| 62 62 | 
             
                            <DropdownMenu
         | 
| 63 63 | 
             
                                items={querySelectorMenuItems}
         | 
| 64 | 
            -
                                 | 
| 64 | 
            +
                                popupProps={{className: b('mode-selector__popup')}}
         | 
| 65 65 | 
             
                                switcher={
         | 
| 66 66 | 
             
                                    <Button className={b('mode-selector__button')}>
         | 
| 67 67 | 
             
                                        <span className={b('mode-selector__button-content')}>
         | 
| @@ -16,6 +16,7 @@ const SchemaViewerColumns = { | |
| 16 16 | 
             
                name: 'Name',
         | 
| 17 17 | 
             
                key: 'Key',
         | 
| 18 18 | 
             
                type: 'Type',
         | 
| 19 | 
            +
                notNull: 'NotNull',
         | 
| 19 20 | 
             
            };
         | 
| 20 21 |  | 
| 21 22 | 
             
            class SchemaViewer extends React.Component {
         | 
| @@ -59,6 +60,17 @@ class SchemaViewer extends React.Component { | |
| 59 60 | 
             
                            name: SchemaViewerColumns.type,
         | 
| 60 61 | 
             
                            width: 100,
         | 
| 61 62 | 
             
                        },
         | 
| 63 | 
            +
                        {
         | 
| 64 | 
            +
                            name: SchemaViewerColumns.notNull,
         | 
| 65 | 
            +
                            width: 100,
         | 
| 66 | 
            +
                            render: ({row}) => {
         | 
| 67 | 
            +
                                if (row.NotNull) {
         | 
| 68 | 
            +
                                    return '\u2713';
         | 
| 69 | 
            +
                                }
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                                return undefined;
         | 
| 72 | 
            +
                            },
         | 
| 73 | 
            +
                        },
         | 
| 62 74 | 
             
                    ];
         | 
| 63 75 |  | 
| 64 76 | 
             
                    const tableData = [...keyColumns, ...restColumns];
         | 
| @@ -2,12 +2,14 @@ import {ReactNode} from 'react'; | |
| 2 2 | 
             
            import {connect} from 'react-redux';
         | 
| 3 3 | 
             
            import cn from 'bem-cn-lite';
         | 
| 4 4 |  | 
| 5 | 
            -
            import {RadioButton, Switch | 
| 5 | 
            +
            import {RadioButton, Switch} from '@gravity-ui/uikit';
         | 
| 6 6 | 
             
            import {Settings} from '@gravity-ui/navigation';
         | 
| 7 7 |  | 
| 8 8 | 
             
            import favoriteFilledIcon from '../../assets/icons/star.svg';
         | 
| 9 9 | 
             
            import flaskIcon from '../../assets/icons/flask.svg';
         | 
| 10 10 |  | 
| 11 | 
            +
            import {LabelWithPopover} from '../../components/LabelWithPopover/LabelWithPopover';
         | 
| 12 | 
            +
             | 
| 11 13 | 
             
            import {
         | 
| 12 14 | 
             
                ENABLE_QUERY_MODES_FOR_EXPLAIN,
         | 
| 13 15 | 
             
                INVERTED_DISKS_KEY,
         | 
| @@ -46,27 +48,27 @@ function UserSettings(props: any) { | |
| 46 48 |  | 
| 47 49 | 
             
                const renderBreakNodesSettingsItem = (title: ReactNode) => {
         | 
| 48 50 | 
             
                    return (
         | 
| 49 | 
            -
                        < | 
| 50 | 
            -
                            { | 
| 51 | 
            -
                             | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
                                 | 
| 55 | 
            -
                             | 
| 56 | 
            -
                         | 
| 51 | 
            +
                        <LabelWithPopover
         | 
| 52 | 
            +
                            className={b('item-with-popup')}
         | 
| 53 | 
            +
                            contentClassName={b('popup')}
         | 
| 54 | 
            +
                            text={title}
         | 
| 55 | 
            +
                            popoverContent={
         | 
| 56 | 
            +
                                'Use /viewer/json/nodes endpoint for Nodes Tab in diagnostics. It returns incorrect data on older versions'
         | 
| 57 | 
            +
                            }
         | 
| 58 | 
            +
                        />
         | 
| 57 59 | 
             
                    );
         | 
| 58 60 | 
             
                };
         | 
| 59 61 |  | 
| 60 62 | 
             
                const renderEnableExplainQueryModesItem = (title: ReactNode) => {
         | 
| 61 63 | 
             
                    return (
         | 
| 62 | 
            -
                        < | 
| 63 | 
            -
                            { | 
| 64 | 
            -
                             | 
| 65 | 
            -
             | 
| 66 | 
            -
             | 
| 67 | 
            -
                                 | 
| 68 | 
            -
                             | 
| 69 | 
            -
                         | 
| 64 | 
            +
                        <LabelWithPopover
         | 
| 65 | 
            +
                            className={b('item-with-popup')}
         | 
| 66 | 
            +
                            contentClassName={b('popup')}
         | 
| 67 | 
            +
                            text={title}
         | 
| 68 | 
            +
                            popoverContent={
         | 
| 69 | 
            +
                                'Enable script | scan query mode selector for both run and explain. May not work on some versions'
         | 
| 70 | 
            +
                            }
         | 
| 71 | 
            +
                        />
         | 
| 70 72 | 
             
                    );
         | 
| 71 73 | 
             
                };
         | 
| 72 74 |  | 
    
        package/dist/services/api.ts
    CHANGED
    
    | @@ -287,7 +287,7 @@ export class YdbEmbeddedAPI extends AxiosWrapper { | |
| 287 287 | 
             
                            stats,
         | 
| 288 288 | 
             
                            timeout: 600000,
         | 
| 289 289 | 
             
                        },
         | 
| 290 | 
            -
                         | 
| 290 | 
            +
                        {},
         | 
| 291 291 | 
             
                        {
         | 
| 292 292 | 
             
                            concurrentId,
         | 
| 293 293 | 
             
                            timeout: 9 * 60 * 1000,
         | 
| @@ -307,7 +307,7 @@ export class YdbEmbeddedAPI extends AxiosWrapper { | |
| 307 307 | 
             
                            action: action || 'explain',
         | 
| 308 308 | 
             
                            timeout: 600000,
         | 
| 309 309 | 
             
                        },
         | 
| 310 | 
            -
                         | 
| 310 | 
            +
                        {},
         | 
| 311 311 | 
             
                    );
         | 
| 312 312 | 
             
                }
         | 
| 313 313 | 
             
                getExplainQueryAst(query: string, database: string) {
         | 
| @@ -319,7 +319,7 @@ export class YdbEmbeddedAPI extends AxiosWrapper { | |
| 319 319 | 
             
                            action: 'explain-ast',
         | 
| 320 320 | 
             
                            timeout: 600000,
         | 
| 321 321 | 
             
                        },
         | 
| 322 | 
            -
                         | 
| 322 | 
            +
                        {},
         | 
| 323 323 | 
             
                    );
         | 
| 324 324 | 
             
                }
         | 
| 325 325 | 
             
                getHotKeys(path: string, enableSampling: boolean) {
         | 
| @@ -334,18 +334,18 @@ export class YdbEmbeddedAPI extends AxiosWrapper { | |
| 334 334 | 
             
                    });
         | 
| 335 335 | 
             
                }
         | 
| 336 336 | 
             
                killTablet(id?: string) {
         | 
| 337 | 
            -
                    return this.get<string>(this.getPath(`/tablets?KillTabletID=${id}`),  | 
| 337 | 
            +
                    return this.get<string>(this.getPath(`/tablets?KillTabletID=${id}`), {});
         | 
| 338 338 | 
             
                }
         | 
| 339 339 | 
             
                stopTablet(id?: string, hiveId?: string) {
         | 
| 340 340 | 
             
                    return this.get<string>(
         | 
| 341 341 | 
             
                        this.getPath(`/tablets/app?TabletID=${hiveId}&page=StopTablet&tablet=${id}`),
         | 
| 342 | 
            -
                         | 
| 342 | 
            +
                        {},
         | 
| 343 343 | 
             
                    );
         | 
| 344 344 | 
             
                }
         | 
| 345 345 | 
             
                resumeTablet(id?: string, hiveId?: string) {
         | 
| 346 346 | 
             
                    return this.get<string>(
         | 
| 347 347 | 
             
                        this.getPath(`/tablets/app?TabletID=${hiveId}&page=ResumeTablet&tablet=${id}`),
         | 
| 348 | 
            -
                         | 
| 348 | 
            +
                        {},
         | 
| 349 349 | 
             
                    );
         | 
| 350 350 | 
             
                }
         | 
| 351 351 | 
             
                getTabletDescribe(tenantId: TDomainKey) {
         | 
| @@ -368,14 +368,14 @@ export class YdbEmbeddedAPI extends AxiosWrapper { | |
| 368 368 | 
             
                            user,
         | 
| 369 369 | 
             
                            password,
         | 
| 370 370 | 
             
                        },
         | 
| 371 | 
            -
                         | 
| 371 | 
            +
                        {},
         | 
| 372 372 | 
             
                    );
         | 
| 373 373 | 
             
                }
         | 
| 374 374 | 
             
                logout() {
         | 
| 375 | 
            -
                    return this.post(this.getPath('/logout'),  | 
| 375 | 
            +
                    return this.post(this.getPath('/logout'), {}, {});
         | 
| 376 376 | 
             
                }
         | 
| 377 377 | 
             
                whoami() {
         | 
| 378 | 
            -
                    return this.get<TUserToken>(this.getPath('/viewer/json/whoami'),  | 
| 378 | 
            +
                    return this.get<TUserToken>(this.getPath('/viewer/json/whoami'), {});
         | 
| 379 379 | 
             
                }
         | 
| 380 380 | 
             
            }
         | 
| 381 381 |  | 
| @@ -116,7 +116,11 @@ export const selectNodeStructure = createSelector( | |
| 116 116 | 
             
                                if (!structure[String(pDiskId)]) {
         | 
| 117 117 | 
             
                                    structure[String(pDiskId)] = {vDisks: {}, ...vd.PDisk};
         | 
| 118 118 | 
             
                                }
         | 
| 119 | 
            -
                                structure[String(pDiskId)].vDisks[vDiskId] =  | 
| 119 | 
            +
                                structure[String(pDiskId)].vDisks[vDiskId] = {
         | 
| 120 | 
            +
                                    ...vd,
         | 
| 121 | 
            +
                                    // VDisk doesn't have its own StoragePoolName when located inside StoragePool data
         | 
| 122 | 
            +
                                    StoragePoolName: pool.Name,
         | 
| 123 | 
            +
                                };
         | 
| 120 124 | 
             
                            });
         | 
| 121 125 | 
             
                        });
         | 
| 122 126 | 
             
                    });
         | 
| @@ -4,10 +4,10 @@ import type { | |
| 4 4 | 
             
                NodesListState,
         | 
| 5 5 | 
             
                NodesListAction,
         | 
| 6 6 | 
             
                NodesListRootStateSlice,
         | 
| 7 | 
            -
                NodesMap,
         | 
| 8 7 | 
             
            } from '../../types/store/nodesList';
         | 
| 9 8 | 
             
            import '../../services/api';
         | 
| 10 9 | 
             
            import {createRequestActionTypes, createApiRequest} from '../utils';
         | 
| 10 | 
            +
            import {prepareNodesMap} from '../../utils/nodes';
         | 
| 11 11 |  | 
| 12 12 | 
             
            export const FETCH_NODES_LIST = createRequestActionTypes('nodesList', 'FETCH_NODES_LIST');
         | 
| 13 13 |  | 
| @@ -50,11 +50,6 @@ export function getNodesList() { | |
| 50 50 | 
             
            }
         | 
| 51 51 |  | 
| 52 52 | 
             
            export const selectNodesMap = (state: NodesListRootStateSlice) =>
         | 
| 53 | 
            -
                state.nodesList.data | 
| 54 | 
            -
                    if (node.Id && node.Host) {
         | 
| 55 | 
            -
                        nodesMap.set(node.Id, node.Host);
         | 
| 56 | 
            -
                    }
         | 
| 57 | 
            -
                    return nodesMap;
         | 
| 58 | 
            -
                }, new Map());
         | 
| 53 | 
            +
                prepareNodesMap(state.nodesList.data);
         | 
| 59 54 |  | 
| 60 55 | 
             
            export default nodesList;
         | 
| @@ -271,10 +271,22 @@ export const getFlatListStorageGroups = createSelector([getStoragePools], (stora | |
| 271 271 | 
             
                                        ? currentType
         | 
| 272 272 | 
             
                                        : 'Mixed';
         | 
| 273 273 | 
             
                                }, '');
         | 
| 274 | 
            +
             | 
| 275 | 
            +
                                // VDisk doesn't have its own StoragePoolName when located inside StoragePool data
         | 
| 276 | 
            +
                                const vDisks = group.VDisks?.map((vdisk) => ({
         | 
| 277 | 
            +
                                    ...vdisk,
         | 
| 278 | 
            +
                                    StoragePoolName: pool.Name,
         | 
| 279 | 
            +
                                    Donors: vdisk.Donors?.map((donor) => ({
         | 
| 280 | 
            +
                                        ...donor,
         | 
| 281 | 
            +
                                        StoragePoolName: pool.Name,
         | 
| 282 | 
            +
                                    })),
         | 
| 283 | 
            +
                                }));
         | 
| 284 | 
            +
             | 
| 274 285 | 
             
                                return [
         | 
| 275 286 | 
             
                                    ...acc,
         | 
| 276 287 | 
             
                                    {
         | 
| 277 288 | 
             
                                        ...group,
         | 
| 289 | 
            +
                                        VDisks: vDisks,
         | 
| 278 290 | 
             
                                        Read: readSpeedBytesPerSec,
         | 
| 279 291 | 
             
                                        Write: writeSpeedBytesPerSec,
         | 
| 280 292 | 
             
                                        PoolName: pool.Name,
         | 
| @@ -11,6 +11,7 @@ import type { | |
| 11 11 | 
             
            import '../../services/api';
         | 
| 12 12 |  | 
| 13 13 | 
             
            import {createRequestActionTypes, createApiRequest} from '../utils';
         | 
| 14 | 
            +
            import {prepareNodesMap} from '../../utils/nodes';
         | 
| 14 15 |  | 
| 15 16 | 
             
            export const FETCH_TABLET = createRequestActionTypes('TABLET', 'FETCH_TABLET');
         | 
| 16 17 | 
             
            export const FETCH_TABLET_DESCRIBE = createRequestActionTypes('TABLET', 'FETCH_TABLET_DESCRIBE');
         | 
| @@ -63,9 +64,19 @@ const tablet: Reducer<ITabletState, ITabletAction> = (state = initialState, acti | |
| 63 64 |  | 
| 64 65 | 
             
            export const getTablet = (id: string) => {
         | 
| 65 66 | 
             
                return createApiRequest({
         | 
| 66 | 
            -
                    request: Promise.all([ | 
| 67 | 
            +
                    request: Promise.all([
         | 
| 68 | 
            +
                        window.api.getTablet({id}),
         | 
| 69 | 
            +
                        window.api.getTabletHistory({id}),
         | 
| 70 | 
            +
                        window.api.getNodesList(),
         | 
| 71 | 
            +
                    ]),
         | 
| 67 72 | 
             
                    actions: FETCH_TABLET,
         | 
| 68 | 
            -
                    dataHandler: ([ | 
| 73 | 
            +
                    dataHandler: ([
         | 
| 74 | 
            +
                        tabletResponseData,
         | 
| 75 | 
            +
                        historyResponseData,
         | 
| 76 | 
            +
                        nodesList,
         | 
| 77 | 
            +
                    ]): ITabletHandledResponse => {
         | 
| 78 | 
            +
                        const nodesMap = prepareNodesMap(nodesList);
         | 
| 79 | 
            +
             | 
| 69 80 | 
             
                        const historyData = Object.keys(historyResponseData).reduce<
         | 
| 70 81 | 
             
                            ITabletPreparedHistoryItem[]
         | 
| 71 82 | 
             
                        >((list, nodeId) => {
         | 
| @@ -75,6 +86,8 @@ export const getTablet = (id: string) => { | |
| 75 86 |  | 
| 76 87 | 
             
                                const {ChangeTime, Generation, State, Leader, FollowerId} = leaderTablet;
         | 
| 77 88 |  | 
| 89 | 
            +
                                const fqdn = nodesMap && nodeId ? nodesMap.get(Number(nodeId)) : undefined;
         | 
| 90 | 
            +
             | 
| 78 91 | 
             
                                list.push({
         | 
| 79 92 | 
             
                                    nodeId,
         | 
| 80 93 | 
             
                                    generation: Generation,
         | 
| @@ -82,6 +95,7 @@ export const getTablet = (id: string) => { | |
| 82 95 | 
             
                                    state: State,
         | 
| 83 96 | 
             
                                    leader: Leader,
         | 
| 84 97 | 
             
                                    followerId: FollowerId,
         | 
| 98 | 
            +
                                    fqdn,
         | 
| 85 99 | 
             
                                });
         | 
| 86 100 | 
             
                            }
         | 
| 87 101 | 
             
                            return list;
         | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            import {Toaster} from '@gravity-ui/uikit';
         | 
| 2 2 |  | 
| 3 | 
            -
            const toaster = new Toaster();
         | 
| 3 | 
            +
            export const toaster = new Toaster();
         | 
| 4 4 |  | 
| 5 5 | 
             
            interface CreateToastProps {
         | 
| 6 6 | 
             
                name?: string;
         | 
| @@ -10,13 +10,13 @@ interface CreateToastProps { | |
| 10 10 | 
             
            }
         | 
| 11 11 |  | 
| 12 12 | 
             
            function createToast({name, title, type, content}: CreateToastProps) {
         | 
| 13 | 
            -
                return toaster. | 
| 13 | 
            +
                return toaster.add({
         | 
| 14 14 | 
             
                    name: name ?? 'Request succeeded',
         | 
| 15 15 | 
             
                    title: title ?? 'Request succeeded',
         | 
| 16 16 | 
             
                    type: type ?? 'success',
         | 
| 17 17 | 
             
                    content: content,
         | 
| 18 18 | 
             
                    isClosable: true,
         | 
| 19 | 
            -
                     | 
| 19 | 
            +
                    autoHiding: type === 'success' ? 5000 : false,
         | 
| 20 20 | 
             
                });
         | 
| 21 21 | 
             
            }
         | 
| 22 22 |  | 
    
        package/dist/utils/nodes.ts
    CHANGED
    
    | @@ -1,5 +1,7 @@ | |
| 1 1 | 
             
            import type {TSystemStateInfo} from '../types/api/nodes';
         | 
| 2 | 
            +
            import type {TNodeInfo} from '../types/api/nodesList';
         | 
| 2 3 | 
             
            import type {INodesPreparedEntity} from '../types/store/nodes';
         | 
| 4 | 
            +
            import type {NodesMap} from '../types/store/nodesList';
         | 
| 3 5 | 
             
            import {EFlag} from '../types/api/enums';
         | 
| 4 6 |  | 
| 5 7 | 
             
            export enum NodesUptimeFilterValues {
         | 
| @@ -20,3 +22,12 @@ export type NodeAddress = Pick<TSystemStateInfo, 'Host' | 'Endpoints'>; | |
| 20 22 | 
             
            export interface AdditionalNodesInfo extends Record<string, unknown> {
         | 
| 21 23 | 
             
                getNodeRef?: (node?: NodeAddress) => string;
         | 
| 22 24 | 
             
            }
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            export const prepareNodesMap = (nodesList?: TNodeInfo[]) => {
         | 
| 27 | 
            +
                return nodesList?.reduce<NodesMap>((nodesMap, node) => {
         | 
| 28 | 
            +
                    if (node.Id && node.Host) {
         | 
| 29 | 
            +
                        nodesMap.set(Number(node.Id), node.Host);
         | 
| 30 | 
            +
                    }
         | 
| 31 | 
            +
                    return nodesMap;
         | 
| 32 | 
            +
                }, new Map());
         | 
| 33 | 
            +
            };
         | 
    
        package/package.json
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            {
         | 
| 2 2 | 
             
              "name": "ydb-embedded-ui",
         | 
| 3 | 
            -
              "version": "4.1 | 
| 3 | 
            +
              "version": "4.2.1",
         | 
| 4 4 | 
             
              "files": [
         | 
| 5 5 | 
             
                "dist"
         | 
| 6 6 | 
             
              ],
         | 
| @@ -12,7 +12,7 @@ | |
| 12 12 | 
             
                "@gravity-ui/axios-wrapper": "^1.3.0",
         | 
| 13 13 | 
             
                "@gravity-ui/date-utils": "^1.1.1",
         | 
| 14 14 | 
             
                "@gravity-ui/i18n": "^1.0.0",
         | 
| 15 | 
            -
                "@gravity-ui/navigation": "^0. | 
| 15 | 
            +
                "@gravity-ui/navigation": "^0.4.0",
         | 
| 16 16 | 
             
                "@gravity-ui/paranoid": "^1.4.0",
         | 
| 17 17 | 
             
                "@gravity-ui/react-data-table": "^1.0.3",
         | 
| 18 18 | 
             
                "axios": "0.19.2",
         | 
| @@ -39,7 +39,7 @@ | |
| 39 39 | 
             
                "reselect": "4.1.6",
         | 
| 40 40 | 
             
                "sass": "1.32.8",
         | 
| 41 41 | 
             
                "web-vitals": "1.1.2",
         | 
| 42 | 
            -
                "ydb-ui-components": "^3.0 | 
| 42 | 
            +
                "ydb-ui-components": "^3.1.0"
         | 
| 43 43 | 
             
              },
         | 
| 44 44 | 
             
              "scripts": {
         | 
| 45 45 | 
             
                "start": "react-app-rewired start",
         | 
| @@ -105,7 +105,7 @@ | |
| 105 105 | 
             
                "@gravity-ui/prettier-config": "^1.0.1",
         | 
| 106 106 | 
             
                "@gravity-ui/stylelint-config": "^1.0.1",
         | 
| 107 107 | 
             
                "@gravity-ui/tsconfig": "^1.0.0",
         | 
| 108 | 
            -
                "@gravity-ui/uikit": "^ | 
| 108 | 
            +
                "@gravity-ui/uikit": "^4.11.1",
         | 
| 109 109 | 
             
                "@playwright/test": "^1.31.1",
         | 
| 110 110 | 
             
                "@testing-library/jest-dom": "^5.15.0",
         | 
| 111 111 | 
             
                "@testing-library/react": "^11.2.7",
         |