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.
Files changed (63) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/components/BasicNodeViewer/BasicNodeViewer.tsx +2 -2
  3. package/dist/components/DiagnosticCard/DiagnosticCard.scss +5 -0
  4. package/dist/components/DiagnosticCard/DiagnosticCard.tsx +17 -0
  5. package/dist/components/EntityStatus/EntityStatus.js +2 -2
  6. package/dist/components/EntityStatus/EntityStatus.scss +15 -0
  7. package/dist/components/NodeHostWrapper/NodeHostWrapper.tsx +2 -1
  8. package/dist/containers/Cluster/Cluster.tsx +2 -2
  9. package/dist/containers/Node/Node.tsx +3 -1
  10. package/dist/containers/Node/NodeStructure/NodeStructure.tsx +3 -1
  11. package/dist/containers/Node/NodeStructure/Pdisk.tsx +2 -2
  12. package/dist/containers/Nodes/Nodes.tsx +3 -2
  13. package/dist/containers/Nodes/getNodesColumns.tsx +3 -1
  14. package/dist/containers/Storage/Storage.tsx +3 -2
  15. package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +2 -2
  16. package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.tsx +5 -12
  17. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +3 -2
  18. package/dist/containers/Tenant/Diagnostics/Healthcheck/Details/Details.tsx +20 -11
  19. package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.scss +14 -1
  20. package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.tsx +26 -37
  21. package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/Preview.tsx +37 -25
  22. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +155 -0
  23. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json +7 -0
  24. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/index.ts +11 -0
  25. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/ru.json +7 -0
  26. package/dist/containers/Tenant/ObjectGeneral/ObjectGeneral.tsx +3 -2
  27. package/dist/containers/Tenant/Query/QueriesHistory/QueriesHistory.scss +4 -1
  28. package/dist/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx +42 -11
  29. package/dist/containers/Tenant/Query/QueryEditor/QueryEditor.js +1 -1
  30. package/dist/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.tsx +11 -7
  31. package/dist/containers/Tenant/Query/i18n/en.json +3 -0
  32. package/dist/containers/Tenant/Query/i18n/ru.json +3 -0
  33. package/dist/containers/Tenant/Tenant.tsx +3 -2
  34. package/dist/containers/UserSettings/Setting.tsx +9 -2
  35. package/dist/containers/UserSettings/i18n/en.json +5 -1
  36. package/dist/containers/UserSettings/i18n/ru.json +5 -1
  37. package/dist/containers/UserSettings/settings.ts +25 -0
  38. package/dist/services/api.ts +16 -16
  39. package/dist/store/reducers/executeQuery.ts +33 -7
  40. package/dist/store/reducers/explainQuery.ts +12 -4
  41. package/dist/store/reducers/healthcheckInfo.ts +27 -11
  42. package/dist/store/reducers/settings/settings.ts +4 -10
  43. package/dist/store/reducers/tenant/tenant.ts +19 -1
  44. package/dist/store/reducers/tenant/types.ts +3 -1
  45. package/dist/store/reducers/tenants/selectors.ts +1 -1
  46. package/dist/store/reducers/tenants/utils.ts +2 -2
  47. package/dist/types/additionalProps.ts +8 -1
  48. package/dist/types/api/tenant.ts +19 -20
  49. package/dist/types/store/executeQuery.ts +11 -1
  50. package/dist/types/store/query.ts +2 -1
  51. package/dist/utils/autofetcher.ts +7 -7
  52. package/dist/utils/constants.ts +2 -1
  53. package/dist/utils/hooks/i18n/en.json +1 -1
  54. package/dist/utils/hooks/i18n/ru.json +1 -1
  55. package/dist/utils/hooks/useQueryModes.ts +4 -2
  56. package/dist/utils/i18n/i18n.ts +10 -4
  57. package/dist/utils/nodes.ts +0 -6
  58. package/dist/utils/query.ts +14 -0
  59. package/dist/utils/settings.ts +10 -0
  60. package/package.json +1 -1
  61. package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/PreviewItem/PreviewItem.tsx +0 -33
  62. package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/PreviewItem/index.ts +0 -1
  63. 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 {IIssuesTree} from '../../../../../types/store/healthcheck';
9
-
10
- import {PreviewItem} from './PreviewItem';
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
- issuesTrees?: IIssuesTree[];
19
+ issuesStatistics?: [StatusFlag, number][];
19
20
  loading?: boolean;
20
- onShowMore?: (id: string) => void;
21
+ onShowMore?: VoidFunction;
21
22
  onUpdate: VoidFunction;
23
+ error?: IResponseError;
22
24
  }
23
25
 
24
26
  export const Preview = (props: PreviewProps) => {
25
- const {selfCheckResult, issuesTrees, loading, onShowMore, onUpdate} = props;
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 renderFirstLevelIssues = () => {
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
- ? i18n('status_message.ok')
54
- : issuesTrees?.map((issueTree) => (
55
- <PreviewItem
56
- key={issueTree.id}
57
- data={issueTree}
58
- onShowMore={onShowMore}
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
- <div className={b('preview')}>
78
+ <DiagnosticCard className={b('preview')}>
67
79
  {renderStatus()}
68
- {renderFirstLevelIssues()}
69
- </div>
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,7 @@
1
+ {
2
+ "no-data": "No data",
3
+ "no-pools-data": "No pools data",
4
+
5
+ "title.pools": "Pools",
6
+ "title.metrics": "Metrics"
7
+ }
@@ -0,0 +1,11 @@
1
+ import {i18n, Lang} from '../../../../../utils/i18n';
2
+
3
+ import en from './en.json';
4
+ import ru from './ru.json';
5
+
6
+ const COMPONENT = 'ydb-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);
@@ -0,0 +1,7 @@
1
+ {
2
+ "no-data": "Нет данных",
3
+ "no-pools-data": "Нет данных о пулах",
4
+
5
+ "title.pools": "Пулы",
6
+ "title.metrics": "Метрики"
7
+ }
@@ -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?: any;
22
- additionalNodesInfo?: any;
22
+ additionalTenantInfo?: AdditionalTenantsProps;
23
+ additionalNodesInfo?: AdditionalNodesProps;
23
24
  }
24
25
 
25
26
  function ObjectGeneral(props: ObjectGeneralProps) {
@@ -9,11 +9,14 @@
9
9
  @include flex-container();
10
10
  @include table-styles;
11
11
 
12
+ &__table-row {
13
+ cursor: pointer;
14
+ }
15
+
12
16
  &__query {
13
17
  overflow: hidden;
14
18
  flex-grow: 1;
15
19
 
16
- cursor: pointer;
17
20
  white-space: pre;
18
21
  text-overflow: ellipsis;
19
22
  }
@@ -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 queriesHistory = useTypedSelector((state) => state.executeQuery.history.queries) ?? [];
28
+ const [queryMode, setQueryMode] = useQueryModes();
29
+
30
+ const queriesHistory = useTypedSelector(selectQueriesHistory);
26
31
  const reversedHistory = [...queriesHistory].reverse();
27
32
 
28
- const onQueryClick = (queryText: string) => {
29
- changeUserInput({input: queryText});
30
- dispatch(setQueryTab(TENANT_QUERY_TABS_ID.newQuery));
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<string>[] = [
52
+ const columns: Column<QueryInHistory>[] = [
34
53
  {
35
54
  name: 'queryText',
36
55
  header: 'Query Text',
37
- render: ({row: query}) => (
38
- <div className={b('query')}>
39
- <TruncatedQuery value={query} maxQueryHeight={MAX_QUERY_HEIGHT} />
40
- </div>
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: 'YQL Script',
24
+ title: QUERY_MODES_TITLES[QUERY_MODES.script],
25
25
  description: i18n('method-description.script'),
26
26
  },
27
27
  [QUERY_MODES.scan]: {
28
- title: 'Scan',
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: 'YQL Script',
35
+ title: QUERY_MODES_TITLES[QUERY_MODES.script],
36
36
  description: i18n('method-description.script'),
37
37
  },
38
38
  [QUERY_MODES.scan]: {
39
- title: 'Scan',
39
+ title: QUERY_MODES_TITLES[QUERY_MODES.scan],
40
40
  description: i18n('method-description.scan'),
41
41
  },
42
42
  [QUERY_MODES.data]: {
43
- title: 'Data',
43
+ title: QUERY_MODES_TITLES[QUERY_MODES.data],
44
44
  description: i18n('method-description.data'),
45
45
  },
46
46
  [QUERY_MODES.query]: {
47
- title: 'YQL - QueryService',
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?: any;
41
- additionalNodesInfo?: any;
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={setValue} />;
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={setValue}>
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' and 'YQL - QueryService' modes. May not work on some versions"
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' и 'YQL - QueryService'. Может работать некорректно на некоторых версиях"
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'),