1
1
mirror of https://github.com/Fabio286/antares.git synced 2025-06-05 21:59:22 +02:00

Compare commits

..

1 Commits

Author SHA1 Message Date
76dc66c14d chore(release): 0.3.10 2021-11-24 17:26:42 +01:00
249 changed files with 16889 additions and 37986 deletions

View File

@@ -1,6 +1,6 @@
{
"projectName": "antares",
"projectOwner": "antares-sql",
"projectOwner": "Fabio286",
"repoType": "github",
"repoHost": "https://github.com",
"files": [
@@ -120,60 +120,6 @@
"contributions": [
"code"
]
},
{
"login": "wenj91",
"name": "文杰",
"avatar_url": "https://avatars.githubusercontent.com/u/12549338?v=4",
"profile": "https://github.com/wenj91",
"contributions": [
"code"
]
},
{
"login": "goYou",
"name": "goYou",
"avatar_url": "https://avatars.githubusercontent.com/u/62732795?v=4",
"profile": "https://github.com/goYou",
"contributions": [
"translation"
]
},
{
"login": "raliqala",
"name": "Topollo",
"avatar_url": "https://avatars.githubusercontent.com/u/30502407?v=4",
"profile": "https://github.com/raliqala",
"contributions": [
"code"
]
},
{
"login": "SmileYzn",
"name": "Cleverson",
"avatar_url": "https://avatars.githubusercontent.com/u/5851851?v=4",
"profile": "https://github.com/SmileYzn",
"contributions": [
"translation"
]
},
{
"login": "fredatgithub",
"name": "fred",
"avatar_url": "https://avatars.githubusercontent.com/u/6720055?v=4",
"profile": "https://github.com/fredatgithub",
"contributions": [
"translation"
]
},
{
"login": "xak666",
"name": "xaka_xak",
"avatar_url": "https://avatars.githubusercontent.com/u/38811437?v=4",
"profile": "https://github.com/xak666",
"contributions": [
"translation"
]
}
],
"contributorsPerLine": 7,

View File

@@ -1,5 +1,4 @@
node_modules
assets
out
dist
build
dist

View File

@@ -6,23 +6,15 @@
},
"extends": [
"standard",
"plugin:@typescript-eslint/recommended",
"plugin:vue/vue3-recommended"
"plugin:vue/recommended"
],
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser",
"parser": "@babel/eslint-parser",
"ecmaVersion": 9,
"sourceType": "module",
"requireConfigFile": false
},
"plugins": [
"vue",
"@typescript-eslint"
],
"rules": {
"space-infix-ops": "off",
"object-curly-newline": "off",
"indent": [
"error",
3,
@@ -77,20 +69,6 @@
"max": 1
}
}
],
"@typescript-eslint/member-delimiter-style": [
"warn",
{
"multiline": {
"delimiter": "semi",
"requireLast": true
},
"singleline": {
"delimiter": "semi",
"requireLast": false
}
}
],
"@typescript-eslint/no-var-requires": "off"
]
}
}

View File

@@ -26,16 +26,13 @@ If applicable, add screenshots to help explain your problem.
**Application (please complete the following information):**
- App client [e.g. MySQL]
- App version [e.g. 0.5.2]
- Installation source: [e.g. exe, Linux Store, AppImage, dmg]
- Version [e.g. 0.14.0]
- Distribution: [e.g. exe, Linux Store, AppImage, dmg]
**Environment (please complete the following information):**
**Desktop (please complete the following information):**
- OS name: [e.g. Windows 11]
- OS version [e.g. 21H2]
- DB name [e.g. MariaDB]
- DB version [e.g. 10.3.34]
- OS: [e.g. iOS]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -8,4 +8,4 @@ updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "monthly"
interval: "weekly"

View File

@@ -1,4 +1,4 @@
name: Build/release [LINUX]
name: Build/release [linux]
on: push
@@ -12,15 +12,12 @@ jobs:
steps:
- name: Check out Git repository
uses: actions/checkout@v2
uses: actions/checkout@v1
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1
with:
node-version: 14
- name: Install dependencies
run: npm i
- name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1

View File

@@ -1,4 +1,4 @@
name: Build/release [MAC]
name: Build/release [mac]
on: push
@@ -12,16 +12,13 @@ jobs:
steps:
- name: Check out Git repository
uses: actions/checkout@v2
uses: actions/checkout@v1
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1
with:
node-version: 14
- name: Install dependencies
run: npm i
- name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1
with:

View File

@@ -1,4 +1,4 @@
name: Build/release [WINDOWS]
name: Build/release [windows]
on: push
@@ -8,20 +8,17 @@ jobs:
strategy:
matrix:
os: [windows-2019]
os: [windows-latest]
steps:
- name: Check out Git repository
uses: actions/checkout@v2
uses: actions/checkout@v1
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1
with:
node-version: 14
- name: Install dependencies
run: npm i
- name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1
with:

View File

@@ -1,26 +0,0 @@
name: Create artifact [LINUX]
on:
workflow_dispatch: {}
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v3
- name: npm install & build
run: |
npm install
npm run build:local
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: linux-build
retention-days: 3
path: |
build
!build/*-unpacked
!build/.icon-ico

View File

@@ -1,26 +0,0 @@
name: Test end-to-end [LINUX]
on: push
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
steps:
- name: Check out Git repository
uses: actions/checkout@v2
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1
with:
node-version: 14
- name: Install dependencies
run: npm i
- name: Run tests
run: npm run test:e2e

4
.gitignore vendored
View File

@@ -1,10 +1,8 @@
.DS_Store
dist
build
misc/*
!misc/.gitkeep
node_modules
thumbs.db
NOTES.md
*.txt
*.heapsnapshot
package-lock.json

12
.vscode/launch.json vendored
View File

@@ -2,8 +2,8 @@
"version": "0.2.0",
"configurations": [
{
"name": "Electron: Main",
"cwd": "${workspaceFolder}",
"name": "Electron: Main",
"port": 9222,
"protocol": "inspector",
"request": "attach",
@@ -18,16 +18,6 @@
"sourceMaps": true,
"type": "chrome",
"webRoot": "${workspaceFolder}"
},
{
"name": "Electron: Worker",
"cwd": "${workspaceFolder}",
"port": 9224,
"protocol": "inspector",
"request": "attach",
"sourceMaps": true,
"type": "node",
"timeout": 1000000
}
],
"compounds": [

View File

@@ -4,10 +4,7 @@
"core",
"MySQL",
"PostgreSQL",
"SQLite",
"Windows",
"translation",
"Linux"
"SQLite"
],
"svg.preview.background": "transparent"
}

View File

@@ -2,398 +2,7 @@
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.11](https://github.com/antares-sql/antares/compare/v0.5.10...v0.5.11) (2022-07-19)
### Bug Fixes
* console events disabled in production ([0b1aa3d](https://github.com/antares-sql/antares/commit/0b1aa3dd299db641df3d4c56c7ee56a187fc3ab3))
* filter persists switching temporary table tabs ([bf768c3](https://github.com/antares-sql/antares/commit/bf768c380087b65604b5b571a9858a7f07bd681d))
* unable to edit table fields content on tables with datetime fields ([91e0630](https://github.com/antares-sql/antares/commit/91e06305133c97ea02dcfdc4e739a4b0a7e7049d))
### [0.5.10](https://github.com/antares-sql/antares/compare/v0.5.9...v0.5.10) (2022-07-18)
### Features
* context menu to copy queries from console ([c21bd60](https://github.com/antares-sql/antares/commit/c21bd6075c1203607c05e45b76233d57e3008190))
* Ctrl+PgUp & Ctrl+PgDn to navigate between tabs ([abf8298](https://github.com/antares-sql/antares/commit/abf829867e567354e534cff3e02a6d43f4c7a262))
* field names suggestion for tables in the query ([b71f04e](https://github.com/antares-sql/antares/commit/b71f04e5aa3c37eaa160dfbc76d1b84789e3543e))
* initial console implementation ([6a6f43a](https://github.com/antares-sql/antares/commit/6a6f43a718561e0abd2cb89048b7fe45d08736ae))
* ipc event channel to send logs to renderer ([f12a04b](https://github.com/antares-sql/antares/commit/f12a04b0524f1172334c89afeb27675c19ff68d2))
* open/close console on single connection ([44647f5](https://github.com/antares-sql/antares/commit/44647f5b5508965bf5a7264add89e175c725e877))
### Bug Fixes
* exception on QueryEditor with null modelValue ([9bc9adb](https://github.com/antares-sql/antares/commit/9bc9adb7cff19b86a99491d968485a4cd7b47b99))
* fields content language detection not working properly ([a91fa8f](https://github.com/antares-sql/antares/commit/a91fa8ff54bbf1f8475666efd3a268a3a4f07f0c))
* **Linux:** ctrl+space shortcut not working ([ed3d35f](https://github.com/antares-sql/antares/commit/ed3d35f1319a1e2edcb8104f2045a71b9e9754a2))
* unable to delete by select all in left bar search, closes [#368](https://github.com/antares-sql/antares/issues/368) ([7725faf](https://github.com/antares-sql/antares/commit/7725fafe852479720fa619ced0970f2fa0099191))
* unable to update data on tables missing primary or unique key ([e0946f0](https://github.com/antares-sql/antares/commit/e0946f04f792d25c187ea56d4714bdacc016ada3))
### Improvements
* improved resize of text editor resizing console height ([3f9e6d8](https://github.com/antares-sql/antares/commit/3f9e6d85ca445eea1028effa32418eee4980f87d))
* **UI:** improved visibility of explore bar tooltips ([f312cf5](https://github.com/antares-sql/antares/commit/f312cf5f855deddd562c26d1835f78d16499b93b))
### [0.5.9](https://github.com/antares-sql/antares/compare/v0.5.8...v0.5.9) (2022-07-06)
### Features
* ability to pin/unpin and delete connections from the "all connections" modal ([8e70570](https://github.com/antares-sql/antares/commit/8e705706aecc5c9790329e63e61a1c02fa5d0342))
* connections sorted by last usage by default and option to pin them ([36e98e0](https://github.com/antares-sql/antares/commit/36e98e0742657e25df7768aa5b3b7cb350df5509))
* ctrl/cmd+space to open all connections modal ([a9a4344](https://github.com/antares-sql/antares/commit/a9a4344a71cc0f8f156b839733f6ddc200a26268))
* modal with all connections ([a703dcc](https://github.com/antares-sql/antares/commit/a703dcc53eb920117bc346a3c21f0c729c0ad96d))
* option to disable scratchpad ([56b0a48](https://github.com/antares-sql/antares/commit/56b0a4815c6f54eef164d849f6ca25af1e142b16))
* search form in all connections modal ([ec5ab73](https://github.com/antares-sql/antares/commit/ec5ab73b19d99e9971ae87e5f0a8d1bd1c34ef00))
### Bug Fixes
* error on export schema ([cf9c7c6](https://github.com/antares-sql/antares/commit/cf9c7c600aa915cef1ec3777866badb7ab1312ee))
* missing option for untrusted ssl connection on connections edit panel ([71a5b5c](https://github.com/antares-sql/antares/commit/71a5b5c8285fb777c43e7f6516006bfe9f52591c))
### Improvements
* **UI:** improved focus visibility for buttons ([d2eb31a](https://github.com/antares-sql/antares/commit/d2eb31a63d612323f8738eded1e1ce7b23554001))
### [0.5.8](https://github.com/antares-sql/antares/compare/v0.5.7...v0.5.8) (2022-07-02)
### Features
* add max visible options prop ([067a6f3](https://github.com/antares-sql/antares/commit/067a6f350757c1e6b4df51f801ae832b47bd3484))
* context shortcut to disconnect from left bar ([e97da37](https://github.com/antares-sql/antares/commit/e97da3710385690b85391938e40145a1591bc2e8))
* **MySQL:** option to disable foreign key checks when empty a table ([902c29f](https://github.com/antares-sql/antares/commit/902c29ffa551bc3489fa1d9136ee926d135ea14f))
### Bug Fixes
* connection string field doesn't appear switching to postgre when editing a connection ([6573fe6](https://github.com/antares-sql/antares/commit/6573fe69aca2b99c7a700879fb0d0930e864cbe6))
* ctrl+a on results doesn't work properly ([5f57a9f](https://github.com/antares-sql/antares/commit/5f57a9f60d281e24e5bee4330c081fa5d8651b36))
* double context menu on table settings rows ([91d0735](https://github.com/antares-sql/antares/commit/91d0735a5f4861bc6ad13b9285ea7a9bd7be9538))
* editor gutter pin not working ([cfd82c8](https://github.com/antares-sql/antares/commit/cfd82c8f419952879b386187eb146847098263fe))
* error on modals missing focusable elements ([7702ca0](https://github.com/antares-sql/antares/commit/7702ca025fcae6209ae3851d0ccd25579f93e243))
* exception on new scheduler tab ([a45d76e](https://github.com/antares-sql/antares/commit/a45d76e8b4ecdecf53438fe174f61ea32f4e10ac))
* focus goes outside modals with tab key navigation ([e42c424](https://github.com/antares-sql/antares/commit/e42c424a13a6901414a1a1c4e2f68cb4ddef7d59))
* reactivity problem on BaseVirtualScroll component ([45b2eb2](https://github.com/antares-sql/antares/commit/45b2eb2934b9f7a08f379ad4d7a44b1c89585449))
* result table cells/rows not loses focus clicking outside ([0a3a482](https://github.com/antares-sql/antares/commit/0a3a4827dd75539666fa2c827415af3bfa224543))
* **UI:** wrong tables scrollable height after switching tabs ([8f01740](https://github.com/antares-sql/antares/commit/8f01740475ea6d5d9b5eefabdbf27099df76f2cf))
* **Windows:** white window buttons with dark theme ([a80d227](https://github.com/antares-sql/antares/commit/a80d22740045a61fd14fd5da401c0d123d54f4de))
* **Windows:** Windows 7 style window frame at startup ([93ce619](https://github.com/antares-sql/antares/commit/93ce619782d58cfb8fb1ecce2ca2137a61ec6181))
### [0.5.7](https://github.com/antares-sql/antares/compare/v0.5.4...v0.5.7) (2022-06-19)
### Features
* added dropdown animation ([5398964](https://github.com/antares-sql/antares/commit/539896419064db9127f6a72acdbb11af2c4aa60a))
* dynamic app window title ([0024269](https://github.com/antares-sql/antares/commit/00242697a102f82dd0c731a3529c984fbdf83b3e))
* hotkeys to navigate forward or backward between tabs ([d3b9e08](https://github.com/antares-sql/antares/commit/d3b9e08446708654b3c6fad565b734d93effe683))
* hotkeys to navigate inside a table resultset ([49abd1e](https://github.com/antares-sql/antares/commit/49abd1ea7f5ec368e9a9201f8fd5b6520c4bd0a8))
* **translation:** russian translation, closes [#266](https://github.com/antares-sql/antares/issues/266) ([9082960](https://github.com/antares-sql/antares/commit/9082960310573a6e4d14bfbe82ed2eb1489f308d))
* **UI:** BaseSelect disabled state ([2b436d8](https://github.com/antares-sql/antares/commit/2b436d8613a1e3dff55d73adbddf5d2cd2452f27))
* **UI:** BaseSelect in table filters ([a037d0c](https://github.com/antares-sql/antares/commit/a037d0cc0148444e8e6c5b87c79f6ba9c2a6f0fe))
* **UI:** BaseSelect option list scrolls automatically using up/down keys ([0043d07](https://github.com/antares-sql/antares/commit/0043d077081fc49724722a5d5a74986d990c539d))
* **UI:** BaseSelect small variant ([5582a12](https://github.com/antares-sql/antares/commit/5582a12bbfade75dbcc7f9d71ada7190ed08d3c2))
* **UI:** BaseSelect supports disabled options ([f7e04d6](https://github.com/antares-sql/antares/commit/f7e04d633340a53420ce1c434e906c9434620e6e))
* **UI:** BaseSelect supports option groups ([1869e6a](https://github.com/antares-sql/antares/commit/1869e6a1482daf9381d9ac2244bf0aeffa758edc))
* **UI:** ForeignKeySelect implements BaseSelect component ([302c664](https://github.com/antares-sql/antares/commit/302c66457deeb50facf4735291640fcf48b78f66))
* **UI:** initial BaseSelect integration ([22622df](https://github.com/antares-sql/antares/commit/22622df2cfcb71054c6f6110b7ad9d4f635553dc))
* **UI:** new BaseSelect component ([745d551](https://github.com/antares-sql/antares/commit/745d551cc9253eae4e39e5d3406ceee051a7d6c1))
* **UI:** select tab replace with BaseSelect component ([42bc919](https://github.com/antares-sql/antares/commit/42bc9196ffc2f64b77f9cb42136255fc74815034))
### Bug Fixes
* empty query tab schema select if no schema selected ([31b7999](https://github.com/antares-sql/antares/commit/31b7999bba5d115913d42087614b9888bc761068))
* exception on app start setting window title ([5b33419](https://github.com/antares-sql/antares/commit/5b33419b6421d7d198a978e79e22d0a76306cdb4))
* fields sorting in table setting tabs ([77d9cac](https://github.com/antares-sql/antares/commit/77d9cac092fbb806810c3463ca066395fcab5307))
* inline field update not working with tables missing primary key ([caf776b](https://github.com/antares-sql/antares/commit/caf776bd55606c793c9763c204aa9f05d1feb27f))
* **Linux:** setting bar tooltip position ([6bad032](https://github.com/antares-sql/antares/commit/6bad032f0d1094736f651b6c06a60d2a0df36c98))
* main process not closed after window close on some conditions ([23acf00](https://github.com/antares-sql/antares/commit/23acf00def77b5662e48b84591a31760737774a7))
* **PostgreSQL:** idle timeout disabled ([a082514](https://github.com/antares-sql/antares/commit/a082514f88040c7e0ffdf4e8357bab45370a4c39))
* query tab content disappears reordering or closing other tabs, closes [#261](https://github.com/antares-sql/antares/issues/261) ([c5baf2b](https://github.com/antares-sql/antares/commit/c5baf2b0d379fdd28ee8cb907628bbfca940e2f6))
* reload tab content on tab sort ([d214c1f](https://github.com/antares-sql/antares/commit/d214c1f35ba231a8a01dbe8c0faad07d4b337752))
* selected foreign key value not visible in the insert row modal ([cba2ce2](https://github.com/antares-sql/antares/commit/cba2ce2e37cedbf0b242cc474b37bf052009ae62))
* **SQLite:** unable to insert rows with TEXT fields ([a7d5e19](https://github.com/antares-sql/antares/commit/a7d5e1973cd59d7d0ef1e74bdcf44d87fba43559))
* SSH tunnel connection error with private key, closes [#260](https://github.com/antares-sql/antares/issues/260) ([c826888](https://github.com/antares-sql/antares/commit/c826888b0dd0908958a4f727ddfa642e846269cf))
* **UI:** BaseSelect keyboard navigation ([7c45203](https://github.com/antares-sql/antares/commit/7c452036368fa0db6b9cde7c35e60a8e57bfece7))
* **UI:** BaseSelect style ([71b0736](https://github.com/antares-sql/antares/commit/71b0736d0ddbd599ab41cde0a6b0823e2bb7da2f))
* **UI:** select closes clicking on scrollbar ([8870304](https://github.com/antares-sql/antares/commit/8870304c15346257a90193807b9ae07c1393e3e2))
* unable to add new table fields ([ee623b0](https://github.com/antares-sql/antares/commit/ee623b0a0f121df0ac53d49d8be437c76ddb8539))
### Improvements
* improved precision of MariaDB or MySQL auto detection ([26aad51](https://github.com/antares-sql/antares/commit/26aad519df6ea1bbc7dffbf540193a7b2ed9ae2a))
* **Linux:** title bar improvements ([85cec05](https://github.com/antares-sql/antares/commit/85cec05f7037a1339ee223554cf127693a527aa1))
* **UI:** max height for query text area increased ([5d5f1da](https://github.com/antares-sql/antares/commit/5d5f1da97b9adfa743197d8fa0bbb6addd565a7a))
* **Windows:** title bar improvements ([5fa8bf3](https://github.com/antares-sql/antares/commit/5fa8bf38e433ef2fb31bcb893cd9e75549bd6a49))
### [0.5.6](https://github.com/antares-sql/antares/compare/v0.5.4...v0.5.6) (2022-06-02)
### Bug Fixes
* empty query tab schema select if no schema selected ([31b7999](https://github.com/antares-sql/antares/commit/31b7999bba5d115913d42087614b9888bc761068))
* inline field update not working with tables missing primary key ([caf776b](https://github.com/antares-sql/antares/commit/caf776bd55606c793c9763c204aa9f05d1feb27f))
* **SQLite:** unable to insert rows with TEXT fields ([a7d5e19](https://github.com/antares-sql/antares/commit/a7d5e1973cd59d7d0ef1e74bdcf44d87fba43559))
* **UI:** select closes clicking on scrollbar ([8870304](https://github.com/antares-sql/antares/commit/8870304c15346257a90193807b9ae07c1393e3e2))
### Improvements
* improved precision of MariaDB or MySQL auto detection ([26aad51](https://github.com/antares-sql/antares/commit/26aad519df6ea1bbc7dffbf540193a7b2ed9ae2a))
### [0.5.5](https://github.com/antares-sql/antares/compare/v0.5.4...v0.5.5) (2022-05-24)
### Features
* added dropdown animation ([5398964](https://github.com/antares-sql/antares/commit/539896419064db9127f6a72acdbb11af2c4aa60a))
* **translation:** russian translation, closes [#266](https://github.com/antares-sql/antares/issues/266) ([9082960](https://github.com/antares-sql/antares/commit/9082960310573a6e4d14bfbe82ed2eb1489f308d))
* **UI:** BaseSelect disabled state ([2b436d8](https://github.com/antares-sql/antares/commit/2b436d8613a1e3dff55d73adbddf5d2cd2452f27))
* **UI:** BaseSelect in table filters ([a037d0c](https://github.com/antares-sql/antares/commit/a037d0cc0148444e8e6c5b87c79f6ba9c2a6f0fe))
* **UI:** BaseSelect option list scrolls automatically using up/down keys ([0043d07](https://github.com/antares-sql/antares/commit/0043d077081fc49724722a5d5a74986d990c539d))
* **UI:** BaseSelect small variant ([5582a12](https://github.com/antares-sql/antares/commit/5582a12bbfade75dbcc7f9d71ada7190ed08d3c2))
* **UI:** BaseSelect supports disabled options ([f7e04d6](https://github.com/antares-sql/antares/commit/f7e04d633340a53420ce1c434e906c9434620e6e))
* **UI:** BaseSelect supports option groups ([1869e6a](https://github.com/antares-sql/antares/commit/1869e6a1482daf9381d9ac2244bf0aeffa758edc))
* **UI:** ForeignKeySelect implements BaseSelect component ([302c664](https://github.com/antares-sql/antares/commit/302c66457deeb50facf4735291640fcf48b78f66))
* **UI:** initial BaseSelect integration ([22622df](https://github.com/antares-sql/antares/commit/22622df2cfcb71054c6f6110b7ad9d4f635553dc))
* **UI:** new BaseSelect component ([745d551](https://github.com/antares-sql/antares/commit/745d551cc9253eae4e39e5d3406ceee051a7d6c1))
* **UI:** select tab replace with BaseSelect component ([42bc919](https://github.com/antares-sql/antares/commit/42bc9196ffc2f64b77f9cb42136255fc74815034))
### Bug Fixes
* **PostgreSQL:** idle timeout disabled ([a082514](https://github.com/antares-sql/antares/commit/a082514f88040c7e0ffdf4e8357bab45370a4c39))
* query tab content disappears reordering or closing other tabs, closes [#261](https://github.com/antares-sql/antares/issues/261) ([c5baf2b](https://github.com/antares-sql/antares/commit/c5baf2b0d379fdd28ee8cb907628bbfca940e2f6))
* SSH tunnel connection error with private key, closes [#260](https://github.com/antares-sql/antares/issues/260) ([c826888](https://github.com/antares-sql/antares/commit/c826888b0dd0908958a4f727ddfa642e846269cf))
* **UI:** BaseSelect keyboard navigation ([7c45203](https://github.com/antares-sql/antares/commit/7c452036368fa0db6b9cde7c35e60a8e57bfece7))
* **UI:** BaseSelect style ([71b0736](https://github.com/antares-sql/antares/commit/71b0736d0ddbd599ab41cde0a6b0823e2bb7da2f))
### Improvements
* **UI:** max height for query text area increased ([5d5f1da](https://github.com/antares-sql/antares/commit/5d5f1da97b9adfa743197d8fa0bbb6addd565a7a))
### [0.5.4](https://github.com/antares-sql/antares/compare/v0.5.3...v0.5.4) (2022-05-10)
### Bug Fixes
* app blocked by BIT fields with no default, closes [#256](https://github.com/antares-sql/antares/issues/256) ([e62f280](https://github.com/antares-sql/antares/commit/e62f280528edb0ff4550ee75038ea216e81e4f10))
* file upload input not working ([58611bf](https://github.com/antares-sql/antares/commit/58611bf07f343e6899a7446bfcd1247b0c75fc7f))
* SSH tunnel not working ([6d61518](https://github.com/antares-sql/antares/commit/6d6151814e5006935d493b9b83dbda1aa5b35391))
* unable to insert auto-generated datetime fields ([ff27244](https://github.com/antares-sql/antares/commit/ff272440bdc2a7fe699e04f8809bd5af8f9529c0))
### Improvements
* **UI:** left alignment for numbers in result tables, closes [#249](https://github.com/antares-sql/antares/issues/249) ([e02565c](https://github.com/antares-sql/antares/commit/e02565c0d9bb63efa76a79f38e3ed3586a30ad1c))
### [0.5.3](https://github.com/antares-sql/antares/compare/v0.5.2...v0.5.3) (2022-05-08)
### Features
* default open schema if only one present ([0fa22fb](https://github.com/antares-sql/antares/commit/0fa22fbe72fd4e3b2cfff6fc1847347f69fafda6))
### Bug Fixes
* can not use vue teleport with props as root element ([88eb9f7](https://github.com/antares-sql/antares/commit/88eb9f7ab80c407ec6517206c27cce715c41eb24))
* importer webpack config ([f82db96](https://github.com/antares-sql/antares/commit/f82db96f34579c5f5f0c025ba82aff13047eb045))
* importer webpack config ([b4d9821](https://github.com/antares-sql/antares/commit/b4d9821300991bf000846ae38ed18d79564db67b))
* LIKE operator in table filter not properly working, closes [#242](https://github.com/antares-sql/antares/issues/242) ([eb1afed](https://github.com/antares-sql/antares/commit/eb1afed108d97e2acb7ea5279aabd582014c3c58))
* locale change ([6af0c33](https://github.com/antares-sql/antares/commit/6af0c33461dfab868bca757e55ae62e6c6094c6c))
* missing storeToRefs imports ([ff8bb45](https://github.com/antares-sql/antares/commit/ff8bb45d6d19303b2e7701639d22e52e4ae2976c))
* missing tabs uid ([18b66b5](https://github.com/antares-sql/antares/commit/18b66b50323e69ed9d0090a4cbbe0ada9e10838d))
* **MySQL:** creating temporary tables on export to overcome view dependency errors ([e4c5d9b](https://github.com/antares-sql/antares/commit/e4c5d9b404c2b20cf113607a80ee928b810e6f05))
* **MySQL:** support to ANSI_QUOTES mode not working, closes [#243](https://github.com/antares-sql/antares/issues/243) ([9fcc7d1](https://github.com/antares-sql/antares/commit/9fcc7d1cefc3c035a63e843616c3138c772037f1))
* prevents query tabs targets previous schema if not in explorebar, closes [#236](https://github.com/antares-sql/antares/issues/236) ([5e4abd3](https://github.com/antares-sql/antares/commit/5e4abd3e81d70bcb9b3a897d38d6f8ec3ff0706b))
* query editor table fields suggestion ([e9dedfa](https://github.com/antares-sql/antares/commit/e9dedfaf3203f5621e54fd707003a47e6e5a5c4d))
* **SQLite:** tables with sqlite in name not visible, closes [#239](https://github.com/antares-sql/antares/issues/239) ([690cdcb](https://github.com/antares-sql/antares/commit/690cdcb2eb1c7bf514271000c31211d1c0b33e54))
* unable to add parameters to new routines/functions ([350d536](https://github.com/antares-sql/antares/commit/350d53642e2491fd2dedbbf999579ba9c9bc59a0))
* unable to disconnect from database ([47e1f27](https://github.com/antares-sql/antares/commit/47e1f27bb83b99288157185ad8b62d190a9e7881))
* unable to set an empty string as field default ([bfdb463](https://github.com/antares-sql/antares/commit/bfdb463390453a55963af29ef0e7cd3aea26f4cf))
* undefined uid variable in the setting bar ([3cca3ed](https://github.com/antares-sql/antares/commit/3cca3ed2b31c5763794c52cce34ccba1e848efe2))
* various warnings and exceptions ([c50cbc5](https://github.com/antares-sql/antares/commit/c50cbc577f667c17ba676960cf2c840d24366c80))
* verbose devtool loggin with missing sourcemap ([1174bab](https://github.com/antares-sql/antares/commit/1174bab0cc895c4e294804d810b0789166602725))
* wrong path module importation ([472fa6f](https://github.com/antares-sql/antares/commit/472fa6f4300b7e3fbf8c8079a548ca34d941f5fb))
* wrong path module importation ([9a0f982](https://github.com/antares-sql/antares/commit/9a0f98272342d201e9bc979d5bc5768cfd20f3fb))
### [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)
### Features
* add Simplified Chinese translation ([6ef565c](https://github.com/Fabio286/antares/commit/6ef565cf078cb3f5b7bcdc226894cddeb6239db9))
* **MySQL:** spatial fields support ([#165](https://github.com/Fabio286/antares/issues/165)) ([48ebf23](https://github.com/Fabio286/antares/commit/48ebf23bd1574408f429f2e1200ce878352007f6))
### Bug Fixes
* cell copy returns "undefined" in some conditions, closes [#170](https://github.com/Fabio286/antares/issues/170) ([8fb1f08](https://github.com/Fabio286/antares/commit/8fb1f0803efd9df0b66521e73bb6e1a229cf9691))
* indexes and foreign keys not cleared after deletion of related field, closes [#182](https://github.com/Fabio286/antares/issues/182) ([9f033fb](https://github.com/Fabio286/antares/commit/9f033fb994916b4fb165e81e55e86127ca817791))
* **PostgreSQL:** schema different than public not automatically selected, closes [#172](https://github.com/Fabio286/antares/issues/172) ([46b45c8](https://github.com/Fabio286/antares/commit/46b45c8ab64fb6837a532c4f8342167e4fd794bb))
* scale on numeric fields that doesn't support it ([0cfd793](https://github.com/Fabio286/antares/commit/0cfd7938ee7d607dbad66ae452d0200223a6bab2))
* **Windows:** temporary fix to Windows 7 style frame on app startup, closes [#169](https://github.com/Fabio286/antares/issues/169) ([1356011](https://github.com/Fabio286/antares/commit/1356011ba3b7dd72e12cb252a8787ce48a364fd4))
### Improvements
* support of scale in field's length setting ([eef7c1d](https://github.com/Fabio286/antares/commit/eef7c1dcecc6593ab0e69ed678187a57fe0a4fb6))
### [0.4.2](https://github.com/Fabio286/antares/compare/v0.4.1...v0.4.2) (2022-01-10)
### Features
* **MySQL:** ability to cancel queries ([a59f77f](https://github.com/Fabio286/antares/commit/a59f77f618aea6156fc80fb832d3efcb9848411f))
* **PostgreSQL:** ability to cancel queries ([0c00291](https://github.com/Fabio286/antares/commit/0c002918eb0226f6b3f21ed62117495f86396fb1))
* save window state ([8f9385d](https://github.com/Fabio286/antares/commit/8f9385d50815635d091758ecd5d00884e3297ca0))
* **UI:** textarea autofocus selecting a query tab, closes [#166](https://github.com/Fabio286/antares/issues/166) ([b4545b1](https://github.com/Fabio286/antares/commit/b4545b178f795712c781a3f4fc35eec31b5ad902))
### Bug Fixes
* **SQLite:** exception with some fields ([e7a1858](https://github.com/Fabio286/antares/commit/e7a18580915e7739bfa97948c6a0c4fc90a7e78a))
### Improvements
* hash for foreign key default names ([48c3e6a](https://github.com/Fabio286/antares/commit/48c3e6afc43c51f70a16703f1a71194f43da7a3e))
* **MySQL:** support to ANSI_QUOTES sql_mode, closes [#158](https://github.com/Fabio286/antares/issues/158) ([d9a3eab](https://github.com/Fabio286/antares/commit/d9a3eab015302e9f23112f659658073ab3242191))
### [0.4.1](https://github.com/Fabio286/antares/compare/v0.4.0...v0.4.1) (2021-12-11)
### Features
* language format detection for text fields ([a5fdcc1](https://github.com/Fabio286/antares/commit/a5fdcc1a85aa188ff1b9a15b1a768aced026f360))
### Bug Fixes
* cell disappear on edit in one column tables ([aaa5549](https://github.com/Fabio286/antares/commit/aaa5549609664665bd4513632d621cb249b379c1))
* false positive with Windows Defender ([992a033](https://github.com/Fabio286/antares/commit/992a033cb2bede3d1eb52e19482d810f6692de1e))
* **MySQL:** wrong datetime fields default in table filler in some cases ([8da0224](https://github.com/Fabio286/antares/commit/8da022487650039b7f34a9c86a7bd9045eba65e2))
* **MySQL:** wrong value for fields "on update" in some conditions ([359e14a](https://github.com/Fabio286/antares/commit/359e14a9ebd48f86069ba7762fe00a7056f52d47))
* select all rows with ctrl+a when editing a cell ([35cb7e1](https://github.com/Fabio286/antares/commit/35cb7e1dc48d3a74e9d106cb1a37f454c1b4a4d1))
* **SQLite:** update rows with a text primary key ([d7f1aa9](https://github.com/Fabio286/antares/commit/d7f1aa97af32a4c51fc7022498bd47e15fa08430))
### Improvements
* **UI:** avoid columns size change when editing cells or scrolling results ([813aa32](https://github.com/Fabio286/antares/commit/813aa320d9ab799efea38a7110b7c0bdf7549123))
* **UI:** disable save button in table creation when no fields are added ([e8af2d2](https://github.com/Fabio286/antares/commit/e8af2d24a869f7667c069936648808952d2062ab))
## [0.4.0](https://github.com/Fabio286/antares/compare/v0.3.9...v0.4.0) (2021-11-24)
### [0.3.10](https://github.com/Fabio286/antares/compare/v0.3.9...v0.3.10) (2021-11-24)
### Features

View File

@@ -1,133 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
fabio286@gmail.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

View File

@@ -2,7 +2,7 @@
Antares SQL is an application based on [Electron.js](https://www.electronjs.org/) that uses [Vue.js](https://vuejs.org/) and [Spectre.css](https://picturepan2.github.io/spectre/) as frontend frameworks.
For the build process it takes advantage of [electron-builder](https://www.electron.build/).
This application uses [Pinia🍍](https://pinia.vuejs.org/) as application state manager and [electron-store](https://github.com/sindresorhus/electron-store) to save the various settings on disc.
This application uses [Vuex](https://vuex.vuejs.org/) as application state manager and [electron-store](https://github.com/sindresorhus/electron-store) to save the various settings on disc.
This guide aims to provide useful information and guidelines to everyone wants to contribute with this open-source project.
For every other question related to this project please [contact me](https://github.com/Fabio286).
@@ -14,7 +14,7 @@ The main files of the application are located inside `src` folder and are groupp
This folder contains small libraries, classes and objects. The purpose of `common` folder is to group together utilities used by **renderer** and **main** processes.
Noteworthy is the `customizations` folder that contains clients related customizations. Those settings are merged with `default.js` that lists every option.
Client related customizations are stored on Pinia and can be accessed by `customizations` property of current workspace object, or importing `common/customizations`.
Client related customizations are stored on Vuex and can be accessed by `customizations` property of current workspace object, or importing `common/customizations`.
An use case of customizations object can be the following:
@@ -62,6 +62,12 @@ The command to build Antares SQL locally is `npm run build:local`.
- [Order of words in component names](https://vuejs.org/v2/style-guide/#Order-of-words-in-component-names-strongly-recommended).
- **kebab-case** in templates for property and event names.
### Vuex
- **snake_case** for state names.
- **camelCase** for getter and action names.
- **SNAKE_CASE (all caps)** for mutation names.
### Code Style
The project includes [ESlint](https://eslint.org/) and [StyleLint](https://stylelint.io/) config files with style rules. I recommend to set the lint on-save option in your code editor.

View File

@@ -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 **forever 100% free (without paid premium feature)**, full featured, as possible community driven, cross platform and open source alternative, empowered by JavaScript ecosystem.
The main goal is to develop a totally free, full featured, cross platform and open source alternative, empowered by JavaScript's 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,6 +72,7 @@ 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
@@ -104,7 +105,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/antares-sql/antares/projects/1)
- 🚧 [Project Board](https://github.com/users/Fabio286/projects/1)
## Contributors ✨
@@ -115,9 +116,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/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://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://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>
@@ -128,15 +129,7 @@ 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/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>
<td align="center"><a href="https://github.com/xak666"><img src="https://avatars.githubusercontent.com/u/38811437?v=4?s=100" width="100px;" alt=""/><br /><sub><b>xaka_xak</b></sub></a><br /><a href="#translation-xak666" title="Translation">🌍</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>
</tr>
</table>

BIN
assets/appx/LargeTile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
assets/appx/SmallTile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

13386
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,37 +1,31 @@
{
"name": "antares",
"productName": "Antares",
"version": "0.5.11",
"version": "0.3.10",
"description": "A modern, fast and productivity driven SQL client with a focus in UX.",
"license": "MIT",
"repository": "https://github.com/antares-sql/antares.git",
"repository": "https://github.com/Fabio286/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:workers && npm run compile:renderer",
"compile": "npm run compile:main && 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 --publish never",
"build:local": "npm run build && electron-builder",
"build:appx": "npm run build:local -- --win appx",
"rebuild:electron": "rimraf ./dist && npm run postinstall",
"rebuild:electron": "npm run postinstall",
"release": "standard-version",
"release:pre": "npm run release -- --prerelease alpha",
"devtools:install": "node scripts/devtoolsInstaller",
"postinstall": "electron-builder install-app-deps && npm run devtools:install",
"test:e2e": "npm run compile && npm run test:e2e-dry",
"test:e2e-dry": "xvfb-maybe -- playwright test",
"lint": "eslint . --ext .js,.ts,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
"lint:fix": "eslint . --ext .js,.ts,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix",
"postinstall": "electron-builder install-app-deps",
"test": "npm run lint",
"lint": "eslint . --ext .js,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
"lint:fix": "eslint . --ext .js,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix",
"contributors:add": "all-contributors add",
"contributors:generate": "all-contributors generate"
},
"author": "Fabio Di Stasio <fabio286@gmail.com>",
"main": "./dist/main.js",
"antares": {
"devtoolsId": "nhdogjmejiglipccpnnnanhbledajbpd"
},
"build": {
"appId": "com.fabio286.antares",
"artifactName": "${productName}-${version}-${os}_${arch}.${ext}",
@@ -82,19 +76,12 @@
"license": "./LICENSE",
"category": "Development"
},
"nsis": {
"license": "./LICENSE",
"installerIcon": "assets/icon.ico",
"uninstallerIcon": "assets/icon.ico",
"installerHeader": "assets/icon.ico"
},
"portable": {
"artifactName": "${productName}-${version}-portable.exe"
},
"appx": {
"displayName": "Antares SQL",
"backgroundColor": "transparent",
"showNameOnTiles": true,
"identityName": "62514FabioDiStasio.AntaresSQLClient",
"publisher": "CN=1A2729ED-865C-41D2-9038-39AE2A63AA52",
"applicationId": "FabioDiStasio.AntaresSQLClient"
@@ -115,85 +102,64 @@
}
},
"dependencies": {
"@electron/remote": "~2.0.1",
"@faker-js/faker": "~6.1.2",
"@mdi/font": "~6.9.96",
"@turf/helpers": "~6.5.0",
"@vueuse/core": "~8.7.5",
"ace-builds": "~1.4.13",
"better-sqlite3": "~7.5.1",
"electron-log": "~4.4.1",
"electron-store": "~8.0.1",
"electron-updater": "~4.6.5",
"electron-window-state": "~5.0.3",
"encoding": "~0.1.13",
"leaflet": "~1.7.1",
"marked": "~4.0.0",
"moment": "~2.29.4",
"mysql2": "~2.3.2",
"pg": "~8.7.1",
"pg-query-stream": "~4.2.3",
"pgsql-ast-parser": "~7.2.1",
"pinia": "~2.0.13",
"source-map-support": "~0.5.20",
"spectre.css": "~0.5.9",
"sql-formatter": "~4.0.2",
"ssh2-promise": "~1.0.2",
"v-mask": "~2.3.0",
"vue": "~3.2.37",
"vue-i18n": "~9.1.9",
"vuedraggable": "~4.1.0"
"@electron/remote": "^2.0.1",
"@mdi/font": "^6.1.95",
"ace-builds": "^1.4.13",
"better-sqlite3": "^7.4.4",
"electron-log": "^4.4.1",
"electron-store": "^8.0.1",
"electron-updater": "^4.3.9",
"faker": "^5.5.3",
"marked": "^4.0.0",
"moment": "^2.29.1",
"mysql2": "^2.3.2",
"pg": "^8.7.1",
"pgsql-ast-parser": "^7.2.1",
"source-map-support": "^0.5.20",
"spectre.css": "^0.5.9",
"sql-formatter": "^4.0.2",
"ssh2-promise": "^1.0.2",
"v-mask": "^2.3.0",
"vue-i18n": "^8.26.5",
"vuedraggable": "^2.24.3",
"vuex": "^3.6.2"
},
"devDependencies": {
"@babel/eslint-parser": "~7.15.7",
"@babel/preset-env": "~7.15.8",
"@babel/preset-typescript": "~7.16.7",
"@playwright/test": "~1.21.1",
"@types/better-sqlite3": "~7.5.0",
"@types/leaflet": "~1.7.9",
"@types/marked": "~4.0.3",
"@types/node": "~17.0.23",
"@types/pg": "~8.6.5",
"@typescript-eslint/eslint-plugin": "~5.18.0",
"@typescript-eslint/parser": "~5.18.0",
"@vue/compiler-sfc": "~3.2.33",
"all-contributors-cli": "~6.20.0",
"babel-loader": "~8.2.3",
"chalk": "~4.1.2",
"cross-env": "~7.0.2",
"css-loader": "~6.5.0",
"electron": "~19.0.5",
"electron-builder": "~23.0.3",
"eslint": "~7.32.0",
"eslint-config-standard": "~16.0.3",
"eslint-plugin-import": "~2.24.2",
"eslint-plugin-node": "~11.1.0",
"eslint-plugin-promise": "~5.2.0",
"eslint-plugin-vue": "~8.0.3",
"file-loader": "~6.2.0",
"html-webpack-plugin": "~5.5.0",
"mini-css-extract-plugin": "~2.4.5",
"node-loader": "~2.0.0",
"playwright": "~1.21.1",
"playwright-core": "~1.21.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",
"style-loader": "~3.3.1",
"stylelint": "~13.13.1",
"stylelint-config-standard": "~22.0.0",
"stylelint-scss": "~3.21.0",
"tree-kill": "~1.2.2",
"ts-loader": "~9.2.8",
"typescript": "~4.6.3",
"unzip-crx-3": "~0.2.0",
"vue-eslint-parser": "~8.3.0",
"vue-loader": "~16.8.3",
"webpack": "~5.72.0",
"webpack-cli": "~4.9.1",
"webpack-dev-server": "~4.4.0",
"xvfb-maybe": "~0.2.1"
"@babel/eslint-parser": "^7.15.7",
"@babel/preset-env": "^7.15.8",
"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.1",
"electron-builder": "^22.13.1",
"electron-devtools-installer": "^3.2.0",
"eslint": "^7.32.0",
"eslint-config-standard": "^16.0.3",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-vue": "^8.0.3",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0",
"mini-css-extract-plugin": "^2.4.3",
"node-loader": "^2.0.0",
"progress-webpack-plugin": "^1.0.12",
"sass": "^1.42.1",
"sass-loader": "^12.3.0",
"standard-version": "^9.3.1",
"style-loader": "^3.3.1",
"stylelint": "^13.13.1",
"stylelint-config-standard": "^22.0.0",
"stylelint-scss": "^3.21.0",
"tree-kill": "^1.2.2",
"vue": "^2.6.14",
"vue-loader": "^15.9.8",
"vue-template-compiler": "^2.6.14",
"webpack": "^5.60.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.4.0"
}
}

View File

@@ -1,6 +1,5 @@
process.env.NODE_ENV = 'development';
// process.env.ELECTRON_ENABLE_LOGGING = true
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = false;
const chalk = require('chalk');
const electron = require('electron');
@@ -13,14 +12,14 @@ 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;
const remoteDebugging = process.argv.includes('--remote-debug');
if (remoteDebugging) {
// disable devtools open in electron
// disable dvtools open in electron
process.env.RENDERER_REMOTE_DEBUGGING = true;
}
@@ -59,13 +58,13 @@ async function restartElectron () {
console.error(chalk.red(data.toString()));
});
electronProcess.on('exit', () => {
electronProcess.on('exit', (code, signal) => {
if (!manualRestart) process.exit(0);
});
}
function startMain () {
const webpackSetup = webpack([mainConfig, workersConfig]);
const webpackSetup = webpack(mainConfig);
webpackSetup.compilers.forEach((compiler) => {
const { name } = compiler;

View File

@@ -1,48 +0,0 @@
// @ts-check
const fs = require('fs');
const path = require('path');
const https = require('https');
const unzip = require('unzip-crx-3');
const { antares } = require('../package.json');
const extensionID = antares.devtoolsId;
const destFolder = path.resolve(__dirname, `../misc/${extensionID}`);
const filePath = path.resolve(__dirname, `${destFolder}${extensionID}.crx`);
const fileUrl = `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${extensionID}%26uc&prodversion=32`;
const fileStream = fs.createWriteStream(filePath);
const downloadFile = url => {
return new Promise((resolve, reject) => {
const request = https.get(url);
request.on('response', response => {
if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
return downloadFile(response.headers.location)
.then(resolve)
.catch(reject);
}
response.pipe(fileStream);
response.on('close', () => {
console.log('Devtools download completed!');
resolve();
});
response.on('error', reject);
});
request.on('error', reject);
request.end();
});
};
(async () => {
try {
await downloadFile(fileUrl);
await unzip(filePath, destFolder);
fs.unlinkSync(filePath);
fs.unlinkSync(`${destFolder}/package.json`);// <- Avoid to display annoyng npm script in vscode
}
catch (error) {
console.log(error);
}
})();

View File

@@ -195,7 +195,7 @@ export default class {
return 1;
return 0;
});
}); ;
}
static getGroupsByType (type) {

View File

@@ -1,6 +1,4 @@
import { Customizations } from '../interfaces/customizations';
export const defaults: Customizations = {
module.exports = {
// Defaults
defaultPort: null,
defaultUser: null,
@@ -13,7 +11,6 @@ export const defaults: Customizations = {
sslConnection: false,
sshConnection: false,
fileConnection: false,
cancelQueries: false,
// Tools
processesList: false,
usersManagement: false,
@@ -31,7 +28,6 @@ export const defaults: Customizations = {
elementsWrapper: '',
stringsWrapper: '"',
tableAdd: false,
tableTruncateDisableFKCheck: false,
viewAdd: false,
triggerAdd: false,
triggerFunctionAdd: false,
@@ -41,9 +37,6 @@ export const defaults: Customizations = {
databaseEdit: false,
schemaEdit: false,
schemaDrop: false,
schemaExport: false,
exportByChunks: false,
schemaImport: false,
tableSettings: false,
tableOptions: false,
tableArray: false,
@@ -71,24 +64,24 @@ export const defaults: Customizations = {
viewUpdateOption: false,
procedureDeterministic: false,
procedureDataAccess: false,
procedureSql: null,
procedureSql: false,
procedureContext: false,
procedureLanguage: false,
functionDeterministic: false,
functionDataAccess: false,
functionSql: null,
functionSql: false,
functionContext: false,
functionLanguage: false,
triggerSql: null,
triggerSql: false,
triggerStatementInCreation: false,
triggerMultipleEvents: false,
triggerTableInName: false,
triggerUpdateColumns: false,
triggerOnlyRename: false,
triggerEnableDisable: false,
triggerFunctionSql: null,
triggerFunctionlanguages: null,
triggerFunctionSql: false,
triggerFunctionlanguages: false,
parametersLength: false,
languages: null,
languages: false,
readOnlyMode: false
};

View File

@@ -0,0 +1,6 @@
module.exports = {
maria: require('./mysql'),
mysql: require('./mysql'),
pg: require('./postgresql'),
sqlite: require('./sqlite')
};

View File

@@ -1,16 +0,0 @@
import * as mysql from 'common/customizations/mysql';
import * as postgresql from 'common/customizations/postgresql';
import * as sqlite from 'common/customizations/sqlite';
import { Customizations } from 'common/interfaces/customizations';
export default {
maria: mysql.customizations,
mysql: mysql.customizations,
pg: postgresql.customizations,
sqlite: sqlite.customizations
} as {
maria: Customizations;
mysql: Customizations;
pg: Customizations;
sqlite: Customizations;
};

View File

@@ -1,7 +1,6 @@
import { Customizations } from '../interfaces/customizations';
import { defaults } from './defaults';
const defaults = require('./defaults');
export const customizations: Customizations = {
module.exports = {
...defaults,
// Defaults
defaultPort: 3306,
@@ -13,7 +12,6 @@ export const customizations: Customizations = {
engines: true,
sslConnection: true,
sshConnection: true,
cancelQueries: true,
// Tools
processesList: true,
// Structure
@@ -28,7 +26,6 @@ export const customizations: Customizations = {
elementsWrapper: '',
stringsWrapper: '"',
tableAdd: true,
tableTruncateDisableFKCheck: true,
viewAdd: true,
triggerAdd: true,
routineAdd: true,
@@ -36,9 +33,6 @@ export const customizations: Customizations = {
schedulerAdd: true,
schemaEdit: true,
schemaDrop: true,
schemaExport: true,
exportByChunks: true,
schemaImport: true,
tableSettings: true,
viewSettings: true,
triggerSettings: true,

View File

@@ -1,7 +1,6 @@
import { Customizations } from '../interfaces/customizations';
import { defaults } from './defaults';
const defaults = require('./defaults');
export const customizations: Customizations = {
module.exports = {
...defaults,
// Defaults
defaultPort: 5432,
@@ -11,7 +10,6 @@ export const customizations: Customizations = {
database: true,
sslConnection: true,
sshConnection: true,
cancelQueries: true,
// Tools
processesList: true,
// Structure
@@ -32,8 +30,6 @@ export const customizations: Customizations = {
routineAdd: true,
functionAdd: true,
schemaDrop: true,
schemaExport: true,
schemaImport: true,
databaseEdit: false,
tableSettings: true,
viewSettings: true,

View File

@@ -1,8 +1,4 @@
import { Customizations } from '../interfaces/customizations';
import { defaults } from './defaults';
export const customizations: Customizations = {
...defaults,
module.exports = {
// Core
fileConnection: true,
// Structure
@@ -11,8 +7,8 @@ export const customizations: Customizations = {
views: true,
triggers: true,
// Settings
elementsWrapper: '"',
stringsWrapper: '\'',
elementsWrapper: '',
stringsWrapper: '"',
tableAdd: true,
viewAdd: true,
triggerAdd: true,

View File

@@ -1,6 +1,4 @@
import { TypesGroup } from 'common/interfaces/antares';
export default [
module.exports = [
{
group: 'integer',
types: [
@@ -60,7 +58,7 @@ export default [
},
{
name: 'DOUBLE',
length: false,
length: true,
collation: false,
unsigned: false,
zerofill: false
@@ -68,7 +66,6 @@ export default [
{
name: 'DECIMAL',
length: true,
scale: true,
collation: false,
unsigned: false,
zerofill: false
@@ -123,7 +120,7 @@ export default [
{
name: 'JSON',
length: false,
collation: false,
collation: true,
unsigned: false,
zerofill: false
}
@@ -221,56 +218,56 @@ export default [
types: [
{
name: 'POINT',
length: false,
length: true,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'LINESTRING',
length: false,
length: true,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'POLYGON',
length: false,
length: true,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'GEOMETRY',
length: false,
length: true,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'MULTIPOINT',
length: false,
length: true,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'MULTILINESTRING',
length: false,
length: true,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'MULTIPOLYGON',
length: false,
length: true,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'GEOMCOLLECTION',
length: false,
name: 'GEOMETRYCOLLECTION',
length: true,
collation: false,
unsigned: false,
zerofill: false
@@ -308,4 +305,4 @@ export default [
}
]
}
] as TypesGroup[];
];

View File

@@ -1,6 +1,4 @@
import { TypesGroup } from 'common/interfaces/antares';
export default [
module.exports = [
{
group: 'integer',
types: [
@@ -24,6 +22,11 @@ export default [
length: false,
unsigned: true
},
{
name: 'NUMERIC',
length: true,
unsigned: true
},
{
name: 'SMALLSERIAL',
length: false,
@@ -49,12 +52,6 @@ export default [
length: false,
unsigned: true
},
{
name: 'NUMERIC',
length: true,
unsigned: true,
scale: true
},
{
name: 'DOUBLE PRECISION',
length: false,
@@ -292,4 +289,4 @@ export default [
}
]
}
] as TypesGroup[];
];

View File

@@ -1,6 +1,4 @@
import { TypesGroup } from 'common/interfaces/antares';
export default [
module.exports = [
{
group: 'integer',
types: [
@@ -136,4 +134,4 @@ export default [
}
]
}
] as TypesGroup[];
];

View File

@@ -8,9 +8,7 @@ export const TEXT = [
export const LONG_TEXT = [
'TEXT',
'MEDIUMTEXT',
'LONGTEXT',
'JSON',
'VARBINARY'
'LONGTEXT'
];
export const ARRAY = [
@@ -41,7 +39,6 @@ export const NUMBER = [
export const FLOAT = [
'FLOAT',
'DECIMAL',
'DOUBLE',
'REAL',
'DOUBLE PRECISION',
@@ -85,24 +82,3 @@ export const BIT = [
'BIT',
'BIT VARYING'
];
export const SPATIAL = [
'POINT',
'LINESTRING',
'POLYGON',
'GEOMETRY',
'MULTIPOINT',
'MULTILINESTRING',
'MULTIPOLYGON',
'GEOMCOLLECTION',
'GEOMETRYCOLLECTION'
];
// Used to check multi spatial fields only
export const IS_MULTI_SPATIAL = [
'MULTIPOINT',
'MULTILINESTRING',
'MULTIPOLYGON',
'GEOMCOLLECTION',
'GEOMETRYCOLLECTION'
];

View File

@@ -1,4 +1,4 @@
export default [
module.exports = [
'PRIMARY',
'INDEX',
'UNIQUE',

View File

@@ -1,4 +1,4 @@
export default [
module.exports = [
'PRIMARY',
'INDEX',
'UNIQUE'

View File

@@ -1,4 +1,4 @@
export default [
module.exports = [
'PRIMARY',
'INDEX',
'UNIQUE'

View File

@@ -1,406 +0,0 @@
import * as mysql from 'mysql2/promise';
import * as pg from 'pg';
import MysqlExporter from 'src/main/libs/exporters/sql/MysqlExporter';
import PostgreSQLExporter from 'src/main/libs/exporters/sql/PostgreSQLExporter';
import MySQLImporter from 'src/main/libs/importers/sql/MySQLlImporter';
import PostgreSQLImporter from 'src/main/libs/importers/sql/PostgreSQLImporter';
import SSHConfig from 'ssh2-promise/lib/sshConfig';
import { MySQLClient } from '../../main/libs/clients/MySQLClient';
import { PostgreSQLClient } from '../../main/libs/clients/PostgreSQLClient';
import { SQLiteClient } from '../../main/libs/clients/SQLiteClient';
export type Client = MySQLClient | PostgreSQLClient | SQLiteClient
export type ClientCode = 'mysql' | 'maria' | 'pg' | 'sqlite'
export type Exporter = MysqlExporter | PostgreSQLExporter
export type Importer = MySQLImporter | PostgreSQLImporter
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export interface IpcResponse<T = any> {
status: 'success' | 'error';
response?: T;
}
/**
* Pasameters needed to create a new Antares connection to a database
*/
export interface ClientParams {
client: ClientCode;
uid?: string;
params:
mysql.ConnectionOptions & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean}
| pg.ClientConfig & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean}
| { databasePath: string; readonly: boolean };
poolSize?: number;
logger?: () => void;
}
/**
* Paramenets insered by user in connection mask
*/
export interface ConnectionParams {
uid: string;
name?: string;
client: ClientCode;
host: string;
database?: string;
schema?: string;
databasePath?: string;
port: number;
user: string;
password: string;
ask: boolean;
readonly: boolean;
ssl: boolean;
cert?: string;
key?: string;
ca?: string;
untrustedConnection: boolean;
ciphers?: string;
ssh: boolean;
sshHost?: string;
sshUser?: string;
sshPass?: string;
sshKey?: string;
sshPort?: number;
sshPassphrase?: string;
}
export interface TypeInformations {
name: string;
length: boolean;
scale: boolean;
collation: boolean;
unsigned: boolean;
zerofill: boolean;
}
export interface TypesGroup {
group: string;
types: TypeInformations[];
}
// Tables
export interface TableInfos {
name: string;
type: string;
rows: number;
created: Date;
updated: Date;
engine: string;
comment: string;
size: number | false;
autoIncrement: number;
collation: string;
}
export type TableOptions = Partial<TableInfos>;
export interface TableField {
// eslint-disable-next-line camelcase
_antares_id?: string;
name: string;
type: string;
schema: string;
table?: string;
numPrecision?: number;
numLength?: number;
datePrecision?: number;
charLength?: number;
numScale?: number;
nullable?: boolean;
unsigned?: boolean;
zerofill?: boolean;
order?: number;
default?: string;
defaultType?: string;
enumValues?: string;
charset?: string;
collation?: string;
autoIncrement?: boolean;
isArray?: boolean;
onUpdate?: string;
comment?: string;
after?: string;
orgName?: string;
length?: number | false;
alias: string;
tableAlias: string;
orgTable: string;
key?: 'pri' | 'uni' | '';
}
export interface TableIndex {
// eslint-disable-next-line camelcase
_antares_id?: string;
name: string;
fields: string[];
type: string;
comment?: string;
indexType?: string;
indexComment?: string;
cardinality?: number;
oldType?: string;
oldName?: string;
}
export interface TableForeign {
// eslint-disable-next-line camelcase
_antares_id?: string;
constraintName: string;
refSchema: string;
table: string;
refTable: string;
field: string;
refField: string;
onUpdate: string;
onDelete: string;
oldName?: string;
}
export interface CreateTableParams {
/** Connection UID */
uid?: string;
schema: string;
fields: TableField[];
foreigns: TableForeign[];
indexes: TableIndex[];
options: TableOptions;
}
export interface AlterTableParams {
/** Connection UID */
uid?: string;
schema: string;
table: string;
additions: TableField[];
changes: TableField[];
deletions: TableField[];
tableStructure: {
name: string;
fields: TableField[];
foreigns: TableForeign[];
indexes: TableIndex[];
};
indexChanges: {
additions: TableIndex[];
changes: TableIndex[];
deletions: TableIndex[];
};
foreignChanges: {
additions: TableForeign[];
changes: TableForeign[];
deletions: TableForeign[];
};
options: TableOptions;
}
// Views
export type ViewInfos = TableInfos
export interface CreateViewParams {
schema: string;
name: string;
algorithm: string;
definer: string;
security: string;
sql: string;
updateOption: string;
}
export interface AlterViewParams extends CreateViewParams {
oldName?: string;
}
// Triggers
export interface TriggerInfos {
name: string;
statement: string;
timing: string;
definer: string;
event: string;
table: string;
sqlMode: string;
created: Date;
charset: string;
enabled?: boolean;
}
export interface CreateTriggerParams {
definer?: string;
schema: string;
name: string;
activation: string;
event: string;
table: string;
sql: string;
}
export interface AlterTriggerParams extends CreateTriggerParams {
oldName?: string;
}
// Routines & Functions
export interface FunctionParam {
// eslint-disable-next-line camelcase
_antares_id: string;
context: string;
name: string;
type: string;
length: number;
}
export interface RoutineInfos {
name: string;
type?: string;
definer: string;
created?: string;
sql?: string;
updated?: string;
comment?: string;
charset?: string;
security?: string;
language?: string;
dataAccess?: string;
deterministic?: boolean;
parameters?: FunctionParam[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
returns?: any;
returnsLength?: number;
}
export type FunctionInfos = RoutineInfos
export type TriggerFunctionInfos = FunctionInfos
export interface CreateRoutineParams {
name: string;
parameters?: FunctionParam[];
definer: string;
schema: string;
deterministic: boolean;
dataAccess: string;
security: string;
comment?: string;
language?: string;
sql: string;
}
export interface AlterRoutineParams extends CreateRoutineParams {
oldName?: string;
}
export interface CreateFunctionParams {
name: string;
parameters?: FunctionParam[];
definer: string;
schema: string;
deterministic: boolean;
dataAccess: string;
security: string;
comment?: string;
sql: string;
returns: string;
returnsLength: number;
language?: string;
}
export interface AlterFunctionParams extends CreateFunctionParams {
oldName?: string;
}
// Events
export interface EventInfos {
definer?: string;
schema: string;
name: string;
execution: string;
every: string[];
starts: string;
ends: string;
at: string;
preserve: boolean;
state: string;
comment: string;
enabled?: boolean;
sql: string;
}
export type CreateEventParams = EventInfos;
export interface AlterEventParams extends CreateEventParams {
oldName?: string;
}
// Schema
export interface SchemaInfos {
name: string;
size: number;
tables: TableInfos[];
functions: FunctionInfos[];
procedures: RoutineInfos[];
triggers: TriggerInfos[];
schedulers: EventInfos[];
}
export interface CollationInfos {
charset: string;
collation: string;
compiled: boolean;
default: boolean;
id: string | number;
sortLen: number;
}
// Query
export interface QueryBuilderObject {
schema: string;
select: string[];
from: string;
where: string[];
groupBy: string[];
orderBy: string[];
limit: number;
offset: number;
join: string[];
update: string[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
insert: {[key: string]: any}[];
delete: boolean;
}
export interface QueryParams {
nest?: boolean;
details?: boolean;
split?: boolean;
comments?: boolean;
autocommit?: boolean;
schema?: string;
tabUid?: string;
}
/**
* @deprecated Use TableFIeld
*/
export type QueryField = TableField
export interface QueryForeign {
schema: string;
table: string;
field: string;
position: number;
constraintPosition: number;
constraintName: string;
refSchema: string;
refTable: string;
refField: string;
onUpdate: string;
onDelete: string;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export interface QueryResult<T = any> {
rows: T[];
report: { affectedRows: number };
fields: QueryField[];
keys: QueryForeign[];
duration: number;
}

View File

@@ -1,92 +0,0 @@
export interface Customizations {
// Defaults
defaultPort?: number;
defaultUser?: string;
defaultDatabase?: string;
// Core
database?: boolean;
collations?: boolean;
engines?: boolean;
connectionSchema?: boolean;
sslConnection?: boolean;
sshConnection?: boolean;
fileConnection?: boolean;
cancelQueries?: boolean;
// Tools
processesList?: boolean;
usersManagement?: boolean;
variables?: boolean;
// Structure
schemas?: boolean;
tables?: boolean;
views?: boolean;
triggers?: boolean;
triggerFunctions?: boolean;
routines?: boolean;
functions?: boolean;
schedulers?: boolean;
// Settings
elementsWrapper: string;
stringsWrapper: string;
tableAdd?: boolean;
tableSettings?: boolean;
tableOptions?: boolean;
tableArray?: boolean;
tableRealCount?: boolean;
tableTruncateDisableFKCheck?: boolean;
viewAdd?: boolean;
viewSettings?: boolean;
triggerAdd?: boolean;
triggerFunctionAdd?: boolean;
routineAdd?: boolean;
functionAdd?: boolean;
schedulerAdd?: boolean;
databaseEdit?: boolean;
schemaEdit?: boolean;
schemaDrop?: boolean;
schemaExport?: boolean;
exportByChunks?: boolean;
schemaImport?: boolean;
triggerSettings?: boolean;
triggerFunctionSettings?: boolean;
routineSettings?: boolean;
functionSettings?: boolean;
schedulerSettings?: boolean;
indexes?: boolean;
foreigns?: boolean;
sortableFields?: boolean;
unsigned?: boolean;
nullable?: boolean;
nullablePrimary?: boolean;
zerofill?: boolean;
autoIncrement?: boolean;
comment?: boolean;
collation?: boolean;
definer?: boolean;
onUpdate?: boolean;
viewAlgorithm?: boolean;
viewSqlSecurity?: boolean;
viewUpdateOption?: boolean;
procedureDeterministic?: boolean;
procedureDataAccess?: boolean;
procedureSql?: string;
procedureContext?: boolean;
procedureLanguage?: boolean;
functionDeterministic?: boolean;
functionDataAccess?: boolean;
functionSql?: string;
functionContext?: boolean;
functionLanguage?: boolean;
triggerSql?: string;
triggerStatementInCreation?: boolean;
triggerMultipleEvents?: boolean;
triggerTableInName?: boolean;
triggerUpdateColumns?: boolean;
triggerOnlyRename?: boolean;
triggerEnableDisable?: boolean;
triggerFunctionSql?: string;
triggerFunctionlanguages?: string[];
parametersLength?: boolean;
languages?: string[];
readOnlyMode?: boolean;
}

View File

@@ -1,28 +0,0 @@
export interface TableParams {
table: string;
includeStructure: boolean;
includeContent: boolean;
includeDropStatement: boolean;
}
export interface ExportOptions {
schema: string;
tables: {
table: string;
includeStructure: boolean;
includeContent: boolean;
includeDropStatement: boolean;
}[];
includes: {[key: string]: boolean};
outputFormat: 'sql' | 'sql.zip';
outputFile: string;
sqlInsertAfter: number;
sqlInsertDivider: 'bytes' | 'rows';
}
export interface ExportState {
totalItems?: number;
currentItemIndex?: number;
currentItem?: string;
op?: string;
}

View File

@@ -1,16 +0,0 @@
import * as antares from './antares';
export interface ImportOptions {
uid: string;
schema: string;
type: antares.ClientCode;
file: string;
}
export interface ImportState {
fileSize?: number;
readPosition?: number;
percentage?: number;
queryCount?: number;
op?: string;
}

View File

@@ -1,49 +0,0 @@
import { UsableLocale } from '@faker-js/faker';
export interface TableUpdateParams {
uid: string;
schema: string;
table: string;
primary?: string;
id: number | string;
content: number | string | boolean | Date | Blob | null;
type: string;
field: string;
}
export interface TableDeleteParams {
uid: string;
schema: string;
table: string;
primary?: string;
field: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
rows: {[key: string]: any};
}
export interface TableFilterClausole {
active: boolean;
field: string;
op: '=' | '!=' | '>' | '<' | '>=' | '<=' | 'IN' | 'NOT IN' | 'LIKE' | 'BETWEEN' | 'IS NULL' | 'IS NOT NULL';
value: '';
value2: '';
}
export interface InsertRowsParams {
uid: string;
schema: string;
table: string;
row: {[key: string]: {
group: string;
method: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
params: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
value: any;
length: number;
};
};
repeat: number;
fields: {[key: string]: string};
locale: UsableLocale;
}

View File

@@ -1,7 +0,0 @@
export type WorkerEvent = 'export-progress' | 'import-progress' | 'query-error' | 'end' | 'cancel' | 'error'
export interface WorkerIpcMessage {
type: WorkerEvent;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
payload: any;
}

View File

@@ -1,6 +1,7 @@
export function bufferToBase64 (buf: Buffer) {
'use strict';
export function bufferToBase64 (buf) {
const binstr = Array.prototype.map.call(buf, ch => {
return String.fromCharCode(ch);
}).join('');
return Buffer.from(binstr, 'binary').toString('base64');
return btoa(binstr);
}

View File

@@ -1,4 +1,5 @@
export function formatBytes (bytes: number, decimals = 2) {
'use strict';
export function formatBytes (bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;

View File

@@ -1,6 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export function getArrayDepth (array: any[]): number {
return Array.isArray(array)
? 1 + Math.max(0, ...array.map(getArrayDepth))
: 0;
}

View File

@@ -1,3 +1,5 @@
'use strict';
const lookup = {
0: '0000',
1: '0001',
@@ -21,13 +23,17 @@ const lookup = {
D: '1101',
E: '1110',
F: '1111'
} as const;
};
export type HexChar = keyof typeof lookup
export default function hexToBinary (hex: HexChar[]) {
/**
* Converts hexadecimal string to binary string
*
* @param {string} hex Hexadecimal string
* @returns {string} Binary string
*/
export default function hexToBinary (hex) {
let binary = '';
for (let i = 0; i < hex.length; i++)
for (let i = 0, len = hex.length; i < len; i++)
binary += lookup[hex[i]];
return binary;

View File

@@ -1,192 +0,0 @@
function isJSON (str: string) {
try {
if (!['{', '['].includes(str.trim()[0]))
return false;
JSON.parse(str);
return true;
}
catch (_) {
return false;
}
}
function isHTML (str: string) {
const tags = [
'a',
'abbr',
'address',
'area',
'article',
'aside',
'audio',
'b',
'base',
'bdi',
'bdo',
'blockquote',
'body',
'br',
'button',
'canvas',
'caption',
'cite',
'code',
'col',
'colgroup',
'data',
'datalist',
'dd',
'del',
'details',
'dfn',
'dialog',
'div',
'dl',
'dt',
'em',
'embed',
'fieldset',
'figcaption',
'figure',
'footer',
'form',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'head',
'header',
'hgroup',
'hr',
'html',
'i',
'iframe',
'img',
'input',
'ins',
'kbd',
'label',
'legend',
'li',
'link',
'main',
'map',
'mark',
'math',
'menu',
'menuitem',
'meta',
'meter',
'nav',
'noscript',
'object',
'ol',
'optgroup',
'option',
'output',
'p',
'param',
'picture',
'pre',
'progress',
'q',
'rb',
'rp',
'rt',
'rtc',
'ruby',
's',
'samp',
'script',
'section',
'select',
'slot',
'small',
'source',
'span',
'strong',
'style',
'sub',
'summary',
'sup',
'svg',
'table',
'tbody',
'td',
'template',
'textarea',
'tfoot',
'th',
'thead',
'time',
'title',
'tr',
'track',
'u',
'ul',
'var',
'video',
'wbr'
];
const doc = new DOMParser().parseFromString(str, 'text/html');
const lowerStr = str.toLowerCase();
if (Array.from(doc.body.childNodes).some(node => node.nodeType === 1))
return tags.some((tag) => lowerStr.includes(`<${tag}>`));
return false;
}
function isSVG (str: string) {
const doc = new DOMParser().parseFromString(str, 'text/xml');
const lowerStr = str.toLowerCase();
const errorNode = doc.querySelector('parsererror');
if (!errorNode)
return lowerStr.includes('<svg');
return false;
}
function isXML (str: string) {
const doc = new DOMParser().parseFromString(str, 'text/xml');
const errorNode = doc.querySelector('parsererror');
return !errorNode;
}
function isMD (str: string) {
const mdChecks = [
'# ',
'`',
'- ',
'+ ',
'* ',
'1. ',
'**',
'__',
'~~',
'>> ',
'](http',
'![',
'[ ]',
'[x]'
];
return mdChecks.some((tag) => str.includes(tag));
}
export function langDetector (str: string) {
if (!str.trim().length)
return 'text';
if (isJSON(str))
return 'json';
if (isHTML(str))
return 'html';
if (isSVG(str))
return 'svg';
if (isXML(str))
return 'xml';
if (isMD(str))
return 'markdown';
return 'text';
}

View File

@@ -1,4 +1,5 @@
export function mimeFromHex (hex: string) {
'use strict';
export function mimeFromHex (hex) {
switch (hex.substring(0, 4)) { // 2 bytes
case '424D':
return { ext: 'bmp', mime: 'image/bmp' };
@@ -42,4 +43,4 @@ export function mimeFromHex (hex: string) {
}
}
}
}
};

View File

@@ -3,7 +3,13 @@
const pattern = /[\0\x08\x09\x1a\n\r"'\\\%]/gm;
const regex = new RegExp(pattern);
function sqlEscaper (string: string) {
/**
* Escapes a string
*
* @param {String} string
* @returns {String}
*/
function sqlEscaper (string) {
return string.replace(regex, char => {
const m = ['\\0', '\\x08', '\\x09', '\\x1a', '\\n', '\\r', '\'', '\"', '\\', '\\\\', '%'];
const r = ['\\\\0', '\\\\b', '\\\\t', '\\\\z', '\\\\n', '\\\\r', '\\\'', '\\\"', '\\\\', '\\\\\\\\', '\%'];

View File

@@ -0,0 +1,8 @@
/**
* @export
* @param {String} [prefix]
* @returns {String} Unique ID
*/
export function uidGen (prefix) {
return (prefix ? `${prefix}:` : '') + Math.random().toString(36).substr(2, 9).toUpperCase();
};

View File

@@ -1,3 +0,0 @@
export function uidGen (prefix?: string) {
return (prefix ? `${prefix}:` : '') + Math.random().toString(36).substr(2, 9).toUpperCase();
}

View File

@@ -1,49 +0,0 @@
interface ShortcutRecord {
event: string;
keys: Electron.Accelerator[];
description: string;
}
const shortcuts: ShortcutRecord[] = [
{
event: 'open-new-tab',
keys: ['CommandOrControl+T'],
description: 'Open a new query tab'
},
{
event: 'close-tab',
keys: ['CommandOrControl+W'],
description: 'Close tab'
},
{
event: 'next-tab',
keys: ['Alt+CommandOrControl+Right', 'CommandOrControl+PageDown'],
description: 'Next tab'
},
{
event: 'prev-tab',
keys: ['Alt+CommandOrControl+Left', 'CommandOrControl+PageUp'],
description: 'Previous tab'
},
{
event: 'open-connections-modal',
keys: ['Shift+CommandOrControl+Space'],
description: 'Show all connections'
},
{
event: 'toggle-console',
keys: ['CommandOrControl+F12', 'CommandOrControl+`'],
description: 'Toggle console'
}
];
for (let i = 1; i <= 9; i++) {
shortcuts.push(
{
event: `select-tab-${i}`,
keys: [`CommandOrControl+${i}`],
description: `Select tab number ${i}`
});
}
export { shortcuts };

View File

@@ -0,0 +1,12 @@
import { app, ipcMain } from 'electron';
export default () => {
ipcMain.on('close-app', () => {
app.exit();
});
ipcMain.on('get-key', async event => {
const key = false;
event.returnValue = key;
});
};

View File

@@ -1,15 +0,0 @@
import { app, ipcMain, dialog } from 'electron';
export default () => {
ipcMain.on('close-app', () => {
app.exit();
});
ipcMain.handle('show-open-dialog', (event, options) => {
return dialog.showOpenDialog(options);
});
ipcMain.handle('get-download-dir-path', () => {
return app.getPath('downloads');
});
};

View File

@@ -1,29 +1,16 @@
import * as antares from 'common/interfaces/antares';
import * as fs from 'fs';
import fs from 'fs';
import { ipcMain } from 'electron';
import { ClientsFactory } from '../libs/ClientsFactory';
import { SslOptions } from 'mysql2';
export default (connections: {[key: string]: antares.Client}) => {
ipcMain.handle('test-connection', async (event, conn: antares.ConnectionParams) => {
export default connections => {
ipcMain.handle('test-connection', async (event, conn) => {
const params = {
host: conn.host,
port: +conn.port,
user: conn.user,
password: conn.password,
readonly: conn.readonly,
database: '',
schema: '',
databasePath: '',
ssl: undefined as SslOptions,
ssh: undefined as {
host: string;
username: string;
password: string;
port: number;
privateKey: string;
passphrase: string;
}
application_name: 'Antares SQL',
readonly: conn.readonly
};
if (conn.database)
@@ -34,11 +21,10 @@ export default (connections: {[key: string]: antares.Client}) => {
if (conn.ssl) {
params.ssl = {
key: conn.key ? fs.readFileSync(conn.key).toString() : null,
cert: conn.cert ? fs.readFileSync(conn.cert).toString() : null,
ca: conn.ca ? fs.readFileSync(conn.ca).toString() : null,
ciphers: conn.ciphers,
rejectUnauthorized: !conn.untrustedConnection
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
};
}
@@ -48,14 +34,13 @@ export default (connections: {[key: string]: antares.Client}) => {
username: conn.sshUser,
password: conn.sshPass,
port: conn.sshPort ? conn.sshPort : 22,
privateKey: conn.sshKey ? fs.readFileSync(conn.sshKey).toString() : null,
privateKey: conn.sshKey ? fs.readFileSync(conn.sshKey) : null,
passphrase: conn.sshPassphrase
};
}
try {
const connection = await ClientsFactory.getClient({
uid: conn.uid,
const connection = await ClientsFactory.getConnection({
client: conn.client,
params
});
@@ -67,7 +52,7 @@ export default (connections: {[key: string]: antares.Client}) => {
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
return { status: 'error', response: err };
}
});
@@ -75,26 +60,14 @@ export default (connections: {[key: string]: antares.Client}) => {
return uid in connections;
});
ipcMain.handle('connect', async (event, conn: antares.ConnectionParams) => {
ipcMain.handle('connect', async (event, conn) => {
const params = {
host: conn.host,
port: +conn.port,
user: conn.user,
password: conn.password,
application_name: 'Antares SQL',
readonly: conn.readonly,
database: '',
schema: '',
databasePath: '',
ssl: undefined as SslOptions,
ssh: undefined as {
host: string;
username: string;
password: string;
port: number;
privateKey: string;
passphrase: string;
}
readonly: conn.readonly
};
if (conn.database)
@@ -108,11 +81,10 @@ export default (connections: {[key: string]: antares.Client}) => {
if (conn.ssl) {
params.ssl = {
key: conn.key ? fs.readFileSync(conn.key).toString() : null,
cert: conn.cert ? fs.readFileSync(conn.cert).toString() : null,
ca: conn.ca ? fs.readFileSync(conn.ca).toString() : null,
ciphers: conn.ciphers,
rejectUnauthorized: !conn.untrustedConnection
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
};
}
@@ -122,14 +94,13 @@ export default (connections: {[key: string]: antares.Client}) => {
username: conn.sshUser,
password: conn.sshPass,
port: conn.sshPort ? conn.sshPort : 22,
privateKey: conn.sshKey ? fs.readFileSync(conn.sshKey).toString() : null,
privateKey: conn.sshKey ? fs.readFileSync(conn.sshKey) : null,
passphrase: conn.sshPassphrase
};
}
try {
const connection = ClientsFactory.getClient({
uid: conn.uid,
const connection = ClientsFactory.getConnection({
client: conn.client,
params,
poolSize: 5

View File

@@ -1,7 +1,6 @@
import * as antares from 'common/interfaces/antares';
import { ipcMain } from 'electron';
export default (connections: {[key: string]: antares.Client}) => {
export default (connections) => {
ipcMain.handle('get-function-informations', async (event, params) => {
try {
const result = await connections[params.uid].getFunctionInformations(params);

View File

@@ -1,5 +1,3 @@
import * as antares from 'common/interfaces/antares';
import connection from './connection';
import tables from './tables';
import views from './views';
@@ -12,7 +10,7 @@ import application from './application';
import schema from './schema';
import users from './users';
const connections: {[key: string]: antares.Client} = {};
const connections = {};
export default () => {
connection(connections);

View File

@@ -1,7 +1,6 @@
import * as antares from 'common/interfaces/antares';
import { ipcMain } from 'electron';
export default (connections: {[key: string]: antares.Client}) => {
export default (connections) => {
ipcMain.handle('get-routine-informations', async (event, params) => {
try {
const result = await connections[params.uid].getRoutineInformations(params);

View File

@@ -1,7 +1,6 @@
import * as antares from 'common/interfaces/antares';
import { ipcMain } from 'electron';
export default (connections: {[key: string]: antares.Client}) => {
export default (connections) => {
ipcMain.handle('get-scheduler-informations', async (event, params) => {
try {
const result = await connections[params.uid].getEventInformations(params);

View File

@@ -0,0 +1,155 @@
import { ipcMain } from 'electron';
export default connections => {
ipcMain.handle('create-schema', async (event, params) => {
try {
await connections[params.uid].createSchema(params);
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('update-schema', async (event, params) => {
try {
await connections[params.uid].alterSchema(params);
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('delete-schema', async (event, params) => {
try {
await connections[params.uid].dropSchema(params);
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('get-schema-collation', async (event, params) => {
try {
const collation = await connections[params.uid].getDatabaseCollation(params);
return { status: 'success', response: collation.rows.length ? collation.rows[0].DEFAULT_COLLATION_NAME : '' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('get-structure', async (event, params) => {
try {
const structure = await connections[params.uid].getStructure(params.schemas);
return { status: 'success', response: structure };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('get-collations', async (event, uid) => {
try {
const result = await connections[uid].getCollations();
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('get-variables', async (event, uid) => {
try {
const result = await connections[uid].getVariables();
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('get-engines', async (event, uid) => {
try {
const result = await connections[uid].getEngines();
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('get-version', async (event, uid) => {
try {
const result = await connections[uid].getVersion();
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('get-processes', async (event, uid) => {
try {
const result = await connections[uid].getProcesses();
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('kill-process', async (event, { uid, pid }) => {
try {
const result = await connections[uid].killProcess(pid);
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('use-schema', async (event, { uid, schema }) => {
if (!schema) return;
try {
await connections[uid].use(schema);
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('raw-query', async (event, { uid, query, schema }) => {
if (!query) return;
try {
const result = await connections[uid].raw(query, {
nest: true,
details: true,
schema,
comments: false
});
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
};

View File

@@ -1,390 +0,0 @@
import * as antares from 'common/interfaces/antares';
import * as workers from 'common/interfaces/workers';
import * as fs from 'fs';
import * as path from 'path';
import { ChildProcess, fork } from 'child_process';
import { ipcMain, dialog } from 'electron';
const isDevelopment = process.env.NODE_ENV !== 'production';
export default (connections: {[key: string]: antares.Client}) => {
let exporter: ChildProcess = null;
let importer: ChildProcess = null;
ipcMain.handle('create-schema', async (event, params) => {
try {
await connections[params.uid].createSchema(params);
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('update-schema', async (event, params) => {
try {
await connections[params.uid].alterSchema(params);
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('delete-schema', async (event, params) => {
try {
await connections[params.uid].dropSchema(params);
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('get-schema-collation', async (event, params) => {
try {
const collation = await connections[params.uid].getDatabaseCollation(
params
);
return {
status: 'success',
response: collation
};
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('get-structure', async (event, params) => {
try {
const structure = await connections[params.uid].getStructure(
params.schemas
);
return { status: 'success', response: structure };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('get-collations', async (event, uid) => {
try {
const result = await connections[uid].getCollations();
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('get-variables', async (event, uid) => {
try {
const result = await connections[uid].getVariables();
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('get-engines', async (event, uid) => {
try {
const result = await connections[uid].getEngines();
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('get-version', async (event, uid) => {
try {
const result = await connections[uid].getVersion();
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('get-processes', async (event, uid) => {
try {
const result = await connections[uid].getProcesses();
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('kill-process', async (event, { uid, pid }) => {
try {
const result = await connections[uid].killProcess(pid);
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('use-schema', async (event, { uid, schema }) => {
if (!schema) return;
try {
await connections[uid].use(schema);
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('raw-query', async (event, { uid, query, schema, tabUid, autocommit }) => {
if (!query) return;
try {
const result = await connections[uid].raw(query, {
nest: true,
details: true,
schema,
tabUid,
autocommit,
comments: false
});
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('export', (event, { uid, type, tables, ...rest }) => {
if (exporter !== null) {
exporter.kill();
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 }: workers.WorkerIpcMessage) => {
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 () => {
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) {
importer.kill();
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 }: workers.WorkerIpcMessage) => {
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 () => {
let willAbort = false;
if (importer) {
const result = await dialog.showMessageBox({
type: 'warning',
message: 'Are you sure you want to abort the import',
buttons: ['Cancel', 'Abort'],
defaultId: 0,
cancelId: 0
});
if (result.response === 1) {
willAbort = true;
importer.send({ type: 'cancel' });
}
}
return { status: 'success', response: { willAbort } };
});
ipcMain.handle('kill-tab-query', async (event, { uid, tabUid }) => {
if (!tabUid) return;
try {
await connections[uid].killTabQuery(tabUid);
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('commit-tab', async (event, { uid, tabUid }) => {
if (!tabUid) return;
try {
await connections[uid].commitTab(tabUid);
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('rollback-tab', async (event, { uid, tabUid }) => {
if (!tabUid) return;
try {
await connections[uid].rollbackTab(tabUid);
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('destroy-connection-to-commit', async (event, { uid, tabUid }) => {
if (!tabUid) return;
try {
await connections[uid].destroyConnectionToCommit(tabUid);
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
};

View File

@@ -1,14 +1,11 @@
import * as fs from 'fs';
import * as antares from 'common/interfaces/antares';
import { InsertRowsParams } from 'common/interfaces/tableApis';
import { ipcMain } from 'electron';
import { faker } from '@faker-js/faker';
import * as moment from 'moment';
import faker from 'faker';
import moment from 'moment';
import { sqlEscaper } from 'common/libs/sqlEscaper';
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BLOB, BIT, DATE, DATETIME } from 'common/fieldTypes';
import customizations from 'common/customizations';
import fs from 'fs';
export default (connections: {[key: string]: antares.Client}) => {
export default (connections) => {
ipcMain.handle('get-table-columns', async (event, params) => {
try {
const result = await connections[params.uid].getTableColumns(params);
@@ -88,12 +85,11 @@ export default (connections: {[key: string]: antares.Client}) => {
ipcMain.handle('update-table-cell', async (event, params) => {
delete params.row._antares_id;
const { stringsWrapper: sw } = customizations[connections[params.uid]._client];
try { // TODO: move to client classes
let escapedParam;
let reload = false;
const id = typeof params.id === 'number' ? params.id : `${sw}${params.id}${sw}`;
const id = typeof params.id === 'number' ? params.id : `"${params.id}"`;
if ([...NUMBER, ...FLOAT].includes(params.type))
escapedParam = params.content;
@@ -104,6 +100,8 @@ export default (connections: {[key: string]: antares.Client}) => {
escapedParam = `"${sqlEscaper(params.content)}"`;
break;
case 'pg':
escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`;
break;
case 'sqlite':
escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`;
break;
@@ -149,7 +147,7 @@ export default (connections: {[key: string]: antares.Client}) => {
}
}
}
else if (BIT.includes(params.type)) {
else if ([...BIT].includes(params.type)) {
escapedParam = `b'${sqlEscaper(params.content)}'`;
reload = true;
}
@@ -169,8 +167,6 @@ export default (connections: {[key: string]: antares.Client}) => {
}
else {
const { orgRow } = params;
delete orgRow._antares_id;
reload = true;
for (const key in orgRow) {
@@ -198,8 +194,7 @@ export default (connections: {[key: string]: antares.Client}) => {
ipcMain.handle('delete-table-rows', async (event, params) => {
if (params.primary) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const idString = params.rows.map((row: {[key: string]: any}) => {
const idString = params.rows.map(row => {
const fieldName = Object.keys(row)[0].includes('.') ? `${params.table}.${params.primary}` : params.primary;
return typeof row[fieldName] === 'string'
@@ -246,12 +241,81 @@ export default (connections: {[key: string]: antares.Client}) => {
}
});
ipcMain.handle('insert-table-fake-rows', async (event, params: InsertRowsParams) => {
ipcMain.handle('insert-table-rows', async (event, params) => {
try { // TODO: move to client classes
const rows: {[key: string]: string | number | boolean | Date | Buffer}[] = [];
const insertObj = {};
for (const key in params.row) {
const type = params.fields[key];
let escapedParam;
if (params.row[key] === null)
escapedParam = 'NULL';
else if ([...NUMBER, ...FLOAT].includes(type))
escapedParam = +params.row[key];
else if ([...TEXT, ...LONG_TEXT].includes(type)) {
switch (connections[params.uid]._client) {
case 'mysql':
case 'maria':
escapedParam = `"${sqlEscaper(params.row[key].value)}"`;
break;
case 'pg':
escapedParam = `'${params.row[key].value.replaceAll('\'', '\'\'')}'`;
break;
}
}
else if (BLOB.includes(type)) {
if (params.row[key].value) {
let fileBlob;
switch (connections[params.uid]._client) {
case 'mysql':
case 'maria':
fileBlob = fs.readFileSync(params.row[key].value);
escapedParam = `0x${fileBlob.toString('hex')}`;
break;
case 'pg':
fileBlob = fs.readFileSync(params.row[key].value);
escapedParam = `decode('${fileBlob.toString('hex')}', 'hex')`;
break;
}
}
else {
switch (connections[params.uid]._client) {
case 'mysql':
case 'maria':
escapedParam = '""';
break;
case 'pg':
escapedParam = 'decode(\'\', \'hex\')';
break;
}
}
}
insertObj[key] = escapedParam;
}
const rows = new Array(+params.repeat).fill(insertObj);
await connections[params.uid]
.schema(params.schema)
.into(params.table)
.insert(rows)
.run();
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('insert-table-fake-rows', async (event, params) => {
try { // TODO: move to client classes
const rows = [];
for (let i = 0; i < +params.repeat; i++) {
const insertObj: {[key: string]: string | number | boolean | Date | Buffer} = {};
const insertObj = {};
for (const key in params.row) {
const type = params.fields[key];
@@ -269,7 +333,6 @@ export default (connections: {[key: string]: antares.Client}) => {
escapedParam = `"${sqlEscaper(params.row[key].value)}"`;
break;
case 'pg':
case 'sqlite':
escapedParam = `'${params.row[key].value.replaceAll('\'', '\'\'')}'`;
break;
}
@@ -310,7 +373,7 @@ export default (connections: {[key: string]: antares.Client}) => {
insertObj[key] = escapedParam;
}
else { // Faker value
const parsedParams: {[key: string]: string | number | boolean | Date | Buffer} = {};
const parsedParams = {};
let fakeValue;
if (params.locale)
@@ -321,16 +384,14 @@ export default (connections: {[key: string]: antares.Client}) => {
if (!isNaN(params.row[key].params[param]))
parsedParams[param] = +params.row[key].params[param];
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
fakeValue = (faker as any)[params.row[key].group][params.row[key].method](parsedParams);
fakeValue = faker[params.row[key].group][params.row[key].method](parsedParams);
}
else
// eslint-disable-next-line @typescript-eslint/no-explicit-any
fakeValue = (faker as any)[params.row[key].group][params.row[key].method]();
fakeValue = faker[params.row[key].group][params.row[key].method]();
if (typeof fakeValue === 'string') {
if (params.row[key].length)
fakeValue = fakeValue.substring(0, params.row[key].length);
fakeValue = fakeValue.substr(0, params.row[key].length);
fakeValue = `'${sqlEscaper(fakeValue)}'`;
}
else if ([...DATE, ...DATETIME].includes(type))

View File

@@ -1,7 +1,6 @@
import * as antares from 'common/interfaces/antares';
import { ipcMain } from 'electron';
export default (connections: {[key: string]: antares.Client}) => {
export default (connections) => {
ipcMain.handle('get-trigger-informations', async (event, params) => {
try {
const result = await connections[params.uid].getTriggerInformations(params);

View File

@@ -1,11 +1,11 @@
import { ipcMain } from 'electron';
import { autoUpdater } from 'electron-updater';
import * as Store from 'electron-store';
const persistentStore = new Store({ name: 'settings', clearInvalidConfig: true });
import Store from 'electron-store';
const persistentStore = new Store({ name: 'settings' });
const isMacOS = process.platform === 'darwin';
let mainWindow: Electron.IpcMainEvent;
autoUpdater.allowPrerelease = persistentStore.get('allow_prerelease', true) as boolean;
let mainWindow;
autoUpdater.allowPrerelease = persistentStore.get('allow_prerelease', true);
export default () => {
ipcMain.on('check-for-updates', event => {
@@ -50,7 +50,7 @@ export default () => {
mainWindow.reply('update-downloaded');
});
// autoUpdater.logger = require('electron-log');
// autoUpdater.logger.transports.console.format = '{h}:{i}:{s} {text}';
// autoUpdater.logger.transports.file.level = 'info';
autoUpdater.logger = require('electron-log');
autoUpdater.logger.transports.console.format = '{h}:{i}:{s} {text}';
autoUpdater.logger.transports.file.level = 'info';
};

View File

@@ -1,7 +1,6 @@
import * as antares from 'common/interfaces/antares';
import { ipcMain } from 'electron';
export default (connections: {[key: string]: antares.Client}) => {
export default (connections) => {
ipcMain.handle('get-users', async (event, uid) => {
try {
const result = await connections[uid].getUsers();

View File

@@ -1,7 +1,6 @@
import * as antares from 'common/interfaces/antares';
import { ipcMain } from 'electron';
export default (connections: {[key: string]: antares.Client}) => {
export default (connections) => {
ipcMain.handle('get-view-informations', async (event, params) => {
try {
const result = await connections[params.uid].getViewInformations(params);

View File

@@ -0,0 +1,149 @@
'use strict';
/**
* As Simple As Possible Query Builder Core
*
* @class AntaresCore
*/
export class AntaresCore {
/**
* Creates an instance of AntaresCore.
*
* @param {Object} args connection params
* @memberof AntaresCore
*/
constructor (args) {
this._client = args.client;
this._params = args.params;
this._poolSize = args.poolSize || false;
this._connection = null;
this._ssh = null;
this._logger = args.logger || console.log;
this._queryDefaults = {
schema: '',
select: [],
from: '',
where: [],
groupBy: [],
orderBy: [],
limit: [],
offset: [],
join: [],
update: [],
insert: [],
delete: false
};
this._query = Object.assign({}, this._queryDefaults);
}
_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;
}
}
}
/**
* Resets the query object after a query
*
* @memberof AntaresCore
*/
_resetQuery () {
this._query = Object.assign({}, this._queryDefaults);
}
schema (schema) {
this._query.schema = schema;
return this;
}
select (...args) {
this._query.select = [...this._query.select, ...args];
return this;
}
from (table) {
this._query.from = table;
return this;
}
into (table) {
this._query.from = table;
return this;
}
delete (table) {
this._query.delete = true;
this.from(table);
return this;
}
where (...args) {
this._query.where = [...this._query.where, ...args];
return this;
}
groupBy (...args) {
this._query.groupBy = [...this._query.groupBy, ...args];
return this;
}
orderBy (...args) {
this._query.orderBy = [...this._query.orderBy, ...args];
return this;
}
limit (...args) {
this._query.limit = args;
return this;
}
offset (...args) {
this._query.offset = args;
return this;
}
/**
* @param {String | Array} args field = value
* @returns
* @memberof AntaresCore
*/
update (...args) {
this._query.update = [...this._query.update, ...args];
return this;
}
/**
* @param {Array} arr Array of row objects
* @returns
* @memberof AntaresCore
*/
insert (arr) {
this._query.insert = [...this._query.insert, ...arr];
return this;
}
/**
* @param {Object} args
* @returns {Promise}
* @memberof AntaresCore
*/
run (args) {
const rawQuery = this.getSQL();
this._resetQuery();
return this.raw(rawQuery, args);
}
}

View File

@@ -1,265 +0,0 @@
import * as antares from 'common/interfaces/antares';
import { webContents } from 'electron';
import mysql from 'mysql2/promise';
import * as pg from 'pg';
import SSH2Promise from 'ssh2-promise';
const queryLogger = ({ sql, cUid }: {sql: string; cUid: string}) => {
// Remove comments, newlines and multiple spaces
const escapedSql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' ');
const mainWindow = webContents.fromId(1);
mainWindow.send('query-log', { cUid, sql: escapedSql, date: new Date() });
if (process.env.NODE_ENV === 'development') console.log(escapedSql);
};
/**
* As Simple As Possible Query Builder Core
*/
export class AntaresCore {
_client: antares.ClientCode;
protected _cUid: string
protected _params: mysql.ConnectionOptions | pg.ClientConfig | { databasePath: string; readonly: boolean};
protected _poolSize: number;
protected _ssh?: SSH2Promise;
protected _logger: (args: {sql: string; cUid: string}) => void;
protected _queryDefaults: antares.QueryBuilderObject;
protected _query: antares.QueryBuilderObject;
constructor (args: antares.ClientParams) {
this._client = args.client;
this._cUid = args.uid;
this._params = args.params;
this._poolSize = args.poolSize || undefined;
this._logger = args.logger || queryLogger;
this._queryDefaults = {
schema: '',
select: [],
from: '',
where: [],
groupBy: [],
orderBy: [],
limit: null,
offset: null,
join: [],
update: [],
insert: [],
delete: false
};
this._query = Object.assign({}, this._queryDefaults);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
protected _reducer (acc: string[], curr: any) {
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;
}
}
}
private _resetQuery () {
this._query = Object.assign({}, this._queryDefaults);
}
schema (schema: string) {
this._query.schema = schema;
return this;
}
select (...args: string[]) {
this._query.select = [...this._query.select, ...args];
return this;
}
from (table: string) {
this._query.from = table;
return this;
}
into (table: string) {
this._query.from = table;
return this;
}
delete (table: string) {
this._query.delete = true;
this.from(table);
return this;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
where (...args: any) {
this._query.where = [...this._query.where, ...args];
return this;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
groupBy (...args: any) {
this._query.groupBy = [...this._query.groupBy, ...args];
return this;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
orderBy (...args: any) {
this._query.orderBy = [...this._query.orderBy, ...args];
return this;
}
limit (limit: number) {
this._query.limit = limit;
return this;
}
offset (offset: number) {
this._query.offset = offset;
return this;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
update (...args: any) {
this._query.update = [...this._query.update, ...args];
return this;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
insert (arr: {[key: string]: any}[]) {
this._query.insert = [...this._query.insert, ...arr];
return this;
}
getSQL (): string {
throw new Error('Client must implement the "getSQL" method');
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
raw<T = antares.QueryResult> (_sql: string, _args?: antares.QueryParams): Promise<T> {
throw new Error('Client must implement the "raw" method');
}
run<RowType> (args?: antares.QueryParams) {
const rawQuery = this.getSQL();
this._resetQuery();
return this.raw<antares.QueryResult<RowType>>(rawQuery, args);
}
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
getDbConfig () {
throw new Error('Method "getDbConfig" not implemented');
}
createSchema (...args: any) {
throw new Error('Method "createSchema" not implemented');
}
alterSchema (...args: any) {
throw new Error('Method "alterSchema" not implemented');
}
dropSchema (...args: any) {
throw new Error('Method "dropSchema" not implemented');
}
getDatabaseCollation (...args: any) {
throw new Error('Method "getDatabaseCollation" not implemented');
}
getFunctionInformations (...args: any) {
throw new Error('Method "getFunctionInformations" not implemented');
}
alterFunction (...args: any) {
throw new Error('Method "alterFunction" not implemented');
}
createTriggerFunction (...args: any) {
throw new Error('Method "createTriggerFunction" not implemented');
}
alterTriggerFunction (...args: any) {
throw new Error('Method "alterTriggerFunction" not implemented');
}
createFunction (...args: any) {
throw new Error('Method "createFunction" not implemented');
}
dropFunction (...args: any) {
throw new Error('Method "dropFunction" not implemented');
}
getCollations () {
throw new Error('Method "getCollations" not implemented');
}
getRoutineInformations (...args: any) {
throw new Error('Method "getRoutineInformations" not implemented');
}
dropRoutine (...args: any) {
throw new Error('Method "dropRoutine" not implemented');
}
alterRoutine (...args: any) {
throw new Error('Method "alterRoutine" not implemented');
}
createRoutine (...args: any) {
throw new Error('Method "createRoutine" not implemented');
}
getVariables () {
throw new Error('Method "getVariables" not implemented');
}
getEventInformations (...args: any) {
throw new Error('Method "getEventInformations" not implemented');
}
dropEvent (...args: any) {
throw new Error('Method "dropEvent" not implemented');
}
alterEvent (...args: any) {
throw new Error('Method "alterEvent" not implemented');
}
createEvent (...args: any) {
throw new Error('Method "createEvent" not implemented');
}
enableEvent (...args: any) {
throw new Error('Method "enableEvent" not implemented');
}
disableEvent (...args: any) {
throw new Error('Method "disableEvent" not implemented');
}
enableTrigger (...args: any) {
throw new Error('Method "enableTrigger" not implemented');
}
disableTrigger (...args: any) {
throw new Error('Method "disableTrigger" not implemented');
}
killTabQuery (...args: any) {
throw new Error('Method "killTabQuery" not implemented');
}
/* eslint-enable @typescript-eslint/no-unused-vars */
/* eslint-enable @typescript-eslint/no-explicit-any */
}

View File

@@ -0,0 +1,47 @@
'use strict';
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.
*
* @param {Object} args
* @param {String} args.client
* @param {Object} args.params
* @param {String} args.params.host
* @param {Number} args.params.port
* @param {String} args.params.password
* @param {String=} args.params.database
* @param {String=} args.params.schema
* @param {String} args.params.ssh.host
* @param {String} args.params.ssh.username
* @param {String} args.params.ssh.password
* @param {Number} args.params.ssh.port
* @param {Number=} args.poolSize
* @returns Database Connection
* @memberof ClientsFactory
*/
static getConnection (args) {
args.logger = queryLogger;
switch (args.client) {
case 'mysql':
case 'maria':
return new MySQLClient(args);
case 'pg':
return new PostgreSQLClient(args);
case 'sqlite':
return new SQLiteClient(args);
default:
throw new Error(`Unknown database client: ${args.client}`);
}
}
}

View File

@@ -1,20 +0,0 @@
import * as antares from 'common/interfaces/antares';
import { MySQLClient } from './clients/MySQLClient';
import { PostgreSQLClient } from './clients/PostgreSQLClient';
import { SQLiteClient } from './clients/SQLiteClient';
export class ClientsFactory {
static getClient (args: antares.ClientParams) {
switch (args.client) {
case 'mysql':
case 'maria':
return new MySQLClient(args);
case 'pg':
return new PostgreSQLClient(args);
case 'sqlite':
return new SQLiteClient(args);
default:
throw new Error(`Unknown database client: ${args.client}`);
}
}
}

View File

@@ -1,77 +1,66 @@
import * as antares from 'common/interfaces/antares';
import * as sqlite from 'better-sqlite3';
'use strict';
import sqlite from 'better-sqlite3';
import { AntaresCore } from '../AntaresCore';
import dataTypes from 'common/data-types/sqlite';
import dataTypes from 'common/data-types/mysql';
import { NUMBER, FLOAT, TIME, DATETIME } from 'common/fieldTypes';
export class SQLiteClient extends AntaresCore {
private _schema?: string;
private _connectionsToCommit: Map<string, sqlite.Database>;
protected _connection?: sqlite.Database;
_params: { databasePath: string; readonly: boolean};
constructor (args: antares.ClientParams) {
constructor (args) {
super(args);
this._schema = null;
this._connectionsToCommit = new Map();
}
getTypeInfo (type: string): antares.TypeInformations {
_getTypeInfo (type) {
return dataTypes
.reduce((acc, group) => [...acc, ...group.types], [])
.filter(_type => _type.name === type.toUpperCase())[0];
}
/**
* @memberof SQLiteClient
*/
async connect () {
this._connection = this.getConnection();
}
getConnection () {
return sqlite(this._params.databasePath, {
this._connection = sqlite(this._params.databasePath, {
fileMustExist: true,
readonly: this._params.readonly
});
}
destroy (): void {
return null;
}
/**
* @memberof SQLiteClient
*/
destroy () {}
use (): void {
return null;
}
/**
* Executes an USE query
*
* @memberof SQLiteClient
*/
use () {}
async getStructure (schemas: Set<string>) {
/* eslint-disable camelcase */
interface ShowTableResult {
Db?: string;
type: string;
name: string;
tbl_name: string;
rootpage:4;
sql: string;
}
type ShowTriggersResult = ShowTableResult
/* eslint-enable camelcase */
const { rows: databases } = await this.raw<antares.QueryResult<{ name: string}>>('SELECT * FROM pragma_database_list');
/**
* @param {Array} schemas list
* @returns {Array.<Object>} databases scructure
* @memberof SQLiteClient
*/
async getStructure (schemas) {
const { rows: databases } = await this.raw('SELECT * FROM pragma_database_list');
const filteredDatabases = databases;
const tablesArr: ShowTableResult[] = [];
const triggersArr: ShowTriggersResult[] = [];
const tablesArr = [];
const triggersArr = [];
let schemaSize = 0;
for (const db of filteredDatabases) {
if (!schemas.has(db.name)) continue;
let { rows: tables } = await this.raw<antares.QueryResult<ShowTableResult>>(`
let { rows: tables } = await this.raw(`
SELECT *
FROM "${db.name}".sqlite_master
WHERE type IN ('table', 'view')
AND name NOT LIKE 'sqlite\\_%' ESCAPE '\\'
AND name NOT LIKE 'sqlite_%'
ORDER BY name
`);
if (tables.length) {
@@ -82,7 +71,7 @@ export class SQLiteClient extends AntaresCore {
tablesArr.push(...tables);
}
let { rows: triggers } = await this.raw<antares.QueryResult<ShowTriggersResult>>(`SELECT * FROM "${db.name}".sqlite_master WHERE type='trigger'`);
let { rows: triggers } = await this.raw(`SELECT * FROM "${db.name}".sqlite_master WHERE type='trigger'`);
if (triggers.length) {
triggers = triggers.map(trigger => {
trigger.Db = db.name;
@@ -139,24 +128,22 @@ export class SQLiteClient extends AntaresCore {
});
}
async getTableColumns ({ schema, table }: { schema: string; table: string }) {
interface TableColumnsResult {
cid: number;
name: string;
type: string;
notnull: 0 | 1;
// eslint-disable-next-line camelcase
dflt_value: string;
pk: 0 | 1;
}
const { rows: fields } = await this.raw<antares.QueryResult<TableColumnsResult>>(`SELECT * FROM "${schema}".pragma_table_info('${table}')`);
/**
* @param {Object} params
* @param {String} params.schema
* @param {String} params.table
* @returns {Object} table scructure
* @memberof SQLiteClient
*/
async getTableColumns ({ schema, table }) {
const { rows: fields } = await this.raw(`SELECT * FROM "${schema}".pragma_table_info('${table}')`);
return fields.map(field => {
const [type, length]: [string, number?] = field.type.includes('(')
? field.type.replace(')', '').split('(').map((el: string | number) => {
if (!isNaN(Number(el))) el = Number(el);
const [type, length] = field.type.includes('(')
? field.type.replace(')', '').split('(').map(el => {
if (!isNaN(el)) el = +el;
return el;
}) as [string, number?]
})
: [field.type, null];
return {
@@ -171,7 +158,7 @@ export class SQLiteClient extends AntaresCore {
nullable: !field.notnull,
unsigned: null,
zerofill: null,
order: typeof field.cid === 'string' ? +field.cid + 1 : field.cid + 1,
order: field.cid + 1,
default: field.dflt_value,
charset: null,
collation: null,
@@ -182,50 +169,54 @@ export class SQLiteClient extends AntaresCore {
});
}
async getTableApproximateCount ({ schema, table }: { schema: string; table: string }): Promise<number> {
/**
* @param {Object} params
* @param {String} params.schema
* @param {String} params.table
* @returns {Object} table row count
* @memberof SQLiteClient
*/
async getTableApproximateCount ({ schema, table }) {
const { rows } = await this.raw(`SELECT COUNT(*) AS count FROM "${schema}"."${table}"`);
return rows.length ? rows[0].count : 0;
}
async getTableOptions ({ table }: { table: string }) {
/**
* @param {Object} params
* @param {String} params.schema
* @param {String} params.table
* @returns {Object} table options
* @memberof SQLiteClient
*/
async getTableOptions ({ schema, table }) {
return { name: table };
}
async getTableIndexes ({ schema, table }: { schema: string; table: string }) {
interface TableColumnsResult {
type: string;
name: string;
// eslint-disable-next-line camelcase
tbl_name: string;
rootpage:4;
sql: string;
}
interface ShowIndexesResult {
seq: number;
name: string;
unique: 0 | 1;
origin: string;
partial: 0 | 1;
}
/**
* @param {Object} params
* @param {String} params.schema
* @param {String} params.table
* @returns {Object} table indexes
* @memberof SQLiteClient
*/
async getTableIndexes ({ schema, table }) {
const remappedIndexes = [];
const { rows: primaryKeys } = await this.raw<antares.QueryResult<TableColumnsResult>>(`SELECT * FROM "${schema}".pragma_table_info('${table}') WHERE pk != 0`);
const { rows: primaryKeys } = await this.raw(`SELECT * FROM "${schema}".pragma_table_info('${table}') WHERE pk != 0`);
for (const key of primaryKeys) {
remappedIndexes.push({
name: 'PRIMARY',
column: key.name,
indexType: null as never,
indexType: null,
type: 'PRIMARY',
cardinality: null as never,
cardinality: null,
comment: '',
indexComment: ''
});
}
const { rows: indexes } = await this.raw<antares.QueryResult<ShowIndexesResult>>(`SELECT * FROM "${schema}".pragma_index_list('${table}');`);
const { rows: indexes } = await this.raw(`SELECT * FROM "${schema}".pragma_index_list('${table}');`);
for (const index of indexes) {
const { rows: details } = await this.raw(`SELECT * FROM "${schema}".pragma_index_info('${index.name}');`);
@@ -234,9 +225,9 @@ export class SQLiteClient extends AntaresCore {
remappedIndexes.push({
name: index.name,
column: detail.name,
indexType: null as never,
indexType: null,
type: index.unique === 1 ? 'UNIQUE' : 'INDEX',
cardinality: null as never,
cardinality: null,
comment: '',
indexComment: ''
});
@@ -246,19 +237,15 @@ export class SQLiteClient extends AntaresCore {
return remappedIndexes;
}
async getKeyUsage ({ schema, table }: { schema: string; table: string }) {
/* eslint-disable camelcase */
interface KeyResult {
from: string;
id: number;
table: string;
to: string;
on_update: string;
on_delete: string;
}
/* eslint-enable camelcase */
const { rows } = await this.raw<antares.QueryResult<KeyResult>>(`SELECT * FROM "${schema}".pragma_foreign_key_list('${table}');`);
/**
* @param {Object} params
* @param {String} params.schema
* @param {String} params.table
* @returns {Object} table key usage
* @memberof SQLiteClient
*/
async getKeyUsage ({ schema, table }) {
const { rows } = await this.raw(`SELECT * FROM "${schema}".pragma_foreign_key_list('${table}');`);
return rows.map(field => {
return {
@@ -277,11 +264,195 @@ export class SQLiteClient extends AntaresCore {
});
}
async getUsers (): Promise<void> {
return null;
async getUsers () {}
/**
* SHOW CREATE VIEW
*
* @returns {Array.<Object>} view informations
* @memberof SQLiteClient
*/
async getViewInformations ({ schema, view }) {
const sql = `SELECT "sql" FROM "${schema}".sqlite_master WHERE "type"='view' AND name='${view}'`;
const results = await this.raw(sql);
return results.rows.map(row => {
return {
sql: row.sql.match(/(?<=AS ).*?$/gs)[0],
name: view
};
})[0];
}
async createTable (params: antares.CreateTableParams) {
/**
* DROP VIEW
*
* @returns {Array.<Object>} parameters
* @memberof SQLiteClient
*/
async dropView (params) {
const sql = `DROP VIEW "${params.schema}"."${params.view}"`;
return await this.raw(sql);
}
/**
* ALTER VIEW
*
* @returns {Array.<Object>} parameters
* @memberof SQLiteClient
*/
async alterView (params) {
const { view } = params;
try {
await this.dropView({ schema: view.schema, view: view.oldName });
await this.createView(view);
}
catch (err) {
return Promise.reject(err);
}
}
/**
* CREATE VIEW
*
* @returns {Array.<Object>} parameters
* @memberof SQLiteClient
*/
async createView (params) {
const sql = `CREATE VIEW "${params.schema}"."${params.name}" AS ${params.sql}`;
return await this.raw(sql);
}
/**
* SHOW CREATE TRIGGER
*
* @returns {Array.<Object>} view informations
* @memberof SQLiteClient
*/
async getTriggerInformations ({ schema, trigger }) {
const sql = `SELECT "sql" FROM "${schema}".sqlite_master WHERE "type"='trigger' AND name='${trigger}'`;
const results = await this.raw(sql);
return results.rows.map(row => {
return {
sql: row.sql.match(/(BEGIN|begin)(.*)(END|end)/gs)[0],
name: trigger,
table: row.sql.match(/(?<=ON `).*?(?=`)/gs)[0],
activation: row.sql.match(/(BEFORE|AFTER)/gs)[0],
event: row.sql.match(/(INSERT|UPDATE|DELETE)/gs)[0]
};
})[0];
}
/**
* DROP TRIGGER
*
* @returns {Array.<Object>} parameters
* @memberof SQLiteClient
*/
async dropTrigger (params) {
const sql = `DROP TRIGGER \`${params.schema}\`.\`${params.trigger}\``;
return await this.raw(sql);
}
/**
* ALTER TRIGGER
*
* @returns {Array.<Object>} parameters
* @memberof SQLiteClient
*/
async alterTrigger (params) {
const { trigger } = params;
const tempTrigger = Object.assign({}, trigger);
tempTrigger.name = `Antares_${tempTrigger.name}_tmp`;
try {
await this.createTrigger(tempTrigger);
await this.dropTrigger({ schema: trigger.schema, trigger: tempTrigger.name });
await this.dropTrigger({ schema: trigger.schema, trigger: trigger.oldName });
await this.createTrigger(trigger);
}
catch (err) {
return Promise.reject(err);
}
}
/**
* CREATE TRIGGER
*
* @returns {Array.<Object>} parameters
* @memberof SQLiteClient
*/
async createTrigger (params) {
const sql = `CREATE ${params.definer ? `DEFINER=${params.definer} ` : ''}TRIGGER \`${params.schema}\`.\`${params.name}\` ${params.activation} ${params.event} ON \`${params.table}\` FOR EACH ROW ${params.sql}`;
return await this.raw(sql, { split: false });
}
/**
* SHOW COLLATION
*
* @returns {Array.<Object>} collations list
* @memberof SQLiteClient
*/
async getCollations () {
return [];
}
/**
* SHOW VARIABLES
*
* @returns {Array.<Object>} variables list
* @memberof SQLiteClient
*/
async getVariables () {
return [];
}
/**
* SHOW ENGINES
*
* @returns {Array.<Object>} engines list
* @memberof SQLiteClient
*/
async getEngines () {
return {
name: 'SQLite',
support: 'YES',
comment: '',
isDefault: true
};
}
/**
* SHOW VARIABLES LIKE '%vers%'
*
* @returns {Array.<Object>} version parameters
* @memberof SQLiteClient
*/
async getVersion () {
const os = require('os');
const sql = 'SELECT sqlite_version() AS version';
const { rows } = await this.raw(sql);
return {
number: rows[0].version,
name: 'SQLite',
arch: process.arch,
os: `${os.type()} ${os.release()}`
};
}
async getProcesses () {}
async killProcess () {}
/**
* CREATE TABLE
*
* @returns {Promise<null>}
* @memberof SQLiteClient
*/
async createTable (params) {
const {
schema,
fields,
@@ -289,24 +460,24 @@ export class SQLiteClient extends AntaresCore {
indexes,
options
} = params;
const newColumns: string[] = [];
const newIndexes: string[] = [];
const manageIndexes: string[] = [];
const newForeigns: string[] = [];
const newColumns = [];
const newIndexes = [];
const manageIndexes = [];
const newForeigns = [];
let sql = `CREATE TABLE "${schema}"."${options.name}"`;
// ADD FIELDS
fields.forEach(field => {
const typeInfo = this.getTypeInfo(field.type);
const typeInfo = this._getTypeInfo(field.type);
const length = typeInfo?.length ? field.enumValues || field.numLength || field.charLength || field.datePrecision : false;
newColumns.push(`"${field.name}"
${field.type.toUpperCase()}${length ? `(${length})` : ''}
${field.type.toUpperCase()}${length && length !== true ? `(${length})` : ''}
${field.unsigned ? 'UNSIGNED' : ''}
${field.nullable ? 'NULL' : 'NOT NULL'}
${field.autoIncrement ? 'AUTO_INCREMENT' : ''}
${field.default !== null ? `DEFAULT ${field.default || '\'\''}` : ''}
${field.default ? `DEFAULT ${field.default}` : ''}
${field.onUpdate ? `ON UPDATE ${field.onUpdate}` : ''}`);
});
@@ -332,37 +503,34 @@ export class SQLiteClient extends AntaresCore {
return await this.raw(sql);
}
async alterTable (params: antares.AlterTableParams) {
const {
table,
schema,
additions,
deletions,
changes,
tableStructure
} = params;
/**
* ALTER TABLE
*
* @returns {Promise<null>}
* @memberof SQLiteClient
*/
async alterTable (params) {
try {
await this.raw('BEGIN TRANSACTION');
await this.raw('PRAGMA foreign_keys = 0');
const tmpName = `Antares_${table}_tmp`;
await this.raw(`CREATE TABLE "${tmpName}" AS SELECT * FROM "${table}"`);
const tmpName = `Antares_${params.table}_tmp`;
await this.raw(`CREATE TABLE "${tmpName}" AS SELECT * FROM "${params.table}"`);
await this.dropTable(params);
const createTableParams = {
schema: schema,
fields: tableStructure.fields,
foreigns: tableStructure.foreigns,
indexes: tableStructure.indexes.filter(index => !index.name.includes('sqlite_autoindex')),
options: { name: tableStructure.name }
schema: params.schema,
fields: params.tableStructure.fields,
foreigns: params.tableStructure.foreigns,
indexes: params.tableStructure.indexes.filter(index => !index.name.includes('sqlite_autoindex')),
options: { name: params.tableStructure.name }
};
await this.createTable(createTableParams);
const insertFields = createTableParams.fields
.filter(field => {
return (
additions.every(add => add.name !== field.name) &&
deletions.every(del => del.name !== field.name)
params.additions.every(add => add.name !== field.name) &&
params.deletions.every(del => del.name !== field.name)
);
})
.reduce((acc, curr) => {
@@ -371,7 +539,7 @@ export class SQLiteClient extends AntaresCore {
}, []);
const selectFields = insertFields.map(field => {
const renamedField = changes.find(change => `"${change.name}"` === field);
const renamedField = params.changes.find(change => `"${change.name}"` === field);
if (renamedField)
return `"${renamedField.orgName}"`;
return field;
@@ -379,7 +547,7 @@ export class SQLiteClient extends AntaresCore {
await this.raw(`INSERT INTO "${createTableParams.options.name}" (${insertFields.join(',')}) SELECT ${selectFields.join(',')} FROM "${tmpName}"`);
await this.dropTable({ schema: schema, table: tmpName });
await this.dropTable({ schema: params.schema, table: tmpName });
await this.raw('PRAGMA foreign_keys = 1');
await this.raw('COMMIT');
}
@@ -389,147 +557,43 @@ export class SQLiteClient extends AntaresCore {
}
}
async duplicateTable (params: { schema: string; table: string }) { // TODO: retrive table informations and create a copy
/**
* DUPLICATE TABLE
*
* @returns {Promise<null>}
* @memberof SQLiteClient
*/
async duplicateTable (params) { // TODO: retrive table informations and create a copy
const sql = `CREATE TABLE "${params.schema}"."${params.table}_copy" AS SELECT * FROM "${params.schema}"."${params.table}"`;
return await this.raw(sql);
}
async truncateTable (params: { schema: string; table: string }) {
/**
* TRUNCATE TABLE
*
* @returns {Promise<null>}
* @memberof SQLiteClient
*/
async truncateTable (params) {
const sql = `DELETE FROM "${params.schema}"."${params.table}"`;
return await this.raw(sql);
}
async dropTable (params: { schema: string; table: string }) {
/**
* DROP TABLE
*
* @returns {Promise<null>}
* @memberof SQLiteClient
*/
async dropTable (params) {
const sql = `DROP TABLE "${params.schema}"."${params.table}"`;
return await this.raw(sql);
}
async getViewInformations ({ schema, view }: { schema: string; view: string }) {
const sql = `SELECT "sql" FROM "${schema}".sqlite_master WHERE "type"='view' AND name='${view}'`;
const results = await this.raw(sql);
return results.rows.map(row => {
return {
sql: row.sql.match(/(?<=AS ).*?$/gs)[0],
name: view
};
})[0];
}
async dropView (params: { schema: string; view: string }) {
const sql = `DROP VIEW "${params.schema}"."${params.view}"`;
return await this.raw(sql);
}
async alterView ({ view }: { view: antares.AlterViewParams }) {
try {
await this.dropView({ schema: view.schema, view: view.oldName });
await this.createView(view);
}
catch (err) {
return Promise.reject(err);
}
}
async createView (params: antares.CreateViewParams) {
const sql = `CREATE VIEW "${params.schema}"."${params.name}" AS ${params.sql}`;
return await this.raw(sql);
}
async getTriggerInformations ({ schema, trigger }: { schema: string; trigger: string }) {
const sql = `SELECT "sql" FROM "${schema}".sqlite_master WHERE "type"='trigger' AND name='${trigger}'`;
const results = await this.raw(sql);
return results.rows.map(row => {
return {
sql: row.sql.match(/(BEGIN|begin)(.*)(END|end)/gs)[0],
name: trigger,
table: row.sql.match(/(?<=ON `).*?(?=`)/gs)[0],
activation: row.sql.match(/(BEFORE|AFTER)/gs)[0],
event: row.sql.match(/(INSERT|UPDATE|DELETE)/gs)[0]
};
})[0];
}
async dropTrigger (params: { schema: string; trigger: string }) {
const sql = `DROP TRIGGER \`${params.schema}\`.\`${params.trigger}\``;
return await this.raw(sql);
}
async alterTrigger ({ trigger } : {trigger: antares.AlterTriggerParams}) {
const tempTrigger = Object.assign({}, trigger);
tempTrigger.name = `Antares_${tempTrigger.name}_tmp`;
try {
await this.createTrigger(tempTrigger);
await this.dropTrigger({ schema: trigger.schema, trigger: tempTrigger.name });
await this.dropTrigger({ schema: trigger.schema, trigger: trigger.oldName });
await this.createTrigger(trigger);
}
catch (err) {
return Promise.reject(err);
}
}
async createTrigger (params: antares.CreateTriggerParams) {
const sql = `CREATE ${params.definer ? `DEFINER=${params.definer} ` : ''}TRIGGER \`${params.schema}\`.\`${params.name}\` ${params.activation} ${params.event} ON \`${params.table}\` FOR EACH ROW ${params.sql}`;
return await this.raw(sql, { split: false });
}
async getEngines () {
return {
name: 'SQLite',
support: 'YES',
comment: '',
isDefault: true
};
}
async getVersion () {
const os = require('os');
const sql = 'SELECT sqlite_version() AS version';
const { rows } = await this.raw(sql);
return {
number: rows[0].version,
name: 'SQLite',
arch: process.arch,
os: `${os.type()} ${os.release()}`
};
}
async getProcesses (): Promise<void> {
return null;
}
async killProcess (): Promise<void> {
return null;
}
async commitTab (tabUid: string) {
const connection = this._connectionsToCommit.get(tabUid);
if (connection) {
connection.prepare('COMMIT').run();
return this.destroyConnectionToCommit(tabUid);
}
}
async rollbackTab (tabUid: string) {
const connection = this._connectionsToCommit.get(tabUid);
if (connection) {
connection.prepare('ROLLBACK').run();
return this.destroyConnectionToCommit(tabUid);
}
}
destroyConnectionToCommit (tabUid: string) {
const connection = this._connectionsToCommit.get(tabUid);
if (connection) {
connection.close();
this._connectionsToCommit.delete(tabUid);
}
}
/**
* @returns {String} SQL string
* @memberof SQLiteClient
*/
getSQL () {
// SELECT
const selectArray = this._query.select.reduce(this._reducer, []);
@@ -577,23 +641,31 @@ export class SQLiteClient extends AntaresCore {
const orderByRaw = orderByArray.length ? `ORDER BY ${orderByArray.join(', ')} ` : '';
// LIMIT
const limitRaw = this._query.limit ? `LIMIT ${this._query.limit} ` : '';
const limitRaw = this._query.limit.length ? `LIMIT ${this._query.limit.join(', ')} ` : '';
// OFFSET
const offsetRaw = this._query.offset ? `OFFSET ${this._query.offset} ` : '';
const offsetRaw = this._query.offset.length ? `OFFSET ${this._query.offset.join(', ')} ` : '';
return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${offsetRaw}${insertRaw}`;
}
async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) {
this._logger({ cUid: this._cUid, sql });// TODO: replace BLOB content with a placeholder
/**
* @param {string} sql raw SQL query
* @param {object} args
* @param {boolean} args.nest
* @param {boolean} args.details
* @param {boolean} args.split
* @returns {Promise}
* @memberof SQLiteClient
*/
async raw (sql, args) {
if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder
args = {
nest: false,
details: false,
split: true,
comments: true,
autocommit: true,
...args
};
@@ -607,52 +679,37 @@ export class SQLiteClient extends AntaresCore {
.filter(Boolean)
.map(q => q.trim())
: [sql];
let connection: sqlite.Database;
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;
const connection = this._connection;
for (const query of queries) {
if (!query) continue;
const timeStart = new Date();
let timeStop;
const keysArr: antares.QueryForeign[] = [];
const keysArr = [];
const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => {
(async () => {
let queryRunResult: sqlite.RunResult;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let queryAllResult: any[];
let queryResult;
let affectedRows;
let fields;
const detectedTypes: {[key: string]: string} = {};
const detectedTypes = {};
try {
const stmt = connection.prepare(query);
if (stmt.reader) {
queryAllResult = stmt.all();
queryResult = stmt.all();
fields = stmt.columns();
if (queryAllResult.length) {
if (queryResult.length) {
fields.forEach(field => {
detectedTypes[field.name] = typeof queryAllResult[0][field.name];
detectedTypes[field.name] = typeof queryResult[0][field.name];
});
}
}
else {
queryRunResult = stmt.run();
affectedRows = queryRunResult.changes;
const info = queryResult = stmt.run();
affectedRows = info.changes;
}
}
catch (err) {
@@ -663,19 +720,19 @@ export class SQLiteClient extends AntaresCore {
let remappedFields = fields
? fields.map(field => {
let [parsedType, length]: [string, number?] = field.type?.includes('(')
? field.type.replace(')', '').split('(').map((el: string | number) => {
if (!isNaN(Number(el)))
el = Number(el);
let [parsedType, length] = field.type?.includes('(')
? field.type.replace(')', '').split('(').map(el => {
if (!isNaN(el))
el = +el;
else
el = (el as string).trim();
el = el.trim();
return el;
}) as [string, number?]
})
: [field.type, null];
if ([...TIME, ...DATETIME].includes(parsedType)) {
const firstNotNull = queryAllResult.find(res => res[field.name] !== null);
if (firstNotNull && firstNotNull[field.name].includes('.'))
const firstNotNull = queryResult.find(res => res[field.name] !== null);
if (firstNotNull[field.name].includes('.'))
length = firstNotNull[field.name].split('.').pop().length;
}
@@ -688,8 +745,7 @@ export class SQLiteClient extends AntaresCore {
tableAlias: field.table,
orgTable: field.table,
type: field.type !== null ? parsedType : detectedTypes[field.name],
length,
key: undefined as string
length
};
}).filter(Boolean)
: [];
@@ -709,12 +765,18 @@ export class SQLiteClient extends AntaresCore {
const indexes = await this.getTableIndexes(paramObj);
remappedFields = remappedFields.map(field => {
// const detailedField = columns.find(f => f.name === field.name);
const fieldIndex = indexes.find(i => i.column === field.name);
if (field.table === paramObj.table && field.schema === paramObj.schema) {
// if (detailedField) {
// const length = detailedField.numPrecision || detailedField.charLength || detailedField.datePrecision || null;
// field = { ...field, ...detailedField, length };
// }
if (fieldIndex) {
const key = fieldIndex.type === 'PRIMARY' ? 'pri' : fieldIndex.type === 'UNIQUE' ? 'uni' : 'mul';
field = { ...field, key };
}
};
}
return field;
@@ -727,8 +789,8 @@ export class SQLiteClient extends AntaresCore {
}
resolve({
duration: timeStop.getTime() - timeStart.getTime(),
rows: Array.isArray(queryAllResult) ? queryAllResult.some(el => Array.isArray(el)) ? [] : queryAllResult : false,
duration: timeStop - timeStart,
rows: Array.isArray(queryResult) ? queryResult.some(el => Array.isArray(el)) ? [] : queryResult : false,
report: affectedRows !== undefined ? { affectedRows } : null,
fields: remappedFields,
keys: keysArr
@@ -739,16 +801,6 @@ export class SQLiteClient extends AntaresCore {
resultsArr.push({ rows, report, fields, keys, duration });
}
const result = resultsArr.length === 1 ? resultsArr[0] : resultsArr;
return result as unknown as T;
}
getVariables (): null[] {
return [];
}
getCollations (): null[] {
return [];
return resultsArr.length === 1 ? resultsArr[0] : resultsArr;
}
}

View File

@@ -1,93 +0,0 @@
import * as exporter from 'common/interfaces/exporter';
import * as fs from 'fs';
import { createGzip, Gzip } from 'zlib';
import * as path from 'path';
import * as EventEmitter from 'events';
export class BaseExporter extends EventEmitter {
protected _tables;
protected _options;
protected _isCancelled;
protected _outputFileStream: fs.WriteStream;
protected _processedStream: fs.WriteStream | Gzip;
protected _state;
constructor (tables: exporter.TableParams[], options: exporter.ExportOptions) {
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: exporter.ExportState) {
this.emit('progress', { ...this._state, ...state });
}
writeString (data: string) {
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');
}
}

View File

@@ -1,451 +0,0 @@
import * as exporter from 'common/interfaces/exporter';
import * as mysql from 'mysql2/promise';
import { SqlExporter } from './SqlExporter';
import { BLOB, BIT, DATE, DATETIME, FLOAT, SPATIAL, IS_MULTI_SPATIAL, NUMBER } from 'common/fieldTypes';
import hexToBinary, { HexChar } from 'common/libs/hexToBinary';
import { getArrayDepth } from 'common/libs/getArrayDepth';
import * as moment from 'moment';
import { lineString, point, polygon } from '@turf/helpers';
import { MySQLClient } from '../../clients/MySQLClient';
export default class MysqlExporter extends SqlExporter {
protected _client: MySQLClient;
constructor (client: MySQLClient, tables: exporter.TableParams[], options: exporter.ExportOptions) {
super(tables, options);
this._client = client;
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: string) {
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: string) {
return `DROP TABLE IF EXISTS \`${tableName}\`;`;
}
async * getTableInsert (tableName: string) {
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 (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.datePrecision; 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') as undefined as HexChar[])}'`;
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._getGeoJSON(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 = '';
sqlString += this.buildComment('Creating temporary tables to overcome VIEW dependency errors');
// Temporary tables
for (const view of views) {
const viewFields = await this._client.getTableColumns({ schema: this.schemaName, table: view.Name });
const tableFields: string[] = [];
for (const field of viewFields) {
const typeInfo = this._client.getTypeInfo(field.type);
const length = typeInfo.length ? field.enumValues || field.numLength || field.charLength || field.datePrecision : false;
tableFields.push(`\`${field.name}\` ${field.type.toUpperCase()}${length ? `(${length}${field.numScale ? `,${field.numScale}` : ''})` : ''} ${field.unsigned ? 'UNSIGNED' : ''} ${field.zerofill ? 'ZEROFILL' : ''} ${field.nullable ? 'NULL' : 'NOT NULL'} ${field.autoIncrement ? 'AUTO_INCREMENT' : ''} ${field.collation ? `COLLATE ${field.collation}` : ''}`);
}
sqlString +=
`
CREATE TABLE \`${view.Name}\`(
${tableFields.join(',\n\t')}
);`;
sqlString += '\n';
}
sqlString += '\n';
for (const view of views) {
sqlString += `DROP TABLE IF EXISTS \`${view.Name}\`;\n`;
const viewSyntax = await this.getCreateTable(view.Name);
sqlString += viewSyntax.replaceAll('`' + this.schemaName + '`.', '');
sqlString += '\n\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: string, type: string, definer: string) {
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: string) {
if (process.env.NODE_ENV === 'development') console.log('EXPORTER:', sql);
const isPool = 'getConnection' in this._client._connection;
const connection = isPool ? await (this._client._connection as mysql.Pool).getConnection() : this._client._connection;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const stream = (connection as any).connection.query(sql).stream();
const dispose = () => (connection as mysql.PoolConnection).release();
stream.on('end', dispose);
stream.on('error', dispose);
stream.on('close', dispose);
return stream;
}
getEscapedDefiner (definer: string) {
return definer
.split('@')
.map(part => '`' + part + '`')
.join('@');
}
escapeAndQuote (val: string) {
// eslint-disable-next-line no-control-regex
const CHARS_TO_ESCAPE = /[\0\b\t\n\r\x1a"'\\]/g;
const CHARS_ESCAPE_MAP: {[key: string]: string} = {
'\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}'`;
}
/* eslint-disable @typescript-eslint/no-explicit-any */
_getGeoJSON (val: any) {
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: any, curr: any) => [...acc, [curr.x, curr.y]], [])));
}
else
return point([val.x, val.y]);
}
/* eslint-enable @typescript-eslint/no-explicit-any */
}

View File

@@ -1,513 +0,0 @@
import * as antares from 'common/interfaces/antares';
import * as exporter from 'common/interfaces/exporter';
import { SqlExporter } from './SqlExporter';
import { BLOB, BIT, DATE, DATETIME, FLOAT, NUMBER, TEXT_SEARCH } from 'common/fieldTypes';
import hexToBinary, { HexChar } from 'common/libs/hexToBinary';
import * as moment from 'moment';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import * as QueryStream from 'pg-query-stream';
import { PostgreSQLClient } from '../../clients/PostgreSQLClient';
export default class PostgreSQLExporter extends SqlExporter {
constructor (client: PostgreSQLClient, tables: exporter.TableParams[], options: exporter.ExportOptions) {
super(tables, options);
this._client = client;
}
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: string) {
/* eslint-disable camelcase */
interface SequenceRecord {
sequence_catalog: string;
sequence_schema: string;
sequence_name: string;
data_type: string;
numeric_precision: number;
numeric_precision_radix: number;
numeric_scale: number;
start_value: string;
minimum_value: string;
maximum_value: string;
increment: string;
cycle_option: string;
}
/* eslint-enable camelcase */
let createSql = '';
const sequences = [];
const columnsSql = [];
const arrayTypes: {[key: string]: string} = {
_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[column.udt_name] + '[]';
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<SequenceRecord>();
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<{indexdef: string}>();
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: string) {
return `DROP TABLE IF EXISTS "${this.schemaName}"."${tableName}";`;
}
async * getTableInsert (tableName: string) {
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.datePrecision; 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 ('isArray' in column) {
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') as undefined as HexChar[])}'`;
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<antares.QueryResult<{typname: string; enumlabel: string}>>(`
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 () {
/* eslint-disable camelcase */
interface TriggersResult {
event_object_table: string;
table_name: string;
trigger_name: string;
events: string[];
event_manipulation: string;
}
/* eslint-enable camelcase */
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<antares.QueryResult<TriggersResult>>(
`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: string) {
if (process.env.NODE_ENV === 'development') console.log('EXPORTER:', sql);
const connection = await this._client.getConnection();
const query = new QueryStream(sql, null);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const stream = (connection as any).query(query);
const dispose = () => connection.end();
stream.on('end', dispose);
stream.on('error', dispose);
stream.on('close', dispose);
return stream;
}
escapeAndQuote (val: string) {
// eslint-disable-next-line no-control-regex
const CHARS_TO_ESCAPE = /[\0\b\t\n\r\x1a"'\\]/g;
const CHARS_ESCAPE_MAP: {[key: string]: string} = {
'\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}'`;
}
}

View File

@@ -1,187 +0,0 @@
import * as moment from 'moment';
import { MySQLClient } from '../../clients/MySQLClient';
import { PostgreSQLClient } from '../../clients/PostgreSQLClient';
import { BaseExporter } from '../BaseExporter';
export class SqlExporter extends BaseExporter {
protected _client: MySQLClient | PostgreSQLClient;
protected _commentChar = '--'
protected _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: 'functions' | 'views' | 'triggers' | 'routines' | 'schedulers') => 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) {
type exporterMethods = 'getViews' | 'getTriggers' | 'getSchedulers' | 'getFunctions' | 'getRoutines'
const processingMethod = `get${item.charAt(0).toUpperCase() + item.slice(1)}` as exporterMethods;
exportState.currentItemIndex++;
exportState.currentItem = item;
exportState.op = 'PROCESSING';
this.emitUpdate(exportState);
if (this[processingMethod]) {
const data = await this[processingMethod]() as unknown as string;
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: string) {
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()}`);
}
/* eslint-disable @typescript-eslint/no-unused-vars */
getCreateTable (_tableName: string): Promise<string> {
throw new Error('Sql Exporter must implement the "getCreateTable" method');
}
getDropTable (_tableName: string): string {
throw new Error('Sql Exporter must implement the "getDropTable" method');
}
getTableInsert (_tableName: string): AsyncGenerator<string> {
throw new Error('Sql Exporter must implement the "getTableInsert" method');
}
getViews () {
throw new Error('Method "getViews" not implemented');
}
getTriggers () {
throw new Error('Method "getTriggers" not implemented');
}
getSchedulers () {
throw new Error('Method "getSchedulers" not implemented');
}
getFunctions () {
throw new Error('Method "getFunctions" not implemented');
}
getRoutines () {
throw new Error('Method "getRoutines" not implemented');
}
/* eslint-enable @typescript-eslint/no-unused-vars */
}

View File

@@ -1,59 +0,0 @@
import * as importer from 'common/interfaces/importer';
import * as fs from 'fs';
import * as EventEmitter from 'events';
export class BaseImporter extends EventEmitter {
protected _options;
protected _isCancelled;
protected _fileHandler;
protected _state;
constructor (options: importer.ImportOptions) {
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: importer.ImportState) {
this.emit('progress', { ...this._state, ...state });
}
import () {
throw new Error('Exporter must implement the "import" method');
}
}

View File

@@ -1,85 +0,0 @@
import * as mysql from 'mysql2';
import * as importer from 'common/interfaces/importer';
import * as fs from 'fs/promises';
import MySQLParser from '../../parsers/MySQLParser';
import { BaseImporter } from '../BaseImporter';
export default class MySQLImporter extends BaseImporter {
protected _client: mysql.Pool
constructor (client: mysql.Pool, options: importer.ImportOptions) {
super(options);
this._client = client;
}
async import (): Promise<void> {
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);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(this._fileHandler as any).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);
}
}
}

View File

@@ -1,85 +0,0 @@
import * as pg from 'pg';
import * as importer from 'common/interfaces/importer';
import * as fs from 'fs/promises';
import PostgreSQLParser from '../../parsers/PostgreSQLParser';
import { BaseImporter } from '../BaseImporter';
export default class PostgreSQLImporter extends BaseImporter {
protected _client: pg.PoolClient;
constructor (client: pg.PoolClient, options: importer.ImportOptions) {
super(options);
this._client = client;
}
async import (): Promise<void> {
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);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(this._fileHandler as any).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);
}
}
}

View File

@@ -1,103 +0,0 @@
import { Transform, TransformCallback, TransformOptions } from 'stream';
export default class MySQLParser extends Transform {
private _buffer: string;
private _lastChar: string;
private _last9Chars: string;
encoding: BufferEncoding;
delimiter: string;
isEscape: boolean;
currentQuote: string;
isDelimiter: boolean;
constructor (opts?: TransformOptions & { delimiter: string }) {
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: Buffer, encoding: BufferEncoding, next: TransformCallback) {
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: string) {
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: string) {
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 | string = 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;
}
}

View File

@@ -1,158 +0,0 @@
import { Transform, TransformCallback, TransformOptions } from 'stream';
export default class PostgreSQLParser extends Transform {
private _buffer: string;
private _lastChar: string;
private _lastChars: string;
private _bodyWrapper: string;
private _bodyWrapperBuffer: string;
private _firstDollarFound: boolean;
private _isBody: boolean;
private _isSingleLineComment: boolean;
private _isMultiLineComment: boolean;
encoding: BufferEncoding;
delimiter: string;
isEscape: boolean;
currentQuote: string;
isDelimiter: boolean;
constructor (opts?: TransformOptions & { delimiter: string }) {
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: Buffer, encoding: BufferEncoding, next: TransformCallback) {
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: string) {
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: string) {
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 | string = 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;
}
}

View File

@@ -1,66 +1,65 @@
import { app, BrowserWindow, globalShortcut, nativeImage, Menu, ipcMain } from 'electron';
'use strict';
import { app, BrowserWindow, /* session, */ nativeImage, Menu } from 'electron';
import * as path from 'path';
import * as Store from 'electron-store';
import * as windowStateKeeper from 'electron-window-state';
import Store from 'electron-store';
import * as remoteMain from '@electron/remote/main';
import ipcHandlers from './ipc-handlers';
import { shortcuts } from 'common/shortcuts';
// remoteMain.initialize();
Store.initRenderer();
const persistentStore = new Store({ name: 'settings' });
const appTheme = persistentStore.get('application_theme');
const isDevelopment = process.env.NODE_ENV !== 'production';
const isMacOS = process.platform === 'darwin';
const isLinux = process.platform === 'linux';
const isWindows = process.platform === 'win32';
const gotTheLock = app.requestSingleInstanceLock();
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
// global reference to mainWindow (necessary to prevent window from being garbage collected)
let mainWindow: BrowserWindow;
let mainWindowState: windowStateKeeper.State;
let mainWindow;
async function createMainWindow () {
const icon = require('../renderer/images/logo-64.png');
const icon = require('../renderer/images/logo-32.png');
const window = new BrowserWindow({
width: mainWindowState.width,
height: mainWindowState.height,
x: mainWindowState.x,
y: mainWindowState.y,
width: 1024,
height: 800,
minWidth: 900,
minHeight: 550,
show: !isWindows,
title: 'Antares SQL',
autoHideMenuBar: true,
icon: nativeImage.createFromDataURL(icon.default),
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
'web-security': false,
spellcheck: false
},
autoHideMenuBar: true,
titleBarStyle: isLinux ? 'default' :'hidden',
titleBarOverlay: isWindows
? {
color: appTheme === 'dark' ? '#3f3f3f' : '#fff',
symbolColor: appTheme === 'dark' ? '#fff' : '#000',
height: 30
}
: false,
frame: false,
titleBarStyle: isMacOS ? 'hidden' : 'default',
trafficLightPosition: isMacOS ? { x: 10, y: 8 } : undefined,
backgroundColor: '#1d1d1d'
});
mainWindowState.manage(window);
window.on('moved', saveWindowState);
remoteMain.enable(window.webContents);
try {
if (isDevelopment)
if (isDevelopment) {
const { default: installExtension, VUEJS3_DEVTOOLS } = require('electron-devtools-installer');
const options = {
loadExtensionOptions: { allowFileAccess: true }
};
try {
const name = await installExtension(VUEJS3_DEVTOOLS, options);
console.log(`Added Extension: ${name}`);
}
catch (err) {
console.log('An error occurred: ', err);
}
await window.loadURL('http://localhost:9080');
}
else {
const indexPath = path.resolve(__dirname, 'index.html');
await window.loadFile(indexPath);
@@ -71,10 +70,16 @@ async function createMainWindow () {
}
window.on('closed', () => {
window.removeListener('moved', saveWindowState);
mainWindow = null;
});
window.webContents.on('devtools-opened', () => {
window.focus();
setImmediate(() => {
window.focus();
});
});
return window;
}
@@ -85,24 +90,10 @@ else {
// Initialize ipcHandlers
ipcHandlers();
ipcMain.on('refresh-theme-settings', () => {
const appTheme = persistentStore.get('application_theme');
if (isWindows && mainWindow) {
mainWindow.setTitleBarOverlay({
color: appTheme === 'dark' ? '#3f3f3f' : '#fff',
symbolColor: appTheme === 'dark' ? '#fff' : '#000'
});
}
});
ipcMain.on('change-window-title', (_, title: string) => {
if (mainWindow) mainWindow.setTitle(title);
});
// quit application when all windows are closed
app.on('window-all-closed', () => {
// on macOS it is common for applications to stay open until the user explicitly quits
if (!isMacOS) app.quit();
if (isMacOS) app.quit();
});
app.on('activate', async () => {
@@ -113,19 +104,11 @@ else {
// create main BrowserWindow when electron is ready
app.on('ready', async () => {
mainWindowState = windowStateKeeper({
defaultWidth: 1024,
defaultHeight: 800
});
mainWindow = await createMainWindow();
createAppMenu();
if (isWindows)
mainWindow.show();
if (isDevelopment)
mainWindow.webContents.openDevTools();
// if (isDevelopment)
// mainWindow.webContents.openDevTools();
process.on('uncaughtException', error => {
mainWindow.webContents.send('unhandled-exception', error);
@@ -135,43 +118,10 @@ else {
mainWindow.webContents.send('unhandled-exception', error);
});
});
app.on('browser-window-created', (event, window) => {
if (isDevelopment) {
const { antares } = require('../../package.json');
const extensionPath = path.resolve(__dirname, `../../misc/${antares.devtoolsId}`);
window.webContents.session.loadExtension(extensionPath, { allowFileAccess: true }).catch(console.error);
}
});
app.on('browser-window-focus', () => {
// Send registered shortcut events to window
for (const shortcut of shortcuts) {
for (const key of shortcut.keys) {
globalShortcut.register(key, () => {
mainWindow.webContents.send(shortcut.event);
if (isDevelopment) console.log('EVENT:', shortcut);
});
}
}
if (isDevelopment) { // Dev shortcuts
globalShortcut.register('Shift+CommandOrControl+F5', () => {
mainWindow.reload();
});
globalShortcut.register('Shift+CommandOrControl+F12', () => {
mainWindow.webContents.openDevTools();
});
}
});
app.on('browser-window-blur', () => {
globalShortcut.unregisterAll();
});
}
function createAppMenu () {
let menu: Electron.Menu = null;
let menu = null;
if (isMacOS) {
menu = Menu.buildFromTemplate([
@@ -210,7 +160,3 @@ function createAppMenu () {
Menu.setApplicationMenu(menu);
}
function saveWindowState () {
mainWindowState.saveState(mainWindow);
}

View File

@@ -1,69 +0,0 @@
import * as antares from 'common/interfaces/antares';
import * as fs from 'fs';
import { MySQLClient } from '../libs/clients/MySQLClient';
import { PostgreSQLClient } from '../libs/clients/PostgreSQLClient';
import { ClientsFactory } from '../libs/ClientsFactory';
import MysqlExporter from '../libs/exporters/sql/MysqlExporter';
import PostgreSQLExporter from '../libs/exporters/sql/PostgreSQLExporter';
let exporter: antares.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
}) as MySQLClient | PostgreSQLClient;
await connection.connect();
switch (client.name) {
case 'mysql':
case 'maria':
exporter = new MysqlExporter(connection as MySQLClient, tables, options);
break;
case 'pg':
exporter = new PostgreSQLExporter(connection as PostgreSQLClient, 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();
});
process.on('beforeExit', console.log);

View File

@@ -1,77 +0,0 @@
import * as antares from 'common/interfaces/antares';
import * as pg from 'pg';
import * as mysql from 'mysql2';
import { MySQLClient } from '../libs/clients/MySQLClient';
import { PostgreSQLClient } from '../libs/clients/PostgreSQLClient';
import { ClientsFactory } from '../libs/ClientsFactory';
import MySQLImporter from '../libs/importers/sql/MySQLlImporter';
import PostgreSQLImporter from '../libs/importers/sql/PostgreSQLImporter';
let importer: antares.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
}) as MySQLClient | PostgreSQLClient;
const pool = await connection.getConnectionPool();
switch (options.type) {
case 'mysql':
case 'maria':
importer = new MySQLImporter(pool as unknown as mysql.Pool, options);
break;
case 'pg':
importer = new PostgreSQLImporter(pool as unknown as pg.PoolClient, 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();
});

View File

@@ -1,8 +1,8 @@
<template>
<div id="wrapper" :class="[`theme-${applicationTheme}`, !disableBlur || 'no-blur']">
<div id="wrapper" :class="`theme-${applicationTheme}`">
<TheTitleBar />
<div id="window-content">
<TheSettingBar @show-connections-modal="isAllConnectionsModal = true" />
<TheSettingBar />
<div id="main-content" class="container">
<div class="columns col-gapless">
<Workspace
@@ -10,9 +10,7 @@
:key="connection.uid"
:connection="connection"
/>
<div class="connection-panel-wrapper">
<WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" />
</div>
<WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" />
</div>
<TheFooter />
<TheNotificationsBoard />
@@ -21,104 +19,89 @@
<BaseTextEditor class="d-none" value="" />
</div>
</div>
<ModalAllConnections v-if="isAllConnectionsModal" @close="isAllConnectionsModal = false" />
</div>
</template>
<script setup lang="ts">
import { defineAsyncComponent, onMounted, Ref, ref } from 'vue';
import { storeToRefs } from 'pinia';
<script>
import { mapActions, mapGetters } from 'vuex';
import { ipcRenderer } from 'electron';
import { useI18n } from 'vue-i18n';
import { Menu, getCurrentWindow } from '@electron/remote';
import { useApplicationStore } from '@/stores/application';
import { useConnectionsStore } from '@/stores/connections';
import { useSettingsStore } from '@/stores/settings';
import { useWorkspacesStore } from '@/stores/workspaces';
import TheSettingBar from '@/components/TheSettingBar.vue';
const { t } = useI18n();
export default {
name: 'App',
components: {
TheTitleBar: () => import(/* webpackChunkName: "TheTitleBar" */'@/components/TheTitleBar'),
TheSettingBar: () => import(/* webpackChunkName: "TheSettingBar" */'@/components/TheSettingBar'),
TheFooter: () => import(/* webpackChunkName: "TheFooter" */'@/components/TheFooter'),
TheNotificationsBoard: () => import(/* webpackChunkName: "TheNotificationsBoard" */'@/components/TheNotificationsBoard'),
Workspace: () => import(/* webpackChunkName: "Workspace" */'@/components/Workspace'),
WorkspaceAddConnectionPanel: () => import(/* webpackChunkName: "WorkspaceAddConnectionPanel" */'@/components/WorkspaceAddConnectionPanel'),
ModalSettings: () => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings'),
TheScratchpad: () => import(/* webpackChunkName: "TheScratchpad" */'@/components/TheScratchpad'),
BaseTextEditor: () => import(/* webpackChunkName: "BaseTextEditor" */'@/components/BaseTextEditor')
},
data () {
return {};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
isLoading: 'application/isLoading',
isSettingModal: 'application/isSettingModal',
isScratchpad: 'application/isScratchpad',
connections: 'connections/getConnections',
applicationTheme: 'settings/getApplicationTheme',
isUnsavedDiscardModal: 'workspaces/isUnsavedDiscardModal'
})
},
mounted () {
ipcRenderer.send('check-for-updates');
this.checkVersionUpdate();
const TheTitleBar = defineAsyncComponent(() => import(/* webpackChunkName: "TheTitleBar" */'@/components/TheTitleBar.vue'));
const TheFooter = defineAsyncComponent(() => import(/* webpackChunkName: "TheFooter" */'@/components/TheFooter.vue'));
const TheNotificationsBoard = defineAsyncComponent(() => import(/* webpackChunkName: "TheNotificationsBoard" */'@/components/TheNotificationsBoard.vue'));
const Workspace = defineAsyncComponent(() => import(/* webpackChunkName: "Workspace" */'@/components/Workspace.vue'));
const WorkspaceAddConnectionPanel = defineAsyncComponent(() => import(/* webpackChunkName: "WorkspaceAddConnectionPanel" */'@/components/WorkspaceAddConnectionPanel.vue'));
const ModalSettings = defineAsyncComponent(() => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings.vue'));
const ModalAllConnections = defineAsyncComponent(() => import(/* webpackChunkName: "ModalAllConnections" */'@/components/ModalAllConnections.vue'));
const TheScratchpad = defineAsyncComponent(() => import(/* webpackChunkName: "TheScratchpad" */'@/components/TheScratchpad.vue'));
const BaseTextEditor = defineAsyncComponent(() => import(/* webpackChunkName: "BaseTextEditor" */'@/components/BaseTextEditor.vue'));
const applicationStore = useApplicationStore();
const connectionsStore = useConnectionsStore();
const settingsStore = useSettingsStore();
const workspacesStore = useWorkspacesStore();
const {
isSettingModal,
isScratchpad
} = storeToRefs(applicationStore);
const { connections } = storeToRefs(connectionsStore);
const { applicationTheme, disableBlur } = storeToRefs(settingsStore);
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { checkVersionUpdate } = applicationStore;
const { changeApplicationTheme } = settingsStore;
const isAllConnectionsModal: Ref<boolean> = ref(false);
ipcRenderer.on('open-connections-modal', () => {
isAllConnectionsModal.value = true;
});
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => {
changeApplicationTheme(applicationTheme.value);// Forces persistentStore to save on file and mail process
}, 1000);
});
onMounted(() => {
ipcRenderer.send('check-for-updates');
checkVersionUpdate();
const InputMenu = Menu.buildFromTemplate([
{
label: t('word.cut'),
role: 'cut'
},
{
label: t('word.copy'),
role: 'copy'
},
{
label: t('word.paste'),
role: 'paste'
},
{
type: 'separator'
},
{
label: t('message.selectAll'),
role: 'selectAll'
}
]);
document.body.addEventListener('contextmenu', (e) => {
e.preventDefault();
e.stopPropagation();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let node: any = e.target;
while (node) {
if (node.nodeName.match(/^(input|textarea)$/i) || node.isContentEditable) {
InputMenu.popup({ window: getCurrentWindow() });
break;
const InputMenu = Menu.buildFromTemplate([
{
label: this.$t('word.cut'),
role: 'cut'
},
{
label: this.$t('word.copy'),
role: 'copy'
},
{
label: this.$t('word.paste'),
role: 'paste'
},
{
type: 'separator'
},
{
label: this.$t('message.selectAll'),
role: 'selectall'
}
node = node.parentNode;
}
});
});
]);
document.body.addEventListener('contextmenu', (e) => {
e.preventDefault();
e.stopPropagation();
let node = e.target;
while (node) {
if (node.nodeName.match(/^(input|textarea)$/i) || node.isContentEditable) {
InputMenu.popup(getCurrentWindow());
break;
}
node = node.parentNode;
}
});
},
methods: {
...mapActions({
showNewConnModal: 'application/showNewConnModal',
checkVersionUpdate: 'application/checkVersionUpdate'
})
}
};
</script>
<style lang="scss">
@@ -147,15 +130,5 @@ onMounted(() => {
> .columns {
height: calc(100vh - #{$footer-height});
}
.connection-panel-wrapper {
height: calc(100vh - #{$excluding-size});
width: 100%;
padding-top: 10vh;
display: flex;
justify-content: center;
align-items: flex-start;
overflow: auto;
}
}
</style>

View File

@@ -1,110 +1,105 @@
<template>
<div class="dummy-wrapper">
<Teleport to="#window-content">
<div class="modal active" :class="modalSizeClass">
<a class="modal-overlay" @click="hideModal" />
<div ref="trapRef" class="modal-container">
<div v-if="hasHeader" class="modal-header pl-2">
<div class="modal-title h6">
<slot name="header" />
</div>
<a class="btn btn-clear float-right" @click="hideModal" />
</div>
<div v-if="hasDefault" class="modal-header">
<div class="modal-title h6">
<slot />
</div>
<a class="btn btn-clear float-right" @click="hideModal" />
</div>
<div v-if="hasBody" class="modal-body pb-0">
<a
v-if="!hasHeader && !hasDefault"
class="btn btn-clear float-right"
@click="hideModal"
/>
<div class="content">
<slot name="body" />
</div>
</div>
<div v-if="!hideFooter" class="modal-footer">
<button
class="btn btn-primary mr-2"
@click.stop="confirmModal"
>
{{ confirmText || $t('word.confirm') }}
</button>
<button
class="btn btn-link"
@click="hideModal"
>
{{ cancelText || $t('word.cancel') }}
</button>
</div>
<div class="modal active" :class="modalSizeClass">
<a class="modal-overlay" @click="hideModal" />
<div class="modal-container">
<div v-if="hasHeader" class="modal-header pl-2">
<div class="modal-title h6">
<slot name="header" />
</div>
<a class="btn btn-clear float-right" @click="hideModal" />
</div>
<div v-if="hasDefault" class="modal-header">
<div class="modal-title h6">
<slot />
</div>
<a class="btn btn-clear float-right" @click="hideModal" />
</div>
<div v-if="hasBody" class="modal-body">
<a
v-if="!hasHeader && !hasDefault"
class="btn btn-clear float-right"
@click="hideModal"
/>
<div class="content">
<slot name="body" />
</div>
</div>
</Teleport>
<div v-if="!hideFooter" class="modal-footer">
<button
class="btn btn-primary mr-2"
@click.stop="confirmModal"
>
{{ confirmText || $t('word.confirm') }}
</button>
<button
class="btn btn-link"
@click="hideModal"
>
{{ cancelText || $t('word.cancel') }}
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useFocusTrap } from '@/composables/useFocusTrap';
import { computed, onBeforeUnmount, PropType, useSlots } from 'vue';
<script>
export default {
name: 'BaseConfirmModal',
props: {
size: {
type: String,
validator: prop => ['small', 'medium', '400', 'large'].includes(prop),
default: 'small'
},
hideFooter: {
type: Boolean,
default: false
},
confirmText: String,
cancelText: String
},
computed: {
hasHeader () {
return !!this.$slots.header;
},
hasBody () {
return !!this.$slots.body;
},
hasDefault () {
return !!this.$slots.default;
},
modalSizeClass () {
if (this.size === 'small')
return 'modal-sm';
if (this.size === '400')
return 'modal-400';
else if (this.size === 'large')
return 'modal-lg';
else return '';
}
},
created () {
window.addEventListener('keydown', this.onKey);
},
beforeDestroy () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
confirmModal () {
this.$emit('confirm');
this.hideModal();
},
const props = defineProps({
size: {
type: String as PropType<'small' | 'medium' | '400' | 'large'>,
validator: (prop: string) => ['small', 'medium', '400', 'large'].includes(prop),
default: 'small'
},
hideFooter: {
type: Boolean,
default: false
},
confirmText: String,
cancelText: String,
disableAutofocus: {
type: Boolean,
default: false
hideModal () {
this.$emit('hide');
},
onKey (e) {
e.stopPropagation();
if (e.key === 'Escape')
this.hideModal();
}
}
});
const emit = defineEmits(['confirm', 'hide']);
const slots = useSlots();
const { trapRef } = useFocusTrap({ disableAutofocus: props.disableAutofocus });
const hasHeader = computed(() => !!slots.header);
const hasBody = computed(() => !!slots.body);
const hasDefault = computed(() => !!slots.default);
const modalSizeClass = computed(() => {
if (props.size === 'small')
return 'modal-sm';
if (props.size === '400')
return 'modal-400';
else if (props.size === 'large')
return 'modal-lg';
else return '';
});
const confirmModal = () => {
emit('confirm');
hideModal();
};
const hideModal = () => {
emit('hide');
};
const onKey = (e: KeyboardEvent) => {
e.stopPropagation();
if (e.key === 'Escape')
hideModal();
};
window.addEventListener('keydown', onKey);
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script>
<style scoped>
@@ -115,4 +110,5 @@ onBeforeUnmount(() => {
.modal.modal-sm .modal-container {
padding: 0;
}
</style>

View File

@@ -15,60 +15,66 @@
</div>
</template>
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, Ref, ref } from 'vue';
<script>
export default {
name: 'BaseContextMenu',
props: {
contextEvent: MouseEvent
},
data () {
return {
contextSize: null,
isBottom: false
};
},
computed: {
position () {
let topCord = 0;
let leftCord = 0;
const contextContent: Ref<HTMLDivElement> = ref(null);
const contextSize: Ref<DOMRect> = ref(null);
const isBottom: Ref<boolean> = ref(false);
const props = defineProps<{contextEvent: MouseEvent}>();
const emit = defineEmits(['close-context']);
if (this.contextEvent) {
const { clientY, clientX } = this.contextEvent;
topCord = `${clientY + 2}px`;
leftCord = `${clientX + 5}px`;
const position = computed(() => {
let topCord = '0px';
let leftCord = '0px';
if (this.contextSize) {
if (clientY + (this.contextSize.height < 200 ? 200 : this.contextSize.height) + 5 >= window.innerHeight) {
topCord = `${clientY + 3 - this.contextSize.height}px`;
this.isBottom = true;
}
if (props.contextEvent) {
const { clientY, clientX } = props.contextEvent;
topCord = `${clientY + 2}px`;
leftCord = `${clientX + 5}px`;
if (contextSize.value) {
if (clientY + (contextSize.value.height < 200 ? 200 : contextSize.value.height) + 5 >= window.innerHeight) {
topCord = `${clientY + 3 - contextSize.value.height}px`;
isBottom.value = true;
if (clientX + this.contextSize.width + 5 >= window.innerWidth)
leftCord = `${clientX - this.contextSize.width}px`;
}
}
if (clientX + contextSize.value.width + 5 >= window.innerWidth)
leftCord = `${clientX - contextSize.value.width}px`;
return {
top: topCord,
left: leftCord
};
}
},
created () {
window.addEventListener('keydown', this.onKey);
},
mounted () {
if (this.$refs.contextContent)
this.contextSize = this.$refs.contextContent.getBoundingClientRect();
},
beforeDestroy () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
close () {
this.$emit('close-context');
},
onKey (e) {
e.stopPropagation();
if (e.key === 'Escape')
this.close();
}
}
return {
top: topCord,
left: leftCord
};
});
const close = () => {
emit('close-context');
};
const onKey = (e: KeyboardEvent) => {
e.stopPropagation();
if (e.key === 'Escape')
close();
};
window.addEventListener('keydown', onKey);
onMounted(() => {
if (contextContent.value)
contextSize.value = contextContent.value.getBoundingClientRect();
});
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script>
<style lang="scss">

View File

@@ -4,6 +4,11 @@
</div>
</template>
<script>
export default {
name: 'BaseLoader'
};
</script>
<style scoped>
.empty {
position: absolute;

View File

@@ -1,106 +0,0 @@
<template>
<div id="map" class="map" />
</template>
<script setup lang="ts">
import { onMounted, PropType, Ref, ref } from 'vue';
import * as L from 'leaflet';
import {
point,
lineString,
polygon
} from '@turf/helpers';
import { GeoJsonObject } from 'geojson';
import { getArrayDepth } from 'common/libs/getArrayDepth';
interface Coordinates { x: number; y: number }
const props = defineProps({
points: [Object, Array] as PropType<Coordinates | Coordinates[]>,
isMultiSpatial: Boolean
});
const map: Ref<L.Map> = ref(null);
const markers: Ref<GeoJsonObject | GeoJsonObject[]> = ref(null);
const center: Ref<[number, number]> = ref(null);
const getMarkers = (points: Coordinates) => {
if (Array.isArray(points)) {
if (getArrayDepth(points) === 1)
return lineString(points.reduce((acc, curr) => [...acc, [curr.x, curr.y]], []));
else
return polygon(points.map(arr => arr.reduce((acc: Coordinates[], curr: Coordinates) => [...acc, [curr.x, curr.y]], [])));
}
else
return point([points.x, points.y]);
};
onMounted(() => {
if (props.isMultiSpatial) {
for (const element of props.points as Coordinates[])
(markers.value as GeoJsonObject[]).push(getMarkers(element));
}
else {
markers.value = getMarkers(props.points as Coordinates);
if (!Array.isArray(props.points))
center.value = [props.points.y, props.points.x];
}
map.value = L.map('map', {
center: center.value || [0, 0],
zoom: 15,
minZoom: 1,
attributionControl: false
});
L.control.attribution({ prefix: '<b>Leaflet</b>' }).addTo(map.value);
const geoJsonObj = L.geoJSON((markers.value as GeoJsonObject), {
style: function () {
return {
weight: 2,
fillColor: '#ff7800',
color: '#ff7800',
opacity: 0.8,
fillOpacity: 0.4
};
},
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, {
radius: 7,
weight: 2,
fillColor: '#ff7800',
color: '#ff7800',
opacity: 0.8,
fillOpacity: 0.4
});
}
}).addTo(map.value);
const southWest = L.latLng(-90, -180);
const northEast = L.latLng(90, 180);
const bounds = L.latLngBounds(southWest, northEast);
map.value.setMaxBounds(bounds);
if (!center.value) map.value.fitBounds(geoJsonObj.getBounds());
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <b>OpenStreetMap</b>'
}).addTo(map.value);
});
</script>
<style lang="scss">
.map {
height: 400px;
}
.marker-icon {
display: flex;
justify-content: center;
align-items: center;
background: $primary-color;
border-radius: 50%;
box-shadow: 0 0 5px 1px darken($body-font-color-dark, 40%);
}
</style>

View File

@@ -14,58 +14,63 @@
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
const props = defineProps({
message: {
type: String,
default: ''
<script>
export default {
name: 'BaseNotification',
props: {
message: {
type: String,
default: ''
},
status: {
type: String,
default: ''
}
},
status: {
type: String,
default: ''
data () {
return {
isExpanded: false
};
},
computed: {
notificationStatus () {
let className = '';
let iconName = '';
switch (this.status) {
case 'success':
className = 'toast-success';
iconName = 'mdi-check';
break;
case 'error':
className = 'toast-error';
iconName = 'mdi-alert-rhombus';
break;
case 'warning':
className = 'toast-warning';
iconName = 'mdi-alert';
break;
case 'primary':
className = 'toast-primary';
iconName = 'mdi-information-outline';
break;
}
return { className, iconName };
},
isExpandable () {
return this.message.length > 80;
}
},
methods: {
hideToast () {
this.$emit('close');
},
toggleExpand () {
this.isExpanded = !this.isExpanded;
}
}
});
const isExpanded = ref(false);
const emit = defineEmits(['close']);
const notificationStatus = computed(() => {
let className = '';
let iconName = '';
switch (props.status) {
case 'success':
className = 'toast-success';
iconName = 'mdi-check';
break;
case 'error':
className = 'toast-error';
iconName = 'mdi-alert-rhombus';
break;
case 'warning':
className = 'toast-warning';
iconName = 'mdi-alert';
break;
case 'primary':
className = 'toast-primary';
iconName = 'mdi-information-outline';
break;
}
return { className, iconName };
});
const isExpandable = computed(() => props.message.length > 80);
const hideToast = () => {
emit('close');
};
const toggleExpand = () => {
isExpanded.value = !isExpanded.value;
};
</script>
<style scoped>
.toast {
display: flex;

View File

@@ -1,462 +0,0 @@
<template>
<div
ref="el"
class="select"
:class="{'select--open': isOpen, 'select--disabled': disabled}"
role="combobox"
:tabindex="searchable || disabled ? -1 : tabindex"
@focus="activate()"
@blur="searchable ? false : handleBlurEvent()"
@keyup.esc="deactivate()"
@keydown.self.down.prevent="moveDown()"
@keydown.self.up.prevent="moveUp"
>
<div class="select__item-text">
<input
v-if="searchable"
ref="searchInput"
class="select__search-input"
:style="searchInputStyle"
type="text"
autocomplete="off"
spellcheck="false"
:tabindex="tabindex"
:value="searchText"
@input="searchText = $event.target.value"
@focus.prevent="!isOpen ? activate() : false"
@blur.prevent="handleBlurEvent()"
@keyup.esc="deactivate()"
@keydown.down.prevent="keyArrows('down')"
@keydown.up.prevent="keyArrows('up')"
@keypress.enter.prevent.stop.self="select(filteredOptions[hightlightedIndex])"
>
<span v-if="searchable && !isOpen || !searchable">{{ currentOptionLabel }}</span>
</div>
<Transition :name="animation">
<div
v-if="isOpen"
ref="optionList"
:class="`select__list-wrapper ${dropdownClass ? dropdownClass : '' }`"
@mousedown="isMouseDown = true"
@mouseup="handleMouseUpEvent()"
>
<ul class="select__list" @mousedown.prevent>
<li
v-for="(opt, index) of filteredOptions"
:key="opt.id"
:ref="(el) => optionRefs[index] = el"
:class="{
'select__item': true,
'select__group': opt.$type === 'group',
'select__option--highlight': opt.$type === 'option' && !opt.disabled && index === hightlightedIndex,
'select__option--selected': opt.$type === 'option' && isSelected(opt),
'select__option--disabled': opt.disabled
}"
@click.stop="select(opt)"
@mousemove.self="hightlightedIndex = index"
>
<slot
name="option"
:option="opt"
:index="index"
>
{{ opt.label }}
</slot>
</li>
</ul>
</div>
</Transition>
</div>
</template>
<script>
import { defineComponent, computed, ref, watch, nextTick, onMounted, onUnmounted } from 'vue';
export default defineComponent({
name: 'BaseSelect',
props: {
modelValue: {
type: [String, Number, Object, Boolean]
},
value: {
type: [String, Number, Object, Boolean]
},
searchable: {
type: Boolean,
default: true
},
preserveSearch: {
type: Boolean,
default: false
},
tabindex: {
type: Number,
default: 0
},
options: {
type: Array,
default: () => []
},
optionTrackBy: {
type: [String, Function],
default: 'value'
},
optionLabel: {
type: [String, Function],
default: 'label'
},
optionDisabled: {
type: Function
},
groupLabel: {
type: String
},
groupValues: {
type: String
},
closeOnSelect: {
type: Boolean,
default: true
},
animation: {
type: String,
default: 'fade-slide-down'
},
dropdownOffsets: {
type: Object,
default: () => ({ top: 10, left: 0 })
},
dropdownClass: {
type: String
},
disabled: {
type: Boolean,
default: false
},
maxVisibleOptions: {
type: Number,
default: 100
}
},
emits: ['select', 'open', 'close', 'update:modelValue', 'change', 'blur'],
setup (props, { emit }) {
const hightlightedIndex = ref(0);
const isOpen = ref(false);
const isMouseDown = ref(false);
const internalValue = ref(props.modelValue !== false ? props.modelValue : props.value);
const el = ref(null);
const searchInput = ref(null);
const optionList = ref(null);
const optionRefs = [];
const searchText = ref('');
const getOptionValue = (opt) => _guess('optionTrackBy', opt);
const getOptionLabel = (opt) => _guess('optionLabel', opt) + '';
const getOptionDisabled = (opt) => _guess('optionDisabled', opt);
const _guess = (name, item) => {
const prop = props[name];
if (typeof prop === 'function')
return prop(item);
return item[prop] !== undefined ? item[prop] : item;
};
const flattenOptions = computed(() => {
return [...props.options].reduce((prev, curr) => {
if (curr[props.groupValues] && curr[props.groupValues].length) {
prev.push({
$type: 'group',
label: curr[props.groupLabel],
id: `group-${curr[props.groupLabel]}`,
count: curr[props.groupLabel].length
});
return prev.concat(curr[props.groupValues].map(el => {
const value = getOptionValue(el);
return {
$type: 'option',
label: getOptionLabel(el),
id: `option-${value}`,
disabled: getOptionDisabled(el) === true,
value,
$data: {
...el
}
};
}));
}
else {
const value = getOptionValue(curr);
prev.push({
$type: 'option',
label: getOptionLabel(curr),
id: `option-${value}`,
disabled: getOptionDisabled(curr) === true,
value,
$data: {
...curr
}
});
}
return prev;
}, []);
});
const filteredOptions = computed(() => {
const searchTerms = (searchText.value || '').toLowerCase().trim();
let options = searchTerms
? flattenOptions.value.filter(opt => opt.$type === 'group' || opt.label.trim().toLowerCase().indexOf(searchTerms) !== -1)
: flattenOptions.value;
if (options.length > props.maxVisibleOptions) {
let sliceStart = 0;
let sliceEnd = sliceStart + props.maxVisibleOptions;
// if no search active try to open the dropdown showing options around the selected one
if (searchTerms === '') {
const index = internalValue.value ? flattenOptions.value.findIndex(el => el.value === internalValue.value) : -1;
if (index < options.length -1) {
sliceStart = Math.max(0, index - Math.floor(props.maxVisibleOptions / 2));
sliceEnd = Math.min(sliceStart + sliceEnd, options.length -1);
}
}
options = options.slice(sliceStart, sliceEnd);
}
return options;
});
const searchInputStyle = computed(() => {
if (props.searchable)
// just hide the input and give the ability to receive focus
return isOpen.value ? { with: '100%' } : { width: 0, position: 'absolute', padding: 0, margin: 0 };
return '';
});
watch(filteredOptions, (options) => {
if (hightlightedIndex.value >= options.length -1)
hightlightedIndex.value = options.length ? options.length -1 : 0;
else
hightlightedIndex.value = 0;
});
watch(() => props.modelValue, (val) => {
internalValue.value = val;
});
watch(() => props.value, (val) => {
internalValue.value = val;
});
const currentOptionLabel = computed(() =>
flattenOptions.value.find(d => d.value === internalValue.value)?.label
);
const select = (opt) => {
if (opt.$type === 'group' || opt.disabled) return;
internalValue.value = opt.value;
emit('select', opt);
emit('update:modelValue', opt.value);
emit('change', opt);
if (props.closeOnSelect)
deactivate();
};
const isSelected = (opt) => {
return internalValue.value === opt.value;
};
const activate = () => {
if (isOpen.value || props.disabled) return;
isOpen.value = true;
hightlightedIndex.value = filteredOptions.value.findIndex(el => el.value === internalValue.value) || 0;
if (props.searchable)
searchInput.value.focus();
else
el.value.focus();
nextTick(() => {
adjustListPosition();
scrollTo(optionRefs[hightlightedIndex.value]);
});
emit('open');
};
const deactivate = () => {
if (!isOpen.value) return;
isOpen.value = false;
if (props.searchable)
searchInput.value?.blur();
else
el.value?.blur();
if (!props.preserveSearch) searchText.value = '';
emit('close');
};
const adjustListPosition = () => {
if (!optionList.value) return;
const element = el.value;
let { left, top } = element.getBoundingClientRect();
const { left: offsetLeft = 0, top: offsetTop = 0 } = props.dropdownOffsets;
top = top + element.clientHeight + offsetTop;
const openBottom = top >= 0 && top + optionList.value.clientHeight <= window.innerHeight;
if (!openBottom) {
top -= (offsetTop * 2 + element.clientHeight);
optionList.value.style.transform = 'translate(0, -100%)';
}
optionList.value.style.left = `${left + offsetLeft}px`;
optionList.value.style.top = `${top}px`;
optionList.value.style.minWidth = `${element.clientWidth}px`;
};
const keyArrows = (direction) => {
const sum = direction === 'down' ? +1 : -1;
let index = hightlightedIndex.value + sum;
index = Math.max(0, index > filteredOptions.value.length - 1 ? filteredOptions.value.length - 1 : index);
if (filteredOptions.value[index].$type === 'group')
index=Math.max(1, index+sum);
hightlightedIndex.value = index;
const optEl = optionRefs[hightlightedIndex.value];
if (!optEl)
return;
scrollTo(optEl);
};
const scrollTo = (optEl) => {
if (!optEl) return;
const visMin = optionList.value.scrollTop;
const visMax = optionList.value.scrollTop + optionList.value.clientHeight - optEl.clientHeight;
if (optEl.offsetTop < visMin)
optionList.value.scrollTop = optEl.offsetTop;
else if (optEl.offsetTop >= visMax)
optionList.value.scrollTop = optEl.offsetTop - optionList.value.clientHeight + optEl.clientHeight;
};
const handleBlurEvent = () => {
if (isMouseDown.value) return;
deactivate();
emit('blur');
};
const handleMouseUpEvent = () => {
isMouseDown.value = false;
searchInput.value.focus();
};
const handleWheelEvent = (e) => {
if (!e.target.className.includes('select__')) deactivate();
};
onMounted(() => {
window.addEventListener('resize', adjustListPosition);
window.addEventListener('wheel', handleWheelEvent);
nextTick(() => {
// fix position when the component is created and opened at the same time
if (isOpen.value) {
setTimeout(() => {
adjustListPosition();
}, 50);
}
});
});
onUnmounted(() => {
window.removeEventListener('resize', adjustListPosition);
window.removeEventListener('wheel', handleWheelEvent);
});
return {
el,
searchInput,
searchText,
searchInputStyle,
filteredOptions,
currentOptionLabel,
activate,
deactivate,
select,
isSelected,
keyArrows,
isOpen,
isMouseDown,
hightlightedIndex,
optionList,
optionRefs,
handleBlurEvent,
handleMouseUpEvent,
internalValue
};
}
});
</script>
<style lang="scss" scoped>
.select {
display: block;
&:focus,
&--open {
z-index: 10;
}
&__search-input {
appearance: none;
border: none;
background: transparent;
outline: none;
color: currentColor;
max-width: 100%;
width: 100%;
}
&__list-wrapper {
cursor: pointer;
position: fixed;
display: block;
z-index: 5;
-webkit-overflow-scrolling: touch;
max-height: 240px;
overflow: auto;
left: 0;
top: 40px;
}
&__list {
list-style: none;
}
&__option {
&--disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
&--disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
</style>

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