{!hideTags && (
diff --git a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeDescription.module.css b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeDescription.module.css
new file mode 100644
index 0000000000..1d4fb726e4
--- /dev/null
+++ b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeDescription.module.css
@@ -0,0 +1,11 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 DBeaver Corp and others
+ *
+ * Licensed under the Apache License, Version 2.0.
+ * you may not use this file except in compliance with the License.
+ */
+.description {
+ composes: theme-typography--caption from global;
+ color: var(--theme-text-hint-on-light);
+}
diff --git a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeDescription.tsx b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeDescription.tsx
new file mode 100644
index 0000000000..b33969895f
--- /dev/null
+++ b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeDescription.tsx
@@ -0,0 +1,26 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 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 { s } from '../../s.js';
+import { useS } from '../../useS.js';
+import classes from './TreeNodeDescription.module.css';
+
+interface Props extends React.HTMLAttributes
{
+ className?: string;
+}
+
+export const TreeNodeDescription: React.FC = observer(function TreeNodeDescription({ className, children, ...rest }) {
+ const styles = useS(classes);
+
+ return (
+
+ {children}
+
+ );
+});
diff --git a/webapp/packages/core-blocks/src/index.ts b/webapp/packages/core-blocks/src/index.ts
index 002ef6cadb..ef56612b57 100644
--- a/webapp/packages/core-blocks/src/index.ts
+++ b/webapp/packages/core-blocks/src/index.ts
@@ -131,6 +131,7 @@ export * from './Tree/TreeNode/TreeNodeControl.js';
export * from './Tree/TreeNode/TreeNodeExpand.js';
export * from './Tree/TreeNode/TreeNodeIcon.js';
export * from './Tree/TreeNode/TreeNodeName.js';
+export * from './Tree/TreeNode/TreeNodeDescription.js';
export * from './Tree/TreeNode/TreeNodeNested.js';
export * from './Tree/TreeNode/TreeNodeNestedMessage.js';
export * from './Tree/TreeNode/TreeNodeSelect.js';
diff --git a/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContext.ts b/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContext.ts
index 2416dd02ee..47dcd65b09 100644
--- a/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContext.ts
+++ b/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContext.ts
@@ -162,6 +162,20 @@ export class ConnectionExecutionContext implements IConnectionExecutionContext {
return mapAsyncTaskInfo(result);
}
+ async getLog() {
+ const result = await this.withContext(async context => {
+ const { log } = await this.graphQLService.sdk.getTransactionLog({
+ projectId: context.projectId,
+ connectionId: context.connectionId,
+ contextId: context.id,
+ });
+
+ return log;
+ });
+
+ return result?.transactionLogInfos;
+ }
+
private withContext(callback: (context: IConnectionExecutionContextInfo) => Promise): Promise {
if (!this.context) {
throw new Error('Execution Context not found');
diff --git a/webapp/packages/core-sdk/src/queries/fragments/DatabaseDriver.gql b/webapp/packages/core-sdk/src/queries/fragments/DatabaseDriver.gql
index 7a84c0a15a..aad8588889 100644
--- a/webapp/packages/core-sdk/src/queries/fragments/DatabaseDriver.gql
+++ b/webapp/packages/core-sdk/src/queries/fragments/DatabaseDriver.gql
@@ -22,6 +22,9 @@ fragment DatabaseDriver on DriverInfo {
applicableAuthModels
applicableNetworkHandlers
configurationTypes
+ driverId
+ driverInstalled
+ downloadable
mainProperties @include(if: $includeMainProperties) {
...DriverPropertyInfo
diff --git a/webapp/packages/core-sdk/src/queries/fragments/NavNodeInfo.gql b/webapp/packages/core-sdk/src/queries/fragments/NavNodeInfo.gql
index 0c87b6b2bc..1d1ccfc04a 100644
--- a/webapp/packages/core-sdk/src/queries/fragments/NavNodeInfo.gql
+++ b/webapp/packages/core-sdk/src/queries/fragments/NavNodeInfo.gql
@@ -2,6 +2,7 @@ fragment NavNodeInfo on NavigatorNodeInfo {
id
name
plainName
+ description
hasChildren
nodeType
icon
diff --git a/webapp/packages/core-sdk/src/queries/transactions/getTransactionCount.gql b/webapp/packages/core-sdk/src/queries/transactions/getTransactionCount.gql
new file mode 100644
index 0000000000..0289c29a88
--- /dev/null
+++ b/webapp/packages/core-sdk/src/queries/transactions/getTransactionCount.gql
@@ -0,0 +1,5 @@
+mutation getTransactionCount($projectId: ID!, $connectionId: ID!, $contextId: ID!) {
+ info: getTransactionLogInfo(projectId: $projectId, connectionId: $connectionId, contextId: $contextId) {
+ count
+ }
+}
diff --git a/webapp/packages/core-sdk/src/queries/transactions/getTransactionLog.gql b/webapp/packages/core-sdk/src/queries/transactions/getTransactionLog.gql
new file mode 100644
index 0000000000..116e622bd1
--- /dev/null
+++ b/webapp/packages/core-sdk/src/queries/transactions/getTransactionLog.gql
@@ -0,0 +1,13 @@
+mutation getTransactionLog($projectId: ID!, $connectionId: ID!, $contextId: ID!) {
+ log: getTransactionLogInfo(projectId: $projectId, connectionId: $connectionId, contextId: $contextId) {
+ transactionLogInfos {
+ id
+ time
+ type
+ queryString
+ durationMs
+ rows
+ result
+ }
+ }
+}
diff --git a/webapp/packages/plugin-connection-custom/src/DriverSelector/Driver.module.css b/webapp/packages/plugin-connection-custom/src/DriverSelector/Driver.module.css
index 1563215dc9..2467b6cf2c 100644
--- a/webapp/packages/plugin-connection-custom/src/DriverSelector/Driver.module.css
+++ b/webapp/packages/plugin-connection-custom/src/DriverSelector/Driver.module.css
@@ -5,8 +5,24 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
+.icon {
+ position: relative;
+}
+
.staticImage {
box-sizing: border-box;
width: 24px;
max-height: 24px;
-};
+}
+
+.indicator {
+ position: absolute;
+ bottom: 2px;
+ right: 0;
+ width: 14px;
+ height: 14px;
+ display: flex;
+ border-radius: 50%;
+ background-color: var(--theme-surface);
+ border: 1px solid var(--theme-surface);
+}
diff --git a/webapp/packages/plugin-connection-custom/src/DriverSelector/Driver.tsx b/webapp/packages/plugin-connection-custom/src/DriverSelector/Driver.tsx
index fdb52fe310..86ab72b4e4 100644
--- a/webapp/packages/plugin-connection-custom/src/DriverSelector/Driver.tsx
+++ b/webapp/packages/plugin-connection-custom/src/DriverSelector/Driver.tsx
@@ -8,7 +8,7 @@
import { observer } from 'mobx-react-lite';
import { useCallback } from 'react';
-import { ListItem, ListItemDescription, ListItemIcon, ListItemName, s, StaticImage, useS } from '@cloudbeaver/core-blocks';
+import { IconOrImage, ListItem, ListItemDescription, ListItemIcon, ListItemName, s, StaticImage, useS, useTranslate } from '@cloudbeaver/core-blocks';
import style from './Driver.module.css';
@@ -17,6 +17,7 @@ export interface IDriver {
icon?: string;
name?: string;
description?: string;
+ driverInstalled?: boolean;
}
interface Props {
@@ -25,13 +26,19 @@ interface Props {
}
export const Driver = observer(function Driver({ driver, onSelect }) {
+ const translate = useTranslate();
const select = useCallback(() => onSelect(driver.id), [driver]);
const styles = useS(style);
return (
-
+
+ {!driver.driverInstalled && (
+
+
+
+ )}
{driver.name}
{driver.description}
diff --git a/webapp/packages/plugin-connection-custom/src/DriverSelector/DriverSelectorDialog.tsx b/webapp/packages/plugin-connection-custom/src/DriverSelector/DriverSelectorDialog.tsx
index 90cffabfd2..f3c2db1d46 100644
--- a/webapp/packages/plugin-connection-custom/src/DriverSelector/DriverSelectorDialog.tsx
+++ b/webapp/packages/plugin-connection-custom/src/DriverSelector/DriverSelectorDialog.tsx
@@ -35,7 +35,7 @@ export const DriverSelectorDialog: DialogComponent = observer(function
-
+
);
diff --git a/webapp/packages/plugin-connection-custom/src/DriverSelector/useDriverSelectorDialog.ts b/webapp/packages/plugin-connection-custom/src/DriverSelector/useDriverSelectorDialog.ts
index 518cb459f0..1bd9451ad3 100644
--- a/webapp/packages/plugin-connection-custom/src/DriverSelector/useDriverSelectorDialog.ts
+++ b/webapp/packages/plugin-connection-custom/src/DriverSelector/useDriverSelectorDialog.ts
@@ -5,20 +5,21 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
-import { action, observable } from 'mobx';
+import { action, computed } from 'mobx';
-import { useObservableRef, useResource } from '@cloudbeaver/core-blocks';
+import { useObservableRef, usePermission, useResource } from '@cloudbeaver/core-blocks';
import { ConnectionsManagerService, DBDriverResource } from '@cloudbeaver/core-connections';
import { useService } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { CachedMapAllKey } from '@cloudbeaver/core-resource';
+import { EAdminPermission } from '@cloudbeaver/core-root';
import { PublicConnectionFormService } from '@cloudbeaver/plugin-connections';
import type { IDriver } from './Driver.js';
interface State {
+ readonly drivers: IDriver[];
select(driverId: string): Promise;
- enabledDrivers: IDriver[];
}
interface DriverSelectorDialogArgs {
@@ -28,17 +29,26 @@ interface DriverSelectorDialogArgs {
}
export function useDriverSelectorDialog({ onSelect, projectId, folderPath }: DriverSelectorDialogArgs) {
+ const isAdmin = usePermission(EAdminPermission.admin);
const notificationService = useService(NotificationService);
const connectionsManagerService = useService(ConnectionsManagerService);
const publicConnectionFormService = useService(PublicConnectionFormService);
const dbDriverResource = useResource(useDriverSelectorDialog, DBDriverResource, CachedMapAllKey);
- const enabledDrivers = dbDriverResource.resource.enabledDrivers;
const state: State = useObservableRef(
() => ({
+ get drivers() {
+ return dbDriverResource.resource.enabledDrivers.filter(driver => {
+ if (this.isAdmin) {
+ return true;
+ }
+
+ return driver.driverInstalled;
+ });
+ },
async select(driverId: string) {
const projects = this.connectionsManagerService.createConnectionProjects;
- const drivers = this.enabledDrivers.map(driver => driver.id);
+ const drivers = this.drivers.map(driver => driver.id);
if (projects.length === 0) {
this.notificationService.logError({ title: 'core_projects_no_default_project' });
@@ -53,8 +63,8 @@ export function useDriverSelectorDialog({ onSelect, projectId, folderPath }: Dri
}
},
}),
- { select: action.bound, enabledDrivers: observable.ref },
- { notificationService, connectionsManagerService, publicConnectionFormService, enabledDrivers },
+ { select: action.bound, drivers: computed },
+ { notificationService, connectionsManagerService, publicConnectionFormService, dbDriverResource, isAdmin },
);
return state;
diff --git a/webapp/packages/plugin-connection-custom/src/locales/en.ts b/webapp/packages/plugin-connection-custom/src/locales/en.ts
index 3c03aca9cf..290e841494 100644
--- a/webapp/packages/plugin-connection-custom/src/locales/en.ts
+++ b/webapp/packages/plugin-connection-custom/src/locales/en.ts
@@ -5,4 +5,7 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
-export default [['plugin_connection_custom_action_custom_label', 'New Connection']];
+export default [
+ ['plugin_connection_custom_action_custom_label', 'New Connection'],
+ ['plugin_connection_custom_drivers_driver_not_installed', 'Driver is not installed'],
+];
diff --git a/webapp/packages/plugin-connection-custom/src/locales/fr.ts b/webapp/packages/plugin-connection-custom/src/locales/fr.ts
index 4c0f72d04d..63aeb3523b 100644
--- a/webapp/packages/plugin-connection-custom/src/locales/fr.ts
+++ b/webapp/packages/plugin-connection-custom/src/locales/fr.ts
@@ -5,4 +5,7 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
-export default [['plugin_connection_custom_action_custom_label', 'Nouvelle connexion']];
+export default [
+ ['plugin_connection_custom_action_custom_label', 'Nouvelle connexion'],
+ ['plugin_connection_custom_drivers_driver_not_installed', 'Driver is not installed'],
+];
diff --git a/webapp/packages/plugin-connection-custom/src/locales/it.ts b/webapp/packages/plugin-connection-custom/src/locales/it.ts
index 3c03aca9cf..290e841494 100644
--- a/webapp/packages/plugin-connection-custom/src/locales/it.ts
+++ b/webapp/packages/plugin-connection-custom/src/locales/it.ts
@@ -5,4 +5,7 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
-export default [['plugin_connection_custom_action_custom_label', 'New Connection']];
+export default [
+ ['plugin_connection_custom_action_custom_label', 'New Connection'],
+ ['plugin_connection_custom_drivers_driver_not_installed', 'Driver is not installed'],
+];
diff --git a/webapp/packages/plugin-connection-custom/src/locales/ru.ts b/webapp/packages/plugin-connection-custom/src/locales/ru.ts
index 4d4f153ef8..76eb1d7456 100644
--- a/webapp/packages/plugin-connection-custom/src/locales/ru.ts
+++ b/webapp/packages/plugin-connection-custom/src/locales/ru.ts
@@ -5,4 +5,7 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
-export default [['plugin_connection_custom_action_custom_label', 'Новое подключение']];
+export default [
+ ['plugin_connection_custom_action_custom_label', 'Новое подключение'],
+ ['plugin_connection_custom_drivers_driver_not_installed', 'Драйвер не установлен'],
+];
diff --git a/webapp/packages/plugin-connection-custom/src/locales/zh.ts b/webapp/packages/plugin-connection-custom/src/locales/zh.ts
index 0de6a0635e..b2c7c30da4 100644
--- a/webapp/packages/plugin-connection-custom/src/locales/zh.ts
+++ b/webapp/packages/plugin-connection-custom/src/locales/zh.ts
@@ -5,4 +5,7 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
-export default [['plugin_connection_custom_action_custom_label', '新建连接']];
+export default [
+ ['plugin_connection_custom_action_custom_label', '新建连接'],
+ ['plugin_connection_custom_drivers_driver_not_installed', 'Driver is not installed'],
+];
diff --git a/webapp/packages/plugin-connections/src/ConnectionForm/Options/Options.tsx b/webapp/packages/plugin-connections/src/ConnectionForm/Options/Options.tsx
index d37e416c24..14d4ba582d 100644
--- a/webapp/packages/plugin-connections/src/ConnectionForm/Options/Options.tsx
+++ b/webapp/packages/plugin-connections/src/ConnectionForm/Options/Options.tsx
@@ -14,11 +14,13 @@ import {
Combobox,
Container,
FieldCheckbox,
+ Flex,
Form,
FormFieldDescription,
getComputed,
Group,
GroupTitle,
+ IconOrImage,
InputField,
Link,
ObjectPropertyInfoForm,
@@ -28,6 +30,7 @@ import {
Textarea,
useAdministrationSettings,
useFormValidator,
+ usePermission,
useResource,
useS,
useTranslate,
@@ -35,7 +38,7 @@ import {
import { DatabaseAuthModelsResource, type DBDriver, DBDriverResource, isLocalConnection } from '@cloudbeaver/core-connections';
import { useService } from '@cloudbeaver/core-di';
import { ProjectInfoResource } from '@cloudbeaver/core-projects';
-import { ServerConfigResource } from '@cloudbeaver/core-root';
+import { EAdminPermission, ServerConfigResource } from '@cloudbeaver/core-root';
import { DriverConfigurationType } from '@cloudbeaver/core-sdk';
import { type TabContainerPanelComponent, TabsContext, useAuthenticationAction } from '@cloudbeaver/core-ui';
import { EMPTY_ARRAY } from '@cloudbeaver/core-utils';
@@ -76,6 +79,7 @@ const driverConfiguration: IDriverConfiguration[] = [
];
export const Options: TabContainerPanelComponent = observer(function Options({ state }) {
+ const isAdmin = usePermission(EAdminPermission.admin);
const serverConfigResource = useResource(Options, ServerConfigResource, undefined);
const projectInfoResource = useService(ProjectInfoResource);
const service = useService(ConnectionFormService);
@@ -151,7 +155,13 @@ export const Options: TabContainerPanelComponent = observe
const edit = state.mode === 'edit';
const originLocal = !info || (originInfo?.origin && isLocalConnection(originInfo.origin));
- const drivers = driverMap.resource.enabledDrivers.filter(({ id }) => availableDrivers.includes(id));
+ const drivers = driverMap.resource.enabledDrivers.filter(({ id, driverInstalled }) => {
+ if (!edit && !isAdmin && !driverInstalled) {
+ return false;
+ }
+
+ return availableDrivers.includes(id);
+ });
let properties = authModel?.properties;
@@ -187,7 +197,12 @@ export const Options: TabContainerPanelComponent = observe
tiny
fill
>
- {translate('connections_connection_driver')}
+
+ {isAdmin && !driver?.driverInstalled && (
+
+ )}
+ {translate('connections_connection_driver')}
+
{configurationTypes.length > 1 && (
diff --git a/webapp/packages/plugin-connections/src/locales/en.ts b/webapp/packages/plugin-connections/src/locales/en.ts
index 61d5f9f09f..77505452bb 100644
--- a/webapp/packages/plugin-connections/src/locales/en.ts
+++ b/webapp/packages/plugin-connections/src/locales/en.ts
@@ -45,4 +45,5 @@ export default [
'There are multiple credentials available for authentication.\nPlease choose credentials you want to use.',
],
['plugin_connections_connection_create_menu_title', 'Connection'],
+ ['plugin_connections_connection_driver_not_installed', 'Driver is not installed. You can install it in the "Administration" part.'],
];
diff --git a/webapp/packages/plugin-connections/src/locales/fr.ts b/webapp/packages/plugin-connections/src/locales/fr.ts
index 5fb33cc349..3dc8842cb2 100644
--- a/webapp/packages/plugin-connections/src/locales/fr.ts
+++ b/webapp/packages/plugin-connections/src/locales/fr.ts
@@ -54,4 +54,5 @@ export default [
['plugin_connections_connection_form_shared_credentials_manage_info_tab_link', 'Onglet Identifiants'],
['plugin_connections_connection_auth_secret_description', 'Veuillez sélectionner les identifiants fournis par une de vos équipes'],
['plugin_connections_connection_create_menu_title', 'Connection'],
+ ['plugin_connections_connection_driver_not_installed', 'Driver is not installed. You can install it in the "Administration" part.'],
];
diff --git a/webapp/packages/plugin-connections/src/locales/it.ts b/webapp/packages/plugin-connections/src/locales/it.ts
index 1789dfe180..52c6997b77 100644
--- a/webapp/packages/plugin-connections/src/locales/it.ts
+++ b/webapp/packages/plugin-connections/src/locales/it.ts
@@ -47,4 +47,5 @@ export default [
'There are multiple credentials available for authentication.\nPlease choose credentials you want to use.',
],
['plugin_connections_connection_create_menu_title', 'Connection'],
+ ['plugin_connections_connection_driver_not_installed', 'Driver is not installed. You can install it in the "Administration" part.'],
];
diff --git a/webapp/packages/plugin-connections/src/locales/ru.ts b/webapp/packages/plugin-connections/src/locales/ru.ts
index e85d1f46cc..c1f4974c6f 100644
--- a/webapp/packages/plugin-connections/src/locales/ru.ts
+++ b/webapp/packages/plugin-connections/src/locales/ru.ts
@@ -45,4 +45,5 @@ export default [
'У вас есть несколько учетных записей для авторизации.\nВыберите учетную запись из списка.',
],
['plugin_connections_connection_create_menu_title', 'Подключение'],
+ ['plugin_connections_connection_driver_not_installed', 'Драйвер не установлен. Вы можете установить его в "Администрированой" части.'],
];
diff --git a/webapp/packages/plugin-connections/src/locales/zh.ts b/webapp/packages/plugin-connections/src/locales/zh.ts
index c22e3872d0..da7d9284ac 100644
--- a/webapp/packages/plugin-connections/src/locales/zh.ts
+++ b/webapp/packages/plugin-connections/src/locales/zh.ts
@@ -41,4 +41,5 @@ export default [
['plugin_connections_connection_form_shared_credentials_manage_info_tab_link', '凭证页签'],
['plugin_connections_connection_auth_secret_description', '有多个凭证可用于身份验证.\n请选择您要使用的凭证。'],
['plugin_connections_connection_create_menu_title', 'Connection'],
+ ['plugin_connections_connection_driver_not_installed', 'Driver is not installed. You can install it in the "Administration" part.'],
];
diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/CellRenderer/CellRenderer.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/CellRenderer/CellRenderer.tsx
index eb5537ec68..f81238bc49 100644
--- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/CellRenderer/CellRenderer.tsx
+++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/CellRenderer/CellRenderer.tsx
@@ -103,7 +103,10 @@ export const CellRenderer = observer): boolean {
- if (!cellContext.cell) {
+ if (
+ !cellContext.cell ||
+ (!dataGridContext.model.hasElementIdentifier(tableDataContext.view.resultIndex) && cellContext.editionState !== DatabaseEditChangeType.add)
+ ) {
return false;
}
diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts
index a4d7a03f5b..a056c6c35d 100644
--- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts
+++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts
@@ -86,9 +86,11 @@ export class DataGridContextMenuCellEditingService {
const isComplex = format.isBinary(key) || format.isGeometry(key);
const isTruncated = content.isTextTruncated(key);
const selectedElements = select?.getSelectedElements() || [];
+ // we can't edit table cells if table doesn't have row identifier, but we can edit new created rows before insert (CB-6063)
+ const canEdit = model.hasElementIdentifier(resultIndex) || editor.getElementState(key) === DatabaseEditChangeType.add;
if (action === ACTION_EDIT) {
- if (!column || cellValue === undefined || format.isReadOnly(key) || isComplex || isTruncated) {
+ if (!column || cellValue === undefined || format.isReadOnly(key) || isComplex || isTruncated || !canEdit) {
return false;
}
@@ -96,7 +98,7 @@ export class DataGridContextMenuCellEditingService {
}
if (action === ACTION_DATA_GRID_EDITING_SET_TO_NULL) {
- return cellValue !== undefined && !format.isReadOnly(key) && !view.getColumn(key.column)?.required && !format.isNull(key);
+ return cellValue !== undefined && !format.isReadOnly(key) && !view.getColumn(key.column)?.required && !format.isNull(key) && canEdit;
}
if (action === ACTION_DATA_GRID_EDITING_ADD_ROW || action === ACTION_DATA_GRID_EDITING_DUPLICATE_ROW) {
@@ -104,11 +106,11 @@ export class DataGridContextMenuCellEditingService {
}
if (action === ACTION_DATA_GRID_EDITING_DELETE_ROW) {
- return !format.isReadOnly(key) && editor.getElementState(key) !== DatabaseEditChangeType.delete;
+ return !format.isReadOnly(key) && canEdit && editor.getElementState(key) !== DatabaseEditChangeType.delete;
}
if (action === ACTION_DATA_GRID_EDITING_DELETE_SELECTED_ROW) {
- if (model.isReadonly(resultIndex) || !editor.hasFeature('delete')) {
+ if (model.isReadonly(resultIndex) || !canEdit || !editor.hasFeature('delete')) {
return false;
}
diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx
index da7b068bdb..919a256102 100644
--- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx
+++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx
@@ -222,12 +222,14 @@ export const DataGridTable = observer(function DataGridT
function handleKeyDown(event: React.KeyboardEvent) {
gridSelectedCellCopy.onKeydownHandler(event);
+ const cell = selectionAction.getFocusedElement();
+ // we can't edit table cells if table doesn't have row identifier, but we can edit new created rows before insert (CB-6063)
+ const canEdit = model.hasElementIdentifier(resultIndex) || !!(cell && tableData.editor.getElementState(cell) === DatabaseEditChangeType.add);
- if (EventContext.has(event, EventStopPropagationFlag) || tableData.isReadOnly() || model.isReadonly(resultIndex)) {
+ if (EventContext.has(event, EventStopPropagationFlag) || !canEdit || tableData.isReadOnly() || model.isReadonly(resultIndex)) {
return;
}
- const cell = selectionAction.getFocusedElement();
const activeElements = selectionAction.getActiveElements();
const activeRows = selectionAction.getActiveRows();
diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableIndexColumnHeader.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableIndexColumnHeader.tsx
index d6ef14007c..0d016feb71 100644
--- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableIndexColumnHeader.tsx
+++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableIndexColumnHeader.tsx
@@ -27,7 +27,12 @@ export const TableIndexColumnHeader = observer>(funct
throw new Error('Contexts required');
}
- const readonly = getComputed(() => tableDataContext.isReadOnly() || dataGridContext.model.isReadonly(dataGridContext.resultIndex));
+ const readonly = getComputed(
+ () =>
+ tableDataContext.isReadOnly() ||
+ dataGridContext.model.isReadonly(dataGridContext.resultIndex) ||
+ !dataGridContext.model.hasElementIdentifier(dataGridContext.resultIndex),
+ );
function handleClick(event: React.MouseEvent) {
selectionContext.selectTable();
diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataModel.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataModel.ts
index eadfee8830..1b101f2cf8 100644
--- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataModel.ts
+++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataModel.ts
@@ -59,6 +59,10 @@ export class DatabaseDataModel = I
return this.source.isReadonly(resultIndex);
}
+ hasElementIdentifier(resultIndex: number): boolean {
+ return this.source.hasElementIdentifier(resultIndex);
+ }
+
isDataAvailable(offset: number, count: number): boolean {
return this.source.isDataAvailable(offset, count);
}
diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts
index aed18b399f..5490880853 100644
--- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts
+++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts
@@ -206,6 +206,10 @@ export abstract class DatabaseDataSource 1 || this.disabled;
}
diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts
index 5557d1b31a..1ecb50bb1e 100644
--- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts
+++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts
@@ -31,6 +31,7 @@ export interface IDatabaseDataModel this;
isReadonly: (resultIndex: number) => boolean;
+ hasElementIdentifier: (resultIndex: number) => boolean;
isDisabled: (resultIndex?: number) => boolean;
isLoading: () => boolean;
isDataAvailable: (offset: number, count: number) => boolean;
diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts
index a8705f3605..cbd4fc739c 100644
--- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts
+++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts
@@ -71,6 +71,7 @@ export interface IDatabaseDataSource boolean;
isLoadable: () => boolean;
isReadonly: (resultIndex: number) => boolean;
+ hasElementIdentifier: (resultIndex: number) => boolean;
isDataAvailable: (offset: number, count: number) => boolean;
isLoading: () => boolean;
isDisabled: (resultIndex?: number) => boolean;
diff --git a/webapp/packages/plugin-data-viewer/src/ResultSet/ResultSetDataSource.ts b/webapp/packages/plugin-data-viewer/src/ResultSet/ResultSetDataSource.ts
index 3aa856dbf2..4a70da9867 100644
--- a/webapp/packages/plugin-data-viewer/src/ResultSet/ResultSetDataSource.ts
+++ b/webapp/packages/plugin-data-viewer/src/ResultSet/ResultSetDataSource.ts
@@ -39,7 +39,7 @@ export abstract class ResultSetDataSource exten
}
override isReadonly(resultIndex: number): boolean {
- return super.isReadonly(resultIndex) || !this.executionContext?.context || this.getResult(resultIndex)?.data?.hasRowIdentifier === false;
+ return super.isReadonly(resultIndex) || !this.executionContext?.context;
}
override async cancel(): Promise {
diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts
index d2820b630b..1a4fb798dd 100644
--- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts
+++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts
@@ -101,13 +101,16 @@ export class TableFooterMenuService {
}
case ACTION_DELETE: {
const editor = model.source.getActionImplementation(resultIndex, DatabaseEditAction);
+ const selectedElements = getActiveElements(model, resultIndex);
- if (!editor) {
+ // we can't edit table cells if table doesn't have row identifier, but we can edit new created rows before insert (CB-6063)
+ const canEdit =
+ model.hasElementIdentifier(resultIndex) || selectedElements.every(key => editor?.getElementState(key) === DatabaseEditChangeType.add);
+
+ if (!editor || !canEdit) {
return true;
}
- const selectedElements = getActiveElements(model, resultIndex);
-
return selectedElements.length === 0 || !selectedElements.some(key => editor.getElementState(key) !== DatabaseEditChangeType.delete);
}
case ACTION_REVERT: {
diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/BooleanValue/BooleanValuePresentation.tsx b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/BooleanValue/BooleanValuePresentation.tsx
index eeba7b5a7b..17f70f82d6 100644
--- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/BooleanValue/BooleanValuePresentation.tsx
+++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/BooleanValue/BooleanValuePresentation.tsx
@@ -51,7 +51,11 @@ export const BooleanValuePresentation: TabContainerPanelComponent
diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/isTextValueReadonly.ts b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/isTextValueReadonly.ts
index a927689d4a..b1ad216ad1 100644
--- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/isTextValueReadonly.ts
+++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/isTextValueReadonly.ts
@@ -30,6 +30,7 @@ export function isTextValueReadonly({ contentAction, formatAction, model, result
formatAction.isGeometry(cell) ||
contentAction.isTextTruncated(cell) ||
model.isReadonly(resultIndex) ||
- model.isDisabled(resultIndex)
+ model.isDisabled(resultIndex) ||
+ !model.hasElementIdentifier(resultIndex)
);
}
diff --git a/webapp/packages/plugin-datasource-transaction-manager/package.json b/webapp/packages/plugin-datasource-transaction-manager/package.json
index 50655af823..f63cb4e288 100644
--- a/webapp/packages/plugin-datasource-transaction-manager/package.json
+++ b/webapp/packages/plugin-datasource-transaction-manager/package.json
@@ -25,15 +25,26 @@
"@cloudbeaver/core-events": "^0",
"@cloudbeaver/core-executor": "^0",
"@cloudbeaver/core-localization": "^0",
+ "@cloudbeaver/core-resource": "^0",
+ "@cloudbeaver/core-root": "^0",
+ "@cloudbeaver/core-sdk": "^0",
"@cloudbeaver/core-settings": "^0",
"@cloudbeaver/core-ui": "^0",
"@cloudbeaver/core-utils": "^0",
"@cloudbeaver/core-view": "^0",
+ "@cloudbeaver/plugin-codemirror6": "^0",
+ "@cloudbeaver/plugin-data-grid": "^0",
"@cloudbeaver/plugin-datasource-context-switch": "^0",
- "@cloudbeaver/plugin-top-app-bar": "^0"
+ "@cloudbeaver/plugin-sql-editor-new": "^0",
+ "@cloudbeaver/plugin-top-app-bar": "^0",
+ "mobx": "^6",
+ "mobx-react-lite": "^4",
+ "react": "^18"
},
"peerDependencies": {},
"devDependencies": {
- "typescript": "^5"
+ "@types/react": "^18",
+ "typescript": "^5",
+ "typescript-plugin-css-modules": "^5"
}
}
diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TRANSACTION_INFO_PARAM_SCHEMA.ts b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TRANSACTION_INFO_PARAM_SCHEMA.ts
new file mode 100644
index 0000000000..b4e0b04890
--- /dev/null
+++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TRANSACTION_INFO_PARAM_SCHEMA.ts
@@ -0,0 +1,27 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 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 { schema } from '@cloudbeaver/core-utils';
+
+export const TRANSACTION_INFO_PARAM_SCHEMA = schema
+ .object({
+ connectionId: schema.string(),
+ projectId: schema.string(),
+ contextId: schema.string(),
+ })
+ .required()
+ .strict();
+
+export type ITransactionInfoParam = schema.infer;
+
+export function createTransactionInfoParam(connectionId: string, projectId: string, contextId: string): ITransactionInfoParam {
+ return {
+ connectionId,
+ projectId,
+ contextId,
+ };
+}
diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionInfoAction.module.css b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionInfoAction.module.css
new file mode 100644
index 0000000000..3408475ccf
--- /dev/null
+++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionInfoAction.module.css
@@ -0,0 +1,38 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 DBeaver Corp and others
+ *
+ * Licensed under the Apache License, Version 2.0.
+ * you may not use this file except in compliance with the License.
+ */
+
+.container {
+ cursor: pointer;
+ padding: 0 12px;
+ position: relative;
+
+ &:after {
+ position: absolute;
+ background: #236ea0;
+ height: 32px;
+ width: 1px;
+ top: 8px;
+ right: -1px;
+ opacity: 1;
+ content: '';
+ }
+
+ &:hover {
+ background: #338ecc;
+ }
+}
+
+.count {
+ border: 1px solid var(--theme-on-primary);
+ border-radius: var(--theme-form-element-radius);
+ height: 26px;
+ width: 50px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionInfoAction.tsx b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionInfoAction.tsx
new file mode 100644
index 0000000000..e225f12d0a
--- /dev/null
+++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionInfoAction.tsx
@@ -0,0 +1,41 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 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 { Container, s, useResource, useS, useTranslate } from '@cloudbeaver/core-blocks';
+import { useService } from '@cloudbeaver/core-di';
+import type { ICustomMenuItemComponent } from '@cloudbeaver/core-view';
+
+import { TransactionManagerService } from '../TransactionManagerService.js';
+import { createTransactionInfoParam } from './TRANSACTION_INFO_PARAM_SCHEMA.js';
+import classes from './TransactionInfoAction.module.css';
+import { TransactionLogCountResource } from './TransactionLogCountResource.js';
+
+export const TransactionInfoAction: ICustomMenuItemComponent = observer(function TransactionInfoAction(props) {
+ const styles = useS(classes);
+ const translate = useTranslate();
+ const transactionManagerService = useService(TransactionManagerService);
+ const transaction = transactionManagerService.getActiveContextTransaction();
+ const context = transaction?.context;
+ const key = context ? createTransactionInfoParam(context.connectionId, context.projectId, context.id) : null;
+
+ const transactionLogCountResource = useResource(TransactionInfoAction, TransactionLogCountResource, key);
+
+ return (
+ props.item.events?.onSelect?.()}
+ >
+ {transactionLogCountResource.data}
+
+ );
+});
diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogCountEventHandler.ts b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogCountEventHandler.ts
new file mode 100644
index 0000000000..e2d81ffbb3
--- /dev/null
+++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogCountEventHandler.ts
@@ -0,0 +1,25 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 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 { type ISessionEvent, type SessionEventId, SessionEventSource, SessionEventTopic, TopicEventHandler } from '@cloudbeaver/core-root';
+import type { WsTransactionalCountEvent } from '@cloudbeaver/core-sdk';
+
+export type IWsTransactionCountEvent = WsTransactionalCountEvent;
+
+type TransactionCountEvent = IWsTransactionCountEvent;
+
+@injectable()
+export class TransactionLogCountEventHandler extends TopicEventHandler {
+ constructor(sessionEventSource: SessionEventSource) {
+ super(SessionEventTopic.CbTransaction, sessionEventSource);
+ }
+
+ map(event: any): TransactionCountEvent {
+ return event;
+ }
+}
diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogCountResource.ts b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogCountResource.ts
new file mode 100644
index 0000000000..d72f03a851
--- /dev/null
+++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogCountResource.ts
@@ -0,0 +1,63 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 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 { toJS } from 'mobx';
+
+import { injectable } from '@cloudbeaver/core-di';
+import { CachedMapResource } from '@cloudbeaver/core-resource';
+import { ServerEventId } from '@cloudbeaver/core-root';
+import { GraphQLService } from '@cloudbeaver/core-sdk';
+import { schemaValidationError } from '@cloudbeaver/core-utils';
+
+import { createTransactionInfoParam, type ITransactionInfoParam, TRANSACTION_INFO_PARAM_SCHEMA } from './TRANSACTION_INFO_PARAM_SCHEMA.js';
+import { type IWsTransactionCountEvent, TransactionLogCountEventHandler } from './TransactionLogCountEventHandler.js';
+
+@injectable()
+export class TransactionLogCountResource extends CachedMapResource {
+ constructor(
+ private readonly graphQLService: GraphQLService,
+ transactionLogCountEventHandler: TransactionLogCountEventHandler,
+ ) {
+ super();
+
+ transactionLogCountEventHandler.onEvent(
+ ServerEventId.CbTransactionCount,
+ async data => {
+ const key = createTransactionInfoParam(data.connectionId, data.projectId, data.contextId);
+ this.set(key, data.transactionalCount);
+ },
+ undefined,
+ this,
+ );
+ }
+
+ protected async loader(key: ITransactionInfoParam) {
+ const { info } = await this.graphQLService.sdk.getTransactionCount({
+ connectionId: key.connectionId,
+ projectId: key.projectId,
+ contextId: key.contextId,
+ });
+
+ this.set(key, info.count);
+
+ return this.data;
+ }
+
+ override isKeyEqual(key: ITransactionInfoParam, secondKey: ITransactionInfoParam): boolean {
+ return key.connectionId === secondKey.connectionId && key.projectId === secondKey.projectId;
+ }
+
+ protected override validateKey(key: ITransactionInfoParam): boolean {
+ const parse = TRANSACTION_INFO_PARAM_SCHEMA.safeParse(toJS(key));
+
+ if (!parse.success) {
+ this.logger.warn(`Invalid resource key ${(schemaValidationError(parse.error).toString(), { prefix: null })}`);
+ }
+
+ return parse.success;
+ }
+}
diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogDialog.tsx b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogDialog.tsx
new file mode 100644
index 0000000000..469772405e
--- /dev/null
+++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogDialog.tsx
@@ -0,0 +1,82 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 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 {
+ Button,
+ CommonDialogBody,
+ CommonDialogFooter,
+ CommonDialogHeader,
+ CommonDialogWrapper,
+ Container,
+ Flex,
+ useAutoLoad,
+ useResource,
+ useTranslate,
+} from '@cloudbeaver/core-blocks';
+import { type ConnectionExecutionContext, ConnectionInfoResource, createConnectionParam } from '@cloudbeaver/core-connections';
+import type { DialogComponent } from '@cloudbeaver/core-dialogs';
+
+import { TransactionLogTable } from './TransactionLogTable/TransactionLogTable.js';
+import { useTransactionLog } from './useTransactionLog.js';
+
+interface IPayload {
+ transaction: ConnectionExecutionContext;
+ onCommit: () => void;
+ onRollback: () => void;
+}
+
+export const TransactionLogDialog: DialogComponent = observer(function TransactionLogDialog(props) {
+ const translate = useTranslate();
+ const context = props.payload.transaction.context;
+ const connectionParam = context ? createConnectionParam(context.projectId, context.connectionId) : null;
+
+ const state = useTransactionLog(props.payload);
+ const connectionInfoResource = useResource(useTransactionLog, ConnectionInfoResource, connectionParam);
+ let title: string = translate('plugin_datasource_transaction_manager_logs');
+
+ if (connectionInfoResource.data?.name) {
+ title = `${title} (${connectionInfoResource.data.name})`;
+ }
+
+ useAutoLoad(TransactionLogDialog, state);
+
+ function handleRollback() {
+ props.payload.onRollback();
+ props.resolveDialog();
+ }
+
+ function handleCommit() {
+ props.payload.onCommit();
+ props.resolveDialog();
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+});
diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/HeaderCell.tsx b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/HeaderCell.tsx
new file mode 100644
index 0000000000..66579a0f70
--- /dev/null
+++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/HeaderCell.tsx
@@ -0,0 +1,17 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 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 { useTranslate } from '@cloudbeaver/core-blocks';
+import { type TransactionLogInfoItem } from '@cloudbeaver/core-sdk';
+import type { RenderHeaderCellProps } from '@cloudbeaver/plugin-data-grid';
+
+export const HeaderCell = observer>(function HeaderCell(props) {
+ const translate = useTranslate();
+ return {typeof props.column.name === 'string' ? translate(props.column.name) : props.column.name}
;
+});
diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/QueryCell.module.css b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/QueryCell.module.css
new file mode 100644
index 0000000000..52479f1294
--- /dev/null
+++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/QueryCell.module.css
@@ -0,0 +1,10 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 DBeaver Corp and others
+ *
+ * Licensed under the Apache License, Version 2.0.
+ * you may not use this file except in compliance with the License.
+ */
+.cell {
+ cursor: pointer;
+}
diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/QueryCell.tsx b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/QueryCell.tsx
new file mode 100644
index 0000000000..1800556829
--- /dev/null
+++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/QueryCell.tsx
@@ -0,0 +1,33 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 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 { useService } from '@cloudbeaver/core-di';
+import { CommonDialogService } from '@cloudbeaver/core-dialogs';
+import type { TransactionLogInfoItem } from '@cloudbeaver/core-sdk';
+import { type RenderCellProps } from '@cloudbeaver/plugin-data-grid';
+
+import classes from './QueryCell.module.css';
+import { QueryDetailsDialog } from './QueryDetailsDialog.js';
+
+export const QueryCell = observer>(function QueryCell(props) {
+ const commonDialogService = useService(CommonDialogService);
+ const value = props.row.queryString;
+
+ async function openDetails() {
+ await commonDialogService.open(QueryDetailsDialog, {
+ text: value,
+ });
+ }
+
+ return (
+
+ {value}
+
+ );
+});
diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/QueryDetailsDialog.tsx b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/QueryDetailsDialog.tsx
new file mode 100644
index 0000000000..19848d8bc7
--- /dev/null
+++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/QueryDetailsDialog.tsx
@@ -0,0 +1,40 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 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 { Button, CommonDialogBody, CommonDialogFooter, CommonDialogHeader, CommonDialogWrapper, Group, useTranslate } from '@cloudbeaver/core-blocks';
+import type { DialogComponent } from '@cloudbeaver/core-dialogs';
+import { useCodemirrorExtensions } from '@cloudbeaver/plugin-codemirror6';
+import { SQLCodeEditorLoader, useSqlDialectExtension } from '@cloudbeaver/plugin-sql-editor-new';
+
+interface IPayload {
+ text: string;
+}
+
+export const QueryDetailsDialog: DialogComponent = observer(function QueryDetailsDialog(props) {
+ const translate = useTranslate();
+ const sqlDialect = useSqlDialectExtension(undefined);
+ const extensions = useCodemirrorExtensions();
+ extensions.set(...sqlDialect);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+});
diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/TimeCell.tsx b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/TimeCell.tsx
new file mode 100644
index 0000000000..201a7f3375
--- /dev/null
+++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/TimeCell.tsx
@@ -0,0 +1,20 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 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 { TransactionLogInfoItem } from '@cloudbeaver/core-sdk';
+import { isSameDay } from '@cloudbeaver/core-utils';
+import { type RenderCellProps } from '@cloudbeaver/plugin-data-grid';
+
+export const TimeCell = observer>(function TimeCell(props) {
+ const date = new Date(props.row.time);
+ const fullTime = date.toLocaleString();
+ const displayTime = isSameDay(date, new Date()) ? date.toLocaleTimeString() : fullTime;
+
+ return {displayTime}
;
+});
diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/TransactionLogTable.module.css b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/TransactionLogTable.module.css
new file mode 100644
index 0000000000..ee44e57732
--- /dev/null
+++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/TransactionLogTable.module.css
@@ -0,0 +1,12 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 DBeaver Corp and others
+ *
+ * Licensed under the Apache License, Version 2.0.
+ * you may not use this file except in compliance with the License.
+ */
+.container {
+ composes: theme-typography--caption theme-border-color-background from global;
+ border: 1px solid;
+ height: 100%;
+}
diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/TransactionLogTable.tsx b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/TransactionLogTable.tsx
new file mode 100644
index 0000000000..6d86bb911d
--- /dev/null
+++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/TransactionLogTable.tsx
@@ -0,0 +1,76 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 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 { s, useS } from '@cloudbeaver/core-blocks';
+import type { TransactionLogInfoItem } from '@cloudbeaver/core-sdk';
+import { type Column, DataGrid } from '@cloudbeaver/plugin-data-grid';
+
+import { HeaderCell } from './HeaderCell.js';
+import { QueryCell } from './QueryCell.js';
+import { TimeCell } from './TimeCell.js';
+import classes from './TransactionLogTable.module.css';
+
+interface Props {
+ log: TransactionLogInfoItem[];
+}
+
+const COLUMNS: Column[] = [
+ {
+ key: 'time',
+ name: 'plugin_datasource_transaction_manager_logs_table_column_time',
+ resizable: true,
+ renderCell: props => ,
+ renderHeaderCell: props => ,
+ },
+ {
+ key: 'type',
+ name: 'plugin_datasource_transaction_manager_logs_table_column_type',
+ resizable: true,
+ renderCell: props => {props.row.type}
,
+ renderHeaderCell: props => ,
+ },
+ {
+ key: 'text',
+ name: 'plugin_datasource_transaction_manager_logs_table_column_text',
+ resizable: true,
+ renderCell: props => ,
+ renderHeaderCell: props => ,
+ },
+ {
+ key: 'duration',
+ name: 'plugin_datasource_transaction_manager_logs_table_column_duration',
+ resizable: true,
+ renderCell: props => {props.row.durationMs}
,
+ renderHeaderCell: props => ,
+ },
+ {
+ key: 'rows',
+ name: 'plugin_datasource_transaction_manager_logs_table_column_rows',
+ resizable: true,
+ renderCell: props => {props.row.rows}
,
+ renderHeaderCell: props => ,
+ },
+ {
+ key: 'result',
+ name: 'plugin_datasource_transaction_manager_logs_table_column_result',
+ resizable: true,
+ renderCell: props => {props.row.result}
,
+ renderHeaderCell: props => ,
+ },
+];
+
+export const TransactionLogTable = observer(function TransactionLogTable(props) {
+ const styles = useS(classes);
+
+ return (
+
+ row.id} columns={COLUMNS} rowHeight={30} />
+
+ );
+});
diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/useTransactionLog.tsx b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/useTransactionLog.tsx
new file mode 100644
index 0000000000..1d75c1c0c8
--- /dev/null
+++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/useTransactionLog.tsx
@@ -0,0 +1,60 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 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 { observable } from 'mobx';
+
+import { useObservableRef } from '@cloudbeaver/core-blocks';
+import type { ConnectionExecutionContext } from '@cloudbeaver/core-connections';
+import type { TransactionLogInfoItem } from '@cloudbeaver/core-sdk';
+import type { ILoadableState } from '@cloudbeaver/core-utils';
+
+interface Payload {
+ transaction: ConnectionExecutionContext;
+}
+
+interface State extends ILoadableState {
+ log: TransactionLogInfoItem[] | null;
+ exception: Error | null;
+ promise: Promise | null;
+ payload: Payload;
+}
+
+export function useTransactionLog(payload: Payload) {
+ const state = useObservableRef(
+ () => ({
+ log: null,
+ exception: null,
+ promise: null,
+ isLoaded() {
+ return this.log !== null;
+ },
+ isError() {
+ return this.exception !== null;
+ },
+ isLoading() {
+ return this.promise !== null;
+ },
+ async load() {
+ try {
+ this.exception = null;
+
+ this.promise = payload.transaction.getLog();
+ const log = await this.promise;
+ this.log = log;
+ } catch (exception: any) {
+ this.exception = exception;
+ } finally {
+ this.promise = null;
+ }
+ },
+ }),
+ { log: observable.ref, promise: observable.ref, exception: observable.ref, payload: observable.ref },
+ { payload },
+ );
+
+ return state;
+}
diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionManagerBootstrap.ts b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionManagerBootstrap.ts
index b3a8c343de..fd485cdf9d 100644
--- a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionManagerBootstrap.ts
+++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionManagerBootstrap.ts
@@ -5,7 +5,7 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
-import { ConfirmationDialog } from '@cloudbeaver/core-blocks';
+import { ConfirmationDialog, importLazyComponent } from '@cloudbeaver/core-blocks';
import {
ConnectionExecutionContext,
ConnectionExecutionContextResource,
@@ -24,15 +24,25 @@ import { ExecutorInterrupter, type IExecutionContextProvider } from '@cloudbeave
import { LocalizationService } from '@cloudbeaver/core-localization';
import { OptionsPanelService } from '@cloudbeaver/core-ui';
import { isNotNullDefined } from '@cloudbeaver/core-utils';
-import { ActionService, MenuService } from '@cloudbeaver/core-view';
+import { ActionService, MenuCustomItem, MenuService } from '@cloudbeaver/core-view';
import { ConnectionSchemaManagerService } from '@cloudbeaver/plugin-datasource-context-switch';
import { MENU_APP_ACTIONS } from '@cloudbeaver/plugin-top-app-bar';
import { ACTION_DATASOURCE_TRANSACTION_COMMIT } from './actions/ACTION_DATASOURCE_TRANSACTION_COMMIT.js';
import { ACTION_DATASOURCE_TRANSACTION_COMMIT_MODE_TOGGLE } from './actions/ACTION_DATASOURCE_TRANSACTION_COMMIT_MODE_TOGGLE.js';
import { ACTION_DATASOURCE_TRANSACTION_ROLLBACK } from './actions/ACTION_DATASOURCE_TRANSACTION_ROLLBACK.js';
+import { createTransactionInfoParam } from './TransactionLog/TRANSACTION_INFO_PARAM_SCHEMA.js';
+import { TransactionLogCountResource } from './TransactionLog/TransactionLogCountResource.js';
import { TransactionManagerSettingsService } from './TransactionManagerSettingsService.js';
+const TransactionInfoAction = importLazyComponent(() =>
+ import('./TransactionLog/TransactionInfoAction.js').then(module => module.TransactionInfoAction),
+);
+
+const TransactionLogDialog = importLazyComponent(() =>
+ import('./TransactionLog/TransactionLogDialog.js').then(module => module.TransactionLogDialog),
+);
+
@injectable()
export class TransactionManagerBootstrap extends Bootstrap {
constructor(
@@ -48,6 +58,7 @@ export class TransactionManagerBootstrap extends Bootstrap {
private readonly commonDialogService: CommonDialogService,
private readonly localizationService: LocalizationService,
private readonly transactionManagerSettingsService: TransactionManagerSettingsService,
+ private readonly transactionLogCountResource: TransactionLogCountResource,
) {
super();
}
@@ -68,12 +79,38 @@ export class TransactionManagerBootstrap extends Bootstrap {
isNotNullDefined(transaction.autoCommit)
);
},
- getItems: (_, items) => [
- ...items,
- ACTION_DATASOURCE_TRANSACTION_COMMIT,
- ACTION_DATASOURCE_TRANSACTION_ROLLBACK,
- ACTION_DATASOURCE_TRANSACTION_COMMIT_MODE_TOGGLE,
- ],
+ getItems: (_, items) => {
+ const transaction = this.getContextTransaction();
+
+ const result = [
+ ...items,
+ ACTION_DATASOURCE_TRANSACTION_COMMIT,
+ ACTION_DATASOURCE_TRANSACTION_ROLLBACK,
+ ACTION_DATASOURCE_TRANSACTION_COMMIT_MODE_TOGGLE,
+ ];
+
+ if (transaction && transaction.autoCommit === false) {
+ result.push(
+ new MenuCustomItem(
+ {
+ id: 'transaction-info',
+ getComponent: () => TransactionInfoAction,
+ },
+ {
+ onSelect: async () => {
+ await this.commonDialogService.open(TransactionLogDialog, {
+ transaction,
+ onCommit: () => this.commit(transaction),
+ onRollback: () => this.rollback(transaction),
+ });
+ },
+ },
+ ),
+ );
+ }
+
+ return result;
+ },
});
this.actionService.addHandler({
@@ -127,19 +164,19 @@ export class TransactionManagerBootstrap extends Bootstrap {
break;
}
case ACTION_DATASOURCE_TRANSACTION_ROLLBACK: {
- try {
- const result = await transaction.rollback();
- this.showTransactionResult(transaction, result);
- } catch (exception: any) {
- this.notificationService.logException(exception, 'plugin_datasource_transaction_manager_rollback_fail');
- }
-
+ await this.rollback(transaction);
break;
}
case ACTION_DATASOURCE_TRANSACTION_COMMIT_MODE_TOGGLE:
try {
await transaction.setAutoCommit(!transaction.autoCommit);
await this.connectionExecutionContextResource.refresh();
+
+ const context = transaction.context;
+
+ if (transaction.autoCommit === true && context) {
+ this.transactionLogCountResource.markOutdated(createTransactionInfoParam(context.connectionId, context.projectId, context.id));
+ }
} catch (exception: any) {
this.notificationService.logException(exception, 'plugin_datasource_transaction_manager_commit_mode_fail');
}
@@ -200,6 +237,15 @@ export class TransactionManagerBootstrap extends Bootstrap {
}
}
+ private async rollback(transaction: ConnectionExecutionContext) {
+ try {
+ const result = await transaction.rollback();
+ this.showTransactionResult(transaction, result);
+ } catch (exception: any) {
+ this.notificationService.logException(exception, 'plugin_datasource_transaction_manager_rollback_fail');
+ }
+ }
+
private async commit(transaction: ConnectionExecutionContext, onError?: (exception: any) => void) {
try {
const result = await transaction.commit();
diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionManagerService.ts b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionManagerService.ts
new file mode 100644
index 0000000000..e3b9ae4699
--- /dev/null
+++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionManagerService.ts
@@ -0,0 +1,28 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 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 { ConnectionExecutionContextService } from '@cloudbeaver/core-connections';
+import { injectable } from '@cloudbeaver/core-di';
+import { ConnectionSchemaManagerService } from '@cloudbeaver/plugin-datasource-context-switch';
+
+@injectable()
+export class TransactionManagerService {
+ constructor(
+ private readonly connectionSchemaManagerService: ConnectionSchemaManagerService,
+ private readonly connectionExecutionContextService: ConnectionExecutionContextService,
+ ) {}
+
+ getActiveContextTransaction() {
+ const context = this.connectionSchemaManagerService.activeExecutionContext;
+
+ if (!context) {
+ return;
+ }
+
+ return this.connectionExecutionContextService.get(context.id);
+ }
+}
diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/locales/en.ts b/webapp/packages/plugin-datasource-transaction-manager/src/locales/en.ts
index f113567706..b9ec6e443f 100644
--- a/webapp/packages/plugin-datasource-transaction-manager/src/locales/en.ts
+++ b/webapp/packages/plugin-datasource-transaction-manager/src/locales/en.ts
@@ -1,3 +1,10 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 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 default [
['plugin_datasource_transaction_manager_commit', 'Commit'],
['plugin_datasource_transaction_manager_rollback', 'Rollback'],
@@ -7,4 +14,13 @@ export default [
['plugin_datasource_transaction_manager_rollback_fail', 'Failed to rollback transaction'],
['plugin_datasource_transaction_manager_commit_mode_fail', 'Failed to change commit mode'],
['plugin_datasource_transaction_manager_commit_confirmation_message', 'Do you want to commit changes?'],
+
+ ['plugin_datasource_transaction_manager_logs', 'Transaction log'],
+ ['plugin_datasource_transaction_manager_logs_tooltip', 'Open transaction log'],
+ ['plugin_datasource_transaction_manager_logs_table_column_time', 'Time'],
+ ['plugin_datasource_transaction_manager_logs_table_column_type', 'Type'],
+ ['plugin_datasource_transaction_manager_logs_table_column_text', 'Query'],
+ ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Duration (ms)'],
+ ['plugin_datasource_transaction_manager_logs_table_column_rows', 'Rows'],
+ ['plugin_datasource_transaction_manager_logs_table_column_result', 'Result'],
];
diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/locales/fr.ts b/webapp/packages/plugin-datasource-transaction-manager/src/locales/fr.ts
index 76278e2a5c..662db9253f 100644
--- a/webapp/packages/plugin-datasource-transaction-manager/src/locales/fr.ts
+++ b/webapp/packages/plugin-datasource-transaction-manager/src/locales/fr.ts
@@ -14,4 +14,13 @@ export default [
['plugin_datasource_transaction_manager_rollback_fail', "Échec de l'annulation de la transaction"],
['plugin_datasource_transaction_manager_commit_mode_fail', 'Échec du changement de mode de commit'],
['plugin_datasource_transaction_manager_commit_confirmation_message', 'Voulez-vous commiter les modifications ?'],
+
+ ['plugin_datasource_transaction_manager_logs', 'Transaction log'],
+ ['plugin_datasource_transaction_manager_logs_tooltip', 'Open transaction log'],
+ ['plugin_datasource_transaction_manager_logs_table_column_time', 'Time'],
+ ['plugin_datasource_transaction_manager_logs_table_column_type', 'Type'],
+ ['plugin_datasource_transaction_manager_logs_table_column_text', 'Query'],
+ ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Duration (ms)'],
+ ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Rows'],
+ ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Result'],
];
diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/locales/it.ts b/webapp/packages/plugin-datasource-transaction-manager/src/locales/it.ts
index f113567706..c398f786d2 100644
--- a/webapp/packages/plugin-datasource-transaction-manager/src/locales/it.ts
+++ b/webapp/packages/plugin-datasource-transaction-manager/src/locales/it.ts
@@ -1,3 +1,10 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 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 default [
['plugin_datasource_transaction_manager_commit', 'Commit'],
['plugin_datasource_transaction_manager_rollback', 'Rollback'],
@@ -7,4 +14,13 @@ export default [
['plugin_datasource_transaction_manager_rollback_fail', 'Failed to rollback transaction'],
['plugin_datasource_transaction_manager_commit_mode_fail', 'Failed to change commit mode'],
['plugin_datasource_transaction_manager_commit_confirmation_message', 'Do you want to commit changes?'],
+
+ ['plugin_datasource_transaction_manager_logs', 'Transaction log'],
+ ['plugin_datasource_transaction_manager_logs_tooltip', 'Open transaction log'],
+ ['plugin_datasource_transaction_manager_logs_table_column_time', 'Time'],
+ ['plugin_datasource_transaction_manager_logs_table_column_type', 'Type'],
+ ['plugin_datasource_transaction_manager_logs_table_column_text', 'Query'],
+ ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Duration (ms)'],
+ ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Rows'],
+ ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Result'],
];
diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/locales/ru.ts b/webapp/packages/plugin-datasource-transaction-manager/src/locales/ru.ts
index 921b0e3d58..cc5098fecf 100644
--- a/webapp/packages/plugin-datasource-transaction-manager/src/locales/ru.ts
+++ b/webapp/packages/plugin-datasource-transaction-manager/src/locales/ru.ts
@@ -1,3 +1,10 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 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 default [
['plugin_datasource_transaction_manager_commit', 'Commit'],
['plugin_datasource_transaction_manager_rollback', 'Rollback'],
@@ -7,4 +14,13 @@ export default [
['plugin_datasource_transaction_manager_rollback_fail', 'Не удалось выполнить откат'],
['plugin_datasource_transaction_manager_commit_mode_fail', 'Не удалось переключить режим коммита'],
['plugin_datasource_transaction_manager_commit_confirmation_message', 'Вы хотите зафиксировать изменения?'],
+
+ ['plugin_datasource_transaction_manager_logs', 'Журнал транзакции'],
+ ['plugin_datasource_transaction_manager_logs_tooltip', 'Открыть журнал транзакции'],
+ ['plugin_datasource_transaction_manager_logs_table_column_time', 'Время'],
+ ['plugin_datasource_transaction_manager_logs_table_column_type', 'Тип'],
+ ['plugin_datasource_transaction_manager_logs_table_column_text', 'Запрос'],
+ ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Продолжительность (мс)'],
+ ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Строки'],
+ ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Результат'],
];
diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/locales/zh.ts b/webapp/packages/plugin-datasource-transaction-manager/src/locales/zh.ts
index ad0fc52178..b86156f6a5 100644
--- a/webapp/packages/plugin-datasource-transaction-manager/src/locales/zh.ts
+++ b/webapp/packages/plugin-datasource-transaction-manager/src/locales/zh.ts
@@ -14,4 +14,13 @@ export default [
['plugin_datasource_transaction_manager_rollback_fail', '回滚事务失败'],
['plugin_datasource_transaction_manager_commit_mode_fail', '切换提交方式失败'],
['plugin_datasource_transaction_manager_commit_confirmation_message', '是否提交更改?'],
+
+ ['plugin_datasource_transaction_manager_logs', 'Transaction log'],
+ ['plugin_datasource_transaction_manager_logs_tooltip', 'Open transaction log'],
+ ['plugin_datasource_transaction_manager_logs_table_column_time', 'Time'],
+ ['plugin_datasource_transaction_manager_logs_table_column_type', 'Type'],
+ ['plugin_datasource_transaction_manager_logs_table_column_text', 'Query'],
+ ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Duration (ms)'],
+ ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Rows'],
+ ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Result'],
];
diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/manifest.ts b/webapp/packages/plugin-datasource-transaction-manager/src/manifest.ts
index bf9094704d..7e8fdf8576 100644
--- a/webapp/packages/plugin-datasource-transaction-manager/src/manifest.ts
+++ b/webapp/packages/plugin-datasource-transaction-manager/src/manifest.ts
@@ -16,5 +16,8 @@ export const datasourceTransactionManagerPlugin: PluginManifest = {
() => import('./TransactionManagerBootstrap.js').then(m => m.TransactionManagerBootstrap),
() => import('./TransactionManagerSettingsService.js').then(m => m.TransactionManagerSettingsService),
() => import('./LocaleService.js').then(m => m.LocaleService),
+ () => import('./TransactionManagerService.js').then(m => m.TransactionManagerService),
+ () => import('./TransactionLog/TransactionLogCountResource.js').then(m => m.TransactionLogCountResource),
+ () => import('./TransactionLog/TransactionLogCountEventHandler.js').then(m => m.TransactionLogCountEventHandler),
],
};
diff --git a/webapp/packages/plugin-datasource-transaction-manager/tsconfig.json b/webapp/packages/plugin-datasource-transaction-manager/tsconfig.json
index 31735c5e23..ac5cc4afcd 100644
--- a/webapp/packages/plugin-datasource-transaction-manager/tsconfig.json
+++ b/webapp/packages/plugin-datasource-transaction-manager/tsconfig.json
@@ -27,6 +27,9 @@
{
"path": "../core-localization/tsconfig.json"
},
+ {
+ "path": "../core-sdk/tsconfig.json"
+ },
{
"path": "../core-settings/tsconfig.json"
},
@@ -39,9 +42,18 @@
{
"path": "../core-view/tsconfig.json"
},
+ {
+ "path": "../plugin-codemirror6/tsconfig.json"
+ },
+ {
+ "path": "../plugin-data-grid/tsconfig.json"
+ },
{
"path": "../plugin-datasource-context-switch/tsconfig.json"
},
+ {
+ "path": "../plugin-sql-editor-new/tsconfig.json"
+ },
{
"path": "../plugin-top-app-bar/tsconfig.json"
}
diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeTools/NavigationTreeSettings/ObjectsDescriptionSettingsForm.tsx b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeTools/NavigationTreeSettings/ObjectsDescriptionSettingsForm.tsx
new file mode 100644
index 0000000000..cdeb7df02e
--- /dev/null
+++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeTools/NavigationTreeSettings/ObjectsDescriptionSettingsForm.tsx
@@ -0,0 +1,42 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 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 PlaceholderComponent, type PlaceholderElement, Switch, useTranslate } from '@cloudbeaver/core-blocks';
+
+import type { IElementsTreeSettingsProps } from './ElementsTreeSettingsService.js';
+
+export const ObjectsDescriptionSettingsForm: PlaceholderComponent = observer(function ObjectsDescriptionSettingsForm({
+ tree: { root, settings },
+}) {
+ const translate = useTranslate();
+
+ if (!settings) {
+ return null;
+ }
+
+ return (
+
+ {translate('app_navigationTree_settings_filter_objects_description')}
+
+ );
+});
+
+export const ObjectsDescriptionSettingsPlaceholderElement: PlaceholderElement = {
+ id: 'settings-objects-description',
+ component: ObjectsDescriptionSettingsForm,
+ order: 2,
+};
diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeTools/NavigationTreeSettings/createElementsTreeSettings.ts b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeTools/NavigationTreeSettings/createElementsTreeSettings.ts
index ede0403de8..3652c1ef53 100644
--- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeTools/NavigationTreeSettings/createElementsTreeSettings.ts
+++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeTools/NavigationTreeSettings/createElementsTreeSettings.ts
@@ -17,6 +17,7 @@ export function createElementsTreeSettings(defaults?: Partial !!navNodeInfoResource.getException(node.id) || !!navTreeResource.getException(node.id));
@@ -110,6 +113,7 @@ export const NavigationNodeControl: NavTreeControlComponent = observer(
let icon = nodeInfo.icon;
const name = nodeInfo.name;
+ const description = nodeInfo.description;
const title = nodeInfo.tooltip;
if (error) {
@@ -145,7 +149,12 @@ export const NavigationNodeControl: NavTreeControlComponent = observer(
{editing ? (
) : (
- {name}
+
+ {name}
+ {elementsTreeContext?.tree.settings?.objectsDescription && description && (
+ {` - ${description}`}
+ )}
+
)}
diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/transformDescriptionNodeInfo.ts b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/transformDescriptionNodeInfo.ts
new file mode 100644
index 0000000000..207033a594
--- /dev/null
+++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/transformDescriptionNodeInfo.ts
@@ -0,0 +1,25 @@
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 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 type { NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree';
+
+import type { IElementsTreeCustomNodeInfo } from './useElementsTree.js';
+
+export function transformDescriptionNodeInfo(navNodeInfoResource: NavNodeInfoResource): IElementsTreeCustomNodeInfo {
+ return function transformDescriptionNodeInfo(nodeId, info) {
+ const node = navNodeInfoResource.get(nodeId);
+
+ if (node?.description && !node.folder) {
+ return {
+ ...info,
+ description: node.description,
+ };
+ }
+
+ return info;
+ };
+}
diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/useElementsTree.ts b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/useElementsTree.ts
index 1b397c3e98..927ff50229 100644
--- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/useElementsTree.ts
+++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/useElementsTree.ts
@@ -77,6 +77,7 @@ export interface IElementsTreeSettings {
showFolderExplorerPath: boolean;
configurable: boolean;
projects: boolean;
+ objectsDescription: boolean;
}
export interface IElementsTreeOptions {
diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/NavigationTree.tsx b/webapp/packages/plugin-navigation-tree/src/NavigationTree/NavigationTree.tsx
index 5f94ed089c..b0d39f5ce2 100644
--- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/NavigationTree.tsx
+++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/NavigationTree.tsx
@@ -24,6 +24,8 @@ import {
createElementsTreeSettings,
validateElementsTreeSettings,
} from './ElementsTree/ElementsTreeTools/NavigationTreeSettings/createElementsTreeSettings.js';
+import { ObjectsDescriptionSettingsPlaceholderElement } from './ElementsTree/ElementsTreeTools/NavigationTreeSettings/ObjectsDescriptionSettingsForm.js';
+import { transformDescriptionNodeInfo } from './ElementsTree/transformDescriptionNodeInfo.js';
import { transformFilteredNodeInfo } from './ElementsTree/transformFilteredNodeInfo.js';
import type { IElementsTreeSettings } from './ElementsTree/useElementsTree.js';
import elementsTreeToolsStyles from './ElementsTreeTools.module.css';
@@ -77,12 +79,13 @@ export const NavigationTree = observer(function NavigationTree() {
[navNodeInfoResource, projectsService, projectsNavNodeService],
);
const transformFilteredNode = useMemo(() => transformFilteredNodeInfo(navNodeInfoResource), [navNodeInfoResource]);
+ const transformDescriptionNode = useMemo(() => transformDescriptionNodeInfo(navNodeInfoResource), [navNodeInfoResource]);
const projectFilter = useMemo(
() => navigationTreeProjectFilter(projectsNavNodeService, projectsService, navNodeInfoResource, navTreeResource),
[projectsNavNodeService, projectsService, navNodeInfoResource, navTreeResource],
);
- const settingsElements = useMemo(() => [ProjectsSettingsPlaceholderElement], []);
+ const settingsElements = useMemo(() => [ProjectsSettingsPlaceholderElement, ObjectsDescriptionSettingsPlaceholderElement], []);
return (
@@ -93,7 +96,7 @@ export const NavigationTree = observer(function NavigationTree() {
filters={[duplicateFilter, connectionGroupFilter, projectFilter]}
renderers={[projectsRendererRenderer, navigationTreeConnectionGroupRenderer, connectionRenderer]}
navNodeFilterCompare={navigationTreeProjectSearchCompare}
- nodeInfoTransformers={[transformFilteredNode]}
+ nodeInfoTransformers={[transformFilteredNode, transformDescriptionNode]}
expandStateGetters={[projectsExpandStateGetter]}
settingsElements={settingsElements}
className={s(styles, { elementsTree: true })}
diff --git a/webapp/packages/plugin-navigation-tree/src/locales/en.ts b/webapp/packages/plugin-navigation-tree/src/locales/en.ts
index c29ccb8baa..3ce0050ba3 100644
--- a/webapp/packages/plugin-navigation-tree/src/locales/en.ts
+++ b/webapp/packages/plugin-navigation-tree/src/locales/en.ts
@@ -12,6 +12,7 @@ export default [
['app_navigationTree_limited', 'Elements are limited to {arg:limit} items'],
['app_navigationTree_action_link_with_editor', 'Link with editor'],
['app_navigationTree_action_collapse_all', 'Collapse all'],
+ ['app_navigationTree_settings_filter_objects_description', 'Show objects description'],
['app_navigationTree_settings_filter_title', 'Filter'],
['app_navigationTree_settings_filter_description', 'Show filtering field'],
['app_navigationTree_settings_filter_all_title', 'Show collapsed'],
diff --git a/webapp/packages/plugin-navigation-tree/src/locales/fr.ts b/webapp/packages/plugin-navigation-tree/src/locales/fr.ts
index f8f14c4060..0c655f5333 100644
--- a/webapp/packages/plugin-navigation-tree/src/locales/fr.ts
+++ b/webapp/packages/plugin-navigation-tree/src/locales/fr.ts
@@ -12,6 +12,7 @@ export default [
['app_navigationTree_limited', 'Les éléments sont limités à {arg:limit} éléments'],
['app_navigationTree_link_with_editor', "Lier avec l'éditeur"],
['app_navigationTree_action_collapse_all', 'Tout réduire'],
+ ['app_navigationTree_settings_filter_objects_description', 'Show objects description'],
['app_navigationTree_settings_filter_title', 'Filtrer'],
['app_navigationTree_settings_filter_description', 'Affiche les éléments filtrés dans les dossiers réduits'],
['app_navigationTree_settings_filter_all_title', 'Tout filtrer'],
diff --git a/webapp/packages/plugin-navigation-tree/src/locales/it.ts b/webapp/packages/plugin-navigation-tree/src/locales/it.ts
index f5ee281364..43f9ba7a4d 100644
--- a/webapp/packages/plugin-navigation-tree/src/locales/it.ts
+++ b/webapp/packages/plugin-navigation-tree/src/locales/it.ts
@@ -12,6 +12,7 @@ export default [
['app_navigationTree_limited', 'Elements are limited to {arg:limit} items'],
['app_navigationTree_action_link_with_editor', 'Link with editor'],
['app_navigationTree_action_collapse_all', 'Collapse all'],
+ ['app_navigationTree_settings_filter_objects_description', 'Show objects description'],
['app_navigationTree_settings_filter_title', 'Filter'],
['app_navigationTree_settings_filter_description', 'Show filtering field'],
['app_navigationTree_settings_filter_all_title', 'Show collapsed'],
diff --git a/webapp/packages/plugin-navigation-tree/src/locales/ru.ts b/webapp/packages/plugin-navigation-tree/src/locales/ru.ts
index 565cc04503..15f97b9e16 100644
--- a/webapp/packages/plugin-navigation-tree/src/locales/ru.ts
+++ b/webapp/packages/plugin-navigation-tree/src/locales/ru.ts
@@ -13,6 +13,7 @@ export default [
['app_navigationTree_action_link_with_editor', 'Синхронизовать с редактором'],
['app_navigationTree_action_collapse_all', 'Свернуть все элементы'],
+ ['app_navigationTree_settings_filter_objects_description', 'Показывать описание объектов'],
['app_navigationTree_settings_filter_title', 'Фильтр'],
['app_navigationTree_settings_filter_description', 'Показывать фильтр'],
diff --git a/webapp/packages/plugin-navigation-tree/src/locales/zh.ts b/webapp/packages/plugin-navigation-tree/src/locales/zh.ts
index bf573f317d..a4ff413d00 100644
--- a/webapp/packages/plugin-navigation-tree/src/locales/zh.ts
+++ b/webapp/packages/plugin-navigation-tree/src/locales/zh.ts
@@ -11,6 +11,7 @@ export default [
['app_navigationTree_limited', '元素数量限制为 {arg:limit} '],
['app_navigationTree_action_link_with_editor', '连接至编辑器'],
['app_navigationTree_action_collapse_all', '收起全部'],
+ ['app_navigationTree_settings_filter_objects_description', 'Show objects description'],
['app_navigationTree_settings_filter_title', '过滤器'],
['app_navigationTree_settings_filter_description', '查看过滤字段'],
['app_navigationTree_settings_filter_all_title', '查看收缩项'],
diff --git a/webapp/packages/plugin-sql-generator/src/GeneratorMenuBootstrap.ts b/webapp/packages/plugin-sql-generator/src/GeneratorMenuBootstrap.ts
index d2f1031429..d2d1e3482f 100644
--- a/webapp/packages/plugin-sql-generator/src/GeneratorMenuBootstrap.ts
+++ b/webapp/packages/plugin-sql-generator/src/GeneratorMenuBootstrap.ts
@@ -43,6 +43,7 @@ export class GeneratorMenuBootstrap extends Bootstrap {
const presentation = context.get(DATA_CONTEXT_DV_PRESENTATION);
return (
!model.isReadonly(resultIndex) &&
+ model.hasElementIdentifier(resultIndex) &&
model.source.getResult(resultIndex)?.dataFormat === ResultDataFormat.Resultset &&
!presentation?.readonly &&
(!presentation || presentation.type === DataViewerPresentationType.Data)
diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/AuthenticationPanel.tsx b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/AuthenticationPanel.tsx
deleted file mode 100644
index 6a04a56dc9..0000000000
--- a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/AuthenticationPanel.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * CloudBeaver - Cloud Database Manager
- * Copyright (C) 2020-2024 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 { ColoredContainer, Container, useAutoLoad } from '@cloudbeaver/core-blocks';
-import { type TabContainerPanelComponent, useTab, useTabState } from '@cloudbeaver/core-ui';
-
-import type { UserProfileFormProps } from '../UserProfileFormService.js';
-import { ChangePassword } from './ChangePassword.js';
-import type { UserProfileFormAuthenticationPart } from './UserProfileFormAuthenticationPart.js';
-
-export const AuthenticationPanel: TabContainerPanelComponent = observer(function AuthenticationPanel({ tabId }) {
- const tab = useTab(tabId);
- const tabState = useTabState();
-
- useAutoLoad(AuthenticationPanel, tabState, tab.selected);
-
- const disabled = tabState.isLoading();
- return (
-
-
-
-
-
- );
-});
diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/ChangePassword.tsx b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/ChangePassword.tsx
index 450929203f..6c6156ef13 100644
--- a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/ChangePassword.tsx
+++ b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/ChangePassword.tsx
@@ -5,20 +5,55 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
+import { observable } from 'mobx';
import { observer } from 'mobx-react-lite';
-import { Form, Group, GroupTitle, InputField, useCustomInputValidation, usePasswordValidation, useTranslate } from '@cloudbeaver/core-blocks';
+import { UserInfoResource } from '@cloudbeaver/core-authentication';
+import {
+ ColoredContainer,
+ ConfirmationDialog,
+ Container,
+ Form,
+ Group,
+ GroupTitle,
+ InputField,
+ ToolsAction,
+ ToolsPanel,
+ useCustomInputValidation,
+ useExecutor,
+ useForm,
+ useObservableRef,
+ usePasswordValidation,
+ useTranslate,
+} from '@cloudbeaver/core-blocks';
+import { useService } from '@cloudbeaver/core-di';
+import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs';
+import { NotificationService } from '@cloudbeaver/core-events';
+import { ExecutorInterrupter } from '@cloudbeaver/core-executor';
import { isValuesEqual } from '@cloudbeaver/core-utils';
-import type { IUserProfileFormAuthenticationState } from './IUserProfileFormAuthenticationState.js';
+import { userProfileContext } from '../../userProfileContext.js';
+import { UserProfileOptionsPanelService } from '../../UserProfileOptionsPanelService.js';
-interface Props {
- state: IUserProfileFormAuthenticationState;
- disabled?: boolean;
-}
-
-export const ChangePassword = observer(function ChangePassword({ state, disabled }) {
+export const ChangePassword = observer(function ChangePassword() {
const translate = useTranslate();
+ const state = useObservableRef(
+ {
+ oldPassword: '',
+ password: '',
+ repeatedPassword: '',
+ },
+ {
+ oldPassword: observable.ref,
+ password: observable.ref,
+ repeatedPassword: observable.ref,
+ },
+ );
+ const notificationService = useService(NotificationService);
+ const userProfileOptionsPanelService = useService(UserProfileOptionsPanelService);
+ const userInfoResource = useService(UserInfoResource);
+ const commonDialogService = useService(CommonDialogService);
+ const disabled = userInfoResource.isLoading();
const passwordValidationRef = usePasswordValidation();
const passwordRepeatRef = useCustomInputValidation(value => {
if (!isValuesEqual(value, state.password, null)) {
@@ -27,47 +62,106 @@ export const ChangePassword = observer(function ChangePassword({ state, d
return null;
});
+ const form = useForm({
+ async onSubmit() {
+ try {
+ await userInfoResource.updateLocalPassword(state.oldPassword, state.password);
+ resetForm();
+ notificationService.logSuccess({ title: 'plugin_user_profile_authentication_change_password_success' });
+ } catch (exception) {
+ if (exception instanceof Error) {
+ notificationService.logException(exception);
+ }
+ }
+ },
+ });
+
+ function resetForm() {
+ state.oldPassword = '';
+ state.password = '';
+ state.repeatedPassword = '';
+ form.ref?.reset();
+ }
+
+ useExecutor({
+ executor: userProfileOptionsPanelService.onClose,
+ handlers: [
+ async function closeHandler(_, contexts) {
+ const context = contexts.getContext(userProfileContext);
+
+ if ((state.oldPassword || state.password || state.repeatedPassword) && !context.force) {
+ const result = await commonDialogService.open(ConfirmationDialog, {
+ title: 'plugin_user_profile_authentication_change_password_cancel_title',
+ message: 'plugin_user_profile_authentication_change_password_cancel_message',
+ confirmActionText: 'ui_processing_ok',
+ });
+
+ if (result === DialogueStateResult.Rejected) {
+ ExecutorInterrupter.interrupt(contexts);
+ }
+ }
+ },
+ ],
+ });
+
return (
-
+
+
+
+
+
);
});
diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/UserProfileFormAuthenticationPart.ts b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/UserProfileFormAuthenticationPart.ts
deleted file mode 100644
index 7f4e6eddf4..0000000000
--- a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/UserProfileFormAuthenticationPart.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * CloudBeaver - Cloud Database Manager
- * Copyright (C) 2020-2024 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 type { PasswordPolicyService, UserInfoMetaParametersResource, UserInfoResource } from '@cloudbeaver/core-authentication';
-import type { IExecutionContextProvider } from '@cloudbeaver/core-executor';
-import { FormPart, formValidationContext, type IFormState } from '@cloudbeaver/core-ui';
-import { isValuesEqual, schemaValidationError } from '@cloudbeaver/core-utils';
-
-import type { IUserProfileFormState } from '../UserProfileFormService.js';
-import {
- type IUserProfileFormAuthenticationState,
- USER_PROFILE_FORM_AUTHENTICATION_PART_STATE_SCHEMA,
-} from './IUserProfileFormAuthenticationState.js';
-
-export class UserProfileFormAuthenticationPart extends FormPart {
- constructor(
- formState: IFormState,
- private readonly userInfoResource: UserInfoResource,
- private readonly passwordPolicyService: PasswordPolicyService,
- private readonly userInfoMetaParametersResource: UserInfoMetaParametersResource,
- ) {
- super(formState, {
- oldPassword: '',
- password: '',
- repeatedPassword: '',
- });
- }
-
- protected override format(data: IFormState, contexts: IExecutionContextProvider>): void {
- const parsed = USER_PROFILE_FORM_AUTHENTICATION_PART_STATE_SCHEMA.safeParse(this.state);
-
- this.state = parsed.success ? parsed.data : this.initialState;
- }
-
- override isOutdated(): boolean {
- return this.userInfoResource.isOutdated(undefined) || this.userInfoMetaParametersResource.isOutdated(undefined);
- }
-
- override isLoaded(): boolean {
- return this.loaded && this.userInfoResource.isLoaded(undefined) && this.userInfoMetaParametersResource.isLoaded(undefined);
- }
-
- override get isChanged(): boolean {
- if (!this.loaded) {
- return false;
- }
-
- return (
- !isValuesEqual(this.state.oldPassword, this.initialState.oldPassword, null) ||
- !isValuesEqual(this.state.password, this.initialState.password, null) ||
- !isValuesEqual(this.state.repeatedPassword, this.initialState.repeatedPassword, null)
- );
- }
-
- protected override validate(
- data: IFormState,
- contexts: IExecutionContextProvider>,
- ): void | Promise {
- const state = USER_PROFILE_FORM_AUTHENTICATION_PART_STATE_SCHEMA.safeParse(this.state);
- const validation = contexts.getContext(formValidationContext);
-
- if (!state.success) {
- validation.error(schemaValidationError(state.error, { prefix: null }).toString());
- return;
- }
-
- const passwordValidation = this.passwordPolicyService.validatePassword(this.state.password);
-
- if (!passwordValidation.isValid) {
- validation.error(passwordValidation.errorMessage);
- return;
- }
- }
-
- protected async saveChanges(): Promise {
- await this.userInfoResource.updateLocalPassword(this.state.oldPassword, this.state.password);
- }
-
- protected override async loader() {
- this.setInitialState({
- oldPassword: '',
- password: '',
- repeatedPassword: '',
- });
- }
-}
diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/UserProfileFormAuthenticationPartBootstrap.ts b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/UserProfileFormAuthenticationPartBootstrap.ts
index a2d86626a9..bbc8b64d06 100644
--- a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/UserProfileFormAuthenticationPartBootstrap.ts
+++ b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/UserProfileFormAuthenticationPartBootstrap.ts
@@ -5,27 +5,30 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
+import { UserInfoResource } from '@cloudbeaver/core-authentication';
import { importLazyComponent } from '@cloudbeaver/core-blocks';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
-import { UserProfileFormService } from '../UserProfileFormService.js';
-import { getUserProfileFormAuthenticationPart } from './getUserProfileFormAuthenticationPart.js';
+import { UserProfileTabsService } from '../../UserProfileTabsService.js';
-const AuthenticationPanel = importLazyComponent(() => import('./AuthenticationPanel.js').then(m => m.AuthenticationPanel));
+const ChangePassword = importLazyComponent(() => import('./ChangePassword.js').then(m => m.ChangePassword));
@injectable()
export class UserProfileFormAuthenticationPartBootstrap extends Bootstrap {
- constructor(private readonly userProfileFormService: UserProfileFormService) {
+ constructor(
+ private readonly userProfileTabsService: UserProfileTabsService,
+ private readonly userInfoResource: UserInfoResource,
+ ) {
super();
}
override register(): void {
- this.userProfileFormService.parts.add({
+ this.userProfileTabsService.tabContainer.add({
key: 'authentication',
name: 'ui_authentication',
- order: 2,
- panel: () => AuthenticationPanel,
- stateGetter: props => () => getUserProfileFormAuthenticationPart(props.formState),
+ order: 4,
+ isHidden: () => this.userInfoResource.isAnonymous(),
+ panel: () => ChangePassword,
});
}
}
diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/UserProfileFormAuthenticationPartService.ts b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/UserProfileFormAuthenticationPartService.ts
deleted file mode 100644
index 217752eea8..0000000000
--- a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/UserProfileFormAuthenticationPartService.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * CloudBeaver - Cloud Database Manager
- * Copyright (C) 2020-2024 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 { PlaceholderContainer } from '@cloudbeaver/core-blocks';
-import { injectable } from '@cloudbeaver/core-di';
-
-import type { UserProfileFormProps } from '../UserProfileFormService.js';
-
-@injectable()
-export class UserProfileFormAuthenticationPartService {
- placeholderContainer: PlaceholderContainer;
- constructor() {
- this.placeholderContainer = new PlaceholderContainer();
- }
-}
diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/getUserProfileFormAuthenticationPart.ts b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/getUserProfileFormAuthenticationPart.ts
deleted file mode 100644
index 6859988100..0000000000
--- a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserAuthenticationPart/getUserProfileFormAuthenticationPart.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * CloudBeaver - Cloud Database Manager
- * Copyright (C) 2020-2024 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 { PasswordPolicyService, UserInfoMetaParametersResource, UserInfoResource } from '@cloudbeaver/core-authentication';
-import { createDataContext, DATA_CONTEXT_DI_PROVIDER } from '@cloudbeaver/core-data-context';
-import type { IFormState } from '@cloudbeaver/core-ui';
-
-import type { IUserProfileFormState } from '../UserProfileFormService.js';
-import { UserProfileFormAuthenticationPart } from './UserProfileFormAuthenticationPart.js';
-
-const DATA_CONTEXT_USER_PROFILE_FORM_AUTHENTICATION_PART = createDataContext('User Profile Form Info Part');
-
-export function getUserProfileFormAuthenticationPart(formState: IFormState): UserProfileFormAuthenticationPart {
- return formState.getPart(DATA_CONTEXT_USER_PROFILE_FORM_AUTHENTICATION_PART, context => {
- const di = context.get(DATA_CONTEXT_DI_PROVIDER)!;
- const userInfoResource = di.getService(UserInfoResource);
- const passwordPolicyService = di.getService(PasswordPolicyService);
- const userInfoMetaParametersResource = di.getService(UserInfoMetaParametersResource);
-
- return new UserProfileFormAuthenticationPart(formState, userInfoResource, passwordPolicyService, userInfoMetaParametersResource);
- });
-}
diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/UserProfileFormInfo.tsx b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/UserProfileFormInfo.tsx
index 1c2f1b3bef..77baed0b0b 100644
--- a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/UserProfileFormInfo.tsx
+++ b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/UserProfileFormInfo.tsx
@@ -5,43 +5,48 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
+import { toJS } from 'mobx';
import { observer } from 'mobx-react-lite';
-import { ColoredContainer, Container, Group, GroupTitle, InputField, Loader, useAutoLoad, useTranslate } from '@cloudbeaver/core-blocks';
-import { type TabContainerPanelComponent, useTab, useTabState } from '@cloudbeaver/core-ui';
+import { UserInfoMetaParametersResource, UserInfoResource } from '@cloudbeaver/core-authentication';
+import { ColoredContainer, Container, Group, GroupTitle, InputField, Loader, useResource, useTranslate } from '@cloudbeaver/core-blocks';
+import { type TabContainerPanelComponent, useTab } from '@cloudbeaver/core-ui';
-import type { UserProfileFormProps } from '../UserProfileFormService.js';
+import type { IUserProfileFormInfoState } from './IUserProfileFormInfoState.js';
import { UserActiveAuthMethods } from './UserActiveAuthMethods/UserActiveAuthMethods.js';
import { UserProfileFormInfoMetaParameters } from './UserProfileFormInfoMetaParameters.js';
-import type { UserProfileFormInfoPart } from './UserProfileFormInfoPart.js';
-export const UserProfileFormInfo: TabContainerPanelComponent = observer(function UserProfileFormInfo({ tabId }) {
+export const UserProfileFormInfo: TabContainerPanelComponent = observer(function UserProfileFormInfo({ tabId }) {
const translate = useTranslate();
+ const userInfoResource = useResource(UserProfileFormInfo, UserInfoResource, undefined);
+ const userInfoMetaParametersResource = useResource(UserProfileFormInfo, UserInfoMetaParametersResource, undefined);
+ const disabled = userInfoResource.isLoading() || userInfoMetaParametersResource.isLoading();
const tab = useTab(tabId);
- const tabState = useTabState();
- useAutoLoad(UserProfileFormInfo, tabState, tab.selected);
-
- const disabled = tabState.isLoading();
+ const state: IUserProfileFormInfoState = {
+ userId: userInfoResource.data?.userId || '',
+ displayName: userInfoResource.data?.displayName || '',
+ authRole: userInfoResource.data?.authRole || '',
+ metaParameters: toJS(userInfoMetaParametersResource.data || {}),
+ };
return (
-
+
- {translate('plugin_user_profile_info')}
-
+
{translate('plugin_user_profile_info_id')}
-
+
{translate('plugin_user_profile_info_displayName')}
-
+
{translate('authentication_user_role')}
-
+
diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/UserProfileFormInfoMetaParameters.tsx b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/UserProfileFormInfoMetaParameters.tsx
index adfd9879f0..0e59ee2cbf 100644
--- a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/UserProfileFormInfoMetaParameters.tsx
+++ b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/UserProfileFormInfoMetaParameters.tsx
@@ -10,15 +10,15 @@ import { observer } from 'mobx-react-lite';
import { UserMetaParametersResource } from '@cloudbeaver/core-authentication';
import { Container, ObjectPropertyInfoForm, useResource } from '@cloudbeaver/core-blocks';
-import type { UserProfileFormInfoPart } from './UserProfileFormInfoPart.js';
+import type { IUserProfileFormInfoState } from './IUserProfileFormInfoState.js';
interface Props {
- tabState: UserProfileFormInfoPart;
+ state: IUserProfileFormInfoState;
tabSelected: boolean;
disabled: boolean;
}
-export const UserProfileFormInfoMetaParameters = observer(function UserProfileFormInfoMetaParameters({ tabState, tabSelected, disabled }) {
+export const UserProfileFormInfoMetaParameters = observer(function UserProfileFormInfoMetaParameters({ state, tabSelected, disabled }) {
const userMetaParameters = useResource(UserProfileFormInfoMetaParameters, UserMetaParametersResource, undefined, { active: tabSelected });
if (userMetaParameters.data.length === 0) {
@@ -27,7 +27,7 @@ export const UserProfileFormInfoMetaParameters = observer(function UserPr
return (
-
+
);
});
diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/UserProfileFormInfoPart.ts b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/UserProfileFormInfoPart.ts
deleted file mode 100644
index 89a5cc7f73..0000000000
--- a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/UserProfileFormInfoPart.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * CloudBeaver - Cloud Database Manager
- * Copyright (C) 2020-2024 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 { toJS } from 'mobx';
-
-import type { UserInfoMetaParametersResource, UserInfoResource } from '@cloudbeaver/core-authentication';
-import type { IExecutionContextProvider } from '@cloudbeaver/core-executor';
-import { FormPart, type IFormState } from '@cloudbeaver/core-ui';
-import { isObjectsEqual, isValuesEqual } from '@cloudbeaver/core-utils';
-
-import type { IUserProfileFormState } from '../UserProfileFormService.js';
-import { type IUserProfileFormInfoState, USER_PROFILE_FORM_INFO_PART_STATE_SCHEMA } from './IUserProfileFormInfoState.js';
-
-export class UserProfileFormInfoPart extends FormPart {
- constructor(
- formState: IFormState,
- private readonly userInfoResource: UserInfoResource,
- private readonly userInfoMetaParametersResource: UserInfoMetaParametersResource,
- ) {
- super(formState, {
- userId: userInfoResource.data?.userId || '',
- displayName: userInfoResource.data?.displayName || '',
- authRole: userInfoResource.data?.authRole || '',
- metaParameters: toJS(userInfoMetaParametersResource.data || {}),
- });
- }
-
- protected override format(data: IFormState, contexts: IExecutionContextProvider>): void {
- this.state = USER_PROFILE_FORM_INFO_PART_STATE_SCHEMA.parse(this.state);
- }
-
- override isOutdated(): boolean {
- return this.userInfoResource.isOutdated(undefined) || this.userInfoMetaParametersResource.isOutdated(undefined);
- }
-
- override isLoaded(): boolean {
- return this.loaded && this.userInfoResource.isLoaded(undefined) && this.userInfoMetaParametersResource.isLoaded(undefined);
- }
-
- override get isChanged(): boolean {
- if (!this.loaded) {
- return false;
- }
-
- return (
- !isValuesEqual(this.state.userId, this.initialState.userId, null) ||
- !isValuesEqual(this.state.displayName, this.initialState.displayName, null) ||
- !isObjectsEqual(this.state.metaParameters, this.initialState.metaParameters) ||
- !isValuesEqual(this.state.authRole, this.initialState.authRole, '')
- );
- }
-
- protected saveChanges(): Promise {
- return Promise.resolve();
- }
-
- protected override async loader() {
- const [user, metaParameters] = await Promise.all([this.userInfoResource.load(undefined), this.userInfoMetaParametersResource.load(undefined)]);
-
- this.setInitialState({
- userId: user?.userId || '',
- displayName: user?.displayName || '',
- authRole: user?.authRole ?? '',
- metaParameters: toJS(metaParameters || {}),
- });
- }
-}
diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/UserProfileFormInfoPartBootstrap.ts b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/UserProfileFormInfoPartBootstrap.ts
index 78b4f743ee..23f0094821 100644
--- a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/UserProfileFormInfoPartBootstrap.ts
+++ b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/UserProfileFormInfoPartBootstrap.ts
@@ -5,27 +5,30 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
+import { UserInfoResource } from '@cloudbeaver/core-authentication';
import { importLazyComponent } from '@cloudbeaver/core-blocks';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
-import { UserProfileFormService } from '../UserProfileFormService.js';
-import { getUserProfileFormInfoPart } from './getUserProfileFormInfoPart.js';
+import { UserProfileTabsService } from '../../UserProfileTabsService.js';
const UserProfileFormInfo = importLazyComponent(() => import('./UserProfileFormInfo.js').then(m => m.UserProfileFormInfo));
@injectable()
export class UserProfileFormInfoPartBootstrap extends Bootstrap {
- constructor(private readonly userProfileFormService: UserProfileFormService) {
+ constructor(
+ private readonly userProfileTabsService: UserProfileTabsService,
+ private readonly userInfoResource: UserInfoResource,
+ ) {
super();
}
override register(): void {
- this.userProfileFormService.parts.add({
- key: 'info',
+ this.userProfileTabsService.tabContainer.add({
+ key: 'user_info',
name: 'plugin_user_profile_info',
order: 1,
+ isHidden: () => this.userInfoResource.isAnonymous(),
panel: () => UserProfileFormInfo,
- stateGetter: props => () => getUserProfileFormInfoPart(props.formState),
});
}
}
diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/UserProfileFormInfoPartService.ts b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/UserProfileFormInfoPartService.ts
deleted file mode 100644
index a53930294b..0000000000
--- a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/UserProfileFormInfoPartService.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * CloudBeaver - Cloud Database Manager
- * Copyright (C) 2020-2024 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 { PlaceholderContainer } from '@cloudbeaver/core-blocks';
-import { injectable } from '@cloudbeaver/core-di';
-
-import type { UserProfileFormProps } from '../UserProfileFormService.js';
-
-@injectable()
-export class UserProfileFormInfoPartService {
- placeholderContainer: PlaceholderContainer;
- constructor() {
- this.placeholderContainer = new PlaceholderContainer();
- }
-}
diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/getUserProfileFormInfoPart.ts b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/getUserProfileFormInfoPart.ts
deleted file mode 100644
index 10580b8e3d..0000000000
--- a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfoPart/getUserProfileFormInfoPart.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * CloudBeaver - Cloud Database Manager
- * Copyright (C) 2020-2024 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 { UserInfoMetaParametersResource, UserInfoResource } from '@cloudbeaver/core-authentication';
-import { createDataContext, DATA_CONTEXT_DI_PROVIDER } from '@cloudbeaver/core-data-context';
-import type { IFormState } from '@cloudbeaver/core-ui';
-
-import type { IUserProfileFormState } from '../UserProfileFormService.js';
-import { UserProfileFormInfoPart } from './UserProfileFormInfoPart.js';
-
-const DATA_CONTEXT_USER_PROFILE_FORM_INFO_PART = createDataContext('User Profile Form Info Part');
-
-export function getUserProfileFormInfoPart(formState: IFormState): UserProfileFormInfoPart {
- return formState.getPart(DATA_CONTEXT_USER_PROFILE_FORM_INFO_PART, context => {
- const di = context.get(DATA_CONTEXT_DI_PROVIDER)!;
- const userInfoResource = di.getService(UserInfoResource);
- const userInfoMetaParametersResource = di.getService(UserInfoMetaParametersResource);
-
- return new UserProfileFormInfoPart(formState, userInfoResource, userInfoMetaParametersResource);
- });
-}
diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserProfileForm.tsx b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserProfileForm.tsx
deleted file mode 100644
index 81a1358326..0000000000
--- a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserProfileForm.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * CloudBeaver - Cloud Database Manager
- * Copyright (C) 2020-2024 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 { useService } from '@cloudbeaver/core-di';
-import { NotificationService } from '@cloudbeaver/core-events';
-import { BaseForm, type IBaseFormSubmitInfo, type IFormState } from '@cloudbeaver/core-ui';
-
-import { type IUserProfileFormState, UserProfileFormService } from './UserProfileFormService.js';
-
-interface Props {
- state: IFormState;
-}
-
-export const UserProfileForm = observer(function UserProfileForm({ state }) {
- const notificationService = useService(NotificationService);
- const userProfileFormService = useService(UserProfileFormService);
-
- function onSubmit({ success }: IBaseFormSubmitInfo) {
- if (success) {
- notificationService.logSuccess({ title: 'authentication_administration_user_updated' });
- } else {
- notificationService.logError({ title: 'authentication_administration_user_update_failed' });
- }
- }
-
- return ;
-});
diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserProfileFormBootstrap.ts b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserProfileFormBootstrap.ts
deleted file mode 100644
index 260ebe8831..0000000000
--- a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserProfileFormBootstrap.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * CloudBeaver - Cloud Database Manager
- * Copyright (C) 2020-2024 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 { UserInfoResource } from '@cloudbeaver/core-authentication';
-import { importLazyComponent } from '@cloudbeaver/core-blocks';
-import { Bootstrap, injectable } from '@cloudbeaver/core-di';
-
-import { UserProfileTabsService } from '../UserProfileTabsService.js';
-
-const UserProfileFormPanel = importLazyComponent(() => import('./UserProfileFormPanel.js').then(module => module.UserProfileFormPanel));
-
-@injectable()
-export class UserProfileFormBootstrap extends Bootstrap {
- constructor(
- private readonly userProfileTabsService: UserProfileTabsService,
- private readonly userInfoResource: UserInfoResource,
- ) {
- super();
- }
-
- override register(): void {
- this.userProfileTabsService.tabContainer.add({
- key: 'account',
- name: 'plugin_user_profile_account_title',
- order: 1,
- isHidden: () => this.userInfoResource.isAnonymous(),
- panel: () => UserProfileFormPanel,
- });
- }
-}
diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserProfileFormPanel.tsx b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserProfileFormPanel.tsx
deleted file mode 100644
index 6831c578d3..0000000000
--- a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserProfileFormPanel.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * CloudBeaver - Cloud Database Manager
- * Copyright (C) 2020-2024 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 { useState } from 'react';
-
-import { ConfirmationDialog, useExecutor } from '@cloudbeaver/core-blocks';
-import { IServiceProvider, useService } from '@cloudbeaver/core-di';
-import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs';
-import { ExecutorInterrupter } from '@cloudbeaver/core-executor';
-import { FormMode, type TabContainerPanelComponent } from '@cloudbeaver/core-ui';
-
-import { userProfileContext } from '../userProfileContext.js';
-import { UserProfileOptionsPanelService } from '../UserProfileOptionsPanelService.js';
-import { UserProfileForm } from './UserProfileForm.js';
-import { UserProfileFormService } from './UserProfileFormService.js';
-import { UserProfileFormState } from './UserProfileFormState.js';
-
-export const UserProfileFormPanel: TabContainerPanelComponent = observer(function UserProfileFormPanel({ tabId }) {
- const serviceProvider = useService(IServiceProvider);
- const userProfileFormService = useService(UserProfileFormService);
- const userProfileOptionsPanelService = useService(UserProfileOptionsPanelService);
- const commonDialogService = useService(CommonDialogService);
-
- const [state] = useState(() => {
- const state = new UserProfileFormState(serviceProvider, userProfileFormService, {});
- state.setMode(FormMode.Edit);
-
- return state;
- });
-
- useExecutor({
- executor: userProfileOptionsPanelService.onClose,
- handlers: [
- async function closeHandler(_, contexts) {
- const context = contexts.getContext(userProfileContext);
-
- if (state.isChanged && !context.force) {
- const result = await commonDialogService.open(ConfirmationDialog, {
- title: 'plugin_connections_connection_edit_cancel_title',
- message: 'plugin_connections_connection_edit_cancel_message',
- confirmActionText: 'ui_processing_ok',
- });
-
- if (result === DialogueStateResult.Rejected) {
- ExecutorInterrupter.interrupt(contexts);
- }
- }
- },
- ],
- });
-
- return ;
-});
diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserProfileFormService.ts b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserProfileFormService.ts
deleted file mode 100644
index 445ecb1dca..0000000000
--- a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserProfileFormService.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * CloudBeaver - Cloud Database Manager
- * Copyright (C) 2020-2024 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 { NotificationService } from '@cloudbeaver/core-events';
-import { LocalizationService } from '@cloudbeaver/core-localization';
-import { FormBaseService, type IFormProps } from '@cloudbeaver/core-ui';
-
-export interface IUserProfileFormState {}
-
-export type UserProfileFormProps = IFormProps;
-
-@injectable()
-export class UserProfileFormService extends FormBaseService {
- constructor(localizationService: LocalizationService, notificationService: NotificationService) {
- super(localizationService, notificationService, 'User profile form');
- }
-}
diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserProfileFormState.ts b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserProfileFormState.ts
deleted file mode 100644
index 79221de577..0000000000
--- a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserProfileFormState.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * CloudBeaver - Cloud Database Manager
- * Copyright (C) 2020-2024 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 type { IServiceProvider } from '@cloudbeaver/core-di';
-import { FormState } from '@cloudbeaver/core-ui';
-
-import type { IUserProfileFormState, UserProfileFormService } from './UserProfileFormService.js';
-
-export class UserProfileFormState extends FormState {
- constructor(serviceProvider: IServiceProvider, service: UserProfileFormService, config: IUserProfileFormState) {
- super(serviceProvider, service, config);
- }
-}
diff --git a/webapp/packages/plugin-user-profile/src/locales/en.ts b/webapp/packages/plugin-user-profile/src/locales/en.ts
index 2baa5952cf..d9f724d4b8 100644
--- a/webapp/packages/plugin-user-profile/src/locales/en.ts
+++ b/webapp/packages/plugin-user-profile/src/locales/en.ts
@@ -24,4 +24,9 @@ export default [
['plugin_user_profile_authentication_change_password_submit_label', 'Change'],
['plugin_user_profile_authentication_change_password_passwords_not_match', "Passwords don't match"],
['plugin_user_profile_authentication_change_password_password_validation_error', 'Password validation failed'],
+ ['plugin_user_profile_authentication_change_password_cancel_title', 'Cancel password change'],
+ [
+ 'plugin_user_profile_authentication_change_password_cancel_message',
+ "You're going to cancel password changes. Unsaved changes will be lost. Are you sure?",
+ ],
];
diff --git a/webapp/packages/plugin-user-profile/src/locales/fr.ts b/webapp/packages/plugin-user-profile/src/locales/fr.ts
index 068d334836..3a2a54f68c 100644
--- a/webapp/packages/plugin-user-profile/src/locales/fr.ts
+++ b/webapp/packages/plugin-user-profile/src/locales/fr.ts
@@ -10,12 +10,12 @@ export default [
['plugin_user_profile_menu', 'Profil'],
['plugin_user_profile_info', 'Infos'],
['plugin_user_profile_info_id', 'Identifiant'],
- ['plugin_user_profile_info_displayName', 'Nom d\'affichage'],
- ['plugin_user_profile_auth_providers', 'Fournisseurs d\'authentification'],
+ ['plugin_user_profile_info_displayName', "Nom d'affichage"],
+ ['plugin_user_profile_auth_providers', "Fournisseurs d'authentification"],
['plugin_user_profile_info', 'Informations utilisateur'],
- ['plugin_user_profile_auth_tokens', 'Jetons d\'authentification'],
- ['plugin_user_profile_auth_providers_active', 'L\'authentification est active'],
-
+ ['plugin_user_profile_auth_tokens', "Jetons d'authentification"],
+ ['plugin_user_profile_auth_providers_active', "L'authentification est active"],
+
['plugin_user_profile_authentication_change_password', 'Changer le mot de passe'],
['plugin_user_profile_authentication_change_password_current_password', 'Mot de passe actuel'],
['plugin_user_profile_authentication_change_password_new_password', 'Nouveau mot de passe'],
@@ -24,4 +24,9 @@ export default [
['plugin_user_profile_authentication_change_password_submit_label', 'Changer'],
['plugin_user_profile_authentication_change_password_passwords_not_match', 'Les mots de passe ne correspondent pas'],
['plugin_user_profile_authentication_change_password_validation_error', 'Échec de la validation du mot de passe'],
+ ['plugin_user_profile_authentication_change_password_cancel_title', 'Cancel password change'],
+ [
+ 'plugin_user_profile_authentication_change_password_cancel_message',
+ "You're going to cancel password changes. Unsaved changes will be lost. Are you sure?",
+ ],
];
diff --git a/webapp/packages/plugin-user-profile/src/locales/it.ts b/webapp/packages/plugin-user-profile/src/locales/it.ts
index 7a51e831df..99cd22746b 100644
--- a/webapp/packages/plugin-user-profile/src/locales/it.ts
+++ b/webapp/packages/plugin-user-profile/src/locales/it.ts
@@ -24,4 +24,9 @@ export default [
['plugin_user_profile_authentication_change_password_submit_label', 'Modifica'],
['plugin_user_profile_authentication_change_password_passwords_not_match', 'Le Passwords non coincidono'],
['plugin_user_profile_authentication_change_password_password_validation_error', 'Password validation failed'],
+ ['plugin_user_profile_authentication_change_password_cancel_title', 'Cancel password change'],
+ [
+ 'plugin_user_profile_authentication_change_password_cancel_message',
+ "You're going to cancel password changes. Unsaved changes will be lost. Are you sure?",
+ ],
];
diff --git a/webapp/packages/plugin-user-profile/src/locales/ru.ts b/webapp/packages/plugin-user-profile/src/locales/ru.ts
index a4af6286c1..ca9500685e 100644
--- a/webapp/packages/plugin-user-profile/src/locales/ru.ts
+++ b/webapp/packages/plugin-user-profile/src/locales/ru.ts
@@ -24,4 +24,9 @@ export default [
['plugin_user_profile_authentication_change_password_submit_label', 'Сменить'],
['plugin_user_profile_authentication_change_password_passwords_not_match', 'Пароли не совпадают'],
['plugin_user_profile_authentication_change_password_password_validation_error', 'Валидация пароля не удалась'],
+ ['plugin_user_profile_authentication_change_password_cancel_title', 'Отменить смену пароля'],
+ [
+ 'plugin_user_profile_authentication_change_password_cancel_message',
+ 'Вы собираетесь отменить смену пароля. Несохраненные данные будут утеряны. Вы уверены?',
+ ],
];
diff --git a/webapp/packages/plugin-user-profile/src/locales/zh.ts b/webapp/packages/plugin-user-profile/src/locales/zh.ts
index 0d61d88faf..75f9ab1aa2 100644
--- a/webapp/packages/plugin-user-profile/src/locales/zh.ts
+++ b/webapp/packages/plugin-user-profile/src/locales/zh.ts
@@ -24,4 +24,9 @@ export default [
['plugin_user_profile_authentication_change_password_submit_label', '更改'],
['plugin_user_profile_authentication_change_password_passwords_not_match', '密码不匹配'],
['plugin_user_profile_authentication_change_password_password_validation_error', '密码校验失败'],
+ ['plugin_user_profile_authentication_change_password_cancel_title', 'Cancel password change'],
+ [
+ 'plugin_user_profile_authentication_change_password_cancel_message',
+ "You're going to cancel password changes. Unsaved changes will be lost. Are you sure?",
+ ],
];
diff --git a/webapp/packages/plugin-user-profile/src/manifest.ts b/webapp/packages/plugin-user-profile/src/manifest.ts
index 410dee4c9b..522faed898 100644
--- a/webapp/packages/plugin-user-profile/src/manifest.ts
+++ b/webapp/packages/plugin-user-profile/src/manifest.ts
@@ -17,17 +17,10 @@ export const userProfilePlugin: PluginManifest = {
() => import('./LocaleService.js').then(m => m.LocaleService),
() => import('./UserProfileTabsService.js').then(m => m.UserProfileTabsService),
() => import('./UserProfileOptionsPanelService.js').then(m => m.UserProfileOptionsPanelService),
- () => import('./UserProfileForm/UserProfileFormBootstrap.js').then(m => m.UserProfileFormBootstrap),
- () => import('./UserProfileForm/UserProfileFormService.js').then(m => m.UserProfileFormService),
() => import('./UserProfileForm/UserInfoPart/UserProfileFormInfoPartBootstrap.js').then(m => m.UserProfileFormInfoPartBootstrap),
- () => import('./UserProfileForm/UserInfoPart/UserProfileFormInfoPartService.js').then(m => m.UserProfileFormInfoPartService),
() =>
import('./UserProfileForm/UserAuthenticationPart/UserProfileFormAuthenticationPartBootstrap.js').then(
m => m.UserProfileFormAuthenticationPartBootstrap,
),
- () =>
- import('./UserProfileForm/UserAuthenticationPart/UserProfileFormAuthenticationPartService.js').then(
- m => m.UserProfileFormAuthenticationPartService,
- ),
],
};
diff --git a/webapp/packages/product-default/package.json b/webapp/packages/product-default/package.json
index f311b824ab..f9d26128e4 100644
--- a/webapp/packages/product-default/package.json
+++ b/webapp/packages/product-default/package.json
@@ -6,7 +6,7 @@
"src/**/*.scss",
"public/**/*"
],
- "version": "24.3.3",
+ "version": "24.3.4",
"description": "CloudBeaver Community",
"license": "Apache-2.0",
"main": "dist/index.js",