ydb-embedded-ui 4.15.1 → 4.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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'),