feat(plugin-data-viewer): CB-1914 add quotas service

This commit is contained in:
Naumov Alexey
2022-04-18 12:04:42 +02:00
parent 8ca6b2d1ae
commit 382e50186b
11 changed files with 109 additions and 36 deletions
@@ -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];
}
}
+1
View File
@@ -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;
@@ -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 />
@@ -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);
}