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
d743d486b8 chore(release): 0.5.5 2022-05-24 14:21:56 +02:00
256 changed files with 15041 additions and 40675 deletions

View File

@@ -174,83 +174,8 @@
"contributions": [ "contributions": [
"translation" "translation"
] ]
},
{
"login": "brdtheo",
"name": "Théo Billardey",
"avatar_url": "https://avatars.githubusercontent.com/u/48206778?v=4",
"profile": "https://codepen.io/theo-billardey",
"contributions": [
"translation"
]
},
{
"login": "dyaskur",
"name": "Muhammad Dyas Yaskur",
"avatar_url": "https://avatars.githubusercontent.com/u/9539970?v=4",
"profile": "http://yaskur.net",
"contributions": [
"translation",
"code"
]
},
{
"login": "jimcat8",
"name": "tianci li",
"avatar_url": "https://avatars.githubusercontent.com/u/86754294?v=4",
"profile": "https://github.com/jimcat8",
"contributions": [
"translation"
]
},
{
"login": "555cider",
"name": "555cider",
"avatar_url": "https://avatars.githubusercontent.com/u/73565447?v=4",
"profile": "https://github.com/555cider",
"contributions": [
"translation"
]
},
{
"login": "m1khal3v",
"name": "Anton Mikhalev",
"avatar_url": "https://avatars.githubusercontent.com/u/41085561?v=4",
"profile": "https://github.com/m1khal3v",
"contributions": [
"translation"
]
},
{
"login": "64knl",
"name": "René",
"avatar_url": "https://avatars.githubusercontent.com/u/3864423?v=4",
"profile": "https://64k.nl/",
"contributions": [
"code",
"translation"
]
},
{
"login": "zxp19821005",
"name": "Woodenman",
"avatar_url": "https://avatars.githubusercontent.com/u/4915850?v=4",
"profile": "https://github.com/zxp19821005",
"contributions": [
"platform"
]
},
{
"login": "markusand",
"name": "Marc Vilella",
"avatar_url": "https://avatars.githubusercontent.com/u/12972543?v=4",
"profile": "https://github.com/markusand",
"contributions": [
"translation"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,
"skipCi": true, "skipCi": true
"commitType": "docs"
} }

View File

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

View File

@@ -6,9 +6,6 @@
version: 2 version: 2
updates: updates:
- package-ecosystem: "npm" - package-ecosystem: "npm"
allow:
- dependency-type: "production"
directory: "/" directory: "/"
schedule: schedule:
interval: "monthly" interval: "weekly"
target-branch: "develop"

View File

@@ -1,42 +0,0 @@
name: Build & release [BETA]
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+'
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-11, ubuntu-latest, windows-latest]
steps:
- name: Check out Git repository
uses: actions/checkout@v3
with:
ref: beta
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 16
- name: Install dependencies
run: npm i
- name: "Build"
run: npm run build
- name: Release
uses: ncipollo/release-action@v1
with:
artifacts: "build/*.AppImage,build/*.yml,build/*.deb,build/*.dmg,build/*.blockmap,build/*.zip,build/*.exe"
allowUpdates: true
draft: true
generateReleaseNotes: true

29
.github/workflows/build-linux.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Build/release [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: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1
with:
github_token: ${{ secrets.github_token }}
release: ${{ startsWith(github.ref, 'refs/tags/v') }}

29
.github/workflows/build-mac.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Build/release [mac]
on: push
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-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: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1
with:
github_token: ${{ secrets.github_token }}
release: ${{ startsWith(github.ref, 'refs/tags/v') }}

29
.github/workflows/build-win.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Build/release [windows]
on: push
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [windows-2019]
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: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1
with:
github_token: ${{ secrets.github_token }}
release: ${{ startsWith(github.ref, 'refs/tags/v') }}

View File

@@ -1,48 +0,0 @@
name: Build & release [STABLE]
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-11, ubuntu-latest, windows-latest]
steps:
- name: Exit if not on master branch
if: contains(env.BRANCH_NAME, 'master') == false
run: |
echo "Wrong environment ${{env.BRANCH_NAME}}"
exit 0
- name: Check out Git repository
uses: actions/checkout@v3
with:
ref: master
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 16
- name: Install dependencies
run: npm i
- name: "Build"
run: npm run build
- name: Release
uses: ncipollo/release-action@v1
with:
artifacts: "build/*.AppImage,build/*.yml,build/*.deb,build/*.dmg,build/*.blockmap,build/*.zip,build/*.exe"
allowUpdates: true
draft: true
generateReleaseNotes: true

View File

@@ -1,32 +0,0 @@
name: Create artifact [LINUX ARM64]
on:
workflow_dispatch: {}
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v3
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 16
- name: Install dependencies
run: npm i
- name: "Build"
run: npm run build -- --arm64 --linux deb AppImage
- 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,32 +0,0 @@
name: Create artifact [LINUX]
on:
workflow_dispatch: {}
jobs:
build:
runs-on: ubuntu-20.04
steps:
- name: Check out Git repository
uses: actions/checkout@v3
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 16
- name: Install dependencies
run: npm i
- name: "Build"
run: npm run build
- 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,31 +0,0 @@
name: Create artifact [MAC]
on:
workflow_dispatch: {}
jobs:
build:
runs-on: macos-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v3
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 16
- name: npm install & build
run: |
npm install
npm run build
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: macos-build
retention-days: 3
path: |
build
!build/*-unpacked
!build/.icon-ico

View File

@@ -1,9 +1,6 @@
name: Test end-to-end [WINDOWS] name: Test end-to-end [linux]
on: on: push
push:
branches:
- master
jobs: jobs:
release: release:
@@ -11,16 +8,16 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [windows-latest] os: [ubuntu-latest]
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v3 uses: actions/checkout@v2
- name: Install Node.js - name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v3 uses: actions/setup-node@v1
with: with:
node-version: 16 node-version: 14
- name: Install dependencies - name: Install dependencies
run: npm i run: npm i

1
.gitignore vendored
View File

@@ -7,4 +7,5 @@ node_modules
thumbs.db thumbs.db
NOTES.md NOTES.md
*.txt *.txt
package-lock.json
*.heapsnapshot *.heapsnapshot

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
v16.13.0

View File

@@ -1,7 +1,6 @@
{ {
"extends": [ "extends": [
"stylelint-config-standard", "stylelint-config-standard"
"stylelint-config-recommended-vue"
], ],
"fix": true, "fix": true,
"formatter": "verbose", "formatter": "verbose",
@@ -11,7 +10,6 @@
"rules": { "rules": {
"at-rule-no-unknown": null, "at-rule-no-unknown": null,
"no-descending-specificity": null, "no-descending-specificity": null,
"font-family-no-missing-generic-family-keyword": null,
"declaration-colon-newline-after": "always-multi-line" "declaration-colon-newline-after": "always-multi-line"
}, },
"syntax": "scss" "syntax": "scss"

2
.vscode/launch.json vendored
View File

@@ -5,6 +5,7 @@
"name": "Electron: Main", "name": "Electron: Main",
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
"port": 9222, "port": 9222,
"protocol": "inspector",
"request": "attach", "request": "attach",
"sourceMaps": true, "sourceMaps": true,
"type": "node", "type": "node",
@@ -22,6 +23,7 @@
"name": "Electron: Worker", "name": "Electron: Worker",
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
"port": 9224, "port": 9224,
"protocol": "inspector",
"request": "attach", "request": "attach",
"sourceMaps": true, "sourceMaps": true,
"type": "node", "type": "node",

View File

@@ -5,12 +5,8 @@
"MySQL", "MySQL",
"PostgreSQL", "PostgreSQL",
"SQLite", "SQLite",
"Firebird SQL",
"Windows", "Windows",
"translation", "translation"
"Linux",
"MacOS",
"deps"
], ],
"svg.preview.background": "transparent" "svg.preview.background": "transparent"
} }

View File

@@ -2,568 +2,6 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.7.16-beta.1](https://github.com/antares-sql/antares/compare/v0.7.16-beta.0...v0.7.16-beta.1) (2023-08-18)
### Features
* ability to export connections ([f5b86e5](https://github.com/antares-sql/antares/commit/f5b86e59e7e41bbbe1555ec8a85ec1f849e8271d))
* ability to import connections ([6df2145](https://github.com/antares-sql/antares/commit/6df214558f8186dd5fbf101505465f4c07c8ad5f))
* Add catalan language ([9c6f1a9](https://github.com/antares-sql/antares/commit/9c6f1a9ea52b4791082bdbae91b764c486684c3b))
### Bug Fixes
* add some missing titles of buttons ([52e42fa](https://github.com/antares-sql/antares/commit/52e42fa1b529512154c0680124c5e425964a5b1a))
* **SQLite:** improved view body parsing ([832aa75](https://github.com/antares-sql/antares/commit/832aa75ebe46776b9c365dde2dd13db3746adcd5))
### [0.7.16-beta.0](https://github.com/antares-sql/antares/compare/v0.7.15...v0.7.16-beta.0) (2023-08-11)
### Features
* ability to change sql dump file name ([2e39d81](https://github.com/antares-sql/antares/commit/2e39d810b519401d0410cbb76520c07c595329fc))
* ability to export a table dump from table context menu ([84b2255](https://github.com/antares-sql/antares/commit/84b2255bf405431070511890b2bca770fc4cc2bc))
* support to links in changelog ([ad7e459](https://github.com/antares-sql/antares/commit/ad7e459c689d70ca664b9a3091d3dcab3f26ce4d))
### Bug Fixes
* **MySQL:** missing error details on mysql importer with some exceptione ([7b1cb4f](https://github.com/antares-sql/antares/commit/7b1cb4ff86dddb5fb4b4d960b47ea17f38a24aa6))
### [0.7.15](https://github.com/antares-sql/antares/compare/v0.7.14...v0.7.15) (2023-07-31)
### Features
* export file name with hour, minutes and seconds, closes [#610](https://github.com/antares-sql/antares/issues/610) ([2f5fa0f](https://github.com/antares-sql/antares/commit/2f5fa0f2e416e88753c9dc2bf32edefba8c4e072))
### Bug Fixes
* display content of BLOB fields if not binary, fixes [#628](https://github.com/antares-sql/antares/issues/628) ([f831fcd](https://github.com/antares-sql/antares/commit/f831fcd442f16a03f0ea83ff5bf77a4a79906eb6))
* **MySQL:** error insertinf new fields with some MySQL configurations, fixes [#360](https://github.com/antares-sql/antares/issues/360) ([51b1419](https://github.com/antares-sql/antares/commit/51b14195a8a6c7e7f1b12bd4b86b7164a56b2aa5))
* **MySQL:** unable to set more than one value in SET fields ([7c820b1](https://github.com/antares-sql/antares/commit/7c820b18276e4ed5e381d91110acd032862a5be9))
### [0.7.14](https://github.com/antares-sql/antares/compare/v0.7.13...v0.7.14) (2023-07-07)
### Features
* button to open view settings tab ([32bbc45](https://github.com/antares-sql/antares/commit/32bbc453294736970ddbcadd3cc45597277f5bda))
### Bug Fixes
* unable to insert new rows ([1c73503](https://github.com/antares-sql/antares/commit/1c735031380c48aa8b08ed5ce3df80008dc72af8))
### Improvements
* **PostgreSQL:** minor improvement to schema selection ([9d8e9a5](https://github.com/antares-sql/antares/commit/9d8e9a5e1fda190ee7b3d4fd59e6178dd5ec1651))
### [0.7.13](https://github.com/antares-sql/antares/compare/v0.7.12...v0.7.13) (2023-07-06)
### Features
* button to open table settings tab, closes [#608](https://github.com/antares-sql/antares/issues/608) ([38bfea2](https://github.com/antares-sql/antares/commit/38bfea279ce93366dfd2021d0e91622a5a88878e))
* update connection ([462ede8](https://github.com/antares-sql/antares/commit/462ede8dc701aaf9c6089b3ec41eea0f31babdf9))
### Bug Fixes
* **PostgreSQL:** unable to connect to database, fixes [#614](https://github.com/antares-sql/antares/issues/614) ([e808b86](https://github.com/antares-sql/antares/commit/e808b86c52b8488e0c079a9f0ddce225338af4c0))
* unable to copy as sql inserts with BIT fileds, fixes [#613](https://github.com/antares-sql/antares/issues/613) ([6e01f0f](https://github.com/antares-sql/antares/commit/6e01f0f2e7194284341f89a44839d16398358f9b))
### [0.7.12](https://github.com/antares-sql/antares/compare/v0.7.11...v0.7.12) (2023-07-03)
### Features
* add Dutch language support ([4ff1d10](https://github.com/antares-sql/antares/commit/4ff1d107b8837887ceb3ffecaed46b9bdb908127))
* format options of csv export, closes [#196](https://github.com/antares-sql/antares/issues/196) ([3ad1e51](https://github.com/antares-sql/antares/commit/3ad1e51f42f3cc37ad66bdab64e9a8a0eadea74b))
* more translations ([575c8ea](https://github.com/antares-sql/antares/commit/575c8ea8cabc1fb315bad1693df6cd4ccd869838))
* **PostgreSQL:** ability to switch the database, closes [#432](https://github.com/antares-sql/antares/issues/432) ([89815bf](https://github.com/antares-sql/antares/commit/89815bf5e7c4062950b7418b31b45552ae0e1276))
* Sort available languages alphabetically ([3ff8d25](https://github.com/antares-sql/antares/commit/3ff8d2571be306a8a72e5ad2b9f963b285e8db26))
### Bug Fixes
* **PostgreSQL:** unable to export as sql inserts ([e07e7b7](https://github.com/antares-sql/antares/commit/e07e7b736ef29368262ec4b17d9e1f54ef3ec390))
* rename components and variables in SettingBarContext ([8a4a099](https://github.com/antares-sql/antares/commit/8a4a099e37c9bbc1365f322f6276f32153e24379))
* spelling corrections ([93e16fd](https://github.com/antares-sql/antares/commit/93e16fdda29601f3e898b1a9470bd8546ea95df6))
* update source string to fix spelling ([001983c](https://github.com/antares-sql/antares/commit/001983c5a2ebe17c943a9be3a58cc587f5cd09e3))
### [0.7.11](https://github.com/antares-sql/antares/compare/v0.7.10...v0.7.11) (2023-06-04)
### Features
* context menu to close tabs, closes [#392](https://github.com/antares-sql/antares/issues/392) ([787014c](https://github.com/antares-sql/antares/commit/787014c38df743315c04962871a3801805a93c55))
### Bug Fixes
* disable filter during table content loading, fixes [#588](https://github.com/antares-sql/antares/issues/588) ([e60726c](https://github.com/antares-sql/antares/commit/e60726c741276b7da0d54c0986d2a45ed9979bc9))
* **MySQL:** unable to get users list with some db settings ([9046707](https://github.com/antares-sql/antares/commit/904670781d47b1ac0dcfd982215ba1786f8c8145))
* unique keys not recognized in table settings on some MariaDB versions ([4461998](https://github.com/antares-sql/antares/commit/446199827be4b07382453739f42d46fa0201d04c))
* weird behavior in some text editors ([22bdaac](https://github.com/antares-sql/antares/commit/22bdaac18b1c46a57802cbbd3ad339ee075ec70b))
### [0.7.10](https://github.com/antares-sql/antares/compare/v0.7.9...v0.7.10) (2023-05-28)
### Features
* copy rows as PHP array ([03638c0](https://github.com/antares-sql/antares/commit/03638c05534e9ce2e594ce5945485587ed99e609))
* DDL query in table settings for MySQL and PostgreSQL, closes [#581](https://github.com/antares-sql/antares/issues/581) ([f454b4b](https://github.com/antares-sql/antares/commit/f454b4bb1ca79eec285b3b4039a2ef66802ff82a))
* export table content or query results as PHP array, closes [#575](https://github.com/antares-sql/antares/issues/575) ([8968179](https://github.com/antares-sql/antares/commit/8968179c11f4fe3e624873aac4685a5a33521024))
* keepalive on mysql/postgre connections, should fix [#577](https://github.com/antares-sql/antares/issues/577) ([17eeb6d](https://github.com/antares-sql/antares/commit/17eeb6d38e45b553e35e004b748569971743ca18))
### Bug Fixes
* disable shorctut to show Ace editor settings, fixes [#585](https://github.com/antares-sql/antares/issues/585) ([2c0b4ff](https://github.com/antares-sql/antares/commit/2c0b4ffe1f2e418f5e9120a40787788d8e7fd27e))
### [0.7.9](https://github.com/antares-sql/antares/compare/v0.7.8...v0.7.9) (2023-05-01)
### Features
* no table results message ([19859f4](https://github.com/antares-sql/antares/commit/19859f45f4457292b6ecfe79bdcfbdcc7722be06))
* option to choose the target table of an SQL INSERT exportation, closes [#556](https://github.com/antares-sql/antares/issues/556) ([c48266c](https://github.com/antares-sql/antares/commit/c48266c336d7c61abe2b56b5702e5bca83bb57b3))
* **translation:** ko-KR translation, closes [#561](https://github.com/antares-sql/antares/issues/561) ([baef4ea](https://github.com/antares-sql/antares/commit/baef4ea4d1747233a86b90fe5b60a0d6cfba1f1c))
### Bug Fixes
* sidebar height out of visible area ([af91d96](https://github.com/antares-sql/antares/commit/af91d96db6e79222e5dbc9b880a904a40332c09b))
* unable to delete rows with null values and no primary key ([74c136f](https://github.com/antares-sql/antares/commit/74c136f8334b6972ae55dd8ee0ade09ef8ae3282))
* vertical scrollbar does not reset after performing a search, fixes [#567](https://github.com/antares-sql/antares/issues/567) ([0cd55fb](https://github.com/antares-sql/antares/commit/0cd55fbfe9ff09589ae5993f16b0dd56a2ea1a5a))
### Improvements
* **translation:** update italian translation ([96e1ceb](https://github.com/antares-sql/antares/commit/96e1ceb1d2488390216553cd3fce2eec261f04eb))
### [0.7.8](https://github.com/antares-sql/antares/compare/v0.7.7...v0.7.8) (2023-04-12)
### Features
* filter schemas in sidebar, closes [#555](https://github.com/antares-sql/antares/issues/555) ([8be9f93](https://github.com/antares-sql/antares/commit/8be9f932e7a44b2067d8b57950d8faafc577123f))
* **MySQL:** option to export from results SQL INSERTS in chunks, closes [#501](https://github.com/antares-sql/antares/issues/501) ([0f24c80](https://github.com/antares-sql/antares/commit/0f24c80e5a2dc45875df6b67d3c097cf1cca458e))
### Bug Fixes
* **MySQL:** missing scale for FLOAT type ([52108d7](https://github.com/antares-sql/antares/commit/52108d76133d5fdffb56faa995d7ab7ee3e7c4bc))
* triggers not exported if related table not included ([d802b32](https://github.com/antares-sql/antares/commit/d802b32597e42ee90a2d691fe74245b3bc2517ee))
* unable to export BLOB values from table content o query result ([afa61a9](https://github.com/antares-sql/antares/commit/afa61a9bc2d698894096a6b5413c49f05b2fd5aa))
### [0.7.7](https://github.com/antares-sql/antares/compare/v0.7.6...v0.7.7) (2023-03-10)
### Bug Fixes
* hide table size tooltip if disabled ([2c46269](https://github.com/antares-sql/antares/commit/2c46269cf262248d3f5644b7c5b89d5d317a89a4))
* **Linux:** remove app menu shown pressing ALT key ([63fece2](https://github.com/antares-sql/antares/commit/63fece2a1b6b09f61ae416f7c3a7b51ee0a53d0e))
* **Linux:** remove app menu shown pressing ALT key ([b925ff9](https://github.com/antares-sql/antares/commit/b925ff9c01afcc727e5d70bafb079d468ed1eede))
* **MySQL:** missing table information in table setting tab ([59846e6](https://github.com/antares-sql/antares/commit/59846e6ff47591ec8dc22403c3be0e70e0f3ccfd))
* unable to set string fields default values starting with 0 ([3546c57](https://github.com/antares-sql/antares/commit/3546c57e398be7b2e226bb8e93e8fc0fb8bab40a))
### [0.7.6](https://github.com/antares-sql/antares/compare/v0.7.5...v0.7.6) (2023-02-27)
### Bug Fixes
* error with import/export tools, fixes [#541](https://github.com/antares-sql/antares/issues/541) ([d1297a0](https://github.com/antares-sql/antares/commit/d1297a0085fd54967817816efaeed5770a887bbf))
### [0.7.5](https://github.com/antares-sql/antares/compare/v0.7.4...v0.7.5) (2023-02-26)
### Features
* **MySQL:** option, disabled by default, to enable table size indicators on sidebar ([313e740](https://github.com/antares-sql/antares/commit/313e7407eb1afe5d19ac49ee4b308950b48cafa8))
### Bug Fixes
* **MariaDB:** exception with event_scheduler DISABLED with MariaDB 10, fixes [#535](https://github.com/antares-sql/antares/issues/535) ([4458177](https://github.com/antares-sql/antares/commit/445817768863616ea7340c8bf62472197b73bd6e))
* single quotes not properly escaped for random generated content ([629ce63](https://github.com/antares-sql/antares/commit/629ce633294c862266db9e27ffa5c154e8fc416c))
* unable to import after a failed import, fixes [#515](https://github.com/antares-sql/antares/issues/515) ([b1fbc43](https://github.com/antares-sql/antares/commit/b1fbc43ab2f39827cb85ac7d21ac889ffc2f4c64))
### [0.7.4](https://github.com/antares-sql/antares/compare/v0.7.3...v0.7.4) (2023-02-10)
### Bug Fixes
* tables sort in sidebar ([ddb7ead](https://github.com/antares-sql/antares/commit/ddb7ead0832708713ba4bb2717661b8b93a47e3f))
### [0.7.3](https://github.com/antares-sql/antares/compare/v0.7.2...v0.7.3) (2023-02-09)
### Features
* **SQLite:** added support to INTEGER UNSIGNED ([e7e4913](https://github.com/antares-sql/antares/commit/e7e491340a037b64d6d8e538376415779d54332e))
### Bug Fixes
* longtext edit modal opens when it shouldn't, fixes [#524](https://github.com/antares-sql/antares/issues/524) ([6decba3](https://github.com/antares-sql/antares/commit/6decba316ca46106520cb4dba44409ceb4a4af75))
* select of table type stuck when editing an unknown type ([e8447e5](https://github.com/antares-sql/antares/commit/e8447e56551871a200517bdaa747ae215ad83cf4))
* **SQLite:** error with integer timestamp fields ([2b5e1e7](https://github.com/antares-sql/antares/commit/2b5e1e7b39c25f536b6139a4d01b9f7f17069ea8))
* **SQLite:** triggers disappear after editing related table, fixes [#523](https://github.com/antares-sql/antares/issues/523) ([d934ae1](https://github.com/antares-sql/antares/commit/d934ae1e6c0747698b4973d9cad217379076a6cf))
### [0.7.2](https://github.com/antares-sql/antares/compare/v0.7.1...v0.7.2) (2023-01-22)
### Features
* add copy shortcut and default copy type setting ([9aca894](https://github.com/antares-sql/antares/commit/9aca89477f1fd7b7f55f1e5c290d495c46f61d8e))
* connection info icons in footer ([ff4bc6c](https://github.com/antares-sql/antares/commit/ff4bc6c39b05a827cebde84466814cf246908208))
### Bug Fixes
* allow comments in queies, fixes [#519](https://github.com/antares-sql/antares/issues/519) ([c7ab3b7](https://github.com/antares-sql/antares/commit/c7ab3b77a22e85cee6fb93064eaad5a8e8ad9fd2))
* **SQLite:** exception saving tables without INT fields length ([0e80e82](https://github.com/antares-sql/antares/commit/0e80e823d059dfe24995b5848d88cc84235e6275))
* ssh connection closed after idle time, fixes [#425](https://github.com/antares-sql/antares/issues/425) ([6fa430a](https://github.com/antares-sql/antares/commit/6fa430adf68013a9d0a093031f56dd741bdc0299))
### [0.7.1](https://github.com/antares-sql/antares/compare/v0.7.0...v0.7.1) (2022-12-23)
### Features
* Copy rows as html table, so we can paste it to spreadsheet ([c32f463](https://github.com/antares-sql/antares/commit/c32f463ea5ac3f54cba32929f77442f1e0ba934a))
* option to disable selected query execution, closes [#477](https://github.com/antares-sql/antares/issues/477) ([1bd26ce](https://github.com/antares-sql/antares/commit/1bd26ceaa68fe66f26c76b3b60fa6eeccea91729))
### Bug Fixes
* bahasa indonesia typos ([897795d](https://github.com/antares-sql/antares/commit/897795ddbb4ade2652b0471f18288b8b3aaf0eb9))
* connection default icon not change after client change ([a6bdf69](https://github.com/antares-sql/antares/commit/a6bdf69a281c8614c41274b6dc2f3563aa89c57e))
* context submenu outside view when near the edge, fixes [#506](https://github.com/antares-sql/antares/issues/506) ([c08946e](https://github.com/antares-sql/antares/commit/c08946e932884e5f0253df2545f98315ab7e5219))
* **i18n:** add missing keys for french translation ([fd129a2](https://github.com/antares-sql/antares/commit/fd129a2ad1c3401372c9172b38f4406254d134df))
* **MySQL:** not every connection gets read-only option ([843c15e](https://github.com/antares-sql/antares/commit/843c15e428c4a0412f19a93ab05d2fcbb60da09b))
* **UI:** white background dragging connections inside folder on Linux ([dd971d7](https://github.com/antares-sql/antares/commit/dd971d70e04faf0d5b239586b12e4a9a42407433))
* white background dragging connections or tabs on Linux, fixes [#486](https://github.com/antares-sql/antares/issues/486) ([669d7e8](https://github.com/antares-sql/antares/commit/669d7e8d4d062ed5bdafe1d5cde8ec51a2f68b26))
## [0.7.0](https://github.com/antares-sql/antares/compare/v0.6.0...v0.7.0) (2022-11-30)
### Features
* **UI:** connections customization ([212b2bd](https://github.com/antares-sql/antares/commit/212b2bdba933db1af22967a057d64f41c96b9930))
* **UI:** folders customization ([72accb7](https://github.com/antares-sql/antares/commit/72accb7b0e62977dde2c82312aee5b9d025e5182))
* **UI:** folders implementation ([ece6c64](https://github.com/antares-sql/antares/commit/ece6c6401def4a9b42280f23efaa038b9ad98de8))
* **UI:** footer color based on folder color ([4fe9dfc](https://github.com/antares-sql/antares/commit/4fe9dfc4d7ca19a798b8a8a74d979ab88aebeaac))
* **UI:** new settimgbar tooltips ([6728964](https://github.com/antares-sql/antares/commit/672896414e901635c83ca037663e355a31ce013b))
### Bug Fixes
* deletion of connections inside folder ([b06bafe](https://github.com/antares-sql/antares/commit/b06bafe06c060233ebe901979b9fc4455a25cb89))
* missing sidebar data after update ([0a1f50a](https://github.com/antares-sql/antares/commit/0a1f50a9b92c9705784b93f14be40a01a78cb0da))
* **UI:** folder to folder drag glitches ([0fca70e](https://github.com/antares-sql/antares/commit/0fca70ebec1af3d594db4e1ce159e52e12224850))
* **UI:** wrong copnnection icons color with light theme ([d010d5a](https://github.com/antares-sql/antares/commit/d010d5aa8f07a5def1183567ee767fd144696ed3))
* wrong position moving elements outside folder ([7af178a](https://github.com/antares-sql/antares/commit/7af178a1e400876e6c45dbe31a198a12d29227a8))
## [0.6.0](https://github.com/antares-sql/antares/compare/v0.5.19...v0.6.0) (2022-11-18)
### Features
* **Firebird SQL:** connections pool ([76df631](https://github.com/antares-sql/antares/commit/76df6319c242ea42f93d4e5d811d96ec2c282aa8))
* **Firebird SQL:** display table content and query results ([95bb41e](https://github.com/antares-sql/antares/commit/95bb41e9db255a780aae1ae32ce4a53ee3bab20e))
* **Firebird SQL:** manual commit mode ([27566c1](https://github.com/antares-sql/antares/commit/27566c1dfae55f72d3f870c50410e5ecda256037))
* **Firebird SQL:** procedure add/edit/delete support ([ae312ef](https://github.com/antares-sql/antares/commit/ae312efbbc3a9941380477b9849bdd8edc5b9fbf))
* **Firebird SQL:** support to blob fields ([0827a04](https://github.com/antares-sql/antares/commit/0827a04d61af75b4366394e5f0289df461d02c98))
* **Firebird SQL:** support to indexes and foreign keys ([2c8509f](https://github.com/antares-sql/antares/commit/2c8509ff4173fbebeff92ab472a37edd3d80a2ac))
* **Firebird SQL:** table add/edit/delete support ([1b5cc31](https://github.com/antares-sql/antares/commit/1b5cc315dddca6b753fb6fe6e196e29441ffed79))
* **Firebird SQL:** trigger add/edit/delete support ([8e422e3](https://github.com/antares-sql/antares/commit/8e422e3f07323f388523621a05f0403a87f19e47))
* **Firebird SQL:** view add/edit/delete support ([7d1967a](https://github.com/antares-sql/antares/commit/7d1967a60977b2ce1095a37b7135f429a83f163d))
* support to text blob fields ([e6f6a02](https://github.com/antares-sql/antares/commit/e6f6a022d1a5bbc3f5303f635a2115813601c61a))
### Bug Fixes
* **Firebird SQL:** connection pool issue ([7ff8e21](https://github.com/antares-sql/antares/commit/7ff8e2149ef911a235b4a1dcc329775af1d2a72b))
* incomplete list of collations, fixes [#478](https://github.com/antares-sql/antares/issues/478) ([1c1403f](https://github.com/antares-sql/antares/commit/1c1403f58641f7b5f8a7c29fc430673ffa88f969))
* loss of precision updating BIGINT values, fixes [#467](https://github.com/antares-sql/antares/issues/467) ([d190a2d](https://github.com/antares-sql/antares/commit/d190a2dd61040d1748dfb97403f9d56015d938fe))
### [0.5.19](https://github.com/antares-sql/antares/compare/v0.5.18...v0.5.19) (2022-10-22)
### Features
* context menu option to fill cell with random values ([0a2124f](https://github.com/antares-sql/antares/commit/0a2124f2c2bdadc7c753db11d1e29f8acb9ddcac))
* uuid fill for string cells ([24edc82](https://github.com/antares-sql/antares/commit/24edc82b1be7299a09df18611b2a0ba361a6b46f))
### Bug Fixes
* app stuck inserting a random value if field length high ([440f74d](https://github.com/antares-sql/antares/commit/440f74dfc1f4942ba585b9bdae7517fe6ab04a81))
* error joining tables with different schema ([88408da](https://github.com/antares-sql/antares/commit/88408da745e45c70de977bc4270e5f61825be65f))
* **SQLite:** save boolean as integer to improve compativility, fixes [#463](https://github.com/antares-sql/antares/issues/463) ([d52b7af](https://github.com/antares-sql/antares/commit/d52b7af2978bc8beafd2d22078c72abb62e9e532))
* unable to edit text fields if value is NULL, fixes [#466](https://github.com/antares-sql/antares/issues/466) ([8621ca5](https://github.com/antares-sql/antares/commit/8621ca5333b5c51dc7a2ea1fcc0c5ec7f752a00a))
### [0.5.18](https://github.com/antares-sql/antares/compare/v0.5.17...v0.5.18) (2022-10-14)
### Features
* **PostgreSQL:** UUID random generation option on UUID fields, closes [#424](https://github.com/antares-sql/antares/issues/424) ([a521274](https://github.com/antares-sql/antares/commit/a521274d01031c1411bbbb136369802d43368089))
### Bug Fixes
* auto-scroll on sidebar not working, fixes [#447](https://github.com/antares-sql/antares/issues/447) ([dd9c089](https://github.com/antares-sql/antares/commit/dd9c089d27c61ab76d49887c7de2849cbb6e88a6))
* trackpad horizontal scroll on tabs not working properly ([ba5a1b6](https://github.com/antares-sql/antares/commit/ba5a1b68ab2d56777a5c94eede26e9bded5819e6))
### [0.5.17](https://github.com/antares-sql/antares/compare/v0.5.16...v0.5.17) (2022-09-22)
### Features
* added more editor font sizes, closes [#440](https://github.com/antares-sql/antares/issues/440) ([d114f8a](https://github.com/antares-sql/antares/commit/d114f8a65164f702b23175095e6fc2b021e0e038))
### Bug Fixes
* "run or reload" shortcut triggers on all connections open ([01f607c](https://github.com/antares-sql/antares/commit/01f607cd40c18ab0f9761b2a05705a966aaae43a))
* cant run procedures with parameters from leftbar ([efe134a](https://github.com/antares-sql/antares/commit/efe134a059700ca87333dc6e66166d6ec8d289e8))
* editor font size doesn't change on new tabs, fixes [#442](https://github.com/antares-sql/antares/issues/442) ([84168d1](https://github.com/antares-sql/antares/commit/84168d1d75460acc2c844bfece7d85f0c977e74c))
* empty definer when editing a view, fixes [#437](https://github.com/antares-sql/antares/issues/437) ([498a9b4](https://github.com/antares-sql/antares/commit/498a9b48e25ee061960f5f649c953cdaf6ff1a58))
* **MacOS:** empty options on macos menubar ([a142d3c](https://github.com/antares-sql/antares/commit/a142d3c4d77e31375dfbea148eec54ce1f635192))
### [0.5.16](https://github.com/antares-sql/antares/compare/v0.5.15...v0.5.16) (2022-08-26)
### Bug Fixes
* CTRL+Right/Left not working on text editor, closes [#427](https://github.com/antares-sql/antares/issues/427) ([ffc645b](https://github.com/antares-sql/antares/commit/ffc645ba5efb1c52670096e4f8c7f992b7335dae))
* issue updating datetime cells with null value, closes [#423](https://github.com/antares-sql/antares/issues/423) ([ebc325a](https://github.com/antares-sql/antares/commit/ebc325ae0c656dca2eb8f7544ab271beaee9b47e))
* ts exceptions ([df68114](https://github.com/antares-sql/antares/commit/df681147aaf0bfca69f3ffdc474cc1846541b1d8))
* **UI:** editor themes group not visible in select element ([9dc700e](https://github.com/antares-sql/antares/commit/9dc700e13ea65bb8c6feac4ff4ffeadd32053614))
* **UI:** wrong position of fields resizable area ([c90ab0e](https://github.com/antares-sql/antares/commit/c90ab0e8807ff30a9ab58e9aa3515cf427dd6e86))
* unable to set null or delete rows without primary key ([39326eb](https://github.com/antares-sql/antares/commit/39326eb52e038728b5419d4a8de8024c7ead3002))
### [0.5.15](https://github.com/antares-sql/antares/compare/v0.5.14...v0.5.15) (2022-08-17)
### Features
* ability to add new shortcuts ([d044a02](https://github.com/antares-sql/antares/commit/d044a02cb79a9d06aadc34cdbf6e81da84360559))
* ability to edit shortcuts ([8eb127e](https://github.com/antares-sql/antares/commit/8eb127e45838bc01ba12f0740fec077fcd975532))
* added more events in shortcuts setting ([5043faf](https://github.com/antares-sql/antares/commit/5043fafa934844ebc2f59cabcec830c6a4d5ca8e))
* delete shortcuts and restore defaults ([c22413f](https://github.com/antares-sql/antares/commit/c22413fde9dfe5501a5f220070cfe552a318c70b))
* dynamic shortcut suggestions on empty query tabs ([4df14c3](https://github.com/antares-sql/antares/commit/4df14c3693955bd7801b4b99103fca85f00f3e8c))
* list of available shortcuts in settings window ([44bb75b](https://github.com/antares-sql/antares/commit/44bb75bc60d7d31bbd99a9ba57f30fd354f7581c))
* **UI:** connection name on left bar, closes [#382](https://github.com/antares-sql/antares/issues/382) [#414](https://github.com/antares-sql/antares/issues/414) ([4887753](https://github.com/antares-sql/antares/commit/48877534d1a41d351b267c0dab925046ca984179))
* **UI:** shortcuts setting UI improved ([49b63bc](https://github.com/antares-sql/antares/commit/49b63bc6f28fc6031e6a892d0a48cd35ae2f26cd))
### Bug Fixes
* startup exception ([c50d17e](https://github.com/antares-sql/antares/commit/c50d17e82b7fd337d4037ddf646cd1a8fc765bae))
### Improvements
* improved keypress detector ([0f219cf](https://github.com/antares-sql/antares/commit/0f219cf9b796b4369c609fb0e8e3b84346a30b07))
* **translation:** updated italian translation ([c05be83](https://github.com/antares-sql/antares/commit/c05be8304f3cf299cf338f67c00184305e022919))
### [0.5.14](https://github.com/antares-sql/antares/compare/v0.5.13...v0.5.14) (2022-08-09)
### Bug Fixes
* unable to open settingbar context menu ([44eb507](https://github.com/antares-sql/antares/commit/44eb507a12bad028a4fa8a8bb0f6442a3e8dde91))
### [0.5.13](https://github.com/antares-sql/antares/compare/v0.5.12...v0.5.13) (2022-08-09)
### Features
* copy row as CSV, closes [#394](https://github.com/antares-sql/antares/issues/394) ([1c3d7aa](https://github.com/antares-sql/antares/commit/1c3d7aa30bb9c2bd900a764ee6b97960729e9263))
* new macos icon ([0bfa14e](https://github.com/antares-sql/antares/commit/0bfa14e1c90320578597df030941530b670a4131))
### Bug Fixes
* **MySQL:** error with ANSI sql_mode ([f64a12a](https://github.com/antares-sql/antares/commit/f64a12a8e9c5f764c3a692f1a032736e008058b5))
* set legacy: false ([104b7c9](https://github.com/antares-sql/antares/commit/104b7c928b9c2abfc056880f16c606a0b1fa7c67))
### [0.5.12](https://github.com/antares-sql/antares/compare/v0.5.11...v0.5.12) (2022-07-26)
### Features
* ability to copy multiple selected rows ([9551afb](https://github.com/antares-sql/antares/commit/9551afbd2d7e525c81f28e98e788b92609ce9de4))
* context menu option to duplicate a table row ([985e5d3](https://github.com/antares-sql/antares/commit/985e5d352793d1b3e1981d004b6f494bfbb049bf))
* copy row as SQL INSERT ([d3da15a](https://github.com/antares-sql/antares/commit/d3da15aa1377dcba73927047563f1d0c2d1284ca))
* execute selected query ([7890263](https://github.com/antares-sql/antares/commit/78902639ebb29a8c53f8aa0d2045c74e0646febc))
* export table content as SQL INSERT ([f3b5de3](https://github.com/antares-sql/antares/commit/f3b5de38c4abfd2c1d738e179fc22e6c8b6f9080))
### Bug Fixes
* disable ctrl+alt+(left/right) shortcut on linux ([8ecaedb](https://github.com/antares-sql/antares/commit/8ecaedbf6c2fc0dc56ff2177a87dd6ede74bdd22))
* error on schema export ([1d151e9](https://github.com/antares-sql/antares/commit/1d151e9349fd97576ccd8ef7f88ca789a1f28b65))
* issue with logger on import/export ([cb038b3](https://github.com/antares-sql/antares/commit/cb038b374a4fe85ad569e42eee7af123c925e775))
* missing defaults on insert row window ([1ead76c](https://github.com/antares-sql/antares/commit/1ead76c02889f48bd91cae702820b082ca2ff54b))
* missing table on insert new records on session restored tabs ([8c83b3f](https://github.com/antares-sql/antares/commit/8c83b3f1447354ec63b2a308db05ad4d54659aa7))
* **MySQL:** missing quoted identifier for column names in table filter, closes [#380](https://github.com/antares-sql/antares/issues/380) ([eb60899](https://github.com/antares-sql/antares/commit/eb60899e6e17879c79a7ee7108061e9aca8596f7))
* prevent ctrl+a in console ([a00c19d](https://github.com/antares-sql/antares/commit/a00c19d3003cd43d3ee6e3132728122bb2b24c97))
### [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) ### [0.5.5](https://github.com/antares-sql/antares/compare/v0.5.4...v0.5.5) (2022-05-24)

View File

@@ -44,7 +44,8 @@ In this folder is located the structure of Vue frontend application.
## Build ## Build
The command to build Antares SQL locally is `npm run build`. The command to build Antares SQL locally is `npm run build:local`.
`build` command (without `:local`) is used exclusively by the GitHub Action.
## Conventions ## Conventions

View File

@@ -7,23 +7,18 @@
# Antares SQL Client # Antares SQL Client
![GitHub package.json version](https://img.shields.io/github/package-json/v/fabio286/antares) ![GitHub](https://img.shields.io/github/license/fabio286/antares) [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Ffabio286%2Fantares%2Fbadge&style=flat)](https://actions-badge.atrox.dev/fabio286/antares/goto) [![antares](https://snapcraft.io/antares/badge.svg)](https://snapcraft.io/antares) [![antares](https://snapcraft.io/antares/trending.svg?name=0)](https://snapcraft.io/antares) ![GitHub package.json version](https://img.shields.io/github/package-json/v/fabio286/antares) ![GitHub](https://img.shields.io/github/license/fabio286/antares) [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Ffabio286%2Fantares%2Fbadge&style=flat)](https://actions-badge.atrox.dev/fabio286/antares/goto) [![antares](https://snapcraft.io/antares/badge.svg)](https://snapcraft.io/antares) [![antares](https://snapcraft.io/antares/trending.svg?name=0)](https://snapcraft.io/antares) [![Twitter Follow](https://img.shields.io/twitter/follow/AntaresSQL?style=social)](https://twitter.com/AntaresSQL) [![Plant a Tree](https://raw.githubusercontent.com/Fabio286/treedom-badge/master/svg/plant-a-tree.svg)](https://www.treedom.net/en/user/fabio-di-stasio/event/antares-for-the-planet)
![Mastodon Follow](https://img.shields.io/mastodon/follow/%20110860460902482117?domain=https%3A%2F%2Ffosstodon.org&style=social) [![Twitter Follow](https://img.shields.io/twitter/follow/AntaresSQL?style=social)](https://twitter.com/AntaresSQL) [![Plant a Tree](https://raw.githubusercontent.com/Fabio286/treedom-badge/master/svg/plant-a-tree.svg)](https://www.treedom.net/en/user/fabio-di-stasio/event/antares-for-the-planet)
Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers. Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers.
Our target is to support as many databases as possible, and all major operating systems, including the ARM versions. Our target is to support as many databases as possible, and all major operating systems, including the ARM versions.
**At the moment this application is in development state, many features will come in future updates**, and supports only MySQL/MariaDB, PostgreSQL, SQLite and Firebird SQL. **At the moment this application is in development state, many features will come in future updates**, and supports only MySQL/MariaDB, PostgreSQL and SQLite.
However, there are all the features necessary to have a pleasant database management experience, so give it a chance and send us your feedback, we would really appreciate it. At the moment, however, there are all the features necessary to have a pleasant database management experience, so give it a chance and send us your feedback, we would really appreciate it.
We are actively working on it, hoping to provide new cool features, improvements and fixes as soon as possible. We are actively working on it, hoping to provide new cool features, improvements and fixes as soon as possible.
🔗 If you are curious to try Antares you can download and install the [latest release](https://github.com/Fabio286/antares/releases/latest). 🔗 If you are curious to try Antares you can download and install the [latest release](https://github.com/Fabio286/antares/releases/latest).
👁 To stay tuned for new releases follow Antares SQL on [Mastodon](https://fosstodon.org/@AntaresSQL) or [Twitter](https://twitter.com/AntaresSQL). 👁 To stay tuned for new releases [follow Antares SQL](https://twitter.com/AntaresSQL) on Twitter.
🌟 Don't forget to **leave a star** if you appreciate this project. 🌟 Don't forget to **leave a star** if you appreciate this project.
🗳️ Polls:
- **[Which is the main OS you use Antares on?](https://github.com/antares-sql/antares/discussions/379)**
- **[Which database do you use the most?](https://github.com/antares-sql/antares/discussions/594)**
## Current key features ## Current key features
@@ -38,7 +33,6 @@ We are actively working on it, hoping to provide new cool features, improvements
- SSH tunnel support. - SSH tunnel support.
- Manual commit mode. - Manual commit mode.
- Import and export database dumps. - Import and export database dumps.
- Customizable keyboard shortcuts.
- Dark and light theme. - Dark and light theme.
- Editor themes. - Editor themes.
@@ -46,20 +40,20 @@ We are actively working on it, hoping to provide new cool features, improvements
Why are we developing an SQL client when there are a lot of them on the market? 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 **forever 100% free (without paid premium feature)**, full featured, as possible community driven, cross platform and open source alternative, empowered by JavaScript ecosystem.
A modern application created with minimalism and simplicity in mind, with features in the right places, not hundreds of tiny buttons, nested tabs or submenues; productivity comes first. 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 ## Installation
Based on your operating system you can have one or more distribution formats to choose based on your preferences. Based on your operating system you can have one or more distribution formats to choose based on your preferences.
Since Antares SQL is a free software we don't have a budget to spend on annual licenses or certificates. This can result that on some platforms you might need to put in some additional work to install this app. Since Antares SQL is a free software we haven't a budget to spend in annual licenses or certificates. This can result that on some platforms you need some additional passages to install this app.
### Linux ### Linux
On Linux you can simply download and run the `.AppImage` distribution, install from Snap Store, from AUR or from our [PPA repository](https://github.com/antares-sql/antares-ppa). On Linux you can simply download and run `.AppImage` distributions, install from Snap Store or from AUR.
### Windows ### Windows
On Windows you can choose between downloading the app from Microsoft Store or downloading the `.exe` from our [website](https://antares-sql.app/downloads) or [this github repo](https://github.com/Fabio286/antares/releases/latest). Distributions that are not from Microsoft Store are not signed with a certificate, so to install you need to click on "More info" and then "Run anyway" on SmartScreen prompt. On Windows you can choose between Microsoft Store and download `.exe` distribution. The latter lacks of a certificate, so to install you need to click on "More info" and then "Run anyway" on SmartScreen prompt.
### MacOS ### MacOS
@@ -67,7 +61,7 @@ On macOS you can run `.dmg` distribution following [this guide](https://support.
## Download ## Download
[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/antares) [![Get it from AUR](https://raw.githubusercontent.com/Fabio286/antares/3e00c4bae6e036300c752c1a40c5a038fea9c169/docs/aur-badge.svg)](https://aur.archlinux.org/packages/antares-sql-bin) [<img src="https://developer.microsoft.com/store/badges/images/English_get-it-from-MS.png" style="height: 56px">](https://www.microsoft.com/p/antares-sql-client/9nhtb9sq51r1?cid=storebadge&ocid=badge&rtc=1&activetab=pivot:overviewtab) [![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/antares) [![Get it from AUR](https://raw.githubusercontent.com/Fabio286/antares/3e00c4bae6e036300c752c1a40c5a038fea9c169/docs/aur-badge.svg)](https://aur.archlinux.org/packages/antares-sql/) [![Get it from Microsoft Store](https://raw.githubusercontent.com/Fabio286/antares/gh-pages/src/assets/ms-store.png)](https://www.microsoft.com/p/antares-sql-client/9nhtb9sq51r1?cid=storebadge&ocid=badge&rtc=1&activetab=pivot:overviewtab)
🚀 **[Other Downloads](https://github.com/Fabio286/antares/releases/latest)** 🚀 **[Other Downloads](https://github.com/Fabio286/antares/releases/latest)**
## Coming soon ## Coming soon
@@ -88,8 +82,8 @@ This is a roadmap with major features will come in near future.
- [x] MySQL/MariaDB - [x] MySQL/MariaDB
- [x] PostgreSQL - [x] PostgreSQL
- [x] SQLite - [x] SQLite
- [x] Firebird SQL - [ ] MSSQL
- [ ] SQL Server - [ ] OracleDB
- [ ] More... - [ ] More...
### Operating Systems ### Operating Systems
@@ -120,42 +114,30 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
<!-- markdownlint-disable --> <!-- markdownlint-disable -->
<table> <table>
<tbody> <tr>
<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" valign="top" width="14.28%"><a href="https://fabiodistasio.it/"><img src="https://avatars.githubusercontent.com/u/31471771?v=4?s=100" width="100px;" alt="Fabio Di Stasio"/><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" valign="top" width="14.28%"><a href="https://www.linkedin.com/in/giulioganci/"><img src="https://avatars.githubusercontent.com/u/4192159?v=4?s=100" width="100px;" alt="Giulio Ganci"/><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" valign="top" width="14.28%"><a href="https://christianratz.de/"><img src="https://avatars.githubusercontent.com/u/2630316?v=4?s=100" width="100px;" alt="Christian Ratz"/><br /><sub><b>Christian Ratz</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=digitalgopnik" title="Code">💻</a> <a href="#translation-digitalgopnik" title="Translation">🌍</a></td> <td align="center"><a href="https://reverb6821.github.io/"><img src="https://avatars.githubusercontent.com/u/55198803?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Giuseppe Gigliotti</b></sub></a><br /><a href="#translation-reverb6821" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://reverb6821.github.io/"><img src="https://avatars.githubusercontent.com/u/55198803?v=4?s=100" width="100px;" alt="Giuseppe Gigliotti"/><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" valign="top" width="14.28%"><a href="https://github.com/Mohd-PH"><img src="https://avatars.githubusercontent.com/u/9362157?v=4?s=100" width="100px;" alt="Mohd-PH"/><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>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hongkfui"><img src="https://avatars.githubusercontent.com/u/37477191?v=4?s=100" width="100px;" alt="hongkfui"/><br /><sub><b>hongkfui</b></sub></a><br /><a href="#translation-hongkfui" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/MrAnyx"><img src="https://avatars.githubusercontent.com/u/44176707?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Robin</b></sub></a><br /><a href="#translation-MrAnyx" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MrAnyx"><img src="https://avatars.githubusercontent.com/u/44176707?v=4?s=100" width="100px;" alt="Robin"/><br /><sub><b>Robin</b></sub></a><br /><a href="#translation-MrAnyx" title="Translation">🌍</a></td> </tr>
</tr> <tr>
<tr> <td align="center"><a href="https://github.com/daeleduardo"><img src="https://avatars.githubusercontent.com/u/8599078?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Daniel Eduardo</b></sub></a><br /><a href="#translation-daeleduardo" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/daeleduardo"><img src="https://avatars.githubusercontent.com/u/8599078?v=4?s=100" width="100px;" alt="Daniel Eduardo"/><br /><sub><b>Daniel Eduardo</b></sub></a><br /><a href="#translation-daeleduardo" title="Translation">🌍</a></td> <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" valign="top" width="14.28%"><a href="https://ngoquocdat.com/"><img src="https://avatars.githubusercontent.com/u/56961917?v=4?s=100" width="100px;" alt="Ngô Quốc Đạt"/><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" valign="top" width="14.28%"><a href="https://github.com/IsamuSugi"><img src="https://avatars.githubusercontent.com/u/7746658?v=4?s=100" width="100px;" alt="Isamu Sugiura"/><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" valign="top" width="14.28%"><a href="http://rsacchetto.nexxontech.it/"><img src="https://avatars.githubusercontent.com/u/18429412?v=4?s=100" width="100px;" alt="Riccardo Sacchetto"/><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" valign="top" width="14.28%"><a href="https://kilianstallinger.com"><img src="https://avatars.githubusercontent.com/u/5290318?v=4?s=100" width="100px;" alt="Kilian Stallinger"/><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" valign="top" width="14.28%"><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>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/goYou"><img src="https://avatars.githubusercontent.com/u/62732795?v=4?s=100" width="100px;" alt="goYou"/><br /><sub><b>goYou</b></sub></a><br /><a href="#translation-goYou" title="Translation">🌍</a></td> </tr>
</tr> <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" valign="top" width="14.28%"><a href="https://github.com/raliqala"><img src="https://avatars.githubusercontent.com/u/30502407?v=4?s=100" width="100px;" alt="Topollo"/><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" valign="top" width="14.28%"><a href="https://github.com/SmileYzn"><img src="https://avatars.githubusercontent.com/u/5851851?v=4?s=100" width="100px;" alt="Cleverson"/><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" valign="top" width="14.28%"><a href="https://github.com/fredatgithub"><img src="https://avatars.githubusercontent.com/u/6720055?v=4?s=100" width="100px;" alt="fred"/><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" valign="top" width="14.28%"><a href="https://github.com/xak666"><img src="https://avatars.githubusercontent.com/u/38811437?v=4?s=100" width="100px;" alt="xaka_xak"/><br /><sub><b>xaka_xak</b></sub></a><br /><a href="#translation-xak666" title="Translation">🌍</a></td> </tr>
<td align="center" valign="top" width="14.28%"><a href="https://codepen.io/theo-billardey"><img src="https://avatars.githubusercontent.com/u/48206778?v=4?s=100" width="100px;" alt="Théo Billardey"/><br /><sub><b>Théo Billardey</b></sub></a><br /><a href="#translation-brdtheo" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://yaskur.net"><img src="https://avatars.githubusercontent.com/u/9539970?v=4?s=100" width="100px;" alt="Muhammad Dyas Yaskur"/><br /><sub><b>Muhammad Dyas Yaskur</b></sub></a><br /><a href="#translation-dyaskur" title="Translation">🌍</a> <a href="https://github.com/antares-sql/antares/commits?author=dyaskur" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jimcat8"><img src="https://avatars.githubusercontent.com/u/86754294?v=4?s=100" width="100px;" alt="tianci li"/><br /><sub><b>tianci li</b></sub></a><br /><a href="#translation-jimcat8" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/555cider"><img src="https://avatars.githubusercontent.com/u/73565447?v=4?s=100" width="100px;" alt="555cider"/><br /><sub><b>555cider</b></sub></a><br /><a href="#translation-555cider" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/m1khal3v"><img src="https://avatars.githubusercontent.com/u/41085561?v=4?s=100" width="100px;" alt="Anton Mikhalev"/><br /><sub><b>Anton Mikhalev</b></sub></a><br /><a href="#translation-m1khal3v" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://64k.nl/"><img src="https://avatars.githubusercontent.com/u/3864423?v=4?s=100" width="100px;" alt="René"/><br /><sub><b>René</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=64knl" title="Code">💻</a> <a href="#translation-64knl" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zxp19821005"><img src="https://avatars.githubusercontent.com/u/4915850?v=4?s=100" width="100px;" alt="Woodenman"/><br /><sub><b>Woodenman</b></sub></a><br /><a href="#platform-zxp19821005" title="Packaging/porting to new platform">📦</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/markusand"><img src="https://avatars.githubusercontent.com/u/12972543?v=4?s=100" width="100px;" alt="Marc Vilella"/><br /><sub><b>Marc Vilella</b></sub></a><br /><a href="#translation-markusand" title="Translation">🌍</a></td>
</tr>
</tbody>
</table> </table>
<!-- markdownlint-restore --> <!-- markdownlint-restore -->

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 889 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

15108
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "antares", "name": "antares",
"productName": "Antares", "productName": "Antares",
"version": "0.7.16-beta.1", "version": "0.5.5",
"description": "A modern, fast and productivity driven SQL client with a focus in UX.", "description": "A modern, fast and productivity driven SQL client with a focus in UX.",
"license": "MIT", "license": "MIT",
"repository": "https://github.com/antares-sql/antares.git", "repository": "https://github.com/antares-sql/antares.git",
@@ -12,18 +12,18 @@
"compile:main": "webpack --mode=production --config webpack.main.config.js", "compile:main": "webpack --mode=production --config webpack.main.config.js",
"compile:workers": "webpack --mode=production --config webpack.workers.config.js", "compile:workers": "webpack --mode=production --config webpack.workers.config.js",
"compile:renderer": "webpack --mode=production --config webpack.renderer.config.js", "compile:renderer": "webpack --mode=production --config webpack.renderer.config.js",
"build": "cross-env NODE_ENV=production npm run compile && electron-builder --publish never", "build": "cross-env NODE_ENV=production npm run compile",
"build:appx": "npm run build -- --win appx", "build:local": "npm run build && electron-builder",
"rebuild:electron": "rimraf ./dist && npm run postinstall && npm run devtools:install", "build:appx": "npm run build:local -- --win appx",
"rebuild:electron": "rimraf ./dist && npm run postinstall",
"release": "standard-version", "release": "standard-version",
"release:beta": "npm run release -- --prerelease beta", "release:pre": "npm run release -- --prerelease alpha",
"devtools:install": "node scripts/devtoolsInstaller", "devtools:install": "node scripts/devtoolsInstaller",
"postinstall": "electron-builder install-app-deps", "postinstall": "electron-builder install-app-deps && npm run devtools:install",
"translation:check": "ts-node ./scripts/translationCheck.ts",
"test:e2e": "npm run compile && npm run test:e2e-dry", "test:e2e": "npm run compile && npm run test:e2e-dry",
"test:e2e-dry": "xvfb-maybe -- playwright test", "test:e2e-dry": "xvfb-maybe -- playwright test",
"lint": "eslint . --ext .js,.ts,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"", "lint": "eslint . --ext .js,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
"lint:fix": "eslint . --ext .js,.ts,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix", "lint:fix": "eslint . --ext .js,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix",
"contributors:add": "all-contributors add", "contributors:add": "all-contributors add",
"contributors:generate": "all-contributors generate" "contributors:generate": "all-contributors generate"
}, },
@@ -65,34 +65,23 @@
"target": [ "target": [
{ {
"target": "deb", "target": "deb",
"arch": [ "arch": "x64"
"x64",
"arm64",
"armv7l"
]
}, },
{ {
"target": "AppImage", "target": "AppImage",
"arch": [ "arch": [
"x64", "x64",
"arm64", "armv7l",
"armv7l" "arm64"
] ]
} }
], ],
"icon": "assets/linux",
"category": "Development" "category": "Development"
}, },
"appImage": { "appImage": {
"license": "./LICENSE", "license": "./LICENSE",
"category": "Development" "category": "Development"
}, },
"nsis": {
"license": "./LICENSE",
"installerIcon": "assets/icon.ico",
"uninstallerIcon": "assets/icon.ico",
"installerHeader": "assets/icon.ico"
},
"portable": { "portable": {
"artifactName": "${productName}-${version}-portable.exe" "artifactName": "${productName}-${version}-portable.exe"
}, },
@@ -122,48 +111,41 @@
"dependencies": { "dependencies": {
"@electron/remote": "~2.0.1", "@electron/remote": "~2.0.1",
"@faker-js/faker": "~6.1.2", "@faker-js/faker": "~6.1.2",
"@mdi/font": "~7.1.96", "@mdi/font": "~6.1.95",
"@turf/helpers": "~6.5.0", "@turf/helpers": "~6.5.0",
"@vueuse/core": "~8.7.5", "@vscode/vscode-languagedetection": "~1.0.21",
"ace-builds": "~1.14.0", "ace-builds": "~1.4.13",
"better-sqlite3": "~8.0.0", "better-sqlite3": "~7.5.0",
"electron-log": "~4.4.1", "electron-log": "~4.4.1",
"electron-store": "~8.1.0", "electron-store": "~8.0.1",
"electron-updater": "~4.6.5", "electron-updater": "~4.6.5",
"electron-window-state": "~5.0.3", "electron-window-state": "~5.0.3",
"encoding": "~0.1.13", "encoding": "~0.1.13",
"floating-vue": "~2.0.0-beta.20",
"json2php": "~0.0.7",
"leaflet": "~1.7.1", "leaflet": "~1.7.1",
"marked": "~4.0.19", "marked": "~4.0.0",
"moment": "~2.29.4", "moment": "~2.29.1",
"mysql2": "~3.5.2", "mysql2": "~2.3.2",
"node-firebird": "~1.1.4", "pg": "~8.7.1",
"pg": "~8.11.1",
"pg-connection-string": "~2.5.0",
"pg-query-stream": "~4.2.3", "pg-query-stream": "~4.2.3",
"pgsql-ast-parser": "~7.2.1", "pgsql-ast-parser": "~7.2.1",
"pinia": "~2.0.28", "pinia": "~2.0.13",
"source-map-support": "~0.5.20", "source-map-support": "~0.5.20",
"spectre.css": "~0.5.9", "spectre.css": "~0.5.9",
"sql-formatter": "~12.2.0", "sql-formatter": "~4.0.2",
"ssh2-promise": "~1.0.2", "ssh2-promise": "~1.0.2",
"v-mask": "~2.3.0", "v-mask": "~2.3.0",
"vue": "~3.2.45", "vue": "~3.2.33",
"vue-i18n": "~9.2.2", "vue-i18n": "~9.1.9",
"vuedraggable": "~4.1.0" "vuedraggable": "~4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/eslint-parser": "~7.15.7", "@babel/eslint-parser": "~7.15.7",
"@babel/preset-env": "~7.15.8", "@babel/preset-env": "~7.15.8",
"@babel/preset-typescript": "~7.16.7", "@babel/preset-typescript": "~7.16.7",
"@playwright/test": "~1.28.1", "@playwright/test": "~1.21.1",
"@types/better-sqlite3": "~7.5.0", "@types/better-sqlite3": "~7.5.0",
"@types/leaflet": "~1.7.9",
"@types/marked": "~4.0.7",
"@types/node": "~17.0.23", "@types/node": "~17.0.23",
"@types/pg": "~8.6.5", "@types/pg": "~8.6.5",
"@types/ssh2": "~1.11.6",
"@typescript-eslint/eslint-plugin": "~5.18.0", "@typescript-eslint/eslint-plugin": "~5.18.0",
"@typescript-eslint/parser": "~5.18.0", "@typescript-eslint/parser": "~5.18.0",
"@vue/compiler-sfc": "~3.2.33", "@vue/compiler-sfc": "~3.2.33",
@@ -172,8 +154,8 @@
"chalk": "~4.1.2", "chalk": "~4.1.2",
"cross-env": "~7.0.2", "cross-env": "~7.0.2",
"css-loader": "~6.5.0", "css-loader": "~6.5.0",
"electron": "~22.0.3", "electron": "~17.4.3",
"electron-builder": "~22.10.3", "electron-builder": "~23.0.3",
"eslint": "~7.32.0", "eslint": "~7.32.0",
"eslint-config-standard": "~16.0.3", "eslint-config-standard": "~16.0.3",
"eslint-plugin-import": "~2.24.2", "eslint-plugin-import": "~2.24.2",
@@ -184,34 +166,26 @@
"html-webpack-plugin": "~5.5.0", "html-webpack-plugin": "~5.5.0",
"mini-css-extract-plugin": "~2.4.5", "mini-css-extract-plugin": "~2.4.5",
"node-loader": "~2.0.0", "node-loader": "~2.0.0",
"playwright": "~1.28.1", "playwright": "~1.21.1",
"playwright-core": "~1.28.1", "playwright-core": "~1.21.1",
"postcss-html": "~1.5.0",
"progress-webpack-plugin": "~1.0.12", "progress-webpack-plugin": "~1.0.12",
"rimraf": "~3.0.2", "rimraf": "~3.0.2",
"sass": "~1.42.1", "sass": "~1.42.1",
"sass-loader": "~12.3.0", "sass-loader": "~12.3.0",
"standard-version": "~9.3.1", "standard-version": "~9.3.1",
"style-loader": "~3.3.1", "style-loader": "~3.3.1",
"stylelint": "~14.9.1", "stylelint": "~13.13.1",
"stylelint-config-recommended-vue": "~1.4.0", "stylelint-config-standard": "~22.0.0",
"stylelint-config-standard": "~26.0.0", "stylelint-scss": "~3.21.0",
"stylelint-scss": "~4.3.0",
"tree-kill": "~1.2.2", "tree-kill": "~1.2.2",
"ts-loader": "~9.2.8", "ts-loader": "~9.2.8",
"ts-node": "~10.9.1",
"typescript": "~4.6.3", "typescript": "~4.6.3",
"unzip-crx-3": "~0.2.0", "unzip-crx-3": "~0.2.0",
"vue-eslint-parser": "~8.3.0", "vue-eslint-parser": "~8.3.0",
"vue-loader": "~16.8.3", "vue-loader": "~16.8.3",
"webpack": "~5.72.0", "webpack": "~5.60.0",
"webpack-cli": "~4.9.1", "webpack-cli": "~4.9.1",
"webpack-dev-server": "~4.11.1", "webpack-dev-server": "~4.4.0",
"xvfb-maybe": "~0.2.1" "xvfb-maybe": "~0.2.1"
},
"overrides": {
"ssh2-promise": {
"ssh2": "github:Fabio286/ssh2"
}
} }
} }

View File

@@ -59,7 +59,7 @@ async function restartElectron () {
console.error(chalk.red(data.toString())); console.error(chalk.red(data.toString()));
}); });
electronProcess.on('exit', () => { electronProcess.on('exit', (code, signal) => {
if (!manualRestart) process.exit(0); if (!manualRestart) process.exit(0);
}); });
} }
@@ -114,6 +114,7 @@ function startRenderer (callback) {
}); });
const server = new WebpackDevServer(compiler, { const server = new WebpackDevServer(compiler, {
hot: true,
port: 9080, port: 9080,
client: { client: {
overlay: true, overlay: true,

View File

@@ -1,5 +1,4 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-check
// @ts-nocheck
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const https = require('https'); const https = require('https');

View File

@@ -1,45 +0,0 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { localesNames } from '../src/renderer/i18n/supported-locales';
import { enUS } from '../src/renderer/i18n/en-US';
const locale = process.argv[2];
let fullCount = 0;
let checkCount = 0;
if (!locale) {
console.log('Please specify locale code as first argument.');
process.exit();
}
if (!Object.keys(localesNames).includes(locale)) {
console.log(`Translation ${locale} not fount in supported locales.`);
process.exit();
}
console.log('Checking missing translations for:', locale);
const i18nFile = require(`../src/renderer/i18n/${locale}`)[locale.replace('-', '')];
for (const group in enUS) {
// @ts-ignore
fullCount += Object.keys(enUS[group]).length;
if (!Object.keys(i18nFile).includes(group)) {
console.log(`Group "\u001b[31m${group}\u001b[0m" missing!`);
continue;
}
// @ts-ignore
for (const term in enUS[group]) {
if (!Object.keys(i18nFile[group]).includes(term))
console.log(`Translation "\u001b[33m${group}.${term}\u001b[0m" missing!`);
// @ts-ignore
else if (i18nFile[group][term] === enUS[group][term]) {
console.log(`Term "\u001b[36m${group}.${term}\u001b[0m" not translated!`);
checkCount++;
}
else
checkCount++;
}
}
console.log(checkCount, 'of', fullCount, 'strings are present in', locale, `(\u001b[32m${(checkCount*100/fullCount).toFixed(1)}%\u001b[0m)`);

View File

@@ -4,7 +4,7 @@ summary: Open source SQL client made to be simple and complete.
description: | description: |
Antares is an SQL client that aims to become an useful and complete tool, especially for developers. Antares is an SQL client that aims to become an useful and complete tool, especially for developers.
The target is to support as many databases as possible, and all major operating systems, including the ARM versions. The target is to support as many databases as possible, and all major operating systems, including the ARM versions.
base: core22 base: core20
grade: stable grade: stable
confinement: strict confinement: strict
@@ -31,10 +31,10 @@ parts:
fi fi
# Get the latest releases json # Get the latest releases json
echo "Get GitHub releases..." echo "Get GitHub releases..."
wget --quiet https://api.github.com/repos/fabio286/antares/releases -O releases.json wget --quiet https://api.github.com/repos/fabio286/antares/releases/latest -O releases.json
# Get the version from the tag_name and the download URL. # Get the version from the tag_name and the download URL.
VERSION=$(jq . releases.json | grep tag_name | head -1 | cut -d'"' -f4 | sed s'/release-//') VERSION=$(jq . releases.json | grep tag_name | cut -d'"' -f4 | sed s'/release-//')
DEB_URL=$(cat releases.json | jq -r ".[0].assets[] | select(.name | test(\"${FILTER}\")) | .browser_download_url") DEB_URL=$(cat releases.json | jq -r ".assets[] | select(.name | test(\"${FILTER}\")) | .browser_download_url")
DEB=$(basename "${DEB_URL}") DEB=$(basename "${DEB_URL}")
echo "Downloading ${DEB_URL}..." echo "Downloading ${DEB_URL}..."
wget --quiet "${DEB_URL}" -O "${SNAPCRAFT_PART_INSTALL}/${DEB}" wget --quiet "${DEB_URL}" -O "${SNAPCRAFT_PART_INSTALL}/${DEB}"
@@ -79,7 +79,7 @@ parts:
- libdb5.3 - libdb5.3
- libdbus-1-3 - libdbus-1-3
- libexpat1 - libexpat1
- libffi8 - libffi7
- libgcc-s1 - libgcc-s1
- libgcrypt20 - libgcrypt20
- libglib2.0-0 - libglib2.0-0
@@ -87,9 +87,9 @@ parts:
- libgnutls30 - libgnutls30
- libgpg-error0 - libgpg-error0
- libgssapi-krb5-2 - libgssapi-krb5-2
- libhogweed6 - libhogweed5
- libidn2-0 - libidn2-0
- libjson-c5 - libjson-c4
- libk5crypto3 - libk5crypto3
- libkeyutils1 - libkeyutils1
- libkrb5-3 - libkrb5-3
@@ -97,12 +97,12 @@ parts:
- liblz4-1 - liblz4-1
- liblzma5 - liblzma5
- libmount1 - libmount1
- libnettle8 - libnettle7
- libp11-kit0 - libp11-kit0
- libpcre2-8-0 - libpcre2-8-0
- libselinux1 - libselinux1
- libsqlite3-0 - libsqlite3-0
- libssl3 - libssl1.1
- libstdc++6 - libstdc++6
- libsystemd0 - libsystemd0
- libtasn1-6 - libtasn1-6
@@ -119,10 +119,10 @@ parts:
cleanup: cleanup:
after: [antares] after: [antares]
plugin: nil plugin: nil
build-snaps: [gnome-42-2204] build-snaps: [gnome-3-38-2004]
override-prime: | override-prime: |
set -eux set -eux
cd /snap/gnome-42-2204/current cd /snap/gnome-3-38-2004/current
find . -type f,l -exec rm -f $SNAPCRAFT_PRIME/{} \; find . -type f,l -exec rm -f $SNAPCRAFT_PRIME/{} \;
mdns-lookup: mdns-lookup:
@@ -136,7 +136,7 @@ parts:
- libnss-mdns - libnss-mdns
override-prime: | override-prime: |
set -eux set -eux
sed -Ee 's/^\s*hosts:(\s+)files/hosts:\1files mdns4_minimal \[NOTFOUND=return\]/' /snap/core22/current/etc/nsswitch.conf > $SNAPCRAFT_STAGE/etc/nsswitch.conf sed -Ee 's/^\s*hosts:(\s+)files/hosts:\1files mdns4_minimal \[NOTFOUND=return\]/' /snap/core20/current/etc/nsswitch.conf > $SNAPCRAFT_STAGE/etc/nsswitch.conf
snapcraftctl prime snapcraftctl prime
prime: prime:
- lib/$SNAPCRAFT_ARCH_TRIPLET/libnss_mdns4_minimal* - lib/$SNAPCRAFT_ARCH_TRIPLET/libnss_mdns4_minimal*
@@ -146,7 +146,10 @@ apps:
antares: antares:
command: opt/Antares/antares --no-sandbox command: opt/Antares/antares --no-sandbox
desktop: usr/share/applications/antares.desktop desktop: usr/share/applications/antares.desktop
extensions: [gnome] extensions: [gnome-3-38]
environment:
# Fallback to XWayland if running in a Wayland session.
DISABLE_WAYLAND: 1
plugs: plugs:
- browser-support - browser-support
- cups-control - cups-control

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export default class { export default class {
static get _methods () { static get _methods () {
return [ return [
@@ -140,7 +139,7 @@ export default class {
{ name: 'arrayElement', group: 'random', types: ['string'] }, { name: 'arrayElement', group: 'random', types: ['string'] },
{ name: 'arrayElements', group: 'random', types: ['string'] }, { name: 'arrayElements', group: 'random', types: ['string'] },
{ name: 'objectElement', group: 'random', types: ['string'] }, { name: 'objectElement', group: 'random', types: ['string'] },
{ name: 'uuid', group: 'random', types: ['string', 'uuid'] }, { name: 'uuid', group: 'random', types: ['string'] },
{ name: 'boolean', group: 'random', types: ['string'] }, { name: 'boolean', group: 'random', types: ['string'] },
{ name: 'word', group: 'random', types: ['string'] }, { name: 'word', group: 'random', types: ['string'] },
{ name: 'words', group: 'random', types: ['string'] }, { name: 'words', group: 'random', types: ['string'] },
@@ -181,7 +180,7 @@ export default class {
acc[curr.group] = new Set(curr.types); acc[curr.group] = new Set(curr.types);
return acc; return acc;
}, {} as any); }, {});
const groupsArr = []; const groupsArr = [];
@@ -199,12 +198,12 @@ export default class {
}); });
} }
static getGroupsByType (type: string) { static getGroupsByType (type) {
if (!type) return []; if (!type) return [];
return this.getGroups().filter(group => group.types.includes(type)); return this.getGroups().filter(group => group.types.includes(type));
} }
static getMethods ({ type, group }: {type: string; group: string}) { static getMethods ({ type, group }) {
return this._methods.filter(method => method.group === group && method.types.includes(type)).sort((a, b) => { return this._methods.filter(method => method.group === group && method.types.includes(type)).sort((a, b) => {
if (a.name < b.name) if (a.name < b.name)
return -1; return -1;

View File

@@ -1,14 +1,8 @@
import { Customizations } from '../interfaces/customizations'; module.exports = {
// Everything OFF
export const defaults: Customizations = {
// Defaults // Defaults
defaultPort: null, defaultPort: null,
defaultUser: null, defaultUser: null,
defaultDatabase: null, defaultDatabase: null,
dataTypes: [],
indexTypes: [],
foreignActions: [],
// Core // Core
database: false, database: false,
collations: false, collations: false,
@@ -31,12 +25,10 @@ export const defaults: Customizations = {
routines: false, routines: false,
functions: false, functions: false,
schedulers: false, schedulers: false,
// Misc // Settings
elementsWrapper: '', elementsWrapper: '',
stringsWrapper: '"', stringsWrapper: '"',
tableAdd: false, tableAdd: false,
tableTruncateDisableFKCheck: false,
tableDdl: false,
viewAdd: false, viewAdd: false,
triggerAdd: false, triggerAdd: false,
triggerFunctionAdd: false, triggerFunctionAdd: false,
@@ -50,9 +42,9 @@ export const defaults: Customizations = {
exportByChunks: false, exportByChunks: false,
schemaImport: false, schemaImport: false,
tableSettings: false, tableSettings: false,
tableOptions: false,
tableArray: false, tableArray: false,
tableRealCount: false, tableRealCount: false,
tableDuplicate: false,
viewSettings: false, viewSettings: false,
triggerSettings: false, triggerSettings: false,
triggerFunctionSettings: false, triggerFunctionSettings: false,
@@ -76,25 +68,24 @@ export const defaults: Customizations = {
viewUpdateOption: false, viewUpdateOption: false,
procedureDeterministic: false, procedureDeterministic: false,
procedureDataAccess: false, procedureDataAccess: false,
procedureSql: null, procedureSql: false,
procedureContext: false, procedureContext: false,
procedureContextValues: [],
procedureLanguage: false, procedureLanguage: false,
functionDeterministic: false, functionDeterministic: false,
functionDataAccess: false, functionDataAccess: false,
functionSql: null, functionSql: false,
functionContext: false, functionContext: false,
functionLanguage: false, functionLanguage: false,
triggerSql: null, triggerSql: false,
triggerStatementInCreation: false, triggerStatementInCreation: false,
triggerMultipleEvents: false, triggerMultipleEvents: false,
triggerTableInName: false, triggerTableInName: false,
triggerUpdateColumns: false, triggerUpdateColumns: false,
triggerOnlyRename: false, triggerOnlyRename: false,
triggerEnableDisable: false, triggerEnableDisable: false,
triggerFunctionSql: null, triggerFunctionSql: false,
triggerFunctionlanguages: null, triggerFunctionlanguages: false,
parametersLength: false, parametersLength: false,
languages: null, languages: false,
readOnlyMode: false readOnlyMode: false
}; };

View File

@@ -1,63 +0,0 @@
import { Customizations } from '../interfaces/customizations';
import { defaults } from './defaults';
import firebirdTypes from '../data-types/firebird';
export const customizations: Customizations = {
...defaults,
// Defaults
defaultPort: 3050,
defaultUser: 'SYSDBA',
defaultDatabase: null,
dataTypes: firebirdTypes,
indexTypes: [
'PRIMARY',
// 'CHECK',
'UNIQUE'
],
foreignActions: [
'RESTRICT',
'NO ACTION',
'CASCADE',
'SET NULL',
'SET DEFAULT'
],
// Core
database: true,
collations: false,
engines: false,
connectionSchema: false,
sslConnection: false,
sshConnection: false,
fileConnection: false,
cancelQueries: false,
// Tools
processesList: false,
usersManagement: false,
variables: false,
// Structure
schemas: false,
tables: true,
views: true,
triggers: true,
routines: true,
functions: false,
// Settings
elementsWrapper: '"',
stringsWrapper: '\'',
tableAdd: true,
tableSettings: true,
tableRealCount: true,
viewAdd: true,
viewSettings: true,
triggerAdd: true,
triggerMultipleEvents: true,
triggerSql: 'BEGIN\r\n\r\nEND',
routineAdd: true,
procedureContext: true,
procedureContextValues: ['IN', 'OUT'],
procedureSql: 'BEGIN\r\n\r\nEND',
parametersLength: true,
indexes: true,
foreigns: true,
nullable: true
};

View File

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

View File

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

View File

@@ -1,26 +1,11 @@
import { Customizations } from '../interfaces/customizations'; const defaults = require('./defaults');
import { defaults } from './defaults';
import mysqlTypes from '../data-types/mysql';
export const customizations: Customizations = { module.exports = {
...defaults, ...defaults,
// Defaults // Defaults
defaultPort: 3306, defaultPort: 3306,
defaultUser: 'root', defaultUser: 'root',
defaultDatabase: null, defaultDatabase: null,
dataTypes: mysqlTypes,
indexTypes: [
'PRIMARY',
'INDEX',
'UNIQUE',
'FULLTEXT'
],
foreignActions: [
'RESTRICT',
'CASCADE',
'SET NULL',
'NO ACTION'
],
// Core // Core
connectionSchema: true, connectionSchema: true,
collations: true, collations: true,
@@ -39,12 +24,9 @@ export const customizations: Customizations = {
functions: true, functions: true,
schedulers: true, schedulers: true,
// Settings // Settings
elementsWrapper: '`', elementsWrapper: '',
stringsWrapper: '"', stringsWrapper: '"',
tableAdd: true, tableAdd: true,
tableTruncateDisableFKCheck: true,
tableDuplicate: true,
tableDdl: true,
viewAdd: true, viewAdd: true,
triggerAdd: true, triggerAdd: true,
routineAdd: true, routineAdd: true,
@@ -67,6 +49,7 @@ export const customizations: Customizations = {
unsigned: true, unsigned: true,
nullable: true, nullable: true,
zerofill: true, zerofill: true,
tableOptions: true,
autoIncrement: true, autoIncrement: true,
comment: true, comment: true,
collation: true, collation: true,
@@ -79,7 +62,6 @@ export const customizations: Customizations = {
procedureDataAccess: true, procedureDataAccess: true,
procedureSql: 'BEGIN\r\n\r\nEND', procedureSql: 'BEGIN\r\n\r\nEND',
procedureContext: true, procedureContext: true,
procedureContextValues: ['IN', 'OUT', 'INOUT'],
triggerSql: 'BEGIN\r\n\r\nEND', triggerSql: 'BEGIN\r\n\r\nEND',
functionDeterministic: true, functionDeterministic: true,
functionDataAccess: true, functionDataAccess: true,

View File

@@ -1,25 +1,11 @@
import { Customizations } from '../interfaces/customizations'; const defaults = require('./defaults');
import { defaults } from './defaults';
import postgresqlTypes from '../data-types/postgresql';
export const customizations: Customizations = { module.exports = {
...defaults, ...defaults,
// Defaults // Defaults
defaultPort: 5432, defaultPort: 5432,
defaultUser: 'postgres', defaultUser: 'postgres',
defaultDatabase: 'postgres', defaultDatabase: 'postgres',
dataTypes: postgresqlTypes,
indexTypes: [
'PRIMARY',
'INDEX',
'UNIQUE'
],
foreignActions: [
'RESTRICT',
'CASCADE',
'SET NULL',
'NO ACTION'
],
// Core // Core
database: true, database: true,
sslConnection: true, sslConnection: true,
@@ -35,12 +21,10 @@ export const customizations: Customizations = {
triggerFunctions: true, triggerFunctions: true,
routines: true, routines: true,
functions: true, functions: true,
// Misc // Settings
elementsWrapper: '"', elementsWrapper: '"',
stringsWrapper: '\'', stringsWrapper: '\'',
tableAdd: true, tableAdd: true,
tableDuplicate: true,
tableDdl: true,
viewAdd: true, viewAdd: true,
triggerAdd: true, triggerAdd: true,
triggerFunctionAdd: true, triggerFunctionAdd: true,
@@ -62,7 +46,6 @@ export const customizations: Customizations = {
tableArray: true, tableArray: true,
procedureSql: '$procedure$\r\n\r\n$procedure$', procedureSql: '$procedure$\r\n\r\n$procedure$',
procedureContext: true, procedureContext: true,
procedureContextValues: ['IN', 'OUT', 'INOUT'],
procedureLanguage: true, procedureLanguage: true,
functionSql: '$function$\r\n\r\n$function$', functionSql: '$function$\r\n\r\n$function$',
triggerFunctionSql: '$function$\r\nBEGIN\r\n\r\nEND\r\n$function$', triggerFunctionSql: '$function$\r\nBEGIN\r\n\r\nEND\r\n$function$',

View File

@@ -1,21 +1,4 @@
import { Customizations } from '../interfaces/customizations'; module.exports = {
import { defaults } from './defaults';
import sqliteTypes from '../data-types/sqlite';
export const customizations: Customizations = {
...defaults,
dataTypes: sqliteTypes,
indexTypes: [
'PRIMARY',
'INDEX',
'UNIQUE'
],
foreignActions: [
'RESTRICT',
'CASCADE',
'SET NULL',
'NO ACTION'
],
// Core // Core
fileConnection: true, fileConnection: true,
// Structure // Structure
@@ -27,7 +10,6 @@ export const customizations: Customizations = {
elementsWrapper: '"', elementsWrapper: '"',
stringsWrapper: '\'', stringsWrapper: '\'',
tableAdd: true, tableAdd: true,
tableDuplicate: true,
viewAdd: true, viewAdd: true,
triggerAdd: true, triggerAdd: true,
schemaEdit: false, schemaEdit: false,

View File

@@ -1,136 +0,0 @@
import { TypesGroup } from 'common/interfaces/antares';
export default [
{
group: 'integer',
types: [
{
name: 'SMALLINT',
length: false,
collation: false,
unsigned: true,
zerofill: true
},
{
name: 'INTEGER',
length: false,
collation: false,
unsigned: true,
zerofill: true
},
{
name: 'BIGINT',
length: false,
collation: false,
unsigned: true,
zerofill: true
}
]
},
{
group: 'float',
types: [
{
name: 'DECIMAL',
length: true,
scale: true,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'NUMERIC',
length: true,
scale: true,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'FLOAT',
length: false,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'DOUBLE PRECISION',
length: false,
collation: false,
unsigned: false,
zerofill: false
}
]
},
{
group: 'string',
types: [
{
name: 'CHAR',
length: true,
collation: true,
unsigned: false,
zerofill: false
},
{
name: 'VARCHAR',
length: true,
collation: true,
unsigned: false,
zerofill: false
},
{
name: 'BLOB-TEXT',
length: false,
collation: true,
unsigned: false,
zerofill: false
}
]
},
{
group: 'binary',
types: [
{
name: 'BLOB',
length: false,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'CHAR-BINARY',
length: false,
collation: false,
unsigned: false,
zerofill: false
}
]
},
{
group: 'time',
types: [
{
name: 'DATE',
length: false,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'TIME',
length: true,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'TIMESTAMP',
length: false,
collation: false,
unsigned: false,
zerofill: false
}
]
}
] as TypesGroup[];

View File

@@ -1,6 +1,4 @@
import { TypesGroup } from 'common/interfaces/antares'; module.exports = [
export default [
{ {
group: 'integer', group: 'integer',
types: [ types: [
@@ -54,7 +52,6 @@ export default [
{ {
name: 'FLOAT', name: 'FLOAT',
length: true, length: true,
scale: true,
collation: false, collation: false,
unsigned: false, unsigned: false,
zerofill: false zerofill: false
@@ -309,4 +306,4 @@ export default [
} }
] ]
} }
] as TypesGroup[]; ];

View File

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

View File

@@ -1,25 +1,16 @@
import { TypesGroup } from 'common/interfaces/antares'; module.exports = [
export default [
{ {
group: 'integer', group: 'integer',
types: [ types: [
{ {
name: 'INT', name: 'INT',
length: 10,
collation: false,
unsigned: true,
zerofill: true
},
{
name: 'INTEGER',
length: true, length: true,
collation: false, collation: false,
unsigned: true, unsigned: true,
zerofill: true zerofill: true
}, },
{ {
name: 'INTEGER UNSIGNED', name: 'INTEGER',
length: true, length: true,
collation: false, collation: false,
unsigned: true, unsigned: true,
@@ -143,4 +134,4 @@ export default [
} }
] ]
} }
] as TypesGroup[]; ];

View File

@@ -10,8 +10,7 @@ export const LONG_TEXT = [
'MEDIUMTEXT', 'MEDIUMTEXT',
'LONGTEXT', 'LONGTEXT',
'JSON', 'JSON',
'VARBINARY', 'VARBINARY'
'BLOB-TEXT'
]; ];
export const ARRAY = [ export const ARRAY = [
@@ -30,14 +29,14 @@ export const NUMBER = [
'SMALLINT', 'SMALLINT',
'MEDIUMINT', 'MEDIUMINT',
'BIGINT', 'BIGINT',
'DECIMAL',
'NUMERIC', 'NUMERIC',
'INTEGER', 'INTEGER',
'SMALLSERIAL', 'SMALLSERIAL',
'SERIAL', 'SERIAL',
'BIGSERIAL', 'BIGSERIAL',
'OID', 'OID',
'XID', 'XID'
'INT64'
]; ];
export const FLOAT = [ export const FLOAT = [
@@ -49,12 +48,6 @@ export const FLOAT = [
'MONEY' 'MONEY'
]; ];
export const IS_BIGINT = [
'BIGINT',
'BIGSERIAL',
'DOUBLE PRECISION'
];
export const BOOLEAN = [ export const BOOLEAN = [
'BOOL', 'BOOL',
'BOOLEAN' 'BOOLEAN'
@@ -85,9 +78,7 @@ export const BLOB = [
'TINYBLOB', 'TINYBLOB',
'MEDIUMBLOB', 'MEDIUMBLOB',
'LONGBLOB', 'LONGBLOB',
'LONG_BLOB', 'BYTEA'
'BYTEA',
'CHAR-BINARY'
]; ];
export const BIT = [ export const BIT = [
@@ -95,14 +86,6 @@ export const BIT = [
'BIT VARYING' 'BIT VARYING'
]; ];
export const BINARY = [
'BINARY'
];
export const UUID = [
'UUID'
];
export const SPATIAL = [ export const SPATIAL = [
'POINT', 'POINT',
'LINESTRING', 'LINESTRING',

View File

@@ -0,0 +1,6 @@
module.exports = [
'PRIMARY',
'INDEX',
'UNIQUE',
'FULLTEXT'
];

View File

@@ -0,0 +1,5 @@
module.exports = [
'PRIMARY',
'INDEX',
'UNIQUE'
];

View File

@@ -0,0 +1,5 @@
module.exports = [
'PRIMARY',
'INDEX',
'UNIQUE'
];

View File

@@ -8,25 +8,17 @@ import SSHConfig from 'ssh2-promise/lib/sshConfig';
import { MySQLClient } from '../../main/libs/clients/MySQLClient'; import { MySQLClient } from '../../main/libs/clients/MySQLClient';
import { PostgreSQLClient } from '../../main/libs/clients/PostgreSQLClient'; import { PostgreSQLClient } from '../../main/libs/clients/PostgreSQLClient';
import { SQLiteClient } from '../../main/libs/clients/SQLiteClient'; import { SQLiteClient } from '../../main/libs/clients/SQLiteClient';
import { FirebirdSQLClient } from 'src/main/libs/clients/FirebirdSQLClient';
export type Client = MySQLClient | PostgreSQLClient | SQLiteClient | FirebirdSQLClient export type Client = MySQLClient | PostgreSQLClient | SQLiteClient
export type ClientCode = 'mysql' | 'maria' | 'pg' | 'sqlite' | 'firebird' export type ClientCode = 'mysql' | 'maria' | 'pg' | 'sqlite'
export type Exporter = MysqlExporter | PostgreSQLExporter export type Exporter = MysqlExporter | PostgreSQLExporter
export type Importer = MySQLImporter | PostgreSQLImporter 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 * Pasameters needed to create a new Antares connection to a database
*/ */
export interface ClientParams { export interface ClientParams {
client: ClientCode; client: ClientCode;
uid?: string;
params: params:
mysql.ConnectionOptions & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean} mysql.ConnectionOptions & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean}
| pg.ClientConfig & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean} | pg.ClientConfig & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean}
@@ -75,32 +67,12 @@ export interface TypeInformations {
zerofill: boolean; zerofill: boolean;
} }
export interface TypesGroup {
group: string;
types: TypeInformations[];
}
// Tables // Tables
export interface TableInfos {
name: string;
type: string;
rows: number;
engine: string;
comment: string;
size: number | false;
collation: string;
autoIncrement?: boolean;
}
export type TableOptions = Partial<TableInfos>;
export interface TableField { export interface TableField {
// eslint-disable-next-line camelcase
_antares_id?: string;
name: string; name: string;
key: string;
type: string; type: string;
schema: string; schema: string;
table?: string;
numPrecision?: number; numPrecision?: number;
numLength?: number; numLength?: number;
datePrecision?: number; datePrecision?: number;
@@ -110,8 +82,7 @@ export interface TableField {
unsigned?: boolean; unsigned?: boolean;
zerofill?: boolean; zerofill?: boolean;
order?: number; order?: number;
default?: string; default?: number | string;
defaultType?: string;
enumValues?: string; enumValues?: string;
charset?: string; charset?: string;
collation?: string; collation?: string;
@@ -121,16 +92,9 @@ export interface TableField {
comment?: string; comment?: string;
after?: string; after?: string;
orgName?: string; orgName?: string;
length?: number | false;
alias: string;
tableAlias: string;
orgTable: string;
key?: 'pri' | 'uni' | '';
} }
export interface TableIndex { export interface TableIndex {
// eslint-disable-next-line camelcase
_antares_id?: string;
name: string; name: string;
fields: string[]; fields: string[];
type: string; type: string;
@@ -143,8 +107,6 @@ export interface TableIndex {
} }
export interface TableForeign { export interface TableForeign {
// eslint-disable-next-line camelcase
_antares_id?: string;
constraintName: string; constraintName: string;
refSchema: string; refSchema: string;
table: string; table: string;
@@ -156,6 +118,15 @@ export interface TableForeign {
oldName?: string; oldName?: string;
} }
export interface TableOptions {
name: string;
type?: 'table' | 'view';
engine?: string;
comment?: string;
collation?: string;
autoIncrement?: number;
}
export interface CreateTableParams { export interface CreateTableParams {
/** Connection UID */ /** Connection UID */
uid?: string; uid?: string;
@@ -194,7 +165,6 @@ export interface AlterTableParams {
} }
// Views // Views
export type ViewInfos = TableInfos
export interface CreateViewParams { export interface CreateViewParams {
schema: string; schema: string;
name: string; name: string;
@@ -210,19 +180,6 @@ export interface AlterViewParams extends CreateViewParams {
} }
// Triggers // 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 { export interface CreateTriggerParams {
definer?: string; definer?: string;
schema: string; schema: string;
@@ -238,38 +195,13 @@ export interface AlterTriggerParams extends CreateTriggerParams {
} }
// Routines & Functions // Routines & Functions
export interface FunctionParam { export interface FunctionParam {
// eslint-disable-next-line camelcase
_antares_id: string;
context: string; context: string;
name: string; name: string;
type: string; type: string;
length: number; 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 { export interface CreateRoutineParams {
name: string; name: string;
parameters?: FunctionParam[]; parameters?: FunctionParam[];
@@ -307,7 +239,7 @@ export interface AlterFunctionParams extends CreateFunctionParams {
} }
// Events // Events
export interface EventInfos { export interface CreateEventParams {
definer?: string; definer?: string;
schema: string; schema: string;
name: string; name: string;
@@ -316,39 +248,16 @@ export interface EventInfos {
starts: string; starts: string;
ends: string; ends: string;
at: string; at: string;
preserve: boolean; preserve: string;
state: string; state: string;
comment: string; comment: string;
enabled?: boolean;
sql: string; sql: string;
} }
export type CreateEventParams = EventInfos;
export interface AlterEventParams extends CreateEventParams { export interface AlterEventParams extends CreateEventParams {
oldName?: string; 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 // Query
export interface QueryBuilderObject { export interface QueryBuilderObject {
schema: string; schema: string;
@@ -376,10 +285,17 @@ export interface QueryParams {
tabUid?: string; tabUid?: string;
} }
/** export interface QueryField {
* @deprecated Use TableFIeld name: string;
*/ alias: string;
export type QueryField = TableField orgName: string;
schema: string;
table: string;
tableAlias: string;
orgTable: string;
type: string;
length: number;
}
export interface QueryForeign { export interface QueryForeign {
schema: string; schema: string;

View File

@@ -1,99 +0,0 @@
import { TypesGroup } from './antares';
export interface Customizations {
// Defaults
defaultPort?: number;
defaultUser?: string;
defaultDatabase?: string;
dataTypes?: TypesGroup[];
indexTypes?: string[];
foreignActions?: 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;
// Misc
elementsWrapper: string;
stringsWrapper: string;
tableAdd?: boolean;
tableSettings?: boolean;
tableDuplicate?: boolean;
tableArray?: boolean;
tableRealCount?: boolean;
tableTruncateDisableFKCheck?: boolean;
tableDdl?: 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;
procedureContextValues?: string[];
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

@@ -7,13 +7,13 @@ export interface TableParams {
export interface ExportOptions { export interface ExportOptions {
schema: string; schema: string;
tables: { includes: {
table: string; functions: boolean;
includeStructure: boolean; views: boolean;
includeContent: boolean; triggers: boolean;
includeDropStatement: boolean; routines: boolean;
}[]; schedulers: boolean;
includes: {[key: string]: boolean}; };
outputFormat: 'sql' | 'sql.zip'; outputFormat: 'sql' | 'sql.zip';
outputFile: string; outputFile: string;
sqlInsertAfter: number; sqlInsertAfter: number;

View File

@@ -1,34 +1,5 @@
import { UsableLocale } from '@faker-js/faker'; 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 { export interface InsertRowsParams {
uid: string; uid: string;
schema: string; schema: string;

View File

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

View File

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

View File

@@ -1,31 +0,0 @@
import * as crypto from 'crypto';
const algorithm = 'aes-256-gcm';
function encrypt (text: string, password: string) {
const iv = crypto.randomBytes(16);
const key = crypto.scryptSync(password, 'antares', 32);
const cipher = crypto.createCipheriv(algorithm, key, iv);
const encrypted = Buffer.concat([cipher.update(text), cipher.final()]);
const authTag = cipher.getAuthTag();
return {
iv: iv.toString('hex'),
authTag: authTag.toString('hex'),
content: encrypted.toString('hex')
};
}
function decrypt (hash: { iv: string; content: string; authTag: string }, password: string) {
const key = crypto.scryptSync(password, 'antares', 32);
const decipher = crypto.createDecipheriv(algorithm, key, Buffer.from(hash.iv, 'hex'));
decipher.setAuthTag(Buffer.from(hash.authTag, 'hex'));
const decrpyted = decipher.update(hash.content, 'hex', 'utf8') + decipher.final('utf8');
return decrpyted;
}
export {
encrypt,
decrypt
};

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'; if (bytes === 0) return '0 Bytes';
const k = 1024; const k = 1024;

View File

@@ -0,0 +1,10 @@
/**
*
* @param {any[]} array
* @returns {number}
*/
export function getArrayDepth (array) {
return Array.isArray(array)
? 1 + Math.max(0, ...array.map(getArrayDepth))
: 0;
}

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 = { const lookup = {
0: '0000', 0: '0000',
1: '0001', 1: '0001',
@@ -21,11 +23,15 @@ const lookup = {
D: '1101', D: '1101',
E: '1110', E: '1110',
F: '1111' F: '1111'
} as const; };
export type HexChar = keyof typeof lookup /**
* Converts hexadecimal string to binary string
export default function hexToBinary (hex: HexChar[]) { *
* @param {string} hex Hexadecimal string
* @returns {string} Binary string
*/
export default function hexToBinary (hex) {
let binary = ''; let binary = '';
for (let i = 0; i < hex.length; i++) for (let i = 0; i < hex.length; i++)
binary += lookup[hex[i]]; binary += lookup[hex[i]];

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 || !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 switch (hex.substring(0, 4)) { // 2 bytes
case '424D': case '424D':
return { ext: 'bmp', mime: 'image/bmp' }; return { ext: 'bmp', mime: 'image/bmp' };
@@ -22,7 +23,7 @@ export function mimeFromHex (hex: string) {
case '425A68': case '425A68':
return { ext: 'bz2', mime: 'application/x-bzip2' }; return { ext: 'bz2', mime: 'application/x-bzip2' };
default: default:
switch (hex) { // 4 bites switch (hex) { // 4 bytes
case '89504E47': case '89504E47':
return { ext: 'png', mime: 'image/png' }; return { ext: 'png', mime: 'image/png' };
case '47494638': case '47494638':

View File

@@ -0,0 +1,20 @@
/* eslint-disable no-useless-escape */
// eslint-disable-next-line no-control-regex
const pattern = /[\0\x08\x09\x1a\n\r"'\\\%]/gm;
const regex = new RegExp(pattern);
/**
* 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', '\\\'', '\\\"', '\\\\', '\\\\\\\\', '\%'];
return r[m.indexOf(char)] || char;
});
}
export { sqlEscaper };

View File

@@ -1,198 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable no-useless-escape */
import * as moment from 'moment';
import { lineString, point, polygon } from '@turf/helpers';
import customizations from '../customizations';
import { ClientCode } from '../interfaces/antares';
import { BLOB, BIT, DATE, DATETIME, FLOAT, SPATIAL, IS_MULTI_SPATIAL, NUMBER, TEXT_SEARCH } from 'common/fieldTypes';
import hexToBinary, { HexChar } from './hexToBinary';
import { getArrayDepth } from './getArrayDepth';
/**
* Escapes a string fo SQL use
*
* @param { String } string
* @returns { String } Escaped string
*/
export const sqlEscaper = (string: string): string => {
// eslint-disable-next-line no-control-regex
const pattern = /[\0\x08\x09\x1a\n\r"'\\\%]/gm;
const regex = new RegExp(pattern);
return string.replace(regex, char => {
const m = ['\\0', '\\x08', '\\x09', '\\x1a', '\\n', '\\r', '\'', '\"', '\\', '\\\\', '%'];
const r = ['\\\\0', '\\\\b', '\\\\t', '\\\\z', '\\\\n', '\\\\r', '\\\'', '\\\"', '\\\\', '\\\\\\\\', '\%'];
return r[m.indexOf(char)] || char;
});
};
export const objectToGeoJSON = (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]);
};
export const escapeAndQuote = (val: string, client: ClientCode) => {
const { stringsWrapper: sw } = customizations[client];
// 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 `${sw}${val}${sw}`;
if (chunkIndex < val.length)
return `${sw}${escapedVal + val.slice(chunkIndex)}${sw}`;
return `${sw}${escapedVal}${sw}`;
};
export const valueToSqlString = (args: {
val: any;
client: ClientCode;
field: {type: string; datePrecision?: number; precision?: number | false; isArray?: boolean};
}): string => {
let parsedValue;
const { val, client, field } = args;
const { stringsWrapper: sw } = customizations[client];
if (val === null)
parsedValue = 'NULL';
else if (DATE.includes(field.type)) {
parsedValue = moment(val).isValid()
? escapeAndQuote(moment(val).format('YYYY-MM-DD'), client)
: val;
}
else if (DATETIME.includes(field.type)) {
let datePrecision = '';
for (let i = 0; i < field.datePrecision; i++)
datePrecision += i === 0 ? '.S' : 'S';
parsedValue = moment(val).isValid()
? escapeAndQuote(moment(val).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`), client)
: escapeAndQuote(val, client);
}
else if ('isArray' in field && field.isArray) {
let localVal;
if (Array.isArray(val))
localVal = JSON.stringify(val).replaceAll('[', '{').replaceAll(']', '}');
else
localVal = typeof val === 'string' ? val.replaceAll('[', '{').replaceAll(']', '}') : '';
parsedValue = `'${localVal}'`;
}
else if (TEXT_SEARCH.includes(field.type))
parsedValue = `'${val.replaceAll('\'', '\'\'')}'`;
else if (BIT.includes(field.type))
parsedValue = `b'${hexToBinary(Buffer.from(new Uint8Array(Object.values(val))).toString('hex') as undefined as HexChar[])}'`;
else if (BLOB.includes(field.type)) {
let buffer: Buffer;
if (val instanceof Uint8Array)
buffer = Buffer.from(val);
else
buffer = val;
if (['mysql', 'maria'].includes(client))
parsedValue = `X'${buffer.toString('hex').toUpperCase()}'`;
else if (client === 'pg')
parsedValue = `decode('${buffer.toString('hex').toUpperCase()}', 'hex')`;
}
else if (NUMBER.includes(field.type))
parsedValue = val;
else if (FLOAT.includes(field.type))
parsedValue = parseFloat(val);
else if (SPATIAL.includes(field.type)) {
let geoJson;
if (IS_MULTI_SPATIAL.includes(field.type)) {
const features = [];
for (const element of val)
features.push(objectToGeoJSON(element));
geoJson = {
type: 'FeatureCollection',
features
};
}
else
geoJson = objectToGeoJSON(val);
parsedValue = `ST_GeomFromGeoJSON('${JSON.stringify(geoJson)}')`;
}
else if (val === '') parsedValue = `${sw}${sw}`;
else {
parsedValue = typeof val === 'string'
? escapeAndQuote(val, client)
: typeof val === 'object'
? escapeAndQuote(JSON.stringify(val), client)
: val;
}
return parsedValue;
};
export const jsonToSqlInsert = (args: {
json: { [key: string]: any}[];
client: ClientCode;
fields: { [key: string]: {type: string; datePrecision: number}};
table: string;
options?: {sqlInsertAfter: number; sqlInsertDivider: 'bytes' | 'rows'};
}) => {
const { client, json, fields, table, options } = args;
const sqlInsertAfter = options && options.sqlInsertAfter ? options.sqlInsertAfter : 1;
const sqlInsertDivider = options && options.sqlInsertDivider ? options.sqlInsertDivider : 'rows';
const { elementsWrapper: ew } = customizations[client];
const fieldNames = Object.keys(json[0]).map(key => `${ew}${key}${ew}`);
let insertStmt = `INSERT INTO ${ew}${table}${ew} (${fieldNames.join(', ')}) VALUES `;
let insertsString = '';
let queryLength = 0;
let rowsWritten = 0;
for (const row of json) {
const values = [];
values.push(Object.keys(row).map(key => (
valueToSqlString({ val: row[key], client, field: fields[key] })
)));
if (
(sqlInsertDivider === 'bytes' && queryLength >= sqlInsertAfter * 1024) ||
(sqlInsertDivider === 'rows' && rowsWritten === sqlInsertAfter)
) {
insertsString += insertStmt+';';
insertStmt = `\nINSERT INTO ${ew}${table}${ew} (${fieldNames.join(', ')}) VALUES `;
rowsWritten = 0;
}
rowsWritten++;
if (rowsWritten > 1) insertStmt += ',\n';
insertStmt += `(${values.join(',')})`;
queryLength = insertStmt.length;
}
if (rowsWritten > 0)
insertsString += insertStmt+';';
return insertsString;
};

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).substring(2, 11).toUpperCase();
}

View File

@@ -1,138 +0,0 @@
export const shortcutEvents: { [key: string]: { l18n: string; l18nParam?: string | number; context?: 'tab' }} = {
'run-or-reload': { l18n: 'application.runOrReload', context: 'tab' },
'open-new-tab': { l18n: 'application.openNewTab', context: 'tab' },
'close-tab': { l18n: 'application.closeTab', context: 'tab' },
'format-query': { l18n: 'database.formatQuery', context: 'tab' },
'kill-query': { l18n: 'database.killQuery', context: 'tab' },
'query-history': { l18n: 'database.queryHistory', context: 'tab' },
'clear-query': { l18n: 'database.clearQuery', context: 'tab' },
'next-tab': { l18n: 'application.nextTab' },
'prev-tab': { l18n: 'application.previousTab' },
'open-all-connections': { l18n: 'application.openAllConnections' },
'open-filter': { l18n: 'application.openFilter' },
'next-page': { l18n: 'application.nextResultsPage' },
'prev-page': { l18n: 'application.previousResultsPage' },
'toggle-console': { l18n: 'application.toggleConsole' },
'save-content': { l18n: 'application.saveContent' },
'create-connection': { l18n: 'connection.createNewConnection' },
'open-settings': { l18n: 'application.openSettings' },
'open-scratchpad': { l18n: 'application.openScratchpad' }
};
interface ShortcutRecord {
event: string;
keys: Electron.Accelerator[] | string[];
/** Needed for default shortcuts */
os: NodeJS.Platform[];
}
/**
* Default shortcuts
*/
const shortcuts: ShortcutRecord[] = [
{
event: 'run-or-reload',
keys: ['F5'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'save-content',
keys: ['CommandOrControl+S'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'kill-query',
keys: ['CommandOrControl+K'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'format-query',
keys: ['CommandOrControl+B'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'clear-query',
keys: ['CommandOrControl+Alt+W'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'query-history',
keys: ['CommandOrControl+G'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'open-new-tab',
keys: ['CommandOrControl+T'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'close-tab',
keys: ['CommandOrControl+W'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'next-tab',
keys: ['Alt+CommandOrControl+Right'],
os: ['darwin', 'win32']
},
{
event: 'prev-tab',
keys: ['Alt+CommandOrControl+Left'],
os: ['darwin', 'win32']
},
{
event: 'next-tab',
keys: ['CommandOrControl+PageDown'],
os: ['linux', 'win32']
},
{
event: 'prev-tab',
keys: ['CommandOrControl+PageUp'],
os: ['linux', 'win32']
},
{
event: 'open-filter',
keys: ['CommandOrControl+F'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'next-page',
keys: ['CommandOrControl+Right'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'prev-page',
keys: ['CommandOrControl+Left'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'open-all-connections',
keys: ['Shift+CommandOrControl+Space'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'toggle-console',
keys: ['CommandOrControl+F12'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'toggle-console',
keys: ['CommandOrControl+`'],
os: ['darwin', 'linux', 'win32']
}
];
for (let i = 1; i <= 9; i++) {
shortcutEvents[`select-tab-${i}`] = {
l18n: 'application.selectTabNumber',
l18nParam: i
};
shortcuts.push({
event: `select-tab-${i}`,
keys: [`CommandOrControl+${i}`],
os: ['darwin', 'linux', 'win32']
});
}
export { shortcuts, ShortcutRecord };

View File

@@ -1,11 +1,15 @@
import { app, ipcMain, dialog } from 'electron'; import { app, ipcMain, dialog } from 'electron';
import { ShortcutRegister } from '../libs/ShortcutRegister';
export default () => { export default () => {
ipcMain.on('close-app', () => { ipcMain.on('close-app', () => {
app.exit(); app.exit();
}); });
ipcMain.on('get-key', async event => {
const key = false;
event.returnValue = key;
});
ipcMain.handle('show-open-dialog', (event, options) => { ipcMain.handle('show-open-dialog', (event, options) => {
return dialog.showOpenDialog(options); return dialog.showOpenDialog(options);
}); });
@@ -13,24 +17,4 @@ export default () => {
ipcMain.handle('get-download-dir-path', () => { ipcMain.handle('get-download-dir-path', () => {
return app.getPath('downloads'); return app.getPath('downloads');
}); });
ipcMain.handle('resotre-default-shortcuts', () => {
const shortCutRegister = ShortcutRegister.getInstance();
shortCutRegister.restoreDefaults();
});
ipcMain.handle('reload-shortcuts', () => {
const shortCutRegister = ShortcutRegister.getInstance();
shortCutRegister.reload();
});
ipcMain.handle('update-shortcuts', (event, shortcuts) => {
const shortCutRegister = ShortcutRegister.getInstance();
shortCutRegister.updateShortcuts(shortcuts);
});
ipcMain.handle('unregister-shortcuts', () => {
const shortCutRegister = ShortcutRegister.getInstance();
shortCutRegister.unregister();
});
}; };

View File

@@ -55,17 +55,12 @@ export default (connections: {[key: string]: antares.Client}) => {
try { try {
const connection = await ClientsFactory.getClient({ const connection = await ClientsFactory.getClient({
uid: conn.uid,
client: conn.client, client: conn.client,
params params
}); });
await connection.connect(); await connection.connect();
if (conn.client === 'firebird') await connection.select('1+1').run();
connection.raw('SELECT rdb$get_context(\'SYSTEM\', \'DB_NAME\') FROM rdb$database');
else
await connection.select('1+1').run();
connection.destroy(); connection.destroy();
return { status: 'success' }; return { status: 'success' };
@@ -133,7 +128,6 @@ export default (connections: {[key: string]: antares.Client}) => {
try { try {
const connection = ClientsFactory.getClient({ const connection = ClientsFactory.getClient({
uid: conn.uid,
client: conn.client, client: conn.client,
params, params,
poolSize: 5 poolSize: 5

View File

@@ -1,14 +0,0 @@
import * as antares from 'common/interfaces/antares';
import { ipcMain } from 'electron';
export default (connections: {[key: string]: antares.Client}) => {
ipcMain.handle('get-databases', async (event, uid) => {
try {
const result = await connections[uid].getDatabases();
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
};

View File

@@ -9,7 +9,6 @@ import functions from './functions';
import schedulers from './schedulers'; import schedulers from './schedulers';
import updates from './updates'; import updates from './updates';
import application from './application'; import application from './application';
import database from './database';
import schema from './schema'; import schema from './schema';
import users from './users'; import users from './users';
@@ -23,7 +22,6 @@ export default () => {
routines(connections); routines(connections);
functions(connections); functions(connections);
schedulers(connections); schedulers(connections);
database(connections);
schema(connections); schema(connections);
users(connections); users(connections);
updates(); updates();

View File

@@ -62,7 +62,7 @@ export default (connections: {[key: string]: antares.Client}) => {
ipcMain.handle('get-structure', async (event, params) => { ipcMain.handle('get-structure', async (event, params) => {
try { try {
const structure: unknown = await connections[params.uid].getStructure( const structure = await connections[params.uid].getStructure(
params.schemas params.schemas
); );
@@ -97,7 +97,7 @@ export default (connections: {[key: string]: antares.Client}) => {
ipcMain.handle('get-engines', async (event, uid) => { ipcMain.handle('get-engines', async (event, uid) => {
try { try {
const result: unknown = await connections[uid].getEngines(); const result = await connections[uid].getEngines();
return { status: 'success', response: result }; return { status: 'success', response: result };
} }
@@ -160,7 +160,8 @@ export default (connections: {[key: string]: antares.Client}) => {
details: true, details: true,
schema, schema,
tabUid, tabUid,
autocommit autocommit,
comments: false
}); });
return { status: 'success', response: result }; return { status: 'success', response: result };
@@ -171,10 +172,7 @@ export default (connections: {[key: string]: antares.Client}) => {
}); });
ipcMain.handle('export', (event, { uid, type, tables, ...rest }) => { ipcMain.handle('export', (event, { uid, type, tables, ...rest }) => {
if (exporter !== null) { if (exporter !== null) return;
exporter.kill();
return;
}
return new Promise((resolve/*, reject */) => { return new Promise((resolve/*, reject */) => {
(async () => { (async () => {
@@ -267,10 +265,7 @@ export default (connections: {[key: string]: antares.Client}) => {
}); });
ipcMain.handle('import-sql', async (event, options) => { ipcMain.handle('import-sql', async (event, options) => {
if (importer !== null) { if (importer !== null) return;
importer.kill();
return;
}
return new Promise((resolve/*, reject */) => { return new Promise((resolve/*, reject */) => {
(async () => { (async () => {

View File

@@ -1,12 +1,12 @@
import * as fs from 'fs';
import * as antares from 'common/interfaces/antares'; import * as antares from 'common/interfaces/antares';
import { InsertRowsParams } from 'common/interfaces/tableApis'; import { InsertRowsParams } from 'common/interfaces/tableApis';
import { ipcMain } from 'electron'; import { ipcMain } from 'electron';
import { faker } from '@faker-js/faker'; import { faker } from '@faker-js/faker';
import * as moment from 'moment'; import * as moment from 'moment';
import { sqlEscaper } from 'common/libs/sqlUtils'; import { sqlEscaper } from 'common/libs/sqlEscaper';
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BLOB, BIT, DATE, DATETIME, BOOLEAN } from 'common/fieldTypes'; import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BLOB, BIT, DATE, DATETIME } from 'common/fieldTypes';
import customizations from 'common/customizations'; import * as customizations from 'common/customizations';
import fs from 'fs';
export default (connections: {[key: string]: antares.Client}) => { export default (connections: {[key: string]: antares.Client}) => {
ipcMain.handle('get-table-columns', async (event, params) => { ipcMain.handle('get-table-columns', async (event, params) => {
@@ -75,17 +75,6 @@ export default (connections: {[key: string]: antares.Client}) => {
} }
}); });
ipcMain.handle('get-table-ddl', async (event, params) => {
try {
const result = await connections[params.uid].getTableDll(params);
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('get-key-usage', async (event, params) => { ipcMain.handle('get-key-usage', async (event, params) => {
try { try {
const result = await connections[params.uid].getKeyUsage(params); const result = await connections[params.uid].getKeyUsage(params);
@@ -115,8 +104,9 @@ export default (connections: {[key: string]: antares.Client}) => {
escapedParam = `"${sqlEscaper(params.content)}"`; escapedParam = `"${sqlEscaper(params.content)}"`;
break; break;
case 'pg': case 'pg':
escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`;
break;
case 'sqlite': case 'sqlite':
case 'firebird':
escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`; escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`;
break; break;
} }
@@ -136,7 +126,6 @@ export default (connections: {[key: string]: antares.Client}) => {
escapedParam = `0x${fileBlob.toString('hex')}`; escapedParam = `0x${fileBlob.toString('hex')}`;
break; break;
case 'pg': case 'pg':
case 'firebird':
fileBlob = fs.readFileSync(params.content); fileBlob = fs.readFileSync(params.content);
escapedParam = `decode('${fileBlob.toString('hex')}', 'hex')`; escapedParam = `decode('${fileBlob.toString('hex')}', 'hex')`;
break; break;
@@ -154,7 +143,6 @@ export default (connections: {[key: string]: antares.Client}) => {
escapedParam = '\'\''; escapedParam = '\'\'';
break; break;
case 'pg': case 'pg':
case 'firebird':
escapedParam = 'decode(\'\', \'hex\')'; escapedParam = 'decode(\'\', \'hex\')';
break; break;
case 'sqlite': case 'sqlite':
@@ -167,19 +155,6 @@ export default (connections: {[key: string]: antares.Client}) => {
escapedParam = `b'${sqlEscaper(params.content)}'`; escapedParam = `b'${sqlEscaper(params.content)}'`;
reload = true; reload = true;
} }
else if (BOOLEAN.includes(params.type)) {
switch (connections[params.uid]._client) {
case 'mysql':
case 'maria':
case 'pg':
case 'firebird':
escapedParam = params.content;
break;
case 'sqlite':
escapedParam = Number(params.content === 'true');
break;
}
}
else if (params.content === null) else if (params.content === null)
escapedParam = 'NULL'; escapedParam = 'NULL';
else else
@@ -196,18 +171,13 @@ export default (connections: {[key: string]: antares.Client}) => {
} }
else { else {
const { orgRow } = params; const { orgRow } = params;
delete orgRow._antares_id;
reload = true; reload = true;
for (const key in orgRow) { for (const key in orgRow) {
if (typeof orgRow[key] === 'string') if (typeof orgRow[key] === 'string')
orgRow[key] = `'${orgRow[key]}'`; orgRow[key] = `'${orgRow[key]}'`;
if (orgRow[key] === null) orgRow[key] = `= ${orgRow[key]}`;
orgRow[key] = `IS ${orgRow[key]}`;
else
orgRow[key] = `= ${orgRow[key]}`;
} }
await connections[params.uid] await connections[params.uid]
@@ -238,11 +208,10 @@ export default (connections: {[key: string]: antares.Client}) => {
}).join(','); }).join(',');
try { try {
const result: unknown = await connections[params.uid] const result = await connections[params.uid]
.schema(params.schema) .schema(params.schema)
.delete(params.table) .delete(params.table)
.where({ [params.primary]: `IN (${idString})` }) .where({ [params.primary]: `IN (${idString})` })
.limit(params.rows.length)
.run(); .run();
return { status: 'success', response: result }; return { status: 'success', response: result };
@@ -258,10 +227,7 @@ export default (connections: {[key: string]: antares.Client}) => {
if (typeof row[key] === 'string') if (typeof row[key] === 'string')
row[key] = `'${row[key]}'`; row[key] = `'${row[key]}'`;
if (row[key] === null) row[key] = `= ${row[key]}`;
row[key] = 'IS NULL';
else
row[key] = `= ${row[key]}`;
} }
await connections[params.uid] await connections[params.uid]
@@ -280,12 +246,84 @@ export default (connections: {[key: string]: antares.Client}) => {
} }
}); });
ipcMain.handle('insert-table-rows', async (event, params) => {
try { // TODO: move to client classes
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const insertObj: {[key: string]: any} = {};
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: InsertRowsParams) => { ipcMain.handle('insert-table-fake-rows', async (event, params: InsertRowsParams) => {
try { // TODO: move to client classes try { // TODO: move to client classes
const rows: {[key: string]: string | number | boolean | Date | Buffer}[] = []; // eslint-disable-next-line @typescript-eslint/no-explicit-any
const rows: {[key: string]: any}[] = [];
for (let i = 0; i < +params.repeat; i++) { for (let i = 0; i < +params.repeat; i++) {
const insertObj: {[key: string]: string | number | boolean | Date | Buffer} = {}; // eslint-disable-next-line @typescript-eslint/no-explicit-any
const insertObj: {[key: string]: any} = {};
for (const key in params.row) { for (const key in params.row) {
const type = params.fields[key]; const type = params.fields[key];
@@ -303,8 +341,6 @@ export default (connections: {[key: string]: antares.Client}) => {
escapedParam = `"${sqlEscaper(params.row[key].value)}"`; escapedParam = `"${sqlEscaper(params.row[key].value)}"`;
break; break;
case 'pg': case 'pg':
case 'sqlite':
case 'firebird':
escapedParam = `'${params.row[key].value.replaceAll('\'', '\'\'')}'`; escapedParam = `'${params.row[key].value.replaceAll('\'', '\'\'')}'`;
break; break;
} }
@@ -345,7 +381,8 @@ export default (connections: {[key: string]: antares.Client}) => {
insertObj[key] = escapedParam; insertObj[key] = escapedParam;
} }
else { // Faker value else { // Faker value
const parsedParams: {[key: string]: string | number | boolean | Date | Buffer} = {}; // eslint-disable-next-line @typescript-eslint/no-explicit-any
const parsedParams: {[key: string]: any} = {};
let fakeValue; let fakeValue;
if (params.locale) if (params.locale)
@@ -365,19 +402,8 @@ export default (connections: {[key: string]: antares.Client}) => {
if (typeof fakeValue === 'string') { if (typeof fakeValue === 'string') {
if (params.row[key].length) if (params.row[key].length)
fakeValue = fakeValue.substring(0, params.row[key].length); fakeValue = fakeValue.substr(0, params.row[key].length);
fakeValue = `'${sqlEscaper(fakeValue)}'`;
switch (connections[params.uid]._client) {
case 'mysql':
case 'maria':
fakeValue = `'${sqlEscaper(fakeValue)}'`;
break;
case 'pg':
case 'sqlite':
case 'firebird':
fakeValue = `'${fakeValue.replaceAll('\'', '\'\'')}'`;
break;
}
} }
else if ([...DATE, ...DATETIME].includes(type)) else if ([...DATE, ...DATETIME].includes(type))
fakeValue = `'${moment(fakeValue).format('YYYY-MM-DD HH:mm:ss.SSSSSS')}'`; fakeValue = `'${moment(fakeValue).format('YYYY-MM-DD HH:mm:ss.SSSSSS')}'`;
@@ -413,20 +439,7 @@ export default (connections: {[key: string]: antares.Client}) => {
if (description) if (description)
query.select(`LEFT(${description}, 20) AS foreign_description`); query.select(`LEFT(${description}, 20) AS foreign_description`);
const results = await query.run<{[key: string]: string}>(); const results = await query.run();
const parsedResults: {[key: string]: string}[] = [];
for (const row of results.rows) {
const remappedRow: {[key: string]: string} = {};
for (const key in row)
remappedRow[key.toLowerCase()] = row[key];// Thanks Firebird -.-
parsedResults.push(remappedRow);
}
results.rows = parsedResults;
return { status: 'success', response: results }; return { status: 'success', response: results };
} }

View File

@@ -1,20 +1,11 @@
import { ipcMain } from 'electron'; import { ipcMain } from 'electron';
import { autoUpdater } from 'electron-updater'; import { autoUpdater } from 'electron-updater';
import * as Store from 'electron-store'; import Store from 'electron-store';
const persistentStore = new Store({ name: 'settings' });
const persistentStore = new Store({
name: 'settings',
clearInvalidConfig: true,
migrations: {
'0.7.15': store => {
store.set('allow_prerelease', false);
}
}
});
const isMacOS = process.platform === 'darwin'; const isMacOS = process.platform === 'darwin';
let mainWindow: Electron.IpcMainEvent; let mainWindow: Electron.IpcMainEvent;
autoUpdater.allowPrerelease = persistentStore.get('allow_prerelease', false) as boolean; autoUpdater.allowPrerelease = persistentStore.get('allow_prerelease', true) as boolean;
export default () => { export default () => {
ipcMain.on('check-for-updates', event => { ipcMain.on('check-for-updates', event => {

View File

@@ -3,32 +3,26 @@ import mysql from 'mysql2/promise';
import * as pg from 'pg'; import * as pg from 'pg';
import SSH2Promise from 'ssh2-promise'; import SSH2Promise from 'ssh2-promise';
const queryLogger = ({ sql, cUid }: {sql: string; cUid: string}) => { const queryLogger = (sql: string) => {
// Remove comments, newlines and multiple spaces // Remove comments, newlines and multiple spaces
const escapedSql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' '); const escapedSql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' ');
if (process.type !== undefined) { console.log(escapedSql);
const mainWindow = require('electron').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 * As Simple As Possible Query Builder Core
*/ */
export abstract class AntaresCore { export class AntaresCore {
_client: antares.ClientCode; _client: antares.ClientCode;
protected _cUid: string
protected _params: mysql.ConnectionOptions | pg.ClientConfig | { databasePath: string; readonly: boolean}; protected _params: mysql.ConnectionOptions | pg.ClientConfig | { databasePath: string; readonly: boolean};
protected _poolSize: number; protected _poolSize: number;
protected _ssh?: SSH2Promise; protected _ssh?: SSH2Promise;
protected _logger: (args: {sql: string; cUid: string}) => void; protected _logger: (sql: string) => void;
protected _queryDefaults: antares.QueryBuilderObject; protected _queryDefaults: antares.QueryBuilderObject;
protected _query: antares.QueryBuilderObject; protected _query: antares.QueryBuilderObject;
constructor (args: antares.ClientParams) { constructor (args: antares.ClientParams) {
this._client = args.client; this._client = args.client;
this._cUid = args.uid;
this._params = args.params; this._params = args.params;
this._poolSize = args.poolSize || undefined; this._poolSize = args.poolSize || undefined;
this._logger = args.logger || queryLogger; this._logger = args.logger || queryLogger;
@@ -162,10 +156,6 @@ export abstract class AntaresCore {
throw new Error('Method "getDbConfig" not implemented'); throw new Error('Method "getDbConfig" not implemented');
} }
getDatabases () {
throw new Error('Method "getDatabases" not implemented');
}
createSchema (...args: any) { createSchema (...args: any) {
throw new Error('Method "createSchema" not implemented'); throw new Error('Method "createSchema" not implemented');
} }
@@ -178,10 +168,6 @@ export abstract class AntaresCore {
throw new Error('Method "dropSchema" not implemented'); throw new Error('Method "dropSchema" not implemented');
} }
getTableDll (...args: any) {
throw new Error('Method "getTableDll" not implemented');
}
getDatabaseCollation (...args: any) { getDatabaseCollation (...args: any) {
throw new Error('Method "getDatabaseCollation" not implemented'); throw new Error('Method "getDatabaseCollation" not implemented');
} }

View File

@@ -2,7 +2,6 @@ import * as antares from 'common/interfaces/antares';
import { MySQLClient } from './clients/MySQLClient'; import { MySQLClient } from './clients/MySQLClient';
import { PostgreSQLClient } from './clients/PostgreSQLClient'; import { PostgreSQLClient } from './clients/PostgreSQLClient';
import { SQLiteClient } from './clients/SQLiteClient'; import { SQLiteClient } from './clients/SQLiteClient';
import { FirebirdSQLClient } from './clients/FirebirdSQLClient';
export class ClientsFactory { export class ClientsFactory {
static getClient (args: antares.ClientParams) { static getClient (args: antares.ClientParams) {
@@ -14,8 +13,6 @@ export class ClientsFactory {
return new PostgreSQLClient(args); return new PostgreSQLClient(args);
case 'sqlite': case 'sqlite':
return new SQLiteClient(args); return new SQLiteClient(args);
case 'firebird':
return new FirebirdSQLClient(args);
default: default:
throw new Error(`Unknown database client: ${args.client}`); throw new Error(`Unknown database client: ${args.client}`);
} }

View File

@@ -1,139 +0,0 @@
import { BrowserWindow, globalShortcut, Menu, MenuItem, MenuItemConstructorOptions } from 'electron';
import * as Store from 'electron-store';
import { ShortcutRecord, shortcuts } from 'common/shortcuts';
const shortcutsStore = new Store({ name: 'shortcuts' });
const isDevelopment = process.env.NODE_ENV !== 'production';
const defaultShortcuts = shortcuts.filter(s => s.os.includes(process.platform));
export type ShortcutMode = 'local' | 'global'
export type OsMenu = {
[key in NodeJS.Platform]?: MenuItemConstructorOptions[];
};
export class ShortcutRegister {
private _shortcuts: ShortcutRecord[];
private _mainWindow: BrowserWindow;
private _menu: Menu;
private _menuTemplate: OsMenu;
private _mode: ShortcutMode;
private static _instance: ShortcutRegister;
private constructor (args: { mainWindow: BrowserWindow; menuTemplate?: OsMenu; mode: ShortcutMode }) {
this._mainWindow = args.mainWindow;
this._menuTemplate = args.menuTemplate || {};
this._mode = args.mode;
this.shortcuts = shortcutsStore.get('shortcuts', defaultShortcuts) as ShortcutRecord[];
}
public static getInstance (args?: { mainWindow?: BrowserWindow; menuTemplate?: OsMenu; mode?: ShortcutMode }) {
if (!ShortcutRegister._instance && args.menuTemplate !== undefined && args.mode !== undefined) {
ShortcutRegister._instance = new ShortcutRegister({
mainWindow: args.mainWindow,
menuTemplate: args.menuTemplate,
mode: args.mode
});
}
return ShortcutRegister._instance;
}
get shortcuts () {
return this._shortcuts;
}
private set shortcuts (value: ShortcutRecord[]) {
this._shortcuts = value;
shortcutsStore.set('shortcuts', value);
}
init () {
this._mainWindow.webContents.send('update-shortcuts', this.shortcuts);
this.buildBaseMenu();
if (this._mode === 'global')
this.setGlobalShortcuts();
else if (this._mode === 'local')
this.setLocalShortcuts();
else
throw new Error(`Unknown mode "${this._mode}"`);
Menu.setApplicationMenu(this._menu);
}
private buildBaseMenu () {
if (Object.keys(this._menuTemplate).includes(process.platform))
this._menu = Menu.buildFromTemplate(this._menuTemplate[process.platform]);
else
this._menu = new Menu();
}
private setLocalShortcuts () {
for (const shortcut of this.shortcuts) {
if (shortcut.os.includes(process.platform)) {
for (const key of shortcut.keys) {
try {
this._menu.append(new MenuItem({
label: '.',
visible: false,
submenu: [{
label: String(key),
accelerator: key,
visible: false,
click: () => {
this._mainWindow.webContents.send(shortcut.event);
if (isDevelopment) console.log('LOCAL EVENT:', shortcut);
}
}]
}));
}
catch (error) {
if (isDevelopment) console.log(error);
this.restoreDefaults();
throw error;
}
}
}
}
}
private setGlobalShortcuts () {
for (const shortcut of this.shortcuts) {
if (shortcut.os.includes(process.platform)) {
for (const key of shortcut.keys) {
try {
globalShortcut.register(key, () => {
this._mainWindow.webContents.send(shortcut.event);
if (isDevelopment) console.log('GLOBAL EVENT:', shortcut);
});
}
catch (error) {
if (isDevelopment) console.log(error);
this.restoreDefaults();
throw error;
}
}
}
}
}
reload () {
this.unregister();
this.init();
}
updateShortcuts (shortcuts: ShortcutRecord[]) {
this.shortcuts = shortcuts;
this.reload();
}
restoreDefaults () {
this.shortcuts = defaultShortcuts;
this.reload();
}
unregister () {
if (this._mode === 'global') globalShortcut.unregisterAll();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
import * as antares from 'common/interfaces/antares'; import * as antares from 'common/interfaces/antares';
import * as mysql from 'mysql2/promise'; import * as mysql from 'mysql2/promise';
import { AntaresCore } from '../AntaresCore'; import { AntaresCore } from '../AntaresCore';
import dataTypes from 'common/data-types/mysql'; import * as dataTypes from 'common/data-types/mysql';
import SSH2Promise = require('ssh2-promise'); import SSH2Promise = require('ssh2-promise');
import SSHConfig from 'ssh2-promise/lib/sshConfig'; import SSHConfig from 'ssh2-promise/lib/sshConfig';
@@ -9,8 +9,6 @@ export class MySQLClient extends AntaresCore {
private _schema?: string; private _schema?: string;
private _runningConnections: Map<string, number>; private _runningConnections: Map<string, number>;
private _connectionsToCommit: Map<string, mysql.Connection | mysql.PoolConnection>; private _connectionsToCommit: Map<string, mysql.Connection | mysql.PoolConnection>;
private _keepaliveTimer: NodeJS.Timer;
private _keepaliveMs: number;
_connection?: mysql.Connection | mysql.Pool; _connection?: mysql.Connection | mysql.Pool;
_params: mysql.ConnectionOptions & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean}; _params: mysql.ConnectionOptions & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean};
@@ -54,7 +52,6 @@ export class MySQLClient extends AntaresCore {
this._schema = null; this._schema = null;
this._runningConnections = new Map(); this._runningConnections = new Map();
this._connectionsToCommit = new Map(); this._connectionsToCommit = new Map();
this._keepaliveMs = 10*60*1000;
} }
private _getType (field: mysql.FieldPacket & { columnType?: number; columnLength?: number }) { private _getType (field: mysql.FieldPacket & { columnType?: number; columnLength?: number }) {
@@ -150,14 +147,7 @@ export class MySQLClient extends AntaresCore {
if (this._params.ssh) { if (this._params.ssh) {
try { try {
if (this._params.ssh.password === '') delete this._params.ssh.password; this._ssh = new SSH2Promise({ ...this._params.ssh });
if (this._params.ssh.passphrase === '') delete this._params.ssh.passphrase;
this._ssh = new SSH2Promise({
...this._params.ssh,
keepaliveInterval: 30*60*1000,
debug: process.env.NODE_ENV !== 'production' ? (s) => console.log(s) : null
});
const tunnel = await this._ssh.addTunnel({ const tunnel = await this._ssh.addTunnel({
remoteAddr: this._params.host, remoteAddr: this._params.host,
@@ -185,8 +175,6 @@ export class MySQLClient extends AntaresCore {
destroy () { destroy () {
this._connection.end(); this._connection.end();
clearInterval(this._keepaliveTimer);
this._keepaliveTimer = undefined;
if (this._ssh) this._ssh.close(); if (this._ssh) this._ssh.close();
} }
@@ -204,14 +192,14 @@ export class MySQLClient extends AntaresCore {
// ANSI_QUOTES check // ANSI_QUOTES check
const [response] = await connection.query<mysql.RowDataPacket[]>('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\''); const [response] = await connection.query<mysql.RowDataPacket[]>('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\'');
const sqlMode: string[] = response[0]?.Value?.split(','); const sqlMode = response[0]?.Value?.split(',');
const hasAnsiQuotes = sqlMode.includes('ANSI') || sqlMode.includes('ANSI_QUOTES'); const hasAnsiQuotes = sqlMode.includes('ANSI_QUOTES');
if (this._params.readonly) if (this._params.readonly)
await connection.query('SET SESSION TRANSACTION READ ONLY'); await connection.query('SET SESSION TRANSACTION READ ONLY');
if (hasAnsiQuotes) if (hasAnsiQuotes)
await connection.query(`SET SESSION sql_mode = '${sqlMode.filter((m: string) => !['ANSI', 'ANSI_QUOTES'].includes(m)).join(',')}'`); await connection.query(`SET SESSION sql_mode = "${sqlMode.filter((m: string) => m !== 'ANSI_QUOTES').join(',')}"`);
return connection; return connection;
} }
@@ -231,36 +219,23 @@ export class MySQLClient extends AntaresCore {
// ANSI_QUOTES check // ANSI_QUOTES check
const [res] = await connection.query<mysql.RowDataPacket[]>('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\''); const [res] = await connection.query<mysql.RowDataPacket[]>('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\'');
const sqlMode: string[] = res[0]?.Value?.split(','); const sqlMode = res[0]?.Value?.split(',');
const hasAnsiQuotes = sqlMode.includes('ANSI') || sqlMode.includes('ANSI_QUOTES'); const hasAnsiQuotes = sqlMode.includes('ANSI_QUOTES');
if (hasAnsiQuotes) if (hasAnsiQuotes)
await connection.query(`SET SESSION sql_mode = '${sqlMode.filter((m: string) => !['ANSI', 'ANSI_QUOTES'].includes(m)).join(',')}'`); await connection.query(`SET SESSION sql_mode = "${sqlMode.filter((m: string) => m !== 'ANSI_QUOTES').join(',')}"`);
if (this._params.readonly)
await connection.query('SET SESSION TRANSACTION READ ONLY');
connection.on('connection', conn => { connection.on('connection', conn => {
if (this._params.readonly) if (this._params.readonly)
conn.query('SET SESSION TRANSACTION READ ONLY'); conn.query('SET SESSION TRANSACTION READ ONLY');
if (hasAnsiQuotes) if (hasAnsiQuotes)
conn.query(`SET SESSION sql_mode = '${sqlMode.filter((m: string) => !['ANSI', 'ANSI_QUOTES'].includes(m)).join(',')}'`); conn.query(`SET SESSION sql_mode = "${sqlMode.filter((m: string) => m !== 'ANSI_QUOTES').join(',')}"`);
}); });
this._keepaliveTimer = setInterval(async () => {
await this.keepAlive();
}, this._keepaliveMs);
return connection; return connection;
} }
private async keepAlive () {
const connection = await (this._connection as mysql.Pool).getConnection();
await connection.ping();
connection.release();
}
use (schema: string) { use (schema: string) {
this._schema = schema; this._schema = schema;
return this.raw(`USE \`${schema}\``); return this.raw(`USE \`${schema}\``);
@@ -269,15 +244,25 @@ export class MySQLClient extends AntaresCore {
async getStructure (schemas: Set<string>) { async getStructure (schemas: Set<string>) {
/* eslint-disable camelcase */ /* eslint-disable camelcase */
interface ShowTableResult { interface ShowTableResult {
TABLE_SCHEMA?: string; Db?: string;
TABLE_NAME: string; Name: string;
TABLE_TYPE: string; Engine: string;
TABLE_ROWS: number; Version: number;
ENGINE: string; Row_format: string;
DATA_LENGTH: number; Rows: number;
INDEX_LENGTH: number; Avg_row_length: number;
TABLE_COLLATION: string; Data_length: number;
TABLE_COMMENT: string; Max_data_length: number;
Index_length: number;
Data_free: number;
Auto_increment: number;
Create_time: Date;
Update_time: Date;
Check_time?: number;
Collation: string;
Checksum?: number;
Create_options: string;
Comment: string;
} }
interface ShowTriggersResult { interface ShowTriggersResult {
@@ -305,69 +290,22 @@ export class MySQLClient extends AntaresCore {
const { rows: functions } = await this.raw('SHOW FUNCTION STATUS'); const { rows: functions } = await this.raw('SHOW FUNCTION STATUS');
const { rows: procedures } = await this.raw('SHOW PROCEDURE STATUS'); const { rows: procedures } = await this.raw('SHOW PROCEDURE STATUS');
// eslint-disable-next-line @typescript-eslint/no-explicit-any const { rows: schedulers } = await this.raw('SELECT *, EVENT_SCHEMA AS `Db`, EVENT_NAME AS `Name` FROM information_schema.`EVENTS`');
let schedulers: any[] = [];
try { // Avoid exception with event_scheduler DISABLED with MariaDB 10
const { rows } = await this.raw('SELECT *, EVENT_SCHEMA AS `Db`, EVENT_NAME AS `Name` FROM information_schema.`EVENTS`');
schedulers = rows;
}
catch (err) {
console.log(err);
}
const tablesArr: ShowTableResult[] = []; const tablesArr: ShowTableResult[] = [];
const triggersArr: ShowTriggersResult[] = []; const triggersArr: ShowTriggersResult[] = [];
let schemaSize = 0; let schemaSize = 0;
const Store = require('electron-store');
Store.initRenderer();
const settingsStore = new Store({ name: 'settings' });
for (const db of filteredDatabases) { for (const db of filteredDatabases) {
if (!schemas.has(db.Database)) continue; if (!schemas.has(db.Database)) continue;
const showTableSize = settingsStore.get('show_table_size', false); let { rows: tables } = await this.raw<antares.QueryResult<ShowTableResult>>(`SHOW TABLE STATUS FROM \`${db.Database}\``);
if (tables.length) {
if (showTableSize) { tables = tables.map(table => {
let { rows: tables } = await this.raw<antares.QueryResult<ShowTableResult>>(` table.Db = db.Database;
SELECT return table;
TABLE_NAME, });
TABLE_TYPE, tablesArr.push(...tables);
ENGINE,
DATA_LENGTH,
INDEX_LENGTH,
TABLE_COMMENT,
TABLE_COLLATION,
CREATE_TIME,
UPDATE_TIME
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = "${db.Database}"
ORDER BY TABLE_NAME
`);
if (tables.length) {
tables = tables.map(table => {
table.TABLE_SCHEMA = db.Database;
return table;
});
tablesArr.push(...tables);
}
}
else {
let { rows: tables } = await this.raw<antares.QueryResult<ShowTableResult>>(`SHOW FULL TABLES FROM \`${db.Database}\``);
if (tables.length) {
tables = tables.map(table => {
const [name, type] = Object.values(table);
table.TABLE_SCHEMA = db.Database;
table.TABLE_NAME = name;
table.TABLE_TYPE = type;
return table;
});
tablesArr.push(...tables);
}
} }
let { rows: triggers } = await this.raw<antares.QueryResult<ShowTriggersResult>>(`SHOW TRIGGERS FROM \`${db.Database}\``); let { rows: triggers } = await this.raw<antares.QueryResult<ShowTriggersResult>>(`SHOW TRIGGERS FROM \`${db.Database}\``);
@@ -383,9 +321,9 @@ export class MySQLClient extends AntaresCore {
return filteredDatabases.map(db => { return filteredDatabases.map(db => {
if (schemas.has(db.Database)) { if (schemas.has(db.Database)) {
// TABLES // TABLES
const remappedTables: antares.TableInfos[] = tablesArr.filter(table => table.TABLE_SCHEMA === db.Database).map(table => { const remappedTables = tablesArr.filter(table => table.Db === db.Database).map(table => {
let tableType; let tableType;
switch (table.TABLE_TYPE) { switch (table.Comment) {
case 'VIEW': case 'VIEW':
tableType = 'view'; tableType = 'view';
break; break;
@@ -394,22 +332,25 @@ export class MySQLClient extends AntaresCore {
break; break;
} }
const tableSize = Number(table.DATA_LENGTH) + Number(table.INDEX_LENGTH); const tableSize = Number(table.Data_length) + Number(table.Index_length);
schemaSize += tableSize; schemaSize += tableSize;
return { return {
name: table.TABLE_NAME, name: table.Name,
type: tableType, type: tableType,
rows: table.TABLE_ROWS, rows: table.Rows,
engine: table.ENGINE, created: table.Create_time,
comment: table.TABLE_COMMENT, updated: table.Update_time,
engine: table.Engine,
comment: table.Comment,
size: tableSize, size: tableSize,
collation: table.TABLE_COLLATION autoIncrement: table.Auto_increment,
collation: table.Collation
}; };
}); });
// PROCEDURES // PROCEDURES
const remappedProcedures: antares.RoutineInfos[] = procedures.filter(procedure => procedure.Db === db.Database).map(procedure => { const remappedProcedures = procedures.filter(procedure => procedure.Db === db.Database).map(procedure => {
return { return {
name: procedure.Name, name: procedure.Name,
type: procedure.Type, type: procedure.Type,
@@ -423,7 +364,7 @@ export class MySQLClient extends AntaresCore {
}); });
// FUNCTIONS // FUNCTIONS
const remappedFunctions: antares.FunctionInfos[] = functions.filter(func => func.Db === db.Database).map(func => { const remappedFunctions = functions.filter(func => func.Db === db.Database).map(func => {
return { return {
name: func.Name, name: func.Name,
type: func.Type, type: func.Type,
@@ -437,26 +378,33 @@ export class MySQLClient extends AntaresCore {
}); });
// SCHEDULERS // SCHEDULERS
const remappedSchedulers: antares.EventInfos[] = schedulers.filter(scheduler => scheduler.Db === db.Database).map(scheduler => { const remappedSchedulers = schedulers.filter(scheduler => scheduler.Db === db.Database).map(scheduler => {
return { return {
name: scheduler.EVENT_NAME, name: scheduler.EVENT_NAME,
schema: scheduler.Db, definition: scheduler.EVENT_DEFINITION,
sql: scheduler.EVENT_DEFINITION, type: scheduler.EVENT_TYPE,
execution: scheduler.EVENT_TYPE === 'RECURRING' ? 'EVERY' : 'ONCE',
definer: scheduler.DEFINER, definer: scheduler.DEFINER,
body: scheduler.EVENT_BODY,
starts: scheduler.STARTS, starts: scheduler.STARTS,
ends: scheduler.ENDS, ends: scheduler.ENDS,
state: scheduler.STATUS === 'ENABLED' ? 'ENABLE' : scheduler.STATE === 'DISABLED' ? 'DISABLE' : 'DISABLE ON SLAVE',
enabled: scheduler.STATUS === 'ENABLED', enabled: scheduler.STATUS === 'ENABLED',
at: scheduler.EXECUTE_AT, executeAt: scheduler.EXECUTE_AT,
every: [scheduler.INTERVAL_FIELD, scheduler.INTERVAL_VALUE], intervalField: scheduler.INTERVAL_FIELD,
preserve: scheduler.ON_COMPLETION.includes('PRESERVE'), intervalValue: scheduler.INTERVAL_VALUE,
comment: scheduler.EVENT_COMMENT onCompletion: scheduler.ON_COMPLETION,
originator: scheduler.ORIGINATOR,
sqlMode: scheduler.SQL_MODE,
created: scheduler.CREATED,
updated: scheduler.LAST_ALTERED,
lastExecuted: scheduler.LAST_EXECUTED,
comment: scheduler.EVENT_COMMENT,
charset: scheduler.CHARACTER_SET_CLIENT,
timezone: scheduler.TIME_ZONE
}; };
}); });
// TRIGGERS // TRIGGERS
const remappedTriggers: antares.TriggerInfos[] = triggersArr.filter(trigger => trigger.Db === db.Database).map(trigger => { const remappedTriggers = triggersArr.filter(trigger => trigger.Db === db.Database).map(trigger => {
return { return {
name: trigger.Trigger, name: trigger.Trigger,
statement: trigger.Statement, statement: trigger.Statement,
@@ -528,12 +476,7 @@ export class MySQLClient extends AntaresCore {
.orderBy({ ORDINAL_POSITION: 'ASC' }) .orderBy({ ORDINAL_POSITION: 'ASC' })
.run<TableColumnsResult>(); .run<TableColumnsResult>();
let fields: CreateTableResult[] = []; const { rows: fields } = await this.raw<antares.QueryResult<CreateTableResult>>(`SHOW CREATE TABLE \`${schema}\`.\`${table}\``);
try {
const { rows } = await this.raw<antares.QueryResult<CreateTableResult>>(`SHOW CREATE TABLE \`${schema}\`.\`${table}\``);
fields = rows;
}
catch (_) {}
const remappedFields = fields.map(row => { const remappedFields = fields.map(row => {
if (!row['Create Table']) return false; if (!row['Create Table']) return false;
@@ -582,7 +525,7 @@ export class MySQLClient extends AntaresCore {
return rows.map((field) => { return rows.map((field) => {
const numLengthMatch = field.COLUMN_TYPE.match(/int\(([^)]+)\)/); const numLengthMatch = field.COLUMN_TYPE.match(/int\(([^)]+)\)/);
const numLength = numLengthMatch ? +numLengthMatch.pop() : field.NUMERIC_PRECISION || null; const numLength = numLengthMatch ? +numLengthMatch.pop() : field.NUMERIC_PRECISION || null;
const enumValues = /(enum)/.test(field.COLUMN_TYPE) const enumValues = /(enum|set)/.test(field.COLUMN_TYPE)
? field.COLUMN_TYPE.match(/\(([^)]+)\)/)[0].slice(1, -1) ? field.COLUMN_TYPE.match(/\(([^)]+)\)/)[0].slice(1, -1)
: null; : null;
@@ -689,11 +632,11 @@ export class MySQLClient extends AntaresCore {
return rows.map(row => { return rows.map(row => {
return { return {
unique: !Number(row.Non_unique), unique: !row.Non_unique,
name: row.Key_name, name: row.Key_name,
column: row.Column_name, column: row.Column_name,
indexType: row.Index_type, indexType: row.Index_type,
type: row.Key_name === 'PRIMARY' ? 'PRIMARY' : !Number(row.Non_unique) ? 'UNIQUE' : row.Index_type === 'FULLTEXT' ? 'FULLTEXT' : 'INDEX', type: row.Key_name === 'PRIMARY' ? 'PRIMARY' : !row.Non_unique ? 'UNIQUE' : row.Index_type === 'FULLTEXT' ? 'FULLTEXT' : 'INDEX',
cardinality: row.Cardinality, cardinality: row.Cardinality,
comment: row.Comment, comment: row.Comment,
indexComment: row.Index_comment indexComment: row.Index_comment
@@ -701,17 +644,6 @@ export class MySQLClient extends AntaresCore {
}); });
} }
async getTableDll ({ schema, table }: { schema: string; table: string }) {
const { rows } = await this.raw<antares.QueryResult<{
'Create Table'?: string;
Table: string;
}>>(`SHOW CREATE TABLE \`${schema}\`.\`${table}\``);
if (rows.length)
return rows[0]['Create Table'];
else return '';
}
async getKeyUsage ({ schema, table }: { schema: string; table: string }) { async getKeyUsage ({ schema, table }: { schema: string; table: string }) {
interface KeyResult { interface KeyResult {
TABLE_SCHEMA: string; TABLE_SCHEMA: string;
@@ -764,7 +696,7 @@ export class MySQLClient extends AntaresCore {
} }
async getUsers () { async getUsers () {
const { rows } = await this.raw('SELECT `user` as \'user\', `host` as \'host\', authentication_string AS `password` FROM `mysql`.`user`'); const { rows } = await this.raw('SELECT `user`, `host`, authentication_string AS `password` FROM `mysql`.`user`');
return rows.map(row => { return rows.map(row => {
return { return {
@@ -865,23 +797,21 @@ export class MySQLClient extends AntaresCore {
options options
} = params; } = params;
const sql = `ALTER TABLE \`${schema}\`.\`${table}\` `; let sql = `ALTER TABLE \`${schema}\`.\`${table}\` `;
const alterColumnsAdd: string[] = []; const alterColumns: string[] = [];
const alterColumnsChange: string[] = [];
const alterColumnsDrop: string[] = [];
// OPTIONS // OPTIONS
if ('comment' in options) alterColumnsChange.push(`COMMENT='${options.comment}'`); if ('comment' in options) alterColumns.push(`COMMENT='${options.comment}'`);
if ('engine' in options) alterColumnsChange.push(`ENGINE=${options.engine}`); if ('engine' in options) alterColumns.push(`ENGINE=${options.engine}`);
if ('autoIncrement' in options) alterColumnsChange.push(`AUTO_INCREMENT=${+options.autoIncrement}`); if ('autoIncrement' in options) alterColumns.push(`AUTO_INCREMENT=${+options.autoIncrement}`);
if ('collation' in options) alterColumnsChange.push(`COLLATE='${options.collation}'`); if ('collation' in options) alterColumns.push(`COLLATE='${options.collation}'`);
// ADD FIELDS // ADD FIELDS
additions.forEach(addition => { additions.forEach(addition => {
const typeInfo = this.getTypeInfo(addition.type); const typeInfo = this.getTypeInfo(addition.type);
const length = typeInfo.length ? addition.enumValues || addition.numLength || addition.charLength || addition.datePrecision : false; const length = typeInfo.length ? addition.enumValues || addition.numLength || addition.charLength || addition.datePrecision : false;
alterColumnsAdd.push(`ADD COLUMN \`${addition.name}\` alterColumns.push(`ADD COLUMN \`${addition.name}\`
${addition.type.toUpperCase()}${length ? `(${length}${addition.numScale ? `,${addition.numScale}` : ''})` : ''} ${addition.type.toUpperCase()}${length ? `(${length}${addition.numScale ? `,${addition.numScale}` : ''})` : ''}
${addition.unsigned ? 'UNSIGNED' : ''} ${addition.unsigned ? 'UNSIGNED' : ''}
${addition.zerofill ? 'ZEROFILL' : ''} ${addition.zerofill ? 'ZEROFILL' : ''}
@@ -900,18 +830,18 @@ export class MySQLClient extends AntaresCore {
let type = addition.type; let type = addition.type;
if (type === 'PRIMARY') if (type === 'PRIMARY')
alterColumnsAdd.push(`ADD PRIMARY KEY (${fields})`); alterColumns.push(`ADD PRIMARY KEY (${fields})`);
else { else {
if (type === 'UNIQUE') if (type === 'UNIQUE')
type = 'UNIQUE INDEX'; type = 'UNIQUE INDEX';
alterColumnsAdd.push(`ADD ${type} \`${addition.name}\` (${fields})`); alterColumns.push(`ADD ${type} \`${addition.name}\` (${fields})`);
} }
}); });
// ADD FOREIGN KEYS // ADD FOREIGN KEYS
foreignChanges.additions.forEach(addition => { foreignChanges.additions.forEach(addition => {
alterColumnsAdd.push(`ADD CONSTRAINT \`${addition.constraintName}\` FOREIGN KEY (\`${addition.field}\`) REFERENCES \`${addition.refTable}\` (\`${addition.refField}\`) ON UPDATE ${addition.onUpdate} ON DELETE ${addition.onDelete}`); alterColumns.push(`ADD CONSTRAINT \`${addition.constraintName}\` FOREIGN KEY (\`${addition.field}\`) REFERENCES \`${addition.refTable}\` (\`${addition.refField}\`) ON UPDATE ${addition.onUpdate} ON DELETE ${addition.onDelete}`);
}); });
// CHANGE FIELDS // CHANGE FIELDS
@@ -919,7 +849,7 @@ export class MySQLClient extends AntaresCore {
const typeInfo = this.getTypeInfo(change.type); const typeInfo = this.getTypeInfo(change.type);
const length = typeInfo.length ? change.enumValues || change.numLength || change.charLength || change.datePrecision : false; const length = typeInfo.length ? change.enumValues || change.numLength || change.charLength || change.datePrecision : false;
alterColumnsChange.push(`CHANGE COLUMN \`${change.orgName}\` \`${change.name}\` alterColumns.push(`CHANGE COLUMN \`${change.orgName}\` \`${change.name}\`
${change.type.toUpperCase()}${length ? `(${length}${change.numScale ? `,${change.numScale}` : ''})` : ''} ${change.type.toUpperCase()}${length ? `(${length}${change.numScale ? `,${change.numScale}` : ''})` : ''}
${change.unsigned ? 'UNSIGNED' : ''} ${change.unsigned ? 'UNSIGNED' : ''}
${change.zerofill ? 'ZEROFILL' : ''} ${change.zerofill ? 'ZEROFILL' : ''}
@@ -935,56 +865,53 @@ export class MySQLClient extends AntaresCore {
// CHANGE INDEX // CHANGE INDEX
indexChanges.changes.forEach(change => { indexChanges.changes.forEach(change => {
if (change.oldType === 'PRIMARY') if (change.oldType === 'PRIMARY')
alterColumnsChange.push('DROP PRIMARY KEY'); alterColumns.push('DROP PRIMARY KEY');
else else
alterColumnsChange.push(`DROP INDEX \`${change.oldName}\``); alterColumns.push(`DROP INDEX \`${change.oldName}\``);
const fields = change.fields.map(field => `\`${field}\``).join(','); const fields = change.fields.map(field => `\`${field}\``).join(',');
let type = change.type; let type = change.type;
if (type === 'PRIMARY') if (type === 'PRIMARY')
alterColumnsChange.push(`ADD PRIMARY KEY (${fields})`); alterColumns.push(`ADD PRIMARY KEY (${fields})`);
else { else {
if (type === 'UNIQUE') if (type === 'UNIQUE')
type = 'UNIQUE INDEX'; type = 'UNIQUE INDEX';
alterColumnsChange.push(`ADD ${type} \`${change.name}\` (${fields})`); alterColumns.push(`ADD ${type} \`${change.name}\` (${fields})`);
} }
}); });
// CHANGE FOREIGN KEYS // CHANGE FOREIGN KEYS
foreignChanges.changes.forEach(change => { foreignChanges.changes.forEach(change => {
alterColumnsChange.push(`DROP FOREIGN KEY \`${change.oldName}\``); alterColumns.push(`DROP FOREIGN KEY \`${change.oldName}\``);
alterColumnsChange.push(`ADD CONSTRAINT \`${change.constraintName}\` FOREIGN KEY (\`${change.field}\`) REFERENCES \`${change.refTable}\` (\`${change.refField}\`) ON UPDATE ${change.onUpdate} ON DELETE ${change.onDelete}`); alterColumns.push(`ADD CONSTRAINT \`${change.constraintName}\` FOREIGN KEY (\`${change.field}\`) REFERENCES \`${change.refTable}\` (\`${change.refField}\`) ON UPDATE ${change.onUpdate} ON DELETE ${change.onDelete}`);
}); });
// DROP FIELDS // DROP FIELDS
deletions.forEach(deletion => { deletions.forEach(deletion => {
alterColumnsDrop.push(`DROP COLUMN \`${deletion.name}\``); alterColumns.push(`DROP COLUMN \`${deletion.name}\``);
}); });
// DROP INDEX // DROP INDEX
indexChanges.deletions.forEach(deletion => { indexChanges.deletions.forEach(deletion => {
if (deletion.type === 'PRIMARY') if (deletion.type === 'PRIMARY')
alterColumnsDrop.push('DROP PRIMARY KEY'); alterColumns.push('DROP PRIMARY KEY');
else else
alterColumnsDrop.push(`DROP INDEX \`${deletion.name}\``); alterColumns.push(`DROP INDEX \`${deletion.name}\``);
}); });
// DROP FOREIGN KEYS // DROP FOREIGN KEYS
foreignChanges.deletions.forEach(deletion => { foreignChanges.deletions.forEach(deletion => {
alterColumnsDrop.push(`DROP FOREIGN KEY \`${deletion.constraintName}\``); alterColumns.push(`DROP FOREIGN KEY \`${deletion.constraintName}\``);
}); });
const alterQueryes = []; sql += alterColumns.join(', ');
if (alterColumnsAdd.length) alterQueryes.push(sql+alterColumnsAdd.join(', '));
if (alterColumnsChange.length) alterQueryes.push(sql+alterColumnsChange.join(', '));
if (alterColumnsDrop.length) alterQueryes.push(sql+alterColumnsDrop.join(', '));
// RENAME // RENAME
if (options.name) alterQueryes.push(`RENAME TABLE \`${schema}\`.\`${table}\` TO \`${schema}\`.\`${options.name}\``); if (options.name) sql += `; RENAME TABLE \`${schema}\`.\`${table}\` TO \`${schema}\`.\`${options.name}\``;
return await this.raw(alterQueryes.join(';')); return await this.raw(sql);
} }
async duplicateTable (params: { schema: string; table: string }) { async duplicateTable (params: { schema: string; table: string }) {
@@ -992,15 +919,8 @@ export class MySQLClient extends AntaresCore {
return await this.raw(sql); return await this.raw(sql);
} }
async truncateTable (params: { schema: string; table: string; force: boolean }) { async truncateTable (params: { schema: string; table: string }) {
let sql = `TRUNCATE TABLE \`${params.schema}\`.\`${params.table}\`;`; const sql = `TRUNCATE TABLE \`${params.schema}\`.\`${params.table}\``;
if (params.force) {
sql = `
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
${sql}
SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1);
`;
}
return await this.raw(sql); return await this.raw(sql);
} }
@@ -1010,22 +930,19 @@ export class MySQLClient extends AntaresCore {
} }
async getViewInformations ({ schema, view }: { schema: string; view: string }) { async getViewInformations ({ schema, view }: { schema: string; view: string }) {
const { rows: algorithm } = await this.raw(`SHOW CREATE VIEW \`${schema}\`.\`${view}\``); const sql = `SHOW CREATE VIEW \`${schema}\`.\`${view}\``;
const { rows: viewInfo } = await this.raw(` const results = await this.raw(sql);
SELECT *
FROM INFORMATION_SCHEMA.VIEWS
WHERE TABLE_SCHEMA = '${schema}'
AND TABLE_NAME = '${view}'
`);
return { return results.rows.map(row => {
algorithm: algorithm[0]['Create View'].match(/(?<=CREATE ALGORITHM=).*?(?=\s)/gs)[0], return {
definer: viewInfo[0].DEFINER.split('@').map((str: string) => `\`${str}\``).join('@'), algorithm: row['Create View'].match(/(?<=CREATE ALGORITHM=).*?(?=\s)/gs)[0],
security: viewInfo[0].SECURITY_TYPE, definer: row['Create View'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0],
updateOption: viewInfo[0].CHECK_OPTION === 'NONE' ? '' : viewInfo[0].CHECK_OPTION, security: row['Create View'].match(/(?<=SQL SECURITY ).*?(?=\s)/gs)[0],
sql: viewInfo[0].VIEW_DEFINITION, updateOption: row['Create View'].match(/(?<=WITH ).*?(?=\s)/gs) ? row['Create View'].match(/(?<=WITH ).*?(?=\s)/gs)[0] : '',
name: viewInfo[0].TABLE_NAME sql: row['Create View'].match(/(?<=AS ).*?$/gs)[0],
}; name: row.View
};
})[0];
} }
async dropView (params: { schema: string; view: string }) { async dropView (params: { schema: string; view: string }) {
@@ -1038,7 +955,7 @@ export class MySQLClient extends AntaresCore {
USE \`${view.schema}\`; USE \`${view.schema}\`;
ALTER ALGORITHM = ${view.algorithm}${view.definer ? ` DEFINER=${view.definer}` : ''} ALTER ALGORITHM = ${view.algorithm}${view.definer ? ` DEFINER=${view.definer}` : ''}
SQL SECURITY ${view.security} SQL SECURITY ${view.security}
VIEW \`${view.schema}\`.\`${view.oldName}\` AS ${view.sql} ${view.updateOption ? `WITH ${view.updateOption} CHECK OPTION` : ''} params \`${view.schema}\`.\`${view.oldName}\` AS ${view.sql} ${view.updateOption ? `WITH ${view.updateOption} CHECK OPTION` : ''}
`; `;
if (view.name !== view.oldName) if (view.name !== view.oldName)
@@ -1464,20 +1381,12 @@ export class MySQLClient extends AntaresCore {
xa: row.XA, xa: row.XA,
savepoints: row.Savepoints, savepoints: row.Savepoints,
isDefault: row.Support.includes('DEFAULT') isDefault: row.Support.includes('DEFAULT')
} as {
name: string;
support: string;
comment: string;
transactions: string;
xa: string;
savepoints: string;
isDefault: boolean;
}; };
}); });
} }
async getVersion () { async getVersion () {
const sql = 'SHOW VARIABLES LIKE \'%vers%\''; const sql = 'SHOW VARIABLES LIKE "%vers%"';
const { rows } = await this.raw(sql); const { rows } = await this.raw(sql);
return rows.reduce((acc, curr) => { return rows.reduce((acc, curr) => {
@@ -1496,12 +1405,7 @@ export class MySQLClient extends AntaresCore {
break; break;
} }
return acc; return acc;
}, {}) as { }, {});
number: string;
name: string;
arch: string;
os: string;
};
} }
async getProcesses () { async getProcesses () {
@@ -1519,15 +1423,6 @@ export class MySQLClient extends AntaresCore {
time: row.TIME, time: row.TIME,
state: row.STATE, state: row.STATE,
info: row.INFO info: row.INFO
} as {
id: number;
user: string;
host: string;
db: string;
command: string;
time: number;
state: string;
info: string;
}; };
}); });
} }
@@ -1616,7 +1511,7 @@ export class MySQLClient extends AntaresCore {
} }
async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) { async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) {
this._logger({ cUid: this._cUid, sql }); if (process.env.NODE_ENV === 'development') this._logger(sql);
args = { args = {
nest: false, nest: false,
@@ -1668,8 +1563,7 @@ export class MySQLClient extends AntaresCore {
let timeStop: Date; let timeStop: Date;
let keysArr: antares.QueryForeign[] = []; let keysArr: antares.QueryForeign[] = [];
// eslint-disable-next-line @typescript-eslint/no-explicit-any const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => {
const { rows, report, fields, keys, duration }: any = await new Promise((resolve, reject) => {
connection.query({ sql: query, nestTables }).then(async ([response, fields]) => { connection.query({ sql: query, nestTables }).then(async ([response, fields]) => {
timeStop = new Date(); timeStop = new Date();
const queryResult = response; const queryResult = response;
@@ -1679,7 +1573,7 @@ export class MySQLClient extends AntaresCore {
if (!field || Array.isArray(field)) if (!field || Array.isArray(field))
return undefined; return undefined;
const type = this._getType(field as undefined); const type = this._getType(field);
return { return {
name: field.orgName, name: field.orgName,

View File

@@ -1,11 +1,12 @@
import * as antares from 'common/interfaces/antares'; import * as antares from 'common/interfaces/antares';
import * as mysql from 'mysql2';
import { builtinsTypes } from 'pg-types';
import * as pg from 'pg'; import * as pg from 'pg';
import * as pgAst from 'pgsql-ast-parser'; import * as pgAst from 'pgsql-ast-parser';
import { AntaresCore } from '../AntaresCore'; import { AntaresCore } from '../AntaresCore';
import dataTypes from 'common/data-types/postgresql'; import * as dataTypes from 'common/data-types/postgresql';
import SSH2Promise = require('ssh2-promise'); import SSH2Promise = require('ssh2-promise');
import SSHConfig from 'ssh2-promise/lib/sshConfig'; import SSHConfig from 'ssh2-promise/lib/sshConfig';
import { ConnectionOptions } from 'tls';
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
function pgToString (value: any) { function pgToString (value: any) {
@@ -18,74 +19,10 @@ pg.types.setTypeParser(1114, pgToString); // timestamp
pg.types.setTypeParser(1184, pgToString); // timestamptz pg.types.setTypeParser(1184, pgToString); // timestamptz
pg.types.setTypeParser(1266, pgToString); // timetz pg.types.setTypeParser(1266, pgToString); // timetz
// from pg-types
type builtinsTypes =
'BOOL' |
'BYTEA' |
'CHAR' |
'INT8' |
'INT2' |
'INT4' |
'REGPROC' |
'TEXT' |
'OID' |
'TID' |
'XID' |
'CID' |
'JSON' |
'XML' |
'PG_NODE_TREE' |
'SMGR' |
'PATH' |
'POLYGON' |
'CIDR' |
'FLOAT4' |
'FLOAT8' |
'ABSTIME' |
'RELTIME' |
'TINTERVAL' |
'CIRCLE' |
'MACADDR8' |
'MONEY' |
'MACADDR' |
'INET' |
'ACLITEM' |
'BPCHAR' |
'VARCHAR' |
'DATE' |
'TIME' |
'TIMESTAMP' |
'TIMESTAMPTZ' |
'INTERVAL' |
'TIMETZ' |
'BIT' |
'VARBIT' |
'NUMERIC' |
'REFCURSOR' |
'REGPROCEDURE' |
'REGOPER' |
'REGOPERATOR' |
'REGCLASS' |
'REGTYPE' |
'UUID' |
'TXID_SNAPSHOT' |
'PG_LSN' |
'PG_NDISTINCT' |
'PG_DEPENDENCIES' |
'TSVECTOR' |
'TSQUERY' |
'GTSVECTOR' |
'REGCONFIG' |
'REGDICTIONARY' |
'JSONB' |
'REGNAMESPACE' |
'REGROLE';
export class PostgreSQLClient extends AntaresCore { export class PostgreSQLClient extends AntaresCore {
private _schema?: string; private _schema?: string;
private _runningConnections: Map<string, number>; private _runningConnections: Map<string, number>;
private _connectionsToCommit: Map<string, pg.Client | pg.PoolClient>; private _connectionsToCommit: Map<string, pg.Client | pg.PoolClient>;
private _keepaliveTimer: NodeJS.Timer;
private _keepaliveMs: number;
protected _connection?: pg.Client | pg.Pool; protected _connection?: pg.Client | pg.Pool;
private types: {[key: string]: string} = {}; private types: {[key: string]: string} = {};
private _arrayTypes: {[key: string]: string} = { private _arrayTypes: {[key: string]: string} = {
@@ -98,7 +35,7 @@ export class PostgreSQLClient extends AntaresCore {
_varchar: 'CHARACTER VARYING' _varchar: 'CHARACTER VARYING'
} }
_params: pg.ClientConfig & {schema: string; ssl?: ConnectionOptions; ssh?: SSHConfig; readonly: boolean}; _params: pg.ClientConfig & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean};
constructor (args: antares.ClientParams) { constructor (args: antares.ClientParams) {
super(args); super(args);
@@ -106,7 +43,6 @@ export class PostgreSQLClient extends AntaresCore {
this._schema = null; this._schema = null;
this._runningConnections = new Map(); this._runningConnections = new Map();
this._connectionsToCommit = new Map(); this._connectionsToCommit = new Map();
this._keepaliveMs = 10*60*1000;
for (const key in pg.types.builtins) { for (const key in pg.types.builtins) {
const builtinKey = key as builtinsTypes; const builtinKey = key as builtinsTypes;
@@ -154,9 +90,9 @@ export class PostgreSQLClient extends AntaresCore {
host: this._params.host, host: this._params.host,
port: this._params.port, port: this._params.port,
user: this._params.user, user: this._params.user,
database: 'postgres' as string, database: undefined as string | undefined,
password: this._params.password, password: this._params.password,
ssl: null as ConnectionOptions ssl: null as mysql.SslOptions
}; };
if (this._params.database?.length) dbConfig.database = this._params.database; if (this._params.database?.length) dbConfig.database = this._params.database;
@@ -165,11 +101,7 @@ export class PostgreSQLClient extends AntaresCore {
if (this._params.ssh) { if (this._params.ssh) {
try { try {
this._ssh = new SSH2Promise({ this._ssh = new SSH2Promise({ ...this._params.ssh });
...this._params.ssh,
keepaliveInterval: 30*60*1000,
debug: process.env.NODE_ENV !== 'production' ? (s) => console.log(s) : null
});
const tunnel = await this._ssh.addTunnel({ const tunnel = await this._ssh.addTunnel({
remoteAddr: this._params.host, remoteAddr: this._params.host,
@@ -225,26 +157,14 @@ export class PostgreSQLClient extends AntaresCore {
}); });
} }
this._keepaliveTimer = setInterval(async () => {
await this.keepAlive();
}, this._keepaliveMs);
return connection; return connection;
} }
destroy () { destroy () {
this._connection.end(); this._connection.end();
clearInterval(this._keepaliveTimer);
this._keepaliveTimer = undefined;
if (this._ssh) this._ssh.close(); if (this._ssh) this._ssh.close();
} }
private async keepAlive () {
const connection = await this._connection.connect() as pg.PoolClient;
await connection.query('SELECT 1+1');
connection.release();
}
use (schema: string, connection?: pg.Client | pg.PoolClient) { use (schema: string, connection?: pg.Client | pg.PoolClient) {
this._schema = schema; this._schema = schema;
@@ -262,18 +182,6 @@ export class PostgreSQLClient extends AntaresCore {
return []; return [];
} }
async getDatabases () {
const { rows } = await this.raw('SELECT datname FROM pg_database WHERE datistemplate = false');
if (rows) {
return rows.reduce((acc, cur) => {
acc.push(cur.datname);
return acc;
}, [] as string[]);
}
else
return [];
}
async getStructure (schemas: Set<string>) { async getStructure (schemas: Set<string>) {
/* eslint-disable camelcase */ /* eslint-disable camelcase */
interface ShowTableResult { interface ShowTableResult {
@@ -571,7 +479,11 @@ export class PostgreSQLClient extends AntaresCore {
return { return {
name: row.constraint_name, name: row.constraint_name,
column: row.column_name, column: row.column_name,
type: row.constraint_type indexType: null as null,
type: row.constraint_type,
cardinality: null as null,
comment: '',
indexComment: ''
}; };
}); });
} }
@@ -594,142 +506,6 @@ export class PostgreSQLClient extends AntaresCore {
}, {} as {table: string; schema: string}[]); }, {} as {table: string; schema: string}[]);
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async getTableDll ({ schema, table }: { schema: string; table: string }) {
// const { rows } = await this.raw<antares.QueryResult<{'ddl'?: string}>>(`
// SELECT
// 'CREATE TABLE ' || relname || E'\n(\n' ||
// array_to_string(
// array_agg(' ' || column_name || ' ' || type || ' '|| not_null)
// , E',\n'
// ) || E'\n);\n' AS ddl
// FROM (
// SELECT
// a.attname AS column_name
// , pg_catalog.format_type(a.atttypid, a.atttypmod) AS type
// , CASE WHEN a.attnotnull THEN 'NOT NULL' ELSE 'NULL' END AS not_null
// , c.relname
// FROM pg_attribute a, pg_class c, pg_type t
// WHERE a.attnum > 0
// AND a.attrelid = c.oid
// AND a.atttypid = t.oid
// AND c.relname = '${table}'
// ORDER BY a.attnum
// ) AS tabledefinition
// GROUP BY relname
// `);
// if (rows.length)
// return rows[0].ddl;
// else return '';
/* 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.raw(`
SELECT *
FROM "information_schema"."columns"
WHERE "table_schema" = '${schema}'
AND "table_name" = '${table}'
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 = `"${schema}".${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.select('*')
.schema('information_schema')
.from('sequences')
.where({ sequence_schema: `= '${schema}'`, sequence_name: `= '${sequence}'` })
.run<SequenceRecord>();
if (rows.length) {
createSql += `CREATE SEQUENCE "${schema}"."${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`;
}
}
// Table create
createSql += `\nCREATE TABLE "${schema}"."${table}"(
${columnsSql.join(',\n ')}
);\n`;
// Table indexes
createSql += '\n';
const { rows: indexes } = await this.select('*')
.schema('pg_catalog')
.from('pg_indexes')
.where({ schemaname: `= '${schema}'`, tablename: `= '${table}'` })
.run<{indexdef: string}>();
for (const index of indexes)
createSql += `${index.indexdef};\n`;
return createSql;
}
async getKeyUsage ({ schema, table }: { schema: string; table: string }) { async getKeyUsage ({ schema, table }: { schema: string; table: string }) {
/* eslint-disable camelcase */ /* eslint-disable camelcase */
interface KeyResult { interface KeyResult {
@@ -1538,7 +1314,7 @@ export class PostgreSQLClient extends AntaresCore {
} }
async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) { async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) {
this._logger({ cUid: this._cUid, sql }); if (process.env.NODE_ENV === 'development') this._logger(sql);
args = { args = {
nest: false, nest: false,
@@ -1579,7 +1355,7 @@ export class PostgreSQLClient extends AntaresCore {
if (args.tabUid && isPool) if (args.tabUid && isPool)
this._runningConnections.set(args.tabUid, (connection as pg.PoolClient & { processID: number }).processID); this._runningConnections.set(args.tabUid, (connection as pg.PoolClient & { processID: number }).processID);
if (args.schema) if (args.schema && args.schema !== 'public')
await this.use(args.schema, connection); await this.use(args.schema, connection);
for (const query of queries) { for (const query of queries) {
@@ -1589,8 +1365,7 @@ export class PostgreSQLClient extends AntaresCore {
let timeStop: Date; let timeStop: Date;
let keysArr: antares.QueryForeign[] = []; let keysArr: antares.QueryForeign[] = [];
// eslint-disable-next-line @typescript-eslint/no-explicit-any const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => {
const { rows, report, fields, keys, duration }: any = await new Promise((resolve, reject) => {
(async () => { (async () => {
try { try {
const res = await connection.query({ rowMode: args.nest ? 'array' : null, text: query }); const res = await connection.query({ rowMode: args.nest ? 'array' : null, text: query });

View File

@@ -1,7 +1,7 @@
import * as antares from 'common/interfaces/antares'; import * as antares from 'common/interfaces/antares';
import * as sqlite from 'better-sqlite3'; import * as sqlite from 'better-sqlite3';
import { AntaresCore } from '../AntaresCore'; import { AntaresCore } from '../AntaresCore';
import dataTypes from 'common/data-types/sqlite'; import * as dataTypes from 'common/data-types/sqlite';
import { NUMBER, FLOAT, TIME, DATETIME } from 'common/fieldTypes'; import { NUMBER, FLOAT, TIME, DATETIME } from 'common/fieldTypes';
export class SQLiteClient extends AntaresCore { export class SQLiteClient extends AntaresCore {
@@ -217,7 +217,11 @@ export class SQLiteClient extends AntaresCore {
remappedIndexes.push({ remappedIndexes.push({
name: 'PRIMARY', name: 'PRIMARY',
column: key.name, column: key.name,
type: 'PRIMARY' indexType: null as never,
type: 'PRIMARY',
cardinality: null as never,
comment: '',
indexComment: ''
}); });
} }
@@ -344,20 +348,6 @@ export class SQLiteClient extends AntaresCore {
const tmpName = `Antares_${table}_tmp`; const tmpName = `Antares_${table}_tmp`;
await this.raw(`CREATE TABLE "${tmpName}" AS SELECT * FROM "${table}"`); await this.raw(`CREATE TABLE "${tmpName}" AS SELECT * FROM "${table}"`);
// Get table triggers before drop
const { rows: triggers } = await this.raw(`SELECT * FROM "${schema}".sqlite_master WHERE type='trigger' AND tbl_name = '${table}'`);
const remappedTriggers = triggers.map((row) => {
return {
schema,
sql: row.sql.match(/(BEGIN|begin)(.*)(END|end)/gs)[0],
name: row.name,
table: row.sql.match(/(?<=ON `).*?(?=`)/gs)[0],
activation: row.sql.match(/(BEFORE|AFTER)/gs)[0],
event: row.sql.match(/(INSERT|UPDATE|DELETE)/gs)[0]
};
});
await this.dropTable(params); await this.dropTable(params);
const createTableParams = { const createTableParams = {
@@ -390,11 +380,6 @@ export class SQLiteClient extends AntaresCore {
await this.raw(`INSERT INTO "${createTableParams.options.name}" (${insertFields.join(',')}) SELECT ${selectFields.join(',')} FROM "${tmpName}"`); 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: schema, table: tmpName });
// Recreates triggers
for (const trigger of remappedTriggers)
await this.createTrigger(trigger);
await this.raw('PRAGMA foreign_keys = 1'); await this.raw('PRAGMA foreign_keys = 1');
await this.raw('COMMIT'); await this.raw('COMMIT');
} }
@@ -425,7 +410,7 @@ export class SQLiteClient extends AntaresCore {
return results.rows.map(row => { return results.rows.map(row => {
return { return {
sql: row.sql.match(/(?<=(AS|as)( |\n)).*?$/gs)[0], sql: row.sql.match(/(?<=AS ).*?$/gs)[0],
name: view name: view
}; };
})[0]; })[0];
@@ -601,7 +586,7 @@ export class SQLiteClient extends AntaresCore {
} }
async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) { async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) {
this._logger({ cUid: this._cUid, sql });// TODO: replace BLOB content with a placeholder if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder
args = { args = {
nest: false, nest: false,
@@ -643,8 +628,7 @@ export class SQLiteClient extends AntaresCore {
let timeStop; let timeStop;
const keysArr: antares.QueryForeign[] = []; const keysArr: antares.QueryForeign[] = [];
// eslint-disable-next-line @typescript-eslint/no-explicit-any const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => {
const { rows, report, fields, keys, duration }: any = await new Promise((resolve, reject) => {
(async () => { (async () => {
let queryRunResult: sqlite.RunResult; let queryRunResult: sqlite.RunResult;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -691,8 +675,8 @@ export class SQLiteClient extends AntaresCore {
if ([...TIME, ...DATETIME].includes(parsedType)) { if ([...TIME, ...DATETIME].includes(parsedType)) {
const firstNotNull = queryAllResult.find(res => res[field.name] !== null); const firstNotNull = queryAllResult.find(res => res[field.name] !== null);
if (firstNotNull && String(firstNotNull[field.name]).includes('.')) if (firstNotNull && firstNotNull[field.name].includes('.'))
length = String(firstNotNull[field.name]).split('.').pop().length; length = firstNotNull[field.name].split('.').pop().length;
} }
return { return {

View File

@@ -1,8 +1,12 @@
import * as exporter from 'common/interfaces/exporter'; import * as exporter from 'common/interfaces/exporter';
import * as mysql from 'mysql2/promise'; import * as mysql from 'mysql2/promise';
import { SqlExporter } from './SqlExporter'; import { SqlExporter } from './SqlExporter';
import { BLOB, BIT, DATE, DATETIME, FLOAT, SPATIAL, IS_MULTI_SPATIAL, NUMBER } from 'common/fieldTypes';
import hexToBinary from 'common/libs/hexToBinary';
import { getArrayDepth } from 'common/libs/getArrayDepth';
import * as moment from 'moment';
import { lineString, point, polygon } from '@turf/helpers';
import { MySQLClient } from '../../clients/MySQLClient'; import { MySQLClient } from '../../clients/MySQLClient';
import { valueToSqlString } from 'common/libs/sqlUtils';
export default class MysqlExporter extends SqlExporter { export default class MysqlExporter extends SqlExporter {
protected _client: MySQLClient; protected _client: MySQLClient;
@@ -118,7 +122,54 @@ ${footer}
const column = notGeneratedColumns[i]; const column = notGeneratedColumns[i];
const val = row[column.name]; const val = row[column.name];
sqlInsertString += valueToSqlString({ val, client: 'mysql', field: column }); 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'))}'`;
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) if (parseInt(i) !== notGeneratedColumns.length - 1)
sqlInsertString += ', '; sqlInsertString += ', ';
@@ -184,9 +235,9 @@ CREATE TABLE \`${view.Name}\`(
const { rows: triggers } = await this._client.raw( const { rows: triggers } = await this._client.raw(
`SHOW TRIGGERS FROM \`${this.schemaName}\`` `SHOW TRIGGERS FROM \`${this.schemaName}\``
); );
// const generatedTables = this._tables const generatedTables = this._tables
// .filter(t => t.includeStructure) .filter(t => t.includeStructure)
// .map(t => t.table); .map(t => t.table);
let sqlString = ''; let sqlString = '';
@@ -200,7 +251,7 @@ CREATE TABLE \`${view.Name}\`(
sql_mode: sqlMode sql_mode: sqlMode
} = trigger; } = trigger;
// if (!generatedTables.includes(table)) continue;// Commented to avoid issues if export contains triggers without tables if (!generatedTables.includes(table)) continue;
const definer = this.getEscapedDefiner(trigger.Definer); const definer = this.getEscapedDefiner(trigger.Definer);
sqlString += '/*!50003 SET @OLD_SQL_MODE=@@SQL_MODE*/;;\n'; sqlString += '/*!50003 SET @OLD_SQL_MODE=@@SQL_MODE*/;;\n';
@@ -384,4 +435,17 @@ CREATE TABLE \`${view.Name}\`(
return `'${escapedVal}'`; 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,11 +1,13 @@
import * as antares from 'common/interfaces/antares'; import * as antares from 'common/interfaces/antares';
import * as exporter from 'common/interfaces/exporter'; import * as exporter from 'common/interfaces/exporter';
import { SqlExporter } from './SqlExporter'; import { SqlExporter } from './SqlExporter';
import { BLOB, BIT, DATE, DATETIME, FLOAT, NUMBER, TEXT_SEARCH } from 'common/fieldTypes';
import hexToBinary from 'common/libs/hexToBinary';
import * as moment from 'moment';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
import * as QueryStream from 'pg-query-stream'; import * as QueryStream from 'pg-query-stream';
import { PostgreSQLClient } from '../../clients/PostgreSQLClient'; import { PostgreSQLClient } from '../../clients/PostgreSQLClient';
import { valueToSqlString } from 'common/libs/sqlUtils';
export default class PostgreSQLExporter extends SqlExporter { export default class PostgreSQLExporter extends SqlExporter {
constructor (client: PostgreSQLClient, tables: exporter.TableParams[], options: exporter.ExportOptions) { constructor (client: PostgreSQLClient, tables: exporter.TableParams[], options: exporter.ExportOptions) {
@@ -221,7 +223,47 @@ SET row_security = off;\n\n\n`;
const column = columns[i]; const column = columns[i];
const val = row[column.name]; const val = row[column.name];
sqlInsertString += valueToSqlString({ val, client: 'pg', field: column }); 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'))}'`;
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) if (parseInt(i) !== columns.length - 1)
sqlInsertString += ', '; sqlInsertString += ', ';

View File

@@ -47,7 +47,7 @@ export default class MySQLImporter extends BaseImporter {
catch (error) { catch (error) {
this.emit('query-error', { this.emit('query-error', {
sql: query, sql: query,
message: error.sqlMessage || error.message, message: error.sqlMessage,
sqlSnippet: error.sql, sqlSnippet: error.sql,
time: new Date().getTime() time: new Date().getTime()
}); });

View File

@@ -1,6 +1,6 @@
import * as pg from 'pg'; import * as pg from 'pg';
import * as importer from 'common/interfaces/importer'; import * as importer from 'common/interfaces/importer';
import * as fs from 'fs/promises'; import fs from 'fs/promises';
import PostgreSQLParser from '../../parsers/PostgreSQLParser'; import PostgreSQLParser from '../../parsers/PostgreSQLParser';
import { BaseImporter } from '../BaseImporter'; import { BaseImporter } from '../BaseImporter';

View File

@@ -1,19 +1,15 @@
import { app, BrowserWindow, nativeImage, ipcMain } from 'electron'; import { app, BrowserWindow, /* session, */ nativeImage, Menu } from 'electron';
import * as path from 'path'; import * as path from 'path';
import * as Store from 'electron-store'; import * as Store from 'electron-store';
import * as windowStateKeeper from 'electron-window-state'; import * as windowStateKeeper from 'electron-window-state';
import * as remoteMain from '@electron/remote/main'; import * as remoteMain from '@electron/remote/main';
import ipcHandlers from './ipc-handlers'; import ipcHandlers from './ipc-handlers';
import { OsMenu, ShortcutRegister } from './libs/ShortcutRegister';
Store.initRenderer(); Store.initRenderer();
const settingsStore = new Store({ name: 'settings' });
const appTheme = settingsStore.get('application_theme');
const isDevelopment = process.env.NODE_ENV !== 'production'; const isDevelopment = process.env.NODE_ENV !== 'production';
const isMacOS = process.platform === 'darwin'; const isMacOS = process.platform === 'darwin';
const isLinux = process.platform === 'linux';
const isWindows = process.platform === 'win32';
const gotTheLock = app.requestSingleInstanceLock(); const gotTheLock = app.requestSingleInstanceLock();
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true'; process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
@@ -23,7 +19,7 @@ let mainWindow: BrowserWindow;
let mainWindowState: windowStateKeeper.State; let mainWindowState: windowStateKeeper.State;
async function createMainWindow () { async function createMainWindow () {
const icon = require('../renderer/images/logo-64.png'); const icon = require('../renderer/images/logo-32.png');
const window = new BrowserWindow({ const window = new BrowserWindow({
width: mainWindowState.width, width: mainWindowState.width,
height: mainWindowState.height, height: mainWindowState.height,
@@ -31,24 +27,16 @@ async function createMainWindow () {
y: mainWindowState.y, y: mainWindowState.y,
minWidth: 900, minWidth: 900,
minHeight: 550, minHeight: 550,
show: !isWindows,
title: 'Antares SQL', title: 'Antares SQL',
autoHideMenuBar: true,
icon: nativeImage.createFromDataURL(icon.default), icon: nativeImage.createFromDataURL(icon.default),
webPreferences: { webPreferences: {
nodeIntegration: true, nodeIntegration: true,
contextIsolation: false, contextIsolation: false,
devTools: isDevelopment,
spellcheck: false spellcheck: false
}, },
autoHideMenuBar: true, frame: false,
titleBarStyle: isLinux ? 'default' :'hidden', titleBarStyle: isMacOS ? 'hidden' : 'default',
titleBarOverlay: isWindows
? {
color: appTheme === 'dark' ? '#3f3f3f' : '#fff',
symbolColor: appTheme === 'dark' ? '#fff' : '#000',
height: 30
}
: false,
trafficLightPosition: isMacOS ? { x: 10, y: 8 } : undefined, trafficLightPosition: isMacOS ? { x: 10, y: 8 } : undefined,
backgroundColor: '#1d1d1d' backgroundColor: '#1d1d1d'
}); });
@@ -85,24 +73,10 @@ else {
// Initialize ipcHandlers // Initialize ipcHandlers
ipcHandlers(); ipcHandlers();
ipcMain.on('refresh-theme-settings', () => {
const appTheme = settingsStore.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 // quit application when all windows are closed
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
// on macOS it is common for applications to stay open until the user explicitly quits // 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 () => { app.on('activate', async () => {
@@ -121,11 +95,8 @@ else {
mainWindow = await createMainWindow(); mainWindow = await createMainWindow();
createAppMenu(); createAppMenu();
if (isWindows) if (isDevelopment)
mainWindow.show(); mainWindow.webContents.openDevTools();
// if (isDevelopment)
// mainWindow.webContents.openDevTools();
process.on('uncaughtException', error => { process.on('uncaughtException', error => {
mainWindow.webContents.send('unhandled-exception', error); mainWindow.webContents.send('unhandled-exception', error);
@@ -146,8 +117,10 @@ else {
} }
function createAppMenu () { function createAppMenu () {
const menuTemplate: OsMenu = { let menu: Electron.Menu = null;
darwin: [
if (isMacOS) {
menu = Menu.buildFromTemplate([
{ {
label: app.name, label: app.name,
submenu: [ submenu: [
@@ -172,14 +145,16 @@ function createAppMenu () {
{ {
role: 'editMenu' role: 'editMenu'
}, },
{
role: 'viewMenu'
},
{ {
role: 'windowMenu' role: 'windowMenu'
} }
] ]);
}; }
const shortCutRegister = ShortcutRegister.getInstance({ mainWindow, menuTemplate, mode: 'local' }); Menu.setApplicationMenu(menu);
shortCutRegister.init();
} }
function saveWindowState () { function saveWindowState () {

View File

@@ -7,7 +7,7 @@ import MysqlExporter from '../libs/exporters/sql/MysqlExporter';
import PostgreSQLExporter from '../libs/exporters/sql/PostgreSQLExporter'; import PostgreSQLExporter from '../libs/exporters/sql/PostgreSQLExporter';
let exporter: antares.Exporter; let exporter: antares.Exporter;
process.on('message', async ({ type, client, tables, options }: any) => { process.on('message', async ({ type, client, tables, options }) => {
if (type === 'init') { if (type === 'init') {
const connection = await ClientsFactory.getClient({ const connection = await ClientsFactory.getClient({
client: client.name, client: client.name,

View File

@@ -6,106 +6,72 @@ import { PostgreSQLClient } from '../libs/clients/PostgreSQLClient';
import { ClientsFactory } from '../libs/ClientsFactory'; import { ClientsFactory } from '../libs/ClientsFactory';
import MySQLImporter from '../libs/importers/sql/MySQLlImporter'; import MySQLImporter from '../libs/importers/sql/MySQLlImporter';
import PostgreSQLImporter from '../libs/importers/sql/PostgreSQLImporter'; import PostgreSQLImporter from '../libs/importers/sql/PostgreSQLImporter';
import SSHConfig from 'ssh2-promise/lib/sshConfig';
import { ImportOptions } from 'common/interfaces/importer';
let importer: antares.Importer; let importer: antares.Importer;
// eslint-disable-next-line @typescript-eslint/no-explicit-any process.on('message', async ({ type, dbConfig, options }) => {
process.on('message', async ({ type, dbConfig, options }: {
type: string;
dbConfig: 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 };
options: ImportOptions;
}) => {
if (type === 'init') { if (type === 'init') {
try { const connection = await ClientsFactory.getClient({
const connection = await ClientsFactory.getClient({ client: options.type,
client: options.type, params: {
params: { ...dbConfig,
...dbConfig, schema: options.schema
schema: options.schema },
}, poolSize: 1
poolSize: 1 }) as MySQLClient | PostgreSQLClient;
}) as MySQLClient | PostgreSQLClient;
const pool = await connection.getConnectionPool(); const pool = await connection.getConnectionPool();
switch (options.type) { switch (options.type) {
case 'mysql': case 'mysql':
case 'maria': case 'maria':
importer = new MySQLImporter(pool as unknown as mysql.Pool, options); importer = new MySQLImporter(pool as unknown as mysql.Pool, options);
break; break;
case 'pg': case 'pg':
importer = new PostgreSQLImporter(pool as unknown as pg.PoolClient, options); importer = new PostgreSQLImporter(pool as unknown as pg.PoolClient, options);
break; break;
default: default:
process.send({
type: 'error',
payload: `"${options.type}" importer not aviable`
});
return;
}
importer.once('error', err => {
console.error(err);
process.send({ process.send({
type: 'error', type: 'error',
payload: err.toString() payload: `"${options.type}" importer not aviable`
}); });
}); return;
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();
} }
catch (err) {
importer.once('error', err => {
console.error(err); console.error(err);
process.send({ process.send({
type: 'error', type: 'error',
payload: err.toString() 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') else if (type === 'cancel')
importer.cancel(); importer.cancel();
}); });
process.on('uncaughtException', (err) => {
console.error(err);
process.send({
type: 'error',
payload: err.toString()
});
});
process.on('unhandledRejection', (err) => {
console.error(err);
process.send({
type: 'error',
payload: err.toString()
});
});

View File

@@ -2,7 +2,7 @@
<div id="wrapper" :class="[`theme-${applicationTheme}`, !disableBlur || 'no-blur']"> <div id="wrapper" :class="[`theme-${applicationTheme}`, !disableBlur || 'no-blur']">
<TheTitleBar /> <TheTitleBar />
<div id="window-content"> <div id="window-content">
<TheSettingBar @show-connections-modal="isAllConnectionsModal = true" /> <TheSettingBar />
<div id="main-content" class="container"> <div id="main-content" class="container">
<div class="columns col-gapless"> <div class="columns col-gapless">
<Workspace <Workspace
@@ -10,7 +10,7 @@
:key="connection.uid" :key="connection.uid"
:connection="connection" :connection="connection"
/> />
<div class="connection-panel-wrapper p-relative"> <div class="connection-panel-wrapper">
<WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" /> <WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" />
</div> </div>
</div> </div>
@@ -21,136 +21,103 @@
<BaseTextEditor class="d-none" value="" /> <BaseTextEditor class="d-none" value="" />
</div> </div>
</div> </div>
<ModalAllConnections
v-if="isAllConnectionsModal"
@close="isAllConnectionsModal = false"
/>
<ModalExportSchema
v-if="isExportSchemaModal"
@close="hideExportModal"
/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script>
import { defineAsyncComponent, onMounted, Ref, ref } from 'vue'; import { defineAsyncComponent } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import { useI18n } from 'vue-i18n';
import { Menu, getCurrentWindow } from '@electron/remote'; import { Menu, getCurrentWindow } from '@electron/remote';
import { useApplicationStore } from '@/stores/application'; import { useApplicationStore } from '@/stores/application';
import { useConnectionsStore } from '@/stores/connections'; import { useConnectionsStore } from '@/stores/connections';
import { useSchemaExportStore } from '@/stores/schemaExport';
import { useSettingsStore } from '@/stores/settings'; import { useSettingsStore } from '@/stores/settings';
import { useWorkspacesStore } from '@/stores/workspaces'; import { useWorkspacesStore } from '@/stores/workspaces';
import TheSettingBar from '@/components/TheSettingBar.vue'; import TheSettingBar from '@/components/TheSettingBar';
import ModalExportSchema from '@/components/ModalExportSchema.vue';
const { t } = useI18n(); export default {
name: 'App',
components: {
TheTitleBar: defineAsyncComponent(() => import(/* webpackChunkName: "TheTitleBar" */'@/components/TheTitleBar')),
TheSettingBar,
TheFooter: defineAsyncComponent(() => import(/* webpackChunkName: "TheFooter" */'@/components/TheFooter')),
TheNotificationsBoard: defineAsyncComponent(() => import(/* webpackChunkName: "TheNotificationsBoard" */'@/components/TheNotificationsBoard')),
Workspace: defineAsyncComponent(() => import(/* webpackChunkName: "Workspace" */'@/components/Workspace')),
WorkspaceAddConnectionPanel: defineAsyncComponent(() => import(/* webpackChunkName: "WorkspaceAddConnectionPanel" */'@/components/WorkspaceAddConnectionPanel')),
ModalSettings: defineAsyncComponent(() => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings')),
TheScratchpad: defineAsyncComponent(() => import(/* webpackChunkName: "TheScratchpad" */'@/components/TheScratchpad')),
BaseTextEditor: defineAsyncComponent(() => import(/* webpackChunkName: "BaseTextEditor" */'@/components/BaseTextEditor'))
},
setup () {
const applicationStore = useApplicationStore();
const connectionsStore = useConnectionsStore();
const settingsStore = useSettingsStore();
const workspacesStore = useWorkspacesStore();
const TheTitleBar = defineAsyncComponent(() => import(/* webpackChunkName: "TheTitleBar" */'@/components/TheTitleBar.vue')); const {
const TheFooter = defineAsyncComponent(() => import(/* webpackChunkName: "TheFooter" */'@/components/TheFooter.vue')); isLoading,
const TheNotificationsBoard = defineAsyncComponent(() => import(/* webpackChunkName: "TheNotificationsBoard" */'@/components/TheNotificationsBoard.vue')); isSettingModal,
const Workspace = defineAsyncComponent(() => import(/* webpackChunkName: "Workspace" */'@/components/Workspace.vue')); isScratchpad
const WorkspaceAddConnectionPanel = defineAsyncComponent(() => import(/* webpackChunkName: "WorkspaceAddConnectionPanel" */'@/components/WorkspaceAddConnectionPanel.vue')); } = storeToRefs(applicationStore);
const ModalSettings = defineAsyncComponent(() => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings.vue')); const { connections } = storeToRefs(connectionsStore);
const ModalAllConnections = defineAsyncComponent(() => import(/* webpackChunkName: "ModalAllConnections" */'@/components/ModalAllConnections.vue')); const { applicationTheme, disableBlur } = storeToRefs(settingsStore);
const TheScratchpad = defineAsyncComponent(() => import(/* webpackChunkName: "TheScratchpad" */'@/components/TheScratchpad.vue')); const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const BaseTextEditor = defineAsyncComponent(() => import(/* webpackChunkName: "BaseTextEditor" */'@/components/BaseTextEditor.vue'));
const applicationStore = useApplicationStore(); const { checkVersionUpdate } = applicationStore;
const connectionsStore = useConnectionsStore();
const settingsStore = useSettingsStore();
const workspacesStore = useWorkspacesStore();
const { return {
isSettingModal, isLoading,
isScratchpad isSettingModal,
} = storeToRefs(applicationStore); isScratchpad,
const { connections } = storeToRefs(connectionsStore); checkVersionUpdate,
const { applicationTheme, disableBlur } = storeToRefs(settingsStore); connections,
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore); applicationTheme,
disableBlur,
selectedWorkspace
};
},
mounted () {
ipcRenderer.send('check-for-updates');
this.checkVersionUpdate();
const { checkVersionUpdate } = applicationStore; const InputMenu = Menu.buildFromTemplate([
const { changeApplicationTheme } = settingsStore; {
label: this.$t('word.cut'),
const schemaExportStore = useSchemaExportStore(); role: 'cut'
const { hideExportModal } = schemaExportStore; },
const { isExportModal: isExportSchemaModal } = storeToRefs(schemaExportStore); {
label: this.$t('word.copy'),
const isAllConnectionsModal: Ref<boolean> = ref(false); role: 'copy'
},
document.addEventListener('DOMContentLoaded', () => { {
setTimeout(() => { label: this.$t('word.paste'),
changeApplicationTheme(applicationTheme.value);// Forces persistentStore to save on file and mail process role: 'paste'
}, 1000); },
}); {
type: 'separator'
onMounted(() => { },
ipcRenderer.on('open-all-connections', () => { {
isAllConnectionsModal.value = true; label: this.$t('message.selectAll'),
}); role: 'selectall'
ipcRenderer.on('open-scratchpad', () => {
isScratchpad.value = true;
});
ipcRenderer.on('open-settings', () => {
isSettingModal.value = true;
});
ipcRenderer.on('create-connection', () => {
workspacesStore.selectWorkspace('NEW');
});
ipcRenderer.send('check-for-updates');
checkVersionUpdate();
const InputMenu = Menu.buildFromTemplate([
{
label: t('general.cut'),
role: 'cut'
},
{
label: t('general.copy'),
role: 'copy'
},
{
label: t('general.paste'),
role: 'paste'
},
{
type: 'separator'
},
{
label: t('general.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;
} }
node = node.parentNode; ]);
}
});
document.addEventListener('keydown', e => { document.body.addEventListener('contextmenu', (e) => {
if (e.altKey && e.key === 'Alt') { // Prevent Alt key to trigger hidden shortcut menu
e.preventDefault(); 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;
}
});
}
};
</script> </script>
<style lang="scss"> <style lang="scss">
@@ -180,14 +147,14 @@ onMounted(() => {
height: calc(100vh - #{$footer-height}); height: calc(100vh - #{$footer-height});
} }
.connection-panel-wrapper { .connection-panel-wrapper{
height: calc(100vh - #{$excluding-size}); height: calc(100vh - #{$excluding-size});
width: 100%; width: 100%;
padding-top: 10vh; padding-top: 15vh;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: flex-start; align-items: flex-start;
overflow: auto; overflow: auto;
} }
} }
</style> </style>

View File

@@ -3,7 +3,7 @@
<Teleport to="#window-content"> <Teleport to="#window-content">
<div class="modal active" :class="modalSizeClass"> <div class="modal active" :class="modalSizeClass">
<a class="modal-overlay" @click="hideModal" /> <a class="modal-overlay" @click="hideModal" />
<div ref="trapRef" class="modal-container"> <div class="modal-container">
<div v-if="hasHeader" class="modal-header pl-2"> <div v-if="hasHeader" class="modal-header pl-2">
<div class="modal-title h6"> <div class="modal-title h6">
<slot name="header" /> <slot name="header" />
@@ -31,13 +31,13 @@
class="btn btn-primary mr-2" class="btn btn-primary mr-2"
@click.stop="confirmModal" @click.stop="confirmModal"
> >
{{ confirmText || t('general.confirm') }} {{ confirmText || $t('word.confirm') }}
</button> </button>
<button <button
class="btn btn-link" class="btn btn-link"
@click="hideModal" @click="hideModal"
> >
{{ cancelText || t('general.cancel') }} {{ cancelText || $t('word.cancel') }}
</button> </button>
</div> </div>
</div> </div>
@@ -46,72 +46,65 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script>
import { useFocusTrap } from '@/composables/useFocusTrap'; export default {
import { computed, onBeforeUnmount, PropType, useSlots } from 'vue'; name: 'BaseConfirmModal',
import { useI18n } from 'vue-i18n'; props: {
size: {
type: String,
validator: prop => ['small', 'medium', '400', 'large'].includes(prop),
default: 'small'
},
hideFooter: {
type: Boolean,
default: false
},
confirmText: String,
cancelText: String
},
emits: ['confirm', 'hide'],
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);
},
beforeUnmount () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
confirmModal () {
this.$emit('confirm');
this.hideModal();
},
const { t } = useI18n(); hideModal () {
this.$emit('hide');
const props = defineProps({ },
size: { onKey (e) {
type: String as PropType<'small' | 'medium' | '400' | 'large'>, e.stopPropagation();
validator: (prop: string) => ['small', 'medium', '400', 'large'].includes(prop), if (e.key === 'Escape')
default: 'small' this.hideModal();
}, }
hideFooter: {
type: Boolean,
default: false
},
confirmText: String,
cancelText: String,
disableAutofocus: {
type: Boolean,
default: false
},
closeOnConfirm: {
type: Boolean,
default: true
} }
});
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');
if (props.closeOnConfirm) 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> </script>
<style scoped> <style scoped>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="context" :class="[{ 'bottom': isBottom }, { 'right': isRight }]"> <div class="context" :class="{'bottom': isBottom}">
<a <a
class="context-overlay" class="context-overlay"
@click="close" @click="close"
@@ -15,173 +15,153 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script>
import { computed, onBeforeUnmount, onMounted, Ref, ref } from 'vue'; export default {
name: 'BaseContextMenu',
props: {
contextEvent: MouseEvent
},
emits: ['close-context'],
data () {
return {
contextSize: null,
isBottom: false
};
},
computed: {
position () {
let topCord = 0;
let leftCord = 0;
const contextContent: Ref<HTMLDivElement> = ref(null); if (this.contextEvent) {
const contextSize: Ref<{height: number; width: number; subHeight?: number; subWidth?: number}> = ref(null); const { clientY, clientX } = this.contextEvent;
const isBottom: Ref<boolean> = ref(false); topCord = `${clientY + 2}px`;
const isRight: Ref<boolean> = ref(false); leftCord = `${clientX + 5}px`;
const props = defineProps<{ contextEvent: MouseEvent }>();
const emit = defineEmits(['close-context']);
const position = computed(() => { if (this.contextSize) {
let topCord = '0px'; if (clientY + (this.contextSize.height < 200 ? 200 : this.contextSize.height) + 5 >= window.innerHeight) {
let leftCord = '0px'; topCord = `${clientY + 3 - this.contextSize.height}px`;
this.isBottom = true;
}
if (props.contextEvent) { if (clientX + this.contextSize.width + 5 >= window.innerWidth)
const { clientY, clientX } = props.contextEvent; leftCord = `${clientX - this.contextSize.width}px`;
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 (clientY + contextSize.value.subHeight + contextSize.value.height >= window.innerHeight) return {
isBottom.value = true; top: topCord,
left: leftCord
if (clientX + contextSize.value.width + 5 >= window.innerWidth) { };
leftCord = `${clientX - contextSize.value.width}px`; }
isRight.value = true; },
} created () {
window.addEventListener('keydown', this.onKey);
if (clientX + contextSize.value.subWidth + contextSize.value.width >= window.innerWidth) },
isRight.value = true; mounted () {
if (this.$refs.contextContent)
this.contextSize = this.$refs.contextContent.getBoundingClientRect();
},
beforeUnmount () {
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();
const submenus = contextContent.value.querySelectorAll<HTMLDivElement>('.context-submenu');
for (const submenu of submenus) {
const submenuSize = submenu.getBoundingClientRect();
if (!contextSize.value.subHeight || submenuSize.height > contextSize.value.subHeight)
contextSize.value.subHeight = submenuSize.height;
if (!contextSize.value.subWidth || submenuSize.width > contextSize.value.subWidth)
contextSize.value.subWidth = submenuSize.width;
}
}
});
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKey);
});
</script> </script>
<style lang="scss"> <style lang="scss">
.context { .context {
display: flex; display: flex;
font-size: 16px; font-size: 16px;
z-index: 400; z-index: 400;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
overflow: hidden; overflow: hidden;
position: fixed; position: fixed;
height: 100vh; height: 100vh;
right: 0; right: 0;
top: 0; top: 0;
left: 0; left: 0;
bottom: 0; bottom: 0;
&:not(.bottom) .context-submenu { &:not(.bottom) .context-submenu {
top: -0.2rem; top: -0.2rem;
} }
&.bottom .context-submenu { &.bottom .context-submenu {
bottom: -0.2rem; bottom: -0.2rem;
} }
&:not(.right) .context-submenu { .context-container {
left: 100%; min-width: 100px;
} z-index: 10;
padding: 0;
background: #1d1d1d;
border-radius: $border-radius;
border: 1px solid $bg-color-light-dark;
display: flex;
flex-direction: column;
position: absolute;
pointer-events: initial;
&.right .context-submenu { .context-element {
right: 100%;
}
.context-container {
min-width: 100px;
z-index: 10;
padding: 0;
background: #1d1d1d;
border-radius: $border-radius;
border: 1px solid $bg-color-light-dark;
display: flex; display: flex;
flex-direction: column; align-items: center;
position: absolute; margin: 0.2rem;
pointer-events: initial; padding: 0.1rem 0.3rem;
border-radius: $border-radius;
cursor: pointer;
justify-content: space-between;
position: relative;
white-space: nowrap;
.context-element { .context-submenu {
display: flex; border-radius: $border-radius;
align-items: center; border: 1px solid $bg-color-light-dark;
margin: 0.2rem; opacity: 0;
padding: 0.1rem 0.3rem; visibility: hidden;
border-radius: $border-radius; transition: opacity 0.2s;
cursor: pointer; position: absolute;
justify-content: space-between; left: 100%;
position: relative; min-width: 100px;
white-space: nowrap; background: #1d1d1d;
.context-submenu {
border-radius: $border-radius;
border: 1px solid $bg-color-light-dark;
opacity: 0;
visibility: hidden;
transition: opacity 0.2s;
position: absolute;
min-width: 100px;
background: #1d1d1d;
}
&:hover {
.context-submenu {
display: block;
visibility: visible;
opacity: 1;
}
}
} }
}
.context-overlay { &:hover {
background: transparent; .context-submenu {
bottom: 0; display: block;
cursor: default; visibility: visible;
display: block; opacity: 1;
left: 0; }
position: absolute; }
right: 0; }
top: 0; }
}
.context-overlay {
background: transparent;
bottom: 0;
cursor: default;
display: block;
left: 0;
position: absolute;
right: 0;
top: 0;
}
} }
.disabled { .disabled {
pointer-events: none; pointer-events: none;
filter: grayscale(100%); filter: grayscale(100%);
opacity: 0.5; opacity: 0.5;
} }
</style> </style>

View File

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

View File

@@ -1,93 +1,95 @@
<template> <template>
<div id="map" class="map" /> <div id="map" class="map" />
</template> </template>
<script>
<script setup lang="ts"> import L from 'leaflet';
import { onMounted, PropType, Ref, ref } from 'vue';
import * as L from 'leaflet';
import { import {
point, point,
lineString, lineString,
polygon polygon
} from '@turf/helpers'; } from '@turf/helpers';
import { GeoJsonObject } from 'geojson';
import { getArrayDepth } from 'common/libs/getArrayDepth'; import { getArrayDepth } from 'common/libs/getArrayDepth';
interface Coordinates { x: number; y: number } export default {
name: 'BaseMap',
const props = defineProps({ props: {
points: [Object, Array] as PropType<Coordinates | Coordinates[]>, points: [Object, Array],
isMultiSpatial: Boolean isMultiSpatial: Boolean
}); },
const map: Ref<L.Map> = ref(null); data () {
const markers: Ref<GeoJsonObject | GeoJsonObject[]> = ref(null); return {
const center: Ref<[number, number]> = ref(null); map: null,
markers: [],
const getMarkers = (points: Coordinates) => { center: null
if (Array.isArray(points)) { };
if (getArrayDepth(points) === 1) },
return lineString(points.reduce((acc, curr) => [...acc, [curr.x, curr.y]], [])); mounted () {
else if (this.isMultiSpatial) {
return polygon(points.map(arr => arr.reduce((acc: Coordinates[], curr: Coordinates) => [...acc, [curr.x, curr.y]], []))); for (const element of this.points)
} this.markers.push(this.getMarkers(element));
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); else {
this.markers = this.getMarkers(this.points);
const southWest = L.latLng(-90, -180); if (!Array.isArray(this.points))
const northEast = L.latLng(90, 180); this.center = [this.points.y, this.points.x];
const bounds = L.latLngBounds(southWest, northEast); }
map.value.setMaxBounds(bounds);
if (!center.value) map.value.fitBounds(geoJsonObj.getBounds()); this.map = L.map('map', {
center: this.center || [0, 0],
zoom: 15,
minZoom: 1,
attributionControl: false
});
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { L.control.attribution({ prefix: '<b>Leaflet</b>' }).addTo(this.map);
attribution: '&copy; <b>OpenStreetMap</b>'
}).addTo(map.value); const geoJsonObj = L.geoJSON(this.markers, {
}); style: function () {
return {
weight: 2,
fillColor: '#ff7800',
color: '#ff7800',
opacity: 0.8,
fillOpacity: 0.4
};
},
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, {
radius: 7,
weight: 2,
fillColor: '#ff7800',
color: '#ff7800',
opacity: 0.8,
fillOpacity: 0.4
});
}
}).addTo(this.map);
const southWest = L.latLng(-90, -180);
const northEast = L.latLng(90, 180);
const bounds = L.latLngBounds(southWest, northEast);
this.map.setMaxBounds(bounds);
if (!this.center) this.map.fitBounds(geoJsonObj.getBounds());
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <b>OpenStreetMap</b>'
}).addTo(this.map);
},
methods: {
getMarkers (points) {
if (Array.isArray(points)) {
if (getArrayDepth(points) === 1)
return lineString(points.reduce((acc, curr) => [...acc, [curr.x, curr.y]], []));
else
return polygon(points.map(arr => arr.reduce((acc, curr) => [...acc, [curr.x, curr.y]], [])));
}
else
return point([points.x, points.y]);
}
}
};
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@@ -14,58 +14,64 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script>
import { computed, ref } from 'vue'; export default {
name: 'BaseNotification',
const props = defineProps({ props: {
message: { message: {
type: String, type: String,
default: '' default: ''
},
status: {
type: String,
default: ''
}
}, },
status: { emits: ['close'],
type: String, data () {
default: '' 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> </script>
<style scoped> <style scoped>
.toast { .toast {
display: flex; display: flex;

View File

@@ -37,8 +37,6 @@
v-if="isOpen" v-if="isOpen"
ref="optionList" ref="optionList"
:class="`select__list-wrapper ${dropdownClass ? dropdownClass : '' }`" :class="`select__list-wrapper ${dropdownClass ? dropdownClass : '' }`"
@mousedown="isMouseDown = true"
@mouseup="handleMouseUpEvent()"
> >
<ul class="select__list" @mousedown.prevent> <ul class="select__list" @mousedown.prevent>
<li <li
@@ -132,18 +130,13 @@ export default defineComponent({
disabled: { disabled: {
type: Boolean, type: Boolean,
default: false default: false
},
maxVisibleOptions: {
type: Number,
default: 100
} }
}, },
emits: ['select', 'open', 'close', 'update:modelValue', 'change', 'blur'], emits: ['select', 'open', 'close', 'update:modelValue', 'change', 'blur'],
setup (props, { emit }) { setup (props, { emit }) {
const hightlightedIndex = ref(0); const hightlightedIndex = ref(0);
const isOpen = ref(false); const isOpen = ref(false);
const isMouseDown = ref(false); const internalValue = ref(props.modelValue || props.value);
const internalValue = ref(props.modelValue !== false ? props.modelValue : props.value);
const el = ref(null); const el = ref(null);
const searchInput = ref(null); const searchInput = ref(null);
const optionList = ref(null); const optionList = ref(null);
@@ -151,14 +144,14 @@ export default defineComponent({
const searchText = ref(''); const searchText = ref('');
const getOptionValue = (opt) => _guess('optionTrackBy', opt); const getOptionValue = (opt) => _guess('optionTrackBy', opt);
const getOptionLabel = (opt) => _guess('optionLabel', opt) + ''; const getOptionLabel = (opt) => _guess('optionLabel', opt);
const getOptionDisabled = (opt) => _guess('optionDisabled', opt); const getOptionDisabled = (opt) => _guess('optionDisabled', opt);
const _guess = (name, item) => { const _guess = (name, item) => {
const prop = props[name]; const prop = props[name];
if (typeof prop === 'function') if (typeof prop === 'function')
return prop(item); return prop(item);
return item[prop] !== undefined ? item[prop] : item; return item[prop] || item;
}; };
const flattenOptions = computed(() => { const flattenOptions = computed(() => {
@@ -204,30 +197,11 @@ export default defineComponent({
}); });
const filteredOptions = computed(() => { const filteredOptions = computed(() => {
const searchTerms = (searchText.value || '').toLowerCase().trim(); const normalizedSearch = (searchText.value || '').toLowerCase().trim();
let options = searchTerms return normalizedSearch
? flattenOptions.value.filter(opt => opt.$type === 'group' || opt.label.trim().toLowerCase().indexOf(searchTerms) !== -1) ? flattenOptions.value.filter(opt => opt.$type === 'group' || opt.label.trim().toLowerCase().indexOf(normalizedSearch) !== -1)
: flattenOptions.value; : 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(() => { const searchInputStyle = computed(() => {
@@ -245,16 +219,8 @@ export default defineComponent({
hightlightedIndex.value = 0; hightlightedIndex.value = 0;
}); });
watch(() => props.modelValue, (val) => {
internalValue.value = val;
});
watch(() => props.value, (val) => {
internalValue.value = val;
});
const currentOptionLabel = computed(() => const currentOptionLabel = computed(() =>
flattenOptions.value.find(d => d.value === internalValue.value)?.label flattenOptions.value.find(d => d.value === props.modelValue)?.label
); );
const select = (opt) => { const select = (opt) => {
@@ -276,7 +242,7 @@ export default defineComponent({
const activate = () => { const activate = () => {
if (isOpen.value || props.disabled) return; if (isOpen.value || props.disabled) return;
isOpen.value = true; isOpen.value = true;
hightlightedIndex.value = filteredOptions.value.findIndex(el => el.value === internalValue.value) || 0; hightlightedIndex.value = flattenOptions.value.findIndex(el => el.value === internalValue.value) || 0;
if (props.searchable) if (props.searchable)
searchInput.value.focus(); searchInput.value.focus();
@@ -308,8 +274,6 @@ export default defineComponent({
}; };
const adjustListPosition = () => { const adjustListPosition = () => {
if (!optionList.value) return;
const element = el.value; const element = el.value;
let { left, top } = element.getBoundingClientRect(); let { left, top } = element.getBoundingClientRect();
const { left: offsetLeft = 0, top: offsetTop = 0 } = props.dropdownOffsets; const { left: offsetLeft = 0, top: offsetTop = 0 } = props.dropdownOffsets;
@@ -355,24 +319,12 @@ export default defineComponent({
}; };
const handleBlurEvent = () => { const handleBlurEvent = () => {
if (isMouseDown.value) return;
deactivate(); deactivate();
emit('blur'); emit('blur');
}; };
const handleMouseUpEvent = () => {
isMouseDown.value = false;
searchInput.value.focus();
};
const handleWheelEvent = (e) => {
if (!e.target.className.includes('select__')) deactivate();
};
onMounted(() => { onMounted(() => {
window.addEventListener('resize', adjustListPosition); window.addEventListener('resize', adjustListPosition);
window.addEventListener('wheel', handleWheelEvent);
nextTick(() => { nextTick(() => {
// fix position when the component is created and opened at the same time // fix position when the component is created and opened at the same time
if (isOpen.value) { if (isOpen.value) {
@@ -384,7 +336,6 @@ export default defineComponent({
}); });
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('resize', adjustListPosition); window.removeEventListener('resize', adjustListPosition);
window.removeEventListener('wheel', handleWheelEvent);
}); });
return { return {
@@ -400,13 +351,10 @@ export default defineComponent({
isSelected, isSelected,
keyArrows, keyArrows,
isOpen, isOpen,
isMouseDown,
hightlightedIndex, hightlightedIndex,
optionList, optionList,
optionRefs, optionRefs,
handleBlurEvent, handleBlurEvent
handleMouseUpEvent,
internalValue
}; };
} }
}); });
@@ -414,55 +362,48 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
.select { .select {
display: block; display: block;
&:focus, &:focus, &--open {
&--open { z-index: 10;
z-index: 10; }
}
&__search-input { &__search-input {
appearance: none; appearance: none;
border: none; border: none;
background: transparent; background: transparent;
outline: none; outline: none;
color: currentColor; color: currentColor;
max-width: 100%; max-width: 100%;
width: 100%; width: 100%;
} }
&__item-text { &__list-wrapper {
overflow: hidden; cursor: pointer;
white-space: nowrap; position: fixed;
text-overflow: ellipsis; display: block;
} z-index: 5;
-webkit-overflow-scrolling: touch;
max-height: 240px;
overflow: auto;
left: 0;
top: 40px;
}
&__list-wrapper { &__list {
cursor: pointer; list-style: none;
position: fixed; }
display: block;
z-index: 5;
-webkit-overflow-scrolling: touch;
max-height: 240px;
overflow: auto;
left: 0;
top: 40px;
}
&__list { &__option {
list-style: none; &--disabled {
} opacity: 0.6;
cursor: not-allowed;
}
}
&__option { &--disabled {
&--disabled {
opacity: 0.6; opacity: 0.6;
cursor: not-allowed; cursor: not-allowed;
} }
}
&--disabled {
opacity: 0.6;
cursor: not-allowed;
}
} }
</style> </style>

View File

@@ -9,121 +9,121 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script>
import { onMounted, watch } from 'vue';
import * as ace from 'ace-builds'; import * as ace from 'ace-builds';
import 'ace-builds/webpack-resolver';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import 'ace-builds/webpack-resolver';
import { useSettingsStore } from '@/stores/settings'; import { useSettingsStore } from '@/stores/settings';
import { uidGen } from 'common/libs/uidGen'; import { uidGen } from 'common/libs/uidGen';
const props = defineProps({ export default {
modelValue: String, name: 'BaseTextEditor',
mode: { type: String, default: 'text' }, props: {
editorClass: { type: String, default: '' }, modelValue: String,
autoFocus: { type: Boolean, default: false }, mode: { type: String, default: 'text' },
readOnly: { type: Boolean, default: false }, editorClass: { type: String, default: '' },
showLineNumbers: { type: Boolean, default: true }, autoFocus: { type: Boolean, default: false },
height: { type: Number, default: 200 } readOnly: { type: Boolean, default: false },
}); showLineNumbers: { type: Boolean, default: true },
const emit = defineEmits(['update:modelValue']); height: { type: Number, default: 200 }
const settingsStore = useSettingsStore(); },
emits: ['update:modelValue'],
setup () {
const settingsStore = useSettingsStore();
const { const {
editorTheme, editorTheme,
editorFontSize, editorFontSize,
autoComplete, autoComplete,
lineWrap lineWrap
} = storeToRefs(settingsStore); } = storeToRefs(settingsStore);
let editor: ace.Ace.Editor; return {
const id = uidGen(); editorTheme,
editorFontSize,
autoComplete,
lineWrap
};
},
data () {
return {
editor: null,
id: uidGen()
};
},
watch: {
mode () {
if (this.editor)
this.editor.session.setMode(`ace/mode/${this.mode}`);
},
editorTheme () {
if (this.editor)
this.editor.setTheme(`ace/theme/${this.editorTheme}`);
},
editorFontSize () {
const sizes = {
small: '12px',
medium: '14px',
large: '16px'
};
watch(() => props.mode, () => { if (this.editor) {
if (editor) this.editor.setOptions({
editor.session.setMode(`ace/mode/${props.mode}`); fontSize: sizes[this.editorFontSize]
}); });
}
watch(() => props.modelValue, () => { },
if (editor && props.readOnly) autoComplete () {
editor.session.setValue(props.modelValue); if (this.editor) {
}); this.editor.setOptions({
enableLiveAutocompletion: this.autoComplete
watch(editorTheme, () => { });
if (editor) }
editor.setTheme(`ace/theme/${editorTheme.value}`); },
}); lineWrap () {
if (this.editor) {
watch(editorFontSize, () => { this.editor.setOptions({
const sizes = { wrap: this.lineWrap
xsmall: '10px', });
small: '12px', }
medium: '14px', }
large: '16px', },
xlarge: '18px', mounted () {
xxlarge: '20px' this.editor = ace.edit(`editor-${this.id}`, {
}; mode: `ace/mode/${this.mode}`,
theme: `ace/theme/${this.editorTheme}`,
if (editor) { value: this.modelValue || '',
editor.setOptions({ fontSize: '14px',
fontSize: sizes[editorFontSize.value] printMargin: false,
readOnly: this.readOnly,
showLineNumbers: this.showLineNumbers,
showGutter: this.showLineNumbers
}); });
}
});
watch(autoComplete, () => { this.editor.setOptions({
if (editor) { enableBasicAutocompletion: false,
editor.setOptions({ wrap: this.lineWrap,
enableLiveAutocompletion: autoComplete.value enableSnippets: false,
enableLiveAutocompletion: false
}); });
}
});
watch(lineWrap, () => { this.editor.session.on('change', () => {
if (editor) { const content = this.editor.getValue();
editor.setOptions({ this.$emit('update:modelValue', content);
wrap: lineWrap.value
}); });
}
});
onMounted(() => { if (this.autoFocus) {
editor = ace.edit(`editor-${id}`, { setTimeout(() => {
mode: `ace/mode/${props.mode}`, this.editor.focus();
theme: `ace/theme/${editorTheme.value}`, this.editor.resize();
value: props.modelValue || '', }, 20);
fontSize: 14, }
printMargin: false,
readOnly: props.readOnly,
showLineNumbers: props.showLineNumbers,
showGutter: props.showLineNumbers
});
editor.setOptions({
enableBasicAutocompletion: false,
wrap: lineWrap,
enableSnippets: false,
enableLiveAutocompletion: false
});
(editor.session as unknown as ace.Ace.Editor).on('change', () => {
const content = editor.getValue();
emit('update:modelValue', content);
});
if (props.autoFocus) {
setTimeout(() => { setTimeout(() => {
editor.focus(); this.editor.resize();
editor.resize();
}, 20); }, 20);
} }
};
editor.commands.removeCommand('showSettingsMenu');
setTimeout(() => {
editor.resize();
}, 20);
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -9,63 +9,67 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script>
import { computed, ref, watch } from 'vue'; export default {
name: 'BaseToast',
const props = defineProps({ props: {
message: { message: {
type: String, type: String,
default: '' default: ''
},
status: {
type: String,
default: ''
}
}, },
status: { emits: ['close'],
type: String, data () {
default: '' return {
isVisible: false
};
},
computed: {
toastStatus () {
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 };
}
},
watch: {
message: function () {
if (this.message)
this.isVisible = true;
else
this.isVisible = false;
}
},
methods: {
hideToast () {
this.isVisible = false;
this.$emit('close');
}
} }
});
const isVisible = ref(false);
const message = ref(props.message);
const emit = defineEmits(['close']);
const toastStatus = 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 };
});
watch(message, () => {
if (message.value)
isVisible.value = true;
else
isVisible.value = false;
});
const hideToast = () => {
isVisible.value = false;
emit('close');
}; };
</script> </script>
<style scoped> <style scoped>
.toast { .toast {
display: flex; display: flex;

View File

@@ -4,10 +4,10 @@
<i class="mdi mdi-folder-open mr-1" />{{ message }} <i class="mdi mdi-folder-open mr-1" />{{ message }}
</span> </span>
<span class="text-ellipsis file-uploader-value"> <span class="text-ellipsis file-uploader-value">
{{ lastPart(modelValue, 19) }} {{ lastPart(modelValue) }}
</span> </span>
<i <i
v-if="modelValue" v-if="modelValue.length"
class="file-uploader-reset mdi mdi-close" class="file-uploader-reset mdi mdi-close"
@click.prevent="clear" @click.prevent="clear"
/> />
@@ -16,40 +16,46 @@
:id="`id_${id}`" :id="`id_${id}`"
class="file-uploader-input" class="file-uploader-input"
type="file" type="file"
:accept="accept"
@change="$emit('change', $event)" @change="$emit('change', $event)"
> >
</form> </form>
</label> </label>
</template> </template>
<script setup lang="ts"> <script>
import { uidGen } from 'common/libs/uidGen'; import { uidGen } from 'common/libs/uidGen';
import { useFilters } from '@/composables/useFilters';
const { lastPart } = useFilters(); export default {
name: 'BaseUploadInput',
props: {
message: {
default: 'Browse',
type: String
},
modelValue: {
default: '',
type: String
}
},
emits: ['change', 'clear'],
data () {
return {
id: uidGen()
};
},
methods: {
clear () {
this.$emit('clear');
},
lastPart (string) {
if (!string) return '';
defineProps({ string = string.split(/[/\\]+/).pop();
message: { if (string.length >= 19)
default: 'Browse', string = `...${string.slice(-19)}`;
type: String return string;
}, }
accept: {
default: '',
type: String
},
modelValue: {
default: '',
type: String
} }
});
const emit = defineEmits(['change', 'clear']);
const id = uidGen();
const clear = () => {
emit('clear');
}; };
</script> </script>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div ref="root" class="vscroll-holder"> <div class="vscroll-holder">
<div <div
class="vscroll-spacer" class="vscroll-spacer"
:style="{ :style="{
@@ -20,76 +20,71 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script>
import { onBeforeUnmount, onMounted, Ref, ref, watch } from 'vue'; export default {
name: 'BaseVirtualScroll',
props: {
items: Array,
itemHeight: Number,
visibleHeight: Number,
scrollElement: {
type: HTMLDivElement,
default: null
}
},
data () {
return {
topHeight: 0,
bottomHeight: 0,
visibleItems: [],
renderTimeout: null,
localScrollElement: null
};
},
watch: {
scrollElement () {
this.setScrollElement();
}
},
mounted () {
this.setScrollElement();
},
beforeUnmount () {
this.localScrollElement.removeEventListener('scroll', this.checkScrollPosition);
},
methods: {
checkScrollPosition (e) {
clearTimeout(this.renderTimeout);
const props = defineProps({ this.renderTimeout = setTimeout(() => {
items: Array, this.updateWindow(e);
itemHeight: Number, }, 200);
visibleHeight: Number, },
scrollElement: { updateWindow () {
type: HTMLDivElement, const visibleItemsCount = Math.ceil(this.visibleHeight / this.itemHeight);
default: null const totalScrollHeight = this.items.length * this.itemHeight;
const offset = 50;
const scrollTop = this.localScrollElement.scrollTop;
const firstVisibleIndex = Math.floor(scrollTop / this.itemHeight);
const lastVisibleIndex = firstVisibleIndex + visibleItemsCount;
const firstCutIndex = Math.max(firstVisibleIndex - offset, 0);
const lastCutIndex = lastVisibleIndex + offset;
this.visibleItems = this.items.slice(firstCutIndex, lastCutIndex);
this.topHeight = firstCutIndex * this.itemHeight;
this.bottomHeight = totalScrollHeight - this.visibleItems.length * this.itemHeight - this.topHeight;
},
setScrollElement () {
if (this.localScrollElement)
this.localScrollElement.removeEventListener('scroll', this.checkScrollPosition);
this.localScrollElement = this.scrollElement ? this.scrollElement : this.$el;
this.updateWindow();
this.localScrollElement.addEventListener('scroll', this.checkScrollPosition);
}
} }
});
const root = ref(null);
const topHeight: Ref<number> = ref(0);
const bottomHeight: Ref<number> = ref(0);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const visibleItems: Ref<any[]> = ref([]);
const renderTimeout: Ref<NodeJS.Timeout> = ref(null);
const localScrollElement: Ref<HTMLDivElement> = ref(null);
const checkScrollPosition = () => {
clearTimeout(renderTimeout.value);
renderTimeout.value = setTimeout(() => {
updateWindow();
}, 200);
}; };
const updateWindow = () => {
const visibleItemsCount = Math.ceil(props.visibleHeight / props.itemHeight);
const totalScrollHeight = props.items.length * props.itemHeight;
const offset = 50;
const scrollTop = localScrollElement.value.scrollTop;
const firstVisibleIndex = Math.floor(scrollTop / props.itemHeight);
const lastVisibleIndex = firstVisibleIndex + visibleItemsCount;
const firstCutIndex = Math.max(firstVisibleIndex - offset, 0);
const lastCutIndex = lastVisibleIndex + offset;
visibleItems.value = props.items.slice(firstCutIndex, lastCutIndex);
topHeight.value = firstCutIndex * props.itemHeight;
bottomHeight.value = totalScrollHeight - visibleItems.value.length * props.itemHeight - topHeight.value;
};
const setScrollElement = () => {
if (localScrollElement.value)
localScrollElement.value.removeEventListener('scroll', checkScrollPosition);
localScrollElement.value = props.scrollElement ? props.scrollElement : root.value;
updateWindow();
localScrollElement.value.addEventListener('scroll', checkScrollPosition);
};
watch(() => props.scrollElement, () => {
setScrollElement();
});
onMounted(() => {
setScrollElement();
});
onBeforeUnmount(() => {
localScrollElement.value.removeEventListener('scroll', checkScrollPosition);
});
defineExpose({
updateWindow
});
</script> </script>

View File

@@ -4,10 +4,10 @@
v-model="selectedGroup" v-model="selectedGroup"
class="form-select" class="form-select"
:options="[{name: 'manual'}, ...fakerGroups]" :options="[{name: 'manual'}, ...fakerGroups]"
:option-label="(opt: any) => opt.name === 'manual' ? t('general.manualValue') : t(`faker.${opt.name}`)" :option-label="(opt) => opt.name === 'manual' ? $t('message.manualValue') : $t(`faker.${opt.name}`)"
option-track-by="name" option-track-by="name"
:disabled="!isChecked" :disabled="!isChecked"
:style="'flex-grow: 0;'" style="flex-grow: 0;"
@change="onChange" @change="onChange"
/> />
@@ -15,7 +15,7 @@
v-if="selectedGroup !== 'manual'" v-if="selectedGroup !== 'manual'"
v-model="selectedMethod" v-model="selectedMethod"
:options="fakerMethods" :options="fakerMethods"
:option-label="(opt: any) => t(`faker.${opt.name}`)" :option-label="(opt) => $t(`faker.${opt.name}`)"
option-track-by="name" option-track-by="name"
class="form-select" class="form-select"
:disabled="!isChecked" :disabled="!isChecked"
@@ -41,7 +41,7 @@
<BaseUploadInput <BaseUploadInput
v-else-if="inputProps().type === 'file'" v-else-if="inputProps().type === 'file'"
:model-value="selectedValue" :model-value="selectedValue"
:message="t('general.browse')" :message="$t('word.browse')"
@clear="clearValue" @clear="clearValue"
@change="filesChange($event)" @change="filesChange($event)"
/> />
@@ -85,158 +85,153 @@
</fieldset> </fieldset>
</template> </template>
<script setup lang="ts"> <script>
import { computed, PropType, Ref, ref, watch } from 'vue'; import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT, UUID, IS_BIGINT } from 'common/fieldTypes'; import BaseUploadInput from '@/components/BaseUploadInput';
import BaseUploadInput from '@/components/BaseUploadInput.vue'; import ForeignKeySelect from '@/components/ForeignKeySelect';
import ForeignKeySelect from '@/components/ForeignKeySelect.vue';
import FakerMethods from 'common/FakerMethods'; import FakerMethods from 'common/FakerMethods';
import BaseSelect from '@/components/BaseSelect.vue'; import BaseSelect from '@/components/BaseSelect.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n(); export default {
name: 'FakerSelect',
components: {
ForeignKeySelect,
BaseUploadInput,
BaseSelect
},
props: {
type: String,
field: Object,
isChecked: Boolean,
foreignKeys: Array,
keyUsage: Array,
fieldLength: Number,
fieldObj: Object
},
emits: ['update:modelValue'],
data () {
return {
localType: null,
selectedGroup: 'manual',
selectedMethod: '',
selectedValue: '',
debounceTimeout: null,
methodParams: {},
enumArray: null
};
},
computed: {
fakerGroups () {
if ([...TEXT, ...LONG_TEXT].includes(this.type))
this.localType = 'string';
else if (NUMBER.includes(this.type))
this.localType = 'number';
else if (FLOAT.includes(this.type))
this.localType = 'float';
else if ([...DATE, ...DATETIME].includes(this.type))
this.localType = 'datetime';
else if (TIME.includes(this.type))
this.localType = 'time';
else
this.localType = 'none';
const props = defineProps({ return FakerMethods.getGroupsByType(this.localType);
type: String, },
field: Object, fakerMethods () {
isChecked: Boolean, return FakerMethods.getMethods({ type: this.localType, group: this.selectedGroup });
foreignKeys: Array, },
keyUsage: Array as PropType<{field: string}[]>, methodData () {
fieldLength: Number, return this.fakerMethods.find(method => method.name === this.selectedMethod);
fieldObj: Object }
}); },
const emit = defineEmits(['update:modelValue']); watch: {
fieldObj () {
const localType: Ref<string> = ref(null); if (this.fieldObj) {
const selectedGroup: Ref<string> = ref('manual'); if (Array.isArray(this.fieldObj.value)) {
const selectedMethod: Ref<string> = ref(''); this.enumArray = this.fieldObj.value;
const selectedValue: Ref<string> = ref(''); this.selectedValue = this.fieldObj.value[0];
const debounceTimeout: Ref<NodeJS.Timeout> = ref(null); }
const methodParams: Ref<{[key: string]: string}> = ref({}); else
const enumArray: Ref<string[]> = ref(null); this.selectedValue = this.fieldObj.value;
}
const fakerGroups = computed(() => { },
if ([...TEXT, ...LONG_TEXT].includes(props.type)) selectedGroup () {
localType.value = 'string'; if (this.fakerMethods.length)
else if (NUMBER.includes(props.type)) this.selectedMethod = this.fakerMethods[0].name;
localType.value = 'number'; else
else if (FLOAT.includes(props.type)) this.selectedMethod = '';
localType.value = 'float'; },
else if ([...DATE, ...DATETIME].includes(props.type)) selectedMethod () {
localType.value = 'datetime'; this.onChange();
else if (TIME.includes(props.type)) },
localType.value = 'time'; selectedValue () {
else if (UUID.includes(props.type)) clearTimeout(this.debounceTimeout);
localType.value = 'uuid'; this.debounceTimeout = null;
else this.debounceTimeout = setTimeout(() => {
localType.value = 'none'; this.onChange();
}, 200);
return FakerMethods.getGroupsByType(localType.value); }
}); },
methods: {
const fakerMethods = computed(() => { inputProps () {
return FakerMethods.getMethods({ type: localType.value, group: selectedGroup.value }); if ([...TEXT, ...LONG_TEXT].includes(this.type))
}); return { type: 'text', mask: false };
const methodData = computed(() => { if ([...NUMBER, ...FLOAT].includes(this.type))
return fakerMethods.value.find(method => method.name === selectedMethod.value); return { type: 'number', mask: false };
});
if (TIME.includes(this.type)) {
const inputProps = () => { let timeMask = '##:##:##';
if ([...TEXT, ...LONG_TEXT].includes(props.type)) const precision = this.fieldLength;
return { type: 'text', mask: false };
for (let i = 0; i < precision; i++)
if ([...NUMBER, ...FLOAT].includes(props.type)) { timeMask += i === 0 ? '.#' : '#';
if (IS_BIGINT.includes(props.type))
return { type: 'text', mask: false }; return { type: 'text', mask: timeMask };
else }
return { type: 'number', mask: false };
} if (DATE.includes(this.type))
return { type: 'text', mask: '####-##-##' };
if (TIME.includes(props.type)) {
let timeMask = '##:##:##'; if (DATETIME.includes(this.type)) {
const precision = props.fieldLength; let datetimeMask = '####-##-## ##:##:##';
const precision = this.fieldLength;
for (let i = 0; i < precision; i++)
timeMask += i === 0 ? '.#' : '#'; for (let i = 0; i < precision; i++)
datetimeMask += i === 0 ? '.#' : '#';
return { type: 'text', mask: timeMask };
} return { type: 'text', mask: datetimeMask };
}
if (DATE.includes(props.type))
return { type: 'text', mask: '####-##-##' }; if (BLOB.includes(this.type))
return { type: 'file', mask: false };
if (DATETIME.includes(props.type)) {
let datetimeMask = '####-##-## ##:##:##'; if (BIT.includes(this.type))
const precision = props.fieldLength; return { type: 'text', mask: false };
for (let i = 0; i < precision; i++) return { type: 'text', mask: false };
datetimeMask += i === 0 ? '.#' : '#'; },
getKeyUsage (keyName) {
return { type: 'text', mask: datetimeMask }; return this.keyUsage.find(key => key.field === keyName);
} },
filesChange (event) {
if (BLOB.includes(props.type)) const { files } = event.target;
return { type: 'file', mask: false }; if (!files.length) return;
if (BIT.includes(props.type)) this.selectedValue = files[0].path;
return { type: 'text', mask: false }; },
clearValue () {
return { type: 'text', mask: false }; this.selectedValue = '';
}; },
onChange () {
const getKeyUsage = (keyName: string) => { this.$emit('update:modelValue', {
return props.keyUsage.find(key => key.field === keyName); group: this.selectedGroup,
}; method: this.selectedMethod,
params: this.methodParams,
const filesChange = ({ target } : {target: HTMLInputElement }) => { value: this.selectedValue,
const { files } = target; length: this.fieldLength
if (!files.length) return; });
selectedValue.value = files[0].path;
};
const clearValue = () => {
selectedValue.value = '';
};
const onChange = () => {
emit('update:modelValue', {
group: selectedGroup.value,
method: selectedMethod.value,
params: methodParams.value,
value: selectedValue.value,
length: props.fieldLength
});
};
watch(() => props.fieldObj, () => {
if (props.fieldObj) {
if (Array.isArray(props.fieldObj.value)) {
enumArray.value = props.fieldObj.value;
selectedValue.value = props.fieldObj.value[0];
} }
else
selectedValue.value = props.fieldObj.value;
} }
}); };
watch(selectedGroup, () => {
if (fakerMethods.value.length)
selectedMethod.value = fakerMethods.value[0].name;
else
selectedMethod.value = '';
});
watch(selectedMethod, () => {
onChange();
});
watch(selectedValue, () => {
clearTimeout(debounceTimeout.value);
debounceTimeout.value = null;
debounceTimeout.value = setTimeout(() => {
onChange();
}, 200);
});
</script> </script>

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