From f2248095e2f81354b349879f36ed14398e5c969b Mon Sep 17 00:00:00 2001 From: astandrik Date: Wed, 27 Nov 2024 17:45:54 +0300 Subject: [PATCH] chore!: split apis --- src/components/MetricChart/getChartData.ts | 2 +- src/containers/Nodes/getNodes.ts | 2 +- src/containers/PDiskPage/PDiskPage.tsx | 4 +- .../Storage/StorageNodes/getNodes.ts | 2 +- src/containers/VDiskPage/VDiskPage.tsx | 4 +- src/lib.ts | 2 +- src/services/api.ts | 996 ------------------ src/services/api/auth.ts | 11 + src/services/api/base.ts | 81 ++ src/services/api/index.ts | 46 + src/services/api/meta.ts | 58 + src/services/api/operation.ts | 55 + src/services/api/pdisk.ts | 91 ++ src/services/api/scheme.ts | 20 + src/services/api/storage.ts | 42 + src/services/api/tablets.ts | 90 ++ src/services/api/trace.ts | 42 + src/services/api/vdisk.ts | 37 + src/services/api/viewer.ts | 523 +++++++++ .../reducers/authentication/authentication.ts | 6 +- src/store/reducers/cancelQuery.ts | 2 +- .../reducers/capabilities/capabilities.ts | 2 +- src/store/reducers/cluster/cluster.ts | 11 +- src/store/reducers/clusters/clusters.ts | 8 +- .../executeTopQueries/executeTopQueries.ts | 4 +- .../healthcheckInfo/healthcheckInfo.ts | 2 +- src/store/reducers/heatmap.ts | 4 +- src/store/reducers/hotKeys/hotKeys.ts | 4 +- src/store/reducers/network/network.ts | 2 +- src/store/reducers/node/node.ts | 4 +- src/store/reducers/nodes/nodes.ts | 2 +- src/store/reducers/nodesList.ts | 2 +- src/store/reducers/operations.ts | 6 +- src/store/reducers/overview/overview.ts | 4 +- src/store/reducers/partitions/partitions.ts | 7 +- src/store/reducers/pdisk/pdisk.ts | 6 +- src/store/reducers/planToSvg.ts | 2 +- src/store/reducers/preview.ts | 2 +- src/store/reducers/query/query.ts | 2 +- src/store/reducers/schema/schema.ts | 7 +- src/store/reducers/schemaAcl/schemaAcl.ts | 2 +- .../reducers/shardsWorkload/shardsWorkload.ts | 2 +- .../reducers/storage/requestStorageData.ts | 6 +- src/store/reducers/storage/storage.ts | 2 +- src/store/reducers/tablet.ts | 18 +- src/store/reducers/tablets.ts | 2 +- src/store/reducers/tenant/tenant.ts | 4 +- .../executeTopTables/executeTopTables.ts | 2 +- .../topShards/tenantOverviewTopShards.ts | 2 +- src/store/reducers/tenants/tenants.ts | 4 +- src/store/reducers/topic.ts | 2 +- src/store/reducers/trace.ts | 2 +- src/store/reducers/vdisk/vdisk.ts | 6 +- src/store/reducers/viewSchema/viewSchema.ts | 2 +- src/types/window.d.ts | 2 +- src/utils/monaco/yql/generateSuggestions.ts | 8 +- 56 files changed, 1194 insertions(+), 1069 deletions(-) delete mode 100644 src/services/api.ts create mode 100644 src/services/api/auth.ts create mode 100644 src/services/api/base.ts create mode 100644 src/services/api/index.ts create mode 100644 src/services/api/meta.ts create mode 100644 src/services/api/operation.ts create mode 100644 src/services/api/pdisk.ts create mode 100644 src/services/api/scheme.ts create mode 100644 src/services/api/storage.ts create mode 100644 src/services/api/tablets.ts create mode 100644 src/services/api/trace.ts create mode 100644 src/services/api/vdisk.ts create mode 100644 src/services/api/viewer.ts diff --git a/src/components/MetricChart/getChartData.ts b/src/components/MetricChart/getChartData.ts index 5bfc45557..5fa993f75 100644 --- a/src/components/MetricChart/getChartData.ts +++ b/src/components/MetricChart/getChartData.ts @@ -19,7 +19,7 @@ export const getChartData = async ( const until = Math.round(Date.now() / 1000); const from = until - TIMEFRAMES[timeFrame]; - return window.api.getChartData( + return window.api.viewer.getChartData( {target: targetString, from, until, maxDataPoints, database}, {signal}, ); diff --git a/src/containers/Nodes/getNodes.ts b/src/containers/Nodes/getNodes.ts index c904811f8..a1906208e 100644 --- a/src/containers/Nodes/getNodes.ts +++ b/src/containers/Nodes/getNodes.ts @@ -35,7 +35,7 @@ export const getNodes: FetchData< const dataFieldsRequired = getRequiredDataFields(columnsIds, NODES_COLUMNS_TO_DATA_FIELDS); - const response = await window.api.getNodes({ + const response = await window.api.viewer.getNodes({ type, storage, tablets, diff --git a/src/containers/PDiskPage/PDiskPage.tsx b/src/containers/PDiskPage/PDiskPage.tsx index 435a5b7be..f7157ca2a 100644 --- a/src/containers/PDiskPage/PDiskPage.tsx +++ b/src/containers/PDiskPage/PDiskPage.tsx @@ -90,7 +90,7 @@ export function PDiskPage() { const handleRestart = async (isRetry?: boolean) => { if (pDiskParamsDefined) { - const response = await window.api[ + const response = await window.api.pdisk[ newDiskApiAvailable ? 'restartPDisk' : 'restartPDiskOld' ]({nodeId, pDiskId, force: isRetry}); @@ -109,7 +109,7 @@ export function PDiskPage() { isRetry?: boolean, ) => { if (pDiskParamsDefined) { - const response = await window.api.changePDiskStatus({ + const response = await window.api.pdisk.changePDiskStatus({ nodeId, pDiskId, force: isRetry, diff --git a/src/containers/Storage/StorageNodes/getNodes.ts b/src/containers/Storage/StorageNodes/getNodes.ts index 81870ccf1..be9a0714e 100644 --- a/src/containers/Storage/StorageNodes/getNodes.ts +++ b/src/containers/Storage/StorageNodes/getNodes.ts @@ -44,7 +44,7 @@ export const getStorageNodes: FetchData< const dataFieldsRequired = getRequiredDataFields(columnsIds, NODES_COLUMNS_TO_DATA_FIELDS); - const response = await window.api.getNodes({ + const response = await window.api.viewer.getNodes({ type, storage, limit, diff --git a/src/containers/VDiskPage/VDiskPage.tsx b/src/containers/VDiskPage/VDiskPage.tsx index f98d4e341..3e5de71b8 100644 --- a/src/containers/VDiskPage/VDiskPage.tsx +++ b/src/containers/VDiskPage/VDiskPage.tsx @@ -70,7 +70,9 @@ export function VDiskPage() { const handleEvictVDisk = async (isRetry?: boolean) => { if (vDiskIdParamsDefined) { - return window.api[newDiskApiAvailable ? 'evictVDisk' : 'evictVDiskOld']({ + return ( + newDiskApiAvailable ? window.api.vdisk.evictVDisk : window.api.tablets.evictVDiskOld + )({ groupId: GroupID, groupGeneration: GroupGeneration, failRealmIdx: Ring, diff --git a/src/lib.ts b/src/lib.ts index 1e4a21a3b..8cb22ac5f 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -9,7 +9,7 @@ export {AsideNavigation} from './containers/AsideNavigation/AsideNavigation'; export {configureStore, rootReducer} from './store'; export {default as appRoutes} from './routes'; -export {createApi, YdbEmbeddedAPI, YdbWebVersionAPI} from './services/api'; +export {createApi, YdbEmbeddedAPI} from './services/api'; export {settingsManager} from './services/settings'; export {getUserSettings} from './containers/UserSettings/settings'; export {setSettingValue, getSettingValue} from './store/reducers/settings/settings'; diff --git a/src/services/api.ts b/src/services/api.ts deleted file mode 100644 index 440db8f96..000000000 --- a/src/services/api.ts +++ /dev/null @@ -1,996 +0,0 @@ -import AxiosWrapper from '@gravity-ui/axios-wrapper'; -import type {AxiosWrapperOptions} from '@gravity-ui/axios-wrapper'; -import type {AxiosRequestConfig} from 'axios'; -import axiosRetry from 'axios-retry'; - -import {backend as BACKEND, metaBackend as META_BACKEND} from '../store'; -import type {PlanToSvgQueryParams} from '../store/reducers/planToSvg'; -import type {TMetaInfo} from '../types/api/acl'; -import type {TQueryAutocomplete} from '../types/api/autocomplete'; -import type {CapabilitiesResponse} from '../types/api/capabilities'; -import type {TClusterInfo} from '../types/api/cluster'; -import type {DescribeConsumerResult} from '../types/api/consumer'; -import type {FeatureFlagConfigs} from '../types/api/featureFlags'; -import type {HealthCheckAPIResponse} from '../types/api/healthcheck'; -import type {JsonHotKeysResponse} from '../types/api/hotkeys'; -import type { - MetaBaseClusterInfo, - MetaBaseClusters, - MetaCluster, - MetaClusters, - MetaTenants, -} from '../types/api/meta'; -import type {ModifyDiskResponse} from '../types/api/modifyDisk'; -import type {TNetInfo} from '../types/api/netInfo'; -import type {NodesRequestParams, TNodesInfo} from '../types/api/nodes'; -import type {TEvNodesInfo} from '../types/api/nodesList'; -import type { - OperationCancelRequestParams, - OperationForgetRequestParams, - OperationListRequestParams, - TOperationList, -} from '../types/api/operations'; -import type {EDecommitStatus, TEvPDiskStateResponse, TPDiskInfoResponse} from '../types/api/pdisk'; -import type { - Actions, - ErrorResponse, - QueryAPIResponse, - Stats, - Timeout, - TracingLevel, -} from '../types/api/query'; -import type {JsonRenderRequestParams, JsonRenderResponse} from '../types/api/render'; -import type {TEvDescribeSchemeResult} from '../types/api/schema'; -import type { - GroupsRequestParams, - StorageGroupsResponse, - StorageRequestParams, - TStorageInfo, -} from '../types/api/storage'; -import type {TEvSystemStateResponse} from '../types/api/systemState'; -import type { - TDomainKey, - TEvTabletStateResponse, - TTabletHiveResponse, - UnmergedTEvTabletStateResponse, -} from '../types/api/tablet'; -import type {TTenantInfo, TTenants} from '../types/api/tenant'; -import type {DescribeTopicResult} from '../types/api/topic'; -import type {TEvVDiskStateResponse} from '../types/api/vdisk'; -import type {TUserToken} from '../types/api/whoami'; -import type {QuerySyntax, TransactionMode} from '../types/store/query'; -import { - BINARY_DATA_IN_PLAIN_TEXT_DISPLAY, - DEV_ENABLE_TRACING_FOR_ALL_REQUESTS, - SECOND_IN_MS, -} from '../utils/constants'; -import {createPDiskDeveloperUILink} from '../utils/developerUI/developerUI'; -import {isAxiosError} from '../utils/response'; -import type {Nullable} from '../utils/typecheckers'; - -import {parseMetaCluster} from './parsers/parseMetaCluster'; -import {parseMetaTenants} from './parsers/parseMetaTenants'; -import {settingsManager} from './settings'; - -const TRACE_RETRY_DELAY = 4 * SECOND_IN_MS; -const TRACE_CHECK_TIMEOUT = 10 * SECOND_IN_MS; -const TRACE_API_ERROR_RETRY_DELAY = 10 * SECOND_IN_MS; -const MAX_TRACE_CHECK_RETRIES = 30; - -export type AxiosOptions = { - concurrentId?: string; - signal?: AbortSignal; - withRetries?: boolean; -}; - -export class YdbEmbeddedAPI extends AxiosWrapper { - DEFAULT_RETRIES_COUNT = 3; - - constructor(options?: AxiosWrapperOptions) { - super(options); - - axiosRetry(this._axios, { - retries: this.DEFAULT_RETRIES_COUNT, - retryDelay: axiosRetry.exponentialDelay, - }); - - // Make possible manually enable tracing for all requests - // For development purposes - this._axios.interceptors.request.use(function (config) { - const enableTracing = settingsManager.readUserSettingsValue( - DEV_ENABLE_TRACING_FOR_ALL_REQUESTS, - ); - - if (enableTracing) { - config.headers['X-Want-Trace'] = 1; - } - - return config; - }); - - // Add traceId to response if it exists - this._axios.interceptors.response.use(function (response) { - if ( - response.data && - response.data instanceof Object && - !Array.isArray(response.data) && - response.headers['traceresponse'] - ) { - const traceId = response.headers['traceresponse'].split('-')[1]; - - response.data = { - ...response.data, - _meta: {...response.data._meta, traceId}, - }; - } - - return response; - }); - - // Interceptor to process OIDC auth - this._axios.interceptors.response.use(null, function (error) { - const response = error.response; - - // OIDC proxy returns 401 response with authUrl in it - // authUrl - external auth service link, after successful auth additional cookies will be appended - // that will allow access to clusters where OIDC proxy is a balancer - if (response && response.status === 401 && response.data?.authUrl) { - window.location.assign(response.data.authUrl); - } - - return Promise.reject(error); - }); - } - - getPath(path: string) { - return `${BACKEND ?? ''}${path}`; - } - prepareArrayRequestParam(arr: (string | number)[]) { - return arr.join(','); - } - - getClusterCapabilities({database}: {database?: string}) { - return this.get(this.getPath('/viewer/capabilities'), {database}, {}); - } - getClusterInfo(clusterName?: string, {concurrentId, signal}: AxiosOptions = {}) { - return this.get( - this.getPath('/viewer/json/cluster'), - { - name: clusterName, - tablets: true, - }, - {concurrentId: concurrentId || `getClusterInfo`, requestConfig: {signal}}, - ); - } - getClusterConfig(database?: string, {concurrentId, signal}: AxiosOptions = {}) { - return this.get( - this.getPath('/viewer/feature_flags'), - { - database, - }, - {concurrentId, requestConfig: {signal}}, - ); - } - getNodeInfo(id?: string | number, {concurrentId, signal}: AxiosOptions = {}) { - return this.get( - this.getPath('/viewer/json/sysinfo?enums=true'), - { - node_id: id, - }, - {concurrentId, requestConfig: {signal}}, - ); - } - getTenants(clusterName?: string, {concurrentId, signal}: AxiosOptions = {}) { - return this.get( - this.getPath('/viewer/json/tenantinfo'), - { - tablets: false, - storage: true, - cluster_name: clusterName, - }, - {concurrentId, requestConfig: {signal}}, - ); - } - getTenantInfo( - {path, database = path}: {path: string; database?: string}, - {concurrentId, signal}: AxiosOptions = {}, - ) { - return this.get( - this.getPath('/viewer/json/tenantinfo'), - { - database, - path, - tablets: false, - storage: true, - memory: true, - }, - {concurrentId, requestConfig: {signal}}, - ); - } - getNodes( - { - type = 'any', - tablets = false, - database, - tenant, - fieldsRequired, - filter, - ...params - }: NodesRequestParams, - {concurrentId, signal}: AxiosOptions = {}, - ) { - const preparedFieldsRequired = Array.isArray(fieldsRequired) - ? this.prepareArrayRequestParam(fieldsRequired) - : fieldsRequired; - - return this.get( - this.getPath('/viewer/json/nodes?enums=true'), - { - type, - tablets, - // Do not send empty string - filter: filter || undefined, - // TODO: remove after remove tenant param - database: database || tenant, - tenant: tenant || database, - fields_required: preparedFieldsRequired, - ...params, - }, - {concurrentId, requestConfig: {signal}}, - ); - } - getStorageInfo( - {tenant, database, nodeId, groupId, pDiskId, filter, ...params}: StorageRequestParams, - {concurrentId, signal}: AxiosOptions = {}, - ) { - return this.get( - this.getPath(`/viewer/json/storage?enums=true`), - { - database: database || tenant, - tenant: tenant || database, - node_id: nodeId, - group_id: groupId, - pdisk_id: pDiskId, - // Do not send empty string - filter: filter || undefined, - ...params, - }, - {concurrentId, requestConfig: {signal}}, - ); - } - getStorageGroups( - {nodeId, pDiskId, groupId, fieldsRequired, filter, ...params}: GroupsRequestParams, - {concurrentId, signal}: AxiosOptions = {}, - ) { - const preparedNodeId = Array.isArray(nodeId) - ? this.prepareArrayRequestParam(nodeId) - : nodeId; - - const preparedPDiskId = Array.isArray(pDiskId) - ? this.prepareArrayRequestParam(pDiskId) - : pDiskId; - - const preparedGroupId = Array.isArray(groupId) - ? this.prepareArrayRequestParam(groupId) - : groupId; - - const preparedFieldsRequired = Array.isArray(fieldsRequired) - ? this.prepareArrayRequestParam(fieldsRequired) - : fieldsRequired; - - return this.get( - this.getPath('/storage/groups'), - { - node_id: preparedNodeId, - pdisk_id: preparedPDiskId, - group_id: preparedGroupId, - fields_required: preparedFieldsRequired, - // Do not send empty string - filter: filter || undefined, - timeout: 20_000, - ...params, - }, - {concurrentId, requestConfig: {signal}}, - ); - } - getNodeWhiteboardPDiskInfo( - {nodeId, pDiskId}: {nodeId: string | number; pDiskId: string | number}, - {concurrentId, signal}: AxiosOptions = {}, - ) { - return this.get( - this.getPath('/viewer/json/pdiskinfo?enums=true'), - { - filter: `(NodeId=${nodeId}${pDiskId ? `;PDiskId=${pDiskId}` : ''})`, - }, - {concurrentId, requestConfig: {signal}}, - ); - } - getPDiskInfo( - {nodeId, pDiskId}: {nodeId: string | number; pDiskId: string | number}, - {concurrentId, signal}: AxiosOptions = {}, - ) { - return this.get( - this.getPath('/pdisk/info'), - { - node_id: nodeId, - pdisk_id: pDiskId, - }, - {concurrentId, requestConfig: {signal}}, - ); - } - getVDiskInfo( - { - vDiskSlotId, - pDiskId, - nodeId, - }: { - vDiskSlotId: string | number; - pDiskId: string | number; - nodeId: string | number; - }, - {concurrentId, signal}: AxiosOptions = {}, - ) { - return this.get( - this.getPath('/viewer/json/vdiskinfo?enums=true'), - { - node_id: nodeId, - filter: `(PDiskId=${pDiskId};VDiskSlotId=${vDiskSlotId})`, - }, - {concurrentId, requestConfig: {signal}}, - ); - } - getGroupInfo(groupId: string | number, {concurrentId, signal}: AxiosOptions = {}) { - return this.get( - this.getPath('/viewer/json/storage?enums=true'), - { - group_id: groupId, - }, - {concurrentId, requestConfig: {signal}}, - ); - } - getTabletsInfo( - {nodeId, path, database}: {nodeId?: string | number; path?: string; database?: string}, - {concurrentId, signal}: AxiosOptions = {}, - ) { - return this.get( - this.getPath('/viewer/json/tabletinfo'), - { - database, - node_id: nodeId, - path, - enums: true, - }, - {concurrentId, requestConfig: {signal}}, - ); - } - getSchema( - {path, database}: {path: string; database: string}, - {concurrentId, signal}: AxiosOptions = {}, - ) { - return this.get>( - this.getPath('/viewer/json/describe'), - { - database, - path, - enums: true, - backup: false, - private: true, - partition_config: false, - partition_stats: false, - partitioning_info: false, - subs: 1, - }, - {concurrentId, requestConfig: {signal}}, - ); - } - getDescribe( - {path, database, timeout}: {path: string; database: string; timeout?: number}, - {concurrentId, signal}: AxiosOptions = {}, - ) { - return this.get>( - this.getPath('/viewer/json/describe'), - { - database, - path, - enums: true, - partition_stats: true, - subs: 0, - }, - {concurrentId: concurrentId || `getDescribe|${path}`, requestConfig: {signal}, timeout}, - ); - } - getSchemaAcl( - {path, database}: {path: string; database: string}, - {concurrentId, signal}: AxiosOptions = {}, - ) { - return this.get( - this.getPath('/viewer/json/acl'), - { - database, - path, - merge_rules: true, - }, - {concurrentId, requestConfig: {signal}}, - ); - } - getHeatmapData( - {path, database}: {path: string; database: string}, - {concurrentId, signal}: AxiosOptions = {}, - ) { - return this.get>( - this.getPath('/viewer/json/describe'), - { - database, - path, - enums: true, - backup: false, - children: false, - partition_config: false, - partition_stats: true, - }, - {concurrentId, requestConfig: {signal}}, - ); - } - getNetwork( - {path, database}: {path: string; database: string}, - {concurrentId, signal}: AxiosOptions = {}, - ) { - return this.get( - this.getPath('/viewer/json/netinfo'), - { - enums: true, - database, - path, - }, - {concurrentId, requestConfig: {signal}}, - ); - } - getTopic( - {path, database}: {path: string; database: string}, - {concurrentId, signal}: AxiosOptions = {}, - ) { - return this.get( - this.getPath('/viewer/json/describe_topic'), - { - enums: true, - include_stats: true, - database, - path, - }, - {concurrentId, requestConfig: {signal}}, - ); - } - getConsumer( - {path, consumer, database}: {path: string; consumer: string; database: string}, - {concurrentId, signal}: AxiosOptions = {}, - ) { - return this.get( - this.getPath('/viewer/json/describe_consumer'), - { - enums: true, - include_stats: true, - database, - path, - consumer, - }, - {concurrentId: concurrentId || 'getConsumer', requestConfig: {signal}}, - ); - } - getTablet( - {id, database}: {id: string; database?: string}, - {concurrentId, signal}: AxiosOptions = {}, - ) { - return this.get( - this.getPath('/viewer/json/tabletinfo'), - { - enums: true, - database, - filter: `(TabletId=${id})`, - }, - { - concurrentId, - requestConfig: {signal}, - }, - ); - } - getTabletHistory( - {id, database}: {id: string; database?: string}, - {concurrentId, signal}: AxiosOptions = {}, - ) { - return this.get( - this.getPath('/viewer/json/tabletinfo'), - { - enums: true, - merge: false, - database, - filter: `(TabletId=${id})`, - }, - { - concurrentId, - requestConfig: {signal}, - }, - ); - } - getNodesList({concurrentId, signal}: AxiosOptions = {}) { - return this.get( - this.getPath('/viewer/json/nodelist'), - { - enums: true, - }, - { - concurrentId, - requestConfig: {signal}, - }, - ); - } - getTenantsList({concurrentId, signal}: AxiosOptions = {}) { - return this.get( - this.getPath('/viewer/json/tenants'), - { - enums: true, - state: 0, - }, - { - concurrentId, - requestConfig: {signal}, - }, - ); - } - sendQuery( - params: { - query?: string; - database?: string; - action?: Action; - syntax?: QuerySyntax; - stats?: Stats; - tracingLevel?: TracingLevel; - transaction_mode?: TransactionMode; - timeout?: Timeout; - query_id?: string; - limit_rows?: number; - }, - {concurrentId, signal, withRetries}: AxiosOptions = {}, - ) { - /** - * Return strings using base64 encoding. - * @link https://github.com/ydb-platform/ydb/pull/647 - */ - const base64 = !settingsManager.readUserSettingsValue( - BINARY_DATA_IN_PLAIN_TEXT_DISPLAY, - true, - ); - - // FIXME: base64 is passed both to params and body to work on versions before and after 24-3 - return this.post | ErrorResponse | null>( - this.getPath('/viewer/json/query'), - {...params, base64}, - {schema: 'multi', base64, timeout: params.timeout}, - { - concurrentId, - timeout: params.timeout, - requestConfig: { - signal, - 'axios-retry': {retries: withRetries ? this.DEFAULT_RETRIES_COUNT : 0}, - }, - headers: params.tracingLevel - ? { - 'X-Trace-Verbosity': params.tracingLevel, - } - : undefined, - }, - ); - } - planToSvg({database, plan}: PlanToSvgQueryParams, {signal}: {signal?: AbortSignal} = {}) { - return this.post( - this.getPath('/viewer/plan2svg'), - plan, - {database}, - { - requestConfig: { - signal, - responseType: 'text', - headers: { - Accept: 'image/svg+xml', - }, - }, - }, - ); - } - getHotKeys( - {path, database, enableSampling}: {path: string; database: string; enableSampling: boolean}, - {concurrentId, signal}: AxiosOptions = {}, - ) { - return this.get( - this.getPath('/viewer/json/hotkeys'), - { - database, - path, - enable_sampling: enableSampling, - }, - {concurrentId: concurrentId || 'getHotKeys', requestConfig: {signal}}, - ); - } - - checkTrace({url}: {url: string}, {concurrentId, signal}: AxiosOptions = {}) { - return this.get( - url, - {}, - { - concurrentId: concurrentId || 'checkTrace', - requestConfig: { - signal, - timeout: TRACE_CHECK_TIMEOUT, - 'axios-retry': { - retries: MAX_TRACE_CHECK_RETRIES, - retryDelay: (_: number, error: unknown) => { - const isTracingError = - isAxiosError(error) && - (error?.response?.status === 404 || error.code === 'ERR_NETWORK'); - - if (isTracingError) { - return TRACE_RETRY_DELAY; - } - - return TRACE_API_ERROR_RETRY_DELAY; - }, - shouldResetTimeout: true, - retryCondition: () => true, - }, - }, - }, - ); - } - getHealthcheckInfo( - {database, maxLevel}: {database: string; maxLevel?: number}, - {concurrentId, signal}: AxiosOptions = {}, - ) { - return this.get( - this.getPath('/viewer/json/healthcheck?merge_records=true'), - {database, tenant: database, max_level: maxLevel}, - {concurrentId, requestConfig: {signal}}, - ); - } - evictVDiskOld({ - groupId, - groupGeneration, - failRealmIdx, - failDomainIdx, - vDiskIdx, - }: { - groupId: string | number; - groupGeneration: string | number; - failRealmIdx: string | number; - failDomainIdx: string | number; - vDiskIdx: string | number; - }) { - // BSC Id is constant for all ydb clusters - const BSC_TABLET_ID = '72057594037932033'; - - return this.post( - this.getPath(`/tablets/app?TabletID=${BSC_TABLET_ID}&exec=1`), - { - Command: { - ReassignGroupDisk: { - GroupId: groupId, - GroupGeneration: groupGeneration, - FailRealmIdx: failRealmIdx, - FailDomainIdx: failDomainIdx, - VDiskIdx: vDiskIdx, - }, - }, - }, - {}, - { - headers: { - // This handler requires exactly this string - // Automatic headers may not suit - Accept: 'application/json', - }, - }, - ); - } - evictVDisk({ - groupId, - groupGeneration, - failRealmIdx, - failDomainIdx, - vDiskIdx, - force, - }: { - groupId: string | number; - groupGeneration: string | number; - failRealmIdx: string | number; - failDomainIdx: string | number; - vDiskIdx: string | number; - force?: boolean; - }) { - return this.post( - this.getPath('/vdisk/evict'), - {}, - { - group_id: groupId, - group_generation_id: groupGeneration, - fail_realm_idx: failRealmIdx, - fail_domain_idx: failDomainIdx, - vdisk_idx: vDiskIdx, - - force, - }, - { - requestConfig: {'axios-retry': {retries: 0}}, - }, - ); - } - - restartPDiskOld({nodeId, pDiskId}: {nodeId: number | string; pDiskId: number | string}) { - const pDiskPath = createPDiskDeveloperUILink({ - nodeId, - pDiskId, - host: this.getPath(''), - }); - - return this.post( - pDiskPath, - 'restartPDisk=', - {}, - { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', - }, - }, - ); - } - - restartPDisk({ - nodeId, - pDiskId, - force, - }: { - nodeId: number | string; - pDiskId: number | string; - force?: boolean; - }) { - return this.post( - this.getPath('/pdisk/restart'), - {}, - { - node_id: nodeId, - pdisk_id: pDiskId, - force, - }, - { - requestConfig: {'axios-retry': {retries: 0}}, - }, - ); - } - changePDiskStatus({ - nodeId, - pDiskId, - force, - decommissionStatus, - }: { - nodeId: number | string; - pDiskId: number | string; - force?: boolean; - decommissionStatus?: EDecommitStatus; - }) { - return this.post( - this.getPath('/pdisk/status'), - { - decommit_status: decommissionStatus, - }, - { - node_id: nodeId, - pdisk_id: pDiskId, - force, - }, - { - requestConfig: {'axios-retry': {retries: 0}}, - }, - ); - } - killTablet(id: string) { - return this.get( - this.getPath(`/tablets?KillTabletID=${id}`), - {}, - {requestConfig: {'axios-retry': {retries: 0}}}, - ); - } - stopTablet(id: string, hiveId: string) { - return this.get( - this.getPath(`/tablets/app?TabletID=${hiveId}&page=StopTablet&tablet=${id}`), - {}, - {requestConfig: {'axios-retry': {retries: 0}}}, - ); - } - resumeTablet(id: string, hiveId: string) { - return this.get( - this.getPath(`/tablets/app?TabletID=${hiveId}&page=ResumeTablet&tablet=${id}`), - {}, - {requestConfig: {'axios-retry': {retries: 0}}}, - ); - } - getTabletFromHive( - {id, hiveId}: {id: string; hiveId: string}, - {concurrentId, signal}: AxiosOptions = {}, - ) { - return this.get>( - this.getPath('/tablets/app'), - { - TabletID: hiveId, - page: 'TabletInfo', - tablet: id, - }, - { - concurrentId, - requestConfig: {signal}, - }, - ); - } - getTabletDescribe(tenantId: TDomainKey, {concurrentId, signal}: AxiosOptions = {}) { - return this.get>( - this.getPath('/viewer/json/describe'), - { - schemeshard_id: tenantId?.SchemeShard, - path_id: tenantId?.PathId, - }, - {concurrentId, requestConfig: {signal}}, - ); - } - getChartData( - {target, from, until, maxDataPoints, database}: JsonRenderRequestParams, - {concurrentId, signal}: AxiosOptions = {}, - ) { - const requestString = `${target}&from=${from}&until=${until}&maxDataPoints=${maxDataPoints}&format=json`; - - return this.post( - this.getPath(`/viewer/json/render?database=${database}`), - requestString, - {}, - { - concurrentId, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - requestConfig: {signal}, - }, - ); - } - authenticate(params: {user: string; password: string}) { - return this.post(this.getPath('/login'), params, {}); - } - logout() { - return this.post(this.getPath('/logout'), {}, {}); - } - whoami() { - return this.get(this.getPath('/viewer/json/whoami'), {}); - } - autocomplete(params: {database: string; prefix?: string; limit?: number; table?: string[]}) { - const {table, ...rest} = params; - const tablesParam = table?.join(','); - return this.get( - this.getPath('/viewer/json/autocomplete'), - {...rest, table: tablesParam}, - {concurrentId: 'sql-autocomplete'}, - ); - } - - createSchemaDirectory( - {database, path}: {database: string; path: string}, - {signal}: {signal?: AbortSignal} = {}, - ) { - return this.post<{test: string}>( - this.getPath('/scheme/directory'), - {}, - { - database, - path, - }, - { - requestConfig: {signal}, - }, - ); - } - - getClustersList(_?: never, __: {signal?: AbortSignal} = {}): Promise { - throw new Error('Method is not implemented.'); - } - - getOperationList( - params: OperationListRequestParams, - {concurrentId, signal}: AxiosOptions = {}, - ) { - return this.get( - this.getPath('/operation/list'), - {...params}, - { - concurrentId, - requestConfig: {signal}, - }, - ); - } - - cancelOperation( - params: OperationCancelRequestParams, - {concurrentId, signal}: AxiosOptions = {}, - ) { - return this.post( - this.getPath('/operation/cancel'), - {}, - {...params}, - { - concurrentId, - requestConfig: {signal}, - }, - ); - } - - forgetOperation( - params: OperationForgetRequestParams, - {concurrentId, signal}: AxiosOptions = {}, - ) { - return this.post( - this.getPath('/operation/forget'), - {}, - {...params}, - { - concurrentId, - requestConfig: {signal}, - }, - ); - } - - getClusterBaseInfo( - _clusterName: string, - _opts: AxiosOptions = {}, - ): Promise { - throw new Error('Method is not implemented.'); - } -} - -export class YdbWebVersionAPI extends YdbEmbeddedAPI { - getClustersList(_?: never, {signal}: {signal?: AbortSignal} = {}) { - return this.get(`${META_BACKEND || ''}/meta/clusters`, null, { - requestConfig: {signal}, - }); - } - - getClusterInfo(clusterName: string, {signal}: AxiosOptions = {}) { - return this.get( - `${META_BACKEND || ''}/meta/cluster`, - { - name: clusterName, - }, - {concurrentId: `getCluster${clusterName}`, requestConfig: {signal}}, - ).then(parseMetaCluster); - } - - getTenants(clusterName: string, {signal}: AxiosOptions = {}) { - return this.get( - `${META_BACKEND || ''}/meta/cp_databases`, - { - cluster_name: clusterName, - }, - {requestConfig: {signal}}, - ).then(parseMetaTenants); - } - - getClusterBaseInfo( - clusterName: string, - {concurrentId, signal}: AxiosOptions = {}, - ): Promise { - return this.get( - `${META_BACKEND || ''}/meta/db_clusters`, - { - name: clusterName, - }, - {concurrentId, requestConfig: {signal}}, - ).then((data) => data.clusters[0]); - } -} - -export function createApi({webVersion = false, withCredentials = false} = {}) { - const config: AxiosRequestConfig = {withCredentials}; - const api = webVersion ? new YdbWebVersionAPI({config}) : new YdbEmbeddedAPI({config}); - return api; -} diff --git a/src/services/api/auth.ts b/src/services/api/auth.ts new file mode 100644 index 000000000..814fc6d28 --- /dev/null +++ b/src/services/api/auth.ts @@ -0,0 +1,11 @@ +import {BaseYdbAPI} from './base'; + +export class AuthAPI extends BaseYdbAPI { + authenticate(params: {user: string; password: string}) { + return this.post(this.getPath('/login'), params, {}); + } + + logout() { + return this.post(this.getPath('/logout'), {}, {}); + } +} diff --git a/src/services/api/base.ts b/src/services/api/base.ts new file mode 100644 index 000000000..623ffe19b --- /dev/null +++ b/src/services/api/base.ts @@ -0,0 +1,81 @@ +import AxiosWrapper from '@gravity-ui/axios-wrapper'; +import type {AxiosWrapperOptions} from '@gravity-ui/axios-wrapper'; +import axiosRetry from 'axios-retry'; + +import {backend as BACKEND} from '../../store'; +import {DEV_ENABLE_TRACING_FOR_ALL_REQUESTS} from '../../utils/constants'; +import {settingsManager} from '../settings'; + +export type AxiosOptions = { + concurrentId?: string; + signal?: AbortSignal; + withRetries?: boolean; +}; + +export class BaseYdbAPI extends AxiosWrapper { + DEFAULT_RETRIES_COUNT = 3; + + constructor(options?: AxiosWrapperOptions) { + super(options); + + axiosRetry(this._axios, { + retries: this.DEFAULT_RETRIES_COUNT, + retryDelay: axiosRetry.exponentialDelay, + }); + + // Make possible manually enable tracing for all requests + // For development purposes + this._axios.interceptors.request.use(function (config) { + const enableTracing = settingsManager.readUserSettingsValue( + DEV_ENABLE_TRACING_FOR_ALL_REQUESTS, + ); + + if (enableTracing) { + config.headers['X-Want-Trace'] = 1; + } + + return config; + }); + + // Add traceId to response if it exists + this._axios.interceptors.response.use(function (response) { + if ( + response.data && + response.data instanceof Object && + !Array.isArray(response.data) && + response.headers['traceresponse'] + ) { + const traceId = response.headers['traceresponse'].split('-')[1]; + + response.data = { + ...response.data, + _meta: {...response.data._meta, traceId}, + }; + } + + return response; + }); + + // Interceptor to process OIDC auth + this._axios.interceptors.response.use(null, function (error) { + const response = error.response; + + // OIDC proxy returns 401 response with authUrl in it + // authUrl - external auth service link, after successful auth additional cookies will be appended + // that will allow access to clusters where OIDC proxy is a balancer + if (response && response.status === 401 && response.data?.authUrl) { + window.location.assign(response.data.authUrl); + } + + return Promise.reject(error); + }); + } + + getPath(path: string) { + return `${BACKEND ?? ''}${path}`; + } + + prepareArrayRequestParam(arr: (string | number)[]) { + return arr.join(','); + } +} diff --git a/src/services/api/index.ts b/src/services/api/index.ts new file mode 100644 index 000000000..fbd795c2d --- /dev/null +++ b/src/services/api/index.ts @@ -0,0 +1,46 @@ +import type {AxiosRequestConfig} from 'axios'; + +import {AuthAPI} from './auth'; +import {MetaAPI} from './meta'; +import {OperationAPI} from './operation'; +import {PDiskAPI} from './pdisk'; +import {SchemeAPI} from './scheme'; +import {StorageAPI} from './storage'; +import {TabletsAPI} from './tablets'; +import {TraceAPI} from './trace'; +import {VDiskAPI} from './vdisk'; +import {ViewerAPI} from './viewer'; + +export class YdbEmbeddedAPI { + auth: AuthAPI; + operation: OperationAPI; + pdisk: PDiskAPI; + scheme: SchemeAPI; + storage: StorageAPI; + tablets: TabletsAPI; + trace: TraceAPI; + vdisk: VDiskAPI; + viewer: ViewerAPI; + meta?: MetaAPI; + + constructor({config, webVersion}: {config: AxiosRequestConfig; webVersion?: boolean}) { + this.auth = new AuthAPI({config}); + if (webVersion) { + this.meta = new MetaAPI({config}); + } + this.operation = new OperationAPI({config}); + this.pdisk = new PDiskAPI({config}); + this.scheme = new SchemeAPI({config}); + this.storage = new StorageAPI({config}); + this.tablets = new TabletsAPI({config}); + this.trace = new TraceAPI({config}); + this.vdisk = new VDiskAPI({config}); + this.viewer = new ViewerAPI({config}); + } +} + +export function createApi({webVersion = false, withCredentials = false} = {}) { + const config: AxiosRequestConfig = {withCredentials}; + const api = new YdbEmbeddedAPI({config, webVersion}); + return api; +} diff --git a/src/services/api/meta.ts b/src/services/api/meta.ts new file mode 100644 index 000000000..006006a64 --- /dev/null +++ b/src/services/api/meta.ts @@ -0,0 +1,58 @@ +import {metaBackend as META_BACKEND} from '../../store'; +import type { + MetaBaseClusterInfo, + MetaBaseClusters, + MetaCluster, + MetaClusters, + MetaTenants, +} from '../../types/api/meta'; +import {parseMetaCluster} from '../parsers/parseMetaCluster'; +import {parseMetaTenants} from '../parsers/parseMetaTenants'; + +import type {AxiosOptions} from './base'; +import {BaseYdbAPI} from './base'; + +export class MetaAPI extends BaseYdbAPI { + getPath(path: string) { + return `${META_BACKEND ?? ''}${path}`; + } + + getClustersList(_?: never, {signal}: {signal?: AbortSignal} = {}) { + return this.get(this.getPath('/meta/clusters'), null, { + requestConfig: {signal}, + }); + } + + getClusterInfo(clusterName?: string, {signal}: AxiosOptions = {}) { + return this.get( + this.getPath('/meta/cluster'), + { + name: clusterName, + }, + {concurrentId: `getCluster${clusterName}`, requestConfig: {signal}}, + ).then(parseMetaCluster); + } + + getTenants(clusterName?: string, {signal}: AxiosOptions = {}) { + return this.get( + this.getPath('/meta/cp_databases'), + { + cluster_name: clusterName, + }, + {requestConfig: {signal}}, + ).then(parseMetaTenants); + } + + getClusterBaseInfo( + clusterName: string, + {concurrentId, signal}: AxiosOptions = {}, + ): Promise { + return this.get( + this.getPath('/meta/db_clusters'), + { + name: clusterName, + }, + {concurrentId, requestConfig: {signal}}, + ).then((data) => data.clusters[0]); + } +} diff --git a/src/services/api/operation.ts b/src/services/api/operation.ts new file mode 100644 index 000000000..fd6a904a4 --- /dev/null +++ b/src/services/api/operation.ts @@ -0,0 +1,55 @@ +import type { + OperationCancelRequestParams, + OperationForgetRequestParams, + OperationListRequestParams, + TOperationList, +} from '../../types/api/operations'; + +import type {AxiosOptions} from './base'; +import {BaseYdbAPI} from './base'; + +export class OperationAPI extends BaseYdbAPI { + getOperationList( + params: OperationListRequestParams, + {concurrentId, signal}: AxiosOptions = {}, + ) { + return this.get( + this.getPath('/operation/list'), + {...params}, + { + concurrentId, + requestConfig: {signal}, + }, + ); + } + + cancelOperation( + params: OperationCancelRequestParams, + {concurrentId, signal}: AxiosOptions = {}, + ) { + return this.post( + this.getPath('/operation/cancel'), + {}, + {...params}, + { + concurrentId, + requestConfig: {signal}, + }, + ); + } + + forgetOperation( + params: OperationForgetRequestParams, + {concurrentId, signal}: AxiosOptions = {}, + ) { + return this.post( + this.getPath('/operation/forget'), + {}, + {...params}, + { + concurrentId, + requestConfig: {signal}, + }, + ); + } +} diff --git a/src/services/api/pdisk.ts b/src/services/api/pdisk.ts new file mode 100644 index 000000000..7736e45eb --- /dev/null +++ b/src/services/api/pdisk.ts @@ -0,0 +1,91 @@ +import type {ModifyDiskResponse} from '../../types/api/modifyDisk'; +import type {EDecommitStatus, TPDiskInfoResponse} from '../../types/api/pdisk'; +import {createPDiskDeveloperUILink} from '../../utils/developerUI/developerUI'; + +import type {AxiosOptions} from './base'; +import {BaseYdbAPI} from './base'; + +export class PDiskAPI extends BaseYdbAPI { + restartPDisk({ + nodeId, + pDiskId, + force, + }: { + nodeId: number | string; + pDiskId: number | string; + force?: boolean; + }) { + return this.post( + this.getPath('/pdisk/restart'), + {}, + { + node_id: nodeId, + pdisk_id: pDiskId, + force, + }, + { + requestConfig: {'axios-retry': {retries: 0}}, + }, + ); + } + + changePDiskStatus({ + nodeId, + pDiskId, + force, + decommissionStatus, + }: { + nodeId: number | string; + pDiskId: number | string; + force?: boolean; + decommissionStatus?: EDecommitStatus; + }) { + return this.post( + this.getPath('/pdisk/status'), + { + decommit_status: decommissionStatus, + }, + { + node_id: nodeId, + pdisk_id: pDiskId, + force, + }, + { + requestConfig: {'axios-retry': {retries: 0}}, + }, + ); + } + + getPDiskInfo( + {nodeId, pDiskId}: {nodeId: string | number; pDiskId: string | number}, + {concurrentId, signal}: AxiosOptions = {}, + ) { + return this.get( + this.getPath('/pdisk/info'), + { + node_id: nodeId, + pdisk_id: pDiskId, + }, + {concurrentId, requestConfig: {signal}}, + ); + } + + restartPDiskOld({nodeId, pDiskId}: {nodeId: number | string; pDiskId: number | string}) { + const pDiskPath = createPDiskDeveloperUILink({ + nodeId, + pDiskId, + host: this.getPath(''), + }); + + return this.post( + pDiskPath, + 'restartPDisk=', + {}, + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + }, + }, + ); + } +} diff --git a/src/services/api/scheme.ts b/src/services/api/scheme.ts new file mode 100644 index 000000000..a12e47ce7 --- /dev/null +++ b/src/services/api/scheme.ts @@ -0,0 +1,20 @@ +import {BaseYdbAPI} from './base'; + +export class SchemeAPI extends BaseYdbAPI { + createSchemaDirectory( + {database, path}: {database: string; path: string}, + {signal}: {signal?: AbortSignal} = {}, + ) { + return this.post<{test: string}>( + this.getPath('/scheme/directory'), + {}, + { + database, + path, + }, + { + requestConfig: {signal}, + }, + ); + } +} diff --git a/src/services/api/storage.ts b/src/services/api/storage.ts new file mode 100644 index 000000000..85b904624 --- /dev/null +++ b/src/services/api/storage.ts @@ -0,0 +1,42 @@ +import type {GroupsRequestParams, StorageGroupsResponse} from '../../types/api/storage'; + +import type {AxiosOptions} from './base'; +import {BaseYdbAPI} from './base'; + +export class StorageAPI extends BaseYdbAPI { + getStorageGroups( + {nodeId, pDiskId, groupId, fieldsRequired, filter, ...params}: GroupsRequestParams, + {concurrentId, signal}: AxiosOptions = {}, + ) { + const preparedNodeId = Array.isArray(nodeId) + ? this.prepareArrayRequestParam(nodeId) + : nodeId; + + const preparedPDiskId = Array.isArray(pDiskId) + ? this.prepareArrayRequestParam(pDiskId) + : pDiskId; + + const preparedGroupId = Array.isArray(groupId) + ? this.prepareArrayRequestParam(groupId) + : groupId; + + const preparedFieldsRequired = Array.isArray(fieldsRequired) + ? this.prepareArrayRequestParam(fieldsRequired) + : fieldsRequired; + + return this.get( + this.getPath('/storage/groups'), + { + node_id: preparedNodeId, + pdisk_id: preparedPDiskId, + group_id: preparedGroupId, + fields_required: preparedFieldsRequired, + // Do not send empty string + filter: filter || undefined, + timeout: 20_000, + ...params, + }, + {concurrentId, requestConfig: {signal}}, + ); + } +} diff --git a/src/services/api/tablets.ts b/src/services/api/tablets.ts new file mode 100644 index 000000000..85c42e07c --- /dev/null +++ b/src/services/api/tablets.ts @@ -0,0 +1,90 @@ +import type {TTabletHiveResponse} from '../../types/api/tablet'; +import type {Nullable} from '../../utils/typecheckers'; + +import type {AxiosOptions} from './base'; +import {BaseYdbAPI} from './base'; + +export class TabletsAPI extends BaseYdbAPI { + evictVDiskOld({ + groupId, + groupGeneration, + failRealmIdx, + failDomainIdx, + vDiskIdx, + }: { + groupId: string | number; + groupGeneration: string | number; + failRealmIdx: string | number; + failDomainIdx: string | number; + vDiskIdx: string | number; + force?: boolean; + }) { + // BSC Id is constant for all ydb clusters + const BSC_TABLET_ID = '72057594037932033'; + + return this.post( + this.getPath(`/tablets/app?TabletID=${BSC_TABLET_ID}&exec=1`), + { + Command: { + ReassignGroupDisk: { + GroupId: groupId, + GroupGeneration: groupGeneration, + FailRealmIdx: failRealmIdx, + FailDomainIdx: failDomainIdx, + VDiskIdx: vDiskIdx, + }, + }, + }, + {}, + { + headers: { + // This handler requires exactly this string + // Automatic headers may not suit + Accept: 'application/json', + }, + }, + ); + } + + killTablet(id: string) { + return this.get( + this.getPath(`/tablets?KillTabletID=${id}`), + {}, + {requestConfig: {'axios-retry': {retries: 0}}}, + ); + } + + stopTablet(id: string, hiveId: string) { + return this.get( + this.getPath(`/tablets/app?TabletID=${hiveId}&page=StopTablet&tablet=${id}`), + {}, + {requestConfig: {'axios-retry': {retries: 0}}}, + ); + } + + resumeTablet(id: string, hiveId: string) { + return this.get( + this.getPath(`/tablets/app?TabletID=${hiveId}&page=ResumeTablet&tablet=${id}`), + {}, + {requestConfig: {'axios-retry': {retries: 0}}}, + ); + } + + getTabletFromHive( + {id, hiveId}: {id: string; hiveId: string}, + {concurrentId, signal}: AxiosOptions = {}, + ) { + return this.get>( + this.getPath('/tablets/app'), + { + TabletID: hiveId, + page: 'TabletInfo', + tablet: id, + }, + { + concurrentId, + requestConfig: {signal}, + }, + ); + } +} diff --git a/src/services/api/trace.ts b/src/services/api/trace.ts new file mode 100644 index 000000000..fe987e612 --- /dev/null +++ b/src/services/api/trace.ts @@ -0,0 +1,42 @@ +import {SECOND_IN_MS} from '../../utils/constants'; +import {isAxiosError} from '../../utils/response'; + +import type {AxiosOptions} from './base'; +import {BaseYdbAPI} from './base'; + +const TRACE_RETRY_DELAY = 4 * SECOND_IN_MS; +const TRACE_CHECK_TIMEOUT = 10 * SECOND_IN_MS; +const TRACE_API_ERROR_RETRY_DELAY = 10 * SECOND_IN_MS; +const MAX_TRACE_CHECK_RETRIES = 30; + +export class TraceAPI extends BaseYdbAPI { + checkTrace({url}: {url: string}, {concurrentId, signal}: AxiosOptions = {}) { + return this.get( + url, + {}, + { + concurrentId: concurrentId || 'checkTrace', + requestConfig: { + signal, + timeout: TRACE_CHECK_TIMEOUT, + 'axios-retry': { + retries: MAX_TRACE_CHECK_RETRIES, + retryDelay: (_: number, error: unknown) => { + const isTracingError = + isAxiosError(error) && + (error?.response?.status === 404 || error.code === 'ERR_NETWORK'); + + if (isTracingError) { + return TRACE_RETRY_DELAY; + } + + return TRACE_API_ERROR_RETRY_DELAY; + }, + shouldResetTimeout: true, + retryCondition: () => true, + }, + }, + }, + ); + } +} diff --git a/src/services/api/vdisk.ts b/src/services/api/vdisk.ts new file mode 100644 index 000000000..1d39c05da --- /dev/null +++ b/src/services/api/vdisk.ts @@ -0,0 +1,37 @@ +import type {ModifyDiskResponse} from '../../types/api/modifyDisk'; + +import {BaseYdbAPI} from './base'; + +export class VDiskAPI extends BaseYdbAPI { + evictVDisk({ + groupId, + groupGeneration, + failRealmIdx, + failDomainIdx, + vDiskIdx, + force, + }: { + groupId: string | number; + groupGeneration: string | number; + failRealmIdx: string | number; + failDomainIdx: string | number; + vDiskIdx: string | number; + force?: boolean; + }) { + return this.post( + this.getPath('/vdisk/evict'), + {}, + { + group_id: groupId, + group_generation_id: groupGeneration, + fail_realm_idx: failRealmIdx, + fail_domain_idx: failDomainIdx, + vdisk_idx: vDiskIdx, + force, + }, + { + requestConfig: {'axios-retry': {retries: 0}}, + }, + ); + } +} diff --git a/src/services/api/viewer.ts b/src/services/api/viewer.ts new file mode 100644 index 000000000..f073c226f --- /dev/null +++ b/src/services/api/viewer.ts @@ -0,0 +1,523 @@ +import type {PlanToSvgQueryParams} from '../../store/reducers/planToSvg'; +import type {TMetaInfo} from '../../types/api/acl'; +import type {TQueryAutocomplete} from '../../types/api/autocomplete'; +import type {CapabilitiesResponse} from '../../types/api/capabilities'; +import type {TClusterInfo} from '../../types/api/cluster'; +import type {DescribeConsumerResult} from '../../types/api/consumer'; +import type {FeatureFlagConfigs} from '../../types/api/featureFlags'; +import type {HealthCheckAPIResponse} from '../../types/api/healthcheck'; +import type {JsonHotKeysResponse} from '../../types/api/hotkeys'; +import type {TNetInfo} from '../../types/api/netInfo'; +import type {NodesRequestParams, TNodesInfo} from '../../types/api/nodes'; +import type {TEvNodesInfo} from '../../types/api/nodesList'; +import type {TEvPDiskStateResponse} from '../../types/api/pdisk'; +import type { + Actions, + ErrorResponse, + QueryAPIResponse, + Stats, + Timeout, + TracingLevel, +} from '../../types/api/query'; +import type {JsonRenderRequestParams, JsonRenderResponse} from '../../types/api/render'; +import type {TEvDescribeSchemeResult} from '../../types/api/schema'; +import type {StorageRequestParams, TStorageInfo} from '../../types/api/storage'; +import type {TEvSystemStateResponse} from '../../types/api/systemState'; +import type { + TDomainKey, + TEvTabletStateResponse, + UnmergedTEvTabletStateResponse, +} from '../../types/api/tablet'; +import type {TTenantInfo, TTenants} from '../../types/api/tenant'; +import type {DescribeTopicResult} from '../../types/api/topic'; +import type {TEvVDiskStateResponse} from '../../types/api/vdisk'; +import type {TUserToken} from '../../types/api/whoami'; +import type {QuerySyntax, TransactionMode} from '../../types/store/query'; +import {BINARY_DATA_IN_PLAIN_TEXT_DISPLAY} from '../../utils/constants'; +import type {Nullable} from '../../utils/typecheckers'; +import {settingsManager} from '../settings'; + +import {BaseYdbAPI} from './base'; +import type {AxiosOptions} from './base'; + +export class ViewerAPI extends BaseYdbAPI { + getClusterCapabilities({database}: {database?: string}) { + return this.get(this.getPath('/viewer/capabilities'), {database}, {}); + } + + getClusterInfo(clusterName?: string, {concurrentId, signal}: AxiosOptions = {}) { + return this.get( + this.getPath('/viewer/json/cluster'), + { + name: clusterName, + tablets: true, + }, + {concurrentId: concurrentId || `getClusterInfo`, requestConfig: {signal}}, + ); + } + + getNodeInfo(id?: string | number, {concurrentId, signal}: AxiosOptions = {}) { + return this.get( + this.getPath('/viewer/json/sysinfo?enums=true'), + { + node_id: id, + }, + {concurrentId, requestConfig: {signal}}, + ); + } + + getTenants(clusterName?: string, {concurrentId, signal}: AxiosOptions = {}) { + return this.get( + this.getPath('/viewer/json/tenantinfo'), + { + tablets: false, + storage: true, + cluster_name: clusterName, + }, + {concurrentId, requestConfig: {signal}}, + ); + } + + getTenantInfo( + {path, database = path}: {path: string; database?: string}, + {concurrentId, signal}: AxiosOptions = {}, + ) { + return this.get( + this.getPath('/viewer/json/tenantinfo'), + { + database, + path, + tablets: false, + storage: true, + memory: true, + }, + {concurrentId, requestConfig: {signal}}, + ); + } + + getNodes( + { + type = 'any', + tablets = false, + database, + tenant, + fieldsRequired, + filter, + ...params + }: NodesRequestParams, + {concurrentId, signal}: AxiosOptions = {}, + ) { + const preparedFieldsRequired = Array.isArray(fieldsRequired) + ? this.prepareArrayRequestParam(fieldsRequired) + : fieldsRequired; + + return this.get( + this.getPath('/viewer/json/nodes?enums=true'), + { + type, + tablets, + // Do not send empty string + filter: filter || undefined, + // TODO: remove after remove tenant param + database: database || tenant, + tenant: tenant || database, + fields_required: preparedFieldsRequired, + ...params, + }, + {concurrentId, requestConfig: {signal}}, + ); + } + + getTabletsInfo( + {nodeId, path, database}: {nodeId?: string | number; path?: string; database?: string}, + {concurrentId, signal}: AxiosOptions = {}, + ) { + return this.get( + this.getPath('/viewer/json/tabletinfo'), + { + database, + node_id: nodeId, + path, + enums: true, + }, + {concurrentId, requestConfig: {signal}}, + ); + } + + getSchema( + {path, database}: {path: string; database: string}, + {concurrentId, signal}: AxiosOptions = {}, + ) { + return this.get>( + this.getPath('/viewer/json/describe'), + { + database, + path, + enums: true, + backup: false, + private: true, + partition_config: false, + partition_stats: false, + partitioning_info: false, + subs: 1, + }, + {concurrentId, requestConfig: {signal}}, + ); + } + + getDescribe( + {path, database, timeout}: {path: string; database: string; timeout?: number}, + {concurrentId, signal}: AxiosOptions = {}, + ) { + return this.get>( + this.getPath('/viewer/json/describe'), + { + database, + path, + enums: true, + partition_stats: true, + subs: 0, + }, + {concurrentId: concurrentId || `getDescribe|${path}`, requestConfig: {signal}, timeout}, + ); + } + + getSchemaAcl( + {path, database}: {path: string; database: string}, + {concurrentId, signal}: AxiosOptions = {}, + ) { + return this.get( + this.getPath('/viewer/json/acl'), + { + database, + path, + merge_rules: true, + }, + {concurrentId, requestConfig: {signal}}, + ); + } + + getHeatmapData( + {path, database}: {path: string; database: string}, + {concurrentId, signal}: AxiosOptions = {}, + ) { + return this.get>( + this.getPath('/viewer/json/describe'), + { + database, + path, + enums: true, + backup: false, + children: false, + partition_config: false, + partition_stats: true, + }, + {concurrentId, requestConfig: {signal}}, + ); + } + + getNetwork( + {path, database}: {path: string; database: string}, + {concurrentId, signal}: AxiosOptions = {}, + ) { + return this.get( + this.getPath('/viewer/json/netinfo'), + { + enums: true, + database, + path, + }, + {concurrentId, requestConfig: {signal}}, + ); + } + + getTopic( + {path, database}: {path: string; database: string}, + {concurrentId, signal}: AxiosOptions = {}, + ) { + return this.get( + this.getPath('/viewer/json/describe_topic'), + { + enums: true, + include_stats: true, + database, + path, + }, + {concurrentId, requestConfig: {signal}}, + ); + } + + getConsumer( + {path, consumer, database}: {path: string; consumer: string; database: string}, + {concurrentId, signal}: AxiosOptions = {}, + ) { + return this.get( + this.getPath('/viewer/json/describe_consumer'), + { + enums: true, + include_stats: true, + database, + path, + consumer, + }, + {concurrentId: concurrentId || 'getConsumer', requestConfig: {signal}}, + ); + } + + getTablet( + {id, database}: {id: string; database?: string}, + {concurrentId, signal}: AxiosOptions = {}, + ) { + return this.get( + this.getPath('/viewer/json/tabletinfo'), + { + enums: true, + database, + filter: `(TabletId=${id})`, + }, + { + concurrentId, + requestConfig: {signal}, + }, + ); + } + + getTabletHistory( + {id, database}: {id: string; database?: string}, + {concurrentId, signal}: AxiosOptions = {}, + ) { + return this.get( + this.getPath('/viewer/json/tabletinfo'), + { + enums: true, + merge: false, + database, + filter: `(TabletId=${id})`, + }, + { + concurrentId, + requestConfig: {signal}, + }, + ); + } + + getNodesList({concurrentId, signal}: AxiosOptions = {}) { + return this.get( + this.getPath('/viewer/json/nodelist'), + { + enums: true, + }, + { + concurrentId, + requestConfig: {signal}, + }, + ); + } + + getTenantsList({concurrentId, signal}: AxiosOptions = {}) { + return this.get( + this.getPath('/viewer/json/tenants'), + { + enums: true, + state: 0, + }, + { + concurrentId, + requestConfig: {signal}, + }, + ); + } + + sendQuery( + params: { + query?: string; + database?: string; + action?: Action; + syntax?: QuerySyntax; + stats?: Stats; + tracingLevel?: TracingLevel; + transaction_mode?: TransactionMode; + timeout?: Timeout; + query_id?: string; + limit_rows?: number; + }, + {concurrentId, signal, withRetries}: AxiosOptions = {}, + ) { + const base64 = !settingsManager.readUserSettingsValue( + BINARY_DATA_IN_PLAIN_TEXT_DISPLAY, + true, + ); + + return this.post | ErrorResponse | null>( + this.getPath('/viewer/json/query'), + {...params, base64}, + {schema: 'multi', base64, timeout: params.timeout}, + { + concurrentId, + timeout: params.timeout, + requestConfig: { + signal, + 'axios-retry': {retries: withRetries ? this.DEFAULT_RETRIES_COUNT : 0}, + }, + headers: params.tracingLevel + ? { + 'X-Trace-Verbosity': params.tracingLevel, + } + : undefined, + }, + ); + } + + getHotKeys( + {path, database, enableSampling}: {path: string; database: string; enableSampling: boolean}, + {concurrentId, signal}: AxiosOptions = {}, + ) { + return this.get( + this.getPath('/viewer/json/hotkeys'), + { + database, + path, + enable_sampling: enableSampling, + }, + {concurrentId: concurrentId || 'getHotKeys', requestConfig: {signal}}, + ); + } + + getTabletDescribe(tenantId: TDomainKey, {concurrentId, signal}: AxiosOptions = {}) { + return this.get>( + this.getPath('/viewer/json/describe'), + { + schemeshard_id: tenantId?.SchemeShard, + path_id: tenantId?.PathId, + }, + {concurrentId, requestConfig: {signal}}, + ); + } + + getStorageInfo( + {tenant, database, nodeId, groupId, pDiskId, filter, ...params}: StorageRequestParams, + {concurrentId, signal}: AxiosOptions = {}, + ) { + return this.get( + this.getPath(`/viewer/json/storage?enums=true`), + { + database: database || tenant, + tenant: tenant || database, + node_id: nodeId, + group_id: groupId, + pdisk_id: pDiskId, + // Do not send empty string + filter: filter || undefined, + ...params, + }, + {concurrentId, requestConfig: {signal}}, + ); + } + + getChartData( + {target, from, until, maxDataPoints, database}: JsonRenderRequestParams, + {concurrentId, signal}: AxiosOptions = {}, + ) { + const requestString = `${target}&from=${from}&until=${until}&maxDataPoints=${maxDataPoints}&format=json`; + + return this.post( + this.getPath(`/viewer/json/render?database=${database}`), + requestString, + {}, + { + concurrentId, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + requestConfig: {signal}, + }, + ); + } + + whoami() { + return this.get(this.getPath('/viewer/json/whoami'), {}); + } + + autocomplete(params: {database: string; prefix?: string; limit?: number; table?: string[]}) { + const {table, ...rest} = params; + const tablesParam = table?.join(','); + return this.get( + this.getPath('/viewer/json/autocomplete'), + {...rest, table: tablesParam}, + {concurrentId: 'sql-autocomplete'}, + ); + } + + getClusterConfig(database?: string, {concurrentId, signal}: AxiosOptions = {}) { + return this.get( + this.getPath('/viewer/feature_flags'), + { + database, + }, + {concurrentId, requestConfig: {signal}}, + ); + } + + getVDiskInfo( + { + vDiskSlotId, + pDiskId, + nodeId, + }: { + vDiskSlotId: string | number; + pDiskId: string | number; + nodeId: string | number; + }, + {concurrentId, signal}: AxiosOptions = {}, + ) { + return this.get( + this.getPath('/viewer/json/vdiskinfo?enums=true'), + { + node_id: nodeId, + filter: `(PDiskId=${pDiskId};VDiskSlotId=${vDiskSlotId})`, + }, + {concurrentId, requestConfig: {signal}}, + ); + } + + getNodeWhiteboardPDiskInfo( + {nodeId, pDiskId}: {nodeId: string | number; pDiskId: string | number}, + {concurrentId, signal}: AxiosOptions = {}, + ) { + return this.get( + this.getPath('/viewer/json/pdiskinfo?enums=true'), + { + filter: `(NodeId=${nodeId}${pDiskId ? `;PDiskId=${pDiskId}` : ''})`, + }, + {concurrentId, requestConfig: {signal}}, + ); + } + + planToSvg({database, plan}: PlanToSvgQueryParams, {signal}: {signal?: AbortSignal} = {}) { + return this.post( + this.getPath('/viewer/plan2svg'), + plan, + {database}, + { + requestConfig: { + signal, + responseType: 'text', + headers: { + Accept: 'image/svg+xml', + }, + }, + }, + ); + } + + getHealthcheckInfo( + {database, maxLevel}: {database: string; maxLevel?: number}, + {concurrentId, signal}: AxiosOptions = {}, + ) { + return this.get( + this.getPath('/viewer/json/healthcheck?merge_records=true'), + {database, tenant: database, max_level: maxLevel}, + {concurrentId, requestConfig: {signal}}, + ); + } +} diff --git a/src/store/reducers/authentication/authentication.ts b/src/store/reducers/authentication/authentication.ts index a6dcc0eeb..1e42abfd8 100644 --- a/src/store/reducers/authentication/authentication.ts +++ b/src/store/reducers/authentication/authentication.ts @@ -52,7 +52,7 @@ export const authenticationApi = api.injectEndpoints({ whoami: build.query({ queryFn: async (_, {dispatch}) => { try { - const data = await window.api.whoami(); + const data = await window.api.viewer.whoami(); dispatch(setUser(data)); return {data}; } catch (error) { @@ -67,7 +67,7 @@ export const authenticationApi = api.injectEndpoints({ authenticate: build.mutation({ queryFn: async (params: {user: string; password: string}, {dispatch}) => { try { - const data = await window.api.authenticate(params); + const data = await window.api.auth.authenticate(params); dispatch(setIsAuthenticated(true)); return {data}; } catch (error) { @@ -79,7 +79,7 @@ export const authenticationApi = api.injectEndpoints({ logout: build.mutation({ queryFn: async (_, {dispatch}) => { try { - const data = await window.api.logout(); + const data = await window.api.auth.logout(); dispatch(setIsAuthenticated(false)); return {data}; } catch (error) { diff --git a/src/store/reducers/cancelQuery.ts b/src/store/reducers/cancelQuery.ts index 15876fce7..a892d8550 100644 --- a/src/store/reducers/cancelQuery.ts +++ b/src/store/reducers/cancelQuery.ts @@ -15,7 +15,7 @@ export const cancelQueryApi = api.injectEndpoints({ const action: CancelActions = 'cancel-query'; try { - const response = await window.api.sendQuery( + const response = await window.api.viewer.sendQuery( { database, action, diff --git a/src/store/reducers/capabilities/capabilities.ts b/src/store/reducers/capabilities/capabilities.ts index 827d27e1e..5ff82b977 100644 --- a/src/store/reducers/capabilities/capabilities.ts +++ b/src/store/reducers/capabilities/capabilities.ts @@ -10,7 +10,7 @@ export const capabilitiesApi = api.injectEndpoints({ getClusterCapabilities: build.query({ queryFn: async (params: {database?: string}) => { try { - const data = await window.api.getClusterCapabilities(params); + const data = await window.api.viewer.getClusterCapabilities(params); return {data}; } catch (error) { // If capabilities endpoint is not available, there will be an error diff --git a/src/store/reducers/cluster/cluster.ts b/src/store/reducers/cluster/cluster.ts index 031e720c2..9657e2ac7 100644 --- a/src/store/reducers/cluster/cluster.ts +++ b/src/store/reducers/cluster/cluster.ts @@ -67,7 +67,9 @@ export const clusterApi = api.injectEndpoints({ >({ queryFn: async (clusterName, {signal}) => { try { - const clusterData = await window.api.getClusterInfo(clusterName, {signal}); + const clusterData = window.api.meta + ? await window.api.meta.getClusterInfo(clusterName, {signal}) + : await window.api.viewer.getClusterInfo(clusterName, {signal}); const clusterRoot = clusterData.Domain; // Without domain we cannot get stats from system tables @@ -90,7 +92,7 @@ export const clusterApi = api.injectEndpoints({ // Normally query request should be fulfilled within 300-400ms even on very big clusters // Table with stats is supposed to be very small (less than 10 rows) // So we batch this request with cluster request to prevent possible layout shifts, if data is missing - const groupsStatsResponse = await window.api.sendQuery({ + const groupsStatsResponse = await window.api.viewer.sendQuery({ query: query, database: clusterRoot, action: 'execute-scan', @@ -118,7 +120,10 @@ export const clusterApi = api.injectEndpoints({ getClusterBaseInfo: builder.query({ queryFn: async (clusterName: string, {signal}) => { try { - const data = await window.api.getClusterBaseInfo(clusterName, {signal}); + if (!window.api.meta) { + throw new Error('Method is not implemented.'); + } + const data = await window.api.meta.getClusterBaseInfo(clusterName, {signal}); return {data}; } catch (error) { return {error}; diff --git a/src/store/reducers/clusters/clusters.ts b/src/store/reducers/clusters/clusters.ts index 7e3df18e9..f94dd45ee 100644 --- a/src/store/reducers/clusters/clusters.ts +++ b/src/store/reducers/clusters/clusters.ts @@ -32,8 +32,12 @@ export const clustersApi = api.injectEndpoints({ getClustersList: builder.query({ queryFn: async (_, {signal}) => { try { - const data = await window.api.getClustersList(undefined, {signal}); - return {data: prepareClustersData(data)}; + if (window.api.meta) { + const data = await window.api.meta.getClustersList(undefined, {signal}); + return {data: prepareClustersData(data)}; + } else { + throw new Error('Method is not implemented.'); + } } catch (error) { return {error}; } diff --git a/src/store/reducers/executeTopQueries/executeTopQueries.ts b/src/store/reducers/executeTopQueries/executeTopQueries.ts index 05fa9a195..4e0837d50 100644 --- a/src/store/reducers/executeTopQueries/executeTopQueries.ts +++ b/src/store/reducers/executeTopQueries/executeTopQueries.ts @@ -57,7 +57,7 @@ export const topQueriesApi = api.injectEndpoints({ }; try { - const response = await window.api.sendQuery( + const response = await window.api.viewer.sendQuery( { query: getQueryText(database, preparedFilters), database, @@ -110,7 +110,7 @@ export const topQueriesApi = api.injectEndpoints({ SessionStartAt LIMIT 100`; - const response = await window.api.sendQuery( + const response = await window.api.viewer.sendQuery( { query: queryText, database, diff --git a/src/store/reducers/healthcheckInfo/healthcheckInfo.ts b/src/store/reducers/healthcheckInfo/healthcheckInfo.ts index 233fad219..59ac7bba8 100644 --- a/src/store/reducers/healthcheckInfo/healthcheckInfo.ts +++ b/src/store/reducers/healthcheckInfo/healthcheckInfo.ts @@ -14,7 +14,7 @@ export const healthcheckApi = api.injectEndpoints({ {signal}, ) => { try { - const data = await window.api.getHealthcheckInfo( + const data = await window.api.viewer.getHealthcheckInfo( {database, maxLevel}, {signal}, ); diff --git a/src/store/reducers/heatmap.ts b/src/store/reducers/heatmap.ts index 333b8c7eb..c23cf80c8 100644 --- a/src/store/reducers/heatmap.ts +++ b/src/store/reducers/heatmap.ts @@ -46,8 +46,8 @@ export const heatmapApi = api.injectEndpoints({ ) => { try { const response = await Promise.all([ - window.api.getTabletsInfo({path, database}, {signal}), - window.api.getHeatmapData({path, database}, {signal}), + window.api.viewer.getTabletsInfo({path, database}, {signal}), + window.api.viewer.getHeatmapData({path, database}, {signal}), ]); const data = transformResponse(response); diff --git a/src/store/reducers/hotKeys/hotKeys.ts b/src/store/reducers/hotKeys/hotKeys.ts index 3e56e8dee..290d05de8 100644 --- a/src/store/reducers/hotKeys/hotKeys.ts +++ b/src/store/reducers/hotKeys/hotKeys.ts @@ -7,7 +7,7 @@ export const hotKeysApi = api.injectEndpoints({ queryFn: async ({path, database}, {signal}) => { try { // Send request that will trigger hot keys sampling (enable_sampling = true) - const initialResponse = await window.api.getHotKeys( + const initialResponse = await window.api.viewer.getHotKeys( {path, database, enableSampling: true}, {signal}, ); @@ -29,7 +29,7 @@ export const hotKeysApi = api.injectEndpoints({ ]); // And request these samples (enable_sampling = false) - const response = await window.api.getHotKeys( + const response = await window.api.viewer.getHotKeys( {path, database, enableSampling: false}, {signal}, ); diff --git a/src/store/reducers/network/network.ts b/src/store/reducers/network/network.ts index 13db34e31..a7f9bbcb6 100644 --- a/src/store/reducers/network/network.ts +++ b/src/store/reducers/network/network.ts @@ -5,7 +5,7 @@ export const networkApi = api.injectEndpoints({ getNetworkInfo: build.query({ queryFn: async (tenant: string, {signal}) => { try { - const data = await window.api.getNetwork( + const data = await window.api.viewer.getNetwork( {path: tenant, database: tenant}, {signal}, ); diff --git a/src/store/reducers/node/node.ts b/src/store/reducers/node/node.ts index fe596452d..0ba5a55c2 100644 --- a/src/store/reducers/node/node.ts +++ b/src/store/reducers/node/node.ts @@ -7,7 +7,7 @@ export const nodeApi = api.injectEndpoints({ getNodeInfo: build.query({ queryFn: async ({nodeId}: {nodeId: string}, {signal}) => { try { - const data = await window.api.getNodeInfo(nodeId, {signal}); + const data = await window.api.viewer.getNodeInfo(nodeId, {signal}); return {data: prepareNodeData(data)}; } catch (error) { return {error}; @@ -18,7 +18,7 @@ export const nodeApi = api.injectEndpoints({ getNodeStructure: build.query({ queryFn: async ({nodeId}: {nodeId: string}, {signal}) => { try { - const data = await window.api.getStorageInfo({nodeId}, {signal}); + const data = await window.api.viewer.getStorageInfo({nodeId}, {signal}); return {data}; } catch (error) { return {error}; diff --git a/src/store/reducers/nodes/nodes.ts b/src/store/reducers/nodes/nodes.ts index 2d54a394b..16034cd62 100644 --- a/src/store/reducers/nodes/nodes.ts +++ b/src/store/reducers/nodes/nodes.ts @@ -8,7 +8,7 @@ export const nodesApi = api.injectEndpoints({ getNodes: builder.query({ queryFn: async (params: NodesRequestParams, {signal}) => { try { - const data = await window.api.getNodes( + const data = await window.api.viewer.getNodes( { type: 'any', storage: false, diff --git a/src/store/reducers/nodesList.ts b/src/store/reducers/nodesList.ts index d7ee1725b..323623aba 100644 --- a/src/store/reducers/nodesList.ts +++ b/src/store/reducers/nodesList.ts @@ -10,7 +10,7 @@ export const nodesListApi = api.injectEndpoints({ getNodesList: build.query({ queryFn: async (_, {signal}) => { try { - const data = await window.api.getNodesList({signal}); + const data = await window.api.viewer.getNodesList({signal}); return {data}; } catch (error) { return {error}; diff --git a/src/store/reducers/operations.ts b/src/store/reducers/operations.ts index f1fed4147..b09abb5b0 100644 --- a/src/store/reducers/operations.ts +++ b/src/store/reducers/operations.ts @@ -11,7 +11,7 @@ export const operationsApi = api.injectEndpoints({ getOperationList: build.query({ queryFn: async (params: OperationListRequestParams, {signal}) => { try { - const data = await window.api.getOperationList(params, {signal}); + const data = await window.api.operation.getOperationList(params, {signal}); return {data}; } catch (error) { return {error}; @@ -22,7 +22,7 @@ export const operationsApi = api.injectEndpoints({ cancelOperation: build.mutation({ queryFn: async (params: OperationCancelRequestParams, {signal}) => { try { - const data = await window.api.cancelOperation(params, {signal}); + const data = await window.api.operation.cancelOperation(params, {signal}); return {data}; } catch (error) { return {error}; @@ -32,7 +32,7 @@ export const operationsApi = api.injectEndpoints({ forgetOperation: build.mutation({ queryFn: async (params: OperationForgetRequestParams, {signal}) => { try { - const data = await window.api.forgetOperation(params, {signal}); + const data = await window.api.operation.forgetOperation(params, {signal}); return {data}; } catch (error) { return {error}; diff --git a/src/store/reducers/overview/overview.ts b/src/store/reducers/overview/overview.ts index ebb7d2e83..47091c9c2 100644 --- a/src/store/reducers/overview/overview.ts +++ b/src/store/reducers/overview/overview.ts @@ -14,7 +14,7 @@ export const overviewApi = api.injectEndpoints({ try { const data = await Promise.all( paths.map((p) => - window.api.getDescribe( + window.api.viewer.getDescribe( { path: p, database, @@ -37,7 +37,7 @@ export const overviewApi = api.injectEndpoints({ {signal}, ) => { try { - const data = await window.api.getDescribe( + const data = await window.api.viewer.getDescribe( { path, database, diff --git a/src/store/reducers/partitions/partitions.ts b/src/store/reducers/partitions/partitions.ts index 3c4349348..b7150ff64 100644 --- a/src/store/reducers/partitions/partitions.ts +++ b/src/store/reducers/partitions/partitions.ts @@ -35,7 +35,7 @@ export const partitionsApi = api.injectEndpoints({ ) => { try { if (consumerName) { - const response = await window.api.getConsumer( + const response = await window.api.viewer.getConsumer( {path, database, consumer: consumerName}, {signal}, ); @@ -43,7 +43,10 @@ export const partitionsApi = api.injectEndpoints({ const data = prepareConsumerPartitions(rawPartitions); return {data}; } else { - const response = await window.api.getTopic({path, database}, {signal}); + const response = await window.api.viewer.getTopic( + {path, database}, + {signal}, + ); const rawPartitions = response.partitions; const data = prepareTopicPartitions(rawPartitions); return {data}; diff --git a/src/store/reducers/pdisk/pdisk.ts b/src/store/reducers/pdisk/pdisk.ts index 8f04e016a..47bb253e3 100644 --- a/src/store/reducers/pdisk/pdisk.ts +++ b/src/store/reducers/pdisk/pdisk.ts @@ -23,9 +23,9 @@ export const pDiskApi = api.injectEndpoints({ let diskInfoPromise: Promise; if (newApiAvailable) { - diskInfoPromise = window.api.getPDiskInfo({nodeId, pDiskId}, {signal}); + diskInfoPromise = window.api.pdisk.getPDiskInfo({nodeId, pDiskId}, {signal}); } else { - diskInfoPromise = window.api + diskInfoPromise = window.api.viewer .getNodeWhiteboardPDiskInfo({nodeId, pDiskId}, {signal}) .then((result) => { if (result.PDiskStateInfo) { @@ -45,7 +45,7 @@ export const pDiskApi = api.injectEndpoints({ try { const response = await Promise.all([ diskInfoPromise, - window.api.getNodeInfo(nodeId, {signal}), + window.api.viewer.getNodeInfo(nodeId, {signal}), ]); const data = preparePDiskDataResponse(response); return {data}; diff --git a/src/store/reducers/planToSvg.ts b/src/store/reducers/planToSvg.ts index 921987f93..5c0171dae 100644 --- a/src/store/reducers/planToSvg.ts +++ b/src/store/reducers/planToSvg.ts @@ -12,7 +12,7 @@ export const planToSvgApi = api.injectEndpoints({ planToSvgQuery: build.query({ queryFn: async ({plan, database}, {signal}) => { try { - const response = await window.api.planToSvg( + const response = await window.api.viewer.planToSvg( { database, plan, diff --git a/src/store/reducers/preview.ts b/src/store/reducers/preview.ts index f3bcf1e8c..dc0cf4769 100644 --- a/src/store/reducers/preview.ts +++ b/src/store/reducers/preview.ts @@ -15,7 +15,7 @@ export const previewApi = api.injectEndpoints({ sendQuery: build.query({ queryFn: async ({query, database, action, limitRows}: SendQueryParams, {signal}) => { try { - const response = await window.api.sendQuery( + const response = await window.api.viewer.sendQuery( {query, database, action, limit_rows: limitRows}, {signal, withRetries: true}, ); diff --git a/src/store/reducers/query/query.ts b/src/store/reducers/query/query.ts index 39c994a52..e67702f6d 100644 --- a/src/store/reducers/query/query.ts +++ b/src/store/reducers/query/query.ts @@ -198,7 +198,7 @@ export const queryApi = api.injectEndpoints({ try { const timeStart = Date.now(); - const response = await window.api.sendQuery( + const response = await window.api.viewer.sendQuery( { query, database, diff --git a/src/store/reducers/schema/schema.ts b/src/store/reducers/schema/schema.ts index d67f77174..537ceca87 100644 --- a/src/store/reducers/schema/schema.ts +++ b/src/store/reducers/schema/schema.ts @@ -35,7 +35,10 @@ export const schemaApi = api.injectEndpoints({ createDirectory: builder.mutation({ queryFn: async ({database, path}, {signal}) => { try { - const data = await window.api.createSchemaDirectory({database, path}, {signal}); + const data = await window.api.scheme.createSchemaDirectory( + {database, path}, + {signal}, + ); return {data}; } catch (error) { return {error}; @@ -48,7 +51,7 @@ export const schemaApi = api.injectEndpoints({ >({ queryFn: async ({path, database}, {signal}) => { try { - const data = await window.api.getSchema({path, database}, {signal}); + const data = await window.api.viewer.getSchema({path, database}, {signal}); if (!data) { return {error: new Error('Schema is not available')}; } diff --git a/src/store/reducers/schemaAcl/schemaAcl.ts b/src/store/reducers/schemaAcl/schemaAcl.ts index 949f0f802..9df58a5bd 100644 --- a/src/store/reducers/schemaAcl/schemaAcl.ts +++ b/src/store/reducers/schemaAcl/schemaAcl.ts @@ -5,7 +5,7 @@ export const schemaAclApi = api.injectEndpoints({ getSchemaAcl: build.query({ queryFn: async ({path, database}: {path: string; database: string}, {signal}) => { try { - const data = await window.api.getSchemaAcl({path, database}, {signal}); + const data = await window.api.viewer.getSchemaAcl({path, database}, {signal}); return { data: { acl: data.Common.ACL, diff --git a/src/store/reducers/shardsWorkload/shardsWorkload.ts b/src/store/reducers/shardsWorkload/shardsWorkload.ts index e59262bb1..4344dd0a7 100644 --- a/src/store/reducers/shardsWorkload/shardsWorkload.ts +++ b/src/store/reducers/shardsWorkload/shardsWorkload.ts @@ -131,7 +131,7 @@ export const shardApi = api.injectEndpoints({ {signal}, ) => { try { - const response = await window.api.sendQuery( + const response = await window.api.viewer.sendQuery( { query: filters?.mode === EShardsWorkloadMode.Immediate diff --git a/src/store/reducers/storage/requestStorageData.ts b/src/store/reducers/storage/requestStorageData.ts index 579773376..70478f3ae 100644 --- a/src/store/reducers/storage/requestStorageData.ts +++ b/src/store/reducers/storage/requestStorageData.ts @@ -1,4 +1,4 @@ -import type {AxiosOptions} from '../../../services/api'; +import type {AxiosOptions} from '../../../services/api/base'; import type {GroupsRequestParams, StorageRequestParams} from '../../../types/api/storage'; import {prepareGroupsResponse, prepareStorageResponse} from './utils'; @@ -12,10 +12,10 @@ export async function requestStorageData( options?: AxiosOptions, ) { if (shouldUseGroupsHandler && version !== 'v1') { - const result = await window.api.getStorageGroups({...params}, options); + const result = await window.api.storage.getStorageGroups({...params}, options); return prepareGroupsResponse(result); } else { - const result = await window.api.getStorageInfo({version, ...params}, options); + const result = await window.api.viewer.getStorageInfo({version, ...params}, options); return prepareStorageResponse(result); } } diff --git a/src/store/reducers/storage/storage.ts b/src/store/reducers/storage/storage.ts index d3054474a..b285b0b67 100644 --- a/src/store/reducers/storage/storage.ts +++ b/src/store/reducers/storage/storage.ts @@ -10,7 +10,7 @@ export const storageApi = api.injectEndpoints({ getStorageNodesInfo: builder.query({ queryFn: async (params: Omit, {signal}) => { try { - const result = await window.api.getNodes( + const result = await window.api.viewer.getNodes( {storage: true, type: 'static', ...params}, {signal}, ); diff --git a/src/store/reducers/tablet.ts b/src/store/reducers/tablet.ts index e3ba91e68..5ba14f781 100644 --- a/src/store/reducers/tablet.ts +++ b/src/store/reducers/tablet.ts @@ -13,9 +13,9 @@ export const tabletApi = api.injectEndpoints({ ) => { try { const [tabletResponseData, historyResponseData, nodesList] = await Promise.all([ - window.api.getTablet({id, database}, {signal}), - window.api.getTabletHistory({id, database}, {signal}), - window.api.getNodesList({signal}), + window.api.viewer.getTablet({id, database}, {signal}), + window.api.viewer.getTabletHistory({id, database}, {signal}), + window.api.viewer.getNodesList({signal}), ]); const nodeHostsMap = prepareNodeHostsMap(nodesList); @@ -65,7 +65,9 @@ export const tabletApi = api.injectEndpoints({ getTabletDescribe: build.query({ queryFn: async ({tenantId}: {tenantId: TDomainKey}, {signal}) => { try { - const tabletDescribe = await window.api.getTabletDescribe(tenantId, {signal}); + const tabletDescribe = await window.api.viewer.getTabletDescribe(tenantId, { + signal, + }); const {SchemeShard, PathId} = tenantId; const tenantPath = tabletDescribe?.Path || `${SchemeShard}:${PathId}`; @@ -79,7 +81,7 @@ export const tabletApi = api.injectEndpoints({ getAdvancedTableInfo: build.query({ queryFn: async ({id, hiveId}: {id: string; hiveId: string}, {signal}) => { try { - const tabletResponseData = await window.api.getTabletFromHive( + const tabletResponseData = await window.api.tablets.getTabletFromHive( {id, hiveId}, {signal}, ); @@ -96,7 +98,7 @@ export const tabletApi = api.injectEndpoints({ killTablet: build.mutation({ queryFn: async ({id}: {id: string}) => { try { - const data = await window.api.killTablet(id); + const data = await window.api.tablets.killTablet(id); return {data}; } catch (error) { return {error}; @@ -112,7 +114,7 @@ export const tabletApi = api.injectEndpoints({ stopTablet: build.mutation({ queryFn: async ({id, hiveId}: {id: string; hiveId: string}) => { try { - const data = await window.api.stopTablet(id, hiveId); + const data = await window.api.tablets.stopTablet(id, hiveId); return {data}; } catch (error) { return {error}; @@ -128,7 +130,7 @@ export const tabletApi = api.injectEndpoints({ resumeTablet: build.mutation({ queryFn: async ({id, hiveId}: {id: string; hiveId: string}) => { try { - const data = await window.api.resumeTablet(id, hiveId); + const data = await window.api.tablets.resumeTablet(id, hiveId); return {data}; } catch (error) { return {error}; diff --git a/src/store/reducers/tablets.ts b/src/store/reducers/tablets.ts index 12d901784..9da0a4c09 100644 --- a/src/store/reducers/tablets.ts +++ b/src/store/reducers/tablets.ts @@ -13,7 +13,7 @@ export const tabletsApi = api.injectEndpoints({ getTabletsInfo: build.query({ queryFn: async (params: TabletsApiRequestParams, {signal}) => { try { - const data = await window.api.getTabletsInfo(params, {signal}); + const data = await window.api.viewer.getTabletsInfo(params, {signal}); return {data}; } catch (error) { return {error}; diff --git a/src/store/reducers/tenant/tenant.ts b/src/store/reducers/tenant/tenant.ts index 1cad80f45..5d211b7bf 100644 --- a/src/store/reducers/tenant/tenant.ts +++ b/src/store/reducers/tenant/tenant.ts @@ -54,7 +54,7 @@ export const tenantApi = api.injectEndpoints({ getTenantInfo: builder.query({ queryFn: async ({path}: {path: string}, {signal}) => { try { - const tenantData = await window.api.getTenantInfo({path}, {signal}); + const tenantData = await window.api.viewer.getTenantInfo({path}, {signal}); return {data: tenantData.TenantInfo?.[0] ?? null}; } catch (error) { return {error}; @@ -65,7 +65,7 @@ export const tenantApi = api.injectEndpoints({ getClusterConfig: builder.query({ queryFn: async ({database}: {database: string}, {signal}) => { try { - const res = await window.api.getClusterConfig(database, {signal}); + const res = await window.api.viewer.getClusterConfig(database, {signal}); const db = res.Databases[0]; return {data: db.FeatureFlags}; diff --git a/src/store/reducers/tenantOverview/executeTopTables/executeTopTables.ts b/src/store/reducers/tenantOverview/executeTopTables/executeTopTables.ts index 4cc750b7f..068140da4 100644 --- a/src/store/reducers/tenantOverview/executeTopTables/executeTopTables.ts +++ b/src/store/reducers/tenantOverview/executeTopTables/executeTopTables.ts @@ -18,7 +18,7 @@ export const topTablesApi = api.injectEndpoints({ getTopTables: builder.query({ queryFn: async ({path}: {path: string}, {signal}) => { try { - const response = await window.api.sendQuery( + const response = await window.api.viewer.sendQuery( { query: getQueryText(path), database: path, diff --git a/src/store/reducers/tenantOverview/topShards/tenantOverviewTopShards.ts b/src/store/reducers/tenantOverview/topShards/tenantOverviewTopShards.ts index ed4b84456..c37961066 100644 --- a/src/store/reducers/tenantOverview/topShards/tenantOverviewTopShards.ts +++ b/src/store/reducers/tenantOverview/topShards/tenantOverviewTopShards.ts @@ -26,7 +26,7 @@ export const topShardsApi = api.injectEndpoints({ getTopShards: builder.query({ queryFn: async ({database, path = ''}: {database: string; path?: string}, {signal}) => { try { - const response = await window.api.sendQuery( + const response = await window.api.viewer.sendQuery( { query: createShardQuery(path, database), database, diff --git a/src/store/reducers/tenants/tenants.ts b/src/store/reducers/tenants/tenants.ts index 9cb363a14..d1c3ccd3b 100644 --- a/src/store/reducers/tenants/tenants.ts +++ b/src/store/reducers/tenants/tenants.ts @@ -27,7 +27,9 @@ export const tenantsApi = api.injectEndpoints({ getTenantsInfo: build.query({ queryFn: async ({clusterName}: {clusterName?: string}, {signal, getState}) => { try { - const response = await window.api.getTenants(clusterName, {signal}); + const response = window.api.meta + ? await window.api.meta.getTenants(clusterName, {signal}) + : await window.api.viewer.getTenants(clusterName, {signal}); let data: PreparedTenant[]; if (Array.isArray(response.TenantInfo)) { const {singleClusterMode} = getState() as RootState; diff --git a/src/store/reducers/topic.ts b/src/store/reducers/topic.ts index 107eefba7..48953c096 100644 --- a/src/store/reducers/topic.ts +++ b/src/store/reducers/topic.ts @@ -12,7 +12,7 @@ export const topicApi = api.injectEndpoints({ getTopic: build.query({ queryFn: async (params: {path: string; database: string}) => { try { - const data = await window.api.getTopic(params); + const data = await window.api.viewer.getTopic(params); // On older version it can return HTML page of Developer UI with an error if (typeof data !== 'object') { return {error: {}}; diff --git a/src/store/reducers/trace.ts b/src/store/reducers/trace.ts index 45969621c..afda3999b 100644 --- a/src/store/reducers/trace.ts +++ b/src/store/reducers/trace.ts @@ -10,7 +10,7 @@ export const traceApi = api.injectEndpoints({ checkTrace: build.query({ queryFn: async ({url}: CheckTraceParams, {signal, dispatch}) => { try { - const response = await window.api.checkTrace({url}, {signal}); + const response = await window.api.trace.checkTrace({url}, {signal}); dispatch(setQueryTraceReady()); return {data: response}; diff --git a/src/store/reducers/vdisk/vdisk.ts b/src/store/reducers/vdisk/vdisk.ts index a507f426c..4f5964a8c 100644 --- a/src/store/reducers/vdisk/vdisk.ts +++ b/src/store/reducers/vdisk/vdisk.ts @@ -15,9 +15,9 @@ export const vDiskApi = api.injectEndpoints({ queryFn: async ({nodeId, pDiskId, vDiskSlotId}: VDiskDataRequestParams, {signal}) => { try { const response = await Promise.all([ - window.api.getVDiskInfo({nodeId, pDiskId, vDiskSlotId}, {signal}), - window.api.getNodeWhiteboardPDiskInfo({nodeId, pDiskId}, {signal}), - window.api.getNodeInfo(nodeId, {signal}), + window.api.viewer.getVDiskInfo({nodeId, pDiskId, vDiskSlotId}, {signal}), + window.api.viewer.getNodeWhiteboardPDiskInfo({nodeId, pDiskId}, {signal}), + window.api.viewer.getNodeInfo(nodeId, {signal}), ]); const data = prepareVDiskDataResponse(response); return {data}; diff --git a/src/store/reducers/viewSchema/viewSchema.ts b/src/store/reducers/viewSchema/viewSchema.ts index 208135553..878c60b20 100644 --- a/src/store/reducers/viewSchema/viewSchema.ts +++ b/src/store/reducers/viewSchema/viewSchema.ts @@ -18,7 +18,7 @@ export const viewSchemaApi = api.injectEndpoints({ timeout?: number; }) => { try { - const response = await window.api.sendQuery( + const response = await window.api.viewer.sendQuery( { query: createViewSchemaQuery(path), database, diff --git a/src/types/window.d.ts b/src/types/window.d.ts index c58bff6bf..a31c06ad2 100644 --- a/src/types/window.d.ts +++ b/src/types/window.d.ts @@ -41,5 +41,5 @@ interface Window { userSettings?: import('../services/settings').SettingsObject; systemSettings?: import('../services/settings').SettingsObject; - api: import('../services/api').YdbEmbeddedAPI; + api: import('../services/api/index').YdbEmbeddedAPI; } diff --git a/src/utils/monaco/yql/generateSuggestions.ts b/src/utils/monaco/yql/generateSuggestions.ts index 77f44f649..efe4fa3dc 100644 --- a/src/utils/monaco/yql/generateSuggestions.ts +++ b/src/utils/monaco/yql/generateSuggestions.ts @@ -220,7 +220,7 @@ export async function generateColumnsSuggestion( // remove duplicates if any const filteredTableNames = Array.from(new Set(normalizedTableNames)); - const autocompleteResponse = await window.api.autocomplete({ + const autocompleteResponse = await window.api.viewer.autocomplete({ database, table: filteredTableNames, limit: 1000, @@ -345,7 +345,11 @@ export async function generateEntitiesSuggestion( prefix?: string, ): Promise { const normalizedPrefix = normalizeEntityPrefix(prefix, database); - const data = await window.api.autocomplete({database, prefix: normalizedPrefix, limit: 1000}); + const data = await window.api.viewer.autocomplete({ + database, + prefix: normalizedPrefix, + limit: 1000, + }); const withBackticks = prefix?.startsWith('`'); if (data.Success) { const filteredEntities = filterAutocompleteEntities(data.Result.Entities, suggestEntities);