mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-16 14:50:15 +08:00
Compare commits
141
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c814017566 | ||
|
|
4ab47583ad | ||
|
|
37cfecbf91 | ||
|
|
046659672c | ||
|
|
2a3a94608c | ||
|
|
bf52e23793 | ||
|
|
4a2ce73a9c | ||
|
|
b73b707b7f | ||
|
|
57461bd8be | ||
|
|
71bebb32dd | ||
|
|
d810edf89d | ||
|
|
28cade86aa | ||
|
|
e64d5ff92c | ||
|
|
92186dd332 | ||
|
|
36be366c64 | ||
|
|
860a9f55f2 | ||
|
|
754c8d562d | ||
|
|
ed0e92cdd6 | ||
|
|
9267e65300 | ||
|
|
4e3ec7cbf4 | ||
|
|
6978ae5355 | ||
|
|
3986debe8f | ||
|
|
38cfb0a690 | ||
|
|
682a387564 | ||
|
|
de0dee29ed | ||
|
|
3a1ae15ecb | ||
|
|
8cf064e845 | ||
|
|
ec1b455f4d | ||
|
|
683d85c8d6 | ||
|
|
8066904d4d | ||
|
|
7ea46ea2c5 | ||
|
|
8aee24cf1c | ||
|
|
5d83098d38 | ||
|
|
3e855c3afd | ||
|
|
9c50589586 | ||
|
|
f99c2e3ebd | ||
|
|
e141575785 | ||
|
|
b93b85f622 | ||
|
|
267a753d87 | ||
|
|
6cea8cd64f | ||
|
|
b630cb6791 | ||
|
|
a9fd8fb909 | ||
|
|
9a5c3f67ab | ||
|
|
21b024f375 | ||
|
|
e2339f3666 | ||
|
|
2fbb71fb96 | ||
|
|
75e9da4705 | ||
|
|
6e3bc7f63f | ||
|
|
11a7dc26dc | ||
|
|
3e9b0bfdd8 | ||
|
|
73eaaa31f9 | ||
|
|
574f893e6e | ||
|
|
10876f1a62 | ||
|
|
54b5c12550 | ||
|
|
69f2a57be5 | ||
|
|
4f7c7ba808 | ||
|
|
25247321b8 | ||
|
|
72975abef0 | ||
|
|
1fadf5009d | ||
|
|
23090e286a | ||
|
|
1cacf6a5cc | ||
|
|
d0e86745f1 | ||
|
|
97d179a103 | ||
|
|
37b8716aee | ||
|
|
d1c2d6547f | ||
|
|
4f53ece164 | ||
|
|
0fe432f45c | ||
|
|
58b8eafc7b | ||
|
|
cdb4c7e1fc | ||
|
|
3a2d4f807d | ||
|
|
39ea1e2b7f | ||
|
|
da7d4eef19 | ||
|
|
d23ab441ff | ||
|
|
220c59ed01 | ||
|
|
eb0f3db330 | ||
|
|
3babafaecd | ||
|
|
4765629537 | ||
|
|
534526e5e4 | ||
|
|
2d6ebd91f1 | ||
|
|
e6e92f0bb3 | ||
|
|
5a394e648d | ||
|
|
e466462269 | ||
|
|
1c57e670c0 | ||
|
|
3808af50c7 | ||
|
|
390452d8a6 | ||
|
|
b733f395d3 | ||
|
|
e31ae9a439 | ||
|
|
07f60c0034 | ||
|
|
ba32c12b9c | ||
|
|
ca3ce9b79d | ||
|
|
96d6dba83a | ||
|
|
6ce5ef235c | ||
|
|
a1404e3495 | ||
|
|
d4e2f2365a | ||
|
|
ab813e21ab | ||
|
|
c5190b2567 | ||
|
|
e896789cb0 | ||
|
|
60827d769a | ||
|
|
65876e9b4e | ||
|
|
048206266d | ||
|
|
f41b9d24fe | ||
|
|
03dabc0e1b | ||
|
|
56946742c2 | ||
|
|
e1e4ae0ba6 | ||
|
|
80f258b561 | ||
|
|
ac48e13039 | ||
|
|
3877e914c4 | ||
|
|
564ceb7d82 | ||
|
|
622b5133c2 | ||
|
|
376575efce | ||
|
|
980e169ee1 | ||
|
|
e9cbaeadd9 | ||
|
|
66764292c9 | ||
|
|
2e2a61a330 | ||
|
|
edda6d8db3 | ||
|
|
c9f4ddd382 | ||
|
|
fb0bdc1017 | ||
|
|
123dc7777c | ||
|
|
2963610498 | ||
|
|
d1e07be46f | ||
|
|
043204002f | ||
|
|
c8c82c7f3e | ||
|
|
a0944850b1 | ||
|
|
31841b7720 | ||
|
|
a2695f3102 | ||
|
|
3c7c1ffc8e | ||
|
|
55c776f3e4 | ||
|
|
8a4d88e721 | ||
|
|
426fd40c82 | ||
|
|
a547b85ef2 | ||
|
|
e32e6eac39 | ||
|
|
901669b802 | ||
|
|
69d7a25fec | ||
|
|
576028b367 | ||
|
|
9276777c3e | ||
|
|
4ff45220ee | ||
|
|
62ad5a61c9 | ||
|
|
587a9e58e1 | ||
|
|
4401cfe92f | ||
|
|
59e33e8c48 | ||
|
|
d16c7d4502 |
@@ -49,21 +49,14 @@ jobs:
|
||||
node-version: "lts/*"
|
||||
cache: "yarn"
|
||||
cache-dependency-path: |
|
||||
cloudbeaver/common-*/yarn.lock
|
||||
cloudbeaver/webapp/yarn.lock
|
||||
|
||||
- name: restore cloudbeaver-common typescript cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "cloudbeaver/common-*/**/*/lib"
|
||||
key: ${{ runner.os }}-common-dist-${{ hashFiles('cloudbeaver/common-*/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-common-dist-
|
||||
|
||||
- name: restore typescript cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "cloudbeaver/webapp/**/packages/*/dist"
|
||||
path: |
|
||||
"cloudbeaver/webapp/common-*/**/*/lib"
|
||||
"cloudbeaver/webapp/**/packages/*/dist"
|
||||
key: ${{ runner.os }}-dist-${{ hashFiles('cloudbeaver/webapp/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-dist-
|
||||
@@ -71,23 +64,11 @@ jobs:
|
||||
- if: inputs.clean-cache
|
||||
run: yarn clear
|
||||
|
||||
- run: yarn install --immutable
|
||||
working-directory: ./cloudbeaver/common-typescript
|
||||
|
||||
- run: yarn install --immutable
|
||||
working-directory: ./cloudbeaver/common-react
|
||||
|
||||
- run: yarn install --immutable
|
||||
|
||||
- run: yarn bundle
|
||||
working-directory: ./cloudbeaver/webapp/packages/product-default
|
||||
|
||||
- run: yarn test
|
||||
working-directory: ./cloudbeaver/common-typescript
|
||||
|
||||
- run: yarn test
|
||||
working-directory: ./cloudbeaver/common-react
|
||||
|
||||
- run: yarn test
|
||||
|
||||
# - name: Archive build artifacts
|
||||
|
||||
@@ -30,10 +30,10 @@ jobs:
|
||||
cache-dependency-path: "./cloudbeaver/webapp/yarn.lock"
|
||||
|
||||
# - run: yarn install --immutable
|
||||
# working-directory: ./cloudbeaver/common-typescript
|
||||
# working-directory: ./cloudbeaver/webapp/common-typescript
|
||||
|
||||
# - run: yarn install --immutable
|
||||
# working-directory: ./cloudbeaver/common-react
|
||||
# working-directory: ./cloudbeaver/webapp/common-react
|
||||
|
||||
- run: |
|
||||
yarn install --immutable
|
||||
|
||||
Vendored
+1
-1
@@ -48,7 +48,7 @@
|
||||
"-Declipse.pde.launch=true",
|
||||
"-XstartOnFirstThread",
|
||||
"-Dfile.encoding=UTF-8",
|
||||
"--add-modules=ALL-SYSTEM",
|
||||
"--add-modules=ALL-DEFAULT",
|
||||
"--add-opens=java.base/java.io=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.lang.reflect=ALL-UNNAMED",
|
||||
|
||||
Vendored
+14
-5
@@ -26,7 +26,7 @@
|
||||
"group": "test",
|
||||
"args": ["test"],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/common-typescript"
|
||||
"cwd": "${workspaceFolder}/webapp/common-typescript"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -36,7 +36,7 @@
|
||||
"group": "test",
|
||||
"args": ["test"],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/common-react"
|
||||
"cwd": "${workspaceFolder}/webapp/common-react"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -92,10 +92,10 @@
|
||||
"label": "Run Backend CE",
|
||||
"type": "shell",
|
||||
"windows": {
|
||||
"command": "./run-server.bat"
|
||||
"command": "./run-cloudbeaver-server.bat"
|
||||
},
|
||||
"osx": {
|
||||
"command": "./run-server.sh"
|
||||
"command": "./run-cloudbeaver-server.sh"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/deploy/cloudbeaver"
|
||||
@@ -133,9 +133,18 @@
|
||||
"type": "shell",
|
||||
"command": "yarn",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/common-react/@dbeaver/ui-kit"
|
||||
"cwd": "${workspaceFolder}/webapp/common-react/@dbeaver/ui-kit"
|
||||
},
|
||||
"args": ["docs"]
|
||||
},
|
||||
{
|
||||
"label": "Run Storybook",
|
||||
"type": "shell",
|
||||
"command": "yarn",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/webapp/packages/storybook"
|
||||
},
|
||||
"args": ["storybook"]
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
|
||||
Vendored
Vendored
Vendored
+2
-2
@@ -2,6 +2,6 @@
|
||||
<?pde version="3.8"?>
|
||||
<target name="org.jkiss.cloudbeaver.tp">
|
||||
<locations>
|
||||
<location path="../../dbeaver-workspace/dependencies/plugins" type="Directory"/>
|
||||
<location path="../../../dbeaver-workspace/dependencies/plugins" type="Directory"/>
|
||||
</locations>
|
||||
</target>
|
||||
</target>
|
||||
@@ -25,61 +25,52 @@ You can see a live demo of CloudBeaver here: https://demo.cloudbeaver.io
|
||||
|
||||
## Changelog
|
||||
|
||||
### 25.1.4 2025-08-04
|
||||
- Added an ability to review the script before execution when users edit tables without primary keys in the Data Editor;
|
||||
- DuckDB: driver has been updated to version 1.3.2;
|
||||
- Databend: driver has been updated to version 0.3.9.
|
||||
### 25.2.3 2025-10-20
|
||||
- Added visualization for column reordering in the Data Editor;
|
||||
- Fixed the critical issue with duplicating the execution of queries executed by a shortcut in the SQL Editor;
|
||||
- Fixed the critical vulnerability (CVE-2025-61927) in the Happy DOM library. The library was updated to version 20.0.2.
|
||||
|
||||
### 25.1.3 2025-07-21
|
||||
- A password confirmation field has been added for administrators in the Easy Config section to help prevent accidental misconfigurations;
|
||||
- Added column descriptions in the Data Editor to provide more metadata context. You can disable this in Preferences > Data Editor;
|
||||
- Added the option to display tabs across multiple rows, allowing you to see all tabs without scrolling. You can enable this in Preferences > Interface;
|
||||
- Changed the default engine used for autocompletion in the SQL Editor. This Semantic engine offers improved suggestions for database objects, keywords, and functions. You can switch back to the Legacy engine in Preferences -> SQL Editor;
|
||||
- Clickhouse: fixed the presentation of tuples and map data types in the data editor.
|
||||
### 25.2.2 2025-10-06
|
||||
- Replaced true/false text with intuitive checkboxes for boolean values in the metadata editor, making editing easier and faster;
|
||||
- Keyboard navigation was improved across the application. Additional shortcuts are available in the main info panel (top-right corner). To help you discover them faster, relevant keyboard shortcuts are also shown directly in the tooltips of Data Editor buttons;
|
||||
- Fixed an issue in the Data Editor where resizing one column in a wide table would cause previously resized columns to revert to their original width. Users can resize multiple columns as intended.
|
||||
|
||||
### 25.1.2 2025-07-07
|
||||
- The CloudBeaver default theme matches the device theme by default now. You can change this behavior in user preferences under the Theme section;
|
||||
- Added a button “Clear” to the Output panel;
|
||||
- Changed the data transfer mechanism to avoid intermediate file creation. The parameter dataExportFileSizeLimit was removed from the server configuration as deprecated;
|
||||
- PostgresSQL: fixed misplaced comment for table DDL generation.
|
||||
### 25.2.1 2025-09-22
|
||||
- Autocomplete values in SQL Editor are ordered alphabetically to simplify object search;
|
||||
- "Show all databases" setting was added on the connection page for MySQL and MariaDB;
|
||||
- Different bug fixes and enhancements have been made.
|
||||
|
||||
### 25.1.1 2025-06-23
|
||||
### 25.2.0 2025-09-01
|
||||
### Changes since 25.1.0:
|
||||
- Added a search panel for SQL Editor and Value panel: press Ctrl+F to open a panel that allows searching and replacing by keyword or regular expression.
|
||||
- Added a new welcome screen for a freshly opened application. This screen contains shortcuts to create a new connection, open SQL editor or documentation.
|
||||
- Databend database support has been added.
|
||||
|
||||
### 25.1.0 2025-06-02
|
||||
### Changes since 25.0.0:
|
||||
- Important:
|
||||
- Connection templates were removed from the application. You can use the Connection Editor to create new connections.
|
||||
- Administration:
|
||||
- Log records now capture create, update, and delete actions for users, teams, and connections for improved tracking and transparency.
|
||||
- LDAP authorization:
|
||||
- Added support for secure [LDAP authentication over SSL](https://github.com/dbeaver/cloudbeaver/wiki/LDAP-Authentication);
|
||||
- Brute force protection is now supported for LDAP authentication. All authentication methods on the application level have this security mechanism.
|
||||
- Added support for multiple server URLs to accommodate different access policies for internal and external users, improving flexibility in network-restricted environments. To set it up, use the Allowed Server URLs field in the Server configuration within the Administration section;
|
||||
- Added the Force HTTPS mode setting in Server Configuration to enforce redirecting from HTTP to HTTPS. This setting helps avoid a potential man-in-the-middle attack. This setting is turned off by default. Please remember to configure your proxy before enabling;
|
||||
- Added the CLOUDBEAVER_BIND_SESSION_TO_IP option to improve session security by linking user sessions to their IP address. It is disabled by default. When enabled, this helps protect against certain types of session hijacking attacks, where an attacker could try to take over a user’s session. Note: Users will be logged out automatically on the IP address change (switching networks or using mobile data);
|
||||
- A password confirmation field has been added for administrators in the Easy Config section to prevent accidental misconfigurations.
|
||||
- SQL Editor:
|
||||
- A new setting to autosave scripts in the SQL editor appeared in global and users' preferences;
|
||||
- Auto-completion has been improved for aliases and camelCase entities.
|
||||
- Changed the default engine used for autocompletion in the SQL Editor. This Semantic engine offers improved suggestions for database objects, keywords, and functions. You can switch back to the Legacy engine in Preferences > SQL Editor;
|
||||
- A “Clear” button was added to the output panel.
|
||||
- Data Editor:
|
||||
- Improved table rendering to scale column width depending on the content;
|
||||
- Accessibility: improved keyboard navigation by sorting buttons in column headers.
|
||||
- Column descriptions were added in the Data Editor under column names to provide more metadata context. You can disable this in Preferences > Data Viewer;
|
||||
- Added an ability to review the script before execution when users edit tables without primary keys.
|
||||
- General:
|
||||
- Added the ability to configure the default database or schema in the connection configuration;
|
||||
- Enhanced initial configuration stage security: a server needs to be restarted if the initial setup time exceeds 1 hour;
|
||||
- Improved Navigation tree performance: lost connection does not lead to the application freezing anymore;
|
||||
- Data transfer: Improved export/import functionality performance by optimizing disk memory consumption;
|
||||
- Vietnamese localization has been added (thanks to [0xhanh](https://github.com/0xhanh)).
|
||||
- Added a new welcome screen for a freshly opened application. This screen contains shortcuts to create a new connection, open SQL Editor, or documentation;
|
||||
- Added a search panel for SQL Editor and Value panel: press Ctrl/Cmd + F to open a panel that allows searching and replacing by keyword or regular expression;
|
||||
- Changed the data transfer mechanism to avoid intermediate file creation. The parameter dataExportFileSizeLimit was removed from the server configuration as deprecated;
|
||||
- The CloudBeaver default theme matches the device theme by default now. You can change this behavior in user preferences under the Theme section;
|
||||
- Added the option to display tabs across multiple rows, allowing you to see all tabs without scrolling. You can enable this in Preferences > Interface;
|
||||
- Improved dark theme accessibility by enhancing color contrast across the app for better readability and compliance with accessibility standards;
|
||||
- The database navigator now automatically hides empty folders in shared projects, reducing visual clutter and speeding up the process of finding active connections.
|
||||
- Databases and drivers:
|
||||
- Clickhouse: driver has been updated to version 0.8.5;
|
||||
- DuckDB: expanded the list of system objects that can be hidden in the Navigation tree;
|
||||
- LibSQL: added the ability to connect using token authentication;
|
||||
- Oracle: users' DDL in Oracle is displayed in the metadata editor on the corresponding tab for users with Oracle administration permissions;
|
||||
- SQLite: added information about a table's Strict mode to the table metadata section (thanks to [eusebe-cda](https://github.com/eusebe-cda)).
|
||||
- Clickhouse: fixed the presentation of tuples and map data types in the Data Editor;
|
||||
- Databend database support has been added (thanks to @hantmac);
|
||||
- DuckDB: driver has been updated to version 1.3;
|
||||
- MySQL: Improved performance when retrieving foreign keys metadata;
|
||||
- PostgreSQL: fixed misplaced comment for table DDL generation.
|
||||
|
||||
## Contribution
|
||||
As a community-driven open-source project, we warmly welcome contributions through GitHub pull requests.
|
||||
|
||||
[We are happy to reward](https://dbeaver.com/help-dbeaver/) our most active contributors every major sprint.
|
||||
The most significant contribution to our code for the major release 25.1.0 was made by:
|
||||
1. [0xhanh](https://github.com/0xhanh) - for Vietnamese localization.
|
||||
The most significant contribution to our code for the major release 25.2.0 was made by:
|
||||
1. [hantmac](https://github.com/hantmac) - added support for Databend in CloudBeaver Community Edition.
|
||||
|
||||
Vendored
-9
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"search.exclude": {
|
||||
"**/.yarn": true,
|
||||
"**/.pnp.*": true
|
||||
},
|
||||
"typescript.tsdk": ".yarn/sdks/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"prettier.prettierPath": ".yarn/sdks/prettier/index.cjs"
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, register} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
const {pathToFileURL} = require(`url`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
|
||||
const absRequire = createRequire(absPnpApiPath);
|
||||
|
||||
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
|
||||
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require prettier/bin/prettier.cjs
|
||||
require(absPnpApiPath).setup();
|
||||
if (isPnpLoaderEnabled && register) {
|
||||
register(pathToFileURL(absPnpLoaderPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
|
||||
? exports => absRequire(absUserWrapperPath)(exports)
|
||||
: exports => exports;
|
||||
|
||||
// Defer to the real prettier/bin/prettier.cjs your application uses
|
||||
module.exports = wrapWithUserWrapper(absRequire(`prettier/bin/prettier.cjs`));
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, register} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
const {pathToFileURL} = require(`url`);
|
||||
|
||||
const relPnpApiPath = "../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
|
||||
const absRequire = createRequire(absPnpApiPath);
|
||||
|
||||
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
|
||||
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require prettier
|
||||
require(absPnpApiPath).setup();
|
||||
if (isPnpLoaderEnabled && register) {
|
||||
register(pathToFileURL(absPnpLoaderPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
|
||||
? exports => absRequire(absUserWrapperPath)(exports)
|
||||
: exports => exports;
|
||||
|
||||
// Defer to the real prettier your application uses
|
||||
module.exports = wrapWithUserWrapper(absRequire(`prettier`));
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"name": "prettier",
|
||||
"version": "3.6.2-sdk",
|
||||
"main": "./index.cjs",
|
||||
"type": "commonjs",
|
||||
"bin": "./bin/prettier.cjs"
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
!lib
|
||||
@@ -1,537 +0,0 @@
|
||||
import * as react2 from "react";
|
||||
import * as react13 from "react";
|
||||
import * as react10 from "react";
|
||||
import { Key, ReactElement, ReactNode } from "react";
|
||||
import * as react_jsx_runtime1 from "react/jsx-runtime";
|
||||
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
||||
import * as react_jsx_runtime9 from "react/jsx-runtime";
|
||||
import * as react_jsx_runtime11 from "react/jsx-runtime";
|
||||
import * as react_jsx_runtime4 from "react/jsx-runtime";
|
||||
import * as react_jsx_runtime5 from "react/jsx-runtime";
|
||||
import * as react_jsx_runtime7 from "react/jsx-runtime";
|
||||
|
||||
//#region src/types.d.ts
|
||||
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
||||
type Maybe<T> = T | undefined | null;
|
||||
interface Column<TRow, TSummaryRow = unknown> {
|
||||
/** The name of the column. Displayed in the header cell by default */
|
||||
readonly name: string | ReactElement;
|
||||
/** A unique key to distinguish each column */
|
||||
readonly key: string;
|
||||
/**
|
||||
* Column width. If not specified, it will be determined automatically based on grid width and specified widths of other columns
|
||||
* @default 'auto'
|
||||
*/
|
||||
readonly width?: Maybe<number | string>;
|
||||
/**
|
||||
* Minimum column width in pixels
|
||||
* @default 50
|
||||
*/
|
||||
readonly minWidth?: Maybe<number>;
|
||||
/** Maximum column width in pixels */
|
||||
readonly maxWidth?: Maybe<number>;
|
||||
/** Class name(s) for the cell */
|
||||
readonly cellClass?: Maybe<string | ((row: TRow) => Maybe<string>)>;
|
||||
/** Class name(s) for the header cell */
|
||||
readonly headerCellClass?: Maybe<string>;
|
||||
/** Class name(s) for the summary cell */
|
||||
readonly summaryCellClass?: Maybe<string | ((row: TSummaryRow) => Maybe<string>)>;
|
||||
/** Render function to render the content of cells */
|
||||
readonly renderCell?: Maybe<(props: RenderCellProps<TRow, TSummaryRow>) => ReactNode>;
|
||||
/** Render function to render the content of the header cell */
|
||||
readonly renderHeaderCell?: Maybe<(props: RenderHeaderCellProps<TRow, TSummaryRow>) => ReactNode>;
|
||||
/** Render function to render the content of summary cells */
|
||||
readonly renderSummaryCell?: Maybe<(props: RenderSummaryCellProps<TSummaryRow, TRow>) => ReactNode>;
|
||||
/** Render function to render the content of group cells */
|
||||
readonly renderGroupCell?: Maybe<(props: RenderGroupCellProps<TRow, TSummaryRow>) => ReactNode>;
|
||||
/** Render function to render the content of edit cells. When set, the column is automatically set to be editable */
|
||||
readonly renderEditCell?: Maybe<(props: RenderEditCellProps<TRow, TSummaryRow>) => ReactNode>;
|
||||
/** Enables cell editing. If set and no editor property specified, then a textinput will be used as the cell editor */
|
||||
readonly editable?: Maybe<boolean | ((row: TRow) => boolean)>;
|
||||
readonly colSpan?: Maybe<(args: ColSpanArgs<TRow, TSummaryRow>) => Maybe<number>>;
|
||||
/** Determines whether column is frozen */
|
||||
readonly frozen?: Maybe<boolean>;
|
||||
/** Enable resizing of the column */
|
||||
readonly resizable?: Maybe<boolean>;
|
||||
/** Enable sorting of the column */
|
||||
readonly sortable?: Maybe<boolean>;
|
||||
/** Enable dragging of the column */
|
||||
readonly draggable?: Maybe<boolean>;
|
||||
/** Sets the column sort order to be descending instead of ascending the first time the column is sorted */
|
||||
readonly sortDescendingFirst?: Maybe<boolean>;
|
||||
/** Options for cell editing */
|
||||
readonly editorOptions?: Maybe<{
|
||||
/**
|
||||
* Render the cell content in addition to the edit cell.
|
||||
* Enable this option when the editor is rendered outside the grid, like a modal for example.
|
||||
* By default, the cell content is not rendered when the edit cell is open.
|
||||
* @default false
|
||||
*/
|
||||
readonly displayCellContent?: Maybe<boolean>;
|
||||
/**
|
||||
* Commit changes when clicking outside the cell
|
||||
* @default true
|
||||
*/
|
||||
readonly commitOnOutsideClick?: Maybe<boolean>;
|
||||
/**
|
||||
* Close the editor when the row changes externally
|
||||
* @default true
|
||||
*/
|
||||
readonly closeOnExternalRowChange?: Maybe<boolean>;
|
||||
}>;
|
||||
}
|
||||
interface CalculatedColumn<TRow, TSummaryRow = unknown> extends Column<TRow, TSummaryRow> {
|
||||
readonly parent: CalculatedColumnParent<TRow, TSummaryRow> | undefined;
|
||||
readonly idx: number;
|
||||
readonly level: number;
|
||||
readonly width: number | string;
|
||||
readonly minWidth: number;
|
||||
readonly maxWidth: number | undefined;
|
||||
readonly resizable: boolean;
|
||||
readonly sortable: boolean;
|
||||
readonly draggable: boolean;
|
||||
readonly frozen: boolean;
|
||||
readonly renderCell: (props: RenderCellProps<TRow, TSummaryRow>) => ReactNode;
|
||||
readonly renderHeaderCell: (props: RenderHeaderCellProps<TRow, TSummaryRow>) => ReactNode;
|
||||
}
|
||||
interface ColumnGroup<R, SR = unknown> {
|
||||
/** The name of the column group, it will be displayed in the header cell */
|
||||
readonly name: string | ReactElement;
|
||||
readonly headerCellClass?: Maybe<string>;
|
||||
readonly children: readonly ColumnOrColumnGroup<R, SR>[];
|
||||
}
|
||||
interface CalculatedColumnParent<R, SR> {
|
||||
readonly name: string | ReactElement;
|
||||
readonly parent: CalculatedColumnParent<R, SR> | undefined;
|
||||
readonly idx: number;
|
||||
readonly colSpan: number;
|
||||
readonly level: number;
|
||||
readonly headerCellClass?: Maybe<string>;
|
||||
}
|
||||
type ColumnOrColumnGroup<R, SR = unknown> = Column<R, SR> | ColumnGroup<R, SR>;
|
||||
type CalculatedColumnOrColumnGroup<R, SR> = CalculatedColumnParent<R, SR> | CalculatedColumn<R, SR>;
|
||||
interface Position {
|
||||
readonly idx: number;
|
||||
readonly rowIdx: number;
|
||||
}
|
||||
interface RenderCellProps<TRow, TSummaryRow = unknown> {
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
row: TRow;
|
||||
rowIdx: number;
|
||||
isCellEditable: boolean;
|
||||
tabIndex: number;
|
||||
onRowChange: (row: TRow) => void;
|
||||
}
|
||||
interface RenderSummaryCellProps<TSummaryRow, TRow = unknown> {
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
row: TSummaryRow;
|
||||
tabIndex: number;
|
||||
}
|
||||
interface RenderGroupCellProps<TRow, TSummaryRow = unknown> {
|
||||
groupKey: unknown;
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
row: GroupRow<TRow>;
|
||||
childRows: readonly TRow[];
|
||||
isExpanded: boolean;
|
||||
tabIndex: number;
|
||||
toggleGroup: () => void;
|
||||
}
|
||||
interface RenderEditCellProps<TRow, TSummaryRow = unknown> {
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
row: TRow;
|
||||
rowIdx: number;
|
||||
onRowChange: (row: TRow, commitChanges?: boolean) => void;
|
||||
onClose: (commitChanges?: boolean, shouldFocusCell?: boolean) => void;
|
||||
}
|
||||
interface RenderHeaderCellProps<TRow, TSummaryRow = unknown> {
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
sortDirection: SortDirection | undefined;
|
||||
priority: number | undefined;
|
||||
tabIndex: number;
|
||||
}
|
||||
interface BaseCellRendererProps<TRow, TSummaryRow = unknown> extends Omit<React.ComponentProps<'div'>, 'children'>, Pick<DataGridProps<TRow, TSummaryRow>, 'onCellMouseDown' | 'onCellClick' | 'onCellDoubleClick' | 'onCellContextMenu'> {
|
||||
rowIdx: number;
|
||||
selectCell: (position: Position, options?: SelectCellOptions) => void;
|
||||
}
|
||||
interface CellRendererProps<TRow, TSummaryRow> extends BaseCellRendererProps<TRow, TSummaryRow> {
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
row: TRow;
|
||||
colSpan: number | undefined;
|
||||
isDraggedOver: boolean;
|
||||
isCellSelected: boolean;
|
||||
onRowChange: (column: CalculatedColumn<TRow, TSummaryRow>, newRow: TRow) => void;
|
||||
}
|
||||
type CellEvent<E extends React.SyntheticEvent<HTMLDivElement>> = E & {
|
||||
preventGridDefault: () => void;
|
||||
isGridDefaultPrevented: () => boolean;
|
||||
};
|
||||
type CellMouseEvent = CellEvent<React.MouseEvent<HTMLDivElement>>;
|
||||
type CellKeyboardEvent = CellEvent<React.KeyboardEvent<HTMLDivElement>>;
|
||||
type CellClipboardEvent = React.ClipboardEvent<HTMLDivElement>;
|
||||
interface CellMouseArgs<TRow, TSummaryRow = unknown> {
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
row: TRow;
|
||||
rowIdx: number;
|
||||
selectCell: (enableEditor?: boolean) => void;
|
||||
}
|
||||
interface SelectCellKeyDownArgs<TRow, TSummaryRow = unknown> {
|
||||
mode: 'SELECT';
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
row: TRow;
|
||||
rowIdx: number;
|
||||
selectCell: (position: Position, options?: SelectCellOptions) => void;
|
||||
}
|
||||
interface EditCellKeyDownArgs<TRow, TSummaryRow = unknown> {
|
||||
mode: 'EDIT';
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
row: TRow;
|
||||
rowIdx: number;
|
||||
navigate: () => void;
|
||||
onClose: (commitChanges?: boolean, shouldFocusCell?: boolean) => void;
|
||||
}
|
||||
type CellKeyDownArgs<TRow, TSummaryRow = unknown> = SelectCellKeyDownArgs<TRow, TSummaryRow> | EditCellKeyDownArgs<TRow, TSummaryRow>;
|
||||
interface CellSelectArgs<TRow, TSummaryRow = unknown> {
|
||||
rowIdx: number;
|
||||
row: TRow | undefined;
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
}
|
||||
type CellMouseEventHandler<R, SR> = Maybe<(args: CellMouseArgs<NoInfer<R>, NoInfer<SR>>, event: CellMouseEvent) => void>;
|
||||
interface BaseRenderRowProps<TRow, TSummaryRow = unknown> extends BaseCellRendererProps<TRow, TSummaryRow> {
|
||||
viewportColumns: readonly CalculatedColumn<TRow, TSummaryRow>[];
|
||||
rowIdx: number;
|
||||
selectedCellIdx: number | undefined;
|
||||
isRowSelectionDisabled: boolean;
|
||||
isRowSelected: boolean;
|
||||
gridRowStart: number;
|
||||
}
|
||||
interface RenderRowProps<TRow, TSummaryRow = unknown> extends BaseRenderRowProps<TRow, TSummaryRow> {
|
||||
row: TRow;
|
||||
lastFrozenColumnIndex: number;
|
||||
draggedOverCellIdx: number | undefined;
|
||||
selectedCellEditor: ReactElement<RenderEditCellProps<TRow>> | undefined;
|
||||
onRowChange: (column: CalculatedColumn<TRow, TSummaryRow>, rowIdx: number, newRow: TRow) => void;
|
||||
rowClass: Maybe<(row: TRow, rowIdx: number) => Maybe<string>>;
|
||||
}
|
||||
interface RowsChangeData<R, SR = unknown> {
|
||||
indexes: number[];
|
||||
column: CalculatedColumn<R, SR>;
|
||||
}
|
||||
interface SelectRowEvent<TRow> {
|
||||
row: TRow;
|
||||
checked: boolean;
|
||||
isShiftClick: boolean;
|
||||
}
|
||||
interface SelectHeaderRowEvent {
|
||||
checked: boolean;
|
||||
}
|
||||
interface FillEvent<TRow> {
|
||||
columnKey: string;
|
||||
sourceRow: TRow;
|
||||
targetRow: TRow;
|
||||
}
|
||||
interface CellCopyPasteArgs<TRow, TSummaryRow = unknown> {
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
row: TRow;
|
||||
}
|
||||
type CellCopyArgs<TRow, TSummaryRow = unknown> = CellCopyPasteArgs<TRow, TSummaryRow>;
|
||||
type CellPasteArgs<TRow, TSummaryRow = unknown> = CellCopyPasteArgs<TRow, TSummaryRow>;
|
||||
interface GroupRow<TRow> {
|
||||
readonly childRows: readonly TRow[];
|
||||
readonly id: string;
|
||||
readonly parentId: unknown;
|
||||
readonly groupKey: unknown;
|
||||
readonly isExpanded: boolean;
|
||||
readonly level: number;
|
||||
readonly posInSet: number;
|
||||
readonly setSize: number;
|
||||
readonly startRowIndex: number;
|
||||
}
|
||||
interface SortColumn {
|
||||
readonly columnKey: string;
|
||||
readonly direction: SortDirection;
|
||||
}
|
||||
type SortDirection = 'ASC' | 'DESC';
|
||||
type ColSpanArgs<TRow, TSummaryRow> = {
|
||||
type: 'HEADER';
|
||||
} | {
|
||||
type: 'ROW';
|
||||
row: TRow;
|
||||
} | {
|
||||
type: 'SUMMARY';
|
||||
row: TSummaryRow;
|
||||
};
|
||||
type RowHeightArgs<TRow> = {
|
||||
type: 'ROW';
|
||||
row: TRow;
|
||||
} | {
|
||||
type: 'GROUP';
|
||||
row: GroupRow<TRow>;
|
||||
};
|
||||
interface RenderSortIconProps {
|
||||
sortDirection: SortDirection | undefined;
|
||||
}
|
||||
interface RenderSortPriorityProps {
|
||||
priority: number | undefined;
|
||||
}
|
||||
interface RenderSortStatusProps extends RenderSortIconProps, RenderSortPriorityProps {}
|
||||
interface RenderCheckboxProps extends Pick<React.ComponentProps<'input'>, 'aria-label' | 'aria-labelledby' | 'checked' | 'tabIndex' | 'disabled'> {
|
||||
indeterminate?: boolean | undefined;
|
||||
onChange: (checked: boolean, shift: boolean) => void;
|
||||
}
|
||||
interface Renderers<TRow, TSummaryRow> {
|
||||
renderCell?: Maybe<(key: Key, props: CellRendererProps<TRow, TSummaryRow>) => ReactNode>;
|
||||
renderCheckbox?: Maybe<(props: RenderCheckboxProps) => ReactNode>;
|
||||
renderRow?: Maybe<(key: Key, props: RenderRowProps<TRow, TSummaryRow>) => ReactNode>;
|
||||
renderSortStatus?: Maybe<(props: RenderSortStatusProps) => ReactNode>;
|
||||
noRowsFallback?: Maybe<ReactNode>;
|
||||
}
|
||||
interface SelectCellOptions {
|
||||
enableEditor?: Maybe<boolean>;
|
||||
shouldFocusCell?: Maybe<boolean>;
|
||||
}
|
||||
interface ColumnWidth {
|
||||
readonly type: 'resized' | 'measured';
|
||||
readonly width: number;
|
||||
}
|
||||
type ColumnWidths = ReadonlyMap<string, ColumnWidth>;
|
||||
type Direction = 'ltr' | 'rtl';
|
||||
//#endregion
|
||||
//#region src/ScrollToCell.d.ts
|
||||
interface PartialPosition {
|
||||
readonly idx?: number | undefined;
|
||||
readonly rowIdx?: number | undefined;
|
||||
}
|
||||
//#endregion
|
||||
//#region src/DataGrid.d.ts
|
||||
type DefaultColumnOptions<R, SR> = Pick<Column<R, SR>, 'renderCell' | 'renderHeaderCell' | 'width' | 'minWidth' | 'maxWidth' | 'resizable' | 'sortable' | 'draggable'>;
|
||||
interface DataGridHandle {
|
||||
element: HTMLDivElement | null;
|
||||
scrollToCell: (position: PartialPosition) => void;
|
||||
selectCell: (position: Position, options?: SelectCellOptions) => void;
|
||||
}
|
||||
type SharedDivProps = Pick<React.ComponentProps<'div'>, 'role' | 'aria-label' | 'aria-labelledby' | 'aria-description' | 'aria-describedby' | 'aria-rowcount' | 'className' | 'style'>;
|
||||
interface DataGridProps<R, SR = unknown, K extends Key = Key> extends SharedDivProps {
|
||||
ref?: Maybe<React.Ref<DataGridHandle>>;
|
||||
/**
|
||||
* Grid and data Props
|
||||
*/
|
||||
/** An array of column definitions */
|
||||
columns: readonly ColumnOrColumnGroup<NoInfer<R>, NoInfer<SR>>[];
|
||||
/** A function called for each rendered row that should return a plain key/value pair object */
|
||||
rows: readonly R[];
|
||||
/** Rows pinned at the top of the grid for summary purposes */
|
||||
topSummaryRows?: Maybe<readonly SR[]>;
|
||||
/** Rows pinned at the bottom of the grid for summary purposes */
|
||||
bottomSummaryRows?: Maybe<readonly SR[]>;
|
||||
/** Function to return a unique key/identifier for each row */
|
||||
rowKeyGetter?: Maybe<(row: NoInfer<R>) => K>;
|
||||
/** Callback triggered when rows are changed */
|
||||
onRowsChange?: Maybe<(rows: NoInfer<R>[], data: RowsChangeData<NoInfer<R>, NoInfer<SR>>) => void>;
|
||||
/**
|
||||
* Dimensions props
|
||||
*/
|
||||
/**
|
||||
* Height of each row in pixels
|
||||
* @default 35
|
||||
*/
|
||||
rowHeight?: Maybe<number | ((row: NoInfer<R>) => number)>;
|
||||
/**
|
||||
* Height of the header row in pixels
|
||||
* @default 35
|
||||
*/
|
||||
headerRowHeight?: Maybe<number>;
|
||||
/**
|
||||
* Height of each summary row in pixels
|
||||
* @default 35
|
||||
*/
|
||||
summaryRowHeight?: Maybe<number>;
|
||||
/** A map of column widths */
|
||||
columnWidths?: Maybe<ColumnWidths>;
|
||||
/** Callback triggered when column widths change */
|
||||
onColumnWidthsChange?: Maybe<(columnWidths: ColumnWidths) => void>;
|
||||
/**
|
||||
* Feature props
|
||||
*/
|
||||
/** A set of selected row keys */
|
||||
selectedRows?: Maybe<ReadonlySet<K>>;
|
||||
/** Function to determine if row selection is disabled for a specific row */
|
||||
isRowSelectionDisabled?: Maybe<(row: NoInfer<R>) => boolean>;
|
||||
/** Callback triggered when the selection changes */
|
||||
onSelectedRowsChange?: Maybe<(selectedRows: Set<NoInfer<K>>) => void>;
|
||||
/** An array of sorted columns */
|
||||
sortColumns?: Maybe<readonly SortColumn[]>;
|
||||
/** Callback triggered when sorting changes */
|
||||
onSortColumnsChange?: Maybe<(sortColumns: SortColumn[]) => void>;
|
||||
/** Default options applied to all columns */
|
||||
defaultColumnOptions?: Maybe<DefaultColumnOptions<NoInfer<R>, NoInfer<SR>>>;
|
||||
onFill?: Maybe<(event: FillEvent<NoInfer<R>>) => NoInfer<R>>;
|
||||
/**
|
||||
* Event props
|
||||
*/
|
||||
/** Callback triggered when a pointer becomes active in a cell */
|
||||
onCellMouseDown?: CellMouseEventHandler<R, SR>;
|
||||
/** Callback triggered when a cell is clicked */
|
||||
onCellClick?: CellMouseEventHandler<R, SR>;
|
||||
/** Callback triggered when a cell is double-clicked */
|
||||
onCellDoubleClick?: CellMouseEventHandler<R, SR>;
|
||||
/** Callback triggered when a cell is right-clicked */
|
||||
onCellContextMenu?: CellMouseEventHandler<R, SR>;
|
||||
/** Callback triggered when a key is pressed in a cell */
|
||||
onCellKeyDown?: Maybe<(args: CellKeyDownArgs<NoInfer<R>, NoInfer<SR>>, event: CellKeyboardEvent) => void>;
|
||||
/** Callback triggered when a cell's content is copied */
|
||||
onCellCopy?: Maybe<(args: CellCopyArgs<NoInfer<R>, NoInfer<SR>>, event: CellClipboardEvent) => void>;
|
||||
/** Callback triggered when content is pasted into a cell */
|
||||
onCellPaste?: Maybe<(args: CellPasteArgs<NoInfer<R>, NoInfer<SR>>, event: CellClipboardEvent) => NoInfer<R>>;
|
||||
/** Function called whenever cell selection is changed */
|
||||
onSelectedCellChange?: Maybe<(args: CellSelectArgs<NoInfer<R>, NoInfer<SR>>) => void>;
|
||||
/** Callback triggered when the grid is scrolled */
|
||||
onScroll?: Maybe<(event: React.UIEvent<HTMLDivElement>) => void>;
|
||||
/** Callback triggered when column is resized */
|
||||
onColumnResize?: Maybe<(column: CalculatedColumn<R, SR>, width: number) => void>;
|
||||
/** Callback triggered when columns are reordered */
|
||||
onColumnsReorder?: Maybe<(sourceColumnKey: string, targetColumnKey: string) => void>;
|
||||
/**
|
||||
* Toggles and modes
|
||||
*/
|
||||
/** @default true */
|
||||
enableVirtualization?: Maybe<boolean>;
|
||||
/**
|
||||
* Miscellaneous
|
||||
*/
|
||||
/** Custom renderers for cells, rows, and other components */
|
||||
renderers?: Maybe<Renderers<NoInfer<R>, NoInfer<SR>>>;
|
||||
/** Function to apply custom class names to rows */
|
||||
rowClass?: Maybe<(row: NoInfer<R>, rowIdx: number) => Maybe<string>>;
|
||||
/** Custom class name for the header row */
|
||||
headerRowClass?: Maybe<string>;
|
||||
/**
|
||||
* Text direction of the grid ('ltr' or 'rtl')
|
||||
* @default 'ltr'
|
||||
* */
|
||||
direction?: Maybe<Direction>;
|
||||
'data-testid'?: Maybe<string>;
|
||||
'data-cy'?: Maybe<string>;
|
||||
}
|
||||
/**
|
||||
* Main API Component to render a data grid of rows and columns
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* <DataGrid columns={columns} rows={rows} />
|
||||
*/
|
||||
declare function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridProps<R, SR, K>): react_jsx_runtime1.JSX.Element;
|
||||
//#endregion
|
||||
//#region src/TreeDataGrid.d.ts
|
||||
interface TreeDataGridProps<R, SR = unknown, K extends Key = Key> extends Omit<DataGridProps<R, SR, K>, 'columns' | 'role' | 'aria-rowcount' | 'rowHeight' | 'onFill' | 'isRowSelectionDisabled'> {
|
||||
columns: readonly Column<NoInfer<R>, NoInfer<SR>>[];
|
||||
rowHeight?: Maybe<number | ((args: RowHeightArgs<NoInfer<R>>) => number)>;
|
||||
groupBy: readonly string[];
|
||||
rowGrouper: (rows: readonly NoInfer<R>[], columnKey: string) => Record<string, readonly NoInfer<R>[]>;
|
||||
expandedGroupIds: ReadonlySet<unknown>;
|
||||
onExpandedGroupIdsChange: (expandedGroupIds: Set<unknown>) => void;
|
||||
groupIdGetter?: Maybe<(groupKey: string, parentId?: string) => string>;
|
||||
}
|
||||
declare function TreeDataGrid<R, SR = unknown, K extends Key = Key>({
|
||||
columns: rawColumns,
|
||||
rows: rawRows,
|
||||
rowHeight: rawRowHeight,
|
||||
rowKeyGetter: rawRowKeyGetter,
|
||||
onCellKeyDown: rawOnCellKeyDown,
|
||||
onCellCopy: rawOnCellCopy,
|
||||
onCellPaste: rawOnCellPaste,
|
||||
onRowsChange,
|
||||
selectedRows: rawSelectedRows,
|
||||
onSelectedRowsChange: rawOnSelectedRowsChange,
|
||||
renderers,
|
||||
groupBy: rawGroupBy,
|
||||
rowGrouper,
|
||||
expandedGroupIds,
|
||||
onExpandedGroupIdsChange,
|
||||
groupIdGetter: rawGroupIdGetter,
|
||||
...props
|
||||
}: TreeDataGridProps<R, SR, K>): react_jsx_runtime0.JSX.Element;
|
||||
//#endregion
|
||||
//#region src/DataGridDefaultRenderersContext.d.ts
|
||||
declare const DataGridDefaultRenderersContext: react2.Context<Maybe<Renderers<any, any>>>;
|
||||
//#endregion
|
||||
//#region src/Row.d.ts
|
||||
declare const RowComponent: <R, SR>(props: RenderRowProps<R, SR>) => React.JSX.Element;
|
||||
//#endregion
|
||||
//#region src/Cell.d.ts
|
||||
declare const CellComponent: <R, SR>(props: CellRendererProps<R, SR>) => React.JSX.Element;
|
||||
//#endregion
|
||||
//#region src/Columns.d.ts
|
||||
declare const SELECT_COLUMN_KEY = "rdg-select-column";
|
||||
declare const SelectColumn: Column<any, any>;
|
||||
//#endregion
|
||||
//#region src/cellRenderers/renderCheckbox.d.ts
|
||||
declare function renderCheckbox({
|
||||
onChange,
|
||||
indeterminate,
|
||||
...props
|
||||
}: RenderCheckboxProps): react_jsx_runtime9.JSX.Element;
|
||||
//#endregion
|
||||
//#region src/cellRenderers/renderToggleGroup.d.ts
|
||||
declare function renderToggleGroup<R, SR>(props: RenderGroupCellProps<R, SR>): react_jsx_runtime11.JSX.Element;
|
||||
declare function ToggleGroup<R, SR>({
|
||||
groupKey,
|
||||
isExpanded,
|
||||
tabIndex,
|
||||
toggleGroup
|
||||
}: RenderGroupCellProps<R, SR>): react_jsx_runtime11.JSX.Element;
|
||||
//#endregion
|
||||
//#region src/cellRenderers/renderValue.d.ts
|
||||
declare function renderValue<R, SR>(props: RenderCellProps<R, SR>): react13.ReactNode;
|
||||
//#endregion
|
||||
//#region src/cellRenderers/SelectCellFormatter.d.ts
|
||||
type SharedInputProps = Pick<RenderCheckboxProps, 'disabled' | 'tabIndex' | 'aria-label' | 'aria-labelledby' | 'indeterminate' | 'onChange'>;
|
||||
interface SelectCellFormatterProps extends SharedInputProps {
|
||||
value: boolean;
|
||||
}
|
||||
declare function SelectCellFormatter({
|
||||
value,
|
||||
tabIndex,
|
||||
indeterminate,
|
||||
disabled,
|
||||
onChange,
|
||||
'aria-label': ariaLabel,
|
||||
'aria-labelledby': ariaLabelledBy
|
||||
}: SelectCellFormatterProps): react10.ReactNode;
|
||||
//#endregion
|
||||
//#region src/editors/textEditor.d.ts
|
||||
declare function textEditor<TRow, TSummaryRow>({
|
||||
row,
|
||||
column,
|
||||
onRowChange,
|
||||
onClose
|
||||
}: RenderEditCellProps<TRow, TSummaryRow>): react_jsx_runtime4.JSX.Element;
|
||||
//#endregion
|
||||
//#region src/renderHeaderCell.d.ts
|
||||
declare function renderHeaderCell<R, SR>({
|
||||
column,
|
||||
sortDirection,
|
||||
priority
|
||||
}: RenderHeaderCellProps<R, SR>): string | react_jsx_runtime5.JSX.Element;
|
||||
//#endregion
|
||||
//#region src/sortStatus.d.ts
|
||||
declare function renderSortIcon({
|
||||
sortDirection
|
||||
}: RenderSortIconProps): react_jsx_runtime7.JSX.Element | null;
|
||||
declare function renderSortPriority({
|
||||
priority
|
||||
}: RenderSortPriorityProps): number | undefined;
|
||||
//#endregion
|
||||
//#region src/hooks/useRowSelection.d.ts
|
||||
declare function useRowSelection(): {
|
||||
isRowSelectionDisabled: boolean;
|
||||
isRowSelected: boolean;
|
||||
onRowSelectionChange: (selectRowEvent: SelectRowEvent<any>) => void;
|
||||
};
|
||||
declare function useHeaderRowSelection(): {
|
||||
isIndeterminate: boolean;
|
||||
isRowSelected: boolean;
|
||||
onRowSelectionChange: (selectRowEvent: SelectHeaderRowEvent) => void;
|
||||
};
|
||||
//#endregion
|
||||
export { CalculatedColumn, CalculatedColumnOrColumnGroup, CalculatedColumnParent, CellComponent as Cell, CellCopyArgs, CellKeyDownArgs, CellKeyboardEvent, CellMouseArgs, CellMouseEvent, CellPasteArgs, CellRendererProps, CellSelectArgs, ColSpanArgs, Column, ColumnGroup, ColumnOrColumnGroup, ColumnWidth, ColumnWidths, DataGrid, DataGridDefaultRenderersContext, DataGridHandle, DataGridProps, DefaultColumnOptions, FillEvent, RenderCellProps, RenderCheckboxProps, RenderEditCellProps, RenderGroupCellProps, RenderHeaderCellProps, RenderRowProps, RenderSortIconProps, RenderSortPriorityProps, RenderSortStatusProps, RenderSummaryCellProps, Renderers, RowComponent as Row, RowHeightArgs, RowsChangeData, SELECT_COLUMN_KEY, SelectCellFormatter, SelectCellOptions, SelectColumn, SelectHeaderRowEvent, SelectRowEvent, SortColumn, SortDirection, ToggleGroup, TreeDataGrid, TreeDataGridProps, renderCheckbox, renderHeaderCell, renderSortIcon, renderSortPriority, renderToggleGroup, renderValue, textEditor, useHeaderRowSelection, useRowSelection };
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,453 +0,0 @@
|
||||
@layer rdg {
|
||||
@layer Defaults,
|
||||
FocusSink,
|
||||
CheckboxInput,
|
||||
CheckboxIcon,
|
||||
CheckboxLabel,
|
||||
Cell,
|
||||
HeaderCell,
|
||||
SummaryCell,
|
||||
EditCell,
|
||||
Row,
|
||||
HeaderRow,
|
||||
SummaryRow,
|
||||
GroupedRow,
|
||||
Root;
|
||||
}
|
||||
|
||||
.mlln6zg7-0-0-beta-55 {
|
||||
@layer rdg.MeasuringCell {
|
||||
contain: strict;
|
||||
grid-row: 1;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.cj343x07-0-0-beta-55 {
|
||||
@layer rdg.Cell {
|
||||
/* max-content does not work with size containment
|
||||
* dynamically switching between different containment styles incurs a heavy relayout penalty
|
||||
* Chromium bug: at odd zoom levels or subpixel positioning,
|
||||
* layout/paint/style containment can make cell borders disappear
|
||||
* https://issues.chromium.org/issues/40840864
|
||||
*/
|
||||
position: relative; /* needed for absolute positioning to work */
|
||||
padding-block: 0;
|
||||
padding-inline: 8px;
|
||||
border-inline-end: 1px solid var(--rdg-border-color);
|
||||
border-block-end: 1px solid var(--rdg-border-color);
|
||||
grid-row-start: var(--rdg-grid-row-start);
|
||||
align-content: center;
|
||||
background-color: inherit;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: clip;
|
||||
text-overflow: ellipsis;
|
||||
outline: none;
|
||||
|
||||
&[aria-selected='true'] {
|
||||
outline: 2px solid var(--rdg-selection-color);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.csofj7r7-0-0-beta-55 {
|
||||
@layer rdg.Cell {
|
||||
position: sticky;
|
||||
/* Should have a higher value than 0 to show up above unfrozen cells */
|
||||
z-index: 1;
|
||||
|
||||
/* Add box-shadow on the last frozen cell */
|
||||
&:nth-last-child(1 of &) {
|
||||
box-shadow: var(--rdg-cell-frozen-box-shadow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ch2wcw87-0-0-beta-55 {
|
||||
@layer rdg.DragHandle {
|
||||
--rdg-drag-handle-size: 8px;
|
||||
z-index: 0;
|
||||
cursor: move;
|
||||
inline-size: var(--rdg-drag-handle-size);
|
||||
block-size: var(--rdg-drag-handle-size);
|
||||
background-color: var(--rdg-selection-color);
|
||||
place-self: end;
|
||||
|
||||
&:hover {
|
||||
--rdg-drag-handle-size: 16px;
|
||||
border: 2px solid var(--rdg-selection-color);
|
||||
background-color: var(--rdg-background-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c1wvphzh7-0-0-beta-55 {
|
||||
@layer rdg.DragHandle {
|
||||
z-index: 1;
|
||||
position: sticky;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.c1bn88vv7-0-0-beta-55 {
|
||||
@layer rdg.CheckboxInput {
|
||||
display: block;
|
||||
margin: auto;
|
||||
inline-size: 20px;
|
||||
block-size: 20px;
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--rdg-checkbox-focus-color);
|
||||
outline-offset: -3px;
|
||||
}
|
||||
|
||||
&:enabled {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.g1s9ylgp7-0-0-beta-55 {
|
||||
@layer rdg.GroupCellContent {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.cz54e4y7-0-0-beta-55 {
|
||||
@layer rdg.GroupCellCaret {
|
||||
margin-inline-start: 4px;
|
||||
stroke: currentColor;
|
||||
stroke-width: 1.5px;
|
||||
fill: transparent;
|
||||
vertical-align: middle;
|
||||
|
||||
> path {
|
||||
transition: d 0.1s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.h44jtk67-0-0-beta-55 {
|
||||
@layer rdg.SortableHeaderCell {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.hcgkhxz7-0-0-beta-55 {
|
||||
@layer rdg.SortableHeaderCellName {
|
||||
flex-grow: 1;
|
||||
overflow: clip;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.c6ra8a37-0-0-beta-55 {
|
||||
@layer rdg.Cell {
|
||||
background-color: #ccccff;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.cis5rrm7-0-0-beta-55 {
|
||||
@layer rdg.EditCell {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.c6l2wv17-0-0-beta-55 {
|
||||
@layer rdg.HeaderCell {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.c1kqdw7y7-0-0-beta-55 {
|
||||
@layer rdg.HeaderCell {
|
||||
touch-action: none;
|
||||
}
|
||||
}
|
||||
|
||||
.r1y6ywlx7-0-0-beta-55 {
|
||||
@layer rdg.HeaderCell {
|
||||
cursor: col-resize;
|
||||
position: absolute;
|
||||
inset-block-start: 0;
|
||||
inset-inline-end: 0;
|
||||
inset-block-end: 0;
|
||||
inline-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.c1bezg5o7-0-0-beta-55 {
|
||||
@layer rdg.HeaderCell {
|
||||
background-color: var(--rdg-header-draggable-background-color);
|
||||
}
|
||||
}
|
||||
|
||||
.c1vc96037-0-0-beta-55 {
|
||||
@layer rdg.HeaderCell {
|
||||
background-color: var(--rdg-header-draggable-background-color);
|
||||
}
|
||||
}
|
||||
|
||||
.d8rwc9w7-0-0-beta-55 {
|
||||
@layer rdg.HeaderCell {
|
||||
border-radius: 4px;
|
||||
width: fit-content;
|
||||
outline: 2px solid hsl(207, 100%, 50%);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.r1upfr807-0-0-beta-55 {
|
||||
@layer rdg.Row {
|
||||
display: contents;
|
||||
background-color: var(--rdg-background-color);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--rdg-row-hover-background-color);
|
||||
}
|
||||
|
||||
&[aria-selected='true'] {
|
||||
background-color: var(--rdg-row-selected-background-color);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--rdg-row-selected-hover-background-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.r190mhd37-0-0-beta-55 {
|
||||
@layer rdg.FocusSink {
|
||||
outline: 2px solid var(--rdg-selection-color);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
.r139qu9m7-0-0-beta-55 {
|
||||
@layer rdg.FocusSink {
|
||||
&::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
block-size: 100%;
|
||||
position: sticky;
|
||||
inset-inline-start: 0;
|
||||
border-inline-start: 2px solid var(--rdg-selection-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.h10tskcx7-0-0-beta-55 {
|
||||
@layer rdg.HeaderRow {
|
||||
display: contents;
|
||||
background-color: var(--rdg-header-background-color);
|
||||
font-weight: bold;
|
||||
|
||||
& > .cj343x07-0-0-beta-55 {
|
||||
/* Should have a higher value than 1 to show up above regular cells and the focus sink */
|
||||
z-index: 2;
|
||||
position: sticky;
|
||||
}
|
||||
|
||||
& > .csofj7r7-0-0-beta-55 {
|
||||
z-index: 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.a3ejtar7-0-0-beta-55 {
|
||||
@layer rdg.SortIcon {
|
||||
fill: currentColor;
|
||||
|
||||
> path {
|
||||
transition: d 0.1s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.rnvodz57-0-0-beta-55 {
|
||||
@layer rdg.Defaults {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
@layer rdg.Root {
|
||||
--rdg-color: #000; --rdg-border-color: #ddd; --rdg-summary-border-color: #aaa; --rdg-background-color: hsl(0deg 0% 100%); --rdg-header-background-color: hsl(0deg 0% 97.5%); --rdg-header-draggable-background-color: hsl(0deg 0% 90.5%); --rdg-row-hover-background-color: hsl(0deg 0% 96%); --rdg-row-selected-background-color: hsl(207deg 76% 92%); --rdg-row-selected-hover-background-color: hsl(207deg 76% 88%); --rdg-checkbox-focus-color: hsl(207deg 100% 69%);
|
||||
--rdg-selection-color: hsl(207, 75%, 66%);
|
||||
--rdg-font-size: 14px;
|
||||
--rdg-cell-frozen-box-shadow: 2px 0 5px -2px rgba(136, 136, 136, 0.3);
|
||||
|
||||
&:dir(rtl) {
|
||||
--rdg-cell-frozen-box-shadow: -2px 0 5px -2px rgba(136, 136, 136, 0.3);
|
||||
}
|
||||
|
||||
display: grid;
|
||||
|
||||
color-scheme: var(--rdg-color-scheme, light dark);
|
||||
accent-color: light-dark(hsl(207deg 100% 29%), hsl(207deg 100% 79%));
|
||||
|
||||
/* https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context */
|
||||
/* We set a stacking context so internal elements don't render on top of external elements. */
|
||||
/* size containment is not used as it could break "width: min-content" for example, and the grid would infinitely resize on Chromium browsers */
|
||||
contain: content;
|
||||
content-visibility: auto;
|
||||
block-size: 350px;
|
||||
border: 1px solid var(--rdg-border-color);
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
background-color: var(--rdg-background-color);
|
||||
color: var(--rdg-color);
|
||||
font-size: var(--rdg-font-size);
|
||||
|
||||
/* needed on Firefox to fix scrollbars */
|
||||
&::before {
|
||||
content: '';
|
||||
grid-column: 1/-1;
|
||||
grid-row: 1/-1;
|
||||
}
|
||||
|
||||
&.rdg-dark {
|
||||
--rdg-color-scheme: dark;
|
||||
--rdg-color: #ddd; --rdg-border-color: #444; --rdg-summary-border-color: #555; --rdg-background-color: hsl(0deg 0% 13%); --rdg-header-background-color: hsl(0deg 0% 10.5%); --rdg-header-draggable-background-color: hsl(0deg 0% 17.5%); --rdg-row-hover-background-color: hsl(0deg 0% 9%); --rdg-row-selected-background-color: hsl(207deg 76% 42%); --rdg-row-selected-hover-background-color: hsl(207deg 76% 38%); --rdg-checkbox-focus-color: hsl(207deg 100% 89%);
|
||||
}
|
||||
|
||||
&.rdg-light {
|
||||
--rdg-color-scheme: light;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
&:not(.rdg-light) {
|
||||
--rdg-color: #ddd; --rdg-border-color: #444; --rdg-summary-border-color: #555; --rdg-background-color: hsl(0deg 0% 13%); --rdg-header-background-color: hsl(0deg 0% 10.5%); --rdg-header-draggable-background-color: hsl(0deg 0% 17.5%); --rdg-row-hover-background-color: hsl(0deg 0% 9%); --rdg-row-selected-background-color: hsl(207deg 76% 42%); --rdg-row-selected-hover-background-color: hsl(207deg 76% 38%); --rdg-checkbox-focus-color: hsl(207deg 100% 89%);
|
||||
}
|
||||
}
|
||||
|
||||
> :nth-last-child(1 of .rdg-top-summary-row) {
|
||||
> .cj343x07-0-0-beta-55 {
|
||||
border-block-end: 2px solid var(--rdg-summary-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
> :nth-child(1 of .rdg-bottom-summary-row) {
|
||||
> .cj343x07-0-0-beta-55 {
|
||||
border-block-start: 2px solid var(--rdg-summary-border-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vlqv91k7-0-0-beta-55 {
|
||||
@layer rdg.Root {
|
||||
user-select: none;
|
||||
|
||||
& .r1upfr807-0-0-beta-55 {
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.f1lsfrzw7-0-0-beta-55 {
|
||||
@layer rdg.FocusSink {
|
||||
grid-column: 1/-1;
|
||||
pointer-events: none;
|
||||
/* Should have a higher value than 1 to show up above regular frozen cells */
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.f1cte0lg7-0-0-beta-55 {
|
||||
@layer rdg.FocusSink {
|
||||
/* Should have a higher value than 3 to show up above header and summary rows */
|
||||
z-index: 3;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.s8wc6fl7-0-0-beta-55 {
|
||||
@layer rdg.SummaryCell {
|
||||
inset-block-start: var(--rdg-summary-row-top);
|
||||
inset-block-end: var(--rdg-summary-row-bottom);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.skuhp557-0-0-beta-55 {
|
||||
@layer rdg.SummaryRow {
|
||||
> .cj343x07-0-0-beta-55 {
|
||||
position: sticky;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tf8l5ub7-0-0-beta-55 {
|
||||
@layer rdg.SummaryRow {
|
||||
> .cj343x07-0-0-beta-55 {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
> .csofj7r7-0-0-beta-55 {
|
||||
z-index: 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.g1yxluv37-0-0-beta-55 {
|
||||
@layer rdg.GroupedRow {
|
||||
&:not([aria-selected='true']) {
|
||||
background-color: var(--rdg-header-background-color);
|
||||
}
|
||||
|
||||
> .cj343x07-0-0-beta-55:not(:last-child, .csofj7r7-0-0-beta-55),
|
||||
> :nth-last-child(n + 2 of .csofj7r7-0-0-beta-55) {
|
||||
border-inline-end: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.t7vyx3i7-0-0-beta-55 {
|
||||
@layer rdg.TextEditor {
|
||||
appearance: none;
|
||||
|
||||
box-sizing: border-box;
|
||||
inline-size: 100%;
|
||||
block-size: 100%;
|
||||
padding-block: 0;
|
||||
padding-inline: 6px;
|
||||
border: 2px solid #ccc;
|
||||
vertical-align: top;
|
||||
color: var(--rdg-color);
|
||||
background-color: var(--rdg-background-color);
|
||||
|
||||
font-family: inherit;
|
||||
font-size: var(--rdg-font-size);
|
||||
|
||||
&:focus {
|
||||
border-color: var(--rdg-selection-color);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: #999;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*# sourceMappingURL=styles.css.map*/
|
||||
File diff suppressed because one or more lines are too long
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"name": "react-data-grid",
|
||||
"version": "7.0.0-beta.55",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./lib/styles.css": "./lib/styles.css",
|
||||
".": {
|
||||
"types": "./lib/index.d.ts",
|
||||
"default": "./lib/index.js"
|
||||
}
|
||||
},
|
||||
"browser": "./lib/index.js",
|
||||
"main": "./lib/index.js",
|
||||
"module": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"sideEffects": [
|
||||
"**/*.css"
|
||||
],
|
||||
"dependencies": {
|
||||
"clsx": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0",
|
||||
"react-dom": "^19.0"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
-9
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"search.exclude": {
|
||||
"**/.yarn": true,
|
||||
"**/.pnp.*": true
|
||||
},
|
||||
"prettier.prettierPath": ".yarn/sdks/prettier/index.cjs",
|
||||
"typescript.tsdk": ".yarn/sdks/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
# This file is automatically generated by @yarnpkg/sdks.
|
||||
# Manual changes might be lost!
|
||||
|
||||
integrations:
|
||||
- vscode
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, register} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
const {pathToFileURL} = require(`url`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
|
||||
const absRequire = createRequire(absPnpApiPath);
|
||||
|
||||
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
|
||||
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require prettier/bin/prettier.cjs
|
||||
require(absPnpApiPath).setup();
|
||||
if (isPnpLoaderEnabled && register) {
|
||||
register(pathToFileURL(absPnpLoaderPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
|
||||
? exports => absRequire(absUserWrapperPath)(exports)
|
||||
: exports => exports;
|
||||
|
||||
// Defer to the real prettier/bin/prettier.cjs your application uses
|
||||
module.exports = wrapWithUserWrapper(absRequire(`prettier/bin/prettier.cjs`));
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, register} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
const {pathToFileURL} = require(`url`);
|
||||
|
||||
const relPnpApiPath = "../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
|
||||
const absRequire = createRequire(absPnpApiPath);
|
||||
|
||||
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
|
||||
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require prettier
|
||||
require(absPnpApiPath).setup();
|
||||
if (isPnpLoaderEnabled && register) {
|
||||
register(pathToFileURL(absPnpLoaderPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
|
||||
? exports => absRequire(absUserWrapperPath)(exports)
|
||||
: exports => exports;
|
||||
|
||||
// Defer to the real prettier your application uses
|
||||
module.exports = wrapWithUserWrapper(absRequire(`prettier`));
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"name": "prettier",
|
||||
"version": "3.6.2-sdk",
|
||||
"main": "./index.cjs",
|
||||
"type": "commonjs",
|
||||
"bin": "./bin/prettier.cjs"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from './isDefined.js';
|
||||
export * from './isNotNullDefined.js';
|
||||
export * from './mutex.js';
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,12 +5,8 @@ echo "Build static content"
|
||||
|
||||
mkdir -p ./cloudbeaver/web
|
||||
|
||||
cd ../../cloudbeaver/common-typescript
|
||||
cd ../../cloudbeaver/webapp
|
||||
|
||||
yarn install --immutable
|
||||
cd ../common-react
|
||||
yarn install --immutable
|
||||
cd ../webapp
|
||||
yarn install --immutable
|
||||
cd ./packages/product-default
|
||||
yarn run bundle
|
||||
@@ -19,11 +15,7 @@ if [[ "$?" -ne 0 ]] ; then
|
||||
echo 'Application build failed'; exit $rc
|
||||
fi
|
||||
|
||||
cd ../../../common-typescript
|
||||
yarn test
|
||||
cd ../common-react
|
||||
yarn test
|
||||
cd ../webapp
|
||||
cd ../../
|
||||
yarn test
|
||||
|
||||
if [[ "$?" -ne 0 ]] ; then
|
||||
@@ -36,4 +28,4 @@ echo "Copy static content"
|
||||
|
||||
cp -rp ../webapp/packages/product-default/lib/* cloudbeaver/web
|
||||
|
||||
echo "Cloudbeaver is ready. Run run-server.sh in cloudbeaver folder to start the server."
|
||||
echo "Cloudbeaver is ready. Run run-cloudbeaver-server.sh in cloudbeaver folder to start the server."
|
||||
|
||||
+1
-1
@@ -72,6 +72,6 @@ echo "Copy static content"
|
||||
|
||||
xcopy /E /Q ..\webapp\packages\product-default\lib cloudbeaver\web >NUL
|
||||
|
||||
echo "Cloudbeaver is ready. Run run-server.bat in cloudbeaver folder to start the server."
|
||||
echo "Cloudbeaver is ready. Run run-cloudbeaver-server.bat in cloudbeaver folder to start the server."
|
||||
|
||||
pause
|
||||
|
||||
@@ -16,6 +16,6 @@ EXPOSE 8978
|
||||
RUN find /opt/cloudbeaver -type d -exec chmod 775 {} \;
|
||||
WORKDIR /opt/cloudbeaver/
|
||||
|
||||
RUN chmod +x "run-server.sh" "/opt/cloudbeaver/launch-product.sh"
|
||||
RUN chmod +x "run-cloudbeaver-server.sh" "/opt/cloudbeaver/launch-product.sh"
|
||||
|
||||
ENTRYPOINT ["./launch-product.sh"]
|
||||
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
set -Eeo pipefail
|
||||
if [[ $# -eq 0 ]] ;
|
||||
then
|
||||
set -- "$@" -help
|
||||
fi
|
||||
set -- "$@" -cli-mode
|
||||
|
||||
source run-cloudbeaver-server.sh "$@"
|
||||
@@ -12,8 +12,8 @@ if [ "$(id -u)" -eq 0 ]; then
|
||||
TARGET_GID=${TARGET_GID:-$DBEAVER_GID}
|
||||
|
||||
chown -R $DBEAVER_UID:$DBEAVER_GID $PWD/workspace
|
||||
# Execute run-server.sh as the dbeaver user with the JAVA_HOME and PATH environment variables
|
||||
exec su "$TARGET_USER" -c "JAVA_HOME=$JAVA_HOME PATH=$PATH ./run-server.sh"
|
||||
# Execute run-cloudbeaver-server.sh as the dbeaver user with the JAVA_HOME and PATH environment variables
|
||||
exec su "$TARGET_USER" -c "JAVA_HOME=$JAVA_HOME PATH=$PATH ./run-cloudbeaver-server.sh"
|
||||
else
|
||||
exec ./run-server.sh
|
||||
exec ./run-cloudbeaver-server.sh
|
||||
fi
|
||||
@@ -17,7 +17,7 @@ If Not Defined JAVA_OPTS (
|
||||
|
||||
java %JAVA_OPTS% ^
|
||||
-Dfile.encoding=UTF-8 ^
|
||||
--add-modules=ALL-SYSTEM ^
|
||||
--add-modules=ALL-DEFAULT ^
|
||||
--add-opens=java.base/java.io=ALL-UNNAMED ^
|
||||
--add-opens=java.base/java.lang=ALL-UNNAMED ^
|
||||
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED ^
|
||||
@@ -11,7 +11,7 @@ echo "Starting Cloudbeaver Server"
|
||||
|
||||
exec java ${JAVA_OPTS} \
|
||||
-Dfile.encoding=UTF-8 \
|
||||
--add-modules=ALL-SYSTEM \
|
||||
--add-modules=ALL-DEFAULT \
|
||||
--add-opens=java.base/java.io=ALL-UNNAMED \
|
||||
--add-opens=java.base/java.lang=ALL-UNNAMED \
|
||||
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED \
|
||||
@@ -36,6 +36,6 @@ exec java ${JAVA_OPTS} \
|
||||
-jar ${launcherJar} \
|
||||
-product io.cloudbeaver.product.ce.product \
|
||||
-web-config conf/cloudbeaver.conf \
|
||||
${CLI_OPTS} \
|
||||
"$@" \
|
||||
-nl en \
|
||||
-registryMultiLanguage
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -Eeo pipefail
|
||||
CLI_OPTS="$@"
|
||||
if [[ $# -eq 0 ]] ;
|
||||
then
|
||||
CLI_OPTS='-help'
|
||||
fi
|
||||
source run-server.sh
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: Cloudbeaver Web Model
|
||||
Bundle-SymbolicName: io.cloudbeaver.model;singleton:=true
|
||||
Bundle-Version: 1.0.83.qualifier
|
||||
Bundle-Release-Date: 20250818
|
||||
Bundle-Version: 1.0.88.qualifier
|
||||
Bundle-Release-Date: 20251103
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: org.jkiss.dbeaver.data.gis;visibility:=reexport,
|
||||
|
||||
@@ -10,4 +10,10 @@ meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.deploymentType.n
|
||||
meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbUrl.name=Security manager database URL
|
||||
meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbDriverName.name=Security manager database driver name
|
||||
meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbProductName.name=Security manager database product name
|
||||
meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbProductVersion.name=Security manager database product version
|
||||
meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbProductVersion.name=Security manager database product version
|
||||
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.readOnly.name = Read-only connection
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = Auto-commit
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = Keep-alive interval (seconds)
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultSchema.name = Default schema
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultCatalog.name = Default catalog
|
||||
@@ -0,0 +1,2 @@
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = Garder actif (en secondes)
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.description = Pas de déconnexion automatique
|
||||
@@ -0,0 +1,5 @@
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.readOnly.name = Только для чтения
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = Авто-коммит
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = Интервал Keep-alive (в секундах)
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultSchema.name = Схема по умолчанию
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultCatalog.name = Каталог по умолчанию
|
||||
@@ -0,0 +1,4 @@
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.readOnly.name = Kết nối chỉ đọc
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = Tự động commit
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = Giữ kết nối (tính bằng giây)
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.description = Không tự động ngắt kết nối
|
||||
@@ -0,0 +1,3 @@
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = 自动提交
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = 保持连接 (秒)
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.description = 无自动断开连接
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.model</artifactId>
|
||||
<version>1.0.83-SNAPSHOT</version>
|
||||
<version>1.0.88-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -17,18 +17,14 @@
|
||||
package io.cloudbeaver;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.app.DBPWorkspace;
|
||||
import org.jkiss.dbeaver.model.auth.SMSessionContext;
|
||||
import org.jkiss.dbeaver.model.impl.app.BaseProjectImpl;
|
||||
import org.jkiss.dbeaver.model.rm.RMController;
|
||||
import org.jkiss.dbeaver.model.rm.RMControllerProvider;
|
||||
import org.jkiss.dbeaver.model.rm.RMProject;
|
||||
import org.jkiss.dbeaver.model.rm.RMProjectType;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
import org.jkiss.utils.Pair;
|
||||
import org.jkiss.dbeaver.model.rm.*;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
|
||||
public abstract class BaseWebProjectImpl extends BaseProjectImpl implements RMControllerProvider {
|
||||
|
||||
@@ -81,6 +77,12 @@ public abstract class BaseWebProjectImpl extends BaseProjectImpl implements RMCo
|
||||
return project.getId();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Path getAbsolutePath() {
|
||||
@@ -102,52 +104,25 @@ public abstract class BaseWebProjectImpl extends BaseProjectImpl implements RMCo
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for Bulk Update of resources properties paths
|
||||
*
|
||||
* @param oldToNewPaths collection of OldPath to NewPath pairs
|
||||
*/
|
||||
public void moveResourcePropertiesBatch(@NotNull Collection<Pair<String, String>> oldToNewPaths) {
|
||||
loadMetadata();
|
||||
synchronized (metadataSync) {
|
||||
for (var pathsPair : oldToNewPaths) {
|
||||
final var oldResourcePath = CommonUtils.normalizeResourcePath(pathsPair.getFirst());
|
||||
final var newResourcePath = CommonUtils.normalizeResourcePath(pathsPair.getSecond());
|
||||
final var resProps = resourceProperties.remove(oldResourcePath);
|
||||
if (resProps != null) {
|
||||
resourceProperties.put(newResourcePath, resProps);
|
||||
}
|
||||
}
|
||||
}
|
||||
flushMetadata();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for Bulk Remove of resources properties
|
||||
*/
|
||||
public boolean resetResourcesPropertiesBatch(@NotNull Collection<String> resourcesPaths) {
|
||||
loadMetadata();
|
||||
boolean propertiesChanged = false;
|
||||
synchronized (metadataSync) {
|
||||
for (var resourcePath : resourcesPaths) {
|
||||
var removedProperties = resourceProperties.remove(CommonUtils.normalizeResourcePath(resourcePath));
|
||||
if (removedProperties != null) {
|
||||
propertiesChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (propertiesChanged) {
|
||||
flushMetadata();
|
||||
}
|
||||
return propertiesChanged;
|
||||
}
|
||||
|
||||
public Path getMetadataFilePath() {
|
||||
return getMetadataPath().resolve(METADATA_STORAGE_FILE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPrivateProject() {
|
||||
return RMProjectType.USER.equals(getRMProject().getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProject(@Nullable String newName, @Nullable String description) throws DBException {
|
||||
RMProject rmProject = getResourceController().updateProject(this.getId(), new RMProjectInfo(newName, description));
|
||||
updateProjectInfo(rmProject.getName(), rmProject.getDescription());
|
||||
}
|
||||
|
||||
public void updateProjectInfo(@Nullable String newName, @Nullable String description) {
|
||||
this.project.setName(newName);
|
||||
this.project.setDescription(description);
|
||||
RMEventManager.fireEvent(
|
||||
new RMEvent(
|
||||
RMEvent.Action.PROJECT_UPDATE,
|
||||
project
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ public interface DBWConstants {
|
||||
String PERMISSION_SQL_RESULT_UPDATE = "sql-result-update";
|
||||
String PERMISSION_SQL_EXECUTE_QUERY = "sql-execute-query";
|
||||
|
||||
String PERMISSION_FS_RESOURCE_EDIT = "fs-resource-edit";
|
||||
|
||||
String GLOBAL_PERMISSION_SCRIPT_EXECUTE = "permission.sql.script.execution";
|
||||
String GLOBAL_PERMISSION_DATA_EDITOR_IMPORT = "permission.data-editor.import";
|
||||
String GLOBAL_PERMISSION_DATA_EDITOR_EDITING = "permission.data-editor.editing";
|
||||
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2024 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver;
|
||||
|
||||
import io.cloudbeaver.model.WebConnectionConfig;
|
||||
import io.cloudbeaver.model.app.WebAppConfiguration;
|
||||
import io.cloudbeaver.utils.ServletAppUtils;
|
||||
import io.cloudbeaver.utils.WebDataSourceUtils;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
|
||||
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriver;
|
||||
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
public class WebConnectionConfigInputHandler<T extends WebConnectionConfig, C extends DataSourceDescriptor> {
|
||||
private static final Log log = Log.getLog(WebConnectionConfigInputHandler.class);
|
||||
protected final T input;
|
||||
protected final DBPDataSourceRegistry registry;
|
||||
|
||||
public WebConnectionConfigInputHandler(@NotNull DBPDataSourceRegistry registry, T configInput) {
|
||||
this.registry = registry;
|
||||
this.input = configInput;
|
||||
}
|
||||
|
||||
public C createDataSourceContainer() throws DBWebException {
|
||||
String driverId = input.getDriverId();
|
||||
if (CommonUtils.isEmpty(driverId)) {
|
||||
throw new DBWebException("Driver must be specified");
|
||||
}
|
||||
DBPDriver driver = WebDataSourceUtils.getDriverById(driverId);
|
||||
|
||||
C newDataSource = createDataSourceContainerFromInput(driver);
|
||||
|
||||
if (ServletAppUtils.getServletApplication().getAppConfiguration() instanceof WebAppConfiguration webAppConfiguration) {
|
||||
newDataSource.setNavigatorSettings(webAppConfiguration.getDefaultNavigatorSettings());
|
||||
}
|
||||
|
||||
WebDataSourceUtils.saveAuthProperties(
|
||||
newDataSource,
|
||||
newDataSource.getConnectionConfiguration(),
|
||||
input.getCredentials(),
|
||||
input.isSaveCredentials(),
|
||||
input.isSharedCredentials()
|
||||
);
|
||||
return newDataSource;
|
||||
}
|
||||
|
||||
public void updateDataSource(@NotNull C dataSource) throws DBWebException {
|
||||
dataSource.setId(dataSource.getId());
|
||||
if (!CommonUtils.isEmpty(input.getName())) {
|
||||
dataSource.setName(input.getName());
|
||||
}
|
||||
|
||||
if (input.getDescription() != null) {
|
||||
dataSource.setDescription(input.getDescription());
|
||||
}
|
||||
|
||||
dataSource.setFolder(input.getFolder() != null ? registry.getFolder(input.getFolder()) : null);
|
||||
if (input.isDefaultAutoCommit() != null) {
|
||||
dataSource.setDefaultAutoCommit(input.isDefaultAutoCommit());
|
||||
}
|
||||
WebDataSourceUtils.setConnectionConfiguration(
|
||||
dataSource.getDriver(),
|
||||
dataSource.getConnectionConfiguration(),
|
||||
input
|
||||
);
|
||||
WebDataSourceUtils.saveAuthProperties(
|
||||
dataSource,
|
||||
dataSource.getConnectionConfiguration(),
|
||||
input.getCredentials(),
|
||||
input.isSaveCredentials(),
|
||||
input.isSharedCredentials()
|
||||
);
|
||||
dataSource.setConnectionReadOnly(input.isReadOnly());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected C createDataSourceContainerFromInput(@NotNull DBPDriver driver) {
|
||||
DBPConnectionConfiguration dsConfig = new DBPConnectionConfiguration();
|
||||
WebDataSourceUtils.setConnectionConfiguration(driver, dsConfig, input);
|
||||
C newDataSource = registry.createDataSource(driver, dsConfig);
|
||||
|
||||
newDataSource.setSavePassword(true);
|
||||
newDataSource.setName(CommonUtils.notNull(input.getName(), "NewConnection"));
|
||||
newDataSource.setDescription(input.getDescription());
|
||||
newDataSource.setConnectionReadOnly(input.isReadOnly());
|
||||
if (input.getFolder() != null) {
|
||||
newDataSource.setFolder(registry.getFolder(input.getFolder()));
|
||||
}
|
||||
return newDataSource;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+1
-1
@@ -36,7 +36,7 @@ public class WebDataSourceConnectEventListener implements DBPEventListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDataSourceEvent(DBPEvent event) {
|
||||
public void handleDataSourceEvent(@NotNull DBPEvent event) {
|
||||
if (!(event.getObject() instanceof DBPDataSourceContainer container)) {
|
||||
return;
|
||||
}
|
||||
|
||||
+2
-1
@@ -35,6 +35,7 @@ import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.secret.DBSSecretController;
|
||||
import org.jkiss.dbeaver.model.struct.DBSObjectFilter;
|
||||
import org.jkiss.dbeaver.registry.DataSourceConfigurationManager;
|
||||
import org.jkiss.dbeaver.registry.DataSourceParseResults;
|
||||
import org.jkiss.dbeaver.registry.DataSourcePersistentRegistry;
|
||||
import org.jkiss.dbeaver.registry.DataSourceRegistry;
|
||||
|
||||
@@ -377,7 +378,7 @@ public class WebGlobalProjectRegistryProxy implements DBPDataSourceRegistry, Dat
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean loadDataSources(
|
||||
public DataSourceParseResults loadDataSources(
|
||||
@NotNull List<DBPDataSourceConfigurationStorage> storages,
|
||||
@NotNull DataSourceConfigurationManager manager,
|
||||
@Nullable Collection<String> dataSourceIds, boolean refresh,
|
||||
|
||||
+142
-22
@@ -16,11 +16,13 @@
|
||||
*/
|
||||
package io.cloudbeaver;
|
||||
|
||||
import io.cloudbeaver.model.WebConnectionConfig;
|
||||
import io.cloudbeaver.model.WebConnectionInfo;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.utils.WebDataSourceUtils;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
|
||||
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
|
||||
@@ -28,19 +30,26 @@ import org.jkiss.dbeaver.model.app.DBPDataSourceRegistryCache;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNModel;
|
||||
import org.jkiss.dbeaver.model.rm.RMProject;
|
||||
import org.jkiss.dbeaver.model.rm.RMUtils;
|
||||
import org.jkiss.dbeaver.model.security.SMObjectType;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty;
|
||||
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
|
||||
import org.jkiss.dbeaver.registry.DataSourceRegistry;
|
||||
import org.jkiss.dbeaver.runtime.jobs.DisconnectJob;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class WebSessionProjectImpl extends WebProjectImpl {
|
||||
private static final Log log = Log.getLog(WebSessionProjectImpl.class);
|
||||
protected final WebSession webSession;
|
||||
private final Map<String, WebConnectionInfo> connections = new HashMap<>();
|
||||
private final Map<String, Object> projectSettings = new ConcurrentHashMap<>();
|
||||
private boolean registryIsLoaded = false;
|
||||
|
||||
public WebSessionProjectImpl(
|
||||
@@ -156,7 +165,7 @@ public class WebSessionProjectImpl extends WebProjectImpl {
|
||||
*/
|
||||
@NotNull
|
||||
public synchronized WebConnectionInfo addConnection(@NotNull DBPDataSourceContainer dataSourceContainer) {
|
||||
WebConnectionInfo connection = new WebConnectionInfo(webSession, dataSourceContainer);
|
||||
WebConnectionInfo connection = createConnectionInfo(dataSourceContainer);
|
||||
synchronized (connections) {
|
||||
connections.put(dataSourceContainer.getId(), connection);
|
||||
}
|
||||
@@ -194,40 +203,31 @@ public class WebSessionProjectImpl extends WebProjectImpl {
|
||||
/**
|
||||
* updates data sources based on event in web session
|
||||
*
|
||||
* @param dataSourceIds list of updated connections
|
||||
* @param eventId id of event
|
||||
* @param event data source updated event
|
||||
*/
|
||||
public synchronized boolean updateProjectDataSources(@NotNull List<String> dataSourceIds, @NotNull String eventId) {
|
||||
public synchronized boolean updateProjectDataSources(@NotNull WSDataSourceEvent event) {
|
||||
var sendDataSourceUpdatedEvent = false;
|
||||
DBPDataSourceRegistry registry = getDataSourceRegistry();
|
||||
// save old connections
|
||||
var oldDataSources = dataSourceIds.stream()
|
||||
.map(registry::getDataSource)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toMap(
|
||||
DBPDataSourceContainer::getId,
|
||||
registry::createDataSource)
|
||||
);
|
||||
if (WSDataSourceEvent.CREATED.equals(eventId) || WSDataSourceEvent.UPDATED.equals(eventId)) {
|
||||
registry.refreshConfig(dataSourceIds);
|
||||
if (WSDataSourceEvent.CREATED.equals(event.getId()) || WSDataSourceEvent.UPDATED.equals(event.getId())) {
|
||||
registry.refreshConfig(event.getDataSourceIds());
|
||||
}
|
||||
for (String dsId : dataSourceIds) {
|
||||
for (String dsId : event.getDataSourceIds()) {
|
||||
DataSourceDescriptor ds = (DataSourceDescriptor) registry.getDataSource(dsId);
|
||||
if (ds == null) {
|
||||
continue;
|
||||
}
|
||||
switch (eventId) {
|
||||
switch (event.getId()) {
|
||||
case WSDataSourceEvent.CREATED -> {
|
||||
addConnection(ds);
|
||||
sendDataSourceUpdatedEvent = true;
|
||||
}
|
||||
case WSDataSourceEvent.UPDATED -> {
|
||||
boolean connectionUpdated = !ds.equalSettings(oldDataSources.get(dsId));
|
||||
if (connectionUpdated) {
|
||||
sendDataSourceUpdatedEvent = true;
|
||||
if (event.getProperty() == WSDataSourceProperty.CONFIGURATION) {
|
||||
WebDataSourceUtils.disconnectDataSource(webSession, ds);
|
||||
}
|
||||
// if settings were changed we need to send event
|
||||
if (event.getProperty() != WSDataSourceProperty.INTERNAL) {
|
||||
sendDataSourceUpdatedEvent = true;
|
||||
}
|
||||
}
|
||||
case WSDataSourceEvent.DELETED -> {
|
||||
WebDataSourceUtils.disconnectDataSource(webSession, ds);
|
||||
@@ -243,4 +243,124 @@ public class WebSessionProjectImpl extends WebProjectImpl {
|
||||
}
|
||||
return sendDataSourceUpdatedEvent;
|
||||
}
|
||||
|
||||
public void refreshProjectSettings() throws DBException {
|
||||
if (webSession.getUser() == null) {
|
||||
projectSettings.clear();
|
||||
return;
|
||||
}
|
||||
Map<String, Object> loadedSettings = webSession.getSecurityController().getObjectSettings(
|
||||
getId(),
|
||||
SMObjectType.project,
|
||||
null
|
||||
);
|
||||
projectSettings.clear();
|
||||
projectSettings.putAll(loadedSettings);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public WebConnectionInfo createConnectionInfo(@NotNull DBPDataSourceContainer dataSourceDescriptor) {
|
||||
return new WebConnectionInfo(webSession, dataSourceDescriptor);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public WebConnectionInfo createConnection(@NotNull Map<String, Object> configMap) throws DBWebException {
|
||||
if (CommonUtils.isEmpty(configMap)) {
|
||||
throw new DBWebException("Connection configuration parameters are missing");
|
||||
}
|
||||
DBPDataSourceContainer newDataSource = getDataSourceContainerFromInput(configMap);
|
||||
return addDataSourceToProject(newDataSource);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public WebConnectionInfo addDataSourceToProject(@NotNull DBPDataSourceContainer newDataSource) throws DBWebException {
|
||||
DBPDataSourceRegistry registry = getDataSourceRegistry();
|
||||
try {
|
||||
registry.addDataSource(newDataSource);
|
||||
registry.checkForErrors();
|
||||
} catch (DBException e) {
|
||||
registry.removeDataSource(newDataSource);
|
||||
throw new DBWebException("Failed to create connection", e);
|
||||
}
|
||||
|
||||
WebConnectionInfo connectionInfo = addConnection(newDataSource);
|
||||
webSession.addInfoMessage("New connection was created - " + WebDataSourceUtils.getConnectionContainerInfo(
|
||||
newDataSource));
|
||||
log.info(String.format(
|
||||
"New connection was created: [info=%s, user=%s]",
|
||||
WebDataSourceUtils.getConnectionContainerInfo(newDataSource),
|
||||
webSession.getUserId()
|
||||
));
|
||||
return connectionInfo;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public WebConnectionInfo updateConnection(@Nullable Map<String, Object> configMap) throws DBWebException {
|
||||
WebConnectionConfig config = getConnectionConfigInput(configMap);
|
||||
WebConnectionInfo connectionInfo = getWebConnectionInfo(config.getConnectionId());
|
||||
DataSourceDescriptor dataSource = (DataSourceDescriptor) connectionInfo.getDataSourceContainer();
|
||||
webSession.addInfoMessage("Update connection - " + WebDataSourceUtils.getConnectionContainerInfo(dataSource));
|
||||
|
||||
DBPDataSourceRegistry registry = getDataSourceRegistry();
|
||||
getInputConfigHandler(configMap).updateDataSource(dataSource);
|
||||
connectionInfo.setCredentialsSavedInSession(null);
|
||||
try {
|
||||
registry.updateDataSource(dataSource);
|
||||
registry.checkForErrors();
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Failed to update connection", e);
|
||||
}
|
||||
return connectionInfo;
|
||||
}
|
||||
|
||||
public boolean deleteConnection(@NotNull String connectionId) throws DBWebException {
|
||||
WebConnectionInfo connectionInfo = getWebConnectionInfo(connectionId);
|
||||
webSession.addInfoMessage("Delete connection - " +
|
||||
WebDataSourceUtils.getConnectionContainerInfo(connectionInfo.getDataSourceContainer()));
|
||||
closeAndDeleteConnection(connectionInfo);
|
||||
|
||||
log.info(String.format(
|
||||
"Connection deleted: [info=%s, userId=%s]",
|
||||
WebDataSourceUtils.getConnectionContainerInfo(connectionInfo.getDataSourceContainer()),
|
||||
webSession.getUserId()
|
||||
));
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private WebConnectionInfo closeAndDeleteConnection(@NotNull WebConnectionInfo connectionInfo) throws DBWebException {
|
||||
DBPDataSourceContainer dataSourceContainer = connectionInfo.getDataSourceContainer();
|
||||
boolean disconnected = WebDataSourceUtils.disconnectDataSource(webSession, dataSourceContainer);
|
||||
DBPDataSourceRegistry registry = getDataSourceRegistry();
|
||||
registry.removeDataSource(dataSourceContainer);
|
||||
try {
|
||||
registry.checkForErrors();
|
||||
} catch (DBException e) {
|
||||
try {
|
||||
registry.addDataSource(dataSourceContainer);
|
||||
} catch (DBException ex) {
|
||||
log.error("Error re-adding after delete attempt", e);
|
||||
}
|
||||
throw new DBWebException("Failed to delete connection", e);
|
||||
}
|
||||
removeConnection(dataSourceContainer);
|
||||
return connectionInfo;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public DataSourceDescriptor getDataSourceContainerFromInput(@NotNull Map<String, Object> configMap) throws DBWebException {
|
||||
return getInputConfigHandler(configMap).createDataSourceContainer();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public WebConnectionConfig getConnectionConfigInput(@Nullable Map<String, Object> configMap) {
|
||||
return new WebConnectionConfig(configMap == null ? Map.of() : configMap);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected WebConnectionConfigInputHandler getInputConfigHandler(@NotNull Map<String, Object> configMap) {
|
||||
return new WebConnectionConfigInputHandler<>(getDataSourceRegistry(), getConnectionConfigInput(configMap));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -10,7 +10,7 @@ import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
public abstract class BaseDatasourceAccessCheckHandler implements DBPDataSourceHandler {
|
||||
@Override
|
||||
public void beforeConnect(
|
||||
DBRProgressMonitor monitor,
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@NotNull DBPDataSourceContainer dataSourceContainer
|
||||
) throws DBException {
|
||||
if (isDriverDisabled(dataSourceContainer.getDriver())) {
|
||||
@@ -20,7 +20,7 @@ public abstract class BaseDatasourceAccessCheckHandler implements DBPDataSourceH
|
||||
|
||||
@Override
|
||||
public void beforeDisconnect(
|
||||
DBRProgressMonitor monitor,
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@NotNull DBPDataSourceContainer dataSourceContainer
|
||||
) throws DBException {
|
||||
|
||||
|
||||
+46
-35
@@ -16,11 +16,11 @@
|
||||
*/
|
||||
package io.cloudbeaver.model;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriverConfigurationType;
|
||||
import org.jkiss.dbeaver.model.data.json.JSONUtils;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -56,6 +56,7 @@ public class WebConnectionConfig {
|
||||
private boolean saveCredentials;
|
||||
private boolean sharedCredentials;
|
||||
private Map<String, Object> mainPropertyValues;
|
||||
private Map<String, Object> expertSettingsValues;
|
||||
private Map<String, Object> providerProperties;
|
||||
private List<WebNetworkHandlerConfigInput> networkHandlersConfig;
|
||||
private DBPDriverConfigurationType configurationType;
|
||||
@@ -67,47 +68,52 @@ public class WebConnectionConfig {
|
||||
public WebConnectionConfig() {
|
||||
}
|
||||
|
||||
public WebConnectionConfig(Map<String, Object> params) {
|
||||
if (!CommonUtils.isEmpty(params)) {
|
||||
connectionId = JSONUtils.getString(params, "connectionId");
|
||||
driverId = JSONUtils.getString(params, "driverId");
|
||||
readOnly = JSONUtils.getBoolean(params, "readOnly");
|
||||
public WebConnectionConfig(@NotNull Map<String, Object> params) {
|
||||
connectionId = JSONUtils.getString(params, "connectionId");
|
||||
driverId = JSONUtils.getString(params, "driverId");
|
||||
|
||||
host = JSONUtils.getString(params, "host");
|
||||
port = JSONUtils.getString(params, "port");
|
||||
serverName = JSONUtils.getString(params, "serverName");
|
||||
databaseName = JSONUtils.getString(params, "databaseName");
|
||||
url = JSONUtils.getString(params, "url");
|
||||
host = JSONUtils.getString(params, "host");
|
||||
port = JSONUtils.getString(params, "port");
|
||||
serverName = JSONUtils.getString(params, "serverName");
|
||||
databaseName = JSONUtils.getString(params, "databaseName");
|
||||
url = JSONUtils.getString(params, "url");
|
||||
|
||||
keepAliveInterval = JSONUtils.getInteger(params, "keepAliveInterval", -1);
|
||||
defaultAutoCommit = JSONUtils.getBoolean(params, "autocommit", true);
|
||||
name = JSONUtils.getString(params, "name");
|
||||
description = JSONUtils.getString(params, "description");
|
||||
folder = JSONUtils.getString(params, "folder");
|
||||
|
||||
name = JSONUtils.getString(params, "name");
|
||||
description = JSONUtils.getString(params, "description");
|
||||
folder = JSONUtils.getString(params, "folder");
|
||||
properties = JSONUtils.getObjectOrNull(params, "properties");
|
||||
userName = JSONUtils.getString(params, "userName");
|
||||
userPassword = JSONUtils.getString(params, "userPassword");
|
||||
selectedSecretId = JSONUtils.getString(params, "selectedSecretId");
|
||||
|
||||
properties = JSONUtils.getObjectOrNull(params, "properties");
|
||||
userName = JSONUtils.getString(params, "userName");
|
||||
userPassword = JSONUtils.getString(params, "userPassword");
|
||||
selectedSecretId = JSONUtils.getString(params, "selectedSecretId");
|
||||
authModelId = JSONUtils.getString(params, "authModelId");
|
||||
credentials = JSONUtils.getObjectOrNull(params, "credentials");
|
||||
saveCredentials = JSONUtils.getBoolean(params, "saveCredentials");
|
||||
sharedCredentials = JSONUtils.getBoolean(params, "sharedCredentials");
|
||||
|
||||
authModelId = JSONUtils.getString(params, "authModelId");
|
||||
credentials = JSONUtils.getObjectOrNull(params, "credentials");
|
||||
saveCredentials = JSONUtils.getBoolean(params, "saveCredentials");
|
||||
sharedCredentials = JSONUtils.getBoolean(params, "sharedCredentials");
|
||||
mainPropertyValues = JSONUtils.getObjectOrNull(params, "mainPropertyValues");
|
||||
providerProperties = JSONUtils.getObjectOrNull(params, "providerProperties");
|
||||
expertSettingsValues = JSONUtils.getObjectOrNull(params, "expertSettingsValues");
|
||||
keepAliveInterval = JSONUtils.getInteger(
|
||||
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_KEEP_ALIVE_INTERVAL, -1);
|
||||
readOnly = JSONUtils.getBoolean(
|
||||
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_READ_ONLY);
|
||||
defaultAutoCommit = JSONUtils.getBoolean(
|
||||
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_AUTO_COMMIT, true);
|
||||
defaultCatalogName = JSONUtils.getString(
|
||||
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_DEFAULT_CATALOG);
|
||||
defaultSchemaName = JSONUtils.getString(
|
||||
expertSettingsValues != null ? expertSettingsValues : params,
|
||||
WebExpertSettingsProperties.PROP_DEFAULT_SCHEMA
|
||||
);
|
||||
|
||||
mainPropertyValues = JSONUtils.getObjectOrNull(params, "mainPropertyValues");
|
||||
providerProperties = JSONUtils.getObjectOrNull(params, "providerProperties");
|
||||
defaultCatalogName = JSONUtils.getString(params, "defaultCatalogName");
|
||||
defaultSchemaName = JSONUtils.getString(params, "defaultSchemaName");
|
||||
String configType = JSONUtils.getString(params, "configurationType");
|
||||
configurationType = configType == null ? null : DBPDriverConfigurationType.valueOf(configType);
|
||||
|
||||
String configType = JSONUtils.getString(params, "configurationType");
|
||||
configurationType = configType == null ? null : DBPDriverConfigurationType.valueOf(configType);
|
||||
|
||||
networkHandlersConfig = new ArrayList<>();
|
||||
for (Map<String, Object> nhc : JSONUtils.getObjectList(params, "networkHandlersConfig")) {
|
||||
networkHandlersConfig.add(new WebNetworkHandlerConfigInput(nhc));
|
||||
}
|
||||
networkHandlersConfig = new ArrayList<>();
|
||||
for (Map<String, Object> nhc : JSONUtils.getObjectList(params, "networkHandlersConfig")) {
|
||||
networkHandlersConfig.add(new WebNetworkHandlerConfigInput(nhc));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,6 +225,11 @@ public class WebConnectionConfig {
|
||||
return mainPropertyValues;
|
||||
}
|
||||
|
||||
@Property
|
||||
public Map<String, Object> getExpertSettingsValues() {
|
||||
return expertSettingsValues;
|
||||
}
|
||||
|
||||
@Property
|
||||
public Map<String, Object> getProviderProperties() {
|
||||
return providerProperties;
|
||||
|
||||
+34
-3
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
package io.cloudbeaver.model;
|
||||
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.WebProjectImpl;
|
||||
import io.cloudbeaver.model.app.BaseWebAppConfiguration;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
@@ -251,6 +252,7 @@ public class WebConnectionInfo {
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public String[] getFeatures() {
|
||||
List<String> features = new ArrayList<>();
|
||||
|
||||
@@ -280,11 +282,13 @@ public class WebConnectionInfo {
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public DBNBrowseSettings getNavigatorSettings() {
|
||||
return dataSourceContainer.getNavigatorSettings();
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public List<WebDataFormat> getSupportedDataFormats() {
|
||||
List<WebDataFormat> formats = new ArrayList<>();
|
||||
formats.add(WebDataFormat.resultset);
|
||||
@@ -299,11 +303,13 @@ public class WebConnectionInfo {
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public WebConnectionOriginInfo getOrigin() {
|
||||
return new WebConnectionOriginInfo(session, dataSourceContainer, dataSourceContainer.getOrigin());
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public DBPDriverConfigurationType getConfigurationType() {
|
||||
DBPDriverConfigurationType configurationType = dataSourceContainer.getConnectionConfiguration().getConfigurationType();
|
||||
if (configurationType == null) {
|
||||
@@ -319,12 +325,17 @@ public class WebConnectionInfo {
|
||||
!dataSourceContainer.getDriver().isAnonymousAccess();
|
||||
}
|
||||
|
||||
public void validateConnection() throws DBWebException {
|
||||
|
||||
}
|
||||
|
||||
// we don't show non-secured properties in FE when connecting to DB without saved credentials
|
||||
private boolean isAuthPropertiesEmpty() {
|
||||
return Arrays.stream(getAuthProperties()).allMatch(f -> f.hasFeature(DBConstants.PROP_FEATURE_NON_SECURED));
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public String getAuthModel() {
|
||||
String authModelId = dataSourceContainer.getConnectionConfiguration().getAuthModelId();
|
||||
if (CommonUtils.isEmpty(authModelId)) {
|
||||
@@ -334,6 +345,7 @@ public class WebConnectionInfo {
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public WebPropertyInfo[] getAuthProperties() {
|
||||
String authModelId = getAuthModel();
|
||||
DBPAuthModelDescriptor authModel = DBWorkbench.getPlatform().getDataSourceProviderRegistry().getAuthModel(authModelId);
|
||||
@@ -356,6 +368,7 @@ public class WebConnectionInfo {
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public List<WebNetworkHandlerConfig> getNetworkHandlersConfig() {
|
||||
var registry = NetworkHandlerRegistry.getInstance();
|
||||
return dataSourceContainer.getConnectionConfiguration()
|
||||
@@ -370,20 +383,23 @@ public class WebConnectionInfo {
|
||||
}
|
||||
|
||||
@Property
|
||||
@Nullable
|
||||
public Map<String, Object> getCredentials() {
|
||||
//dataSourceContainer.getConnectionConfiguration().getCredentialsProvider().getCredentials();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Map<String, Object> getSavedAuthProperties() {
|
||||
return savedAuthProperties;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public List<WebNetworkHandlerConfigInput> getSavedNetworkCredentials() {
|
||||
return savedNetworkCredentials;
|
||||
}
|
||||
|
||||
public void setSavedCredentials(Map<String, Object> authProperties, List<WebNetworkHandlerConfigInput> networkCredentials) {
|
||||
public void setSavedCredentials(@Nullable Map<String, Object> authProperties, @Nullable List<WebNetworkHandlerConfigInput> networkCredentials) {
|
||||
this.savedAuthProperties = authProperties;
|
||||
this.savedNetworkCredentials = networkCredentials;
|
||||
}
|
||||
@@ -408,6 +424,7 @@ public class WebConnectionInfo {
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public Map<String, String> getMainPropertyValues() {
|
||||
Map<String, String> mainProperties = new LinkedHashMap<>();
|
||||
mainProperties.put(DBConstants.PROP_HOST, getHost());
|
||||
@@ -417,6 +434,17 @@ public class WebConnectionInfo {
|
||||
return mainProperties;
|
||||
}
|
||||
|
||||
@Property
|
||||
public Map<String, Object> getExpertSettingsValues() {
|
||||
Map<String, Object> expertSettings = new LinkedHashMap<>();
|
||||
expertSettings.put(WebExpertSettingsProperties.PROP_AUTO_COMMIT, isAutocommit());
|
||||
expertSettings.put(WebExpertSettingsProperties.PROP_KEEP_ALIVE_INTERVAL, getKeepAliveInterval());
|
||||
expertSettings.put(WebExpertSettingsProperties.PROP_READ_ONLY, isReadOnly());
|
||||
expertSettings.put(WebExpertSettingsProperties.PROP_DEFAULT_CATALOG, getDefaultCatalogName());
|
||||
expertSettings.put(WebExpertSettingsProperties.PROP_DEFAULT_SCHEMA, getDefaultSchemaName());
|
||||
return expertSettings;
|
||||
}
|
||||
|
||||
@Property
|
||||
public Map<String, String> getProviderProperties() {
|
||||
return dataSourceContainer.getConnectionConfiguration().getProviderProperties();
|
||||
@@ -452,7 +480,7 @@ public class WebConnectionInfo {
|
||||
return dataSourceContainer.getRequiredExternalAuth();
|
||||
}
|
||||
|
||||
private boolean hasProjectPermission(RMProjectPermission projectPermission) {
|
||||
private boolean hasProjectPermission(@NotNull RMProjectPermission projectPermission) {
|
||||
DBPProject project = dataSourceContainer.getProject();
|
||||
if (!(project instanceof WebProjectImpl webProject)) {
|
||||
return false;
|
||||
@@ -492,18 +520,21 @@ public class WebConnectionInfo {
|
||||
}
|
||||
|
||||
@Property
|
||||
@Nullable
|
||||
public String getDefaultCatalogName() {
|
||||
DBPConnectionConfiguration connectionConfiguration = dataSourceContainer.getConnectionConfiguration();
|
||||
return connectionConfiguration.getBootstrap().getDefaultCatalogName();
|
||||
}
|
||||
|
||||
@Property
|
||||
@Nullable
|
||||
public String getDefaultSchemaName() {
|
||||
DBPConnectionConfiguration connectionConfiguration = dataSourceContainer.getConnectionConfiguration();
|
||||
return connectionConfiguration.getBootstrap().getDefaultSchemaName();
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public List<WebSecretInfo> getSharedSecrets() throws DBException {
|
||||
return dataSourceContainer.listSharedCredentials()
|
||||
.stream()
|
||||
@@ -511,8 +542,8 @@ public class WebConnectionInfo {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
@NotNull
|
||||
public List<String> getTools() {
|
||||
if (!session.hasPermission(RMConstants.PERMISSION_DATABASE_DEVELOPER)) {
|
||||
return List.of();
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package io.cloudbeaver.model;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.DBPObject;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriver;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriverConstants;
|
||||
import org.jkiss.dbeaver.model.meta.IPropertyValueValidator;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
/**
|
||||
* Web expert settings properties. Class for returning filtered expert settings properties in web interface.
|
||||
*/
|
||||
public class WebExpertSettingsProperties implements DBPObject {
|
||||
public static final String PROP_READ_ONLY = "readOnly";
|
||||
public static final String PROP_AUTO_COMMIT = "autocommit";
|
||||
public static final String PROP_KEEP_ALIVE_INTERVAL = "keepAliveInterval";
|
||||
public static final String PROP_DEFAULT_CATALOG = "defaultCatalogName";
|
||||
public static final String PROP_DEFAULT_SCHEMA = "defaultSchemaName";
|
||||
|
||||
private final DBPDriver driver;
|
||||
|
||||
public WebExpertSettingsProperties(@NotNull DBPDriver driver) {
|
||||
this.driver = driver;
|
||||
}
|
||||
|
||||
@Property(order = 1, id = PROP_KEEP_ALIVE_INTERVAL, visibleIf = KeepAliveIntervalFieldValidator.class)
|
||||
public int getKeepAliveInterval() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Property(order = 2, id = PROP_AUTO_COMMIT, visibleIf = AutoCommitFieldValidator.class)
|
||||
public boolean isAutoCommit() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Property(order = 3, id = PROP_READ_ONLY, visibleIf = ReadOnlyFieldValidator.class)
|
||||
public boolean isReadOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Property(order = 4, id = PROP_DEFAULT_CATALOG, visibleIf = DefaultCatalogFieldVisibleValidator.class)
|
||||
public String getDefaultCatalog() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Property(order = 5, id = PROP_DEFAULT_SCHEMA, visibleIf = DefaultSchemaFieldVisibleValidator.class)
|
||||
public String getDefaultSchema() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static class KeepAliveIntervalFieldValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
|
||||
@Override
|
||||
public boolean isValidValue(@NotNull WebExpertSettingsProperties object, @Nullable Object value) throws IllegalArgumentException {
|
||||
return CommonUtils.toBoolean(!object.driver.isEmbedded(), true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class AutoCommitFieldValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
|
||||
@Override
|
||||
public boolean isValidValue(@NotNull WebExpertSettingsProperties object, @Nullable Object value) throws IllegalArgumentException {
|
||||
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_TRANSACTIONS), true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReadOnlyFieldValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
|
||||
@Override
|
||||
public boolean isValidValue(@NotNull WebExpertSettingsProperties object, @Nullable Object value) throws IllegalArgumentException {
|
||||
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_READ_ONLY_MODE), true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DefaultCatalogFieldVisibleValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
|
||||
@Override
|
||||
public boolean isValidValue(@NotNull WebExpertSettingsProperties object, @Nullable Object value) throws IllegalArgumentException {
|
||||
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_CATALOG_SELECTION), true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DefaultSchemaFieldVisibleValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
|
||||
@Override
|
||||
public boolean isValidValue(@NotNull WebExpertSettingsProperties object, @Nullable Object value) throws IllegalArgumentException {
|
||||
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_SCHEMA_SELECTION), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -37,6 +37,7 @@ public interface ApiCallInterceptor {
|
||||
@NotNull HttpServletRequest request,
|
||||
@Nullable Map<String, Object> variables,
|
||||
@NotNull String apiCall,
|
||||
@Nullable String userId,
|
||||
@NotNull LocalDateTime startTime,
|
||||
@Nullable String errorMessage,
|
||||
@NotNull String apiProtocol
|
||||
|
||||
+14
-2
@@ -41,6 +41,7 @@ import org.jkiss.dbeaver.utils.GeneralUtils;
|
||||
import org.jkiss.dbeaver.utils.RuntimeUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
@@ -198,20 +199,26 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Object start(IApplicationContext context) {
|
||||
initializeApplicationServices();
|
||||
try {
|
||||
try {
|
||||
this.instanceServer = new CloudBeaverInstanceServer();
|
||||
this.instanceServer = createInstanceServer();
|
||||
} catch (Exception e) {
|
||||
log.error("Error initializing instance server", e);
|
||||
}
|
||||
startServer();
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return EXIT_ERROR_UNSPECIFIED;
|
||||
}
|
||||
return null;
|
||||
return EXIT_OK;
|
||||
}
|
||||
|
||||
protected CloudBeaverInstanceServer createInstanceServer() throws IOException {
|
||||
return new CloudBeaverInstanceServer();
|
||||
}
|
||||
|
||||
protected abstract void startServer() throws DBException;
|
||||
@@ -279,4 +286,9 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
|
||||
public ApplicationInstanceController getInstanceServer() {
|
||||
return instanceServer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAnonymousAccessEnabled() {
|
||||
return getAppConfiguration().isAnonymousAccessEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -100,4 +100,6 @@ public interface ServletApplication extends DBPApplication {
|
||||
default void getStatusInfo(Map<String, Object> infoMap) {
|
||||
|
||||
}
|
||||
|
||||
boolean isAnonymousAccessEnabled();
|
||||
}
|
||||
|
||||
+10
-1
@@ -62,6 +62,7 @@ public class ServletSystemInformationCollector<T extends ServletApplication> imp
|
||||
private DBPConnectionInformation smDatabaseInfo;
|
||||
private String workspacePath;
|
||||
private final DeploymentType deploymentType;
|
||||
private boolean internalDatabaseInfoCollected = false;
|
||||
|
||||
public ServletSystemInformationCollector(@NotNull T application) {
|
||||
this.application = application;
|
||||
@@ -158,7 +159,15 @@ public class ServletSystemInformationCollector<T extends ServletApplication> imp
|
||||
/**
|
||||
* Collects info about internal databases.
|
||||
*/
|
||||
public void collectInternalDatabaseUseInformation() throws DBException {
|
||||
public synchronized void collectInternalDatabaseUseInformation() throws DBException {
|
||||
if (internalDatabaseInfoCollected) {
|
||||
return;
|
||||
}
|
||||
collectInternalDatabaseInfo();
|
||||
this.internalDatabaseInfoCollected = true;
|
||||
}
|
||||
|
||||
protected void collectInternalDatabaseInfo() throws DBException {
|
||||
this.smDatabaseInfo = application.getAdminSecurityController(new NoAuthCredentialsProvider())
|
||||
.getInternalDatabaseInformation();
|
||||
}
|
||||
|
||||
+29
-8
@@ -16,20 +16,41 @@
|
||||
*/
|
||||
package io.cloudbeaver.model.cli;
|
||||
|
||||
import org.jkiss.dbeaver.model.cli.ApplicationCommandLine;
|
||||
import org.jkiss.dbeaver.model.cli.ApplicationInstanceController;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.cli.*;
|
||||
import org.jkiss.dbeaver.model.cli.model.CommandLineAuthenticator;
|
||||
import picocli.CommandLine;
|
||||
|
||||
public class CloudBeaverCommandLine extends ApplicationCommandLine<ApplicationInstanceController> {
|
||||
private static CloudBeaverCommandLine INSTANCE = null;
|
||||
|
||||
private CloudBeaverCommandLine() {
|
||||
@Nullable
|
||||
private final CommandLineAuthenticator authenticator;
|
||||
|
||||
public CloudBeaverCommandLine(@Nullable CommandLineAuthenticator authenticator) {
|
||||
super();
|
||||
this.authenticator = authenticator;
|
||||
}
|
||||
|
||||
public synchronized static CloudBeaverCommandLine getInstance() {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new CloudBeaverCommandLine();
|
||||
@Override
|
||||
protected CloudBeaverTopLevelCommand createTopLevelCommand(
|
||||
@Nullable ApplicationInstanceController applicationInstanceController,
|
||||
@NotNull CommandLineContext context,
|
||||
@NotNull CLIRunMeta runMeta
|
||||
) {
|
||||
if (authenticator != null) {
|
||||
context.setContextParameter(CLIConstants.CONTEXT_PARAM_AUTHENTICATOR, authenticator);
|
||||
}
|
||||
return INSTANCE;
|
||||
return new CloudBeaverTopLevelCommand(applicationInstanceController, context, runMeta);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected CommandLine initCommandLine(
|
||||
@Nullable ApplicationInstanceController applicationInstanceController,
|
||||
@NotNull CommandLineContext context,
|
||||
@NotNull CLIRunMeta runMeta
|
||||
) {
|
||||
return super.initCommandLine(applicationInstanceController, context, runMeta);
|
||||
}
|
||||
}
|
||||
|
||||
+16
-8
@@ -16,31 +16,39 @@
|
||||
*/
|
||||
package io.cloudbeaver.model.cli;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.cli.ApplicationInstanceController;
|
||||
import org.jkiss.dbeaver.model.cli.ApplicationInstanceServer;
|
||||
import org.jkiss.dbeaver.model.cli.CmdProcessResult;
|
||||
import org.jkiss.dbeaver.model.cli.CLIProcessResult;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class CloudBeaverInstanceServer extends ApplicationInstanceServer<ApplicationInstanceController> {
|
||||
private final CloudBeaverCommandLine commandLine;
|
||||
|
||||
public CloudBeaverInstanceServer() throws IOException {
|
||||
super(ApplicationInstanceController.class);
|
||||
this.commandLine = new CloudBeaverCommandLine(null);
|
||||
}
|
||||
|
||||
protected CloudBeaverInstanceServer(@NotNull CloudBeaverCommandLine commandLine) throws IOException {
|
||||
super(ApplicationInstanceController.class);
|
||||
this.commandLine = commandLine;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CmdProcessResult handleCommandLine(@NotNull String[] args) {
|
||||
CommandLine cmd = CloudBeaverCommandLine.getInstance().getCommandLine(args);
|
||||
public CLIProcessResult handleCommandLine(@NotNull String[] args) {
|
||||
try {
|
||||
return CloudBeaverCommandLine.getInstance().executeCommandLineCommands(
|
||||
cmd,
|
||||
return commandLine.executeCommandLineCommands(
|
||||
this,
|
||||
false
|
||||
false,
|
||||
false,
|
||||
args
|
||||
);
|
||||
} catch (Exception e) {
|
||||
return new CmdProcessResult(CmdProcessResult.PostAction.ERROR, "Error executing command: " + e.getMessage());
|
||||
return new CLIProcessResult(CLIProcessResult.PostAction.ERROR, "Error executing command: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.model.cli;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.cli.ApplicationInstanceController;
|
||||
import org.jkiss.dbeaver.model.cli.CLIRunMeta;
|
||||
import org.jkiss.dbeaver.model.cli.CommandLineContext;
|
||||
import org.jkiss.dbeaver.model.cli.command.AbstractTopLevelCommand;
|
||||
import picocli.CommandLine;
|
||||
|
||||
@CommandLine.Command(name = "cbvr", description = "CloudBeaver commands", mixinStandardHelpOptions = true)
|
||||
public class CloudBeaverTopLevelCommand extends AbstractTopLevelCommand {
|
||||
protected CloudBeaverTopLevelCommand(
|
||||
@Nullable ApplicationInstanceController controller,
|
||||
@NotNull CommandLineContext context,
|
||||
@NotNull CLIRunMeta meta
|
||||
|
||||
) {
|
||||
super(controller, context, meta);
|
||||
}
|
||||
}
|
||||
+11
-2
@@ -60,26 +60,31 @@ public class DBNResourceManagerProject extends DBNAbstractResourceManagerNode {
|
||||
return project.getId();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getNodeType() {
|
||||
return "rm.project";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getNodeDisplayName() {
|
||||
return project.getDisplayName();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getLocalizedName(String locale) {
|
||||
public String getLocalizedName(@NotNull String locale) {
|
||||
return getNodeDisplayName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getNodeDescription() {
|
||||
return project.getDescription();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DBPImage getNodeIcon() {
|
||||
return DBIcon.PROJECT;
|
||||
@@ -90,6 +95,7 @@ public class DBNResourceManagerProject extends DBNAbstractResourceManagerNode {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DBNResourceManagerResource[] getChildren(@NotNull DBRProgressMonitor monitor) throws DBException {
|
||||
if (children == null && !monitor.isForceCacheUsage()) {
|
||||
@@ -109,14 +115,16 @@ public class DBNResourceManagerProject extends DBNAbstractResourceManagerNode {
|
||||
return ((DBNResourceManagerRoot) getParentNode()).getResourceController();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Deprecated
|
||||
@Override
|
||||
public String getNodeItemPath() {
|
||||
return getParentNode().getNodeItemPath() + "/" + getName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DBNNode refreshNode(DBRProgressMonitor monitor, Object source) throws DBException {
|
||||
public DBNNode refreshNode(@NotNull DBRProgressMonitor monitor, @Nullable Object source) throws DBException {
|
||||
children = null;
|
||||
return this;
|
||||
}
|
||||
@@ -141,6 +149,7 @@ public class DBNResourceManagerProject extends DBNAbstractResourceManagerNode {
|
||||
return project;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return getNodeDisplayName();
|
||||
|
||||
+10
-2
@@ -49,21 +49,25 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getNodeType() {
|
||||
return "rm.resource";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getNodeDisplayName() {
|
||||
return resource.getName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getNodeDescription() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DBPImage getNodeIcon() {
|
||||
if (resource.isFolder()) {
|
||||
@@ -109,6 +113,7 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
|
||||
return resource.isFolder();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DBNNode[] getChildren(@NotNull DBRProgressMonitor monitor) throws DBException {
|
||||
if (children == null && !monitor.isForceCacheUsage()) {
|
||||
@@ -154,14 +159,16 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
|
||||
throw new DBException("Can't detect resource root node");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Deprecated
|
||||
@Override
|
||||
public String getNodeItemPath() {
|
||||
return getParentNode().getNodeItemPath() + "/" + getNodeDisplayName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DBNNode refreshNode(DBRProgressMonitor monitor, Object source) throws DBException {
|
||||
public DBNNode refreshNode(@NotNull DBRProgressMonitor monitor, @Nullable Object source) throws DBException {
|
||||
this.children = null;
|
||||
return super.refreshNode(monitor, source);
|
||||
}
|
||||
@@ -171,7 +178,7 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rename(DBRProgressMonitor monitor, String newName) throws DBException {
|
||||
public void rename(@NotNull DBRProgressMonitor monitor, @NotNull String newName) throws DBException {
|
||||
String resourceName = resource.getName();
|
||||
try {
|
||||
if (newName.indexOf('.') == -1) {
|
||||
@@ -192,6 +199,7 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return getNodeDisplayName();
|
||||
|
||||
+21
-1
@@ -50,6 +50,7 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
|
||||
return resourceController;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getNodeType() {
|
||||
return "rm";
|
||||
@@ -61,16 +62,19 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
|
||||
return "rm";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getNodeDisplayName() {
|
||||
return "resources";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getNodeDescription() {
|
||||
return "Resources";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DBPImage getNodeIcon() {
|
||||
return null;
|
||||
@@ -81,6 +85,7 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DBNResourceManagerProject[] getChildren(@NotNull DBRProgressMonitor monitor) throws DBException {
|
||||
if (projects == null && !monitor.isForceCacheUsage()) {
|
||||
@@ -110,6 +115,7 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
|
||||
return projects;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Deprecated
|
||||
@Override
|
||||
public String getNodeItemPath() {
|
||||
@@ -117,8 +123,9 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
|
||||
return NodePathType.ext.getPrefix() + getNodeDisplayName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DBNNode refreshNode(DBRProgressMonitor monitor, Object source) throws DBException {
|
||||
public DBNNode refreshNode(@NotNull DBRProgressMonitor monitor, @Nullable Object source) throws DBException {
|
||||
projects = null;
|
||||
return this;
|
||||
}
|
||||
@@ -141,6 +148,9 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
|
||||
case PROJECT_ADD:
|
||||
addProjectNode(event.getProject());
|
||||
break;
|
||||
case PROJECT_UPDATE:
|
||||
updateProjectNode(event.getProject());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,4 +197,14 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
|
||||
private void addProjectNode(RMProject project) {
|
||||
projects = ArrayUtils.add(DBNResourceManagerProject.class, projects, new DBNResourceManagerProject(this, project));
|
||||
}
|
||||
|
||||
private void updateProjectNode(@NotNull RMProject project) {
|
||||
var projectNode = getProjectNode(project);
|
||||
projectNode.ifPresent(
|
||||
dbnResourceManagerProject -> {
|
||||
dbnResourceManagerProject.getProject().setName(project.getName());
|
||||
dbnResourceManagerProject.getProject().setDescription(project.getDescription());
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+19
-6
@@ -99,7 +99,7 @@ public abstract class BaseLocalResourceController implements RMController {
|
||||
@NotNull String propName,
|
||||
@NotNull Object propValue
|
||||
) throws DBException {
|
||||
BaseWebProjectImpl webProject = getWebProject(projectId, false);
|
||||
RMLocalProject webProject = getWebProject(projectId, false);
|
||||
doFileWriteOperation(projectId, webProject.getMetadataFilePath(),
|
||||
() -> {
|
||||
log.debug("Updating value for property '" + propName + "' in project '" + projectId + "'");
|
||||
@@ -149,27 +149,37 @@ public abstract class BaseLocalResourceController implements RMController {
|
||||
@NotNull String projectId,
|
||||
@NotNull String configuration,
|
||||
@Nullable List<String> dataSourceIds
|
||||
) throws DBException {
|
||||
return updateProjectDataSourcesConfig(projectId, configuration, dataSourceIds) != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected DataSourceParseResults updateProjectDataSourcesConfig(
|
||||
@NotNull String projectId,
|
||||
@NotNull String configuration,
|
||||
@Nullable List<String> dataSourceIds
|
||||
) throws DBException {
|
||||
try (var lock = lockController.lock(projectId, "updateProjectDataSources")) {
|
||||
DBPProject project = getWebProject(projectId, false);
|
||||
return doFileWriteOperation(projectId, project.getMetadataFolder(false),
|
||||
return doFileWriteOperation(
|
||||
projectId, project.getMetadataFolder(false),
|
||||
() -> {
|
||||
DBPDataSourceRegistry registry = project.getDataSourceRegistry();
|
||||
DBPDataSourceConfigurationStorage storage = new DataSourceMemoryStorage(configuration.getBytes(
|
||||
StandardCharsets.UTF_8));
|
||||
DataSourceConfigurationManager manager = new DataSourceConfigurationManagerBuffer();
|
||||
var configChanged = ((DataSourcePersistentRegistry) registry).loadDataSources(
|
||||
final DataSourceParseResults parseResults = ((DataSourcePersistentRegistry) registry).loadDataSources(
|
||||
List.of(storage),
|
||||
manager,
|
||||
dataSourceIds,
|
||||
true,
|
||||
false
|
||||
dataSourceIds == null
|
||||
);
|
||||
registry.checkForErrors();
|
||||
log.debug("Save data sources configuration in project '" + projectId + "'");
|
||||
((DataSourcePersistentRegistry) registry).saveDataSources();
|
||||
registry.checkForErrors();
|
||||
return configChanged;
|
||||
return parseResults;
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -217,6 +227,7 @@ public abstract class BaseLocalResourceController implements RMController {
|
||||
var parent = result.getParent();
|
||||
var parentFolder = parent == null ? null : registry.getFolder(parent.toString().replace("\\", "/"));
|
||||
DBPDataSourceFolder newFolder = registry.addFolder(parentFolder, newName);
|
||||
((DataSourcePersistentRegistry) registry).saveDataSources();
|
||||
registry.checkForErrors();
|
||||
return null;
|
||||
}
|
||||
@@ -244,6 +255,7 @@ public abstract class BaseLocalResourceController implements RMController {
|
||||
log.warn("Can not find folder by path [" + folderPath + "] for deletion");
|
||||
}
|
||||
}
|
||||
((DataSourcePersistentRegistry) registry).saveDataSources();
|
||||
registry.checkForErrors();
|
||||
return null;
|
||||
}
|
||||
@@ -265,13 +277,14 @@ public abstract class BaseLocalResourceController implements RMController {
|
||||
DBPDataSourceRegistry registry = project.getDataSourceRegistry();
|
||||
registry.moveFolder(oldPath, newPath);
|
||||
registry.checkForErrors();
|
||||
((DataSourcePersistentRegistry) registry).saveDataSources();
|
||||
return null;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract BaseWebProjectImpl getWebProject(String projectId, boolean refresh) throws DBException;
|
||||
protected abstract RMLocalProject getWebProject(String projectId, boolean refresh) throws DBException;
|
||||
|
||||
protected abstract <T> T doFileWriteOperation(String projectId, Path file, RMFileOperation<T> operation)
|
||||
throws DBException;
|
||||
|
||||
+321
-101
@@ -16,42 +16,50 @@
|
||||
*/
|
||||
package io.cloudbeaver.model.rm.local;
|
||||
|
||||
import io.cloudbeaver.BaseWebProjectImpl;
|
||||
import io.cloudbeaver.DBWConstants;
|
||||
import io.cloudbeaver.model.app.ServletApplication;
|
||||
import io.cloudbeaver.service.security.SMUtils;
|
||||
import io.cloudbeaver.service.sql.WebSQLConstants;
|
||||
import io.cloudbeaver.utils.ServletAppUtils;
|
||||
import io.cloudbeaver.utils.WebRMUtils;
|
||||
import io.cloudbeaver.utils.file.UniversalFileVisitor;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
|
||||
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
|
||||
import org.jkiss.dbeaver.model.app.DBPWorkspace;
|
||||
import org.jkiss.dbeaver.model.auth.SMCredentials;
|
||||
import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
|
||||
import org.jkiss.dbeaver.model.fs.lock.FileLockController;
|
||||
import org.jkiss.dbeaver.model.impl.app.BaseProjectImpl;
|
||||
import org.jkiss.dbeaver.model.impl.auth.SessionContextImpl;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNLocalFolder;
|
||||
import org.jkiss.dbeaver.model.rm.*;
|
||||
import org.jkiss.dbeaver.model.security.SMController;
|
||||
import org.jkiss.dbeaver.model.security.SMObjectType;
|
||||
import org.jkiss.dbeaver.model.sql.DBQuotaException;
|
||||
import org.jkiss.dbeaver.model.websocket.event.MessageType;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSSessionLogUpdatedEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDatasourceFolderEvent;
|
||||
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
|
||||
import org.jkiss.dbeaver.registry.DataSourceParseResults;
|
||||
import org.jkiss.dbeaver.registry.ResourceTypeDescriptor;
|
||||
import org.jkiss.dbeaver.registry.ResourceTypeRegistry;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
import org.jkiss.utils.IOUtils;
|
||||
import org.jkiss.utils.Pair;
|
||||
import org.jkiss.utils.StandardConstants;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.text.MessageFormat;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -73,7 +81,7 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
private Supplier<SMController> smControllerSupplier;
|
||||
protected final List<RMFileOperationHandler> fileHandlers;
|
||||
|
||||
private final Map<String, BaseWebProjectImpl> projectRegistries = new LinkedHashMap<>();
|
||||
private final Map<String, RMLocalProject> projectRegistries = new LinkedHashMap<>();
|
||||
|
||||
public LocalResourceController(
|
||||
DBPWorkspace workspace,
|
||||
@@ -108,13 +116,11 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
return userId == null ? null : this.userProjectsPath.resolve(userId);
|
||||
}
|
||||
|
||||
protected BaseWebProjectImpl getWebProject(String projectId, boolean refresh) throws DBException {
|
||||
protected RMLocalProject getWebProject(String projectId, boolean refresh) throws DBException {
|
||||
synchronized (projectRegistries) {
|
||||
BaseWebProjectImpl project = projectRegistries.get(projectId);
|
||||
RMLocalProject project = projectRegistries.get(projectId);
|
||||
if (project == null || refresh) {
|
||||
SessionContextImpl sessionContext = new SessionContextImpl(null);
|
||||
RMProject rmProject = makeProjectFromId(projectId, false);
|
||||
project = createWebProjectImpl(projectId, sessionContext, rmProject);
|
||||
project = createWebProjectImpl(projectId, new SessionContextImpl(null));
|
||||
projectRegistries.put(projectId, project);
|
||||
}
|
||||
return project;
|
||||
@@ -122,12 +128,11 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected InternalWebProjectImpl createWebProjectImpl(
|
||||
String projectId,
|
||||
SessionContextImpl sessionContext,
|
||||
RMProject rmProject
|
||||
protected RMLocalProject createWebProjectImpl(
|
||||
@NotNull String projectId,
|
||||
@NotNull SessionContextImpl sessionContext
|
||||
) throws DBException {
|
||||
return new InternalWebProjectImpl(sessionContext, rmProject, getProjectPath(projectId));
|
||||
return new RMLocalProject(workspace, sessionContext, getProjectPath(projectId), WebRMUtils.parseProjectName(projectId).getType());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -182,7 +187,7 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
.stream()
|
||||
.filter(smObjectPermissions -> CommonUtils.isNotEmpty(smObjectPermissions.getObjectId()))
|
||||
.map(projectPermission -> makeProjectFromPath(
|
||||
sharedProjectsPath.resolve(parseProjectNameUnsafe(projectPermission.getObjectId()).getName()),
|
||||
sharedProjectsPath.resolve(WebRMUtils.parseProjectNameUnsafe(projectPermission.getObjectId()).getName()),
|
||||
Arrays.stream(projectPermission.getPermissions()).map(RMProjectPermission::fromPermission).collect(Collectors.toSet()),
|
||||
RMProjectType.SHARED, true)
|
||||
)
|
||||
@@ -248,7 +253,7 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
var allPaths = list.toList();
|
||||
for (Path path : allPaths) {
|
||||
var projectPerms = getProjectPermissions(
|
||||
makeProjectIdFromPath(path, RMProjectType.SHARED),
|
||||
RMUtils.makeProjectIdFromPath(path, RMProjectType.SHARED),
|
||||
RMProjectType.SHARED
|
||||
);
|
||||
var rmProject = makeProjectFromPath(path, projectPerms, RMProjectType.SHARED, false);
|
||||
@@ -273,28 +278,54 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
}
|
||||
}
|
||||
validateResourcePath(name);
|
||||
RMProject project;
|
||||
validateProjectName(null, name);
|
||||
var projectPath = sharedProjectsPath.resolve(name);
|
||||
if (Files.exists(projectPath)) {
|
||||
throw new DBException("Project '" + name + "' already exists");
|
||||
}
|
||||
project = makeProjectFromPath(projectPath, Set.of(), RMProjectType.SHARED, false);
|
||||
if (project == null) {
|
||||
throw new DBException("Project '" + name + "' not created");
|
||||
}
|
||||
RMLocalProject webProject = new RMLocalProject(workspace, new SessionContextImpl(null), projectPath, RMProjectType.SHARED);
|
||||
webProject.updateProject(name, description);
|
||||
RMProject rmProject = createRmProjectFromWebProject(projectPath, webProject, new String[0]);
|
||||
try {
|
||||
log.debug("Creating project '" + project.getId() + "'");
|
||||
log.debug("Creating project '" + rmProject.getId() + "'");
|
||||
Files.createDirectories(projectPath);
|
||||
if (ServletAppUtils.getServletApplication().isMultiNode()) {
|
||||
createResourceTypeFolders(projectPath);
|
||||
}
|
||||
fireRmProjectAddEvent(project);
|
||||
return project;
|
||||
fireRmProjectAddEvent(rmProject);
|
||||
return rmProject;
|
||||
} catch (IOException e) {
|
||||
throw new DBException("Error creating project path", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RMProject updateProject(@NotNull String projectId, @NotNull RMProjectInfo projectInfo) throws DBException {
|
||||
validateProjectName(projectId, projectInfo.getName());
|
||||
try (var projectLock = lockController.lock(projectId, "updateProject")) {
|
||||
RMLocalProject project = getWebProject(projectId, false);
|
||||
Path targetPath = getProjectPath(projectId);
|
||||
if (!Files.exists(targetPath)) {
|
||||
throw new DBException("Project folder '" + projectId + "' not found");
|
||||
}
|
||||
if (!project.canUpdateProjectName()) {
|
||||
throw new DBException("Project '" + projectId + "' is not shared");
|
||||
}
|
||||
project.updateProject(projectInfo.getName(), projectInfo.getDescription());
|
||||
return WebRMUtils.createRmProjectFromWebProject(project);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateProjectName(@Nullable String projectId, @Nullable String name) throws DBException {
|
||||
boolean duplicatedName = Arrays.stream(listAllSharedProjects())
|
||||
.filter(p -> !p.getId().equals(projectId))
|
||||
.map(RMProject::getName)
|
||||
.noneMatch(n -> n.equalsIgnoreCase(name));
|
||||
if (!duplicatedName) {
|
||||
throw new DBException("Project name '" + name + "' is already used");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteProject(@NotNull String projectId) throws DBException {
|
||||
try (var projectLock = lockController.lock(projectId, "deleteProject")) {
|
||||
@@ -334,6 +365,239 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
return project;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createProjectDataSources(
|
||||
@NotNull String projectId,
|
||||
@NotNull String configuration,
|
||||
@Nullable List<String> dataSourceIds
|
||||
) throws DBException {
|
||||
super.createProjectDataSources(projectId, configuration, dataSourceIds);
|
||||
if (credentialsProvider.getActiveUserCredentials() != null && dataSourceIds != null) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDataSourceEvent.create(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
projectId,
|
||||
dataSourceIds,
|
||||
WSDataSourceProperty.CONFIGURATION
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateProjectDataSources(
|
||||
@NotNull String projectId,
|
||||
@NotNull String configuration,
|
||||
@Nullable List<String> dataSourceIds
|
||||
) throws DBException {
|
||||
DBPDataSourceRegistry registry = getWebProject(projectId, false).getDataSourceRegistry();
|
||||
Map<String, DataSourceDescriptor> oldDataSources = registry.getDataSources().stream()
|
||||
.filter(ds -> dataSourceIds == null || dataSourceIds.contains(ds.getId()))
|
||||
.collect(Collectors.toMap(
|
||||
DBPDataSourceContainer::getId,
|
||||
registry::createDataSource
|
||||
)
|
||||
);
|
||||
DataSourceParseResults parseResults = super.updateProjectDataSourcesConfig(projectId, configuration, dataSourceIds);
|
||||
sendDataSourcesConfigUpdatedEvent(registry, oldDataSources, parseResults);
|
||||
return parseResults != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteProjectDataSources(@NotNull String projectId, @NotNull String[] dataSourceIds) throws DBException {
|
||||
super.deleteProjectDataSources(projectId, dataSourceIds);
|
||||
if (credentialsProvider.getActiveUserCredentials() != null && dataSourceIds.length > 0) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDataSourceEvent.delete(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
projectId,
|
||||
Arrays.asList(dataSourceIds),
|
||||
WSDataSourceProperty.CONFIGURATION
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createProjectDataSourceFolder(@NotNull String projectId, @NotNull String folderPath) throws DBException {
|
||||
super.createProjectDataSourceFolder(projectId, folderPath);
|
||||
if (credentialsProvider.getActiveUserCredentials() != null) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDatasourceFolderEvent.create(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
projectId,
|
||||
List.of(createNodePathFromFolderPath(projectId, folderPath))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveProjectDataSourceFolder(
|
||||
@NotNull String projectId,
|
||||
@NotNull String oldPath,
|
||||
@NotNull String newPath
|
||||
) throws DBException {
|
||||
super.moveProjectDataSourceFolder(projectId, oldPath, newPath);
|
||||
if (credentialsProvider.getActiveUserCredentials() != null) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDatasourceFolderEvent.delete(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
projectId,
|
||||
List.of(createNodePathFromFolderPath(projectId, oldPath))
|
||||
)
|
||||
);
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDatasourceFolderEvent.create(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
projectId,
|
||||
List.of(createNodePathFromFolderPath(projectId, newPath))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteProjectDataSourceFolders(@NotNull String projectId, @NotNull String[] folderPaths, boolean dropContents)
|
||||
throws DBException {
|
||||
super.deleteProjectDataSourceFolders(projectId, folderPaths, dropContents);
|
||||
if (credentialsProvider.getActiveUserCredentials() != null) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDatasourceFolderEvent.create(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
projectId,
|
||||
Arrays.stream(folderPaths).map(
|
||||
p -> createNodePathFromFolderPath(projectId, p)
|
||||
).collect(Collectors.toList())
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private String createNodePathFromFolderPath(String projectId, String folderPath) {
|
||||
return DBNLocalFolder.makeLocalFolderItemPath(projectId, folderPath);
|
||||
}
|
||||
|
||||
private void sendDataSourcesConfigUpdatedEvent(
|
||||
@NotNull DBPDataSourceRegistry registry,
|
||||
@NotNull Map<String, DataSourceDescriptor> oldDataSources,
|
||||
@Nullable DataSourceParseResults parseResults
|
||||
) {
|
||||
if (parseResults == null || credentialsProvider.getActiveUserCredentials() == null || oldDataSources.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<String> updatedConfigurationDataSourceIds = new ArrayList<>();
|
||||
List<String> updatedNameDataSourceIds = new ArrayList<>();
|
||||
List<String> updatedInternalConfigurationDataSourceIds = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, DataSourceDescriptor> entry : oldDataSources.entrySet()) {
|
||||
String dsId = entry.getKey();
|
||||
DataSourceDescriptor oldDs = entry.getValue();
|
||||
DataSourceDescriptor newDs = (DataSourceDescriptor) registry.getDataSource(dsId);
|
||||
if (newDs == null) {
|
||||
continue;
|
||||
}
|
||||
if (!oldDs.equalConfiguration(newDs)) {
|
||||
updatedConfigurationDataSourceIds.add(dsId);
|
||||
} else if (!oldDs.isLooselyEqualTo(newDs)) {
|
||||
updatedNameDataSourceIds.add(dsId);
|
||||
} else if (!oldDs.equalInternalConfiguration(newDs)) {
|
||||
updatedInternalConfigurationDataSourceIds.add(dsId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!updatedConfigurationDataSourceIds.isEmpty()) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDataSourceEvent.update(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
registry.getProject().getId(),
|
||||
updatedConfigurationDataSourceIds,
|
||||
WSDataSourceProperty.CONFIGURATION
|
||||
)
|
||||
);
|
||||
}
|
||||
if (!updatedNameDataSourceIds.isEmpty()) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDataSourceEvent.update(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
registry.getProject().getId(),
|
||||
updatedNameDataSourceIds,
|
||||
WSDataSourceProperty.NAME
|
||||
)
|
||||
);
|
||||
}
|
||||
if (!updatedInternalConfigurationDataSourceIds.isEmpty()) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDataSourceEvent.update(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
registry.getProject().getId(),
|
||||
updatedInternalConfigurationDataSourceIds,
|
||||
WSDataSourceProperty.INTERNAL
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!parseResults.addedDataSources.isEmpty()) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDataSourceEvent.create(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
registry.getProject().getId(),
|
||||
updatedNameDataSourceIds,
|
||||
WSDataSourceProperty.CONFIGURATION
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!parseResults.removedDataSources.isEmpty()) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDataSourceEvent.delete(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
registry.getProject().getId(),
|
||||
updatedNameDataSourceIds,
|
||||
WSDataSourceProperty.CONFIGURATION
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!parseResults.addedFolders.isEmpty()) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDatasourceFolderEvent.create(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
registry.getProject().getId(),
|
||||
parseResults.addedFolders.stream().map(
|
||||
f -> createNodePathFromFolderPath(registry.getProject().getId(), f.getFolderPath())
|
||||
).toList()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!parseResults.removedFolders.isEmpty()) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSDatasourceFolderEvent.delete(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
registry.getProject().getId(),
|
||||
parseResults.removedFolders.stream().map(
|
||||
f -> createNodePathFromFolderPath(registry.getProject().getId(), f.getFolderPath())
|
||||
).toList()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public RMResource[] listResources(
|
||||
@@ -634,7 +898,7 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
) throws DBException {
|
||||
try (var ignoredLock = lockController.lock(projectId, "resourcePropertyUpdate")) {
|
||||
validateResourcePath(resourcePath);
|
||||
BaseWebProjectImpl webProject = getWebProject(projectId, false);
|
||||
RMLocalProject webProject = getWebProject(projectId, false);
|
||||
doFileWriteOperation(projectId, webProject.getMetadataFilePath(),
|
||||
() -> {
|
||||
log.debug("Updating resource property '" + propertyName + "' in project '" + projectId + "'");
|
||||
@@ -655,7 +919,7 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
) throws DBException {
|
||||
try (var ignoredLock = lockController.lock(projectId, "resourcePropertyUpdate")) {
|
||||
validateResourcePath(resourcePath);
|
||||
BaseWebProjectImpl webProject = getWebProject(projectId, false);
|
||||
RMLocalProject webProject = getWebProject(projectId, false);
|
||||
doFileWriteOperation(projectId, webProject.getMetadataFilePath(),
|
||||
() -> {
|
||||
log.debug("Updating resource '" + resourcePath + "' properties in project '" + projectId + "'");
|
||||
@@ -689,15 +953,9 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String makeProjectIdFromPath(Path path, RMProjectType type) {
|
||||
String projectName = path.getFileName().toString();
|
||||
return type.getPrefix() + "_" + projectName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected RMProject makeProjectFromId(String projectId, boolean loadPermissions) throws DBException {
|
||||
var projectName = parseProjectName(projectId);
|
||||
var projectName = WebRMUtils.parseProjectName(projectId);
|
||||
var projectPath = getProjectPath(projectId);
|
||||
if (!Files.exists(projectPath)) {
|
||||
if (isPrivateProject(projectId) && isProjectOwner(projectId)) {
|
||||
@@ -734,28 +992,8 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
.flatMap(rmProjectPermission -> rmProjectPermission.getAllPermissions().stream())
|
||||
.toArray(String[]::new);
|
||||
|
||||
RMProject project = new RMProject();
|
||||
String projectName = path.getFileName().toString();
|
||||
project.setName(projectName);
|
||||
project.setId(makeProjectIdFromPath(path, type));
|
||||
project.setType(type);
|
||||
project.setProjectPermissions(allProjectPermissions);
|
||||
if (Files.exists(path)) {
|
||||
try {
|
||||
project.setCreateTime(
|
||||
OffsetDateTime.ofInstant(Files.getLastModifiedTime(path).toInstant(), ZoneId.of("UTC")).toInstant().toEpochMilli());
|
||||
} catch (IOException e) {
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
// Resource types
|
||||
project.setResourceTypes(ResourceTypeRegistry.getInstance().getResourceTypes()
|
||||
.stream()
|
||||
.filter(ResourceTypeDescriptor::isManagable)
|
||||
.map(RMResourceType::new)
|
||||
.toArray(RMResourceType[]::new));
|
||||
|
||||
return project;
|
||||
RMLocalProject webProject = new RMLocalProject(workspace, new SessionContextImpl(null), path, type);
|
||||
return createRmProjectFromWebProject(path, webProject, allProjectPermissions);
|
||||
}
|
||||
|
||||
private void createResourceTypeFolders(Path path) {
|
||||
@@ -834,7 +1072,7 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
}
|
||||
|
||||
protected Path getProjectPath(String projectId) throws DBException {
|
||||
RMProjectName project = parseProjectName(projectId);
|
||||
RMProjectName project = WebRMUtils.parseProjectName(projectId);
|
||||
RMProjectType type = project.getType();
|
||||
String projectName = project.getName();
|
||||
switch (type) {
|
||||
@@ -996,56 +1234,13 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
}
|
||||
}
|
||||
|
||||
public static class RMProjectName {
|
||||
String prefix;
|
||||
String name;
|
||||
|
||||
private RMProjectName(String prefix, String name) {
|
||||
this.prefix = prefix;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public RMProjectType getType() {
|
||||
return RMProjectType.getByPrefix(prefix);
|
||||
}
|
||||
}
|
||||
|
||||
public static RMProjectName parseProjectName(String projectId) throws DBException {
|
||||
if (CommonUtils.isEmpty(projectId)) {
|
||||
throw new DBException("Project id is empty");
|
||||
}
|
||||
return parseProjectNameUnsafe(projectId);
|
||||
}
|
||||
|
||||
private static RMProjectName parseProjectNameUnsafe(String projectId) {
|
||||
String prefix;
|
||||
String name;
|
||||
int divPos = projectId.indexOf("_");
|
||||
if (divPos < 0) {
|
||||
prefix = RMProjectType.USER.getPrefix();
|
||||
name = projectId;
|
||||
} else {
|
||||
prefix = projectId.substring(0, divPos);
|
||||
name = projectId.substring(divPos + 1);
|
||||
}
|
||||
return new RMProjectName(prefix, name);
|
||||
}
|
||||
|
||||
public static boolean isGlobalProject(String projectId) {
|
||||
RMProjectName rmProjectName = parseProjectNameUnsafe(projectId);
|
||||
RMProjectName rmProjectName = WebRMUtils.parseProjectNameUnsafe(projectId);
|
||||
return RMProjectType.GLOBAL.getPrefix().equals(rmProjectName.getPrefix());
|
||||
}
|
||||
|
||||
public static boolean isPrivateProject(String projectId) {
|
||||
RMProjectName rmProjectName = parseProjectNameUnsafe(projectId);
|
||||
RMProjectName rmProjectName = WebRMUtils.parseProjectNameUnsafe(projectId);
|
||||
return RMProjectType.USER.getPrefix().equals(rmProjectName.getPrefix());
|
||||
}
|
||||
|
||||
@@ -1056,9 +1251,34 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
}
|
||||
|
||||
public static boolean isProjectOwner(String projectId, String userId) {
|
||||
RMProjectName rmProjectName = parseProjectNameUnsafe(projectId);
|
||||
RMProjectName rmProjectName = WebRMUtils.parseProjectNameUnsafe(projectId);
|
||||
return RMProjectType.USER.getPrefix().equals(rmProjectName.getPrefix()) &&
|
||||
rmProjectName.name.equals(userId);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static RMProject createRmProjectFromWebProject(Path path, RMLocalProject webProject, String[] allProjectPermissions) {
|
||||
RMProject project = WebRMUtils.createRmProjectFromWebProject(webProject);
|
||||
project.setProjectPermissions(allProjectPermissions);
|
||||
if (Files.exists(path)) {
|
||||
try {
|
||||
project.setCreateTime(OffsetDateTime.ofInstant(
|
||||
Files.getLastModifiedTime(path).toInstant(),
|
||||
StandardConstants.ZONE_ID_UTC
|
||||
).toInstant().toEpochMilli());
|
||||
} catch (IOException e) {
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
// Resource types
|
||||
project.setResourceTypes(ResourceTypeRegistry.getInstance().getResourceTypes()
|
||||
.stream()
|
||||
.filter(ResourceTypeDescriptor::isManageable)
|
||||
.map(RMResourceType::new)
|
||||
.toArray(RMResourceType[]::new));
|
||||
|
||||
return project;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.model.rm.local;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.app.DBPWorkspace;
|
||||
import org.jkiss.dbeaver.model.auth.SMSessionContext;
|
||||
import org.jkiss.dbeaver.model.rm.RMProjectType;
|
||||
import org.jkiss.dbeaver.model.rm.RMUtils;
|
||||
import org.jkiss.dbeaver.registry.project.LocalProjectImpl;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class RMLocalProject extends LocalProjectImpl {
|
||||
@NotNull
|
||||
private final RMProjectType projectType;
|
||||
|
||||
public RMLocalProject(
|
||||
@NotNull DBPWorkspace workspace,
|
||||
@Nullable SMSessionContext sessionContext,
|
||||
@NotNull Path projectPath,
|
||||
@NotNull RMProjectType type
|
||||
) {
|
||||
super(workspace, sessionContext, projectPath);
|
||||
this.projectType = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVirtual() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getId() {
|
||||
return RMUtils.makeProjectIdFromPath(projectPath, projectType);
|
||||
}
|
||||
|
||||
|
||||
public boolean canUpdateProjectName() {
|
||||
return RMProjectType.SHARED.equals(projectType);
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.model.rm.local;
|
||||
|
||||
import org.jkiss.dbeaver.model.rm.RMProjectType;
|
||||
|
||||
public class RMProjectName {
|
||||
String prefix;
|
||||
String name;
|
||||
|
||||
public RMProjectName(String prefix, String name) {
|
||||
this.prefix = prefix;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public RMProjectType getType() {
|
||||
return RMProjectType.getByPrefix(prefix);
|
||||
}
|
||||
}
|
||||
+21
-2
@@ -29,6 +29,8 @@ import org.jkiss.dbeaver.model.auth.SMAuthSpace;
|
||||
import org.jkiss.dbeaver.model.auth.SMSessionContext;
|
||||
import org.jkiss.dbeaver.model.auth.impl.AbstractSessionPersistent;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.dbeaver.model.rm.RMProjectInfo;
|
||||
import org.jkiss.dbeaver.model.security.user.SMTeam;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSEventDeleteTempFile;
|
||||
import org.jkiss.dbeaver.model.websocket.event.session.WSSessionExpiredEvent;
|
||||
@@ -36,6 +38,7 @@ import org.jkiss.dbeaver.model.websocket.event.session.WSSessionExpiredEvent;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
@@ -55,7 +58,7 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
|
||||
protected volatile long lastAccessTime;
|
||||
|
||||
private final List<CBWebSessionEventHandler> sessionEventHandlers = new CopyOnWriteArrayList<>();
|
||||
private WebSessionEventsFilter eventsFilter = new WebSessionEventsFilter();
|
||||
private WebSessionEventsFilter eventsFilter;
|
||||
private final WebSessionWorkspace workspace;
|
||||
|
||||
public BaseWebSession(@NotNull String id, @NotNull ServletApplication application) throws DBException {
|
||||
@@ -66,6 +69,7 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
|
||||
this.workspace = createWebWorkspace();
|
||||
this.workspace.getAuthContext().addSession(this);
|
||||
this.userContext = createUserContext();
|
||||
this.eventsFilter = new WebSessionEventsFilter(this);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -122,6 +126,12 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
|
||||
userContext.refreshPermissions();
|
||||
if (userContext.isAuthorizedInSecurityManager()) {
|
||||
userContext.refreshAccessibleProjects();
|
||||
if (userContext.getUser() != null) {
|
||||
List<String> userTeamIds = Arrays.stream(userContext.getSecurityController().getCurrentUserTeams())
|
||||
.map(SMTeam::getTeamId)
|
||||
.toList();
|
||||
userContext.getUser().setTeams(userTeamIds.toArray(new String[0]));
|
||||
}
|
||||
}
|
||||
} catch (DBException e) {
|
||||
addSessionError(e);
|
||||
@@ -225,6 +235,11 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
|
||||
userContext.getAccessibleProjectIds().add(projectId);
|
||||
}
|
||||
|
||||
public void updateSessionProject(@NotNull String projectId, @NotNull RMProjectInfo rmProjectInfo) throws DBException {
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void removeSessionProject(@Nullable String projectId) throws DBException {
|
||||
userContext.getAccessibleProjectIds().remove(projectId);
|
||||
}
|
||||
@@ -239,8 +254,12 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
|
||||
@Property
|
||||
public long getRemainingTime() {
|
||||
if (application instanceof ServletAuthApplication authApplication) {
|
||||
return authApplication.getMaxSessionIdleTime() + lastAccessTime - System.currentTimeMillis();
|
||||
return getMaxSessionIdleTime(authApplication) + lastAccessTime - System.currentTimeMillis();
|
||||
}
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
protected long getMaxSessionIdleTime(@NotNull ServletAuthApplication authApplication) {
|
||||
return authApplication.getMaxSessionIdleTime();
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -18,9 +18,10 @@ package io.cloudbeaver.model.session;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.utils.HttpConstants;
|
||||
|
||||
public class WebHttpRequestInfo {
|
||||
public static final String USER_AGENT = "User-Agent";
|
||||
public static final String USER_AGENT = HttpConstants.HEADER_USER_AGENT;
|
||||
|
||||
@Nullable
|
||||
private final String id;
|
||||
|
||||
+15
-6
@@ -49,10 +49,7 @@ import org.jkiss.dbeaver.model.fs.DBFFileSystemManager;
|
||||
import org.jkiss.dbeaver.model.meta.Association;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNModel;
|
||||
import org.jkiss.dbeaver.model.rm.RMController;
|
||||
import org.jkiss.dbeaver.model.rm.RMProject;
|
||||
import org.jkiss.dbeaver.model.rm.RMProjectType;
|
||||
import org.jkiss.dbeaver.model.rm.RMUtils;
|
||||
import org.jkiss.dbeaver.model.rm.*;
|
||||
import org.jkiss.dbeaver.model.runtime.AbstractJob;
|
||||
import org.jkiss.dbeaver.model.runtime.BaseProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
@@ -307,7 +304,7 @@ public class WebSession extends BaseWebSession
|
||||
for (RMProject project : rmProjects) {
|
||||
createWebProject(project);
|
||||
}
|
||||
if (user == null && application.getAppConfiguration().isAnonymousAccessEnabled()) {
|
||||
if (user == null && application.isAnonymousAccessEnabled()) {
|
||||
WebProjectImpl anonymousProject = createWebProject(RMUtils.createAnonymousProject());
|
||||
anonymousProject.setInMemory(true);
|
||||
}
|
||||
@@ -327,6 +324,7 @@ public class WebSession extends BaseWebSession
|
||||
} else {
|
||||
sessionProject = createSessionProject(project);
|
||||
}
|
||||
sessionProject.refreshProjectSettings();
|
||||
// do not load data sources for anonymous project
|
||||
if (project.getType() == RMProjectType.USER && userContext.getUser() == null) {
|
||||
sessionProject.setInMemory(true);
|
||||
@@ -399,9 +397,10 @@ public class WebSession extends BaseWebSession
|
||||
|
||||
|
||||
private synchronized void authAsAnonymousUser() throws DBException {
|
||||
if (!application.getAppConfiguration().isAnonymousAccessEnabled()) {
|
||||
if (!application.isAnonymousAccessEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SMAuthInfo authInfo = getSecurityController().authenticateAnonymousUser(this.id, getSessionParameters(), CB_SESSION_TYPE);
|
||||
updateSMSession(authInfo);
|
||||
notifySessionAuthChange();
|
||||
@@ -981,6 +980,16 @@ public class WebSession extends BaseWebSession
|
||||
createWebProject(rmProject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSessionProject(@NotNull String projectId, @NotNull RMProjectInfo rmProjectInfo) throws DBException {
|
||||
super.updateSessionProject(projectId, rmProjectInfo);
|
||||
var project = getProjectById(projectId);
|
||||
if (project == null) {
|
||||
return;
|
||||
}
|
||||
project.updateProjectInfo(rmProjectInfo.getName(), rmProjectInfo.getDescription());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSessionProject(@Nullable String projectId) throws DBException {
|
||||
super.removeSessionProject(projectId);
|
||||
|
||||
+3
-1
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2010-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -118,6 +118,8 @@ public class WebSessionAuthProcessor {
|
||||
// We may need to associate new credentials with active user
|
||||
if (linkWithActiveUser) {
|
||||
securityController.setCurrentUserCredentials(authProviderDescriptor.getId(), authAttrs);
|
||||
//link only first(main) credentials
|
||||
alreadyLoggedIn = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+25
@@ -21,18 +21,37 @@ import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSProjectResourceEvent;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
public class WebSessionEventsFilter {
|
||||
private final BaseWebSession session;
|
||||
private final Set<String> subscribedEventTopics = new CopyOnWriteArraySet<>();
|
||||
private final Set<String> subscribedProjectIds = new CopyOnWriteArraySet<>();
|
||||
private final Map<String, List<WebTopicActivityListener>> topicActivityListeners = new ConcurrentHashMap<>();
|
||||
private final Set<String> handledStartTopics = new CopyOnWriteArraySet<>();
|
||||
|
||||
public WebSessionEventsFilter(BaseWebSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
public void subscribeOnEventTopic(@Nullable String topic) {
|
||||
if (topic == null) {
|
||||
return;
|
||||
}
|
||||
subscribedEventTopics.add(topic);
|
||||
|
||||
topicActivityListeners
|
||||
.getOrDefault(topic, List.of()).stream()
|
||||
.filter(listener -> !handledStartTopics.contains(listener.getTopicName()))
|
||||
.forEach(listener -> {
|
||||
listener.handelStartSubscriptionTopic(session);
|
||||
handledStartTopics.add(topic);
|
||||
});
|
||||
}
|
||||
|
||||
public void unsubscribeFromEventTopic(@Nullable String topic) {
|
||||
@@ -67,4 +86,10 @@ public class WebSessionEventsFilter {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void addTopicSubscribeListener(WebTopicActivityListener listener) {
|
||||
topicActivityListeners
|
||||
.computeIfAbsent(listener.getTopicName(), k -> new CopyOnWriteArrayList<>())
|
||||
.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -62,12 +62,12 @@ public class WebSessionPreferenceStore extends AbstractUserPreferenceStore {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultString(String name) {
|
||||
public String getDefaultString(@NotNull String name) {
|
||||
return parentStore.getDefaultString(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDefault(String name) {
|
||||
public boolean isDefault(@NotNull String name) {
|
||||
return !userPreferences.containsKey(name) && parentStore.isDefault(name);
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ public class WebSessionPreferenceStore extends AbstractUserPreferenceStore {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setToDefault(String name) {
|
||||
public void setToDefault(@NotNull String name) {
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -137,6 +137,7 @@ public class WebSessionWorkspace implements DBPWorkspace {
|
||||
clearProjects();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DBPImage getResourceIcon(DBPAdaptable resourceAdapter) {
|
||||
return null;
|
||||
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2010-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,11 +14,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.server;
|
||||
package io.cloudbeaver.model.session;
|
||||
|
||||
public interface HttpConstants {
|
||||
String CONTENT_TYPE = "Content-Type";
|
||||
public interface WebTopicActivityListener {
|
||||
|
||||
String TYPE_JSON = "application/json";
|
||||
String HEADER_AUTHORIZATION = "Authorization";
|
||||
void handelStartSubscriptionTopic(BaseWebSession session);
|
||||
|
||||
String getTopicName();
|
||||
}
|
||||
+5
-1
@@ -190,6 +190,10 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
return isAuthorizedInSecurityManager() && getUser() != null;
|
||||
}
|
||||
|
||||
public synchronized boolean isAnonymousUserAuthorizedInSM() {
|
||||
return isAuthorizedInSecurityManager() && getUser() == null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public synchronized WebUser getUser() {
|
||||
return user;
|
||||
@@ -239,7 +243,7 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
}
|
||||
|
||||
private Set<String> getDefaultPermissions() {
|
||||
return application.getAppConfiguration().isAnonymousAccessEnabled() ? null : Set.of();
|
||||
return application.isAnonymousAccessEnabled() ? null : Set.of();
|
||||
}
|
||||
|
||||
public RMController getRmController() {
|
||||
|
||||
@@ -77,6 +77,10 @@ public class WebUser {
|
||||
return user.getUserTeams();
|
||||
}
|
||||
|
||||
public void setTeams(@NotNull String[] teams) {
|
||||
user.setUserTeams(teams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return user.getUserId().hashCode();
|
||||
|
||||
@@ -56,6 +56,7 @@ public abstract class BaseServletPlatform extends BasePlatformImpl {
|
||||
return folder;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public abstract ServletApplication getApplication();
|
||||
|
||||
private void initTempFolder(@NotNull DBRProgressMonitor monitor) {
|
||||
|
||||
@@ -98,4 +98,5 @@ public class CBConstants {
|
||||
|
||||
public static final String BIND_SESSION_ENABLE = "enable";
|
||||
public static final String BIND_SESSION_DISABLE = "disable";
|
||||
public static final String DEFAULT_CLOUD_PROJECT_NAME = "GlobalConfiguration";
|
||||
}
|
||||
|
||||
+28
-27
@@ -18,6 +18,7 @@ package io.cloudbeaver.server;
|
||||
|
||||
import io.cloudbeaver.utils.ServletAppUtils;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.impl.preferences.AbstractPreferenceStore;
|
||||
import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore;
|
||||
|
||||
@@ -34,37 +35,37 @@ public class WebServerPreferenceStore extends AbstractPreferenceStore {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String name) {
|
||||
public boolean contains(@NotNull String name) {
|
||||
return productConf().containsKey(name) || parentStore.contains(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(String name) {
|
||||
public boolean getBoolean(@NotNull String name) {
|
||||
return toBoolean(getString(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDouble(String name) {
|
||||
public double getDouble(@NotNull String name) {
|
||||
return toDouble(getString(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloat(String name) {
|
||||
public float getFloat(@NotNull String name) {
|
||||
return toFloat(getString(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(String name) {
|
||||
public int getInt(@NotNull String name) {
|
||||
return toInt(getString(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(String name) {
|
||||
public long getLong(@NotNull String name) {
|
||||
return toLong(getString(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(String name) {
|
||||
public String getString(@NotNull String name) {
|
||||
Object value = productConf().get(name);
|
||||
if (value == null) {
|
||||
return parentStore.getString(name);
|
||||
@@ -73,38 +74,38 @@ public class WebServerPreferenceStore extends AbstractPreferenceStore {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getDefaultBoolean(String name) {
|
||||
public boolean getDefaultBoolean(@NotNull String name) {
|
||||
return getBoolean(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDefaultDouble(String name) {
|
||||
public double getDefaultDouble(@NotNull String name) {
|
||||
return getDouble(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDefaultFloat(String name) {
|
||||
public float getDefaultFloat(@NotNull String name) {
|
||||
return getFloat(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefaultInt(String name) {
|
||||
public int getDefaultInt(@NotNull String name) {
|
||||
return getInt(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDefaultLong(String name) {
|
||||
public long getDefaultLong(@NotNull String name) {
|
||||
return getLong(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultString(String name) {
|
||||
public String getDefaultString(@NotNull String name) {
|
||||
// TODO: split product.conf and runtime.product.conf
|
||||
return getString(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDefault(String name) {
|
||||
public boolean isDefault(@NotNull String name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -114,68 +115,68 @@ public class WebServerPreferenceStore extends AbstractPreferenceStore {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefault(String name, double value) {
|
||||
public void setDefault(@NotNull String name, double value) {
|
||||
setDefault(name, String.valueOf(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefault(String name, float value) {
|
||||
public void setDefault(@NotNull String name, float value) {
|
||||
setDefault(name, String.valueOf(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefault(String name, int value) {
|
||||
public void setDefault(@NotNull String name, int value) {
|
||||
setDefault(name, String.valueOf(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefault(String name, long value) {
|
||||
public void setDefault(@NotNull String name, long value) {
|
||||
setDefault(name, String.valueOf(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefault(String name, String defaultObject) {
|
||||
public void setDefault(@NotNull String name, @Nullable String defaultObject) {
|
||||
// do not store global default properties in product.conf
|
||||
this.parentStore.setDefault(name, defaultObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefault(String name, boolean value) {
|
||||
public void setDefault(@NotNull String name, boolean value) {
|
||||
setDefault(name, String.valueOf(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setToDefault(String name) {
|
||||
public void setToDefault(@NotNull String name) {
|
||||
parentStore.setToDefault(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String name, double value) {
|
||||
public void setValue(@NotNull String name, double value) {
|
||||
parentStore.setValue(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String name, float value) {
|
||||
public void setValue(@NotNull String name, float value) {
|
||||
parentStore.setValue(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String name, int value) {
|
||||
public void setValue(@NotNull String name, int value) {
|
||||
parentStore.setValue(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String name, long value) {
|
||||
public void setValue(@NotNull String name, long value) {
|
||||
parentStore.setValue(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String name, String value) {
|
||||
public void setValue(@NotNull String name, @Nullable String value) {
|
||||
parentStore.setValue(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String name, boolean value) {
|
||||
public void setValue(@NotNull String name, boolean value) {
|
||||
parentStore.setValue(name, value);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
package io.cloudbeaver.utils;
|
||||
|
||||
import org.jkiss.dbeaver.model.DBConstants;
|
||||
import org.jkiss.utils.StandardConstants;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public class CBModelConstants {
|
||||
public static final DateTimeFormatter ISO_DATE_FORMAT = DateTimeFormatter.ofPattern(DBConstants.DEFAULT_ISO_TIMESTAMP_FORMAT)
|
||||
.withZone(ZoneId.of("UTC"));
|
||||
.withZone(StandardConstants.ZONE_ID_UTC);
|
||||
}
|
||||
|
||||
@@ -292,7 +292,7 @@ public class ServletAppUtils {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getOriginFromRequestOrThrow(HttpServletRequest request) throws DBWebException {
|
||||
public static String getOriginFromRequest(@NotNull HttpServletRequest request) {
|
||||
String origin = request.getHeader(HEADER_ORIGIN);
|
||||
if (CommonUtils.isEmpty(origin)) {
|
||||
origin = request.getHeader(HEADER_X_ORIGIN);
|
||||
@@ -353,7 +353,22 @@ public class ServletAppUtils {
|
||||
if (CommonUtils.isEmpty(rootUri)) {
|
||||
return uri;
|
||||
}
|
||||
String[] split = uri.split(rootUri);
|
||||
return split[0];
|
||||
rootUri = "/" + rootUri;
|
||||
if (!uri.endsWith(rootUri)) {
|
||||
rootUri = rootUri + "/";
|
||||
}
|
||||
return substringBeforeLast(uri, rootUri);
|
||||
}
|
||||
|
||||
public static String substringBeforeLast(
|
||||
@NotNull String string,
|
||||
@NotNull String separator
|
||||
) {
|
||||
if (CommonUtils.isNotEmpty(string) && CommonUtils.isNotEmpty(separator)) {
|
||||
int pos = string.lastIndexOf(separator);
|
||||
return pos == -1 ? string : string.substring(0, pos);
|
||||
} else {
|
||||
return string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-6
@@ -21,7 +21,6 @@ import io.cloudbeaver.model.WebConnectionFolderInfo;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.DBPDataSourceFolder;
|
||||
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
public class WebConnectionFolderUtils {
|
||||
@@ -45,9 +44,4 @@ public class WebConnectionFolderUtils {
|
||||
throw new DBWebException("Folder name '" + folderName + "' contains illegal characters: /");
|
||||
}
|
||||
}
|
||||
|
||||
public static DBPDataSourceFolder createFolder(WebConnectionFolderInfo parentFolder, String newName, DBPDataSourceRegistry registry) throws DBWebException {
|
||||
DBPDataSourceFolder folder = registry.addFolder(parentFolder == null ? null : parentFolder.getDataSourceFolder(), newName);
|
||||
return folder;
|
||||
}
|
||||
}
|
||||
|
||||
+185
-3
@@ -23,6 +23,7 @@ import com.google.gson.Strictness;
|
||||
import io.cloudbeaver.DBWConstants;
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.WebSessionProjectImpl;
|
||||
import io.cloudbeaver.model.WebConnectionConfig;
|
||||
import io.cloudbeaver.model.WebConnectionInfo;
|
||||
import io.cloudbeaver.model.WebNetworkHandlerConfigInput;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
@@ -30,21 +31,25 @@ import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBConstants;
|
||||
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
|
||||
import org.jkiss.dbeaver.model.access.DBAAuthCredentials;
|
||||
import org.jkiss.dbeaver.model.access.DBAAuthCredentialsWithComplexProperties;
|
||||
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
|
||||
import org.jkiss.dbeaver.model.app.DBPProject;
|
||||
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDataSourceProviderDescriptor;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriver;
|
||||
import org.jkiss.dbeaver.model.impl.auth.AuthModelDatabaseNativeCredentials;
|
||||
import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration;
|
||||
import org.jkiss.dbeaver.model.net.ssh.SSHConstants;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceDisconnectEvent;
|
||||
import org.jkiss.dbeaver.registry.network.NetworkHandlerDescriptor;
|
||||
import org.jkiss.dbeaver.registry.network.NetworkHandlerRegistry;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
|
||||
public class WebDataSourceUtils {
|
||||
|
||||
@@ -219,4 +224,181 @@ public class WebDataSourceUtils {
|
||||
}
|
||||
credGson.fromJson(credGson.toJsonTree(properties), credentials.getClass());
|
||||
}
|
||||
|
||||
public static void saveAuthProperties(
|
||||
@NotNull DBPDataSourceContainer dataSourceContainer,
|
||||
@NotNull DBPConnectionConfiguration configuration,
|
||||
@Nullable Map<String, Object> authProperties,
|
||||
boolean saveCredentials,
|
||||
boolean sharedCredentials
|
||||
) {
|
||||
saveAuthProperties(dataSourceContainer, configuration, authProperties, saveCredentials, sharedCredentials, false);
|
||||
}
|
||||
|
||||
public static void saveAuthProperties(
|
||||
@NotNull DBPDataSourceContainer dataSourceContainer,
|
||||
@NotNull DBPConnectionConfiguration configuration,
|
||||
@Nullable Map<String, Object> authProperties,
|
||||
boolean saveCredentials,
|
||||
boolean sharedCredentials,
|
||||
boolean isTest
|
||||
) {
|
||||
dataSourceContainer.setSavePassword(saveCredentials);
|
||||
dataSourceContainer.setSharedCredentials(sharedCredentials);
|
||||
if (!saveCredentials) {
|
||||
// Reset credentials
|
||||
if (authProperties == null) {
|
||||
authProperties = new LinkedHashMap<>();
|
||||
}
|
||||
authProperties.replace(AuthModelDatabaseNativeCredentials.PROP_USER_PASSWORD, null);
|
||||
dataSourceContainer.resetPassword();
|
||||
} else {
|
||||
if (authProperties == null) {
|
||||
// No changes
|
||||
return;
|
||||
}
|
||||
}
|
||||
{
|
||||
// Read save credentials
|
||||
DBAAuthCredentials credentials = configuration.getAuthModel().loadCredentials(dataSourceContainer, configuration);
|
||||
|
||||
if (isTest) {
|
||||
var currentAuthProps = new HashMap<String, String>();
|
||||
for (Map.Entry<String, Object> stringObjectEntry : authProperties.entrySet()) {
|
||||
var value = stringObjectEntry.getValue() == null ? null : stringObjectEntry.getValue().toString();
|
||||
currentAuthProps.put(stringObjectEntry.getKey(), value);
|
||||
}
|
||||
configuration.setAuthProperties(currentAuthProps);
|
||||
}
|
||||
if (!authProperties.isEmpty()) {
|
||||
updateCredentialsFromProperties(credentials, authProperties);
|
||||
}
|
||||
|
||||
configuration.getAuthModel().saveCredentials(dataSourceContainer, configuration, credentials);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setConnectionConfiguration(
|
||||
@NotNull DBPDriver driver,
|
||||
@NotNull DBPConnectionConfiguration dsConfig,
|
||||
@NotNull WebConnectionConfig config
|
||||
) {
|
||||
setMainProperties(dsConfig, config);
|
||||
if (config.getProperties() != null) {
|
||||
Map<String, String> newProps = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, Object> pe : config.getProperties().entrySet()) {
|
||||
newProps.put(pe.getKey(), CommonUtils.toString(pe.getValue()));
|
||||
}
|
||||
dsConfig.setProperties(newProps);
|
||||
}
|
||||
if (config.getUserName() != null) {
|
||||
dsConfig.setUserName(config.getUserName());
|
||||
}
|
||||
if (config.getUserPassword() != null) {
|
||||
dsConfig.setUserPassword(config.getUserPassword());
|
||||
}
|
||||
if (config.getAuthModelId() != null) {
|
||||
dsConfig.setAuthModelId(config.getAuthModelId());
|
||||
}
|
||||
if (config.getKeepAliveInterval() >= 0) {
|
||||
dsConfig.setKeepAliveInterval(config.getKeepAliveInterval());
|
||||
}
|
||||
if (config.isDefaultAutoCommit() != null) {
|
||||
dsConfig.getBootstrap().setDefaultAutoCommit(config.isDefaultAutoCommit());
|
||||
}
|
||||
dsConfig.getBootstrap().setDefaultCatalogName(config.getDefaultCatalogName());
|
||||
dsConfig.getBootstrap().setDefaultSchemaName(config.getDefaultSchemaName());
|
||||
// Save provider props
|
||||
if (config.getProviderProperties() != null) {
|
||||
dsConfig.setProviderProperties(new LinkedHashMap<>());
|
||||
for (Map.Entry<String, Object> e : config.getProviderProperties().entrySet()) {
|
||||
dsConfig.setProviderProperty(e.getKey(), CommonUtils.toString(e.getValue()));
|
||||
}
|
||||
}
|
||||
if (config.getConfigurationType() != null) {
|
||||
dsConfig.setConfigurationType(config.getConfigurationType());
|
||||
}
|
||||
if (CommonUtils.isEmpty(config.getUrl())) {
|
||||
dsConfig.setUrl(driver.getConnectionURL(dsConfig));
|
||||
}
|
||||
// Save network handlers
|
||||
if (config.getNetworkHandlersConfig() != null) {
|
||||
for (WebNetworkHandlerConfigInput nhc : config.getNetworkHandlersConfig()) {
|
||||
DBWHandlerConfiguration handlerConfig = dsConfig.getHandler(nhc.getId());
|
||||
if (handlerConfig == null) {
|
||||
NetworkHandlerDescriptor handlerDescriptor = NetworkHandlerRegistry.getInstance().getDescriptor(nhc.getId());
|
||||
if (handlerDescriptor == null) {
|
||||
log.warn("Can't find network handler '" + nhc.getId() + "'");
|
||||
continue;
|
||||
} else {
|
||||
handlerConfig = new DBWHandlerConfiguration(handlerDescriptor, null);
|
||||
WebDataSourceUtils.updateHandlerConfig(handlerConfig, nhc);
|
||||
}
|
||||
} else {
|
||||
WebDataSourceUtils.updateHandlerConfig(handlerConfig, nhc);
|
||||
}
|
||||
dsConfig.updateHandler(handlerConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void setMainProperties(@NotNull DBPConnectionConfiguration dsConfig, @NotNull WebConnectionConfig config) {
|
||||
if (CommonUtils.isNotEmpty(config.getUrl())) {
|
||||
dsConfig.setUrl(config.getUrl());
|
||||
return;
|
||||
}
|
||||
if (config.getMainPropertyValues() != null) {
|
||||
for (Map.Entry<String, Object> e : config.getMainPropertyValues().entrySet()) {
|
||||
if (e.getValue() == null) {
|
||||
continue;
|
||||
}
|
||||
switch (e.getKey()) {
|
||||
case DBConstants.PROP_HOST -> dsConfig.setHostName(CommonUtils.toString(e.getValue()));
|
||||
case DBConstants.PROP_PORT -> dsConfig.setHostPort(CommonUtils.toString(e.getValue()));
|
||||
case DBConstants.PROP_DATABASE -> dsConfig.setDatabaseName(CommonUtils.toString(e.getValue()));
|
||||
case DBConstants.PROP_SERVER -> dsConfig.setServerName(CommonUtils.toString(e.getValue()));
|
||||
default -> throw new IllegalStateException("Unexpected value: " + e.getKey());
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (config.getHost() != null) {
|
||||
dsConfig.setHostName(config.getHost());
|
||||
}
|
||||
if (config.getPort() != null) {
|
||||
dsConfig.setHostPort(config.getPort());
|
||||
}
|
||||
if (config.getDatabaseName() != null) {
|
||||
dsConfig.setDatabaseName(config.getDatabaseName());
|
||||
}
|
||||
if (config.getServerName() != null) {
|
||||
dsConfig.setServerName(config.getServerName());
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static DBPDriver getDriverById(String id) throws DBWebException {
|
||||
int divPos = id.indexOf(':');
|
||||
if (divPos < 0) {
|
||||
throw new DBWebException("Bad driver id [" + id + "]");
|
||||
}
|
||||
String dsId = id.substring(0, divPos);
|
||||
String driverId = id.substring(divPos + 1);
|
||||
DBPDataSourceProviderDescriptor dsProvider = DBWorkbench.getPlatform().getDataSourceProviderRegistry().getDataSourceProvider(dsId);
|
||||
if (dsProvider == null) {
|
||||
throw new DBWebException("Data source provider '" + dsId + "' not found");
|
||||
}
|
||||
DBPDriver driver = dsProvider.getDriver(driverId);
|
||||
if (driver == null) {
|
||||
throw new DBWebException("Driver '" + driverId + "' not found in provider '" + dsId + "'");
|
||||
}
|
||||
return driver;
|
||||
}
|
||||
|
||||
public static String getConnectionContainerInfo(@Nullable DBPDataSourceContainer container) {
|
||||
if (container == null) {
|
||||
return null;
|
||||
}
|
||||
return container.getName() + " [" + container.getId() + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package io.cloudbeaver.utils;
|
||||
|
||||
import io.cloudbeaver.model.rm.local.RMProjectName;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.app.DBPProject;
|
||||
import org.jkiss.dbeaver.model.rm.RMProject;
|
||||
import org.jkiss.dbeaver.model.rm.RMProjectType;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
public class WebRMUtils {
|
||||
|
||||
public static RMProject createRmProjectFromWebProject(@NotNull DBPProject project) {
|
||||
RMProject rmProject = new RMProject();
|
||||
rmProject.setId(project.getId());
|
||||
rmProject.setName(project.getName());
|
||||
rmProject.setDescription(project.getDescription());
|
||||
rmProject.setType(parseProjectNameUnsafe(rmProject.getId()).getType());
|
||||
return rmProject;
|
||||
}
|
||||
|
||||
public static RMProjectName parseProjectName(@NotNull String projectId) throws DBException {
|
||||
if (CommonUtils.isEmpty(projectId)) {
|
||||
throw new DBException("Project id is empty");
|
||||
}
|
||||
return parseProjectNameUnsafe(projectId);
|
||||
}
|
||||
|
||||
public static RMProjectName parseProjectNameUnsafe(String projectId) {
|
||||
String prefix;
|
||||
String name;
|
||||
int divPos = projectId.indexOf("_");
|
||||
if (divPos < 0) {
|
||||
prefix = RMProjectType.USER.getPrefix();
|
||||
name = projectId;
|
||||
} else {
|
||||
prefix = projectId.substring(0, divPos);
|
||||
name = projectId.substring(divPos + 1);
|
||||
}
|
||||
return new RMProjectName(prefix, name);
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.websocket;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.websocket.WSConstants;
|
||||
import org.jkiss.dbeaver.model.websocket.event.session.WSAbstractSessionEvent;
|
||||
|
||||
public class WSServerNotificationEvent extends WSAbstractSessionEvent {
|
||||
|
||||
public static final String EVENT_ID = "cb_server_notification";
|
||||
private String title;
|
||||
private String message;
|
||||
private WSServerNotificationEventType notificationType;
|
||||
|
||||
public WSServerNotificationEvent(
|
||||
@Nullable String title,
|
||||
@Nullable String message,
|
||||
@NotNull WSServerNotificationEventType notificationType
|
||||
) {
|
||||
super(EVENT_ID, WSConstants.TOPIC_USER_NOTIFICATION);
|
||||
this.title = title;
|
||||
this.message = message;
|
||||
this.notificationType = notificationType;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public WSServerNotificationEventType getNotificationType() {
|
||||
return notificationType;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user