Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 377cd64556 | |||
| 955ca99cf3 | |||
| 98f5bb4124 | |||
| b3943f005d | |||
| 8d4178b984 | |||
| 52dce7dfd3 | |||
| 1b5646f526 | |||
| 12e6afbaad | |||
| 7579f6e42a | |||
| 4d61c74a8b | |||
| 85b7e3ebe3 | |||
| 851d2e9151 | |||
| b3130225b5 | |||
| 65512defed | |||
| 3b1c8748f1 | |||
| aba660eddb | |||
| 137eac7dbf | |||
| fdbd08f511 | |||
| ace1cec1f6 | |||
| 0c15e524d7 | |||
| b0b5b1c30d | |||
| 30b4c85c5a | |||
| 910f9cadfe | |||
| 6de37ebd16 | |||
| de57c4e87e | |||
| b85cf66490 | |||
| 8e638ea9a6 | |||
| b4849ec495 | |||
| 09c12d52ac | |||
| db6a2ddd7e | |||
| 12ef9463ab | |||
| fa5fda0c3b | |||
| 251609e274 | |||
| a557ad177e | |||
| c0287e49d8 | |||
| 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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -26,13 +26,15 @@ function pickImportantTableInfo(engine, table) {
|
||||
.map(props =>
|
||||
_.omitBy(props, (v, k) => k == 'defaultValue' && v == 'NULL' && engine.setNullDefaultInsteadOfDrop)
|
||||
),
|
||||
// foreignKeys: table.foreignKeys
|
||||
// .sort((a, b) => a.refTableName.localeCompare(b.refTableName))
|
||||
// .map(fk => ({
|
||||
// constraintType: fk.constraintType,
|
||||
// refTableName: fk.refTableName,
|
||||
// columns: fk.columns.map(col => ({ columnName: col.columnName, refColumnName: col.refColumnName })),
|
||||
// })),
|
||||
|
||||
// TODO:
|
||||
foreignKeys: table.foreignKeys
|
||||
.sort((a, b) => a.refTableName.localeCompare(b.refTableName))
|
||||
.map(fk => ({
|
||||
constraintType: fk.constraintType,
|
||||
refTableName: fk.refTableName,
|
||||
columns: fk.columns.map(col => ({ columnName: col.columnName, refColumnName: col.refColumnName })),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -103,6 +105,7 @@ async function testTableDiff(engine, conn, driver, mangle, changedTable = 't1')
|
||||
|
||||
await driver.script(conn, sql);
|
||||
|
||||
// TODO:
|
||||
// if (!engine.skipIncrementalAnalysis) {
|
||||
// const structure2RealIncremental = await driver.analyseIncremental(conn, structure1Source);
|
||||
// checkTableStructure(engine, tget(structure2RealIncremental), tget(structure2));
|
||||
@@ -116,6 +119,7 @@ async function testTableDiff(engine, conn, driver, mangle, changedTable = 't1')
|
||||
|
||||
const TESTED_COLUMNS = ['col_pk', 'col_std', 'col_def', 'col_fk', 'col_ref', 'col_idx', 'col_uq'];
|
||||
// const TESTED_COLUMNS = ['col_pk'];
|
||||
// const TESTED_COLUMNS = ['col_fk'];
|
||||
// const TESTED_COLUMNS = ['col_idx'];
|
||||
// const TESTED_COLUMNS = ['col_def'];
|
||||
// const TESTED_COLUMNS = ['col_std'];
|
||||
@@ -179,11 +183,25 @@ describe('Alter table', () => {
|
||||
)(
|
||||
'Drop column - %s - %s',
|
||||
testWrapper(async (conn, driver, column, engine) => {
|
||||
await testTableDiff(engine, conn, driver, tbl => (tbl.columns = tbl.columns.filter(x => x.columnName != column)));
|
||||
await testTableDiff(engine, conn, driver,
|
||||
tbl => {
|
||||
tbl.columns = tbl.columns.filter(x => x.columnName != column);
|
||||
tbl.foreignKeys = tbl.foreignKeys
|
||||
.map(fk => ({
|
||||
...fk,
|
||||
columns: fk.columns.filter(col => col.columnName != column)
|
||||
}))
|
||||
.filter(fk => fk.columns.length > 0);
|
||||
}
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
test.each(createEnginesColumnsSource(engines.filter(x => !x.skipNullable && !x.skipChangeNullability)))(
|
||||
test.each(
|
||||
createEnginesColumnsSource(engines.filter(x => !x.skipNullability && !x.skipChangeNullability)).filter(
|
||||
([_label, col]) => !col.endsWith('_pk')
|
||||
)
|
||||
)(
|
||||
'Change nullability - %s - %s',
|
||||
testWrapper(async (conn, driver, column, engine) => {
|
||||
await testTableDiff(
|
||||
@@ -202,7 +220,11 @@ describe('Alter table', () => {
|
||||
engine,
|
||||
conn,
|
||||
driver,
|
||||
tbl => (tbl.columns = tbl.columns.map(x => (x.columnName == column ? { ...x, columnName: 'col_renamed' } : x)))
|
||||
tbl => {
|
||||
tbl.columns = tbl.columns.map(x => (x.columnName == column ? { ...x, columnName: 'col_renamed' } : x));
|
||||
tbl.foreignKeys = tbl.foreignKeys.map(fk => ({...fk, columns: fk.columns.map(col => col.columnName == column ? { ...col, columnName: 'col_renamed' } : col)
|
||||
}));
|
||||
}
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -44,7 +44,7 @@ services:
|
||||
# - 15942:9042
|
||||
#
|
||||
# clickhouse:
|
||||
# image: bitnami/clickhouse:24.8.4
|
||||
# image: bitnamilegacy/clickhouse:24.8.4
|
||||
# restart: always
|
||||
# ports:
|
||||
# - 15005:8123
|
||||
|
||||
@@ -22,7 +22,9 @@ async function connect(engine, database) {
|
||||
if (engine.generateDbFile) {
|
||||
const conn = await driver.connect({
|
||||
...connection,
|
||||
databaseFile: (engine.databaseFileLocationOnServer ?? 'dbtemp/') + database,
|
||||
databaseFile:
|
||||
(engine.databaseFileLocationOnServer ?? (process.env.CITEST ? 'dbtemp/' : 'integration-tests/dbtemp/')) +
|
||||
database,
|
||||
});
|
||||
return conn;
|
||||
} else {
|
||||
|
||||
+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": [],
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -26,6 +26,7 @@ import _isDate from 'lodash/isDate';
|
||||
import _isArray from 'lodash/isArray';
|
||||
import _isPlainObject from 'lodash/isPlainObject';
|
||||
import _keys from 'lodash/keys';
|
||||
import _cloneDeep from 'lodash/cloneDeep';
|
||||
import uuidv1 from 'uuid/v1';
|
||||
|
||||
export class SqlDumper implements AlterProcessor {
|
||||
@@ -666,6 +667,68 @@ export class SqlDumper implements AlterProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
sanitizeTableConstraints(table: TableInfo): TableInfo {
|
||||
// Create a deep copy of the table
|
||||
const sanitized = _cloneDeep(table);
|
||||
|
||||
// Get the set of existing column names
|
||||
const existingColumns = new Set(sanitized.columns.map(col => col.columnName));
|
||||
|
||||
// Filter primary key columns to only include existing columns
|
||||
if (sanitized.primaryKey) {
|
||||
const validPkColumns = sanitized.primaryKey.columns.filter(col => existingColumns.has(col.columnName));
|
||||
if (validPkColumns.length === 0) {
|
||||
// If no valid columns remain, remove the primary key entirely
|
||||
sanitized.primaryKey = null;
|
||||
} else if (validPkColumns.length < sanitized.primaryKey.columns.length) {
|
||||
// Update primary key with only valid columns
|
||||
sanitized.primaryKey = {
|
||||
...sanitized.primaryKey,
|
||||
columns: validPkColumns
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Filter sorting key columns to only include existing columns
|
||||
if (sanitized.sortingKey) {
|
||||
const validSkColumns = sanitized.sortingKey.columns.filter(col => existingColumns.has(col.columnName));
|
||||
if (validSkColumns.length === 0) {
|
||||
sanitized.sortingKey = null;
|
||||
} else if (validSkColumns.length < sanitized.sortingKey.columns.length) {
|
||||
sanitized.sortingKey = {
|
||||
...sanitized.sortingKey,
|
||||
columns: validSkColumns
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Filter foreign keys to only include those with all columns present
|
||||
if (sanitized.foreignKeys) {
|
||||
sanitized.foreignKeys = sanitized.foreignKeys.filter(fk =>
|
||||
fk.columns.every(col => existingColumns.has(col.columnName))
|
||||
);
|
||||
}
|
||||
|
||||
// Filter indexes to only include those with all columns present
|
||||
if (sanitized.indexes) {
|
||||
sanitized.indexes = sanitized.indexes.filter(idx =>
|
||||
idx.columns.every(col => existingColumns.has(col.columnName))
|
||||
);
|
||||
}
|
||||
|
||||
// Filter unique constraints to only include those with all columns present
|
||||
if (sanitized.uniques) {
|
||||
sanitized.uniques = sanitized.uniques.filter(uq =>
|
||||
uq.columns.every(col => existingColumns.has(col.columnName))
|
||||
);
|
||||
}
|
||||
|
||||
// Filter dependencies (references from other tables) - these should remain as-is
|
||||
// since they don't affect the CREATE TABLE statement for this table
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
recreateTable(oldTable: TableInfo, newTable: TableInfo) {
|
||||
if (!oldTable.pairingId || !newTable.pairingId || oldTable.pairingId != newTable.pairingId) {
|
||||
throw new Error('Recreate is not possible: oldTable.paringId != newTable.paringId');
|
||||
@@ -680,48 +743,51 @@ export class SqlDumper implements AlterProcessor {
|
||||
}))
|
||||
.filter(x => x.newcol);
|
||||
|
||||
// Create a sanitized version of newTable with constraints that only reference existing columns
|
||||
const sanitizedNewTable = this.sanitizeTableConstraints(newTable);
|
||||
|
||||
if (this.driver.supportsTransactions) {
|
||||
this.dropConstraints(oldTable, true);
|
||||
this.renameTable(oldTable, tmpTable);
|
||||
|
||||
this.createTable(newTable);
|
||||
this.createTable(sanitizedNewTable);
|
||||
|
||||
const autoinc = newTable.columns.find(x => x.autoIncrement);
|
||||
const autoinc = sanitizedNewTable.columns.find(x => x.autoIncrement);
|
||||
if (autoinc) {
|
||||
this.allowIdentityInsert(newTable, true);
|
||||
this.allowIdentityInsert(sanitizedNewTable, true);
|
||||
}
|
||||
|
||||
this.putCmd(
|
||||
'^insert ^into %f (%,i) select %,i ^from %f',
|
||||
newTable,
|
||||
sanitizedNewTable,
|
||||
columnPairs.map(x => x.newcol.columnName),
|
||||
columnPairs.map(x => x.oldcol.columnName),
|
||||
{ ...oldTable, pureName: tmpTable }
|
||||
);
|
||||
|
||||
if (autoinc) {
|
||||
this.allowIdentityInsert(newTable, false);
|
||||
this.allowIdentityInsert(sanitizedNewTable, false);
|
||||
}
|
||||
|
||||
if (this.dialect.dropForeignKey) {
|
||||
newTable.dependencies.forEach(cnt => this.createConstraint(cnt));
|
||||
sanitizedNewTable.dependencies.forEach(cnt => this.createConstraint(cnt));
|
||||
}
|
||||
|
||||
this.dropTable({ ...oldTable, pureName: tmpTable });
|
||||
} else {
|
||||
// we have to preserve old table as long as possible
|
||||
this.createTable({ ...newTable, pureName: tmpTable });
|
||||
this.createTable({ ...sanitizedNewTable, pureName: tmpTable });
|
||||
|
||||
this.putCmd(
|
||||
'^insert ^into %f (%,i) select %,s ^from %f',
|
||||
{ ...newTable, pureName: tmpTable },
|
||||
{ ...sanitizedNewTable, pureName: tmpTable },
|
||||
columnPairs.map(x => x.newcol.columnName),
|
||||
columnPairs.map(x => x.oldcol.columnName),
|
||||
oldTable
|
||||
);
|
||||
|
||||
this.dropTable(oldTable);
|
||||
this.renameTable({ ...newTable, pureName: tmpTable }, newTable.pureName);
|
||||
this.renameTable({ ...sanitizedNewTable, pureName: tmpTable }, newTable.pureName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+121
-45
@@ -91,8 +91,8 @@ interface AlterOperation_RenameConstraint {
|
||||
}
|
||||
interface AlterOperation_RecreateTable {
|
||||
operationType: 'recreateTable';
|
||||
table: TableInfo;
|
||||
operations: AlterOperation[];
|
||||
oldTable: TableInfo;
|
||||
newTable: TableInfo;
|
||||
}
|
||||
interface AlterOperation_FillPreloadedRows {
|
||||
operationType: 'fillPreloadedRows';
|
||||
@@ -249,11 +249,11 @@ export class AlterPlan {
|
||||
});
|
||||
}
|
||||
|
||||
recreateTable(table: TableInfo, operations: AlterOperation[]) {
|
||||
recreateTable(oldTable: TableInfo, newTable: TableInfo) {
|
||||
this.operations.push({
|
||||
operationType: 'recreateTable',
|
||||
table,
|
||||
operations,
|
||||
oldTable,
|
||||
newTable,
|
||||
});
|
||||
this.recreates.tables += 1;
|
||||
}
|
||||
@@ -337,7 +337,13 @@ export class AlterPlan {
|
||||
return opRes;
|
||||
}),
|
||||
op,
|
||||
];
|
||||
].filter(op => {
|
||||
// filter duplicated drops
|
||||
const existingDrop = this.operations.find(
|
||||
o => o.operationType == 'dropConstraint' && o.oldObject === op['oldObject']
|
||||
);
|
||||
return existingDrop == null;
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -498,53 +504,121 @@ export class AlterPlan {
|
||||
return [];
|
||||
}
|
||||
|
||||
const table = this.wholeNewDb.tables.find(
|
||||
const oldTable = this.wholeOldDb.tables.find(
|
||||
x => x.pureName == op[objectField].pureName && x.schemaName == op[objectField].schemaName
|
||||
);
|
||||
const newTable = this.wholeNewDb.tables.find(
|
||||
x => x.pureName == op[objectField].pureName && x.schemaName == op[objectField].schemaName
|
||||
);
|
||||
this.recreates.tables += 1;
|
||||
return [
|
||||
{
|
||||
operationType: 'recreateTable',
|
||||
table,
|
||||
operations: [op],
|
||||
oldTable,
|
||||
newTable,
|
||||
// operations: [op],
|
||||
},
|
||||
];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_groupTableRecreations(): AlterOperation[] {
|
||||
const res = [];
|
||||
const recreates = {};
|
||||
_removeRecreatedTableAlters(): AlterOperation[] {
|
||||
const res: AlterOperation[] = [];
|
||||
const recreates = new Set<string>();
|
||||
for (const op of this.operations) {
|
||||
if (op.operationType == 'recreateTable' && op.table) {
|
||||
const existingRecreate = recreates[`${op.table.schemaName}||${op.table.pureName}`];
|
||||
if (existingRecreate) {
|
||||
existingRecreate.operations.push(...op.operations);
|
||||
} else {
|
||||
const recreate = {
|
||||
...op,
|
||||
operations: [...op.operations],
|
||||
};
|
||||
res.push(recreate);
|
||||
recreates[`${op.table.schemaName}||${op.table.pureName}`] = recreate;
|
||||
}
|
||||
} else {
|
||||
// @ts-ignore
|
||||
const oldObject: TableInfo = op.oldObject || op.object;
|
||||
if (oldObject) {
|
||||
const recreated = recreates[`${oldObject.schemaName}||${oldObject.pureName}`];
|
||||
if (recreated) {
|
||||
recreated.operations.push(op);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
res.push(op);
|
||||
if (op.operationType == 'recreateTable' && op.oldTable && op.newTable) {
|
||||
const key = `${op.oldTable.schemaName}||${op.oldTable.pureName}`;
|
||||
recreates.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
for (const op of this.operations) {
|
||||
switch (op.operationType) {
|
||||
case 'createColumn':
|
||||
case 'createConstraint':
|
||||
{
|
||||
const key = `${op.newObject.schemaName}||${op.newObject.pureName}`;
|
||||
if (recreates.has(key)) {
|
||||
// skip create inside recreated table
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'dropColumn':
|
||||
case 'dropConstraint':
|
||||
case 'changeColumn':
|
||||
{
|
||||
const key = `${op.oldObject.schemaName}||${op.oldObject.pureName}`;
|
||||
if (recreates.has(key)) {
|
||||
// skip drop/change inside recreated table
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'renameColumn':
|
||||
{
|
||||
const key = `${op.object.schemaName}||${op.object.pureName}`;
|
||||
if (recreates.has(key)) {
|
||||
// skip rename inside recreated table
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
res.push(op);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
_groupTableRecreations(): AlterOperation[] {
|
||||
const res = [];
|
||||
const recreates = new Set<string>();
|
||||
for (const op of this.operations) {
|
||||
if (op.operationType == 'recreateTable' && op.oldTable && op.newTable) {
|
||||
const key = `${op.oldTable.schemaName}||${op.oldTable.pureName}`;
|
||||
if (recreates.has(key)) {
|
||||
// prevent duplicate recreates
|
||||
continue;
|
||||
}
|
||||
recreates.add(key);
|
||||
}
|
||||
|
||||
res.push(op);
|
||||
}
|
||||
return res;
|
||||
|
||||
// const res = [];
|
||||
// const recreates = {};
|
||||
// for (const op of this.operations) {
|
||||
// if (op.operationType == 'recreateTable' && op.table) {
|
||||
// const existingRecreate = recreates[`${op.table.schemaName}||${op.table.pureName}`];
|
||||
// if (existingRecreate) {
|
||||
// existingRecreate.operations.push(...op.operations);
|
||||
// } else {
|
||||
// const recreate = {
|
||||
// ...op,
|
||||
// operations: [...op.operations],
|
||||
// };
|
||||
// res.push(recreate);
|
||||
// recreates[`${op.table.schemaName}||${op.table.pureName}`] = recreate;
|
||||
// }
|
||||
// } else {
|
||||
// // @ts-ignore
|
||||
// const oldObject: TableInfo = op.oldObject || op.object;
|
||||
// if (oldObject) {
|
||||
// const recreated = recreates[`${oldObject.schemaName}||${oldObject.pureName}`];
|
||||
// if (recreated) {
|
||||
// recreated.operations.push(op);
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
// res.push(op);
|
||||
// }
|
||||
// }
|
||||
// return res;
|
||||
}
|
||||
|
||||
_moveForeignKeysToLast(): AlterOperation[] {
|
||||
if (!this.dialect.createForeignKey) {
|
||||
return this.operations;
|
||||
@@ -611,6 +685,8 @@ export class AlterPlan {
|
||||
|
||||
// console.log('*****************OPERATIONS3', this.operations);
|
||||
|
||||
this.operations = this._removeRecreatedTableAlters();
|
||||
|
||||
this.operations = this._moveForeignKeysToLast();
|
||||
|
||||
// console.log('*****************OPERATIONS4', this.operations);
|
||||
@@ -673,16 +749,16 @@ export function runAlterOperation(op: AlterOperation, processor: AlterProcessor)
|
||||
break;
|
||||
case 'recreateTable':
|
||||
{
|
||||
const oldTable = generateTablePairingId(op.table);
|
||||
const newTable = _.cloneDeep(oldTable);
|
||||
const newDb = DatabaseAnalyser.createEmptyStructure();
|
||||
newDb.tables.push(newTable);
|
||||
// console.log('////////////////////////////newTable1', newTable);
|
||||
op.operations.forEach(child => runAlterOperation(child, new DatabaseInfoAlterProcessor(newDb)));
|
||||
// console.log('////////////////////////////op.operations', op.operations);
|
||||
// console.log('////////////////////////////op.table', op.table);
|
||||
// console.log('////////////////////////////newTable2', newTable);
|
||||
processor.recreateTable(oldTable, newTable);
|
||||
// const oldTable = generateTablePairingId(op.table);
|
||||
// const newTable = _.cloneDeep(oldTable);
|
||||
// const newDb = DatabaseAnalyser.createEmptyStructure();
|
||||
// newDb.tables.push(newTable);
|
||||
// // console.log('////////////////////////////newTable1', newTable);
|
||||
// op.operations.forEach(child => runAlterOperation(child, new DatabaseInfoAlterProcessor(newDb)));
|
||||
// // console.log('////////////////////////////op.operations', op.operations);
|
||||
// // console.log('////////////////////////////op.table', op.table);
|
||||
// // console.log('////////////////////////////newTable2', newTable);
|
||||
processor.recreateTable(op.oldTable, op.newTable);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -256,7 +277,7 @@ export function stringifyCellValue(
|
||||
// return { value: '0x' + arrayToHexString(value.data), gridStyle: 'valueCellStyle' };
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
if (editorTypes?.parseObjectIdAsDollar) {
|
||||
if (value?.$oid) {
|
||||
switch (intent) {
|
||||
@@ -270,13 +291,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 +372,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() };
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
];
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -46,6 +46,8 @@ const dialect = {
|
||||
dropReferencesWhenDropTable: false,
|
||||
requireStandaloneSelectForScopeIdentity: true,
|
||||
|
||||
dropColumnDependencies: ['dependencies'],
|
||||
|
||||
columnProperties: {
|
||||
columnComment: true,
|
||||
isUnsigned: true,
|
||||
@@ -184,6 +186,7 @@ const mysqlDriverBase = {
|
||||
defaultAuthTypeName: 'hostPort',
|
||||
defaultSocketPath: '/var/run/mysqld/mysqld.sock',
|
||||
supportsTransactions: true,
|
||||
supportsIncrementalAnalysis: true,
|
||||
|
||||
getNewObjectTemplates() {
|
||||
return [
|
||||
|
||||
@@ -64,6 +64,21 @@ class Dumper extends SqlDumper {
|
||||
this.putCmd('^alter ^table %f ^rename ^column %i ^to %i', column, column.columnName, newcol);
|
||||
}
|
||||
|
||||
createForeignKeyFore(fk) {
|
||||
if (fk.constraintName != null && !this.dialect.anonymousForeignKey) {
|
||||
this.put('^constraint %i ', fk.constraintName);
|
||||
}
|
||||
this.put(
|
||||
'^foreign ^key (%,i) ^references %f (%,i)',
|
||||
fk.columns.map(x => x.columnName),
|
||||
{ schemaName: fk.refSchemaName, pureName: fk.refTableName },
|
||||
fk.columns.map(x => x.refColumnName)
|
||||
);
|
||||
if (fk.deleteAction && fk.deleteAction.toUpperCase() !== 'NO ACTION') {
|
||||
this.put(' ^on ^delete %k', fk.deleteAction);
|
||||
}
|
||||
}
|
||||
|
||||
// dropTable(obj, options = {}) {
|
||||
// this.put('^drop ^table');
|
||||
// if (options.testIfExists) this.put(' ^if ^exists');
|
||||
|
||||
@@ -121,6 +121,7 @@ const dialect = {
|
||||
const postgresDriverBase = {
|
||||
...driverBase,
|
||||
supportsTransactions: true,
|
||||
supportsIncrementalAnalysis: true,
|
||||
dumperClass: Dumper,
|
||||
dialect,
|
||||
// showConnectionField: (field, values) =>
|
||||
|
||||
@@ -129,6 +129,7 @@ class Analyser extends DatabaseAnalyser {
|
||||
updateAction: fkcol.on_update,
|
||||
deleteAction: fkcol.on_delete,
|
||||
constraintName: `FK_${tableName}_${fkcol.id}`,
|
||||
constraintType: 'foreignKey',
|
||||
};
|
||||
return fk;
|
||||
});
|
||||
|
||||
@@ -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