diff --git a/.github/workflows/build-app-pro-beta.yaml b/.github/workflows/build-app-pro-beta.yaml index d3d64bf70..93e99a798 100644 --- a/.github/workflows/build-app-pro-beta.yaml +++ b/.github/workflows/build-app-pro-beta.yaml @@ -39,7 +39,7 @@ jobs: repository: dbgate/dbgate-pro token: ${{ secrets.GH_TOKEN }} path: dbgate-pro - ref: e4e157db83a2785057ef4778c3297abe758a7505 + ref: 32bb976b7a07547641d1e8517c86e7c4d70bd088 - name: Merge dbgate/dbgate-pro run: | mkdir ../dbgate-pro diff --git a/.github/workflows/build-app-pro.yaml b/.github/workflows/build-app-pro.yaml index 603b5e5d5..9b7deed62 100644 --- a/.github/workflows/build-app-pro.yaml +++ b/.github/workflows/build-app-pro.yaml @@ -39,7 +39,7 @@ jobs: repository: dbgate/dbgate-pro token: ${{ secrets.GH_TOKEN }} path: dbgate-pro - ref: e4e157db83a2785057ef4778c3297abe758a7505 + ref: 32bb976b7a07547641d1e8517c86e7c4d70bd088 - name: Merge dbgate/dbgate-pro run: | mkdir ../dbgate-pro diff --git a/.github/workflows/build-cloud-pro.yaml b/.github/workflows/build-cloud-pro.yaml index 372d2512e..1f0be4388 100644 --- a/.github/workflows/build-cloud-pro.yaml +++ b/.github/workflows/build-cloud-pro.yaml @@ -39,7 +39,7 @@ jobs: repository: dbgate/dbgate-pro token: ${{ secrets.GH_TOKEN }} path: dbgate-pro - ref: e4e157db83a2785057ef4778c3297abe758a7505 + ref: 32bb976b7a07547641d1e8517c86e7c4d70bd088 - name: Merge dbgate/dbgate-pro run: | mkdir ../dbgate-pro diff --git a/.github/workflows/build-docker-pro.yaml b/.github/workflows/build-docker-pro.yaml index c87ee971a..41a8d6f97 100644 --- a/.github/workflows/build-docker-pro.yaml +++ b/.github/workflows/build-docker-pro.yaml @@ -44,7 +44,7 @@ jobs: repository: dbgate/dbgate-pro token: ${{ secrets.GH_TOKEN }} path: dbgate-pro - ref: e4e157db83a2785057ef4778c3297abe758a7505 + ref: 32bb976b7a07547641d1e8517c86e7c4d70bd088 - name: Merge dbgate/dbgate-pro run: | mkdir ../dbgate-pro diff --git a/.github/workflows/build-npm-pro.yaml b/.github/workflows/build-npm-pro.yaml index cd1b99166..141f429bb 100644 --- a/.github/workflows/build-npm-pro.yaml +++ b/.github/workflows/build-npm-pro.yaml @@ -32,7 +32,7 @@ jobs: repository: dbgate/dbgate-pro token: ${{ secrets.GH_TOKEN }} path: dbgate-pro - ref: e4e157db83a2785057ef4778c3297abe758a7505 + ref: 32bb976b7a07547641d1e8517c86e7c4d70bd088 - name: Merge dbgate/dbgate-pro run: | mkdir ../dbgate-pro diff --git a/.github/workflows/e2e-pro.yaml b/.github/workflows/e2e-pro.yaml index 8edcbb014..d21285668 100644 --- a/.github/workflows/e2e-pro.yaml +++ b/.github/workflows/e2e-pro.yaml @@ -26,7 +26,7 @@ jobs: repository: dbgate/dbgate-pro token: ${{ secrets.GH_TOKEN }} path: dbgate-pro - ref: e4e157db83a2785057ef4778c3297abe758a7505 + ref: 32bb976b7a07547641d1e8517c86e7c4d70bd088 - name: Merge dbgate/dbgate-pro run: | mkdir ../dbgate-pro diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fe547910..f20a1ad35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,17 @@ Builds: - linux - application for linux - win - application for Windows +### 6.3.0 +- ADDED: Support for libSQL and Turso (Premium) +- ADDED: Native backup and restore database for MySQL and PostgreSQL (Premium) +- REMOVED: DbGate internal dump export for MySQL (replaced with call of mysqldump) +- REMOVED: Import SQL dump with internal DbGate capabilities (replaced by calling of mysql and psql utilities) +- FIXED: Many fixes in stream processing (imoprt/export), especialy for MongoDB +- ADDED: Indicating progress of import/export tasks, better error reporting +- CHANGED: #1060 - Changed shortcut for AI assistant +- ADDED: /health endpoint with diagnostic info +- FIXED: Linux Appimage crash => A JavaScript error occurred in the main process #1065 , #1067 + ### 6.2.1 - ADDED: Commit/rollback and autocommit in scripts #1039 - FIXED: Doesn't import all the records from MongoDB #1044 diff --git a/e2e-tests/cypress/e2e/browse-data.cy.js b/e2e-tests/cypress/e2e/browse-data.cy.js index 107888cb7..00713657f 100644 --- a/e2e-tests/cypress/e2e/browse-data.cy.js +++ b/e2e-tests/cypress/e2e/browse-data.cy.js @@ -15,11 +15,16 @@ beforeEach(() => { describe('Data browser data', () => { it('Export window', () => { cy.contains('MySql-connection').click(); - cy.contains('MyChinook').rightclick(); + cy.contains('MyChinook').click(); + cy.contains('Album').rightclick(); cy.contains('Export').click(); + cy.contains('Export advanced').click(); cy.wait(1000); // cy.testid('SourceTargetConfig_buttonCurrentArchive_target').click(); - cy.testid('FormTablesSelect_buttonAll_tables').click(); + // cy.testid('FormTablesSelect_buttonAll_tables').click(); + // cy.testid('SourceTargetConfig_tablesSelect_source').click(); + // cy.find('.listContainer').contains('Album').click(); + // cy.find('.listContainer').contains('Track').click(); // cy.wait(4000); // cy.contains('All tables').click(); cy.contains('Run').click(); @@ -76,6 +81,20 @@ describe('Data browser data', () => { cy.contains('Aerosmith').should('not.exist'); }); + it('Data filter', () => { + cy.contains('MySql-connection').click(); + cy.contains('MyChinook').click(); + cy.contains('Album').click(); + cy.testid('DataFilterControl_input_Title').type('Rock{enter}'); + cy.contains('Rows: 7'); + cy.testid('DataFilterControl_input_AlbumId').type('>10{enter}'); + cy.contains('Rows: 5'); + cy.testid('DataFilterControl_filtermenu_Title').click(); + cy.themeshot('filter'); + cy.testid('DataGridCore_button_clearFilters').click(); + cy.contains('Rows: 347'); + }); + it('Data grid screenshots', () => { cy.contains('MySql-connection').click(); cy.window().then(win => { @@ -350,7 +369,7 @@ describe('Data browser data', () => { cy.themeshot('comparesettings'); }); - it.skip('Query editor - AI assistant', () => { + it('Query editor - AI assistant', () => { cy.contains('MySql-connection').click(); cy.contains('MyChinook').click(); cy.testid('TabsPanel_buttonNewQuery').click(); diff --git a/package.json b/package.json index 5b353f268..2797b6839 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "6.2.2-beta.10", + "version": "6.3.1", "name": "dbgate-all", "workspaces": [ "packages/*", diff --git a/packages/api/src/controllers/auth.js b/packages/api/src/controllers/auth.js index 509d4c9c1..0d474b8ef 100644 --- a/packages/api/src/controllers/auth.js +++ b/packages/api/src/controllers/auth.js @@ -43,6 +43,7 @@ function authMiddleware(req, res, next) { '/connections/dblogin-app', '/connections/dblogin-auth', '/connections/dblogin-auth-token', + '/health', ]; // console.log('********************* getAuthProvider()', getAuthProvider()); diff --git a/packages/api/src/controllers/runners.js b/packages/api/src/controllers/runners.js index eabb2f1c2..39e205e82 100644 --- a/packages/api/src/controllers/runners.js +++ b/packages/api/src/controllers/runners.js @@ -171,6 +171,7 @@ module.exports = { this.rejectRequest(runid, { message: 'No data returned, maybe input data source is too big' }); logger.info({ code, pid: subprocess.pid }, 'Exited process'); socket.emit(`runner-done-${runid}`, code); + this.opened = this.opened.filter(x => x.runid != runid); }); subprocess.on('error', error => { // console.log('... ERROR subprocess', error); @@ -180,6 +181,7 @@ module.exports = { severity: 'error', message: error.toString(), }); + this.opened = this.opened.filter(x => x.runid != runid); }); const newOpened = { runid, @@ -224,6 +226,7 @@ module.exports = { if (onFinished) { onFinished(); } + this.opened = this.opened.filter(x => x.runid != runid); }); subprocess.on('spawn', () => { this.dispatchMessage(runid, `Started external process ${command}`); @@ -241,6 +244,7 @@ module.exports = { }); } socket.emit(`runner-done-${runid}`); + this.opened = this.opened.filter(x => x.runid != runid); }); if (stdinFilePath) { diff --git a/packages/api/src/controllers/sessions.js b/packages/api/src/controllers/sessions.js index 5091fa6a8..08e3e11e2 100644 --- a/packages/api/src/controllers/sessions.js +++ b/packages/api/src/controllers/sessions.js @@ -127,6 +127,9 @@ module.exports = { this.dispatchMessage(sesid, 'Query session closed'); socket.emit(`session-closed-${sesid}`); }); + subprocess.on('error', () => { + this.opened = this.opened.filter(x => x.sesid != sesid); + }); subprocess.send({ msgtype: 'connect', diff --git a/packages/api/src/main.js b/packages/api/src/main.js index b3b51671c..22001ad26 100644 --- a/packages/api/src/main.js +++ b/packages/api/src/main.js @@ -38,6 +38,7 @@ const { getLogger } = require('dbgate-tools'); const { getDefaultAuthProvider } = require('./auth/authProvider'); const startCloudUpgradeTimer = require('./utility/cloudUpgrade'); const { isProApp } = require('./utility/checkLicense'); +const getHealthStatus = require('./utility/healthStatus'); const logger = getLogger('main'); @@ -117,6 +118,12 @@ function start() { }); }); + app.get(getExpressPath('/health'), async function (req, res) { + res.setHeader('Content-Type', 'application/json'); + const health = await getHealthStatus(); + res.end(JSON.stringify(health, null, 2)); + }); + app.use(bodyParser.json({ limit: '50mb' })); app.use( diff --git a/packages/api/src/utility/healthStatus.js b/packages/api/src/utility/healthStatus.js new file mode 100644 index 000000000..3a458e0d8 --- /dev/null +++ b/packages/api/src/utility/healthStatus.js @@ -0,0 +1,27 @@ +const os = require('os'); + +const databaseConnections = require('../controllers/databaseConnections'); +const serverConnections = require('../controllers/serverConnections'); +const sessions = require('../controllers/sessions'); +const runners = require('../controllers/runners'); + +async function getHealthStatus() { + const memory = process.memoryUsage(); + const cpuUsage = process.cpuUsage(); + + return { + status: 'ok', + databaseConnectionCount: databaseConnections.opened.length, + serverConnectionCount: serverConnections.opened.length, + sessionCount: sessions.opened.length, + runProcessCount: runners.opened.length, + memory, + cpuUsage, + systemMemory: { + total: os.totalmem(), + free: os.freemem(), + }, + }; +} + +module.exports = getHealthStatus; diff --git a/packages/web/src/datagrid/DataFilterControl.svelte b/packages/web/src/datagrid/DataFilterControl.svelte index 7337f2729..594cd81e4 100644 --- a/packages/web/src/datagrid/DataFilterControl.svelte +++ b/packages/web/src/datagrid/DataFilterControl.svelte @@ -296,11 +296,21 @@ {/if} {#if conid && database && driver} {#if driver?.databaseEngineTypes?.includes('sql') && foreignKey} - + {:else if (pureName && columnName) || (pureName && uniqueName && driver?.databaseEngineTypes?.includes('document'))} - + {/if} @@ -309,7 +319,12 @@ {/if} - + {#if showResizeSplitter}
{/if} diff --git a/packages/web/src/datagrid/DataGridCore.svelte b/packages/web/src/datagrid/DataGridCore.svelte index 80ed66b7e..d1d619723 100644 --- a/packages/web/src/datagrid/DataGridCore.svelte +++ b/packages/web/src/datagrid/DataGridCore.svelte @@ -1946,7 +1946,11 @@ style={`width:${headerColWidth}px; min-width:${headerColWidth}px; max-width:${headerColWidth}px`} > {#if display.filterCount > 0} - display.clearFilters()} square> + display.clearFilters()} + square + data-testid="DataGridCore_button_clearFilters" + > {/if} diff --git a/packages/web/src/forms/SelectField.svelte b/packages/web/src/forms/SelectField.svelte index a3826861b..aed798cd8 100644 --- a/packages/web/src/forms/SelectField.svelte +++ b/packages/web/src/forms/SelectField.svelte @@ -64,6 +64,7 @@ {isMulti} bind:listOpen bind:isFocused + class={$$props['data-testid'] ? 'select-testid-' + $$props['data-testid'] : undefined} />
{/if} diff --git a/packages/web/src/impexp/SourceTargetConfig.svelte b/packages/web/src/impexp/SourceTargetConfig.svelte index 2fa607d7f..32a0dc896 100644 --- a/packages/web/src/impexp/SourceTargetConfig.svelte +++ b/packages/web/src/impexp/SourceTargetConfig.svelte @@ -141,6 +141,9 @@ schemaName={schemaNameField} databaseName={databaseNameField} name={tablesField} + data-testid={direction == 'source' + ? 'SourceTargetConfig_tablesSelect_source' + : 'SourceTargetConfig_tablesSelect_target'} label={_t('importExport.tablesViewsCollections', { defaultMessage: 'Tables / views / collections' })} /> {/if} diff --git a/packages/web/src/stores.ts b/packages/web/src/stores.ts index e49b4d394..fbcb73cb2 100644 --- a/packages/web/src/stores.ts +++ b/packages/web/src/stores.ts @@ -26,6 +26,10 @@ export interface TabDefinition { focused?: boolean; } +function getSystemTheme() { + return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'theme-dark' : 'theme-light'; +} + export function writableWithStorage(defaultValue: T, storageName) { const init = localStorage.getItem(storageName); const res = writable(init ? safeJsonParse(init, defaultValue, true) : defaultValue); @@ -100,8 +104,8 @@ export const extensions = writable(null); export const visibleCommandPalette = writable(null); export const commands = writable({}); export const currentTheme = getElectron() - ? writableSettingsValue('theme-light', 'currentTheme') - : writableWithStorage('theme-light', 'currentTheme'); + ? writableSettingsValue(getSystemTheme(), 'currentTheme') + : writableWithStorage(getSystemTheme(), 'currentTheme'); export const currentEditorTheme = getElectron() ? writableSettingsValue(null, 'currentEditorTheme') : writableWithStorage(null, 'currentEditorTheme'); @@ -194,8 +198,13 @@ export const connectionAppObjectSearchSettings = writableWithStorage( ); export const currentThemeDefinition = derived([currentTheme, extensions], ([$currentTheme, $extensions]) => - $extensions.themes.find(x => x.themeClassName == $currentTheme) + $extensions?.themes?.find(x => x.themeClassName == $currentTheme) ); +currentThemeDefinition.subscribe(value => { + if (value?.themeType) { + localStorage.setItem('currentThemeType', value?.themeType); + } +}); export const openedConnectionsWithTemporary = derived( [openedConnections, temporaryOpenedConnections, openedSingleDatabaseConnections], ([$openedConnections, $temporaryOpenedConnections, $openedSingleDatabaseConnections]) => diff --git a/packages/web/src/widgets/AppStartInfo.svelte b/packages/web/src/widgets/AppStartInfo.svelte index 7f46b0542..0597874da 100644 --- a/packages/web/src/widgets/AppStartInfo.svelte +++ b/packages/web/src/widgets/AppStartInfo.svelte @@ -1,8 +1,14 @@ -
+
@@ -15,21 +21,34 @@