mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Merge remote-tracking branch 'origin/devel' into CB-92-builder-package
This commit is contained in:
@@ -24,6 +24,15 @@ You can see live demo of CloudBeaver here: https://demo.cloudbeaver.io
|
||||
|
||||
## Changelog
|
||||
|
||||
### CloudBeaver 1.0.2 - 2020-05-19
|
||||
|
||||
- Data export feature UI was improved
|
||||
- User authentication and permission management
|
||||
- Driver management was redesigned
|
||||
- Clickhouse driver was added
|
||||
- Build procedure was redesigned
|
||||
- Many minor bugfixes
|
||||
|
||||
### CloudBeaver 1.0.1 - 2020-05-05
|
||||
|
||||
- Data export feature added to the data viewer
|
||||
|
||||
@@ -47,3 +47,5 @@ echo Copy static content
|
||||
xcopy /E /Q ..\webapp\packages\dbeaver\dist cloudbeaver\web >NUL
|
||||
|
||||
echo Cloudbeaver is ready. Run run-server.bat in cloudbeaver folder to start the server.
|
||||
|
||||
pause
|
||||
+7
-4
@@ -30,16 +30,15 @@ import org.eclipse.core.runtime.Status;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
|
||||
import org.jkiss.dbeaver.model.app.DBPPlatform;
|
||||
import org.jkiss.dbeaver.model.app.DBPProject;
|
||||
import org.jkiss.dbeaver.model.exec.DBCException;
|
||||
import org.jkiss.dbeaver.model.meta.Association;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNModel;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNNode;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNProject;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNProjectDatabases;
|
||||
import org.jkiss.dbeaver.model.runtime.*;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
import org.jkiss.dbeaver.runtime.jobs.DisconnectJob;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
@@ -171,8 +170,8 @@ public class WebSession {
|
||||
}
|
||||
|
||||
private void initNavigatorModel() {
|
||||
DBPPlatform platform = DBWorkbench.getPlatform();
|
||||
this.navigatorModel = new DBNModel(platform, false);
|
||||
CBPlatform platform = CBPlatform.getInstance();
|
||||
this.navigatorModel = new DBNModel(platform, this);
|
||||
this.navigatorModel.initialize();
|
||||
|
||||
DBPProject project = platform.getWorkspace().getActiveProject();
|
||||
@@ -252,6 +251,10 @@ public class WebSession {
|
||||
return databases;
|
||||
}
|
||||
|
||||
public DBNNode getNavigatorNodes() {
|
||||
return databases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns and clears progress messages
|
||||
*/
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ public class CBResourceHandler implements DBPResourceHandler {
|
||||
|
||||
@Override
|
||||
public String getResourceNodeName(IResource resource) {
|
||||
return resource.toString();
|
||||
return resource.getName();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
+19
-9
@@ -19,10 +19,10 @@ package io.cloudbeaver.service.navigator.impl;
|
||||
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.model.WebConnectionInfo;
|
||||
import io.cloudbeaver.service.navigator.WebDatabaseObjectInfo;
|
||||
import io.cloudbeaver.service.navigator.WebNavigatorNodeInfo;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.service.navigator.DBWServiceNavigator;
|
||||
import io.cloudbeaver.service.navigator.WebDatabaseObjectInfo;
|
||||
import io.cloudbeaver.service.navigator.WebNavigatorNodeInfo;
|
||||
import io.cloudbeaver.service.navigator.WebStructContainers;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
@@ -53,18 +53,28 @@ public class WebServiceNavigator implements DBWServiceNavigator {
|
||||
try {
|
||||
DBRProgressMonitor monitor = session.getProgressMonitor();
|
||||
|
||||
DBNNode parentNode = CommonUtils.isEmpty(parentPath) || "/".equals(parentPath) ? session.getDatabases() : session.getNavigatorModel().getNodeByPath(monitor, parentPath);
|
||||
if (parentNode == null) {
|
||||
throw new DBWebException("Node '" + parentPath + "' not found");
|
||||
DBNNode[] nodeChildren;
|
||||
if (CommonUtils.isEmpty(parentPath) || "/".equals(parentPath)) {
|
||||
nodeChildren = session.getDatabases().getChildren(monitor);
|
||||
} else {
|
||||
DBNNode parentNode = session.getNavigatorModel().getNodeByPath(monitor, parentPath);
|
||||
if (parentNode == null) {
|
||||
throw new DBWebException("Node '" + parentPath + "' not found");
|
||||
}
|
||||
if (!parentNode.hasChildren(true)) {
|
||||
return EMPTY_NODE_LIST;
|
||||
}
|
||||
nodeChildren = parentNode.getChildren(monitor);
|
||||
}
|
||||
if (!parentNode.hasChildren(true)) {
|
||||
return EMPTY_NODE_LIST;
|
||||
}
|
||||
DBNNode[] nodeChildren = parentNode.getChildren(monitor);
|
||||
if (nodeChildren == null) {
|
||||
return EMPTY_NODE_LIST;
|
||||
}
|
||||
List<WebNavigatorNodeInfo> result = new ArrayList<>();
|
||||
// Add navigator extensions
|
||||
for (DBNNode extraNode : session.getNavigatorModel().getRoot().getExtraNodes()) {
|
||||
result.add(new WebNavigatorNodeInfo(session, extraNode));
|
||||
}
|
||||
|
||||
for (DBNNode node : nodeChildren) {
|
||||
if (!CommonUtils.toBoolean(onlyFolders) || node instanceof DBNContainer) {
|
||||
result.add(new WebNavigatorNodeInfo(session, node));
|
||||
|
||||
+2
@@ -29,6 +29,8 @@ import java.util.Map;
|
||||
*/
|
||||
public interface DBWServiceAuth extends DBWService {
|
||||
|
||||
String ATTR_USER_AUTH = "user-auth-info";
|
||||
|
||||
@WebAction(requirePermissions = {} )
|
||||
WebAuthInfo authLogin(WebSession webSession, String providerId, Map<String, Object> credentials) throws DBWebException;
|
||||
|
||||
|
||||
+5
-2
@@ -16,11 +16,10 @@
|
||||
*/
|
||||
package io.cloudbeaver.service.auth;
|
||||
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.model.user.WebUser;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* WebAuthInfo
|
||||
@@ -76,4 +75,8 @@ public class WebAuthInfo {
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public static WebAuthInfo getFromSession(WebSession webSession) {
|
||||
return webSession.getAttribute(DBWServiceAuth.ATTR_USER_AUTH);
|
||||
}
|
||||
}
|
||||
|
||||
-2
@@ -42,8 +42,6 @@ import java.util.Map;
|
||||
*/
|
||||
public class WebServiceAuthImpl implements DBWServiceAuth {
|
||||
|
||||
private static final String ATTR_USER_AUTH = "user-auth-info";
|
||||
|
||||
@Override
|
||||
public WebAuthInfo authLogin(WebSession webSession, String providerId, Map<String, Object> authParameters) throws DBWebException {
|
||||
if (!CBApplication.getInstance().getAppConfiguration().isAuthenticationEnabled()) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -76,7 +76,7 @@ module.exports = {
|
||||
app_shared_settingsMenu_theme: 'Theme',
|
||||
app_shared_settingsMenu_lang: 'Language',
|
||||
|
||||
core_eventsLog_dbeaverErrorDetails: 'DBeaver error details',
|
||||
core_eventsLog_dbeaverErrorDetails: 'Error details',
|
||||
|
||||
app_shared_inlineEditor_dialog_title: 'Edit mode',
|
||||
app_shared_inlineEditor_dialog_apply: 'Apply',
|
||||
|
||||
@@ -56,7 +56,7 @@ export class ConnectionDialogsService {
|
||||
id: 'closeConnection',
|
||||
isPresent(context) {
|
||||
return context.contextType === NavigationTreeContextMenuService.nodeContextType
|
||||
&& Boolean((context.data as NodeWithParent)?.object?.features?.includes(EObjectFeature.dataSource));
|
||||
&& !!context.data.object?.features?.includes(EObjectFeature.dataSource);
|
||||
},
|
||||
title: 'Disconnect',
|
||||
onClick: (context: IMenuContext<NodeWithParent>) => {
|
||||
|
||||
@@ -17,4 +17,13 @@ export class NodeManagerUtils {
|
||||
return connectionNodeId.replace('database://', '');
|
||||
}
|
||||
|
||||
static nodeIdToConnectionId(nodeId: string): string {
|
||||
const matches = nodeId.match(/database:\/\/(.*?)(|\/.*)$/);
|
||||
if (!matches) {
|
||||
throw new Error('Not database object');
|
||||
}
|
||||
|
||||
return matches[1];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { IMenuContext } from './IMenuContext';
|
||||
export interface IContextMenuItem<T> extends IMenuItemOptions {
|
||||
onClick?: (context: IMenuContext<T>) => void;
|
||||
// if isPresent is false menu item will not be included in resulting context menu
|
||||
isPresent: (context: IMenuContext<any>) => boolean;
|
||||
isPresent: (context: IMenuContext<T>) => boolean;
|
||||
isDisabled?: (context: IMenuContext<T>) => boolean;
|
||||
// When the item is present in menu it can be hidden based on certain conditions
|
||||
isHidden?: (context: IMenuContext<T>) => boolean;
|
||||
|
||||
@@ -6,8 +6,13 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
NavigationTreeContextMenuService, NodeManagerUtils, NodeWithParent, EObjectFeature
|
||||
} from '@dbeaver/core/app';
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
import { IContextMenuItem, IMenuContext, CommonDialogService } from '@dbeaver/core/dialogs';
|
||||
import {
|
||||
IContextMenuItem, IMenuContext, CommonDialogService, ContextMenuService
|
||||
} from '@dbeaver/core/dialogs';
|
||||
import { TableFooterMenuService, TableViewerModel } from '@dbeaver/data-viewer-plugin';
|
||||
|
||||
import { DataExportDialog } from './Dialog/DataExportDialog';
|
||||
@@ -18,6 +23,7 @@ export class DataExportMenuService {
|
||||
constructor(
|
||||
private commonDialogService: CommonDialogService,
|
||||
private tableFooterMenuService: TableFooterMenuService,
|
||||
private contextMenuService: ContextMenuService,
|
||||
) { }
|
||||
|
||||
register() {
|
||||
@@ -32,6 +38,27 @@ export class DataExportMenuService {
|
||||
onClick: this.exportData.bind(this),
|
||||
};
|
||||
this.tableFooterMenuService.registerMenuItem(exportData);
|
||||
|
||||
this.contextMenuService.addMenuItem<NodeWithParent>(
|
||||
this.contextMenuService.getRootMenuToken(),
|
||||
{
|
||||
id: 'export',
|
||||
isPresent(context) {
|
||||
return context.contextType === NavigationTreeContextMenuService.nodeContextType
|
||||
&& !!context.data.object?.features?.includes(EObjectFeature.dataContainer);
|
||||
},
|
||||
order: 2,
|
||||
title: 'Export',
|
||||
onClick: (context) => {
|
||||
const node = context.data;
|
||||
const connectionId = NodeManagerUtils.nodeIdToConnectionId(node.id);
|
||||
this.commonDialogService.open(DataExportDialog, {
|
||||
connectionId,
|
||||
containerNodePath: node.id,
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private exportData(context: IMenuContext<TableViewerModel>) {
|
||||
|
||||
@@ -8,10 +8,12 @@
|
||||
|
||||
import { observable, computed } from 'mobx';
|
||||
|
||||
import { ErrorDetailsDialog } from '@dbeaver/core/app';
|
||||
import { IProperty } from '@dbeaver/core/blocks';
|
||||
import { injectable, IInitializableController } from '@dbeaver/core/di';
|
||||
import { injectable, IInitializableController, IDestructibleController } from '@dbeaver/core/di';
|
||||
import { CommonDialogService } from '@dbeaver/core/dialogs';
|
||||
import { NotificationService } from '@dbeaver/core/eventsLog';
|
||||
import { DataTransferProcessorInfo } from '@dbeaver/core/sdk';
|
||||
import { DataTransferProcessorInfo, GQLErrorCatcher } from '@dbeaver/core/sdk';
|
||||
|
||||
import { DataExportService } from '../DataExportService';
|
||||
import { IExportContext } from '../IExportContext';
|
||||
@@ -22,7 +24,7 @@ export enum DataExportStep {
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class DataExportController implements IInitializableController {
|
||||
export class DataExportController implements IInitializableController, IDestructibleController {
|
||||
@observable step = DataExportStep.DataTransferProcessor
|
||||
get isLoading() {
|
||||
return this.dataExportService.processors.isLoading();
|
||||
@@ -35,18 +37,22 @@ export class DataExportController implements IInitializableController {
|
||||
.from(
|
||||
this.dataExportService.processors.data.values()
|
||||
)
|
||||
.sort((a, b) => this.sortProcessors(a, b));
|
||||
.sort(sortProcessors);
|
||||
}
|
||||
|
||||
@observable processorProperties: any = {}
|
||||
@observable properties: IProperty[] = []
|
||||
|
||||
readonly error = new GQLErrorCatcher();
|
||||
|
||||
private context!: IExportContext;
|
||||
private close!: () => void;
|
||||
private isDistructed = false;
|
||||
|
||||
constructor(
|
||||
private dataExportService: DataExportService,
|
||||
private notificationService: NotificationService
|
||||
private notificationService: NotificationService,
|
||||
private commonDialogService: CommonDialogService
|
||||
) { }
|
||||
|
||||
init(context: IExportContext, close: () => void) {
|
||||
@@ -55,6 +61,10 @@ export class DataExportController implements IInitializableController {
|
||||
this.loadProcessors();
|
||||
}
|
||||
|
||||
destruct(): void {
|
||||
this.isDistructed = true;
|
||||
}
|
||||
|
||||
prepareExport = async () => {
|
||||
if (!this.processor || this.isExporting) {
|
||||
return;
|
||||
@@ -71,7 +81,9 @@ export class DataExportController implements IInitializableController {
|
||||
);
|
||||
this.close();
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Can\'t export');
|
||||
if (!this.error.catch(exception) || this.isDistructed) {
|
||||
this.notificationService.logException(exception, 'Can\'t export');
|
||||
}
|
||||
} finally {
|
||||
this.isExporting = false;
|
||||
close();
|
||||
@@ -100,6 +112,13 @@ export class DataExportController implements IInitializableController {
|
||||
this.processorProperties = {};
|
||||
|
||||
this.step = DataExportStep.Configure;
|
||||
this.error.clear();
|
||||
}
|
||||
|
||||
showDetails = () => {
|
||||
if (this.error.exception) {
|
||||
this.commonDialogService.open(ErrorDetailsDialog, this.error.exception);
|
||||
}
|
||||
}
|
||||
|
||||
private async loadProcessors() {
|
||||
@@ -109,13 +128,13 @@ export class DataExportController implements IInitializableController {
|
||||
this.notificationService.logException(exception, 'Can\'t load data export processors');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sortProcessors(processorA: DataTransferProcessorInfo, processorB: DataTransferProcessorInfo): number {
|
||||
if (processorA.order === processorB.order)
|
||||
{
|
||||
return (processorA.name || '').localeCompare((processorB.name || ''));
|
||||
}
|
||||
function sortProcessors(processorA: DataTransferProcessorInfo, processorB: DataTransferProcessorInfo): number {
|
||||
if (processorA.order === processorB.order)
|
||||
{
|
||||
return (processorA.name || '').localeCompare((processorB.name || ''));
|
||||
}
|
||||
|
||||
return processorA.order - processorB.order;
|
||||
}
|
||||
return processorA.order - processorB.order;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,9 @@ export const DataExportDialog: DialogComponent<IExportContext, null> = observer(
|
||||
processor={controller.processor}
|
||||
properties={controller.properties}
|
||||
processorProperties={controller.processorProperties}
|
||||
error={controller.error}
|
||||
isExporting={controller.isExporting}
|
||||
onShowDetails={controller.showDetails}
|
||||
onBack={() => controller.setStep(DataExportStep.DataTransferProcessor)}
|
||||
onClose={props.rejectDialog}
|
||||
onExport={controller.prepareExport}
|
||||
|
||||
@@ -9,31 +9,49 @@
|
||||
import { observer } from 'mobx-react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { IProperty, PropertiesTable } from '@dbeaver/core/blocks';
|
||||
import { IProperty, PropertiesTable, ErrorMessage } from '@dbeaver/core/blocks';
|
||||
import { CommonDialogWrapper } from '@dbeaver/core/dialogs';
|
||||
import { useTranslate } from '@dbeaver/core/localization';
|
||||
import { DataTransferProcessorInfo } from '@dbeaver/core/sdk';
|
||||
import { DataTransferProcessorInfo, GQLErrorCatcher } from '@dbeaver/core/sdk';
|
||||
import { composes, useStyles } from '@dbeaver/core/theming';
|
||||
|
||||
import { ProcessorConfigureDialogFooter } from './ProcessorConfigureDialogFooter';
|
||||
|
||||
const styles = css`
|
||||
CommonDialogWrapper {
|
||||
max-height: 500px;
|
||||
min-height: 500px;
|
||||
}
|
||||
PropertiesTable {
|
||||
flex: 1;
|
||||
}
|
||||
message {
|
||||
margin: auto;
|
||||
}
|
||||
`;
|
||||
const styles = composes(
|
||||
css`
|
||||
Tab {
|
||||
composes: theme-ripple theme-background-secondary theme-text-on-secondary from global;
|
||||
}
|
||||
ErrorMessage {
|
||||
composes: theme-background-secondary from global;
|
||||
}
|
||||
`,
|
||||
css`
|
||||
CommonDialogWrapper {
|
||||
max-height: 500px;
|
||||
min-height: 500px;
|
||||
}
|
||||
PropertiesTable {
|
||||
flex: 1;
|
||||
}
|
||||
message {
|
||||
margin: auto;
|
||||
}
|
||||
ErrorMessage {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
padding: 8px 24px;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
type ProcessorSelectDialogProps = {
|
||||
processor: DataTransferProcessorInfo;
|
||||
properties: IProperty[];
|
||||
processorProperties: any;
|
||||
error: GQLErrorCatcher;
|
||||
isExporting: boolean;
|
||||
onShowDetails(): void;
|
||||
onClose(): void;
|
||||
onBack(): void;
|
||||
onExport(): void;
|
||||
@@ -44,7 +62,9 @@ export const ProcessorConfigureDialog = observer(
|
||||
processor,
|
||||
properties,
|
||||
processorProperties,
|
||||
error,
|
||||
isExporting,
|
||||
onShowDetails,
|
||||
onClose,
|
||||
onBack,
|
||||
onExport,
|
||||
@@ -52,7 +72,7 @@ export const ProcessorConfigureDialog = observer(
|
||||
const translate = useTranslate();
|
||||
const title = `${translate('data_transfer_dialog_configuration_title')} (${processor.name})`;
|
||||
|
||||
return styled(styles)(
|
||||
return styled(useStyles(styles))(
|
||||
<CommonDialogWrapper
|
||||
title={title}
|
||||
noBodyPadding
|
||||
@@ -66,11 +86,15 @@ export const ProcessorConfigureDialog = observer(
|
||||
}
|
||||
onReject={onClose}
|
||||
>
|
||||
{isExporting && <message as="div">{translate('data_transfer_dialog_preparation')}</message>}
|
||||
{!isExporting && (
|
||||
<PropertiesTable
|
||||
properties={properties}
|
||||
propertiesState={processorProperties}
|
||||
<PropertiesTable
|
||||
properties={properties}
|
||||
propertiesState={processorProperties}
|
||||
/>
|
||||
{error.responseMessage && (
|
||||
<ErrorMessage
|
||||
text={error.responseMessage}
|
||||
hasDetails={error.hasDetails}
|
||||
onShowDetails={onShowDetails}
|
||||
/>
|
||||
)}
|
||||
</CommonDialogWrapper>
|
||||
|
||||
@@ -66,7 +66,7 @@ export const ProcessorSelectDialog = observer(
|
||||
>
|
||||
<export-object as="div">
|
||||
{context.sourceName ? translate('data_transfer_exporting_sql') : `${translate('data_transfer_exporting_table')} ${node?.name}`}
|
||||
<pre>{context.sourceName}</pre>
|
||||
<pre title={context.sourceName}>{context.sourceName}</pre>
|
||||
</export-object>
|
||||
{isLoading && <Loader />}
|
||||
{!isLoading && <ExportProcessorList processors={processors} onSelect={onSelect}/>}
|
||||
|
||||
@@ -59,7 +59,7 @@ export const ExportNotification = observer(function ExportNotification({
|
||||
<notification-body as="div">
|
||||
<source-name as="div">
|
||||
{controller.sourceName}
|
||||
<pre>{controller.task?.context.sourceName}</pre>
|
||||
<pre title={controller.task?.context.sourceName}>{controller.task?.context.sourceName}</pre>
|
||||
</source-name>
|
||||
<actions as="div">
|
||||
{controller.isSuccess && (
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
See [Wiki](https://github.com/dbeaver-corp/dbeaver-web/wiki/Frontend-CI&CD)
|
||||
Reference in New Issue
Block a user