mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-28 15:16:00 +00:00
Merge branch 'master' into feature/duckdb-2
This commit is contained in:
2
.github/workflows/build-app-pro-beta.yaml
vendored
2
.github/workflows/build-app-pro-beta.yaml
vendored
@@ -39,7 +39,7 @@ jobs:
|
|||||||
repository: dbgate/dbgate-pro
|
repository: dbgate/dbgate-pro
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
path: dbgate-pro
|
path: dbgate-pro
|
||||||
ref: 54f1f9a82fbcca0307aa5c83f765b33af3325466
|
ref: 00da2abe10e1ec8a3887b49dfabd42ccda365514
|
||||||
- name: Merge dbgate/dbgate-pro
|
- name: Merge dbgate/dbgate-pro
|
||||||
run: |
|
run: |
|
||||||
mkdir ../dbgate-pro
|
mkdir ../dbgate-pro
|
||||||
|
|||||||
2
.github/workflows/build-app-pro.yaml
vendored
2
.github/workflows/build-app-pro.yaml
vendored
@@ -39,7 +39,7 @@ jobs:
|
|||||||
repository: dbgate/dbgate-pro
|
repository: dbgate/dbgate-pro
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
path: dbgate-pro
|
path: dbgate-pro
|
||||||
ref: 54f1f9a82fbcca0307aa5c83f765b33af3325466
|
ref: 00da2abe10e1ec8a3887b49dfabd42ccda365514
|
||||||
- name: Merge dbgate/dbgate-pro
|
- name: Merge dbgate/dbgate-pro
|
||||||
run: |
|
run: |
|
||||||
mkdir ../dbgate-pro
|
mkdir ../dbgate-pro
|
||||||
|
|||||||
2
.github/workflows/build-cloud-pro.yaml
vendored
2
.github/workflows/build-cloud-pro.yaml
vendored
@@ -39,7 +39,7 @@ jobs:
|
|||||||
repository: dbgate/dbgate-pro
|
repository: dbgate/dbgate-pro
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
path: dbgate-pro
|
path: dbgate-pro
|
||||||
ref: 54f1f9a82fbcca0307aa5c83f765b33af3325466
|
ref: 00da2abe10e1ec8a3887b49dfabd42ccda365514
|
||||||
- name: Merge dbgate/dbgate-pro
|
- name: Merge dbgate/dbgate-pro
|
||||||
run: |
|
run: |
|
||||||
mkdir ../dbgate-pro
|
mkdir ../dbgate-pro
|
||||||
|
|||||||
2
.github/workflows/build-docker-pro.yaml
vendored
2
.github/workflows/build-docker-pro.yaml
vendored
@@ -44,7 +44,7 @@ jobs:
|
|||||||
repository: dbgate/dbgate-pro
|
repository: dbgate/dbgate-pro
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
path: dbgate-pro
|
path: dbgate-pro
|
||||||
ref: 54f1f9a82fbcca0307aa5c83f765b33af3325466
|
ref: 00da2abe10e1ec8a3887b49dfabd42ccda365514
|
||||||
- name: Merge dbgate/dbgate-pro
|
- name: Merge dbgate/dbgate-pro
|
||||||
run: |
|
run: |
|
||||||
mkdir ../dbgate-pro
|
mkdir ../dbgate-pro
|
||||||
|
|||||||
2
.github/workflows/build-npm-pro.yaml
vendored
2
.github/workflows/build-npm-pro.yaml
vendored
@@ -32,7 +32,7 @@ jobs:
|
|||||||
repository: dbgate/dbgate-pro
|
repository: dbgate/dbgate-pro
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
path: dbgate-pro
|
path: dbgate-pro
|
||||||
ref: 54f1f9a82fbcca0307aa5c83f765b33af3325466
|
ref: 00da2abe10e1ec8a3887b49dfabd42ccda365514
|
||||||
- name: Merge dbgate/dbgate-pro
|
- name: Merge dbgate/dbgate-pro
|
||||||
run: |
|
run: |
|
||||||
mkdir ../dbgate-pro
|
mkdir ../dbgate-pro
|
||||||
|
|||||||
2
.github/workflows/e2e-pro.yaml
vendored
2
.github/workflows/e2e-pro.yaml
vendored
@@ -26,7 +26,7 @@ jobs:
|
|||||||
repository: dbgate/dbgate-pro
|
repository: dbgate/dbgate-pro
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
path: dbgate-pro
|
path: dbgate-pro
|
||||||
ref: 54f1f9a82fbcca0307aa5c83f765b33af3325466
|
ref: 00da2abe10e1ec8a3887b49dfabd42ccda365514
|
||||||
- name: Merge dbgate/dbgate-pro
|
- name: Merge dbgate/dbgate-pro
|
||||||
run: |
|
run: |
|
||||||
mkdir ../dbgate-pro
|
mkdir ../dbgate-pro
|
||||||
|
|||||||
5
.github/workflows/run-tests.yaml
vendored
5
.github/workflows/run-tests.yaml
vendored
@@ -37,6 +37,11 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd packages/datalib
|
cd packages/datalib
|
||||||
yarn test:ci
|
yarn test:ci
|
||||||
|
- name: Tools tests
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
cd packages/tools
|
||||||
|
yarn test:ci
|
||||||
- uses: tanmen/jest-reporter@v1
|
- uses: tanmen/jest-reporter@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
|
|||||||
14
CHANGELOG.md
14
CHANGELOG.md
@@ -8,6 +8,18 @@ Builds:
|
|||||||
- linux - application for linux
|
- linux - application for linux
|
||||||
- win - application for Windows
|
- win - application for Windows
|
||||||
|
|
||||||
|
### 6.3.2
|
||||||
|
- ADDED: "Use system theme" switch, use changed system theme without restart #1084
|
||||||
|
- ADDED: "Skip SETNAME instruction" option for Redis #1077
|
||||||
|
- FIXED: Clickhouse views are now available even for user with limited permissions #1076
|
||||||
|
- ADDED: Multiple-token search delimited with comma (=OR) in structure search boxes
|
||||||
|
- CHANGED: When filtering columns in data browser, data view shows only filtered columns
|
||||||
|
- ADDED: Advanced settings for diagrams (Premium)
|
||||||
|
- ADDED: Diagrams - zoom with Ctrl+mouse wheel
|
||||||
|
- FIXED: Scrollable diagram exports + scroll by mouse drag
|
||||||
|
- FIXED: Fixed many problems in diagrams when zoom is applied
|
||||||
|
- FIXED: Correctly end connection process after succesful/unsuccesful connect
|
||||||
|
|
||||||
### 6.3.0
|
### 6.3.0
|
||||||
- ADDED: Support for libSQL and Turso (Premium)
|
- ADDED: Support for libSQL and Turso (Premium)
|
||||||
- ADDED: Native backup and restore database for MySQL and PostgreSQL (Premium)
|
- ADDED: Native backup and restore database for MySQL and PostgreSQL (Premium)
|
||||||
@@ -29,7 +41,7 @@ Builds:
|
|||||||
- FIXED: Scroll in XML cell view, XML view respect themes
|
- FIXED: Scroll in XML cell view, XML view respect themes
|
||||||
- REMOVED: armv7l build for Linux (because of problems with glibc compatibility)
|
- REMOVED: armv7l build for Linux (because of problems with glibc compatibility)
|
||||||
- CHANGED: Upgraded to node:22 for docker builds
|
- CHANGED: Upgraded to node:22 for docker builds
|
||||||
- CHANGED: Upgraded SQLite engine version (better-sqlite3@11.8.1)
|
- CHANGED: Upgraded SQLite engine version
|
||||||
|
|
||||||
### 6.2.0
|
### 6.2.0
|
||||||
- ADDED: Query AI Assistant (Premium)
|
- ADDED: Query AI Assistant (Premium)
|
||||||
|
|||||||
@@ -357,6 +357,7 @@ function createWindow() {
|
|||||||
title: isProApp() ? 'DbGate Premium' : 'DbGate',
|
title: isProApp() ? 'DbGate Premium' : 'DbGate',
|
||||||
frame: useNativeMenu,
|
frame: useNativeMenu,
|
||||||
titleBarStyle: useNativeMenu ? undefined : 'hidden',
|
titleBarStyle: useNativeMenu ? undefined : 'hidden',
|
||||||
|
backgroundColor: electron.nativeTheme.shouldUseDarkColors ? '#111111' : undefined,
|
||||||
...bounds,
|
...bounds,
|
||||||
icon: os.platform() == 'win32' ? 'icon.ico' : path.resolve(__dirname, '../icon.png'),
|
icon: os.platform() == 'win32' ? 'icon.ico' : path.resolve(__dirname, '../icon.png'),
|
||||||
partition: isProApp() ? 'persist:dbgate-premium' : 'persist:dbgate',
|
partition: isProApp() ? 'persist:dbgate-premium' : 'persist:dbgate',
|
||||||
|
|||||||
@@ -13,16 +13,22 @@ describe('Add connection', () => {
|
|||||||
it('adds connection', () => {
|
it('adds connection', () => {
|
||||||
// cy.get('[data-testid=ConnectionList_buttonNewConnection]').click();
|
// cy.get('[data-testid=ConnectionList_buttonNewConnection]').click();
|
||||||
cy.get('[data-testid=ConnectionDriverFields_connectionType]').select('MySQL');
|
cy.get('[data-testid=ConnectionDriverFields_connectionType]').select('MySQL');
|
||||||
cy.themeshot('connection');
|
cy.themeshot('new-connection');
|
||||||
cy.get('[data-testid=ConnectionDriverFields_user]').clear().type('root');
|
cy.get('[data-testid=ConnectionDriverFields_user]').clear().type('root');
|
||||||
cy.get('[data-testid=ConnectionDriverFields_password]').clear().type('Pwd2020Db');
|
cy.get('[data-testid=ConnectionDriverFields_password]').clear().type('Pwd2020Db');
|
||||||
cy.get('[data-testid=ConnectionDriverFields_port]').clear().type('16004');
|
cy.get('[data-testid=ConnectionDriverFields_port]').clear().type('16004');
|
||||||
cy.get('[data-testid=ConnectionDriverFields_displayName]').clear().type('test-mysql-1');
|
cy.get('[data-testid=ConnectionDriverFields_displayName]').clear().type('test-mysql-1');
|
||||||
|
|
||||||
// test connection
|
// test connection
|
||||||
cy.get('[data-testid=ConnectionTab_buttonTest]').click();
|
cy.testid('ConnectionTab_buttonTest').click();
|
||||||
cy.contains('Connected:');
|
cy.contains('Connected:');
|
||||||
|
|
||||||
|
cy.testid('ConnectionTab_tabSshTunnel').click();
|
||||||
|
cy.testid('ConnectionTab_tabControlContent').themeshot('connection-sshtunnel-window', { padding: 50 });
|
||||||
|
|
||||||
|
cy.testid('ConnectionTab_tabSsl').click();
|
||||||
|
cy.testid('ConnectionTab_tabControlContent').themeshot('connection-ssl-window', { padding: 50 });
|
||||||
|
|
||||||
// save and connect
|
// save and connect
|
||||||
cy.get('[data-testid=ConnectionTab_buttonSave]').click();
|
cy.get('[data-testid=ConnectionTab_buttonSave]').click();
|
||||||
cy.get('[data-testid=ConnectionTab_buttonConnect]').click();
|
cy.get('[data-testid=ConnectionTab_buttonConnect]').click();
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ describe('Data browser data', () => {
|
|||||||
cy.contains('Finished job script');
|
cy.contains('Finished job script');
|
||||||
cy.contains('Album.csv');
|
cy.contains('Album.csv');
|
||||||
cy.testid('WidgetIconPanel_database').click();
|
cy.testid('WidgetIconPanel_database').click();
|
||||||
cy.themeshot('exportcsv');
|
cy.themeshot('configure-export-csv');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Data archive editor - macros', () => {
|
it('Data archive editor - macros', () => {
|
||||||
@@ -42,7 +42,7 @@ describe('Data browser data', () => {
|
|||||||
cy.contains('Out Of Exile').click({ shiftKey: true });
|
cy.contains('Out Of Exile').click({ shiftKey: true });
|
||||||
cy.contains('Change text case').click();
|
cy.contains('Change text case').click();
|
||||||
cy.contains('AUDIOSLAVE');
|
cy.contains('AUDIOSLAVE');
|
||||||
cy.themeshot('freetable');
|
cy.themeshot('data-archive-macros');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Load table data', () => {
|
it('Load table data', () => {
|
||||||
@@ -87,19 +87,19 @@ describe('Data browser data', () => {
|
|||||||
cy.contains('Album').click();
|
cy.contains('Album').click();
|
||||||
cy.testid('DataFilterControl_input_Title').type('Rock{enter}');
|
cy.testid('DataFilterControl_input_Title').type('Rock{enter}');
|
||||||
cy.contains('Rows: 7');
|
cy.contains('Rows: 7');
|
||||||
cy.testid('DataFilterControl_input_AlbumId').type('>10{enter}');
|
cy.testid('DataFilterControl_input_AlbumId').type('>10xxx{enter}');
|
||||||
cy.contains('Rows: 5');
|
cy.contains('Rows: 7');
|
||||||
cy.testid('DataFilterControl_filtermenu_Title').click();
|
cy.testid('DataFilterControl_filtermenu_Title').click();
|
||||||
cy.themeshot('filter');
|
// hide what is not needed
|
||||||
|
cy.testid('WidgetIconPanel_database').click();
|
||||||
|
cy.testid('DataGrid_itemReferences').click();
|
||||||
|
cy.themeshot('data-browser-filter');
|
||||||
cy.testid('DataGridCore_button_clearFilters').click();
|
cy.testid('DataGridCore_button_clearFilters').click();
|
||||||
cy.contains('Rows: 347');
|
cy.contains('Rows: 347');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Data grid screenshots', () => {
|
it('Data grid screenshots', () => {
|
||||||
cy.contains('MySql-connection').click();
|
cy.contains('MySql-connection').click();
|
||||||
cy.window().then(win => {
|
|
||||||
win.__changeCurrentTheme('theme-dark');
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.contains('MyChinook').click();
|
cy.contains('MyChinook').click();
|
||||||
|
|
||||||
@@ -114,16 +114,25 @@ describe('Data browser data', () => {
|
|||||||
cy.contains('PgChinook').click();
|
cy.contains('PgChinook').click();
|
||||||
cy.contains('customer').click();
|
cy.contains('customer').click();
|
||||||
cy.contains('Leonie').click();
|
cy.contains('Leonie').click();
|
||||||
cy.themeshot('datagrid');
|
cy.themeshot('common-data-browser');
|
||||||
|
|
||||||
cy.contains('invoice').click();
|
cy.contains('invoice').click();
|
||||||
cy.contains('invoice_line (invoice_id)').click();
|
cy.contains('invoice_line (invoice_id)').click();
|
||||||
cy.themeshot('masterdetail');
|
cy.themeshot('data-browser-master-detail');
|
||||||
|
|
||||||
cy.contains('9, Place Louis Barthou').click();
|
cy.contains('9, Place Louis Barthou').click();
|
||||||
cy.contains('Switch to form').click();
|
cy.contains('Switch to form').click();
|
||||||
cy.contains('Switch to table'); // test that we are in form view
|
cy.contains('Switch to table'); // test that we are in form view
|
||||||
cy.themeshot('formview');
|
cy.themeshot('data-browser-form-view');
|
||||||
|
});
|
||||||
|
|
||||||
|
it.only('Column search', () => {
|
||||||
|
cy.contains('MySql-connection').click();
|
||||||
|
cy.contains('MyChinook').click();
|
||||||
|
cy.contains('Customer').click();
|
||||||
|
cy.testid('ColumnManager_searchColumns').clear().type('name,id{enter}');
|
||||||
|
cy.contains('Company').should('not.exist');
|
||||||
|
cy.themeshot('data-browser-column-search');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('SQL Gen', () => {
|
it('SQL Gen', () => {
|
||||||
@@ -131,7 +140,7 @@ describe('Data browser data', () => {
|
|||||||
cy.contains('PgChinook').rightclick();
|
cy.contains('PgChinook').rightclick();
|
||||||
cy.contains('SQL Generator').click();
|
cy.contains('SQL Generator').click();
|
||||||
cy.contains('Check all').click();
|
cy.contains('Check all').click();
|
||||||
cy.themeshot('sqlgen');
|
cy.themeshot('sql-generator');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Macros in DB', () => {
|
it('Macros in DB', () => {
|
||||||
@@ -146,7 +155,7 @@ describe('Data browser data', () => {
|
|||||||
cy.testid('DataGrid_itemMacros').click();
|
cy.testid('DataGrid_itemMacros').click();
|
||||||
cy.contains('Change text case').click();
|
cy.contains('Change text case').click();
|
||||||
cy.contains('NIELSEN');
|
cy.contains('NIELSEN');
|
||||||
cy.themeshot('macros');
|
cy.themeshot('data-browser-macros');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Perspectives', () => {
|
it('Perspectives', () => {
|
||||||
@@ -162,7 +171,7 @@ describe('Data browser data', () => {
|
|||||||
// check track is loaded
|
// check track is loaded
|
||||||
cy.contains('Put The Finger On You');
|
cy.contains('Put The Finger On You');
|
||||||
|
|
||||||
cy.themeshot('perspective1');
|
cy.themeshot('perspective-designer');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Query editor - code completion', () => {
|
it('Query editor - code completion', () => {
|
||||||
@@ -176,7 +185,7 @@ describe('Data browser data', () => {
|
|||||||
cy.get('body').realType('select * from Album where Album.');
|
cy.get('body').realType('select * from Album where Album.');
|
||||||
// code completion
|
// code completion
|
||||||
cy.contains('ArtistId');
|
cy.contains('ArtistId');
|
||||||
cy.themeshot('query');
|
cy.themeshot('query-editor-code-completion');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Query editor - join wizard', () => {
|
it('Query editor - join wizard', () => {
|
||||||
@@ -189,7 +198,7 @@ describe('Data browser data', () => {
|
|||||||
cy.get('body').realPress(['Control', 'j']);
|
cy.get('body').realPress(['Control', 'j']);
|
||||||
// JOIN wizard
|
// JOIN wizard
|
||||||
cy.contains('INNER JOIN Customer ON Invoice.CustomerId = Customer.CustomerId');
|
cy.contains('INNER JOIN Customer ON Invoice.CustomerId = Customer.CustomerId');
|
||||||
cy.themeshot('joinwizard');
|
cy.themeshot('query-editor-join-wizard');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Mongo JSON data view', () => {
|
it('Mongo JSON data view', () => {
|
||||||
@@ -206,7 +215,7 @@ describe('Data browser data', () => {
|
|||||||
cy.testid('WidgetIconPanel_cell-data').click();
|
cy.testid('WidgetIconPanel_cell-data').click();
|
||||||
// test JSON view
|
// test JSON view
|
||||||
cy.contains('Country: "Brazil"');
|
cy.contains('Country: "Brazil"');
|
||||||
cy.themeshot('mongoquery');
|
cy.themeshot('mongo-query-json-view');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('SQL preview', () => {
|
it('SQL preview', () => {
|
||||||
@@ -216,7 +225,7 @@ describe('Data browser data', () => {
|
|||||||
cy.contains('Show SQL').click();
|
cy.contains('Show SQL').click();
|
||||||
// index should be part of create script
|
// index should be part of create script
|
||||||
cy.contains('CREATE INDEX `IFK_CustomerSupportRepId`');
|
cy.contains('CREATE INDEX `IFK_CustomerSupportRepId`');
|
||||||
cy.themeshot('sqlpreview');
|
cy.themeshot('sql-preview-create-index');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Query designer', () => {
|
it('Query designer', () => {
|
||||||
@@ -225,7 +234,7 @@ describe('Data browser data', () => {
|
|||||||
cy.testid('WidgetIconPanel_file').click();
|
cy.testid('WidgetIconPanel_file').click();
|
||||||
cy.contains('customer').click();
|
cy.contains('customer').click();
|
||||||
// cy.contains('left join').rightclick();
|
// cy.contains('left join').rightclick();
|
||||||
cy.themeshot('querydesigner');
|
cy.themeshot('query-designer');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Database diagram', () => {
|
it('Database diagram', () => {
|
||||||
@@ -236,7 +245,7 @@ describe('Data browser data', () => {
|
|||||||
cy.testid('WidgetIconPanel_file').click();
|
cy.testid('WidgetIconPanel_file').click();
|
||||||
// check diagram is shown
|
// check diagram is shown
|
||||||
cy.contains('MediaTypeId');
|
cy.contains('MediaTypeId');
|
||||||
cy.themeshot('diagram');
|
cy.themeshot('database-diagram');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Charts', () => {
|
it('Charts', () => {
|
||||||
@@ -245,7 +254,7 @@ describe('Data browser data', () => {
|
|||||||
cy.contains('line-chart').click();
|
cy.contains('line-chart').click();
|
||||||
cy.testid('TabsPanel_buttonSplit').click();
|
cy.testid('TabsPanel_buttonSplit').click();
|
||||||
cy.testid('WidgetIconPanel_file').click();
|
cy.testid('WidgetIconPanel_file').click();
|
||||||
cy.themeshot('charts');
|
cy.themeshot('view-split-charts');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Keyboard configuration', () => {
|
it('Keyboard configuration', () => {
|
||||||
@@ -253,7 +262,7 @@ describe('Data browser data', () => {
|
|||||||
cy.contains('Keyboard shortcuts').click();
|
cy.contains('Keyboard shortcuts').click();
|
||||||
cy.contains('dataForm.refresh').click();
|
cy.contains('dataForm.refresh').click();
|
||||||
cy.testid('CommandModal_keyboardButton').click();
|
cy.testid('CommandModal_keyboardButton').click();
|
||||||
cy.themeshot('keyboard');
|
cy.themeshot('keyboard-configuration');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Command palette', () => {
|
it('Command palette', () => {
|
||||||
@@ -264,7 +273,7 @@ describe('Data browser data', () => {
|
|||||||
// cy.realPress('F1');
|
// cy.realPress('F1');
|
||||||
cy.realPress('PageDown');
|
cy.realPress('PageDown');
|
||||||
cy.realPress('PageDown');
|
cy.realPress('PageDown');
|
||||||
cy.testid('CommandPalette_main').themeshot('commandpalette', { padding: 50 });
|
cy.testid('CommandPalette_main').themeshot('command-palette', { padding: 50 });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Show map', () => {
|
it('Show map', () => {
|
||||||
@@ -277,7 +286,7 @@ describe('Data browser data', () => {
|
|||||||
cy.contains('13.9').click({ shiftKey: true });
|
cy.contains('13.9').click({ shiftKey: true });
|
||||||
cy.testid('WidgetIconPanel_cell-data').click();
|
cy.testid('WidgetIconPanel_cell-data').click();
|
||||||
cy.wait(2000);
|
cy.wait(2000);
|
||||||
cy.themeshot('map');
|
cy.themeshot('cell-map-view');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Search in connections', () => {
|
it('Search in connections', () => {
|
||||||
@@ -289,7 +298,7 @@ describe('Data browser data', () => {
|
|||||||
cy.contains('Album').click();
|
cy.contains('Album').click();
|
||||||
cy.testid('SqlObjectList_searchMenuDropDown').click();
|
cy.testid('SqlObjectList_searchMenuDropDown').click();
|
||||||
cy.contains('Column name').click();
|
cy.contains('Column name').click();
|
||||||
cy.themeshot('connsearch');
|
cy.themeshot('search-in-connections');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Plugin tab', () => {
|
it('Plugin tab', () => {
|
||||||
@@ -299,7 +308,7 @@ describe('Data browser data', () => {
|
|||||||
cy.contains('Total white theme');
|
cy.contains('Total white theme');
|
||||||
// wait for load logos
|
// wait for load logos
|
||||||
cy.wait(2000);
|
cy.wait(2000);
|
||||||
cy.themeshot('plugin');
|
cy.themeshot('view-plugin-tab');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Edit mongo data JSON', () => {
|
it('Edit mongo data JSON', () => {
|
||||||
@@ -326,7 +335,7 @@ describe('Data browser data', () => {
|
|||||||
cy.contains('Helena').rightclick();
|
cy.contains('Helena').rightclick();
|
||||||
cy.contains('Delete document').click();
|
cy.contains('Delete document').click();
|
||||||
cy.contains('Save').click();
|
cy.contains('Save').click();
|
||||||
cy.themeshot('mongosave');
|
cy.themeshot('save-changes-mongodb');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Edit mongo data JSON', () => {
|
it('Edit mongo data JSON', () => {
|
||||||
@@ -340,7 +349,7 @@ describe('Data browser data', () => {
|
|||||||
cy.testid('ColumnManagerRow_checkbox__id').click();
|
cy.testid('ColumnManagerRow_checkbox__id').click();
|
||||||
cy.testid('DataFilterControl_input_countries.1').type('EXISTS{enter}');
|
cy.testid('DataFilterControl_input_countries.1').type('EXISTS{enter}');
|
||||||
cy.testid('WidgetIconPanel_cell-data').click();
|
cy.testid('WidgetIconPanel_cell-data').click();
|
||||||
cy.themeshot('collection');
|
cy.themeshot('mongodb-json-cell-view');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Table structure editor', () => {
|
it('Table structure editor', () => {
|
||||||
@@ -349,10 +358,10 @@ describe('Data browser data', () => {
|
|||||||
cy.contains('Customer').rightclick();
|
cy.contains('Customer').rightclick();
|
||||||
cy.contains('Open structure').click();
|
cy.contains('Open structure').click();
|
||||||
cy.contains('varchar(40)');
|
cy.contains('varchar(40)');
|
||||||
cy.themeshot('structure');
|
cy.themeshot('table-structure-editor');
|
||||||
cy.contains('EmployeeId').click();
|
cy.contains('EmployeeId').click();
|
||||||
cy.contains('Ref column - Employee');
|
cy.contains('Ref column - Employee');
|
||||||
cy.themeshot('fkeditor');
|
cy.themeshot('foreign-key-editor');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Compare database', () => {
|
it('Compare database', () => {
|
||||||
@@ -364,10 +373,10 @@ describe('Data browser data', () => {
|
|||||||
cy.testid('CompareModelTab_gridObjects_Customer_Customer').click();
|
cy.testid('CompareModelTab_gridObjects_Customer_Customer').click();
|
||||||
cy.testid('WidgetIconPanel_database').click();
|
cy.testid('WidgetIconPanel_database').click();
|
||||||
cy.testid('CompareModelTab_tabDdl').click();
|
cy.testid('CompareModelTab_tabDdl').click();
|
||||||
cy.themeshot('dbcompare');
|
cy.themeshot('compare-database-models');
|
||||||
cy.contains('Settings').click();
|
cy.contains('Settings').click();
|
||||||
cy.testid('CompareModelTab_tabOperations').click();
|
cy.testid('CompareModelTab_tabOperations').click();
|
||||||
cy.themeshot('comparesettings');
|
cy.themeshot('compare-database-settings');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Query editor - AI assistant', () => {
|
it('Query editor - AI assistant', () => {
|
||||||
@@ -382,7 +391,7 @@ describe('Data browser data', () => {
|
|||||||
cy.contains('Use this', { timeout: 10000 }).click();
|
cy.contains('Use this', { timeout: 10000 }).click();
|
||||||
cy.testid('QueryTab_executeButton').click();
|
cy.testid('QueryTab_executeButton').click();
|
||||||
cy.contains('Balls to the Wall');
|
cy.contains('Balls to the Wall');
|
||||||
cy.themeshot('aiassistant');
|
cy.themeshot('ai-assistant');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Modify data', () => {
|
it('Modify data', () => {
|
||||||
@@ -408,7 +417,7 @@ describe('Data browser data', () => {
|
|||||||
cy.contains('INSERT INTO `Employee`');
|
cy.contains('INSERT INTO `Employee`');
|
||||||
cy.contains("SET `FirstName`='Jane'");
|
cy.contains("SET `FirstName`='Jane'");
|
||||||
cy.contains('DELETE FROM `Employee`');
|
cy.contains('DELETE FROM `Employee`');
|
||||||
cy.themeshot('modifydata');
|
cy.themeshot('data-browser-save-changes');
|
||||||
|
|
||||||
// cy.testid('ConfirmSqlModal_okButton').click();
|
// cy.testid('ConfirmSqlModal_okButton').click();
|
||||||
// cy.contains('Cannot delete or update a parent row')
|
// cy.contains('Cannot delete or update a parent row')
|
||||||
@@ -430,8 +439,11 @@ describe('Data browser data', () => {
|
|||||||
cy.contains('Album').click();
|
cy.contains('Album').click();
|
||||||
cy.testid('DataFilterControl_input_ArtistId').type('22{enter}');
|
cy.testid('DataFilterControl_input_ArtistId').type('22{enter}');
|
||||||
// cy.contains('Presence').rightclick();
|
// cy.contains('Presence').rightclick();
|
||||||
|
// cy.contains('Coda').rightclick();
|
||||||
|
// cy.testid('DropDownMenu-container-0').contains('Export').click();
|
||||||
cy.contains('Export').click();
|
cy.contains('Export').click();
|
||||||
cy.themeshot('simpleexport');
|
// cy.wait(1000);
|
||||||
|
cy.themeshot('data-browser-export-menu');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('MySQL native backup', () => {
|
it('MySQL native backup', () => {
|
||||||
@@ -439,7 +451,7 @@ describe('Data browser data', () => {
|
|||||||
cy.contains('MyChinook').rightclick();
|
cy.contains('MyChinook').rightclick();
|
||||||
cy.contains('Create database backup').click();
|
cy.contains('Create database backup').click();
|
||||||
cy.contains('Customer');
|
cy.contains('Customer');
|
||||||
cy.themeshot('mysqlbackup');
|
cy.themeshot('mysql-backup-configuration');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('View table YAML model', () => {
|
it('View table YAML model', () => {
|
||||||
@@ -449,10 +461,11 @@ describe('Data browser data', () => {
|
|||||||
cy.testid('ExportDbModelModal_archiveFolder').select('(Create new)');
|
cy.testid('ExportDbModelModal_archiveFolder').select('(Create new)');
|
||||||
cy.testid('InputTextModal_value').clear().type('test-model');
|
cy.testid('InputTextModal_value').clear().type('test-model');
|
||||||
cy.testid('InputTextModal_ok').click();
|
cy.testid('InputTextModal_ok').click();
|
||||||
|
cy.testid('ModalBase_window').themeshot('export-database-model-window', { padding: 50 });
|
||||||
cy.testid('ExportDbModelModal_exportButton').click();
|
cy.testid('ExportDbModelModal_exportButton').click();
|
||||||
cy.contains('Album').click();
|
cy.contains('Album').click();
|
||||||
cy.contains('autoIncrement');
|
cy.contains('autoIncrement');
|
||||||
cy.themeshot('tableyaml');
|
cy.themeshot('database-model-table-yaml');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Data duplicator', () => {
|
it('Data duplicator', () => {
|
||||||
@@ -462,8 +475,8 @@ describe('Data browser data', () => {
|
|||||||
cy.contains('chinook-archive').rightclick();
|
cy.contains('chinook-archive').rightclick();
|
||||||
cy.contains('Data duplicator').click();
|
cy.contains('Data duplicator').click();
|
||||||
cy.contains('Dry run').click();
|
cy.contains('Dry run').click();
|
||||||
cy.testid("DataDuplicatorTab_importIntoDb").click();
|
cy.testid('DataDuplicatorTab_importIntoDb').click();
|
||||||
cy.contains('Duplicated Album, inserted 347 rows, mapped 0 rows, missing 0 rows, skipped 0 rows');
|
cy.contains('Duplicated Album, inserted 347 rows, mapped 0 rows, missing 0 rows, skipped 0 rows');
|
||||||
cy.themeshot('dataduplicator');
|
cy.themeshot('data-duplicator');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,21 +11,19 @@ describe('Team edition tests', () => {
|
|||||||
|
|
||||||
cy.testid('AdminMenuWidget_itemConnections').click();
|
cy.testid('AdminMenuWidget_itemConnections').click();
|
||||||
cy.contains('New connection').click();
|
cy.contains('New connection').click();
|
||||||
cy.contains('New connection').click();
|
|
||||||
cy.contains('New connection').click();
|
|
||||||
cy.testid('ConnectionDriverFields_connectionType').select('PostgreSQL');
|
cy.testid('ConnectionDriverFields_connectionType').select('PostgreSQL');
|
||||||
cy.themeshot('connadmin');
|
cy.themeshot('connection-administration');
|
||||||
|
|
||||||
cy.testid('AdminMenuWidget_itemRoles').click();
|
cy.testid('AdminMenuWidget_itemRoles').click();
|
||||||
cy.contains('Permissions').click();
|
cy.contains('logged-user').click();
|
||||||
cy.themeshot('roleadmin');
|
cy.themeshot('role-administration');
|
||||||
|
|
||||||
cy.testid('AdminMenuWidget_itemAuthentication').click();
|
cy.testid('AdminMenuWidget_itemAuthentication').click();
|
||||||
cy.contains('Add authentication').click();
|
cy.contains('Add authentication').click();
|
||||||
cy.contains('Use database login').click();
|
cy.contains('Use database login').click();
|
||||||
cy.contains('Add authentication').click();
|
cy.contains('Add authentication').click();
|
||||||
cy.contains('OAuth 2.0').click();
|
cy.contains('OAuth 2.0').click();
|
||||||
cy.themeshot('authadmin');
|
cy.themeshot('authentication-administration');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('OAuth authentication', () => {
|
it('OAuth authentication', () => {
|
||||||
@@ -77,6 +75,5 @@ describe('Team edition tests', () => {
|
|||||||
cy.testid('LoginPage_submitLogin').click();
|
cy.testid('LoginPage_submitLogin').click();
|
||||||
cy.testid('AdminMenuWidget_itemUsers').click();
|
cy.testid('AdminMenuWidget_itemUsers').click();
|
||||||
cy.contains('test@example.com');
|
cy.contains('test@example.com');
|
||||||
cy.contains('Rows: 1');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
0
e2e-tests/screenshots/.gitkeep
Normal file
0
e2e-tests/screenshots/.gitkeep
Normal file
@@ -1 +0,0 @@
|
|||||||
Folder with screenshots
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "6.3.1",
|
"version": "6.3.2",
|
||||||
"name": "dbgate-all",
|
"name": "dbgate-all",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*",
|
"packages/*",
|
||||||
|
|||||||
@@ -102,12 +102,21 @@ function getPortalCollections() {
|
|||||||
trustServerCertificate: process.env[`SSL_TRUST_CERTIFICATE_${id}`],
|
trustServerCertificate: process.env[`SSL_TRUST_CERTIFICATE_${id}`],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
for(const conn of connections) {
|
||||||
|
for(const prop in process.env) {
|
||||||
|
if (prop.startsWith(`CONNECTION_${conn._id}_`)) {
|
||||||
|
const name = prop.substring(`CONNECTION_${conn._id}_`.length);
|
||||||
|
conn[name] = process.env[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logger.info({ connections: connections.map(pickSafeConnectionInfo) }, 'Using connections from ENV variables');
|
logger.info({ connections: connections.map(pickSafeConnectionInfo) }, 'Using connections from ENV variables');
|
||||||
const noengine = connections.filter(x => !x.engine);
|
const noengine = connections.filter(x => !x.engine);
|
||||||
if (noengine.length > 0) {
|
if (noengine.length > 0) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
{ connections: noengine.map(x => x._id) },
|
{ connections: noengine.map(x => x._id) },
|
||||||
'Invalid CONNECTIONS configutation, missing ENGINE for connection ID'
|
'Invalid CONNECTIONS configuration, missing ENGINE for connection ID'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return connections;
|
return connections;
|
||||||
|
|||||||
@@ -195,8 +195,8 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
exportDiagram_meta: true,
|
exportDiagram_meta: true,
|
||||||
async exportDiagram({ filePath, html, css, themeType, themeClassName }) {
|
async exportDiagram({ filePath, html, css, themeType, themeClassName, watermark }) {
|
||||||
await fs.writeFile(filePath, getDiagramExport(html, css, themeType, themeClassName));
|
await fs.writeFile(filePath, getDiagramExport(html, css, themeType, themeClassName, watermark));
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ function start() {
|
|||||||
detail: formatErrorDetail(e, connection),
|
detail: formatErrorDetail(e, connection),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,26 @@ function loadEncryptionKey() {
|
|||||||
return _encryptionKey;
|
return _encryptionKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadEncryptionKeyFromExternal(storedValue, setStoredValue) {
|
||||||
|
const encryptor = simpleEncryptor.createEncryptor(defaultEncryptionKey);
|
||||||
|
|
||||||
|
if (!storedValue) {
|
||||||
|
const generatedKey = crypto.randomBytes(32);
|
||||||
|
const newKey = generatedKey.toString('hex');
|
||||||
|
const result = {
|
||||||
|
encryptionKey: newKey,
|
||||||
|
};
|
||||||
|
await setStoredValue(encryptor.encrypt(result));
|
||||||
|
|
||||||
|
setEncryptionKey(newKey);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = encryptor.decrypt(storedValue);
|
||||||
|
setEncryptionKey(data['encryptionKey']);
|
||||||
|
}
|
||||||
|
|
||||||
let _encryptor = null;
|
let _encryptor = null;
|
||||||
|
|
||||||
function getEncryptor() {
|
function getEncryptor() {
|
||||||
@@ -43,35 +63,32 @@ function getEncryptor() {
|
|||||||
return _encryptor;
|
return _encryptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
function encryptPasswordField(connection, field) {
|
function encryptObjectPasswordField(obj, field) {
|
||||||
if (
|
if (obj && obj[field] && !obj[field].startsWith('crypt:')) {
|
||||||
connection &&
|
|
||||||
connection[field] &&
|
|
||||||
!connection[field].startsWith('crypt:') &&
|
|
||||||
connection.passwordMode != 'saveRaw'
|
|
||||||
) {
|
|
||||||
return {
|
return {
|
||||||
...connection,
|
...obj,
|
||||||
[field]: 'crypt:' + getEncryptor().encrypt(connection[field]),
|
[field]: 'crypt:' + getEncryptor().encrypt(obj[field]),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return connection;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
function decryptPasswordField(connection, field) {
|
function decryptObjectPasswordField(obj, field) {
|
||||||
if (connection && connection[field] && connection[field].startsWith('crypt:')) {
|
if (obj && obj[field] && obj[field].startsWith('crypt:')) {
|
||||||
return {
|
return {
|
||||||
...connection,
|
...obj,
|
||||||
[field]: getEncryptor().decrypt(connection[field].substring('crypt:'.length)),
|
[field]: getEncryptor().decrypt(obj[field].substring('crypt:'.length)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return connection;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
function encryptConnection(connection) {
|
function encryptConnection(connection) {
|
||||||
connection = encryptPasswordField(connection, 'password');
|
if (connection.passwordMode != 'saveRaw') {
|
||||||
connection = encryptPasswordField(connection, 'sshPassword');
|
connection = encryptObjectPasswordField(connection, 'password');
|
||||||
connection = encryptPasswordField(connection, 'sshKeyfilePassword');
|
connection = encryptObjectPasswordField(connection, 'sshPassword');
|
||||||
|
connection = encryptObjectPasswordField(connection, 'sshKeyfilePassword');
|
||||||
|
}
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,12 +98,24 @@ function maskConnection(connection) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function decryptConnection(connection) {
|
function decryptConnection(connection) {
|
||||||
connection = decryptPasswordField(connection, 'password');
|
connection = decryptObjectPasswordField(connection, 'password');
|
||||||
connection = decryptPasswordField(connection, 'sshPassword');
|
connection = decryptObjectPasswordField(connection, 'sshPassword');
|
||||||
connection = decryptPasswordField(connection, 'sshKeyfilePassword');
|
connection = decryptObjectPasswordField(connection, 'sshKeyfilePassword');
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function encryptUser(user) {
|
||||||
|
if (user.encryptPassword) {
|
||||||
|
user = encryptObjectPasswordField(user, 'password');
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decryptUser(user) {
|
||||||
|
user = decryptObjectPasswordField(user, 'password');
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
function pickSafeConnectionInfo(connection) {
|
function pickSafeConnectionInfo(connection) {
|
||||||
if (process.env.LOG_CONNECTION_SENSITIVE_VALUES) {
|
if (process.env.LOG_CONNECTION_SENSITIVE_VALUES) {
|
||||||
return connection;
|
return connection;
|
||||||
@@ -99,10 +128,18 @@ function pickSafeConnectionInfo(connection) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setEncryptionKey(encryptionKey) {
|
||||||
|
_encryptionKey = encryptionKey;
|
||||||
|
_encryptor = null;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
loadEncryptionKey,
|
loadEncryptionKey,
|
||||||
encryptConnection,
|
encryptConnection,
|
||||||
|
encryptUser,
|
||||||
|
decryptUser,
|
||||||
decryptConnection,
|
decryptConnection,
|
||||||
maskConnection,
|
maskConnection,
|
||||||
pickSafeConnectionInfo,
|
pickSafeConnectionInfo,
|
||||||
|
loadEncryptionKeyFromExternal,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
const getDiagramExport = (html, css, themeType, themeClassName) => {
|
const getDiagramExport = (html, css, themeType, themeClassName, watermark) => {
|
||||||
|
const watermarkHtml = watermark
|
||||||
|
? `
|
||||||
|
<div style="position: fixed; bottom: 0; right: 0; padding: 5px; font-size: 12px; color: var(--theme-font-2); background-color: var(--theme-bg-2); border-top-left-radius: 5px; border: 1px solid var(--theme-border);">
|
||||||
|
${watermark}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: '';
|
||||||
return `<html>
|
return `<html>
|
||||||
<meta charset='utf-8'>
|
<meta charset='utf-8'>
|
||||||
|
|
||||||
@@ -13,10 +20,44 @@ const getDiagramExport = (html, css, themeType, themeClassName) => {
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<link rel="stylesheet" href='https://cdn.jsdelivr.net/npm/@mdi/font@6.5.95/css/materialdesignicons.css' />
|
<link rel="stylesheet" href='https://cdn.jsdelivr.net/npm/@mdi/font@6.5.95/css/materialdesignicons.css' />
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let lastX = null;
|
||||||
|
let lastY = null;
|
||||||
|
|
||||||
|
const handleMoveDown = e => {
|
||||||
|
lastX = e.clientX;
|
||||||
|
lastY = e.clientY;
|
||||||
|
document.addEventListener('mousemove', handleMoveMove, true);
|
||||||
|
document.addEventListener('mouseup', handleMoveEnd, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMoveMove = e => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
document.body.scrollLeft -= e.clientX - lastX;
|
||||||
|
document.body.scrollTop -= e.clientY - lastY;
|
||||||
|
|
||||||
|
lastX = e.clientX;
|
||||||
|
lastY = e.clientY;
|
||||||
|
};
|
||||||
|
const handleMoveEnd = e => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
lastX = null;
|
||||||
|
lastY = null;
|
||||||
|
document.removeEventListener('mousemove', handleMoveMove, true);
|
||||||
|
document.removeEventListener('mouseup', handleMoveEnd, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('mousedown', handleMoveDown);
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class='${themeType == 'dark' ? 'theme-type-dark' : 'theme-type-light'} ${themeClassName}'>
|
<body class='${themeType == 'dark' ? 'theme-type-dark' : 'theme-type-light'} ${themeClassName}' style='user-select:none; cursor:pointer'>
|
||||||
${html}
|
${html}
|
||||||
|
${watermarkHtml}
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>`;
|
</html>`;
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ export interface GridConfig extends GridConfigColumns {
|
|||||||
isFormView?: boolean;
|
isFormView?: boolean;
|
||||||
formViewRecordNumber?: number;
|
formViewRecordNumber?: number;
|
||||||
formFilterColumns: string[];
|
formFilterColumns: string[];
|
||||||
formColumnFilterText?: string;
|
|
||||||
multiColumnFilter?: string;
|
multiColumnFilter?: string;
|
||||||
|
searchInColumns?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GridCache {
|
export interface GridCache {
|
||||||
|
|||||||
@@ -196,9 +196,24 @@ export abstract class GridDisplay {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setSearchInColumns(searchInColumns: string) {
|
||||||
|
this.setConfig(cfg => ({
|
||||||
|
...cfg,
|
||||||
|
searchInColumns,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
get hiddenColumnIndexes() {
|
get hiddenColumnIndexes() {
|
||||||
// console.log('GridDisplay.hiddenColumn', this.config.hiddenColumns);
|
// console.log('GridDisplay.hiddenColumn', this.config.hiddenColumns);
|
||||||
return (this.config.hiddenColumns || []).map(x => _.findIndex(this.allColumns, y => y.uniqueName == x));
|
const res = (this.config.hiddenColumns || []).map(x => _.findIndex(this.allColumns, y => y.uniqueName == x));
|
||||||
|
if (this.config.searchInColumns) {
|
||||||
|
for (let i = 0; i < this.allColumns.length; i++) {
|
||||||
|
if (!filterName(this.config.searchInColumns, this.allColumns[i].columnName)) {
|
||||||
|
res.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _.sortBy(_.uniq(res));
|
||||||
}
|
}
|
||||||
|
|
||||||
isColumnChecked(column: DisplayColumn) {
|
isColumnChecked(column: DisplayColumn) {
|
||||||
|
|||||||
5
packages/tools/jest.config.js
Normal file
5
packages/tools/jest.config.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
preset: 'ts-jest',
|
||||||
|
testEnvironment: 'node',
|
||||||
|
moduleFileExtensions: ['js'],
|
||||||
|
};
|
||||||
@@ -17,8 +17,9 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"start": "tsc --watch",
|
"start": "tsc --watch",
|
||||||
|
"prepublishOnly": "yarn build",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"prepublishOnly": "yarn build"
|
"test:ci": "jest --json --outputFile=result.json --testLocationInResults"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"lib"
|
"lib"
|
||||||
@@ -26,8 +27,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^13.7.0",
|
"@types/node": "^13.7.0",
|
||||||
"dbgate-types": "^6.0.0-alpha.1",
|
"dbgate-types": "^6.0.0-alpha.1",
|
||||||
"jest": "^24.9.0",
|
"jest": "^28.1.3",
|
||||||
"ts-jest": "^25.2.1",
|
"ts-jest": "^28.0.7",
|
||||||
"typescript": "^4.4.3"
|
"typescript": "^4.4.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -354,6 +354,7 @@ export class DatabaseAnalyser {
|
|||||||
logger.error(extractErrorLogData(err, { template }), 'Error running analyser query');
|
logger.error(extractErrorLogData(err, { template }), 'Error running analyser query');
|
||||||
return {
|
return {
|
||||||
rows: [],
|
rows: [],
|
||||||
|
isError: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
63
packages/tools/src/diagramTools.ts
Normal file
63
packages/tools/src/diagramTools.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { DatabaseInfo, TableInfo } from 'dbgate-types';
|
||||||
|
import { extendDatabaseInfo } from './structureTools';
|
||||||
|
import _sortBy from 'lodash/sortBy';
|
||||||
|
import _uniq from 'lodash/uniq';
|
||||||
|
import { filterName } from './filterName';
|
||||||
|
|
||||||
|
function tableWeight(table: TableInfo, maxRowcount?: number) {
|
||||||
|
let weight = 0;
|
||||||
|
const tableDependenciesCount = _uniq(table.dependencies?.map(x => x.pureName) || []).length;
|
||||||
|
const tableFkCount = _uniq(table.foreignKeys?.map(x => x.refTableName) || []).length;
|
||||||
|
|
||||||
|
if (table.primaryKey) weight += 1;
|
||||||
|
if (tableFkCount) weight += tableFkCount * 1;
|
||||||
|
if (maxRowcount && table.tableRowCount) {
|
||||||
|
const rowcount = parseInt(table.tableRowCount as string);
|
||||||
|
if (rowcount > 0)
|
||||||
|
weight += Math.log(rowcount) * table.columns.length * (tableFkCount || 1) * (tableDependenciesCount || 1);
|
||||||
|
} else {
|
||||||
|
if (table.columns) weight += table.columns.length * 2;
|
||||||
|
}
|
||||||
|
if (table.dependencies) weight += tableDependenciesCount * 10;
|
||||||
|
if (maxRowcount) return weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function chooseTopTables(tables: TableInfo[], count: number, tableFilter: string, omitTablesFilter: string) {
|
||||||
|
const filteredTables = tables.filter(table => {
|
||||||
|
if (tableFilter) {
|
||||||
|
if (!filterName(tableFilter, table?.pureName)) return false;
|
||||||
|
}
|
||||||
|
if (omitTablesFilter) {
|
||||||
|
if (filterName(omitTablesFilter, table?.pureName)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!(count > 0)) {
|
||||||
|
return filteredTables;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dbinfo: DatabaseInfo = {
|
||||||
|
tables: filteredTables,
|
||||||
|
} as DatabaseInfo;
|
||||||
|
|
||||||
|
const extended = extendDatabaseInfo(dbinfo);
|
||||||
|
|
||||||
|
const maxRowcount = Math.max(
|
||||||
|
...extended.tables
|
||||||
|
.map(x => x.tableRowCount || 0)
|
||||||
|
.map(x => parseInt(x as string))
|
||||||
|
.filter(x => x > 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
const sorted = _sortBy(
|
||||||
|
_sortBy(extended.tables, x => `${x.schemaName}.${x.pureName}`),
|
||||||
|
table => -tableWeight(table, maxRowcount)
|
||||||
|
);
|
||||||
|
|
||||||
|
return sorted.slice(0, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DIAGRAM_ZOOMS = [0.1, 0.15, 0.2, 0.3, 0.4, 0.5, 0.6, 0.8, 1, 1.25, 1.5, 1.75, 2];
|
||||||
|
|
||||||
|
export const DIAGRAM_DEFAULT_WATERMARK = 'Powered by [dbgate.io](https://dbgate.io)';
|
||||||
20
packages/tools/src/filterName.test.ts
Normal file
20
packages/tools/src/filterName.test.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
const { tokenizeBySearchFilter } = require('./filterName');
|
||||||
|
|
||||||
|
test('tokenize single token', () => {
|
||||||
|
const tokenized = tokenizeBySearchFilter('Album', 'al');
|
||||||
|
// console.log(JSON.stringify(tokenized, null, 2));
|
||||||
|
expect(tokenized).toEqual([
|
||||||
|
{ text: 'Al', isMatch: true },
|
||||||
|
{ text: 'bum', isMatch: false },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('tokenize two tokens', () => {
|
||||||
|
const tokenized = tokenizeBySearchFilter('Album', 'al,um');
|
||||||
|
// console.log(JSON.stringify(tokenized, null, 2));
|
||||||
|
expect(tokenized).toEqual([
|
||||||
|
{ text: 'Al', isMatch: true },
|
||||||
|
{ text: 'b', isMatch: false },
|
||||||
|
{ text: 'um', isMatch: true },
|
||||||
|
]);
|
||||||
|
});
|
||||||
@@ -6,6 +6,29 @@ import _startCase from 'lodash/startCase';
|
|||||||
// childName: string;
|
// childName: string;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
interface TokenFactor {
|
||||||
|
tokens: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TokenTree {
|
||||||
|
factors: TokenFactor[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseTokenTree(filter: string): TokenTree {
|
||||||
|
const factors = filter
|
||||||
|
.split(',')
|
||||||
|
.map(x => x.trim())
|
||||||
|
.filter(x => x.length > 0);
|
||||||
|
return {
|
||||||
|
factors: factors.map(x => ({
|
||||||
|
tokens: x
|
||||||
|
.split(' ')
|
||||||
|
.map(x => x.trim())
|
||||||
|
.filter(x => x.length > 0),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function camelMatch(filter: string, text: string): boolean {
|
function camelMatch(filter: string, text: string): boolean {
|
||||||
if (!text) return false;
|
if (!text) return false;
|
||||||
if (!filter) return true;
|
if (!filter) return true;
|
||||||
@@ -24,31 +47,27 @@ export function filterName(filter: string, ...names: string[]) {
|
|||||||
if (!filter) return true;
|
if (!filter) return true;
|
||||||
|
|
||||||
// const camelVariants = [name.replace(/[^A-Z]/g, '')]
|
// const camelVariants = [name.replace(/[^A-Z]/g, '')]
|
||||||
const tokens = filter.split(' ').map(x => x.trim());
|
const tree = parseTokenTree(filter);
|
||||||
|
|
||||||
|
if (tree.factors.length == 0) return true;
|
||||||
|
|
||||||
const namesCompacted = _compact(names);
|
const namesCompacted = _compact(names);
|
||||||
|
|
||||||
for (const token of tokens) {
|
for (const factor of tree.factors) {
|
||||||
const found = namesCompacted.find(name => camelMatch(token, name));
|
let factorOk = true;
|
||||||
if (!found) return false;
|
for (const token of factor.tokens) {
|
||||||
|
const found = namesCompacted.find(name => camelMatch(token, name));
|
||||||
|
if (!found) factorOk = false;
|
||||||
|
}
|
||||||
|
if (factorOk) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function filterNameCompoud(
|
function clasifyCompoudCategory(tokens: string[], namesCompactedMain: string[], namesCompactedChild: string[]) {
|
||||||
filter: string,
|
|
||||||
namesMain: string[],
|
|
||||||
namesChild: string[]
|
|
||||||
): 'main' | 'child' | 'both' | 'none' {
|
|
||||||
if (!filter) return 'both';
|
|
||||||
|
|
||||||
// const camelVariants = [name.replace(/[^A-Z]/g, '')]
|
|
||||||
const tokens = filter.split(' ').map(x => x.trim());
|
|
||||||
|
|
||||||
const namesCompactedMain = _compact(namesMain);
|
|
||||||
const namesCompactedChild = _compact(namesChild);
|
|
||||||
|
|
||||||
let isMainOnly = true;
|
let isMainOnly = true;
|
||||||
let isChildOnly = true;
|
let isChildOnly = true;
|
||||||
|
|
||||||
@@ -67,10 +86,42 @@ export function filterNameCompoud(
|
|||||||
return 'none';
|
return 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function filterNameCompoud(
|
||||||
|
filter: string,
|
||||||
|
namesMain: string[],
|
||||||
|
namesChild: string[]
|
||||||
|
): 'main' | 'child' | 'both' | 'none' {
|
||||||
|
if (!filter) return 'both';
|
||||||
|
|
||||||
|
// const camelVariants = [name.replace(/[^A-Z]/g, '')]
|
||||||
|
const tree = parseTokenTree(filter);
|
||||||
|
|
||||||
|
const namesCompactedMain = _compact(namesMain);
|
||||||
|
const namesCompactedChild = _compact(namesChild);
|
||||||
|
|
||||||
|
if (tree.factors.length == 0) return 'both';
|
||||||
|
|
||||||
|
const factorRes = [];
|
||||||
|
|
||||||
|
for (const factor of tree.factors) {
|
||||||
|
const category = clasifyCompoudCategory(factor.tokens, namesCompactedMain, namesCompactedChild);
|
||||||
|
factorRes.push(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (factorRes.includes('both')) return 'both';
|
||||||
|
if (factorRes.includes('main') && factorRes.includes('child')) return 'both';
|
||||||
|
if (factorRes.includes('main')) return 'main';
|
||||||
|
if (factorRes.includes('child')) return 'child';
|
||||||
|
return 'none';
|
||||||
|
}
|
||||||
|
|
||||||
export function tokenizeBySearchFilter(text: string, filter: string): { text: string; isMatch: boolean }[] {
|
export function tokenizeBySearchFilter(text: string, filter: string): { text: string; isMatch: boolean }[] {
|
||||||
const camelTokens = [];
|
const camelTokens = [];
|
||||||
const stdTokens = [];
|
const stdTokens = [];
|
||||||
for (const token of filter.split(' ').map(x => x.trim())) {
|
for (const token of filter
|
||||||
|
.split(/[ ,]/)
|
||||||
|
.map(x => x.trim())
|
||||||
|
.filter(x => x.length > 0)) {
|
||||||
if (token.replace(/[A-Z]/g, '').length == 0) {
|
if (token.replace(/[A-Z]/g, '').length == 0) {
|
||||||
camelTokens.push(token);
|
camelTokens.push(token);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -26,3 +26,4 @@ export * from './filterBehaviours';
|
|||||||
export * from './schemaInfoTools';
|
export * from './schemaInfoTools';
|
||||||
export * from './dbKeysLoader';
|
export * from './dbKeysLoader';
|
||||||
export * from './rowProgressReporter';
|
export * from './rowProgressReporter';
|
||||||
|
export * from './diagramTools';
|
||||||
|
|||||||
@@ -9,16 +9,17 @@ import type {
|
|||||||
import _flatten from 'lodash/flatten';
|
import _flatten from 'lodash/flatten';
|
||||||
import _uniq from 'lodash/uniq';
|
import _uniq from 'lodash/uniq';
|
||||||
import _keys from 'lodash/keys';
|
import _keys from 'lodash/keys';
|
||||||
|
import _compact from 'lodash/compact';
|
||||||
|
|
||||||
export function addTableDependencies(db: DatabaseInfo): DatabaseInfo {
|
export function addTableDependencies(db: DatabaseInfo): DatabaseInfo {
|
||||||
if (!db.tables) {
|
if (!db.tables) {
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
const allForeignKeys = _flatten(db.tables.map(x => x.foreignKeys || []));
|
const allForeignKeys = _flatten(db.tables.map(x => x?.foreignKeys || []));
|
||||||
return {
|
return {
|
||||||
...db,
|
...db,
|
||||||
tables: db.tables.map(table => ({
|
tables: _compact(db.tables).map(table => ({
|
||||||
...table,
|
...table,
|
||||||
dependencies: allForeignKeys.filter(x => x.refSchemaName == table.schemaName && x.refTableName == table.pureName),
|
dependencies: allForeignKeys.filter(x => x.refSchemaName == table.schemaName && x.refTableName == table.pureName),
|
||||||
})),
|
})),
|
||||||
|
|||||||
1
packages/types/engines.d.ts
vendored
1
packages/types/engines.d.ts
vendored
@@ -306,6 +306,7 @@ export interface EngineDriver<TClient = any> extends FilterBehaviourProvider {
|
|||||||
command: 'backup' | 'restore'
|
command: 'backup' | 'restore'
|
||||||
): { message: string; severity: 'info' | 'error' | 'debug' } | null;
|
): { message: string; severity: 'info' | 'error' | 'debug' } | null;
|
||||||
getNativeOperationFormArgs(operation: 'backup' | 'restore'): any[];
|
getNativeOperationFormArgs(operation: 'backup' | 'restore'): any[];
|
||||||
|
getAdvancedConnectionFields(): any[];
|
||||||
|
|
||||||
analyserClass?: any;
|
analyserClass?: any;
|
||||||
dumperClass?: any;
|
dumperClass?: any;
|
||||||
|
|||||||
@@ -58,6 +58,24 @@ body {
|
|||||||
.relative {
|
.relative {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
.scroll {
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
.bg-0 {
|
||||||
|
background-color: var(--theme-bg-0);
|
||||||
|
}
|
||||||
|
.bg-1 {
|
||||||
|
background-color: var(--theme-bg-1);
|
||||||
|
}
|
||||||
|
.bg-2 {
|
||||||
|
background-color: var(--theme-bg-2);
|
||||||
|
}
|
||||||
|
.bg-3 {
|
||||||
|
background-color: var(--theme-bg-3);
|
||||||
|
}
|
||||||
|
.bg-4 {
|
||||||
|
background-color: var(--theme-bg-4);
|
||||||
|
}
|
||||||
|
|
||||||
.col-10 {
|
.col-10 {
|
||||||
flex-basis: 83.3333%;
|
flex-basis: 83.3333%;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
visibleCommandPalette,
|
visibleCommandPalette,
|
||||||
visibleTitleBar,
|
visibleTitleBar,
|
||||||
visibleToolbar,
|
visibleToolbar,
|
||||||
|
systemThemeStore,
|
||||||
} from './stores';
|
} from './stores';
|
||||||
import TabsPanel from './tabpanel/TabsPanel.svelte';
|
import TabsPanel from './tabpanel/TabsPanel.svelte';
|
||||||
import TabRegister from './tabpanel/TabRegister.svelte';
|
import TabRegister from './tabpanel/TabRegister.svelte';
|
||||||
@@ -50,7 +51,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class={`${$currentTheme} ${currentThemeType} root dbgate-screen`}
|
class={`${$currentTheme ?? $systemThemeStore} ${currentThemeType} root dbgate-screen`}
|
||||||
class:isElectron
|
class:isElectron
|
||||||
use:dragDropFileTarget
|
use:dragDropFileTarget
|
||||||
on:contextmenu={e => e.preventDefault()}
|
on:contextmenu={e => e.preventDefault()}
|
||||||
|
|||||||
@@ -5,13 +5,18 @@
|
|||||||
|
|
||||||
export let filter;
|
export let filter;
|
||||||
export let showDisabled = false;
|
export let showDisabled = false;
|
||||||
|
export let onClearFilter = null;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if filter || showDisabled}
|
{#if filter || showDisabled}
|
||||||
<InlineButton
|
<InlineButton
|
||||||
on:click
|
on:click
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
filter = '';
|
if (onClearFilter) {
|
||||||
|
onClearFilter();
|
||||||
|
} else {
|
||||||
|
filter = '';
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
title="Clear filter"
|
title="Clear filter"
|
||||||
disabled={!filter}
|
disabled={!filter}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="content" class:scrollContent>
|
<div class="content" class:scrollContent class:isComponentActive>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -41,6 +41,10 @@
|
|||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content.isComponentActive {
|
||||||
|
max-height: calc(100% - 30px);
|
||||||
|
}
|
||||||
|
|
||||||
.toolstrip {
|
.toolstrip {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
@@ -14,8 +14,9 @@
|
|||||||
function handleClick(e) {
|
function handleClick(e) {
|
||||||
const rect = e.detail.target.getBoundingClientRect();
|
const rect = e.detail.target.getBoundingClientRect();
|
||||||
const left = rect.left;
|
const left = rect.left;
|
||||||
const top = rect.bottom;
|
const top = rect.top;
|
||||||
currentDropDownMenu.set({ left, top, items: menu });
|
// const top = rect.bottom;
|
||||||
|
currentDropDownMenu.set({ left, bottom: window.innerHeight - top, items: menu });
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -619,6 +619,24 @@ registerCommand({
|
|||||||
onClick: doLogout,
|
onClick: doLogout,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
registerCommand({
|
||||||
|
id: 'app.loggedUserCommands',
|
||||||
|
category: 'App',
|
||||||
|
name: 'Logged user',
|
||||||
|
getSubCommands: () => {
|
||||||
|
const config = getCurrentConfig();
|
||||||
|
if (!config) return [];
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
text: 'Logout',
|
||||||
|
onClick: () => {
|
||||||
|
doLogout();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
registerCommand({
|
registerCommand({
|
||||||
id: 'app.disconnect',
|
id: 'app.disconnect',
|
||||||
category: 'App',
|
category: 'App',
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
export let isSortDefined = false;
|
export let isSortDefined = false;
|
||||||
export let allowDefineVirtualReferences = false;
|
export let allowDefineVirtualReferences = false;
|
||||||
export let setGrouping;
|
export let setGrouping;
|
||||||
|
export let seachInColumns = '';
|
||||||
|
|
||||||
const openReferencedTable = () => {
|
const openReferencedTable = () => {
|
||||||
openDatabaseObjectDetail('TableDataTab', null, {
|
openDatabaseObjectDetail('TableDataTab', null, {
|
||||||
@@ -86,7 +87,7 @@
|
|||||||
{grouping == 'COUNT DISTINCT' ? 'distinct' : grouping.toLowerCase()}
|
{grouping == 'COUNT DISTINCT' ? 'distinct' : grouping.toLowerCase()}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
<ColumnLabel {...column} />
|
<ColumnLabel {...column} filter={seachInColumns} />
|
||||||
|
|
||||||
{#if _.isString(column.displayedDataType || column.dataType) && !order}
|
{#if _.isString(column.displayedDataType || column.dataType) && !order}
|
||||||
<span class="data-type" title={column.dataType}>
|
<span class="data-type" title={column.dataType}>
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
export let changeSetState: { value: ChangeSet } = null;
|
export let changeSetState: { value: ChangeSet } = null;
|
||||||
export let dispatchChangeSet = null;
|
export let dispatchChangeSet = null;
|
||||||
|
|
||||||
let filter;
|
|
||||||
let domFocusField;
|
let domFocusField;
|
||||||
|
|
||||||
let selectedColumns = [];
|
let selectedColumns = [];
|
||||||
@@ -36,7 +35,9 @@
|
|||||||
let dragStartColumnIndex = null;
|
let dragStartColumnIndex = null;
|
||||||
let shiftOriginColumnIndex = null;
|
let shiftOriginColumnIndex = null;
|
||||||
|
|
||||||
$: items = display?.getColumns(filter)?.filter(column => filterName(filter, column.columnName)) || [];
|
$: currentFilter = display?.config?.searchInColumns;
|
||||||
|
|
||||||
|
$: items = display?.getColumns(currentFilter)?.filter(column => filterName(currentFilter, column.columnName)) || [];
|
||||||
|
|
||||||
function selectColumnIndexCore(index, e) {
|
function selectColumnIndexCore(index, e) {
|
||||||
const uniqueName = items[index].uniqueName;
|
const uniqueName = items[index].uniqueName;
|
||||||
@@ -173,8 +174,13 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<SearchBoxWrapper>
|
<SearchBoxWrapper>
|
||||||
<SearchInput placeholder="Search columns" bind:value={filter} />
|
<SearchInput
|
||||||
<CloseSearchButton bind:filter />
|
placeholder="Search columns"
|
||||||
|
value={currentFilter}
|
||||||
|
onChange={value => display.setSearchInColumns(value)}
|
||||||
|
data-testid="ColumnManager_searchColumns"
|
||||||
|
/>
|
||||||
|
<CloseSearchButton filter={currentFilter} onClearFilter={() => display.setSearchInColumns('')} />
|
||||||
{#if isDynamicStructure && !isJsonView}
|
{#if isDynamicStructure && !isJsonView}
|
||||||
<InlineButton
|
<InlineButton
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
@@ -235,7 +241,7 @@
|
|||||||
{columnIndex}
|
{columnIndex}
|
||||||
{allowChangeChangeSetStructure}
|
{allowChangeChangeSetStructure}
|
||||||
isSelected={selectedColumns.includes(column.uniqueName) || currentColumnUniqueName == column.uniqueName}
|
isSelected={selectedColumns.includes(column.uniqueName) || currentColumnUniqueName == column.uniqueName}
|
||||||
{filter}
|
filter={currentFilter}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
if (domFocusField) domFocusField.focus();
|
if (domFocusField) domFocusField.focus();
|
||||||
selectedColumns = [column.uniqueName];
|
selectedColumns = [column.uniqueName];
|
||||||
|
|||||||
@@ -1933,6 +1933,7 @@
|
|||||||
setGrouping={display.groupable ? groupFunc => display.setGrouping(col.uniqueName, groupFunc) : null}
|
setGrouping={display.groupable ? groupFunc => display.setGrouping(col.uniqueName, groupFunc) : null}
|
||||||
grouping={display.getGrouping(col.uniqueName)}
|
grouping={display.getGrouping(col.uniqueName)}
|
||||||
{allowDefineVirtualReferences}
|
{allowDefineVirtualReferences}
|
||||||
|
seachInColumns={display.config?.searchInColumns}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -195,12 +195,12 @@
|
|||||||
{#if designer?.style?.showNullability || designer?.style?.showDataType}
|
{#if designer?.style?.showNullability || designer?.style?.showDataType}
|
||||||
<div class="space" />
|
<div class="space" />
|
||||||
{#if designer?.style?.showDataType && column?.dataType}
|
{#if designer?.style?.showDataType && column?.dataType}
|
||||||
<div class="ml-2">
|
<div class="ml-2 data-type">
|
||||||
{(column?.displayedDataType || column?.dataType).toLowerCase()}
|
{(column?.displayedDataType || column?.dataType).toLowerCase()}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if designer?.style?.showNullability}
|
{#if designer?.style?.showNullability}
|
||||||
<div class="ml-2">
|
<div class="ml-2 nullability">
|
||||||
{column?.notNull ? 'NOT NULL' : 'NULL'}
|
{column?.notNull ? 'NOT NULL' : 'NULL'}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -238,4 +238,12 @@
|
|||||||
background: var(--theme-bg-2);
|
background: var(--theme-bg-2);
|
||||||
color: var(--theme-font-hover);
|
color: var(--theme-font-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nullability {
|
||||||
|
color: var(--theme-font-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-type {
|
||||||
|
color: var(--theme-font-4);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -47,10 +47,13 @@
|
|||||||
import { showModal } from '../modals/modalTools';
|
import { showModal } from '../modals/modalTools';
|
||||||
import ChooseColorModal from '../modals/ChooseColorModal.svelte';
|
import ChooseColorModal from '../modals/ChooseColorModal.svelte';
|
||||||
import { currentThemeDefinition } from '../stores';
|
import { currentThemeDefinition } from '../stores';
|
||||||
import { extendDatabaseInfoFromApps } from 'dbgate-tools';
|
import { chooseTopTables, DIAGRAM_DEFAULT_WATERMARK, DIAGRAM_ZOOMS, extendDatabaseInfoFromApps } from 'dbgate-tools';
|
||||||
import SearchInput from '../elements/SearchInput.svelte';
|
import SearchInput from '../elements/SearchInput.svelte';
|
||||||
import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
|
import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
|
||||||
import DragColumnMemory from './DragColumnMemory.svelte';
|
import DragColumnMemory from './DragColumnMemory.svelte';
|
||||||
|
import createRef from '../utility/createRef';
|
||||||
|
import { isProApp } from '../utility/proTools';
|
||||||
|
import dragScroll from '../utility/dragScroll';
|
||||||
|
|
||||||
export let value;
|
export let value;
|
||||||
export let onChange;
|
export let onChange;
|
||||||
@@ -59,15 +62,18 @@
|
|||||||
export let menu;
|
export let menu;
|
||||||
export let settings;
|
export let settings;
|
||||||
export let referenceComponent;
|
export let referenceComponent;
|
||||||
|
export let onReportCounts = undefined;
|
||||||
|
|
||||||
export const activator = createActivator('Designer', true);
|
export const activator = createActivator('Designer', true);
|
||||||
|
|
||||||
let domCanvas;
|
let domCanvas;
|
||||||
|
let domWrapper;
|
||||||
let canvasWidth = 3000;
|
let canvasWidth = 3000;
|
||||||
let canvasHeight = 3000;
|
let canvasHeight = 3000;
|
||||||
let dragStartPoint = null;
|
let dragStartPoint = null;
|
||||||
let dragCurrentPoint = null;
|
let dragCurrentPoint = null;
|
||||||
let columnFilter;
|
export let columnFilter;
|
||||||
|
export let showColumnFilter = true;
|
||||||
|
|
||||||
const sourceDragColumn$ = writable(null);
|
const sourceDragColumn$ = writable(null);
|
||||||
const targetDragColumn$ = writable(null);
|
const targetDragColumn$ = writable(null);
|
||||||
@@ -75,14 +81,24 @@
|
|||||||
const dbInfo = settings?.updateFromDbInfo ? useDatabaseInfo({ conid, database }) : null;
|
const dbInfo = settings?.updateFromDbInfo ? useDatabaseInfo({ conid, database }) : null;
|
||||||
$: dbInfoExtended = $dbInfo ? extendDatabaseInfoFromApps($dbInfo, $apps) : null;
|
$: dbInfoExtended = $dbInfo ? extendDatabaseInfoFromApps($dbInfo, $apps) : null;
|
||||||
|
|
||||||
$: tables = value?.tables as any[];
|
$: tables =
|
||||||
$: references = value?.references as any[];
|
(value?.tables
|
||||||
|
? chooseTopTables(
|
||||||
|
value?.tables,
|
||||||
|
value?.style?.topTables,
|
||||||
|
value?.style?.tableFilter,
|
||||||
|
value?.style?.omitTablesFilter
|
||||||
|
)
|
||||||
|
: value?.tables) || ([] as any[]);
|
||||||
|
$: references = (value?.references || [])?.filter(
|
||||||
|
ref => tables.find(x => x.designerId == ref.sourceId) && tables.find(x => x.designerId == ref.targetId)
|
||||||
|
) as any[];
|
||||||
$: zoomKoef = settings?.customizeStyle && value?.style?.zoomKoef ? value?.style?.zoomKoef : 1;
|
$: zoomKoef = settings?.customizeStyle && value?.style?.zoomKoef ? value?.style?.zoomKoef : 1;
|
||||||
$: apps = useUsedApps();
|
$: apps = useUsedApps();
|
||||||
|
|
||||||
$: isMultipleTableSelection = tables.filter(x => x.isSelectedTable).length >= 2;
|
$: isMultipleTableSelection = tables.filter(x => x.isSelectedTable).length >= 2;
|
||||||
|
|
||||||
const tableRefs = {};
|
let tableRefs = {};
|
||||||
const referenceRefs = {};
|
const referenceRefs = {};
|
||||||
let domTables;
|
let domTables;
|
||||||
$: {
|
$: {
|
||||||
@@ -132,12 +148,14 @@
|
|||||||
onChange(current => {
|
onChange(current => {
|
||||||
let newTables = current.tables || [];
|
let newTables = current.tables || [];
|
||||||
for (const table of current.tables || []) {
|
for (const table of current.tables || []) {
|
||||||
const dbTable = (db.tables || []).find(x => x.pureName == table.pureName && x.schemaName == table.schemaName);
|
const dbTable = (db.tables || []).find(
|
||||||
|
x => x?.pureName == table?.pureName && x?.schemaName == table?.schemaName
|
||||||
|
);
|
||||||
if (
|
if (
|
||||||
stableStringify(_.pick(dbTable, ['columns', 'primaryKey', 'foreignKeys'])) !=
|
stableStringify(_.pick(dbTable, ['columns', 'primaryKey', 'foreignKeys'])) !=
|
||||||
stableStringify(_.pick(table, ['columns', 'primaryKey', 'foreignKeys']))
|
stableStringify(_.pick(table, ['columns', 'primaryKey', 'foreignKeys']))
|
||||||
) {
|
) {
|
||||||
newTables = newTables.map(x =>
|
newTables = _.compact(newTables).map(x =>
|
||||||
x == table
|
x == table
|
||||||
? {
|
? {
|
||||||
...table,
|
...table,
|
||||||
@@ -152,7 +170,7 @@
|
|||||||
if (settings?.useDatabaseReferences) {
|
if (settings?.useDatabaseReferences) {
|
||||||
references = [];
|
references = [];
|
||||||
for (const table of newTables) {
|
for (const table of newTables) {
|
||||||
for (const fk of table.foreignKeys) {
|
for (const fk of table.foreignKeys || []) {
|
||||||
const dst = newTables.find(x => x.pureName == fk.refTableName && x.schemaName == fk.refSchemaName);
|
const dst = newTables.find(x => x.pureName == fk.refTableName && x.schemaName == fk.refSchemaName);
|
||||||
if (!dst) continue;
|
if (!dst) continue;
|
||||||
references.push({
|
references.push({
|
||||||
@@ -620,11 +638,19 @@
|
|||||||
...current,
|
...current,
|
||||||
tables: (current.tables || []).map(x => {
|
tables: (current.tables || []).map(x => {
|
||||||
const domTable = domTables[x.designerId] as any;
|
const domTable = domTables[x.designerId] as any;
|
||||||
const rect = domTable.getRect();
|
if (domTable) {
|
||||||
return {
|
const rect = domTable.getRect();
|
||||||
...x,
|
const rectZoomed = {
|
||||||
isSelectedTable: rectanglesHaveIntersection(rect, bounds),
|
left: rect.left / zoomKoef,
|
||||||
};
|
right: rect.right / zoomKoef,
|
||||||
|
top: rect.top / zoomKoef,
|
||||||
|
bottom: rect.bottom / zoomKoef,
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
...x,
|
||||||
|
isSelectedTable: rectanglesHaveIntersection(rectZoomed, bounds),
|
||||||
|
};
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
true
|
true
|
||||||
@@ -637,7 +663,7 @@
|
|||||||
|
|
||||||
function recomputeReferencePositions() {
|
function recomputeReferencePositions() {
|
||||||
for (const ref of Object.values(referenceRefs) as any[]) {
|
for (const ref of Object.values(referenceRefs) as any[]) {
|
||||||
if (ref) ref.recomputePosition();
|
if (ref) ref.recomputePosition(zoomKoef);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -662,21 +688,26 @@
|
|||||||
|
|
||||||
export function arrange(skipUndoChain = false, arrangeAll = true, circleMiddle = { x: 0, y: 0 }) {
|
export function arrange(skipUndoChain = false, arrangeAll = true, circleMiddle = { x: 0, y: 0 }) {
|
||||||
const graph = new GraphDefinition();
|
const graph = new GraphDefinition();
|
||||||
for (const table of value?.tables || []) {
|
for (const table of tables || []) {
|
||||||
const domTable = domTables[table.designerId] as any;
|
const domTable = domTables[table.designerId] as any;
|
||||||
if (!domTable) continue;
|
if (!domTable) continue;
|
||||||
const rect = domTable.getRect();
|
const rect = domTable.getRect();
|
||||||
graph.addNode(
|
graph.addNode(
|
||||||
table.designerId,
|
table.designerId,
|
||||||
rect.right - rect.left,
|
(rect.right - rect.left) / zoomKoef,
|
||||||
rect.bottom - rect.top,
|
(rect.bottom - rect.top) / zoomKoef,
|
||||||
arrangeAll || table.needsArrange ? null : { x: (rect.left + rect.right) / 2, y: (rect.top + rect.bottom) / 2 }
|
arrangeAll || table.needsArrange
|
||||||
|
? null
|
||||||
|
: {
|
||||||
|
x: (rect.left + rect.right) / 2 / zoomKoef,
|
||||||
|
y: (rect.top + rect.bottom) / 2 / zoomKoef,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const reference of settings?.sortAutoLayoutReferences
|
for (const reference of settings?.sortAutoLayoutReferences
|
||||||
? settings?.sortAutoLayoutReferences(value?.references)
|
? settings?.sortAutoLayoutReferences(references)
|
||||||
: value?.references) {
|
: references) {
|
||||||
graph.addEdge(reference.sourceId, reference.targetId);
|
graph.addEdge(reference.sourceId, reference.targetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -710,7 +741,7 @@
|
|||||||
current => {
|
current => {
|
||||||
return {
|
return {
|
||||||
...current,
|
...current,
|
||||||
tables: (current?.tables || []).map(table => {
|
tables: _.compact(current?.tables || []).map(table => {
|
||||||
const node = layout.nodes[table.designerId];
|
const node = layout.nodes[table.designerId];
|
||||||
// console.log('POSITION', position);
|
// console.log('POSITION', position);
|
||||||
return node
|
return node
|
||||||
@@ -732,6 +763,16 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getWatermarkHtml() {
|
||||||
|
const replaceLinks = text => text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" style="color: var(--theme-font-link)" target="_blank">$1</a>');
|
||||||
|
|
||||||
|
if (value?.style?.omitExportWatermark) return null;
|
||||||
|
if (value?.style?.exportWatermark) {
|
||||||
|
return replaceLinks(value?.style?.exportWatermark);
|
||||||
|
}
|
||||||
|
return replaceLinks(DIAGRAM_DEFAULT_WATERMARK);
|
||||||
|
}
|
||||||
|
|
||||||
export async function exportDiagram() {
|
export async function exportDiagram() {
|
||||||
const cssLinks = ['global.css', 'build/bundle.css'];
|
const cssLinks = ['global.css', 'build/bundle.css'];
|
||||||
let css = '';
|
let css = '';
|
||||||
@@ -745,6 +786,7 @@
|
|||||||
if (css) css += '\n';
|
if (css) css += '\n';
|
||||||
css += $currentThemeDefinition?.themeCss;
|
css += $currentThemeDefinition?.themeCss;
|
||||||
}
|
}
|
||||||
|
css += ' body { overflow: scroll; }';
|
||||||
saveFileToDisk(async filePath => {
|
saveFileToDisk(async filePath => {
|
||||||
await apiCall('files/export-diagram', {
|
await apiCall('files/export-diagram', {
|
||||||
filePath,
|
filePath,
|
||||||
@@ -752,6 +794,7 @@
|
|||||||
css,
|
css,
|
||||||
themeType: $currentThemeDefinition?.themeType,
|
themeType: $currentThemeDefinition?.themeType,
|
||||||
themeClassName: $currentThemeDefinition?.themeClassName,
|
themeClassName: $currentThemeDefinition?.themeClassName,
|
||||||
|
watermark: getWatermarkHtml(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -773,7 +816,7 @@
|
|||||||
menu,
|
menu,
|
||||||
settings?.customizeStyle && [
|
settings?.customizeStyle && [
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
{
|
isProApp() && {
|
||||||
text: 'Column properties',
|
text: 'Column properties',
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
@@ -786,12 +829,12 @@
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
isProApp() && {
|
||||||
text: `Columns - ${_.startCase(value?.style?.filterColumns || 'all')}`,
|
text: `Columns - ${_.startCase(value?.style?.filterColumns || 'all')}`,
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
text: 'All',
|
text: 'All',
|
||||||
onClick: changeStyleFunc('filterColumns', null),
|
onClick: changeStyleFunc('filterColumns', ''),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Primary Key',
|
text: 'Primary Key',
|
||||||
@@ -813,56 +856,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: `Zoom - ${(value?.style?.zoomKoef || 1) * 100}%`,
|
text: `Zoom - ${(value?.style?.zoomKoef || 1) * 100}%`,
|
||||||
submenu: [
|
submenu: DIAGRAM_ZOOMS.map(koef => ({
|
||||||
{
|
text: `${koef * 100} %`,
|
||||||
text: `10 %`,
|
onClick: changeStyleFunc('zoomKoef', koef.toString()),
|
||||||
onClick: changeStyleFunc('zoomKoef', 0.1),
|
})),
|
||||||
},
|
|
||||||
{
|
|
||||||
text: `15 %`,
|
|
||||||
onClick: changeStyleFunc('zoomKoef', 0.15),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: `20 %`,
|
|
||||||
onClick: changeStyleFunc('zoomKoef', 0.2),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: `40 %`,
|
|
||||||
onClick: changeStyleFunc('zoomKoef', 0.4),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: `60 %`,
|
|
||||||
onClick: changeStyleFunc('zoomKoef', 0.6),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: `80 %`,
|
|
||||||
onClick: changeStyleFunc('zoomKoef', 0.8),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: `100 %`,
|
|
||||||
onClick: changeStyleFunc('zoomKoef', 1),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: `120 %`,
|
|
||||||
onClick: changeStyleFunc('zoomKoef', 1.2),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: `140 %`,
|
|
||||||
onClick: changeStyleFunc('zoomKoef', 1.4),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: `160 %`,
|
|
||||||
onClick: changeStyleFunc('zoomKoef', 1.6),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: `180 %`,
|
|
||||||
onClick: changeStyleFunc('zoomKoef', 1.8),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: `200 %`,
|
|
||||||
onClick: changeStyleFunc('zoomKoef', 2),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
@@ -875,9 +872,105 @@
|
|||||||
recomputeDomTables();
|
recomputeDomTables();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const oldTopTablesRef = createRef(value?.style?.topTables);
|
||||||
|
$: {
|
||||||
|
if (value?.style?.topTables > 0 && oldTopTablesRef.get() != value?.style?.topTables) {
|
||||||
|
oldTopTablesRef.set(value?.style?.topTables);
|
||||||
|
tick().then(() => {
|
||||||
|
arrange();
|
||||||
|
tick().then(() => {
|
||||||
|
recomputeReferencePositions();
|
||||||
|
recomputeDomTables();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleWheel(event) {
|
||||||
|
if (event.ctrlKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
const zoomIndex = DIAGRAM_ZOOMS.findIndex(x => x == value?.style?.zoomKoef);
|
||||||
|
if (zoomIndex < 0) DIAGRAM_ZOOMS.findIndex(x => x == 1);
|
||||||
|
|
||||||
|
let newZoomIndex = zoomIndex;
|
||||||
|
if (event.deltaY < 0) {
|
||||||
|
newZoomIndex += 1;
|
||||||
|
}
|
||||||
|
if (event.deltaY > 0) {
|
||||||
|
newZoomIndex -= 1;
|
||||||
|
}
|
||||||
|
if (newZoomIndex < 0) {
|
||||||
|
newZoomIndex = 0;
|
||||||
|
}
|
||||||
|
if (newZoomIndex >= DIAGRAM_ZOOMS.length) {
|
||||||
|
newZoomIndex = DIAGRAM_ZOOMS.length - 1;
|
||||||
|
}
|
||||||
|
const newZoomKoef = DIAGRAM_ZOOMS[newZoomIndex];
|
||||||
|
|
||||||
|
callChange(
|
||||||
|
current => ({
|
||||||
|
...current,
|
||||||
|
style: {
|
||||||
|
...current?.style,
|
||||||
|
zoomKoef: newZoomKoef.toString(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragScroll(x, y) {
|
||||||
|
domWrapper.scrollLeft -= x;
|
||||||
|
domWrapper.scrollTop -= y;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldZoomKoefRef = createRef(value?.style?.zoomKoef || 1);
|
||||||
|
$: {
|
||||||
|
if (
|
||||||
|
domWrapper &&
|
||||||
|
value?.style?.zoomKoef != oldZoomKoefRef.get() &&
|
||||||
|
value?.style?.zoomKoef > 0 &&
|
||||||
|
oldZoomKoefRef.get() > 0
|
||||||
|
) {
|
||||||
|
domWrapper.scrollLeft = Math.round((domWrapper.scrollLeft / oldZoomKoefRef.get()) * value?.style?.zoomKoef);
|
||||||
|
domWrapper.scrollTop = Math.round((domWrapper.scrollTop / oldZoomKoefRef.get()) * value?.style?.zoomKoef);
|
||||||
|
}
|
||||||
|
oldZoomKoefRef.set(value?.style?.zoomKoef);
|
||||||
|
}
|
||||||
|
|
||||||
|
// $: console.log('DESIGNER VALUE', value);
|
||||||
|
|
||||||
|
// $: console.log('TABLES ARRAY', tables);
|
||||||
|
|
||||||
|
// $: {
|
||||||
|
// if (value?.tables?.find(x => !x)) {
|
||||||
|
// console.log('**** INCORRECT DESIGNER VALUE**** ', value);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// $: {
|
||||||
|
// if (value?.tables?.length < 100) {
|
||||||
|
// console.log('**** SMALL TABLES**** ', value);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
$: if (onReportCounts) {
|
||||||
|
// console.log('REPORTING COUNTS');
|
||||||
|
onReportCounts({
|
||||||
|
all: _.compact(value?.tables || []).length,
|
||||||
|
filtered: _.compact(tables || []).length,
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper noselect" use:contextMenu={createMenu}>
|
<div
|
||||||
|
class="wrapper noselect"
|
||||||
|
use:contextMenu={createMenu}
|
||||||
|
on:wheel={handleWheel}
|
||||||
|
bind:this={domWrapper}
|
||||||
|
use:dragScroll={handleDragScroll}
|
||||||
|
>
|
||||||
{#if !(tables?.length > 0)}
|
{#if !(tables?.length > 0)}
|
||||||
<div class="empty">Drag & drop tables or views from left panel here</div>
|
<div class="empty">Drag & drop tables or views from left panel here</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -887,8 +980,8 @@
|
|||||||
bind:this={domCanvas}
|
bind:this={domCanvas}
|
||||||
on:dragover={e => e.preventDefault()}
|
on:dragover={e => e.preventDefault()}
|
||||||
on:drop={handleDrop}
|
on:drop={handleDrop}
|
||||||
style={`width:${canvasWidth}px;height:${canvasHeight}px;
|
style={`width:${canvasWidth / zoomKoef}px;height:${canvasHeight / zoomKoef}px;
|
||||||
${settings?.customizeStyle && value?.style?.zoomKoef ? `zoom:${value?.style?.zoomKoef};` : ''}
|
${settings?.customizeStyle && value?.style?.zoomKoef ? `transform:scale(${value?.style?.zoomKoef});transform-origin: top left;` : ''}
|
||||||
`}
|
`}
|
||||||
on:mousedown={e => {
|
on:mousedown={e => {
|
||||||
if (e.button == 0 && settings?.canSelectTables) {
|
if (e.button == 0 && settings?.canSelectTables) {
|
||||||
@@ -913,6 +1006,7 @@
|
|||||||
onRemoveReference={removeReference}
|
onRemoveReference={removeReference}
|
||||||
designer={value}
|
designer={value}
|
||||||
{settings}
|
{settings}
|
||||||
|
{zoomKoef}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
<!--
|
<!--
|
||||||
@@ -971,7 +1065,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if tables?.length > 0}
|
{#if showColumnFilter && tables?.length > 0}
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<DragColumnMemory {settings} {sourceDragColumn$} {targetDragColumn$} />
|
<DragColumnMemory {settings} {sourceDragColumn$} {targetDragColumn$} />
|
||||||
<div class="searchbox">
|
<div class="searchbox">
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Designer from './Designer.svelte';
|
import Designer from './Designer.svelte';
|
||||||
import DiagramDesignerReference from './DiagramDesignerReference.svelte';
|
import DiagramDesignerReference from './DiagramDesignerReference.svelte';
|
||||||
|
// import QueryDesignerReference from './QueryDesignerReference.svelte';
|
||||||
|
|
||||||
|
export let columnFilter;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Designer
|
<Designer
|
||||||
@@ -18,4 +21,6 @@
|
|||||||
arrangeAlg: 'springy',
|
arrangeAlg: 'springy',
|
||||||
}}
|
}}
|
||||||
referenceComponent={DiagramDesignerReference}
|
referenceComponent={DiagramDesignerReference}
|
||||||
|
showColumnFilter={false}
|
||||||
|
{columnFilter}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
export let domTables;
|
export let domTables;
|
||||||
export let settings;
|
export let settings;
|
||||||
|
|
||||||
|
export let zoomKoef;
|
||||||
|
|
||||||
let src = null;
|
let src = null;
|
||||||
let dst = null;
|
let dst = null;
|
||||||
|
|
||||||
@@ -19,7 +21,7 @@
|
|||||||
const arhi = 12;
|
const arhi = 12;
|
||||||
const arpad = 3;
|
const arpad = 3;
|
||||||
|
|
||||||
export function recomputePosition() {
|
export function recomputePosition(zoomKoef) {
|
||||||
const { designerId, sourceId, targetId, columns, joinType } = reference;
|
const { designerId, sourceId, targetId, columns, joinType } = reference;
|
||||||
|
|
||||||
/** @type {DomTableRef} */
|
/** @type {DomTableRef} */
|
||||||
@@ -31,6 +33,17 @@
|
|||||||
const targetRect = targetTable.getRect();
|
const targetRect = targetTable.getRect();
|
||||||
if (!sourceRect || !targetRect) return null;
|
if (!sourceRect || !targetRect) return null;
|
||||||
|
|
||||||
|
if (zoomKoef > 0) {
|
||||||
|
sourceRect.left /= zoomKoef;
|
||||||
|
sourceRect.right /= zoomKoef;
|
||||||
|
sourceRect.top /= zoomKoef;
|
||||||
|
sourceRect.bottom /= zoomKoef;
|
||||||
|
targetRect.left /= zoomKoef;
|
||||||
|
targetRect.right /= zoomKoef;
|
||||||
|
targetRect.top /= zoomKoef;
|
||||||
|
targetRect.bottom /= zoomKoef;
|
||||||
|
}
|
||||||
|
|
||||||
src = {
|
src = {
|
||||||
x: (sourceRect.left + sourceRect.right) / 2,
|
x: (sourceRect.left + sourceRect.right) / 2,
|
||||||
y: (sourceRect.top + sourceRect.bottom) / 2,
|
y: (sourceRect.top + sourceRect.bottom) / 2,
|
||||||
@@ -47,7 +60,7 @@
|
|||||||
|
|
||||||
$: {
|
$: {
|
||||||
domTables;
|
domTables;
|
||||||
recomputePosition();
|
recomputePosition(zoomKoef);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
1
packages/web/src/designer/DiagramSettings.svelte
Normal file
1
packages/web/src/designer/DiagramSettings.svelte
Normal file
@@ -0,0 +1 @@
|
|||||||
|
This is part of DbGate Premium
|
||||||
@@ -8,12 +8,17 @@
|
|||||||
$: searchValue = value || '';
|
$: searchValue = value || '';
|
||||||
export let isDebounced = false;
|
export let isDebounced = false;
|
||||||
export let onFocusFilteredList = null;
|
export let onFocusFilteredList = null;
|
||||||
|
export let onChange = null;
|
||||||
|
|
||||||
let domInput;
|
let domInput;
|
||||||
|
|
||||||
function handleKeyDown(e) {
|
function handleKeyDown(e) {
|
||||||
if (e.keyCode == keycodes.escape) {
|
if (e.keyCode == keycodes.escape) {
|
||||||
value = '';
|
if (onChange) {
|
||||||
|
onChange('');
|
||||||
|
} else {
|
||||||
|
value = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (e.keyCode == keycodes.downArrow || e.keyCode == keycodes.pageDown || e.keyCode == keycodes.enter) {
|
if (e.keyCode == keycodes.downArrow || e.keyCode == keycodes.pageDown || e.keyCode == keycodes.enter) {
|
||||||
onFocusFilteredList?.();
|
onFocusFilteredList?.();
|
||||||
@@ -27,7 +32,11 @@
|
|||||||
domInput.focus();
|
domInput.focus();
|
||||||
if (text) {
|
if (text) {
|
||||||
domInput.value = text;
|
domInput.value = text;
|
||||||
value = text;
|
if (onChange) {
|
||||||
|
onChange(text);
|
||||||
|
} else {
|
||||||
|
value = text;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -37,8 +46,12 @@
|
|||||||
{placeholder}
|
{placeholder}
|
||||||
value={searchValue}
|
value={searchValue}
|
||||||
on:input={e => {
|
on:input={e => {
|
||||||
if (isDebounced) debouncedSet(domInput.value);
|
if (onChange) {
|
||||||
else value = domInput.value;
|
onChange(domInput.value);
|
||||||
|
} else {
|
||||||
|
if (isDebounced) debouncedSet(domInput.value);
|
||||||
|
else value = domInput.value;
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
on:keydown={handleKeyDown}
|
on:keydown={handleKeyDown}
|
||||||
bind:this={domInput}
|
bind:this={domInput}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
export let isInline = false;
|
export let isInline = false;
|
||||||
export let containerMaxWidth = undefined;
|
export let containerMaxWidth = undefined;
|
||||||
export let flex1 = true;
|
export let flex1 = true;
|
||||||
|
export let contentTestId = undefined;
|
||||||
|
export let inlineTabs = false;
|
||||||
|
|
||||||
export function setValue(index) {
|
export function setValue(index) {
|
||||||
value = index;
|
value = index;
|
||||||
@@ -26,7 +28,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="main" class:flex1>
|
<div class="main" class:flex1>
|
||||||
<div class="tabs">
|
<div class="tabs" class:inlineTabs>
|
||||||
{#each _.compact(tabs) as tab, index}
|
{#each _.compact(tabs) as tab, index}
|
||||||
<div class="tab-item" class:selected={value == index} on:click={() => (value = index)} data-testid={tab.testid}>
|
<div class="tab-item" class:selected={value == index} on:click={() => (value = index)} data-testid={tab.testid}>
|
||||||
<span class="ml-2">
|
<span class="ml-2">
|
||||||
@@ -39,7 +41,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content-container">
|
<div class="content-container" data-testid={contentTestId}>
|
||||||
{#each _.compact(tabs) as tab, index}
|
{#each _.compact(tabs) as tab, index}
|
||||||
<div class="container" class:isInline class:tabVisible={index == value} style:max-width={containerMaxWidth}>
|
<div class="container" class:isInline class:tabVisible={index == value} style:max-width={containerMaxWidth}>
|
||||||
<svelte:component this={tab.component} {...tab.props} tabControlHiddenTab={index != value} />
|
<svelte:component this={tab.component} {...tab.props} tabControlHiddenTab={index != value} />
|
||||||
@@ -77,17 +79,27 @@
|
|||||||
height: var(--dim-tabs-height);
|
height: var(--dim-tabs-height);
|
||||||
min-height: var(--dim-tabs-height);
|
min-height: var(--dim-tabs-height);
|
||||||
right: 0;
|
right: 0;
|
||||||
background-color: var(--theme-bg-2);
|
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tabs:not(.inlineTabs) {
|
||||||
|
background-color: var(--theme-bg-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs.inlineTabs {
|
||||||
|
border-bottom: 1px solid var(--theme-border);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs.inlineTabs .tab-item.selected {
|
||||||
|
border-bottom: 2px solid var(--theme-font-link);
|
||||||
|
}
|
||||||
.tabs::-webkit-scrollbar {
|
.tabs::-webkit-scrollbar {
|
||||||
height: 7px;
|
height: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-item {
|
.tab-item {
|
||||||
border-right: 1px solid var(--theme-border);
|
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -95,6 +107,10 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tabs:not(.inlineTabs) .tab-item {
|
||||||
|
border-right: 1px solid var(--theme-border);
|
||||||
|
}
|
||||||
|
|
||||||
/* .tab-item:hover {
|
/* .tab-item:hover {
|
||||||
color: ${props => props.theme.tabs_font_hover};
|
color: ${props => props.theme.tabs_font_hover};
|
||||||
} */
|
} */
|
||||||
@@ -123,4 +139,5 @@
|
|||||||
.container.isInline:not(.tabVisible) {
|
.container.isInline:not(.tabVisible) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -31,6 +31,11 @@
|
|||||||
export let noCellPadding = false;
|
export let noCellPadding = false;
|
||||||
|
|
||||||
export let domTable = undefined;
|
export let domTable = undefined;
|
||||||
|
export let stickyHeader = false;
|
||||||
|
|
||||||
|
export let checkedKeys = null;
|
||||||
|
export let onSetCheckedKeys = null;
|
||||||
|
export let extractCheckedKey = x => x.id;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
@@ -63,11 +68,15 @@
|
|||||||
on:keydown
|
on:keydown
|
||||||
tabindex={selectable ? -1 : undefined}
|
tabindex={selectable ? -1 : undefined}
|
||||||
on:keydown={handleKeyDown}
|
on:keydown={handleKeyDown}
|
||||||
|
class:stickyHeader
|
||||||
>
|
>
|
||||||
<thead>
|
<thead class:stickyHeader>
|
||||||
<tr>
|
<tr>
|
||||||
|
{#if checkedKeys}
|
||||||
|
<th></th>
|
||||||
|
{/if}
|
||||||
{#each columnList as col}
|
{#each columnList as col}
|
||||||
<td
|
<th
|
||||||
class:clickable={col.sortable}
|
class:clickable={col.sortable}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
if (col.sortable) {
|
if (col.sortable) {
|
||||||
@@ -89,7 +98,7 @@
|
|||||||
{#if sortedByField == col.fieldName}
|
{#if sortedByField == col.fieldName}
|
||||||
<FontIcon icon={sortOrderIsDesc ? 'img sort-desc' : 'img sort-asc'} padLeft />
|
<FontIcon icon={sortOrderIsDesc ? 'img sort-desc' : 'img sort-asc'} padLeft />
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</th>
|
||||||
{/each}
|
{/each}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -108,6 +117,18 @@
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{#if checkedKeys}
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={checkedKeys.includes(extractCheckedKey(row))}
|
||||||
|
on:change={e => {
|
||||||
|
if (e.target['checked']) onSetCheckedKeys(_.uniq([...checkedKeys, extractCheckedKey(row)]));
|
||||||
|
else onSetCheckedKeys(checkedKeys.filter(x => x != extractCheckedKey(row)));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
{/if}
|
||||||
{#each columnList as col}
|
{#each columnList as col}
|
||||||
{@const rowProps = { ...col.props, ...(col.getProps ? col.getProps(row) : null) }}
|
{@const rowProps = { ...col.props, ...(col.getProps ? col.getProps(row) : null) }}
|
||||||
<td class:isHighlighted={col.isHighlighted && col.isHighlighted(row)} class:noCellPadding>
|
<td class:isHighlighted={col.isHighlighted && col.isHighlighted(row)} class:noCellPadding>
|
||||||
@@ -164,7 +185,7 @@
|
|||||||
background: var(--theme-bg-hover);
|
background: var(--theme-bg-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
thead td {
|
thead th {
|
||||||
border: 1px solid var(--theme-border);
|
border: 1px solid var(--theme-border);
|
||||||
background-color: var(--theme-bg-1);
|
background-color: var(--theme-bg-1);
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
@@ -184,4 +205,31 @@
|
|||||||
td.clickable {
|
td.clickable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
thead.stickyHeader {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
border-top: 1px solid var(--theme-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.stickyHeader th {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead.stickyHeader :global(tr:first-child) :global(th) {
|
||||||
|
border-top: 1px solid var(--theme-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.stickyHeader td {
|
||||||
|
border: 0px;
|
||||||
|
border-bottom: 1px solid var(--theme-border);
|
||||||
|
border-right: 1px solid var(--theme-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.stickyHeader {
|
||||||
|
border-spacing: 0;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-left: 1px solid var(--theme-border);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
79
packages/web/src/forms/ExtendedCheckBoxField.svelte
Normal file
79
packages/web/src/forms/ExtendedCheckBoxField.svelte
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let value;
|
||||||
|
|
||||||
|
// use for 3-state checkbox
|
||||||
|
export let inheritedValue = null;
|
||||||
|
|
||||||
|
export let onChange;
|
||||||
|
|
||||||
|
export let label;
|
||||||
|
|
||||||
|
$: renderedValue = value ?? inheritedValue;
|
||||||
|
$: isInherited = inheritedValue != null && value == null;
|
||||||
|
|
||||||
|
function getNextValue() {
|
||||||
|
if (inheritedValue != null) {
|
||||||
|
// 3-state logic
|
||||||
|
if (isInherited) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (renderedValue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !value;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="wrapper"
|
||||||
|
on:click|preventDefault|stopPropagation={() => {
|
||||||
|
onChange(getNextValue());
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="checkbox" {...$$restProps} class:checked={!!renderedValue} class:isInherited />
|
||||||
|
<div class="label">
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
width: 14px !important;
|
||||||
|
height: 14px !important;
|
||||||
|
margin: 5px;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
-o-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
outline: 1px solid var(--theme-border);
|
||||||
|
box-shadow: none;
|
||||||
|
font-size: 0.8em;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1em;
|
||||||
|
background: var(--theme-bg-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checked:after {
|
||||||
|
content: '✔';
|
||||||
|
color: var(--theme-font-1);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isInherited {
|
||||||
|
background: var(--theme-bg-2) !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -459,7 +459,7 @@
|
|||||||
setConfig(x => ({
|
setConfig(x => ({
|
||||||
...x,
|
...x,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
formColumnFilterText: (x.formColumnFilterText || '') + event.key,
|
searchInColumns: (x.searchInColumns || '') + event.key,
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -473,7 +473,7 @@
|
|||||||
if (event.keyCode == keycodes.escape) {
|
if (event.keyCode == keycodes.escape) {
|
||||||
setConfig(x => ({
|
setConfig(x => ({
|
||||||
...x,
|
...x,
|
||||||
formColumnFilterText: '',
|
searchInColumns: '',
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,7 +541,7 @@
|
|||||||
columnIndex = incrementFunc(columnIndex);
|
columnIndex = incrementFunc(columnIndex);
|
||||||
while (
|
while (
|
||||||
isInRange(columnIndex) &&
|
isInRange(columnIndex) &&
|
||||||
!filterName(display.config.formColumnFilterText, display.formColumns[columnIndex].columnName)
|
!filterName(display.config.searchInColumns, display.formColumns[columnIndex].columnName)
|
||||||
) {
|
) {
|
||||||
columnIndex = incrementFunc(columnIndex);
|
columnIndex = incrementFunc(columnIndex);
|
||||||
}
|
}
|
||||||
@@ -549,7 +549,7 @@
|
|||||||
columnIndex = firstInRange;
|
columnIndex = firstInRange;
|
||||||
while (
|
while (
|
||||||
isInRange(columnIndex) &&
|
isInRange(columnIndex) &&
|
||||||
!filterName(display.config.formColumnFilterText, display.formColumns[columnIndex].columnName)
|
!filterName(display.config.searchInColumns, display.formColumns[columnIndex].columnName)
|
||||||
) {
|
) {
|
||||||
columnIndex = incrementFunc(columnIndex);
|
columnIndex = incrementFunc(columnIndex);
|
||||||
}
|
}
|
||||||
@@ -572,7 +572,7 @@
|
|||||||
case keycodes.rightArrow:
|
case keycodes.rightArrow:
|
||||||
return moveCurrentCell(currentCell[0], currentCell[1] + 1);
|
return moveCurrentCell(currentCell[0], currentCell[1] + 1);
|
||||||
case keycodes.upArrow:
|
case keycodes.upArrow:
|
||||||
if (currentCell[1] % 2 == 0 && display.config.formColumnFilterText) {
|
if (currentCell[1] % 2 == 0 && display.config.searchInColumns) {
|
||||||
return findFilteredColumn(
|
return findFilteredColumn(
|
||||||
x => x - 1,
|
x => x - 1,
|
||||||
x => x >= 0,
|
x => x >= 0,
|
||||||
@@ -583,7 +583,7 @@
|
|||||||
|
|
||||||
return moveCurrentCell(currentCell[0] - 1, currentCell[1]);
|
return moveCurrentCell(currentCell[0] - 1, currentCell[1]);
|
||||||
case keycodes.downArrow:
|
case keycodes.downArrow:
|
||||||
if (currentCell[1] % 2 == 0 && display.config.formColumnFilterText) {
|
if (currentCell[1] % 2 == 0 && display.config.searchInColumns) {
|
||||||
return findFilteredColumn(
|
return findFilteredColumn(
|
||||||
x => x + 1,
|
x => x + 1,
|
||||||
x => x < display.formColumns.length,
|
x => x < display.formColumns.length,
|
||||||
@@ -631,8 +631,8 @@
|
|||||||
data-row={rowIndex}
|
data-row={rowIndex}
|
||||||
data-col={chunkIndex * 2}
|
data-col={chunkIndex * 2}
|
||||||
style={rowHeight > 1 ? `height: ${rowHeight}px` : undefined}
|
style={rowHeight > 1 ? `height: ${rowHeight}px` : undefined}
|
||||||
class:columnFiltered={display.config.formColumnFilterText &&
|
class:columnFiltered={display.config.searchInColumns &&
|
||||||
filterName(display.config.formColumnFilterText, col.columnName)}
|
filterName(display.config.searchInColumns, col.columnName)}
|
||||||
class:isSelected={currentCell[0] == rowIndex && currentCell[1] == chunkIndex * 2}
|
class:isSelected={currentCell[0] == rowIndex && currentCell[1] == chunkIndex * 2}
|
||||||
bind:this={domCells[`${rowIndex},${chunkIndex * 2}`]}
|
bind:this={domCells[`${rowIndex},${chunkIndex * 2}`]}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -39,12 +39,12 @@
|
|||||||
<div class="flex">
|
<div class="flex">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={display?.config?.formColumnFilterText || ''}
|
value={display?.config?.searchInColumns || ''}
|
||||||
on:keydown={e => {
|
on:keydown={e => {
|
||||||
if (e.keyCode == keycodes.escape) {
|
if (e.keyCode == keycodes.escape) {
|
||||||
setConfig(x => ({
|
setConfig(x => ({
|
||||||
...x,
|
...x,
|
||||||
formColumnFilterText: '',
|
searchInColumns: '',
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
setConfig(x => ({
|
setConfig(x => ({
|
||||||
...x,
|
...x,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
formColumnFilterText: e.target.value,
|
searchInColumns: e.target.value,
|
||||||
}))}
|
}))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -217,6 +217,8 @@
|
|||||||
'icon autocommit-on': 'mdi mdi-check-circle',
|
'icon autocommit-on': 'mdi mdi-check-circle',
|
||||||
'icon autocommit-off': 'mdi mdi-check-circle-outline',
|
'icon autocommit-off': 'mdi mdi-check-circle-outline',
|
||||||
|
|
||||||
|
'icon premium': 'mdi mdi-star',
|
||||||
|
|
||||||
'img ok': 'mdi mdi-check-circle color-icon-green',
|
'img ok': 'mdi mdi-check-circle color-icon-green',
|
||||||
'img ok-inv': 'mdi mdi-check-circle color-icon-inv-green',
|
'img ok-inv': 'mdi mdi-check-circle color-icon-inv-green',
|
||||||
'img alert': 'mdi mdi-alert-circle color-icon-blue',
|
'img alert': 'mdi mdi-alert-circle color-icon-blue',
|
||||||
|
|||||||
@@ -300,7 +300,10 @@
|
|||||||
initialValue: $values[`columns_${row}`],
|
initialValue: $values[`columns_${row}`],
|
||||||
sourceTableInfo: $sourceDbinfo?.tables?.find(x => x.pureName?.toLowerCase() == row?.toLowerCase()),
|
sourceTableInfo: $sourceDbinfo?.tables?.find(x => x.pureName?.toLowerCase() == row?.toLowerCase()),
|
||||||
targetTableInfo: $targetDbinfo?.tables?.find(x => x.pureName?.toLowerCase() == targetNameLower),
|
targetTableInfo: $targetDbinfo?.tables?.find(x => x.pureName?.toLowerCase() == targetNameLower),
|
||||||
onConfirm: value => setFieldValue(`columns_${row}`, value),
|
onConfirm: value => {
|
||||||
|
setFieldValue(`columns_${row}`, value);
|
||||||
|
targetEditKey += 1;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>{columnCount > 0 ? `(${columnCount} columns)` : '(copy from source)'}
|
>{columnCount > 0 ? `(${columnCount} columns)` : '(copy from source)'}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { copyTextToClipboard } from '../utility/clipboard';
|
import { copyTextToClipboard } from '../utility/clipboard';
|
||||||
import { openJsonLinesData } from '../utility/openJsonLinesData';
|
import { openJsonLinesData } from '../utility/openJsonLinesData';
|
||||||
|
import { useSettings } from '../utility/metadataLoaders';
|
||||||
|
|
||||||
setContext('json-tree-context-key', {});
|
setContext('json-tree-context-key', {});
|
||||||
|
|
||||||
@@ -23,6 +24,9 @@
|
|||||||
export let isInserted = false;
|
export let isInserted = false;
|
||||||
export let isModified = false;
|
export let isModified = false;
|
||||||
|
|
||||||
|
const settings = useSettings();
|
||||||
|
$: wrap = $settings?.['behaviour.jsonPreviewWrap'];
|
||||||
|
|
||||||
setContext('json-tree-default-expanded', expandAll);
|
setContext('json-tree-default-expanded', expandAll);
|
||||||
if (slicedKeyCount) setContext('json-tree-sliced-key-count', slicedKeyCount);
|
if (slicedKeyCount) setContext('json-tree-sliced-key-count', slicedKeyCount);
|
||||||
|
|
||||||
@@ -66,6 +70,7 @@
|
|||||||
class:isDeleted
|
class:isDeleted
|
||||||
class:isInserted
|
class:isInserted
|
||||||
class:isModified
|
class:isModified
|
||||||
|
class:wrap
|
||||||
>
|
>
|
||||||
<JSONNode
|
<JSONNode
|
||||||
{key}
|
{key}
|
||||||
@@ -115,6 +120,9 @@
|
|||||||
list-style: none;
|
list-style: none;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
ul.wrap :global(li) {
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
ul,
|
ul,
|
||||||
ul :global(ul) {
|
ul :global(ul) {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
Build date: <span>{moment(buildTime).format('YYYY-MM-DD')}</span>
|
Build date: <span>{moment(buildTime).format('YYYY-MM-DD')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-1">
|
<div class="m-1">
|
||||||
Web: <Link href="https://dbgate.org">dbgate.org</Link>
|
Web: <Link href="https://dbgate.io">dbgate.io</Link>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-1">
|
<div class="m-1">
|
||||||
Source codes: <Link href="https://github.com/dbgate/dbgate/">github</Link>
|
Source codes: <Link href="https://github.com/dbgate/dbgate/">github</Link>
|
||||||
@@ -32,9 +32,6 @@
|
|||||||
<div class="m-1">
|
<div class="m-1">
|
||||||
Docker container: <Link href="https://hub.docker.com/r/dbgate/dbgate">docker hub</Link>
|
Docker container: <Link href="https://hub.docker.com/r/dbgate/dbgate">docker hub</Link>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-1">
|
|
||||||
Online demo: <Link href="https://demo.dbgate.org">demo.dbgate.org</Link>
|
|
||||||
</div>
|
|
||||||
<div class="m-1">
|
<div class="m-1">
|
||||||
Search plugins: <Link href="https://www.npmjs.com/search?q=keywords:dbgateplugin">npmjs.com</Link>
|
Search plugins: <Link href="https://www.npmjs.com/search?q=keywords:dbgateplugin">npmjs.com</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import DropDownButton from '../buttons/DropDownButton.svelte';
|
|
||||||
import FormStyledButton from '../buttons/FormStyledButton.svelte';
|
import FormStyledButton from '../buttons/FormStyledButton.svelte';
|
||||||
import ColumnMapColumnDropdown from '../elements/ColumnMapColumnDropdown.svelte';
|
import ColumnMapColumnDropdown from '../elements/ColumnMapColumnDropdown.svelte';
|
||||||
import Link from '../elements/Link.svelte';
|
import Link from '../elements/Link.svelte';
|
||||||
@@ -8,8 +7,10 @@
|
|||||||
|
|
||||||
import FormProvider from '../forms/FormProvider.svelte';
|
import FormProvider from '../forms/FormProvider.svelte';
|
||||||
import FormSubmit from '../forms/FormSubmit.svelte';
|
import FormSubmit from '../forms/FormSubmit.svelte';
|
||||||
|
import FontIcon from '../icons/FontIcon.svelte';
|
||||||
import ModalBase from './ModalBase.svelte';
|
import ModalBase from './ModalBase.svelte';
|
||||||
import { closeCurrentModal } from './modalTools';
|
import { closeCurrentModal } from './modalTools';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
export let header = 'Configure columns';
|
export let header = 'Configure columns';
|
||||||
export let onConfirm;
|
export let onConfirm;
|
||||||
@@ -34,6 +35,15 @@
|
|||||||
skip: false,
|
skip: false,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
if (sourceTableInfo && targetTableInfo) {
|
||||||
|
return sourceTableInfo.columns
|
||||||
|
.map(x => ({
|
||||||
|
src: x.columnName,
|
||||||
|
dst: targetTableInfo.columns.find(y => y.columnName == x.columnName)?.columnName,
|
||||||
|
skip: false,
|
||||||
|
}))
|
||||||
|
.filter(x => x.dst != null);
|
||||||
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +61,32 @@
|
|||||||
$: differentFromReset = !equalValues(value, resetValue);
|
$: differentFromReset = !equalValues(value, resetValue);
|
||||||
|
|
||||||
let value = initialValue?.length > 0 ? initialValue : resetValue;
|
let value = initialValue?.length > 0 ? initialValue : resetValue;
|
||||||
|
|
||||||
|
let validationError;
|
||||||
|
|
||||||
|
function validate() {
|
||||||
|
validationError = null;
|
||||||
|
if (!value) return;
|
||||||
|
if (value.length == 0) return;
|
||||||
|
if (value.some(x => !x.src || !x.dst)) {
|
||||||
|
validationError = 'Source and target columns must be defined';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const duplicates = _.chain(value.map(x => x.dst))
|
||||||
|
.countBy()
|
||||||
|
.pickBy(count => count > 1)
|
||||||
|
.keys()
|
||||||
|
.value();
|
||||||
|
if (duplicates.length > 0) {
|
||||||
|
validationError = 'Target columns must be unique, duplicates found: ' + duplicates.join(', ');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
value;
|
||||||
|
validate();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FormProvider>
|
<FormProvider>
|
||||||
@@ -91,8 +127,8 @@
|
|||||||
<svelte:fragment slot="2" let:row let:index>
|
<svelte:fragment slot="2" let:row let:index>
|
||||||
<ColumnMapColumnDropdown
|
<ColumnMapColumnDropdown
|
||||||
value={row['dst']}
|
value={row['dst']}
|
||||||
onChange={e =>
|
onChange={column =>
|
||||||
(value = (value || []).map((x, i) => (i == index ? { ...x, dst: e.target.value, ignore: false } : x)))}
|
(value = (value || []).map((x, i) => (i == index ? { ...x, dst: column, ignore: false } : x)))}
|
||||||
tableInfo={targetTableInfo}
|
tableInfo={targetTableInfo}
|
||||||
/>
|
/>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
@@ -105,9 +141,17 @@
|
|||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</TableControl>
|
</TableControl>
|
||||||
|
|
||||||
|
{#if validationError}
|
||||||
|
<div class="error-result">
|
||||||
|
<FontIcon icon="img error" />
|
||||||
|
{validationError}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<svelte:fragment slot="footer">
|
<svelte:fragment slot="footer">
|
||||||
<FormSubmit
|
<FormSubmit
|
||||||
value="OK"
|
value="OK"
|
||||||
|
disabled={!!validationError}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
closeCurrentModal();
|
closeCurrentModal();
|
||||||
onConfirm(!value || value.length == 0 || !differentFromReset ? null : value);
|
onConfirm(!value || value.length == 0 || !differentFromReset ? null : value);
|
||||||
@@ -132,3 +176,9 @@
|
|||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</ModalBase>
|
</ModalBase>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.error-result {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -8,15 +8,23 @@
|
|||||||
|
|
||||||
export let message;
|
export let message;
|
||||||
export let onConfirm;
|
export let onConfirm;
|
||||||
|
export let confirmLabel = 'OK';
|
||||||
|
export let header = null;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FormProvider>
|
<FormProvider>
|
||||||
<ModalBase {...$$restProps}>
|
<ModalBase {...$$restProps}>
|
||||||
|
<svelte:fragment slot="header">
|
||||||
|
{#if header}
|
||||||
|
{header}
|
||||||
|
{/if}
|
||||||
|
</svelte:fragment>
|
||||||
|
|
||||||
{message}
|
{message}
|
||||||
|
|
||||||
<svelte:fragment slot="footer">
|
<svelte:fragment slot="footer">
|
||||||
<FormSubmit
|
<FormSubmit
|
||||||
value="OK"
|
value={confirmLabel}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
closeCurrentModal();
|
closeCurrentModal();
|
||||||
onConfirm();
|
onConfirm();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<DropDownMenu
|
<DropDownMenu
|
||||||
left={$currentDropDownMenu.left}
|
left={$currentDropDownMenu.left}
|
||||||
top={$currentDropDownMenu.top}
|
top={$currentDropDownMenu.top}
|
||||||
|
bottom={$currentDropDownMenu.bottom}
|
||||||
items={$currentDropDownMenu.items}
|
items={$currentDropDownMenu.items}
|
||||||
targetElement={$currentDropDownMenu.targetElement}
|
targetElement={$currentDropDownMenu.targetElement}
|
||||||
on:close={() => ($currentDropDownMenu = null)}
|
on:close={() => ($currentDropDownMenu = null)}
|
||||||
|
|||||||
@@ -7,29 +7,6 @@
|
|||||||
if (side == 'right') return { top: top, left: left + box.width };
|
if (side == 'right') return { top: top, left: left + box.width };
|
||||||
return { top: top, left: left };
|
return { top: top, left: left };
|
||||||
}
|
}
|
||||||
|
|
||||||
function fixPopupPlacement(element) {
|
|
||||||
const { width, height } = element.getBoundingClientRect();
|
|
||||||
let offset = getElementOffset(element);
|
|
||||||
|
|
||||||
let newLeft = null;
|
|
||||||
let newTop = null;
|
|
||||||
|
|
||||||
if (offset.left + width > window.innerWidth) {
|
|
||||||
newLeft = offset.left - width;
|
|
||||||
|
|
||||||
if (newLeft < 0) newLeft = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (offset.top + height > window.innerHeight) {
|
|
||||||
newTop = offset.top - height;
|
|
||||||
|
|
||||||
if (newTop < 0) newTop = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newLeft != null) element.style.left = `${newLeft}px`;
|
|
||||||
if (newTop != null) element.style.top = `${newTop}px`;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -43,12 +20,17 @@
|
|||||||
|
|
||||||
export let items;
|
export let items;
|
||||||
export let top;
|
export let top;
|
||||||
|
export let bottom;
|
||||||
export let left;
|
export let left;
|
||||||
export let onCloseParent;
|
export let onCloseParent;
|
||||||
export let targetElement;
|
export let targetElement;
|
||||||
|
export let submenuLevel = 0;
|
||||||
|
|
||||||
let element;
|
let element;
|
||||||
|
|
||||||
|
let newLeft = undefined;
|
||||||
|
let newTop = undefined;
|
||||||
|
|
||||||
let hoverItem;
|
let hoverItem;
|
||||||
let hoverOffset;
|
let hoverOffset;
|
||||||
|
|
||||||
@@ -57,6 +39,8 @@
|
|||||||
|
|
||||||
let switchIndex = 0;
|
let switchIndex = 0;
|
||||||
|
|
||||||
|
let submenuKey = 0;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
let closeHandlers = [];
|
let closeHandlers = [];
|
||||||
|
|
||||||
@@ -68,6 +52,25 @@
|
|||||||
closeHandlers = [];
|
closeHandlers = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fixPopupPlacement(element) {
|
||||||
|
const { width, height } = element.getBoundingClientRect();
|
||||||
|
let offset = getElementOffset(element);
|
||||||
|
|
||||||
|
if (offset.left + width > window.innerWidth) {
|
||||||
|
newLeft = offset.left - width;
|
||||||
|
|
||||||
|
if (newLeft < 0) newLeft = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset.top < 0) {
|
||||||
|
newTop = 0;
|
||||||
|
} else if (offset.top + height > window.innerHeight) {
|
||||||
|
newTop = offset.top - height;
|
||||||
|
|
||||||
|
if (newTop < 0) newTop = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function registerCloseHandler(handler) {
|
function registerCloseHandler(handler) {
|
||||||
closeHandlers.push(handler);
|
closeHandlers.push(handler);
|
||||||
}
|
}
|
||||||
@@ -80,6 +83,7 @@
|
|||||||
|
|
||||||
submenuItem = item;
|
submenuItem = item;
|
||||||
submenuOffset = hoverOffset;
|
submenuOffset = hoverOffset;
|
||||||
|
submenuKey += 1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (item.switchStore && item.switchValue) {
|
if (item.switchStore && item.switchValue) {
|
||||||
@@ -109,6 +113,7 @@
|
|||||||
const changeActiveSubmenu = _.throttle(() => {
|
const changeActiveSubmenu = _.throttle(() => {
|
||||||
submenuItem = hoverItem;
|
submenuItem = hoverItem;
|
||||||
submenuOffset = hoverOffset;
|
submenuOffset = hoverOffset;
|
||||||
|
submenuKey += 1;
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
$: preparedItems = prepareMenuItems(items, { targetElement, registerCloseHandler }, $commandsCustomized);
|
$: preparedItems = prepareMenuItems(items, { targetElement, registerCloseHandler }, $commandsCustomized);
|
||||||
@@ -128,7 +133,14 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ul class="dropDownMenuMarker" style={`left: ${left}px; top: ${top}px`} bind:this={element}>
|
<ul
|
||||||
|
class="dropDownMenuMarker"
|
||||||
|
style:top={(newTop ?? top) != null ? `${newTop ?? top}px` : undefined}
|
||||||
|
style:bottom={newTop == null && bottom != null ? `${bottom}px` : undefined}
|
||||||
|
style:left={(newLeft ?? left) != null ? `${newLeft ?? left}px` : undefined}
|
||||||
|
bind:this={element}
|
||||||
|
data-testid={`DropDownMenu-container-${submenuLevel}`}
|
||||||
|
>
|
||||||
{#each preparedItems as item}
|
{#each preparedItems as item}
|
||||||
{#if item.divider}
|
{#if item.divider}
|
||||||
<li class="divider" />
|
<li class="divider" />
|
||||||
@@ -172,14 +184,17 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
{#if submenuItem?.submenu}
|
{#if submenuItem?.submenu}
|
||||||
<svelte:self
|
{#key submenuKey}
|
||||||
items={submenuItem?.submenu}
|
<svelte:self
|
||||||
{...submenuOffset}
|
items={submenuItem?.submenu}
|
||||||
onCloseParent={() => {
|
{...submenuOffset}
|
||||||
if (onCloseParent) onCloseParent();
|
onCloseParent={() => {
|
||||||
dispatchClose();
|
if (onCloseParent) onCloseParent();
|
||||||
}}
|
dispatchClose();
|
||||||
/>
|
}}
|
||||||
|
submenuLevel={submenuLevel + 1}
|
||||||
|
/>
|
||||||
|
{/key}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -39,7 +39,14 @@
|
|||||||
<!-- The Modal -->
|
<!-- The Modal -->
|
||||||
<div id="myModal" class="bglayer">
|
<div id="myModal" class="bglayer">
|
||||||
<!-- Modal content -->
|
<!-- Modal content -->
|
||||||
<div class="window" class:fullScreen class:simple use:clickOutside on:clickOutside={handleClickOutside}>
|
<div
|
||||||
|
class="window"
|
||||||
|
class:fullScreen
|
||||||
|
class:simple
|
||||||
|
use:clickOutside
|
||||||
|
on:clickOutside={handleClickOutside}
|
||||||
|
data-testid="ModalBase_window"
|
||||||
|
>
|
||||||
{#if $$slots.header}
|
{#if $$slots.header}
|
||||||
<div class="header" class:fullScreen>
|
<div class="header" class:fullScreen>
|
||||||
<div><slot name="header" /></div>
|
<div><slot name="header" /></div>
|
||||||
|
|||||||
@@ -1,13 +1,24 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import FormTextField from '../forms/FormTextField.svelte';
|
import FormTextField from '../forms/FormTextField.svelte';
|
||||||
import { openedConnections, openedSingleDatabaseConnections } from '../stores';
|
import { extensions, openedConnections, openedSingleDatabaseConnections } from '../stores';
|
||||||
import { getFormContext } from '../forms/FormProviderCore.svelte';
|
import { getFormContext } from '../forms/FormProviderCore.svelte';
|
||||||
import FormTextAreaField from '../forms/FormTextAreaField.svelte';
|
import FormTextAreaField from '../forms/FormTextAreaField.svelte';
|
||||||
|
import FormArgumentList from '../forms/FormArgumentList.svelte';
|
||||||
|
|
||||||
const { values } = getFormContext();
|
const { values } = getFormContext();
|
||||||
|
|
||||||
|
$: engine = $values.engine;
|
||||||
|
|
||||||
|
$: driver = $extensions.drivers.find(x => x.engine == engine);
|
||||||
|
|
||||||
$: isConnected = $openedConnections.includes($values._id) || $openedSingleDatabaseConnections.includes($values._id);
|
$: isConnected = $openedConnections.includes($values._id) || $openedSingleDatabaseConnections.includes($values._id);
|
||||||
|
|
||||||
|
$: advancedFields = driver?.getAdvancedConnectionFields ? driver?.getAdvancedConnectionFields() : null;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FormTextAreaField label="Allowed databases, one per line" name="allowedDatabases" disabled={isConnected} rows={8} />
|
<FormTextAreaField label="Allowed databases, one per line" name="allowedDatabases" disabled={isConnected} rows={8} />
|
||||||
<FormTextField label="Allowed databases regular expression" name="allowedDatabasesRegex" disabled={isConnected} />
|
<FormTextField label="Allowed databases regular expression" name="allowedDatabasesRegex" disabled={isConnected} />
|
||||||
|
|
||||||
|
{#if advancedFields}
|
||||||
|
<FormArgumentList args={advancedFields} />
|
||||||
|
{/if}
|
||||||
|
|||||||
@@ -28,6 +28,8 @@
|
|||||||
selectedWidget,
|
selectedWidget,
|
||||||
lockedDatabaseMode,
|
lockedDatabaseMode,
|
||||||
visibleWidgetSideBar,
|
visibleWidgetSideBar,
|
||||||
|
currentTheme,
|
||||||
|
getSystemTheme,
|
||||||
} from '../stores';
|
} from '../stores';
|
||||||
import { isMac } from '../utility/common';
|
import { isMac } from '../utility/common';
|
||||||
import getElectron from '../utility/getElectron';
|
import getElectron from '../utility/getElectron';
|
||||||
@@ -280,6 +282,32 @@ ORDER BY
|
|||||||
|
|
||||||
<svelte:fragment slot="3">
|
<svelte:fragment slot="3">
|
||||||
<div class="heading">Application theme</div>
|
<div class="heading">Application theme</div>
|
||||||
|
|
||||||
|
<FormFieldTemplateLarge
|
||||||
|
label="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">
|
<div class="themes">
|
||||||
{#each $extensions.themes as theme}
|
{#each $extensions.themes as theme}
|
||||||
<ThemeSkeleton {theme} />
|
<ThemeSkeleton {theme} />
|
||||||
@@ -403,6 +431,12 @@ ORDER BY
|
|||||||
|
|
||||||
<FormCheckboxField name="behaviour.useTabPreviewMode" label="Use tab preview mode" defaultValue={true} />
|
<FormCheckboxField name="behaviour.useTabPreviewMode" label="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">
|
<div class="tip">
|
||||||
<FontIcon icon="img tip" /> When you single-click or select a file in the "Tables, Views, Functions" view, it
|
<FontIcon icon="img tip" /> 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
|
is shown in a preview mode and reuses an existing tab (preview tab). This is useful if you are quickly browsing
|
||||||
@@ -495,16 +529,12 @@ ORDER BY
|
|||||||
label="Folder with mysql plugins (for example for authentication). Set only in case of problems"
|
label="Folder with mysql plugins (for example for authentication). Set only in case of problems"
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
/>
|
/>
|
||||||
<FormTextField
|
<FormTextField
|
||||||
name="externalTools.pg_dump"
|
name="externalTools.pg_dump"
|
||||||
label="pg_dump (backup PostgreSQL database)"
|
label="pg_dump (backup PostgreSQL database)"
|
||||||
defaultValue="pg_dump"
|
defaultValue="pg_dump"
|
||||||
/>
|
/>
|
||||||
<FormTextField
|
<FormTextField name="externalTools.psql" label="psql (restore PostgreSQL database)" defaultValue="psql" />
|
||||||
name="externalTools.psql"
|
|
||||||
label="psql (restore PostgreSQL database)"
|
|
||||||
defaultValue="psql"
|
|
||||||
/>
|
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</TabControl>
|
</TabControl>
|
||||||
</FormValues>
|
</FormValues>
|
||||||
|
|||||||
@@ -26,15 +26,29 @@ export interface TabDefinition {
|
|||||||
focused?: boolean;
|
focused?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSystemTheme() {
|
const darkModeMediaQuery = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
|
||||||
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'theme-dark' : 'theme-light';
|
|
||||||
|
export const systemThemeStore = writable(darkModeMediaQuery?.matches ? 'theme-dark' : 'theme-light');
|
||||||
|
|
||||||
|
if (darkModeMediaQuery) {
|
||||||
|
darkModeMediaQuery.addEventListener('change', e => {
|
||||||
|
systemThemeStore.set(e.matches ? 'theme-dark' : 'theme-light');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function writableWithStorage<T>(defaultValue: T, storageName) {
|
export function getSystemTheme() {
|
||||||
|
return darkModeMediaQuery?.matches ? 'theme-dark' : 'theme-light';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function writableWithStorage<T>(defaultValue: T, storageName, removeCondition?: (value: T) => boolean) {
|
||||||
const init = localStorage.getItem(storageName);
|
const init = localStorage.getItem(storageName);
|
||||||
const res = writable<T>(init ? safeJsonParse(init, defaultValue, true) : defaultValue);
|
const res = writable<T>(init ? safeJsonParse(init, defaultValue, true) : defaultValue);
|
||||||
res.subscribe(value => {
|
res.subscribe(value => {
|
||||||
localStorage.setItem(storageName, JSON.stringify(value));
|
if (removeCondition && removeCondition(value)) {
|
||||||
|
localStorage.removeItem(storageName);
|
||||||
|
} else {
|
||||||
|
localStorage.setItem(storageName, JSON.stringify(value));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@@ -104,8 +118,8 @@ export const extensions = writable<ExtensionsDirectory>(null);
|
|||||||
export const visibleCommandPalette = writable(null);
|
export const visibleCommandPalette = writable(null);
|
||||||
export const commands = writable({});
|
export const commands = writable({});
|
||||||
export const currentTheme = getElectron()
|
export const currentTheme = getElectron()
|
||||||
? writableSettingsValue(getSystemTheme(), 'currentTheme')
|
? writableSettingsValue(null, 'currentTheme')
|
||||||
: writableWithStorage(getSystemTheme(), 'currentTheme');
|
: writableWithStorage(null, 'currentTheme', x => x == null);
|
||||||
export const currentEditorTheme = getElectron()
|
export const currentEditorTheme = getElectron()
|
||||||
? writableSettingsValue(null, 'currentEditorTheme')
|
? writableSettingsValue(null, 'currentEditorTheme')
|
||||||
: writableWithStorage(null, 'currentEditorTheme');
|
: writableWithStorage(null, 'currentEditorTheme');
|
||||||
@@ -197,12 +211,24 @@ export const connectionAppObjectSearchSettings = writableWithStorage(
|
|||||||
'connectionAppObjectSearchSettings2'
|
'connectionAppObjectSearchSettings2'
|
||||||
);
|
);
|
||||||
|
|
||||||
export const currentThemeDefinition = derived([currentTheme, extensions], ([$currentTheme, $extensions]) =>
|
let currentThemeValue = null;
|
||||||
$extensions?.themes?.find(x => x.themeClassName == $currentTheme)
|
currentTheme.subscribe(value => {
|
||||||
|
currentThemeValue = value;
|
||||||
|
});
|
||||||
|
export const getCurrentTheme = () => currentThemeValue;
|
||||||
|
|
||||||
|
export const currentThemeDefinition = derived(
|
||||||
|
[currentTheme, extensions, systemThemeStore],
|
||||||
|
([$currentTheme, $extensions, $systemTheme]) => {
|
||||||
|
const usedTheme = $currentTheme ?? $systemTheme;
|
||||||
|
return $extensions?.themes?.find(x => x.themeClassName == usedTheme);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
currentThemeDefinition.subscribe(value => {
|
currentThemeDefinition.subscribe(value => {
|
||||||
if (value?.themeType) {
|
if (value?.themeType && getCurrentTheme()) {
|
||||||
localStorage.setItem('currentThemeType', value?.themeType);
|
localStorage.setItem('currentThemeType', value?.themeType);
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem('currentThemeType');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
export const openedConnectionsWithTemporary = derived(
|
export const openedConnectionsWithTemporary = derived(
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
export let tabid;
|
export let tabid;
|
||||||
export let conid;
|
export let conid;
|
||||||
export let connectionStore = undefined;
|
export let connectionStore = undefined;
|
||||||
|
export let inlineTabs = false;
|
||||||
|
|
||||||
export let onlyTestButton;
|
export let onlyTestButton;
|
||||||
|
|
||||||
@@ -237,8 +238,10 @@
|
|||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<TabControl
|
<TabControl
|
||||||
isInline
|
isInline
|
||||||
|
{inlineTabs}
|
||||||
containerMaxWidth="800px"
|
containerMaxWidth="800px"
|
||||||
flex1={false}
|
flex1={false}
|
||||||
|
contentTestId="ConnectionTab_tabControlContent"
|
||||||
tabs={[
|
tabs={[
|
||||||
{
|
{
|
||||||
label: 'General',
|
label: 'General',
|
||||||
|
|||||||
@@ -15,24 +15,29 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import useEditorData from '../query/useEditorData';
|
import useEditorData from '../query/useEditorData';
|
||||||
import { extensions } from '../stores';
|
|
||||||
import { useConnectionInfo, useDatabaseInfo } from '../utility/metadataLoaders';
|
|
||||||
import { registerFileCommands } from '../commands/stdCommands';
|
import { registerFileCommands } from '../commands/stdCommands';
|
||||||
import createUndoReducer from '../utility/createUndoReducer';
|
import createUndoReducer from '../utility/createUndoReducer';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { findEngineDriver } from 'dbgate-tools';
|
|
||||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||||
import DiagramDesigner from '../designer/DiagramDesigner.svelte';
|
import DiagramDesigner from '../designer/DiagramDesigner.svelte';
|
||||||
import ToolStripContainer from '../buttons/ToolStripContainer.svelte';
|
import ToolStripContainer from '../buttons/ToolStripContainer.svelte';
|
||||||
import ToolStripCommandButton from '../buttons/ToolStripCommandButton.svelte';
|
import ToolStripCommandButton from '../buttons/ToolStripCommandButton.svelte';
|
||||||
import invalidateCommands from '../commands/invalidateCommands';
|
import invalidateCommands from '../commands/invalidateCommands';
|
||||||
import ToolStripSaveButton from '../buttons/ToolStripSaveButton.svelte';
|
import ToolStripSaveButton from '../buttons/ToolStripSaveButton.svelte';
|
||||||
import VerticalSplitter from "../elements/VerticalSplitter.svelte";
|
import HorizontalSplitter from '../elements/HorizontalSplitter.svelte';
|
||||||
|
import WidgetColumnBar from '../widgets/WidgetColumnBar.svelte';
|
||||||
|
import WidgetColumnBarItem from '../widgets/WidgetColumnBarItem.svelte';
|
||||||
|
import WidgetsInnerContainer from '../widgets/WidgetsInnerContainer.svelte';
|
||||||
|
import ToolStripButton from '../buttons/ToolStripButton.svelte';
|
||||||
|
import DiagramSettings from '../designer/DiagramSettings.svelte';
|
||||||
|
import { derived } from 'svelte/store';
|
||||||
|
import { isProApp } from '../utility/proTools';
|
||||||
|
|
||||||
export let tabid;
|
export let tabid;
|
||||||
export let conid;
|
export let conid;
|
||||||
export let database;
|
export let database;
|
||||||
export let initialArgs;
|
|
||||||
|
let tableCounts = {};
|
||||||
|
|
||||||
export const activator = createActivator('DiagramTab', true);
|
export const activator = createActivator('DiagramTab', true);
|
||||||
|
|
||||||
@@ -87,6 +92,22 @@
|
|||||||
references: [],
|
references: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const setStyle = style =>
|
||||||
|
// @ts-ignore
|
||||||
|
dispatchModel({
|
||||||
|
type: 'compute',
|
||||||
|
compute: v => ({ ...v, style: _.isFunction(style) ? style(v.style) : style }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const styleDerivedStore = derived(modelState, ($modelState: any) =>
|
||||||
|
$modelState.value ? $modelState.value.style || {} : {}
|
||||||
|
);
|
||||||
|
const styleStore = {
|
||||||
|
...styleDerivedStore,
|
||||||
|
update: setStyle,
|
||||||
|
set: setStyle,
|
||||||
|
};
|
||||||
|
|
||||||
function createMenu() {
|
function createMenu() {
|
||||||
return [
|
return [
|
||||||
{ command: 'diagram.save' },
|
{ command: 'diagram.save' },
|
||||||
@@ -98,14 +119,42 @@
|
|||||||
{ command: 'diagram.redo' },
|
{ command: 'diagram.redo' },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleReportCounts(counts) {
|
||||||
|
tableCounts = counts;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ToolStripContainer>
|
<ToolStripContainer>
|
||||||
<VerticalSplitter isSplitter={false}>
|
<HorizontalSplitter isSplitter={isProApp() ? ($styleStore.settingsVisible ?? true) : false} initialSizeRight={300}>
|
||||||
<svelte:fragment slot="1">
|
<svelte:fragment slot="1">
|
||||||
<DiagramDesigner value={$modelState.value || {}} {conid} {database} onChange={handleChange} menu={createMenu} />
|
<DiagramDesigner
|
||||||
|
value={$modelState.value || {}}
|
||||||
|
{conid}
|
||||||
|
{database}
|
||||||
|
onChange={handleChange}
|
||||||
|
menu={createMenu}
|
||||||
|
columnFilter={$styleStore.columnFilter}
|
||||||
|
onReportCounts={handleReportCounts}
|
||||||
|
/>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</VerticalSplitter>
|
<svelte:fragment slot="2">
|
||||||
|
<WidgetColumnBar>
|
||||||
|
<WidgetColumnBarItem
|
||||||
|
title="Settings"
|
||||||
|
name="diagramSettings"
|
||||||
|
storageName="diagramSettingsWidget"
|
||||||
|
onClose={() => {
|
||||||
|
styleStore.update(x => ({ ...x, settingsVisible: false }));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<WidgetsInnerContainer skipDefineWidth>
|
||||||
|
<DiagramSettings values={styleStore} {tableCounts} />
|
||||||
|
</WidgetsInnerContainer>
|
||||||
|
</WidgetColumnBarItem>
|
||||||
|
</WidgetColumnBar>
|
||||||
|
</svelte:fragment>
|
||||||
|
</HorizontalSplitter>
|
||||||
|
|
||||||
<svelte:fragment slot="toolstrip">
|
<svelte:fragment slot="toolstrip">
|
||||||
<ToolStripCommandButton command="designer.arrange" />
|
<ToolStripCommandButton command="designer.arrange" />
|
||||||
@@ -113,5 +162,13 @@
|
|||||||
<ToolStripCommandButton command="diagram.export" />
|
<ToolStripCommandButton command="diagram.export" />
|
||||||
<ToolStripCommandButton command="diagram.undo" />
|
<ToolStripCommandButton command="diagram.undo" />
|
||||||
<ToolStripCommandButton command="diagram.redo" />
|
<ToolStripCommandButton command="diagram.redo" />
|
||||||
|
{#if isProApp()}
|
||||||
|
<ToolStripButton
|
||||||
|
icon="icon settings"
|
||||||
|
on:click={() => {
|
||||||
|
styleStore.update(x => ({ ...x, settingsVisible: !x.settingsVisible }));
|
||||||
|
}}>Settings</ToolStripButton
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</ToolStripContainer>
|
</ToolStripContainer>
|
||||||
|
|||||||
@@ -5,16 +5,26 @@ import { runGroupCommand } from '../commands/runCommand';
|
|||||||
import { currentDropDownMenu, visibleCommandPalette } from '../stores';
|
import { currentDropDownMenu, visibleCommandPalette } from '../stores';
|
||||||
import getAsArray from './getAsArray';
|
import getAsArray from './getAsArray';
|
||||||
|
|
||||||
|
let isContextMenuSupressed = false;
|
||||||
|
|
||||||
export function registerMenu(...items) {
|
export function registerMenu(...items) {
|
||||||
const parentMenu = getContext('componentContextMenu');
|
const parentMenu = getContext('componentContextMenu');
|
||||||
setContext('componentContextMenu', [parentMenu, ...items]);
|
setContext('componentContextMenu', [parentMenu, ...items]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function supressContextMenu() {
|
||||||
|
isContextMenuSupressed = true;
|
||||||
|
}
|
||||||
|
|
||||||
export default function contextMenu(node, items: any = []) {
|
export default function contextMenu(node, items: any = []) {
|
||||||
const handleContextMenu = async e => {
|
const handleContextMenu = async e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (isContextMenuSupressed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await invalidateCommands();
|
await invalidateCommands();
|
||||||
|
|
||||||
if (items) {
|
if (items) {
|
||||||
@@ -24,13 +34,19 @@ export default function contextMenu(node, items: any = []) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleMouseDown = () => {
|
||||||
|
isContextMenuSupressed = false;
|
||||||
|
};
|
||||||
|
|
||||||
if (items == '__no_menu') return;
|
if (items == '__no_menu') return;
|
||||||
|
|
||||||
node.addEventListener('contextmenu', handleContextMenu);
|
node.addEventListener('contextmenu', handleContextMenu);
|
||||||
|
node.addEventListener('mousedown', handleMouseDown);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
destroy() {
|
destroy() {
|
||||||
node.removeEventListener('contextmenu', handleContextMenu);
|
node.removeEventListener('contextmenu', handleContextMenu);
|
||||||
|
node.removeEventListener('mousedown', handleMouseDown);
|
||||||
},
|
},
|
||||||
update(value) {
|
update(value) {
|
||||||
items = value;
|
items = value;
|
||||||
|
|||||||
58
packages/web/src/utility/dragScroll.ts
Normal file
58
packages/web/src/utility/dragScroll.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { supressContextMenu } from './contextMenu';
|
||||||
|
|
||||||
|
export default function dragScroll(node, onScroll) {
|
||||||
|
if (!onScroll) return;
|
||||||
|
|
||||||
|
let lastX = null;
|
||||||
|
let lastY = null;
|
||||||
|
|
||||||
|
let sumMoved = 0;
|
||||||
|
|
||||||
|
const handleMoveDown = e => {
|
||||||
|
if (e.button != 2) return;
|
||||||
|
|
||||||
|
lastX = e.clientX;
|
||||||
|
lastY = e.clientY;
|
||||||
|
document.addEventListener('mousemove', handleMoveMove, true);
|
||||||
|
document.addEventListener('mouseup', handleMoveEnd, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMoveMove = e => {
|
||||||
|
// const zoomKoef = window.getComputedStyle(node)['zoom'];
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
sumMoved += Math.abs(e.clientX - lastX) + Math.abs(e.clientY - lastY);
|
||||||
|
if (sumMoved > 20) {
|
||||||
|
supressContextMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
onScroll(e.clientX - lastX, e.clientY - lastY);
|
||||||
|
|
||||||
|
lastX = e.clientX;
|
||||||
|
lastY = e.clientY;
|
||||||
|
};
|
||||||
|
const handleMoveEnd = e => {
|
||||||
|
// const zoomKoef = window.getComputedStyle(node)['zoom'];
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
lastX = null;
|
||||||
|
lastY = null;
|
||||||
|
document.removeEventListener('mousemove', handleMoveMove, true);
|
||||||
|
document.removeEventListener('mouseup', handleMoveEnd, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
node.addEventListener('mousedown', handleMoveDown);
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
node.removeEventListener('mousedown', handleMoveDown);
|
||||||
|
if (lastX != null || lastY != null) {
|
||||||
|
document.removeEventListener('mousemove', handleMoveMove, true);
|
||||||
|
document.removeEventListener('mouseup', handleMoveEnd, true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -11,11 +11,16 @@ export default function moveDrag(node, dragEvents) {
|
|||||||
const handleMoveDown = e => {
|
const handleMoveDown = e => {
|
||||||
if (e.button != 0) return;
|
if (e.button != 0) return;
|
||||||
|
|
||||||
const zoomKoef = window.getComputedStyle(node)['zoom'];
|
// const zoomKoef = window.getComputedStyle(node)['zoom'];
|
||||||
|
|
||||||
const clientRect = node.getBoundingClientRect();
|
const clientRect = node.getBoundingClientRect();
|
||||||
clientX = clientRect.left * zoomKoef;
|
clientX = clientRect.left;
|
||||||
clientY = clientRect.top * zoomKoef;
|
clientY = clientRect.top;
|
||||||
|
|
||||||
|
// console.log('ZOOM', zoomKoef);
|
||||||
|
// console.log('CLIENT RECT', clientRect);
|
||||||
|
// console.log('e.clientX', e.clientX);
|
||||||
|
// console.log('e.clientY', e.clientY);
|
||||||
|
|
||||||
startX = e.clientX;
|
startX = e.clientX;
|
||||||
startY = e.clientY;
|
startY = e.clientY;
|
||||||
@@ -25,7 +30,7 @@ export default function moveDrag(node, dragEvents) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleMoveMove = e => {
|
const handleMoveMove = e => {
|
||||||
const zoomKoef = window.getComputedStyle(node)['zoom'];
|
// const zoomKoef = window.getComputedStyle(node)['zoom'];
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const diffX = e.clientX - startX;
|
const diffX = e.clientX - startX;
|
||||||
@@ -36,7 +41,7 @@ export default function moveDrag(node, dragEvents) {
|
|||||||
onMove(diffX, diffY, e.clientX - clientX, e.clientY - clientY);
|
onMove(diffX, diffY, e.clientX - clientX, e.clientY - clientY);
|
||||||
};
|
};
|
||||||
const handleMoveEnd = e => {
|
const handleMoveEnd = e => {
|
||||||
const zoomKoef = window.getComputedStyle(node)['zoom'];
|
// const zoomKoef = window.getComputedStyle(node)['zoom'];
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
startX = null;
|
startX = null;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { createGridConfig } from 'dbgate-datalib';
|
import { createGridConfig } from 'dbgate-datalib';
|
||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
|
import { getOpenedTabs, openedTabs } from '../stores';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
function doLoadGridConfigFunc(tabid) {
|
function doLoadGridConfigFunc(tabid) {
|
||||||
try {
|
try {
|
||||||
@@ -17,9 +19,35 @@ function doLoadGridConfigFunc(tabid) {
|
|||||||
return createGridConfig();
|
return createGridConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function containsNotEmptyObject(obj) {
|
||||||
|
for (const key of Object.keys(obj)) {
|
||||||
|
if (!_.isEmpty(obj[key])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
export default function useGridConfig(tabid) {
|
export default function useGridConfig(tabid) {
|
||||||
const config = writable(doLoadGridConfigFunc(tabid));
|
const config = writable(doLoadGridConfigFunc(tabid));
|
||||||
const unsubscribe = config.subscribe(value => localStorage.setItem(`tabdata_grid_${tabid}`, JSON.stringify(value)));
|
const unsubscribe = config.subscribe(value => {
|
||||||
|
localStorage.setItem(`tabdata_grid_${tabid}`, JSON.stringify(value));
|
||||||
|
|
||||||
|
if (containsNotEmptyObject(value)) {
|
||||||
|
if (getOpenedTabs().find(x => x.tabid == tabid)?.tabPreviewMode) {
|
||||||
|
openedTabs.update(tabs =>
|
||||||
|
tabs.map(x =>
|
||||||
|
x.tabid == tabid
|
||||||
|
? {
|
||||||
|
...x,
|
||||||
|
tabPreviewMode: false,
|
||||||
|
}
|
||||||
|
: x
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
onDestroy(unsubscribe);
|
onDestroy(unsubscribe);
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|||||||
80
packages/web/src/widgets/AdminPremiumPromoWidget.svelte
Normal file
80
packages/web/src/widgets/AdminPremiumPromoWidget.svelte
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<script>
|
||||||
|
import FormStyledButton from '../buttons/FormStyledButton.svelte';
|
||||||
|
import { openWebLink } from '../utility/simpleTools';
|
||||||
|
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<WidgetsInnerContainer>
|
||||||
|
<h2>Try DbGate Premium</h2>
|
||||||
|
|
||||||
|
<p>Upgrade to get exclusive features:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Query designer</li>
|
||||||
|
<li>Compare database models</li>
|
||||||
|
<li>Synchronize database structure</li>
|
||||||
|
<li>Backup & restore database</li>
|
||||||
|
<li>Advanced ER diagram settings</li>
|
||||||
|
<li>Export database model</li>
|
||||||
|
<li>AI assistant</li>
|
||||||
|
<li>libSQL, Turso, CosmosDB, Redshift support</li>
|
||||||
|
<li>Amazon and Azure identity providers</li>
|
||||||
|
<li>E-mail support</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Download DbGate Premium</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Free 30 day trial</li>
|
||||||
|
<li>DbGate Premium will reuse your connections and files from DbGate Community</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="center">
|
||||||
|
<FormStyledButton on:click={() => openWebLink('https://dbgate.io/download')} value="Download" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Purchase DbGate Premium</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Use monthly or yearly subscription</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="center">
|
||||||
|
<FormStyledButton on:click={() => openWebLink('https://dbgate.io/purchase/premium')} value="Purchase" />
|
||||||
|
</div>
|
||||||
|
</WidgetsInnerContainer>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
h2 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 10px;
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 1rem;
|
||||||
|
margin-bottom: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
li:before {
|
||||||
|
content: '\F0E1E';
|
||||||
|
font-family: 'Material Design Icons';
|
||||||
|
color: var(--theme-icon-green);
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
#starting-dbgate {
|
.starting-dbgate {
|
||||||
background-color: #111;
|
background-color: #111;
|
||||||
color: #e3e3e3;
|
color: #e3e3e3;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,12 @@
|
|||||||
visibleCommandPalette,
|
visibleCommandPalette,
|
||||||
} from '../stores';
|
} from '../stores';
|
||||||
import { getConnectionLabel } from 'dbgate-tools';
|
import { getConnectionLabel } from 'dbgate-tools';
|
||||||
import { useConnectionList, useDatabaseServerVersion, useDatabaseStatus } from '../utility/metadataLoaders';
|
import {
|
||||||
|
useConfig,
|
||||||
|
useConnectionList,
|
||||||
|
useDatabaseServerVersion,
|
||||||
|
useDatabaseStatus,
|
||||||
|
} from '../utility/metadataLoaders';
|
||||||
import { findCommand } from '../commands/runCommand';
|
import { findCommand } from '../commands/runCommand';
|
||||||
import { useConnectionColor } from '../utility/useConnectionColor';
|
import { useConnectionColor } from '../utility/useConnectionColor';
|
||||||
import { apiCall } from '../utility/api';
|
import { apiCall } from '../utility/api';
|
||||||
@@ -27,6 +32,7 @@
|
|||||||
$: dbid = connection ? { conid: connection._id, database: databaseName } : null;
|
$: dbid = connection ? { conid: connection._id, database: databaseName } : null;
|
||||||
$: status = useDatabaseStatus(dbid || {});
|
$: status = useDatabaseStatus(dbid || {});
|
||||||
$: serverVersion = useDatabaseServerVersion(dbid || {});
|
$: serverVersion = useDatabaseServerVersion(dbid || {});
|
||||||
|
$: config = useConfig();
|
||||||
|
|
||||||
$: contextItems = $statusBarTabInfo[$activeTabId] as any[];
|
$: contextItems = $statusBarTabInfo[$activeTabId] as any[];
|
||||||
$: connectionLabel = getConnectionLabel(connection, { allowExplicitDatabase: false });
|
$: connectionLabel = getConnectionLabel(connection, { allowExplicitDatabase: false });
|
||||||
@@ -171,6 +177,13 @@
|
|||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
{#if $config?.isUserLoggedIn && $config?.login}
|
||||||
|
<div class="item clickable" on:click={() => visibleCommandPalette.set(findCommand('app.loggedUserCommands'))}>
|
||||||
|
<FontIcon icon="icon users" padRight />
|
||||||
|
{$config?.login}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if $appUpdateStatus}
|
{#if $appUpdateStatus}
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<FontIcon icon={$appUpdateStatus.icon} padRight />
|
<FontIcon icon={$appUpdateStatus.icon} padRight />
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
import HistoryWidget from './HistoryWidget.svelte';
|
import HistoryWidget from './HistoryWidget.svelte';
|
||||||
import AppWidget from './AppWidget.svelte';
|
import AppWidget from './AppWidget.svelte';
|
||||||
import AdminMenuWidget from './AdminMenuWidget.svelte';
|
import AdminMenuWidget from './AdminMenuWidget.svelte';
|
||||||
|
import AdminPremiumPromoWidget from './AdminPremiumPromoWidget.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DatabaseWidget hidden={$visibleSelectedWidget != 'database'} />
|
<DatabaseWidget hidden={$visibleSelectedWidget != 'database'} />
|
||||||
@@ -33,3 +34,6 @@
|
|||||||
{#if $visibleSelectedWidget == 'admin'}
|
{#if $visibleSelectedWidget == 'admin'}
|
||||||
<AdminMenuWidget />
|
<AdminMenuWidget />
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if $visibleSelectedWidget == 'premium'}
|
||||||
|
<AdminPremiumPromoWidget />
|
||||||
|
{/if}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
} from '../stores';
|
} from '../stores';
|
||||||
import mainMenuDefinition from '../../../../app/src/mainMenuDefinition';
|
import mainMenuDefinition from '../../../../app/src/mainMenuDefinition';
|
||||||
import hasPermission from '../utility/hasPermission';
|
import hasPermission from '../utility/hasPermission';
|
||||||
|
import { isProApp } from '../utility/proTools';
|
||||||
|
|
||||||
let domSettings;
|
let domSettings;
|
||||||
let domMainMenu;
|
let domMainMenu;
|
||||||
@@ -61,6 +62,12 @@
|
|||||||
name: 'app',
|
name: 'app',
|
||||||
title: 'Application layers',
|
title: 'Application layers',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: 'icon premium',
|
||||||
|
name: 'premium',
|
||||||
|
title: 'Premium promo',
|
||||||
|
isPremiumPromo: true,
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// icon: 'icon settings',
|
// icon: 'icon settings',
|
||||||
// name: 'settings',
|
// name: 'settings',
|
||||||
@@ -104,7 +111,9 @@
|
|||||||
<FontIcon icon="icon menu" />
|
<FontIcon icon="icon menu" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#each widgets.filter(x => x && hasPermission(`widgets/${x.name}`)) as item}
|
{#each widgets
|
||||||
|
.filter(x => x && hasPermission(`widgets/${x.name}`))
|
||||||
|
.filter(x => !x.isPremiumPromo || !isProApp()) as item}
|
||||||
<div
|
<div
|
||||||
class="wrapper"
|
class="wrapper"
|
||||||
class:selected={item.name == $visibleSelectedWidget}
|
class:selected={item.name == $visibleSelectedWidget}
|
||||||
@@ -112,6 +121,9 @@
|
|||||||
on:click={() => handleChangeWidget(item.name)}
|
on:click={() => handleChangeWidget(item.name)}
|
||||||
>
|
>
|
||||||
<FontIcon icon={item.icon} title={item.title} />
|
<FontIcon icon={item.icon} title={item.title} />
|
||||||
|
{#if item.isPremiumPromo}
|
||||||
|
<div class="premium-promo">Premium</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
@@ -141,6 +153,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: var(--theme-font-inv-2);
|
color: var(--theme-font-inv-2);
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
.wrapper:hover {
|
.wrapper:hover {
|
||||||
color: var(--theme-font-inv-1);
|
color: var(--theme-font-inv-1);
|
||||||
@@ -154,4 +167,15 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.premium-promo {
|
||||||
|
position: absolute;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 6pt;
|
||||||
|
background: var(--theme-bg-inv-3);
|
||||||
|
color: var(--theme-font-inv-2);
|
||||||
|
padding: 1px 3px;
|
||||||
|
border-radius: 3px;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
export let hideContent = false;
|
export let hideContent = false;
|
||||||
export let fixedWidth = 0;
|
export let fixedWidth = 0;
|
||||||
|
export let skipDefineWidth = false;
|
||||||
|
|
||||||
export function scrollTop() {
|
export function scrollTop() {
|
||||||
domDiv.scrollTop = 0;
|
domDiv.scrollTop = 0;
|
||||||
@@ -13,7 +14,7 @@
|
|||||||
on:drop
|
on:drop
|
||||||
bind:this={domDiv}
|
bind:this={domDiv}
|
||||||
class:hideContent
|
class:hideContent
|
||||||
class:leftFixedWidth={!fixedWidth}
|
class:leftFixedWidth={!fixedWidth && !skipDefineWidth}
|
||||||
data-testid={$$props['data-testid']}
|
data-testid={$$props['data-testid']}
|
||||||
style:width={fixedWidth ? `${fixedWidth}px` : undefined}
|
style:width={fixedWidth ? `${fixedWidth}px` : undefined}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -34,7 +34,10 @@ class Analyser extends DatabaseAnalyser {
|
|||||||
this.feedback({ analysingMessage: 'Loading columns' });
|
this.feedback({ analysingMessage: 'Loading columns' });
|
||||||
const columns = await this.analyserQuery('columns', ['tables', 'views']);
|
const columns = await this.analyserQuery('columns', ['tables', 'views']);
|
||||||
this.feedback({ analysingMessage: 'Loading views' });
|
this.feedback({ analysingMessage: 'Loading views' });
|
||||||
const views = await this.analyserQuery('views', ['views']);
|
let views = await this.analyserQuery('views', ['views']);
|
||||||
|
if (views?.isError) {
|
||||||
|
views = await this.analyserQuery('viewsNoDefinition', ['views']);
|
||||||
|
}
|
||||||
|
|
||||||
const res = {
|
const res = {
|
||||||
tables: tables.rows.map((table) => ({
|
tables: tables.rows.map((table) => ({
|
||||||
@@ -64,7 +67,7 @@ class Analyser extends DatabaseAnalyser {
|
|||||||
...col,
|
...col,
|
||||||
...extractDataType(col.dataType),
|
...extractDataType(col.dataType),
|
||||||
})),
|
})),
|
||||||
createSql: `CREATE VIEW "${view.pureName}"\nAS\n${view.viewDefinition}`,
|
createSql: view.viewDefinition ? `CREATE VIEW "${view.pureName}"\nAS\n${view.viewDefinition}` : '',
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
this.feedback({ analysingMessage: null });
|
this.feedback({ analysingMessage: null });
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
const columns = require('./columns');
|
const columns = require('./columns');
|
||||||
const tables = require('./tables');
|
const tables = require('./tables');
|
||||||
const views = require('./views');
|
const views = require('./views');
|
||||||
|
const viewsNoDefinition = require('./viewsNoDefinition');
|
||||||
const tableModifications = require('./tableModifications');
|
const tableModifications = require('./tableModifications');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
columns,
|
columns,
|
||||||
tables,
|
tables,
|
||||||
views,
|
views,
|
||||||
|
viewsNoDefinition,
|
||||||
tableModifications,
|
tableModifications,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
module.exports = `
|
||||||
|
select
|
||||||
|
tables.name as "pureName",
|
||||||
|
tables.uuid as "objectId",
|
||||||
|
tables.metadata_modification_time as "contentHash"
|
||||||
|
from system.tables
|
||||||
|
where tables.database='#DATABASE#' and tables.uuid =OBJECT_ID_CONDITION and tables.engine = 'View'
|
||||||
|
`;
|
||||||
@@ -164,7 +164,7 @@ class Analyser extends DatabaseAnalyser {
|
|||||||
this.feedback({ analysingMessage: 'Loading triggers' });
|
this.feedback({ analysingMessage: 'Loading triggers' });
|
||||||
const triggers = await this.analyserQuery('triggers');
|
const triggers = await this.analyserQuery('triggers');
|
||||||
|
|
||||||
this.feedback({ analysingMessage: 'Loading scehduler events' });
|
this.feedback({ analysingMessage: 'Loading scheduler events' });
|
||||||
const schedulerEvents = await this.analyserQuery('schedulerEvents');
|
const schedulerEvents = await this.analyserQuery('schedulerEvents');
|
||||||
|
|
||||||
const uniqueNames = await this.analyserQuery('uniqueNames', ['tables']);
|
const uniqueNames = await this.analyserQuery('uniqueNames', ['tables']);
|
||||||
|
|||||||
@@ -81,12 +81,25 @@ function splitCommandLine(str) {
|
|||||||
const driver = {
|
const driver = {
|
||||||
...driverBase,
|
...driverBase,
|
||||||
analyserClass: Analyser,
|
analyserClass: Analyser,
|
||||||
async connect({ server, port, user, password, database, useDatabaseUrl, databaseUrl, treeKeySeparator, ssl }) {
|
async connect({
|
||||||
|
server,
|
||||||
|
port,
|
||||||
|
user,
|
||||||
|
password,
|
||||||
|
database,
|
||||||
|
useDatabaseUrl,
|
||||||
|
databaseUrl,
|
||||||
|
treeKeySeparator,
|
||||||
|
ssl,
|
||||||
|
skipSetName,
|
||||||
|
}) {
|
||||||
let db = 0;
|
let db = 0;
|
||||||
let client;
|
let client;
|
||||||
if (useDatabaseUrl) {
|
if (useDatabaseUrl) {
|
||||||
client = new Redis(databaseUrl);
|
client = new Redis(databaseUrl);
|
||||||
await client.client('SETNAME', 'dbgate');
|
if (!skipSetName) {
|
||||||
|
await client.client('SETNAME', 'dbgate');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (_.isString(database) && database.startsWith('db')) db = parseInt(database.substring(2));
|
if (_.isString(database) && database.startsWith('db')) db = parseInt(database.substring(2));
|
||||||
if (_.isNumber(database)) db = database;
|
if (_.isNumber(database)) db = database;
|
||||||
@@ -96,15 +109,18 @@ const driver = {
|
|||||||
passphrase: ssl.password,
|
passphrase: ssl.password,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
client = new Redis({
|
const connectionOptions = {
|
||||||
host: server,
|
host: server,
|
||||||
port,
|
port,
|
||||||
username: user,
|
username: user,
|
||||||
password,
|
password,
|
||||||
db,
|
db,
|
||||||
connectionName: 'dbgate',
|
|
||||||
tls: ssl,
|
tls: ssl,
|
||||||
});
|
};
|
||||||
|
if (!skipSetName) {
|
||||||
|
connectionOptions.connectionName = 'dbgate';
|
||||||
|
}
|
||||||
|
client = new Redis(connectionOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -82,6 +82,16 @@ const driver = {
|
|||||||
}
|
}
|
||||||
return ['server', 'port', 'user', 'password', 'isReadOnly', 'treeKeySeparator'].includes(field);
|
return ['server', 'port', 'user', 'password', 'isReadOnly', 'treeKeySeparator'].includes(field);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getAdvancedConnectionFields() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: 'checkbox',
|
||||||
|
name: 'skipSetName',
|
||||||
|
label: 'Skip SETNAME instruction',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = driver;
|
module.exports = driver;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ checkout-and-merge-pro:
|
|||||||
repository: dbgate/dbgate-pro
|
repository: dbgate/dbgate-pro
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
path: dbgate-pro
|
path: dbgate-pro
|
||||||
ref: 54f1f9a82fbcca0307aa5c83f765b33af3325466
|
ref: 00da2abe10e1ec8a3887b49dfabd42ccda365514
|
||||||
- name: Merge dbgate/dbgate-pro
|
- name: Merge dbgate/dbgate-pro
|
||||||
run: |
|
run: |
|
||||||
mkdir ../dbgate-pro
|
mkdir ../dbgate-pro
|
||||||
|
|||||||
@@ -43,6 +43,12 @@ jobs:
|
|||||||
cd packages/datalib
|
cd packages/datalib
|
||||||
yarn test:ci
|
yarn test:ci
|
||||||
|
|
||||||
|
- name: Tools tests
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
cd packages/tools
|
||||||
|
yarn test:ci
|
||||||
|
|
||||||
- uses: tanmen/jest-reporter@v1
|
- uses: tanmen/jest-reporter@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
|
|||||||
Reference in New Issue
Block a user