Compare commits

..

84 Commits

Author SHA1 Message Date
Stela Augustinova c6d3fc06a3 Add support for deserializing decimal type 2025-12-08 13:57:10 +01:00
Stela Augustinova e67ee4ffdb Add support for PostgreSQL decimal type in filter and grid utilities 2025-12-08 12:52:47 +01:00
Stela Augustinova 2baf975847 Added PostgreSQL decimal type in DataGridCell component 2025-12-05 13:14:14 +01:00
Stela Augustinova c1672ebc8e Handling decimal values in putValue method 2025-12-05 13:13:56 +01:00
Stela Augustinova bbbd291065 Formatting decimal values in stringifyCellValue function 2025-12-05 13:13:29 +01:00
Stela Augustinova 0a3c1efdd4 Add support for parsing bigint and decimal types in PostgreSQL driver 2025-12-05 13:13:08 +01:00
Jan Prochazka 0c15e524d7 changelog 2025-12-03 18:32:14 +01:00
Jan Prochazka b0b5b1c30d v6.7.3 2025-12-03 18:22:02 +01:00
Jan Prochazka 30b4c85c5a better formating 2025-12-03 18:21:04 +01:00
Jan Prochazka 910f9cadfe v6.7.3-premium-beta.1 2025-12-03 17:37:20 +01:00
Jan Prochazka 6de37ebd16 cypress mocha reporter 2025-12-03 17:33:37 +01:00
Jan Prochazka de57c4e87e Skip tests with AI API calls 2025-12-03 17:14:51 +01:00
Jan Prochazka b85cf66490 Merge branch 'feature/pgsql-droptable-fix' 2025-12-03 17:06:45 +01:00
Jan Prochazka 8e638ea9a6 fix 2025-12-03 17:00:04 +01:00
Jan Prochazka b4849ec495 fix problem with diff analysing after drop object 2025-12-03 16:47:50 +01:00
Jan Prochazka 09c12d52ac more tests 2025-12-03 16:39:42 +01:00
Jan Prochazka db6a2ddd7e Merge pull request #1286 from dbgate/feature/custom-thousands-separator
Feature/custom thousands separator
2025-12-03 15:40:04 +01:00
Jan Prochazka 12ef9463ab Merge pull request #1284 from dbgate/feature/numeric-align-right
Added isTypeNumber check for right alignment in DataGridCell
2025-12-03 15:37:58 +01:00
Jan Prochazka a557ad177e changelog 2025-12-03 14:57:11 +01:00
Stela Augustinova 78e838f2f0 Custom thousands separator formatting in grid cell values 2025-12-03 13:53:47 +01:00
Stela Augustinova c1f216c7c7 Deleted checkbox for thousands separator and updated select field options 2025-12-03 13:53:10 +01:00
Jan Prochazka b75ff99e4c v6.7.2 2025-12-03 13:44:35 +01:00
Jan Prochazka 780dd8ade9 language icon 2025-12-03 13:37:13 +01:00
Jan Prochazka e1c10b7653 v6.7.2-beta.7 2025-12-03 12:57:52 +01:00
Jan Prochazka be9505f8fe SYNC: translations 2025-12-03 11:55:53 +00:00
Jan Prochazka d6bcd4f94f changelog 2025-12-03 12:52:40 +01:00
Jan Prochazka 7d2196f4c3 v6.7.2-premium-beta.6 2025-12-03 12:42:25 +01:00
Jan Prochazka 0539174317 SYNC: fixed e2e test 2025-12-03 11:34:55 +00:00
Jan Prochazka b4b52e12d5 SYNC: try to fix test 2025-12-03 10:13:16 +00:00
CI workflows f2e0b1cfa2 chore: auto-update github workflows 2025-12-03 10:10:08 +00:00
CI workflows 8020e2a263 Update pro ref 2025-12-03 10:09:55 +00:00
Jan Prochazka 6112d9b1b0 SYNC: settings storage changed 2025-12-03 10:09:42 +00:00
Stela Augustinova 4a1fbcbd31 Added select field for thousands separator 2025-12-03 10:46:34 +01:00
Stela Augustinova a02a3230f1 Added isTypeNumber check for right alignment in DataGridCell 2025-12-03 10:34:09 +01:00
CI workflows 0218bb4990 chore: auto-update github workflows 2025-12-03 07:43:56 +00:00
CI workflows 3769c03565 Update pro ref 2025-12-03 07:43:40 +00:00
SPRINX0\prochazka d96cb10476 behaviour settings changed 2025-12-02 18:14:55 +01:00
SPRINX0\prochazka b6b6123434 refresh DB - don't offer incremental analysis when not supported 2025-12-02 18:07:28 +01:00
SPRINX0\prochazka b40877fcc1 fix - don't show update mode in web 2025-12-02 17:59:41 +01:00
SPRINX0\prochazka af5ae29b73 changelog 2025-12-02 17:51:44 +01:00
SPRINX0\prochazka 082fceebbe v6.7.2-premium-beta.5 2025-12-02 17:29:27 +01:00
CI workflows f1dab80a06 chore: auto-update github workflows 2025-12-02 15:10:33 +00:00
CI workflows cbf2fac2cf Update pro ref 2025-12-02 15:10:16 +00:00
Jan Prochazka 4564bd7180 SYNC: Merge pull request #17 from dbgate/feature/settings-test 2025-12-02 15:10:00 +00:00
SPRINX0\prochazka d12ad7b882 SYNC: sorted translation keys 2025-12-02 13:42:48 +00:00
SPRINX0\prochazka f8e9f07a00 SYNC: translated files 2025-12-02 13:42:46 +00:00
Jan Prochazka 4ac3891e07 Merge pull request #1281 from dbgate/feature/translation4
Feature/translation4
2025-12-02 14:11:51 +01:00
SPRINX0\prochazka a2d55d3fdd SYNC: removed button 2025-12-02 13:09:32 +00:00
Stela Augustinova d015a24300 Merge branch 'master' into feature/translation4 2025-12-02 14:03:59 +01:00
Stela Augustinova be38acbede Update translation tag for columns count 2025-12-02 14:01:04 +01:00
Stela Augustinova 34d0cb4dc7 Refactor translation tags in ResultTabs component to use _t function 2025-12-02 13:58:31 +01:00
SPRINX0\prochazka 18d0558b19 SYNC: fixed language test 2025-12-02 12:47:33 +00:00
SPRINX0\prochazka d4469f3a2d SYNC: fixed import CSV test 2025-12-02 12:08:16 +00:00
SPRINX0\prochazka 43b760b4bf fix 2025-12-02 12:34:57 +01:00
SPRINX0\prochazka 3d47932c09 v6.7.2-premium-beta.4 2025-12-02 12:26:52 +01:00
SPRINX0\prochazka 7196d6e1bf SYNC: fixed plugin tyab test, moived to add-connection tests 2025-12-02 11:16:05 +00:00
SPRINX0\prochazka ec06a7d861 SYNC: restore table fixes & backup table tests 2025-12-02 10:08:38 +00:00
Jan Prochazka ef23f0d18e Merge pull request #1280 from dbgate/feature/settings-tab-update2
Feature/settings tab update2
2025-12-02 10:28:21 +01:00
Stela Augustinova e9abc5f07f Changed translation tag 2025-12-02 10:27:21 +01:00
Stela Augustinova 6f9d6ff849 Changed initial width of settings menu items 2025-12-02 10:21:39 +01:00
Stela Augustinova eab18d3c11 Update general settings layout 2025-12-02 10:16:16 +01:00
SPRINX0\prochazka 3a509a6a97 SYNC: backup test WIP 2025-12-01 16:09:20 +00:00
SPRINX0\prochazka 615c6f4e24 SYNC: popup menu fix 2025-12-01 15:43:24 +00:00
CI workflows 96660b4539 chore: auto-update github workflows 2025-12-01 15:37:27 +00:00
CI workflows 1be284974b Update pro ref 2025-12-01 15:37:11 +00:00
Jan Prochazka 790b6478dc Merge pull request #1279 from dbgate/feature/settings-tab-update
Feature/settings tab update
2025-12-01 16:28:21 +01:00
Stela Augustinova 106344b33e Deleted Settings Modal 2025-12-01 16:26:20 +01:00
Stela Augustinova 85a2a4b873 Revert "Button for svg export"
This reverts commit 352b6cbe04.
2025-12-01 16:22:35 +01:00
Stela Augustinova 4ab694de0c Added top border for first menu item 2025-12-01 16:14:23 +01:00
Stela Augustinova 5e193c1725 Removed Other settings component 2025-12-01 16:10:30 +01:00
Stela Augustinova 2b055c028c Update theme command to open settings tab with selected item 2025-12-01 16:00:05 +01:00
SPRINX0\prochazka b341749e45 v6.7.2-premium-beta.3 2025-12-01 15:48:06 +01:00
Stela Augustinova 6ae381b1fd Merge branch 'master' of https://github.com/dbgate/dbgate 2025-12-01 15:45:42 +01:00
Stela Augustinova 352b6cbe04 Button for svg export 2025-12-01 15:45:16 +01:00
CI workflows e5e6d2701e chore: auto-update github workflows 2025-12-01 14:28:22 +00:00
CI workflows 9ad1924488 Update pro ref 2025-12-01 14:28:07 +00:00
SPRINX0\prochazka 2aadbfc64a v6.7.2-premium-beta.2 2025-12-01 14:51:22 +01:00
Jan Prochazka 1c5d652f93 SYNC: fixed test 2025-12-01 13:34:34 +00:00
SPRINX0\prochazka b2355a3b2d Merge branch 'feature/restore-script' 2025-12-01 14:27:29 +01:00
Stela Augustinova 4ee6d089d5 Added translation tags for objects, widgets, forms 2025-12-01 12:54:42 +01:00
Jan Prochazka 43a826e2e5 Merge pull request #1277 from dbgate/feature/handle-full-refresh
Update refresh button to handle full database refresh
2025-12-01 11:07:18 +01:00
Stela Augustinova 4ce9faf39b Update refresh button to handle full database refresh 2025-12-01 10:42:49 +01:00
Stela Augustinova d94a67d0af Added translation tags for modals 2025-12-01 10:25:43 +01:00
Stela Augustinova e14f59256d Added translation tags for settings, tabs, modals 2025-11-30 19:38:01 +01:00
132 changed files with 4897 additions and 1965 deletions
+1 -1
View File
@@ -43,7 +43,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: 3b9ca48888d17d96806820c4e54bb047c18d6278
ref: ae1fcf6e61c6f7dfbb21005daa259c68e899a80a
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+1 -1
View File
@@ -43,7 +43,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: 3b9ca48888d17d96806820c4e54bb047c18d6278
ref: ae1fcf6e61c6f7dfbb21005daa259c68e899a80a
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+1 -1
View File
@@ -39,7 +39,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: 3b9ca48888d17d96806820c4e54bb047c18d6278
ref: ae1fcf6e61c6f7dfbb21005daa259c68e899a80a
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+1 -1
View File
@@ -44,7 +44,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: 3b9ca48888d17d96806820c4e54bb047c18d6278
ref: ae1fcf6e61c6f7dfbb21005daa259c68e899a80a
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: 3b9ca48888d17d96806820c4e54bb047c18d6278
ref: ae1fcf6e61c6f7dfbb21005daa259c68e899a80a
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: 3b9ca48888d17d96806820c4e54bb047c18d6278
ref: ae1fcf6e61c6f7dfbb21005daa259c68e899a80a
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+14
View File
@@ -8,6 +8,20 @@ Builds:
- linux - application for linux
- win - application for Windows
## 6.7.3
- FIXED: Fixed problem in analyser core - in PostgreSQL, after dropping table, dropped table still appeared in structure
- FIXED: PostgreSQL numeric columns do not align right #1254
- ADDED: Custom thousands separator #1213
## 6.7.2
- CHANGED: Settings modal redesign - now is settings opened in tab instead of modal, similarily as in VSCode
- FIXED: Fixed search in table shortcuts #1273
- CHANGED: Improved foreign key editor UX
- FIXED: Fixed incremental DB structure refresh for PostgreSQL, optimalized slow loading primary keys in PostgreSQL
- CHANGED: You could now choose, how to refresh structure, added ability to disconnect or reconnect
- ADDED: Better processing of table backups, generate table restore script #1274
- CHANGED: Improved storage of settings, especially for Team Premium edition
## 6.7.1
- ADDED: LANGUAGE environment variable for the web version. #1266
- ADDED: New localizations (Italian, Portugese (Brazil), Japanese)
+1
View File
@@ -10,6 +10,7 @@ module.exports = defineConfig({
// baseUrl: 'http://localhost:3000',
// trashAssetsBeforeRuns: false,
chromeWebSecurity: false,
reporter: process.env.CI ? 'mocha-reporter-gha' : 'spec',
setupNodeEvents(on, config) {
// implement node event listeners here
@@ -113,6 +113,18 @@ describe('Add connection', () => {
cy.contains('performance_schema');
});
it('Plugin tab', () => {
cy.testid('WidgetIconPanel_menu').click();
cy.contains('Tools').click();
cy.contains('Manage plugins').click();
cy.contains('dbgate-plugin-theme-total-white').click();
// text from plugin markdown
cy.contains('Total white theme');
// wait for load logos
cy.wait(2000);
cy.themeshot('view-plugin-tab');
});
it('export connections', () => {
cy.testid('WidgetIconPanel_menu').click();
cy.contains('Tools').click();
-11
View File
@@ -310,17 +310,6 @@ describe('Data browser data', () => {
cy.themeshot('search-in-connections');
});
it('Plugin tab', () => {
cy.testid('WidgetIconPanel_settings').click();
cy.contains('Manage plugins').click();
cy.contains('dbgate-plugin-theme-total-white').click();
// text from plugin markdown
cy.contains('Total white theme');
// wait for load logos
cy.wait(2000);
cy.themeshot('view-plugin-tab');
});
it('Edit mongo data JSON', () => {
// TODO FIX: Missing button+ctx menu Revert all changes, missing button+ctx menu add document
// TODO: Dark theme - not visible changed and deleted document
+78 -14
View File
@@ -110,7 +110,7 @@ describe('Charts', () => {
cy.themeshot('new-object-window');
});
it('Database chat - charts', () => {
it.skip('Database chat - charts', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').click();
cy.testid('TabsPanel_buttonNewObject').click();
@@ -125,7 +125,7 @@ describe('Charts', () => {
cy.themeshot('database-chat-chart');
});
it('Database chat', () => {
it.skip('Database chat', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').click();
cy.testid('TabsPanel_buttonNewObject').click();
@@ -146,7 +146,7 @@ describe('Charts', () => {
// cy.themeshot('database-chat');
});
it('Explain query error', () => {
it.skip('Explain query error', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').click();
cy.testid('TabsPanel_buttonNewObject').click();
@@ -163,45 +163,109 @@ describe('Charts', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').click();
cy.testid('WidgetIconPanel_settings').click();
cy.contains('Settings').click();
cy.testid('SettingsModal_languageSelect').select('Deutsch');
cy.testid('ConfirmModal_okButton').click();
cy.testid('WidgetIconPanel_settings').click();
cy.contains('Einstellungen').click();
cy.contains('Lokalisierung');
cy.contains('Sprache');
cy.themeshot('switch-language-de');
cy.testid('SettingsModal_languageSelect').select('Français');
cy.testid('ConfirmModal_okButton').click();
cy.testid('WidgetIconPanel_settings').click();
cy.contains('Paramètres').click();
cy.contains('Localisation');
cy.contains('Langue');
cy.themeshot('switch-language-fr');
cy.testid('SettingsModal_languageSelect').select('Español');
cy.testid('ConfirmModal_okButton').click();
cy.testid('WidgetIconPanel_settings').click();
cy.contains('Configuración').click();
cy.contains('Localización');
cy.contains('Idioma');
cy.themeshot('switch-language-es');
cy.testid('SettingsModal_languageSelect').select('Čeština');
cy.testid('ConfirmModal_okButton').click();
cy.testid('WidgetIconPanel_settings').click();
cy.contains('Nastavení').click();
cy.contains('Lokalizace');
cy.contains('Jazyk');
cy.themeshot('switch-language-cs');
cy.testid('SettingsModal_languageSelect').select('中文');
cy.testid('ConfirmModal_okButton').click();
cy.testid('WidgetIconPanel_settings').click();
cy.contains('设置').click();
cy.contains('本地化');
cy.contains('语言');
cy.themeshot('switch-language-zh');
cy.testid('SettingsModal_languageSelect').select('English');
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');
});
});
+64 -5
View File
@@ -103,13 +103,70 @@ describe('Transactions', () => {
describe('Backup table', () => {
multiTest({ skipMongo: true }, (connectionName, databaseName, engine, options = {}) => {
const implicitTransactions = options.implicitTransactions ?? false;
cy.contains(connectionName).click();
if (databaseName) cy.contains(databaseName).click();
cy.contains('customers').rightclick();
cy.contains('addresses').rightclick();
cy.contains('Create table backup').click();
cy.testid('ConfirmSqlModal_okButton').click();
cy.contains('_customers').click();
cy.contains('Rows: 8').should('be.visible');
cy.testid('app-object-group-items-table-backups').contains('addresses').click();
cy.contains('Rows: 12').should('be.visible');
cy.testid('app-object-group-items-tables').contains('addresses').click();
cy.contains('Ridgewood').click();
cy.testid('TableDataTab_deleteSelectedRows').click();
cy.contains('Rosewood').click();
cy.testid('TableDataTab_deleteSelectedRows').click();
cy.contains('Vermont').click();
cy.get('body').realType('Wermont{enter}');
cy.testid('TableDataTab_insertNewRow').click();
cy.get('body').realType('Modranska{enter}');
cy.realPress(['ArrowLeft']);
cy.realPress(['ArrowLeft']);
cy.get('body').realType('13{enter}');
cy.realPress(['ArrowRight']);
cy.get('body').realType('1{enter}');
cy.realPress(['ArrowRight']);
cy.realPress(['ArrowRight']);
cy.realPress(['ArrowRight']);
cy.get('body').realType('Prague{enter}');
cy.realPress(['ArrowRight']);
cy.get('body').realType('CZ{enter}');
cy.realPress(['ArrowRight']);
cy.get('body').realType('10000{enter}');
cy.realPress(['ArrowRight']);
cy.get('body').realType('111222333{enter}');
cy.testid('TableDataTab_save').click();
cy.testid('ConfirmSqlModal_okButton').click();
cy.contains('Rows: 11').should('be.visible'); // wait for save
cy.testid('app-object-group-items-table-backups').contains('addresses').rightclick();
cy.contains('restore script').click();
cy.contains('UPDATE'); // wait for query
cy.testid('QueryTab_executeButton').click();
cy.contains('Query execution finished');
if (implicitTransactions) {
cy.testid('QueryTab_commitTransactionButton').click();
cy.contains('Commit Transaction finished');
}
cy.realPress('F1');
cy.realType('Close all');
cy.realPress('Enter');
// cy.testid('CloseTabModal_buttonConfirm').click();
cy.wait(1000);
cy.testid('app-object-group-items-tables').contains('addresses').click();
// check whether data was successfully restored
cy.contains('Rows: 12').should('be.visible');
cy.contains('Ridgewood');
cy.contains('Vermont');
});
});
@@ -146,13 +203,15 @@ describe('Import CSV', () => {
cy.contains('Import').click();
cy.get('input[type=file]').selectFile('cypress/fixtures/customers-20.csv', { force: true });
cy.contains('customers-20');
cy.testid('ImportExportConfigurator_tableMappingSection').contains('customers-20');
cy.testid('ImportExportTab_preview_content').contains('50ddd99fAdF48B3').should('be.visible');
cy.testid('ImportExportTab_executeButton').click();
cy.contains('20 rows written').should('be.visible');
cy.testid('ImportExportConfigurator_tableMappingSection').contains('20 rows written').should('be.visible');
cy.testid('SqlObjectList_refreshButton').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');
+1 -4
View File
@@ -10,11 +10,11 @@
"cypress-real-events": "^1.13.0",
"env-cmd": "^10.1.0",
"kill-port": "^2.0.1",
"mocha-reporter-gha": "^1.1.1",
"start-server-and-test": "^2.0.8"
},
"scripts": {
"cy:open": "cypress open --config experimentalInteractiveRunEvents=true",
"cy:run:add-connection": "cypress run --spec cypress/e2e/add-connection.cy.js",
"cy:run:portal": "cypress run --spec cypress/e2e/portal.cy.js",
"cy:run:oauth": "cypress run --spec cypress/e2e/oauth.cy.js",
@@ -23,7 +23,6 @@
"cy:run:multi-sql": "cypress run --spec cypress/e2e/multi-sql.cy.js",
"cy:run:cloud": "cypress run --spec cypress/e2e/cloud.cy.js",
"cy:run:charts": "cypress run --spec cypress/e2e/charts.cy.js",
"start:add-connection": "node clearTestingData && cd .. && node packer/build/bundle.js --listen-api --run-e2e-tests",
"start:portal": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/portal/.env node e2e-tests/init/portal.js && env-cmd -f e2e-tests/env/portal/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
"start:oauth": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/oauth/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
@@ -32,7 +31,6 @@
"start:multi-sql": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/multi-sql/.env node e2e-tests/init/multi-sql.js && env-cmd -f e2e-tests/env/multi-sql/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
"start:cloud": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/cloud/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
"start:charts": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/charts/.env node e2e-tests/init/charts.js && env-cmd -f e2e-tests/env/charts/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
"test:add-connection": "start-server-and-test start:add-connection http://localhost:3000 cy:run:add-connection",
"test:portal": "start-server-and-test start:portal http://localhost:3000 cy:run:portal",
"test:oauth": "start-server-and-test start:oauth http://localhost:3000 cy:run:oauth",
@@ -41,7 +39,6 @@
"test:multi-sql": "start-server-and-test start:multi-sql http://localhost:3000 cy:run:multi-sql",
"test:cloud": "start-server-and-test start:cloud http://localhost:3000 cy:run:cloud",
"test:charts": "start-server-and-test start:charts http://localhost:3000 cy:run:charts",
"test": "yarn test:add-connection && yarn test:portal && yarn test:oauth && yarn test:browse-data && yarn test:team && yarn test:multi-sql && yarn test:cloud && yarn test:charts",
"test:ci": "yarn test"
},
+52
View File
@@ -2,6 +2,34 @@
# yarn lockfile v1
"@actions/core@^1.10.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.11.1.tgz#ae683aac5112438021588030efb53b1adb86f172"
integrity sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==
dependencies:
"@actions/exec" "^1.1.1"
"@actions/http-client" "^2.0.1"
"@actions/exec@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@actions/exec/-/exec-1.1.1.tgz#2e43f28c54022537172819a7cf886c844221a611"
integrity sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==
dependencies:
"@actions/io" "^1.0.1"
"@actions/http-client@^2.0.1":
version "2.2.3"
resolved "https://registry.yarnpkg.com/@actions/http-client/-/http-client-2.2.3.tgz#31fc0b25c0e665754ed39a9f19a8611fc6dab674"
integrity sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==
dependencies:
tunnel "^0.0.6"
undici "^5.25.4"
"@actions/io@^1.0.1":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@actions/io/-/io-1.1.3.tgz#4cdb6254da7962b07473ff5c335f3da485d94d71"
integrity sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==
"@colors/colors@1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
@@ -39,6 +67,11 @@
debug "^3.1.0"
lodash.once "^4.1.1"
"@fastify/busboy@^2.0.0":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d"
integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==
"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0":
version "9.3.0"
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb"
@@ -947,6 +980,13 @@ minimist@^1.2.8:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
mocha-reporter-gha@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/mocha-reporter-gha/-/mocha-reporter-gha-1.1.1.tgz#e1248abd0769f55b57b36ccd7db2b0b6573d5adf"
integrity sha512-CFbcgM56V4yWlbF91XuwrE6a5X/IqjVXTPefO7m8cY8Es8G1UhJ2KKOrk16AcSemRzVWXp2Fdy3bWJ7j45snWw==
dependencies:
"@actions/core" "^1.10.1"
ms@^2.1.1, ms@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
@@ -1292,6 +1332,11 @@ tunnel-agent@^0.6.0:
dependencies:
safe-buffer "^5.0.1"
tunnel@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
@@ -1307,6 +1352,13 @@ undici-types@~6.20.0:
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433"
integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
undici@^5.25.4:
version "5.29.0"
resolved "https://registry.yarnpkg.com/undici/-/undici-5.29.0.tgz#419595449ae3f2cdcba3580a2e8903399bd1f5a3"
integrity sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==
dependencies:
"@fastify/busboy" "^2.0.0"
universalify@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
@@ -33,7 +33,9 @@ describe('Schema tests', () => {
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();
if (!engine.skipIncrementalAnalysis) {
expect(structure2).toBeNull();
}
})
);
@@ -51,7 +53,9 @@ describe('Schema tests', () => {
const structure2 = await driver.analyseIncremental(conn, structure1);
const schemas2 = await driver.listSchemas(conn);
expect(schemas2.find(x => x.schemaName == 'myschema')).toBeFalsy();
expect(structure2).toBeNull();
if (!engine.skipIncrementalAnalysis) {
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)));
+2 -1
View File
@@ -1,6 +1,6 @@
{
"private": true,
"version": "6.7.2-alpha.1",
"version": "6.7.3",
"name": "dbgate-all",
"workspaces": [
"packages/*",
@@ -75,6 +75,7 @@
"translations:remove-unused": "node common/translations-cli/index.js remove-unused",
"translations:check": "node common/translations-cli/index.js check",
"translations:sort": "node common/translations-cli/index.js sort",
"translations:translate": "node common/translations-cli/translate.js",
"errors": "node common/assign-dbgm-codes.mjs ."
},
"dependencies": {
+4 -9
View File
@@ -289,16 +289,11 @@ module.exports = {
const res = await lock.acquire('settings', async () => {
const currentValue = await this.loadSettings();
try {
let updated = currentValue;
let updated = {
...currentValue,
...values,
};
if (process.env.STORAGE_DATABASE) {
updated = {
...currentValue,
..._.mapValues(values, v => {
if (v === true) return 'true';
if (v === false) return 'false';
return v;
}),
};
await storage.writeConfig({
group: 'settings',
config: updated,
+6
View File
@@ -360,6 +360,12 @@ module.exports = {
"columnName": "value",
"dataType": "varchar(1000)",
"notNull": false
},
{
"pureName": "config",
"columnName": "valueType",
"dataType": "varchar(50)",
"notNull": false
}
],
"foreignKeys": [],
+1
View File
@@ -21,6 +21,7 @@ export function getFilterValueExpression(value, dataType?) {
if (value === false) return 'FALSE';
if (value.$oid) return `ObjectId("${value.$oid}")`;
if (value.$bigint) return value.$bigint;
if (value.$decimal) return value.$decimal;
if (value.type == 'Buffer' && Array.isArray(value.data)) {
return '0x' + arrayToHexString(value.data);
}
@@ -19,6 +19,7 @@ function isLike(value, test) {
function extractRawValue(value) {
if (value?.$bigint) return value.$bigint;
if (value?.$oid) return value.$oid;
if (value?.$decimal) return value.$decimal;
return value;
}
+5
View File
@@ -164,6 +164,11 @@ export class DatabaseAnalyser<TClient = any> {
const res = {};
for (const field of STRUCTURE_FIELDS) {
const isAll = this.modifications.some(x => x.action == 'all' && x.objectTypeField == field);
if (isAll) {
res[field] = newlyAnalysed[field] || [];
continue;
}
const removedIds = this.modifications
.filter(x => x.action == 'remove' && x.objectTypeField == field)
.map(x => x.objectId);
+1
View File
@@ -87,6 +87,7 @@ export class SqlDumper implements AlterProcessor {
this.putByteArrayValue(bytes);
}
else if (value?.$bigint) this.putRaw(value?.$bigint);
else if (value?.$decimal) this.putRaw(value?.$decimal);
else if (_isPlainObject(value) || _isArray(value)) this.putStringValue(JSON.stringify(value));
else this.put('^null');
}
+51 -19
View File
@@ -45,14 +45,15 @@ export function hexStringToArray(inputString) {
export function base64ToHex(base64String) {
const binaryString = atob(base64String);
const hexString = Array.from(binaryString, c =>
c.charCodeAt(0).toString(16).padStart(2, '0')
).join('');
const hexString = Array.from(binaryString, c => c.charCodeAt(0).toString(16).padStart(2, '0')).join('');
return '0x' + hexString.toUpperCase();
};
}
export function hexToBase64(hexString) {
const binaryString = hexString.match(/.{1,2}/g).map(byte => String.fromCharCode(parseInt(byte, 16))).join('');
const binaryString = hexString
.match(/.{1,2}/g)
.map(byte => String.fromCharCode(parseInt(byte, 16)))
.join('');
return btoa(binaryString);
}
@@ -68,9 +69,9 @@ export function parseCellValue(value, editorTypes?: DataEditorTypesBehaviour) {
if (mHex) {
return {
$binary: {
base64: hexToBase64(value.substring(2))
}
}
base64: hexToBase64(value.substring(2)),
},
};
}
}
@@ -200,6 +201,26 @@ function stringifyJsonToGrid(value): ReturnType<typeof stringifyCellValue> {
return { value: '(JSON)', gridStyle: 'nullCellStyle' };
}
function formatNumberCustomSeparator(value, thousandsSeparator) {
const [intPart, decPart] = value.split('.');
const intPartWithSeparator = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSeparator);
return decPart ? `${intPartWithSeparator}.${decPart}` : intPartWithSeparator;
}
function formatCellNumber(value, gridFormattingOptions?: { thousandsSeparator?: string }) {
const separator = gridFormattingOptions?.thousandsSeparator;
if (_isNumber(value)) {
if (separator === 'none' || (value < 1000 && value > -1000)) return value.toString();
if (separator === 'system') return value.toLocaleString();
}
// fallback for system locale
if (separator === 'space' || separator === 'system') return formatNumberCustomSeparator(value.toString(), ' ');
if (separator === 'narrowspace') return formatNumberCustomSeparator(value.toString(), '\u202F');
if (separator === 'comma') return formatNumberCustomSeparator(value.toString(), ',');
if (separator === 'dot') return formatNumberCustomSeparator(value.toString(), '.');
return value.toString();
}
export function stringifyCellValue(
value,
intent:
@@ -210,7 +231,7 @@ export function stringifyCellValue(
| 'exportIntent'
| 'clipboardIntent',
editorTypes?: DataEditorTypesBehaviour,
gridFormattingOptions?: { useThousandsSeparator?: boolean },
gridFormattingOptions?: { thousandsSeparator?: string },
jsonParsedValue?: any
): {
value: string;
@@ -251,12 +272,19 @@ export function stringifyCellValue(
};
}
if (value?.$decimal) {
return {
value: formatCellNumber(value.$decimal, gridFormattingOptions),
gridStyle: 'valueCellStyle',
};
}
if (editorTypes?.parseHexAsBuffer) {
// if (value?.type == 'Buffer' && _isArray(value.data)) {
// return { value: '0x' + arrayToHexString(value.data), gridStyle: 'valueCellStyle' };
// }
}
if (editorTypes?.parseObjectIdAsDollar) {
if (value?.$oid) {
switch (intent) {
@@ -270,13 +298,13 @@ export function stringifyCellValue(
}
if (value?.$bigint) {
return {
value: value.$bigint,
value: formatCellNumber(value.$bigint, gridFormattingOptions),
gridStyle: 'valueCellStyle',
};
}
if (typeof value === 'bigint') {
return {
value: value.toString(),
value: formatCellNumber(value.toString(), gridFormattingOptions),
gridStyle: 'valueCellStyle',
};
}
@@ -351,13 +379,8 @@ export function stringifyCellValue(
if (_isNumber(value)) {
switch (intent) {
case 'gridCellIntent':
return {
value:
gridFormattingOptions?.useThousandsSeparator && (value >= 10000 || value <= -10000)
? value.toLocaleString()
: value.toString(),
gridStyle: 'valueCellStyle',
};
const separator = gridFormattingOptions?.thousandsSeparator;
return { value: formatCellNumber(value, gridFormattingOptions), gridStyle: 'valueCellStyle' };
default:
return { value: value.toString() };
}
@@ -449,6 +472,9 @@ export function shouldOpenMultilineDialog(value) {
if (value?.$bigint) {
return false;
}
if (value?.$decimal) {
return false;
}
if (_isPlainObject(value) || _isArray(value)) {
return true;
}
@@ -699,6 +725,9 @@ export function deserializeJsTypesFromJsonParse(obj) {
if (value?.$bigint) {
return BigInt(value.$bigint);
}
if (value?.$decimal) {
return value.$decimal;
}
});
}
@@ -713,6 +742,9 @@ export function deserializeJsTypesReviver(key, value) {
if (value?.$bigint) {
return BigInt(value.$bigint);
}
if (value?.$decimal) {
return value.$decimal;
}
return value;
}
+1
View File
@@ -238,6 +238,7 @@ export interface EngineDriver<TClient = any, TDataBase = any> extends FilterBeha
supportsDatabaseRestore?: boolean;
supportsServerSummary?: boolean;
supportsDatabaseProfiler?: boolean;
supportsIncrementalAnalysis?: boolean;
requiresDefaultSortCriteria?: boolean;
profilerFormatterFunction?: string;
profilerTimestampFunction?: string;
@@ -53,14 +53,15 @@
import InputTextModal from '../modals/InputTextModal.svelte';
import ConfirmModal from '../modals/ConfirmModal.svelte';
import { apiCall } from '../utility/api';
import { _t } from '../translations';
export let data;
const handleRename = () => {
showModal(InputTextModal, {
value: data.fileName,
label: 'New file name',
header: 'Rename file',
label: _t('appFile.newFileName', { defaultMessage: 'New file name' }),
header: _t('appFile.renameFile', { defaultMessage: 'Rename file' }),
onConfirm: newFile => {
apiCall('apps/rename-file', {
file: data.fileName,
@@ -74,7 +75,7 @@
const handleDelete = () => {
showModal(ConfirmModal, {
message: `Really delete file ${data.fileName}?`,
message: _t('appFile.deleteFileConfirm', { defaultMessage: 'Really delete file {fileName}?', values: { fileName: data.fileName } }),
onConfirm: () => {
apiCall('apps/delete-file', {
file: data.fileName,
@@ -101,10 +102,10 @@
function createMenu() {
return [
{ text: 'Delete', onClick: handleDelete },
{ text: 'Rename', onClick: handleRename },
data.fileType.endsWith('.sql') && { text: 'Open SQL', onClick: handleOpenSqlFile },
data.fileType.endsWith('.json') && { text: 'Open JSON', onClick: handleOpenJsonFile },
{ text: _t('common.delete', { defaultMessage: 'Delete' }), onClick: handleDelete },
{ text: _t('common.rename', { defaultMessage: 'Rename' }), onClick: handleRename },
data.fileType.endsWith('.sql') && { text: _t('common.openSql', { defaultMessage: 'Open SQL' }), onClick: handleOpenSqlFile },
data.fileType.endsWith('.json') && { text: _t('common.openJson', { defaultMessage: 'Open JSON' }), onClick: handleOpenJsonFile },
// data.fileType.endsWith('.yaml') && { text: 'Open YAML', onClick: handleOpenYamlFile },
];
@@ -15,6 +15,7 @@
import InputTextModal from '../modals/InputTextModal.svelte';
import { apiCall } from '../utility/api';
import { useConnectionList } from '../utility/metadataLoaders';
import { _t } from '../translations';
export let data;
@@ -34,8 +35,8 @@
showModal(InputTextModal, {
value: name,
label: 'New application name',
header: 'Rename application',
label: _t('appFolder.newApplicationName', { defaultMessage: 'New application name' }),
header: _t('appFolder.renameApplication', { defaultMessage: 'Rename application' }),
onConfirm: async newFolder => {
await apiCall('apps/rename-folder', {
folder: data.name,
@@ -60,16 +61,16 @@
function createMenu() {
return [
{ text: 'Delete', onClick: handleDelete },
{ text: 'Rename', onClick: handleRename },
{ text: _t('common.delete', { defaultMessage: 'Delete' }), onClick: handleDelete },
{ text: _t('common.rename', { defaultMessage: 'Rename' }), onClick: handleRename },
$currentDatabase && [
!isOnCurrentDb($currentDatabase, $connections) && {
text: 'Enable on current database',
text: _t('appFolder.enableOnCurrentDatabase', { defaultMessage: 'Enable on current database' }),
onClick: () => setOnCurrentDb(true),
},
isOnCurrentDb($currentDatabase, $connections) && {
text: 'Disable on current database',
text: _t('appFolder.disableOnCurrentDatabase', { defaultMessage: 'Disable on current database' }),
onClick: () => setOnCurrentDb(false),
},
],
@@ -90,7 +91,7 @@
title={data.name}
icon={'img app'}
statusIcon={isOnCurrentDb($currentDatabase, $connections) ? 'icon check' : null}
statusTitle={`Application ${data.name} is used for database ${$currentDatabase?.name}`}
statusTitle={_t('appFolder.applicationUsedForDatabase', { defaultMessage: 'Application {application} is used for database {database}', values: { application: data.name, database: $currentDatabase?.name } })}
isBold={data.name == $currentApplication}
on:click={() => ($currentApplication = data.name)}
menu={createMenu}
@@ -77,7 +77,7 @@
</div>
{/if}
<div on:drop={handleDrop}>
<div on:drop={handleDrop} data-testid={`app-object-group-items-${_.kebabCase(group)}`}>
{#each items as item}
<AppObjectListItem
isHidden={!item.isMatched}
@@ -82,6 +82,7 @@
import { apiCall } from '../utility/api';
import { openImportExportTab } from '../utility/importExportTools';
import { isProApp } from '../utility/proTools';
import { _t } from '../translations';
export let data;
$: isZipped = data.folderName?.endsWith('.zip');
@@ -89,8 +90,8 @@
const handleRename = () => {
showModal(InputTextModal, {
value: data.fileName,
label: 'New file name',
header: 'Rename file',
label: _t('archiveFile.newFileName', { defaultMessage: 'New file name' }),
header: _t('archiveFile.renameFile', { defaultMessage: 'Rename file' }),
onConfirm: newFile => {
apiCall('archive/rename-file', {
file: data.fileName,
@@ -104,7 +105,7 @@
const handleDelete = () => {
showModal(ConfirmModal, {
message: `Really delete file ${data.fileName}?`,
message: _t('archiveFile.deleteFileConfirm', { defaultMessage: 'Really delete file {fileName}?', values: { fileName: data.fileName } }),
onConfirm: () => {
apiCall('archive/delete-file', {
file: data.fileName,
@@ -147,10 +148,10 @@
}
return [
data.fileType == 'jsonl' && { text: 'Open', onClick: handleOpenArchive },
data.fileType == 'jsonl' && { text: 'Open in text editor', onClick: handleOpenJsonLinesText },
!isZipped && { text: 'Delete', onClick: handleDelete },
!isZipped && { text: 'Rename', onClick: handleRename },
data.fileType == 'jsonl' && { text: _t('common.open', { defaultMessage: 'Open' }), onClick: handleOpenArchive },
data.fileType == 'jsonl' && { text: _t('common.openInTextEditor', { defaultMessage: 'Open in text editor' }), onClick: handleOpenJsonLinesText },
!isZipped && { text: _t('common.delete', { defaultMessage: 'Delete' }), onClick: handleDelete },
!isZipped && { text: _t('common.rename', { defaultMessage: 'Rename' }), onClick: handleRename },
data.fileType == 'jsonl' &&
createQuickExportMenu(
fmt => async () => {
@@ -185,19 +186,19 @@
},
}
),
data.fileType.endsWith('.sql') && { text: 'Open SQL', onClick: handleOpenSqlFile },
data.fileType.endsWith('.yaml') && { text: 'Open YAML', onClick: handleOpenYamlFile },
data.fileType.endsWith('.sql') && { text: _t('common.openSql', { defaultMessage: 'Open SQL' }), onClick: handleOpenSqlFile },
data.fileType.endsWith('.yaml') && { text: _t('common.openYaml', { defaultMessage: 'Open YAML' }), onClick: handleOpenYamlFile },
!isZipped &&
isProApp() &&
data.fileType == 'jsonl' && {
text: 'Open in profiler',
text: _t('common.openInProfiler', { defaultMessage: 'Open in profiler' }),
submenu: getExtensions()
.drivers.filter(eng => eng.profilerFormatterFunction)
.map(eng => ({
text: eng.title,
onClick: () => {
openNewTab({
title: 'Profiler',
title: _t('common.profiler', { defaultMessage: 'Profiler' }),
icon: 'img profiler',
tabComponent: 'ProfilerTab',
props: {
@@ -21,14 +21,15 @@
import { isProApp } from '../utility/proTools';
import { extractShellConnection } from '../impexp/createImpExpScript';
import { saveFileToDisk } from '../utility/exportFileTools';
import { _t } from '../translations';
export let data;
const handleDelete = () => {
showModal(ConfirmModal, {
message: data.name.endsWith('.link')
? `Really delete link to folder ${data.name}? Folder content remains untouched.`
: `Really delete folder ${data.name}?`,
? _t('archiveFolder.deleteLinkConfirm', { defaultMessage: 'Really delete link to folder {folderName}? Folder content remains untouched.', values: { folderName: data.name } })
: _t('archiveFolder.deleteFolderConfirm', { defaultMessage: 'Really delete folder {folderName}?', values: { folderName: data.name } }),
onConfirm: () => {
apiCall('archive/delete-folder', { folder: data.name });
},
@@ -42,8 +43,8 @@
showModal(InputTextModal, {
value: name,
label: 'New folder name',
header: 'Rename folder',
label: _t('archiveFolder.newFolderName', { defaultMessage: 'New folder name' }),
header: _t('archiveFolder.renameFolder', { defaultMessage: 'Rename folder' }),
onConfirm: async newFolder => {
await apiCall('archive/rename-folder', {
folder: data.name,
@@ -95,7 +96,7 @@ await dbgateApi.deployDb(${JSON.stringify(
const handleCompareWithCurrentDb = () => {
openNewTab(
{
title: 'Compare',
title: _t('common.compare', { defaultMessage: 'Compare' }),
icon: 'img compare',
tabComponent: 'CompareModelTab',
props: {
@@ -153,7 +154,7 @@ await dbgateApi.deployDb(${JSON.stringify(
});
},
{
formatLabel: 'ZIP files',
formatLabel: _t('common.zipFiles', { defaultMessage: 'ZIP files' }),
formatExtension: 'zip',
defaultFileName: data.name?.endsWith('.zip') ? data.name : data.name + '.zip',
}
@@ -162,28 +163,28 @@ await dbgateApi.deployDb(${JSON.stringify(
function createMenu() {
return [
data.name != 'default' && { text: 'Delete', onClick: handleDelete },
data.name != 'default' && { text: 'Rename', onClick: handleRename },
isProApp() && { text: 'Data deployer', onClick: handleOpenDataDeployTab },
data.name != 'default' && { text: _t('common.delete', { defaultMessage: 'Delete' }), onClick: handleDelete },
data.name != 'default' && { text: _t('common.rename', { defaultMessage: 'Rename' }), onClick: handleRename },
isProApp() && { text: _t('common.dataDeployer', { defaultMessage: 'Data deployer' }), onClick: handleOpenDataDeployTab },
$currentDatabase && [
{ text: 'Generate deploy DB SQL', onClick: handleGenerateDeploySql },
hasPermission(`run-shell-script`) && { text: 'Shell: Deploy DB', onClick: handleGenerateDeployScript },
{ text: _t('archiveFolder.generateDeployDbSql', { defaultMessage: 'Generate deploy DB SQL' }), onClick: handleGenerateDeploySql },
hasPermission(`run-shell-script`) && { text: _t('archiveFolder.shellDeployDb', { defaultMessage: 'Shell: Deploy DB' }), onClick: handleGenerateDeployScript },
],
data.name != 'default' &&
isProApp() &&
data.name.endsWith('.zip') && { text: 'Unpack ZIP', onClick: () => handleZipUnzip('archive/unzip') },
data.name.endsWith('.zip') && { text: _t('archiveFolder.unpackZip', { defaultMessage: 'Unpack ZIP' }), onClick: () => handleZipUnzip('archive/unzip') },
data.name != 'default' &&
isProApp() &&
!data.name.endsWith('.zip') && { text: 'Pack (create ZIP)', onClick: () => handleZipUnzip('archive/zip') },
!data.name.endsWith('.zip') && { text: _t('archiveFolder.packZip', { defaultMessage: 'Pack (create ZIP)' }), onClick: () => handleZipUnzip('archive/zip') },
isProApp() && { text: 'Download ZIP', onClick: handleDownloadZip },
isProApp() && { text: _t('archiveFolder.downloadZip', { defaultMessage: 'Download ZIP' }), onClick: handleDownloadZip },
data.name != 'default' &&
hasPermission('dbops/model/compare') &&
isProApp() &&
_.get($currentDatabase, 'connection._id') && {
onClick: handleCompareWithCurrentDb,
text: `Compare with ${_.get($currentDatabase, 'name')}`,
text: _t('archiveFolder.compareWithCurrentDb', { defaultMessage: 'Compare with {name}', values: { name: _.get($currentDatabase, 'name') } }),
},
];
}
@@ -407,8 +407,8 @@ await dbgateApi.executeQuery(${JSON.stringify(
const handleCreateNewApp = () => {
showModal(InputTextModal, {
header: 'New application',
label: 'Application name',
header: _t('database.newApplication', { defaultMessage: 'New application' }),
label: _t('database.applicationName', { defaultMessage: 'Application name' }),
value: _.startCase(name),
onConfirm: async appName => {
const newAppId = await apiCall('apps/create-app-from-db', {
@@ -199,12 +199,12 @@
divider: true,
},
hasPermission('dbops/export') && {
label: 'Export',
label: _t('common.export', { defaultMessage: 'Export' }),
functionName: 'tableReader',
isExport: true,
},
hasPermission('dbops/import') && {
label: 'Import',
label: _t('common.import', { defaultMessage: 'Import' }),
isImport: true,
requiresWriteAccess: true,
},
@@ -260,7 +260,7 @@
divider: true,
},
{
label: 'Export',
label: _t('common.export', { defaultMessage: 'Export' }),
isExport: true,
functionName: 'tableReader',
},
@@ -310,7 +310,7 @@
divider: true,
},
{
label: 'Export',
label: _t('common.export', { defaultMessage: 'Export' }),
isExport: true,
functionName: 'tableReader',
},
@@ -402,7 +402,7 @@
icon: 'img perspective',
},
hasPermission('dbops/export') && {
label: 'Export',
label: _t('common.export', { defaultMessage: 'Export' }),
isExport: true,
functionName: 'tableReader',
},
@@ -754,9 +754,8 @@
const coreMenus = createMenusCore(objectTypeField, driver, data);
const filteredSumenus = coreMenus.map(item => {
if (!item) return item;
if (!item.submenu) {
if (!item) return item;
return { ...item, label: _tval(item.label) };
}
return {
@@ -773,7 +772,7 @@
};
});
const filteredNoEmptySubmenus = filteredSumenus.filter(x => !x.submenu || x.submenu.length > 0);
const filteredNoEmptySubmenus = _.compact(filteredSumenus).filter(x => !x.submenu || x.submenu.length > 0);
return filteredNoEmptySubmenus;
}
@@ -193,6 +193,7 @@
import { saveFileToDisk } from '../utility/exportFileTools';
import { getConnectionInfo } from '../utility/metadataLoaders';
import { showSnackbarError } from '../utility/snackbar';
import { _t } from '../translations';
export let data;
@@ -214,27 +215,26 @@
function createMenu() {
return [
handler?.tabComponent && { text: 'Open', onClick: openTab },
handler?.tabComponent && { text: _t('common.open', { defaultMessage: 'Open' }), onClick: openTab },
!data.teamFileId && hasPermission(`files/${data.folder}/write`) && { text: 'Rename', onClick: handleRename },
!data.teamFileId && hasPermission(`files/${data.folder}/write`) && { text: 'Create copy', onClick: handleCopy },
!data.teamFileId && hasPermission(`files/${data.folder}/write`) && { text: 'Delete', onClick: handleDelete },
data.teamFileId && data.allowWrite && { text: 'Rename', onClick: handleRename },
!data.teamFileId && hasPermission(`files/${data.folder}/write`) && { text: _t('common.rename', { defaultMessage: 'Rename' }), onClick: handleRename },
!data.teamFileId && hasPermission(`files/${data.folder}/write`) && { text: _t('common.createCopy', { defaultMessage: 'Create copy' }), onClick: handleCopy },
!data.teamFileId && hasPermission(`files/${data.folder}/write`) && { text: _t('common.delete', { defaultMessage: 'Delete' }), onClick: handleDelete },
data.teamFileId && data.allowWrite && { text: _t('common.rename', { defaultMessage: 'Rename' }), onClick: handleRename },
data.teamFileId &&
data.allowRead &&
hasPermission('all-team-files/create') && { text: 'Create copy', onClick: handleCopy },
data.teamFileId && data.allowWrite && { text: 'Delete', onClick: handleDelete },
hasPermission('all-team-files/create') && { text: _t('common.createCopy', { defaultMessage: 'Create copy' }), onClick: handleCopy },
data.teamFileId && data.allowWrite && { text: _t('common.delete', { defaultMessage: 'Delete' }), onClick: handleDelete },
folder == 'markdown' && { text: 'Show page', onClick: showMarkdownPage },
!data.teamFileId && { text: 'Download', onClick: handleDownload },
data.teamFileId && data.allowRead && { text: 'Download', onClick: handleDownload },
folder == 'markdown' && { text: _t('common.showPage', { defaultMessage: 'Show page' }), onClick: showMarkdownPage },
!data.teamFileId && { text: _t('common.download', { defaultMessage: 'Download' }), onClick: handleDownload },
data.teamFileId && data.allowRead && { text: _t('common.download', { defaultMessage: 'Download' }), onClick: handleDownload },
];
}
const handleDelete = () => {
showModal(ConfirmModal, {
message: `Really delete file ${data.file}?`,
message: _t('common.reallyDeleteFile', { defaultMessage: 'Really delete file {file}?', values: { file: data.file } }),
onConfirm: () => {
if (data.teamFileId) {
apiCall('team-files/delete', { teamFileId: data.teamFileId });
@@ -253,8 +253,8 @@
const handleRename = () => {
showModal(InputTextModal, {
value: data.file,
label: 'New file name',
header: 'Rename file',
label: _t('common.newFileName', { defaultMessage: 'New file name' }),
header: _t('common.renameFile', { defaultMessage: 'Rename file' }),
onConfirm: newFile => {
if (data.teamFileId) {
apiCall('team-files/update', { teamFileId: data.teamFileId, name: newFile });
@@ -274,8 +274,8 @@
const handleCopy = () => {
showModal(InputTextModal, {
value: data.file,
label: 'New file name',
header: 'Copy file',
label: _t('savedFile.newFileName', { defaultMessage: 'New file name' }),
header: _t('savedFile.copyFile', { defaultMessage: 'Copy file' }),
onConfirm: newFile => {
if (data.teamFileId) {
apiCall('team-files/copy', { teamFileId: data.teamFileId, newName: newFile });
@@ -323,12 +323,12 @@
if (data.teamFileId) {
if (data?.metadata?.autoExecute) {
if (!data.allowUse) {
showSnackbarError('You do not have permission to use this team file');
showSnackbarError(_t('savedFile.noPermissionUseTeamFile', { defaultMessage: 'You do not have permission to use this team file' }));
return;
}
} else {
if (!data.allowRead) {
showSnackbarError('You do not have permission to read this team file');
showSnackbarError(_t('savedFile.noPermissionReadTeamFile', { defaultMessage: 'You do not have permission to read this team file' }));
return;
}
}
@@ -81,7 +81,7 @@
import { getLocalStorage } from '../utility/storageCache';
import registerCommand from './registerCommand';
import { formatKeyText, switchCurrentDatabase } from '../utility/common';
import { _tval, __t } from '../translations';
import { _tval, __t, _t } from '../translations';
let domInput;
let filter = '';
@@ -181,7 +181,7 @@
domInput.focus();
}}
>
<FontIcon icon="icon menu" /> Commands
<FontIcon icon="icon menu" /> {_t('commandPalette.commands', { defaultMessage: 'Commands' })}
</div>
<div
class="page"
@@ -191,7 +191,7 @@
domInput.focus();
}}
>
<FontIcon icon="icon database" /> Database
<FontIcon icon="icon database" /> {_t('common.database', { defaultMessage: 'Database' })}
</div>
</div>
<div class="mainInner">
@@ -201,8 +201,8 @@
bind:this={domInput}
bind:value={filter}
on:keydown={handleKeyDown}
placeholder={parentCommand?.text ||
($visibleCommandPalette == 'database' ? 'Search in database' : 'Search in commands')}
placeholder={_tval(parentCommand?.text) ||
($visibleCommandPalette == 'database' ? _t('commandPalette.searchInDatabase', { defaultMessage: 'Search in database' }) : _t('commandPalette.searchInCommands', { defaultMessage: 'Search in commands' }))}
/>
</div>
<div class="content">
@@ -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);
},
});
+11 -8
View File
@@ -16,7 +16,6 @@ import {
import registerCommand from './registerCommand';
import { get } from 'svelte/store';
import AboutModal from '../modals/AboutModal.svelte';
import SettingsModal from '../settings/SettingsModal.svelte';
import SqlGeneratorModal from '../modals/SqlGeneratorModal.svelte';
import { showModal } from '../modals/modalTools';
import newQuery, { newDiagram, newPerspective, newQueryDesign } from '../query/newQuery';
@@ -41,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';
@@ -74,7 +71,15 @@ registerCommand({
category: __t('command.theme', { defaultMessage: 'Theme' }),
name: __t('command.theme.change', { defaultMessage: 'Change' }),
toolbarName: __t('command.theme.changeToolbar', { defaultMessage: 'Change theme' }),
onClick: () => showModal(SettingsModal, { selectedTab: 'theme' }),
onClick: () =>
openNewTab({
title: 'Settings',
icon: 'icon settings',
tabComponent: 'SettingsTab',
props: {
selectedItem: 'theme',
},
}),
// getSubCommands: () => get(extensions).themes.map(themeCommand),
});
@@ -1224,8 +1229,7 @@ registerCommand({
},
});
if ( hasPermission('application-log'))
{
if (hasPermission('application-log')) {
registerCommand({
id: 'app.showLogs',
category: __t('command.application', { defaultMessage: 'Application' }),
@@ -1240,8 +1244,7 @@ if ( hasPermission('application-log'))
});
}
if (hasPermission('widgets/plugins'))
{
if (hasPermission('widgets/plugins')) {
registerCommand({
id: 'app.managePlugins',
category: __t('command.application', { defaultMessage: 'Application' }),
+2 -2
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import _ from 'lodash';
import { getBoolSettingsValue } from '../settings/settingsTools';
import { getStringSettingsValue } from '../settings/settingsTools';
import { stringifyCellValue } from 'dbgate-tools';
export let rowData;
@@ -13,7 +13,7 @@
value,
'gridCellIntent',
editorTypes,
{ useThousandsSeparator: getBoolSettingsValue('dataGrid.thousandsSeparator', false) },
{ thousandsSeparator: getStringSettingsValue('dataGrid.thousandsSeparatorChar', 'none') },
jsonParsedValue
);
@@ -1,7 +1,7 @@
<script lang="ts">
import _, { isPlainObject } from 'lodash';
import ShowFormButton from '../formview/ShowFormButton.svelte';
import { detectTypeIcon, getConvertValueMenu, isJsonLikeLongString, safeJsonParse } from 'dbgate-tools';
import { detectTypeIcon, getConvertValueMenu, isJsonLikeLongString, safeJsonParse, isTypeNumber } from 'dbgate-tools';
import { openJsonDocument } from '../tabs/JsonTab.svelte';
import CellValue from './CellValue.svelte';
import { openJsonLinesData } from '../utility/openJsonLinesData';
@@ -57,7 +57,7 @@
$: style = computeStyle(maxWidth, col);
$: isJson =
_.isPlainObject(value) && !(value?.type == 'Buffer' && _.isArray(value.data)) && !value.$oid && !value.$bigint;
_.isPlainObject(value) && !(value?.type == 'Buffer' && _.isArray(value.data)) && !value.$oid && !value.$bigint && !value.$decimal;
// don't parse JSON for explicit data types
$: jsonParsedValue = !editorTypes?.explicitDataType && isJsonLikeLongString(value) ? safeJsonParse(value) : null;
@@ -80,7 +80,7 @@
class:isFocusedColumn
class:hasOverlayValue
class:isMissingOverlayField
class:alignRight={_.isNumber(value) && !showHint}
class:alignRight={ (_.isNumber(value) || isTypeNumber(col.dataType)) && !showHint}
{style}
>
{#if hasOverlayValue}
@@ -1,5 +1,5 @@
<script context="module" lang="ts">
import { __t } from '../translations'
import { __t, _t } from '../translations'
const getCurrentEditor = () => getActiveComponent('SqlDataGridCore');
registerCommand({
@@ -127,7 +127,7 @@
export function openQuery(sql?) {
openNewTab(
{
title: 'Query #',
title: _t('common.queryNumber', { defaultMessage: 'Query #' }),
icon: 'img sql-file',
tabComponent: 'QueryTab',
focused: true,
+1
View File
@@ -73,6 +73,7 @@ export function countColumnSizes(grider: Grider, columns, containerWidth, displa
if (_.isArray(value)) text = `[${value.length} items]`;
else if (value?.$oid) text = `ObjectId("${value.$oid}")`;
else if (value?.$bigint) text = value.$bigint;
else if (value?.$decimal) text = value.$decimal;
else if (isJsonLikeLongString(value) && safeJsonParse(value)) text = '(JSON)';
const width = context.measureText(typeof text == 'string' ? text.slice(0, MAX_GRID_TEXT_LENGTH) : text).width + 8;
// console.log('colName', colName, text, width);
+14 -14
View File
@@ -67,7 +67,7 @@
import { isProApp } from '../utility/proTools';
import dragScroll from '../utility/dragScroll';
import FormStyledButton from '../buttons/FormStyledButton.svelte';
import { __t } from '../translations';
import { __t, _t } from '../translations';
export let value;
export let onChange;
@@ -849,45 +849,45 @@
settings?.customizeStyle && [
{ divider: true },
isProApp() && {
text: 'Column properties',
text: _t('designer.columnProperties', { defaultMessage: 'Column properties' }),
submenu: [
{
text: `Nullability: ${value?.style?.showNullability ? 'YES' : 'NO'}`,
text: _t('designer.nullabilityYesNo', { defaultMessage: 'Nullability: {show}', values: { show: value?.style?.showNullability ? 'YES' : 'NO' } }),
onClick: changeStyleFunc('showNullability', !value?.style?.showNullability),
},
{
text: `Data type: ${value?.style?.showDataType ? 'YES' : 'NO'}`,
text: _t('designer.dataTypeYesNo', { defaultMessage: 'Data type: {show}', values: { show: value?.style?.showDataType ? 'YES' : 'NO' } }),
onClick: changeStyleFunc('showDataType', !value?.style?.showDataType),
},
],
},
isProApp() && {
text: `Columns - ${_.startCase(value?.style?.filterColumns || 'all')}`,
text: _t('designer.columns', { defaultMessage: 'Columns - { filterColumns }', values: { filterColumns: _.startCase(value?.style?.filterColumns || 'all') } }),
submenu: [
{
text: 'All',
text: _t('designer.all', { defaultMessage: 'All' }),
onClick: changeStyleFunc('filterColumns', ''),
},
{
text: 'Primary Key',
text: _t('designer.primaryKey', { defaultMessage: 'Primary Key' }),
onClick: changeStyleFunc('filterColumns', 'primaryKey'),
},
{
text: 'All Keys',
text: _t('designer.allKeys', { defaultMessage: 'All Keys' }),
onClick: changeStyleFunc('filterColumns', 'allKeys'),
},
{
text: 'Not Null',
text: _t('designer.notNull', { defaultMessage: 'Not Null' }),
onClick: changeStyleFunc('filterColumns', 'notNull'),
},
{
text: 'Keys And Not Null',
text: _t('designer.keysAndNotNull', { defaultMessage: 'Keys And Not Null' }),
onClick: changeStyleFunc('filterColumns', 'keysAndNotNull'),
},
],
},
{
text: `Zoom - ${(value?.style?.zoomKoef || 1) * 100}%`,
text: _t('designer.zoom', { defaultMessage: 'Zoom - {zoom}%', values: { zoom: ((value?.style?.zoomKoef || 1) * 100) } }),
submenu: DIAGRAM_ZOOMS.map(koef => ({
text: `${koef * 100} %`,
onClick: changeStyleFunc('zoomKoef', koef.toString()),
@@ -1016,11 +1016,11 @@
use:dragScroll={handleDragScroll}
>
{#if !(tables?.length > 0)}
<div class="empty">Drag &amp; drop tables or views from left panel here</div>
<div class="empty">{_t('designer.dragDropTables', { defaultMessage: 'Drag & drop tables or views from left panel here' })}</div>
{#if allowAddTablesButton}
<div class="addAllTables">
<FormStyledButton value="Add all tables" on:click={handleAddAllTables} />
<FormStyledButton value={_t('designer.addAllTables', { defaultMessage: 'Add all tables' })} on:click={handleAddAllTables} />
</div>
{/if}
{/if}
@@ -1119,7 +1119,7 @@
<div class="panel">
<DragColumnMemory {settings} {sourceDragColumn$} {targetDragColumn$} />
<div class="searchbox">
<SearchInput bind:value={columnFilter} placeholder="Filter columns" />
<SearchInput bind:value={columnFilter} placeholder={_t('designer.filterColumns', { defaultMessage: 'Filter columns' })} />
<CloseSearchButton bind:filter={columnFilter} />
</div>
</div>
@@ -31,7 +31,7 @@
</script>
<div class="main" class:maxHeight100 class:flex1>
<HorizontalSplitter initialValue="20%">
<HorizontalSplitter initialValue="170px">
<svelte:fragment slot="1">
<div class="menu">
{#each _.compact(items) as item, index}
@@ -118,6 +118,10 @@
transition: background-color 0.2s ease;
}
.menu-item:first-child {
border-top: 1px solid var(--theme-border);
}
.menu-item:hover {
background-color: var(--theme-bg-hover);
}
@@ -133,6 +137,7 @@
position: relative;
overflow: hidden;
height: 100%;
background-color: var(--theme-bg-0);
}
.scrollableContentContainer {
+2 -1
View File
@@ -1,6 +1,7 @@
<script lang="ts">
import _ from 'lodash';
import DropDownButton from '../buttons/DropDownButton.svelte';
import { _tval } from '../translations';
interface TabDef {
label: string;
@@ -46,7 +47,7 @@
data-testid={tab.testid}
>
<span class="ml-2 noselect">
{tab.label}
{_tval(tab.label)}
</span>
</div>
{/each}
@@ -8,6 +8,7 @@
import { getFormContext } from './FormProviderCore.svelte';
import FormSelectField from './FormSelectField.svelte';
import { _t } from '../translations';
export let additionalFolders = [];
export let name;
@@ -35,7 +36,7 @@
label: folder,
})),
allowCreateNew && {
label: '(Create new)',
label: _t('archiveFolder.createNew', { defaultMessage: '(Create new)' }),
value: '@create',
},
];
@@ -48,8 +49,8 @@
function handleChange(e) {
if (e.detail == '@create') {
showModal(InputTextModal, {
header: 'Archive',
label: 'Name of new archive folder',
header: _t('archiveFolder.archive', { defaultMessage: 'Archive' }),
label: _t('archiveFolder.nameOfNewArchiveFolder', { defaultMessage: 'Name of new archive folder' }),
onConfirm: createOption,
});
}
@@ -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 />
@@ -11,6 +11,7 @@
import { showModal } from '../modals/modalTools';
import InputTextModal from '../modals/InputTextModal.svelte';
import { apiCall } from '../utility/api';
import { _t } from '../translations';
export let value = '';
export let conid;
@@ -33,8 +34,8 @@
async function handleAddNewApplication() {
showModal(InputTextModal, {
header: 'New application',
label: 'Application name',
header: _t('database.newApplication', { defaultMessage: 'New application' }),
label: _t('database.applicationName', { defaultMessage: 'Application name' }),
value: _.startCase(database),
onConfirm: async appName => {
const newAppId = await apiCall('apps/create-app-from-db', {
@@ -211,8 +211,11 @@
/>
</div>
<div class="m-2">
<div class="title"><FontIcon icon="icon tables" /> {_t('importExport.mapSourceTablesFiles', { defaultMessage: "Map source tables/files" })}</div>
<div class="m-2" data-testid="ImportExportConfigurator_tableMappingSection">
<div class="title">
<FontIcon icon="icon tables" />
{_t('importExport.mapSourceTablesFiles', { defaultMessage: 'Map source tables/files' })}
</div>
{#key targetEditKey}
{#key progressHolder}
@@ -221,34 +224,34 @@
columns={[
{
fieldName: 'source',
header: _t('importExport.source', { defaultMessage: "Source" }),
header: _t('importExport.source', { defaultMessage: 'Source' }),
component: SourceName,
getProps: row => ({ name: row }),
},
{
fieldName: 'action',
header: _t('importExport.action', { defaultMessage: "Action" }),
header: _t('importExport.action', { defaultMessage: 'Action' }),
component: SourceAction,
getProps: row => ({ name: row, targetDbinfo }),
},
{
fieldName: 'target',
header: _t('importExport.target', { defaultMessage: "Target" }),
header: _t('importExport.target', { defaultMessage: 'Target' }),
slot: 1,
},
supportsPreview && {
fieldName: 'preview',
header: _t('importExport.preview', { defaultMessage: "Preview" }),
header: _t('importExport.preview', { defaultMessage: 'Preview' }),
slot: 0,
},
!!progressHolder && {
fieldName: 'status',
header: _t('importExport.status', { defaultMessage: "Status" }),
header: _t('importExport.status', { defaultMessage: 'Status' }),
slot: 3,
},
{
fieldName: 'columns',
header: _t('importExport.columns', { defaultMessage: "Columns" }),
header: _t('importExport.columns', { defaultMessage: 'Columns' }),
slot: 2,
},
]}
@@ -307,21 +310,21 @@
},
});
}}
>{columnCount > 0 ? `(${columnCount} columns)` : '(copy from source)'}
>{columnCount > 0 ? _t('importExport.columnsCount', { defaultMessage: '({columnCount} columns)', values: { columnCount } }) : _t('importExport.copyFromSource', { defaultMessage: '(copy from source)' })}
</Link>
</svelte:fragment>
<svelte:fragment slot="3" let:row>
{#if progressHolder[row]?.status == 'running' && isRunning}
<FontIcon icon="icon loading" />
{#if progressHolder[row]?.writtenRowCount}
{progressHolder[row]?.writtenRowCount} rows writtem
{progressHolder[row]?.writtenRowCount} {_t('importExport.rowsWritten', { defaultMessage: 'rows written' })}
{:else if progressHolder[row]?.readRowCount}
{progressHolder[row]?.readRowCount} rows read
{progressHolder[row]?.readRowCount} {_t('importExport.rowsRead', { defaultMessage: 'rows read' })}
{:else}
Running
{_t('importExport.running', { defaultMessage: 'Running' })}
{/if}
{:else if progressHolder[row]?.status == 'error'}
<FontIcon icon="img error" /> Error
<FontIcon icon="img error" /> {_t('common.error', { defaultMessage: 'Error' })}
{#if progressHolder[row]?.errorMessage}
<FontIcon
icon="img info"
@@ -334,20 +337,20 @@
{:else if progressHolder[row]?.status == 'done'}
<FontIcon icon="img ok" />
{#if progressHolder[row]?.writtenRowCount}
{progressHolder[row]?.writtenRowCount} rows written
{progressHolder[row]?.writtenRowCount} {_t('importExport.rowsWritten', { defaultMessage: 'rows written' })}
{:else if progressHolder[row]?.readRowCount}
{progressHolder[row]?.readRowCount} rows written
{progressHolder[row]?.readRowCount} {_t('importExport.rowsWritten', { defaultMessage: 'rows written' })}
{:else}
Done
{_t('common.done', { defaultMessage: 'Done' })}
{/if}
{:else}
<FontIcon icon="icon wait" />
{#if progressHolder[row]?.writtenRowCount}
{progressHolder[row]?.writtenRowCount} rows writtem
{progressHolder[row]?.writtenRowCount} {_t('importExport.rowsWritten', { defaultMessage: 'rows written' })}
{:else if progressHolder[row]?.readRowCount}
{progressHolder[row]?.readRowCount} rows read
{progressHolder[row]?.readRowCount} {_t('importExport.rowsRead', { defaultMessage: 'rows read' })}
{:else}
Queued
{_t('importExport.queued', { defaultMessage: 'Queued' })}
{/if}
{/if}
</svelte:fragment>
@@ -114,8 +114,8 @@
value={_t('importExport.newArchive', { defaultMessage: "New archive" })}
on:click={() => {
showModal(InputTextModal, {
header: 'Archive',
label: 'Name of new archive folder',
header: _t('importExport.archive', { defaultMessage: 'Archive' }),
label: _t('importExport.nameOfNewArchiveFolder', { defaultMessage: 'Name of new archive folder' }),
value: `import-${moment().format('YYYY-MM-DD-hh-mm-ss')}`,
onConfirm: value => {
values.update(x => ({
+12 -11
View File
@@ -8,6 +8,7 @@
import Link from '../elements/Link.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import { isProApp } from '../utility/proTools';
import { _t } from '../translations';
const config = useConfig();
$: version = $config?.version;
@@ -15,18 +16,18 @@
</script>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">About DbGate</svelte:fragment>
<svelte:fragment slot="header">{_t('aboutModal.aboutDbGate', { defaultMessage: 'About DbGate' })}</svelte:fragment>
<div class="flex">
<img src="logo192.png" />
<div class="ml-4">
<div>
Version: <span>{version}</span>
{_t('aboutModal.version', { defaultMessage: 'Version' })}: <span>{version}</span>
</div>
<div>
Build date: <span>{moment(buildTime).format('YYYY-MM-DD')}</span>
{_t('aboutModal.buildDate', { defaultMessage: 'Build date' })}: <span>{moment(buildTime).format('YYYY-MM-DD')}</span>
</div>
<div>
License type: <span
{_t('aboutModal.licenseType', { defaultMessage: 'License type' })}: <span
>{$config?.checkedLicense && $config?.checkedLicense?.type != 'community'
? ($config?.checkedLicense?.licenseTypeObj?.name ?? 'Unknown')
: 'Community'}</span
@@ -34,16 +35,16 @@
</div>
{#if $config?.checkedLicense?.users}
<div>
User count: <span>{$config?.checkedLicense?.users}</span>
{_t('aboutModal.userCount', { defaultMessage: 'User count' })}: <span>{$config?.checkedLicense?.users}</span>
</div>
{/if}
<div class="mt-2">
<FontIcon icon="mdi mdi-web color-icon-blue" /> Web: <Link href="https://www.dbgate.io">dbgate.io</Link>
<FontIcon icon="mdi mdi-web color-icon-blue" /> {_t('aboutModal.web', { defaultMessage: 'Web' })}: <Link href="https://www.dbgate.io">dbgate.io</Link>
</div>
{#if isProApp()}
<div>
<FontIcon icon="mdi mdi-email color-icon-red" /> Support: <Link href="mailto:support@dbgate.io"
<FontIcon icon="mdi mdi-email color-icon-red" /> {_t('aboutModal.support', { defaultMessage: 'Support' })}: <Link href="mailto:support@dbgate.io"
>support@dbgate.io</Link
>
</div>
@@ -55,10 +56,10 @@
</div> -->
<div class="mt-2">
Source codes: <Link href="https://github.com/dbgate/dbgate/">GitHub</Link>
{_t('aboutModal.sourceCodes', { defaultMessage: 'Source codes' })}: <Link href="https://github.com/dbgate/dbgate/">GitHub</Link>
</div>
<div>
Docker container: <Link
{_t('aboutModal.dockerContainer', { defaultMessage: 'Docker container' })}: <Link
href={isProApp()
? 'https://hub.docker.com/r/dbgate/dbgate-premium'
: 'https://hub.docker.com/r/dbgate/dbgate'}>Docker Hub</Link
@@ -69,13 +70,13 @@
</div> -->
<div class="mt-2">
Produced by: <span>Sprinx System a.s.</span>
{_t('aboutModal.producedBy', { defaultMessage: 'Produced by' })}: <span>Sprinx System a.s.</span>
</div>
</div>
</div>
<svelte:fragment slot="footer">
<FormStyledButton value="Close" on:click={closeCurrentModal} />
<FormStyledButton value={_t('common.close', { defaultMessage: 'Close' })} on:click={closeCurrentModal} />
</svelte:fragment>
</ModalBase>
+6 -5
View File
@@ -8,6 +8,7 @@
import TextField from '../forms/TextField.svelte';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
import { _t } from '../translations';
export let conid;
export let database;
@@ -26,10 +27,10 @@
<FormProvider>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">Add key</svelte:fragment>
<svelte:fragment slot="header">{_t('addDbKeyModal.addKey', { defaultMessage: 'Add key' })}</svelte:fragment>
<div class="container">
<FormFieldTemplateLarge label="Key" type="text" noMargin>
<FormFieldTemplateLarge label={_t('addDbKeyModal.key', { defaultMessage: 'Key' })} type="text" noMargin>
<TextField
value={keyName}
on:change={e => {
@@ -41,7 +42,7 @@
<div class="m-3" />
<FormFieldTemplateLarge label="Type" type="combo" noMargin>
<FormFieldTemplateLarge label={_t('addDbKeyModal.type', { defaultMessage: 'Type' })} type="combo" noMargin>
<SelectField
options={driver.supportedKeyTypes.map(t => ({ value: t.name, label: t.label }))}
value={type}
@@ -62,8 +63,8 @@
</div>
<svelte:fragment slot="footer">
<FormStyledButton value="OK" on:click={e => handleSubmit()} />
<FormStyledButton type="button" value="Cancel" on:click={closeCurrentModal} />
<FormStyledButton value={_t('common.ok', { defaultMessage: 'OK' })} on:click={e => handleSubmit()} />
<FormStyledButton type="button" value={_t('common.cancel', { defaultMessage: 'Cancel' })} on:click={closeCurrentModal} />
</svelte:fragment>
</ModalBase>
</FormProvider>
@@ -6,6 +6,7 @@
import FormTextField from '../forms/FormTextField.svelte';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
import { _t } from '../translations';
export let onConfirm;
export let url;
@@ -18,13 +19,13 @@
<FormProvider initialValues={{ url }}>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">Download imported file from web</svelte:fragment>
<svelte:fragment slot="header">{_t('changeDownloadUrlModal.header', { defaultMessage: 'Download imported file from web' })}</svelte:fragment>
<FormTextField label="URL" name="url" style={{ width: '30vw' }} focused />
<FormTextField label={_t('changeDownloadUrlModal.urlLabel', { defaultMessage: 'URL' })} name="url" style={{ width: '30vw' }} focused />
<svelte:fragment slot="footer">
<FormSubmit value="OK" on:click={handleSubmit} />
<FormStyledButton value="Cancel" on:click={closeCurrentModal} />
<FormSubmit value={_t('common.ok', { defaultMessage: 'OK' })} on:click={handleSubmit} />
<FormStyledButton value={_t('common.cancel', { defaultMessage: 'Cancel' })} on:click={closeCurrentModal} />
</svelte:fragment>
</ModalBase>
</FormProvider>
@@ -6,6 +6,7 @@
import FormSubmit from '../forms/FormSubmit.svelte';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
import { _t } from '../translations';
export let message = '';
export let onConfirm;
@@ -13,21 +14,21 @@
<FormProvider>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">Choose archive folder</svelte:fragment>
<svelte:fragment slot="header">{_t('archiveFolderModal.chooseArchiveFolder', { defaultMessage: 'Choose archive folder' })}</svelte:fragment>
<div>{message}</div>
<FormArchiveFolderSelect label="Archive folder" name="archiveFolder" isNative allowCreateNew />
<FormArchiveFolderSelect label={_t('archiveFolderModal.archiveFolder', { defaultMessage: 'Archive folder' })} name="archiveFolder" isNative allowCreateNew />
<svelte:fragment slot="footer">
<FormSubmit
value="OK"
value={_t('common.ok', { defaultMessage: 'OK' })}
on:click={e => {
closeCurrentModal();
onConfirm(e.detail.archiveFolder);
}}
/>
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
<FormStyledButton type="button" value={_t('common.close', { defaultMessage: 'Close' })} on:click={closeCurrentModal} />
</svelte:fragment>
</ModalBase>
</FormProvider>
@@ -7,6 +7,7 @@
import { useCloudContentList } from '../utility/metadataLoaders';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
import { _t } from '../translations';
export let message = '';
export let onConfirm;
@@ -18,22 +19,21 @@
{#if $cloudContentList}
<FormProvider initialValues={{ cloudFolder: $cloudContentList?.find(x => x.isPrivate)?.folid }}>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">Choose cloud folder</svelte:fragment>
<svelte:fragment slot="header">{_t('cloudFolderModal.chooseCloudFolder', { defaultMessage: 'Choose cloud folder' })}</svelte:fragment>
<div>{message}</div>
<FormCloudFolderSelect label="Cloud folder" name="cloudFolder" isNative {requiredRoleVariants} />
<FormCloudFolderSelect label={_t('cloudFolderModal.cloudFolder', { defaultMessage: 'Cloud folder' })} name="cloudFolder" isNative {requiredRoleVariants} />
<svelte:fragment slot="footer">
<FormSubmit
value="OK"
value={_t('common.ok', { defaultMessage: 'OK' })}
on:click={e => {
closeCurrentModal();
console.log('onConfirm', e.detail);
onConfirm(e.detail.cloudFolder);
}}
/>
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
<FormStyledButton type="button" value={_t('common.close', { defaultMessage: 'Close' })} on:click={closeCurrentModal} />
</svelte:fragment>
</ModalBase>
</FormProvider>
@@ -44,6 +44,7 @@
<svelte:fragment slot="footer">
<FormSubmit
value={_t('datagrid.closeTabs.close', { defaultMessage: 'Close tabs' })}
data-testid="CloseTabModal_buttonConfirm"
on:click={() => {
closeCurrentModal();
onConfirm();
@@ -52,6 +53,7 @@
<FormStyledButton
type="button"
value={_t('common.cancel', { defaultMessage: 'Cancel' })}
data-testid="CloseTabModal_buttonCancel"
on:click={() => {
closeCurrentModal();
onCancel();
+12 -11
View File
@@ -11,8 +11,9 @@
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
import _ from 'lodash';
import { _t } from '../translations';
export let header = 'Configure columns';
export let header = _t('columnMapModal.configureColumns', { defaultMessage: 'Configure columns' });
export let onConfirm;
export let sourceTableInfo;
@@ -69,7 +70,7 @@
if (!value) return;
if (value.length == 0) return;
if (value.some(x => !x.src || !x.dst)) {
validationError = 'Source and target columns must be defined';
validationError = _t('columnMapModal.sourceAndTargetColumnsMustBeDefined', { defaultMessage: 'Source and target columns must be defined' });
return;
}
const duplicates = _.chain(value.map(x => x.dst))
@@ -78,7 +79,7 @@
.keys()
.value();
if (duplicates.length > 0) {
validationError = 'Target columns must be unique, duplicates found: ' + duplicates.join(', ');
validationError = _t('columnMapModal.targetColumnsMustBeUnique', { defaultMessage: 'Target columns must be unique, duplicates found: ' }) + duplicates.join(', ');
return;
}
}
@@ -95,19 +96,19 @@
{#if resetValue.length == 0}
<div class="m-3">
When no columns are defined in this mapping, source row is copied to target without any modifications
{_t('columnMapModal.noColumnsDefined', { defaultMessage: 'When no columns are defined in this mapping, source row is copied to target without any modifications' })}
</div>
{/if}
<TableControl
columns={[
{ fieldName: 'use', header: 'Use', slot: 4 },
{ fieldName: 'src', header: 'Source column', slot: 1 },
{ fieldName: 'dst', header: 'Target column', slot: 2 },
{ fieldName: 'use', header: _t('columnMapModal.use', { defaultMessage: 'Use' }), slot: 4 },
{ fieldName: 'src', header: _t('columnMapModal.sourceColumn', { defaultMessage: 'Source column' }), slot: 1 },
{ fieldName: 'dst', header: _t('columnMapModal.targetColumn', { defaultMessage: 'Target column' }), slot: 2 },
{ fieldName: 'actions', header: '', slot: 3 },
]}
rows={value || []}
emptyMessage="No transform defined"
emptyMessage={_t('columnMapModal.noTransformDefined', { defaultMessage: 'No transform defined' })}
>
<svelte:fragment slot="4" let:row let:index>
<CheckboxField
@@ -136,7 +137,7 @@
<Link
onClick={() => {
value = value.filter((x, i) => i != index);
}}>Remove</Link
}}>{_t('common.Remove', { defaultMessage: 'Remove' })}</Link
>
</svelte:fragment>
</TableControl>
@@ -160,14 +161,14 @@
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
<FormStyledButton
type="button"
value="Add column"
value={_t('columnMapModal.addColumn', { defaultMessage: 'Add column' })}
on:click={() => {
value = [...(value || []), {}];
}}
/>
<FormStyledButton
type="button"
value="Reset"
value={_t('columnMapModal.reset', { defaultMessage: 'Reset' })}
disabled={!differentFromReset}
on:click={() => {
value = resetValue;
+4 -3
View File
@@ -5,17 +5,18 @@
import FormSubmit from '../forms/FormSubmit.svelte';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
import { _t } from '../translations';
export let message;
export let onConfirm;
export let confirmLabel = 'OK';
export let confirmLabel = _t('common.ok', { defaultMessage: 'OK' });
export let header = null;
</script>
<FormProvider>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">
{header || 'Confirm'}
{header || _t('common.confirm', { defaultMessage: 'Confirm' })}
</svelte:fragment>
{message}
@@ -31,7 +32,7 @@
/>
<FormStyledButton
type="button"
value="Close"
value={_t('common.close', { defaultMessage: 'Close' })}
on:click={closeCurrentModal}
data-testid="ConfirmModal_closeButton"
/>
@@ -9,6 +9,7 @@
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
import { _t } from '../translations';
export let script;
export let onConfirm;
@@ -19,7 +20,7 @@
<FormProvider>
<ModalBase {...$$restProps}>
<div slot="header">Save changes</div>
<div slot="header">{_t('common.saveChanges', { defaultMessage: 'Save changes' })}</div>
<div class="editor">
<AceEditor mode="javascript" readOnly value={script} />
@@ -28,7 +29,7 @@
{#if skipConfirmSettingKey}
<div class="mt-2">
<TemplatedCheckboxField
label="Don't ask again"
label={_t('common.dontAskAgain', { defaultMessage: "Don't ask again" })}
templateProps={{ noMargin: true }}
checked={dontAskAgain}
on:change={e => {
@@ -41,16 +42,16 @@
<div slot="footer">
<FormSubmit
value="OK"
value={_t('common.ok', { defaultMessage: 'OK' })}
on:click={() => {
closeCurrentModal();
onConfirm();
}}
/>
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
<FormStyledButton type="button" value={_t('common.close', { defaultMessage: 'Close' })} on:click={closeCurrentModal} />
<FormStyledButton
type="button"
value="Open script"
value={_t('common.openScript', { defaultMessage: 'Open script' })}
on:click={() => {
newQuery({
initialData: script,
+11 -11
View File
@@ -54,6 +54,7 @@
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal, showModal } from './modalTools';
import { _t } from '../translations';
export let sql;
export let onConfirm;
@@ -81,7 +82,7 @@
<FormProviderCore {values}>
<ModalBase {...$$restProps}>
<div slot="header">Save changes</div>
<div slot="header">{_t('common.saveChanges', { defaultMessage: 'Save changes' })}</div>
<div class="editor">
<SqlEditor {engine} value={currentScript} readOnly />
@@ -91,7 +92,7 @@
<div class="mt-2">
<FormCheckboxField
templateProps={{ noMargin: true }}
label="Delete references CASCADE"
label={_t('sqlModal.deleteReferencesCascade', { defaultMessage: 'Delete references CASCADE' })}
name="deleteReferencesCascade"
data-testid="ConfirmSqlModal_deleteReferencesCascade"
/>
@@ -101,13 +102,13 @@
{#if $values.deleteReferencesCascade}
<div class="form-margin flex">
<FormStyledButton
value="Check all"
value={_t('common.checkAll', { defaultMessage: 'Check all' })}
on:click={() => {
$values = _.omitBy($values, (v, k) => k.startsWith('deleteReferencesFor_'));
}}
/>
<FormStyledButton
value="Uncheck all"
value={_t('common.uncheckAll', { defaultMessage: 'Uncheck all' })}
on:click={() => {
const newValues = { ...$values };
for (const item of deleteCascadesScripts) {
@@ -135,12 +136,11 @@
{#if isRecreated}
<div class="form-margin">
<div>
<FontIcon icon="img warn" /> This operation is not directly supported by SQL engine. DbGate can emulate it, but
please check the generated SQL script.
<FontIcon icon="img warn" /> {_t('sqlModal.recreateWarning', { defaultMessage: "This operation is not directly supported by SQL engine. DbGate can emulate it, but please check the generated SQL script." })}
</div>
<FormCheckboxField
templateProps={{ noMargin: true }}
label="Allow recreate (don't use on production databases)"
label={_t('sqlModal.allowRecreate', { defaultMessage: "Allow recreate (don't use on production databases)" })}
name="allowRecreate"
/>
</div>
@@ -149,7 +149,7 @@
{#if skipConfirmSettingKey}
<div class="mt-2">
<TemplatedCheckboxField
label="Don't ask again"
label={_t('common.dontAskAgain', { defaultMessage: "Don't ask again" })}
templateProps={{ noMargin: true }}
checked={dontAskAgain}
on:change={e => {
@@ -162,7 +162,7 @@
<div slot="footer">
<FormSubmit
value="OK"
value={_t('common.ok', { defaultMessage: 'OK' })}
disabled={isRecreated && !$values.allowRecreate}
on:click={e => {
closeCurrentModal();
@@ -172,13 +172,13 @@
/>
<FormStyledButton
type="button"
value="Close"
value={_t('common.close', { defaultMessage: 'Close' })}
on:click={closeCurrentModal}
data-testid="ConfirmSqlModal_closeButton"
/>
<FormStyledButton
type="button"
value="Open script"
value={_t('common.openScript', { defaultMessage: 'Open script' })}
on:click={() => {
newQuery({
initialData: currentScript,
@@ -28,6 +28,7 @@
import { callServerPing } from '../utility/connectionsPinger';
import { getConnectionLabel } from 'dbgate-tools';
import { openedConnections } from '../stores';
import { _t } from '../translations';
export let conid;
export let passwordMode;
@@ -125,11 +126,11 @@
<FormProviderCore {values}>
<ModalBase {...$$restProps} simple>
<svelte:fragment slot="header">Database Log In ({engineTitle})</svelte:fragment>
<svelte:fragment slot="header">{_t('databaseLoginModal.header', { defaultMessage: 'Database Log In ({engineTitle})', values: {engineTitle} })}</svelte:fragment>
<FormTextField label="Connection" name="connectionLabel" disabled />
<FormTextField label={_t('databaseLoginModal.connection', { defaultMessage: 'Connection' })} name="connectionLabel" disabled />
<FormTextField
label="Username"
label={_t('databaseLoginModal.username', { defaultMessage: 'Username' })}
name="user"
autocomplete="username"
disabled={usedPasswordMode == 'askPassword'}
@@ -138,7 +139,7 @@
data-testid="DatabaseLoginModal_username"
/>
<FormPasswordField
label="Password"
label={_t('databaseLoginModal.password', { defaultMessage: 'Password' })}
name="password"
autocomplete="current-password"
focused={usedPasswordMode == 'askPassword'}
@@ -148,34 +149,34 @@
{#if isTesting}
<div>
<FontIcon icon="icon loading" /> Testing connection
<FontIcon icon="icon loading" /> {_t('databaseLoginModal.testingConnection', { defaultMessage: 'Testing connection' })}
</div>
{/if}
{#if !isTesting && sqlConnectResult && sqlConnectResult.msgtype == 'error'}
<div class="error-result">
Connect failed: <FontIcon icon="img error" />
{_t('databaseLoginModal.connectFailed', { defaultMessage: 'Connect failed:' })} <FontIcon icon="img error" />
{sqlConnectResult.error}
<Link
onClick={() =>
showModal(ErrorMessageModal, {
message: sqlConnectResult.detail,
showAsCode: true,
title: 'Database connection error',
title: _t('databaseLoginModal.connectionError', { defaultMessage: 'Database connection error' }),
})}
>
Show detail
{_t('databaseLoginModal.showDetail', { defaultMessage: 'Show detail' })}
</Link>
</div>
{/if}
<svelte:fragment slot="footer">
{#if isTesting}
<FormStyledButton value="Stop connecting" on:click={handleCancelTest} data-testid="DatabaseLoginModal_stop" />
<FormStyledButton value={_t('databaseLoginModal.stopConnecting', { defaultMessage: 'Stop connecting' })} on:click={handleCancelTest} data-testid="DatabaseLoginModal_stop" />
{:else}
<FormSubmit value="Connect" on:click={handleSubmit} data-testid="DatabaseLoginModal_connect" />
<FormSubmit value={_t('databaseLoginModal.connect', { defaultMessage: 'Connect' })} on:click={handleSubmit} data-testid="DatabaseLoginModal_connect" />
{/if}
<FormStyledButton value="Close" on:click={closeCurrentModal} data-testid="DatabaseLoginModal_close" />
<FormStyledButton value={_t('common.close', { defaultMessage: 'Close' })} on:click={closeCurrentModal} data-testid="DatabaseLoginModal_close" />
</svelte:fragment>
</ModalBase>
</FormProviderCore>
@@ -5,6 +5,7 @@
import FormProvider from '../forms/FormProvider.svelte';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
import { _t } from '../translations';
export let keyInfo;
export let label;
@@ -21,7 +22,7 @@
<FormProvider>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">Add item</svelte:fragment>
<svelte:fragment slot="header">{_t('dbKeyAddItemModal.header', { defaultMessage: 'Add item' })}</svelte:fragment>
<div class="container">
<DbKeyItemDetail
@@ -34,8 +35,8 @@
</div>
<svelte:fragment slot="footer">
<FormStyledButton value="OK" on:click={e => handleSubmit()} />
<FormStyledButton type="button" value="Cancel" on:click={closeCurrentModal} />
<FormStyledButton value={_t('common.ok', { defaultMessage: 'OK' })} on:click={e => handleSubmit()} />
<FormStyledButton type="button" value={_t('common.cancel', { defaultMessage: 'Cancel' })} on:click={closeCurrentModal} />
</svelte:fragment>
</ModalBase>
</FormProvider>
@@ -21,6 +21,7 @@
import { currentDatabase } from '../stores';
import { filterAppsForDatabase } from '../utility/appTools';
import { apiCall } from '../utility/api';
import { _t } from '../translations';
export let conid;
export let database;
@@ -52,10 +53,10 @@
<FormProviderCore {values}>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">Define description</svelte:fragment>
<svelte:fragment slot="header">{_t('defineDictionaryDescriptionModal.header', { defaultMessage: 'Define description' })}</svelte:fragment>
<FormSelectField
label="Target application (mandatory)"
label={_t('defineDictionaryDescriptionModal.targetApplication', { defaultMessage: 'Target application (mandatory)' })}
name="targetApplication"
disableInitialize
selectFieldComponent={TargetApplicationSelect}
@@ -68,8 +69,8 @@
rows={$tableInfo?.columns || []}
columns={[
{ fieldName: 'checked', header: '', slot: 1 },
{ fieldName: 'columnName', header: 'Column' },
{ fieldName: 'dataType', header: 'Data type' },
{ fieldName: 'columnName', header: _t('defineDictionaryDescriptionModal.column', { defaultMessage: 'Column' }) },
{ fieldName: 'dataType', header: _t('defineDictionaryDescriptionModal.dataType', { defaultMessage: 'Data type' }) },
]}
>
<input
@@ -88,15 +89,15 @@
</TableControl>
</div>
<FormTextField name="columns" label="Show columns" />
<FormTextField name="columns" label={_t('defineDictionaryDescriptionModal.showColumns', { defaultMessage: 'Show columns' })} />
<FormTextField name="delimiter" label="Delimiter" />
<FormTextField name="delimiter" label={_t('defineDictionaryDescriptionModal.delimiter', { defaultMessage: 'Delimiter' })} />
<!-- <FormCheckboxField name="useForAllDatabases" label="Use for all databases" /> -->
<svelte:fragment slot="footer">
<FormSubmit
value="OK"
value={_t('common.ok', { defaultMessage: 'OK' })}
disabled={!checkDescriptionExpression($values?.columns, $tableInfo) || !$values.targetApplication}
on:click={async () => {
closeCurrentModal();
@@ -122,7 +123,7 @@
onConfirm?.();
}}
/>
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
<FormStyledButton type="button" value={_t('common.close', { defaultMessage: 'Close' })} on:click={closeCurrentModal} />
</svelte:fragment>
</ModalBase>
</FormProviderCore>
@@ -15,6 +15,7 @@
import FormTextField from '../forms/FormTextField.svelte';
import _ from 'lodash';
import { apiCall } from '../utility/api';
import { _t } from '../translations';
export let onConfirm;
export let conid;
@@ -126,15 +127,15 @@
<FormProvider>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">Lookup from {pureName}</svelte:fragment>
<svelte:fragment slot="header">{_t('dictionaryLookupModal.header', { defaultMessage: 'Lookup from {pureName}', values: {pureName} })}</svelte:fragment>
<!-- <FormTextField name="search" label='Search' placeholder="Search" bind:value={search} /> -->
<div class="largeFormMarker">
<SearchInput placeholder="Search" bind:value={search} isDebounced />
<SearchInput placeholder={_t("common.search", { defaultMessage: "Search" })} bind:value={search} isDebounced />
</div>
{#if isLoading}
<LoadingInfo message="Loading data" />
<LoadingInfo message={_t('dictionaryLookupModal.loadingData', { defaultMessage: "Loading data" })} />
{/if}
{#if !isLoading && tableInfo && description && rows && tableInfo?.primaryKey?.columns?.length == 1}
@@ -161,13 +162,13 @@
},
{
fieldName: 'value',
header: 'Value',
header: _t('dictionaryLookupModal.value', { defaultMessage: 'Value' }),
formatter: row => row[tableInfo.primaryKey.columns[0].columnName],
width: '100px',
},
{
fieldName: 'description',
header: 'Description',
header: _t('dictionaryLookupModal.description', { defaultMessage: 'Description' }),
formatter: row => description.columns.map(col => row[col]).join(description.delimiter || ' '),
},
]}
@@ -194,15 +195,15 @@
<svelte:fragment slot="footer">
{#if multiselect}
<FormSubmit
value="OK"
value={_t('common.ok', { defaultMessage: 'OK' })}
on:click={() => {
closeCurrentModal();
onConfirm(checkedKeys);
}}
/>
{/if}
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
<FormStyledButton type="button" value="Customize" on:click={defineDescription} />
<FormStyledButton type="button" value={_t('common.close', { defaultMessage: 'Close' })} on:click={closeCurrentModal} />
<FormStyledButton type="button" value={_t('dictionaryLookupModal.customize', { defaultMessage: 'Customize' })} on:click={defineDescription} />
</svelte:fragment>
</ModalBase>
</FormProvider>
+6 -1
View File
@@ -164,7 +164,12 @@
changeActiveSubmenu();
}}
>
<a on:click={e => handleClick(e, item)} class:disabled={item.disabled} class:bold={item.isBold}>
<a
on:click={e => handleClick(e, item)}
class:disabled={item.disabled}
class:bold={item.isBold}
data-testid={item.testid}
>
<span>
{#if item.switchValue && item.switchStoreGetter}
{#key switchIndex}
@@ -66,7 +66,7 @@
<div slot="footer" class="footer">
<div>
<FormStyledButton
value="OK"
value={_t('common.ok', { defaultMessage: 'OK' })}
title="Ctrl+Enter"
on:click={() => {
onSave(parseCellValue(textValue, dataEditorTypesBehaviour));
+2 -3
View File
@@ -30,11 +30,10 @@
<FormProvider>
<ModalBase {...$$restProps}>
<div slot="header">Edit JSON value</div>
<div slot="header">{_t('editJsonModal.header', { defaultMessage: 'Edit JSON value' })}</div>
{#if showPasteInfo}
<div class="m-2">
Edit JSON object or array. You can paste JSON array or object directly into data grid, new row(s) will be added
to recordset.
{_t('editJsonModal.pasteInfo', { defaultMessage: "Edit JSON object or array. You can paste JSON array or object directly into data grid, new row(s) will be added to recordset." })}
</div>
{/if}
@@ -168,11 +168,11 @@
<FormProvider>
<ModalBase {...$$restProps} fullScreen>
<div slot="header">
{mode == 'export' ? 'Export' : 'Import'} connections &amp; settings
{mode == 'export' ? 'Export' : 'Import'} {_t('importExport.connectionsSettings', { defaultMessage: 'connections & settings' })}
<span class="check-uncheck">
<Link onClick={() => handleCheckAll(true)}>Check all</Link>
<Link onClick={() => handleCheckAll(true)}>{_t('common.checkAll', { defaultMessage: 'Check all' })}</Link>
|
<Link onClick={() => handleCheckAll(false)}>Uncheck all</Link>
<Link onClick={() => handleCheckAll(false)}>{_t('common.uncheckAll', { defaultMessage: 'Uncheck all' })}</Link>
</span>
</div>
@@ -180,16 +180,16 @@
<TabControl
tabs={_.compact([
connections?.length && {
label: `Connections (${checkedConnections?.length}/${connections?.length})`,
label: _t('importExport.connectionsNum', { defaultMessage:'Connections ({checkedConnections}/{connections})', values: { checkedConnections: checkedConnections?.length, connections: connections?.length } }),
slot: 1,
},
users?.length && { label: `Users (${checkedUsers?.length}/${users?.length})`, slot: 2 },
roles?.length && { label: `Roles (${checkedRoles?.length}/${roles?.length})`, slot: 3 },
users?.length && { label: _t('importExport.usersNum', { defaultMessage:'Users ({checkedUsers}/{users})', values: { checkedUsers: checkedUsers?.length, users: users?.length } }), slot: 2 },
roles?.length && { label: _t('importExport.rolesNum', { defaultMessage:'Roles ({checkedRoles}/{roles})', values: { checkedRoles: checkedRoles?.length, roles: roles?.length } }), slot: 3 },
authMethods?.length && {
label: `Auth methods (${checkedAuthMethods?.length}/${authMethods?.length})`,
label: _t('importExport.authMethodsNum', { defaultMessage:'Auth methods ({checkedAuthMethods}/{authMethods})', values: { checkedAuthMethods: checkedAuthMethods?.length, authMethods: authMethods?.length } }),
slot: 4,
},
config?.length && { label: `Config (${checkedConfig?.length}/${config?.length})`, slot: 5 },
config?.length && { label: _t('importExport.configNum', { defaultMessage:'Config ({checkedConfig}/{config})', values: { checkedConfig: checkedConfig?.length, config: config?.length } }), slot: 5 },
])}
>
<svelte:fragment slot="1">
@@ -199,10 +199,10 @@
stickyHeader
columns={[
{ header: 'ID', fieldName: 'id', sortable: true, filterable: true },
{ header: 'Display name', fieldName: 'displayName', sortable: true, filterable: true },
{ header: 'Engine', fieldName: 'engine', sortable: true, filterable: true },
{ header: 'Server', fieldName: 'server', sortable: true, filterable: true },
{ header: 'User', fieldName: 'user', sortable: true, filterable: true },
{ header: _t('importExport.displayName', { defaultMessage: 'Display name' }), fieldName: 'displayName', sortable: true, filterable: true },
{ header: _t('importExport.engine', { defaultMessage: 'Engine' }), fieldName: 'engine', sortable: true, filterable: true },
{ header: _t('importExport.server', { defaultMessage: 'Server' }), fieldName: 'server', sortable: true, filterable: true },
{ header: _t('importExport.user', { defaultMessage: 'User' }), fieldName: 'user', sortable: true, filterable: true },
]}
clickable
rows={connections}
@@ -225,8 +225,8 @@
stickyHeader
columns={[
{ header: 'ID', fieldName: 'id', sortable: true, filterable: true },
{ header: 'Login', fieldName: 'login', sortable: true, filterable: true },
{ header: 'E-mail', fieldName: 'email', sortable: true, filterable: true },
{ header: _t('importExport.login', { defaultMessage: 'Login' }), fieldName: 'login', sortable: true, filterable: true },
{ header: _t('importExport.email', { defaultMessage: 'E-mail' }), fieldName: 'email', sortable: true, filterable: true },
]}
clickable
rows={users}
@@ -249,7 +249,7 @@
stickyHeader
columns={[
{ header: 'ID', fieldName: 'id', sortable: true, filterable: true },
{ header: 'Name', fieldName: 'name', sortable: true, filterable: true },
{ header: _t('importExport.name', { defaultMessage: 'Name' }), fieldName: 'name', sortable: true, filterable: true },
]}
clickable
rows={roles}
@@ -272,8 +272,8 @@
stickyHeader
columns={[
{ header: 'ID', fieldName: 'id', sortable: true, filterable: true },
{ header: 'Name', fieldName: 'name', sortable: true, filterable: true },
{ header: 'Type', fieldName: 'type', sortable: true, filterable: true },
{ header: _t('importExport.name', { defaultMessage: 'Name' }), fieldName: 'name', sortable: true, filterable: true },
{ header: _t('importExport.type', { defaultMessage: 'Type' }), fieldName: 'type', sortable: true, filterable: true },
]}
clickable
rows={authMethods}
@@ -296,9 +296,9 @@
stickyHeader
columns={[
{ header: 'ID', fieldName: 'id', sortable: true, filterable: true },
{ header: 'Group', fieldName: 'group', sortable: true, filterable: true },
{ header: 'Key', fieldName: 'key', sortable: true, filterable: true },
{ header: 'Value', fieldName: 'value', sortable: true, filterable: true },
{ header: _t('importExport.group', { defaultMessage: 'Group' }), fieldName: 'group', sortable: true, filterable: true },
{ header: _t('importExport.key', { defaultMessage: 'Key' }), fieldName: 'key', sortable: true, filterable: true },
{ header: _t('importExport.value', { defaultMessage: 'Value' }), fieldName: 'value', sortable: true, filterable: true },
]}
clickable
rows={config}
@@ -340,7 +340,7 @@
>
{/if}
<LargeButton icon="icon close" on:click={closeCurrentModal} data-testid="EditJsonModal_closeButton"
>Close</LargeButton
>{_t('common.close', { defaultMessage: 'Close' })}</LargeButton
>
</div>
</div>
+14 -13
View File
@@ -16,6 +16,7 @@
import FormSubmit from '../forms/FormSubmit.svelte';
import FormButton from '../forms/FormButton.svelte';
import { apiCall } from '../utility/api';
import { _t } from '../translations';
export let editingData;
export let savingTab;
@@ -113,28 +114,28 @@
<FormProvider {initialValues}>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">{editingData ? 'Edit favorite' : 'Share / add to favorites'}</svelte:fragment>
<svelte:fragment slot="header">{editingData ? _t('favorite.editFavorite', { defaultMessage: 'Edit favorite' }) : _t('favorite.shareAddToFavorites', { defaultMessage: 'Share / add to favorites' })}</svelte:fragment>
<FormTextField label="Title" name="title" focused />
<FormTextField label="Icon" name="icon" />
<FormTextField label={_t('favorite.title', { defaultMessage: 'Title' })} name="title" focused />
<FormTextField label={_t('favorite.icon', { defaultMessage: 'Icon' })} name="icon" />
<FormTextField label="URL path" name="urlPath" />
<FormTextField label={_t('favorite.urlPath', { defaultMessage: 'URL path' })} name="urlPath" />
{#if !!savingTab && !electron && canWriteFavorite}
<FormCheckboxField label="Share as link" name="shareAsLink" />
<FormCheckboxField label={_t('favorite.shareAsLink', { defaultMessage: 'Share as link' })} name="shareAsLink" />
{/if}
<FormValues let:values>
{#if !values.shareAsLink && canWriteFavorite}
<FormCheckboxField label="Show in toolbar" name="showInToolbar" />
<FormCheckboxField label="Open on startup" name="openOnStartup" />
<FormCheckboxField label={_t('favorite.showInToolbar', { defaultMessage: 'Show in toolbar' })} name="showInToolbar" />
<FormCheckboxField label={_t('favorite.openOnStartup', { defaultMessage: 'Open on startup' })} name="openOnStartup" />
{/if}
</FormValues>
{#if !!savingTab && !!savedFile}
<FormSelectField
label="What to save"
label={_t('favorite.whatToSave', { defaultMessage: 'What to save' })}
name="whatToSave"
options={[
{ label: 'Link to file', value: 'fileName' },
{ label: 'Content', value: 'content' },
{ label: _t('favorite.linkToFile', { defaultMessage: 'Link to file' }), value: 'fileName' },
{ label: _t('favorite.content', { defaultMessage: 'Content' }), value: 'content' },
]}
/>
{/if}
@@ -142,12 +143,12 @@
<svelte:fragment slot="footer">
<FormValues let:values>
{#if !values.shareAsLink && canWriteFavorite}
<FormSubmit value="OK" on:click={handleSubmit} />
<FormSubmit value={_t('common.ok', { defaultMessage: 'OK' })} on:click={handleSubmit} />
{/if}
{#if values.shareAsLink || !canWriteFavorite}
<FormButton value="Copy link" on:click={handleCopyLink} />
<FormButton value={_t('common.copyLink', { defaultMessage: 'Copy link' })} on:click={handleCopyLink} />
{/if}
<FormButton value="Cancel" on:click={closeCurrentModal} />
<FormButton value={_t('common.cancel', { defaultMessage: 'Cancel' })} on:click={closeCurrentModal} />
</FormValues>
</svelte:fragment>
</ModalBase>
@@ -3,6 +3,7 @@
import FormStyledButton from '../buttons/FormStyledButton.svelte';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
import { _t } from '../translations';
export let onFilter;
@@ -16,35 +17,35 @@
</script>
<ModalBase {...$$restProps}>
<div slot="header">Filter multiple values</div>
<div slot="header">{_t('filterMultipleValues.filterMultipleValues', { defaultMessage: 'Filter multiple values' })}</div>
<div class="flex">
<TextAreaField rows={10} bind:value focused />
<div>
<div>
<input type="radio" bind:group value="is" id="__is" />
<label for="__is">Is one of line</label>'
<label for="__is">{_t('filterMultipleValues.isOneOfLine', { defaultMessage: 'Is one of line' })}</label>'
</div>
<div>
<input type="radio" bind:group value="is_not" id="__is_not" />
<label for="__is_not">Is not one of line</label>'
<label for="__is_not">{_t('filterMultipleValues.isNotOneOfLine', { defaultMessage: 'Is not one of line' })}</label>'
</div>
<div>
<input type="radio" bind:group value="contains" id="__contains" />
<label for="__contains">Contains</label>'
<label for="__contains">{_t('filterMultipleValues.contains', { defaultMessage: 'Contains' })}</label>'
</div>
<div>
<input type="radio" bind:group value="begins" id="__begins" />
<label for="__begins">Begins</label>'
<label for="__begins">{_t('filterMultipleValues.begins', { defaultMessage: 'Begins' })}</label>'
</div>
<div>
<input type="radio" bind:group value="ends" id="__ends" />
<label for="__ends">Ends</label>'
<label for="__ends">{_t('filterMultipleValues.ends', { defaultMessage: 'Ends' })}</label>'
</div>
</div>
</div>
<div slot="footer">
<FormStyledButton value="OK" on:click={handleOk} />
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
<FormStyledButton value={_t('common.ok', { defaultMessage: 'OK' })} on:click={handleOk} />
<FormStyledButton type="button" value={_t('common.close', { defaultMessage: 'Close' })} on:click={closeCurrentModal} />
</div>
</ModalBase>
@@ -9,6 +9,7 @@
import newQuery from '../query/newQuery';
import SqlEditor from '../query/SqlEditor.svelte';
import keycodes from '../utility/keycodes';
import { _t } from '../translations';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
@@ -77,24 +78,23 @@
<FormProvider>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">Generate SQL from data</svelte:fragment>
<svelte:fragment slot="header">{_t('generateSqlFromData.generateSqlFromData', { defaultMessage: 'Generate SQL from data' })}</svelte:fragment>
<div class="flex mb-3">
<div class="m-1 col-4">
<div class="m-1">Choose query type</div>
<div class="m-1">{_t('generateSqlFromData.chooseQueryType', { defaultMessage: 'Choose query type' })}</div>
<TableControl
rows={QUERY_TYPES.map(name => ({ name }))}
bind:selectedIndex={queryTypeIndex}
bind:domTable={domQueryType}
focusOnCreate
selectable
columns={[{ fieldName: 'name', header: 'Query type' }]}
columns={[{ fieldName: 'name', header: _t('generateSqlFromData.queryType', { defaultMessage: 'Query type' }) }]}
/>
</div>
<div class="m-1 col-4">
<div class="m-1">Value columns</div>
<div class="m-1">{_t('generateSqlFromData.valueColumns', { defaultMessage: 'Value columns' })}</div>
<CheckableColumnList
{allColumns}
@@ -120,13 +120,13 @@
<svelte:fragment slot="footer">
<FormSubmit
value="OK"
value={_t('common.ok', { defaultMessage: 'OK' })}
on:click={() => {
newQuery({ initialData: sqlPreview });
closeCurrentModal();
}}
/>
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
<FormStyledButton type="button" value={_t('common.close', { defaultMessage: 'Close' })} on:click={closeCurrentModal} />
</svelte:fragment>
</ModalBase>
</FormProvider>
@@ -29,7 +29,7 @@
<FormTextField {label} name="value" focused data-testid="InputTextModal_value" />
<svelte:fragment slot="footer">
<FormSubmit value="OK" on:click={e => handleSubmit(e.detail)} data-testid="InputTextModal_ok" />
<FormSubmit value={_t('common.ok', { defaultMessage: 'OK' })} on:click={e => handleSubmit(e.detail)} data-testid="InputTextModal_ok" />
<FormStyledButton type="button" value={_t('common.cancel', { defaultMessage: 'Cancel' })} on:click={closeCurrentModal} data-testid="InputTextModal_cancel" />
</svelte:fragment>
</ModalBase>
+15 -14
View File
@@ -9,6 +9,8 @@
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
import { _t } from '../translations';
import _ from 'lodash';
export let sql;
export let onInsert;
@@ -104,12 +106,11 @@
</script>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">Insert join</svelte:fragment>
<svelte:fragment slot="header">{_t('insertJoin.insertJoin', { defaultMessage: 'Insert join' })}</svelte:fragment>
<div class="flex mb-3">
<div class="m-1 col-3">
<div class="m-1">Existing table</div>
<div class="m-1">{_t('insertJoin.existingTable', { defaultMessage: 'Existing table' })}</div>
<TableControl
rows={sources}
focusOnCreate
@@ -118,14 +119,14 @@
selectable
on:keydown={sourceKeyDown}
columns={[
{ fieldName: 'alias', header: 'Alias' },
{ fieldName: 'name', header: 'Name' },
{ fieldName: 'alias', header: _t('insertJoin.alias', { defaultMessage: 'Alias' }) },
{ fieldName: 'name', header: _t('insertJoin.name', { defaultMessage: 'Name' }) },
]}
/>
</div>
<div class="m-1 col-6">
<div class="m-1">New table</div>
<div class="m-1">{_t('insertJoin.newTable', { defaultMessage: 'New table' })}</div>
<TableControl
rows={targets}
@@ -134,15 +135,15 @@
selectable
on:keydown={targetKeyDown}
columns={[
{ fieldName: 'baseColumns', header: 'Column from' },
{ fieldName: 'refTable', header: 'Table to' },
{ fieldName: 'refColumns', header: 'Column to' },
{ fieldName: 'baseColumns', header: _t('insertJoin.columnFrom', { defaultMessage: 'Column from' }) },
{ fieldName: 'refTable', header: _t('insertJoin.tableTo', { defaultMessage: 'Table to' }) },
{ fieldName: 'refColumns', header: _t('insertJoin.columnTo', { defaultMessage: 'Column to' }) },
]}
/>
</div>
<div class="m-1 col-3">
<div class="m-1">Join</div>
<div class="m-1">{_t('insertJoin.join', { defaultMessage: 'Join' })}</div>
<TableControl
rows={JOIN_TYPES.map(name => ({ name }))}
@@ -150,10 +151,10 @@
bind:domTable={domJoin}
selectable
on:keydown={joinKeyDown}
columns={[{ fieldName: 'name', header: 'Join type' }]}
columns={[{ fieldName: 'name', header: _t('insertJoin.joinType', { defaultMessage: 'Join type' }) }]}
/>
<div class="m-1">Alias</div>
<div class="m-1">{_t('insertJoin.alias', { defaultMessage: 'Alias' })}</div>
<TextField
value={alias}
on:input={e => {
@@ -171,13 +172,13 @@
<svelte:fragment slot="footer">
<FormStyledButton
value="OK"
value={_t('common.ok', { defaultMessage: 'OK' })}
on:click={() => {
closeCurrentModal();
onInsert(sqlPreview);
}}
/>
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
<FormStyledButton type="button" value={_t('common.close', { defaultMessage: 'Close' })} on:click={closeCurrentModal} />
</svelte:fragment>
</ModalBase>
+1 -1
View File
@@ -39,7 +39,7 @@
</script>
<ModalBase {...$$restProps} simple>
<div class="mb-2">_{_t('commandModal.showKeyCombination', { defaultMessage: 'Show desired key combination and press ENTER' })}</div>
<div class="mb-2">{_t('commandModal.showKeyCombination', { defaultMessage: 'Show desired key combination and press ENTER' })}</div>
<div class="largeFormMarker">
<TextField on:keydown={handleKeyDown} bind:value focused />
</div>
@@ -5,6 +5,7 @@
import FontIcon from '../icons/FontIcon.svelte';
import { isProApp } from '../utility/proTools';
import { openWebLink } from '../utility/simpleTools';
import { _t } from '../translations';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
@@ -15,7 +16,7 @@
<FormProvider>
<ModalBase {...$$restProps}>
<div slot="header">License limit error</div>
<div slot="header">{_t('licenseLimit.licenseLimitError', { defaultMessage: 'License limit error' })}</div>
<div class="wrapper">
<div class="icon">
@@ -23,15 +24,15 @@
</div>
<div data-testid="LicenseLimitMessageModal_message">
<p>
Cloud operation ended with error:<br />
{_t('licenseLimit.cloudOperationEndedWithError', { defaultMessage: 'Cloud operation ended with error:' })}<br />
{message}
</p>
<p>
This is a limitation of the free version of DbGate. To continue using cloud operations, please {#if !isProApp()}download
and{/if} purchase DbGate Premium.
{_t('licenseLimit.limitationMessage', { defaultMessage: 'This is a limitation of the free version of DbGate. To continue using cloud operations, please' })} {#if !isProApp()} {_t('licenseLimit.download', { defaultMessage: 'download and' })}
{/if} {_t('licenseLimit.purchase', { defaultMessage: 'purchase DbGate Premium.' })}
</p>
<p>Free version limit:</p>
<p>{_t('licenseLimit.freeVersionLimit', { defaultMessage: 'Free version limit:' })}</p>
<ul>
{#each licenseLimits || [] as limit}
<li>{limit}</li>
@@ -41,16 +42,16 @@
</div>
<div slot="footer">
<FormSubmit value="Close" on:click={closeCurrentModal} data-testid="LicenseLimitMessageModal_closeButton" />
<FormSubmit value={_t('common.close', { defaultMessage: 'Close' })} on:click={closeCurrentModal} data-testid="LicenseLimitMessageModal_closeButton" />
{#if !isProApp()}
<FormStyledButton
value="Download DbGate Premium"
value={_t('licenseLimit.downloadDbGatePremium', { defaultMessage: 'Download DbGate Premium' })}
on:click={() => openWebLink('https://www.dbgate.io/download/')}
skipWidth
/>
{/if}
<FormStyledButton
value="Purchase DbGate Premium"
value={_t('licenseLimit.purchaseDbGatePremium', { defaultMessage: 'Purchase DbGate Premium' })}
on:click={() => openWebLink('https://www.dbgate.io/purchase/premium/')}
skipWidth
/>
@@ -30,9 +30,9 @@
const { errorMessage } = resp || {};
if (errorMessage) {
showModal(ErrorMessageModal, { title: 'Error when executing operation', message: errorMessage });
showModal(ErrorMessageModal, { title: _t('error.executingOperation', { defaultMessage: 'Error when executing operation' }), message: errorMessage });
} else {
showSnackbarSuccess('Saved to database');
showSnackbarSuccess(_t('common.savedToDatabase', { defaultMessage: 'Saved to database' }));
apiCall('database-connections/sync-model', dbid);
closeCurrentModal();
}
@@ -51,7 +51,7 @@
<FormArgumentList args={driver?.newCollectionFormParams} />
<svelte:fragment slot="footer">
<FormSubmit value="OK" on:click={e => handleSubmit(e.detail)} disabled={isSaving} />
<FormSubmit value={_t('common.ok', { defaultMessage: 'OK' })} on:click={e => handleSubmit(e.detail)} disabled={isSaving} />
<FormStyledButton type="button" value={_t('common.cancel', { defaultMessage: 'Cancel' })} on:click={closeCurrentModal} />
</svelte:fragment>
</ModalBase>
@@ -102,7 +102,7 @@
{
icon: 'icon compare',
colorClass: 'color-icon-red',
title: _t('common.compare', { defaultMessage: 'Compare database' }),
title: _t('common.compareDatabase', { defaultMessage: 'Compare database' }),
description: _t('newObject.compareDescription', { defaultMessage: 'Compare database schemas' }),
command: 'database.compare',
testid: 'NewObjectModal_databaseCompare',
@@ -5,6 +5,7 @@
import FormSubmit from '../forms/FormSubmit.svelte';
import FormTextField from '../forms/FormTextField.svelte';
import { apiCall } from '../utility/api';
import { _t } from '../translations';
import getElectron from '../utility/getElectron';
import ModalBase from './ModalBase.svelte';
@@ -26,7 +27,7 @@
<FormProvider initialValues={parameterValues}>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">Edit query parameters</svelte:fragment>
<svelte:fragment slot="header">{_t('queryParameters.editQueryParameters', { defaultMessage: 'Edit query parameters' })}</svelte:fragment>
<div class="params">
{#each parameterNames as parameterName, index}
@@ -34,11 +35,11 @@
{/each}
</div>
<div>String values must be 'quoted'. You can use valid SQL expressions.</div>
<div>{_t('queryParameters.stringValuesMustBeQuoted', { defaultMessage: "String values must be 'quoted'. You can use valid SQL expressions." })}</div>
<svelte:fragment slot="footer">
<FormSubmit value="Run query" on:click={handleSubmit} />
<FormStyledButton value="Close" on:click={handleClose} />
<FormSubmit value={_t('queryParameters.runQuery', { defaultMessage: 'Run query' })} on:click={handleSubmit} />
<FormStyledButton value={_t('common.close', { defaultMessage: 'Close' })} on:click={handleClose} />
</svelte:fragment>
</ModalBase>
</FormProvider>
@@ -6,6 +6,7 @@
import FormTextField from '../forms/FormTextField.svelte';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
import { _t } from '../translations';
export let value;
export let onConfirm;
@@ -18,24 +19,24 @@
<FormProvider initialValues={{ value }}>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">Rows limit</svelte:fragment>
<svelte:fragment slot="header">{_t('query.rowsLimit', { defaultMessage: 'Rows limit' })}</svelte:fragment>
<FormTextField
label="Return only N rows from query"
label={_t('query.returnOnlyNRows', { defaultMessage: 'Return only N rows from query' })}
name="value"
focused
data-testid="RowsLimitModal_value"
placeholder="(No rows limit)"
placeholder={_t('query.noRowsLimit', { defaultMessage: '(No rows limit)' })}
/>
<svelte:fragment slot="footer">
<FormSubmit
value="OK"
value={_t('common.ok', { defaultMessage: 'OK' })}
on:click={e => handleSubmit(parseInt(e.detail.value) || null)}
data-testid="RowsLimitModal_setLimit"
/>
<FormStyledButton value="Set no limit" on:click={e => handleSubmit(null)} data-testid="RowsLimitModal_setNoLimit" />
<FormStyledButton type="button" value="Cancel" on:click={closeCurrentModal} data-testid="RowsLimitModal_cancel" />
<FormStyledButton value={_t('common.setNoLimit', { defaultMessage: 'Set no limit' })} on:click={e => handleSubmit(null)} data-testid="RowsLimitModal_setNoLimit" />
<FormStyledButton type="button" value={_t('common.cancel', { defaultMessage: 'Cancel' })} on:click={closeCurrentModal} data-testid="RowsLimitModal_cancel" />
</svelte:fragment>
</ModalBase>
</FormProvider>
@@ -10,6 +10,7 @@
import { showSnackbarError } from '../utility/snackbar';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
import { _t } from '../translations';
export let script;
export let header;
@@ -77,14 +78,14 @@
<svelte:fragment slot="footer">
{#if isRunning}
<FormStyledButton value="Stop" on:click={handleStop} data-testid="RunScriptModal_stop" />
<FormStyledButton value={_t('script.stop', { defaultMessage: 'Stop' })} on:click={handleStop} data-testid="RunScriptModal_stop" />
{:else}
<FormStyledButton value="Close" on:click={handleClose} data-testid="RunScriptModal_close" />
<FormStyledButton value={_t('common.close', { defaultMessage: 'Close' })} on:click={handleClose} data-testid="RunScriptModal_close" />
{/if}
{#if onOpenResult && !isRunning}
<FormStyledButton
value={openResultLabel || 'Open result'}
value={openResultLabel || _t('script.openResult', { defaultMessage: 'Open result' })}
on:click={() => {
closeCurrentModal();
onOpenResult();
@@ -24,10 +24,10 @@
<FormProvider initialValues={{ file, folder }}>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">Save to archive</svelte:fragment>
<svelte:fragment slot="header">{_t('archive.saveToArchive', { defaultMessage: 'Save to archive' })}</svelte:fragment>
<FormArchiveFolderSelect label="Folder" name="folder" isNative allowCreateNew skipZipFiles />
<FormTextField label="File name" name="file" disabled={fileIsReadOnly} />
<FormArchiveFolderSelect label={_t('archive.folder', { defaultMessage: 'Folder' })} name="folder" isNative allowCreateNew skipZipFiles />
<FormTextField label={_t('archive.fileName', { defaultMessage: 'File name' })} name="file" disabled={fileIsReadOnly} />
<svelte:fragment slot="footer">
<FormSubmit value={_t('common.save', { defaultMessage: 'Save' })} on:click={handleSubmit} />
+6 -6
View File
@@ -125,7 +125,7 @@
<FormTextField label="File name" name="name" focused />
{#if $cloudSigninTokenHolder && !$values['saveToTeamFolder']}
<FormCloudFolderSelect
label="Choose cloud folder"
label={_t('cloud.chooseCloudFolder', { defaultMessage: "Choose cloud folder" })}
name="cloudFolder"
isNative
requiredRoleVariants={['write', 'admin']}
@@ -134,13 +134,13 @@
: [
{
folid: '__local',
name: "Local folder (don't store on cloud)",
name: _t('cloud.localFolder', { defaultMessage: "Local folder (don't store on cloud)" }),
},
]}
/>
{/if}
{#if $configValue?.storageDatabase}
<FormCheckboxField label="Save to team folder" name="saveToTeamFolder" />
<FormCheckboxField label={_t('cloud.saveToTeamFolder', { defaultMessage: "Save to team folder" })} name="saveToTeamFolder" />
{/if}
<svelte:fragment slot="footer">
@@ -148,12 +148,12 @@
{#if electron}
<FormStyledButton
type="button"
value="Save to disk"
value={_t('common.saveToDisk', { defaultMessage: 'Save to disk' })}
on:click={async () => {
const file = await electron.showSaveDialog({
filters: [
{ name: `${fileExtension.toUpperCase()} files`, extensions: [fileExtension] },
{ name: `All files`, extensions: ['*'] },
{ name: _t('common.fileType', { defaultMessage: '{extension} files', values: {extension: fileExtension.toUpperCase()} }), extensions: [fileExtension] },
{ name: _t('common.allFiles', { defaultMessage: 'All files' }), extensions: ['*'] },
],
defaultPath: filePath || `${name}.${fileExtension}`,
properties: ['showOverwriteConfirmation'],
@@ -84,7 +84,7 @@
</div>
<div slot="footer">
<FormSubmit value="OK" on:click={handleOk} />
<FormSubmit value={_t('common.ok', {defaultMessage: "OK"})} on:click={handleOk} />
<FormButton type="button" value={_t('common.close', {defaultMessage: 'Close'})} on:click={closeCurrentModal} />
</div>
</ModalBase>
+2 -2
View File
@@ -14,7 +14,7 @@
</script>
<ModalBase {...$$restProps}>
<div slot="header">SQL Script</div>
<div slot="header">{_t('script.sqlScript', { defaultMessage: 'SQL Script' })}</div>
<div class="editor">
<SqlEditor {engine} value={sql} readOnly />
@@ -29,7 +29,7 @@
/>
<FormStyledButton
type="button"
value="Open script"
value={_t('common.openScript', { defaultMessage: "Open script" })}
on:click={() => {
newQuery({
initialData: sql,
@@ -32,6 +32,7 @@
import LoadingInfo from '../elements/LoadingInfo.svelte';
import { getObjectTypeFieldLabel } from '../utility/common';
import { apiCall } from '../utility/api';
import { _t } from '../translations';
export let conid;
export let database;
@@ -113,7 +114,7 @@
function editSql() {
openNewTab(
{
title: 'Query #',
title: _t('query.queryNumber', { defaultMessage: 'Query #' }),
icon: 'img sql-file',
tabComponent: 'QueryTab',
focused: true,
@@ -133,7 +134,7 @@
<FormProviderCore values={valuesStore} template={FormFieldTemplateTiny}>
<ModalBase {...$$restProps} fullScreen>
<svelte:fragment slot="header">
SQL Generator
{_t('sqlGenerator.sqlGenerator', { defaultMessage: 'SQL Generator' })}
<span class="dbname">
<FontIcon icon="icon database" />
{database}
@@ -146,9 +147,9 @@
<HorizontalSplitter initialValue="300px" bind:size={managerSize}>
<svelte:fragment slot="1">
<div class="flexcol flex1">
<WidgetTitle>Choose objects</WidgetTitle>
<WidgetTitle>{_t('sqlGenerator.chooseObjects', { defaultMessage: 'Choose objects' })}</WidgetTitle>
<SearchBoxWrapper>
<SearchInput placeholder="Search tables or objects" bind:value={objectsFilter} />
<SearchInput placeholder={_t('sqlGenerator.searchTablesOrObjects', { defaultMessage: 'Search tables or objects' })} bind:value={objectsFilter} />
</SearchBoxWrapper>
<WidgetsInnerContainer>
@@ -174,54 +175,53 @@
{:else}
<div class="flexcol flex1">
{#if truncated}
<ErrorInfo icon="img warn" message="SQL truncated, file size limit exceed" />
<ErrorInfo icon="img warn" message={_t('sqlGenerator.sqlTruncated', { defaultMessage: 'SQL truncated, file size limit exceed' })} />
{/if}
<div class="relative flex1">
<SqlEditor readOnly value={sqlPreview} />
</div>
</div>
{#if busy}
<LoadingInfo wrapper message="Loading SQL preview" />
<LoadingInfo wrapper message={_t('sqlGenerator.loadingSqlPreview', { defaultMessage: "Loading SQL preview" })} />
{/if}
{/if}
</svelte:fragment>
<svelte:fragment slot="2">
<div class="flexcol flex1">
<WidgetTitle>Generator settings</WidgetTitle>
<WidgetTitle>{_t('sqlGenerator.generatorSettings', { defaultMessage: 'Generator settings' })}</WidgetTitle>
<WidgetsInnerContainer>
<FormValues let:values>
<div class="obj-heading">Tables</div>
<FormCheckboxField label="Drop tables" name="dropTables" />
<div class="obj-heading">{_t('sqlGenerator.tables', { defaultMessage: 'Tables' })}</div>
<FormCheckboxField label={_t('sqlGenerator.dropTables', { defaultMessage: 'Drop tables' })} name="dropTables" />
{#if values.dropTables}
<div class="ml-2">
<FormCheckboxField label="Test if exists" name="checkIfTableExists" />
<FormCheckboxField label={_t('sqlGenerator.testIfExists', { defaultMessage: 'Test if exists' })} name="checkIfTableExists" />
</div>
{/if}
<FormCheckboxField label="Drop references" name="dropReferences" />
<FormCheckboxField label={_t('sqlGenerator.dropReferences', { defaultMessage: 'Drop references' })} name="dropReferences" />
<FormCheckboxField label="Create tables" name="createTables" />
<FormCheckboxField label="Create references" name="createReferences" />
<FormCheckboxField label="Create foreign keys" name="createForeignKeys" />
<FormCheckboxField label="Create indexes" name="createIndexes" />
<FormCheckboxField label={_t('sqlGenerator.createTables', { defaultMessage: 'Create tables' })} name="createTables" />
<FormCheckboxField label={_t('sqlGenerator.createReferences', { defaultMessage: 'Create references' })} name="createReferences" />
<FormCheckboxField label={_t('sqlGenerator.createForeignKeys', { defaultMessage: 'Create foreign keys' })} name="createForeignKeys" />
<FormCheckboxField label={_t('sqlGenerator.createIndexes', { defaultMessage: 'Create indexes' })} name="createIndexes" />
<FormCheckboxField label="Insert" name="insert" />
<FormCheckboxField label={_t('sqlGenerator.insert', { defaultMessage: 'Insert' })} name="insert" />
{#if values.insert}
<div class="ml-2">
<FormCheckboxField label="Skip autoincrement column" name="skipAutoincrementColumn" />
<FormCheckboxField label="Disable constraints" name="disableConstraints" />
<FormCheckboxField label="Omit NULL values" name="omitNulls" />
<FormCheckboxField label={_t('sqlGenerator.skipAutoincrementColumn', { defaultMessage: 'Skip autoincrement column' })} name="skipAutoincrementColumn" />
<FormCheckboxField label={_t('sqlGenerator.disableConstraints', { defaultMessage: 'Disable constraints' })} name="disableConstraints" />
<FormCheckboxField label={_t('sqlGenerator.omitNulls', { defaultMessage: 'Omit NULL values' })} name="omitNulls" />
</div>
{/if}
<FormCheckboxField label="Truncate tables (delete all rows)" name="truncate" />
<FormCheckboxField label={_t('sqlGenerator.truncate', { defaultMessage: 'Truncate tables (delete all rows)' })} name="truncate" />
{#each ['View', 'Matview', 'Procedure', 'Function', 'Trigger', 'SchedulerEvent'] as objtype}
<div class="obj-heading">{getObjectTypeFieldLabel(objtype + 's')}</div>
<FormCheckboxField label="Create" name={`create${objtype}s`} />
<FormCheckboxField label="Drop" name={`drop${objtype}s`} />
<FormCheckboxField label={_t('sqlGenerator.create', { defaultMessage: 'Create {objtype}s', values: {objtype} })} name={`create${objtype}s`} />
<FormCheckboxField label={_t('sqlGenerator.drop', { defaultMessage: `Drop ${objtype}s` })} name={`drop${objtype}s`} />
{#if values[`drop${objtype}s`]}
<div class="ml-2">
<FormCheckboxField label="Check if exists" name={`checkIf${objtype}Exists`} />
<FormCheckboxField label={_t('sqlGenerator.checkIfExists', { defaultMessage: 'Check if exists' })} name={`checkIf${objtype}Exists`} />
</div>
{/if}
{/each}
@@ -241,8 +241,8 @@
<svelte:fragment slot="footer">
<div class="flex m-2">
<LargeButton on:click={editSql} icon="icon sql-file">Edit SQL</LargeButton>
<LargeButton on:click={closeCurrentModal} icon="icon close">Close</LargeButton>
<LargeButton on:click={editSql} icon="icon sql-file">{_t('sqlGenerator.editSql', { defaultMessage: 'Edit SQL' })}</LargeButton>
<LargeButton on:click={closeCurrentModal} icon="icon close">{_t('common.close', { defaultMessage: 'Close' })}</LargeButton>
</div>
</svelte:fragment>
</ModalBase>
@@ -19,6 +19,7 @@
import FormConnectionSelect from '../impexp/FormConnectionSelect.svelte';
import FormDatabaseSelect from '../impexp/FormDatabaseSelect.svelte';
import { changeTab } from '../utility/common';
import { _t } from '../translations';
export let editingData;
export let callingTab;
@@ -46,15 +47,15 @@
<FormProvider {initialValues}>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">Switch database</svelte:fragment>
<svelte:fragment slot="header">{_t('switchDatabase.switchDatabase', { defaultMessage: 'Switch database' })}</svelte:fragment>
<FormConnectionSelect name="conid" label="Server" direction="source" isNative />
<FormDatabaseSelect conidName="conid" name="database" label="Database" isNative />
<FormConnectionSelect name="conid" label={_t('switchDatabase.server', { defaultMessage: 'Server' })} direction="source" isNative />
<FormDatabaseSelect conidName="conid" name="database" label={_t('common.database', { defaultMessage: 'Database' })} isNative />
<svelte:fragment slot="footer">
<FormValues let:values>
<FormSubmit value="OK" on:click={handleSubmit} />
<FormButton value="Cancel" on:click={closeCurrentModal} />
<FormSubmit value={_t('common.ok', { defaultMessage: 'OK' })} on:click={handleSubmit} />
<FormButton value={_t('common.cancel', { defaultMessage: 'Cancel' })} on:click={closeCurrentModal} />
</FormValues>
</svelte:fragment>
</ModalBase>
@@ -141,7 +141,7 @@
<svelte:fragment slot="footer">
{#if multiselect}
<FormSubmit
value="OK"
value={_t('common.ok', { defaultMessage: 'OK' })}
on:click={() => {
closeCurrentModal();
onConfirm(checkedKeys);
@@ -20,6 +20,7 @@
import type { ChangePerspectiveConfigFunc, PerspectiveConfig, PerspectiveCustomJoinConfig } from 'dbgate-datalib';
import uuidv1 from 'uuid/v1';
import TextField from '../forms/TextField.svelte';
import { _t } from '../translations';
export let conid;
export let database;
@@ -90,7 +91,7 @@
$: connections = useConnectionList();
$: connectionOptions = [
{ value: null, label: 'The same as root' },
{ value: null, label: _t('customJoin.theSameAsRoot', { defaultMessage: 'The same as root' }) },
..._.sortBy(
($connections || [])
// .filter(x => !x.unsaved)
@@ -107,7 +108,7 @@
$: databases = useDatabaseList({ conid: conidOverride || conid });
$: databaseOptions = [
{ value: null, label: 'The same as root' },
{ value: null, label: _t('customJoin.theSameAsRoot', { defaultMessage: 'The same as root' }) },
..._.sortBy(
($databases || []).map(db => ({
value: db.name,
@@ -147,11 +148,11 @@
<FormProvider>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">Define custom join</svelte:fragment>
<svelte:fragment slot="header">{_t('customJoin.defineCustomJoin', { defaultMessage: 'Define custom join' })}</svelte:fragment>
<div class="largeFormMarker">
<div class="row">
<div class="label col-3">Join name</div>
<div class="label col-3">{_t('customJoin.joinName', { defaultMessage: 'Join name' })}</div>
<div class="col-9">
<TextField
value={joinName}
@@ -164,7 +165,7 @@
</div>
<div class="row">
<div class="label col-3">Base table</div>
<div class="label col-3">{_t('customJoin.baseTable', { defaultMessage: 'Base table' })}</div>
<div class="col-9">
<SelectField
value={fromDesignerId}
@@ -181,7 +182,7 @@
</div>
<div class="row">
<div class="label col-3">Connection</div>
<div class="label col-3">{_t('customJoin.connection', { defaultMessage: 'Connection' })}</div>
<div class="col-9">
<SelectField
value={conidOverride}
@@ -195,7 +196,7 @@
</div>
<div class="row">
<div class="label col-3">Database</div>
<div class="label col-3">{_t('customJoin.database', { defaultMessage: 'Database' })}</div>
<div class="col-9">
<SelectField
value={databaseOverride}
@@ -212,7 +213,7 @@
<FormDatabaseSelect conidName={connectionIdField} name={databaseNameField} label="Database" /> -->
<div class="row">
<div class="label col-3">Referenced table</div>
<div class="label col-3">{_t('customJoin.referencedTable', { defaultMessage: 'Referenced table' })}</div>
<div class="col-9">
<SelectField
value={fullNameToString({ pureName: refTableName, schemaName: refSchemaName })}
@@ -239,10 +240,10 @@
<div class="row">
<div class="col-5 mr-1">
Base column - {fromTableInfo?.pureName}
{_t('customJoin.baseColumn', { defaultMessage: 'Base column' })} - {fromTableInfo?.pureName}
</div>
<div class="col-5 ml-1">
Ref column - {refTableName || '(table not set)'}
{_t('customJoin.refColumn', { defaultMessage: 'Ref column' })} - {refTableName || _t('customJoin.tableNotSet', { defaultMessage: '(table not set)' })}
</div>
</div>
@@ -286,7 +287,7 @@
</div>
<div class="col-2 button">
<FormStyledButton
value="Delete"
value={_t('common.delete', { defaultMessage: 'Delete' })}
on:click={e => {
const x = [...columns];
x.splice(index, 1);
@@ -299,7 +300,7 @@
<FormStyledButton
type="button"
value="Add column"
value={_t('customJoin.addColumn', { defaultMessage: 'Add column' })}
on:click={() => {
columns = [
...columns,
@@ -314,7 +315,7 @@
<svelte:fragment slot="footer">
<FormSubmit
value={'Save'}
value={_t('common.save', { defaultMessage: 'Save' })}
on:click={() => {
setConfig(cfg => {
const newNode = createPerspectiveNodeConfig({ pureName: refTableName, schemaName: refSchemaName });
@@ -361,7 +362,7 @@
}}
/>
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
<FormStyledButton type="button" value={_t('common.close', { defaultMessage: 'Close' })} on:click={closeCurrentModal} />
</svelte:fragment>
</ModalBase>
</FormProvider>
+14 -13
View File
@@ -6,6 +6,7 @@
import { filterName } from 'dbgate-tools';
import InlineButton from '../buttons/InlineButton.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import { _t } from '../translations';
export let items: any[];
export let showProcedure = false;
@@ -49,49 +50,49 @@
}}
>
<FontIcon icon="icon delete" padRight />
Clear
{_t('messageView.clear', { defaultMessage: "Clear" })}
</InlineButton>
{/if}
<RowsFilterSwitcher
icon="img debug"
label="Debug"
label={_t('messageView.debug', { defaultMessage: "Debug" })}
{values}
field="hideDebug"
count={items.filter(x => x.severity == 'debug').length}
/>
<RowsFilterSwitcher
icon="img info"
label="Info"
label={_t('messageView.info', { defaultMessage: "Info" })}
{values}
field="hideInfo"
count={items.filter(x => x.severity == 'info').length}
/>
<RowsFilterSwitcher
icon="img error"
label="Error"
label={_t('messageView.error', { defaultMessage: "Error" })}
{values}
field="hideError"
count={items.filter(x => x.severity == 'error').length}
/>
<SearchInput placeholder="Filter log messages" bind:value={filter} />
<SearchInput placeholder={_t('messageView.filterLogMessages', { defaultMessage: "Filter log messages" })} bind:value={filter} />
</div>
<div class="tablewrap">
<table>
<thead>
<tr>
<td class="header">Number</td>
<td class="header">Message</td>
<td class="header">Time</td>
<td class="header">Delta</td>
<td class="header">Duration</td>
<td class="header">{_t('messageView.number', { defaultMessage: 'Number' })}</td>
<td class="header">{_t('messageView.message', { defaultMessage: 'Message' })}</td>
<td class="header">{_t('messageView.time', { defaultMessage: 'Time' })}</td>
<td class="header">{_t('messageView.delta', { defaultMessage: 'Delta' })}</td>
<td class="header">{_t('messageView.duration', { defaultMessage: 'Duration' })}</td>
{#if showProcedure}
<td class="header">Procedure</td>
<td class="header">{_t('messageView.procedure', { defaultMessage: 'Procedure' })}</td>
{/if}
{#if showLine}
<td class="header">Line</td>
<td class="header">{_t('messageView.line', { defaultMessage: 'Line' })}</td>
{/if}
{#if showCaller}
<td class="header">Caller</td>
<td class="header">{_t('messageView.caller', { defaultMessage: 'Caller' })}</td>
{/if}
</tr>
</thead>
+6 -5
View File
@@ -11,6 +11,7 @@
import AllResultsTab from './AllResultsTab.svelte';
import JslChart from '../charts/JslChart.svelte';
import { isProApp } from '../utility/proTools';
import { __t, _t } from '../translations';
export let tabs = [];
export let sessionId;
@@ -68,7 +69,7 @@
...(oneTab && resultInfos.length > 0
? [
{
label: 'Results',
label: _t('resultTabs.results', { defaultMessage: 'Results' }),
isResult: true,
component: AllResultsTab,
props: {
@@ -77,14 +78,14 @@
},
]
: resultInfos.map((info, index) => ({
label: `Result ${index + 1}`,
label: _t('resultTabs.resultNumber', { defaultMessage: 'Result {number}', values: { number: index + 1 } }),
isResult: true,
component: JslDataGrid,
resultIndex: info.resultIndex,
props: { jslid: info.jslid, driver, onOpenChart: () => handleOpenChart(info.resultIndex) },
}))),
...charts.map((info, index) => ({
label: `Chart ${info.resultIndex + 1}`,
label: _t('resultTabs.chartNumber', { defaultMessage: 'Chart {number}', values: { number: info.resultIndex + 1 } }),
isChart: true,
resultIndex: info.resultIndex,
component: JslChart,
@@ -174,8 +175,8 @@
tabs={allTabs}
menu={resultInfos.length > 0 && [
oneTab
? { text: 'Every result in single tab', onClick: () => setOneTabValue(false) }
: { text: 'All results in one tab', onClick: () => setOneTabValue(true) },
? { text: _t('resultTabs.everyResultInSingleTab', { defaultMessage: 'Every result in single tab' }), onClick: () => setOneTabValue(false) }
: { text: _t('resultTabs.allResultsInOneTab', { defaultMessage: 'All results in one tab' }), onClick: () => setOneTabValue(true) },
]}
onUserChange={value => {
if (allTabs[value].isChart) {
@@ -8,6 +8,7 @@
import { downloadFromApi } from '../utility/exportFileTools';
import useEffect from '../utility/useEffect';
import Link from '../elements/Link.svelte';
import { _t } from '../translations';
export let runnerId;
export let executeNumber;
@@ -40,28 +41,28 @@
</script>
{#if !files || files.length == 0}
<ErrorInfo message="No output files" icon="img alert" />
<ErrorInfo message={_t('query.NoOutputFiles', { defaultMessage: 'No output files' })} icon="img alert" />
{:else}
<div class="flex1 scroll">
<TableControl
rows={files}
stickyHeader
columns={[
{ fieldName: 'name', header: 'Name' },
{ fieldName: 'size', header: 'Size', formatter: row => formatFileSize(row.size) },
{ fieldName: 'name', header: _t('query.Name', { defaultMessage: 'Name' }) },
{ fieldName: 'size', header: _t('query.Size', { defaultMessage: 'Size' }), formatter: row => formatFileSize(row.size) },
!electron && {
fieldName: 'download',
header: 'Download',
header: _t('query.Download', { defaultMessage: 'Download' }),
slot: 0,
},
electron && {
fieldName: 'copy',
header: 'Copy',
header: _t('query.Copy', { defaultMessage: 'Copy' }),
slot: 1,
},
electron && {
fieldName: 'show',
header: 'Show',
header: _t('query.Show', { defaultMessage: 'Show' }),
slot: 2,
},
]}
@@ -72,7 +73,7 @@
downloadFromApi(`runners/data/${runnerId}/${row.name}`, row.name);
}}
>
download
{_t('query.download', { defaultMessage: 'download' })}
</Link>
</svelte:fragment>
@@ -86,7 +87,7 @@
}
}}
>
save
{_t('query.save', { defaultMessage: 'save' })}
</Link>
</svelte:fragment>
@@ -96,7 +97,7 @@
electron.showItemInFolder(row.path);
}}
>
show
{_t('query.show', { defaultMessage: 'show' })}
</Link>
</svelte:fragment>
</TableControl>
@@ -3,6 +3,7 @@
import WidgetTitle from '../widgets/WidgetTitle.svelte';
import RunnerOutputFiles from './RunnerOutputFiles.svelte';
import SocketMessageView from './SocketMessageView.svelte';
import { _t } from '../translations';
export let runnerId;
export let executeNumber;
@@ -10,7 +11,7 @@
<HorizontalSplitter>
<div class="container" slot="1">
<WidgetTitle>Messages</WidgetTitle>
<WidgetTitle>{_t('query.Messages', { defaultMessage: 'Messages' })}</WidgetTitle>
<SocketMessageView
eventName={runnerId ? `runner-info-${runnerId}` : null}
{executeNumber}
@@ -19,7 +20,7 @@
/>
</div>
<div class="container" slot="2">
<WidgetTitle>Output files</WidgetTitle>
<WidgetTitle>{_t('query.OutputFiles', { defaultMessage: 'Output files' })}</WidgetTitle>
<RunnerOutputFiles {runnerId} {executeNumber} />
</div>
</HorizontalSplitter>
@@ -3,6 +3,7 @@
import ErrorInfo from '../elements/ErrorInfo.svelte';
import { apiOff, apiOn } from '../utility/api';
import createRef from '../utility/createRef';
import { _t } from '../translations';
import useEffect from '../utility/useEffect';
@@ -75,7 +76,7 @@
</script>
{#if showNoMessagesAlert && (!displayedMessages || displayedMessages.length == 0)}
<ErrorInfo message="No messages" icon="img alert" />
<ErrorInfo message={_t('message.NoMessages', { defaultMessage: 'No messages' })} icon="img alert" />
{:else}
<MessageView
items={displayedMessages}
@@ -1,58 +1,61 @@
<script lang="ts">
import FormCheckboxField from "../forms/FormCheckboxField.svelte";
import { _t } from "../translations";
import FontIcon from '../icons/FontIcon.svelte';
import FormValues from "../forms/FormValues.svelte";
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
import { _t } from '../translations';
import FontIcon from '../icons/FontIcon.svelte';
import FormValues from '../forms/FormValues.svelte';
</script>
<div class="wrapper">
<FormValues let:values>
<FormValues let:values>
<div class="heading">{_t('settings.behaviour', { defaultMessage: 'Behaviour' })}</div>
<FormCheckboxField
name="behaviour.useTabPreviewMode"
label={_t('settings.behaviour.useTabPreviewMode', { defaultMessage: 'Use tab preview mode' })}
defaultValue={true}
name="behaviour.jsonPreviewWrap"
label={_t('settings.behaviour.jsonPreviewWrap', { defaultMessage: 'Wrap JSON in preview' })}
defaultValue={false}
/>
<FormCheckboxField
name="behaviour.jsonPreviewWrap"
label={_t('settings.behaviour.jsonPreviewWrap', { defaultMessage: 'Wrap JSON in preview' })}
defaultValue={false}
name="behaviour.openDetailOnArrows"
label={_t('settings.behaviour.openDetailOnArrows', {
defaultMessage: 'Open detail on keyboard navigation',
})}
defaultValue={true}
disabled={values['behaviour.useTabPreviewMode'] === false}
/>
<div class="heading">{_t('settings.tabPreviewMode', { defaultMessage: 'Tab Preview Mode' })}</div>
<div class="tip">
<FontIcon icon="img tip" />
{_t('settings.behaviour.singleClickPreview', {
<FontIcon icon="img tip" />
{_t('settings.behaviour.singleClickPreview', {
defaultMessage:
'When you single-click or select a file in the "Tables, Views, Functions" view, it is shown in a preview mode and reuses an existing tab (preview tab). This is useful if you are quickly browsing tables and don\'t want every visited table to have its own tab. When you start editing the table or use double-click to open the table from the "Tables" view, a new tab is dedicated to that table.',
})}
'When you single-click or select a file in the "Tables, Views, Functions" view, it is shown in a preview mode and reuses an existing tab (preview tab). This is useful if you are quickly browsing tables and don\'t want every visited table to have its own tab. When you start editing the table or use double-click to open the table from the "Tables" view, a new tab is dedicated to that table.',
})}
</div>
<FormCheckboxField
name="behaviour.openDetailOnArrows"
label={_t('settings.behaviour.openDetailOnArrows', {
defaultMessage: 'Open detail on keyboard navigation',
})}
defaultValue={true}
disabled={values['behaviour.useTabPreviewMode'] === false}
name="behaviour.useTabPreviewMode"
label={_t('settings.behaviour.useTabPreviewMode', { defaultMessage: 'Use tab preview mode' })}
defaultValue={true}
data-testid="BehaviourSettings_useTabPreviewMode"
/>
<div class="heading">{_t('settings.confirmations', { defaultMessage: 'Confirmations' })}</div>
<FormCheckboxField
name="skipConfirm.tableDataSave"
label={_t('settings.confirmations.skipConfirm.tableDataSave', {
name="skipConfirm.tableDataSave"
label={_t('settings.confirmations.skipConfirm.tableDataSave', {
defaultMessage: 'Skip confirmation when saving table data (SQL)',
})}
})}
/>
<FormCheckboxField
name="skipConfirm.collectionDataSave"
label={_t('settings.confirmations.skipConfirm.collectionDataSave', {
name="skipConfirm.collectionDataSave"
label={_t('settings.confirmations.skipConfirm.collectionDataSave', {
defaultMessage: 'Skip confirmation when saving collection data (NoSQL)',
})}
})}
/>
</FormValues>
</FormValues>
</div>
<style>
@@ -67,4 +70,4 @@
margin-left: var(--dim-large-form-margin);
margin-top: var(--dim-large-form-margin);
}
</style>
</style>
@@ -1,81 +1,64 @@
<script lang="ts">
import CheckboxField from "../forms/CheckboxField.svelte";
import FormCheckboxField from "../forms/FormCheckboxField.svelte";
import FormFieldTemplateLarge from "../forms/FormFieldTemplateLarge.svelte";
import FormSelectField from "../forms/FormSelectField.svelte";
import FormTextField from "../forms/FormTextField.svelte";
import FormValues from "../forms/FormValues.svelte";
import { lockedDatabaseMode } from "../stores";
import { _t } from "../translations";
import CheckboxField from '../forms/CheckboxField.svelte';
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
import FormFieldTemplateLarge from '../forms/FormFieldTemplateLarge.svelte';
import FormSelectField from '../forms/FormSelectField.svelte';
import FormTextField from '../forms/FormTextField.svelte';
import FormValues from '../forms/FormValues.svelte';
import { lockedDatabaseMode } from '../stores';
import { _t } from '../translations';
</script>
<div class="wrapper">
<FormValues let:values>
<FormValues let:values>
<div class="heading">{_t('settings.connection', { defaultMessage: 'Connection' })}</div>
<FormFieldTemplateLarge
label={_t('settings.connection.showOnlyTabsFromSelectedDatabase', {
defaultMessage: 'Show only tabs from selected database',
})}
type="checkbox"
labelProps={{
onClick: () => {
$lockedDatabaseMode = !$lockedDatabaseMode;
},
}}
>
<CheckboxField checked={$lockedDatabaseMode} on:change={e => ($lockedDatabaseMode = e.target.checked)} />
</FormFieldTemplateLarge>
<FormCheckboxField
name="connection.autoRefresh"
label={_t('settings.connection.autoRefresh', {
name="connection.autoRefresh"
label={_t('settings.connection.autoRefresh', {
defaultMessage: 'Automatic refresh of database model on background',
})}
defaultValue={false}
})}
defaultValue={false}
/>
<FormTextField
name="connection.autoRefreshInterval"
label={_t('settings.connection.autoRefreshInterval', {
name="connection.autoRefreshInterval"
label={_t('settings.connection.autoRefreshInterval', {
defaultMessage: 'Interval between automatic DB structure reloads in seconds',
})}
defaultValue="30"
disabled={values['connection.autoRefresh'] === false}
})}
defaultValue="30"
disabled={values['connection.autoRefresh'] === false}
/>
<FormSelectField
label={_t('settings.connection.sshBindHost', { defaultMessage: 'Local host address for SSH connections' })}
name="connection.sshBindHost"
isNative
defaultValue="127.0.0.1"
options={[
label={_t('settings.connection.sshBindHost', { defaultMessage: 'Local host address for SSH connections' })}
name="connection.sshBindHost"
isNative
defaultValue="127.0.0.1"
options={[
{ value: '127.0.0.1', label: '127.0.0.1 (IPv4)' },
{ value: '::1', label: '::1 (IPv6)' },
{ value: 'localhost', label: 'localhost (domain name)' },
]}
]}
/>
<div class="heading">{_t('settings.session', { defaultMessage: 'Query sessions' })}</div>
<FormCheckboxField
name="session.autoClose"
label={_t('settings.session.autoClose', {
name="session.autoClose"
label={_t('settings.session.autoClose', {
defaultMessage: 'Automatic close query sessions after period without any activity',
})}
defaultValue={true}
})}
defaultValue={true}
/>
<FormTextField
name="session.autoCloseTimeout"
label={_t('settings.session.autoCloseTimeout', {
name="session.autoCloseTimeout"
label={_t('settings.session.autoCloseTimeout', {
defaultMessage: 'Interval, after which query session without activity is closed (in minutes)',
})}
defaultValue="15"
disabled={values['session.autoClose'] === false}
})}
defaultValue="15"
disabled={values['session.autoClose'] === false}
/>
</FormValues>
</FormValues>
</div>
<style>
.heading {
font-size: 20px;
@@ -84,4 +67,11 @@
margin-top: var(--dim-large-form-margin);
}
</style>
.wrapper :global(input) {
max-width: 400px;
}
.wrapper :global(select) {
max-width: 400px;
}
</style>
+101 -81
View File
@@ -1,93 +1,105 @@
<script lang="ts">
import FormCheckboxField from "../forms/FormCheckboxField.svelte";
import FormSelectField from "../forms/FormSelectField.svelte";
import FormTextField from "../forms/FormTextField.svelte";
import { _t } from "../translations";
import { isProApp } from "../utility/proTools";
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
import FormSelectField from '../forms/FormSelectField.svelte';
import FormTextField from '../forms/FormTextField.svelte';
import { _t } from '../translations';
import { isProApp } from '../utility/proTools';
</script>
<div class="wrapper">
<div class="heading">{_t('settings.dataGrid.title', { defaultMessage: 'Data grid' })}</div>
<FormTextField
name="dataGrid.pageSize"
label={_t('settings.dataGrid.pageSize', {
defaultMessage: 'Page size (number of rows for incremental loading, must be between 5 and 50000)',
})}
defaultValue="100"
/>
{#if isProApp()}
<FormCheckboxField
name="dataGrid.showHintColumns"
label={_t('settings.dataGrid.showHintColumns', { defaultMessage: 'Show foreign key hints' })}
defaultValue={true}
/>
{/if}
<!-- <FormCheckboxField name="dataGrid.showHintColumns" label="Show foreign key hints" defaultValue={true} /> -->
<div class="heading">{_t('settings.dataGrid.title', { defaultMessage: 'Data grid' })}</div>
<FormTextField
name="dataGrid.pageSize"
label={_t('settings.dataGrid.pageSize', {
defaultMessage: 'Page size (number of rows for incremental loading, must be between 5 and 50000)',
})}
defaultValue="100"
/>
{#if isProApp()}
<FormCheckboxField
name="dataGrid.showHintColumns"
label={_t('settings.dataGrid.showHintColumns', { defaultMessage: 'Show foreign key hints' })}
defaultValue={true}
data-testid="DataGridSettings_showHintColumns"
/>
{/if}
<!-- <FormCheckboxField name="dataGrid.showHintColumns" label="Show foreign key hints" defaultValue={true} /> -->
<FormCheckboxField
name="dataGrid.thousandsSeparator"
label={_t('settings.dataGrid.thousandsSeparator', {
defaultMessage: 'Use thousands separator for numbers',
})}
/>
<FormSelectField
label={_t('settings.dataGrid.thousandsSeparator', { defaultMessage: 'Thousands separator for numbers' })}
name="dataGrid.thousandsSeparatorChar"
isNative
defaultValue="none"
options={[
{ value: 'none', label: _t('settings.dataGrid.thousandsSeparator.none', { defaultMessage: 'None' }) },
{ value: 'system', label: _t('settings.dataGrid.thousandsSeparator.system', { defaultMessage: 'System' }) },
{ value: 'space', label: _t('settings.dataGrid.thousandsSeparator.space', { defaultMessage: 'Space' }) },
{
value: 'narrowspace',
label: _t('settings.dataGrid.thousandsSeparator.narrowSpace', {
defaultMessage: 'Narrow space',
}),
},
{ value: 'comma', label: _t('settings.dataGrid.thousandsSeparator.comma', { defaultMessage: 'Comma (,)' }) },
{ value: 'dot', label: _t('settings.dataGrid.thousandsSeparator.dot', { defaultMessage: 'Dot (.)' }) },
]}
/>
<FormTextField
name="dataGrid.defaultAutoRefreshInterval"
label={_t('settings.dataGrid.defaultAutoRefreshInterval', {
defaultMessage: 'Default grid auto refresh interval in seconds',
})}
defaultValue="10"
/>
<FormTextField
name="dataGrid.defaultAutoRefreshInterval"
label={_t('settings.dataGrid.defaultAutoRefreshInterval', {
defaultMessage: 'Default grid auto refresh interval in seconds',
})}
defaultValue="10"
/>
<FormCheckboxField
name="dataGrid.alignNumbersRight"
label={_t('settings.dataGrid.alignNumbersRight', { defaultMessage: 'Align numbers to right' })}
defaultValue={false}
/>
<FormCheckboxField
name="dataGrid.alignNumbersRight"
label={_t('settings.dataGrid.alignNumbersRight', { defaultMessage: 'Align numbers to right' })}
defaultValue={false}
/>
<FormTextField
name="dataGrid.collectionPageSize"
label={_t('settings.dataGrid.collectionPageSize', {
defaultMessage: 'Collection page size (for MongoDB JSON view, must be between 5 and 1000)',
})}
defaultValue="50"
/>
<FormTextField
name="dataGrid.collectionPageSize"
label={_t('settings.dataGrid.collectionPageSize', {
defaultMessage: 'Collection page size (for MongoDB JSON view, must be between 5 and 1000)',
})}
defaultValue="50"
/>
<FormSelectField
label={_t('settings.dataGrid.coloringMode', { defaultMessage: 'Row coloring mode' })}
name="dataGrid.coloringMode"
isNative
defaultValue="36"
options={[
{
value: '36',
label: _t('settings.dataGrid.coloringMode.36', { defaultMessage: 'Every 3rd and 6th row' }),
},
{
value: '2-primary',
label: _t('settings.dataGrid.coloringMode.2-primary', {
defaultMessage: 'Every 2-nd row, primary color',
}),
},
{
value: '2-secondary',
label: _t('settings.dataGrid.coloringMode.2-secondary', {
defaultMessage: 'Every 2-nd row, secondary color',
}),
},
{ value: 'none', label: _t('settings.dataGrid.coloringMode.none', { defaultMessage: 'None' }) },
]}
/>
<FormSelectField
label={_t('settings.dataGrid.coloringMode', { defaultMessage: 'Row coloring mode' })}
name="dataGrid.coloringMode"
isNative
defaultValue="36"
options={[
{
value: '36',
label: _t('settings.dataGrid.coloringMode.36', { defaultMessage: 'Every 3rd and 6th row' }),
},
{
value: '2-primary',
label: _t('settings.dataGrid.coloringMode.2-primary', {
defaultMessage: 'Every 2-nd row, primary color',
}),
},
{
value: '2-secondary',
label: _t('settings.dataGrid.coloringMode.2-secondary', {
defaultMessage: 'Every 2-nd row, secondary color',
}),
},
{ value: 'none', label: _t('settings.dataGrid.coloringMode.none', { defaultMessage: 'None' }) },
]}
/>
<FormCheckboxField
name="dataGrid.showAllColumnsWhenSearch"
label={_t('settings.dataGrid.showAllColumnsWhenSearch', {
defaultMessage: 'Show all columns when searching',
})}
defaultValue={false}
/>
<FormCheckboxField
name="dataGrid.showAllColumnsWhenSearch"
label={_t('settings.dataGrid.showAllColumnsWhenSearch', {
defaultMessage: 'Show all columns when searching',
})}
defaultValue={false}
/>
</div>
<style>
@@ -97,4 +109,12 @@ defaultValue={false}
margin-left: var(--dim-large-form-margin);
margin-top: var(--dim-large-form-margin);
}
</style>
.wrapper :global(select) {
max-width: 400px;
}
.wrapper :global(input) {
max-width: 400px;
}
</style>
@@ -10,7 +10,7 @@
<div class="wrapper">
<FormValues let:values>
<div class="heading">{_t('settings.defaultActions', { defaultMessage: 'Default actions' })}</div>
<div class="heading">{_t('settings.defaultActions', { defaultMessage: 'Default Actions' })}</div>
<FormSelectField
label={_t('settings.defaultActions.connectionClick', { defaultMessage: 'Connection click' })}
@@ -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>
@@ -4,7 +4,7 @@
</script>
<div class="wrapper">
<div class="heading">{_t('settings.externalTools', { defaultMessage: 'External tools' })}</div>
<div class="heading">{_t('settings.externalTools', { defaultMessage: 'External Tools' })}</div>
<FormTextField
name="externalTools.mysqldump"
label={_t('settings.other.externalTools.mysqldump', {
+113 -64
View File
@@ -1,92 +1,138 @@
<script lang="ts">
import { internalRedirectTo } from '../clientAuth';
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 SelectField from '../forms/SelectField.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import { showModal } from '../modals/modalTools';
import { EDITOR_KEYBINDINGS_MODES } from '../query/AceEditor.svelte';
import { currentEditorKeybindigMode, currentEditorWrapEnabled } from '../stores';
import { _t, getSelectedLanguage, setSelectedLanguage } from '../translations';
import { isMac } from '../utility/common';
import getElectron from '../utility/getElectron';
import { isProApp } from '../utility/proTools';
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.general', { defaultMessage: 'General' })}</div>
{#if electron}
<div class="heading">{_t('settings.appearance', { defaultMessage: 'Appearance' })}</div>
<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>
{#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}
<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()
name="app.useNativeMenu"
label={isMac()
? _t('settings.useNativeWindowTitle', { defaultMessage: 'Use native window title' })
: _t('settings.useSystemNativeMenu', { defaultMessage: 'Use system native menu' })}
on:change={() => {
on:change={() => {
restartWarning = true;
}}
}}
/>
{#if restartWarning}
<div class="ml-5 mb-3">
<div class="ml-5 mb-3">
<FontIcon icon="img warn" />
{_t('settings.nativeMenuRestartWarning', {
defaultMessage: 'Native menu settings will be applied after app restart',
defaultMessage: 'Native menu settings will be applied after app restart',
})}
</div>
</div>
{/if}
{/if}
{/if}
<FormCheckboxField
name="tabGroup.showServerName"
label={_t('settings.tabGroup.showServerName', {
defaultMessage: 'Show server name alongside database name in title of the tab group',
})}
defaultValue={false}
/>
<div class="heading">{_t('settings.localization', { defaultMessage: 'Localization' })}</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>
<FormCheckboxField
name="tabGroup.showServerName"
label={_t('settings.tabGroup.showServerName', {
defaultMessage: 'Show server name alongside database name in title of the tab group',
})}
defaultValue={false}
disabled={!hasPermission('settings/change')}
/>
</div>
<style>
@@ -97,4 +143,7 @@ type="combo"
margin-top: var(--dim-large-form-margin);
}
</style>
.wrapper :global(select) {
max-width: 400px;
}
</style>
@@ -1,64 +0,0 @@
<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";
</script>
<div class="wrapper">
<div class="heading">{_t('settings.other', { defaultMessage: 'Other' })}</div>
<FormTextField
name="other.gistCreateToken"
label={_t('settings.other.gistCreateToken', { defaultMessage: 'API token for creating error gists' })}
defaultValue=""
/>
<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 isProApp()}
<FormCheckboxField
name="ai.allowSendModels"
label={_t('settings.other.ai.allowSendModels', {
defaultMessage: 'Allow to send DB models and query snippets to AI service',
})}
defaultValue={false}
/>
{/if}
</div>
<style>
.heading {
font-size: 20px;
margin: 5px;
margin-left: var(--dim-large-form-margin);
margin-top: var(--dim-large-form-margin);
}
</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>
@@ -1,850 +0,0 @@
<script lang="ts">
import _ from 'lodash';
import FormStyledButton from '../buttons/FormStyledButton.svelte';
import Link from '../elements/Link.svelte';
import TabControl from '../elements/TabControl.svelte';
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 SelectField from '../forms/SelectField.svelte';
import SettingsFormProvider from '../forms/SettingsFormProvider.svelte';
import TextField from '../forms/TextField.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import ModalBase from '../modals/ModalBase.svelte';
import { closeCurrentModal } from '../modals/modalTools';
import { EDITOR_KEYBINDINGS_MODES, EDITOR_THEMES, FONT_SIZES } from '../query/AceEditor.svelte';
import SqlEditor from '../query/SqlEditor.svelte';
import {
currentEditorFontSize,
currentEditorWrapEnabled,
currentEditorTheme,
currentEditorKeybindigMode,
extensions,
selectedWidget,
lockedDatabaseMode,
visibleWidgetSideBar,
currentTheme,
getSystemTheme,
} from '../stores';
import { isMac } from '../utility/common';
import getElectron from '../utility/getElectron';
import ThemeSkeleton from './ThemeSkeleton.svelte';
import { isProApp } from '../utility/proTools';
import FormTextAreaField from '../forms/FormTextAreaField.svelte';
import { apiCall } from '../utility/api';
import { useSettings } from '../utility/metadataLoaders';
import { derived } from 'svelte/store';
import { safeFormatDate } from 'dbgate-tools';
import FormDefaultActionField from './FormDefaultActionField.svelte';
import AiSettingsTab from './AiSettingsTab.svelte';
import { _t, setSelectedLanguage } from '../translations';
import hasPermission from '../utility/hasPermission';
import ConfirmModal from '../modals/ConfirmModal.svelte';
import { showModal } from '../modals/modalTools';
import { internalRedirectTo } from '../clientAuth';
import { getSelectedLanguage } from '../translations';
const electron = getElectron();
let restartWarning = false;
let licenseKeyCheckResult = null;
export let selectedTab = 'general';
const sqlPreview = `-- example query
SELECT
MAX(Album.AlbumId) AS max_album,
MAX(Album.Title) AS max_title,
Artist.ArtistId,
'album' AS test_string,
123 AS test_number
FROM
Album
INNER JOIN Artist ON Album.ArtistId = Artist.ArtistId
GROUP BY
Artist.ArtistId
ORDER BY
Artist.Name ASC
`;
function openThemePlugins() {
closeCurrentModal();
$selectedWidget = 'plugins';
$visibleWidgetSideBar = true;
}
const settings = useSettings();
const settingsValues = derived(settings, $settings => {
if (!$settings) {
return {};
}
return $settings;
});
$: licenseKey = $settingsValues['other.licenseKey'];
let checkedLicenseKey = false;
$: if (licenseKey && !checkedLicenseKey) {
checkedLicenseKey = true;
apiCall('config/check-license', { licenseKey }).then(result => {
licenseKeyCheckResult = result;
});
}
</script>
<SettingsFormProvider>
<ModalBase {...$$restProps} noPadding fixedHeight>
<div slot="header">{_t('settings.title', { defaultMessage: 'Settings' })}</div>
<FormValues let:values>
<TabControl
bind:value={selectedTab}
isInline
inlineTabs
scrollableContentContainer
containerMaxWidth="100%"
containerMaxHeight="calc(100% - 34px)"
maxHeight100
flex1
tabs={[
hasPermission('settings/change') && {
identifier: 'general',
label: _t('settings.general', { defaultMessage: 'General' }),
slot: 1,
},
isProApp() &&
electron && {
identifier: 'license',
label: _t('settings.license', { defaultMessage: 'License' }),
slot: 7,
},
hasPermission('settings/change') && {
identifier: 'connection',
label: _t('settings.connection', { defaultMessage: 'Connection' }),
slot: 2,
},
{ identifier: 'theme', label: _t('settings.theme', { defaultMessage: 'Themes' }), slot: 3 },
hasPermission('settings/change') && {
identifier: 'default-actions',
label: _t('settings.defaultActions', { defaultMessage: 'Default Actions' }),
slot: 4,
},
hasPermission('settings/change') && {
identifier: 'behaviour',
label: _t('settings.behaviour', { defaultMessage: 'Behaviour' }),
slot: 5,
},
hasPermission('settings/change') && {
identifier: 'external-tools',
label: _t('settings.externalTools', { defaultMessage: 'External tools' }),
slot: 8,
},
hasPermission('settings/change') && {
identifier: 'other',
label: _t('settings.other', { defaultMessage: 'Other' }),
slot: 6,
},
isProApp() && hasPermission('settings/change') && { identifier: 'ai', label: 'AI', slot: 9 },
]}
>
<svelte:fragment slot="1">
{#if electron}
<div class="heading">{_t('settings.appearance', { defaultMessage: 'Appearance' })}</div>
<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',
})}
defaultValue={false}
/>
<div class="heading">{_t('settings.localization', { defaultMessage: 'Localization' })}</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>
<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} /> -->
<FormCheckboxField
name="dataGrid.thousandsSeparator"
label={_t('settings.dataGrid.thousandsSeparator', {
defaultMessage: 'Use thousands separator for numbers',
})}
/>
<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}
/>
<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' }) },
]}
/>
<FormCheckboxField
name="dataGrid.showAllColumnsWhenSearch"
label={_t('settings.dataGrid.showAllColumnsWhenSearch', {
defaultMessage: 'Show all columns when searching',
})}
defaultValue={false}
/>
<div class="heading">{_t('settings.sqlEditor', { defaultMessage: 'SQL editor' })}</div>
<div class="flex">
<div class="col-3">
<FormSelectField
label={_t('settings.sqlEditor.sqlCommandsCase', { defaultMessage: 'SQL commands case' })}
name="sqlEditor.sqlCommandsCase"
isNative
defaultValue="upperCase"
options={[
{ value: 'upperCase', label: 'UPPER CASE' },
{ value: 'lowerCase', label: 'lower case' },
]}
/>
</div>
<div class="col-3">
<FormFieldTemplateLarge
label={_t('settings.editor.keybinds', { defaultMessage: 'Editor keybinds' })}
type="combo"
>
<SelectField
isNative
defaultValue="default"
options={EDITOR_KEYBINDINGS_MODES.map(mode => ({ label: mode.label, value: mode.value }))}
value={$currentEditorKeybindigMode}
on:change={e => ($currentEditorKeybindigMode = e.detail)}
/>
</FormFieldTemplateLarge>
</div>
<div class="col-3">
<FormFieldTemplateLarge
label={_t('settings.editor.wordWrap', { defaultMessage: 'Enable word wrap' })}
type="combo"
>
<CheckboxField
checked={$currentEditorWrapEnabled}
on:change={e => ($currentEditorWrapEnabled = e.target.checked)}
/>
</FormFieldTemplateLarge>
</div>
</div>
<FormTextField
name="sqlEditor.limitRows"
label={_t('settings.sqlEditor.limitRows', { defaultMessage: 'Return only N rows from query' })}
placeholder={_t('settings.sqlEditor.limitRowsPlaceholder', { defaultMessage: '(No rows limit)' })}
/>
<FormCheckboxField
name="sqlEditor.showTableAliasesInCodeCompletion"
label={_t('settings.sqlEditor.showTableAliasesInCodeCompletion', {
defaultMessage: 'Show table aliases in code completion',
})}
defaultValue={false}
/>
<FormCheckboxField
name="sqlEditor.disableSplitByEmptyLine"
label={_t('settings.sqlEditor.disableSplitByEmptyLine', { defaultMessage: 'Disable split by empty line' })}
defaultValue={false}
/>
<FormCheckboxField
name="sqlEditor.disableExecuteCurrentLine"
label={_t('settings.sqlEditor.disableExecuteCurrentLine', {
defaultMessage: 'Disable current line execution (Execute current)',
})}
defaultValue={false}
/>
<FormCheckboxField
name="sqlEditor.hideColumnsPanel"
label={_t('settings.sqlEditor.hideColumnsPanel', { defaultMessage: 'Hide Columns/Filters panel by default' })}
defaultValue={false}
/>
</svelte:fragment>
<svelte:fragment slot="2">
<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', {
defaultMessage: 'Automatic refresh of database model on background',
})}
defaultValue={false}
/>
<FormTextField
name="connection.autoRefreshInterval"
label={_t('settings.connection.autoRefreshInterval', {
defaultMessage: 'Interval between automatic DB structure reloads in seconds',
})}
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={[
{ 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', {
defaultMessage: 'Automatic close query sessions after period without any activity',
})}
defaultValue={true}
/>
<FormTextField
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}
/>
</svelte:fragment>
<svelte:fragment slot="3">
<div class="heading">{_t('settings.appearance', { defaultMessage: 'Application theme' })}</div>
<FormFieldTemplateLarge
label={_t('settings.appearance.useSystemTheme', { defaultMessage: 'Use system theme' })}
type="checkbox"
labelProps={{
onClick: () => {
if ($currentTheme) {
$currentTheme = null;
} else {
$currentTheme = getSystemTheme();
}
},
}}
>
<CheckboxField
checked={!$currentTheme}
on:change={e => {
if (e.target['checked']) {
$currentTheme = null;
} else {
$currentTheme = getSystemTheme();
}
}}
/>
</FormFieldTemplateLarge>
<div class="themes">
{#each $extensions.themes as theme}
<ThemeSkeleton {theme} />
{/each}
</div>
<div class="m-5">
{_t('settings.appearance.moreThemes', { defaultMessage: 'More themes are available as' })}
<Link onClick={openThemePlugins}>plugins</Link>
<br />
{_t('settings.appearance.afterInstalling', {
defaultMessage:
'After installing theme plugin (try search "theme" in available extensions) new themes will be available here.',
})}
</div>
<div class="heading">{_t('settings.appearance.editorTheme', { defaultMessage: 'Editor theme' })}</div>
<div class="flex">
<div class="col-3">
<FormFieldTemplateLarge
label={_t('settings.appearance.editorTheme', { defaultMessage: 'Theme' })}
type="combo"
>
<SelectField
isNative
notSelected={_t('settings.appearance.editorTheme.default', { defaultMessage: '(use theme default)' })}
options={EDITOR_THEMES.map(theme => ({ label: theme, value: theme }))}
value={$currentEditorTheme}
on:change={e => ($currentEditorTheme = e.detail)}
/>
</FormFieldTemplateLarge>
</div>
<div class="col-3">
<FormFieldTemplateLarge
label={_t('settings.appearance.fontSize', { defaultMessage: 'Font size' })}
type="combo"
>
<SelectField
isNative
notSelected="(default)"
options={FONT_SIZES}
value={FONT_SIZES.find(x => x.value == $currentEditorFontSize) ? $currentEditorFontSize : 'custom'}
on:change={e => ($currentEditorFontSize = e.detail)}
/>
</FormFieldTemplateLarge>
</div>
<div class="col-3">
<FormFieldTemplateLarge
label={_t('settings.appearance.customSize', { defaultMessage: 'Custom size' })}
type="text"
>
<TextField
value={$currentEditorFontSize == 'custom' ? '' : $currentEditorFontSize}
on:change={e => ($currentEditorFontSize = e.target['value'])}
disabled={!!FONT_SIZES.find(x => x.value == $currentEditorFontSize) &&
$currentEditorFontSize != 'custom'}
/>
</FormFieldTemplateLarge>
</div>
<div class="col-3">
<FormTextField
name="editor.fontFamily"
label={_t('settings.appearance.fontFamily', { defaultMessage: 'Editor font family' })}
/>
</div>
</div>
<div class="editor">
<SqlEditor value={sqlPreview} readOnly />
</div>
</svelte:fragment>
<svelte:fragment slot="4">
<div class="heading">{_t('settings.defaultActions', { defaultMessage: 'Default actions' })}</div>
<FormSelectField
label={_t('settings.defaultActions.connectionClick', { defaultMessage: 'Connection click' })}
name="defaultAction.connectionClick"
isNative
defaultValue="connect"
options={[
{
value: 'openDetails',
label: _t('settings.defaultActions.connectionClick.openDetails', {
defaultMessage: 'Edit / open details',
}),
},
{
value: 'connect',
label: _t('settings.defaultActions.connectionClick.connect', { defaultMessage: 'Connect' }),
},
{
value: 'none',
label: _t('settings.defaultActions.connectionClick.none', { defaultMessage: 'Do nothing' }),
},
]}
/>
<FormSelectField
label={_t('settings.defaultActions.databaseClick', { defaultMessage: 'Database click' })}
name="defaultAction.databaseClick"
isNative
defaultValue="switch"
options={[
{
value: 'switch',
label: _t('settings.defaultActions.databaseClick.switch', { defaultMessage: 'Switch database' }),
},
{
value: 'none',
label: _t('settings.defaultActions.databaseClick.none', { defaultMessage: 'Do nothing' }),
},
]}
/>
<FormCheckboxField
name="defaultAction.useLastUsedAction"
label={_t('settings.defaultActions.useLastUsedAction', { defaultMessage: 'Use last used action' })}
defaultValue={true}
/>
<FormDefaultActionField
label={_t('settings.defaultActions.tableClick', { defaultMessage: 'Table click' })}
objectTypeField="tables"
disabled={values['defaultAction.useLastUsedAction'] !== false}
/>
<FormDefaultActionField
label={_t('settings.defaultActions.viewClick', { defaultMessage: 'View click' })}
objectTypeField="views"
disabled={values['defaultAction.useLastUsedAction'] !== false}
/>
<FormDefaultActionField
label={_t('settings.defaultActions.materializedViewClick', { defaultMessage: 'Materialized view click' })}
objectTypeField="matviews"
disabled={values['defaultAction.useLastUsedAction'] !== false}
/>
<FormDefaultActionField
label={_t('settings.defaultActions.procedureClick', { defaultMessage: 'Procedure click' })}
objectTypeField="procedures"
disabled={values['defaultAction.useLastUsedAction'] !== false}
/>
<FormDefaultActionField
label={_t('settings.defaultActions.functionClick', { defaultMessage: 'Function click' })}
objectTypeField="functions"
disabled={values['defaultAction.useLastUsedAction'] !== false}
/>
<FormDefaultActionField
label={_t('settings.defaultActions.collectionClick', { defaultMessage: 'NoSQL collection click' })}
objectTypeField="collections"
disabled={values['defaultAction.useLastUsedAction'] !== false}
/>
</svelte:fragment>
<svelte:fragment slot="5">
<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}
/>
<FormCheckboxField
name="behaviour.jsonPreviewWrap"
label={_t('settings.behaviour.jsonPreviewWrap', { defaultMessage: 'Wrap JSON in preview' })}
defaultValue={false}
/>
<div class="tip">
<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.',
})}
</div>
<FormCheckboxField
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.confirmations', { defaultMessage: 'Confirmations' })}</div>
<FormCheckboxField
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', {
defaultMessage: 'Skip confirmation when saving collection data (NoSQL)',
})}
/>
</svelte:fragment>
<svelte:fragment slot="6">
<div class="heading">{_t('settings.other', { defaultMessage: 'Other' })}</div>
<FormTextField
name="other.gistCreateToken"
label={_t('settings.other.gistCreateToken', { defaultMessage: 'API token for creating error gists' })}
defaultValue=""
/>
<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 isProApp()}
<FormCheckboxField
name="ai.allowSendModels"
label={_t('settings.other.ai.allowSendModels', {
defaultMessage: 'Allow to send DB models and query snippets to AI service',
})}
defaultValue={false}
/>
{/if}
</svelte:fragment>
<svelte:fragment slot="7">
<div class="heading">{_t('settings.other.license', { defaultMessage: 'License' })}</div>
<FormTextAreaField
name="other.licenseKey"
label={_t('settings.other.licenseKey', { defaultMessage: 'License key' })}
rows={7}
onChange={async value => {
licenseKeyCheckResult = await apiCall('config/check-license', { licenseKey: value });
}}
/>
{#if licenseKeyCheckResult}
<div class="m-3 ml-5">
{#if licenseKeyCheckResult.status == 'ok'}
<div>
<FontIcon icon="img ok" />
{_t('settings.other.licenseKey.valid', { defaultMessage: 'License key is valid' })}
</div>
{#if licenseKeyCheckResult.validTo}
<div>
{_t('settings.other.licenseKey.validTo', { defaultMessage: 'License valid to:' })}
{licenseKeyCheckResult.validTo}
</div>
{/if}
{#if licenseKeyCheckResult.expiration}
<div>
{_t('settings.other.licenseKey.expiration', { defaultMessage: 'License key expiration:' })}
<b>{safeFormatDate(licenseKeyCheckResult.expiration)}</b>
</div>
{/if}
{:else if licenseKeyCheckResult.status == 'error'}
<div>
<FontIcon icon="img error" />
{licenseKeyCheckResult.errorMessage ??
_t('settings.other.licenseKey.invalid', { defaultMessage: 'License key is invalid' })}
{#if licenseKeyCheckResult.expiration}
<div>
{_t('settings.other.licenseKey.expiration', { defaultMessage: 'License key expiration:' })}
<b>{safeFormatDate(licenseKeyCheckResult.expiration)}</b>
</div>
{/if}
</div>
{#if licenseKeyCheckResult.isExpired}
<div class="mt-2">
<FormStyledButton
value={_t('settings.other.licenseKey.checkForNew', {
defaultMessage: 'Check for new license key',
})}
skipWidth
on:click={async () => {
licenseKeyCheckResult = await apiCall('config/get-new-license', { oldLicenseKey: licenseKey });
if (licenseKeyCheckResult.licenseKey) {
apiCall('config/update-settings', { 'other.licenseKey': licenseKeyCheckResult.licenseKey });
}
}}
/>
</div>
{/if}
{/if}
</div>
{/if}
</svelte:fragment>
<svelte:fragment slot="8">
<div class="heading">{_t('settings.externalTools', { defaultMessage: 'External tools' })}</div>
<FormTextField
name="externalTools.mysqldump"
label={_t('settings.other.externalTools.mysqldump', {
defaultMessage: 'mysqldump (backup MySQL database)',
})}
defaultValue="mysqldump"
/>
<FormTextField
name="externalTools.mysql"
label={_t('settings.other.externalTools.mysql', { defaultMessage: 'mysql (restore MySQL database)' })}
defaultValue="mysql"
/>
<FormTextField
name="externalTools.mysqlPlugins"
label={_t('settings.other.externalTools.mysqlPlugins', {
defaultMessage:
'Folder with mysql plugins (for example for authentication). Set only in case of problems',
})}
defaultValue=""
/>
<FormTextField
name="externalTools.pg_dump"
label={_t('settings.other.externalTools.pg_dump', {
defaultMessage: 'pg_dump (backup PostgreSQL database)',
})}
defaultValue="pg_dump"
/>
<FormTextField
name="externalTools.psql"
label={_t('settings.other.externalTools.psql', { defaultMessage: 'psql (restore PostgreSQL database)' })}
defaultValue="psql"
/>
</svelte:fragment>
<svelte:fragment slot="9">
<AiSettingsTab {values} />
</svelte:fragment>
</TabControl>
</FormValues>
<div slot="footer">
<!-- <FormSubmit value="OK" on:click={handleOk} /> -->
<FormStyledButton value={_t('common.close', { defaultMessage: 'Close' })} on:click={closeCurrentModal} />
</div>
</ModalBase>
</SettingsFormProvider>
<style>
.heading {
font-size: 20px;
margin: 5px;
margin-left: var(--dim-large-form-margin);
margin-top: var(--dim-large-form-margin);
}
.tip {
margin-left: var(--dim-large-form-margin);
margin-top: var(--dim-large-form-margin);
}
.themes {
overflow-x: scroll;
display: flex;
}
.editor {
position: relative;
height: 200px;
width: 400px;
margin-left: var(--dim-large-form-margin);
}
</style>

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