ydb-embedded-ui 4.15.1 → 4.16.1
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +21 -0
- package/dist/components/BasicNodeViewer/BasicNodeViewer.tsx +2 -2
- package/dist/components/DiagnosticCard/DiagnosticCard.scss +5 -0
- package/dist/components/DiagnosticCard/DiagnosticCard.tsx +17 -0
- package/dist/components/EntityStatus/EntityStatus.js +2 -2
- package/dist/components/EntityStatus/EntityStatus.scss +15 -0
- package/dist/components/NodeHostWrapper/NodeHostWrapper.tsx +2 -1
- package/dist/containers/Cluster/Cluster.tsx +2 -2
- package/dist/containers/Node/Node.tsx +3 -1
- package/dist/containers/Node/NodeStructure/NodeStructure.tsx +3 -1
- package/dist/containers/Node/NodeStructure/Pdisk.tsx +2 -2
- package/dist/containers/Nodes/Nodes.tsx +3 -2
- package/dist/containers/Nodes/getNodesColumns.tsx +3 -1
- package/dist/containers/Storage/Storage.tsx +3 -2
- package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +2 -2
- package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.tsx +5 -12
- package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +3 -2
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Details/Details.tsx +20 -11
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.scss +14 -1
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.tsx +26 -37
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/Preview.tsx +37 -25
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +155 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json +7 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/index.ts +11 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/ru.json +7 -0
- package/dist/containers/Tenant/ObjectGeneral/ObjectGeneral.tsx +3 -2
- package/dist/containers/Tenant/Query/QueriesHistory/QueriesHistory.scss +4 -1
- package/dist/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx +42 -11
- package/dist/containers/Tenant/Query/QueryEditor/QueryEditor.js +1 -1
- package/dist/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.tsx +11 -7
- package/dist/containers/Tenant/Query/i18n/en.json +3 -0
- package/dist/containers/Tenant/Query/i18n/ru.json +3 -0
- package/dist/containers/Tenant/Tenant.tsx +3 -2
- package/dist/containers/UserSettings/Setting.tsx +9 -2
- package/dist/containers/UserSettings/i18n/en.json +5 -1
- package/dist/containers/UserSettings/i18n/ru.json +5 -1
- package/dist/containers/UserSettings/settings.ts +25 -0
- package/dist/services/api.ts +16 -16
- package/dist/store/reducers/executeQuery.ts +33 -7
- package/dist/store/reducers/explainQuery.ts +12 -4
- package/dist/store/reducers/healthcheckInfo.ts +27 -11
- package/dist/store/reducers/settings/settings.ts +4 -10
- package/dist/store/reducers/tenant/tenant.ts +19 -1
- package/dist/store/reducers/tenant/types.ts +3 -1
- package/dist/store/reducers/tenants/selectors.ts +1 -1
- package/dist/store/reducers/tenants/utils.ts +2 -2
- package/dist/types/additionalProps.ts +8 -1
- package/dist/types/api/tenant.ts +19 -20
- package/dist/types/store/executeQuery.ts +11 -1
- package/dist/types/store/query.ts +2 -1
- package/dist/utils/autofetcher.ts +7 -7
- package/dist/utils/constants.ts +2 -1
- package/dist/utils/hooks/i18n/en.json +1 -1
- package/dist/utils/hooks/i18n/ru.json +1 -1
- package/dist/utils/hooks/useQueryModes.ts +4 -2
- package/dist/utils/i18n/i18n.ts +10 -4
- package/dist/utils/nodes.ts +0 -6
- package/dist/utils/query.ts +14 -0
- package/dist/utils/settings.ts +10 -0
- package/package.json +1 -1
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/PreviewItem/PreviewItem.tsx +0 -33
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/PreviewItem/index.ts +0 -1
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.js +0 -213
@@ -1,13 +1,14 @@
|
|
1
1
|
import cn from 'bem-cn-lite';
|
2
2
|
|
3
|
-
import {Button, Icon} from '@gravity-ui/uikit';
|
3
|
+
import {Button, Icon, Link} from '@gravity-ui/uikit';
|
4
4
|
|
5
5
|
import updateArrow from '../../../../../assets/icons/update-arrow.svg';
|
6
6
|
|
7
|
-
import {SelfCheckResult} from '../../../../../types/api/healthcheck';
|
8
|
-
import type {
|
9
|
-
|
10
|
-
import
|
7
|
+
import {SelfCheckResult, type StatusFlag} from '../../../../../types/api/healthcheck';
|
8
|
+
import type {IResponseError} from '../../../../../types/api/error';
|
9
|
+
import {DiagnosticCard} from '../../../../../components/DiagnosticCard/DiagnosticCard';
|
10
|
+
import EntityStatus from '../../../../../components/EntityStatus/EntityStatus';
|
11
|
+
import {ResponseError} from '../../../../../components/Errors/ResponseError';
|
11
12
|
|
12
13
|
import i18n from '../i18n';
|
13
14
|
|
@@ -15,21 +16,18 @@ const b = cn('healthcheck');
|
|
15
16
|
|
16
17
|
interface PreviewProps {
|
17
18
|
selfCheckResult: SelfCheckResult;
|
18
|
-
|
19
|
+
issuesStatistics?: [StatusFlag, number][];
|
19
20
|
loading?: boolean;
|
20
|
-
onShowMore?:
|
21
|
+
onShowMore?: VoidFunction;
|
21
22
|
onUpdate: VoidFunction;
|
23
|
+
error?: IResponseError;
|
22
24
|
}
|
23
25
|
|
24
26
|
export const Preview = (props: PreviewProps) => {
|
25
|
-
const {selfCheckResult,
|
27
|
+
const {selfCheckResult, issuesStatistics, loading, onShowMore, onUpdate, error} = props;
|
26
28
|
|
27
29
|
const isStatusOK = selfCheckResult === SelfCheckResult.GOOD;
|
28
30
|
|
29
|
-
if (!issuesTrees) {
|
30
|
-
return null;
|
31
|
-
}
|
32
|
-
|
33
31
|
const renderStatus = () => {
|
34
32
|
const modifier = selfCheckResult.toLowerCase();
|
35
33
|
|
@@ -46,26 +44,40 @@ export const Preview = (props: PreviewProps) => {
|
|
46
44
|
);
|
47
45
|
};
|
48
46
|
|
49
|
-
const
|
47
|
+
const renderContent = () => {
|
48
|
+
if (error) {
|
49
|
+
return <ResponseError error={error} defaultMessage={i18n('no-data')} />;
|
50
|
+
}
|
51
|
+
|
50
52
|
return (
|
51
53
|
<div className={b('preview-content')}>
|
52
|
-
{isStatusOK
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
54
|
+
{isStatusOK || !issuesStatistics || !issuesStatistics.length ? (
|
55
|
+
i18n('status_message.ok')
|
56
|
+
) : (
|
57
|
+
<>
|
58
|
+
<div>Issues:</div>
|
59
|
+
<div className={b('issues-statistics')}>
|
60
|
+
{issuesStatistics.map(([status, count]) => (
|
61
|
+
<EntityStatus
|
62
|
+
key={status}
|
63
|
+
mode="icons"
|
64
|
+
status={status}
|
65
|
+
label={count.toString()}
|
66
|
+
size="l"
|
67
|
+
/>
|
68
|
+
))}
|
69
|
+
</div>
|
70
|
+
<Link onClick={() => onShowMore?.()}>{i18n('label.show-details')}</Link>
|
71
|
+
</>
|
72
|
+
)}
|
61
73
|
</div>
|
62
74
|
);
|
63
75
|
};
|
64
76
|
|
65
77
|
return (
|
66
|
-
<
|
78
|
+
<DiagnosticCard className={b('preview')}>
|
67
79
|
{renderStatus()}
|
68
|
-
{
|
69
|
-
</
|
80
|
+
{renderContent()}
|
81
|
+
</DiagnosticCard>
|
70
82
|
);
|
71
83
|
};
|
@@ -0,0 +1,155 @@
|
|
1
|
+
import cn from 'bem-cn-lite';
|
2
|
+
import {useCallback} from 'react';
|
3
|
+
import {useDispatch} from 'react-redux';
|
4
|
+
|
5
|
+
import {Loader} from '@gravity-ui/uikit';
|
6
|
+
|
7
|
+
import {InfoViewer} from '../../../../components/InfoViewer';
|
8
|
+
import {PoolUsage} from '../../../../components/PoolUsage/PoolUsage';
|
9
|
+
import {Tablet} from '../../../../components/Tablet';
|
10
|
+
import EntityStatus from '../../../../components/EntityStatus/EntityStatus';
|
11
|
+
import {formatCPU} from '../../../../utils';
|
12
|
+
import {TABLET_STATES, TENANT_DEFAULT_TITLE} from '../../../../utils/constants';
|
13
|
+
import {bytesToGB} from '../../../../utils/utils';
|
14
|
+
import {mapDatabaseTypeToDBName} from '../../utils/schema';
|
15
|
+
import {useAutofetcher, useTypedSelector} from '../../../../utils/hooks';
|
16
|
+
import type {ETabletVolatileState} from '../../../../types/api/tenant';
|
17
|
+
import type {AdditionalTenantsProps} from '../../../../types/additionalProps';
|
18
|
+
import {getTenantInfo, setDataWasNotLoaded} from '../../../../store/reducers/tenant/tenant';
|
19
|
+
|
20
|
+
import i18n from './i18n';
|
21
|
+
import './TenantOverview.scss';
|
22
|
+
|
23
|
+
const b = cn('tenant-overview');
|
24
|
+
|
25
|
+
interface TenantOverviewProps {
|
26
|
+
tenantName: string;
|
27
|
+
additionalTenantInfo?: AdditionalTenantsProps;
|
28
|
+
}
|
29
|
+
|
30
|
+
export function TenantOverview({tenantName, additionalTenantInfo}: TenantOverviewProps) {
|
31
|
+
const {tenant, loading, wasLoaded} = useTypedSelector((state) => state.tenant);
|
32
|
+
const {autorefresh} = useTypedSelector((state) => state.schema);
|
33
|
+
const dispatch = useDispatch();
|
34
|
+
const fetchTenant = useCallback(
|
35
|
+
(isBackground = true) => {
|
36
|
+
if (!isBackground) {
|
37
|
+
dispatch(setDataWasNotLoaded());
|
38
|
+
}
|
39
|
+
dispatch(getTenantInfo({path: tenantName}));
|
40
|
+
},
|
41
|
+
[dispatch, tenantName],
|
42
|
+
);
|
43
|
+
|
44
|
+
useAutofetcher(fetchTenant, [fetchTenant], autorefresh);
|
45
|
+
|
46
|
+
const {
|
47
|
+
Metrics = {},
|
48
|
+
PoolStats,
|
49
|
+
StateStats = [],
|
50
|
+
MemoryUsed,
|
51
|
+
Name,
|
52
|
+
State,
|
53
|
+
CoresUsed,
|
54
|
+
StorageGroups,
|
55
|
+
StorageAllocatedSize,
|
56
|
+
Type,
|
57
|
+
SystemTablets,
|
58
|
+
} = tenant || {};
|
59
|
+
|
60
|
+
const tenantType = mapDatabaseTypeToDBName(Type);
|
61
|
+
const memoryRaw = MemoryUsed ?? Metrics.Memory;
|
62
|
+
|
63
|
+
const memory = (memoryRaw && bytesToGB(memoryRaw)) || i18n('no-data');
|
64
|
+
const storage = (Metrics.Storage && bytesToGB(Metrics.Storage)) || i18n('no-data');
|
65
|
+
const storageGroups = StorageGroups ?? i18n('no-data');
|
66
|
+
const blobStorage =
|
67
|
+
(StorageAllocatedSize && bytesToGB(StorageAllocatedSize)) || i18n('no-data');
|
68
|
+
const storageEfficiency =
|
69
|
+
Metrics.Storage && StorageAllocatedSize
|
70
|
+
? `${((parseInt(Metrics.Storage) * 100) / parseInt(StorageAllocatedSize)).toFixed(2)}%`
|
71
|
+
: i18n('no-data');
|
72
|
+
|
73
|
+
const cpuRaw = CoresUsed !== undefined ? Number(CoresUsed) * 1_000_000 : Metrics.CPU;
|
74
|
+
|
75
|
+
const cpu = formatCPU(cpuRaw);
|
76
|
+
|
77
|
+
const metricsInfo = [
|
78
|
+
{label: 'Type', value: Type},
|
79
|
+
{label: 'Memory', value: memory},
|
80
|
+
{label: 'CPU', value: cpu},
|
81
|
+
{label: 'Tablet storage', value: storage},
|
82
|
+
{label: 'Storage groups', value: storageGroups},
|
83
|
+
{label: 'Blob storage', value: blobStorage},
|
84
|
+
{label: 'Storage efficiency', value: storageEfficiency},
|
85
|
+
];
|
86
|
+
|
87
|
+
const tabletsInfo = StateStats.filter(
|
88
|
+
(item): item is {VolatileState: ETabletVolatileState; Count: number} => {
|
89
|
+
return item.VolatileState !== undefined && item.Count !== undefined;
|
90
|
+
},
|
91
|
+
).map((info) => {
|
92
|
+
return {label: TABLET_STATES[info.VolatileState], value: info.Count};
|
93
|
+
});
|
94
|
+
|
95
|
+
const renderName = () => {
|
96
|
+
return (
|
97
|
+
<div className={b('tenant-name-wrapper')}>
|
98
|
+
<EntityStatus
|
99
|
+
status={State}
|
100
|
+
name={Name || TENANT_DEFAULT_TITLE}
|
101
|
+
withLeftTrim
|
102
|
+
hasClipboardButton={Boolean(tenant)}
|
103
|
+
clipboardButtonAlwaysVisible
|
104
|
+
/>
|
105
|
+
</div>
|
106
|
+
);
|
107
|
+
};
|
108
|
+
|
109
|
+
if (loading && !wasLoaded) {
|
110
|
+
return (
|
111
|
+
<div className={b('loader')}>
|
112
|
+
<Loader size="m" />
|
113
|
+
</div>
|
114
|
+
);
|
115
|
+
}
|
116
|
+
|
117
|
+
return (
|
118
|
+
<div className={b()}>
|
119
|
+
<div className={b('top-label')}>{tenantType}</div>
|
120
|
+
<div className={b('top')}>
|
121
|
+
{renderName()}
|
122
|
+
{additionalTenantInfo?.getMonitoringLink?.(Name, Type)}
|
123
|
+
</div>
|
124
|
+
<div className={b('system-tablets')}>
|
125
|
+
{SystemTablets &&
|
126
|
+
SystemTablets.map((tablet, tabletIndex) => (
|
127
|
+
<Tablet key={tabletIndex} tablet={tablet} tenantName={Name} />
|
128
|
+
))}
|
129
|
+
</div>
|
130
|
+
<div className={b('common-info')}>
|
131
|
+
<div>
|
132
|
+
<div className={b('section-title')}>{i18n('title.pools')}</div>
|
133
|
+
{PoolStats ? (
|
134
|
+
<div className={b('section', {pools: true})}>
|
135
|
+
{PoolStats.map((pool, poolIndex) => (
|
136
|
+
<PoolUsage key={poolIndex} data={pool} />
|
137
|
+
))}
|
138
|
+
</div>
|
139
|
+
) : (
|
140
|
+
<div className="error">{i18n('no-pools-data')}</div>
|
141
|
+
)}
|
142
|
+
</div>
|
143
|
+
<InfoViewer
|
144
|
+
title={i18n('title.metrics')}
|
145
|
+
className={b('section', {metrics: true})}
|
146
|
+
info={metricsInfo}
|
147
|
+
/>
|
148
|
+
|
149
|
+
<div className={b('section')}>
|
150
|
+
<InfoViewer info={tabletsInfo} title="Tablets" />
|
151
|
+
</div>
|
152
|
+
</div>
|
153
|
+
</div>
|
154
|
+
);
|
155
|
+
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import {i18n, Lang} from '../../../../../utils/i18n';
|
2
|
+
|
3
|
+
import en from './en.json';
|
4
|
+
import ru from './ru.json';
|
5
|
+
|
6
|
+
const COMPONENT = 'ydb-diagnostics-tenant-overview';
|
7
|
+
|
8
|
+
i18n.registerKeyset(Lang.En, COMPONENT, en);
|
9
|
+
i18n.registerKeyset(Lang.Ru, COMPONENT, ru);
|
10
|
+
|
11
|
+
export default i18n.keyset(COMPONENT);
|
@@ -4,6 +4,7 @@ import cn from 'bem-cn-lite';
|
|
4
4
|
import {useThemeValue} from '@gravity-ui/uikit';
|
5
5
|
|
6
6
|
import type {EPathType} from '../../../types/api/schema';
|
7
|
+
import type {AdditionalTenantsProps, AdditionalNodesProps} from '../../../types/additionalProps';
|
7
8
|
import {TENANT_PAGES_IDS} from '../../../store/reducers/tenant/constants';
|
8
9
|
import {useSetting} from '../../../utils/hooks';
|
9
10
|
import {TENANT_INITIAL_PAGE_KEY} from '../../../utils/constants';
|
@@ -18,8 +19,8 @@ const b = cn('object-general');
|
|
18
19
|
|
19
20
|
interface ObjectGeneralProps {
|
20
21
|
type?: EPathType;
|
21
|
-
additionalTenantInfo?:
|
22
|
-
additionalNodesInfo?:
|
22
|
+
additionalTenantInfo?: AdditionalTenantsProps;
|
23
|
+
additionalNodesInfo?: AdditionalNodesProps;
|
23
24
|
}
|
24
25
|
|
25
26
|
function ObjectGeneral(props: ObjectGeneralProps) {
|
@@ -3,10 +3,13 @@ import block from 'bem-cn-lite';
|
|
3
3
|
|
4
4
|
import DataTable, {Column} from '@gravity-ui/react-data-table';
|
5
5
|
|
6
|
+
import type {QueryInHistory} from '../../../../types/store/executeQuery';
|
6
7
|
import {TruncatedQuery} from '../../../../components/TruncatedQuery/TruncatedQuery';
|
7
8
|
import {setQueryTab} from '../../../../store/reducers/tenant/tenant';
|
9
|
+
import {selectQueriesHistory} from '../../../../store/reducers/executeQuery';
|
8
10
|
import {TENANT_QUERY_TABS_ID} from '../../../../store/reducers/tenant/constants';
|
9
|
-
import {useTypedSelector} from '../../../../utils/hooks';
|
11
|
+
import {useQueryModes, useTypedSelector} from '../../../../utils/hooks';
|
12
|
+
import {QUERY_MODES, QUERY_MODES_TITLES, QUERY_SYNTAX} from '../../../../utils/query';
|
10
13
|
import {MAX_QUERY_HEIGHT, QUERY_TABLE_SETTINGS} from '../../utils/constants';
|
11
14
|
|
12
15
|
import i18n from '../i18n';
|
@@ -22,24 +25,51 @@ interface QueriesHistoryProps {
|
|
22
25
|
function QueriesHistory({changeUserInput}: QueriesHistoryProps) {
|
23
26
|
const dispatch = useDispatch();
|
24
27
|
|
25
|
-
const
|
28
|
+
const [queryMode, setQueryMode] = useQueryModes();
|
29
|
+
|
30
|
+
const queriesHistory = useTypedSelector(selectQueriesHistory);
|
26
31
|
const reversedHistory = [...queriesHistory].reverse();
|
27
32
|
|
28
|
-
const onQueryClick = (
|
29
|
-
|
30
|
-
|
33
|
+
const onQueryClick = (query: QueryInHistory) => {
|
34
|
+
let isQueryModeSet = true;
|
35
|
+
|
36
|
+
if (query.syntax === QUERY_SYNTAX.pg && queryMode !== QUERY_MODES.pg) {
|
37
|
+
isQueryModeSet = setQueryMode(
|
38
|
+
QUERY_MODES.pg,
|
39
|
+
i18n('history.cannot-set-mode', {mode: QUERY_MODES_TITLES[QUERY_MODES.pg]}),
|
40
|
+
);
|
41
|
+
} else if (query.syntax !== QUERY_SYNTAX.pg && queryMode === QUERY_MODES.pg) {
|
42
|
+
// Set query mode for queries with yql syntax
|
43
|
+
isQueryModeSet = setQueryMode(QUERY_MODES.script);
|
44
|
+
}
|
45
|
+
|
46
|
+
if (isQueryModeSet) {
|
47
|
+
changeUserInput({input: query.queryText});
|
48
|
+
dispatch(setQueryTab(TENANT_QUERY_TABS_ID.newQuery));
|
49
|
+
}
|
31
50
|
};
|
32
51
|
|
33
|
-
const columns: Column<
|
52
|
+
const columns: Column<QueryInHistory>[] = [
|
34
53
|
{
|
35
54
|
name: 'queryText',
|
36
55
|
header: 'Query Text',
|
37
|
-
render: ({row
|
38
|
-
|
39
|
-
<
|
40
|
-
|
41
|
-
|
56
|
+
render: ({row}) => {
|
57
|
+
return (
|
58
|
+
<div className={b('query')}>
|
59
|
+
<TruncatedQuery value={row.queryText} maxQueryHeight={MAX_QUERY_HEIGHT} />
|
60
|
+
</div>
|
61
|
+
);
|
62
|
+
},
|
63
|
+
sortable: false,
|
64
|
+
},
|
65
|
+
{
|
66
|
+
name: 'syntax',
|
67
|
+
header: 'Syntax',
|
68
|
+
render: ({row}) => {
|
69
|
+
return row.syntax === QUERY_SYNTAX.pg ? 'PostgreSQL' : 'YQL';
|
70
|
+
},
|
42
71
|
sortable: false,
|
72
|
+
width: 200,
|
43
73
|
},
|
44
74
|
];
|
45
75
|
|
@@ -52,6 +82,7 @@ function QueriesHistory({changeUserInput}: QueriesHistoryProps) {
|
|
52
82
|
settings={QUERY_TABLE_SETTINGS}
|
53
83
|
emptyDataMessage={i18n('history.empty')}
|
54
84
|
onRowClick={(row) => onQueryClick(row)}
|
85
|
+
rowClassName={() => b('table-row')}
|
55
86
|
/>
|
56
87
|
</div>
|
57
88
|
);
|
@@ -264,7 +264,7 @@ function QueryEditor(props) {
|
|
264
264
|
|
265
265
|
const {queries, currentIndex} = history;
|
266
266
|
if (input !== queries[currentIndex]) {
|
267
|
-
saveQueryToHistory(input);
|
267
|
+
saveQueryToHistory(input, mode);
|
268
268
|
}
|
269
269
|
dispatchResultVisibilityState(PaneVisibilityActionTypes.triggerExpand);
|
270
270
|
};
|
@@ -4,7 +4,7 @@ import {Button, ButtonView, DropdownMenu} from '@gravity-ui/uikit';
|
|
4
4
|
import {useMemo} from 'react';
|
5
5
|
|
6
6
|
import type {QueryAction, QueryMode} from '../../../../types/store/query';
|
7
|
-
import {QUERY_MODES} from '../../../../utils/query';
|
7
|
+
import {QUERY_MODES, QUERY_MODES_TITLES} from '../../../../utils/query';
|
8
8
|
import {Icon} from '../../../../components/Icon';
|
9
9
|
import {LabelWithPopover} from '../../../../components/LabelWithPopover';
|
10
10
|
|
@@ -21,32 +21,36 @@ const b = block('ydb-query-editor-controls');
|
|
21
21
|
|
22
22
|
const OldQueryModeSelectorOptions = {
|
23
23
|
[QUERY_MODES.script]: {
|
24
|
-
title:
|
24
|
+
title: QUERY_MODES_TITLES[QUERY_MODES.script],
|
25
25
|
description: i18n('method-description.script'),
|
26
26
|
},
|
27
27
|
[QUERY_MODES.scan]: {
|
28
|
-
title:
|
28
|
+
title: QUERY_MODES_TITLES[QUERY_MODES.scan],
|
29
29
|
description: i18n('method-description.scan'),
|
30
30
|
},
|
31
31
|
} as const;
|
32
32
|
|
33
33
|
const QueryModeSelectorOptions = {
|
34
34
|
[QUERY_MODES.script]: {
|
35
|
-
title:
|
35
|
+
title: QUERY_MODES_TITLES[QUERY_MODES.script],
|
36
36
|
description: i18n('method-description.script'),
|
37
37
|
},
|
38
38
|
[QUERY_MODES.scan]: {
|
39
|
-
title:
|
39
|
+
title: QUERY_MODES_TITLES[QUERY_MODES.scan],
|
40
40
|
description: i18n('method-description.scan'),
|
41
41
|
},
|
42
42
|
[QUERY_MODES.data]: {
|
43
|
-
title:
|
43
|
+
title: QUERY_MODES_TITLES[QUERY_MODES.data],
|
44
44
|
description: i18n('method-description.data'),
|
45
45
|
},
|
46
46
|
[QUERY_MODES.query]: {
|
47
|
-
title:
|
47
|
+
title: QUERY_MODES_TITLES[QUERY_MODES.query],
|
48
48
|
description: i18n('method-description.query'),
|
49
49
|
},
|
50
|
+
[QUERY_MODES.pg]: {
|
51
|
+
title: QUERY_MODES_TITLES[QUERY_MODES.pg],
|
52
|
+
description: i18n('method-description.pg'),
|
53
|
+
},
|
50
54
|
} as const;
|
51
55
|
|
52
56
|
interface QueryEditorControlsProps {
|
@@ -8,6 +8,8 @@
|
|
8
8
|
"history.empty": "History is empty",
|
9
9
|
"saved.empty": "There are no saved queries",
|
10
10
|
|
11
|
+
"history.cannot-set-mode": "This query is available only with '{{mode}}' query mode. You need to turn in additional query modes in settings to enable it",
|
12
|
+
|
11
13
|
"delete-dialog.header": "Delete query",
|
12
14
|
"delete-dialog.question": "Are you sure you want to delete query",
|
13
15
|
"delete-dialog.delete": "Delete",
|
@@ -21,6 +23,7 @@
|
|
21
23
|
"method-description.scan": "Read-only queries, potentially reading a lot of data.\nAPI call: table.ExecuteScan",
|
22
24
|
"method-description.data": "DML queries for changing and fetching data in serialization mode.\nAPI call: table.executeDataQuery",
|
23
25
|
"method-description.query": "Any query. An experimental API call supposed to replace all existing methods.\nAPI Call: query.ExecuteScript",
|
26
|
+
"method-description.pg": "Queries in postgresql syntax.\nAPI call: query.ExecuteScript",
|
24
27
|
|
25
28
|
"query-duration.description": "Duration of server-side query execution"
|
26
29
|
}
|
@@ -8,6 +8,8 @@
|
|
8
8
|
"history.empty": "История пуста",
|
9
9
|
"saved.empty": "Нет сохраненных запросов",
|
10
10
|
|
11
|
+
"history.cannot-set-mode": "Этот запрос доступен только в режиме '{{mode}}'. Вам необходимо включить дополнительные режимы выполнения запросов в настройках",
|
12
|
+
|
11
13
|
"delete-dialog.header": "Удалить запрос",
|
12
14
|
"delete-dialog.question": "Вы уверены что хотите удалить запрос",
|
13
15
|
"delete-dialog.delete": "Удалить",
|
@@ -21,6 +23,7 @@
|
|
21
23
|
"method-description.scan": "Только читающие запросы, потенциально читающие много данных.\nAPI call: table.ExecuteScan",
|
22
24
|
"method-description.data": "DML-запросы для изменения и выборки данных в режиме изоляции Serializable.\nAPI call: table.executeDataQuery",
|
23
25
|
"method-description.query": "Любые запросы. Экспериментальный перспективный метод, который в будущем заменит все остальные.\nAPI call: query.ExecuteScript",
|
26
|
+
"method-description.pg": "Запросы в синтаксисе postgresql.\nAPI call: query.ExecuteScript",
|
24
27
|
|
25
28
|
"query-duration.description": "Время выполнения запроса на стороне сервера"
|
26
29
|
}
|
@@ -5,6 +5,7 @@ import {useLocation} from 'react-router';
|
|
5
5
|
import qs from 'qs';
|
6
6
|
|
7
7
|
import type {TEvDescribeSchemeResult} from '../../types/api/schema';
|
8
|
+
import type {AdditionalTenantsProps, AdditionalNodesProps} from '../../types/additionalProps';
|
8
9
|
|
9
10
|
import {DEFAULT_IS_TENANT_SUMMARY_COLLAPSED, DEFAULT_SIZE_TENANT_KEY} from '../../utils/constants';
|
10
11
|
import {useTypedSelector} from '../../utils/hooks';
|
@@ -37,8 +38,8 @@ const initialTenantSummaryState = {
|
|
37
38
|
};
|
38
39
|
|
39
40
|
interface TenantProps {
|
40
|
-
additionalTenantInfo?:
|
41
|
-
additionalNodesInfo?:
|
41
|
+
additionalTenantInfo?: AdditionalTenantsProps;
|
42
|
+
additionalNodesInfo?: AdditionalNodesProps;
|
42
43
|
}
|
43
44
|
|
44
45
|
function Tenant(props: TenantProps) {
|
@@ -18,6 +18,7 @@ export interface SettingProps {
|
|
18
18
|
helpPopoverContent?: ReactNode;
|
19
19
|
options?: {value: string; content: string}[];
|
20
20
|
defaultValue?: unknown;
|
21
|
+
onValueUpdate?: VoidFunction;
|
21
22
|
}
|
22
23
|
|
23
24
|
export const Setting = ({
|
@@ -27,9 +28,15 @@ export const Setting = ({
|
|
27
28
|
helpPopoverContent,
|
28
29
|
options,
|
29
30
|
defaultValue,
|
31
|
+
onValueUpdate,
|
30
32
|
}: SettingProps) => {
|
31
33
|
const [settingValue, setValue] = useSetting(settingKey, defaultValue);
|
32
34
|
|
35
|
+
const onUpdate = (value: unknown) => {
|
36
|
+
setValue(value);
|
37
|
+
onValueUpdate?.();
|
38
|
+
};
|
39
|
+
|
33
40
|
const renderTitleComponent = (value: ReactNode) => {
|
34
41
|
if (helpPopoverContent) {
|
35
42
|
return (
|
@@ -48,7 +55,7 @@ export const Setting = ({
|
|
48
55
|
const getSettingsElement = (elementType: SettingsElementType) => {
|
49
56
|
switch (elementType) {
|
50
57
|
case 'switch': {
|
51
|
-
return <Switch checked={Boolean(settingValue)} onUpdate={
|
58
|
+
return <Switch checked={Boolean(settingValue)} onUpdate={onUpdate} />;
|
52
59
|
}
|
53
60
|
|
54
61
|
case 'radio': {
|
@@ -57,7 +64,7 @@ export const Setting = ({
|
|
57
64
|
}
|
58
65
|
|
59
66
|
return (
|
60
|
-
<RadioButton value={String(settingValue)} onUpdate={
|
67
|
+
<RadioButton value={String(settingValue)} onUpdate={onUpdate}>
|
61
68
|
{options.map(({value, content}) => {
|
62
69
|
return (
|
63
70
|
<RadioButton.Option value={value} key={value}>
|
@@ -10,6 +10,10 @@
|
|
10
10
|
"settings.theme.option-light": "Light",
|
11
11
|
"settings.theme.option-system": "System",
|
12
12
|
|
13
|
+
"settings.language.title": "Interface language",
|
14
|
+
"settings.language.option-russian": "Russian",
|
15
|
+
"settings.language.option-english": "English",
|
16
|
+
|
13
17
|
"settings.invertedDisks.title": "Inverted disks space indicators",
|
14
18
|
|
15
19
|
"settings.useNodesEndpoint.title": "Break the Nodes tab in Diagnostics",
|
@@ -19,5 +23,5 @@
|
|
19
23
|
"settings.useBackendParamsForTables.popover": "Filter and sort Nodes and Storage tables with request params. May increase performance, but could causes additional fetches and longer loading time on older versions",
|
20
24
|
|
21
25
|
"settings.enableAdditionalQueryModes.title": "Enable additional query modes",
|
22
|
-
"settings.enableAdditionalQueryModes.popover": "Adds 'Data'
|
26
|
+
"settings.enableAdditionalQueryModes.popover": "Adds 'Data', 'YQL - QueryService' and 'PostgreSQL' modes. May not work on some versions"
|
23
27
|
}
|
@@ -10,6 +10,10 @@
|
|
10
10
|
"settings.theme.option-light": "Светлая",
|
11
11
|
"settings.theme.option-system": "Системная",
|
12
12
|
|
13
|
+
"settings.language.title": "Язык интерфейса",
|
14
|
+
"settings.language.option-russian": "Русский",
|
15
|
+
"settings.language.option-english": "English",
|
16
|
+
|
13
17
|
"settings.invertedDisks.title": "Инвертированные индикаторы места на дисках",
|
14
18
|
|
15
19
|
"settings.useNodesEndpoint.title": "Сломать вкладку Nodes в диагностике",
|
@@ -19,5 +23,5 @@
|
|
19
23
|
"settings.useBackendParamsForTables.popover": "Добавляет фильтрацию и сортировку таблиц Nodes и Storage с использованием параметров запроса. Может улушить производительность, но на старых версиях может привести к дополнительным запросам и большему времени ожидания загрузки",
|
20
24
|
|
21
25
|
"settings.enableAdditionalQueryModes.title": "Включить дополнительные режимы выполнения запросов",
|
22
|
-
"settings.enableAdditionalQueryModes.popover": "Добавляет режимы 'Data'
|
26
|
+
"settings.enableAdditionalQueryModes.popover": "Добавляет режимы 'Data', 'YQL - QueryService' и 'PostgreSQL'. Может работать некорректно на некоторых версиях"
|
23
27
|
}
|
@@ -6,10 +6,12 @@ import flaskIcon from '../../assets/icons/flask.svg';
|
|
6
6
|
import {
|
7
7
|
ENABLE_ADDITIONAL_QUERY_MODES,
|
8
8
|
INVERTED_DISKS_KEY,
|
9
|
+
LANGUAGE_KEY,
|
9
10
|
THEME_KEY,
|
10
11
|
USE_BACKEND_PARAMS_FOR_TABLES_KEY,
|
11
12
|
USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY,
|
12
13
|
} from '../../utils/constants';
|
14
|
+
import {Lang, defaultLang} from '../../utils/i18n';
|
13
15
|
|
14
16
|
import type {SettingProps} from './Setting';
|
15
17
|
import i18n from './i18n';
|
@@ -50,6 +52,29 @@ export const themeSetting: SettingProps = {
|
|
50
52
|
type: 'radio',
|
51
53
|
options: themeOptions,
|
52
54
|
};
|
55
|
+
|
56
|
+
const languageOptions = [
|
57
|
+
{
|
58
|
+
value: Lang.Ru,
|
59
|
+
content: i18n('settings.language.option-russian'),
|
60
|
+
},
|
61
|
+
{
|
62
|
+
value: Lang.En,
|
63
|
+
content: i18n('settings.language.option-english'),
|
64
|
+
},
|
65
|
+
];
|
66
|
+
|
67
|
+
export const languageSetting: SettingProps = {
|
68
|
+
settingKey: LANGUAGE_KEY,
|
69
|
+
title: i18n('settings.language.title'),
|
70
|
+
type: 'radio',
|
71
|
+
options: languageOptions,
|
72
|
+
defaultValue: defaultLang,
|
73
|
+
onValueUpdate: () => {
|
74
|
+
window.location.reload();
|
75
|
+
},
|
76
|
+
};
|
77
|
+
|
53
78
|
export const invertedDisksSetting: SettingProps = {
|
54
79
|
settingKey: INVERTED_DISKS_KEY,
|
55
80
|
title: i18n('settings.invertedDisks.title'),
|