Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2a88ed38c4 | |||
| 6ebee92542 | |||
| 7024e4b40d | |||
| bc2e27d7da | |||
| 142ebe3d27 | |||
| 38c25cae74 | |||
| 190c610466 | |||
| c6d3fc06a3 | |||
| d220525ac7 | |||
| 5e4a631ff2 | |||
| 9099ce42b9 | |||
| df226fea22 | |||
| e67ee4ffdb | |||
| 2baf975847 | |||
| c1672ebc8e | |||
| bbbd291065 | |||
| 0a3c1efdd4 | |||
| 89121a2608 | |||
| 23cf264d4d | |||
| 0c15e524d7 | |||
| b0b5b1c30d | |||
| 30b4c85c5a | |||
| 910f9cadfe | |||
| 6de37ebd16 | |||
| de57c4e87e | |||
| b85cf66490 | |||
| 8e638ea9a6 | |||
| b4849ec495 | |||
| 09c12d52ac | |||
| db6a2ddd7e | |||
| 12ef9463ab | |||
| a557ad177e | |||
| 78e838f2f0 | |||
| c1f216c7c7 | |||
| b75ff99e4c | |||
| 780dd8ade9 | |||
| e1c10b7653 | |||
| be9505f8fe | |||
| d6bcd4f94f | |||
| 7d2196f4c3 | |||
| 0539174317 | |||
| b4b52e12d5 | |||
| f2e0b1cfa2 | |||
| 8020e2a263 | |||
| 6112d9b1b0 | |||
| 4a1fbcbd31 | |||
| 0218bb4990 | |||
| 3769c03565 | |||
| d96cb10476 | |||
| b6b6123434 | |||
| b40877fcc1 | |||
| af5ae29b73 | |||
| 082fceebbe | |||
| f1dab80a06 | |||
| cbf2fac2cf | |||
| 4564bd7180 |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "6.7.2-premium-beta.4",
|
||||
"version": "6.7.3",
|
||||
"name": "dbgate-all",
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -360,6 +360,12 @@ module.exports = {
|
||||
"columnName": "value",
|
||||
"dataType": "varchar(1000)",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"pureName": "config",
|
||||
"columnName": "valueType",
|
||||
"dataType": "varchar(50)",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"foreignKeys": [],
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Vendored
+1
@@ -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);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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' }),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "ネイティブウィンドウタイトルを使用",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "使用原生窗口标题",
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user