Compare commits
134 Commits
Author | SHA1 | Date | |
---|---|---|---|
88cd097ec0 | |||
f12e6a96dd | |||
|
6fcae957e1 | ||
|
1cd2d8abf3 | ||
ed3c5fe559 | |||
|
9601c59392 | ||
|
14d20a30c1 | ||
e9079adb25 | |||
8f3efabb69 | |||
db628f7722 | |||
e4bd747381 | |||
88eb113e53 | |||
9109b2c328 | |||
dd070d008d | |||
ee415da127 | |||
f0d368e3e3 | |||
4be55f3fe9 | |||
fd00ea42ee | |||
50b4411e9a | |||
|
cd0a5dc034 | ||
|
9fd427c4ff | ||
f3759b6541 | |||
191a354020 | |||
|
7dc314eecf | ||
|
330a80fe70 | ||
|
b2ce533b82 | ||
|
12ce6b1135 | ||
|
71c5829702 | ||
1c4d5b05b3 | |||
e85197a2cc | |||
|
abf2b92e6e | ||
|
3be826df4b | ||
9db6bfd255 | |||
|
110adf90de | ||
|
40e4fbda15 | ||
8e983ad2cb | |||
|
6305752ad1 | ||
cc02e2c5a8 | |||
|
f4a63eae2a | ||
763be8532d | |||
|
a6f5645a22 | ||
|
bbe13f27dc | ||
f444746f46 | |||
b4af645941 | |||
a5c8daa5b8 | |||
1a9fc37285 | |||
f0351e5b94 | |||
9bda4e71b7 | |||
7c00055034 | |||
4479a9600b | |||
|
7a6bd8bdbd | ||
ad713fcf35 | |||
251795e2d2 | |||
45cda7a7cc | |||
b7039553cc | |||
ddee68b4c2 | |||
573ac6d42e | |||
265f28b4d9 | |||
1990d9a3d4 | |||
748d44977e | |||
4051eff382 | |||
4276586e11 | |||
832fb0fb03 | |||
328ab61757 | |||
95d15de1bd | |||
7dcd4441c4 | |||
d81e0911ab | |||
5bfff649e9 | |||
76743e8f7c | |||
4ed2f9a939 | |||
c5eb73ed3f | |||
fa3f3e1fd8 | |||
2c7c97852f | |||
48ebf23bd1 | |||
64deedc5ad | |||
9f033fb994 | |||
401cb49687 | |||
1356011ba3 | |||
0cfd7938ee | |||
745b55a942 | |||
eef7c1dcec | |||
aa8fc545d7 | |||
a780c7e0ff | |||
|
9a1bb0599f | ||
|
d847870f67 | ||
cd24371576 | |||
|
6ef565cf07 | ||
46b45c8ab6 | |||
f28531a225 | |||
8fb1f0803e | |||
020ce36312 | |||
b4545b178f | |||
d9a3eab015 | |||
2ab49c218d | |||
|
8f9385d508 | ||
|
4e9f8d16ee | ||
0c002918eb | |||
3d56ec7b49 | |||
48c3e6afc4 | |||
|
50a5c8fe1e | ||
b0d1f115c9 | |||
a59f77f618 | |||
e7a1858091 | |||
648a51efe8 | |||
38962c4807 | |||
|
43e4cdd4cf | ||
|
91046c1ac1 | ||
|
63f8b9b6a1 | ||
|
ed476d9b5c | ||
|
f41d8c0480 | ||
e3b54a8be1 | |||
|
c2c0394624 | ||
8a6f5eac59 | |||
a5fdcc1a85 | |||
1df21da47c | |||
8da0224876 | |||
359e14a9eb | |||
813aa320d9 | |||
aaa5549609 | |||
35cb7e1dc4 | |||
992a033cb2 | |||
5267b37eaf | |||
8fe30d8b6d | |||
37ccb6b00d | |||
e8af2d24a8 | |||
d7f1aa97af | |||
c46224635a | |||
|
d25c62b4da | ||
|
8cf738bac8 | ||
409ed54608 | |||
d9d3bf2bc9 | |||
9e9de7b5c5 | |||
|
b2a5b40c03 | ||
|
0de2321920 |
@@ -120,6 +120,42 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"code"
|
"code"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "wenj91",
|
||||||
|
"name": "文杰",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/12549338?v=4",
|
||||||
|
"profile": "https://github.com/wenj91",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "goYou",
|
||||||
|
"name": "goYou",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/62732795?v=4",
|
||||||
|
"profile": "https://github.com/goYou",
|
||||||
|
"contributions": [
|
||||||
|
"translation"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "raliqala",
|
||||||
|
"name": "Topollo",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/30502407?v=4",
|
||||||
|
"profile": "https://github.com/raliqala",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "SmileYzn",
|
||||||
|
"name": "Cleverson",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/5851851?v=4",
|
||||||
|
"profile": "https://github.com/SmileYzn",
|
||||||
|
"contributions": [
|
||||||
|
"translation"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
|
5
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -26,13 +26,14 @@ If applicable, add screenshots to help explain your problem.
|
|||||||
|
|
||||||
**Application (please complete the following information):**
|
**Application (please complete the following information):**
|
||||||
|
|
||||||
|
- Client [e.g. MySQL]
|
||||||
- Version [e.g. 0.14.0]
|
- Version [e.g. 0.14.0]
|
||||||
- Distribution: [e.g. exe, Linux Store, AppImage, dmg]
|
- Distribution: [e.g. exe, Linux Store, AppImage, dmg]
|
||||||
|
|
||||||
**Desktop (please complete the following information):**
|
**Desktop (please complete the following information):**
|
||||||
|
|
||||||
- OS: [e.g. iOS]
|
- OS: [e.g. Windows 11]
|
||||||
- Version [e.g. 22]
|
- Version [e.g. 21H2]
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
Add any other context about the problem here.
|
Add any other context about the problem here.
|
||||||
|
8
.github/workflows/build-linux.yml
vendored
@@ -12,12 +12,18 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out Git repository
|
- name: Check out Git repository
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Install Node.js, NPM and Yarn
|
- name: Install Node.js, NPM and Yarn
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 14
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm i
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: npm run test
|
||||||
|
|
||||||
- name: Build/release Electron app
|
- name: Build/release Electron app
|
||||||
uses: samuelmeuli/action-electron-builder@v1
|
uses: samuelmeuli/action-electron-builder@v1
|
||||||
|
8
.github/workflows/build-mac.yml
vendored
@@ -12,12 +12,18 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out Git repository
|
- name: Check out Git repository
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Install Node.js, NPM and Yarn
|
- name: Install Node.js, NPM and Yarn
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 14
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm i
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: npm run test
|
||||||
|
|
||||||
- name: Build/release Electron app
|
- name: Build/release Electron app
|
||||||
uses: samuelmeuli/action-electron-builder@v1
|
uses: samuelmeuli/action-electron-builder@v1
|
||||||
|
10
.github/workflows/build-win.yml
vendored
@@ -8,17 +8,23 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [windows-latest]
|
os: [windows-2019]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out Git repository
|
- name: Check out Git repository
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Install Node.js, NPM and Yarn
|
- name: Install Node.js, NPM and Yarn
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 14
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm i
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: npm run test
|
||||||
|
|
||||||
- name: Build/release Electron app
|
- name: Build/release Electron app
|
||||||
uses: samuelmeuli/action-electron-builder@v1
|
uses: samuelmeuli/action-electron-builder@v1
|
||||||
with:
|
with:
|
||||||
|
3
.gitignore
vendored
@@ -5,4 +5,5 @@ node_modules
|
|||||||
thumbs.db
|
thumbs.db
|
||||||
NOTES.md
|
NOTES.md
|
||||||
*.txt
|
*.txt
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
*.heapsnapshot
|
14
.vscode/launch.json
vendored
@@ -2,8 +2,8 @@
|
|||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"cwd": "${workspaceFolder}",
|
|
||||||
"name": "Electron: Main",
|
"name": "Electron: Main",
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
"port": 9222,
|
"port": 9222,
|
||||||
"protocol": "inspector",
|
"protocol": "inspector",
|
||||||
"request": "attach",
|
"request": "attach",
|
||||||
@@ -18,7 +18,17 @@
|
|||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"type": "chrome",
|
"type": "chrome",
|
||||||
"webRoot": "${workspaceFolder}"
|
"webRoot": "${workspaceFolder}"
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"name": "Electron: Worker",
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"port": 9224,
|
||||||
|
"protocol": "inspector",
|
||||||
|
"request": "attach",
|
||||||
|
"sourceMaps": true,
|
||||||
|
"type": "node",
|
||||||
|
"timeout": 1000000
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"compounds": [
|
"compounds": [
|
||||||
{
|
{
|
||||||
|
3
.vscode/settings.json
vendored
@@ -4,7 +4,8 @@
|
|||||||
"core",
|
"core",
|
||||||
"MySQL",
|
"MySQL",
|
||||||
"PostgreSQL",
|
"PostgreSQL",
|
||||||
"SQLite"
|
"SQLite",
|
||||||
|
"Windows"
|
||||||
],
|
],
|
||||||
"svg.preview.background": "transparent"
|
"svg.preview.background": "transparent"
|
||||||
}
|
}
|
142
CHANGELOG.md
@@ -2,7 +2,147 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||||
|
|
||||||
### [0.3.10](https://github.com/Fabio286/antares/compare/v0.3.9...v0.3.10) (2021-11-24)
|
### [0.5.1](https://github.com/Fabio286/antares/compare/v0.5.0...v0.5.1) (2022-03-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* export database as zip sql file ([8f3efab](https://github.com/Fabio286/antares/commit/8f3efabb6962c55c23a43c8da1433185dbc3fb41))
|
||||||
|
* **UI:** option to disable blur effects, closes [#209](https://github.com/Fabio286/antares/issues/209) ([e9079ad](https://github.com/Fabio286/antares/commit/e9079adb25ec28e9546acd54bc2565b8d6e28120))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* numeric scale displayed on non decimal fields ([db628f7](https://github.com/Fabio286/antares/commit/db628f77226b161c3df31b7450b92a6e58754ab7))
|
||||||
|
* **UI:** connection buttons out of screen on small displays, closes [#213](https://github.com/Fabio286/antares/issues/213) ([f12e6a9](https://github.com/Fabio286/antares/commit/f12e6a96dd66140b06c55eda775af48a666627dd))
|
||||||
|
|
||||||
|
## [0.5.0](https://github.com/Fabio286/antares/compare/v0.4.4...v0.5.0) (2022-03-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* delete dump file when the export is canceled ([d25c62b](https://github.com/Fabio286/antares/commit/d25c62b4da9480e040d0bfac8b76732a4c69a5f1))
|
||||||
|
* initial db export implementation ([0de2321](https://github.com/Fabio286/antares/commit/0de232192076d1de827424c593ac9dff63903531))
|
||||||
|
* initial mysql import support ([4e9f8d1](https://github.com/Fabio286/antares/commit/4e9f8d16ee3c204d5f0c2bed081206f8b38207a6))
|
||||||
|
* mysql export for trigger, views, schedulers, functions and routines ([b2a5b40](https://github.com/Fabio286/antares/commit/b2a5b40c03d56bced5a7968c3454f36060e56dd0))
|
||||||
|
* **MySQL:** enhance export characters escaping ([3be826d](https://github.com/Fabio286/antares/commit/3be826df4b02ff0df0aa922d96755b31b7155784))
|
||||||
|
* **MySQL:** support to multi spatial fields export ([4be55f3](https://github.com/Fabio286/antares/commit/4be55f3fe9bb48324b780734762f2ff6da2ccb61))
|
||||||
|
* **PostgreSQL:** :sparkles: Postgress connection string feature for local and server connection string ([6305752](https://github.com/Fabio286/antares/commit/6305752ad117cc29c04bce3ce3df321f743cdc44))
|
||||||
|
* **PostgreSQL:** :sparkles: Postgress connection string feature for local and server connection string ([f4a63ea](https://github.com/Fabio286/antares/commit/f4a63eae2aca2a84647a5027137614950aef1eac))
|
||||||
|
* **UI:** auto-refresh schema at the end of the import process ([abf2b92](https://github.com/Fabio286/antares/commit/abf2b92e6e66b6668e698c5addf4e3c00ae5157b))
|
||||||
|
* **UI:** better real-time import stats ([a6f5645](https://github.com/Fabio286/antares/commit/a6f5645a226454cc2c415311ac321ba3d4db4454))
|
||||||
|
* **UI:** toggle tables checkbox by column on export modal ([1c4d5b0](https://github.com/Fabio286/antares/commit/1c4d5b05b3f94b3e7bef930aa7f89bdaa596c0b9))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **MySQL:** exception exporting empty procedures/functions ([ee415da](https://github.com/Fabio286/antares/commit/ee415da127d6d0de95aac901a2a01af863736344))
|
||||||
|
* **MySQL:** export crash with large databases ([8cf738b](https://github.com/Fabio286/antares/commit/8cf738bac85698fddd0504eef7844279e8c11f44))
|
||||||
|
* **MySQL:** missing functions and procedures definer escapes on exporter ([f0351e5](https://github.com/Fabio286/antares/commit/f0351e5b94830f9f52256096c2601b0ca9cd811d))
|
||||||
|
* **MySQL:** missing initial delimiter for exported procedures ([1a9fc37](https://github.com/Fabio286/antares/commit/1a9fc3728580f789727256d7893ca4bb90c16a50))
|
||||||
|
* **MySQL:** procedures exportation ([4276586](https://github.com/Fabio286/antares/commit/4276586e1141500401ff1ab570b29e485f459987))
|
||||||
|
* sql parser hangs during import ([7a6bd8b](https://github.com/Fabio286/antares/commit/7a6bd8bdbd69e3b5fe265d0bb0be844699dd77c2))
|
||||||
|
* wrong schema and table size on explore bar ([4479a96](https://github.com/Fabio286/antares/commit/4479a9600b5e59ef1bcf9135d661b4d7900a4bde))
|
||||||
|
* wrong soft sort algorithm for numeric fields, closes [#199](https://github.com/Fabio286/antares/issues/199) ([763be85](https://github.com/Fabio286/antares/commit/763be8532d2b61d0b4d45e72343f6a2e5fee1db9))
|
||||||
|
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
|
||||||
|
* avoid to load schema elements if already loaded in export modal ([d9d3bf2](https://github.com/Fabio286/antares/commit/d9d3bf2bc9d39ce8eec5dffbecbf767fbcf47782))
|
||||||
|
* **MySQL:** import performance improvement ([f444746](https://github.com/Fabio286/antares/commit/f444746f465ed0e8bd2e4c007faf17e167814278))
|
||||||
|
* **MySQL:** import tasks managed with async queue ([bbe13f2](https://github.com/Fabio286/antares/commit/bbe13f27dc29f997898f8c13f36b5d582770b21d))
|
||||||
|
* **MySQL:** improved several field types support on exporter ([1990d9a](https://github.com/Fabio286/antares/commit/1990d9a3d441f0e2075ac7e893d5b166275c48c0))
|
||||||
|
* **MySQL:** prevent memory leak on large dump import ([f3759b6](https://github.com/Fabio286/antares/commit/f3759b65411a40d92b98208176cdf8e6dd8230ce))
|
||||||
|
* **PostgreSQL:** :zap: Postgres connection update, better error handling and connection string accommodation. ([330a80f](https://github.com/Fabio286/antares/commit/330a80fe70b81f466f5e883029f42087b4b5c411))
|
||||||
|
* split the export select query to avoid running out of memory ([409ed54](https://github.com/Fabio286/antares/commit/409ed54608ad402b63fcc26a6e724bc447ba89d2))
|
||||||
|
* use fork() for the export process ([748d449](https://github.com/Fabio286/antares/commit/748d44977e76c6c8d6344df52e8e3ccfab84f670))
|
||||||
|
* use fork() for the import process ([573ac6d](https://github.com/Fabio286/antares/commit/573ac6d42ef833f250d102e5b30ae6cf5877f330))
|
||||||
|
|
||||||
|
### [0.4.4](https://github.com/Fabio286/antares/compare/v0.4.3...v0.4.4) (2022-02-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* execution notification for ROLLBACK and COMMIT ([76743e8](https://github.com/Fabio286/antares/commit/76743e8f7c02b824cb21540bfbcbe66ba43de8fa))
|
||||||
|
* **MySQL:** manual commit mode ([4ed2f9a](https://github.com/Fabio286/antares/commit/4ed2f9a93937b4293436a64318b7d6ae3a0d93c2))
|
||||||
|
* **PostgreSQL:** manual commit mode ([d81e091](https://github.com/Fabio286/antares/commit/d81e0911ab82fb75745ab11e67e867a00d8ac273))
|
||||||
|
* reminder for uncommitted changes closing a tab ([5bfff64](https://github.com/Fabio286/antares/commit/5bfff649e92f6fe5aba4b16aa4c8d5a5a70b31b2))
|
||||||
|
* **SQLite:** manual commit mode ([7dcd444](https://github.com/Fabio286/antares/commit/7dcd4441c49fafc0f47e12c2129708fe1092e1a4))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* bigint support, closes [#197](https://github.com/Fabio286/antares/issues/197) ([b703955](https://github.com/Fabio286/antares/commit/b7039553ccaac4fd59e521530c4a053922854130))
|
||||||
|
* **MySQL:** default value not displayed for DECIMAL fields ([fa3f3e1](https://github.com/Fabio286/antares/commit/fa3f3e1fd8101f19f18df71e90d60fd37cdddaee))
|
||||||
|
* zero-padded bit fields beyond length ([265f28b](https://github.com/Fabio286/antares/commit/265f28b4d94cde4608a1d6d3d306824134808ec2))
|
||||||
|
|
||||||
|
### [0.4.3](https://github.com/Fabio286/antares/compare/v0.4.2...v0.4.3) (2022-01-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add Simplified Chinese translation ([6ef565c](https://github.com/Fabio286/antares/commit/6ef565cf078cb3f5b7bcdc226894cddeb6239db9))
|
||||||
|
* **MySQL:** spatial fields support ([#165](https://github.com/Fabio286/antares/issues/165)) ([48ebf23](https://github.com/Fabio286/antares/commit/48ebf23bd1574408f429f2e1200ce878352007f6))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* cell copy returns "undefined" in some conditions, closes [#170](https://github.com/Fabio286/antares/issues/170) ([8fb1f08](https://github.com/Fabio286/antares/commit/8fb1f0803efd9df0b66521e73bb6e1a229cf9691))
|
||||||
|
* indexes and foreign keys not cleared after deletion of related field, closes [#182](https://github.com/Fabio286/antares/issues/182) ([9f033fb](https://github.com/Fabio286/antares/commit/9f033fb994916b4fb165e81e55e86127ca817791))
|
||||||
|
* **PostgreSQL:** schema different than public not automatically selected, closes [#172](https://github.com/Fabio286/antares/issues/172) ([46b45c8](https://github.com/Fabio286/antares/commit/46b45c8ab64fb6837a532c4f8342167e4fd794bb))
|
||||||
|
* scale on numeric fields that doesn't support it ([0cfd793](https://github.com/Fabio286/antares/commit/0cfd7938ee7d607dbad66ae452d0200223a6bab2))
|
||||||
|
* **Windows:** temporary fix to Windows 7 style frame on app startup, closes [#169](https://github.com/Fabio286/antares/issues/169) ([1356011](https://github.com/Fabio286/antares/commit/1356011ba3b7dd72e12cb252a8787ce48a364fd4))
|
||||||
|
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
|
||||||
|
* support of scale in field's length setting ([eef7c1d](https://github.com/Fabio286/antares/commit/eef7c1dcecc6593ab0e69ed678187a57fe0a4fb6))
|
||||||
|
|
||||||
|
### [0.4.2](https://github.com/Fabio286/antares/compare/v0.4.1...v0.4.2) (2022-01-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **MySQL:** ability to cancel queries ([a59f77f](https://github.com/Fabio286/antares/commit/a59f77f618aea6156fc80fb832d3efcb9848411f))
|
||||||
|
* **PostgreSQL:** ability to cancel queries ([0c00291](https://github.com/Fabio286/antares/commit/0c002918eb0226f6b3f21ed62117495f86396fb1))
|
||||||
|
* save window state ([8f9385d](https://github.com/Fabio286/antares/commit/8f9385d50815635d091758ecd5d00884e3297ca0))
|
||||||
|
* **UI:** textarea autofocus selecting a query tab, closes [#166](https://github.com/Fabio286/antares/issues/166) ([b4545b1](https://github.com/Fabio286/antares/commit/b4545b178f795712c781a3f4fc35eec31b5ad902))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **SQLite:** exception with some fields ([e7a1858](https://github.com/Fabio286/antares/commit/e7a18580915e7739bfa97948c6a0c4fc90a7e78a))
|
||||||
|
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
|
||||||
|
* hash for foreign key default names ([48c3e6a](https://github.com/Fabio286/antares/commit/48c3e6afc43c51f70a16703f1a71194f43da7a3e))
|
||||||
|
* **MySQL:** support to ANSI_QUOTES sql_mode, closes [#158](https://github.com/Fabio286/antares/issues/158) ([d9a3eab](https://github.com/Fabio286/antares/commit/d9a3eab015302e9f23112f659658073ab3242191))
|
||||||
|
|
||||||
|
### [0.4.1](https://github.com/Fabio286/antares/compare/v0.4.0...v0.4.1) (2021-12-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* language format detection for text fields ([a5fdcc1](https://github.com/Fabio286/antares/commit/a5fdcc1a85aa188ff1b9a15b1a768aced026f360))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* cell disappear on edit in one column tables ([aaa5549](https://github.com/Fabio286/antares/commit/aaa5549609664665bd4513632d621cb249b379c1))
|
||||||
|
* false positive with Windows Defender ([992a033](https://github.com/Fabio286/antares/commit/992a033cb2bede3d1eb52e19482d810f6692de1e))
|
||||||
|
* **MySQL:** wrong datetime fields default in table filler in some cases ([8da0224](https://github.com/Fabio286/antares/commit/8da022487650039b7f34a9c86a7bd9045eba65e2))
|
||||||
|
* **MySQL:** wrong value for fields "on update" in some conditions ([359e14a](https://github.com/Fabio286/antares/commit/359e14a9ebd48f86069ba7762fe00a7056f52d47))
|
||||||
|
* select all rows with ctrl+a when editing a cell ([35cb7e1](https://github.com/Fabio286/antares/commit/35cb7e1dc48d3a74e9d106cb1a37f454c1b4a4d1))
|
||||||
|
* **SQLite:** update rows with a text primary key ([d7f1aa9](https://github.com/Fabio286/antares/commit/d7f1aa97af32a4c51fc7022498bd47e15fa08430))
|
||||||
|
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
|
||||||
|
* **UI:** avoid columns size change when editing cells or scrolling results ([813aa32](https://github.com/Fabio286/antares/commit/813aa320d9ab799efea38a7110b7c0bdf7549123))
|
||||||
|
* **UI:** disable save button in table creation when no fields are added ([e8af2d2](https://github.com/Fabio286/antares/commit/e8af2d24a869f7667c069936648808952d2062ab))
|
||||||
|
|
||||||
|
## [0.4.0](https://github.com/Fabio286/antares/compare/v0.3.9...v0.4.0) (2021-11-24)
|
||||||
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
11
README.md
@@ -31,10 +31,10 @@ We are actively working on it, hoping to provide new cool features, improvements
|
|||||||
- Query suggestions and auto complete.
|
- Query suggestions and auto complete.
|
||||||
- Query history: search through the last 1000 queries.
|
- Query history: search through the last 1000 queries.
|
||||||
- SSH tunnel support.
|
- SSH tunnel support.
|
||||||
|
- Manual commit mode.
|
||||||
|
- Import and export database dumps.
|
||||||
- Dark and light theme.
|
- Dark and light theme.
|
||||||
- Editor themes.
|
- Editor themes.
|
||||||
- Scratchpad.
|
|
||||||
- Secure password storage.
|
|
||||||
|
|
||||||
## Philosophy
|
## Philosophy
|
||||||
|
|
||||||
@@ -72,7 +72,6 @@ This is a roadmap with major features will come in near future.
|
|||||||
- Users management (add/edit/delete).
|
- Users management (add/edit/delete).
|
||||||
- More context menu shortcuts.
|
- More context menu shortcuts.
|
||||||
- More keyboard shortcuts.
|
- More keyboard shortcuts.
|
||||||
- Import/export and migration.
|
|
||||||
- Support for other databases.
|
- Support for other databases.
|
||||||
- Apple Silicon distribution
|
- Apple Silicon distribution
|
||||||
|
|
||||||
@@ -130,6 +129,12 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||||||
<td align="center"><a href="https://github.com/IsamuSugi"><img src="https://avatars.githubusercontent.com/u/7746658?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Isamu Sugiura</b></sub></a><br /><a href="#translation-IsamuSugi" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://github.com/IsamuSugi"><img src="https://avatars.githubusercontent.com/u/7746658?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Isamu Sugiura</b></sub></a><br /><a href="#translation-IsamuSugi" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="http://rsacchetto.nexxontech.it/"><img src="https://avatars.githubusercontent.com/u/18429412?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Riccardo Sacchetto</b></sub></a><br /><a href="#platform-Occhioverde" title="Packaging/porting to new platform">📦</a></td>
|
<td align="center"><a href="http://rsacchetto.nexxontech.it/"><img src="https://avatars.githubusercontent.com/u/18429412?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Riccardo Sacchetto</b></sub></a><br /><a href="#platform-Occhioverde" title="Packaging/porting to new platform">📦</a></td>
|
||||||
<td align="center"><a href="https://kilianstallinger.com"><img src="https://avatars.githubusercontent.com/u/5290318?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kilian Stallinger</b></sub></a><br /><a href="https://github.com/Fabio286/antares/commits?author=kilianstallz" title="Code">💻</a></td>
|
<td align="center"><a href="https://kilianstallinger.com"><img src="https://avatars.githubusercontent.com/u/5290318?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kilian Stallinger</b></sub></a><br /><a href="https://github.com/Fabio286/antares/commits?author=kilianstallz" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/wenj91"><img src="https://avatars.githubusercontent.com/u/12549338?v=4?s=100" width="100px;" alt=""/><br /><sub><b>文杰</b></sub></a><br /><a href="https://github.com/Fabio286/antares/commits?author=wenj91" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/goYou"><img src="https://avatars.githubusercontent.com/u/62732795?v=4?s=100" width="100px;" alt=""/><br /><sub><b>goYou</b></sub></a><br /><a href="#translation-goYou" title="Translation">🌍</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center"><a href="https://github.com/raliqala"><img src="https://avatars.githubusercontent.com/u/30502407?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Topollo</b></sub></a><br /><a href="https://github.com/Fabio286/antares/commits?author=raliqala" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/SmileYzn"><img src="https://avatars.githubusercontent.com/u/5851851?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cleverson</b></sub></a><br /><a href="#translation-SmileYzn" title="Translation">🌍</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 7.4 KiB |
29
package.json
@@ -1,24 +1,25 @@
|
|||||||
{
|
{
|
||||||
"name": "antares",
|
"name": "antares",
|
||||||
"productName": "Antares",
|
"productName": "Antares",
|
||||||
"version": "0.3.10",
|
"version": "0.5.1",
|
||||||
"description": "A modern, fast and productivity driven SQL client with a focus in UX.",
|
"description": "A modern, fast and productivity driven SQL client with a focus in UX.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": "https://github.com/Fabio286/antares.git",
|
"repository": "https://github.com/Fabio286/antares.git",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"debug": "npm run rebuild:electron && npm run debug-runner",
|
"debug": "npm run rebuild:electron && npm run debug-runner",
|
||||||
"debug-runner": "node scripts/devRunner.js --remote-debug",
|
"debug-runner": "node scripts/devRunner.js --remote-debug",
|
||||||
"compile": "npm run compile:main && npm run compile:renderer",
|
"compile": "npm run compile:main && npm run compile:workers && npm run compile:renderer",
|
||||||
"compile:main": "webpack --mode=production --config webpack.main.config.js",
|
"compile:main": "webpack --mode=production --config webpack.main.config.js",
|
||||||
|
"compile:workers": "webpack --mode=production --config webpack.workers.config.js",
|
||||||
"compile:renderer": "webpack --mode=production --config webpack.renderer.config.js",
|
"compile:renderer": "webpack --mode=production --config webpack.renderer.config.js",
|
||||||
"build": "cross-env NODE_ENV=production npm run compile",
|
"build": "cross-env NODE_ENV=production npm run compile",
|
||||||
"build:local": "npm run build && electron-builder",
|
"build:local": "npm run build && electron-builder",
|
||||||
"build:appx": "npm run build:local -- --win appx",
|
"build:appx": "npm run build:local -- --win appx",
|
||||||
"rebuild:electron": "npm run postinstall",
|
"rebuild:electron": "rimraf ./dist && npm run postinstall",
|
||||||
"release": "standard-version",
|
"release": "standard-version",
|
||||||
"release:pre": "npm run release -- --prerelease alpha",
|
"release:pre": "npm run release -- --prerelease alpha",
|
||||||
"postinstall": "electron-builder install-app-deps",
|
"postinstall": "electron-builder install-app-deps",
|
||||||
"test": "npm run lint",
|
"test": "npm run compile && node tests/app.spec.js",
|
||||||
"lint": "eslint . --ext .js,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
|
"lint": "eslint . --ext .js,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
|
||||||
"lint:fix": "eslint . --ext .js,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix",
|
"lint:fix": "eslint . --ext .js,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix",
|
||||||
"contributors:add": "all-contributors add",
|
"contributors:add": "all-contributors add",
|
||||||
@@ -82,6 +83,7 @@
|
|||||||
"appx": {
|
"appx": {
|
||||||
"displayName": "Antares SQL",
|
"displayName": "Antares SQL",
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
|
"showNameOnTiles": true,
|
||||||
"identityName": "62514FabioDiStasio.AntaresSQLClient",
|
"identityName": "62514FabioDiStasio.AntaresSQLClient",
|
||||||
"publisher": "CN=1A2729ED-865C-41D2-9038-39AE2A63AA52",
|
"publisher": "CN=1A2729ED-865C-41D2-9038-39AE2A63AA52",
|
||||||
"applicationId": "FabioDiStasio.AntaresSQLClient"
|
"applicationId": "FabioDiStasio.AntaresSQLClient"
|
||||||
@@ -104,12 +106,16 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/remote": "^2.0.1",
|
"@electron/remote": "^2.0.1",
|
||||||
"@mdi/font": "^6.1.95",
|
"@mdi/font": "^6.1.95",
|
||||||
|
"@turf/helpers": "^6.5.0",
|
||||||
|
"@vscode/vscode-languagedetection": "^1.0.21",
|
||||||
"ace-builds": "^1.4.13",
|
"ace-builds": "^1.4.13",
|
||||||
"better-sqlite3": "^7.4.4",
|
"better-sqlite3": "^7.5.0",
|
||||||
"electron-log": "^4.4.1",
|
"electron-log": "^4.4.1",
|
||||||
"electron-store": "^8.0.1",
|
"electron-store": "^8.0.1",
|
||||||
"electron-updater": "^4.3.9",
|
"electron-updater": "^4.6.1",
|
||||||
|
"electron-window-state": "^5.0.3",
|
||||||
"faker": "^5.5.3",
|
"faker": "^5.5.3",
|
||||||
|
"leaflet": "^1.7.1",
|
||||||
"marked": "^4.0.0",
|
"marked": "^4.0.0",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"mysql2": "^2.3.2",
|
"mysql2": "^2.3.2",
|
||||||
@@ -130,23 +136,24 @@
|
|||||||
"all-contributors-cli": "^6.20.0",
|
"all-contributors-cli": "^6.20.0",
|
||||||
"babel-loader": "^8.2.3",
|
"babel-loader": "^8.2.3",
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
"clean-webpack-plugin": "^4.0.0",
|
|
||||||
"cross-env": "^7.0.2",
|
"cross-env": "^7.0.2",
|
||||||
"css-loader": "^6.5.0",
|
"css-loader": "^6.5.0",
|
||||||
"electron": "^16.0.1",
|
"electron": "^17.0.1",
|
||||||
"electron-builder": "^22.13.1",
|
"electron-builder": "^22.14.11",
|
||||||
"electron-devtools-installer": "^3.2.0",
|
"electron-devtools-installer": "^3.2.0",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
"eslint-config-standard": "^16.0.3",
|
"eslint-config-standard": "^16.0.3",
|
||||||
"eslint-plugin-import": "^2.24.2",
|
"eslint-plugin-import": "^2.24.2",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"eslint-plugin-promise": "^5.1.0",
|
"eslint-plugin-promise": "^5.2.0",
|
||||||
"eslint-plugin-vue": "^8.0.3",
|
"eslint-plugin-vue": "^8.0.3",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
"html-webpack-plugin": "^5.5.0",
|
"html-webpack-plugin": "^5.5.0",
|
||||||
"mini-css-extract-plugin": "^2.4.3",
|
"mini-css-extract-plugin": "~2.4.5",
|
||||||
"node-loader": "^2.0.0",
|
"node-loader": "^2.0.0",
|
||||||
|
"playwright": "^1.18.1",
|
||||||
"progress-webpack-plugin": "^1.0.12",
|
"progress-webpack-plugin": "^1.0.12",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
"sass": "^1.42.1",
|
"sass": "^1.42.1",
|
||||||
"sass-loader": "^12.3.0",
|
"sass-loader": "^12.3.0",
|
||||||
"standard-version": "^9.3.1",
|
"standard-version": "^9.3.1",
|
||||||
|
@@ -12,7 +12,7 @@ const { spawn } = require('child_process');
|
|||||||
|
|
||||||
const mainConfig = require('../webpack.main.config');
|
const mainConfig = require('../webpack.main.config');
|
||||||
const rendererConfig = require('../webpack.renderer.config');
|
const rendererConfig = require('../webpack.renderer.config');
|
||||||
// const workersConfig = require('../webpack.workers.config');
|
const workersConfig = require('../webpack.workers.config');
|
||||||
|
|
||||||
let electronProcess = null;
|
let electronProcess = null;
|
||||||
let manualRestart = null;
|
let manualRestart = null;
|
||||||
@@ -64,7 +64,7 @@ async function restartElectron () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function startMain () {
|
function startMain () {
|
||||||
const webpackSetup = webpack(mainConfig);
|
const webpackSetup = webpack([mainConfig, workersConfig]);
|
||||||
|
|
||||||
webpackSetup.compilers.forEach((compiler) => {
|
webpackSetup.compilers.forEach((compiler) => {
|
||||||
const { name } = compiler;
|
const { name } = compiler;
|
||||||
|
@@ -134,7 +134,7 @@ export default class {
|
|||||||
{ name: 'phoneNumberFormat', group: 'phone', types: ['string'] },
|
{ name: 'phoneNumberFormat', group: 'phone', types: ['string'] },
|
||||||
{ name: 'phoneFormats', group: 'phone', types: ['string'] },
|
{ name: 'phoneFormats', group: 'phone', types: ['string'] },
|
||||||
|
|
||||||
{ name: 'number', group: 'random', types: ['string', 'number'], params: ['min', 'max'] },
|
{ name: 'number', group: 'datatype', types: ['string', 'number'], params: ['min', 'max'] },
|
||||||
{ name: 'float', group: 'random', types: ['string', 'float'], params: ['min', 'max'] },
|
{ name: 'float', group: 'random', types: ['string', 'float'], params: ['min', 'max'] },
|
||||||
{ name: 'arrayElement', group: 'random', types: ['string'] },
|
{ name: 'arrayElement', group: 'random', types: ['string'] },
|
||||||
{ name: 'arrayElements', group: 'random', types: ['string'] },
|
{ name: 'arrayElements', group: 'random', types: ['string'] },
|
||||||
|
@@ -11,6 +11,7 @@ module.exports = {
|
|||||||
sslConnection: false,
|
sslConnection: false,
|
||||||
sshConnection: false,
|
sshConnection: false,
|
||||||
fileConnection: false,
|
fileConnection: false,
|
||||||
|
cancelQueries: false,
|
||||||
// Tools
|
// Tools
|
||||||
processesList: false,
|
processesList: false,
|
||||||
usersManagement: false,
|
usersManagement: false,
|
||||||
@@ -37,6 +38,8 @@ module.exports = {
|
|||||||
databaseEdit: false,
|
databaseEdit: false,
|
||||||
schemaEdit: false,
|
schemaEdit: false,
|
||||||
schemaDrop: false,
|
schemaDrop: false,
|
||||||
|
schemaExport: false,
|
||||||
|
schemaImport: false,
|
||||||
tableSettings: false,
|
tableSettings: false,
|
||||||
tableOptions: false,
|
tableOptions: false,
|
||||||
tableArray: false,
|
tableArray: false,
|
||||||
|
@@ -12,6 +12,7 @@ module.exports = {
|
|||||||
engines: true,
|
engines: true,
|
||||||
sslConnection: true,
|
sslConnection: true,
|
||||||
sshConnection: true,
|
sshConnection: true,
|
||||||
|
cancelQueries: true,
|
||||||
// Tools
|
// Tools
|
||||||
processesList: true,
|
processesList: true,
|
||||||
// Structure
|
// Structure
|
||||||
@@ -33,6 +34,8 @@ module.exports = {
|
|||||||
schedulerAdd: true,
|
schedulerAdd: true,
|
||||||
schemaEdit: true,
|
schemaEdit: true,
|
||||||
schemaDrop: true,
|
schemaDrop: true,
|
||||||
|
schemaExport: true,
|
||||||
|
schemaImport: true,
|
||||||
tableSettings: true,
|
tableSettings: true,
|
||||||
viewSettings: true,
|
viewSettings: true,
|
||||||
triggerSettings: true,
|
triggerSettings: true,
|
||||||
|
@@ -10,6 +10,7 @@ module.exports = {
|
|||||||
database: true,
|
database: true,
|
||||||
sslConnection: true,
|
sslConnection: true,
|
||||||
sshConnection: true,
|
sshConnection: true,
|
||||||
|
cancelQueries: true,
|
||||||
// Tools
|
// Tools
|
||||||
processesList: true,
|
processesList: true,
|
||||||
// Structure
|
// Structure
|
||||||
@@ -31,6 +32,8 @@ module.exports = {
|
|||||||
functionAdd: true,
|
functionAdd: true,
|
||||||
schemaDrop: true,
|
schemaDrop: true,
|
||||||
databaseEdit: false,
|
databaseEdit: false,
|
||||||
|
schemaExport: false,
|
||||||
|
schemaImport: false,
|
||||||
tableSettings: true,
|
tableSettings: true,
|
||||||
viewSettings: true,
|
viewSettings: true,
|
||||||
triggerSettings: true,
|
triggerSettings: true,
|
||||||
|
@@ -7,8 +7,8 @@ module.exports = {
|
|||||||
views: true,
|
views: true,
|
||||||
triggers: true,
|
triggers: true,
|
||||||
// Settings
|
// Settings
|
||||||
elementsWrapper: '',
|
elementsWrapper: '"',
|
||||||
stringsWrapper: '"',
|
stringsWrapper: '\'',
|
||||||
tableAdd: true,
|
tableAdd: true,
|
||||||
viewAdd: true,
|
viewAdd: true,
|
||||||
triggerAdd: true,
|
triggerAdd: true,
|
||||||
|
@@ -66,6 +66,7 @@ module.exports = [
|
|||||||
{
|
{
|
||||||
name: 'DECIMAL',
|
name: 'DECIMAL',
|
||||||
length: true,
|
length: true,
|
||||||
|
scale: true,
|
||||||
collation: false,
|
collation: false,
|
||||||
unsigned: false,
|
unsigned: false,
|
||||||
zerofill: false
|
zerofill: false
|
||||||
@@ -120,7 +121,7 @@ module.exports = [
|
|||||||
{
|
{
|
||||||
name: 'JSON',
|
name: 'JSON',
|
||||||
length: false,
|
length: false,
|
||||||
collation: true,
|
collation: false,
|
||||||
unsigned: false,
|
unsigned: false,
|
||||||
zerofill: false
|
zerofill: false
|
||||||
}
|
}
|
||||||
@@ -218,56 +219,56 @@ module.exports = [
|
|||||||
types: [
|
types: [
|
||||||
{
|
{
|
||||||
name: 'POINT',
|
name: 'POINT',
|
||||||
length: true,
|
length: false,
|
||||||
collation: false,
|
collation: false,
|
||||||
unsigned: false,
|
unsigned: false,
|
||||||
zerofill: false
|
zerofill: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'LINESTRING',
|
name: 'LINESTRING',
|
||||||
length: true,
|
length: false,
|
||||||
collation: false,
|
collation: false,
|
||||||
unsigned: false,
|
unsigned: false,
|
||||||
zerofill: false
|
zerofill: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'POLYGON',
|
name: 'POLYGON',
|
||||||
length: true,
|
length: false,
|
||||||
collation: false,
|
collation: false,
|
||||||
unsigned: false,
|
unsigned: false,
|
||||||
zerofill: false
|
zerofill: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'GEOMETRY',
|
name: 'GEOMETRY',
|
||||||
length: true,
|
length: false,
|
||||||
collation: false,
|
collation: false,
|
||||||
unsigned: false,
|
unsigned: false,
|
||||||
zerofill: false
|
zerofill: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'MULTIPOINT',
|
name: 'MULTIPOINT',
|
||||||
length: true,
|
length: false,
|
||||||
collation: false,
|
collation: false,
|
||||||
unsigned: false,
|
unsigned: false,
|
||||||
zerofill: false
|
zerofill: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'MULTILINESTRING',
|
name: 'MULTILINESTRING',
|
||||||
length: true,
|
length: false,
|
||||||
collation: false,
|
collation: false,
|
||||||
unsigned: false,
|
unsigned: false,
|
||||||
zerofill: false
|
zerofill: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'MULTIPOLYGON',
|
name: 'MULTIPOLYGON',
|
||||||
length: true,
|
length: false,
|
||||||
collation: false,
|
collation: false,
|
||||||
unsigned: false,
|
unsigned: false,
|
||||||
zerofill: false
|
zerofill: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'GEOMETRYCOLLECTION',
|
name: 'GEOMCOLLECTION',
|
||||||
length: true,
|
length: false,
|
||||||
collation: false,
|
collation: false,
|
||||||
unsigned: false,
|
unsigned: false,
|
||||||
zerofill: false
|
zerofill: false
|
||||||
|
@@ -22,11 +22,6 @@ module.exports = [
|
|||||||
length: false,
|
length: false,
|
||||||
unsigned: true
|
unsigned: true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'NUMERIC',
|
|
||||||
length: true,
|
|
||||||
unsigned: true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'SMALLSERIAL',
|
name: 'SMALLSERIAL',
|
||||||
length: false,
|
length: false,
|
||||||
@@ -52,6 +47,12 @@ module.exports = [
|
|||||||
length: false,
|
length: false,
|
||||||
unsigned: true
|
unsigned: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'NUMERIC',
|
||||||
|
length: true,
|
||||||
|
unsigned: true,
|
||||||
|
scale: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'DOUBLE PRECISION',
|
name: 'DOUBLE PRECISION',
|
||||||
length: false,
|
length: false,
|
||||||
|
@@ -8,7 +8,9 @@ export const TEXT = [
|
|||||||
export const LONG_TEXT = [
|
export const LONG_TEXT = [
|
||||||
'TEXT',
|
'TEXT',
|
||||||
'MEDIUMTEXT',
|
'MEDIUMTEXT',
|
||||||
'LONGTEXT'
|
'LONGTEXT',
|
||||||
|
'JSON',
|
||||||
|
'VARBINARY'
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ARRAY = [
|
export const ARRAY = [
|
||||||
@@ -39,6 +41,7 @@ export const NUMBER = [
|
|||||||
|
|
||||||
export const FLOAT = [
|
export const FLOAT = [
|
||||||
'FLOAT',
|
'FLOAT',
|
||||||
|
'DECIMAL',
|
||||||
'DOUBLE',
|
'DOUBLE',
|
||||||
'REAL',
|
'REAL',
|
||||||
'DOUBLE PRECISION',
|
'DOUBLE PRECISION',
|
||||||
@@ -82,3 +85,24 @@ export const BIT = [
|
|||||||
'BIT',
|
'BIT',
|
||||||
'BIT VARYING'
|
'BIT VARYING'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const SPATIAL = [
|
||||||
|
'POINT',
|
||||||
|
'LINESTRING',
|
||||||
|
'POLYGON',
|
||||||
|
'GEOMETRY',
|
||||||
|
'MULTIPOINT',
|
||||||
|
'MULTILINESTRING',
|
||||||
|
'MULTIPOLYGON',
|
||||||
|
'GEOMCOLLECTION',
|
||||||
|
'GEOMETRYCOLLECTION'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Used to check multi spatial fields only
|
||||||
|
export const IS_MULTI_SPATIAL = [
|
||||||
|
'MULTIPOINT',
|
||||||
|
'MULTILINESTRING',
|
||||||
|
'MULTIPOLYGON',
|
||||||
|
'GEOMCOLLECTION',
|
||||||
|
'GEOMETRYCOLLECTION'
|
||||||
|
];
|
||||||
|
10
src/common/libs/getArrayDepth.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {any[]} array
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
export function getArrayDepth (array) {
|
||||||
|
return Array.isArray(array)
|
||||||
|
? 1 + Math.max(0, ...array.map(getArrayDepth))
|
||||||
|
: 0;
|
||||||
|
}
|
@@ -33,7 +33,7 @@ const lookup = {
|
|||||||
*/
|
*/
|
||||||
export default function hexToBinary (hex) {
|
export default function hexToBinary (hex) {
|
||||||
let binary = '';
|
let binary = '';
|
||||||
for (let i = 0, len = hex.length; i < len; i++)
|
for (let i = 0; i < hex.length; i++)
|
||||||
binary += lookup[hex[i]];
|
binary += lookup[hex[i]];
|
||||||
|
|
||||||
return binary;
|
return binary;
|
||||||
|
@@ -23,7 +23,7 @@ export function mimeFromHex (hex) {
|
|||||||
case '425A68':
|
case '425A68':
|
||||||
return { ext: 'bz2', mime: 'application/x-bzip2' };
|
return { ext: 'bz2', mime: 'application/x-bzip2' };
|
||||||
default:
|
default:
|
||||||
switch (hex) { // 4 bites
|
switch (hex) { // 4 bytes
|
||||||
case '89504E47':
|
case '89504E47':
|
||||||
return { ext: 'png', mime: 'image/png' };
|
return { ext: 'png', mime: 'image/png' };
|
||||||
case '47494638':
|
case '47494638':
|
||||||
|
93
src/common/libs/sqlParser.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import { Transform } from 'stream';
|
||||||
|
|
||||||
|
export default class SqlParser extends Transform {
|
||||||
|
constructor (opts) {
|
||||||
|
opts = {
|
||||||
|
delimiter: ';',
|
||||||
|
encoding: 'utf8',
|
||||||
|
writableObjectMode: true,
|
||||||
|
readableObjectMode: true,
|
||||||
|
...opts
|
||||||
|
};
|
||||||
|
super(opts);
|
||||||
|
this._buffer = '';
|
||||||
|
this._lastChar = '';
|
||||||
|
this._last9Chars = '';
|
||||||
|
this.encoding = opts.encoding;
|
||||||
|
this.delimiter = opts.delimiter;
|
||||||
|
|
||||||
|
this.isEscape = false;
|
||||||
|
this.currentQuote = null;
|
||||||
|
this.isDelimiter = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_transform (chunk, encoding, next) {
|
||||||
|
for (const char of chunk.toString(this.encoding)) {
|
||||||
|
this.checkEscape();
|
||||||
|
this._buffer += char;
|
||||||
|
this._lastChar = char;
|
||||||
|
this._last9Chars += char.trim().toLocaleLowerCase();
|
||||||
|
|
||||||
|
if (this._last9Chars.length > 9)
|
||||||
|
this._last9Chars = this._last9Chars.slice(-9);
|
||||||
|
|
||||||
|
this.checkNewDelimiter(char);
|
||||||
|
this.checkQuote(char);
|
||||||
|
const query = this.getQuery();
|
||||||
|
|
||||||
|
if (query)
|
||||||
|
this.push(query);
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
checkEscape () {
|
||||||
|
if (this._buffer.length > 0) {
|
||||||
|
this.isEscape = this._lastChar === '\\'
|
||||||
|
? !this.isEscape
|
||||||
|
: false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkNewDelimiter (char) {
|
||||||
|
if (this.currentQuote === null && this._last9Chars === 'delimiter') {
|
||||||
|
this.isDelimiter = true;
|
||||||
|
this._buffer = '';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const isNewLine = char === '\n' || char === '\r';
|
||||||
|
if (isNewLine && this.isDelimiter) {
|
||||||
|
this.isDelimiter = false;
|
||||||
|
this.delimiter = this._buffer.trim();
|
||||||
|
this._buffer = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkQuote (char) {
|
||||||
|
const isQuote = !this.isEscape && (char === '\'' || char === '"');
|
||||||
|
if (isQuote && this.currentQuote === char)
|
||||||
|
this.currentQuote = null;
|
||||||
|
|
||||||
|
else if (isQuote && this.currentQuote === null)
|
||||||
|
this.currentQuote = char;
|
||||||
|
}
|
||||||
|
|
||||||
|
getQuery () {
|
||||||
|
if (this.isDelimiter)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
let query = false;
|
||||||
|
let demiliterFound = false;
|
||||||
|
if (this.currentQuote === null && this._buffer.length >= this.delimiter.length)
|
||||||
|
demiliterFound = this._last9Chars.slice(-this.delimiter.length) === this.delimiter;
|
||||||
|
|
||||||
|
if (demiliterFound) {
|
||||||
|
const parsedStr = this._buffer.trim();
|
||||||
|
query = parsedStr.slice(0, parsedStr.length - this.delimiter.length);
|
||||||
|
this._buffer = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
import { app, ipcMain } from 'electron';
|
import { app, ipcMain, dialog } from 'electron';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
ipcMain.on('close-app', () => {
|
ipcMain.on('close-app', () => {
|
||||||
@@ -9,4 +9,12 @@ export default () => {
|
|||||||
const key = false;
|
const key = false;
|
||||||
event.returnValue = key;
|
event.returnValue = key;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('showOpenDialog', (event, options) => {
|
||||||
|
return dialog.showOpenDialog(options);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('get-download-dir-path', () => {
|
||||||
|
return app.getPath('downloads');
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@@ -40,7 +40,7 @@ export default connections => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const connection = await ClientsFactory.getConnection({
|
const connection = await ClientsFactory.getClient({
|
||||||
client: conn.client,
|
client: conn.client,
|
||||||
params
|
params
|
||||||
});
|
});
|
||||||
@@ -52,7 +52,7 @@ export default connections => {
|
|||||||
return { status: 'success' };
|
return { status: 'success' };
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
return { status: 'error', response: err };
|
return { status: 'error', response: err.toString() };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ export default connections => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const connection = ClientsFactory.getConnection({
|
const connection = ClientsFactory.getClient({
|
||||||
client: conn.client,
|
client: conn.client,
|
||||||
params,
|
params,
|
||||||
poolSize: 5
|
poolSize: 5
|
||||||
|
@@ -1,7 +1,15 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { fork } from 'child_process';
|
||||||
|
import { ipcMain, dialog } from 'electron';
|
||||||
|
|
||||||
import { ipcMain } from 'electron';
|
// @TODO: need some factories
|
||||||
|
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||||
|
|
||||||
export default connections => {
|
export default connections => {
|
||||||
|
let exporter = null;
|
||||||
|
let importer = null;
|
||||||
|
|
||||||
ipcMain.handle('create-schema', async (event, params) => {
|
ipcMain.handle('create-schema', async (event, params) => {
|
||||||
try {
|
try {
|
||||||
await connections[params.uid].createSchema(params);
|
await connections[params.uid].createSchema(params);
|
||||||
@@ -37,9 +45,16 @@ export default connections => {
|
|||||||
|
|
||||||
ipcMain.handle('get-schema-collation', async (event, params) => {
|
ipcMain.handle('get-schema-collation', async (event, params) => {
|
||||||
try {
|
try {
|
||||||
const collation = await connections[params.uid].getDatabaseCollation(params);
|
const collation = await connections[params.uid].getDatabaseCollation(
|
||||||
|
params
|
||||||
|
);
|
||||||
|
|
||||||
return { status: 'success', response: collation.rows.length ? collation.rows[0].DEFAULT_COLLATION_NAME : '' };
|
return {
|
||||||
|
status: 'success',
|
||||||
|
response: collation.rows.length
|
||||||
|
? collation.rows[0].DEFAULT_COLLATION_NAME
|
||||||
|
: ''
|
||||||
|
};
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
return { status: 'error', response: err.toString() };
|
return { status: 'error', response: err.toString() };
|
||||||
@@ -48,7 +63,9 @@ export default connections => {
|
|||||||
|
|
||||||
ipcMain.handle('get-structure', async (event, params) => {
|
ipcMain.handle('get-structure', async (event, params) => {
|
||||||
try {
|
try {
|
||||||
const structure = await connections[params.uid].getStructure(params.schemas);
|
const structure = await connections[params.uid].getStructure(
|
||||||
|
params.schemas
|
||||||
|
);
|
||||||
|
|
||||||
return { status: 'success', response: structure };
|
return { status: 'success', response: structure };
|
||||||
}
|
}
|
||||||
@@ -135,7 +152,7 @@ export default connections => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('raw-query', async (event, { uid, query, schema }) => {
|
ipcMain.handle('raw-query', async (event, { uid, query, schema, tabUid, autocommit }) => {
|
||||||
if (!query) return;
|
if (!query) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -143,6 +160,8 @@ export default connections => {
|
|||||||
nest: true,
|
nest: true,
|
||||||
details: true,
|
details: true,
|
||||||
schema,
|
schema,
|
||||||
|
tabUid,
|
||||||
|
autocommit,
|
||||||
comments: false
|
comments: false
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -152,4 +171,215 @@ export default connections => {
|
|||||||
return { status: 'error', response: err.toString() };
|
return { status: 'error', response: err.toString() };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('export', (event, { uid, type, tables, ...rest }) => {
|
||||||
|
if (exporter !== null) return;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
(async () => {
|
||||||
|
if (fs.existsSync(rest.outputFile)) { // If file exists ask for replace
|
||||||
|
const result = await dialog.showMessageBox({
|
||||||
|
type: 'warning',
|
||||||
|
message: `File ${rest.outputFile} already exists. Do you want to replace it?`,
|
||||||
|
detail: 'A file with the same name already exists in the target folder. Replacing it will overwrite its current contents.',
|
||||||
|
buttons: ['Cancel', 'Replace'],
|
||||||
|
defaultId: 0,
|
||||||
|
cancelId: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.response !== 1) {
|
||||||
|
resolve({
|
||||||
|
type: 'error',
|
||||||
|
response: 'Operation aborted'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init exporter process
|
||||||
|
exporter = fork(isDevelopment ? './dist/exporter.js' : path.resolve(__dirname, './exporter.js'), [], {
|
||||||
|
execArgv: isDevelopment ? ['--inspect=9224'] : undefined
|
||||||
|
});
|
||||||
|
exporter.send({
|
||||||
|
type: 'init',
|
||||||
|
client: {
|
||||||
|
name: type,
|
||||||
|
config: await connections[uid].getDbConfig()
|
||||||
|
},
|
||||||
|
tables,
|
||||||
|
options: rest
|
||||||
|
});
|
||||||
|
|
||||||
|
// Exporter message listener
|
||||||
|
exporter.on('message', ({ type, payload }) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'export-progress':
|
||||||
|
event.sender.send('export-progress', payload);
|
||||||
|
break;
|
||||||
|
case 'end':
|
||||||
|
setTimeout(() => { // Ensures that writing process has finished
|
||||||
|
exporter.kill();
|
||||||
|
exporter = null;
|
||||||
|
}, 2000);
|
||||||
|
resolve({ status: 'success', response: payload });
|
||||||
|
break;
|
||||||
|
case 'cancel':
|
||||||
|
exporter.kill();
|
||||||
|
exporter = null;
|
||||||
|
resolve({ status: 'error', response: 'Operation cancelled' });
|
||||||
|
break;
|
||||||
|
case 'error':
|
||||||
|
exporter.kill();
|
||||||
|
exporter = null;
|
||||||
|
resolve({ status: 'error', response: payload });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
exporter.on('exit', code => {
|
||||||
|
exporter = null;
|
||||||
|
resolve({ status: 'error', response: `Operation ended with code: ${code}` });
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('abort-export', async event => {
|
||||||
|
let willAbort = false;
|
||||||
|
|
||||||
|
if (exporter) {
|
||||||
|
const result = await dialog.showMessageBox({
|
||||||
|
type: 'warning',
|
||||||
|
message: 'Are you sure you want to abort the export',
|
||||||
|
buttons: ['Cancel', 'Abort'],
|
||||||
|
defaultId: 0,
|
||||||
|
cancelId: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.response === 1) {
|
||||||
|
willAbort = true;
|
||||||
|
exporter.send({ type: 'cancel' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { status: 'success', response: { willAbort } };
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('import-sql', async (event, options) => {
|
||||||
|
if (importer !== null) return;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
(async () => {
|
||||||
|
const dbConfig = await connections[options.uid].getDbConfig();
|
||||||
|
|
||||||
|
// Init importer process
|
||||||
|
importer = fork(isDevelopment ? './dist/importer.js' : path.resolve(__dirname, './importer.js'), [], {
|
||||||
|
execArgv: isDevelopment ? ['--inspect=9224'] : undefined
|
||||||
|
});
|
||||||
|
importer.send({
|
||||||
|
type: 'init',
|
||||||
|
dbConfig,
|
||||||
|
options
|
||||||
|
});
|
||||||
|
|
||||||
|
// Importer message listener
|
||||||
|
importer.on('message', ({ type, payload }) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'import-progress':
|
||||||
|
event.sender.send('import-progress', payload);
|
||||||
|
break;
|
||||||
|
case 'query-error':
|
||||||
|
event.sender.send('query-error', payload);
|
||||||
|
break;
|
||||||
|
case 'end':
|
||||||
|
setTimeout(() => { // Ensures that writing process has finished
|
||||||
|
importer?.kill();
|
||||||
|
importer = null;
|
||||||
|
}, 2000);
|
||||||
|
resolve({ status: 'success', response: payload });
|
||||||
|
break;
|
||||||
|
case 'cancel':
|
||||||
|
importer.kill();
|
||||||
|
importer = null;
|
||||||
|
resolve({ status: 'error', response: 'Operation cancelled' });
|
||||||
|
break;
|
||||||
|
case 'error':
|
||||||
|
importer.kill();
|
||||||
|
importer = null;
|
||||||
|
resolve({ status: 'error', response: payload });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('abort-import-sql', async event => {
|
||||||
|
let willAbort = false;
|
||||||
|
|
||||||
|
if (importer) {
|
||||||
|
const result = await dialog.showMessageBox({
|
||||||
|
type: 'warning',
|
||||||
|
message: 'Are you sure you want to abort the import',
|
||||||
|
buttons: ['Cancel', 'Abort'],
|
||||||
|
defaultId: 0,
|
||||||
|
cancelId: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.response === 1) {
|
||||||
|
willAbort = true;
|
||||||
|
importer.send({ type: 'cancel' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { status: 'success', response: { willAbort } };
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('kill-tab-query', async (event, { uid, tabUid }) => {
|
||||||
|
if (!tabUid) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await connections[uid].killTabQuery(tabUid);
|
||||||
|
return { status: 'success' };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('commit-tab', async (event, { uid, tabUid }) => {
|
||||||
|
if (!tabUid) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await connections[uid].commitTab(tabUid);
|
||||||
|
return { status: 'success' };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('rollback-tab', async (event, { uid, tabUid }) => {
|
||||||
|
if (!tabUid) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await connections[uid].rollbackTab(tabUid);
|
||||||
|
return { status: 'success' };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('destroy-connection-to-commit', async (event, { uid, tabUid }) => {
|
||||||
|
if (!tabUid) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await connections[uid].destroyConnectionToCommit(tabUid);
|
||||||
|
return { status: 'success' };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@@ -3,6 +3,7 @@ import faker from 'faker';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { sqlEscaper } from 'common/libs/sqlEscaper';
|
import { sqlEscaper } from 'common/libs/sqlEscaper';
|
||||||
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BLOB, BIT, DATE, DATETIME } from 'common/fieldTypes';
|
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BLOB, BIT, DATE, DATETIME } from 'common/fieldTypes';
|
||||||
|
import * as customizations from 'common/customizations';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
|
||||||
export default (connections) => {
|
export default (connections) => {
|
||||||
@@ -85,11 +86,12 @@ export default (connections) => {
|
|||||||
|
|
||||||
ipcMain.handle('update-table-cell', async (event, params) => {
|
ipcMain.handle('update-table-cell', async (event, params) => {
|
||||||
delete params.row._antares_id;
|
delete params.row._antares_id;
|
||||||
|
const { stringsWrapper: sw } = customizations[connections[params.uid]._client];
|
||||||
|
|
||||||
try { // TODO: move to client classes
|
try { // TODO: move to client classes
|
||||||
let escapedParam;
|
let escapedParam;
|
||||||
let reload = false;
|
let reload = false;
|
||||||
const id = typeof params.id === 'number' ? params.id : `"${params.id}"`;
|
const id = typeof params.id === 'number' ? params.id : `${sw}${params.id}${sw}`;
|
||||||
|
|
||||||
if ([...NUMBER, ...FLOAT].includes(params.type))
|
if ([...NUMBER, ...FLOAT].includes(params.type))
|
||||||
escapedParam = params.content;
|
escapedParam = params.content;
|
||||||
@@ -147,7 +149,7 @@ export default (connections) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ([...BIT].includes(params.type)) {
|
else if (BIT.includes(params.type)) {
|
||||||
escapedParam = `b'${sqlEscaper(params.content)}'`;
|
escapedParam = `b'${sqlEscaper(params.content)}'`;
|
||||||
reload = true;
|
reload = true;
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,10 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
const queryLogger = sql => {
|
||||||
|
// Remove comments, newlines and multiple spaces
|
||||||
|
const escapedSql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' ');
|
||||||
|
console.log(escapedSql);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* As Simple As Possible Query Builder Core
|
* As Simple As Possible Query Builder Core
|
||||||
*
|
*
|
||||||
@@ -17,7 +23,7 @@ export class AntaresCore {
|
|||||||
this._poolSize = args.poolSize || false;
|
this._poolSize = args.poolSize || false;
|
||||||
this._connection = null;
|
this._connection = null;
|
||||||
this._ssh = null;
|
this._ssh = null;
|
||||||
this._logger = args.logger || console.log;
|
this._logger = args.logger || queryLogger;
|
||||||
|
|
||||||
this._queryDefaults = {
|
this._queryDefaults = {
|
||||||
schema: '',
|
schema: '',
|
||||||
|
@@ -2,13 +2,6 @@
|
|||||||
import { MySQLClient } from './clients/MySQLClient';
|
import { MySQLClient } from './clients/MySQLClient';
|
||||||
import { PostgreSQLClient } from './clients/PostgreSQLClient';
|
import { PostgreSQLClient } from './clients/PostgreSQLClient';
|
||||||
import { SQLiteClient } from './clients/SQLiteClient';
|
import { SQLiteClient } from './clients/SQLiteClient';
|
||||||
|
|
||||||
const queryLogger = sql => {
|
|
||||||
// Remove comments, newlines and multiple spaces
|
|
||||||
const escapedSql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' ');
|
|
||||||
console.log(escapedSql);
|
|
||||||
};
|
|
||||||
|
|
||||||
export class ClientsFactory {
|
export class ClientsFactory {
|
||||||
/**
|
/**
|
||||||
* Returns a database connection based on received args.
|
* Returns a database connection based on received args.
|
||||||
@@ -29,9 +22,7 @@ export class ClientsFactory {
|
|||||||
* @returns Database Connection
|
* @returns Database Connection
|
||||||
* @memberof ClientsFactory
|
* @memberof ClientsFactory
|
||||||
*/
|
*/
|
||||||
static getConnection (args) {
|
static getClient (args) {
|
||||||
args.logger = queryLogger;
|
|
||||||
|
|
||||||
switch (args.client) {
|
switch (args.client) {
|
||||||
case 'mysql':
|
case 'mysql':
|
||||||
case 'maria':
|
case 'maria':
|
||||||
|
@@ -9,6 +9,8 @@ export class MySQLClient extends AntaresCore {
|
|||||||
super(args);
|
super(args);
|
||||||
|
|
||||||
this._schema = null;
|
this._schema = null;
|
||||||
|
this._runningConnections = new Map();
|
||||||
|
this._connectionsToCommit = new Map();
|
||||||
|
|
||||||
this.types = {
|
this.types = {
|
||||||
0: 'DECIMAL',
|
0: 'DECIMAL',
|
||||||
@@ -99,10 +101,32 @@ export class MySQLClient extends AntaresCore {
|
|||||||
.filter(_type => _type.name === type.toUpperCase())[0];
|
.filter(_type => _type.name === type.toUpperCase())[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_reducer (acc, curr) {
|
||||||
|
const type = typeof curr;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'number':
|
||||||
|
case 'string':
|
||||||
|
return [...acc, curr];
|
||||||
|
case 'object':
|
||||||
|
if (Array.isArray(curr))
|
||||||
|
return [...acc, ...curr];
|
||||||
|
else {
|
||||||
|
const clausoles = [];
|
||||||
|
for (const key in curr)
|
||||||
|
clausoles.push(`\`${key}\` ${curr[key]}`);
|
||||||
|
|
||||||
|
return clausoles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
*
|
||||||
|
* @returns dbConfig
|
||||||
* @memberof MySQLClient
|
* @memberof MySQLClient
|
||||||
*/
|
*/
|
||||||
async connect () {
|
async getDbConfig () {
|
||||||
delete this._params.application_name;
|
delete this._params.application_name;
|
||||||
|
|
||||||
const dbConfig = {
|
const dbConfig = {
|
||||||
@@ -110,7 +134,9 @@ export class MySQLClient extends AntaresCore {
|
|||||||
port: this._params.port,
|
port: this._params.port,
|
||||||
user: this._params.user,
|
user: this._params.user,
|
||||||
password: this._params.password,
|
password: this._params.password,
|
||||||
ssl: null
|
ssl: null,
|
||||||
|
supportBigNumbers: true,
|
||||||
|
bigNumberStrings: true
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this._params.schema?.length) dbConfig.database = this._params.schema;
|
if (this._params.schema?.length) dbConfig.database = this._params.schema;
|
||||||
@@ -133,30 +159,17 @@ export class MySQLClient extends AntaresCore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._poolSize) {
|
return dbConfig;
|
||||||
this._connection = await mysql.createConnection(dbConfig);
|
}
|
||||||
|
|
||||||
if (this._params.readonly)
|
/**
|
||||||
await this.raw('SET SESSION TRANSACTION READ ONLY');
|
* @memberof MySQLClient
|
||||||
}
|
*/
|
||||||
else {
|
async connect () {
|
||||||
this._connection = mysql.createPool({
|
if (!this._poolSize)
|
||||||
...dbConfig,
|
this._connection = await this.getConnection();
|
||||||
connectionLimit: this._poolSize,
|
else
|
||||||
typeCast: (field, next) => {
|
this._connection = await this.getConnectionPool();
|
||||||
if (field.type === 'DATETIME')
|
|
||||||
return field.string();
|
|
||||||
else
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this._params.readonly) {
|
|
||||||
this._connection.on('connection', connection => {
|
|
||||||
connection.query('SET SESSION TRANSACTION READ ONLY');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -167,6 +180,64 @@ export class MySQLClient extends AntaresCore {
|
|||||||
if (this._ssh) this._ssh.close();
|
if (this._ssh) this._ssh.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getConnection () {
|
||||||
|
const dbConfig = await this.getDbConfig();
|
||||||
|
const connection = await mysql.createConnection({
|
||||||
|
...dbConfig,
|
||||||
|
typeCast: (field, next) => {
|
||||||
|
if (field.type === 'DATETIME')
|
||||||
|
return field.string();
|
||||||
|
else
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ANSI_QUOTES check
|
||||||
|
const [res] = await connection.query('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\'');
|
||||||
|
const sqlMode = res[0]?.Variable_name?.split(',');
|
||||||
|
const hasAnsiQuotes = sqlMode.includes('ANSI_QUOTES');
|
||||||
|
|
||||||
|
if (this._params.readonly)
|
||||||
|
await connection.query('SET SESSION TRANSACTION READ ONLY');
|
||||||
|
|
||||||
|
if (hasAnsiQuotes)
|
||||||
|
await connection.query(`SET SESSION sql_mode = "${sqlMode.filter(m => m !== 'ANSI_QUOTES').join(',')}"`);
|
||||||
|
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getConnectionPool () {
|
||||||
|
const dbConfig = await this.getDbConfig();
|
||||||
|
const connection = mysql.createPool({
|
||||||
|
...dbConfig,
|
||||||
|
connectionLimit: this._poolSize,
|
||||||
|
typeCast: (field, next) => {
|
||||||
|
if (field.type === 'DATETIME')
|
||||||
|
return field.string();
|
||||||
|
else
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ANSI_QUOTES check
|
||||||
|
const [res] = await connection.query('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\'');
|
||||||
|
const sqlMode = res[0]?.Variable_name?.split(',');
|
||||||
|
const hasAnsiQuotes = sqlMode.includes('ANSI_QUOTES');
|
||||||
|
|
||||||
|
if (hasAnsiQuotes)
|
||||||
|
await connection.query(`SET SESSION sql_mode = "${sqlMode.filter(m => m !== 'ANSI_QUOTES').join(',')}"`);
|
||||||
|
|
||||||
|
connection.on('connection', conn => {
|
||||||
|
if (this._params.readonly)
|
||||||
|
conn.query('SET SESSION TRANSACTION READ ONLY');
|
||||||
|
|
||||||
|
if (hasAnsiQuotes)
|
||||||
|
conn.query(`SET SESSION sql_mode = "${sqlMode.filter(m => m !== 'ANSI_QUOTES').join(',')}"`);
|
||||||
|
});
|
||||||
|
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes an USE query
|
* Executes an USE query
|
||||||
*
|
*
|
||||||
@@ -235,7 +306,7 @@ export class MySQLClient extends AntaresCore {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tableSize = table.Data_length + table.Index_length;
|
const tableSize = Number(table.Data_length) + Number(table.Index_length);
|
||||||
schemaSize += tableSize;
|
schemaSize += tableSize;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -376,7 +447,7 @@ export class MySQLClient extends AntaresCore {
|
|||||||
return acc;
|
return acc;
|
||||||
}, '')
|
}, '')
|
||||||
.replaceAll('\n', '')
|
.replaceAll('\n', '')
|
||||||
.split(',')
|
.split(/,\s?(?![^(]*\))/)
|
||||||
.map(f => {
|
.map(f => {
|
||||||
try {
|
try {
|
||||||
const fieldArr = f.trim().split(' ');
|
const fieldArr = f.trim().split(' ');
|
||||||
@@ -416,18 +487,25 @@ export class MySQLClient extends AntaresCore {
|
|||||||
|
|
||||||
return rows.map(field => {
|
return rows.map(field => {
|
||||||
let numLength = field.COLUMN_TYPE.match(/int\(([^)]+)\)/);
|
let numLength = field.COLUMN_TYPE.match(/int\(([^)]+)\)/);
|
||||||
numLength = numLength ? +numLength.pop() : null;
|
numLength = numLength ? +numLength.pop() : field.NUMERIC_PRECISION || null;
|
||||||
const enumValues = /(enum|set)/.test(field.COLUMN_TYPE)
|
const enumValues = /(enum|set)/.test(field.COLUMN_TYPE)
|
||||||
? field.COLUMN_TYPE.match(/\(([^)]+)\)/)[0].slice(1, -1)
|
? field.COLUMN_TYPE.match(/\(([^)]+)\)/)[0].slice(1, -1)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
const defaultValue = (remappedFields && remappedFields[field.COLUMN_NAME])
|
||||||
|
? remappedFields[field.COLUMN_NAME].default
|
||||||
|
: field.COLUMN_DEFAULT;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: field.COLUMN_NAME,
|
name: field.COLUMN_NAME,
|
||||||
key: field.COLUMN_KEY.toLowerCase(),
|
key: field.COLUMN_KEY.toLowerCase(),
|
||||||
type: remappedFields ? remappedFields[field.COLUMN_NAME].type : field.DATA_TYPE,
|
type: (remappedFields && remappedFields[field.COLUMN_NAME])
|
||||||
|
? remappedFields[field.COLUMN_NAME].type
|
||||||
|
: field.DATA_TYPE.toUpperCase(),
|
||||||
schema: field.TABLE_SCHEMA,
|
schema: field.TABLE_SCHEMA,
|
||||||
table: field.TABLE_NAME,
|
table: field.TABLE_NAME,
|
||||||
numPrecision: field.NUMERIC_PRECISION,
|
numPrecision: field.NUMERIC_PRECISION,
|
||||||
|
numScale: Number(field.NUMERIC_SCALE),
|
||||||
numLength,
|
numLength,
|
||||||
enumValues,
|
enumValues,
|
||||||
datePrecision: field.DATETIME_PRECISION,
|
datePrecision: field.DATETIME_PRECISION,
|
||||||
@@ -436,11 +514,14 @@ export class MySQLClient extends AntaresCore {
|
|||||||
unsigned: field.COLUMN_TYPE.includes('unsigned'),
|
unsigned: field.COLUMN_TYPE.includes('unsigned'),
|
||||||
zerofill: field.COLUMN_TYPE.includes('zerofill'),
|
zerofill: field.COLUMN_TYPE.includes('zerofill'),
|
||||||
order: field.ORDINAL_POSITION,
|
order: field.ORDINAL_POSITION,
|
||||||
default: remappedFields ? remappedFields[field.COLUMN_NAME].default : field.COLUMN_DEFAULT,
|
default: defaultValue,
|
||||||
charset: field.CHARACTER_SET_NAME,
|
charset: field.CHARACTER_SET_NAME,
|
||||||
collation: field.COLLATION_NAME,
|
collation: field.COLLATION_NAME,
|
||||||
autoIncrement: field.EXTRA.includes('auto_increment'),
|
autoIncrement: field.EXTRA.includes('auto_increment'),
|
||||||
onUpdate: field.EXTRA.toLowerCase().includes('on update') ? field.EXTRA.replace('on update', '') : '',
|
generated: field.EXTRA.toLowerCase().includes('generated'),
|
||||||
|
onUpdate: field.EXTRA.toLowerCase().includes('on update')
|
||||||
|
? field.EXTRA.substr(field.EXTRA.indexOf('on update') + 9, field.EXTRA.length).trim()
|
||||||
|
: '',
|
||||||
comment: field.COLUMN_COMMENT
|
comment: field.COLUMN_COMMENT
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -1137,6 +1218,26 @@ export class MySQLClient extends AntaresCore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SHOW VARIABLES LIKE %variable%
|
||||||
|
*
|
||||||
|
* @param {String} variable
|
||||||
|
* @param {'global'|'session'|null} level
|
||||||
|
* @returns {Object} variable
|
||||||
|
* @memberof MySQLClient
|
||||||
|
*/
|
||||||
|
async getVariable (variable, level) {
|
||||||
|
const sql = `SHOW${level ? ' ' + level.toUpperCase() : ''} VARIABLES LIKE '%${variable}%'`;
|
||||||
|
const results = await this.raw(sql);
|
||||||
|
|
||||||
|
if (results.rows.length) {
|
||||||
|
return {
|
||||||
|
name: results.rows[0].Variable_name,
|
||||||
|
value: results.rows[0].Value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SHOW ENGINES
|
* SHOW ENGINES
|
||||||
*
|
*
|
||||||
@@ -1208,10 +1309,56 @@ export class MySQLClient extends AntaresCore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} id
|
||||||
|
* @returns {Promise<null>}
|
||||||
|
*/
|
||||||
async killProcess (id) {
|
async killProcess (id) {
|
||||||
return await this.raw(`KILL ${id}`);
|
return await this.raw(`KILL ${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} tabUid
|
||||||
|
* @returns {Promise<null>}
|
||||||
|
*/
|
||||||
|
async killTabQuery (tabUid) {
|
||||||
|
const id = this._runningConnections.get(tabUid);
|
||||||
|
if (id)
|
||||||
|
return await this.killProcess(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} tabUid
|
||||||
|
* @returns {Promise<null>}
|
||||||
|
*/
|
||||||
|
async commitTab (tabUid) {
|
||||||
|
const connection = this._connectionsToCommit.get(tabUid);
|
||||||
|
if (connection)
|
||||||
|
return await connection.query('COMMIT');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} tabUid
|
||||||
|
* @returns {Promise<null>}
|
||||||
|
*/
|
||||||
|
async rollbackTab (tabUid) {
|
||||||
|
const connection = this._connectionsToCommit.get(tabUid);
|
||||||
|
if (connection)
|
||||||
|
return await connection.query('ROLLBACK');
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyConnectionToCommit (tabUid) {
|
||||||
|
const connection = this._connectionsToCommit.get(tabUid);
|
||||||
|
if (connection) {
|
||||||
|
connection.destroy();
|
||||||
|
this._connectionsToCommit.delete(tabUid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CREATE TABLE
|
* CREATE TABLE
|
||||||
*
|
*
|
||||||
@@ -1238,7 +1385,7 @@ export class MySQLClient extends AntaresCore {
|
|||||||
const length = typeInfo.length ? field.enumValues || field.numLength || field.charLength || field.datePrecision : false;
|
const length = typeInfo.length ? field.enumValues || field.numLength || field.charLength || field.datePrecision : false;
|
||||||
|
|
||||||
newColumns.push(`\`${field.name}\`
|
newColumns.push(`\`${field.name}\`
|
||||||
${field.type.toUpperCase()}${length ? `(${length})` : ''}
|
${field.type.toUpperCase()}${length ? `(${length}${field.numScale ? `,${field.numScale}` : ''})` : ''}
|
||||||
${field.unsigned ? 'UNSIGNED' : ''}
|
${field.unsigned ? 'UNSIGNED' : ''}
|
||||||
${field.zerofill ? 'ZEROFILL' : ''}
|
${field.zerofill ? 'ZEROFILL' : ''}
|
||||||
${field.nullable ? 'NULL' : 'NOT NULL'}
|
${field.nullable ? 'NULL' : 'NOT NULL'}
|
||||||
@@ -1307,7 +1454,7 @@ export class MySQLClient extends AntaresCore {
|
|||||||
const length = typeInfo.length ? addition.enumValues || addition.numLength || addition.charLength || addition.datePrecision : false;
|
const length = typeInfo.length ? addition.enumValues || addition.numLength || addition.charLength || addition.datePrecision : false;
|
||||||
|
|
||||||
alterColumns.push(`ADD COLUMN \`${addition.name}\`
|
alterColumns.push(`ADD COLUMN \`${addition.name}\`
|
||||||
${addition.type.toUpperCase()}${length ? `(${length})` : ''}
|
${addition.type.toUpperCase()}${length ? `(${length}${addition.numScale ? `,${addition.numScale}` : ''})` : ''}
|
||||||
${addition.unsigned ? 'UNSIGNED' : ''}
|
${addition.unsigned ? 'UNSIGNED' : ''}
|
||||||
${addition.zerofill ? 'ZEROFILL' : ''}
|
${addition.zerofill ? 'ZEROFILL' : ''}
|
||||||
${addition.nullable ? 'NULL' : 'NOT NULL'}
|
${addition.nullable ? 'NULL' : 'NOT NULL'}
|
||||||
@@ -1345,7 +1492,7 @@ export class MySQLClient extends AntaresCore {
|
|||||||
const length = typeInfo.length ? change.enumValues || change.numLength || change.charLength || change.datePrecision : false;
|
const length = typeInfo.length ? change.enumValues || change.numLength || change.charLength || change.datePrecision : false;
|
||||||
|
|
||||||
alterColumns.push(`CHANGE COLUMN \`${change.orgName}\` \`${change.name}\`
|
alterColumns.push(`CHANGE COLUMN \`${change.orgName}\` \`${change.name}\`
|
||||||
${change.type.toUpperCase()}${length ? `(${length})` : ''}
|
${change.type.toUpperCase()}${length ? `(${length}${change.numScale ? `,${change.numScale}` : ''})` : ''}
|
||||||
${change.unsigned ? 'UNSIGNED' : ''}
|
${change.unsigned ? 'UNSIGNED' : ''}
|
||||||
${change.zerofill ? 'ZEROFILL' : ''}
|
${change.zerofill ? 'ZEROFILL' : ''}
|
||||||
${change.nullable ? 'NULL' : 'NOT NULL'}
|
${change.nullable ? 'NULL' : 'NOT NULL'}
|
||||||
@@ -1476,7 +1623,7 @@ export class MySQLClient extends AntaresCore {
|
|||||||
let insertRaw = '';
|
let insertRaw = '';
|
||||||
|
|
||||||
if (this._query.insert.length) {
|
if (this._query.insert.length) {
|
||||||
const fieldsList = Object.keys(this._query.insert[0]);
|
const fieldsList = Object.keys(this._query.insert[0]).map(col => '`' + col + '`');
|
||||||
const rowsList = this._query.insert.map(el => `(${Object.values(el).join(', ')})`);
|
const rowsList = this._query.insert.map(el => `(${Object.values(el).join(', ')})`);
|
||||||
|
|
||||||
insertRaw = `(${fieldsList.join(', ')}) VALUES ${rowsList.join(', ')} `;
|
insertRaw = `(${fieldsList.join(', ')}) VALUES ${rowsList.join(', ')} `;
|
||||||
@@ -1516,6 +1663,7 @@ export class MySQLClient extends AntaresCore {
|
|||||||
details: false,
|
details: false,
|
||||||
split: true,
|
split: true,
|
||||||
comments: true,
|
comments: true,
|
||||||
|
autocommit: true,
|
||||||
...args
|
...args
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1530,8 +1678,24 @@ export class MySQLClient extends AntaresCore {
|
|||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.map(q => q.trim())
|
.map(q => q.trim())
|
||||||
: [sql];
|
: [sql];
|
||||||
|
|
||||||
|
let connection;
|
||||||
const isPool = typeof this._connection.getConnection === 'function';
|
const isPool = typeof this._connection.getConnection === 'function';
|
||||||
const connection = isPool ? await this._connection.getConnection() : this._connection;
|
|
||||||
|
if (!args.autocommit && args.tabUid) { // autocommit OFF
|
||||||
|
if (this._connectionsToCommit.has(args.tabUid))
|
||||||
|
connection = this._connectionsToCommit.get(args.tabUid);
|
||||||
|
else {
|
||||||
|
connection = await this.getConnection();
|
||||||
|
await connection.query('SET SESSION autocommit=0');
|
||||||
|
this._connectionsToCommit.set(args.tabUid, connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else// autocommit ON
|
||||||
|
connection = isPool ? await this._connection.getConnection() : this._connection;
|
||||||
|
|
||||||
|
if (args.tabUid && isPool)
|
||||||
|
this._runningConnections.set(args.tabUid, connection.connection.connectionId);
|
||||||
|
|
||||||
if (args.schema)
|
if (args.schema)
|
||||||
await connection.query(`USE \`${args.schema}\``);
|
await connection.query(`USE \`${args.schema}\``);
|
||||||
@@ -1593,7 +1757,10 @@ export class MySQLClient extends AntaresCore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
if (isPool) connection.release();
|
if (isPool && args.autocommit) {
|
||||||
|
connection.release();
|
||||||
|
this._runningConnections.delete(args.tabUid);
|
||||||
|
}
|
||||||
reject(err);
|
reject(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1602,7 +1769,10 @@ export class MySQLClient extends AntaresCore {
|
|||||||
keysArr = keysArr ? [...keysArr, ...response] : response;
|
keysArr = keysArr ? [...keysArr, ...response] : response;
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
if (isPool) connection.release();
|
if (isPool && args.autocommit) {
|
||||||
|
connection.release();
|
||||||
|
this._runningConnections.delete(args.tabUid);
|
||||||
|
}
|
||||||
reject(err);
|
reject(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1617,7 +1787,10 @@ export class MySQLClient extends AntaresCore {
|
|||||||
keys: keysArr
|
keys: keysArr
|
||||||
});
|
});
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
if (isPool) connection.release();
|
if (isPool && args.autocommit) {
|
||||||
|
connection.release();
|
||||||
|
this._runningConnections.delete(args.tabUid);
|
||||||
|
}
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1625,7 +1798,10 @@ export class MySQLClient extends AntaresCore {
|
|||||||
resultsArr.push({ rows, report, fields, keys, duration });
|
resultsArr.push({ rows, report, fields, keys, duration });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPool) connection.release();
|
if (isPool && args.autocommit) {
|
||||||
|
connection.release();
|
||||||
|
this._runningConnections.delete(args.tabUid);
|
||||||
|
}
|
||||||
|
|
||||||
return resultsArr.length === 1 ? resultsArr[0] : resultsArr;
|
return resultsArr.length === 1 ? resultsArr[0] : resultsArr;
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,6 @@ function pgToString (value) {
|
|||||||
return value.toString();
|
return value.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
types.setTypeParser(20, a => parseInt(a));// bigint string to number
|
|
||||||
types.setTypeParser(1082, pgToString); // date
|
types.setTypeParser(1082, pgToString); // date
|
||||||
types.setTypeParser(1083, pgToString); // time
|
types.setTypeParser(1083, pgToString); // time
|
||||||
types.setTypeParser(1114, pgToString); // timestamp
|
types.setTypeParser(1114, pgToString); // timestamp
|
||||||
@@ -21,6 +20,8 @@ export class PostgreSQLClient extends AntaresCore {
|
|||||||
super(args);
|
super(args);
|
||||||
|
|
||||||
this._schema = null;
|
this._schema = null;
|
||||||
|
this._runningConnections = new Map();
|
||||||
|
this._connectionsToCommit = new Map();
|
||||||
|
|
||||||
this.types = {};
|
this.types = {};
|
||||||
for (const key in types.builtins)
|
for (const key in types.builtins)
|
||||||
@@ -70,9 +71,11 @@ export class PostgreSQLClient extends AntaresCore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
*
|
||||||
|
* @returns dbConfig
|
||||||
* @memberof PostgreSQLClient
|
* @memberof PostgreSQLClient
|
||||||
*/
|
*/
|
||||||
async connect () {
|
async getDbConfig () {
|
||||||
const dbConfig = {
|
const dbConfig = {
|
||||||
host: this._params.host,
|
host: this._params.host,
|
||||||
port: this._params.port,
|
port: this._params.port,
|
||||||
@@ -101,24 +104,43 @@ export class PostgreSQLClient extends AntaresCore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._poolSize) {
|
return dbConfig;
|
||||||
const client = new Client(dbConfig);
|
}
|
||||||
await client.connect();
|
|
||||||
this._connection = client;
|
|
||||||
|
|
||||||
if (this._params.readonly)
|
/**
|
||||||
await this.raw('SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY');
|
* @memberof PostgreSQLClient
|
||||||
}
|
*/
|
||||||
else {
|
async connect () {
|
||||||
const pool = new Pool({ ...dbConfig, max: this._poolSize });
|
if (!this._poolSize)
|
||||||
this._connection = pool;
|
this._connection = await this.getConnection();
|
||||||
|
else
|
||||||
|
this._connection = await this.getConnectionPool();
|
||||||
|
}
|
||||||
|
|
||||||
if (this._params.readonly) {
|
async getConnection () {
|
||||||
this._connection.on('connect', connection => {
|
const dbConfig = await this.getDbConfig();
|
||||||
connection.query('SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY');
|
const client = new Client(dbConfig);
|
||||||
});
|
await client.connect();
|
||||||
}
|
const connection = client;
|
||||||
|
|
||||||
|
if (this._params.readonly)
|
||||||
|
await connection.query('SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY');
|
||||||
|
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getConnectionPool () {
|
||||||
|
const dbConfig = await this.getDbConfig();
|
||||||
|
const pool = new Pool({ ...dbConfig, max: this._poolSize });
|
||||||
|
const connection = pool;
|
||||||
|
|
||||||
|
if (this._params.readonly) {
|
||||||
|
connection.on('connect', conn => {
|
||||||
|
conn.query('SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -130,15 +152,23 @@ export class PostgreSQLClient extends AntaresCore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes an "USE" query
|
* Executes an 'SET search_path TO "${schema}"' query
|
||||||
*
|
*
|
||||||
* @param {String} schema
|
* @param {String} schema
|
||||||
|
* @param {Object?} connection optional
|
||||||
* @memberof PostgreSQLClient
|
* @memberof PostgreSQLClient
|
||||||
*/
|
*/
|
||||||
use (schema) {
|
use (schema, connection) {
|
||||||
this._schema = schema;
|
this._schema = schema;
|
||||||
if (schema)
|
|
||||||
return this.raw(`SET search_path TO "${schema}"`);
|
if (schema) {
|
||||||
|
const sql = `SET search_path TO "${schema}"`;
|
||||||
|
|
||||||
|
if (connection === undefined)
|
||||||
|
return this.raw(sql);
|
||||||
|
else
|
||||||
|
return connection.query(sql);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -208,7 +238,7 @@ export class PostgreSQLClient extends AntaresCore {
|
|||||||
if (schemas.has(db.database)) {
|
if (schemas.has(db.database)) {
|
||||||
// TABLES
|
// TABLES
|
||||||
const remappedTables = tablesArr.filter(table => table.Db === db.database).map(table => {
|
const remappedTables = tablesArr.filter(table => table.Db === db.database).map(table => {
|
||||||
const tableSize = +table.data_length + table.index_length;
|
const tableSize = Number(table.data_length) + Number(table.index_length);
|
||||||
schemaSize += tableSize;
|
schemaSize += tableSize;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -317,6 +347,7 @@ export class PostgreSQLClient extends AntaresCore {
|
|||||||
isArray,
|
isArray,
|
||||||
schema: field.table_schema,
|
schema: field.table_schema,
|
||||||
table: field.table_name,
|
table: field.table_name,
|
||||||
|
numScale: field.numeric_scale,
|
||||||
numPrecision: field.numeric_precision,
|
numPrecision: field.numeric_precision,
|
||||||
datePrecision: field.datetime_precision,
|
datePrecision: field.datetime_precision,
|
||||||
charLength: field.character_maximum_length,
|
charLength: field.character_maximum_length,
|
||||||
@@ -1088,10 +1119,60 @@ export class PostgreSQLClient extends AntaresCore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} id
|
||||||
|
* @returns {Promise<null>}
|
||||||
|
*/
|
||||||
async killProcess (id) {
|
async killProcess (id) {
|
||||||
return await this.raw(`SELECT pg_terminate_backend(${id})`);
|
return await this.raw(`SELECT pg_terminate_backend(${id})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} tabUid
|
||||||
|
* @returns {Promise<null>}
|
||||||
|
*/
|
||||||
|
async killTabQuery (tabUid) {
|
||||||
|
const id = this._runningConnections.get(tabUid);
|
||||||
|
if (id)
|
||||||
|
return await this.raw(`SELECT pg_cancel_backend(${id})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} tabUid
|
||||||
|
* @returns {Promise<null>}
|
||||||
|
*/
|
||||||
|
async commitTab (tabUid) {
|
||||||
|
const connection = this._connectionsToCommit.get(tabUid);
|
||||||
|
if (connection) {
|
||||||
|
await connection.query('COMMIT');
|
||||||
|
return this.destroyConnectionToCommit(tabUid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} tabUid
|
||||||
|
* @returns {Promise<null>}
|
||||||
|
*/
|
||||||
|
async rollbackTab (tabUid) {
|
||||||
|
const connection = this._connectionsToCommit.get(tabUid);
|
||||||
|
if (connection) {
|
||||||
|
await connection.query('ROLLBACK');
|
||||||
|
return this.destroyConnectionToCommit(tabUid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyConnectionToCommit (tabUid) {
|
||||||
|
const connection = this._connectionsToCommit.get(tabUid);
|
||||||
|
if (connection) {
|
||||||
|
connection.end();
|
||||||
|
this._connectionsToCommit.delete(tabUid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CREATE TABLE
|
* CREATE TABLE
|
||||||
*
|
*
|
||||||
@@ -1119,7 +1200,7 @@ export class PostgreSQLClient extends AntaresCore {
|
|||||||
const length = typeInfo.length ? field.enumValues || field.numLength || field.charLength || field.datePrecision : false;
|
const length = typeInfo.length ? field.enumValues || field.numLength || field.charLength || field.datePrecision : false;
|
||||||
|
|
||||||
newColumns.push(`"${field.name}"
|
newColumns.push(`"${field.name}"
|
||||||
${field.type.toUpperCase()}${length ? `(${length})` : ''}
|
${field.type.toUpperCase()}${length ? `(${length}${field.numScale !== null ? `,${field.numScale}` : ''})` : ''}
|
||||||
${field.unsigned ? 'UNSIGNED' : ''}
|
${field.unsigned ? 'UNSIGNED' : ''}
|
||||||
${field.zerofill ? 'ZEROFILL' : ''}
|
${field.zerofill ? 'ZEROFILL' : ''}
|
||||||
${field.nullable ? 'NULL' : 'NOT NULL'}
|
${field.nullable ? 'NULL' : 'NOT NULL'}
|
||||||
@@ -1183,7 +1264,7 @@ export class PostgreSQLClient extends AntaresCore {
|
|||||||
const length = typeInfo.length ? addition.numLength || addition.charLength || addition.datePrecision : false;
|
const length = typeInfo.length ? addition.numLength || addition.charLength || addition.datePrecision : false;
|
||||||
|
|
||||||
alterColumns.push(`ADD COLUMN "${addition.name}"
|
alterColumns.push(`ADD COLUMN "${addition.name}"
|
||||||
${addition.type.toUpperCase()}${length ? `(${length})` : ''}${addition.isArray ? '[]' : ''}
|
${addition.type.toUpperCase()}${length ? `(${length}${addition.numScale !== null ? `,${addition.numScale}` : ''})` : ''}${addition.isArray ? '[]' : ''}
|
||||||
${addition.unsigned ? 'UNSIGNED' : ''}
|
${addition.unsigned ? 'UNSIGNED' : ''}
|
||||||
${addition.zerofill ? 'ZEROFILL' : ''}
|
${addition.zerofill ? 'ZEROFILL' : ''}
|
||||||
${addition.nullable ? 'NULL' : 'NOT NULL'}
|
${addition.nullable ? 'NULL' : 'NOT NULL'}
|
||||||
@@ -1229,7 +1310,7 @@ export class PostgreSQLClient extends AntaresCore {
|
|||||||
localType = change.type.toLowerCase();
|
localType = change.type.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
alterColumns.push(`ALTER COLUMN "${change.name}" TYPE ${localType}${length ? `(${length})` : ''}${change.isArray ? '[]' : ''} USING "${change.name}"::${localType}`);
|
alterColumns.push(`ALTER COLUMN "${change.name}" TYPE ${localType}${length ? `(${length}${change.numScale ? `,${change.numScale}` : ''})` : ''}${change.isArray ? '[]' : ''} USING "${change.name}"::${localType}`);
|
||||||
alterColumns.push(`ALTER COLUMN "${change.name}" ${change.nullable ? 'DROP NOT NULL' : 'SET NOT NULL'}`);
|
alterColumns.push(`ALTER COLUMN "${change.name}" ${change.nullable ? 'DROP NOT NULL' : 'SET NOT NULL'}`);
|
||||||
alterColumns.push(`ALTER COLUMN "${change.name}" ${change.default ? `SET DEFAULT ${change.default}` : 'DROP DEFAULT'}`);
|
alterColumns.push(`ALTER COLUMN "${change.name}" ${change.default ? `SET DEFAULT ${change.default}` : 'DROP DEFAULT'}`);
|
||||||
|
|
||||||
@@ -1396,17 +1477,17 @@ export class PostgreSQLClient extends AntaresCore {
|
|||||||
* @memberof PostgreSQLClient
|
* @memberof PostgreSQLClient
|
||||||
*/
|
*/
|
||||||
async raw (sql, args) {
|
async raw (sql, args) {
|
||||||
|
if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder
|
||||||
|
|
||||||
args = {
|
args = {
|
||||||
nest: false,
|
nest: false,
|
||||||
details: false,
|
details: false,
|
||||||
split: true,
|
split: true,
|
||||||
comments: true,
|
comments: true,
|
||||||
|
autocommit: true,
|
||||||
...args
|
...args
|
||||||
};
|
};
|
||||||
|
|
||||||
if (args.schema && args.schema !== 'public')
|
|
||||||
await this.use(args.schema);
|
|
||||||
|
|
||||||
if (!args.comments)
|
if (!args.comments)
|
||||||
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments
|
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments
|
||||||
|
|
||||||
@@ -1418,7 +1499,26 @@ export class PostgreSQLClient extends AntaresCore {
|
|||||||
.map(q => q.trim())
|
.map(q => q.trim())
|
||||||
: [sql];
|
: [sql];
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder
|
let connection;
|
||||||
|
const isPool = this._connection instanceof Pool;
|
||||||
|
|
||||||
|
if (!args.autocommit && args.tabUid) { // autocommit OFF
|
||||||
|
if (this._connectionsToCommit.has(args.tabUid))
|
||||||
|
connection = this._connectionsToCommit.get(args.tabUid);
|
||||||
|
else {
|
||||||
|
connection = await this.getConnection();
|
||||||
|
await connection.query('START TRANSACTION');
|
||||||
|
this._connectionsToCommit.set(args.tabUid, connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else// autocommit ON
|
||||||
|
connection = isPool ? await this._connection.connect() : this._connection;
|
||||||
|
|
||||||
|
if (args.tabUid && isPool)
|
||||||
|
this._runningConnections.set(args.tabUid, connection.processID);
|
||||||
|
|
||||||
|
if (args.schema && args.schema !== 'public')
|
||||||
|
await this.use(args.schema, connection);
|
||||||
|
|
||||||
for (const query of queries) {
|
for (const query of queries) {
|
||||||
if (!query) continue;
|
if (!query) continue;
|
||||||
@@ -1428,15 +1528,12 @@ export class PostgreSQLClient extends AntaresCore {
|
|||||||
let keysArr = [];
|
let keysArr = [];
|
||||||
|
|
||||||
const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => {
|
const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => {
|
||||||
this._connection.query({
|
(async () => {
|
||||||
rowMode: args.nest ? 'array' : null,
|
try {
|
||||||
text: query
|
const res = await connection.query({ rowMode: args.nest ? 'array' : null, text: query });
|
||||||
}, async (err, res) => {
|
|
||||||
timeStop = new Date();
|
timeStop = new Date();
|
||||||
|
|
||||||
if (err)
|
|
||||||
reject(err);
|
|
||||||
else {
|
|
||||||
let ast;
|
let ast;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -1525,6 +1622,10 @@ export class PostgreSQLClient extends AntaresCore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
|
if (isPool && args.autocommit) {
|
||||||
|
connection.release();
|
||||||
|
this._runningConnections.delete(args.tabUid);
|
||||||
|
}
|
||||||
reject(err);
|
reject(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1533,6 +1634,10 @@ export class PostgreSQLClient extends AntaresCore {
|
|||||||
keysArr = keysArr ? [...keysArr, ...response] : response;
|
keysArr = keysArr ? [...keysArr, ...response] : response;
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
|
if (isPool && args.autocommit) {
|
||||||
|
connection.release();
|
||||||
|
this._runningConnections.delete(args.tabUid);
|
||||||
|
}
|
||||||
reject(err);
|
reject(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1547,12 +1652,24 @@ export class PostgreSQLClient extends AntaresCore {
|
|||||||
keys: keysArr
|
keys: keysArr
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
catch (err) {
|
||||||
|
if (isPool && args.autocommit) {
|
||||||
|
connection.release();
|
||||||
|
this._runningConnections.delete(args.tabUid);
|
||||||
|
}
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
})();
|
||||||
});
|
});
|
||||||
|
|
||||||
resultsArr.push({ rows, report, fields, keys, duration });
|
resultsArr.push({ rows, report, fields, keys, duration });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isPool && args.autocommit) {
|
||||||
|
connection.release();
|
||||||
|
this._runningConnections.delete(args.tabUid);
|
||||||
|
}
|
||||||
|
|
||||||
return resultsArr.length === 1 ? resultsArr[0] : resultsArr;
|
return resultsArr.length === 1 ? resultsArr[0] : resultsArr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import sqlite from 'better-sqlite3';
|
import sqlite from 'better-sqlite3';
|
||||||
import { AntaresCore } from '../AntaresCore';
|
import { AntaresCore } from '../AntaresCore';
|
||||||
import dataTypes from 'common/data-types/mysql';
|
import dataTypes from 'common/data-types/sqlite';
|
||||||
import { NUMBER, FLOAT, TIME, DATETIME } from 'common/fieldTypes';
|
import { NUMBER, FLOAT, TIME, DATETIME } from 'common/fieldTypes';
|
||||||
|
|
||||||
export class SQLiteClient extends AntaresCore {
|
export class SQLiteClient extends AntaresCore {
|
||||||
@@ -9,6 +9,7 @@ export class SQLiteClient extends AntaresCore {
|
|||||||
super(args);
|
super(args);
|
||||||
|
|
||||||
this._schema = null;
|
this._schema = null;
|
||||||
|
this._connectionsToCommit = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
_getTypeInfo (type) {
|
_getTypeInfo (type) {
|
||||||
@@ -21,7 +22,11 @@ export class SQLiteClient extends AntaresCore {
|
|||||||
* @memberof SQLiteClient
|
* @memberof SQLiteClient
|
||||||
*/
|
*/
|
||||||
async connect () {
|
async connect () {
|
||||||
this._connection = sqlite(this._params.databasePath, {
|
this._connection = this.getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
getConnection () {
|
||||||
|
return sqlite(this._params.databasePath, {
|
||||||
fileMustExist: true,
|
fileMustExist: true,
|
||||||
readonly: this._params.readonly
|
readonly: this._params.readonly
|
||||||
});
|
});
|
||||||
@@ -158,7 +163,7 @@ export class SQLiteClient extends AntaresCore {
|
|||||||
nullable: !field.notnull,
|
nullable: !field.notnull,
|
||||||
unsigned: null,
|
unsigned: null,
|
||||||
zerofill: null,
|
zerofill: null,
|
||||||
order: field.cid + 1,
|
order: typeof field.cid === 'string' ? +field.cid + 1 : field.cid + 1,
|
||||||
default: field.dflt_value,
|
default: field.dflt_value,
|
||||||
charset: null,
|
charset: null,
|
||||||
collation: null,
|
collation: null,
|
||||||
@@ -446,6 +451,40 @@ export class SQLiteClient extends AntaresCore {
|
|||||||
|
|
||||||
async killProcess () {}
|
async killProcess () {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} tabUid
|
||||||
|
* @returns {Promise<null>}
|
||||||
|
*/
|
||||||
|
async commitTab (tabUid) {
|
||||||
|
const connection = this._connectionsToCommit.get(tabUid);
|
||||||
|
if (connection) {
|
||||||
|
connection.prepare('COMMIT').run();
|
||||||
|
return this.destroyConnectionToCommit(tabUid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} tabUid
|
||||||
|
* @returns {Promise<null>}
|
||||||
|
*/
|
||||||
|
async rollbackTab (tabUid) {
|
||||||
|
const connection = this._connectionsToCommit.get(tabUid);
|
||||||
|
if (connection) {
|
||||||
|
connection.prepare('ROLLBACK').run();
|
||||||
|
return this.destroyConnectionToCommit(tabUid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyConnectionToCommit (tabUid) {
|
||||||
|
const connection = this._connectionsToCommit.get(tabUid);
|
||||||
|
if (connection) {
|
||||||
|
connection.close();
|
||||||
|
this._connectionsToCommit.delete(tabUid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CREATE TABLE
|
* CREATE TABLE
|
||||||
*
|
*
|
||||||
@@ -666,6 +705,7 @@ export class SQLiteClient extends AntaresCore {
|
|||||||
details: false,
|
details: false,
|
||||||
split: true,
|
split: true,
|
||||||
comments: true,
|
comments: true,
|
||||||
|
autocommit: true,
|
||||||
...args
|
...args
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -679,7 +719,20 @@ export class SQLiteClient extends AntaresCore {
|
|||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.map(q => q.trim())
|
.map(q => q.trim())
|
||||||
: [sql];
|
: [sql];
|
||||||
const connection = this._connection;
|
|
||||||
|
let connection;
|
||||||
|
|
||||||
|
if (!args.autocommit && args.tabUid) { // autocommit OFF
|
||||||
|
if (this._connectionsToCommit.has(args.tabUid))
|
||||||
|
connection = this._connectionsToCommit.get(args.tabUid);
|
||||||
|
else {
|
||||||
|
connection = this.getConnection();
|
||||||
|
connection.prepare('BEGIN TRANSACTION').run();
|
||||||
|
this._connectionsToCommit.set(args.tabUid, connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else// autocommit ON
|
||||||
|
connection = this._connection;
|
||||||
|
|
||||||
for (const query of queries) {
|
for (const query of queries) {
|
||||||
if (!query) continue;
|
if (!query) continue;
|
||||||
@@ -732,7 +785,7 @@ export class SQLiteClient extends AntaresCore {
|
|||||||
|
|
||||||
if ([...TIME, ...DATETIME].includes(parsedType)) {
|
if ([...TIME, ...DATETIME].includes(parsedType)) {
|
||||||
const firstNotNull = queryResult.find(res => res[field.name] !== null);
|
const firstNotNull = queryResult.find(res => res[field.name] !== null);
|
||||||
if (firstNotNull[field.name].includes('.'))
|
if (firstNotNull && firstNotNull[field.name].includes('.'))
|
||||||
length = firstNotNull[field.name].split('.').pop().length;
|
length = firstNotNull[field.name].split('.').pop().length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
85
src/main/libs/exporters/BaseExporter.js
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import { createGzip } from 'zlib';
|
||||||
|
import path from 'path';
|
||||||
|
import EventEmitter from 'events';
|
||||||
|
|
||||||
|
export class BaseExporter extends EventEmitter {
|
||||||
|
constructor (tables, options) {
|
||||||
|
super();
|
||||||
|
this._tables = tables;
|
||||||
|
this._options = options;
|
||||||
|
this._isCancelled = false;
|
||||||
|
this._outputFileStream = fs.createWriteStream(this._options.outputFile, { flags: 'w' });
|
||||||
|
this._processedStream = null;
|
||||||
|
this._state = {};
|
||||||
|
|
||||||
|
if (this._options.outputFormat === 'sql.zip') {
|
||||||
|
const outputZipStream = createGzip();
|
||||||
|
outputZipStream.pipe(this._outputFileStream);
|
||||||
|
this._processedStream = outputZipStream;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this._processedStream = this._outputFileStream;
|
||||||
|
|
||||||
|
this._processedStream.once('error', err => {
|
||||||
|
this._isCancelled = true;
|
||||||
|
this.emit('error', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run () {
|
||||||
|
try {
|
||||||
|
this.emit('start', this);
|
||||||
|
await this.dump();
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.emit('error', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this._processedStream.end();
|
||||||
|
this.emit('end');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get isCancelled () {
|
||||||
|
return this._isCancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
get outputFile () {
|
||||||
|
return this._options.outputFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputFileExists () {
|
||||||
|
return fs.existsSync(this._options.outputFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel () {
|
||||||
|
this._isCancelled = true;
|
||||||
|
this.emit('cancel');
|
||||||
|
this.emitUpdate({ op: 'cancelling' });
|
||||||
|
}
|
||||||
|
|
||||||
|
emitUpdate (state) {
|
||||||
|
this.emit('progress', { ...this._state, ...state });
|
||||||
|
}
|
||||||
|
|
||||||
|
writeString (data) {
|
||||||
|
if (this._isCancelled) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.accessSync(this._options.outputFile);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this._isCancelled = true;
|
||||||
|
|
||||||
|
const fileName = path.basename(this._options.outputFile);
|
||||||
|
this.emit('error', `The file ${fileName} is not accessible`);
|
||||||
|
}
|
||||||
|
this._processedStream.write(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
dump () {
|
||||||
|
throw new Error('Exporter must implement the "dump" method');
|
||||||
|
}
|
||||||
|
}
|
412
src/main/libs/exporters/sql/MysqlExporter.js
Normal file
@@ -0,0 +1,412 @@
|
|||||||
|
import { SqlExporter } from './SqlExporter';
|
||||||
|
import { BLOB, BIT, DATE, DATETIME, FLOAT, SPATIAL, IS_MULTI_SPATIAL, NUMBER } from 'common/fieldTypes';
|
||||||
|
import hexToBinary from 'common/libs/hexToBinary';
|
||||||
|
import { getArrayDepth } from 'common/libs/getArrayDepth';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { lineString, point, polygon } from '@turf/helpers';
|
||||||
|
|
||||||
|
export default class MysqlExporter extends SqlExporter {
|
||||||
|
async getSqlHeader () {
|
||||||
|
let dump = await super.getSqlHeader();
|
||||||
|
dump += `
|
||||||
|
|
||||||
|
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||||
|
SET NAMES utf8mb4;
|
||||||
|
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||||
|
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||||
|
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;`;
|
||||||
|
|
||||||
|
return dump;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFooter () {
|
||||||
|
const footer = await super.getFooter();
|
||||||
|
|
||||||
|
return `/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||||
|
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||||
|
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||||
|
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||||
|
|
||||||
|
${footer}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCreateTable (tableName) {
|
||||||
|
const { rows } = await this._client.raw(
|
||||||
|
`SHOW CREATE TABLE \`${this.schemaName}\`.\`${tableName}\``
|
||||||
|
);
|
||||||
|
|
||||||
|
if (rows.length !== 1) return '';
|
||||||
|
|
||||||
|
const col = 'Create View' in rows[0] ? 'Create View' : 'Create Table';
|
||||||
|
|
||||||
|
return rows[0][col] + ';';
|
||||||
|
}
|
||||||
|
|
||||||
|
getDropTable (tableName) {
|
||||||
|
return `DROP TABLE IF EXISTS \`${tableName}\`;`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async * getTableInsert (tableName) {
|
||||||
|
let rowCount = 0;
|
||||||
|
let sqlStr = '';
|
||||||
|
|
||||||
|
const countResults = await this._client.raw(`SELECT COUNT(1) as count FROM \`${this.schemaName}\`.\`${tableName}\``);
|
||||||
|
if (countResults.rows.length === 1) rowCount = countResults.rows[0].count;
|
||||||
|
|
||||||
|
if (rowCount > 0) {
|
||||||
|
let queryLength = 0;
|
||||||
|
let rowsWritten = 0;
|
||||||
|
let rowIndex = 0;
|
||||||
|
const { sqlInsertDivider, sqlInsertAfter } = this._options;
|
||||||
|
const columns = await this._client.getTableColumns({
|
||||||
|
table: tableName,
|
||||||
|
schema: this.schemaName
|
||||||
|
});
|
||||||
|
|
||||||
|
const notGeneratedColumns = columns.filter(col => !col.generated);
|
||||||
|
const columnNames = notGeneratedColumns.map(col => '`' + col.name + '`');
|
||||||
|
const insertStmt = `INSERT INTO \`${tableName}\` (${columnNames.join(
|
||||||
|
', '
|
||||||
|
)}) VALUES`;
|
||||||
|
|
||||||
|
sqlStr += `LOCK TABLES \`${tableName}\` WRITE;\n`;
|
||||||
|
sqlStr += `/*!40000 ALTER TABLE \`${tableName}\` DISABLE KEYS */;`;
|
||||||
|
sqlStr += '\n\n';
|
||||||
|
yield sqlStr;
|
||||||
|
|
||||||
|
yield insertStmt;
|
||||||
|
|
||||||
|
const stream = await this._queryStream(
|
||||||
|
`SELECT ${columnNames.join(', ')} FROM \`${this.schemaName}\`.\`${tableName}\``
|
||||||
|
);
|
||||||
|
|
||||||
|
for await (const row of stream) {
|
||||||
|
if (this.isCancelled) {
|
||||||
|
stream.destroy();
|
||||||
|
yield null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sqlInsertString = '';
|
||||||
|
|
||||||
|
if (
|
||||||
|
(sqlInsertDivider === 'bytes' && queryLength >= sqlInsertAfter * 1024) ||
|
||||||
|
(sqlInsertDivider === 'rows' && rowsWritten === sqlInsertAfter)
|
||||||
|
) {
|
||||||
|
sqlInsertString += `;\n${insertStmt}\n\t(`;
|
||||||
|
queryLength = 0;
|
||||||
|
rowsWritten = 0;
|
||||||
|
}
|
||||||
|
else if (parseInt(rowIndex) === 0) sqlInsertString += '\n\t(';
|
||||||
|
else sqlInsertString += ',\n\t(';
|
||||||
|
|
||||||
|
for (const i in notGeneratedColumns) {
|
||||||
|
const column = notGeneratedColumns[i];
|
||||||
|
const val = row[column.name];
|
||||||
|
|
||||||
|
if (val === null) sqlInsertString += 'NULL';
|
||||||
|
else if (DATE.includes(column.type)) {
|
||||||
|
sqlInsertString += moment(val).isValid()
|
||||||
|
? this.escapeAndQuote(moment(val).format('YYYY-MM-DD'))
|
||||||
|
: val;
|
||||||
|
}
|
||||||
|
else if (DATETIME.includes(column.type)) {
|
||||||
|
let datePrecision = '';
|
||||||
|
for (let i = 0; i < column.precision; i++)
|
||||||
|
datePrecision += i === 0 ? '.S' : 'S';
|
||||||
|
|
||||||
|
sqlInsertString += moment(val).isValid()
|
||||||
|
? this.escapeAndQuote(moment(val).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`))
|
||||||
|
: this.escapeAndQuote(val);
|
||||||
|
}
|
||||||
|
else if (BIT.includes(column.type))
|
||||||
|
sqlInsertString += `b'${hexToBinary(Buffer.from(val).toString('hex'))}'`;
|
||||||
|
else if (BLOB.includes(column.type))
|
||||||
|
sqlInsertString += `X'${val.toString('hex').toUpperCase()}'`;
|
||||||
|
else if (NUMBER.includes(column.type))
|
||||||
|
sqlInsertString += val;
|
||||||
|
else if (FLOAT.includes(column.type))
|
||||||
|
sqlInsertString += parseFloat(val);
|
||||||
|
else if (SPATIAL.includes(column.type)) {
|
||||||
|
let geoJson;
|
||||||
|
if (IS_MULTI_SPATIAL.includes(column.type)) {
|
||||||
|
const features = [];
|
||||||
|
for (const element of val)
|
||||||
|
features.push(this.getMarkers(element));
|
||||||
|
|
||||||
|
geoJson = {
|
||||||
|
type: 'FeatureCollection',
|
||||||
|
features
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
geoJson = this._getGeoJSON(val);
|
||||||
|
|
||||||
|
sqlInsertString += `ST_GeomFromGeoJSON('${JSON.stringify(geoJson)}')`;
|
||||||
|
}
|
||||||
|
else if (val === '') sqlInsertString += '\'\'';
|
||||||
|
else {
|
||||||
|
sqlInsertString += typeof val === 'string'
|
||||||
|
? this.escapeAndQuote(val)
|
||||||
|
: typeof val === 'object'
|
||||||
|
? this.escapeAndQuote(JSON.stringify(val))
|
||||||
|
: val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parseInt(i) !== notGeneratedColumns.length - 1)
|
||||||
|
sqlInsertString += ', ';
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlInsertString += ')';
|
||||||
|
|
||||||
|
queryLength += sqlInsertString.length;
|
||||||
|
rowsWritten++;
|
||||||
|
rowIndex++;
|
||||||
|
yield sqlInsertString;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlStr = ';\n\n';
|
||||||
|
sqlStr += `/*!40000 ALTER TABLE \`${tableName}\` ENABLE KEYS */;\n`;
|
||||||
|
sqlStr += 'UNLOCK TABLES;';
|
||||||
|
|
||||||
|
yield sqlStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getViews () {
|
||||||
|
const { rows: views } = await this._client.raw(
|
||||||
|
`SHOW TABLE STATUS FROM \`${this.schemaName}\` WHERE Comment = 'VIEW'`
|
||||||
|
);
|
||||||
|
let sqlString = '';
|
||||||
|
|
||||||
|
for (const view of views) {
|
||||||
|
sqlString += `DROP VIEW IF EXISTS \`${view.Name}\`;\n`;
|
||||||
|
const viewSyntax = await this.getCreateTable(view.Name);
|
||||||
|
sqlString += viewSyntax.replaceAll('`' + this.schemaName + '`.', '');
|
||||||
|
sqlString += '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
return sqlString;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTriggers () {
|
||||||
|
const { rows: triggers } = await this._client.raw(
|
||||||
|
`SHOW TRIGGERS FROM \`${this.schemaName}\``
|
||||||
|
);
|
||||||
|
const generatedTables = this._tables
|
||||||
|
.filter(t => t.includeStructure)
|
||||||
|
.map(t => t.table);
|
||||||
|
|
||||||
|
let sqlString = '';
|
||||||
|
|
||||||
|
for (const trigger of triggers) {
|
||||||
|
const {
|
||||||
|
Trigger: name,
|
||||||
|
Timing: timing,
|
||||||
|
Event: event,
|
||||||
|
Table: table,
|
||||||
|
Statement: statement,
|
||||||
|
sql_mode: sqlMode
|
||||||
|
} = trigger;
|
||||||
|
|
||||||
|
if (!generatedTables.includes(table)) continue;
|
||||||
|
|
||||||
|
const definer = this.getEscapedDefiner(trigger.Definer);
|
||||||
|
sqlString += '/*!50003 SET @OLD_SQL_MODE=@@SQL_MODE*/;;\n';
|
||||||
|
sqlString += `/*!50003 SET SQL_MODE="${sqlMode}" */;\n`;
|
||||||
|
sqlString += 'DELIMITER ;;\n';
|
||||||
|
sqlString += '/*!50003 CREATE*/ ';
|
||||||
|
sqlString += `/*!50017 DEFINER=${definer}*/ `;
|
||||||
|
sqlString += `/*!50003 TRIGGER \`${name}\` ${timing} ${event} ON \`${table}\` FOR EACH ROW ${statement}*/;;\n`;
|
||||||
|
sqlString += 'DELIMITER ;\n';
|
||||||
|
sqlString += '/*!50003 SET SQL_MODE=@OLD_SQL_MODE */;\n\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
return sqlString;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSchedulers () {
|
||||||
|
const { rows: schedulers } = await this._client.raw(
|
||||||
|
`SELECT *, EVENT_SCHEMA AS \`Db\`, EVENT_NAME AS \`Name\` FROM information_schema.\`EVENTS\` WHERE EVENT_SCHEMA = '${this.schemaName}'`
|
||||||
|
);
|
||||||
|
let sqlString = '';
|
||||||
|
|
||||||
|
for (const scheduler of schedulers) {
|
||||||
|
const {
|
||||||
|
EVENT_NAME: name,
|
||||||
|
SQL_MODE: sqlMode,
|
||||||
|
EVENT_TYPE: type,
|
||||||
|
INTERVAL_VALUE: intervalValue,
|
||||||
|
INTERVAL_FIELD: intervalField,
|
||||||
|
STARTS: starts,
|
||||||
|
ENDS: ends,
|
||||||
|
EXECUTE_AT: at,
|
||||||
|
ON_COMPLETION: onCompletion,
|
||||||
|
STATUS: status,
|
||||||
|
EVENT_DEFINITION: definition
|
||||||
|
} = scheduler;
|
||||||
|
|
||||||
|
const definer = this.getEscapedDefiner(scheduler.DEFINER);
|
||||||
|
const comment = this.escapeAndQuote(scheduler.EVENT_COMMENT);
|
||||||
|
|
||||||
|
sqlString += `/*!50106 DROP EVENT IF EXISTS \`${name}\` */;\n`;
|
||||||
|
sqlString += '/*!50003 SET @OLD_SQL_MODE=@@SQL_MODE*/;;\n';
|
||||||
|
sqlString += `/*!50003 SET SQL_MODE='${sqlMode}' */;\n`;
|
||||||
|
sqlString += 'DELIMITER ;;\n';
|
||||||
|
sqlString += '/*!50106 CREATE*/ ';
|
||||||
|
sqlString += `/*!50117 DEFINER=${definer}*/ `;
|
||||||
|
sqlString += `/*!50106 EVENT \`${name}\` ON SCHEDULE `;
|
||||||
|
if (type === 'RECURRING') {
|
||||||
|
sqlString += `EVERY ${intervalValue} ${intervalField} STARTS '${starts}' `;
|
||||||
|
|
||||||
|
if (ends) sqlString += `ENDS '${ends}' `;
|
||||||
|
}
|
||||||
|
else sqlString += `AT '${at}' `;
|
||||||
|
sqlString += `ON COMPLETION ${onCompletion} ${
|
||||||
|
status === 'disabled' ? 'DISABLE' : 'ENABLE'
|
||||||
|
} COMMENT ${comment || '\'\''} DO ${definition}*/;;\n`;
|
||||||
|
sqlString += 'DELIMITER ;\n';
|
||||||
|
sqlString += '/*!50003 SET SQL_MODE=@OLD_SQL_MODE*/;;\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
return sqlString;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFunctions () {
|
||||||
|
const { rows: functions } = await this._client.raw(
|
||||||
|
`SHOW FUNCTION STATUS WHERE \`Db\` = '${this.schemaName}';`
|
||||||
|
);
|
||||||
|
|
||||||
|
let sqlString = '';
|
||||||
|
|
||||||
|
for (const func of functions) {
|
||||||
|
const definer = this.getEscapedDefiner(func.Definer);
|
||||||
|
sqlString += await this.getRoutineSyntax(
|
||||||
|
func.Name,
|
||||||
|
func.Type,
|
||||||
|
definer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sqlString;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRoutines () {
|
||||||
|
const { rows: routines } = await this._client.raw(
|
||||||
|
`SHOW PROCEDURE STATUS WHERE \`Db\` = '${this.schemaName}';`
|
||||||
|
);
|
||||||
|
|
||||||
|
let sqlString = '';
|
||||||
|
|
||||||
|
for (const routine of routines) {
|
||||||
|
const definer = this.getEscapedDefiner(routine.Definer);
|
||||||
|
|
||||||
|
sqlString += await this.getRoutineSyntax(
|
||||||
|
routine.Name,
|
||||||
|
routine.Type,
|
||||||
|
definer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sqlString;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRoutineSyntax (name, type, definer) {
|
||||||
|
const { rows: routines } = await this._client.raw(
|
||||||
|
`SHOW CREATE ${type} \`${this.schemaName}\`.\`${name}\``
|
||||||
|
);
|
||||||
|
|
||||||
|
if (routines.length === 0) return '';
|
||||||
|
|
||||||
|
const routine = routines[0];
|
||||||
|
|
||||||
|
const fieldName = `Create ${type === 'PROCEDURE' ? 'Procedure' : 'Function'}`;
|
||||||
|
const sqlMode = routine.sql_mode;
|
||||||
|
const createProcedure = routine[fieldName];
|
||||||
|
let sqlString = '';
|
||||||
|
|
||||||
|
if (createProcedure) { // If procedure body not empty
|
||||||
|
const startOffset = createProcedure.indexOf(type);
|
||||||
|
const procedureBody = createProcedure.substring(startOffset);
|
||||||
|
|
||||||
|
sqlString += `/*!50003 DROP ${type} IF EXISTS ${name}*/;;\n`;
|
||||||
|
sqlString += '/*!50003 SET @OLD_SQL_MODE=@@SQL_MODE*/;;\n';
|
||||||
|
sqlString += `/*!50003 SET SQL_MODE="${sqlMode}"*/;;\n`;
|
||||||
|
sqlString += 'DELIMITER ;;\n';
|
||||||
|
sqlString += `/*!50003 CREATE*/ /*!50020 DEFINER=${definer}*/ /*!50003 ${procedureBody}*/;;\n`;
|
||||||
|
sqlString += 'DELIMITER ;\n';
|
||||||
|
sqlString += '/*!50003 SET SQL_MODE=@OLD_SQL_MODE*/;\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
return sqlString;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _queryStream (sql) {
|
||||||
|
if (process.env.NODE_ENV === 'development') console.log('EXPORTER:', sql);
|
||||||
|
const isPool = typeof this._client._connection.getConnection === 'function';
|
||||||
|
const connection = isPool ? await this._client._connection.getConnection() : this._client._connection;
|
||||||
|
const stream = connection.connection.query(sql).stream();
|
||||||
|
const dispose = () => connection.destroy();
|
||||||
|
|
||||||
|
stream.on('end', dispose);
|
||||||
|
stream.on('error', dispose);
|
||||||
|
stream.on('close', dispose);
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEscapedDefiner (definer) {
|
||||||
|
return definer
|
||||||
|
.split('@')
|
||||||
|
.map(part => '`' + part + '`')
|
||||||
|
.join('@');
|
||||||
|
}
|
||||||
|
|
||||||
|
escapeAndQuote (val) {
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
|
const CHARS_TO_ESCAPE = /[\0\b\t\n\r\x1a"'\\]/g;
|
||||||
|
const CHARS_ESCAPE_MAP = {
|
||||||
|
'\0': '\\0',
|
||||||
|
'\b': '\\b',
|
||||||
|
'\t': '\\t',
|
||||||
|
'\n': '\\n',
|
||||||
|
'\r': '\\r',
|
||||||
|
'\x1a': '\\Z',
|
||||||
|
'"': '\\"',
|
||||||
|
'\'': '\\\'',
|
||||||
|
'\\': '\\\\'
|
||||||
|
};
|
||||||
|
let chunkIndex = CHARS_TO_ESCAPE.lastIndex = 0;
|
||||||
|
let escapedVal = '';
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = CHARS_TO_ESCAPE.exec(val))) {
|
||||||
|
escapedVal += val.slice(chunkIndex, match.index) + CHARS_ESCAPE_MAP[match[0]];
|
||||||
|
chunkIndex = CHARS_TO_ESCAPE.lastIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunkIndex === 0)
|
||||||
|
return `'${val}'`;
|
||||||
|
|
||||||
|
if (chunkIndex < val.length)
|
||||||
|
return `'${escapedVal + val.slice(chunkIndex)}'`;
|
||||||
|
|
||||||
|
return `'${escapedVal}'`;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getGeoJSON (val) {
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
if (getArrayDepth(val) === 1)
|
||||||
|
return lineString(val.reduce((acc, curr) => [...acc, [curr.x, curr.y]], []));
|
||||||
|
else
|
||||||
|
return polygon(val.map(arr => arr.reduce((acc, curr) => [...acc, [curr.x, curr.y]], [])));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return point([val.x, val.y]);
|
||||||
|
}
|
||||||
|
}
|
162
src/main/libs/exporters/sql/SqlExporter.js
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
import { BaseExporter } from '../BaseExporter';
|
||||||
|
|
||||||
|
export class SqlExporter extends BaseExporter {
|
||||||
|
constructor (client, tables, options) {
|
||||||
|
super(tables, options);
|
||||||
|
this._client = client;
|
||||||
|
this._commentChar = '#';
|
||||||
|
}
|
||||||
|
|
||||||
|
get schemaName () {
|
||||||
|
return this._options.schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
get host () {
|
||||||
|
return this._client._params.host;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getServerVersion () {
|
||||||
|
const version = await this._client.getVersion();
|
||||||
|
return `${version.name} ${version.number}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async dump () {
|
||||||
|
const { includes } = this._options;
|
||||||
|
const extraItems = Object.keys(includes).filter(key => includes[key]);
|
||||||
|
const totalTableToProcess = this._tables.filter(
|
||||||
|
t => t.includeStructure || t.includeContent || t.includeDropStatement
|
||||||
|
).length;
|
||||||
|
const processingItemCount = totalTableToProcess + extraItems.length;
|
||||||
|
|
||||||
|
const exportState = {
|
||||||
|
totalItems: processingItemCount,
|
||||||
|
currentItemIndex: 0,
|
||||||
|
currentItem: '',
|
||||||
|
op: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
const header = await this.getSqlHeader();
|
||||||
|
this.writeString(header);
|
||||||
|
this.writeString('\n\n\n');
|
||||||
|
|
||||||
|
for (const item of this._tables) {
|
||||||
|
// user abort operation
|
||||||
|
if (this.isCancelled) return;
|
||||||
|
|
||||||
|
// skip item if not set to output any detail for them
|
||||||
|
if (
|
||||||
|
!item.includeStructure &&
|
||||||
|
!item.includeContent &&
|
||||||
|
!item.includeDropStatement
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
exportState.currentItemIndex++;
|
||||||
|
exportState.currentItem = item.table;
|
||||||
|
exportState.op = 'FETCH';
|
||||||
|
|
||||||
|
this.emitUpdate(exportState);
|
||||||
|
|
||||||
|
const tableHeader = this.buildComment(
|
||||||
|
`Dump of table ${item.table}\n------------------------------------------------------------`
|
||||||
|
);
|
||||||
|
this.writeString(tableHeader);
|
||||||
|
this.writeString('\n\n');
|
||||||
|
|
||||||
|
if (item.includeDropStatement) {
|
||||||
|
const dropTableSyntax = this.getDropTable(item.table);
|
||||||
|
this.writeString(dropTableSyntax);
|
||||||
|
this.writeString('\n\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.includeStructure) {
|
||||||
|
const createTableSyntax = await this.getCreateTable(item.table);
|
||||||
|
this.writeString(createTableSyntax);
|
||||||
|
this.writeString('\n\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.includeContent) {
|
||||||
|
exportState.op = 'WRITE';
|
||||||
|
this.emitUpdate(exportState);
|
||||||
|
for await (const sqlStr of this.getTableInsert(item.table)) {
|
||||||
|
if (this.isCancelled) return;
|
||||||
|
this.writeString(sqlStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.writeString('\n\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.writeString('\n\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of extraItems) {
|
||||||
|
const processingMethod = `get${item.charAt(0).toUpperCase() + item.slice(1)}`;
|
||||||
|
exportState.currentItemIndex++;
|
||||||
|
exportState.currentItem = item;
|
||||||
|
exportState.op = 'PROCESSING';
|
||||||
|
this.emitUpdate(exportState);
|
||||||
|
|
||||||
|
if (this[processingMethod]) {
|
||||||
|
const data = await this[processingMethod]();
|
||||||
|
if (data !== '') {
|
||||||
|
const header =
|
||||||
|
this.buildComment(
|
||||||
|
`Dump of ${item}\n------------------------------------------------------------`
|
||||||
|
) + '\n\n';
|
||||||
|
|
||||||
|
this.writeString(header);
|
||||||
|
this.writeString(data);
|
||||||
|
this.writeString('\n\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const footer = await this.getFooter();
|
||||||
|
this.writeString(footer);
|
||||||
|
}
|
||||||
|
|
||||||
|
buildComment (text) {
|
||||||
|
return text
|
||||||
|
.split('\n')
|
||||||
|
.map(txt => `${this._commentChar} ${txt}`)
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSqlHeader () {
|
||||||
|
const serverVersion = await this.getServerVersion();
|
||||||
|
const header = `************************************************************
|
||||||
|
Antares - SQL Client
|
||||||
|
Version ${process.env.PACKAGE_VERSION}
|
||||||
|
|
||||||
|
https://antares-sql.app/
|
||||||
|
https://github.com/Fabio286/antares
|
||||||
|
|
||||||
|
Host: ${this.host} (${serverVersion})
|
||||||
|
Database: ${this.schemaName}
|
||||||
|
Generation time: ${moment().format()}
|
||||||
|
************************************************************`;
|
||||||
|
|
||||||
|
return this.buildComment(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFooter () {
|
||||||
|
return this.buildComment(`Dump completed on ${moment().format()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCreateTable (tableName) {
|
||||||
|
throw new Error(
|
||||||
|
'Sql Exporter must implement the "getCreateTable" method'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDropTable (tableName) {
|
||||||
|
throw new Error('Sql Exporter must implement the "getDropTable" method');
|
||||||
|
}
|
||||||
|
|
||||||
|
getTableInsert (tableName) {
|
||||||
|
throw new Error(
|
||||||
|
'Sql Exporter must implement the "getTableInsert" method'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
53
src/main/libs/importers/BaseImporter.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import EventEmitter from 'events';
|
||||||
|
|
||||||
|
export class BaseImporter extends EventEmitter {
|
||||||
|
constructor (options) {
|
||||||
|
super();
|
||||||
|
this._options = options;
|
||||||
|
this._isCancelled = false;
|
||||||
|
this._fileHandler = fs.createReadStream(this._options.file, {
|
||||||
|
flags: 'r',
|
||||||
|
highWaterMark: 4 * 1024
|
||||||
|
});
|
||||||
|
this._state = {};
|
||||||
|
|
||||||
|
this._fileHandler.once('error', err => {
|
||||||
|
this._isCancelled = true;
|
||||||
|
this.emit('error', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run () {
|
||||||
|
try {
|
||||||
|
this.emit('start', this);
|
||||||
|
await this.import();
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.emit('error', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this._fileHandler.close();
|
||||||
|
this.emit('end');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get isCancelled () {
|
||||||
|
return this._isCancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel () {
|
||||||
|
this._isCancelled = true;
|
||||||
|
this.emit('cancel');
|
||||||
|
this.emitUpdate({ op: 'cancelling' });
|
||||||
|
}
|
||||||
|
|
||||||
|
emitUpdate (state) {
|
||||||
|
this.emit('progress', { ...this._state, ...state });
|
||||||
|
}
|
||||||
|
|
||||||
|
import () {
|
||||||
|
throw new Error('Exporter must implement the "import" method');
|
||||||
|
}
|
||||||
|
}
|
85
src/main/libs/importers/sql/MysqlImporter.js
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import fs from 'fs/promises';
|
||||||
|
import SqlParser from '../../../../common/libs/sqlParser';
|
||||||
|
import { BaseImporter } from '../BaseImporter';
|
||||||
|
|
||||||
|
export default class MysqlImporter extends BaseImporter {
|
||||||
|
constructor (client, options) {
|
||||||
|
super(options);
|
||||||
|
this._client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
async import () {
|
||||||
|
try {
|
||||||
|
const { size: totalFileSize } = await fs.stat(this._options.file);
|
||||||
|
const parser = new SqlParser();
|
||||||
|
let readPosition = 0;
|
||||||
|
let queryCount = 0;
|
||||||
|
|
||||||
|
this.emitUpdate({
|
||||||
|
fileSize: totalFileSize,
|
||||||
|
readPosition: 0,
|
||||||
|
percentage: 0,
|
||||||
|
queryCount: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
// 1. detect file encoding
|
||||||
|
// 2. set fh encoding
|
||||||
|
// 3. detect sql mode
|
||||||
|
// 4. restore sql mode in case of exception
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this._fileHandler.pipe(parser);
|
||||||
|
|
||||||
|
parser.on('error', reject);
|
||||||
|
|
||||||
|
parser.on('close', async () => {
|
||||||
|
console.log('TOTAL QUERIES', queryCount);
|
||||||
|
console.log('import end');
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
parser.on('data', async (query) => {
|
||||||
|
queryCount++;
|
||||||
|
parser.pause();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this._client.query(query);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
this.emit('query-error', {
|
||||||
|
sql: query,
|
||||||
|
message: error.sqlMessage,
|
||||||
|
sqlSnippet: error.sql,
|
||||||
|
time: new Date().getTime()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emitUpdate({
|
||||||
|
queryCount,
|
||||||
|
readPosition,
|
||||||
|
percentage: readPosition / totalFileSize * 100
|
||||||
|
});
|
||||||
|
this._fileHandler.pipe(parser);
|
||||||
|
parser.resume();
|
||||||
|
});
|
||||||
|
|
||||||
|
parser.on('pause', () => {
|
||||||
|
this._fileHandler.unpipe(parser);
|
||||||
|
this._fileHandler.readableFlowing = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
this._fileHandler.on('data', (chunk) => {
|
||||||
|
readPosition += chunk.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
this._fileHandler.on('error', (err) => {
|
||||||
|
console.log(err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -3,6 +3,7 @@
|
|||||||
import { app, BrowserWindow, /* session, */ nativeImage, Menu } from 'electron';
|
import { app, BrowserWindow, /* session, */ nativeImage, Menu } from 'electron';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import Store from 'electron-store';
|
import Store from 'electron-store';
|
||||||
|
import * as windowStateKeeper from 'electron-window-state';
|
||||||
import * as remoteMain from '@electron/remote/main';
|
import * as remoteMain from '@electron/remote/main';
|
||||||
|
|
||||||
import ipcHandlers from './ipc-handlers';
|
import ipcHandlers from './ipc-handlers';
|
||||||
@@ -18,12 +19,15 @@ process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
|
|||||||
|
|
||||||
// global reference to mainWindow (necessary to prevent window from being garbage collected)
|
// global reference to mainWindow (necessary to prevent window from being garbage collected)
|
||||||
let mainWindow;
|
let mainWindow;
|
||||||
|
let mainWindowState;
|
||||||
|
|
||||||
async function createMainWindow () {
|
async function createMainWindow () {
|
||||||
const icon = require('../renderer/images/logo-32.png');
|
const icon = require('../renderer/images/logo-32.png');
|
||||||
const window = new BrowserWindow({
|
const window = new BrowserWindow({
|
||||||
width: 1024,
|
width: mainWindowState.width,
|
||||||
height: 800,
|
height: mainWindowState.height,
|
||||||
|
x: mainWindowState.x,
|
||||||
|
y: mainWindowState.y,
|
||||||
minWidth: 900,
|
minWidth: 900,
|
||||||
minHeight: 550,
|
minHeight: 550,
|
||||||
title: 'Antares SQL',
|
title: 'Antares SQL',
|
||||||
@@ -41,6 +45,9 @@ async function createMainWindow () {
|
|||||||
backgroundColor: '#1d1d1d'
|
backgroundColor: '#1d1d1d'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mainWindowState.manage(window);
|
||||||
|
window.on('moved', saveWindowState);
|
||||||
|
|
||||||
remoteMain.enable(window.webContents);
|
remoteMain.enable(window.webContents);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -70,16 +77,10 @@ async function createMainWindow () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.on('closed', () => {
|
window.on('closed', () => {
|
||||||
|
window.removeListener('moved', saveWindowState);
|
||||||
mainWindow = null;
|
mainWindow = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
window.webContents.on('devtools-opened', () => {
|
|
||||||
window.focus();
|
|
||||||
setImmediate(() => {
|
|
||||||
window.focus();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return window;
|
return window;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +105,11 @@ else {
|
|||||||
|
|
||||||
// create main BrowserWindow when electron is ready
|
// create main BrowserWindow when electron is ready
|
||||||
app.on('ready', async () => {
|
app.on('ready', async () => {
|
||||||
|
mainWindowState = windowStateKeeper({
|
||||||
|
defaultWidth: 1024,
|
||||||
|
defaultHeight: 800
|
||||||
|
});
|
||||||
|
|
||||||
mainWindow = await createMainWindow();
|
mainWindow = await createMainWindow();
|
||||||
createAppMenu();
|
createAppMenu();
|
||||||
|
|
||||||
@@ -160,3 +166,7 @@ function createAppMenu () {
|
|||||||
|
|
||||||
Menu.setApplicationMenu(menu);
|
Menu.setApplicationMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function saveWindowState () {
|
||||||
|
mainWindowState.saveState(mainWindow);
|
||||||
|
}
|
||||||
|
60
src/main/workers/exporter.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { ClientsFactory } from '../libs/ClientsFactory';
|
||||||
|
import MysqlExporter from '../libs/exporters/sql/MysqlExporter.js';
|
||||||
|
import fs from 'fs';
|
||||||
|
let exporter;
|
||||||
|
|
||||||
|
process.on('message', async ({ type, client, tables, options }) => {
|
||||||
|
if (type === 'init') {
|
||||||
|
const connection = await ClientsFactory.getClient({
|
||||||
|
client: client.name,
|
||||||
|
params: client.config,
|
||||||
|
poolSize: 5
|
||||||
|
});
|
||||||
|
await connection.connect();
|
||||||
|
|
||||||
|
switch (client.name) {
|
||||||
|
case 'mysql':
|
||||||
|
case 'maria':
|
||||||
|
exporter = new MysqlExporter(connection, tables, options);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
process.send({
|
||||||
|
type: 'error',
|
||||||
|
payload: `"${client.name}" exporter not aviable`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
exporter.once('error', err => {
|
||||||
|
console.error(err);
|
||||||
|
process.send({
|
||||||
|
type: 'error',
|
||||||
|
payload: err.toString()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
exporter.once('end', () => {
|
||||||
|
process.send({
|
||||||
|
type: 'end',
|
||||||
|
payload: { cancelled: exporter.isCancelled }
|
||||||
|
});
|
||||||
|
connection.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
exporter.once('cancel', () => {
|
||||||
|
fs.unlinkSync(exporter.outputFile);
|
||||||
|
process.send({ type: 'cancel' });
|
||||||
|
});
|
||||||
|
|
||||||
|
exporter.on('progress', state => {
|
||||||
|
process.send({
|
||||||
|
type: 'export-progress',
|
||||||
|
payload: state
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
exporter.run();
|
||||||
|
}
|
||||||
|
else if (type === 'cancel')
|
||||||
|
exporter.cancel();
|
||||||
|
});
|
68
src/main/workers/importer.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { ClientsFactory } from '../libs/ClientsFactory';
|
||||||
|
import MysqlImporter from '../libs/importers/sql/MysqlImporter';
|
||||||
|
let importer;
|
||||||
|
|
||||||
|
process.on('message', async ({ type, dbConfig, options }) => {
|
||||||
|
if (type === 'init') {
|
||||||
|
const connection = await ClientsFactory.getClient({
|
||||||
|
client: options.type,
|
||||||
|
params: {
|
||||||
|
...dbConfig,
|
||||||
|
schema: options.schema
|
||||||
|
},
|
||||||
|
poolSize: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
const pool = await connection.getConnectionPool();
|
||||||
|
|
||||||
|
switch (options.type) {
|
||||||
|
case 'mysql':
|
||||||
|
case 'maria':
|
||||||
|
importer = new MysqlImporter(pool, options);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
process.send({
|
||||||
|
type: 'error',
|
||||||
|
payload: `"${options.type}" importer not aviable`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
importer.once('error', err => {
|
||||||
|
console.error(err);
|
||||||
|
process.send({
|
||||||
|
type: 'error',
|
||||||
|
payload: err.toString()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
importer.once('end', () => {
|
||||||
|
process.send({
|
||||||
|
type: 'end',
|
||||||
|
payload: { cancelled: importer.isCancelled }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
importer.once('cancel', () => {
|
||||||
|
process.send({ type: 'cancel' });
|
||||||
|
});
|
||||||
|
|
||||||
|
importer.on('progress', state => {
|
||||||
|
process.send({
|
||||||
|
type: 'import-progress',
|
||||||
|
payload: state
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
importer.on('query-error', state => {
|
||||||
|
process.send({
|
||||||
|
type: 'query-error',
|
||||||
|
payload: state
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
importer.run();
|
||||||
|
}
|
||||||
|
else if (type === 'cancel')
|
||||||
|
importer.cancel();
|
||||||
|
});
|
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="wrapper" :class="`theme-${applicationTheme}`">
|
<div id="wrapper" :class="[`theme-${applicationTheme}`, !disableBlur || 'no-blur']">
|
||||||
<TheTitleBar />
|
<TheTitleBar />
|
||||||
<div id="window-content">
|
<div id="window-content">
|
||||||
<TheSettingBar />
|
<TheSettingBar />
|
||||||
@@ -10,7 +10,9 @@
|
|||||||
:key="connection.uid"
|
:key="connection.uid"
|
||||||
:connection="connection"
|
:connection="connection"
|
||||||
/>
|
/>
|
||||||
<WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" />
|
<div class="connection-panel-wrapper">
|
||||||
|
<WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<TheFooter />
|
<TheFooter />
|
||||||
<TheNotificationsBoard />
|
<TheNotificationsBoard />
|
||||||
@@ -51,6 +53,7 @@ export default {
|
|||||||
isScratchpad: 'application/isScratchpad',
|
isScratchpad: 'application/isScratchpad',
|
||||||
connections: 'connections/getConnections',
|
connections: 'connections/getConnections',
|
||||||
applicationTheme: 'settings/getApplicationTheme',
|
applicationTheme: 'settings/getApplicationTheme',
|
||||||
|
disableBlur: 'settings/getDisableBlur',
|
||||||
isUnsavedDiscardModal: 'workspaces/isUnsavedDiscardModal'
|
isUnsavedDiscardModal: 'workspaces/isUnsavedDiscardModal'
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -130,5 +133,15 @@ export default {
|
|||||||
> .columns {
|
> .columns {
|
||||||
height: calc(100vh - #{$footer-height});
|
height: calc(100vh - #{$footer-height});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.connection-panel-wrapper{
|
||||||
|
height: calc(100vh - #{$excluding-size});
|
||||||
|
width: 100%;
|
||||||
|
padding-top: 15vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -110,5 +110,4 @@ export default {
|
|||||||
.modal.modal-sm .modal-container {
|
.modal.modal-sm .modal-container {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
108
src/renderer/components/BaseMap.vue
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<template>
|
||||||
|
<div id="map" class="map" />
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import L from 'leaflet';
|
||||||
|
import {
|
||||||
|
point,
|
||||||
|
lineString,
|
||||||
|
polygon
|
||||||
|
} from '@turf/helpers';
|
||||||
|
import { getArrayDepth } from 'common/libs/getArrayDepth';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'BaseMap',
|
||||||
|
props: {
|
||||||
|
points: [Object, Array],
|
||||||
|
isMultiSpatial: Boolean
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
map: null,
|
||||||
|
markers: [],
|
||||||
|
center: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
if (this.isMultiSpatial) {
|
||||||
|
for (const element of this.points)
|
||||||
|
this.markers.push(this.getMarkers(element));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.markers = this.getMarkers(this.points);
|
||||||
|
|
||||||
|
if (!Array.isArray(this.points))
|
||||||
|
this.center = [this.points.y, this.points.x];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.map = L.map('map', {
|
||||||
|
center: this.center || [0, 0],
|
||||||
|
zoom: 15,
|
||||||
|
minZoom: 1,
|
||||||
|
attributionControl: false
|
||||||
|
});
|
||||||
|
|
||||||
|
L.control.attribution({ prefix: '<b>Leaflet</b>' }).addTo(this.map);
|
||||||
|
|
||||||
|
const geoJsonObj = L.geoJSON(this.markers, {
|
||||||
|
style: function () {
|
||||||
|
return {
|
||||||
|
weight: 2,
|
||||||
|
fillColor: '#ff7800',
|
||||||
|
color: '#ff7800',
|
||||||
|
opacity: 0.8,
|
||||||
|
fillOpacity: 0.4
|
||||||
|
};
|
||||||
|
},
|
||||||
|
pointToLayer: function (feature, latlng) {
|
||||||
|
return L.circleMarker(latlng, {
|
||||||
|
radius: 7,
|
||||||
|
weight: 2,
|
||||||
|
fillColor: '#ff7800',
|
||||||
|
color: '#ff7800',
|
||||||
|
opacity: 0.8,
|
||||||
|
fillOpacity: 0.4
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).addTo(this.map);
|
||||||
|
|
||||||
|
const southWest = L.latLng(-90, -180);
|
||||||
|
const northEast = L.latLng(90, 180);
|
||||||
|
const bounds = L.latLngBounds(southWest, northEast);
|
||||||
|
this.map.setMaxBounds(bounds);
|
||||||
|
|
||||||
|
if (!this.center) this.map.fitBounds(geoJsonObj.getBounds());
|
||||||
|
|
||||||
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
attribution: '© <b>OpenStreetMap</b>'
|
||||||
|
}).addTo(this.map);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getMarkers (points) {
|
||||||
|
if (Array.isArray(points)) {
|
||||||
|
if (getArrayDepth(points) === 1)
|
||||||
|
return lineString(points.reduce((acc, curr) => [...acc, [curr.x, curr.y]], []));
|
||||||
|
else
|
||||||
|
return polygon(points.map(arr => arr.reduce((acc, curr) => [...acc, [curr.x, curr.y]], [])));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return point([points.x, points.y]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.map {
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker-icon {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: $primary-color;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 0 5px 1px darken($body-font-color-dark, 40%);
|
||||||
|
}
|
||||||
|
</style>
|
@@ -78,6 +78,7 @@ export default {
|
|||||||
|
|
||||||
.file-uploader-message {
|
.file-uploader-message {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
word-break: keep-all;
|
||||||
border-radius: $border-radius 0 0 $border-radius;
|
border-radius: $border-radius 0 0 $border-radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
@blur="$emit('blur')"
|
@blur="$emit('blur')"
|
||||||
>
|
>
|
||||||
<option v-if="!isValidDefault" :value="value">
|
<option v-if="!isValidDefault" :value="value">
|
||||||
{{ value }} - {{ $t('message.invalidDefault') }}
|
{{ value === null ? 'NULL' : value }}
|
||||||
</option>
|
</option>
|
||||||
<option
|
<option
|
||||||
v-for="row in foreignList"
|
v-for="row in foreignList"
|
||||||
|
@@ -6,13 +6,13 @@
|
|||||||
@confirm="runRoutine"
|
@confirm="runRoutine"
|
||||||
@hide="closeModal"
|
@hide="closeModal"
|
||||||
>
|
>
|
||||||
<template slot="header">
|
<template #header>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-play mr-1" />
|
<i class="mdi mdi-24px mdi-play mr-1" />
|
||||||
<span class="cut-text">{{ $t('word.parameters') }}: {{ localRoutine.name }}</span>
|
<span class="cut-text">{{ $t('word.parameters') }}: {{ localRoutine.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div slot="body">
|
<template #body>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<form class="form-horizontal">
|
<form class="form-horizontal">
|
||||||
<div
|
<div
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@@ -5,16 +5,16 @@
|
|||||||
@confirm="$emit('confirm')"
|
@confirm="$emit('confirm')"
|
||||||
@hide="$emit('close')"
|
@hide="$emit('close')"
|
||||||
>
|
>
|
||||||
<template slot="header">
|
<template #header>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-content-save-alert mr-1" /> {{ $t('message.unsavedChanges') }}
|
<i class="mdi mdi-24px mdi-content-save-alert mr-1" /> {{ $t('message.unsavedChanges') }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div slot="body">
|
<template #body>
|
||||||
<div>
|
<div>
|
||||||
{{ $t('message.discardUnsavedChanges') }}
|
{{ $t('message.discardUnsavedChanges') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
507
src/renderer/components/ModalExportSchema.vue
Normal file
@@ -0,0 +1,507 @@
|
|||||||
|
<template>
|
||||||
|
<div class="modal active">
|
||||||
|
<a class="modal-overlay" @click.stop="closeModal" />
|
||||||
|
<div class="modal-container p-0">
|
||||||
|
<div class="modal-header pl-2">
|
||||||
|
<div class="modal-title h6">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="mdi mdi-24px mdi-database-arrow-down mr-1" />
|
||||||
|
<span class="cut-text">{{ $t('message.exportSchema') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||||
|
</div>
|
||||||
|
<div class="modal-body pb-0">
|
||||||
|
<div class="container">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="col-3">
|
||||||
|
<label class="form-label">{{ $t('message.directoryPath') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-9">
|
||||||
|
<fieldset class="input-group">
|
||||||
|
<input
|
||||||
|
v-model="basePath"
|
||||||
|
class="form-input"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
readonly
|
||||||
|
:placeholder="$t('message.schemaName')"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary input-group-btn"
|
||||||
|
@click.prevent="openPathDialog"
|
||||||
|
>
|
||||||
|
{{ $t('word.change') }}
|
||||||
|
</button>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="columns export-options">
|
||||||
|
<div class="column col-8 left">
|
||||||
|
<div class="columns mb-2">
|
||||||
|
<div class="column col-auto d-flex text-italic ">
|
||||||
|
<i class="mdi mdi-file-document-outline mr-2" />
|
||||||
|
{{ filename }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column col-auto col-ml-auto ">
|
||||||
|
<button
|
||||||
|
class="btn btn-dark btn-sm"
|
||||||
|
:title="$t('word.refresh')"
|
||||||
|
@click="refresh"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-database-refresh" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-dark btn-sm"
|
||||||
|
:title="$t('message.uncheckAllTables')"
|
||||||
|
:disabled="isRefreshing"
|
||||||
|
@click="uncheckAllTables"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-file-tree-outline" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-dark btn-sm"
|
||||||
|
:title="$t('message.checkAllTables')"
|
||||||
|
:disabled="isRefreshing"
|
||||||
|
@click="checkAllTables"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-file-tree" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="workspace-query-results">
|
||||||
|
<div ref="table" class="table table-hover">
|
||||||
|
<div class="thead">
|
||||||
|
<div class="tr text-center">
|
||||||
|
<div class="th no-border" style="width: 50%;" />
|
||||||
|
<div class="th no-border">
|
||||||
|
<label
|
||||||
|
class="form-checkbox m-0 px-2 form-inline"
|
||||||
|
@click.prevent="toggleAllTablesOption('includeStructure')"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:indeterminate.prop="includeStructureStatus === 2"
|
||||||
|
:checked.prop="!!includeStructureStatus"
|
||||||
|
>
|
||||||
|
<i class="form-icon" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="th no-border">
|
||||||
|
<label
|
||||||
|
class="form-checkbox m-0 px-2 form-inline"
|
||||||
|
@click.prevent="toggleAllTablesOption('includeContent')"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:indeterminate.prop="includeContentStatus === 2"
|
||||||
|
:checked.prop="!!includeContentStatus"
|
||||||
|
>
|
||||||
|
<i class="form-icon" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="th no-border">
|
||||||
|
<label
|
||||||
|
class="form-checkbox m-0 px-2 form-inline"
|
||||||
|
@click.prevent="toggleAllTablesOption('includeDropStatement')"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:indeterminate.prop="includeDropStatementStatus === 2"
|
||||||
|
:checked.prop="!!includeDropStatementStatus"
|
||||||
|
>
|
||||||
|
<i class="form-icon" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tr">
|
||||||
|
<div class="th" style="width: 50%;">
|
||||||
|
<div class="table-column-title">
|
||||||
|
<span>{{ $t('word.table') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="th text-center">
|
||||||
|
<div class="table-column-title">
|
||||||
|
<span>{{ $t('word.structure') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="th text-center">
|
||||||
|
<div class="table-column-title">
|
||||||
|
<span>{{ $t('word.content') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="th text-center">
|
||||||
|
<div class="table-column-title">
|
||||||
|
<span>{{ $t('word.drop') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tbody">
|
||||||
|
<div
|
||||||
|
v-for="item in tables"
|
||||||
|
:key="item.name"
|
||||||
|
class="tr"
|
||||||
|
>
|
||||||
|
<div class="td">
|
||||||
|
{{ item.table }}
|
||||||
|
</div>
|
||||||
|
<div class="td text-center">
|
||||||
|
<label class="form-checkbox m-0 px-2 form-inline">
|
||||||
|
<input
|
||||||
|
v-model="item.includeStructure"
|
||||||
|
type="checkbox"
|
||||||
|
><i class="form-icon" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="td text-center">
|
||||||
|
<label class="form-checkbox m-0 px-2 form-inline">
|
||||||
|
<input
|
||||||
|
v-model="item.includeContent"
|
||||||
|
type="checkbox"
|
||||||
|
><i class="form-icon" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="td text-center">
|
||||||
|
<label class="form-checkbox m-0 px-2 form-inline">
|
||||||
|
<input
|
||||||
|
v-model="item.includeDropStatement"
|
||||||
|
type="checkbox"
|
||||||
|
><i class="form-icon" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column col-4">
|
||||||
|
<h5 class="h5">
|
||||||
|
{{ $t('word.options') }}
|
||||||
|
</h5>
|
||||||
|
<span class="h6">{{ $t('word.includes') }}:</span>
|
||||||
|
<label
|
||||||
|
v-for="(_, key) in options.includes"
|
||||||
|
:key="key"
|
||||||
|
class="form-checkbox"
|
||||||
|
>
|
||||||
|
<input v-model="options.includes[key]" type="checkbox"><i class="form-icon" /> {{ $t(`word.${key}`) }}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="h6 mt-4 mb-2">
|
||||||
|
{{ $t('message.newInserStmtEvery') }}:
|
||||||
|
</div>
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column col-6">
|
||||||
|
<input
|
||||||
|
v-model.number="options.sqlInsertAfter"
|
||||||
|
type="number"
|
||||||
|
class="form-input"
|
||||||
|
value="250"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="column col-6">
|
||||||
|
<select v-model="options.sqlInsertDivider" class="form-select">
|
||||||
|
<option value="bytes">
|
||||||
|
KiB
|
||||||
|
</option>
|
||||||
|
<option value="rows">
|
||||||
|
{{ $tc('word.row', 2) }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="h6 mb-2 mt-4">
|
||||||
|
{{ $t('message.ourputFormat') }}:
|
||||||
|
</div>
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column h5 mb-4">
|
||||||
|
<select v-model="options.outputFormat" class="form-select">
|
||||||
|
<option value="sql">
|
||||||
|
{{ $t('message.singleFile', {ext: '.sql'}) }}
|
||||||
|
</option>
|
||||||
|
<option value="sql.zip">
|
||||||
|
{{ $t('message.zipCompressedFile', {ext: '.sql'}) }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer columns">
|
||||||
|
<div class="column col modal-progress-wrapper text-left">
|
||||||
|
<div v-if="progressPercentage > 0" class="export-progress">
|
||||||
|
<span class="progress-status">
|
||||||
|
{{ progressPercentage }}% - {{ progressStatus }}
|
||||||
|
</span>
|
||||||
|
<progress
|
||||||
|
class="progress d-block"
|
||||||
|
:value="progressPercentage"
|
||||||
|
max="100"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column col-auto px-0">
|
||||||
|
<button class="btn btn-link" @click.stop="closeModal">
|
||||||
|
{{ $t('word.close') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-primary mr-2"
|
||||||
|
:class="{'loading': isExporting}"
|
||||||
|
:disabled="isExporting || isRefreshing"
|
||||||
|
autofocus
|
||||||
|
@click.prevent="startExport"
|
||||||
|
>
|
||||||
|
{{ $t('word.export') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { ipcRenderer } from 'electron';
|
||||||
|
import { mapActions, mapGetters } from 'vuex';
|
||||||
|
import moment from 'moment';
|
||||||
|
import customizations from 'common/customizations';
|
||||||
|
import Application from '@/ipc-api/Application';
|
||||||
|
import Schema from '@/ipc-api/Schema';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ModalExportSchema',
|
||||||
|
|
||||||
|
props: {
|
||||||
|
selectedSchema: String
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
isExporting: false,
|
||||||
|
isRefreshing: false,
|
||||||
|
progressPercentage: 0,
|
||||||
|
progressStatus: '',
|
||||||
|
tables: [],
|
||||||
|
options: {
|
||||||
|
includes: {},
|
||||||
|
outputFormat: 'sql',
|
||||||
|
sqlInsertAfter: 250,
|
||||||
|
sqlInsertDivider: 'bytes'
|
||||||
|
},
|
||||||
|
basePath: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
selectedWorkspace: 'workspaces/getSelected',
|
||||||
|
getWorkspace: 'workspaces/getWorkspace',
|
||||||
|
getDatabaseVariable: 'workspaces/getDatabaseVariable'
|
||||||
|
}),
|
||||||
|
currentWorkspace () {
|
||||||
|
return this.getWorkspace(this.selectedWorkspace);
|
||||||
|
},
|
||||||
|
schemaItems () {
|
||||||
|
const db = this.currentWorkspace.structure.find(db => db.name === this.selectedSchema);
|
||||||
|
if (db)
|
||||||
|
return db.tables.filter(table => table.type === 'table');
|
||||||
|
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
filename () {
|
||||||
|
const date = moment().format('YYYY-MM-DD');
|
||||||
|
return `${this.selectedSchema}_${date}.${this.options.outputFormat}`;
|
||||||
|
},
|
||||||
|
dumpFilePath () {
|
||||||
|
return `${this.basePath}/${this.filename}`;
|
||||||
|
},
|
||||||
|
includeStructureStatus () {
|
||||||
|
if (this.tables.every(item => item.includeStructure)) return 1;
|
||||||
|
else if (this.tables.some(item => item.includeStructure)) return 2;
|
||||||
|
else return 0;
|
||||||
|
},
|
||||||
|
includeContentStatus () {
|
||||||
|
if (this.tables.every(item => item.includeContent)) return 1;
|
||||||
|
else if (this.tables.some(item => item.includeContent)) return 2;
|
||||||
|
else return 0;
|
||||||
|
},
|
||||||
|
includeDropStatementStatus () {
|
||||||
|
if (this.tables.every(item => item.includeDropStatement)) return 1;
|
||||||
|
else if (this.tables.some(item => item.includeDropStatement)) return 2;
|
||||||
|
else return 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async created () {
|
||||||
|
if (!this.schemaItems.length) await this.refresh();
|
||||||
|
|
||||||
|
window.addEventListener('keydown', this.onKey);
|
||||||
|
|
||||||
|
this.basePath = await Application.getDownloadPathDirectory();
|
||||||
|
this.tables = this.schemaItems.map(item => ({
|
||||||
|
table: item.name,
|
||||||
|
includeStructure: true,
|
||||||
|
includeContent: true,
|
||||||
|
includeDropStatement: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
const structure = ['views', 'triggers', 'routines', 'functions', 'schedulers', 'triggerFunctions'];
|
||||||
|
|
||||||
|
structure.forEach(feat => {
|
||||||
|
const val = customizations[this.currentWorkspace.client][feat];
|
||||||
|
if (val)
|
||||||
|
this.$set(this.options.includes, feat, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('export-progress', this.updateProgress);
|
||||||
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
window.removeEventListener('keydown', this.onKey);
|
||||||
|
ipcRenderer.off('export-progress', this.updateProgress);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions({
|
||||||
|
addNotification: 'notifications/addNotification',
|
||||||
|
refreshSchema: 'workspaces/refreshSchema'
|
||||||
|
}),
|
||||||
|
async startExport () {
|
||||||
|
this.isExporting = true;
|
||||||
|
const { uid, client } = this.currentWorkspace;
|
||||||
|
const params = {
|
||||||
|
uid,
|
||||||
|
type: client,
|
||||||
|
schema: this.selectedSchema,
|
||||||
|
outputFile: this.dumpFilePath,
|
||||||
|
tables: [...this.tables],
|
||||||
|
...this.options
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { status, response } = await Schema.export(params);
|
||||||
|
if (status === 'success')
|
||||||
|
this.progressStatus = response.cancelled ? this.$t('word.aborted') : this.$t('word.completed');
|
||||||
|
else {
|
||||||
|
this.progressStatus = response;
|
||||||
|
this.addNotification({ status: 'error', message: response });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isExporting = false;
|
||||||
|
},
|
||||||
|
updateProgress (event, state) {
|
||||||
|
this.progressPercentage = Number((state.currentItemIndex / state.totalItems * 100).toFixed(1));
|
||||||
|
switch (state.op) {
|
||||||
|
case 'PROCESSING':
|
||||||
|
this.progressStatus = this.$t('message.processingTableExport', { table: state.currentItem });
|
||||||
|
break;
|
||||||
|
case 'FETCH':
|
||||||
|
this.progressStatus = this.$t('message.fechingTableExport', { table: state.currentItem });
|
||||||
|
break;
|
||||||
|
case 'WRITE':
|
||||||
|
this.progressStatus = this.$t('message.writingTableExport', { table: state.currentItem });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async closeModal () {
|
||||||
|
let willClose = true;
|
||||||
|
if (this.isExporting) {
|
||||||
|
willClose = false;
|
||||||
|
const { response } = await Schema.abortExport();
|
||||||
|
willClose = response.willAbort;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (willClose)
|
||||||
|
this.$emit('close');
|
||||||
|
},
|
||||||
|
onKey (e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (e.key === 'Escape')
|
||||||
|
this.closeModal();
|
||||||
|
},
|
||||||
|
checkAllTables () {
|
||||||
|
this.tables = this.tables.map(item => ({ ...item, includeStructure: true, includeContent: true, includeDropStatement: true }));
|
||||||
|
},
|
||||||
|
uncheckAllTables () {
|
||||||
|
this.tables = this.tables.map(item => ({ ...item, includeStructure: false, includeContent: false, includeDropStatement: false }));
|
||||||
|
},
|
||||||
|
toggleAllTablesOption (option) {
|
||||||
|
const options = ['includeStructure', 'includeContent', 'includeDropStatement'];
|
||||||
|
if (!options.includes(option)) return;
|
||||||
|
|
||||||
|
if (this[`${option}Status`] !== 1)
|
||||||
|
this.tables = this.tables.map(item => ({ ...item, [option]: true }));
|
||||||
|
else
|
||||||
|
this.tables = this.tables.map(item => ({ ...item, [option]: false }));
|
||||||
|
},
|
||||||
|
async refresh () {
|
||||||
|
this.isRefreshing = true;
|
||||||
|
await this.refreshSchema({ uid: this.currentWorkspace.uid, schema: this.selectedSchema });
|
||||||
|
this.isRefreshing = false;
|
||||||
|
},
|
||||||
|
async openPathDialog () {
|
||||||
|
const result = await Application.showOpenDialog({ properties: ['openDirectory'] });
|
||||||
|
if (result && !result.canceled)
|
||||||
|
this.basePath = result.filePaths[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.export-options {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-query-results {
|
||||||
|
flex: 1 0 1px;
|
||||||
|
.table {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-checkbox {
|
||||||
|
min-height: 0.8rem;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.form-icon {
|
||||||
|
top: 0.1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
|
||||||
|
.modal-container {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
max-height: 60vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-status {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
@@ -6,7 +6,7 @@
|
|||||||
<div class="modal-title h6">
|
<div class="modal-title h6">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
|
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
|
||||||
<span class="cut-text">{{ $t('message.tableFiller') }}</span>
|
<span class="cut-text">{{ $tc('message.insertRow', 2) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
<label class="form-checkbox ml-3" :title="$t('word.insert')">
|
<label class="form-checkbox ml-3" :title="$t('word.insert')">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:checked="!field.autoIncrement"
|
:checked="!fieldsToExclude.includes(field.name)"
|
||||||
@change.prevent="toggleFields($event, field)"
|
@change.prevent="toggleFields($event, field)"
|
||||||
><i class="form-icon" />
|
><i class="form-icon" />
|
||||||
</label>
|
</label>
|
||||||
@@ -264,7 +264,7 @@ export default {
|
|||||||
else if (BIT.includes(field.type))
|
else if (BIT.includes(field.type))
|
||||||
fieldDefault = field.default.replaceAll('\'', '').replaceAll('b', '');
|
fieldDefault = field.default.replaceAll('\'', '').replaceAll('b', '');
|
||||||
else if (DATETIME.includes(field.type)) {
|
else if (DATETIME.includes(field.type)) {
|
||||||
if (field.default && ['current_timestamp', 'now()'].includes(field.default.toLowerCase())) {
|
if (field.default && ['current_timestamp', 'now()'].some(term => field.default.toLowerCase().includes(term))) {
|
||||||
let datePrecision = '';
|
let datePrecision = '';
|
||||||
for (let i = 0; i < field.datePrecision; i++)
|
for (let i = 0; i < field.datePrecision; i++)
|
||||||
datePrecision += i === 0 ? '.S' : 'S';
|
datePrecision += i === 0 ? '.S' : 'S';
|
||||||
@@ -281,7 +281,7 @@ export default {
|
|||||||
|
|
||||||
rowObj[field.name] = { value: fieldDefault };
|
rowObj[field.name] = { value: fieldDefault };
|
||||||
|
|
||||||
if (field.autoIncrement)// Disable by default auto increment fields
|
if (field.autoIncrement || !!field.onUpdate)// Disable by default auto increment or "on update" fields
|
||||||
this.fieldsToExclude = [...this.fieldsToExclude, field.name];
|
this.fieldsToExclude = [...this.fieldsToExclude, field.name];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
181
src/renderer/components/ModalImportSchema.vue
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
<template>
|
||||||
|
<div class="modal active">
|
||||||
|
<a class="modal-overlay" @click.stop="closeModal" />
|
||||||
|
<div class="modal-container p-0">
|
||||||
|
<div class="modal-header pl-2">
|
||||||
|
<div class="modal-title h6">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="mdi mdi-24px mdi-database-arrow-up mr-1" />
|
||||||
|
<span class="cut-text">{{ $t('message.importSchema') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||||
|
</div>
|
||||||
|
<div class="modal-body pb-0">
|
||||||
|
{{ sqlFile }}
|
||||||
|
<div v-if="queryErrors.length > 0" class="mt-2">
|
||||||
|
<label>{{ $tc('message.importQueryErrors', queryErrors.length) }}</label>
|
||||||
|
<textarea
|
||||||
|
v-model="formattedQueryErrors"
|
||||||
|
class="form-input"
|
||||||
|
rows="5"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer columns">
|
||||||
|
<div class="column col modal-progress-wrapper text-left">
|
||||||
|
<div class="import-progress">
|
||||||
|
<span class="progress-status">
|
||||||
|
{{ progressPercentage }}% - {{ progressStatus }} - {{ $tc('message.executedQueries', queryCount) }}
|
||||||
|
</span>
|
||||||
|
<progress
|
||||||
|
class="progress d-block"
|
||||||
|
:value="progressPercentage"
|
||||||
|
max="100"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column col-auto px-0">
|
||||||
|
<button class="btn btn-link" @click.stop="closeModal">
|
||||||
|
{{ completed ? $t('word.close') : $t('word.cancel') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { ipcRenderer } from 'electron';
|
||||||
|
import { mapActions, mapGetters } from 'vuex';
|
||||||
|
import moment from 'moment';
|
||||||
|
import Schema from '@/ipc-api/Schema';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ModalImportSchema',
|
||||||
|
|
||||||
|
props: {
|
||||||
|
selectedSchema: String
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
sqlFile: '',
|
||||||
|
isImporting: false,
|
||||||
|
progressPercentage: 0,
|
||||||
|
queryCount: 0,
|
||||||
|
completed: false,
|
||||||
|
progressStatus: 'Reading',
|
||||||
|
queryErrors: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
selectedWorkspace: 'workspaces/getSelected',
|
||||||
|
getWorkspace: 'workspaces/getWorkspace'
|
||||||
|
}),
|
||||||
|
currentWorkspace () {
|
||||||
|
return this.getWorkspace(this.selectedWorkspace);
|
||||||
|
},
|
||||||
|
formattedQueryErrors () {
|
||||||
|
return this.queryErrors.map(err =>
|
||||||
|
`Time: ${moment(err.time).format('HH:mm:ss.S')} (${err.time})\nError: ${err.message}`
|
||||||
|
).join('\n\n');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async created () {
|
||||||
|
window.addEventListener('keydown', this.onKey);
|
||||||
|
|
||||||
|
ipcRenderer.on('import-progress', this.updateProgress);
|
||||||
|
ipcRenderer.on('query-error', this.handleQueryError);
|
||||||
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
window.removeEventListener('keydown', this.onKey);
|
||||||
|
ipcRenderer.off('import-progress', this.updateProgress);
|
||||||
|
ipcRenderer.off('query-error', this.handleQueryError);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions({
|
||||||
|
addNotification: 'notifications/addNotification',
|
||||||
|
refreshSchema: 'workspaces/refreshSchema'
|
||||||
|
}),
|
||||||
|
async startImport (sqlFile) {
|
||||||
|
this.isImporting = true;
|
||||||
|
this.sqlFile = sqlFile;
|
||||||
|
|
||||||
|
const { uid, client } = this.currentWorkspace;
|
||||||
|
const params = {
|
||||||
|
uid,
|
||||||
|
type: client,
|
||||||
|
schema: this.selectedSchema,
|
||||||
|
file: sqlFile
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.completed = false;
|
||||||
|
const { status, response } = await Schema.import(params);
|
||||||
|
if (status === 'success')
|
||||||
|
this.progressStatus = response.cancelled ? this.$t('word.aborted') : this.$t('word.completed');
|
||||||
|
else {
|
||||||
|
this.progressStatus = response;
|
||||||
|
this.addNotification({ status: 'error', message: response });
|
||||||
|
}
|
||||||
|
this.refreshSchema({ uid, schema: this.selectedSchema });
|
||||||
|
this.completed = true;
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isImporting = false;
|
||||||
|
},
|
||||||
|
updateProgress (event, state) {
|
||||||
|
this.progressPercentage = Number(state.percentage).toFixed(1);
|
||||||
|
this.queryCount = Number(state.queryCount);
|
||||||
|
},
|
||||||
|
handleQueryError (event, err) {
|
||||||
|
this.queryErrors.push(err);
|
||||||
|
},
|
||||||
|
async closeModal () {
|
||||||
|
let willClose = true;
|
||||||
|
if (this.isImporting) {
|
||||||
|
willClose = false;
|
||||||
|
const { response } = await Schema.abortImport();
|
||||||
|
willClose = response.willAbort;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (willClose)
|
||||||
|
this.$emit('close');
|
||||||
|
},
|
||||||
|
onKey (e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (e.key === 'Escape')
|
||||||
|
this.closeModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.modal {
|
||||||
|
|
||||||
|
.modal-container {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
max-height: 60vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-status {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
</style>
|
@@ -22,12 +22,12 @@
|
|||||||
:hide-footer="true"
|
:hide-footer="true"
|
||||||
@hide="hideInfoModal"
|
@hide="hideInfoModal"
|
||||||
>
|
>
|
||||||
<template :slot="'header'">
|
<template #header>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-information-outline mr-1" /> {{ $t('message.processInfo') }}
|
<i class="mdi mdi-24px mdi-information-outline mr-1" /> {{ $t('message.processInfo') }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div :slot="'body'">
|
<template #body>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<TextEditor
|
<TextEditor
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@@ -117,6 +117,19 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group mb-0">
|
||||||
|
<div class="col-7 col-sm-12">
|
||||||
|
<label class="form-label">
|
||||||
|
{{ $t('message.disableBlur') }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 col-sm-12">
|
||||||
|
<label class="form-switch d-inline-block" @click.prevent="toggleDisableBlur">
|
||||||
|
<input type="checkbox" :checked="disableBlur">
|
||||||
|
<i class="form-icon" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-7 col-sm-12">
|
<div class="col-7 col-sm-12">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
@@ -282,12 +295,18 @@
|
|||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<img src="../images/logo.svg" width="128">
|
<img src="../images/logo.svg" width="128">
|
||||||
<h4>{{ appName }}</h4>
|
<h4>{{ appName }}</h4>
|
||||||
<p>
|
<p class="mb-2">
|
||||||
{{ $t('word.version') }} {{ appVersion }}<br>
|
{{ $t('word.version') }} {{ appVersion }}<br>
|
||||||
<a class="c-hand" @click="openOutside('https://github.com/Fabio286/antares')"><i class="mdi mdi-github d-inline" /> GitHub</a> • <a class="c-hand" @click="openOutside('https://twitter.com/AntaresSQL')"><i class="mdi mdi-twitter d-inline" /> Twitter</a> • <a class="c-hand" @click="openOutside('https://antares-sql.app/')"><i class="mdi mdi-web d-inline" /> Website</a><br>
|
<a class="c-hand" @click="openOutside('https://github.com/Fabio286/antares')"><i class="mdi mdi-github d-inline" /> GitHub</a> • <a class="c-hand" @click="openOutside('https://twitter.com/AntaresSQL')"><i class="mdi mdi-twitter d-inline" /> Twitter</a> • <a class="c-hand" @click="openOutside('https://antares-sql.app/')"><i class="mdi mdi-web d-inline" /> Website</a><br>
|
||||||
<small>{{ $t('word.author') }} <a class="c-hand" @click="openOutside('https://github.com/Fabio286')">Fabio Di Stasio</a></small><br>
|
<small>{{ $t('word.author') }} <a class="c-hand" @click="openOutside('https://github.com/Fabio286')">{{ appAuthor }}</a></small><br>
|
||||||
<small>{{ $t('message.madeWithJS') }}</small>
|
|
||||||
</p>
|
</p>
|
||||||
|
<div class="mb-2">
|
||||||
|
<small class="d-block text-uppercase">{{ $t('word.contributors') }}:</small>
|
||||||
|
<div class="d-block py-1">
|
||||||
|
<small v-for="(contributor, i) in otherContributors" :key="i">{{ i !== 0 ? ', ' : '' }}{{ contributor }}</small>
|
||||||
|
</div>
|
||||||
|
<small>{{ $t('message.madeWithJS') }}</small>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -313,6 +332,7 @@ export default {
|
|||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
appAuthor: 'Fabio Di Stasio',
|
||||||
localLocale: null,
|
localLocale: null,
|
||||||
localPageSize: null,
|
localPageSize: null,
|
||||||
localTimeout: null,
|
localTimeout: null,
|
||||||
@@ -367,7 +387,8 @@ export default {
|
|||||||
{ code: 'vibrant_ink', name: 'Vibrant Ink' }
|
{ code: 'vibrant_ink', name: 'Vibrant Ink' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
contributors: process.env.APP_CONTRIBUTORS
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -381,6 +402,7 @@ export default {
|
|||||||
selectedLineWrap: 'settings/getLineWrap',
|
selectedLineWrap: 'settings/getLineWrap',
|
||||||
notificationsTimeout: 'settings/getNotificationsTimeout',
|
notificationsTimeout: 'settings/getNotificationsTimeout',
|
||||||
restoreTabs: 'settings/getRestoreTabs',
|
restoreTabs: 'settings/getRestoreTabs',
|
||||||
|
disableBlur: 'settings/getDisableBlur',
|
||||||
applicationTheme: 'settings/getApplicationTheme',
|
applicationTheme: 'settings/getApplicationTheme',
|
||||||
editorTheme: 'settings/getEditorTheme',
|
editorTheme: 'settings/getEditorTheme',
|
||||||
editorFontSize: 'settings/getEditorFontSize',
|
editorFontSize: 'settings/getEditorFontSize',
|
||||||
@@ -417,6 +439,12 @@ GROUP BY
|
|||||||
ORDER BY
|
ORDER BY
|
||||||
employee.id ASC;
|
employee.id ASC;
|
||||||
`;
|
`;
|
||||||
|
},
|
||||||
|
otherContributors () {
|
||||||
|
return this.contributors
|
||||||
|
.split(',')
|
||||||
|
.filter(c => !c.includes(this.appAuthor))
|
||||||
|
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
@@ -436,6 +464,7 @@ ORDER BY
|
|||||||
changeLocale: 'settings/changeLocale',
|
changeLocale: 'settings/changeLocale',
|
||||||
changePageSize: 'settings/changePageSize',
|
changePageSize: 'settings/changePageSize',
|
||||||
changeRestoreTabs: 'settings/changeRestoreTabs',
|
changeRestoreTabs: 'settings/changeRestoreTabs',
|
||||||
|
changeDisableBlur: 'settings/changeDisableBlur',
|
||||||
changeAutoComplete: 'settings/changeAutoComplete',
|
changeAutoComplete: 'settings/changeAutoComplete',
|
||||||
changeLineWrap: 'settings/changeLineWrap',
|
changeLineWrap: 'settings/changeLineWrap',
|
||||||
changeApplicationTheme: 'settings/changeApplicationTheme',
|
changeApplicationTheme: 'settings/changeApplicationTheme',
|
||||||
@@ -463,6 +492,9 @@ ORDER BY
|
|||||||
toggleRestoreSession () {
|
toggleRestoreSession () {
|
||||||
this.changeRestoreTabs(!this.restoreTabs);
|
this.changeRestoreTabs(!this.restoreTabs);
|
||||||
},
|
},
|
||||||
|
toggleDisableBlur () {
|
||||||
|
this.changeDisableBlur(!this.disableBlur);
|
||||||
|
},
|
||||||
toggleAutoComplete () {
|
toggleAutoComplete () {
|
||||||
this.changeAutoComplete(!this.selectedAutoComplete);
|
this.changeAutoComplete(!this.selectedAutoComplete);
|
||||||
},
|
},
|
||||||
|
@@ -15,16 +15,16 @@
|
|||||||
@confirm="confirmDeleteConnection"
|
@confirm="confirmDeleteConnection"
|
||||||
@hide="hideConfirmModal"
|
@hide="hideConfirmModal"
|
||||||
>
|
>
|
||||||
<template :slot="'header'">
|
<template #header>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-server-remove mr-1" /> {{ $t('message.deleteConnection') }}
|
<i class="mdi mdi-24px mdi-server-remove mr-1" /> {{ $t('message.deleteConnection') }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div :slot="'body'">
|
<template #body>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
{{ $t('message.deleteCorfirm') }} <b>{{ connectionName }}</b>?
|
{{ $t('message.deleteCorfirm') }} <b>{{ connectionName }}</b>?
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
</BaseContextMenu>
|
</BaseContextMenu>
|
||||||
</template>
|
</template>
|
||||||
|
@@ -11,9 +11,9 @@
|
|||||||
|
|
||||||
<div class="footer-right-elements">
|
<div class="footer-right-elements">
|
||||||
<ul class="footer-elements">
|
<ul class="footer-elements">
|
||||||
<li class="footer-element footer-link" @click="openOutside('https://www.treedom.net/en/user/fabio-di-stasio/event/antares-for-the-planet')">
|
<li class="footer-element footer-link" @click="openOutside('https://www.paypal.com/paypalme/fabiodistasio')">
|
||||||
<i class="mdi mdi-18px mdi-tree mr-1" />
|
<i class="mdi mdi-18px mdi-coffee mr-1" />
|
||||||
<small>{{ $t('message.plantATree') }}</small>
|
<small>{{ $t('word.donate') }}</small>
|
||||||
</li>
|
</li>
|
||||||
<li class="footer-element footer-link" @click="openOutside('https://github.com/Fabio286/antares/issues')">
|
<li class="footer-element footer-link" @click="openOutside('https://github.com/Fabio286/antares/issues')">
|
||||||
<i class="mdi mdi-18px mdi-bug" />
|
<i class="mdi mdi-18px mdi-bug" />
|
||||||
|
@@ -6,12 +6,12 @@
|
|||||||
:hide-footer="true"
|
:hide-footer="true"
|
||||||
@hide="hideScratchpad"
|
@hide="hideScratchpad"
|
||||||
>
|
>
|
||||||
<template :slot="'header'">
|
<template #header>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-notebook-edit-outline mr-1" /> {{ $t('word.scratchpad') }}
|
<i class="mdi mdi-24px mdi-notebook-edit-outline mr-1" /> {{ $t('word.scratchpad') }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div :slot="'body'">
|
<template #body>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<TextEditor
|
<TextEditor
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<small class="text-gray">{{ $t('message.markdownSupported') }}</small>
|
<small class="text-gray">{{ $t('message.markdownSupported') }}</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@@ -200,7 +200,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: "";
|
||||||
height: 0;
|
height: 0;
|
||||||
width: 3px;
|
width: 3px;
|
||||||
transition: height 0.2s;
|
transition: height 0.2s;
|
||||||
|
@@ -18,14 +18,17 @@
|
|||||||
<li
|
<li
|
||||||
v-for="(tab, i) of draggableTabs"
|
v-for="(tab, i) of draggableTabs"
|
||||||
:key="i"
|
:key="i"
|
||||||
:ref="selectedTab === tab.uid ? 'tab-selected' : ''"
|
|
||||||
class="tab-item tab-draggable"
|
class="tab-item tab-draggable"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
:class="{'active': selectedTab === tab.uid}"
|
:class="{'active': selectedTab === tab.uid}"
|
||||||
@mousedown.left="selectTab({uid: workspace.uid, tab: tab.uid})"
|
@mousedown.left="selectTab({uid: workspace.uid, tab: tab.uid})"
|
||||||
@mouseup.middle="closeTab(tab)"
|
@mouseup.middle="closeTab(tab)"
|
||||||
>
|
>
|
||||||
<a v-if="tab.type === 'query'" class="tab-link">
|
<a
|
||||||
|
v-if="tab.type === 'query'"
|
||||||
|
class="tab-link"
|
||||||
|
:class="{'badge': tab.isChanged}"
|
||||||
|
>
|
||||||
<i class="mdi mdi-18px mdi-code-tags mr-1" />
|
<i class="mdi mdi-18px mdi-code-tags mr-1" />
|
||||||
<span>
|
<span>
|
||||||
<span>{{ tab.content || 'Query' | cutText }} #{{ tab.index }}</span>
|
<span>{{ tab.content || 'Query' | cutText }} #{{ tab.index }}</span>
|
||||||
@@ -256,56 +259,59 @@
|
|||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<template #header>
|
||||||
v-if="workspace.customizations.processesList"
|
<li
|
||||||
slot="header"
|
v-if="workspace.customizations.processesList"
|
||||||
class="tab-item dropdown tools-dropdown"
|
class="tab-item dropdown tools-dropdown"
|
||||||
>
|
|
||||||
<a
|
|
||||||
class="tab-link workspace-tools-link dropdown-toggle"
|
|
||||||
tabindex="0"
|
|
||||||
:title="$t('word.tools')"
|
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-tools" />
|
<a
|
||||||
</a>
|
class="tab-link workspace-tools-link dropdown-toggle"
|
||||||
<ul v-if="hasTools" class="menu text-left text-uppercase">
|
tabindex="0"
|
||||||
<li class="menu-item">
|
:title="$t('word.tools')"
|
||||||
<a class="c-hand p-vcentered" @click="showProcessesModal">
|
|
||||||
<i class="mdi mdi-memory mr-1 tool-icon" />
|
|
||||||
<span>{{ $t('message.processesList') }}</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
v-if="workspace.customizations.variables"
|
|
||||||
class="menu-item"
|
|
||||||
title="Coming..."
|
|
||||||
>
|
>
|
||||||
<a class="c-hand p-vcentered disabled">
|
<i class="mdi mdi-24px mdi-tools" />
|
||||||
<i class="mdi mdi-shape mr-1 tool-icon" />
|
</a>
|
||||||
<span>{{ $t('word.variables') }}</span>
|
<ul v-if="hasTools" class="menu text-left text-uppercase">
|
||||||
</a>
|
<li class="menu-item">
|
||||||
</li>
|
<a class="c-hand p-vcentered" @click="showProcessesModal">
|
||||||
<li
|
<i class="mdi mdi-memory mr-1 tool-icon" />
|
||||||
v-if="workspace.customizations.usersManagement"
|
<span>{{ $t('message.processesList') }}</span>
|
||||||
class="menu-item"
|
</a>
|
||||||
title="Coming..."
|
</li>
|
||||||
|
<li
|
||||||
|
v-if="workspace.customizations.variables"
|
||||||
|
class="menu-item"
|
||||||
|
title="Coming..."
|
||||||
|
>
|
||||||
|
<a class="c-hand p-vcentered disabled">
|
||||||
|
<i class="mdi mdi-shape mr-1 tool-icon" />
|
||||||
|
<span>{{ $t('word.variables') }}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
v-if="workspace.customizations.usersManagement"
|
||||||
|
class="menu-item"
|
||||||
|
title="Coming..."
|
||||||
|
>
|
||||||
|
<a class="c-hand p-vcentered disabled">
|
||||||
|
<i class="mdi mdi-account-group mr-1 tool-icon" />
|
||||||
|
<span>{{ $t('message.manageUsers') }}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<li class="tab-item">
|
||||||
|
<a
|
||||||
|
class="tab-add"
|
||||||
|
:title="$t('message.openNewTab')"
|
||||||
|
@click="addQueryTab"
|
||||||
>
|
>
|
||||||
<a class="c-hand p-vcentered disabled">
|
<i class="mdi mdi-24px mdi-plus" />
|
||||||
<i class="mdi mdi-account-group mr-1 tool-icon" />
|
</a>
|
||||||
<span>{{ $t('message.manageUsers') }}</span>
|
</li>
|
||||||
</a>
|
</template>
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li slot="footer" class="tab-item">
|
|
||||||
<a
|
|
||||||
class="tab-add"
|
|
||||||
:title="$t('message.openNewTab')"
|
|
||||||
@click="addQueryTab"
|
|
||||||
>
|
|
||||||
<i class="mdi mdi-24px mdi-plus" />
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</Draggable>
|
</Draggable>
|
||||||
<WorkspaceEmptyState v-if="!workspace.tabs.length" @new-tab="addQueryTab" />
|
<WorkspaceEmptyState v-if="!workspace.tabs.length" @new-tab="addQueryTab" />
|
||||||
<template v-for="tab of workspace.tabs">
|
<template v-for="tab of workspace.tabs">
|
||||||
@@ -444,7 +450,9 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<WorkspaceEditConnectionPanel v-else :connection="connection" />
|
<div v-else class="connection-panel-wrapper">
|
||||||
|
<WorkspaceEditConnectionPanel :connection="connection" />
|
||||||
|
</div>
|
||||||
<ModalProcessesList
|
<ModalProcessesList
|
||||||
v-if="isProcessesModal"
|
v-if="isProcessesModal"
|
||||||
:connection="connection"
|
:connection="connection"
|
||||||
@@ -565,7 +573,7 @@ export default {
|
|||||||
return this.workspace ? this.workspace.selectedTab : null;
|
return this.workspace ? this.workspace.selectedTab : null;
|
||||||
},
|
},
|
||||||
queryTabs () {
|
queryTabs () {
|
||||||
return this.workspace.tabs.filter(tab => tab.type === 'query');
|
return this.workspace ? this.workspace.tabs.filter(tab => tab.type === 'query') : [];
|
||||||
},
|
},
|
||||||
schemaChild () {
|
schemaChild () {
|
||||||
for (const key in this.workspace.breadcrumbs) {
|
for (const key in this.workspace.breadcrumbs) {
|
||||||
@@ -581,16 +589,12 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
selectedTab (newVal, oldVal) {
|
queryTabs: function (newVal, oldVal) {
|
||||||
if (newVal !== oldVal) {
|
if (newVal.length > oldVal.length) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const element = this.$refs['tab-selected'] ? this.$refs['tab-selected'][0] : null;
|
const scroller = this.$refs.tabWrap;
|
||||||
if (element) {
|
if (scroller) scroller.$el.scrollLeft = scroller.$el.scrollWidth;
|
||||||
element.setAttribute('tabindex', '-1');
|
}, 0);
|
||||||
element.focus();
|
|
||||||
element.removeAttribute('tabindex');
|
|
||||||
}
|
|
||||||
}, 50);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -34,7 +34,7 @@
|
|||||||
<fieldset class="m-0" :disabled="isBusy">
|
<fieldset class="m-0" :disabled="isBusy">
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.connectionName') }}</label>
|
<label class="form-label cut-text">{{ $t('word.connectionName') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.client') }}</label>
|
<label class="form-label cut-text">{{ $t('word.client') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<select v-model="connection.client" class="form-select">
|
<select v-model="connection.client" class="form-select">
|
||||||
@@ -61,9 +61,22 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="connection.client === 'pg'" class="form-group columns">
|
||||||
|
<div class="column col-4 col-sm-12">
|
||||||
|
<label class="form-label cut-text">{{ $t('word.connectionString') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-8 col-sm-12">
|
||||||
|
<input
|
||||||
|
ref="pgString"
|
||||||
|
v-model="connection.pgConnString"
|
||||||
|
class="form-input"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.hostName') }}/IP</label>
|
<label class="form-label cut-text">{{ $t('word.hostName') }}/IP</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -75,7 +88,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="customizations.fileConnection" class="form-group columns">
|
<div v-if="customizations.fileConnection" class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.database') }}</label>
|
<label class="form-label cut-text">{{ $t('word.database') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<BaseUploadInput
|
<BaseUploadInput
|
||||||
@@ -88,7 +101,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.port') }}</label>
|
<label class="form-label cut-text">{{ $t('word.port') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -102,7 +115,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="customizations.database" class="form-group columns">
|
<div v-if="customizations.database" class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.database') }}</label>
|
<label class="form-label cut-text">{{ $t('word.database') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -114,7 +127,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.user') }}</label>
|
<label class="form-label cut-text">{{ $t('word.user') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -127,7 +140,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.password') }}</label>
|
<label class="form-label cut-text">{{ $t('word.password') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -140,7 +153,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="customizations.connectionSchema" class="form-group columns">
|
<div v-if="customizations.connectionSchema" class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.schema') }}</label>
|
<label class="form-label cut-text">{{ $t('word.schema') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -176,7 +189,7 @@
|
|||||||
<form class="form-horizontal">
|
<form class="form-horizontal">
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">
|
<label class="form-label cut-text">
|
||||||
{{ $t('message.enableSsl') }}
|
{{ $t('message.enableSsl') }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -190,7 +203,7 @@
|
|||||||
<fieldset class="m-0" :disabled="isBusy || !connection.ssl">
|
<fieldset class="m-0" :disabled="isBusy || !connection.ssl">
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.privateKey') }}</label>
|
<label class="form-label cut-text">{{ $t('word.privateKey') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<BaseUploadInput
|
<BaseUploadInput
|
||||||
@@ -203,7 +216,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.certificate') }}</label>
|
<label class="form-label cut-text">{{ $t('word.certificate') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<BaseUploadInput
|
<BaseUploadInput
|
||||||
@@ -216,7 +229,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.caCertificate') }}</label>
|
<label class="form-label cut-text">{{ $t('word.caCertificate') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<BaseUploadInput
|
<BaseUploadInput
|
||||||
@@ -229,7 +242,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.ciphers') }}</label>
|
<label class="form-label cut-text">{{ $t('word.ciphers') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -249,7 +262,7 @@
|
|||||||
<form class="form-horizontal">
|
<form class="form-horizontal">
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">
|
<label class="form-label cut-text">
|
||||||
{{ $t('message.enableSsh') }}
|
{{ $t('message.enableSsh') }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -263,7 +276,7 @@
|
|||||||
<fieldset class="m-0" :disabled="isBusy || !connection.ssh">
|
<fieldset class="m-0" :disabled="isBusy || !connection.ssh">
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.hostName') }}/IP</label>
|
<label class="form-label cut-text">{{ $t('word.hostName') }}/IP</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -275,7 +288,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.user') }}</label>
|
<label class="form-label cut-text">{{ $t('word.user') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -287,7 +300,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.password') }}</label>
|
<label class="form-label cut-text">{{ $t('word.password') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -299,7 +312,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.port') }}</label>
|
<label class="form-label cut-text">{{ $t('word.port') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -313,7 +326,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.privateKey') }}</label>
|
<label class="form-label cut-text">{{ $t('word.privateKey') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<BaseUploadInput
|
<BaseUploadInput
|
||||||
@@ -326,7 +339,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.passphrase') }}</label>
|
<label class="form-label cut-text">{{ $t('word.passphrase') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -412,7 +425,8 @@ export default {
|
|||||||
sshUser: '',
|
sshUser: '',
|
||||||
sshPass: '',
|
sshPass: '',
|
||||||
sshKey: '',
|
sshKey: '',
|
||||||
sshPort: 22
|
sshPort: 22,
|
||||||
|
pgConnString: ''
|
||||||
},
|
},
|
||||||
isConnecting: false,
|
isConnecting: false,
|
||||||
isTesting: false,
|
isTesting: false,
|
||||||
@@ -540,12 +554,12 @@ export default {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.connection-panel {
|
.connection-panel {
|
||||||
margin-top: 15vh;
|
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
width: 450px;
|
min-width: 450px;
|
||||||
border-radius: $border-radius;
|
border-radius: $border-radius;
|
||||||
|
|
||||||
.panel-body {
|
.panel-body {
|
||||||
|
@@ -34,7 +34,7 @@
|
|||||||
<fieldset class="m-0" :disabled="isBusy">
|
<fieldset class="m-0" :disabled="isBusy">
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.connectionName') }}</label>
|
<label class="form-label cut-text">{{ $t('word.connectionName') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.client') }}</label>
|
<label class="form-label cut-text">{{ $t('word.client') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<select v-model="localConnection.client" class="form-select">
|
<select v-model="localConnection.client" class="form-select">
|
||||||
@@ -61,9 +61,22 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="connection.client === 'pg'" class="form-group columns">
|
||||||
|
<div class="column col-4 col-sm-12">
|
||||||
|
<label class="form-label cut-text">{{ $t('word.connectionString') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="column col-8 col-sm-12">
|
||||||
|
<input
|
||||||
|
ref="pgString"
|
||||||
|
v-model="localConnection.pgConnString"
|
||||||
|
class="form-input"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.hostName') }}/IP</label>
|
<label class="form-label cut-text">{{ $t('word.hostName') }}/IP</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -75,7 +88,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="customizations.fileConnection" class="form-group columns">
|
<div v-if="customizations.fileConnection" class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.database') }}</label>
|
<label class="form-label cut-text">{{ $t('word.database') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<BaseUploadInput
|
<BaseUploadInput
|
||||||
@@ -88,7 +101,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.port') }}</label>
|
<label class="form-label cut-text">{{ $t('word.port') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -102,7 +115,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="customizations.database" class="form-group columns">
|
<div v-if="customizations.database" class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.database') }}</label>
|
<label class="form-label cut-text">{{ $t('word.database') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -114,7 +127,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.user') }}</label>
|
<label class="form-label cut-text">{{ $t('word.user') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -127,7 +140,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.password') }}</label>
|
<label class="form-label cut-text">{{ $t('word.password') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -140,7 +153,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="customizations.connectionSchema" class="form-group columns">
|
<div v-if="customizations.connectionSchema" class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.schema') }}</label>
|
<label class="form-label cut-text">{{ $t('word.schema') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -176,7 +189,7 @@
|
|||||||
<form class="form-horizontal">
|
<form class="form-horizontal">
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">
|
<label class="form-label cut-text">
|
||||||
{{ $t('message.enableSsl') }}
|
{{ $t('message.enableSsl') }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -190,7 +203,7 @@
|
|||||||
<fieldset class="m-0" :disabled="isBusy || !localConnection.ssl">
|
<fieldset class="m-0" :disabled="isBusy || !localConnection.ssl">
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.privateKey') }}</label>
|
<label class="form-label cut-text">{{ $t('word.privateKey') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<BaseUploadInput
|
<BaseUploadInput
|
||||||
@@ -203,7 +216,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.certificate') }}</label>
|
<label class="form-label cut-text">{{ $t('word.certificate') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<BaseUploadInput
|
<BaseUploadInput
|
||||||
@@ -216,7 +229,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.caCertificate') }}</label>
|
<label class="form-label cut-text">{{ $t('word.caCertificate') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<BaseUploadInput
|
<BaseUploadInput
|
||||||
@@ -229,7 +242,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.ciphers') }}</label>
|
<label class="form-label cut-text">{{ $t('word.ciphers') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -249,7 +262,7 @@
|
|||||||
<form class="form-horizontal">
|
<form class="form-horizontal">
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">
|
<label class="form-label cut-text">
|
||||||
{{ $t('message.enableSsh') }}
|
{{ $t('message.enableSsh') }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -263,7 +276,7 @@
|
|||||||
<fieldset class="m-0" :disabled="isBusy || !localConnection.ssh">
|
<fieldset class="m-0" :disabled="isBusy || !localConnection.ssh">
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.hostName') }}/IP</label>
|
<label class="form-label cut-text">{{ $t('word.hostName') }}/IP</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -275,7 +288,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.user') }}</label>
|
<label class="form-label cut-text">{{ $t('word.user') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -287,7 +300,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.password') }}</label>
|
<label class="form-label cut-text">{{ $t('word.password') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -299,7 +312,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.port') }}</label>
|
<label class="form-label cut-text">{{ $t('word.port') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -313,7 +326,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.privateKey') }}</label>
|
<label class="form-label cut-text">{{ $t('word.privateKey') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<BaseUploadInput
|
<BaseUploadInput
|
||||||
@@ -326,7 +339,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group columns">
|
<div class="form-group columns">
|
||||||
<div class="column col-4 col-sm-12">
|
<div class="column col-4 col-sm-12">
|
||||||
<label class="form-label">{{ $t('word.passphrase') }}</label>
|
<label class="form-label cut-text">{{ $t('word.passphrase') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="column col-8 col-sm-12">
|
<div class="column col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
@@ -519,12 +532,12 @@ export default {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.connection-panel {
|
.connection-panel {
|
||||||
margin-top: 15vh;
|
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
width: 450px;
|
min-width: 450px;
|
||||||
border-radius: $border-radius;
|
border-radius: $border-radius;
|
||||||
|
|
||||||
.panel-body {
|
.panel-body {
|
||||||
|
@@ -42,17 +42,17 @@
|
|||||||
@confirm="deleteMisc"
|
@confirm="deleteMisc"
|
||||||
@hide="hideDeleteModal"
|
@hide="hideDeleteModal"
|
||||||
>
|
>
|
||||||
<template slot="header">
|
<template #header>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-delete mr-1" />
|
<i class="mdi mdi-24px mdi-delete mr-1" />
|
||||||
<span class="cut-text">{{ deleteMessage }}</span>
|
<span class="cut-text">{{ deleteMessage }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div slot="body">
|
<template #body>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
{{ $t('message.deleteCorfirm') }} "<b>{{ selectedMisc.name }}</b>"?
|
{{ $t('message.deleteCorfirm') }} "<b>{{ selectedMisc.name }}</b>"?
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
<ModalAskParameters
|
<ModalAskParameters
|
||||||
v-if="isAskingParameters"
|
v-if="isAskingParameters"
|
||||||
|
@@ -452,9 +452,9 @@ export default {
|
|||||||
top: 0;
|
top: 0;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
|
||||||
.schema-size{
|
.schema-size {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
width: 22.5px;
|
width: 22.5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,8 +502,8 @@ export default {
|
|||||||
&:hover {
|
&:hover {
|
||||||
border-radius: $border-radius;
|
border-radius: $border-radius;
|
||||||
|
|
||||||
.schema-size{
|
.schema-size {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -58,6 +58,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="workspace.customizations.schemaExport"
|
||||||
|
class="context-element"
|
||||||
|
@click="showExportSchemaModal"
|
||||||
|
>
|
||||||
|
<span class="d-flex"><i class="mdi mdi-18px mdi-database-arrow-down text-light pr-1" /> {{ $t('word.export') }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="workspace.customizations.schemaImport"
|
||||||
|
class="context-element"
|
||||||
|
@click="initImport"
|
||||||
|
>
|
||||||
|
<span class="d-flex"><i class="mdi mdi-18px mdi-database-arrow-up text-light pr-1" /> {{ $t('word.import') }}</span>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="workspace.customizations.schemaEdit"
|
v-if="workspace.customizations.schemaEdit"
|
||||||
class="context-element"
|
class="context-element"
|
||||||
@@ -78,23 +92,34 @@
|
|||||||
@confirm="deleteSchema"
|
@confirm="deleteSchema"
|
||||||
@hide="hideDeleteModal"
|
@hide="hideDeleteModal"
|
||||||
>
|
>
|
||||||
<template slot="header">
|
<template #header>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-database-remove mr-1" />
|
<i class="mdi mdi-24px mdi-database-remove mr-1" />
|
||||||
<span class="cut-text">{{ $t('message.deleteSchema') }}</span>
|
<span class="cut-text">{{ $t('message.deleteSchema') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div slot="body">
|
<template #body>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
{{ $t('message.deleteCorfirm') }} "<b>{{ selectedSchema }}</b>"?
|
{{ $t('message.deleteCorfirm') }} "<b>{{ selectedSchema }}</b>"?
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
<ModalEditSchema
|
<ModalEditSchema
|
||||||
v-if="isEditModal"
|
v-if="isEditModal"
|
||||||
:selected-schema="selectedSchema"
|
:selected-schema="selectedSchema"
|
||||||
@close="hideEditModal"
|
@close="hideEditModal"
|
||||||
/>
|
/>
|
||||||
|
<ModalExportSchema
|
||||||
|
v-if="isExportSchemaModal"
|
||||||
|
:selected-schema="selectedSchema"
|
||||||
|
@close="hideExportSchemaModal"
|
||||||
|
/>
|
||||||
|
<ModalImportSchema
|
||||||
|
v-if="isImportSchemaModal"
|
||||||
|
ref="importModalRef"
|
||||||
|
:selected-schema="selectedSchema"
|
||||||
|
@close="hideImportSchemaModal"
|
||||||
|
/>
|
||||||
</BaseContextMenu>
|
</BaseContextMenu>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -103,14 +128,19 @@ import { mapGetters, mapActions } from 'vuex';
|
|||||||
import BaseContextMenu from '@/components/BaseContextMenu';
|
import BaseContextMenu from '@/components/BaseContextMenu';
|
||||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||||
import ModalEditSchema from '@/components/ModalEditSchema';
|
import ModalEditSchema from '@/components/ModalEditSchema';
|
||||||
|
import ModalExportSchema from '@/components/ModalExportSchema';
|
||||||
|
import ModalImportSchema from '@/components/ModalImportSchema';
|
||||||
import Schema from '@/ipc-api/Schema';
|
import Schema from '@/ipc-api/Schema';
|
||||||
|
import Application from '@/ipc-api/Application';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'WorkspaceExploreBarSchemaContext',
|
name: 'WorkspaceExploreBarSchemaContext',
|
||||||
components: {
|
components: {
|
||||||
BaseContextMenu,
|
BaseContextMenu,
|
||||||
ConfirmModal,
|
ConfirmModal,
|
||||||
ModalEditSchema
|
ModalEditSchema,
|
||||||
|
ModalExportSchema,
|
||||||
|
ModalImportSchema
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
contextEvent: MouseEvent,
|
contextEvent: MouseEvent,
|
||||||
@@ -119,7 +149,9 @@ export default {
|
|||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isDeleteModal: false,
|
isDeleteModal: false,
|
||||||
isEditModal: false
|
isEditModal: false,
|
||||||
|
isExportSchemaModal: false,
|
||||||
|
isImportSchemaModal: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -170,6 +202,30 @@ export default {
|
|||||||
this.isEditModal = false;
|
this.isEditModal = false;
|
||||||
this.closeContext();
|
this.closeContext();
|
||||||
},
|
},
|
||||||
|
showExportSchemaModal () {
|
||||||
|
this.isExportSchemaModal = true;
|
||||||
|
},
|
||||||
|
hideExportSchemaModal () {
|
||||||
|
this.isExportSchemaModal = false;
|
||||||
|
this.closeContext();
|
||||||
|
},
|
||||||
|
showImportSchemaModal () {
|
||||||
|
this.isImportSchemaModal = true;
|
||||||
|
},
|
||||||
|
hideImportSchemaModal () {
|
||||||
|
this.isImportSchemaModal = false;
|
||||||
|
this.closeContext();
|
||||||
|
},
|
||||||
|
async initImport () {
|
||||||
|
const result = await Application.showOpenDialog({ properties: ['openFile'], filters: [{ name: 'SQL', extensions: ['sql'] }] });
|
||||||
|
if (result && !result.canceled) {
|
||||||
|
const file = result.filePaths[0];
|
||||||
|
this.showImportSchemaModal();
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.importModalRef.startImport(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
closeContext () {
|
closeContext () {
|
||||||
this.$emit('close-context');
|
this.$emit('close-context');
|
||||||
},
|
},
|
||||||
|
@@ -40,33 +40,33 @@
|
|||||||
@confirm="emptyTable"
|
@confirm="emptyTable"
|
||||||
@hide="hideEmptyModal"
|
@hide="hideEmptyModal"
|
||||||
>
|
>
|
||||||
<template slot="header">
|
<template #header>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-table-off mr-1" /> <span class="cut-text">{{ $t('message.emptyTable') }}</span>
|
<i class="mdi mdi-24px mdi-table-off mr-1" /> <span class="cut-text">{{ $t('message.emptyTable') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div slot="body">
|
<template #body>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
{{ $t('message.emptyCorfirm') }} "<b>{{ selectedTable.name }}</b>"?
|
{{ $t('message.emptyCorfirm') }} "<b>{{ selectedTable.name }}</b>"?
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
v-if="isDeleteModal"
|
v-if="isDeleteModal"
|
||||||
@confirm="deleteTable"
|
@confirm="deleteTable"
|
||||||
@hide="hideDeleteModal"
|
@hide="hideDeleteModal"
|
||||||
>
|
>
|
||||||
<template slot="header">
|
<template #header>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-table-remove mr-1" />
|
<i class="mdi mdi-24px mdi-table-remove mr-1" />
|
||||||
<span class="cut-text">{{ selectedTable.type === 'table' ? $t('message.deleteTable') : $t('message.deleteView') }}</span>
|
<span class="cut-text">{{ selectedTable.type === 'table' ? $t('message.deleteTable') : $t('message.deleteView') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div slot="body">
|
<template #body>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
{{ $t('message.deleteCorfirm') }} "<b>{{ selectedTable.name }}</b>"?
|
{{ $t('message.deleteCorfirm') }} "<b>{{ selectedTable.name }}</b>"?
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
</BaseContextMenu>
|
</BaseContextMenu>
|
||||||
</template>
|
</template>
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
<div class="workspace-query-buttons">
|
<div class="workspace-query-buttons">
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary btn-sm"
|
class="btn btn-primary btn-sm"
|
||||||
:disabled="!isChanged"
|
:disabled="!isChanged || !isValid"
|
||||||
:class="{'loading':isSaving}"
|
:class="{'loading':isSaving}"
|
||||||
title="CTRL+S"
|
title="CTRL+S"
|
||||||
@click="saveChanges"
|
@click="saveChanges"
|
||||||
@@ -242,6 +242,9 @@ export default {
|
|||||||
JSON.stringify(this.originalKeyUsage) !== JSON.stringify(this.localKeyUsage) ||
|
JSON.stringify(this.originalKeyUsage) !== JSON.stringify(this.localKeyUsage) ||
|
||||||
JSON.stringify(this.originalIndexes) !== JSON.stringify(this.localIndexes) ||
|
JSON.stringify(this.originalIndexes) !== JSON.stringify(this.localIndexes) ||
|
||||||
JSON.stringify(this.tableOptions) !== JSON.stringify(this.localOptions);
|
JSON.stringify(this.tableOptions) !== JSON.stringify(this.localOptions);
|
||||||
|
},
|
||||||
|
isValid () {
|
||||||
|
return !!this.localFields.length && !!this.localOptions.name.trim().length;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -287,7 +290,7 @@ export default {
|
|||||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
|
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
|
||||||
}),
|
}),
|
||||||
async saveChanges () {
|
async saveChanges () {
|
||||||
if (this.isSaving) return;
|
if (this.isSaving || !this.isValid) return;
|
||||||
this.isSaving = true;
|
this.isSaving = true;
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
|
@@ -6,13 +6,13 @@
|
|||||||
@confirm="confirmParametersChange"
|
@confirm="confirmParametersChange"
|
||||||
@hide="$emit('hide')"
|
@hide="$emit('hide')"
|
||||||
>
|
>
|
||||||
<template :slot="'header'">
|
<template #header>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
|
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
|
||||||
<span class="cut-text">{{ $t('word.parameters') }} "{{ func }}"</span>
|
<span class="cut-text">{{ $t('word.parameters') }} "{{ func }}"</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div :slot="'body'">
|
<template #body>
|
||||||
<div class="columns col-gapless">
|
<div class="columns col-gapless">
|
||||||
<div class="column col-5">
|
<div class="column col-5">
|
||||||
<div class="panel" :style="{ height: modalInnerHeight + 'px'}">
|
<div class="panel" :style="{ height: modalInnerHeight + 'px'}">
|
||||||
@@ -167,7 +167,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@@ -6,13 +6,13 @@
|
|||||||
@confirm="confirmParametersChange"
|
@confirm="confirmParametersChange"
|
||||||
@hide="$emit('hide')"
|
@hide="$emit('hide')"
|
||||||
>
|
>
|
||||||
<template :slot="'header'">
|
<template #header>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
|
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
|
||||||
<span class="cut-text">{{ $t('word.parameters') }} "{{ routine }}"</span>
|
<span class="cut-text">{{ $t('word.parameters') }} "{{ routine }}"</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div :slot="'body'">
|
<template #body>
|
||||||
<div class="columns col-gapless">
|
<div class="columns col-gapless">
|
||||||
<div class="column col-5">
|
<div class="column col-5">
|
||||||
<div class="panel" :style="{ height: modalInnerHeight + 'px'}">
|
<div class="panel" :style="{ height: modalInnerHeight + 'px'}">
|
||||||
@@ -167,7 +167,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@@ -5,13 +5,13 @@
|
|||||||
@confirm="confirmOptionsChange"
|
@confirm="confirmOptionsChange"
|
||||||
@hide="$emit('hide')"
|
@hide="$emit('hide')"
|
||||||
>
|
>
|
||||||
<template :slot="'header'">
|
<template #header>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-timer mr-1" />
|
<i class="mdi mdi-24px mdi-timer mr-1" />
|
||||||
<span class="cut-text">{{ $t('word.timing') }} "{{ localOptions.name }}"</span>
|
<span class="cut-text">{{ $t('word.timing') }} "{{ localOptions.name }}"</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div :slot="'body'">
|
<template #body>
|
||||||
<form class="form-horizontal">
|
<form class="form-horizontal">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label col-4">
|
<label class="form-label col-4">
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</template>
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@@ -616,6 +616,14 @@ export default {
|
|||||||
},
|
},
|
||||||
removeField (uid) {
|
removeField (uid) {
|
||||||
this.localFields = this.localFields.filter(field => field._antares_id !== uid);
|
this.localFields = this.localFields.filter(field => field._antares_id !== uid);
|
||||||
|
this.localKeyUsage = this.localKeyUsage.filter(fk =>// Clear foreign keys
|
||||||
|
this.localFields.some(field => field.name === fk.field)
|
||||||
|
);
|
||||||
|
this.localIndexes = this.localIndexes.filter(index =>// Clear indexes
|
||||||
|
this.localFields.some(field =>
|
||||||
|
index.fields.includes(field.name)
|
||||||
|
)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
addNewIndex (payload) {
|
addNewIndex (payload) {
|
||||||
this.localIndexes = [...this.localIndexes, {
|
this.localIndexes = [...this.localIndexes, {
|
||||||
|
@@ -6,13 +6,13 @@
|
|||||||
@confirm="confirmForeignsChange"
|
@confirm="confirmForeignsChange"
|
||||||
@hide="$emit('hide')"
|
@hide="$emit('hide')"
|
||||||
>
|
>
|
||||||
<template :slot="'header'">
|
<template #header>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-key-link mr-1" />
|
<i class="mdi mdi-24px mdi-key-link mr-1" />
|
||||||
<span class="cut-text">{{ $t('word.foreignKeys') }} "{{ table }}"</span>
|
<span class="cut-text">{{ $t('word.foreignKeys') }} "{{ table }}"</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div :slot="'body'">
|
<template #body>
|
||||||
<div class="columns col-gapless">
|
<div class="columns col-gapless">
|
||||||
<div class="column col-5">
|
<div class="column col-5">
|
||||||
<div class="panel" :style="{ height: modalInnerHeight + 'px'}">
|
<div class="panel" :style="{ height: modalInnerHeight + 'px'}">
|
||||||
@@ -197,7 +197,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -289,7 +289,7 @@ export default {
|
|||||||
addForeign () {
|
addForeign () {
|
||||||
this.foreignProxy = [...this.foreignProxy, {
|
this.foreignProxy = [...this.foreignProxy, {
|
||||||
_antares_id: uidGen(),
|
_antares_id: uidGen(),
|
||||||
constraintName: `FK_${this.foreignProxy.length + 1}`,
|
constraintName: `FK_${uidGen()}`,
|
||||||
refSchema: this.schema,
|
refSchema: this.schema,
|
||||||
table: this.table,
|
table: this.table,
|
||||||
refTable: '',
|
refTable: '',
|
||||||
|
@@ -6,13 +6,13 @@
|
|||||||
@confirm="confirmIndexesChange"
|
@confirm="confirmIndexesChange"
|
||||||
@hide="$emit('hide')"
|
@hide="$emit('hide')"
|
||||||
>
|
>
|
||||||
<template :slot="'header'">
|
<template #header>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-key mdi-rotate-45 mr-1" />
|
<i class="mdi mdi-24px mdi-key mdi-rotate-45 mr-1" />
|
||||||
<span class="cut-text">{{ $t('word.indexes') }} "{{ table }}"</span>
|
<span class="cut-text">{{ $t('word.indexes') }} "{{ table }}"</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div :slot="'body'">
|
<template #body>
|
||||||
<div class="columns col-gapless">
|
<div class="columns col-gapless">
|
||||||
<div class="column col-5">
|
<div class="column col-5">
|
||||||
<div class="panel" :style="{ height: modalInnerHeight + 'px'}">
|
<div class="panel" :style="{ height: modalInnerHeight + 'px'}">
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@@ -99,6 +99,9 @@
|
|||||||
<span v-if="localRow.enumValues">
|
<span v-if="localRow.enumValues">
|
||||||
{{ localRow.enumValues }}
|
{{ localRow.enumValues }}
|
||||||
</span>
|
</span>
|
||||||
|
<span v-else-if="localRow.numScale">
|
||||||
|
{{ localLength }}, {{ localRow.numScale }}
|
||||||
|
</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
{{ localLength }}
|
{{ localLength }}
|
||||||
</span>
|
</span>
|
||||||
@@ -112,6 +115,16 @@
|
|||||||
class="editable-field form-input input-sm px-1"
|
class="editable-field form-input input-sm px-1"
|
||||||
@blur="editOFF"
|
@blur="editOFF"
|
||||||
>
|
>
|
||||||
|
<input
|
||||||
|
v-else-if="fieldType.scale"
|
||||||
|
ref="editField"
|
||||||
|
v-model="editingContent"
|
||||||
|
type="text"
|
||||||
|
autofocus
|
||||||
|
class="editable-field form-input input-sm px-1"
|
||||||
|
@keypress="checkLengthScale"
|
||||||
|
@blur="editOFF"
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
v-else
|
v-else
|
||||||
ref="editField"
|
ref="editField"
|
||||||
@@ -230,13 +243,13 @@
|
|||||||
@confirm="editOFF"
|
@confirm="editOFF"
|
||||||
@hide="hideDefaultModal"
|
@hide="hideDefaultModal"
|
||||||
>
|
>
|
||||||
<template :slot="'header'">
|
<template #header>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-playlist-edit mr-1" />
|
<i class="mdi mdi-24px mdi-playlist-edit mr-1" />
|
||||||
<span class="cut-text">{{ $t('word.default') }} "{{ row.name }}"</span>
|
<span class="cut-text">{{ $t('word.default') }} "{{ row.name }}"</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div :slot="'body'">
|
<template #body>
|
||||||
<form class="form-horizontal">
|
<form class="form-horizontal">
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label class="form-radio form-inline">
|
<label class="form-radio form-inline">
|
||||||
@@ -324,7 +337,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</template>
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -480,6 +493,11 @@ export default {
|
|||||||
this.editingContent = this.localRow.enumValues;
|
this.editingContent = this.localRow.enumValues;
|
||||||
this.originalContent = this.localRow.enumValues;
|
this.originalContent = this.localRow.enumValues;
|
||||||
}
|
}
|
||||||
|
else if (this.fieldType.scale && field === 'length') {
|
||||||
|
const scale = this.localRow.numScale !== null ? this.localRow.numScale : 0;
|
||||||
|
this.editingContent = `${content}, ${scale}`;
|
||||||
|
this.originalContent = `${content}, ${scale}`;
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
this.editingContent = content;
|
this.editingContent = content;
|
||||||
this.originalContent = content;
|
this.originalContent = content;
|
||||||
@@ -502,10 +520,17 @@ export default {
|
|||||||
if (this.editingField === 'name')
|
if (this.editingField === 'name')
|
||||||
this.$emit('rename-field', { old: this.localRow[this.editingField], new: this.editingContent });
|
this.$emit('rename-field', { old: this.localRow[this.editingField], new: this.editingContent });
|
||||||
|
|
||||||
this.localRow[this.editingField] = this.editingContent;
|
if (this.editingField === 'numLength' && this.fieldType.scale) {
|
||||||
|
const [length, scale] = this.editingContent.split(',');
|
||||||
|
this.localRow.numLength = +length;
|
||||||
|
this.localRow.numScale = scale ? +scale : null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.localRow[this.editingField] = this.editingContent;
|
||||||
|
|
||||||
if (this.editingField === 'type' && this.editingContent !== this.originalContent) {
|
if (this.editingField === 'type' && this.editingContent !== this.originalContent) {
|
||||||
this.localRow.numLength = null;
|
this.localRow.numLength = null;
|
||||||
|
this.localRow.numScale = null;
|
||||||
this.localRow.charLength = null;
|
this.localRow.charLength = null;
|
||||||
this.localRow.datePrecision = null;
|
this.localRow.datePrecision = null;
|
||||||
this.localRow.enumValues = '';
|
this.localRow.enumValues = '';
|
||||||
@@ -560,6 +585,15 @@ export default {
|
|||||||
this.originalContent = null;
|
this.originalContent = null;
|
||||||
this.editingField = null;
|
this.editingField = null;
|
||||||
},
|
},
|
||||||
|
checkLengthScale (e) {
|
||||||
|
e = (e) || window.event;
|
||||||
|
const charCode = (e.which) ? e.which : e.keyCode;
|
||||||
|
|
||||||
|
if (((charCode > 31 && (charCode < 48 || charCode > 57)) && charCode !== 44) || (charCode === 44 && e.target.value.includes(',')))
|
||||||
|
e.preventDefault();
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
},
|
||||||
hideDefaultModal () {
|
hideDefaultModal () {
|
||||||
this.isDefaultModal = false;
|
this.isDefaultModal = false;
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
class="workspace-query-tab column col-12 columns col-gapless no-outline p-0"
|
class="workspace-query-tab column col-12 columns col-gapless no-outline p-0"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@keydown.116="runQuery(query)"
|
@keydown.116="runQuery(query)"
|
||||||
|
@keydown.75="killTabQuery"
|
||||||
@keydown.ctrl.alt.87="clear"
|
@keydown.ctrl.alt.87="clear"
|
||||||
@keydown.ctrl.66="beautify"
|
@keydown.ctrl.66="beautify"
|
||||||
@keydown.ctrl.71="openHistoryModal"
|
@keydown.ctrl.71="openHistoryModal"
|
||||||
@@ -22,15 +23,46 @@
|
|||||||
<div ref="resizer" class="query-area-resizer" />
|
<div ref="resizer" class="query-area-resizer" />
|
||||||
<div class="workspace-query-runner-footer">
|
<div class="workspace-query-runner-footer">
|
||||||
<div class="workspace-query-buttons">
|
<div class="workspace-query-buttons">
|
||||||
|
<div @mouseenter="setCancelButtonVisibility(true)" @mouseleave="setCancelButtonVisibility(false)">
|
||||||
|
<button
|
||||||
|
v-if="showCancel && isQuering"
|
||||||
|
class="btn btn-primary btn-sm cancellable"
|
||||||
|
:disabled="!query"
|
||||||
|
:title="$t('word.cancel')"
|
||||||
|
@click="killTabQuery()"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-24px mdi-window-close" />
|
||||||
|
<span class="d-invisible pr-1">{{ $t('word.run') }}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
class="btn btn-primary btn-sm"
|
||||||
|
:class="{'loading':isQuering}"
|
||||||
|
:disabled="!query"
|
||||||
|
title="F5"
|
||||||
|
@click="runQuery(query)"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-24px mdi-play pr-1" />
|
||||||
|
<span>{{ $t('word.run') }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary btn-sm"
|
v-if="!autocommit"
|
||||||
|
class="btn btn-dark btn-sm"
|
||||||
:class="{'loading':isQuering}"
|
:class="{'loading':isQuering}"
|
||||||
:disabled="!query"
|
@click="commitTab()"
|
||||||
title="F5"
|
|
||||||
@click="runQuery(query)"
|
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-play pr-1" />
|
<i class="mdi mdi-24px mdi-cube-send pr-1" />
|
||||||
<span>{{ $t('word.run') }}</span>
|
<span>{{ $t('word.commit') }}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="!autocommit"
|
||||||
|
class="btn btn-dark btn-sm"
|
||||||
|
:class="{'loading':isQuering}"
|
||||||
|
@click="rollbackTab()"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-24px mdi-undo-variant pr-1" />
|
||||||
|
<span>{{ $t('word.rollback') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn btn-link btn-sm mr-0"
|
class="btn btn-link btn-sm mr-0"
|
||||||
@@ -64,7 +96,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<div class="dropdown table-dropdown pr-2">
|
<div class="dropdown table-dropdown pr-2">
|
||||||
<button
|
<button
|
||||||
:disabled="!results.length || isQuering"
|
:disabled="!hasResults || isQuering"
|
||||||
class="btn btn-dark btn-sm dropdown-toggle mr-0 pr-0"
|
class="btn btn-dark btn-sm dropdown-toggle mr-0 pr-0"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
@@ -81,6 +113,17 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="input-group pr-2" :title="$t('message.commitMode')">
|
||||||
|
<i class="input-group-addon addon-sm mdi mdi-24px mdi-source-commit p-0" />
|
||||||
|
<select v-model="autocommit" class="form-select select-sm text-bold">
|
||||||
|
<option :value="true">
|
||||||
|
{{ $t('message.autoCommit') }}
|
||||||
|
</option>
|
||||||
|
<option :value="false">
|
||||||
|
{{ $t('message.manualCommit') }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-info">
|
<div class="workspace-query-info">
|
||||||
<div
|
<div
|
||||||
@@ -90,11 +133,19 @@
|
|||||||
>
|
>
|
||||||
<i class="mdi mdi-timer-sand mdi-rotate-180 pr-1" /> <b>{{ durationsCount / 1000 }}s</b>
|
<i class="mdi mdi-timer-sand mdi-rotate-180 pr-1" /> <b>{{ durationsCount / 1000 }}s</b>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="resultsCount">
|
<div
|
||||||
{{ $t('word.results') }}: <b>{{ resultsCount.toLocaleString() }}</b>
|
v-if="resultsCount"
|
||||||
|
class="d-flex"
|
||||||
|
:title="$t('word.results')"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-equal pr-1" /> <b>{{ resultsCount.toLocaleString() }}</b>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="affectedCount !== null">
|
<div
|
||||||
{{ $t('message.affectedRows') }}: <b>{{ affectedCount }}</b>
|
v-if="hasAffected"
|
||||||
|
class="d-flex"
|
||||||
|
:title="$t('message.affectedRows')"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-target pr-1" /> <b>{{ affectedCount }}</b>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group" :title="$t('word.schema')">
|
<div class="input-group" :title="$t('word.schema')">
|
||||||
<i class="input-group-addon addon-sm mdi mdi-24px mdi-database" />
|
<i class="input-group-addon addon-sm mdi mdi-24px mdi-database" />
|
||||||
@@ -110,7 +161,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<WorkspaceTabQueryEmptyState v-if="!results.length && !isQuering" />
|
<WorkspaceTabQueryEmptyState v-if="!results.length && !isQuering" :customizations="workspace.customizations" />
|
||||||
<div class="workspace-query-results p-relative column col-12">
|
<div class="workspace-query-results p-relative column col-12">
|
||||||
<BaseLoader v-if="isQuering" />
|
<BaseLoader v-if="isQuering" />
|
||||||
<WorkspaceTabQueryTable
|
<WorkspaceTabQueryTable
|
||||||
@@ -166,6 +217,9 @@ export default {
|
|||||||
query: '',
|
query: '',
|
||||||
lastQuery: '',
|
lastQuery: '',
|
||||||
isQuering: false,
|
isQuering: false,
|
||||||
|
isCancelling: false,
|
||||||
|
showCancel: false,
|
||||||
|
autocommit: true,
|
||||||
results: [],
|
results: [],
|
||||||
selectedSchema: null,
|
selectedSchema: null,
|
||||||
resultsCount: 0,
|
resultsCount: 0,
|
||||||
@@ -184,6 +238,9 @@ export default {
|
|||||||
workspace () {
|
workspace () {
|
||||||
return this.getWorkspace(this.connection.uid);
|
return this.getWorkspace(this.connection.uid);
|
||||||
},
|
},
|
||||||
|
tabUid () {
|
||||||
|
return this.$vnode.key;
|
||||||
|
},
|
||||||
breadcrumbsSchema () {
|
breadcrumbsSchema () {
|
||||||
return this.workspace.breadcrumbs.schema || null;
|
return this.workspace.breadcrumbs.schema || null;
|
||||||
},
|
},
|
||||||
@@ -198,12 +255,23 @@ export default {
|
|||||||
},
|
},
|
||||||
history () {
|
history () {
|
||||||
return this.getHistoryByWorkspace(this.connection.uid) || [];
|
return this.getHistoryByWorkspace(this.connection.uid) || [];
|
||||||
|
},
|
||||||
|
hasResults () {
|
||||||
|
return this.results.length && this.results[0].rows;
|
||||||
|
},
|
||||||
|
hasAffected () {
|
||||||
|
return this.affectedCount || (!this.resultsCount && this.affectedCount !== null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
isSelected (val) {
|
isSelected (val) {
|
||||||
if (val)
|
if (val) {
|
||||||
this.changeBreadcrumbs({ schema: this.selectedSchema, query: `Query #${this.tab.index}` });
|
this.changeBreadcrumbs({ schema: this.selectedSchema, query: `Query #${this.tab.index}` });
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.$refs.queryEditor)
|
||||||
|
this.$refs.queryEditor.editor.focus();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
selectedSchema () {
|
selectedSchema () {
|
||||||
this.changeBreadcrumbs({ schema: this.selectedSchema, query: `Query #${this.tab.index}` });
|
this.changeBreadcrumbs({ schema: this.selectedSchema, query: `Query #${this.tab.index}` });
|
||||||
@@ -230,12 +298,18 @@ export default {
|
|||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
window.removeEventListener('keydown', this.onKey);
|
window.removeEventListener('keydown', this.onKey);
|
||||||
|
const params = {
|
||||||
|
uid: this.connection.uid,
|
||||||
|
tabUid: this.tab.uid
|
||||||
|
};
|
||||||
|
Schema.destroyConnectionToCommit(params);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions({
|
...mapActions({
|
||||||
addNotification: 'notifications/addNotification',
|
addNotification: 'notifications/addNotification',
|
||||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
|
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
|
||||||
updateTabContent: 'workspaces/updateTabContent',
|
updateTabContent: 'workspaces/updateTabContent',
|
||||||
|
setUnsavedChanges: 'workspaces/setUnsavedChanges',
|
||||||
saveHistory: 'history/saveHistory'
|
saveHistory: 'history/saveHistory'
|
||||||
}),
|
}),
|
||||||
async runQuery (query) {
|
async runQuery (query) {
|
||||||
@@ -248,6 +322,8 @@ export default {
|
|||||||
const params = {
|
const params = {
|
||||||
uid: this.connection.uid,
|
uid: this.connection.uid,
|
||||||
schema: this.selectedSchema,
|
schema: this.selectedSchema,
|
||||||
|
tabUid: this.tab.uid,
|
||||||
|
autocommit: this.autocommit,
|
||||||
query
|
query
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -272,6 +348,8 @@ export default {
|
|||||||
content: query
|
content: query
|
||||||
});
|
});
|
||||||
this.saveHistory(params);
|
this.saveHistory(params);
|
||||||
|
if (!this.autocommit)
|
||||||
|
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: true });
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
this.addNotification({ status: 'error', message: response });
|
this.addNotification({ status: 'error', message: response });
|
||||||
@@ -283,6 +361,29 @@ export default {
|
|||||||
this.isQuering = false;
|
this.isQuering = false;
|
||||||
this.lastQuery = query;
|
this.lastQuery = query;
|
||||||
},
|
},
|
||||||
|
async killTabQuery () {
|
||||||
|
if (this.isCancelling) return;
|
||||||
|
|
||||||
|
this.isCancelling = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
uid: this.connection.uid,
|
||||||
|
tabUid: this.tab.uid
|
||||||
|
};
|
||||||
|
|
||||||
|
await Schema.killTabQuery(params);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isCancelling = false;
|
||||||
|
},
|
||||||
|
setCancelButtonVisibility (val) {
|
||||||
|
if (this.workspace.customizations.cancelQueries)
|
||||||
|
this.showCancel = val;
|
||||||
|
},
|
||||||
reloadTable () {
|
reloadTable () {
|
||||||
this.runQuery(this.lastQuery);
|
this.runQuery(this.lastQuery);
|
||||||
},
|
},
|
||||||
@@ -346,6 +447,42 @@ export default {
|
|||||||
},
|
},
|
||||||
downloadTable (format) {
|
downloadTable (format) {
|
||||||
this.$refs.queryTable.downloadTable(format, `${this.tab.type}-${this.tab.index}`);
|
this.$refs.queryTable.downloadTable(format, `${this.tab.type}-${this.tab.index}`);
|
||||||
|
},
|
||||||
|
async commitTab () {
|
||||||
|
this.isQuering = true;
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
uid: this.connection.uid,
|
||||||
|
tabUid: this.tab.uid
|
||||||
|
};
|
||||||
|
|
||||||
|
await Schema.commitTab(params);
|
||||||
|
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: false });
|
||||||
|
this.addNotification({ status: 'success', message: this.$t('message.actionSuccessful', { action: 'COMMIT' }) });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isQuering = false;
|
||||||
|
},
|
||||||
|
async rollbackTab () {
|
||||||
|
this.isQuering = true;
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
uid: this.connection.uid,
|
||||||
|
tabUid: this.tab.uid
|
||||||
|
};
|
||||||
|
|
||||||
|
await Schema.rollbackTab(params);
|
||||||
|
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: false });
|
||||||
|
this.addNotification({ status: 'success', message: this.$t('message.actionSuccessful', { action: 'ROLLBACK' }) });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isQuering = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -374,10 +511,12 @@ export default {
|
|||||||
|
|
||||||
.workspace-query-runner-footer {
|
.workspace-query-runner-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
row-gap: 0.4rem;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 0.3rem 0.6rem 0.4rem;
|
padding: 0.3rem 0.6rem 0.4rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 42px;
|
min-height: 42px;
|
||||||
|
|
||||||
.workspace-query-buttons,
|
.workspace-query-buttons,
|
||||||
.workspace-query-info {
|
.workspace-query-info {
|
||||||
|
@@ -5,6 +5,9 @@
|
|||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
{{ $t('message.runQuery') }}
|
{{ $t('message.runQuery') }}
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="customizations.cancelQueries" class="mb-4">
|
||||||
|
{{ $t('message.killQuery') }}
|
||||||
|
</div>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
{{ $t('word.format') }}
|
{{ $t('word.format') }}
|
||||||
</div>
|
</div>
|
||||||
@@ -25,6 +28,9 @@
|
|||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<code>F5</code>
|
<code>F5</code>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="customizations.cancelQueries" class="mb-4">
|
||||||
|
<code>CTRL</code> + <code>K</code>
|
||||||
|
</div>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<code>CTRL</code> + <code>B</code>
|
<code>CTRL</code> + <code>B</code>
|
||||||
</div>
|
</div>
|
||||||
@@ -47,7 +53,10 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'WorkspaceTabQueryEmptyState'
|
name: 'WorkspaceTabQueryEmptyState',
|
||||||
|
props: {
|
||||||
|
customizations: Object
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
tabindex="0"
|
tabindex="0"
|
||||||
:style="{'height': resultsSize+'px'}"
|
:style="{'height': resultsSize+'px'}"
|
||||||
@keyup.46="showDeleteConfirmModal"
|
@keyup.46="showDeleteConfirmModal"
|
||||||
@keydown.ctrl.65="selectAllRows"
|
@keydown.ctrl.65="selectAllRows($event)"
|
||||||
@keydown.esc="deselectRows"
|
@keydown.esc="deselectRows"
|
||||||
>
|
>
|
||||||
<TableContext
|
<TableContext
|
||||||
@@ -53,6 +53,7 @@
|
|||||||
class="mdi sort-icon"
|
class="mdi sort-icon"
|
||||||
:class="currentSortDir === 'asc' ? 'mdi-sort-ascending':'mdi-sort-descending'"
|
:class="currentSortDir === 'asc' ? 'mdi-sort-ascending':'mdi-sort-descending'"
|
||||||
/>
|
/>
|
||||||
|
<i v-else class="mdi sort-icon mdi-minus d-invisible" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -62,7 +63,7 @@
|
|||||||
v-if="resultsWithRows[resultsetIndex] && resultsWithRows[resultsetIndex].rows"
|
v-if="resultsWithRows[resultsetIndex] && resultsWithRows[resultsetIndex].rows"
|
||||||
ref="resultTable"
|
ref="resultTable"
|
||||||
:items="sortedResults"
|
:items="sortedResults"
|
||||||
:item-height="23"
|
:item-height="rowHeight"
|
||||||
class="tbody"
|
class="tbody"
|
||||||
:visible-height="resultsSize"
|
:visible-height="resultsSize"
|
||||||
:scroll-element="scrollElement"
|
:scroll-element="scrollElement"
|
||||||
@@ -71,6 +72,7 @@
|
|||||||
<WorkspaceTabQueryTableRow
|
<WorkspaceTabQueryTableRow
|
||||||
v-for="row in items"
|
v-for="row in items"
|
||||||
:key="row._antares_id"
|
:key="row._antares_id"
|
||||||
|
:item-height="rowHeight"
|
||||||
:row="row"
|
:row="row"
|
||||||
:fields="fieldsObj"
|
:fields="fieldsObj"
|
||||||
:key-usage="keyUsage"
|
:key-usage="keyUsage"
|
||||||
@@ -89,17 +91,17 @@
|
|||||||
@confirm="deleteSelected"
|
@confirm="deleteSelected"
|
||||||
@hide="hideDeleteConfirmModal"
|
@hide="hideDeleteConfirmModal"
|
||||||
>
|
>
|
||||||
<template :slot="'header'">
|
<template #header>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-delete mr-1" />
|
<i class="mdi mdi-24px mdi-delete mr-1" />
|
||||||
<span class="cut-text">{{ $tc('message.deleteRows', selectedRows.length) }}</span>
|
<span class="cut-text">{{ $tc('message.deleteRows', selectedRows.length) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div :slot="'body'">
|
<template #body>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
{{ $tc('message.confirmToDeleteRows', selectedRows.length) }}
|
{{ $tc('message.confirmToDeleteRows', selectedRows.length) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -142,7 +144,8 @@ export default {
|
|||||||
currentSort: '',
|
currentSort: '',
|
||||||
currentSortDir: 'asc',
|
currentSortDir: 'asc',
|
||||||
resultsetIndex: 0,
|
resultsetIndex: 0,
|
||||||
scrollElement: null
|
scrollElement: null,
|
||||||
|
rowHeight: 23
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -172,8 +175,10 @@ export default {
|
|||||||
if (this.currentSort && !this.isHardSort) {
|
if (this.currentSort && !this.isHardSort) {
|
||||||
return [...this.localResults].sort((a, b) => {
|
return [...this.localResults].sort((a, b) => {
|
||||||
let modifier = 1;
|
let modifier = 1;
|
||||||
const valA = typeof a[this.currentSort] === 'string' ? a[this.currentSort].toLowerCase() : a[this.currentSort];
|
let valA = typeof a[this.currentSort] === 'string' ? a[this.currentSort].toLowerCase() : a[this.currentSort];
|
||||||
const valB = typeof b[this.currentSort] === 'string' ? b[this.currentSort].toLowerCase() : b[this.currentSort];
|
if (!isNaN(valA)) valA = Number(valA);
|
||||||
|
let valB = typeof b[this.currentSort] === 'string' ? b[this.currentSort].toLowerCase() : b[this.currentSort];
|
||||||
|
if (!isNaN(valB)) valB = Number(valB);
|
||||||
if (this.currentSortDir === 'desc') modifier = -1;
|
if (this.currentSortDir === 'desc') modifier = -1;
|
||||||
if (valA < valB) return -1 * modifier;
|
if (valA < valB) return -1 * modifier;
|
||||||
if (valA > valB) return 1 * modifier;
|
if (valA > valB) return 1 * modifier;
|
||||||
@@ -242,6 +247,11 @@ export default {
|
|||||||
|
|
||||||
if (this.$refs.tableWrapper)
|
if (this.$refs.tableWrapper)
|
||||||
this.scrollElement = this.$refs.tableWrapper;
|
this.scrollElement = this.$refs.tableWrapper;
|
||||||
|
|
||||||
|
document.querySelectorAll('.column-resizable').forEach(element => {
|
||||||
|
if (element.clientWidth !== 0)
|
||||||
|
element.style.width = element.clientWidth + 'px';
|
||||||
|
});
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
window.addEventListener('resize', this.resizeResults);
|
window.addEventListener('resize', this.resizeResults);
|
||||||
@@ -272,6 +282,7 @@ export default {
|
|||||||
fieldLength (field) {
|
fieldLength (field) {
|
||||||
if ([...BLOB, ...LONG_TEXT].includes(field.type)) return null;
|
if ([...BLOB, ...LONG_TEXT].includes(field.type)) return null;
|
||||||
else if (TEXT.includes(field.type)) return field.charLength;
|
else if (TEXT.includes(field.type)) return field.charLength;
|
||||||
|
else if (field.numScale) return `${field.numPrecision}, ${field.numScale}`;
|
||||||
return field.length;
|
return field.length;
|
||||||
},
|
},
|
||||||
keyName (key) {
|
keyName (key) {
|
||||||
@@ -399,10 +410,13 @@ export default {
|
|||||||
const row = this.localResults.find(row => this.selectedRows.includes(row._antares_id));
|
const row = this.localResults.find(row => this.selectedRows.includes(row._antares_id));
|
||||||
const cellName = Object.keys(row).find(prop => [
|
const cellName = Object.keys(row).find(prop => [
|
||||||
this.selectedCell.field,
|
this.selectedCell.field,
|
||||||
|
this.selectedCell.orgField,
|
||||||
`${this.fields[0].table}.${this.selectedCell.field}`,
|
`${this.fields[0].table}.${this.selectedCell.field}`,
|
||||||
`${this.fields[0].tableAlias}.${this.selectedCell.field}`
|
`${this.fields[0].tableAlias}.${this.selectedCell.field}`
|
||||||
].includes(prop));
|
].includes(prop));
|
||||||
const valueToCopy = row[cellName];
|
let valueToCopy = row[cellName];
|
||||||
|
if (typeof valueToCopy === 'object')
|
||||||
|
valueToCopy = JSON.stringify(valueToCopy);
|
||||||
navigator.clipboard.writeText(valueToCopy);
|
navigator.clipboard.writeText(valueToCopy);
|
||||||
},
|
},
|
||||||
copyRow () {
|
copyRow () {
|
||||||
@@ -450,7 +464,9 @@ export default {
|
|||||||
else
|
else
|
||||||
this.selectedRows = [row];
|
this.selectedRows = [row];
|
||||||
},
|
},
|
||||||
selectAllRows () {
|
selectAllRows (e) {
|
||||||
|
if (e.target.classList.contains('editable-field')) return;
|
||||||
|
|
||||||
this.selectedRows = this.localResults.reduce((acc, curr) => {
|
this.selectedRows = this.localResults.reduce((acc, curr) => {
|
||||||
acc.push(curr._antares_id);
|
acc.push(curr._antares_id);
|
||||||
return acc;
|
return acc;
|
||||||
|
@@ -61,8 +61,6 @@ export default {
|
|||||||
selectedRows: Array,
|
selectedRows: Array,
|
||||||
selectedCell: Object
|
selectedCell: Object
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
showConfirmModal () {
|
showConfirmModal () {
|
||||||
this.$emit('show-delete-modal');
|
this.$emit('show-delete-modal');
|
||||||
|
@@ -1,12 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="tr" @click="selectRow($event, row._antares_id)">
|
<div
|
||||||
|
class="tr"
|
||||||
|
:style="{height: itemHeight+'px'}"
|
||||||
|
@click="selectRow($event, row._antares_id)"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(col, cKey) in row"
|
v-for="(col, cKey) in row"
|
||||||
v-show="cKey !== '_antares_id'"
|
v-show="cKey !== '_antares_id'"
|
||||||
:key="cKey"
|
:key="cKey"
|
||||||
class="td p-0"
|
class="td p-0"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@contextmenu.prevent="openContext($event, { id: row._antares_id, field: cKey })"
|
@contextmenu.prevent="openContext($event, { id: row._antares_id, orgField: cKey })"
|
||||||
>
|
>
|
||||||
<template v-if="cKey !== '_antares_id'">
|
<template v-if="cKey !== '_antares_id'">
|
||||||
<span
|
<span
|
||||||
@@ -72,12 +76,12 @@
|
|||||||
@confirm="editOFF"
|
@confirm="editOFF"
|
||||||
@hide="hideEditorModal"
|
@hide="hideEditorModal"
|
||||||
>
|
>
|
||||||
<template :slot="'header'">
|
<template #header>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-playlist-edit mr-1" /> <span class="cut-text">{{ $t('word.edit') }} "{{ editingField }}"</span>
|
<i class="mdi mdi-24px mdi-playlist-edit mr-1" /> <span class="cut-text">{{ $t('word.edit') }} "{{ editingField }}"</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div :slot="'body'">
|
<template #body>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<div>
|
<div>
|
||||||
<TextEditor
|
<TextEditor
|
||||||
@@ -96,23 +100,12 @@
|
|||||||
v-model="editorMode"
|
v-model="editorMode"
|
||||||
class="form-select select-sm"
|
class="form-select select-sm"
|
||||||
>
|
>
|
||||||
<option value="text">
|
<option
|
||||||
TEXT
|
v-for="language in availableLanguages"
|
||||||
</option>
|
:key="language.slug"
|
||||||
<option value="html">
|
:value="language.slug"
|
||||||
HTML
|
>
|
||||||
</option>
|
{{ language.name }}
|
||||||
<option value="xml">
|
|
||||||
XML
|
|
||||||
</option>
|
|
||||||
<option value="json">
|
|
||||||
JSON
|
|
||||||
</option>
|
|
||||||
<option value="svg">
|
|
||||||
SVG
|
|
||||||
</option>
|
|
||||||
<option value="yaml">
|
|
||||||
YAML
|
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -128,7 +121,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
|
</ConfirmModal>
|
||||||
|
<ConfirmModal
|
||||||
|
v-if="isMapModal"
|
||||||
|
:hide-footer="true"
|
||||||
|
size="medium"
|
||||||
|
@hide="hideEditorModal"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="mdi mdi-24px mdi-map mr-1" /> <span class="cut-text">"{{ editingField }}"</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #body>
|
||||||
|
<BaseMap :points="editingContent" :is-multi-spatial="isMultiSpatial" />
|
||||||
|
</template>
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
v-if="isBlobEditor"
|
v-if="isBlobEditor"
|
||||||
@@ -136,13 +144,13 @@
|
|||||||
@confirm="editOFF"
|
@confirm="editOFF"
|
||||||
@hide="hideEditorModal"
|
@hide="hideEditorModal"
|
||||||
>
|
>
|
||||||
<template :slot="'header'">
|
<template #header>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<i class="mdi mdi-24px mdi-playlist-edit mr-1" />
|
<i class="mdi mdi-24px mdi-playlist-edit mr-1" />
|
||||||
<span class="cut-text">{{ $t('word.edit') }} "{{ editingField }}"</span>
|
<span class="cut-text">{{ $t('word.edit') }} "{{ editingField }}"</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div :slot="'body'">
|
<template #body>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<transition name="jump-down">
|
<transition name="jump-down">
|
||||||
<div v-if="contentInfo.size">
|
<div v-if="contentInfo.size">
|
||||||
@@ -182,21 +190,39 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import { ModelOperations } from '@vscode/vscode-languagedetection';
|
||||||
import { mimeFromHex } from 'common/libs/mimeFromHex';
|
import { mimeFromHex } from 'common/libs/mimeFromHex';
|
||||||
import { formatBytes } from 'common/libs/formatBytes';
|
import { formatBytes } from 'common/libs/formatBytes';
|
||||||
import { bufferToBase64 } from 'common/libs/bufferToBase64';
|
import { bufferToBase64 } from 'common/libs/bufferToBase64';
|
||||||
import hexToBinary from 'common/libs/hexToBinary';
|
import hexToBinary from 'common/libs/hexToBinary';
|
||||||
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BOOLEAN, DATE, TIME, DATETIME, BLOB, BIT, HAS_TIMEZONE } from 'common/fieldTypes';
|
import {
|
||||||
|
TEXT,
|
||||||
|
LONG_TEXT,
|
||||||
|
ARRAY,
|
||||||
|
TEXT_SEARCH,
|
||||||
|
NUMBER,
|
||||||
|
FLOAT,
|
||||||
|
BOOLEAN,
|
||||||
|
DATE,
|
||||||
|
TIME,
|
||||||
|
DATETIME,
|
||||||
|
BLOB,
|
||||||
|
BIT,
|
||||||
|
HAS_TIMEZONE,
|
||||||
|
SPATIAL,
|
||||||
|
IS_MULTI_SPATIAL
|
||||||
|
} from 'common/fieldTypes';
|
||||||
import { VueMaskDirective } from 'v-mask';
|
import { VueMaskDirective } from 'v-mask';
|
||||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||||
import TextEditor from '@/components/BaseTextEditor';
|
import TextEditor from '@/components/BaseTextEditor';
|
||||||
|
import BaseMap from '@/components/BaseMap';
|
||||||
import ForeignKeySelect from '@/components/ForeignKeySelect';
|
import ForeignKeySelect from '@/components/ForeignKeySelect';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -204,7 +230,8 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
ConfirmModal,
|
ConfirmModal,
|
||||||
TextEditor,
|
TextEditor,
|
||||||
ForeignKeySelect
|
ForeignKeySelect,
|
||||||
|
BaseMap
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
mask: VueMaskDirective
|
mask: VueMaskDirective
|
||||||
@@ -245,7 +272,8 @@ export default {
|
|||||||
if (BIT.includes(type)) {
|
if (BIT.includes(type)) {
|
||||||
if (typeof val === 'number') val = [val];
|
if (typeof val === 'number') val = [val];
|
||||||
const hex = Buffer.from(val).toString('hex');
|
const hex = Buffer.from(val).toString('hex');
|
||||||
return hexToBinary(hex);
|
const bitString = hexToBinary(hex);
|
||||||
|
return parseInt(bitString).toString().padStart(precision, '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ARRAY.includes(type)) {
|
if (ARRAY.includes(type)) {
|
||||||
@@ -254,13 +282,17 @@ export default {
|
|||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
return val;
|
if (SPATIAL.includes(type))
|
||||||
|
return val;
|
||||||
|
|
||||||
|
return typeof val === 'object' ? JSON.stringify(val) : val;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
row: Object,
|
row: Object,
|
||||||
fields: Object,
|
fields: Object,
|
||||||
keyUsage: Array,
|
keyUsage: Array,
|
||||||
|
itemHeight: Number,
|
||||||
elementType: { type: String, default: 'table' }
|
elementType: { type: String, default: 'table' }
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
@@ -268,6 +300,8 @@ export default {
|
|||||||
isInlineEditor: {},
|
isInlineEditor: {},
|
||||||
isTextareaEditor: false,
|
isTextareaEditor: false,
|
||||||
isBlobEditor: false,
|
isBlobEditor: false,
|
||||||
|
isMapModal: false,
|
||||||
|
isMultiSpatial: false,
|
||||||
willBeDeleted: false,
|
willBeDeleted: false,
|
||||||
originalContent: null,
|
originalContent: null,
|
||||||
editingContent: null,
|
editingContent: null,
|
||||||
@@ -280,7 +314,17 @@ export default {
|
|||||||
mime: '',
|
mime: '',
|
||||||
size: null
|
size: null
|
||||||
},
|
},
|
||||||
fileToUpload: null
|
fileToUpload: null,
|
||||||
|
availableLanguages: [
|
||||||
|
{ name: 'TEXT', slug: 'text', id: 'text' },
|
||||||
|
{ name: 'HTML', slug: 'html', id: 'html' },
|
||||||
|
{ name: 'XML', slug: 'xml', id: 'xml' },
|
||||||
|
{ name: 'JSON', slug: 'json', id: 'json' },
|
||||||
|
{ name: 'SVG', slug: 'svg', id: 'svg' },
|
||||||
|
{ name: 'INI', slug: 'ini', id: 'ini' },
|
||||||
|
{ name: 'MARKDOWN', slug: 'markdown', id: 'md' },
|
||||||
|
{ name: 'YAML', slug: 'yaml', id: 'yaml' }
|
||||||
|
]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -326,6 +370,9 @@ export default {
|
|||||||
if (BOOLEAN.includes(this.editingType))
|
if (BOOLEAN.includes(this.editingType))
|
||||||
return { type: 'boolean', mask: false };
|
return { type: 'boolean', mask: false };
|
||||||
|
|
||||||
|
if (SPATIAL.includes(this.editingType))
|
||||||
|
return { type: 'map', mask: false };
|
||||||
|
|
||||||
return { type: 'text', mask: false };
|
return { type: 'text', mask: false };
|
||||||
},
|
},
|
||||||
isImage () {
|
isImage () {
|
||||||
@@ -362,6 +409,21 @@ export default {
|
|||||||
Object.keys(this.fields).forEach(field => {
|
Object.keys(this.fields).forEach(field => {
|
||||||
this.isInlineEditor[field.name] = false;
|
this.isInlineEditor[field.name] = false;
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
isTextareaEditor (val) {
|
||||||
|
if (val) {
|
||||||
|
const modelOperations = new ModelOperations();
|
||||||
|
(async () => {
|
||||||
|
const detected = await modelOperations.runModel(this.editingContent);
|
||||||
|
const filteredLanguages = detected.filter(dLang =>
|
||||||
|
this.availableLanguages.some(aLang => aLang.id === dLang.languageId) &&
|
||||||
|
dLang.confidence > 0.1
|
||||||
|
);
|
||||||
|
|
||||||
|
if (filteredLanguages.length)
|
||||||
|
this.editorMode = this.availableLanguages.find(lang => lang.id === filteredLanguages[0].languageId).slug;
|
||||||
|
})();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -383,7 +445,7 @@ export default {
|
|||||||
return bufferToBase64(val);
|
return bufferToBase64(val);
|
||||||
},
|
},
|
||||||
editON (event, content, field) {
|
editON (event, content, field) {
|
||||||
if (!this.isEditable) return;
|
if (!this.isEditable || this.editingType === 'none') return;
|
||||||
|
|
||||||
window.addEventListener('keydown', this.onKey);
|
window.addEventListener('keydown', this.onKey);
|
||||||
|
|
||||||
@@ -399,6 +461,15 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SPATIAL.includes(type)) {
|
||||||
|
if (content) {
|
||||||
|
this.isMultiSpatial = IS_MULTI_SPATIAL.includes(type);
|
||||||
|
this.isMapModal = true;
|
||||||
|
this.editingContent = this.$options.filters.typeFormat(content, type);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (BLOB.includes(type)) {
|
if (BLOB.includes(type)) {
|
||||||
this.isBlobEditor = true;
|
this.isBlobEditor = true;
|
||||||
this.editingContent = content || '';
|
this.editingContent = content || '';
|
||||||
@@ -470,6 +541,8 @@ export default {
|
|||||||
hideEditorModal () {
|
hideEditorModal () {
|
||||||
this.isTextareaEditor = false;
|
this.isTextareaEditor = false;
|
||||||
this.isBlobEditor = false;
|
this.isBlobEditor = false;
|
||||||
|
this.isMapModal = false;
|
||||||
|
this.isMultiSpatial = false;
|
||||||
},
|
},
|
||||||
downloadFile () {
|
downloadFile () {
|
||||||
const downloadLink = document.createElement('a');
|
const downloadLink = document.createElement('a');
|
||||||
@@ -506,7 +579,7 @@ export default {
|
|||||||
return this.keyUsage.find(key => key.field === keyName);
|
return this.keyUsage.find(key => key.field === keyName);
|
||||||
},
|
},
|
||||||
openContext (event, payload) {
|
openContext (event, payload) {
|
||||||
payload.field = this.fields[payload.field].name;// Ensures field name only
|
payload.field = this.fields[payload.orgField].name;// Ensures field name only
|
||||||
payload.isEditable = this.isEditable;
|
payload.isEditable = this.isEditable;
|
||||||
this.$emit('contextmenu', event, payload);
|
this.$emit('contextmenu', event, payload);
|
||||||
},
|
},
|
||||||
|
@@ -85,7 +85,7 @@
|
|||||||
@click="showFakerModal"
|
@click="showFakerModal"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
|
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
|
||||||
<span>{{ $t('message.tableFiller') }}</span>
|
<span>{{ $tc('message.insertRow', 2) }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="dropdown table-dropdown pr-2">
|
<div class="dropdown table-dropdown pr-2">
|
||||||
|
@@ -80,6 +80,7 @@ module.exports = {
|
|||||||
deterministic: 'Deterministic',
|
deterministic: 'Deterministic',
|
||||||
context: 'Context',
|
context: 'Context',
|
||||||
export: 'Export',
|
export: 'Export',
|
||||||
|
import: 'Import',
|
||||||
returns: 'Returns',
|
returns: 'Returns',
|
||||||
timing: 'Timing',
|
timing: 'Timing',
|
||||||
state: 'State',
|
state: 'State',
|
||||||
@@ -122,9 +123,23 @@ module.exports = {
|
|||||||
select: 'Select',
|
select: 'Select',
|
||||||
passphrase: 'Passphrase',
|
passphrase: 'Passphrase',
|
||||||
filter: 'Filter',
|
filter: 'Filter',
|
||||||
|
change: 'Change',
|
||||||
|
views: 'Views',
|
||||||
|
triggers: 'Triggers',
|
||||||
|
routines: 'Routines',
|
||||||
|
functions: 'Functions',
|
||||||
|
schedulers: 'Schedulers',
|
||||||
|
includes: 'Includes',
|
||||||
|
drop: 'Drop',
|
||||||
|
completed: 'Completed',
|
||||||
|
aborted: 'Aborted',
|
||||||
disabled: 'Disabled',
|
disabled: 'Disabled',
|
||||||
enable: 'Enable',
|
enable: 'Enable',
|
||||||
disable: 'Disable'
|
disable: 'Disable',
|
||||||
|
commit: 'Commit',
|
||||||
|
rollback: 'Rollback',
|
||||||
|
connectionString: 'Connection string',
|
||||||
|
contributors: 'Contributors'
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
appWelcome: 'Welcome to Antares SQL Client!',
|
appWelcome: 'Welcome to Antares SQL Client!',
|
||||||
@@ -250,8 +265,29 @@ module.exports = {
|
|||||||
searchForQueries: 'Search for queries',
|
searchForQueries: 'Search for queries',
|
||||||
killProcess: 'Kill process',
|
killProcess: 'Kill process',
|
||||||
closeTab: 'Close tab',
|
closeTab: 'Close tab',
|
||||||
|
exportSchema: 'Export schema',
|
||||||
|
importSchema: 'Import schema',
|
||||||
|
directoryPath: 'Directory path',
|
||||||
|
newInserStmtEvery: 'New INSERT statement every',
|
||||||
|
processingTableExport: 'Processing {table}',
|
||||||
|
fechingTableExport: 'Fetching {table} data',
|
||||||
|
writingTableExport: 'Writing {table} data',
|
||||||
|
checkAllTables: 'Check all tables',
|
||||||
|
uncheckAllTables: 'Uncheck all tables',
|
||||||
goToDownloadPage: 'Go to download page',
|
goToDownloadPage: 'Go to download page',
|
||||||
readOnlyMode: 'Read-only mode'
|
readOnlyMode: 'Read-only mode',
|
||||||
|
killQuery: 'Kill query',
|
||||||
|
insertRow: 'Insert row | Insert rows',
|
||||||
|
commitMode: 'Commit mode',
|
||||||
|
autoCommit: 'Auto commit',
|
||||||
|
manualCommit: 'Manual commit',
|
||||||
|
actionSuccessful: '{action} successful',
|
||||||
|
importQueryErrors: 'Warning: {n} error has accurrend | Warning: {n} errors occurred',
|
||||||
|
executedQueries: '{n} query executed | {n} queries executed',
|
||||||
|
ourputFormat: 'Output format',
|
||||||
|
singleFile: 'Single {ext} file',
|
||||||
|
zipCompressedFile: 'ZIP compressed {ext} file',
|
||||||
|
disableBlur: 'Disable blur'
|
||||||
},
|
},
|
||||||
faker: {
|
faker: {
|
||||||
address: 'Address',
|
address: 'Address',
|
||||||
|
@@ -13,7 +13,8 @@ const i18n = new VueI18n({
|
|||||||
'pt-BR': require('./pt-BR'),
|
'pt-BR': require('./pt-BR'),
|
||||||
'de-DE': require('./de-DE'),
|
'de-DE': require('./de-DE'),
|
||||||
'vi-VN': require('./vi-VN'),
|
'vi-VN': require('./vi-VN'),
|
||||||
'ja-JP': require('./ja-JP')
|
'ja-JP': require('./ja-JP'),
|
||||||
|
'zh-CN': require('./zh-CN')
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
export default i18n;
|
export default i18n;
|
||||||
|
@@ -80,6 +80,7 @@ module.exports = {
|
|||||||
deterministic: 'Deterministico',
|
deterministic: 'Deterministico',
|
||||||
context: 'Contesto',
|
context: 'Contesto',
|
||||||
export: 'Esporta',
|
export: 'Esporta',
|
||||||
|
import: 'Importa',
|
||||||
returns: 'Ritorna',
|
returns: 'Ritorna',
|
||||||
timing: 'Temporizzazione',
|
timing: 'Temporizzazione',
|
||||||
state: 'Stato',
|
state: 'Stato',
|
||||||
@@ -122,6 +123,16 @@ module.exports = {
|
|||||||
select: 'Seleziona',
|
select: 'Seleziona',
|
||||||
passphrase: 'Passphrase',
|
passphrase: 'Passphrase',
|
||||||
filter: 'Filtra',
|
filter: 'Filtra',
|
||||||
|
change: 'Cambia',
|
||||||
|
views: 'Viste',
|
||||||
|
triggers: 'Trigger',
|
||||||
|
routines: 'Routine',
|
||||||
|
functions: 'Function',
|
||||||
|
schedulers: 'Scheduler',
|
||||||
|
includes: 'Includi',
|
||||||
|
drop: 'Drop',
|
||||||
|
completed: 'Completato',
|
||||||
|
aborted: 'Annullato',
|
||||||
disabled: 'Disabilitato',
|
disabled: 'Disabilitato',
|
||||||
enable: 'Abilita',
|
enable: 'Abilita',
|
||||||
disable: 'Disabilita'
|
disable: 'Disabilita'
|
||||||
@@ -237,6 +248,15 @@ module.exports = {
|
|||||||
noOpenTabs: 'Non ci sono tab aperte, naviga nella barra sinistra o:',
|
noOpenTabs: 'Non ci sono tab aperte, naviga nella barra sinistra o:',
|
||||||
noSchema: 'Nessuno schema',
|
noSchema: 'Nessuno schema',
|
||||||
restorePreviourSession: 'Ripristina sessione precedente',
|
restorePreviourSession: 'Ripristina sessione precedente',
|
||||||
|
exportSchema: 'Esporta schema',
|
||||||
|
importSchema: 'Importa schema',
|
||||||
|
directoryPath: 'Percorso directory',
|
||||||
|
newInserStmtEvery: 'Nuova istruzione INSERT ogni',
|
||||||
|
processingTableExport: 'Processo {table}',
|
||||||
|
fechingTableExport: 'Ricavo i dati {table}',
|
||||||
|
writingTableExport: 'Scrittura dati {table}',
|
||||||
|
checkAllTables: 'Seleziona tutte le tabelle',
|
||||||
|
uncheckAllTables: 'Deseleziona tutte le tabelle',
|
||||||
runQuery: 'Esegui query',
|
runQuery: 'Esegui query',
|
||||||
thereAreNoTableFields: 'Non ci sono campi della tabella',
|
thereAreNoTableFields: 'Non ci sono campi della tabella',
|
||||||
newTable: 'Nuova tabella',
|
newTable: 'Nuova tabella',
|
||||||
@@ -251,7 +271,9 @@ module.exports = {
|
|||||||
killProcess: 'Uccidi processo',
|
killProcess: 'Uccidi processo',
|
||||||
closeTab: 'Chiudi tab',
|
closeTab: 'Chiudi tab',
|
||||||
goToDownloadPage: 'Vai alla pagina di download',
|
goToDownloadPage: 'Vai alla pagina di download',
|
||||||
readOnlyMode: 'Modalità sola lettura'
|
readOnlyMode: 'Modalità sola lettura',
|
||||||
|
importQueryErrors: 'Attenzione: si è verificato un errore | Attenzione si sono verificati {n} errori',
|
||||||
|
executedQueries: '{n} query eseguite | {n} query eseguite'
|
||||||
},
|
},
|
||||||
faker: {
|
faker: {
|
||||||
address: 'Indirizzo',
|
address: 'Indirizzo',
|
||||||
|
@@ -106,7 +106,39 @@ module.exports = {
|
|||||||
array: 'Array',
|
array: 'Array',
|
||||||
changelog: 'Logs de alteração',
|
changelog: 'Logs de alteração',
|
||||||
format: 'Formato',
|
format: 'Formato',
|
||||||
sshTunnel: 'SSH túnel'
|
sshTunnel: 'SSH túnel',
|
||||||
|
structure: 'Estrutura',
|
||||||
|
small: 'Pequeno',
|
||||||
|
medium: 'Médio',
|
||||||
|
large: 'Grande',
|
||||||
|
row: 'Linha | Linhas',
|
||||||
|
cell: 'Celula | Células',
|
||||||
|
triggerFunction: 'Gatinho de função | Gatilhos de Funções',
|
||||||
|
all: 'Todos',
|
||||||
|
duplicate: 'Duplicado',
|
||||||
|
routine: 'Rotina',
|
||||||
|
new: 'Novo',
|
||||||
|
history: 'Histórico',
|
||||||
|
select: 'Seleciomar',
|
||||||
|
passphrase: 'Palavara-Passe',
|
||||||
|
filter: 'Filtrar',
|
||||||
|
change: 'Alterar',
|
||||||
|
views: 'Visualizações',
|
||||||
|
triggers: 'Gatilhos',
|
||||||
|
routines: 'Rotinas',
|
||||||
|
functions: 'Funções',
|
||||||
|
schedulers: 'Agendadores',
|
||||||
|
includes: 'Includes',
|
||||||
|
drop: 'Drop',
|
||||||
|
completed: 'Completo',
|
||||||
|
aborted: 'Abortado',
|
||||||
|
disabled: 'Inativo',
|
||||||
|
enable: 'Ativo',
|
||||||
|
disable: 'Disable',
|
||||||
|
commit: 'Enviar',
|
||||||
|
rollback: 'Reverter',
|
||||||
|
connectionString: 'String da conexão',
|
||||||
|
contributors: 'Contribuintes'
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
appWelcome: 'Bem vindo ao Antares SQL Client!',
|
appWelcome: 'Bem vindo ao Antares SQL Client!',
|
||||||
@@ -212,7 +244,48 @@ module.exports = {
|
|||||||
deleteSchema: 'Apagar schema',
|
deleteSchema: 'Apagar schema',
|
||||||
markdownSupported: 'Markdown suportado',
|
markdownSupported: 'Markdown suportado',
|
||||||
plantATree: 'Plante uma árvore',
|
plantATree: 'Plante uma árvore',
|
||||||
enableSsh: 'Habilitar SSH'
|
enableSsh: 'Habilitar SSH',
|
||||||
|
pageNumber: 'Número de página',
|
||||||
|
duplicateTable: 'Duplicar tabela',
|
||||||
|
noOpenTabs: 'Nenhuma aba aberta, navege na barra lateral ou:',
|
||||||
|
noSchema: 'Nenhum banco de dados',
|
||||||
|
restorePreviourSession: 'Restaurar sessão anterior',
|
||||||
|
runQuery: 'Executar Query',
|
||||||
|
thereAreNoTableFields: 'Nenhum campo na tabela',
|
||||||
|
newTable: 'Nova tabela',
|
||||||
|
newView: 'Nova vista',
|
||||||
|
newTrigger: 'Novo gatilho',
|
||||||
|
newRoutine: 'Nova rotina',
|
||||||
|
newFunction: 'Nova função',
|
||||||
|
newScheduler: 'Novo agendento',
|
||||||
|
newTriggerFunction: 'Novo gatinho de função',
|
||||||
|
thereIsNoQueriesYet: 'Nenhuma consulta ainda',
|
||||||
|
searchForQueries: 'Pesquisar por consultas',
|
||||||
|
killProcess: 'Matar processo',
|
||||||
|
closeTab: 'Fechar aba',
|
||||||
|
exportSchema: 'Exportar banco de dados',
|
||||||
|
importSchema: 'Importar banco de dados',
|
||||||
|
directoryPath: 'Caminho da pasta',
|
||||||
|
newInserStmtEvery: 'Nova query INSERT',
|
||||||
|
processingTableExport: 'Processando {table}',
|
||||||
|
fechingTableExport: 'Carregando dados da tabela {table}',
|
||||||
|
writingTableExport: 'Escrevendo dados na tabela {table}',
|
||||||
|
checkAllTables: 'Marcar todas as tabelas',
|
||||||
|
uncheckAllTables: 'Desmarcar todas as tabelas',
|
||||||
|
goToDownloadPage: 'Ir a página de download',
|
||||||
|
readOnlyMode: 'Modo somente leitura',
|
||||||
|
killQuery: 'Matar consulta',
|
||||||
|
insertRow: 'Inserir linha | Inserir linhas',
|
||||||
|
commitMode: 'Modo de confirmação',
|
||||||
|
autoCommit: 'Auto confirmar',
|
||||||
|
manualCommit: 'Confirmar manualmente',
|
||||||
|
actionSuccessful: '{action} teve sucesso',
|
||||||
|
importQueryErrors: 'Aviso: ocorreu {n} erro | Aviso: ocorreram {n} erros',
|
||||||
|
executedQueries: '{n} consulta executada | {n} consultas executadas',
|
||||||
|
ourputFormat: 'Formato da saída',
|
||||||
|
singleFile: 'Arquivo {ext} único',
|
||||||
|
zipCompressedFile: 'Arquivo compactado {ext} ZIP',
|
||||||
|
disableBlur: 'Desabilitar Blur'
|
||||||
},
|
},
|
||||||
faker: {
|
faker: {
|
||||||
address: 'Endereço',
|
address: 'Endereço',
|
||||||
|
@@ -7,5 +7,6 @@ export default {
|
|||||||
'pt-BR': 'Português (Brasil)',
|
'pt-BR': 'Português (Brasil)',
|
||||||
'de-DE': 'Deutsch (Deutschland)',
|
'de-DE': 'Deutsch (Deutschland)',
|
||||||
'vi-VN': 'Tiếng Việt',
|
'vi-VN': 'Tiếng Việt',
|
||||||
'ja-JP': '日本語'
|
'ja-JP': '日本語',
|
||||||
|
'zh-CN': '简体中文'
|
||||||
};
|
};
|
||||||
|
@@ -120,7 +120,11 @@ module.exports = {
|
|||||||
new: 'Mới',
|
new: 'Mới',
|
||||||
history: 'Lịch sử',
|
history: 'Lịch sử',
|
||||||
select: 'Chọn',
|
select: 'Chọn',
|
||||||
passphrase: 'Cụm mật khẩu'
|
passphrase: 'Cụm mật khẩu',
|
||||||
|
filter: 'Bộ lọc',
|
||||||
|
disabled: 'Đã tắt',
|
||||||
|
enable: 'Bật',
|
||||||
|
disable: 'Tắt'
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
appWelcome: 'Chào bạn đến với Antares SQL Client!',
|
appWelcome: 'Chào bạn đến với Antares SQL Client!',
|
||||||
@@ -244,7 +248,10 @@ module.exports = {
|
|||||||
newTriggerFunction: 'Chức năng kích hoạt mới',
|
newTriggerFunction: 'Chức năng kích hoạt mới',
|
||||||
thereIsNoQueriesYet: 'Không có truy vấn nào',
|
thereIsNoQueriesYet: 'Không có truy vấn nào',
|
||||||
searchForQueries: 'Tìm kiếm truy vấn',
|
searchForQueries: 'Tìm kiếm truy vấn',
|
||||||
killProcess: 'Huỷ quá trình'
|
killProcess: 'Huỷ quá trình',
|
||||||
|
closeTab: 'Đóng tab',
|
||||||
|
goToDownloadPage: 'Tới trang tải về',
|
||||||
|
readOnlyMode: 'Chế độ chỉ đọc'
|
||||||
},
|
},
|
||||||
faker: {
|
faker: {
|
||||||
address: 'Địa chỉ',
|
address: 'Địa chỉ',
|
||||||
|
421
src/renderer/i18n/zh-CN.js
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
module.exports = {
|
||||||
|
word: {
|
||||||
|
edit: '编辑',
|
||||||
|
save: '保存',
|
||||||
|
close: '关闭',
|
||||||
|
delete: '删除',
|
||||||
|
confirm: '确定',
|
||||||
|
cancel: '取消',
|
||||||
|
send: '发送',
|
||||||
|
connectionName: '连接名称',
|
||||||
|
client: 'Client',
|
||||||
|
hostName: '主机名',
|
||||||
|
port: '端口',
|
||||||
|
user: '用户',
|
||||||
|
password: '密码',
|
||||||
|
credentials: '凭据',
|
||||||
|
connect: '连接',
|
||||||
|
connected: '已连接',
|
||||||
|
disconnect: '断开连接',
|
||||||
|
disconnected: '已断开',
|
||||||
|
refresh: '刷新',
|
||||||
|
settings: '设置',
|
||||||
|
general: '一般',
|
||||||
|
themes: '主题',
|
||||||
|
update: '更新',
|
||||||
|
about: '关于',
|
||||||
|
language: '语言',
|
||||||
|
version: '版本',
|
||||||
|
donate: '捐赠',
|
||||||
|
run: '运行',
|
||||||
|
schema: 'schema',
|
||||||
|
results: '结果',
|
||||||
|
size: '尺寸',
|
||||||
|
seconds: '秒',
|
||||||
|
type: '类型',
|
||||||
|
mimeType: 'MIME类型',
|
||||||
|
download: '下载',
|
||||||
|
add: '新增',
|
||||||
|
data: '数据',
|
||||||
|
properties: '属性',
|
||||||
|
insert: '插入',
|
||||||
|
connecting: '连接中',
|
||||||
|
name: '名称',
|
||||||
|
collation: '排序规则',
|
||||||
|
clear: '清除',
|
||||||
|
options: '选项',
|
||||||
|
autoRefresh: '自动刷新',
|
||||||
|
indexes: '索引',
|
||||||
|
foreignKeys: '外键',
|
||||||
|
length: '长度',
|
||||||
|
unsigned: '无符号',
|
||||||
|
default: '默认',
|
||||||
|
comment: '注释',
|
||||||
|
key: '键',
|
||||||
|
order: 'Order',
|
||||||
|
expression: '表达式',
|
||||||
|
autoIncrement: '自动增量',
|
||||||
|
engine: 'Engine',
|
||||||
|
field: '字段',
|
||||||
|
approximately: '大约',
|
||||||
|
total: '总计',
|
||||||
|
table: '表',
|
||||||
|
discard: '弃置',
|
||||||
|
stay: '等待',
|
||||||
|
author: '作者',
|
||||||
|
light: 'Light',
|
||||||
|
dark: 'Dark',
|
||||||
|
autoCompletion: '自动完成',
|
||||||
|
application: '应用程序',
|
||||||
|
editor: '编辑器',
|
||||||
|
view: '视图',
|
||||||
|
definer: '定义者',
|
||||||
|
algorithm: 'Algorithm',
|
||||||
|
trigger: '触发器',
|
||||||
|
storedRoutine: '存储例程',
|
||||||
|
scheduler: '调度器',
|
||||||
|
event: '事件',
|
||||||
|
parameters: '参数',
|
||||||
|
function: '函数',
|
||||||
|
deterministic: 'Deterministic',
|
||||||
|
context: '上下文',
|
||||||
|
export: '导出',
|
||||||
|
returns: '返回',
|
||||||
|
timing: '定时器',
|
||||||
|
state: '状态',
|
||||||
|
execution: '执行',
|
||||||
|
starts: '开始',
|
||||||
|
ends: '结束',
|
||||||
|
ssl: 'SSL',
|
||||||
|
privateKey: '私钥',
|
||||||
|
certificate: '证书',
|
||||||
|
caCertificate: 'CA 证书',
|
||||||
|
ciphers: 'Ciphers',
|
||||||
|
upload: '上传',
|
||||||
|
browse: '浏览',
|
||||||
|
faker: 'Faker',
|
||||||
|
content: '内容',
|
||||||
|
cut: '剪切',
|
||||||
|
copy: '复制',
|
||||||
|
paste: '粘贴',
|
||||||
|
tools: '工具',
|
||||||
|
variables: '变量',
|
||||||
|
processes: '进程',
|
||||||
|
database: '数据库',
|
||||||
|
scratchpad: 'Scratchpad',
|
||||||
|
array: '数组',
|
||||||
|
changelog: '更改日志',
|
||||||
|
format: '格式',
|
||||||
|
sshTunnel: 'SSH 隧道',
|
||||||
|
structure: '结构',
|
||||||
|
small: '小',
|
||||||
|
medium: '中',
|
||||||
|
large: '大',
|
||||||
|
row: '行',
|
||||||
|
cell: '单元格',
|
||||||
|
triggerFunction: '触发函数',
|
||||||
|
all: '全部',
|
||||||
|
duplicate: '重复',
|
||||||
|
routine: '例程',
|
||||||
|
new: 'New',
|
||||||
|
history: '历史记录',
|
||||||
|
select: '选择',
|
||||||
|
passphrase: '密码',
|
||||||
|
filter: '过滤器',
|
||||||
|
disabled: '禁用',
|
||||||
|
enable: '启用',
|
||||||
|
disable: '是否禁用'
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
appWelcome: '欢迎来到Antares SQL Client!',
|
||||||
|
appFirstStep: '你的第一步: 创建一个新的数据库连接.',
|
||||||
|
addConnection: '添加连接',
|
||||||
|
createConnection: '创建连接',
|
||||||
|
createNewConnection: '创建新的连接',
|
||||||
|
askCredentials: '询问凭证',
|
||||||
|
testConnection: '测试连接',
|
||||||
|
editConnection: '编辑连接',
|
||||||
|
deleteConnection: '删除连接',
|
||||||
|
deleteCorfirm: '您是否确认取消',
|
||||||
|
connectionSuccessfullyMade: '连接成功建立!',
|
||||||
|
madeWithJS: '用💛和JavaScript制造!',
|
||||||
|
checkForUpdates: '检查更新',
|
||||||
|
noUpdatesAvailable: '没有可用的更新',
|
||||||
|
checkingForUpdate: '正在检查更新',
|
||||||
|
checkFailure: '检查失败,请稍后再试',
|
||||||
|
updateAvailable: '可用的更新',
|
||||||
|
downloadingUpdate: '正在下载更新',
|
||||||
|
updateDownloaded: '更新已下载',
|
||||||
|
restartToInstall: '重启Antares完成更新',
|
||||||
|
unableEditFieldWithoutPrimary: '无法编辑一个在结果集中没有主键的字段',
|
||||||
|
editCell: '编辑单元格',
|
||||||
|
deleteRows: '删除行 | 删除{count}行',
|
||||||
|
confirmToDeleteRows: '你是否确认要删除一行? | 您是否确认要删除{count}行?',
|
||||||
|
notificationsTimeout: '通知超时',
|
||||||
|
uploadFile: '上传文件',
|
||||||
|
addNewRow: '添加新行',
|
||||||
|
numberOfInserts: '插入的数量',
|
||||||
|
openNewTab: '打开一个新标签',
|
||||||
|
affectedRows: '受影响的行',
|
||||||
|
createNewDatabase: '创建新的数据库',
|
||||||
|
databaseName: '数据库名称',
|
||||||
|
serverDefault: '默认服务器',
|
||||||
|
deleteDatabase: '删除数据库',
|
||||||
|
editDatabase: '编辑数据库',
|
||||||
|
clearChanges: '清除变化',
|
||||||
|
addNewField: '添加新字段',
|
||||||
|
manageIndexes: '管理索引',
|
||||||
|
manageForeignKeys: '管理外键',
|
||||||
|
allowNull: '允许NULL',
|
||||||
|
zeroFill: '填充零',
|
||||||
|
customValue: '自定义值',
|
||||||
|
onUpdate: '在更新时',
|
||||||
|
deleteField: '删除字段',
|
||||||
|
createNewIndex: '创建新的索引',
|
||||||
|
addToIndex: '添加到索引',
|
||||||
|
createNewTable: '创建新表',
|
||||||
|
emptyTable: '清空表',
|
||||||
|
deleteTable: '删除表',
|
||||||
|
emptyCorfirm: '你是否确认清空',
|
||||||
|
unsavedChanges: '未保存的更改',
|
||||||
|
discardUnsavedChanges: '你有一些未保存的修改。关闭这个标签,这些变化将被丢弃.',
|
||||||
|
thereAreNoIndexes: '没有索引',
|
||||||
|
thereAreNoForeign: '没有外键',
|
||||||
|
createNewForeign: '创建新的外键',
|
||||||
|
referenceTable: '参考表',
|
||||||
|
referenceField: '参考字段',
|
||||||
|
foreignFields: '外键字段',
|
||||||
|
invalidDefault: '无效的默认值',
|
||||||
|
onDelete: '在删除时',
|
||||||
|
applicationTheme: '应用主题',
|
||||||
|
editorTheme: '编辑器主题',
|
||||||
|
wrapLongLines: '超出换行显示',
|
||||||
|
selectStatement: '选择语句',
|
||||||
|
triggerStatement: '触发器语句',
|
||||||
|
sqlSecurity: 'SQL安全',
|
||||||
|
updateOption: '更新选项',
|
||||||
|
deleteView: '删除视图',
|
||||||
|
createNewView: '创建新视图',
|
||||||
|
deleteTrigger: '删除触发器',
|
||||||
|
createNewTrigger: '创建新的触发器',
|
||||||
|
currentUser: '当前用户',
|
||||||
|
routineBody: '例程主体',
|
||||||
|
dataAccess: '数据访问',
|
||||||
|
thereAreNoParameters: '没有参数',
|
||||||
|
createNewParameter: '创建新参数',
|
||||||
|
createNewRoutine: '创建新的例程',
|
||||||
|
deleteRoutine: '删除例程',
|
||||||
|
functionBody: '函数体',
|
||||||
|
createNewFunction: '创建新函数',
|
||||||
|
deleteFunction: '删除函数',
|
||||||
|
schedulerBody: '调度器主体',
|
||||||
|
createNewScheduler: '创建新的调度器',
|
||||||
|
deleteScheduler: '删除调度器',
|
||||||
|
preserveOnCompletion: '完成时保存',
|
||||||
|
enableSsl: '启用SSL',
|
||||||
|
manualValue: '手动值',
|
||||||
|
tableFiller: '表填充器',
|
||||||
|
fakeDataLanguage: '伪造的数据语言',
|
||||||
|
searchForElements: '搜索元素',
|
||||||
|
selectAll: '选择所有',
|
||||||
|
queryDuration: '查询时间',
|
||||||
|
includeBetaUpdates: '包括测试版更新',
|
||||||
|
setNull: '设置NULL',
|
||||||
|
processesList: '进程列表',
|
||||||
|
processInfo: '进程信息',
|
||||||
|
manageUsers: '管理用户',
|
||||||
|
createNewSchema: '创建新模式',
|
||||||
|
schemaName: '模式名称',
|
||||||
|
editSchema: '编辑模式',
|
||||||
|
deleteSchema: '删除模式',
|
||||||
|
markdownSupported: '支持Markdown',
|
||||||
|
plantATree: '种植一棵树',
|
||||||
|
dataTabPageSize: '数据标签的页面大小',
|
||||||
|
enableSsh: '启用SSH',
|
||||||
|
pageNumber: '页数',
|
||||||
|
duplicateTable: '重复的表格',
|
||||||
|
noOpenTabs: '没有打开的标签,在左栏导航或:',
|
||||||
|
noSchema: '没有模式',
|
||||||
|
restorePreviourSession: '恢复以前的会话',
|
||||||
|
runQuery: '运行查询',
|
||||||
|
thereAreNoTableFields: '没有表的字段',
|
||||||
|
newTable: '新表',
|
||||||
|
newView: '新视图',
|
||||||
|
newTrigger: '新触发器',
|
||||||
|
newRoutine: '新例程',
|
||||||
|
newFunction: '新函数',
|
||||||
|
newScheduler: '新调度器',
|
||||||
|
newTriggerFunction: '新触发函数',
|
||||||
|
thereIsNoQueriesYet: '还没有查询',
|
||||||
|
searchForQueries: '搜索查询',
|
||||||
|
killProcess: '杀死进程',
|
||||||
|
closeTab: '关闭标签',
|
||||||
|
goToDownloadPage: '跳转到下载页面',
|
||||||
|
readOnlyMode: '只读模式',
|
||||||
|
killQuery: '停止查询'
|
||||||
|
},
|
||||||
|
faker: {
|
||||||
|
address: '地址',
|
||||||
|
commerce: '商业',
|
||||||
|
company: '公司',
|
||||||
|
database: '数据库',
|
||||||
|
date: '日期',
|
||||||
|
finance: '财务',
|
||||||
|
git: 'Git',
|
||||||
|
hacker: '黑客',
|
||||||
|
internet: '互联网',
|
||||||
|
lorem: 'Lorem',
|
||||||
|
name: '姓名',
|
||||||
|
music: '音乐',
|
||||||
|
phone: '电话',
|
||||||
|
random: '随机',
|
||||||
|
system: '系统',
|
||||||
|
time: '时间',
|
||||||
|
vehicle: '车辆',
|
||||||
|
zipCode: '邮政编码',
|
||||||
|
zipCodeByState: '按州的邮编',
|
||||||
|
city: '城市',
|
||||||
|
cityPrefix: '城市前缀',
|
||||||
|
citySuffix: '城市后缀',
|
||||||
|
streetName: '街道名称',
|
||||||
|
streetAddress: '街道地址',
|
||||||
|
streetSuffix: '街道前缀',
|
||||||
|
streetPrefix: '街道后缀',
|
||||||
|
secondaryAddress: '次要地址',
|
||||||
|
county: '县',
|
||||||
|
country: '国家',
|
||||||
|
countryCode: '国家代码',
|
||||||
|
state: '州',
|
||||||
|
stateAbbr: '州的缩写',
|
||||||
|
latitude: '纬度',
|
||||||
|
longitude: '经度',
|
||||||
|
direction: '方向',
|
||||||
|
cardinalDirection: 'Cardinal direction',
|
||||||
|
ordinalDirection: 'Ordinal direction',
|
||||||
|
nearbyGPSCoordinate: '附近的GPS坐标',
|
||||||
|
timeZone: '时区',
|
||||||
|
color: '颜色',
|
||||||
|
department: '部门',
|
||||||
|
productName: '产品名称',
|
||||||
|
price: '价格',
|
||||||
|
productAdjective: '产品形容词',
|
||||||
|
productMaterial: '产品材料',
|
||||||
|
product: '产品',
|
||||||
|
productDescription: '产品描述',
|
||||||
|
suffixes: '后缀',
|
||||||
|
companyName: '公司名称',
|
||||||
|
companySuffix: '公司后缀',
|
||||||
|
catchPhrase: 'Catch phrase',
|
||||||
|
bs: 'BS',
|
||||||
|
catchPhraseAdjective: 'Catch phrase adjective',
|
||||||
|
catchPhraseDescriptor: 'Catch phrase descriptor',
|
||||||
|
catchPhraseNoun: 'Catch phrase noun',
|
||||||
|
bsAdjective: 'BS adjective',
|
||||||
|
bsBuzz: 'BS buzz',
|
||||||
|
bsNoun: 'BS noun',
|
||||||
|
column: '列',
|
||||||
|
type: '类型',
|
||||||
|
collation: '校对',
|
||||||
|
engine: 'Engine',
|
||||||
|
past: '过去',
|
||||||
|
future: '未来',
|
||||||
|
between: '之间',
|
||||||
|
recent: '最近',
|
||||||
|
soon: '很快',
|
||||||
|
month: '月',
|
||||||
|
weekday: '工作日',
|
||||||
|
account: '账户',
|
||||||
|
accountName: '账户名称',
|
||||||
|
routingNumber: '路由号码',
|
||||||
|
mask: '掩码',
|
||||||
|
amount: '金额',
|
||||||
|
transactionType: '交易类型',
|
||||||
|
currencyCode: '货币代码',
|
||||||
|
currencyName: '货币名称',
|
||||||
|
currencySymbol: '货币符号',
|
||||||
|
bitcoinAddress: '比特币地址',
|
||||||
|
litecoinAddress: '莱特币地址',
|
||||||
|
creditCardNumber: '信用卡号码',
|
||||||
|
creditCardCVV: '信用卡CVV',
|
||||||
|
ethereumAddress: '以太坊地址',
|
||||||
|
iban: 'Iban',
|
||||||
|
bic: 'Bic',
|
||||||
|
transactionDescription: '交易描述',
|
||||||
|
branch: '分支',
|
||||||
|
commitEntry: '提交条目',
|
||||||
|
commitMessage: '提交信息',
|
||||||
|
commitSha: '提交 SHA',
|
||||||
|
shortSha: 'Short SHA',
|
||||||
|
abbreviation: '缩写',
|
||||||
|
adjective: '形容词',
|
||||||
|
noun: '名词',
|
||||||
|
verb: '动词',
|
||||||
|
ingverb: 'Ingverb',
|
||||||
|
phrase: '短语',
|
||||||
|
avatar: '头像',
|
||||||
|
email: '电子邮箱',
|
||||||
|
exampleEmail: '电子邮件例子',
|
||||||
|
userName: '用户名',
|
||||||
|
protocol: '协议',
|
||||||
|
url: 'Url',
|
||||||
|
domainName: 'Domin name',
|
||||||
|
domainSuffix: '域名后缀',
|
||||||
|
domainWord: 'Domain word',
|
||||||
|
ip: 'Ip',
|
||||||
|
ipv6: 'Ipv6',
|
||||||
|
userAgent: 'User agent',
|
||||||
|
mac: 'Mac',
|
||||||
|
password: '密码',
|
||||||
|
word: 'Word',
|
||||||
|
words: 'Words',
|
||||||
|
sentence: '句子',
|
||||||
|
slug: 'Slug',
|
||||||
|
sentences: '句子',
|
||||||
|
paragraph: '段落',
|
||||||
|
paragraphs: '段落',
|
||||||
|
text: '文本',
|
||||||
|
lines: '行',
|
||||||
|
genre: 'Genre',
|
||||||
|
firstName: '名',
|
||||||
|
lastName: '姓氏',
|
||||||
|
middleName: '中间名',
|
||||||
|
findName: '全名',
|
||||||
|
jobTitle: '职位名称',
|
||||||
|
gender: '性别',
|
||||||
|
prefix: '前缀',
|
||||||
|
suffix: '后缀',
|
||||||
|
title: '标题',
|
||||||
|
jobDescriptor: '工作描述',
|
||||||
|
jobArea: '工作领域',
|
||||||
|
jobType: '工作类型',
|
||||||
|
phoneNumber: '电话号码',
|
||||||
|
phoneNumberFormat: '电话号码格式',
|
||||||
|
phoneFormats: '电话格式',
|
||||||
|
number: 'Number',
|
||||||
|
float: 'Float',
|
||||||
|
arrayElement: '数组元素',
|
||||||
|
arrayElements: '数组元素',
|
||||||
|
objectElement: '对象元素',
|
||||||
|
uuid: 'Uuid',
|
||||||
|
boolean: 'Boolean',
|
||||||
|
image: 'Image',
|
||||||
|
locale: 'Locale',
|
||||||
|
alpha: 'Alpha',
|
||||||
|
alphaNumeric: 'Alphanumeric',
|
||||||
|
hexaDecimal: 'Hexadecimal',
|
||||||
|
fileName: '文件名',
|
||||||
|
commonFileName: '普通文件名',
|
||||||
|
mimeType: 'MIME类型',
|
||||||
|
commonFileType: '常见的文件类型',
|
||||||
|
commonFileExt: '常见的文件扩展名',
|
||||||
|
fileType: '文件类型',
|
||||||
|
fileExt: '文件扩展名',
|
||||||
|
directoryPath: '目录路径',
|
||||||
|
filePath: '文件路径',
|
||||||
|
semver: 'Semver',
|
||||||
|
manufacturer: '制造商',
|
||||||
|
model: '型号',
|
||||||
|
fuel: 'Fuel',
|
||||||
|
vin: 'Vin'
|
||||||
|
}
|
||||||
|
};
|
@@ -3,6 +3,7 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
import '@mdi/font/css/materialdesignicons.css';
|
import '@mdi/font/css/materialdesignicons.css';
|
||||||
|
import 'leaflet/dist/leaflet.css';
|
||||||
import '@/scss/main.scss';
|
import '@/scss/main.scss';
|
||||||
|
|
||||||
import App from '@/App.vue';
|
import App from '@/App.vue';
|
||||||
|
@@ -5,4 +5,12 @@ export default class {
|
|||||||
static getKey (params) {
|
static getKey (params) {
|
||||||
return ipcRenderer.sendSync('get-key', params);
|
return ipcRenderer.sendSync('get-key', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static showOpenDialog (options) {
|
||||||
|
return ipcRenderer.invoke('showOpenDialog', options);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDownloadPathDirectory () {
|
||||||
|
return ipcRenderer.invoke('get-download-dir-path');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,17 +1,21 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import { ipcRenderer } from 'electron';
|
import { ipcRenderer } from 'electron';
|
||||||
|
|
||||||
|
import connStringConstruct from '../libs/connStringDecode';
|
||||||
|
|
||||||
export default class {
|
export default class {
|
||||||
static makeTest (params) {
|
static makeTest (params) {
|
||||||
|
params = connStringConstruct(params);
|
||||||
return ipcRenderer.invoke('test-connection', params);
|
return ipcRenderer.invoke('test-connection', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
static checkConnection (params) {
|
static connect (params) {
|
||||||
return ipcRenderer.invoke('check-connection', params);
|
params = connStringConstruct(params);
|
||||||
|
return ipcRenderer.invoke('connect', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
static connect (params) {
|
static checkConnection (uid) {
|
||||||
return ipcRenderer.invoke('connect', params);
|
return ipcRenderer.invoke('check-connection', uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
static disconnect (uid) {
|
static disconnect (uid) {
|
||||||
|
@@ -46,6 +46,22 @@ export default class {
|
|||||||
return ipcRenderer.invoke('kill-process', params);
|
return ipcRenderer.invoke('kill-process', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static killTabQuery (params) {
|
||||||
|
return ipcRenderer.invoke('kill-tab-query', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static commitTab (params) {
|
||||||
|
return ipcRenderer.invoke('commit-tab', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static rollbackTab (params) {
|
||||||
|
return ipcRenderer.invoke('rollback-tab', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static destroyConnectionToCommit (params) {
|
||||||
|
return ipcRenderer.invoke('destroy-connection-to-commit', params);
|
||||||
|
}
|
||||||
|
|
||||||
static useSchema (params) {
|
static useSchema (params) {
|
||||||
return ipcRenderer.invoke('use-schema', params);
|
return ipcRenderer.invoke('use-schema', params);
|
||||||
}
|
}
|
||||||
@@ -53,4 +69,20 @@ export default class {
|
|||||||
static rawQuery (params) {
|
static rawQuery (params) {
|
||||||
return ipcRenderer.invoke('raw-query', params);
|
return ipcRenderer.invoke('raw-query', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static export (params) {
|
||||||
|
return ipcRenderer.invoke('export', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static abortExport () {
|
||||||
|
return ipcRenderer.invoke('abort-export');
|
||||||
|
}
|
||||||
|
|
||||||
|
static import (params) {
|
||||||
|
return ipcRenderer.invoke('import-sql', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static abortImport () {
|
||||||
|
return ipcRenderer.invoke('abort-import-sql');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
48
src/renderer/libs/connStringDecode.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import formatter from 'pg-connection-string'; // parses a connection string
|
||||||
|
|
||||||
|
const formatHost = host => {
|
||||||
|
const results = host === 'localhost' ? '127.0.0.1' : host;
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkForSSl = conn => {
|
||||||
|
return conn.includes('ssl=true');
|
||||||
|
};
|
||||||
|
|
||||||
|
const connStringConstruct = (args) => {
|
||||||
|
if (!args.pgConnString)
|
||||||
|
return args;
|
||||||
|
|
||||||
|
if (typeof args.pgConnString !== 'string')
|
||||||
|
return args;
|
||||||
|
|
||||||
|
const stringArgs = formatter.parse(args.pgConnString);
|
||||||
|
|
||||||
|
const client = args.client || 'pg';
|
||||||
|
|
||||||
|
args.client = client;
|
||||||
|
args.host = formatHost(stringArgs.host);
|
||||||
|
args.database = stringArgs.database;
|
||||||
|
args.port = stringArgs.port || '5432';
|
||||||
|
args.user = stringArgs.user;
|
||||||
|
args.password = stringArgs.password;
|
||||||
|
|
||||||
|
// ssh
|
||||||
|
args.ssh = stringArgs.ssh || args.ssh;
|
||||||
|
args.sshHost = stringArgs.sshHost;
|
||||||
|
args.sshUser = stringArgs.sshUser;
|
||||||
|
args.sshPass = stringArgs.sshPass;
|
||||||
|
args.sshKey = stringArgs.sshKey;
|
||||||
|
args.sshPort = stringArgs.sshPort;
|
||||||
|
|
||||||
|
// ssl mode
|
||||||
|
args.ssl = checkForSSl(args.pgConnString);
|
||||||
|
args.cert = stringArgs.sslcert;
|
||||||
|
args.key = stringArgs.sslkey;
|
||||||
|
args.ca = stringArgs.sslrootcert;
|
||||||
|
args.ciphers = stringArgs.ciphers;
|
||||||
|
|
||||||
|
return args;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connStringConstruct;
|
@@ -81,6 +81,15 @@
|
|||||||
"tsvector": $array-color,
|
"tsvector": $array-color,
|
||||||
"tsquery": $array-color,
|
"tsquery": $array-color,
|
||||||
"pg_node_tree": $array-color,
|
"pg_node_tree": $array-color,
|
||||||
|
"point": $array-color,
|
||||||
|
"linestring": $array-color,
|
||||||
|
"polygon": $array-color,
|
||||||
|
"geometry": $array-color,
|
||||||
|
"multipoint": $array-color,
|
||||||
|
"multilinestring": $array-color,
|
||||||
|
"multipolygon": $array-color,
|
||||||
|
"geomcollection": $array-color,
|
||||||
|
"geometrycollection": $array-color,
|
||||||
"aclitem": $array-color,
|
"aclitem": $array-color,
|
||||||
"unknown": $unknown-color,
|
"unknown": $unknown-color,
|
||||||
)
|
)
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
background-image: url("../images/svg/pg.svg");
|
background-image: url("../images/svg/pg.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dbi-sqlite {
|
&.dbi-sqlite {
|
||||||
background-image: url("../images/svg/sqlite.svg");
|
background-image: url("../images/svg/sqlite.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -59,6 +59,34 @@ option:checked {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cancellable {
|
||||||
|
color: transparent !important;
|
||||||
|
min-height: 0.8rem;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
> .mdi,
|
||||||
|
> .span {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "\2715";
|
||||||
|
color: $light-color;
|
||||||
|
font-weight: 700;
|
||||||
|
top: 36%;
|
||||||
|
display: block;
|
||||||
|
height: 0.8rem;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -0.4rem;
|
||||||
|
margin-top: -0.4rem;
|
||||||
|
opacity: 1;
|
||||||
|
padding: 0;
|
||||||
|
position: absolute;
|
||||||
|
width: 0.8rem;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.workspace-tabs {
|
.workspace-tabs {
|
||||||
align-content: baseline;
|
align-content: baseline;
|
||||||
|
|
||||||
@@ -137,6 +165,18 @@ option:checked {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-overlay{
|
||||||
|
background: rgba( 255, 255, 255, 0.1);
|
||||||
|
box-shadow: 0 8px 32px 0 rgba( 31, 38, 135, 0.37 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#wrapper:not(.no-blur){
|
||||||
|
.modal-overlay{
|
||||||
|
backdrop-filter: blur( 4px );
|
||||||
|
-webkit-backdrop-filter: blur( 4px );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab {
|
.tab {
|
||||||
|
@@ -20,7 +20,8 @@ export default {
|
|||||||
application_theme: persistentStore.get('application_theme', defaultAppTheme),
|
application_theme: persistentStore.get('application_theme', defaultAppTheme),
|
||||||
editor_theme: persistentStore.get('editor_theme', defaultEditorTheme),
|
editor_theme: persistentStore.get('editor_theme', defaultEditorTheme),
|
||||||
editor_font_size: persistentStore.get('editor_font_size', 'medium'),
|
editor_font_size: persistentStore.get('editor_font_size', 'medium'),
|
||||||
restore_tabs: persistentStore.get('restore_tabs', true)
|
restore_tabs: persistentStore.get('restore_tabs', true),
|
||||||
|
disable_blur: persistentStore.get('disable_blur', false)
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
getLocale: state => state.locale,
|
getLocale: state => state.locale,
|
||||||
@@ -33,7 +34,8 @@ export default {
|
|||||||
getApplicationTheme: state => state.application_theme,
|
getApplicationTheme: state => state.application_theme,
|
||||||
getEditorTheme: state => state.editor_theme,
|
getEditorTheme: state => state.editor_theme,
|
||||||
getEditorFontSize: state => state.editor_font_size,
|
getEditorFontSize: state => state.editor_font_size,
|
||||||
getRestoreTabs: state => state.restore_tabs
|
getRestoreTabs: state => state.restore_tabs,
|
||||||
|
getDisableBlur: state => state.disable_blur
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
SET_LOCALE (state, locale) {
|
SET_LOCALE (state, locale) {
|
||||||
@@ -80,6 +82,10 @@ export default {
|
|||||||
SET_RESTORE_TABS (state, val) {
|
SET_RESTORE_TABS (state, val) {
|
||||||
state.restore_tabs = val;
|
state.restore_tabs = val;
|
||||||
persistentStore.set('restore_tabs', state.restore_tabs);
|
persistentStore.set('restore_tabs', state.restore_tabs);
|
||||||
|
},
|
||||||
|
SET_DISABLE_BLUR (state, val) {
|
||||||
|
state.disable_blur = val;
|
||||||
|
persistentStore.set('disable_blur', state.disable_blur);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
@@ -115,6 +121,9 @@ export default {
|
|||||||
},
|
},
|
||||||
changeRestoreTabs ({ commit }, size) {
|
changeRestoreTabs ({ commit }, size) {
|
||||||
commit('SET_RESTORE_TABS', size);
|
commit('SET_RESTORE_TABS', size);
|
||||||
|
},
|
||||||
|
changeDisableBlur ({ commit }, val) {
|
||||||
|
commit('SET_DISABLE_BLUR', val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|