Compare commits

..
Author SHA1 Message Date
kseniaguzeeva c814017566 Merge branch 'devel' into dbeaver/pro#5476-task-scheduler-refactor 2025-10-27 16:02:04 +03:00
AinurandDaria Marutkina 4ab47583ad dbeaver/pro#7017 limit max idle time for anonymous user (#3846)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-10-27 11:38:34 +01:00
sergeyteleshevandEvgenia 37cfecbf91 [CB-5925] Charts - add export option (image) (#3819)
* dbeaver/pro#5078 adds chart export

* adds ExportImageDialog and reuses it across the app

* uses canvas2html for downloading images

* Revert "uses canvas2html for downloading images"

This reverts commit b59374cf85.

* moves transformClassesToStyles to helpers

* transforms styling attributes to computed styles

* adds file name

* fixes translations

* fixes styles for png image so it do not exports in black&white mode with incorrect sizing

* fixes styles for svg export

* uses domToImage library to save chart with legend

* cleanup

* moves helpers back to d3js diagrams

* cleanup

* uses html-to-image instead of dom-to-image

* cleanup

* pr fixes

* replaces the lib

* fixes svg so it is not blur

* validation for image urls

* cleanup

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-10-27 17:09:23 +08:00
046659672c dbeaver/pro#6956 redesign cli tools (#3830)
* dbeaver/pro#6956 redesign cli tools

* dbeaver/pro#6956 help per command

* dbeaver/pro#6956 refactor some commands to options

* dbeaver/pro#6982 Top level commands title fix

* dbeaver/pro#6956 fixes and more hidden options

* dbeaver/pro#6956 fix auth context

---------

Co-authored-by: serge-rider <serge@dbeaver.com>
Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
2025-10-24 18:30:41 +02:00
kseniaguzeeva 2a3a94608c Merge branch 'devel' into dbeaver/pro#5476-task-scheduler-refactor 2025-10-24 17:32:15 +03:00
Ainur bf52e23793 dbeaver/pro#7022 do not throw error if team with the same name exists (#3806) 2025-10-24 13:16:03 +00:00
Ainur 4a2ce73a9c dbeaver/pro#7181 collect internal db properties on graphql call (#3845) 2025-10-24 13:09:27 +00:00
serge-rider b73b707b7f Merge remote-tracking branch 'origin/devel' into devel 2025-10-24 08:59:26 +02:00
serge-rider 57461bd8be dbeaver/pro#7259 Nullability annotations 2025-10-24 08:56:48 +02:00
Sychev AndreyandEvgenia 71bebb32dd dbeaver/pro#7218 fix: value panel don't change the value of a cell (#3848)
The problem was that In ReactCodemirror, the updateListener calls onChange whenever the document changes and it's not marked as a remote transaction, but in value panel, when we switch changes, the new value coming to the panel was considered as the user change.
So, update listener were calling onChange from TextValuePresentation which calls editAction.set() and changes the real cell value.

In order to fix it, this commit mark programmatic updates by dispatching transaction annotations together with a change.

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-10-24 14:48:56 +08:00
ShadelessFox d810edf89d Merge branch 'devel' of https://github.com/dbeaver/cloudbeaver into dbeaver/pro#5476-task-scheduler-refactor 2025-10-23 14:11:54 +02:00
Sychev Andrey 28cade86aa dbeaver/pro#6026 feat: merge value panel tabs in one group (#3829)
* dbeaver/pro#6026 feat: merge value panel tabs in one group

move all text representations to the first tab level
specific representation is used if present (boolean, image)
auto type detection is used only for text

* dbeaver/pro#6026 refactor: remove unused CSS files for TextValuePresentation

* dbeaver/pro#6026 refactor: remove automatic state update in ValuePanel component

* dbeaver/pro#6026 refactor: update content type handling in ValuePanel

Move all the content type determination logic into the hook
Simplify the component

* dbeaver/pro#6026 refactor: consolidate content type handling in useAutoContentType hook

* dbeaver/pro#6026 fix: show scroll on tabs in value panel when overflow
2025-10-23 17:04:41 +08:00
MashaKorax e64d5ff92c Merge branch 'devel' into dbeaver/pro#5476-task-scheduler-refactor 2025-10-21 19:01:59 +02:00
Daria Marutkina 92186dd332 dbeaver/pro#7168 Update README file (#3842) 2025-10-20 12:16:08 +02:00
ShadelessFox 36be366c64 dbeaver/pro#5476 Move ZONE_ID_UTC to StandardConstants 2025-10-16 18:46:58 +02:00
sergeyteleshev 860a9f55f2 dbeaver/pro#7183 updates happy-dom to 20.0.2 (#3835) 2025-10-17 00:42:19 +08:00
dbeaver-devops 754c8d562d dbeaver/pro#7168 25.2.4 version bump (#3827) 2025-10-14 19:26:58 +02:00
Alexey PotsetsuevandEvgenia ed0e92cdd6 dbeaver/pro#6294 fix: dnd styles (#3824)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-10-14 23:45:33 +08:00
Serge Rider 9267e65300 dbeaver/pro#7142 Check node children for null (#3825) 2025-10-14 13:41:53 +02:00
Sychev AndreyandDaria Marutkina 4e3ec7cbf4 dbeaver/pro#7001 feat: add combobox cancel feature (#3801)
* dbeaver/pro#7001 feat: add combobox cancel feature

* dbeaver/pro#7001 refactor: reuse handleSelect for clear action

also changed cancel button styles

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-10-14 18:38:55 +08:00
AinurandDaria Marutkina 6978ae5355 dbeaver/pro#7058 fix error log for fs request (#3818)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-10-14 09:52:56 +02:00
Ruslan MusaevandEvgenia 3986debe8f dbeaver/cloudbeaver#3484 add ldap referral handling (#3817)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-10-14 09:40:55 +02:00
sergeyteleshev 38cfb0a690 [CB] Fix critical vulnerability with happy-dom (#3821)
* dbeaver/pro#7132 updates deps

* Revert "dbeaver/pro#7132 updates deps"

This reverts commit 522c56660c.

* updates only happy-dom
2025-10-13 22:48:36 +08:00
682a387564 dbeaver/pro#7080 refactor: grid API (#3799)
* dbeaver/pro#7080 refactor: grid API

* chore: cleanup after review

* chore: fix grid view action dependencies

* fix: build

* fix: update null checks and improve type safety in data handling

* fix: improve cache handling in useResultTraceDetails hook

* fix: correct cache reference in useResultTraceDetails function

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-10-13 19:30:54 +08:00
AinurandDaria Marutkina de0dee29ed dbeaver/pro#7019 grant connection permissions fix (#3807)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-10-10 15:02:47 +02:00
Sychev AndreyandDaria Marutkina 3a1ae15ecb dbeaver/pro#7026 fix: update cancel action to revert only selected el… (#3805)
* dbeaver/pro#7026 fix: update cancel action to revert only selected elements

* dbeaver/pro#7026 fix: bind revert action on esc

It was a misunderstanding at first, but esc is actually a revert action, not cancel action.

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-10-09 19:30:28 +08:00
Ainurandkseniaguzeeva 8cf064e845 dbeaver/pro#4038 add additional validation for servlets (#3779)
* dbeaver/pro#4038 add additional validation for servlets

* dbeaver/pro#4038 normalize paths for validation

* dbeaver/pro#4038 add fs update permission

---------

Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2025-10-09 11:18:49 +02:00
Ainur ec1b455f4d dbeaver/pro#7082 use same ws connection after relogin fix (#3803) 2025-10-09 10:52:07 +02:00
Ruslan Musaev 683d85c8d6 dbeaver/pro#7089 fix migration hotfix (#3814) 2025-10-08 16:04:34 +02:00
sergeyteleshev 8066904d4d [CB] The text of the settings in the navigator tree began to appear in bold (#3812)
* dbeaver/pro#6943 fixes bold title styles for switch

* build fix
2025-10-08 21:53:45 +08:00
Alexey Potsetsuev 7ea46ea2c5 dbeaver/pro#7111 fix: doubled hot keys handler (#3804) 2025-10-08 16:07:23 +08:00
Alexander Skoblikov 8aee24cf1c dbeaver/pro#6982 sql execution in dbvr (#3802) 2025-10-07 18:21:27 +02:00
sergeyteleshevandDaria Marutkina 5d83098d38 dbeaver/pro#6947 moves secrets parameters to a separate island (#3789)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-10-06 21:20:22 +08:00
Daria Marutkina 3e855c3afd dbeaver/pro#7052 Update README file (#3800) 2025-10-06 10:29:52 +02:00
Serge Rider 9c50589586 dbeaver/pro#7054 Nullable annotations 2025-10-03 14:27:52 +02:00
Serge Riderandmr-anton-t f99c2e3ebd dbeaver/pro#7054 Check null in remote clients (#3792)
* dbeaver/pro#7054 Check null in remote clients

* dbeaver/pro#7054 Do not verify nullable params

* dbeaver/pro#7054 Fix nullable

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-10-03 14:25:43 +02:00
Serge Rider e141575785 dbeaver/pro#2787 Annotations 2025-10-03 12:09:14 +02:00
b93b85f622 dbeaver/pro#6802 Update SQL schema config file paths (#3790)
* dbeaver/pro#6802 Update SQL schema config file paths

* dbeaver/pro#6802 Fix build

* dbeaver/pro#6802 Fix build

---------

Co-authored-by: Ivan Gagarkin <gagarkin@Ivans-MacBook-Pro.local>
Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
2025-10-03 11:51:02 +02:00
Serge Rider 267a753d87 Merge branch 'devel' of https://github.com/dbeaver/cloudbeaver into devel 2025-10-02 15:53:17 +02:00
Ruslan Musaev 6cea8cd64f dbeaver/pro#7060 fix connection read only (#3796) 2025-10-02 15:53:08 +02:00
serge-rider b630cb6791 dbeaver/pro#7052 25.2.3 version bump 2025-09-30 19:44:41 +02:00
a9fd8fb909 [CB] Migrate context menu to aria kit #6756 (#3760)
* dbeaver/pro#6756 adds Menu to ui-kit, core-blocks + uses it in connection properties form

* allows to render any items not only strings

* dbeaver/pro#6756 adds context menu (has freeze on click)

* cleanup

* reverts object menu cell usage of new menu (problems with portal - infinite rerender)

* ui fixes

* reverts context menu logic

* adds hover styles

* cleanup

* removes MenuField

* adds combobox instead of PropertyValueSelector

* adds ability to have custom value for comboboxes + uses inside driver properties

* allows to revert values in combobox

* adjusts combobox api with correctly working custom value

* cleanup

* adds documentation for the Menu

* fix: combobox and driver properties

* chore: remove unused component

* chore: cleanup styles

* chore: add value tooltip

* fix: remove default value from combobox

* fix: tooltips

* build fix

* reverts old behavior with default value for combobox

* removes button styles + adds max width for the items

* Revert "reverts old behavior with default value for combobox"

This reverts commit 2a92c00f32.

* removes double focus

* fixes placeholder so it is not undefined string

* fixes value placeholder + moves title to input

* build fix

* fixes border behavior

* adds default value for property items

* fix: improve combobox types

* fix: use default value

* adds layers to InputField and moves border-offset-layout

* fix: eslint

* Revert "adds layers to InputField and moves border-offset-layout"

This reverts commit 272baafab6.

* reverts styles fixes for double border

* cleanup

* adds medium size to ui kit

---------

Co-authored-by: Alexey Potsetsuev <wrouds@gmail.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-09-30 19:14:23 +02:00
Ruslan Musaev 9a5c3f67ab dbeaver/pro#7044 fix anon shared projects (#3788) 2025-09-30 18:13:20 +02:00
AinurandDaria Marutkina 21b024f375 dbeaver/pro#6529 use separate bundle for lang3 (#3765)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-09-30 12:34:43 +02:00
e2339f3666 dbeaver/pro#5963 add expandable fields to the json view (#3775)
Co-authored-by: Alexey Potsetsuev <wrouds@gmail.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-09-30 18:18:22 +08:00
Sychev Andreyandmr-anton-t 2fbb71fb96 dbeaver/pro#6586 fix: combobox disclosure size (#3787)
* dbeaver/pro#6586 fix: combobox disclosure size

By default, this component has 1em height and width and thats why it looked different in different components. Pixels values will provide consistency across different environments.

* dbeaver/pro#6586 refactor: set parent's font size instead of svg dimensions

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-09-30 02:25:42 +08:00
sergeyteleshevandmr-anton-t 75e9da4705 [CB] Grid - unable to resize multiple columns in wide tables (#3783)
* dbeaver/pro#6667 patches react-data-grid columns widths on cloudbeaver side

* fixes: auto resize columns should not be resized + column resize do not triggers resize of other oversized columns

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-09-30 02:25:23 +08:00
Ruslan Musaev 6e3bc7f63f dbeaver/pro#5908 limit the number of anon sessions (#3724) 2025-09-29 13:30:49 +00:00
11a7dc26dc dbeaver/pro#5848 api for renaming projects (#3600)
* dbeaver/pro#5848 api for renaming projects

* dbeaver/pro#5848 ci: update stylus dependency to version 0.0.1-security

* dbeaver/pro#5848 feat: add gql mutation and method for project info update

* dbeaver/pro#5848 use project settings for renaming

* dbeaver/pro#5848 add events for project renaming

* dbeaver/pro#5848 use display name

* dbeaver/pro#5848 add rename to desktop te

* dbeaver/pro#5848 fix validating project name

* dbeaver/pro#5848 validate project name on creation

* dbeaver/pro#5848 fix adding description and add event to graphql

* dbeaver/pro#5848 fix events for non-admin users

* dbeaver/pro#5848 feat: mark project as outdated on update

* dbeaver/pro#5848 handle rm project update in nav tree

---------

Co-authored-by: Sychev Andrey <nukemoore@gmail.com>
Co-authored-by: Sychev Andrey <44414066+SychevAndrey@users.noreply.github.com>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-09-29 13:20:46 +02:00
AinurandDaria Marutkina 3e9b0bfdd8 dbeaver/pro#6532 api for object settings (#3720)
* dbeaver/pro#6532 api for object settings

* dbeaver/pro#6532 save object settings on user side

* dbeaver/pro#6532 fix adding project settings

* dbeaver/pro#6532 remove redundant code

* dbeaver/pro#6532 rename gql functions

* dbeaver/pro#6532 rename method from add to set

* dbeaver/pro#6532 test fix

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-09-29 12:38:15 +02:00
Sychev AndreyandDaria Marutkina 73eaaa31f9 dbeaver/pro#6568 fix: layout jump in notifications portal (#3784)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-09-29 17:52:12 +08:00
574f893e6e dbeaver/pro#6639 fix: don't render table error component when no error (#3710)
* dbeaver/pro#6639 fix: don't render table error component when no error

We used to render TableError component all the time no matter if we have an error or not, just hiding this component visually. This approach breaks accessibility because Table error component was still reachable from a keyboard or a screen reader. This fix completely removes the node from DOM if the error is not visible.

* dbeaver/pro#6639 fix: change tabindex of TableError component

previous fix rendered this component only if error is present. That fix was wrong because we want to always keep that component in DOM to animate it properly in case of an error. That why we decided to just change the tabindex to -1 when there is no error to exclude it from tab path.
This fix is also adds aria-hidden attribute to hide this component completely from the accessibility tree when it's not visible.

* dbeaver/pro#6639 style: indent

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-09-29 17:51:36 +08:00
Alexander SkoblikovandMashaKorax 10876f1a62 dbeaver/pro#6772 import license cli (#3774)
Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
2025-09-29 10:59:27 +02:00
54b5c12550 dbeaver/pro#6403 api for default catalog and schema (#3768)
* dbeaver/pro#6403 api for default catalog and schema

* dbeaver/pro#6403 add default schema and catalog to gql

---------

Co-authored-by: Ainur <ainur.iagudin@dbeaver.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Ainur <59531286+yagudin10@users.noreply.github.com>
2025-09-26 12:27:19 +02:00
Ainur 69f2a57be5 dbeaver/pro#6716 rename server scripts (#3762) 2025-09-26 10:16:35 +02:00
Ainurandmr-anton-t 4f7c7ba808 dbeaver/pro#6656 create web connection info objects in a session project (#3751)
* dbeaver/pro#6656 create web connection info objects in a session project

* dbeaver/pro#6656 fix data source connection test

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-09-26 10:09:48 +02:00
Sychev AndreyandDaria Marutkina 25247321b8 dbeaver/pro#5471 [CB] Add shortcuts to tooltips data editor toolbar (#3750)
* dbeaver/pro#5471 fix: "Delete" data viewer action tooltip

* dbeaver/pro#5471 refactor: "merge" save and save as script actions

We used to have two buttons with different shortcuts for saving SQL as a script(first save) and saving already created script (updating). On UI they had the same icon and were more or less identical for the user. We decided to use the same shortcut and labels for both buttons (mod + S).

* dbeaver/pro#5471 feat: adds Find and Comment shortcuts to the shortcuts list

* dbeaver/pro#5471 refactor: use common l10n keys for save/undo/redo

* dbeaver/pro#5471 refactor: use key bindings API for Delete action in TableView

* dbeaver/pro#5471 refactor: use key bindings API for add, duplicate and revert actions

* dbeaver/pro#5471 fix: extra import

* dbeaver/pro#5471 refactor: don't use a symbol for delete shortcut

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-09-25 18:18:17 +08:00
Sychev Andrey 72975abef0 dbeaver/pro#6142 fix: can't expand nodes using filter (#3773)
If folder node had not been loaded before the filter is used, we would get into situation that filtering manually expanding this node without children and we would loose the ability to load them by expanding by click(our main flow is to load on expand).

This fix removes the auto expanding on filtering. Now it works similarly to the main navigation tree with filters
2025-09-25 18:17:23 +08:00
Alexey Potsetsuev 1fadf5009d dbeaver/pro#6968 fix: eslint (#3777) 2025-09-25 17:48:58 +08:00
Alexander Skoblikovandkseniaguzeeva 23090e286a dbeaver/pro#5999 do not link shared credentials with users (#3748)
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2025-09-24 16:55:51 +02:00
Alexey Potsetsuevandmr-anton-t 1cacf6a5cc dbeaver/pro#6980 fix: elements ordering (#3770)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-09-24 21:57:11 +08:00
d0e86745f1 dbeaver/pro#6520 fix: header cell layout (#3759)
* dbeaver/pro#6520 fix: header cell layout

* dbeaver/pro#6520 refactor: table cell always shows 2 rows

* dbeaver/pro#6520 fix: use two rows grid only if a table has any description

---------

Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-09-24 16:30:59 +08:00
Alexander SkoblikovandMashaKorax 97d179a103 dbeaver/pro#6229 execute sql via cli (#3721)
* dbeaver/pro#6229 execute sql via cli

* dbeaver/pro#6229 review fix

* dbeaver/pro#6229 update run scripts

---------

Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
2025-09-23 18:48:26 +02:00
sergeyteleshev 37b8716aee dbeaver/pro#6935 replaces sass-embedded with sass (#3771) 2025-09-24 00:34:16 +08:00
Alexey Potsetsuevandsergeyteleshev d1c2d6547f dbeaver/pro#6968 feat: add storybook (#3767)
* dbeaver/pro#6968 feat: add storybook

* chore: remove duplicate

* fix: ladle

* chore: remove storybook onboarding

* adds task to run storybook

---------

Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2025-09-23 21:02:07 +08:00
4f53ece164 dbeaver/pro#6327 refactor: sql editor & vqb (#3742)
* dbeaver/pro#6327 refactor: sql editor & vqb

* fix: state sync

* fix: segment resolution

* chore: add comment

* refactor: remove old code, add todo about problem

* fix: yarn.lock

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-09-23 18:45:45 +08:00
Alexey Potsetsuevandmr-anton-t 0fe432f45c dbeaver/pro#6783 refactor: plugins bundles (#3761)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-09-23 18:37:24 +08:00
Sychev AndreyandDaria Marutkina 58b8eafc7b dbeaver/pro#6892 fix: letter spacing in combobox item (#3747)
Make the same as in combobox input

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-09-23 18:26:35 +08:00
alexandDaria Marutkina cdb4c7e1fc dbeaver/pro#6924 pass selectedValue to provider (#3752)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-09-23 00:03:25 +08:00
alexandDaria Marutkina 3a2d4f807d dbeaver/pro#5752 add boolean component to metadata editor (#3754)
* dbeaver/pro#5752 add boolean component to metadata editor

* dbeaver/pro#5752 fix linter

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-09-22 23:34:43 +08:00
39ea1e2b7f dbeaver/pro#4742 add tree context menu provider (#3745)
* dbeaver/pro#4742 add tree context menu provider

* dbeaver/pro#4742 add useTreeMenu hook

* dbeaver/pro#4742 revert hook renaming

---------

Co-authored-by: Alexey Potsetsuev <wrouds@gmail.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-09-22 19:43:27 +08:00
Daria Marutkina da7d4eef19 dbeaver/pro#6916 Update README file (#3764) 2025-09-22 11:50:45 +02:00
alexandDaria Marutkina d23ab441ff dbeaver/pro#6305 fix input label hover blinking (#3753)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-09-22 17:07:14 +08:00
serge-rider 220c59ed01 dbeaver/pro#6952 Use ALL-DEFAULT modules mode 2025-09-21 11:40:36 +02:00
serge-rider eb0f3db330 dbeaver/pro#6759 CLI model 2025-09-20 07:36:00 +02:00
Serge Rider 3babafaecd dbeaver/pro#6759 dbvr app stub and config 2025-09-19 14:49:59 +02:00
serge-rider 4765629537 dbeaver/pro#6934 XML parser annotations 2025-09-19 08:25:22 +02:00
Serge RiderandMatvey16 534526e5e4 dbeaver/pro#6775 AI functions API (#3712)
Co-authored-by: Matvey16 <82543000+Matvey16@users.noreply.github.com>
2025-09-17 12:25:48 +02:00
dbeaver-devops 2d6ebd91f1 dbeaver/pro#6916 25.2.2 version bump (#3749) 2025-09-16 19:05:20 +02:00
Ainur e6e92f0bb3 dbeaver/pro#5614 api for expert settings props (#3727) 2025-09-16 13:51:38 +00:00
5a394e648d [CB-6198] Migrate secrets ui (public) from backend to frontend part (#3701)
* dbeaver/pro#5170 adds refactoring boilerplate

* dbeaver/pro#6198 remove redundant code about secret configuration

* moves secrets into provider properties

* renames optionsExtendedForm to ProviderPropertiesExtendedForm

* build fix

* dbeaver/pro#5170 set external params fix

* moves providerProperties container to ConnectionFormService

* cleanup

* adds empty arrays to connection secret reset state

* truncates select items and select itself

* Revert "truncates select items and select itself"

This reverts commit 56c943b5a0.

---------

Co-authored-by: Ainur <ainur.iagudin@dbeaver.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Ainur <59531286+yagudin10@users.noreply.github.com>
2025-09-16 15:05:28 +02:00
sergeyteleshevandmr-anton-t e466462269 [CB-4362] Add trim to all inputs in forms (#3687)
* dbeaver/pro#4696 adds zod schema parsing for form parts

* adds validation error to context

* adds schema parsing for UserFormInfoPart & ConnectionFormOptionsPart

* spreads RecordKeySchema across zod validations

* fixes form editing for connection info part

* Revert "spreads RecordKeySchema across zod validations"

This reverts commit 0e6719b32f.

* removes connection form errors during saving when empty fields

* reverts connection form options part parsing

* removes trimming from teams & authRole

* build fix

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-09-15 21:47:55 +08:00
sergeyteleshevandmr-anton-t 1c57e670c0 [CB] get back switch title bold styles (#3738)
* dbeaver/pro#6878 gets back switch bold title

* incapsulates field styles only

* moves all composes out of the layer

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-09-15 15:16:36 +08:00
alexandDaria Marutkina 3808af50c7 dbeaver/pro#5014 use debounce instead of throttle (#3734)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-09-15 00:14:56 +02:00
Alexander Skoblikov 390452d8a6 dbeaver/pro#6880 more correct origin parsing (#3741) 2025-09-12 15:27:00 +00:00
Alexey Potsetsuevandmr-anton-t b733f395d3 dbeaver/pro#6847 fix: elements order (#3729)
* dbeaver/pro#6847 fix: elements order

* fix: linting

* fix: actions order

* fix: actions order

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-09-12 23:21:54 +08:00
sergeyteleshev e31ae9a439 dbeaver/pro#6881 updates deps (#3743) 2025-09-12 19:57:54 +08:00
sergeyteleshevandDaria Marutkina 07f60c0034 [CB] Bump NPM dependencies (#3723)
* dbeaver/pro#5765 updates deps

* updates @wroud/vite-plugin-tsc

* updates @vitejs/plugin-react

* removes sass-embedded

* resolves sass to sass-embedded

* build fix

* linter fix

* handles empty json table edge case

* removes redundant translations

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-09-12 17:39:48 +08:00
sergeyteleshevandEvgenia ba32c12b9c [CB-5023] Add vizualization for focus on switchers (#3677)
* dbeaver/pro#4862 adds focus on switches

* fixes border radius

* less border-radius

* applies hack with borders and position to save border radius and offset

* removes variable

* fix: applies focus only for tab

* removes focus on ESC button

* removes onkeydown

* cleanup

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-09-11 17:32:33 +08:00
Ainur ca3ce9b79d dbeaver/pro#6786 load engine properties from object (#3732) 2025-09-11 08:38:04 +00:00
96d6dba83a dbeaver/pro#6644 update licence status after users decrease (#3711)
* dbeaver/pro#6644 update licence status after users decrease

* dbeaver/pro#6644 update licence status after user decrease

* dbeaver/pro#6644 update license status after users decrease

---------

Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
Co-authored-by: Ainur <59531286+yagudin10@users.noreply.github.com>
2025-09-11 08:20:14 +02:00
Ruslan MusaevandEvgenia 6ce5ef235c dbeaver/pro#6259 fix query logging for import (#3733)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-09-11 08:13:20 +02:00
a1404e3495 dbeaver/pro#6224 Add soring completion alphabetically for web (#3697)
* dbeaver/pro#6224 Add soring completion alphabetically for web

* dbeaver/pro#6224 Add soring completion alphabetically for web

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: Elizabeth <28875055+E1izabeth@users.noreply.github.com>
2025-09-10 23:44:11 +03:00
sergeyteleshev d4e2f2365a [CB] application freezes during loading on heavy resultsets (ORIGINAL FIX) (#3717)
* dbeaver/pro#6731 enables virtualization always

* removes default virtualization prop

* [CB] application freezes during loading on heavy resultsets (OPTIONAL FIXES) (#3715)

* dbeaver/pro#6731 adds some optimizations

* cleanup

* pr fixes

* cleanup

* removes optimizations

* linter fixes
2025-09-10 21:06:54 +08:00
ab813e21ab dbeaver/pro#5129 cb 6095 sql editor buttons shortcut labels are hardcoded (#3696)
* dbeaver/pro#5129 feat: use action item bindings API to get binding label

removes hardcoded shortcuts
adds formatting for shortcuts, uses OS-specific shortcuts

* dbeaver/pro#5129 feat: add example of manual using getBindingLabel function

* dbeaver/pro#5129 feat: use menu api for sql editor actions

* dbeaver/pro#5129 fix: lint async

* dbeaver/pro#5129 fix: remove wrong action icons

move private methods to the end of files

* dbeaver/pro#5129 refactor: use context via useDataContextLink hook

* dbeaver/pro#5129 refactor: DRY ACTIONS

* dbeaver/pro#5129 fix: DRY ACTIONS

* dbeaver/pro#5129 fix: action titles

* dbeaver/pro#5129 fix: DI after devel merge

* dbeaver/pro#5129 bonus: fix text content in dropdowns

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: Alexey Potsetsuev <wrouds@gmail.com>
2025-09-10 21:04:03 +08:00
AinurandDaria Marutkina c5190b2567 dbeaver/pro#6249 fix events for changing extensions (#3667)
* dbeaver/pro#6249 fix events for changing extensions

* dbeaver/pro#6249 send connection updated info event on rm side

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-09-10 14:33:34 +02:00
alexandEvgenia e896789cb0 dbeaver/pro#6155 do not set whereFilter on constraint change (#3702)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-09-10 18:39:02 +08:00
Serge Riderandmr-anton-t 60827d769a dbeaver/pro#6791 LDAP icon fix (#3714)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-09-09 12:36:29 +02:00
Alexey Potsetsuevandmr-anton-t 65876e9b4e dbeaver/pro#6744 fix: elements order (#3706)
* dbeaver/pro#6744 fix: elements order

* fix: fixate position of elements

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-09-04 02:40:01 +08:00
Alexander SkoblikovandMashaKorax 048206266d dbeaver/pro#5878 cli custom exit code (#3705)
Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
2025-09-03 10:49:12 +02:00
Alexey Potsetsuev f41b9d24fe dbeaver/pro#6662 chore: cleanup (#3713) 2025-09-02 18:41:49 +08:00
Daria MarutkinaandAnastasiya 03dabc0e1b dbeaver/pro#6715 Update README file (#3709)
* dbeaver/pro#6715 Update README file

* Update README.md

Co-authored-by: Anastasiya <45152336+LonwoLonwo@users.noreply.github.com>

* Update README.md

* Update README.md

Co-authored-by: Anastasiya <45152336+LonwoLonwo@users.noreply.github.com>

---------

Co-authored-by: Anastasiya <45152336+LonwoLonwo@users.noreply.github.com>
2025-09-01 13:03:44 +02:00
Daria Marutkina 56946742c2 dbeaver/pro#6715 Update README file (#3708) 2025-09-01 11:21:23 +02:00
serge-rider e1e4ae0ba6 dbeaver/pro#6773 Move ldap icon to cloudbeaver 2025-09-01 07:29:53 +02:00
Alexey Potsetsuevandmr-anton-t 80f258b561 dbeaver/pro#6744 refactor: migration to @wroud/di (#3704)
* dbeaver/pro#6744 refactor: migration to `@wroud/di`

* tests: enable settings tests

* chore: update syntax

* fix: dynamic theme detection

* fix: skip not stable test

* fix: skip whole test

* fix: make theme optional

* fix: page reload

* tests: skip unstable test

* fix: remove optional

* fix: load system theme

* fix: prevent the system theme from always loading despite not being selected

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-08-29 18:22:11 +08:00
Alexander SkoblikovandDaria Marutkina ac48e13039 dbeaver/pro#6703 use local host address by default (#3695)
* dbeaver/pro#6703 use local host address by default

* dbeaver/pro#6703 rename method

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-08-27 12:20:18 +02:00
3877e914c4 dbeaver/pro#6462 error instead of redirect for gql requests (#3690)
* dbeaver/pro#6462 error instead of redirect for gql requests

* feat: force redirect on supportedHosts change

* fix: replace redirect with warning message

* fix: prevent active host removal

* fix: error message and ignore when on IP address

---------

Co-authored-by: Alexey Potsetsuev <wrouds@gmail.com>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2025-08-26 15:01:25 +02:00
sergeyteleshev 564ceb7d82 dbeaver/pro#6708 adds @dbeaver/react-tests to packages to execute tests locally (#3698) 2025-08-25 20:42:44 +08:00
serge-rider 622b5133c2 dbeaver/pro#6715 25.2.1 version bump 2025-08-22 18:55:11 +02:00
Alexey Potsetsuev 376575efce dbeaver/pro#5902 fix: settings override and aliases resolution (#3693)
* dbeaver/pro#5902 fix: settings override and aliases resolution

* fix: import
2025-08-22 18:45:25 +02:00
980e169ee1 [CB] Ability to change multiserver licenses users/roles from UI (#3598)
* dbeaver/pro#6311 removes multi instance limits on frontend side (allows to create/delete/disable users in users and provisioning)

* dbeaver/pro#6311 add flag to server config

* build fix

* reverts all in favor of licenseUsersModificationEnabled

* dbeaver/pro#6311 review fixes

* adds extended serverConfig

* dbeaver/pro#6311 fix disabled users

---------

Co-authored-by: Ruslan <pupkinvasya7000@gmail.com>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-08-22 18:24:18 +02:00
AinurandEvgenia e9cbaeadd9 dbeaver/pro#6695 no web session found error log fix (#3692)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-08-22 18:23:54 +02:00
Ruslan MusaevandEvgenia 66764292c9 Dbeaver/pro#6326 fix incorect paramters parsing (#3685)
* dbeaver/pro#6326 fixes

* dbeaver/pro#6326 fixes

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-08-22 13:29:34 +02:00
2e2a61a330 dbeaver/pro#6692 do not load settings until authorized (#3688)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-08-22 19:11:10 +08:00
Ruslan MusaevandDaria Marutkina edda6d8db3 dbeaver/pro#5921 fix postgre npe (#3686)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-08-22 12:28:02 +02:00
c9f4ddd382 Dbeaver/pro#6622 permission fixes after demo (#3680)
* dbeaver/pro#6622 preference fixes after the demo

* dbeaver/pro#6622 fixes

---------

Co-authored-by: Sychev Andrey <44414066+SychevAndrey@users.noreply.github.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-08-22 18:09:49 +08:00
alexandDaria Marutkina fb0bdc1017 dbeaver/pro#6556 change Alert structure (#3676)
* dbeaver/pro#6556 change Alert structure

* dbeaver/pro#6556 wrap in text

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-08-22 18:09:00 +08:00
serge-rider 123dc7777c dbeaver/pro#6655 Code style + NPE checks 2025-08-22 09:29:42 +02:00
alexandEvgenia 2963610498 dbeaver/pro#4966 handle long names case in menus (#3683)
* dbeaver/pro#4966 handle long names case in menus

* dbeaver/pro#4966 use nested selector

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-08-22 04:33:35 +08:00
sergeyteleshevandEvgenia d1e07be46f [CB] Long text in the Configuration ID dropdown is not truncated and overlaps other UI elements (#3682)
* dbeaver/pro#6642 truncates all select items

* adds title for render field selects

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-08-21 19:16:29 +08:00
043204002f dbeaver/pro#5902 fix: settings resolution (#3673)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-08-21 19:15:30 +08:00
Alexey PotsetsuevandEvgenia c8c82c7f3e dbeaver/pro#6565 fix: prevent requests on session expire (#3678)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-08-21 12:36:35 +08:00
a0944850b1 dbeaver/pro#6605 fix: reset filters on logout (#3675)
* dbeaver/pro#6605 fix: reset filters on logout

This commit adds onLogout handler in DataViewerTabService which disposes opened tabs on logout.

Previously we didn't handle that at all and model was persisted in the memory after logout. On next login, if there was any filters, we would try to send them to backend and got an error, because backend doesn't support initial requests(without resultId) with filters.

The main logic of a handler repeats the already existed onDisconnect handler logic, which was extracted to handleTabsCleanup method. The difference between handlers is only in a set of tabs(onLogout we dispose all of them, onDisconnect - only tabs of a selected connection) and ability to interrupt the process.

* Revert "dbeaver/pro#6605 fix: reset filters on logout"

This reverts commit d124b444c7.

* dbeaver/pro#6605 fix: add onLoad Handler to ObjectViewerTabService

* Update webapp/packages/plugin-object-viewer/src/ObjectViewerTabService.ts

Co-authored-by: Alexey Potsetsuev <wrouds@gmail.com>

---------

Co-authored-by: Alexey Potsetsuev <wrouds@gmail.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-08-21 12:35:41 +08:00
Ruslan Musaev 31841b7720 dbeaver/pro#6687 revert audit loging (#3674)" (#3684)
This reverts commit 55c776f3e4.
2025-08-20 15:13:55 +00:00
serge-rider a2695f3102 Merge branch 'devel' of https://github.com/dbeaver/cloudbeaver into devel 2025-08-20 15:14:28 +02:00
serge-rider 3c7c1ffc8e dbeaver/pro#6681 TE desktop errors during launch fix 2025-08-20 15:13:42 +02:00
Ruslan Musaev 55c776f3e4 dbeaver/pro#6326 fixes (#3674) 2025-08-20 10:38:55 +00:00
Alexander Skoblikov 8a4d88e721 dbeaver/pro#6622 preference fixes after the demo (#3670) revert (#3679)
This reverts commit 426fd40c82.
2025-08-20 10:33:11 +00:00
Ruslan Musaev 426fd40c82 dbeaver/pro#6622 preference fixes after the demo (#3670) 2025-08-20 10:16:41 +00:00
alexandDaria Marutkina a547b85ef2 dbeaver/pro#6598 open panel on tab select (#3662)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-08-20 16:43:51 +08:00
Sychev AndreyandDaria Marutkina e32e6eac39 dbeaver/pro#6325 fix: set cursor to the beginning of the file when opened (#3669)
In previous ticket CB-5555 we fixed cursor position on undo/redo actions by adding a check that if selection was not set. In that case we put the cursor to the end of inserted text. Due to previous fixes in CB-4134 we don't set a selection if cursor don't change it's position and initially it's 0. So our check from 5555 is being fired on initially opened script and cursor goes to the end.

To avoid that situation, this solution introduces a new isInitialScriptOpening flag that is used to check the first insertion.

When initially opening a script, the cursor will stay at the position where we explicitly set it (e.g., position 0).
After the first actual content change, the flag isInitialScriptOpening gets set to false.
Subsequent changes will then follow the normal cursor behavior (moving to end when there are changes but no explicit cursor position).

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-08-19 23:39:13 +08:00
Sychev AndreyandEvgenia 901669b802 dbeaver/pro#6472 fix: restore focus after menu actions and on syncFocus (#3657)
* dbeaver/pro#6472 fix: restore focus after menu actions and on syncFocus

* dbeaver/pro#6472 refactor: revert changes with using restoreFocus function from context

* dbeaver/pro#6472 fix: delete row from a context menu works as with a hotkey

set to null and revert also keep focus in place

* Revert "dbeaver/pro#6472 fix: delete row from a context menu works as with a hotkey"

This reverts commit f54abf3956.

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-08-19 18:03:41 +08:00
alexandEvgenia 69d7a25fec dbeaver/pro#6411 check for includes also (#3655)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-08-19 18:03:03 +08:00
Sychev AndreyandDaria Marutkina 576028b367 dbeaver/pro#6253 [CB] [users] default team should be marked and read only (#3658)
* dbeaver/pro#6253 fix: set disable state for checkbox respecting readonly

There was a bug in UiKitCheckbox props which rewrites disabled state using only disabled prop.

* dbeaver/pro#6253 fix: remove redundant readonly property in Options

First of all, Radio doesn't support readonly attribute. Moreover, we don't render this RadioGroup when condition in readonly prop is true.

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-08-19 18:02:39 +08:00
Alexey PotsetsuevandDaria Marutkina 9276777c3e dbeaver/pro#6600 fix: connections sync (#3661)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-08-19 18:02:13 +08:00
Sychev AndreyandDaria Marutkina 4ff45220ee dbeaver/pro#6557 [CB] Dropdown for roles is too small (#3663)
* dbeaver/pro#6557 refactor: remove redundant width: 100%

There are a few reasons why we don't need this property:
0. It's already present in base Select styles
1. Select component is flex container by default, i.e. takes all available place
2. No evidences that it is needed

* dbeaver/pro#6557 fix: user filters selects

Simplifies layout and set min width for selects

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-08-19 18:01:41 +08:00
Alexey Potsetsuev 62ad5a61c9 dbeaver/pro#6652 fix: version set (#3671) 2025-08-18 22:21:49 +08:00
Daria Marutkina 587a9e58e1 dbeaver/pro#6594 Update README file (#3668) 2025-08-18 10:46:45 +02:00
Alexey Potsetsuev 4401cfe92f dbeaver/pro#6617 fix: remove default value for key parameter in useResourceTracker (#3664)
* dbeaver/pro#6617 fix: remove default value for key parameter in useResourceTracker

* fix: re-subscribe on resource

* fix: ensure subscription removed
2025-08-15 23:27:34 +08:00
Alexey Potsetsuev 59e33e8c48 dbeaver/pro#6592 fix: resources loading (#3659) 2025-08-14 11:12:42 +08:00
serge-rider d16c7d4502 dbeaver/pro#6594 25.2.0 version bump 2025-08-12 20:23:19 +02:00
1670 changed files with 17412 additions and 27010 deletions
+3 -22
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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",
+14 -5
View File
@@ -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": [
@@ -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>
+35 -44
View File
@@ -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.
-9
View File
@@ -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"
}
-32
View File
@@ -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
View File
@@ -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`));
-7
View File
@@ -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
View File
@@ -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
}
-5
View File
@@ -1,5 +0,0 @@
# This file is automatically generated by @yarnpkg/sdks.
# Manual changes might be lost!
integrations:
- vscode
-32
View File
@@ -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
View File
@@ -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`));
-7
View File
@@ -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
+3 -11
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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"]
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
set -Eeo pipefail
if [[ $# -eq 0 ]] ;
then
set -- "$@" -help
fi
set -- "$@" -cli-mode
source run-cloudbeaver-server.sh "$@"
+3 -3
View File
@@ -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
-8
View File
@@ -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 = 无自动断开连接
+1 -1
View File
@@ -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";
@@ -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;
}
}
@@ -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;
}
@@ -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,
@@ -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));
}
}
@@ -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 {
@@ -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;
@@ -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();
@@ -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);
}
}
}
@@ -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
@@ -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();
}
}
@@ -100,4 +100,6 @@ public interface ServletApplication extends DBPApplication {
default void getStatusInfo(Map<String, Object> infoMap) {
}
boolean isAnonymousAccessEnabled();
}
@@ -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();
}
@@ -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,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());
}
}
}
@@ -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);
}
}
@@ -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();
@@ -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();
@@ -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());
}
);
}
}
@@ -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;
@@ -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;
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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();
}
}
@@ -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;
@@ -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);
@@ -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;
}
}
@@ -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);
}
}
@@ -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");
}
@@ -137,6 +137,7 @@ public class WebSessionWorkspace implements DBPWorkspace {
clearProjects();
}
@Nullable
@Override
public DBPImage getResourceIcon(DBPAdaptable resourceAdapter) {
return null;
@@ -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();
}
@@ -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";
}
@@ -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;
}
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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