mirror of
https://github.com/Fabio286/antares.git
synced 2025-06-05 21:59:22 +02:00
Compare commits
99 Commits
Author | SHA1 | Date | |
---|---|---|---|
0b861d962d | |||
6cc098c6f0 | |||
40828cb3ff | |||
6086ca4a80 | |||
d92facf518 | |||
|
e9643a0d6b | ||
|
cc609a7051 | ||
796f61bf2f | |||
0f9c991f53 | |||
638a88a1fb | |||
026d74c8c8 | |||
77f8cac6cf | |||
d6fadf5db0 | |||
408ddeda56 | |||
88cd097ec0 | |||
f12e6a96dd | |||
a8ca8f2f76 | |||
|
6fcae957e1 | ||
|
1cd2d8abf3 | ||
ed3c5fe559 | |||
|
9601c59392 | ||
|
14d20a30c1 | ||
42376b4bc6 | |||
e9079adb25 | |||
86f011f34f | |||
bb02479b71 | |||
a67071e284 | |||
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 | |||
|
4e9f8d16ee | ||
|
d25c62b4da | ||
|
8cf738bac8 | ||
409ed54608 | |||
d9d3bf2bc9 | |||
9e9de7b5c5 | |||
|
b2a5b40c03 | ||
|
0de2321920 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"projectName": "antares",
|
||||
"projectOwner": "Fabio286",
|
||||
"projectOwner": "antares-sql",
|
||||
"repoType": "github",
|
||||
"repoHost": "https://github.com",
|
||||
"files": [
|
||||
@@ -138,6 +138,33 @@
|
||||
"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"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "fredatgithub",
|
||||
"name": "fred",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/6720055?v=4",
|
||||
"profile": "https://github.com/fredatgithub",
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
5
.github/ISSUE_TEMPLATE/bug_report.md
vendored
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):**
|
||||
|
||||
- Client [e.g. MySQL]
|
||||
- Version [e.g. 0.14.0]
|
||||
- Distribution: [e.g. exe, Linux Store, AppImage, dmg]
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
|
||||
- OS: [e.g. iOS]
|
||||
- Version [e.g. 22]
|
||||
- OS: [e.g. Windows 11]
|
||||
- Version [e.g. 21H2]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
2
.github/workflows/build-win.yml
vendored
2
.github/workflows/build-win.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest]
|
||||
os: [windows-2019]
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -5,4 +5,5 @@ node_modules
|
||||
thumbs.db
|
||||
NOTES.md
|
||||
*.txt
|
||||
package-lock.json
|
||||
package-lock.json
|
||||
*.heapsnapshot
|
14
.vscode/launch.json
vendored
14
.vscode/launch.json
vendored
@@ -2,8 +2,8 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Electron: Main",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"port": 9222,
|
||||
"protocol": "inspector",
|
||||
"request": "attach",
|
||||
@@ -18,7 +18,17 @@
|
||||
"sourceMaps": true,
|
||||
"type": "chrome",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Electron: Worker",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"port": 9224,
|
||||
"protocol": "inspector",
|
||||
"request": "attach",
|
||||
"sourceMaps": true,
|
||||
"type": "node",
|
||||
"timeout": 1000000
|
||||
},
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
|
100
CHANGELOG.md
100
CHANGELOG.md
@@ -2,6 +2,106 @@
|
||||
|
||||
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.5.2](https://github.com/antares-sql/antares/compare/v0.5.1...v0.5.2) (2022-04-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **core:** option to allow untrusted SSL connections ([6cc098c](https://github.com/antares-sql/antares/commit/6cc098c6f02fb52cc71c0141431ab75f12744a1c))
|
||||
* french translation updated, closes [#222](https://github.com/antares-sql/antares/issues/222) ([796f61b](https://github.com/antares-sql/antares/commit/796f61bf2feab0da515901e2137dc7bf04371d7d))
|
||||
* **PostgreSQL:** export functions and procedures ([a8ca8f2](https://github.com/antares-sql/antares/commit/a8ca8f2f76ab36c4afe84d602709386315f4b7d1))
|
||||
* **PostgreSQL:** export tables ([a67071e](https://github.com/antares-sql/antares/commit/a67071e28470bcbd0ec26780bb86f3c65750ded8))
|
||||
* **PostgreSQL:** export triggers ([42376b4](https://github.com/antares-sql/antares/commit/42376b4bc6dd8b630402d09b026d9fbc0b8646bb))
|
||||
* **PostgreSQL:** export user-defined types before tables ([bb02479](https://github.com/antares-sql/antares/commit/bb02479b71bf75a6e69e28af57c5fe213d3f30bc))
|
||||
* **PostgreSQL:** export views ([86f011f](https://github.com/antares-sql/antares/commit/86f011f34fec9d6829bce324493fea888a863ffc))
|
||||
* **PostgreSQL:** sql dump importer ([6086ca4](https://github.com/antares-sql/antares/commit/6086ca4a80b9ad6a07086446253d781f052d3abc))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **PostgreSQL:** wrong values exporting table content ([0f9c991](https://github.com/antares-sql/antares/commit/0f9c991f539560913fa0e9361a16e6448a066a27))
|
||||
* ssh tunnel not properly working, closes [#220](https://github.com/antares-sql/antares/issues/220) ([026d74c](https://github.com/antares-sql/antares/commit/026d74c8c88c605a3c8c963c211078f5b3dcfda1))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **PostgreSQL:** improved dump file ([408dded](https://github.com/antares-sql/antares/commit/408ddeda5634ab6bf41eff760271669170b60eb6))
|
||||
* **PostgreSQL:** improved views exportation ([638a88a](https://github.com/antares-sql/antares/commit/638a88a1fb35c048ff4c6d120aaaef831c846f58))
|
||||
|
||||
### [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)
|
||||
|
||||
|
||||
|
24
README.md
24
README.md
@@ -31,15 +31,15 @@ We are actively working on it, hoping to provide new cool features, improvements
|
||||
- Query suggestions and auto complete.
|
||||
- Query history: search through the last 1000 queries.
|
||||
- SSH tunnel support.
|
||||
- Manual commit mode.
|
||||
- Import and export database dumps.
|
||||
- Dark and light theme.
|
||||
- Editor themes.
|
||||
- Scratchpad.
|
||||
- Secure password storage.
|
||||
|
||||
## Philosophy
|
||||
|
||||
Why are we developing an SQL client when there are a lot of them on the market?
|
||||
The main goal is to develop a totally free, full featured, cross platform and open source alternative, empowered by JavaScript's ecosystem.
|
||||
The main goal is to develop a **forever 100% free (without paid premium feature)**, full featured, as possible community driven, cross platform and open source alternative, empowered by JavaScript ecosystem.
|
||||
A modern application created with minimalism and semplicity in mind, with features in the right places, not hundreds of tiny buttons, nested tabs or submenu; productivity comes first.
|
||||
|
||||
## Installation
|
||||
@@ -72,7 +72,6 @@ This is a roadmap with major features will come in near future.
|
||||
- Users management (add/edit/delete).
|
||||
- More context menu shortcuts.
|
||||
- More keyboard shortcuts.
|
||||
- Import/export and migration.
|
||||
- Support for other databases.
|
||||
- Apple Silicon distribution
|
||||
|
||||
@@ -105,7 +104,7 @@ This is a roadmap with major features will come in near future.
|
||||
|
||||
- 🌍 [Translate Antares](https://github.com/Fabio286/antares/wiki/Translate-Antares)
|
||||
- 📖 [Contributors Guide](https://github.com/Fabio286/antares/wiki/Contributors-Guide)
|
||||
- 🚧 [Project Board](https://github.com/users/Fabio286/projects/1)
|
||||
- 🚧 [Project Board](https://github.com/antares-sql/antares/projects/1)
|
||||
|
||||
## Contributors ✨
|
||||
|
||||
@@ -116,9 +115,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="https://fabiodistasio.it/"><img src="https://avatars.githubusercontent.com/u/31471771?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fabio Di Stasio</b></sub></a><br /><a href="https://github.com/Fabio286/antares/commits?author=Fabio286" title="Code">💻</a> <a href="#translation-Fabio286" title="Translation">🌍</a> <a href="https://github.com/Fabio286/antares/commits?author=Fabio286" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/giulioganci/"><img src="https://avatars.githubusercontent.com/u/4192159?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Giulio Ganci</b></sub></a><br /><a href="https://github.com/Fabio286/antares/commits?author=toriphes" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://christianratz.de/"><img src="https://avatars.githubusercontent.com/u/2630316?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Christian Ratz</b></sub></a><br /><a href="https://github.com/Fabio286/antares/commits?author=digitalgopnik" title="Code">💻</a> <a href="#translation-digitalgopnik" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://fabiodistasio.it/"><img src="https://avatars.githubusercontent.com/u/31471771?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fabio Di Stasio</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=Fabio286" title="Code">💻</a> <a href="#translation-Fabio286" title="Translation">🌍</a> <a href="https://github.com/antares-sql/antares/commits?author=Fabio286" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/giulioganci/"><img src="https://avatars.githubusercontent.com/u/4192159?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Giulio Ganci</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=toriphes" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://christianratz.de/"><img src="https://avatars.githubusercontent.com/u/2630316?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Christian Ratz</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=digitalgopnik" title="Code">💻</a> <a href="#translation-digitalgopnik" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://reverb6821.github.io/"><img src="https://avatars.githubusercontent.com/u/55198803?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Giuseppe Gigliotti</b></sub></a><br /><a href="#translation-reverb6821" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/Mohd-PH"><img src="https://avatars.githubusercontent.com/u/9362157?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mohd-PH</b></sub></a><br /><a href="#translation-Mohd-PH" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/hongkfui"><img src="https://avatars.githubusercontent.com/u/37477191?v=4?s=100" width="100px;" alt=""/><br /><sub><b>hongkfui</b></sub></a><br /><a href="#translation-hongkfui" title="Translation">🌍</a></td>
|
||||
@@ -129,10 +128,15 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<td align="center"><a href="https://ngoquocdat.com/"><img src="https://avatars.githubusercontent.com/u/56961917?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ngô Quốc Đạt</b></sub></a><br /><a href="#translation-datlechin" 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="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://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/antares-sql/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/antares-sql/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/antares-sql/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>
|
||||
<td align="center"><a href="https://github.com/fredatgithub"><img src="https://avatars.githubusercontent.com/u/6720055?v=4?s=100" width="100px;" alt=""/><br /><sub><b>fred</b></sub></a><br /><a href="#translation-fredatgithub" title="Translation">🌍</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
|
19
package.json
19
package.json
@@ -1,20 +1,21 @@
|
||||
{
|
||||
"name": "antares",
|
||||
"productName": "Antares",
|
||||
"version": "0.4.3",
|
||||
"version": "0.5.2",
|
||||
"description": "A modern, fast and productivity driven SQL client with a focus in UX.",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/Fabio286/antares.git",
|
||||
"repository": "https://github.com/antares-sql/antares.git",
|
||||
"scripts": {
|
||||
"debug": "npm run rebuild:electron && npm run debug-runner",
|
||||
"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:workers": "webpack --mode=production --config webpack.workers.config.js",
|
||||
"compile:renderer": "webpack --mode=production --config webpack.renderer.config.js",
|
||||
"build": "cross-env NODE_ENV=production npm run compile",
|
||||
"build:local": "npm run build && electron-builder",
|
||||
"build:appx": "npm run build:local -- --win appx",
|
||||
"rebuild:electron": "npm run postinstall",
|
||||
"rebuild:electron": "rimraf ./dist && npm run postinstall",
|
||||
"release": "standard-version",
|
||||
"release:pre": "npm run release -- --prerelease alpha",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
@@ -51,8 +52,7 @@
|
||||
"target": {
|
||||
"target": "default",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64"
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -109,7 +109,7 @@
|
||||
"@turf/helpers": "^6.5.0",
|
||||
"@vscode/vscode-languagedetection": "^1.0.21",
|
||||
"ace-builds": "^1.4.13",
|
||||
"better-sqlite3": "^7.4.4",
|
||||
"better-sqlite3": "^7.5.0",
|
||||
"electron-log": "^4.4.1",
|
||||
"electron-store": "^8.0.1",
|
||||
"electron-updater": "^4.6.1",
|
||||
@@ -120,6 +120,7 @@
|
||||
"moment": "^2.29.1",
|
||||
"mysql2": "^2.3.2",
|
||||
"pg": "^8.7.1",
|
||||
"pg-query-stream": "^4.2.3",
|
||||
"pgsql-ast-parser": "^7.2.1",
|
||||
"source-map-support": "^0.5.20",
|
||||
"spectre.css": "^0.5.9",
|
||||
@@ -136,10 +137,9 @@
|
||||
"all-contributors-cli": "^6.20.0",
|
||||
"babel-loader": "^8.2.3",
|
||||
"chalk": "^4.1.2",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"css-loader": "^6.5.0",
|
||||
"electron": "^16.0.8",
|
||||
"electron": "^17.0.1",
|
||||
"electron-builder": "^22.14.11",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"eslint": "^7.32.0",
|
||||
@@ -154,6 +154,7 @@
|
||||
"node-loader": "^2.0.0",
|
||||
"playwright": "^1.18.1",
|
||||
"progress-webpack-plugin": "^1.0.12",
|
||||
"rimraf": "^3.0.2",
|
||||
"sass": "^1.42.1",
|
||||
"sass-loader": "^12.3.0",
|
||||
"standard-version": "^9.3.1",
|
||||
|
@@ -12,7 +12,7 @@ const { spawn } = require('child_process');
|
||||
|
||||
const mainConfig = require('../webpack.main.config');
|
||||
const rendererConfig = require('../webpack.renderer.config');
|
||||
// const workersConfig = require('../webpack.workers.config');
|
||||
const workersConfig = require('../webpack.workers.config');
|
||||
|
||||
let electronProcess = null;
|
||||
let manualRestart = null;
|
||||
@@ -64,7 +64,7 @@ async function restartElectron () {
|
||||
}
|
||||
|
||||
function startMain () {
|
||||
const webpackSetup = webpack(mainConfig);
|
||||
const webpackSetup = webpack([mainConfig, workersConfig]);
|
||||
|
||||
webpackSetup.compilers.forEach((compiler) => {
|
||||
const { name } = compiler;
|
||||
|
@@ -134,7 +134,7 @@ export default class {
|
||||
{ name: 'phoneNumberFormat', 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: 'arrayElement', group: 'random', types: ['string'] },
|
||||
{ name: 'arrayElements', group: 'random', types: ['string'] },
|
||||
|
@@ -38,6 +38,9 @@ module.exports = {
|
||||
databaseEdit: false,
|
||||
schemaEdit: false,
|
||||
schemaDrop: false,
|
||||
schemaExport: false,
|
||||
exportByChunks: false,
|
||||
schemaImport: false,
|
||||
tableSettings: false,
|
||||
tableOptions: false,
|
||||
tableArray: false,
|
||||
|
@@ -34,6 +34,9 @@ module.exports = {
|
||||
schedulerAdd: true,
|
||||
schemaEdit: true,
|
||||
schemaDrop: true,
|
||||
schemaExport: true,
|
||||
exportByChunks: true,
|
||||
schemaImport: true,
|
||||
tableSettings: true,
|
||||
viewSettings: true,
|
||||
triggerSettings: true,
|
||||
|
@@ -31,6 +31,8 @@ module.exports = {
|
||||
routineAdd: true,
|
||||
functionAdd: true,
|
||||
schemaDrop: true,
|
||||
schemaExport: true,
|
||||
schemaImport: true,
|
||||
databaseEdit: false,
|
||||
tableSettings: true,
|
||||
viewSettings: true,
|
||||
|
@@ -121,7 +121,7 @@ module.exports = [
|
||||
{
|
||||
name: 'JSON',
|
||||
length: false,
|
||||
collation: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
}
|
||||
|
@@ -41,6 +41,7 @@ export const NUMBER = [
|
||||
|
||||
export const FLOAT = [
|
||||
'FLOAT',
|
||||
'DECIMAL',
|
||||
'DOUBLE',
|
||||
'REAL',
|
||||
'DOUBLE PRECISION',
|
||||
|
@@ -33,7 +33,7 @@ const lookup = {
|
||||
*/
|
||||
export default function hexToBinary (hex) {
|
||||
let binary = '';
|
||||
for (let i = 0, len = hex.length; i < len; i++)
|
||||
for (let i = 0; i < hex.length; i++)
|
||||
binary += lookup[hex[i]];
|
||||
|
||||
return binary;
|
||||
|
@@ -23,7 +23,7 @@ export function mimeFromHex (hex) {
|
||||
case '425A68':
|
||||
return { ext: 'bz2', mime: 'application/x-bzip2' };
|
||||
default:
|
||||
switch (hex) { // 4 bites
|
||||
switch (hex) { // 4 bytes
|
||||
case '89504E47':
|
||||
return { ext: 'png', mime: 'image/png' };
|
||||
case '47494638':
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { app, ipcMain } from 'electron';
|
||||
import { app, ipcMain, dialog } from 'electron';
|
||||
|
||||
export default () => {
|
||||
ipcMain.on('close-app', () => {
|
||||
@@ -9,4 +9,12 @@ export default () => {
|
||||
const key = false;
|
||||
event.returnValue = key;
|
||||
});
|
||||
|
||||
ipcMain.handle('showOpenDialog', (event, options) => {
|
||||
return dialog.showOpenDialog(options);
|
||||
});
|
||||
|
||||
ipcMain.handle('get-download-dir-path', () => {
|
||||
return app.getPath('downloads');
|
||||
});
|
||||
};
|
||||
|
@@ -24,7 +24,8 @@ export default connections => {
|
||||
key: conn.key ? fs.readFileSync(conn.key) : null,
|
||||
cert: conn.cert ? fs.readFileSync(conn.cert) : null,
|
||||
ca: conn.ca ? fs.readFileSync(conn.ca) : null,
|
||||
ciphers: conn.ciphers
|
||||
ciphers: conn.ciphers,
|
||||
rejectUnauthorized: !conn.untrustedConnection
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,7 +41,7 @@ export default connections => {
|
||||
}
|
||||
|
||||
try {
|
||||
const connection = await ClientsFactory.getConnection({
|
||||
const connection = await ClientsFactory.getClient({
|
||||
client: conn.client,
|
||||
params
|
||||
});
|
||||
@@ -84,7 +85,8 @@ export default connections => {
|
||||
key: conn.key ? fs.readFileSync(conn.key) : null,
|
||||
cert: conn.cert ? fs.readFileSync(conn.cert) : null,
|
||||
ca: conn.ca ? fs.readFileSync(conn.ca) : null,
|
||||
ciphers: conn.ciphers
|
||||
ciphers: conn.ciphers,
|
||||
rejectUnauthorized: !conn.untrustedConnection
|
||||
};
|
||||
}
|
||||
|
||||
@@ -100,7 +102,7 @@ export default connections => {
|
||||
}
|
||||
|
||||
try {
|
||||
const connection = ClientsFactory.getConnection({
|
||||
const connection = ClientsFactory.getClient({
|
||||
client: conn.client,
|
||||
params,
|
||||
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 => {
|
||||
let exporter = null;
|
||||
let importer = null;
|
||||
|
||||
ipcMain.handle('create-schema', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].createSchema(params);
|
||||
@@ -37,9 +45,16 @@ export default connections => {
|
||||
|
||||
ipcMain.handle('get-schema-collation', async (event, params) => {
|
||||
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) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
@@ -48,7 +63,9 @@ export default connections => {
|
||||
|
||||
ipcMain.handle('get-structure', async (event, params) => {
|
||||
try {
|
||||
const structure = await connections[params.uid].getStructure(params.schemas);
|
||||
const structure = await connections[params.uid].getStructure(
|
||||
params.schemas
|
||||
);
|
||||
|
||||
return { status: 'success', response: structure };
|
||||
}
|
||||
@@ -135,7 +152,7 @@ export default connections => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('raw-query', async (event, { uid, query, schema, tabUid }) => {
|
||||
ipcMain.handle('raw-query', async (event, { uid, query, schema, tabUid, autocommit }) => {
|
||||
if (!query) return;
|
||||
|
||||
try {
|
||||
@@ -144,6 +161,7 @@ export default connections => {
|
||||
details: true,
|
||||
schema,
|
||||
tabUid,
|
||||
autocommit,
|
||||
comments: false
|
||||
});
|
||||
|
||||
@@ -154,6 +172,169 @@ export default connections => {
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
@@ -165,4 +346,40 @@ export default connections => {
|
||||
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() };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@@ -149,7 +149,7 @@ export default (connections) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ([...BIT].includes(params.type)) {
|
||||
else if (BIT.includes(params.type)) {
|
||||
escapedParam = `b'${sqlEscaper(params.content)}'`;
|
||||
reload = true;
|
||||
}
|
||||
|
@@ -1,4 +1,10 @@
|
||||
'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
|
||||
*
|
||||
@@ -17,7 +23,7 @@ export class AntaresCore {
|
||||
this._poolSize = args.poolSize || false;
|
||||
this._connection = null;
|
||||
this._ssh = null;
|
||||
this._logger = args.logger || console.log;
|
||||
this._logger = args.logger || queryLogger;
|
||||
|
||||
this._queryDefaults = {
|
||||
schema: '',
|
||||
|
@@ -2,13 +2,6 @@
|
||||
import { MySQLClient } from './clients/MySQLClient';
|
||||
import { PostgreSQLClient } from './clients/PostgreSQLClient';
|
||||
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 {
|
||||
/**
|
||||
* Returns a database connection based on received args.
|
||||
@@ -29,9 +22,7 @@ export class ClientsFactory {
|
||||
* @returns Database Connection
|
||||
* @memberof ClientsFactory
|
||||
*/
|
||||
static getConnection (args) {
|
||||
args.logger = queryLogger;
|
||||
|
||||
static getClient (args) {
|
||||
switch (args.client) {
|
||||
case 'mysql':
|
||||
case 'maria':
|
||||
|
@@ -10,6 +10,7 @@ export class MySQLClient extends AntaresCore {
|
||||
|
||||
this._schema = null;
|
||||
this._runningConnections = new Map();
|
||||
this._connectionsToCommit = new Map();
|
||||
|
||||
this.types = {
|
||||
0: 'DECIMAL',
|
||||
@@ -100,10 +101,32 @@ export class MySQLClient extends AntaresCore {
|
||||
.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
|
||||
*/
|
||||
async connect () {
|
||||
async getDbConfig () {
|
||||
delete this._params.application_name;
|
||||
|
||||
const dbConfig = {
|
||||
@@ -111,7 +134,9 @@ export class MySQLClient extends AntaresCore {
|
||||
port: this._params.port,
|
||||
user: this._params.user,
|
||||
password: this._params.password,
|
||||
ssl: null
|
||||
ssl: null,
|
||||
supportBigNumbers: true,
|
||||
bigNumberStrings: true
|
||||
};
|
||||
|
||||
if (this._params.schema?.length) dbConfig.database = this._params.schema;
|
||||
@@ -126,6 +151,8 @@ export class MySQLClient extends AntaresCore {
|
||||
remoteAddr: this._params.host,
|
||||
remotePort: this._params.port
|
||||
});
|
||||
|
||||
dbConfig.host = this._ssh.config.host;
|
||||
dbConfig.port = tunnel.localPort;
|
||||
}
|
||||
catch (err) {
|
||||
@@ -134,48 +161,17 @@ export class MySQLClient extends AntaresCore {
|
||||
}
|
||||
}
|
||||
|
||||
if (!this._poolSize) {
|
||||
this._connection = await mysql.createConnection(dbConfig);
|
||||
return dbConfig;
|
||||
}
|
||||
|
||||
// ANSI_QUOTES check
|
||||
const res = await this.getVariable('sql_mode', 'global');
|
||||
const sqlMode = res?.value.split(',');
|
||||
const hasAnsiQuotes = sqlMode.includes('ANSI_QUOTES');
|
||||
|
||||
if (this._params.readonly)
|
||||
await this.raw('SET SESSION TRANSACTION READ ONLY');
|
||||
|
||||
if (hasAnsiQuotes)
|
||||
await this.raw(`SET SESSION sql_mode = "${sqlMode.filter(m => m !== 'ANSI_QUOTES').join(',')}"`);
|
||||
}
|
||||
else {
|
||||
this._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 this.getVariable('sql_mode', 'global');
|
||||
const sqlMode = res?.value.split(',');
|
||||
const hasAnsiQuotes = sqlMode.includes('ANSI_QUOTES');
|
||||
|
||||
if (hasAnsiQuotes)
|
||||
await this._connection.query(`SET SESSION sql_mode = "${sqlMode.filter(m => m !== 'ANSI_QUOTES').join(',')}"`);
|
||||
|
||||
this._connection.on('connection', connection => {
|
||||
if (this._params.readonly)
|
||||
connection.query('SET SESSION TRANSACTION READ ONLY');
|
||||
|
||||
if (hasAnsiQuotes)
|
||||
connection.query(`SET SESSION sql_mode = "${sqlMode.filter(m => m !== 'ANSI_QUOTES').join(',')}"`);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async connect () {
|
||||
if (!this._poolSize)
|
||||
this._connection = await this.getConnection();
|
||||
else
|
||||
this._connection = await this.getConnectionPool();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,6 +182,64 @@ export class MySQLClient extends AntaresCore {
|
||||
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
|
||||
*
|
||||
@@ -254,7 +308,7 @@ export class MySQLClient extends AntaresCore {
|
||||
break;
|
||||
}
|
||||
|
||||
const tableSize = table.Data_length + table.Index_length;
|
||||
const tableSize = Number(table.Data_length) + Number(table.Index_length);
|
||||
schemaSize += tableSize;
|
||||
|
||||
return {
|
||||
@@ -395,7 +449,7 @@ export class MySQLClient extends AntaresCore {
|
||||
return acc;
|
||||
}, '')
|
||||
.replaceAll('\n', '')
|
||||
.split(',')
|
||||
.split(/,\s?(?![^(]*\))/)
|
||||
.map(f => {
|
||||
try {
|
||||
const fieldArr = f.trim().split(' ');
|
||||
@@ -440,6 +494,10 @@ export class MySQLClient extends AntaresCore {
|
||||
? field.COLUMN_TYPE.match(/\(([^)]+)\)/)[0].slice(1, -1)
|
||||
: null;
|
||||
|
||||
const defaultValue = (remappedFields && remappedFields[field.COLUMN_NAME])
|
||||
? remappedFields[field.COLUMN_NAME].default
|
||||
: field.COLUMN_DEFAULT;
|
||||
|
||||
return {
|
||||
name: field.COLUMN_NAME,
|
||||
key: field.COLUMN_KEY.toLowerCase(),
|
||||
@@ -449,7 +507,7 @@ export class MySQLClient extends AntaresCore {
|
||||
schema: field.TABLE_SCHEMA,
|
||||
table: field.TABLE_NAME,
|
||||
numPrecision: field.NUMERIC_PRECISION,
|
||||
numScale: field.NUMERIC_SCALE,
|
||||
numScale: Number(field.NUMERIC_SCALE),
|
||||
numLength,
|
||||
enumValues,
|
||||
datePrecision: field.DATETIME_PRECISION,
|
||||
@@ -458,10 +516,11 @@ export class MySQLClient extends AntaresCore {
|
||||
unsigned: field.COLUMN_TYPE.includes('unsigned'),
|
||||
zerofill: field.COLUMN_TYPE.includes('zerofill'),
|
||||
order: field.ORDINAL_POSITION,
|
||||
default: (remappedFields && remappedFields[field.COLUMN_NAME]) ? remappedFields[field.COLUMN_NAME].default : field.COLUMN_DEFAULT,
|
||||
default: defaultValue,
|
||||
charset: field.CHARACTER_SET_NAME,
|
||||
collation: field.COLLATION_NAME,
|
||||
autoIncrement: field.EXTRA.includes('auto_increment'),
|
||||
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()
|
||||
: '',
|
||||
@@ -1272,6 +1331,36 @@ export class MySQLClient extends AntaresCore {
|
||||
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
|
||||
*
|
||||
@@ -1536,7 +1625,7 @@ export class MySQLClient extends AntaresCore {
|
||||
let insertRaw = '';
|
||||
|
||||
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(', ')})`);
|
||||
|
||||
insertRaw = `(${fieldsList.join(', ')}) VALUES ${rowsList.join(', ')} `;
|
||||
@@ -1576,6 +1665,7 @@ export class MySQLClient extends AntaresCore {
|
||||
details: false,
|
||||
split: true,
|
||||
comments: true,
|
||||
autocommit: true,
|
||||
...args
|
||||
};
|
||||
|
||||
@@ -1590,8 +1680,21 @@ export class MySQLClient extends AntaresCore {
|
||||
.filter(Boolean)
|
||||
.map(q => q.trim())
|
||||
: [sql];
|
||||
|
||||
let connection;
|
||||
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);
|
||||
@@ -1656,7 +1759,7 @@ export class MySQLClient extends AntaresCore {
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
if (isPool) {
|
||||
if (isPool && args.autocommit) {
|
||||
connection.release();
|
||||
this._runningConnections.delete(args.tabUid);
|
||||
}
|
||||
@@ -1668,7 +1771,7 @@ export class MySQLClient extends AntaresCore {
|
||||
keysArr = keysArr ? [...keysArr, ...response] : response;
|
||||
}
|
||||
catch (err) {
|
||||
if (isPool) {
|
||||
if (isPool && args.autocommit) {
|
||||
connection.release();
|
||||
this._runningConnections.delete(args.tabUid);
|
||||
}
|
||||
@@ -1686,7 +1789,7 @@ export class MySQLClient extends AntaresCore {
|
||||
keys: keysArr
|
||||
});
|
||||
}).catch((err) => {
|
||||
if (isPool) {
|
||||
if (isPool && args.autocommit) {
|
||||
connection.release();
|
||||
this._runningConnections.delete(args.tabUid);
|
||||
}
|
||||
@@ -1697,7 +1800,7 @@ export class MySQLClient extends AntaresCore {
|
||||
resultsArr.push({ rows, report, fields, keys, duration });
|
||||
}
|
||||
|
||||
if (isPool) {
|
||||
if (isPool && args.autocommit) {
|
||||
connection.release();
|
||||
this._runningConnections.delete(args.tabUid);
|
||||
}
|
||||
|
@@ -9,7 +9,6 @@ function pgToString (value) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
types.setTypeParser(20, a => parseInt(a));// bigint string to number
|
||||
types.setTypeParser(1082, pgToString); // date
|
||||
types.setTypeParser(1083, pgToString); // time
|
||||
types.setTypeParser(1114, pgToString); // timestamp
|
||||
@@ -22,6 +21,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
|
||||
this._schema = null;
|
||||
this._runningConnections = new Map();
|
||||
this._connectionsToCommit = new Map();
|
||||
|
||||
this.types = {};
|
||||
for (const key in types.builtins)
|
||||
@@ -71,9 +71,11 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns dbConfig
|
||||
* @memberof PostgreSQLClient
|
||||
*/
|
||||
async connect () {
|
||||
async getDbConfig () {
|
||||
const dbConfig = {
|
||||
host: this._params.host,
|
||||
port: this._params.port,
|
||||
@@ -94,6 +96,8 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
remoteAddr: this._params.host,
|
||||
remotePort: this._params.port
|
||||
});
|
||||
|
||||
dbConfig.host = this._ssh.config.host;
|
||||
dbConfig.port = tunnel.localPort;
|
||||
}
|
||||
catch (err) {
|
||||
@@ -102,24 +106,43 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
}
|
||||
}
|
||||
|
||||
if (!this._poolSize) {
|
||||
const client = new Client(dbConfig);
|
||||
await client.connect();
|
||||
this._connection = client;
|
||||
return dbConfig;
|
||||
}
|
||||
|
||||
if (this._params.readonly)
|
||||
await this.raw('SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY');
|
||||
}
|
||||
else {
|
||||
const pool = new Pool({ ...dbConfig, max: this._poolSize });
|
||||
this._connection = pool;
|
||||
/**
|
||||
* @memberof PostgreSQLClient
|
||||
*/
|
||||
async connect () {
|
||||
if (!this._poolSize)
|
||||
this._connection = await this.getConnection();
|
||||
else
|
||||
this._connection = await this.getConnectionPool();
|
||||
}
|
||||
|
||||
if (this._params.readonly) {
|
||||
this._connection.on('connect', connection => {
|
||||
connection.query('SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY');
|
||||
});
|
||||
}
|
||||
async getConnection () {
|
||||
const dbConfig = await this.getDbConfig();
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,7 +240,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
if (schemas.has(db.database)) {
|
||||
// TABLES
|
||||
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;
|
||||
|
||||
return {
|
||||
@@ -549,7 +572,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async dropSchema (params) {
|
||||
return await this.raw(`DROP SCHEMA "${params.database}"`);
|
||||
return await this.raw(`DROP SCHEMA "${params.database}" CASCADE`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1118,6 +1141,40 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
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
|
||||
*
|
||||
@@ -1429,6 +1486,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
details: false,
|
||||
split: true,
|
||||
comments: true,
|
||||
autocommit: true,
|
||||
...args
|
||||
};
|
||||
|
||||
@@ -1443,8 +1501,20 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
.map(q => q.trim())
|
||||
: [sql];
|
||||
|
||||
let connection;
|
||||
const isPool = this._connection instanceof Pool;
|
||||
const connection = isPool ? await this._connection.connect() : 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('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);
|
||||
@@ -1554,7 +1624,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
if (isPool) {
|
||||
if (isPool && args.autocommit) {
|
||||
connection.release();
|
||||
this._runningConnections.delete(args.tabUid);
|
||||
}
|
||||
@@ -1566,7 +1636,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
keysArr = keysArr ? [...keysArr, ...response] : response;
|
||||
}
|
||||
catch (err) {
|
||||
if (isPool) {
|
||||
if (isPool && args.autocommit) {
|
||||
connection.release();
|
||||
this._runningConnections.delete(args.tabUid);
|
||||
}
|
||||
@@ -1585,7 +1655,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
if (isPool) {
|
||||
if (isPool && args.autocommit) {
|
||||
connection.release();
|
||||
this._runningConnections.delete(args.tabUid);
|
||||
}
|
||||
@@ -1597,7 +1667,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
resultsArr.push({ rows, report, fields, keys, duration });
|
||||
}
|
||||
|
||||
if (isPool) {
|
||||
if (isPool && args.autocommit) {
|
||||
connection.release();
|
||||
this._runningConnections.delete(args.tabUid);
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ export class SQLiteClient extends AntaresCore {
|
||||
super(args);
|
||||
|
||||
this._schema = null;
|
||||
this._connectionsToCommit = new Map();
|
||||
}
|
||||
|
||||
_getTypeInfo (type) {
|
||||
@@ -21,7 +22,11 @@ export class SQLiteClient extends AntaresCore {
|
||||
* @memberof SQLiteClient
|
||||
*/
|
||||
async connect () {
|
||||
this._connection = sqlite(this._params.databasePath, {
|
||||
this._connection = this.getConnection();
|
||||
}
|
||||
|
||||
getConnection () {
|
||||
return sqlite(this._params.databasePath, {
|
||||
fileMustExist: true,
|
||||
readonly: this._params.readonly
|
||||
});
|
||||
@@ -158,7 +163,7 @@ export class SQLiteClient extends AntaresCore {
|
||||
nullable: !field.notnull,
|
||||
unsigned: null,
|
||||
zerofill: null,
|
||||
order: field.cid + 1,
|
||||
order: typeof field.cid === 'string' ? +field.cid + 1 : field.cid + 1,
|
||||
default: field.dflt_value,
|
||||
charset: null,
|
||||
collation: null,
|
||||
@@ -446,6 +451,40 @@ export class SQLiteClient extends AntaresCore {
|
||||
|
||||
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
|
||||
*
|
||||
@@ -666,6 +705,7 @@ export class SQLiteClient extends AntaresCore {
|
||||
details: false,
|
||||
split: true,
|
||||
comments: true,
|
||||
autocommit: true,
|
||||
...args
|
||||
};
|
||||
|
||||
@@ -679,7 +719,20 @@ export class SQLiteClient extends AntaresCore {
|
||||
.filter(Boolean)
|
||||
.map(q => q.trim())
|
||||
: [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) {
|
||||
if (!query) continue;
|
||||
|
85
src/main/libs/exporters/BaseExporter.js
Normal file
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');
|
||||
}
|
||||
}
|
418
src/main/libs/exporters/sql/MysqlExporter.js
Normal file
418
src/main/libs/exporters/sql/MysqlExporter.js
Normal file
@@ -0,0 +1,418 @@
|
||||
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 {
|
||||
constructor (...args) {
|
||||
super(...args);
|
||||
|
||||
this._commentChar = '#';
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
495
src/main/libs/exporters/sql/PostgreSQLExporter.js
Normal file
495
src/main/libs/exporters/sql/PostgreSQLExporter.js
Normal file
@@ -0,0 +1,495 @@
|
||||
import { SqlExporter } from './SqlExporter';
|
||||
import { BLOB, BIT, DATE, DATETIME, FLOAT, NUMBER, TEXT_SEARCH } 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';
|
||||
import QueryStream from 'pg-query-stream';
|
||||
|
||||
export default class PostgreSQLExporter extends SqlExporter {
|
||||
async getSqlHeader () {
|
||||
let dump = await super.getSqlHeader();
|
||||
dump += `
|
||||
|
||||
|
||||
SET statement_timeout = 0;
|
||||
SET lock_timeout = 0;
|
||||
SET idle_in_transaction_session_timeout = 0;
|
||||
SET client_encoding = 'UTF8';
|
||||
SET standard_conforming_strings = on;
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
SET check_function_bodies = false;
|
||||
SET xmloption = content;
|
||||
SET client_min_messages = warning;
|
||||
SET row_security = off;\n\n\n`;
|
||||
|
||||
if (this.schemaName !== 'public') dump += `CREATE SCHEMA "${this.schemaName}";\n\n`;
|
||||
|
||||
dump += await this.getCreateTypes();
|
||||
|
||||
return dump;
|
||||
}
|
||||
|
||||
async getCreateTable (tableName) {
|
||||
let createSql = '';
|
||||
const sequences = [];
|
||||
const columnsSql = [];
|
||||
const arrayTypes = {
|
||||
_int2: 'smallint',
|
||||
_int4: 'integer',
|
||||
_int8: 'bigint',
|
||||
_float4: 'real',
|
||||
_float8: 'double precision',
|
||||
_char: '"char"',
|
||||
_varchar: 'character varying'
|
||||
};
|
||||
|
||||
// Table columns
|
||||
const { rows } = await this._client.raw(`
|
||||
SELECT *
|
||||
FROM "information_schema"."columns"
|
||||
WHERE "table_schema" = '${this.schemaName}'
|
||||
AND "table_name" = '${tableName}'
|
||||
ORDER BY "ordinal_position" ASC
|
||||
`, { schema: 'information_schema' });
|
||||
|
||||
if (!rows.length) return '';
|
||||
|
||||
for (const column of rows) {
|
||||
let fieldType = column.data_type;
|
||||
if (fieldType === 'USER-DEFINED') fieldType = `"${this.schemaName}".${column.udt_name}`;
|
||||
else if (fieldType === 'ARRAY') {
|
||||
if (Object.keys(arrayTypes).includes(fieldType))
|
||||
fieldType = arrayTypes[type] + '[]';
|
||||
else
|
||||
fieldType = column.udt_name.replaceAll('_', '') + '[]';
|
||||
}
|
||||
|
||||
const columnArr = [
|
||||
`"${column.column_name}"`,
|
||||
`${fieldType}${column.character_maximum_length ? `(${column.character_maximum_length})` : ''}`
|
||||
];
|
||||
|
||||
if (column.column_default) {
|
||||
columnArr.push(`DEFAULT ${column.column_default}`);
|
||||
if (column.column_default.includes('nextval')) {
|
||||
const sequenceName = column.column_default.split('\'')[1];
|
||||
sequences.push(sequenceName);
|
||||
}
|
||||
}
|
||||
if (column.is_nullable === 'NO') columnArr.push('NOT NULL');
|
||||
|
||||
columnsSql.push(columnArr.join(' '));
|
||||
}
|
||||
|
||||
// Table sequences
|
||||
for (let sequence of sequences) {
|
||||
if (sequence.includes('.')) sequence = sequence.split('.')[1];
|
||||
|
||||
const { rows } = await this._client
|
||||
.select('*')
|
||||
.schema('information_schema')
|
||||
.from('sequences')
|
||||
.where({ sequence_schema: `= '${this.schemaName}'`, sequence_name: `= '${sequence}'` })
|
||||
.run();
|
||||
|
||||
if (rows.length) {
|
||||
createSql += `CREATE SEQUENCE "${this.schemaName}"."${sequence}"
|
||||
START WITH ${rows[0].start_value}
|
||||
INCREMENT BY ${rows[0].increment}
|
||||
MINVALUE ${rows[0].minimum_value}
|
||||
MAXVALUE ${rows[0].maximum_value}
|
||||
CACHE 1;\n`;
|
||||
|
||||
// createSql += `\nALTER TABLE "${sequence}" OWNER TO ${this._client._params.user};\n\n`;
|
||||
}
|
||||
}
|
||||
|
||||
// Table create
|
||||
createSql += `\nCREATE TABLE "${this.schemaName}"."${tableName}"(
|
||||
${columnsSql.join(',\n ')}
|
||||
);\n`;
|
||||
|
||||
// createSql += `\nALTER TABLE "${tableName}" OWNER TO ${this._client._params.user};\n\n`;
|
||||
|
||||
// Table indexes
|
||||
createSql += '\n';
|
||||
const { rows: indexes } = await this._client
|
||||
.select('*')
|
||||
.schema('pg_catalog')
|
||||
.from('pg_indexes')
|
||||
.where({ schemaname: `= '${this.schemaName}'`, tablename: `= '${tableName}'` })
|
||||
.run();
|
||||
|
||||
for (const index of indexes)
|
||||
createSql += `${index.indexdef};\n`;
|
||||
|
||||
// Table foreigns
|
||||
const { rows: foreigns } = await this._client.raw(`
|
||||
SELECT
|
||||
tc.table_schema,
|
||||
tc.constraint_name,
|
||||
tc.table_name,
|
||||
kcu.column_name,
|
||||
ccu.table_schema AS foreign_table_schema,
|
||||
ccu.table_name AS foreign_table_name,
|
||||
ccu.column_name AS foreign_column_name,
|
||||
rc.update_rule,
|
||||
rc.delete_rule
|
||||
FROM information_schema.table_constraints AS tc
|
||||
JOIN information_schema.key_column_usage AS kcu
|
||||
ON tc.constraint_name = kcu.constraint_name
|
||||
AND tc.table_schema = kcu.table_schema
|
||||
JOIN information_schema.constraint_column_usage AS ccu
|
||||
ON ccu.constraint_name = tc.constraint_name
|
||||
AND ccu.table_schema = tc.table_schema
|
||||
JOIN information_schema.referential_constraints AS rc
|
||||
ON rc.constraint_name = kcu.constraint_name
|
||||
WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_schema = '${this.schemaName}'
|
||||
AND tc.table_name = '${tableName}'
|
||||
`);
|
||||
|
||||
for (const foreign of foreigns) {
|
||||
this._postTablesSql += `\nALTER TABLE ONLY "${this.schemaName}"."${tableName}"
|
||||
ADD CONSTRAINT "${foreign.constraint_name}" FOREIGN KEY ("${foreign.column_name}") REFERENCES "${this.schemaName}"."${foreign.foreign_table_name}" ("${foreign.foreign_column_name}") ON UPDATE ${foreign.update_rule} ON DELETE ${foreign.delete_rule};\n`;
|
||||
}
|
||||
|
||||
return createSql;
|
||||
}
|
||||
|
||||
getDropTable (tableName) {
|
||||
return `DROP TABLE IF EXISTS "${this.schemaName}"."${tableName}";`;
|
||||
}
|
||||
|
||||
async * getTableInsert (tableName) {
|
||||
let rowCount = 0;
|
||||
const 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) {
|
||||
const columns = await this._client.getTableColumns({
|
||||
table: tableName,
|
||||
schema: this.schemaName
|
||||
});
|
||||
|
||||
const columnNames = columns.map(col => '"' + col.name + '"').join(', ');
|
||||
|
||||
yield sqlStr;
|
||||
|
||||
const stream = await this._queryStream(
|
||||
`SELECT ${columnNames} FROM "${this.schemaName}"."${tableName}"`
|
||||
);
|
||||
|
||||
for await (const row of stream) {
|
||||
if (this.isCancelled) {
|
||||
stream.destroy();
|
||||
yield null;
|
||||
return;
|
||||
}
|
||||
|
||||
let sqlInsertString = `INSERT INTO "${this.schemaName}"."${tableName}" (${columnNames}) VALUES`;
|
||||
|
||||
sqlInsertString += ' (';
|
||||
|
||||
for (const i in columns) {
|
||||
const column = columns[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 (column.isArray) {
|
||||
let parsedVal;
|
||||
if (Array.isArray(val))
|
||||
parsedVal = JSON.stringify(val).replaceAll('[', '{').replaceAll(']', '}');
|
||||
else
|
||||
parsedVal = typeof val === 'string' ? val.replaceAll('[', '{').replaceAll(']', '}') : '';
|
||||
sqlInsertString += `'${parsedVal}'`;
|
||||
}
|
||||
else if (TEXT_SEARCH.includes(column.type))
|
||||
sqlInsertString += `'${val.replaceAll('\'', '\'\'')}'`;
|
||||
else if (BIT.includes(column.type))
|
||||
sqlInsertString += `b'${hexToBinary(Buffer.from(val).toString('hex'))}'`;
|
||||
else if (BLOB.includes(column.type))
|
||||
sqlInsertString += `decode('${val.toString('hex').toUpperCase()}', 'hex')`;
|
||||
else if (NUMBER.includes(column.type))
|
||||
sqlInsertString += val;
|
||||
else if (FLOAT.includes(column.type))
|
||||
sqlInsertString += parseFloat(val);
|
||||
else if (val === '') sqlInsertString += '\'\'';
|
||||
else {
|
||||
sqlInsertString += typeof val === 'string'
|
||||
? this.escapeAndQuote(val)
|
||||
: typeof val === 'object'
|
||||
? this.escapeAndQuote(JSON.stringify(val))
|
||||
: val;
|
||||
}
|
||||
|
||||
if (parseInt(i) !== columns.length - 1)
|
||||
sqlInsertString += ', ';
|
||||
}
|
||||
|
||||
sqlInsertString += ');\n';
|
||||
|
||||
yield sqlInsertString;
|
||||
}
|
||||
|
||||
yield sqlStr;
|
||||
}
|
||||
}
|
||||
|
||||
async getCreateTypes () {
|
||||
let sqlString = '';
|
||||
const { rows: types } = await this._client.raw(`
|
||||
SELECT pg_type.typname, pg_enum.enumlabel
|
||||
FROM pg_type
|
||||
JOIN pg_enum ON pg_enum.enumtypid = pg_type.oid;
|
||||
`);
|
||||
|
||||
if (types.length) { // TODO: refactor
|
||||
sqlString += this.buildComment('Dump of types\n------------------------------------------------------------') + '\n\n';
|
||||
|
||||
const typesArr = types.reduce((arr, type) => {
|
||||
if (arr.every(el => el.name !== type.typname))
|
||||
arr.push({ name: type.typname, enums: [this.escapeAndQuote(type.enumlabel)] });
|
||||
else {
|
||||
const i = arr.findIndex(el => el.name === type.typname);
|
||||
arr[i].enums.push(this.escapeAndQuote(type.enumlabel));
|
||||
}
|
||||
|
||||
return arr;
|
||||
}, []);
|
||||
|
||||
for (const type of typesArr) {
|
||||
sqlString += `CREATE TYPE "${this.schemaName}"."${type.name}" AS ENUM (
|
||||
${type.enums.join(',\n\t')}
|
||||
);`;
|
||||
}
|
||||
|
||||
// sqlString += `\nALTER TYPE "${tableName}" OWNER TO ${this._client._params.user};\n`
|
||||
}
|
||||
|
||||
return sqlString;
|
||||
}
|
||||
|
||||
async getCreateAggregates () {
|
||||
let sqlString = '';
|
||||
|
||||
const { rows: aggregates } = await this._client.raw(`
|
||||
SELECT proname
|
||||
FROM pg_proc
|
||||
WHERE prokind = 'a'
|
||||
AND pronamespace::regnamespace::text = '${this.schemaName}'
|
||||
ORDER BY 1;
|
||||
`);
|
||||
|
||||
if (aggregates.length) {
|
||||
for (const aggregate of aggregates) {
|
||||
const { rows: aggregateDef } = await this._client.raw(
|
||||
`SELECT
|
||||
format(
|
||||
E'CREATE AGGREGATE %s (\n%s\n);'
|
||||
, (pg_identify_object('pg_proc'::regclass, aggfnoid, 0)).identity
|
||||
, array_to_string(
|
||||
ARRAY[
|
||||
format(E'\tSFUNC = %s', aggtransfn::regproc)
|
||||
, format(E'\tSTYPE = %s', format_type(aggtranstype, NULL))
|
||||
, CASE aggfinalfn WHEN '-'::regproc THEN NULL ELSE format(E'\tFINALFUNC = %s',aggfinalfn::text) END
|
||||
, CASE aggsortop WHEN 0 THEN NULL ELSE format(E'\tSORTOP = %s', oprname) END
|
||||
, CASE WHEN agginitval IS NULL THEN NULL ELSE format(E'\tINITCOND = %s', agginitval) END
|
||||
]
|
||||
, E',\n'
|
||||
)
|
||||
)
|
||||
FROM pg_aggregate
|
||||
LEFT JOIN pg_operator ON pg_operator.oid = aggsortop
|
||||
WHERE aggfnoid = '${this.schemaName}.${aggregate.proname}'::regproc;`
|
||||
);
|
||||
|
||||
if (aggregateDef.length)
|
||||
sqlString += '\n\n' + aggregateDef[0].format;
|
||||
}
|
||||
}
|
||||
|
||||
return sqlString + '\n\n\n';
|
||||
}
|
||||
|
||||
async getViews () {
|
||||
const { rows: views } = await this._client.raw(`SELECT * FROM "pg_views" WHERE "schemaname"='${this.schemaName}'`);
|
||||
let sqlString = '';
|
||||
|
||||
for (const view of views) {
|
||||
sqlString += `\nDROP VIEW IF EXISTS "${view.viewname}";\n`;
|
||||
|
||||
// const { rows: columns } = await this._client
|
||||
// .select('*')
|
||||
// .schema('information_schema')
|
||||
// .from('columns')
|
||||
// .where({ table_schema: `= '${this.schemaName}'`, table_name: `= '${view.viewname}'` })
|
||||
// .orderBy({ ordinal_position: 'ASC' })
|
||||
// .run();
|
||||
|
||||
// sqlString += `
|
||||
// CREATE VIEW "${this.schemaName}"."${view.viewname}" AS
|
||||
// SELECT
|
||||
// ${columns.reduce((acc, curr) => {
|
||||
// const fieldType = curr.data_type === 'USER-DEFINED' ? curr.udt_name : curr.data_type;
|
||||
// acc.push(`NULL::${fieldType}${curr.character_maximum_length ? `(${curr.character_maximum_length})` : ''} AS "${curr.column_name}"`);
|
||||
// return acc;
|
||||
// }, []).join(',\n ')};
|
||||
// `;
|
||||
|
||||
sqlString += `\nCREATE OR REPLACE VIEW "${this.schemaName}"."${view.viewname}" AS \n${view.definition}\n`;
|
||||
}
|
||||
|
||||
return sqlString;
|
||||
}
|
||||
|
||||
async getTriggers () {
|
||||
let sqlString = '';
|
||||
|
||||
// Trigger functions
|
||||
const { rows: triggerFunctions } = await this._client.raw(
|
||||
`SELECT DISTINCT routine_name AS name FROM information_schema.routines WHERE routine_type = 'FUNCTION' AND routine_schema = '${this.schemaName}' AND data_type = 'trigger'`
|
||||
);
|
||||
|
||||
for (const func of triggerFunctions) {
|
||||
const { rows: functionDef } = await this._client.raw(
|
||||
`SELECT pg_get_functiondef((SELECT oid FROM pg_proc WHERE proname = '${func.name}')) AS definition`
|
||||
);
|
||||
sqlString += `\n${functionDef[0].definition};\n`;
|
||||
}
|
||||
|
||||
const { rows: triggers } = await this._client.raw(
|
||||
`SELECT * FROM "information_schema"."triggers" WHERE "trigger_schema"='${this.schemaName}'`
|
||||
);
|
||||
|
||||
const remappedTriggers = triggers.reduce((acc, trigger) => {
|
||||
const i = acc.findIndex(t => t.trigger_name === trigger.trigger_name && t.event_object_table === trigger.event_object_table);
|
||||
if (i === -1) {
|
||||
trigger.events = [trigger.event_manipulation];
|
||||
acc.push(trigger);
|
||||
}
|
||||
else
|
||||
acc[i].events.push(trigger.event_manipulation);
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
for (const trigger of remappedTriggers)
|
||||
sqlString += `\nCREATE TRIGGER "${trigger.trigger_name}" ${trigger.action_timing} ${trigger.events.join(' OR ')} ON "${this.schemaName}"."${trigger.event_object_table}" FOR EACH ${trigger.action_orientation} ${trigger.action_statement};\n`;
|
||||
|
||||
return sqlString;
|
||||
}
|
||||
|
||||
async getFunctions () {
|
||||
let sqlString = '';
|
||||
const { rows: functions } = await this._client.raw(
|
||||
`SELECT DISTINCT routine_name AS name FROM information_schema.routines WHERE routine_type = 'FUNCTION' AND routine_schema = '${this.schemaName}' AND data_type != 'trigger'`
|
||||
);
|
||||
|
||||
for (const func of functions) {
|
||||
const { rows: functionDef } = await this._client.raw(
|
||||
`SELECT pg_get_functiondef((SELECT oid FROM pg_proc WHERE proname = '${func.name}')) AS definition`
|
||||
);
|
||||
sqlString += `\n${functionDef[0].definition};\n`;
|
||||
}
|
||||
|
||||
sqlString += await this.getCreateAggregates();
|
||||
|
||||
return sqlString;
|
||||
}
|
||||
|
||||
async getRoutines () {
|
||||
let sqlString = '';
|
||||
const { rows: functions } = await this._client.raw(
|
||||
`SELECT DISTINCT routine_name AS name FROM information_schema.routines WHERE routine_type = 'PROCEDURE' AND routine_schema = '${this.schemaName}'`
|
||||
);
|
||||
|
||||
for (const func of functions) {
|
||||
const { rows: functionDef } = await this._client.raw(
|
||||
`SELECT pg_get_functiondef((SELECT oid FROM pg_proc WHERE proname = '${func.name}')) AS definition`
|
||||
);
|
||||
sqlString += `\n${functionDef[0].definition};\n`;
|
||||
}
|
||||
|
||||
return sqlString;
|
||||
}
|
||||
|
||||
async _queryStream (sql) {
|
||||
if (process.env.NODE_ENV === 'development') console.log('EXPORTER:', sql);
|
||||
const connection = await this._client.getConnection();
|
||||
const query = new QueryStream(sql, null);
|
||||
const stream = connection.query(query);
|
||||
const dispose = () => connection.end();
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
169
src/main/libs/exporters/sql/SqlExporter.js
Normal file
169
src/main/libs/exporters/sql/SqlExporter.js
Normal file
@@ -0,0 +1,169 @@
|
||||
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 = '--';
|
||||
this._postTablesSql = '';
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
// SQL to execute after tables creation
|
||||
if (this._postTablesSql) {
|
||||
this.writeString(this._postTablesSql);
|
||||
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/antares-sql/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
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');
|
||||
}
|
||||
}
|
80
src/main/libs/importers/sql/MysqlImporter.js
Normal file
80
src/main/libs/importers/sql/MysqlImporter.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import fs from 'fs/promises';
|
||||
import MySQLParser from '../../parsers/MySQLParser';
|
||||
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 MySQLParser();
|
||||
let readPosition = 0;
|
||||
let queryCount = 0;
|
||||
|
||||
this.emitUpdate({
|
||||
fileSize: totalFileSize,
|
||||
readPosition: 0,
|
||||
percentage: 0,
|
||||
queryCount: 0
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
80
src/main/libs/importers/sql/PostgreSQLImporter.js
Normal file
80
src/main/libs/importers/sql/PostgreSQLImporter.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import fs from 'fs/promises';
|
||||
import PostgreSQLParser from '../../parsers/PostgreSQLParser';
|
||||
import { BaseImporter } from '../BaseImporter';
|
||||
|
||||
export default class PostgreSQLImporter 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 PostgreSQLParser();
|
||||
let readPosition = 0;
|
||||
let queryCount = 0;
|
||||
|
||||
this.emitUpdate({
|
||||
fileSize: totalFileSize,
|
||||
readPosition: 0,
|
||||
percentage: 0,
|
||||
queryCount: 0
|
||||
});
|
||||
|
||||
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.hint || error.toString(),
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
93
src/main/libs/parsers/MySQLParser.js
Normal file
93
src/main/libs/parsers/MySQLParser.js
Normal file
@@ -0,0 +1,93 @@
|
||||
import { Transform } from 'stream';
|
||||
|
||||
export default class MySQLParser 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;
|
||||
}
|
||||
}
|
142
src/main/libs/parsers/PostgreSQLParser.js
Normal file
142
src/main/libs/parsers/PostgreSQLParser.js
Normal file
@@ -0,0 +1,142 @@
|
||||
import { Transform } from 'stream';
|
||||
|
||||
export default class PostgreSQLParser extends Transform {
|
||||
constructor (opts) {
|
||||
opts = {
|
||||
delimiter: ';',
|
||||
encoding: 'utf8',
|
||||
writableObjectMode: true,
|
||||
readableObjectMode: true,
|
||||
...opts
|
||||
};
|
||||
super(opts);
|
||||
this._buffer = '';
|
||||
this._lastChar = '';
|
||||
this._lastChars = '';
|
||||
this.encoding = opts.encoding;
|
||||
this.delimiter = opts.delimiter;// ';'
|
||||
this._bodyWrapper = '';
|
||||
this._bodyWrapperBuffer = '';
|
||||
|
||||
this.isEscape = false;
|
||||
this.currentQuote = null;
|
||||
this._firstDollarFound = false;
|
||||
this._isBody = false;
|
||||
this._isSingleLineComment = false;
|
||||
this._isMultiLineComment = false;
|
||||
}
|
||||
|
||||
get _isComment () {
|
||||
return this._isSingleLineComment || this._isMultiLineComment;
|
||||
}
|
||||
|
||||
_transform (chunk, encoding, next) {
|
||||
for (const char of chunk.toString(this.encoding)) {
|
||||
this.checkEscape();
|
||||
this._buffer += char;
|
||||
this._lastChar = char;
|
||||
this._lastChars += char;
|
||||
|
||||
if (this._lastChars.length > this._bodyWrapper.length)
|
||||
this._lastChars = this._lastChars.slice(-(this._bodyWrapper.length || 2));
|
||||
|
||||
this.checkBodyWrapper(char);
|
||||
this.checkQuote(char);
|
||||
this.checkCommentRow();
|
||||
const query = this.getQuery();
|
||||
|
||||
if (query)
|
||||
this.push(query);
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
checkEscape () {
|
||||
if (this._buffer.length > 0) {
|
||||
this.isEscape = this._lastChar === '\\'
|
||||
? !this.isEscape
|
||||
: false;
|
||||
}
|
||||
}
|
||||
|
||||
checkCommentRow () {
|
||||
if (this._isBody) return;
|
||||
|
||||
if (!this._isComment) {
|
||||
if (this.currentQuote === null && this._lastChars.includes('--'))
|
||||
this._isSingleLineComment = true;
|
||||
|
||||
if (this.currentQuote === null && this._lastChars.includes('/*'))
|
||||
this._isMultiLineComment = true;
|
||||
}
|
||||
else {
|
||||
if (this._isSingleLineComment && (this._lastChar === '\n' || this._lastChar === '\r')) {
|
||||
this._buffer = '';
|
||||
this._isSingleLineComment = false;
|
||||
}
|
||||
|
||||
if (this._isMultiLineComment && this._lastChars.includes('*/')) {
|
||||
this._buffer = '';
|
||||
this._isMultiLineComment = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkBodyWrapper (char) {
|
||||
if (this._isBody)
|
||||
this._isBody = this._lastChars !== this._bodyWrapper;
|
||||
|
||||
if (this.currentQuote === null && char === '$' && !this._firstDollarFound && !this._bodyWrapper) {
|
||||
this._firstDollarFound = true;
|
||||
this._bodyWrapperBuffer += char;
|
||||
this._isBody = true;
|
||||
}
|
||||
else if (this._firstDollarFound) {
|
||||
if (char === '\n' || char === ' ') {
|
||||
this._firstDollarFound = false;
|
||||
this._bodyWrapperBuffer = '';
|
||||
this._bodyWrapper = '';
|
||||
this._isBody = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this._bodyWrapperBuffer += char;
|
||||
const isEndDollar = char === '$';
|
||||
|
||||
if (isEndDollar) {
|
||||
this._firstDollarFound = false;
|
||||
this._bodyWrapper = this._bodyWrapperBuffer;
|
||||
this._bodyWrapperBuffer = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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._isBody || this._isComment)
|
||||
return false;
|
||||
|
||||
let query = false;
|
||||
let demiliterFound = false;
|
||||
|
||||
if (this.currentQuote === null && this._buffer.length >= this.delimiter.length)
|
||||
demiliterFound = this._lastChars.slice(-this.delimiter.length) === this.delimiter;
|
||||
|
||||
if (demiliterFound) {
|
||||
const parsedStr = this._buffer.trim();
|
||||
query = parsedStr;
|
||||
this._buffer = '';
|
||||
this._bodyWrapper = '';
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
}
|
@@ -13,7 +13,6 @@ Store.initRenderer();
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||
const isMacOS = process.platform === 'darwin';
|
||||
const isWindows = process.platform === 'win32';
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
|
||||
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
|
||||
@@ -33,7 +32,6 @@ async function createMainWindow () {
|
||||
minHeight: 550,
|
||||
title: 'Antares SQL',
|
||||
autoHideMenuBar: true,
|
||||
show: !isWindows, // Temporary workaround to https://github.com/electron/electron/issues/30024
|
||||
icon: nativeImage.createFromDataURL(icon.default),
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
@@ -115,9 +113,6 @@ else {
|
||||
mainWindow = await createMainWindow();
|
||||
createAppMenu();
|
||||
|
||||
if (isWindows) // Temporary workaround to https://github.com/electron/electron/issues/30024
|
||||
mainWindow.show();
|
||||
|
||||
// if (isDevelopment)
|
||||
// mainWindow.webContents.openDevTools();
|
||||
|
||||
|
64
src/main/workers/exporter.js
Normal file
64
src/main/workers/exporter.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import fs from 'fs';
|
||||
import { ClientsFactory } from '../libs/ClientsFactory';
|
||||
import MysqlExporter from '../libs/exporters/sql/MysqlExporter.js';
|
||||
import PostgreSQLExporter from '../libs/exporters/sql/PostgreSQLExporter';
|
||||
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;
|
||||
case 'pg':
|
||||
exporter = new PostgreSQLExporter(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();
|
||||
});
|
72
src/main/workers/importer.js
Normal file
72
src/main/workers/importer.js
Normal file
@@ -0,0 +1,72 @@
|
||||
import { ClientsFactory } from '../libs/ClientsFactory';
|
||||
import MySQLImporter from '../libs/importers/sql/MysqlImporter';
|
||||
import PostgreSQLImporter from '../libs/importers/sql/PostgreSQLImporter';
|
||||
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;
|
||||
case 'pg':
|
||||
importer = new PostgreSQLImporter(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>
|
||||
<div id="wrapper" :class="`theme-${applicationTheme}`">
|
||||
<div id="wrapper" :class="[`theme-${applicationTheme}`, !disableBlur || 'no-blur']">
|
||||
<TheTitleBar />
|
||||
<div id="window-content">
|
||||
<TheSettingBar />
|
||||
@@ -10,7 +10,9 @@
|
||||
:key="connection.uid"
|
||||
:connection="connection"
|
||||
/>
|
||||
<WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" />
|
||||
<div class="connection-panel-wrapper">
|
||||
<WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" />
|
||||
</div>
|
||||
</div>
|
||||
<TheFooter />
|
||||
<TheNotificationsBoard />
|
||||
@@ -51,6 +53,7 @@ export default {
|
||||
isScratchpad: 'application/isScratchpad',
|
||||
connections: 'connections/getConnections',
|
||||
applicationTheme: 'settings/getApplicationTheme',
|
||||
disableBlur: 'settings/getDisableBlur',
|
||||
isUnsavedDiscardModal: 'workspaces/isUnsavedDiscardModal'
|
||||
})
|
||||
},
|
||||
@@ -130,5 +133,15 @@ export default {
|
||||
> .columns {
|
||||
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>
|
||||
|
@@ -110,5 +110,4 @@ export default {
|
||||
.modal.modal-sm .modal-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@@ -93,16 +93,16 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.map{
|
||||
height: 400px;
|
||||
.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%);
|
||||
.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 {
|
||||
display: flex;
|
||||
word-break: keep-all;
|
||||
border-radius: $border-radius 0 0 $border-radius;
|
||||
}
|
||||
|
||||
|
@@ -7,7 +7,7 @@
|
||||
@blur="$emit('blur')"
|
||||
>
|
||||
<option v-if="!isValidDefault" :value="value">
|
||||
{{ value }} - {{ $t('message.invalidDefault') }}
|
||||
{{ value === null ? 'NULL' : value }}
|
||||
</option>
|
||||
<option
|
||||
v-for="row in foreignList"
|
||||
|
511
src/renderer/components/ModalExportSchema.vue
Normal file
511
src/renderer/components/ModalExportSchema.vue
Normal file
@@ -0,0 +1,511 @@
|
||||
<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" /> {{ $tc(`word.${key}`, 2) }}
|
||||
</label>
|
||||
<div v-if="customizations.exportByChunks">
|
||||
<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>
|
||||
|
||||
<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);
|
||||
},
|
||||
customizations () {
|
||||
return this.currentWorkspace.customizations;
|
||||
},
|
||||
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 = ['functions', 'views', 'triggers', 'routines', 'schedulers'];
|
||||
|
||||
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="d-flex">
|
||||
<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>
|
||||
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||
|
181
src/renderer/components/ModalImportSchema.vue
Normal file
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>
|
@@ -117,6 +117,19 @@
|
||||
</label>
|
||||
</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="col-7 col-sm-12">
|
||||
<label class="form-label">
|
||||
@@ -282,12 +295,18 @@
|
||||
<div class="text-center">
|
||||
<img src="../images/logo.svg" width="128">
|
||||
<h4>{{ appName }}</h4>
|
||||
<p>
|
||||
<p class="mb-2">
|
||||
{{ $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>
|
||||
<small>{{ $t('word.author') }} <a class="c-hand" @click="openOutside('https://github.com/Fabio286')">Fabio Di Stasio</a></small><br>
|
||||
<small>{{ $t('message.madeWithJS') }}</small>
|
||||
<a class="c-hand" @click="openOutside('https://github.com/antares-sql/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')">{{ appAuthor }}</a></small><br>
|
||||
</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>
|
||||
@@ -313,6 +332,7 @@ export default {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
appAuthor: 'Fabio Di Stasio',
|
||||
localLocale: null,
|
||||
localPageSize: null,
|
||||
localTimeout: null,
|
||||
@@ -367,7 +387,8 @@ export default {
|
||||
{ code: 'vibrant_ink', name: 'Vibrant Ink' }
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
contributors: process.env.APP_CONTRIBUTORS
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -381,6 +402,7 @@ export default {
|
||||
selectedLineWrap: 'settings/getLineWrap',
|
||||
notificationsTimeout: 'settings/getNotificationsTimeout',
|
||||
restoreTabs: 'settings/getRestoreTabs',
|
||||
disableBlur: 'settings/getDisableBlur',
|
||||
applicationTheme: 'settings/getApplicationTheme',
|
||||
editorTheme: 'settings/getEditorTheme',
|
||||
editorFontSize: 'settings/getEditorFontSize',
|
||||
@@ -417,6 +439,12 @@ GROUP BY
|
||||
ORDER BY
|
||||
employee.id ASC;
|
||||
`;
|
||||
},
|
||||
otherContributors () {
|
||||
return this.contributors
|
||||
.split(',')
|
||||
.filter(c => !c.includes(this.appAuthor))
|
||||
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
||||
}
|
||||
},
|
||||
created () {
|
||||
@@ -436,6 +464,7 @@ ORDER BY
|
||||
changeLocale: 'settings/changeLocale',
|
||||
changePageSize: 'settings/changePageSize',
|
||||
changeRestoreTabs: 'settings/changeRestoreTabs',
|
||||
changeDisableBlur: 'settings/changeDisableBlur',
|
||||
changeAutoComplete: 'settings/changeAutoComplete',
|
||||
changeLineWrap: 'settings/changeLineWrap',
|
||||
changeApplicationTheme: 'settings/changeApplicationTheme',
|
||||
@@ -463,6 +492,9 @@ ORDER BY
|
||||
toggleRestoreSession () {
|
||||
this.changeRestoreTabs(!this.restoreTabs);
|
||||
},
|
||||
toggleDisableBlur () {
|
||||
this.changeDisableBlur(!this.disableBlur);
|
||||
},
|
||||
toggleAutoComplete () {
|
||||
this.changeAutoComplete(!this.selectedAutoComplete);
|
||||
},
|
||||
|
@@ -11,11 +11,11 @@
|
||||
|
||||
<div class="footer-right-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')">
|
||||
<i class="mdi mdi-18px mdi-tree mr-1" />
|
||||
<small>{{ $t('message.plantATree') }}</small>
|
||||
<li class="footer-element footer-link" @click="openOutside('https://www.paypal.com/paypalme/fabiodistasio')">
|
||||
<i class="mdi mdi-18px mdi-coffee mr-1" />
|
||||
<small>{{ $t('word.donate') }}</small>
|
||||
</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/antares-sql/antares/issues')">
|
||||
<i class="mdi mdi-18px mdi-bug" />
|
||||
</li>
|
||||
<li class="footer-element footer-link" @click="showSettingModal('about')">
|
||||
|
@@ -24,7 +24,11 @@
|
||||
@mousedown.left="selectTab({uid: workspace.uid, tab: tab.uid})"
|
||||
@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" />
|
||||
<span>
|
||||
<span>{{ tab.content || 'Query' | cutText }} #{{ tab.index }}</span>
|
||||
@@ -446,7 +450,9 @@
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<WorkspaceEditConnectionPanel v-else :connection="connection" />
|
||||
<div v-else class="connection-panel-wrapper">
|
||||
<WorkspaceEditConnectionPanel :connection="connection" />
|
||||
</div>
|
||||
<ModalProcessesList
|
||||
v-if="isProcessesModal"
|
||||
:connection="connection"
|
||||
|
@@ -34,7 +34,7 @@
|
||||
<fieldset class="m-0" :disabled="isBusy">
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -47,7 +47,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<select v-model="connection.client" class="form-select">
|
||||
@@ -61,9 +61,22 @@
|
||||
</select>
|
||||
</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 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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -75,7 +88,7 @@
|
||||
</div>
|
||||
<div v-if="customizations.fileConnection" class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
@@ -88,7 +101,7 @@
|
||||
</div>
|
||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -102,7 +115,7 @@
|
||||
</div>
|
||||
<div v-if="customizations.database" class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -114,7 +127,7 @@
|
||||
</div>
|
||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -127,7 +140,7 @@
|
||||
</div>
|
||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -140,7 +153,7 @@
|
||||
</div>
|
||||
<div v-if="customizations.connectionSchema" class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -176,7 +189,7 @@
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label">
|
||||
<label class="form-label cut-text">
|
||||
{{ $t('message.enableSsl') }}
|
||||
</label>
|
||||
</div>
|
||||
@@ -190,7 +203,7 @@
|
||||
<fieldset class="m-0" :disabled="isBusy || !connection.ssl">
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
@@ -203,7 +216,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
@@ -216,7 +229,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
@@ -229,7 +242,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -240,6 +253,14 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12" />
|
||||
<div class="column col-8 col-sm-12">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="connection.untrustedConnection" type="checkbox"><i class="form-icon" /> {{ $t('message.untrustedConnection') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
@@ -249,7 +270,7 @@
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label">
|
||||
<label class="form-label cut-text">
|
||||
{{ $t('message.enableSsh') }}
|
||||
</label>
|
||||
</div>
|
||||
@@ -263,7 +284,7 @@
|
||||
<fieldset class="m-0" :disabled="isBusy || !connection.ssh">
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -275,7 +296,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -287,7 +308,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -299,7 +320,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -313,7 +334,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
@@ -326,7 +347,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -407,12 +428,14 @@ export default {
|
||||
key: '',
|
||||
ca: '',
|
||||
ciphers: '',
|
||||
untrustedConnection: false,
|
||||
ssh: false,
|
||||
sshHost: '',
|
||||
sshUser: '',
|
||||
sshPass: '',
|
||||
sshKey: '',
|
||||
sshPort: 22
|
||||
sshPort: 22,
|
||||
pgConnString: ''
|
||||
},
|
||||
isConnecting: false,
|
||||
isTesting: false,
|
||||
@@ -540,12 +563,12 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.connection-panel {
|
||||
margin-top: 15vh;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.panel {
|
||||
width: 450px;
|
||||
min-width: 450px;
|
||||
border-radius: $border-radius;
|
||||
|
||||
.panel-body {
|
||||
|
@@ -34,7 +34,7 @@
|
||||
<fieldset class="m-0" :disabled="isBusy">
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -47,7 +47,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<select v-model="localConnection.client" class="form-select">
|
||||
@@ -61,9 +61,22 @@
|
||||
</select>
|
||||
</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 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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -75,7 +88,7 @@
|
||||
</div>
|
||||
<div v-if="customizations.fileConnection" class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
@@ -88,7 +101,7 @@
|
||||
</div>
|
||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -102,7 +115,7 @@
|
||||
</div>
|
||||
<div v-if="customizations.database" class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -114,7 +127,7 @@
|
||||
</div>
|
||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -127,7 +140,7 @@
|
||||
</div>
|
||||
<div v-if="!customizations.fileConnection" class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -140,7 +153,7 @@
|
||||
</div>
|
||||
<div v-if="customizations.connectionSchema" class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -176,7 +189,7 @@
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label">
|
||||
<label class="form-label cut-text">
|
||||
{{ $t('message.enableSsl') }}
|
||||
</label>
|
||||
</div>
|
||||
@@ -190,7 +203,7 @@
|
||||
<fieldset class="m-0" :disabled="isBusy || !localConnection.ssl">
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
@@ -203,7 +216,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
@@ -216,7 +229,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
@@ -229,7 +242,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -240,6 +253,14 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12" />
|
||||
<div class="column col-8 col-sm-12">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="localConnection.untrustedConnection" type="checkbox"><i class="form-icon" /> {{ $t('message.untrustedConnection') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
@@ -249,7 +270,7 @@
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label">
|
||||
<label class="form-label cut-text">
|
||||
{{ $t('message.enableSsh') }}
|
||||
</label>
|
||||
</div>
|
||||
@@ -263,7 +284,7 @@
|
||||
<fieldset class="m-0" :disabled="isBusy || !localConnection.ssh">
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -275,7 +296,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -287,7 +308,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -299,7 +320,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -313,7 +334,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
@@ -326,7 +347,7 @@
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<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 class="column col-8 col-sm-12">
|
||||
<input
|
||||
@@ -519,12 +540,12 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.connection-panel {
|
||||
margin-top: 15vh;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.panel {
|
||||
width: 450px;
|
||||
min-width: 450px;
|
||||
border-radius: $border-radius;
|
||||
|
||||
.panel-body {
|
||||
|
@@ -58,6 +58,20 @@
|
||||
</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
|
||||
v-if="workspace.customizations.schemaEdit"
|
||||
class="context-element"
|
||||
@@ -95,6 +109,17 @@
|
||||
:selected-schema="selectedSchema"
|
||||
@close="hideEditModal"
|
||||
/>
|
||||
<ModalExportSchema
|
||||
v-if="isExportSchemaModal"
|
||||
:selected-schema="selectedSchema"
|
||||
@close="hideExportSchemaModal"
|
||||
/>
|
||||
<ModalImportSchema
|
||||
v-if="isImportSchemaModal"
|
||||
ref="importModalRef"
|
||||
:selected-schema="selectedSchema"
|
||||
@close="hideImportSchemaModal"
|
||||
/>
|
||||
</BaseContextMenu>
|
||||
</template>
|
||||
|
||||
@@ -103,14 +128,19 @@ import { mapGetters, mapActions } from 'vuex';
|
||||
import BaseContextMenu from '@/components/BaseContextMenu';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
import ModalEditSchema from '@/components/ModalEditSchema';
|
||||
import ModalExportSchema from '@/components/ModalExportSchema';
|
||||
import ModalImportSchema from '@/components/ModalImportSchema';
|
||||
import Schema from '@/ipc-api/Schema';
|
||||
import Application from '@/ipc-api/Application';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceExploreBarSchemaContext',
|
||||
components: {
|
||||
BaseContextMenu,
|
||||
ConfirmModal,
|
||||
ModalEditSchema
|
||||
ModalEditSchema,
|
||||
ModalExportSchema,
|
||||
ModalImportSchema
|
||||
},
|
||||
props: {
|
||||
contextEvent: MouseEvent,
|
||||
@@ -119,7 +149,9 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
isDeleteModal: false,
|
||||
isEditModal: false
|
||||
isEditModal: false,
|
||||
isExportSchemaModal: false,
|
||||
isImportSchemaModal: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -170,6 +202,30 @@ export default {
|
||||
this.isEditModal = false;
|
||||
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 () {
|
||||
this.$emit('close-context');
|
||||
},
|
||||
|
@@ -46,6 +46,24 @@
|
||||
<span>{{ $t('word.run') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
v-if="!autocommit"
|
||||
class="btn btn-dark btn-sm"
|
||||
:class="{'loading':isQuering}"
|
||||
@click="commitTab()"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-cube-send pr-1" />
|
||||
<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
|
||||
class="btn btn-link btn-sm mr-0"
|
||||
:disabled="!query || isQuering"
|
||||
@@ -78,7 +96,7 @@
|
||||
</button>
|
||||
<div class="dropdown table-dropdown pr-2">
|
||||
<button
|
||||
:disabled="!results.length || isQuering"
|
||||
:disabled="!hasResults || isQuering"
|
||||
class="btn btn-dark btn-sm dropdown-toggle mr-0 pr-0"
|
||||
tabindex="0"
|
||||
>
|
||||
@@ -95,6 +113,17 @@
|
||||
</li>
|
||||
</ul>
|
||||
</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 class="workspace-query-info">
|
||||
<div
|
||||
@@ -104,11 +133,19 @@
|
||||
>
|
||||
<i class="mdi mdi-timer-sand mdi-rotate-180 pr-1" /> <b>{{ durationsCount / 1000 }}s</b>
|
||||
</div>
|
||||
<div v-if="resultsCount">
|
||||
{{ $t('word.results') }}: <b>{{ resultsCount.toLocaleString() }}</b>
|
||||
<div
|
||||
v-if="resultsCount"
|
||||
class="d-flex"
|
||||
:title="$t('word.results')"
|
||||
>
|
||||
<i class="mdi mdi-equal pr-1" /> <b>{{ resultsCount.toLocaleString() }}</b>
|
||||
</div>
|
||||
<div v-if="affectedCount !== null">
|
||||
{{ $t('message.affectedRows') }}: <b>{{ affectedCount }}</b>
|
||||
<div
|
||||
v-if="hasAffected"
|
||||
class="d-flex"
|
||||
:title="$t('message.affectedRows')"
|
||||
>
|
||||
<i class="mdi mdi-target pr-1" /> <b>{{ affectedCount }}</b>
|
||||
</div>
|
||||
<div class="input-group" :title="$t('word.schema')">
|
||||
<i class="input-group-addon addon-sm mdi mdi-24px mdi-database" />
|
||||
@@ -182,6 +219,7 @@ export default {
|
||||
isQuering: false,
|
||||
isCancelling: false,
|
||||
showCancel: false,
|
||||
autocommit: true,
|
||||
results: [],
|
||||
selectedSchema: null,
|
||||
resultsCount: 0,
|
||||
@@ -200,6 +238,9 @@ export default {
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
tabUid () {
|
||||
return this.$vnode.key;
|
||||
},
|
||||
breadcrumbsSchema () {
|
||||
return this.workspace.breadcrumbs.schema || null;
|
||||
},
|
||||
@@ -214,6 +255,12 @@ export default {
|
||||
},
|
||||
history () {
|
||||
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: {
|
||||
@@ -251,12 +298,18 @@ export default {
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
tabUid: this.tab.uid
|
||||
};
|
||||
Schema.destroyConnectionToCommit(params);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
|
||||
updateTabContent: 'workspaces/updateTabContent',
|
||||
setUnsavedChanges: 'workspaces/setUnsavedChanges',
|
||||
saveHistory: 'history/saveHistory'
|
||||
}),
|
||||
async runQuery (query) {
|
||||
@@ -270,6 +323,7 @@ export default {
|
||||
uid: this.connection.uid,
|
||||
schema: this.selectedSchema,
|
||||
tabUid: this.tab.uid,
|
||||
autocommit: this.autocommit,
|
||||
query
|
||||
};
|
||||
|
||||
@@ -294,6 +348,8 @@ export default {
|
||||
content: query
|
||||
});
|
||||
this.saveHistory(params);
|
||||
if (!this.autocommit)
|
||||
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: true });
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
@@ -391,6 +447,42 @@ export default {
|
||||
},
|
||||
downloadTable (format) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -419,10 +511,12 @@ export default {
|
||||
|
||||
.workspace-query-runner-footer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
row-gap: 0.4rem;
|
||||
justify-content: space-between;
|
||||
padding: 0.3rem 0.6rem 0.4rem;
|
||||
align-items: center;
|
||||
height: 42px;
|
||||
min-height: 42px;
|
||||
|
||||
.workspace-query-buttons,
|
||||
.workspace-query-info {
|
||||
|
@@ -175,8 +175,10 @@ export default {
|
||||
if (this.currentSort && !this.isHardSort) {
|
||||
return [...this.localResults].sort((a, b) => {
|
||||
let modifier = 1;
|
||||
const 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];
|
||||
let valA = typeof a[this.currentSort] === 'string' ? a[this.currentSort].toLowerCase() : a[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 (valA < valB) return -1 * modifier;
|
||||
if (valA > valB) return 1 * modifier;
|
||||
|
@@ -272,7 +272,8 @@ export default {
|
||||
if (BIT.includes(type)) {
|
||||
if (typeof val === 'number') val = [val];
|
||||
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)) {
|
||||
|
@@ -85,7 +85,7 @@
|
||||
@click="showFakerModal"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
|
||||
<span>{{ $t('message.tableFiller') }}</span>
|
||||
<span>{{ $tc('message.insertRow', 2) }}</span>
|
||||
</button>
|
||||
|
||||
<div class="dropdown table-dropdown pr-2">
|
||||
|
@@ -80,6 +80,7 @@ module.exports = {
|
||||
deterministic: 'Deterministic',
|
||||
context: 'Context',
|
||||
export: 'Export',
|
||||
import: 'Import',
|
||||
returns: 'Returns',
|
||||
timing: 'Timing',
|
||||
state: 'State',
|
||||
@@ -122,9 +123,23 @@ module.exports = {
|
||||
select: 'Select',
|
||||
passphrase: 'Passphrase',
|
||||
filter: 'Filter',
|
||||
change: 'Change',
|
||||
views: 'Views',
|
||||
triggers: 'Triggers',
|
||||
routines: 'Routines',
|
||||
functions: 'Functions',
|
||||
schedulers: 'Schedulers',
|
||||
includes: 'Includes',
|
||||
drop: 'Drop',
|
||||
completed: 'Completed',
|
||||
aborted: 'Aborted',
|
||||
disabled: 'Disabled',
|
||||
enable: 'Enable',
|
||||
disable: 'Disable'
|
||||
disable: 'Disable',
|
||||
commit: 'Commit',
|
||||
rollback: 'Rollback',
|
||||
connectionString: 'Connection string',
|
||||
contributors: 'Contributors'
|
||||
},
|
||||
message: {
|
||||
appWelcome: 'Welcome to Antares SQL Client!',
|
||||
@@ -250,9 +265,30 @@ module.exports = {
|
||||
searchForQueries: 'Search for queries',
|
||||
killProcess: 'Kill process',
|
||||
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',
|
||||
readOnlyMode: 'Read-only mode',
|
||||
killQuery: 'Kill query'
|
||||
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',
|
||||
untrustedConnection: 'Untrusted connection'
|
||||
},
|
||||
faker: {
|
||||
address: 'Address',
|
||||
|
@@ -1,196 +1,298 @@
|
||||
module.exports = {
|
||||
word: {
|
||||
edit: 'Éditer',
|
||||
save: 'Enregistrer',
|
||||
edit: 'Editer',
|
||||
save: 'Sauver',
|
||||
close: 'Fermer',
|
||||
delete: 'Supprimer',
|
||||
delete: 'Effacer',
|
||||
confirm: 'Confirmer',
|
||||
cancel: 'Annuler',
|
||||
send: 'Envoyer',
|
||||
connectionName: 'Nom de connexion',
|
||||
connectionName: 'Nom de la connexion',
|
||||
client: 'Client',
|
||||
hostName: 'Nom d\'hôte',
|
||||
hostName: 'Nom du host',
|
||||
port: 'Port',
|
||||
user: 'Utilisateur',
|
||||
password: 'Mot de passe',
|
||||
credentials: 'Identifiants',
|
||||
connect: 'Se connecter',
|
||||
credentials: 'Credentials',
|
||||
connect: 'Connexion',
|
||||
connected: 'Connecté',
|
||||
disconnect: 'Se déconnecter',
|
||||
disconnect: 'Déconnection',
|
||||
disconnected: 'Déconnecté',
|
||||
refresh: 'Rafraichir',
|
||||
refresh: 'Rafrechir',
|
||||
settings: 'Paramètres',
|
||||
general: 'Général',
|
||||
themes: 'Thèmes',
|
||||
update: 'Mise à jour',
|
||||
about: 'À propos',
|
||||
language: 'Langue',
|
||||
about: 'A propos de',
|
||||
language: 'Langage',
|
||||
version: 'Version',
|
||||
donate: 'Faire un don',
|
||||
run: 'Exécuter',
|
||||
donate: 'Faire une donation',
|
||||
run: 'Lancer',
|
||||
schema: 'Schéma',
|
||||
results: 'Résutats',
|
||||
results: 'Resultats',
|
||||
size: 'Taille',
|
||||
seconds: 'Secondes',
|
||||
type: 'Type',
|
||||
mimeType: 'Mime-Type',
|
||||
download: 'Télécharger',
|
||||
add: 'Ajouter',
|
||||
data: 'Données',
|
||||
data: 'Donnée',
|
||||
properties: 'Propriétés',
|
||||
insert: 'Insérer',
|
||||
connecting: 'Connexion',
|
||||
connecting: 'Connexion en cours',
|
||||
name: 'Nom',
|
||||
collation: 'Collation',
|
||||
collation: 'Langage',
|
||||
clear: 'Effacer',
|
||||
options: 'Options',
|
||||
autoRefresh: 'Auto-rafraichissement',
|
||||
indexes: 'Index',
|
||||
foreignKeys: 'Clés étrangères',
|
||||
length: 'Taille',
|
||||
unsigned: 'Non-signé',
|
||||
default: 'Défaut',
|
||||
autoRefresh: 'Auto-rafraichissemnt',
|
||||
indexes: 'Indexs',
|
||||
foreignKeys: 'Clefs étrangères',
|
||||
length: 'Longueur',
|
||||
unsigned: 'non signé',
|
||||
default: 'Par défault',
|
||||
comment: 'Commentaire',
|
||||
key: 'Clé | Clés',
|
||||
key: 'Clef | Clefs',
|
||||
order: 'Ordre',
|
||||
expression: 'Expression',
|
||||
autoIncrement: 'Auto Increment',
|
||||
engine: 'Engine',
|
||||
autoIncrement: 'Auto Incrémentation',
|
||||
engine: 'Moteur',
|
||||
field: 'Champ | Champs',
|
||||
approximately: 'Approximativement',
|
||||
total: 'Totale',
|
||||
total: 'Total',
|
||||
table: 'Table',
|
||||
discard: 'Abandonner',
|
||||
discard: 'Jeter',
|
||||
stay: 'Rester',
|
||||
author: 'Auteur',
|
||||
light: 'Clair',
|
||||
light: 'Léger',
|
||||
dark: 'Sombre',
|
||||
autoCompletion: 'Completion auto',
|
||||
autoCompletion: 'Auto Completion',
|
||||
application: 'Application',
|
||||
editor: 'Editeur',
|
||||
editor: 'Editor',
|
||||
view: 'Vue',
|
||||
definer: 'Définisseur',
|
||||
algorithm: 'Algorithme',
|
||||
algorithme: 'Algorithme',
|
||||
trigger: 'Déclencheur | Déclencheurs',
|
||||
storedRoutine: 'Procedure stockée | Procedures stockées',
|
||||
scheduler: 'Opération planifiée | Opérations planifiées',
|
||||
event: 'Evenement',
|
||||
storedRoutine: 'Routine stockée | Routines stockées',
|
||||
scheduler: 'Planificateur | Planificateurs',
|
||||
event: 'Évènement',
|
||||
parameters: 'Paramètres',
|
||||
function: 'Fonction | Fonctions',
|
||||
deterministic: 'Déterministe',
|
||||
context: 'Contextz',
|
||||
export: 'Exporter',
|
||||
returns: 'Retourner',
|
||||
timing: 'Horaire',
|
||||
state: 'État',
|
||||
context: 'Contexte',
|
||||
export: 'Export',
|
||||
import: 'Import',
|
||||
returns: 'Retourne',
|
||||
timing: 'Temps',
|
||||
state: 'Etat',
|
||||
execution: 'Exécution',
|
||||
starts: 'Débuts',
|
||||
ends: 'Fins',
|
||||
starts: 'Commence',
|
||||
ends: 'Fini',
|
||||
ssl: 'SSL',
|
||||
privateKey: 'Clé privée',
|
||||
certificate: 'Certificat',
|
||||
caCertificate: 'CA certificat',
|
||||
ciphers: 'Chiffrement',
|
||||
upload: 'Charger',
|
||||
browse: 'Parcourir',
|
||||
faker: 'Faker',
|
||||
sshTunnel: 'SSH tunnel'
|
||||
caCertificate: 'Certificat CA',
|
||||
ciphers: 'Codes secrets',
|
||||
upload: 'Téléverser',
|
||||
browse: 'Naviguer',
|
||||
faker: 'Imposteur',
|
||||
sshTunnel: 'SSH tunnel',
|
||||
content: 'Contenu',
|
||||
cut: 'Couper',
|
||||
copy: 'Copier',
|
||||
paste: 'Coller',
|
||||
tools: 'Outils',
|
||||
variables: 'Variables',
|
||||
processes: 'Processus',
|
||||
Database: 'Base de données',
|
||||
scratchpad: 'Bloc-notes',
|
||||
array: 'Tableau',
|
||||
changelog: 'Log de changement',
|
||||
Format: 'Format',
|
||||
structure: 'Structure',
|
||||
small: 'Petit',
|
||||
medium: 'Moyen',
|
||||
large: 'Grand',
|
||||
row: 'Ligne | Lignes',
|
||||
cell: 'Cellule | Cellules',
|
||||
triggerFunction: 'Fonction de déclenchement | Fonctions de déclenchement',
|
||||
all: 'Tout',
|
||||
duplicate: 'Double',
|
||||
routine: 'Routine',
|
||||
new: 'Nouveau',
|
||||
history: 'Passé',
|
||||
select: 'Sélectionner',
|
||||
passphrase: 'Phrase',
|
||||
filter: 'Filtre',
|
||||
change: 'Changement',
|
||||
views: 'Vues',
|
||||
triggers: 'Déclencheurs',
|
||||
routines: 'Routines',
|
||||
fonctions: 'Fonctions',
|
||||
schedulers: 'Programmateurs',
|
||||
includes: 'Inclut',
|
||||
drop: 'Effacer',
|
||||
completed: 'Terminé',
|
||||
aborted: 'Annulé',
|
||||
disabled: 'Désactivé',
|
||||
enable: 'activer',
|
||||
disable: 'Désactiver',
|
||||
commit: 'Appliquer',
|
||||
rollback: 'Retour arrière',
|
||||
connectionString: 'Chaîne de connexion',
|
||||
contributors: 'Contributeurs'
|
||||
},
|
||||
message: {
|
||||
appWelcome: 'Bienvenu sur le client SQL Antares!',
|
||||
appFirstStep: 'Première étape: Créer une nouvelle connexion à une base de données.',
|
||||
appWelcome: 'Bienvenue dans le client SQL Antares!',
|
||||
appFirstStep: 'Votre première étape : créer une nouvelle connexion à la base de données.',
|
||||
addConnection: 'Ajouter une connexion',
|
||||
createConnection: 'Créer une connexion',
|
||||
createNewConnection: 'Créer une nouvelle connexion',
|
||||
askCredentials: 'Demander les identifiants',
|
||||
askCredentials: 'Demander des informations d\'identification',
|
||||
testConnection: 'Tester la connexion',
|
||||
editConnection: 'Editer la connexion',
|
||||
editConnection: 'Modifier la connexion',
|
||||
deleteConnection: 'Supprimer la connexion',
|
||||
deleteCorfirm: 'Êtes-vous sûr de vouloir annuler',
|
||||
connectionSuccessfullyMade: 'Connexion établie avec succès!',
|
||||
madeWithJS: 'Créé avec 💛 et JavaScript!',
|
||||
checkForUpdates: 'Rechercher des mises à jour',
|
||||
deleteCorfirm: 'Confirmez-vous l\'annulation de',
|
||||
connectionSuccessfullyMade: 'Connexion établie avec succès !',
|
||||
madeWithJS: 'Fait avec 💛 et JavaScript !',
|
||||
checkForUpdates: 'Vérifier les mises à jour',
|
||||
noUpdatesAvailable: 'Aucune mise à jour disponible',
|
||||
checkingForUpdate: 'Recherche de mise à jour',
|
||||
checkFailure: 'Erreur lors de la recherche, essayez plus tard',
|
||||
updateAvailable: 'Une mise à jour est disponible',
|
||||
checkingForUpdate: 'Vérification des mises à jour',
|
||||
checkFailure: 'La vérification a échoué, veuillez essayer plus tard',
|
||||
updateAvailable: 'Mise à jour disponible',
|
||||
downloadingUpdate: 'Téléchargement de la mise à jour',
|
||||
updateDownloaded: 'Mise à jour téléchargée',
|
||||
restartToInstall: 'Redémarrer Antares pour l\'installer',
|
||||
unableEditFieldWithoutPrimary: 'Impossible de modifier un champ sans clé primaire dans l\'ensemble de résultats',
|
||||
editCell: 'Modifier une cellule',
|
||||
deleteRows: 'Supprimer une ligne | Supprimer {count} lignes',
|
||||
confirmToDeleteRows: 'Êtes-vous sûr de vouloir supprimer une ligne? | Êtes-vous sûr de vouloir supprimer {count} lignes?',
|
||||
notificationsTimeout: 'Délai d\'expiration des notifications',
|
||||
uploadFile: 'Charger un fichier',
|
||||
addNewRow: 'Ajouter une ligne',
|
||||
restartToInstall: 'Redémarrer Antares pour installer',
|
||||
unableEditFieldWithoutPrimary: 'Impossible de modifier un champ sans clé primaire dans le jeu de résultats',
|
||||
editCell: 'Editer la cellule',
|
||||
deleteRows: 'Effacer la ligne | Effacer {count} lignes',
|
||||
confirmToDeleteRows: 'Confirmez-vous la suppression d\'une ligne ? | Confirmez-vous la suppression de {count} lignes ?',
|
||||
notificationsTimeout: 'Délai d\'attente pour les notifications',
|
||||
uploadFile: 'Télécharger un fichier',
|
||||
addNewRow: 'Ajouter une nouvelle ligne',
|
||||
numberOfInserts: 'Nombre d\'insertions',
|
||||
openNewTab: 'Ouvrir un nouvel onglet',
|
||||
affectedRows: 'Lignes concernées',
|
||||
affectedRows: 'Lignes affectées',
|
||||
createNewDatabase: 'Créer une nouvelle base de données',
|
||||
databaseName: 'Nom par défaut',
|
||||
databaseName: 'Nom de la base de données',
|
||||
serverDefault: 'Serveur par défaut',
|
||||
deleteDatabase: 'Supprimer la base de données',
|
||||
editDatabase: 'Modifier la base de données',
|
||||
editDatabase: 'Editer la base de données',
|
||||
clearChanges: 'Effacer les modifications',
|
||||
addNewField: 'Ajouter un champ',
|
||||
manageIndexes: 'Gérer les index',
|
||||
manageForeignKeys: 'Gérer les clés étrangères',
|
||||
allowNull: 'NULL autorisé',
|
||||
zeroFill: 'Remplissage zéro',
|
||||
addNewField: 'Ajouter un nouveau champ',
|
||||
manageIndexes: 'Gère les index',
|
||||
manageForeignKeys: 'Gère les clés étrangères',
|
||||
allowNull: 'Autoriser NULL',
|
||||
zeroFill: 'Remplissage avec zéro',
|
||||
customValue: 'Valeur personnalisée',
|
||||
onUpdate: 'Lors d\'une mise à jour',
|
||||
onUpdate: 'lors de la mise à jour',
|
||||
deleteField: 'Supprimer le champ',
|
||||
createNewIndex: 'Créer un index',
|
||||
createNewIndex: 'Créer un nouvel index',
|
||||
addToIndex: 'Ajouter à l\'index',
|
||||
createNewTable: 'Créer une nouvelle table',
|
||||
emptyTable: 'Table vide',
|
||||
deleteTable: 'Supprimer la table',
|
||||
emptyCorfirm: 'Êtes-vous sûr de vouloir videz',
|
||||
unsavedChanges: 'Changements non sauvegardés',
|
||||
discardUnsavedChanges: 'Vous avez des modifications non sauvegardées. En quittant cet onglet, ces changements seront supprimés.',
|
||||
thereAreNoIndexes: 'Il n\'y a pas d\'indexes',
|
||||
emptyTable: 'Vider la table',
|
||||
deleteTable: 'Supprimer une table',
|
||||
emptyCorfirm: 'Confirmez-vous que vous souhaitez vider la table ?',
|
||||
unsavedChanges: 'Modifications non sauvegardées',
|
||||
discardUnsavedChanges: 'Vous avez des modifications non sauvegardées. En fermant cet onglet, ces modifications seront supprimées',
|
||||
thereAreNoIndexes: 'Il n\'y a pas d\'index',
|
||||
thereAreNoForeign: 'Il n\'y a pas de clés étrangères',
|
||||
createNewForeign: 'Créer une clés étrangère',
|
||||
createNewForeign: 'Créer une nouvelle clé étrangère',
|
||||
referenceTable: 'Table de référence',
|
||||
referenceField: 'CHamp de référence',
|
||||
foreignFields: 'Champ étrangé',
|
||||
referenceField: 'Champ de référence',
|
||||
foreignFields: 'Champs étrangers',
|
||||
invalidDefault: 'Valeur par défaut invalide',
|
||||
onDelete: 'Lors de la suppression',
|
||||
onDelete: 'lors de l\'effacement',
|
||||
applicationTheme: 'Thème de l\'application',
|
||||
editorTheme: 'Thème de l\'éditeur',
|
||||
wrapLongLines: 'Retour à la ligne automatique',
|
||||
selectStatement: 'Sélectionnez la déclaration',
|
||||
triggerStatement: 'Déclaration de déclencheur',
|
||||
wrapLongLines: 'Retour à la ligne pour les lignes longues',
|
||||
selectStatement: 'Sélectionner un état',
|
||||
triggerStatement: 'Déclencher un état',
|
||||
sqlSecurity: 'Sécurité SQL',
|
||||
updateOption: 'Options de mises à jour',
|
||||
deleteView: 'Supprimer la vue',
|
||||
updateOption: 'Mide à jour d\'une option',
|
||||
deleteView: 'Effacer une vue',
|
||||
createNewView: 'Créer une nouvelle vue',
|
||||
deleteTrigger: 'Supprimer le déclencheur',
|
||||
createNewTrigger: 'Créer un nouveau déclencheur',
|
||||
currentUser: 'Utilisateur actuel',
|
||||
routineBody: 'Contenu de la procédure',
|
||||
deleteTrigger: 'Effacer un déclencheur',
|
||||
createNewTrigger: 'Cr"er un nouveau déclencheur',
|
||||
currentUser: 'utilisateur courant',
|
||||
routineBody: 'Corps de la routine',
|
||||
dataAccess: 'Accès aux données',
|
||||
thereAreNoParameters: 'Il n\'y a pas de paramètres',
|
||||
thereAreNoParameters: 'Il n\'y a pas de paramètre',
|
||||
createNewParameter: 'Créer un nouveau paramètre',
|
||||
createNewRoutine: 'Créer une nouvelle procédure stockée',
|
||||
deleteRoutine: 'Supprimer une procédure stockée',
|
||||
functionBody: 'Contenu de la fonction',
|
||||
createNewRoutine: 'Créer une nouvelle routine stockée',
|
||||
deleteRoutine: 'Effacer une routine stockée',
|
||||
functionBody: 'Corps d\'une fonction',
|
||||
createNewFunction: 'Créer une nouvelle fonction',
|
||||
deleteFunction: 'Supprimer la fonction',
|
||||
schedulerBody: 'Contenu du opération planifiée',
|
||||
createNewScheduler: 'Créere une nouvelle opération planifiée',
|
||||
deleteScheduler: 'Supprimer l\'opération planifiée',
|
||||
preserveOnCompletion: 'Préserver à l\'achèvement',
|
||||
deleteFunction: 'Effacer une fonction',
|
||||
schedulerBody: 'Corps d\'un programmateur',
|
||||
createNewScheduler: 'Créer un nouveau programmateur',
|
||||
deleteScheduler: 'Effacer un programmateur',
|
||||
preserveOnCompletion: 'Préserver à la terminaison',
|
||||
enableSsl: 'Activer le SSL',
|
||||
manualValue: 'Valeur manuelle',
|
||||
tableFiller: 'Remplisseur de table',
|
||||
enableSsh: 'Activer le SSH'
|
||||
fakeDataLanguage: 'Language de données fausses',
|
||||
searchForElements: 'Rechercher des éléments',
|
||||
selectAll: 'Tout séélectionner',
|
||||
queryDuration: 'Temps de requêtage',
|
||||
includeBetaUpdates: 'Inclure les mises à jour beta',
|
||||
setNull: 'Définir comme NULL',
|
||||
processesList: 'List des processus',
|
||||
processInfo: 'Information sur le processus',
|
||||
manageUsers: 'Organisation des utilisateurs',
|
||||
createNewSchema: 'Créer un nouveau schéma',
|
||||
schemaName: 'Nom du Schéma',
|
||||
editSchema: 'Modifier le schéma',
|
||||
deleteSchema: 'Effacer le schéma',
|
||||
markdownSupported: 'Support du Markdown',
|
||||
plantATree: 'Planter un arbre',
|
||||
dataTabPageSize: 'Taille de la page de l\'onglet données',
|
||||
enableSsh: 'Activer le SSH',
|
||||
pageNumber: 'Numéro de la page',
|
||||
duplicateTable: 'Copier la table',
|
||||
noOpenTabs: 'Il n\'y a pas d\'onglet ouvert, naviguer vers la barre gauche ou :',
|
||||
noSchema: 'Pas de schéma',
|
||||
restorePreviourSession: 'Restorer une session précédente',
|
||||
runQuery: 'Lancer la requête',
|
||||
thereAreNoTableFields: 'Il n\'y a pas de champ table',
|
||||
newTable: 'Nouvelle table',
|
||||
newView: 'Nouvelle vue',
|
||||
newTrigger: 'Nouveau déclencheur',
|
||||
newRoutine: 'Nouvelle routine',
|
||||
newFunction: 'Nouvelle function',
|
||||
newScheduler: 'Nouveau déclencheur',
|
||||
newTriggerFunction: 'Nouvelle fonction de déclencheur',
|
||||
thereIsNoQueriesYet: 'Il n\'y a pas encore de requête',
|
||||
searchForQueries: 'Rechercher des requêtes',
|
||||
killProcess: 'tuer un processus',
|
||||
closeTab: 'Fermer un onglet',
|
||||
exportSchema: 'Exporter un schéma',
|
||||
importSchema: 'Importer un schéma',
|
||||
directoryPath: 'chemin du répertoire',
|
||||
newInserStmtEvery: 'Nouvelle déclaration d\'insertion tous les',
|
||||
processingTableExport: 'Traitement {table}',
|
||||
fechingTableExport: 'Recherche des données de la table {table}',
|
||||
writingTableExport: 'Ecriture des données de la table {table}',
|
||||
checkAllTables: 'Vérification de toutes les tables',
|
||||
uncheckAllTables: 'Désélectionner toutes les tables',
|
||||
goToDownloadPage: 'Aller vers la page de téléchargement',
|
||||
readOnlyMode: 'Mode lecture seule',
|
||||
killQuery: 'Terminer une requête',
|
||||
insertRow: 'Insérer un ligne | Insérer des lignes',
|
||||
commitMode: 'Mode insertion directe',
|
||||
autoCommit: 'Insertion directe automatique',
|
||||
manualCommit: 'Insertion directe manuelle',
|
||||
actionSuccessful: '{action} réussie',
|
||||
importQueryErrors: 'Attention : {n} erreurs se sont produites | Attention: {n} erreurs sont apparues',
|
||||
executedQueries: '{n} requêtes ont été exécutées | {n} requêtes exécutées',
|
||||
ourputFormat: 'Format de sortie',
|
||||
singleFile: 'Seul fichier avec l\'extension {ext}',
|
||||
zipCompressedFile: 'Fichier compréssé avec l\'extension {ext}',
|
||||
disableBlur: 'Désactiver le floue'
|
||||
},
|
||||
faker: {
|
||||
address: 'Adresse',
|
||||
commerce: 'Commerce',
|
||||
company: 'Entreprise',
|
||||
company: 'Companie',
|
||||
database: 'Base de données',
|
||||
date: 'Date',
|
||||
finance: 'Finance',
|
||||
@@ -204,105 +306,105 @@ module.exports = {
|
||||
random: 'Aléatoire',
|
||||
system: 'Système',
|
||||
time: 'Temps',
|
||||
vehicle: 'Véhicle',
|
||||
vehicle: 'Véhicule',
|
||||
zipCode: 'Code postal',
|
||||
zipCodeByState: 'Code postal par région',
|
||||
zipCodeByState: 'Code postal par état',
|
||||
city: 'Ville',
|
||||
cityPrefix: 'Préfixe de la ville',
|
||||
citySuffix: 'Suffixe de la ville',
|
||||
streetName: 'Ne de la rue',
|
||||
streetAddress: 'Adresse',
|
||||
streetName: 'Nom de la rue',
|
||||
streetAddress: 'Adresse de la rue',
|
||||
streetSuffix: 'Suffixe de la rue',
|
||||
streetPrefix: 'Préfixe de la rue',
|
||||
secondaryAddress: 'Adresse secondaire',
|
||||
county: 'Comté',
|
||||
county: 'Département',
|
||||
country: 'Pays',
|
||||
countryCode: 'Code du pays',
|
||||
state: 'Région',
|
||||
stateAbbr: 'Abbreviation de la région',
|
||||
state: 'Etat',
|
||||
stateAbbr: 'Abréviation de l\'état',
|
||||
latitude: 'Latitude',
|
||||
longitude: 'Longitude',
|
||||
direction: 'Direction',
|
||||
cardinalDirection: 'Orientation cardinale',
|
||||
ordinalDirection: 'Orientation originale',
|
||||
nearbyGPSCoordinate: 'Coordonnées GPS des environs',
|
||||
timeZone: 'Fuseau horaire',
|
||||
cardinalDirection: 'Direction cardinale',
|
||||
ordinalDirection: 'Direction Ordinale',
|
||||
nearbyGPSCoordinate: 'Coordonnées GPS proches',
|
||||
timeZone: 'fuseau horaire',
|
||||
color: 'Couleur',
|
||||
department: 'Département',
|
||||
productName: 'Nom de produit',
|
||||
department: 'Départment',
|
||||
productName: 'Nom du produit',
|
||||
price: 'Prix',
|
||||
productAdjective: 'Adjectif du produit',
|
||||
productMaterial: 'Matériau du produit',
|
||||
productMaterial: 'Matériaux du produit',
|
||||
product: 'Produit',
|
||||
productDescription: 'Description du produit',
|
||||
suffixes: 'Suffixes',
|
||||
companyName: 'Nom de l\'entreprise',
|
||||
companySuffix: 'Suffixe de l\'entreprise',
|
||||
catchPhrase: 'Slogan',
|
||||
companyName: 'Nom de la société',
|
||||
companySuffix: 'Suffixe de la société',
|
||||
catchPhrase: 'Phrase de rappel',
|
||||
bs: 'BS',
|
||||
catchPhraseAdjective: 'Adjectif du slogan',
|
||||
catchPhraseDescriptor: 'Descripteur de slogan',
|
||||
catchPhraseNoun: 'Nom de la phrase d\'accroche',
|
||||
catchPhraseAdjective: 'Adjectif de la phrase de rappel',
|
||||
catchPhraseDescriptor: 'Descripteur de la phrase de rappel',
|
||||
catchPhraseNoun: 'Nom de la phrase de rappel',
|
||||
bsAdjective: 'Adjectif BS',
|
||||
bsBuzz: 'BS buzz',
|
||||
bsNoun: 'Nom BS',
|
||||
column: 'Colonne',
|
||||
type: 'Type',
|
||||
collation: 'Collation',
|
||||
engine: 'Engine',
|
||||
collation: 'Langue',
|
||||
engine: 'Moteur',
|
||||
past: 'Passé',
|
||||
future: 'Futur',
|
||||
future: 'Future',
|
||||
between: 'Entre',
|
||||
recent: 'Récent',
|
||||
soon: 'Bientôt',
|
||||
month: 'Mois',
|
||||
weekday: 'Mercredi',
|
||||
account: 'Compte',
|
||||
accountName: 'Nom de compte',
|
||||
routingNumber: 'Numéros de routage',
|
||||
weekday: 'Jour de la semaine',
|
||||
account: 'compte',
|
||||
accountName: 'Nom du compte',
|
||||
routingNumber: 'Numéro de la routing',
|
||||
mask: 'Masque',
|
||||
amount: 'Quantité',
|
||||
transactionType: 'Type de transaction',
|
||||
amount: 'Montant',
|
||||
transactionType: 'Type de la transaction',
|
||||
currencyCode: 'Code de la devise',
|
||||
currencyName: 'Nom de la devise',
|
||||
currencySymbol: 'Symbole de la devise',
|
||||
bitcoinAddress: 'Adresse Bitcoin',
|
||||
litecoinAddress: 'Adresse Litecoin',
|
||||
creditCardNumber: 'Numero de carte de crédit',
|
||||
creditCardCVV: 'Cryptogramme',
|
||||
creditCardNumber: 'Numéro de la carte de bancaire',
|
||||
creditCardCVV: 'Numéro de vérification',
|
||||
ethereumAddress: 'Adresse Ethereum',
|
||||
iban: 'Iban',
|
||||
bic: 'Bic',
|
||||
transactionDescription: 'Description de la transaction',
|
||||
branch: 'Branche',
|
||||
commitEntry: 'Valider l\'entrée',
|
||||
commitMessage: 'Valider le message',
|
||||
commitSha: 'Valider le SHA',
|
||||
shortSha: 'SHA court',
|
||||
abbreviation: 'Abbréviation',
|
||||
commitEntry: 'Insérer les données',
|
||||
commitMessage: 'Insérer le message',
|
||||
commitSha: 'Insérer SHA',
|
||||
shortSha: 'SHA réduit',
|
||||
abbreviation: 'Abréviation',
|
||||
adjective: 'Adjectif',
|
||||
noun: 'Nom',
|
||||
verb: 'Verbe',
|
||||
ingverb: 'Ingverb',
|
||||
phrase: 'Phrase',
|
||||
avatar: 'Avatar',
|
||||
email: 'Email',
|
||||
exampleEmail: 'Exemple d\'email',
|
||||
userName: 'Nom d\'utilisateur',
|
||||
email: 'mail',
|
||||
exampleEmail: 'Exemple de mail',
|
||||
userName: 'Nom utilisateur',
|
||||
protocol: 'Protocole',
|
||||
url: 'Url',
|
||||
domainName: 'Nom de domaine',
|
||||
domainSuffix: 'Suffixe du nom de domaine',
|
||||
domainWord: 'Mot de domaine',
|
||||
domainName: 'Nom du domaine',
|
||||
domainSuffix: 'Suffixe du domaine',
|
||||
domainWord: 'Mot domaine',
|
||||
ip: 'Ip',
|
||||
ipv6: 'Ipv6',
|
||||
userAgent: 'User agent',
|
||||
userAgent: 'Agent utilisateur',
|
||||
mac: 'Mac',
|
||||
password: 'Mot de passe',
|
||||
word: 'Mot',
|
||||
words: 'Mots',
|
||||
sentence: 'Phrase',
|
||||
slug: 'Slug',
|
||||
slug: 'Identifiant',
|
||||
sentences: 'Phrases',
|
||||
paragraph: 'Paragraphe',
|
||||
paragraphs: 'Paragraphes',
|
||||
@@ -310,45 +412,45 @@ module.exports = {
|
||||
lines: 'Lignes',
|
||||
genre: 'Genre',
|
||||
firstName: 'Prénom',
|
||||
lastName: 'Nom',
|
||||
lastName: 'Nom de famille',
|
||||
middleName: 'Deuxième prénom',
|
||||
findName: 'Nom et prénom',
|
||||
jobTitle: 'Intitulé du poste',
|
||||
gender: 'Genre',
|
||||
findName: 'Nom complet',
|
||||
jobTitle: 'Nom du poste',
|
||||
gender: 'Sexe',
|
||||
prefix: 'Préfixe',
|
||||
suffix: 'Suffixe',
|
||||
title: 'Titre',
|
||||
jobDescriptor: 'Descripteur de poste',
|
||||
jobArea: 'Domaine d\'activité',
|
||||
jobType: 'Type de poste',
|
||||
title: 'Fonction',
|
||||
jobDescriptor: 'Desciption du travail',
|
||||
jobArea: 'Zone du travail',
|
||||
jobType: 'Type de travail',
|
||||
phoneNumber: 'Numéro de téléphone',
|
||||
phoneNumberFormat: 'Format du numéro de téléphone',
|
||||
phoneFormats: 'Formats de téléphone',
|
||||
number: 'Numéro',
|
||||
float: 'Nombre décimaux',
|
||||
arrayElement: 'Élément de Liste',
|
||||
arrayElements: 'Éléments de liste',
|
||||
objectElement: 'Élément d\'objet',
|
||||
phoneNumberFormat: 'Format du Numéro de téléphone',
|
||||
phoneFormats: 'Formats téléphone',
|
||||
number: 'Nombre',
|
||||
float: 'décimal',
|
||||
arrayElement: 'élément du tableau',
|
||||
arrayElements: 'éléments du tableau',
|
||||
objectElement: 'élément de l\'object',
|
||||
uuid: 'Uuid',
|
||||
boolean: 'Boolean',
|
||||
boolean: 'Booléen',
|
||||
image: 'Image',
|
||||
locale: 'Locale',
|
||||
alpha: 'Alpha',
|
||||
alphaNumeric: 'Alphanumerique',
|
||||
hexaDecimal: 'Hexadecimale',
|
||||
fileName: 'Nom deu fichier',
|
||||
hexaDecimal: 'Hexadécimal',
|
||||
fileName: 'Nom du fichier',
|
||||
commonFileName: 'Nom de fichier commun',
|
||||
mimeType: 'Mime type',
|
||||
commonFileType: 'Type de dossier commun',
|
||||
mimeType: 'Type Mime',
|
||||
commonFileType: 'Type de fichier commun',
|
||||
commonFileExt: 'Extension de fichier commun',
|
||||
fileType: 'Type de fichier',
|
||||
fileExt: 'Extension de fichier',
|
||||
directoryPath: 'Chemin du répertoire',
|
||||
filePath: 'Chemin d\'accès au fichier',
|
||||
filePath: 'Chemin du fichier',
|
||||
semver: 'Semver',
|
||||
manufacturer: 'Fabricant',
|
||||
manufacturer: 'Constructeur',
|
||||
model: 'Modèle',
|
||||
fuel: 'Carburant',
|
||||
vin: 'Vin'
|
||||
fuel: 'Essence',
|
||||
vin: 'Plaque d\'imatriculation'
|
||||
}
|
||||
};
|
||||
|
@@ -80,6 +80,7 @@ module.exports = {
|
||||
deterministic: 'Deterministico',
|
||||
context: 'Contesto',
|
||||
export: 'Esporta',
|
||||
import: 'Importa',
|
||||
returns: 'Ritorna',
|
||||
timing: 'Temporizzazione',
|
||||
state: 'Stato',
|
||||
@@ -122,6 +123,16 @@ module.exports = {
|
||||
select: 'Seleziona',
|
||||
passphrase: 'Passphrase',
|
||||
filter: 'Filtra',
|
||||
change: 'Cambia',
|
||||
views: 'Viste',
|
||||
triggers: 'Trigger',
|
||||
routines: 'Routine',
|
||||
functions: 'Function',
|
||||
schedulers: 'Scheduler',
|
||||
includes: 'Includi',
|
||||
drop: 'Drop',
|
||||
completed: 'Completato',
|
||||
aborted: 'Annullato',
|
||||
disabled: 'Disabilitato',
|
||||
enable: 'Abilita',
|
||||
disable: 'Disabilita'
|
||||
@@ -237,6 +248,15 @@ module.exports = {
|
||||
noOpenTabs: 'Non ci sono tab aperte, naviga nella barra sinistra o:',
|
||||
noSchema: 'Nessuno schema',
|
||||
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',
|
||||
thereAreNoTableFields: 'Non ci sono campi della tabella',
|
||||
newTable: 'Nuova tabella',
|
||||
@@ -251,7 +271,9 @@ module.exports = {
|
||||
killProcess: 'Uccidi processo',
|
||||
closeTab: 'Chiudi tab',
|
||||
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: {
|
||||
address: 'Indirizzo',
|
||||
|
@@ -106,7 +106,39 @@ module.exports = {
|
||||
array: 'Array',
|
||||
changelog: 'Logs de alteração',
|
||||
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: {
|
||||
appWelcome: 'Bem vindo ao Antares SQL Client!',
|
||||
@@ -212,7 +244,48 @@ module.exports = {
|
||||
deleteSchema: 'Apagar schema',
|
||||
markdownSupported: 'Markdown suportado',
|
||||
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: {
|
||||
address: 'Endereço',
|
||||
|
@@ -5,4 +5,12 @@ export default class {
|
||||
static getKey (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';
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
import connStringConstruct from '../libs/connStringDecode';
|
||||
|
||||
export default class {
|
||||
static makeTest (params) {
|
||||
params = connStringConstruct(params);
|
||||
return ipcRenderer.invoke('test-connection', params);
|
||||
}
|
||||
|
||||
static checkConnection (params) {
|
||||
return ipcRenderer.invoke('check-connection', params);
|
||||
static connect (params) {
|
||||
params = connStringConstruct(params);
|
||||
return ipcRenderer.invoke('connect', params);
|
||||
}
|
||||
|
||||
static connect (params) {
|
||||
return ipcRenderer.invoke('connect', params);
|
||||
static checkConnection (uid) {
|
||||
return ipcRenderer.invoke('check-connection', uid);
|
||||
}
|
||||
|
||||
static disconnect (uid) {
|
||||
|
@@ -50,6 +50,18 @@ export default class {
|
||||
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) {
|
||||
return ipcRenderer.invoke('use-schema', params);
|
||||
}
|
||||
@@ -57,4 +69,20 @@ export default class {
|
||||
static rawQuery (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
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;
|
@@ -165,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 {
|
||||
|
@@ -6,7 +6,7 @@ export default {
|
||||
namespaced: true,
|
||||
strict: true,
|
||||
state: {
|
||||
notes: persistentStore.get('notes', '# HOW TO SUPPORT ANTARES\n\n- [ ] Leave a star to Antares [GitHub repo](https://github.com/Fabio286/antares)\n- [ ] Send feedbacks and advices\n- [ ] Report for bugs\n- [ ] If you enjoy, share Antares with friends\n\n# ABOUT SCRATCHPAD\n\nThis is a scratchpad where you can save your **personal notes**. It supports `markdown` format, but you are free to use plain text.\nThis content is just a placeholder, feel free to clear it to make space for your notes.\n')
|
||||
notes: persistentStore.get('notes', '# HOW TO SUPPORT ANTARES\n\n- [ ] Leave a star to Antares [GitHub repo](https://github.com/antares-sql/antares)\n- [ ] Send feedbacks and advices\n- [ ] Report for bugs\n- [ ] If you enjoy, share Antares with friends\n\n# ABOUT SCRATCHPAD\n\nThis is a scratchpad where you can save your **personal notes**. It supports `markdown` format, but you are free to use plain text.\nThis content is just a placeholder, feel free to clear it to make space for your notes.\n')
|
||||
},
|
||||
getters: {
|
||||
getNotes: state => state.notes
|
||||
|
@@ -20,7 +20,8 @@ export default {
|
||||
application_theme: persistentStore.get('application_theme', defaultAppTheme),
|
||||
editor_theme: persistentStore.get('editor_theme', defaultEditorTheme),
|
||||
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: {
|
||||
getLocale: state => state.locale,
|
||||
@@ -33,7 +34,8 @@ export default {
|
||||
getApplicationTheme: state => state.application_theme,
|
||||
getEditorTheme: state => state.editor_theme,
|
||||
getEditorFontSize: state => state.editor_font_size,
|
||||
getRestoreTabs: state => state.restore_tabs
|
||||
getRestoreTabs: state => state.restore_tabs,
|
||||
getDisableBlur: state => state.disable_blur
|
||||
},
|
||||
mutations: {
|
||||
SET_LOCALE (state, locale) {
|
||||
@@ -80,6 +82,10 @@ export default {
|
||||
SET_RESTORE_TABS (state, val) {
|
||||
state.restore_tabs = val;
|
||||
persistentStore.set('restore_tabs', state.restore_tabs);
|
||||
},
|
||||
SET_DISABLE_BLUR (state, val) {
|
||||
state.disable_blur = val;
|
||||
persistentStore.set('disable_blur', state.disable_blur);
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
@@ -115,6 +121,9 @@ export default {
|
||||
},
|
||||
changeRestoreTabs ({ commit }, size) {
|
||||
commit('SET_RESTORE_TABS', size);
|
||||
},
|
||||
changeDisableBlur ({ commit }, val) {
|
||||
commit('SET_DISABLE_BLUR', val);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,25 +1,11 @@
|
||||
const { _electron: electron } = require('playwright');
|
||||
const { strict: assert } = require('assert');
|
||||
|
||||
const isWindows = process.platform === 'win32';
|
||||
async function wait (ms) {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
(async () => {
|
||||
if (isWindows) {
|
||||
console.log('Termporary skipping tests on Windows');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Starting tests');
|
||||
// Launch Electron app.
|
||||
const electronApp = await electron.launch({ args: ['dist/main.js'] });
|
||||
|
||||
if (isWindows) await wait(5000);
|
||||
|
||||
/**
|
||||
* App main window state
|
||||
* @type {{isVisible: boolean; isDevToolsOpened: boolean; isCrashed: boolean}}
|
||||
|
@@ -1,70 +1,72 @@
|
||||
const path = require('path');
|
||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
const webpack = require('webpack');
|
||||
const ProgressPlugin = require('progress-webpack-plugin');
|
||||
|
||||
const { dependencies, devDependencies } = require('./package.json');
|
||||
const { dependencies, devDependencies, version } = require('./package.json');
|
||||
|
||||
const externals = Object.keys(dependencies).concat(Object.keys(devDependencies));
|
||||
const isDevMode = process.env.NODE_ENV === 'development';
|
||||
const whiteListedModules = [];
|
||||
|
||||
module.exports = [
|
||||
{ // Main
|
||||
name: 'main',
|
||||
mode: process.env.NODE_ENV,
|
||||
devtool: isDevMode ? 'eval-source-map' : false,
|
||||
entry: {
|
||||
main: path.join(__dirname, './src/main/main.js')
|
||||
module.exports = { // Main
|
||||
name: 'main',
|
||||
mode: process.env.NODE_ENV,
|
||||
devtool: isDevMode ? 'eval-source-map' : false,
|
||||
entry: {
|
||||
main: path.join(__dirname, './src/main/main.js')
|
||||
},
|
||||
target: 'electron-main',
|
||||
output: {
|
||||
libraryTarget: 'commonjs2',
|
||||
path: path.join(__dirname, 'dist'),
|
||||
filename: '[name].js'
|
||||
},
|
||||
node: {
|
||||
global: true,
|
||||
__dirname: isDevMode,
|
||||
__filename: isDevMode
|
||||
},
|
||||
externals: externals.filter((d) => !whiteListedModules.includes(d)),
|
||||
resolve: {
|
||||
extensions: ['.js', '.json'],
|
||||
alias: {
|
||||
src: path.join(__dirname, 'src/'),
|
||||
common: path.resolve(__dirname, 'src/common')
|
||||
},
|
||||
target: 'electron-main',
|
||||
output: {
|
||||
libraryTarget: 'commonjs2',
|
||||
path: path.join(__dirname, 'dist'),
|
||||
filename: '[name].js'
|
||||
},
|
||||
node: {
|
||||
global: true,
|
||||
__dirname: isDevMode,
|
||||
__filename: isDevMode
|
||||
},
|
||||
externals: externals.filter((d) => !whiteListedModules.includes(d)),
|
||||
resolve: {
|
||||
extensions: ['.js', '.json'],
|
||||
alias: {
|
||||
src: path.join(__dirname, 'src/'),
|
||||
common: path.resolve(__dirname, 'src/common')
|
||||
},
|
||||
fallback: {
|
||||
'pg-native': false,
|
||||
'cpu-features': false,
|
||||
cardinal: false
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new ProgressPlugin(true),
|
||||
new CleanWebpackPlugin({ root: path.join(__dirname, 'dist') })
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.node$/,
|
||||
loader: 'node-loader',
|
||||
options: {
|
||||
name: '[path][name].[ext]'
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader'
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif)$/,
|
||||
use: [{
|
||||
loader: 'file-loader'
|
||||
}]
|
||||
}
|
||||
]
|
||||
fallback: {
|
||||
'pg-native': false,
|
||||
'cpu-features': false,
|
||||
cardinal: false
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new ProgressPlugin(true),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
PACKAGE_VERSION: `"${version}"`
|
||||
}
|
||||
})
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.node$/,
|
||||
loader: 'node-loader',
|
||||
options: {
|
||||
name: '[path][name].[ext]'
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader'
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif)$/,
|
||||
use: [{
|
||||
loader: 'file-loader'
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
};
|
||||
|
@@ -1,3 +1,4 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
@@ -6,6 +7,11 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const ProgressPlugin = require('progress-webpack-plugin');
|
||||
|
||||
const { dependencies, devDependencies, version } = require('./package.json');
|
||||
const { contributors } = JSON.parse(fs.readFileSync('./.all-contributorsrc', 'utf-8'));
|
||||
const parsedContributors = contributors.reduce((acc, c) => {
|
||||
acc.push(c.name);
|
||||
return acc;
|
||||
}, []).join(',');
|
||||
|
||||
const externals = Object.keys(dependencies).concat(Object.keys(devDependencies));
|
||||
const isDevMode = process.env.NODE_ENV === 'development';
|
||||
@@ -64,7 +70,8 @@ const config = {
|
||||
new VueLoaderPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
PACKAGE_VERSION: `"${version}"`
|
||||
PACKAGE_VERSION: `"${version}"`,
|
||||
APP_CONTRIBUTORS: `"${parsedContributors}"`
|
||||
}
|
||||
})
|
||||
],
|
||||
|
81
webpack.workers.config.js
Normal file
81
webpack.workers.config.js
Normal file
@@ -0,0 +1,81 @@
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const ProgressPlugin = require('progress-webpack-plugin');
|
||||
|
||||
const { dependencies, devDependencies, version } = require('./package.json');
|
||||
|
||||
const externals = Object.keys(dependencies).concat(Object.keys(devDependencies));
|
||||
const isDevMode = process.env.NODE_ENV === 'development';
|
||||
const whiteListedModules = [];
|
||||
|
||||
const config = {
|
||||
name: 'workers',
|
||||
mode: process.env.NODE_ENV,
|
||||
devtool: isDevMode ? 'eval-source-map' : false,
|
||||
entry: {
|
||||
exporter: path.join(__dirname, './src/main/workers/exporter.js'),
|
||||
importer: path.join(__dirname, './src/main/workers/importer.js')
|
||||
},
|
||||
target: 'node',
|
||||
output: {
|
||||
libraryTarget: 'commonjs2',
|
||||
path: path.join(__dirname, 'dist'),
|
||||
filename: '[name].js'
|
||||
},
|
||||
node: {
|
||||
global: true,
|
||||
__dirname: isDevMode,
|
||||
__filename: isDevMode
|
||||
},
|
||||
externals: externals.filter((d) => !whiteListedModules.includes(d)),
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: 'babel-loader',
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.node$/,
|
||||
use: 'node-loader'
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.json'],
|
||||
alias: {
|
||||
src: path.join(__dirname, 'src/'),
|
||||
common: path.resolve(__dirname, 'src/common')
|
||||
},
|
||||
fallback: {
|
||||
'pg-native': false,
|
||||
'cpu-features': false,
|
||||
cardinal: false
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new ProgressPlugin(true),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
PACKAGE_VERSION: `"${version}"`
|
||||
}
|
||||
})
|
||||
]
|
||||
};
|
||||
|
||||
/**
|
||||
* Adjust rendererConfig for production settings
|
||||
*/
|
||||
if (isDevMode) {
|
||||
// any dev only config
|
||||
config.plugins.push(new webpack.HotModuleReplacementPlugin());
|
||||
}
|
||||
else {
|
||||
config.plugins.push(
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
minimize: true
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = config;
|
Reference in New Issue
Block a user