mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-24 11:35:54 +08:00
feat(plugin-data-viewer): CB-1914 add quotas service
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
|
||||
interface IQuotas {
|
||||
dataExportFileSizeLimit: number;
|
||||
sqlMaxRunningQueries: number;
|
||||
sqlResultSetRowsLimit: number;
|
||||
sqlResultSetMemoryLimit: number;
|
||||
sqlTextPreviewMaxLength: number;
|
||||
sqlBinaryPreviewMaxLength: number;
|
||||
}
|
||||
|
||||
type QuotaKey = keyof IQuotas;
|
||||
|
||||
const DEFAULT_QUOTAS: IQuotas = {
|
||||
dataExportFileSizeLimit: 1.0E7,
|
||||
sqlMaxRunningQueries: 100,
|
||||
sqlResultSetRowsLimit: 100000,
|
||||
sqlResultSetMemoryLimit: 2000000,
|
||||
sqlTextPreviewMaxLength: 4096,
|
||||
sqlBinaryPreviewMaxLength: 261120,
|
||||
};
|
||||
|
||||
function isNumber(value: any): value is number {
|
||||
return typeof value === 'number';
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class QuotasService {
|
||||
get quotas(): IQuotas {
|
||||
return {
|
||||
dataExportFileSizeLimit: this.getQuota('dataExportFileSizeLimit'),
|
||||
sqlMaxRunningQueries: this.getQuota('sqlMaxRunningQueries'),
|
||||
sqlResultSetRowsLimit: this.getQuota('sqlResultSetRowsLimit'),
|
||||
sqlResultSetMemoryLimit: this.getQuota('sqlResultSetMemoryLimit'),
|
||||
sqlTextPreviewMaxLength: this.getQuota('sqlTextPreviewMaxLength'),
|
||||
sqlBinaryPreviewMaxLength: this.getQuota('sqlBinaryPreviewMaxLength'),
|
||||
};
|
||||
}
|
||||
|
||||
constructor(private readonly serverConfigResource: ServerConfigResource) { }
|
||||
|
||||
getQuota(key: QuotaKey) {
|
||||
const serverQuota = this.serverConfigResource.data?.resourceQuotas[key];
|
||||
|
||||
if (isNumber(serverQuota)) {
|
||||
return serverQuota;
|
||||
}
|
||||
|
||||
return DEFAULT_QUOTAS[key];
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,7 @@ export * from './NavigationTree/ElementsTree/useElementsTree';
|
||||
export * from './NavigationTree/NavigationTreeBootstrap';
|
||||
export * from './NavigationTree/NavigationTreeService';
|
||||
export * from './AppLocaleService';
|
||||
export * from './QuotasService';
|
||||
|
||||
// Models
|
||||
export * from './shared/NavigationTabs/ITab';
|
||||
|
||||
@@ -42,7 +42,8 @@ import {
|
||||
SessionExpireWarningDialogService,
|
||||
ConnectionSchemaManagerBootstrap,
|
||||
NavigationTreeBootstrap,
|
||||
ToolsPanelService
|
||||
ToolsPanelService,
|
||||
QuotasService
|
||||
} from '@cloudbeaver/core-app';
|
||||
import { coreAuthenticationManifest } from '@cloudbeaver/core-authentication';
|
||||
import { BlocksLocaleService } from '@cloudbeaver/core-blocks';
|
||||
@@ -85,6 +86,7 @@ export const coreManifests: PluginManifest[] = [
|
||||
ProductManagerService,
|
||||
PluginManagerService,
|
||||
CoreSettingsService,
|
||||
QuotasService,
|
||||
CommonDialogService,
|
||||
SessionExpireWarningDialogService,
|
||||
SessionExpiredDialogService,
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
import { computed, makeObservable, observable } from 'mobx';
|
||||
|
||||
import type { QuotasService } from '@cloudbeaver/core-app';
|
||||
import type { ConnectionExecutionContextService, IConnectionExecutionContext, IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections';
|
||||
import type { ITask } from '@cloudbeaver/core-executor';
|
||||
import type { ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
import { AsyncTaskInfoService, GraphQLService, ResultDataFormat, SqlExecuteInfo, SqlQueryResults, UpdateResultsDataBatchMutationVariables } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { DocumentEditAction } from './DatabaseDataModel/Actions/Document/DocumentEditAction';
|
||||
@@ -37,11 +37,11 @@ export class ContainerDataSource extends DatabaseDataSource<IDataContainerOption
|
||||
private readonly graphQLService: GraphQLService,
|
||||
private readonly asyncTaskInfoService: AsyncTaskInfoService,
|
||||
private readonly connectionExecutionContextService: ConnectionExecutionContextService,
|
||||
private readonly serverConfigResource: ServerConfigResource,
|
||||
private readonly quotasService: QuotasService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.dataManager = new DatabaseDataManager(this.graphQLService, this.serverConfigResource, this);
|
||||
this.dataManager = new DatabaseDataManager(this.graphQLService, this.quotasService, this);
|
||||
this.currentTask = null;
|
||||
this.executionContext = null;
|
||||
|
||||
|
||||
@@ -6,10 +6,9 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { EObjectFeature, NavNode } from '@cloudbeaver/core-app';
|
||||
import { EObjectFeature, NavNode, QuotasService } from '@cloudbeaver/core-app';
|
||||
import { ConnectionExecutionContextService, Connection } from '@cloudbeaver/core-connections';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
import { AsyncTaskInfoService, GraphQLService } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { ContainerDataSource, IDataContainerOptions } from './ContainerDataSource';
|
||||
@@ -29,7 +28,7 @@ export class DataViewerTableService {
|
||||
private readonly asyncTaskInfoService: AsyncTaskInfoService,
|
||||
private readonly connectionExecutionContextService: ConnectionExecutionContextService,
|
||||
private readonly dataViewerService: DataViewerService,
|
||||
private readonly serverConfigResource: ServerConfigResource
|
||||
private readonly quotasService: QuotasService
|
||||
) { }
|
||||
|
||||
has(tableId: string): boolean {
|
||||
@@ -57,7 +56,7 @@ export class DataViewerTableService {
|
||||
this.graphQLService,
|
||||
this.asyncTaskInfoService,
|
||||
this.connectionExecutionContextService,
|
||||
this.serverConfigResource
|
||||
this.quotasService
|
||||
);
|
||||
|
||||
source
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { computed, makeObservable, observable } from 'mobx';
|
||||
import { makeObservable, observable } from 'mobx';
|
||||
|
||||
import type { ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
import type { QuotasService } from '@cloudbeaver/core-app';
|
||||
import type { GraphQLService } from '@cloudbeaver/core-sdk';
|
||||
import { download, GlobalConstants } from '@cloudbeaver/core-utils';
|
||||
|
||||
@@ -24,19 +24,14 @@ import type { IDatabaseDataSource } from './IDatabaseDataSource';
|
||||
import type { IDatabaseResultSet } from './IDatabaseResultSet';
|
||||
|
||||
const RESULT_VALUE_PATH = 'sql-result-value';
|
||||
const BINARY_MAX_LENGTH_DEFAULT = 261120;
|
||||
|
||||
export class DatabaseDataManager implements IDatabaseDataManager {
|
||||
private readonly cache: Map<string, string>;
|
||||
activeElement: IResultSetElementKey | null;
|
||||
|
||||
get binaryMaxLength() {
|
||||
return this.serverConfigResource.resourceQuotas.sqlBinaryPreviewMaxLength ?? BINARY_MAX_LENGTH_DEFAULT;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly graphQLService: GraphQLService,
|
||||
private readonly serverConfigResource: ServerConfigResource,
|
||||
private readonly quotasService: QuotasService,
|
||||
private readonly source: IDatabaseDataSource<any, any>
|
||||
) {
|
||||
this.cache = new Map();
|
||||
@@ -45,7 +40,6 @@ export class DatabaseDataManager implements IDatabaseDataManager {
|
||||
makeObservable<this, 'cache'>(this, {
|
||||
cache: observable,
|
||||
activeElement: observable.ref,
|
||||
binaryMaxLength: computed,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -57,7 +51,7 @@ export class DatabaseDataManager implements IDatabaseDataManager {
|
||||
}
|
||||
|
||||
isContentTruncated(content: IResultSetContentValue) {
|
||||
return (content.contentLength ?? 0) > this.binaryMaxLength;
|
||||
return (content.contentLength ?? 0) > this.quotasService.getQuota('sqlBinaryPreviewMaxLength');
|
||||
}
|
||||
|
||||
async getFileDataUrl(element: IResultSetElementKey, resultIndex: number) {
|
||||
|
||||
@@ -10,7 +10,6 @@ import type { IResultSetContentValue } from './Actions/ResultSet/IResultSetConte
|
||||
import type { IResultSetElementKey } from './Actions/ResultSet/IResultSetDataKey';
|
||||
|
||||
export interface IDatabaseDataManager {
|
||||
readonly binaryMaxLength: number;
|
||||
activeElement: IResultSetElementKey | null;
|
||||
isContent: (element: IResultSetElementKey, resultIndex: number) => boolean;
|
||||
isContentTruncated: (content: IResultSetContentValue) => boolean;
|
||||
|
||||
+14
-5
@@ -10,10 +10,13 @@ import { action, computed, observable } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { EAdminPermission } from '@cloudbeaver/core-administration';
|
||||
import { QuotasService } from '@cloudbeaver/core-app';
|
||||
import { IconOrImage, Link, useObservableRef } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import { usePermission } from '@cloudbeaver/core-root';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
import type { TabContainerPanelComponent } from '@cloudbeaver/core-ui';
|
||||
import { bytesToSize, download, getMIME, isImageFormat, isValidUrl } from '@cloudbeaver/core-utils';
|
||||
@@ -53,7 +56,7 @@ const styles = css`
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
Link {
|
||||
limit {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
@@ -118,7 +121,9 @@ export const ImageValuePresentation: TabContainerPanelComponent<IDataValuePanelP
|
||||
}) {
|
||||
const translate = useTranslate();
|
||||
const notificationService = useService(NotificationService);
|
||||
const quotasService = useService(QuotasService);
|
||||
const style = useStyles(styles);
|
||||
const admin = usePermission(EAdminPermission.admin);
|
||||
|
||||
const state = useObservableRef(() => ({
|
||||
get selectedCell() {
|
||||
@@ -190,7 +195,7 @@ export const ImageValuePresentation: TabContainerPanelComponent<IDataValuePanelP
|
||||
const loading = model.isLoading();
|
||||
|
||||
if (state.truncated && !state.savedSrc) {
|
||||
const limit = bytesToSize(model.source.dataManager.binaryMaxLength);
|
||||
const limit = bytesToSize(quotasService.getQuota('sqlBinaryPreviewMaxLength'));
|
||||
const valueSize = bytesToSize((state.cellValue as unknown as IResultSetContentValue).contentLength ?? 0);
|
||||
|
||||
const load = async () => {
|
||||
@@ -213,9 +218,13 @@ export const ImageValuePresentation: TabContainerPanelComponent<IDataValuePanelP
|
||||
{translate('data_viewer_presentation_value_content_was_truncated')}
|
||||
<p>
|
||||
{translate('data_viewer_presentation_value_content_truncated_placeholder') + ' '}
|
||||
<Link href='https://cloudbeaver.io/docs/Server-configuration#resource-quotas' target='_blank' indicator>
|
||||
{translate('ui_limit')}
|
||||
</Link>
|
||||
<limit>
|
||||
{admin ? (
|
||||
<Link href='https://cloudbeaver.io/docs/Server-configuration#resource-quotas' target='_blank' indicator>
|
||||
{translate('ui_limit')}
|
||||
</Link>
|
||||
) : translate('ui_limit')}
|
||||
</limit>
|
||||
</p>
|
||||
{`${translate('ui_limit')}: ${limit}`}
|
||||
<br />
|
||||
|
||||
+14
-5
@@ -10,10 +10,13 @@ import { observable } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { EAdminPermission } from '@cloudbeaver/core-administration';
|
||||
import { QuotasService } from '@cloudbeaver/core-app';
|
||||
import { BASE_CONTAINERS_STYLES, IconOrImage, Link, Textarea, useObservableRef } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import { usePermission } from '@cloudbeaver/core-root';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
import { BASE_TAB_STYLES, TabContainerPanelComponent, TabList, TabsState, UNDERLINE_TAB_STYLES } from '@cloudbeaver/core-ui';
|
||||
import { bytesToSize } from '@cloudbeaver/core-utils';
|
||||
@@ -58,7 +61,7 @@ const styles = css`
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
Link {
|
||||
limit {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
CodeEditorLoader {
|
||||
@@ -86,8 +89,10 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
|
||||
}) {
|
||||
const translate = useTranslate();
|
||||
const notificationService = useService(NotificationService);
|
||||
const quotasService = useService(QuotasService);
|
||||
const textValuePresentationService = useService(TextValuePresentationService);
|
||||
const style = useStyles(styles, BASE_CONTAINERS_STYLES, UNDERLINE_TAB_STYLES, VALUE_PANEL_TOOLS_STYLES);
|
||||
const admin = usePermission(EAdminPermission.admin);
|
||||
const state = useObservableRef(() => ({
|
||||
currentContentType: 'text/plain',
|
||||
lastContentType: 'text/plain',
|
||||
@@ -133,7 +138,7 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
|
||||
valueTruncated = model.source.dataManager.isContentTruncated(value);
|
||||
|
||||
if (valueTruncated) {
|
||||
limit = bytesToSize(model.source.dataManager.binaryMaxLength);
|
||||
limit = bytesToSize(quotasService.getQuota('sqlBinaryPreviewMaxLength'));
|
||||
valueSize = bytesToSize(value.contentLength ?? 0);
|
||||
}
|
||||
|
||||
@@ -222,9 +227,13 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
|
||||
{translate('data_viewer_presentation_value_content_was_truncated')}
|
||||
<p>
|
||||
{translate('data_viewer_presentation_value_content_truncated_placeholder') + ' '}
|
||||
<Link href='https://cloudbeaver.io/docs/Server-configuration#resource-quotas' target='_blank' indicator>
|
||||
{translate('ui_limit')}
|
||||
</Link>
|
||||
<limit>
|
||||
{admin ? (
|
||||
<Link href='https://cloudbeaver.io/docs/Server-configuration#resource-quotas' target='_blank' indicator>
|
||||
{translate('ui_limit')}
|
||||
</Link>
|
||||
) : translate('ui_limit')}
|
||||
</limit>
|
||||
</p>
|
||||
{`${translate('ui_limit')}: ${limit}`}
|
||||
<br />
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
import { observable, makeObservable } from 'mobx';
|
||||
|
||||
import type { QuotasService } from '@cloudbeaver/core-app';
|
||||
import type { IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections';
|
||||
import type { ITask } from '@cloudbeaver/core-executor';
|
||||
import type { ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
import { AsyncTaskInfoService, GraphQLService, ResultDataFormat, SqlExecuteInfo, SqlQueryResults, UpdateResultsDataBatchMutationVariables } from '@cloudbeaver/core-sdk';
|
||||
import { DatabaseDataManager, DatabaseDataSource, DocumentEditAction, IDatabaseDataManager, IDatabaseDataOptions, IDatabaseResultSet, IRequestInfo, ResultSetEditAction } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
@@ -34,11 +34,11 @@ export class QueryDataSource extends DatabaseDataSource<IDataQueryOptions, IData
|
||||
constructor(
|
||||
private readonly graphQLService: GraphQLService,
|
||||
private readonly asyncTaskInfoService: AsyncTaskInfoService,
|
||||
private readonly serverConfigResource: ServerConfigResource
|
||||
private readonly quotasService: QuotasService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.dataManager = new DatabaseDataManager(this.graphQLService, this.serverConfigResource, this);
|
||||
this.dataManager = new DatabaseDataManager(this.graphQLService, this.quotasService, this);
|
||||
this.currentTask = null;
|
||||
this.requestInfo = {
|
||||
requestDuration: 0,
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
|
||||
import { makeObservable, observable } from 'mobx';
|
||||
|
||||
import { QuotasService } from '@cloudbeaver/core-app';
|
||||
import { ConnectionExecutionContextService, ConnectionInfoResource } from '@cloudbeaver/core-connections';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
import { AsyncTaskInfoService, GraphQLService } from '@cloudbeaver/core-sdk';
|
||||
import {
|
||||
DatabaseDataAccessMode, DatabaseDataModel, DatabaseEditAction, DataViewerDataChangeConfirmationService,
|
||||
@@ -49,7 +49,7 @@ export class SqlQueryService {
|
||||
private readonly asyncTaskInfoService: AsyncTaskInfoService,
|
||||
private readonly dataViewerDataChangeConfirmationService: DataViewerDataChangeConfirmationService,
|
||||
private readonly dataViewerService: DataViewerService,
|
||||
private readonly serverConfigResource: ServerConfigResource
|
||||
private readonly quotasService: QuotasService
|
||||
) {
|
||||
this.statisticsMap = new Map();
|
||||
|
||||
@@ -83,7 +83,7 @@ export class SqlQueryService {
|
||||
let tabGroup = this.sqlQueryResultService.getSelectedGroup(editorState);
|
||||
|
||||
if (inNewTab || !tabGroup) {
|
||||
source = new QueryDataSource(this.graphQLService, this.asyncTaskInfoService, this.serverConfigResource);
|
||||
source = new QueryDataSource(this.graphQLService, this.asyncTaskInfoService, this.quotasService);
|
||||
model = this.tableViewerStorageService.add(new DatabaseDataModel(source));
|
||||
this.dataViewerDataChangeConfirmationService.trackTableDataUpdate(model.id);
|
||||
tabGroup = this.sqlQueryResultService.createGroup(editorState, model.id, query);
|
||||
@@ -173,7 +173,7 @@ export class SqlQueryService {
|
||||
options?.onQueryExecutionStart?.(query, i);
|
||||
|
||||
if (!model || !source) {
|
||||
source = new QueryDataSource(this.graphQLService, this.asyncTaskInfoService, this.serverConfigResource);
|
||||
source = new QueryDataSource(this.graphQLService, this.asyncTaskInfoService, this.quotasService);
|
||||
model = this.tableViewerStorageService.add(new DatabaseDataModel(source));
|
||||
this.dataViewerDataChangeConfirmationService.trackTableDataUpdate(model.id);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user