Compare commits

..

92 Commits

Author SHA1 Message Date
Stela Augustinova 0443a21e05 Add support for uppercase boolean format in CSV export 2026-02-02 10:41:36 +01:00
Stela Augustinova 7bc31dde70 Add boolean format option to CSV export configuration 2026-01-30 17:42:07 +01:00
Stela Augustinova 65f2f1d08f Add support for bigint and binary values in CSV export 2026-01-30 15:39:57 +01:00
Stela Augustinova 684027eaab Handle decimal and boolean values in CSV export 2026-01-30 14:59:08 +01:00
Jan Prochazka 1c3ec9c3bb SYNC: Merge pull request #45 from dbgate/feature/statusbar-colors 2026-01-30 06:33:36 +00:00
SPRINX0\prochazka 3a8ff2c05d SYNC: FIXED: Search for database in cloud connection #1329 2026-01-29 14:25:33 +00:00
SPRINX0\prochazka d5bd179c68 SYNC: configurable toolbar position #1326 2026-01-29 09:54:01 +00:00
SPRINX0\prochazka 8b938a39cf SYNC: changed: prioritize map selection in CellDataWidget autodetection #1330 2026-01-29 08:09:41 +00:00
Jan Prochazka c9610cbc39 SYNC: Merge pull request #44 from dbgate/feature/test-connection-msentra 2026-01-28 13:52:06 +00:00
SPRINX0\prochazka 931733d605 SYNC: Optimalized loading MySQL primary keys #1261 2026-01-28 07:29:54 +00:00
SPRINX0\prochazka 44e5d0e195 v7.0.1 2026-01-28 08:09:58 +01:00
SPRINX0\prochazka 08b83dc3fd changelog 2026-01-28 07:43:44 +01:00
SPRINX0\prochazka b7f261a836 v7.0.1-premium-beta.5 2026-01-28 07:39:56 +01:00
SPRINX0\prochazka d0b4ca33c2 changelog 2026-01-28 07:39:45 +01:00
Jan Prochazka 160391f5a9 SYNC: Merge pull request #43 from dbgate/feature/editor-theme 2026-01-27 15:10:02 +00:00
SPRINX0\prochazka dfe4a96b02 SYNC: added missing test run 2026-01-27 14:11:15 +00:00
SPRINX0\prochazka a3f67eb519 SYNC: fixed test 2026-01-27 13:36:07 +00:00
Jan Prochazka 0f9d52552b SYNC: Merge pull request #42 from dbgate/feature/redis-test 2026-01-27 11:59:53 +00:00
SPRINX0\prochazka a217de4c39 english in templates 2026-01-27 10:47:19 +01:00
SPRINX0\prochazka d2d85e63f6 english in issue templates 2026-01-27 10:45:51 +01:00
SPRINX0\prochazka 7a6077b5ff SYNC: Ability to skip computed columns is SQL generator SQL Generator #1319 2026-01-26 15:06:56 +00:00
SPRINX0\prochazka d48c4d9729 SYNC: fixed: The JsonB field in the cell data view always displays as null. #1320 2026-01-26 14:17:27 +00:00
SPRINX0\prochazka 6d677401bf SYNC: FIXED: Foreign key actions not detected on PostgreSQL #1323 2026-01-26 13:56:12 +00:00
SPRINX0\prochazka a3d4fa2f86 SYNC: fix 2026-01-26 13:04:56 +00:00
SPRINX0\prochazka 59e19b6a22 SYNC: translations 2026-01-26 12:58:25 +00:00
SPRINX0\prochazka 1a76da40d1 SYNC: datagrid translations 2026-01-26 12:58:24 +00:00
SPRINX0\prochazka cb15ba01f0 SYNC: solarized theme screenshot 2026-01-26 12:20:15 +00:00
SPRINX0\prochazka 78af7f136e SYNC: korean localization 2026-01-26 11:24:02 +00:00
SPRINX0\prochazka cc6a95b579 SYNC: korean translation 2026-01-26 11:24:00 +00:00
SPRINX0\prochazka 4b3f723bdc SYNC: missing translations 2026-01-26 11:23:58 +00:00
SPRINX0\prochazka d372e2ff76 SYNC: translations 2026-01-26 11:23:56 +00:00
SPRINX0\prochazka 4201d1cb1e SYNC: translation 2026-01-26 11:09:08 +00:00
SPRINX0\prochazka afed70ba63 v7.0.1-premium-beta.4 2026-01-26 11:09:06 +01:00
SPRINX0\prochazka be488346c5 v7.0.1-premium-beta.3 2026-01-26 11:08:04 +01:00
CI workflows eeeb688439 chore: auto-update github workflows 2026-01-26 10:08:00 +00:00
CI workflows b84ce77326 Update pro ref 2026-01-26 10:07:42 +00:00
CI workflows 30fca423dc chore: auto-update github workflows 2026-01-26 10:01:44 +00:00
CI workflows fabbb31572 Update pro ref 2026-01-26 10:01:27 +00:00
SPRINX0\prochazka ac76ac004e SYNC: admin file - change content 2026-01-26 10:01:17 +00:00
SPRINX0\prochazka 9d2051183a v7.0.1-premium-beta.2 2026-01-26 10:24:19 +01:00
SPRINX0\prochazka 942fdb51d5 v7.0.1-premium.beta.2 2026-01-26 10:20:44 +01:00
CI workflows d2600a3168 chore: auto-update github workflows 2026-01-26 09:19:22 +00:00
CI workflows c4248cce22 Update pro ref 2026-01-26 09:19:07 +00:00
SPRINX0\prochazka 16f16f9fed theme docs 2026-01-23 14:51:55 +01:00
SPRINX0\prochazka d49cb976bc v7.0.1-premium-beta.1 2026-01-23 14:47:45 +01:00
SPRINX0\prochazka 6fae6a9865 changelog 2026-01-23 14:47:23 +01:00
SPRINX0\prochazka 06f3730756 SYNC: upgraded cross-spawn #1322 2026-01-23 13:25:50 +00:00
SPRINX0\prochazka 30e1333f75 SYNC: axios upgrade #1322 2026-01-23 13:18:56 +00:00
Jan Prochazka 0d6fa98767 SYNC: redis test changed 2026-01-22 14:17:52 +00:00
SPRINX0\prochazka ae6c9edd0d SYNC: green theme screenshot 2026-01-22 12:07:55 +00:00
SPRINX0\prochazka 35de1f1c4e v7.0.0 2026-01-22 10:07:46 +01:00
SPRINX0\prochazka 57142f4afb v7.0.0-alpha.12 2026-01-22 09:47:33 +01:00
CI workflows cd72d65b89 chore: auto-update github workflows 2026-01-22 08:46:26 +00:00
CI workflows 2199ab0513 Update pro ref 2026-01-22 08:46:10 +00:00
SPRINX0\prochazka e93f058109 Revert "downgraded NPM refs"
This reverts commit 3f05934b6b.
2026-01-22 09:43:27 +01:00
SPRINX0\prochazka b68de49cbd v7.0.0-alpha.11 2026-01-22 09:41:58 +01:00
SPRINX0\prochazka 3f05934b6b downgraded NPM refs 2026-01-22 09:41:45 +01:00
SPRINX0\prochazka 3a5713dbb7 v7.0.0-alpha.10 2026-01-22 09:37:36 +01:00
SPRINX0\prochazka 4c43158285 v7.0.0-beta.9 2026-01-22 09:37:24 +01:00
SPRINX0\prochazka daa743b3b3 upgraded NPM version 2026-01-22 09:37:13 +01:00
SPRINX0\prochazka 41f0ae18c4 changelog - 7.0.0 2026-01-22 09:37:13 +01:00
CI workflows e6b8aefe5b chore: auto-update github workflows 2026-01-22 07:09:52 +00:00
CI workflows 8b2437cb16 Update pro ref 2026-01-22 07:09:37 +00:00
SPRINX0\prochazka 292495ab0d SYNC: redis CSS variables renamed 2026-01-22 07:09:25 +00:00
CI workflows 017b137d7f chore: auto-update github workflows 2026-01-22 07:02:40 +00:00
CI workflows 7969030313 Update pro ref 2026-01-22 07:02:25 +00:00
SPRINX0\prochazka c8efad4c3f SYNC: fix 2026-01-22 07:02:13 +00:00
SPRINX0\prochazka 7bf9d8f675 SYNC: redis screenshot 2026-01-22 06:47:32 +00:00
SPRINX0\prochazka e275f15f00 SYNC: redis hash screenshot 2026-01-21 18:10:20 +00:00
SPRINX0\prochazka 30017a5217 v7.0.0-premium-beta.8 2026-01-21 18:56:13 +01:00
Jan Prochazka 64c5cbe8c3 SYNC: Merge pull request #41 from dbgate/feature/widget-panel 2026-01-21 17:53:24 +00:00
Jan Prochazka b2b226573c SYNC: Merge pull request #40 from dbgate/feature/redis-refactor-2 2026-01-21 17:44:43 +00:00
CI workflows 69f796998f chore: auto-update github workflows 2026-01-21 10:59:33 +00:00
CI workflows 4d64be3ac7 Update pro ref 2026-01-21 10:59:16 +00:00
Stela Augustinova 4408b794d6 v7.0.0-premium-beta.7 2026-01-21 11:27:06 +01:00
SPRINX0\prochazka 666da8a879 v7.0.0-premium-beta.5 2026-01-21 10:47:10 +01:00
SPRINX0\prochazka b166342579 v7.0.0-premium-beta.4 2026-01-21 10:47:10 +01:00
SPRINX0\prochazka 433f5bf7d2 compiled workflows 2026-01-21 10:47:10 +01:00
SPRINX0\prochazka b8ae153ef5 set python version 2026-01-21 10:47:10 +01:00
Jan Prochazka bb59c2bab7 SYNC: Merge pull request #38 from dbgate/feature/db-icons-light-dark 2026-01-21 08:36:42 +00:00
CI workflows ab7c6c5118 chore: auto-update github workflows 2026-01-20 09:16:40 +00:00
CI workflows 85c1ea449e Update pro ref 2026-01-20 09:16:26 +00:00
Jan Prochazka b51d679b78 SYNC: Merge pull request #37 from dbgate/feature/theme-refactor 2026-01-20 09:16:15 +00:00
Jan Prochazka 2a2bc9e625 SYNC: Merge pull request #36 from dbgate/feature/redis-refactor 2026-01-19 15:44:14 +00:00
SPRINX0\prochazka d00f059567 SYNC: redis styling 2026-01-19 14:27:00 +00:00
SPRINX0\prochazka 81a840347c SYNC: fixed for system dark mode 2026-01-19 14:11:39 +00:00
SPRINX0\prochazka e691675bf9 SYNC: better mysql icon 2026-01-19 13:43:37 +00:00
SPRINX0\prochazka 9cd57c3ae1 SYNC: removed console.log 2026-01-19 13:31:26 +00:00
CI workflows 0e3310a39b chore: auto-update github workflows 2026-01-19 13:20:59 +00:00
CI workflows 447818ac2a Update pro ref 2026-01-19 13:20:44 +00:00
Jan Prochazka dd0eb846b0 SYNC: Merge pull request #35 from dbgate/feature/theme-refactor 2026-01-19 13:20:34 +00:00
Jan Prochazka 1b62ca4b21 SYNC: Merge pull request #34 from dbgate/feature/refresh-keys 2026-01-19 13:20:33 +00:00
171 changed files with 5677 additions and 3350 deletions
+3 -1
View File
@@ -1,12 +1,14 @@
---
name: Bug report
about: Create a report to help us improve DbGate
about: Create a report to help us improve DbGate (in ENGLISH)
title: 'BUG: Say something here'
labels: ''
assignees: ''
---
Please keep communication in ENGLISH to reach more contributors.
**Describe the bug**
A clear and concise description of what the bug is.
+3 -1
View File
@@ -1,12 +1,14 @@
---
name: Feature request
about: Suggest an idea for DbGate
about: Suggest an idea for DbGate (in ENGLISH)
title: 'FEAT: '
labels: ''
assignees: ''
---
Please keep communication in ENGLISH to reach more contributors.
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+3 -1
View File
@@ -1,12 +1,14 @@
---
name: Question
about: Ask a question about how to do something
about: Ask a question about how to do something (in ENGLISH)
title: 'QUESTION: Summary of your question'
labels: ''
assignees: ''
---
Please keep communication in ENGLISH to reach more contributors.
**Details:**
Details about your question
+1 -1
View File
@@ -47,7 +47,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: a5f52768cea7e98cae5e5b1f5fef3c47a475b8a6
ref: ee7f0398bbcaf57db379734136458525b11ca974
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+1 -1
View File
@@ -47,7 +47,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: a5f52768cea7e98cae5e5b1f5fef3c47a475b8a6
ref: ee7f0398bbcaf57db379734136458525b11ca974
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+1 -1
View File
@@ -39,7 +39,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: a5f52768cea7e98cae5e5b1f5fef3c47a475b8a6
ref: ee7f0398bbcaf57db379734136458525b11ca974
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+1 -1
View File
@@ -44,7 +44,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: a5f52768cea7e98cae5e5b1f5fef3c47a475b8a6
ref: ee7f0398bbcaf57db379734136458525b11ca974
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: a5f52768cea7e98cae5e5b1f5fef3c47a475b8a6
ref: ee7f0398bbcaf57db379734136458525b11ca974
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: a5f52768cea7e98cae5e5b1f5fef3c47a475b8a6
ref: ee7f0398bbcaf57db379734136458525b11ca974
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+25
View File
@@ -8,6 +8,31 @@ Builds:
- linux - application for linux
- win - application for Windows
## 7.0.1
- FIXED: Foreign key actions not detected on PostgreSQL #1323
- FIXED: Vulnerabilities in bundled dependencies: axios, cross-spawn, glob #1322
- FIXED: The JsonB field in the cell data view always displays as null. #1320
- ADDED: Possibility to skip computed coumn in SQL generator
- ADDED: Improved team file editing, move between team folders
- ADDED: Korean localization
- FIXED: Added missing localization strings
- ADDED: Default editor theme is part of application theme now
## 7.0.0
- CHANGED: New design of application, new theme system
- ADDED: Theme AI assistant - create custom themes using AI (Premium)
- CHANGED: Themes are now defined in JSON files, custom themes could be shared via DbGate Cloud
- REMOVED: Custom themes are no longer part of plugins
- CHANGED: Huge improvements of Redis support
- ADDED: Support for Redis JSON and Stream types
- ADDED: Editing Redis values (Strings, Hashes, Lists, Sets, Sorted Sets, JSON, Streams)
- ADDED: Support for Team Folders (Team Premium)
- CHANGED: Upgraded Svelte to version 4
- ADDED: Differentiate pinned database with same name #1306
- ADDED: Database icons/logos for faster visual recognition #1222
- CHANGED: Reorganized left sidebar widgets
- ADDED: Widget for currently opened tabs
## 6.8.2
- FIXED: Initialize storage database from envoronment variables failed with PostgreSQL
+3 -2
View File
@@ -61,7 +61,7 @@ DbGate is licensed under GPL-3.0 license and is free to use for any purpose.
* Edit table schema, indexes, primary and foreign keys
* Compare and synchronize database structure
* ER diagram
* Light and dark theme, next themes available as plugins from github community
* Light and dark theme, next themes available from DbGate Cloud
* Huge support for work with related data - master/detail views, foreign key lookups, expanding columns from related tables in flat data view
* Query designer - visual SQL query builder without writing SQL code. Complex conditions like WHERE NOT EXISTS.
* Query perspectives innovative nested table view over complex relational data, something like query designer on MongoDB databases
@@ -94,7 +94,8 @@ Any contributions are welcome. If you want to contribute without coding, conside
* Create some tutorial video on [youtube](https://www.youtube.com/playlist?list=PLCo7KjCVXhr0RfUSjM9wJMsp_ShL1q61A)
* Become a backer on [GitHub sponsors](https://github.com/sponsors/dbgate) or [Open collective](https://opencollective.com/dbgate)
* Add a SQL script to [Public Knowledge Base](https://github.com/dbgate/dbgate-knowledge-base)
* Where a small coding is acceptable for you, you could [create plugin](https://docs.dbgate.io/plugin-development). Plugins for new themes can be created actually without JS coding
* Where a small coding is acceptable for you, you could [create plugin](https://docs.dbgate.io/plugin-development)
* Create a new custom theme and share it on [DbGate Cloud](https://github.com/dbgate/dbgate-knowledge-base/tree/master/folder-Themes)
Thank you!
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "dbgate",
"version": "6.0.0-alpha.1",
"version": "7.0.0-alpha.1",
"private": true,
"author": "Jan Prochazka <jenasoft.database@gmail.com>",
"description": "Opensource database administration tool",
+1
View File
@@ -15,6 +15,7 @@ const languageNames = {
'fr.json': 'French',
'it.json': 'Italian',
'ja.json': 'Japanese',
'ko.json': 'Korean',
'pt.json': 'Portuguese',
'sk.json': 'Slovak',
'zh.json': 'Chinese'
+3
View File
@@ -49,6 +49,9 @@ module.exports = defineConfig({
case 'charts':
serverProcess = exec('yarn start:charts');
break;
case 'redis':
serverProcess = exec('yarn start:redis');
break;
}
await waitOn({ resources: ['http://localhost:3000'] });
+18 -2
View File
@@ -225,7 +225,6 @@ describe('Charts', () => {
cy.contains('Default Actions').click();
cy.get('[data-testid=DefaultActionsSettings_useLastUsedAction]').uncheck();
// Themes
cy.contains('Themes').click();
cy.themeshot('app-settings-themes');
@@ -256,7 +255,6 @@ describe('Charts', () => {
cy.contains('OK').click();
cy.contains('Ctrl+G');
cy.contains('AI').click();
cy.themeshot('app-settings-ai');
cy.get('[data-testid=AISettings_addProviderButton]').click();
@@ -266,4 +264,22 @@ describe('Charts', () => {
cy.contains('OK').click();
cy.contains('Provider 1').should('not.exist');
});
it('Custom theme', () => {
cy.testid('WidgetIconPanel_settings').click();
cy.contains('Themes').click();
cy.testid('ThemeSettings-themeList').contains('Green-Sample').click();
cy.testid('WidgetIconPanel_file').click();
cy.themeshot('green-theme', { keepTheme: true });
cy.testid('ThemeSettings-themeList').contains('Solarized-light').click();
cy.testid('WidgetIconPanel_database').click();
cy.contains('MySql-connection').click();
cy.contains('MyChinook').click();
cy.contains('Customer').click();
cy.contains('Leonie');
cy.testid('WidgetIconPanel_file').click();
cy.themeshot('solarized-theme', { keepTheme: true });
});
});
+120
View File
@@ -0,0 +1,120 @@
Cypress.on('uncaught:exception', (err, runnable) => {
// if the error message matches the one about WorkerGlobalScope importScripts
if (err.message.includes("Failed to execute 'importScripts' on 'WorkerGlobalScope'")) {
// return false to let Cypress know we intentionally want to ignore this error
return false;
}
// otherwise let Cypress throw the error
});
beforeEach(() => {
cy.visit('http://localhost:3000');
cy.viewport(1250, 900);
});
describe('Redis data', () => {
it('String test', () => {
cy.contains('Redis-connection').click();
cy.contains('db1').click();
cy.contains('app').click();
cy.contains('version').click();
cy.testid('RedisValueDetail_AceEditor').click().realPress('Backspace').realType('1');
cy.contains('Save').click();
cy.contains('OK').click();
});
it('Hash test', () => {
cy.contains('Redis-connection').click();
cy.contains('db1').click();
cy.contains('user').click();
cy.contains('alice').click();
cy.testid('RedisKeyDetailTab_RenameKeyButton').click();
cy.themeshot('redis-rename-key');
cy.realType('3');
cy.contains('OK').click();
cy.contains('age').click();
cy.testid('RedisValueHashDetail_ValueSection').click().realPress('Backspace').realType('8');
cy.contains('Add field').click();
cy.testid('RedisValueListLikeEdit_key').click().realType('phone');
cy.testid('RedisValueListLikeEdit_value').click().realType('123-456-7890');
cy.contains('Refresh').click();
cy.themeshot('redis-hash-edit');
cy.contains('Save').click();
cy.themeshot('redis-hash-script-edit');
cy.contains('OK').click();
});
it('List test', () => {
cy.contains('Redis-connection').click();
cy.contains('db1').click();
cy.contains('queue').click();
cy.contains('emails').click();
cy.contains('Add field').click();
cy.testid('RedisValueListLikeEdit_value').click().realType('reset');
cy.contains('Save').click();
cy.contains('OK').click();
});
it('Set test', () => {
cy.contains('Redis-connection').click();
cy.contains('db1').click();
cy.contains('tags').click();
cy.contains('Add field').click();
cy.testid('RedisValueListLikeEdit_value').click().realType('newtag');
cy.contains('Save').click();
cy.contains('OK').click();
});
it('ZSet test', () => {
cy.contains('Redis-connection').click();
cy.contains('db1').click();
cy.contains('leaderboard').click();
cy.contains('alice').click();
cy.testid('RedisValueZSetDetail_score')
.click()
.realPress('Backspace')
.realPress('Backspace')
.realPress('Backspace')
.realType('35');
cy.contains('Save').click();
cy.contains('OK').click();
cy.contains('35').should('exist');
});
it('JSON test', () => {
cy.contains('Redis-connection').click();
cy.contains('db1').click();
cy.contains('user').click();
cy.contains('1:*').click();
cy.contains('json').click();
cy.testid('RedisValueDetail_displaySelect').select('JSON view');
cy.themeshot('redis-json-detail');
});
it('Stream test', () => {
cy.contains('Redis-connection').click();
cy.contains('db1').click();
cy.contains('events').click();
cy.contains('Add field').click();
cy.testid('RedisValueListLikeEdit_field').click().realType('message');
cy.testid('RedisValueListLikeEdit_value').click().realType('Hello, World!');
cy.contains('Save').click();
cy.contains('OK').click();
cy.themeshot('redis-stream');
});
it('Add key', () => {
cy.contains('Redis-connection').click();
cy.contains('db1').click();
cy.testid('RedisKeysTree_addKeyDropdown').click();
cy.contains('String').click();
cy.testid('NewRedisKeyTab_keyName').click().realType('newstringkey');
cy.testid('RedisValueDetail_AceEditor').click().realType('This is a new string key.');
cy.contains('Save').click();
cy.contains('OK').click();
cy.contains('newstringkey').should('exist');
cy.testid('RedisKeysTree_addKeyDropdown').click();
cy.contains('Hash').click();
cy.themeshot('redis-add-hash-key');
});
});
+10 -6
View File
@@ -36,9 +36,11 @@ Cypress.Commands.add(
prevSubject: 'optional',
},
(subject, file, options) => {
cy.window().then(win => {
win.__changeCurrentTheme('dark');
});
if (!options?.keepTheme) {
cy.window().then(win => {
win.__changeCurrentTheme('dark');
});
}
// cy.screenshot(`${file}-dark`, {
// onAfterScreenshot: (doc, props) => {
@@ -63,9 +65,11 @@ Cypress.Commands.add(
// });
// });
cy.window().then(win => {
win.__changeCurrentTheme('light');
});
if (!options?.keepTheme) {
cy.window().then(win => {
win.__changeCurrentTheme('light');
});
}
if (subject) {
cy.wrap(subject).screenshot(`${file}-light`, options);
+253
View File
@@ -0,0 +1,253 @@
{
"themeName": "Green-Sample",
"themeType": "light",
"themeVariables": {
"--theme-generic-font": "oklch(27% 0.07 130)",
"--theme-generic-font-hover": "oklch(40% 0.15 130)",
"--theme-generic-font-grayed": "oklch(65% 0.05 130)",
"--theme-link-foreground": "oklch(40% 0.25 130)",
"--theme-content-background": "oklch(95% 0.05 130)",
"--theme-widget-panel-background": "oklch(80% 0.1 130)",
"--theme-widget-panel-foreground": "oklch(27% 0.07 130)",
"--theme-widget-icon-background-active": "oklch(50% 0.12 130)",
"--theme-widget-icon-foreground-active": "white",
"--theme-widget-icon-foreground-hover": "white",
"--theme-widget-icon-border-active": "1px solid white",
"--theme-scrollbar-background": "oklch(90% 0.08 130)",
"--theme-scrollbar-thumb-background": "oklch(70% 0.12 130)",
"--theme-scrollbar-thumb-background-hover": "oklch(40% 0.15 130)",
"--theme-scrollbar-corner-background": "oklch(85% 0.1 130)",
"--theme-tabs-panel-border": "1px solid oklch(95% 0.05 130)",
"--theme-tabs-panel-foreground": "oklch(20% 0.06 130)",
"--theme-tabs-panel-active-foreground": "oklch(10% 0.06 130)",
"--theme-tabs-panel-background": "oklch(95.5% 0.04 130)",
"--theme-tabs-panel-active-background": "oklch(80% 0.12 130)",
"--theme-tabs-panel-item-background": "oklch(90% 0.1 130)",
"--theme-tabs-panel-active-border": "1px solid oklch(50% 0.2 130)",
"--theme-splitter-active": "oklch(50% 0.2 130)",
"--theme-splitter-button-background": "oklch(90% 0.1 130)",
"--theme-splitter-button-background-active": "oklch(85% 0.15 130)",
"--theme-splitter-button-foreground": "oklch(10% 0.06 130)",
"--theme-sidebar-background": "oklch(90% 0.1 130)",
"--theme-sidebar-background-hover": "oklch(80% 0.12 130)",
"--theme-sidebar-background-active": "oklch(75% 0.14 130)",
"--theme-sidebar-background-focused": "oklch(70% 0.18 130)",
"--theme-sidebar-foreground": "oklch(20% 0.06 130)",
"--theme-sidebar-foreground-button": "oklch(40% 0.12 130)",
"--theme-sidebar-foreground-grayed": "oklch(65% 0.05 130)",
"--theme-sidebar-foreground-hover": "oklch(50% 0.25 130)",
"--theme-sidebar-section-background": "oklch(65% 0.05 130)",
"--theme-sidebar-section-border": "none",
"--theme-sidebar-section-border-top": "1px solid oklch(80% 0.1 130)",
"--theme-sidebar-section-foreground": "oklch(10% 0.06 130)",
"--theme-sidebar-border": "none",
"--theme-altsidebar-background": "oklch(95% 0.05 130)",
"--theme-altsidebar-background-grayed": "oklch(97% 0.02 130)",
"--theme-altsidebar-background-hover": "oklch(85% 0.1 130)",
"--theme-altsidebar-background-active": "oklch(80% 0.12 130)",
"--theme-altsidebar-background-focused": "oklch(75% 0.15 130)",
"--theme-altsidebar-foreground": "oklch(20% 0.06 130)",
"--theme-altsidebar-foreground-button": "oklch(40% 0.12 130)",
"--theme-altsidebar-foreground-grayed": "oklch(65% 0.05 130)",
"--theme-altsidebar-foreground-hover": "oklch(50% 0.25 130)",
"--theme-altsidebar-section-background": "oklch(97% 0.02 130)",
"--theme-altsidebar-section-border": "none",
"--theme-altsidebar-section-border-top": "1px solid oklch(85% 0.1 130)",
"--theme-altsidebar-section-foreground": "oklch(10% 0.06 130)",
"--theme-altsidebar-border": "1px solid oklch(90% 0.1 130)",
"--theme-searchbox-background": "oklch(80% 0.12 130)",
"--theme-searchbox-placeholder": "oklch(65% 0.05 130)",
"--theme-searchbox-border": "1px solid oklch(70% 0.15 130)",
"--theme-searchbox-background-filtered": "oklch(95% 0.04 110)",
"--theme-altsearchbox-background": "oklch(90% 0.1 130)",
"--theme-altsearchbox-placeholder": "oklch(65% 0.05 130)",
"--theme-altsearchbox-border": "1px solid oklch(80% 0.1 130)",
"--theme-inlinebutton-foreground": "oklch(40% 0.12 130)",
"--theme-inlinebutton-foreground-disabled": "oklch(65% 0.05 130)",
"--theme-inlinebutton-foreground-hover": "black",
"--theme-inlinebutton-circle-hover-background": "oklch(85% 0.1 130)",
"--theme-inlinebutton-bordered-border": "1px solid oklch(85% 0.1 130)",
"--theme-inlinebutton-bordered-hover-border": "1px solid oklch(70% 0.15 130)",
"--theme-inlinebutton-bordered-background": "linear-gradient(to bottom, oklch(95% 0.04 130) 5%, oklch(90% 0.1 130) 100%)",
"--theme-inlinebutton-bordered-hover-background": "linear-gradient(to bottom, oklch(90% 0.1 130) 5%, oklch(95% 0.04 130) 100%)",
"--theme-datagrid-background": "oklch(95% 0.04 130)",
"--theme-datagrid-foreground": "oklch(20% 0.06 130)",
"--theme-datagrid-foreground-grayed": "oklch(65% 0.05 130)",
"--theme-datagrid-border-horizontal": "1px solid oklch(90% 0.1 130)",
"--theme-datagrid-border-vertical": "1px solid oklch(95% 0.04 130)",
"--theme-datagrid-cell-background": "oklch(97% 0.02 130)",
"--theme-datagrid-headercell-background": "oklch(95% 0.04 130)",
"--theme-datagrid-cell-background-alt": "oklch(95% 0.04 130)",
"--theme-datagrid-cell-background-alt2": "oklch(90% 0.1 130)",
"--theme-datagrid-filter-background": "oklch(90% 0.1 130)",
"--theme-datagrid-filter-border": "1px solid oklch(85% 0.1 130)",
"--theme-datagrid-filter-ok-background": "oklch(95% 0.1 135)",
"--theme-datagrid-filter-error-background": "oklch(95% 0.12 30)",
"--theme-datagrid-modified-row-background": "oklch(95% 0.1 135)",
"--theme-datagrid-modified-cell-background": "oklch(90% 0.15 135)",
"--theme-datagrid-inserted-row-background": "oklch(95% 0.1 110)",
"--theme-datagrid-deleted-row-background": "oklch(95% 0.1 25)",
"--theme-datagrid-selected-cell-background": "oklch(80% 0.1 130)",
"--theme-datagrid-focused-cell-background": "oklch(75% 0.15 130)",
"--theme-datagrid-focused-cell-border-horizontal": "1px solid oklch(70% 0.2 130)",
"--theme-datagrid-focused-cell-border-vertical": "1px solid oklch(70% 0.2 130)",
"--theme-datagrid-selected-point-marker": "oklch(50% 0.25 130)",
"--theme-datagrid-corner-label-background": "oklch(75% 0.15 130)",
"--theme-datagrid-corner-label-border": "1px solid oklch(70% 0.2 130)",
"--theme-datagrid-detail-header-background": "oklch(85% 0.05 130)",
"--theme-datagrid-detail-header-border": "1px solid oklch(80% 0.1 130)",
"--theme-datagrid-cell-foreground-value-green": "oklch(45% 0.2 140)",
"--theme-checkbox-check": "oklch(90% 0.1 130)",
"--theme-checkbox-background": "oklch(40% 0.25 130)",
"--theme-checkbox-border": "1px solid oklch(70% 0.15 130)",
"--theme-checkbox-mark": "white",
"--theme-checkbox-background-disabled": "oklch(95% 0.04 130)",
"--theme-checkbox-background-disabled-before": "oklch(70% 0.15 130)",
"--theme-checkbox-hover-not-disabled": "oklch(65% 0.05 130)",
"--theme-checkbox-background-inherited": "oklch(85% 0.1 130)",
"--theme-table-border": "1px solid oklch(85% 0.1 130)",
"--theme-table-cell-background": "oklch(97% 0.02 130)",
"--theme-table-cell-empty-background": "oklch(95% 0.04 130)",
"--theme-table-cell-empty-foreground": "oklch(65% 0.05 130)",
"--theme-table-header-background": "oklch(95% 0.04 130)",
"--theme-table-selected-background": "oklch(75% 0.15 130)",
"--theme-table-active-background": "oklch(80% 0.1 130)",
"--theme-table-hover-background": "oklch(95% 0.04 130)",
"--theme-table-added-background": "oklch(95% 0.1 110)",
"--theme-table-changed-background": "oklch(95% 0.1 135)",
"--theme-table-deleted-background": "oklch(95% 0.1 25)",
"--theme-cell-active-border": "2px solid oklch(50% 0.25 130)",
"--theme-object-header-background": "oklch(95% 0.04 130)",
"--theme-modal-background": "oklch(97% 0.02 130)",
"--theme-modal-header-background": "oklch(85% 0.1 130)",
"--theme-modal-footer-background": "oklch(97% 0.02 130)",
"--theme-modal-border": "1px solid oklch(85% 0.1 130)",
"--theme-modal-overlay-background": "color-mix(in srgb, #124012 40%, transparent)",
"--theme-modal-shadow": "0 20px 25px -5px color-mix(in srgb, #124012 10%, transparent)",
"--theme-modal-close-hover-background": "oklch(70% 0.15 130)",
"--theme-formbutton-foreground": "white",
"--theme-formbutton-border": "1px solid oklch(40% 0.25 130)",
"--theme-formbutton-border-hover": "1px solid oklch(50% 0.3 130)",
"--theme-formbutton-border-active": "2px solid oklch(55% 0.35 130)",
"--theme-formbutton-background": "oklch(40% 0.25 130)",
"--theme-formbutton-background-disabled": "oklch(85% 0.1 130)",
"--theme-formbutton-border-disabled": "1px solid oklch(85% 0.1 130)",
"--theme-formbutton-foreground-disabled": "oklch(65% 0.05 130)",
"--theme-formbutton-background-hover": "oklch(35% 0.3 130)",
"--theme-formbutton-background-active": "oklch(35% 0.3 130)",
"--theme-outlinebutton-foreground": "oklch(10% 0.06 130)",
"--theme-outlinebutton-border": "1px solid oklch(40% 0.25 130)",
"--theme-outlinebutton-hover-foreground": "oklch(40% 0.25 130)",
"--theme-outlinebutton-hover-border": "2px solid oklch(50% 0.3 130)",
"--theme-tabs-control-background": "oklch(95% 0.04 130)",
"--theme-tabs-control-border": "1px solid oklch(90% 0.1 130)",
"--theme-tabs-control-selected-background": "oklch(98% 0.01 130)",
"--theme-tabs-control-selected-border": "2px solid oklch(50% 0.25 130)",
"--theme-inline-tabs-border": "1px solid oklch(90% 0.1 130)",
"--theme-inline-tabs-border-active": "2px solid oklch(50% 0.25 130)",
"--theme-toolstrip-background": "oklch(97% 0.02 130)",
"--theme-toolstrip-border": "1px solid oklch(90% 0.1 130)",
"--theme-toolstrip-button-foreground": "oklch(27% 0.07 130)",
"--theme-panel-border-subtle": "1px solid color-mix(in srgb, oklch(20% 0.06 130) 5%, transparent)",
"--theme-panel-type-label-color": "oklch(65% 0.05 130)",
"--theme-toolstrip-button-foreground-disabled": "oklch(65% 0.05 130)",
"--theme-toolstrip-button-foreground-icon": "oklch(40% 0.12 130)",
"--theme-toolstrip-button-background": "oklch(97% 0.02 130)",
"--theme-toolstrip-button-background-hover": "oklch(95% 0.04 130)",
"--theme-toolstrip-button-background-active": "oklch(90% 0.1 130)",
"--theme-toolstrip-button-border": "1px solid oklch(90% 0.1 130)",
"--theme-toolstrip-button-border-hover": "1px solid oklch(85% 0.1 130)",
"--theme-toolstrip-button-border-disabled": "1px solid oklch(90% 0.1 130)",
"--theme-toolstrip-button-split-separator-border": "1px solid oklch(85% 0.1 130)",
"--theme-designer-background": "oklch(97% 0.02 130)",
"--theme-designer-item-background": "oklch(95% 0.04 130)",
"--theme-designer-selection-marker": "oklch(35% 0.3 130)",
"--theme-designer-item-border": "1px solid oklch(90% 0.1 130)",
"--theme-designer-stroke-color": "oklch(65% 0.05 130)",
"--theme-designer-arrow-color": "oklch(27% 0.07 130)",
"--theme-designer-select-reactangle-foreground": "oklch(50% 0.25 130)",
"--theme-designer-header-background-1": "oklch(70% 0.15 130)",
"--theme-designer-header-background-2": "oklch(70% 0.18 180)",
"--theme-designer-header-background-3": "oklch(68% 0.15 100)",
"--theme-designer-header-background-grayed": "oklch(85% 0.1 130)",
"--theme-designer-close-background": "oklch(90% 0.1 130)",
"--theme-designer-close-background-hover": "oklch(85% 0.1 130)",
"--theme-designer-close-background-active": "oklch(70% 0.15 130)",
"--theme-designer-drag-column-background": "oklch(90% 0.2 110)",
"--theme-designer-select-column-background": "oklch(90% 0.1 130)",
"--theme-statusbar-background": "oklch(40% 0.25 130)",
"--theme-statusbar-foreground": "oklch(95% 0.04 130)",
"--theme-statusbar-background-hover": "oklch(35% 0.3 130)",
"--theme-statusbar-button-background": "oklch(85% 0.1 130)",
"--theme-statusbar-button-foreground": "oklch(27% 0.07 130)",
"--theme-statusbar-icon-error": "oklch(80% 0.1 25)",
"--theme-statusbar-icon-ok": "oklch(85% 0.2 130)",
"--theme-aichat-user-background": "oklch(93% 0.06 130)",
"--theme-aichat-assistant-background": "oklch(95% 0.04 130)",
"--theme-applog-details-background": "oklch(98% 0.01 130)",
"--theme-input-border": "1px solid oklch(85% 0.1 130)",
"--theme-input-border-hover": "1px solid oklch(70% 0.15 130)",
"--theme-input-border-hover-color": "oklch(70% 0.15 130)",
"--theme-input-border-focus": "1px solid oklch(50% 0.25 130)",
"--theme-input-border-focus-color": "oklch(50% 0.25 130)",
"--theme-input-border-disabled": "1px solid oklch(90% 0.1 130)",
"--theme-input-background": "white",
"--theme-input-foreground": "oklch(20% 0.06 130)",
"--theme-input-placeholder": "oklch(65% 0.05 130)",
"--theme-input-background-disabled": "oklch(95% 0.04 130)",
"--theme-input-foreground-disabled": "oklch(65% 0.05 130)",
"--theme-input-focus-ring": "0 0 0 3px color-mix(in srgb, oklch(50% 0.25 130) 10%, transparent)",
"--theme-input-multi-clear-background": "oklch(90% 0.1 130)",
"--theme-input-multi-clear-foreground": "oklch(40% 0.12 130)",
"--theme-input-multi-clear-hover": "oklch(85% 0.1 130)",
"--theme-input-shadow": "0 1px 2px 0 color-mix(in srgb, oklch(20% 0.06 130) 5%, transparent)",
"--theme-input-shadow-hover": "0 4px 6px -2px color-mix(in srgb, oklch(20% 0.06 130) 8%, transparent)",
"--theme-input-shadow-focus": "0 1px 2px 0 color-mix(in srgb, oklch(20% 0.06 130) 5%, transparent)",
"--theme-input-inplace-select-shadow": "0 1px 10px 1px oklch(40% 0.12 130)",
"--theme-color-selected-border": "2px solid oklch(27% 0.07 130)",
"--theme-new-object-button-background": "oklch(90% 0.1 130)",
"--theme-new-object-button-background-hover": "oklch(85% 0.1 130)",
"--theme-status-valid-background": "oklch(95% 0.1 110)",
"--theme-status-testing-background": "oklch(95% 0.1 135)",
"--theme-status-error-background": "oklch(95% 0.1 25)",
"--theme-status-unconfigured-background": "oklch(95% 0.04 130)",
"--theme-status-untested-background": "oklch(94% 0.1 65)",
"--theme-dropdown-icon-hover": "oklch(45% 0.3 130)",
"--theme-icon-picker-background": "oklch(90% 0.1 130)",
"--theme-icon-picker-border": "1px solid oklch(85% 0.1 130)",
"--theme-icon-picker-hover": "oklch(85% 0.1 130)",
"--theme-icon-picker-selected": "oklch(80% 0.15 130)",
"--theme-dbkey-background": "oklch(98% 0.01 130)",
"--theme-dbkey-border": "1px solid oklch(90% 0.1 130)",
"--theme-dbkey-icon-hover": "oklch(70% 0.15 130)",
"--theme-chip-background": "oklch(85% 0.1 130)",
"--theme-titlebar-background": "oklch(85% 0.1 130)",
"--theme-titlebar-button-hover": "oklch(70% 0.15 130)",
"--theme-card-background": "oklch(90% 0.1 130)",
"--theme-card-border": "1px solid oklch(85% 0.1 130)",
"--theme-content-background-hover": "oklch(95% 0.04 130)",
"--theme-admin-menu-item-hover": "oklch(95% 0.04 130)",
"--theme-admin-menu-item-active": "oklch(85% 0.1 130)",
"--theme-admin-menu-background": "oklch(90% 0.1 130)",
"--theme-admin-menu-border": "1px solid oklch(90% 0.1 130)",
"--theme-json-tree-string-color": "oklch(45% 0.3 110)",
"--theme-json-tree-symbol-color": "oklch(45% 0.3 110)",
"--theme-json-tree-boolean-color": "oklch(40% 0.25 130)",
"--theme-json-tree-function-color": "oklch(40% 0.25 130)",
"--theme-json-tree-number-color": "oklch(50% 0.3 130)",
"--theme-json-tree-label-color": "oklch(55% 0.3 140)",
"--theme-json-tree-arrow-color": "oklch(65% 0.05 130)",
"--theme-json-tree-null-color": "oklch(65% 0.05 130)",
"--theme-json-tree-undefined-color": "oklch(65% 0.05 130)",
"--theme-json-tree-date-color": "oklch(65% 0.05 130)",
"--theme-json-tree-deleted-background": "oklch(95% 0.1 25)",
"--theme-json-tree-modified-background": "oklch(95% 0.1 135)",
"--theme-json-tree-inserted-background": "oklch(95% 0.1 110)",
"--theme-icon-blue": "oklch(40% 0.25 130)",
"--theme-icon-green": "oklch(45% 0.2 140)",
"--theme-icon-red": "oklch(40% 0.3 25)",
"--theme-icon-gold": "oklch(50% 0.2 60)",
"--theme-icon-yellow": "oklch(50% 0.15 80)",
"--theme-icon-magenta": "oklch(45% 0.3 135)"
}
}
+15
View File
@@ -0,0 +1,15 @@
HSET "actor:1000" "first_name" "Sandra"
HSET "actor:1000" "last_name" "Bullock"
HSET "actor:1000" "date_of_birth" "1964"
HSET "actor:1001" "first_name" "Jon"
HSET "actor:1001" "last_name" "Hamm"
HSET "actor:1001" "date_of_birth" "1971"
HSET "actor:1002" "first_name" "Allison"
HSET "actor:1002" "last_name" "Janney"
HSET "actor:1002" "date_of_birth" "1959"
HSET "actor:1003" "first_name" "Steve"
HSET "actor:1003" "last_name" "Coogan"
HSET "actor:1003" "date_of_birth" "1965"
+14
View File
@@ -0,0 +1,14 @@
SET app:name "App"
SET app:version "1.0.0"
SET app:env "test"
SET user:1:json "{\"id\":1,\"name\":\"Alice\",\"email\":\"alice@app.test\",\"roles\":[\"admin\",\"user\"],\"settings\":{\"theme\":\"dark\",\"language\":\"sk\"}}"
SET user:2:json "{\"id\":2,\"name\":\"Bob\",\"email\":\"bob@app.test\",\"roles\":[\"user\"],\"settings\":{\"theme\":\"light\",\"language\":\"en\"}}"
RPUSH queue:emails "welcome" "reset-password" "newsletter" "promotion" "weekly-digest"
HSET user:alice name "Alice" email "alice@app.test" active "true" age "29" country "SK"
HSET user:bob name "Bob" email "bob@app.test" active "false" age "34" country "CZ"
SADD tags "app" "backend" "database" "redis" "test" "production"
ZADD leaderboard 100 "alice" 250 "bob" 180 "carol" 90 "dave" 300 "eve"
XADD events * type "login" userId "1" ip "127.0.0.1" device "web"
XADD events * type "update-profile" userId "1" field "email" old "alice@app.test" new "alice@new.app"
XADD events * type "login" userId "2" ip "10.0.0.5" device "mobile"
XADD events * type "logout" userId "1" reason "manual"
+1 -6
View File
@@ -1,4 +1,4 @@
CONNECTIONS=mysql,postgres,mongo,redis
CONNECTIONS=mysql,postgres,mongo
LABEL_mysql=MySql-connection
SERVER_mysql=localhost
@@ -22,8 +22,3 @@ USER_mongo=root
PASSWORD_mongo=Pwd2020Db
PORT_mongo=16010
ENGINE_mongo=mongo@dbgate-plugin-mongo
LABEL_redis=Redis-connection
SERVER_redis=localhost
ENGINE_redis=redis@dbgate-plugin-redis
PORT_redis=16011
+6
View File
@@ -0,0 +1,6 @@
CONNECTIONS=redis
LABEL_redis=Redis-connection
SERVER_redis=localhost
ENGINE_redis=redis@dbgate-plugin-redis
PORT_redis=16011
-42
View File
@@ -125,46 +125,6 @@ async function initMongoDatabase(dbname, inputDirectory) {
// });
}
async function initRedisDatabase(inputDirectory) {
await dbgateApi.executeQuery({
connection: {
server: process.env.SERVER_redis,
user: process.env.USER_redis,
password: process.env.PASSWORD_redis,
port: process.env.PORT_redis,
engine: 'redis@dbgate-plugin-redis',
},
sql: 'FLUSHALL',
});
for (const file of fs.readdirSync(inputDirectory)) {
await dbgateApi.executeQuery({
connection: {
server: process.env.SERVER_redis,
user: process.env.USER_redis,
password: process.env.PASSWORD_redis,
port: process.env.PORT_redis,
engine: 'redis@dbgate-plugin-redis',
database: 0,
},
sqlFile: path.join(inputDirectory, file),
// logScriptItems: true,
});
}
// await dbgateApi.importDatabase({
// connection: {
// server: process.env.SERVER_postgres,
// user: process.env.USER_postgres,
// password: process.env.PASSWORD_postgres,
// port: process.env.PORT_postgres,
// database: dbname,
// engine: 'postgres@dbgate-plugin-postgres',
// },
// inputFile,
// });
}
const baseDir = path.join(os.homedir(), '.dbgate');
async function copyFolder(source, target) {
@@ -188,8 +148,6 @@ async function run() {
await initMongoDatabase('MgChinook', path.resolve(path.join(__dirname, '../data/chinook-jsonl')));
await initMongoDatabase('MgRivers', path.resolve(path.join(__dirname, '../data/rivers-jsonl')));
await initRedisDatabase(path.resolve(path.join(__dirname, '../data/redis')));
await copyFolder(
path.resolve(path.join(__dirname, '../data/chinook-jsonl')),
path.join(baseDir, 'archive-e2etests', 'default')
+5
View File
@@ -90,6 +90,11 @@ async function run() {
path.join(baseDir, 'files-e2etests', 'sql')
);
await copyFolder(
path.resolve(path.join(__dirname, '../data/files/themes')),
path.join(baseDir, 'files-e2etests', 'themes')
);
await initMySqlDatabase('MyChinook', path.resolve(path.join(__dirname, '../data/chinook-mysql.sql')));
}
+55
View File
@@ -0,0 +1,55 @@
const path = require('path');
const fs = require('fs');
const dbgateApi = require('dbgate-api');
dbgateApi.initializeApiEnvironment();
const dbgatePluginRedis = require('dbgate-plugin-redis');
dbgateApi.registerPlugins(dbgatePluginRedis);
async function initRedisDatabase() {
await dbgateApi.executeQuery({
connection: {
server: process.env.SERVER_redis,
user: process.env.USER_redis,
password: process.env.PASSWORD_redis,
port: process.env.PORT_redis,
engine: 'redis@dbgate-plugin-redis',
},
sql: 'FLUSHALL',
});
const files = [
{
file: path.resolve(__dirname, '../data/redis-db1.redis'),
database: 0,
},
{
file: path.resolve(__dirname, '../data/redis-db2.redis'),
database: 1,
},
];
for (const { file, database } of files) {
await dbgateApi.executeQuery({
connection: {
server: process.env.SERVER_redis,
user: process.env.USER_redis,
password: process.env.PASSWORD_redis,
port: process.env.PORT_redis,
engine: 'redis@dbgate-plugin-redis',
database,
},
sqlFile: file,
});
}
}
async function run() {
await initRedisDatabase();
}
dbgateApi.runScript(run);
module.exports = {
initRedisDatabase,
};
+4 -1
View File
@@ -23,6 +23,7 @@
"cy:run:multi-sql": "cypress run --spec cypress/e2e/multi-sql.cy.js",
"cy:run:cloud": "cypress run --spec cypress/e2e/cloud.cy.js",
"cy:run:charts": "cypress run --spec cypress/e2e/charts.cy.js",
"cy:run:redis": "cypress run --spec cypress/e2e/redis.cy.js",
"start:add-connection": "node clearTestingData && cd .. && node packer/build/bundle.js --listen-api --run-e2e-tests",
"start:portal": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/portal/.env node e2e-tests/init/portal.js && env-cmd -f e2e-tests/env/portal/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
"start:oauth": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/oauth/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
@@ -31,6 +32,7 @@
"start:multi-sql": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/multi-sql/.env node e2e-tests/init/multi-sql.js && env-cmd -f e2e-tests/env/multi-sql/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
"start:cloud": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/cloud/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
"start:charts": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/charts/.env node e2e-tests/init/charts.js && env-cmd -f e2e-tests/env/charts/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
"start:redis": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/redis/.env node e2e-tests/init/redis.js && env-cmd -f e2e-tests/env/redis/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
"test:add-connection": "start-server-and-test start:add-connection http://localhost:3000 cy:run:add-connection",
"test:portal": "start-server-and-test start:portal http://localhost:3000 cy:run:portal",
"test:oauth": "start-server-and-test start:oauth http://localhost:3000 cy:run:oauth",
@@ -39,7 +41,8 @@
"test:multi-sql": "start-server-and-test start:multi-sql http://localhost:3000 cy:run:multi-sql",
"test:cloud": "start-server-and-test start:cloud http://localhost:3000 cy:run:cloud",
"test:charts": "start-server-and-test start:charts http://localhost:3000 cy:run:charts",
"test": "yarn test:add-connection && yarn test:portal && yarn test:oauth && yarn test:browse-data && yarn test:team && yarn test:multi-sql && yarn test:cloud && yarn test:charts",
"test:redis": "start-server-and-test start:redis http://localhost:3000 cy:run:redis",
"test": "yarn test:add-connection && yarn test:portal && yarn test:oauth && yarn test:browse-data && yarn test:team && yarn test:multi-sql && yarn test:cloud && yarn test:charts && yarn test:redis",
"test:ci": "yarn test"
},
"dependencies": {}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "dbgate-integration-tests",
"version": "6.0.0-alpha.1",
"version": "7.0.0-alpha.1",
"homepage": "https://dbgate.org/",
"repository": {
"type": "git",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"private": true,
"version": "7.0.0-premium-beta.5",
"version": "7.0.1",
"name": "dbgate-all",
"workspaces": [
"packages/*",
+6 -6
View File
@@ -1,7 +1,7 @@
{
"name": "dbgate-api",
"main": "src/index.js",
"version": "6.0.0-alpha.1",
"version": "7.0.0-alpha.1",
"homepage": "https://dbgate.org/",
"repository": {
"type": "git",
@@ -24,16 +24,16 @@
"activedirectory2": "^2.1.0",
"archiver": "^7.0.1",
"async-lock": "^1.2.6",
"axios": "^0.21.1",
"axios": "^1.13.2",
"body-parser": "^1.19.0",
"byline": "^5.0.0",
"compare-versions": "^3.6.0",
"cors": "^2.8.5",
"cross-env": "^6.0.3",
"dbgate-datalib": "^6.0.0-alpha.1",
"dbgate-datalib": "^7.0.0-alpha.1",
"dbgate-query-splitter": "^4.11.9",
"dbgate-sqltree": "^6.0.0-alpha.1",
"dbgate-tools": "^6.0.0-alpha.1",
"dbgate-sqltree": "^7.0.0-alpha.1",
"dbgate-tools": "^7.0.0-alpha.1",
"debug": "^4.3.4",
"diff": "^5.0.0",
"diff2html": "^3.4.13",
@@ -87,7 +87,7 @@
"devDependencies": {
"@types/fs-extra": "^9.0.11",
"@types/lodash": "^4.14.149",
"dbgate-types": "^6.0.0-alpha.1",
"dbgate-types": "^7.0.0-alpha.1",
"env-cmd": "^10.1.0",
"jsdoc-to-markdown": "^9.0.5",
"node-loader": "^1.0.2",
+82 -2
View File
@@ -24,10 +24,12 @@ const requireEngineDriver = require('../utility/requireEngineDriver');
const { getAuthProviderById } = require('../auth/authProvider');
const { startTokenChecking } = require('../utility/authProxy');
const { extractConnectionsFromEnv } = require('../utility/envtools');
const { MissingCredentialsError } = require('../utility/exceptions');
const logger = getLogger('connections');
let volatileConnections = {};
let pendingTestSubprocesses = {}; // Map of conid -> subprocess for MS Entra auth flows
function getNamedArgs() {
const res = {};
@@ -239,14 +241,60 @@ module.exports = {
);
pipeForkLogs(subprocess);
subprocess.send({ ...connection, requestDbList });
return new Promise(resolve => {
return new Promise((resolve, reject) => {
let isWaitingForVolatile = false;
const cleanup = () => {
if (connection._id && pendingTestSubprocesses[connection._id]) {
delete pendingTestSubprocesses[connection._id];
}
};
subprocess.on('message', resp => {
if (handleProcessCommunication(resp, subprocess)) return;
// @ts-ignore
const { msgtype } = resp;
const { msgtype, missingCredentialsDetail } = resp;
if (msgtype == 'connected' || msgtype == 'error') {
cleanup();
resolve(resp);
}
if (msgtype == 'missingCredentials') {
if (missingCredentialsDetail?.redirectToDbLogin) {
// Store the subprocess for later when volatile connection is ready
isWaitingForVolatile = true;
pendingTestSubprocesses[connection._id] = {
subprocess,
requestDbList,
};
// Return immediately with redirectToDbLogin status in the old format
resolve({
missingCredentials: true,
detail: {
...missingCredentialsDetail,
keepErrorResponseFromApi: true,
},
});
return;
}
reject(new MissingCredentialsError(missingCredentialsDetail));
}
});
subprocess.on('exit', (code) => {
// If exit happens while waiting for volatile, that's expected
if (isWaitingForVolatile && code === 0) {
cleanup();
return;
}
cleanup();
if (code !== 0) {
reject(new Error(`Test subprocess exited with code ${code}`));
}
});
subprocess.on('error', (err) => {
cleanup();
reject(err);
});
});
},
@@ -279,6 +327,38 @@ module.exports = {
return testRes;
} else {
volatileConnections[res._id] = res;
// Check if there's a pending test subprocess waiting for this volatile connection
const pendingTest = pendingTestSubprocesses[conid];
if (pendingTest) {
const { subprocess, requestDbList } = pendingTest;
try {
// Send the volatile connection to the waiting subprocess
subprocess.send({ ...res, requestDbList, isVolatileResolved: true });
// Wait for the test result and emit it as an event
subprocess.once('message', resp => {
if (handleProcessCommunication(resp, subprocess)) return;
const { msgtype } = resp;
if (msgtype == 'connected' || msgtype == 'error') {
// Emit SSE event with test result
socket.emit(`connection-test-result-${conid}`, {
...resp,
volatileConId: res._id,
});
delete pendingTestSubprocesses[conid];
}
});
} catch (err) {
logger.error(extractErrorLogData(err), 'DBGM-00118 Error sending volatile connection to test subprocess');
socket.emit(`connection-test-result-${conid}`, {
msgtype: 'error',
error: err.message,
});
delete pendingTestSubprocesses[conid];
}
}
return res;
}
},
@@ -393,6 +393,12 @@ module.exports = {
return null;
},
dispatchRedisKeysChanged_meta: true,
dispatchRedisKeysChanged({ conid, database }) {
socket.emit(`redis-keys-changed-${conid}-${database}`);
return null;
},
loadKeys_meta: true,
async loadKeys({ conid, database, root, filter, limit }, req) {
await testConnectionPermission(conid, req);
+1 -1
View File
@@ -1,5 +1,5 @@
module.exports = {
version: '6.0.0-alpha.1',
version: '7.0.0-alpha.1',
buildTime: '2024-12-01T00:00:00Z'
};
+35 -2
View File
@@ -18,13 +18,36 @@ Platform: ${process.platform}
function start() {
childProcessChecker();
process.on('message', async connection => {
let isWaitingForVolatile = false;
const handleConnection = async connection => {
// @ts-ignore
const { requestDbList } = connection;
if (handleProcessCommunication(connection)) return;
try {
const driver = requireEngineDriver(connection);
const dbhan = await connectUtility(driver, connection, 'app');
const connectionChanged = driver?.beforeConnectionSave ? driver.beforeConnectionSave(connection) : connection;
if (!connection.isVolatileResolved) {
if (connectionChanged.useRedirectDbLogin) {
process.send({
msgtype: 'missingCredentials',
missingCredentialsDetail: {
// @ts-ignore
conid: connection._id,
redirectToDbLogin: true,
keepErrorResponseFromApi: true,
},
});
// Don't exit - wait for volatile connection to be sent
isWaitingForVolatile = true;
return;
}
}
const dbhan = await connectUtility(driver, connectionChanged, 'app');
let version = {
version: 'Unknown',
};
@@ -45,6 +68,16 @@ function start() {
}
process.exit(0);
};
process.on('message', async connection => {
// If we're waiting for volatile and receive a new connection, use it
if (isWaitingForVolatile) {
isWaitingForVolatile = false;
await handleConnection(connection);
} else {
await handleConnection(connection);
}
});
}
+3 -3
View File
@@ -1,7 +1,7 @@
const getDiagramExport = (html, css, themeType, themeVariables, 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);">
<div style="position: fixed; bottom: 0; right: 0; padding: 5px; font-size: 12px; color: var(--theme-generic-font-grayed); background-color: var(--theme-datagrid-background); border-top-left-radius: 5px; border: var(--theme-card-border);">
${watermark}
</div>
`
@@ -22,8 +22,8 @@ const getDiagramExport = (html, css, themeType, themeVariables, watermark) => {
${css}
body {
background: var(--theme-bg-1);
color: var(--theme-font-1);
background: var(--theme-datagrid-background);
color: var(--theme-generic-font);
}
</style>
+5 -5
View File
@@ -1,5 +1,5 @@
{
"version": "6.0.0-alpha.1",
"version": "7.0.0-alpha.1",
"name": "dbgate-datalib",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
@@ -19,14 +19,14 @@
],
"dependencies": {
"date-fns": "^4.1.0",
"dbgate-filterparser": "^6.0.0-alpha.1",
"dbgate-sqltree": "^6.0.0-alpha.1",
"dbgate-tools": "^6.0.0-alpha.1",
"dbgate-filterparser": "^7.0.0-alpha.1",
"dbgate-sqltree": "^7.0.0-alpha.1",
"dbgate-tools": "^7.0.0-alpha.1",
"uuid": "^3.4.0"
},
"devDependencies": {
"@types/node": "^13.7.0",
"dbgate-types": "^6.0.0-alpha.1",
"dbgate-types": "^7.0.0-alpha.1",
"jest": "^28.1.3",
"ts-jest": "^28.0.7",
"typescript": "^4.4.3"
+30 -8
View File
@@ -15,7 +15,7 @@ export interface ChangeSetRedis_JSON {
export interface ChangeSetRedis_Hash {
key: string;
type: 'hash';
inserts: { key: string; value: string; ttl: number }[];
inserts: { key: string; value: string; ttl: number; editorRowId: string }[];
updates: { key: string; value: string; ttl: number }[];
deletes: string[];
}
@@ -23,7 +23,7 @@ export interface ChangeSetRedis_Hash {
export interface ChangeSetRedis_List {
key: string;
type: 'list';
inserts: { index: number; value: string }[];
inserts: { value: string; editorRowId: string }[];
updates: { index: number; value: string }[];
deletes: number[];
}
@@ -31,25 +31,34 @@ export interface ChangeSetRedis_List {
export interface ChangeSetRedis_Set {
key: string;
type: 'set';
inserts: string[];
inserts: { value: string; editorRowId: string }[];
deletes: string[];
}
export interface ChangeSetRedis_ZSet {
key: string;
type: 'zset';
inserts: { member: string; score: number }[];
inserts: { member: string; score: number; editorRowId: string }[];
updates: { member: string; score: number }[];
deletes: string[];
}
export interface ChangeSetRedis_Stream {
key: string;
type: 'stream';
generatedId?: string;
inserts: { field: string; value: string; editorRowId: string }[];
deletes: string[];
}
export type ChangeSetRedisType =
| ChangeSetRedis_String
| ChangeSetRedis_JSON
| ChangeSetRedis_Hash
| ChangeSetRedis_List
| ChangeSetRedis_Set
| ChangeSetRedis_ZSet;
| ChangeSetRedis_ZSet
| ChangeSetRedis_Stream;
export interface ChangeSetRedis {
changes: ChangeSetRedisType[];
@@ -160,7 +169,7 @@ export function redisChangeSetToRedisCommands(changeSet: ChangeSetRedis): Databa
for (const insert of change.inserts) {
calls.push({
method: 'SADD',
args: [change.key, insert],
args: [change.key, insert.value],
});
}
}
@@ -173,6 +182,19 @@ export function redisChangeSetToRedisCommands(changeSet: ChangeSetRedis): Databa
});
}
}
} else if (change.type === 'stream') {
if (change.inserts.length > 0) {
calls.push({
method: 'XADD',
args: [change.key, change.generatedId || '*', ...change.inserts.flatMap(f => [f.field, f.value])],
});
}
for (const delValue of change.deletes) {
calls.push({
method: 'XDEL',
args: [change.key, delValue],
});
}
}
}
@@ -182,7 +204,7 @@ export function redisChangeSetToRedisCommands(changeSet: ChangeSetRedis): Databa
export function convertRedisCallListToScript(callList: DatabaseMethodCallList): string {
let script = '';
for (const call of callList.calls) {
script += `${call.method} ${call.args.map((arg) => (typeof arg === 'string' ? `"${arg}"` : arg)).join(' ')}\n`;
script += `${call.method} ${call.args.map(arg => (typeof arg === 'string' ? `"${arg}"` : arg)).join(' ')}\n`;
}
return script;
}
}
+11 -11
View File
@@ -1,6 +1,6 @@
{
"name": "dbmodel",
"version": "6.0.0-alpha.1",
"version": "7.0.0-alpha.1",
"homepage": "https://dbgate.org/",
"repository": {
"type": "git",
@@ -30,16 +30,16 @@
],
"dependencies": {
"commander": "^10.0.0",
"dbgate-api": "^6.0.0-alpha.1",
"dbgate-plugin-csv": "^6.0.0-alpha.1",
"dbgate-plugin-excel": "^6.0.0-alpha.1",
"dbgate-plugin-mongo": "^6.0.0-alpha.1",
"dbgate-plugin-mssql": "^6.0.0-alpha.1",
"dbgate-plugin-mysql": "^6.0.0-alpha.1",
"dbgate-plugin-postgres": "^6.0.0-alpha.1",
"dbgate-plugin-xml": "^6.0.0-alpha.1",
"dbgate-plugin-oracle": "^6.0.0-alpha.1",
"dbgate-web": "^6.0.0-alpha.1",
"dbgate-api": "^7.0.0-alpha.1",
"dbgate-plugin-csv": "^7.0.0-alpha.1",
"dbgate-plugin-excel": "^7.0.0-alpha.1",
"dbgate-plugin-mongo": "^7.0.0-alpha.1",
"dbgate-plugin-mssql": "^7.0.0-alpha.1",
"dbgate-plugin-mysql": "^7.0.0-alpha.1",
"dbgate-plugin-postgres": "^7.0.0-alpha.1",
"dbgate-plugin-xml": "^7.0.0-alpha.1",
"dbgate-plugin-oracle": "^7.0.0-alpha.1",
"dbgate-web": "^7.0.0-alpha.1",
"dotenv": "^16.0.0",
"pinomin": "^1.0.5"
}
+3 -3
View File
@@ -1,5 +1,5 @@
{
"version": "6.0.0-alpha.1",
"version": "7.0.0-alpha.1",
"name": "dbgate-filterparser",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
@@ -17,7 +17,7 @@
"lib"
],
"devDependencies": {
"dbgate-types": "^6.0.0-alpha.1",
"dbgate-types": "^7.0.0-alpha.1",
"@types/jest": "^25.1.4",
"@types/node": "^13.7.0",
"jest": "^28.1.3",
@@ -26,7 +26,7 @@
},
"dependencies": {
"@types/parsimmon": "^1.10.1",
"dbgate-tools": "^6.0.0-alpha.1",
"dbgate-tools": "^7.0.0-alpha.1",
"lodash": "^4.17.21",
"date-fns": "^4.1.0",
"moment": "^2.24.0",
+14 -14
View File
@@ -1,6 +1,6 @@
{
"name": "dbgate-serve",
"version": "6.0.0-alpha.1",
"version": "7.0.0-alpha.1",
"homepage": "https://dbgate.org/",
"repository": {
"type": "git",
@@ -18,19 +18,19 @@
"web"
],
"dependencies": {
"dbgate-api": "^6.0.0-alpha.1",
"dbgate-plugin-clickhouse": "^6.0.0-alpha.1",
"dbgate-plugin-csv": "^6.0.0-alpha.1",
"dbgate-plugin-excel": "^6.0.0-alpha.1",
"dbgate-plugin-mongo": "^6.0.0-alpha.1",
"dbgate-plugin-mssql": "^6.0.0-alpha.1",
"dbgate-plugin-mysql": "^6.0.0-alpha.1",
"dbgate-plugin-oracle": "^6.0.0-alpha.1",
"dbgate-plugin-postgres": "^6.0.0-alpha.1",
"dbgate-plugin-redis": "^6.0.0-alpha.1",
"dbgate-plugin-sqlite": "^6.0.0-alpha.1",
"dbgate-plugin-xml": "^6.0.0-alpha.1",
"dbgate-web": "^6.0.0-alpha.1",
"dbgate-api": "^7.0.0-alpha.1",
"dbgate-plugin-clickhouse": "^7.0.0-alpha.1",
"dbgate-plugin-csv": "^7.0.0-alpha.1",
"dbgate-plugin-excel": "^7.0.0-alpha.1",
"dbgate-plugin-mongo": "^7.0.0-alpha.1",
"dbgate-plugin-mssql": "^7.0.0-alpha.1",
"dbgate-plugin-mysql": "^7.0.0-alpha.1",
"dbgate-plugin-oracle": "^7.0.0-alpha.1",
"dbgate-plugin-postgres": "^7.0.0-alpha.1",
"dbgate-plugin-redis": "^7.0.0-alpha.1",
"dbgate-plugin-sqlite": "^7.0.0-alpha.1",
"dbgate-plugin-xml": "^7.0.0-alpha.1",
"dbgate-web": "^7.0.0-alpha.1",
"dotenv": "^16.0.0"
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
{
"version": "6.0.0-alpha.1",
"version": "7.0.0-alpha.1",
"name": "dbgate-sqltree",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
@@ -27,7 +27,7 @@
],
"devDependencies": {
"@types/node": "^13.7.0",
"dbgate-types": "^6.0.0-alpha.1",
"dbgate-types": "^7.0.0-alpha.1",
"typescript": "^4.4.3"
},
"dependencies": {
+3 -3
View File
@@ -1,5 +1,5 @@
{
"version": "6.0.0-alpha.1",
"version": "7.0.0-alpha.1",
"name": "dbgate-tools",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
@@ -26,7 +26,7 @@
],
"devDependencies": {
"@types/node": "^13.7.0",
"dbgate-types": "^6.0.0-alpha.1",
"dbgate-types": "^7.0.0-alpha.1",
"jest": "^28.1.3",
"ts-jest": "^28.0.7",
"typescript": "^4.4.3"
@@ -34,7 +34,7 @@
"dependencies": {
"blueimp-md5": "^2.19.0",
"dbgate-query-splitter": "^4.11.9",
"dbgate-sqltree": "^6.0.0-alpha.1",
"dbgate-sqltree": "^7.0.0-alpha.1",
"debug": "^4.3.4",
"json-stable-stringify": "^1.0.1",
"lodash": "^4.17.21",
+5 -1
View File
@@ -27,6 +27,7 @@ interface SqlGeneratorOptions {
createIndexes: boolean;
insert: boolean;
skipAutoincrementColumn: boolean;
skipComputedColumns: boolean;
disableConstraints: boolean;
omitNulls: boolean;
truncate: boolean;
@@ -260,9 +261,12 @@ export class SqlGenerator {
}
processReadable(table: TableInfo, readable) {
const columnsFiltered = this.options.skipAutoincrementColumn
const columnsFilteredPre = this.options.skipAutoincrementColumn
? table.columns.filter(x => !x.autoIncrement)
: table.columns;
const columnsFiltered = this.options.skipComputedColumns
? columnsFilteredPre.filter(x => !x.computedExpression)
: columnsFilteredPre;
const columnNames = columnsFiltered.map(x => x.columnName);
let isClosed = false;
let isHeaderRead = false;
-351
View File
@@ -1,351 +0,0 @@
import _omit from 'lodash/omit';
import _sortBy from 'lodash/sortBy';
export const DB_KEYS_SHOW_INCREMENT = 100;
export interface DbKeysNodeModelBase {
text?: string;
sortKey: string;
key: string;
count?: number;
level: number;
keyPath: string[];
parentKey: string;
}
export interface DbKeysLeafNodeModel extends DbKeysNodeModelBase {
type: 'string' | 'hash' | 'set' | 'list' | 'zset' | 'stream' | 'binary' | 'ReJSON-RL';
}
export interface DbKeysFolderNodeModel extends DbKeysNodeModelBase {
// root: string;
type: 'dir';
// visibleCount?: number;
// isExpanded?: boolean;
}
export interface DbKeysFolderStateMode {
key: string;
visibleCount?: number;
isExpanded?: boolean;
}
export interface DbKeysTreeModel {
treeKeySeparator: string;
root: DbKeysFolderNodeModel;
dirsByKey: { [key: string]: DbKeysFolderNodeModel };
dirStateByKey: { [key: string]: DbKeysFolderStateMode };
childrenByKey: { [key: string]: DbKeysNodeModel[] };
keyObjectsByKey: { [key: string]: DbKeysNodeModel };
scannedKeys: number;
loadCount: number;
dbsize: number;
cursor: string;
loadedAll: boolean;
// refreshAll?: boolean;
}
export type DbKeysNodeModel = DbKeysLeafNodeModel | DbKeysFolderNodeModel;
export interface DbKeyLoadedModel {
key: string;
type: 'string' | 'hash' | 'set' | 'list' | 'zset' | 'stream' | 'binary' | 'ReJSON-RL';
count?: number;
}
export interface DbKeysLoadResult {
nextCursor: string;
keys: DbKeyLoadedModel[];
dbsize: number;
}
// export type DbKeysLoadFunction = (root: string, limit: number) => Promise<DbKeysLoadResult>;
export type DbKeysChangeModelFunction = (
func: (model: DbKeysTreeModel) => DbKeysTreeModel,
loadNextPage: boolean
) => void;
// function dbKeys_findFolderNode(node: DbKeysNodeModel, root: string) {
// if (node.type != 'dir') {
// return null;
// }
// if (node.root === root) {
// return node;
// }
// for (const child of node.children ?? []) {
// const res = dbKeys_findFolderNode(child, root);
// if (res) {
// return res;
// }
// }
// return null;
// }
// export async function dbKeys_loadKeysFromNode(
// tree: DbKeysTreeModel,
// callingRoot: string,
// separator: string,
// loader: DbKeysLoadFunction
// ): Promise<DbKeysTreeModel> {
// const callingRootNode = tree.dirsByKey[callingRoot];
// if (!callingRootNode) {
// return tree;
// }
// const newItems = await loader(callingRoot, callingRootNode.maxShowCount ?? SHOW_INCREMENT);
// return {
// ...tree,
// childrenByKey: {
// ...tree.childrenByKey,
// [callingRoot]: newItems,
// },
// };
// }
// export async function dbKeys_loadMissing(tree: DbKeysTreeModel, loader: DbKeysLoadFunction): Promise<DbKeysTreeModel> {
// const childrenByKey = { ...tree.childrenByKey };
// const dirsByKey = { ...tree.dirsByKey };
// for (const root in tree.dirsByKey) {
// const dir = tree.dirsByKey[root];
// if (dir.isExpanded && dir.shouldLoadNext) {
// if (!tree.childrenByKey[root] || dir.hasNext) {
// const loadCount = dir.maxShowCount && dir.shouldLoadNext ? dir.maxShowCount + SHOW_INCREMENT : SHOW_INCREMENT;
// const items = await loader(root, loadCount + 1);
// childrenByKey[root] = items.slice(0, loadCount);
// dirsByKey[root] = {
// ...dir,
// shouldLoadNext: false,
// maxShowCount: loadCount,
// hasNext: items.length > loadCount,
// };
// for (const child of items.slice(0, loadCount)) {
// if (child.type == 'dir' && !dirsByKey[child.root]) {
// dirsByKey[child.root] = {
// shouldLoadNext: false,
// maxShowCount: null,
// hasNext: false,
// isExpanded: false,
// type: 'dir',
// level: dir.level + 1,
// root: child.root,
// text: child.text,
// };
// }
// }
// } else {
// dirsByKey[root] = {
// ...dir,
// shouldLoadNext: false,
// };
// }
// }
// }
// return {
// ...tree,
// dirsByKey,
// childrenByKey,
// refreshAll: false,
// };
// }
export function dbKeys_mergeNextPage(tree: DbKeysTreeModel, nextPage: DbKeysLoadResult): DbKeysTreeModel {
const keyObjectsByKey = { ...tree.keyObjectsByKey };
for (const keyObj of nextPage.keys) {
const keyPath = keyObj.key.split(tree.treeKeySeparator);
keyObjectsByKey[keyObj.key] = {
...keyObj,
level: keyPath.length,
text: keyPath[keyPath.length - 1],
sortKey: keyPath[keyPath.length - 1],
keyPath,
parentKey: keyPath.slice(0, -1).join(tree.treeKeySeparator),
};
}
const dirsByKey: { [key: string]: DbKeysFolderNodeModel } = {};
const childrenByKey: { [key: string]: DbKeysNodeModel[] } = {};
dirsByKey[''] = tree.root;
for (const keyObj of Object.values(keyObjectsByKey)) {
const dirPath = keyObj.keyPath.slice(0, -1);
const dirKey = dirPath.join(tree.treeKeySeparator);
let dirDepth = keyObj.keyPath.length - 1;
while (dirDepth > 0) {
const newDirPath = keyObj.keyPath.slice(0, dirDepth);
const newDirKey = newDirPath.join(tree.treeKeySeparator);
if (!dirsByKey[newDirKey]) {
dirsByKey[newDirKey] = {
level: keyObj.level - 1,
keyPath: newDirPath,
parentKey: newDirPath.slice(0, -1).join(tree.treeKeySeparator),
type: 'dir',
key: newDirKey,
text: `${newDirPath[newDirPath.length - 1]}${tree.treeKeySeparator}*`,
sortKey: newDirPath[newDirPath.length - 1],
};
}
dirDepth -= 1;
}
if (!childrenByKey[dirKey]) {
childrenByKey[dirKey] = [];
}
childrenByKey[dirKey].push(keyObj);
}
for (const dirObj of Object.values(dirsByKey)) {
if (dirObj.key == '') {
continue;
}
if (!childrenByKey[dirObj.parentKey]) {
childrenByKey[dirObj.parentKey] = [];
}
childrenByKey[dirObj.parentKey].push(dirObj);
// set key count
dirsByKey[dirObj.key].count = childrenByKey[dirObj.key].length;
}
for (const key in childrenByKey) {
childrenByKey[key] = _sortBy(childrenByKey[key], 'sortKey');
}
return {
...tree,
cursor: nextPage.nextCursor,
dirsByKey,
childrenByKey,
keyObjectsByKey,
scannedKeys: tree.scannedKeys + tree.loadCount,
loadedAll: nextPage.nextCursor == '0',
dbsize: nextPage.dbsize,
};
}
export function dbKeys_markNodeExpanded(tree: DbKeysTreeModel, root: string, isExpanded: boolean): DbKeysTreeModel {
const node = tree.dirStateByKey[root];
return {
...tree,
dirStateByKey: {
...tree.dirStateByKey,
[root]: {
...node,
isExpanded,
},
},
};
}
export function dbKeys_showNextItems(tree: DbKeysTreeModel, root: string): DbKeysTreeModel {
const node = tree.dirStateByKey[root];
return {
...tree,
dirStateByKey: {
...tree.dirStateByKey,
[root]: {
...node,
visibleCount: (node?.visibleCount ?? DB_KEYS_SHOW_INCREMENT) + DB_KEYS_SHOW_INCREMENT,
},
},
};
}
export function dbKeys_createNewModel(treeKeySeparator: string): DbKeysTreeModel {
const root: DbKeysFolderNodeModel = {
level: 0,
type: 'dir',
keyPath: [],
parentKey: '',
key: '',
sortKey: '',
};
return {
treeKeySeparator,
childrenByKey: {},
keyObjectsByKey: {},
dirsByKey: {
'': root,
},
dirStateByKey: {
'': {
key: '',
visibleCount: DB_KEYS_SHOW_INCREMENT,
isExpanded: true,
},
},
scannedKeys: 0,
dbsize: 0,
loadCount: 2000,
cursor: '0',
root,
loadedAll: false,
};
}
export function dbKeys_clearLoadedData(tree: DbKeysTreeModel): DbKeysTreeModel {
return {
...tree,
childrenByKey: {},
keyObjectsByKey: {},
dirsByKey: {
'': tree.root,
},
scannedKeys: 0,
dbsize: 0,
cursor: '0',
loadedAll: false,
};
}
// export function dbKeys_reloadFolder(tree: DbKeysTreeModel, root: string): DbKeysTreeModel {
// return {
// ...tree,
// childrenByKey: _omit(tree.childrenByKey, root),
// dirsByKey: {
// ...tree.dirsByKey,
// [root]: {
// ...tree.dirsByKey[root],
// shouldLoadNext: true,
// hasNext: undefined,
// },
// },
// };
// }
function addFlatItems(tree: DbKeysTreeModel, root: string, res: DbKeysNodeModel[], visitedRoots: string[] = []) {
const item = tree.dirStateByKey[root];
if (!item?.isExpanded) {
return false;
}
const children = tree.childrenByKey[root] || [];
for (const child of children) {
res.push(child);
if (child.type == 'dir') {
if (visitedRoots.includes(child.key)) {
console.warn('Redis: preventing infinite loop for root', child.key);
return false;
}
addFlatItems(tree, child.key, res, [...visitedRoots, root]);
}
}
}
export function dbKeys_getFlatList(tree: DbKeysTreeModel) {
const res: DbKeysNodeModel[] = [];
addFlatItems(tree, '', res);
return res;
}
+1 -1
View File
@@ -24,6 +24,6 @@ export * from './getConnectionLabel';
export * from './detectSqlFilterBehaviour';
export * from './filterBehaviours';
export * from './schemaInfoTools';
export * from './dbKeysLoader';
export * from './redisKeysLoader';
export * from './rowProgressReporter';
export * from './diagramTools';
+328
View File
@@ -0,0 +1,328 @@
import _omit from 'lodash/omit';
import _sortBy from 'lodash/sortBy';
export const DB_KEYS_SHOW_INCREMENT = 100;
export interface RedisNodeModelBase {
text?: string;
sortKey: string;
key: string;
count?: number;
level: number;
keyPath: string[];
parentKey: string;
}
export interface RedisLeafNodeModel extends RedisNodeModelBase {
type: 'string' | 'hash' | 'set' | 'list' | 'zset' | 'stream' | 'binary' | 'ReJSON-RL';
}
export interface RedisFolderNodeModel extends RedisNodeModelBase {
// root: string;
type: 'dir';
// visibleCount?: number;
// isExpanded?: boolean;
}
export interface RedisFolderStateMode {
key: string;
visibleCount?: number;
isExpanded?: boolean;
}
export interface RedisTreeModel {
treeKeySeparator: string;
root: RedisFolderNodeModel;
dirsByKey: { [key: string]: RedisFolderNodeModel };
dirStateByKey: { [key: string]: RedisFolderStateMode };
childrenByKey: { [key: string]: RedisNodeModel[] };
keyObjectsByKey: { [key: string]: RedisNodeModel };
scannedKeys: number;
loadCount: number;
dbsize: number;
cursor: string;
loadedAll: boolean;
// refreshAll?: boolean;
}
export type RedisNodeModel = RedisLeafNodeModel | RedisFolderNodeModel;
export interface RedisLoadedModel {
key: string;
type: 'string' | 'hash' | 'set' | 'list' | 'zset' | 'stream' | 'binary' | 'ReJSON-RL';
count?: number;
}
export interface RedisLoadResult {
nextCursor: string;
keys: RedisLoadedModel[];
dbsize: number;
}
export type RedisChangeModelFunction = (func: (model: RedisTreeModel) => RedisTreeModel, loadNextPage: boolean) => void;
export function redis_mergeNextPage(tree: RedisTreeModel, nextPage: RedisLoadResult): RedisTreeModel {
const keyObjectsByKey = { ...tree.keyObjectsByKey };
for (const keyObj of nextPage.keys) {
const keyPath = keyObj.key.split(tree.treeKeySeparator);
keyObjectsByKey[keyObj.key] = {
...keyObj,
level: keyPath.length,
text: keyPath[keyPath.length - 1],
sortKey: keyPath[keyPath.length - 1],
keyPath,
parentKey: keyPath.slice(0, -1).join(tree.treeKeySeparator),
};
}
const dirsByKey: { [key: string]: RedisFolderNodeModel } = {};
const childrenByKey: { [key: string]: RedisNodeModel[] } = {};
dirsByKey[''] = tree.root;
for (const keyObj of Object.values(keyObjectsByKey)) {
const dirPath = keyObj.keyPath.slice(0, -1);
const dirKey = dirPath.join(tree.treeKeySeparator);
let dirDepth = keyObj.keyPath.length - 1;
while (dirDepth > 0) {
const newDirPath = keyObj.keyPath.slice(0, dirDepth);
const newDirKey = newDirPath.join(tree.treeKeySeparator);
if (!dirsByKey[newDirKey]) {
dirsByKey[newDirKey] = {
level: keyObj.level - 1,
keyPath: newDirPath,
parentKey: newDirPath.slice(0, -1).join(tree.treeKeySeparator),
type: 'dir',
key: newDirKey,
text: `${newDirPath[newDirPath.length - 1]}${tree.treeKeySeparator}*`,
sortKey: newDirPath[newDirPath.length - 1],
};
}
dirDepth -= 1;
}
if (!childrenByKey[dirKey]) {
childrenByKey[dirKey] = [];
}
childrenByKey[dirKey].push(keyObj);
}
for (const dirObj of Object.values(dirsByKey)) {
if (dirObj.key == '') {
continue;
}
if (!childrenByKey[dirObj.parentKey]) {
childrenByKey[dirObj.parentKey] = [];
}
childrenByKey[dirObj.parentKey].push(dirObj);
// set key count
dirsByKey[dirObj.key].count = childrenByKey[dirObj.key].length;
}
for (const key in childrenByKey) {
childrenByKey[key] = _sortBy(childrenByKey[key], 'sortKey');
}
return {
...tree,
cursor: nextPage.nextCursor,
dirsByKey,
childrenByKey,
keyObjectsByKey,
scannedKeys: tree.scannedKeys + tree.loadCount,
loadedAll: nextPage.nextCursor == '0',
dbsize: nextPage.dbsize,
};
}
export function redis_markNodeExpanded(tree: RedisTreeModel, root: string, isExpanded: boolean): RedisTreeModel {
const node = tree.dirStateByKey[root];
return {
...tree,
dirStateByKey: {
...tree.dirStateByKey,
[root]: {
...node,
isExpanded,
},
},
};
}
export function redis_showNextItems(tree: RedisTreeModel, root: string): RedisTreeModel {
const node = tree.dirStateByKey[root];
return {
...tree,
dirStateByKey: {
...tree.dirStateByKey,
[root]: {
...node,
visibleCount: (node?.visibleCount ?? DB_KEYS_SHOW_INCREMENT) + DB_KEYS_SHOW_INCREMENT,
},
},
};
}
export function redis_createNewModel(treeKeySeparator: string): RedisTreeModel {
const root: RedisFolderNodeModel = {
level: 0,
type: 'dir',
keyPath: [],
parentKey: '',
key: '',
sortKey: '',
};
return {
treeKeySeparator,
childrenByKey: {},
keyObjectsByKey: {},
dirsByKey: {
'': root,
},
dirStateByKey: {
'': {
key: '',
visibleCount: DB_KEYS_SHOW_INCREMENT,
isExpanded: true,
},
},
scannedKeys: 0,
dbsize: 0,
loadCount: 2000,
cursor: '0',
root,
loadedAll: false,
};
}
export function redis_clearLoadedData(tree: RedisTreeModel): RedisTreeModel {
return {
...tree,
childrenByKey: {},
keyObjectsByKey: {},
dirsByKey: {
'': tree.root,
},
scannedKeys: 0,
dbsize: 0,
cursor: '0',
loadedAll: false,
};
}
function addFlatItems(tree: RedisTreeModel, root: string, res: RedisNodeModel[], visitedRoots: string[] = []) {
const item = tree.dirStateByKey[root];
if (!item?.isExpanded) {
return false;
}
const children = tree.childrenByKey[root] || [];
for (const child of children) {
res.push(child);
if (child.type == 'dir') {
if (visitedRoots.includes(child.key)) {
console.warn('Redis: preventing infinite loop for root', child.key);
return false;
}
addFlatItems(tree, child.key, res, [...visitedRoots, root]);
}
}
}
export function redis_getFlatList(tree: RedisTreeModel) {
const res: RedisNodeModel[] = [];
addFlatItems(tree, '', res);
return res;
}
export interface SupportedRedisKeyType {
name: string;
label: string;
dbKeyFields: {
name: string;
cols?: number;
label?: string;
placeholder?: string;
}[];
dbKeyFieldsForGrid?: {
name: string;
cols?: number;
label?: string;
}[];
keyColumn?: string;
showItemList?: boolean;
showGeneratedId?: boolean;
}
export const supportedRedisKeyTypes: SupportedRedisKeyType[] = [
{
name: 'string',
label: 'String',
dbKeyFields: [{ name: 'value' }],
},
{
name: 'list',
label: 'List',
dbKeyFields: [{ name: 'value', cols: 12 }],
showItemList: true,
},
{
name: 'set',
label: 'Set',
dbKeyFields: [{ name: 'value', cols: 12 }],
keyColumn: 'value',
showItemList: true,
},
{
name: 'zset',
label: 'Sorted Set',
dbKeyFields: [
{ name: 'member', cols: 8 },
{ name: 'score', cols: 4 },
],
keyColumn: 'member',
showItemList: true,
},
{
name: 'hash',
label: 'Hash',
dbKeyFields: [
{ name: 'key', cols: 3, label: 'Field' },
{ name: 'value', cols: 7 },
{ name: 'ttl', cols: 2, label: 'TTL' },
],
keyColumn: 'key',
showItemList: true,
},
{
name: 'stream',
label: 'Stream',
dbKeyFields: [
{ name: 'field', cols: 6 },
{ name: 'value', cols: 6 },
],
dbKeyFieldsForGrid: [
{ name: 'id', cols: 6 },
{ name: 'value', cols: 6 },
],
keyColumn: 'id',
showItemList: true,
showGeneratedId: true,
},
{
name: 'json',
label: 'JSON',
dbKeyFields: [{ name: 'value' }],
},
];
export function findSupportedRedisKeyType(type: string): SupportedRedisKeyType | undefined {
return supportedRedisKeyTypes.find(t => t.name === type);
}
+3 -2
View File
@@ -15,6 +15,8 @@ import {
} from './dbinfo';
import { FilterBehaviour } from './filter-type';
export type EngineDriverIcon = string | { light: string; dark?: string };
export interface StreamOptions {
recordset: (columns) => void;
row: (row) => void;
@@ -240,7 +242,6 @@ export interface EngineDriver<TClient = any, TDataBase = any> extends FilterBeha
databaseEngineTypes: string[];
editorMode?: string;
readOnlySessions: boolean;
supportedKeyTypes: SupportedDbKeyType[];
dataEditorTypesBehaviour: DataEditorTypesBehaviour;
supportsDatabaseUrl?: boolean;
supportsDatabaseBackup?: boolean;
@@ -262,7 +263,7 @@ export interface EngineDriver<TClient = any, TDataBase = any> extends FilterBeha
collectionPluralLabel?: string;
collectionNameLabel?: string;
newCollectionFormParams?: any[];
icon?: any;
icon?: EngineDriverIcon;
supportedCreateDatabase?: boolean;
showConnectionField?: (
+1
View File
@@ -28,6 +28,7 @@ export interface ThemeDefinition {
isBuiltInTheme?: boolean;
themeVariables?: { [key: string]: string };
themePublicCloudPath?: string;
editorTheme?: string;
}
export interface PluginDefinition {
+1 -1
View File
@@ -1,5 +1,5 @@
{
"version": "6.0.0-alpha.1",
"version": "7.0.0-alpha.1",
"name": "dbgate-types",
"homepage": "https://dbgate.org/",
"repository": {
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "dbgate-web",
"version": "6.0.0-alpha.1",
"version": "7.0.0-alpha.1",
"scripts": {
"build": "yarn build:index && rollup -c",
"dev": "yarn build:index && cross-env API_URL=http://localhost:3000 rollup -c -w",
@@ -33,11 +33,11 @@
"chartjs-adapter-moment": "^1.0.0",
"chartjs-plugin-datalabels": "^2.2.0",
"cross-env": "^7.0.3",
"dbgate-datalib": "^6.0.0-alpha.1",
"dbgate-datalib": "^7.0.0-alpha.1",
"dbgate-query-splitter": "^4.11.9",
"dbgate-sqltree": "^6.0.0-alpha.1",
"dbgate-tools": "^6.0.0-alpha.1",
"dbgate-types": "^6.0.0-alpha.1",
"dbgate-sqltree": "^7.0.0-alpha.1",
"dbgate-tools": "^7.0.0-alpha.1",
"dbgate-types": "^7.0.0-alpha.1",
"diff": "^5.0.0",
"diff2html": "^3.4.13",
"file-selector": "^0.2.4",
+3 -16
View File
@@ -91,19 +91,7 @@ body {
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);
background-color: var(--theme-content-background);
}
.col-10 {
@@ -290,13 +278,12 @@ textarea[disabled] {
}
.ace_gutter-cell.ace-gutter-sql-run:hover {
background-color: var(--theme-bg-2);
background-color: var(--theme-datagrid-cell-background-alt);
}
.ace_gutter-cell.ace-gutter-current-part {
/* background-color: var(--theme-bg-2); */
font-weight: bold;
color: var(--theme-font-hover);
color: var(--theme-generic-font-hover);
}
input[type='checkbox'] {
@@ -41,7 +41,7 @@
left: 0;
right: 0;
bottom: 0;
background: var(--theme-bg-selected);
background: var(--theme-table-selected-background);
align-items: center;
justify-content: space-around;
z-index: 1000;
+1 -1
View File
@@ -155,7 +155,7 @@
height: var(--dim-toolbar-height);
left: 0;
right: 0;
background: var(--theme-bg-1);
background: var(--theme-toolstrip-background);
}
.left-splitter {
@@ -1,23 +1,29 @@
<script lang="ts" context="module">
import { cloudConnectionsStore } from '../stores';
import { cloudConnectionsStore, DEFAULT_CONNECTION_SEARCH_SETTINGS } from '../stores';
import { apiCall } from '../utility/api';
import AppObjectCore from './AppObjectCore.svelte';
export const extractKey = data => data.cntid;
export const createMatcher =
filter =>
({ name }) =>
filterName(filter, name);
(filter, cfg = DEFAULT_CONNECTION_SEARCH_SETTINGS) =>
props => {
const { conid, name } = props;
const databases = getLocalStorage(`database_list_${conid}`) || [];
return filterNameCompoud(filter, [name], cfg.database ? databases.map(x => x.name) : []);
};
</script>
<script lang="ts">
import { filterName, getConnectionLabel } from 'dbgate-tools';
import { filterNameCompoud } from 'dbgate-tools';
import ConnectionAppObject, { openConnection } from './ConnectionAppObject.svelte';
import { _t } from '../translations';
import openNewTab from '../utility/openNewTab';
import { showModal } from '../modals/modalTools';
import ConfirmModal from '../modals/ConfirmModal.svelte';
import SavedFileAppObject from './SavedFileAppObject.svelte';
import { getLocalStorage } from '../utility/storageCache';
export let data;
export let passProps;
@@ -143,6 +143,8 @@
import { getConnectionClickActionSetting } from '../settings/settingsTools';
import { _t } from '../translations';
import { isProApp } from '../utility/proTools';
import { currentThemeType } from '../plugins/themes';
import { getDriverIcon } from '../utility/driverIcons';
export let data;
export let passProps;
@@ -152,6 +154,8 @@
let extInfo = null;
let engineStatusIcon = null;
let engineStatusTitle = null;
let driverIcon = null;
let connectionIcon = null;
$: isPinned = data.singleDatabase && !!$pinnedDatabases.find(x => x?.connection?._id == data?._id);
@@ -432,13 +436,17 @@
$: apps = useAllApps();
$: driver = $extensions.drivers.find(x => x.engine == data.engine);
$: driverIcon = getDriverIcon(driver, $currentThemeType);
$: connectionIcon =
driverIcon ||
(data._id.startsWith('cloud://') ? 'img cloud-connection' : data.singleDatabase ? 'img database' : 'img server');
</script>
<AppObjectCore
{...$$restProps}
{data}
title={getConnectionLabel(data, { showUnsaved: true })}
icon={driver?.icon || (data._id.startsWith('cloud://') ? 'img cloud-connection' : data.singleDatabase ? 'img database' : 'img server')}
icon={connectionIcon}
isBold={data.singleDatabase
? $currentDatabase?.connection?._id == data._id && $currentDatabase?.name == data.defaultDatabase
: $currentDatabase?.connection?._id == data._id}
+10 -3
View File
@@ -1,7 +1,6 @@
<script lang="ts" context="module">
import DatabaseAppObject from './DatabaseAppObject.svelte';
import DatabaseObjectAppObject from './DatabaseObjectAppObject.svelte';
import { extensions } from '../stores';
export const extractKey = data => {
if (data.objectTypeField) {
@@ -27,11 +26,19 @@
<script lang="ts">
import _, { values } from 'lodash';
import { draggedPinnedObject, pinnedDatabases, pinnedTables } from '../stores';
import { draggedPinnedObject, extensions, pinnedDatabases, pinnedTables } from '../stores';
import { getConnectionLabel } from 'dbgate-tools';
import { currentThemeType } from '../plugins/themes';
import { getDriverIcon } from '../utility/driverIcons';
export let data;
export let passProps;
let pinnedDriver = null;
let pinnedDriverIcon = null;
$: pinnedDriver = $extensions?.drivers?.find(x => x.engine == data?.connection?.engine);
$: pinnedDriverIcon = getDriverIcon(pinnedDriver, $currentThemeType);
</script>
{#if data}
@@ -73,7 +80,7 @@
$draggedPinnedObject = null;
}}
passExtInfo={getConnectionLabel(data.connection)}
passIcon={$extensions.drivers.find(x => x.engine == data.connection.engine)?.icon}
passIcon={pinnedDriverIcon}
passColorMark={passProps?.connectionColorFactory && passProps?.connectionColorFactory({ conid: data.connection._id })}
/>
{/if}
@@ -3,8 +3,12 @@
import SubDatabaseList from './SubDatabaseList.svelte';
export let data;
import { getLocalStorage } from '../utility/storageCache';
</script>
{#if data.conid && $cloudConnectionsStore[data.conid]}
<SubDatabaseList {...$$props} data={$cloudConnectionsStore[data.conid]} />
{:else if data.conid && getLocalStorage(`database_list_${data.conid}`)}
<SubDatabaseList {...$$props} data={{ _id: data.conid }} />
{/if}
@@ -45,7 +45,7 @@
}
.outer.useBorder {
background-color: var(--theme-inlinebutton-bordered-background);
background: var(--theme-inlinebutton-bordered-background);
border: var(--theme-inlinebutton-bordered-border);
}
@@ -1,8 +1,10 @@
<script lang="ts">
import { get_current_component } from 'svelte/internal';
import createActivator, { isComponentActiveStore } from '../utility/createActivator';
import { useSettings } from '../utility/metadataLoaders';
const thisInstance = get_current_component();
const settings = useSettings();
export let showAlways = false;
export const activator = showAlways ? null : createActivator('ToolStripContainer', true);
@@ -13,13 +15,19 @@
export let scrollContent = false;
export let hideToolStrip = false;
export let toolstripPosition = 'top'; // 'top' | 'bottom'
export let toolstripPosition = 'auto'; // 'top' | 'bottom'
$: isComponentActive = showAlways || ($isComponentActiveStore('ToolStripContainer', thisInstance) && !hideToolStrip);
$: realToolstripPosition =
toolstripPosition == 'auto' ? ($settings?.['settings.toolbarPosition'] ?? 'top') : toolstripPosition;
$: realToolstripPositionFixed =
realToolstripPosition == 'top' || realToolstripPosition == 'bottom' ? realToolstripPosition : 'top';
</script>
<div class="wrapper">
{#if isComponentActive && toolstripPosition === 'top'}
{#if isComponentActive && realToolstripPositionFixed === 'top'}
<div class="toolstrip">
<slot name="toolstrip" />
</div>
@@ -29,7 +37,7 @@
<slot />
</div>
{#if isComponentActive && toolstripPosition === 'bottom'}
{#if isComponentActive && realToolstripPositionFixed === 'bottom'}
<div class="toolstrip">
<slot name="toolstrip" />
</div>
@@ -227,6 +227,7 @@
function getJsonParsedValue(value) {
if (editorTypes?.explicitDataType) return null;
if (_.isPlainObject(value) || _.isArray(value)) return value;
if (!isJsonLikeLongString(value)) return null;
return safeJsonParse(value);
}
@@ -23,7 +23,7 @@
testEnabled: () => getVisibleCommandPalette() != 'database',
});
function extractDbItems(db, dbConnectionInfo, connectionList, $extensions) {
function extractDbItems(db, dbConnectionInfo, connectionList, $extensions, currentThemeType) {
const objectList = _.flatten(
['tables', 'collections', 'views', 'matviews', 'procedures', 'functions'].map(objectTypeField =>
_.sortBy(
@@ -44,7 +44,8 @@
const databases = getLocalStorage(`database_list_${conid}`) || [];
const driver = findEngineDriver(connection, $extensions);
const connectionIcon = driver?.icon || 'img database';
const driverIcon = getDriverIcon(driver, currentThemeType);
const connectionIcon = driverIcon || 'img database';
for (const db of databases) {
databaseList.push({
@@ -87,6 +88,8 @@
import registerCommand from './registerCommand';
import { formatKeyText, switchCurrentDatabase } from '../utility/common';
import { _tval, __t, _t } from '../translations';
import { getDriverIcon } from '../utility/driverIcons';
import { currentThemeType } from '../plugins/themes';
let domInput;
let filter = '';
@@ -117,7 +120,7 @@
.filter(
filter,
($visibleCommandPalette == 'database'
? extractDbItems($databaseInfo, { conid, database }, $connectionList, $extensions)
? extractDbItems($databaseInfo, { conid, database }, $connectionList, $extensions, $currentThemeType)
: parentCommand
? parentCommand.getSubCommands()
: sortedComands
@@ -172,12 +172,6 @@
align-self: center;
font-size: 18px;
}
/* .resizer {
background-color: var(--theme-border);
width: 2px;
cursor: col-resize;
z-index: 1;
} */
.grouping {
color: var(--theme-datagrid-cell-foreground-value-green);
white-space: nowrap;
@@ -305,6 +305,6 @@
}
.selectwrap {
border-bottom: 1px solid var(--theme-border);
border-bottom: var(--theme-table-border);
}
</style>
@@ -186,11 +186,11 @@
overflow: hidden;
}
td.isFrameSelected {
outline: 3px solid var(--theme-bg-selected);
outline: 3px solid var(--theme-table-selected-background);
outline-offset: -3px;
}
td.isAutofillSelected {
outline: 3px solid var(--theme-bg-selected);
outline: 3px solid var(--theme-table-selected-background);
outline-offset: -3px;
}
td.isFocusedColumn {
@@ -235,7 +235,7 @@
.autoFillMarker {
width: 8px;
height: 8px;
background: var(--theme-bg-selected-point);
background: var(--theme-datagrid-selected-point-marker);
position: absolute;
right: 0px;
bottom: 0px;
@@ -268,7 +268,7 @@
}
td.isMissingOverlayField {
background: var(--theme-bg-orange);
background: var(--theme-datagrid-modified-cell-background);
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAEElEQVQImWNgIAX8x4KJBAD+agT8INXz9wAAAABJRU5ErkJggg==');
background-repeat: repeat-x;
+23 -23
View File
@@ -599,8 +599,8 @@
export function addNewColumn() {
showModal(InputTextModal, {
value: '',
label: 'Column name',
header: 'Add new column',
label: _t('datagrid.columnName', { defaultMessage: 'Column name' }),
header: _t('datagrid.addNewColumn', { defaultMessage: 'Add new column' }),
onConfirm: name => {
display.addDynamicColumn(name);
tick().then(() => {
@@ -744,19 +744,19 @@
export function openSelectionInMap() {
const selection = getCellsPublished(selectedCells);
if (!selectionCouldBeShownOnMap(selection)) {
showModal(ErrorMessageModal, { message: 'There is nothing to be shown on map' });
showModal(ErrorMessageModal, { message: _t('datagrid.mapError.noSelection', { defaultMessage: 'There is nothing to be shown on map' }) });
return;
}
const geoJson = createGeoJsonFromSelection(selection);
if (!geoJson) {
showModal(ErrorMessageModal, { message: 'There is nothing to be shown on map' });
showModal(ErrorMessageModal, { message: _t('datagrid.mapError.noGeoJson', { defaultMessage: 'There is nothing to be shown on map' }) });
return;
}
openNewTab(
{
title: 'Map',
title: _t('tabs.map', { defaultMessage: 'Map' }),
icon: 'img map',
tabComponent: 'MapTab',
},
@@ -806,7 +806,7 @@
const electron = getElectron();
const files = await electron.showOpenDialog({
properties: ['showHiddenFiles', 'openFile'],
filters: [{ name: 'All Files', extensions: ['*'] }],
filters: [{ name: _t('common.files.allFiles', { defaultMessage: 'All Files' }), extensions: ['*'] }],
});
const file = files && files[0];
if (file) {
@@ -1955,7 +1955,7 @@
isProApp() &&
hasPermission('dbops/charts') &&
onOpenChart && {
text: 'Open chart',
text: _t('datagrid.openChart', { defaultMessage: 'Open chart' }),
onClick: () => onOpenChart(),
},
{ command: 'dataGrid.generateSqlFromData' },
@@ -1991,17 +1991,17 @@
{#if !display || (!isDynamicStructure && (!columns || columns.length == 0))}
{#if $databaseStatus?.name == 'pending' || $databaseStatus?.name == 'checkStructure' || $databaseStatus?.name == 'loadStructure'}
<LoadingInfo wrapper message="Waiting for structure" />
<LoadingInfo wrapper message={_t('datagrid.structure.waiting', { defaultMessage: 'Waiting for structure' })} />
{:else}
<ErrorInfo alignTop message="No structure was loaded, probably table doesn't exist in current database" />
<ErrorInfo alignTop message={_t('datagrid.structure.notLoaded', { defaultMessage: "No structure was loaded, probably table doesn't exist in current database" })} />
{/if}
{:else if errorMessage}
<div>
<ErrorInfo message={errorMessage} alignTop />
<FormStyledButton value="Reset filter" on:click={() => display.clearFilters()} />
<FormStyledButton value="Reset view" on:click={() => display.resetConfig()} />
<FormStyledButton value={_t('datagrid.resetFilter', { defaultMessage: 'Reset filter' })} on:click={() => display.clearFilters()} />
<FormStyledButton value={_t('datagrid.resetView', { defaultMessage: 'Reset view' })} on:click={() => display.resetConfig()} />
{#if onOpenQueryOnError ?? onOpenQuery}
<FormStyledButton value="Open Query" on:click={() => (onOpenQueryOnError ?? onOpenQuery)()} />
<FormStyledButton value={_t('datagrid.openQuery', { defaultMessage: 'Open Query' })} on:click={() => (onOpenQueryOnError ?? onOpenQuery)()} />
{/if}
</div>
{:else if isDynamicStructure && isLoadedAll && grider?.rowCount == 0}
@@ -2009,17 +2009,17 @@
<ErrorInfo
alignTop
message={grider.editable
? 'No rows loaded, check filter or add new documents. You could copy documents from other collections/tables with Copy advanved/Copy as JSON command.'
: 'No rows loaded'}
? _t('datagrid.noRows.withEditable', { defaultMessage: 'No rows loaded, check filter or add new documents. You could copy documents from other collections/tables with Copy advanved/Copy as JSON command.' })
: _t('datagrid.noRows', { defaultMessage: 'No rows loaded' })}
/>
{#if display.filterCount > 0}
<FormStyledButton value="Reset filter" on:click={() => display.clearFilters()} />
<FormStyledButton value={_t('datagrid.resetFilter', { defaultMessage: 'Reset filter' })} on:click={() => display.clearFilters()} />
{/if}
{#if grider.editable}
<FormStyledButton value="Add document" on:click={addJsonDocument} />
<FormStyledButton value={_t('datagrid.addDocument', { defaultMessage: 'Add document' })} on:click={addJsonDocument} />
{/if}
{#if onOpenQuery}
<FormStyledButton value="Open Query" on:click={() => onOpenQuery()} />
<FormStyledButton value={_t('datagrid.openQuery', { defaultMessage: 'Open Query' })} on:click={() => onOpenQuery()} />
{/if}
</div>
{:else if grider.errors && grider.errors.length > 0}
@@ -2205,16 +2205,16 @@
{#if !isDynamicStructure && isLoadedAll && grider?.rowCount == 0}
<div class="no-rows-info ml-2">
<div class="mb-3">
<ErrorInfo alignTop message="No rows loaded" icon="img info" />
<ErrorInfo alignTop message={_t('datagrid.noRows', { defaultMessage: 'No rows loaded' })} icon="img info" />
</div>
{#if display.filterCount > 0}
<FormStyledButton value="Reset filter" on:click={() => display.clearFilters()} />
<FormStyledButton value={_t('datagrid.resetFilter', { defaultMessage: 'Reset filter' })} on:click={() => display.clearFilters()} />
{/if}
{#if grider.editable}
<FormStyledButton value="Add row" on:click={insertNewRow} />
<FormStyledButton value={_t('datagrid.addRow', { defaultMessage: 'Add row' })} on:click={insertNewRow} />
{/if}
{#if onOpenQuery}
<FormStyledButton value="Open Query" on:click={() => onOpenQuery()} />
<FormStyledButton value={_t('datagrid.openQuery', { defaultMessage: 'Open Query' })} on:click={() => onOpenQuery()} />
{/if}
</div>
{/if}
@@ -2264,7 +2264,7 @@
</div>
{:else if allRowCount != null && multipleGridsOnTab}
<div class="row-count-label">
Rows: {allRowCount.toLocaleString()}
{_t('datagrid.rows', { defaultMessage: 'Rows' })}: {allRowCount.toLocaleString()}
</div>
{/if}
@@ -2273,7 +2273,7 @@
{/if}
{#if !tabControlHiddenTab && !multipleGridsOnTab && allRowCount != null}
<StatusBarTabItem text={`Rows: ${allRowCount.toLocaleString()}`} />
<StatusBarTabItem text={`${_t('datagrid.rows', { defaultMessage: 'Rows' })}: ${allRowCount.toLocaleString()}`} />
{/if}
</div>
{/if}
@@ -112,10 +112,10 @@
list-style: none;
margin: 0;
padding: 0;
background-color: var(--theme-bg-alt);
background-color: var(--theme-toolstrip-button-background);
max-height: 150px;
overflow: auto;
box-shadow: 0 1px 10px 1px var(--theme-bg-inv-3);
box-shadow: var(--theme-input-inplace-select-shadow);
}
.value {
@@ -124,7 +124,7 @@
left: 0;
z-index: 20;
min-height: 17px;
background-color: var(--theme-bg-0);
background-color: var(--theme-toolstrip-button-background);
height: 100%;
width: calc(100% - 4px);
padding: 0 2px;
@@ -146,12 +146,12 @@
label {
padding: 2px 3px;
border-bottom: 1px solid var(--theme-border);
border-bottom: var(--theme-toolstrip-button-border);
display: block;
min-height: 16px;
}
label:hover {
background-color: var(--theme-bg-hover);
background-color: var(--theme-toolstrip-button-background-hover);
}
</style>
@@ -5,6 +5,9 @@
import { apiCall } from '../utility/api';
import createRef from '../utility/createRef';
import FontIcon from '../icons/FontIcon.svelte';
import { supportedRedisKeyTypes } from 'dbgate-tools';
import uuidv1 from 'uuid/v1';
import _ from 'lodash';
export let conid;
export let database;
@@ -45,7 +48,7 @@
if (keyInfo.keyColumn && newRows.find(x => x[keyInfo.keyColumn] == row[keyInfo.keyColumn])) {
continue;
}
newRows.push({ rowNumber: newRows.length + 1, ...row });
newRows.push({ rowNumber: newRows.length, editorRowId: uuidv1(), ...row });
}
rows = newRows;
@@ -63,10 +66,10 @@
}
$: {
if (onChangeSelected && rows[selectedIndex]) {
if (onChangeSelected && displayRows[selectedIndex]) {
if (oldIndexRef.get() != selectedIndex) {
oldIndexRef.set(selectedIndex);
onChangeSelected(rows[selectedIndex]);
onChangeSelected(displayRows[selectedIndex]);
}
}
}
@@ -79,22 +82,20 @@
});
$: displayRows = createDisplayRows(rows, changeSetRedis, keyInfo, modifyRow);
function createDisplayRows(sourceRows, changeSet, keyInfoParam, modifyRowFunc) {
let result = modifyRowFunc ? sourceRows.map(row => modifyRowFunc(row)) : sourceRows;
// Mark deleted rows and add inserted rows from changeset
if (changeSet && keyInfoParam) {
const existingChange = changeSet.changes.find(
c => c.key === keyInfoParam.key && c.type === keyInfoParam.type
);
const existingChange = changeSet.changes.find(c => c.key === keyInfoParam.key && c.type === keyInfoParam.type);
if (existingChange) {
// Mark existing rows as deleted if they're in the deletes array
if (existingChange.deletes) {
result = result.map(row => {
let isDeleted = false;
if (keyInfoParam.type === 'hash') {
isDeleted = existingChange.deletes.includes(row.key);
} else if (keyInfoParam.type === 'set') {
@@ -104,41 +105,57 @@
} else if (keyInfoParam.type === 'stream') {
isDeleted = existingChange.deletes.includes(row.id);
}
return isDeleted ? { ...row, __isDeleted: true } : row;
});
}
// Add inserted rows from changeset
if (existingChange.inserts) {
const insertedRows = existingChange.inserts.map((insert, index) => {
let row = {
rowNumber: result.length + index + 1,
__isAdded: true
};
// Mark rows updated in the changeset
if (existingChange.updates) {
result = result.map(row => {
let isChanged = false;
if (keyInfoParam.type === 'hash') {
row.key = insert.key || '';
row.value = insert.value || '';
if (insert.ttl !== undefined) row.TTL = insert.ttl;
} else if (keyInfoParam.type === 'list' || keyInfoParam.type === 'set') {
row.value = insert.value || '';
const originalKey = row._originalKey || row.key;
isChanged = existingChange.updates.some(update => update.originalKey === originalKey);
} else if (keyInfoParam.type === 'list') {
isChanged = existingChange.updates.some(update => update.index === row.rowNumber);
} else if (keyInfoParam.type === 'zset') {
row.member = insert.member || '';
row.score = insert.score || '';
isChanged = existingChange.updates.some(update => update.member === row.member);
} else if (keyInfoParam.type === 'stream') {
row.id = insert.id || '';
row.value = insert.value || '';
isChanged = existingChange.updates.some(update => update.id === row.id);
}
return row;
return isChanged ? { ...row, __isChanged: true } : row;
});
result = [...result, ...insertedRows];
}
// Add inserted rows from changeset
if (existingChange.inserts?.length > 0) {
if (existingChange.type == 'stream') {
result = [
...result,
{
__isAdded: true,
rowNumber: result.length,
id: existingChange.generatedId || '*',
value: JSON.stringify(_.fromPairs(existingChange.inserts.map(i => [i.field, i.value]))),
},
];
} else {
result = [
...result,
...existingChange.inserts.map((insert, index) => ({
...insert,
rowNumber: result.length + index,
__isAdded: true,
})),
];
}
}
}
}
return result;
}
@@ -149,50 +166,66 @@
onRemoveItem(row);
}
}
$: keyType = supportedRedisKeyTypes.find(t => t.name === keyInfo?.type);
</script>
<ScrollableTableControl
columns={[
{
fieldName: 'rowNumber',
header: 'num',
width: '60px',
},
...keyInfo.keyType.dbKeyFields.map(column => ({
fieldName: column.name,
header: column.name,
})),
...(shouldShowRemoveColumn ? [{
fieldName: '__remove',
header: '',
width: '30px',
slot: 0
}] : []),
]}
rows={displayRows}
onLoadNext={isLoadedAll ? null : loadNextRows}
selectable
singleLineRow
bind:selectedIndex
>
<div slot="0" let:row>
<button
class="delete-button"
on:click={() => handleRemoveItem(row)}
>
<FontIcon icon="icon delete" />
</button>
</div>
</ScrollableTableControl>
<div class="table-wrapper">
<ScrollableTableControl
columns={[
{
fieldName: 'rowNumber',
header: 'num',
width: '60px',
slot: 1,
},
...(keyType.dbKeyFieldsForGrid ?? keyType.dbKeyFields).map(column => ({
fieldName: column.name,
header: column.name,
})),
...(shouldShowRemoveColumn
? [
{
fieldName: '__remove',
header: '',
width: '30px',
slot: 0,
},
]
: []),
]}
rows={displayRows}
onLoadNext={isLoadedAll ? null : loadNextRows}
selectable
singleLineRow
bind:selectedIndex
disableFocusOutline
>
<div slot="0" let:row>
<button class="delete-button" on:click={() => handleRemoveItem(row)}>
<FontIcon icon="icon delete" />
</button>
</div>
<div slot="1" let:row>
{row['rowNumber'] + 1}
</div>
</ScrollableTableControl>
</div>
<style>
:global(tr.isDeleted td) {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAEElEQVQImWNgIAX8x4KJBAD+agT8INXz9wAAAABJRU5ErkJggg==') !important;
background-repeat: repeat-x !important;
background-position: 50% 50% !important;
opacity: 0.7;
.table-wrapper {
display: flex;
flex-direction: column;
overflow: auto;
padding: 8px 16px;
width: 100%;
}
.table-wrapper :global(.wrapper) {
position: relative !important;
height: 100%;
}
.delete-button {
background: none;
border: none;
@@ -200,7 +233,7 @@
padding: 4px;
color: var(--theme-generic-font-grayed);
}
.delete-button:hover {
color: var(--theme-generic-font-hover);
}
@@ -1,53 +0,0 @@
<script lang="ts">
import _ from 'lodash';
import DbKeyValueDetail from './DbKeyValueDetail.svelte';
export let dbKeyFields;
export let item;
export let onChangeItem = null;
export let keyColumn = null;
$: console.log('DbKeyItemEdit', { item, dbKeyFields, keyColumn, onChangeItem: !!onChangeItem });
function getValueAsString(value) {
if (value === null || value === undefined) return undefined;
if (typeof value === 'string') return value;
if (typeof value === 'number') return String(value);
return JSON.stringify(value);
}
</script>
<div class="props">
{#each dbKeyFields as column}
<div class="field-wrapper">
<DbKeyValueDetail
value={getValueAsString(item?.[column.name])}
columnTitle={_.startCase(column.name)}
onChangeValue={onChangeItem && column.name !== keyColumn
? value => {
onChangeItem?.({
...item,
[column.name]: value,
});
}
: null}
/>
</div>
{/each}
</div>
<style>
.props {
flex: 1;
gap: 10px;
padding: 10px;
overflow: hidden;
}
.field-wrapper {
flex: 1;
min-width: 0;
overflow: hidden;
max-height: 100px;
}
</style>
@@ -1,190 +0,0 @@
<script lang="ts">
import _ from 'lodash';
import TextField from '../forms/TextField.svelte';
import FormFieldTemplateLarge from '../forms/FormFieldTemplateLarge.svelte';
import FontIcon from '../icons/FontIcon.svelte';
export let dbKeyFields;
export let item;
export let onChangeItem = null;
export let keyColumn = null;
let records = [{ key: '', value: '', ttl: '' }];
let lastItem = null;
$: if (item !== lastItem) {
if (item?.records && Array.isArray(item.records)) {
records = [...item.records];
} else if (!item) {
records = [{ key: '', value: '', ttl: '' }];
}
lastItem = item;
}
$: console.log('DbKeyItemEdit', { item, dbKeyFields, keyColumn, onChangeItem: !!onChangeItem });
function getValueAsString(value) {
if (value === null || value === undefined) return '';
if (typeof value === 'string') return value;
if (typeof value === 'number') return String(value);
return JSON.stringify(value);
}
function handleFieldChange(index, fieldName, value) {
records = records.map((record, idx) => (idx === index ? { ...record, [fieldName]: value } : record));
if (onChangeItem && fieldName !== keyColumn) {
onChangeItem?.({
...item,
records: records,
});
}
}
function addRecord() {
records = [...records, { key: '', value: '', ttl: '' }];
if (onChangeItem) {
onChangeItem({
...item,
records: records,
});
}
}
</script>
<div class="container">
{#each records as record, index}
<div class="props flex">
<div class="field-wrapper col-3">
<FormFieldTemplateLarge label="Key" type="text" noMargin>
<TextField
value={record.key}
on:change={e => {
if (e.target['value'] != record.key) {
handleFieldChange(index, 'key', e.target['value']);
}
}}
disabled={keyColumn === 'key'}
/>
</FormFieldTemplateLarge>
</div>
<div class="field-wrapper col-7">
<FormFieldTemplateLarge label="Value" type="text" noMargin>
<TextField
value={record.value}
on:change={e => {
if (e.target['value'] != record.value) {
handleFieldChange(index, 'value', e.target['value']);
}
}}
disabled={keyColumn === 'value'}
/>
</FormFieldTemplateLarge>
</div>
<div class="field-wrapper col-2">
<FormFieldTemplateLarge label="TTL" type="text" noMargin>
<TextField
value={record.ttl}
on:change={e => {
if (e.target['value'] != record.ttl) {
handleFieldChange(index, 'ttl', e.target['value']);
}
}}
disabled={keyColumn === 'ttl'}
/>
</FormFieldTemplateLarge>
</div>
<div class="delete-wrapper col-1">
<button
class="delete-button"
on:click={() => {
records = records.filter((_, idx) => idx !== index);
if (onChangeItem) {
onChangeItem({
...item,
records: records,
});
}
}}
>
<FontIcon icon="icon delete" />
</button>
</div>
</div>
{/each}
<div class="add-button-wrapper">
<button class="add-button" on:click={addRecord}>
<FontIcon icon="icon add" />
</button>
</div>
</div>
<style>
.container {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
overflow-y: auto;
}
.props {
display: flex;
flex-direction: row;
gap: 8px;
align-items: center;
width: 100%;
}
.field-wrapper {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
}
.delete-wrapper {
display: flex;
align-items: center;
justify-content: center;
}
.delete-button {
background: none;
border: none;
padding: 0;
cursor: pointer;
color: var(--theme-generic-font-grayed);
transition: color 0.2s;
font-size: 24px;
display: flex;
align-items: center;
justify-content: center;
margin-top: 20px;
}
.delete-button:hover {
color: var(--theme-dbkey-icon-hover);
}
.add-button-wrapper {
display: flex;
justify-content: flex-end;
margin-top: 4px;
}
.add-button {
background: none;
border: none;
padding: 0;
cursor: pointer;
color: var(--theme-generic-font-grayed);
transition: color 0.2s;
font-size: 24px;
}
.add-button:hover {
color: var(--theme-dbkey-icon-hover);
}
</style>
@@ -1,151 +0,0 @@
<script lang="ts">
import _ from 'lodash';
import TextField from '../forms/TextField.svelte';
import FormFieldTemplateLarge from '../forms/FormFieldTemplateLarge.svelte';
import FontIcon from '../icons/FontIcon.svelte';
export let dbKeyFields;
export let item;
export let onChangeItem = null;
export let keyColumn = null;
let records = [{ value: '' }];
let lastItem = null;
$: if (item !== lastItem) {
if (item?.records && Array.isArray(item.records)) {
records = [...item.records];
} else if (!item) {
records = [{ value: '' }];
}
lastItem = item;
}
$: console.log('DbKeyValueListEdit', { item, dbKeyFields, keyColumn, onChangeItem: !!onChangeItem });
function handleFieldChange(index, fieldName, value) {
records = records.map((record, idx) =>
idx === index ? { ...record, [fieldName]: value } : record
);
if (onChangeItem && fieldName !== keyColumn) {
onChangeItem?.({
...item,
records: records,
});
}
}
function addRecord() {
records = [...records, { value: '' }];
if (onChangeItem) {
onChangeItem({
...item,
records: records,
});
}
}
</script>
<div class="container">
{#each records as record, index}
<div class="props flex">
<div class="field-wrapper col-11">
<FormFieldTemplateLarge label="Value" type="text" noMargin>
<TextField
value={record.value}
on:change={e => handleFieldChange(index, 'value', e.target.value)}
disabled={keyColumn === 'value'}
/>
</FormFieldTemplateLarge>
</div>
<div class="delete-wrapper col-1">
<button class="delete-button" on:click={() => {
records = records.filter((_, idx) => idx !== index);
if (onChangeItem) {
onChangeItem({
...item,
records: records,
});
}
}}>
<FontIcon icon="icon delete" />
</button>
</div>
</div>
{/each}
<div class="add-button-wrapper">
<button class="add-button" on:click={addRecord}>
<FontIcon icon="icon add" />
</button>
</div>
</div>
<style>
.container {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
overflow-y: auto;
}
.props {
display: flex;
flex-direction: row;
gap: 8px;
align-items: center;
}
.field-wrapper {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
}
.delete-wrapper {
display: flex;
align-items: center;
justify-content: center;
}
.delete-button {
background: none;
border: none;
padding: 0;
cursor: pointer;
color: var(--theme-generic-font-grayed);
transition: color 0.2s;
font-size: 24px;
display: flex;
align-items: center;
justify-content: center;
margin-top: 20px;
}
.delete-button:hover {
color: var(--theme-dbkey-icon-hover);
}
.add-button-wrapper {
display: flex;
justify-content: flex-end;
margin-top: 4px;
}
.add-button {
background: none;
border: none;
padding: 0;
cursor: pointer;
color: var(--theme-generic-font-grayed);
transition: color 0.2s;
font-size: 24px;
}
.add-button:hover {
color: var(--theme-dbkey-icon-hover);
}
</style>
@@ -1,151 +0,0 @@
<script lang="ts">
import _ from 'lodash';
import TextField from '../forms/TextField.svelte';
import FormFieldTemplateLarge from '../forms/FormFieldTemplateLarge.svelte';
import FontIcon from '../icons/FontIcon.svelte';
export let dbKeyFields;
export let item;
export let onChangeItem = null;
export let keyColumn = null;
let records = [{ value: '' }];
let lastItem = null;
$: if (item !== lastItem) {
if (item?.records && Array.isArray(item.records)) {
records = [...item.records];
} else if (!item) {
records = [{ value: '' }];
}
lastItem = item;
}
$: console.log('DbKeyValueSetEdit', { item, dbKeyFields, keyColumn, onChangeItem: !!onChangeItem });
function handleFieldChange(index, fieldName, value) {
records = records.map((record, idx) =>
idx === index ? { ...record, [fieldName]: value } : record
);
if (onChangeItem && fieldName !== keyColumn) {
onChangeItem?.({
...item,
records: records,
});
}
}
function addRecord() {
records = [...records, { value: '' }];
if (onChangeItem) {
onChangeItem({
...item,
records: records,
});
}
}
</script>
<div class="container">
{#each records as record, index}
<div class="props flex">
<div class="field-wrapper col-11">
<FormFieldTemplateLarge label="Value" type="text" noMargin>
<TextField
value={record.value}
on:change={e => handleFieldChange(index, 'value', e.target.value)}
disabled={keyColumn === 'value'}
/>
</FormFieldTemplateLarge>
</div>
<div class="delete-wrapper col-1">
<button class="delete-button" on:click={() => {
records = records.filter((_, idx) => idx !== index);
if (onChangeItem) {
onChangeItem({
...item,
records: records,
});
}
}}>
<FontIcon icon="icon delete" />
</button>
</div>
</div>
{/each}
<div class="add-button-wrapper">
<button class="add-button" on:click={addRecord}>
<FontIcon icon="icon add" />
</button>
</div>
</div>
<style>
.container {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
overflow-y: auto;
}
.props {
display: flex;
flex-direction: row;
gap: 8px;
align-items: center;
}
.field-wrapper {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
}
.delete-wrapper {
display: flex;
align-items: center;
justify-content: center;
}
.delete-button {
background: none;
border: none;
padding: 0;
cursor: pointer;
color: var(--theme-generic-font-grayed);
transition: color 0.2s;
font-size: 24px;
display: flex;
align-items: center;
justify-content: center;
margin-top: 20px;
}
.delete-button:hover {
color: var(--theme-dbkey-icon-hover);
}
.add-button-wrapper {
display: flex;
justify-content: flex-end;
margin-top: 4px;
}
.add-button {
background: none;
border: none;
padding: 0;
cursor: pointer;
color: var(--theme-generic-font-grayed);
transition: color 0.2s;
font-size: 24px;
}
.add-button:hover {
color: var(--theme-dbkey-icon-hover);
}
</style>
@@ -1,166 +0,0 @@
<script lang="ts">
import _ from 'lodash';
import TextField from '../forms/TextField.svelte';
import FormFieldTemplateLarge from '../forms/FormFieldTemplateLarge.svelte';
import FontIcon from '../icons/FontIcon.svelte';
export let dbKeyFields;
export let item;
export let onChangeItem = null;
export let keyColumn = null;
let records = [{ id: '', value: '' }];
let lastItem = null;
$: if (item !== lastItem) {
if (item?.records && Array.isArray(item.records)) {
records = [...item.records];
} else if (!item) {
records = [{ id: '', value: '' }];
}
lastItem = item;
}
$: console.log('DbKeyValueStreamEdit', { item, dbKeyFields, keyColumn, onChangeItem: !!onChangeItem });
function getValueAsString(value) {
if (value === null || value === undefined) return '';
if (typeof value === 'string') return value;
if (typeof value === 'number') return String(value);
return JSON.stringify(value);
}
function handleFieldChange(index, fieldName, value) {
records = records.map((record, idx) => (idx === index ? { ...record, [fieldName]: value } : record));
if (onChangeItem && fieldName !== keyColumn) {
onChangeItem?.({
...item,
records: records,
});
}
}
function addRecord() {
records = [...records, { id: '', value: '' }];
if (onChangeItem) {
onChangeItem({
...item,
records: records,
});
}
}
</script>
<div class="container">
{#each records as record, index}
<div class="props flex">
<div class="field-wrapper col-3">
<FormFieldTemplateLarge label="ID" type="text" noMargin>
<TextField
value={record.id}
on:change={e => handleFieldChange(index, 'id', e.target.value)}
disabled={keyColumn === 'id'}
placeholder="* for auto"
/>
</FormFieldTemplateLarge>
</div>
<div class="field-wrapper col-9">
<FormFieldTemplateLarge label="Value" type="text" noMargin>
<TextField
value={record.value}
on:change={e => handleFieldChange(index, 'value', e.target.value)}
disabled={keyColumn === 'value'}
/>
</FormFieldTemplateLarge>
</div>
<div class="delete-wrapper col-1">
<button class="delete-button" on:click={() => {
records = records.filter((_, idx) => idx !== index);
if (onChangeItem) {
onChangeItem({
...item,
records: records,
});
}
}}>
<FontIcon icon="icon delete" />
</button>
</div>
</div>
{/each}
<div class="add-button-wrapper">
<button class="add-button" on:click={addRecord}>
<FontIcon icon="icon add" />
</button>
</div>
</div>
<style>
.container {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
overflow-y: auto;
}
.props {
display: flex;
flex-direction: row;
gap: 8px;
align-items: center;
}
.field-wrapper {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
}
.delete-wrapper {
display: flex;
align-items: center;
justify-content: center;
}
.delete-button {
background: none;
border: none;
padding: 0;
cursor: pointer;
color: var(--theme-generic-font-grayed);
transition: color 0.2s;
font-size: 24px;
display: flex;
align-items: center;
justify-content: center;
margin-top: 20px;
}
.delete-button:hover {
color: var(--theme-dbkey-icon-hover);
}
.add-button-wrapper {
display: flex;
justify-content: flex-end;
margin-top: 4px;
}
.add-button {
background: none;
border: none;
padding: 0;
cursor: pointer;
color: var(--theme-generic-font-grayed);
transition: color 0.2s;
font-size: 24px;
}
.add-button:hover {
color: var(--theme-dbkey-icon-hover);
}
</style>
@@ -1,166 +0,0 @@
<script lang="ts">
import _ from 'lodash';
import TextField from '../forms/TextField.svelte';
import FormFieldTemplateLarge from '../forms/FormFieldTemplateLarge.svelte';
import FontIcon from '../icons/FontIcon.svelte';
export let dbKeyFields;
export let item;
export let onChangeItem = null;
export let keyColumn = null;
let records = [{ member: '', score: '' }];
let lastItem = null;
$: if (item !== lastItem) {
if (item?.records && Array.isArray(item.records)) {
records = [...item.records];
} else if (!item) {
records = [{ member: '', score: '' }];
}
lastItem = item;
}
$: console.log('DbKeyValueZSetEdit', { item, dbKeyFields, keyColumn, onChangeItem: !!onChangeItem });
function getValueAsString(value) {
if (value === null || value === undefined) return '';
if (typeof value === 'string') return value;
if (typeof value === 'number') return String(value);
return JSON.stringify(value);
}
function handleFieldChange(index, fieldName, value) {
records = records.map((record, idx) => (idx === index ? { ...record, [fieldName]: value } : record));
if (onChangeItem && fieldName !== keyColumn) {
onChangeItem?.({
...item,
records: records,
});
}
}
function addRecord() {
records = [...records, { member: '', score: '' }];
if (onChangeItem) {
onChangeItem({
...item,
records: records,
});
}
}
</script>
<div class="container">
{#each records as record, index}
<div class="props flex">
<div class="field-wrapper col-8">
<FormFieldTemplateLarge label="Member" type="text" noMargin>
<TextField
value={record.member}
on:change={e => handleFieldChange(index, 'member', e.target.value)}
disabled={keyColumn === 'member'}
/>
</FormFieldTemplateLarge>
</div>
<div class="field-wrapper col-4">
<FormFieldTemplateLarge label="Score" type="text" noMargin>
<TextField
value={record.score}
on:change={e => handleFieldChange(index, 'score', e.target.value)}
disabled={keyColumn === 'score'}
/>
</FormFieldTemplateLarge>
</div>
<div class="delete-wrapper col-1">
<button class="delete-button" on:click={() => {
records = records.filter((_, idx) => idx !== index);
if (onChangeItem) {
onChangeItem({
...item,
records: records,
});
}
}}>
<FontIcon icon="icon delete" />
</button>
</div>
</div>
{/each}
<div class="add-button-wrapper">
<button class="add-button" on:click={addRecord}>
<FontIcon icon="icon add" />
</button>
</div>
</div>
<style>
.container {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
overflow-y: auto;
}
.props {
display: flex;
flex-direction: row;
gap: 8px;
align-items: center;
width: 100%;
}
.field-wrapper {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
}
.delete-wrapper {
display: flex;
align-items: center;
justify-content: center;
}
.delete-button {
background: none;
border: none;
padding: 0;
cursor: pointer;
color: var(--theme-generic-font-grayed);
transition: color 0.2s;
font-size: 24px;
display: flex;
align-items: center;
justify-content: center;
margin-top: 20px;
}
.delete-button:hover {
color: var(--theme-dbkey-icon-hover);
}
.add-button-wrapper {
display: flex;
justify-content: flex-end;
margin-top: 4px;
}
.add-button {
background: none;
border: none;
padding: 0;
cursor: pointer;
color: var(--theme-generic-font-grayed);
transition: color 0.2s;
font-size: 24px;
}
.add-button:hover {
color: var(--theme-dbkey-icon-hover);
}
</style>
+1 -1
View File
@@ -795,7 +795,7 @@
const replaceLinks = text =>
text.replace(
/\[([^\]]+)\]\(([^)]+)\)/g,
'<a href="$2" style="color: var(--theme-font-link)" target="_blank">$1</a>'
'<a href="$2" style="color: var(--theme-link-foreground)" target="_blank">$1</a>'
);
if (value?.style?.omitExportWatermark) return null;
@@ -348,9 +348,6 @@
background-color: var(--theme-designer-item-background);
border: var(--theme-designer-item-border);
}
/* :global(.dbgate-screen) .isSelectedTable {
border: 3px solid var(--theme-border);
} */
.selection-marker {
display: none;
position: absolute;
@@ -221,6 +221,7 @@
table {
width: 100%;
flex: 1;
background-color: var(--theme-table-header-background);
}
table thead,
@@ -274,7 +275,9 @@
padding: 5px;
}
tbody td {
border: var(--theme-table-border);
border-left: var(--theme-table-border);
border-right: var(--theme-table-border);
border-bottom: var(--theme-table-border);
padding: 5px;
}
@@ -292,4 +295,10 @@
tr.isDeleted {
background: var(--theme-table-deleted-background);
}
tr.isDeleted td {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAEElEQVQImWNgIAX8x4KJBAD+agT8INXz9wAAAABJRU5ErkJggg==') !important;
background-repeat: repeat-x !important;
background-position: 50% 50% !important;
}
</style>
+1 -8
View File
@@ -1,19 +1,12 @@
<script lang="ts">
import _ from 'lodash';
import { presetPrimaryColors } from '@ant-design/colors';
import { presetPalettes, presetDarkPalettes } from '@ant-design/colors';
import { currentThemeDefinition } from '../plugins/themes';
import FontIcon from '../icons/FontIcon.svelte';
import { createEventDispatcher } from 'svelte';
export let value;
export let disabled = false;
function colorValue(color, colorIndex, themeDef) {
const palettes = themeDef?.themeType == 'dark' ? presetDarkPalettes : presetPalettes;
return palettes[color][colorIndex];
}
const dispatch = createEventDispatcher();
</script>
@@ -30,7 +23,7 @@
</div>
{#each _.keys(presetPrimaryColors) as color}
<div
style={`background:${colorValue(color, 3, $currentThemeDefinition)}`}
style={`background: var(--theme-connection-${color})`}
class="item"
class:disabled
class:selected={color == value}
@@ -56,7 +56,7 @@
.label.disabled {
cursor: not-allowed;
color: var(--theme-font-3);
color: var(--theme-input-foreground-disabled);
}
.checkbox {
@@ -67,26 +67,26 @@
-moz-appearance: none;
-o-appearance: none;
appearance: none;
outline: 1px solid var(--theme-border);
outline: var(--theme-input-border);
box-shadow: none;
font-size: 0.8em;
text-align: center;
line-height: 1em;
background: var(--theme-bg-0);
background: var(--theme-input-background);
}
.checked:after {
content: '✔';
color: var(--theme-font-1);
color: var(--theme-input-foreground);
font-weight: bold;
}
.isInherited {
background: var(--theme-bg-2) !important;
background: var(--theme-checkbox-background-inherited) !important;
}
.checkbox.disabled {
background: var(--theme-bg-2) !important;
background: var(--theme-input-background-disabled) !important;
cursor: not-allowed;
}
</style>
+10 -1
View File
@@ -4,6 +4,7 @@
import { _tval } from '../translations';
export let name;
export let saveOnlyName = null;
export let defaultValue;
export let saveOnInput = false;
@@ -13,10 +14,18 @@
<TextField
{...$$restProps}
value={$values?.[name] ? _tval($values[name]) : defaultValue}
on:input={e => setFieldValue(name, e.target['value'])}
on:input={e => {
setFieldValue(name, e.target['value']);
if (saveOnlyName) {
setFieldValue(saveOnlyName, e.target['value']);
}
}}
on:input={e => {
if (saveOnInput) {
setFieldValue(name, e.target['value']);
if (saveOnlyName) {
setFieldValue(saveOnlyName, e.target['value']);
}
}
}}
/>
+30 -13
View File
@@ -5,22 +5,37 @@
export let focused = false;
export let domEditor = undefined;
export let autocomplete = 'new-password';
export let isTextArea = false;
if (focused) onMount(() => domEditor.focus());
</script>
<input
class="text-input"
type="text"
{...$$restProps}
bind:value
on:change
on:input
on:click
bind:this={domEditor}
on:keydown
{autocomplete}
/>
{#if isTextArea}
<textarea
class="text-input"
{...$$restProps}
bind:value
on:change
on:input
on:click
bind:this={domEditor}
on:keydown
{autocomplete}
></textarea>
{:else}
<input
class="text-input text-input-one-line"
type="text"
{...$$restProps}
bind:value
on:change
on:input
on:click
bind:this={domEditor}
on:keydown
{autocomplete}
/>
{/if}
<style>
.text-input {
@@ -32,11 +47,13 @@
font-size: 13px;
transition: all 0.15s ease;
font-family: inherit;
height: 40px;
box-shadow: var(--theme-input-shadow);
width: 100%;
box-sizing: border-box;
}
.text-input-one-line {
height: 40px;
}
.text-input::placeholder {
color: var(--theme-input-placeholder);
+6 -5
View File
@@ -32,7 +32,8 @@
export let padRight = false;
export let style = null;
export let colorClass = null;
$: isSvgString = icon && icon.trim().startsWith('<svg');
$: iconValue = typeof icon === 'string' ? icon : icon?.light || icon?.dark || '';
$: isSvgString = iconValue.trim().startsWith('<svg');
const iconNames = {
'icon minus-box': 'mdi mdi-minus-box-outline',
@@ -368,11 +369,11 @@
{#if isSvgString}
<span class="svg-inline" class:padLeft class:padRight {title} {style} on:click data-testid={$$props['data-testid']}>
{@html icon}
{@html iconValue}
</span>
{:else}
<span
class={`${iconNames[icon] || icon} ${colorClass || ''}`}
class={`${iconNames[iconValue] || iconValue} ${colorClass || ''}`}
{title}
class:padLeft
class:padRight
@@ -388,8 +389,8 @@
line-height: 1;
}
.svg-inline :global(svg) {
width: 1.125em;
height: 1.125em;
width: 18px;
height: 18px;
vertical-align: middle;
overflow: visible;
}
+2 -2
View File
@@ -136,10 +136,10 @@
.load-more {
margin-left: 2em;
font-style: italic;
color: var(--theme-font-link);
color: var(--theme-link-foreground);
}
.load-more a {
color: var(--theme-font-link);
color: var(--theme-link-foreground);
text-decoration: none;
cursor: pointer;
}
+13 -25
View File
@@ -85,29 +85,17 @@
</ul>
<style>
:global(.theme-type-dark) ul {
--json-tree-string-color: #ffc5c5;
--json-tree-symbol-color: #ffc5c5;
--json-tree-boolean-color: #b6c3ff;
--json-tree-function-color: #b6c3ff;
--json-tree-number-color: #bfbdff;
--json-tree-label-color: #e9aaed;
--json-tree-arrow-color: #d4d4d4;
--json-tree-null-color: #dcdcdc;
--json-tree-undefined-color: #dcdcdc;
--json-tree-date-color: #dcdcdc;
}
ul {
--string-color: var(--json-tree-string-color, #cb3f41);
--symbol-color: var(--json-tree-symbol-color, #cb3f41);
--boolean-color: var(--json-tree-boolean-color, #112aa7);
--function-color: var(--json-tree-function-color, #112aa7);
--number-color: var(--json-tree-number-color, #3029cf);
--label-color: var(--json-tree-label-color, #871d8f);
--arrow-color: var(--json-tree-arrow-color, #727272);
--null-color: var(--json-tree-null-color, #8d8d8d);
--undefined-color: var(--json-tree-undefined-color, #8d8d8d);
--date-color: var(--json-tree-date-color, #8d8d8d);
--string-color: var(--theme-json-tree-string-color);
--symbol-color: var(--theme-json-tree-symbol-color);
--boolean-color: var(--theme-json-tree-boolean-color);
--function-color: var(--theme-json-tree-function-color);
--number-color: var(--theme-json-tree-number-color);
--label-color: var(--theme-json-tree-label-color);
--arrow-color: var(--theme-json-tree-arrow-color);
--null-color: var(--theme-json-tree-null-color);
--undefined-color: var(--theme-json-tree-undefined-color);
--date-color: var(--theme-json-tree-date-color);
--li-identation: var(--json-tree-li-indentation, 1em);
--li-line-height: var(--json-tree-li-line-height, 1.3);
--li-colon-space: 0.3em;
@@ -130,15 +118,15 @@
margin: 0;
}
ul.isDeleted {
background: var(--theme-bg-volcano);
background: var(--theme-json-tree-deleted-background);
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAEElEQVQImWNgIAX8x4KJBAD+agT8INXz9wAAAABJRU5ErkJggg==');
background-repeat: repeat-x;
background-position: 50% 50%;
}
ul.isModified {
background: var(--theme-bg-gold);
background: var(--theme-json-tree-modified-background);
}
ul.isInserted {
background: var(--theme-bg-green);
background: var(--theme-json-tree-inserted-background);
}
</style>
@@ -1,89 +0,0 @@
<script lang="ts">
import FormStyledButton from '../buttons/FormStyledButton.svelte';
import DbKeyItemDetail from '../dbkeyvalue/DbKeyItemDetail.svelte';
import DbKeyValueHashEdit from '../dbkeyvalue/DbKeyValueHashEdit.svelte';
import FormFieldTemplateLarge from '../forms/FormFieldTemplateLarge.svelte';
import FormProvider from '../forms/FormProvider.svelte';
import SelectField from '../forms/SelectField.svelte';
import TextField from '../forms/TextField.svelte';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
import { _t } from '../translations';
export let conid;
export let database;
export let driver;
export let onConfirm;
let item = {};
let keyName = '';
let type = driver.supportedKeyTypes[0].name;
const handleSubmit = async () => {
closeCurrentModal();
onConfirm({ type, keyName, ...item });
};
</script>
<FormProvider>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">{_t('addDbKeyModal.addKey', { defaultMessage: 'Add key' })}</svelte:fragment>
<div class="container">
<FormFieldTemplateLarge label={_t('addDbKeyModal.key', { defaultMessage: 'Key' })} type="text" noMargin>
<TextField
value={keyName}
on:change={e => {
// @ts-ignore
keyName = e.target.value;
}}
/>
</FormFieldTemplateLarge>
<div class="m-3" />
<FormFieldTemplateLarge label={_t('addDbKeyModal.type', { defaultMessage: 'Type' })} type="combo" noMargin>
<SelectField
options={driver.supportedKeyTypes.map(t => ({ value: t.name, label: t.label }))}
value={type}
isNative
on:change={e => {
type = e.detail;
}}
/>
</FormFieldTemplateLarge>
{#if type === 'hash'}
<DbKeyValueHashEdit
dbKeyFields={driver.supportedKeyTypes.find(x => x.name == type).dbKeyFields}
{item}
onChangeItem={value => {
item = value;
}}
/>
{:else}
<DbKeyItemDetail
dbKeyFields={driver.supportedKeyTypes.find(x => x.name == type).dbKeyFields}
{item}
onChangeItem={value => {
item = value;
}}
/>
{/if}
</div>
<svelte:fragment slot="footer">
<FormStyledButton value={_t('common.ok', { defaultMessage: 'OK' })} on:click={e => handleSubmit()} />
<FormStyledButton type="button" value={_t('common.cancel', { defaultMessage: 'Cancel' })} on:click={closeCurrentModal} />
</svelte:fragment>
</ModalBase>
</FormProvider>
<style>
.container {
display: flex;
flex-direction: column;
height: 30vh;
}
</style>
@@ -9,7 +9,7 @@
export let header;
export let text;
const initialColor = useConnectionColor({ conid, database }, null, null, false, false);
const initialColor = useConnectionColor({ conid, database }, false, false, false);
$: value = $initialColor;
</script>
@@ -1,49 +0,0 @@
<script lang="ts">
import FormStyledButton from '../buttons/FormStyledButton.svelte';
import DbKeyItemDetail from '../dbkeyvalue/DbKeyItemDetail.svelte';
import FormProvider from '../forms/FormProvider.svelte';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
import { _t } from '../translations';
export let keyInfo;
export let label;
export let onConfirm;
let item = {};
const handleSubmit = async () => {
if (await onConfirm(item)) {
closeCurrentModal();
}
};
</script>
<FormProvider>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">{_t('dbKeyAddItemModal.header', { defaultMessage: 'Add item' })}</svelte:fragment>
<div class="container">
<DbKeyItemDetail
dbKeyFields={keyInfo.keyType.dbKeyFields}
{item}
onChangeItem={value => {
item = value;
}}
/>
</div>
<svelte:fragment slot="footer">
<FormStyledButton value={_t('common.ok', { defaultMessage: 'OK' })} on:click={e => handleSubmit()} />
<FormStyledButton type="button" value={_t('common.cancel', { defaultMessage: 'Cancel' })} on:click={closeCurrentModal} />
</svelte:fragment>
</ModalBase>
</FormProvider>
<style>
.container {
display: flex;
height: 30vh;
}
</style>
+1 -1
View File
@@ -89,7 +89,7 @@
.window {
background-color: var(--theme-content-background);
border: var(--theme-content-border);
border: var(--theme-modal-border);
overflow: auto;
outline: none;
display: flex;
@@ -209,6 +209,7 @@
{#if values.insert}
<div class="ml-2">
<FormCheckboxField label={_t('sqlGenerator.skipAutoincrementColumn', { defaultMessage: 'Skip autoincrement column' })} name="skipAutoincrementColumn" />
<FormCheckboxField label={_t('sqlGenerator.skipComputedColumns', { defaultMessage: 'Skip computed columns' })} name="skipComputedColumns" />
<FormCheckboxField label={_t('sqlGenerator.disableConstraints', { defaultMessage: 'Disable constraints' })} name="disableConstraints" />
<FormCheckboxField label={_t('sqlGenerator.omitNulls', { defaultMessage: 'Omit NULL values' })} name="omitNulls" />
</div>
@@ -48,7 +48,6 @@
<style>
td {
font-weight: normal;
/* border: 1px solid var(--theme-border); */
background-color: var(--theme-table-cell-background);
padding: 2px;
position: relative;
@@ -69,16 +69,8 @@
align-self: center;
font-size: 18px;
}
/* .grouping {
color: var(--theme-font-alt);
white-space: nowrap;
}
.data-type {
color: var(--theme-font-3);
} */
th {
/* border: 1px solid var(--theme-border); */
text-align: left;
padding: 2px;
margin: 0;
@@ -94,10 +94,6 @@
white-space: nowrap;
}
.row:hover {
background: var(--theme-bg-hover);
background: var(--theme-table-hover-background);
}
/* .row.isSelected {
background: var(--theme-bg-selected);
} */
</style>
+57 -53
View File
@@ -106,6 +106,7 @@ export const themeDarkColors = {
'--theme-checkbox-background-disabled': 'var(--tw-color-zinc-800)',
'--theme-checkbox-background-disabled-before': 'var(--tw-color-zinc-600)',
'--theme-checkbox-hover-not-disabled': 'var(--tw-color-zinc-500)',
'--theme-checkbox-background-inherited': 'var(--tw-color-zinc-700)',
'--theme-chip-background': 'var(--tw-color-zinc-700)',
'--theme-table-border': '1px solid var(--tw-color-zinc-800)',
'--theme-table-cell-background': 'var(--tw-color-zinc-950)',
@@ -150,9 +151,9 @@ export const themeDarkColors = {
'--theme-icon-picker-border': '1px solid var(--tw-color-zinc-700)',
'--theme-icon-picker-hover': 'var(--tw-color-zinc-800)',
'--theme-icon-picker-selected': 'var(--tw-color-sky-900)',
'--theme-dbkey-background': 'var(--tw-color-zinc-900)',
'--theme-dbkey-border': '2px solid var(--tw-color-zinc-700)',
'--theme-dbkey-icon-hover': 'var(--tw-color-zinc-500)',
'--theme-redis-background': 'var(--tw-color-zinc-900)',
'--theme-redis-border': '1px solid var(--tw-color-zinc-700)',
'--theme-redis-icon-hover': 'var(--tw-color-zinc-500)',
'--theme-outlinebutton-foreground': 'var(--tw-color-zinc-50)',
'--theme-outlinebutton-border': '1px solid var(--tw-color-sky-700)',
'--theme-outlinebutton-hover-foreground': 'var(--tw-color-sky-400)',
@@ -167,7 +168,24 @@ export const themeDarkColors = {
'--theme-titlebar-button-hover': 'var(--tw-color-zinc-600)',
'--theme-card-background': 'var(--tw-color-zinc-600)',
'--theme-card-border': '1px solid var(--tw-color-zinc-800)',
'--theme-content-hover-background': 'var(--tw-color-zinc-800)',
'--theme-content-background-hover': 'var(--tw-color-zinc-800)',
'--theme-admin-menu-item-hover': 'var(--tw-color-zinc-900)',
'--theme-admin-menu-item-active': 'var(--tw-color-zinc-700)',
'--theme-admin-menu-background': 'var(--tw-color-zinc-800)',
'--theme-admin-menu-border': '1px solid var(--tw-color-zinc-800)',
'--theme-json-tree-string-color': 'var(--tw-color-red-300)',
'--theme-json-tree-symbol-color': 'var(--tw-color-red-300)',
'--theme-json-tree-boolean-color': 'var(--tw-color-blue-300)',
'--theme-json-tree-function-color': 'var(--tw-color-blue-300)',
'--theme-json-tree-number-color': 'var(--tw-color-indigo-300)',
'--theme-json-tree-label-color': 'var(--tw-color-purple-300)',
'--theme-json-tree-arrow-color': 'var(--tw-color-zinc-400)',
'--theme-json-tree-null-color': 'var(--tw-color-zinc-400)',
'--theme-json-tree-undefined-color': 'var(--tw-color-zinc-400)',
'--theme-json-tree-date-color': 'var(--tw-color-zinc-400)',
'--theme-json-tree-deleted-background': 'var(--tw-color-red-950)',
'--theme-json-tree-modified-background': 'var(--tw-color-yellow-950)',
'--theme-json-tree-inserted-background': 'var(--tw-color-green-950)',
'--theme-toolstrip-background': 'var(--tw-color-zinc-900)',
'--theme-toolstrip-border': '1px solid var(--tw-color-zinc-800)',
'--theme-toolstrip-button-foreground': 'var(--tw-color-zinc-200)',
@@ -199,8 +217,8 @@ export const themeDarkColors = {
'--theme-designer-drag-column-background': 'var(--tw-color-yellow-900)',
'--theme-designer-select-column-background': 'var(--tw-color-zinc-800)',
'--theme-statusbar-background': 'var(--tw-color-sky-700)',
'--theme-statusbar-foreground': 'var(--tw-color-zinc-100)',
'--theme-statusbar-background-hover': 'var(--tw-color-sky-800)',
'--theme-statusbar-foreground': 'var(--tw-color-zinc-800)',
'--theme-statusbar-background-hover': 'color-mix(in srgb, black 20%, transparent)',
'--theme-statusbar-button-background': 'var(--tw-color-zinc-300)',
'--theme-statusbar-button-foreground': 'var(--tw-color-zinc-800)',
'--theme-statusbar-icon-error': 'var(--tw-color-red-300)',
@@ -226,53 +244,39 @@ export const themeDarkColors = {
'--theme-input-shadow': '0 1px 2px 0 color-mix(in srgb, var(--tw-color-zinc-950) 35%, transparent)',
'--theme-input-shadow-hover': '0 4px 6px -2px color-mix(in srgb, var(--tw-color-zinc-950) 40%, transparent)',
'--theme-input-shadow-focus': '0 1px 2px 0 color-mix(in srgb, var(--tw-color-zinc-950) 35%, transparent)',
'--theme-font-1': '#e3e3e3',
'--theme-font-2': '#b5b5b5',
'--theme-font-3': '#888888',
'--theme-font-4': '#5a5a5a',
'--theme-font-hover': '#8dcff8',
'--theme-font-link': '#65b7f3',
'--theme-font-alt': '#b2e58b',
'--theme-bg-0': '#111',
'--theme-bg-1': '#333',
'--theme-bg-2': '#4d4d4d',
'--theme-bg-3': '#676767',
'--theme-bg-4': '#818181',
'--theme-bg-alt': '#111d2c',
'--theme-bg-gold': '#443111',
'--theme-bg-orange': '#442a11',
'--theme-bg-green': '#1d3712',
'--theme-bg-volcano': '#441d12',
'--theme-bg-red': '#431418',
'--theme-bg-blue': '#15395b',
'--theme-bg-magenta': '#551c3b',
'--theme-font-inv-1': '#ffffff',
'--theme-font-inv-15': '#dedede',
'--theme-font-inv-2': '#b3b3b3',
'--theme-font-inv-3': '#808080',
'--theme-font-inv-4': '#4d4d4d',
'--theme-bg-inv-1': '#222',
'--theme-bg-inv-2': '#3c3c3c',
'--theme-bg-inv-3': '#565656',
'--theme-bg-inv-4': '#707070',
'--theme-border': '#555',
'--theme-bg-hover': '#555',
'--theme-bg-selected': '#1d4d7e',
'--theme-bg-selected-point': '#1765ad',
'--theme-bg-statusbar-inv': '#0050b3',
'--theme-bg-statusbar-inv-hover': '#096dd9',
'--theme-bg-statusbar-inv-font': '#222',
'--theme-bg-statusbar-inv-bg': '#ccc',
'--theme-bg-modalheader': 'rgb(43, 60, 61)',
'--theme-bg-button-inv': '#004488',
'--theme-bg-button-inv-2': '#1a5794',
'--theme-bg-button-inv-3': '#346aa0',
'--theme-icon-blue': '#3c9ae8',
'--theme-icon-green': '#8fd460',
'--theme-icon-red': '#e84749',
'--theme-icon-gold': '#e8b339',
'--theme-icon-yellow': '#e8d639',
'--theme-icon-magenta': '#e0529c',
'--theme-input-inplace-select-shadow': '0 1px 10px 1px var(--tw-color-zinc-400)',
'--theme-icon-blue': 'var(--tw-color-sky-400)',
'--theme-icon-green': 'var(--tw-color-lime-400)',
'--theme-icon-red': 'var(--tw-color-red-500)',
'--theme-icon-gold': 'var(--tw-color-amber-400)',
'--theme-icon-yellow': 'var(--tw-color-yellow-400)',
'--theme-icon-magenta': 'var(--tw-color-pink-500)',
'--theme-connection-red': 'var(--tw-color-red-600)',
'--theme-connection-volcano': '#d4380d',
'--theme-connection-gold': 'var(--tw-color-amber-600)',
'--theme-connection-orange': 'var(--tw-color-orange-600)',
'--theme-connection-yellow': 'var(--tw-color-yellow-600)',
'--theme-connection-lime': 'var(--tw-color-lime-600)',
'--theme-connection-green': 'var(--tw-color-green-600)',
'--theme-connection-cyan': 'var(--tw-color-teal-600)',
'--theme-connection-blue': 'var(--tw-color-sky-600)',
'--theme-connection-geekblue': 'var(--tw-color-indigo-600)',
'--theme-connection-purple': 'var(--tw-color-purple-600)',
'--theme-connection-magenta': 'var(--tw-color-pink-600)',
'--theme-connection-grey': 'var(--tw-color-gray-600)',
'--theme-statusbar-red': 'var(--tw-color-red-500)',
'--theme-statusbar-volcano': '#fa541c',
'--theme-statusbar-gold': 'var(--tw-color-amber-500)',
'--theme-statusbar-orange': 'var(--tw-color-orange-500)',
'--theme-statusbar-yellow': 'var(--tw-color-yellow-500)',
'--theme-statusbar-lime': 'var(--tw-color-lime-500)',
'--theme-statusbar-green': 'var(--tw-color-green-500)',
'--theme-statusbar-cyan': 'var(--tw-color-teal-500)',
'--theme-statusbar-blue': 'var(--tw-color-sky-500)',
'--theme-statusbar-geekblue': 'var(--tw-color-indigo-500)',
'--theme-statusbar-purple': 'var(--tw-color-purple-500)',
'--theme-statusbar-magenta': 'var(--tw-color-pink-500)',
'--theme-statusbar-grey': 'var(--tw-color-gray-500)',
'--token-base': '#303030',
'--token-text': '#ddd',
'--token-keyword': '#096dd9',
+51 -47
View File
@@ -105,6 +105,7 @@ export const themeLightColors = {
'--theme-checkbox-background-disabled': 'var(--tw-color-zinc-100)',
'--theme-checkbox-background-disabled-before': 'var(--tw-color-zinc-400)',
'--theme-checkbox-hover-not-disabled': 'var(--tw-color-zinc-500)',
'--theme-checkbox-background-inherited': 'var(--tw-color-zinc-300)',
'--theme-table-border': '1px solid var(--tw-color-zinc-300)',
'--theme-table-cell-background': 'white',
'--theme-table-cell-empty-background': 'var(--tw-color-zinc-100)',
@@ -175,9 +176,9 @@ export const themeLightColors = {
'--theme-designer-close-background-active': 'var(--tw-color-zinc-400)',
'--theme-designer-drag-column-background': 'var(--tw-color-yellow-200)',
'--theme-designer-select-column-background': 'var(--tw-color-zinc-200)',
'--theme-statusbar-background': 'var(--tw-color-sky-700)',
'--theme-statusbar-background': 'var(--tw-color-sky-600)',
'--theme-statusbar-foreground': 'var(--tw-color-zinc-100)',
'--theme-statusbar-background-hover': 'var(--tw-color-sky-800)',
'--theme-statusbar-background-hover': 'color-mix(in srgb, white 20%, transparent)',
'--theme-statusbar-button-background': 'var(--tw-color-zinc-300)',
'--theme-statusbar-button-foreground': 'var(--tw-color-zinc-800)',
'--theme-statusbar-icon-error': 'var(--tw-color-red-300)',
@@ -203,6 +204,7 @@ export const themeLightColors = {
'--theme-input-shadow': '0 1px 2px 0 color-mix(in srgb, var(--tw-color-zinc-900) 5%, transparent)',
'--theme-input-shadow-hover': '0 4px 6px -2px color-mix(in srgb, var(--tw-color-zinc-900) 8%, transparent)',
'--theme-input-shadow-focus': '0 1px 2px 0 color-mix(in srgb, var(--tw-color-zinc-900) 5%, transparent)',
'--theme-input-inplace-select-shadow': '0 1px 10px 1px var(--tw-color-zinc-600)',
'--theme-color-selected-border': '2px solid var(--tw-color-zinc-800)',
'--theme-new-object-button-background': 'var(--tw-color-zinc-200)',
'--theme-new-object-button-background-hover': 'var(--tw-color-zinc-300)',
@@ -216,62 +218,64 @@ export const themeLightColors = {
'--theme-icon-picker-border' : '1px solid var(--tw-color-zinc-300)',
'--theme-icon-picker-hover': 'var(--tw-color-zinc-300)',
'--theme-icon-picker-selected' : 'var(--tw-color-sky-300)',
'--theme-dbkey-background': 'var(--tw-color-zinc-50)',
'--theme-dbkey-border': '2px solid var(--tw-color-zinc-200)',
'--theme-dbkey-icon-hover': 'var(--tw-color-zinc-400)',
'--theme-redis-background': 'var(--tw-color-zinc-50)',
'--theme-redis-border': '1px solid var(--tw-color-zinc-200)',
'--theme-redis-icon-hover': 'var(--tw-color-zinc-400)',
'--theme-chip-background': 'var(--tw-color-zinc-300)',
'--theme-titlebar-background': 'var(--tw-color-zinc-300)',
'--theme-titlebar-button-hover': 'var(--tw-color-zinc-400)',
'--theme-card-background': 'var(--tw-color-zinc-200)',
'--theme-card-border': '1px solid var(--tw-color-zinc-300)',
'--theme-content-hover-background': 'var(--tw-color-zinc-100)',
'--theme-font-1': '#262626',
'--theme-font-2': '#4d4d4d',
'--theme-font-3': '#808080',
'--theme-font-4': '#b3b3b3',
'--theme-font-hover': '#061178',
'--theme-font-link': '#10239e',
'--theme-font-alt': '#135200',
'--theme-bg-0': '#fff',
'--theme-bg-1': '#ededed',
'--theme-bg-2': '#d4d4d4',
'--theme-bg-3': '#bbbbbb',
'--theme-bg-4': '#a2a2a2',
'--theme-bg-alt': '#f0f5ff',
'--theme-bg-gold': '#fff1b8',
'--theme-bg-orange': '#ffe7ba',
'--theme-bg-green': '#d9f7be',
'--theme-bg-volcano': '#ffd8bf',
'--theme-bg-red': '#ffccc7',
'--theme-bg-blue': '#91d5ff',
'--theme-bg-magenta': '#ffadd2',
'--theme-font-inv-1': '#ffffff',
'--theme-font-inv-15': '#dedede',
'--theme-font-inv-2': '#b3b3b3',
'--theme-font-inv-3': '#808080',
'--theme-font-inv-4': '#4d4d4d',
'--theme-bg-inv-1': '#222',
'--theme-bg-inv-2': '#3c3c3c',
'--theme-bg-inv-3': '#565656',
'--theme-bg-inv-4': '#707070',
'--theme-border': '#ccc',
'--theme-bg-hover': '#ccc',
'--theme-bg-selected': '#91d5ff',
'--theme-bg-selected-point': '#40a9ff',
'--theme-bg-statusbar-inv': '#0050b3',
'--theme-bg-statusbar-inv-hover': '#096dd9',
'--theme-bg-statusbar-inv-font': '#222',
'--theme-bg-statusbar-inv-bg': '#ccc',
'--theme-bg-modalheader': '#eff',
'--theme-bg-button-inv': '#337ab7',
'--theme-bg-button-inv-2': '#4d8bc0',
'--theme-bg-button-inv-3': '#679cc9',
'--theme-content-background-hover': 'var(--tw-color-zinc-100)',
'--theme-admin-menu-item-hover': 'var(--tw-color-zinc-100)',
'--theme-admin-menu-item-active': 'var(--tw-color-zinc-300)',
'--theme-admin-menu-background': 'var(--tw-color-zinc-200)',
'--theme-admin-menu-border': '1px solid var(--tw-color-zinc-200)',
'--theme-json-tree-string-color': 'var(--tw-color-red-600)',
'--theme-json-tree-symbol-color': 'var(--tw-color-red-600)',
'--theme-json-tree-boolean-color': 'var(--tw-color-blue-700)',
'--theme-json-tree-function-color': 'var(--tw-color-blue-700)',
'--theme-json-tree-number-color': 'var(--tw-color-indigo-700)',
'--theme-json-tree-label-color': 'var(--tw-color-purple-700)',
'--theme-json-tree-arrow-color': 'var(--tw-color-zinc-500)',
'--theme-json-tree-null-color': 'var(--tw-color-zinc-500)',
'--theme-json-tree-undefined-color': 'var(--tw-color-zinc-500)',
'--theme-json-tree-date-color': 'var(--tw-color-zinc-500)',
'--theme-json-tree-deleted-background': 'var(--tw-color-red-100)',
'--theme-json-tree-modified-background': 'var(--tw-color-amber-100)',
'--theme-json-tree-inserted-background': 'var(--tw-color-green-100)',
'--theme-icon-blue': 'var(--tw-color-sky-700)',
'--theme-icon-green': 'var(--tw-color-green-700)',
'--theme-icon-red': 'var(--tw-color-red-700)',
'--theme-icon-gold': 'var(--tw-color-amber-700)',
'--theme-icon-yellow': 'var(--tw-color-yellow-700)',
'--theme-icon-magenta': 'var(--tw-color-pink-700)',
'--theme-connection-red': 'var(--tw-color-red-300)',
'--theme-connection-volcano': '#ff9c6e',
'--theme-connection-gold': 'var(--tw-color-amber-300)',
'--theme-connection-orange': 'var(--tw-color-orange-300)',
'--theme-connection-yellow': 'var(--tw-color-yellow-300)',
'--theme-connection-lime': 'var(--tw-color-lime-300)',
'--theme-connection-green': 'var(--tw-color-green-300)',
'--theme-connection-cyan': 'var(--tw-color-teal-300)',
'--theme-connection-blue': 'var(--tw-color-sky-300)',
'--theme-connection-geekblue': 'var(--tw-color-indigo-300)',
'--theme-connection-purple': 'var(--tw-color-purple-300)',
'--theme-connection-magenta': 'var(--tw-color-pink-300)',
'--theme-connection-grey': 'var(--tw-color-gray-300)',
'--theme-statusbar-red': 'var(--tw-color-red-600)',
'--theme-statusbar-volcano': '#d4380d',
'--theme-statusbar-gold': 'var(--tw-color-amber-600)',
'--theme-statusbar-orange': 'var(--tw-color-orange-600)',
'--theme-statusbar-yellow': 'var(--tw-color-yellow-600)',
'--theme-statusbar-lime': 'var(--tw-color-lime-600)',
'--theme-statusbar-green': 'var(--tw-color-green-600)',
'--theme-statusbar-cyan': 'var(--tw-color-teal-600)',
'--theme-statusbar-blue': 'var(--tw-color-sky-600)',
'--theme-statusbar-geekblue': 'var(--tw-color-indigo-600)',
'--theme-statusbar-purple': 'var(--tw-color-purple-600)',
'--theme-statusbar-magenta': 'var(--tw-color-pink-600)',
'--theme-statusbar-grey': 'var(--tw-color-gray-600)',
'--token-base': 'white',
'--token-text': 'black',
'--token-comment': '#008000',
+4 -1
View File
@@ -190,7 +190,10 @@
showPrintMargin: false,
};
$: theme = $currentEditorTheme || ($currentThemeDefinition?.themeType == 'dark' ? 'merbivore' : 'github');
$: theme =
$currentEditorTheme ||
$currentThemeDefinition?.editorTheme ||
($currentThemeDefinition?.themeType == 'dark' ? 'merbivore' : 'github');
$: keyBindingMode = $currentEditorKeybindigMode || null;
$: watchEditorFontSize($currentEditorFontSize);
+2 -2
View File
@@ -29,14 +29,14 @@
display: flex;
width: 100%;
height: calc(50% - 2px);
border-bottom: 2px solid var(--theme-border);
border-bottom: var(--theme-redis-border);
}
.main3 .wrapper {
position: relative;
display: flex;
width: 100%;
height: 40%;
border-bottom: 2px solid var(--theme-border);
border-bottom: var(--theme-redis-border);
}
.main {
position: absolute;
@@ -1,6 +1,6 @@
<script lang="ts">
import _ from 'lodash';
import DbKeyValueDetail from './DbKeyValueDetail.svelte';
import RedisValueDetail from './RedisValueDetail.svelte';
export let dbKeyFields;
export let item;
@@ -9,7 +9,7 @@
<div class="props">
{#each dbKeyFields as column}
<DbKeyValueDetail
<RedisValueDetail
value={item && item[column.name] != null ? String(item[column.name]) : ''}
columnTitle={_.startCase(column.name)}
onChangeValue={onChangeItem && !column.readOnly
@@ -29,12 +29,10 @@
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
padding: 16px;
gap: 16px;
width: 100%;
overflow: auto;
background-color: var(--theme-dbkey-background);
border-top: var(--theme-dbkey-border);
background-color: var(--theme-redis-background);
border-top: var(--theme-redis-border);
}
</style>
@@ -0,0 +1,51 @@
<script lang="ts">
import _ from 'lodash';
import RedisValueDetail from './RedisValueDetail.svelte';
export let dbKeyFields;
export let item;
export let onChangeItem = null;
export let keyColumn = null;
function getValueAsString(value) {
if (value === null || value === undefined) return undefined;
if (typeof value === 'string') return value;
if (typeof value === 'number') return String(value);
return JSON.stringify(value);
}
</script>
<div class="props">
{#each dbKeyFields as column}
<div class="field-wrapper">
<RedisValueDetail
value={getValueAsString(item?.[column.name])}
columnTitle={_.startCase(column.name)}
onChangeValue={onChangeItem && column.name !== keyColumn
? value => {
onChangeItem?.({
...item,
[column.name]: value,
});
}
: null}
/>
</div>
{/each}
</div>
<style>
.props {
flex: 1;
gap: 10px;
padding: 10px;
overflow: hidden;
}
.field-wrapper {
flex: 1;
min-width: 0;
overflow: hidden;
max-height: 100px;
}
</style>
@@ -6,6 +6,7 @@
import JsonTree from '../jsontree/JSONTree.svelte';
import AceEditor from '../query/AceEditor.svelte';
import createRef from '../utility/createRef';
let display = 'text';
@@ -13,6 +14,9 @@
export let value;
export let onChangeValue = null;
export let keyType = null;
const isFirstChangeRef = createRef(true);
const createdTime = Date.now();
</script>
<div class="colnamewrap">
@@ -27,17 +31,26 @@
{ label: 'Text', value: 'text' },
{ label: 'JSON view', value: 'json' },
]}
data-testid="RedisValueDetail_displaySelect"
/>
</div>
<div class="colvalue">
{#if display == 'text'}
<div class="editor-wrapper">
<div class="editor-wrapper" data-testid="RedisValueDetail_AceEditor">
<AceEditor
readOnly={!onChangeValue}
value={value != null ? String(value) : ''}
mode={keyType === 'JSON' ? 'json' : undefined}
on:input={e => {
onChangeValue?.(e.detail);
if (e.detail == '' && isFirstChangeRef.get() && Date.now() - createdTime < 100) {
isFirstChangeRef.set(false);
return;
}
isFirstChangeRef.set(false);
// console.log('AceEditor input event', e, 'VALUE', value);
if (value != e.detail) {
onChangeValue?.(e.detail);
}
}}
/>
</div>
@@ -68,7 +81,7 @@
justify-content: space-between;
}
.colnamewrap :global(select){
.colnamewrap :global(select) {
padding: 2px 4px;
}
@@ -78,7 +91,7 @@
min-height: 60px;
max-height: 1000px;
}
.outer {
flex: 1;
position: relative;
@@ -2,13 +2,13 @@
import _ from 'lodash';
import TextField from '../forms/TextField.svelte';
import FormFieldTemplateLarge from '../forms/FormFieldTemplateLarge.svelte';
import DbKeyValueDetail from './DbKeyValueDetail.svelte';
import RedisValueDetail from './RedisValueDetail.svelte';
export let item;
export let onChangeItem = null;
$: key = item?.key || '';
$: ttl = item?.TTL != null ? String(item.TTL) : '';
$: ttl = item?.ttl != null ? String(item.ttl) : '';
function handleKeyChange(newKey) {
onChangeItem?.({
@@ -18,10 +18,10 @@
}
function handleTtlChange(newTtl) {
const ttlValue = newTtl.trim() === '' ? undefined : parseInt(newTtl);
const ttlValue = newTtl.trim() ? newTtl : undefined;
onChangeItem?.({
...item,
TTL: ttlValue,
ttl: ttlValue,
});
}
</script>
@@ -59,9 +59,9 @@
</div>
</div>
<div class="value-section">
<div class="value-section" data-testid="RedisValueHashDetail_ValueSection">
{#key key}
<DbKeyValueDetail
<RedisValueDetail
value={item?.value || ''}
columnTitle="Value"
onChangeValue={onChangeItem
@@ -83,13 +83,12 @@
.container {
display: flex;
flex-direction: column;
height: 100%;
padding: 16px;
gap: 16px;
width: 100%;
overflow: auto;
background-color: var(--theme-dbkey-background);
border-top: var(--theme-dbkey-border);
background-color: var(--theme-redis-background);
border-top: var(--theme-redis-border);
}
.value-section {
@@ -0,0 +1,111 @@
<script lang="ts">
import _ from 'lodash';
import TextField from '../forms/TextField.svelte';
import FormFieldTemplateLarge from '../forms/FormFieldTemplateLarge.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import { findSupportedRedisKeyType } from 'dbgate-tools';
export let records;
export let onChangeRecords;
export let keyColumn = null;
export let type = 'list';
$: keyType = findSupportedRedisKeyType(type);
function handleFieldChange(index, fieldName, value) {
const newRecords = records.map((record, idx) => (idx === index ? { ...record, [fieldName]: value } : record));
onChangeRecords?.(newRecords);
}
</script>
<div class="container">
{#each records as record, index}
<div class="props-and-button">
<div class="col-11 props-line">
{#each keyType?.dbKeyFields ?? [] as field}
<div class="field-wrapper col-{field.cols}">
<FormFieldTemplateLarge label={field.label ?? _.startCase(field.name)} type="text" noMargin>
<TextField
value={record[field.name]}
placeholder={field.placeholder ?? ''}
on:input={e => {
if (e.target['value'] != record[field.name]) {
handleFieldChange(index, field.name, e.target['value']);
}
}}
disabled={keyColumn === field.name}
data-testid={`RedisValueListLikeEdit_${field.name}`}
/>
</FormFieldTemplateLarge>
</div>
{/each}
</div>
<div class="delete-wrapper col-1">
<button
class="delete-button"
on:click={() => {
const newRecords = records.filter((_, idx) => idx !== index);
onChangeRecords?.(newRecords);
}}
>
<FontIcon icon="icon delete" />
</button>
</div>
</div>
{/each}
</div>
<style>
.container {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
overflow-y: auto;
}
.props-and-button {
display: flex;
flex-direction: row;
gap: 8px;
align-items: center;
width: 100%;
}
.props-line {
display: flex;
flex: 1;
gap: 8px;
}
.field-wrapper {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
}
.delete-wrapper {
display: flex;
align-items: center;
justify-content: center;
}
.delete-button {
background: none;
border: none;
padding: 0;
cursor: pointer;
color: var(--theme-generic-font-grayed);
transition: color 0.2s;
font-size: 24px;
display: flex;
align-items: center;
justify-content: center;
margin-top: 20px;
}
.delete-button:hover {
color: var(--theme-redis-icon-hover);
}
</style>
@@ -18,10 +18,7 @@
<div class="container">
<FormFieldTemplateLarge label="ID" type="text" noMargin>
<TextField
value={id}
disabled={true}
/>
<TextField value={id} disabled={true} />
</FormFieldTemplateLarge>
<div class="value-section">
@@ -42,10 +39,7 @@
<div class="colvalue">
{#if display == 'text'}
<div class="editor-wrapper">
<AceEditor
readOnly={true}
value={value}
/>
<AceEditor readOnly={true} {value} />
</div>
{/if}
{#if display == 'json'}
@@ -63,13 +57,12 @@
.container {
display: flex;
flex-direction: column;
height: 100%;
padding: 16px;
gap: 16px;
width: 100%;
flex: 1;
overflow: auto;
background-color: var(--theme-dbkey-background);
border-top: var(--theme-dbkey-border);
background-color: var(--theme-redis-background);
border-top: var(--theme-redis-border);
}
.value-section {
@@ -95,8 +88,8 @@
align-items: center;
margin-bottom: 5px;
}
.colnamewrap :global(select){
.colnamewrap :global(select) {
padding: 2px 4px;
}

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