Merge branch 'devel' into dbeaver/pro#5476-task-scheduler-refactor

This commit is contained in:
kseniaguzeeva
2025-10-27 16:02:04 +03:00
committed by GitHub
23 changed files with 312 additions and 20 deletions
@@ -41,6 +41,7 @@ import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.dbeaver.utils.RuntimeUtils;
import org.jkiss.utils.CommonUtils;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
@@ -204,7 +205,7 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
initializeApplicationServices();
try {
try {
this.instanceServer = new CloudBeaverInstanceServer();
this.instanceServer = createInstanceServer();
} catch (Exception e) {
log.error("Error initializing instance server", e);
}
@@ -216,6 +217,10 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
return EXIT_OK;
}
protected CloudBeaverInstanceServer createInstanceServer() throws IOException {
return new CloudBeaverInstanceServer();
}
protected abstract void startServer() throws DBException;
@Override
@@ -16,20 +16,41 @@
*/
package io.cloudbeaver.model.cli;
import org.jkiss.dbeaver.model.cli.ApplicationCommandLine;
import org.jkiss.dbeaver.model.cli.ApplicationInstanceController;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.cli.*;
import org.jkiss.dbeaver.model.cli.model.CommandLineAuthenticator;
import picocli.CommandLine;
public class CloudBeaverCommandLine extends ApplicationCommandLine<ApplicationInstanceController> {
private static CloudBeaverCommandLine INSTANCE = null;
private CloudBeaverCommandLine() {
@Nullable
private final CommandLineAuthenticator authenticator;
public CloudBeaverCommandLine(@Nullable CommandLineAuthenticator authenticator) {
super();
this.authenticator = authenticator;
}
public synchronized static CloudBeaverCommandLine getInstance() {
if (INSTANCE == null) {
INSTANCE = new CloudBeaverCommandLine();
@Override
protected CloudBeaverTopLevelCommand createTopLevelCommand(
@Nullable ApplicationInstanceController applicationInstanceController,
@NotNull CommandLineContext context,
@NotNull CLIRunMeta runMeta
) {
if (authenticator != null) {
context.setContextParameter(CLIConstants.CONTEXT_PARAM_AUTHENTICATOR, authenticator);
}
return INSTANCE;
return new CloudBeaverTopLevelCommand(applicationInstanceController, context, runMeta);
}
@NotNull
@Override
protected CommandLine initCommandLine(
@Nullable ApplicationInstanceController applicationInstanceController,
@NotNull CommandLineContext context,
@NotNull CLIRunMeta runMeta
) {
return super.initCommandLine(applicationInstanceController, context, runMeta);
}
}
@@ -16,7 +16,7 @@
*/
package io.cloudbeaver.model.cli;
import org.apache.commons.cli.CommandLine;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.cli.ApplicationInstanceController;
import org.jkiss.dbeaver.model.cli.ApplicationInstanceServer;
@@ -25,20 +25,27 @@ import org.jkiss.dbeaver.model.cli.CLIProcessResult;
import java.io.IOException;
public class CloudBeaverInstanceServer extends ApplicationInstanceServer<ApplicationInstanceController> {
private final CloudBeaverCommandLine commandLine;
public CloudBeaverInstanceServer() throws IOException {
super(ApplicationInstanceController.class);
this.commandLine = new CloudBeaverCommandLine(null);
}
protected CloudBeaverInstanceServer(@NotNull CloudBeaverCommandLine commandLine) throws IOException {
super(ApplicationInstanceController.class);
this.commandLine = commandLine;
}
@NotNull
@Override
public CLIProcessResult handleCommandLine(@NotNull String[] args) {
CommandLine cmd = CloudBeaverCommandLine.getInstance().getCommandLine(args);
try {
return CloudBeaverCommandLine.getInstance().executeCommandLineCommands(
cmd,
return commandLine.executeCommandLineCommands(
this,
false,
false
false,
args
);
} catch (Exception e) {
return new CLIProcessResult(CLIProcessResult.PostAction.ERROR, "Error executing command: " + e.getMessage());
@@ -0,0 +1,37 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.model.cli;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.cli.ApplicationInstanceController;
import org.jkiss.dbeaver.model.cli.CLIRunMeta;
import org.jkiss.dbeaver.model.cli.CommandLineContext;
import org.jkiss.dbeaver.model.cli.command.AbstractTopLevelCommand;
import picocli.CommandLine;
@CommandLine.Command(name = "cbvr", description = "CloudBeaver commands", mixinStandardHelpOptions = true)
public class CloudBeaverTopLevelCommand extends AbstractTopLevelCommand {
protected CloudBeaverTopLevelCommand(
@Nullable ApplicationInstanceController controller,
@NotNull CommandLineContext context,
@NotNull CLIRunMeta meta
) {
super(controller, context, meta);
}
}
@@ -254,8 +254,12 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
@Property
public long getRemainingTime() {
if (application instanceof ServletAuthApplication authApplication) {
return authApplication.getMaxSessionIdleTime() + lastAccessTime - System.currentTimeMillis();
return getMaxSessionIdleTime(authApplication) + lastAccessTime - System.currentTimeMillis();
}
return Integer.MAX_VALUE;
}
protected long getMaxSessionIdleTime(@NotNull ServletAuthApplication authApplication) {
return authApplication.getMaxSessionIdleTime();
}
}
@@ -190,6 +190,10 @@ public class WebUserContext implements SMCredentialsProvider {
return isAuthorizedInSecurityManager() && getUser() != null;
}
public synchronized boolean isAnonymousUserAuthorizedInSM() {
return isAuthorizedInSecurityManager() && getUser() == null;
}
@Nullable
public synchronized WebUser getUser() {
return user;
@@ -304,14 +304,11 @@ public class CBSessionManager implements WebAppSessionManager {
}
public void expireIdleSessions() {
long maxSessionIdleTime = application.getMaxSessionIdleTime();
List<BaseWebSession> expiredList = new ArrayList<>();
synchronized (sessionMap) {
for (Iterator<BaseWebSession> iterator = sessionMap.values().iterator(); iterator.hasNext(); ) {
var session = iterator.next();
long idleMillis = System.currentTimeMillis() - session.getLastAccessTimeMillis();
if (idleMillis >= maxSessionIdleTime) {
if (!session.isValid()) {
iterator.remove();
expiredList.add(session);
}
@@ -0,0 +1,101 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 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 { observer } from 'mobx-react-lite';
import type { DialogComponentProps } from '@cloudbeaver/core-dialogs';
import { observable } from 'mobx';
import { EXPORT_IMAGE_FORMATS, type ExportImageFormat } from './ExportImageFormats.js';
import { useTranslate } from '../localization/useTranslate.js';
import { useObservableRef } from '../useObservableRef.js';
import { CommonDialogBody } from '../CommonDialog/CommonDialog/CommonDialogBody.js';
import { CommonDialogFooter } from '../CommonDialog/CommonDialog/CommonDialogFooter.js';
import { CommonDialogHeader } from '../CommonDialog/CommonDialog/CommonDialogHeader.js';
import { CommonDialogWrapper } from '../CommonDialog/CommonDialog/CommonDialogWrapper.js';
import { Container } from '../Containers/Container.js';
import { ErrorMessage } from '../ErrorMessage.js';
import { FieldCheckbox } from '../FormControls/Checkboxes/FieldCheckbox.js';
import { InputField } from '../FormControls/InputField/InputField.js';
import { useErrorDetails } from '../useErrorDetails.js';
import { Select } from '../FormControls/Select.js';
import { Button } from '../Button.js';
export interface IExportImageOptions {
format: ExportImageFormat;
transparent: boolean;
fileName: string;
}
export interface ExportImagePayload {
export: (options: IExportImageOptions) => Promise<void>;
defaultFileName: string;
}
interface State extends IExportImageOptions {
error: Error | null;
}
export const ExportImageDialog = observer<DialogComponentProps<ExportImagePayload, null>>(function ExportImageDialog(props) {
const translate = useTranslate();
const state = useObservableRef<State>(
() => ({
format: 'SVG',
transparent: false,
fileName: props.payload.defaultFileName,
error: null,
}),
{
format: observable.ref,
transparent: observable.ref,
fileName: observable.ref,
error: observable.ref,
},
false,
);
const errorDetails = useErrorDetails(state.error);
async function onExportHandler() {
try {
await props.payload.export(state);
props.resolveDialog();
} catch (error: any) {
state.error = error;
}
}
return (
<CommonDialogWrapper size="small">
<CommonDialogHeader title="core_blocks_export_image_dialog_title" onReject={props.rejectDialog} />
<CommonDialogBody>
<Container>
<Container small gap>
<InputField state={state} name="fileName">
{translate('ui_name')}
</InputField>
<Select items={EXPORT_IMAGE_FORMATS} keySelector={value => value} valueSelector={value => value} state={state} name="format">
{translate('core_blocks_export_image_dialog_format')}
</Select>
<FieldCheckbox state={state} name="transparent" label={translate('core_blocks_export_image_dialog_transparent_background')} />
</Container>
</Container>
{errorDetails.name && (
<ErrorMessage text={errorDetails.message || errorDetails.name} hasDetails={errorDetails.hasDetails} onShowDetails={errorDetails.open} />
)}
</CommonDialogBody>
<CommonDialogFooter className="tw:justify-end tw:items-center tw:gap-[24px]">
<Button type="button" variant="secondary" onClick={props.rejectDialog}>
{translate('app_shared_inlineEditor_dialog_cancel')}
</Button>
<Button type="button" onClick={onExportHandler}>
{translate('ui_export')}
</Button>
</CommonDialogFooter>
</CommonDialogWrapper>
);
});
@@ -0,0 +1,11 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 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 { importLazyComponent } from '../importLazyComponent.js';
export const ExportImageDialog = importLazyComponent(() => import('./ExportImageDialog.js').then(m => m.ExportImageDialog));
@@ -0,0 +1,11 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
export type ExportImageFormat = 'SVG' | 'PNG';
export const EXPORT_IMAGE_FORMATS: ExportImageFormat[] = ['SVG', 'PNG'];
+2
View File
@@ -19,6 +19,8 @@ export { default as ConfirmationDialogStyles } from './CommonDialog/Confirmation
export * from './CommonDialog/ConfirmationDialogDelete.js';
export * from './CommonDialog/RenameDialog.js';
export * from './CommonDialog/DialogsPortal.js';
export * from './ExportImageDialog/ExportImageDialogLazy.js';
export * from './ExportImageDialog/ExportImageFormats.js';
export * from './ErrorDetailsDialog/ErrorDetailsDialog.js';
@@ -13,4 +13,7 @@ export default [
['core_blocks_exception_message_error_title', 'Unable to display content'],
['core_blocks_exception_message_error_message', 'Something went wrong.'],
['core_blocks_custom_input_validation_error', 'Field value is not valid'],
['core_blocks_export_image_dialog_title', 'Export Image'],
['core_blocks_export_image_dialog_format', 'File format'],
['core_blocks_export_image_dialog_transparent_background', 'Transparent background'],
];
@@ -12,4 +12,7 @@ export default [
['core_blocks_exception_message_error_title', "Impossible d'afficher le contenu"],
['core_blocks_exception_message_error_message', 'Quelque chose a mal tourné.'],
['core_blocks_custom_input_validation_error', "La valeur du champ n'est pas valide"],
['core_blocks_export_image_dialog_title', "Exporter l'image"],
['core_blocks_export_image_dialog_format', 'Format de fichier'],
['core_blocks_export_image_dialog_transparent_background', 'Fond transparent'],
];
@@ -7,4 +7,7 @@ export default [
['core_blocks_object_property_info_form_empty_placeholder', 'Properties empty'],
['core_blocks_properties_table_item_remove', 'Remove property'],
['core_blocks_custom_input_validation_error', 'Field value is not valid'],
['core_blocks_export_image_dialog_title', 'Esporta immagine'],
['core_blocks_export_image_dialog_format', 'Formato file'],
['core_blocks_export_image_dialog_transparent_background', 'Sfondo trasparente'],
];
@@ -10,4 +10,7 @@ export default [
['combobox_no_results_placeholder', 'Нет совпадений'],
['combobox_select_placeholder', 'Выберите опцию'],
['core_blocks_custom_input_validation_error', 'Значение не валидно'],
['core_blocks_export_image_dialog_title', 'Экспорт изображения'],
['core_blocks_export_image_dialog_format', 'Формат файла'],
['core_blocks_export_image_dialog_transparent_background', 'Прозрачный фон'],
];
@@ -13,4 +13,7 @@ export default [
['core_blocks_exception_message_error_title', 'Không thể hiển thị nội dung'],
['core_blocks_exception_message_error_message', 'Đã xảy ra lỗi.'],
['core_blocks_custom_input_validation_error', 'Giá trị trường không hợp lệ'],
['core_blocks_export_image_dialog_title', 'Xuất hình ảnh'],
['core_blocks_export_image_dialog_format', 'Định dạng tệp'],
['core_blocks_export_image_dialog_transparent_background', 'Nền trong suốt'],
];
@@ -13,4 +13,7 @@ export default [
['core_blocks_exception_message_error_title', '无法显示内容'],
['core_blocks_exception_message_error_message', '出现错误.'],
['core_blocks_custom_input_validation_error', '字段值无效'],
['core_blocks_export_image_dialog_title', '导出图像'],
['core_blocks_export_image_dialog_format', '文件格式'],
['core_blocks_export_image_dialog_transparent_background', '透明背景'],
];
@@ -12,7 +12,7 @@ module.exports = [
'joint',
'backbone',
'lodash',
'canvg',
'modern-screenshot',
'dagre',
'svg-pathdata',
'@emotion',
+1
View File
@@ -27,6 +27,7 @@
"fast-deep-equal": "^3",
"md5": "^2",
"mobx": "^6",
"modern-screenshot": "^4",
"tslib": "^2",
"underscore": "^1",
"uuid": "^13",
@@ -0,0 +1,41 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 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 * as modernScreenshot from 'modern-screenshot';
import type { Options } from 'modern-screenshot';
import { download } from './download.js';
import { isImageBroken } from './isBrokenImage.js';
export type { Options as IScreenshotOptions };
const BROKEN_IMAGE_ERROR_MESSAGE = 'Something went wrong. please try select another file format';
export async function downloadSvg<T extends Node>(element: T, options: Options, fileName: string): Promise<void> {
const svg = await modernScreenshot.domToForeignObjectSvg<T>(element, options);
const svgData = new XMLSerializer().serializeToString(svg);
const blob = new Blob(['<?xml version="1.0" standalone="no"?>\r\n', svgData], { type: 'image/svg+xml;charset=utf-8' });
const uri = URL.createObjectURL(blob);
const isBrokenImage = await isImageBroken(uri);
if (isBrokenImage) {
throw new Error(BROKEN_IMAGE_ERROR_MESSAGE);
}
download(blob, `${fileName}.svg`);
}
export async function downloadPng<T extends Node>(element: T, options: Options, fileName: string): Promise<void> {
const png = await modernScreenshot.domToPng(element, options);
const isBrokenImage = await isImageBroken(png);
if (isBrokenImage) {
throw new Error(BROKEN_IMAGE_ERROR_MESSAGE);
}
download(png, `${fileName}.png`);
}
+1
View File
@@ -89,3 +89,4 @@ export * from './bindFunctions.js';
export * from './getDomainFromUrl.js';
export * from './isNumber.js';
export * from './getSubjectDifferences.js';
export * from './downloadImage.js';
@@ -0,0 +1,23 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
export function isImageBroken(imageUrl: string): Promise<boolean> {
return new Promise(resolve => {
const img = new Image();
img.onload = () => {
resolve(false);
};
img.onerror = () => {
resolve(true);
};
img.src = imageUrl;
});
}
+8
View File
@@ -2230,6 +2230,7 @@ __metadata:
fast-deep-equal: "npm:^3"
md5: "npm:^2"
mobx: "npm:^6"
modern-screenshot: "npm:^4"
rimraf: "npm:^6"
tslib: "npm:^2"
typescript: "npm:^5"
@@ -15160,6 +15161,13 @@ __metadata:
languageName: node
linkType: hard
"modern-screenshot@npm:^4":
version: 4.6.6
resolution: "modern-screenshot@npm:4.6.6"
checksum: 10c0/0632cdd89db1a7c0b2d8e78d3e05daca77a62d14ee172f6480da3b2de97fd37938c6ed71041271014a601fe2bcb470d01a78c10456ef7ac59a8674b82c64d0a1
languageName: node
linkType: hard
"ms@npm:^2.1.3":
version: 2.1.3
resolution: "ms@npm:2.1.3"