Compare commits

...
Author SHA1 Message Date
Alexey Potsetsuev 9d71a77a48 dbeaver/pro#7111 fix: doubled hot keys handler (#3810) 2025-10-08 10:09:50 +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
Alexey PotsetsuevandEvgenia a51765f516 dbeaver/pro#6189 feat: group extra tools (#3653)
* dbeaver/pro#6189 feat: group extra tools

* fix: context menu size

* fix: make context menu more compact

* fix: context menu shortcuts

* fix: change color for shortcuts in context menu

* fix: codestyle

* fix: revert more tools

* fix: submenu arrow position

* fix: show output

* fix: upload / download tooltips

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-08-12 19:03:56 +02:00
Alexander SkoblikovandDaria Marutkina 27d8f4c011 dbeaver/pro#6548 skip domain for ip addresses (#3647)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-08-12 19:03:25 +02:00
0bb7803565 [CB-6024] More granular permissions on data export (#3607)
* dbeaver/pro#5108 create plugin-data-editor

* registers some data editor settings only for CE product

* renames plugin

* moves disable import setting to public settings

* adds localizations

* fixes references

* adds comments for registration

* renames Data Viewer -> Data Editor

* dbeaver/pro#5108 add permissions

* maps settings to permissions

* fixes default settings for data import

* adds permissions redirect for default team + removes permission tab if admin full access provided

* moves settings resolvers to original place

* build fix

* fixes mapping so it actually works

* cleanup

* dbeaver/pro#5108 permissions

* dbeaver/pro#5108 more granular permissions

* dbeaver/pro#5108 more granular permissions

* dbeaver/pro#5108 review fixes

* dbeaver/pro#5108 review fixes

* changes permissions settings

* fixes mapping

---------

Co-authored-by: Ruslan <pupkinvasya7000@gmail.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Alexey Potsetsuev <wrouds@gmail.com>
2025-08-12 14:38:40 +02:00
0727974c64 dbeaver/pro#6118 (#3652)
* dbeaver/pro#6118 feat: show driver description as an item's title in new connection dialog

* dbeaver/pro#6118 feat: show driver description as an item's title in new connection dialog

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: Ainur <59531286+yagudin10@users.noreply.github.com>
2025-08-12 14:13:09 +02:00
Serge Rider cabacfb475 dbeaver/pro#6569 SQL script execute fix (#3650)
* dbeaver/pro#6569 SQL script execute fix

* CB-5369 Do not use deprecated methods
2025-08-12 12:04:45 +02:00
fea2d78ef2 dbeaver/cloudbeaver#3495 filter empty folder in global project (#3636)
* dbeaver/cloudbeaver#3495 filter empty folder in global project

* fixes loading nested folder-nodes during connection creation

* Revert "fixes loading nested folder-nodes during connection creation"

This reverts commit 514bb0238b.

* uses more correct solution for preloading folders

* build fix

* cleanup

* fixes deletion of the connections so it removes empty folders

* fix: outdate only one node

---------

Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
Co-authored-by: Alexey Potsetsuev <wrouds@gmail.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-08-12 12:03:21 +02:00
Alexey Potsetsuev ddf94efef0 dbeaver/pro#6016 fix: script sync (#3651) 2025-08-12 13:01:26 +08:00
Sychev Andreyandmr-anton-t c3be008ba2 dbeaver/pro#6283 Add Combobox component (#3597)
* dbeaver/pro#6283 feat: add simple Combobox implementation

It has fuzzy search inside to filter items.

* dbeaver/pro#6283 feat: implement Combobox component with search functionality and update related files

* dbeaver/pro#6283 refactor: remove unused onChange prop

* dbeaver/pro#6283 style: update imports in Combobox component

* dbeaver/pro#6283 feat: update styles, add popoverClassName prop

* dbeaver/pro#6283 refactor: keep Combobox composable for better control

* dbeaver/pro#6283 refactor: SRP in Combobox, split provider and components

* dbeaver/pro#6283 feat: implement advanced search

see AdvancedSearch example in stories

* dbeaver/pro#6283 refactor: use strict comparison

* dbeaver/pro#6283 refactor: replace fuzzy search with standard string search

* dbeaver/pro#6283 refactor: simplify Combobox component

* dbeaver/pro#6283 feat: add value formatter for ComboboxInput

It might be useful for cases when value is not in user friendly format. Combobox doesn't support such transformations from the box

Updated examples, added valueFormatter to advanced search, added MultiSelect example

* dbeaver/pro#6283 feat: sync styles with select

* dbeaver/pro#6283 fix: formatter

* dbeaver/pro#6283 fix: respect the user's reduce motion preference

* dbeaver/pro#6283 refactor: simplify component

remove search context,
use some built-in features for input value manipulations and for auto selection of the first item

* dbeaver/pro#6283 refactor: use tailwind for Field styles

* dbeaver/pro#6283 fix: should open all options on first interaction with any value

* dbeaver/pro#6283 refactor: strip Combobox component

Due to difficulties with items and search value managing in Combobox using only native combobox store, it was decided to move all that logic to CloudBeaver combobox component.

This commit removes all the logic from UIKit Combobox and partially restores it in Cloudbeaver Combobox

* dbeaver/pro#6283 feat: show no results when no items

* dbeaver/pro#6283 feat: restore input to the last valid value

* dbeaver/pro#6283 refactor: simplify restore input logic

* dbeaver/pro#6283 refactor: reset input value on blur

* dbeaver/pro#6283 refactor: don't sent input value on item click

* dbeaver/pro#6283 refactor: remove getComputed for filteredItems

getComputed will not work as expected for not primitive values( for example, for arrays)

* dbeaver/pro#6283 refactor: remove getComputed for filteredItems

* dbeaver/pro#6283 style: update z-index for  ComboboxPopover component

our backdrop component has 999 z-index(weird?), we need more to show popover in modals

* dbeaver/pro#6283 feat: put CB Combobox near the component, fix layers issue

* dbeaver/pro#6283 fix: radio title wrap

* dbeaver/pro#6283 fix: use the same letter spacing as in other inputs

* dbeaver/pro#6283 fix:combobox item styles

* dbeaver/pro#6283 fix: clear value on click

this fix is for situation when user started to type a word, found an option and clicked on it. Previously we didn't clear value and just setSelectedValue on click. This fix clears value as it happens onBlur and we use the value from selectedItem.

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-08-11 23:07:28 +08:00
Sychev AndreyandEvgenia 7acbbfe4ec dbeaver/pro#5968 refactor: update dark theme styles (#3639)
* dbeaver/pro#5968 refactor: update dark theme styles

introduces new theme variable link-color which with a color which is used for coloring anchor elements in our app
adjust this color in dark theme to have better contrast with a background, use this color in secondary button for the same reasons

* dbeaver/pro#5968 fix: adjust dark theme color values for improved contrast

* dbeaver/pro#5968 fix: update negative color in dark theme

* dbeaver/pro#5968 fix: update negative color in dark theme

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-08-11 18:03:31 +08:00
7e0ad33fa4 dbeaver/pro#6243 add permissions for executing and updating results (#3634)
* dbeaver/pro#6243 add permissions for executing and updating results

* feat: support special method for dataset execution

* fix: disable sql editor screen when permission is not granted

---------

Co-authored-by: Wroud <wrouds@gmail.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-08-11 11:55:51 +02:00
Alexander SkoblikovandDaria Marutkina d9d30a0e35 dbeaver/pro#6561 fix origin in provider (#3648)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-08-11 10:35:08 +02:00
Ruslan MusaevandDaria Marutkina 94e70f2d16 dbeaver/pro#6284 read validate function from pref (#3643)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-08-11 09:30:45 +02:00
9ee4701fd0 dbeaver/pro#6298 refactor: useResource (#3635)
* dbeaver/pro#6298 refactor: useResource

* fix: CachedResource isOutdated logic

* fix: linting

* fix: resource isOutdated tracking

---------

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-08-11 13:35:33 +08:00
Serge RiderandMashaKorax 9395659666 dbeaver/pro#6543 AI scripts processing (#3642)
Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
2025-08-08 17:22:22 +02:00
Alexander Skoblikovandkseniaguzeeva 13a6dc9da7 dbeaver/pro#6216 fix missing errors (#3645)
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2025-08-08 16:09:02 +02:00
36071c244e dbeaver/pro#6449 make default panel width bigger (#3633)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: Ainur <59531286+yagudin10@users.noreply.github.com>
2025-08-08 15:10:46 +02:00
3e722ce3b7 5679 cb execute gql requests via cli (#3644)
* dbeaver/pro#5679 wip

* dbeaver/pro#5679 wip

---------

Co-authored-by: Serge Rider <serge@jkiss.org>
Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
2025-08-08 11:41:21 +02:00
Sychev AndreyandDaria Marutkina 3c9ddcdee0 dbeaver/pro#6061 [CB] Welcome Page Improvements (#3638)
* dbeaver/pro#6061 fix: remove extra comma in ru l10n

* dbeaver/pro#6061 fix: make Cell component accessible

* dbeaver/pro#6061 fix: use proper product doc page

* dbeaver/pro#6061 fix: Cell component layout

* dbeaver/pro#6061 refactor: split Cell component interface for better dx

* Revert "dbeaver/pro#6061 refactor: split Cell component interface for better dx"

This reverts commit 812414f0f1.

* Revert "dbeaver/pro#6061 fix: make Cell component accessible"

This reverts commit 09aff9b08a.

* dbeaver/pro#6061 refactor: restrict using onClick on Cell component

force to use composition instead

* dbeaver/pro#6061 feat: generate a link to specific product version docs

* dbeaver/pro#6061 fix: use arg in all l10n files

* dbeaver/pro#6061 refactor: extract version handling logic for documentation URLs

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-08-08 17:35:06 +08:00
Sychev AndreyandDaria Marutkina 0b589734e8 dbeaver/pro#6537 fix: render SelectPopover in portal by default (#3641)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-08-07 20:24:30 +08:00
serge-rider b8db43573b Merge remote-tracking branch 'origin/devel' into devel 2025-08-07 12:26:07 +02:00
serge-rider 7c1535a95a Revert "5679 cb execute gql requests via cli (#3599)"
This reverts commit 0a50d841d0.
2025-08-07 12:20:17 +02:00
sergeyteleshevandmr-anton-t 900be41734 [CB] Bump NPM dependencies (#3628)
* dbeaver/pro#5764 updates current dependencies

* migrates to zod 4

* updates deps

* lint fix

* pr fixes

* reverts connection changes

* reverts connection changes 2

* fix types for screen service

* reverts changes for screen service

* reverts string boolean refactor

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-08-07 18:03:31 +08:00
0a50d841d0 5679 cb execute gql requests via cli (#3599)
* dbeaver/pro#5679 wip

* dbeaver/pro#5679 wip

---------

Co-authored-by: Serge Rider <serge@jkiss.org>
Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
2025-08-07 11:01:06 +02:00
sergeyteleshevandDaria Marutkina 00b214b8bc [CB] Rename the Enable HTTPs mode on server config (#3631)
* dbeaver/pro#6469 corrects enable https mode translation and switch position in server config form

* adds link for proxy configuration article

* fixes punctuation for ru locale

* updates description translations

* removes extra dot in ru locale

* adds advanced documentation link

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-08-06 16:12:04 +08:00
Sychev AndreyandEvgenia ea6c960353 dbeaver/pro#6444 refactor: fix Radio component (#3632)
removes RadioGroupContext(radio group/radio use their own provider)
removes all the logic of state changing from Radio component since it's creating a wrong understanding of how these components should be used together with RadioGroup. Radio still can be controllable, but it should be used in most scenarios only with RadioGroup which is controlling the state.
simplify styles by removing extra class

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-08-06 16:11:13 +08:00
Viktor Kirst 44c0cfe856 Dbeaver/pro#2951 Show message about disabling user (#3624)
* dbeaver/pro#2951 Emit WSUserDisabledEvent after disabling a user

* dbeaver/pro#2951 Close user sessions on WSUserDisabledEvent

* dbeaver/pro#2951 Unify session termination logic for deleted and disabled users
2025-08-05 18:32:27 +02:00
Ainur 470d64718b dbeaver/pro#6214 invert feature and enable by default (#3615) 2025-08-05 13:43:10 +00:00
Evgenia 0c0669f0c7 dbeaver/pro#6457 Update README file (#3630) 2025-08-04 19:43:21 +02:00
2a012d600c dbeaver/pro#6382 feat: add bind session to ip combobox in server config (#3626)
* dbeaver/pro#6382 feat: add bind session to ip combobox in server config

* dbeaver/pro#6382 use select for bindSessionToIP setting instead of combobox

* dbeaver/pro#6382 bind session ip to dbeaver's hosts

* dbeaver/pro#6382 feat: add placeholder for bindToIp setting

* dbeaver/pro#6382 fix: defaultServerConfig

* dbeaver/pro#6382 refactor: remove l10n from CE

* dbeaver/pro#6382 debug log

* dbeaver/pro#6382 remove debug logs

---------

Co-authored-by: Sychev Andrey <nukemoore@gmail.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-08-04 16:37:56 +02:00
Alexander Skoblikovandkseniaguzeeva 0b2370d414 dbeaver/pro#6467 ignore default ports (#3622)
* dbeaver/pro#6467 ignore default ports

* dbeaver/pro#6467 check port

* dbeaver/pro#6467 fix ip host parsing

* dbeaver/pro#6467 debug log

* dbeaver/pro#6467 remove debug logs

---------

Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2025-08-04 13:47:18 +02:00
sergeyteleshev a0def9f3ae [CB] write unit tests (#3621)
* dbeaver/pro#6436 initializes unit tests for common-react

* installs common-react in the scripts

* adds @dbeaver/react-tests

* setups @dbeaver/react-test into tests-runner

* implements react-testing library into @dbeaver/react-tests

* removes unused deps

* adds test for frontend build github action
2025-08-04 18:45:22 +08:00
alex 13cf2911e0 dbeaver/pro#6470 remove pre commit hook dep from deps list (#3623) 2025-07-30 21:55:41 +08:00
Serge Rider c0c3594633 dbeaver/pro#6457 25.1.5 version bump 2025-07-29 21:41:44 +03:00
180ec65dd9 dbeaver/pro#5552 new gql api (#3579)
* dbeaver/pro#5552 new gql api

* dbeaver/pro#5552 fixes

* adds available hosts fields

* adds warnings for the https mode

* adds default host for domain manager + fixes some translations

* reverts server URL for domain manager

* fixes description for available hosts

* separates available hosts by new line in textarea

* removes dots

* removes http, https from examples for available hosts field

* dbeaver/pro#5552 validate request's host

* dbeaver/pro#5552 review fixes

* dbeaver/pro#5552 update default configs

* refactors availableHosts -> supportedHosts

* review fix

* dbeaver/pro#5552 fix build

* refactor: supportedHosts - array -> string

* cleanup

* reverts serverConfigurationInfoForm interfaces

* refactors secureCookies -> forceHttps (frontend)

* dbeaver/pro#5552 rename secureCookies param to forceHttps

* dbeaver/pro#5552 rename secureCookies param to forceHttps

* dbeaver/pro#5552 rename secureCookies param to forceHttps

* dbeaver/pro#5552 enable https for old deployments

* fixes translations, readonly state & hosts examples for TE

* dbeaver/pro#5552 fixes

* dbeaver/pro#5552 redirect from http to https

* dbeaver/pro#5552 redirect to https

* dbeaver/pro#5886 use scheme from origin

* dbeaver/pro#5552 exclude special servlets

* dbeaver/pro#5552 saml test

* dbeaver/pro#5552 remove slashes from hostname

---------

Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2025-07-29 19:04:14 +02:00
79d5702744 dbeaver/pro#5957 save lock state in sm controller (#3576)
* dbeaver/pro#5957 save lock state in sm controller

* dbeaver/pro#5957 fix read config

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-07-29 18:10:32 +02:00
603b94c551 dbeaver/pro#5241 [CB] Screen reader does not read some fields in the application (#3585)
* dbeaver/pro#5241 fix: input doesn't create label wrapper if label is not provided

* dbeaver/pro#5241 fix: link label to input inside InputFieldBase

* dbeaver/pro#5241 fix: make required field asterisk invisible for assistive technologies

* dbeaver/pro#5241 fix: link FieldLabel to textarea with generated id and add aria-label for accessibility

even with linked label, voiceOver doesn't pronounce label when focusing textarea, that's why aria-label is used there

* dbeaver/pro#5241 refactor: force aria-label for input field

* dbeaver/pro#5241 refactor: change role for snackbar

aria-live polite works with aria regions which initially empty and then dynamically populated with some data. If we want assistive technologies read initial content, we need to use alert role.

also hide icon from assistive technologies (no additional information from that)

* dbeaver/pro#5241 refactor: remove aria-label from input

use native htmlFor

* dbeaver/pro#5241 refactor: remove aria-label from Textarea component

---------

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-07-29 23:09:05 +08:00
1528 changed files with 11131 additions and 17426 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
+12 -2
View File
@@ -26,7 +26,17 @@
"group": "test",
"args": ["test"],
"options": {
"cwd": "${workspaceFolder}/common-typescript"
"cwd": "${workspaceFolder}/webapp/common-typescript"
}
},
{
"label": "Run Tests Common React",
"type": "shell",
"command": "yarn",
"group": "test",
"args": ["test"],
"options": {
"cwd": "${workspaceFolder}/webapp/common-react"
}
},
{
@@ -123,7 +133,7 @@
"type": "shell",
"command": "yarn",
"options": {
"cwd": "${workspaceFolder}/common-react/@dbeaver/ui-kit"
"cwd": "${workspaceFolder}/webapp/common-react/@dbeaver/ui-kit"
},
"args": ["docs"]
}
@@ -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>
+23 -42
View File
@@ -25,56 +25,37 @@ You can see a live demo of CloudBeaver here: https://demo.cloudbeaver.io
## Changelog
### 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 Viewer;
- 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.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.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,29 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { type ComponentPropsWithRef } from 'react';
import './Input.css';
import clsx from 'clsx';
import { componentProviderWrapper } from '../componentProviderWrapper.js';
export interface InputProps extends Omit<ComponentPropsWithRef<'input'>, 'size'> {
size?: 'small' | 'medium' | 'large' | 'xlarge';
label?: React.ReactNode;
}
export const InputBase: React.FC<InputProps> = function Input({ size, className, ...props }) {
const classNameToApply = clsx(`dbv-kit-input`, `dbv-kit-input--${size ?? 'medium'}`, className);
return (
<label className="dbv-kit-input-wrapper">
{props.label && <div className={clsx('dbv-kit-input__title', props.required && 'dbv-kit-input__title--required')}>{props.label}</div>}
<input className={classNameToApply} {...props} />
</label>
);
};
export const Input = componentProviderWrapper('Input', InputBase);
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"
}
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -1,7 +1,7 @@
{
server: {
serverPort: "${CLOUDBEAVER_WEB_SERVER_PORT:8978}",
secureCookies: "${CLOUDBEAVER_SECURE_COOKIES:true}",
forceHttps: "${CLOUDBEAVER_FORCE_HTTPS:false}",
contentRoot: "web",
driversLocation: "drivers",
@@ -10,6 +10,7 @@
rootURI: "${CLOUDBEAVER_ROOT_URI:/}",
serviceURI: "/api/",
supportedHosts: [],
productSettings: {
# Global properties
@@ -27,7 +28,7 @@
},
expireSessionAfterPeriod: "${CLOUDBEAVER_EXPIRE_SESSION_AFTER_PERIOD:1800000}",
bindSessionToIp: "${CLOUDBEAVER_BIND_SESSION_TO_IP:true}",
bindSessionToIp: "${CLOUDBEAVER_BIND_SESSION_TO_IP:disable}",
develMode: "${CLOUDBEAVER_DEVEL_MODE:false}",
enableSecurityManager: false,
+2 -6
View File
@@ -5,10 +5,8 @@ echo "Build static content"
mkdir -p ./cloudbeaver/web
cd ../../cloudbeaver/common-typescript
cd ../../cloudbeaver/webapp
yarn install --immutable
cd ../webapp
yarn install --immutable
cd ./packages/product-default
yarn run bundle
@@ -17,9 +15,7 @@ if [[ "$?" -ne 0 ]] ; then
echo 'Application build failed'; exit $rc
fi
cd ../../../common-typescript
yarn test
cd ../webapp
cd ../../
yarn test
if [[ "$?" -ne 0 ]] ; then
+1 -1
View File
@@ -1,6 +1,6 @@
lefthook: |
cd webapp
yarn lefthook
yarn dlx --quiet lefthook
output: false
pre-commit:
commands:
@@ -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.82.qualifier
Bundle-Release-Date: 20250804
Bundle-Version: 1.0.85.qualifier
Bundle-Release-Date: 20250922
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.82-SNAPSHOT</version>
<version>1.0.85-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -29,8 +29,12 @@ public interface DBWConstants {
String PERMISSION_CONFIGURATION_MANAGER = RMConstants.PERMISSION_CONFIGURATION_MANAGER;
String PERMISSION_PRIVATE_PROJECT_ACCESS = "private-project-access";
String PERMISSION_SECRET_MANAGER = "secret-manager";
String PERMISSION_SQL_RESULT_UPDATE = "sql-result-update";
String PERMISSION_SQL_EXECUTE_QUERY = "sql-execute-query";
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";
String PERMISSION_EDIT_STRUCTURE = "edit-meta";
String PERMISSION_EDIT_DATA = "edit-data";
@@ -36,4 +36,6 @@ public interface DBWFeatureSet {
boolean isEnabled();
boolean isEnabledByDefault();
}
@@ -17,6 +17,7 @@
package io.cloudbeaver;
import io.cloudbeaver.model.session.WebSession;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
@@ -34,12 +35,11 @@ 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;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -48,12 +48,19 @@ import java.util.stream.Collectors;
* We need to filter some data sources in case of inaccessibility (not enough permissions).
*/
public class WebGlobalProjectRegistryProxy implements DBPDataSourceRegistry, DataSourcePersistentRegistry, DBPDataSourceRegistryCache {
@NotNull
private final WebSession webSession;
@NotNull
private final DataSourceFilter dataSourceFilter;
@NotNull
private final DataSourceRegistry<?> dataSourceRegistry;
public WebGlobalProjectRegistryProxy(@NotNull DataSourceRegistry<?> dataSourceRegistry, @NotNull DataSourceFilter filter) {
public WebGlobalProjectRegistryProxy(
@NotNull WebSession webSession,
@NotNull DataSourceRegistry<?> dataSourceRegistry,
@NotNull DataSourceFilter filter
) {
this.webSession = webSession;
this.dataSourceRegistry = dataSourceRegistry;
this.dataSourceFilter = filter;
}
@@ -181,13 +188,30 @@ public class WebGlobalProjectRegistryProxy implements DBPDataSourceRegistry, Dat
@NotNull
@Override
public List<? extends DBPDataSourceFolder> getAllFolders() {
return dataSourceRegistry.getAllFolders();
if (webSession.hasPermission(DBWConstants.PERMISSION_ADMIN)) {
return dataSourceRegistry.getAllFolders();
}
Set<DBPDataSourceFolder> set = new LinkedHashSet<>();
for (DBPDataSourceContainer container : getDataSources()) {
DBPDataSourceFolder folder = container.getFolder();
while (folder != null) {
set.add(folder);
folder = folder.getParent();
}
}
return new ArrayList<>(set);
}
@NotNull
@Override
public List<? extends DBPDataSourceFolder> getRootFolders() {
return dataSourceRegistry.getRootFolders();
if (webSession.hasPermission(DBWConstants.PERMISSION_ADMIN)) {
return dataSourceRegistry.getRootFolders();
}
return getDataSources().stream()
.map(DBPDataSourceContainer::getFolder)
.filter(folder -> folder != null && folder.getParent() == null)
.toList();
}
@NotNull
@@ -354,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,
@@ -50,6 +50,7 @@ public class WebSessionGlobalProjectImpl extends WebSessionProjectImpl {
@Override
protected DBPDataSourceRegistry createDataSourceRegistry() {
return new WebGlobalProjectRegistryProxy(
webSession,
createRegistryWithCredentialsProvider(),
this::isDataSourceAccessible
);
@@ -29,13 +29,16 @@ 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.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 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;
public class WebSessionProjectImpl extends WebProjectImpl {
private static final Log log = Log.getLog(WebSessionProjectImpl.class);
@@ -194,40 +197,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);
@@ -56,7 +56,9 @@ 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 Map<String, Object> externalParameters;
private List<WebNetworkHandlerConfigInput> networkHandlersConfig;
private DBPDriverConfigurationType configurationType;
private String selectedSecretId;
@@ -71,7 +73,6 @@ public class WebConnectionConfig {
if (!CommonUtils.isEmpty(params)) {
connectionId = JSONUtils.getString(params, "connectionId");
driverId = JSONUtils.getString(params, "driverId");
readOnly = JSONUtils.getBoolean(params, "readOnly");
host = JSONUtils.getString(params, "host");
port = JSONUtils.getString(params, "port");
@@ -79,9 +80,6 @@ public class WebConnectionConfig {
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");
@@ -98,11 +96,22 @@ public class WebConnectionConfig {
mainPropertyValues = JSONUtils.getObjectOrNull(params, "mainPropertyValues");
providerProperties = JSONUtils.getObjectOrNull(params, "providerProperties");
defaultCatalogName = JSONUtils.getString(params, "defaultCatalogName");
defaultSchemaName = JSONUtils.getString(params, "defaultSchemaName");
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);
String configType = JSONUtils.getString(params, "configurationType");
configurationType = configType == null ? null : DBPDriverConfigurationType.valueOf(configType);
externalParameters = JSONUtils.getObjectOrNull(params, "externalParameters");
networkHandlersConfig = new ArrayList<>();
for (Map<String, Object> nhc : JSONUtils.getObjectList(params, "networkHandlersConfig")) {
@@ -219,6 +228,11 @@ public class WebConnectionConfig {
return mainPropertyValues;
}
@Property
public Map<String, Object> getExpertSettingsValues() {
return expertSettingsValues;
}
@Property
public Map<String, Object> getProviderProperties() {
return providerProperties;
@@ -248,4 +262,8 @@ public class WebConnectionConfig {
public String getDefaultSchemaName() {
return defaultSchemaName;
}
public Map<String, Object> getExternalParameters() {
return externalParameters;
}
}
@@ -417,6 +417,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();
@@ -0,0 +1,87 @@
package io.cloudbeaver.model;
import org.jkiss.code.NotNull;
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(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
return CommonUtils.toBoolean(!object.driver.isEmbedded(), true);
}
}
public static class AutoCommitFieldValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
@Override
public boolean isValidValue(WebExpertSettingsProperties object, 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(WebExpertSettingsProperties object, 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(WebExpertSettingsProperties object, 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(WebExpertSettingsProperties object, 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
@@ -36,6 +36,7 @@ public abstract class BaseWebAppConfiguration implements ServletAppConfiguration
protected boolean secretManagerEnabled;
protected boolean showReadOnlyConnectionInfo;
protected String[] enabledFeatures;
protected String[] disabledFeatures;
protected String[] disabledBetaFeatures;
@@ -43,6 +44,7 @@ public abstract class BaseWebAppConfiguration implements ServletAppConfiguration
this.plugins = new LinkedHashMap<>();
this.resourceManagerEnabled = true;
this.enabledFeatures = null;
this.disabledFeatures = new String[0];
this.disabledBetaFeatures = new String[0];
this.showReadOnlyConnectionInfo = false;
this.secretManagerEnabled = false;
@@ -53,6 +55,7 @@ public abstract class BaseWebAppConfiguration implements ServletAppConfiguration
this.defaultUserTeam = src.defaultUserTeam;
this.resourceManagerEnabled = src.resourceManagerEnabled;
this.enabledFeatures = src.enabledFeatures;
this.disabledFeatures = src.disabledFeatures;
this.disabledBetaFeatures = src.disabledBetaFeatures;
this.showReadOnlyConnectionInfo = src.showReadOnlyConnectionInfo;
this.secretManagerEnabled = src.secretManagerEnabled;
@@ -122,6 +125,16 @@ public abstract class BaseWebAppConfiguration implements ServletAppConfiguration
this.enabledFeatures = enabledFeatures;
}
@NotNull
@Override
public String[] getDisabledFeatures() {
return disabledFeatures;
}
public void setDisabledFeatures(@NotNull String[] disabledFeatures) {
this.disabledFeatures = disabledFeatures;
}
public boolean isShowReadOnlyConnectionInfo() {
return showReadOnlyConnectionInfo;
}
@@ -51,6 +51,16 @@ public interface ServletAppConfiguration {
return new String[0];
}
/**
* Returns disabled features.
*
* @return array of disabled feature IDs
*/
@NotNull
default String[] getDisabledFeatures() {
return new String[0];
}
default boolean isSupportsCustomConnections() {
return true;
}
@@ -20,7 +20,7 @@ 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;
@@ -31,7 +31,7 @@ public class CloudBeaverInstanceServer extends ApplicationInstanceServer<Applica
@NotNull
@Override
public CmdProcessResult handleCommandLine(@NotNull String[] args) {
public CliProcessResult handleCommandLine(@NotNull String[] args) {
CommandLine cmd = CloudBeaverCommandLine.getInstance().getCommandLine(args);
try {
return CloudBeaverCommandLine.getInstance().executeCommandLineCommands(
@@ -40,7 +40,7 @@ public class CloudBeaverInstanceServer extends ApplicationInstanceServer<Applica
false
);
} 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,21 @@
/*
* 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;
public interface CloudbeaverCliConstants {
String CLI_MODE = "cliMode";
}
@@ -21,10 +21,12 @@ package io.cloudbeaver.model.config;
*/
public class ConnectionBruteForceConfiguration {
private static final int DEFAULT_ERROR_ATTEMPTS_CHECK_MINUTES = 24 * 60;
private static final int BLOCK_TIME_IN_MINUTES = 60;
private static final int DEFAULT_MAX_CONNECT_ATTEMPTS = 5;
private boolean enabled = false;
private int maxFailedConnectAttempts = DEFAULT_MAX_CONNECT_ATTEMPTS;
private int errorAttemptsPeriodInMinutes = DEFAULT_ERROR_ATTEMPTS_CHECK_MINUTES;
private int blockTimeInMinutes = BLOCK_TIME_IN_MINUTES;
public boolean isEnabled() {
return enabled;
@@ -49,4 +51,12 @@ public class ConnectionBruteForceConfiguration {
public void setErrorAttemptsPeriodInMinutes(int errorAttemptsPeriodInMinutes) {
this.errorAttemptsPeriodInMinutes = errorAttemptsPeriodInMinutes;
}
public int getBlockTimeInMinutes() {
return blockTimeInMinutes;
}
public void setBlockTimeInMinutes(int blockTimeInMinutes) {
this.blockTimeInMinutes = blockTimeInMinutes;
}
}
@@ -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,6 +277,7 @@ public abstract class BaseLocalResourceController implements RMController {
DBPDataSourceRegistry registry = project.getDataSourceRegistry();
registry.moveFolder(oldPath, newPath);
registry.checkForErrors();
((DataSourcePersistentRegistry) registry).saveDataSources();
return null;
}
);
@@ -28,18 +28,26 @@ 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;
@@ -334,6 +342,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(
@@ -29,6 +29,7 @@ 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.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 +37,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;
@@ -122,6 +124,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);
@@ -137,6 +137,7 @@ public class WebSessionWorkspace implements DBPWorkspace {
clearProjects();
}
@Nullable
@Override
public DBPImage getResourceIcon(DBPAdaptable resourceAdapter) {
return null;
@@ -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();
@@ -23,6 +23,7 @@ import org.eclipse.core.runtime.IConfigurationElement;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.DBPImage;
import org.jkiss.dbeaver.model.impl.AbstractContextDescriptor;
import org.jkiss.utils.CommonUtils;
/**
* WebFeatureDescriptor
@@ -35,6 +36,7 @@ public class WebFeatureDescriptor extends AbstractContextDescriptor implements D
private final String label;
private final String description;
private final DBPImage icon;
private final boolean enabledByDefault;
public WebFeatureDescriptor(IConfigurationElement config)
{
@@ -43,6 +45,7 @@ public class WebFeatureDescriptor extends AbstractContextDescriptor implements D
this.label = config.getAttribute("label");
this.description = config.getAttribute("description");
this.icon = iconToImage(config.getAttribute("icon"));
this.enabledByDefault = CommonUtils.getBoolean(config.getAttribute("enabledByDefault"), false);
}
@NotNull
@@ -69,4 +72,9 @@ public class WebFeatureDescriptor extends AbstractContextDescriptor implements D
return ServletAppUtils.getServletApplication().getAppConfiguration().isFeatureEnabled(this.id);
}
@Override
public boolean isEnabledByDefault() {
return enabledByDefault;
}
}
@@ -68,4 +68,9 @@ public class WebServerFeatureDescriptor extends AbstractContextDescriptor implem
return true;
}
@Override
public boolean isEnabledByDefault() {
return false;
}
}
@@ -32,7 +32,9 @@ public class CBConstants {
public static final String PARAM_SERVER_PORT = "serverPort";
public static final String PARAM_SERVER_HOST = "serverHost";
public static final String PARAM_SERVER_NAME = "serverName";
public static final String PARAM_SECURE_COOKIES = "secureCookies";
public static final String PARAM_FORCE_HTTPS = "forceHttps";
public static final String PARAM_BIND_SESSION_TO_IP = "bindSessionToIp";
public static final String PARAM_SUPPORTED_HOSTS = "supportedHosts";
public static final String PARAM_SSL_CONFIGURATION_PATH = "sslConfigurationPath";
public static final String PARAM_CONTENT_ROOT = "contentRoot";
public static final String PARAM_SERVER_URL = "serverURL";
@@ -93,4 +95,7 @@ public class CBConstants {
public static final String ADMIN_AUTO_GRANT = "auto-grant";
public static final String HOST_LOCALHOST = "localhost";
public static final String HOST_127_0_0_1 = "127.0.0.1";
public static final String BIND_SESSION_ENABLE = "enable";
public static final String BIND_SESSION_DISABLE = "disable";
}
@@ -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.
@@ -17,8 +17,11 @@
package io.cloudbeaver.service;
import io.cloudbeaver.model.app.ServletApplication;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import java.util.List;
/**
* Servlet service
*/
@@ -28,4 +31,9 @@ public interface DBWServiceBindingServlet<APPLICATION extends ServletApplication
}
void addServlets(APPLICATION application, DBWServletContext servletContext) throws DBException;
@NotNull
default List<String> getExcludedServletPaths(@NotNull APPLICATION application) {
return List.of();
}
}
@@ -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.
@@ -37,6 +37,7 @@ import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.CommonUtils;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.*;
@@ -47,6 +48,11 @@ public class ServletAppUtils {
private static final String HEADER_FORWARDED_SCHEME = "X-Forwarded-Scheme";
private static final String HEADER_FORWARDED_HOST = "X-Forwarded-Host";
private static final Set<Integer> DEFAULT_PORTS = Set.of(
80,
443
);
private static final Log log = Log.getLog(ServletAppUtils.class);
@@ -286,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);
@@ -309,7 +315,26 @@ public class ServletAppUtils {
if (!origin.endsWith(rootUri)) {
origin = origin + "/" + rootUri + "/";
}
return removeSideSlashes(origin);
origin = removeSideSlashes(origin);
URI uri = URI.create(origin);
if (DEFAULT_PORTS.contains(uri.getPort())) {
try {
origin = new URI(
uri.getScheme(),
uri.getUserInfo(),
uri.getHost(),
-1,
uri.getPath(),
uri.getQuery(),
uri.getFragment()
).toString();
} catch (URISyntaxException e) {
log.error("Failed to create URI without port", e);
}
}
return origin;
}
public static String getRootUrlFromUri(@NotNull URI uri) {
@@ -328,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;
}
}
@@ -19,12 +19,15 @@ package io.cloudbeaver.utils;
import io.cloudbeaver.model.WebAsyncTaskInfo;
import io.cloudbeaver.model.session.WebSession;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.security.SMSubjectType;
import org.jkiss.dbeaver.model.websocket.WSConstants;
import org.jkiss.dbeaver.model.websocket.event.WSEvent;
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.model.websocket.event.permissions.WSSubjectPermissionEvent;
import org.jkiss.dbeaver.model.websocket.event.resource.WSResourceProperty;
import org.jkiss.dbeaver.model.websocket.event.resource.WSResourceUpdatedEvent;
import org.jkiss.dbeaver.model.websocket.event.session.WSSessionTaskInfoEvent;
@@ -195,4 +198,19 @@ public class WebEventUtils {
);
}
public static void addSubjectPermissionsUpdateEvent(
@NotNull String subjectId,
@NotNull SMSubjectType subjectType,
@Nullable String smSessionId,
@Nullable String userId
) {
var event = WSSubjectPermissionEvent.update(
smSessionId,
userId,
subjectType,
subjectId
);
ServletAppUtils.getServletApplication().getEventController().addEvent(event);
}
}
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Community Product
Bundle-SymbolicName: io.cloudbeaver.product.ce;singleton:=true
Bundle-Version: 25.1.4.qualifier
Bundle-Release-Date: 20250804
Bundle-Version: 25.2.1.qualifier
Bundle-Release-Date: 20250922
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.product.ce</artifactId>
<version>25.1.4-SNAPSHOT</version>
<version>25.2.1-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -2,8 +2,8 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Cloudbeaver Base JDBC drivers
Bundle-SymbolicName: io.cloudbeaver.resources.drivers.base;singleton:=true
Bundle-Version: 1.0.127.qualifier
Bundle-Release-Date: 20250804
Bundle-Version: 1.0.130.qualifier
Bundle-Release-Date: 20250922
Bundle-Vendor: DBeaver Corp
Bundle-ActivationPolicy: lazy
Automatic-Module-Name: io.cloudbeaver.resources.drivers.base
@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.resources.drivers.base</artifactId>
<version>1.0.127-SNAPSHOT</version>
<version>1.0.130-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,12 +3,13 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver CE Server
Bundle-SymbolicName: io.cloudbeaver.server.ce;singleton:=true
Bundle-Version: 25.1.4.qualifier
Bundle-Release-Date: 20250804
Bundle-Version: 25.2.1.qualifier
Bundle-Release-Date: 20250922
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Bundle-Activator: io.cloudbeaver.server.CBPlatformActivator
Require-Bundle: io.cloudbeaver.server;visibility:=reexport
Require-Bundle: io.cloudbeaver.server;visibility:=reexport,
com.google.guava
Export-Package: io.cloudbeaver,
io.cloudbeaver.model,
io.cloudbeaver.model.config,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server.ce</artifactId>
<version>25.1.4-SNAPSHOT</version>
<version>25.2.1-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,5 +1,5 @@
extend type ServerConfig {
serverURL: String!
serverURL: String! @deprecated
rootURI: String!
hostName: String! @deprecated(reason: "use container id instead")
containerId: String!
@@ -12,5 +12,11 @@ extend type ServerConfig {
redirectOnFederatedAuth: Boolean!
enabledAuthProviders: [ID!]!
passwordPolicyConfiguration: PasswordPolicyConfig! @since(version: "23.3.3")
"Mark all cookies as secure, recommend for HTTPS deployment configurations"
forceHttps: Boolean! @since(version: "25.1.3")
"List of allowed hosts to use the application - if empty, all hosts are allowed"
supportedHosts: [String!]! @since(version: "25.1.3")
"Indicates whether the session should be linked to the user's IP address"
bindSessionToIp: String! @since(version: "25.1.4")
}
@@ -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.
@@ -22,6 +22,8 @@ import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.utils.CommonUtils;
import java.util.List;
public class CBWebServerConfig extends WebServerConfig {
private final CBApplication<?> cbApp;
@@ -86,4 +88,21 @@ public class CBWebServerConfig extends WebServerConfig {
public PasswordPolicyConfiguration getPasswordPolicyConfiguration() {
return cbApp.getServerConfiguration().getSecurityManagerConfiguration().getPasswordPolicyConfiguration();
}
@Property
public boolean isForceHttps() {
return cbApp.getServerConfiguration().isForceHttps();
}
@NotNull
@Property
public List<String> getSupportedHosts() {
return cbApp.getServerConfiguration().getSupportedHosts();
}
@NotNull
@Property
public String getBindSessionToIp() {
return cbApp.getServerConfiguration().getBindSessionToIp();
}
}
@@ -16,6 +16,7 @@
*/
package io.cloudbeaver.model.config;
import com.google.common.net.InetAddresses;
import com.google.gson.annotations.SerializedName;
import io.cloudbeaver.auth.CBAuthConstants;
import io.cloudbeaver.model.app.WebServerConfiguration;
@@ -23,15 +24,17 @@ import io.cloudbeaver.server.CBConstants;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import java.util.HashMap;
import java.util.Map;
import java.net.URI;
import java.util.*;
public class CBServerConfig implements WebServerConfiguration {
private static final Log log = Log.getLog(CBServerConfig.class);
protected String serverURL;
protected boolean secureCookies;
@NotNull
protected List<String> supportedHosts = new ArrayList<>();
protected boolean forceHttps;
protected int serverPort = CBConstants.DEFAULT_SERVER_PORT;
private String serverHost = null;
private String serverName = null;
@@ -52,7 +55,8 @@ public class CBServerConfig implements WebServerConfiguration {
@SerializedName("database")
private WebDatabaseConfig databaseConfiguration = new WebDatabaseConfig();
private String staticContent = "";
private boolean bindSessionToIp = true;
@NotNull
private String bindSessionToIp = CBConstants.BIND_SESSION_DISABLE;
public CBServerConfig() {
this.securityManagerConfiguration = createSecurityManagerConfiguration();
@@ -184,11 +188,54 @@ public class CBServerConfig implements WebServerConfiguration {
return new SMControllerConfiguration();
}
public boolean isSecureCookies() {
return secureCookies;
public boolean isForceHttps() {
return forceHttps;
}
public boolean isBindSessionToIp() {
public void setForceHttps(boolean forceHttps) {
this.forceHttps = forceHttps;
}
@NotNull
public List<String> getSupportedHosts() {
return new ArrayList<>(supportedHosts);
}
public void setSupportedHosts(@NotNull Collection<String> availableHosts) {
LinkedHashSet<String> uniqueHosts = new LinkedHashSet<>();
for (String host : availableHosts) {
try {
if (!host.startsWith("http://") && !host.startsWith("https://")) {
host = "http://" + host; // Default to HTTP if no scheme is provided to avoid uri parse exception
}
URI uri = URI.create(host);
String hostName = uri.getHost() != null ? uri.getHost() : host;
if (InetAddresses.isInetAddress(hostName)) {
log.warn("Host URI contains an IP address: " + hostName + ", skipped.");
continue;
}
StringBuilder hostNameBuilder = new StringBuilder(hostName);
if (uri.getPort() > 0) {
hostNameBuilder.append(':')
.append(uri.getPort());
}
uniqueHosts.add(hostNameBuilder.toString());
} catch (Exception e) {
log.error("Invalid host URI: " + host, e);
}
}
this.supportedHosts.clear();
this.supportedHosts.addAll(uniqueHosts);
}
@NotNull
public String getBindSessionToIp() {
return bindSessionToIp;
}
public void setBindSessionToIp(@NotNull String bindSessionToIp) {
this.bindSessionToIp = bindSessionToIp;
}
}
@@ -27,6 +27,7 @@ import io.cloudbeaver.model.app.ServletSystemInformationCollector;
import io.cloudbeaver.model.config.CBAppConfig;
import io.cloudbeaver.model.config.CBServerConfig;
import io.cloudbeaver.registry.WebDriverRegistry;
import io.cloudbeaver.registry.WebFeatureRegistry;
import io.cloudbeaver.registry.WebServiceRegistry;
import io.cloudbeaver.server.jetty.CBJettyServer;
import io.cloudbeaver.service.ConnectionController;
@@ -102,7 +103,6 @@ public abstract class CBApplication<T extends CBServerConfig>
// Persistence
protected SMAdminController securityController;
private boolean configurationMode = false;
private String localHostAddress;
protected String containerId;
private final List<InetAddress> localInetAddresses = new ArrayList<>();
@@ -115,6 +115,8 @@ public abstract class CBApplication<T extends CBServerConfig>
private CBJettyServer jettyServer;
private final Map<String, Object> applicationContext = new ConcurrentHashMap<>();
public CBApplication() {
this.homeDirectory = new File(initHomeFolder());
}
@@ -214,19 +216,15 @@ public abstract class CBApplication<T extends CBServerConfig>
configurationMode = CommonUtils.isEmpty(getServerConfiguration().getServerName());
refreshDisabledDriversConfig();
try {
refreshServerConfiguration();
} catch (DBException e) {
log.error("Error refreshing server configuration", e);
return;
}
eventController.setForceSkipEvents(isConfigurationMode()); // do not send events if configuration mode is on
// Determine address for local host
localHostAddress = System.getenv(CBConstants.VAR_CB_LOCAL_HOST_ADDR);
if (CommonUtils.isEmpty(localHostAddress)) {
localHostAddress = System.getProperty(CBConstants.VAR_CB_LOCAL_HOST_ADDR);
}
if (CommonUtils.isEmpty(localHostAddress) || CBConstants.HOST_127_0_0_1.equals(localHostAddress) || "::0".equals(
localHostAddress)) {
localHostAddress = CBConstants.HOST_LOCALHOST;
}
Location instanceLoc = Platform.getInstanceLocation();
try {
@@ -331,6 +329,25 @@ public abstract class CBApplication<T extends CBServerConfig>
log.debug("Shutdown");
}
private void refreshServerConfiguration() throws DBException {
refreshDisabledDriversConfig();
refreshEnabledFeatures();
if (!isConfigurationMode()) {
flushConfiguration();
}
}
private void refreshEnabledFeatures() {
Set<String> enabledFeatures = new LinkedHashSet<>(Arrays.asList(getAppConfiguration().getEnabledFeatures()));
Set<String> disabledFeatures = new LinkedHashSet<>(Arrays.asList(getAppConfiguration().getDisabledFeatures()));
WebFeatureRegistry.getInstance().getWebFeatures().stream()
.filter(f -> f.isEnabledByDefault() && !disabledFeatures.contains(f.getId()))
.forEach(f -> enabledFeatures.add(f.getId()));
getAppConfiguration().setEnabledFeatures(enabledFeatures.toArray(new String[0]));
}
protected ServletSystemInformationCollector<?> createSystemInformationCollector() {
return new ServletSystemInformationCollector<>(this);
}
@@ -502,7 +519,7 @@ public abstract class CBApplication<T extends CBServerConfig>
}
public String getLocalHostAddress() {
return localHostAddress;
return getServerConfigurationController().getLocalHostAddress();
}
public List<InetAddress> getLocalInetAddresses() {
@@ -777,4 +794,13 @@ public abstract class CBApplication<T extends CBServerConfig>
public ServletSystemInformationCollector<?> getSystemInformationCollector() {
return systemInformationCollector;
}
public void addApplicationContextValue(@NotNull String key, @NotNull Object value) {
applicationContext.put(key, value);
}
@Nullable
public <T> T getApplicationContextValue(@NotNull String key) {
return (T) applicationContext.get(key);
}
}
@@ -44,8 +44,6 @@ import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.IOUtils;
import java.io.*;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -66,6 +64,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
protected final Path homeDirectory;
private final Map<String, String> externalProperties = new LinkedHashMap<>();
private final Map<String, Object> originalConfigurationProperties = new LinkedHashMap<>();
private String localHostAddress;
protected CBServerConfigurationController(@NotNull T serverConfiguration, @NotNull Path homeDirectory) {
super(homeDirectory);
@@ -80,6 +79,15 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
@Override
public void loadServerConfiguration(Path configPath) throws DBException {
log.debug("Using configuration [" + configPath + "]");
// Determine address for local host
localHostAddress = System.getenv(CBConstants.VAR_CB_LOCAL_HOST_ADDR);
if (CommonUtils.isEmpty(localHostAddress)) {
localHostAddress = System.getProperty(CBConstants.VAR_CB_LOCAL_HOST_ADDR);
}
if (CommonUtils.isEmpty(localHostAddress) || CBConstants.HOST_127_0_0_1.equals(localHostAddress) || "::0".equals(
localHostAddress)) {
localHostAddress = CBConstants.HOST_LOCALHOST;
}
if (!Files.exists(configPath)) {
log.error("Configuration file " + configPath + " doesn't exist. Use defaults.");
@@ -93,6 +101,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
log.debug("Runtime configuration [" + runtimeConfigPath.toAbsolutePath() + "]");
loadConfiguration(runtimeConfigPath);
}
// Set default preferences
PrefUtils.setDefaultPreferenceValue(DBWorkbench.getPlatform().getPreferenceStore(),
ModelPreferences.UI_DRIVERS_HOME,
@@ -107,7 +116,8 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
return CommonUtils.nullIfEmpty(envValue);
}
public void loadConfiguration(Path configPath) throws DBException {
@NotNull
public Map<String, Object> loadConfiguration(Path configPath) throws DBException {
CBAppConfig prevConfig = new CBAppConfig(appConfiguration);
Map<String, Object> configProps = readConfiguration(configPath);
try {
@@ -123,6 +133,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
mergeOldConfiguration(prevConfig);
patchConfigurationWithProperties(getServerConfiguration().getProductSettings());
return configProps;
}
protected void parseConfiguration(Map<String, Object> configProps) throws DBException {
@@ -159,12 +170,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
if (config.getServerURL() == null) {
String hostName = config.getServerHost();
if (CommonUtils.isEmpty(hostName)) {
try {
hostName = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
log.debug("Error resolving localhost address: " + e.getMessage());
hostName = CBConstants.HOST_LOCALHOST;
}
hostName = getLocalHostAddress();
}
config.setServerURL("http://" + hostName + ":" + config.getServerPort());
}
@@ -531,8 +537,20 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
copyConfigValue(
originServerConfig,
serverConfigProperties,
CBConstants.PARAM_SECURE_COOKIES,
serverConfig.isSecureCookies()
CBConstants.PARAM_FORCE_HTTPS,
serverConfig.isForceHttps()
);
copyConfigValue(
originServerConfig,
serverConfigProperties,
CBConstants.PARAM_SUPPORTED_HOSTS,
serverConfig.getSupportedHosts()
);
copyConfigValue(
originServerConfig,
serverConfigProperties,
CBConstants.PARAM_BIND_SESSION_TO_IP,
serverConfig.getBindSessionToIp()
);
var productConfigProperties = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
Map<String, Object> oldProductRuntimeConfig = JSONUtils.getObject(originServerConfig,
@@ -654,4 +672,8 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
public void validateFinalServerConfiguration() throws DBException {
}
public String getLocalHostAddress() {
return localHostAddress;
}
}
@@ -54,18 +54,15 @@ public class WSSubjectPermissionUpdatedEventHandler extends WSDefaultEventHandle
if (!super.isAcceptableInSession(activeUserSession, event)) {
return false;
}
var user = activeUserSession.getUserContext().getUser();
if (user == null) {
return false;
}
var subjectId = event.getSubjectId();
switch (event.getSubjectType()) {
case user:
return CommonUtils.equalObjects(user.getUserId(), subjectId);
case team:
return ArrayUtils.containsIgnoreCase(user.getTeams(), subjectId);
default:
return false;
}
return switch (event.getSubjectType()) {
case user -> CommonUtils.equalObjects(user.getUserId(), subjectId);
case team -> ArrayUtils.containsIgnoreCase(user.getTeams(), subjectId);
};
}
}
@@ -22,29 +22,24 @@ import org.jkiss.dbeaver.model.websocket.WSEventHandler;
import org.jkiss.dbeaver.model.websocket.event.WSAbstractEvent;
import org.jkiss.dbeaver.model.websocket.event.WSUserCloseSessionsEvent;
import org.jkiss.dbeaver.model.websocket.event.WSUserDeletedEvent;
import org.jkiss.dbeaver.model.websocket.event.WSUserDisabledEvent;
public class WSUserEventHandler<EVENT extends WSAbstractEvent> implements WSEventHandler<EVENT> {
@Override
public void handleEvent(@NotNull EVENT event) {
var sessionManager = CBApplication.getInstance().getSessionManager();
switch (event.getId()) {
case WSUserCloseSessionsEvent.ID:
if (event instanceof WSUserCloseSessionsEvent closeSessionsEvent) {
if (closeSessionsEvent.getSessionIds().isEmpty()) {
sessionManager.closeAllSessions(closeSessionsEvent.getSessionId());
} else {
sessionManager.closeSessions(closeSessionsEvent.getSessionIds());
}
}
break;
case WSUserDeletedEvent.ID:
if (event instanceof WSUserDeletedEvent userDeletedEvent) {
sessionManager.closeUserSession(userDeletedEvent);
}
break;
default:
break;
}
switch (event) {
case WSUserCloseSessionsEvent closeSessionsEvent -> {
if (closeSessionsEvent.getSessionIds().isEmpty()) {
sessionManager.closeAllSessions(closeSessionsEvent.getSessionId());
} else {
sessionManager.closeSessions(closeSessionsEvent.getSessionIds());
}
}
case WSUserDeletedEvent e -> sessionManager.closeUserSession(e);
case WSUserDisabledEvent e -> sessionManager.closeUserSession(e);
default -> { }
}
}
}
@@ -16,12 +16,13 @@
*/
package io.cloudbeaver.server.jetty;
import graphql.GraphQL;
import io.cloudbeaver.model.config.CBServerConfig;
import io.cloudbeaver.registry.WebServiceRegistry;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.server.CBConstants;
import io.cloudbeaver.server.graphql.GraphQLEndpoint;
import io.cloudbeaver.server.filters.ServerConfigurationTimeLimitFilter;
import io.cloudbeaver.server.graphql.GraphQLEndpoint;
import io.cloudbeaver.server.servlets.CBImageServlet;
import io.cloudbeaver.server.servlets.CBStaticServlet;
import io.cloudbeaver.server.servlets.WebStatusServlet;
@@ -30,10 +31,7 @@ import io.cloudbeaver.server.websockets.CBWebSocketServerConfigurator;
import io.cloudbeaver.service.DBWServiceBindingServlet;
import io.cloudbeaver.service.DBWServiceBindingWebSocket;
import jakarta.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.ee10.servlet.ErrorPageErrorHandler;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.eclipse.jetty.ee10.servlet.ServletMapping;
import org.eclipse.jetty.ee10.servlet.*;
import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.util.resource.ResourceFactory;
@@ -48,6 +46,8 @@ import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class CBJettyServer {
@@ -92,6 +92,7 @@ public class CBJettyServer {
}
{
// Handler configuration
Path contentRootPath = Path.of(serverConfiguration.getContentRoot());
ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
@@ -99,6 +100,7 @@ public class CBJettyServer {
String rootURI = serverConfiguration.getRootURI();
servletContextHandler.setContextPath(rootURI);
ServletHolder staticServletHolder = new ServletHolder(
"static", new CBStaticServlet(Path.of(serverConfiguration.getContentRoot()))
);
@@ -116,17 +118,20 @@ public class CBJettyServer {
servletContextHandler.addServlet(new ServletHolder("status", new WebStatusServlet()), "/status");
GraphQLEndpoint endpoint = new GraphQLEndpoint(new ServerConfigurationTimeLimitFilter(application));
application.addApplicationContextValue(GraphQL.class.getName(), endpoint.getGraphQL());
String gqlServletPath = serverConfiguration.getServicesURI() + "gql/*";
servletContextHandler.addServlet(
new ServletHolder(
"graphql",
new GraphQLEndpoint(new ServerConfigurationTimeLimitFilter(application))
endpoint
),
serverConfiguration.getServicesURI() + "gql/*"
gqlServletPath
);
servletContextHandler.addEventListener(new CBServerContextListener(application));
// Add extensions from services
Set<String> excludedFilterPaths = new HashSet<>();
CBJettyServletContext servletContext = new CBJettyServletContext(servletContextHandler);
for (DBWServiceBindingServlet wsd : WebServiceRegistry.getInstance()
.getWebServices(DBWServiceBindingServlet.class)
@@ -134,6 +139,7 @@ public class CBJettyServer {
if (wsd.isApplicable(this.application)) {
try {
wsd.addServlets(this.application, servletContext);
excludedFilterPaths.addAll(wsd.getExcludedServletPaths(this.application));
} catch (DBException e) {
log.error(e.getMessage(), e);
}
@@ -152,6 +158,13 @@ public class CBJettyServer {
}
}
}
FilterHolder hostsFilter = new FilterHolder(new RequestHostFilter(
application,
excludedFilterPaths,
Set.of(gqlServletPath)
));
servletContextHandler.addFilter(hostsFilter, "/*", null);
JakartaWebSocketServletContainerInitializer.configure(servletContextHandler, (context, container) -> {
// Add echo endpoint to server container
@@ -231,7 +244,7 @@ public class CBJettyServer {
&& servletContextHandler.getSessionHandler() instanceof CBSessionHandler cbSessionHandler
) {
cbSessionHandler.setMaxCookieAge((int) (application.getMaxSessionIdleTime() / 1000));
cbSessionHandler.setSecureCookies(application.getServerConfiguration().isSecureCookies());
cbSessionHandler.setSecureCookies(application.getServerConfiguration().isForceHttps());
}
}
}
@@ -0,0 +1,184 @@
/*
* 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.server.jetty;
import com.google.common.net.InetAddresses;
import io.cloudbeaver.model.config.CBServerConfig;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.utils.ServletAppUtils;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import org.jkiss.utils.CommonUtils;
import java.io.IOException;
import java.net.URI;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class RequestHostFilter implements Filter {
private static final Log log = Log.getLog(RequestHostFilter.class);
@NotNull
private final CBApplication<?> application;
private final Set<String> excludedPaths = new HashSet<>();
private final Set<String> errorPaths = new HashSet<>();
public RequestHostFilter(
@NotNull CBApplication<?> application,
@NotNull Set<String> excludedPaths,
@NotNull Set<String> errorPaths
) {
this.application = application;
this.excludedPaths.addAll(
excludedPaths.stream()
.map(path -> ServletAppUtils.removeSideSlashes(path.replace("*", "")))
.toList()
);
this.errorPaths.addAll(
errorPaths.stream()
.map(path -> ServletAppUtils.removeSideSlashes(path.replace("*", "")))
.toList()
);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
boolean requestAllowed = true;
if (request instanceof HttpServletRequest httpRequest) {
CBServerConfig serverConfig = application.getServerConfiguration();
URI originUri;
try {
String origin = ServletAppUtils.getOriginFromRequest(httpRequest);
originUri = URI.create(origin);
} catch (Exception e) {
log.error("Failed to get origin from request", e);
chain.doFilter(request, response);
return;
}
boolean isIpAddress = InetAddresses.isInetAddress(originUri.getHost());
String servletPath = httpRequest.getServletPath();
requestAllowed = isIpAddress;
if (!requestAllowed) {
if (CommonUtils.isNotEmpty(servletPath)) {
for (String excludedPath : excludedPaths) {
if (servletPath.contains(excludedPath)) {
chain.doFilter(request, response);
return;
}
}
}
requestAllowed = validateHosts(serverConfig, httpRequest, (HttpServletResponse) response, originUri);
}
if (requestAllowed) {
requestAllowed = validateSchema(serverConfig, httpRequest, (HttpServletResponse) response, originUri);
}
}
if (requestAllowed) {
chain.doFilter(request, response);
}
}
private boolean validateSchema(
@NotNull CBServerConfig serverConfig,
@NotNull HttpServletRequest httpRequest,
@NotNull HttpServletResponse response,
@NotNull URI originUri
) {
boolean httpsExpected = serverConfig.isForceHttps();
try {
if ("http".equals(originUri.getScheme()) && httpsExpected) {
log.warn("Request schema is 'http' but 'forceHttps' is enabled. Redirecting to 'https'.");
StringBuilder redirectUrlBuilder = new StringBuilder("https://")
.append(originUri.getHost());
if (originUri.getPort() > -1) {
redirectUrlBuilder.append(':').append(originUri.getPort());
}
redirectUrlBuilder.append(httpRequest.getRequestURI());
if (httpRequest.getQueryString() != null) {
redirectUrlBuilder.append("?")
.append(httpRequest.getQueryString());
}
response.sendRedirect(redirectUrlBuilder.toString());
return false;
}
} catch (Exception e) {
log.error("Failed to redirect to HTTPS", e);
}
return true;
}
private boolean validateHosts(
@NotNull CBServerConfig serverConfig,
@NotNull HttpServletRequest httpRequest,
@NotNull HttpServletResponse response,
URI originUri
) throws IOException {
List<String> availableHosts = serverConfig.getSupportedHosts();
if (CommonUtils.isEmpty(availableHosts)) {
return true;
}
try {
var requestHostBuilder = new StringBuilder(originUri.getHost());
if (originUri.getPort() > -1) {
requestHostBuilder.append(':').append(originUri.getPort());
}
String requestHost = requestHostBuilder.toString();
if (!availableHosts.contains(requestHost)) {
for (String errorPath : errorPaths) {
if (httpRequest.getServletPath().contains(errorPath)) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write(
"Request host '" + requestHost + "' is not allowed. Available hosts: " + availableHosts
);
return false;
}
}
log.warn("Request host '" + requestHost + "' is not allowed. Redirect to default: " + availableHosts);
redirectToDefaultHost(response, httpRequest, availableHosts);
return false;
}
} catch (Throwable e) {
log.error(e.getMessage(), e);
redirectToDefaultHost(response, httpRequest, availableHosts);
return false;
}
return true;
}
private void redirectToDefaultHost(
@NotNull HttpServletResponse response,
@NotNull HttpServletRequest httpRequest,
@NotNull List<String> availableHosts
) throws IOException {
boolean https = application.getServerConfiguration().isForceHttps();
String redirectUrl = (https ? "https://" : "http://") + getDefaultHost(availableHosts) + httpRequest.getRequestURI();
if (httpRequest.getQueryString() != null) {
redirectUrl += "?" + httpRequest.getQueryString();
}
response.sendRedirect(redirectUrl);
}
@NotNull
private String getDefaultHost(@NotNull List<String> availableHosts) {
return availableHosts.getFirst();
}
}
@@ -22,6 +22,7 @@ import io.cloudbeaver.model.session.*;
import io.cloudbeaver.registry.WebHandlerRegistry;
import io.cloudbeaver.registry.WebSessionHandlerDescriptor;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.server.CBConstants;
import io.cloudbeaver.server.WebAppSessionManager;
import io.cloudbeaver.server.events.WSWebUtils;
import io.cloudbeaver.service.DBWSessionHandler;
@@ -34,7 +35,9 @@ import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.auth.SMAuthInfo;
import org.jkiss.dbeaver.model.security.user.SMAuthPermissions;
import org.jkiss.dbeaver.model.websocket.event.WSAbstractEvent;
import org.jkiss.dbeaver.model.websocket.event.WSUserDeletedEvent;
import org.jkiss.dbeaver.model.websocket.event.WSUserDisabledEvent;
import org.jkiss.dbeaver.model.websocket.event.session.WSSessionStateEvent;
import org.jkiss.utils.CommonUtils;
@@ -156,8 +159,15 @@ public class CBSessionManager implements WebAppSessionManager {
}
}
validateSessionIp(request, webSession);
return webSession;
}
private void validateSessionIp(@NotNull HttpServletRequest request, WebSession webSession) {
boolean bindingEnabled = isSessionBindingEnabled(request);
String currentRemote = request.getRemoteAddr();
if (application.getServerConfiguration().isBindSessionToIp()
if (bindingEnabled
&& (CommonUtils.isEmpty(currentRemote) || !currentRemote.equals(webSession.getLastRemoteAddr()))
) {
var error = new DBWebException(
@@ -169,8 +179,11 @@ public class CBSessionManager implements WebAppSessionManager {
webSession.addSessionError(error);
closeSession(webSession.getSessionId());
}
}
return webSession;
protected boolean isSessionBindingEnabled(@NotNull HttpServletRequest request) {
String bindingState = application.getServerConfiguration().getBindSessionToIp();
return CBConstants.BIND_SESSION_ENABLE.equalsIgnoreCase(bindingState) || Boolean.parseBoolean(bindingState);
}
@NotNull
@@ -391,14 +404,14 @@ public class CBSessionManager implements WebAppSessionManager {
}
}
public void closeUserSession(@NotNull WSUserDeletedEvent userDeletedEvent) {
public void closeUserSession(@NotNull WSAbstractEvent event) {
synchronized (sessionMap) {
for (Iterator<BaseWebSession> iterator = sessionMap.values().iterator(); iterator.hasNext(); ) {
var session = iterator.next();
if (CommonUtils.equalObjects(session.getUserContext().getUserId(),
userDeletedEvent.getDeletedUserId())) {
event.getUserId())) {
if (session instanceof WebHeadlessSession headlessSession) {
headlessSession.addSessionEvent(userDeletedEvent);
headlessSession.addSessionEvent(event);
}
iterator.remove();
session.close();
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Server
Bundle-SymbolicName: io.cloudbeaver.server;singleton:=true
Bundle-Version: 25.1.4.qualifier
Bundle-Release-Date: 20250804
Bundle-Version: 25.2.1.qualifier
Bundle-Release-Date: 20250922
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.eclipse.core.runtime;visibility:=reexport,
@@ -1,2 +1,2 @@
log.api.graphql.debug = Log GraphQL requests to console
log.api.graphql.debug.description = Enable detailed logging of GraphQL queries in the server log, including all provided variables
log.api.graphql.debug.description = Enable detailed logging of GraphQL queries in the server log, including all provided variables
@@ -1,2 +1,2 @@
log.api.graphql.debug =Логировать GraphQL-запросы в консоль
log.api.graphql.debug.description = Включите подробное логирование запросов GraphQL в журнале сервера, включая все предоставленные переменные
log.api.graphql.debug.description = Включите подробное логирование запросов GraphQL в журнале сервера, включая все предоставленные переменные
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server</artifactId>
<version>25.1.4-SNAPSHOT</version>
<version>25.2.1-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -351,6 +351,9 @@ type DriverInfo {
"""
providerProperties: [ObjectPropertyInfo!]!
"Expert driver settings properties. Returns properties (like keep-alive interval) that are not often used and can be hidden in UI"
expertSettingsProperties: [ObjectPropertyInfo!]! @since(version: "25.2.1")
"False for drivers which do not support authentication."
anonymousAccess: Boolean
"Default auth model that is used for this driver (see authModels)"
@@ -486,6 +489,9 @@ type ConnectionInfo {
"Main connection properties. Contains host, port, database, server name fields"
mainPropertyValues: Object @since(version: "24.1.2")
"Expert connection settings. Contains expert settings properties (like keep-alive interval or auto-commit) that are not often used"
expertSettingsValues: Object @since(version: "25.2.1")
"Connection keep-alive interval in seconds"
keepAliveInterval: Int!
"Defines if the connection is in auto-commit mode"
@@ -672,6 +678,9 @@ input ConnectionConfig {
"Host, port, serverName, databaseName are also stored in mainPropertyValues for custom pages"
mainPropertyValues: Object @since(version: "24.1.2")
"Keep-alive, auto-commit, read-only and other expert settings are stored in expertSettingsValues"
expertSettingsValues: Object @since(version: "25.2.1")
"Sets connection URL jdbc:{driver}://{host}[:{port}]/[{database}]"
url: String
@@ -679,13 +688,13 @@ input ConnectionConfig {
properties: Object
"Set keep-alive interval"
keepAliveInterval: Int
keepAliveInterval: Int @deprecated(reason: "25.2.1 use expertPropertyValues instead")
"Sets auto-commit connection state"
autocommit: Boolean
autocommit: Boolean @deprecated(reason: "25.2.1 use expertPropertyValues instead")
"Sets read-only connection state"
readOnly: Boolean
readOnly: Boolean @deprecated(reason: "25.2.1 use expertPropertyValues instead")
# User credentials
@@ -721,9 +730,9 @@ input ConnectionConfig {
"Configuration type (MANUAL, URL)"
configurationType: DriverConfigurationType
"Sets catalog name for the connection"
defaultCatalogName: String @since(version: "25.0.5")
defaultCatalogName: String @since(version: "25.0.5") @deprecated(reason: "25.2.1 use expertPropertyValues instead")
"Sets schema name for the connection"
defaultSchemaName: String @since(version: "25.0.5")
defaultSchemaName: String @since(version: "25.0.5") @deprecated(reason: "25.2.1 use expertPropertyValues instead")
}
####################################################
@@ -41,7 +41,6 @@ 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.DBPObject;
import org.jkiss.dbeaver.model.access.DBAAuthCredentials;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPProject;
@@ -318,9 +317,11 @@ public class WebServiceUtils extends WebCommonUtils {
return container.getName() + " [" + container.getId() + "]";
}
public static void updateConfigAndRefreshDatabases(WebSession session, String projectId) throws DBWebException {
public static void refreshDatabases(WebSession session, String projectId) throws DBWebException {
DBNProject projectNode = session.getNavigatorModelOrThrow().getRoot().getProjectNode(session.getProjectById(projectId));
DBNModel.updateConfigAndRefreshDatabases(projectNode.getDatabases());
if (projectNode != null) {
projectNode.getDatabases().refreshChildren();
}
}
public static boolean isGlobalProject(DBPProject project) {
@@ -363,7 +364,7 @@ public class WebServiceUtils extends WebCommonUtils {
@NotNull
public static WebPropertyInfo[] getObjectFilteredProperties(
@NotNull WebSession session,
@NotNull DBPObject object,
@NotNull Object object,
@Nullable WebPropertyFilter filter
) {
PropertyCollector propertyCollector = new PropertyCollector(object, true);
@@ -18,10 +18,7 @@ package io.cloudbeaver.model;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.model.app.WebAppConfiguration;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.model.utils.ConfigurationUtils;
import io.cloudbeaver.server.WebAppUtils;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBConstants;
@@ -39,8 +36,6 @@ import org.jkiss.utils.CommonUtils;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
/**
* Web driver configuration
@@ -257,29 +252,18 @@ public class WebDatabaseDriverInfo {
@Property
public WebPropertyInfo[] getProviderProperties() {
WebPropertyInfo[] additionalWebProperty = Optional.of(WebAppUtils.getWebApplication())
.filter(app -> app.getAppConfiguration().isSecretManagerEnabled())
.map(app -> app.getConnectionController().getExternalInfo(webSession))
.orElse(new WebPropertyInfo[0]);
WebPropertyInfo[] providerProperties = Arrays.stream(driver.getProviderPropertyDescriptors())
return Arrays.stream(driver.getProviderPropertyDescriptors())
.map(p -> new WebPropertyInfo(webSession, p, null))
.toArray(WebPropertyInfo[]::new);
}
return Stream.concat(
Arrays.stream(additionalWebProperty),
Arrays.stream(providerProperties)
).toArray(WebPropertyInfo[]::new);
public WebPropertyInfo[] getExpertSettingsProperties() {
return WebServiceUtils.getObjectFilteredProperties(webSession, new WebExpertSettingsProperties(driver), null);
}
@Property
public boolean isEnabled() {
WebAppConfiguration config = WebAppUtils.getWebApplication().getAppConfiguration();
return ConfigurationUtils.isDriverEnabled(
driver,
config.getEnabledDrivers(),
config.getDisabledDrivers()
);
return WebServiceUtils.isDriverEnabled(driver);
}
@Property
@@ -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.
@@ -41,7 +41,7 @@ import java.util.Map;
public class WebServerConfig {
private final WebApplication application;
private boolean provideSensitiveInformation = true;
protected boolean provideSensitiveInformation = true;
public WebServerConfig(@NotNull WebApplication application) {
this.application = application;
@@ -136,7 +136,7 @@ public class WebServerConfig {
@Property
public WebServerLanguage[] getSupportedLanguages() {
List<PlatformLanguageDescriptor> langs = PlatformLanguageRegistry.getInstance().getLanguages();
List<PlatformLanguageDescriptor> langs = new ArrayList<>(PlatformLanguageRegistry.getInstance().getLanguages());
// FIXME: remove hardcoded ordering
langs.sort(Comparator.comparingInt(x -> x.getCode().equals("vi") ? 1 : -1));
WebServerLanguage[] webLangs = new WebServerLanguage[langs.size()];
@@ -39,10 +39,7 @@ public class WSDataSourceUpdatedEventHandlerImpl extends WSAbstractProjectEventH
log.debug("Project " + event.getProjectId() + " is not found in session " + webSession.getSessionId());
return;
}
sendEvent = project.updateProjectDataSources(
event.getDataSourceIds(),
event.getId()
);
sendEvent = project.updateProjectDataSources(event);
}
if (sendEvent) {
activeUserSession.addSessionEvent(event);
@@ -38,10 +38,11 @@ import io.cloudbeaver.service.DBWBindingContext;
import io.cloudbeaver.service.DBWServiceBindingGraphQL;
import io.cloudbeaver.service.WebServiceBindingBase;
import io.cloudbeaver.utils.ServletAppUtils;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.utils.CommonUtils;
@@ -73,7 +74,7 @@ public class GraphQLEndpoint extends HttpServlet {
public static final String API_PROTOCOL = "GraphQL";
private final GraphQL graphQL;
private static final Gson gson = new GsonBuilder()
public static final Gson gson = new GsonBuilder()
.serializeNulls()
.setPrettyPrinting()
.create();
@@ -143,7 +144,7 @@ public class GraphQLEndpoint extends HttpServlet {
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
String contentType = request.getContentType();
if (CommonUtils.isEmpty(contentType) || !contentType.startsWith(HttpConstants.TYPE_JSON)) {
String error = "Bad request," + (CommonUtils.isEmpty(contentType)
@@ -224,11 +225,11 @@ public class GraphQLEndpoint extends HttpServlet {
}
private void executeQuery(
HttpServletRequest request,
HttpServletResponse response,
String query,
Map<String, Object> variables,
String operationName
@NotNull HttpServletRequest request,
@NotNull HttpServletResponse response,
@NotNull String query,
@Nullable Map<String, Object> variables,
@Nullable String operationName
) throws IOException {
Map<String, Object> mapOfContext =
Map.of(
@@ -270,15 +271,23 @@ public class GraphQLEndpoint extends HttpServlet {
errorMessage = executionException.getMessage();
}
if (WebAppUtils.getWebApplication() instanceof ApiCallInterceptor apiCallInterceptor) {
apiCallInterceptor.onApiCallEvent(request, variables, operationName, startTime, errorMessage, API_PROTOCOL);
apiCallInterceptor.onApiCallEvent(
request,
variables,
CommonUtils.notEmpty(operationName), userId, startTime,
errorMessage,
API_PROTOCOL
);
}
}
Map<String, Object> resJSON = executionResult.toSpecification();
String resString = gson.toJson(resJSON);
setDevelHeaders(request, response);
response.setContentType(GraphQLConstants.CONTENT_TYPE_JSON_UTF8);
response.getWriter().print(resString);
if (executionResult != null) {
Map<String, Object> resJSON = executionResult.toSpecification();
String resString = gson.toJson(resJSON);
setDevelHeaders(request, response);
response.setContentType(GraphQLConstants.CONTENT_TYPE_JSON_UTF8);
response.getWriter().print(resString);
}
}
@@ -296,8 +305,8 @@ public class GraphQLEndpoint extends HttpServlet {
if (exception instanceof GraphQLException && exception.getCause() != null) {
exception = exception.getCause();
}
if (exception instanceof InvocationTargetException) {
exception = ((InvocationTargetException) exception).getTargetException();
if (exception instanceof InvocationTargetException ite) {
exception = ite.getTargetException();
}
log.debug(
"GraphQL call failed at '" + handlerParameters.getPath() + "'" /*+ ", " + handlerParameters.getArgumentValues()*/,
@@ -317,17 +326,17 @@ public class GraphQLEndpoint extends HttpServlet {
if (!(exception instanceof GraphQLError)) {
exception = new DBWebException(exception.getMessage(), exception);
}
if (exception instanceof DBWebException) {
((DBWebException) exception).setPath(path.toList());
((DBWebException) exception).setLocations(Collections.singletonList(sourceLocation));
if (exception instanceof DBWebException webException) {
webException.setPath(path.toList());
webException.setLocations(Collections.singletonList(sourceLocation));
}
var result = handlerResult.error((GraphQLError) exception).build();
return CompletableFuture.completedFuture(result);
}
}
public static HttpServletRequest getServletRequest(DataFetchingEnvironment env) {
@NotNull
public static HttpServletRequest getServletRequestOrThrow(DataFetchingEnvironment env) {
GraphQLContext context = env.getGraphQlContext();
HttpServletRequest request = context.get("request");
if (request == null) {
@@ -350,4 +359,8 @@ public class GraphQLEndpoint extends HttpServlet {
return context.get("bindingContext");
}
@NotNull
public GraphQL getGraphQL() {
return graphQL;
}
}
@@ -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.
@@ -25,6 +25,7 @@ import jakarta.websocket.HandshakeResponse;
import jakarta.websocket.server.HandshakeRequest;
import jakarta.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.ee10.websocket.jakarta.server.internal.JakartaWebSocketCreator;
import org.eclipse.jetty.http.BadMessageException;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
@@ -88,7 +89,7 @@ public class CBWebSocketServerConfigurator extends ServerEndpointConfig.Configur
throw new RuntimeException(e.getMessage(), e);
}
if (sec.getUserProperties().get(PROP_WEB_SESSION) == null) {
throw new RuntimeException("No web session found for websocket request");
throw new BadMessageException("No web session found for websocket request");
}
}
@@ -21,11 +21,9 @@ import io.cloudbeaver.WebObjectId;
import io.cloudbeaver.model.WebConnectionConfig;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.WebNetworkHandlerConfigInput;
import io.cloudbeaver.model.WebPropertyInfo;
import io.cloudbeaver.model.session.WebSession;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
@@ -98,5 +96,4 @@ public interface ConnectionController {
void validateConnection(DBPDataSourceContainer dataSourceContainer) throws DBWebException;
WebPropertyInfo[] getExternalInfo(WebSession webSession);
}

Some files were not shown because too many files have changed in this diff Show More