ydb-embedded-ui 4.13.0 → 4.14.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +16 -0
- package/dist/components/Tablet/Tablet.scss +1 -16
- package/dist/components/Tablet/Tablet.tsx +5 -5
- package/dist/components/TabletIcon/TabletIcon.scss +17 -0
- package/dist/components/TabletIcon/TabletIcon.tsx +18 -0
- package/dist/containers/Header/Header.scss +2 -0
- package/dist/containers/Header/Header.tsx +2 -7
- package/dist/containers/Header/{breadcrumbs.ts → breadcrumbs.tsx} +19 -8
- package/dist/containers/Nodes/Nodes.tsx +53 -16
- package/dist/containers/Nodes/getNodesColumns.tsx +31 -13
- package/dist/containers/Tablet/Tablet.tsx +9 -3
- package/dist/containers/Tenant/Query/QueryDuration/QueryDuration.scss +8 -0
- package/dist/containers/Tenant/Query/QueryDuration/QueryDuration.tsx +13 -1
- package/dist/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.scss +3 -1
- package/dist/containers/Tenant/Query/i18n/en.json +6 -4
- package/dist/containers/Tenant/Query/i18n/ru.json +6 -4
- package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +4 -0
- package/dist/containers/Tenant/utils/schemaActions.ts +3 -10
- package/dist/containers/Tenant/utils/schemaControls.tsx +69 -0
- package/dist/containers/UserSettings/i18n/en.json +3 -0
- package/dist/containers/UserSettings/i18n/ru.json +3 -0
- package/dist/containers/UserSettings/settings.ts +12 -1
- package/dist/services/api.ts +24 -12
- package/dist/store/reducers/header/types.ts +2 -0
- package/dist/store/reducers/nodes/nodes.ts +23 -6
- package/dist/store/reducers/nodes/selectors.ts +2 -2
- package/dist/store/reducers/nodes/types.ts +15 -5
- package/dist/store/reducers/settings/settings.ts +5 -0
- package/dist/types/api/compute.ts +0 -12
- package/dist/types/api/nodes.ts +0 -12
- package/dist/utils/constants.ts +3 -0
- package/dist/utils/filters.ts +23 -0
- package/dist/utils/hooks/index.ts +3 -0
- package/dist/utils/hooks/useNodesRequestParams.ts +46 -0
- package/dist/utils/hooks/useTableSort.ts +37 -0
- package/dist/utils/nodes.ts +25 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [4.14.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.13.0...v4.14.0) (2023-08-11)
|
4
|
+
|
5
|
+
|
6
|
+
### Features
|
7
|
+
|
8
|
+
* **Nodes:** filter and sort on backend ([#503](https://github.com/ydb-platform/ydb-embedded-ui/issues/503)) ([2e8ab8e](https://github.com/ydb-platform/ydb-embedded-ui/commit/2e8ab8e9965db61ec281f7340b89dd3967b639df))
|
9
|
+
* **Query:** add explanation to query duration ([#501](https://github.com/ydb-platform/ydb-embedded-ui/issues/501)) ([a5f5140](https://github.com/ydb-platform/ydb-embedded-ui/commit/a5f5140a23864147d8495e3c6b94709e5e710a9b))
|
10
|
+
|
11
|
+
|
12
|
+
### Bug Fixes
|
13
|
+
|
14
|
+
* **Header:** add icons for nodes and tablets ([#500](https://github.com/ydb-platform/ydb-embedded-ui/issues/500)) ([862660c](https://github.com/ydb-platform/ydb-embedded-ui/commit/862660c1928c2c2b626e4417cd043f0bd5a65df9))
|
15
|
+
* **Query:** fix query method selector help text ([#504](https://github.com/ydb-platform/ydb-embedded-ui/issues/504)) ([65cdf9e](https://github.com/ydb-platform/ydb-embedded-ui/commit/65cdf9ee93277c193cc1ad036b2cb38d2ae15b71))
|
16
|
+
* **Query:** transfer API calls to a new line ([#499](https://github.com/ydb-platform/ydb-embedded-ui/issues/499)) ([de3d540](https://github.com/ydb-platform/ydb-embedded-ui/commit/de3d5404310f32ba05598bb99a1afb1b65ab45a1))
|
17
|
+
* **SchemaTree:** transfer Show Preview to SchemaTree ([#505](https://github.com/ydb-platform/ydb-embedded-ui/issues/505)) ([46220c4](https://github.com/ydb-platform/ydb-embedded-ui/commit/46220c4b2cd111acf12712b4693744c52aaf7231))
|
18
|
+
|
3
19
|
## [4.13.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.12.0...v4.13.0) (2023-08-04)
|
4
20
|
|
5
21
|
|
@@ -1,17 +1,8 @@
|
|
1
1
|
.tablet {
|
2
|
-
display: flex;
|
3
|
-
justify-content: center;
|
4
|
-
|
5
|
-
width: 23px;
|
6
|
-
height: 18px;
|
7
|
-
|
8
|
-
font-size: 10px;
|
9
2
|
cursor: pointer;
|
10
|
-
text-transform: uppercase;
|
11
3
|
|
12
4
|
color: var(--yc-color-text-complementary);
|
13
|
-
border:
|
14
|
-
border-radius: 4px;
|
5
|
+
border-color: var(--yc-color-base-generic-medium-hover);
|
15
6
|
|
16
7
|
&__wrapper {
|
17
8
|
margin-right: 2px;
|
@@ -25,12 +16,6 @@
|
|
25
16
|
padding: 10px;
|
26
17
|
}
|
27
18
|
|
28
|
-
&__type {
|
29
|
-
line-height: 17px;
|
30
|
-
|
31
|
-
color: var(--yc-color-text-complementary);
|
32
|
-
}
|
33
|
-
|
34
19
|
&_status_gray {
|
35
20
|
background-color: var(--yc-color-text-complementary);
|
36
21
|
}
|
@@ -5,6 +5,7 @@ import {getTabletLabel} from '../../utils/constants';
|
|
5
5
|
import routes, {createHref} from '../../routes';
|
6
6
|
|
7
7
|
import {ContentWithPopup} from '../ContentWithPopup/ContentWithPopup';
|
8
|
+
import {TabletIcon} from '../TabletIcon/TabletIcon';
|
8
9
|
import {InternalLink} from '../InternalLink';
|
9
10
|
import {TabletTooltipContent} from '../TooltipsContent';
|
10
11
|
|
@@ -18,10 +19,11 @@ interface TabletProps {
|
|
18
19
|
}
|
19
20
|
|
20
21
|
export const Tablet = ({tablet = {}, tenantName}: TabletProps) => {
|
21
|
-
const {TabletId: id, NodeId} = tablet;
|
22
|
+
const {TabletId: id, NodeId, Type} = tablet;
|
22
23
|
const status = tablet.Overall?.toLowerCase();
|
23
24
|
|
24
|
-
const tabletPath =
|
25
|
+
const tabletPath =
|
26
|
+
id && createHref(routes.tablet, {id}, {nodeId: NodeId, tenantName, type: Type});
|
25
27
|
|
26
28
|
return (
|
27
29
|
<ContentWithPopup
|
@@ -29,9 +31,7 @@ export const Tablet = ({tablet = {}, tenantName}: TabletProps) => {
|
|
29
31
|
content={<TabletTooltipContent data={tablet} className={b('popup-content')} />}
|
30
32
|
>
|
31
33
|
<InternalLink to={tabletPath}>
|
32
|
-
<
|
33
|
-
<div className={b('type')}>{[getTabletLabel(tablet.Type)]}</div>
|
34
|
-
</div>
|
34
|
+
<TabletIcon className={b({status})} text={getTabletLabel(tablet.Type)} />
|
35
35
|
</InternalLink>
|
36
36
|
</ContentWithPopup>
|
37
37
|
);
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import cn from 'bem-cn-lite';
|
2
|
+
|
3
|
+
import './TabletIcon.scss';
|
4
|
+
|
5
|
+
interface TabletIconProps {
|
6
|
+
text?: string;
|
7
|
+
className?: string;
|
8
|
+
}
|
9
|
+
|
10
|
+
const b = cn('tablet-icon');
|
11
|
+
|
12
|
+
export const TabletIcon = ({text, className}: TabletIconProps) => {
|
13
|
+
return (
|
14
|
+
<div className={b(null, className)}>
|
15
|
+
<div className={b('type')}>{text || 'T'}</div>
|
16
|
+
</div>
|
17
|
+
);
|
18
|
+
};
|
@@ -3,7 +3,7 @@ import {useHistory, useLocation} from 'react-router';
|
|
3
3
|
import {useDispatch} from 'react-redux';
|
4
4
|
import block from 'bem-cn-lite';
|
5
5
|
|
6
|
-
import {Breadcrumbs
|
6
|
+
import {Breadcrumbs} from '@gravity-ui/uikit';
|
7
7
|
|
8
8
|
import {ExternalLinkWithIcon} from '../../components/ExternalLinkWithIcon/ExternalLinkWithIcon';
|
9
9
|
|
@@ -91,12 +91,7 @@ function Header({mainPage}: HeaderProps) {
|
|
91
91
|
}
|
92
92
|
return (
|
93
93
|
<span className={b('breadcrumb')}>
|
94
|
-
<
|
95
|
-
width={16}
|
96
|
-
height={16}
|
97
|
-
data={icon}
|
98
|
-
className={b('breadcrumb__icon')}
|
99
|
-
/>
|
94
|
+
<div className={b('breadcrumb__icon')}>{icon}</div>
|
100
95
|
{text}
|
101
96
|
</span>
|
102
97
|
);
|
@@ -1,5 +1,9 @@
|
|
1
|
-
import
|
2
|
-
|
1
|
+
import {
|
2
|
+
NodesRight as ClusterIcon,
|
3
|
+
Database as DatabaseIcon,
|
4
|
+
Cpu as ComputeNodeIcon,
|
5
|
+
HardDrive as StorageNodeIcon,
|
6
|
+
} from '@gravity-ui/icons';
|
3
7
|
|
4
8
|
import type {
|
5
9
|
BreadcrumbsOptions,
|
@@ -15,8 +19,9 @@ import {
|
|
15
19
|
TENANT_PAGE,
|
16
20
|
TENANT_PAGES_IDS,
|
17
21
|
} from '../../store/reducers/tenant/constants';
|
22
|
+
import {TabletIcon} from '../../components/TabletIcon/TabletIcon';
|
18
23
|
import routes, {createHref} from '../../routes';
|
19
|
-
import {CLUSTER_DEFAULT_TITLE} from '../../utils/constants';
|
24
|
+
import {CLUSTER_DEFAULT_TITLE, getTabletLabel} from '../../utils/constants';
|
20
25
|
|
21
26
|
import {getClusterPath} from '../Cluster/utils';
|
22
27
|
import {TenantTabsGroups, getTenantPath} from '../Tenant/TenantPages';
|
@@ -29,7 +34,7 @@ const prepareTenantName = (tenantName: string) => {
|
|
29
34
|
export interface RawBreadcrumbItem {
|
30
35
|
text: string;
|
31
36
|
link?: string;
|
32
|
-
icon?:
|
37
|
+
icon?: JSX.Element;
|
33
38
|
}
|
34
39
|
|
35
40
|
const getClusterBreadcrumbs = (
|
@@ -42,7 +47,7 @@ const getClusterBreadcrumbs = (
|
|
42
47
|
{
|
43
48
|
text: clusterName || CLUSTER_DEFAULT_TITLE,
|
44
49
|
link: getClusterPath(clusterTab, query),
|
45
|
-
icon:
|
50
|
+
icon: <ClusterIcon />,
|
46
51
|
},
|
47
52
|
];
|
48
53
|
};
|
@@ -56,7 +61,7 @@ const getTenantBreadcrumbs = (
|
|
56
61
|
const text = tenantName ? prepareTenantName(tenantName) : 'Tenant';
|
57
62
|
const link = tenantName ? getTenantPath({...query, name: tenantName}) : undefined;
|
58
63
|
|
59
|
-
return [...getClusterBreadcrumbs(options, query), {text, link, icon:
|
64
|
+
return [...getClusterBreadcrumbs(options, query), {text, link, icon: <DatabaseIcon />}];
|
60
65
|
};
|
61
66
|
|
62
67
|
const getNodeBreadcrumbs = (options: NodeBreadcrumbsOptions, query = {}): RawBreadcrumbItem[] => {
|
@@ -81,8 +86,13 @@ const getNodeBreadcrumbs = (options: NodeBreadcrumbsOptions, query = {}): RawBre
|
|
81
86
|
|
82
87
|
const text = nodeId ? `Node ${nodeId}` : 'Node';
|
83
88
|
const link = nodeId ? getDefaultNodePath(nodeId, query) : undefined;
|
89
|
+
const icon = isStorageNode ? <StorageNodeIcon /> : <ComputeNodeIcon />;
|
84
90
|
|
85
|
-
breadcrumbs.push({
|
91
|
+
breadcrumbs.push({
|
92
|
+
text,
|
93
|
+
link,
|
94
|
+
icon,
|
95
|
+
});
|
86
96
|
|
87
97
|
return breadcrumbs;
|
88
98
|
};
|
@@ -123,12 +133,13 @@ const getTabletBreadcrubms = (
|
|
123
133
|
options: TabletBreadcrumbsOptions,
|
124
134
|
query = {},
|
125
135
|
): RawBreadcrumbItem[] => {
|
126
|
-
const {tabletId} = options;
|
136
|
+
const {tabletId, tabletType} = options;
|
127
137
|
|
128
138
|
const breadcrumbs = getTabletsBreadcrubms(options, query);
|
129
139
|
|
130
140
|
breadcrumbs.push({
|
131
141
|
text: tabletId || 'Tablet',
|
142
|
+
icon: <TabletIcon text={getTabletLabel(tabletType)} />,
|
132
143
|
});
|
133
144
|
|
134
145
|
return breadcrumbs;
|
@@ -3,9 +3,11 @@ import cn from 'bem-cn-lite';
|
|
3
3
|
import {useDispatch} from 'react-redux';
|
4
4
|
|
5
5
|
import DataTable from '@gravity-ui/react-data-table';
|
6
|
+
import {ASCENDING} from '@gravity-ui/react-data-table/build/esm/lib/constants';
|
6
7
|
|
7
8
|
import type {EPathType} from '../../types/api/schema';
|
8
9
|
import type {ProblemFilterValue} from '../../store/reducers/settings/types';
|
10
|
+
import type {NodesSortParams} from '../../store/reducers/nodes/types';
|
9
11
|
|
10
12
|
import {AccessDenied} from '../../components/Errors/403';
|
11
13
|
import {Illustration} from '../../components/Illustration';
|
@@ -17,7 +19,13 @@ import {TableWithControlsLayout} from '../../components/TableWithControlsLayout/
|
|
17
19
|
import {ResponseError} from '../../components/Errors/ResponseError';
|
18
20
|
|
19
21
|
import {DEFAULT_TABLE_SETTINGS, USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY} from '../../utils/constants';
|
20
|
-
import {
|
22
|
+
import {
|
23
|
+
useAutofetcher,
|
24
|
+
useSetting,
|
25
|
+
useTypedSelector,
|
26
|
+
useNodesRequestParams,
|
27
|
+
useTableSort,
|
28
|
+
} from '../../utils/hooks';
|
21
29
|
import {AdditionalNodesInfo, isUnavailableNode, NodesUptimeFilterValues} from '../../utils/nodes';
|
22
30
|
|
23
31
|
import {
|
@@ -26,6 +34,8 @@ import {
|
|
26
34
|
setSearchValue,
|
27
35
|
resetNodesState,
|
28
36
|
getComputeNodes,
|
37
|
+
setDataWasNotLoaded,
|
38
|
+
setSort,
|
29
39
|
} from '../../store/reducers/nodes/nodes';
|
30
40
|
import {selectFilteredNodes} from '../../store/reducers/nodes/selectors';
|
31
41
|
import {changeFilter, ProblemFilterValues} from '../../store/reducers/settings/settings';
|
@@ -58,8 +68,16 @@ export const Nodes = ({path, type, additionalNodesInfo = {}}: NodesProps) => {
|
|
58
68
|
dispatch(resetNodesState());
|
59
69
|
}, [dispatch, path]);
|
60
70
|
|
61
|
-
const {
|
62
|
-
|
71
|
+
const {
|
72
|
+
wasLoaded,
|
73
|
+
loading,
|
74
|
+
error,
|
75
|
+
nodesUptimeFilter,
|
76
|
+
searchValue,
|
77
|
+
sortOrder = ASCENDING,
|
78
|
+
sortValue = 'NodeId',
|
79
|
+
totalNodes,
|
80
|
+
} = useTypedSelector((state) => state.nodes);
|
63
81
|
const problemFilter = useTypedSelector((state) => state.settings.problemFilter);
|
64
82
|
const {autorefresh} = useTypedSelector((state) => state.schema);
|
65
83
|
|
@@ -67,18 +85,39 @@ export const Nodes = ({path, type, additionalNodesInfo = {}}: NodesProps) => {
|
|
67
85
|
|
68
86
|
const [useNodesEndpoint] = useSetting(USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY);
|
69
87
|
|
70
|
-
const
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
88
|
+
const requestParams = useNodesRequestParams({
|
89
|
+
filter: searchValue,
|
90
|
+
problemFilter,
|
91
|
+
nodesUptimeFilter,
|
92
|
+
sortOrder,
|
93
|
+
sortValue,
|
94
|
+
});
|
95
|
+
|
96
|
+
const fetchNodes = useCallback(
|
97
|
+
(isBackground) => {
|
98
|
+
if (!isBackground) {
|
99
|
+
dispatch(setDataWasNotLoaded());
|
100
|
+
}
|
101
|
+
|
102
|
+
const params = requestParams || {};
|
103
|
+
|
104
|
+
// For not DB entities we always use /compute endpoint instead of /nodes
|
105
|
+
// since /nodes can return data only for tenants
|
106
|
+
if (path && (!useNodesEndpoint || !isDatabaseEntityType(type))) {
|
107
|
+
dispatch(getComputeNodes({path, ...params}));
|
108
|
+
} else {
|
109
|
+
dispatch(getNodes({tenant: path, ...params}));
|
110
|
+
}
|
111
|
+
},
|
112
|
+
[dispatch, path, type, useNodesEndpoint, requestParams],
|
113
|
+
);
|
79
114
|
|
80
115
|
useAutofetcher(fetchNodes, [fetchNodes], isClusterNodes ? true : autorefresh);
|
81
116
|
|
117
|
+
const [sort, handleSort] = useTableSort({sortValue, sortOrder}, (sortParams) =>
|
118
|
+
dispatch(setSort(sortParams as NodesSortParams)),
|
119
|
+
);
|
120
|
+
|
82
121
|
const handleSearchQueryChange = (value: string) => {
|
83
122
|
dispatch(setSearchValue(value));
|
84
123
|
};
|
@@ -132,10 +171,8 @@ export const Nodes = ({path, type, additionalNodesInfo = {}}: NodesProps) => {
|
|
132
171
|
data={nodes || []}
|
133
172
|
columns={columns}
|
134
173
|
settings={DEFAULT_TABLE_SETTINGS}
|
135
|
-
|
136
|
-
|
137
|
-
order: DataTable.ASCENDING,
|
138
|
-
}}
|
174
|
+
sortOrder={sort}
|
175
|
+
onSort={handleSort}
|
139
176
|
emptyDataMessage={i18n('empty.default')}
|
140
177
|
rowClassName={(row) => b('node', {unavailable: isUnavailableNode(row)})}
|
141
178
|
/>
|
@@ -6,26 +6,42 @@ import ProgressViewer from '../../components/ProgressViewer/ProgressViewer';
|
|
6
6
|
import {TabletsStatistic} from '../../components/TabletsStatistic';
|
7
7
|
import {NodeHostWrapper} from '../../components/NodeHostWrapper/NodeHostWrapper';
|
8
8
|
|
9
|
-
import type
|
9
|
+
import {isSortableNodesProperty, type NodeAddress} from '../../utils/nodes';
|
10
10
|
import {formatBytesToGigabyte} from '../../utils/index';
|
11
11
|
|
12
12
|
import type {NodesPreparedEntity} from '../../store/reducers/nodes/types';
|
13
13
|
|
14
|
+
const NODES_COLUMNS_IDS = {
|
15
|
+
NodeId: 'NodeId',
|
16
|
+
Host: 'Host',
|
17
|
+
DC: 'DC',
|
18
|
+
Rack: 'Rack',
|
19
|
+
Version: 'Version',
|
20
|
+
Uptime: 'Uptime',
|
21
|
+
Memory: 'Memory',
|
22
|
+
CPU: 'CPU',
|
23
|
+
LoadAverage: 'LoadAverage',
|
24
|
+
Tablets: 'Tablets',
|
25
|
+
};
|
26
|
+
|
14
27
|
interface GetNodesColumnsProps {
|
15
28
|
tabletsPath?: string;
|
16
29
|
getNodeRef?: (node?: NodeAddress) => string | null;
|
17
30
|
}
|
18
31
|
|
19
|
-
export function getNodesColumns({
|
32
|
+
export function getNodesColumns({
|
33
|
+
tabletsPath,
|
34
|
+
getNodeRef,
|
35
|
+
}: GetNodesColumnsProps): Column<NodesPreparedEntity>[] {
|
20
36
|
const columns: Column<NodesPreparedEntity>[] = [
|
21
37
|
{
|
22
|
-
name:
|
38
|
+
name: NODES_COLUMNS_IDS.NodeId,
|
23
39
|
header: '#',
|
24
40
|
width: '80px',
|
25
41
|
align: DataTable.RIGHT,
|
26
42
|
},
|
27
43
|
{
|
28
|
-
name:
|
44
|
+
name: NODES_COLUMNS_IDS.Host,
|
29
45
|
render: ({row}) => {
|
30
46
|
return <NodeHostWrapper node={row} getNodeRef={getNodeRef} />;
|
31
47
|
},
|
@@ -33,21 +49,21 @@ export function getNodesColumns({tabletsPath, getNodeRef}: GetNodesColumnsProps)
|
|
33
49
|
align: DataTable.LEFT,
|
34
50
|
},
|
35
51
|
{
|
36
|
-
name:
|
52
|
+
name: NODES_COLUMNS_IDS.DC,
|
37
53
|
header: 'DC',
|
38
54
|
align: DataTable.LEFT,
|
39
55
|
render: ({row}) => (row.DataCenter ? row.DataCenter : '—'),
|
40
56
|
width: '60px',
|
41
57
|
},
|
42
58
|
{
|
43
|
-
name:
|
59
|
+
name: NODES_COLUMNS_IDS.Rack,
|
44
60
|
header: 'Rack',
|
45
61
|
align: DataTable.LEFT,
|
46
62
|
render: ({row}) => (row.Rack ? row.Rack : '—'),
|
47
63
|
width: '80px',
|
48
64
|
},
|
49
65
|
{
|
50
|
-
name:
|
66
|
+
name: NODES_COLUMNS_IDS.Version,
|
51
67
|
width: '200px',
|
52
68
|
align: DataTable.LEFT,
|
53
69
|
render: ({row}) => {
|
@@ -55,14 +71,14 @@ export function getNodesColumns({tabletsPath, getNodeRef}: GetNodesColumnsProps)
|
|
55
71
|
},
|
56
72
|
},
|
57
73
|
{
|
58
|
-
name:
|
74
|
+
name: NODES_COLUMNS_IDS.Uptime,
|
59
75
|
header: 'Uptime',
|
60
76
|
sortAccessor: ({StartTime}) => StartTime && -StartTime,
|
61
77
|
align: DataTable.RIGHT,
|
62
78
|
width: '110px',
|
63
79
|
},
|
64
80
|
{
|
65
|
-
name:
|
81
|
+
name: NODES_COLUMNS_IDS.Memory,
|
66
82
|
header: 'Memory',
|
67
83
|
sortAccessor: ({MemoryUsed = 0}) => Number(MemoryUsed),
|
68
84
|
defaultOrder: DataTable.DESCENDING,
|
@@ -77,7 +93,7 @@ export function getNodesColumns({tabletsPath, getNodeRef}: GetNodesColumnsProps)
|
|
77
93
|
width: '120px',
|
78
94
|
},
|
79
95
|
{
|
80
|
-
name:
|
96
|
+
name: NODES_COLUMNS_IDS.CPU,
|
81
97
|
header: 'CPU',
|
82
98
|
sortAccessor: ({PoolStats = []}) =>
|
83
99
|
PoolStats.reduce((acc, item) => {
|
@@ -93,7 +109,7 @@ export function getNodesColumns({tabletsPath, getNodeRef}: GetNodesColumnsProps)
|
|
93
109
|
width: '120px',
|
94
110
|
},
|
95
111
|
{
|
96
|
-
name:
|
112
|
+
name: NODES_COLUMNS_IDS.LoadAverage,
|
97
113
|
header: 'Load average',
|
98
114
|
sortAccessor: ({LoadAverage = []}) =>
|
99
115
|
LoadAverage.slice(0, 1).reduce((acc, item) => acc + item, 0),
|
@@ -112,7 +128,7 @@ export function getNodesColumns({tabletsPath, getNodeRef}: GetNodesColumnsProps)
|
|
112
128
|
width: '140px',
|
113
129
|
},
|
114
130
|
{
|
115
|
-
name:
|
131
|
+
name: NODES_COLUMNS_IDS.Tablets,
|
116
132
|
width: '430px',
|
117
133
|
render: ({row}) => {
|
118
134
|
return row.Tablets ? (
|
@@ -129,5 +145,7 @@ export function getNodesColumns({tabletsPath, getNodeRef}: GetNodesColumnsProps)
|
|
129
145
|
},
|
130
146
|
];
|
131
147
|
|
132
|
-
return columns
|
148
|
+
return columns.map((column) => {
|
149
|
+
return {...column, sortable: isSortableNodesProperty(column.name)};
|
150
|
+
});
|
133
151
|
}
|
@@ -13,6 +13,7 @@ import {DEVELOPER_UI_TITLE} from '../../utils/constants';
|
|
13
13
|
import '../../services/api';
|
14
14
|
import {parseQuery} from '../../routes';
|
15
15
|
|
16
|
+
import type {EType} from '../../types/api/tablet';
|
16
17
|
import EntityStatus from '../../components/EntityStatus/EntityStatus';
|
17
18
|
import {ResponseError} from '../../components/Errors/ResponseError';
|
18
19
|
import {Tag} from '../../components/Tag';
|
@@ -48,10 +49,14 @@ export const Tablet = () => {
|
|
48
49
|
error,
|
49
50
|
} = useTypedSelector((state) => state.tablet);
|
50
51
|
|
51
|
-
const {
|
52
|
-
|
52
|
+
const {
|
53
|
+
nodeId: queryNodeId,
|
54
|
+
tenantName: queryTenantName,
|
55
|
+
type: queryTabletType,
|
56
|
+
} = parseQuery(location);
|
53
57
|
const nodeId = tablet.NodeId?.toString() || queryNodeId?.toString();
|
54
58
|
const tenantName = tenantPath || queryTenantName?.toString();
|
59
|
+
const type = tablet.Type || (queryTabletType?.toString() as EType | undefined);
|
55
60
|
|
56
61
|
// NOTE: should be reviewed when migrating to React 18
|
57
62
|
useEffect(() => {
|
@@ -79,9 +84,10 @@ export const Tablet = () => {
|
|
79
84
|
nodeIds: nodeId ? [nodeId] : [],
|
80
85
|
tenantName,
|
81
86
|
tabletId: id,
|
87
|
+
tabletType: type,
|
82
88
|
}),
|
83
89
|
);
|
84
|
-
}, [dispatch, tenantName, id, nodeId]);
|
90
|
+
}, [dispatch, tenantName, id, nodeId, type]);
|
85
91
|
|
86
92
|
const renderExternalLinks = (link: {name: string; path: string}, index: number) => {
|
87
93
|
return (
|
@@ -1,6 +1,9 @@
|
|
1
1
|
import block from 'bem-cn-lite';
|
2
2
|
|
3
3
|
import {formatDurationToShortTimeFormat, parseUsToMs} from '../../../../utils/timeParsers';
|
4
|
+
import {LabelWithPopover} from '../../../../components/LabelWithPopover';
|
5
|
+
|
6
|
+
import i18n from '../i18n';
|
4
7
|
|
5
8
|
import './QueryDuration.scss';
|
6
9
|
|
@@ -17,5 +20,14 @@ export const QueryDuration = ({duration}: QueryDurationProps) => {
|
|
17
20
|
|
18
21
|
const parsedDuration = formatDurationToShortTimeFormat(parseUsToMs(duration), 1);
|
19
22
|
|
20
|
-
return
|
23
|
+
return (
|
24
|
+
<span className={b()}>
|
25
|
+
<LabelWithPopover
|
26
|
+
className={b('item-with-popover')}
|
27
|
+
contentClassName={b('popover')}
|
28
|
+
text={parsedDuration}
|
29
|
+
popoverContent={i18n('query-duration.description')}
|
30
|
+
/>
|
31
|
+
</span>
|
32
|
+
);
|
21
33
|
};
|
@@ -17,8 +17,10 @@
|
|
17
17
|
"preview.not-available": "Preview is not available",
|
18
18
|
"preview.close": "Close preview",
|
19
19
|
|
20
|
-
"method-description.script": "For YQL-scripts combining DDL and DML
|
21
|
-
"method-description.scan": "Read-only queries, potentially reading a lot of data
|
22
|
-
"method-description.data": "DML queries for changing and fetching data in serialization mode
|
23
|
-
"method-description.query": "Any
|
20
|
+
"method-description.script": "For YQL-scripts combining DDL and DML.\nAPI call: schema.scripting",
|
21
|
+
"method-description.scan": "Read-only queries, potentially reading a lot of data.\nAPI call: table.ExecuteScan",
|
22
|
+
"method-description.data": "DML queries for changing and fetching data in serialization mode.\nAPI call: table.executeDataQuery",
|
23
|
+
"method-description.query": "Any query. An experimental API call supposed to replace all existing methods.\nAPI Call: query.ExecuteScript",
|
24
|
+
|
25
|
+
"query-duration.description": "Duration of server-side query execution"
|
24
26
|
}
|
@@ -17,8 +17,10 @@
|
|
17
17
|
"preview.not-available": "Предпросмотр недоступен",
|
18
18
|
"preview.close": "Закрыть предпросмотр",
|
19
19
|
|
20
|
-
"method-description.script": "Для скриптов, совмещающих DDL и DML
|
21
|
-
"method-description.scan": "Только читающие запросы, потенциально читающие много
|
22
|
-
"method-description.data": "DML-запросы для изменения и выборки данных в режиме изоляции Serializable
|
23
|
-
"method-description.query": "Любые запросы. Экспериментальный перспективный метод, который в будущем заменит все
|
20
|
+
"method-description.script": "Для скриптов, совмещающих DDL и DML-конструкции.\nAPI call: schema.scripting",
|
21
|
+
"method-description.scan": "Только читающие запросы, потенциально читающие много данных.\nAPI call: table.ExecuteScan",
|
22
|
+
"method-description.data": "DML-запросы для изменения и выборки данных в режиме изоляции Serializable.\nAPI call: table.executeDataQuery",
|
23
|
+
"method-description.query": "Любые запросы. Экспериментальный перспективный метод, который в будущем заменит все остальные.\nAPI call: query.ExecuteScript",
|
24
|
+
|
25
|
+
"query-duration.description": "Время выполнения запроса на стороне сервера"
|
24
26
|
}
|
@@ -9,6 +9,7 @@ import {useQueryModes} from '../../../../utils/hooks';
|
|
9
9
|
|
10
10
|
import {isChildlessPathType, mapPathTypeToNavigationTreeType} from '../../utils/schema';
|
11
11
|
import {getActions} from '../../utils/schemaActions';
|
12
|
+
import {getControls} from '../../utils/schemaControls';
|
12
13
|
|
13
14
|
interface SchemaTreeProps {
|
14
15
|
rootPath: string;
|
@@ -78,6 +79,9 @@ export function SchemaTree(props: SchemaTreeProps) {
|
|
78
79
|
setActivePath: handleActivePathUpdate,
|
79
80
|
setQueryMode,
|
80
81
|
})}
|
82
|
+
renderAdditionalNodeElements={getControls(dispatch, {
|
83
|
+
setActivePath: handleActivePathUpdate,
|
84
|
+
})}
|
81
85
|
activePath={currentPath}
|
82
86
|
onActivePathUpdate={handleActivePathUpdate}
|
83
87
|
cache={false}
|
@@ -6,7 +6,6 @@ import type {NavigationTreeNodeType, NavigationTreeProps} from 'ydb-ui-component
|
|
6
6
|
import type {QueryMode} from '../../../types/store/query';
|
7
7
|
import type {SetQueryModeIfAvailable} from '../../../utils/hooks';
|
8
8
|
import {changeUserInput} from '../../../store/reducers/executeQuery';
|
9
|
-
import {setShowPreview} from '../../../store/reducers/schema/schema';
|
10
9
|
import {setQueryTab, setTenantPage} from '../../../store/reducers/tenant/tenant';
|
11
10
|
import {TENANT_QUERY_TABS_ID, TENANT_PAGES_IDS} from '../../../store/reducers/tenant/constants';
|
12
11
|
import createToast from '../../../utils/createToast';
|
@@ -109,12 +108,6 @@ const bindActions = (
|
|
109
108
|
});
|
110
109
|
}
|
111
110
|
},
|
112
|
-
openPreview: () => {
|
113
|
-
dispatch(setShowPreview(true));
|
114
|
-
dispatch(setTenantPage(TENANT_PAGES_IDS.query));
|
115
|
-
dispatch(setQueryTab(TENANT_QUERY_TABS_ID.newQuery));
|
116
|
-
setActivePath(path);
|
117
|
-
},
|
118
111
|
};
|
119
112
|
};
|
120
113
|
|
@@ -125,14 +118,13 @@ export const getActions =
|
|
125
118
|
(path: string, type: NavigationTreeNodeType) => {
|
126
119
|
const actions = bindActions(path, dispatch, additionalEffects);
|
127
120
|
const copyItem = {text: i18n('actions.copyPath'), action: actions.copyPath};
|
128
|
-
const openPreview = {text: i18n('actions.openPreview'), action: actions.openPreview};
|
129
121
|
|
130
122
|
const DIR_SET: ActionsSet = [
|
131
123
|
[copyItem],
|
132
124
|
[{text: i18n('actions.createTable'), action: actions.createTable}],
|
133
125
|
];
|
134
126
|
const TABLE_SET: ActionsSet = [
|
135
|
-
[
|
127
|
+
[copyItem],
|
136
128
|
[
|
137
129
|
{text: i18n('actions.alterTable'), action: actions.alterTable},
|
138
130
|
{text: i18n('actions.selectQuery'), action: actions.selectQuery},
|
@@ -141,7 +133,7 @@ export const getActions =
|
|
141
133
|
];
|
142
134
|
|
143
135
|
const EXTERNAL_TABLE_SET = [
|
144
|
-
[
|
136
|
+
[copyItem],
|
145
137
|
[
|
146
138
|
{
|
147
139
|
text: i18n('actions.selectQuery'),
|
@@ -169,6 +161,7 @@ export const getActions =
|
|
169
161
|
|
170
162
|
index_table: JUST_COPY,
|
171
163
|
topic: JUST_COPY,
|
164
|
+
stream: JUST_COPY,
|
172
165
|
|
173
166
|
index: JUST_COPY,
|
174
167
|
|
@@ -0,0 +1,69 @@
|
|
1
|
+
import {Dispatch} from 'react';
|
2
|
+
|
3
|
+
import {NavigationTreeNodeType, NavigationTreeProps} from 'ydb-ui-components';
|
4
|
+
import {Button} from '@gravity-ui/uikit';
|
5
|
+
|
6
|
+
import {setShowPreview} from '../../../store/reducers/schema/schema';
|
7
|
+
import {setQueryTab, setTenantPage} from '../../../store/reducers/tenant/tenant';
|
8
|
+
import {TENANT_PAGES_IDS, TENANT_QUERY_TABS_ID} from '../../../store/reducers/tenant/constants';
|
9
|
+
import {IconWrapper} from '../../../components/Icon';
|
10
|
+
|
11
|
+
import i18n from '../i18n';
|
12
|
+
|
13
|
+
interface ControlsAdditionalEffects {
|
14
|
+
setActivePath: (path: string) => void;
|
15
|
+
}
|
16
|
+
|
17
|
+
const bindActions = (
|
18
|
+
path: string,
|
19
|
+
dispatch: Dispatch<any>,
|
20
|
+
additionalEffects: ControlsAdditionalEffects,
|
21
|
+
) => {
|
22
|
+
const {setActivePath} = additionalEffects;
|
23
|
+
|
24
|
+
return {
|
25
|
+
openPreview: () => {
|
26
|
+
dispatch(setShowPreview(true));
|
27
|
+
dispatch(setTenantPage(TENANT_PAGES_IDS.query));
|
28
|
+
dispatch(setQueryTab(TENANT_QUERY_TABS_ID.newQuery));
|
29
|
+
setActivePath(path);
|
30
|
+
},
|
31
|
+
};
|
32
|
+
};
|
33
|
+
|
34
|
+
type Controls = ReturnType<Required<NavigationTreeProps>['renderAdditionalNodeElements']>;
|
35
|
+
|
36
|
+
export const getControls =
|
37
|
+
(dispatch: Dispatch<any>, additionalEffects: ControlsAdditionalEffects) =>
|
38
|
+
(path: string, type: NavigationTreeNodeType) => {
|
39
|
+
const options = bindActions(path, dispatch, additionalEffects);
|
40
|
+
const openPreview = (
|
41
|
+
<Button
|
42
|
+
view="flat-secondary"
|
43
|
+
onClick={options.openPreview}
|
44
|
+
title={i18n('actions.openPreview')}
|
45
|
+
size="s"
|
46
|
+
>
|
47
|
+
<IconWrapper name="tablePreview" />
|
48
|
+
</Button>
|
49
|
+
);
|
50
|
+
|
51
|
+
const nodeTypeToControls: Record<NavigationTreeNodeType, Controls> = {
|
52
|
+
database: undefined,
|
53
|
+
directory: undefined,
|
54
|
+
|
55
|
+
table: openPreview,
|
56
|
+
column_table: openPreview,
|
57
|
+
|
58
|
+
index_table: undefined,
|
59
|
+
topic: undefined,
|
60
|
+
stream: undefined,
|
61
|
+
|
62
|
+
index: undefined,
|
63
|
+
|
64
|
+
external_table: openPreview,
|
65
|
+
external_data_source: undefined,
|
66
|
+
};
|
67
|
+
|
68
|
+
return nodeTypeToControls[type];
|
69
|
+
};
|
@@ -15,6 +15,9 @@
|
|
15
15
|
"settings.useNodesEndpoint.title": "Break the Nodes tab in Diagnostics",
|
16
16
|
"settings.useNodesEndpoint.popover": "Use /viewer/json/nodes endpoint for Nodes Tab in diagnostics. It returns incorrect data on versions before 23-1",
|
17
17
|
|
18
|
+
"settings.useBackendParamsForTables.title": "Offload tables filters and sorting to backend",
|
19
|
+
"settings.useBackendParamsForTables.popover": "Filter and sort Nodes table with request params. May increase performance, but could causes additional fetches and longer loading time on older versions",
|
20
|
+
|
18
21
|
"settings.enableAdditionalQueryModes.title": "Enable additional query modes",
|
19
22
|
"settings.enableAdditionalQueryModes.popover": "Adds 'Data' and 'YQL - QueryService' modes. May not work on some versions"
|
20
23
|
}
|
@@ -15,6 +15,9 @@
|
|
15
15
|
"settings.useNodesEndpoint.title": "Сломать вкладку Nodes в диагностике",
|
16
16
|
"settings.useNodesEndpoint.popover": "Использовать эндпоинт /viewer/json/nodes для вкладки Nodes в диагностике. Может возвращать некорректные данные на версиях до 23-1",
|
17
17
|
|
18
|
+
"settings.useBackendParamsForTables.title": "Перенести фильтры и сортировку таблиц на бэкенд",
|
19
|
+
"settings.useBackendParamsForTables.popover": "Добавляет фильтрацию и сортировку таблицы Nodes с использованием параметров запроса. Может улушить производительность, но на старых версиях может привести к дополнительным запросам и большему времени ожидания загрузки",
|
20
|
+
|
18
21
|
"settings.enableAdditionalQueryModes.title": "Включить дополнительные режимы выполнения запросов",
|
19
22
|
"settings.enableAdditionalQueryModes.popover": "Добавляет режимы 'Data' и 'YQL - QueryService'. Может работать некорректно на некоторых версиях"
|
20
23
|
}
|
@@ -7,6 +7,7 @@ import {
|
|
7
7
|
ENABLE_ADDITIONAL_QUERY_MODES,
|
8
8
|
INVERTED_DISKS_KEY,
|
9
9
|
THEME_KEY,
|
10
|
+
USE_BACKEND_PARAMS_FOR_TABLES_KEY,
|
10
11
|
USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY,
|
11
12
|
} from '../../utils/constants';
|
12
13
|
|
@@ -58,6 +59,11 @@ export const useNodesEndpointSetting: SettingProps = {
|
|
58
59
|
title: i18n('settings.useNodesEndpoint.title'),
|
59
60
|
helpPopoverContent: i18n('settings.useNodesEndpoint.popover'),
|
60
61
|
};
|
62
|
+
export const useBackendParamsForTables: SettingProps = {
|
63
|
+
settingKey: USE_BACKEND_PARAMS_FOR_TABLES_KEY,
|
64
|
+
title: i18n('settings.useBackendParamsForTables.title'),
|
65
|
+
helpPopoverContent: i18n('settings.useBackendParamsForTables.popover'),
|
66
|
+
};
|
61
67
|
export const enableQueryModesForExplainSetting: SettingProps = {
|
62
68
|
settingKey: ENABLE_ADDITIONAL_QUERY_MODES,
|
63
69
|
title: i18n('settings.enableAdditionalQueryModes.title'),
|
@@ -72,7 +78,12 @@ export const generalSection: SettingsSection = {
|
|
72
78
|
export const experimentsSection: SettingsSection = {
|
73
79
|
id: 'experimentsSection',
|
74
80
|
title: i18n('section.experiments'),
|
75
|
-
settings: [
|
81
|
+
settings: [
|
82
|
+
invertedDisksSetting,
|
83
|
+
useNodesEndpointSetting,
|
84
|
+
useBackendParamsForTables,
|
85
|
+
enableQueryModesForExplainSetting,
|
86
|
+
],
|
76
87
|
};
|
77
88
|
|
78
89
|
export const generalPage: SettingsPage = {
|
package/dist/services/api.ts
CHANGED
@@ -32,6 +32,7 @@ import type {ComputeApiRequestParams, NodesApiRequestParams} from '../store/redu
|
|
32
32
|
import type {StorageApiRequestParams} from '../store/reducers/storage/types';
|
33
33
|
|
34
34
|
import {backend as BACKEND} from '../store';
|
35
|
+
import {prepareSortValue} from '../utils/filters';
|
35
36
|
|
36
37
|
const config = {withCredentials: !window.custom_backend};
|
37
38
|
|
@@ -82,24 +83,35 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
|
|
82
83
|
});
|
83
84
|
}
|
84
85
|
getNodes(
|
85
|
-
{
|
86
|
+
{
|
87
|
+
visibleEntities,
|
88
|
+
type = 'any',
|
89
|
+
tablets = true,
|
90
|
+
sortOrder,
|
91
|
+
sortValue,
|
92
|
+
...params
|
93
|
+
}: NodesApiRequestParams,
|
86
94
|
{concurrentId}: AxiosOptions = {},
|
87
95
|
) {
|
96
|
+
const sort = prepareSortValue(sortValue, sortOrder);
|
97
|
+
|
88
98
|
return this.get<TNodesInfo>(
|
89
99
|
this.getPath('/viewer/json/nodes?enums=true'),
|
90
|
-
{
|
91
|
-
|
92
|
-
type,
|
93
|
-
tablets,
|
94
|
-
...params,
|
95
|
-
},
|
96
|
-
{
|
97
|
-
concurrentId,
|
98
|
-
},
|
100
|
+
{with: visibleEntities, type, tablets, sort, ...params},
|
101
|
+
{concurrentId},
|
99
102
|
);
|
100
103
|
}
|
101
|
-
getCompute(
|
102
|
-
|
104
|
+
getCompute(
|
105
|
+
{sortOrder, sortValue, ...params}: ComputeApiRequestParams,
|
106
|
+
{concurrentId}: AxiosOptions = {},
|
107
|
+
) {
|
108
|
+
const sort = prepareSortValue(sortValue, sortOrder);
|
109
|
+
|
110
|
+
return this.get<TComputeInfo>(
|
111
|
+
this.getPath('/viewer/json/compute?enums=true'),
|
112
|
+
{sort, ...params},
|
113
|
+
{concurrentId},
|
114
|
+
);
|
103
115
|
}
|
104
116
|
getStorageInfo(
|
105
117
|
{tenant, visibleEntities, nodeId}: StorageApiRequestParams,
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import type {ClusterTab} from '../../../containers/Cluster/utils';
|
2
|
+
import type {EType} from '../../../types/api/tablet';
|
2
3
|
|
3
4
|
import {setHeaderBreadcrumbs} from './header';
|
4
5
|
|
@@ -23,6 +24,7 @@ export interface TabletsBreadcrumbsOptions extends TenantBreadcrumbsOptions {
|
|
23
24
|
|
24
25
|
export interface TabletBreadcrumbsOptions extends TabletsBreadcrumbsOptions {
|
25
26
|
tabletId?: string;
|
27
|
+
tabletType?: EType;
|
26
28
|
}
|
27
29
|
|
28
30
|
export type BreadcrumbsOptions =
|
@@ -10,6 +10,7 @@ import type {
|
|
10
10
|
ComputeApiRequestParams,
|
11
11
|
NodesAction,
|
12
12
|
NodesApiRequestParams,
|
13
|
+
NodesSortParams,
|
13
14
|
NodesState,
|
14
15
|
} from './types';
|
15
16
|
import {prepareComputeNodesData, prepareNodesData} from './utils';
|
@@ -20,6 +21,7 @@ const RESET_NODES_STATE = 'nodes/RESET_NODES_STATE';
|
|
20
21
|
const SET_NODES_UPTIME_FILTER = 'nodes/SET_NODES_UPTIME_FILTER';
|
21
22
|
const SET_DATA_WAS_NOT_LOADED = 'nodes/SET_DATA_WAS_NOT_LOADED';
|
22
23
|
const SET_SEARCH_VALUE = 'nodes/SET_SEARCH_VALUE';
|
24
|
+
const SET_SORT = 'nodes/SET_SORT';
|
23
25
|
|
24
26
|
const initialState = {
|
25
27
|
loading: false,
|
@@ -47,6 +49,10 @@ const nodes: Reducer<NodesState, NodesAction> = (state = initialState, action) =
|
|
47
49
|
};
|
48
50
|
}
|
49
51
|
case FETCH_NODES.FAILURE: {
|
52
|
+
if (action.error?.isCancelled) {
|
53
|
+
return state;
|
54
|
+
}
|
55
|
+
|
50
56
|
return {
|
51
57
|
...state,
|
52
58
|
error: action.error,
|
@@ -74,7 +80,13 @@ const nodes: Reducer<NodesState, NodesAction> = (state = initialState, action) =
|
|
74
80
|
searchValue: action.data,
|
75
81
|
};
|
76
82
|
}
|
77
|
-
|
83
|
+
case SET_SORT: {
|
84
|
+
return {
|
85
|
+
...state,
|
86
|
+
sortValue: action.data.sortValue,
|
87
|
+
sortOrder: action.data.sortOrder,
|
88
|
+
};
|
89
|
+
}
|
78
90
|
case SET_DATA_WAS_NOT_LOADED: {
|
79
91
|
return {
|
80
92
|
...state,
|
@@ -85,13 +97,11 @@ const nodes: Reducer<NodesState, NodesAction> = (state = initialState, action) =
|
|
85
97
|
return state;
|
86
98
|
}
|
87
99
|
};
|
100
|
+
const concurrentId = 'getNodes';
|
88
101
|
|
89
102
|
export function getNodes({type = 'any', ...params}: NodesApiRequestParams) {
|
90
103
|
return createApiRequest({
|
91
|
-
request: window.api.getNodes({
|
92
|
-
type,
|
93
|
-
...params,
|
94
|
-
}),
|
104
|
+
request: window.api.getNodes({type, ...params}, {concurrentId}),
|
95
105
|
actions: FETCH_NODES,
|
96
106
|
dataHandler: prepareNodesData,
|
97
107
|
});
|
@@ -99,7 +109,7 @@ export function getNodes({type = 'any', ...params}: NodesApiRequestParams) {
|
|
99
109
|
|
100
110
|
export function getComputeNodes({version = EVersion.v2, ...params}: ComputeApiRequestParams) {
|
101
111
|
return createApiRequest({
|
102
|
-
request: window.api.getCompute({version, ...params}),
|
112
|
+
request: window.api.getCompute({version, ...params}, {concurrentId}),
|
103
113
|
actions: FETCH_NODES,
|
104
114
|
dataHandler: prepareComputeNodesData,
|
105
115
|
});
|
@@ -130,4 +140,11 @@ export const setSearchValue = (value: string) => {
|
|
130
140
|
} as const;
|
131
141
|
};
|
132
142
|
|
143
|
+
export const setSort = (sortParams: NodesSortParams) => {
|
144
|
+
return {
|
145
|
+
type: SET_SORT,
|
146
|
+
data: sortParams,
|
147
|
+
} as const;
|
148
|
+
};
|
149
|
+
|
133
150
|
export default nodes;
|
@@ -1,10 +1,10 @@
|
|
1
1
|
import {Selector, createSelector} from 'reselect';
|
2
|
-
import {escapeRegExp} from 'lodash';
|
3
2
|
|
4
3
|
import {EFlag} from '../../../types/api/enums';
|
5
4
|
import {calcUptimeInSeconds} from '../../../utils';
|
6
5
|
import {HOUR_IN_SECONDS} from '../../../utils/constants';
|
7
6
|
import {NodesUptimeFilterValues} from '../../../utils/nodes';
|
7
|
+
import {prepareSearchValue} from '../../../utils/filters';
|
8
8
|
|
9
9
|
import type {ProblemFilterValue} from '../settings/types';
|
10
10
|
import type {NodesPreparedEntity, NodesStateSlice} from './types';
|
@@ -41,7 +41,7 @@ const filterNodesBySearchValue = (nodesList: NodesPreparedEntity[] = [], searchV
|
|
41
41
|
if (!searchValue) {
|
42
42
|
return nodesList;
|
43
43
|
}
|
44
|
-
const re =
|
44
|
+
const re = prepareSearchValue(searchValue);
|
45
45
|
|
46
46
|
return nodesList.filter((node) => {
|
47
47
|
return node.Host ? re.test(node.Host) || re.test(String(node.NodeId)) : true;
|
@@ -1,3 +1,5 @@
|
|
1
|
+
import type {OrderType} from '@gravity-ui/react-data-table';
|
2
|
+
|
1
3
|
import type {IResponseError} from '../../../types/api/error';
|
2
4
|
import type {TEndpoint, TPoolStats} from '../../../types/api/nodes';
|
3
5
|
import type {
|
@@ -9,13 +11,14 @@ import type {EFlag} from '../../../types/api/enums';
|
|
9
11
|
import type {ApiRequestAction} from '../../utils';
|
10
12
|
import type {VisibleEntities} from '../storage/types';
|
11
13
|
|
12
|
-
import {NodesUptimeFilterValues} from '../../../utils/nodes';
|
14
|
+
import type {NodesSortValue, NodesUptimeFilterValues} from '../../../utils/nodes';
|
13
15
|
import {
|
14
16
|
FETCH_NODES,
|
15
17
|
resetNodesState,
|
16
18
|
setDataWasNotLoaded,
|
17
19
|
setNodesUptimeFilter,
|
18
20
|
setSearchValue,
|
21
|
+
setSort,
|
19
22
|
} from './nodes';
|
20
23
|
|
21
24
|
// Since nodes from different endpoints can have different types,
|
@@ -42,6 +45,8 @@ export interface NodesState {
|
|
42
45
|
wasLoaded: boolean;
|
43
46
|
nodesUptimeFilter: NodesUptimeFilterValues;
|
44
47
|
searchValue: string;
|
48
|
+
sortValue?: NodesSortValue;
|
49
|
+
sortOrder?: OrderType;
|
45
50
|
data?: NodesPreparedEntity[];
|
46
51
|
totalNodes?: number;
|
47
52
|
error?: IResponseError;
|
@@ -49,17 +54,21 @@ export interface NodesState {
|
|
49
54
|
|
50
55
|
export type NodeType = 'static' | 'dynamic' | 'any';
|
51
56
|
|
52
|
-
interface
|
57
|
+
export interface NodesSortParams {
|
58
|
+
sortOrder?: OrderType;
|
59
|
+
sortValue?: NodesSortValue;
|
60
|
+
}
|
61
|
+
|
62
|
+
export interface NodesGeneralRequestParams extends NodesSortParams {
|
53
63
|
filter?: string; // NodeId or Host
|
54
64
|
uptime?: number; // return nodes with less uptime in seconds
|
55
65
|
problems_only?: boolean; // return nodes with SystemState !== EFlag.Green
|
56
|
-
sort?: string; // Sort by one of ESort params (may differ for /nodes and /compute)
|
57
66
|
|
58
67
|
offser?: number;
|
59
68
|
limit?: number;
|
60
69
|
}
|
61
70
|
|
62
|
-
export interface NodesApiRequestParams extends
|
71
|
+
export interface NodesApiRequestParams extends NodesGeneralRequestParams {
|
63
72
|
tenant?: string;
|
64
73
|
type?: NodeType;
|
65
74
|
visibleEntities?: VisibleEntities; // "with" param
|
@@ -67,7 +76,7 @@ export interface NodesApiRequestParams extends RequestParams {
|
|
67
76
|
tablets?: boolean;
|
68
77
|
}
|
69
78
|
|
70
|
-
export interface ComputeApiRequestParams extends
|
79
|
+
export interface ComputeApiRequestParams extends NodesGeneralRequestParams {
|
71
80
|
path: string;
|
72
81
|
version?: EVersion; // only v2 works with filters
|
73
82
|
}
|
@@ -90,6 +99,7 @@ export type NodesAction =
|
|
90
99
|
| ReturnType<typeof setDataWasNotLoaded>
|
91
100
|
| ReturnType<typeof setNodesUptimeFilter>
|
92
101
|
| ReturnType<typeof setSearchValue>
|
102
|
+
| ReturnType<typeof setSort>
|
93
103
|
| ReturnType<typeof resetNodesState>
|
94
104
|
);
|
95
105
|
|
@@ -13,6 +13,7 @@ import {
|
|
13
13
|
ENABLE_ADDITIONAL_QUERY_MODES,
|
14
14
|
CLUSTER_INFO_HIDDEN_KEY,
|
15
15
|
LAST_USED_QUERY_ACTION_KEY,
|
16
|
+
USE_BACKEND_PARAMS_FOR_TABLES_KEY,
|
16
17
|
} from '../../../utils/constants';
|
17
18
|
import '../../../services/api';
|
18
19
|
import {getValueFromLS, parseJson} from '../../../utils/utils';
|
@@ -76,6 +77,10 @@ export const initialState = {
|
|
76
77
|
[ASIDE_HEADER_COMPACT_KEY]: readSavedSettingsValue(ASIDE_HEADER_COMPACT_KEY, 'true'),
|
77
78
|
[PARTITIONS_HIDDEN_COLUMNS_KEY]: readSavedSettingsValue(PARTITIONS_HIDDEN_COLUMNS_KEY),
|
78
79
|
[CLUSTER_INFO_HIDDEN_KEY]: readSavedSettingsValue(CLUSTER_INFO_HIDDEN_KEY, 'true'),
|
80
|
+
[USE_BACKEND_PARAMS_FOR_TABLES_KEY]: readSavedSettingsValue(
|
81
|
+
USE_BACKEND_PARAMS_FOR_TABLES_KEY,
|
82
|
+
'false',
|
83
|
+
),
|
79
84
|
},
|
80
85
|
systemSettings,
|
81
86
|
};
|
@@ -65,15 +65,3 @@ export enum EVersion {
|
|
65
65
|
v1 = 'v1',
|
66
66
|
v2 = 'v2', // only this versions works with sorting
|
67
67
|
}
|
68
|
-
|
69
|
-
export enum ESort {
|
70
|
-
NodeId = 'NodeId',
|
71
|
-
Host = 'Host',
|
72
|
-
DC = 'DC',
|
73
|
-
Rack = 'Rack',
|
74
|
-
Version = 'Version',
|
75
|
-
Uptime = 'Uptime',
|
76
|
-
Memory = 'Memory',
|
77
|
-
CPU = 'CPU',
|
78
|
-
LoadAverage = 'LoadAverage',
|
79
|
-
}
|
package/dist/types/api/nodes.ts
CHANGED
@@ -101,15 +101,3 @@ enum EConfigState {
|
|
101
101
|
'Consistent' = 'Consistent',
|
102
102
|
'Outdated' = 'Outdated',
|
103
103
|
}
|
104
|
-
|
105
|
-
export enum ESort {
|
106
|
-
NodeId = 'NodeId',
|
107
|
-
Host = 'Host',
|
108
|
-
DC = 'DC',
|
109
|
-
Rack = 'Rack',
|
110
|
-
Version = 'Version',
|
111
|
-
Uptime = 'Uptime',
|
112
|
-
Memory = 'Memory',
|
113
|
-
CPU = 'CPU',
|
114
|
-
LoadAverage = 'LoadAverage',
|
115
|
-
}
|
package/dist/utils/constants.ts
CHANGED
@@ -119,3 +119,6 @@ export const CLUSTER_INFO_HIDDEN_KEY = 'clusterInfoHidden';
|
|
119
119
|
|
120
120
|
// Remain "tab" in key name for backward compatibility
|
121
121
|
export const TENANT_INITIAL_PAGE_KEY = 'saved_tenant_initial_tab';
|
122
|
+
|
123
|
+
// Send filters and sort params to backend for Nodes and Storage tables
|
124
|
+
export const USE_BACKEND_PARAMS_FOR_TABLES_KEY = 'useBackendParamsForTables';
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import {escapeRegExp} from 'lodash';
|
2
|
+
|
3
|
+
import type {OrderType} from '@gravity-ui/react-data-table';
|
4
|
+
import {DESCENDING} from '@gravity-ui/react-data-table/build/esm/lib/constants';
|
5
|
+
|
6
|
+
export const prepareSortValue = (
|
7
|
+
sortValue: string | undefined,
|
8
|
+
sortOrder: OrderType = DESCENDING,
|
9
|
+
) => {
|
10
|
+
if (!sortValue) {
|
11
|
+
return '';
|
12
|
+
}
|
13
|
+
|
14
|
+
if (sortOrder === DESCENDING) {
|
15
|
+
return '-' + sortValue;
|
16
|
+
}
|
17
|
+
|
18
|
+
return sortValue;
|
19
|
+
};
|
20
|
+
|
21
|
+
export const prepareSearchValue = (searchValue = '') => {
|
22
|
+
return new RegExp(escapeRegExp(searchValue), 'i');
|
23
|
+
};
|
@@ -0,0 +1,46 @@
|
|
1
|
+
import {useMemo} from 'react';
|
2
|
+
|
3
|
+
import type {NodesGeneralRequestParams} from '../../store/reducers/nodes/types';
|
4
|
+
import type {ProblemFilterValue} from '../../store/reducers/settings/types';
|
5
|
+
import {ProblemFilterValues} from '../../store/reducers/settings/settings';
|
6
|
+
|
7
|
+
import {HOUR_IN_SECONDS, USE_BACKEND_PARAMS_FOR_TABLES_KEY} from '../constants';
|
8
|
+
import {NodesUptimeFilterValues} from '../nodes';
|
9
|
+
import {useSetting} from './useSetting';
|
10
|
+
|
11
|
+
interface NodesRawRequestParams
|
12
|
+
extends Omit<NodesGeneralRequestParams, 'problems_only' | 'uptime'> {
|
13
|
+
problemFilter?: ProblemFilterValue;
|
14
|
+
nodesUptimeFilter?: NodesUptimeFilterValues;
|
15
|
+
}
|
16
|
+
|
17
|
+
export const useNodesRequestParams = ({
|
18
|
+
filter,
|
19
|
+
problemFilter,
|
20
|
+
nodesUptimeFilter,
|
21
|
+
sortOrder,
|
22
|
+
sortValue,
|
23
|
+
}: NodesRawRequestParams) => {
|
24
|
+
const [useBackendParamsForTables] = useSetting<boolean>(USE_BACKEND_PARAMS_FOR_TABLES_KEY);
|
25
|
+
|
26
|
+
// If backend params are enabled, update params value to use them in fetch request
|
27
|
+
// Otherwise no params will be updated, no hooks that depend on requestParams will be triggered
|
28
|
+
return useMemo(() => {
|
29
|
+
if (useBackendParamsForTables) {
|
30
|
+
const problemsOnly = problemFilter === ProblemFilterValues.PROBLEMS;
|
31
|
+
const uptime =
|
32
|
+
nodesUptimeFilter === NodesUptimeFilterValues.SmallUptime
|
33
|
+
? HOUR_IN_SECONDS
|
34
|
+
: undefined;
|
35
|
+
|
36
|
+
return {
|
37
|
+
filter,
|
38
|
+
problems_only: problemsOnly,
|
39
|
+
uptime,
|
40
|
+
sortOrder,
|
41
|
+
sortValue,
|
42
|
+
};
|
43
|
+
}
|
44
|
+
return undefined;
|
45
|
+
}, [useBackendParamsForTables, filter, problemFilter, nodesUptimeFilter, sortOrder, sortValue]);
|
46
|
+
};
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import {useMemo} from 'react';
|
2
|
+
|
3
|
+
import {OrderType, SortOrder} from '@gravity-ui/react-data-table';
|
4
|
+
import {DESCENDING} from '@gravity-ui/react-data-table/build/esm/lib/constants';
|
5
|
+
|
6
|
+
interface SortParams {
|
7
|
+
sortValue: string | undefined;
|
8
|
+
sortOrder: OrderType | undefined;
|
9
|
+
}
|
10
|
+
|
11
|
+
type HandleSort = (rawValue: SortOrder | SortOrder[] | undefined) => void;
|
12
|
+
|
13
|
+
export const useTableSort = (
|
14
|
+
{sortValue, sortOrder = DESCENDING}: SortParams,
|
15
|
+
onSort: (params: SortParams) => void,
|
16
|
+
): [SortOrder | undefined, HandleSort] => {
|
17
|
+
const sort: SortOrder | undefined = useMemo(() => {
|
18
|
+
if (!sortValue) {
|
19
|
+
return undefined;
|
20
|
+
}
|
21
|
+
|
22
|
+
return {
|
23
|
+
columnId: sortValue,
|
24
|
+
order: sortOrder,
|
25
|
+
};
|
26
|
+
}, [sortValue, sortOrder]);
|
27
|
+
|
28
|
+
const handleSort: HandleSort = (rawValue) => {
|
29
|
+
const value = Array.isArray(rawValue) ? rawValue[0] : rawValue;
|
30
|
+
onSort({
|
31
|
+
sortValue: value?.columnId,
|
32
|
+
sortOrder: value?.order,
|
33
|
+
});
|
34
|
+
};
|
35
|
+
|
36
|
+
return [sort, handleSort];
|
37
|
+
};
|
package/dist/utils/nodes.ts
CHANGED
@@ -2,6 +2,7 @@ import type {TSystemStateInfo} from '../types/api/nodes';
|
|
2
2
|
import type {TNodeInfo} from '../types/api/nodesList';
|
3
3
|
import type {NodesPreparedEntity} from '../store/reducers/nodes/types';
|
4
4
|
import type {NodesMap} from '../types/store/nodesList';
|
5
|
+
import type {ValueOf} from '../types/common';
|
5
6
|
import {EFlag} from '../types/api/enums';
|
6
7
|
|
7
8
|
export enum NodesUptimeFilterValues {
|
@@ -31,3 +32,27 @@ export const prepareNodesMap = (nodesList?: TNodeInfo[]) => {
|
|
31
32
|
return nodesMap;
|
32
33
|
}, new Map());
|
33
34
|
};
|
35
|
+
|
36
|
+
/**
|
37
|
+
* Values to sort /compute v2 and /nodes responses
|
38
|
+
*
|
39
|
+
* For actual values go to:\
|
40
|
+
* /nodes: https://github.com/ydb-platform/ydb/blob/main/ydb/core/viewer/json_nodes.h\
|
41
|
+
* /compute: https://github.com/ydb-platform/ydb/blob/main/ydb/core/viewer/json_compute.h
|
42
|
+
*/
|
43
|
+
export const NODES_SORT_VALUES = {
|
44
|
+
NodeId: 'NodeId',
|
45
|
+
Host: 'Host',
|
46
|
+
DC: 'DC',
|
47
|
+
Rack: 'Rack',
|
48
|
+
Version: 'Version',
|
49
|
+
Uptime: 'Uptime',
|
50
|
+
Memory: 'Memory',
|
51
|
+
CPU: 'CPU',
|
52
|
+
LoadAverage: 'LoadAverage',
|
53
|
+
} as const;
|
54
|
+
|
55
|
+
export type NodesSortValue = ValueOf<typeof NODES_SORT_VALUES>;
|
56
|
+
|
57
|
+
export const isSortableNodesProperty = (value: string): value is NodesSortValue =>
|
58
|
+
Object.values(NODES_SORT_VALUES).includes(value as NodesSortValue);
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "ydb-embedded-ui",
|
3
|
-
"version": "4.
|
3
|
+
"version": "4.14.0",
|
4
4
|
"files": [
|
5
5
|
"dist"
|
6
6
|
],
|
@@ -40,7 +40,7 @@
|
|
40
40
|
"reselect": "4.1.6",
|
41
41
|
"sass": "1.32.8",
|
42
42
|
"web-vitals": "1.1.2",
|
43
|
-
"ydb-ui-components": "^3.
|
43
|
+
"ydb-ui-components": "^3.3.1"
|
44
44
|
},
|
45
45
|
"scripts": {
|
46
46
|
"start": "react-app-rewired start",
|