Compare commits

...
Author SHA1 Message Date
Alexey Potsetsuev 6fd0ab1027 dbeaver/pro#7111 fix: doubled hot keys handler (#3809) 2025-10-08 16:07:32 +08:00
Ruslan 254e6e3b8e dbeaver/pro#7060 fix connection read only 2025-10-02 15:53:54 +02:00
a9fd8fb909 [CB] Migrate context menu to aria kit #6756 (#3760)
* dbeaver/pro#6756 adds Menu to ui-kit, core-blocks + uses it in connection properties form

* allows to render any items not only strings

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

* cleanup

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

* ui fixes

* reverts context menu logic

* adds hover styles

* cleanup

* removes MenuField

* adds combobox instead of PropertyValueSelector

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

* allows to revert values in combobox

* adjusts combobox api with correctly working custom value

* cleanup

* adds documentation for the Menu

* fix: combobox and driver properties

* chore: remove unused component

* chore: cleanup styles

* chore: add value tooltip

* fix: remove default value from combobox

* fix: tooltips

* build fix

* reverts old behavior with default value for combobox

* removes button styles + adds max width for the items

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

This reverts commit 2a92c00f32.

* removes double focus

* fixes placeholder so it is not undefined string

* fixes value placeholder + moves title to input

* build fix

* fixes border behavior

* adds default value for property items

* fix: improve combobox types

* fix: use default value

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

* fix: eslint

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

This reverts commit 272baafab6.

* reverts styles fixes for double border

* cleanup

* adds medium size to ui kit

---------

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

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

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

---------

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

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

---------

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

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

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

* dbeaver/pro#5848 use project settings for renaming

* dbeaver/pro#5848 add events for project renaming

* dbeaver/pro#5848 use display name

* dbeaver/pro#5848 add rename to desktop te

* dbeaver/pro#5848 fix validating project name

* dbeaver/pro#5848 validate project name on creation

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

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

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

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

---------

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

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

* dbeaver/pro#6532 fix adding project settings

* dbeaver/pro#6532 remove redundant code

* dbeaver/pro#6532 rename gql functions

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

* dbeaver/pro#6532 test fix

---------

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

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

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

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

* dbeaver/pro#6639 style: indent

---------

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

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

---------

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

* dbeaver/pro#6656 fix data source connection test

---------

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

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

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

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

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

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

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

* dbeaver/pro#5471 fix: extra import

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

---------

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

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

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

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

---------

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

* dbeaver/pro#6229 review fix

* dbeaver/pro#6229 update run scripts

---------

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

* chore: remove duplicate

* fix: ladle

* chore: remove storybook onboarding

* adds task to run storybook

---------

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

* fix: state sync

* fix: segment resolution

* chore: add comment

* refactor: remove old code, add todo about problem

* fix: yarn.lock

---------

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

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

* dbeaver/pro#5752 fix linter

---------

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

* dbeaver/pro#4742 add useTreeMenu hook

* dbeaver/pro#4742 revert hook renaming

---------

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

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

* moves secrets into provider properties

* renames optionsExtendedForm to ProviderPropertiesExtendedForm

* build fix

* dbeaver/pro#5170 set external params fix

* moves providerProperties container to ConnectionFormService

* cleanup

* adds empty arrays to connection secret reset state

* truncates select items and select itself

* Revert "truncates select items and select itself"

This reverts commit 56c943b5a0.

---------

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

* adds validation error to context

* adds schema parsing for UserFormInfoPart & ConnectionFormOptionsPart

* spreads RecordKeySchema across zod validations

* fixes form editing for connection info part

* Revert "spreads RecordKeySchema across zod validations"

This reverts commit 0e6719b32f.

* removes connection form errors during saving when empty fields

* reverts connection form options part parsing

* removes trimming from teams & authRole

* build fix

---------

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

* incapsulates field styles only

* moves all composes out of the layer

---------

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

* fix: linting

* fix: actions order

* fix: actions order

---------

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

* updates @wroud/vite-plugin-tsc

* updates @vitejs/plugin-react

* removes sass-embedded

* resolves sass to sass-embedded

* build fix

* linter fix

* handles empty json table edge case

* removes redundant translations

---------

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

* fixes border radius

* less border-radius

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

* removes variable

* fix: applies focus only for tab

* removes focus on ESC button

* removes onkeydown

* cleanup

---------

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

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

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

---------

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

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

---------

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

* removes default virtualization prop

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

* dbeaver/pro#6731 adds some optimizations

* cleanup

* pr fixes

* cleanup

* removes optimizations

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

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

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

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

* dbeaver/pro#5129 fix: lint async

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

move private methods to the end of files

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

* dbeaver/pro#5129 refactor: DRY ACTIONS

* dbeaver/pro#5129 fix: DRY ACTIONS

* dbeaver/pro#5129 fix: action titles

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

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

---------

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

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

---------

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

* fix: fixate position of elements

---------

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

* Update README.md

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

* Update README.md

* Update README.md

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

---------

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

* tests: enable settings tests

* chore: update syntax

* fix: dynamic theme detection

* fix: skip not stable test

* fix: skip whole test

* fix: make theme optional

* fix: page reload

* tests: skip unstable test

* fix: remove optional

* fix: load system theme

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

---------

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

* dbeaver/pro#6703 rename method

---------

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

* feat: force redirect on supportedHosts change

* fix: replace redirect with warning message

* fix: prevent active host removal

* fix: error message and ignore when on IP address

---------

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

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

* dbeaver/pro#6311 add flag to server config

* build fix

* reverts all in favor of licenseUsersModificationEnabled

* dbeaver/pro#6311 review fixes

* adds extended serverConfig

* dbeaver/pro#6311 fix disabled users

---------

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

* dbeaver/pro#6326 fixes

---------

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

* dbeaver/pro#6622 fixes

---------

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

* dbeaver/pro#6556 wrap in text

---------

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

* dbeaver/pro#4966 use nested selector

---------

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

* adds title for render field selects

---------

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

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

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

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

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

This reverts commit d124b444c7.

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

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

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

---------

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

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

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

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

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

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

set to null and revert also keep focus in place

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

This reverts commit f54abf3956.

---------

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

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

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

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

---------

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

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

* dbeaver/pro#6557 fix: user filters selects

Simplifies layout and set min width for selects

---------

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

* fix: re-subscribe on resource

* fix: ensure subscription removed
2025-08-15 23:27:34 +08:00
Alexey Potsetsuev 59e33e8c48 dbeaver/pro#6592 fix: resources loading (#3659) 2025-08-14 11:12:42 +08:00
serge-rider d16c7d4502 dbeaver/pro#6594 25.2.0 version bump 2025-08-12 20:23:19 +02:00
Alexey PotsetsuevandEvgenia a51765f516 dbeaver/pro#6189 feat: group extra tools (#3653)
* dbeaver/pro#6189 feat: group extra tools

* fix: context menu size

* fix: make context menu more compact

* fix: context menu shortcuts

* fix: change color for shortcuts in context menu

* fix: codestyle

* fix: revert more tools

* fix: submenu arrow position

* fix: show output

* fix: upload / download tooltips

---------

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

* registers some data editor settings only for CE product

* renames plugin

* moves disable import setting to public settings

* adds localizations

* fixes references

* adds comments for registration

* renames Data Viewer -> Data Editor

* dbeaver/pro#5108 add permissions

* maps settings to permissions

* fixes default settings for data import

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

* moves settings resolvers to original place

* build fix

* fixes mapping so it actually works

* cleanup

* dbeaver/pro#5108 permissions

* dbeaver/pro#5108 more granular permissions

* dbeaver/pro#5108 more granular permissions

* dbeaver/pro#5108 review fixes

* dbeaver/pro#5108 review fixes

* changes permissions settings

* fixes mapping

---------

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

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

---------

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

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

* fixes loading nested folder-nodes during connection creation

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

This reverts commit 514bb0238b.

* uses more correct solution for preloading folders

* build fix

* cleanup

* fixes deletion of the connections so it removes empty folders

* fix: outdate only one node

---------

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

It has fuzzy search inside to filter items.

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

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

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

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

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

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

* dbeaver/pro#6283 feat: implement advanced search

see AdvancedSearch example in stories

* dbeaver/pro#6283 refactor: use strict comparison

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

* dbeaver/pro#6283 refactor: simplify Combobox component

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

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

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

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

* dbeaver/pro#6283 fix: formatter

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

* dbeaver/pro#6283 refactor: simplify component

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

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

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

* dbeaver/pro#6283 refactor: strip Combobox component

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

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

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

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

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

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

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

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

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

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

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

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

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

* dbeaver/pro#6283 fix: radio title wrap

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

* dbeaver/pro#6283 fix:combobox item styles

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

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

---------

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

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

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

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

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

---------

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

* feat: support special method for dataset execution

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

---------

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

* fix: CachedResource isOutdated logic

* fix: linting

* fix: resource isOutdated tracking

---------

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

* dbeaver/pro#5679 wip

---------

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

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

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

* dbeaver/pro#6061 fix: Cell component layout

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

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

This reverts commit 812414f0f1.

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

This reverts commit 09aff9b08a.

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

force to use composition instead

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

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

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

---------

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

* migrates to zod 4

* updates deps

* lint fix

* pr fixes

* reverts connection changes

* reverts connection changes 2

* fix types for screen service

* reverts changes for screen service

* reverts string boolean refactor

---------

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

* dbeaver/pro#5679 wip

---------

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

* adds link for proxy configuration article

* fixes punctuation for ru locale

* updates description translations

* removes extra dot in ru locale

* adds advanced documentation link

---------

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

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

* dbeaver/pro#2951 Close user sessions on WSUserDisabledEvent

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

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

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

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

* dbeaver/pro#6382 fix: defaultServerConfig

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

* dbeaver/pro#6382 debug log

* dbeaver/pro#6382 remove debug logs

---------

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

* dbeaver/pro#6467 check port

* dbeaver/pro#6467 fix ip host parsing

* dbeaver/pro#6467 debug log

* dbeaver/pro#6467 remove debug logs

---------

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

* installs common-react in the scripts

* adds @dbeaver/react-tests

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

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

* removes unused deps

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

* dbeaver/pro#5552 fixes

* adds available hosts fields

* adds warnings for the https mode

* adds default host for domain manager + fixes some translations

* reverts server URL for domain manager

* fixes description for available hosts

* separates available hosts by new line in textarea

* removes dots

* removes http, https from examples for available hosts field

* dbeaver/pro#5552 validate request's host

* dbeaver/pro#5552 review fixes

* dbeaver/pro#5552 update default configs

* refactors availableHosts -> supportedHosts

* review fix

* dbeaver/pro#5552 fix build

* refactor: supportedHosts - array -> string

* cleanup

* reverts serverConfigurationInfoForm interfaces

* refactors secureCookies -> forceHttps (frontend)

* dbeaver/pro#5552 rename secureCookies param to forceHttps

* dbeaver/pro#5552 rename secureCookies param to forceHttps

* dbeaver/pro#5552 rename secureCookies param to forceHttps

* dbeaver/pro#5552 enable https for old deployments

* fixes translations, readonly state & hosts examples for TE

* dbeaver/pro#5552 fixes

* dbeaver/pro#5552 redirect from http to https

* dbeaver/pro#5552 redirect to https

* dbeaver/pro#5886 use scheme from origin

* dbeaver/pro#5552 exclude special servlets

* dbeaver/pro#5552 saml test

* dbeaver/pro#5552 remove slashes from hostname

---------

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

* dbeaver/pro#5957 fix read config

---------

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

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

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

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

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

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

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

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

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

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

use native htmlFor

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

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2025-07-29 23:09:05 +08:00
1671 changed files with 18173 additions and 26533 deletions
+3 -22
View File
@@ -49,21 +49,14 @@ jobs:
node-version: "lts/*"
cache: "yarn"
cache-dependency-path: |
cloudbeaver/common-*/yarn.lock
cloudbeaver/webapp/yarn.lock
- name: restore cloudbeaver-common typescript cache
uses: actions/cache@v4
with:
path: "cloudbeaver/common-*/**/*/lib"
key: ${{ runner.os }}-common-dist-${{ hashFiles('cloudbeaver/common-*/yarn.lock') }}
restore-keys: |
${{ runner.os }}-common-dist-
- name: restore typescript cache
uses: actions/cache@v4
with:
path: "cloudbeaver/webapp/**/packages/*/dist"
path: |
"cloudbeaver/webapp/common-*/**/*/lib"
"cloudbeaver/webapp/**/packages/*/dist"
key: ${{ runner.os }}-dist-${{ hashFiles('cloudbeaver/webapp/yarn.lock') }}
restore-keys: |
${{ runner.os }}-dist-
@@ -71,23 +64,11 @@ jobs:
- if: inputs.clean-cache
run: yarn clear
- run: yarn install --immutable
working-directory: ./cloudbeaver/common-typescript
- run: yarn install --immutable
working-directory: ./cloudbeaver/common-react
- run: yarn install --immutable
- run: yarn bundle
working-directory: ./cloudbeaver/webapp/packages/product-default
- run: yarn test
working-directory: ./cloudbeaver/common-typescript
# - run: yarn test
# working-directory: ./cloudbeaver/common-react
- run: yarn test
# - name: Archive build artifacts
+2 -2
View File
@@ -30,10 +30,10 @@ jobs:
cache-dependency-path: "./cloudbeaver/webapp/yarn.lock"
# - run: yarn install --immutable
# working-directory: ./cloudbeaver/common-typescript
# working-directory: ./cloudbeaver/webapp/common-typescript
# - run: yarn install --immutable
# working-directory: ./cloudbeaver/common-react
# working-directory: ./cloudbeaver/webapp/common-react
- run: |
yarn install --immutable
+1 -1
View File
@@ -48,7 +48,7 @@
"-Declipse.pde.launch=true",
"-XstartOnFirstThread",
"-Dfile.encoding=UTF-8",
"--add-modules=ALL-SYSTEM",
"--add-modules=ALL-DEFAULT",
"--add-opens=java.base/java.io=ALL-UNNAMED",
"--add-opens=java.base/java.lang=ALL-UNNAMED",
"--add-opens=java.base/java.lang.reflect=ALL-UNNAMED",
+23 -4
View File
@@ -26,7 +26,17 @@
"group": "test",
"args": ["test"],
"options": {
"cwd": "${workspaceFolder}/common-typescript"
"cwd": "${workspaceFolder}/webapp/common-typescript"
}
},
{
"label": "Run Tests Common React",
"type": "shell",
"command": "yarn",
"group": "test",
"args": ["test"],
"options": {
"cwd": "${workspaceFolder}/webapp/common-react"
}
},
{
@@ -82,10 +92,10 @@
"label": "Run Backend CE",
"type": "shell",
"windows": {
"command": "./run-server.bat"
"command": "./run-cloudbeaver-server.bat"
},
"osx": {
"command": "./run-server.sh"
"command": "./run-cloudbeaver-server.sh"
},
"options": {
"cwd": "${workspaceFolder}/deploy/cloudbeaver"
@@ -123,9 +133,18 @@
"type": "shell",
"command": "yarn",
"options": {
"cwd": "${workspaceFolder}/common-react/@dbeaver/ui-kit"
"cwd": "${workspaceFolder}/webapp/common-react/@dbeaver/ui-kit"
},
"args": ["docs"]
},
{
"label": "Run Storybook",
"type": "shell",
"command": "yarn",
"options": {
"cwd": "${workspaceFolder}/webapp/packages/storybook"
},
"args": ["storybook"]
}
],
"inputs": [
@@ -2,6 +2,6 @@
<?pde version="3.8"?>
<target name="org.jkiss.cloudbeaver.tp">
<locations>
<location path="../../dbeaver-workspace/dependencies/plugins" type="Directory"/>
<location path="../../../dbeaver-workspace/dependencies/plugins" type="Directory"/>
</locations>
</target>
</target>
+27 -41
View File
@@ -25,56 +25,42 @@ You can see a live demo of CloudBeaver here: https://demo.cloudbeaver.io
## Changelog
### 25.1.3 2025-07-21
- A password confirmation field has been added for administrators in the Easy Config section to help prevent accidental misconfigurations;
- Added column descriptions in the Data Editor to provide more metadata context. You can disable this in Preferences > Data Viewer;
- Added the option to display tabs across multiple rows, allowing you to see all tabs without scrolling. You can enable this in Preferences > Interface;
- Changed the default engine used for autocompletion in the SQL Editor. This Semantic engine offers improved suggestions for database objects, keywords, and functions. You can switch back to the Legacy engine in Preferences -> SQL Editor;
- Clickhouse: fixed the presentation of tuples and map data types in the data editor.
### 25.2.1 2025-09-22
- Autocomplete values in SQL Editor are ordered alphabetically to simplify object search.
- "Show all databases" setting was added on the connection page for MySQL and MariaDB.
- Different bug fixes and enhancements have been made.
### 25.1.2 2025-07-07
- The CloudBeaver default theme matches the device theme by default now. You can change this behavior in user preferences under the Theme section;
- Added a button “Clear” to the Output panel;
- Changed the data transfer mechanism to avoid intermediate file creation. The parameter dataExportFileSizeLimit was removed from the server configuration as deprecated;
- PostgresSQL: fixed misplaced comment for table DDL generation.
### 25.1.1 2025-06-23
### 25.2.0 2025-09-01
### Changes since 25.1.0:
- Added a search panel for SQL Editor and Value panel: press Ctrl+F to open a panel that allows searching and replacing by keyword or regular expression.
- Added a new welcome screen for a freshly opened application. This screen contains shortcuts to create a new connection, open SQL editor or documentation.
- Databend database support has been added.
### 25.1.0 2025-06-02
### Changes since 25.0.0:
- Important:
- Connection templates were removed from the application. You can use the Connection Editor to create new connections.
- Administration:
- Log records now capture create, update, and delete actions for users, teams, and connections for improved tracking and transparency.
- LDAP authorization:
- Added support for secure [LDAP authentication over SSL](https://github.com/dbeaver/cloudbeaver/wiki/LDAP-Authentication);
- Brute force protection is now supported for LDAP authentication. All authentication methods on the application level have this security mechanism.
- Added support for multiple server URLs to accommodate different access policies for internal and external users, improving flexibility in network-restricted environments. To set it up, use the Allowed Server URLs field in the Server configuration within the Administration section;
- Added the Force HTTPS mode setting in Server Configuration to enforce redirecting from HTTP to HTTPS. This setting helps avoid a potential man-in-the-middle attack. This setting is turned off by default. Please remember to configure your proxy before enabling;
- Added the CLOUDBEAVER_BIND_SESSION_TO_IP option to improve session security by linking user sessions to their IP address. It is disabled by default. When enabled, this helps protect against certain types of session hijacking attacks, where an attacker could try to take over a user’s session. Note: Users will be logged out automatically on the IP address change (switching networks or using mobile data);
- A password confirmation field has been added for administrators in the Easy Config section to prevent accidental misconfigurations.
- SQL Editor:
- A new setting to autosave scripts in the SQL editor appeared in global and users' preferences;
- Auto-completion has been improved for aliases and camelCase entities.
- Changed the default engine used for autocompletion in the SQL Editor. This Semantic engine offers improved suggestions for database objects, keywords, and functions. You can switch back to the Legacy engine in Preferences > SQL Editor;
- A “Clear” button was added to the output panel.
- Data Editor:
- Improved table rendering to scale column width depending on the content;
- Accessibility: improved keyboard navigation by sorting buttons in column headers.
- Column descriptions were added in the Data Editor under column names to provide more metadata context. You can disable this in Preferences > Data Viewer;
- Added an ability to review the script before execution when users edit tables without primary keys.
- General:
- Added the ability to configure the default database or schema in the connection configuration;
- Enhanced initial configuration stage security: a server needs to be restarted if the initial setup time exceeds 1 hour;
- Improved Navigation tree performance: lost connection does not lead to the application freezing anymore;
- Data transfer: Improved export/import functionality performance by optimizing disk memory consumption;
- Vietnamese localization has been added (thanks to [0xhanh](https://github.com/0xhanh)).
- Added a new welcome screen for a freshly opened application. This screen contains shortcuts to create a new connection, open SQL Editor, or documentation;
- Added a search panel for SQL Editor and Value panel: press Ctrl/Cmd + F to open a panel that allows searching and replacing by keyword or regular expression;
- Changed the data transfer mechanism to avoid intermediate file creation. The parameter dataExportFileSizeLimit was removed from the server configuration as deprecated;
- The CloudBeaver default theme matches the device theme by default now. You can change this behavior in user preferences under the Theme section;
- Added the option to display tabs across multiple rows, allowing you to see all tabs without scrolling. You can enable this in Preferences > Interface;
- Improved dark theme accessibility by enhancing color contrast across the app for better readability and compliance with accessibility standards;
- The database navigator now automatically hides empty folders in shared projects, reducing visual clutter and speeding up the process of finding active connections.
- Databases and drivers:
- Clickhouse: driver has been updated to version 0.8.5;
- DuckDB: expanded the list of system objects that can be hidden in the Navigation tree;
- LibSQL: added the ability to connect using token authentication;
- Oracle: users' DDL in Oracle is displayed in the metadata editor on the corresponding tab for users with Oracle administration permissions;
- SQLite: added information about a table's Strict mode to the table metadata section (thanks to [eusebe-cda](https://github.com/eusebe-cda)).
- Clickhouse: fixed the presentation of tuples and map data types in the Data Editor;
- Databend database support has been added (thanks to @hantmac);
- DuckDB: driver has been updated to version 1.3;
- MySQL: Improved performance when retrieving foreign keys metadata;
- PostgreSQL: fixed misplaced comment for table DDL generation.
## Contribution
As a community-driven open-source project, we warmly welcome contributions through GitHub pull requests.
[We are happy to reward](https://dbeaver.com/help-dbeaver/) our most active contributors every major sprint.
The most significant contribution to our code for the major release 25.1.0 was made by:
1. [0xhanh](https://github.com/0xhanh) - for Vietnamese localization.
The most significant contribution to our code for the major release 25.2.0 was made by:
1. [hantmac](https://github.com/hantmac) - added support for Databend in CloudBeaver Community Edition.
-9
View File
@@ -1,9 +0,0 @@
{
"search.exclude": {
"**/.yarn": true,
"**/.pnp.*": true
},
"typescript.tsdk": ".yarn/sdks/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"prettier.prettierPath": ".yarn/sdks/prettier/index.cjs"
}
-32
View File
@@ -1,32 +0,0 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require prettier/bin/prettier.cjs
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real prettier/bin/prettier.cjs your application uses
module.exports = wrapWithUserWrapper(absRequire(`prettier/bin/prettier.cjs`));
-32
View File
@@ -1,32 +0,0 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require prettier
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real prettier your application uses
module.exports = wrapWithUserWrapper(absRequire(`prettier`));
-7
View File
@@ -1,7 +0,0 @@
{
"name": "prettier",
"version": "3.6.2-sdk",
"main": "./index.cjs",
"type": "commonjs",
"bin": "./bin/prettier.cjs"
}
@@ -1 +0,0 @@
!lib
@@ -1,537 +0,0 @@
import * as react2 from "react";
import * as react13 from "react";
import * as react10 from "react";
import { Key, ReactElement, ReactNode } from "react";
import * as react_jsx_runtime1 from "react/jsx-runtime";
import * as react_jsx_runtime0 from "react/jsx-runtime";
import * as react_jsx_runtime9 from "react/jsx-runtime";
import * as react_jsx_runtime11 from "react/jsx-runtime";
import * as react_jsx_runtime4 from "react/jsx-runtime";
import * as react_jsx_runtime5 from "react/jsx-runtime";
import * as react_jsx_runtime7 from "react/jsx-runtime";
//#region src/types.d.ts
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
type Maybe<T> = T | undefined | null;
interface Column<TRow, TSummaryRow = unknown> {
/** The name of the column. Displayed in the header cell by default */
readonly name: string | ReactElement;
/** A unique key to distinguish each column */
readonly key: string;
/**
* Column width. If not specified, it will be determined automatically based on grid width and specified widths of other columns
* @default 'auto'
*/
readonly width?: Maybe<number | string>;
/**
* Minimum column width in pixels
* @default 50
*/
readonly minWidth?: Maybe<number>;
/** Maximum column width in pixels */
readonly maxWidth?: Maybe<number>;
/** Class name(s) for the cell */
readonly cellClass?: Maybe<string | ((row: TRow) => Maybe<string>)>;
/** Class name(s) for the header cell */
readonly headerCellClass?: Maybe<string>;
/** Class name(s) for the summary cell */
readonly summaryCellClass?: Maybe<string | ((row: TSummaryRow) => Maybe<string>)>;
/** Render function to render the content of cells */
readonly renderCell?: Maybe<(props: RenderCellProps<TRow, TSummaryRow>) => ReactNode>;
/** Render function to render the content of the header cell */
readonly renderHeaderCell?: Maybe<(props: RenderHeaderCellProps<TRow, TSummaryRow>) => ReactNode>;
/** Render function to render the content of summary cells */
readonly renderSummaryCell?: Maybe<(props: RenderSummaryCellProps<TSummaryRow, TRow>) => ReactNode>;
/** Render function to render the content of group cells */
readonly renderGroupCell?: Maybe<(props: RenderGroupCellProps<TRow, TSummaryRow>) => ReactNode>;
/** Render function to render the content of edit cells. When set, the column is automatically set to be editable */
readonly renderEditCell?: Maybe<(props: RenderEditCellProps<TRow, TSummaryRow>) => ReactNode>;
/** Enables cell editing. If set and no editor property specified, then a textinput will be used as the cell editor */
readonly editable?: Maybe<boolean | ((row: TRow) => boolean)>;
readonly colSpan?: Maybe<(args: ColSpanArgs<TRow, TSummaryRow>) => Maybe<number>>;
/** Determines whether column is frozen */
readonly frozen?: Maybe<boolean>;
/** Enable resizing of the column */
readonly resizable?: Maybe<boolean>;
/** Enable sorting of the column */
readonly sortable?: Maybe<boolean>;
/** Enable dragging of the column */
readonly draggable?: Maybe<boolean>;
/** Sets the column sort order to be descending instead of ascending the first time the column is sorted */
readonly sortDescendingFirst?: Maybe<boolean>;
/** Options for cell editing */
readonly editorOptions?: Maybe<{
/**
* Render the cell content in addition to the edit cell.
* Enable this option when the editor is rendered outside the grid, like a modal for example.
* By default, the cell content is not rendered when the edit cell is open.
* @default false
*/
readonly displayCellContent?: Maybe<boolean>;
/**
* Commit changes when clicking outside the cell
* @default true
*/
readonly commitOnOutsideClick?: Maybe<boolean>;
/**
* Close the editor when the row changes externally
* @default true
*/
readonly closeOnExternalRowChange?: Maybe<boolean>;
}>;
}
interface CalculatedColumn<TRow, TSummaryRow = unknown> extends Column<TRow, TSummaryRow> {
readonly parent: CalculatedColumnParent<TRow, TSummaryRow> | undefined;
readonly idx: number;
readonly level: number;
readonly width: number | string;
readonly minWidth: number;
readonly maxWidth: number | undefined;
readonly resizable: boolean;
readonly sortable: boolean;
readonly draggable: boolean;
readonly frozen: boolean;
readonly renderCell: (props: RenderCellProps<TRow, TSummaryRow>) => ReactNode;
readonly renderHeaderCell: (props: RenderHeaderCellProps<TRow, TSummaryRow>) => ReactNode;
}
interface ColumnGroup<R, SR = unknown> {
/** The name of the column group, it will be displayed in the header cell */
readonly name: string | ReactElement;
readonly headerCellClass?: Maybe<string>;
readonly children: readonly ColumnOrColumnGroup<R, SR>[];
}
interface CalculatedColumnParent<R, SR> {
readonly name: string | ReactElement;
readonly parent: CalculatedColumnParent<R, SR> | undefined;
readonly idx: number;
readonly colSpan: number;
readonly level: number;
readonly headerCellClass?: Maybe<string>;
}
type ColumnOrColumnGroup<R, SR = unknown> = Column<R, SR> | ColumnGroup<R, SR>;
type CalculatedColumnOrColumnGroup<R, SR> = CalculatedColumnParent<R, SR> | CalculatedColumn<R, SR>;
interface Position {
readonly idx: number;
readonly rowIdx: number;
}
interface RenderCellProps<TRow, TSummaryRow = unknown> {
column: CalculatedColumn<TRow, TSummaryRow>;
row: TRow;
rowIdx: number;
isCellEditable: boolean;
tabIndex: number;
onRowChange: (row: TRow) => void;
}
interface RenderSummaryCellProps<TSummaryRow, TRow = unknown> {
column: CalculatedColumn<TRow, TSummaryRow>;
row: TSummaryRow;
tabIndex: number;
}
interface RenderGroupCellProps<TRow, TSummaryRow = unknown> {
groupKey: unknown;
column: CalculatedColumn<TRow, TSummaryRow>;
row: GroupRow<TRow>;
childRows: readonly TRow[];
isExpanded: boolean;
tabIndex: number;
toggleGroup: () => void;
}
interface RenderEditCellProps<TRow, TSummaryRow = unknown> {
column: CalculatedColumn<TRow, TSummaryRow>;
row: TRow;
rowIdx: number;
onRowChange: (row: TRow, commitChanges?: boolean) => void;
onClose: (commitChanges?: boolean, shouldFocusCell?: boolean) => void;
}
interface RenderHeaderCellProps<TRow, TSummaryRow = unknown> {
column: CalculatedColumn<TRow, TSummaryRow>;
sortDirection: SortDirection | undefined;
priority: number | undefined;
tabIndex: number;
}
interface BaseCellRendererProps<TRow, TSummaryRow = unknown> extends Omit<React.ComponentProps<'div'>, 'children'>, Pick<DataGridProps<TRow, TSummaryRow>, 'onCellMouseDown' | 'onCellClick' | 'onCellDoubleClick' | 'onCellContextMenu'> {
rowIdx: number;
selectCell: (position: Position, options?: SelectCellOptions) => void;
}
interface CellRendererProps<TRow, TSummaryRow> extends BaseCellRendererProps<TRow, TSummaryRow> {
column: CalculatedColumn<TRow, TSummaryRow>;
row: TRow;
colSpan: number | undefined;
isDraggedOver: boolean;
isCellSelected: boolean;
onRowChange: (column: CalculatedColumn<TRow, TSummaryRow>, newRow: TRow) => void;
}
type CellEvent<E extends React.SyntheticEvent<HTMLDivElement>> = E & {
preventGridDefault: () => void;
isGridDefaultPrevented: () => boolean;
};
type CellMouseEvent = CellEvent<React.MouseEvent<HTMLDivElement>>;
type CellKeyboardEvent = CellEvent<React.KeyboardEvent<HTMLDivElement>>;
type CellClipboardEvent = React.ClipboardEvent<HTMLDivElement>;
interface CellMouseArgs<TRow, TSummaryRow = unknown> {
column: CalculatedColumn<TRow, TSummaryRow>;
row: TRow;
rowIdx: number;
selectCell: (enableEditor?: boolean) => void;
}
interface SelectCellKeyDownArgs<TRow, TSummaryRow = unknown> {
mode: 'SELECT';
column: CalculatedColumn<TRow, TSummaryRow>;
row: TRow;
rowIdx: number;
selectCell: (position: Position, options?: SelectCellOptions) => void;
}
interface EditCellKeyDownArgs<TRow, TSummaryRow = unknown> {
mode: 'EDIT';
column: CalculatedColumn<TRow, TSummaryRow>;
row: TRow;
rowIdx: number;
navigate: () => void;
onClose: (commitChanges?: boolean, shouldFocusCell?: boolean) => void;
}
type CellKeyDownArgs<TRow, TSummaryRow = unknown> = SelectCellKeyDownArgs<TRow, TSummaryRow> | EditCellKeyDownArgs<TRow, TSummaryRow>;
interface CellSelectArgs<TRow, TSummaryRow = unknown> {
rowIdx: number;
row: TRow | undefined;
column: CalculatedColumn<TRow, TSummaryRow>;
}
type CellMouseEventHandler<R, SR> = Maybe<(args: CellMouseArgs<NoInfer<R>, NoInfer<SR>>, event: CellMouseEvent) => void>;
interface BaseRenderRowProps<TRow, TSummaryRow = unknown> extends BaseCellRendererProps<TRow, TSummaryRow> {
viewportColumns: readonly CalculatedColumn<TRow, TSummaryRow>[];
rowIdx: number;
selectedCellIdx: number | undefined;
isRowSelectionDisabled: boolean;
isRowSelected: boolean;
gridRowStart: number;
}
interface RenderRowProps<TRow, TSummaryRow = unknown> extends BaseRenderRowProps<TRow, TSummaryRow> {
row: TRow;
lastFrozenColumnIndex: number;
draggedOverCellIdx: number | undefined;
selectedCellEditor: ReactElement<RenderEditCellProps<TRow>> | undefined;
onRowChange: (column: CalculatedColumn<TRow, TSummaryRow>, rowIdx: number, newRow: TRow) => void;
rowClass: Maybe<(row: TRow, rowIdx: number) => Maybe<string>>;
}
interface RowsChangeData<R, SR = unknown> {
indexes: number[];
column: CalculatedColumn<R, SR>;
}
interface SelectRowEvent<TRow> {
row: TRow;
checked: boolean;
isShiftClick: boolean;
}
interface SelectHeaderRowEvent {
checked: boolean;
}
interface FillEvent<TRow> {
columnKey: string;
sourceRow: TRow;
targetRow: TRow;
}
interface CellCopyPasteArgs<TRow, TSummaryRow = unknown> {
column: CalculatedColumn<TRow, TSummaryRow>;
row: TRow;
}
type CellCopyArgs<TRow, TSummaryRow = unknown> = CellCopyPasteArgs<TRow, TSummaryRow>;
type CellPasteArgs<TRow, TSummaryRow = unknown> = CellCopyPasteArgs<TRow, TSummaryRow>;
interface GroupRow<TRow> {
readonly childRows: readonly TRow[];
readonly id: string;
readonly parentId: unknown;
readonly groupKey: unknown;
readonly isExpanded: boolean;
readonly level: number;
readonly posInSet: number;
readonly setSize: number;
readonly startRowIndex: number;
}
interface SortColumn {
readonly columnKey: string;
readonly direction: SortDirection;
}
type SortDirection = 'ASC' | 'DESC';
type ColSpanArgs<TRow, TSummaryRow> = {
type: 'HEADER';
} | {
type: 'ROW';
row: TRow;
} | {
type: 'SUMMARY';
row: TSummaryRow;
};
type RowHeightArgs<TRow> = {
type: 'ROW';
row: TRow;
} | {
type: 'GROUP';
row: GroupRow<TRow>;
};
interface RenderSortIconProps {
sortDirection: SortDirection | undefined;
}
interface RenderSortPriorityProps {
priority: number | undefined;
}
interface RenderSortStatusProps extends RenderSortIconProps, RenderSortPriorityProps {}
interface RenderCheckboxProps extends Pick<React.ComponentProps<'input'>, 'aria-label' | 'aria-labelledby' | 'checked' | 'tabIndex' | 'disabled'> {
indeterminate?: boolean | undefined;
onChange: (checked: boolean, shift: boolean) => void;
}
interface Renderers<TRow, TSummaryRow> {
renderCell?: Maybe<(key: Key, props: CellRendererProps<TRow, TSummaryRow>) => ReactNode>;
renderCheckbox?: Maybe<(props: RenderCheckboxProps) => ReactNode>;
renderRow?: Maybe<(key: Key, props: RenderRowProps<TRow, TSummaryRow>) => ReactNode>;
renderSortStatus?: Maybe<(props: RenderSortStatusProps) => ReactNode>;
noRowsFallback?: Maybe<ReactNode>;
}
interface SelectCellOptions {
enableEditor?: Maybe<boolean>;
shouldFocusCell?: Maybe<boolean>;
}
interface ColumnWidth {
readonly type: 'resized' | 'measured';
readonly width: number;
}
type ColumnWidths = ReadonlyMap<string, ColumnWidth>;
type Direction = 'ltr' | 'rtl';
//#endregion
//#region src/ScrollToCell.d.ts
interface PartialPosition {
readonly idx?: number | undefined;
readonly rowIdx?: number | undefined;
}
//#endregion
//#region src/DataGrid.d.ts
type DefaultColumnOptions<R, SR> = Pick<Column<R, SR>, 'renderCell' | 'renderHeaderCell' | 'width' | 'minWidth' | 'maxWidth' | 'resizable' | 'sortable' | 'draggable'>;
interface DataGridHandle {
element: HTMLDivElement | null;
scrollToCell: (position: PartialPosition) => void;
selectCell: (position: Position, options?: SelectCellOptions) => void;
}
type SharedDivProps = Pick<React.ComponentProps<'div'>, 'role' | 'aria-label' | 'aria-labelledby' | 'aria-description' | 'aria-describedby' | 'aria-rowcount' | 'className' | 'style'>;
interface DataGridProps<R, SR = unknown, K extends Key = Key> extends SharedDivProps {
ref?: Maybe<React.Ref<DataGridHandle>>;
/**
* Grid and data Props
*/
/** An array of column definitions */
columns: readonly ColumnOrColumnGroup<NoInfer<R>, NoInfer<SR>>[];
/** A function called for each rendered row that should return a plain key/value pair object */
rows: readonly R[];
/** Rows pinned at the top of the grid for summary purposes */
topSummaryRows?: Maybe<readonly SR[]>;
/** Rows pinned at the bottom of the grid for summary purposes */
bottomSummaryRows?: Maybe<readonly SR[]>;
/** Function to return a unique key/identifier for each row */
rowKeyGetter?: Maybe<(row: NoInfer<R>) => K>;
/** Callback triggered when rows are changed */
onRowsChange?: Maybe<(rows: NoInfer<R>[], data: RowsChangeData<NoInfer<R>, NoInfer<SR>>) => void>;
/**
* Dimensions props
*/
/**
* Height of each row in pixels
* @default 35
*/
rowHeight?: Maybe<number | ((row: NoInfer<R>) => number)>;
/**
* Height of the header row in pixels
* @default 35
*/
headerRowHeight?: Maybe<number>;
/**
* Height of each summary row in pixels
* @default 35
*/
summaryRowHeight?: Maybe<number>;
/** A map of column widths */
columnWidths?: Maybe<ColumnWidths>;
/** Callback triggered when column widths change */
onColumnWidthsChange?: Maybe<(columnWidths: ColumnWidths) => void>;
/**
* Feature props
*/
/** A set of selected row keys */
selectedRows?: Maybe<ReadonlySet<K>>;
/** Function to determine if row selection is disabled for a specific row */
isRowSelectionDisabled?: Maybe<(row: NoInfer<R>) => boolean>;
/** Callback triggered when the selection changes */
onSelectedRowsChange?: Maybe<(selectedRows: Set<NoInfer<K>>) => void>;
/** An array of sorted columns */
sortColumns?: Maybe<readonly SortColumn[]>;
/** Callback triggered when sorting changes */
onSortColumnsChange?: Maybe<(sortColumns: SortColumn[]) => void>;
/** Default options applied to all columns */
defaultColumnOptions?: Maybe<DefaultColumnOptions<NoInfer<R>, NoInfer<SR>>>;
onFill?: Maybe<(event: FillEvent<NoInfer<R>>) => NoInfer<R>>;
/**
* Event props
*/
/** Callback triggered when a pointer becomes active in a cell */
onCellMouseDown?: CellMouseEventHandler<R, SR>;
/** Callback triggered when a cell is clicked */
onCellClick?: CellMouseEventHandler<R, SR>;
/** Callback triggered when a cell is double-clicked */
onCellDoubleClick?: CellMouseEventHandler<R, SR>;
/** Callback triggered when a cell is right-clicked */
onCellContextMenu?: CellMouseEventHandler<R, SR>;
/** Callback triggered when a key is pressed in a cell */
onCellKeyDown?: Maybe<(args: CellKeyDownArgs<NoInfer<R>, NoInfer<SR>>, event: CellKeyboardEvent) => void>;
/** Callback triggered when a cell's content is copied */
onCellCopy?: Maybe<(args: CellCopyArgs<NoInfer<R>, NoInfer<SR>>, event: CellClipboardEvent) => void>;
/** Callback triggered when content is pasted into a cell */
onCellPaste?: Maybe<(args: CellPasteArgs<NoInfer<R>, NoInfer<SR>>, event: CellClipboardEvent) => NoInfer<R>>;
/** Function called whenever cell selection is changed */
onSelectedCellChange?: Maybe<(args: CellSelectArgs<NoInfer<R>, NoInfer<SR>>) => void>;
/** Callback triggered when the grid is scrolled */
onScroll?: Maybe<(event: React.UIEvent<HTMLDivElement>) => void>;
/** Callback triggered when column is resized */
onColumnResize?: Maybe<(column: CalculatedColumn<R, SR>, width: number) => void>;
/** Callback triggered when columns are reordered */
onColumnsReorder?: Maybe<(sourceColumnKey: string, targetColumnKey: string) => void>;
/**
* Toggles and modes
*/
/** @default true */
enableVirtualization?: Maybe<boolean>;
/**
* Miscellaneous
*/
/** Custom renderers for cells, rows, and other components */
renderers?: Maybe<Renderers<NoInfer<R>, NoInfer<SR>>>;
/** Function to apply custom class names to rows */
rowClass?: Maybe<(row: NoInfer<R>, rowIdx: number) => Maybe<string>>;
/** Custom class name for the header row */
headerRowClass?: Maybe<string>;
/**
* Text direction of the grid ('ltr' or 'rtl')
* @default 'ltr'
* */
direction?: Maybe<Direction>;
'data-testid'?: Maybe<string>;
'data-cy'?: Maybe<string>;
}
/**
* Main API Component to render a data grid of rows and columns
*
* @example
*
* <DataGrid columns={columns} rows={rows} />
*/
declare function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridProps<R, SR, K>): react_jsx_runtime1.JSX.Element;
//#endregion
//#region src/TreeDataGrid.d.ts
interface TreeDataGridProps<R, SR = unknown, K extends Key = Key> extends Omit<DataGridProps<R, SR, K>, 'columns' | 'role' | 'aria-rowcount' | 'rowHeight' | 'onFill' | 'isRowSelectionDisabled'> {
columns: readonly Column<NoInfer<R>, NoInfer<SR>>[];
rowHeight?: Maybe<number | ((args: RowHeightArgs<NoInfer<R>>) => number)>;
groupBy: readonly string[];
rowGrouper: (rows: readonly NoInfer<R>[], columnKey: string) => Record<string, readonly NoInfer<R>[]>;
expandedGroupIds: ReadonlySet<unknown>;
onExpandedGroupIdsChange: (expandedGroupIds: Set<unknown>) => void;
groupIdGetter?: Maybe<(groupKey: string, parentId?: string) => string>;
}
declare function TreeDataGrid<R, SR = unknown, K extends Key = Key>({
columns: rawColumns,
rows: rawRows,
rowHeight: rawRowHeight,
rowKeyGetter: rawRowKeyGetter,
onCellKeyDown: rawOnCellKeyDown,
onCellCopy: rawOnCellCopy,
onCellPaste: rawOnCellPaste,
onRowsChange,
selectedRows: rawSelectedRows,
onSelectedRowsChange: rawOnSelectedRowsChange,
renderers,
groupBy: rawGroupBy,
rowGrouper,
expandedGroupIds,
onExpandedGroupIdsChange,
groupIdGetter: rawGroupIdGetter,
...props
}: TreeDataGridProps<R, SR, K>): react_jsx_runtime0.JSX.Element;
//#endregion
//#region src/DataGridDefaultRenderersContext.d.ts
declare const DataGridDefaultRenderersContext: react2.Context<Maybe<Renderers<any, any>>>;
//#endregion
//#region src/Row.d.ts
declare const RowComponent: <R, SR>(props: RenderRowProps<R, SR>) => React.JSX.Element;
//#endregion
//#region src/Cell.d.ts
declare const CellComponent: <R, SR>(props: CellRendererProps<R, SR>) => React.JSX.Element;
//#endregion
//#region src/Columns.d.ts
declare const SELECT_COLUMN_KEY = "rdg-select-column";
declare const SelectColumn: Column<any, any>;
//#endregion
//#region src/cellRenderers/renderCheckbox.d.ts
declare function renderCheckbox({
onChange,
indeterminate,
...props
}: RenderCheckboxProps): react_jsx_runtime9.JSX.Element;
//#endregion
//#region src/cellRenderers/renderToggleGroup.d.ts
declare function renderToggleGroup<R, SR>(props: RenderGroupCellProps<R, SR>): react_jsx_runtime11.JSX.Element;
declare function ToggleGroup<R, SR>({
groupKey,
isExpanded,
tabIndex,
toggleGroup
}: RenderGroupCellProps<R, SR>): react_jsx_runtime11.JSX.Element;
//#endregion
//#region src/cellRenderers/renderValue.d.ts
declare function renderValue<R, SR>(props: RenderCellProps<R, SR>): react13.ReactNode;
//#endregion
//#region src/cellRenderers/SelectCellFormatter.d.ts
type SharedInputProps = Pick<RenderCheckboxProps, 'disabled' | 'tabIndex' | 'aria-label' | 'aria-labelledby' | 'indeterminate' | 'onChange'>;
interface SelectCellFormatterProps extends SharedInputProps {
value: boolean;
}
declare function SelectCellFormatter({
value,
tabIndex,
indeterminate,
disabled,
onChange,
'aria-label': ariaLabel,
'aria-labelledby': ariaLabelledBy
}: SelectCellFormatterProps): react10.ReactNode;
//#endregion
//#region src/editors/textEditor.d.ts
declare function textEditor<TRow, TSummaryRow>({
row,
column,
onRowChange,
onClose
}: RenderEditCellProps<TRow, TSummaryRow>): react_jsx_runtime4.JSX.Element;
//#endregion
//#region src/renderHeaderCell.d.ts
declare function renderHeaderCell<R, SR>({
column,
sortDirection,
priority
}: RenderHeaderCellProps<R, SR>): string | react_jsx_runtime5.JSX.Element;
//#endregion
//#region src/sortStatus.d.ts
declare function renderSortIcon({
sortDirection
}: RenderSortIconProps): react_jsx_runtime7.JSX.Element | null;
declare function renderSortPriority({
priority
}: RenderSortPriorityProps): number | undefined;
//#endregion
//#region src/hooks/useRowSelection.d.ts
declare function useRowSelection(): {
isRowSelectionDisabled: boolean;
isRowSelected: boolean;
onRowSelectionChange: (selectRowEvent: SelectRowEvent<any>) => void;
};
declare function useHeaderRowSelection(): {
isIndeterminate: boolean;
isRowSelected: boolean;
onRowSelectionChange: (selectRowEvent: SelectHeaderRowEvent) => void;
};
//#endregion
export { CalculatedColumn, CalculatedColumnOrColumnGroup, CalculatedColumnParent, CellComponent as Cell, CellCopyArgs, CellKeyDownArgs, CellKeyboardEvent, CellMouseArgs, CellMouseEvent, CellPasteArgs, CellRendererProps, CellSelectArgs, ColSpanArgs, Column, ColumnGroup, ColumnOrColumnGroup, ColumnWidth, ColumnWidths, DataGrid, DataGridDefaultRenderersContext, DataGridHandle, DataGridProps, DefaultColumnOptions, FillEvent, RenderCellProps, RenderCheckboxProps, RenderEditCellProps, RenderGroupCellProps, RenderHeaderCellProps, RenderRowProps, RenderSortIconProps, RenderSortPriorityProps, RenderSortStatusProps, RenderSummaryCellProps, Renderers, RowComponent as Row, RowHeightArgs, RowsChangeData, SELECT_COLUMN_KEY, SelectCellFormatter, SelectCellOptions, SelectColumn, SelectHeaderRowEvent, SelectRowEvent, SortColumn, SortDirection, ToggleGroup, TreeDataGrid, TreeDataGridProps, renderCheckbox, renderHeaderCell, renderSortIcon, renderSortPriority, renderToggleGroup, renderValue, textEditor, useHeaderRowSelection, useRowSelection };
//# sourceMappingURL=index.d.ts.map
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -1,453 +0,0 @@
@layer rdg {
@layer Defaults,
FocusSink,
CheckboxInput,
CheckboxIcon,
CheckboxLabel,
Cell,
HeaderCell,
SummaryCell,
EditCell,
Row,
HeaderRow,
SummaryRow,
GroupedRow,
Root;
}
.mlln6zg7-0-0-beta-55 {
@layer rdg.MeasuringCell {
contain: strict;
grid-row: 1;
visibility: hidden;
}
}
.cj343x07-0-0-beta-55 {
@layer rdg.Cell {
/* max-content does not work with size containment
* dynamically switching between different containment styles incurs a heavy relayout penalty
* Chromium bug: at odd zoom levels or subpixel positioning,
* layout/paint/style containment can make cell borders disappear
* https://issues.chromium.org/issues/40840864
*/
position: relative; /* needed for absolute positioning to work */
padding-block: 0;
padding-inline: 8px;
border-inline-end: 1px solid var(--rdg-border-color);
border-block-end: 1px solid var(--rdg-border-color);
grid-row-start: var(--rdg-grid-row-start);
align-content: center;
background-color: inherit;
white-space: nowrap;
overflow: clip;
text-overflow: ellipsis;
outline: none;
&[aria-selected='true'] {
outline: 2px solid var(--rdg-selection-color);
outline-offset: -2px;
}
}
}
.csofj7r7-0-0-beta-55 {
@layer rdg.Cell {
position: sticky;
/* Should have a higher value than 0 to show up above unfrozen cells */
z-index: 1;
/* Add box-shadow on the last frozen cell */
&:nth-last-child(1 of &) {
box-shadow: var(--rdg-cell-frozen-box-shadow);
}
}
}
.ch2wcw87-0-0-beta-55 {
@layer rdg.DragHandle {
--rdg-drag-handle-size: 8px;
z-index: 0;
cursor: move;
inline-size: var(--rdg-drag-handle-size);
block-size: var(--rdg-drag-handle-size);
background-color: var(--rdg-selection-color);
place-self: end;
&:hover {
--rdg-drag-handle-size: 16px;
border: 2px solid var(--rdg-selection-color);
background-color: var(--rdg-background-color);
}
}
}
.c1wvphzh7-0-0-beta-55 {
@layer rdg.DragHandle {
z-index: 1;
position: sticky;
}
}
.c1bn88vv7-0-0-beta-55 {
@layer rdg.CheckboxInput {
display: block;
margin: auto;
inline-size: 20px;
block-size: 20px;
&:focus-visible {
outline: 2px solid var(--rdg-checkbox-focus-color);
outline-offset: -3px;
}
&:enabled {
cursor: pointer;
}
}
}
.g1s9ylgp7-0-0-beta-55 {
@layer rdg.GroupCellContent {
outline: none;
}
}
.cz54e4y7-0-0-beta-55 {
@layer rdg.GroupCellCaret {
margin-inline-start: 4px;
stroke: currentColor;
stroke-width: 1.5px;
fill: transparent;
vertical-align: middle;
> path {
transition: d 0.1s;
}
}
}
.h44jtk67-0-0-beta-55 {
@layer rdg.SortableHeaderCell {
display: flex;
}
}
.hcgkhxz7-0-0-beta-55 {
@layer rdg.SortableHeaderCellName {
flex-grow: 1;
overflow: clip;
text-overflow: ellipsis;
}
}
.c6ra8a37-0-0-beta-55 {
@layer rdg.Cell {
background-color: #ccccff;
}
}
.cis5rrm7-0-0-beta-55 {
@layer rdg.EditCell {
padding: 0;
}
}
.c6l2wv17-0-0-beta-55 {
@layer rdg.HeaderCell {
cursor: pointer;
}
}
.c1kqdw7y7-0-0-beta-55 {
@layer rdg.HeaderCell {
touch-action: none;
}
}
.r1y6ywlx7-0-0-beta-55 {
@layer rdg.HeaderCell {
cursor: col-resize;
position: absolute;
inset-block-start: 0;
inset-inline-end: 0;
inset-block-end: 0;
inline-size: 10px;
}
}
.c1bezg5o7-0-0-beta-55 {
@layer rdg.HeaderCell {
background-color: var(--rdg-header-draggable-background-color);
}
}
.c1vc96037-0-0-beta-55 {
@layer rdg.HeaderCell {
background-color: var(--rdg-header-draggable-background-color);
}
}
.d8rwc9w7-0-0-beta-55 {
@layer rdg.HeaderCell {
border-radius: 4px;
width: fit-content;
outline: 2px solid hsl(207, 100%, 50%);
outline-offset: -2px;
}
}
.r1upfr807-0-0-beta-55 {
@layer rdg.Row {
display: contents;
background-color: var(--rdg-background-color);
&:hover {
background-color: var(--rdg-row-hover-background-color);
}
&[aria-selected='true'] {
background-color: var(--rdg-row-selected-background-color);
&:hover {
background-color: var(--rdg-row-selected-hover-background-color);
}
}
}
}
.r190mhd37-0-0-beta-55 {
@layer rdg.FocusSink {
outline: 2px solid var(--rdg-selection-color);
outline-offset: -2px;
}
}
.r139qu9m7-0-0-beta-55 {
@layer rdg.FocusSink {
&::before {
content: '';
display: inline-block;
block-size: 100%;
position: sticky;
inset-inline-start: 0;
border-inline-start: 2px solid var(--rdg-selection-color);
}
}
}
.h10tskcx7-0-0-beta-55 {
@layer rdg.HeaderRow {
display: contents;
background-color: var(--rdg-header-background-color);
font-weight: bold;
& > .cj343x07-0-0-beta-55 {
/* Should have a higher value than 1 to show up above regular cells and the focus sink */
z-index: 2;
position: sticky;
}
& > .csofj7r7-0-0-beta-55 {
z-index: 3;
}
}
}
.a3ejtar7-0-0-beta-55 {
@layer rdg.SortIcon {
fill: currentColor;
> path {
transition: d 0.1s;
}
}
}
.rnvodz57-0-0-beta-55 {
@layer rdg.Defaults {
*,
*::before,
*::after {
box-sizing: inherit;
}
}
@layer rdg.Root {
--rdg-color: #000; --rdg-border-color: #ddd; --rdg-summary-border-color: #aaa; --rdg-background-color: hsl(0deg 0% 100%); --rdg-header-background-color: hsl(0deg 0% 97.5%); --rdg-header-draggable-background-color: hsl(0deg 0% 90.5%); --rdg-row-hover-background-color: hsl(0deg 0% 96%); --rdg-row-selected-background-color: hsl(207deg 76% 92%); --rdg-row-selected-hover-background-color: hsl(207deg 76% 88%); --rdg-checkbox-focus-color: hsl(207deg 100% 69%);
--rdg-selection-color: hsl(207, 75%, 66%);
--rdg-font-size: 14px;
--rdg-cell-frozen-box-shadow: 2px 0 5px -2px rgba(136, 136, 136, 0.3);
&:dir(rtl) {
--rdg-cell-frozen-box-shadow: -2px 0 5px -2px rgba(136, 136, 136, 0.3);
}
display: grid;
color-scheme: var(--rdg-color-scheme, light dark);
accent-color: light-dark(hsl(207deg 100% 29%), hsl(207deg 100% 79%));
/* https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context */
/* We set a stacking context so internal elements don't render on top of external elements. */
/* size containment is not used as it could break "width: min-content" for example, and the grid would infinitely resize on Chromium browsers */
contain: content;
content-visibility: auto;
block-size: 350px;
border: 1px solid var(--rdg-border-color);
box-sizing: border-box;
overflow: auto;
background-color: var(--rdg-background-color);
color: var(--rdg-color);
font-size: var(--rdg-font-size);
/* needed on Firefox to fix scrollbars */
&::before {
content: '';
grid-column: 1/-1;
grid-row: 1/-1;
}
&.rdg-dark {
--rdg-color-scheme: dark;
--rdg-color: #ddd; --rdg-border-color: #444; --rdg-summary-border-color: #555; --rdg-background-color: hsl(0deg 0% 13%); --rdg-header-background-color: hsl(0deg 0% 10.5%); --rdg-header-draggable-background-color: hsl(0deg 0% 17.5%); --rdg-row-hover-background-color: hsl(0deg 0% 9%); --rdg-row-selected-background-color: hsl(207deg 76% 42%); --rdg-row-selected-hover-background-color: hsl(207deg 76% 38%); --rdg-checkbox-focus-color: hsl(207deg 100% 89%);
}
&.rdg-light {
--rdg-color-scheme: light;
}
@media (prefers-color-scheme: dark) {
&:not(.rdg-light) {
--rdg-color: #ddd; --rdg-border-color: #444; --rdg-summary-border-color: #555; --rdg-background-color: hsl(0deg 0% 13%); --rdg-header-background-color: hsl(0deg 0% 10.5%); --rdg-header-draggable-background-color: hsl(0deg 0% 17.5%); --rdg-row-hover-background-color: hsl(0deg 0% 9%); --rdg-row-selected-background-color: hsl(207deg 76% 42%); --rdg-row-selected-hover-background-color: hsl(207deg 76% 38%); --rdg-checkbox-focus-color: hsl(207deg 100% 89%);
}
}
> :nth-last-child(1 of .rdg-top-summary-row) {
> .cj343x07-0-0-beta-55 {
border-block-end: 2px solid var(--rdg-summary-border-color);
}
}
> :nth-child(1 of .rdg-bottom-summary-row) {
> .cj343x07-0-0-beta-55 {
border-block-start: 2px solid var(--rdg-summary-border-color);
}
}
}
}
.vlqv91k7-0-0-beta-55 {
@layer rdg.Root {
user-select: none;
& .r1upfr807-0-0-beta-55 {
cursor: move;
}
}
}
.f1lsfrzw7-0-0-beta-55 {
@layer rdg.FocusSink {
grid-column: 1/-1;
pointer-events: none;
/* Should have a higher value than 1 to show up above regular frozen cells */
z-index: 1;
}
}
.f1cte0lg7-0-0-beta-55 {
@layer rdg.FocusSink {
/* Should have a higher value than 3 to show up above header and summary rows */
z-index: 3;
}
}
.s8wc6fl7-0-0-beta-55 {
@layer rdg.SummaryCell {
inset-block-start: var(--rdg-summary-row-top);
inset-block-end: var(--rdg-summary-row-bottom);
}
}
.skuhp557-0-0-beta-55 {
@layer rdg.SummaryRow {
> .cj343x07-0-0-beta-55 {
position: sticky;
}
}
}
.tf8l5ub7-0-0-beta-55 {
@layer rdg.SummaryRow {
> .cj343x07-0-0-beta-55 {
z-index: 2;
}
> .csofj7r7-0-0-beta-55 {
z-index: 3;
}
}
}
.g1yxluv37-0-0-beta-55 {
@layer rdg.GroupedRow {
&:not([aria-selected='true']) {
background-color: var(--rdg-header-background-color);
}
> .cj343x07-0-0-beta-55:not(:last-child, .csofj7r7-0-0-beta-55),
> :nth-last-child(n + 2 of .csofj7r7-0-0-beta-55) {
border-inline-end: none;
}
}
}
.t7vyx3i7-0-0-beta-55 {
@layer rdg.TextEditor {
appearance: none;
box-sizing: border-box;
inline-size: 100%;
block-size: 100%;
padding-block: 0;
padding-inline: 6px;
border: 2px solid #ccc;
vertical-align: top;
color: var(--rdg-color);
background-color: var(--rdg-background-color);
font-family: inherit;
font-size: var(--rdg-font-size);
&:focus {
border-color: var(--rdg-selection-color);
outline: none;
}
&::placeholder {
color: #999;
opacity: 1;
}
}
}
/*# sourceMappingURL=styles.css.map*/
File diff suppressed because one or more lines are too long
@@ -1,29 +0,0 @@
{
"name": "react-data-grid",
"version": "7.0.0-beta.55",
"type": "module",
"exports": {
"./lib/styles.css": "./lib/styles.css",
".": {
"types": "./lib/index.d.ts",
"default": "./lib/index.js"
}
},
"browser": "./lib/index.js",
"main": "./lib/index.js",
"module": "./lib/index.js",
"types": "./lib/index.d.ts",
"files": [
"lib"
],
"sideEffects": [
"**/*.css"
],
"dependencies": {
"clsx": "^2.0.0"
},
"peerDependencies": {
"react": "^19.0",
"react-dom": "^19.0"
}
}
@@ -1,29 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { type ComponentPropsWithRef } from 'react';
import './Input.css';
import clsx from 'clsx';
import { componentProviderWrapper } from '../componentProviderWrapper.js';
export interface InputProps extends Omit<ComponentPropsWithRef<'input'>, 'size'> {
size?: 'small' | 'medium' | 'large' | 'xlarge';
label?: React.ReactNode;
}
export const InputBase: React.FC<InputProps> = function Input({ size, className, ...props }) {
const classNameToApply = clsx(`dbv-kit-input`, `dbv-kit-input--${size ?? 'medium'}`, className);
return (
<label className="dbv-kit-input-wrapper">
{props.label && <div className={clsx('dbv-kit-input__title', props.required && 'dbv-kit-input__title--required')}>{props.label}</div>}
<input className={classNameToApply} {...props} />
</label>
);
};
export const Input = componentProviderWrapper('Input', InputBase);
File diff suppressed because it is too large Load Diff
-9
View File
@@ -1,9 +0,0 @@
{
"search.exclude": {
"**/.yarn": true,
"**/.pnp.*": true
},
"prettier.prettierPath": ".yarn/sdks/prettier/index.cjs",
"typescript.tsdk": ".yarn/sdks/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}
-5
View File
@@ -1,5 +0,0 @@
# This file is automatically generated by @yarnpkg/sdks.
# Manual changes might be lost!
integrations:
- vscode
-32
View File
@@ -1,32 +0,0 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require prettier/bin/prettier.cjs
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real prettier/bin/prettier.cjs your application uses
module.exports = wrapWithUserWrapper(absRequire(`prettier/bin/prettier.cjs`));
-32
View File
@@ -1,32 +0,0 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require prettier
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real prettier your application uses
module.exports = wrapWithUserWrapper(absRequire(`prettier`));
-7
View File
@@ -1,7 +0,0 @@
{
"name": "prettier",
"version": "3.6.2-sdk",
"main": "./index.cjs",
"type": "commonjs",
"bin": "./bin/prettier.cjs"
}
@@ -1,3 +0,0 @@
export * from './isDefined.js';
export * from './isNotNullDefined.js';
export * from './mutex.js';
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -1,7 +1,7 @@
{
server: {
serverPort: "${CLOUDBEAVER_WEB_SERVER_PORT:8978}",
secureCookies: "${CLOUDBEAVER_SECURE_COOKIES:true}",
forceHttps: "${CLOUDBEAVER_FORCE_HTTPS:false}",
contentRoot: "web",
driversLocation: "drivers",
@@ -10,6 +10,7 @@
rootURI: "${CLOUDBEAVER_ROOT_URI:/}",
serviceURI: "/api/",
supportedHosts: [],
productSettings: {
# Global properties
@@ -27,7 +28,7 @@
},
expireSessionAfterPeriod: "${CLOUDBEAVER_EXPIRE_SESSION_AFTER_PERIOD:1800000}",
bindSessionToIp: "${CLOUDBEAVER_BIND_SESSION_TO_IP:true}",
bindSessionToIp: "${CLOUDBEAVER_BIND_SESSION_TO_IP:disable}",
develMode: "${CLOUDBEAVER_DEVEL_MODE:false}",
enableSecurityManager: false,
+3 -7
View File
@@ -5,10 +5,8 @@ echo "Build static content"
mkdir -p ./cloudbeaver/web
cd ../../cloudbeaver/common-typescript
cd ../../cloudbeaver/webapp
yarn install --immutable
cd ../webapp
yarn install --immutable
cd ./packages/product-default
yarn run bundle
@@ -17,9 +15,7 @@ if [[ "$?" -ne 0 ]] ; then
echo 'Application build failed'; exit $rc
fi
cd ../../../common-typescript
yarn test
cd ../webapp
cd ../../
yarn test
if [[ "$?" -ne 0 ]] ; then
@@ -32,4 +28,4 @@ echo "Copy static content"
cp -rp ../webapp/packages/product-default/lib/* cloudbeaver/web
echo "Cloudbeaver is ready. Run run-server.sh in cloudbeaver folder to start the server."
echo "Cloudbeaver is ready. Run run-cloudbeaver-server.sh in cloudbeaver folder to start the server."
+1 -1
View File
@@ -72,6 +72,6 @@ echo "Copy static content"
xcopy /E /Q ..\webapp\packages\product-default\lib cloudbeaver\web >NUL
echo "Cloudbeaver is ready. Run run-server.bat in cloudbeaver folder to start the server."
echo "Cloudbeaver is ready. Run run-cloudbeaver-server.bat in cloudbeaver folder to start the server."
pause
+1 -1
View File
@@ -16,6 +16,6 @@ EXPOSE 8978
RUN find /opt/cloudbeaver -type d -exec chmod 775 {} \;
WORKDIR /opt/cloudbeaver/
RUN chmod +x "run-server.sh" "/opt/cloudbeaver/launch-product.sh"
RUN chmod +x "run-cloudbeaver-server.sh" "/opt/cloudbeaver/launch-product.sh"
ENTRYPOINT ["./launch-product.sh"]
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
set -Eeo pipefail
if [[ $# -eq 0 ]] ;
then
set -- "$@" -help
fi
set -- "$@" -cli-mode
source run-cloudbeaver-server.sh "$@"
+3 -3
View File
@@ -12,8 +12,8 @@ if [ "$(id -u)" -eq 0 ]; then
TARGET_GID=${TARGET_GID:-$DBEAVER_GID}
chown -R $DBEAVER_UID:$DBEAVER_GID $PWD/workspace
# Execute run-server.sh as the dbeaver user with the JAVA_HOME and PATH environment variables
exec su "$TARGET_USER" -c "JAVA_HOME=$JAVA_HOME PATH=$PATH ./run-server.sh"
# Execute run-cloudbeaver-server.sh as the dbeaver user with the JAVA_HOME and PATH environment variables
exec su "$TARGET_USER" -c "JAVA_HOME=$JAVA_HOME PATH=$PATH ./run-cloudbeaver-server.sh"
else
exec ./run-server.sh
exec ./run-cloudbeaver-server.sh
fi
@@ -17,7 +17,7 @@ If Not Defined JAVA_OPTS (
java %JAVA_OPTS% ^
-Dfile.encoding=UTF-8 ^
--add-modules=ALL-SYSTEM ^
--add-modules=ALL-DEFAULT ^
--add-opens=java.base/java.io=ALL-UNNAMED ^
--add-opens=java.base/java.lang=ALL-UNNAMED ^
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED ^
@@ -11,7 +11,7 @@ echo "Starting Cloudbeaver Server"
exec java ${JAVA_OPTS} \
-Dfile.encoding=UTF-8 \
--add-modules=ALL-SYSTEM \
--add-modules=ALL-DEFAULT \
--add-opens=java.base/java.io=ALL-UNNAMED \
--add-opens=java.base/java.lang=ALL-UNNAMED \
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED \
@@ -36,6 +36,6 @@ exec java ${JAVA_OPTS} \
-jar ${launcherJar} \
-product io.cloudbeaver.product.ce.product \
-web-config conf/cloudbeaver.conf \
${CLI_OPTS} \
"$@" \
-nl en \
-registryMultiLanguage
-8
View File
@@ -1,8 +0,0 @@
#!/bin/bash
set -Eeo pipefail
CLI_OPTS="$@"
if [[ $# -eq 0 ]] ;
then
CLI_OPTS='-help'
fi
source run-server.sh
+1 -1
View File
@@ -1,6 +1,6 @@
lefthook: |
cd webapp
yarn lefthook
yarn dlx --quiet lefthook
output: false
pre-commit:
commands:
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Model
Bundle-SymbolicName: io.cloudbeaver.model;singleton:=true
Bundle-Version: 1.0.82.qualifier
Bundle-Release-Date: 20250804
Bundle-Version: 1.0.86.qualifier
Bundle-Release-Date: 20251006
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.data.gis;visibility:=reexport,
@@ -10,4 +10,10 @@ meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.deploymentType.n
meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbUrl.name=Security manager database URL
meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbDriverName.name=Security manager database driver name
meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbProductName.name=Security manager database product name
meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbProductVersion.name=Security manager database product version
meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbProductVersion.name=Security manager database product version
meta.io.cloudbeaver.model.WebExpertSettingsProperties.readOnly.name = Read-only connection
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = Auto-commit
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = Keep-alive interval (seconds)
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultSchema.name = Default schema
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultCatalog.name = Default catalog
@@ -0,0 +1,2 @@
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = Garder actif (en secondes)
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.description = Pas de déconnexion automatique
@@ -0,0 +1,5 @@
meta.io.cloudbeaver.model.WebExpertSettingsProperties.readOnly.name = Только для чтения
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = Авто-коммит
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = Интервал Keep-alive (в секундах)
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultSchema.name = Схема по умолчанию
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultCatalog.name = Каталог по умолчанию
@@ -0,0 +1,4 @@
meta.io.cloudbeaver.model.WebExpertSettingsProperties.readOnly.name = Kết nối chỉ đọc
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = Tự động commit
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = Giữ kết nối (tính bằng giây)
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.description = Không tự động ngắt kết nối
@@ -0,0 +1,3 @@
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = 自动提交
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = 保持连接 (秒)
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.description = 无自动断开连接
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.model</artifactId>
<version>1.0.82-SNAPSHOT</version>
<version>1.0.86-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -17,18 +17,14 @@
package io.cloudbeaver;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.auth.SMSessionContext;
import org.jkiss.dbeaver.model.impl.app.BaseProjectImpl;
import org.jkiss.dbeaver.model.rm.RMController;
import org.jkiss.dbeaver.model.rm.RMControllerProvider;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.rm.RMProjectType;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.Pair;
import org.jkiss.dbeaver.model.rm.*;
import java.nio.file.Path;
import java.util.Collection;
public abstract class BaseWebProjectImpl extends BaseProjectImpl implements RMControllerProvider {
@@ -81,6 +77,12 @@ public abstract class BaseWebProjectImpl extends BaseProjectImpl implements RMCo
return project.getId();
}
@NotNull
@Override
public String getDisplayName() {
return getName();
}
@NotNull
@Override
public Path getAbsolutePath() {
@@ -102,52 +104,25 @@ public abstract class BaseWebProjectImpl extends BaseProjectImpl implements RMCo
return false;
}
/**
* Method for Bulk Update of resources properties paths
*
* @param oldToNewPaths collection of OldPath to NewPath pairs
*/
public void moveResourcePropertiesBatch(@NotNull Collection<Pair<String, String>> oldToNewPaths) {
loadMetadata();
synchronized (metadataSync) {
for (var pathsPair : oldToNewPaths) {
final var oldResourcePath = CommonUtils.normalizeResourcePath(pathsPair.getFirst());
final var newResourcePath = CommonUtils.normalizeResourcePath(pathsPair.getSecond());
final var resProps = resourceProperties.remove(oldResourcePath);
if (resProps != null) {
resourceProperties.put(newResourcePath, resProps);
}
}
}
flushMetadata();
}
/**
* Method for Bulk Remove of resources properties
*/
public boolean resetResourcesPropertiesBatch(@NotNull Collection<String> resourcesPaths) {
loadMetadata();
boolean propertiesChanged = false;
synchronized (metadataSync) {
for (var resourcePath : resourcesPaths) {
var removedProperties = resourceProperties.remove(CommonUtils.normalizeResourcePath(resourcePath));
if (removedProperties != null) {
propertiesChanged = true;
}
}
}
if (propertiesChanged) {
flushMetadata();
}
return propertiesChanged;
}
public Path getMetadataFilePath() {
return getMetadataPath().resolve(METADATA_STORAGE_FILE);
}
@Override
public boolean isPrivateProject() {
return RMProjectType.USER.equals(getRMProject().getType());
}
@Override
public void updateProject(@Nullable String newName, @Nullable String description) throws DBException {
RMProject rmProject = getResourceController().updateProject(this.getId(), new RMProjectInfo(newName, description));
updateProjectInfo(rmProject.getName(), rmProject.getDescription());
}
public void updateProjectInfo(@Nullable String newName, @Nullable String description) {
this.project.setName(newName);
this.project.setDescription(description);
RMEventManager.fireEvent(
new RMEvent(
RMEvent.Action.PROJECT_UPDATE,
project
)
);
}
}
@@ -29,8 +29,12 @@ public interface DBWConstants {
String PERMISSION_CONFIGURATION_MANAGER = RMConstants.PERMISSION_CONFIGURATION_MANAGER;
String PERMISSION_PRIVATE_PROJECT_ACCESS = "private-project-access";
String PERMISSION_SECRET_MANAGER = "secret-manager";
String PERMISSION_SQL_RESULT_UPDATE = "sql-result-update";
String PERMISSION_SQL_EXECUTE_QUERY = "sql-execute-query";
String GLOBAL_PERMISSION_SCRIPT_EXECUTE = "permission.sql.script.execution";
String GLOBAL_PERMISSION_DATA_EDITOR_IMPORT = "permission.data-editor.import";
String GLOBAL_PERMISSION_DATA_EDITOR_EDITING = "permission.data-editor.editing";
String PERMISSION_EDIT_STRUCTURE = "edit-meta";
String PERMISSION_EDIT_DATA = "edit-data";
@@ -36,4 +36,6 @@ public interface DBWFeatureSet {
boolean isEnabled();
boolean isEnabledByDefault();
}
@@ -0,0 +1,110 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver;
import io.cloudbeaver.model.WebConnectionConfig;
import io.cloudbeaver.model.app.WebAppConfiguration;
import io.cloudbeaver.utils.ServletAppUtils;
import io.cloudbeaver.utils.WebDataSourceUtils;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.utils.CommonUtils;
public class WebConnectionConfigInputHandler<T extends WebConnectionConfig, C extends DataSourceDescriptor> {
private static final Log log = Log.getLog(WebConnectionConfigInputHandler.class);
protected final T input;
protected final DBPDataSourceRegistry registry;
public WebConnectionConfigInputHandler(@NotNull DBPDataSourceRegistry registry, T configInput) {
this.registry = registry;
this.input = configInput;
}
public C createDataSourceContainer() throws DBWebException {
String driverId = input.getDriverId();
if (CommonUtils.isEmpty(driverId)) {
throw new DBWebException("Driver must be specified");
}
DBPDriver driver = WebDataSourceUtils.getDriverById(driverId);
C newDataSource = createDataSourceContainerFromInput(driver);
if (ServletAppUtils.getServletApplication().getAppConfiguration() instanceof WebAppConfiguration webAppConfiguration) {
newDataSource.setNavigatorSettings(webAppConfiguration.getDefaultNavigatorSettings());
}
WebDataSourceUtils.saveAuthProperties(
newDataSource,
newDataSource.getConnectionConfiguration(),
input.getCredentials(),
input.isSaveCredentials(),
input.isSharedCredentials()
);
return newDataSource;
}
public void updateDataSource(@NotNull C dataSource) throws DBWebException {
dataSource.setId(dataSource.getId());
if (!CommonUtils.isEmpty(input.getName())) {
dataSource.setName(input.getName());
}
if (input.getDescription() != null) {
dataSource.setDescription(input.getDescription());
}
dataSource.setFolder(input.getFolder() != null ? registry.getFolder(input.getFolder()) : null);
if (input.isDefaultAutoCommit() != null) {
dataSource.setDefaultAutoCommit(input.isDefaultAutoCommit());
}
WebDataSourceUtils.setConnectionConfiguration(
dataSource.getDriver(),
dataSource.getConnectionConfiguration(),
input
);
WebDataSourceUtils.saveAuthProperties(
dataSource,
dataSource.getConnectionConfiguration(),
input.getCredentials(),
input.isSaveCredentials(),
input.isSharedCredentials()
);
dataSource.setConnectionReadOnly(input.isReadOnly());
}
@NotNull
protected C createDataSourceContainerFromInput(@NotNull DBPDriver driver) {
DBPConnectionConfiguration dsConfig = new DBPConnectionConfiguration();
WebDataSourceUtils.setConnectionConfiguration(driver, dsConfig, input);
C newDataSource = registry.createDataSource(driver, dsConfig);
newDataSource.setSavePassword(true);
newDataSource.setName(CommonUtils.notNull(input.getName(), "NewConnection"));
newDataSource.setDescription(input.getDescription());
newDataSource.setConnectionReadOnly(input.isReadOnly());
if (input.getFolder() != null) {
newDataSource.setFolder(registry.getFolder(input.getFolder()));
}
return newDataSource;
}
}
@@ -17,6 +17,7 @@
package io.cloudbeaver;
import io.cloudbeaver.model.session.WebSession;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
@@ -34,12 +35,11 @@ import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.secret.DBSSecretController;
import org.jkiss.dbeaver.model.struct.DBSObjectFilter;
import org.jkiss.dbeaver.registry.DataSourceConfigurationManager;
import org.jkiss.dbeaver.registry.DataSourceParseResults;
import org.jkiss.dbeaver.registry.DataSourcePersistentRegistry;
import org.jkiss.dbeaver.registry.DataSourceRegistry;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -48,12 +48,19 @@ import java.util.stream.Collectors;
* We need to filter some data sources in case of inaccessibility (not enough permissions).
*/
public class WebGlobalProjectRegistryProxy implements DBPDataSourceRegistry, DataSourcePersistentRegistry, DBPDataSourceRegistryCache {
@NotNull
private final WebSession webSession;
@NotNull
private final DataSourceFilter dataSourceFilter;
@NotNull
private final DataSourceRegistry<?> dataSourceRegistry;
public WebGlobalProjectRegistryProxy(@NotNull DataSourceRegistry<?> dataSourceRegistry, @NotNull DataSourceFilter filter) {
public WebGlobalProjectRegistryProxy(
@NotNull WebSession webSession,
@NotNull DataSourceRegistry<?> dataSourceRegistry,
@NotNull DataSourceFilter filter
) {
this.webSession = webSession;
this.dataSourceRegistry = dataSourceRegistry;
this.dataSourceFilter = filter;
}
@@ -181,13 +188,30 @@ public class WebGlobalProjectRegistryProxy implements DBPDataSourceRegistry, Dat
@NotNull
@Override
public List<? extends DBPDataSourceFolder> getAllFolders() {
return dataSourceRegistry.getAllFolders();
if (webSession.hasPermission(DBWConstants.PERMISSION_ADMIN)) {
return dataSourceRegistry.getAllFolders();
}
Set<DBPDataSourceFolder> set = new LinkedHashSet<>();
for (DBPDataSourceContainer container : getDataSources()) {
DBPDataSourceFolder folder = container.getFolder();
while (folder != null) {
set.add(folder);
folder = folder.getParent();
}
}
return new ArrayList<>(set);
}
@NotNull
@Override
public List<? extends DBPDataSourceFolder> getRootFolders() {
return dataSourceRegistry.getRootFolders();
if (webSession.hasPermission(DBWConstants.PERMISSION_ADMIN)) {
return dataSourceRegistry.getRootFolders();
}
return getDataSources().stream()
.map(DBPDataSourceContainer::getFolder)
.filter(folder -> folder != null && folder.getParent() == null)
.toList();
}
@NotNull
@@ -354,7 +378,7 @@ public class WebGlobalProjectRegistryProxy implements DBPDataSourceRegistry, Dat
}
@Override
public boolean loadDataSources(
public DataSourceParseResults loadDataSources(
@NotNull List<DBPDataSourceConfigurationStorage> storages,
@NotNull DataSourceConfigurationManager manager,
@Nullable Collection<String> dataSourceIds, boolean refresh,
@@ -50,6 +50,7 @@ public class WebSessionGlobalProjectImpl extends WebSessionProjectImpl {
@Override
protected DBPDataSourceRegistry createDataSourceRegistry() {
return new WebGlobalProjectRegistryProxy(
webSession,
createRegistryWithCredentialsProvider(),
this::isDataSourceAccessible
);
@@ -16,11 +16,13 @@
*/
package io.cloudbeaver;
import io.cloudbeaver.model.WebConnectionConfig;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.utils.WebDataSourceUtils;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
@@ -28,19 +30,26 @@ import org.jkiss.dbeaver.model.app.DBPDataSourceRegistryCache;
import org.jkiss.dbeaver.model.navigator.DBNModel;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.rm.RMUtils;
import org.jkiss.dbeaver.model.security.SMObjectType;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceEvent;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.registry.DataSourceRegistry;
import org.jkiss.dbeaver.runtime.jobs.DisconnectJob;
import org.jkiss.utils.CommonUtils;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class WebSessionProjectImpl extends WebProjectImpl {
private static final Log log = Log.getLog(WebSessionProjectImpl.class);
protected final WebSession webSession;
private final Map<String, WebConnectionInfo> connections = new HashMap<>();
private final Map<String, Object> projectSettings = new ConcurrentHashMap<>();
private boolean registryIsLoaded = false;
public WebSessionProjectImpl(
@@ -156,7 +165,7 @@ public class WebSessionProjectImpl extends WebProjectImpl {
*/
@NotNull
public synchronized WebConnectionInfo addConnection(@NotNull DBPDataSourceContainer dataSourceContainer) {
WebConnectionInfo connection = new WebConnectionInfo(webSession, dataSourceContainer);
WebConnectionInfo connection = createConnectionInfo(dataSourceContainer);
synchronized (connections) {
connections.put(dataSourceContainer.getId(), connection);
}
@@ -194,40 +203,31 @@ public class WebSessionProjectImpl extends WebProjectImpl {
/**
* updates data sources based on event in web session
*
* @param dataSourceIds list of updated connections
* @param eventId id of event
* @param event data source updated event
*/
public synchronized boolean updateProjectDataSources(@NotNull List<String> dataSourceIds, @NotNull String eventId) {
public synchronized boolean updateProjectDataSources(@NotNull WSDataSourceEvent event) {
var sendDataSourceUpdatedEvent = false;
DBPDataSourceRegistry registry = getDataSourceRegistry();
// save old connections
var oldDataSources = dataSourceIds.stream()
.map(registry::getDataSource)
.filter(Objects::nonNull)
.collect(Collectors.toMap(
DBPDataSourceContainer::getId,
registry::createDataSource)
);
if (WSDataSourceEvent.CREATED.equals(eventId) || WSDataSourceEvent.UPDATED.equals(eventId)) {
registry.refreshConfig(dataSourceIds);
if (WSDataSourceEvent.CREATED.equals(event.getId()) || WSDataSourceEvent.UPDATED.equals(event.getId())) {
registry.refreshConfig(event.getDataSourceIds());
}
for (String dsId : dataSourceIds) {
for (String dsId : event.getDataSourceIds()) {
DataSourceDescriptor ds = (DataSourceDescriptor) registry.getDataSource(dsId);
if (ds == null) {
continue;
}
switch (eventId) {
switch (event.getId()) {
case WSDataSourceEvent.CREATED -> {
addConnection(ds);
sendDataSourceUpdatedEvent = true;
}
case WSDataSourceEvent.UPDATED -> {
boolean connectionUpdated = !ds.equalSettings(oldDataSources.get(dsId));
if (connectionUpdated) {
sendDataSourceUpdatedEvent = true;
if (event.getProperty() == WSDataSourceProperty.CONFIGURATION) {
WebDataSourceUtils.disconnectDataSource(webSession, ds);
}
// if settings were changed we need to send event
if (event.getProperty() != WSDataSourceProperty.INTERNAL) {
sendDataSourceUpdatedEvent = true;
}
}
case WSDataSourceEvent.DELETED -> {
WebDataSourceUtils.disconnectDataSource(webSession, ds);
@@ -243,4 +243,124 @@ public class WebSessionProjectImpl extends WebProjectImpl {
}
return sendDataSourceUpdatedEvent;
}
public void refreshProjectSettings() throws DBException {
if (webSession.getUser() == null) {
projectSettings.clear();
return;
}
Map<String, Object> loadedSettings = webSession.getSecurityController().getObjectSettings(
getId(),
SMObjectType.project,
null
);
projectSettings.clear();
projectSettings.putAll(loadedSettings);
}
@NotNull
public WebConnectionInfo createConnectionInfo(@NotNull DBPDataSourceContainer dataSourceDescriptor) {
return new WebConnectionInfo(webSession, dataSourceDescriptor);
}
@NotNull
public WebConnectionInfo createConnection(@NotNull Map<String, Object> configMap) throws DBWebException {
if (CommonUtils.isEmpty(configMap)) {
throw new DBWebException("Connection configuration parameters are missing");
}
DBPDataSourceContainer newDataSource = getDataSourceContainerFromInput(configMap);
return addDataSourceToProject(newDataSource);
}
@NotNull
public WebConnectionInfo addDataSourceToProject(@NotNull DBPDataSourceContainer newDataSource) throws DBWebException {
DBPDataSourceRegistry registry = getDataSourceRegistry();
try {
registry.addDataSource(newDataSource);
registry.checkForErrors();
} catch (DBException e) {
registry.removeDataSource(newDataSource);
throw new DBWebException("Failed to create connection", e);
}
WebConnectionInfo connectionInfo = addConnection(newDataSource);
webSession.addInfoMessage("New connection was created - " + WebDataSourceUtils.getConnectionContainerInfo(
newDataSource));
log.info(String.format(
"New connection was created: [info=%s, user=%s]",
WebDataSourceUtils.getConnectionContainerInfo(newDataSource),
webSession.getUserId()
));
return connectionInfo;
}
@NotNull
public WebConnectionInfo updateConnection(@Nullable Map<String, Object> configMap) throws DBWebException {
WebConnectionConfig config = getConnectionConfigInput(configMap);
WebConnectionInfo connectionInfo = getWebConnectionInfo(config.getConnectionId());
DataSourceDescriptor dataSource = (DataSourceDescriptor) connectionInfo.getDataSourceContainer();
webSession.addInfoMessage("Update connection - " + WebDataSourceUtils.getConnectionContainerInfo(dataSource));
DBPDataSourceRegistry registry = getDataSourceRegistry();
getInputConfigHandler(configMap).updateDataSource(dataSource);
connectionInfo.setCredentialsSavedInSession(null);
try {
registry.updateDataSource(dataSource);
registry.checkForErrors();
} catch (DBException e) {
throw new DBWebException("Failed to update connection", e);
}
return connectionInfo;
}
public boolean deleteConnection(@NotNull String connectionId) throws DBWebException {
WebConnectionInfo connectionInfo = getWebConnectionInfo(connectionId);
webSession.addInfoMessage("Delete connection - " +
WebDataSourceUtils.getConnectionContainerInfo(connectionInfo.getDataSourceContainer()));
closeAndDeleteConnection(connectionInfo);
log.info(String.format(
"Connection deleted: [info=%s, userId=%s]",
WebDataSourceUtils.getConnectionContainerInfo(connectionInfo.getDataSourceContainer()),
webSession.getUserId()
));
return true;
}
@NotNull
private WebConnectionInfo closeAndDeleteConnection(@NotNull WebConnectionInfo connectionInfo) throws DBWebException {
DBPDataSourceContainer dataSourceContainer = connectionInfo.getDataSourceContainer();
boolean disconnected = WebDataSourceUtils.disconnectDataSource(webSession, dataSourceContainer);
DBPDataSourceRegistry registry = getDataSourceRegistry();
registry.removeDataSource(dataSourceContainer);
try {
registry.checkForErrors();
} catch (DBException e) {
try {
registry.addDataSource(dataSourceContainer);
} catch (DBException ex) {
log.error("Error re-adding after delete attempt", e);
}
throw new DBWebException("Failed to delete connection", e);
}
removeConnection(dataSourceContainer);
return connectionInfo;
}
@NotNull
public DataSourceDescriptor getDataSourceContainerFromInput(@NotNull Map<String, Object> configMap) throws DBWebException {
return getInputConfigHandler(configMap).createDataSourceContainer();
}
@NotNull
public WebConnectionConfig getConnectionConfigInput(@Nullable Map<String, Object> configMap) {
return new WebConnectionConfig(configMap == null ? Map.of() : configMap);
}
@NotNull
protected WebConnectionConfigInputHandler getInputConfigHandler(@NotNull Map<String, Object> configMap) {
return new WebConnectionConfigInputHandler<>(getDataSourceRegistry(), getConnectionConfigInput(configMap));
}
}
@@ -16,11 +16,11 @@
*/
package io.cloudbeaver.model;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.connection.DBPDriverConfigurationType;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.utils.CommonUtils;
import java.util.ArrayList;
import java.util.List;
@@ -56,6 +56,7 @@ public class WebConnectionConfig {
private boolean saveCredentials;
private boolean sharedCredentials;
private Map<String, Object> mainPropertyValues;
private Map<String, Object> expertSettingsValues;
private Map<String, Object> providerProperties;
private List<WebNetworkHandlerConfigInput> networkHandlersConfig;
private DBPDriverConfigurationType configurationType;
@@ -67,47 +68,52 @@ public class WebConnectionConfig {
public WebConnectionConfig() {
}
public WebConnectionConfig(Map<String, Object> params) {
if (!CommonUtils.isEmpty(params)) {
connectionId = JSONUtils.getString(params, "connectionId");
driverId = JSONUtils.getString(params, "driverId");
readOnly = JSONUtils.getBoolean(params, "readOnly");
public WebConnectionConfig(@NotNull Map<String, Object> params) {
connectionId = JSONUtils.getString(params, "connectionId");
driverId = JSONUtils.getString(params, "driverId");
host = JSONUtils.getString(params, "host");
port = JSONUtils.getString(params, "port");
serverName = JSONUtils.getString(params, "serverName");
databaseName = JSONUtils.getString(params, "databaseName");
url = JSONUtils.getString(params, "url");
host = JSONUtils.getString(params, "host");
port = JSONUtils.getString(params, "port");
serverName = JSONUtils.getString(params, "serverName");
databaseName = JSONUtils.getString(params, "databaseName");
url = JSONUtils.getString(params, "url");
keepAliveInterval = JSONUtils.getInteger(params, "keepAliveInterval", -1);
defaultAutoCommit = JSONUtils.getBoolean(params, "autocommit", true);
name = JSONUtils.getString(params, "name");
description = JSONUtils.getString(params, "description");
folder = JSONUtils.getString(params, "folder");
name = JSONUtils.getString(params, "name");
description = JSONUtils.getString(params, "description");
folder = JSONUtils.getString(params, "folder");
properties = JSONUtils.getObjectOrNull(params, "properties");
userName = JSONUtils.getString(params, "userName");
userPassword = JSONUtils.getString(params, "userPassword");
selectedSecretId = JSONUtils.getString(params, "selectedSecretId");
properties = JSONUtils.getObjectOrNull(params, "properties");
userName = JSONUtils.getString(params, "userName");
userPassword = JSONUtils.getString(params, "userPassword");
selectedSecretId = JSONUtils.getString(params, "selectedSecretId");
authModelId = JSONUtils.getString(params, "authModelId");
credentials = JSONUtils.getObjectOrNull(params, "credentials");
saveCredentials = JSONUtils.getBoolean(params, "saveCredentials");
sharedCredentials = JSONUtils.getBoolean(params, "sharedCredentials");
authModelId = JSONUtils.getString(params, "authModelId");
credentials = JSONUtils.getObjectOrNull(params, "credentials");
saveCredentials = JSONUtils.getBoolean(params, "saveCredentials");
sharedCredentials = JSONUtils.getBoolean(params, "sharedCredentials");
mainPropertyValues = JSONUtils.getObjectOrNull(params, "mainPropertyValues");
providerProperties = JSONUtils.getObjectOrNull(params, "providerProperties");
expertSettingsValues = JSONUtils.getObjectOrNull(params, "expertSettingsValues");
keepAliveInterval = JSONUtils.getInteger(
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_KEEP_ALIVE_INTERVAL, -1);
readOnly = JSONUtils.getBoolean(
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_READ_ONLY);
defaultAutoCommit = JSONUtils.getBoolean(
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_AUTO_COMMIT, true);
defaultCatalogName = JSONUtils.getString(
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_DEFAULT_CATALOG);
defaultSchemaName = JSONUtils.getString(
expertSettingsValues != null ? expertSettingsValues : params,
WebExpertSettingsProperties.PROP_DEFAULT_SCHEMA
);
mainPropertyValues = JSONUtils.getObjectOrNull(params, "mainPropertyValues");
providerProperties = JSONUtils.getObjectOrNull(params, "providerProperties");
defaultCatalogName = JSONUtils.getString(params, "defaultCatalogName");
defaultSchemaName = JSONUtils.getString(params, "defaultSchemaName");
String configType = JSONUtils.getString(params, "configurationType");
configurationType = configType == null ? null : DBPDriverConfigurationType.valueOf(configType);
String configType = JSONUtils.getString(params, "configurationType");
configurationType = configType == null ? null : DBPDriverConfigurationType.valueOf(configType);
networkHandlersConfig = new ArrayList<>();
for (Map<String, Object> nhc : JSONUtils.getObjectList(params, "networkHandlersConfig")) {
networkHandlersConfig.add(new WebNetworkHandlerConfigInput(nhc));
}
networkHandlersConfig = new ArrayList<>();
for (Map<String, Object> nhc : JSONUtils.getObjectList(params, "networkHandlersConfig")) {
networkHandlersConfig.add(new WebNetworkHandlerConfigInput(nhc));
}
}
@@ -219,6 +225,11 @@ public class WebConnectionConfig {
return mainPropertyValues;
}
@Property
public Map<String, Object> getExpertSettingsValues() {
return expertSettingsValues;
}
@Property
public Map<String, Object> getProviderProperties() {
return providerProperties;
@@ -16,6 +16,7 @@
*/
package io.cloudbeaver.model;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebProjectImpl;
import io.cloudbeaver.model.app.BaseWebAppConfiguration;
import io.cloudbeaver.model.session.WebSession;
@@ -319,6 +320,10 @@ public class WebConnectionInfo {
!dataSourceContainer.getDriver().isAnonymousAccess();
}
public void validateConnection() throws DBWebException {
}
// we don't show non-secured properties in FE when connecting to DB without saved credentials
private boolean isAuthPropertiesEmpty() {
return Arrays.stream(getAuthProperties()).allMatch(f -> f.hasFeature(DBConstants.PROP_FEATURE_NON_SECURED));
@@ -417,6 +422,17 @@ public class WebConnectionInfo {
return mainProperties;
}
@Property
public Map<String, Object> getExpertSettingsValues() {
Map<String, Object> expertSettings = new LinkedHashMap<>();
expertSettings.put(WebExpertSettingsProperties.PROP_AUTO_COMMIT, isAutocommit());
expertSettings.put(WebExpertSettingsProperties.PROP_KEEP_ALIVE_INTERVAL, getKeepAliveInterval());
expertSettings.put(WebExpertSettingsProperties.PROP_READ_ONLY, isReadOnly());
expertSettings.put(WebExpertSettingsProperties.PROP_DEFAULT_CATALOG, getDefaultCatalogName());
expertSettings.put(WebExpertSettingsProperties.PROP_DEFAULT_SCHEMA, getDefaultSchemaName());
return expertSettings;
}
@Property
public Map<String, String> getProviderProperties() {
return dataSourceContainer.getConnectionConfiguration().getProviderProperties();
@@ -0,0 +1,87 @@
package io.cloudbeaver.model;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.DBPObject;
import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.model.connection.DBPDriverConstants;
import org.jkiss.dbeaver.model.meta.IPropertyValueValidator;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.utils.CommonUtils;
/**
* Web expert settings properties. Class for returning filtered expert settings properties in web interface.
*/
public class WebExpertSettingsProperties implements DBPObject {
public static final String PROP_READ_ONLY = "readOnly";
public static final String PROP_AUTO_COMMIT = "autocommit";
public static final String PROP_KEEP_ALIVE_INTERVAL = "keepAliveInterval";
public static final String PROP_DEFAULT_CATALOG = "defaultCatalogName";
public static final String PROP_DEFAULT_SCHEMA = "defaultSchemaName";
private final DBPDriver driver;
public WebExpertSettingsProperties(@NotNull DBPDriver driver) {
this.driver = driver;
}
@Property(order = 1, id = PROP_KEEP_ALIVE_INTERVAL, visibleIf = KeepAliveIntervalFieldValidator.class)
public int getKeepAliveInterval() {
return 0;
}
@Property(order = 2, id = PROP_AUTO_COMMIT, visibleIf = AutoCommitFieldValidator.class)
public boolean isAutoCommit() {
return true;
}
@Property(order = 3, id = PROP_READ_ONLY, visibleIf = ReadOnlyFieldValidator.class)
public boolean isReadOnly() {
return false;
}
@Property(order = 4, id = PROP_DEFAULT_CATALOG, visibleIf = DefaultCatalogFieldVisibleValidator.class)
public String getDefaultCatalog() {
return null;
}
@Property(order = 5, id = PROP_DEFAULT_SCHEMA, visibleIf = DefaultSchemaFieldVisibleValidator.class)
public String getDefaultSchema() {
return null;
}
public static class KeepAliveIntervalFieldValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
@Override
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
return CommonUtils.toBoolean(!object.driver.isEmbedded(), true);
}
}
public static class AutoCommitFieldValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
@Override
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_TRANSACTIONS), true);
}
}
public static class ReadOnlyFieldValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
@Override
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_READ_ONLY_MODE), true);
}
}
public static class DefaultCatalogFieldVisibleValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
@Override
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_CATALOG_SELECTION), true);
}
}
public static class DefaultSchemaFieldVisibleValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
@Override
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_SCHEMA_SELECTION), true);
}
}
}
@@ -37,6 +37,7 @@ public interface ApiCallInterceptor {
@NotNull HttpServletRequest request,
@Nullable Map<String, Object> variables,
@NotNull String apiCall,
@Nullable String userId,
@NotNull LocalDateTime startTime,
@Nullable String errorMessage,
@NotNull String apiProtocol
@@ -198,6 +198,7 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
}
}
@NotNull
@Override
public Object start(IApplicationContext context) {
initializeApplicationServices();
@@ -210,8 +211,9 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
startServer();
} catch (Exception e) {
log.error(e.getMessage(), e);
return EXIT_ERROR_UNSPECIFIED;
}
return null;
return EXIT_OK;
}
protected abstract void startServer() throws DBException;
@@ -279,4 +281,9 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
public ApplicationInstanceController getInstanceServer() {
return instanceServer;
}
@Override
public boolean isAnonymousAccessEnabled() {
return getAppConfiguration().isAnonymousAccessEnabled();
}
}
@@ -36,6 +36,7 @@ public abstract class BaseWebAppConfiguration implements ServletAppConfiguration
protected boolean secretManagerEnabled;
protected boolean showReadOnlyConnectionInfo;
protected String[] enabledFeatures;
protected String[] disabledFeatures;
protected String[] disabledBetaFeatures;
@@ -43,6 +44,7 @@ public abstract class BaseWebAppConfiguration implements ServletAppConfiguration
this.plugins = new LinkedHashMap<>();
this.resourceManagerEnabled = true;
this.enabledFeatures = null;
this.disabledFeatures = new String[0];
this.disabledBetaFeatures = new String[0];
this.showReadOnlyConnectionInfo = false;
this.secretManagerEnabled = false;
@@ -53,6 +55,7 @@ public abstract class BaseWebAppConfiguration implements ServletAppConfiguration
this.defaultUserTeam = src.defaultUserTeam;
this.resourceManagerEnabled = src.resourceManagerEnabled;
this.enabledFeatures = src.enabledFeatures;
this.disabledFeatures = src.disabledFeatures;
this.disabledBetaFeatures = src.disabledBetaFeatures;
this.showReadOnlyConnectionInfo = src.showReadOnlyConnectionInfo;
this.secretManagerEnabled = src.secretManagerEnabled;
@@ -122,6 +125,16 @@ public abstract class BaseWebAppConfiguration implements ServletAppConfiguration
this.enabledFeatures = enabledFeatures;
}
@NotNull
@Override
public String[] getDisabledFeatures() {
return disabledFeatures;
}
public void setDisabledFeatures(@NotNull String[] disabledFeatures) {
this.disabledFeatures = disabledFeatures;
}
public boolean isShowReadOnlyConnectionInfo() {
return showReadOnlyConnectionInfo;
}
@@ -51,6 +51,16 @@ public interface ServletAppConfiguration {
return new String[0];
}
/**
* Returns disabled features.
*
* @return array of disabled feature IDs
*/
@NotNull
default String[] getDisabledFeatures() {
return new String[0];
}
default boolean isSupportsCustomConnections() {
return true;
}
@@ -100,4 +100,6 @@ public interface ServletApplication extends DBPApplication {
default void getStatusInfo(Map<String, Object> infoMap) {
}
boolean isAnonymousAccessEnabled();
}
@@ -20,7 +20,7 @@ import org.apache.commons.cli.CommandLine;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.cli.ApplicationInstanceController;
import org.jkiss.dbeaver.model.cli.ApplicationInstanceServer;
import org.jkiss.dbeaver.model.cli.CmdProcessResult;
import org.jkiss.dbeaver.model.cli.CLIProcessResult;
import java.io.IOException;
@@ -31,16 +31,17 @@ public class CloudBeaverInstanceServer extends ApplicationInstanceServer<Applica
@NotNull
@Override
public CmdProcessResult handleCommandLine(@NotNull String[] args) {
public CLIProcessResult handleCommandLine(@NotNull String[] args) {
CommandLine cmd = CloudBeaverCommandLine.getInstance().getCommandLine(args);
try {
return CloudBeaverCommandLine.getInstance().executeCommandLineCommands(
cmd,
this,
false,
false
);
} catch (Exception e) {
return new CmdProcessResult(CmdProcessResult.PostAction.ERROR, "Error executing command: " + e.getMessage());
return new CLIProcessResult(CLIProcessResult.PostAction.ERROR, "Error executing command: " + e.getMessage());
}
}
}
@@ -0,0 +1,21 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.model.cli;
public interface CloudbeaverCliConstants {
String CLI_MODE = "cliMode";
}
@@ -21,10 +21,12 @@ package io.cloudbeaver.model.config;
*/
public class ConnectionBruteForceConfiguration {
private static final int DEFAULT_ERROR_ATTEMPTS_CHECK_MINUTES = 24 * 60;
private static final int BLOCK_TIME_IN_MINUTES = 60;
private static final int DEFAULT_MAX_CONNECT_ATTEMPTS = 5;
private boolean enabled = false;
private int maxFailedConnectAttempts = DEFAULT_MAX_CONNECT_ATTEMPTS;
private int errorAttemptsPeriodInMinutes = DEFAULT_ERROR_ATTEMPTS_CHECK_MINUTES;
private int blockTimeInMinutes = BLOCK_TIME_IN_MINUTES;
public boolean isEnabled() {
return enabled;
@@ -49,4 +51,12 @@ public class ConnectionBruteForceConfiguration {
public void setErrorAttemptsPeriodInMinutes(int errorAttemptsPeriodInMinutes) {
this.errorAttemptsPeriodInMinutes = errorAttemptsPeriodInMinutes;
}
public int getBlockTimeInMinutes() {
return blockTimeInMinutes;
}
public void setBlockTimeInMinutes(int blockTimeInMinutes) {
this.blockTimeInMinutes = blockTimeInMinutes;
}
}
@@ -19,7 +19,6 @@ package io.cloudbeaver.model.rm;
import io.cloudbeaver.model.session.WebSession;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPHiddenObject;
@@ -141,6 +140,9 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
case PROJECT_ADD:
addProjectNode(event.getProject());
break;
case PROJECT_UPDATE:
updateProjectNode(event.getProject());
break;
}
}
@@ -187,4 +189,14 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject,
private void addProjectNode(RMProject project) {
projects = ArrayUtils.add(DBNResourceManagerProject.class, projects, new DBNResourceManagerProject(this, project));
}
private void updateProjectNode(@NotNull RMProject project) {
var projectNode = getProjectNode(project);
projectNode.ifPresent(
dbnResourceManagerProject -> {
dbnResourceManagerProject.getProject().setName(project.getName());
dbnResourceManagerProject.getProject().setDescription(project.getDescription());
}
);
}
}
@@ -99,7 +99,7 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String propName,
@NotNull Object propValue
) throws DBException {
BaseWebProjectImpl webProject = getWebProject(projectId, false);
RMLocalProject webProject = getWebProject(projectId, false);
doFileWriteOperation(projectId, webProject.getMetadataFilePath(),
() -> {
log.debug("Updating value for property '" + propName + "' in project '" + projectId + "'");
@@ -149,27 +149,37 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String projectId,
@NotNull String configuration,
@Nullable List<String> dataSourceIds
) throws DBException {
return updateProjectDataSourcesConfig(projectId, configuration, dataSourceIds) != null;
}
@Nullable
protected DataSourceParseResults updateProjectDataSourcesConfig(
@NotNull String projectId,
@NotNull String configuration,
@Nullable List<String> dataSourceIds
) throws DBException {
try (var lock = lockController.lock(projectId, "updateProjectDataSources")) {
DBPProject project = getWebProject(projectId, false);
return doFileWriteOperation(projectId, project.getMetadataFolder(false),
return doFileWriteOperation(
projectId, project.getMetadataFolder(false),
() -> {
DBPDataSourceRegistry registry = project.getDataSourceRegistry();
DBPDataSourceConfigurationStorage storage = new DataSourceMemoryStorage(configuration.getBytes(
StandardCharsets.UTF_8));
DataSourceConfigurationManager manager = new DataSourceConfigurationManagerBuffer();
var configChanged = ((DataSourcePersistentRegistry) registry).loadDataSources(
final DataSourceParseResults parseResults = ((DataSourcePersistentRegistry) registry).loadDataSources(
List.of(storage),
manager,
dataSourceIds,
true,
false
dataSourceIds == null
);
registry.checkForErrors();
log.debug("Save data sources configuration in project '" + projectId + "'");
((DataSourcePersistentRegistry) registry).saveDataSources();
registry.checkForErrors();
return configChanged;
return parseResults;
}
);
}
@@ -217,6 +227,7 @@ public abstract class BaseLocalResourceController implements RMController {
var parent = result.getParent();
var parentFolder = parent == null ? null : registry.getFolder(parent.toString().replace("\\", "/"));
DBPDataSourceFolder newFolder = registry.addFolder(parentFolder, newName);
((DataSourcePersistentRegistry) registry).saveDataSources();
registry.checkForErrors();
return null;
}
@@ -244,6 +255,7 @@ public abstract class BaseLocalResourceController implements RMController {
log.warn("Can not find folder by path [" + folderPath + "] for deletion");
}
}
((DataSourcePersistentRegistry) registry).saveDataSources();
registry.checkForErrors();
return null;
}
@@ -265,13 +277,14 @@ public abstract class BaseLocalResourceController implements RMController {
DBPDataSourceRegistry registry = project.getDataSourceRegistry();
registry.moveFolder(oldPath, newPath);
registry.checkForErrors();
((DataSourcePersistentRegistry) registry).saveDataSources();
return null;
}
);
}
}
protected abstract BaseWebProjectImpl getWebProject(String projectId, boolean refresh) throws DBException;
protected abstract RMLocalProject getWebProject(String projectId, boolean refresh) throws DBException;
protected abstract <T> T doFileWriteOperation(String projectId, Path file, RMFileOperation<T> operation)
throws DBException;
@@ -16,30 +16,38 @@
*/
package io.cloudbeaver.model.rm.local;
import io.cloudbeaver.BaseWebProjectImpl;
import io.cloudbeaver.DBWConstants;
import io.cloudbeaver.model.app.ServletApplication;
import io.cloudbeaver.service.security.SMUtils;
import io.cloudbeaver.service.sql.WebSQLConstants;
import io.cloudbeaver.utils.ServletAppUtils;
import io.cloudbeaver.utils.WebRMUtils;
import io.cloudbeaver.utils.file.UniversalFileVisitor;
import org.eclipse.core.runtime.IPath;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.auth.SMCredentials;
import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
import org.jkiss.dbeaver.model.fs.lock.FileLockController;
import org.jkiss.dbeaver.model.impl.app.BaseProjectImpl;
import org.jkiss.dbeaver.model.impl.auth.SessionContextImpl;
import org.jkiss.dbeaver.model.navigator.DBNLocalFolder;
import org.jkiss.dbeaver.model.rm.*;
import org.jkiss.dbeaver.model.security.SMController;
import org.jkiss.dbeaver.model.security.SMObjectType;
import org.jkiss.dbeaver.model.sql.DBQuotaException;
import org.jkiss.dbeaver.model.websocket.event.MessageType;
import org.jkiss.dbeaver.model.websocket.event.WSSessionLogUpdatedEvent;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceEvent;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDatasourceFolderEvent;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.registry.DataSourceParseResults;
import org.jkiss.dbeaver.registry.ResourceTypeDescriptor;
import org.jkiss.dbeaver.registry.ResourceTypeRegistry;
import org.jkiss.dbeaver.runtime.DBWorkbench;
@@ -73,7 +81,7 @@ public class LocalResourceController extends BaseLocalResourceController {
private Supplier<SMController> smControllerSupplier;
protected final List<RMFileOperationHandler> fileHandlers;
private final Map<String, BaseWebProjectImpl> projectRegistries = new LinkedHashMap<>();
private final Map<String, RMLocalProject> projectRegistries = new LinkedHashMap<>();
public LocalResourceController(
DBPWorkspace workspace,
@@ -108,13 +116,11 @@ public class LocalResourceController extends BaseLocalResourceController {
return userId == null ? null : this.userProjectsPath.resolve(userId);
}
protected BaseWebProjectImpl getWebProject(String projectId, boolean refresh) throws DBException {
protected RMLocalProject getWebProject(String projectId, boolean refresh) throws DBException {
synchronized (projectRegistries) {
BaseWebProjectImpl project = projectRegistries.get(projectId);
RMLocalProject project = projectRegistries.get(projectId);
if (project == null || refresh) {
SessionContextImpl sessionContext = new SessionContextImpl(null);
RMProject rmProject = makeProjectFromId(projectId, false);
project = createWebProjectImpl(projectId, sessionContext, rmProject);
project = createWebProjectImpl(projectId, new SessionContextImpl(null));
projectRegistries.put(projectId, project);
}
return project;
@@ -122,12 +128,11 @@ public class LocalResourceController extends BaseLocalResourceController {
}
@NotNull
protected InternalWebProjectImpl createWebProjectImpl(
String projectId,
SessionContextImpl sessionContext,
RMProject rmProject
protected RMLocalProject createWebProjectImpl(
@NotNull String projectId,
@NotNull SessionContextImpl sessionContext
) throws DBException {
return new InternalWebProjectImpl(sessionContext, rmProject, getProjectPath(projectId));
return new RMLocalProject(workspace, sessionContext, getProjectPath(projectId), WebRMUtils.parseProjectName(projectId).getType());
}
@NotNull
@@ -182,7 +187,7 @@ public class LocalResourceController extends BaseLocalResourceController {
.stream()
.filter(smObjectPermissions -> CommonUtils.isNotEmpty(smObjectPermissions.getObjectId()))
.map(projectPermission -> makeProjectFromPath(
sharedProjectsPath.resolve(parseProjectNameUnsafe(projectPermission.getObjectId()).getName()),
sharedProjectsPath.resolve(WebRMUtils.parseProjectNameUnsafe(projectPermission.getObjectId()).getName()),
Arrays.stream(projectPermission.getPermissions()).map(RMProjectPermission::fromPermission).collect(Collectors.toSet()),
RMProjectType.SHARED, true)
)
@@ -248,7 +253,7 @@ public class LocalResourceController extends BaseLocalResourceController {
var allPaths = list.toList();
for (Path path : allPaths) {
var projectPerms = getProjectPermissions(
makeProjectIdFromPath(path, RMProjectType.SHARED),
RMUtils.makeProjectIdFromPath(path, RMProjectType.SHARED),
RMProjectType.SHARED
);
var rmProject = makeProjectFromPath(path, projectPerms, RMProjectType.SHARED, false);
@@ -273,28 +278,54 @@ public class LocalResourceController extends BaseLocalResourceController {
}
}
validateResourcePath(name);
RMProject project;
validateProjectName(null, name);
var projectPath = sharedProjectsPath.resolve(name);
if (Files.exists(projectPath)) {
throw new DBException("Project '" + name + "' already exists");
}
project = makeProjectFromPath(projectPath, Set.of(), RMProjectType.SHARED, false);
if (project == null) {
throw new DBException("Project '" + name + "' not created");
}
RMLocalProject webProject = new RMLocalProject(workspace, new SessionContextImpl(null), projectPath, RMProjectType.SHARED);
webProject.updateProject(name, description);
RMProject rmProject = createRmProjectFromWebProject(projectPath, webProject, new String[0]);
try {
log.debug("Creating project '" + project.getId() + "'");
log.debug("Creating project '" + rmProject.getId() + "'");
Files.createDirectories(projectPath);
if (ServletAppUtils.getServletApplication().isMultiNode()) {
createResourceTypeFolders(projectPath);
}
fireRmProjectAddEvent(project);
return project;
fireRmProjectAddEvent(rmProject);
return rmProject;
} catch (IOException e) {
throw new DBException("Error creating project path", e);
}
}
@Override
public RMProject updateProject(@NotNull String projectId, @NotNull RMProjectInfo projectInfo) throws DBException {
validateProjectName(projectId, projectInfo.getName());
try (var projectLock = lockController.lock(projectId, "updateProject")) {
RMLocalProject project = getWebProject(projectId, false);
Path targetPath = getProjectPath(projectId);
if (!Files.exists(targetPath)) {
throw new DBException("Project folder '" + projectId + "' not found");
}
if (!project.canUpdateProjectName()) {
throw new DBException("Project '" + projectId + "' is not shared");
}
project.updateProject(projectInfo.getName(), projectInfo.getDescription());
return WebRMUtils.createRmProjectFromWebProject(project);
}
}
private void validateProjectName(@Nullable String projectId, @Nullable String name) throws DBException {
boolean duplicatedName = Arrays.stream(listAllSharedProjects())
.filter(p -> !p.getId().equals(projectId))
.map(RMProject::getName)
.noneMatch(n -> n.equalsIgnoreCase(name));
if (!duplicatedName) {
throw new DBException("Project name '" + name + "' is already used");
}
}
@Override
public void deleteProject(@NotNull String projectId) throws DBException {
try (var projectLock = lockController.lock(projectId, "deleteProject")) {
@@ -334,6 +365,239 @@ public class LocalResourceController extends BaseLocalResourceController {
return project;
}
@Override
public void createProjectDataSources(
@NotNull String projectId,
@NotNull String configuration,
@Nullable List<String> dataSourceIds
) throws DBException {
super.createProjectDataSources(projectId, configuration, dataSourceIds);
if (credentialsProvider.getActiveUserCredentials() != null && dataSourceIds != null) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDataSourceEvent.create(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
projectId,
dataSourceIds,
WSDataSourceProperty.CONFIGURATION
)
);
}
}
@Override
public boolean updateProjectDataSources(
@NotNull String projectId,
@NotNull String configuration,
@Nullable List<String> dataSourceIds
) throws DBException {
DBPDataSourceRegistry registry = getWebProject(projectId, false).getDataSourceRegistry();
Map<String, DataSourceDescriptor> oldDataSources = registry.getDataSources().stream()
.filter(ds -> dataSourceIds == null || dataSourceIds.contains(ds.getId()))
.collect(Collectors.toMap(
DBPDataSourceContainer::getId,
registry::createDataSource
)
);
DataSourceParseResults parseResults = super.updateProjectDataSourcesConfig(projectId, configuration, dataSourceIds);
sendDataSourcesConfigUpdatedEvent(registry, oldDataSources, parseResults);
return parseResults != null;
}
@Override
public void deleteProjectDataSources(@NotNull String projectId, @NotNull String[] dataSourceIds) throws DBException {
super.deleteProjectDataSources(projectId, dataSourceIds);
if (credentialsProvider.getActiveUserCredentials() != null && dataSourceIds.length > 0) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDataSourceEvent.delete(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
projectId,
Arrays.asList(dataSourceIds),
WSDataSourceProperty.CONFIGURATION
)
);
}
}
@Override
public void createProjectDataSourceFolder(@NotNull String projectId, @NotNull String folderPath) throws DBException {
super.createProjectDataSourceFolder(projectId, folderPath);
if (credentialsProvider.getActiveUserCredentials() != null) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDatasourceFolderEvent.create(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
projectId,
List.of(createNodePathFromFolderPath(projectId, folderPath))
)
);
}
}
@Override
public void moveProjectDataSourceFolder(
@NotNull String projectId,
@NotNull String oldPath,
@NotNull String newPath
) throws DBException {
super.moveProjectDataSourceFolder(projectId, oldPath, newPath);
if (credentialsProvider.getActiveUserCredentials() != null) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDatasourceFolderEvent.delete(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
projectId,
List.of(createNodePathFromFolderPath(projectId, oldPath))
)
);
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDatasourceFolderEvent.create(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
projectId,
List.of(createNodePathFromFolderPath(projectId, newPath))
)
);
}
}
@Override
public void deleteProjectDataSourceFolders(@NotNull String projectId, @NotNull String[] folderPaths, boolean dropContents)
throws DBException {
super.deleteProjectDataSourceFolders(projectId, folderPaths, dropContents);
if (credentialsProvider.getActiveUserCredentials() != null) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDatasourceFolderEvent.create(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
projectId,
Arrays.stream(folderPaths).map(
p -> createNodePathFromFolderPath(projectId, p)
).collect(Collectors.toList())
)
);
}
}
private String createNodePathFromFolderPath(String projectId, String folderPath) {
return DBNLocalFolder.makeLocalFolderItemPath(projectId, folderPath);
}
private void sendDataSourcesConfigUpdatedEvent(
@NotNull DBPDataSourceRegistry registry,
@NotNull Map<String, DataSourceDescriptor> oldDataSources,
@Nullable DataSourceParseResults parseResults
) {
if (parseResults == null || credentialsProvider.getActiveUserCredentials() == null || oldDataSources.isEmpty()) {
return;
}
List<String> updatedConfigurationDataSourceIds = new ArrayList<>();
List<String> updatedNameDataSourceIds = new ArrayList<>();
List<String> updatedInternalConfigurationDataSourceIds = new ArrayList<>();
for (Map.Entry<String, DataSourceDescriptor> entry : oldDataSources.entrySet()) {
String dsId = entry.getKey();
DataSourceDescriptor oldDs = entry.getValue();
DataSourceDescriptor newDs = (DataSourceDescriptor) registry.getDataSource(dsId);
if (newDs == null) {
continue;
}
if (!oldDs.equalConfiguration(newDs)) {
updatedConfigurationDataSourceIds.add(dsId);
} else if (!oldDs.isLooselyEqualTo(newDs)) {
updatedNameDataSourceIds.add(dsId);
} else if (!oldDs.equalInternalConfiguration(newDs)) {
updatedInternalConfigurationDataSourceIds.add(dsId);
}
}
if (!updatedConfigurationDataSourceIds.isEmpty()) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDataSourceEvent.update(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
registry.getProject().getId(),
updatedConfigurationDataSourceIds,
WSDataSourceProperty.CONFIGURATION
)
);
}
if (!updatedNameDataSourceIds.isEmpty()) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDataSourceEvent.update(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
registry.getProject().getId(),
updatedNameDataSourceIds,
WSDataSourceProperty.NAME
)
);
}
if (!updatedInternalConfigurationDataSourceIds.isEmpty()) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDataSourceEvent.update(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
registry.getProject().getId(),
updatedInternalConfigurationDataSourceIds,
WSDataSourceProperty.INTERNAL
)
);
}
if (!parseResults.addedDataSources.isEmpty()) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDataSourceEvent.create(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
registry.getProject().getId(),
updatedNameDataSourceIds,
WSDataSourceProperty.CONFIGURATION
)
);
}
if (!parseResults.removedDataSources.isEmpty()) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDataSourceEvent.delete(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
registry.getProject().getId(),
updatedNameDataSourceIds,
WSDataSourceProperty.CONFIGURATION
)
);
}
if (!parseResults.addedFolders.isEmpty()) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDatasourceFolderEvent.create(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
registry.getProject().getId(),
parseResults.addedFolders.stream().map(
f -> createNodePathFromFolderPath(registry.getProject().getId(), f.getFolderPath())
).toList()
)
);
}
if (!parseResults.removedFolders.isEmpty()) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
WSDatasourceFolderEvent.delete(
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
credentialsProvider.getActiveUserCredentials().getUserId(),
registry.getProject().getId(),
parseResults.removedFolders.stream().map(
f -> createNodePathFromFolderPath(registry.getProject().getId(), f.getFolderPath())
).toList()
)
);
}
}
@NotNull
@Override
public RMResource[] listResources(
@@ -634,7 +898,7 @@ public class LocalResourceController extends BaseLocalResourceController {
) throws DBException {
try (var ignoredLock = lockController.lock(projectId, "resourcePropertyUpdate")) {
validateResourcePath(resourcePath);
BaseWebProjectImpl webProject = getWebProject(projectId, false);
RMLocalProject webProject = getWebProject(projectId, false);
doFileWriteOperation(projectId, webProject.getMetadataFilePath(),
() -> {
log.debug("Updating resource property '" + propertyName + "' in project '" + projectId + "'");
@@ -655,7 +919,7 @@ public class LocalResourceController extends BaseLocalResourceController {
) throws DBException {
try (var ignoredLock = lockController.lock(projectId, "resourcePropertyUpdate")) {
validateResourcePath(resourcePath);
BaseWebProjectImpl webProject = getWebProject(projectId, false);
RMLocalProject webProject = getWebProject(projectId, false);
doFileWriteOperation(projectId, webProject.getMetadataFilePath(),
() -> {
log.debug("Updating resource '" + resourcePath + "' properties in project '" + projectId + "'");
@@ -689,15 +953,9 @@ public class LocalResourceController extends BaseLocalResourceController {
}
}
private String makeProjectIdFromPath(Path path, RMProjectType type) {
String projectName = path.getFileName().toString();
return type.getPrefix() + "_" + projectName;
}
@Nullable
protected RMProject makeProjectFromId(String projectId, boolean loadPermissions) throws DBException {
var projectName = parseProjectName(projectId);
var projectName = WebRMUtils.parseProjectName(projectId);
var projectPath = getProjectPath(projectId);
if (!Files.exists(projectPath)) {
if (isPrivateProject(projectId) && isProjectOwner(projectId)) {
@@ -734,28 +992,8 @@ public class LocalResourceController extends BaseLocalResourceController {
.flatMap(rmProjectPermission -> rmProjectPermission.getAllPermissions().stream())
.toArray(String[]::new);
RMProject project = new RMProject();
String projectName = path.getFileName().toString();
project.setName(projectName);
project.setId(makeProjectIdFromPath(path, type));
project.setType(type);
project.setProjectPermissions(allProjectPermissions);
if (Files.exists(path)) {
try {
project.setCreateTime(
OffsetDateTime.ofInstant(Files.getLastModifiedTime(path).toInstant(), ZoneId.of("UTC")).toInstant().toEpochMilli());
} catch (IOException e) {
log.error(e);
}
}
// Resource types
project.setResourceTypes(ResourceTypeRegistry.getInstance().getResourceTypes()
.stream()
.filter(ResourceTypeDescriptor::isManagable)
.map(RMResourceType::new)
.toArray(RMResourceType[]::new));
return project;
RMLocalProject webProject = new RMLocalProject(workspace, new SessionContextImpl(null), path, type);
return createRmProjectFromWebProject(path, webProject, allProjectPermissions);
}
private void createResourceTypeFolders(Path path) {
@@ -834,7 +1072,7 @@ public class LocalResourceController extends BaseLocalResourceController {
}
protected Path getProjectPath(String projectId) throws DBException {
RMProjectName project = parseProjectName(projectId);
RMProjectName project = WebRMUtils.parseProjectName(projectId);
RMProjectType type = project.getType();
String projectName = project.getName();
switch (type) {
@@ -996,56 +1234,13 @@ public class LocalResourceController extends BaseLocalResourceController {
}
}
public static class RMProjectName {
String prefix;
String name;
private RMProjectName(String prefix, String name) {
this.prefix = prefix;
this.name = name;
}
public String getPrefix() {
return prefix;
}
public String getName() {
return name;
}
public RMProjectType getType() {
return RMProjectType.getByPrefix(prefix);
}
}
public static RMProjectName parseProjectName(String projectId) throws DBException {
if (CommonUtils.isEmpty(projectId)) {
throw new DBException("Project id is empty");
}
return parseProjectNameUnsafe(projectId);
}
private static RMProjectName parseProjectNameUnsafe(String projectId) {
String prefix;
String name;
int divPos = projectId.indexOf("_");
if (divPos < 0) {
prefix = RMProjectType.USER.getPrefix();
name = projectId;
} else {
prefix = projectId.substring(0, divPos);
name = projectId.substring(divPos + 1);
}
return new RMProjectName(prefix, name);
}
public static boolean isGlobalProject(String projectId) {
RMProjectName rmProjectName = parseProjectNameUnsafe(projectId);
RMProjectName rmProjectName = WebRMUtils.parseProjectNameUnsafe(projectId);
return RMProjectType.GLOBAL.getPrefix().equals(rmProjectName.getPrefix());
}
public static boolean isPrivateProject(String projectId) {
RMProjectName rmProjectName = parseProjectNameUnsafe(projectId);
RMProjectName rmProjectName = WebRMUtils.parseProjectNameUnsafe(projectId);
return RMProjectType.USER.getPrefix().equals(rmProjectName.getPrefix());
}
@@ -1056,9 +1251,32 @@ public class LocalResourceController extends BaseLocalResourceController {
}
public static boolean isProjectOwner(String projectId, String userId) {
RMProjectName rmProjectName = parseProjectNameUnsafe(projectId);
RMProjectName rmProjectName = WebRMUtils.parseProjectNameUnsafe(projectId);
return RMProjectType.USER.getPrefix().equals(rmProjectName.getPrefix()) &&
rmProjectName.name.equals(userId);
}
@NotNull
private static RMProject createRmProjectFromWebProject(Path path, RMLocalProject webProject, String[] allProjectPermissions) {
RMProject project = WebRMUtils.createRmProjectFromWebProject(webProject);
project.setProjectPermissions(allProjectPermissions);
if (Files.exists(path)) {
try {
project.setCreateTime(
OffsetDateTime.ofInstant(Files.getLastModifiedTime(path).toInstant(), ZoneId.of("UTC")).toInstant().toEpochMilli());
} catch (IOException e) {
log.error(e);
}
}
// Resource types
project.setResourceTypes(ResourceTypeRegistry.getInstance().getResourceTypes()
.stream()
.filter(ResourceTypeDescriptor::isManagable)
.map(RMResourceType::new)
.toArray(RMResourceType[]::new));
return project;
}
}
@@ -0,0 +1,147 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.model.rm.local;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.auth.SMSessionContext;
import org.jkiss.dbeaver.model.impl.app.BaseProjectImpl;
import org.jkiss.dbeaver.model.rm.RMProjectType;
import org.jkiss.dbeaver.model.rm.RMUtils;
import org.jkiss.dbeaver.registry.DataSourceRegistry;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.Pair;
import java.nio.file.Path;
import java.util.Collection;
public class RMLocalProject extends BaseProjectImpl {
@NotNull
private final Path projectPath;
@NotNull
private final RMProjectType projectType;
public RMLocalProject(
@NotNull DBPWorkspace workspace,
@Nullable SMSessionContext sessionContext,
@NotNull Path projectPath,
@NotNull RMProjectType type
) {
super(workspace, sessionContext);
this.projectPath = projectPath;
this.projectType = type;
}
@Override
public boolean isVirtual() {
return true;
}
@NotNull
@Override
public String getId() {
return RMUtils.makeProjectIdFromPath(projectPath, projectType);
}
@NotNull
@Override
public String getName() {
Object projectName = this.getProjectProperty(PROP_PROJECT_NAME);
if (projectName != null) {
return projectName.toString();
}
return projectPath.getFileName().toString();
}
@NotNull
@Override
public Path getAbsolutePath() {
return projectPath;
}
@Override
public boolean isOpen() {
return true;
}
@Override
public void ensureOpen() {
}
@Override
public boolean isUseSecretStorage() {
return false;
}
public Path getMetadataFilePath() {
return getMetadataPath().resolve(METADATA_STORAGE_FILE);
}
/**
* Method for Bulk Update of resources properties paths
*
* @param oldToNewPaths collection of OldPath to NewPath pairs
*/
public void moveResourcePropertiesBatch(@NotNull Collection<Pair<String, String>> oldToNewPaths) {
loadMetadata();
synchronized (metadataSync) {
for (var pathsPair : oldToNewPaths) {
final var oldResourcePath = CommonUtils.normalizeResourcePath(pathsPair.getFirst());
final var newResourcePath = CommonUtils.normalizeResourcePath(pathsPair.getSecond());
final var resProps = resourceProperties.remove(oldResourcePath);
if (resProps != null) {
resourceProperties.put(newResourcePath, resProps);
}
}
}
flushMetadata();
}
/**
* Method for Bulk Remove of resources properties
*/
public boolean resetResourcesPropertiesBatch(@NotNull Collection<String> resourcesPaths) {
loadMetadata();
boolean propertiesChanged = false;
synchronized (metadataSync) {
for (var resourcePath : resourcesPaths) {
var removedProperties = resourceProperties.remove(CommonUtils.normalizeResourcePath(resourcePath));
if (removedProperties != null) {
propertiesChanged = true;
}
}
}
if (propertiesChanged) {
flushMetadata();
}
return propertiesChanged;
}
public boolean canUpdateProjectName() {
return RMProjectType.SHARED.equals(projectType);
}
@NotNull
@Override
protected DBPDataSourceRegistry createDataSourceRegistry() {
return new DataSourceRegistry<>(this);
}
}
@@ -0,0 +1,41 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.model.rm.local;
import org.jkiss.dbeaver.model.rm.RMProjectType;
public class RMProjectName {
String prefix;
String name;
public RMProjectName(String prefix, String name) {
this.prefix = prefix;
this.name = name;
}
public String getPrefix() {
return prefix;
}
public String getName() {
return name;
}
public RMProjectType getType() {
return RMProjectType.getByPrefix(prefix);
}
}
@@ -29,6 +29,8 @@ import org.jkiss.dbeaver.model.auth.SMAuthSpace;
import org.jkiss.dbeaver.model.auth.SMSessionContext;
import org.jkiss.dbeaver.model.auth.impl.AbstractSessionPersistent;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.security.user.SMTeam;
import org.jkiss.dbeaver.model.rm.RMProjectInfo;
import org.jkiss.dbeaver.model.websocket.event.WSEvent;
import org.jkiss.dbeaver.model.websocket.event.WSEventDeleteTempFile;
import org.jkiss.dbeaver.model.websocket.event.session.WSSessionExpiredEvent;
@@ -36,6 +38,7 @@ import org.jkiss.dbeaver.model.websocket.event.session.WSSessionExpiredEvent;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -55,7 +58,7 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
protected volatile long lastAccessTime;
private final List<CBWebSessionEventHandler> sessionEventHandlers = new CopyOnWriteArrayList<>();
private WebSessionEventsFilter eventsFilter = new WebSessionEventsFilter();
private WebSessionEventsFilter eventsFilter;
private final WebSessionWorkspace workspace;
public BaseWebSession(@NotNull String id, @NotNull ServletApplication application) throws DBException {
@@ -66,6 +69,7 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
this.workspace = createWebWorkspace();
this.workspace.getAuthContext().addSession(this);
this.userContext = createUserContext();
this.eventsFilter = new WebSessionEventsFilter(this);
}
@NotNull
@@ -122,6 +126,12 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
userContext.refreshPermissions();
if (userContext.isAuthorizedInSecurityManager()) {
userContext.refreshAccessibleProjects();
if (userContext.getUser() != null) {
List<String> userTeamIds = Arrays.stream(userContext.getSecurityController().getCurrentUserTeams())
.map(SMTeam::getTeamId)
.toList();
userContext.getUser().setTeams(userTeamIds.toArray(new String[0]));
}
}
} catch (DBException e) {
addSessionError(e);
@@ -225,6 +235,11 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
userContext.getAccessibleProjectIds().add(projectId);
}
public void updateSessionProject(@NotNull String projectId, @NotNull RMProjectInfo rmProjectInfo) throws DBException {
}
public void removeSessionProject(@Nullable String projectId) throws DBException {
userContext.getAccessibleProjectIds().remove(projectId);
}
@@ -18,9 +18,10 @@ package io.cloudbeaver.model.session;
import jakarta.servlet.http.HttpServletRequest;
import org.jkiss.code.Nullable;
import org.jkiss.utils.HttpConstants;
public class WebHttpRequestInfo {
public static final String USER_AGENT = "User-Agent";
public static final String USER_AGENT = HttpConstants.HEADER_USER_AGENT;
@Nullable
private final String id;
@@ -49,10 +49,7 @@ import org.jkiss.dbeaver.model.fs.DBFFileSystemManager;
import org.jkiss.dbeaver.model.meta.Association;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.navigator.DBNModel;
import org.jkiss.dbeaver.model.rm.RMController;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.rm.RMProjectType;
import org.jkiss.dbeaver.model.rm.RMUtils;
import org.jkiss.dbeaver.model.rm.*;
import org.jkiss.dbeaver.model.runtime.AbstractJob;
import org.jkiss.dbeaver.model.runtime.BaseProgressMonitor;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
@@ -307,7 +304,7 @@ public class WebSession extends BaseWebSession
for (RMProject project : rmProjects) {
createWebProject(project);
}
if (user == null && application.getAppConfiguration().isAnonymousAccessEnabled()) {
if (user == null && application.isAnonymousAccessEnabled()) {
WebProjectImpl anonymousProject = createWebProject(RMUtils.createAnonymousProject());
anonymousProject.setInMemory(true);
}
@@ -327,6 +324,7 @@ public class WebSession extends BaseWebSession
} else {
sessionProject = createSessionProject(project);
}
sessionProject.refreshProjectSettings();
// do not load data sources for anonymous project
if (project.getType() == RMProjectType.USER && userContext.getUser() == null) {
sessionProject.setInMemory(true);
@@ -399,9 +397,10 @@ public class WebSession extends BaseWebSession
private synchronized void authAsAnonymousUser() throws DBException {
if (!application.getAppConfiguration().isAnonymousAccessEnabled()) {
if (!application.isAnonymousAccessEnabled()) {
return;
}
SMAuthInfo authInfo = getSecurityController().authenticateAnonymousUser(this.id, getSessionParameters(), CB_SESSION_TYPE);
updateSMSession(authInfo);
notifySessionAuthChange();
@@ -794,7 +793,7 @@ public class WebSession extends BaseWebSession
}
}
public List<WebAuthInfo> removeAuthInfo(String providerId) throws DBException {
public List<WebAuthInfo> removeAuthInfo(String providerId, boolean needResetUserState) throws DBException {
List<WebAuthInfo> oldInfo;
if (providerId == null) {
oldInfo = clearAuthTokens();
@@ -807,12 +806,16 @@ public class WebSession extends BaseWebSession
oldInfo = List.of();
}
}
if (authTokens.isEmpty()) {
if (authTokens.isEmpty() && needResetUserState) {
resetUserState();
}
return oldInfo;
}
public List<WebAuthInfo> removeAuthInfo(String providerId) throws DBException {
return removeAuthInfo(providerId, true);
}
public List<DBACredentialsProvider> getContextCredentialsProviders() {
return getAdapters(DBACredentialsProvider.class);
}
@@ -981,6 +984,16 @@ public class WebSession extends BaseWebSession
createWebProject(rmProject);
}
@Override
public void updateSessionProject(@NotNull String projectId, @NotNull RMProjectInfo rmProjectInfo) throws DBException {
super.updateSessionProject(projectId, rmProjectInfo);
var project = getProjectById(projectId);
if (project == null) {
return;
}
project.updateProjectInfo(rmProjectInfo.getName(), rmProjectInfo.getDescription());
}
@Override
public void removeSessionProject(@Nullable String projectId) throws DBException {
super.removeSessionProject(projectId);
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -118,6 +118,8 @@ public class WebSessionAuthProcessor {
// We may need to associate new credentials with active user
if (linkWithActiveUser) {
securityController.setCurrentUserCredentials(authProviderDescriptor.getId(), authAttrs);
//link only first(main) credentials
alreadyLoggedIn = true;
}
}
@@ -21,18 +21,37 @@ import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.websocket.event.WSEvent;
import org.jkiss.dbeaver.model.websocket.event.WSProjectResourceEvent;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
public class WebSessionEventsFilter {
private final BaseWebSession session;
private final Set<String> subscribedEventTopics = new CopyOnWriteArraySet<>();
private final Set<String> subscribedProjectIds = new CopyOnWriteArraySet<>();
private final Map<String, List<WebTopicActivityListener>> topicActivityListeners = new ConcurrentHashMap<>();
private final Set<String> handledStartTopics = new CopyOnWriteArraySet<>();
public WebSessionEventsFilter(BaseWebSession session) {
this.session = session;
}
public void subscribeOnEventTopic(@Nullable String topic) {
if (topic == null) {
return;
}
subscribedEventTopics.add(topic);
topicActivityListeners
.getOrDefault(topic, List.of()).stream()
.filter(listener -> !handledStartTopics.contains(listener.getTopicName()))
.forEach(listener -> {
listener.handelStartSubscriptionTopic(session);
handledStartTopics.add(topic);
});
}
public void unsubscribeFromEventTopic(@Nullable String topic) {
@@ -67,4 +86,10 @@ public class WebSessionEventsFilter {
return true;
}
public void addTopicSubscribeListener(WebTopicActivityListener listener) {
topicActivityListeners
.computeIfAbsent(listener.getTopicName(), k -> new CopyOnWriteArrayList<>())
.add(listener);
}
}
@@ -62,12 +62,12 @@ public class WebSessionPreferenceStore extends AbstractUserPreferenceStore {
}
@Override
public String getDefaultString(String name) {
public String getDefaultString(@NotNull String name) {
return parentStore.getDefaultString(name);
}
@Override
public boolean isDefault(String name) {
public boolean isDefault(@NotNull String name) {
return !userPreferences.containsKey(name) && parentStore.isDefault(name);
}
@@ -77,7 +77,7 @@ public class WebSessionPreferenceStore extends AbstractUserPreferenceStore {
}
@Override
public void setToDefault(String name) {
public void setToDefault(@NotNull String name) {
throw new RuntimeException("Not implemented");
}
@@ -137,6 +137,7 @@ public class WebSessionWorkspace implements DBPWorkspace {
clearProjects();
}
@Nullable
@Override
public DBPImage getResourceIcon(DBPAdaptable resourceAdapter) {
return null;
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.server;
package io.cloudbeaver.model.session;
public interface HttpConstants {
String CONTENT_TYPE = "Content-Type";
public interface WebTopicActivityListener {
String TYPE_JSON = "application/json";
String HEADER_AUTHORIZATION = "Authorization";
void handelStartSubscriptionTopic(BaseWebSession session);
String getTopicName();
}
@@ -239,7 +239,7 @@ public class WebUserContext implements SMCredentialsProvider {
}
private Set<String> getDefaultPermissions() {
return application.getAppConfiguration().isAnonymousAccessEnabled() ? null : Set.of();
return application.isAnonymousAccessEnabled() ? null : Set.of();
}
public RMController getRmController() {
@@ -77,6 +77,10 @@ public class WebUser {
return user.getUserTeams();
}
public void setTeams(@NotNull String[] teams) {
user.setUserTeams(teams);
}
@Override
public int hashCode() {
return user.getUserId().hashCode();
@@ -23,6 +23,7 @@ import org.eclipse.core.runtime.IConfigurationElement;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.DBPImage;
import org.jkiss.dbeaver.model.impl.AbstractContextDescriptor;
import org.jkiss.utils.CommonUtils;
/**
* WebFeatureDescriptor
@@ -35,6 +36,7 @@ public class WebFeatureDescriptor extends AbstractContextDescriptor implements D
private final String label;
private final String description;
private final DBPImage icon;
private final boolean enabledByDefault;
public WebFeatureDescriptor(IConfigurationElement config)
{
@@ -43,6 +45,7 @@ public class WebFeatureDescriptor extends AbstractContextDescriptor implements D
this.label = config.getAttribute("label");
this.description = config.getAttribute("description");
this.icon = iconToImage(config.getAttribute("icon"));
this.enabledByDefault = CommonUtils.getBoolean(config.getAttribute("enabledByDefault"), false);
}
@NotNull
@@ -69,4 +72,9 @@ public class WebFeatureDescriptor extends AbstractContextDescriptor implements D
return ServletAppUtils.getServletApplication().getAppConfiguration().isFeatureEnabled(this.id);
}
@Override
public boolean isEnabledByDefault() {
return enabledByDefault;
}
}
@@ -68,4 +68,9 @@ public class WebServerFeatureDescriptor extends AbstractContextDescriptor implem
return true;
}
@Override
public boolean isEnabledByDefault() {
return false;
}
}
@@ -32,7 +32,9 @@ public class CBConstants {
public static final String PARAM_SERVER_PORT = "serverPort";
public static final String PARAM_SERVER_HOST = "serverHost";
public static final String PARAM_SERVER_NAME = "serverName";
public static final String PARAM_SECURE_COOKIES = "secureCookies";
public static final String PARAM_FORCE_HTTPS = "forceHttps";
public static final String PARAM_BIND_SESSION_TO_IP = "bindSessionToIp";
public static final String PARAM_SUPPORTED_HOSTS = "supportedHosts";
public static final String PARAM_SSL_CONFIGURATION_PATH = "sslConfigurationPath";
public static final String PARAM_CONTENT_ROOT = "contentRoot";
public static final String PARAM_SERVER_URL = "serverURL";
@@ -93,4 +95,8 @@ public class CBConstants {
public static final String ADMIN_AUTO_GRANT = "auto-grant";
public static final String HOST_LOCALHOST = "localhost";
public static final String HOST_127_0_0_1 = "127.0.0.1";
public static final String BIND_SESSION_ENABLE = "enable";
public static final String BIND_SESSION_DISABLE = "disable";
public static final String DEFAULT_CLOUD_PROJECT_NAME = "GlobalConfiguration";
}
@@ -18,6 +18,7 @@ package io.cloudbeaver.server;
import io.cloudbeaver.utils.ServletAppUtils;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.impl.preferences.AbstractPreferenceStore;
import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore;
@@ -34,37 +35,37 @@ public class WebServerPreferenceStore extends AbstractPreferenceStore {
}
@Override
public boolean contains(String name) {
public boolean contains(@NotNull String name) {
return productConf().containsKey(name) || parentStore.contains(name);
}
@Override
public boolean getBoolean(String name) {
public boolean getBoolean(@NotNull String name) {
return toBoolean(getString(name));
}
@Override
public double getDouble(String name) {
public double getDouble(@NotNull String name) {
return toDouble(getString(name));
}
@Override
public float getFloat(String name) {
public float getFloat(@NotNull String name) {
return toFloat(getString(name));
}
@Override
public int getInt(String name) {
public int getInt(@NotNull String name) {
return toInt(getString(name));
}
@Override
public long getLong(String name) {
public long getLong(@NotNull String name) {
return toLong(getString(name));
}
@Override
public String getString(String name) {
public String getString(@NotNull String name) {
Object value = productConf().get(name);
if (value == null) {
return parentStore.getString(name);
@@ -73,38 +74,38 @@ public class WebServerPreferenceStore extends AbstractPreferenceStore {
}
@Override
public boolean getDefaultBoolean(String name) {
public boolean getDefaultBoolean(@NotNull String name) {
return getBoolean(name);
}
@Override
public double getDefaultDouble(String name) {
public double getDefaultDouble(@NotNull String name) {
return getDouble(name);
}
@Override
public float getDefaultFloat(String name) {
public float getDefaultFloat(@NotNull String name) {
return getFloat(name);
}
@Override
public int getDefaultInt(String name) {
public int getDefaultInt(@NotNull String name) {
return getInt(name);
}
@Override
public long getDefaultLong(String name) {
public long getDefaultLong(@NotNull String name) {
return getLong(name);
}
@Override
public String getDefaultString(String name) {
public String getDefaultString(@NotNull String name) {
// TODO: split product.conf and runtime.product.conf
return getString(name);
}
@Override
public boolean isDefault(String name) {
public boolean isDefault(@NotNull String name) {
return true;
}
@@ -114,68 +115,68 @@ public class WebServerPreferenceStore extends AbstractPreferenceStore {
}
@Override
public void setDefault(String name, double value) {
public void setDefault(@NotNull String name, double value) {
setDefault(name, String.valueOf(value));
}
@Override
public void setDefault(String name, float value) {
public void setDefault(@NotNull String name, float value) {
setDefault(name, String.valueOf(value));
}
@Override
public void setDefault(String name, int value) {
public void setDefault(@NotNull String name, int value) {
setDefault(name, String.valueOf(value));
}
@Override
public void setDefault(String name, long value) {
public void setDefault(@NotNull String name, long value) {
setDefault(name, String.valueOf(value));
}
@Override
public void setDefault(String name, String defaultObject) {
public void setDefault(@NotNull String name, @Nullable String defaultObject) {
// do not store global default properties in product.conf
this.parentStore.setDefault(name, defaultObject);
}
@Override
public void setDefault(String name, boolean value) {
public void setDefault(@NotNull String name, boolean value) {
setDefault(name, String.valueOf(value));
}
@Override
public void setToDefault(String name) {
public void setToDefault(@NotNull String name) {
parentStore.setToDefault(name);
}
@Override
public void setValue(String name, double value) {
public void setValue(@NotNull String name, double value) {
parentStore.setValue(name, value);
}
@Override
public void setValue(String name, float value) {
public void setValue(@NotNull String name, float value) {
parentStore.setValue(name, value);
}
@Override
public void setValue(String name, int value) {
public void setValue(@NotNull String name, int value) {
parentStore.setValue(name, value);
}
@Override
public void setValue(String name, long value) {
public void setValue(@NotNull String name, long value) {
parentStore.setValue(name, value);
}
@Override
public void setValue(String name, String value) {
public void setValue(@NotNull String name, @Nullable String value) {
parentStore.setValue(name, value);
}
@Override
public void setValue(String name, boolean value) {
public void setValue(@NotNull String name, boolean value) {
parentStore.setValue(name, value);
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,8 +17,11 @@
package io.cloudbeaver.service;
import io.cloudbeaver.model.app.ServletApplication;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import java.util.List;
/**
* Servlet service
*/
@@ -28,4 +31,9 @@ public interface DBWServiceBindingServlet<APPLICATION extends ServletApplication
}
void addServlets(APPLICATION application, DBWServletContext servletContext) throws DBException;
@NotNull
default List<String> getExcludedServletPaths(@NotNull APPLICATION application) {
return List.of();
}
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,6 +37,7 @@ import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.CommonUtils;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.*;
@@ -47,6 +48,11 @@ public class ServletAppUtils {
private static final String HEADER_FORWARDED_SCHEME = "X-Forwarded-Scheme";
private static final String HEADER_FORWARDED_HOST = "X-Forwarded-Host";
private static final Set<Integer> DEFAULT_PORTS = Set.of(
80,
443
);
private static final Log log = Log.getLog(ServletAppUtils.class);
@@ -286,7 +292,7 @@ public class ServletAppUtils {
}
@NotNull
public static String getOriginFromRequestOrThrow(HttpServletRequest request) throws DBWebException {
public static String getOriginFromRequest(@NotNull HttpServletRequest request) {
String origin = request.getHeader(HEADER_ORIGIN);
if (CommonUtils.isEmpty(origin)) {
origin = request.getHeader(HEADER_X_ORIGIN);
@@ -309,7 +315,26 @@ public class ServletAppUtils {
if (!origin.endsWith(rootUri)) {
origin = origin + "/" + rootUri + "/";
}
return removeSideSlashes(origin);
origin = removeSideSlashes(origin);
URI uri = URI.create(origin);
if (DEFAULT_PORTS.contains(uri.getPort())) {
try {
origin = new URI(
uri.getScheme(),
uri.getUserInfo(),
uri.getHost(),
-1,
uri.getPath(),
uri.getQuery(),
uri.getFragment()
).toString();
} catch (URISyntaxException e) {
log.error("Failed to create URI without port", e);
}
}
return origin;
}
public static String getRootUrlFromUri(@NotNull URI uri) {
@@ -328,7 +353,22 @@ public class ServletAppUtils {
if (CommonUtils.isEmpty(rootUri)) {
return uri;
}
String[] split = uri.split(rootUri);
return split[0];
rootUri = "/" + rootUri;
if (!uri.endsWith(rootUri)) {
rootUri = rootUri + "/";
}
return substringBeforeLast(uri, rootUri);
}
public static String substringBeforeLast(
@NotNull String string,
@NotNull String separator
) {
if (CommonUtils.isNotEmpty(string) && CommonUtils.isNotEmpty(separator)) {
int pos = string.lastIndexOf(separator);
return pos == -1 ? string : string.substring(0, pos);
} else {
return string;
}
}
}

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