Compare commits

...
272 Commits
Author SHA1 Message Date
Dmitrii Barnukov 3f2a005598 dbeaver/pro#5562 revert hide password fields (#4226) 2026-03-19 10:31:58 +01:00
40385f3b1c [CB] Reuse DnD from base grid component for Data Editor (#4201)
* dbeaver/pro#7159 adds dnd to column body cells + introduces new drop column styles

* adds line on dnd

* removes rdg-cell-column-drop class

* cleanup for class names

* renames to useDnD box

* cleanup

* renames hook

* fixes colors scheme + border line index

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-03-17 18:50:05 +01:00
sergeyteleshevandEvgenia 4bc74883e0 [CB] Improve fuzzy search in autocompletion in the SQL Editor (#4204)
* dbeaver/pro#8418 adds fuzzy search for sql editor proposals

* sorts proposals so startsWith and includes word is showed first

* tunes DEFAULT_FUZZY_THRESHOLD

* tunes DEFAULT_FUZZY_THRESHOLD

* Revert "tunes DEFAULT_FUZZY_THRESHOLD"

This reverts commit 7ac8757215.

* fixes ranging and threshold

* rename variable

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-03-17 18:39:19 +01:00
33d1d97fb7 dbeaver/pro#8515 sync properties state (#4214)
* dbeaver/pro#8515 sync properties state

* dbeaver/pro#8515 fixed null to empty strings props conversion

* dbeaver/pro#8515 do not send property if its in default value

* Revert "dbeaver/pro#8515 fixed null to empty strings props conversion"

This reverts commit 5c4f181edf.

* dbeaver/pro#8515 fix firefox icon

* dbeaver/pro#8515 change prop order

* dbeaver/pro#8515 add comment

---------

Co-authored-by: Dmitrii Barnukov <dmitrii.barnukov@dbeaver.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2026-03-17 17:35:45 +01:00
296e03ec74 7023-cb-find-and-replace-new (#4085)
* dbeaver/pro#7023 feat: add SearchPanel component and related styles to UI-kit
add chevron Icon to UI-kit (used in Search Panel)

* dbeaver/pro#7023 feat: add SearchPanel story

* dbeaver/pro#7023 feat: update SearchPanel integration in CodeMirror

* dbeaver/pro#7023 feat: add onReplaceToggle prop to SearchPanel for toggle functionality

* dbeaver/pro#7023 fix: replace-input styles

* dbeaver/pro#7023 feat: implement search functionality in DataGrid v2

* dbeaver/pro#7023 fix: update react-data-grid workspace reference in yarn.lock

* dbeaver/pro#7023 feat: don't focus cell on replace

* dbeaver/pro#7023 feat: add searchReadOnly prop to DataGrid and GridSearchPanel components

* dbeaver/pro#7023 fix: update styles for search panel

* dbeaver/pro#7023 feat: add theme variables for improved styling in storybook

* dbeaver/pro#7023 feat: update search panel styles in datagrid

* dbeaver/pro#7023 feat: improve search functionality by resetting active match index and triggering search on find actions

* dbeaver/pro#7023 refactor: useGridSearch

* dbeaver/pro#7023 fix: search panel styles

* dbeaver/pro#7023 feat: implement grid search state management

* dbeaver/pro#7023 feat: add refresh functionality to search components

* dbeaver/pro#7023 feat: add history action to refresh search results on undo/redo

* dbeaver/pro#7023 feat: add batch cell update functionality to data grid

That functionality comes in handy when we want to use Undo and Replace All functions together:
Before that commit we used to perform N operations where N is a number of replaced cells. And history would record them as atomic operations, so pressing Undo would revert only the last replace.
setMany enables batch editing and batch reverting, so now we can Replace All and then revert it by using one Undo operation

* dbeaver/pro#7023 refactor: remove unnecessary eslint disable comment in useGridSearch

* dbeaver/pro#7023 feat: select affected cell on history change

* dbeaver/pro#7023 feat: add searchReadOnly prop to DataGridTable for conditional read-only state

* dbeaver/pro#7023 refactor: simplify isReadOnly logic in GridSearchPanel

* dbeaver/pro#7023 refactor: use constants for flags

* dbeaver/pro#7023 refactor: update focus and refresh methods in GridSearchPanel

* dbeaver/pro#7023 refactor: consolidate grid search state update logic

* dbeaver/pro#7023 refactor: update scrollToCell method signature and related usages

* dbeaver/pro#7023 refactor: update handleCellChangeBatch to use ICellChange type

* dbeaver/pro#7023 refactor: remove unwanted code

* dbeaver/pro#7023 feat: refactor search functionality to use standalone functions

* dbeaver/pro#7023 feat: implement useDataGridSearch hook to avoid bloating the  DataGrid component with the search-related logic

* dbeaver/pro#7023 feat: update search functionality to use onReplace for cell value updates

* dbeaver/pro#7023 feat: use Activity component for search panel visibility in DataGrid

* dbeaver/pro#7023 refactor: use common value transformation fn in set and setMany methods

* dbeaver/pro#7023 refactor: rename methods for "unnkeyed" cache

* dbeaver/pro#7023 refactor: unify cell edit history handling and remove batch edit references

* dbeaver/pro#7023 fix: update searchReadOnly logic

* dbeaver/pro#7023 refactor: replace rowsCountRef with useState (Error: Cannot access refs during render)

* dbeaver/pro#7023 refactor: expose getCellClassName via useImperativeHandle

Replaced onCellClassNameChange prop + useEffect with useImperativeHandle as suggested in code review. Using useState setter directly as callback ref either caused infinite re-renders (without deps) or lost context synchronization entirely (with stable deps). Settled on useCallback ref that sets both useRef (for method access) and useState (for context propagation).

* dbeaver/pro#7023 fix: Cmd+F not re-focusing the search panel when already open

* dbeaver/pro#7023 refactor:  cell edit history compression

* dbeaver/pro#7023 fix: refresh search state on revert

* dbeaver/pro#7023 refactor: move focus blocking during replace into grid component

Previously DataGridTable checked isReplacing() before syncing
focus/selection, leaking grid internals
Now the grid's selectCell blocks during replacing internally and
supports deferred mode (setTimeout + rAF) for post-render focus sync.

* dbeaver/pro#7023 refactor: restructure grid search logic with useReducer and improve state management

* dbeaver/pro#7023 refactor: improve grid search

moved ref synchronization out of render,
unified search+scroll into one shared path,
added a SET_MATCHES no-op guard to reduce unnecessary updates,
removed duplicate open=false persistence from useDataGridSearch (single owner now in useGridSearch).

* dbeaver/pro#7023 refactor: update search state types

* dbeaver/pro#7023 refactor: restructure grid search state interfaces for improved clarity

* dbeaver/pro#7023 refactor: optimize match retrieval logic after removal in grid search

* dbeaver/pro#7023 refactor: sync refs and computed props in one effect

* dbeaver/pro#7023 fix: Esc double handling between SearchPanel and CaptureView hotkeys

* dbeaver/pro#7023 refactor: fix review comments in useGridSearch

* dbeaver/pro#7023 refactor: unify hotkey stop propagation via shared EventContext/EventStopPropagationFlag

* dbeaver/pro#7023 refactor: pass reactive cellText to useGridSearch

* dbeaver/pro#7023 refactor: update grid search reducer actions and navigation logic

reuse SET_ACTIVE_MATCH and computeActiveMatchIdx for consistency

* dbeaver/pro#7023 refactor: improve equality check for cell matches

* dbeaver/pro#7023 feat: implement cell change tracking subscriptions and cleanup logic

* dbeaver/pro#7023 refactor: remove redundant clearTrackedCellSubscriptions

Cleanup implemented in useTrackedGridSearchCellSubscriptions hook

* dbeaver/pro#7023 fix: cancel stale deferred selectCell to prevent infinite focus loop

When multiple rows are deleted, competing deferred selectCell timeouts
cause focusSyncRef mismatch and endless focus-change cycle.

* dbeaver/pro#7023 fix: update keyboard shortcut for search to use 'KeyF'

* dbeaver/pro#7023 feat: make search disabled by default

* dbeaver/pro#7023 fix: layout

* dbeaver/pro#7023 fix: update scrollBox selection method in Table component

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Viktor Kirst <viktor.kirst@dbeaver.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2026-03-17 17:28:02 +01:00
2abdbce8ff dbeaver/pro#7566 feat: add settings functionality to the new tree (#4184)
* dbeaver/pro#7566 feat: add settings functionality to the new tree

Implemented settings as a part of Toolbar. The key reason was that the TreeToolbar sets different contexts for all other actions related to tree and easily extendible as a menu service. It might limit in some weird cases, but for now it looks like a valid decision

Settings are made as unknown intentionally to be polymorphic. We could use Record<string, unknown> instead, but I don't see it really improving anything.

* dbeaver/pro#7566 refactor: introduce ITreeSettings interface

add filter with settings connection via useTreeFilterState hook

* dbeaver/pro#7566 refactor: remove redundant hooks, update ITreeSettings

* dbeaver/pro#7566 refactor: move actions to cloudbeaver part

* dbeaver/pro#7566 feat: add universal hook that persists any tree settings via UserDataService as a flat key-value record

* dbeaver/pro#7566 refactor: remove unused childrenTransformers and stateTransformers from ITreeFilterState

* dbeaver/pro#7566 feat: enhance tree settings functionality with replace method and user data synchronization

* dbeaver/pro#7566 refactor: remove toolbarFilter from ITreeFilterState interface

* dbeaver/pro#7566 refactor: simplify useUserTreeSettings

* dbeaver/pro#7566 fix: lint rule and defaults inconsistency

* dbeaver/pro#7566 fix: defaults

* dbeaver/pro#7566 fix: update Russian translations for navigation tree settings

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2026-03-17 17:21:35 +01:00
Tsimafei Labanovich 051695451b dbeaver/pro#8267 Fix git comments (#4189) 2026-03-17 12:28:21 +00:00
sergeyteleshevandEvgenia 5b7a7fc3d6 [CB] Disable constraint on SQL generation for LOBs (#4215)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-03-17 12:30:28 +01:00
sergeyteleshev 0ba013e7ad [CB] fix high CVE-2026-32141 in flatted (#4217) 2026-03-17 12:27:11 +01:00
Dmitrii Barnukov 2d2f7476b4 dbeaver/pro#5562 removed auth password from props if not applicable (#4216) 2026-03-17 08:33:01 +01:00
23a0ad49b9 dbeaver/pro#8627 Redesign node extensions (#4212)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-03-16 19:58:57 +01:00
Dmitrii BarnukovandEvgenia d21a5aac7f dbeaver/pro#8637 enable search inside names by default (#4219)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-03-16 17:30:02 +01:00
f9cd51db0f [CB] add ability for data grid selection to apply for context menu (#4196)
* dbeaver/pro#8187 Adds right click for the cell menu

* supports multiple pin/unpin columns

* applies selection for the features: duplicate, delete, revert, set to NULL

* fixes pin column for 1 column selected

* renames to multiple pin/unpin columns

* reverts sandwich

* adds multiple add row action

* dbeaver/pro#8601 TE + Git fixes (#4193)

* dbeaver/pro#8601 Fix event handlers on RM server

* dbeaver/pro#8601 Fix RM node icons

* dbeaver/pro#8601 Fix RM project obtain

* dbeaver/pro#8592 support new headers (#4190)

* dbeaver/pro#7657 avoid blicking by debounce loading state (#4174)

* dbeaver/pro#7657 avoid blicking by debounce loading state

* dbeaver/pro#7657 use existing hooks

* dbeaver/pro#7657 keep the old behaviour

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>

* dbeaver/pro#8298 fix: preserve query param order, fix incorrect repla… (#4164)

* dbeaver/pro#8298 fix: preserve query param order, fix incorrect replacement, add tooltip

* dbeaver/pro#8298 refactor: rename ordered prop to sortByName and update sorting logic

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>

* fix: on selected cell menu click opens menu

* fix: duplicates on right click 1 row at time

* removes selection on cell click + removes menu sandwich for selected multiple cells

* value panel, upload, download features available only for 1 selected cell

* adds translations for the multiple rows/columns actions

---------

Co-authored-by: Serge Rider <serge@jkiss.org>
Co-authored-by: Alexander Skoblikov <aleksandr.skoblikov@dbeaver.com>
Co-authored-by: alex <48489896+devnaumov@users.noreply.github.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Sychev Andrey <44414066+SychevAndrey@users.noreply.github.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-03-15 10:59:32 +01:00
GeorgiiandDiana 3687a645b6 dbeaver/pro#8591 clickhouse driver update (#4195)
Signed-off-by: Georgii Gvinepadze <fireshowel@gmail.com>
Co-authored-by: Diana <31996417+uslss@users.noreply.github.com>
2026-03-13 15:05:34 +01:00
c142996db0 dbeaver/pro#8497 added constraint attribute name (#4206)
* dbeaver/pro#8497 added constraint attribute name

* dbeaver/pro#8497 fix: deleteConstraint call

we can use ! here because we check for currentConstraint in the code above which we get by comparing constraint.attributePosition === attributePosition, so we can be sure the attributePosition prop is present

---------

Co-authored-by: Sychev Andrey <nukemoore@gmail.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: Ainur <59531286+yagudin10@users.noreply.github.com>
2026-03-13 14:45:10 +01:00
Dmitrii BarnukovandAinur 36d2b46496 dbeaver/pro#8331 added sql generation for large objects (#4208)
* dbeaver/pro#8331 added sql generation for large objects

* dbeaver/pro#8331 check only relevant columns

---------

Co-authored-by: Ainur <59531286+yagudin10@users.noreply.github.com>
2026-03-13 12:46:43 +01:00
Ruslan Musaevandmr-anton-t cb8b1cfc52 dbeaver/pro#8467 fix npe read only status (#4183)
* dbeaver/pro#8467 fix npe read only status

* dbeaver/pro#8467 fix navigator model read write lock

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-03-13 12:13:24 +01:00
743afcaad7 dbeaver/pro#7565 feat: implement drag-and-drop functionality in the n… (#4179)
* dbeaver/pro#7565 feat: implement drag-and-drop functionality in the new tree component

* dbeaver/pro#7565 refactor: optimize node state handling in drag-and-drop functionality

this will avoid additional re-renders in some cases

* dbeaver/pro#7565 refactor: simplify drag-and-drop context handling in useNodeDnD

* dbeaver/pro#7565 fix: add extra check to avoid redundant calls

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: alex <48489896+devnaumov@users.noreply.github.com>
2026-03-12 23:38:15 +01:00
sergeyteleshevandDaria Marutkina 52c78e1aee [CB] Fix high CVE-2025-45141 in tar (#4211)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-03-12 23:25:16 +01:00
alexandmr-anton-t ff275c36b6 dbeaver/pro#8493 add fetch size tooltip (#4203)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-03-12 15:42:13 +01:00
9a94639167 [CB] Error in the console when opening the admin panel (#4194)
* dbeaver/pro#5495 removes formState hack in ServerConfig

* Revert "dbeaver/pro#5495 removes formState hack in ServerConfig"

This reverts commit 1635e60b38.

* respects formState if it is null

* cleanup

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Viktor Kirst <viktor.kirst@dbeaver.com>
2026-03-11 16:41:17 +01:00
alex 74d09ee4dc dbeaver/pro#8665 always show tabs for the right panel (#4210) 2026-03-11 16:37:31 +01:00
360aaed795 [CB] Bump NPM dependencies (#4186)
* dbeaver/pro#8307 updates yarn lock

* updates eslint

* updates graphql libs

* updates @types/whatwg-mimetype

* updates whatwg-mimetype

* covers evaluate with unit tests

* updates subscript

* removes Rollup CVE

* removes CVE for storybook

* removes CVE for koa

* removes CVE for minimatch

* removes CVE for ajv

* removes redundant types

* cleanup

* updates yarn.lock

* reverts eslint

* updates yarn.lock

* updates yarn lock

* reverts subscript version

* fixes immutable CVE

* fixes serialize-javascript CVE

* updates yarn lock

---------

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>
Co-authored-by: Serge Rider <serge@jkiss.org>
2026-03-11 13:27:17 +01:00
912ca3fdcd dbeaver/pro#8394 fix ability to test ssh config for the existing conn… (#4166)
* dbeaver/pro#8394 fix ability to test ssh config for the existing connection

* dbeaver/pro#8394 pass the same config to test tunnel

* dbeaver/pro#8394 fix checking not empty for ssh password

* dbeaver/pro#8394 fix after review

---------

Co-authored-by: naumov <iamemptyhuh@gmail.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-03-11 12:33:04 +01:00
Ruslan MusaevandDaria Marutkina 32bbfc3a37 dbeaver/pro#8149 fix audit logging ip (#4182)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-03-11 12:32:38 +01:00
1e05ad9eea dbeaver/pro#8223 add new events for messages (#4110)
* dbeaver/pro#8223 add new events for messages

* dbeaver/pro#7138 add error alert variant

* dbeaver/pro#7138 add size for the icon

---------

Co-authored-by: naumov <iamemptyhuh@gmail.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-03-11 12:32:03 +01:00
Serge RiderandEvgenia 4b516c770d dbeaver/pro#8351 Disable devel mode by default (#4136)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-03-11 12:31:37 +01:00
Serge Rider 4e1fed8177 dbeaver/pro#8626 Refactor QM model init (#4200) 2026-03-10 18:59:22 +01:00
alexandEvgenia 4b30d1073b dbeaver/pro#8355 throttle hover and menu showing (#4176)
* dbeaver/pro#8355 throttle hover and menu showing

* Revert "dbeaver/pro#8355 throttle hover and menu showing"

This reverts commit a88f7aee8b.

* dbeaver/pro#8355 mark hover update as non urgent and non-blocking

* dbeaver/pro#8355 use useHover hook for data grid

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-03-10 15:48:48 +01:00
269128360d dbeaver/pro#8298 fix: preserve query param order, fix incorrect repla… (#4164)
* dbeaver/pro#8298 fix: preserve query param order, fix incorrect replacement, add tooltip

* dbeaver/pro#8298 refactor: rename ordered prop to sortByName and update sorting logic

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2026-03-09 17:28:02 +01:00
alexandEvgenia 07db19635e dbeaver/pro#7657 avoid blicking by debounce loading state (#4174)
* dbeaver/pro#7657 avoid blicking by debounce loading state

* dbeaver/pro#7657 use existing hooks

* dbeaver/pro#7657 keep the old behaviour

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-03-09 14:12:16 +01:00
Alexander Skoblikov 41d9ac4296 dbeaver/pro#8592 support new headers (#4190) 2026-03-05 17:57:52 +01:00
Serge Rider a536a93ea3 dbeaver/pro#8601 TE + Git fixes (#4193)
* dbeaver/pro#8601 Fix event handlers on RM server

* dbeaver/pro#8601 Fix RM node icons

* dbeaver/pro#8601 Fix RM project obtain
2026-03-05 17:54:46 +01:00
Alexander Skoblikov aefdca73fa dbeaver/pro#8557 log null host (#4187) 2026-03-04 12:18:26 +01:00
sergeyteleshevandDaria Marutkina 5d402d0c20 Confusing "something went wrong" in case of network issues (#4172)
* dbeaver/pro#8468 adds server is not available network error

* removes localization

* renames method for network state issues

* adds comment

* adds explanations for dev and prod mode errors

* supports multiple browser with network errors

* moves logic to a single method + reuses for events

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-03-03 10:41:04 +01:00
Alexander SkoblikovandSerge Rider fcd1924853 dbeaver/pro#8479 annotation for non executable options (#4170)
Co-authored-by: Serge Rider <serge@jkiss.org>
2026-03-03 09:47:29 +01:00
Malcolm-B-Breaks 05c0aafb53 [CB] Small edit in wording (#4154) 2026-03-02 13:54:14 +01:00
Daria Marutkina 159b1b8bd6 dbeaver/pro#8483 Update README file (#4181)
Updated changelog for version 26.0.0, detailing new features, improvements, and fixes since version 25.3.0.
2026-03-02 11:19:37 +01:00
Tsimafei Labanovich f03c08bde7 dbeaver/pro#8120 Fix kerbeors properrties (#4178) 2026-02-27 11:16:01 +00:00
serge-rider 1b07a66192 dbeaver/pro#8105 Cleanup Http constants 2026-02-27 07:20:39 +01:00
Serge Rider ce1686e0eb dbeaver/pro#8504 Deadlock fix (#4175)
* dbeaver/pro#8504 Deadlock fix

* dbeaver/pro#8504 Deadlock fix
2026-02-26 12:17:58 +01:00
091160a59a dbeaver/pro#8441 fix ssl parameters (#4162)
Co-authored-by: Serge Rider <serge@jkiss.org>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-02-25 16:12:00 +01:00
dbeaver-devops 47d2377b39 dbeaver/pro#8483 26.0.1 version bump 2026-02-24 12:27:49 +01:00
Dmitrii BarnukovandEvgenia 8a3f6455ec dbeaver/pro#8190 secured values masking (#4143)
* dbeaver/pro#8190 secured values masking

* dbeaver/pro#6688 added idp values masking

* dbeaver/pro#8190 fix masking for saved params

* dbeaver/pro#8190 revert showProtected, review improvements

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-23 18:28:56 +01:00
801139e3ff dbeaver/pro#8068 [CB] Fix: form reload on save and AI settings language (#4140)
* dbeaver/pro#8068 fix: FormPart reload after save: race condition between loaded=false and savingPromise

Problem:
FormPart.save() sets loaded=false after successful save, expecting
useAutoLoad to call load() and reload data from server. However,
load() is blocked by formState.savingPromise guard (added recently in dbeaver/pro#8151, 9c406a4e1e),
which is only cleared later in FormState.save() finally block.
By the time savingPromise is cleared, no observable changes trigger
a React re-render, so useAutoLoad never retries — loaded stays false
forever and isChanged always returns false, permanently disabling
the Save button.

Solution:
Make FormPart.save() self-contained by adding an inline post-save
reload. After saveChanges(), set loaded=false (so setInitialState
resets both initialState and state), call loader() directly, then
set loaded=true. This eliminates the dependency on the external
useAutoLoad mechanism for post-save reload, avoiding the race
condition with savingPromise entirely.

* dbeaver/pro#8068 fix(Combobox): preserve custom value on Enter key press

When allowCustomValue is enabled, pressing Enter was selecting
an auto-highlighted item from the dropdown instead of keeping
the user's typed value. Disable autoSelect and close the popover
via Ariakit store on Enter to match the blur behavior.

* dbeaver/pro#8068 fix: after saveChanges(), reset state synchronously via setInitialState

* dbeaver/pro#8068 fix: remove redundant state loading in FormPart class

* dbeaver/pro#8068 feat: improve loadable state handling in useAutoLoad and FormPart

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-02-23 17:18:50 +01:00
457909dd82 dbeaver/pro#8423 patch resolutions (#4157)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-02-23 17:18:16 +01:00
Sychev AndreyandEvgenia 9f7e086369 dbeaver/pro#8279 feat: rename CloudBeaver CE to CloudBeaver Community… (#4153)
* dbeaver/pro#8279 feat: rename CloudBeaver CE to CloudBeaver Community across multiple files

* dbeaver/pro#8279 ci: remove unused dependency

* dbeaver/pro#8279 feat: remove unnecessary description fields in plugin.xml and defaultServerConfig.ts

* dbeaver/pro#8279 feat: update copyright year and remove product description fields in multiple locale files

* dbeaver/pro#8279 feat: return product version back to Logo component

* dbeaver/pro#8279 fix: remove exclamation mark from easy config heading

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-23 17:17:31 +01:00
Serge Rider 6e8a09a49e dbeaver/pro#8462 session npe (#4161)
* dbeaver/pro#8462 Persistent session refactoring. Nullability annotations.

* dbeaver/pro#8462 Synchronize active projects
2026-02-23 17:16:42 +01:00
alexandDaria Marutkina 8d11f94ad6 dbeaver/pro#8372 add claude code snippet (#4156)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-02-23 13:51:57 +01:00
Dmitrii BarnukovandEvgenia 3b0cd66232 dbeaver/pro#8444 bring back error prefix (#4160)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-23 12:37:07 +01:00
alexandEvgenia a60b7d4496 dbeaver/pro#8028 use UsersResourceNewUsers alias (#4150)
* dbeaver/pro#8028 use UsersResourceNewUsers alias

* dbeaver/pro#8028 use executor to add update pagination

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-20 15:40:34 +01:00
Ruslan MusaevandEvgenia 07a3a069fb dbeaver/pro#8281 check inner database password (#4155)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-20 12:32:01 +01:00
mr-anton-t 4511208cad dbeaver/pro#8413 fix fr locale (#4152) 2026-02-20 12:29:34 +01:00
1ab8d15b00 dbeaver/pro#8082 send query parameters as list fix (#4149)
* dbeaver/pro#8082 send query parameters as list fix

* dbeaver/pro#8082 fix: change query parameters handling in QueryDataSource

---------

Co-authored-by: Sychev Andrey <nukemoore@gmail.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-02-20 11:20:11 +01:00
alexandEvgenia ae331effde dbeaver/pro#3822 make selection accountable (#4144)
* dbeaver/pro#3822 make selection accountable

* dbeaver/pro#3822 pass cursor to get queries in range

* dbeaver/pro#3822 keep range queries clamped in selection

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-19 21:58:11 +01:00
alexandkseniaguzeeva 6c88030106 dbeaver/pro#8382 pass originalQuery to gql (#4151)
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2026-02-19 12:19:30 +01:00
alexandDaria Marutkina 5e53341ba1 dbeaver/pro#8317 show all users by default (#4142)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-02-19 10:04:04 +01:00
Dmitrii BarnukovandDaria Marutkina 9338539564 dbeaver/pro#8023 added nullable exception message handling (#4146)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-02-19 10:03:33 +01:00
Ruslan MusaevandDaria Marutkina af1aa83d8b dbeaver/pro#7628 fix session refresh on server config update (#4126)
* dbeaver/pro#7628 fix session refresh on server config update

* dbeaver/pro#7628 fix session refresh on server config update

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-02-19 06:06:21 +01:00
AinurandEvgenia 32671ff239 dbeaver/pro#8196 validate projects on copying connection (#4141)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-19 06:03:50 +01:00
AinurandDaria Marutkina f7c33a7f7e dbeaver/pro#8283 execute get request for gql endpoint in devel mode (#4145)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-02-18 16:14:01 +01:00
alexandEvgenia d70e4976e5 dbeaver/pro#7511 move readonly loaders to the component (#4137)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-18 12:29:24 +01:00
a0e6154a08 [CB] The generated Custom SSL certificate domain is not displayed (#4127)
* dbeaver/pro#8305 removes serverUrl

* removes unused helper

* adds getCurrentDomain helper

---------

Co-authored-by: Ainur <ainur.iagudin@dbeaver.com>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2026-02-18 12:29:03 +01:00
Daria Marutkina 1b6e729506 dbeaver/pro#8330 Update README file (#4148)
Added changelog entries for version 25.3.5, detailing new features and improvements in Data Editor, Navigator, and General sections.
2026-02-18 10:47:31 +01:00
Serge Rider 3adb898f5a dbeaver/pro#8365 Cert storage init fix 2026-02-17 11:50:31 +01:00
c886ee7bcd dbeaver/pro#8078 fix: remove translate property from menu transition (#4125)
Initial translation of a menu led to a unwanted hover effect on a first item due to menu and trigger overlapping. Removing this property fixes the problem and the menu still has a nice animation effect, thanks to opacity and scale

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-02-16 12:54:32 +01:00
serge-rider a5a9700ab9 dbeaver/pro#7928 Code style, constants 2026-02-15 09:21:36 +01:00
2c38e75a99 dbeaver/pro#6761 listen to localhost with host network mode (#4003)
* dbeaver/pro#6761 listen to localhost with host network mode

* dbeaver/pro#6761 merge

---------

Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
Co-authored-by: Alexander Skoblikov <aleksandr.skoblikov@dbeaver.com>
2026-02-13 12:46:05 +01:00
Dmitrii BarnukovandEvgenia 12bc5ba5dc dbeaver/pro#8325 fixed oracle generated selects (#4130)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-11 18:19:23 +01:00
sergeyteleshev 2719f53655 [CB] SQL Query generation: front-end part (#4132)
* dbeaver/pro#8048 pass commit message validation

* fixes json + translations

* fixes gis generation

* uses format action to get text value for sql generators
2026-02-11 18:16:38 +01:00
dbeaver-devops a2d336166b dbeaver/pro#8330 26.0.0 version bump 2026-02-11 12:17:47 +01:00
sergeyteleshevandmr-anton-t 06da0705d2 dbeaver/pro#8303 fixes axios CVE (#4123)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-02-11 11:14:47 +01:00
058a341815 dbeaver/pro#8197 add connection preferences plugin (#4111)
* dbeaver/pro#8197 add connection preferences plugin

* dbeaver/pro#8197 add preferences form

* dbeaver/pro#8197 wrap to observable

* dbeaver/pro#8197 use config from core package

* dbeaver/pro#8197 adjust naming

* dbeaver/pro#8197 add separate resource for preferences info

* dbeaver/pro#8197 change action naming

---------

Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-10 17:16:48 +01:00
Dmitrii BarnukovandDiana f5b0aae8bd dbeaver/pro#8047 exposed resultset SQL generators to CB (#4116)
* dbeaver/pro#8047 exposed resultset SQL generators to CB

* dbeaver/pro#8047 minor fix

* dbeaver/pro#7998 added sql generator tests

* dbeaver/pro#8047 moved data provider

* dbeaver/pro#8047 fixed imports after move

* dbeaver/pro#8047 extracted methods to DBDResultSetModel

---------

Co-authored-by: Diana <31996417+uslss@users.noreply.github.com>
2026-02-10 16:21:17 +01:00
Ainurandkseniaguzeeva 789760aa9e dbeaver/pro#8181 fix saving data source config on init (#4121)
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2026-02-10 16:13:56 +01:00
Tsimafei LabanovichandMashaKorax fe77133226 dbeaver/pro#8119 Add CLI commands for project management (create, rename, delete, list) (#4106)
* dbeaver/pro#8119 Add CLI commands for project management (create, rename, delete, list)

* dbeaver/pro#8119 Fix git issues

---------

Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
2026-02-10 16:07:06 +01:00
AinurandDaria Marutkina 8e237d1bbc dbeaver/pro#8142 remove public url param (#4119)
* dbeaver/pro#8142 remove public url param

* dbeaver/pro#8142 remove server url in all products

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-02-10 16:04:22 +01:00
0f3b8033df [CB] Ability to clone connections (#4107)
* dbeaver/pro#6998 adds clone connection in context menu

* adds copy to cloned connection

* dbeaver/pro#6998 fix copy navigator settings

* preload connections names to create correct name for cloned node

* pr fixes: adds translation + unique name among all connections

* adds "копия" locale

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Ainur <ainur.iagudin@dbeaver.com>
2026-02-10 12:16:56 +01:00
586d4154ab dbeaver/pro#4999 Data editor icons to see pk details (#4040)
* dbeaver/pro#4999 feat: add TableStatusIndicator component and update tooltips for key detection

* dbeaver/pro#4999 feat: update TableStatusIndicator to handle read-only connection state and improve tooltips

* dbeaver/pro#4999 feat: use new backend endpoint to provide info about identifier

* dbeaver/pro#4999 refactor: simplify TableStatusIndicator by removing props and using context directly

* dbeaver/pro#4999 refactor: move hasElementIdentifier to ResultSetDataSource

* pr fixes

* fixes indicator center position + removes duplicated translation

* fixes contrast for color-status (JSON edit row + keys indicator for tables)

---------

Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-02-10 09:41:39 +01:00
e0fe55c3bf dbeaver/pro#6306 api for original query (#4108)
* dbeaver/pro#6306 api for original query

* uses original query from backend for sql data source

* adds description for popup

* removes message

* fixes creating dataset from grouping panel

* adds new full query abstraction

* pr fixes

---------

Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2026-02-10 09:40:55 +01:00
9c406a4e1e dbeaver/pro#8151 fix: reset password and key fields on change detection (#4102)
* dbeaver/pro#8151 fix: reset password and key fields on change detection

* fixes ssh save form part flow

* removes race condition

---------

Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-09 12:12:37 +01:00
alexandDaria Marutkina 05bf79e59c dbeaver/pro#8218 add doc locale (#4113)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-02-09 12:11:03 +01:00
sergeyteleshevandEvgenia d013ece4f6 dbeaver/pro#8262 fixes CVE for @isaacs/brace-expansion (#4115)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-09 12:10:18 +01:00
Ruslan Musaev b0205e34c7 dbeaver/cloudbeaver#3833 remove mandatory backup for database community (#4118) 2026-02-06 16:29:46 +01:00
Ruslan MusaevandEvgenia 0b41ae89e8 dbeaver/pro#8211 update duck db version (#4114)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-05 15:30:14 +01:00
Ruslan Musaev b318c262b9 dbeaver/pro#7914 jetty server websocket version bump (#4079) 2026-02-05 11:55:08 +00:00
alex 547831d060 dbeaver/pro#8214 change naming (#4112) 2026-02-05 09:45:06 +01:00
sergeyteleshevandmr-anton-t 93af9a8719 [CB] Decrize the size of radio buttons (#4109)
* dbeaver/pro#8202 decreases border for radio buttons + matches border colors

* fixes checkboxes and radios space conflicts so it aligned on the left and has correct spacing with text also

* reverts margin top for checkboxes

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-02-04 16:25:34 +01:00
Alexander Skoblikov 19125fed5e dbeaver/pro#7888 interface for cli context (#4100) 2026-02-04 10:02:43 +00:00
sergeyteleshevandDaria Marutkina ea5fb47a56 [CB] remove shortcuts intersection for layouts (#4103)
* dbeaver/pro#8104 removes shortcuts intersection

* removes check of an active element (handled by the lib)

* build fix

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-02-03 17:30:59 +01:00
alexandEvgenia 89b4d1f970 dbeaver/pro#7357 use the same saved value indicator across the app (#4099)
* dbeaver/pro#7357 use the same saved value indicator across the app

* dbeaver/pro#7357 check id first

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-03 17:26:06 +01:00
Daria Marutkina 0bd89236d2 dbeaver/pro#8155 Update README file (#4105)
Added changelog for version 25.3.4, detailing new features and fixes in Editors, Navigator, and General sections.
2026-02-02 11:36:44 +01:00
Dmitrii Barnukovandmr-anton-t abe0f6fb7d dbeaver/pro#7975 removed vks from api and logic (#4098)
* dbeaver/pro#7975 removed vks from api and logic

* dbeaver/pro#7975 revert api changes

* dbeaver/pro#7975 revert api changes

* dbeaver/pro#7975 revert api changes

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-02-02 11:01:03 +01:00
sergeyteleshevandEvgenia 0b2b851772 dbeaver/pro#8156 filters menu bar with hidden actions (#4093)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-01-30 23:05:08 +08:00
alexandmr-anton-t dcb8e536b8 dbeaver/pro#8067 add tooltip (#4101)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-01-30 23:04:45 +08:00
sergeyteleshev 7881551821 dbeaver/pro#8175 removes tar and lodash CVEs (#4095) 2026-01-29 12:15:39 +01:00
alexandDaria Marutkina cdc94dce5f dbeaver/pro#8089 validate date before formatting (#4091)
* dbeaver/pro#8089 validate date before formatting

* dbeaver/pro#8089 treat date and time the same

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-01-28 12:30:09 +01:00
dbeaver-devops 70b67ce6f4 dbeaver/pro#8155 25.3.5 version bump 2026-01-27 19:55:11 +01:00
sergeyteleshevandEvgenia e570a05175 [CB] Improve undo/redo behavior for Data Editor (#4060)
* dbeaver/pro#7614 adds first implementation of grid history action (has bugs)

* some pr fixes

* replace last instead of update method

* adds history compression to cell edit

* add add row history for undo/redo

* adds history for delete row

* adds duplicate row undo/redo

* adds history for revert action

* clears history inside edit clear action

* refactors the undo/redo history to have more clean structure

* cleanup

* adds limits to history

* uses data viewer undo/redo actions

* cleanup for undo/redo actions + adds to the top of table icons

* applies tooltips for the header table only

* removes createEditorDataAccess (refactor)

* removes getPrevCellValue (refactor)

* bug fix: add row, change something, delete row, undo/redo in this history

* adds more specific function naming

* Revert "bug fix: add row, change something, delete row, undo/redo in this history"

This reverts commit 5f1799fe26.

* adds batching history to the adding/deleting operations

* refactors GridHistoryHandlers & GridEditAction API + fixes the bug with deleting added row

* handling edge cases

* cleanup

* pr fixes

* refactor: removes extra interfaces, uses addRow for duplication rows action, handlers cleanup

* refactors the code to split editor changes to private and edits, history & action executions to public methods

* Revert "refactors the code to split editor changes to private and edits, history & action executions to public methods"

This reverts commit 374201179a.

* refactor: removes duplications of the code

* cleanup

* fixes menu bar item tooltip

* fixes labels

* removes get action info

* hides undo/redo for readonly table action

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-01-27 19:04:04 +01:00
Dmitrii Barnukovandmr-anton-t f82598ad66 Dbeaver/pro#7975 Added row identifier data (#4056)
* dbeaver/pro#7975 added pk info to api

* dbeaver/pro#7975 added row id state

* dbeaver/pro#7975 update doc

* dbeaver/pro#7975 update api

* dbeaver/pro#7975 added copyright

* dbeaver/pro#7975-pk-info extended row id api, added tests

* dbeaver/pro#7975-pk-info added attribute

* dbeaver/pro#7975-pk-info renamed type

* dbeaver/pro#7975-pk-info removed excess params

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-01-27 19:02:18 +01:00
d3b0b96bae dbeaver/pro#7897 add connection view plugin (#4031)
* dbeaver/pro#7897 add connection view plugin

* dbeaver/pro#7897 add view settings to the connection dialog

* dbeaver/pro#7897 navigator settings as separate resource

* dbeaver/pro#7897 api for original navigator settings config

* dbeaver/pro#7897 use original settings for connection dialog

* dbeaver/pro#7897 save connection view fixes

* dbeaver/pro#7897 update node on reset

* dbeaver/pro#7897 remove extra comments

* dbeaver/pro#7897 do not update node if only global settings had changed

* dbeaver/pro#7897 reuse view

* dbeaver/pro#7897 fixes after review

* dbeaver/pro#7897 return old api for user settings

* dbeaver/pro#7897 change field name

* dbeaver/pro#7897 add descriptions

* dbeaver/pro#7897 get defaults settings in creation mode

* dbeaver/pro#7897 get defaults based on userSettings flag

* dbeaver/pro#7897 remove extra locales

* dbeaver/pro#7897 send object event fix

---------

Co-authored-by: Ainur <ainur.iagudin@dbeaver.com>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: Ainur <59531286+yagudin10@users.noreply.github.com>
2026-01-27 18:58:55 +01:00
sergeyteleshev ec9c2b895b dbeaver/pro#8150 this reverts commit 4088e7998e (#4090) 2026-01-27 16:59:00 +01:00
be8744232b dbeaver/pro#7578 break long words (#4088)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2026-01-27 15:46:58 +01:00
4088e7998e dbeaver/pro#7578 fix: auth dialog overflow (#4086)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2026-01-27 15:46:52 +01:00
c540728fee dbeaver/cloudbeaver#4025 fix cloudbeaver semantic sql completion (#4081)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2026-01-27 14:29:21 +01:00
84f0903fc7 dbeaver/pro#7576 feat: mcp configuration (#4084)
* dbeaver/pro#7576 feat: mcp configuration

* fix: close form after creation

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2026-01-27 14:24:29 +01:00
sergeyteleshevandmr-anton-t 89152a09a1 [CB] Bump NPM dependencies (#4070)
* dbeaver/pro#7626 bumps yarn locks

* updates globals

* bumps subscript major version

* bumps react-data-grid

* fixes react-data-grid checksum

* bumps yarn.lock

* reverts 'true' & 'false' literals for ObjectPropertyInfo

* reverts subscript to ver v9 due to poor v10 library support (it is not working api)

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-01-27 14:11:49 +01:00
0bfdcf0886 dbeaver/pro#7769 fix properties mapping for enums (#4052)
* dbeaver/pro#7769 fix properties mapping for enums

* dbeaver/pro#7769 fix annotations

* dbeaver/pro#7769 use custom extended interface

* dbeaver/pro#7769 check explicit value

* dbeaver/pro#7769 avoid declaring extra variable

* dbeaver/pro#7769 always use isNotNullDefined in terms of backend data

---------

Co-authored-by: naumov <iamemptyhuh@gmail.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Ainur <59531286+yagudin10@users.noreply.github.com>
2026-01-27 12:53:40 +01:00
Ruslan MusaevandEvgenia d2d3ada58a dbeaver/pro#7268 bump logback version (#4083)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-01-27 09:35:34 +01:00
alexandDaria Marutkina a62478a574 dbeaver/pro#7383 make pointer target at least 24px (#4080)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-01-27 00:50:28 +08:00
sergeyteleshevandEvgenia 980473918e [CB] Use the Tab key for autocomplete (#4051)
* dbeaver/pro#7555 adds autocomplete on tab + refactors shortcuts api

* adds unit tests for getCommonAndOSSpecificKeys

* uses prec.high to give some space to overriding shortcuts

* reverts keysWin + fixes the tests

* pr fixes

* adds escape editor shortcut to the shortcuts helper

* shows only 1 shortcut for autocomplete + shows os specific keys first in help doc

* fixes shortcuts so no repeats applied

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-01-26 17:20:15 +08:00
Ruslan MusaevandEvgenia 26712e7bca dbeaver/pro#7458 backend now send event on canceling query job (#4068)
* dbeaver/pro#7458 backend now send event on canceling query job

* dbeaver/pro#7458 fixes

* dbeaver/pro#7458 fixes

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-01-23 17:18:12 +01:00
Ainur 9597e99bc9 dbeaver/pro#8064 return features from license and add tests (#4075)
* dbeaver/pro#8064 return features from license and add tests

* dbeaver/pro#8064 get feature registry from application

* dbeaver/pro#8064 implement get feature registry in base method
2026-01-23 12:10:34 +01:00
Alexey PotsetsuevandDaria Marutkina 7f92abd33e dbeaver/pro#4541 feat: show children of tables (#4073)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-01-23 19:02:05 +08:00
alexandEvgenia ad686faca8 dbeaver/pro#4692 show spinner when loading new page (#4072)
* dbeaver/pro#4692 show spinner when loading new page

* dbeaver/pro#4692 use button instead of div

* dbeaver/pro#4692 use button from core blocks

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-01-22 17:17:27 +08:00
Ainur c02411085e dbeaver/pro#8016 fix tests (#4077) 2026-01-21 17:33:50 +01:00
Alexey PotsetsuevandDaria Marutkina 90fcbe6b7f dbeaver/pro#7854 feat: display query with parameters (#4064)
* dbeaver/pro#7854 feat: display query with parameters

* fix: use replaceAll instead of replace

* dbeaver/pro#7854 fix: support parameters and variables

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-01-22 00:18:25 +08:00
sergeyteleshevandDaria Marutkina 1deffb984b Critical UX Issue: Delete key marks entire row as deleted during inline cell editing (#4063)
* dbeaver/pro#4061 changes KEY_BINDING_DELETE_ROW shortcut to prevent deleting row during cell editing

* considers the case with deleting whole row during editing

* uses only delete shortcut

* considers browser default behavior

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-01-21 20:27:59 +08:00
Ruslan MusaevandEvgenia bd52ee6583 dbeaver/pro#7936 Change object settings table (#4046)
* dbeaver/pro#7936 Change object settings table

* dbeaver/pro#7936 Change object settings table

* dbeaver/pro#7936 Change object settings table

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-01-21 12:39:01 +01:00
sergeyteleshevandDaria Marutkina 013ad81581 dbeaver/pro#7738 adds unique name to notification for create connection success (#4071)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-01-21 16:46:49 +08:00
sergeyteleshevandEvgenia 4843002e0d [CB] fix contrast colors in sql editor whitespace highlight system (#4067)
* dbeaver/pro#8055 fixes contrast color issues with whitespace highlighting in sql editor

* makes a bit more contrast colors

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-01-21 16:46:33 +08:00
Ruslan MusaevandDaria Marutkina 9a05d0bf7f dbeaver/pro#7881 fix unauthorized qm session (#4039)
* dbeaver/pro#7881 fix unauthorized qm session

* dbeaver/pro#7881 fix unauthorized qm session

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-01-20 18:44:59 +01:00
ecaa4934b1 dbeaver/pro#4917 save project information in one place (#3985)
* dbeaver/pro#4917 save project information in one place

* dbeaver/pro#4917 remove projects info after deleting project

* dbeaver/pro#4917 fix creating resource type folders

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2026-01-20 18:44:18 +01:00
8d89bc25ba dbeaver/pro#5145 add rename action for connection (#4041)
* dbeaver/pro#5145 add rename action for connection

* dbeaver/pro#5145 return node id in renaming node function

* dbeaver/pro#5145 add can rename feature for data source

* dbeaver/pro#5145 use new node path from backend

* dbeaver/pro#5145 use new node id

---------

Co-authored-by: Ainur <ainur.iagudin@dbeaver.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-01-20 18:43:48 +08:00
Daria Marutkina 40581e746b dbeaver/pro#8017 Update README file (#4069) 2026-01-19 13:41:14 +01:00
alexandDaria Marutkina a9911d9297 dbeaver/pro#7339 do not confirm transaction if count is zero (#4057)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-01-19 19:33:40 +08:00
Sychev Andrey a8d437dce6 dbeaver/pro#8051 fix: support time-only formatting in DateTimeFormatter and update related tests (#4065) 2026-01-17 10:38:12 +01:00
sergeyteleshev bacd1bff91 dbeaver/pro#8025 fixes filters for select cell (#4055) 2026-01-14 16:28:14 +01:00
dbeaver-devops 1d9b0a2943 dbeaver/pro#8017 25.3.4 version bump 2026-01-13 18:56:01 +01:00
Alexey PotsetsuevandDaria Marutkina aa6a81428c dbeaver/pro#5880 fix: column size (#4049)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-01-14 00:48:05 +08:00
sergeyteleshev b91ca06359 No translation and tooltips for connection switcher (#4050)
* dbeaver/pro#8001 fixes tooltip and translation

* adds shorten connections limits
2026-01-13 23:51:01 +08:00
Alexey PotsetsuevandDaria Marutkina 244479cd06 dbeaver/pro#7397 feat: reload after session expire (#4043)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-01-13 22:54:08 +08:00
Dmitrii Barnukovandmr-anton-t 1c56d55462 dbeaver/pro#7978 fix duckdb driver id (#4048)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-01-13 13:11:17 +01:00
be78d2fe92 [CB] Remove tooltips from context menu (#4033)
* dbeaver/pro#7772 adds truncated tooltips for context menus

* dbeaver/pro#7772 adds " ... " for long labels

* adds unit tests

* Update webapp/packages/core-blocks/src/Menu/MenuItemElement.tsx

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

* adds binding for delete row

* removes tooltips

* fixes tree filters clipping

* clips only names, not whole labels

* clips only names, not whole labels [2]

* reverts getBindingLabels + fixes the os specific keys to show it first

* clips only names, not whole labels [3]

* dbeaver/pro#7772 simplifies keys helpers logic

---------

Co-authored-by: Alexey Potsetsuev <wrouds@gmail.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-01-13 19:57:27 +08:00
Sychev Andreyandmr-anton-t 4178826ff4 7855 cb add setting use the users os formatting for numbersdates (#4020)
* dbeaver/pro#7855 feat: Add setting to use the user's OS formatting for numbers and dates

* dbeaver/pro#7855 refactor: update OS formatting option value in DataGridSettingsService

* dbeaver/pro#7855 feat: implement OS formatting for date and time in DataGrid

* dbeaver/pro#7855 feat: implement user-specific formatting for numbers in DataGrid

* dbeaver/pro#7855 feat: add translations

* dbeaver/pro#7855 style: update date formatter

* dbeaver/pro#7855 feat: add locale-specific formatting options for DataGrid settings

* dbeaver/pro#7855 feat: add support for locale-specific formatting with regions

* dbeaver/pro#7855 refactor: move date type detection to the grid level

* dbeaver/pro#7855 refactor: simplify cell formatter selection logic

* dbeaver/pro#7855 refactor: move formatting to a separate context

* dbeaver/pro#7855 refactor: optimize extended date kind retrieval logic

* dbeaver/pro#7855 fix: tw class names

* dbeaver/pro#7855 refactor: change caching mechanism and other improvements

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-01-13 18:29:48 +08:00
27ef1fdf73 [CB] [TE] [FE] Permission denied for using dataset grouping panel by Viewer/Editor (#4035)
* dbeaver/pro#7849 uses asyncSqlGropingResultSet for grouping result set instead of getResultsetGroupingQuery

* dbeaver/pro#7849 fix validation and rename graphql function

* dbeaver/pro#7849 add current results id for filtering

* refactors asyncSqlGroupingResultSet so it has current results id

---------

Co-authored-by: Ainur <ainur.iagudin@dbeaver.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-01-13 17:38:07 +08:00
Alexey Potsetsuev 5b0bedcf5e dbeaver/pro#5890 chore: users cache improvements (#4045) 2026-01-13 17:37:45 +08:00
Sychev AndreyandDaria Marutkina 821805eba8 dbeaver/pro#7840 [CB] support opening web socket event that sends an url on front end side (#4036)
* dbeaver/pro#7840 feat: add core session actions module with open URL event handler

* dbeaver/pro#7840 feat: add localization support

* dbeaver/pro#7840 feat: show url in textarea

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-01-12 17:29:32 +08:00
6531e45357 [CB] [f] CB user used as db user (#4009)
* dbeaver/pro#7813 disables autocomplete for passwords (connection authentication)

* removes autocomplete in ssh tab

* removes auto-complete for auth provider

* fixes autofills

* reverts autocompleteSectionName="section-connection-authentication"

* removes password autocomplete

* Revert "removes password autocomplete"

This reverts commit d470c002ed.

* dbeaver/pro#7813 fixes more cases with autocomplete

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-01-12 17:09:44 +08:00
Serge Rider 598fb92eaf dbeaver/dbeaver#40069 Change auto-commit setting UIX (#4038) 2026-01-09 16:43:55 +01:00
71dda4aa45 dbeaver/pro#7603 add long polling transport (#4002)
* dbeaver/pro#7603 add long polling transport

* dbeaver/pro#7603 use next observer

* dbeaver/pro#7603 use ref count instead of internal observers

---------

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>
2026-01-09 17:57:20 +08:00
Serge RiderandMatvey16 cc1861c614 dbeaver/dbeaver#39210 Add Sybase dialect. Disable AS keyword. MArk system schemas (#4037)
Co-authored-by: Matvey16 <82543000+Matvey16@users.noreply.github.com>
2026-01-08 17:11:08 +01:00
67c15af74d [CB] Shortcuts for the Data Editor (#4016)
* dbeaver/pro#7025 moves shortcuts for data editor to plugin-data-viewer and reuses it in shortcuts helper plugin

* adds save binding to data editor

* adds cancel shortcut

* changes save shortcut

* dbeaver/pro#7025 change binding key

* uses mod+. shortcut for cancel action (data editor)

* Revert "uses mod+. shortcut for cancel action (data editor)"

This reverts commit c5b375c2cf.

* adds mod+. shortcut for cancel

* adds shortcut for mac for deleting row in table

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: naumov <iamemptyhuh@gmail.com>
2026-01-08 22:57:57 +08:00
Sychev Andreyandmr-anton-t 79194b2b50 dbeaver/pro#7370 feat: change checkbox, combobox, and select border widths to 1px (#4028)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-01-08 18:57:11 +08:00
sergeyteleshevandDaria Marutkina 673b824cb5 dbeaver/pro#6679 fixes fill containers so status message truncated fine (#4030)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-01-08 18:56:55 +08:00
4e7555c419 dbeaver/pro#5232 [CB] Trap Focus In the Right Panel (#3952)
* dbeaver/pro#5232 feat: add Dialog component to UI-kit

* dbeaver/pro#5232 feat: add slide variant for Dialog component

* dbeaver/pro#5232 refactor: dialog backdrop component

* dbeaver/pro#5232 feat: use dialog component for side panel to prevent focus escaping

* dbeaver/pro#5232 feat: add DialogHeader, DialogBody, and DialogFooter components for improved dialog layout

* dbeaver/pro#5232 feat: add Storysource addon to Storybook configuration

* dbeaver/pro#5232 refactor: use ui-kit dialog for CommonDialog implementation

* dbeaver/pro#5232 feat: add animated prop to Dialog component to control animations

* dbeaver/pro#5232 refactor: remove dialog animation for side panel

* dbeaver/pro#5232 refactor: remove unused backdrop and focus management logic from DialogsPortal

now handled by ariakit dialog component

* dbeaver/pro#5232 style: adjust dialog padding and size for large variant

* dbeaver/pro#5232 feat: add dialog to admin part implementation

* dbeaver/pro#5232 fix: remove autofocus prop from CommonDialogWrapper in AuthDialog and DriverSelectorDialog

* dbeaver/pro#5232 style: correct apostrophe in label text for NestedDialogs component

* dbeaver/pro#5232 fix: update dialog component styles for improved accessibility and layout

* dbeaver/pro#5232 refactor: add DialogElement component to get rid of code duplication

* dbeaver/pro#5232 fix: correct min-width for large fixedWidth dialog size

* dbeaver/pro#5232 refactor: update DialogElement properties for improved focus management

* dbeaver/pro#5232 fix: use l10n for DialogElement close buttons

* dbeaver/pro#5232 refactor: update DialogElement to be a modal to trap focus

* dbeaver/pro#5232 refactor: remove unmountOnHide prop from CommonDialogWrapper

* dbeaver/pro#5232 feat: return ESC hotkey handling in SlideBox

* dbeaver/pro#5232 feat: make background panel inert to make focus never reach hidden panel

* dbeaver/pro#5232 feat: add SlidePanel component to replace DialogElement by removing Dialog

* dbeaver/pro#5232 feat: add inert prop to SlideElement for focus trapping in visible part

* dbeaver/pro#5232 feat: pass onClose handler to SlideBox to activate hotkey

* dbeaver/pro#5232 style: return to initial state to avoid redundant diffs

* dbeaver/pro#5232 style: update copyright year in SlideElement.module.css

* dbeaver/pro#5232 chore: remove ui-kit dependency from package.json and tsconfig.json

* dbeaver/pro#5232 build: update @storybook/addon-storysource dependency version

* dbeaver/pro#5232 build: update @storybook/addon-storysource dependency version

* dbeaver/pro#5232 style: remove border styles from dialog header and footer

* dbeaver/pro#5232 fix: update hover background color for icon button

* dbeaver/pro#5232 feat: move focus from driver dialog to panel when applicable

* dbeaver/pro#6326 fix: large window size

* dbeaver/pro#5232 fix: update focus on hide logic

* dbeaver/pro#5232 fix: update hover and focus styles for icon button in SlidePanel

* dbeaver/pro#5232 feat: return back the common backdrop, replace common fallback to spinner

* dbeaver/pro#5232 fix: replace useFocus by dialog's initial focus in Rename dialog

* dbeaver/pro#5232 build: update deps after merge

* dbeaver/pro#5232 refactor: use constant for button id

* dbeaver/pro#5232 feat: add Loader component to DialogsPortal

* dbeaver/pro#5232 refactor: update Dialog component to use semantic HTML elements for header and footer

* dbeaver/pro#5232 fix: gap in footer of dialogs

* dbeaver/pro#5232 feat: focus input initially in New Folder dialog

* dbeaver/pro#5232 feat: add autoFocusOnShow prop to CommonDialogWrapper for improved focus management

* dbeaver/pro#5232 fix: padding for CommonDialogFooter

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-01-08 18:56:13 +08:00
Dmitrii Barnukovandmr-anton-t 9d0e75dba8 dbeaver/pro#7618 added lsp tests to cb (#4000)
* dbeaver/pro#7618 moved lsp tests to cb

* dbeaver/pro#7618 added plugin to feature

* dbeaver/pro#7618 added module

* dbeaver/pro#7618 added module

* dbeaver/pro#7618 fix module

* dbeaver/pro#7618 fix dependency

* dbeaver/pro#7618 moved resource utils to model

* dbeaver/pro#7618 review improvements

* dbeaver/pro#7618 removed rcp

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-01-06 18:07:49 +01:00
Alexander SkoblikovandMashaKorax 44cc1badf4 dbeaver/pro#7710 merge fixes (#4026)
Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
2026-01-06 06:03:15 +01:00
Sychev Andrey fdb4894ae4 dbeaver/pro#7940 feat: add helper function to get localized display names (#4029) 2026-01-06 01:01:45 +08:00
Daria Marutkina a836412ee4 dbeaver/pro#7916 Update README file (#4027)
* dbeaver/pro#7916 Update README file

* Update README.md

* Update README.md
2026-01-05 11:54:49 +01:00
Serge Rider 38b0246240 dbeaver/pro#7850 Base model refactoring (#4022)
* dbeaver/pro#7906 App shutdown fixes (listeners unregister)

* dbeaver/pro#7906 File systems usage refactoring (move to model)

* dbeaver/pro#7906 Logging fix

* dbeaver/pro#7906 Logging fix

* dbeaver/pro#7850 Logging fix
2026-01-03 09:17:15 +01:00
Serge Rider 0bf769a662 dbeaver/pro#2787 Adapters annotations and interface 2025-12-31 10:59:38 +01:00
dbeaver-devops 0349bcee4c dbeaver/pro#7916 Bump version 25.3.3 2025-12-29 19:42:03 +01:00
44b89296d2 dbeaver/pro#7798 [CB] Fix: menu after migration to new (#4008)
* dbeaver/pro#7798 fix: remove unwanted wrapper and relevant CSS module

* dbeaver/pro#7798 feat: add refresh action to user administration tools menu

* dbeaver/pro#7798 fix: add loader to context menu rendering

The menu was failing to open on first click due to lazy-loaded custom items (ContextMenuSearchItem) suspending the entire top navigation bar on initial load, causing portal recreation and loss of open state. Wrapped menu items in a local Loader to ensure lazy components load within the popover without affecting the parent component.

* dbeaver/pro#7798 fix: use suspense for each renderMenuItem

* dbeaver/pro#7798 refactor: simplify orderItems method in CreateUserBootstrap

* dbeaver/pro#7798 fix: fix menuBarItem styles

* dbeaver/pro#7798 refactor: update CreateUserBootstrap to include AdministrationScreenService

---------

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-12-29 18:38:06 +01:00
Sychev AndreyandDaria Marutkina b75d802883 dbeaver/pro#4612 feat: remove closeOnBlur option from autocompletion configuration (#4014)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-29 17:03:46 +01:00
9652520073 dbeaver/pro#7090 move auth island, show desc as placeholder (#4012)
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-12-29 17:03:33 +01:00
AinurandEvgenia 7e7d6634de dbeaver/pro#7157 save origin in web session (#3976)
* dbeaver/pro#7157 save origin in web session

* dbeaver/pro#7157 rename origin and add event object

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-12-29 14:00:26 +01:00
c12561965f dbeaver/pro#7481 ability to read/write to an external fs (#4007)
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
2025-12-29 13:50:10 +01:00
Dmitrii BarnukovandDaria Marutkina 7a12ad4b91 dbeaver/pro#7693 added key attributes row id check (#4019)
* dbeaver/pro#7693 added key attributes row id check

* dbeaver/pro#7693 added fix for single row tables

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-29 12:35:47 +01:00
serge-rider c39de17df8 dbeaver/pro#7906 App initialization error handle 2025-12-29 10:29:59 +01:00
serge-rider 147edde923 dbeaver/pro#7850 Remove cloud config parametrization 2025-12-28 17:05:39 +01:00
serge-rider aeaa864f57 dbeaver/pro#7850 Logging fix 2025-12-28 16:35:00 +01:00
serge-rider bcda3e4312 dbeaver/pro#7253 Remove redundant code + comments. 2025-12-27 06:13:42 +01:00
serge-rider 72a007ceb6 dbeaver/pro#7900 Logging config added 2025-12-26 18:00:36 +01:00
serge-rider e08ba6845e dbeaver/pro#7900 Redundant logging removed 2025-12-26 17:34:21 +01:00
Georgii 8f1fc1f36e dbeaver/pro#7900 fix unit test run (#4021)
Signed-off-by: Georgii Gvinepadze <fireshowel@gmail.com>
2025-12-26 17:24:21 +01:00
b5cfcaef31 dbeaver/pro#7253 save nav settings as specific to user (#3949)
* dbeaver/pro#7253 save nav settings as specific to user

* dbeaver/pro#7253 delete all project settings fix

* dbeaver/pro#7253 fixes after review and add api for getting object settings

* dbeaver/pro#7253 delete object info from db and change logic of loading settings

* dbeaver/pro#7253 fixes after review

* dbeaver/pro#7253 serialize/deserialize nav settings in common place

* dbeaver/pro#7253 fix updating schema for postgres

* dbeaver/pro#7253 add oracle migration

* dbeaver/pro#7253 fix oracle migration

* dbeaver/pro#7253 fix getting object settings for anonymous user

* dbeaver/pro#7253 fix applying config for anonymous user

* dbeaver/pro#7253 add events to handle connection view update

* dbeaver/pro#7253 update schema and change user navigator settings logic

* dbeaver/pro#7253 setSettings fixes. Remove redundant json serialization. Use string->string map everywhere. Delete datasource settings handler.

* dbeaver/pro#7253 Simplify model. Add object type as parameter.

* dbeaver/pro#7253 Remove copy-paste in project settings impl.

* dbeaver/pro#7253 Remove copy-paste in project settings impl.

* dbeaver/pro#7253 Navigator settings API and update fixes

* dbeaver/pro#7253 Navigator settings save/clear redesign (keep in separate options)

* dbeaver/pro#7253 User settings test. Test refactoring.

* dbeaver/pro#7253 setUserSettings API and test

* dbeaver/pro#7253 setUserSettings API and test

* dbeaver/pro#7253 setUserSettings API and test

* dbeaver/pro#7253 Fix drivers Maven config

* dbeaver/pro#7253 Schema update

* dbeaver/pro#7894 API comments

* dbeaver/pro#7862 Object setting cleanup fix (do it on DS level)

* dbeaver/pro#7862 REST API fixes

* dbeaver/pro#7253 Fix REST parameters encoding + annotations

---------

Co-authored-by: Serge Rider <serge@jkiss.org>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
Co-authored-by: serge-rider <serge@dbeaver.com>
Co-authored-by: Daria Markaryan <43252954+DariaMarkaryan@users.noreply.github.com>
2025-12-26 17:17:22 +01:00
Ruslan Musaev e9b1079f85 dbeaver/cloudbeaver#3998 ldap transitive group matching (#4015) 2025-12-25 16:18:41 +00:00
Georgii 326514f914 dbeaver/pro#6739 remove classifier (#4018)
Signed-off-by: Georgii Gvinepadze <fireshowel@gmail.com>
2025-12-25 14:27:48 +04:00
Viktor Kirst 67489a03b3 dbeaver/pro#7865 Do not extend web sessions on long-poll GET requests (#4010) 2025-12-25 09:38:15 +00:00
GeorgiiandMatvey16 4ea0baf79b dbeaver/pro#6739 update clickhouse to version 0.9.5 (#3999)
Signed-off-by: Georgii Gvinepadze <fireshowel@gmail.com>
Co-authored-by: Matvey16 <82543000+Matvey16@users.noreply.github.com>
2025-12-25 07:12:39 +01:00
Sychev Andreyandsergeyteleshev 939f4d046a dbeaver/pro#7564 feat: Add "File Tree Selection" to the Tree component (#3974)
* dbeaver/pro#7564 feat: add selectChildren option to tree selection API

* dbeaver/pro#7564 feat: add node multi selection API to the new tree component

* dbeaver/pro#7564 feat: add new selection type

* dbeaver/pro#7564 refactor: remove getSelectedNodes method from tree selection interfaces

* dbeaver/pro#7564 fix: update selection clearing logic in useTreeClickSelection to avoid multiple callback calls

* dbeaver/pro#7564 refactor: optimize node collection in collectVisibleNodes function

* dbeaver/pro#7564 refactor: simplify selection handling logic in Node component

* dbeaver/pro#7564 refactor: streamline selection state handling in useTreeClickSelection

* dbeaver/pro#7564 refactor: replace inline default selection state with constant

* dbeaver/pro#7564 refactor: replace default node selection constant with a function

---------

Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2025-12-24 14:19:50 +01:00
cd3cc2e92f dbeaver/pro#7667 feat: add tree toolbar functionality to new tree (#3981)
* dbeaver/pro#7667 feat: add tree toolbar functionality to new tree

* dbeaver/pro#7667 refactor: remove unused ITreeSelection import and selection property from ITreeToolbar

* dbeaver/pro#7667 refactor: remove autoFocus from Filter component

* dbeaver/pro#7667 refactor: move onClick handler from Icon to IconButton

* dbeaver/pro#7667 refactor: add className prop to TreeToolbarMenu component

* dbeaver/pro#7667 feat: implement new navigation tree toolbar using composition and data context

* dbeaver/pro#7667 feat: update toolbar menu localization files

* dbeaver/pro#7667 refactor: navigation tree components to use lazy loading and improve type definitions

* dbeaver/pro#7667 refactor: restructure Tree component removing extra wrapper

* dbeaver/pro#7667 refactor: update treeToolbarActionHandler to use async/await for refresh actions

* dbeaver/pro#7667 refactor: remove undefined type from data context creation

* dbeaver/pro#7667 fix: restructure Tree component to fix Toolbar with scroll

---------

Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-12-24 14:04:07 +01:00
7a8c4a141f [CB] Ability to pin column in the Data Editor (#3794)
* dbeaver/pro#6859 adds pin/unpin methods for data grid

* resets state when pins + makes pin icon stay when pinned

* adds TableHeaderCellIcon component with new Menu

* make actions alive

* cleanup

* moves pin logic into data-viewer

* cleanup

* restructures pin logic via contexts

* cleanup

* synchronizes column ids with view ids so pining works fine

* fixes actions visibility

* fix: correct layout on editing frozen cell

* fixes layout so frozen cell box-shadow can divide pinned and unpinned columns

* fixes border and box shadow interactions for frozen and editing cell

* makes pinned columns non transparent

* makes frozen border 3px and reverts previous style changes

* styles cleanup

* adds column true and visual indexes and reuses it across the data grid features

* cleanup

* reverts removed code

* reverts hacks with ids

* dbeaver/pro#7748 updates yarn.locks

* fixes incorrect types

* updates helper libs

* updates vitest + fixes test cases incorrect mocks

* runs tests sequentially with document and windows objects to prevent mock collisions

* Revert "runs tests sequentially with document and windows objects to prevent mock collisions"

This reverts commit f871837df3.

* skips download test

* removes export from resetDeprecatedSettings

* updates react-data-grid to beta-59 and react to 19.2

* updates storybook

* pr fixes

* updates yarn lock

* removes skip for download test

* cleanups download ts with empty body html

* updates vitest for core-connections

* updates yarn.lock

* linter fix + mocks once

* fixes hotkeys

* reverts version of react-hotkeys-hook library

* pr fixes

* code style fix

* dbeaver/pro#6859 adds selectCellByKey for opening editor

* reverts styles

* adds pin/unpin icons

* Revert "reverts styles"

This reverts commit 7857ef672d.

* on add actions sets correct focus

* removes incorrect positioning when editing pinned column

* tunes dark theme colors

* adds DnD to pin/unpin feature

* pr fixes

* fixes multiple selections for pinned & unpinned columns

* refactors multiple selections for pinned & unpinned columns so it uses data-grid api of getting sorted columns

* moves pin logic to usePinnedColumns hook

* moves color styles into data grid

* fixes letter

* adds styles for pinned border only for data editor

* adds unpin all columns action

* moves pinned logic to grid view action to save pinned state between tabs

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Sychev Andrey <44414066+SychevAndrey@users.noreply.github.com>
2025-12-24 13:44:02 +01:00
5db7a0de5e [CB] Setting to show spaces and line breaks in the SQL Editor (#3997)
* dbeaver/pro#7570 adds highlight for text spaces and new line

* refactors + changes colors

* allows to add props to extensions

* pointer events none for new line

* do not show enter symbol if line has no whitespaces below this line

* removes highlightTrailingWhitespace so the red background disappears

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: Sychev Andrey <44414066+SychevAndrey@users.noreply.github.com>
2025-12-24 13:09:02 +01:00
Ivan GagarkinandIvan Gagarkin 15c61239ec dbeaver/pro#7572 Refactor WebSession attribute handling to use ThrowableConsumer and ThrowableFunction (#4004)
Co-authored-by: Ivan Gagarkin <gagarkin@Ivans-MacBook-Pro.local>
2025-12-24 10:50:48 +01:00
alexandmr-anton-t 164d155e7d dbeaver/pro#7868 increase table font size (#4011)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-12-23 19:34:54 +01:00
821c9c8e56 [CB] Always preload specific icons for application (#3984)
* dbeaver/pro#6985 preloads icons

* simplifies the structure

* dbeaver/pro#6985 adds icons to preload

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: alex <48489896+devnaumov@users.noreply.github.com>
2025-12-23 16:47:16 +01:00
Ruslan Musaev b58e01794d dbeaver/pro#7688 Add missing fields description graphql (#4001)
* dbeaver/pro#7688 Add missing fields description graphql
2025-12-23 10:20:11 +01:00
sergeyteleshev fbc5f2d3ce dbeaver/pro#7848 removes storybook high CVE (#4005) 2025-12-22 19:51:14 +01:00
ae9e87950a dbeaver/pro#7809 use popover to show auth methods (#3995)
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-12-22 19:47:22 +01:00
Anastasiya 58db928e34 dbeaver/pro#7812 Add CloudBeaver CE release notes 25.3.1; add alt attribute for logo (#4006) 2025-12-22 16:22:53 +03:00
Serge Riderandkseniaguzeeva 583519d9a7 dbeaver/pro#7797 Product deployment model (#3988)
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2025-12-22 09:49:18 +01:00
Ruslan Musaev a96862a444 dbeaver/pro#7073 add grouping endpoint grahpql (#3960)
* dbeaver/pro#7073 add grouping endpoint grahpql

* dbeaver/pro#7073 add grouping endpoint grahpql

* dbeaver/pro#7073 add isInteractive var

* dbeaver/pro#7073 added working test

* dbeaver/pro#7073 fix test
2025-12-19 14:37:22 +01:00
Viktor Kirst b9d115ec33 Dbeaver/pro#7777 polling error http 400 (#3983)
* dbeaver/pro#7777 Handle expired access token in long-polling servlet

* dbeaver/pro#7777 Touch web session on long-poll activity

* dbeaver/pro#7804 Support cb_session_id cookie for HTTP long-polling sessions

* dbeaver/pro#7804 Refactor LP session resolvers and unify WebHttpRequestInfo creation

* dbeaver/pro#7777 Use token-expired HTTP status for LP auth failures

* dbeaver/pro#7777 Use SMException for session resolution errors and token expiration

* dbeaver/pro#7777 Remove unused constant
2025-12-19 13:51:24 +01:00
sergeyteleshevandmr-anton-t 78daa21c87 [CB] update frontend deps (#3973)
* dbeaver/pro#7748 updates yarn.locks

* fixes incorrect types

* updates helper libs

* updates vitest + fixes test cases incorrect mocks

* runs tests sequentially with document and windows objects to prevent mock collisions

* Revert "runs tests sequentially with document and windows objects to prevent mock collisions"

This reverts commit f871837df3.

* skips download test

* removes export from resetDeprecatedSettings

* updates react-data-grid to beta-59 and react to 19.2

* updates storybook

* pr fixes

* updates yarn lock

* removes skip for download test

* cleanups download ts with empty body html

* updates vitest for core-connections

* updates yarn.lock

* linter fix + mocks once

* fixes hotkeys

* reverts version of react-hotkeys-hook library

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-12-18 12:49:11 +01:00
Sychev AndreyandDaria Marutkina 50090a3035 dbeaver/pro#7810 fix: improve submenu visibility handling for lazy-lo… (#3993)
* dbeaver/pro#7810 fix: improve submenu visibility handling for lazy-loaded items

* dbeaver/pro#7810 fix: return back isDisabled for handler

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-18 10:59:40 +01:00
Sychev Andrey fc9b1d388f dbeaver/pro#7811 feat: fix file selection for uploads by removing focus and visibility listeners (#3992) 2025-12-17 16:51:25 +01:00
dbeaver-devops 0c053efd68 automerged by dbeaver-devops 2025-12-16 20:01:35 +01:00
abf07b8c5b dbeaver/pro#7189 add event for query params (#3866)
* dbeaver/pro#7189 add event for query params

* dbeaver/pro#7189 add event objects to graphql

* dbeaver/pro#7189 cancel future fix

* feat: query parameters

* dbeaver/pro#7389 fix: save confirmation

* dbeaver/pro#7189 extract base model from parameters provider

* dbeaver/pro#7189 move client events to cloudbeaver

* fix: do not ask previous params not presented in request

* dbeaver/pro#7189 resolve conflicts

* chore: change form UI

* fix: reset query parameters in SQL Editor

---------

Co-authored-by: Alexey Potsetsuev <wrouds@gmail.com>
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-12-16 19:30:49 +01:00
Ruslan MusaevandDaria Marutkina c119c93a7d Dbeaver/pro#6123 add unauthorize audit loging (#3977)
* dbeaver/pro#6123 remove reset user state

* dbeaver/pro#6123 remove reset user state on auth

* dbeaver/pro#6123 remove reset user state on auth

* dbeaver/pro#6123 fix last remote addr

* dbeaver/pro#6123 fix last remote addr

* dbeaver/pro#6123 fix last remote addr

* dbeaver/pro#6123 revert taking ip from header

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-16 15:20:12 +01:00
c2045091d0 dbeaver/pro#7610 add polyfils (#3978)
* dbeaver/pro#7610 add polyfils

* dbeaver/pro#7610 sync browserlist config

* dbeaver/pro#7610 specify only major version

* dbeaver/pro#7610 change to fixed year

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2025-12-16 14:54:58 +01:00
alex 9483df70ac dbeaver/pro#7801 convert to boolean (#3986) 2025-12-16 14:49:12 +01:00
AinurandDaria Marutkina d774540803 dbeaver/pro#7743 update lz4 to 1.10.1 (#3962)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-16 12:27:25 +01:00
Serge Rider 736442be35 dbeaver/pro#7796 Annotations 2025-12-13 15:19:45 +01:00
serge-rider 8ee58e0ce8 dbeaver/pro#7796 Trace logging and fixes 2025-12-13 07:37:06 +01:00
serge-rider 163b96bcae dbeaver/pro#7796 Trace logging and fixes 2025-12-13 07:33:07 +01:00
Alexander SkoblikovandMashaKorax 6c915eedc3 dbeaver/pro#7778 ignore splash/nosplash options (#3980)
Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
2025-12-13 06:09:01 +01:00
b9d55bc121 dbeaver/pro#4503 migrate admin tables to data grid (#3942)
* dbeaver/pro#4503 migrate admin tables to data grid

* dbeaver/pro#4503 migrate users and teams pages

* dbeaver/pro#4503 add additional deps

* dbeaver/pro#4503 dont mutate original constant

* dbeaver/pro#4503 simplify conditional column adding

* dbeaver/pro#4503 unify styles

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Alexey Potsetsuev <wrouds@gmail.com>
2025-12-13 10:50:25 +08:00
Alexey Potsetsuevandsergeyteleshev cb23fda311 dbeaver/pro#7465 refactor: migrate context menu to ariakit (#3963)
* dbeaver/pro#7465 refactor: migrate context menu to ariakit

* refactor: remove unused modal prop from ContextMenu and clean up schema name handling

* chore: add memoization to the base context menu components

* fix: menu issues

---------

Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2025-12-13 10:44:15 +08:00
Viktor Kirst dc97f315ab dbeaver/pro#7767 Add cookie-based session support (#3975) 2025-12-12 11:13:00 +00:00
sergeyteleshev 5d2c6dc062 [CB] write unit tests (#3928)
* dbeaver/pro#7563 adds unit tests for core-utils

* removes restore all mocks

* adds eslint rules: now arrow function + no unnamed functions (to improve debug experience)

* fixes test according new eslint rules

* removes arrow function expression eslint

* adds Icon Or Image unit tests

* removes problematic test

* useSMock & sMock + reuses these mocks

* adds StaticImage tests

* adds UploadArea tests

* adds tests for useAdministrationSettings

* un-skips useCombinedHandler

* tries to fix useObjectRef via correct mocking (failed github action)

* adds useErrorDetails tests

* rewrites useObjectRef tests so it works stable

* unskips app-init & getTextFileReadingProcess

* adds useFuzzySearch tests

* adds useSearch tests

* pr fixes after copilot review

* self review fixes

* pr fixes

* introduces user events

* uses get by role id to check accessibility of the components

* fixes redefined focus in body

* removes mocks

* removes mocks 2

* removes mocks 3

* removes repeated test cases
2025-12-12 18:55:50 +08:00
Alexey Potsetsuevandmr-anton-t 435aac2860 dbeaver/pro#6962 refactor: replace complex loader with-simple solution (#3968)
* dbeaver/pro#6962 refactor: replace complex loader with-simple solution

* refactor: simplify function declarations in createLazyLoader

* fix: ensure notify is called after loading state updates in createLazyLoader

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-12-12 17:54:19 +08:00
sergeyteleshevandmr-anton-t e0cdf745ad [CB] Improve description for ordering icon (#3958)
* dbeaver/pro#7554 adds translation for order button

* moves translate context to render app level + adds 2 translations for the sorting with the flag

* cleanup

* linter fix

* pr fixes

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-12-12 17:53:45 +08:00
5e11a1d0b1 dbeaver/pro#6987 replace regex with js (#3954)
* dbeaver/pro#6987 replace regex with js

* dbeaver/pro#6987 update lock file

* dbeaver/pro#6987 add additional deps for testing

* dbeaver/pro#6987 use first index

---------

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-12-11 22:36:43 +08:00
sergeyteleshevandmr-anton-t 83125bf8b5 [CB] Use only 1 Input component across the app (#3938)
* dbeaver/pro#7041 replaces uikit input with core-blocks InputField for PropertyItem (connection driver properties)

* uses Input from ui-kit for react-data-grid from common-react

* separates Input and InputField logic so we can reuse Input somewhere else

* uses input from core-blocks in search panel

* uses input from core-blocks inside fetch size action

* removes hover styles if input is focused

* adds Input to inline editor

* removes style registries

* replaces module styles with css layers api

* cleanup

* calcs padding from spacing

* reverts input field base

* reverts input fields base 2

* fixes styles for inline editor and fetch size action

* fixes outline for inputs

* fixes hover styles for combobox so it matches input hover behavior

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-12-11 18:23:04 +08:00
3b435459a2 dbeaver/pro#6545 allow to save anonym preferences (#3948)
* dbeaver/pro#6545 allow to save anonym preferences

* dbeaver/pro#6545 remove validation for updating user preferences

* dbeaver/pro#6545 fix returning anonymous user info

* dbeaver/pro#6545 add field to gql

---------

Co-authored-by: Ainur <ainur.iagudin@dbeaver.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-11 18:22:41 +08:00
Ruslan MusaevandDaria Marutkina 5dad35b73d dbeaver/pro#7607 fix Firefox gql console (#3969)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-11 10:31:46 +01:00
AinurandDaria Marutkina e801f85e7c dbeaver/pro#7636 add and fix tests (#3940)
* dbeaver/pro#7636 add and fix tests

* dbeaver/pro#7636 fixes after review

* dbeaver/pro#7636 fix locale name

* dbeaver/pro#7636 fixes after review

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-11 10:31:14 +01:00
alexandDaria Marutkina a39af0fd1b dbeaver/pro#7527 persist right panel tabs (#3951)
* dbeaver/pro#7527 persist right panel tabs

* dbeaver/pro#7527 fix typo

* dbeaver/pro#7527 set open context right before opening panel

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-10 17:13:57 +08:00
sergeyteleshevandDaria Marutkina 9ec227ddb1 dbeaver/pro#6407 fixes error during sorting (#3967)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-10 17:13:18 +08:00
Ruslan MusaevandEvgenia 749c116743 dbeaver/pro#6612 ldap group mapping full dn (#3966)
* dbeaver/pro#6612 ldap group mapping full dn

* dbeaver/pro#6612 ldap group mapping full dn

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-12-09 14:10:36 +01:00
Ruslan MusaevandDaria Marutkina d6221e3d33 dbeaver/pro#7295 do not use GSON for connection creds serialization (#3937)
* dbeaver/pro#7295 do not use GSON for connection creds serialization

* dbeaver/pro#7295 do not use GSON for connection creds serialization

* dbeaver/pro#7295 do not use GSON for connection creds serialization

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-09 11:51:02 +01:00
sergeyteleshevandDaria Marutkina 6316ab0cbb [CB] Error updating metadata (#3953)
* dbeaver/pro#6480 checks if nodes parents loaded to verify if some entities are missing

* fixes linter await issues

* uses offset pagination key to check if all parents were loaded

* adds isNodeLoadedInAnyPage logic

* reverts all changes

* adds hasMorePagesForRootNode logic

* linter fix

* checks if parent has more data for each closest parent (not just a root one) + renames helper

* renames helper to sound shorter

* build fix

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-12-09 15:49:52 +08:00
sergeyteleshevandmr-anton-t e2e18e01e9 [CB] Migrate Clickable (reakit) to the Command (ariakit) (#3941)
* dbeaver/pro#7238 adds replaces Reakit Clickable element with AriaKit Command

* uses focusable ariakit element instead of command

* cleanup

* Revert "cleanup"

This reverts commit 84ef074242.

* Revert "uses focusable ariakit element instead of command"

This reverts commit ab255989b9.

* handles tree node expand click events + removes unused styles

* removes reakit-system dep

* fixes ref for formatting rule

* cleans storybook

* clickable cleanup

* replaces Clickable component with Command from ui-kit

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-12-09 15:49:24 +08:00
Ainur d042a18d46 dbeaver/pro#7615 do not load project settings fix (#3936) 2025-12-08 16:53:38 +01:00
alexandEvgenia 88d7a8d6eb dbeaver/pro#7662 adjust markdown styles (#3947)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-12-08 22:58:42 +08:00
Ruslan MusaevandEvgenia 33bf82970f dbeaver/cloudbeaver#3484 ldap rbac (#3959)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-12-07 16:47:55 +01:00
AinurandEvgenia ea552d613b dbeaver/pro#7704 update lz4 to 1.8.1 (#3956)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-12-05 12:35:04 +01:00
Serge Rider 0ae6a648d8 dbeaver/pro#7703 Annotations and code style (#3955)
* dbeaver/pro#7703 Annotations and code style

* dbeaver/pro#7703 Annotations and code style

* dbeaver/pro#7703 Remove redundant feature s config
2025-12-05 07:19:16 +01:00
Alexander Skoblikov a674e11960 dbeaver/pro#7352 remove cli dependency from model bundles (#3945) 2025-12-04 14:46:34 +01:00
Viktor Kirst d1065037b9 Dbeaver/pro#7442 http long polling fallback for ws event (#3900)
* dbeaver/pro#7442 Add Long-Polling transport support for event manager

* dbeaver/pro#7442 Inline headless-session creation in LongPolling servlet

* dbeaver/pro#7442 Improve long-poll session resolution and add unit tests

* dbeaver/pro#7442 Expose resolveSession for testing and remove reflection in tests

* dbeaver/pro#7442 Add copyright

* dbeaver/pro#7442 Improve long-poll CORS and interrupt handling
2025-12-04 12:08:21 +01:00
serge-rider dd3ab9d5a1 dbeaver/pro#6200 Fix GQL parameters logging 2025-12-04 09:04:40 +01:00
serge-rider 7b94dc60ab dbeaver/pro#7644 Auth model replace detect. GCP localization. 2025-12-04 08:50:20 +01:00
serge-rider 25089a9a70 dbeaver/pro#4996 Remove redundant logging 2025-12-04 08:36:41 +01:00
Serge Rider d645365869 dbeaver/pro#7644 Auth model replace detect. GCP localization. (#3950) 2025-12-04 08:25:14 +01:00
Solovev Anton eb4f11aa0a dbeaver/pro#7632: fix drop message creation (#3939) 2025-12-02 15:24:36 +01:00
Daria Marutkina ec3ba12699 dbeaver/pro#7593 Update README file (#3946)
* dbeaver/pro#7593 Update README file
2025-12-01 11:46:51 +01:00
Ainur a75a7ea5a4 dbeaver/pro#7648 global permission validation fix (#3943) 2025-11-28 16:14:33 +00:00
Sychev Andreyandmr-anton-t 3ca904924b dbeaver/pro#7106 refactor: replace ScriptImportDialog with Confirmati… (#3930)
* dbeaver/pro#7106 refactor: replace ScriptImportDialog with ConfirmationDialog

* dbeaver/pro#7106 feat: enable extra action in confirmation dialog for unsaved changes

* dbeaver/pro#7106 fix: properly handles extra action (no download)

* dbeaver/pro#7106 refactor: simplify import dialog

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-11-28 19:15:37 +08:00
ShadelessFox 46a0adb682 dbeaver/pro#7221 Change API (#3935) 2025-11-25 14:05:00 +00:00
dbeaver-devops b9b4dc040c automerged by dbeaver-devops 2025-11-24 20:50:25 +01:00
Sychev Andrey 5973bd708f dbeaver/pro#7567 [CB] [F] invalid style for dropdown button (#3932)
* dbeaver/pro#7567 fix: update disabled state styles for select and combobox components

* dbeaver/pro#7567 fix: update foreground variable for disclosure component
2025-11-24 17:58:37 +01:00
698ee3cba1 dbeaver/pro#7556 hide show sql button for editor and viewer (#3929)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2025-11-21 18:21:20 +01:00
9394667673 dbeaver/pro#7413 [CB] New Disclosure Component (#3927)
* dbeaver/pro#7413 feat: add Disclosure component to UI Kit

* dbeaver/pro#7413 feat: implement new disclosure component in CB with proper outline support and animations

* dbeaver/pro#7413 style: update disclosure gap to use tw spacing

* dbeaver/pro#7413 refactor: add Disclosure component story to Storybook (remove from ladle)

* dbeaver/pro#7413 refactor: update class names for disclosure components in Expandable

* dbeaver/pro#7413 feat: disable transitions for reduced motion preference

* dbeaver/pro#7413 refactor: convert arrow function to named function in Expandable component

* dbeaver/pro#7413 refactor: simplify media query for reduced motion transitions

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2025-11-21 18:16:31 +01:00
AinurandEvgenia 37877ccf66 dbeaver/pro#5020 fix counting rows filtered data (#3923)
* dbeaver/pro#5020 fix counting rows filtered data

* dbeaver/pro#5020 fix progress monitor

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-21 18:10:27 +01:00
Sychev Andreyandmr-anton-t dc28d68208 #6724 Accessibility improvements (#3877)
* dbeaver/pro#6724 feat: add authentication model label to connection form

* dbeaver/pro#6724 refactor: change interface in StaticImage component to pass native props to provide a11y properties

* dbeaver/pro#6724 fix: add role attribute for icons in Driver selector

Those driver icons are not adding any meaning to the list items, so we can easily hide them from a11y tools.

* dbeaver/pro#6724 refactor: rename autofillToken to autocompleteSectionName and update related logic in forms

We used to have autocomplete parameters that do not meet specifications of HTML Input Autocomplete attribute.
We can't use any string there, there are list of tokens that browsers support: https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill plus two global states: "on" and "off".

Logic was refactored to use section token + username or current password tokens fo form where we provide auth details.
All other inputs use just general "on" state to let browsers decide how to fill this field, except cases where we disable password autocompletion.

also fixes a typo

* dbeaver/pro#6724 fix: don't turn on autocomplete if disabled

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-11-21 23:53:53 +08:00
b55398a969 dbeaver/pro#7320 inject mixins (#3901)
Co-authored-by: Serge Rider <serge@jkiss.org>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2025-11-21 13:22:13 +01:00
Ainur 6584a2252e dbeaver/pro#7553 remove redundant gql type param (#3925) 2025-11-21 12:15:21 +01:00
Alexey PotsetsuevandDaria Marutkina 411def42f9 dbeaver/pro#5083 feat: script status message (#3914)
* dbeaver/pro#5083 feat: script status message

* chore: use `TLocalizationToken`

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-11-21 16:58:47 +08:00
8b471138db [CB] Fix high CVE-2025-64756 on glob (#3913)
* dbeaver/pro#7520 updates glob lib to reduce high vulnerability

* sets version ^11.1

* uses yarn depute

* moves resolution to the webapp

* moves glob to dev deps

* reverts glob for devDeps

---------

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-11-21 16:58:18 +08:00
Sychev Andreyandmr-anton-t e2a5e1fca2 dbeaver/pro#6416 refactor: adjust height property for treeNodeNested (#3891)
* dbeaver/pro#6416 refactor: adjust height property for treeNodeNested

* dbeaver/pro#6416 refactor: remove height styles for TreeNodeNested and refactor empty placeholder rendering in NodeChildren

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-11-21 16:53:23 +08:00
alexandDaria Marutkina 9c2bc100b1 dbeaver/pro#4893 add ability to reselect file (#3924)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-11-21 16:52:52 +08:00
sergeyteleshevandDaria Marutkina 8b08927af8 [CB] Context menu corrupted on Windows + remove delete shortcut (#3912)
* dbeaver/pro#7024 fixes corrupted layout + removes delete shortcut from erase action

* removes delete binding for "remove column filter" + adds translations

* adds quotes for columns

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-11-21 16:51:57 +08:00
7ea627a24f dbeaver/pro#7547 do not refresh user state on server config save (#3922)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2025-11-21 09:24:22 +01:00
Ruslan MusaevandEvgenia dc5f86c40c Dbeaver/pro#7192 ldap case insensitivity (#3907)
* dbeaver/pro#7192 ldap case insensitivity

* dbeaver/pro#7192 ldap case-insensitive matching

* dbeaver/pro#7192 case insensitive from auth descriptor

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-21 09:23:28 +01:00
24b7b6021d Add reorder option to tabs #4583 (#3872)
* dbeaver/pro#4583 feat: add tab reordering functionality in NavigationTabsService

* dbeaver/pro#4583 feat: update tab filtering to support tabs order

* dbeaver/pro#4583 feat: add tab drag-and-drop functionality and associated tests

* dbeaver/pro#4583 feat: implement tab drag-and-drop functionality

* dbeaver/pro#4583 fix: adjust tab reordering logic to handle dragged tab position correctly

since our reordering logic is duplicating in two components and, moreover, I found a bug in one of the implementations, I decided to put all the common logic to a dedicated helper with test coverage to be sure we use the right logic everywhere.

* dbeaver/pro#4583 test: remove redundant tests for onReorder in useTabDragAndDrop

* dbeaver/pro#4583 refactor: update reorderTab method to remove return value

* dbeaver/pro#4583 feat: add tab reordering functionality to SqlResultTabs component

* dbeaver/pro#4583 feat: add tabs order persistence via useUserData hook

* dbeaver/pro#4583 feat: integrate react-dnd for tab drag-and-drop functionality

* dbeaver/pro#4583 chore: update deps

* dbeaver/pro#4583 feat: integrate @dbeaver/js-helpers for array reordering in useHeaderDnD

* dbeaver/pro#4583 refactor: simplify reorderArray implementation by extracting some logic to helpers

* dbeaver/pro#4583 refactor: extracted tab reordering to a separate hook

- moved the tab order persistence logic into a new useTabOrderPersistence hook to make the flow is more obvious (we use this default persistence only with tabs container)
- introduced new reorderStateKey tab state to have an identifier for a tab panel, use it in canDrop check to prevent cross-panel DnD
-replaced reorderable prop with reorderStateKey, because without a key you can't use reordering

* dbeaver/pro#4583 refactor: add dropAllowed state to useTabDragAndDrop for improved drag-and-drop UI

* dbeaver/pro#4583 chore: add a comment about reorderStateKey

* dbeaver/pro#4583 refactor: add safe guard in SqlResultTabs.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* dbeaver/pro#4583 refactor: wrap onReorder function into useCallback for improved performance

* dbeaver/pro#4583 refactor: remove applyOrder method from ITabsContainer and update useTabOrderPersistence to not have dependencies on container

* dbeaver/pro#4583 refactor: use refactored hook in SideBarPanel and ToolsPanel

Since we are not mutating a container state anymore, we have to update sorting separately in TabList level because it reads directly from the container.

* dbeaver/pro#4583 refactor: remove fallback for stateKey

* dbeaver/pro#4583 refactor: replace hardcoded values with constants in useTabDragAndDrop

* dbeaver/pro#4583 refactor: add DragPosition type

* dbeaver/pro#4583 refactor: remove dependencies in TabsState effect

* dbeaver/pro#4583 refactor: remove stateKey from useTab hook

* dbeaver/pro#4583 feat: add panelId prop to SideBarPanel and use as persistence key

* dbeaver/pro#4583 refactor: persistence key in useTabOrderPersistence

* dbeaver/pro#4583 feat: add reorderArrayWithOrder helper and update SqlResultTabs to use it

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-11-20 19:25:04 +08:00
Sychev Andrey 2f64827ad4 dbeaver/pro#7549 refactor: adjust padding and color for disabled sele… (#3921)
* dbeaver/pro#7549 refactor: adjust padding and color for disabled select state to unify view between disabled controls

unify icon view as well

* dbeaver/pro#7549 style: fix if clause
2025-11-20 17:46:42 +08:00
Alexey PotsetsuevandEvgenia e443a88ae7 dbeaver/pro#5818 fix: error message (#3920)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-20 15:43:49 +08:00
Alexey PotsetsuevandEvgenia f46857fe6d dbeaver/pro#6188 fix: loader styles (#3918)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-20 15:42:45 +08:00
alexandEvgenia 11f8568238 dbeaver/pro#5581 use new method to check if user is presenter (#3917)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-20 15:42:09 +08:00
alexandEvgenia b81c7732d1 dbeaver/pro#7282 show alert for admin team in connection tab (#3911)
* dbeaver/pro#7282 show alert for admin team in connection tab

* dbeaver/pro#7282 use team part to get permissions

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-20 15:41:37 +08:00
3592e77851 dbeaver/pro#7171 fixes incorrect padding for comboboxes (#3887)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-11-20 15:40:56 +08:00
4b0e8b5e91 dbeaver/pro#7514 fix: sql query segment end inclusion (#3909)
* dbeaver/pro#7514 fix: sql query segment end inclusion

* fix: query execution in new tab

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-11-19 15:55:50 +01:00
Alexander SkoblikovandMashaKorax 802a3e69fe dbeaver/dbeaver#39682 fix -help command (#3915)
Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
2025-11-19 13:40:15 +01:00
Ruslan MusaevandEvgenia d409aad739 dbeaver/pro#7473 fix ldap port saving (#3910)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-19 12:44:51 +01:00
Sychev Andrey 9b01659a6f dbeaver/pro#7544 chore: replace deprecated VSCode intelliCode extension with GitHub Copilot Chat (#3919) 2025-11-19 18:46:37 +08:00
sergeyteleshevandEvgenia 354f13d0d4 [CB] put new user to top after creation (#3903)
* dbeaver/pro#5085 sorts users alphabetically + shows new users on top

* reverts sorting for enabled and disabled users (required backend to sort properly)

* cleanup

* removes repetitive filters and sorting

* simplifies users logic + user.createdAt is number type

* refactor: sortUsersById -> compareUsers

* compare new users

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-19 16:51:19 +08:00
Sychev AndreyandEvgenia e39b1b1ee2 dbeaver/pro#5833 feat: add animation for enabling pointer events when content is in place (#3908)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-19 16:50:34 +08:00
sergeyteleshev f818fb1c85 [CB] TE too many requests navNodeInfo (connection edit) which freezes interface (#3894)
* dbeaver/pro#7480 fix: preloads only connection info by key (not all) to prevent connectioInfoResource loading of all data and all following handlers actions

* adds console error and return empty array for origin info loader

* warning instead of error
2025-11-18 19:36:40 +08:00
AinurandDaria Marutkina 6253d3e543 dbeaver/cloudbeaver#3736 filter non root empty folders fix (#3902)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-11-18 11:16:30 +01:00
AinurandEvgenia c7a6cc967e dbeaver/pro#7164 fix test connection without saved creds (#3906)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-18 11:15:33 +01:00
Ainur c3ac257fdd dbeaver/pro#7474 fix stack overflow on gql extended logs (#3895) 2025-11-18 11:12:18 +01:00
sergeyteleshevandDaria Marutkina c9b1278788 dbeaver/pro#7316 changes order of inputs in folder dialog (#3897)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-11-18 17:11:34 +08:00
alexandEvgenia 58d316f396 dbeaver/pro#5591 add padding to popup (#3883)
* dbeaver/pro#5591 add padding to popup

* dbeaver/pro#5591 sanitize input

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-18 17:10:48 +08:00
sergeyteleshevandDaria Marutkina 37e5e1865c [CB] [f] invalid dialog name when export VQB diagram (#3889)
* dbeaver/pro#7419 renames header for export image dialog

* fixes text case for eng locale

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-11-18 17:10:28 +08:00
alexandDaria Marutkina 8cfe6a98e5 dbeaver/pro#7382 change combobox to select (#3888)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-11-18 17:09:50 +08:00
Alexey PotsetsuevandDaria Marutkina 20026d1d30 dbeaver/pro#7389 fix: save confirmation (#3904)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-11-17 23:07:26 +08:00
Daria Marutkina 7c72232215 dbeaver/pro#7470 Update README file (#3905) 2025-11-17 12:38:09 +01:00
alexandEvgenia 64118de61c dbeaver/pro#5094 use safe property access (#3893)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2025-11-17 19:10:16 +08:00
Alexey PotsetsuevandDaria Marutkina 17331e6a8f dbeaver/pro#6595 fix: pagination cache (#3886)
dbeaver/pro#6988 fix: allow empty query in VQB
dbeaver/pro#6512 fix: remove bind session from easy config
dbeaver/pro#6734 fix: allowed server urls description

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2025-11-17 18:52:10 +08:00
dbeaver-devops 46079dea6a dbeaver/pro#7470 25.3.0 version bump (#3890) 2025-11-12 20:07:34 +01:00
1153 changed files with 32036 additions and 10504 deletions
+2 -2
View File
@@ -15,11 +15,11 @@
"streetsidesoftware.code-spell-checker",
"streetsidesoftware.code-spell-checker-russian",
"syler.sass-indented",
"VisualStudioExptTeam.vscodeintellicode",
"github.copilot-chat",
"yzhang.markdown-all-in-one",
"GraphQL.vscode-graphql-syntax",
"mrmlnc.vscode-json5",
"esbenp.prettier-vscode",
"devnaumov.licensify"
]
}
}
+49 -51
View File
@@ -1,4 +1,4 @@
<img src="https://github.com/dbeaver/cloudbeaver/wiki/images/cloudbeaver-logo.png" align="right" width="250"/>
<img src="https://github.com/dbeaver/cloudbeaver/wiki/images/cloudbeaver-logo.png" alt="CloudBeaver logo" align="right" width="250"/>
# CloudBeaver Community
@@ -25,57 +25,55 @@ You can see a live demo of CloudBeaver here: https://demo.cloudbeaver.io
## Changelog
### 25.2.4 2025-11-03
- A confirmation prompt has been added for potentially dangerous queries in the SQL Editor to help prevent unintended execution. You can customize this behavior in Preferences > SQL Editor;
- The value panel in the Data Editor has been redesigned to provide users with more space for viewing and editing data. Tab buttons for different data type visualizations are now aligned on a single level, creating a cleaner layout;
- Permission configuration for connections was simplified, providing a clearer appearance and improved performance.
### 26.0.0 2026-03-02
### 25.2.3 2025-10-20
- Added visualization for column reordering in the Data Editor;
- Fixed the critical issue with duplicating the execution of queries executed by a shortcut in the SQL Editor;
- Fixed the critical vulnerability (CVE-2025-61927) in the Happy DOM library. The library was updated to version 20.0.2.
### Changes since 25.3.0:
### 25.2.2 2025-10-06
- Replaced true/false text with intuitive checkboxes for boolean values in the metadata editor, making editing easier and faster;
- Keyboard navigation was improved across the application. Additional shortcuts are available in the main info panel (top-right corner). To help you discover them faster, relevant keyboard shortcuts are also shown directly in the tooltips of Data Editor buttons;
- Fixed an issue in the Data Editor where resizing one column in a wide table would cause previously resized columns to revert to their original width. Users can resize multiple columns as intended.
### 25.2.1 2025-09-22
- Autocomplete values in SQL Editor are ordered alphabetically to simplify object search;
- "Show all databases" setting was added on the connection page for MySQL and MariaDB;
- Different bug fixes and enhancements have been made.
### 25.2.0 2025-09-01
### Changes since 25.1.0:
- Administration:
- 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:
- 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:
- 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 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: 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.
- Security:
- Enforced complete logout and screen data clearance upon session expiration;
- Fixed the high vulnerability (CVE-2026-25639) in the axios library. The library was updated to version 1.13.5.
- Administration:
- Added support for mapping users to CloudBeaver teams based on LDAP memberOf group membership;
- Change the User list settings in the Administration part to show both active and inactive users by default.
- SQL Editor:
- Added support for parameters and variables in queries. This feature allows queries to be reused by changing parameters at execution time. Enabled by default and configurable in personal preferences;
- Added SQL preview to the Bind parameters/variables dialog to review queries with changed values on the fly;
- Enabled Tab key for autocompletion in the SQL Editor alongside the Enter key;
- Added a new setting in the SQL Editor to highlight spaces, tabs, and other whitespace characters to help users read, debug, and maintain their scripts. It is turned off by default and can be configured in personal preferences;
- Dangerous query confirmation is now shown for all DROP statements, not just for tables.
- Data Editor:
- Added ability to automatically generate INSERT, SELECT, DELETE, and UPDATE statements for the selected values;
- Added undo and redo functionality for cell edits, row operations, and other data modifications. Retains the last 50 actions across the Data Editor, result sets, and related panels;
- Added "Use local formatting" setting. Users can choose how to display numbers and dates: using the OS locale, a custom locale, or keeping values unformatted. This formatting applies only to displayed values. Data in the database remains unchanged;
- Added column pinning to keep key columns (e.g., IDs, names) visible while horizontal scrolling through wide tables;
- Added status indicator icon in the top-left corner with tooltips explaining table editability. Indicates presence of primary keys, read-only connection settings, or read-only columns;
- Added shortcut Ctrl/Cmd + . to cancel operations in Data Editor;
- Fixed application freeze in canceling fetch size requests for large tables.
- Navigator tree:
- Added the ability to duplicate connection configuration in the project navigation tree. The "Clone connection" feature is available in the context menu;
- Added the ability for users to configure the Simple or Advanced view in the Navigation tree for all connections or for each connection separately;
- Added the ability to show table objects, such as columns or keys, in the Navigation tree. The setting is disabled by default and can be turned on in the Navigator settings panel;
- Added the ability to rename connections via context menu in the Navigation Tree;
- Added Connection Info tab to display basic information about the current connection for all users.
- General:
- Added support for long polling as a fallback when WebSockets are unavailable or blocked. Ensures reliable communication for metadata updates and SQL execution;
- Extended browser support to versions up to three years old;
- Redesigned the connection configuration page. Reorganized form fields and sections to provide more input space and reduce visual clutter;
- Expanded pointer target areas for icons in the Navigator, editors, and tabs according to the accessibility standards;
- Fixed a keyboard navigation issue for panels to keep the focus inside;
- Renamed "Database Native" authentication type to "Username/password" in the connection dialog;
- Fixed issue where the missing pg_dump utility caused errors during initialization or deployment updates when PostgreSQL was selected as the inner database.
- Databases:
- ClickHouse:
- Updated driver to version 0.9.5
- Added spatial data support
- Fixed an issue with displaying arrays of UUID, IPv4/IPv6, and Map types
- Fixed JSON data type reading
- Resolved an issue with CTE expressions
- DuckDB:
- Updated driver to version 1.4.4.0;
- Added support for the dollar-quoted string syntax for the SQL Editor
- Oracle: Added a new "Set Username to OS_USER" option in the Misc section of Oracle connection settings. Automatically uses the current database username as the operating system user identifier in session metadata when enabled;
- PostgreSQL: Added DDL display support for PostgreSQL policies.
[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.2.0 was made by:
1. [hantmac](https://github.com/hantmac) - added support for Databend in CloudBeaver Community Edition.
@@ -0,0 +1,13 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Model
Bundle-SymbolicName: io.cloudbeaver.model.cli;singleton:=true
Bundle-Version: 1.0.6.qualifier
Bundle-Release-Date: 20260323
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.model.cli;visibility:=reexport
Export-Package: io.cloudbeaver.model.cli
Automatic-Module-Name: io.cloudbeaver.model.cli
Import-Package: org.slf4j
@@ -0,0 +1,5 @@
source.. = src/
output.. = target/classes/
bin.includes = .,\
META-INF/,\
plugin.xml
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
</plugin>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.cloudbeaver</groupId>
<artifactId>bundles</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.model.cli</artifactId>
<version>1.0.6-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -18,29 +18,33 @@ package io.cloudbeaver.model.cli;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.cli.*;
import org.jkiss.dbeaver.model.cli.model.CommandLineAuthenticator;
import org.jkiss.dbeaver.model.cli.ApplicationCommandLine;
import org.jkiss.dbeaver.model.cli.ApplicationInstanceController;
import org.jkiss.dbeaver.model.cli.CLIContextImpl;
import org.jkiss.dbeaver.model.cli.CLIRunMeta;
import picocli.CommandLine;
public class CloudBeaverCommandLine extends ApplicationCommandLine<ApplicationInstanceController> {
private final CloudBeaverMixin mixin;
@Nullable
private final CommandLineAuthenticator authenticator;
public CloudBeaverCommandLine(@Nullable CommandLineAuthenticator authenticator) {
public CloudBeaverCommandLine() {
super();
this.authenticator = authenticator;
this.mixin = new CloudBeaverMixin();
}
public CloudBeaverCommandLine(
@NotNull CloudBeaverMixin mixin
) {
super();
this.mixin = mixin;
}
@Override
protected CloudBeaverTopLevelCommand createTopLevelCommand(
@Nullable ApplicationInstanceController applicationInstanceController,
@NotNull CommandLineContext context,
@NotNull CLIContextImpl context,
@NotNull CLIRunMeta runMeta
) {
if (authenticator != null) {
context.setContextParameter(CLIConstants.CONTEXT_PARAM_AUTHENTICATOR, authenticator);
}
return new CloudBeaverTopLevelCommand(applicationInstanceController, context, runMeta);
}
@@ -48,9 +52,11 @@ public class CloudBeaverCommandLine extends ApplicationCommandLine<ApplicationIn
@Override
protected CommandLine initCommandLine(
@Nullable ApplicationInstanceController applicationInstanceController,
@NotNull CommandLineContext context,
@NotNull CLIContextImpl context,
@NotNull CLIRunMeta runMeta
) {
return super.initCommandLine(applicationInstanceController, context, runMeta);
var cmd = super.initCommandLine(applicationInstanceController, context, runMeta);
cmd.addMixin("cloudbeaver", mixin);
return cmd;
}
}
@@ -29,10 +29,10 @@ public class CloudBeaverInstanceServer extends ApplicationInstanceServer<Applica
public CloudBeaverInstanceServer() throws IOException {
super(ApplicationInstanceController.class);
this.commandLine = new CloudBeaverCommandLine(null);
this.commandLine = new CloudBeaverCommandLine();
}
protected CloudBeaverInstanceServer(@NotNull CloudBeaverCommandLine commandLine) throws IOException {
public CloudBeaverInstanceServer(@NotNull CloudBeaverCommandLine commandLine) throws IOException {
super(ApplicationInstanceController.class);
this.commandLine = commandLine;
}
@@ -0,0 +1,34 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.model.cli;
import org.jkiss.dbeaver.model.cli.model.NonExecutableOption;
import picocli.CommandLine;
/**
* CloudBeaver specific mixin options
*/
public class CloudBeaverMixin {
@NonExecutableOption
@CommandLine.Option(
names = {"-web-config"},
arity = "1",
hidden = true,
scope = CommandLine.ScopeType.INHERIT
)
private String webConfig;
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -19,16 +19,25 @@ package io.cloudbeaver.model.cli;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.cli.ApplicationInstanceController;
import org.jkiss.dbeaver.model.cli.CLIContextImpl;
import org.jkiss.dbeaver.model.cli.CLIRunMeta;
import org.jkiss.dbeaver.model.cli.CommandLineContext;
import org.jkiss.dbeaver.model.cli.command.AbstractTopLevelCommand;
import org.jkiss.dbeaver.model.cli.model.NonExecutableOption;
import picocli.CommandLine;
@CommandLine.Command(name = "cbvr", description = "CloudBeaver commands", mixinStandardHelpOptions = true)
@CommandLine.Command(name = "cbvr", description = "CloudBeaver commands")
public class CloudBeaverTopLevelCommand extends AbstractTopLevelCommand {
@NonExecutableOption
@CommandLine.Option(
names = {NOSPASH_OPTION},
hidden = true,
scope = CommandLine.ScopeType.INHERIT
)
private boolean noSplash;
protected CloudBeaverTopLevelCommand(
@Nullable ApplicationInstanceController controller,
@NotNull CommandLineContext context,
@NotNull CLIContextImpl context,
@NotNull CLIRunMeta meta
) {
@@ -3,18 +3,18 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Model
Bundle-SymbolicName: io.cloudbeaver.model;singleton:=true
Bundle-Version: 1.0.89.qualifier
Bundle-Release-Date: 20251117
Bundle-Version: 1.0.97.qualifier
Bundle-Release-Date: 20260323
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.data.gis;visibility:=reexport,
org.jkiss.dbeaver.model;visibility:=reexport,
org.jkiss.dbeaver.model.cli;visibility:=reexport,
org.jkiss.dbeaver.model.sm;visibility:=reexport,
org.jkiss.dbeaver.model.event;visibility:=reexport,
org.jkiss.dbeaver.model.nio;visibility:=reexport,
org.jkiss.dbeaver.registry;visibility:=reexport,
org.jkiss.dbeaver.net.ssh,
org.jkiss.bundle.logback;visibility:=reexport,
org.jkiss.bundle.graphql;visibility:=reexport,
org.jkiss.bundle.apache.dbcp,
com.google.gson;visibility:=reexport,
@@ -26,17 +26,16 @@ Export-Package: io.cloudbeaver,
io.cloudbeaver.auth.provider.local,
io.cloudbeaver.auth.provisioning,
io.cloudbeaver.websocket,
io.cloudbeaver.websocket.event.client,
io.cloudbeaver.websocket.event.task,
io.cloudbeaver.model,
io.cloudbeaver.model.app,
io.cloudbeaver.model.config,
io.cloudbeaver.model.apilog,
io.cloudbeaver.model.cli,
io.cloudbeaver.model.events,
io.cloudbeaver.model.fs,
io.cloudbeaver.model.log,
io.cloudbeaver.model.rm,
io.cloudbeaver.model.rm.local,
io.cloudbeaver.model.rm.lock,
io.cloudbeaver.model.session,
io.cloudbeaver.model.user,
io.cloudbeaver.model.utils,
@@ -25,4 +25,27 @@
label="Default secret controller" description="Default void secret controller"/>
</extension>
<extension point="org.jkiss.dbeaver.ws.event">
<event topicId="cb_session_task" id="cb_session_task_info_updated"
class="io.cloudbeaver.websocket.event.task.WSSessionTaskInfoEvent"/>
<event topicId="cb_session_task" id="cb_session_task_confirmation_request"
class="io.cloudbeaver.websocket.event.task.WSSessionTaskConfirmationRequestEvent"/>
<event topicId="cb_session_task" id="cb_session_task_query_confirmation_request"
class="io.cloudbeaver.websocket.event.task.WSSessionTaskQueryConfirmationRequestEvent"/>
<event topicId="cb_session_task" id="cb_session_task_query_params_confirmation_request"
class="io.cloudbeaver.websocket.event.task.WSSessionTaskQueryParamsConfirmationEvent"/>
<clientEvent id="cb_client_session_ping" class="io.cloudbeaver.websocket.event.client.WSSessionPingClientEvent"/>
<clientEvent id="cb_client_topic_subscribe" class="io.cloudbeaver.websocket.event.client.WSSubscribeOnTopicClientEvent"/>
<clientEvent id="cb_client_topic_unsubscribe"
class="io.cloudbeaver.websocket.event.client.WSUnsubscribeFromTopicClientEvent"/>
<clientEvent id="cb_client_projects_active"
class="io.cloudbeaver.websocket.event.client.WSUpdateActiveProjectsClientEvent"/>
<clientEvent id="cb_client_session_task_confirmation"
class="io.cloudbeaver.websocket.event.client.WSSessionTaskConfirmationEvent"/>
<clientEvent id="cb_client_session_task_with_parameters_confirmation"
class="io.cloudbeaver.websocket.event.client.WSSessionTaskWithParametersConfirmationEvent"/>
</extension>
</plugin>
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.model</artifactId>
<version>1.0.89-SNAPSHOT</version>
<version>1.0.97-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -146,6 +146,8 @@ public class DBWebException extends DBException implements GraphQLError {
} else {
return message;
}
} else if (cause.getMessage() == null && cause.getCause() != null) {
return message + ":\n" + cause.getCause().getClass().getSimpleName();
}
if (CommonUtils.isEmpty(message)) {
if (cause.getMessage() != null) {
@@ -18,6 +18,7 @@ package io.cloudbeaver;
import io.cloudbeaver.model.WebConnectionConfig;
import io.cloudbeaver.model.app.WebAppConfiguration;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.utils.ServletAppUtils;
import io.cloudbeaver.utils.WebDataSourceUtils;
import org.jkiss.code.NotNull;
@@ -32,8 +33,10 @@ public class WebConnectionConfigInputHandler<T extends WebConnectionConfig, C ex
private static final Log log = Log.getLog(WebConnectionConfigInputHandler.class);
protected final T input;
protected final DBPDataSourceRegistry registry;
protected final WebSession webSession;
public WebConnectionConfigInputHandler(@NotNull DBPDataSourceRegistry registry, T configInput) {
public WebConnectionConfigInputHandler(@NotNull WebSession webSession, @NotNull DBPDataSourceRegistry registry, T configInput) {
this.webSession = webSession;
this.registry = registry;
this.input = configInput;
}
@@ -52,6 +55,7 @@ public class WebConnectionConfigInputHandler<T extends WebConnectionConfig, C ex
}
WebDataSourceUtils.saveAuthProperties(
webSession.getProgressMonitor(),
newDataSource,
newDataSource.getConnectionConfiguration(),
input.getCredentials(),
@@ -73,7 +77,7 @@ public class WebConnectionConfigInputHandler<T extends WebConnectionConfig, C ex
dataSource.setFolder(input.getFolder() != null ? registry.getFolder(input.getFolder()) : null);
if (input.isDefaultAutoCommit() != null) {
dataSource.setDefaultAutoCommit(input.isDefaultAutoCommit());
dataSource.getConnectionConfiguration().getBootstrap().setDefaultAutoCommit(input.isDefaultAutoCommit());
}
WebDataSourceUtils.setConnectionConfiguration(
dataSource.getDriver(),
@@ -81,6 +85,7 @@ public class WebConnectionConfigInputHandler<T extends WebConnectionConfig, C ex
input
);
WebDataSourceUtils.saveAuthProperties(
webSession.getProgressMonitor(),
dataSource,
dataSource.getConnectionConfiguration(),
input.getCredentials(),
@@ -24,53 +24,46 @@ 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.DBPAdaptable;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.DBPObjectSettingsProvider;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistryCache;
import org.jkiss.dbeaver.model.navigator.DBNModel;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.rm.RMUtils;
import org.jkiss.dbeaver.model.security.SMControllerUtils;
import org.jkiss.dbeaver.model.security.SMObjectType;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceEvent;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.registry.DataSourceRegistry;
import org.jkiss.dbeaver.registry.project.BaseProjectSettings;
import org.jkiss.dbeaver.runtime.jobs.DisconnectJob;
import org.jkiss.utils.CommonUtils;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.*;
public class WebSessionProjectImpl extends WebProjectImpl {
public class WebSessionProjectImpl extends WebProjectImpl implements DBPAdaptable {
private static final Log log = Log.getLog(WebSessionProjectImpl.class);
protected final WebSession webSession;
private final Map<String, WebConnectionInfo> connections = new HashMap<>();
private final Map<String, Object> projectSettings = new ConcurrentHashMap<>();
private final BaseProjectSettings projectSettings;
private boolean registryIsLoaded = false;
public WebSessionProjectImpl(
@NotNull WebSession webSession,
@NotNull RMProject project
) {
super(
webSession.getWorkspace(),
webSession.getRmController(),
webSession.getSessionContext(),
project,
webSession.getUserPreferenceStore(),
RMUtils.getProjectPath(project)
);
this.webSession = webSession;
this(webSession, project, null);
}
public WebSessionProjectImpl(
@NotNull WebSession webSession,
@NotNull RMProject project,
@NotNull Path path
@Nullable Path path
) {
super(
webSession.getWorkspace(),
@@ -78,9 +71,36 @@ public class WebSessionProjectImpl extends WebProjectImpl {
webSession.getSessionContext(),
project,
webSession.getUserPreferenceStore(),
path
path == null ? RMUtils.getProjectPath(project) : path
);
this.webSession = webSession;
this.projectSettings = new BaseProjectSettings(this) {
@NotNull
@Override
protected Map<SMObjectType, Map<String, Map<String, String>>> loadAllProjectSettings() throws DBException {
if (webSession.getUser() == null) {
return new LinkedHashMap<>();
}
return SMControllerUtils.getObjectSettingsMap(WebSessionProjectImpl.this, webSession.getSecurityController());
}
@Override
protected void saveProjectSettings(
@NotNull SMObjectType objectType,
@NotNull String objectId,
@NotNull Map<String, String> settings
) throws DBException {
if (webSession.getUserContext().isNonAnonymousUserAuthorizedInSM()) {
webSession.getSecurityController().setObjectSettings(getId(), objectType, objectId, settings);
}
}
};
}
@NotNull
public BaseProjectSettings getProjectSettings() {
return projectSettings;
}
@Nullable
@@ -89,6 +109,14 @@ public class WebSessionProjectImpl extends WebProjectImpl {
return webSession.getNavigatorModel();
}
@Override
public <T> T getAdapter(@NotNull Class<T> adapter) {
if (adapter == DBPObjectSettingsProvider.class) {
return adapter.cast(projectSettings);
}
return null;
}
@NotNull
@Override
protected DBPDataSourceRegistry createDataSourceRegistry() {
@@ -190,6 +218,7 @@ public class WebSessionProjectImpl extends WebProjectImpl {
*
* @return connections from cache.
*/
@NotNull
public List<WebConnectionInfo> getConnections() {
if (!registryIsLoaded) {
addDataSourcesToCache();
@@ -244,20 +273,6 @@ public class WebSessionProjectImpl extends WebProjectImpl {
return sendDataSourceUpdatedEvent;
}
public void refreshProjectSettings() throws DBException {
if (webSession.getUser() == null) {
projectSettings.clear();
return;
}
Map<String, Object> loadedSettings = webSession.getSecurityController().getObjectSettings(
getId(),
SMObjectType.project,
null
);
projectSettings.clear();
projectSettings.putAll(loadedSettings);
}
@NotNull
public WebConnectionInfo createConnectionInfo(@NotNull DBPDataSourceContainer dataSourceDescriptor) {
return new WebConnectionInfo(webSession, dataSourceDescriptor);
@@ -268,7 +283,7 @@ public class WebSessionProjectImpl extends WebProjectImpl {
if (CommonUtils.isEmpty(configMap)) {
throw new DBWebException("Connection configuration parameters are missing");
}
DBPDataSourceContainer newDataSource = getDataSourceContainerFromInput(configMap);
DBPDataSourceContainer newDataSource = getDataSourceContainerFromInput(getConnectionConfigInput(configMap));
return addDataSourceToProject(newDataSource);
}
@@ -302,7 +317,7 @@ public class WebSessionProjectImpl extends WebProjectImpl {
webSession.addInfoMessage("Update connection - " + WebDataSourceUtils.getConnectionContainerInfo(dataSource));
DBPDataSourceRegistry registry = getDataSourceRegistry();
getInputConfigHandler(configMap).updateDataSource(dataSource);
getInputConfigHandler(config).updateDataSource(dataSource);
connectionInfo.setCredentialsSavedInSession(null);
try {
registry.updateDataSource(dataSource);
@@ -330,26 +345,16 @@ public class WebSessionProjectImpl extends WebProjectImpl {
@NotNull
private WebConnectionInfo closeAndDeleteConnection(@NotNull WebConnectionInfo connectionInfo) throws DBWebException {
DBPDataSourceContainer dataSourceContainer = connectionInfo.getDataSourceContainer();
boolean disconnected = WebDataSourceUtils.disconnectDataSource(webSession, dataSourceContainer);
WebDataSourceUtils.disconnectDataSource(webSession, dataSourceContainer);
DBPDataSourceRegistry registry = getDataSourceRegistry();
registry.removeDataSource(dataSourceContainer);
try {
registry.checkForErrors();
} catch (DBException e) {
try {
registry.addDataSource(dataSourceContainer);
} catch (DBException ex) {
log.error("Error re-adding after delete attempt", e);
}
throw new DBWebException("Failed to delete connection", e);
}
removeConnection(dataSourceContainer);
return connectionInfo;
}
@NotNull
public DataSourceDescriptor getDataSourceContainerFromInput(@NotNull Map<String, Object> configMap) throws DBWebException {
return getInputConfigHandler(configMap).createDataSourceContainer();
public DataSourceDescriptor getDataSourceContainerFromInput(@NotNull WebConnectionConfig configInput) throws DBWebException {
return getInputConfigHandler(configInput).createDataSourceContainer();
}
@NotNull
@@ -358,8 +363,8 @@ public class WebSessionProjectImpl extends WebProjectImpl {
}
@NotNull
protected WebConnectionConfigInputHandler getInputConfigHandler(@NotNull Map<String, Object> configMap) {
return new WebConnectionConfigInputHandler<>(getDataSourceRegistry(), getConnectionConfigInput(configMap));
protected WebConnectionConfigInputHandler getInputConfigHandler(@NotNull WebConnectionConfig configInput) {
return new WebConnectionConfigInputHandler<>(webSession, getDataSourceRegistry(), configInput);
}
@@ -0,0 +1,40 @@
/*
* 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;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.utils.WebEventUtils;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.runtime.AbstractJob;
public abstract class AbstractCancelableJob extends AbstractJob implements CustomCancelableJob {
protected AbstractCancelableJob(@NotNull String name) {
super(name);
}
@Override
public void cancelJob(@NotNull WebSession webSession, @NotNull WebAsyncTaskInfo taskInfo) {
cancelJob(webSession, taskInfo, "Canceled by the user");
}
public void cancelJob(@NotNull WebSession webSession, @NotNull WebAsyncTaskInfo taskInfo, @NotNull String errorMessage) {
taskInfo.setRunning(false);
taskInfo.setJobError(new DBException(errorMessage));
WebEventUtils.sendAsyncTaskEvent(webSession, taskInfo);
}
}
@@ -1,3 +1,19 @@
/*
* 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;
import org.jkiss.code.NotNull;
@@ -0,0 +1,50 @@
/*
* 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;
import org.jkiss.code.NotNull;
public class WebBasicEnumObjectInfo {
@NotNull
private String displayName;
@NotNull
private String value;
public WebBasicEnumObjectInfo(@NotNull String displayName, @NotNull String value) {
this.displayName = displayName;
this.value = value;
}
@NotNull
public String getDisplayName() {
return displayName;
}
public void setDisplayName(@NotNull String displayName) {
this.displayName = displayName;
}
@NotNull
public String getValue() {
return value;
}
public void setValue(@NotNull String value) {
this.value = value;
}
}
@@ -42,7 +42,7 @@ public class WebConnectionConfig {
private String databaseName;
private String url;
private int keepAliveInterval;
private Integer keepAliveInterval;
private String name;
private String description;
@@ -53,15 +53,15 @@ public class WebConnectionConfig {
private String authModelId;
private Map<String, Object> credentials;
private boolean saveCredentials;
private boolean sharedCredentials;
private Boolean saveCredentials;
private Boolean sharedCredentials;
private Map<String, Object> mainPropertyValues;
private Map<String, Object> expertSettingsValues;
private Map<String, Object> providerProperties;
private List<WebNetworkHandlerConfigInput> networkHandlersConfig;
private DBPDriverConfigurationType configurationType;
private String selectedSecretId;
private boolean defaultAutoCommit;
private Boolean defaultAutoCommit;
private String defaultCatalogName;
private String defaultSchemaName;
@@ -122,102 +122,178 @@ public class WebConnectionConfig {
return connectionId;
}
public void setConnectionId(String connectionId) {
this.connectionId = connectionId;
}
@Property
public String getDriverId() {
return driverId;
}
public void setDriverId(String driverId) {
this.driverId = driverId;
}
@Property
public boolean isReadOnly() {
return readOnly;
}
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
@Property
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Property
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Property
public String getFolder() {
return folder;
}
public void setFolder(String folder) {
this.folder = folder;
}
@Property
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
@Property
public String getPort() {
return port;
}
public void setPort(String port) {
this.port = port;
}
@Property
public String getServerName() {
return serverName;
}
public void setServerName(String serverName) {
this.serverName = serverName;
}
@Property
public String getDatabaseName() {
return databaseName;
}
public void setDatabaseName(String databaseName) {
this.databaseName = databaseName;
}
@Property
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
@Property
public Map<String, Object> getProperties() {
return properties;
}
public void setProperties(Map<String, Object> properties) {
this.properties = properties;
}
@Property
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Property
public String getUserPassword() {
return userPassword;
}
public void setUserPassword(String userPassword) {
this.userPassword = userPassword;
}
@Property
public String getAuthModelId() {
return authModelId;
}
public void setAuthModelId(String authModelId) {
this.authModelId = authModelId;
}
@Property
public DBPDriverConfigurationType getConfigurationType() {
return configurationType;
}
public void setConfigurationType(DBPDriverConfigurationType configurationType) {
this.configurationType = configurationType;
}
@Property
public Map<String, Object> getCredentials() {
return credentials;
}
public void setCredentials(Map<String, Object> credentials) {
this.credentials = credentials;
}
public List<WebNetworkHandlerConfigInput> getNetworkHandlersConfig() {
return networkHandlersConfig;
}
public void setNetworkHandlersConfig(List<WebNetworkHandlerConfigInput> networkHandlersConfig) {
this.networkHandlersConfig = networkHandlersConfig;
}
@Property
public boolean isSaveCredentials() {
return saveCredentials;
}
public void setSaveCredentials(boolean saveCredentials) {
this.saveCredentials = saveCredentials;
}
@Property
public boolean isSharedCredentials() {
return sharedCredentials;
}
public void setSaveCredentials(boolean saveCredentials) {
this.saveCredentials = saveCredentials;
public void setSharedCredentials(boolean sharedCredentials) {
this.sharedCredentials = sharedCredentials;
}
@Property
@@ -225,38 +301,70 @@ public class WebConnectionConfig {
return mainPropertyValues;
}
public void setMainPropertyValues(Map<String, Object> mainPropertyValues) {
this.mainPropertyValues = mainPropertyValues;
}
@Property
public Map<String, Object> getExpertSettingsValues() {
return expertSettingsValues;
}
public void setExpertSettingsValues(Map<String, Object> expertSettingsValues) {
this.expertSettingsValues = expertSettingsValues;
}
@Property
public Map<String, Object> getProviderProperties() {
return providerProperties;
}
public void setProviderProperties(Map<String, Object> providerProperties) {
this.providerProperties = providerProperties;
}
@Property
public Integer getKeepAliveInterval() {
return keepAliveInterval;
}
public void setKeepAliveInterval(int keepAliveInterval) {
this.keepAliveInterval = keepAliveInterval;
}
@Property
public Boolean isDefaultAutoCommit() {
return defaultAutoCommit;
}
public void setDefaultAutoCommit(boolean defaultAutoCommit) {
this.defaultAutoCommit = defaultAutoCommit;
}
@Nullable
public String getSelectedSecretId() {
return selectedSecretId;
}
public void setSelectedSecretId(String selectedSecretId) {
this.selectedSecretId = selectedSecretId;
}
@Property
public String getDefaultCatalogName() {
return defaultCatalogName;
}
public void setDefaultCatalogName(String defaultCatalogName) {
this.defaultCatalogName = defaultCatalogName;
}
@Property
public String getDefaultSchemaName() {
return defaultSchemaName;
}
public void setDefaultSchemaName(String defaultSchemaName) {
this.defaultSchemaName = defaultSchemaName;
}
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -20,6 +20,7 @@ import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebProjectImpl;
import io.cloudbeaver.model.app.BaseWebAppConfiguration;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.server.CBConstants;
import io.cloudbeaver.service.security.SMUtils;
import io.cloudbeaver.service.sql.WebDataFormat;
import io.cloudbeaver.utils.CBModelConstants;
@@ -59,7 +60,6 @@ import java.util.stream.Collectors;
public class WebConnectionInfo {
private static final Log log = Log.getLog(WebConnectionInfo.class);
public static final String SECURED_VALUE = "********";
private static final String FEATURE_HAS_TOOLS = "hasTools";
private static final String FEATURE_CONNECTED = "connected";
@@ -127,7 +127,7 @@ public class WebConnectionInfo {
if (canViewReadOnlyConnections()) {
return dataSourceContainer.getConnectionConfiguration().getHostName();
}
return SECURED_VALUE;
return CBConstants.SECURED_VALUE;
}
@Property
@@ -135,7 +135,7 @@ public class WebConnectionInfo {
if (canViewReadOnlyConnections()) {
return dataSourceContainer.getConnectionConfiguration().getHostPort();
}
return SECURED_VALUE;
return CBConstants.SECURED_VALUE;
}
@Property
@@ -148,7 +148,7 @@ public class WebConnectionInfo {
if (canViewReadOnlyConnections()) {
return dataSourceContainer.getConnectionConfiguration().getDatabaseName();
}
return SECURED_VALUE;
return CBConstants.SECURED_VALUE;
}
@Property
@@ -156,7 +156,7 @@ public class WebConnectionInfo {
if (canViewReadOnlyConnections()) {
return dataSourceContainer.getConnectionConfiguration().getUrl();
}
return SECURED_VALUE;
return CBConstants.SECURED_VALUE;
}
@Property
@@ -287,6 +287,12 @@ public class WebConnectionInfo {
return dataSourceContainer.getNavigatorSettings();
}
@Property
@NotNull
public DBNBrowseSettings getDefaultNavigatorSettings() {
return dataSourceContainer.getNavigatorSettings().getOriginalSettings();
}
@Property
@NotNull
public List<WebDataFormat> getSupportedDataFormats() {
@@ -341,6 +347,16 @@ public class WebConnectionInfo {
if (CommonUtils.isEmpty(authModelId)) {
authModelId = AuthModelDatabaseNative.ID;
}
// Patch auth model
DBPAuthModelDescriptor authModel = DBWorkbench.getPlatform().getDataSourceProviderRegistry().getAuthModel(authModelId);
if (authModel != null) {
DBPAuthModelDescriptor amReplace = authModel.getReplacedBy(dataSourceContainer.getDriver());
if (amReplace != null && amReplace != authModel) {
return amReplace.getId();
}
}
return authModelId;
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -16,6 +16,7 @@
*/
package io.cloudbeaver.model;
import io.cloudbeaver.server.CBConstants;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration;
import org.jkiss.dbeaver.model.net.DBWHandlerType;
@@ -88,7 +89,9 @@ public class WebNetworkHandlerConfig {
DBPPropertyDescriptor[] descriptor = configuration.getHandlerDescriptor().getHandlerProperties();
for (DBPPropertyDescriptor p : descriptor) {
if (p.hasFeature(RegistryConstants.ATTR_PASSWORD)) {
secureProperties.computeIfPresent(p.getId(), (k, v) -> CommonUtils.isEmpty(v) ? null : WebConnectionInfo.SECURED_VALUE);
secureProperties.computeIfPresent(p.getId(), (k, v) ->
CommonUtils.isEmpty(v) ? null : CBConstants.SECURED_VALUE
);
}
}
return secureProperties;
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 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,11 +17,13 @@
package io.cloudbeaver.model;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.server.CBConstants;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.*;
import org.jkiss.dbeaver.model.connection.DBPDriverConfigurationType;
import org.jkiss.dbeaver.model.impl.PropertyDescriptor;
import org.jkiss.dbeaver.model.impl.ProviderPropertyDescriptor;
import org.jkiss.dbeaver.model.meta.IPropertyValueListProvider;
import org.jkiss.dbeaver.model.meta.Property;
@@ -30,6 +32,7 @@ import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
import org.jkiss.dbeaver.model.preferences.DBPPropertySource;
import org.jkiss.dbeaver.registry.settings.ProductSettingDescriptor;
import org.jkiss.dbeaver.runtime.properties.ObjectPropertyDescriptor;
import org.jkiss.utils.ArrayUtils;
import org.jkiss.utils.CommonUtils;
import java.lang.reflect.Array;
@@ -136,13 +139,14 @@ public class WebPropertyInfo {
@Property
public Object getValue() throws DBException {
Object value = propertySource == null ? null : propertySource.getPropertyValue(session.getProgressMonitor(), property.getId());
if (property instanceof ObjectPropertyDescriptor) {
ObjectPropertyDescriptor opd = (ObjectPropertyDescriptor) property;
if (!showProtected && opd.isPassword() || opd.isHidden()) {
if (value == null || value.toString().isEmpty()) {
return "";
}
return "******";
if (property instanceof ObjectPropertyDescriptor opd &&
(!showProtected && opd.isPassword() || opd.isHidden())
) {
return maskValue(value);
} else if (property instanceof PropertyDescriptor pd) {
String[] features = pd.getFeatures();
if (features != null && ArrayUtils.contains(features, DBConstants.PROP_FEATURE_PASSWORD)) {
return maskValue(value);
}
}
return value == null ? null : makePropertyValue(value);
@@ -243,9 +247,11 @@ public class WebPropertyInfo {
List<Object> result = new ArrayList<>();
int length = Array.getLength(value);
for (int i = 0; i < length; i++) {
result.add(Array.get(value, i));
result.add(makePropertyValue(Array.get(value, i)));
}
return result;
} else if (value instanceof Enum<?> enumValue) {
return new WebBasicEnumObjectInfo(enumValue.toString(), enumValue.name());
}
Class<?> dataType = property.getDataType();
if (dataType == Boolean.class || dataType == Boolean.TYPE) {
@@ -305,4 +311,13 @@ public class WebPropertyInfo {
READ_ONLY
}
}
@NotNull
private static String maskValue(@Nullable Object value) {
if (value == null || value.toString().isEmpty()) {
return "";
} else {
return CBConstants.SECURED_VALUE;
}
}
}
@@ -20,13 +20,11 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.registry.fs.FileSystemProviderRegistry;
import org.jkiss.dbeaver.model.fs.DBFUtils;
import org.jkiss.utils.CommonUtils;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
/**
@@ -65,30 +63,20 @@ public abstract class BaseServerConfigurationController<T extends ServletServerC
if (CommonUtils.isEmpty(workspaceLocation)) {
return defaultWorkspaceLocation;
}
URI workspaceUri = URI.create(workspaceLocation);
if (workspaceUri.getScheme() == null) {
// default filesystem
return getHomeDirectory().resolve(workspaceLocation);
} else {
var externalFsProvider =
FileSystemProviderRegistry.getInstance().getFileSystemProviderBySchema(workspaceUri.getScheme());
if (externalFsProvider == null) {
log.error("File system not found for scheme: " + workspaceUri.getScheme() + " default workspace " +
"location will be used");
try {
Path externalFsPath = DBFUtils.getPathFromURI(workspaceLocation);
if (externalFsPath == null) {
log.warn("Failed to detect workspace path from URI: " + workspaceLocation +
" default workspace location will be used");
return defaultWorkspaceLocation;
}
ClassLoader fsClassloader = externalFsProvider.getInstance().getClass().getClassLoader();
try (FileSystem externalFileSystem = FileSystems.newFileSystem(workspaceUri,
System.getenv(),
fsClassloader)) {
log.info("Path from external filesystem used for workspace: " + workspaceUri);
return externalFileSystem.provider().getPath(workspaceUri);
} catch (Exception e) {
log.error("Failed to initialize workspace path: " + workspaceUri + " default workspace " +
"location will be used", e);
}
log.info("Path from external filesystem used for workspace: " + externalFsPath);
return externalFsPath;
} catch (DBException e) {
log.error("Failed to detect workspace path from URI: " + workspaceLocation +
" default workspace location will be used", e);
return defaultWorkspaceLocation;
}
return defaultWorkspaceLocation;
}
@Nullable
@@ -16,8 +16,8 @@
*/
package io.cloudbeaver.model.app;
import io.cloudbeaver.model.cli.CloudBeaverInstanceServer;
import io.cloudbeaver.model.log.SLF4JLogHandler;
import io.cloudbeaver.registry.WebFeatureRegistry;
import org.eclipse.core.runtime.Platform;
import org.eclipse.equinox.app.IApplicationContext;
import org.jkiss.code.NotNull;
@@ -28,7 +28,6 @@ import org.jkiss.dbeaver.model.DBFileController;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
import org.jkiss.dbeaver.model.auth.SMSessionContext;
import org.jkiss.dbeaver.model.cli.ApplicationInstanceController;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.impl.app.ApplicationRegistry;
import org.jkiss.dbeaver.model.impl.app.BaseApplicationImpl;
@@ -41,7 +40,6 @@ import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.dbeaver.utils.RuntimeUtils;
import org.jkiss.utils.CommonUtils;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
@@ -60,7 +58,7 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
private static final Log log = Log.getLog(BaseServletApplication.class);
private String instanceId;
private CloudBeaverInstanceServer instanceServer;
@Override
public RMController createResourceController(
@NotNull SMCredentialsProvider credentialsProvider,
@@ -163,6 +161,7 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
* Method returns VoidSecretController instance.
* Advanced apps may implement it differently.
*/
@NotNull
@Override
public DBSSecretController getSecretController(
@NotNull SMCredentialsProvider credentialsProvider,
@@ -204,11 +203,6 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
public Object start(IApplicationContext context) {
initializeApplicationServices();
try {
try {
this.instanceServer = createInstanceServer();
} catch (Exception e) {
log.error("Error initializing instance server", e);
}
startServer();
} catch (Exception e) {
log.error(e.getMessage(), e);
@@ -217,12 +211,9 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
return EXIT_OK;
}
protected CloudBeaverInstanceServer createInstanceServer() throws IOException {
return new CloudBeaverInstanceServer();
}
protected abstract void startServer() throws DBException;
@NotNull
@Override
public synchronized String getApplicationInstanceId() throws DBException {
if (instanceId == null) {
@@ -247,6 +238,7 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
return BaseWorkspaceImpl.readWorkspaceIdProperty();
}
@NotNull
@Override
public Path getWorkspaceDirectory() {
return getServerConfigurationController().getWorkspacePath();
@@ -261,6 +253,7 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
}
}
@NotNull
@Override
public WSEventController getEventController() {
return null;
@@ -281,14 +274,14 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
}
}
@Nullable
@Override
public ApplicationInstanceController getInstanceServer() {
return instanceServer;
}
@Override
public boolean isAnonymousAccessEnabled() {
return getAppConfiguration().isAnonymousAccessEnabled();
}
@NotNull
@Override
public WebFeatureRegistry getFeatureRegistry() {
return WebFeatureRegistry.getInstance();
}
}
@@ -17,7 +17,6 @@
package io.cloudbeaver.model.app;
import io.cloudbeaver.DBWFeatureSet;
import io.cloudbeaver.registry.WebFeatureRegistry;
import io.cloudbeaver.utils.ServletAppUtils;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.DBConstants;
@@ -115,7 +114,7 @@ public abstract class BaseWebAppConfiguration implements ServletAppConfiguration
public String[] getEnabledFeatures() {
if (enabledFeatures == null) {
// No config - enable all features (+backward compatibility)
return WebFeatureRegistry.getInstance().getWebFeatures()
return ServletAppUtils.getServletApplication().getFeatureRegistry().getWebFeatures()
.stream().map(DBWFeatureSet::getId).toArray(String[]::new);
}
return enabledFeatures;
@@ -16,7 +16,9 @@
*/
package io.cloudbeaver.model.app;
import io.cloudbeaver.registry.WebFeatureRegistry;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.DBFileController;
import org.jkiss.dbeaver.model.app.DBPApplication;
@@ -42,14 +44,19 @@ public interface ServletApplication extends DBPApplication {
return false;
}
@NotNull
ServletAppConfiguration getAppConfiguration();
@NotNull
ServletServerConfiguration getServerConfiguration();
@NotNull
Path getDataDirectory(boolean create);
@NotNull
Path getWorkspaceDirectory();
@NotNull
Path getHomeDirectory();
boolean isMultiNode();
@@ -58,30 +65,35 @@ public interface ServletApplication extends DBPApplication {
SMAdminController getAdminSecurityController(@NotNull SMCredentialsProvider credentialsProvider) throws DBException;
@NotNull
DBSSecretController getSecretController(
@NotNull SMCredentialsProvider credentialsProvider,
SMSessionContext smSessionContext
) throws DBException;
@NotNull
RMController createResourceController(
@NotNull SMCredentialsProvider credentialsProvider,
@NotNull DBPWorkspace workspace
) throws DBException;
@NotNull
DBFileController createFileController(@NotNull SMCredentialsProvider credentialsProvider);
String getServerURL();
@NotNull
default String getServicesURI() {
return "/";
}
@NotNull
default String getRootURI() {
return "";
}
@NotNull
String getApplicationInstanceId() throws DBException;
@NotNull
WSEventController getEventController();
/**
@@ -94,12 +106,18 @@ public interface ServletApplication extends DBPApplication {
/**
* Collector that contains information about system.
*/
@NotNull
ServletSystemInformationCollector getSystemInformationCollector();
@Nullable
ServletSystemInformationCollector<?> getSystemInformationCollector();
default void getStatusInfo(Map<String, Object> infoMap) {
default void getStatusInfo(@NotNull Map<String, Object> infoMap) {
}
boolean isAnonymousAccessEnabled();
/**
* Web feature registry
*/
@NotNull
WebFeatureRegistry getFeatureRegistry();
}
@@ -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.
@@ -28,7 +28,7 @@ import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import java.nio.file.Path;
public class FSUtils {
public class WebFSUtils {
@NotNull
public static String makeUniqueFsId(@NotNull DBFVirtualFileSystem fileSystem) {
return fileSystem.getType() + "://" + fileSystem.getId();
@@ -31,8 +31,6 @@ import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.rm.RMResource;
import org.jkiss.dbeaver.model.rm.RMResourceType;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.registry.ResourceTypeDescriptor;
import org.jkiss.dbeaver.registry.ResourceTypeRegistry;
import org.jkiss.utils.ArrayUtils;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.IOUtils;
@@ -44,7 +42,7 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
private final RMResource resource;
DBNResourceManagerResource(DBNNode parentNode, RMResource resource) {
DBNResourceManagerResource(@NotNull DBNNode parentNode, @NotNull RMResource resource) {
super(parentNode);
this.resource = resource;
}
@@ -71,6 +69,7 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
@Override
public DBPImage getNodeIcon() {
if (resource.isFolder()) {
/*
if (getParentNode() instanceof DBNResourceManagerResource) {
return DBIcon.TREE_FOLDER;
}
@@ -82,11 +81,12 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
if (folderResType != null) {
return folderResType.getFolderIcon();
}
*/
return DBIcon.TREE_FOLDER;
} else {
var fileExtension = IOUtils.getFileExtension(getNodeDisplayName());
if (!CommonUtils.isEmpty(fileExtension)) {
RMProject project = getProjectNode();
RMProject project = getRmProject();
if (project != null) {
for (RMResourceType rt : project.getResourceTypes()) {
if (ArrayUtils.contains(rt.getFileExtensions(), fileExtension)) {
@@ -99,7 +99,8 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
}
}
private RMProject getProjectNode() {
@Nullable
public RMProject getRmProject() {
for (DBNNode node = this; node != null; node = node.getParentNode()) {
if (node instanceof DBNResourceManagerProject) {
return ((DBNResourceManagerProject) node).getProject();
@@ -16,7 +16,6 @@
*/
package io.cloudbeaver.model.rm.local;
import io.cloudbeaver.BaseWebProjectImpl;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
@@ -28,7 +27,6 @@ import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.fs.lock.FileLockController;
import org.jkiss.dbeaver.model.impl.auth.SessionContextImpl;
import org.jkiss.dbeaver.model.rm.RMController;
import org.jkiss.dbeaver.model.rm.RMEvent;
import org.jkiss.dbeaver.model.rm.RMEventManager;
@@ -51,8 +49,6 @@ public abstract class BaseLocalResourceController implements RMController {
private static final Log log = Log.getLog(BaseLocalResourceController.class);
public static final String DEFAULT_CHANGE_ID = "0";
private static final String FILE_REGEX = "(?U)[\\w.$()@/\\\\ -]+";
private static final String PROJECT_REGEX = "(?U)[\\w.$()@ -]+"; // slash not allowed in project name
@NotNull
protected final DBPWorkspace workspace;
@@ -67,6 +63,7 @@ public abstract class BaseLocalResourceController implements RMController {
this.lockController = lockController;
}
@Nullable
@Override
public RMProject getProject(@NotNull String projectId, boolean readResources, boolean readProperties)
throws DBException {
@@ -85,6 +82,7 @@ public abstract class BaseLocalResourceController implements RMController {
return project;
}
@Nullable
@Override
public Object getProjectProperty(@NotNull String projectId, @NotNull String propName) throws DBException {
var project = getWebProject(projectId, false);
@@ -97,7 +95,7 @@ public abstract class BaseLocalResourceController implements RMController {
public void setProjectProperty(
@NotNull String projectId,
@NotNull String propName,
@NotNull Object propValue
@Nullable Object propValue
) throws DBException {
RMLocalProject webProject = getWebProject(projectId, false);
doFileWriteOperation(projectId, webProject.getMetadataFilePath(),
@@ -159,7 +157,7 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String configuration,
@Nullable List<String> dataSourceIds
) throws DBException {
try (var lock = lockController.lock(projectId, "updateProjectDataSources")) {
try (var ignoredLock = lockController.lock(projectId, "updateProjectDataSources")) {
DBPProject project = getWebProject(projectId, false);
return doFileWriteOperation(
projectId, project.getMetadataFolder(false),
@@ -190,7 +188,7 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String projectId,
@NotNull String[] dataSourceIds
) throws DBException {
try (var projectLock = lockController.lock(projectId, "deleteDatasources")) {
try (var ignoredLock = lockController.lock(projectId, "deleteDataSources")) {
DBPProject project = getWebProject(projectId, false);
doFileWriteOperation(projectId, project.getMetadataFolder(false), () -> {
DBPDataSourceRegistry registry = project.getDataSourceRegistry();
@@ -215,7 +213,7 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String projectId,
@NotNull String folderPath
) throws DBException {
try (var projectLock = lockController.lock(projectId, "createDatasourceFolder")) {
try (var ignoredLock = lockController.lock(projectId, "createDatasourceFolder")) {
DBPProject project = getWebProject(projectId, false);
log.debug("Creating data source folder '" + folderPath + "' in project '" + projectId + "'");
doFileWriteOperation(projectId, project.getMetadataFolder(false),
@@ -226,8 +224,10 @@ public abstract class BaseLocalResourceController implements RMController {
GeneralUtils.validateResourceName(newName);
var parent = result.getParent();
var parentFolder = parent == null ? null : registry.getFolder(parent.toString().replace("\\", "/"));
DBPDataSourceFolder newFolder = registry.addFolder(parentFolder, newName);
((DataSourcePersistentRegistry) registry).saveDataSources();
registry.addFolder(parentFolder, newName);
if (registry instanceof DataSourcePersistentRegistry pr) {
pr.saveDataSources();
}
registry.checkForErrors();
return null;
}
@@ -241,21 +241,19 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String[] folderPaths,
boolean dropContents
) throws DBException {
try (var projectLock = lockController.lock(projectId, "createDatasourceFolder")) {
try (var ignoredLock = lockController.lock(projectId, "createDatasourceFolder")) {
DBPProject project = getWebProject(projectId, false);
doFileWriteOperation(projectId, project.getMetadataFolder(false),
() -> {
DBPDataSourceRegistry registry = project.getDataSourceRegistry();
for (String folderPath : folderPaths) {
DBPDataSourceFolder folder = registry.getFolder(folderPath);
if (folder != null) {
log.debug("Deleting data source folder '" + folderPath + "' in project '" + projectId + "'");
registry.removeFolder(folder, dropContents);
} else {
log.warn("Can not find folder by path [" + folderPath + "] for deletion");
}
log.debug("Deleting data source folder '" + folderPath + "' in project '" + projectId + "'");
registry.removeFolder(folder, dropContents);
}
if (registry instanceof DataSourcePersistentRegistry pr) {
pr.saveDataSources();
}
((DataSourcePersistentRegistry) registry).saveDataSources();
registry.checkForErrors();
return null;
}
@@ -269,7 +267,7 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String oldPath,
@NotNull String newPath
) throws DBException {
try (var projectLock = lockController.lock(projectId, "createDatasourceFolder")) {
try (var ignoredLock = lockController.lock(projectId, "createDatasourceFolder")) {
DBPProject project = getWebProject(projectId, false);
log.debug("Moving data source folder from '" + oldPath + "' to '" + newPath + "' in project '" + projectId + "'");
doFileWriteOperation(projectId, project.getMetadataFolder(false),
@@ -284,19 +282,19 @@ public abstract class BaseLocalResourceController implements RMController {
}
}
protected abstract RMLocalProject getWebProject(String projectId, boolean refresh) throws DBException;
protected abstract RMLocalProject getWebProject(@NotNull String projectId, boolean refresh) throws DBException;
protected abstract <T> T doFileWriteOperation(String projectId, Path file, RMFileOperation<T> operation)
protected abstract <T> T doFileWriteOperation(@NotNull String projectId, @NotNull Path file, @NotNull RMFileOperation<T> operation)
throws DBException;
protected abstract <T> T doFileReadOperation(String projectId, Path file, RMFileOperation<T> operation)
protected abstract <T> T doFileReadOperation(@NotNull String projectId, @NotNull Path file, @NotNull RMFileOperation<T> operation)
throws DBException;
protected abstract <T> T doProjectOperation(String projectId, RMFileOperation<T> operation) throws DBException;
protected abstract <T> T doProjectOperation(@NotNull String projectId, @NotNull RMFileOperation<T> operation) throws DBException;
protected abstract RMProject makeProjectFromId(String projectId, boolean loadPermissions) throws DBException;
protected abstract RMProject makeProjectFromId(@NotNull String projectId, boolean loadPermissions) throws DBException;
protected void validateResourcePath(String resourcePath) throws DBException {
protected void validateResourcePath(@NotNull String resourcePath) throws DBException {
var fullPath = Paths.get(resourcePath);
for (Path path : fullPath) {
String fileName = IOUtils.getFileNameWithoutExtension(path);
@@ -314,28 +312,6 @@ public abstract class BaseLocalResourceController implements RMController {
}
}
protected class InternalWebProjectImpl extends BaseWebProjectImpl {
public InternalWebProjectImpl(
@NotNull SessionContextImpl sessionContext,
@NotNull RMProject rmProject,
@NotNull Path projectPath
) {
super(
BaseLocalResourceController.this.workspace,
BaseLocalResourceController.this,
sessionContext,
rmProject,
projectPath
);
}
@NotNull
@Override
protected DBPDataSourceRegistry createDataSourceRegistry() {
return new DataSourceRegistry(this);
}
}
protected void fireRmResourceAddEvent(@NotNull String projectId, @NotNull String resourcePath) throws DBException {
RMEventManager.fireEvent(
new RMEvent(RMEvent.Action.RESOURCE_ADD,
@@ -38,7 +38,7 @@ 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.SMAdminController;
import org.jkiss.dbeaver.model.security.SMObjectType;
import org.jkiss.dbeaver.model.sql.DBQuotaException;
import org.jkiss.dbeaver.model.websocket.event.MessageType;
@@ -78,18 +78,19 @@ public class LocalResourceController extends BaseLocalResourceController {
private final Path userProjectsPath;
private final Path sharedProjectsPath;
private final String globalProjectName;
private Supplier<SMController> smControllerSupplier;
private Supplier<SMAdminController> smControllerSupplier;
protected final List<RMFileOperationHandler> fileHandlers;
private final Map<String, RMLocalProject> projectRegistries = new LinkedHashMap<>();
private final ProjectsMetadataInfo sharedProjectsMetadataInfo;
public LocalResourceController(
DBPWorkspace workspace,
SMCredentialsProvider credentialsProvider,
Path rootPath,
Path userProjectsPath,
Path sharedProjectsPath,
Supplier<SMController> smControllerSupplier
@NotNull DBPWorkspace workspace,
@NotNull SMCredentialsProvider credentialsProvider,
@NotNull Path rootPath,
@NotNull Path userProjectsPath,
@NotNull Path sharedProjectsPath,
@NotNull Supplier<SMAdminController> smControllerSupplier
) throws DBException {
super(workspace, new FileLockController(ServletAppUtils.getServletApplication().getApplicationInstanceId()));
this.credentialsProvider = credentialsProvider;
@@ -100,27 +101,34 @@ public class LocalResourceController extends BaseLocalResourceController {
this.globalProjectName = DBWorkbench.getPlatform().getApplication().getDefaultProjectName();
this.fileHandlers = RMFileOperationHandlersRegistry.getInstance().getFileHandlers();
this.sharedProjectsMetadataInfo = new ProjectsMetadataInfo(sharedProjectsPath);
}
protected SMController getSecurityController() {
@NotNull
protected SMAdminController getSecurityController() {
return smControllerSupplier.get();
}
@Nullable
private Path getGlobalProjectPath() {
return globalProjectName == null ? null : this.rootPath.resolve(this.globalProjectName);
}
@Nullable
private Path getPrivateProjectPath() {
SMCredentials activeUserCredentials = credentialsProvider.getActiveUserCredentials();
String userId = activeUserCredentials == null ? null : activeUserCredentials.getUserId();
return userId == null ? null : this.userProjectsPath.resolve(userId);
}
protected RMLocalProject getWebProject(String projectId, boolean refresh) throws DBException {
protected RMLocalProject getWebProject(@NotNull String projectId, boolean refresh) throws DBException {
synchronized (projectRegistries) {
RMLocalProject project = projectRegistries.get(projectId);
if (project == null || refresh) {
project = createWebProjectImpl(projectId, new SessionContextImpl(null));
if (project.getProjectType() == RMProjectType.SHARED) {
project.setProjectInfo(sharedProjectsMetadataInfo.getProjectInfo(projectId));
}
projectRegistries.put(projectId, project);
}
return project;
@@ -167,11 +175,6 @@ public class LocalResourceController extends BaseLocalResourceController {
projects.add(0, userProject);
}
}
if (ServletAppUtils.getServletApplication().isMultiNode()) {
for (RMProject rmProject : projects) {
handleProjectOpened(rmProject.getId());
}
}
projects.sort(Comparator.comparing(RMProject::getDisplayName));
return projects.toArray(new RMProject[0]);
@@ -289,9 +292,6 @@ public class LocalResourceController extends BaseLocalResourceController {
try {
log.debug("Creating project '" + rmProject.getId() + "'");
Files.createDirectories(projectPath);
if (ServletAppUtils.getServletApplication().isMultiNode()) {
createResourceTypeFolders(projectPath);
}
fireRmProjectAddEvent(rmProject);
return rmProject;
} catch (IOException e) {
@@ -299,10 +299,11 @@ public class LocalResourceController extends BaseLocalResourceController {
}
}
@NotNull
@Override
public RMProject updateProject(@NotNull String projectId, @NotNull RMProjectInfo projectInfo) throws DBException {
validateProjectName(projectId, projectInfo.getName());
try (var projectLock = lockController.lock(projectId, "updateProject")) {
try (var ignoredLock = lockController.lock(projectId, "updateProject")) {
RMLocalProject project = getWebProject(projectId, false);
Path targetPath = getProjectPath(projectId);
if (!Files.exists(targetPath)) {
@@ -312,6 +313,7 @@ public class LocalResourceController extends BaseLocalResourceController {
throw new DBException("Project '" + projectId + "' is not shared");
}
project.updateProject(projectInfo.getName(), projectInfo.getDescription());
sharedProjectsMetadataInfo.updateProjectInfo(projectId, projectInfo);
return WebRMUtils.createRmProjectFromWebProject(project);
}
}
@@ -328,26 +330,28 @@ public class LocalResourceController extends BaseLocalResourceController {
@Override
public void deleteProject(@NotNull String projectId) throws DBException {
try (var projectLock = lockController.lock(projectId, "deleteProject")) {
RMProject project = makeProjectFromId(projectId, false);
try (var ignoredLock = lockController.lock(projectId, "deleteProject")) {
Path targetPath = getProjectPath(projectId);
if (!Files.exists(targetPath)) {
log.debug(MessageFormat.format("Project folder ''{0}'' is not found", projectId));
log.error(MessageFormat.format("Project folder ''{0}'' is not found", projectId));
return;
}
try {
log.debug("Deleting project '" + projectId + "'");
log.debug("Deleting project '" + projectId + "' in persistence storage");
getSecurityController().deleteObject(projectId, projectId, SMObjectType.project);
log.debug("Deleting project '" + projectId + "' folder");
IOUtils.deleteDirectory(targetPath);
getSecurityController().deleteAllObjectPermissions(projectId, SMObjectType.project);
synchronized (projectRegistries) {
projectRegistries.remove(projectId);
}
sharedProjectsMetadataInfo.updateProjectInfo(projectId, null);
} catch (IOException e) {
throw new DBException("Error deleting project '" + project.getName() + "'", e);
throw new DBException("Error deleting project '" + projectId + "'", e);
}
}
}
@Nullable
@Override
public RMProject getProject(@NotNull String projectId, boolean readResources, boolean readProperties) throws DBException {
RMProject project = makeProjectFromId(projectId, true);
@@ -407,6 +411,15 @@ public class LocalResourceController extends BaseLocalResourceController {
@Override
public void deleteProjectDataSources(@NotNull String projectId, @NotNull String[] dataSourceIds) throws DBException {
super.deleteProjectDataSources(projectId, dataSourceIds);
log.debug("Delete datasources '" + Arrays.toString(dataSourceIds) + "' from security controller");
for (String dsId : dataSourceIds) {
try {
getSecurityController().deleteObject(projectId, dsId, SMObjectType.datasource);
} catch (DBException e) {
log.error("Error deleting datasource '" + dsId + "' from database");
}
}
if (credentialsProvider.getActiveUserCredentials() != null && dataSourceIds.length > 0) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDataSourceEvent.delete(
@@ -614,7 +627,8 @@ public class LocalResourceController extends BaseLocalResourceController {
}
return doProjectOperation(projectId, () -> {
try {
Path folderPath = CommonUtils.isEmpty(folder) ?
boolean projectFolder = CommonUtils.isEmpty(folder);
Path folderPath = projectFolder ?
projectPath :
projectPath.resolve(folder);
folderPath = folderPath.normalize();
@@ -623,7 +637,7 @@ public class LocalResourceController extends BaseLocalResourceController {
throw new DBException("Invalid folder path");
}
createFolder(folderPath);
return readChildResources(projectId, folderPath, nameMask, readProperties, readHistory, recursive);
return readChildResources(projectId, folderPath, nameMask, projectFolder, readProperties, readHistory, recursive);
} catch (NoSuchFileException e) {
throw new DBException("Invalid resource folder " + folder);
} catch (IOException e) {
@@ -637,22 +651,48 @@ public class LocalResourceController extends BaseLocalResourceController {
@NotNull String projectId,
@NotNull Path folderPath,
@Nullable String nameMask,
boolean projectFolder,
boolean readProperties,
boolean readHistory,
boolean recursive
) throws IOException {
try (Stream<Path> files = Files.list(folderPath)) {
return files.filter(path -> {
Set<RMResource> children = files.filter(path -> {
String fileName = path.getFileName().toString();
return (nameMask == null || nameMask.equals(fileName)) && !fileName.startsWith(".");
}) // skip hidden files
.sorted(Comparator.comparing(path -> path.getFileName().toString(), String.CASE_INSENSITIVE_ORDER))
.map((Path path) -> makeResourceFromPath(projectId, path, nameMask, readProperties, readHistory, recursive))
.filter(Objects::nonNull)
.collect(Collectors.toSet());
if (ServletAppUtils.getServletApplication().isMultiNode() && projectFolder) {
addResourceTypeFolders(folderPath, children);
}
return children.stream()
.sorted(Comparator.comparing(RMResource::getName, String.CASE_INSENSITIVE_ORDER))
.toArray(RMResource[]::new);
}
}
private void addResourceTypeFolders(@NotNull Path projectPath, @NotNull Set<RMResource> children) {
Set<String> folderNames = children.stream()
.filter(RMResource::isFolder)
.map(RMResource::getName)
.collect(Collectors.toSet());
// fake resources for default resource type folders
for (ResourceTypeDescriptor type : ResourceTypeRegistry.getInstance().getResourceTypes()) {
String defaultRoot = type.getDefaultRoot(null);
if (CommonUtils.isEmpty(defaultRoot)) {
continue;
}
Path resourceFolder = projectPath.resolve(defaultRoot);
if (!folderNames.contains(resourceFolder.getFileName().toString())) {
RMResource resource = new RMResource(resourceFolder.getFileName().toString());
resource.setFolder(true);
children.add(resource);
}
}
}
@Override
public String createResource(
@NotNull String projectId,
@@ -954,7 +994,7 @@ public class LocalResourceController extends BaseLocalResourceController {
}
@Nullable
protected RMProject makeProjectFromId(String projectId, boolean loadPermissions) throws DBException {
protected RMProject makeProjectFromId(@NotNull String projectId, boolean loadPermissions) throws DBException {
var projectName = WebRMUtils.parseProjectName(projectId);
var projectPath = getProjectPath(projectId);
if (!Files.exists(projectPath)) {
@@ -993,29 +1033,13 @@ public class LocalResourceController extends BaseLocalResourceController {
.toArray(String[]::new);
RMLocalProject webProject = new RMLocalProject(workspace, new SessionContextImpl(null), path, type);
if (type == RMProjectType.SHARED) {
webProject.setProjectInfo(sharedProjectsMetadataInfo.getProjectInfo(webProject.getId()));
}
return createRmProjectFromWebProject(path, webProject, allProjectPermissions);
}
private void createResourceTypeFolders(Path path) {
// FIXME: do not create folders by force!!!
var resourceTypes = ResourceTypeRegistry.getInstance().getResourceTypes();
for (var resourceType : resourceTypes) {
var defaultRoot = resourceType.getDefaultRoot(null);
if (defaultRoot == null) {
continue;
}
var typeFolder = path.resolve(defaultRoot);
try {
if (!Files.exists(typeFolder)) {
createFolder(typeFolder);
}
} catch (Exception e) {
log.error("Resource folder " + typeFolder + " is not created", e);
}
}
}
protected <T> T doProjectOperation(String projectId, RMFileOperation<T> operation) throws DBException {
protected <T> T doProjectOperation(@NotNull String projectId, @NotNull RMFileOperation<T> operation) throws DBException {
for (RMFileOperationHandler fileHandler : fileHandlers) {
try {
fileHandler.projectOpened(projectId);
@@ -1033,7 +1057,7 @@ public class LocalResourceController extends BaseLocalResourceController {
return operation.doOperation();
}
protected <T> T doFileReadOperation(String projectId, Path file, RMFileOperation<T> operation) throws DBException {
protected <T> T doFileReadOperation(@NotNull String projectId, @NotNull Path file, @NotNull RMFileOperation<T> operation) throws DBException {
for (RMFileOperationHandler fileHandler : fileHandlers) {
try {
fileHandler.beforeFileRead(projectId, file);
@@ -1052,7 +1076,11 @@ public class LocalResourceController extends BaseLocalResourceController {
return operation.doOperation();
}
protected <T> T doFileWriteOperation(String projectId, Path file, RMFileOperation<T> operation) throws DBException {
protected <T> T doFileWriteOperation(
@NotNull String projectId,
@NotNull Path file,
@NotNull RMFileOperation<T> operation
) throws DBException {
for (RMFileOperationHandler fileHandler : fileHandlers) {
fileHandler.beforeFileChange(projectId, file);
}
@@ -1071,7 +1099,7 @@ public class LocalResourceController extends BaseLocalResourceController {
return result;
}
protected Path getProjectPath(String projectId) throws DBException {
protected Path getProjectPath(@NotNull String projectId) throws DBException {
RMProjectName project = WebRMUtils.parseProjectName(projectId);
RMProjectType type = project.getType();
String projectName = project.getName();
@@ -1162,7 +1190,7 @@ public class LocalResourceController extends BaseLocalResourceController {
if (recursive && resource.isFolder()) {
try {
resource.setChildren(readChildResources(projectId, path, nameMask, readProperties, readHistory, true));
resource.setChildren(readChildResources(projectId, path, nameMask, false, readProperties, readHistory, true));
} catch (IOException e) {
log.error(e);
}
@@ -1176,14 +1204,10 @@ public class LocalResourceController extends BaseLocalResourceController {
return getProjectPath(projectId).toAbsolutePath().relativize(path).toString().replace('\\', IPath.SEPARATOR);
}
protected void handleProjectOpened(String projectId) throws DBException {
createResourceTypeFolders(getProjectPath(projectId));
}
public static Builder builder(
SMCredentialsProvider credentialsProvider,
DBPWorkspace workspace,
Supplier<SMController> smControllerSupplier
Supplier<SMAdminController> smControllerSupplier
) {
return new Builder(workspace, credentialsProvider, smControllerSupplier);
}
@@ -1195,7 +1219,7 @@ public class LocalResourceController extends BaseLocalResourceController {
public static class Builder {
protected final SMCredentialsProvider credentialsProvider;
protected final Supplier<SMController> smController;
protected final Supplier<SMAdminController> smController;
protected final DBPWorkspace workspace;
protected Path rootPath;
@@ -1204,7 +1228,7 @@ public class LocalResourceController extends BaseLocalResourceController {
protected Builder(
DBPWorkspace workspace, SMCredentialsProvider credentialsProvider,
Supplier<SMController> smControllerSupplier
Supplier<SMAdminController> smControllerSupplier
) {
this.workspace = workspace;
this.credentialsProvider = credentialsProvider;
@@ -0,0 +1,151 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.model.rm.local;
import com.google.gson.reflect.TypeToken;
import io.cloudbeaver.utils.ServletAppUtils;
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.app.DBPProject;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.fs.lock.FileLockController;
import org.jkiss.dbeaver.model.impl.app.BaseProjectImpl;
import org.jkiss.dbeaver.model.rm.RMProjectInfo;
import org.jkiss.dbeaver.model.rm.RMProjectType;
import org.jkiss.dbeaver.model.rm.RMUtils;
import org.jkiss.utils.CommonUtils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Stream;
public class ProjectsMetadataInfo {
private static final Log log = Log.getLog(ProjectsMetadataInfo.class);
private static final String PROJECTS_INFO_FILE_NAME = "projects-info.json";
private final Map<String, RMProjectInfo> projectsInfo = new LinkedHashMap<>();
private final Path projectsPath;
private final FileLockController lockController;
public ProjectsMetadataInfo(@NotNull Path projectsPath) throws DBException {
this.projectsPath = projectsPath;
this.lockController = new FileLockController(ServletAppUtils.getServletApplication().getApplicationInstanceId());
readProjectInfos(projectsPath);
}
public RMProjectInfo getProjectInfo(@NotNull String projectId) {
return projectsInfo.computeIfAbsent(projectId, key -> new RMProjectInfo());
}
public void updateProjectInfo(@NotNull String projectId, @Nullable RMProjectInfo projectInfo) {
if (projectInfo != null) {
projectsInfo.put(projectId, projectInfo);
} else {
projectsInfo.remove(projectId);
}
saveProjectsInfo();
}
private void readProjectInfos(@NotNull Path path) {
if (!Files.exists(path)) {
return;
}
Path projectInfoFile = path.resolve(PROJECTS_INFO_FILE_NAME);
if (!Files.exists(projectInfoFile)) {
loadAndMigrateProjectDataToCommonFile(path);
saveProjectsInfo();
return;
}
readProjectInfosFromFile(projectInfoFile);
}
private void readProjectInfosFromFile(@NotNull Path projectInfoFile) {
try {
log.info("Reading project metadata information");
String content = Files.readString(projectInfoFile, StandardCharsets.UTF_8);
Map<String, RMProjectInfo> loaded = JSONUtils.GSON.fromJson(
content,
TypeToken.getParameterized(Map.class, String.class, RMProjectInfo.class).getType()
);
projectsInfo.clear();
if (loaded != null) {
projectsInfo.putAll(loaded);
}
} catch (IOException e) {
log.error("Error reading existing " + PROJECTS_INFO_FILE_NAME, e);
}
}
private void loadAndMigrateProjectDataToCommonFile(@NotNull Path path) {
log.info("Migrating shared project information to the common place");
Map<String, RMProjectInfo> infos = new LinkedHashMap<>();
try (Stream<Path> stream = Files.list(path)) {
stream.filter(Files::isDirectory).forEach(projectDir -> {
RMProjectInfo info = getProjectInfoFromProjectSettings(projectDir);
String projectId = RMUtils.makeProjectIdFromPath(projectDir, RMProjectType.SHARED);
infos.put(projectId, info);
});
} catch (IOException e) {
log.error("Error listing shared projects path", e);
}
log.info("Migration for project information completed");
projectsInfo.clear();
projectsInfo.putAll(infos);
}
@NotNull
private RMProjectInfo getProjectInfoFromProjectSettings(@NotNull Path projectPath) {
String name = null;
String description = null;
Path settings = projectPath.resolve(DBPProject.METADATA_FOLDER).resolve(BaseProjectImpl.SETTINGS_STORAGE_FILE);
if (Files.exists(settings) && Files.isRegularFile(settings)) {
try {
String json = Files.readString(settings, StandardCharsets.UTF_8);
Map<String, Object> map = JSONUtils.GSON.fromJson(json, JSONUtils.MAP_TYPE_TOKEN);
name = JSONUtils.getString(map, BaseProjectImpl.PROP_PROJECT_NAME);
description = JSONUtils.getString(map, BaseProjectImpl.PROP_PROJECT_DESCRIPTION);
} catch (IOException e) {
log.warn("Failed to read project settings for " + projectPath + ": " + e.getMessage());
} catch (Exception e) {
log.warn("Failed to parse project settings for " + projectPath + ": " + e.getMessage());
}
}
RMProjectInfo info = new RMProjectInfo();
info.setName(CommonUtils.isEmpty(name) ? projectPath.getFileName().toString() : name);
info.setDescription(description);
return info;
}
private void saveProjectsInfo() {
try (var lock = lockController.lock(PROJECTS_INFO_FILE_NAME, "saveProjectsInfo")) {
log.info("Saving project information");
Files.writeString(projectsPath.resolve(PROJECTS_INFO_FILE_NAME), JSONUtils.GSON.toJson(projectsInfo));
} catch (IOException e) {
log.error("Error writing " + PROJECTS_INFO_FILE_NAME, e);
} catch (DBException e) {
log.error("Error locking file " + PROJECTS_INFO_FILE_NAME + ": " + e.getMessage());
}
}
}
@@ -18,8 +18,10 @@ package io.cloudbeaver.model.rm.local;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.auth.SMSessionContext;
import org.jkiss.dbeaver.model.rm.RMProjectInfo;
import org.jkiss.dbeaver.model.rm.RMProjectType;
import org.jkiss.dbeaver.model.rm.RMUtils;
import org.jkiss.dbeaver.registry.project.LocalProjectImpl;
@@ -29,6 +31,8 @@ import java.nio.file.Path;
public class RMLocalProject extends LocalProjectImpl {
@NotNull
private final RMProjectType projectType;
@Nullable
private RMProjectInfo projectInfo;
public RMLocalProject(
@NotNull DBPWorkspace workspace,
@@ -51,6 +55,39 @@ public class RMLocalProject extends LocalProjectImpl {
return RMUtils.makeProjectIdFromPath(projectPath, projectType);
}
@Nullable
@Override
public String getDescription() {
if (projectInfo == null) {
return null;
}
return projectInfo.getDescription();
}
@NotNull
@Override
public String getName() {
if (projectInfo == null || projectInfo.getName() == null) {
return projectPath.getFileName().toString();
}
return projectInfo.getName();
}
@NotNull
public RMProjectType getProjectType() {
return projectType;
}
@Override
public void updateProject(@Nullable String newName, @Nullable String description) throws DBException {
this.projectInfo = new RMProjectInfo();
projectInfo.setName(newName);
projectInfo.setDescription(description);
}
public void setProjectInfo(@NotNull RMProjectInfo projectInfo) {
this.projectInfo = projectInfo;
}
public boolean canUpdateProjectName() {
return RMProjectType.SHARED.equals(projectType);
@@ -21,7 +21,6 @@ import io.cloudbeaver.model.app.ServletApplication;
import io.cloudbeaver.model.app.ServletAuthApplication;
import io.cloudbeaver.websocket.CBWebSessionEventHandler;
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.auth.SMAuthInfo;
@@ -103,7 +102,7 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
}
}
public abstract void addSessionError(Throwable exception);
public abstract void addSessionError(@NotNull Throwable exception);
public void addEventHandler(@NotNull CBWebSessionEventHandler handler) {
synchronized (sessionEventHandlers) {
@@ -117,11 +116,11 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
}
}
public synchronized boolean updateSMSession(SMAuthInfo smAuthInfo) throws DBException {
public boolean updateSMSession(SMAuthInfo smAuthInfo) throws DBException {
return userContext.refresh(smAuthInfo);
}
public synchronized void refreshUserData() {
public void refreshUserData() {
try {
userContext.refreshPermissions();
if (userContext.isAuthorizedInSecurityManager()) {
@@ -151,7 +150,7 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
return workspace.getAuthContext();
}
protected void clearSessionContext() {
protected synchronized void clearSessionContext() {
this.workspace.getAuthContext().clear();
this.workspace.getAuthContext().addSession(this);
}
@@ -177,12 +176,12 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
return lastAccessTime;
}
public synchronized void touchSession() {
public void touchSession() {
this.lastAccessTime = System.currentTimeMillis();
}
@NotNull
public synchronized WebUserContext getUserContext() {
public WebUserContext getUserContext() {
return userContext;
}
@@ -227,7 +226,7 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
this.eventsFilter = eventsFilter;
}
public boolean isProjectAccessible(String projectId) {
public boolean isProjectAccessible(@NotNull String projectId) {
return userContext.getAccessibleProjectIds().contains(projectId);
}
@@ -240,11 +239,11 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
}
public void removeSessionProject(@Nullable String projectId) throws DBException {
public void removeSessionProject(@NotNull String projectId) throws DBException {
userContext.getAccessibleProjectIds().remove(projectId);
}
public abstract void addSessionMessage(WebServerMessage message);
public abstract void addSessionMessage(@NotNull WebServerMessage message);
@Property
public boolean isValid() {
@@ -41,6 +41,7 @@ public class WebAuthInfo implements SMSessionPrincipal, WebUserAuthToken {
private final WebSession session;
private final WebUser user;
@NotNull
private final WebAuthProviderDescriptor authProvider;
private String authProviderConfigurationId;
@NotNull
@@ -71,6 +72,7 @@ public class WebAuthInfo implements SMSessionPrincipal, WebUserAuthToken {
this.loginTime = loginTime;
}
@NotNull
@Property
public OffsetDateTime getLoginTime() {
return loginTime;
@@ -91,16 +93,19 @@ public class WebAuthInfo implements SMSessionPrincipal, WebUserAuthToken {
return new WebUserOriginInfo(session, user, authProvider);
}
@NotNull
@Property
public String getUserId() {
return userIdentity.getId();
}
@NotNull
@Property
public String getDisplayName() {
return userIdentity.getDisplayName();
}
@NotNull
@Property
public String getAuthProvider() {
return authProvider.getId();
@@ -138,13 +143,11 @@ public class WebAuthInfo implements SMSessionPrincipal, WebUserAuthToken {
}
void closeAuth() {
if (authProvider != null && authSession != null) {
try {
SMAuthProvider authProviderInstance = this.authProvider.getInstance();
authProviderInstance.closeSession(session, authSession);
} catch (Exception e) {
log.error(e);
}
try {
SMAuthProvider authProviderInstance = this.authProvider.getInstance();
authProviderInstance.closeSession(session, authSession);
} catch (Exception e) {
log.error(e);
}
}
@@ -35,12 +35,12 @@ public class WebHeadlessSession extends BaseWebSession {
}
@Override
public void addSessionError(Throwable exception) {
public void addSessionError(@NotNull Throwable exception) {
}
@Override
public void addSessionMessage(WebServerMessage message) {
public void addSessionMessage(@NotNull WebServerMessage message) {
}
@@ -51,6 +51,7 @@ public class WebHttpRequestInfo {
this.lastRemoteUserAgent = lastRemoteUserAgent;
}
@Nullable
public String getId() {
return id;
}
@@ -17,10 +17,7 @@
package io.cloudbeaver.model.session;
import io.cloudbeaver.*;
import io.cloudbeaver.model.CustomCancelableJob;
import io.cloudbeaver.model.WebAsyncTaskInfo;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.WebServerMessage;
import io.cloudbeaver.model.*;
import io.cloudbeaver.model.app.ServletApplication;
import io.cloudbeaver.model.app.ServletAuthApplication;
import io.cloudbeaver.model.session.monitor.TaskProgressMonitor;
@@ -30,7 +27,6 @@ import io.cloudbeaver.service.sql.WebSQLConstants;
import io.cloudbeaver.utils.CBModelConstants;
import io.cloudbeaver.utils.WebDataSourceUtils;
import io.cloudbeaver.utils.WebEventUtils;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.jkiss.code.NotNull;
@@ -38,6 +34,7 @@ import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBFileController;
import org.jkiss.dbeaver.model.DBPAdaptable;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.DBPEventListener;
import org.jkiss.dbeaver.model.access.DBAAuthCredentials;
@@ -45,7 +42,6 @@ import org.jkiss.dbeaver.model.access.DBACredentialsProvider;
import org.jkiss.dbeaver.model.auth.*;
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.fs.DBFFileSystemManager;
import org.jkiss.dbeaver.model.meta.Association;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.navigator.DBNModel;
@@ -68,7 +64,8 @@ import java.time.Instant;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
/**
* Web session.
@@ -76,16 +73,16 @@ import java.util.stream.Collectors;
*/
//TODO: split to authenticated and non authenticated context
public class WebSession extends BaseWebSession
implements SMSessionWithAuth, SMCredentialsProvider, DBACredentialsProvider, IAdaptable {
implements SMSessionWithAuth, SMCredentialsProvider, DBACredentialsProvider, DBPAdaptable {
private static final Log log = Log.getLog(WebSession.class);
public static final SMSessionType CB_SESSION_TYPE = new SMSessionType("CloudBeaver");
private static final String WEB_SESSION_AUTH_CONTEXT_TYPE = "web-session";
private static final String ATTR_LOCALE = "locale";
private static final AtomicInteger TASK_ID = new AtomicInteger();
public static String RUNTIME_PARAM_AUTH_INFOS = "auth-infos";
public static String RUNTIME_PARAM_CLIENT_ORIGIN = "client-origin";
private final AtomicInteger taskCount = new AtomicInteger();
private final String lastRemoteAddr;
@@ -93,27 +90,29 @@ public class WebSession extends BaseWebSession
private String locale;
private boolean cacheExpired;
private String clientOrigin;
protected WebSessionGlobalProjectImpl globalProject;
private final List<WebServerMessage> sessionMessages = new ArrayList<>();
private final Map<String, WebAsyncTaskInfo> asyncTasks = new HashMap<>();
private final Map<String, Function<Object, Object>> attributeDisposers = new HashMap<>();
// Map of auth tokens. Key is authentication provider
private final List<WebAuthInfo> authTokens = new ArrayList<>();
private DBNModel navigatorModel;
private final ReadWriteLock navigatorModelLock = new ReentrantReadWriteLock(true);
private final DBRProgressMonitor progressMonitor = new SessionProgressMonitor();
private final Map<String, DBWSessionHandler> sessionHandlers;
private final Map<String, DBWSessionHandler<WebSession>> sessionHandlers;
private final WebDataSourceConnectEventListener connectListener = new WebDataSourceConnectEventListener(this);
public WebSession(
@NotNull WebHttpRequestInfo requestInfo,
@NotNull ServletAuthApplication application,
@NotNull Map<String, DBWSessionHandler> sessionHandlers
@NotNull Map<String, DBWSessionHandler<WebSession>> sessionHandlers
) throws DBException {
this(requestInfo.getId(),
this(
Objects.requireNonNull(requestInfo.getId()),
CommonUtils.toString(requestInfo.getLocale()),
application,
sessionHandlers,
@@ -126,8 +125,8 @@ public class WebSession extends BaseWebSession
@NotNull String id,
@Nullable String locale,
@NotNull ServletApplication application,
@NotNull Map<String, DBWSessionHandler> sessionHandlers,
@NotNull String remoteAddr
@NotNull Map<String, DBWSessionHandler<WebSession>> sessionHandlers,
@Nullable String remoteAddr
) throws DBException {
super(id, application);
if (CommonUtils.isEmpty(remoteAddr)) {
@@ -152,7 +151,7 @@ public class WebSession extends BaseWebSession
if (authTokens.isEmpty()) {
return null;
}
return authTokens.get(0);
return authTokens.getFirst();
}
}
@@ -189,11 +188,12 @@ public class WebSession extends BaseWebSession
this.cacheExpired = cacheExpired;
}
public synchronized WebUser getUser() {
public WebUser getUser() {
return this.userContext.getUser();
}
public synchronized Map<String, String> getUserMetaParameters() {
@NotNull
public Map<String, String> getUserMetaParameters() {
var user = getUser();
if (user == null) {
return Map.of();
@@ -205,20 +205,20 @@ public class WebSession extends BaseWebSession
return allMetaParams;
}
public synchronized String getUserId() {
public String getUserId() {
return userContext.getUserId();
}
public synchronized boolean hasPermission(String perm) {
return getSessionPermissions().contains(DBWConstants.PERMISSION_ADMIN) ||
getSessionPermissions().contains(perm);
Set<String> sessionPermissions = getSessionPermissions();
return sessionPermissions.contains(DBWConstants.PERMISSION_ADMIN) || sessionPermissions.contains(perm);
}
public synchronized boolean isAuthorizedInSecurityManager() {
public boolean isAuthorizedInSecurityManager() {
return userContext.isAuthorizedInSecurityManager();
}
public synchronized Set<String> getSessionPermissions() {
public Set<String> getSessionPermissions() {
if (userContext.getUserPermissions() == null) {
refreshSessionAuth();
}
@@ -226,28 +226,30 @@ public class WebSession extends BaseWebSession
}
@NotNull
public synchronized SMController getSecurityController() {
public SMController getSecurityController() {
return userContext.getSecurityController();
}
@NotNull
public synchronized SMAdminController getAdminSecurityController() throws DBException {
public SMAdminController getAdminSecurityController() throws DBException {
if (!hasPermission(DBWConstants.PERMISSION_ADMIN)) {
throw new DBException("Admin permissions required");
}
return userContext.getAdminSecurityController();
}
public synchronized RMController getRmController() {
@NotNull
public RMController getRmController() {
return userContext.getRmController();
}
public synchronized DBFileController getFileController() {
@NotNull
public DBFileController getFileController() {
return userContext.getFileController();
}
@Override
public synchronized void refreshUserData() {
public void refreshUserData() {
super.refreshUserData();
refreshSessionAuth();
@@ -255,39 +257,58 @@ public class WebSession extends BaseWebSession
}
// Note: for admin use only
public synchronized void resetUserState() throws DBException {
public void resetUserState(boolean needResetUserCache) throws DBException {
clearAuthTokens();
try {
resetSessionCache();
} catch (DBCException e) {
addSessionError(e);
log.error(e);
if (needResetUserCache) {
try {
resetSessionCache();
} catch (DBCException e) {
addSessionError(e);
log.error(e);
}
}
refreshUserData();
clearSessionContext();
}
public void resetUserState() throws DBException {
resetUserState(true);
}
@NotNull
public DBPEventListener getDataSourceConnectListener() {
return connectListener;
}
@NotNull
public String getClientOrigin() {
return clientOrigin;
}
public void updateClientOrigin(@NotNull String originFromRequest) {
this.clientOrigin = originFromRequest;
}
private void initNavigatorModel() {
navigatorModelLock.writeLock().lock();
try {
// Cleanup current data
if (this.navigatorModel != null) {
this.navigatorModel.dispose();
this.navigatorModel = null;
}
this.globalProject = null;
// Cleanup current data
if (this.navigatorModel != null) {
this.navigatorModel.dispose();
this.navigatorModel = null;
loadProjects();
this.navigatorModel = new DBNModel(DBWorkbench.getPlatform(), getWorkspace().getProjects());
this.navigatorModel.setModelAuthContext(getWorkspace().getAuthContext());
this.navigatorModel.initialize();
this.locale = Locale.getDefault().getLanguage();
} finally {
navigatorModelLock.writeLock().unlock();
}
this.globalProject = null;
loadProjects();
this.navigatorModel = new DBNModel(DBWorkbench.getPlatform(), getWorkspace().getProjects());
this.navigatorModel.setModelAuthContext(getWorkspace().getAuthContext());
this.navigatorModel.initialize();
this.locale = Locale.getDefault().getLanguage();
}
private void loadProjects() {
@@ -310,7 +331,7 @@ public class WebSession extends BaseWebSession
anonymousProject.setInMemory(true);
}
if (workspace.getActiveProject() == null && !workspace.getProjects().isEmpty()) {
workspace.setActiveProject(workspace.getProjects().get(0));
workspace.setActiveProject(workspace.getProjects().getFirst());
}
} catch (DBException e) {
addSessionError(e);
@@ -318,14 +339,14 @@ public class WebSession extends BaseWebSession
}
}
private WebSessionProjectImpl createWebProject(RMProject project) throws DBException {
@NotNull
private WebSessionProjectImpl createWebProject(@NotNull RMProject project) throws DBException {
WebSessionProjectImpl sessionProject;
if (project.isGlobal()) {
sessionProject = createGlobalProject(project);
} else {
sessionProject = createSessionProject(project);
}
sessionProject.refreshProjectSettings();
// do not load data sources for anonymous project
if (project.getType() == RMProjectType.USER && userContext.getUser() == null) {
sessionProject.setInMemory(true);
@@ -334,13 +355,10 @@ public class WebSession extends BaseWebSession
if (!project.isShared() || application.isConfigurationMode()) {
getWorkspace().setActiveProject(sessionProject);
}
log.info(String.format(
"Project created: [ID=%s, Name=%s, Type=%s, Creator=%s]",
project.getId(), project.getName(), project.getType(), project.getCreator()
));
return sessionProject;
}
@NotNull
protected WebSessionProjectImpl createSessionProject(@NotNull RMProject project) throws DBException {
return new WebSessionProjectImpl(this, project, getProjectPath(project));
}
@@ -350,32 +368,24 @@ public class WebSession extends BaseWebSession
return RMUtils.getProjectPath(project);
}
@NotNull
protected WebSessionProjectImpl createGlobalProject(RMProject project) {
globalProject = new WebSessionGlobalProjectImpl(this, project);
globalProject.refreshAccessibleConnectionIds();
return globalProject;
}
private void resetSessionCache() throws DBCException {
// Clear attributes
synchronized (attributes) {
for (Map.Entry<String, Function<Object, Object>> attrDisposer : attributeDisposers.entrySet()) {
Object attrValue = attributes.get(attrDisposer.getKey());
attrDisposer.getValue().apply(attrValue);
}
attributeDisposers.clear();
// Remove all non-persistent attributes
attributes.entrySet().removeIf(
entry -> !(entry.getValue() instanceof PersistentAttribute));
}
}
private void resetNavigationModel() {
getWorkspace().getProjects().forEach(WebSessionProjectImpl::dispose);
navigatorModelLock.writeLock().lock();
try {
getWorkspace().getProjects().forEach(WebSessionProjectImpl::dispose);
if (this.navigatorModel != null) {
this.navigatorModel.dispose();
this.navigatorModel = null;
if (this.navigatorModel != null) {
this.navigatorModel.dispose();
this.navigatorModel = null;
}
} finally {
navigatorModelLock.writeLock().unlock();
}
}
@@ -418,15 +428,25 @@ public class WebSession extends BaseWebSession
@Nullable
public DBNModel getNavigatorModel() {
return navigatorModel;
navigatorModelLock.readLock().lock();
try {
return navigatorModel;
} finally {
navigatorModelLock.readLock().unlock();
}
}
@NotNull
public DBNModel getNavigatorModelOrThrow() throws DBWebException {
if (navigatorModel != null) {
return navigatorModel;
navigatorModelLock.readLock().lock();
try {
if (navigatorModel != null) {
return navigatorModel;
}
throw new DBWebException("Navigator model is not found in session");
} finally {
navigatorModelLock.readLock().unlock();
}
throw new DBWebException("Navigator model is not found in session");
}
/**
* Returns and clears progress messages
@@ -440,8 +460,8 @@ public class WebSession extends BaseWebSession
}
}
public synchronized void updateInfo(boolean isOldHttpSessionUsed) {
log.debug("Update session lifetime " + getSessionId() + " for user " + getUserId());
public void updateInfo(boolean isOldHttpSessionUsed) {
log.trace("Update session lifetime " + getSessionId() + " for user " + getUserId());
touchSession();
if (isOldHttpSessionUsed) {
try {
@@ -504,6 +524,7 @@ public class WebSession extends BaseWebSession
super.close(clearTokens, sendSessionExpiredEvent);
}
@NotNull
private List<WebAuthInfo> clearAuthTokens() throws DBException {
ArrayList<WebAuthInfo> tokensCopy;
synchronized (authTokens) {
@@ -560,6 +581,10 @@ public class WebSession extends BaseWebSession
if (job instanceof CustomCancelableJob cancelableJob) {
cancelableJob.cancelJob(this, taskInfo);
}
CompletableFuture<?> future = getAttribute(getTaskConfirmationAttributeName(taskId));
if (future != null) {
future.cancel(false);
}
if (job != null) {
job.cancel();
}
@@ -569,8 +594,7 @@ public class WebSession extends BaseWebSession
public WebAsyncTaskInfo createAsyncTask(@NotNull String taskName) {
int taskId = TASK_ID.incrementAndGet();
WebAsyncTaskInfo asyncTask = getAsyncTask(String.valueOf(taskId), taskName, true);
return asyncTask;
return getAsyncTask(String.valueOf(taskId), taskName, true);
}
public List<WebAsyncTaskInfo> findTasksByJob(@NotNull Class<? extends AbstractJob> jobClass) {
@@ -591,9 +615,10 @@ public class WebSession extends BaseWebSession
}
public WebAsyncTaskInfo runAsyncTask(@NotNull WebAsyncTaskInfo asyncTask, @NotNull WebAsyncTaskProcessor<?> runnable) {
AbstractJob job = new AbstractJob(asyncTask.getName()) {
AbstractJob job = new AbstractCancelableJob(asyncTask.getName()) {
@NotNull
@Override
protected IStatus run(DBRProgressMonitor monitor) {
protected IStatus run(@NotNull DBRProgressMonitor monitor) {
int curTaskCount = taskCount.incrementAndGet();
DBRProgressMonitor taskMonitor = new TaskProgressMonitor(monitor, WebSession.this, asyncTask);
@@ -629,11 +654,11 @@ public class WebSession extends BaseWebSession
return asyncTask;
}
public void addSessionError(Throwable exception) {
public void addSessionError(@NotNull Throwable exception) {
addSessionMessage(new WebServerMessage(exception));
}
public void addSessionMessage(WebServerMessage message) {
public void addSessionMessage(@NotNull WebServerMessage message) {
synchronized (sessionMessages) {
sessionMessages.add(message);
}
@@ -644,14 +669,15 @@ public class WebSession extends BaseWebSession
message.getMessage()));
}
public void addInfoMessage(String message) {
public void addInfoMessage(@NotNull String message) {
addSessionMessage(new WebServerMessage(MessageType.INFO, message));
}
public void addWarningMessage(String message) {
public void addWarningMessage(@NotNull String message) {
addSessionMessage(new WebServerMessage(MessageType.WARNING, message));
}
@NotNull
public List<WebServerMessage> readLog(Integer maxEntries, Boolean clearLog) {
synchronized (sessionMessages) {
List<WebServerMessage> messages = new ArrayList<>();
@@ -671,42 +697,6 @@ public class WebSession extends BaseWebSession
}
}
@Override
public <T> T getAttribute(String name) {
synchronized (attributes) {
Object value = attributes.get(name);
if (value instanceof PersistentAttribute persistentAttribute) {
value = persistentAttribute.value();
}
return (T) value;
}
}
public void setAttribute(String name, Object value, boolean persistent) {
synchronized (attributes) {
attributes.put(name, persistent ? new PersistentAttribute(value) : value);
}
}
public <T> T getAttribute(String name, Function<T, T> creator, Function<T, T> disposer) {
synchronized (attributes) {
Object value = attributes.get(name);
if (value instanceof PersistentAttribute persistentAttribute) {
value = persistentAttribute.value();
}
if (value == null) {
value = creator.apply(null);
if (value != null) {
attributes.put(name, value);
if (disposer != null) {
attributeDisposers.put(name, (Function<Object, Object>) disposer);
}
}
}
return (T) value;
}
}
@Property
public Map<String, Object> getActionParameters() {
WebActionParameters action = WebActionParameters.fromSession(this, true);
@@ -724,7 +714,7 @@ public class WebSession extends BaseWebSession
}
return null;
}
return authTokens.isEmpty() ? null : authTokens.get(0);
return authTokens.isEmpty() ? null : authTokens.getFirst();
}
}
@@ -735,7 +725,7 @@ public class WebSession extends BaseWebSession
}
}
@NotNull
public List<WebAuthInfo> getAllAuthInfo() {
synchronized (authTokens) {
return new ArrayList<>(authTokens);
@@ -767,10 +757,7 @@ public class WebSession extends BaseWebSession
if (oldAuthInfo != null) {
removeAuthInfo(oldAuthInfo);
}
SMSession authSession = authInfo.getAuthSession();
if (authSession != null) {
getSessionContext().addSession(authSession);
}
getSessionContext().addSession(authInfo.getAuthSession());
}
synchronized (authTokens) {
Collections.addAll(authTokens, tokens);
@@ -790,7 +777,7 @@ public class WebSession extends BaseWebSession
});
}
private void removeAuthInfo(WebAuthInfo oldAuthInfo) {
private void removeAuthInfo(@NotNull WebAuthInfo oldAuthInfo) {
oldAuthInfo.closeAuth();
synchronized (authTokens) {
authTokens.remove(oldAuthInfo);
@@ -816,6 +803,7 @@ public class WebSession extends BaseWebSession
return oldInfo;
}
@Nullable
public List<DBACredentialsProvider> getContextCredentialsProviders() {
return getAdapters(DBACredentialsProvider.class);
}
@@ -834,6 +822,7 @@ public class WebSession extends BaseWebSession
contextCredentialsProvider.provideAuthParameters(monitor, dataSourceContainer, configuration);
}
configuration.setRuntimeAttribute(RUNTIME_PARAM_AUTH_INFOS, getAllAuthInfo());
configuration.setRuntimeAttribute(RUNTIME_PARAM_CLIENT_ORIGIN, this.clientOrigin);
WebSessionProjectImpl project = getProjectById(dataSourceContainer.getProject().getId());
if (project != null) {
@@ -846,7 +835,7 @@ public class WebSession extends BaseWebSession
// uncommented because we had the problem with non-native auth models
// (for example, can't connect to DynamoDB if credentials are not saved)
DBAAuthCredentials credentials = configuration.getAuthModel().loadCredentials(dataSourceContainer, configuration);
WebDataSourceUtils.updateCredentialsFromProperties(credentials, configuration.getAuthProperties());
WebDataSourceUtils.updateCredentialsFromProperties(this.progressMonitor, credentials, configuration.getAuthProperties());
configuration.getAuthModel().provideCredentials(dataSourceContainer, configuration, credentials);
} catch (DBException e) {
@@ -864,7 +853,7 @@ public class WebSession extends BaseWebSession
// May be called to extract auth information from session
@Override
public <T> T getAdapter(Class<T> adapter) {
public <T> T getAdapter(@NotNull Class<T> adapter) {
synchronized (authTokens) {
for (WebAuthInfo authInfo : authTokens) {
if (isAuthInfoInstanceOf(authInfo, adapter)) {
@@ -886,7 +875,7 @@ public class WebSession extends BaseWebSession
}
private <T> boolean isAuthInfoInstanceOf(WebAuthInfo authInfo, Class<T> adapter) {
if (authInfo != null && authInfo.getAuthSession() != null) {
if (authInfo != null) {
return adapter.isInstance(authInfo.getAuthSession());
}
return false;
@@ -902,11 +891,11 @@ public class WebSession extends BaseWebSession
return parameters;
}
public synchronized void resetAuthToken() throws DBException {
public void resetAuthToken() throws DBException {
this.userContext.reset();
}
public synchronized boolean updateSMSession(SMAuthInfo smAuthInfo) throws DBException {
public boolean updateSMSession(SMAuthInfo smAuthInfo) throws DBException {
boolean contextChanged = super.updateSMSession(smAuthInfo);
if (contextChanged) {
refreshUserData();
@@ -925,7 +914,7 @@ public class WebSession extends BaseWebSession
}
@Nullable
public WebSessionProjectImpl getProjectById(@Nullable String projectId) {
public WebSessionProjectImpl getProjectById(@NotNull String projectId) {
return getWorkspace().getProjectById(projectId);
}
@@ -934,6 +923,7 @@ public class WebSession extends BaseWebSession
*
* @throws DBWebException if project with provided id is not found.
*/
@NotNull
public WebSessionProjectImpl getAccessibleProjectById(@Nullable String projectId) throws DBWebException {
WebSessionProjectImpl project = null;
if (projectId != null) {
@@ -945,6 +935,7 @@ public class WebSession extends BaseWebSession
return project;
}
@NotNull
public List<WebSessionProjectImpl> getAccessibleProjects() {
return getWorkspace().getProjects();
}
@@ -954,26 +945,35 @@ public class WebSession extends BaseWebSession
*/
public void addSessionProject(@NotNull WebSessionProjectImpl project) {
getWorkspace().addProject(project);
if (navigatorModel != null) {
navigatorModel.getRoot().addProject(project, false);
navigatorModelLock.readLock().lock();
try {
if (navigatorModel != null) {
navigatorModel.getRoot().addProject(project, false);
}
} finally {
navigatorModelLock.readLock().unlock();
}
}
/**
* Removes project from session cache and navigator tree.
*/
public void deleteSessionProject(@Nullable WebSessionProjectImpl project) {
if (project != null) {
RMProject rmProject = project.getRMProject();
log.info(String.format(
"Project deleted: [ID=%s, Name=%s, Type=%s, Creator=%s]",
rmProject.getId(), rmProject.getName(), rmProject.getType(), rmProject.getCreator()
));
project.dispose();
}
public void deleteSessionProject(@NotNull WebSessionProjectImpl project) {
RMProject rmProject = project.getRMProject();
log.info(String.format(
"Project deleted: [ID=%s, Name=%s, Type=%s, Creator=%s]",
rmProject.getId(), rmProject.getName(), rmProject.getType(), rmProject.getCreator()
));
project.dispose();
getWorkspace().removeProject(project);
if (navigatorModel != null) {
navigatorModel.getRoot().removeProject(project);
navigatorModelLock.readLock().lock();
try {
if (navigatorModel != null) {
navigatorModel.getRoot().removeProject(project);
}
} finally {
navigatorModelLock.readLock().unlock();
}
}
@@ -981,6 +981,10 @@ public class WebSession extends BaseWebSession
public void addSessionProject(@NotNull String projectId) throws DBException {
super.addSessionProject(projectId);
var rmProject = getRmController().getProject(projectId, false, false);
if (rmProject == null) {
log.error("RM project '" + projectId + "' not found");
return;
}
createWebProject(rmProject);
}
@@ -995,7 +999,7 @@ public class WebSession extends BaseWebSession
}
@Override
public void removeSessionProject(@Nullable String projectId) throws DBException {
public void removeSessionProject(@NotNull String projectId) throws DBException {
super.removeSessionProject(projectId);
var project = getProjectById(projectId);
if (project == null) {
@@ -1004,15 +1008,6 @@ public class WebSession extends BaseWebSession
deleteSessionProject(project);
}
@NotNull
public DBFFileSystemManager getFileSystemManager(String projectId) throws DBException {
var project = getProjectById(projectId);
if (project == null) {
throw new DBException("Project not found: " + projectId);
}
return project.getFileSystemManager();
}
@NotNull
public WebSessionPreferenceStore getUserPreferenceStore() {
return getUserContext().getPreferenceStore();
@@ -1028,7 +1023,7 @@ public class WebSession extends BaseWebSession
boolean confirmed,
boolean skipConfirmations
) {
String attributeName = WebSQLConstants.TASK_CONFIRMATION_ATTR_PREFIX + taskId;
String attributeName = getTaskConfirmationAttributeName(taskId);
if (confirmed && skipConfirmations) {
setAttribute(WebSQLConstants.SKIP_TASK_CONFIRMATIONS_ATTR, Boolean.TRUE);
}
@@ -1042,14 +1037,31 @@ public class WebSession extends BaseWebSession
}
}
public void handleTaskConfirmationWithParameters(@NotNull String taskId, @NotNull Map<String, Object> parameters) {
String attributeName = getTaskConfirmationAttributeName(taskId);
CompletableFuture<Map<String, Object>> confirmationFuture = getAttribute(attributeName);
if (confirmationFuture != null) {
confirmationFuture.complete(parameters);
removeAttribute(attributeName);
} else {
log.error("Received unexpected confirmation event for taskId: " + taskId);
}
}
@NotNull
private String getTaskConfirmationAttributeName(@NotNull String taskId) {
return WebSQLConstants.TASK_CONFIRMATION_ATTR_PREFIX + taskId;
}
private class SessionProgressMonitor extends BaseProgressMonitor {
@Override
public void beginTask(String name, int totalWork) {
public void beginTask(@NotNull String name, int totalWork) {
addInfoMessage(name);
}
@Override
public void subTask(String name) {
public void subTask(@NotNull String name) {
addInfoMessage(name);
}
}
@@ -1058,6 +1070,4 @@ public class WebSession extends BaseWebSession
return true;
}
private record PersistentAttribute(Object value) {
}
}
@@ -74,7 +74,7 @@ public class WebSessionAuthProcessor {
}
} catch (Exception e) {
if (resetUserStateOnError) {
webSession.resetUserState();
webSession.resetUserState(false);
}
throw new DBException(e.getMessage(), e);
}
@@ -19,9 +19,12 @@ package io.cloudbeaver.model.session;
import io.cloudbeaver.WebSessionProjectImpl;
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.DBPAdaptable;
import org.jkiss.dbeaver.model.DBPImage;
import org.jkiss.dbeaver.model.app.DBPPlatform;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.impl.auth.SessionContextImpl;
import org.jkiss.dbeaver.model.rm.RMUtils;
@@ -36,6 +39,7 @@ import java.util.List;
* Web workspace
*/
public class WebSessionWorkspace implements DBPWorkspace {
private static final Log log = Log.getLog(WebSessionWorkspace.class);
private final BaseWebSession session;
private final SessionContextImpl workspaceAuthContext;
@@ -87,7 +91,9 @@ public class WebSessionWorkspace implements DBPWorkspace {
@NotNull
@Override
public List<WebSessionProjectImpl> getProjects() {
return accessibleProjects;
synchronized (accessibleProjects) {
return new ArrayList<>(accessibleProjects);
}
}
@Nullable
@@ -99,26 +105,30 @@ public class WebSessionWorkspace implements DBPWorkspace {
@Nullable
@Override
public WebSessionProjectImpl getProject(@NotNull String projectName) {
for (WebSessionProjectImpl project : accessibleProjects) {
if (project.getName().equals(projectName)) {
return project;
synchronized (accessibleProjects) {
for (WebSessionProjectImpl project : accessibleProjects) {
if (project.getName().equals(projectName)) {
return project;
}
}
}
log.error("Project name '" + projectName + "' not found in session workspace");
return null;
}
@Nullable
@Override
public WebSessionProjectImpl getProjectById(@NotNull String projectId) {
if (projectId == null) {
return activeProject;
}
for (WebSessionProjectImpl project : accessibleProjects) {
if (project.getId().equals(projectId)) {
return project;
synchronized (accessibleProjects) {
for (WebSessionProjectImpl project : accessibleProjects) {
if (project.getId().equals(projectId)) {
return project;
}
}
}
return null;
log.error("Project ID '" + projectId + "' not found in session workspace");
// FIXME: return null here
return activeProject;
}
@NotNull
@@ -143,25 +153,47 @@ public class WebSessionWorkspace implements DBPWorkspace {
return null;
}
public void setActiveProject(WebSessionProjectImpl activeProject) {
@NotNull
@Override
public WebSessionProjectImpl createProject(@NotNull String name, @Nullable String description) throws DBException {
throw new DBException("Project creation is not supported in web session workspace");
}
@Override
public void deleteProject(@NotNull DBPProject project) throws DBException {
throw new DBException("Project removing is not supported in web session workspace");
}
@Override
public void renameProject(@NotNull DBPProject project, @NotNull String newName) throws DBException {
throw new DBException("Project renaming is not supported in web session workspace");
}
public void setActiveProject(@NotNull WebSessionProjectImpl activeProject) {
this.activeProject = activeProject;
}
void addProject(WebSessionProjectImpl project) {
accessibleProjects.add(project);
void addProject(@NotNull WebSessionProjectImpl project) {
synchronized (accessibleProjects) {
accessibleProjects.add(project);
}
}
void removeProject(WebSessionProjectImpl project) {
accessibleProjects.remove(project);
void removeProject(@NotNull WebSessionProjectImpl project) {
synchronized (accessibleProjects) {
accessibleProjects.remove(project);
}
}
void clearProjects() {
if (!this.accessibleProjects.isEmpty()) {
for (WebSessionProjectImpl project : accessibleProjects) {
project.dispose();
synchronized (accessibleProjects) {
if (!this.accessibleProjects.isEmpty()) {
for (WebSessionProjectImpl project : accessibleProjects) {
project.dispose();
}
this.activeProject = null;
this.accessibleProjects.clear();
}
this.activeProject = null;
this.accessibleProjects.clear();
}
}
@@ -53,9 +53,9 @@ public class WebUserContext implements SMCredentialsProvider {
private final ServletApplication application;
private final DBPWorkspace workspace;
private WebUser user;
private Set<String> userPermissions;
private SMCredentials smCredentials = null;
private volatile WebUser user;
private volatile Set<String> userPermissions;
private volatile SMCredentials smCredentials = null;
private String smSessionId;
private String refreshToken;
@@ -63,11 +63,14 @@ public class WebUserContext implements SMCredentialsProvider {
private SMAdminController adminSecurityController;
private DBSSecretController secretController;
private RMController rmController;
private DBFileController fileController;
private Set<String> accessibleProjectIds = new HashSet<>();
private final DBFileController fileController;
private final Set<String> accessibleProjectIds = new HashSet<>();
private final WebSessionPreferenceStore preferenceStore;
public WebUserContext(ServletApplication application, DBPWorkspace workspace) throws DBException {
public WebUserContext(
@NotNull ServletApplication application,
@NotNull DBPWorkspace workspace
) throws DBException {
this.application = application;
this.workspace = workspace;
this.securityController = application.createSecurityController(this);
@@ -84,7 +87,7 @@ public class WebUserContext implements SMCredentialsProvider {
* @return - true if context changed
* @throws DBException - if user already authorized and new token come from another user
*/
public synchronized boolean refresh(SMAuthInfo smAuthInfo) throws DBException {
public boolean refresh(@NotNull SMAuthInfo smAuthInfo) throws DBException {
if (smAuthInfo.getAuthPermissions() == null && !isAuthorizedInSecurityManager()) {
throw new DBCException("Required information about session permissions is missing");
}
@@ -172,53 +175,56 @@ public class WebUserContext implements SMCredentialsProvider {
}
@NotNull
public synchronized SMController getSecurityController() {
public SMController getSecurityController() {
return securityController;
}
@Nullable
@Override
public synchronized SMCredentials getActiveUserCredentials() {
public SMCredentials getActiveUserCredentials() {
return smCredentials;
}
public synchronized boolean isAuthorizedInSecurityManager() {
public boolean isAuthorizedInSecurityManager() {
return smCredentials != null;
}
public synchronized boolean isNonAnonymousUserAuthorizedInSM() {
public boolean isNonAnonymousUserAuthorizedInSM() {
return isAuthorizedInSecurityManager() && getUser() != null;
}
public synchronized boolean isAnonymousUserAuthorizedInSM() {
public boolean isAnonymousUserAuthorizedInSM() {
return isAuthorizedInSecurityManager() && getUser() == null;
}
@Nullable
public synchronized WebUser getUser() {
public WebUser getUser() {
return user;
}
public synchronized String getUserId() {
@Nullable
public String getUserId() {
return user == null ? null : user.getUserId();
}
protected synchronized void setUser(@Nullable WebUser user) {
protected void setUser(@Nullable WebUser user) {
this.user = user;
}
public synchronized SMAdminController getAdminSecurityController() {
@NotNull
public SMAdminController getAdminSecurityController() {
return adminSecurityController;
}
public synchronized Set<String> getUserPermissions() {
@Nullable
public Set<String> getUserPermissions() {
return userPermissions;
}
/**
* reread the current user's permissions
*/
public synchronized void refreshPermissions() throws DBException {
public void refreshPermissions() throws DBException {
if (isAuthorizedInSecurityManager()) {
log.debug("refresh permissions " + getUserId() + " " + getSmSessionId());
setUserPermissions(securityController.getTokenPermissions().getPermissions());
@@ -231,6 +237,7 @@ public class WebUserContext implements SMCredentialsProvider {
this.userPermissions = permissions;
}
@NotNull
public DBSSecretController getSecretController() throws DBException {
if (this.securityController == null) {
this.secretController = application.getSecretController(this, workspace.getAuthContext());
@@ -238,18 +245,22 @@ public class WebUserContext implements SMCredentialsProvider {
return secretController;
}
public synchronized String getSmSessionId() {
@NotNull
public String getSmSessionId() {
return smSessionId;
}
@Nullable
private Set<String> getDefaultPermissions() {
return application.isAnonymousAccessEnabled() ? null : Set.of();
}
@NotNull
public RMController getRmController() {
return rmController;
}
@NotNull
public DBFileController getFileController() {
return fileController;
}
@@ -263,6 +274,7 @@ public class WebUserContext implements SMCredentialsProvider {
this.refreshToken = refreshToken;
}
@NotNull
public WebSessionPreferenceStore getPreferenceStore() {
return preferenceStore;
}
@@ -40,14 +40,14 @@ public class TaskProgressMonitor extends ProxyProgressMonitor {
}
@Override
public void beginTask(String name, int totalWork) {
public void beginTask(@NotNull String name, int totalWork) {
super.beginTask(name, totalWork);
asyncTask.setStatus(name);
WebEventUtils.sendAsyncTaskEvent(webSession, asyncTask);
}
@Override
public void subTask(String name) {
public void subTask(@NotNull String name) {
super.subTask(name);
asyncTask.setStatus(name);
WebEventUtils.sendAsyncTaskEvent(webSession, asyncTask);
@@ -38,8 +38,7 @@ public class WebFeatureDescriptor extends AbstractContextDescriptor implements D
private final DBPImage icon;
private final boolean enabledByDefault;
public WebFeatureDescriptor(IConfigurationElement config)
{
public WebFeatureDescriptor(@NotNull IConfigurationElement config) {
super(config);
this.id = config.getAttribute("id");
this.label = config.getAttribute("label");
@@ -77,4 +76,7 @@ public class WebFeatureDescriptor extends AbstractContextDescriptor implements D
return enabledByDefault;
}
public boolean isApplicable() {
return true;
}
}
@@ -20,10 +20,12 @@ import io.cloudbeaver.DBWFeatureSet;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.Platform;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class WebFeatureRegistry {
@@ -33,7 +35,7 @@ public class WebFeatureRegistry {
private static WebFeatureRegistry instance = null;
public synchronized static WebFeatureRegistry getInstance() {
public static synchronized WebFeatureRegistry getInstance() {
if (instance == null) {
instance = new WebFeatureRegistry();
instance.loadExtensions(Platform.getExtensionRegistry());
@@ -41,23 +43,28 @@ public class WebFeatureRegistry {
return instance;
}
private final List<DBWFeatureSet> webFeatures = new ArrayList<>();
private final List<WebFeatureDescriptor> webFeatures = new ArrayList<>();
private WebFeatureRegistry() {
}
private void loadExtensions(IExtensionRegistry registry) {
protected void loadExtensions(@NotNull IExtensionRegistry registry) {
IConfigurationElement[] extConfigs = registry.getConfigurationElementsFor(WebFeatureDescriptor.EXTENSION_ID);
for (IConfigurationElement ext : extConfigs) {
if (TAG_FEATURE.equals(ext.getName())) {
this.webFeatures.add(
new WebFeatureDescriptor(ext));
this.webFeatures.add(createFeatureDescriptor(ext));
}
}
}
@NotNull
protected WebFeatureDescriptor createFeatureDescriptor(@NotNull IConfigurationElement ext) {
return new WebFeatureDescriptor(ext);
}
@NotNull
public List<DBWFeatureSet> getWebFeatures() {
return webFeatures;
return webFeatures.stream()
.filter(WebFeatureDescriptor::isApplicable)
.collect(Collectors.toList());
}
}
@@ -41,7 +41,7 @@ public abstract class BaseServletPlatform extends BasePlatformImpl {
if (tempFolder == null) {
synchronized (this) {
if (tempFolder == null) {
initTempFolder(monitor);
initTempFolder();
}
}
}
@@ -59,9 +59,8 @@ public abstract class BaseServletPlatform extends BasePlatformImpl {
@NotNull
public abstract ServletApplication getApplication();
private void initTempFolder(@NotNull DBRProgressMonitor monitor) {
private void initTempFolder() {
// Make temp folder
monitor.subTask("Create temp folder");
String sysTempFolder = System.getProperty(StandardConstants.ENV_TMP_DIR);
if (CommonUtils.isNotEmpty(sysTempFolder)) {
tempFolder = Path.of(sysTempFolder).resolve(BASE_TEMP_DIR).resolve(DBWConstants.WORK_DATA_FOLDER_NAME);
@@ -69,9 +68,11 @@ public abstract class BaseServletPlatform extends BasePlatformImpl {
//we do not use workspace because it can be in external file system
tempFolder = getApplication().getHomeDirectory().resolve(DBWConstants.WORK_DATA_FOLDER_NAME);
}
log.debug("Temp folder: " + tempFolder);
}
public synchronized void dispose() {
super.dispose();
// Remove temp folder
if (tempFolder != null) {
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -28,50 +28,34 @@ public class CBConstants {
public static final String RUNTIME_PRODUCT_CONFIG_FILE_NAME = ".product.runtime.conf";
public static final String AUTO_CONFIG_FILE_NAME = ".cloudbeaver.auto.conf";
public static final String PARAM_SERVER_CONFIGURATION = "server";
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_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";
public static final String PARAM_ROOT_URI = "rootURI";
public static final String PARAM_SERVICES_URI = "serviceURI";
public static final String PARAM_DRIVERS_LOCATION = "driversLocation";
public static final String PARAM_WORKSPACE_LOCATION = "workspaceLocation";
//legacy path to a product.conf file
@Deprecated
public static final String PARAM_PRODUCT_CONFIGURATION = "productConfiguration";
public static final String PARAM_PRODUCT_SETTINGS = "productSettings";
public static final String PARAM_EXTERNAL_PROPERTIES = "externalProperties";
public static final String PARAM_STATIC_CONTENT = "staticContent";
public static final String PARAM_RESOURCE_QUOTAS = "resourceQuotas";
public static final String PARAM_RESOURCE_MANAGER_ENABLED = "resourceManagerEnabled";
public static final String PARAM_SECRET_MANAGER_ENABLED = "secretManagerEnabled";
public static final String PARAM_SHOW_READ_ONLY_CONN_INFO = "showReadOnlyConnectionInfo";
public static final String PARAM_CONN_GRANT_ANON_ACCESS = "grantConnectionsAccessToAnonymousTeam";
public static final String PARAM_AUTH_PROVIDERS = "authConfiguration";
public static final String PARAM_DB_CONFIGURATION = "database";
public static final String PARAM_SESSION_EXPIRE_PERIOD = "expireSessionAfterPeriod";
public static final String PARAM_DEVEL_MODE = "develMode";
public static final String PARAM_SECURITY_MANAGER = "enableSecurityManager";
public static final String PARAM_SM_CONFIGURATION = "sm";
public static final String PARAM_PASSWORD_POLICY_CONFIGURATION = "passwordPolicy";
public static final int DEFAULT_SERVER_PORT = 8080;
//public static final String DEFAULT_SERVER_NAME = "CloudBeaver Web Server";
public static final String DEFAULT_CONTENT_ROOT = "/var/www/cloudbeaver";
public static final String DEFAULT_ROOT_URI = "/";
public static final String DEFAULT_SERVICES_URI = "/api/";
public static final String DEFAULT_DEPLOY_LOCATION = "/opt/cloudbeaver";
public static final String DEFAULT_DRIVERS_LOCATION = DEFAULT_DEPLOY_LOCATION + "/drivers";
public static final String DEFAULT_WORKSPACE_LOCATION = DEFAULT_DEPLOY_LOCATION + "/workspace";
public static final String DEFAULT_PRODUCT_CONFIGURATION = "conf/product.conf";
public static final String DEFAULT_DATASOURCE_PERMISSIONS_CONFIGURATION = "data-sources-permissions.json";
public static final String DEFAULT_ADMIN_NAME = "cbadmin";
@@ -84,14 +68,11 @@ public class CBConstants {
public static final String VAR_HOST_DOCKER_INTERNAL = "host.docker.internal";
public static final String VAR_AUTO_CB_SERVER_NAME = "CB_SERVER_NAME";
public static final String VAR_AUTO_CB_SERVER_URL = "CB_SERVER_URL";
public static final String VAR_AUTO_CB_ADMIN_NAME = "CB_ADMIN_NAME";
public static final String VAR_AUTO_CB_ADMIN_PASSWORD = "CB_ADMIN_PASSWORD";
public static final String CB_SESSION_COOKIE_NAME = "cb-session-id";
public static final String APPLICATION_JSON = "application/json";
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";
@@ -99,4 +80,6 @@ public class CBConstants {
public static final String BIND_SESSION_ENABLE = "enable";
public static final String BIND_SESSION_DISABLE = "disable";
public static final String DEFAULT_CLOUD_PROJECT_NAME = "GlobalConfiguration";
public static final String SECURED_VALUE = "******";
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -16,6 +16,7 @@
*/
package io.cloudbeaver.service.sql;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.data.DBDAttributeConstraint;
import org.jkiss.dbeaver.model.gis.DBGeometry;
import org.jkiss.utils.CommonUtils;
@@ -27,6 +28,7 @@ import java.util.Map;
*/
public class WebSQLDataFilterConstraint {
private String attributeName;
private Integer attributePosition;
private Integer orderPosition;
private Boolean orderAsc;
@@ -38,8 +40,9 @@ public class WebSQLDataFilterConstraint {
private WebSQLDataFilterConstraint() {
}
public WebSQLDataFilterConstraint(Map<String, Object> map) {
this.attributePosition = CommonUtils.toInt(map.get("attributePosition"));
public WebSQLDataFilterConstraint(@NotNull Map<String, Object> map) {
this.attributeName = CommonUtils.toString(map.get("attributeName"));
this.attributePosition = CommonUtils.toInteger(map.get("attributePosition"), null);
this.orderPosition = map.containsKey("orderPosition") ?
CommonUtils.toInt(map.get("orderPosition")) + 1 : // Use position + 1 because 0 means no ordering (because of legacy compatibility)
null;
@@ -64,8 +67,10 @@ public class WebSQLDataFilterConstraint {
}
public static WebSQLDataFilterConstraint from(DBDAttributeConstraint constraint) {
@NotNull
public static WebSQLDataFilterConstraint from(@NotNull DBDAttributeConstraint constraint) {
var webConstraint = new WebSQLDataFilterConstraint();
webConstraint.attributeName = constraint.getAttributeName();
webConstraint.orderPosition = constraint.getOrderPosition();
webConstraint.orderAsc = !constraint.isOrderDescending();
webConstraint.criteria = constraint.getCriteria();
@@ -76,6 +81,10 @@ public class WebSQLDataFilterConstraint {
return webConstraint;
}
public String getAttributeName() {
return attributeName;
}
public Integer getAttributePosition() {
return attributePosition;
}
@@ -0,0 +1,23 @@
/*
* 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.service.sql;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
public record WebSQLQueryParameterRecord(@NotNull String name, @Nullable String value) {
}
@@ -21,6 +21,7 @@ import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.data.DBDAttributeBinding;
import org.jkiss.dbeaver.model.data.DBDDataFilter;
import org.jkiss.dbeaver.model.data.DBDRowIdentifier;
import org.jkiss.dbeaver.model.exec.trace.DBCTrace;
import org.jkiss.dbeaver.model.struct.*;
@@ -42,6 +43,8 @@ public class WebSQLResultsInfo {
// TODO: find a way to remove isSingleRow and use virtual keys for reading BLOB and string cell values.
private boolean isSingleRow;
private DBCTrace trace;
@Nullable
private DBDDataFilter dataFilter;
private String queryText;
public WebSQLResultsInfo(@NotNull DBSDataContainer dataContainer, @NotNull String id) {
@@ -151,4 +154,13 @@ public class WebSQLResultsInfo {
public void setSingleRow(boolean singleRow) {
isSingleRow = singleRow;
}
@Nullable
public DBDDataFilter getDataFilter() {
return dataFilter;
}
public void setDataFilter(@Nullable DBDDataFilter dataFilter) {
this.dataFilter = dataFilter;
}
}
@@ -18,18 +18,20 @@ package io.cloudbeaver.test;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.cloudbeaver.server.CBConstants;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.HttpConstants;
import java.net.CookieManager;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
@@ -56,10 +58,13 @@ public class WebGQLClient {
@NotNull
private final HttpClient httpClient;
@NotNull
private final CookieManager cookieManager;
@NotNull
private final String apiUrl;
public WebGQLClient(@NotNull HttpClient httpClient, @NotNull String apiUrl) {
public WebGQLClient(@NotNull HttpClient httpClient, @NotNull CookieManager cookieManager, @NotNull String apiUrl) {
this.httpClient = httpClient;
this.cookieManager = cookieManager;
this.apiUrl = apiUrl;
}
@@ -70,9 +75,18 @@ public class WebGQLClient {
* @param variables GraphQL query variables
* @return GraphQL response
*/
@NotNull
@Nullable
public <T> T sendQuery(@NotNull String query, @Nullable Map<String, Object> variables) throws Exception {
return sendQueryWithHeaders(query, variables, List.of());
return sendQueryWithHeaders(query, variables, Map.of());
}
@NotNull
public Map<String, Object> sendQueryAndGetHeaders(
@NotNull String query,
@Nullable Map<String, Object> variables,
@NotNull Map<String, String> requestHeaders
) throws Exception {
return executeGQLRequest(query, variables, requestHeaders);
}
/**
@@ -87,15 +101,36 @@ public class WebGQLClient {
public <T> T sendQueryWithHeaders(
@NotNull String query,
@Nullable Map<String, Object> variables,
@NotNull List<String> headers
@NotNull Map<String, String> headers
) throws Exception {
Map<String, Object> parsed = executeGQLRequest(query, variables, headers);
// graphql response will be in "data" key
return (T) parsed.get("data");
}
@NotNull
private Map<String, Object> executeGQLRequest(
@NotNull String query,
@Nullable Map<String, Object> variables,
@NotNull Map<String, String> requestHeaders
) throws Exception {
return executeGQLRequest(query, variables, requestHeaders, "result");
}
@NotNull
public Map<String, Object> executeGQLRequest(
@NotNull String query,
@Nullable Map<String, Object> variables,
@NotNull Map<String, String> requestHeaders,
@NotNull String resultName
) throws Exception {
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(URI.create(apiUrl))
.POST(HttpRequest.BodyPublishers.ofString(makeGQLRequest(query, variables)))
.setHeader("TE-Client-Version", GeneralUtils.getMajorVersion())
.header("Content-Type", "application/json");
if (!headers.isEmpty()) {
requestBuilder.headers(headers.toArray(String[]::new));
.header(HttpConstants.HEADER_CONTENT_TYPE, HttpConstants.CONTENT_TYPE_JSON);
if (!requestHeaders.isEmpty()) {
requestHeaders.forEach(requestBuilder::header);
}
HttpRequest request = requestBuilder.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
@@ -107,8 +142,10 @@ public class WebGQLClient {
String message = JSONUtils.getString(JSONUtils.getObjectList(body, "errors").get(0), "message");
throw new DBException(message);
}
// graphql response will be in "data" key
return (T) JSONUtils.getObject(body, "data").get("result");
Map<String, Object> parsed = new LinkedHashMap<>();
parsed.put("data", JSONUtils.getObject(body, "data").get(resultName));
parsed.put("headers", response.headers().map());
return parsed;
}
@NotNull
@@ -121,4 +158,13 @@ public class WebGQLClient {
return gson.toJson(request, Map.class);
}
@NotNull
public String getSessionIdCookie() {
return cookieManager.getCookieStore().getCookies().stream()
.filter(cookie -> cookie.getName().equals(CBConstants.CB_SESSION_COOKIE_NAME))
.findFirst()
.get()
.getValue();
}
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -47,6 +47,8 @@ public class ServletAppUtils {
private static final String HEADER_REFERER = "Referer";
private static final String HEADER_FORWARDED_SCHEME = "X-Forwarded-Scheme";
private static final String HEADER_FORWARDED_PROTO = "X-Forwarded-Proto";
private static final String HEADER_FORWARDED_PORT = "X-Forwarded-Port";
private static final String HEADER_FORWARDED_HOST = "X-Forwarded-Host";
private static final Set<Integer> DEFAULT_PORTS = Set.of(
80,
@@ -294,19 +296,37 @@ public class ServletAppUtils {
@NotNull
public static String getOriginFromRequest(@NotNull HttpServletRequest request) {
String origin = request.getHeader(HEADER_ORIGIN);
if (log.isTraceEnabled()) {
log.trace("Origin header: " + origin);
}
if (CommonUtils.isEmpty(origin)) {
origin = request.getHeader(HEADER_X_ORIGIN);
if (log.isTraceEnabled()) {
log.trace("X-Origin header: " + origin);
}
}
if (CommonUtils.isEmpty(origin)) {
origin = request.getHeader(HEADER_REFERER);
if (log.isTraceEnabled()) {
log.trace("Referer header: " + origin);
}
}
String forwardedScheme = request.getHeader(HEADER_FORWARDED_SCHEME);
if (CommonUtils.isEmpty(forwardedScheme)) {
forwardedScheme = request.getHeader(HEADER_FORWARDED_PROTO);
}
String forwardedHost = request.getHeader(HEADER_FORWARDED_HOST);
if (CommonUtils.isNotEmpty(forwardedScheme) && CommonUtils.isNotEmpty(forwardedHost)) {
origin = forwardedScheme + "://" + forwardedHost;
if (log.isTraceEnabled()) {
log.trace("forwarded origin: " + origin);
}
}
if (CommonUtils.isEmpty(origin)) {
URI requestUrl = URI.create(request.getRequestURL().toString());
if (log.isTraceEnabled()) {
log.trace("Request URL: " + requestUrl);
}
origin = getRootUrlFromUri(requestUrl) + "/";
}
origin = removeSideSlashes(origin);
@@ -318,7 +338,15 @@ public class ServletAppUtils {
origin = removeSideSlashes(origin);
URI uri = URI.create(origin);
if (DEFAULT_PORTS.contains(uri.getPort())) {
int port = uri.getPort();
if (CommonUtils.isNotEmpty(request.getHeader(HEADER_FORWARDED_PORT))) {
try {
port = Integer.parseInt(request.getHeader(HEADER_FORWARDED_PORT));
} catch (NumberFormatException e) {
log.error("Failed to parse port from header: " + request.getHeader(HEADER_FORWARDED_PORT), e);
}
}
if (DEFAULT_PORTS.contains(port)) {
try {
origin = new URI(
uri.getScheme(),
@@ -333,6 +361,9 @@ public class ServletAppUtils {
log.error("Failed to create URI without port", e);
}
}
if (log.isTraceEnabled()) {
log.trace("Origin URI: " + origin);
}
return origin;
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -16,10 +16,6 @@
*/
package io.cloudbeaver.utils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.Strictness;
import io.cloudbeaver.DBWConstants;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebSessionProjectImpl;
@@ -34,7 +30,6 @@ import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBConstants;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.access.DBAAuthCredentials;
import org.jkiss.dbeaver.model.access.DBAAuthCredentialsWithComplexProperties;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
@@ -43,10 +38,12 @@ import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.model.impl.auth.AuthModelDatabaseNativeCredentials;
import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration;
import org.jkiss.dbeaver.model.net.ssh.SSHConstants;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceDisconnectEvent;
import org.jkiss.dbeaver.registry.network.NetworkHandlerDescriptor;
import org.jkiss.dbeaver.registry.network.NetworkHandlerRegistry;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.dbeaver.utils.PropertySerializationUtils;
import org.jkiss.utils.CommonUtils;
import java.util.*;
@@ -211,31 +208,30 @@ public class WebDataSourceUtils {
return webSession.getAccessibleProjectById(projectId).getWebConnectionInfo(connectionId);
}
public static void updateCredentialsFromProperties(@NotNull DBAAuthCredentials credentials, @NotNull Map<String, ?> properties) {
InstanceCreator<DBAAuthCredentials> credTypeAdapter = type -> credentials;
Gson credGson = new GsonBuilder()
.setStrictness(Strictness.LENIENT)
.registerTypeAdapter(credentials.getClass(), credTypeAdapter)
.create();
if (credentials instanceof DBAAuthCredentialsWithComplexProperties complexProperties) {
complexProperties.updateCredentialsFromComplexProperties(properties);
public static void updateCredentialsFromProperties(
@NotNull DBRProgressMonitor progressMonitor,
@NotNull DBAAuthCredentials credentials,
@Nullable Map<String, ?> properties
) {
if (properties == null) {
return;
}
credGson.fromJson(credGson.toJsonTree(properties), credentials.getClass());
PropertySerializationUtils.updateCredentialsFromProperties(progressMonitor, credentials, properties);
}
public static void saveAuthProperties(
@NotNull DBRProgressMonitor progressMonitor,
@NotNull DBPDataSourceContainer dataSourceContainer,
@NotNull DBPConnectionConfiguration configuration,
@Nullable Map<String, Object> authProperties,
boolean saveCredentials,
boolean sharedCredentials
) {
saveAuthProperties(dataSourceContainer, configuration, authProperties, saveCredentials, sharedCredentials, false);
saveAuthProperties(progressMonitor, dataSourceContainer, configuration, authProperties, saveCredentials, sharedCredentials, false);
}
public static void saveAuthProperties(
@NotNull DBRProgressMonitor progressMonitor,
@NotNull DBPDataSourceContainer dataSourceContainer,
@NotNull DBPConnectionConfiguration configuration,
@Nullable Map<String, Object> authProperties,
@@ -271,7 +267,7 @@ public class WebDataSourceUtils {
configuration.setAuthProperties(currentAuthProps);
}
if (!authProperties.isEmpty()) {
updateCredentialsFromProperties(credentials, authProperties);
updateCredentialsFromProperties(progressMonitor, credentials, authProperties);
}
configuration.getAuthModel().saveCredentials(dataSourceContainer, configuration, credentials);
@@ -18,6 +18,7 @@ package io.cloudbeaver.utils;
import io.cloudbeaver.model.WebAsyncTaskInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.websocket.event.task.WSSessionTaskInfoEvent;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.app.DBPProject;
@@ -30,7 +31,6 @@ import org.jkiss.dbeaver.model.websocket.event.datasource.WSDatasourceFolderEven
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;
import java.util.List;
@@ -17,8 +17,11 @@
package io.cloudbeaver.utils;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.utils.GeneralUtils;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
@@ -39,4 +42,30 @@ public class WebTestUtils {
return false;
}
}
public static void setPrivateField(
@NotNull Object target,
@NotNull String fieldName,
@Nullable Object value
) throws Exception {
Class<?> cls = target.getClass();
Field f = null;
while (cls != null) {
Field[] fields = cls.getDeclaredFields();
for (Field field : fields) {
if (field.getName().equals(fieldName)) {
f = field;
break;
}
}
cls = cls.getSuperclass();
}
if (f == null) {
throw new NoSuchFieldException(fieldName);
}
boolean isAccessible = f.canAccess(target);
f.setAccessible(true);
f.set(target, value);
f.setAccessible(isAccessible);
}
}
@@ -0,0 +1,28 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.websocket.event.client;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
public class WSSessionPingClientEvent extends WSClientEvent {
public static final String ID = "cb_client_session_ping";
public WSSessionPingClientEvent(@Nullable String topicId) {
super(ID, topicId);
}
}
@@ -0,0 +1,53 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.websocket.event.client;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.websocket.WSConstants;
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
public class WSSessionTaskConfirmationEvent extends WSClientEvent {
public static final String ID = "cb_client_session_task_confirmation";
private final String taskId;
private final boolean confirmed;
private final boolean skipConfirmations;
public WSSessionTaskConfirmationEvent(
@NotNull String taskId,
boolean confirmed,
boolean skipConfirmations
) {
super(ID, WSConstants.TOPIC_SESSION_TASK);
this.taskId = taskId;
this.confirmed = confirmed;
this.skipConfirmations = skipConfirmations;
}
public String getTaskId() {
return taskId;
}
public boolean isConfirmed() {
return confirmed;
}
public boolean isSkipConfirmations() {
return skipConfirmations;
}
}
@@ -0,0 +1,47 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.websocket.event.client;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.websocket.WSConstants;
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
import java.util.Map;
public class WSSessionTaskWithParametersConfirmationEvent extends WSClientEvent {
public static final String ID = "cb_client_session_task_with_parameters_confirmation";
@NotNull
private final String taskId;
@NotNull
private final Map<String, Object> parameters;
protected WSSessionTaskWithParametersConfirmationEvent(@NotNull String taskId, @NotNull Map<String, Object> parameters) {
super(ID, WSConstants.TOPIC_SESSION_TASK);
this.taskId = taskId;
this.parameters = parameters;
}
@NotNull
public String getTaskId() {
return taskId;
}
@NotNull
public Map<String, Object> getParameters() {
return parameters;
}
}
@@ -0,0 +1,30 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.websocket.event.client;
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
/**
* Subscribe on event topic
*/
public class WSSubscribeOnTopicClientEvent extends WSClientEvent {
public static final String ID = "cb_client_topic_subscribe";
public WSSubscribeOnTopicClientEvent(String topicId) {
super(ID, topicId);
}
}
@@ -0,0 +1,30 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.websocket.event.client;
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
/**
* Unsubscribe from event topic
*/
public class WSUnsubscribeFromTopicClientEvent extends WSClientEvent {
public static final String ID = "cb_client_topic_unsubscribe";
public WSUnsubscribeFromTopicClientEvent(String topicId) {
super(ID, topicId);
}
}
@@ -0,0 +1,43 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.websocket.event.client;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.websocket.WSConstants;
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
import java.util.Set;
/**
* Subscribe on event topic
*/
public class WSUpdateActiveProjectsClientEvent extends WSClientEvent {
public static final String ID = "cb_client_projects_active";
@NotNull
private final Set<String> projectIds;
public WSUpdateActiveProjectsClientEvent(@NotNull Set<String> projectIds) {
super(ID, WSConstants.TOPIC_PROJECTS);
this.projectIds = projectIds;
}
@NotNull
public Set<String> getProjectIds() {
return projectIds;
}
}
@@ -0,0 +1,57 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.websocket.event.task;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.websocket.WSConstants;
import org.jkiss.dbeaver.model.websocket.event.session.WSAbstractSessionEvent;
public abstract class WSAbstractSessionTaskEvent extends WSAbstractSessionEvent {
@NotNull
private final String taskId;
@NotNull
private final String title;
@NotNull
private final String message;
public WSAbstractSessionTaskEvent(
@NotNull String eventId,
@NotNull String taskId,
@NotNull String title,
@NotNull String message
) {
super(eventId, WSConstants.TOPIC_SESSION_TASK);
this.taskId = taskId;
this.title = title;
this.message = message;
}
@NotNull
public String getTaskId() {
return taskId;
}
@NotNull
public String getTitle() {
return title;
}
@NotNull
public String getMessage() {
return message;
}
}
@@ -0,0 +1,31 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.websocket.event.task;
import org.jkiss.code.NotNull;
public class WSSessionTaskConfirmationRequestEvent extends WSAbstractSessionTaskEvent {
private static final String ID = "cb_session_task_confirmation_request";
public WSSessionTaskConfirmationRequestEvent(
@NotNull String taskId,
@NotNull String title,
@NotNull String message
) {
super(ID, taskId, title, message);
}
}
@@ -0,0 +1,58 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.websocket.event.task;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.websocket.WSConstants;
import org.jkiss.dbeaver.model.websocket.event.session.WSAbstractSessionEvent;
/**
* Async task info event.
*/
public class WSSessionTaskInfoEvent extends WSAbstractSessionEvent {
@NotNull
private final String taskId;
@Nullable
private final String statusName;
private final boolean running;
public WSSessionTaskInfoEvent(
@NotNull String taskId,
@Nullable String statusName,
boolean running
) {
super("cb_session_task_info_updated", WSConstants.TOPIC_SESSION_TASK);
this.taskId = taskId;
this.statusName = statusName;
this.running = running;
}
@NotNull
public String getTaskId() {
return taskId;
}
@Nullable
public String getStatusName() {
return statusName;
}
public boolean isRunning() {
return running;
}
}
@@ -0,0 +1,42 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.websocket.event.task;
import org.jkiss.code.NotNull;
public class WSSessionTaskQueryConfirmationRequestEvent extends WSAbstractSessionTaskEvent {
private static final String ID = "cb_session_task_query_confirmation_request";
@NotNull
private final String query;
public WSSessionTaskQueryConfirmationRequestEvent(
@NotNull String taskId,
@NotNull String title,
@NotNull String message,
@NotNull String query
) {
super(ID, taskId, title, message);
this.query = query;
}
@NotNull
public String getQuery() {
return query;
}
}
@@ -0,0 +1,52 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.websocket.event.task;
import io.cloudbeaver.service.sql.WebSQLQueryParameterRecord;
import org.jkiss.code.NotNull;
import java.util.List;
public class WSSessionTaskQueryParamsConfirmationEvent extends WSAbstractSessionTaskEvent {
private static final String ID = "cb_session_task_query_params_confirmation_request";
@NotNull
private final String query;
@NotNull
private final List<WebSQLQueryParameterRecord> parameters;
public WSSessionTaskQueryParamsConfirmationEvent(
@NotNull String taskId,
@NotNull String title,
@NotNull String message,
@NotNull String query,
@NotNull List<WebSQLQueryParameterRecord> parameters
) {
super(ID, taskId, title, message);
this.query = query;
this.parameters = parameters;
}
@NotNull
public String getQuery() {
return query;
}
@NotNull
public List<WebSQLQueryParameterRecord> getParameters() {
return parameters;
}
}
@@ -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.2.5.qualifier
Bundle-Release-Date: 20251117
Bundle-Version: 26.0.1.qualifier
Bundle-Release-Date: 20260323
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce
@@ -10,9 +10,8 @@
</extension>
<extension point="org.eclipse.core.runtime.products" id="product">
<product application="io.cloudbeaver.product.ce.application" description="CloudBeaver CE Web UI Application"
name="CloudBeaver CE Server">
<property name="appName" value="CloudBeaver CE Server"/>
<product application="io.cloudbeaver.product.ce.application" name="CloudBeaver Community">
<property name="appName" value="CloudBeaver Community"/>
<property name="versionUpdateURL" value="https://dbeaver.com/product/cloudbeaver-ce-version.json"/>
</product>
</extension>
@@ -21,8 +20,7 @@
<application
id="cloudbeaver-ce"
family="CB"
name="CloudBeaver CE Server"
description="CloudBeaver CE Web UI"
name="CloudBeaver Community"
server="true"/>
</extension>
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.product.ce</artifactId>
<version>25.2.5-SNAPSHOT</version>
<version>26.0.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.134.qualifier
Bundle-Release-Date: 20251117
Bundle-Version: 1.0.142.qualifier
Bundle-Release-Date: 20260323
Bundle-Vendor: DBeaver Corp
Bundle-ActivationPolicy: lazy
Automatic-Module-Name: io.cloudbeaver.resources.drivers.base
@@ -59,7 +59,7 @@
<driver id="sqlite:libsql_jdbc"/>
<driver id="sqlserver:microsoft"/>
<driver id="generic:trino_jdbc"/>
<driver id="generic:duckdb_jdbc"/>
<driver id="duckdb:duckdb_jdbc"/>
<driver id="generic:kyuubi_hive"/>
<driver id="databend:databend"/>
</extension>
@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.resources.drivers.base</artifactId>
<version>1.0.134-SNAPSHOT</version>
<version>1.0.142-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,14 +1,15 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver CE Server
Bundle-Name: CloudBeaver Community
Bundle-SymbolicName: io.cloudbeaver.server.ce;singleton:=true
Bundle-Version: 25.2.5.qualifier
Bundle-Release-Date: 20251117
Bundle-Version: 26.0.1.qualifier
Bundle-Release-Date: 20260323
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Bundle-Activator: io.cloudbeaver.server.CBPlatformActivator
Require-Bundle: io.cloudbeaver.server;visibility:=reexport,
io.cloudbeaver.model.cli;visibility:=reexport,
com.google.guava
Export-Package: io.cloudbeaver,
io.cloudbeaver.model,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server.ce</artifactId>
<version>25.2.5-SNAPSHOT</version>
<version>26.0.1-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -34,7 +34,7 @@ public class CBWebServerConfig extends WebServerConfig {
@Property
public String getServerURL() {
return CommonUtils.notEmpty(cbApp.getServerConfiguration().getServerURL());
return ""; // backward compatibility
}
@Property
@@ -31,7 +31,6 @@ public class CBServerConfig implements WebServerConfiguration {
private static final Log log = Log.getLog(CBServerConfig.class);
protected String serverURL;
@NotNull
protected List<String> supportedHosts = new ArrayList<>();
protected boolean forceHttps;
@@ -62,10 +61,6 @@ public class CBServerConfig implements WebServerConfiguration {
this.securityManagerConfiguration = createSecurityManagerConfiguration();
}
public String getServerURL() {
return serverURL;
}
public int getServerPort() {
return serverPort;
}
@@ -86,10 +81,12 @@ public class CBServerConfig implements WebServerConfiguration {
return contentRoot;
}
@NotNull
public String getRootURI() {
return rootURI;
}
@NotNull
public String getServicesURI() {
return serviceURI;
}
@@ -106,10 +103,6 @@ public class CBServerConfig implements WebServerConfiguration {
return staticContent;
}
public void setServerURL(String serverURL) {
this.serverURL = serverURL;
}
public void setServerPort(int serverPort) {
this.serverPort = serverPort;
}
@@ -24,15 +24,17 @@ import io.cloudbeaver.model.app.BaseServletApplication;
import io.cloudbeaver.model.app.ServletAuthApplication;
import io.cloudbeaver.model.app.ServletAuthConfiguration;
import io.cloudbeaver.model.app.ServletSystemInformationCollector;
import io.cloudbeaver.model.cli.CloudBeaverInstanceServer;
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.DBWServiceInitializer;
import io.cloudbeaver.service.DBWServiceServerConfigurator;
import io.cloudbeaver.service.security.CBEmbeddedSecurityController;
import io.cloudbeaver.service.session.CBSessionManager;
import io.cloudbeaver.utils.ServletAppUtils;
import io.cloudbeaver.utils.WebDataSourceUtils;
import org.eclipse.core.runtime.Platform;
import org.eclipse.osgi.service.datalocation.Location;
@@ -48,7 +50,6 @@ import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.impl.app.BaseApplicationImpl;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.security.SMAdminController;
import org.jkiss.dbeaver.model.security.SMConstants;
import org.jkiss.dbeaver.model.security.SMObjectType;
@@ -86,7 +87,6 @@ public abstract class CBApplication<T extends CBServerConfig>
*/
private static final long CONFIGURATION_MODE_SESSION_IDLE_TIME = 60 * 60 * 1000 * 24 * 7;
static {
Log.setDefaultDebugStream(System.out);
}
@@ -119,10 +119,6 @@ public abstract class CBApplication<T extends CBServerConfig>
this.homeDirectory = new File(initHomeFolder());
}
public String getServerURL() {
return getServerConfiguration().getServerURL();
}
// Port this server listens on. If set the 0 a random port is assigned which may be obtained with getLocalPort()
@Override
public int getServerPort() {
@@ -138,15 +134,18 @@ public abstract class CBApplication<T extends CBServerConfig>
return getServerConfiguration().getServerName();
}
@NotNull
public String getRootURI() {
return getServerConfiguration().getRootURI();
}
@NotNull
public String getServicesURI() {
return getServerConfiguration().getServicesURI();
}
@NotNull
public Path getHomeDirectory() {
return homeDirectory.toPath();
}
@@ -170,10 +169,12 @@ public abstract class CBApplication<T extends CBServerConfig>
* @return max session idle time from server configuration, may differ from {@link #getMaxSessionIdleTime()}
*/
@NotNull
public CBAppConfig getAppConfiguration() {
return getServerConfigurationController().getAppConfiguration();
}
@NotNull
public T getServerConfiguration() {
return getServerConfigurationController().getServerConfiguration();
}
@@ -199,6 +200,11 @@ public abstract class CBApplication<T extends CBServerConfig>
@Override
protected void startServer() {
try {
createInstanceServer();
} catch (Exception e) {
log.error("Error initializing instance server", e);
}
try {
if (!loadServerConfiguration()) {
return;
@@ -214,23 +220,13 @@ public abstract class CBApplication<T extends CBServerConfig>
configurationMode = CommonUtils.isEmpty(getServerConfiguration().getServerName());
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
Location instanceLoc = Platform.getInstanceLocation();
try {
if (!instanceLoc.isSet()) { // always false?
URL wsLocationURL = new URL(
"file", //$NON-NLS-1$
null,
getWorkspaceDirectory().toAbsolutePath().toString());
URL wsLocationURL = getWorkspaceDirectory().toUri().toURL();
instanceLoc.set(wsLocationURL, true);
}
} catch (Exception e) {
@@ -258,9 +254,6 @@ public abstract class CBApplication<T extends CBServerConfig>
//log.debug("\tProduct details: " + application.getInfoDetails());
log.debug("\tListen port: " + getServerPort() + (CommonUtils.isEmpty(getServerHost()) ? " on all interfaces" : " on " + getServerHost()));
log.debug("\tBase URI: " + getServicesURI());
if (!isConfigurationMode()) {
log.debug("\tGlobal access server URL: " + getServerConfiguration().getServerURL());
}
if (isDevelMode()) {
log.debug("\tDevelopment mode");
} else {
@@ -273,7 +266,8 @@ public abstract class CBApplication<T extends CBServerConfig>
determineLocalAddresses();
log.debug("\tLocal host addresses:");
for (InetAddress ia : localInetAddresses) {
log.debug("\t\t" + ia.getHostAddress() + " (" + ia.getCanonicalHostName() + ")");
log.debug("\t\t" + ia.getHostAddress() +
(Objects.equals(ia.getHostAddress(), ia.getCanonicalHostName()) ? "" : (" (" + ia.getCanonicalHostName() + ")")));
}
}
{
@@ -292,7 +286,8 @@ public abstract class CBApplication<T extends CBServerConfig>
try {
initializeServer();
} catch (DBException e) {
log.error("Error initializing server", e);
log.error("Error initializing " + systemInformationCollector.getProductName(), e);
shutdown();
return;
}
@@ -300,6 +295,7 @@ public abstract class CBApplication<T extends CBServerConfig>
initializeSecurityController();
} catch (Exception e) {
log.error("Error initializing database", e);
shutdown();
return;
}
@@ -334,13 +330,14 @@ public abstract class CBApplication<T extends CBServerConfig>
Set<String> enabledFeatures = new LinkedHashSet<>(Arrays.asList(getAppConfiguration().getEnabledFeatures()));
Set<String> disabledFeatures = new LinkedHashSet<>(Arrays.asList(getAppConfiguration().getDisabledFeatures()));
WebFeatureRegistry.getInstance().getWebFeatures().stream()
ServletAppUtils.getServletApplication().getFeatureRegistry().getWebFeatures().stream()
.filter(f -> f.isEnabledByDefault() && !disabledFeatures.contains(f.getId()))
.forEach(f -> enabledFeatures.add(f.getId()));
getAppConfiguration().setEnabledFeatures(enabledFeatures.toArray(new String[0]));
}
@NotNull
protected ServletSystemInformationCollector<?> createSystemInformationCollector() {
return new ServletSystemInformationCollector<>(this);
}
@@ -351,7 +348,6 @@ public abstract class CBApplication<T extends CBServerConfig>
*/
protected void performAutoConfiguration(Path configPath) {
String autoServerName = System.getenv(CBConstants.VAR_AUTO_CB_SERVER_NAME);
String autoServerURL = System.getenv(CBConstants.VAR_AUTO_CB_SERVER_URL);
String autoAdminName = System.getenv(CBConstants.VAR_AUTO_CB_ADMIN_NAME);
String autoAdminPassword = System.getenv(CBConstants.VAR_AUTO_CB_ADMIN_PASSWORD);
@@ -366,7 +362,6 @@ public abstract class CBApplication<T extends CBServerConfig>
autoProps.load(is);
autoServerName = autoProps.getProperty(CBConstants.VAR_AUTO_CB_SERVER_NAME);
autoServerURL = autoProps.getProperty(CBConstants.VAR_AUTO_CB_SERVER_URL);
autoAdminName = autoProps.getProperty(CBConstants.VAR_AUTO_CB_ADMIN_NAME);
autoAdminPassword = autoProps.getProperty(CBConstants.VAR_AUTO_CB_ADMIN_PASSWORD);
} catch (IOException e) {
@@ -384,7 +379,6 @@ public abstract class CBApplication<T extends CBServerConfig>
}
CBServerConfig serverConfig = new CBServerConfig();
serverConfig.setServerName(autoServerName);
serverConfig.setServerURL(autoServerURL);
serverConfig.setMaxSessionIdleTime(getMaxSessionIdleTime());
try {
finishConfiguration(
@@ -404,6 +398,7 @@ public abstract class CBApplication<T extends CBServerConfig>
}
protected void initializeServer() throws DBException {
refreshServerConfiguration(); // update features and drivers
for (DBWServiceServerConfigurator wsc : WebServiceRegistry.getInstance()
.getWebServices(DBWServiceServerConfigurator.class)) {
try {
@@ -494,10 +489,20 @@ public abstract class CBApplication<T extends CBServerConfig>
protected void shutdown() {
log.debug("Cloudbeaver Server is stopping"); //$NON-NLS-1$
try {
if (securityController instanceof CBEmbeddedSecurityController<?> embeddedSecurityController) {
embeddedSecurityController.shutdown();
}
} catch (Exception e) {
log.error(e);
}
eventController.scheduleCheckJob();
}
@Override
public String getInfoDetails(DBRProgressMonitor monitor) {
public String getInfoDetails() {
return "";
}
@@ -519,6 +524,7 @@ public abstract class CBApplication<T extends CBServerConfig>
return getServerConfigurationController().getLocalHostAddress();
}
@NotNull
public List<InetAddress> getLocalInetAddresses() {
return localInetAddresses;
}
@@ -667,6 +673,7 @@ public abstract class CBApplication<T extends CBServerConfig>
return null;
}
@NotNull
public CBSessionManager getSessionManager() {
if (sessionManager == null) {
sessionManager = createSessionManager();
@@ -691,6 +698,7 @@ public abstract class CBApplication<T extends CBServerConfig>
return List.of();
}
@NotNull
@Override
public WSEventController getEventController() {
return eventController;
@@ -772,17 +780,18 @@ public abstract class CBApplication<T extends CBServerConfig>
initActions.remove(actionId);
}
@NotNull
public Map<String, String> getInitActions() {
return Map.copyOf(initActions);
}
@NotNull
@Override
public WebServerConfig getWebServerConfig() {
return new CBWebServerConfig(this);
}
@NotNull
@Override
public ServletSystemInformationCollector<?> getSystemInformationCollector() {
return systemInformationCollector;
}
@@ -795,4 +804,8 @@ public abstract class CBApplication<T extends CBServerConfig>
public <T> T getApplicationContextValue(@NotNull String key) {
return (T) applicationContext.get(key);
}
protected CloudBeaverInstanceServer createInstanceServer() throws IOException {
return new CloudBeaverInstanceServer();
}
}
@@ -17,8 +17,6 @@
package io.cloudbeaver.server;
import io.cloudbeaver.auth.NoAuthCredentialsProvider;
import io.cloudbeaver.model.CBWebServerConfig;
import io.cloudbeaver.model.WebServerConfig;
import io.cloudbeaver.model.config.CBServerConfig;
import io.cloudbeaver.model.rm.local.LocalResourceController;
import io.cloudbeaver.service.security.CBEmbeddedSecurityController;
@@ -79,6 +77,7 @@ public class CBApplicationCE extends CBApplication<CBServerConfig> {
@NotNull
@Override
public RMController createResourceController(@NotNull SMCredentialsProvider credentialsProvider,
@NotNull DBPWorkspace workspace) throws DBException {
@@ -96,17 +95,6 @@ public class CBApplicationCE extends CBApplication<CBServerConfig> {
return serverConfigController;
}
protected void shutdown() {
try {
if (securityController instanceof CBEmbeddedSecurityController<?> embeddedSecurityController) {
embeddedSecurityController.shutdown();
}
} catch (Exception e) {
log.error(e);
}
super.shutdown();
}
protected void finishSecurityServiceConfiguration(
@NotNull String adminName,
@Nullable String adminPassword,
@@ -116,4 +104,9 @@ public class CBApplicationCE extends CBApplication<CBServerConfig> {
embeddedSecurityController.finishConfiguration(adminName, adminPassword, authInfoList);
}
}
@Override
public boolean isCommunity() {
return true;
}
}
@@ -20,14 +20,13 @@ package io.cloudbeaver.server;
import io.cloudbeaver.server.jobs.SessionStateJob;
import io.cloudbeaver.server.jobs.WebDataSourceMonitorJob;
import io.cloudbeaver.server.jobs.WebSessionMonitorJob;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.app.DBPRegistryListener;
import org.jkiss.dbeaver.model.impl.app.BaseApplicationImpl;
import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore;
import org.jkiss.dbeaver.model.runtime.AbstractJob;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.runtime.LoggingProgressMonitor;
import org.jkiss.dbeaver.registry.DataSourceProviderRegistry;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.utils.IOUtils;
@@ -38,12 +37,11 @@ import java.io.IOException;
*/
public class CBPlatform extends BaseWebPlatform {
// The plug-in ID
public static final String PLUGIN_ID = "io.cloudbeaver.server"; //$NON-NLS-1$
private static final Log log = Log.getLog(CBPlatform.class);
private WebServerPreferenceStore preferenceStore;
private DBPRegistryListener driverRegistryListener;
private boolean isShutdown;
public static CBPlatform getInstance() {
return (CBPlatform) DBWorkbench.getPlatform();
@@ -55,10 +53,19 @@ public class CBPlatform extends BaseWebPlatform {
@Override
protected synchronized void initialize() {
long startTime = System.currentTimeMillis();
log.info("Initialize web platform...: ");
log.info("Initialize web platform " + getClass().getSimpleName());
this.preferenceStore = new WebServerPreferenceStore(WebPlatformActivator.getInstance().getPreferences());
super.initialize();
scheduleServerJobs();
this.scheduleServerJobs();
this.driverRegistryListener = new DBPRegistryListener() {
@Override
public void handleRegistryReload() {
WebAppUtils.getWebApplication().getDriverRegistry().refreshApplicableDrivers();
}
};
DataSourceProviderRegistry.getInstance().addDataSourceRegistryListener(driverRegistryListener);
log.info("Web platform initialized (" + (System.currentTimeMillis() - startTime) + "ms)");
}
@@ -73,23 +80,24 @@ public class CBPlatform extends BaseWebPlatform {
new WebDataSourceMonitorJob(this, getApplication().getSessionManager())
.scheduleMonitor();
new AbstractJob("Delete temp folder") {
@Override
protected IStatus run(DBRProgressMonitor monitor) {
try {
IOUtils.deleteDirectory(getTempFolder(monitor, TEMP_FILE_FOLDER));
IOUtils.deleteDirectory(getTempFolder(monitor, TEMP_FILE_IMPORT_FOLDER));
} catch (IOException e) {
throw new RuntimeException(e);
}
return Status.OK_STATUS;
}
}.schedule();
try {
LoggingProgressMonitor monitor = new LoggingProgressMonitor(log);
IOUtils.deleteDirectory(getTempFolder(monitor, TEMP_FILE_FOLDER));
IOUtils.deleteDirectory(getTempFolder(monitor, TEMP_FILE_IMPORT_FOLDER));
} catch (IOException e) {
log.error(e);
}
}
public synchronized void dispose() {
long startTime = System.currentTimeMillis();
log.debug("Shutdown Core...");
isShutdown = true;
log.debug("Shutdown web platform");
if (driverRegistryListener != null) {
DataSourceProviderRegistry.getInstance().removeDataSourceRegistryListener(driverRegistryListener);
driverRegistryListener = null;
}
super.dispose();
@@ -112,7 +120,7 @@ public class CBPlatform extends BaseWebPlatform {
@Override
public boolean isShuttingDown() {
return false;
return isShutdown;
}
}
@@ -56,6 +56,9 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
private static final Log log = Log.getLog(CBServerConfigurationController.class);
private static final String NETWORK_MODE_ENV = "NETWORK_MODE";
private static final String NETWORK_MODE_VALUE_HOST = "host";
// Configurations
@NotNull
private final T serverConfiguration;
@@ -167,12 +170,9 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
public T parseServerConfiguration() {
var config = getServerConfiguration();
if (config.getServerURL() == null) {
String hostName = config.getServerHost();
if (CommonUtils.isEmpty(hostName)) {
hostName = getLocalHostAddress();
}
config.setServerURL("http://" + hostName + ":" + config.getServerPort());
if (NETWORK_MODE_VALUE_HOST.equalsIgnoreCase(System.getenv(NETWORK_MODE_ENV))) {
String hostName = getLocalHostAddress();
config.setServerHost(hostName);
}
config.setContentRoot(ServletAppUtils.getRelativePath(config.getContentRoot(), homeDirectory));
@@ -383,12 +383,6 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
}
}
public synchronized void updateServerUrl(@NotNull SMCredentialsProvider credentialsProvider,
@Nullable String newPublicUrl) throws DBException {
getServerConfiguration().setServerURL(newPublicUrl);
}
protected Map<String, Object> collectConfigurationProperties(
@NotNull CBServerConfig serverConfig,
@NotNull CBAppConfig appConfig
@@ -523,10 +517,6 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
CBConstants.PARAM_SERVER_NAME,
serverConfig.getServerName());
}
if (!CommonUtils.isEmpty(serverConfig.getServerURL())) {
copyConfigValue(
originServerConfig, serverConfigProperties, CBConstants.PARAM_SERVER_URL, serverConfig.getServerURL());
}
if (serverConfig.getMaxSessionIdleTime() > 0) {
copyConfigValue(
originServerConfig,
@@ -26,13 +26,14 @@ import io.cloudbeaver.server.graphql.GraphQLEndpoint;
import io.cloudbeaver.server.servlets.CBImageServlet;
import io.cloudbeaver.server.servlets.CBStaticServlet;
import io.cloudbeaver.server.servlets.WebStatusServlet;
import io.cloudbeaver.server.websockets.CBEventsLongPollingServlet;
import io.cloudbeaver.server.websockets.CBEventsWebSocket;
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.*;
import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer;
import org.eclipse.jetty.ee11.servlet.*;
import org.eclipse.jetty.ee11.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.xml.XmlConfiguration;
@@ -47,11 +48,13 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class CBJettyServer {
private static final Log log = Log.getLog(CBJettyServer.class);
static {
// Set Jetty log level to WARN
System.setProperty("org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.StdErrLog");
@@ -118,6 +121,9 @@ public class CBJettyServer {
servletContextHandler.addServlet(new ServletHolder("status", new WebStatusServlet()), "/status");
ServletHolder eventsServletHolder = new ServletHolder("events", new CBEventsLongPollingServlet());
servletContextHandler.addServlet(eventsServletHolder, serverConfiguration.getServicesURI() + "events/*");
GraphQLEndpoint endpoint = new GraphQLEndpoint(new ServerConfigurationTimeLimitFilter(application));
application.addApplicationContextValue(GraphQL.class.getName(), endpoint.getGraphQL());
String gqlServletPath = serverConfiguration.getServicesURI() + "gql/*";
@@ -195,11 +201,15 @@ public class CBJettyServer {
log.debug("\t" + sm.getServletName() + ": " + Arrays.toString(sm.getPathSpecs())); //$NON-NLS-1$
}
log.debug("Active websocket mappings:");
for (String mapping : webSocketContext.getMappings()) {
log.debug("\t" + mapping);
List<String> wsMappings = webSocketContext.getMappings();
if (!wsMappings.isEmpty()) {
log.debug("Active websocket mappings:");
for (String mapping : wsMappings) {
log.debug("\t" + mapping);
}
} else {
log.debug("No websocket mappings");
}
}
boolean forwardProxy = application.getAppConfiguration().isEnabledForwardProxy();
@@ -39,7 +39,6 @@ public class CBServerContextListener implements ServletContextListener {
//scf.setDomain(domain);
//scf.setMaxAge(CB_SESSION_LIFE_TIME);
cookieConfig.setPath(CBApplication.getInstance().getRootURI());
// cookieConfig.setSecure(application.getServerURL().startsWith("https"));
cookieConfig.setHttpOnly(true);
cookieConfig.setName(CBConstants.CB_SESSION_COOKIE_NAME);
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -33,6 +33,7 @@ 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);
@@ -74,9 +75,19 @@ public class RequestHostFilter implements Filter {
chain.doFilter(request, response);
return;
}
boolean isIpAddress = InetAddresses.isInetAddress(originUri.getHost());
if (CommonUtils.isNotEmpty(originUri.getHost())) {
requestAllowed = InetAddresses.isInetAddress(originUri.getHost());
} else {
log.debug(
"Request origin host is null, request URI - " + originUri +
", request path - " + httpRequest.getServletPath() +
", request url - " + httpRequest.getRequestURL()
);
}
String servletPath = httpRequest.getServletPath();
requestAllowed = isIpAddress;
if (!requestAllowed) {
if (CommonUtils.isNotEmpty(servletPath)) {
for (String excludedPath : excludedPaths) {
@@ -34,7 +34,7 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.ee10.servlet.DefaultServlet;
import org.eclipse.jetty.ee11.servlet.DefaultServlet;
import org.eclipse.jetty.http.HttpHeader;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
@@ -17,6 +17,7 @@
package io.cloudbeaver.service;
import io.cloudbeaver.server.CBApplication;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
/**
@@ -24,6 +25,6 @@ import org.jkiss.dbeaver.DBException;
*/
public interface DBWServiceInitializer extends DBWServiceBinding {
void initializeService(CBApplication<?> application) throws DBException;
void initializeService(@NotNull CBApplication<?> application) throws DBException;
}
@@ -26,6 +26,7 @@ import io.cloudbeaver.server.CBConstants;
import io.cloudbeaver.server.WebAppSessionManager;
import io.cloudbeaver.server.events.WSWebUtils;
import io.cloudbeaver.service.DBWSessionHandler;
import io.cloudbeaver.utils.ServletAppUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
@@ -49,10 +50,10 @@ public class CBSessionManager implements WebAppSessionManager {
private static final Log log = Log.getLog(CBSessionManager.class);
private final CBApplication application;
private final CBApplication<?> application;
private final Map<String, BaseWebSession> sessionMap = new HashMap<>();
public CBSessionManager(CBApplication application) {
public CBSessionManager(CBApplication<?> application) {
this.application = application;
}
@@ -88,7 +89,7 @@ public class CBSessionManager implements WebAppSessionManager {
return null;
}
protected CBApplication getApplication() {
protected CBApplication<?> getApplication() {
return application;
}
@@ -165,6 +166,8 @@ public class CBSessionManager implements WebAppSessionManager {
validateSessionIp(request, webSession);
webSession.updateClientOrigin(ServletAppUtils.getOriginFromRequest(request));
return webSession;
}
@@ -264,7 +267,7 @@ public class CBSessionManager implements WebAppSessionManager {
}
@NotNull
protected Map<String, DBWSessionHandler> getSessionHandlers() {
protected Map<String, DBWSessionHandler<WebSession>> getSessionHandlers() {
return WebHandlerRegistry.getInstance().getSessionHandlers()
.stream()
.collect(Collectors.toMap(WebSessionHandlerDescriptor::getId, WebSessionHandlerDescriptor::getInstance));
@@ -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.2.5.qualifier
Bundle-Release-Date: 20251117
Bundle-Version: 26.0.1.qualifier
Bundle-Release-Date: 20260323
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.eclipse.core.runtime;visibility:=reexport,
@@ -18,6 +18,7 @@ Require-Bundle: org.eclipse.core.runtime;visibility:=reexport,
org.jkiss.bundle.apache.dbcp,
org.jkiss.dbeaver.net.ssh,
io.cloudbeaver.model;visibility:=reexport,
io.cloudbeaver.model.cli,
io.cloudbeaver.service.security;visibility:=reexport
Export-Package: io.cloudbeaver,
io.cloudbeaver.model,
@@ -39,7 +39,7 @@
</extension>
<extension point="org.jkiss.dbeaver.ws.event.handler">
<eventHandler class="io.cloudbeaver.server.events.WSDefaultEventHandler">
<eventHandler class="io.cloudbeaver.server.events.WSServerConfigurationChangedEventHandler">
<topic id="cb_config"/>
</eventHandler>
<eventHandler class="io.cloudbeaver.server.events.WSDataSourceUpdatedEventHandlerImpl">
@@ -60,6 +60,9 @@
<eventHandler class="io.cloudbeaver.server.events.WSEventHandlerWorkspaceConfigUpdate">
<topic id="cb_workspace_configuration"/>
</eventHandler>
<eventHandler class="io.cloudbeaver.server.events.WSObjectSettingsEventHandler">
<topic id="cb_object_settings"/>
</eventHandler>
</extension>
<extension point="org.jkiss.dbeaver.settings">
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server</artifactId>
<version>25.2.5-SNAPSHOT</version>
<version>26.0.1-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -548,7 +548,11 @@ type ConnectionInfo {
"Supported features (provided etc)"
features: [ String! ]!
"Navigator settings for the connection (with user settings applied)"
navigatorSettings: NavigatorSettings!
"Default navigator settings for the connection (without user settings applied)"
defaultNavigatorSettings: NavigatorSettings!
supportedDataFormats: [ ResultDataFormat! ]!
configurationType: DriverConfigurationType
@@ -595,6 +599,8 @@ type NavigatorSettings {
hideFolders: Boolean!
hideSchemas: Boolean!
hideVirtualModel: Boolean!
"Shows whether these settings are user-specific or global"
userSettings: Boolean! @since(version: "25.3.2")
}
type RMResourceType {
@@ -637,6 +643,8 @@ input NavigatorSettingsInput {
hideFolders: Boolean!
hideSchemas: Boolean!
hideVirtualModel: Boolean!
userSettings: Boolean @since(version: "25.3.2")
}
input NetworkHandlerConfigInput {
@@ -816,10 +824,10 @@ extend type Mutation {
"Test connection configuration. Returns remote server version"
testConnection( config: ConnectionConfig!, projectId: ID): ConnectionInfo!
"Test network handler"
testNetworkHandler( config: NetworkHandlerConfigInput! ): NetworkEndpointInfo!
"Test network handler connectivity"
testNetworkHandler(projectId: ID, connectionId: ID, config: NetworkHandlerConfigInput! ): NetworkEndpointInfo!
"Initiate existing connection"
"Initiate database connection. User credentials may be (optionally) override default settings."
initConnection(
id: ID!,
projectId: ID,
@@ -830,11 +838,13 @@ extend type Mutation {
selectedSecretId:String
): ConnectionInfo!
"Disconnect from database"
"Disconnect from the database"
closeConnection( id: ID!, projectId: ID ): ConnectionInfo!
"Change navigator settings for connection"
"Change navigator settings for connection. It can change default settings or user settings depending on 'default' parameter."
setConnectionNavigatorSettings( id: ID!, projectId: ID, settings: NavigatorSettingsInput!): ConnectionInfo!
"Sets to default navigator settings for connection. Resets all user navigator settings for this connection."
clearConnectionNavigatorSettings(projectId: ID!, id: ID!): ConnectionInfo! @since(version: "25.3.2")
#### Generic async functions
@@ -59,6 +59,7 @@ enum CBServerEventId {
cb_database_output_log_updated,
cb_ai_chat_message_chunk @since(version: "25.1.1")
cb_ai_chat_message_error @since(version: "25.1.1")
cb_ai_chat_message @since(version: "25.3.5")
"Transaction count updated"
cb_transaction_count @since(version: "24.3.3"),
@@ -71,8 +72,12 @@ enum CBServerEventId {
cb_server_notification@since(version: "25.2.1")
"Session task confirmations"
cb_session_task_confirmation_request@since(version: "25.2.2")
cb_session_task_query_confirmation_request@since(version: "25.2.2")
cb_session_task_confirmation_request @since(version: "25.2.2")
cb_session_task_query_confirmation_request @since(version: "25.2.2")
cb_session_task_query_params_confirmation_request @since(version: "25.2.5")
"Open URL request in client"
cb_open_url @since(version: "25.3.1")
}
"Events sent by client"
@@ -81,7 +86,8 @@ enum CBClientEventId {
cb_client_topic_unsubscribe,
cb_client_projects_active,
cb_client_session_ping,
cb_client_session_task_confirmation@since(version: "25.2.2")
cb_client_session_task_confirmation @since(version: "25.2.2")
cb_client_session_task_with_parameters_confirmation @since(version: "25.2.5")
}
"Client subscribes on topic to receive only related events"
@@ -106,6 +112,7 @@ enum CBEventTopic {
cb_workspace_configuration @since(version: "25.1.1")
cb_ai @since(version: "25.1.1")
cb_notification @since(version: "25.2.1")
cb_session_action @since(version: "25.3.1")
}
"Base server event interface"
@@ -284,12 +291,31 @@ type WSAIChatMessageErrorEvent implements CBServerEvent @since(version: "25.1.1"
errorMessage: String
}
type WSAIChatMessageEvent implements CBServerEvent @since(version: "25.3.5") {
id: CBServerEventId!
topicId: CBEventTopic
conversationId: ID!
messageId: ID!
role: String!
content: String!
displayMessage: String!
time: String!
}
enum WSServerNotificationEventType {
INFO
ERROR
LOADING
CUSTOM
}
"Query parameter information"
type WSQueryParameterInfo {
name: String!
value: String
}
"General event for showing user notification"
type WSServerNotificationEvent implements CBServerEvent @since(version: "25.2.1") {
id: CBServerEventId!
@@ -299,6 +325,14 @@ type WSServerNotificationEvent implements CBServerEvent @since(version: "25.2.1"
notificationType: WSServerNotificationEventType!
}
"Event for opening URL in client application"
type WSOpenUrlEvent implements CBServerEvent @since(version: "25.3.1") {
id: CBServerEventId!
topicId: CBEventTopic!
timestamp: Int!
url: String!
}
type WSSessionTaskConfirmationRequestEvent implements CBServerEvent @since(version: "25.2.2") {
id: CBServerEventId!
topicId: CBEventTopic!
@@ -324,6 +358,23 @@ type WSSessionTaskConfirmationEvent implements CBClientEvent @since(version: "25
skipConfirmations: Boolean!
}
type WSSessionTaskQueryParamsConfirmationEvent implements CBServerEvent @since(version: "26.0.0") {
id: CBServerEventId!
topicId: CBEventTopic!
taskId: ID!
title: String!
message: String!
query: String!
parameters: [WSQueryParameterInfo]!
}
type WSSessionTaskWithParametersConfirmationEvent implements CBClientEvent @since(version: "25.2.5") {
id: CBClientEventId!
topicId: CBEventTopic
taskId: ID!
parameters: Object!
}
extend type Query {
emptyEvent: Boolean
}
@@ -155,8 +155,8 @@ extend type Mutation {
"Reloads node and returns updated node info"
navReloadNode( nodePath: ID! ): NavigatorNodeInfo!
"Renames node and returns new node name"
navRenameNode( nodePath: ID!, newName: String! ): String
"Renames node and returns new node id"
navRenameNode( nodePath: ID!, newName: String! ): String!
"Deletes nodes with specified IDs and returns number of deleted nodes"
navDeleteNodes( nodePaths: [ID!]! ): Int
@@ -51,7 +51,9 @@ type SQLContextInfo {
}
input SQLDataFilterConstraint {
attributePosition: Int!
"Either attributeName or attributePosition must be specified"
attributeName: String
attributePosition: Int
orderPosition: Int
orderAsc: Boolean
@@ -126,7 +128,11 @@ type SQLResultSet {
# server always returns hasMoreData = false
hasMoreData: Boolean!
"Identifies if result set has row identifier. If true then it is possible update data or load LOB files"
hasRowIdentifier: Boolean!
hasRowIdentifier: Boolean! @deprecated (reason: "use rowIdentifierState (25.3.4)")
"State of a table row identifier: METADATA_NOT_FOUND, NONE, PRIMARY_KEY, VIRTUAL_KEY"
rowIdentifierState: SQLRowIdentifierState @since(version: "25.3.4")
"Contains info about a result set row identifier. Present if rowIdentifierState is PRIMARY_KEY"
rowIdentifier: SQLRowIdentifier @since(version: "25.3.4")
"Identifies if result has children collections. If true then children collections can be read from the result set"
hasChildrenCollection: Boolean!
"Identifies if result set supports dynamic trace. If true then dynamic trace can be read from the result set"
@@ -139,6 +145,38 @@ type SQLResultSet {
readOnlyStatus: String @since(version: "25.0.1")
}
enum SQLRowIdentifierState {
METADATA_NOT_FOUND,
NONE,
PRIMARY_KEY,
VIRTUAL_KEY
}
enum SQLResultSetGeneratorId {
"SELECT .. WHERE .. ="
dataSelect,
"SELECT .. WHERE .. IN"
dataSelectMany,
"INSERT"
dataInsert,
"UPDATE"
dataUpdate,
"DELETE by Unique Key"
dataDeleteByUniqueKey,
}
type SQLRowIdentifier {
"Single or multiple attributes acting as a row identifier"
attributes: [SQLRowIdentifierAttribute!]!
"Type of a row identifier constraint"
constraintType: String!
}
type SQLRowIdentifierAttribute {
name: String!
ordinalPosition: Int!
}
type SQLQueryResults {
title: String
updateRowCount: Float
@@ -158,10 +196,10 @@ type SQLExecuteInfo {
duration: Int!
# Actual conditions applied to query
filterText: String
# # original sql query without SQLDataFilter
# originalQuery: String
# Full query that was executed, contains all used filters
fullQuery: String
# Original query that was executed, without modifications
originalQuery: String
# Results
results: [ SQLQueryResults! ]!
}
@@ -315,6 +353,19 @@ extend type Query {
nodePathList: [String!]!
): String!
"""
Returns an auto-generated SQL query based on provided rows data.
Uses generatorId value to establish the type of query (e.g. SELECT or INSERT)
"""
sqlGenerateResultSetQuery(
projectId: ID,
connectionId: ID!,
contextId: ID!,
generatorId: SQLResultSetGeneratorId!,
resultsId: ID!,
selectedRows: [SQLResultRow!]!,
): String!
"Parses SQL script and returns script info with queries start and end positions"
sqlParseScript(
projectId: ID,
@@ -339,7 +390,24 @@ extend type Query {
columnNames: [String!],
functions: [String!],
showDuplicatesOnly: Boolean
): String!
): String! @deprecated (reason: "use asyncSqlGroupingResultSet (25.3.1)")
"Creates async task to get grouped SQL results"
asyncSqlGroupingResultSet(
projectId: ID,
contextId: ID!,
connectionId: ID!,
"Original results ID to group"
originalResultsId: ID!,
"Current results ID is needed to apply filters/sorting on top of grouping"
currentResultsId: ID,
columnNames: [String!],
functions: [String!],
showDuplicatesOnly: Boolean,
filter: SQLDataFilter,
dataFormat: ResultDataFormat,
isInteractive: Boolean
): AsyncTaskInfo! @since(version: "25.3.1")
}
extend type Mutation {
@@ -361,7 +429,7 @@ extend type Mutation {
resultId: ID,
filter: SQLDataFilter,
dataFormat: ResultDataFormat, # requested data format. May be ignored by server
readLogs: Boolean # added 23.2.1
readLogs: Boolean, # added 23.2.1
isInteractive: Boolean # Support WS events requested by server, e.g. confirmations. Added in 23.2.3
): AsyncTaskInfo!
@@ -39,7 +39,10 @@ import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.model.navigator.*;
import org.jkiss.dbeaver.model.navigator.DBNContainer;
import org.jkiss.dbeaver.model.navigator.DBNDataSource;
import org.jkiss.dbeaver.model.navigator.DBNNode;
import org.jkiss.dbeaver.model.navigator.DBNProject;
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
import org.jkiss.dbeaver.model.rm.RMProjectType;
import org.jkiss.dbeaver.registry.DataSourceNavigatorSettings;
@@ -68,7 +71,7 @@ public class WebServiceUtils extends WebCommonUtils {
return WebServiceUtils.class.getClassLoader().getResourceAsStream(path);
}
public static DBNBrowseSettings parseNavigatorSettings(Map<String, Object> settingsMap) {
public static DataSourceNavigatorSettings parseNavigatorSettings(Map<String, Object> settingsMap) {
return gson.fromJson(
gson.toJsonTree(settingsMap), DataSourceNavigatorSettings.class);
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -82,6 +82,7 @@ public class WebDatabaseAuthModel {
return Arrays.stream(credentialsSource.getProperties())
.filter(predicate)
.filter(p -> !p.isDesktop())
.map(p -> new WebPropertyInfo(webSession, p, credentialsSource)).toArray(WebPropertyInfo[]::new);
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 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.
@@ -61,6 +61,7 @@ public class WebNetworkHandlerDescriptor {
public WebPropertyInfo[] getProperties() {
return Arrays.stream(descriptor.getHandlerProperties())
.filter(p -> !p.isDesktop() && !p.isHidden())
.map(p -> new WebPropertyInfo(session, p, null)).toArray(WebPropertyInfo[]::new);
}
@@ -20,7 +20,6 @@ import io.cloudbeaver.utils.ServletAppUtils;
import org.eclipse.core.runtime.IProduct;
import org.eclipse.core.runtime.Platform;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.CommonUtils;
@@ -75,7 +74,7 @@ public class WebProductInfo {
@Property
public String getLicenseInfo() {
return provideSensitiveInformation
? ServletAppUtils.getServletApplication().getInfoDetails(new VoidProgressMonitor())
? ServletAppUtils.getServletApplication().getInfoDetails()
: "";
}
@@ -17,6 +17,7 @@
package io.cloudbeaver.registry;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.DBWSessionHandler;
import org.eclipse.core.runtime.IConfigurationElement;
import org.jkiss.code.NotNull;
@@ -31,7 +32,7 @@ public class WebSessionHandlerDescriptor extends AbstractContextDescriptor {
public static final String EXTENSION_ID = "io.cloudbeaver.servlet.handler"; //$NON-NLS-1$
private final String id;
private final DBWSessionHandler instance;
private final DBWSessionHandler<WebSession> instance;
public WebSessionHandlerDescriptor(IConfigurationElement config) throws DBException {
super(config);
@@ -45,7 +46,7 @@ public class WebSessionHandlerDescriptor extends AbstractContextDescriptor {
return id;
}
public DBWSessionHandler getInstance() {
public DBWSessionHandler<WebSession> getInstance() {
return instance;
}
@@ -23,20 +23,14 @@ import org.jkiss.dbeaver.model.DBConstants;
import org.jkiss.dbeaver.model.app.DBACertificateStorage;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.impl.app.DefaultCertificateStorage;
import org.jkiss.dbeaver.model.qm.QMRegistry;
import org.jkiss.dbeaver.model.qm.QMUtils;
import org.jkiss.dbeaver.registry.DataSourceProviderRegistry;
import org.jkiss.dbeaver.runtime.SecurityProviderUtils;
import org.jkiss.dbeaver.runtime.qm.QMLogFileWriter;
import org.jkiss.dbeaver.runtime.qm.QMRegistryImpl;
public abstract class BaseWebPlatform extends BaseServletPlatform {
public static final String TEMP_FILE_FOLDER = "temp-sql-upload-files";
public static final String TEMP_FILE_IMPORT_FOLDER = "temp-import-files";
private QMRegistryImpl queryManager;
private QMLogFileWriter qmLogWriter;
private DBACertificateStorage certificateStorage;
private ServerGlobalWorkspace workspace;
@@ -49,14 +43,11 @@ public abstract class BaseWebPlatform extends BaseServletPlatform {
getApplication().beforeWorkspaceInitialization();
this.workspace = new ServerGlobalWorkspace(this, getApplication());
this.workspace.initializeProjects();
QMUtils.initApplication(this);
this.queryManager = new QMRegistryImpl();
this.qmLogWriter = new QMLogFileWriter();
this.queryManager.registerMetaListener(qmLogWriter);
QMUtils.initPlatform(true);
this.certificateStorage = new DefaultCertificateStorage(
this,
WebPlatformActivator.getInstance()
.getStateLocation()
.toFile()
@@ -92,21 +83,8 @@ public abstract class BaseWebPlatform extends BaseServletPlatform {
@Override
public synchronized void dispose() {
super.dispose();
if (this.qmLogWriter != null) {
this.queryManager.unregisterMetaListener(qmLogWriter);
this.qmLogWriter.dispose();
this.qmLogWriter = null;
}
if (this.queryManager != null) {
this.queryManager.dispose();
//queryManager = null;
}
QMUtils.disposePlatform();
DataSourceProviderRegistry.dispose();
}
@NotNull
public QMRegistry getQueryManager() {
return queryManager;
}
}
@@ -16,13 +16,21 @@
*/
package io.cloudbeaver.server;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.runtime.DBWorkbench;
public class WebAppUtils {
public static boolean isWebApplication() {
return DBWorkbench.getPlatform().getApplication() instanceof WebApplication;
}
@NotNull
public static WebApplication getWebApplication() {
return (WebApplication) DBWorkbench.getPlatform().getApplication();
}
@NotNull
public static BaseWebPlatform getWebPlatform() {
return (BaseWebPlatform) DBWorkbench.getPlatform();
}

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