Compare commits

...

56 Commits

Author SHA1 Message Date
Stela Augustinova 2a88ed38c4 Added translation tags to TableCellView component, updated decimal handling 2025-12-08 16:45:18 +01:00
Stela Augustinova 6ebee92542 Merge branch 'master' into feature/table-cell-data-view 2025-12-08 16:07:55 +01:00
Jan Prochazka 7024e4b40d Merge pull request #1289 from dbgate/feature/postgresql-decimal
Postgresql decimal
2025-12-08 15:42:19 +01:00
Jan Prochazka bc2e27d7da Merge pull request #1290 from david-pivonka/feature/table-cell-data-view
Add Table format to Cell data view sidebar
2025-12-08 15:35:35 +01:00
David Pivoňka 142ebe3d27 Fix scrolling in Table - Row view
Use absolute positioning pattern for proper scrolling behavior
when many columns are displayed.
2025-12-08 15:23:59 +01:00
David Pivoňka 38c25cae74 Add multi-row selection support with bulk editing
- Show "(Multiple values)" when selected rows have different values
- Allow bulk editing: changes apply to all selected rows
- Rename format to "Table - Row" for clarity
2025-12-08 15:01:02 +01:00
David Pivoňka 190c610466 Add column filter/search to Table cell data view
Adds a search input at the top of the Table view that filters columns
by name with regex support.
2025-12-08 14:31:38 +01:00
Stela Augustinova c6d3fc06a3 Add support for deserializing decimal type 2025-12-08 13:57:10 +01:00
David Pivoňka d220525ac7 Use braces for isChangedRef.get() blocks to match codebase style 2025-12-08 13:47:35 +01:00
David Pivoňka 5e4a631ff2 Remove comments and apply early return pattern 2025-12-08 13:43:41 +01:00
David Pivoňka 9099ce42b9 Add Table format to Cell data view sidebar
Adds a new "Table" format option to the Cell data view widget that
displays the selected row as a vertical list with column names above
values, inspired by TablePlus.

Features:
- Shows all columns from the selected row in grid display order
- Inline editing support for regular values (double-click to edit)
- JSON values open Edit Cell modal on double-click
- Open-in-new button for JSON values to view in JSON tab
2025-12-08 13:37:55 +01:00
Jan Prochazka df226fea22 import test - greater timeout 2025-12-08 13:12:08 +01:00
Stela Augustinova e67ee4ffdb Add support for PostgreSQL decimal type in filter and grid utilities 2025-12-08 12:52:47 +01:00
Stela Augustinova 2baf975847 Added PostgreSQL decimal type in DataGridCell component 2025-12-05 13:14:14 +01:00
Stela Augustinova c1672ebc8e Handling decimal values in putValue method 2025-12-05 13:13:56 +01:00
Stela Augustinova bbbd291065 Formatting decimal values in stringifyCellValue function 2025-12-05 13:13:29 +01:00
Stela Augustinova 0a3c1efdd4 Add support for parsing bigint and decimal types in PostgreSQL driver 2025-12-05 13:13:08 +01:00
SPRINX0\prochazka 89121a2608 handled UTF-8 BOM in CSV input 2025-12-04 16:44:08 +01:00
SPRINX0\prochazka 23cf264d4d fix 2025-12-04 16:29:06 +01:00
Jan Prochazka 0c15e524d7 changelog 2025-12-03 18:32:14 +01:00
Jan Prochazka b0b5b1c30d v6.7.3 2025-12-03 18:22:02 +01:00
Jan Prochazka 30b4c85c5a better formating 2025-12-03 18:21:04 +01:00
Jan Prochazka 910f9cadfe v6.7.3-premium-beta.1 2025-12-03 17:37:20 +01:00
Jan Prochazka 6de37ebd16 cypress mocha reporter 2025-12-03 17:33:37 +01:00
Jan Prochazka de57c4e87e Skip tests with AI API calls 2025-12-03 17:14:51 +01:00
Jan Prochazka b85cf66490 Merge branch 'feature/pgsql-droptable-fix' 2025-12-03 17:06:45 +01:00
Jan Prochazka 8e638ea9a6 fix 2025-12-03 17:00:04 +01:00
Jan Prochazka b4849ec495 fix problem with diff analysing after drop object 2025-12-03 16:47:50 +01:00
Jan Prochazka 09c12d52ac more tests 2025-12-03 16:39:42 +01:00
Jan Prochazka db6a2ddd7e Merge pull request #1286 from dbgate/feature/custom-thousands-separator
Feature/custom thousands separator
2025-12-03 15:40:04 +01:00
Jan Prochazka 12ef9463ab Merge pull request #1284 from dbgate/feature/numeric-align-right
Added isTypeNumber check for right alignment in DataGridCell
2025-12-03 15:37:58 +01:00
Jan Prochazka a557ad177e changelog 2025-12-03 14:57:11 +01:00
Stela Augustinova 78e838f2f0 Custom thousands separator formatting in grid cell values 2025-12-03 13:53:47 +01:00
Stela Augustinova c1f216c7c7 Deleted checkbox for thousands separator and updated select field options 2025-12-03 13:53:10 +01:00
Jan Prochazka b75ff99e4c v6.7.2 2025-12-03 13:44:35 +01:00
Jan Prochazka 780dd8ade9 language icon 2025-12-03 13:37:13 +01:00
Jan Prochazka e1c10b7653 v6.7.2-beta.7 2025-12-03 12:57:52 +01:00
Jan Prochazka be9505f8fe SYNC: translations 2025-12-03 11:55:53 +00:00
Jan Prochazka d6bcd4f94f changelog 2025-12-03 12:52:40 +01:00
Jan Prochazka 7d2196f4c3 v6.7.2-premium-beta.6 2025-12-03 12:42:25 +01:00
Jan Prochazka 0539174317 SYNC: fixed e2e test 2025-12-03 11:34:55 +00:00
Jan Prochazka b4b52e12d5 SYNC: try to fix test 2025-12-03 10:13:16 +00:00
CI workflows f2e0b1cfa2 chore: auto-update github workflows 2025-12-03 10:10:08 +00:00
CI workflows 8020e2a263 Update pro ref 2025-12-03 10:09:55 +00:00
Jan Prochazka 6112d9b1b0 SYNC: settings storage changed 2025-12-03 10:09:42 +00:00
Stela Augustinova 4a1fbcbd31 Added select field for thousands separator 2025-12-03 10:46:34 +01:00
CI workflows 0218bb4990 chore: auto-update github workflows 2025-12-03 07:43:56 +00:00
CI workflows 3769c03565 Update pro ref 2025-12-03 07:43:40 +00:00
SPRINX0\prochazka d96cb10476 behaviour settings changed 2025-12-02 18:14:55 +01:00
SPRINX0\prochazka b6b6123434 refresh DB - don't offer incremental analysis when not supported 2025-12-02 18:07:28 +01:00
SPRINX0\prochazka b40877fcc1 fix - don't show update mode in web 2025-12-02 17:59:41 +01:00
SPRINX0\prochazka af5ae29b73 changelog 2025-12-02 17:51:44 +01:00
SPRINX0\prochazka 082fceebbe v6.7.2-premium-beta.5 2025-12-02 17:29:27 +01:00
CI workflows f1dab80a06 chore: auto-update github workflows 2025-12-02 15:10:33 +00:00
CI workflows cbf2fac2cf Update pro ref 2025-12-02 15:10:16 +00:00
Jan Prochazka 4564bd7180 SYNC: Merge pull request #17 from dbgate/feature/settings-test 2025-12-02 15:10:00 +00:00
62 changed files with 1156 additions and 443 deletions
+1 -1
View File
@@ -43,7 +43,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: ca69c4857d7d93c4b066018e8a9a0a0ece2300e7
ref: ae1fcf6e61c6f7dfbb21005daa259c68e899a80a
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+1 -1
View File
@@ -43,7 +43,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: ca69c4857d7d93c4b066018e8a9a0a0ece2300e7
ref: ae1fcf6e61c6f7dfbb21005daa259c68e899a80a
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+1 -1
View File
@@ -39,7 +39,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: ca69c4857d7d93c4b066018e8a9a0a0ece2300e7
ref: ae1fcf6e61c6f7dfbb21005daa259c68e899a80a
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+1 -1
View File
@@ -44,7 +44,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: ca69c4857d7d93c4b066018e8a9a0a0ece2300e7
ref: ae1fcf6e61c6f7dfbb21005daa259c68e899a80a
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: ca69c4857d7d93c4b066018e8a9a0a0ece2300e7
ref: ae1fcf6e61c6f7dfbb21005daa259c68e899a80a
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: ca69c4857d7d93c4b066018e8a9a0a0ece2300e7
ref: ae1fcf6e61c6f7dfbb21005daa259c68e899a80a
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+15 -1
View File
@@ -8,6 +8,20 @@ Builds:
- linux - application for linux
- win - application for Windows
## 6.7.3
- FIXED: Fixed problem in analyser core - in PostgreSQL, after dropping table, dropped table still appeared in structure
- FIXED: PostgreSQL numeric columns do not align right #1254
- ADDED: Custom thousands separator #1213
## 6.7.2
- CHANGED: Settings modal redesign - now is settings opened in tab instead of modal, similarily as in VSCode
- FIXED: Fixed search in table shortcuts #1273
- CHANGED: Improved foreign key editor UX
- FIXED: Fixed incremental DB structure refresh for PostgreSQL, optimalized slow loading primary keys in PostgreSQL
- CHANGED: You could now choose, how to refresh structure, added ability to disconnect or reconnect
- ADDED: Better processing of table backups, generate table restore script #1274
- CHANGED: Improved storage of settings, especially for Team Premium edition
## 6.7.1
- ADDED: LANGUAGE environment variable for the web version. #1266
- ADDED: New localizations (Italian, Portugese (Brazil), Japanese)
@@ -65,7 +79,7 @@ Builds:
- ADDED: SQL AI assistant - powered by database chat, could help you to write SQL queries (Premium)
- ADDED: Explain SQL error (powered by AI) (Premium)
- ADDED: Database chat (and SQL AI Assistant) now supports showing charts (Premium)
- FIXED: Fxied editing new files and roles (Team Premium)
- FIXED: Fixed editing new files and roles (Team Premium)
- FIXED: Connection to standalone database could be now pinned
- FIXED: Cannot open up large JSON file #1215
+1
View File
@@ -10,6 +10,7 @@ module.exports = defineConfig({
// baseUrl: 'http://localhost:3000',
// trashAssetsBeforeRuns: false,
chromeWebSecurity: false,
reporter: process.env.CI ? 'mocha-reporter-gha' : 'spec',
setupNodeEvents(on, config) {
// implement node event listeners here
+73 -3
View File
@@ -110,7 +110,7 @@ describe('Charts', () => {
cy.themeshot('new-object-window');
});
it('Database chat - charts', () => {
it.skip('Database chat - charts', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').click();
cy.testid('TabsPanel_buttonNewObject').click();
@@ -125,7 +125,7 @@ describe('Charts', () => {
cy.themeshot('database-chat-chart');
});
it('Database chat', () => {
it.skip('Database chat', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').click();
cy.testid('TabsPanel_buttonNewObject').click();
@@ -146,7 +146,7 @@ describe('Charts', () => {
// cy.themeshot('database-chat');
});
it('Explain query error', () => {
it.skip('Explain query error', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').click();
cy.testid('TabsPanel_buttonNewObject').click();
@@ -198,4 +198,74 @@ describe('Charts', () => {
cy.testid('ConfirmModal_okButton').click();
cy.testid('WidgetIconPanel_settings');
});
it('Settings', () => {
cy.testid('WidgetIconPanel_settings').click();
cy.themeshot('app-settings-general');
cy.contains('Behaviour').click();
cy.themeshot('app-settings-behaviour');
cy.get('[data-testid=BehaviourSettings_useTabPreviewMode]').uncheck();
// SQL Editor
cy.contains('SQL Editor').click();
cy.get('[data-testid=SQLEditorSettings_sqlCommandsCase]').select('lowerCase');
cy.contains('MySql-connection').click();
cy.contains('charts_sample').click();
cy.contains('employees').click();
cy.contains('MyChinook').click();
cy.contains('Customer').rightclick();
cy.contains('SQL template').click();
cy.contains('CREATE TABLE').click();
cy.contains('create table');
// Default Actions
cy.testid('WidgetIconPanel_settings').click();
cy.contains('Default Actions').click();
cy.get('[data-testid=DefaultActionsSettings_useLastUsedAction]').uncheck();
// Themes
cy.contains('Themes').click();
cy.themeshot('app-settings-themes');
cy.contains('Dark').click();
cy.get('body').find('.theme-dark').should('exist');
cy.contains('Light').click();
cy.get('body').find('.theme-light').should('exist');
// General
cy.contains(/^General$/).click();
cy.contains('charts_sample');
cy.get('[data-testid=GeneralSettings_lockedDatabaseMode]').check();
cy.contains('Connections').click();
cy.contains('charts_sample').should('not.exist');
// Datagrid
cy.contains('Data grid').click();
cy.get('[data-testid=DataGridSettings_showHintColumns]').uncheck();
cy.wait(500);
cy.contains('Album').click();
cy.contains('AC/DC').should('not.exist');
cy.testid('WidgetIconPanel_settings').click();
cy.contains('Keyboard shortcuts').click();
cy.themeshot('app-settings-keyboard-shortcuts');
cy.contains('Chart').click();
cy.testid('CommandModal_keyboardButton').click();
cy.realPress(['Control', 'g']);
cy.realPress('Enter');
cy.contains('OK').click();
cy.contains('Ctrl+G');
cy.contains('AI').click();
cy.themeshot('app-settings-ai');
cy.get('[data-testid=AISettings_addProviderButton]').click();
cy.contains('Provider 1');
cy.get('[data-testid=AiProviderCard_removeButton]').click();
cy.contains('Are you sure you want to remove Provider 1 provider?');
cy.contains('OK').click();
cy.contains('Provider 1').should('not.exist');
});
});
+4 -3
View File
@@ -210,7 +210,8 @@ describe('Import CSV', () => {
cy.testid('ImportExportConfigurator_tableMappingSection').contains('20 rows written').should('be.visible');
cy.testid('SqlObjectList_refreshButton').click();
cy.contains('Refresh DB structure (incremental)').click();
cy.testid('DatabasStatusMenu_refreshFull').click();
// cy.contains('Refresh DB structure (incremental)').click();
cy.testid('SqlObjectList_container').contains('customers-20').click();
cy.contains('Rows: 20').should('be.visible');
@@ -236,7 +237,7 @@ describe('Import CSV - source error', () => {
cy.testid('ImportExportTab_preview_content').contains('Invalid Closing Quote').should('be.visible');
cy.testid('ImportExportTab_executeButton').click();
cy.testid('ImportExportConfigurator_errorInfoIcon_customers-20-err').click();
cy.testid('ImportExportConfigurator_errorInfoIcon_customers-20-err', { timeout: 10000 }).click();
cy.testid('ErrorMessageModal_message').contains('Invalid Closing Quote').should('be.visible');
});
@@ -255,7 +256,7 @@ describe('Import CSV - target error', () => {
cy.contains('customers-20');
cy.testid('ImportExportConfigurator_targetName_customers-20').clear().type('system."]`');
cy.testid('ImportExportTab_executeButton').click();
cy.testid('ImportExportConfigurator_errorInfoIcon_customers-20').click();
cy.testid('ImportExportConfigurator_errorInfoIcon_customers-20', { timeout: 10000 }).click();
cy.testid('ErrorMessageModal_message').should('be.visible');
});
});
+1 -4
View File
@@ -10,11 +10,11 @@
"cypress-real-events": "^1.13.0",
"env-cmd": "^10.1.0",
"kill-port": "^2.0.1",
"mocha-reporter-gha": "^1.1.1",
"start-server-and-test": "^2.0.8"
},
"scripts": {
"cy:open": "cypress open --config experimentalInteractiveRunEvents=true",
"cy:run:add-connection": "cypress run --spec cypress/e2e/add-connection.cy.js",
"cy:run:portal": "cypress run --spec cypress/e2e/portal.cy.js",
"cy:run:oauth": "cypress run --spec cypress/e2e/oauth.cy.js",
@@ -23,7 +23,6 @@
"cy:run:multi-sql": "cypress run --spec cypress/e2e/multi-sql.cy.js",
"cy:run:cloud": "cypress run --spec cypress/e2e/cloud.cy.js",
"cy:run:charts": "cypress run --spec cypress/e2e/charts.cy.js",
"start:add-connection": "node clearTestingData && cd .. && node packer/build/bundle.js --listen-api --run-e2e-tests",
"start:portal": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/portal/.env node e2e-tests/init/portal.js && env-cmd -f e2e-tests/env/portal/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
"start:oauth": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/oauth/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
@@ -32,7 +31,6 @@
"start:multi-sql": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/multi-sql/.env node e2e-tests/init/multi-sql.js && env-cmd -f e2e-tests/env/multi-sql/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
"start:cloud": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/cloud/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
"start:charts": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/charts/.env node e2e-tests/init/charts.js && env-cmd -f e2e-tests/env/charts/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
"test:add-connection": "start-server-and-test start:add-connection http://localhost:3000 cy:run:add-connection",
"test:portal": "start-server-and-test start:portal http://localhost:3000 cy:run:portal",
"test:oauth": "start-server-and-test start:oauth http://localhost:3000 cy:run:oauth",
@@ -41,7 +39,6 @@
"test:multi-sql": "start-server-and-test start:multi-sql http://localhost:3000 cy:run:multi-sql",
"test:cloud": "start-server-and-test start:cloud http://localhost:3000 cy:run:cloud",
"test:charts": "start-server-and-test start:charts http://localhost:3000 cy:run:charts",
"test": "yarn test:add-connection && yarn test:portal && yarn test:oauth && yarn test:browse-data && yarn test:team && yarn test:multi-sql && yarn test:cloud && yarn test:charts",
"test:ci": "yarn test"
},
+52
View File
@@ -2,6 +2,34 @@
# yarn lockfile v1
"@actions/core@^1.10.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.11.1.tgz#ae683aac5112438021588030efb53b1adb86f172"
integrity sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==
dependencies:
"@actions/exec" "^1.1.1"
"@actions/http-client" "^2.0.1"
"@actions/exec@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@actions/exec/-/exec-1.1.1.tgz#2e43f28c54022537172819a7cf886c844221a611"
integrity sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==
dependencies:
"@actions/io" "^1.0.1"
"@actions/http-client@^2.0.1":
version "2.2.3"
resolved "https://registry.yarnpkg.com/@actions/http-client/-/http-client-2.2.3.tgz#31fc0b25c0e665754ed39a9f19a8611fc6dab674"
integrity sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==
dependencies:
tunnel "^0.0.6"
undici "^5.25.4"
"@actions/io@^1.0.1":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@actions/io/-/io-1.1.3.tgz#4cdb6254da7962b07473ff5c335f3da485d94d71"
integrity sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==
"@colors/colors@1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
@@ -39,6 +67,11 @@
debug "^3.1.0"
lodash.once "^4.1.1"
"@fastify/busboy@^2.0.0":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d"
integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==
"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0":
version "9.3.0"
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb"
@@ -947,6 +980,13 @@ minimist@^1.2.8:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
mocha-reporter-gha@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/mocha-reporter-gha/-/mocha-reporter-gha-1.1.1.tgz#e1248abd0769f55b57b36ccd7db2b0b6573d5adf"
integrity sha512-CFbcgM56V4yWlbF91XuwrE6a5X/IqjVXTPefO7m8cY8Es8G1UhJ2KKOrk16AcSemRzVWXp2Fdy3bWJ7j45snWw==
dependencies:
"@actions/core" "^1.10.1"
ms@^2.1.1, ms@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
@@ -1292,6 +1332,11 @@ tunnel-agent@^0.6.0:
dependencies:
safe-buffer "^5.0.1"
tunnel@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
@@ -1307,6 +1352,13 @@ undici-types@~6.20.0:
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433"
integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
undici@^5.25.4:
version "5.29.0"
resolved "https://registry.yarnpkg.com/undici/-/undici-5.29.0.tgz#419595449ae3f2cdcba3580a2e8903399bd1f5a3"
integrity sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==
dependencies:
"@fastify/busboy" "^2.0.0"
universalify@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
@@ -28,12 +28,12 @@ describe('Schema tests', () => {
const count = schemas1.length;
expect(structure1.tables.length).toEqual(2);
await runCommandOnDriver(conn, driver, dmp => dmp.createSchema('myschema'));
const structure2 = await driver.analyseIncremental(conn, structure1);
const schemas2 = await driver.listSchemas(conn);
expect(schemas2.find(x => x.schemaName == 'myschema')).toBeTruthy();
expect(schemas2.length).toEqual(count + 1);
expect(schemas2.find(x => x.isDefault).schemaName).toEqual(engine.defaultSchemaName);
if (!engine.skipIncrementalAnalysis) {
const structure2 = await driver.analyseIncremental(conn, structure1);
const schemas2 = await driver.listSchemas(conn);
expect(schemas2.find(x => x.schemaName == 'myschema')).toBeTruthy();
expect(schemas2.length).toEqual(count + 1);
expect(schemas2.find(x => x.isDefault).schemaName).toEqual(engine.defaultSchemaName);
expect(structure2).toBeNull();
}
})
@@ -50,10 +50,10 @@ describe('Schema tests', () => {
expect(schemas1.find(x => x.schemaName == 'myschema')).toBeTruthy();
expect(structure1.tables.length).toEqual(2);
await runCommandOnDriver(conn, driver, dmp => dmp.dropSchema('myschema'));
const structure2 = await driver.analyseIncremental(conn, structure1);
const schemas2 = await driver.listSchemas(conn);
expect(schemas2.find(x => x.schemaName == 'myschema')).toBeFalsy();
if (!engine.skipIncrementalAnalysis) {
const structure2 = await driver.analyseIncremental(conn, structure1);
const schemas2 = await driver.listSchemas(conn);
expect(schemas2.find(x => x.schemaName == 'myschema')).toBeFalsy();
expect(structure2).toBeNull();
}
})
@@ -94,7 +94,7 @@ describe('Table analyse', () => {
})
);
test.each(engines.filter(x => !x.skipIncrementalAnalysis).map(engine => [engine.label, engine]))(
test.each(engines.map(engine => [engine.label, engine]))(
'Table add - incremental analysis - %s',
testWrapper(async (conn, driver, engine) => {
await runCommandOnDriver(conn, driver, dmp => dmp.put(t2Sql(engine)));
@@ -112,7 +112,7 @@ describe('Table analyse', () => {
})
);
test.each(engines.filter(x => !x.skipIncrementalAnalysis).map(engine => [engine.label, engine]))(
test.each(engines.map(engine => [engine.label, engine]))(
'Table remove - incremental analysis - %s',
testWrapper(async (conn, driver, engine) => {
await runCommandOnDriver(conn, driver, dmp => dmp.put(t1Sql(engine)));
@@ -130,7 +130,7 @@ describe('Table analyse', () => {
})
);
test.each(engines.filter(x => !x.skipIncrementalAnalysis).map(engine => [engine.label, engine]))(
test.each(engines.map(engine => [engine.label, engine]))(
'Table change - incremental analysis - %s',
testWrapper(async (conn, driver, engine) => {
await runCommandOnDriver(conn, driver, dmp => dmp.put(t1Sql(engine)));
+1 -1
View File
@@ -1,6 +1,6 @@
{
"private": true,
"version": "6.7.2-premium-beta.4",
"version": "6.7.3",
"name": "dbgate-all",
"workspaces": [
"packages/*",
+4 -9
View File
@@ -289,16 +289,11 @@ module.exports = {
const res = await lock.acquire('settings', async () => {
const currentValue = await this.loadSettings();
try {
let updated = currentValue;
let updated = {
...currentValue,
...values,
};
if (process.env.STORAGE_DATABASE) {
updated = {
...currentValue,
..._.mapValues(values, v => {
if (v === true) return 'true';
if (v === false) return 'false';
return v;
}),
};
await storage.writeConfig({
group: 'settings',
config: updated,
+6
View File
@@ -360,6 +360,12 @@ module.exports = {
"columnName": "value",
"dataType": "varchar(1000)",
"notNull": false
},
{
"pureName": "config",
"columnName": "valueType",
"dataType": "varchar(50)",
"notNull": false
}
],
"foreignKeys": [],
+1
View File
@@ -21,6 +21,7 @@ export function getFilterValueExpression(value, dataType?) {
if (value === false) return 'FALSE';
if (value.$oid) return `ObjectId("${value.$oid}")`;
if (value.$bigint) return value.$bigint;
if (value.$decimal) return value.$decimal;
if (value.type == 'Buffer' && Array.isArray(value.data)) {
return '0x' + arrayToHexString(value.data);
}
@@ -19,6 +19,7 @@ function isLike(value, test) {
function extractRawValue(value) {
if (value?.$bigint) return value.$bigint;
if (value?.$oid) return value.$oid;
if (value?.$decimal) return value.$decimal;
return value;
}
+5
View File
@@ -164,6 +164,11 @@ export class DatabaseAnalyser<TClient = any> {
const res = {};
for (const field of STRUCTURE_FIELDS) {
const isAll = this.modifications.some(x => x.action == 'all' && x.objectTypeField == field);
if (isAll) {
res[field] = newlyAnalysed[field] || [];
continue;
}
const removedIds = this.modifications
.filter(x => x.action == 'remove' && x.objectTypeField == field)
.map(x => x.objectId);
+1
View File
@@ -87,6 +87,7 @@ export class SqlDumper implements AlterProcessor {
this.putByteArrayValue(bytes);
}
else if (value?.$bigint) this.putRaw(value?.$bigint);
else if (value?.$decimal) this.putRaw(value?.$decimal);
else if (_isPlainObject(value) || _isArray(value)) this.putStringValue(JSON.stringify(value));
else this.put('^null');
}
+51 -19
View File
@@ -45,14 +45,15 @@ export function hexStringToArray(inputString) {
export function base64ToHex(base64String) {
const binaryString = atob(base64String);
const hexString = Array.from(binaryString, c =>
c.charCodeAt(0).toString(16).padStart(2, '0')
).join('');
const hexString = Array.from(binaryString, c => c.charCodeAt(0).toString(16).padStart(2, '0')).join('');
return '0x' + hexString.toUpperCase();
};
}
export function hexToBase64(hexString) {
const binaryString = hexString.match(/.{1,2}/g).map(byte => String.fromCharCode(parseInt(byte, 16))).join('');
const binaryString = hexString
.match(/.{1,2}/g)
.map(byte => String.fromCharCode(parseInt(byte, 16)))
.join('');
return btoa(binaryString);
}
@@ -68,9 +69,9 @@ export function parseCellValue(value, editorTypes?: DataEditorTypesBehaviour) {
if (mHex) {
return {
$binary: {
base64: hexToBase64(value.substring(2))
}
}
base64: hexToBase64(value.substring(2)),
},
};
}
}
@@ -200,6 +201,26 @@ function stringifyJsonToGrid(value): ReturnType<typeof stringifyCellValue> {
return { value: '(JSON)', gridStyle: 'nullCellStyle' };
}
function formatNumberCustomSeparator(value, thousandsSeparator) {
const [intPart, decPart] = value.split('.');
const intPartWithSeparator = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSeparator);
return decPart ? `${intPartWithSeparator}.${decPart}` : intPartWithSeparator;
}
function formatCellNumber(value, gridFormattingOptions?: { thousandsSeparator?: string }) {
const separator = gridFormattingOptions?.thousandsSeparator;
if (_isNumber(value)) {
if (separator === 'none' || (value < 1000 && value > -1000)) return value.toString();
if (separator === 'system') return value.toLocaleString();
}
// fallback for system locale
if (separator === 'space' || separator === 'system') return formatNumberCustomSeparator(value.toString(), ' ');
if (separator === 'narrowspace') return formatNumberCustomSeparator(value.toString(), '\u202F');
if (separator === 'comma') return formatNumberCustomSeparator(value.toString(), ',');
if (separator === 'dot') return formatNumberCustomSeparator(value.toString(), '.');
return value.toString();
}
export function stringifyCellValue(
value,
intent:
@@ -210,7 +231,7 @@ export function stringifyCellValue(
| 'exportIntent'
| 'clipboardIntent',
editorTypes?: DataEditorTypesBehaviour,
gridFormattingOptions?: { useThousandsSeparator?: boolean },
gridFormattingOptions?: { thousandsSeparator?: string },
jsonParsedValue?: any
): {
value: string;
@@ -251,12 +272,19 @@ export function stringifyCellValue(
};
}
if (value?.$decimal) {
return {
value: formatCellNumber(value.$decimal, gridFormattingOptions),
gridStyle: 'valueCellStyle',
};
}
if (editorTypes?.parseHexAsBuffer) {
// if (value?.type == 'Buffer' && _isArray(value.data)) {
// return { value: '0x' + arrayToHexString(value.data), gridStyle: 'valueCellStyle' };
// }
}
if (editorTypes?.parseObjectIdAsDollar) {
if (value?.$oid) {
switch (intent) {
@@ -270,13 +298,13 @@ export function stringifyCellValue(
}
if (value?.$bigint) {
return {
value: value.$bigint,
value: formatCellNumber(value.$bigint, gridFormattingOptions),
gridStyle: 'valueCellStyle',
};
}
if (typeof value === 'bigint') {
return {
value: value.toString(),
value: formatCellNumber(value.toString(), gridFormattingOptions),
gridStyle: 'valueCellStyle',
};
}
@@ -351,13 +379,8 @@ export function stringifyCellValue(
if (_isNumber(value)) {
switch (intent) {
case 'gridCellIntent':
return {
value:
gridFormattingOptions?.useThousandsSeparator && (value >= 10000 || value <= -10000)
? value.toLocaleString()
: value.toString(),
gridStyle: 'valueCellStyle',
};
const separator = gridFormattingOptions?.thousandsSeparator;
return { value: formatCellNumber(value, gridFormattingOptions), gridStyle: 'valueCellStyle' };
default:
return { value: value.toString() };
}
@@ -449,6 +472,9 @@ export function shouldOpenMultilineDialog(value) {
if (value?.$bigint) {
return false;
}
if (value?.$decimal) {
return false;
}
if (_isPlainObject(value) || _isArray(value)) {
return true;
}
@@ -699,6 +725,9 @@ export function deserializeJsTypesFromJsonParse(obj) {
if (value?.$bigint) {
return BigInt(value.$bigint);
}
if (value?.$decimal) {
return value.$decimal;
}
});
}
@@ -713,6 +742,9 @@ export function deserializeJsTypesReviver(key, value) {
if (value?.$bigint) {
return BigInt(value.$bigint);
}
if (value?.$decimal) {
return value.$decimal;
}
return value;
}
+1
View File
@@ -238,6 +238,7 @@ export interface EngineDriver<TClient = any, TDataBase = any> extends FilterBeha
supportsDatabaseRestore?: boolean;
supportsServerSummary?: boolean;
supportsDatabaseProfiler?: boolean;
supportsIncrementalAnalysis?: boolean;
requiresDefaultSortCriteria?: boolean;
profilerFormatterFunction?: string;
profilerTimestampFunction?: string;
@@ -0,0 +1,356 @@
<script lang="ts">
import _ from 'lodash';
import { tick } from 'svelte';
import CellValue from '../datagrid/CellValue.svelte';
import { isJsonLikeLongString, safeJsonParse, parseCellValue, stringifyCellValue } from 'dbgate-tools';
import keycodes from '../utility/keycodes';
import createRef from '../utility/createRef';
import { showModal } from '../modals/modalTools';
import EditCellDataModal from '../modals/EditCellDataModal.svelte';
import ShowFormButton from '../formview/ShowFormButton.svelte';
import { openJsonDocument } from '../tabs/JsonTab.svelte';
import SearchBoxWrapper from '../elements/SearchBoxWrapper.svelte';
import SearchInput from '../elements/SearchInput.svelte';
import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
import { _t } from '../translations'
export let selection;
$: firstSelection = selection?.[0];
$: rowData = firstSelection?.rowData;
$: editable = firstSelection?.editable;
$: editorTypes = firstSelection?.editorTypes;
$: columns = selection?.columns || [];
$: realColumnUniqueNames = selection?.realColumnUniqueNames || [];
$: setCellValue = selection?.setCellValue;
$: uniqueRows = _.uniqBy(selection || [], 'row');
$: isMultipleRows = uniqueRows.length > 1;
function areValuesEqual(val1, val2) {
if (val1 === val2) return true;
if (val1 == null && val2 == null) return true;
if (val1 == null || val2 == null) return false;
return _.isEqual(val1, val2);
}
function getFieldValue(colName) {
if (!isMultipleRows) return { value: rowData?.[colName], hasMultipleValues: false };
const values = uniqueRows.map(sel => sel.rowData?.[colName]);
const firstValue = values[0];
const allSame = values.every(v => areValuesEqual(v, firstValue));
return allSame ? { value: firstValue, hasMultipleValues: false } : { value: null, hasMultipleValues: true };
}
let filter = '';
$: orderedFields = realColumnUniqueNames
.map(colName => {
const col = columns.find(c => c.uniqueName === colName);
if (!col) return null;
const { value, hasMultipleValues } = getFieldValue(colName);
return {
columnName: col.columnName || colName,
uniqueName: colName,
value,
hasMultipleValues,
col,
};
})
.filter(Boolean);
$: filteredFields = orderedFields.filter(field => {
if (!filter) return true;
try {
const regex = new RegExp(filter, 'i');
return regex.test(field.columnName);
} catch (e) {
return field.columnName.toLowerCase().includes(filter.toLowerCase());
}
});
let editingColumn = null;
let editValue = '';
let domEditor = null;
const isChangedRef = createRef(false);
function isJsonValue(value) {
if (_.isPlainObject(value) && !(value?.type == 'Buffer' && _.isArray(value.data)) && !value.$oid && !value.$bigint && !value.$decimal) {
return true;
}
if (_.isArray(value)) return true;
if (typeof value !== 'string') return false;
if (!isJsonLikeLongString(value)) return false;
const parsed = safeJsonParse(value);
return parsed !== null && (_.isPlainObject(parsed) || _.isArray(parsed));
}
function getJsonObject(value) {
if (_.isPlainObject(value) || _.isArray(value)) return value;
if (typeof value === 'string') return safeJsonParse(value);
return null;
}
function handleDoubleClick(field) {
if (!editable || !setCellValue) return;
if (isJsonValue(field.value) && !field.hasMultipleValues) {
openEditModal(field);
return;
}
startEditing(field);
}
function startEditing(field) {
if (!editable || !setCellValue) return;
editingColumn = field.uniqueName;
editValue = field.hasMultipleValues ? '' : stringifyCellValue(field.value, 'inlineEditorIntent', editorTypes).value;
isChangedRef.set(false);
tick().then(() => {
if (!domEditor) return;
domEditor.focus();
if (!field.hasMultipleValues) domEditor.select();
});
}
function handleKeyDown(event, field) {
switch (event.keyCode) {
case keycodes.escape:
isChangedRef.set(false);
editingColumn = null;
break;
case keycodes.enter:
if (isChangedRef.get()) {
saveValue(field);
}
editingColumn = null;
event.preventDefault();
break;
case keycodes.tab:
if (isChangedRef.get()) {
saveValue(field);
}
editingColumn = null;
event.preventDefault();
moveToNextField(field, event.shiftKey);
break;
}
}
function moveToNextField(field, reverse) {
const currentIndex = filteredFields.findIndex(f => f.uniqueName === field.uniqueName);
const nextIndex = reverse ? currentIndex - 1 : currentIndex + 1;
if (nextIndex < 0 || nextIndex >= filteredFields.length) return;
tick().then(() => {
const nextField = filteredFields[nextIndex];
if (isJsonValue(nextField.value)) {
openEditModal(nextField);
} else {
startEditing(nextField);
}
});
}
function handleSearchKeyDown(e) {
if (e.keyCode === keycodes.backspace && (e.metaKey || e.ctrlKey)) {
filter = '';
e.stopPropagation();
e.preventDefault();
}
}
function handleBlur(field) {
if (isChangedRef.get()) {
saveValue(field);
}
editingColumn = null;
}
function saveValue(field) {
if (!setCellValue) return;
const parsedValue = parseCellValue(editValue, editorTypes);
setCellValue(field.uniqueName, parsedValue);
isChangedRef.set(false);
}
function openEditModal(field) {
if (!setCellValue) return;
showModal(EditCellDataModal, {
value: field.value,
dataEditorTypesBehaviour: editorTypes,
onSave: value => setCellValue(field.uniqueName, value),
});
}
function openJsonInNewTab(field) {
const jsonObj = getJsonObject(field.value);
if (jsonObj) openJsonDocument(jsonObj, undefined, true);
}
function getJsonParsedValue(value) {
if (editorTypes?.explicitDataType) return null;
if (!isJsonLikeLongString(value)) return null;
return safeJsonParse(value);
}
</script>
<div class="outer">
<div class="content">
{#if rowData}
<div class="search-wrapper" on:keydown={handleSearchKeyDown}>
<SearchBoxWrapper noMargin>
<SearchInput placeholder={_t('tableCell.filterColumns', { defaultMessage: "Filter columns (regex)" })} bind:value={filter} />
<CloseSearchButton bind:filter />
</SearchBoxWrapper>
</div>
{/if}
<div class="inner">
{#if !rowData}
<div class="no-data">{_t('tableCell.noDataSelected', { defaultMessage: "No data selected" })}</div>
{:else}
{#each filteredFields as field (field.uniqueName)}
<div class="field">
<div class="field-name">{field.columnName}</div>
<div
class="field-value"
class:editable
on:dblclick={() => handleDoubleClick(field)}
>
{#if editingColumn === field.uniqueName}
<div class="editor-wrapper">
<input
type="text"
bind:this={domEditor}
bind:value={editValue}
on:input={() => isChangedRef.set(true)}
on:keydown={e => handleKeyDown(e, field)}
on:blur={() => handleBlur(field)}
class="inline-editor"
/>
{#if editable && !field.hasMultipleValues}
<ShowFormButton
icon="icon edit"
on:click={() => {
editingColumn = null;
openEditModal(field);
}}
/>
{/if}
</div>
{:else if field.hasMultipleValues}
<span class="multiple-values">({_t('tableCell.multipleValues', { defaultMessage: "Multiple values" })})</span>
{:else}
<CellValue
{rowData}
value={field.value}
jsonParsedValue={getJsonParsedValue(field.value)}
{editorTypes}
/>
{#if isJsonValue(field.value)}
<ShowFormButton
icon="icon open-in-new"
on:click={() => openJsonInNewTab(field)}
/>
{/if}
{/if}
</div>
</div>
{/each}
{/if}
</div>
</div>
</div>
<style>
.outer {
flex: 1;
position: relative;
}
.content {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
}
.search-wrapper {
padding: 4px 4px 0 4px;
flex-shrink: 0;
}
.inner {
overflow: auto;
flex: 1;
padding: 4px;
}
.no-data {
color: var(--theme-font-3);
font-style: italic;
padding: 8px;
}
.field {
margin-bottom: 8px;
border: 1px solid var(--theme-border);
border-radius: 3px;
overflow: hidden;
}
.field-name {
background: var(--theme-bg-1);
padding: 4px 8px;
font-weight: 500;
font-size: 11px;
color: var(--theme-font-2);
border-bottom: 1px solid var(--theme-border);
}
.field-value {
padding: 6px 8px;
background: var(--theme-bg-0);
min-height: 20px;
word-break: break-all;
position: relative;
}
.field-value.editable {
cursor: text;
}
.field-value.editable:hover {
background: var(--theme-bg-hover);
}
.editor-wrapper {
display: flex;
align-items: center;
}
.inline-editor {
flex: 1;
border: none;
outline: none;
background: var(--theme-bg-0);
color: var(--theme-font-1);
padding: 0;
margin: 0;
font-family: inherit;
font-size: inherit;
}
.inline-editor:focus {
outline: none;
}
.multiple-values {
color: var(--theme-font-3);
font-style: italic;
}
</style>
@@ -1,10 +1,11 @@
import _ from 'lodash';
import { currentDatabase, getCurrentDatabase } from '../stores';
import { currentDatabase, getCurrentDatabase, getExtensions } from '../stores';
import getElectron from '../utility/getElectron';
import registerCommand from './registerCommand';
import { apiCall } from '../utility/api';
import { getDatabasStatusMenu, switchCurrentDatabase } from '../utility/common';
import { __t } from '../translations';
import { findEngineDriver } from 'dbgate-tools';
registerCommand({
id: 'database.changeState',
@@ -18,7 +19,8 @@ registerCommand({
conid: connection._id,
database: name,
};
const driver = findEngineDriver(connection, getExtensions());
return getDatabasStatusMenu(dbid);
return getDatabasStatusMenu(dbid, driver);
},
});
+4 -7
View File
@@ -40,8 +40,6 @@ import { getSettings } from '../utility/metadataLoaders';
import { isMac, switchCurrentDatabase } from '../utility/common';
import { doLogout } from '../clientAuth';
import { disconnectServerConnection } from '../appobj/ConnectionAppObject.svelte';
import UploadErrorModal from '../modals/UploadErrorModal.svelte';
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
import NewCollectionModal from '../modals/NewCollectionModal.svelte';
import ConfirmModal from '../modals/ConfirmModal.svelte';
import localforage from 'localforage';
@@ -73,7 +71,8 @@ registerCommand({
category: __t('command.theme', { defaultMessage: 'Theme' }),
name: __t('command.theme.change', { defaultMessage: 'Change' }),
toolbarName: __t('command.theme.changeToolbar', { defaultMessage: 'Change theme' }),
onClick: () => openNewTab({
onClick: () =>
openNewTab({
title: 'Settings',
icon: 'icon settings',
tabComponent: 'SettingsTab',
@@ -1230,8 +1229,7 @@ registerCommand({
},
});
if ( hasPermission('application-log'))
{
if (hasPermission('application-log')) {
registerCommand({
id: 'app.showLogs',
category: __t('command.application', { defaultMessage: 'Application' }),
@@ -1246,8 +1244,7 @@ if ( hasPermission('application-log'))
});
}
if (hasPermission('widgets/plugins'))
{
if (hasPermission('widgets/plugins')) {
registerCommand({
id: 'app.managePlugins',
category: __t('command.application', { defaultMessage: 'Application' }),
+2 -2
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import _ from 'lodash';
import { getBoolSettingsValue } from '../settings/settingsTools';
import { getStringSettingsValue } from '../settings/settingsTools';
import { stringifyCellValue } from 'dbgate-tools';
export let rowData;
@@ -13,7 +13,7 @@
value,
'gridCellIntent',
editorTypes,
{ useThousandsSeparator: getBoolSettingsValue('dataGrid.thousandsSeparator', false) },
{ thousandsSeparator: getStringSettingsValue('dataGrid.thousandsSeparatorChar', 'none') },
jsonParsedValue
);
@@ -57,7 +57,7 @@
$: style = computeStyle(maxWidth, col);
$: isJson =
_.isPlainObject(value) && !(value?.type == 'Buffer' && _.isArray(value.data)) && !value.$oid && !value.$bigint;
_.isPlainObject(value) && !(value?.type == 'Buffer' && _.isArray(value.data)) && !value.$oid && !value.$bigint && !value.$decimal;
// don't parse JSON for explicit data types
$: jsonParsedValue = !editorTypes?.explicitDataType && isJsonLikeLongString(value) ? safeJsonParse(value) : null;
@@ -1258,9 +1258,27 @@
condition: display?.getChangeSetCondition(rowData),
insertedRowIndex: grider?.getInsertedRowIndex(row),
rowStatus: grider.getRowStatus(row),
onSetValue: value => grider.setCellValue(row, column, value),
editable: grider.editable,
editorTypes: display?.driver?.dataEditorTypesBehaviour,
};
})
.filter(x => x.column);
res.columns = columns;
res.realColumnUniqueNames = realColumnUniqueNames;
if (res.length > 0) {
const uniqueRowIndices = _.uniq(res.map(x => x.row));
res.setCellValue = (columnName, value) => {
grider.beginUpdate();
for (const row of uniqueRowIndices) {
grider.setCellValue(row, columnName, value);
}
grider.endUpdate();
};
}
return res;
}
+1
View File
@@ -73,6 +73,7 @@ export function countColumnSizes(grider: Grider, columns, containerWidth, displa
if (_.isArray(value)) text = `[${value.length} items]`;
else if (value?.$oid) text = `ObjectId("${value.$oid}")`;
else if (value?.$bigint) text = value.$bigint;
else if (value?.$decimal) text = value.$decimal;
else if (isJsonLikeLongString(value) && safeJsonParse(value)) text = '(JSON)';
const width = context.measureText(typeof text == 'string' ? text.slice(0, MAX_GRID_TEXT_LENGTH) : text).width + 8;
// console.log('colName', colName, text, width);
@@ -137,6 +137,7 @@
position: relative;
overflow: hidden;
height: 100%;
background-color: var(--theme-bg-0);
}
.scrollableContentContainer {
@@ -1,9 +1,12 @@
<script lang="ts">
import FontIcon from "../icons/FontIcon.svelte";
export let type;
export let label;
export let noMargin = false;
export let disabled = false;
export let labelProps: any = {};
export let labelIcon = null;
</script>
<div class="largeFormMarker" class:noMargin>
@@ -12,6 +15,9 @@
<span {...labelProps} on:click={labelProps.onClick} class:disabled class='checkLabel'>{label}</span>
{:else}
<div class="label" {...labelProps} on:click={labelProps.onClick}>
{#if labelIcon}
<FontIcon icon={labelIcon} padRight />
{/if}
<span {...labelProps} on:click={labelProps.onClick} class:disabled>{label}</span>
</div>
<slot />
+6 -1
View File
@@ -164,7 +164,12 @@
changeActiveSubmenu();
}}
>
<a on:click={e => handleClick(e, item)} class:disabled={item.disabled} class:bold={item.isBold}>
<a
on:click={e => handleClick(e, item)}
class:disabled={item.disabled}
class:bold={item.isBold}
data-testid={item.testid}
>
<span>
{#if item.switchValue && item.switchStoreGetter}
{#key switchIndex}
@@ -1,58 +1,61 @@
<script lang="ts">
import FormCheckboxField from "../forms/FormCheckboxField.svelte";
import { _t } from "../translations";
import FontIcon from '../icons/FontIcon.svelte';
import FormValues from "../forms/FormValues.svelte";
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
import { _t } from '../translations';
import FontIcon from '../icons/FontIcon.svelte';
import FormValues from '../forms/FormValues.svelte';
</script>
<div class="wrapper">
<FormValues let:values>
<FormValues let:values>
<div class="heading">{_t('settings.behaviour', { defaultMessage: 'Behaviour' })}</div>
<FormCheckboxField
name="behaviour.useTabPreviewMode"
label={_t('settings.behaviour.useTabPreviewMode', { defaultMessage: 'Use tab preview mode' })}
defaultValue={true}
name="behaviour.jsonPreviewWrap"
label={_t('settings.behaviour.jsonPreviewWrap', { defaultMessage: 'Wrap JSON in preview' })}
defaultValue={false}
/>
<FormCheckboxField
name="behaviour.jsonPreviewWrap"
label={_t('settings.behaviour.jsonPreviewWrap', { defaultMessage: 'Wrap JSON in preview' })}
defaultValue={false}
name="behaviour.openDetailOnArrows"
label={_t('settings.behaviour.openDetailOnArrows', {
defaultMessage: 'Open detail on keyboard navigation',
})}
defaultValue={true}
disabled={values['behaviour.useTabPreviewMode'] === false}
/>
<div class="heading">{_t('settings.tabPreviewMode', { defaultMessage: 'Tab Preview Mode' })}</div>
<div class="tip">
<FontIcon icon="img tip" />
{_t('settings.behaviour.singleClickPreview', {
<FontIcon icon="img tip" />
{_t('settings.behaviour.singleClickPreview', {
defaultMessage:
'When you single-click or select a file in the "Tables, Views, Functions" view, it is shown in a preview mode and reuses an existing tab (preview tab). This is useful if you are quickly browsing tables and don\'t want every visited table to have its own tab. When you start editing the table or use double-click to open the table from the "Tables" view, a new tab is dedicated to that table.',
})}
'When you single-click or select a file in the "Tables, Views, Functions" view, it is shown in a preview mode and reuses an existing tab (preview tab). This is useful if you are quickly browsing tables and don\'t want every visited table to have its own tab. When you start editing the table or use double-click to open the table from the "Tables" view, a new tab is dedicated to that table.',
})}
</div>
<FormCheckboxField
name="behaviour.openDetailOnArrows"
label={_t('settings.behaviour.openDetailOnArrows', {
defaultMessage: 'Open detail on keyboard navigation',
})}
defaultValue={true}
disabled={values['behaviour.useTabPreviewMode'] === false}
name="behaviour.useTabPreviewMode"
label={_t('settings.behaviour.useTabPreviewMode', { defaultMessage: 'Use tab preview mode' })}
defaultValue={true}
data-testid="BehaviourSettings_useTabPreviewMode"
/>
<div class="heading">{_t('settings.confirmations', { defaultMessage: 'Confirmations' })}</div>
<FormCheckboxField
name="skipConfirm.tableDataSave"
label={_t('settings.confirmations.skipConfirm.tableDataSave', {
name="skipConfirm.tableDataSave"
label={_t('settings.confirmations.skipConfirm.tableDataSave', {
defaultMessage: 'Skip confirmation when saving table data (SQL)',
})}
})}
/>
<FormCheckboxField
name="skipConfirm.collectionDataSave"
label={_t('settings.confirmations.skipConfirm.collectionDataSave', {
name="skipConfirm.collectionDataSave"
label={_t('settings.confirmations.skipConfirm.collectionDataSave', {
defaultMessage: 'Skip confirmation when saving collection data (NoSQL)',
})}
})}
/>
</FormValues>
</FormValues>
</div>
<style>
@@ -67,4 +70,4 @@
margin-left: var(--dim-large-form-margin);
margin-top: var(--dim-large-form-margin);
}
</style>
</style>
@@ -1,81 +1,64 @@
<script lang="ts">
import CheckboxField from "../forms/CheckboxField.svelte";
import FormCheckboxField from "../forms/FormCheckboxField.svelte";
import FormFieldTemplateLarge from "../forms/FormFieldTemplateLarge.svelte";
import FormSelectField from "../forms/FormSelectField.svelte";
import FormTextField from "../forms/FormTextField.svelte";
import FormValues from "../forms/FormValues.svelte";
import { lockedDatabaseMode } from "../stores";
import { _t } from "../translations";
import CheckboxField from '../forms/CheckboxField.svelte';
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
import FormFieldTemplateLarge from '../forms/FormFieldTemplateLarge.svelte';
import FormSelectField from '../forms/FormSelectField.svelte';
import FormTextField from '../forms/FormTextField.svelte';
import FormValues from '../forms/FormValues.svelte';
import { lockedDatabaseMode } from '../stores';
import { _t } from '../translations';
</script>
<div class="wrapper">
<FormValues let:values>
<FormValues let:values>
<div class="heading">{_t('settings.connection', { defaultMessage: 'Connection' })}</div>
<FormFieldTemplateLarge
label={_t('settings.connection.showOnlyTabsFromSelectedDatabase', {
defaultMessage: 'Show only tabs from selected database',
})}
type="checkbox"
labelProps={{
onClick: () => {
$lockedDatabaseMode = !$lockedDatabaseMode;
},
}}
>
<CheckboxField checked={$lockedDatabaseMode} on:change={e => ($lockedDatabaseMode = e.target.checked)} />
</FormFieldTemplateLarge>
<FormCheckboxField
name="connection.autoRefresh"
label={_t('settings.connection.autoRefresh', {
name="connection.autoRefresh"
label={_t('settings.connection.autoRefresh', {
defaultMessage: 'Automatic refresh of database model on background',
})}
defaultValue={false}
})}
defaultValue={false}
/>
<FormTextField
name="connection.autoRefreshInterval"
label={_t('settings.connection.autoRefreshInterval', {
name="connection.autoRefreshInterval"
label={_t('settings.connection.autoRefreshInterval', {
defaultMessage: 'Interval between automatic DB structure reloads in seconds',
})}
defaultValue="30"
disabled={values['connection.autoRefresh'] === false}
})}
defaultValue="30"
disabled={values['connection.autoRefresh'] === false}
/>
<FormSelectField
label={_t('settings.connection.sshBindHost', { defaultMessage: 'Local host address for SSH connections' })}
name="connection.sshBindHost"
isNative
defaultValue="127.0.0.1"
options={[
label={_t('settings.connection.sshBindHost', { defaultMessage: 'Local host address for SSH connections' })}
name="connection.sshBindHost"
isNative
defaultValue="127.0.0.1"
options={[
{ value: '127.0.0.1', label: '127.0.0.1 (IPv4)' },
{ value: '::1', label: '::1 (IPv6)' },
{ value: 'localhost', label: 'localhost (domain name)' },
]}
]}
/>
<div class="heading">{_t('settings.session', { defaultMessage: 'Query sessions' })}</div>
<FormCheckboxField
name="session.autoClose"
label={_t('settings.session.autoClose', {
name="session.autoClose"
label={_t('settings.session.autoClose', {
defaultMessage: 'Automatic close query sessions after period without any activity',
})}
defaultValue={true}
})}
defaultValue={true}
/>
<FormTextField
name="session.autoCloseTimeout"
label={_t('settings.session.autoCloseTimeout', {
name="session.autoCloseTimeout"
label={_t('settings.session.autoCloseTimeout', {
defaultMessage: 'Interval, after which query session without activity is closed (in minutes)',
})}
defaultValue="15"
disabled={values['session.autoClose'] === false}
})}
defaultValue="15"
disabled={values['session.autoClose'] === false}
/>
</FormValues>
</FormValues>
</div>
<style>
.heading {
font-size: 20px;
@@ -84,4 +67,11 @@
margin-top: var(--dim-large-form-margin);
}
</style>
.wrapper :global(input) {
max-width: 400px;
}
.wrapper :global(select) {
max-width: 400px;
}
</style>
+101 -81
View File
@@ -1,93 +1,105 @@
<script lang="ts">
import FormCheckboxField from "../forms/FormCheckboxField.svelte";
import FormSelectField from "../forms/FormSelectField.svelte";
import FormTextField from "../forms/FormTextField.svelte";
import { _t } from "../translations";
import { isProApp } from "../utility/proTools";
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
import FormSelectField from '../forms/FormSelectField.svelte';
import FormTextField from '../forms/FormTextField.svelte';
import { _t } from '../translations';
import { isProApp } from '../utility/proTools';
</script>
<div class="wrapper">
<div class="heading">{_t('settings.dataGrid.title', { defaultMessage: 'Data grid' })}</div>
<FormTextField
name="dataGrid.pageSize"
label={_t('settings.dataGrid.pageSize', {
defaultMessage: 'Page size (number of rows for incremental loading, must be between 5 and 50000)',
})}
defaultValue="100"
/>
{#if isProApp()}
<FormCheckboxField
name="dataGrid.showHintColumns"
label={_t('settings.dataGrid.showHintColumns', { defaultMessage: 'Show foreign key hints' })}
defaultValue={true}
/>
{/if}
<!-- <FormCheckboxField name="dataGrid.showHintColumns" label="Show foreign key hints" defaultValue={true} /> -->
<div class="heading">{_t('settings.dataGrid.title', { defaultMessage: 'Data grid' })}</div>
<FormTextField
name="dataGrid.pageSize"
label={_t('settings.dataGrid.pageSize', {
defaultMessage: 'Page size (number of rows for incremental loading, must be between 5 and 50000)',
})}
defaultValue="100"
/>
{#if isProApp()}
<FormCheckboxField
name="dataGrid.showHintColumns"
label={_t('settings.dataGrid.showHintColumns', { defaultMessage: 'Show foreign key hints' })}
defaultValue={true}
data-testid="DataGridSettings_showHintColumns"
/>
{/if}
<!-- <FormCheckboxField name="dataGrid.showHintColumns" label="Show foreign key hints" defaultValue={true} /> -->
<FormCheckboxField
name="dataGrid.thousandsSeparator"
label={_t('settings.dataGrid.thousandsSeparator', {
defaultMessage: 'Use thousands separator for numbers',
})}
/>
<FormSelectField
label={_t('settings.dataGrid.thousandsSeparator', { defaultMessage: 'Thousands separator for numbers' })}
name="dataGrid.thousandsSeparatorChar"
isNative
defaultValue="none"
options={[
{ value: 'none', label: _t('settings.dataGrid.thousandsSeparator.none', { defaultMessage: 'None' }) },
{ value: 'system', label: _t('settings.dataGrid.thousandsSeparator.system', { defaultMessage: 'System' }) },
{ value: 'space', label: _t('settings.dataGrid.thousandsSeparator.space', { defaultMessage: 'Space' }) },
{
value: 'narrowspace',
label: _t('settings.dataGrid.thousandsSeparator.narrowSpace', {
defaultMessage: 'Narrow space',
}),
},
{ value: 'comma', label: _t('settings.dataGrid.thousandsSeparator.comma', { defaultMessage: 'Comma (,)' }) },
{ value: 'dot', label: _t('settings.dataGrid.thousandsSeparator.dot', { defaultMessage: 'Dot (.)' }) },
]}
/>
<FormTextField
name="dataGrid.defaultAutoRefreshInterval"
label={_t('settings.dataGrid.defaultAutoRefreshInterval', {
defaultMessage: 'Default grid auto refresh interval in seconds',
})}
defaultValue="10"
/>
<FormTextField
name="dataGrid.defaultAutoRefreshInterval"
label={_t('settings.dataGrid.defaultAutoRefreshInterval', {
defaultMessage: 'Default grid auto refresh interval in seconds',
})}
defaultValue="10"
/>
<FormCheckboxField
name="dataGrid.alignNumbersRight"
label={_t('settings.dataGrid.alignNumbersRight', { defaultMessage: 'Align numbers to right' })}
defaultValue={false}
/>
<FormCheckboxField
name="dataGrid.alignNumbersRight"
label={_t('settings.dataGrid.alignNumbersRight', { defaultMessage: 'Align numbers to right' })}
defaultValue={false}
/>
<FormTextField
name="dataGrid.collectionPageSize"
label={_t('settings.dataGrid.collectionPageSize', {
defaultMessage: 'Collection page size (for MongoDB JSON view, must be between 5 and 1000)',
})}
defaultValue="50"
/>
<FormTextField
name="dataGrid.collectionPageSize"
label={_t('settings.dataGrid.collectionPageSize', {
defaultMessage: 'Collection page size (for MongoDB JSON view, must be between 5 and 1000)',
})}
defaultValue="50"
/>
<FormSelectField
label={_t('settings.dataGrid.coloringMode', { defaultMessage: 'Row coloring mode' })}
name="dataGrid.coloringMode"
isNative
defaultValue="36"
options={[
{
value: '36',
label: _t('settings.dataGrid.coloringMode.36', { defaultMessage: 'Every 3rd and 6th row' }),
},
{
value: '2-primary',
label: _t('settings.dataGrid.coloringMode.2-primary', {
defaultMessage: 'Every 2-nd row, primary color',
}),
},
{
value: '2-secondary',
label: _t('settings.dataGrid.coloringMode.2-secondary', {
defaultMessage: 'Every 2-nd row, secondary color',
}),
},
{ value: 'none', label: _t('settings.dataGrid.coloringMode.none', { defaultMessage: 'None' }) },
]}
/>
<FormSelectField
label={_t('settings.dataGrid.coloringMode', { defaultMessage: 'Row coloring mode' })}
name="dataGrid.coloringMode"
isNative
defaultValue="36"
options={[
{
value: '36',
label: _t('settings.dataGrid.coloringMode.36', { defaultMessage: 'Every 3rd and 6th row' }),
},
{
value: '2-primary',
label: _t('settings.dataGrid.coloringMode.2-primary', {
defaultMessage: 'Every 2-nd row, primary color',
}),
},
{
value: '2-secondary',
label: _t('settings.dataGrid.coloringMode.2-secondary', {
defaultMessage: 'Every 2-nd row, secondary color',
}),
},
{ value: 'none', label: _t('settings.dataGrid.coloringMode.none', { defaultMessage: 'None' }) },
]}
/>
<FormCheckboxField
name="dataGrid.showAllColumnsWhenSearch"
label={_t('settings.dataGrid.showAllColumnsWhenSearch', {
defaultMessage: 'Show all columns when searching',
})}
defaultValue={false}
/>
<FormCheckboxField
name="dataGrid.showAllColumnsWhenSearch"
label={_t('settings.dataGrid.showAllColumnsWhenSearch', {
defaultMessage: 'Show all columns when searching',
})}
defaultValue={false}
/>
</div>
<style>
@@ -97,4 +109,12 @@ defaultValue={false}
margin-left: var(--dim-large-form-margin);
margin-top: var(--dim-large-form-margin);
}
</style>
.wrapper :global(select) {
max-width: 400px;
}
.wrapper :global(input) {
max-width: 400px;
}
</style>
@@ -56,6 +56,7 @@
name="defaultAction.useLastUsedAction"
label={_t('settings.defaultActions.useLastUsedAction', { defaultMessage: 'Use last used action' })}
defaultValue={true}
data-testid="DefaultActionsSettings_useLastUsedAction"
/>
<FormDefaultActionField
@@ -100,4 +101,8 @@
margin-top: var(--dim-large-form-margin);
}
.wrapper :global(select){
max-width: 400px;
}
</style>
+126 -100
View File
@@ -1,115 +1,138 @@
<script lang="ts">
import { internalRedirectTo } from '../clientAuth';
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
import FormFieldTemplateLarge from '../forms/FormFieldTemplateLarge.svelte';
import FormSelectField from '../forms/FormSelectField.svelte';
import SelectField from '../forms/SelectField.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import { showModal } from '../modals/modalTools';
import { _t, getSelectedLanguage, setSelectedLanguage } from '../translations';
import { isMac } from '../utility/common';
import getElectron from '../utility/getElectron';
import ConfirmModal from '../modals/ConfirmModal.svelte';
import { internalRedirectTo } from '../clientAuth';
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
import FormFieldTemplateLarge from '../forms/FormFieldTemplateLarge.svelte';
import FormSelectField from '../forms/FormSelectField.svelte';
import SelectField from '../forms/SelectField.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import { showModal } from '../modals/modalTools';
import { _t, getSelectedLanguage, setSelectedLanguage } from '../translations';
import { isMac } from '../utility/common';
import getElectron from '../utility/getElectron';
import ConfirmModal from '../modals/ConfirmModal.svelte';
import hasPermission from '../utility/hasPermission';
import CheckboxField from '../forms/CheckboxField.svelte';
import { lockedDatabaseMode } from '../stores';
const electron = getElectron();
let restartWarning = false;
const electron = getElectron();
let restartWarning = false;
</script>
<div class="wrapper">
<div class="heading">{_t('settings.application', { defaultMessage: 'Application' })}</div>
<FormFieldTemplateLarge
label={_t('settings.localization.language', { defaultMessage: 'Language' })}
type="combo"
>
<SelectField
isNative
data-testid="SettingsModal_languageSelect"
options={[
{ value: 'cs', label: 'Čeština' },
{ value: 'de', label: 'Deutsch' },
{ value: 'en', label: 'English' },
{ value: 'es', label: 'Español' },
{ value: 'fr', label: 'Français' },
{ value: 'it', label: 'Italiano' },
{ value: 'pt', label: 'Português (Brasil)' },
{ value: 'sk', label: 'Slovenčina' },
{ value: 'ja', label: '日本語' },
{ value: 'zh', label: '中文' },
]}
defaultValue={getSelectedLanguage()}
value={getSelectedLanguage()}
on:change={e => {
setSelectedLanguage(e.detail);
showModal(ConfirmModal, {
message: _t('settings.localization.reloadWarning', {
defaultMessage: 'Application will be reloaded to apply new language settings',
}),
onConfirm: () => {
setTimeout(() => {
internalRedirectTo(electron ? '/index.html' : '/');
}, 100);
},
});
}}
/>
</FormFieldTemplateLarge>
<FormSelectField
label={_t('settings.other.autoUpdateApplication', { defaultMessage: 'Auto update application' })}
name="app.autoUpdateMode"
isNative
defaultValue=""
options={[
{
value: 'skip',
label: _t('settings.other.autoUpdateApplication.skip', {
defaultMessage: 'Do not check for new versions',
}),
},
{
value: '',
label: _t('settings.other.autoUpdateApplication.check', { defaultMessage: 'Check for new versions' }),
},
{
value: 'download',
label: _t('settings.other.autoUpdateApplication.download', {
defaultMessage: 'Check and download new versions',
}),
},
]}
<div class="heading">{_t('settings.application', { defaultMessage: 'Application' })}</div>
<FormFieldTemplateLarge
label={_t('settings.localization.language', { defaultMessage: 'Language' })}
type="combo"
labelIcon="mdi mdi-translate"
>
<SelectField
isNative
data-testid="SettingsModal_languageSelect"
options={[
{ value: 'cs', label: 'Čeština' },
{ value: 'de', label: 'Deutsch' },
{ value: 'en', label: 'English' },
{ value: 'es', label: 'Español' },
{ value: 'fr', label: 'Français' },
{ value: 'it', label: 'Italiano' },
{ value: 'pt', label: 'Português (Brasil)' },
{ value: 'sk', label: 'Slovenčina' },
{ value: 'ja', label: '日本語' },
{ value: 'zh', label: '中文' },
]}
defaultValue={getSelectedLanguage()}
value={getSelectedLanguage()}
on:change={e => {
setSelectedLanguage(e.detail);
showModal(ConfirmModal, {
message: _t('settings.localization.reloadWarning', {
defaultMessage: 'Application will be reloaded to apply new language settings',
}),
onConfirm: () => {
setTimeout(() => {
internalRedirectTo(electron ? '/index.html' : '/');
}, 100);
},
});
}}
/>
</FormFieldTemplateLarge>
<div class="heading">{_t('settings.appearance', { defaultMessage: 'Appearance' })}</div>
{#if electron}
<FormSelectField
label={_t('settings.other.autoUpdateApplication', { defaultMessage: 'Auto update application' })}
name="app.autoUpdateMode"
isNative
defaultValue=""
options={[
{
value: 'skip',
label: _t('settings.other.autoUpdateApplication.skip', {
defaultMessage: 'Do not check for new versions',
}),
},
{
value: '',
label: _t('settings.other.autoUpdateApplication.check', { defaultMessage: 'Check for new versions' }),
},
{
value: 'download',
label: _t('settings.other.autoUpdateApplication.download', {
defaultMessage: 'Check and download new versions',
}),
},
]}
/>
{/if}
{#if electron}
<FormCheckboxField
name="app.useNativeMenu"
label={isMac()
? _t('settings.useNativeWindowTitle', { defaultMessage: 'Use native window title' })
: _t('settings.useSystemNativeMenu', { defaultMessage: 'Use system native menu' })}
on:change={() => {
restartWarning = true;
}}
/>
{#if restartWarning}
<div class="ml-5 mb-3">
<FontIcon icon="img warn" />
{_t('settings.nativeMenuRestartWarning', {
defaultMessage: 'Native menu settings will be applied after app restart',
})}
</div>
{/if}
{/if}
<FormFieldTemplateLarge
label={_t('settings.connection.showOnlyTabsFromSelectedDatabase', {
defaultMessage: 'Show only tabs from selected database',
})}
type="checkbox"
labelProps={{
onClick: () => {
$lockedDatabaseMode = !$lockedDatabaseMode;
},
}}
>
<CheckboxField
checked={$lockedDatabaseMode}
on:change={e => ($lockedDatabaseMode = e.target['checked'])}
data-testid="GeneralSettings_lockedDatabaseMode"
/>
</FormFieldTemplateLarge>
<div class="heading">{_t('settings.appearance', { defaultMessage: 'Appearance' })}</div>
{#if electron}
<FormCheckboxField
name="app.useNativeMenu"
label={isMac()
? _t('settings.useNativeWindowTitle', { defaultMessage: 'Use native window title' })
: _t('settings.useSystemNativeMenu', { defaultMessage: 'Use system native menu' })}
on:change={() => {
restartWarning = true;
}}
/>
{#if restartWarning}
<div class="ml-5 mb-3">
<FontIcon icon="img warn" />
{_t('settings.nativeMenuRestartWarning', {
defaultMessage: 'Native menu settings will be applied after app restart',
})}
</div>
{/if}
{/if}
<FormCheckboxField
name="tabGroup.showServerName"
label={_t('settings.tabGroup.showServerName', {
defaultMessage: 'Show server name alongside database name in title of the tab group',
defaultMessage: 'Show server name alongside database name in title of the tab group',
})}
defaultValue={false}
/>
disabled={!hasPermission('settings/change')}
/>
</div>
<style>
@@ -120,4 +143,7 @@
margin-top: var(--dim-large-form-margin);
}
</style>
.wrapper :global(select) {
max-width: 400px;
}
</style>
@@ -26,6 +26,7 @@
{ value: 'upperCase', label: 'UPPER CASE' },
{ value: 'lowerCase', label: 'lower case' },
]}
data-testid="SQLEditorSettings_sqlCommandsCase"
/>
</div>
<div class="col-3">
@@ -97,4 +98,8 @@ defaultValue={false}
margin-left: var(--dim-large-form-margin);
margin-top: var(--dim-large-form-margin);
}
.wrapper :global(input){
max-width: 400px;
}
</style>
@@ -29,6 +29,13 @@ export function getStringSettingsValue(name, defaultValue) {
return res;
}
export function getObjectSettingsValue(name, defaultValue) {
const settings = getCurrentSettings();
const res = settings[name];
if (res == null) return defaultValue;
return res;
}
export function getConnectionClickActionSetting(): 'connect' | 'openDetails' | 'none' {
return getStringSettingsValue('defaultAction.connectionClick', 'connect');
}
+105 -102
View File
@@ -3,111 +3,114 @@
</script>
<script lang="ts">
import SettingsMenuControl from "../elements/SettingsMenuControl.svelte";
import GeneralSettings from "../settings/GeneralSettings.svelte";
import SettingsFormProvider from "../forms/SettingsFormProvider.svelte";
import ConnectionSettings from "../settings/ConnectionSettings.svelte";
import ThemeSettings from "../settings/ThemeSettings.svelte";
import DefaultActionsSettings from "../settings/DefaultActionsSettings.svelte";
import BehaviourSettings from "../settings/BehaviourSettings.svelte";
import ExternalToolsSettings from "../settings/ExternalToolsSettings.svelte";
import LicenseSettings from "../settings/LicenseSettings.svelte";
import { isProApp } from "../utility/proTools";
import { _t } from "../translations";
import CommandListTab from "./CommandListTab.svelte";
import DataGridSettings from "../settings/DataGridSettings.svelte";
import SQLEditorSettings from "../settings/SQLEditorSettings.svelte";
import AiSettingsTab from "../settings/AiSettingsTab.svelte";
import SettingsMenuControl from '../elements/SettingsMenuControl.svelte';
import GeneralSettings from '../settings/GeneralSettings.svelte';
import SettingsFormProvider from '../forms/SettingsFormProvider.svelte';
import ConnectionSettings from '../settings/ConnectionSettings.svelte';
import ThemeSettings from '../settings/ThemeSettings.svelte';
import DefaultActionsSettings from '../settings/DefaultActionsSettings.svelte';
import BehaviourSettings from '../settings/BehaviourSettings.svelte';
import ExternalToolsSettings from '../settings/ExternalToolsSettings.svelte';
import LicenseSettings from '../settings/LicenseSettings.svelte';
import { isProApp } from '../utility/proTools';
import { _t } from '../translations';
import CommandListTab from './CommandListTab.svelte';
import DataGridSettings from '../settings/DataGridSettings.svelte';
import SQLEditorSettings from '../settings/SQLEditorSettings.svelte';
import AiSettingsTab from '../settings/AiSettingsTab.svelte';
import hasPermission from '../utility/hasPermission';
export let selectedItem = 'general';
export let selectedItem = 'general';
const menuItems = [
{
label: _t('settings.general', { defaultMessage: 'General' }),
identifier: 'general',
component: GeneralSettings,
props: {},
testid: 'settings-general',
},
{
label: _t('settings.connection', { defaultMessage: 'Connection' }),
identifier: 'connection',
component: ConnectionSettings,
props: {},
testid: 'settings-connection',
},
{
label: _t('settings.dataGrid.title', { defaultMessage: 'Data grid' }),
identifier: 'data-grid',
component: DataGridSettings,
props: {},
testid: 'settings-data-grid',
},
{
label: _t('settings.sqlEditor.title', { defaultMessage: 'SQL Editor' }),
identifier: 'sql-editor',
component: SQLEditorSettings,
props: {},
testid: 'settings-sql-editor',
},
{
label: _t('settings.theme', { defaultMessage: 'Themes' }),
identifier: 'theme',
component: ThemeSettings,
props: {},
testid: 'settings-themes',
},
{
label: _t('settings.defaultActions', { defaultMessage: 'Default Actions' }),
identifier: 'default-actions',
component: DefaultActionsSettings,
props: {},
testid: 'settings-default-actions',
},
{
label: _t('settings.behaviour', { defaultMessage: 'Behaviour' }),
identifier: 'behaviour',
component: BehaviourSettings,
props: {},
testid: 'settings-behaviour',
},
{
label: _t('settings.externalTools', { defaultMessage: 'External Tools' }),
identifier: 'external-tools',
component: ExternalToolsSettings,
props: {},
testid: 'settings-external-tools',
},
{
label: _t('command.settings.shortcuts', { defaultMessage: 'Keyboard shortcuts' }),
identifier: 'shortcuts',
component: CommandListTab,
props: {},
testid: 'settings-shortcuts',
},
isProApp() && {
label: _t('settings.license', { defaultMessage: 'License' }),
identifier: 'license',
component: LicenseSettings,
props: {},
testid: 'settings-license',
},
isProApp() && {
label: _t('settings.AI', { defaultMessage: 'AI'}),
identifier: 'ai',
component: AiSettingsTab,
props: {},
testid: 'settings-ai',
},
];
const menuItems = [
{
label: _t('settings.general', { defaultMessage: 'General' }),
identifier: 'general',
component: GeneralSettings,
props: {},
testid: 'settings-general',
},
hasPermission('settings/change') && {
label: _t('settings.connection', { defaultMessage: 'Connection' }),
identifier: 'connection',
component: ConnectionSettings,
props: {},
testid: 'settings-connection',
},
hasPermission('settings/change') && {
label: _t('settings.dataGrid.title', { defaultMessage: 'Data grid' }),
identifier: 'data-grid',
component: DataGridSettings,
props: {},
testid: 'settings-data-grid',
},
hasPermission('settings/change') && {
label: _t('settings.sqlEditor.title', { defaultMessage: 'SQL Editor' }),
identifier: 'sql-editor',
component: SQLEditorSettings,
props: {},
testid: 'settings-sql-editor',
},
{
label: _t('settings.theme', { defaultMessage: 'Themes' }),
identifier: 'theme',
component: ThemeSettings,
props: {},
testid: 'settings-themes',
},
hasPermission('settings/change') && {
label: _t('settings.defaultActions', { defaultMessage: 'Default Actions' }),
identifier: 'default-actions',
component: DefaultActionsSettings,
props: {},
testid: 'settings-default-actions',
},
hasPermission('settings/change') && {
label: _t('settings.behaviour', { defaultMessage: 'Behaviour' }),
identifier: 'behaviour',
component: BehaviourSettings,
props: {},
testid: 'settings-behaviour',
},
hasPermission('settings/change') && {
label: _t('settings.externalTools', { defaultMessage: 'External Tools' }),
identifier: 'external-tools',
component: ExternalToolsSettings,
props: {},
testid: 'settings-external-tools',
},
hasPermission('settings/change') && {
label: _t('command.settings.shortcuts', { defaultMessage: 'Keyboard shortcuts' }),
identifier: 'shortcuts',
component: CommandListTab,
props: {},
testid: 'settings-shortcuts',
},
hasPermission('settings/change') &&
isProApp() && {
label: _t('settings.license', { defaultMessage: 'License' }),
identifier: 'license',
component: LicenseSettings,
props: {},
testid: 'settings-license',
},
hasPermission('settings/change') &&
isProApp() && {
label: _t('settings.AI', { defaultMessage: 'AI' }),
identifier: 'ai',
component: AiSettingsTab,
props: {},
testid: 'settings-ai',
},
];
</script>
<SettingsFormProvider>
<SettingsMenuControl
items={menuItems}
bind:value={selectedItem}
flex1={true}
flexColContainer={true}
scrollableContentContainer={true}
<SettingsMenuControl
items={menuItems}
bind:value={selectedItem}
flex1={true}
flexColContainer={true}
scrollableContentContainer={true}
/>
</SettingsFormProvider>
</SettingsFormProvider>
+11 -5
View File
@@ -155,24 +155,28 @@ export function getKeyTextFromEvent(e) {
return keyText;
}
export function getDatabasStatusMenu(dbid) {
export function getDatabasStatusMenu(dbid, driver = null) {
function callSchemalListChanged() {
apiCall('database-connections/dispatch-database-changed-event', { event: 'schema-list-changed', ...dbid });
}
return [
{
return _.compact([
driver?.supportsIncrementalAnalysis && {
text: _t('command.database.refreshIncremental', { defaultMessage: 'Refresh DB structure (incremental)' }),
onClick: () => {
apiCall('database-connections/sync-model', dbid);
callSchemalListChanged();
},
testid: 'DatabasStatusMenu_refreshIncremental',
},
{
text: _t('command.database.refreshFull', { defaultMessage: 'Refresh DB structure (full)' }),
text: driver?.supportsIncrementalAnalysis
? _t('command.database.refreshFull', { defaultMessage: 'Refresh DB structure (full)' })
: _t('command.database.refresh', { defaultMessage: 'Refresh DB structure' }),
onClick: () => {
apiCall('database-connections/sync-model', { ...dbid, isFullRefresh: true });
callSchemalListChanged();
},
testid: 'DatabasStatusMenu_refreshFull',
},
{
text: _t('command.database.reopenConnection', { defaultMessage: 'Reopen connection' }),
@@ -180,6 +184,7 @@ export function getDatabasStatusMenu(dbid) {
apiCall('database-connections/refresh', dbid);
callSchemalListChanged();
},
testid: 'DatabasStatusMenu_reopenConnection',
},
{
text: _t('command.database.disconnect', { defaultMessage: 'Disconnect' }),
@@ -188,6 +193,7 @@ export function getDatabasStatusMenu(dbid) {
if (electron) apiCall('database-connections/disconnect', dbid);
switchCurrentDatabase(null);
},
testid: 'DatabasStatusMenu_disconnect',
},
];
]);
}
@@ -14,6 +14,12 @@
component: TextCellViewNoWrap,
single: false,
},
{
type: 'table',
title: 'Table - Row',
component: TableCellView,
single: false,
},
{
type: 'json',
title: 'Json',
@@ -92,6 +98,7 @@
import JsonRowView from '../celldata/JsonRowView.svelte';
import MapCellView from '../celldata/MapCellView.svelte';
import PictureCellView from '../celldata/PictureCellView.svelte';
import TableCellView from '../celldata/TableCellView.svelte';
import TextCellViewNoWrap from '../celldata/TextCellViewNoWrap.svelte';
import TextCellViewWrap from '../celldata/TextCellViewWrap.svelte';
import ErrorInfo from '../elements/ErrorInfo.svelte';
@@ -143,7 +143,7 @@
}
function createRefreshDatabaseMenu() {
return getDatabasStatusMenu({ conid, database });
return getDatabasStatusMenu({ conid, database }, driver);
}
function handleFullRefreshDatabase() {
@@ -147,6 +147,7 @@ const driver = {
dialect,
engine: 'clickhouse@dbgate-plugin-clickhouse',
title: 'ClickHouse',
supportsIncrementalAnalysis: true,
showConnectionField: (field, values) => {
return ['databaseUrl', 'defaultDatabase', 'singleDatabase', 'isReadOnly', 'user', 'password'].includes(field);
},
@@ -6,6 +6,56 @@ const lineReader = require('line-reader');
let dbgateApi;
class StripUtf8BomTransform extends stream.Transform {
constructor(options) {
super(options);
this._checkedBOM = false;
this._pending = Buffer.alloc(0); // store initial bytes until we know if BOM is present
}
_transform(chunk, encoding, callback) {
if (this._checkedBOM) {
// We already handled BOM decision, just pass through
this.push(chunk);
return callback();
}
// Accumulate into pending until we can decide
this._pending = Buffer.concat([this._pending, chunk]);
if (this._pending.length < 3) {
// Still don't know if it's BOM or not (need at least 3 bytes)
return callback();
}
// Now we can check the first 3 bytes
const BOM = [0xef, 0xbb, 0xbf];
const hasBom = this._pending[0] === BOM[0] && this._pending[1] === BOM[1] && this._pending[2] === BOM[2];
if (hasBom) {
// Drop the BOM, push the rest
this.push(this._pending.slice(3));
} else {
// No BOM, push everything as-is
this.push(this._pending);
}
this._pending = Buffer.alloc(0);
this._checkedBOM = true;
callback();
}
_flush(callback) {
// Stream ended but we never had enough bytes to decide (length < 3)
if (!this._checkedBOM && this._pending.length > 0) {
// If it's less than 3 bytes, it can't be a UTF-8 BOM, so just pass it through
this.push(this._pending);
}
this._pending = Buffer.alloc(0);
callback();
}
}
function readFirstLine(file) {
return new Promise((resolve, reject) => {
lineReader.open(file, (err, reader) => {
@@ -95,7 +145,7 @@ async function reader({ fileName, encoding = 'utf-8', header = true, delimiter,
});
const fileStream = fs.createReadStream(downloadedFile, encoding);
const csvPrepare = new CsvPrepareStream({ header });
return [fileStream, csvStream, csvPrepare];
return [fileStream, new StripUtf8BomTransform(), csvStream, csvPrepare];
// fileStream.pipe(csvStream);
// csvStream.pipe(csvPrepare);
// return csvPrepare;
@@ -171,6 +171,7 @@ const driver = {
defaultPort: 1433,
defaultAuthTypeName: 'tedious',
supportsTransactions: true,
supportsIncrementalAnalysis: true,
// databaseUrlPlaceholder: 'e.g. server=localhost&authentication.type=default&authentication.type.user=myuser&authentication.type.password=pwd&options.database=mydb',
getNewObjectTemplates() {
@@ -184,6 +184,7 @@ const mysqlDriverBase = {
defaultAuthTypeName: 'hostPort',
defaultSocketPath: '/var/run/mysqld/mysqld.sock',
supportsTransactions: true,
supportsIncrementalAnalysis: true,
getNewObjectTemplates() {
return [
@@ -26,8 +26,11 @@ pg.types.setTypeParser(1184, 'text', val => val); // timestamp
pg.types.setTypeParser(20, 'text', val => {
const parsed = parseInt(val);
if (Number.isSafeInteger(parsed)) return parsed;
return BigInt(val);
return { $bigint: val };
}); // timestamp
pg.types.setTypeParser(1700, 'text', val => {
return { $decimal: val };
}); // numeric
function extractGeographyDate(value) {
try {
@@ -121,6 +121,7 @@ const dialect = {
const postgresDriverBase = {
...driverBase,
supportsTransactions: true,
supportsIncrementalAnalysis: true,
dumperClass: Dumper,
dialect,
// showConnectionField: (field, values) =>
@@ -48,6 +48,7 @@ const sqliteDriverBase = {
dialect,
readOnlySessions: true,
supportsTransactions: true,
supportsIncrementalAnalysis: true,
getQuerySplitterOptions: (usage) =>
usage == 'editor'
+2
View File
@@ -171,6 +171,7 @@
"command.database.databaseSearch": "Vyhledávání v databázi",
"command.database.disconnect": "Odpojit",
"command.database.export": "Exportovat databázi",
"command.database.refresh": "Obnovit strukturu DB",
"command.database.refreshFull": "Obnovit strukturu DB (úplně)",
"command.database.refreshIncremental": "Obnovit strukturu DB (inkrementálně)",
"command.database.reopenConnection": "Znovu otevřít připojení",
@@ -1198,6 +1199,7 @@
"settings.sqlEditor.sqlCommandsCase": "Velikost písmen SQL příkazů",
"settings.sqlEditor.title": "SQL editor",
"settings.tabGroup.showServerName": "Zobrazit název serveru vedle názvu databáze v záhlaví skupiny karet",
"settings.tabPreviewMode": "Režim náhledu karet",
"settings.theme": "Vzhled",
"settings.title": "Nastavení",
"settings.useNativeWindowTitle": "Použít nativní titulek okna",
+2
View File
@@ -171,6 +171,7 @@
"command.database.databaseSearch": "Datenbanksuche",
"command.database.disconnect": "Trennen",
"command.database.export": "Datenbank exportieren",
"command.database.refresh": "DB-Struktur aktualisieren",
"command.database.refreshFull": "DB-Struktur aktualisieren (vollständig)",
"command.database.refreshIncremental": "DB-Struktur aktualisieren (inkrementell)",
"command.database.reopenConnection": "Verbindung erneut öffnen",
@@ -1198,6 +1199,7 @@
"settings.sqlEditor.sqlCommandsCase": "Groß-/Kleinschreibung der SQL-Befehle",
"settings.sqlEditor.title": "SQL-Editor",
"settings.tabGroup.showServerName": "Servername neben Datenbankname im Titel der Tab-Gruppe anzeigen",
"settings.tabPreviewMode": "Tab-Vorschaumodus",
"settings.theme": "Designs",
"settings.title": "Einstellungen",
"settings.useNativeWindowTitle": "Nativen Fenstertitel verwenden",
+2
View File
@@ -167,6 +167,7 @@
"command.database.databaseSearch": "Database search",
"command.database.disconnect": "Disconnect",
"command.database.export": "Export database",
"command.database.refresh": "Refresh DB structure",
"command.database.refreshFull": "Refresh DB structure (full)",
"command.database.refreshIncremental": "Refresh DB structure (incremental)",
"command.database.reopenConnection": "Reopen connection",
@@ -1190,6 +1191,7 @@
"settings.sqlEditor.sqlCommandsCase": "SQL commands case",
"settings.sqlEditor.title": "SQL Editor",
"settings.tabGroup.showServerName": "Show server name alongside database name in title of the tab group",
"settings.tabPreviewMode": "Tab Preview Mode",
"settings.theme": "Themes",
"settings.useNativeWindowTitle": "Use native window title",
"settings.useSystemNativeMenu": "Use system native menu",
+2
View File
@@ -171,6 +171,7 @@
"command.database.databaseSearch": "Búsqueda en base de datos",
"command.database.disconnect": "Desconectar",
"command.database.export": "Exportar base de datos",
"command.database.refresh": "Refrescar estructura de BD",
"command.database.refreshFull": "Refrescar estructura de BD (completa)",
"command.database.refreshIncremental": "Refrescar estructura de BD (incremental)",
"command.database.reopenConnection": "Reabrir conexión",
@@ -1198,6 +1199,7 @@
"settings.sqlEditor.sqlCommandsCase": "Mayúsculas/minúsculas de comandos SQL",
"settings.sqlEditor.title": "Editor SQL",
"settings.tabGroup.showServerName": "Mostrar nombre del servidor junto con nombre de base de datos en el título del grupo de pestañas",
"settings.tabPreviewMode": "Modo de vista previa de pestaña",
"settings.theme": "Temas",
"settings.title": "Configuración",
"settings.useNativeWindowTitle": "Usar título de ventana nativo",
+2
View File
@@ -171,6 +171,7 @@
"command.database.databaseSearch": "Recherche dans la base de données",
"command.database.disconnect": "Déconnecter",
"command.database.export": "Exporter la base de données",
"command.database.refresh": "Rafraîchir la structure de BD",
"command.database.refreshFull": "Rafraîchir la structure de BD (complète)",
"command.database.refreshIncremental": "Rafraîchir la structure de BD (incrémentale)",
"command.database.reopenConnection": "Rouvrir la connexion",
@@ -1198,6 +1199,7 @@
"settings.sqlEditor.sqlCommandsCase": "Casse des commandes SQL",
"settings.sqlEditor.title": "Éditeur SQL",
"settings.tabGroup.showServerName": "Afficher le nom du serveur à côté du nom de la base de données dans le titre du groupe d'onglets",
"settings.tabPreviewMode": "Mode aperçu d'onglet",
"settings.theme": "Thèmes",
"settings.title": "Paramètres",
"settings.useNativeWindowTitle": "Utiliser le titre de fenêtre natif",
+2
View File
@@ -171,6 +171,7 @@
"command.database.databaseSearch": "Ricerca database",
"command.database.disconnect": "Disconnetti",
"command.database.export": "Esporta database",
"command.database.refresh": "Aggiorna struttura DB",
"command.database.refreshFull": "Aggiorna struttura DB (completo)",
"command.database.refreshIncremental": "Aggiorna struttura DB (incrementale)",
"command.database.reopenConnection": "Riapri connessione",
@@ -1198,6 +1199,7 @@
"settings.sqlEditor.sqlCommandsCase": "Maiuscole/minuscole comandi SQL",
"settings.sqlEditor.title": "Editor SQL",
"settings.tabGroup.showServerName": "Mostra nome server accanto al nome database nel titolo del gruppo schede",
"settings.tabPreviewMode": "Modalità anteprima scheda",
"settings.theme": "Temi",
"settings.title": "Impostazioni",
"settings.useNativeWindowTitle": "Usa titolo finestra nativo",
+2
View File
@@ -171,6 +171,7 @@
"command.database.databaseSearch": "データベース検索",
"command.database.disconnect": "切断",
"command.database.export": "データベースをエクスポート",
"command.database.refresh": "DB構造を更新",
"command.database.refreshFull": "DB構造を更新(フル)",
"command.database.refreshIncremental": "DB構造を更新(増分)",
"command.database.reopenConnection": "接続を再度開く",
@@ -1198,6 +1199,7 @@
"settings.sqlEditor.sqlCommandsCase": "SQLコマンドの大文字小文字",
"settings.sqlEditor.title": "SQLエディター",
"settings.tabGroup.showServerName": "タブグループのタイトルにデータベース名と並んでサーバー名を表示",
"settings.tabPreviewMode": "タブプレビューモード",
"settings.theme": "テーマ",
"settings.title": "設定",
"settings.useNativeWindowTitle": "ネイティブウィンドウタイトルを使用",
+2
View File
@@ -171,6 +171,7 @@
"command.database.databaseSearch": "Pesquisar banco de dados",
"command.database.disconnect": "Desconectar",
"command.database.export": "Exportar banco de dados",
"command.database.refresh": "Atualizar estrutura do BD",
"command.database.refreshFull": "Atualizar estrutura do BD (completa)",
"command.database.refreshIncremental": "Atualizar estrutura do BD (incremental)",
"command.database.reopenConnection": "Reabrir conexão",
@@ -1198,6 +1199,7 @@
"settings.sqlEditor.sqlCommandsCase": "Maiúsculas/minúsculas de comandos SQL",
"settings.sqlEditor.title": "Editor SQL",
"settings.tabGroup.showServerName": "Mostrar nome do servidor junto ao nome do banco de dados no título do grupo de abas",
"settings.tabPreviewMode": "Modo de visualização de aba",
"settings.theme": "Temas",
"settings.title": "Configurações",
"settings.useNativeWindowTitle": "Usar título de janela nativo",
+2
View File
@@ -171,6 +171,7 @@
"command.database.databaseSearch": "Hľadanie v databáze",
"command.database.disconnect": "Odpojiť",
"command.database.export": "Exportovať databázu",
"command.database.refresh": "Obnoviť štruktúru DB",
"command.database.refreshFull": "Obnoviť štruktúru DB (úplne)",
"command.database.refreshIncremental": "Obnoviť štruktúru DB (inkrementálne)",
"command.database.reopenConnection": "Znovu otvoriť pripojenie",
@@ -1198,6 +1199,7 @@
"settings.sqlEditor.sqlCommandsCase": "Veľkosť písmen",
"settings.sqlEditor.title": "SQL editor",
"settings.tabGroup.showServerName": "Zobraziť názov servera vedľa názvu databázy v názve skupiny kariet",
"settings.tabPreviewMode": "Režim náhľadu kariet",
"settings.theme": "Vzhľad",
"settings.title": "Nastavenia",
"settings.useNativeWindowTitle": "Použiť natívne menu",
+2
View File
@@ -171,6 +171,7 @@
"command.database.databaseSearch": "数据库搜索",
"command.database.disconnect": "断开连接",
"command.database.export": "导出数据库",
"command.database.refresh": "刷新数据库结构",
"command.database.refreshFull": "刷新数据库结构(完整)",
"command.database.refreshIncremental": "刷新数据库结构(增量)",
"command.database.reopenConnection": "重新打开连接",
@@ -1198,6 +1199,7 @@
"settings.sqlEditor.sqlCommandsCase": "SQL命令大小写",
"settings.sqlEditor.title": "SQL 编辑器",
"settings.tabGroup.showServerName": "在标签页组标题中显示服务器名称和数据库名称",
"settings.tabPreviewMode": "标签页预览模式",
"settings.theme": "主题",
"settings.title": "设置",
"settings.useNativeWindowTitle": "使用原生窗口标题",
+1 -1
View File
@@ -7,7 +7,7 @@ checkout-and-merge-pro:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: ca69c4857d7d93c4b066018e8a9a0a0ece2300e7
ref: ae1fcf6e61c6f7dfbb21005daa259c68e899a80a
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro