Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f43ffd9af4 |
@@ -11,8 +11,8 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
# os: [ubuntu-18.04, windows-2016]
|
||||
os: [macOS-10.14, windows-2016, ubuntu-18.04]
|
||||
os: [ubuntu-18.04, windows-2016]
|
||||
# os: [macOS-10.14, windows-2016, ubuntu-18.04]
|
||||
|
||||
steps:
|
||||
- name: Context
|
||||
@@ -63,18 +63,11 @@ jobs:
|
||||
run: |
|
||||
mkdir artifacts
|
||||
|
||||
cp app/dist/*.deb artifacts/dbgate-beta.deb || true
|
||||
cp app/dist/*.AppImage artifacts/dbgate-beta.AppImage || true
|
||||
cp app/dist/*.exe artifacts/dbgate-beta.exe || true
|
||||
cp app/dist/*windows*.zip artifacts/dbgate-windows-beta.zip || true
|
||||
cp app/dist/*.dmg artifacts/dbgate-beta.dmg || true
|
||||
|
||||
mv app/dist/*.exe artifacts/ || true
|
||||
mv app/dist/*.zip artifacts/ || true
|
||||
mv app/dist/*.AppImage artifacts/ || true
|
||||
mv app/dist/*.deb artifacts/ || true
|
||||
mv app/dist/*.snap artifacts/ || true
|
||||
mv app/dist/*.dmg artifacts/ || true
|
||||
# mv app/dist/*.dmg artifacts/ || true
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v1
|
||||
|
||||
@@ -74,11 +74,9 @@ jobs:
|
||||
cp app/dist/*.deb artifacts/dbgate-latest.deb || true
|
||||
cp app/dist/*.AppImage artifacts/dbgate-latest.AppImage || true
|
||||
cp app/dist/*.exe artifacts/dbgate-latest.exe || true
|
||||
cp app/dist/*windows*.zip artifacts/dbgate-windows-latest.zip || true
|
||||
cp app/dist/*.dmg artifacts/dbgate-latest.dmg || true
|
||||
|
||||
mv app/dist/*.exe artifacts/ || true
|
||||
mv app/dist/*.zip artifacts/ || true
|
||||
mv app/dist/*.AppImage artifacts/ || true
|
||||
mv app/dist/*.deb artifacts/ || true
|
||||
mv app/dist/*.dmg artifacts/ || true
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
name: Docker image
|
||||
|
||||
# on: [push]
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-18.04]
|
||||
|
||||
steps:
|
||||
- name: Context
|
||||
env:
|
||||
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||
run: echo "$GITHUB_CONTEXT"
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Use Node.js 10.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 10.x
|
||||
- name: yarn install
|
||||
run: |
|
||||
yarn install
|
||||
- name: setCurrentVersion
|
||||
run: |
|
||||
yarn setCurrentVersion
|
||||
- name: Prepare docker image
|
||||
run: |
|
||||
yarn run prepare:docker
|
||||
- name: Build docker image
|
||||
run: |
|
||||
docker build ./docker -t dbgate
|
||||
- name: Push docker image
|
||||
run: |
|
||||
docker tag dbgate dbgate/dbgate:beta
|
||||
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
|
||||
docker push dbgate/dbgate:beta
|
||||
@@ -89,33 +89,3 @@ jobs:
|
||||
working-directory: packages/dbgate
|
||||
run: |
|
||||
npm publish
|
||||
|
||||
- name: Publish dbgate-plugin-csv
|
||||
working-directory: plugins/dbgate-plugin-csv
|
||||
run: |
|
||||
npm publish
|
||||
|
||||
- name: Publish dbgate-plugin-excel
|
||||
working-directory: plugins/dbgate-plugin-excel
|
||||
run: |
|
||||
npm publish
|
||||
|
||||
- name: Publish dbgate-plugin-mssql
|
||||
working-directory: plugins/dbgate-plugin-mssql
|
||||
run: |
|
||||
npm publish
|
||||
|
||||
- name: Publish dbgate-plugin-mysql
|
||||
working-directory: plugins/dbgate-plugin-mysql
|
||||
run: |
|
||||
npm publish
|
||||
|
||||
- name: Publish dbgate-plugin-mongo
|
||||
working-directory: plugins/dbgate-plugin-mongo
|
||||
run: |
|
||||
npm publish
|
||||
|
||||
- name: Publish dbgate-plugin-postgres
|
||||
working-directory: plugins/dbgate-plugin-postgres
|
||||
run: |
|
||||
npm publish
|
||||
|
||||
@@ -13,10 +13,8 @@ build
|
||||
dist
|
||||
|
||||
app/packages/web/public
|
||||
app/packages/plugins
|
||||
docker/public
|
||||
docker/bundle.js
|
||||
docker/plugins
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
@@ -1,42 +1,5 @@
|
||||
# ChangeLog
|
||||
|
||||
### 4.1.11
|
||||
- FIX: Fixed crash of API process when using SSH tunnel connection (race condition)
|
||||
|
||||
### 4.1.11
|
||||
- FIX: fixed processing postgre query containing $$
|
||||
- FIX: fixed postgre analysing procedures & functions
|
||||
- FIX: patched svelte crash #105
|
||||
- ADDED: ability to disbale background DB model updates
|
||||
- ADDED: Duplicate connection
|
||||
- ADDED: Duplicate tab
|
||||
- FIX: SSH tunnel connection using keyfile auth #106
|
||||
- FIX: All tables button fix in export #109
|
||||
- CHANGED: Add to favorites moved from toolbar to tab context menu
|
||||
- CHANGED: Toolbar design - current tab related commands are delimited
|
||||
|
||||
### 4.1.10
|
||||
- ADDED: Default database option in connectin settings #96 #92
|
||||
- FIX: Bundle size optimalization for Windows #97
|
||||
- FIX: Popup menu placement on smaller displays #94
|
||||
- ADDED: Browse table data with SQL Server 2008 #93
|
||||
- FIX: Prevented malicious origins / DNS rebinding #91
|
||||
- ADDED: Handle JSON fields in data editor (eg. jsonb field in Postgres) #90
|
||||
- FIX: Fixed crash on Windows with Hyper-V #86
|
||||
- ADDED: Show database server version in status bar
|
||||
- ADDED: Show detailed info about error, when connect to database fails
|
||||
- ADDED: Portable ZIP distribution for Windows #84
|
||||
### 4.1.9
|
||||
- FIX: Incorrect row count info in query result #83
|
||||
|
||||
### 4.1.1
|
||||
- CHANGED: Default plugins are now part of installation
|
||||
### 4.1.0
|
||||
- ADDED: MongoDB support
|
||||
- ADDED: Configurable keyboard shortcuts
|
||||
- ADDED: JSON row cell data view
|
||||
- FIX: Fixed some problems from migration to Svelte
|
||||
|
||||
### 4.0.3
|
||||
- FIX: fixes for FireFox (mainly incorrent handle of bind:clientHeight, replaces with resizeobserver)
|
||||
### 4.0.2
|
||||
|
||||
@@ -1,35 +1,27 @@
|
||||
[](https://www.npmjs.com/package/dbgate)
|
||||

|
||||
[](https://snapcraft.io/dbgate)
|
||||
[](https://snapcraft.io/dbgate)
|
||||
[](https://github.com/prettier/prettier)
|
||||
[](https://paypal.me/JanProchazkaCz/30eur)
|
||||
|
||||
# DbGate - database administration tool
|
||||
|
||||
DbGate modern, fast and easy to use database manager
|
||||
DbGate is fast and easy to use database manager. Works with MySQL, PostgreSQL and SQL Server.
|
||||
|
||||
* Try it online - [demo.dbgate.org](https://demo.dbgate.org) - online demo application
|
||||
* Download application for Windows, Linux or Mac from [dbgate.org](https://dbgate.org/download/)
|
||||
* Run web version as [NPM package](https://www.npmjs.com/package/dbgate) or as [docker image](https://hub.docker.com/r/dbgate/dbgate)
|
||||
|
||||
Supported databases:
|
||||
* MySQL
|
||||
* PostgreSQL
|
||||
* SQL Server
|
||||
* MongoDB
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
* Connect to Microsoft SQL Server, Postgre SQL, MySQL, MongoDB
|
||||
* Connect to Microsoft SQL Server, Postgre SQL, MySQL
|
||||
* Table data editing, with SQL change script preview
|
||||
* Master/detail views
|
||||
* Query designer
|
||||
* Form view for comfortable work with tables with many columns
|
||||
* JSON view on MongoDB collections
|
||||
* Explore tables, views, procedures, functions, MongoDB collections
|
||||
* Explore tables, views, procedures, functions
|
||||
* SQL editor, execute SQL script, SQL code formatter, SQL code completion, SQL join wizard
|
||||
* Mongo JavaScript editor, execute Mongo script (with NodeJs syntax)
|
||||
* Runs as application for Windows, Linux and Mac. Or in Docker container on server and in web Browser on client.
|
||||
* Import, export from/to CSV, Excel, JSON
|
||||
* Free table editor - quick table data editing (cleanup data after import/before export, prototype tables etc.)
|
||||
@@ -64,8 +56,6 @@ Currently following extensions can be implemented using plugins:
|
||||
- File format parsers/writers
|
||||
- Database engine connectors
|
||||
|
||||
Basic set of plugins is part of DbGate git repository and is installed with app. Additional plugins pust be downloaded from NPM (this task is handled by DbGate)
|
||||
|
||||
## How to run development environment
|
||||
|
||||
```sh
|
||||
@@ -73,7 +63,7 @@ yarn
|
||||
yarn start
|
||||
```
|
||||
|
||||
If you want to make modifications in libraries or plugins, run library compiler in watch mode in the second terminal:
|
||||
If you want to make modifications in TypeScript packages, run TypeScript compiler in watch mode in seconds terminal:
|
||||
```sh
|
||||
yarn lib
|
||||
```
|
||||
@@ -88,7 +78,7 @@ yarn start
|
||||
```
|
||||
|
||||
## How to run built electron app locally
|
||||
This mode is very similar to production run of electron app. Electron app forks process with API on dynamically allocated port, works with compiled javascript files and uses compiled version of plugins (doesn't use localhost:5000)
|
||||
This mode is very similar to production run of electron app. Electron app forks process with API on dynamically allocated port, works with compiled javascript files (doesn't use localhost:5000)
|
||||
|
||||
```sh
|
||||
cd app
|
||||
|
||||
+5
-13
@@ -1,11 +1,10 @@
|
||||
{
|
||||
"name": "dbgate",
|
||||
"version": "4.1.1",
|
||||
"version": "4.0.0",
|
||||
"private": true,
|
||||
"author": "Jan Prochazka <jenasoft.database@gmail.com>",
|
||||
"description": "Opensource database administration tool",
|
||||
"dependencies": {
|
||||
"better-sqlite3-with-prebuilds": "^7.1.8",
|
||||
"electron-log": "^4.3.1",
|
||||
"electron-store": "^5.1.1",
|
||||
"electron-updater": "^4.3.5"
|
||||
@@ -22,13 +21,7 @@
|
||||
"artifactName": "dbgate-mac-${version}.${ext}",
|
||||
"publish": [
|
||||
"github"
|
||||
],
|
||||
"target": {
|
||||
"target": "default",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
@@ -55,8 +48,7 @@
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
"nsis",
|
||||
"zip"
|
||||
"nsis"
|
||||
],
|
||||
"artifactName": "dbgate-windows-${version}.${ext}",
|
||||
"icon": "icon.ico",
|
||||
@@ -79,7 +71,7 @@
|
||||
"build": "cd ../packages/api && yarn build && cd ../web && yarn build && cd ../../app && yarn dist",
|
||||
"build:local": "cd ../packages/api && yarn build && cd ../web && yarn build && cd ../../app && yarn predist",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"predist": "copyfiles ../packages/api/dist/* packages && copyfiles \"../packages/web/public/*\" packages && copyfiles \"../packages/web/public/**/*\" packages && copyfiles --up 3 \"../plugins/dist/**/*\" packages/plugins"
|
||||
"predist": "copyfiles ../packages/api/dist/* packages && copyfiles \"../packages/web/public/*\" packages && copyfiles \"../packages/web/public/**/*\" packages"
|
||||
},
|
||||
"main": "src/electron.js",
|
||||
"devDependencies": {
|
||||
@@ -91,4 +83,4 @@
|
||||
"optionalDependencies": {
|
||||
"msnodesqlv8": "^2.0.10"
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-3
@@ -205,16 +205,14 @@ function createWindow() {
|
||||
} else {
|
||||
const apiProcess = fork(path.join(__dirname, '../packages/api/dist/bundle.js'), [
|
||||
'--dynport',
|
||||
'--is-electron-bundle',
|
||||
'--native-modules',
|
||||
path.join(__dirname, 'nativeModules'),
|
||||
// '../../../src/nativeModules'
|
||||
]);
|
||||
apiProcess.on('message', msg => {
|
||||
if (msg.msgtype == 'listening') {
|
||||
const { port, authorization } = msg;
|
||||
const { port } = msg;
|
||||
global['port'] = port;
|
||||
global['authorization'] = authorization;
|
||||
loadMainWindow();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -60,11 +60,6 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/integer@latest":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/integer/-/integer-4.0.0.tgz#3b778715df72d2cf8ba73bad27bd9d830907f944"
|
||||
integrity sha512-2U1i6bIRiqizl6O+ETkp2HhUZIxg7g+burUabh9tzGd0qcszfNaFRaY9bGNlQKgEU7DCsH5qMajRDW5QamWQbw==
|
||||
|
||||
"@types/node@*":
|
||||
version "13.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.1.tgz#96f606f8cd67fb018847d9b61e93997dabdefc72"
|
||||
@@ -237,23 +232,6 @@ base64-js@^1.3.1:
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
|
||||
better-sqlite3-with-prebuilds@^7.1.8:
|
||||
version "7.1.8"
|
||||
resolved "https://registry.yarnpkg.com/better-sqlite3-with-prebuilds/-/better-sqlite3-with-prebuilds-7.1.8.tgz#3090c478fe9b60e74ce053a76807b189784f62d7"
|
||||
integrity sha512-trwg1qhN91cPYEB8D2K0KVHIsMsiAnxKx6/syfQ7rLrtD+zOS3fqJq4VGszMF+OuYAZJNAR4oLsikys3YW/6aA==
|
||||
dependencies:
|
||||
"@types/integer" latest
|
||||
bindings "^1.5.0"
|
||||
prebuild-install "^6.0.1"
|
||||
tar "^6.1.0"
|
||||
|
||||
bindings@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
|
||||
integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==
|
||||
dependencies:
|
||||
file-uri-to-path "1.0.0"
|
||||
|
||||
bl@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489"
|
||||
@@ -391,11 +369,6 @@ chownr@^1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
|
||||
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
|
||||
|
||||
chownr@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
|
||||
integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
|
||||
|
||||
chromium-pickle-js@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz#04a106672c18b085ab774d983dfa3ea138f22205"
|
||||
@@ -842,11 +815,6 @@ fd-slicer@~1.0.1:
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
file-uri-to-path@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
|
||||
integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
|
||||
|
||||
filelist@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.1.tgz#f10d1a3ae86c1694808e8f20906f43d4c9132dbb"
|
||||
@@ -885,13 +853,6 @@ fs-extra@^9.0.1:
|
||||
jsonfile "^6.0.1"
|
||||
universalify "^1.0.0"
|
||||
|
||||
fs-minipass@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
|
||||
integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
|
||||
dependencies:
|
||||
minipass "^3.0.0"
|
||||
|
||||
fs.realpath@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
@@ -1334,21 +1295,6 @@ minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5:
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||
|
||||
minipass@^3.0.0:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd"
|
||||
integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
minizlib@^2.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
|
||||
integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
|
||||
dependencies:
|
||||
minipass "^3.0.0"
|
||||
yallist "^4.0.0"
|
||||
|
||||
mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
|
||||
@@ -1361,11 +1307,6 @@ mkdirp@0.5.1, mkdirp@^0.5.1:
|
||||
dependencies:
|
||||
minimist "0.0.8"
|
||||
|
||||
mkdirp@^1.0.3:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
@@ -1394,13 +1335,6 @@ napi-build-utils@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806"
|
||||
integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==
|
||||
|
||||
node-abi@^2.21.0:
|
||||
version "2.26.0"
|
||||
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.26.0.tgz#355d5d4bc603e856f74197adbf3f5117a396ba40"
|
||||
integrity sha512-ag/Vos/mXXpWLLAYWsAoQdgS+gW7IwvgMLOgqopm/DbzAjazLltzgzpVMsFlgmo9TzG5hGXeaBZx2AI731RIsQ==
|
||||
dependencies:
|
||||
semver "^5.4.1"
|
||||
|
||||
node-abi@^2.7.0:
|
||||
version "2.19.3"
|
||||
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.19.3.tgz#252f5dcab12dad1b5503b2d27eddd4733930282d"
|
||||
@@ -1575,26 +1509,6 @@ prebuild-install@^6.0.0:
|
||||
tunnel-agent "^0.6.0"
|
||||
which-pm-runs "^1.0.0"
|
||||
|
||||
prebuild-install@^6.0.1:
|
||||
version "6.1.2"
|
||||
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-6.1.2.tgz#6ce5fc5978feba5d3cbffedca0682b136a0b5bff"
|
||||
integrity sha512-PzYWIKZeP+967WuKYXlTOhYBgGOvTRSfaKI89XnfJ0ansRAH7hDU45X+K+FZeI1Wb/7p/NnuctPH3g0IqKUuSQ==
|
||||
dependencies:
|
||||
detect-libc "^1.0.3"
|
||||
expand-template "^2.0.3"
|
||||
github-from-package "0.0.0"
|
||||
minimist "^1.2.3"
|
||||
mkdirp-classic "^0.5.3"
|
||||
napi-build-utils "^1.0.1"
|
||||
node-abi "^2.21.0"
|
||||
noop-logger "^0.1.1"
|
||||
npmlog "^4.0.1"
|
||||
pump "^3.0.0"
|
||||
rc "^1.2.7"
|
||||
simple-get "^3.0.3"
|
||||
tar-fs "^2.0.0"
|
||||
tunnel-agent "^0.6.0"
|
||||
|
||||
prepend-http@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
|
||||
@@ -2030,18 +1944,6 @@ tar-stream@^2.1.4:
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^3.1.1"
|
||||
|
||||
tar@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83"
|
||||
integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==
|
||||
dependencies:
|
||||
chownr "^2.0.0"
|
||||
fs-minipass "^2.0.0"
|
||||
minipass "^3.0.0"
|
||||
minizlib "^2.1.1"
|
||||
mkdirp "^1.0.3"
|
||||
yallist "^4.0.0"
|
||||
|
||||
temp-file@^3.3.7:
|
||||
version "3.3.7"
|
||||
resolved "https://registry.yarnpkg.com/temp-file/-/temp-file-3.3.7.tgz#686885d635f872748e384e871855958470aeb18a"
|
||||
|
||||
@@ -2,10 +2,10 @@ const fs = require('fs');
|
||||
|
||||
let fillContent = '';
|
||||
|
||||
// if (!process.argv.includes('--electron')) {
|
||||
if (process.platform == 'win32') {
|
||||
fillContent += `content.msnodesqlv8 = () => require('msnodesqlv8');`;
|
||||
}
|
||||
fillContent += `content['better-sqlite3-with-prebuilds'] = () => require('better-sqlite3-with-prebuilds');`;
|
||||
|
||||
const getContent = (empty) => `
|
||||
// this file is generated automatically by script fillNativeModules.js, do not edit it manually
|
||||
|
||||
+11
-15
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "4.2.0-beta.3",
|
||||
"version": "4.0.3-beta.1",
|
||||
"name": "dbgate-all",
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
"plugins/*"
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"start:api": "yarn workspace dbgate-api start",
|
||||
@@ -20,27 +19,25 @@
|
||||
"build:filterparser": "yarn workspace dbgate-filterparser build",
|
||||
"build:tools": "yarn workspace dbgate-tools build",
|
||||
"build:lib": "yarn build:tools && yarn build:sqltree && yarn build:filterparser && yarn build:datalib",
|
||||
"build:app": "yarn plugins:copydist && cd app && yarn install && yarn build",
|
||||
"build:app": "cd app && yarn install && yarn build",
|
||||
"build:api": "yarn workspace dbgate-api build",
|
||||
"build:web:docker": "yarn workspace dbgate-web build",
|
||||
"build:plugins:frontend": "workspaces-run --only=\"dbgate-plugin-*\" -- yarn build:frontend",
|
||||
"build:plugins:frontend:watch": "workspaces-run --parallel --only=\"dbgate-plugin-*\" -- yarn build:frontend:watch",
|
||||
"plugins:copydist": "workspaces-run --only=\"dbgate-plugin-*\" -- yarn copydist",
|
||||
"build:app:local": "yarn plugins:copydist && cd app && yarn build:local",
|
||||
"build:app:local": "cd app && yarn build:local",
|
||||
"start:app:local": "cd app && yarn start:local",
|
||||
"setCurrentVersion": "node setCurrentVersion",
|
||||
"generatePadFile": "node generatePadFile",
|
||||
"fillNativeModules": "node fillNativeModules",
|
||||
"fillNativeModulesElectron": "node fillNativeModules --electron",
|
||||
"fillNativeModulesElectron": "node fillNativeModules --eletron",
|
||||
"prettier": "prettier --write packages/api/src && prettier --write packages/datalib/src && prettier --write packages/filterparser/src && prettier --write packages/sqltree/src && prettier --write packages/tools/src && prettier --write packages/types && prettier --write packages/web/src && prettier --write app/src",
|
||||
"copy:docker:build": "copyfiles packages/api/dist/* docker -f && copyfiles packages/web/public/* docker -u 2 && copyfiles \"packages/web/public/**/*\" docker -u 2 && copyfiles \"plugins/dist/**/*\" docker/plugins -u 2",
|
||||
"prepare:docker": "yarn plugins:copydist && yarn build:web:docker && yarn build:api && yarn copy:docker:build",
|
||||
"copy:docker:build": "copyfiles packages/api/dist/* docker -f && copyfiles packages/web/public/* docker -u 2 && copyfiles \"packages/web/public/**/*\" docker -u 2",
|
||||
"prepare:docker": "yarn build:web:docker && yarn build:api && yarn copy:docker:build",
|
||||
"prepare": "yarn build:lib",
|
||||
"start": "concurrently --kill-others-on-fail \"yarn start:api\" \"yarn start:web\"",
|
||||
"lib": "concurrently --kill-others-on-fail \"yarn start:sqltree\" \"yarn start:filterparser\" \"yarn start:datalib\" \"yarn start:tools\" \"yarn build:plugins:frontend:watch\"",
|
||||
"lib": "concurrently --kill-others-on-fail \"yarn start:sqltree\" \"yarn start:filterparser\" \"yarn start:datalib\" \"yarn start:tools\"",
|
||||
"ts:api": "yarn workspace dbgate-api ts",
|
||||
"ts:web": "yarn workspace dbgate-web ts",
|
||||
"ts": "yarn ts:api && yarn ts:web",
|
||||
"postinstall": "yarn build:lib && patch-package && yarn fillNativeModules && yarn build:plugins:frontend"
|
||||
"postinstall": "patch-package && yarn fillNativeModules"
|
||||
},
|
||||
"dependencies": {
|
||||
"concurrently": "^5.1.0",
|
||||
@@ -49,7 +46,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"copyfiles": "^2.2.0",
|
||||
"prettier": "^2.2.1",
|
||||
"workspaces-run": "^1.0.1"
|
||||
"prettier": "^2.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
DEVMODE=1
|
||||
|
||||
CONNECTIONS=mysql
|
||||
|
||||
LABEL_mysql=MySql
|
||||
+15
-1
@@ -1 +1,15 @@
|
||||
DEVMODE=1
|
||||
CONNECTIONS=mysql,postgres
|
||||
|
||||
LABEL_mysql=MySql localhost
|
||||
SERVER_mysql=localhost
|
||||
USER_mysql=root
|
||||
PASSWORD_mysql=test
|
||||
PORT_mysql=3307
|
||||
ENGINE_mysql=mysql@dbgate-plugin-mysql
|
||||
|
||||
LABEL_postgres=Postgres localhost
|
||||
SERVER_postgres=localhost
|
||||
USER_postgres=postgres
|
||||
PASSWORD_postgres=test
|
||||
PORT_postgres=5433
|
||||
ENGINE_postgres=postgres@dbgate-plugin-postgres
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
DEVMODE=1
|
||||
|
||||
CONNECTIONS=mysql,postgres
|
||||
|
||||
LABEL_mysql=MySql localhost
|
||||
SERVER_mysql=localhost
|
||||
USER_mysql=root
|
||||
PASSWORD_mysql=test
|
||||
PORT_mysql=3307
|
||||
ENGINE_mysql=mysql@dbgate-plugin-mysql
|
||||
|
||||
LABEL_postgres=Postgres localhost
|
||||
SERVER_postgres=localhost
|
||||
USER_postgres=postgres
|
||||
PASSWORD_postgres=test
|
||||
PORT_postgres=5433
|
||||
ENGINE_postgres=postgres@dbgate-plugin-postgres
|
||||
+11
-12
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"name": "dbgate-api",
|
||||
"main": "src/index.js",
|
||||
"version": "4.1.1",
|
||||
"version": "4.0.0",
|
||||
"homepage": "https://dbgate.org/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dbgate/dbgate.git"
|
||||
},
|
||||
"funding": "https://www.paypal.com/paypalme/JanProchazkaCz/30eur",
|
||||
"author": "Jan Prochazka",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
@@ -19,21 +20,20 @@
|
||||
"dependencies": {
|
||||
"async-lock": "^1.2.4",
|
||||
"axios": "^0.19.0",
|
||||
"better-sqlite3-with-prebuilds": "^7.1.8",
|
||||
"body-parser": "^1.19.0",
|
||||
"bufferutil": "^4.0.1",
|
||||
"byline": "^5.0.0",
|
||||
"compare-versions": "^3.6.0",
|
||||
"cors": "^2.8.5",
|
||||
"cross-env": "^6.0.3",
|
||||
"dbgate-sqltree": "^4.1.1",
|
||||
"dbgate-tools": "^4.1.1",
|
||||
"dbgate-sqltree": "^4.0.0",
|
||||
"dbgate-tools": "^4.0.0",
|
||||
"eslint": "^6.8.0",
|
||||
"express": "^4.17.1",
|
||||
"express-basic-auth": "^1.2.0",
|
||||
"express-fileupload": "^1.2.0",
|
||||
"fs-extra": "^9.1.0",
|
||||
"get-port": "^5.1.1",
|
||||
"find-free-port": "^2.0.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"http": "^0.0.0",
|
||||
"json-stable-stringify": "^1.0.1",
|
||||
"line-reader": "^0.4.0",
|
||||
@@ -49,16 +49,15 @@
|
||||
"uuid": "^3.4.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "env-cmd node src/index.js",
|
||||
"start:portal": "env-cmd -f .env-portal node src/index.js",
|
||||
"start:covid": "env-cmd -f .env-covid node src/index.js",
|
||||
"start": "node src/index.js",
|
||||
"start:portal": "env-cmd nodemon src/index.js",
|
||||
"start:covid": "env-cmd -f .covid-env nodemon src/index.js",
|
||||
"ts": "tsc",
|
||||
"build": "webpack"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^9.0.11",
|
||||
"@types/lodash": "^4.14.149",
|
||||
"dbgate-types": "^4.1.1",
|
||||
"dbgate-types": "^4.0.0",
|
||||
"env-cmd": "^10.1.0",
|
||||
"node-loader": "^1.0.2",
|
||||
"nodemon": "^2.0.2",
|
||||
@@ -69,4 +68,4 @@
|
||||
"optionalDependencies": {
|
||||
"msnodesqlv8": "^2.0.10"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,7 @@
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const { datadir } = require('../utility/directories');
|
||||
const hasPermission = require('../utility/hasPermission');
|
||||
const socket = require('../utility/socket');
|
||||
const _ = require('lodash');
|
||||
|
||||
const currentVersion = require('../currentVersion');
|
||||
const platformInfo = require('../utility/platformInfo');
|
||||
|
||||
module.exports = {
|
||||
settingsValue: {},
|
||||
|
||||
async _init() {
|
||||
try {
|
||||
this.settingsValue = JSON.parse(await fs.readFile(path.join(datadir(), 'settings.json'), { encoding: 'utf-8' }));
|
||||
} catch (err) {
|
||||
this.settingsValue = {};
|
||||
}
|
||||
},
|
||||
|
||||
get_meta: 'get',
|
||||
async get() {
|
||||
// const toolbarButtons = process.env.TOOLBAR;
|
||||
@@ -54,26 +37,5 @@ module.exports = {
|
||||
async platformInfo() {
|
||||
return platformInfo;
|
||||
},
|
||||
|
||||
getSettings_meta: 'get',
|
||||
async getSettings() {
|
||||
return this.settingsValue;
|
||||
},
|
||||
|
||||
updateSettings_meta: 'post',
|
||||
async updateSettings(values) {
|
||||
if (!hasPermission(`settings/change`)) return false;
|
||||
try {
|
||||
const updated = {
|
||||
...this.settingsValue,
|
||||
...values,
|
||||
};
|
||||
await fs.writeFile(path.join(datadir(), 'settings.json'), JSON.stringify(updated, undefined, 2));
|
||||
this.settingsValue = updated;
|
||||
socket.emitChanged(`settings-changed`);
|
||||
return updated;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
@@ -46,7 +46,7 @@ module.exports = {
|
||||
raw: true,
|
||||
},
|
||||
test(req, res) {
|
||||
const subprocess = fork(process.argv[1], ['--start-process', 'connectProcess', ...process.argv.slice(3)]);
|
||||
const subprocess = fork(process.argv[1], ['connectProcess', ...process.argv.slice(3)]);
|
||||
subprocess.on('message', resp => {
|
||||
if (handleProcessCommunication(resp, subprocess)) return;
|
||||
// @ts-ignore
|
||||
|
||||
@@ -4,7 +4,6 @@ const socket = require('../utility/socket');
|
||||
const { fork } = require('child_process');
|
||||
const { DatabaseAnalyser } = require('dbgate-tools');
|
||||
const { handleProcessCommunication } = require('../utility/processComm');
|
||||
const config = require('./config');
|
||||
|
||||
module.exports = {
|
||||
/** @type {import('dbgate-types').OpenedDatabaseConnection[]} */
|
||||
@@ -18,13 +17,6 @@ module.exports = {
|
||||
existing.structure = structure;
|
||||
socket.emitChanged(`database-structure-changed-${conid}-${database}`);
|
||||
},
|
||||
handle_version(conid, database, { version }) {
|
||||
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
||||
if (!existing) return;
|
||||
existing.serverVersion = version;
|
||||
socket.emitChanged(`database-server-version-changed-${conid}-${database}`);
|
||||
},
|
||||
|
||||
handle_error(conid, database, props) {
|
||||
const { error } = props;
|
||||
console.log(`Error in database connection ${conid}, database ${database}: ${error}`);
|
||||
@@ -48,18 +40,13 @@ module.exports = {
|
||||
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
||||
if (existing) return existing;
|
||||
const connection = await connections.get({ conid });
|
||||
const subprocess = fork(process.argv[1], [
|
||||
'--start-process',
|
||||
'databaseConnectionProcess',
|
||||
...process.argv.slice(3),
|
||||
]);
|
||||
const subprocess = fork(process.argv[1], ['databaseConnectionProcess', ...process.argv.slice(3)]);
|
||||
const lastClosed = this.closed[`${conid}/${database}`];
|
||||
const newOpened = {
|
||||
conid,
|
||||
database,
|
||||
subprocess,
|
||||
structure: lastClosed ? lastClosed.structure : DatabaseAnalyser.createEmptyStructure(),
|
||||
serverVersion: lastClosed ? lastClosed.serverVersion : null,
|
||||
connection,
|
||||
status: { name: 'pending' },
|
||||
};
|
||||
@@ -80,7 +67,6 @@ module.exports = {
|
||||
msgtype: 'connect',
|
||||
connection: { ...connection, database },
|
||||
structure: lastClosed ? lastClosed.structure : null,
|
||||
globalSettings: config.settingsValue,
|
||||
});
|
||||
return newOpened;
|
||||
},
|
||||
@@ -113,13 +99,6 @@ module.exports = {
|
||||
return res.result;
|
||||
},
|
||||
|
||||
updateCollection_meta: 'post',
|
||||
async updateCollection({ conid, database, changeSet }) {
|
||||
const opened = await this.ensureOpened(conid, database);
|
||||
const res = await this.sendRequest(opened, { msgtype: 'updateCollection', changeSet });
|
||||
return res.result;
|
||||
},
|
||||
|
||||
status_meta: 'get',
|
||||
async status({ conid, database }) {
|
||||
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
||||
@@ -141,7 +120,7 @@ module.exports = {
|
||||
} else {
|
||||
existing = await this.ensureOpened(conid, database);
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
status: 'ok',
|
||||
connectionStatus: existing ? existing.status : null,
|
||||
@@ -149,8 +128,8 @@ module.exports = {
|
||||
},
|
||||
|
||||
refresh_meta: 'post',
|
||||
async refresh({ conid, database, keepOpen }) {
|
||||
if (!keepOpen) this.close(conid, database);
|
||||
async refresh({ conid, database }) {
|
||||
this.close(conid, database);
|
||||
|
||||
await this.ensureOpened(conid, database);
|
||||
return { status: 'ok' };
|
||||
@@ -173,12 +152,6 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
disconnect_meta: 'post',
|
||||
async disconnect({ conid, database }) {
|
||||
await this.close(conid, database, true);
|
||||
return { status: 'ok' };
|
||||
},
|
||||
|
||||
structure_meta: 'get',
|
||||
async structure({ conid, database }) {
|
||||
const opened = await this.ensureOpened(conid, database);
|
||||
@@ -191,12 +164,6 @@ module.exports = {
|
||||
// };
|
||||
},
|
||||
|
||||
serverVersion_meta: 'get',
|
||||
async serverVersion({ conid, database }) {
|
||||
const opened = await this.ensureOpened(conid, database);
|
||||
return opened.serverVersion;
|
||||
},
|
||||
|
||||
sqlPreview_meta: 'post',
|
||||
async sqlPreview({ conid, database, objects, options }) {
|
||||
// wait for structure
|
||||
|
||||
@@ -2,21 +2,43 @@ const fs = require('fs-extra');
|
||||
const axios = require('axios');
|
||||
const path = require('path');
|
||||
const { extractPackageName } = require('dbgate-tools');
|
||||
const { pluginsdir, packagedPluginsDir } = require('../utility/directories');
|
||||
const { pluginsdir, datadir } = require('../utility/directories');
|
||||
const socket = require('../utility/socket');
|
||||
const compareVersions = require('compare-versions');
|
||||
const requirePlugin = require('../shell/requirePlugin');
|
||||
const downloadPackage = require('../utility/downloadPackage');
|
||||
const hasPermission = require('../utility/hasPermission');
|
||||
const _ = require('lodash');
|
||||
|
||||
// async function loadPackageInfo(dir) {
|
||||
// const readmeFile = path.join(dir, 'README.md');
|
||||
// const packageFile = path.join(dir, 'package.json');
|
||||
|
||||
// if (!(await fs.exists(packageFile))) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// let readme = null;
|
||||
// let manifest = null;
|
||||
// if (await fs.exists(readmeFile)) readme = await fs.readFile(readmeFile, { encoding: 'utf-8' });
|
||||
// if (await fs.exists(packageFile)) manifest = JSON.parse(await fs.readFile(packageFile, { encoding: 'utf-8' }));
|
||||
// return {
|
||||
// readme,
|
||||
// manifest,
|
||||
// };
|
||||
// }
|
||||
|
||||
const preinstallPluginMinimalVersions = {
|
||||
'dbgate-plugin-mssql': '1.2.1',
|
||||
'dbgate-plugin-mysql': '1.2.1',
|
||||
'dbgate-plugin-postgres': '1.2.1',
|
||||
'dbgate-plugin-csv': '1.0.8',
|
||||
'dbgate-plugin-excel': '1.0.6',
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
script_meta: 'get',
|
||||
async script({ packageName }) {
|
||||
const file1 = path.join(packagedPluginsDir(), packageName, 'dist', 'frontend.js');
|
||||
const file2 = path.join(pluginsdir(), packageName, 'dist', 'frontend.js');
|
||||
// @ts-ignore
|
||||
const file = (await fs.exists(file1)) ? file1 : file2;
|
||||
const file = path.join(pluginsdir(), packageName, 'dist', 'frontend.js');
|
||||
const data = await fs.readFile(file, {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
@@ -40,13 +62,10 @@ module.exports = {
|
||||
const { latest } = infoResp.data['dist-tags'];
|
||||
const manifest = infoResp.data.versions[latest];
|
||||
const { readme } = infoResp.data;
|
||||
// @ts-ignore
|
||||
const isPackaged = await fs.exists(path.join(packagedPluginsDir(), packageName));
|
||||
|
||||
return {
|
||||
readme,
|
||||
manifest,
|
||||
isPackaged,
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
@@ -54,52 +73,52 @@ module.exports = {
|
||||
error: err.message,
|
||||
};
|
||||
}
|
||||
|
||||
// const dir = path.join(pluginstmpdir(), packageName);
|
||||
// if (!(await fs.exists(dir))) {
|
||||
// await downloadPackage(packageName, dir);
|
||||
// }
|
||||
// return await loadPackageInfo(dir);
|
||||
// return await {
|
||||
// ...loadPackageInfo(dir),
|
||||
// installed: loadPackageInfo(path.join(pluginsdir(), packageName)),
|
||||
// };
|
||||
},
|
||||
|
||||
installed_meta: 'get',
|
||||
async installed() {
|
||||
const files1 = await fs.readdir(packagedPluginsDir());
|
||||
const files2 = await fs.readdir(pluginsdir());
|
||||
|
||||
const files = await fs.readdir(pluginsdir());
|
||||
const res = [];
|
||||
for (const packageName of _.union(files1, files2)) {
|
||||
if (!/^dbgate-plugin-.*$/.test(packageName)) continue;
|
||||
try {
|
||||
const isPackaged = files1.includes(packageName);
|
||||
const manifest = await fs
|
||||
.readFile(path.join(isPackaged ? packagedPluginsDir() : pluginsdir(), packageName, 'package.json'), {
|
||||
encoding: 'utf-8',
|
||||
})
|
||||
.then(x => JSON.parse(x));
|
||||
const readmeFile = path.join(isPackaged ? packagedPluginsDir() : pluginsdir(), packageName, 'README.md');
|
||||
// @ts-ignore
|
||||
if (await fs.exists(readmeFile)) {
|
||||
manifest.readme = await fs.readFile(readmeFile, { encoding: 'utf-8' });
|
||||
}
|
||||
manifest.isPackaged = isPackaged;
|
||||
res.push(manifest);
|
||||
} catch (err) {
|
||||
console.log(`Skipped plugin ${packageName}, error:`, err.message);
|
||||
for (const packageName of files) {
|
||||
const manifest = await fs.readFile(path.join(pluginsdir(), packageName, 'package.json')).then(x => JSON.parse(x));
|
||||
const readmeFile = path.join(pluginsdir(), packageName, 'README.md');
|
||||
if (await fs.exists(readmeFile)) {
|
||||
manifest.readme = await fs.readFile(readmeFile, { encoding: 'utf-8' });
|
||||
}
|
||||
res.push(manifest);
|
||||
}
|
||||
return res;
|
||||
// const res = await Promise.all(
|
||||
// files.map((packageName) =>
|
||||
// fs.readFile(path.join(pluginsdir(), packageName, 'package.json')).then((x) => JSON.parse(x))
|
||||
// )
|
||||
// );
|
||||
},
|
||||
|
||||
// async saveRemovePlugins() {
|
||||
// await fs.writeFile(path.join(datadir(), 'removed-plugins'), this.removedPlugins.join('\n'));
|
||||
// },
|
||||
async saveRemovePlugins() {
|
||||
await fs.writeFile(path.join(datadir(), 'removed-plugins'), this.removedPlugins.join('\n'));
|
||||
},
|
||||
|
||||
install_meta: 'post',
|
||||
async install({ packageName }) {
|
||||
if (!hasPermission(`plugins/install`)) return;
|
||||
const dir = path.join(pluginsdir(), packageName);
|
||||
// @ts-ignore
|
||||
if (!(await fs.exists(dir))) {
|
||||
await downloadPackage(packageName, dir);
|
||||
}
|
||||
socket.emitChanged(`installed-plugins-changed`);
|
||||
// this.removedPlugins = this.removedPlugins.filter(x => x != packageName);
|
||||
// await this.saveRemovePlugins();
|
||||
this.removedPlugins = this.removedPlugins.filter(x => x != packageName);
|
||||
await this.saveRemovePlugins();
|
||||
},
|
||||
|
||||
uninstall_meta: 'post',
|
||||
@@ -108,7 +127,7 @@ module.exports = {
|
||||
const dir = path.join(pluginsdir(), packageName);
|
||||
await fs.rmdir(dir, { recursive: true });
|
||||
socket.emitChanged(`installed-plugins-changed`);
|
||||
// this.removedPlugins.push(packageName);
|
||||
this.removedPlugins.push(packageName);
|
||||
await this.saveRemovePlugins();
|
||||
},
|
||||
|
||||
@@ -116,7 +135,6 @@ module.exports = {
|
||||
async upgrade({ packageName }) {
|
||||
if (!hasPermission(`plugins/install`)) return;
|
||||
const dir = path.join(pluginsdir(), packageName);
|
||||
// @ts-ignore
|
||||
if (await fs.exists(dir)) {
|
||||
await fs.rmdir(dir, { recursive: true });
|
||||
await downloadPackage(packageName, dir);
|
||||
@@ -139,46 +157,46 @@ module.exports = {
|
||||
return content.driver.getAuthTypes() || null;
|
||||
},
|
||||
|
||||
// async _init() {
|
||||
// const installed = await this.installed();
|
||||
// try {
|
||||
// this.removedPlugins = (await fs.readFile(path.join(datadir(), 'removed-plugins'), { encoding: 'utf-8' })).split(
|
||||
// '\n'
|
||||
// );
|
||||
// } catch (err) {
|
||||
// this.removedPlugins = [];
|
||||
// }
|
||||
async _init() {
|
||||
const installed = await this.installed();
|
||||
try {
|
||||
this.removedPlugins = (await fs.readFile(path.join(datadir(), 'removed-plugins'), { encoding: 'utf-8' })).split(
|
||||
'\n'
|
||||
);
|
||||
} catch (err) {
|
||||
this.removedPlugins = [];
|
||||
}
|
||||
|
||||
// for (const packageName of Object.keys(preinstallPluginMinimalVersions)) {
|
||||
// const installedVersion = installed.find(x => x.name == packageName);
|
||||
// if (installedVersion) {
|
||||
// // plugin installed, test, whether upgrade
|
||||
// const requiredVersion = preinstallPluginMinimalVersions[packageName];
|
||||
// if (compareVersions(installedVersion.version, requiredVersion) < 0) {
|
||||
// console.log(
|
||||
// `Upgrading preinstalled plugin ${packageName}, found ${installedVersion.version}, required version ${requiredVersion}`
|
||||
// );
|
||||
// await this.upgrade({ packageName });
|
||||
// } else {
|
||||
// console.log(
|
||||
// `Plugin ${packageName} not upgraded, found ${installedVersion.version}, required version ${requiredVersion}`
|
||||
// );
|
||||
// }
|
||||
for (const packageName of Object.keys(preinstallPluginMinimalVersions)) {
|
||||
const installedVersion = installed.find(x => x.name == packageName);
|
||||
if (installedVersion) {
|
||||
// plugin installed, test, whether upgrade
|
||||
const requiredVersion = preinstallPluginMinimalVersions[packageName];
|
||||
if (compareVersions(installedVersion.version, requiredVersion) < 0) {
|
||||
console.log(
|
||||
`Upgrading preinstalled plugin ${packageName}, found ${installedVersion.version}, required version ${requiredVersion}`
|
||||
);
|
||||
await this.upgrade({ packageName });
|
||||
} else {
|
||||
console.log(
|
||||
`Plugin ${packageName} not upgraded, found ${installedVersion.version}, required version ${requiredVersion}`
|
||||
);
|
||||
}
|
||||
|
||||
// continue;
|
||||
// }
|
||||
continue;
|
||||
}
|
||||
|
||||
// if (this.removedPlugins.includes(packageName)) {
|
||||
// // plugin was remvoed, don't install again
|
||||
// continue;
|
||||
// }
|
||||
if (this.removedPlugins.includes(packageName)) {
|
||||
// plugin was remvoed, don't install again
|
||||
continue;
|
||||
}
|
||||
|
||||
// try {
|
||||
// console.log('Preinstalling plugin', packageName);
|
||||
// await this.install({ packageName });
|
||||
// } catch (err) {
|
||||
// console.error('Error preinstalling plugin', packageName, err);
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
try {
|
||||
console.log('Preinstalling plugin', packageName);
|
||||
await this.install({ packageName });
|
||||
} catch (err) {
|
||||
console.error('Error preinstalling plugin', packageName, err);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ const uuidv1 = require('uuid/v1');
|
||||
const byline = require('byline');
|
||||
const socket = require('../utility/socket');
|
||||
const { fork } = require('child_process');
|
||||
const { rundir, uploadsdir, pluginsdir, getPluginBackendPath, packagedPluginList } = require('../utility/directories');
|
||||
const { rundir, uploadsdir, pluginsdir } = require('../utility/directories');
|
||||
const { extractShellApiPlugins, extractShellApiFunctionName } = require('dbgate-tools');
|
||||
const { handleProcessCommunication } = require('../utility/processComm');
|
||||
|
||||
@@ -92,7 +92,7 @@ module.exports = {
|
||||
const scriptFile = path.join(uploadsdir(), runid + '.js');
|
||||
fs.writeFileSync(`${scriptFile}`, scriptText);
|
||||
fs.mkdirSync(directory);
|
||||
const pluginNames = _.union(fs.readdirSync(pluginsdir()), packagedPluginList);
|
||||
const pluginNames = fs.readdirSync(pluginsdir());
|
||||
console.log(`RUNNING SCRIPT ${scriptFile}`);
|
||||
// const subprocess = fork(scriptFile, ['--checkParent', '--max-old-space-size=8192'], {
|
||||
const subprocess = fork(scriptFile, ['--checkParent', ...process.argv.slice(3)], {
|
||||
@@ -101,7 +101,7 @@ module.exports = {
|
||||
env: {
|
||||
...process.env,
|
||||
DBGATE_API: global['dbgateApiModulePath'] || process.argv[1],
|
||||
..._.fromPairs(pluginNames.map(name => [`PLUGIN_${_.camelCase(name)}`, getPluginBackendPath(name)])),
|
||||
..._.fromPairs(pluginNames.map(name => [`PLUGIN_${_.camelCase(name)}`, path.join(pluginsdir(), name)])),
|
||||
},
|
||||
});
|
||||
const pipeDispatcher = severity => data =>
|
||||
|
||||
@@ -5,7 +5,6 @@ const _ = require('lodash');
|
||||
const AsyncLock = require('async-lock');
|
||||
const { handleProcessCommunication } = require('../utility/processComm');
|
||||
const lock = new AsyncLock();
|
||||
const config = require('./config');
|
||||
|
||||
module.exports = {
|
||||
opened: [],
|
||||
@@ -18,12 +17,6 @@ module.exports = {
|
||||
existing.databases = databases;
|
||||
socket.emitChanged(`database-list-changed-${conid}`);
|
||||
},
|
||||
handle_version(conid, { version }) {
|
||||
const existing = this.opened.find(x => x.conid == conid);
|
||||
if (!existing) return;
|
||||
existing.version = version;
|
||||
socket.emitChanged(`server-version-changed-${conid}`);
|
||||
},
|
||||
handle_status(conid, { status }) {
|
||||
const existing = this.opened.find(x => x.conid == conid);
|
||||
if (!existing) return;
|
||||
@@ -37,11 +30,7 @@ module.exports = {
|
||||
const existing = this.opened.find(x => x.conid == conid);
|
||||
if (existing) return existing;
|
||||
const connection = await connections.get({ conid });
|
||||
const subprocess = fork(process.argv[1], [
|
||||
'--start-process',
|
||||
'serverConnectionProcess',
|
||||
...process.argv.slice(3),
|
||||
]);
|
||||
const subprocess = fork(process.argv[1], ['serverConnectionProcess', ...process.argv.slice(3)]);
|
||||
const newOpened = {
|
||||
conid,
|
||||
subprocess,
|
||||
@@ -66,7 +55,7 @@ module.exports = {
|
||||
if (newOpened.disconnected) return;
|
||||
this.close(conid, false);
|
||||
});
|
||||
subprocess.send({ msgtype: 'connect', ...connection, globalSettings: config.settingsValue });
|
||||
subprocess.send({ msgtype: 'connect', ...connection });
|
||||
return newOpened;
|
||||
});
|
||||
return res;
|
||||
@@ -86,24 +75,12 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
disconnect_meta: 'post',
|
||||
async disconnect({ conid }) {
|
||||
await this.close(conid, true);
|
||||
return { status: 'ok' };
|
||||
},
|
||||
|
||||
listDatabases_meta: 'get',
|
||||
async listDatabases({ conid }) {
|
||||
const opened = await this.ensureOpened(conid);
|
||||
return opened.databases;
|
||||
},
|
||||
|
||||
version_meta: 'get',
|
||||
async version({ conid }) {
|
||||
const opened = await this.ensureOpened(conid);
|
||||
return opened.version;
|
||||
},
|
||||
|
||||
serverStatus_meta: 'get',
|
||||
async serverStatus() {
|
||||
return {
|
||||
@@ -129,8 +106,8 @@ module.exports = {
|
||||
},
|
||||
|
||||
refresh_meta: 'post',
|
||||
async refresh({ conid, keepOpen }) {
|
||||
if (!keepOpen) this.close(conid);
|
||||
async refresh({ conid }) {
|
||||
this.close(conid);
|
||||
|
||||
await this.ensureOpened(conid);
|
||||
return { status: 'ok' };
|
||||
|
||||
@@ -65,7 +65,7 @@ module.exports = {
|
||||
async create({ conid, database }) {
|
||||
const sesid = uuidv1();
|
||||
const connection = await connections.get({ conid });
|
||||
const subprocess = fork(process.argv[1], ['--start-process', 'sessionProcess', ...process.argv.slice(3)]);
|
||||
const subprocess = fork(process.argv[1], ['sessionProcess', ...process.argv.slice(3)]);
|
||||
const newOpened = {
|
||||
conid,
|
||||
database,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
module.exports = {
|
||||
version: '4.1.1',
|
||||
buildTime: '2021-04-17T07:22:49.702Z'
|
||||
version: '4.0.0',
|
||||
buildTime: '2021-04-01T10:48:22.253Z'
|
||||
};
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
const shell = require('./shell');
|
||||
const processArgs = require('./utility/processArgs');
|
||||
|
||||
if (processArgs.startProcess) {
|
||||
const argument = process.argv[2];
|
||||
if (argument && argument.endsWith('Process')) {
|
||||
const proc = require('./proc');
|
||||
const module = proc[processArgs.startProcess];
|
||||
|
||||
const module = proc[argument];
|
||||
module.start();
|
||||
} else if (!module['parent'] && !processArgs.checkParent) {
|
||||
} else if (!module['parent'] && !process.argv.includes('--checkParent')) {
|
||||
const main = require('./main');
|
||||
|
||||
main.start();
|
||||
main.start(argument);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -6,10 +6,9 @@ const http = require('http');
|
||||
const cors = require('cors');
|
||||
const io = require('socket.io');
|
||||
const fs = require('fs');
|
||||
const getPort = require('get-port');
|
||||
const findFreePort = require('find-free-port');
|
||||
const childProcessChecker = require('./utility/childProcessChecker');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const useController = require('./utility/useController');
|
||||
const socket = require('./utility/socket');
|
||||
@@ -29,14 +28,8 @@ const files = require('./controllers/files');
|
||||
const scheduler = require('./controllers/scheduler');
|
||||
|
||||
const { rundir } = require('./utility/directories');
|
||||
const platformInfo = require('./utility/platformInfo');
|
||||
const processArgs = require('./utility/processArgs');
|
||||
const timingSafeCheckToken = require('./utility/timingSafeCheckToken');
|
||||
|
||||
let authorization = null;
|
||||
let checkLocalhostOrigin = null;
|
||||
|
||||
function start() {
|
||||
function start(argument = null) {
|
||||
// console.log('process.argv', process.argv);
|
||||
|
||||
const app = express();
|
||||
@@ -56,29 +49,6 @@ function start() {
|
||||
);
|
||||
}
|
||||
|
||||
app.use(function (req, res, next) {
|
||||
if (authorization && !timingSafeCheckToken(req.headers.authorization, authorization)) {
|
||||
return res.status(403).json({ error: 'Not authorized!' });
|
||||
}
|
||||
if (checkLocalhostOrigin) {
|
||||
if (
|
||||
req.headers.origin &&
|
||||
req.headers.origin != checkLocalhostOrigin &&
|
||||
req.headers.origin != `http://${checkLocalhostOrigin}`
|
||||
) {
|
||||
console.log('API origin check FAILED');
|
||||
console.log('HEADERS', { ...req.headers, authorization: '***' });
|
||||
return res.status(403).json({ error: 'Not authorized!' });
|
||||
}
|
||||
if (!req.headers.origin && req.headers.host != checkLocalhostOrigin) {
|
||||
console.log('API host check FAILED');
|
||||
console.log('HEADERS', { ...req.headers, authorization: '***' });
|
||||
return res.status(403).json({ error: 'Not authorized!' });
|
||||
}
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
app.use(cors());
|
||||
app.use(bodyParser.json({ limit: '50mb' }));
|
||||
|
||||
@@ -109,32 +79,29 @@ function start() {
|
||||
|
||||
app.use('/runners/data', express.static(rundir()));
|
||||
|
||||
if (platformInfo.isDocker) {
|
||||
if (fs.existsSync('/home/dbgate-docker/public')) {
|
||||
// server static files inside docker container
|
||||
app.use(express.static('/home/dbgate-docker/public'));
|
||||
} else {
|
||||
if (!platformInfo.isNpmDist) {
|
||||
if (argument != 'startNodeWeb') {
|
||||
app.get('/', (req, res) => {
|
||||
res.send('DbGate API');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (processArgs.dynport) {
|
||||
if (argument == '--dynport') {
|
||||
childProcessChecker();
|
||||
|
||||
authorization = crypto.randomBytes(32).toString('hex');
|
||||
|
||||
getPort().then(port => {
|
||||
checkLocalhostOrigin = `localhost:${port}`;
|
||||
findFreePort(53911, function (err, port) {
|
||||
server.listen(port, () => {
|
||||
console.log(`DbGate API listening on port ${port}`);
|
||||
process.send({ msgtype: 'listening', port, authorization });
|
||||
process.send({ msgtype: 'listening', port });
|
||||
});
|
||||
});
|
||||
} else if (platformInfo.isNpmDist) {
|
||||
} else if (argument == 'startNodeWeb') {
|
||||
app.use(express.static(path.join(__dirname, '../../dbgate-web/public')));
|
||||
getPort({ port: 5000 }).then(port => {
|
||||
findFreePort(5000, function (err, port) {
|
||||
server.listen(port, () => {
|
||||
console.log(`DbGate API listening on port ${port}`);
|
||||
});
|
||||
|
||||
@@ -2,25 +2,6 @@ const childProcessChecker = require('../utility/childProcessChecker');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const connectUtility = require('../utility/connectUtility');
|
||||
const { handleProcessCommunication } = require('../utility/processComm');
|
||||
const _ = require('lodash');
|
||||
|
||||
function pickSafeConnectionInfo(connection) {
|
||||
return _.mapValues(connection, (v, k) => {
|
||||
if (k == 'engine' || k == 'port' || k == 'authType' || k == 'sshMode' || k == 'passwordMode') return v;
|
||||
if (v === null || v === true || v === false) return v;
|
||||
if (v) return '***';
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
const formatErrorDetail = (e, connection) => `${e.stack}
|
||||
|
||||
Error JSON: ${JSON.stringify(e, undefined, 2)}
|
||||
|
||||
Connection: ${JSON.stringify(pickSafeConnectionInfo(connection), undefined, 2)}
|
||||
|
||||
Platform: ${process.platform}
|
||||
`;
|
||||
|
||||
function start() {
|
||||
childProcessChecker();
|
||||
@@ -33,11 +14,7 @@ function start() {
|
||||
process.send({ msgtype: 'connected', ...res });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.send({
|
||||
msgtype: 'error',
|
||||
error: e.message,
|
||||
detail: formatErrorDetail(e, connection),
|
||||
});
|
||||
process.send({ msgtype: 'error', error: e.message });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
const stableStringify = require('json-stable-stringify');
|
||||
const childProcessChecker = require('../utility/childProcessChecker');
|
||||
const { extractBoolSettingsValue, extractIntSettingsValue } = require('dbgate-tools');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const connectUtility = require('../utility/connectUtility');
|
||||
const { handleProcessCommunication } = require('../utility/processComm');
|
||||
@@ -30,7 +29,6 @@ async function checkedAsyncCall(promise) {
|
||||
|
||||
async function handleFullRefresh() {
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
setStatusName('loadStructure');
|
||||
analysedStructure = await checkedAsyncCall(driver.analyseFull(systemConnection));
|
||||
process.send({ msgtype: 'structure', structure: analysedStructure });
|
||||
setStatusName('ok');
|
||||
@@ -38,7 +36,6 @@ async function handleFullRefresh() {
|
||||
|
||||
async function handleIncrementalRefresh() {
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
setStatusName('checkStructure');
|
||||
const newStructure = await checkedAsyncCall(driver.analyseIncremental(systemConnection, analysedStructure));
|
||||
if (newStructure != null) {
|
||||
analysedStructure = newStructure;
|
||||
@@ -59,34 +56,20 @@ function setStatusName(name) {
|
||||
setStatus({ name });
|
||||
}
|
||||
|
||||
async function readVersion() {
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
const version = await driver.getVersion(systemConnection);
|
||||
process.send({ msgtype: 'version', version });
|
||||
}
|
||||
|
||||
async function handleConnect({ connection, structure, globalSettings }) {
|
||||
async function handleConnect({ connection, structure }) {
|
||||
storedConnection = connection;
|
||||
lastPing = new Date().getTime();
|
||||
|
||||
if (!structure) setStatusName('pending');
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
systemConnection = await checkedAsyncCall(connectUtility(driver, storedConnection));
|
||||
readVersion();
|
||||
if (structure) {
|
||||
analysedStructure = structure;
|
||||
handleIncrementalRefresh();
|
||||
} else {
|
||||
handleFullRefresh();
|
||||
}
|
||||
|
||||
if (extractBoolSettingsValue(globalSettings, 'connection.autoRefresh', true)) {
|
||||
setInterval(
|
||||
handleIncrementalRefresh,
|
||||
extractIntSettingsValue(globalSettings, 'connection.autoRefreshInterval', 30, 3, 3600) * 1000
|
||||
);
|
||||
}
|
||||
|
||||
setInterval(handleIncrementalRefresh, 30 * 1000);
|
||||
for (const [resolve] of afterConnectCallbacks) {
|
||||
resolve();
|
||||
}
|
||||
@@ -122,17 +105,6 @@ async function handleCollectionData({ msgid, options }) {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleUpdateCollection({ msgid, changeSet }) {
|
||||
await waitConnected();
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
const result = await driver.updateCollection(systemConnection, changeSet);
|
||||
process.send({ msgtype: 'response', msgid, result });
|
||||
} catch (err) {
|
||||
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSqlPreview({ msgid, objects, options }) {
|
||||
await waitConnected();
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
@@ -168,7 +140,6 @@ function handlePing() {
|
||||
const messageHandlers = {
|
||||
connect: handleConnect,
|
||||
queryData: handleQueryData,
|
||||
updateCollection: handleUpdateCollection,
|
||||
collectionData: handleCollectionData,
|
||||
sqlPreview: handleSqlPreview,
|
||||
ping: handlePing,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const stableStringify = require('json-stable-stringify');
|
||||
const { extractBoolSettingsValue, extractIntSettingsValue } = require('dbgate-tools');
|
||||
const childProcessChecker = require('../utility/childProcessChecker');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const { decryptConnection } = require('../utility/crypting');
|
||||
@@ -32,12 +31,6 @@ async function handleRefresh() {
|
||||
}
|
||||
}
|
||||
|
||||
async function readVersion() {
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
const version = await driver.getVersion(systemConnection);
|
||||
process.send({ msgtype: 'version', version });
|
||||
}
|
||||
|
||||
function setStatus(status) {
|
||||
const statusString = stableStringify(status);
|
||||
if (lastStatus != statusString) {
|
||||
@@ -52,18 +45,14 @@ function setStatusName(name) {
|
||||
|
||||
async function handleConnect(connection) {
|
||||
storedConnection = connection;
|
||||
const { globalSettings } = storedConnection;
|
||||
setStatusName('pending');
|
||||
lastPing = new Date().getTime();
|
||||
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
systemConnection = await connectUtility(driver, storedConnection);
|
||||
readVersion();
|
||||
handleRefresh();
|
||||
if (extractBoolSettingsValue(globalSettings, 'connection.autoRefresh', true)) {
|
||||
setInterval(handleRefresh, extractIntSettingsValue(globalSettings, 'connection.autoRefreshInterval', 30, 5, 3600) * 1000);
|
||||
}
|
||||
setInterval(handleRefresh, 30 * 1000);
|
||||
} catch (err) {
|
||||
setStatus({
|
||||
name: 'error',
|
||||
@@ -82,11 +71,7 @@ async function handleCreateDatabase({ name }) {
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
systemConnection = await connectUtility(driver, storedConnection);
|
||||
console.log(`RUNNING SCRIPT: CREATE DATABASE ${driver.dialect.quoteIdentifier(name)}`);
|
||||
if (driver.createDatabase) {
|
||||
await driver.createDatabase(systemConnection, name);
|
||||
} else {
|
||||
await driver.query(systemConnection, `CREATE DATABASE ${driver.dialect.quoteIdentifier(name)}`);
|
||||
}
|
||||
await driver.query(systemConnection, `CREATE DATABASE ${driver.dialect.quoteIdentifier(name)}`);
|
||||
await handleRefresh();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,18 +17,12 @@ let afterConnectCallbacks = [];
|
||||
// let currentHandlers = [];
|
||||
|
||||
class TableWriter {
|
||||
constructor(structure, resultIndex) {
|
||||
constructor(columns, resultIndex) {
|
||||
this.jslid = uuidv1();
|
||||
this.currentFile = path.join(jsldir(), `${this.jslid}.jsonl`);
|
||||
this.currentRowCount = 0;
|
||||
this.currentChangeIndex = 1;
|
||||
fs.writeFileSync(
|
||||
this.currentFile,
|
||||
JSON.stringify({
|
||||
...structure,
|
||||
__isStreamHeader: true,
|
||||
}) + '\n'
|
||||
);
|
||||
fs.writeFileSync(this.currentFile, JSON.stringify({ columns }) + '\n');
|
||||
this.currentStream = fs.createWriteStream(this.currentFile, { flags: 'a' });
|
||||
this.writeCurrentStats(false, false);
|
||||
this.resultIndex = resultIndex;
|
||||
@@ -98,7 +92,7 @@ class StreamHandler {
|
||||
|
||||
recordset(columns) {
|
||||
this.closeCurrentWriter();
|
||||
this.currentWriter = new TableWriter(Array.isArray(columns) ? { columns } : columns, this.resultIndexHolder.value);
|
||||
this.currentWriter = new TableWriter(columns, this.resultIndexHolder.value);
|
||||
this.resultIndexHolder.value += 1;
|
||||
|
||||
// this.writeCurrentStats();
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
const EnsureStreamHeaderStream = require('../utility/EnsureStreamHeaderStream');
|
||||
|
||||
function copyStream(input, output) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const ensureHeader = new EnsureStreamHeaderStream();
|
||||
const finisher = output['finisher'] || output;
|
||||
finisher.on('finish', resolve);
|
||||
finisher.on('error', reject);
|
||||
input.pipe(ensureHeader);
|
||||
ensureHeader.pipe(output);
|
||||
input.pipe(output);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@ const stream = require('stream');
|
||||
const byline = require('byline');
|
||||
|
||||
class ParseStream extends stream.Transform {
|
||||
constructor({ limitRows }) {
|
||||
constructor({ header, limitRows }) {
|
||||
super({ objectMode: true });
|
||||
this.header = header;
|
||||
this.wasHeader = false;
|
||||
this.limitRows = limitRows;
|
||||
this.rowsWritten = 0;
|
||||
@@ -12,14 +13,7 @@ class ParseStream extends stream.Transform {
|
||||
_transform(chunk, encoding, done) {
|
||||
const obj = JSON.parse(chunk);
|
||||
if (!this.wasHeader) {
|
||||
if (
|
||||
!obj.__isStreamHeader &&
|
||||
// TODO remove isArray test
|
||||
!Array.isArray(obj.columns)
|
||||
) {
|
||||
this.push({ columns: Object.keys(obj).map(columnName => ({ columnName })) });
|
||||
}
|
||||
|
||||
if (!this.header) this.push({ columns: Object.keys(obj).map(columnName => ({ columnName })) });
|
||||
this.wasHeader = true;
|
||||
}
|
||||
if (!this.limitRows || this.rowsWritten < this.limitRows) {
|
||||
@@ -30,12 +24,12 @@ class ParseStream extends stream.Transform {
|
||||
}
|
||||
}
|
||||
|
||||
async function jsonLinesReader({ fileName, encoding = 'utf-8', limitRows = undefined }) {
|
||||
async function jsonLinesReader({ fileName, encoding = 'utf-8', header = true, limitRows = undefined }) {
|
||||
console.log(`Reading file ${fileName}`);
|
||||
|
||||
const fileStream = fs.createReadStream(fileName, encoding);
|
||||
const liner = byline(fileStream);
|
||||
const parser = new ParseStream({ limitRows });
|
||||
const parser = new ParseStream({ header, limitRows });
|
||||
liner.pipe(parser);
|
||||
return parser;
|
||||
}
|
||||
|
||||
@@ -8,16 +8,10 @@ class StringifyStream extends stream.Transform {
|
||||
this.wasHeader = false;
|
||||
}
|
||||
_transform(chunk, encoding, done) {
|
||||
let skip = false;
|
||||
if (!this.wasHeader) {
|
||||
skip =
|
||||
(chunk.__isStreamHeader ||
|
||||
// TODO remove isArray test
|
||||
Array.isArray(chunk.columns)) &&
|
||||
!this.header;
|
||||
if (this.header) this.push(JSON.stringify(chunk) + '\n');
|
||||
this.wasHeader = true;
|
||||
}
|
||||
if (!skip) {
|
||||
} else {
|
||||
this.push(JSON.stringify(chunk) + '\n');
|
||||
}
|
||||
done();
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { pluginsdir, packagedPluginsDir, getPluginBackendPath } = require('../utility/directories');
|
||||
const { pluginsdir } = require('../utility/directories');
|
||||
const nativeModules = require('../nativeModules');
|
||||
const platformInfo = require('../utility/platformInfo');
|
||||
|
||||
const loadedPlugins = {};
|
||||
|
||||
@@ -10,19 +8,20 @@ const dbgateEnv = {
|
||||
dbgateApi: null,
|
||||
nativeModules,
|
||||
};
|
||||
|
||||
function requirePlugin(packageName, requiredPlugin = null) {
|
||||
if (!packageName) throw new Error('Missing packageName in plugin');
|
||||
if (loadedPlugins[packageName]) return loadedPlugins[packageName];
|
||||
|
||||
if (requiredPlugin == null) {
|
||||
let module;
|
||||
const modulePath = getPluginBackendPath(packageName);
|
||||
const modulePath = path.join(pluginsdir(), packageName, 'dist', 'backend.js');
|
||||
console.log(`Loading module ${packageName} from ${modulePath}`);
|
||||
try {
|
||||
// @ts-ignore
|
||||
module = __non_webpack_require__(modulePath);
|
||||
} catch (err) {
|
||||
console.log('Failed load webpacked module', err.message);
|
||||
console.error('Failed load webpacked module', err);
|
||||
module = require(modulePath);
|
||||
}
|
||||
requiredPlugin = module.__esModule ? module.default : module;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
const childProcessChecker = require('../utility/childProcessChecker');
|
||||
const processArgs = require('../utility/processArgs');
|
||||
|
||||
async function runScript(func) {
|
||||
if (processArgs.checkParent) {
|
||||
if (process.argv.includes('--checkParent')) {
|
||||
childProcessChecker();
|
||||
}
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const { quoteFullName, fullNameToString } = require('dbgate-tools');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const { decryptConnection } = require('../utility/crypting');
|
||||
const connectUtility = require('../utility/connectUtility');
|
||||
|
||||
async function tableReader({ connection, pureName, schemaName }) {
|
||||
@@ -9,13 +10,6 @@ async function tableReader({ connection, pureName, schemaName }) {
|
||||
|
||||
const fullName = { pureName, schemaName };
|
||||
|
||||
if (driver.dialect.nosql) {
|
||||
// @ts-ignore
|
||||
console.log(`Reading collection ${fullNameToString(fullName)}`);
|
||||
// @ts-ignore
|
||||
return await driver.readQuery(pool, JSON.stringify(fullName));
|
||||
}
|
||||
|
||||
const table = await driver.analyseSingleObject(pool, fullName, 'tables');
|
||||
const query = `select * from ${quoteFullName(driver.dialect, fullName)}`;
|
||||
if (table) {
|
||||
|
||||
@@ -29,7 +29,7 @@ class DatastoreProxy {
|
||||
|
||||
async ensureSubprocess() {
|
||||
if (!this.subprocess) {
|
||||
this.subprocess = fork(process.argv[1], ['--start-process', 'jslDatastoreProcess', ...process.argv.slice(3)]);
|
||||
this.subprocess = fork(process.argv[1], ['jslDatastoreProcess', ...process.argv.slice(3)]);
|
||||
|
||||
this.subprocess.on('message', message => {
|
||||
// @ts-ignore
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
const stream = require('stream');
|
||||
|
||||
class EnsureStreamHeaderStream extends stream.Transform {
|
||||
constructor() {
|
||||
super({ objectMode: true });
|
||||
this.wasHeader = false;
|
||||
}
|
||||
_transform(chunk, encoding, done) {
|
||||
if (!this.wasHeader) {
|
||||
if (chunk.__isDynamicStructure) {
|
||||
// ignore dynamic structure header
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!chunk.__isStreamHeader &&
|
||||
// TODO remove isArray test
|
||||
!Array.isArray(chunk.columns)
|
||||
) {
|
||||
this.push({
|
||||
__isStreamHeader: true,
|
||||
__isComputedStructure: true,
|
||||
columns: Object.keys(chunk).map(columnName => ({ columnName })),
|
||||
});
|
||||
}
|
||||
|
||||
this.wasHeader = true;
|
||||
}
|
||||
this.push(chunk);
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EnsureStreamHeaderStream;
|
||||
@@ -7,7 +7,6 @@ const { getSshTunnelProxy } = require('./sshTunnelProxy');
|
||||
|
||||
async function connectUtility(driver, storedConnection) {
|
||||
const connection = {
|
||||
database: storedConnection.defaultDatabase,
|
||||
...decryptConnection(storedConnection),
|
||||
};
|
||||
|
||||
@@ -39,10 +38,6 @@ async function connectUtility(driver, storedConnection) {
|
||||
connection.ssl.key = await fs.readFile(connection.sslKeyFile);
|
||||
}
|
||||
|
||||
if (connection.sslCertFilePassword) {
|
||||
connection.ssl.password = connection.sslCertFilePassword;
|
||||
}
|
||||
|
||||
if (!connection.ssl.key && !connection.ssl.ca && !connection.ssl.cert) {
|
||||
// TODO: provide this as an option in settings
|
||||
// or per-connection as 'reject self-signed certs'
|
||||
|
||||
@@ -2,7 +2,6 @@ const os = require('os');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const cleanDirectory = require('./cleanDirectory');
|
||||
const platformInfo = require('./platformInfo');
|
||||
|
||||
const createDirectories = {};
|
||||
const ensureDirectory = (dir, clean) => {
|
||||
@@ -40,37 +39,6 @@ const pluginsdir = dirFunc('plugins');
|
||||
const archivedir = dirFunc('archive');
|
||||
const filesdir = dirFunc('files');
|
||||
|
||||
function packagedPluginsDir() {
|
||||
if (platformInfo.isDevMode) {
|
||||
return path.resolve(__dirname, '../../../../plugins');
|
||||
}
|
||||
if (platformInfo.isDocker) {
|
||||
return '/home/dbgate-docker/plugins';
|
||||
}
|
||||
if (platformInfo.isNpmDist) {
|
||||
// node_modules
|
||||
return global['dbgateApiPackagedPluginsPath'];
|
||||
}
|
||||
if (platformInfo.isElectronBundle) {
|
||||
return path.resolve(__dirname, '../../plugins');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const packagedPluginList =
|
||||
packagedPluginsDir() != null ? fs.readdirSync(packagedPluginsDir()).filter(x => x.startsWith('dbgate-plugin-')) : [];
|
||||
|
||||
function getPluginBackendPath(packageName) {
|
||||
if (packagedPluginList.includes(packageName)) {
|
||||
if (platformInfo.isDevMode) {
|
||||
return path.join(packagedPluginsDir(), packageName, 'src', 'backend', 'index.js');
|
||||
}
|
||||
return path.join(packagedPluginsDir(), packageName, 'dist', 'backend.js');
|
||||
}
|
||||
|
||||
return path.join(pluginsdir(), packageName, 'dist', 'backend.js');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
datadir,
|
||||
jsldir,
|
||||
@@ -80,7 +48,4 @@ module.exports = {
|
||||
ensureDirectory,
|
||||
pluginsdir,
|
||||
filesdir,
|
||||
packagedPluginsDir,
|
||||
packagedPluginList,
|
||||
getPluginBackendPath,
|
||||
};
|
||||
|
||||
@@ -1,42 +1,26 @@
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const processArgs = require('./processArgs');
|
||||
|
||||
const platform = process.env.OS_OVERRIDE ? process.env.OS_OVERRIDE : process.platform;
|
||||
const p = process;
|
||||
const platform = p.env.OS_OVERRIDE ? p.env.OS_OVERRIDE : p.platform;
|
||||
const isWindows = platform === 'win32';
|
||||
const isMac = platform === 'darwin';
|
||||
const isLinux = platform === 'linux';
|
||||
const isDocker = fs.existsSync('/home/dbgate-docker/public');
|
||||
const isDevMode = process.env.DEVMODE == '1';
|
||||
const isNpmDist = !!global['dbgateApiModulePath'];
|
||||
|
||||
// function moduleAvailable(name) {
|
||||
// try {
|
||||
// require.resolve(name);
|
||||
// return true;
|
||||
// } catch (e) {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
const isElectronBundle = processArgs.isElectronBundle;
|
||||
const isDocker = fs.existsSync('/home/dbgate-docker/build');
|
||||
|
||||
const platformInfo = {
|
||||
isWindows,
|
||||
isMac,
|
||||
isLinux,
|
||||
isDocker,
|
||||
isElectronBundle,
|
||||
isDevMode,
|
||||
isNpmDist,
|
||||
isSnap: process.env.ELECTRON_SNAP == 'true',
|
||||
isPortable: isWindows && process.env.PORTABLE_EXECUTABLE_DIR,
|
||||
isAppImage: process.env.DESKTOPINTEGRATION === 'AppImageLauncher',
|
||||
sshAuthSock: process.env.SSH_AUTH_SOCK,
|
||||
isSnap: p.env.ELECTRON_SNAP == 'true',
|
||||
isPortable: isWindows && p.env.PORTABLE_EXECUTABLE_DIR,
|
||||
isAppImage: p.env.DESKTOPINTEGRATION === 'AppImageLauncher',
|
||||
sshAuthSock: p.env.SSH_AUTH_SOCK,
|
||||
environment: process.env.NODE_ENV,
|
||||
platform,
|
||||
runningInWebpack: !!process.env.WEBPACK_DEV_SERVER_URL,
|
||||
runningInWebpack: !!p.env.WEBPACK_DEV_SERVER_URL,
|
||||
defaultKeyFile: path.join(os.homedir(), '.ssh/id_rsa'),
|
||||
};
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
function getNamedArg(name) {
|
||||
const argIndex = process.argv.indexOf(name);
|
||||
if (argIndex > 0) {
|
||||
return process.argv[argIndex + 1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const checkParent = process.argv.includes('--checkParent');
|
||||
const dynport = process.argv.includes('--dynport');
|
||||
const nativeModules = getNamedArg('--native-modules');
|
||||
const startProcess = getNamedArg('--start-process');
|
||||
const isElectronBundle = process.argv.includes('--is-electron-bundle');
|
||||
|
||||
module.exports = {
|
||||
checkParent,
|
||||
nativeModules,
|
||||
startProcess,
|
||||
dynport,
|
||||
isElectronBundle,
|
||||
};
|
||||
@@ -4,8 +4,6 @@ const portfinder = require('portfinder');
|
||||
const stableStringify = require('json-stable-stringify');
|
||||
const _ = require('lodash');
|
||||
const platformInfo = require('./platformInfo');
|
||||
const AsyncLock = require('async-lock');
|
||||
const lock = new AsyncLock();
|
||||
|
||||
const sshConnectionCache = {};
|
||||
const sshTunnelCache = {};
|
||||
@@ -36,7 +34,7 @@ async function getSshConnection(connection) {
|
||||
password: connection.sshMode == 'userPassword' ? connection.sshPassword : undefined,
|
||||
agentSocket: connection.sshMode == 'agent' ? platformInfo.sshAuthSock : undefined,
|
||||
privateKey:
|
||||
connection.sshMode == 'keyFile' && connection.sshKeyfile ? await fs.readFile(connection.sshKeyfile) : undefined,
|
||||
connection.sshMode == 'keyFile' && connection.sshKeyFile ? await fs.readFile(connection.sshKeyFile) : undefined,
|
||||
skipAutoPrivateKey: true,
|
||||
noReadline: true,
|
||||
};
|
||||
@@ -47,43 +45,36 @@ async function getSshConnection(connection) {
|
||||
}
|
||||
|
||||
async function getSshTunnel(connection) {
|
||||
const sshConn = await getSshConnection(connection);
|
||||
const tunnelCacheKey = stableStringify(_.pick(connection, TUNNEL_FIELDS));
|
||||
if (sshTunnelCache[tunnelCacheKey]) return sshTunnelCache[tunnelCacheKey];
|
||||
|
||||
return await lock.acquire(tunnelCacheKey, async () => {
|
||||
const sshConn = await getSshConnection(connection);
|
||||
if (sshTunnelCache[tunnelCacheKey]) return sshTunnelCache[tunnelCacheKey];
|
||||
const localPort = await portfinder.getPortPromise({ port: 10000, stopPort: 60000 });
|
||||
// workaround for `getPortPromise` not releasing the port quickly enough
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
const tunnelConfig = {
|
||||
fromPort: localPort,
|
||||
toPort: connection.port,
|
||||
toHost: connection.server,
|
||||
};
|
||||
try {
|
||||
const tunnel = await sshConn.forward(tunnelConfig);
|
||||
console.log(
|
||||
`Created SSH tunnel to ${connection.sshHost}-${connection.server}:${connection.port}, using local port ${localPort}`
|
||||
);
|
||||
|
||||
const localPort = await portfinder.getPortPromise({ port: 10000, stopPort: 60000 });
|
||||
// workaround for `getPortPromise` not releasing the port quickly enough
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
const tunnelConfig = {
|
||||
fromPort: localPort,
|
||||
toPort: connection.port,
|
||||
toHost: connection.server,
|
||||
sshTunnelCache[tunnelCacheKey] = {
|
||||
state: 'ok',
|
||||
localPort,
|
||||
};
|
||||
try {
|
||||
console.log(
|
||||
`Creating SSH tunnel to ${connection.sshHost}-${connection.server}:${connection.port}, using local port ${localPort}`
|
||||
);
|
||||
|
||||
const tunnel = await sshConn.forward(tunnelConfig);
|
||||
console.log(
|
||||
`Created SSH tunnel to ${connection.sshHost}-${connection.server}:${connection.port}, using local port ${localPort}`
|
||||
);
|
||||
|
||||
sshTunnelCache[tunnelCacheKey] = {
|
||||
state: 'ok',
|
||||
localPort,
|
||||
};
|
||||
return sshTunnelCache[tunnelCacheKey];
|
||||
} catch (err) {
|
||||
// error is not cached
|
||||
return {
|
||||
state: 'error',
|
||||
message: err.message,
|
||||
};
|
||||
}
|
||||
});
|
||||
return sshTunnelCache[tunnelCacheKey];
|
||||
} catch (err) {
|
||||
// error is not cached
|
||||
return {
|
||||
state: 'error',
|
||||
message: err.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
const crypto = require('crypto');
|
||||
|
||||
function timingSafeCheckToken(a, b) {
|
||||
if (!a || !b) return false;
|
||||
if (a.length != b.length) return false;
|
||||
return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
|
||||
}
|
||||
|
||||
module.exports = timingSafeCheckToken;
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"version": "4.1.1",
|
||||
"version": "4.0.0",
|
||||
"name": "dbgate-datalib",
|
||||
"main": "lib/index.js",
|
||||
"typings": "lib/index.d.ts",
|
||||
"scripts": {
|
||||
"prepare": "yarn build",
|
||||
"build": "tsc",
|
||||
"start": "tsc --watch"
|
||||
},
|
||||
@@ -11,11 +12,11 @@
|
||||
"lib"
|
||||
],
|
||||
"dependencies": {
|
||||
"dbgate-sqltree": "^4.1.1",
|
||||
"dbgate-filterparser": "^4.1.1"
|
||||
"dbgate-sqltree": "^4.0.0",
|
||||
"dbgate-filterparser": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dbgate-types": "^4.1.1",
|
||||
"dbgate-types": "^4.0.0",
|
||||
"@types/node": "^13.7.0",
|
||||
"typescript": "^3.7.5"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ export interface ChangeSetItem {
|
||||
pureName: string;
|
||||
schemaName?: string;
|
||||
insertedRowIndex?: number;
|
||||
document?: any;
|
||||
condition?: { [column: string]: string };
|
||||
fields?: { [column: string]: string };
|
||||
}
|
||||
@@ -118,43 +117,6 @@ export function setChangeSetValue(
|
||||
};
|
||||
}
|
||||
|
||||
export function setChangeSetRowData(changeSet: ChangeSet, definition: ChangeSetRowDefinition, document: any): ChangeSet {
|
||||
if (!changeSet || !definition) return changeSet;
|
||||
let [fieldName, existingItem] = findExistingChangeSetItem(changeSet, definition);
|
||||
if (fieldName == 'deletes') {
|
||||
changeSet = revertChangeSetRowChanges(changeSet, definition);
|
||||
[fieldName, existingItem] = findExistingChangeSetItem(changeSet, definition);
|
||||
}
|
||||
if (existingItem) {
|
||||
return {
|
||||
...changeSet,
|
||||
[fieldName]: changeSet[fieldName].map(item =>
|
||||
item == existingItem
|
||||
? {
|
||||
...item,
|
||||
fields: {},
|
||||
document,
|
||||
}
|
||||
: item
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...changeSet,
|
||||
[fieldName]: [
|
||||
...changeSet[fieldName],
|
||||
{
|
||||
pureName: definition.pureName,
|
||||
schemaName: definition.schemaName,
|
||||
condition: definition.condition,
|
||||
insertedRowIndex: definition.insertedRowIndex,
|
||||
document,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// export function batchUpdateChangeSet(
|
||||
// changeSet: ChangeSet,
|
||||
// rowDefinitions: ChangeSetRowDefinition[],
|
||||
@@ -370,7 +332,6 @@ export function getChangeSetInsertedRows(changeSet: ChangeSet, name?: NamedObjec
|
||||
}
|
||||
|
||||
export function changeSetInsertNewRow(changeSet: ChangeSet, name?: NamedObjectInfo): ChangeSet {
|
||||
console.log('INSERT', name);
|
||||
const insertedRows = getChangeSetInsertedRows(changeSet, name);
|
||||
return {
|
||||
...changeSet,
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
import { FormViewDisplay } from './FormViewDisplay';
|
||||
import _ from 'lodash';
|
||||
import { GridDisplay, ChangeCacheFunc, DisplayColumn, DisplayedColumnInfo, ChangeConfigFunc } from './GridDisplay';
|
||||
import { TableInfo, EngineDriver, ViewInfo, ColumnInfo, NamedObjectInfo, DatabaseInfo } from 'dbgate-types';
|
||||
import { GridConfig, GridCache, createGridCache } from './GridConfig';
|
||||
import {
|
||||
Expression,
|
||||
Select,
|
||||
treeToSql,
|
||||
dumpSqlSelect,
|
||||
mergeConditions,
|
||||
Condition,
|
||||
OrderByExpression,
|
||||
} from 'dbgate-sqltree';
|
||||
import { filterName } from './filterName';
|
||||
import { TableGridDisplay } from './TableGridDisplay';
|
||||
import stableStringify from 'json-stable-stringify';
|
||||
import { ChangeSetFieldDefinition, ChangeSetRowDefinition } from './ChangeSet';
|
||||
import { CollectionGridDisplay } from './CollectionGridDisplay';
|
||||
|
||||
export class CollectionFormViewDisplay extends FormViewDisplay {
|
||||
// use utility functions from GridDisplay and publish result in FromViewDisplay interface
|
||||
private gridDisplay: CollectionGridDisplay;
|
||||
|
||||
constructor(
|
||||
public collectionName: NamedObjectInfo,
|
||||
driver: EngineDriver,
|
||||
config: GridConfig,
|
||||
setConfig: ChangeConfigFunc,
|
||||
cache: GridCache,
|
||||
setCache: ChangeCacheFunc,
|
||||
loadedRow: any
|
||||
) {
|
||||
super(config, setConfig, cache, setCache, driver);
|
||||
this.gridDisplay = new CollectionGridDisplay(collectionName, driver, config, setConfig, cache, setCache, [loadedRow]);
|
||||
|
||||
this.isLoadedCorrectly = this.gridDisplay.isLoadedCorrectly && !!this.driver;
|
||||
this.columns = [];
|
||||
this.addDisplayColumns(this.gridDisplay.columns);
|
||||
}
|
||||
|
||||
addDisplayColumns(columns: DisplayColumn[]) {
|
||||
for (const col of columns) {
|
||||
this.columns.push(col);
|
||||
}
|
||||
}
|
||||
|
||||
navigate(row) {
|
||||
const formViewKey = this.extractKey(row);
|
||||
this.setConfig(cfg => ({
|
||||
...cfg,
|
||||
formViewKey,
|
||||
}));
|
||||
}
|
||||
|
||||
isLoadedCurrentRow(row) {
|
||||
if (!row) return false;
|
||||
const formViewKey = this.extractKey(row);
|
||||
return stableStringify(formViewKey) == stableStringify(this.config.formViewKey);
|
||||
}
|
||||
|
||||
navigateRowQuery(commmand: 'begin' | 'previous' | 'next' | 'end') {
|
||||
if (!this.driver) return null;
|
||||
const select = this.gridDisplay.createSelect();
|
||||
if (!select) return null;
|
||||
const { primaryKey } = this.gridDisplay.baseTable;
|
||||
|
||||
function getOrderBy(direction): OrderByExpression[] {
|
||||
return primaryKey.columns.map(({ columnName }) => ({
|
||||
exprType: 'column',
|
||||
columnName,
|
||||
direction,
|
||||
}));
|
||||
}
|
||||
|
||||
select.topRecords = 1;
|
||||
switch (commmand) {
|
||||
case 'begin':
|
||||
select.orderBy = getOrderBy('ASC');
|
||||
break;
|
||||
case 'end':
|
||||
select.orderBy = getOrderBy('DESC');
|
||||
break;
|
||||
case 'previous':
|
||||
select.orderBy = getOrderBy('DESC');
|
||||
select.where = mergeConditions(select.where, this.getPrimaryKeyOperatorCondition('<'));
|
||||
break;
|
||||
case 'next':
|
||||
select.orderBy = getOrderBy('ASC');
|
||||
select.where = mergeConditions(select.where, this.getPrimaryKeyOperatorCondition('>'));
|
||||
break;
|
||||
}
|
||||
|
||||
const sql = treeToSql(this.driver, select, dumpSqlSelect);
|
||||
return sql;
|
||||
}
|
||||
|
||||
getChangeSetRow(row): ChangeSetRowDefinition {
|
||||
if (!this.baseTable) return null;
|
||||
return {
|
||||
pureName: this.baseTable.pureName,
|
||||
schemaName: this.baseTable.schemaName,
|
||||
condition: this.extractKey(row),
|
||||
};
|
||||
}
|
||||
|
||||
getChangeSetField(row, uniqueName): ChangeSetFieldDefinition {
|
||||
const col = this.columns.find(x => x.uniqueName == uniqueName);
|
||||
if (!col) return null;
|
||||
if (!this.baseTable) return null;
|
||||
if (this.baseTable.pureName != col.pureName || this.baseTable.schemaName != col.schemaName) return null;
|
||||
return {
|
||||
...this.getChangeSetRow(row),
|
||||
uniqueName: uniqueName,
|
||||
columnName: col.columnName,
|
||||
};
|
||||
}
|
||||
|
||||
toggleExpandedColumn(uniqueName: string) {
|
||||
this.gridDisplay.toggleExpandedColumn(uniqueName);
|
||||
this.gridDisplay.reload();
|
||||
}
|
||||
|
||||
isExpandedColumn(uniqueName: string) {
|
||||
return this.gridDisplay.isExpandedColumn(uniqueName);
|
||||
}
|
||||
}
|
||||
@@ -25,62 +25,6 @@ function createHeaderText(path) {
|
||||
return res;
|
||||
}
|
||||
|
||||
function getColumnsForObject(basePath, obj, res: any[], display) {
|
||||
for (const name of getObjectKeys(obj)) {
|
||||
const uniqueName = [...basePath, name].join('.');
|
||||
let column = res.find(x => x.uniqueName == uniqueName);
|
||||
if (!column) {
|
||||
column = getDisplayColumn(basePath, name, display);
|
||||
if (basePath.length > 0) {
|
||||
const lastIndex1 = _.findLastIndex(res, x => x.parentHeaderText.startsWith(column.parentHeaderText));
|
||||
const lastIndex2 = _.findLastIndex(res, x => x.headerText == column.parentHeaderText);
|
||||
// console.log(uniqueName, lastIndex1, lastIndex2);
|
||||
if (lastIndex1 >= 0) res.splice(lastIndex1 + 1, 0, column);
|
||||
else if (lastIndex2 >= 0) res.splice(lastIndex2 + 1, 0, column);
|
||||
else res.push(column);
|
||||
} else {
|
||||
res.push(column);
|
||||
}
|
||||
}
|
||||
if (_.isPlainObject(obj[name]) || _.isArray(obj[name])) {
|
||||
column.isExpandable = true;
|
||||
}
|
||||
|
||||
if (display.isExpandedColumn(column.uniqueName)) {
|
||||
getColumnsForObject([...basePath, name], obj[name], res, display);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getDisplayColumn(basePath, columnName, display) {
|
||||
const uniquePath = [...basePath, columnName];
|
||||
const uniqueName = uniquePath.join('.');
|
||||
return {
|
||||
columnName,
|
||||
headerText: createHeaderText(uniquePath),
|
||||
uniqueName,
|
||||
uniquePath,
|
||||
isStructured: true,
|
||||
parentHeaderText: createHeaderText(basePath),
|
||||
filterType: 'mongo',
|
||||
pureName: display.collection?.pureName,
|
||||
schemaName: display.collection?.schemaName,
|
||||
};
|
||||
}
|
||||
|
||||
export function analyseCollectionDisplayColumns(rows, display) {
|
||||
const res = [];
|
||||
for (const row of rows || []) {
|
||||
getColumnsForObject([], row, res, display);
|
||||
}
|
||||
return (
|
||||
res.map(col => ({
|
||||
...col,
|
||||
isChecked: display.isColumnChecked(col),
|
||||
})) || []
|
||||
);
|
||||
}
|
||||
|
||||
export class CollectionGridDisplay extends GridDisplay {
|
||||
constructor(
|
||||
public collection: CollectionInfo,
|
||||
@@ -92,13 +36,65 @@ export class CollectionGridDisplay extends GridDisplay {
|
||||
loadedRows
|
||||
) {
|
||||
super(config, setConfig, cache, setCache, driver);
|
||||
this.columns = analyseCollectionDisplayColumns(loadedRows, this);
|
||||
this.columns = this.getDisplayColumns(loadedRows || []);
|
||||
this.filterable = true;
|
||||
this.sortable = true;
|
||||
this.editable = true;
|
||||
this.editable = false;
|
||||
this.supportsReload = true;
|
||||
this.isDynamicStructure = true;
|
||||
this.changeSetKeyFields = ['_id'];
|
||||
this.baseCollection = collection;
|
||||
}
|
||||
|
||||
getDisplayColumns(rows) {
|
||||
const res = [];
|
||||
for (const row of rows) {
|
||||
this.getColumnsForObject([], row, res);
|
||||
}
|
||||
return (
|
||||
res.map(col => ({
|
||||
...col,
|
||||
isChecked: this.isColumnChecked(col),
|
||||
})) || []
|
||||
);
|
||||
}
|
||||
|
||||
getColumnsForObject(basePath, obj, res: any[]) {
|
||||
for (const name of getObjectKeys(obj)) {
|
||||
const uniqueName = [...basePath, name].join('.');
|
||||
let column = res.find(x => x.uniqueName == uniqueName);
|
||||
if (!column) {
|
||||
column = this.getDisplayColumn(basePath, name);
|
||||
if (basePath.length > 0) {
|
||||
const lastIndex1 = _.findLastIndex(res, x => x.parentHeaderText.startsWith(column.parentHeaderText));
|
||||
const lastIndex2 = _.findLastIndex(res, x => x.headerText == column.parentHeaderText);
|
||||
// console.log(uniqueName, lastIndex1, lastIndex2);
|
||||
if (lastIndex1 >= 0) res.splice(lastIndex1 + 1, 0, column);
|
||||
else if (lastIndex2 >= 0) res.splice(lastIndex2 + 1, 0, column);
|
||||
else res.push(column);
|
||||
} else {
|
||||
res.push(column);
|
||||
}
|
||||
}
|
||||
if (_.isPlainObject(obj[name]) || _.isArray(obj[name])) {
|
||||
column.isExpandable = true;
|
||||
}
|
||||
|
||||
if (this.isExpandedColumn(column.uniqueName)) {
|
||||
this.getColumnsForObject([...basePath, name], obj[name], res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getDisplayColumn(basePath, columnName) {
|
||||
const uniquePath = [...basePath, columnName];
|
||||
const uniqueName = uniquePath.join('.');
|
||||
return {
|
||||
columnName,
|
||||
headerText: createHeaderText(uniquePath),
|
||||
uniqueName,
|
||||
uniquePath,
|
||||
isStructured: true,
|
||||
parentHeaderText: createHeaderText(basePath),
|
||||
filterType: 'mongo',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import { GridConfig, GridCache, GridConfigColumns, createGridCache, GroupFunc } from './GridConfig';
|
||||
import {
|
||||
ForeignKeyInfo,
|
||||
TableInfo,
|
||||
ColumnInfo,
|
||||
EngineDriver,
|
||||
NamedObjectInfo,
|
||||
DatabaseInfo,
|
||||
SqlDialect,
|
||||
} from 'dbgate-types';
|
||||
import { ForeignKeyInfo, TableInfo, ColumnInfo, EngineDriver, NamedObjectInfo, DatabaseInfo } from 'dbgate-types';
|
||||
import { parseFilter, getFilterType, getFilterValueExpression } from 'dbgate-filterparser';
|
||||
import { filterName } from './filterName';
|
||||
import { ChangeSetFieldDefinition, ChangeSetRowDefinition } from './ChangeSet';
|
||||
@@ -20,7 +12,6 @@ export class FormViewDisplay {
|
||||
isLoadedCorrectly = true;
|
||||
columns: DisplayColumn[];
|
||||
public baseTable: TableInfo;
|
||||
dialect: SqlDialect;
|
||||
|
||||
constructor(
|
||||
public config: GridConfig,
|
||||
@@ -28,11 +19,8 @@ export class FormViewDisplay {
|
||||
public cache: GridCache,
|
||||
protected setCache: ChangeCacheFunc,
|
||||
public driver?: EngineDriver,
|
||||
public dbinfo: DatabaseInfo = null,
|
||||
public serverVersion = null
|
||||
) {
|
||||
this.dialect = (driver?.dialectByVersion && driver?.dialectByVersion(serverVersion)) || driver?.dialect;
|
||||
}
|
||||
public dbinfo: DatabaseInfo = null
|
||||
) {}
|
||||
|
||||
addFilterColumn(column) {
|
||||
if (!column) return;
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import { GridConfig, GridCache, GridConfigColumns, createGridCache, GroupFunc } from './GridConfig';
|
||||
import {
|
||||
ForeignKeyInfo,
|
||||
TableInfo,
|
||||
ColumnInfo,
|
||||
EngineDriver,
|
||||
NamedObjectInfo,
|
||||
DatabaseInfo,
|
||||
CollectionInfo,
|
||||
SqlDialect,
|
||||
} from 'dbgate-types';
|
||||
import { ForeignKeyInfo, TableInfo, ColumnInfo, EngineDriver, NamedObjectInfo, DatabaseInfo } from 'dbgate-types';
|
||||
import { parseFilter, getFilterType } from 'dbgate-filterparser';
|
||||
import { filterName } from './filterName';
|
||||
import { ChangeSetFieldDefinition, ChangeSetRowDefinition } from './ChangeSet';
|
||||
@@ -61,18 +52,10 @@ export abstract class GridDisplay {
|
||||
public cache: GridCache,
|
||||
protected setCache: ChangeCacheFunc,
|
||||
public driver?: EngineDriver,
|
||||
public dbinfo: DatabaseInfo = null,
|
||||
public serverVersion = null
|
||||
) {
|
||||
this.dialect = (driver?.dialectByVersion && driver?.dialectByVersion(serverVersion)) || driver?.dialect;
|
||||
}
|
||||
dialect: SqlDialect;
|
||||
public dbinfo: DatabaseInfo = null
|
||||
) {}
|
||||
columns: DisplayColumn[];
|
||||
baseTable?: TableInfo;
|
||||
baseCollection?: CollectionInfo;
|
||||
get baseTableOrCollection(): NamedObjectInfo {
|
||||
return this.baseTable || this.baseCollection;
|
||||
}
|
||||
changeSetKeyFields: string[] = null;
|
||||
sortable = false;
|
||||
filterable = false;
|
||||
@@ -115,7 +98,11 @@ export abstract class GridDisplay {
|
||||
}
|
||||
|
||||
reload() {
|
||||
this.setCache(reloadDataCacheFunc);
|
||||
this.setCache(cache => ({
|
||||
// ...cache,
|
||||
...createGridCache(),
|
||||
refreshTime: new Date().getTime(),
|
||||
}));
|
||||
}
|
||||
|
||||
includeInColumnSet(field: keyof GridConfigColumns, uniqueName: string, isIncluded: boolean) {
|
||||
@@ -307,28 +294,6 @@ export abstract class GridDisplay {
|
||||
this.reload();
|
||||
}
|
||||
|
||||
showFilter(uniqueName) {
|
||||
this.setConfig(cfg => {
|
||||
if (!cfg.filters.uniqueName)
|
||||
return {
|
||||
...cfg,
|
||||
filters: {
|
||||
..._.omitBy(cfg.filters, v => !v),
|
||||
[uniqueName]: '',
|
||||
},
|
||||
};
|
||||
return cfg;
|
||||
});
|
||||
}
|
||||
|
||||
removeFilter(uniqueName) {
|
||||
this.setConfig(cfg => ({
|
||||
...cfg,
|
||||
filters: _.omit(cfg.filters, [uniqueName]),
|
||||
}));
|
||||
this.reload();
|
||||
}
|
||||
|
||||
setFilters(dct) {
|
||||
this.setConfig(cfg => ({
|
||||
...cfg,
|
||||
@@ -404,12 +369,8 @@ export abstract class GridDisplay {
|
||||
getChangeSetField(row, uniqueName, insertedRowIndex): ChangeSetFieldDefinition {
|
||||
const col = this.columns.find(x => x.uniqueName == uniqueName);
|
||||
if (!col) return null;
|
||||
const baseTableOrCollection = this.baseTableOrCollection;
|
||||
if (!baseTableOrCollection) return null;
|
||||
if (baseTableOrCollection.pureName != col.pureName || baseTableOrCollection.schemaName != col.schemaName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.baseTable) return null;
|
||||
if (this.baseTable.pureName != col.pureName || this.baseTable.schemaName != col.schemaName) return null;
|
||||
return {
|
||||
...this.getChangeSetRow(row, insertedRowIndex),
|
||||
uniqueName: uniqueName,
|
||||
@@ -418,11 +379,10 @@ export abstract class GridDisplay {
|
||||
}
|
||||
|
||||
getChangeSetRow(row, insertedRowIndex): ChangeSetRowDefinition {
|
||||
const baseTableOrCollection = this.baseTableOrCollection;
|
||||
if (!baseTableOrCollection) return null;
|
||||
if (!this.baseTable) return null;
|
||||
return {
|
||||
pureName: baseTableOrCollection.pureName,
|
||||
schemaName: baseTableOrCollection.schemaName,
|
||||
pureName: this.baseTable.pureName,
|
||||
schemaName: this.baseTable.schemaName,
|
||||
insertedRowIndex,
|
||||
condition: insertedRowIndex == null ? this.getChangeSetCondition(row) : null,
|
||||
};
|
||||
@@ -465,75 +425,12 @@ export abstract class GridDisplay {
|
||||
return select;
|
||||
}
|
||||
|
||||
getRowNumberOverSelect(select: Select, offset: number, count: number): Select {
|
||||
const innerSelect: Select = {
|
||||
commandType: 'select',
|
||||
from: select.from,
|
||||
where: select.where,
|
||||
columns: [
|
||||
...select.columns,
|
||||
{
|
||||
alias: '_rowNumber',
|
||||
exprType: 'rowNumber',
|
||||
orderBy: select.orderBy
|
||||
? select.orderBy.map(x =>
|
||||
x.exprType != 'column'
|
||||
? x
|
||||
: x.source
|
||||
? x
|
||||
: {
|
||||
...x,
|
||||
source: { alias: 'basetbl' },
|
||||
}
|
||||
)
|
||||
: [
|
||||
{
|
||||
...select.columns[0],
|
||||
direction: 'ASC',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const res: Select = {
|
||||
commandType: 'select',
|
||||
selectAll: true,
|
||||
from: {
|
||||
subQuery: innerSelect,
|
||||
alias: '_RowNumberResult',
|
||||
},
|
||||
where: {
|
||||
conditionType: 'between',
|
||||
expr: {
|
||||
exprType: 'column',
|
||||
columnName: '_RowNumber',
|
||||
source: {
|
||||
alias: '_RowNumberResult',
|
||||
},
|
||||
},
|
||||
left: {
|
||||
exprType: 'value',
|
||||
value: offset + 1,
|
||||
},
|
||||
right: {
|
||||
exprType: 'value',
|
||||
value: offset + count,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
getPageQuery(offset: number, count: number) {
|
||||
if (!this.driver) return null;
|
||||
let select = this.createSelect();
|
||||
const select = this.createSelect();
|
||||
if (!select) return null;
|
||||
if (this.dialect.rangeSelect) select.range = { offset: offset, limit: count };
|
||||
else if (this.dialect.rowNumberOverPaging && offset > 0)
|
||||
select = this.getRowNumberOverSelect(select, offset, count);
|
||||
else if (this.dialect.limitSelect) select.topRecords = count;
|
||||
if (this.driver.dialect.rangeSelect) select.range = { offset: offset, limit: count };
|
||||
else if (this.driver.dialect.limitSelect) select.topRecords = count;
|
||||
const sql = treeToSql(this.driver, select, dumpSqlSelect);
|
||||
return sql;
|
||||
}
|
||||
@@ -642,19 +539,4 @@ export abstract class GridDisplay {
|
||||
formViewKeyRequested: null,
|
||||
}));
|
||||
}
|
||||
|
||||
switchToJsonView() {
|
||||
this.setConfig(cfg => ({
|
||||
...cfg,
|
||||
isJsonView: true,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
export function reloadDataCacheFunc(cache: GridCache): GridCache {
|
||||
return {
|
||||
// ...cache,
|
||||
...createGridCache(),
|
||||
refreshTime: new Date().getTime(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,44 +1,34 @@
|
||||
import { GridDisplay, ChangeCacheFunc, ChangeConfigFunc } from './GridDisplay';
|
||||
import { QueryResultColumn } from 'dbgate-types';
|
||||
import { GridConfig, GridCache } from './GridConfig';
|
||||
import { analyseCollectionDisplayColumns } from './CollectionGridDisplay';
|
||||
|
||||
export class JslGridDisplay extends GridDisplay {
|
||||
constructor(
|
||||
jslid,
|
||||
structure,
|
||||
columns: QueryResultColumn[],
|
||||
config: GridConfig,
|
||||
setConfig: ChangeConfigFunc,
|
||||
cache: GridCache,
|
||||
setCache: ChangeCacheFunc,
|
||||
rows: any
|
||||
setCache: ChangeCacheFunc
|
||||
) {
|
||||
super(config, setConfig, cache, setCache, null);
|
||||
|
||||
this.filterable = true;
|
||||
|
||||
if (structure.columns) {
|
||||
this.columns = structure.columns
|
||||
.map(col => ({
|
||||
columnName: col.columnName,
|
||||
headerText: col.columnName,
|
||||
uniqueName: col.columnName,
|
||||
uniquePath: [col.columnName],
|
||||
notNull: col.notNull,
|
||||
autoIncrement: col.autoIncrement,
|
||||
pureName: null,
|
||||
schemaName: null,
|
||||
}))
|
||||
?.map(col => ({
|
||||
...col,
|
||||
isChecked: this.isColumnChecked(col),
|
||||
}));
|
||||
}
|
||||
|
||||
if (structure.__isDynamicStructure) {
|
||||
this.columns = analyseCollectionDisplayColumns(rows, this);
|
||||
}
|
||||
|
||||
if (!this.columns) this.columns = [];
|
||||
this.columns = columns
|
||||
.map(col => ({
|
||||
columnName: col.columnName,
|
||||
headerText: col.columnName,
|
||||
uniqueName: col.columnName,
|
||||
uniquePath: [col.columnName],
|
||||
notNull: col.notNull,
|
||||
autoIncrement: col.autoIncrement,
|
||||
pureName: null,
|
||||
schemaName: null,
|
||||
}))
|
||||
?.map(col => ({
|
||||
...col,
|
||||
isChecked: this.isColumnChecked(col),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,22 +28,10 @@ export class TableFormViewDisplay extends FormViewDisplay {
|
||||
setConfig: ChangeConfigFunc,
|
||||
cache: GridCache,
|
||||
setCache: ChangeCacheFunc,
|
||||
dbinfo: DatabaseInfo,
|
||||
displayOptions,
|
||||
serverVersion
|
||||
dbinfo: DatabaseInfo
|
||||
) {
|
||||
super(config, setConfig, cache, setCache, driver, dbinfo, serverVersion);
|
||||
this.gridDisplay = new TableGridDisplay(
|
||||
tableName,
|
||||
driver,
|
||||
config,
|
||||
setConfig,
|
||||
cache,
|
||||
setCache,
|
||||
dbinfo,
|
||||
displayOptions,
|
||||
serverVersion
|
||||
);
|
||||
super(config, setConfig, cache, setCache, driver, dbinfo);
|
||||
this.gridDisplay = new TableGridDisplay(tableName, driver, config, setConfig, cache, setCache, dbinfo);
|
||||
this.gridDisplay.addAllExpandedColumnsToSelected = true;
|
||||
|
||||
this.isLoadedCorrectly = this.gridDisplay.isLoadedCorrectly && !!this.driver;
|
||||
|
||||
@@ -17,11 +17,9 @@ export class TableGridDisplay extends GridDisplay {
|
||||
setConfig: ChangeConfigFunc,
|
||||
cache: GridCache,
|
||||
setCache: ChangeCacheFunc,
|
||||
dbinfo: DatabaseInfo,
|
||||
public displayOptions: any,
|
||||
serverVersion
|
||||
dbinfo: DatabaseInfo
|
||||
) {
|
||||
super(config, setConfig, cache, setCache, driver, dbinfo, serverVersion);
|
||||
super(config, setConfig, cache, setCache, driver, dbinfo);
|
||||
|
||||
this.table = this.findTable(tableName);
|
||||
if (!this.table) {
|
||||
@@ -170,7 +168,7 @@ export class TableGridDisplay extends GridDisplay {
|
||||
|
||||
processReferences(select: Select, displayedColumnInfo: DisplayedColumnInfo, options) {
|
||||
this.addJoinsFromExpandedColumns(select, this.columns, 'basetbl', displayedColumnInfo);
|
||||
if (!options.isExport && this.displayOptions.showHintColumns) {
|
||||
if (!options.isExport) {
|
||||
this.addHintsToSelect(select);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,9 @@ export class ViewGridDisplay extends GridDisplay {
|
||||
config: GridConfig,
|
||||
setConfig: ChangeConfigFunc,
|
||||
cache: GridCache,
|
||||
setCache: ChangeCacheFunc,
|
||||
serverVersion
|
||||
setCache: ChangeCacheFunc
|
||||
) {
|
||||
super(config, setConfig, cache, setCache, driver, serverVersion);
|
||||
super(config, setConfig, cache, setCache, driver);
|
||||
this.columns = this.getDisplayColumns(view);
|
||||
this.filterable = true;
|
||||
this.sortable = true;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
[](https://github.com/prettier/prettier)
|
||||
[](https://paypal.me/JanProchazkaCz/30eur)
|
||||
[](https://www.npmjs.com/package/dbgate)
|
||||
|
||||
# DbGate - database administration tool
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const path = require('path');
|
||||
|
||||
global.dbgateApiModulePath = path.dirname(path.dirname(require.resolve('dbgate-api')));
|
||||
global.dbgateApiPackagedPluginsPath = path.dirname(global.dbgateApiModulePath);
|
||||
|
||||
const dbgateApi = require('dbgate-api');
|
||||
|
||||
dbgateApi.getMainModule().start();
|
||||
global.dbgateApiModulePath = require.resolve('dbgate-api');
|
||||
|
||||
dbgateApi.getMainModule().start('startNodeWeb');
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "dbgate",
|
||||
"version": "4.1.1",
|
||||
"version": "4.0.0",
|
||||
"homepage": "https://dbgate.org/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dbgate/dbgate.git"
|
||||
},
|
||||
"funding": "https://www.paypal.com/paypalme/JanProchazkaCz/30eur",
|
||||
"description": "Opensource database administration tool - web interface",
|
||||
"author": "Jan Prochazka",
|
||||
"license": "MIT",
|
||||
@@ -18,13 +19,7 @@
|
||||
"web"
|
||||
],
|
||||
"dependencies": {
|
||||
"dbgate-api": "^4.1.1",
|
||||
"dbgate-web": "^4.1.1",
|
||||
"dbgate-plugin-csv": "^4.1.1",
|
||||
"dbgate-plugin-excel": "^4.1.1",
|
||||
"dbgate-plugin-mongo": "^4.1.1",
|
||||
"dbgate-plugin-mysql": "^4.1.1",
|
||||
"dbgate-plugin-mssql": "^4.1.1",
|
||||
"dbgate-plugin-postgres": "^4.1.1"
|
||||
"dbgate-api": "^4.0.0",
|
||||
"dbgate-web": "^4.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"version": "4.1.1",
|
||||
"version": "4.0.0",
|
||||
"name": "dbgate-filterparser",
|
||||
"main": "lib/index.js",
|
||||
"typings": "lib/index.d.ts",
|
||||
"scripts": {
|
||||
"prepare": "yarn build",
|
||||
"build": "tsc",
|
||||
"start": "tsc --watch",
|
||||
"test": "jest"
|
||||
@@ -12,7 +13,7 @@
|
||||
"lib"
|
||||
],
|
||||
"devDependencies": {
|
||||
"dbgate-types": "^4.1.1",
|
||||
"dbgate-types": "^4.0.0",
|
||||
"@types/jest": "^25.1.4",
|
||||
"@types/node": "^13.7.0",
|
||||
"jest": "^24.9.0",
|
||||
@@ -21,7 +22,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/parsimmon": "^1.10.1",
|
||||
"dbgate-tools": "^4.1.1",
|
||||
"dbgate-tools": "^4.0.0",
|
||||
"lodash": "^4.17.15",
|
||||
"moment": "^2.24.0",
|
||||
"parsimmon": "^1.13.0"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "4.1.1",
|
||||
"version": "4.0.0",
|
||||
"name": "dbgate-sqltree",
|
||||
"main": "lib/index.js",
|
||||
"typings": "lib/index.d.ts",
|
||||
@@ -8,6 +8,7 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/dbgate/dbgate.git"
|
||||
},
|
||||
"funding": "https://www.paypal.com/paypalme/JanProchazkaCz/30eur",
|
||||
"author": "Jan Prochazka",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
@@ -19,6 +20,7 @@
|
||||
"dbgate"
|
||||
],
|
||||
"scripts": {
|
||||
"prepare": "yarn build",
|
||||
"build": "tsc",
|
||||
"start": "tsc --watch"
|
||||
},
|
||||
@@ -27,7 +29,7 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/node": "^13.7.0",
|
||||
"dbgate-types": "^4.1.1",
|
||||
"dbgate-types": "^4.0.0",
|
||||
"typescript": "^3.7.5"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -62,12 +62,5 @@ export function dumpSqlCondition(dmp: SqlDumper, condition: Condition) {
|
||||
dumpSqlSelect(dmp, condition.subQuery);
|
||||
dmp.put(')');
|
||||
break;
|
||||
case 'between':
|
||||
dumpSqlExpression(dmp, condition.expr);
|
||||
dmp.put(' ^between ');
|
||||
dumpSqlExpression(dmp, condition.left);
|
||||
dmp.put(' ^and ');
|
||||
dumpSqlExpression(dmp, condition.right);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,14 +38,5 @@ export function dumpSqlExpression(dmp: SqlDumper, expr: Expression) {
|
||||
case 'transform':
|
||||
dmp.transform(expr.transform, () => dumpSqlExpression(dmp, expr.expr));
|
||||
break;
|
||||
|
||||
case 'rowNumber':
|
||||
dmp.put(" ^row_number() ^over (^order ^by ");
|
||||
dmp.putCollection(', ', expr.orderBy, x => {
|
||||
dumpSqlExpression(dmp, x);
|
||||
dmp.put(' %k', x.direction);
|
||||
});
|
||||
dmp.put(")");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,13 +92,6 @@ export interface NotExistsCondition {
|
||||
subQuery: Select;
|
||||
}
|
||||
|
||||
export interface BetweenCondition {
|
||||
conditionType: 'between';
|
||||
expr: Expression;
|
||||
left: Expression;
|
||||
right: Expression;
|
||||
}
|
||||
|
||||
export type Condition =
|
||||
| BinaryCondition
|
||||
| NotCondition
|
||||
@@ -106,8 +99,7 @@ export type Condition =
|
||||
| CompoudCondition
|
||||
| LikeCondition
|
||||
| ExistsCondition
|
||||
| NotExistsCondition
|
||||
| BetweenCondition;
|
||||
| NotExistsCondition;
|
||||
|
||||
export interface Source {
|
||||
name?: NamedObjectInfo;
|
||||
@@ -161,19 +153,13 @@ export interface TranformExpression {
|
||||
transform: TransformType;
|
||||
}
|
||||
|
||||
export interface RowNumberExpression {
|
||||
exprType: 'rowNumber';
|
||||
orderBy: OrderByExpression[];
|
||||
}
|
||||
|
||||
export type Expression =
|
||||
| ColumnRefExpression
|
||||
| ValueExpression
|
||||
| PlaceholderExpression
|
||||
| RawExpression
|
||||
| CallExpression
|
||||
| TranformExpression
|
||||
| RowNumberExpression;
|
||||
| TranformExpression;
|
||||
export type OrderByExpression = Expression & { direction: 'ASC' | 'DESC' };
|
||||
|
||||
export type ResultField = Expression & { alias?: string };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "4.1.1",
|
||||
"version": "4.0.3-rc.1",
|
||||
"name": "dbgate-tools",
|
||||
"main": "lib/index.js",
|
||||
"typings": "lib/index.d.ts",
|
||||
@@ -8,6 +8,7 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/dbgate/dbgate.git"
|
||||
},
|
||||
"funding": "https://www.paypal.com/paypalme/JanProchazkaCz/30eur",
|
||||
"author": "Jan Prochazka",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
@@ -15,6 +16,7 @@
|
||||
"dbgate"
|
||||
],
|
||||
"scripts": {
|
||||
"prepare": "yarn build",
|
||||
"build": "tsc",
|
||||
"start": "tsc --watch",
|
||||
"test": "jest",
|
||||
@@ -25,7 +27,7 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/node": "^13.7.0",
|
||||
"dbgate-types": "^4.1.1",
|
||||
"dbgate-types": "^4.0.0",
|
||||
"jest": "^24.9.0",
|
||||
"ts-jest": "^25.2.1",
|
||||
"typescript": "^3.7.5"
|
||||
@@ -33,4 +35,4 @@
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.15"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,17 +71,6 @@ export class DatabaseAnalyser {
|
||||
// }
|
||||
}
|
||||
|
||||
getRequestedObjectPureNames(objectTypeField, allPureNames) {
|
||||
if (this.singleObjectFilter) {
|
||||
const { typeField, pureName } = this.singleObjectFilter;
|
||||
if (typeField == objectTypeField) return [pureName];
|
||||
}
|
||||
if (this.modifications) {
|
||||
return this.modifications.filter(x => x.objectTypeField == objectTypeField).map(x => x.newName.pureName);
|
||||
}
|
||||
return allPureNames;
|
||||
}
|
||||
|
||||
// findObjectById(id) {
|
||||
// return this.structure.tables.find((x) => x.objectId == id);
|
||||
// }
|
||||
|
||||
@@ -24,10 +24,7 @@ export const driverBase = {
|
||||
const analyser = new this.analyserClass(pool, this);
|
||||
analyser.singleObjectFilter = { ...name, typeField };
|
||||
const res = await analyser.fullAnalysis();
|
||||
if (res[typeField].length == 1) return res[typeField][0];
|
||||
const obj = res[typeField].find(x => x.pureName == name.pureName && x.schemaName == name.schemaName);
|
||||
// console.log('FIND', name, obj);
|
||||
return obj;
|
||||
return res.tables[0];
|
||||
},
|
||||
analyseSingleTable(pool, name) {
|
||||
return this.analyseSingleObject(pool, name, 'tables');
|
||||
|
||||
@@ -7,6 +7,6 @@ export * from './DatabaseAnalyser';
|
||||
export * from './driverBase';
|
||||
export * from './SqlDumper';
|
||||
export * from './testPermission';
|
||||
export * from './splitPostgresQuery';
|
||||
export * from './SqlGenerator';
|
||||
export * from './structureTools';
|
||||
export * from './settingsExtractors';
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
export function extractIntSettingsValue(settings, name, defaultValue, min = null, max = null) {
|
||||
const parsed = parseInt(settings[name]);
|
||||
if (_.isNaN(parsed)) {
|
||||
return defaultValue;
|
||||
}
|
||||
if (_.isNumber(parsed)) {
|
||||
if (min != null && parsed < min) return min;
|
||||
if (max != null && parsed > max) return max;
|
||||
return parsed;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
export function extractBoolSettingsValue(settings, name, defaultValue) {
|
||||
const res = settings[name];
|
||||
if (res == null) return defaultValue;
|
||||
return !!res;
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
const SINGLE_QUOTE = "'";
|
||||
const DOUBLE_QUOTE = '"';
|
||||
// const BACKTICK = '`';
|
||||
const DOUBLE_DASH_COMMENT_START = '--';
|
||||
const HASH_COMMENT_START = '#';
|
||||
const C_STYLE_COMMENT_START = '/*';
|
||||
const SEMICOLON = ';';
|
||||
const LINE_FEED = '\n';
|
||||
const DELIMITER_KEYWORD = 'DELIMITER';
|
||||
|
||||
export interface SplitOptions {
|
||||
multipleStatements?: boolean;
|
||||
retainComments?: boolean;
|
||||
}
|
||||
|
||||
interface SqlStatement {
|
||||
value: string;
|
||||
supportMulti: boolean;
|
||||
}
|
||||
|
||||
interface SplitExecutionContext extends Required<SplitOptions> {
|
||||
unread: string;
|
||||
currentDelimiter: string;
|
||||
currentStatement: SqlStatement;
|
||||
output: SqlStatement[];
|
||||
}
|
||||
|
||||
interface FindExpResult {
|
||||
expIndex: number;
|
||||
exp: string | null;
|
||||
nextIndex: number;
|
||||
}
|
||||
|
||||
const regexEscapeSetRegex = /[-/\\^$*+?.()|[\]{}]/g;
|
||||
const singleQuoteStringEndRegex = /(?<!\\)'/;
|
||||
const doubleQuoteStringEndRegex = /(?<!\\)"/;
|
||||
// const backtickQuoteEndRegex = /(?<!`)`(?!`)/;
|
||||
const doubleDashCommentStartRegex = /--[ \f\n\r\t\v]/;
|
||||
const cStyleCommentStartRegex = /\/\*/;
|
||||
const cStyleCommentEndRegex = /(?<!\/)\*\//;
|
||||
const newLineRegex = /(?:[\r\n]+|$)/;
|
||||
const delimiterStartRegex = /(?:^|[\n\r]+)[ \f\t\v]*DELIMITER[ \t]+/i;
|
||||
// Best effort only, unable to find a syntax specification on delimiter
|
||||
const delimiterTokenRegex = /^(?:'(.+)'|"(.+)"|`(.+)`|([^\s]+))/;
|
||||
const semicolonKeyTokenRegex = buildKeyTokenRegex(SEMICOLON);
|
||||
const quoteEndRegexDict: Record<string, RegExp> = {
|
||||
[SINGLE_QUOTE]: singleQuoteStringEndRegex,
|
||||
[DOUBLE_QUOTE]: doubleQuoteStringEndRegex,
|
||||
// [BACKTICK]: backtickQuoteEndRegex,
|
||||
};
|
||||
|
||||
function escapeRegex(value: string): string {
|
||||
return value.replace(regexEscapeSetRegex, '\\$&');
|
||||
}
|
||||
|
||||
function buildKeyTokenRegex(delimiter: string): RegExp {
|
||||
return new RegExp(
|
||||
'(?:' +
|
||||
[
|
||||
escapeRegex(delimiter),
|
||||
SINGLE_QUOTE,
|
||||
DOUBLE_QUOTE,
|
||||
// BACKTICK,
|
||||
doubleDashCommentStartRegex.source,
|
||||
HASH_COMMENT_START,
|
||||
cStyleCommentStartRegex.source,
|
||||
delimiterStartRegex.source,
|
||||
].join('|') +
|
||||
')',
|
||||
'i'
|
||||
);
|
||||
}
|
||||
|
||||
function findExp(content: string, regex: RegExp): FindExpResult {
|
||||
const match = content.match(regex);
|
||||
let result: FindExpResult;
|
||||
if (match?.index !== undefined) {
|
||||
result = {
|
||||
expIndex: match.index,
|
||||
exp: match[0],
|
||||
nextIndex: match.index + match[0].length,
|
||||
};
|
||||
} else {
|
||||
result = {
|
||||
expIndex: -1,
|
||||
exp: null,
|
||||
nextIndex: content.length,
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function findKeyToken(content: string, currentDelimiter: string): FindExpResult {
|
||||
let regex;
|
||||
if (currentDelimiter === SEMICOLON) {
|
||||
regex = semicolonKeyTokenRegex;
|
||||
} else {
|
||||
regex = buildKeyTokenRegex(currentDelimiter);
|
||||
}
|
||||
return findExp(content, regex);
|
||||
}
|
||||
|
||||
function findEndQuote(content: string, quote: string): FindExpResult {
|
||||
if (!(quote in quoteEndRegexDict)) {
|
||||
throw new TypeError(`Incorrect quote ${quote} supplied`);
|
||||
}
|
||||
return findExp(content, quoteEndRegexDict[quote]);
|
||||
}
|
||||
|
||||
function read(
|
||||
context: SplitExecutionContext,
|
||||
readToIndex: number,
|
||||
nextUnreadIndex?: number,
|
||||
checkSemicolon?: boolean
|
||||
): void {
|
||||
if (checkSemicolon === undefined) {
|
||||
checkSemicolon = true;
|
||||
}
|
||||
const readContent = context.unread.slice(0, readToIndex);
|
||||
if (checkSemicolon && readContent.includes(SEMICOLON)) {
|
||||
context.currentStatement.supportMulti = false;
|
||||
}
|
||||
context.currentStatement.value += readContent;
|
||||
if (nextUnreadIndex !== undefined && nextUnreadIndex > 0) {
|
||||
context.unread = context.unread.slice(nextUnreadIndex);
|
||||
} else {
|
||||
context.unread = context.unread.slice(readToIndex);
|
||||
}
|
||||
}
|
||||
|
||||
function readTillNewLine(context: SplitExecutionContext, checkSemicolon?: boolean): void {
|
||||
const findResult = findExp(context.unread, newLineRegex);
|
||||
read(context, findResult.expIndex, findResult.expIndex, checkSemicolon);
|
||||
}
|
||||
|
||||
function discard(context: SplitExecutionContext, nextUnreadIndex: number): void {
|
||||
if (nextUnreadIndex > 0) {
|
||||
context.unread = context.unread.slice(nextUnreadIndex);
|
||||
}
|
||||
}
|
||||
|
||||
function discardTillNewLine(context: SplitExecutionContext): void {
|
||||
const findResult = findExp(context.unread, newLineRegex);
|
||||
discard(context, findResult.expIndex);
|
||||
}
|
||||
|
||||
function publishStatementInMultiMode(splitOutput: SqlStatement[], currentStatement: SqlStatement): void {
|
||||
if (splitOutput.length === 0) {
|
||||
splitOutput.push({
|
||||
value: '',
|
||||
supportMulti: true,
|
||||
});
|
||||
}
|
||||
const lastSplitResult = splitOutput[splitOutput.length - 1];
|
||||
if (currentStatement.supportMulti) {
|
||||
if (lastSplitResult.supportMulti) {
|
||||
if (lastSplitResult.value !== '' && !lastSplitResult.value.endsWith(LINE_FEED)) {
|
||||
lastSplitResult.value += LINE_FEED;
|
||||
}
|
||||
lastSplitResult.value += currentStatement.value + SEMICOLON;
|
||||
} else {
|
||||
splitOutput.push({
|
||||
value: currentStatement.value + SEMICOLON,
|
||||
supportMulti: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
splitOutput.push({
|
||||
value: currentStatement.value,
|
||||
supportMulti: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function publishStatement(context: SplitExecutionContext): void {
|
||||
const trimmed = context.currentStatement.value.trim();
|
||||
if (trimmed !== '') {
|
||||
if (!context.multipleStatements) {
|
||||
context.output.push({
|
||||
value: trimmed,
|
||||
supportMulti: context.currentStatement.supportMulti,
|
||||
});
|
||||
} else {
|
||||
context.currentStatement.value = trimmed;
|
||||
publishStatementInMultiMode(context.output, context.currentStatement);
|
||||
}
|
||||
}
|
||||
context.currentStatement.value = '';
|
||||
context.currentStatement.supportMulti = true;
|
||||
}
|
||||
|
||||
function handleKeyTokenFindResult(context: SplitExecutionContext, findResult: FindExpResult): void {
|
||||
switch (findResult.exp?.trim()) {
|
||||
case context.currentDelimiter:
|
||||
read(context, findResult.expIndex, findResult.nextIndex);
|
||||
publishStatement(context);
|
||||
break;
|
||||
// case BACKTICK:
|
||||
case SINGLE_QUOTE:
|
||||
case DOUBLE_QUOTE: {
|
||||
read(context, findResult.nextIndex);
|
||||
const findQuoteResult = findEndQuote(context.unread, findResult.exp);
|
||||
read(context, findQuoteResult.nextIndex, undefined, false);
|
||||
break;
|
||||
}
|
||||
case DOUBLE_DASH_COMMENT_START: {
|
||||
if (context.retainComments) {
|
||||
read(context, findResult.nextIndex);
|
||||
readTillNewLine(context, false);
|
||||
} else {
|
||||
read(context, findResult.expIndex, findResult.expIndex + DOUBLE_DASH_COMMENT_START.length);
|
||||
discardTillNewLine(context);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HASH_COMMENT_START: {
|
||||
if (context.retainComments) {
|
||||
read(context, findResult.nextIndex);
|
||||
readTillNewLine(context, false);
|
||||
} else {
|
||||
read(context, findResult.expIndex, findResult.nextIndex);
|
||||
discardTillNewLine(context);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case C_STYLE_COMMENT_START: {
|
||||
if (['!', '+'].includes(context.unread[findResult.nextIndex]) || context.retainComments) {
|
||||
// Should not be skipped, see https://dev.mysql.com/doc/refman/5.7/en/comments.html
|
||||
read(context, findResult.nextIndex);
|
||||
const findCommentResult = findExp(context.unread, cStyleCommentEndRegex);
|
||||
read(context, findCommentResult.nextIndex);
|
||||
} else {
|
||||
read(context, findResult.expIndex, findResult.nextIndex);
|
||||
const findCommentResult = findExp(context.unread, cStyleCommentEndRegex);
|
||||
discard(context, findCommentResult.nextIndex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DELIMITER_KEYWORD: {
|
||||
read(context, findResult.expIndex, findResult.nextIndex);
|
||||
// MySQL client will return `DELIMITER cannot contain a backslash character` if backslash is used
|
||||
// Shall we reject backslash as well?
|
||||
const matched = context.unread.match(delimiterTokenRegex);
|
||||
if (matched?.index !== undefined) {
|
||||
context.currentDelimiter = matched[0].trim();
|
||||
discard(context, matched[0].length);
|
||||
}
|
||||
discardTillNewLine(context);
|
||||
break;
|
||||
}
|
||||
case undefined:
|
||||
case null:
|
||||
read(context, findResult.nextIndex);
|
||||
publishStatement(context);
|
||||
break;
|
||||
default:
|
||||
// This should never happen
|
||||
throw new Error(`Unknown token '${findResult.exp ?? '(null)'}'`);
|
||||
}
|
||||
}
|
||||
|
||||
export function splitPostgresQuery(sql: string, options?: SplitOptions): string[] {
|
||||
options = options ?? {};
|
||||
const context: SplitExecutionContext = {
|
||||
multipleStatements: options.multipleStatements ?? false,
|
||||
retainComments: options.retainComments ?? false,
|
||||
unread: sql,
|
||||
currentDelimiter: SEMICOLON,
|
||||
currentStatement: {
|
||||
value: '',
|
||||
supportMulti: true,
|
||||
},
|
||||
output: [],
|
||||
};
|
||||
let findResult: FindExpResult = {
|
||||
expIndex: -1,
|
||||
exp: null,
|
||||
nextIndex: 0,
|
||||
};
|
||||
let lastUnreadLength;
|
||||
do {
|
||||
lastUnreadLength = context.unread.length;
|
||||
findResult = findKeyToken(context.unread, context.currentDelimiter);
|
||||
handleKeyTokenFindResult(context, findResult);
|
||||
// Prevent infinite loop by returning incorrect result
|
||||
if (lastUnreadLength === context.unread.length) {
|
||||
read(context, context.unread.length);
|
||||
}
|
||||
} while (context.unread !== '');
|
||||
publishStatement(context);
|
||||
return context.output.map(v => v.value);
|
||||
}
|
||||
Vendored
-1
@@ -1,7 +1,6 @@
|
||||
export interface SqlDialect {
|
||||
rangeSelect?: boolean;
|
||||
limitSelect?: boolean;
|
||||
rowNumberOverPaging?: boolean;
|
||||
stringEscapeChar: string;
|
||||
offsetFetchRangeSyntax?: boolean;
|
||||
quoteIdentifier(s: string): string;
|
||||
|
||||
Vendored
+1
-9
@@ -27,19 +27,15 @@ export interface EngineAuthType {
|
||||
export interface ReadCollectionOptions {
|
||||
pureName: string;
|
||||
schemaName?: string;
|
||||
|
||||
|
||||
countDocuments?: boolean;
|
||||
skip?: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface EngineDriver {
|
||||
engine: string;
|
||||
title: string;
|
||||
defaultPort?: number;
|
||||
supportsDatabaseUrl?: boolean;
|
||||
isFileDatabase?: boolean;
|
||||
databaseUrlPlaceholder?: string;
|
||||
connect({ server, port, user, password, database }): any;
|
||||
query(pool: any, sql: string): Promise<QueryResult>;
|
||||
stream(pool: any, sql: string, options: StreamOptions);
|
||||
@@ -62,13 +58,9 @@ export interface EngineDriver {
|
||||
analyseFull(pool: any): Promise<DatabaseInfo>;
|
||||
analyseIncremental(pool: any, structure: DatabaseInfo): Promise<DatabaseInfo>;
|
||||
dialect: SqlDialect;
|
||||
dialectByVersion(version): SqlDialect;
|
||||
createDumper(): SqlDumper;
|
||||
getAuthTypes(): EngineAuthType[];
|
||||
readCollection(pool: any, options: ReadCollectionOptions): Promise<any>;
|
||||
updateCollection(pool: any, changeSet: any): Promise<any>;
|
||||
getCollectionUpdateScript(changeSet: any): string;
|
||||
createDatabase(pool: any, name: string): Promise;
|
||||
|
||||
analyserClass?: any;
|
||||
dumperClass?: any;
|
||||
|
||||
Vendored
-1
@@ -4,7 +4,6 @@ export interface OpenedDatabaseConnection {
|
||||
conid: string;
|
||||
database: string;
|
||||
structure: DatabaseInfo;
|
||||
serverVersion?: any;
|
||||
subprocess: ChildProcess;
|
||||
disconnected?: boolean;
|
||||
status?: {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"version": "4.1.1",
|
||||
"version": "4.0.0",
|
||||
"name": "dbgate-types",
|
||||
"homepage": "https://dbgate.org/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dbgate/dbgate.git"
|
||||
},
|
||||
"funding": "https://www.paypal.com/paypalme/JanProchazkaCz/30eur",
|
||||
"author": "Jan Prochazka",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
+12
-11
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dbgate-web",
|
||||
"version": "4.1.1",
|
||||
"version": "4.0.0",
|
||||
"scripts": {
|
||||
"build": "rollup -c",
|
||||
"dev": "cross-env API_URL=http://localhost:3000 rollup -c -w",
|
||||
@@ -13,7 +13,6 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@ant-design/colors": "^5.0.0",
|
||||
"@mdi/font": "^5.9.55",
|
||||
"@rollup/plugin-commonjs": "^17.0.0",
|
||||
"@rollup/plugin-node-resolve": "^11.0.0",
|
||||
"@rollup/plugin-replace": "^2.4.1",
|
||||
@@ -22,16 +21,14 @@
|
||||
"ace-builds": "^1.4.8",
|
||||
"chart.js": "^2.9.4",
|
||||
"cross-env": "^7.0.3",
|
||||
"dbgate-datalib": "^4.1.1",
|
||||
"dbgate-sqltree": "^4.1.1",
|
||||
"dbgate-tools": "^4.1.1",
|
||||
"dbgate-types": "^4.1.1",
|
||||
"file-selector": "^0.2.4",
|
||||
"dbgate-datalib": "^4.0.0",
|
||||
"dbgate-sqltree": "^4.0.0",
|
||||
"dbgate-tools": "^4.0.0",
|
||||
"dbgate-types": "^4.0.0",
|
||||
"json-stable-stringify": "^1.0.1",
|
||||
"localforage": "^1.9.0",
|
||||
"lodash": "^4.17.15",
|
||||
"randomcolor": "^0.6.2",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"rollup": "^2.3.4",
|
||||
"rollup-plugin-copy": "^3.3.0",
|
||||
"rollup-plugin-css-only": "^3.1.0",
|
||||
@@ -43,11 +40,15 @@
|
||||
"sql-formatter": "^2.3.3",
|
||||
"svelte": "^3.35.0",
|
||||
"svelte-check": "^1.0.0",
|
||||
"svelte-markdown": "^0.1.4",
|
||||
"svelte-preprocess": "^4.0.0",
|
||||
"svelte-select": "^3.17.0",
|
||||
"tslib": "^2.0.0",
|
||||
"typescript": "^3.9.3",
|
||||
"uuid": "^3.4.0"
|
||||
"uuid": "^3.4.0",
|
||||
"@mdi/font": "^5.9.55",
|
||||
"file-selector": "^0.2.4",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"svelte-json-tree": "^0.1.0",
|
||||
"svelte-markdown": "^0.1.4",
|
||||
"svelte-select": "^3.17.0"
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,11 @@
|
||||
<script lang="ts">
|
||||
import CommandListener from './commands/CommandListener.svelte';
|
||||
import DataGridRowHeightMeter from './datagrid/DataGridRowHeightMeter.svelte';
|
||||
import LoadingInfo from './elements/LoadingInfo.svelte';
|
||||
|
||||
import PluginsProvider from './plugins/PluginsProvider.svelte';
|
||||
import Screen from './Screen.svelte';
|
||||
import ErrorHandler from './utility/ErrorHandler.svelte';
|
||||
import { useSettings } from './utility/metadataLoaders';
|
||||
import OpenTabsOnStartup from './utility/OpenTabsOnStartup.svelte';
|
||||
|
||||
const settings = useSettings();
|
||||
</script>
|
||||
|
||||
<DataGridRowHeightMeter />
|
||||
@@ -17,9 +13,4 @@
|
||||
<PluginsProvider />
|
||||
<CommandListener />
|
||||
<OpenTabsOnStartup />
|
||||
|
||||
{#if $settings}
|
||||
<Screen />
|
||||
{:else}
|
||||
<LoadingInfo message="Loading settings..." wrapper />
|
||||
{/if}
|
||||
<Screen />
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
$: fileTypeNames = _.compact([
|
||||
...$extensions.fileFormats.filter(x => x.readerFunc).map(x => x.name),
|
||||
electron ? 'SQL' : null,
|
||||
electron ? 'SQLite database' : null,
|
||||
]);
|
||||
</script>
|
||||
|
||||
|
||||
@@ -72,7 +72,6 @@
|
||||
}
|
||||
.iconbar {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
left: 0;
|
||||
top: var(--dim-header-top);
|
||||
bottom: var(--dim-statusbar-height);
|
||||
|
||||
@@ -1,83 +1,24 @@
|
||||
<script context="module">
|
||||
export const extractKey = data => data._id;
|
||||
export const createMatcher = ({ displayName, server }) => filter => filterName(filter, displayName, server);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import AppObjectCore from './AppObjectCore.svelte';
|
||||
import { currentDatabase, extensions, getCurrentConfig, openedConnections } from '../stores';
|
||||
import axiosInstance from '../utility/axiosInstance';
|
||||
import { filterName } from 'dbgate-datalib';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import ConnectionModal from '../modals/ConnectionModal.svelte';
|
||||
import ConfirmModal from '../modals/ConfirmModal.svelte';
|
||||
import InputTextModal from '../modals/InputTextModal.svelte';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import { getDatabaseMenuItems } from './DatabaseAppObject.svelte';
|
||||
import getElectron from '../utility/getElectron';
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
|
||||
export let data;
|
||||
|
||||
let statusIcon = null;
|
||||
let statusTitle = null;
|
||||
let extInfo = null;
|
||||
let engineStatusIcon = null;
|
||||
let engineStatusTitle = null;
|
||||
|
||||
const electron = getElectron();
|
||||
|
||||
const handleConnect = () => {
|
||||
if (data.singleDatabase) {
|
||||
$currentDatabase = { connection: data, name: data.defaultDatabase };
|
||||
axiosInstance.post('database-connections/refresh', {
|
||||
conid: data._id,
|
||||
database: data.defaultDatabase,
|
||||
keepOpen: true,
|
||||
});
|
||||
} else {
|
||||
$openedConnections = _.uniq([...$openedConnections, data._id]);
|
||||
axiosInstance.post('server-connections/refresh', {
|
||||
conid: data._id,
|
||||
keepOpen: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getContextMenu = () => {
|
||||
const getContextMenu = (data, $openedConnections) => () => {
|
||||
const config = getCurrentConfig();
|
||||
const handleRefresh = () => {
|
||||
axiosInstance.post('server-connections/refresh', { conid: data._id });
|
||||
};
|
||||
const handleDisconnect = () => {
|
||||
openedConnections.update(list => list.filter(x => x != data._id));
|
||||
if (electron) {
|
||||
axiosInstance.post('server-connections/disconnect', { conid: data._id });
|
||||
}
|
||||
if (_.get($currentDatabase, 'connection._id') == data._id) {
|
||||
if (electron) {
|
||||
axiosInstance.post('database-connections/disconnect', { conid: data._id, database: $currentDatabase.name });
|
||||
}
|
||||
currentDatabase.set(null);
|
||||
}
|
||||
};
|
||||
const handleConnect = () => {
|
||||
openedConnections.update(list => _.uniq([...list, data._id]));
|
||||
};
|
||||
const handleEdit = () => {
|
||||
showModal(ConnectionModal, { connection: data });
|
||||
};
|
||||
const handleDelete = () => {
|
||||
showModal(ConfirmModal, {
|
||||
message: `Really delete connection ${getConnectionLabel(data)}?`,
|
||||
message: `Really delete connection ${data.displayName || data.server}?`,
|
||||
onConfirm: () => axiosInstance.post('connections/delete', data),
|
||||
});
|
||||
};
|
||||
const handleDuplicate = () => {
|
||||
axiosInstance.post('connections/save', {
|
||||
...data,
|
||||
_id: undefined,
|
||||
displayName: `${getConnectionLabel(data)} - copy`,
|
||||
});
|
||||
};
|
||||
const handleCreateDatabase = () => {
|
||||
showModal(InputTextModal, {
|
||||
header: 'Create database',
|
||||
@@ -90,18 +31,6 @@
|
||||
}),
|
||||
});
|
||||
};
|
||||
const handleNewQuery = () => {
|
||||
const tooltip = `${getConnectionLabel(data)}`;
|
||||
openNewTab({
|
||||
title: 'Query #',
|
||||
icon: 'img sql-file',
|
||||
tooltip,
|
||||
tabComponent: 'QueryTab',
|
||||
props: {
|
||||
conid: data._id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return [
|
||||
config.runAsPortal == false && [
|
||||
@@ -113,38 +42,50 @@
|
||||
text: 'Delete',
|
||||
onClick: handleDelete,
|
||||
},
|
||||
{
|
||||
text: 'Duplicate',
|
||||
onClick: handleDuplicate,
|
||||
},
|
||||
],
|
||||
!data.singleDatabase && [
|
||||
!$openedConnections.includes(data._id) && {
|
||||
text: 'Connect',
|
||||
onClick: handleConnect,
|
||||
!$openedConnections.includes(data._id) && {
|
||||
text: 'Connect',
|
||||
onClick: handleConnect,
|
||||
},
|
||||
$openedConnections.includes(data._id) &&
|
||||
data.status && {
|
||||
text: 'Refresh',
|
||||
onClick: handleRefresh,
|
||||
},
|
||||
{ onClick: handleNewQuery, text: 'New query' },
|
||||
$openedConnections.includes(data._id) &&
|
||||
data.status && {
|
||||
text: 'Refresh',
|
||||
onClick: handleRefresh,
|
||||
},
|
||||
$openedConnections.includes(data._id) && {
|
||||
text: 'Disconnect',
|
||||
onClick: handleDisconnect,
|
||||
},
|
||||
$openedConnections.includes(data._id) && {
|
||||
text: 'Create database',
|
||||
onClick: handleCreateDatabase,
|
||||
},
|
||||
],
|
||||
data.singleDatabase && [
|
||||
{ divider: true },
|
||||
getDatabaseMenuItems(data, data.defaultDatabase, $extensions, $currentDatabase),
|
||||
],
|
||||
$openedConnections.includes(data._id) && {
|
||||
text: 'Disconnect',
|
||||
onClick: handleDisconnect,
|
||||
},
|
||||
$openedConnections.includes(data._id) && {
|
||||
text: 'Create database',
|
||||
onClick: handleCreateDatabase,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const extractKey = data => data._id;
|
||||
export const createMatcher = ({ displayName, server }) => filter => filterName(filter, displayName, server);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import AppObjectCore from './AppObjectCore.svelte';
|
||||
import { currentDatabase, extensions, getCurrentConfig, openedConnections } from '../stores';
|
||||
import axiosInstance from '../utility/axiosInstance';
|
||||
import { filterName } from 'dbgate-datalib';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import ConnectionModal from '../modals/ConnectionModal.svelte';
|
||||
import ConfirmModal from '../modals/ConfirmModal.svelte';
|
||||
import InputTextModal from '../modals/InputTextModal.svelte';
|
||||
|
||||
export let data;
|
||||
|
||||
let statusIcon = null;
|
||||
let statusTitle = null;
|
||||
let extInfo = null;
|
||||
let engineStatusIcon = null;
|
||||
let engineStatusTitle = null;
|
||||
|
||||
$: {
|
||||
if ($extensions.drivers.find(x => x.engine == data.engine)) {
|
||||
const match = (data.engine || '').match(/^([^@]*)@/);
|
||||
@@ -173,21 +114,34 @@
|
||||
statusTitle = null;
|
||||
}
|
||||
}
|
||||
|
||||
// const handleEdit = () => {
|
||||
// showModal(modalState => <ConnectionModal modalState={modalState} connection={data} />);
|
||||
// };
|
||||
// const handleDelete = () => {
|
||||
// showModal(modalState => (
|
||||
// <ConfirmModal
|
||||
// modalState={modalState}
|
||||
// message={`Really delete connection ${data.displayName || data.server}?`}
|
||||
// onConfirm={() => axios.post('connections/delete', data)}
|
||||
// />
|
||||
// ));
|
||||
// };
|
||||
// const handleCreateDatabase = () => {
|
||||
// showModal(modalState => <CreateDatabaseModal modalState={modalState} conid={data._id} />);
|
||||
// };
|
||||
</script>
|
||||
|
||||
<AppObjectCore
|
||||
{...$$restProps}
|
||||
{data}
|
||||
title={getConnectionLabel(data)}
|
||||
icon={data.singleDatabase ? 'img database' : 'img server'}
|
||||
isBold={data.singleDatabase
|
||||
? _.get($currentDatabase, 'connection._id') == data._id && _.get($currentDatabase, 'name') == data.defaultDatabase
|
||||
: _.get($currentDatabase, 'connection._id') == data._id}
|
||||
title={data.displayName || data.server}
|
||||
icon="img server"
|
||||
isBold={_.get($currentDatabase, 'connection._id') == data._id}
|
||||
statusIcon={statusIcon || engineStatusIcon}
|
||||
statusTitle={statusTitle || engineStatusTitle}
|
||||
{extInfo}
|
||||
menu={getContextMenu}
|
||||
on:click={handleConnect}
|
||||
menu={getContextMenu(data, $openedConnections)}
|
||||
on:click={() => ($openedConnections = _.uniq([...$openedConnections, data._id]))}
|
||||
on:click
|
||||
on:expand
|
||||
/>
|
||||
|
||||
@@ -1,87 +1,64 @@
|
||||
<script lang="ts" context="module">
|
||||
export const extractKey = props => props.name;
|
||||
const electron = getElectron();
|
||||
|
||||
export function getDatabaseMenuItems(connection, name, $extensions, $currentDatabase) {
|
||||
const handleNewQuery = () => {
|
||||
const tooltip = `${getConnectionLabel(connection)}\n${name}`;
|
||||
openNewTab({
|
||||
title: 'Query #',
|
||||
icon: 'img sql-file',
|
||||
tooltip,
|
||||
tabComponent: 'QueryTab',
|
||||
props: {
|
||||
conid: connection._id,
|
||||
database: name,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleImport = () => {
|
||||
showModal(ImportExportModal, {
|
||||
initialValues: {
|
||||
sourceStorageType: getDefaultFileFormat($extensions).storageType,
|
||||
targetStorageType: 'database',
|
||||
targetConnectionId: connection._id,
|
||||
targetDatabaseName: name,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleExport = () => {
|
||||
showModal(ImportExportModal, {
|
||||
initialValues: {
|
||||
targetStorageType: getDefaultFileFormat($extensions).storageType,
|
||||
sourceStorageType: 'database',
|
||||
sourceConnectionId: connection._id,
|
||||
sourceDatabaseName: name,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleSqlGenerator = () => {
|
||||
showModal(SqlGeneratorModal, {
|
||||
conid: connection._id,
|
||||
database: name,
|
||||
});
|
||||
};
|
||||
|
||||
const handleDisconnect = () => {
|
||||
if (electron) {
|
||||
axiosInstance.post('database-connections/disconnect', { conid: connection._id, database: name });
|
||||
}
|
||||
currentDatabase.set(null);
|
||||
};
|
||||
|
||||
return [
|
||||
{ onClick: handleNewQuery, text: 'New query' },
|
||||
{ onClick: handleImport, text: 'Import' },
|
||||
{ onClick: handleExport, text: 'Export' },
|
||||
{ onClick: handleSqlGenerator, text: 'SQL Generator' },
|
||||
|
||||
_.get($currentDatabase, 'connection._id') == _.get(connection, '_id') &&
|
||||
_.get($currentDatabase, 'name') == name && { onClick: handleDisconnect, text: 'Disconnect' },
|
||||
];
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
|
||||
import _ from 'lodash';
|
||||
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import SqlGeneratorModal from '../modals/SqlGeneratorModal.svelte';
|
||||
import { getDefaultFileFormat } from '../plugins/fileformats';
|
||||
import { currentDatabase, extensions } from '../stores';
|
||||
import axiosInstance from '../utility/axiosInstance';
|
||||
import getElectron from '../utility/getElectron';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import AppObjectCore from './AppObjectCore.svelte';
|
||||
export let data;
|
||||
|
||||
const handleNewQuery = () => {
|
||||
const { connection, name } = data;
|
||||
const tooltip = `${connection.displayName || connection.server}\n${name}`;
|
||||
openNewTab({
|
||||
title: 'Query #',
|
||||
icon: 'img sql-file',
|
||||
tooltip,
|
||||
tabComponent: 'QueryTab',
|
||||
props: {
|
||||
conid: connection._id,
|
||||
database: name,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleImport = () => {
|
||||
const { connection, name } = data;
|
||||
|
||||
showModal(ImportExportModal, {
|
||||
initialValues: {
|
||||
sourceStorageType: getDefaultFileFormat($extensions).storageType,
|
||||
targetStorageType: 'database',
|
||||
targetConnectionId: connection._id,
|
||||
targetDatabaseName: name,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleExport = () => {
|
||||
const { connection, name } = data;
|
||||
|
||||
showModal(ImportExportModal, {
|
||||
initialValues: {
|
||||
targetStorageType: getDefaultFileFormat($extensions).storageType,
|
||||
sourceStorageType: 'database',
|
||||
sourceConnectionId: connection._id,
|
||||
sourceDatabaseName: name,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
function createMenu() {
|
||||
return getDatabaseMenuItems(data.connection, data.name, $extensions, $currentDatabase);
|
||||
return [
|
||||
{ onClick: handleNewQuery, text: 'New query' },
|
||||
{ onClick: handleImport, text: 'Import' },
|
||||
{ onClick: handleExport, text: 'Export' },
|
||||
];
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -61,11 +61,11 @@
|
||||
},
|
||||
{
|
||||
label: 'SQL: CREATE TABLE',
|
||||
scriptTemplate: 'CREATE TABLE',
|
||||
sqlTemplate: 'CREATE TABLE',
|
||||
},
|
||||
{
|
||||
label: 'SQL: SELECT',
|
||||
scriptTemplate: 'SELECT',
|
||||
sqlTemplate: 'SELECT',
|
||||
},
|
||||
{
|
||||
label: 'SQL Generator: CREATE TABLE',
|
||||
@@ -123,15 +123,15 @@
|
||||
},
|
||||
{
|
||||
label: 'SQL: CREATE VIEW',
|
||||
scriptTemplate: 'CREATE OBJECT',
|
||||
sqlTemplate: 'CREATE OBJECT',
|
||||
},
|
||||
{
|
||||
label: 'SQL: CREATE TABLE',
|
||||
scriptTemplate: 'CREATE TABLE',
|
||||
sqlTemplate: 'CREATE TABLE',
|
||||
},
|
||||
{
|
||||
label: 'SQL: SELECT',
|
||||
scriptTemplate: 'SELECT',
|
||||
sqlTemplate: 'SELECT',
|
||||
},
|
||||
{
|
||||
label: 'SQL Generator: CREATE VIEW',
|
||||
@@ -149,11 +149,11 @@
|
||||
procedures: [
|
||||
{
|
||||
label: 'SQL: CREATE PROCEDURE',
|
||||
scriptTemplate: 'CREATE OBJECT',
|
||||
sqlTemplate: 'CREATE OBJECT',
|
||||
},
|
||||
{
|
||||
label: 'SQL: EXECUTE',
|
||||
scriptTemplate: 'EXECUTE PROCEDURE',
|
||||
sqlTemplate: 'EXECUTE PROCEDURE',
|
||||
},
|
||||
{
|
||||
label: 'SQL Generator: CREATE PROCEDURE',
|
||||
@@ -171,7 +171,7 @@
|
||||
functions: [
|
||||
{
|
||||
label: 'SQL: CREATE FUNCTION',
|
||||
scriptTemplate: 'CREATE OBJECT',
|
||||
sqlTemplate: 'CREATE OBJECT',
|
||||
},
|
||||
{
|
||||
label: 'SQL Generator: CREATE FUNCTION',
|
||||
@@ -186,66 +186,34 @@
|
||||
},
|
||||
},
|
||||
],
|
||||
collections: [
|
||||
{
|
||||
label: 'Open data',
|
||||
tab: 'CollectionDataTab',
|
||||
forceNewTab: true,
|
||||
},
|
||||
{
|
||||
label: 'Open JSON',
|
||||
tab: 'CollectionDataTab',
|
||||
forceNewTab: true,
|
||||
initialData: {
|
||||
grid: {
|
||||
isJsonView: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Export',
|
||||
isExport: true,
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
label: 'JS: dropCollection()',
|
||||
scriptTemplate: 'dropCollection',
|
||||
},
|
||||
{
|
||||
label: 'JS: find()',
|
||||
scriptTemplate: 'findCollection',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export async function openDatabaseObjectDetail(
|
||||
tabComponent,
|
||||
scriptTemplate,
|
||||
sqlTemplate,
|
||||
{ schemaName, pureName, conid, database, objectTypeField },
|
||||
forceNewTab,
|
||||
initialData
|
||||
) {
|
||||
const connection = await getConnectionInfo({ conid });
|
||||
const tooltip = `${getConnectionLabel(connection)}\n${database}\n${fullDisplayName({
|
||||
const tooltip = `${connection.displayName || connection.server}\n${database}\n${fullDisplayName({
|
||||
schemaName,
|
||||
pureName,
|
||||
})}`;
|
||||
|
||||
openNewTab(
|
||||
{
|
||||
title: scriptTemplate ? 'Query #' : pureName,
|
||||
title: sqlTemplate ? 'Query #' : pureName,
|
||||
tooltip,
|
||||
icon: scriptTemplate ? 'img sql-file' : icons[objectTypeField],
|
||||
tabComponent: scriptTemplate ? 'QueryTab' : tabComponent,
|
||||
icon: sqlTemplate ? 'img sql-file' : icons[objectTypeField],
|
||||
tabComponent: sqlTemplate ? 'QueryTab' : tabComponent,
|
||||
props: {
|
||||
schemaName,
|
||||
pureName,
|
||||
conid,
|
||||
database,
|
||||
objectTypeField,
|
||||
initialArgs: scriptTemplate ? { scriptTemplate } : null,
|
||||
initialArgs: sqlTemplate ? { sqlTemplate } : null,
|
||||
},
|
||||
},
|
||||
initialData,
|
||||
@@ -267,7 +235,6 @@
|
||||
import { findEngineDriver } from 'dbgate-tools';
|
||||
import uuidv1 from 'uuid/v1';
|
||||
import SqlGeneratorModal from '../modals/SqlGeneratorModal.svelte';
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
|
||||
export let data;
|
||||
|
||||
@@ -399,7 +366,7 @@ import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
database: data.database,
|
||||
});
|
||||
} else {
|
||||
openDatabaseObjectDetail(menu.tab, menu.scriptTemplate, data, menu.forceNewTab, menu.initialData);
|
||||
openDatabaseObjectDetail(menu.tab, menu.sqlTemplate, data, menu.forceNewTab, menu.initialData);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -67,7 +67,6 @@
|
||||
import { currentDatabase } from '../stores';
|
||||
|
||||
import axiosInstance from '../utility/axiosInstance';
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
import hasPermission from '../utility/hasPermission';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
|
||||
@@ -131,7 +130,7 @@
|
||||
const database = _.get($currentDatabase, 'name');
|
||||
connProps.conid = connection._id;
|
||||
connProps.database = database;
|
||||
tooltip = `${getConnectionLabel(connection)}\n${database}`;
|
||||
tooltip = `${connection.displayName || connection.server}\n${database}`;
|
||||
}
|
||||
|
||||
openNewTab(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import JSONTree from 'svelte-json-tree';
|
||||
import ErrorInfo from '../elements/ErrorInfo.svelte';
|
||||
import JSONTree from '../jsontree/JSONTree.svelte';
|
||||
|
||||
export let selection;
|
||||
export let showWholeRow = false;
|
||||
@@ -31,7 +31,7 @@
|
||||
{:else}
|
||||
<div class="outer">
|
||||
<div class="inner">
|
||||
<JSONTree value={json} expanded />
|
||||
<JSONTree value={json} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,19 +1,6 @@
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
|
||||
export let selection;
|
||||
export let wrap;
|
||||
</script>
|
||||
|
||||
<textarea
|
||||
class="flex1"
|
||||
{wrap}
|
||||
readonly
|
||||
value={selection
|
||||
.map(cell => {
|
||||
const { value } = cell;
|
||||
if (_.isPlainObject(value) || _.isArray(value)) return JSON.stringify(value, undefined, 2);
|
||||
return cell.value;
|
||||
})
|
||||
.join('\n')}
|
||||
/>
|
||||
<textarea class="flex1" {wrap} readonly value={selection.map(cell => cell.value).join('\n')} />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" context="module">
|
||||
import { commandsCustomized, visibleCommandPalette } from '../stores';
|
||||
import { commands, visibleCommandPalette } from '../stores';
|
||||
import { get } from 'svelte/store';
|
||||
import { runGroupCommand } from './runCommand';
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
// console.log('keyText', keyText);
|
||||
|
||||
const commandsValue = get(commandsCustomized);
|
||||
const commandsValue = get(commands);
|
||||
const commandsFiltered: any = Object.values(commandsValue).filter(
|
||||
(x: any) =>
|
||||
x.keyText &&
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
import _ from 'lodash';
|
||||
import { onMount } from 'svelte';
|
||||
import { commands, commandsCustomized, getVisibleCommandPalette, visibleCommandPalette } from '../stores';
|
||||
import { commands, getVisibleCommandPalette, visibleCommandPalette } from '../stores';
|
||||
import clickOutside from '../utility/clickOutside';
|
||||
import keycodes from '../utility/keycodes';
|
||||
import registerCommand from './registerCommand';
|
||||
@@ -40,7 +40,7 @@
|
||||
});
|
||||
|
||||
$: sortedComands = _.sortBy(
|
||||
Object.values($commandsCustomized).filter(x => x.enabled),
|
||||
Object.values($commands).filter(x => x.enabled),
|
||||
'text'
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
import { recentDatabases, currentDatabase, getRecentDatabases } from '../stores';
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
import registerCommand from './registerCommand';
|
||||
|
||||
currentDatabase.subscribe(value => {
|
||||
@@ -16,7 +15,7 @@ currentDatabase.subscribe(value => {
|
||||
|
||||
function switchDatabaseCommand(db) {
|
||||
return {
|
||||
text: `${db.name} on ${getConnectionLabel(db?.connection, { allowExplicitDatabase: false })}`,
|
||||
text: `${db.name} on ${db?.connection?.displayName || db?.connection?.server}`,
|
||||
onClick: () => currentDatabase.set(db),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,24 +27,17 @@ export interface GlobalCommand {
|
||||
menuName?: string;
|
||||
toolbarOrder?: number;
|
||||
disableHandleKeyText?: string;
|
||||
isRelatedToTab?: boolean,
|
||||
}
|
||||
|
||||
export default function registerCommand(command: GlobalCommand) {
|
||||
const { testEnabled } = command;
|
||||
commands.update(x => {
|
||||
if (x[command.id]) {
|
||||
console.error(`Command ${command.id} already registered`);
|
||||
return x;
|
||||
}
|
||||
return {
|
||||
...x,
|
||||
[command.id]: {
|
||||
text: `${command.category}: ${command.name}`,
|
||||
...command,
|
||||
enabled: !testEnabled,
|
||||
},
|
||||
};
|
||||
});
|
||||
commands.update(x => ({
|
||||
...x,
|
||||
[command.id]: {
|
||||
text: `${command.category}: ${command.name}`,
|
||||
...command,
|
||||
enabled: !testEnabled,
|
||||
},
|
||||
}));
|
||||
invalidateCommandDefinitions();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { derived, get } from 'svelte/store';
|
||||
import { ThemeDefinition } from 'dbgate-types';
|
||||
import ConnectionModal from '../modals/ConnectionModal.svelte';
|
||||
import AboutModal from '../modals/AboutModal.svelte';
|
||||
import SettingsModal from '../settings/SettingsModal.svelte';
|
||||
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||
import SqlGeneratorModal from '../modals/SqlGeneratorModal.svelte';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
@@ -16,7 +15,6 @@ import { openElectronFile } from '../utility/openElectronFile';
|
||||
import { getDefaultFileFormat } from '../plugins/fileformats';
|
||||
import { getCurrentConfig, getCurrentDatabase } from '../stores';
|
||||
import './recentDatabaseSwitch';
|
||||
import hasPermission from '../utility/hasPermission';
|
||||
|
||||
const electron = getElectron();
|
||||
|
||||
@@ -46,7 +44,7 @@ registerCommand({
|
||||
id: 'toolbar.show',
|
||||
category: 'Toolbar',
|
||||
name: 'Show',
|
||||
onClick: () => visibleToolbar.set(true),
|
||||
onClick: () => visibleToolbar.set(1),
|
||||
testEnabled: () => !getVisibleToolbar(),
|
||||
});
|
||||
|
||||
@@ -54,7 +52,7 @@ registerCommand({
|
||||
id: 'toolbar.hide',
|
||||
category: 'Toolbar',
|
||||
name: 'Hide',
|
||||
onClick: () => visibleToolbar.set(false),
|
||||
onClick: () => visibleToolbar.set(0),
|
||||
testEnabled: () => getVisibleToolbar(),
|
||||
});
|
||||
|
||||
@@ -205,30 +203,6 @@ registerCommand({
|
||||
}),
|
||||
});
|
||||
|
||||
if (hasPermission('settings/change')) {
|
||||
registerCommand({
|
||||
id: 'settings.commands',
|
||||
category: 'Settings',
|
||||
name: 'Keyboard shortcuts',
|
||||
onClick: () => {
|
||||
openNewTab({
|
||||
title: 'Keyboard Shortcuts',
|
||||
icon: 'icon keyboard',
|
||||
tabComponent: 'CommandListTab',
|
||||
props: {},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'settings.show',
|
||||
category: 'Settings',
|
||||
name: 'Change',
|
||||
toolbarName: 'Settings',
|
||||
onClick: () => showModal(SettingsModal),
|
||||
});
|
||||
}
|
||||
|
||||
export function registerFileCommands({
|
||||
idPrefix,
|
||||
category,
|
||||
@@ -241,7 +215,6 @@ export function registerFileCommands({
|
||||
toggleComment = false,
|
||||
findReplace = false,
|
||||
undoRedo = false,
|
||||
executeAdditionalCondition = null,
|
||||
}) {
|
||||
if (save) {
|
||||
registerCommand({
|
||||
@@ -252,7 +225,6 @@ export function registerFileCommands({
|
||||
// keyText: 'Ctrl+S',
|
||||
icon: 'icon save',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => saveTabFile(getCurrentEditor(), false, folder, format, fileExtension),
|
||||
});
|
||||
@@ -273,12 +245,8 @@ export function registerFileCommands({
|
||||
name: 'Execute',
|
||||
icon: 'icon run',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
keyText: 'F5 | Ctrl+Enter',
|
||||
testEnabled: () =>
|
||||
getCurrentEditor() != null &&
|
||||
!getCurrentEditor()?.isBusy() &&
|
||||
(executeAdditionalCondition == null || executeAdditionalCondition()),
|
||||
testEnabled: () => getCurrentEditor() != null && !getCurrentEditor()?.isBusy(),
|
||||
onClick: () => getCurrentEditor().execute(),
|
||||
});
|
||||
registerCommand({
|
||||
@@ -287,7 +255,6 @@ export function registerFileCommands({
|
||||
name: 'Kill',
|
||||
icon: 'icon close',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
testEnabled: () => getCurrentEditor()?.canKill && getCurrentEditor().canKill(),
|
||||
onClick: () => getCurrentEditor().kill(),
|
||||
});
|
||||
|
||||
@@ -11,23 +11,11 @@ import {
|
||||
MacroSelectedCell,
|
||||
revertChangeSetRowChanges,
|
||||
setChangeSetValue,
|
||||
setChangeSetRowData,
|
||||
compileMacroFunction,
|
||||
runMacroOnValue,
|
||||
} from 'dbgate-datalib';
|
||||
import Grider, { GriderRowStatus } from './Grider';
|
||||
|
||||
function getRowFromItem(row, matchedChangeSetItem) {
|
||||
return matchedChangeSetItem.document
|
||||
? {
|
||||
...matchedChangeSetItem.document,
|
||||
...matchedChangeSetItem.fields,
|
||||
}
|
||||
: {
|
||||
...row,
|
||||
...matchedChangeSetItem.fields,
|
||||
};
|
||||
}
|
||||
export default class ChangeSetGrider extends Grider {
|
||||
public insertedRows: any[];
|
||||
public changeSet: ChangeSet;
|
||||
@@ -45,13 +33,13 @@ export default class ChangeSetGrider extends Grider {
|
||||
public changeSetState,
|
||||
public dispatchChangeSet,
|
||||
public display: GridDisplay,
|
||||
public macro: MacroDefinition = null,
|
||||
public macroArgs: {} = {},
|
||||
public selectedCells: MacroSelectedCell[] = []
|
||||
public macro: MacroDefinition,
|
||||
public macroArgs: {},
|
||||
public selectedCells: MacroSelectedCell[]
|
||||
) {
|
||||
super();
|
||||
this.changeSet = changeSetState && changeSetState.value;
|
||||
this.insertedRows = getChangeSetInsertedRows(this.changeSet, display?.baseTableOrCollection);
|
||||
this.insertedRows = getChangeSetInsertedRows(this.changeSet, display?.baseTable);
|
||||
this.setChangeSet = value => dispatchChangeSet({ type: 'set', value });
|
||||
this.rowCacheIndexes = new Set();
|
||||
this.rowDataCache = {};
|
||||
@@ -59,8 +47,6 @@ export default class ChangeSetGrider extends Grider {
|
||||
this.rowDefinitionsCache = {};
|
||||
this.batchChangeSet = null;
|
||||
this.compiledMacroFunc = compileMacroFunction(macro, this._errors);
|
||||
|
||||
// console.log('changeSet', this.changeSet);
|
||||
}
|
||||
|
||||
get errors() {
|
||||
@@ -83,7 +69,7 @@ export default class ChangeSetGrider extends Grider {
|
||||
const rowDefinition = this.display?.getChangeSetRow(row, insertedRowIndex);
|
||||
const [matchedField, matchedChangeSetItem] = findExistingChangeSetItem(this.changeSet, rowDefinition);
|
||||
const rowUpdated = matchedChangeSetItem
|
||||
? getRowFromItem(row, matchedChangeSetItem)
|
||||
? { ...row, ...matchedChangeSetItem.fields }
|
||||
: this.compiledMacroFunc
|
||||
? { ...row }
|
||||
: row;
|
||||
@@ -124,7 +110,7 @@ export default class ChangeSetGrider extends Grider {
|
||||
}
|
||||
|
||||
get canInsert() {
|
||||
return !!this.display.baseTableOrCollection;
|
||||
return !!this.display.baseTable;
|
||||
}
|
||||
|
||||
getRowData(index: number) {
|
||||
@@ -155,12 +141,6 @@ export default class ChangeSetGrider extends Grider {
|
||||
this.applyModification(chs => setChangeSetValue(chs, definition, value));
|
||||
}
|
||||
|
||||
setRowData(index: number, document: any) {
|
||||
const row = this.getRowSource(index);
|
||||
const definition = this.display.getChangeSetRow(row, this.getInsertedRowIndex(index));
|
||||
this.applyModification(chs => setChangeSetRowData(chs, definition, document));
|
||||
}
|
||||
|
||||
deleteRow(index: number) {
|
||||
this.requireRowCache(index);
|
||||
this.applyModification(chs => deleteChangeSetRows(chs, this.rowDefinitionsCache[index]));
|
||||
@@ -177,7 +157,7 @@ export default class ChangeSetGrider extends Grider {
|
||||
|
||||
insertRow(): number {
|
||||
const res = this.rowCountInUpdate;
|
||||
this.applyModification(chs => changeSetInsertNewRow(chs, this.display.baseTableOrCollection));
|
||||
this.applyModification(chs => changeSetInsertNewRow(chs, this.display.baseTable));
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<script lang="ts">
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
|
||||
export let collapsed;
|
||||
</script>
|
||||
|
||||
<div on:click|stopPropagation class='collapseButtonMarker'>
|
||||
<FontIcon icon={collapsed ? 'icon triple-right' : 'icon triple-left'} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 4px;
|
||||
color: var(--theme-font-3);
|
||||
background-color: var(--theme-bg-1);
|
||||
border: 1px solid var(--theme-bg-1);
|
||||
}
|
||||
|
||||
div:hover {
|
||||
color: var(--theme-font-hover);
|
||||
border: var(--theme-border);
|
||||
top: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,24 +1,5 @@
|
||||
<script context="module" lang="ts">
|
||||
const getCurrentEditor = () => getActiveComponent('CollectionDataGridCore');
|
||||
|
||||
registerCommand({
|
||||
id: 'collectionDataGrid.openQuery',
|
||||
category: 'Data grid',
|
||||
name: 'Open query',
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => getCurrentEditor().openQuery(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'collectionDataGrid.export',
|
||||
category: 'Data grid',
|
||||
name: 'Export',
|
||||
keyText: 'Ctrl+E',
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => getCurrentEditor().exportGrid(),
|
||||
});
|
||||
|
||||
function buildGridMongoCondition(props) {
|
||||
function buildCondition(props) {
|
||||
const filters = props?.display?.config?.filters;
|
||||
|
||||
const conditions = [];
|
||||
@@ -46,20 +27,7 @@
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function buildMongoSort(props) {
|
||||
const sort = props?.display?.config?.sort;
|
||||
|
||||
if (sort?.length > 0) {
|
||||
return _.zipObject(
|
||||
sort.map(col => col.uniqueName),
|
||||
sort.map(col => (col.order == 'DESC' ? -1 : 1))
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function loadCollectionDataPage(props, offset, limit) {
|
||||
async function loadDataPage(props, offset, limit) {
|
||||
const { conid, database } = props;
|
||||
|
||||
const response = await axiosInstance.request({
|
||||
@@ -74,8 +42,7 @@
|
||||
pureName: props.pureName,
|
||||
limit,
|
||||
skip: offset,
|
||||
condition: buildGridMongoCondition(props),
|
||||
sort: buildMongoSort(props),
|
||||
condition: buildCondition(props),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -105,7 +72,7 @@
|
||||
options: {
|
||||
pureName: props.pureName,
|
||||
countDocuments: true,
|
||||
condition: buildGridMongoCondition(props),
|
||||
condition: buildCondition(props),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -119,16 +86,13 @@
|
||||
import { parseFilter } from 'dbgate-filterparser';
|
||||
import { scriptToSql } from 'dbgate-sqltree';
|
||||
import _ from 'lodash';
|
||||
import registerCommand from '../commands/registerCommand';
|
||||
import ErrorInfo from '../elements/ErrorInfo.svelte';
|
||||
import ConfirmNoSqlModal from '../modals/ConfirmNoSqlModal.svelte';
|
||||
import ConfirmSqlModal from '../modals/ConfirmSqlModal.svelte';
|
||||
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
|
||||
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
|
||||
import axiosInstance from '../utility/axiosInstance';
|
||||
import { registerMenu } from '../utility/contextMenu';
|
||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import ChangeSetGrider from './ChangeSetGrider';
|
||||
|
||||
@@ -151,8 +115,6 @@
|
||||
|
||||
export let loadedRows = [];
|
||||
|
||||
export const activator = createActivator('CollectionDataGridCore', false);
|
||||
|
||||
// $: console.log('loadedRows BIND', loadedRows);
|
||||
$: grider = new ChangeSetGrider(
|
||||
loadedRows,
|
||||
@@ -166,51 +128,97 @@
|
||||
// $: console.log('GRIDER', grider);
|
||||
// $: if (onChangeGrider) onChangeGrider(grider);
|
||||
|
||||
function getExportQuery() {
|
||||
return `db.collection('${pureName}')
|
||||
.find(${JSON.stringify(buildGridMongoCondition($$props) || {})})
|
||||
.sort(${JSON.stringify(buildMongoSort($$props) || {})})`;
|
||||
async function handleConfirmSql(sql) {
|
||||
const resp = await axiosInstance.request({
|
||||
url: 'database-connections/query-data',
|
||||
method: 'post',
|
||||
params: {
|
||||
conid,
|
||||
database,
|
||||
},
|
||||
data: { sql },
|
||||
});
|
||||
const { errorMessage } = resp.data || {};
|
||||
if (errorMessage) {
|
||||
showModal(ErrorMessageModal, { title: 'Error when saving', message: errorMessage });
|
||||
} else {
|
||||
dispatchChangeSet({ type: 'reset', value: createChangeSet() });
|
||||
display.reload();
|
||||
}
|
||||
}
|
||||
|
||||
export function exportGrid() {
|
||||
function handleSave() {
|
||||
const script = changeSetToSql(changeSetState && changeSetState.value, display.dbinfo);
|
||||
const sql = scriptToSql(display.driver, script);
|
||||
showModal(ConfirmSqlModal, {
|
||||
sql,
|
||||
onConfirm: () => handleConfirmSql(sql),
|
||||
engine: display.engine,
|
||||
});
|
||||
}
|
||||
|
||||
function exportGrid() {
|
||||
const initialValues: any = {};
|
||||
initialValues.sourceStorageType = 'query';
|
||||
initialValues.sourceConnectionId = conid;
|
||||
initialValues.sourceDatabaseName = database;
|
||||
initialValues.sourceSql = getExportQuery();
|
||||
initialValues.sourceList = [pureName];
|
||||
initialValues.sourceSql = display.getExportQuery();
|
||||
initialValues.sourceList = display.baseTable ? [display.baseTable.pureName] : [];
|
||||
showModal(ImportExportModal, { initialValues });
|
||||
}
|
||||
|
||||
export function openQuery() {
|
||||
function openQuery() {
|
||||
openNewTab(
|
||||
{
|
||||
title: 'Query #',
|
||||
icon: 'img sql-file',
|
||||
tabComponent: 'QueryTab',
|
||||
props: {
|
||||
schemaName: display.baseTable.schemaName,
|
||||
pureName: display.baseTable.pureName,
|
||||
conid,
|
||||
database,
|
||||
},
|
||||
},
|
||||
{
|
||||
editor: getExportQuery(),
|
||||
editor: display.getExportQuery(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
registerMenu(
|
||||
{ command: 'collectionDataGrid.openQuery', tag: 'export' },
|
||||
{ command: 'collectionDataGrid.export', tag: 'export' }
|
||||
);
|
||||
function openActiveChart() {
|
||||
openNewTab(
|
||||
{
|
||||
title: 'Chart #',
|
||||
icon: 'img chart',
|
||||
tabComponent: 'ChartTab',
|
||||
props: {
|
||||
conid,
|
||||
database,
|
||||
},
|
||||
},
|
||||
{
|
||||
editor: {
|
||||
config: { chartType: 'bar' },
|
||||
sql: display.getExportQuery(select => {
|
||||
select.orderBy = null;
|
||||
}),
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<LoadingDataGridCore
|
||||
{...$$props}
|
||||
loadDataPage={loadCollectionDataPage}
|
||||
{loadDataPage}
|
||||
{dataPageAvailable}
|
||||
{loadRowCount}
|
||||
onExportGrid={exportGrid}
|
||||
onOpenQuery={openQuery}
|
||||
onOpenActiveChart={openActiveChart}
|
||||
bind:loadedRows
|
||||
frameSelection={!!macroPreview}
|
||||
{grider}
|
||||
onSave={handleSave}
|
||||
/>
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
export let managerSize;
|
||||
export let display: GridDisplay;
|
||||
export let isJsonView = false;
|
||||
|
||||
let filter;
|
||||
</script>
|
||||
@@ -24,6 +23,6 @@
|
||||
{#each display
|
||||
?.getColumns(filter)
|
||||
?.filter(column => filterName(filter, column.columnName)) || [] as column (column.uniqueName)}
|
||||
<ColumnManagerRow {display} {column} {isJsonView} />
|
||||
<ColumnManagerRow {display} {column} />
|
||||
{/each}
|
||||
</ManagerInnerContainer>
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
export let column;
|
||||
export let display;
|
||||
export let isJsonView = false;
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -14,25 +13,21 @@
|
||||
on:click={e => {
|
||||
// @ts-ignore
|
||||
if (e.target.closest('.expandColumnIcon')) return;
|
||||
if (isJsonView) display.showFilter(column.uniqueName);
|
||||
else display.focusColumn(column.uniqueName);
|
||||
display.focusColumn(column.uniqueName);
|
||||
}}
|
||||
>
|
||||
<span class="expandColumnIcon" style={`margin-right: ${5 + (column.uniquePath.length - 1) * 10}px`}>
|
||||
<span class="expandColumnIcon">
|
||||
<FontIcon
|
||||
icon={column.isExpandable ? plusExpandIcon(display.isExpandedColumn(column.uniqueName)) : 'icon invisible-box'}
|
||||
on:click={() => display.toggleExpandedColumn(column.uniqueName)}
|
||||
/>
|
||||
</span>
|
||||
{#if isJsonView}
|
||||
<FontIcon icon="img column" />
|
||||
{:else}
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={column.isChecked}
|
||||
on:change={() => display.setColumnVisibility(column.uniquePath, !column.isChecked)}
|
||||
/>
|
||||
{/if}
|
||||
<input
|
||||
type="checkbox"
|
||||
style={`margin-left: ${5 + (column.uniquePath.length - 1) * 10}px`}
|
||||
checked={column.isChecked}
|
||||
on:change={() => display.setColumnVisibility(column.uniquePath, !column.isChecked)}
|
||||
/>
|
||||
<ColumnLabel {...column} />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -226,7 +226,6 @@
|
||||
on:paste={handlePaste}
|
||||
class:isError
|
||||
class:isOk
|
||||
placeholder='Filter'
|
||||
/>
|
||||
<DropDownButton icon="icon filter" menu={createMenu} />
|
||||
{#if showResizeSplitter}
|
||||
|
||||
@@ -1,42 +1,4 @@
|
||||
<script lang="ts" context="module">
|
||||
const getCurrentEditor = () => getActiveComponent('DataGrid');
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.switchToForm',
|
||||
category: 'Data grid',
|
||||
name: 'Switch to form',
|
||||
keyText: 'F4',
|
||||
testEnabled: () => getCurrentEditor()?.switchViewEnabled('form'),
|
||||
onClick: () => getCurrentEditor().switchToView('form'),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.switchToJson',
|
||||
category: 'Data grid',
|
||||
name: 'Switch to JSON',
|
||||
keyText: 'F4',
|
||||
testEnabled: () => getCurrentEditor()?.switchViewEnabled('json'),
|
||||
onClick: () => getCurrentEditor().switchToView('json'),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.switchToTable',
|
||||
category: 'Data grid',
|
||||
name: 'Switch to table',
|
||||
keyText: 'F4',
|
||||
testEnabled: () => getCurrentEditor()?.switchViewEnabled('table'),
|
||||
onClick: () => getCurrentEditor().switchToView('table'),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.toggleLeftPanel',
|
||||
category: 'Data grid',
|
||||
name: 'Toggle left panel',
|
||||
keyText: 'Ctrl+L',
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => getCurrentEditor().toggleLeftPanel(),
|
||||
});
|
||||
|
||||
function extractMacroValuesForMacro(macroValues, macro) {
|
||||
// return {};
|
||||
if (!macro) return {};
|
||||
@@ -50,31 +12,23 @@
|
||||
<script lang="ts">
|
||||
import { setContext } from 'svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import { runMacroOnChangeSet } from 'dbgate-datalib';
|
||||
|
||||
import HorizontalSplitter from '../elements/HorizontalSplitter.svelte';
|
||||
import VerticalSplitter from '../elements/VerticalSplitter.svelte';
|
||||
import FormViewFilters from '../formview/FormViewFilters.svelte';
|
||||
import MacroDetail from '../freetable/MacroDetail.svelte';
|
||||
import MacroManager from '../freetable/MacroManager.svelte';
|
||||
import createRef from '../utility/createRef';
|
||||
import WidgetColumnBar from '../widgets/WidgetColumnBar.svelte';
|
||||
import WidgetColumnBarItem from '../widgets/WidgetColumnBarItem.svelte';
|
||||
import ColumnManager from './ColumnManager.svelte';
|
||||
import ReferenceManager from './ReferenceManager.svelte';
|
||||
import FreeTableColumnEditor from '../freetable/FreeTableColumnEditor.svelte';
|
||||
import JsonViewFilters from '../jsonview/JsonViewFilters.svelte';
|
||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||
import _ from 'lodash';
|
||||
import registerCommand from '../commands/registerCommand';
|
||||
import { registerMenu } from '../utility/contextMenu';
|
||||
import { useSettings } from '../utility/metadataLoaders';
|
||||
import { getCurrentSettings } from '../stores';
|
||||
import { getBoolSettingsValue } from '../settings/settingsTools';
|
||||
|
||||
export let config;
|
||||
export let setConfig;
|
||||
export let gridCoreComponent;
|
||||
export let formViewComponent = null;
|
||||
export let jsonViewComponent = null;
|
||||
export let formViewComponent;
|
||||
export let formDisplay;
|
||||
export let display;
|
||||
export let changeSetState;
|
||||
@@ -90,8 +44,6 @@
|
||||
|
||||
export let loadedRows;
|
||||
|
||||
export const activator = createActivator('DataGrid', false);
|
||||
|
||||
let selectedCellsPublished = () => [];
|
||||
|
||||
const selectedMacro = writable(null);
|
||||
@@ -100,60 +52,37 @@
|
||||
setContext('macroValues', macroValues);
|
||||
|
||||
let managerSize;
|
||||
const collapsedLeftColumnStore = writable(getBoolSettingsValue('dataGrid.hideLeftColumn', false));
|
||||
|
||||
$: isFormView = !!(formDisplay && formDisplay.config && formDisplay.config.isFormView);
|
||||
$: isJsonView = !!config?.isJsonView;
|
||||
|
||||
const handleExecuteMacro = () => {
|
||||
onRunMacro($selectedMacro, extractMacroValuesForMacro($macroValues, $selectedMacro), selectedCellsPublished());
|
||||
$selectedMacro = null;
|
||||
|
||||
// const newChangeSet = runMacroOnChangeSet(
|
||||
// $selectedMacro,
|
||||
// extractMacroValuesForMacro($macroValues, $selectedMacro),
|
||||
// selectedCellsPublished,
|
||||
// changeSetState?.value,
|
||||
// display
|
||||
// );
|
||||
// if (newChangeSet) {
|
||||
// dispatchChangeSet({ type: 'set', value: newChangeSet });
|
||||
// }
|
||||
// $selectedMacro = null;
|
||||
};
|
||||
|
||||
export function switchViewEnabled(view) {
|
||||
if (view == 'form') return !!formViewComponent && !!formDisplay && !isFormView && display?.baseTable?.primaryKey;
|
||||
if (view == 'table') return !!(isFormView || isJsonView);
|
||||
if (view == 'json') return !!jsonViewComponent && !isJsonView;
|
||||
}
|
||||
|
||||
export function switchToView(view) {
|
||||
if (view == 'form') {
|
||||
display.switchToFormView(selectedCellsPublished()[0]?.rowData);
|
||||
}
|
||||
if (view == 'table') {
|
||||
setConfig(cfg => ({
|
||||
...cfg,
|
||||
isFormView: false,
|
||||
isJsonView: false,
|
||||
formViewKey: null,
|
||||
}));
|
||||
}
|
||||
if (view == 'json') {
|
||||
display.switchToJsonView();
|
||||
}
|
||||
}
|
||||
|
||||
export function toggleLeftPanel() {
|
||||
collapsedLeftColumnStore.update(x => !x);
|
||||
}
|
||||
|
||||
registerMenu(
|
||||
{ command: 'dataGrid.switchToForm', tag: 'switch', hideDisabled: true },
|
||||
{ command: 'dataGrid.switchToTable', tag: 'switch', hideDisabled: true },
|
||||
{ command: 'dataGrid.switchToJson', tag: 'switch', hideDisabled: true },
|
||||
{ command: 'dataGrid.toggleLeftPanel', tag: 'switch' }
|
||||
);
|
||||
</script>
|
||||
|
||||
<HorizontalSplitter initialValue="300px" bind:size={managerSize} hideFirst={$collapsedLeftColumnStore}>
|
||||
<HorizontalSplitter initialValue="300px" bind:size={managerSize}>
|
||||
<div class="left" slot="1">
|
||||
<WidgetColumnBar>
|
||||
<WidgetColumnBarItem title="Columns" name="columns" height="45%" skip={freeTableColumn || isFormView}>
|
||||
<ColumnManager {...$$props} {managerSize} {isJsonView} />
|
||||
</WidgetColumnBarItem>
|
||||
|
||||
<WidgetColumnBarItem title="Filters" name="jsonFilters" height="30%" skip={!isDynamicStructure}>
|
||||
<JsonViewFilters {...$$props} {managerSize} />
|
||||
<WidgetColumnBarItem
|
||||
title="Columns"
|
||||
name="columns"
|
||||
height={showReferences ? '40%' : '60%'}
|
||||
skip={freeTableColumn || isFormView}
|
||||
>
|
||||
<ColumnManager {...$$props} {managerSize} />
|
||||
</WidgetColumnBarItem>
|
||||
|
||||
<WidgetColumnBarItem title="Columns" name="freeColumns" height="40%" skip={!freeTableColumn}>
|
||||
@@ -174,7 +103,7 @@
|
||||
<ReferenceManager {...$$props} {managerSize} />
|
||||
</WidgetColumnBarItem>
|
||||
|
||||
<WidgetColumnBarItem title="Macros" name="macros" skip={!showMacros} collapsed>
|
||||
<WidgetColumnBarItem title="Macros" name="macros" skip={!showMacros} collapsed={isDetailView}>
|
||||
<MacroManager {...$$props} {managerSize} />
|
||||
</WidgetColumnBarItem>
|
||||
</WidgetColumnBar>
|
||||
@@ -184,13 +113,10 @@
|
||||
<svelte:fragment slot="1">
|
||||
{#if isFormView}
|
||||
<svelte:component this={formViewComponent} {...$$props} />
|
||||
{:else if isJsonView}
|
||||
<svelte:component this={jsonViewComponent} {...$$props} bind:loadedRows />
|
||||
{:else}
|
||||
<svelte:component
|
||||
this={gridCoreComponent}
|
||||
{...$$props}
|
||||
{collapsedLeftColumnStore}
|
||||
formViewAvailable={!!formViewComponent && !!formDisplay}
|
||||
macroValues={extractMacroValuesForMacro($macroValues, $selectedMacro)}
|
||||
macroPreview={$selectedMacro}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
export let col;
|
||||
export let rowData;
|
||||
export let colIndex = undefined;
|
||||
export let allowHintField = false;
|
||||
export let hintFieldsAllowed = undefined;
|
||||
|
||||
export let isSelected = false;
|
||||
export let isFrameSelected = false;
|
||||
@@ -39,7 +39,6 @@
|
||||
export let hideContent = false;
|
||||
export let onSetFormView;
|
||||
export let isDynamicStructure = false;
|
||||
export let isAutoFillMarker = false;
|
||||
|
||||
$: value = col.isStructured ? _.get(rowData || {}, col.uniquePath) : (rowData || {})[col.uniqueName];
|
||||
</script>
|
||||
@@ -100,14 +99,14 @@
|
||||
{:else if value.type == 'Buffer' && _.isArray(value.data)}
|
||||
<span class="null">({value.data.length} bytes)</span>
|
||||
{:else if _.isPlainObject(value)}
|
||||
<span class="null" title={JSON.stringify(value, undefined, 2)}>(JSON)</span>
|
||||
<span class="null">(JSON)</span>
|
||||
{:else if _.isArray(value)}
|
||||
<span class="null">[{value.length} items]</span>
|
||||
{:else}
|
||||
{value.toString()}
|
||||
{/if}
|
||||
|
||||
{#if allowHintField && rowData && rowData[col.hintColumnName]}
|
||||
{#if hintFieldsAllowed && hintFieldsAllowed.includes(col.uniqueName) && rowData && rowData[col.hintColumnName]}
|
||||
<span class="hint">{rowData[col.hintColumnName]}</span>
|
||||
{/if}
|
||||
|
||||
@@ -115,10 +114,6 @@
|
||||
<ShowFormButton on:click={() => onSetFormView(rowData, col)} />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if isAutoFillMarker}
|
||||
<div class="autoFillMarker autofillHandleMarker" />
|
||||
{/if}
|
||||
</td>
|
||||
|
||||
<!-- {#if _.isArray(value.data)}
|
||||
@@ -180,15 +175,4 @@
|
||||
.value {
|
||||
color: var(--theme-icon-green);
|
||||
}
|
||||
|
||||
.autoFillMarker {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--theme-bg-selected-point);
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
overflow: visible;
|
||||
cursor: crosshair;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<script lang="ts" context="module">
|
||||
const getCurrentDataGrid = () => getActiveComponent('DataGridCore');
|
||||
let lastFocusedDataGrid = null;
|
||||
const getCurrentDataGrid = () =>
|
||||
lastFocusedDataGrid?.getTabId && lastFocusedDataGrid?.getTabId() == getActiveTabId() ? lastFocusedDataGrid : null;
|
||||
export function clearLastFocusedDataGrid() {
|
||||
lastFocusedDataGrid = null;
|
||||
}
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.refresh',
|
||||
@@ -7,12 +12,23 @@
|
||||
name: 'Refresh',
|
||||
keyText: 'F5',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
icon: 'icon reload',
|
||||
testEnabled: () => getCurrentDataGrid()?.getDisplay()?.supportsReload,
|
||||
onClick: () => getCurrentDataGrid().refresh(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.save',
|
||||
group: 'save',
|
||||
category: 'Data grid',
|
||||
name: 'Save',
|
||||
// keyText: 'Ctrl+S',
|
||||
toolbar: true,
|
||||
icon: 'icon save',
|
||||
testEnabled: () => getCurrentDataGrid()?.getGrider()?.allowSave,
|
||||
onClick: () => getCurrentDataGrid().save(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.revertRowChanges',
|
||||
category: 'Data grid',
|
||||
@@ -64,7 +80,6 @@
|
||||
group: 'undo',
|
||||
icon: 'icon undo',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
testEnabled: () => getCurrentDataGrid()?.getGrider()?.canUndo,
|
||||
onClick: () => getCurrentDataGrid().undo(),
|
||||
});
|
||||
@@ -76,7 +91,6 @@
|
||||
group: 'redo',
|
||||
icon: 'icon redo',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
testEnabled: () => getCurrentDataGrid()?.getGrider()?.canRedo,
|
||||
onClick: () => getCurrentDataGrid().redo(),
|
||||
});
|
||||
@@ -100,11 +114,21 @@
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.editJsonDocument',
|
||||
id: 'dataGrid.export',
|
||||
category: 'Data grid',
|
||||
name: 'Edit row as JSON document',
|
||||
testEnabled: () => getCurrentDataGrid()?.editJsonEnabled(),
|
||||
onClick: () => getCurrentDataGrid().editJsonDocument(),
|
||||
name: 'Export',
|
||||
keyText: 'Ctrl+E',
|
||||
testEnabled: () => getCurrentDataGrid()?.exportEnabled(),
|
||||
onClick: () => getCurrentDataGrid().exportGrid(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.switchToForm',
|
||||
category: 'Data grid',
|
||||
name: 'Switch to form',
|
||||
keyText: 'F4',
|
||||
testEnabled: () => getCurrentDataGrid()?.formViewEnabled(),
|
||||
onClick: () => getCurrentDataGrid().switchToForm(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
@@ -125,6 +149,14 @@
|
||||
onClick: () => getCurrentDataGrid().clearFilter(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.openQuery',
|
||||
category: 'Data grid',
|
||||
name: 'Open query',
|
||||
testEnabled: () => getCurrentDataGrid()?.openQueryEnabled(),
|
||||
onClick: () => getCurrentDataGrid().openQuery(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.openFreeTable',
|
||||
category: 'Data grid',
|
||||
@@ -141,6 +173,14 @@
|
||||
onClick: () => getCurrentDataGrid().openChartFromSelection(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataGrid.openActiveChart',
|
||||
category: 'Data grid',
|
||||
name: 'Open active chart',
|
||||
testEnabled: () => getCurrentDataGrid()?.openActiveChartEnabled(),
|
||||
onClick: () => getCurrentDataGrid().openActiveChart(),
|
||||
});
|
||||
|
||||
function getRowCountInfo(selectedCells, grider, realColumnUniqueNames, selectedRowData, allRowCount) {
|
||||
if (selectedCells.length > 1 && selectedCells.every(x => _.isNumber(x[0]) && _.isNumber(x[1]))) {
|
||||
let sum = _.sumBy(selectedCells, cell => {
|
||||
@@ -164,25 +204,20 @@
|
||||
if (allRowCount == null) return 'Loading row count...';
|
||||
return `Rows: ${allRowCount.toLocaleString()}`;
|
||||
}
|
||||
|
||||
function getCopiedValue(value) {
|
||||
if (value === null) return '(NULL)';
|
||||
if (value === undefined) return '(NoField)';
|
||||
if (_.isPlainObject(value) || _.isArray(value)) return JSON.stringify(value);
|
||||
return value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { GridDisplay } from 'dbgate-datalib';
|
||||
import { changeSetContainsChanges, GridDisplay } from 'dbgate-datalib';
|
||||
import { get_current_component } from 'svelte/internal';
|
||||
import { getContext } from 'svelte';
|
||||
import _ from 'lodash';
|
||||
import { writable, get, derived } from 'svelte/store';
|
||||
import registerCommand from '../commands/registerCommand';
|
||||
import ColumnHeaderControl from './ColumnHeaderControl.svelte';
|
||||
import DataGridRow from './DataGridRow.svelte';
|
||||
import { getFilterType, getFilterValueExpression } from 'dbgate-filterparser';
|
||||
import stableStringify from 'json-stable-stringify';
|
||||
import contextMenu, { getContextMenu, registerMenu } from '../utility/contextMenu';
|
||||
import contextMenu from '../utility/contextMenu';
|
||||
import { tick } from 'svelte';
|
||||
import {
|
||||
cellIsSelected,
|
||||
@@ -200,19 +235,18 @@
|
||||
import DataFilterControl from './DataFilterControl.svelte';
|
||||
import createReducer from '../utility/createReducer';
|
||||
import keycodes from '../utility/keycodes';
|
||||
import { selectedCellsCallback } from '../stores';
|
||||
import { activeTabId, getActiveTabId, nullStore, selectedCellsCallback } from '../stores';
|
||||
import memberStore from '../utility/memberStore';
|
||||
import axiosInstance from '../utility/axiosInstance';
|
||||
import { copyTextToClipboard } from '../utility/clipboard';
|
||||
import invalidateCommands from '../commands/invalidateCommands';
|
||||
import createRef from '../utility/createRef';
|
||||
import { clearLastFocusedFormView } from '../formview/FormView.svelte';
|
||||
import openReferenceForm, { openPrimaryKeyForm } from '../formview/openReferenceForm';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import ErrorInfo from '../elements/ErrorInfo.svelte';
|
||||
import { dataGridRowHeight } from './DataGridRowHeightMeter.svelte';
|
||||
import FormStyledButton from '../elements/FormStyledButton.svelte';
|
||||
import { editJsonRowDocument } from '../jsonview/CollectionJsonRow.svelte';
|
||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||
import CollapseButton from './CollapseButton.svelte';
|
||||
|
||||
export let onLoadNextData = undefined;
|
||||
export let grider = undefined;
|
||||
@@ -225,7 +259,11 @@
|
||||
export let onReferenceSourceChanged = undefined;
|
||||
export let onReferenceClick = undefined;
|
||||
// export let onSelectedCellsPublishedChanged = undefined;
|
||||
export let onSave;
|
||||
export let focusOnVisible = false;
|
||||
export let onExportGrid = null;
|
||||
export let onOpenQuery = null;
|
||||
export let onOpenActiveChart = null;
|
||||
export let formViewAvailable = false;
|
||||
export let errorMessage = undefined;
|
||||
|
||||
@@ -234,12 +272,11 @@
|
||||
export let changeSetStore;
|
||||
export let isDynamicStructure = false;
|
||||
export let selectedCellsPublished = () => [];
|
||||
export let collapsedLeftColumnStore;
|
||||
// export let generalAllowSave = false;
|
||||
|
||||
export const activator = createActivator('DataGridCore', false);
|
||||
|
||||
const wheelRowCount = 5;
|
||||
const instance = get_current_component();
|
||||
const tabid = getContext('tabid');
|
||||
const tabVisible: any = getContext('tabVisible');
|
||||
|
||||
let containerHeight = 0;
|
||||
@@ -264,6 +301,14 @@
|
||||
display.reload();
|
||||
}
|
||||
|
||||
export function getTabId() {
|
||||
return tabid;
|
||||
}
|
||||
|
||||
export function save() {
|
||||
if (onSave) onSave();
|
||||
}
|
||||
|
||||
export function getGrider() {
|
||||
return grider;
|
||||
}
|
||||
@@ -276,6 +321,14 @@
|
||||
return display;
|
||||
}
|
||||
|
||||
export function exportGrid() {
|
||||
if (onExportGrid) onExportGrid();
|
||||
}
|
||||
|
||||
export function exportEnabled() {
|
||||
return !!onExportGrid;
|
||||
}
|
||||
|
||||
export function revertRowChanges() {
|
||||
grider.beginUpdate();
|
||||
for (const index of getSelectedRowIndexes()) {
|
||||
@@ -296,7 +349,7 @@
|
||||
grider.endUpdate();
|
||||
}
|
||||
|
||||
export async function insertNewRow() {
|
||||
export function insertNewRow() {
|
||||
if (grider.canInsert) {
|
||||
const rowIndex = grider.insertRow();
|
||||
const cell = [rowIndex, (currentCell && currentCell[1]) || 0];
|
||||
@@ -304,7 +357,6 @@
|
||||
currentCell = cell;
|
||||
// @ts-ignore
|
||||
selectedCells = [cell];
|
||||
await tick();
|
||||
scrollIntoView(cell);
|
||||
}
|
||||
}
|
||||
@@ -339,7 +391,7 @@
|
||||
if (!rowData) return '';
|
||||
const line = colIndexes
|
||||
.map(col => realColumnUniqueNames[col])
|
||||
.map(col => getCopiedValue(rowData[col]))
|
||||
.map(col => (rowData[col] == null ? '(NULL)' : rowData[col]))
|
||||
.join('\t');
|
||||
return line;
|
||||
});
|
||||
@@ -353,6 +405,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
export function formViewEnabled() {
|
||||
return formViewAvailable && display.baseTable && display.baseTable.primaryKey;
|
||||
}
|
||||
|
||||
export function switchToForm() {
|
||||
const cell = currentCell;
|
||||
const rowData = isRegularCell(cell) ? grider.getRowData(cell[0]) : null;
|
||||
display.switchToFormView(rowData);
|
||||
}
|
||||
|
||||
export function filterSelectedValue() {
|
||||
const flts = {};
|
||||
for (const cell of selectedCells) {
|
||||
@@ -376,6 +438,22 @@
|
||||
return display.filterCount > 0;
|
||||
}
|
||||
|
||||
export function openQuery() {
|
||||
if (onOpenQuery) onOpenQuery();
|
||||
}
|
||||
|
||||
export function openQueryEnabled() {
|
||||
return onOpenQuery != null;
|
||||
}
|
||||
|
||||
export function openActiveChart() {
|
||||
if (onOpenActiveChart) onOpenActiveChart();
|
||||
}
|
||||
|
||||
export function openActiveChartEnabled() {
|
||||
return onOpenActiveChart != null;
|
||||
}
|
||||
|
||||
export function openFreeTable() {
|
||||
openNewTab(
|
||||
{
|
||||
@@ -405,14 +483,9 @@
|
||||
);
|
||||
}
|
||||
|
||||
export function editJsonEnabled() {
|
||||
return grider.editable && isDynamicStructure && _.uniq(selectedCells.map(x => x[0])).length == 1;
|
||||
}
|
||||
|
||||
export function editJsonDocument() {
|
||||
const rowIndex = selectedCells[0][0];
|
||||
editJsonRowDocument(grider, rowIndex);
|
||||
}
|
||||
// export function getGeneralAllowSave() {
|
||||
// return generalAllowSave;
|
||||
// }
|
||||
|
||||
$: autofillMarkerCell =
|
||||
selectedCells && selectedCells.length > 0 && _.uniq(selectedCells.map(x => x[0])).length == 1
|
||||
@@ -533,7 +606,6 @@
|
||||
rowData,
|
||||
column,
|
||||
value: rowData && rowData[column],
|
||||
engine: display?.driver,
|
||||
};
|
||||
})
|
||||
.filter(x => x.column);
|
||||
@@ -578,7 +650,6 @@
|
||||
function handleGridMouseDown(event) {
|
||||
if (event.target.closest('.buttonLike')) return;
|
||||
if (event.target.closest('.resizeHandleControl')) return;
|
||||
if (event.target.closest('.collapseButtonMarker')) return;
|
||||
if (event.target.closest('input')) return;
|
||||
|
||||
// event.target.closest('table').focus();
|
||||
@@ -915,12 +986,12 @@
|
||||
// if (action.mode == 'save') setTimeout(handleSave, 0);
|
||||
return {};
|
||||
}
|
||||
// case 'shouldSave': {
|
||||
// return {
|
||||
// ...state,
|
||||
// shouldSave: true,
|
||||
// };
|
||||
// }
|
||||
case 'shouldSave': {
|
||||
return {
|
||||
...state,
|
||||
shouldSave: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}, {});
|
||||
@@ -932,32 +1003,31 @@
|
||||
return ['filter', columnRealIndex];
|
||||
}
|
||||
|
||||
registerMenu(
|
||||
{ command: 'dataGrid.refresh' },
|
||||
{ command: 'dataGrid.copyToClipboard' },
|
||||
{ placeTag: 'switch' },
|
||||
{ divider: true },
|
||||
{ placeTag: 'save' },
|
||||
{ command: 'dataGrid.revertRowChanges' },
|
||||
{ command: 'dataGrid.revertAllChanges' },
|
||||
{ command: 'dataGrid.deleteSelectedRows' },
|
||||
{ command: 'dataGrid.insertNewRow' },
|
||||
{ command: 'dataGrid.setNull' },
|
||||
{ placeTag: 'edit' },
|
||||
{ divider: true },
|
||||
{ command: 'dataGrid.filterSelected' },
|
||||
{ command: 'dataGrid.clearFilter' },
|
||||
{ command: 'dataGrid.undo' },
|
||||
{ command: 'dataGrid.redo' },
|
||||
{ command: 'dataGrid.editJsonDocument' },
|
||||
{ divider: true },
|
||||
{ placeTag: 'export' },
|
||||
{ command: 'dataGrid.openFreeTable' },
|
||||
{ command: 'dataGrid.openChartFromSelection' },
|
||||
{ placeTag: 'chart' }
|
||||
);
|
||||
|
||||
const menu = getContextMenu();
|
||||
function createMenu() {
|
||||
return [
|
||||
{ command: 'dataGrid.refresh' },
|
||||
{ command: 'dataGrid.copyToClipboard' },
|
||||
{ command: 'dataGrid.export' },
|
||||
{ command: 'dataGrid.switchToForm' },
|
||||
{ divider: true },
|
||||
{ command: 'dataGrid.save' },
|
||||
{ command: 'dataGrid.revertRowChanges' },
|
||||
{ command: 'dataGrid.revertAllChanges' },
|
||||
{ command: 'dataGrid.deleteSelectedRows' },
|
||||
{ command: 'dataGrid.insertNewRow' },
|
||||
{ command: 'dataGrid.setNull' },
|
||||
{ divider: true },
|
||||
{ command: 'dataGrid.filterSelected' },
|
||||
{ command: 'dataGrid.clearFilter' },
|
||||
{ command: 'dataGrid.undo' },
|
||||
{ command: 'dataGrid.redo' },
|
||||
{ divider: true },
|
||||
{ command: 'dataGrid.openQuery' },
|
||||
{ command: 'dataGrid.openFreeTable' },
|
||||
{ command: 'dataGrid.openChartFromSelection' },
|
||||
{ command: 'dataGrid.openActiveChart' },
|
||||
];
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if !display || (!isDynamicStructure && (!columns || columns.length == 0))}
|
||||
@@ -978,14 +1048,20 @@
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="container" bind:clientWidth={containerWidth} bind:clientHeight={containerHeight} use:contextMenu={menu}>
|
||||
<div
|
||||
class="container"
|
||||
bind:clientWidth={containerWidth}
|
||||
bind:clientHeight={containerHeight}
|
||||
use:contextMenu={createMenu}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="focus-field"
|
||||
bind:this={domFocusField}
|
||||
on:keydown={handleGridKeyDown}
|
||||
on:focus={() => {
|
||||
activator.activate();
|
||||
lastFocusedDataGrid = instance;
|
||||
clearLastFocusedFormView();
|
||||
invalidateCommands();
|
||||
}}
|
||||
on:paste={handlePaste}
|
||||
@@ -1005,12 +1081,7 @@
|
||||
data-row="header"
|
||||
data-col="header"
|
||||
style={`width:${headerColWidth}px; min-width:${headerColWidth}px; max-width:${headerColWidth}px`}
|
||||
>
|
||||
<CollapseButton
|
||||
collapsed={$collapsedLeftColumnStore}
|
||||
on:click={() => collapsedLeftColumnStore.update(x => !x)}
|
||||
/>
|
||||
</td>
|
||||
/>
|
||||
{#each visibleRealColumns as col (col.uniqueName)}
|
||||
<td
|
||||
class="header-cell"
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
{rowIndex}
|
||||
{rowData}
|
||||
{col}
|
||||
allowHintField={hintFieldsAllowed?.includes(col.uniqueName)}
|
||||
{hintFieldsAllowed}
|
||||
isSelected={frameSelection ? false : cellIsSelected(rowIndex, col.colIndex, selectedCells)}
|
||||
isFrameSelected={frameSelection ? cellIsSelected(rowIndex, col.colIndex, selectedCells) : false}
|
||||
isAutofillSelected={cellIsSelected(rowIndex, col.colIndex, autofillSelectedCells)}
|
||||
@@ -64,10 +64,6 @@
|
||||
(rowStatus.deletedFields && rowStatus.deletedFields.has(col.uniqueName))}
|
||||
{onSetFormView}
|
||||
{isDynamicStructure}
|
||||
isAutoFillMarker={autofillMarkerCell &&
|
||||
autofillMarkerCell[1] == col.colIndex &&
|
||||
autofillMarkerCell[0] == rowIndex &&
|
||||
grider.editable}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
<script lang="ts" context="module">
|
||||
function getEditedValue(value) {
|
||||
if (_.isPlainObject(value) || _.isArray(value)) return JSON.stringify(value);
|
||||
return value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import keycodes from '../utility/keycodes';
|
||||
import { onMount } from 'svelte';
|
||||
import createRef from '../utility/createRef';
|
||||
import _ from 'lodash';
|
||||
|
||||
export let inplaceEditorState;
|
||||
export let dispatchInsplaceEditor;
|
||||
@@ -62,7 +54,7 @@
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
domEditor.value = inplaceEditorState.text || getEditedValue(cellValue);
|
||||
domEditor.value = inplaceEditorState.text || cellValue;
|
||||
domEditor.focus();
|
||||
if (inplaceEditorState.selectAll) {
|
||||
domEditor.select();
|
||||
|
||||
@@ -10,27 +10,20 @@
|
||||
|
||||
export let jslid;
|
||||
|
||||
let loadedRows;
|
||||
|
||||
$: info = useFetch({
|
||||
params: { jslid },
|
||||
url: 'jsldata/get-info',
|
||||
defaultValue: {},
|
||||
});
|
||||
|
||||
// $: columns = ($info && $info.columns) || [];
|
||||
$: columns = ($info && $info.columns) || [];
|
||||
const config = writable(createGridConfig());
|
||||
const cache = writable(createGridCache());
|
||||
|
||||
$: display = new JslGridDisplay(jslid, $info, $config, config.update, $cache, cache.update, loadedRows);
|
||||
$: display = new JslGridDisplay(jslid, columns, $config, config.update, $cache, cache.update);
|
||||
|
||||
</script>
|
||||
|
||||
{#key jslid}
|
||||
<DataGrid
|
||||
{display}
|
||||
{jslid}
|
||||
gridCoreComponent={JslDataGridCore}
|
||||
bind:loadedRows
|
||||
isDynamicStructure={$info?.__isDynamicStructure}
|
||||
/>
|
||||
<DataGrid {display} {jslid} gridCoreComponent={JslDataGridCore} />
|
||||
{/key}
|
||||
|
||||
@@ -1,15 +1,4 @@
|
||||
<script context="module" lang="ts">
|
||||
const getCurrentEditor = () => getActiveComponent('JslDataGridCore');
|
||||
|
||||
registerCommand({
|
||||
id: 'jslTableGrid.export',
|
||||
category: 'Data grid',
|
||||
name: 'Export',
|
||||
keyText: 'Ctrl+E',
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => getCurrentEditor().exportGrid(),
|
||||
});
|
||||
|
||||
async function loadDataPage(props, offset, limit) {
|
||||
const { jslid, display } = props;
|
||||
|
||||
@@ -43,13 +32,10 @@
|
||||
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import registerCommand from '../commands/registerCommand';
|
||||
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
|
||||
import axiosInstance from '../utility/axiosInstance';
|
||||
import { registerMenu } from '../utility/contextMenu';
|
||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||
import socket from '../utility/socket';
|
||||
import useEffect from '../utility/useEffect';
|
||||
|
||||
@@ -58,9 +44,7 @@
|
||||
|
||||
export let jslid;
|
||||
|
||||
export const activator = createActivator('JslDataGridCore', false);
|
||||
|
||||
export let loadedRows = [];
|
||||
let loadedRows = [];
|
||||
let domGrid;
|
||||
|
||||
let changeIndex = 0;
|
||||
@@ -88,7 +72,7 @@
|
||||
|
||||
$: grider = new RowsArrayGrider(loadedRows);
|
||||
|
||||
export function exportGrid() {
|
||||
function exportGrid() {
|
||||
const initialValues = {} as any;
|
||||
const archiveMatch = jslid.match(/^archive:\/\/([^/]+)\/(.*)$/);
|
||||
if (archiveMatch) {
|
||||
@@ -102,14 +86,13 @@
|
||||
}
|
||||
showModal(ImportExportModal, { initialValues });
|
||||
}
|
||||
|
||||
registerMenu({ command: 'jslTableGrid.export', tag: 'export' });
|
||||
</script>
|
||||
|
||||
<LoadingDataGridCore
|
||||
bind:this={domGrid}
|
||||
{...$$props}
|
||||
bind:loadedRows
|
||||
onExportGrid={exportGrid}
|
||||
{loadDataPage}
|
||||
{dataPageAvailable}
|
||||
{loadRowCount}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user