ydb-embedded-ui 4.13.0 → 4.14.0
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 +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",
|