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

Compare commits

...

127 Commits

Author SHA1 Message Date
c9ccf786cd chore(release): 0.1.11 2021-06-02 15:47:12 +02:00
5e9c88a7fd fix(UI): prevent canc key to trigger delete modal when editing a row 2021-06-02 12:32:12 +02:00
e0e6483d1f build: update dependencies 2021-06-02 12:13:43 +02:00
66227569f4 fix: table row loses internal id after cell update 2021-06-02 11:58:34 +02:00
faa799c8ea fix(MySQL): missing schema altering tables in some conditions 2021-05-31 17:07:48 +02:00
acc1eeb094 fix: empty offset in cell update queries 2021-05-31 14:27:02 +02:00
865dad80df chore: update README.md 2021-05-30 10:54:58 +02:00
f502620779 chore(release): 0.1.10 2021-05-29 11:41:48 +02:00
f61d7eeaf4 feat: key shortcuts to change DATA tab page 2021-05-29 11:26:23 +02:00
e71c7568c0 feat: option to set DATA tab page size 2021-05-29 11:04:02 +02:00
79f033e524 feat: prev/next buttons to browse the results pages of data tab 2021-05-27 22:13:59 +02:00
310cfaa3c2 fix(MySQL): wrong schema in view data tab select, closes #71 2021-05-26 17:44:33 +02:00
6f93e1f9ab chore: AUR badge on README.md 2021-05-26 13:19:48 +02:00
04bdd085a5 fix: wrong detection of field default expressions in some cases 2021-05-25 16:54:13 +02:00
c568acae14 build: update dependencies 2021-05-25 10:09:09 +02:00
7a46a119d8 chore(release): 0.1.9 2021-05-23 11:35:54 +02:00
f1636f1528 fix(MySQL): can't access tables having UNIQUE KEY, closes #69 2021-05-23 11:12:09 +02:00
f839d5cd3c chore: Twitter link in about tab 2021-05-23 10:19:56 +02:00
8f4f1d393c chore: update README.md 2021-05-22 17:33:03 +02:00
ab17c6d4bf chore: update README.md 2021-05-22 17:26:32 +02:00
b4b2f3d0cf chore(release): 0.1.8 2021-05-22 16:25:52 +02:00
7a766f04e6 perf: improved the way how field default value are handled 2021-05-22 16:24:19 +02:00
f6b77d1243 build: update dependencies 2021-05-22 16:12:08 +02:00
840241c3cc refactor: better description in foreign key value select 2021-05-20 15:07:51 +02:00
0f10c9e824 fix: internal id not removed before row update 2021-05-20 12:45:49 +02:00
1e37f2a96f fix: unable to add new ENUM fields 2021-05-20 12:38:05 +02:00
0d6137195d fix: unable to delete table rows 2021-05-19 16:55:38 +02:00
d26f168250 build: update build script 2021-05-17 19:08:36 +02:00
5efcad4d3f chore(release): 0.1.7 2021-05-16 21:08:52 +02:00
0a872e7023 build: separate build configurations 2021-05-16 21:03:04 +02:00
0e425a9c6d build: removed linux arm64 build from build process 2021-05-16 19:50:06 +02:00
434711a360 perf(MySQL): improved connections pool handling 2021-05-15 21:47:30 +02:00
854472c7a3 fix: row loses internal id after cell update 2021-05-15 19:33:31 +02:00
9a7cd90d5c build: arm mac (apple silicon) build configuration 2021-05-14 22:24:50 +02:00
026400d242 build: arm linux build configuration 2021-05-14 18:59:58 +02:00
4bd8cbbf6c build: moved to sass from node-sass 2021-05-14 17:27:56 +02:00
1382bc9300 Merge pull request #64 from Fabio286/dependabot/npm_and_yarn/pgsql-ast-parser-8.1.1
build(deps): bump pgsql-ast-parser from 7.2.1 to 8.1.1
2021-05-14 17:21:10 +02:00
ae103e5477 perf(core): increased connection pool size to improve performance 2021-05-14 17:17:37 +02:00
6b0b8b19d7 fix(MySQL): connection loses schema in some conditions 2021-05-14 17:02:27 +02:00
475397ca34 fix: issue with ENUM and SET fields on table filler modal 2021-05-13 20:46:44 +02:00
7a62131cc7 fix: issue with ENUM and SET length when creating a new field 2021-05-13 15:45:54 +02:00
c7663be338 fix: multiple row select on sorted tables not work properly 2021-05-11 18:32:11 +02:00
496490b14a build: update dependencies and build config 2021-05-11 12:00:22 +02:00
dependabot[bot]
672573d8c2 build(deps): bump pgsql-ast-parser from 7.2.1 to 8.1.1
Bumps [pgsql-ast-parser](https://github.com/oguimbal/pgsql-ast-parser) from 7.2.1 to 8.1.1.
- [Release notes](https://github.com/oguimbal/pgsql-ast-parser/releases)
- [Changelog](https://github.com/oguimbal/pgsql-ast-parser/blob/master/changelog.md)
- [Commits](https://github.com/oguimbal/pgsql-ast-parser/compare/7.2.1...8.1.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-10 06:16:49 +00:00
d923453919 chore(release): 0.1.6 2021-05-08 17:33:25 +02:00
0435279604 build: temporarily disabled Windows builds due issues with dependencies 2021-05-08 17:22:45 +02:00
79cef61fea build: revert to electron-builder@22.9.1 due Windows build issues 2021-05-08 17:13:24 +02:00
dec334737f chore: update dependencies 2021-05-08 16:41:18 +02:00
84c3692e95 chore: icons for appx build 2021-05-08 16:04:37 +02:00
5dc0b0bea4 perf: italian translation updated 2021-05-07 12:11:55 +02:00
19d16e46bb build: update dependencies 2021-05-06 22:55:10 +02:00
3baf6fa173 fix: better detection and handling of field default type 2021-05-06 22:21:42 +02:00
29e2d92b5b fix: no quotes around strings in field default custom value 2021-05-05 17:13:12 +02:00
bebba64d06 feat(MySQL): ENUM and SET fields support, closes #61 2021-05-04 21:50:41 +02:00
9dfe7cca22 fix(UI): data type figure twice on type select 2021-05-03 22:55:39 +02:00
35ef070725 fix: support to mDNS/zeroconf in snap build, closes #58 2021-05-01 12:16:57 +02:00
370ad6a536 chore(release): 0.1.5 2021-04-30 17:37:04 +02:00
5822b3df43 perf(UI): new application icon 2021-04-30 14:14:01 +02:00
5208ec171b fix(MySQL): multiple queries non properly split in some cases 2021-04-29 21:03:32 +02:00
6e332da425 chore: update issue template 2021-04-28 16:56:04 +02:00
bf3367b41d build: minor changes to build and dependencies 2021-04-28 12:11:39 +02:00
ecfb732c26 fix: % character not properly escaped, closes #60 2021-04-28 12:10:43 +02:00
fd4f032a6f build: mssql temporarily removed from dependencies 2021-04-28 11:55:29 +02:00
1b09909126 fix: semicolon inside strings breaks queries, closes #59 2021-04-28 11:50:07 +02:00
04cd806954 chore: update README.md 2021-04-26 10:16:55 +02:00
773cb36ca1 Merge pull request #57 from Fabio286/dependabot/npm_and_yarn/electron-store-8.0.0
build(deps): bump electron-store from 7.0.3 to 8.0.0
2021-04-26 10:12:32 +02:00
ccacd3e2c3 Merge pull request #56 from Fabio286/dependabot/npm_and_yarn/electron-12.0.5
build(deps-dev): bump electron from 11.4.3 to 12.0.5
2021-04-26 10:08:32 +02:00
15948b30c9 refactor: modifications for electron 12 support 2021-04-26 10:07:47 +02:00
3dcb5d4f14 Merge pull request #55 from Fabio286/dependabot/npm_and_yarn/stylelint-config-standard-22.0.0
build(deps-dev): bump stylelint-config-standard from 21.0.0 to 22.0.0
2021-04-26 08:59:35 +02:00
dependabot[bot]
64d93d7c40 build(deps): bump electron-store from 7.0.3 to 8.0.0
Bumps [electron-store](https://github.com/sindresorhus/electron-store) from 7.0.3 to 8.0.0.
- [Release notes](https://github.com/sindresorhus/electron-store/releases)
- [Commits](https://github.com/sindresorhus/electron-store/compare/v7.0.3...v8.0.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-26 06:26:31 +00:00
dependabot[bot]
2d85295093 build(deps-dev): bump electron from 11.4.3 to 12.0.5
Bumps [electron](https://github.com/electron/electron) from 11.4.3 to 12.0.5.
- [Release notes](https://github.com/electron/electron/releases)
- [Changelog](https://github.com/electron/electron/blob/master/docs/breaking-changes.md)
- [Commits](https://github.com/electron/electron/compare/v11.4.3...v12.0.5)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-26 06:25:55 +00:00
dependabot[bot]
cd58e2d8ca build(deps-dev): bump stylelint-config-standard from 21.0.0 to 22.0.0
Bumps [stylelint-config-standard](https://github.com/stylelint/stylelint-config-standard) from 21.0.0 to 22.0.0.
- [Release notes](https://github.com/stylelint/stylelint-config-standard/releases)
- [Changelog](https://github.com/stylelint/stylelint-config-standard/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint-config-standard/compare/21.0.0...22.0.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-26 06:25:16 +00:00
d87495822e Merge pull request #54 from daeleduardo/master
feat: portugues (Brasil) translation
2021-04-25 11:44:55 +02:00
Daniel Aguiar
8720bcdad6 New Translation: Portugues (Brasil) 2021-04-25 03:53:21 -03:00
1df57dc705 chore(release): 0.1.4 2021-04-22 15:18:07 +02:00
86240fb53c refactor(PostgreSQL): preparing code to support triggers 2021-04-22 15:15:08 +02:00
1d363f755e feat: query results export 2021-04-22 15:08:22 +02:00
0d77aee3eb refactor: Improved pulse animation code 2021-04-22 14:24:34 +02:00
a41cf1ab56 fix: wrong changelog in some cases 2021-04-22 11:35:59 +02:00
5ceddb8e00 perf(UI): improved connection status indicator 2021-04-21 16:41:42 +02:00
16e17b39b6 feat(UI): ctrl+s shortcut to save changes 2021-04-20 17:39:15 +02:00
20cba6ee9b feat(UI): canc press to delete selected rows 2021-04-20 16:30:10 +02:00
9ffd443a66 feat(UI): format and clear queries 2021-04-19 19:15:06 +02:00
f82dbd24dc fix: launch from shortcut of procedures or functions with parameters without name dont works 2021-04-19 15:40:25 +02:00
6eb2977568 fix(UI): data type not listed in selection if not present in global types 2021-04-19 11:07:29 +02:00
cafb65560a chore: update README.md 2021-04-17 12:28:55 +02:00
532d963019 chore: update README.md 2021-04-17 11:28:02 +02:00
1b0a63ff31 chore(release): 0.1.3 2021-04-17 10:36:07 +02:00
c22187c305 perf(UI): improved table fields suggestion in query editor 2021-04-17 10:33:15 +02:00
dcccb544f9 fix(MySQL): invalid JavaScript datetime values not shown 2021-04-16 18:48:56 +02:00
7d2ace9456 fix: field apparently loses index or foreign key on rename in table editor 2021-04-16 17:42:16 +02:00
2584c9b9c2 chore: replaced link for donations with Treedom 2021-04-15 14:55:37 +02:00
a6b75ad0dc fix: approximate row count shown for results less than 1000 2021-04-15 10:13:55 +02:00
90fd9db917 perf(MySQL): improved the way to get routine and functions parameters 2021-04-14 18:06:20 +02:00
c0f54b9514 build: update dependencies 2021-04-14 10:42:00 +02:00
cd31413256 feat(PostgreSQL): functions management 2021-04-13 18:05:03 +02:00
b33199ea59 feat(PostgreSQL): procedure language select 2021-04-12 18:46:35 +02:00
dea5ec7513 chore(release): 0.1.2 2021-04-11 12:39:56 +02:00
be816e8588 perf(UI): improved setting modal rendering 2021-04-11 12:38:50 +02:00
1e938adc5d feat: in-app last release changelog 2021-04-11 12:35:16 +02:00
8735a0c5f9 feat(PostgreSQL): edit timezone in cell editor 2021-04-11 10:55:22 +02:00
3dde1c109e feat(PostgreSQL): procedures management 2021-04-10 20:38:46 +02:00
d0b3e1b1b8 feat(PostgreSQL): support of arrays in table settings 2021-04-09 19:31:41 +02:00
c20bff7bcb fix: deletion of rows from query results 2021-04-08 21:49:38 +02:00
9f5ec0276c fix: no foreign key select when cell value is NULL, closes #50 2021-04-08 18:02:16 +02:00
55932fe115 fix: cell edit doesn't properly use primary or unique index to update if both present, closes #51 2021-04-08 17:47:10 +02:00
d374372e20 fix: wrong datetime conversion when updating a row without an unique key 2021-04-07 15:05:11 +02:00
bb5f44681f fix(UI): white readonly inputs with dark theme 2021-04-07 09:20:11 +02:00
49a4e1cb7b fix(PostgreSQL): issue with selected schema different than public 2021-04-06 12:48:40 +02:00
c2f76e490a chore(release): 0.1.1 2021-04-03 12:21:34 +02:00
e349dd5eab feat: scratchpad to save persistent notes 2021-04-03 12:17:40 +02:00
280697698e feat(UI): light theme 2021-04-03 11:21:58 +02:00
0783f8b57e chore: update README.md 2021-04-02 15:27:29 +02:00
c981244d7a fix(UI): editor theme preview not properly loaded in some cases 2021-04-01 15:26:49 +02:00
dcb135dd01 fix: hide update tab for Windows Store distributions 2021-04-01 14:12:49 +02:00
99f7511c4d feat(PostgreSQL): views management 2021-03-31 16:54:06 +02:00
fe4c8e12b3 feat(PostgreSQL): foreign keys management 2021-03-31 15:57:23 +02:00
21728a663d chore: update README.md 2021-03-31 10:59:19 +02:00
9ca03f4625 feat(PostgreSQL): indexes management 2021-03-30 19:07:04 +02:00
affb7288b0 chore: appx logos 2021-03-30 19:06:48 +02:00
614e0d3275 feat(PostgreSQL): unique keys management 2021-03-29 20:18:44 +02:00
feef5e30ee feat(PostgreSQL): tables addition 2021-03-28 11:55:15 +02:00
82c25711b6 ci: moving to GitHub actions 2021-03-26 18:03:44 +01:00
2ca2988832 chore: appx build configuration 2021-03-26 16:52:49 +01:00
e3f259c6e8 feat(PostgreSQL): table fields edit 2021-03-25 18:33:29 +01:00
e7401cc96e fix: fields of ref. table not automatically loaded in foreign keys modal 2021-03-22 18:04:19 +01:00
13b9840f3d chore: update README.md 2021-03-21 14:39:11 +01:00
110 changed files with 3973 additions and 1281 deletions

View File

@@ -9,22 +9,25 @@
"plugin:vue/recommended"
],
"parserOptions": {
"parser": "babel-eslint",
"parser": "@babel/eslint-parser",
"ecmaVersion": 9,
"sourceType": "module"
"sourceType": "module",
"requireConfigFile": false
},
"rules": {
"indent": [
"error",
3,
{ "SwitchCase": 1 }
{
"SwitchCase": 1
}
],
"linebreak-style": [
"error",
"unix"
],
"brace-style": [
"error",
"error",
"stroustrup"
],
"quotes": [
@@ -36,7 +39,7 @@
"always"
],
"curly": [
"error",
"error",
"multi-or-nest"
],
"no-console": "off",
@@ -44,18 +47,25 @@
"vue/no-side-effects-in-computed-properties": "off",
"vue/require-default-prop": "off",
"vue/no-v-html": "off",
"vue/html-indent": ["error", 3, {
"attribute": 1,
"baseIndent": 1,
"closeBracket": 0,
"ignores": []
}],
"vue/max-attributes-per-line": ["error", {
"singleline": 2,
"multiline": {
"max": 1,
"allowFirstLine": false
"vue/html-indent": [
"error",
3,
{
"attribute": 1,
"baseIndent": 1,
"closeBracket": 0,
"ignores": []
}
}]
],
"vue/max-attributes-per-line": [
"error",
{
"singleline": 2,
"multiline": {
"max": 1,
"allowFirstLine": false
}
}
]
}
}

2
.github/FUNDING.yml vendored
View File

@@ -1,7 +1,7 @@
# These are supported funding model platforms
github: [fabio286]
patreon: fabio286
patreon: #fabio286
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel

View File

@@ -12,6 +12,7 @@ A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
@@ -23,9 +24,15 @@ A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Application (please complete the following information):**
- Version [e.g. 0.14.0]
- Distribution: [e.g. exe, Linux Store, AppImage, dmg]
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Version [e.g. 22]
- OS: [e.g. iOS]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

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

@@ -0,0 +1,26 @@
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@v1
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1
with:
node-version: 12
- name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1
with:
github_token: ${{ secrets.github_token }}
release: ${{ startsWith(github.ref, 'refs/tags/v') }}

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

@@ -0,0 +1,26 @@
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@v1
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1
with:
node-version: 12
- name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1
with:
github_token: ${{ secrets.github_token }}
release: ${{ startsWith(github.ref, 'refs/tags/v') }}

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

@@ -0,0 +1,26 @@
name: Build/release [windows]
on: push
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [windows-latest]
steps:
- name: Check out Git repository
uses: actions/checkout@v1
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1
with:
node-version: 12
- 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

@@ -8,7 +8,8 @@
"stylelint-scss"
],
"rules": {
"at-rule-no-unknown": null
"at-rule-no-unknown": null,
"no-descending-specificity": null
},
"syntax": "scss"
}

View File

@@ -1,52 +0,0 @@
language: node_js
node_js: 12
cache:
directories:
- node_modules
- app/node_modules
- $HOME/.cache/electron
- $HOME/.cache/electron-builder
- $HOME/.npm/_prebuilds
env:
global:
- ELECTRON_CACHE=$HOME/.cache/electron
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
jobs:
include:
- stage: Test
before_install:
- sudo apt-get install libsecret-1-dev
- npm install
script:
- npm test
- stage: Deploy Linux & Windows
if: tag IS present
os: linux
services: docker
before_install:
- sudo apt-get install libsecret-1-dev
- npm install
script:
- docker run --rm --env-file <(env | grep -iE 'DEBUG|NODE_|ELECTRON_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|_TOKEN|_KEY|AWS_|STRIP|BUILD_') -v ${PWD}:/project -v ~/.cache/electron:/root/.cache/electron -v ~/.cache/electron-builder:/root/.cache/electron-builder electronuserland/builder:wine /bin/bash -c "npm run build -- --linux --win -p always"
before_cache:
- rm -rf $HOME/.cache/electron-builder/wine
- stage: Deploy Mac
if: tag IS present
os: osx
before_install:
- npm install
osx_image: xcode10.2
script:
- npm run build -- -p always
# - stage: Deploy ARM Linux
# if: tag IS present
# os: linux
# arch: arm64
# script:
# - npm run build -- --linux AppImage -p always

View File

@@ -2,6 +2,192 @@
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.1.11](https://github.com/Fabio286/antares/compare/v0.1.10...v0.1.11) (2021-06-02)
### Bug Fixes
* **UI:** prevent canc key to trigger delete modal when editing a row ([5e9c88a](https://github.com/Fabio286/antares/commit/5e9c88a7fd656dd69b92ba4384ca81f4e8cdd422))
* table row loses internal id after cell update ([6622756](https://github.com/Fabio286/antares/commit/66227569f4c032298e20d44c253d30109ffde0b2))
* **MySQL:** missing schema altering tables in some conditions ([faa799c](https://github.com/Fabio286/antares/commit/faa799c8ea2329e7ca1b54c9414a060bc8a2a840))
* empty offset in cell update queries ([acc1eeb](https://github.com/Fabio286/antares/commit/acc1eeb0948db35f2c9a4d85d70fb654b41b15e5))
### [0.1.10](https://github.com/Fabio286/antares/compare/v0.1.9...v0.1.10) (2021-05-29)
### Features
* key shortcuts to change DATA tab page ([f61d7ee](https://github.com/Fabio286/antares/commit/f61d7eeaf42406eb88b3285ac4606794f56e88d3))
* option to set DATA tab page size ([e71c756](https://github.com/Fabio286/antares/commit/e71c7568c09bf8ad184753d3d0f14046f1e386d0))
* prev/next buttons to browse the results pages of data tab ([79f033e](https://github.com/Fabio286/antares/commit/79f033e5245e0e744c35540d25a30b1eadc4d603))
### Bug Fixes
* **MySQL:** wrong schema in view data tab select, closes [#71](https://github.com/Fabio286/antares/issues/71) ([310cfaa](https://github.com/Fabio286/antares/commit/310cfaa3c2324b1159a46be566b6af2555dc9732))
* wrong detection of field default expressions in some cases ([04bdd08](https://github.com/Fabio286/antares/commit/04bdd085a56240273133586d23196e52341d5f27))
### [0.1.9](https://github.com/Fabio286/antares/compare/v0.1.8...v0.1.9) (2021-05-23)
### Bug Fixes
* **MySQL:** can't access tables having UNIQUE KEY, closes [#69](https://github.com/Fabio286/antares/issues/69) ([f1636f1](https://github.com/Fabio286/antares/commit/f1636f1528b5765423535f84027fdc853c58ce80))
### [0.1.8](https://github.com/Fabio286/antares/compare/v0.1.7...v0.1.8) (2021-05-22)
### Bug Fixes
* internal id not removed before row update ([0f10c9e](https://github.com/Fabio286/antares/commit/0f10c9e824b679cfebd8a31085236da3d38a7953))
* unable to add new ENUM fields ([1e37f2a](https://github.com/Fabio286/antares/commit/1e37f2a96f660a36f2739fea57ca298059e9c31a))
* unable to delete table rows ([0d61371](https://github.com/Fabio286/antares/commit/0d6137195da9e0e9afe68f6d3ebcd50e913a888e))
### Improvements
* improved the way how field default value are handled ([7a766f0](https://github.com/Fabio286/antares/commit/7a766f04e668868e8844aac1d7d445bc2da21558))
### [0.1.7](https://github.com/Fabio286/antares/compare/v0.1.6...v0.1.7) (2021-05-16)
### Bug Fixes
* row loses internal id after cell update ([854472c](https://github.com/Fabio286/antares/commit/854472c7a3d8442b1eede12f978cad5aa684094e))
* **MySQL:** connection loses schema in some conditions ([6b0b8b1](https://github.com/Fabio286/antares/commit/6b0b8b19d7176ef8647d6f401a33315f8732fdf3))
* issue with ENUM and SET fields on table filler modal ([475397c](https://github.com/Fabio286/antares/commit/475397ca34c5b7d8925e2d97d32216d5b80d8211))
* issue with ENUM and SET length when creating a new field ([7a62131](https://github.com/Fabio286/antares/commit/7a62131cc707aa1c98bb12513de76229472b7c38))
* multiple row select on sorted tables not work properly ([c7663be](https://github.com/Fabio286/antares/commit/c7663be338ccb5ff31e241e824593531ad95bd32))
### Improvements
* **core:** increased connection pool size to improve performance ([ae103e5](https://github.com/Fabio286/antares/commit/ae103e5477ad190c541d0f29e277c41de6947063))
* **MySQL:** improved connections pool handling ([434711a](https://github.com/Fabio286/antares/commit/434711a360bd7539d2ec3adec92e7ccc96fa828e))
### [0.1.6](https://github.com/Fabio286/antares/compare/v0.1.5...v0.1.6) (2021-05-08)
### Features
* **MySQL:** ENUM and SET fields support, closes [#61](https://github.com/Fabio286/antares/issues/61) ([bebba64](https://github.com/Fabio286/antares/commit/bebba64d06532990405763284e27cb768dc050f7))
### Bug Fixes
* better detection and handling of field default type ([3baf6fa](https://github.com/Fabio286/antares/commit/3baf6fa1736a405c95fb02d17c11514861ca9e04))
* no quotes around strings in field default custom value ([29e2d92](https://github.com/Fabio286/antares/commit/29e2d92b5bf66ff9e80bb1fee1274967c4418601))
* **UI:** data type figure twice on type select ([9dfe7cc](https://github.com/Fabio286/antares/commit/9dfe7cca2234ce6de512bb4c21205c2217e7f765))
* support to mDNS/zeroconf in snap build, closes [#58](https://github.com/Fabio286/antares/issues/58) ([35ef070](https://github.com/Fabio286/antares/commit/35ef070725f5779923bf3ad428b44accddf22dbe))
### Improvements
* italian translation updated ([5dc0b0b](https://github.com/Fabio286/antares/commit/5dc0b0bea40f9d5a5944af2f1c5dc6ff3e60c396))
### [0.1.5](https://github.com/Fabio286/antares/compare/v0.1.4...v0.1.5) (2021-04-30)
### Bug Fixes
* **MySQL:** multiple queries non properly split in some cases ([5208ec1](https://github.com/Fabio286/antares/commit/5208ec171b44da0e6bfa93f15bfedd03ef2aa868))
* % character not properly escaped, closes [#60](https://github.com/Fabio286/antares/issues/60) ([ecfb732](https://github.com/Fabio286/antares/commit/ecfb732c265a5485e131e75f3d20ff07d9409753))
* semicolon inside strings breaks queries, closes [#59](https://github.com/Fabio286/antares/issues/59) ([1b09909](https://github.com/Fabio286/antares/commit/1b0990912627a3a4a4e8d62b4593f8a7aa3a7fe5))
### Improvements
* **UI:** new application icon ([5822b3d](https://github.com/Fabio286/antares/commit/5822b3df432e0a2b305d0ff37a20dc466c3a3992))
### [0.1.4](https://github.com/Fabio286/antares/compare/v0.1.3...v0.1.4) (2021-04-22)
### Features
* query results export ([1d363f7](https://github.com/Fabio286/antares/commit/1d363f755e025d0fc6fec61cbd47ff87a8f25728))
* **UI:** canc press to delete selected rows ([20cba6e](https://github.com/Fabio286/antares/commit/20cba6ee9bc1daa902b04d6e2ddcb31d04fbf805))
* **UI:** ctrl+s shortcut to save changes ([16e17b3](https://github.com/Fabio286/antares/commit/16e17b39b6c8b561cc018d02afee2276190ce304))
* **UI:** format and clear queries ([9ffd443](https://github.com/Fabio286/antares/commit/9ffd443a66303f88fc4529896f6d1d7917454f7a))
### Bug Fixes
* launch from shortcut of procedures or functions with parameters without name dont works ([f82dbd2](https://github.com/Fabio286/antares/commit/f82dbd24dcef7b4d8d127a604e256b3f79a6c617))
* wrong changelog in some cases ([a41cf1a](https://github.com/Fabio286/antares/commit/a41cf1ab5662f5f5fdedff4a9e1c626c23071377))
* **UI:** data type not listed in selection if not present in global types ([6eb2977](https://github.com/Fabio286/antares/commit/6eb2977568987b9440b62ae7dbd7183338bfcc9b))
### Improvements
* **UI:** improved connection status indicator ([5ceddb8](https://github.com/Fabio286/antares/commit/5ceddb8e00f3bc1984b8e47de270dc39b367903f))
### [0.1.3](https://github.com/Fabio286/antares/compare/v0.1.2...v0.1.3) (2021-04-17)
### Features
* **PostgreSQL:** functions management ([cd31413](https://github.com/Fabio286/antares/commit/cd3141325681ea572c06b8998dd7bd334ceb3236))
* **PostgreSQL:** procedure language select ([b33199e](https://github.com/Fabio286/antares/commit/b33199ea59c60b467601f333857494aa40adf4e8))
### Bug Fixes
* **MySQL:** invalid JavaScript datetime values not shown ([dcccb54](https://github.com/Fabio286/antares/commit/dcccb544f9ec24ad693c9e81fb4bcfbdbb7cc4e1))
* approximate row count shown for results less than 1000 ([a6b75ad](https://github.com/Fabio286/antares/commit/a6b75ad0dc0d884332464c277e8542b2698630b9))
* field apparently loses index or foreign key on rename in table editor ([7d2ace9](https://github.com/Fabio286/antares/commit/7d2ace94562f8da307b15b83c89d919727d800c8))
### Improvements
* **MySQL:** improved the way to get routine and functions parameters ([90fd9db](https://github.com/Fabio286/antares/commit/90fd9db917c40262f2bc2501ab86f5feba3d8db4))
* **UI:** improved table fields suggestion in query editor ([c22187c](https://github.com/Fabio286/antares/commit/c22187c3053aef368a351cc35e2f1d407ecde209))
### [0.1.2](https://github.com/Fabio286/antares/compare/v0.1.1...v0.1.2) (2021-04-11)
### Features
* in-app last release changelog ([1e938ad](https://github.com/Fabio286/antares/commit/1e938adc5d8eb5ad16ab16342375eecd88f68d20))
* **PostgreSQL:** edit timezone in cell editor ([8735a0c](https://github.com/Fabio286/antares/commit/8735a0c5f9e5b6b3bcaadf37ce158aa7beae2c48))
* **PostgreSQL:** procedures management ([3dde1c1](https://github.com/Fabio286/antares/commit/3dde1c109e23342d94362626ef7350dc123ea859))
* **PostgreSQL:** support of arrays in table settings ([d0b3e1b](https://github.com/Fabio286/antares/commit/d0b3e1b1b8be9d2c038d70e16d4478671315de8f))
### Bug Fixes
* cell edit doesn't properly use primary or unique index to update if both present, closes [#51](https://github.com/Fabio286/antares/issues/51) ([55932fe](https://github.com/Fabio286/antares/commit/55932fe11583bd5ff48f82b8408965adba4f5071))
* deletion of rows from query results ([c20bff7](https://github.com/Fabio286/antares/commit/c20bff7bcbe340ac99ebcacaba3359edd61c068a))
* no foreign key select when cell value is NULL, closes [#50](https://github.com/Fabio286/antares/issues/50) ([9f5ec02](https://github.com/Fabio286/antares/commit/9f5ec0276c92904975fdaea34b4c845c92bfe8d4))
* wrong datetime conversion when updating a row without an unique key ([d374372](https://github.com/Fabio286/antares/commit/d374372e208318d7e50b258a8041145bdf7992c5))
* **PostgreSQL:** issue with selected schema different than public ([49a4e1c](https://github.com/Fabio286/antares/commit/49a4e1cb7b24642641265d5830d3fee370cceeb4))
* **UI:** white readonly inputs with dark theme ([bb5f446](https://github.com/Fabio286/antares/commit/bb5f44681f87aacf2cd2f60a6d958c5732289790))
### Improvements
* **UI:** improved setting modal rendering ([be816e8](https://github.com/Fabio286/antares/commit/be816e85888b4f3d26cbb9caac0adbc4dde0ea94))
### [0.1.1](https://github.com/Fabio286/antares/compare/v0.1.0...v0.1.1) (2021-04-03)
### Features
* scratchpad to save persistent notes ([e349dd5](https://github.com/Fabio286/antares/commit/e349dd5eaba608591257f2799b830805e4936c27))
* **PostgreSQL:** foreign keys management ([fe4c8e1](https://github.com/Fabio286/antares/commit/fe4c8e12b39dd3cdfc233f07e3fe2ff0676252b0))
* **PostgreSQL:** indexes management ([9ca03f4](https://github.com/Fabio286/antares/commit/9ca03f462560b634970a19d3d97b878d60509acc))
* **PostgreSQL:** table fields edit ([e3f259c](https://github.com/Fabio286/antares/commit/e3f259c6e8327d71bd7dd0a9c33d957dc6ca1fb8))
* **PostgreSQL:** tables addition ([feef5e3](https://github.com/Fabio286/antares/commit/feef5e30eec915cbb219223cc428bd4e98d2e9c5))
* **PostgreSQL:** unique keys management ([614e0d3](https://github.com/Fabio286/antares/commit/614e0d32758c13b59139d349d4682a5bafc3ca88))
* **PostgreSQL:** views management ([99f7511](https://github.com/Fabio286/antares/commit/99f7511c4d5fab4030b30d5134cd03248167faea))
* **UI:** light theme ([2806976](https://github.com/Fabio286/antares/commit/280697698ea5fae6d54326970c823878888c196c))
### Bug Fixes
* **UI:** editor theme preview not properly loaded in some cases ([c981244](https://github.com/Fabio286/antares/commit/c981244d7aa93ca18ca2de44bf8df06f253b9d20))
* fields of ref. table not automatically loaded in foreign keys modal ([e7401cc](https://github.com/Fabio286/antares/commit/e7401cc96e76e00100a88eea9f40541fd8027adb))
* hide update tab for Windows Store distributions ([dcb135d](https://github.com/Fabio286/antares/commit/dcb135dd015b8f8c6cfb44021211bb8cf3089192))
## [0.1.0](https://github.com/Fabio286/antares/compare/v0.0.20...v0.1.0) (2021-03-21)

View File

@@ -4,24 +4,29 @@
# Antares SQL Client
![GitHub package.json version](https://img.shields.io/github/package-json/v/fabio286/antares) [![Build Status](https://travis-ci.com/Fabio286/antares.svg?branch=master)](https://travis-ci.com/Fabio286/antares) ![GitHub All Releases](https://img.shields.io/github/downloads/fabio286/antares/total) ![GitHub](https://img.shields.io/github/license/fabio286/antares) [![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) [![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) ![GitHub All Releases](https://img.shields.io/github/downloads/fabio286/antares/total) ![GitHub](https://img.shields.io/github/license/fabio286/antares) [![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)
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.
My target is to support as many databases as possible, and all major operating systems, including the ARM versions.
**At the moment this application is an alpha, it lacks many features** and supports only MySQL.
Most of its current features might be enough for basic MySQL use, so give it a chance and send me your feedback, I would really appreciate it.
I'm actively working on it (yes, i'm a lone dev), hoping to provide cool features and fixes as soon as possible.
**At the moment this application is in development state, many features will come in future updates**, and supports only MySQL/MariaDB and PostgreSQL (partially).
Many of its current features are enough to have a pleasant user experience with MySQL/MariaDB, and basic functionalites with PostgreSQL, so give it a chance and send me your feedback, I would really appreciate it.
I'm actively working on it, hoping to provide cool features and fixes as soon as possible.
🔗 If you are curious to try this early state of Antares you can download and install the [latest release](https://github.com/Fabio286/antares/releases).
👁 To stay tuned for new releases watch this repo on **Release only** channel.
🔗 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](https://twitter.com/AntaresSQL) on Twitter.
🌟 Don't forget to **leave a star** if you appreciate this project.
## Philosophy
Why am I developing an SQL client when there are a lot of them on the market?
The main goal is to develop a totally free, full featured, cross platform and open source alternative, empowered by JavaScript's ecosystem.
An application created with minimalism and semplicity in mind, with features in the right places, not hundreds of tiny buttons or submenu.
A modern application created with minimalism and semplicity in mind, with features in the right places, not hundreds of tiny buttons, tabs or submenu.
## 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/) [![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)**
## How to contribute
@@ -36,17 +41,17 @@ An application created with minimalism and semplicity in mind, with features in
- Fake table data filler.
- Run queries on multiple tabs.
- Query suggestions and auto complete.
- Native dark theme.
- Dark and light theme.
- Scratchpad.
- Multi language.
- Secure password storage.
- Auto updates.
## Coming soon
This is a roadmap with major features will come in near future.
- Support for other databases.
- Database tools (variables, process list...).
- Database tools.
- SSH tunnel support.
- Users management (add/edit/delete).
- UI/UX improvements.
@@ -55,25 +60,13 @@ This is a roadmap with major features will come in near future.
- More keyboard shortcuts.
- Query logs console.
- Import/export and migration.
- Light theme.
## Troubleshooting
### **Linux**
With KDE may need necessary installation of the additional `gnome-keyring` package.
Depending on your distribution, you will need to run the following command:
- Debian/Ubuntu: `sudo apt-get install gnome-keyring`
- Red Hat-based: `sudo yum install gnome-keyring`
- Arch Linux: `sudo pacman -S gnome-keyring`
## Currently supported
### Databases
- [x] MySQL/MariaDB
- [ ] PostgreSQL
- [x] PostgreSQL (partially, work in progress)
- [ ] SQLite
- [ ] MSSQL
- [ ] OracleDB
@@ -90,15 +83,16 @@ Depending on your distribution, you will need to run the following command:
#### • ARM
- [ ] Windows
- [ ] Linux
- [x] Linux
- [ ] MacOS
## Translations
**Italian Translation** (46%) / [Giuseppe Gigliotti](https://github.com/ReverbOD) [[#20](https://github.com/Fabio286/antares/pull/20)]
**Arabic Translation** (45%) / [Mohd-PH](https://github.com/Mohd-PH) [[#29](https://github.com/Fabio286/antares/pull/29)]
**Spanish Translation** (46%) / [hongkfui](https://github.com/hongkfui) [[#32](https://github.com/Fabio286/antares/pull/32)]
**French Translation** (100%) / [MrAnyx](https://github.com/MrAnyx) [[#44](https://github.com/Fabio286/antares/pull/44)]
**Italian Translation** / [Giuseppe Gigliotti](https://github.com/ReverbOD) [[#20](https://github.com/Fabio286/antares/pull/20)]
**Arabic Translation** / [Mohd-PH](https://github.com/Mohd-PH) [[#29](https://github.com/Fabio286/antares/pull/29)]
**Spanish Translation** / [hongkfui](https://github.com/hongkfui) [[#32](https://github.com/Fabio286/antares/pull/32)]
**French Translation** / [MrAnyx](https://github.com/MrAnyx) [[#44](https://github.com/Fabio286/antares/pull/44)]
**Portugues (Brasil)** / [Daniel Eduardo](https://github.com/daeleduardo) [[#54](https://github.com/Fabio286/antares/pull/54)]
## Reviews

BIN
build/appx/LargeTile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
build/appx/SmallTile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
build/appx/StoreLogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 50 KiB

77
docs/aur-badge.svg Normal file
View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
style="isolation:isolate" viewBox="0 0 182 56" width="182px" height="56px">
<defs>
<clipPath id="_clipPath_tR1uglJ1Zei76xP861DY1TsjAiQWS9qF">
<rect width="182" height="56" />
</clipPath>
</defs>
<g clip-path="url(#_clipPath_tR1uglJ1Zei76xP861DY1TsjAiQWS9qF)">
<path
d="M 2.5 0.5 L 180.5 0.5 C 181.604 0.5 182.5 1.396 182.5 2.5 L 182.5 54.5 C 182.5 55.604 181.604 56.5 180.5 56.5 L 2.5 56.5 C 1.396 56.5 0.5 55.604 0.5 54.5 L 0.5 2.5 C 0.5 1.396 1.396 0.5 2.5 0.5 Z"
style="stroke:none;fill:#252525;stroke-miterlimit:10;" />
<path
d="M 2.5 0.5 L 180.5 0.5 C 181.604 0.5 182.5 1.396 182.5 2.5 L 182.5 54.5 C 182.5 55.604 181.604 56.5 180.5 56.5 L 2.5 56.5 C 1.396 56.5 0.5 55.604 0.5 54.5 L 0.5 2.5 C 0.5 1.396 1.396 0.5 2.5 0.5 Z"
style="fill:none;stroke:#CDCDCD;stroke-width:1;stroke-miterlimit:2;" />
<g>
<g>
<path
d=" M 60.898 13.777 C 58.555 13.774 56.61 14.254 55.858 14.516 L 55.083 18.697 C 55.081 18.712 58.937 17.669 60.635 17.73 C 63.447 17.831 63.706 18.805 63.656 20.119 C 63.704 20.196 62.931 18.931 60.498 18.889 C 57.43 18.836 53.098 19.976 53.104 24.608 C 53.022 29.818 56.997 31.351 59.704 31.379 C 62.138 31.335 63.279 30.458 63.904 29.988 C 64.726 29.129 65.665 28.265 66.561 27.229 C 65.714 28.77 64.978 29.835 64.213 30.651 L 64.213 31.339 L 67.913 30.716 L 67.938 20.66 C 67.901 19.237 68.754 13.791 60.898 13.777 Z M 60.367 22.533 C 61.9 22.554 63.659 23.31 63.662 25.129 C 63.669 26.784 61.589 27.674 60.235 27.66 C 58.881 27.646 57.085 26.596 57.077 24.982 C 57.103 23.54 58.771 22.496 60.367 22.533 Z "
fill-rule="evenodd" fill="rgb(255,255,255)" />
<path
d=" M 70.378 14.707 L 70.352 31.36 L 74.662 30.529 L 74.67 21.087 C 74.671 19.681 76.679 18.039 79.198 18.065 C 79.733 17.097 80.738 14.625 80.983 14.062 C 75.354 14.049 75.282 15.68 74.303 16.483 C 74.293 14.952 74.3 14.033 74.3 14.033 L 70.378 14.707 L 70.378 14.707 Z "
fill-rule="evenodd" fill="rgb(255,255,255)" />
<path
d=" M 94.632 16.893 C 94.591 16.873 92.385 14.312 87.949 14.292 C 83.795 14.223 79.135 15.834 79.061 22.8 C 79.097 28.925 83.537 31.318 87.973 31.365 C 92.72 31.414 94.609 28.396 94.722 28.322 C 94.156 27.83 92.034 25.728 92.034 25.728 C 92.034 25.728 90.709 27.615 88.138 27.639 C 85.566 27.664 83.331 25.651 83.299 22.844 C 83.266 20.036 85.354 18.515 88.157 18.392 C 90.584 18.392 91.984 19.959 91.984 19.959 L 94.632 16.893 L 94.632 16.893 Z "
fill-rule="evenodd" fill="rgb(255,255,255)" />
<path
d=" M 100.065 8.879 L 95.996 9.835 L 96.026 31.526 L 100.034 30.802 L 100.08 20.595 C 100.089 19.524 101.628 17.88 104.2 17.933 C 106.658 17.958 107.207 19.571 107.201 19.775 L 107.272 31.592 L 111.224 30.894 L 111.239 18.363 C 111.265 17.157 108.598 14.61 104.311 14.592 C 102.273 14.596 101.145 15.057 100.571 15.397 C 99.588 16.156 98.466 16.883 97.362 17.811 C 98.382 16.501 99.239 15.595 100.075 14.921 L 100.065 8.879 L 100.065 8.879 Z "
fill-rule="evenodd" fill="rgb(255,255,255)" />
</g>
<g>
<path
d=" M 114.673 9.441 L 116.508 8.982 L 116.595 30.85 L 114.73 31.168 L 114.673 9.441 L 114.673 9.441 Z "
fill-rule="evenodd" fill="rgb(23,147,209)" />
<path
d=" M 119.663 15.968 L 121.271 15.252 L 121.285 30.932 L 119.731 31.253 L 119.663 15.968 L 119.663 15.968 Z M 119.28 10.314 L 120.577 9.255 L 121.655 10.454 L 120.357 11.54 L 119.28 10.314 L 119.28 10.314 Z "
fill-rule="evenodd" fill="rgb(23,147,209)" />
<path
d=" M 124.296 15.682 L 126.131 15.308 L 126.14 18.586 C 126.14 18.727 127.148 14.924 132.008 15.009 C 136.727 15.035 137.499 18.688 137.473 19.507 L 137.531 31.034 L 135.922 31.384 L 135.913 19.998 C 135.932 19.665 135.178 16.853 131.843 16.843 C 128.509 16.833 126.199 19.265 126.203 20.818 L 126.229 30.848 L 124.365 31.335 L 124.296 15.682 L 124.296 15.682 Z "
fill-rule="evenodd" fill="rgb(23,147,209)" />
<path
d=" M 153.547 31.117 L 151.711 31.492 L 151.703 28.214 C 151.703 28.073 150.694 31.876 145.835 31.791 C 141.116 31.765 140.344 28.112 140.37 27.293 L 140.311 15.765 L 142.261 15.372 L 142.292 26.758 C 142.292 27.069 142.665 29.947 145.999 29.957 C 149.334 29.967 151.669 27.949 151.686 24.911 L 151.662 15.928 L 153.477 15.464 L 153.547 31.117 L 153.547 31.117 Z "
fill-rule="evenodd" fill="rgb(23,147,209)" />
<path
d=" M 157.144 15.553 L 155.857 16.56 L 160.792 23.018 L 155.529 30.478 L 156.894 31.492 L 161.841 24.563 L 166.948 31.656 L 168.211 30.649 L 162.738 23.065 L 167.104 16.933 L 165.762 15.797 L 161.785 21.472 L 157.144 15.553 L 157.144 15.553 Z "
fill-rule="evenodd" fill="rgb(23,147,209)" />
</g>
<path
d=" M 33.112 3.879 C 30.965 9.143 29.67 12.587 27.279 17.695 C 28.745 19.248 30.544 21.058 33.466 23.101 C 30.325 21.809 28.182 20.511 26.581 19.164 C 23.521 25.549 18.728 34.643 9 52.121 C 16.645 47.707 22.572 44.986 28.095 43.948 C 27.858 42.928 27.723 41.824 27.733 40.673 L 27.742 40.428 C 27.863 35.53 30.411 31.763 33.43 32.019 C 36.448 32.274 38.794 36.455 38.673 41.353 C 38.65 42.275 38.546 43.162 38.364 43.984 C 43.828 45.053 49.691 47.767 57.233 52.121 C 55.746 49.383 54.419 46.915 53.151 44.565 C 51.154 43.017 49.072 41.003 44.823 38.822 C 47.743 39.581 49.834 40.456 51.464 41.435 C 38.575 17.439 37.532 14.251 33.112 3.879 Z "
fill-rule="evenodd" fill="rgb(23,147,209)" />
<g>
<path
d=" M 170.614 30.156 L 170.614 28.802 L 170.109 28.802 L 170.109 28.621 L 171.325 28.621 L 171.325 28.802 L 170.817 28.802 L 170.817 30.156 L 170.614 30.156 Z "
fill="rgb(23,147,209)" />
<path
d=" M 171.536 30.156 L 171.536 28.621 L 171.842 28.621 L 172.205 29.708 C 172.238 29.809 172.263 29.884 172.278 29.935 C 172.295 29.879 172.323 29.797 172.36 29.689 L 172.727 28.621 L 173 28.621 L 173 30.156 L 172.804 30.156 L 172.804 28.871 L 172.358 30.156 L 172.175 30.156 L 171.732 28.849 L 171.732 30.156 L 171.536 30.156 Z "
fill="rgb(23,147,209)" />
</g>
<g>
<path
d=" M 57.471 47.815 L 57.471 46.493 L 56.977 46.493 L 56.977 46.316 L 58.166 46.316 L 58.166 46.493 L 57.67 46.493 L 57.67 47.815 L 57.471 47.815 Z "
fill="rgb(23,147,209)" />
<path
d=" M 58.372 47.815 L 58.372 46.316 L 58.671 46.316 L 59.026 47.377 C 59.059 47.476 59.083 47.55 59.098 47.599 C 59.115 47.545 59.141 47.465 59.177 47.359 L 59.536 46.316 L 59.803 46.316 L 59.803 47.815 L 59.612 47.815 L 59.612 46.56 L 59.176 47.815 L 58.997 47.815 L 58.564 46.539 L 58.564 47.815 L 58.372 47.815"
fill="rgb(23,147,209)" />
</g>
</g>
<g clip-path="url(#_clipPath_NvFIpNfWUS6M4fZAtfyVzggsKR3URDoi)"><text transform="matrix(1,0,0,1,87.023,43.899)"
style="font-family:'Open Sans';font-weight:700;font-size:11px;font-style:normal;fill:#ffffff;stroke:none;">user
repository</text></g>
<defs>
<clipPath id="_clipPath_NvFIpNfWUS6M4fZAtfyVzggsKR3URDoi">
<rect x="0" y="0" width="86" height="14.98" transform="matrix(1,0,0,1,87,32.142)" />
</clipPath>
</defs>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 172 KiB

View File

@@ -1,20 +1,22 @@
{
"name": "antares",
"productName": "Antares",
"version": "0.1.0",
"version": "0.1.11",
"description": "A cross-platform easy to use SQL client.",
"license": "MIT",
"repository": "https://github.com/Fabio286/antares.git",
"scripts": {
"dev": "cross-env NODE_ENV=development electron-webpack dev",
"compile": "electron-webpack",
"build": "cross-env NODE_ENV=production npm run compile && electron-builder",
"build": "cross-env NODE_ENV=production npm run compile",
"build:local": "npm run build && electron-builder",
"build:appx": "npm run build:local -- --win appx",
"release": "standard-version",
"release:pre": "npm run release -- --prerelease alpha",
"release:snap": "npm run build -- --linux snap && snapcraft upload --release=edge ./dist/Antares-${npm_package_version}-linux_amd64.snap",
"test": "npm run lint",
"lint": "eslint . --ext .js,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
"lint:fix": "eslint . --ext .js,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix"
"lint:fix": "eslint . --ext .js,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix",
"postinstall": "electron-builder install-app-deps"
},
"author": "Fabio Di Stasio <fabio286@gmail.com>",
"build": {
@@ -26,6 +28,44 @@
"portable"
]
},
"mac": {
"target": {
"target": "default",
"arch": [
"x64"
]
}
},
"linux": {
"target": [
{
"target": "deb",
"arch": "x64"
},
{
"target": "AppImage",
"arch": [
"x64",
"armv7l",
"arm64"
]
}
],
"category": "Development"
},
"appImage": {
"license": "./LICENSE",
"category": "Development"
},
"portable": {
"artifactName": "${productName}-${version}-portable.exe"
},
"appx": {
"displayName": "Antares SQL Client",
"identityName": "62514FabioDiStasio.AntaresSQLClient",
"publisher": "CN=1A2729ED-865C-41D2-9038-39AE2A63AA52",
"applicationId": "FabioDiStasio.AntaresSQLClient"
},
"dmg": {
"contents": [
{
@@ -39,20 +79,6 @@
"path": "/Applications"
}
]
},
"linux": {
"target": [
"deb",
"AppImage"
],
"category": "Development"
},
"appImage": {
"license": "./LICENSE",
"category": "Development"
},
"portable": {
"artifactName": "${productName}-${version}-portable.exe"
}
},
"electronWebpack": {
@@ -61,48 +87,48 @@
}
},
"dependencies": {
"@electron/remote": "^1.1.0",
"@mdi/font": "^5.9.55",
"ace-builds": "^1.4.12",
"electron-log": "^4.3.0",
"electron-store": "^7.0.0",
"electron-updater": "^4.3.5",
"faker": "^5.3.1",
"keytar": "^7.3.0",
"electron-log": "^4.3.5",
"electron-store": "^8.0.0",
"electron-updater": "^4.3.9",
"faker": "^5.5.3",
"marked": "^2.0.5",
"moment": "^2.29.1",
"mssql": "^6.2.3",
"mysql2": "^2.2.5",
"node-sql-parser": "^3.1.0",
"pg": "^8.5.1",
"pgsql-ast-parser": "^7.0.2",
"pgsql-ast-parser": "^7.2.1",
"source-map-support": "^0.5.16",
"spectre.css": "^0.5.9",
"sql-formatter": "^4.0.2",
"v-mask": "^2.2.4",
"vue-i18n": "^8.22.4",
"vue-i18n": "^8.24.4",
"vuedraggable": "^2.24.3",
"vuex": "^3.6.0"
"vuex": "^3.6.2"
},
"devDependencies": {
"babel-eslint": "^10.1.0",
"@babel/eslint-parser": "^7.14.3",
"cross-env": "^7.0.2",
"electron": "^11.3.0",
"electron-builder": "^22.9.1",
"electron-devtools-installer": "^3.1.1",
"electron": "^13.0.0",
"electron-builder": "22.10.5",
"electron-devtools-installer": "^3.2.0",
"electron-webpack": "^2.8.2",
"electron-webpack-vue": "^2.4.0",
"eslint": "^7.20.0",
"eslint-config-standard": "^16.0.2",
"eslint-plugin-import": "^2.22.1",
"eslint": "^7.27.0",
"eslint-config-standard": "^16.0.3",
"eslint-plugin-import": "^2.23.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.3.1",
"eslint-plugin-vue": "^7.6.0",
"node-sass": "^5.0.0",
"sass-loader": "^10.1.1",
"standard-version": "^9.1.0",
"stylelint": "^13.9.0",
"stylelint-config-standard": "^20.0.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-vue": "^7.10.0",
"sass": "^1.34.0",
"sass-loader": "^10.2.0",
"standard-version": "^9.3.0",
"stylelint": "^13.13.1",
"stylelint-config-standard": "^22.0.0",
"stylelint-scss": "^3.19.0",
"vue": "^2.6.12",
"vue-template-compiler": "^2.6.12",
"vue": "^2.6.13",
"vue-template-compiler": "^2.6.13",
"webpack": "^4.46.0"
}
}

View File

@@ -14,6 +14,9 @@ confinement: strict
architectures:
- build-on: amd64
compression: lzo
layout:
/etc/nsswitch.conf:
bind-file: $SNAP/etc/nsswitch.conf
parts:
antares:
@@ -71,11 +74,28 @@ parts:
cleanup:
after: [antares]
plugin: nil
build-snaps: [ gnome-3-28-1804 ]
build-snaps: [gnome-3-28-1804]
override-prime: |
set -eux
cd /snap/gnome-3-28-1804/current
find . -type f,l -exec rm -f $SNAPCRAFT_PRIME/{} \;
set -eux
cd /snap/gnome-3-28-1804/current
find . -type f,l -exec rm -f $SNAPCRAFT_PRIME/{} \;
mdns-lookup:
# Make resolution of ".local" host names (Zero-Conf/mDNS/DNS-SD)
# working: Take the original nsswitch.conf file from the base
# Snap and add "mdns4_minimal [NOTFOUND=return]" to its "hosts:" line
# Also install corresponding mdns4_minimal plug-in
# See: https://forum.snapcraft.io/t/no-mdns-support-in-snaps-should-core-have-a-modified-nsswitch-conf/
plugin: nil
stage-packages:
- libnss-mdns
override-prime: |
set -eux
sed -Ee 's/^\s*hosts:(\s+)files/hosts:\1files mdns4_minimal \[NOTFOUND=return\]/' /snap/core18/current/etc/nsswitch.conf > $SNAPCRAFT_STAGE/etc/nsswitch.conf
snapcraftctl prime
prime:
- lib/$SNAPCRAFT_ARCH_TRIPLET/libnss_mdns4_minimal*
- etc/nsswitch.conf
apps:
antares:
@@ -93,4 +113,4 @@ apps:
- opengl
- pulseaudio
- removable-media
- unity7
- unity7

View File

@@ -16,6 +16,7 @@ module.exports = {
tables: false,
views: false,
triggers: false,
triggerFunctions: false,
routines: false,
functions: false,
schedulers: false,
@@ -23,6 +24,7 @@ module.exports = {
tableAdd: false,
viewAdd: false,
triggerAdd: false,
triggerFunctionAdd: false,
routineAdd: false,
functionAdd: false,
schedulerAdd: false,
@@ -31,11 +33,37 @@ module.exports = {
tableSettings: false,
viewSettings: false,
triggerSettings: false,
triggerFunctionSettings: false,
routineSettings: false,
functionSettings: false,
schedulerSettings: false,
indexes: false,
foreigns: false,
sortableFields: false,
zerofill: false
unsigned: false,
nullable: false,
zerofill: false,
autoIncrement: false,
comment: false,
collation: false,
definer: false,
onUpdate: false,
tableArray: false,
viewAlgorithm: false,
viewSqlSecurity: false,
viewUpdateOption: false,
procedureDeterministic: false,
procedureDataAccess: false,
procedureSql: false,
procedureContext: false,
procedureLanguage: false,
functionDeterministic: false,
functionDataAccess: false,
functionSql: false,
functionContext: false,
functionLanguage: false,
triggerMiltipleEvents: false,
triggerUpdateColumns: false,
parametersLength: false,
languages: false
};

View File

@@ -36,5 +36,23 @@ module.exports = {
indexes: true,
foreigns: true,
sortableFields: true,
zerofill: true
unsigned: true,
nullable: true,
zerofill: true,
autoIncrement: true,
comment: true,
collation: true,
definer: true,
onUpdate: true,
viewAlgorithm: true,
viewSqlSecurity: true,
viewUpdateOption: true,
procedureDeterministic: true,
procedureDataAccess: true,
procedureSql: 'BEGIN\r\n\r\nEND',
procedureContext: true,
functionDeterministic: true,
functionDataAccess: true,
functionSql: 'BEGIN\r\n\r\nEND',
parametersLength: true
};

View File

@@ -12,20 +12,31 @@ module.exports = {
processesList: true,
// Structure
tables: true,
views: false,
views: true,
triggers: false,
routines: false,
functions: false,
schedulers: false,
routines: true,
functions: true,
// Settings
tableAdd: true,
viewAdd: true,
triggerAdd: false,
routineAdd: true,
functionAdd: true,
databaseEdit: false,
tableSettings: false,
viewSettings: false,
tableSettings: true,
viewSettings: true,
triggerSettings: false,
routineSettings: false,
functionSettings: false,
schedulerSettings: false,
routineSettings: true,
functionSettings: true,
indexes: true,
foreigns: true,
sortableFields: false
nullable: true,
tableArray: true,
procedureSql: '$BODY$\r\n\r\n$BODY$',
procedureContext: true,
procedureLanguage: true,
functionSql: '$BODY$\r\n\r\n$BODY$',
functionContext: true,
functionLanguage: true,
languages: ['sql', 'plpgsql', 'c', 'internal']
};

View File

@@ -4,7 +4,7 @@ module.exports = [
types: [
{
name: 'TINYINT',
length: true,
length: 4,
collation: false,
unsigned: true,
zerofill: true
@@ -277,13 +277,6 @@ module.exports = [
{
group: 'other',
types: [
{
name: 'UNKNOWN',
length: false,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'ENUM',
length: true,
@@ -299,5 +292,17 @@ module.exports = [
zerofill: false
}
]
},
{
group: 'unknown',
types: [
{
name: 'UNKNOWN',
length: false,
collation: false,
unsigned: false,
zerofill: false
}
]
}
];

View File

@@ -4,22 +4,22 @@ module.exports = [
types: [
{
name: 'SMALLINT',
length: true,
length: false,
unsigned: true
},
{
name: 'INTEGER',
length: true,
length: false,
unsigned: true
},
{
name: 'BIGINT',
length: true,
length: false,
unsigned: true
},
{
name: 'DECIMAL',
length: true,
length: false,
unsigned: true
},
{
@@ -29,17 +29,17 @@ module.exports = [
},
{
name: 'SMALLSERIAL',
length: true,
length: false,
unsigned: true
},
{
name: 'SERIAL',
length: true,
length: false,
unsigned: true
},
{
name: 'BIGSERIAL',
length: true,
length: false,
unsigned: true
}
]
@@ -49,12 +49,12 @@ module.exports = [
types: [
{
name: 'REAL',
length: true,
length: false,
unsigned: true
},
{
name: 'DOUBLE PRECISION',
length: true,
length: false,
unsigned: true
}
]
@@ -64,7 +64,7 @@ module.exports = [
types: [
{
name: 'money',
length: true,
length: false,
unsigned: true
}
]
@@ -77,14 +77,9 @@ module.exports = [
length: true,
unsigned: false
},
{
name: 'CHAR',
length: false,
unsigned: false
},
{
name: 'CHARACTER',
length: false,
length: true,
unsigned: false
},
{
@@ -109,7 +104,7 @@ module.exports = [
types: [
{
name: 'BYTEA',
length: true,
length: false,
unsigned: false
}
]
@@ -129,17 +124,17 @@ module.exports = [
},
{
name: 'DATE',
length: true,
length: false,
unsigned: false
},
{
name: 'TIME',
length: true,
name: 'TIME WITHOUT TIME ZONE',
length: false,
unsigned: false
},
{
name: 'TIME WITH TIME ZONE',
length: true,
length: false,
unsigned: false
},
{
@@ -229,12 +224,12 @@ module.exports = [
types: [
{
name: 'BIT',
length: false,
length: true,
unsigned: false
},
{
name: 'BIT VARYING',
length: false,
length: true,
unsigned: false
}
]

View File

@@ -54,6 +54,7 @@ export const TIME = [
'TIME',
'TIME WITH TIME ZONE'
];
export const DATETIME = [
'DATETIME',
'TIMESTAMP',
@@ -61,6 +62,12 @@ export const DATETIME = [
'TIMESTAMP WITH TIME ZONE'
];
// Used to check datetime fields only
export const HAS_TIMEZONE = [
'TIMESTAMP WITH TIME ZONE',
'TIME WITH TIME ZONE'
];
export const BLOB = [
'BLOB',
'TINYBLOB',

View File

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

View File

@@ -12,7 +12,7 @@ const regex = new RegExp(pattern);
function sqlEscaper (string) {
return string.replace(regex, char => {
const m = ['\\0', '\\x08', '\\x09', '\\x1a', '\\n', '\\r', '\'', '\"', '\\', '\\\\', '%'];
const r = ['\\\\0', '\\\\b', '\\\\t', '\\\\z', '\\\\n', '\\\\r', '\\\'', '\\\"', '\\\\', '\\\\\\\\', '\\%'];
const r = ['\\\\0', '\\\\b', '\\\\t', '\\\\z', '\\\\n', '\\\\r', '\\\'', '\\\"', '\\\\', '\\\\\\\\', '\%'];
return r[m.indexOf(char)] || char;
});
}

View File

@@ -2,9 +2,7 @@
import { app, BrowserWindow, nativeImage } from 'electron';
import * as path from 'path';
import crypto from 'crypto';
import { format as formatUrl } from 'url';
import keytar from 'keytar';
import Store from 'electron-store';
import ipcHandlers from './ipc-handlers';
@@ -31,6 +29,7 @@ async function createMainWindow () {
icon: nativeImage.createFromDataURL(icon.default),
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
'web-security': false,
enableRemoteModule: true,
spellcheck: false
@@ -39,26 +38,25 @@ async function createMainWindow () {
backgroundColor: '#1d1d1d'
});
if (isDevelopment) {
await window.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`);
try {
if (isDevelopment) {
await window.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`);
const { default: installExtension, VUEJS_DEVTOOLS } = require('electron-devtools-installer');
window.webContents.openDevTools();
const { default: installExtension, VUEJS_DEVTOOLS } = require('electron-devtools-installer');
installExtension(VUEJS_DEVTOOLS)
.then(name => {
console.log(name, 'installed');
})
.catch(err => {
console.log(err);
});
const toolName = await installExtension(VUEJS_DEVTOOLS);
console.log(toolName, 'installed');
}
else {
await window.loadURL(formatUrl({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file',
slashes: true
}));
}
}
else {
await window.loadURL(formatUrl({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file',
slashes: true
}));
catch (err) {
console.log(err);
}
window.on('closed', () => {
@@ -78,6 +76,8 @@ async function createMainWindow () {
if (!gotTheLock)
app.quit();
else {
require('@electron/remote/main').initialize();
// Initialize ipcHandlers
ipcHandlers();
@@ -88,26 +88,19 @@ else {
app.quit();
});
app.on('activate', () => {
app.on('activate', async () => {
// on macOS it is common to re-create a window even after all windows have been closed
if (mainWindow === null)
mainWindow = createMainWindow();
if (mainWindow === null) {
mainWindow = await createMainWindow();
if (isDevelopment)
mainWindow.webContents.openDevTools();
}
});
// create main BrowserWindow when electron is ready
app.on('ready', async () => {
try {
let key = await keytar.getPassword('antares', 'user');
if (!key) {
key = crypto.randomBytes(16).toString('hex');
keytar.setPassword('antares', 'user', key);
}
}
catch (err) {
console.log(err);
}
mainWindow = createMainWindow();
mainWindow = await createMainWindow();
if (isDevelopment)
mainWindow.webContents.openDevTools();
});
}

View File

@@ -1,4 +1,3 @@
import keytar from 'keytar';
import { app, ipcMain } from 'electron';
export default () => {
@@ -7,14 +6,7 @@ export default () => {
});
ipcMain.on('get-key', async event => {
let key = false;
try {
key = await keytar.getPassword('antares', 'user');
}
catch (err) {
console.log(err);
}
const key = false;
event.returnValue = key;
});
};

View File

@@ -8,7 +8,8 @@ export default connections => {
host: conn.host,
port: +conn.port,
user: conn.user,
password: conn.password
password: conn.password,
application_name: 'Antares SQL'
};
if (conn.database)
@@ -50,7 +51,8 @@ export default connections => {
host: conn.host,
port: +conn.port,
user: conn.user,
password: conn.password
password: conn.password,
application_name: 'Antares SQL'
};
if (conn.database)
@@ -69,7 +71,7 @@ export default connections => {
const connection = ClientsFactory.getConnection({
client: conn.client,
params,
poolSize: 1
poolSize: 5
});
await connection.connect();

View File

@@ -128,7 +128,7 @@ export default connections => {
if (!query) return;
try {
const result = await connections[uid].raw(query, { nest: true, details: true });
const result = await connections[uid].raw(query, { nest: true, details: true, schema });
return { status: 'success', response: result };
}

View File

@@ -16,13 +16,15 @@ export default (connections) => {
}
});
ipcMain.handle('get-table-data', async (event, { uid, schema, table, sortParams }) => {
ipcMain.handle('get-table-data', async (event, { uid, schema, table, limit, page, sortParams }) => {
try {
const offset = (page - 1) * limit;
const query = connections[uid]
.select('*')
.schema(schema)
.from(table)
.limit(1000);
.limit(limit)
.offset(offset);
if (sortParams && sortParams.field && sortParams.dir)
query.orderBy({ [sortParams.field]: sortParams.dir.toUpperCase() });
@@ -59,6 +61,8 @@ export default (connections) => {
});
ipcMain.handle('update-table-cell', async (event, params) => {
delete params.row._id;
try { // TODO: move to client classes
let escapedParam;
let reload = false;
@@ -157,9 +161,13 @@ export default (connections) => {
ipcMain.handle('delete-table-rows', async (event, params) => {
if (params.primary) {
const idString = params.rows.map(row => typeof row[params.primary] === 'string'
? `"${row[params.primary]}"`
: row[params.primary]).join(',');
const idString = params.rows.map(row => {
const fieldName = Object.keys(row)[0].includes('.') ? `${params.table}.${params.primary}` : params.primary;
return typeof row[fieldName] === 'string'
? `"${row[fieldName]}"`
: row[fieldName];
}).join(',');
try {
const result = await connections[params.uid]

View File

@@ -9,10 +9,13 @@ autoUpdater.allowPrerelease = persistentStore.get('allow_prerelease', true);
export default () => {
ipcMain.on('check-for-updates', event => {
mainWindow = event;
autoUpdater.checkForUpdatesAndNotify().catch(() => {
mainWindow.reply('check-failed');
});
if (process.windowsStore)
mainWindow.reply('no-auto-update');
else {
autoUpdater.checkForUpdatesAndNotify().catch(() => {
mainWindow.reply('check-failed');
});
}
});
ipcMain.on('restart-to-update', () => {

View File

@@ -26,6 +26,7 @@ export class AntaresCore {
groupBy: [],
orderBy: [],
limit: [],
offset: [],
join: [],
update: [],
insert: [],
@@ -109,6 +110,11 @@ export class AntaresCore {
return this;
}
offset (...args) {
this._query.offset = args;
return this;
}
/**
* @param {String | Array} args field = value
* @returns

View File

@@ -1,5 +1,5 @@
'use strict';
import mysql from 'mysql2';
import mysql from 'mysql2/promise';
import { AntaresCore } from '../AntaresCore';
import dataTypes from 'common/data-types/mysql';
@@ -7,6 +7,8 @@ export class MySQLClient extends AntaresCore {
constructor (args) {
super(args);
this._schema = null;
this.types = {
0: 'DECIMAL',
1: 'TINYINT',
@@ -100,10 +102,22 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
async connect () {
delete this._params.application_name;
if (!this._poolSize)
this._connection = mysql.createConnection(this._params);
else
this._connection = mysql.createPool({ ...this._params, connectionLimit: this._poolSize });
this._connection = await mysql.createConnection(this._params);
else {
this._connection = mysql.createPool({
...this._params,
connectionLimit: this._poolSize,
typeCast: (field, next) => {
if (field.type === 'DATETIME')
return field.string();
else
return next();
}
});
}
}
/**
@@ -120,6 +134,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
use (schema) {
this._schema = schema;
return this.raw(`USE \`${schema}\``);
}
@@ -290,29 +305,78 @@ export class MySQLClient extends AntaresCore {
.select('*')
.schema('information_schema')
.from('COLUMNS')
.where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'` })
.where({ TABLE_SCHEMA: `= '${this._schema || schema}'`, TABLE_NAME: `= '${table}'` })
.orderBy({ ORDINAL_POSITION: 'ASC' })
.run();
const { rows: fields } = await this.raw(`SHOW CREATE TABLE \`${this._schema || schema}\`.\`${table}\``);
const remappedFields = fields.map(row => {
let n = 0;
return row['Create Table']
.split('')
.reduce((acc, curr) => {
if (curr === ')') n--;
if (n !== 0) acc += curr;
if (curr === '(') n++;
return acc;
}, '')
.replaceAll('\n', '')
.split(',')
.map(f => {
try {
const fieldArr = f.trim().split(' ');
const nameAndType = fieldArr.slice(0, 2);
if (nameAndType[0].charAt(0) !== '`') return false;
const details = fieldArr.slice(2).join(' ');
let defaultValue = null;
if (details.includes('DEFAULT'))
defaultValue = details.match(/(?<=DEFAULT ).*?$/gs)[0].split(' COMMENT')[0];
const typeAndLength = nameAndType[1].replace(')', '').split('(');
return {
name: nameAndType[0].replaceAll('`', ''),
type: typeAndLength[0].toUpperCase(),
length: typeAndLength[1] ? typeAndLength[1] : null,
default: defaultValue
};
}
catch (err) {
return false;
}
})
.filter(Boolean)
.reduce((acc, curr) => {
acc[curr.name] = curr;
return acc;
}, {});
})[0];
return rows.map(field => {
let numLength = field.COLUMN_TYPE.match(/int\(([^)]+)\)/);
numLength = numLength ? +numLength.pop() : null;
const enumValues = /(enum|set)/.test(field.COLUMN_TYPE)
? field.COLUMN_TYPE.match(/\(([^)]+)\)/)[0].slice(1, -1)
: null;
return {
name: field.COLUMN_NAME,
key: field.COLUMN_KEY.toLowerCase(),
type: field.DATA_TYPE.toUpperCase(),
type: remappedFields[field.COLUMN_NAME].type,
schema: field.TABLE_SCHEMA,
table: field.TABLE_NAME,
numPrecision: field.NUMERIC_PRECISION,
numLength,
enumValues,
datePrecision: field.DATETIME_PRECISION,
charLength: field.CHARACTER_MAXIMUM_LENGTH,
nullable: field.IS_NULLABLE.includes('YES'),
unsigned: field.COLUMN_TYPE.includes('unsigned'),
zerofill: field.COLUMN_TYPE.includes('zerofill'),
order: field.ORDINAL_POSITION,
default: field.COLUMN_DEFAULT,
default: remappedFields[field.COLUMN_NAME].default,
charset: field.CHARACTER_SET_NAME,
collation: field.COLLATION_NAME,
autoIncrement: field.EXTRA.includes('auto_increment'),
@@ -471,7 +535,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
async dropView (params) {
const sql = `DROP VIEW \`${params.view}\``;
const sql = `DROP VIEW \`${this._schema}\`.\`${params.view}\``;
return await this.raw(sql);
}
@@ -483,10 +547,10 @@ export class MySQLClient extends AntaresCore {
*/
async alterView (params) {
const { view } = params;
let sql = `ALTER ALGORITHM = ${view.algorithm}${view.definer ? ` DEFINER=${view.definer}` : ''} SQL SECURITY ${view.security} VIEW \`${view.oldName}\` AS ${view.sql} ${view.updateOption ? `WITH ${view.updateOption} CHECK OPTION` : ''}`;
let sql = `ALTER ALGORITHM = ${view.algorithm}${view.definer ? ` DEFINER=${view.definer}` : ''} SQL SECURITY ${view.security} VIEW \`${this._schema}\`.\`${view.oldName}\` AS ${view.sql} ${view.updateOption ? `WITH ${view.updateOption} CHECK OPTION` : ''}`;
if (view.name !== view.oldName)
sql += `; RENAME TABLE \`${view.oldName}\` TO \`${view.name}\``;
sql += `; RENAME TABLE \`${this._schema}\`.\`${view.oldName}\` TO \`${this._schema}\`.\`${view.name}\``;
return await this.raw(sql);
}
@@ -498,7 +562,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
async createView (view) {
const sql = `CREATE ALGORITHM = ${view.algorithm} ${view.definer ? `DEFINER=${view.definer} ` : ''}SQL SECURITY ${view.security} VIEW \`${view.name}\` AS ${view.sql} ${view.updateOption ? `WITH ${view.updateOption} CHECK OPTION` : ''}`;
const sql = `CREATE ALGORITHM = ${view.algorithm} ${view.definer ? `DEFINER=${view.definer} ` : ''}SQL SECURITY ${view.security} VIEW \`${this._schema}\`.\`${view.name}\` AS ${view.sql} ${view.updateOption ? `WITH ${view.updateOption} CHECK OPTION` : ''}`;
return await this.raw(sql);
}
@@ -531,7 +595,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
async dropTrigger (params) {
const sql = `DROP TRIGGER \`${params.trigger}\``;
const sql = `DROP TRIGGER \`${this._schema}\`.\`${params.trigger}\``;
return await this.raw(sql);
}
@@ -564,7 +628,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
async createTrigger (trigger) {
const sql = `CREATE ${trigger.definer ? `DEFINER=${trigger.definer} ` : ''}TRIGGER \`${trigger.name}\` ${trigger.event1} ${trigger.event2} ON \`${trigger.table}\` FOR EACH ROW ${trigger.sql}`;
const sql = `CREATE ${trigger.definer ? `DEFINER=${trigger.definer} ` : ''}TRIGGER \`${this._schema}\`.\`${trigger.name}\` ${trigger.event1} ${trigger.event2} ON \`${trigger.table}\` FOR EACH ROW ${trigger.sql}`;
return await this.raw(sql, { split: false });
}
@@ -578,7 +642,7 @@ export class MySQLClient extends AntaresCore {
const sql = `SHOW CREATE PROCEDURE \`${schema}\`.\`${routine}\``;
const results = await this.raw(sql);
return results.rows.map(row => {
return results.rows.map(async row => {
if (!row['Create Procedure']) {
return {
definer: null,
@@ -592,22 +656,23 @@ export class MySQLClient extends AntaresCore {
};
}
const parameters = row['Create Procedure']
.match(/(\([^()]*(?:(?:\([^()]*\))[^()]*)*\)\s*)/s)[0]
.replaceAll('\r', '')
.replaceAll('\t', '')
.slice(1, -1)
.split(',')
.map(el => {
const param = el.split(' ');
const type = param[2] ? param[2].replace(')', '').split('(') : ['', null];
return {
name: param[1] ? param[1].replaceAll('`', '') : '',
type: type[0].replaceAll('\n', ''),
length: +type[1] ? +type[1].replace(/\D/g, '') : '',
context: param[0] ? param[0].replace('\n', '') : ''
};
}).filter(el => el.name);
const sql = `SELECT *
FROM information_schema.parameters
WHERE SPECIFIC_NAME = '${routine}'
AND SPECIFIC_SCHEMA = '${schema}'
ORDER BY ORDINAL_POSITION
`;
const results = await this.raw(sql);
const parameters = results.rows.map(row => {
return {
name: row.PARAMETER_NAME,
type: row.DATA_TYPE.toUpperCase(),
length: row.NUMERIC_PRECISION || row.DATETIME_PRECISION || row.CHARACTER_MAXIMUM_LENGTH || '',
context: row.PARAMETER_MODE
};
});
let dataAccess = 'CONTAINS SQL';
if (row['Create Procedure'].includes('NO SQL'))
@@ -637,7 +702,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
async dropRoutine (params) {
const sql = `DROP PROCEDURE \`${params.routine}\``;
const sql = `DROP PROCEDURE \`${this._schema}\`.\`${params.routine}\``;
return await this.raw(sql);
}
@@ -677,7 +742,7 @@ export class MySQLClient extends AntaresCore {
}, []).join(',')
: '';
const sql = `CREATE ${routine.definer ? `DEFINER=${routine.definer} ` : ''}PROCEDURE \`${routine.name}\`(${parameters})
const sql = `CREATE ${routine.definer ? `DEFINER=${routine.definer} ` : ''}PROCEDURE \`${this._schema}\`.\`${routine.name}\`(${parameters})
LANGUAGE SQL
${routine.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'}
${routine.dataAccess}
@@ -698,7 +763,7 @@ export class MySQLClient extends AntaresCore {
const sql = `SHOW CREATE FUNCTION \`${schema}\`.\`${func}\``;
const results = await this.raw(sql);
return results.rows.map(row => {
return results.rows.map(async row => {
if (!row['Create Function']) {
return {
definer: null,
@@ -714,22 +779,23 @@ export class MySQLClient extends AntaresCore {
};
}
const parameters = row['Create Function']
.match(/(\([^()]*(?:(?:\([^()]*\))[^()]*)*\)\s*)/s)[0]
.replaceAll('\r', '')
.replaceAll('\t', '')
.slice(1, -1)
.split(',')
.map(el => {
const param = el.split(' ');
const type = param[1] ? param[1].replace(')', '').split('(') : ['', null];
const sql = `SELECT *
FROM information_schema.parameters
WHERE SPECIFIC_NAME = '${func}'
AND SPECIFIC_SCHEMA = '${schema}'
ORDER BY ORDINAL_POSITION
`;
return {
name: param[0] ? param[0].replaceAll('`', '') : '',
type: type[0],
length: +type[1] ? +type[1].replace(/\D/g, '') : ''
};
}).filter(el => el.name);
const results = await this.raw(sql);
const parameters = results.rows.filter(row => row.PARAMETER_MODE).map(row => {
return {
name: row.PARAMETER_NAME,
type: row.DATA_TYPE.toUpperCase(),
length: row.NUMERIC_PRECISION || row.DATETIME_PRECISION || row.CHARACTER_MAXIMUM_LENGTH || '',
context: row.PARAMETER_MODE
};
});
let dataAccess = 'CONTAINS SQL';
if (row['Create Function'].includes('NO SQL'))
@@ -763,7 +829,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
async dropFunction (params) {
const sql = `DROP FUNCTION \`${params.func}\``;
const sql = `DROP FUNCTION \`${this._schema}\`.\`${params.func}\``;
return await this.raw(sql);
}
@@ -801,13 +867,15 @@ export class MySQLClient extends AntaresCore {
return acc;
}, []).join(',');
const sql = `CREATE ${func.definer ? `DEFINER=${func.definer} ` : ''}FUNCTION \`${func.name}\`(${parameters}) RETURNS ${func.returns}${func.returnsLength ? `(${func.returnsLength})` : ''}
const body = func.returns ? func.sql : 'BEGIN\n RETURN 0;\nEND';
const sql = `CREATE ${func.definer ? `DEFINER=${func.definer} ` : ''}FUNCTION \`${this._schema}\`.\`${func.name}\`(${parameters}) RETURNS ${func.returns || 'SMALLINT'}${func.returnsLength ? `(${func.returnsLength})` : ''}
LANGUAGE SQL
${func.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'}
${func.dataAccess}
SQL SECURITY ${func.security}
COMMENT '${func.comment}'
${func.sql}`;
${body}`;
return await this.raw(sql, { split: false });
}
@@ -853,7 +921,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
async dropEvent (params) {
const sql = `DROP EVENT \`${params.scheduler}\``;
const sql = `DROP EVENT \`${this._schema}\`.\`${params.scheduler}\``;
return await this.raw(sql);
}
@@ -869,13 +937,13 @@ export class MySQLClient extends AntaresCore {
if (scheduler.execution === 'EVERY' && scheduler.every[0].includes('-'))
scheduler.every[0] = `'${scheduler.every[0]}'`;
const sql = `ALTER ${scheduler.definer ? ` DEFINER=${scheduler.definer}` : ''} EVENT \`${scheduler.oldName}\`
const sql = `ALTER ${scheduler.definer ? ` DEFINER=${scheduler.definer}` : ''} EVENT \`${this._schema}\`.\`${scheduler.oldName}\`
ON SCHEDULE
${scheduler.execution === 'EVERY'
? `EVERY ${scheduler.every.join(' ')}${scheduler.starts ? ` STARTS '${scheduler.starts}'` : ''}${scheduler.ends ? ` ENDS '${scheduler.ends}'` : ''}`
: `AT '${scheduler.at}'`}
ON COMPLETION${!scheduler.preserve ? ' NOT' : ''} PRESERVE
${scheduler.name !== scheduler.oldName ? `RENAME TO \`${scheduler.name}\`` : ''}
${scheduler.name !== scheduler.oldName ? `RENAME TO \`${this._schema}\`.\`${scheduler.name}\`` : ''}
${scheduler.state}
COMMENT '${scheduler.comment}'
DO ${scheduler.sql}`;
@@ -890,7 +958,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
async createEvent (scheduler) {
const sql = `CREATE ${scheduler.definer ? ` DEFINER=${scheduler.definer}` : ''} EVENT \`${scheduler.name}\`
const sql = `CREATE ${scheduler.definer ? ` DEFINER=${scheduler.definer}` : ''} EVENT \`${this._schema}\`.\`${scheduler.name}\`
ON SCHEDULE
${scheduler.execution === 'EVERY'
? `EVERY ${scheduler.every.join(' ')}${scheduler.starts ? ` STARTS '${scheduler.starts}'` : ''}${scheduler.ends ? ` ENDS '${scheduler.ends}'` : ''}`
@@ -1027,7 +1095,7 @@ export class MySQLClient extends AntaresCore {
engine
} = params;
const sql = `CREATE TABLE \`${name}\` (\`${name}_ID\` INT NULL) COMMENT='${comment}', COLLATE='${collation}', ENGINE=${engine}`;
const sql = `CREATE TABLE \`${this._schema}\`.\`${name}\` (\`${name}_ID\` INT NULL) COMMENT='${comment}', COLLATE='${collation}', ENGINE=${engine}`;
return await this.raw(sql);
}
@@ -1049,7 +1117,7 @@ export class MySQLClient extends AntaresCore {
options
} = params;
let sql = `ALTER TABLE \`${table}\` `;
let sql = `ALTER TABLE \`${this._schema || params.options.schema}\`.\`${table}\` `;
const alterColumns = [];
// OPTIONS
@@ -1061,7 +1129,7 @@ export class MySQLClient extends AntaresCore {
// ADD FIELDS
additions.forEach(addition => {
const typeInfo = this._getTypeInfo(addition.type);
const length = typeInfo.length ? addition.numLength || addition.charLength || addition.datePrecision : false;
const length = typeInfo.length ? addition.enumValues || addition.numLength || addition.charLength || addition.datePrecision : false;
alterColumns.push(`ADD COLUMN \`${addition.name}\`
${addition.type.toUpperCase()}${length ? `(${length})` : ''}
@@ -1099,7 +1167,7 @@ export class MySQLClient extends AntaresCore {
// CHANGE FIELDS
changes.forEach(change => {
const typeInfo = this._getTypeInfo(change.type);
const length = typeInfo.length ? change.numLength || change.charLength || change.datePrecision : false;
const length = typeInfo.length ? change.enumValues || change.numLength || change.charLength || change.datePrecision : false;
alterColumns.push(`CHANGE COLUMN \`${change.orgName}\` \`${change.name}\`
${change.type.toUpperCase()}${length ? `(${length})` : ''}
@@ -1161,7 +1229,7 @@ export class MySQLClient extends AntaresCore {
sql += alterColumns.join(', ');
// RENAME
if (options.name) sql += `; RENAME TABLE \`${table}\` TO \`${options.name}\``;
if (options.name) sql += `; RENAME TABLE \`${this._schema}\`.\`${table}\` TO \`${this._schema}\`.\`${options.name}\``;
return await this.raw(sql);
}
@@ -1173,7 +1241,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
async truncateTable (params) {
const sql = `TRUNCATE TABLE \`${params.table}\``;
const sql = `TRUNCATE TABLE \`${this._schema}\`.\`${params.table}\``;
return await this.raw(sql);
}
@@ -1184,7 +1252,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
async dropTable (params) {
const sql = `DROP TABLE \`${params.table}\``;
const sql = `DROP TABLE \`${this._schema}\`.\`${params.table}\``;
return await this.raw(sql);
}
@@ -1239,7 +1307,10 @@ export class MySQLClient extends AntaresCore {
// LIMIT
const limitRaw = this._query.limit.length ? `LIMIT ${this._query.limit.join(', ')} ` : '';
return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${insertRaw}`;
// OFFSET
const offsetRaw = this._query.offset.length ? `OFFSET ${this._query.offset.join(', ')} ` : '';
return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${offsetRaw}${insertRaw}`;
}
/**
@@ -1252,18 +1323,24 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
async raw (sql, args) {
if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder
args = {
nest: false,
details: false,
split: true,
...args
};
const nestTables = args.nest ? '.' : false;
const resultsArr = [];
let paramsArr = [];
const queries = args.split ? sql.split(';') : [sql];
const queries = args.split ? sql.split(/((?:[^;'"]*(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*')[^;'"]*)+)|;/gm) : [sql];
const isPool = typeof this._connection.getConnection === 'function';
const connection = isPool ? await this._connection.getConnection() : this._connection;
if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder
if (args.schema)
await connection.query(`USE \`${args.schema}\``);
for (const query of queries) {
if (!query) continue;
@@ -1272,87 +1349,87 @@ export class MySQLClient extends AntaresCore {
let keysArr = [];
const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => {
this._connection.query({ sql: query, nestTables }, async (err, response, fields) => {
connection.query({ sql: query, nestTables }).then(async ([response, fields]) => {
timeStop = new Date();
const queryResult = response;
if (err)
reject(err);
else {
let remappedFields = fields
? fields.map(field => {
if (!field || Array.isArray(field))
return false;
let remappedFields = fields
? fields.map(field => {
if (!field || Array.isArray(field))
return false;
const type = this._getType(field);
const type = this._getType(field);
return {
name: field.orgName,
alias: field.name,
orgName: field.orgName,
schema: field.schema,
table: field.table,
tableAlias: field.table,
orgTable: field.orgTable,
type: type.name,
length: type.length
};
}).filter(Boolean)
: [];
if (args.details) {
let cachedTable;
if (remappedFields.length) {
paramsArr = remappedFields.map(field => {
if (field.orgTable) cachedTable = field.orgTable;// Needed for some queries on information_schema
return {
name: field.orgName,
alias: field.name,
orgName: field.orgName,
schema: field.schema,
table: field.table,
tableAlias: field.table,
orgTable: field.orgTable,
type: type.name,
length: type.length
table: field.orgTable || cachedTable,
schema: field.schema || 'INFORMATION_SCHEMA'
};
}).filter(Boolean)
: [];
}).filter((val, i, arr) => arr.findIndex(el => el.schema === val.schema && el.table === val.table) === i);
if (args.details) {
let cachedTable;
for (const paramObj of paramsArr) {
if (!paramObj.table || !paramObj.schema) continue;
if (remappedFields.length) {
paramsArr = remappedFields.map(field => {
if (field.orgTable) cachedTable = field.orgTable;// Needed for some queries on information_schema
return {
table: field.orgTable || cachedTable,
schema: field.schema || 'INFORMATION_SCHEMA'
};
}).filter((val, i, arr) => arr.findIndex(el => el.schema === val.schema && el.table === val.table) === i);
try { // Column details
const response = await this.getTableColumns(paramObj);
remappedFields = remappedFields.map(field => {
const detailedField = response.find(f => f.name === field.name);
if (detailedField && field.orgTable === paramObj.table && field.schema === paramObj.schema)
field = { ...field, ...detailedField };
return field;
});
}
catch (err) {
if (isPool) connection.release();
reject(err);
}
for (const paramObj of paramsArr) {
if (!paramObj.table || !paramObj.schema) continue;
try { // Column details
const response = await this.getTableColumns(paramObj);
remappedFields = remappedFields.map(field => {
const detailedField = response.find(f => f.name === field.name);
if (detailedField && field.orgTable === paramObj.table && field.schema === paramObj.schema)
field = { ...detailedField, ...field };
return field;
});
}
catch (err) {
reject(err);
}
try { // Key usage (foreign keys)
const response = await this.getKeyUsage(paramObj);
keysArr = keysArr ? [...keysArr, ...response] : response;
}
catch (err) {
reject(err);
}
try { // Key usage (foreign keys)
const response = await this.getKeyUsage(paramObj);
keysArr = keysArr ? [...keysArr, ...response] : response;
}
catch (err) {
if (isPool) connection.release();
reject(err);
}
}
}
resolve({
duration: timeStop - timeStart,
rows: Array.isArray(queryResult) ? queryResult.some(el => Array.isArray(el)) ? [] : queryResult : false,
report: !Array.isArray(queryResult) ? queryResult : false,
fields: remappedFields,
keys: keysArr
});
}
});
resolve({
duration: timeStop - timeStart,
rows: Array.isArray(queryResult) ? queryResult.some(el => Array.isArray(el)) ? [] : queryResult : false,
report: !Array.isArray(queryResult) ? queryResult : false,
fields: remappedFields,
keys: keysArr
});
}).catch(reject);
});
resultsArr.push({ rows, report, fields, keys, duration });
}
if (isPool) connection.release();
return resultsArr.length === 1 ? resultsArr[0] : resultsArr;
}
}

View File

@@ -1,5 +1,5 @@
'use strict';
import pg, { Pool, Client, types } from 'pg';
import { Pool, Client, types } from 'pg';
import { parse } from 'pgsql-ast-parser';
import { AntaresCore } from '../AntaresCore';
import dataTypes from 'common/data-types/postgresql';
@@ -8,11 +8,11 @@ function pgToString (value) {
return value.toString();
}
pg.types.setTypeParser(1082, pgToString); // date
pg.types.setTypeParser(1083, pgToString); // time
pg.types.setTypeParser(1114, pgToString); // timestamp
pg.types.setTypeParser(1184, pgToString); // timestamptz
pg.types.setTypeParser(1266, pgToString); // timetz
types.setTypeParser(1082, pgToString); // date
types.setTypeParser(1083, pgToString); // time
types.setTypeParser(1114, pgToString); // timestamp
types.setTypeParser(1184, pgToString); // timestamptz
types.setTypeParser(1266, pgToString); // timetz
export class PostgreSQLClient extends AntaresCore {
constructor (args) {
@@ -23,54 +23,16 @@ export class PostgreSQLClient extends AntaresCore {
this.types = {};
for (const key in types.builtins)
this.types[types.builtins[key]] = key;
}
_getType (field) {
let name = this.types[field.columnType];
let length = field.columnLength;
if (['DATE', 'TIME', 'YEAR', 'DATETIME'].includes(name))
length = field.decimals;
if (name === 'TIMESTAMP')
length = 0;
if (field.charsetNr === 63) { // if binary
if (name === 'CHAR')
name = 'BINARY';
else if (name === 'VARCHAR')
name = 'VARBINARY';
}
if (name === 'BLOB') {
switch (length) {
case 765:
name = 'TYNITEXT';
break;
case 196605:
name = 'TEXT';
break;
case 50331645:
name = 'MEDIUMTEXT';
break;
case 4294967295:
name = field.charsetNr === 63 ? 'LONGBLOB' : 'LONGTEXT';
break;
case 255:
name = 'TINYBLOB';
break;
case 65535:
name = 'BLOB';
break;
case 16777215:
name = 'MEDIUMBLOB';
break;
default:
name = field.charsetNr === 63 ? 'BLOB' : 'TEXT';
}
}
return { name, length };
this._arrayTypes = {
_int2: 'SMALLINT',
_int4: 'INTEGER',
_int8: 'BIGINT',
_float4: 'REAL',
_float8: 'DOUBLE PRECISION',
_char: '"CHAR"',
_varchar: 'CHARACTER VARYING'
};
}
_getTypeInfo (type) {
@@ -79,6 +41,12 @@ export class PostgreSQLClient extends AntaresCore {
.filter(_type => _type.name === type.toUpperCase())[0];
}
_getArrayType (type) {
if (Object.keys(this._arrayTypes).includes(type))
return this._arrayTypes[type];
return type.replace('_', '');
}
/**
* @memberof PostgreSQLClient
*/
@@ -109,7 +77,8 @@ export class PostgreSQLClient extends AntaresCore {
*/
use (schema) {
this._schema = schema;
return this.raw(`SET search_path TO '${schema}', '$user'`);
if (schema)
return this.raw(`SET search_path TO ${schema}`);
}
/**
@@ -189,41 +158,43 @@ export class PostgreSQLClient extends AntaresCore {
});
// PROCEDURES
const remappedProcedures = procedures.filter(procedure => procedure.Db === db.database).map(procedure => {
const remappedProcedures = procedures.filter(procedure => procedure.routine_schema === db.database).map(procedure => {
return {
name: procedure.Name,
type: procedure.Type,
definer: procedure.Definer,
created: procedure.Created,
updated: procedure.Modified,
comment: procedure.Comment,
charset: procedure.character_set_client,
security: procedure.Security_type
name: procedure.routine_name,
type: procedure.routine_type,
security: procedure.security_type
};
});
// FUNCTIONS
const remappedFunctions = functions.filter(func => func.Db === db.database).map(func => {
const remappedFunctions = functions.filter(func => func.routine_schema === db.database && func.data_type !== 'trigger').map(func => {
return {
name: func.routine_name,
type: func.routine_type,
definer: null, // func.Definer,
created: null, // func.Created,
updated: null, // func.Modified,
comment: null, // func.Comment,
charset: null, // func.character_set_client,
security: func.security_type
};
});
// TRIGGER FUNCTIONS
const remappedTriggerFunctions = functions.filter(func => func.routine_schema === db.database && func.data_type === 'trigger').map(func => {
return {
name: func.routine_name,
type: func.routine_type,
security: func.security_type
};
});
// TRIGGERS
const remappedTriggers = triggersArr.filter(trigger => trigger.Db === db.database).map(trigger => {
return {
name: trigger.trigger_name,
name: `${trigger.table_name}.${trigger.trigger_name}`,
orgName: trigger.trigger_name,
timing: trigger.activation,
definer: trigger.definition, // ???
definer: '',
definition: trigger.definition,
event: trigger.event,
table: trigger.table_trigger,
sqlMode: trigger.sql_mode
table: trigger.table_name,
sqlMode: ''
};
});
@@ -233,6 +204,7 @@ export class PostgreSQLClient extends AntaresCore {
functions: remappedFunctions,
procedures: remappedProcedures,
triggers: remappedTriggers,
triggerFunctions: remappedTriggerFunctions,
schedulers: []
};
}
@@ -256,7 +228,7 @@ export class PostgreSQLClient extends AntaresCore {
* @returns {Object} table scructure
* @memberof PostgreSQLClient
*/
async getTableColumns ({ schema, table }) {
async getTableColumns ({ schema, table }, arrayRemap = true) {
const { rows } = await this
.select('*')
.schema('information_schema')
@@ -266,10 +238,17 @@ export class PostgreSQLClient extends AntaresCore {
.run();
return rows.map(field => {
let type = field.data_type;
const isArray = type === 'ARRAY';
if (isArray && arrayRemap)
type = this._getArrayType(field.udt_name);
return {
name: field.column_name,
key: null,
type: field.data_type.toUpperCase(),
type: type.toUpperCase(),
isArray,
schema: field.table_schema,
table: field.table_name,
numPrecision: field.numeric_precision,
@@ -282,7 +261,7 @@ export class PostgreSQLClient extends AntaresCore {
default: field.column_default,
charset: field.character_set_name,
collation: field.collation_name,
autoIncrement: null,
autoIncrement: false,
onUpdate: null,
comment: ''
};
@@ -297,11 +276,14 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async getTableIndexes ({ schema, table }) {
if (schema !== 'public')
await this.use(schema);
const { rows } = await this.raw(`WITH ndx_list AS (
SELECT pg_index.indexrelid, pg_class.oid
FROM pg_index, pg_class
WHERE pg_class.relname = '${table}' AND pg_class.oid = pg_index.indrelid), ndx_cols AS (
SELECT pg_class.relname, UNNEST(i.indkey) AS col_ndx, CASE i.indisprimary WHEN TRUE THEN 'PRIMARY' ELSE CASE i.indisunique WHEN TRUE THEN 'UNIQUE' ELSE 'KEY' END END AS CONSTRAINT_TYPE, pg_class.oid
SELECT pg_class.relname, UNNEST(i.indkey) AS col_ndx, CASE i.indisprimary WHEN TRUE THEN 'PRIMARY' ELSE CASE i.indisunique WHEN TRUE THEN 'UNIQUE' ELSE 'INDEX' END END AS CONSTRAINT_TYPE, pg_class.oid
FROM pg_class
JOIN pg_index i ON (pg_class.oid = i.indexrelid)
JOIN ndx_list ON (pg_class.oid = ndx_list.indexrelid)
@@ -317,7 +299,10 @@ export class PostgreSQLClient extends AntaresCore {
name: row.constraint_name,
column: row.column_name,
indexType: null,
type: row.constraint_type
type: row.constraint_type,
cardinality: null,
comment: '',
indexComment: ''
};
});
}
@@ -359,6 +344,8 @@ export class PostgreSQLClient extends AntaresCore {
tc.constraint_name,
tc.table_name,
kcu.column_name,
kcu.position_in_unique_constraint,
kcu.ordinal_position,
ccu.table_schema AS foreign_table_schema,
ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name,
@@ -449,17 +436,17 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async getViewInformations ({ schema, view }) {
const sql = `SHOW CREATE VIEW \`${schema}\`.\`${view}\``;
const sql = `SELECT "definition" FROM "pg_views" WHERE "viewname"='${view}' AND "schemaname"='${schema}'`;
const results = await this.raw(sql);
return results.rows.map(row => {
return {
algorithm: row['Create View'].match(/(?<=CREATE ALGORITHM=).*?(?=\s)/gs)[0],
definer: row['Create View'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0],
security: row['Create View'].match(/(?<=SQL SECURITY ).*?(?=\s)/gs)[0],
updateOption: row['Create View'].match(/(?<=WITH ).*?(?=\s)/gs) ? row['Create View'].match(/(?<=WITH ).*?(?=\s)/gs)[0] : '',
sql: row['Create View'].match(/(?<=AS ).*?$/gs)[0],
name: row.View
algorithm: '',
definer: '',
security: '',
updateOption: '',
sql: row.definition,
name: view
};
})[0];
}
@@ -471,7 +458,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async dropView (params) {
const sql = `DROP VIEW \`${params.view}\``;
const sql = `DROP VIEW ${this._schema}.${params.view}`;
return await this.raw(sql);
}
@@ -483,10 +470,10 @@ export class PostgreSQLClient extends AntaresCore {
*/
async alterView (params) {
const { view } = params;
let sql = `ALTER ALGORITHM = ${view.algorithm}${view.definer ? ` DEFINER=${view.definer}` : ''} SQL SECURITY ${view.security} VIEW \`${view.oldName}\` AS ${view.sql} ${view.updateOption ? `WITH ${view.updateOption} CHECK OPTION` : ''}`;
let sql = `CREATE OR REPLACE VIEW ${this._schema}.${view.oldName} AS ${view.sql}`;
if (view.name !== view.oldName)
sql += `; RENAME TABLE \`${view.oldName}\` TO \`${view.name}\``;
sql += `; ALTER VIEW ${this._schema}.${view.oldName} RENAME TO ${view.name}`;
return await this.raw(sql);
}
@@ -498,7 +485,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async createView (view) {
const sql = `CREATE ALGORITHM = ${view.algorithm} ${view.definer ? `DEFINER=${view.definer} ` : ''}SQL SECURITY ${view.security} VIEW \`${view.name}\` AS ${view.sql} ${view.updateOption ? `WITH ${view.updateOption} CHECK OPTION` : ''}`;
const sql = `CREATE VIEW ${this._schema}.${view.name} AS ${view.sql}`;
return await this.raw(sql);
}
@@ -531,7 +518,8 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async dropTrigger (params) {
const sql = `DROP TRIGGER \`${params.trigger}\``;
const triggerParts = params.trigger.split('.');
const sql = `DROP TRIGGER ${triggerParts[1]} ON ${triggerParts[0]}`;
return await this.raw(sql);
}
@@ -575,16 +563,16 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async getRoutineInformations ({ schema, routine }) {
const sql = `SHOW CREATE PROCEDURE \`${schema}\`.\`${routine}\``;
const sql = `SELECT pg_get_functiondef((SELECT oid FROM pg_proc WHERE proname = '${routine}'));`;
const results = await this.raw(sql);
return results.rows.map(row => {
if (!row['Create Procedure']) {
return results.rows.map(async row => {
if (!row.pg_get_functiondef) {
return {
definer: null,
sql: '',
parameters: [],
name: row.Procedure,
name: routine,
comment: '',
security: 'DEFINER',
deterministic: false,
@@ -592,40 +580,48 @@ export class PostgreSQLClient extends AntaresCore {
};
}
const parameters = row['Create Procedure']
.match(/(\([^()]*(?:(?:\([^()]*\))[^()]*)*\)\s*)/s)[0]
.replaceAll('\r', '')
.replaceAll('\t', '')
.slice(1, -1)
.split(',')
.map(el => {
const param = el.split(' ');
const type = param[2] ? param[2].replace(')', '').split('(') : ['', null];
return {
name: param[1] ? param[1].replaceAll('`', '') : '',
type: type[0].replaceAll('\n', ''),
length: +type[1] ? +type[1].replace(/\D/g, '') : '',
context: param[0] ? param[0].replace('\n', '') : ''
};
}).filter(el => el.name);
const sql = `SELECT proc.specific_schema AS procedure_schema,
proc.specific_name,
proc.routine_name AS procedure_name,
proc.external_language,
args.parameter_name,
args.parameter_mode,
args.data_type
FROM information_schema.routines proc
LEFT JOIN information_schema.parameters args
ON proc.specific_schema = args.specific_schema
AND proc.specific_name = args.specific_name
WHERE proc.routine_schema not in ('pg_catalog', 'information_schema')
AND proc.routine_type = 'PROCEDURE'
AND proc.routine_name = '${routine}'
AND proc.specific_schema = '${schema}'
ORDER BY procedure_schema,
specific_name,
procedure_name,
args.ordinal_position
`;
let dataAccess = 'CONTAINS SQL';
if (row['Create Procedure'].includes('NO SQL'))
dataAccess = 'NO SQL';
if (row['Create Procedure'].includes('READS SQL DATA'))
dataAccess = 'READS SQL DATA';
if (row['Create Procedure'].includes('MODIFIES SQL DATA'))
dataAccess = 'MODIFIES SQL DATA';
const results = await this.raw(sql);
const parameters = results.rows.map(row => {
return {
name: row.parameter_name,
type: row.data_type.toUpperCase(),
length: '',
context: row.parameter_mode
};
});
return {
definer: row['Create Procedure'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0],
sql: row['Create Procedure'].match(/(BEGIN|begin)(.*)(END|end)/gs)[0],
definer: '',
sql: row.pg_get_functiondef.match(/(\$(.*)\$)(.*)(\$(.*)\$)/gs)[0],
parameters: parameters || [],
name: row.Procedure,
comment: row['Create Procedure'].match(/(?<=COMMENT ').*?(?=')/gs) ? row['Create Procedure'].match(/(?<=COMMENT ').*?(?=')/gs)[0] : '',
security: row['Create Procedure'].includes('SQL SECURITY INVOKER') ? 'INVOKER' : 'DEFINER',
deterministic: row['Create Procedure'].includes('DETERMINISTIC'),
dataAccess
name: routine,
comment: '',
security: row.pg_get_functiondef.includes('SECURITY DEFINER') ? 'DEFINER' : 'INVOKER',
deterministic: null,
dataAccess: null,
language: row.pg_get_functiondef.match(/(?<=LANGUAGE )(.*)(?<=[\S+\n\r\s])/gm)[0]
};
})[0];
}
@@ -637,7 +633,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async dropRoutine (params) {
const sql = `DROP PROCEDURE \`${params.routine}\``;
const sql = `DROP PROCEDURE ${this._schema}.${params.routine}`;
return await this.raw(sql);
}
@@ -672,18 +668,18 @@ export class PostgreSQLClient extends AntaresCore {
async createRoutine (routine) {
const parameters = 'parameters' in routine
? routine.parameters.reduce((acc, curr) => {
acc.push(`${curr.context} \`${curr.name}\` ${curr.type}${curr.length ? `(${curr.length})` : ''}`);
acc.push(`${curr.context} ${curr.name} ${curr.type}${curr.length ? `(${curr.length})` : ''}`);
return acc;
}, []).join(',')
: '';
const sql = `CREATE ${routine.definer ? `DEFINER=${routine.definer} ` : ''}PROCEDURE \`${routine.name}\`(${parameters})
LANGUAGE SQL
${routine.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'}
${routine.dataAccess}
SQL SECURITY ${routine.security}
COMMENT '${routine.comment}'
${routine.sql}`;
if (this._schema !== 'public')
await this.use(this._schema);
const sql = `CREATE PROCEDURE ${this._schema}.${routine.name}(${parameters})
LANGUAGE ${routine.language}
SECURITY ${routine.security}
AS ${routine.sql}`;
return await this.raw(sql, { split: false });
}
@@ -695,63 +691,66 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async getFunctionInformations ({ schema, func }) {
const sql = `SHOW CREATE FUNCTION \`${schema}\`.\`${func}\``;
const sql = `SELECT pg_get_functiondef((SELECT oid FROM pg_proc WHERE proname = '${func}'));`;
const results = await this.raw(sql);
return results.rows.map(row => {
if (!row['Create Function']) {
return results.rows.map(async row => {
if (!row.pg_get_functiondef) {
return {
definer: null,
sql: '',
parameters: [],
name: row.Procedure,
name: func,
comment: '',
security: 'DEFINER',
deterministic: false,
dataAccess: 'CONTAINS SQL',
returns: 'INT',
returnsLength: null
dataAccess: 'CONTAINS SQL'
};
}
const parameters = row['Create Function']
.match(/(\([^()]*(?:(?:\([^()]*\))[^()]*)*\)\s*)/s)[0]
.replaceAll('\r', '')
.replaceAll('\t', '')
.slice(1, -1)
.split(',')
.map(el => {
const param = el.split(' ');
const type = param[1] ? param[1].replace(')', '').split('(') : ['', null];
const sql = `SELECT proc.specific_schema AS procedure_schema,
proc.specific_name,
proc.routine_name AS procedure_name,
proc.external_language,
args.parameter_name,
args.parameter_mode,
args.data_type
FROM information_schema.routines proc
LEFT JOIN information_schema.parameters args
ON proc.specific_schema = args.specific_schema
AND proc.specific_name = args.specific_name
WHERE proc.routine_schema not in ('pg_catalog', 'information_schema')
AND proc.routine_type = 'FUNCTION'
AND proc.routine_name = '${func}'
AND proc.specific_schema = '${schema}'
ORDER BY procedure_schema,
specific_name,
procedure_name,
args.ordinal_position
`;
return {
name: param[0] ? param[0].replaceAll('`', '') : '',
type: type[0],
length: +type[1] ? +type[1].replace(/\D/g, '') : ''
};
}).filter(el => el.name);
const results = await this.raw(sql);
let dataAccess = 'CONTAINS SQL';
if (row['Create Function'].includes('NO SQL'))
dataAccess = 'NO SQL';
if (row['Create Function'].includes('READS SQL DATA'))
dataAccess = 'READS SQL DATA';
if (row['Create Function'].includes('MODIFIES SQL DATA'))
dataAccess = 'MODIFIES SQL DATA';
const output = row['Create Function'].match(/(?<=RETURNS ).*?(?=\s)/gs).length ? row['Create Function'].match(/(?<=RETURNS ).*?(?=\s)/gs)[0].replace(')', '').split('(') : ['', null];
const parameters = results.rows.filter(row => row.parameter_mode).map(row => {
return {
name: row.parameter_name,
type: row.data_type.toUpperCase(),
length: '',
context: row.parameter_mode
};
});
return {
definer: row['Create Function'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0],
sql: row['Create Function'].match(/(BEGIN|begin)(.*)(END|end)/gs)[0],
definer: '',
sql: row.pg_get_functiondef.match(/(\$(.*)\$)(.*)(\$(.*)\$)/gs)[0],
parameters: parameters || [],
name: row.Function,
comment: row['Create Function'].match(/(?<=COMMENT ').*?(?=')/gs) ? row['Create Function'].match(/(?<=COMMENT ').*?(?=')/gs)[0] : '',
security: row['Create Function'].includes('SQL SECURITY INVOKER') ? 'INVOKER' : 'DEFINER',
deterministic: row['Create Function'].includes('DETERMINISTIC'),
dataAccess,
returns: output[0].toUpperCase(),
returnsLength: +output[1]
name: func,
comment: '',
security: row.pg_get_functiondef.includes('SECURITY DEFINER') ? 'DEFINER' : 'INVOKER',
deterministic: null,
dataAccess: null,
language: row.pg_get_functiondef.match(/(?<=LANGUAGE )(.*)(?<=[\S+\n\r\s])/gm)[0],
returns: row.pg_get_functiondef.match(/(?<=RETURNS )(.*)(?<=[\S+\n\r\s])/gm)[0].replace('SETOF ', '').toUpperCase()
};
})[0];
}
@@ -763,7 +762,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async dropFunction (params) {
const sql = `DROP FUNCTION \`${params.func}\``;
const sql = `DROP FUNCTION ${this._schema}.${params.func}`;
return await this.raw(sql);
}
@@ -796,109 +795,23 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async createFunction (func) {
const parameters = func.parameters.reduce((acc, curr) => {
acc.push(`\`${curr.name}\` ${curr.type}${curr.length ? `(${curr.length})` : ''}`);
return acc;
}, []).join(',');
const parameters = 'parameters' in func
? func.parameters.reduce((acc, curr) => {
acc.push(`${curr.context} ${curr.name} ${curr.type}${curr.length ? `(${curr.length})` : ''}`);
return acc;
}, []).join(',')
: '';
const sql = `CREATE ${func.definer ? `DEFINER=${func.definer} ` : ''}FUNCTION \`${func.name}\`(${parameters}) RETURNS ${func.returns}${func.returnsLength ? `(${func.returnsLength})` : ''}
LANGUAGE SQL
${func.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'}
${func.dataAccess}
SQL SECURITY ${func.security}
COMMENT '${func.comment}'
${func.sql}`;
if (this._schema !== 'public')
await this.use(this._schema);
return await this.raw(sql, { split: false });
}
const body = func.returns ? func.sql : '$BODY$\n$BODY$';
/**
* SHOW CREATE EVENT
*
* @returns {Array.<Object>} view informations
* @memberof PostgreSQLClient
*/
async getEventInformations ({ schema, scheduler }) {
const sql = `SHOW CREATE EVENT \`${schema}\`.\`${scheduler}\``;
const results = await this.raw(sql);
return results.rows.map(row => {
const schedule = row['Create Event'];
const execution = schedule.includes('EVERY') ? 'EVERY' : 'ONCE';
const every = execution === 'EVERY' ? row['Create Event'].match(/(?<=EVERY )(\s*([^\s]+)){0,2}/gs)[0].replaceAll('\'', '').split(' ') : [];
const starts = execution === 'EVERY' && schedule.includes('STARTS') ? schedule.match(/(?<=STARTS ').*?(?='\s)/gs)[0] : '';
const ends = execution === 'EVERY' && schedule.includes('ENDS') ? schedule.match(/(?<=ENDS ').*?(?='\s)/gs)[0] : '';
const at = execution === 'ONCE' && schedule.includes('AT') ? schedule.match(/(?<=AT ').*?(?='\s)/gs)[0] : '';
return {
definer: row['Create Event'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0],
sql: row['Create Event'].match(/(?<=DO )(.*)/gs)[0],
name: row.Event,
comment: row['Create Event'].match(/(?<=COMMENT ').*?(?=')/gs) ? row['Create Event'].match(/(?<=COMMENT ').*?(?=')/gs)[0] : '',
state: row['Create Event'].includes('ENABLE') ? 'ENABLE' : row['Create Event'].includes('DISABLE ON SLAVE') ? 'DISABLE ON SLAVE' : 'DISABLE',
preserve: row['Create Event'].includes('ON COMPLETION PRESERVE'),
execution,
every,
starts,
ends,
at
};
})[0];
}
/**
* DROP EVENT
*
* @returns {Array.<Object>} parameters
* @memberof PostgreSQLClient
*/
async dropEvent (params) {
const sql = `DROP EVENT \`${params.scheduler}\``;
return await this.raw(sql);
}
/**
* ALTER EVENT
*
* @returns {Array.<Object>} parameters
* @memberof PostgreSQLClient
*/
async alterEvent (params) {
const { scheduler } = params;
if (scheduler.execution === 'EVERY' && scheduler.every[0].includes('-'))
scheduler.every[0] = `'${scheduler.every[0]}'`;
const sql = `ALTER ${scheduler.definer ? ` DEFINER=${scheduler.definer}` : ''} EVENT \`${scheduler.oldName}\`
ON SCHEDULE
${scheduler.execution === 'EVERY'
? `EVERY ${scheduler.every.join(' ')}${scheduler.starts ? ` STARTS '${scheduler.starts}'` : ''}${scheduler.ends ? ` ENDS '${scheduler.ends}'` : ''}`
: `AT '${scheduler.at}'`}
ON COMPLETION${!scheduler.preserve ? ' NOT' : ''} PRESERVE
${scheduler.name !== scheduler.oldName ? `RENAME TO \`${scheduler.name}\`` : ''}
${scheduler.state}
COMMENT '${scheduler.comment}'
DO ${scheduler.sql}`;
return await this.raw(sql, { split: false });
}
/**
* CREATE EVENT
*
* @returns {Array.<Object>} parameters
* @memberof PostgreSQLClient
*/
async createEvent (scheduler) {
const sql = `CREATE ${scheduler.definer ? ` DEFINER=${scheduler.definer}` : ''} EVENT \`${scheduler.name}\`
ON SCHEDULE
${scheduler.execution === 'EVERY'
? `EVERY ${scheduler.every.join(' ')}${scheduler.starts ? ` STARTS '${scheduler.starts}'` : ''}${scheduler.ends ? ` ENDS '${scheduler.ends}'` : ''}`
: `AT '${scheduler.at}'`}
ON COMPLETION${!scheduler.preserve ? ' NOT' : ''} PRESERVE
${scheduler.state}
COMMENT '${scheduler.comment}'
DO ${scheduler.sql}`;
const sql = `CREATE FUNCTION ${this._schema}.${func.name}(${parameters})
RETURNS ${func.returns || 'void'}
LANGUAGE ${func.language}
SECURITY ${func.security}
AS ${body}`;
return await this.raw(sql, { split: false });
}
@@ -992,13 +905,10 @@ export class PostgreSQLClient extends AntaresCore {
*/
async createTable (params) {
const {
name,
collation,
comment,
engine
name
} = params;
const sql = `CREATE TABLE \`${name}\` (\`${name}_ID\` INT NULL) COMMENT='${comment}', COLLATE='${collation}', ENGINE=${engine}`;
const sql = `CREATE TABLE ${this._schema}.${name} (${name}_id INTEGER NULL); ALTER TABLE ${this._schema}.${name} DROP COLUMN ${name}_id`;
return await this.raw(sql);
}
@@ -1020,8 +930,14 @@ export class PostgreSQLClient extends AntaresCore {
options
} = params;
let sql = `ALTER TABLE \`${table}\` `;
if (this._schema !== 'public')
await this.use(this._schema);
let sql = '';
const alterColumns = [];
const renameColumns = [];
const createSequences = [];
const manageIndexes = [];
// OPTIONS
if ('comment' in options) alterColumns.push(`COMMENT='${options.comment}'`);
@@ -1034,8 +950,8 @@ export class PostgreSQLClient extends AntaresCore {
const typeInfo = this._getTypeInfo(addition.type);
const length = typeInfo.length ? addition.numLength || addition.charLength || addition.datePrecision : false;
alterColumns.push(`ADD COLUMN \`${addition.name}\`
${addition.type.toUpperCase()}${length ? `(${length})` : ''}
alterColumns.push(`ADD COLUMN ${addition.name}
${addition.type.toUpperCase()}${length ? `(${length})` : ''}${addition.isArray ? '[]' : ''}
${addition.unsigned ? 'UNSIGNED' : ''}
${addition.zerofill ? 'ZEROFILL' : ''}
${addition.nullable ? 'NULL' : 'NOT NULL'}
@@ -1043,96 +959,109 @@ export class PostgreSQLClient extends AntaresCore {
${addition.default ? `DEFAULT ${addition.default}` : ''}
${addition.comment ? `COMMENT '${addition.comment}'` : ''}
${addition.collation ? `COLLATE ${addition.collation}` : ''}
${addition.onUpdate ? `ON UPDATE ${addition.onUpdate}` : ''}
${addition.after ? `AFTER \`${addition.after}\`` : 'FIRST'}`);
${addition.onUpdate ? `ON UPDATE ${addition.onUpdate}` : ''}`);
});
// ADD INDEX
indexChanges.additions.forEach(addition => {
const fields = addition.fields.map(field => `\`${field}\``).join(',');
let type = addition.type;
const fields = addition.fields.map(field => `${field}`).join(',');
const type = addition.type;
if (type === 'PRIMARY')
alterColumns.push(`ADD PRIMARY KEY (${fields})`);
else {
if (type === 'UNIQUE')
type = 'UNIQUE INDEX';
alterColumns.push(`ADD ${type} \`${addition.name}\` (${fields})`);
}
else if (type === 'UNIQUE')
alterColumns.push(`ADD CONSTRAINT ${addition.name} UNIQUE (${fields})`);
else
manageIndexes.push(`CREATE INDEX ${addition.name} ON ${this._schema}.${table}(${fields})`);
});
// ADD FOREIGN KEYS
foreignChanges.additions.forEach(addition => {
alterColumns.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
changes.forEach(change => {
const typeInfo = this._getTypeInfo(change.type);
const length = typeInfo.length ? change.numLength || change.charLength || change.datePrecision : false;
let localType;
alterColumns.push(`CHANGE COLUMN \`${change.orgName}\` \`${change.name}\`
${change.type.toUpperCase()}${length ? `(${length})` : ''}
${change.unsigned ? 'UNSIGNED' : ''}
${change.zerofill ? 'ZEROFILL' : ''}
${change.nullable ? 'NULL' : 'NOT NULL'}
${change.autoIncrement ? 'AUTO_INCREMENT' : ''}
${change.default ? `DEFAULT ${change.default}` : ''}
${change.comment ? `COMMENT '${change.comment}'` : ''}
${change.collation ? `COLLATE ${change.collation}` : ''}
${change.onUpdate ? `ON UPDATE ${change.onUpdate}` : ''}
${change.after ? `AFTER \`${change.after}\`` : 'FIRST'}`);
switch (change.type) {
case 'SERIAL':
localType = 'integer';
break;
case 'SMALLSERIAL':
localType = 'smallint';
break;
case 'BIGSERIAL':
localType = 'bigint';
break;
default:
localType = change.type.toLowerCase();
}
alterColumns.push(`ALTER COLUMN "${change.name}" TYPE ${localType}${length ? `(${length})` : ''}${change.isArray ? '[]' : ''} USING "${change.name}"::${localType}`);
alterColumns.push(`ALTER COLUMN "${change.name}" ${change.nullable ? 'DROP NOT NULL' : 'SET NOT NULL'}`);
alterColumns.push(`ALTER COLUMN "${change.name}" ${change.default ? `SET DEFAULT ${change.default}` : 'DROP DEFAULT'}`);
if (['SERIAL', 'SMALLSERIAL', 'BIGSERIAL'].includes(change.type)) {
const sequenceName = `${table}_${change.name}_seq`.replace(' ', '_');
createSequences.push(`CREATE SEQUENCE IF NOT EXISTS ${sequenceName} OWNED BY "${table}"."${change.name}"`);
alterColumns.push(`ALTER COLUMN "${change.name}" SET DEFAULT nextval('${sequenceName}')`);
}
if (change.orgName !== change.name)
renameColumns.push(`ALTER TABLE "${this._schema}"."${table}" RENAME COLUMN "${change.orgName}" TO "${change.name}"`);
});
// CHANGE INDEX
indexChanges.changes.forEach(change => {
if (change.oldType === 'PRIMARY')
alterColumns.push('DROP PRIMARY KEY');
if (['PRIMARY', 'UNIQUE'].includes(change.oldType))
alterColumns.push(`DROP CONSTRAINT ${change.oldName}`);
else
alterColumns.push(`DROP INDEX \`${change.oldName}\``);
manageIndexes.push(`DROP INDEX ${change.oldName}`);
const fields = change.fields.map(field => `\`${field}\``).join(',');
let type = change.type;
const fields = change.fields.map(field => `${field}`).join(',');
const type = change.type;
if (type === 'PRIMARY')
alterColumns.push(`ADD PRIMARY KEY (${fields})`);
else {
if (type === 'UNIQUE')
type = 'UNIQUE INDEX';
alterColumns.push(`ADD ${type} \`${change.name}\` (${fields})`);
}
else if (type === 'UNIQUE')
alterColumns.push(`ADD CONSTRAINT ${change.name} UNIQUE (${fields})`);
else
manageIndexes.push(`CREATE INDEX ${change.name} ON ${this._schema}.${table}(${fields})`);
});
// CHANGE FOREIGN KEYS
foreignChanges.changes.forEach(change => {
alterColumns.push(`DROP FOREIGN KEY \`${change.oldName}\``);
alterColumns.push(`ADD CONSTRAINT \`${change.constraintName}\` FOREIGN KEY (\`${change.field}\`) REFERENCES \`${change.refTable}\` (\`${change.refField}\`) ON UPDATE ${change.onUpdate} ON DELETE ${change.onDelete}`);
alterColumns.push(`DROP CONSTRAINT ${change.oldName}`);
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
deletions.forEach(deletion => {
alterColumns.push(`DROP COLUMN \`${deletion.name}\``);
alterColumns.push(`DROP COLUMN ${deletion.name}`);
});
// DROP INDEX
indexChanges.deletions.forEach(deletion => {
if (deletion.type === 'PRIMARY')
alterColumns.push('DROP PRIMARY KEY');
if (['PRIMARY', 'UNIQUE'].includes(deletion.type))
alterColumns.push(`DROP CONSTRAINT ${deletion.name}`);
else
alterColumns.push(`DROP INDEX \`${deletion.name}\``);
manageIndexes.push(`DROP INDEX ${deletion.name}`);
});
// DROP FOREIGN KEYS
foreignChanges.deletions.forEach(deletion => {
alterColumns.push(`DROP FOREIGN KEY \`${deletion.constraintName}\``);
alterColumns.push(`DROP CONSTRAINT ${deletion.constraintName}`);
});
sql += alterColumns.join(', ');
if (alterColumns.length) sql += `ALTER TABLE "${this._schema}"."${table}" ${alterColumns.join(', ')}; `;
if (createSequences.length) sql = `${createSequences.join(';')}; ${sql}`;
if (manageIndexes.length) sql = `${manageIndexes.join(';')}; ${sql}`;
if (options.name) sql += `ALTER TABLE "${this._schema}"."${table}" RENAME TO "${options.name}"; `;
// RENAME
if (options.name) sql += `; RENAME TABLE \`${table}\` TO \`${options.name}\``;
if (renameColumns.length) sql = `${renameColumns.join(';')}; ${sql}`;
return await this.raw(sql);
}
@@ -1144,7 +1073,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async truncateTable (params) {
const sql = `TRUNCATE TABLE ${params.table}`;
const sql = `TRUNCATE TABLE ${this._schema}.${params.table}`;
return await this.raw(sql);
}
@@ -1155,7 +1084,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async dropTable (params) {
const sql = `DROP TABLE ${params.table}`;
const sql = `DROP TABLE ${this._schema}.${params.table}`;
return await this.raw(sql);
}
@@ -1210,7 +1139,10 @@ export class PostgreSQLClient extends AntaresCore {
// LIMIT
const limitRaw = selectArray.length && this._query.limit.length ? `LIMIT ${this._query.limit.join(', ')} ` : '';
return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${insertRaw}`;
// OFFSET
const offsetRaw = selectArray.length && this._query.offset.length ? `OFFSET ${this._query.offset.join(', ')} ` : '';
return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${offsetRaw}${insertRaw}`;
}
/**
@@ -1229,9 +1161,13 @@ export class PostgreSQLClient extends AntaresCore {
split: true,
...args
};
if (args.schema && args.schema !== 'public')
await this.use(args.schema);
const resultsArr = [];
let paramsArr = [];
const queries = args.split ? sql.split(';') : [sql];
const queries = args.split ? sql.split(/(?!\B'[^']*);(?![^']*'\B)/gm) : [sql];
if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder
@@ -1318,7 +1254,7 @@ export class PostgreSQLClient extends AntaresCore {
if (!paramObj.table || !paramObj.schema) continue;
try { // Column details
const columns = await this.getTableColumns(paramObj);
const columns = await this.getTableColumns(paramObj, false);
const indexes = await this.getTableIndexes(paramObj);
remappedFields = remappedFields.map(field => {

View File

@@ -1,5 +1,5 @@
<template>
<div id="wrapper">
<div id="wrapper" :class="`theme-${applicationTheme}`">
<TheTitleBar />
<div id="window-content">
<TheSettingBar />
@@ -16,6 +16,7 @@
<TheFooter />
<TheNotificationsBoard />
<ModalNewConnection v-if="isNewConnModal" />
<TheScratchpad v-if="isScratchpad" />
<ModalSettings v-if="isSettingModal" />
<ModalDiscardChanges v-if="isUnsavedDiscardModal" />
</div>
@@ -24,7 +25,8 @@
<script>
import { mapActions, mapGetters } from 'vuex';
import { ipcRenderer, remote } from 'electron';
import { ipcRenderer } from 'electron';
import { Menu, getCurrentWindow } from '@electron/remote';
export default {
name: 'App',
@@ -37,6 +39,7 @@ export default {
Workspace: () => import(/* webpackChunkName: "Workspace" */'@/components/Workspace'),
ModalNewConnection: () => import(/* webpackChunkName: "ModalNewConnection" */'@/components/ModalNewConnection'),
ModalSettings: () => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings'),
TheScratchpad: () => import(/* webpackChunkName: "TheScratchpad" */'@/components/TheScratchpad'),
ModalDiscardChanges: () => import(/* webpackChunkName: "ModalDiscardChanges" */'@/components/ModalDiscardChanges')
},
data () {
@@ -48,14 +51,15 @@ export default {
isNewConnModal: 'application/isNewModal',
isEditModal: 'application/isEditModal',
isSettingModal: 'application/isSettingModal',
isScratchpad: 'application/isScratchpad',
connections: 'connections/getConnections',
applicationTheme: 'settings/getApplicationTheme',
isUnsavedDiscardModal: 'workspaces/isUnsavedDiscardModal'
})
},
mounted () {
ipcRenderer.send('check-for-updates');
const Menu = remote.Menu;
this.checkVersionUpdate();
const InputMenu = Menu.buildFromTemplate([
{
@@ -87,7 +91,7 @@ export default {
while (node) {
if (node.nodeName.match(/^(input|textarea)$/i) || node.isContentEditable) {
InputMenu.popup(remote.getCurrentWindow());
InputMenu.popup(getCurrentWindow());
break;
}
node = node.parentNode;
@@ -96,7 +100,8 @@ export default {
},
methods: {
...mapActions({
showNewConnModal: 'application/showNewConnModal'
showNewConnModal: 'application/showNewConnModal',
checkVersionUpdate: 'application/checkVersionUpdate'
})
}
};

View File

@@ -71,7 +71,6 @@ export default {
<style lang="scss">
.context {
display: flex;
color: $body-font-color;
font-size: 16px;
z-index: 400;
justify-content: center;
@@ -87,7 +86,6 @@ export default {
.context-container {
min-width: 100px;
z-index: 10;
box-shadow: 0 0 2px 0 #000;
padding: 0;
background: #1d1d1d;
border-radius: 0.1rem;
@@ -111,14 +109,10 @@ export default {
position: absolute;
left: 100%;
top: 0;
background: #1d1d1d;
box-shadow: 0 0 2px 0 #000;
min-width: 100px;
}
&:hover {
background: $primary-color;
.context-submenu {
display: block;
visibility: visible;

View File

@@ -22,6 +22,7 @@ export default {
editorClass: { type: String, default: '' },
autoFocus: { type: Boolean, default: false },
readOnly: { type: Boolean, default: false },
showLineNumbers: { type: Boolean, default: true },
height: { type: Number, default: 200 }
},
data () {
@@ -68,10 +69,12 @@ export default {
this.editor = ace.edit(`editor-${this.id}`, {
mode: `ace/mode/${this.mode}`,
theme: `ace/theme/${this.editorTheme}`,
value: this.value,
value: this.value || '',
fontSize: '14px',
printMargin: false,
readOnly: this.readOnly
readOnly: this.readOnly,
showLineNumbers: this.showLineNumbers,
showGutter: this.showLineNumbers
});
this.editor.setOptions({
@@ -102,8 +105,6 @@ export default {
<style lang="scss" scoped>
.editor-wrapper {
border-bottom: 1px solid #444;
.editor {
width: 100%;
}
@@ -113,17 +114,4 @@ export default {
display: inline-block;
width: 17px;
}
.ace_dark.ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {
background-color: #c9561a99;
}
.ace_dark.ace_editor.ace_autocomplete .ace_marker-layer .ace_line-hover {
background-color: #c9571a33;
border: none;
}
.ace_dark.ace_editor.ace_autocomplete .ace_completion-highlight {
color: #e0d00c;
}
</style>

View File

@@ -63,13 +63,11 @@ export default {
<style lang="scss" scoped>
.file-uploader {
border: 0.05rem solid $bg-color-light;
border-radius: 0.1rem;
height: 1.8rem;
line-height: 1.2rem;
display: flex;
cursor: pointer;
background-color: $bg-color-gray;
transition: background 0.2s, border 0.2s, box-shadow 0.2s, color 0.2s;
position: relative;
flex: 1 1 auto;
@@ -80,8 +78,6 @@ export default {
.file-uploader-message {
display: flex;
border-right: 0.05rem solid $bg-color-light;
background-color: $bg-color;
}
.file-uploader-input {
@@ -105,7 +101,6 @@ export default {
:disabled {
.file-uploader {
cursor: not-allowed;
background-color: #151515;
opacity: 0.5;
}
}

View File

@@ -66,6 +66,21 @@
:type="inputProps().type"
:disabled="!isChecked"
>
<select
v-else-if="enumArray"
v-model="selectedValue"
class="form-select"
:disabled="!isChecked"
@change="onChange"
>
<option
v-for="val in enumArray"
:key="val"
:value="val"
>
{{ val }}
</option>
</select>
<input
v-else
ref="formInput"
@@ -121,7 +136,8 @@ export default {
selectedMethod: '',
selectedValue: '',
debounceTimeout: null,
methodParams: {}
methodParams: {},
enumArray: null
};
},
computed: {
@@ -150,8 +166,14 @@ export default {
},
watch: {
fieldObj () {
if (this.fieldObj)
this.selectedValue = this.fieldObj.value;
if (this.fieldObj) {
if (Array.isArray(this.fieldObj.value)) {
this.enumArray = this.fieldObj.value;
this.selectedValue = this.fieldObj.value[0];
}
else
this.selectedValue = this.fieldObj.value;
}
},
selectedGroup () {
if (this.fakerMethods.length)

View File

@@ -51,6 +51,7 @@ export default {
}),
isValidDefault () {
if (!this.foreignList.length) return true;
if (this.value === null) return false;
return this.foreignList.some(foreign => foreign.foreign_column.toString() === this.value.toString());
}
},
@@ -65,7 +66,7 @@ export default {
try { // Field data
const { status, response } = await Tables.getTableColumns(params);
if (status === 'success') {
const textField = response.find(field => [...TEXT, ...LONG_TEXT].includes(field.type));
const textField = response.find(field => [...TEXT, ...LONG_TEXT].includes(field.type) && field.name !== this.keyUsage.refField);
foreignDesc = textField ? textField.name : false;
}
else

View File

@@ -15,7 +15,7 @@
<div class="content">
<form class="form-horizontal">
<div
v-for="(parameter, i) in localRoutine.parameters"
v-for="(parameter, i) in inParameters"
:key="parameter._id"
class="form-group"
>
@@ -26,7 +26,7 @@
<div class="input-group">
<input
:ref="i === 0 ? 'firstInput' : ''"
v-model="values[parameter.name]"
v-model="values[`${i}-${parameter.name}`]"
class="form-input"
type="text"
>
@@ -43,6 +43,7 @@
</template>
<script>
import { NUMBER, FLOAT } from 'common/fieldTypes';
import ConfirmModal from '@/components/BaseConfirmModal';
export default {
@@ -57,13 +58,19 @@ export default {
}
},
props: {
localRoutine: Object
localRoutine: Object,
client: String
},
data () {
return {
values: {}
};
},
computed: {
inParameters () {
return this.localRoutine.parameters.filter(param => param.context === 'IN');
}
},
created () {
window.addEventListener('keydown', this.onKey);
@@ -81,8 +88,23 @@ export default {
return '';
},
runRoutine () {
const valArr = Object.keys(this.values).reduce((acc, curr) => {
const value = isNaN(this.values[curr]) ? `"${this.values[curr]}"` : this.values[curr];
const valArr = Object.keys(this.values).reduce((acc, curr, i) => {
let qc;
switch (this.client) {
case 'maria':
case 'mysql':
qc = '"';
break;
case 'pg':
qc = '\'';
break;
default:
qc = '"';
}
const param = this.localRoutine.parameters.find(param => `${i}-${param.name}` === curr);
const value = [...NUMBER, ...FLOAT].includes(param.type) ? this.values[curr] : `${qc}${this.values[curr]}${qc}`;
acc.push(value);
return acc;
}, []);

View File

@@ -250,10 +250,15 @@ export default {
if (field.default === 'NULL') fieldDefault = null;
else {
if ([...NUMBER, ...FLOAT].includes(field.type))
fieldDefault = +field.default;
fieldDefault = Number.isNaN(+field.default) ? null : +field.default;
if ([...TEXT, ...LONG_TEXT].includes(field.type))
fieldDefault = field.default ? field.default.substring(1, field.default.length - 1) : '';
if ([...TEXT, ...LONG_TEXT].includes(field.type)) {
fieldDefault = field.default
? field.default.includes('\'')
? field.default.split('\'')[1]
: field.default
: '';
}
if ([...TIME, ...DATE].includes(field.type))
fieldDefault = field.default;
@@ -269,6 +274,9 @@ export default {
fieldDefault = moment().format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
}
}
if (field.enumValues)
fieldDefault = field.enumValues.replaceAll('\'', '').split(',');
}
rowObj[field.name] = { value: fieldDefault };

View File

@@ -7,7 +7,7 @@
>
<template :slot="'header'">
<div class="d-flex">
<i class="mdi mdi-24px mdi-plus mr-1" /> {{ $t('message.createNewRoutine') }}
<i class="mdi mdi-24px mdi-plus mr-1" /> {{ $t('message.createNewFunction') }}
</div>
</template>
<div :slot="'body'">
@@ -25,7 +25,19 @@
>
</div>
</div>
<div class="form-group">
<div v-if="customizations.languages" class="form-group">
<label class="form-label col-4">
{{ $t('word.language') }}
</label>
<div class="column">
<select v-model="localFunction.language" class="form-select">
<option v-for="language in customizations.languages" :key="language">
{{ language }}
</option>
</select>
</div>
</div>
<div v-if="customizations.definer" class="form-group">
<label class="form-label col-4">
{{ $t('word.definer') }}
</label>
@@ -53,42 +65,7 @@
</select>
</div>
</div>
<div class="form-group">
<label class="form-label col-4">
{{ $t('word.returns') }}
</label>
<div class="column">
<div class="input-group">
<select
v-model="localFunction.returns"
class="form-select text-uppercase"
style="width: 0;"
>
<optgroup
v-for="group in workspace.dataTypes"
:key="group.group"
:label="group.group"
>
<option
v-for="type in group.types"
:key="type.name"
:selected="localFunction.returns === type.name"
:value="type.name"
>
{{ type.name }}
</option>
</optgroup>
</select>
<input
v-model="localFunction.returnsLength"
class="form-input"
type="number"
min="0"
>
</div>
</div>
</div>
<div class="form-group">
<div v-if="customizations.comment" class="form-group">
<label class="form-label col-4">
{{ $t('word.comment') }}
</label>
@@ -111,7 +88,7 @@
</select>
</div>
</div>
<div class="form-group">
<div v-if="customizations.functionDataAccess" class="form-group">
<label class="form-label col-4">
{{ $t('message.dataAccess') }}
</label>
@@ -124,7 +101,7 @@
</select>
</div>
</div>
<div class="form-group">
<div v-if="customizations.functionDeterministic" class="form-group">
<div class="col-4" />
<div class="column">
<label class="form-checkbox form-inline">
@@ -152,11 +129,12 @@ export default {
return {
localFunction: {
definer: '',
sql: 'BEGIN\r\n RETURN NULL;\r\nEND',
sql: '',
parameters: [],
name: '',
comment: '',
returns: 'INT',
language: null,
returns: null,
returnsLength: 10,
security: 'DEFINER',
deterministic: false,
@@ -168,9 +146,17 @@ export default {
computed: {
schema () {
return this.workspace.breadcrumbs.schema;
},
customizations () {
return this.workspace.customizations;
}
},
mounted () {
if (this.customizations.languages)
this.localFunction.language = this.customizations.languages[0];
if (this.customizations.procedureSql)
this.localFunction.sql = this.customizations.procedureSql;
setTimeout(() => {
this.$refs.firstInput.focus();
}, 20);

View File

@@ -25,7 +25,19 @@
>
</div>
</div>
<div class="form-group">
<div v-if="customizations.languages" class="form-group">
<label class="form-label col-4">
{{ $t('word.language') }}
</label>
<div class="column">
<select v-model="localRoutine.language" class="form-select">
<option v-for="language in customizations.languages" :key="language">
{{ language }}
</option>
</select>
</div>
</div>
<div v-if="customizations.definer" class="form-group">
<label class="form-label col-4">
{{ $t('word.definer') }}
</label>
@@ -53,7 +65,7 @@
</select>
</div>
</div>
<div class="form-group">
<div v-if="customizations.comment" class="form-group">
<label class="form-label col-4">
{{ $t('word.comment') }}
</label>
@@ -76,7 +88,7 @@
</select>
</div>
</div>
<div class="form-group">
<div v-if="customizations.comment" class="form-group">
<label class="form-label col-4">
{{ $t('message.dataAccess') }}
</label>
@@ -89,7 +101,7 @@
</select>
</div>
</div>
<div class="form-group">
<div v-if="customizations.procedureDeterministic" class="form-group">
<div class="col-4" />
<div class="column">
<label class="form-checkbox form-inline">
@@ -117,10 +129,11 @@ export default {
return {
localRoutine: {
definer: '',
sql: 'BEGIN\r\n\r\nEND',
sql: '',
parameters: [],
name: '',
comment: '',
language: null,
security: 'DEFINER',
deterministic: false,
dataAccess: 'CONTAINS SQL'
@@ -131,9 +144,17 @@ export default {
computed: {
schema () {
return this.workspace.breadcrumbs.schema;
},
customizations () {
return this.workspace.customizations;
}
},
mounted () {
if (this.customizations.languages)
this.localRoutine.language = this.customizations.languages[0];
if (this.customizations.procedureSql)
this.localRoutine.sql = this.customizations.procedureSql;
setTimeout(() => {
this.$refs.firstInput.focus();
}, 20);

View File

@@ -25,7 +25,7 @@
>
</div>
</div>
<div class="form-group">
<div v-if="workspace.customizations.comment" class="form-group">
<label class="form-label col-4">
{{ $t('word.comment') }}
</label>
@@ -37,7 +37,7 @@
>
</div>
</div>
<div class="form-group">
<div v-if="workspace.customizations.collations" class="form-group">
<label class="form-label col-4">
{{ $t('word.collation') }}
</label>
@@ -53,7 +53,7 @@
</select>
</div>
</div>
<div class="form-group">
<div v-if="workspace.customizations.engines" class="form-group">
<label class="form-label col-4">
{{ $t('word.engine') }}
</label>
@@ -103,10 +103,14 @@ export default {
getDatabaseVariable: 'workspaces/getDatabaseVariable'
}),
defaultCollation () {
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value || '';
if (this.workspace.customizations.collations)
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value || '';
return '';
},
defaultEngine () {
return this.workspace.engines.find(engine => engine.isDefault).name;
if (this.workspace.customizations.engines)
return this.workspace.engines.find(engine => engine.isDefault).name;
return '';
}
},
mounted () {

View File

@@ -25,7 +25,7 @@
</div>
</div>
<div class="column col-6">
<div class="form-group">
<div v-if="workspace.customizations.definer" class="form-group">
<label class="form-label">{{ $t('word.definer') }}</label>
<select v-model="localView.definer" class="form-select">
<option value="">
@@ -44,7 +44,7 @@
</div>
<div class="columns">
<div class="column col-4">
<div class="form-group">
<div v-if="workspace.customizations.viewSqlSecurity" class="form-group">
<label class="form-label">{{ $t('message.sqlSecurity') }}</label>
<label class="form-radio">
<input
@@ -67,7 +67,7 @@
</div>
</div>
<div class="column col-4">
<div class="form-group">
<div v-if="workspace.customizations.viewAlgorithm" class="form-group">
<label class="form-label">{{ $t('word.algorithm') }}</label>
<label class="form-radio">
<input
@@ -99,7 +99,7 @@
</div>
</div>
<div class="column col-4">
<div class="form-group">
<div v-if="workspace.customizations.viewUpdateOption" class="form-group">
<label class="form-label">{{ $t('message.updateOption') }}</label>
<label class="form-radio">
<input

View File

@@ -30,12 +30,20 @@
<a class="c-hand">{{ $t('word.themes') }}</a>
</li>
<li
v-if="updateStatus !== 'disabled'"
class="tab-item"
:class="{'active': selectedTab === 'update'}"
@click="selectTab('update')"
>
<a class="c-hand" :class="{'badge badge-update': hasUpdates}">{{ $t('word.update') }}</a>
</li>
<li
class="tab-item"
:class="{'active': selectedTab === 'changelog'}"
@click="selectTab('changelog')"
>
<a class="c-hand">{{ $t('word.changelog') }}</a>
</li>
<li
class="tab-item"
:class="{'active': selectedTab === 'about'}"
@@ -45,7 +53,7 @@
</li>
</ul>
</div>
<div v-if="selectedTab === 'general'" class="panel-body py-4">
<div v-show="selectedTab === 'general'" class="panel-body py-4">
<div class="container">
<form class="form-horizontal columns">
<div class="column col-12 h6 text-uppercase mb-1">
@@ -75,6 +83,27 @@
</select>
</div>
</div>
<div class="form-group">
<div class="col-6 col-sm-12">
<label class="form-label">
{{ $t('message.dataTabPageSize') }}
</label>
</div>
<div class="col-6 col-sm-12">
<select
v-model="localPageSize"
class="form-select"
@change="changePageSize(+localPageSize)"
>
<option
v-for="size in pageSizes"
:key="size"
>
{{ size }}
</option>
</select>
</div>
</div>
<div class="form-group">
<div class="col-6 col-sm-12">
<label class="form-label">
@@ -133,26 +162,35 @@
</div>
</div>
<div v-if="selectedTab === 'themes'" class="panel-body py-4">
<div v-show="selectedTab === 'themes'" class="panel-body py-4">
<div class="container">
<div class="columns">
<div class="column col-12 h6 text-uppercase mb-2">
{{ $t('message.applicationTheme') }}
</div>
<div class="column col-6 c-hand theme-block" :class="{'selected': applicationTheme === 'dark'}">
<div
class="column col-6 c-hand theme-block"
:class="{'selected': applicationTheme === 'dark'}"
@click="changeApplicationTheme('dark')"
>
<img :src="require('@/images/dark.png').default" class="img-responsive img-fit-cover s-rounded">
<div class="theme-name">
<div class="theme-name text-light">
<i class="mdi mdi-moon-waning-crescent mdi-48px" />
<div class="h6 mt-4">
{{ $t('word.dark') }}
</div>
</div>
</div>
<div class="column col-6 theme-block disabled" :class="{'selected': applicationTheme === 'light'}">
<div class="theme-name">
<div
class="column col-6 c-hand theme-block"
:class="{'selected': applicationTheme === 'light'}"
@click="changeApplicationTheme('light')"
>
<img :src="require('@/images/light.png').default" class="img-responsive img-fit-cover s-rounded">
<div class="theme-name text-dark">
<i class="mdi mdi-white-balance-sunny mdi-48px" />
<div class="h6 mt-4">
{{ $t('word.light') }} (Coming)
{{ $t('word.light') }}
</div>
</div>
</div>
@@ -185,8 +223,9 @@
</select>
</div>
<div class="column col-12">
<QueryEditor
<BaseTextEditor
:value="exampleQuery"
mode="sql"
:workspace="workspace"
:read-only="true"
:height="270"
@@ -196,17 +235,20 @@
</div>
</div>
<div v-if="selectedTab === 'update'" class="panel-body py-4">
<div v-show="selectedTab === 'update'" class="panel-body py-4">
<ModalSettingsUpdate />
</div>
<div v-show="selectedTab === 'changelog'" class="panel-body py-4">
<ModalSettingsChangelog />
</div>
<div v-if="selectedTab === 'about'" class="panel-body py-4">
<div v-show="selectedTab === 'about'" class="panel-body py-4">
<div class="text-center">
<img :src="require('@/images/logo.svg').default" width="128">
<h4>{{ appName }}</h4>
<p>
{{ $t('word.version') }} {{ appVersion }}<br>
<a class="c-hand" @click="openOutside('https://github.com/Fabio286/antares')">GitHub</a> | <a class="c-hand" @click="openOutside('https://github.com/Fabio286/antares/blob/master/CHANGELOG.md')">CHANGELOG</a><br>
<a class="c-hand" @click="openOutside('https://github.com/Fabio286/antares')"><i class="mdi mdi-github d-inline" /> GitHub</a> <a class="c-hand" @click="openOutside('https://twitter.com/AntaresSQL')"><i class="mdi mdi-twitter d-inline" /> Twitter</a> <a class="c-hand" @click="openOutside('https://antares-sql.app/')"><i class="mdi mdi-web d-inline" /> Website</a><br>
<small>{{ $t('word.author') }} <a class="c-hand" @click="openOutside('https://github.com/Fabio286')">Fabio Di Stasio</a></small><br>
<small>{{ $t('message.madeWithJS') }}</small>
</p>
@@ -222,21 +264,25 @@
import { mapActions, mapGetters } from 'vuex';
import localesNames from '@/i18n/supported-locales';
import ModalSettingsUpdate from '@/components/ModalSettingsUpdate';
import QueryEditor from '@/components/QueryEditor';
import ModalSettingsChangelog from '@/components/ModalSettingsChangelog';
import BaseTextEditor from '@/components/BaseTextEditor';
const { shell } = require('electron');
export default {
name: 'ModalSettings',
components: {
ModalSettingsUpdate,
QueryEditor
ModalSettingsChangelog,
BaseTextEditor
},
data () {
return {
localLocale: null,
localPageSize: null,
localTimeout: null,
localEditorTheme: null,
selectedTab: 'general',
pageSizes: [40, 100, 250, 500, 1000],
editorThemes: [
{
group: this.$t('word.light'),
@@ -294,6 +340,7 @@ export default {
appVersion: 'application/appVersion',
selectedSettingTab: 'application/selectedSettingTab',
selectedLocale: 'settings/getLocale',
pageSize: 'settings/getDataTabLimit',
selectedAutoComplete: 'settings/getAutoComplete',
selectedLineWrap: 'settings/getLineWrap',
notificationsTimeout: 'settings/getNotificationsTimeout',
@@ -336,6 +383,7 @@ ORDER BY
},
created () {
this.localLocale = this.selectedLocale;
this.localPageSize = this.pageSize;
this.localTimeout = this.notificationsTimeout;
this.localEditorTheme = this.editorTheme;
this.selectedTab = this.selectedSettingTab;
@@ -348,8 +396,10 @@ ORDER BY
...mapActions({
closeModal: 'application/hideSettingModal',
changeLocale: 'settings/changeLocale',
changePageSize: 'settings/changePageSize',
changeAutoComplete: 'settings/changeAutoComplete',
changeLineWrap: 'settings/changeLineWrap',
changeApplicationTheme: 'settings/changeApplicationTheme',
changeEditorTheme: 'settings/changeEditorTheme',
updateNotificationsTimeout: 'settings/updateNotificationsTimeout'
}),
@@ -382,54 +432,59 @@ ORDER BY
<style lang="scss">
#settings {
.modal-body {
overflow: hidden;
.modal-container {
position: absolute;
top: 17.5vh;
.panel-body {
height: calc(70vh - 70px);
overflow: auto;
.modal-body {
overflow: hidden;
.theme-block {
position: relative;
text-align: center;
.panel-body {
min-height: calc(25vh - 70px);
max-height: 65vh;
overflow: auto;
&.selected {
img {
box-shadow: 0 0 0 3px $primary-color;
.theme-block {
position: relative;
text-align: center;
&.selected {
img {
box-shadow: 0 0 0 3px $primary-color;
}
}
&.disabled {
cursor: not-allowed;
opacity: 0.5;
}
.theme-name {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
top: 0;
height: 100%;
width: 100%;
}
}
&.disabled {
cursor: not-allowed;
opacity: 0.5;
}
.theme-name {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
top: 0;
height: 100%;
width: 100%;
text-shadow: 0 0 8px #000;
}
}
}
.badge::after {
background: #32b643;
}
.badge::after {
background: #32b643;
}
.badge-update::after {
bottom: initial;
background: $primary-color;
}
.badge-update::after {
bottom: initial;
background: $primary-color;
}
.form-label {
display: flex;
align-items: center;
.form-label {
display: flex;
align-items: center;
}
}
}
}

View File

@@ -0,0 +1,82 @@
<template>
<div class="p-relative">
<BaseLoader v-if="isLoading" />
<div
id="changelog"
class="container"
v-html="changelog"
/>
<div v-if="isError" class="empty">
<div class="empty-icon">
<i class="mdi mdi-48px mdi-alert-outline" />
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import marked from 'marked';
import BaseLoader from '@/components/BaseLoader';
export default {
name: 'ModalSettingsChangelog',
components: {
BaseLoader
},
data () {
return {
changelog: '',
isLoading: true,
error: '',
isError: false
};
},
computed: {
...mapGetters({ appVersion: 'application/appVersion' })
},
created () {
this.getChangelog();
},
methods: {
async getChangelog () {
try {
const apiRes = await fetch(`https://api.github.com/repos/Fabio286/antares/releases/tags/v${this.appVersion}`, {
method: 'GET'
});
const { body } = await apiRes.json();
const markdown = body.substr(0, body.indexOf('### Download'));
const renderer = {
link (href, title, text) {
return text;
},
listitem (text) {
return `<li>${text.replace(/ *\([^)]*\) */g, '')}</li>`;
}
};
marked.use({ renderer });
this.changelog = marked(markdown);
}
catch (err) {
this.error = err.message;
this.isError = true;
}
this.isLoading = false;
}
}
};
</script>
<style lang="scss">
#changelog {
h3 {
font-size: 1rem;
}
li {
margin-top: 0;
}
}
</style>

View File

@@ -92,9 +92,3 @@ export default {
}
};
</script>
<style lang="scss">
.empty {
color: $body-font-color;
}
</style>

View File

@@ -126,9 +126,13 @@ export default {
return 'sql';
}
},
cursorPosition () {
return this.editor.session.doc.positionToIndex(this.editor.getCursorPosition());
},
lastWord () {
const words = this.value.split(' ');
return words[words.length - 1];
const charsBefore = this.value.slice(0, this.cursorPosition);
const words = charsBefore.replaceAll('\n', ' ').split(' ').filter(Boolean);
return words.pop();
},
isLastWordATable () {
return /\w+\.\w*/gm.test(this.lastWord);
@@ -209,7 +213,7 @@ export default {
if (['insertstring', 'backspace', 'del'].includes(e.command.name)) {
if (this.isLastWordATable || e.args === '.') {
if (e.args !== ' ') {
const table = this.tables.find(t => t.name === this.lastWord.split('.').pop());
const table = this.tables.find(t => t.name === this.lastWord.split('.').pop().trim());
if (table) {
const params = {
@@ -312,19 +316,6 @@ export default {
width: 17px;
}
.ace_dark.ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {
background-color: #c9561a99;
}
.ace_dark.ace_editor.ace_autocomplete .ace_marker-layer .ace_line-hover {
background-color: #c9571a33;
border: none;
}
.ace_dark.ace_editor.ace_autocomplete .ace_completion-highlight {
color: #e0d00c;
}
.ace_gutter-cell.ace_breakpoint {
&::before {
content: '\F0403';

View File

@@ -11,9 +11,9 @@
<div class="footer-right-elements">
<ul class="footer-elements">
<li class="footer-element footer-link" @click="openOutside('https://github.com/sponsors/Fabio286')">
<i class="mdi mdi-18px mdi-coffee mr-1" />
<small>{{ $t('word.donate') }}</small>
<li class="footer-element footer-link" @click="openOutside('https://www.treedom.net/en/user/fabio-di-stasio/event/antares-for-the-planet')">
<i class="mdi mdi-18px mdi-tree mr-1" />
<small>{{ $t('message.plantATree') }}</small>
</li>
<li class="footer-element footer-link" @click="openOutside('https://github.com/Fabio286/antares/issues')">
<i class="mdi mdi-18px mdi-bug" />
@@ -64,13 +64,11 @@ export default {
display: flex;
justify-content: space-between;
align-items: center;
background: $primary-color;
padding: 0 0.2rem;
position: fixed;
bottom: 0;
left: 0;
right: 0;
box-shadow: 0 0 1px 0 #000;
.footer-elements {
list-style: none;
@@ -88,10 +86,6 @@ export default {
&.footer-link {
cursor: pointer;
transition: background 0.2s;
&:hover {
background: rgba($color: #fff, $alpha: 0.1);
}
}
}
}

View File

@@ -0,0 +1,75 @@
<template>
<ConfirmModal
:confirm-text="$t('word.update')"
:cancel-text="$t('word.close')"
size="large"
:hide-footer="true"
@hide="hideScratchpad"
>
<template :slot="'header'">
<div class="d-flex">
<i class="mdi mdi-24px mdi-notebook-edit-outline mr-1" /> {{ $t('word.scratchpad') }}
</div>
</template>
<div :slot="'body'">
<div>
<div>
<TextEditor
:value.sync="localNotes"
editor-class="textarea-editor"
mode="markdown"
:auto-focus="true"
:show-line-numbers="false"
/>
</div>
<small class="text-gray">{{ $t('message.markdownSupported') }}</small>
</div>
</div>
</ConfirmModal>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import ConfirmModal from '@/components/BaseConfirmModal';
import TextEditor from '@/components/BaseTextEditor';
export default {
name: 'TheScratchpad',
components: {
ConfirmModal,
TextEditor
},
data () {
return {
localNotes: '',
debounceTimeout: null
};
},
computed: {
...mapGetters({
notes: 'scratchpad/getNotes'
})
},
watch: {
localNotes () {
clearTimeout(this.debounceTimeout);
this.debounceTimeout = setTimeout(() => {
this.changeNotes(this.localNotes);
}, 200);
}
},
created () {
this.localNotes = this.notes;
},
methods: {
...mapActions({
hideScratchpad: 'application/hideScratchpad',
changeNotes: 'scratchpad/changeNotes'
}),
hideModal () {
this.$emit('hide');
}
}
};
</script>

View File

@@ -19,7 +19,7 @@
@contextmenu.prevent="contextMenu($event, connection)"
@mouseover.self="tooltipPosition"
>
<i class="settingbar-element-icon dbi" :class="`dbi-${connection.client} ${connected.includes(connection.uid) ? 'badge' : ''}`" />
<i class="settingbar-element-icon dbi" :class="`dbi-${connection.client} ${getStatusBadge(connection.uid)}`" />
<span class="ex-tooltip-content">{{ getConnectionName(connection.uid) }}</span>
</li>
</draggable>
@@ -36,6 +36,10 @@
<div class="settingbar-bottom-elements">
<ul class="settingbar-elements">
<li class="settingbar-element btn btn-link ex-tooltip" @click="showScratchpad">
<i class="settingbar-element-icon mdi mdi-24px mdi-notebook-edit-outline text-light" />
<span class="ex-tooltip-content">{{ $t('word.scratchpad') }}</span>
</li>
<li class="settingbar-element btn btn-link ex-tooltip" @click="showSettingModal('general')">
<i class="settingbar-element-icon mdi mdi-24px mdi-cog text-light" :class="{' badge badge-update': hasUpdates}" />
<span class="ex-tooltip-content">{{ $t('word.settings') }}</span>
@@ -69,7 +73,7 @@ export default {
...mapGetters({
getConnections: 'connections/getConnections',
getConnectionName: 'connections/getConnectionName',
connected: 'workspaces/getConnected',
getWorkspace: 'workspaces/getWorkspace',
selectedWorkspace: 'workspaces/getSelected',
updateStatus: 'application/getUpdateStatus'
}),
@@ -90,6 +94,7 @@ export default {
updateConnections: 'connections/updateConnections',
showNewConnModal: 'application/showNewConnModal',
showSettingModal: 'application/showSettingModal',
showScratchpad: 'application/showScratchpad',
selectWorkspace: 'workspaces/selectWorkspace'
}),
contextMenu (event, connection) {
@@ -104,6 +109,22 @@ export default {
const el = e.target;
const fromTop = window.pageYOffset + el.getBoundingClientRect().top - (el.offsetHeight / 4);
el.querySelector('.ex-tooltip-content').style.top = `${fromTop}px`;
},
getStatusBadge (uid) {
if (this.getWorkspace(uid)) {
const status = this.getWorkspace(uid).connection_status;
switch (status) {
case 'connected':
return 'badge badge-connected';
case 'connecting':
return 'badge badge-connecting';
case 'failed':
return 'badge badge-failed';
default:
return '';
}
}
}
}
};
@@ -117,9 +138,7 @@ export default {
flex-direction: column;
justify-content: space-between;
align-items: center;
background: $bg-color-light;
padding: 0;
box-shadow: 0 0 1px 0 #000;
z-index: 9;
.settingbar-top-elements {
@@ -134,7 +153,6 @@ export default {
.settingbar-bottom-elements {
padding-top: 0.5rem;
background: $bg-color-light;
z-index: 1;
}
@@ -162,7 +180,6 @@ export default {
}
&.selected {
border-left-color: $body-font-color;
opacity: 1;
}
@@ -171,12 +188,10 @@ export default {
bottom: -10px;
right: 0;
position: absolute;
background: $success-color;
}
&.badge-update::after {
bottom: initial;
background: $primary-color;
}
}
}

View File

@@ -37,15 +37,16 @@
</template>
<script>
import { remote, ipcRenderer } from 'electron';
import { ipcRenderer } from 'electron';
import { getCurrentWindow } from '@electron/remote';
import { mapGetters } from 'vuex';
export default {
name: 'TheTitleBar',
data () {
return {
w: remote.getCurrentWindow(),
isMaximized: remote.getCurrentWindow().isMaximized(),
w: getCurrentWindow(),
isMaximized: getCurrentWindow().isMaximized(),
isDevelopment: process.env.NODE_ENV === 'development'
};
},
@@ -102,12 +103,10 @@ export default {
display: flex;
position: relative;
justify-content: space-between;
background: $bg-color-light;
align-items: center;
height: $titlebar-height;
-webkit-app-region: drag;
user-select: none;
box-shadow: 0 0 1px 0 #000;
z-index: 9999;
.titlebar-resizer {
@@ -134,7 +133,7 @@ export default {
.titlebar-logo {
height: $titlebar-height;
padding: 0 0.4rem;
padding: 0.3rem 0.4rem;
}
.titlebar-element {
@@ -149,11 +148,6 @@ export default {
&:hover {
opacity: 1;
background: rgba($color: #fff, $alpha: 0.2);
}
&.close-button:hover {
background: red;
}
}
}

View File

@@ -1,7 +1,7 @@
<template>
<div v-show="isSelected" class="workspace column columns col-gapless">
<WorkspaceExploreBar :connection="connection" :is-selected="isSelected" />
<div v-if="workspace.connected" class="workspace-tabs column columns col-gapless">
<div v-if="workspace.connection_status === 'connected'" class="workspace-tabs column columns col-gapless">
<ul
id="tabWrap"
ref="tabWrap"
@@ -304,7 +304,6 @@ export default {
height: calc(100vh - #{$excluding-size});
.tab-block {
background: $bg-color-light;
margin-top: 0;
flex-direction: row;
align-items: flex-start;
@@ -324,7 +323,6 @@ export default {
> a {
padding: 0.2rem 0.8rem;
color: $body-font-color;
cursor: pointer;
display: flex;
align-items: center;
@@ -355,7 +353,6 @@ export default {
&.tools-dropdown {
.tab-link:focus {
color: $primary-color;
opacity: 1;
outline: 0;
box-shadow: none;
@@ -374,11 +371,6 @@ export default {
white-space: nowrap;
border: 0;
&:hover {
color: $primary-color;
background: $bg-color-gray;
}
.tool-icon {
line-height: 1;
display: inline-block;
@@ -414,11 +406,9 @@ export default {
.th {
position: sticky;
top: 0;
background: $bg-color;
border: 1px solid;
border-left: none;
border-bottom-width: 2px;
border-color: $bg-color-light;
padding: 0;
font-weight: 700;
font-size: 0.7rem;
@@ -433,7 +423,6 @@ export default {
.td {
border-right: 1px solid;
border-bottom: 1px solid;
border-color: $bg-color-light;
padding: 0 0.4rem;
text-overflow: ellipsis;
max-width: 200px;
@@ -443,8 +432,6 @@ export default {
position: relative;
&:focus {
box-shadow: inset 0 0 0 1px $body-font-color;
background: rgba($color: #000, $alpha: 0.3);
outline: none;
}
}

View File

@@ -1,6 +1,6 @@
<template>
<div class="columns">
<div class="column col-12 empty text-light">
<div class="column col-12 empty">
<div class="empty-icon">
<i class="mdi mdi-48px mdi-power-plug-off" />
</div>

View File

@@ -8,7 +8,7 @@
>
<div class="workspace-explorebar-header">
<span class="workspace-explorebar-title">{{ connectionName }}</span>
<span v-if="workspace.connected" class="workspace-explorebar-tools">
<span v-if="workspace.connection_status === 'connected'" class="workspace-explorebar-tools">
<i
class="mdi mdi-18px mdi-database-plus c-hand mr-2"
:title="$t('message.createNewSchema')"
@@ -28,7 +28,7 @@
</span>
</div>
<div class="workspace-explorebar-search">
<div v-if="workspace.connected" class="has-icon-right">
<div v-if="workspace.connection_status === 'connected'" class="has-icon-right">
<input
v-model="searchTerm"
class="form-input input-sm"
@@ -39,7 +39,7 @@
</div>
</div>
<WorkspaceConnectPanel
v-if="!workspace.connected"
v-if="workspace.connection_status !== 'connected'"
class="workspace-explorebar-body"
:connection="connection"
/>
@@ -460,8 +460,6 @@ export default {
justify-content: flex-start;
align-items: center;
text-align: left;
background: $bg-color-gray;
box-shadow: 0 0 1px 0 #000;
z-index: 8;
flex: initial;
position: relative;

View File

@@ -32,6 +32,7 @@
<ModalAskParameters
v-if="isAskingParameters"
:local-routine="localElement"
:client="workspace.client"
@confirm="runElement"
@close="hideAskParamsModal"
/>
@@ -205,13 +206,13 @@ export default {
case 'maria':
case 'mysql':
case 'pg':
sql = `CALL \`${this.localElement.name}\` (${params.join(',')})`;
sql = `CALL ${this.localElement.name}(${params.join(',')})`;
break;
case 'mssql':
sql = `EXEC ${this.localElement.name} ${params.join(',')}`;
break;
default:
sql = `CALL \`${this.localElement.name}\` (${params.join(',')})`;
sql = `CALL \`${this.localElement.name}\`(${params.join(',')})`;
}
this.newTab({ uid: this.workspace.uid, content: sql, autorun: true });
@@ -247,9 +248,11 @@ export default {
switch (this.workspace.client) { // TODO: move in a better place
case 'maria':
case 'mysql':
case 'pg':
sql = `SELECT \`${this.localElement.name}\` (${params.join(',')})`;
break;
case 'pg':
sql = `SELECT ${this.localElement.name}(${params.join(',')})`;
break;
case 'mssql':
sql = `SELECT ${this.localElement.name} ${params.join(',')}`;
break;

View File

@@ -75,8 +75,8 @@
<div>
<ul class="menu menu-nav pt-0">
<li
v-for="procedure of filteredProcedures"
:key="procedure.name"
v-for="(procedure, i) of filteredProcedures"
:key="`${procedure.name}-${i}`"
class="menu-item"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.procedure === procedure.name}"
@click="setBreadcrumbs({schema: database.name, procedure: procedure.name})"
@@ -103,8 +103,8 @@
<div>
<ul class="menu menu-nav pt-0">
<li
v-for="func of filteredFunctions"
:key="func.name"
v-for="(func, i) of filteredFunctions"
:key="`${func.name}-${i}`"
class="menu-item"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.function === func.name}"
@click="setBreadcrumbs({schema: database.name, function: func.name})"
@@ -171,7 +171,8 @@ export default {
...mapGetters({
getLoadedSchemas: 'workspaces/getLoadedSchemas',
getWorkspace: 'workspaces/getWorkspace',
getSearchTerm: 'workspaces/getSearchTerm'
getSearchTerm: 'workspaces/getSearchTerm',
applicationTheme: 'settings/getApplicationTheme'
}),
searchTerm () {
return this.getSearchTerm(this.connection.uid);
@@ -239,7 +240,10 @@ export default {
},
piePercentage (val) {
const perc = val / this.maxSize * 100;
return { background: `conic-gradient(lime ${perc}%, white 0)` };
if (this.applicationTheme === 'dark')
return { background: `conic-gradient(lime ${perc}%, white 0)` };
else
return { background: `conic-gradient(teal ${perc}%, silver 0)` };
},
setBreadcrumbs (payload) {
if (this.breadcrumbs.schema === payload.schema && this.breadcrumbs.table === payload.table) return;
@@ -262,7 +266,6 @@ export default {
.database-name {
position: sticky;
top: 0;
background: $bg-color-gray;
z-index: 2;
}
@@ -308,26 +311,15 @@ export default {
.database-name,
.misc-name {
&:hover {
color: $body-font-color;
background: $bg-color-light;
border-radius: 2px;
}
}
a.table-name {
&:hover {
color: inherit;
background: inherit;
}
}
.menu-item {
line-height: 1.2;
position: relative;
&:hover {
color: $body-font-color;
background: rgba($color: #fff, $alpha: 0.05);
border-radius: 2px;
}
}

View File

@@ -2,6 +2,7 @@
<ConfirmModal
:confirm-text="$t('word.confirm')"
size="medium"
class="options-modal"
@confirm="confirmForeignsChange"
@hide="$emit('hide')"
>
@@ -36,7 +37,7 @@
v-for="foreign in foreignProxy"
:key="foreign._id"
class="tile tile-centered c-hand mb-1 p-1"
:class="{'selected-foreign': selectedForeignID === foreign._id}"
:class="{'selected-element': selectedForeignID === foreign._id}"
@click="selectForeign($event, foreign._id)"
>
<div class="tile-icon">
@@ -268,8 +269,10 @@ export default {
this.$emit('foreigns-update', this.foreignProxy);
},
selectForeign (event, id) {
if (this.selectedForeignID !== id && !event.target.classList.contains('remove-field'))
if (this.selectedForeignID !== id && !event.target.classList.contains('remove-field')) {
this.selectedForeignID = id;
this.getRefFields();
}
},
getModalInnerHeight () {
const modalBody = document.querySelector('.modal-body');
@@ -369,23 +372,16 @@ export default {
}
&:hover {
background: $bg-color-light;
.tile-action {
opacity: 1;
}
}
&.selected-foreign {
background: $bg-color-light;
&.selected-element {
opacity: 1;
}
}
.editor-col {
border-left: 2px solid $bg-color-light;
}
.fields-list {
max-height: 80px;
overflow: auto;

View File

@@ -26,7 +26,19 @@
>
</div>
</div>
<div class="form-group">
<div v-if="customizations.languages" class="form-group">
<label class="form-label col-4">
{{ $t('word.language') }}
</label>
<div class="column">
<select v-model="optionsProxy.language" class="form-select">
<option v-for="language in customizations.languages" :key="language">
{{ language }}
</option>
</select>
</div>
</div>
<div v-if="customizations.definer" class="form-group">
<label class="form-label col-4">
{{ $t('word.definer') }}
</label>
@@ -65,6 +77,12 @@
class="form-select text-uppercase"
style="width: 0;"
>
<option v-if="localOptions.returns === 'VOID'">
VOID
</option>
<option v-if="!isInDataTypes">
{{ localOptions.returns }}
</option>
<optgroup
v-for="group in workspace.dataTypes"
:key="group.group"
@@ -81,6 +99,7 @@
</optgroup>
</select>
<input
v-if="customizations.parametersLength"
v-model="optionsProxy.returnsLength"
class="form-input"
type="number"
@@ -89,7 +108,7 @@
</div>
</div>
</div>
<div class="form-group">
<div v-if="customizations.comment" class="form-group">
<label class="form-label col-4">
{{ $t('word.comment') }}
</label>
@@ -112,7 +131,7 @@
</select>
</div>
</div>
<div class="form-group">
<div v-if="customizations.functionDataAccess" class="form-group">
<label class="form-label col-4">
{{ $t('message.dataAccess') }}
</label>
@@ -125,7 +144,7 @@
</select>
</div>
</div>
<div class="form-group">
<div v-if="customizations.functionDeterministic" class="form-group">
<div class="col-4" />
<div class="column">
<label class="form-checkbox form-inline">
@@ -159,6 +178,19 @@ export default {
computed: {
isTableNameValid () {
return this.optionsProxy.name !== '';
},
customizations () {
return this.workspace.customizations;
},
isInDataTypes () {
let typeNames = [];
for (const group of this.workspace.dataTypes) {
typeNames = group.types.reduce((acc, curr) => {
acc.push(curr.name);
return acc;
}, []);
}
return typeNames.includes(this.localOptions.returns);
}
},
created () {

View File

@@ -2,6 +2,7 @@
<ConfirmModal
:confirm-text="$t('word.confirm')"
size="medium"
class="options-modal"
@confirm="confirmParametersChange"
@hide="$emit('hide')"
>
@@ -36,7 +37,7 @@
v-for="param in parametersProxy"
:key="param._id"
class="tile tile-centered c-hand mb-1 p-1"
:class="{'selected-param': selectedParam === param._id}"
:class="{'selected-element': selectedParam === param._id}"
@click="selectParameter($event, param._id)"
>
<div class="tile-icon">
@@ -48,7 +49,7 @@
<div class="tile-title">
{{ param.name }}
</div>
<small class="tile-subtitle text-gray">{{ param.type }}{{ param.length ? `(${param.length})` : '' }}</small>
<small class="tile-subtitle text-gray">{{ param.type }}{{ param.length ? `(${param.length})` : '' }} · {{ param.context }}</small>
</div>
<div class="tile-action">
<button
@@ -105,7 +106,7 @@
</select>
</div>
</div>
<div class="form-group">
<div v-if="customizations.parametersLength" class="form-group">
<label class="form-label col-3">
{{ $t('word.length') }}
</label>
@@ -118,6 +119,37 @@
>
</div>
</div>
<div v-if="customizations.functionContext" class="form-group">
<label class="form-label col-3">
{{ $t('word.context') }}
</label>
<div class="column">
<label class="form-radio">
<input
v-model="selectedParamObj.context"
type="radio"
name="context"
value="IN"
> <i class="form-icon" /> IN
</label>
<label class="form-radio">
<input
v-model="selectedParamObj.context"
type="radio"
value="OUT"
name="context"
> <i class="form-icon" /> OUT
</label>
<label class="form-radio">
<input
v-model="selectedParamObj.context"
type="radio"
value="INOUT"
name="context"
> <i class="form-icon" /> INOUT
</label>
</div>
</div>
</form>
<div v-if="!parametersProxy.length" class="empty">
<div class="empty-icon">
@@ -167,6 +199,9 @@ export default {
},
isChanged () {
return JSON.stringify(this.localParameters) !== JSON.stringify(this.parametersProxy);
},
customizations () {
return this.workspace.customizations;
}
},
mounted () {
@@ -249,23 +284,16 @@ export default {
}
&:hover {
background: $bg-color-light;
.tile-action {
opacity: 1;
}
}
&.selected-param {
background: $bg-color-light;
&.selected-element {
opacity: 1;
}
}
.editor-col {
border-left: 2px solid $bg-color-light;
}
.fields-list {
max-height: 300px;
overflow: auto;

View File

@@ -2,6 +2,7 @@
<ConfirmModal
:confirm-text="$t('word.confirm')"
size="medium"
class="options-modal"
@confirm="confirmIndexesChange"
@hide="$emit('hide')"
>
@@ -36,7 +37,7 @@
v-for="index in indexesProxy"
:key="index._id"
class="tile tile-centered c-hand mb-1 p-1"
:class="{'selected-index': selectedIndexID === index._id}"
:class="{'selected-element': selectedIndexID === index._id}"
@click="selectIndex($event, index._id)"
>
<div class="tile-icon">
@@ -256,23 +257,16 @@ export default {
}
&:hover {
background: $bg-color-light;
.tile-action {
opacity: 1;
}
}
&.selected-index {
background: $bg-color-light;
&.selected-element {
opacity: 1;
}
}
.editor-col {
border-left: 2px solid $bg-color-light;
}
.fields-list {
max-height: 300px;
overflow: auto;

View File

@@ -26,7 +26,7 @@
>
</div>
</div>
<div class="form-group">
<div v-if="workspace.customizations.comment" class="form-group">
<label class="form-label col-4">
{{ $t('word.comment') }}
</label>
@@ -38,7 +38,7 @@
>
</div>
</div>
<div class="form-group">
<div v-if="workspace.customizations.autoIncrement" class="form-group">
<label class="form-label col-4">
{{ $t('word.autoIncrement') }}
</label>
@@ -50,7 +50,7 @@
>
</div>
</div>
<div class="form-group">
<div v-if="workspace.customizations.collations" class="form-group">
<label class="form-label col-4">
{{ $t('word.collation') }}
</label>
@@ -66,7 +66,7 @@
</select>
</div>
</div>
<div class="form-group">
<div v-if="workspace.customizations.engines" class="form-group">
<label class="form-label col-4">
{{ $t('word.engine') }}
</label>

View File

@@ -26,7 +26,19 @@
>
</div>
</div>
<div class="form-group">
<div v-if="customizations.languages" class="form-group">
<label class="form-label col-4">
{{ $t('word.language') }}
</label>
<div class="column">
<select v-model="optionsProxy.language" class="form-select">
<option v-for="language in customizations.languages" :key="language">
{{ language }}
</option>
</select>
</div>
</div>
<div v-if="customizations.definer" class="form-group">
<label class="form-label col-4">
{{ $t('word.definer') }}
</label>
@@ -54,7 +66,7 @@
</select>
</div>
</div>
<div class="form-group">
<div v-if="customizations.comment" class="form-group">
<label class="form-label col-4">
{{ $t('word.comment') }}
</label>
@@ -77,7 +89,7 @@
</select>
</div>
</div>
<div class="form-group">
<div v-if="customizations.procedureDataAccess" class="form-group">
<label class="form-label col-4">
{{ $t('message.dataAccess') }}
</label>
@@ -90,7 +102,7 @@
</select>
</div>
</div>
<div class="form-group">
<div v-if="customizations.procedureDeterministic" class="form-group">
<div class="col-4" />
<div class="column">
<label class="form-checkbox form-inline">
@@ -124,6 +136,9 @@ export default {
computed: {
isTableNameValid () {
return this.optionsProxy.name !== '';
},
customizations () {
return this.workspace.customizations;
}
},
created () {

View File

@@ -2,6 +2,7 @@
<ConfirmModal
:confirm-text="$t('word.confirm')"
size="medium"
class="options-modal"
@confirm="confirmParametersChange"
@hide="$emit('hide')"
>
@@ -36,7 +37,7 @@
v-for="param in parametersProxy"
:key="param._id"
class="tile tile-centered c-hand mb-1 p-1"
:class="{'selected-param': selectedParam === param._id}"
:class="{'selected-element': selectedParam === param._id}"
@click="selectParameter($event, param._id)"
>
<div class="tile-icon">
@@ -105,7 +106,7 @@
</select>
</div>
</div>
<div class="form-group">
<div v-if="customizations.parametersLength" class="form-group">
<label class="form-label col-3">
{{ $t('word.length') }}
</label>
@@ -118,7 +119,7 @@
>
</div>
</div>
<div class="form-group">
<div v-if="customizations.procedureContext" class="form-group">
<label class="form-label col-3">
{{ $t('word.context') }}
</label>
@@ -198,6 +199,9 @@ export default {
},
isChanged () {
return JSON.stringify(this.localParameters) !== JSON.stringify(this.parametersProxy);
},
customizations () {
return this.workspace.customizations;
}
},
mounted () {
@@ -234,10 +238,10 @@ export default {
addParameter () {
this.parametersProxy = [...this.parametersProxy, {
_id: uidGen(),
name: `Param${this.i++}`,
type: 'INT',
name: `param${this.i++}`,
type: this.workspace.dataTypes[0].types[0].name,
context: 'IN',
length: 10
length: ''
}];
if (this.parametersProxy.length === 1)
@@ -280,23 +284,16 @@ export default {
}
&:hover {
background: $bg-color-light;
.tile-action {
opacity: 1;
}
}
&.selected-param {
background: $bg-color-light;
&.selected-element {
opacity: 1;
}
}
.editor-col {
border-left: 2px solid $bg-color-light;
}
.fields-list {
max-height: 300px;
overflow: auto;

View File

@@ -7,6 +7,7 @@
class="btn btn-primary btn-sm"
:disabled="!isChanged"
:class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges"
>
<span>{{ $t('word.save') }}</span>
@@ -68,6 +69,7 @@
@remove-field="removeField"
@add-new-index="addNewIndex"
@add-to-index="addToIndex"
@rename-field="renameField"
/>
</div>
<WorkspacePropsOptionsModal
@@ -149,6 +151,7 @@ export default {
computed: {
...mapGetters({
getWorkspace: 'workspaces/getWorkspace',
selectedWorkspace: 'workspaces/getSelected',
getDatabaseVariable: 'workspaces/getDatabaseVariable'
}),
workspace () {
@@ -162,7 +165,7 @@ export default {
return this.getDatabaseVariable(this.connection.uid, 'default_storage_engine').value || '';
},
isSelected () {
return this.workspace.selected_tab === 'prop';
return this.workspace.selected_tab === 'prop' && this.selectedWorkspace === this.workspace.uid && this.table;
},
schema () {
return this.workspace.breadcrumbs.schema;
@@ -199,6 +202,12 @@ export default {
this.setUnsavedChanges(val);
}
},
created () {
window.addEventListener('keydown', this.onKey);
},
beforeDestroy () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
@@ -224,6 +233,20 @@ export default {
const { status, response } = await Tables.getTableColumns(params);
if (status === 'success') {
this.originalFields = response.map(field => {
if (field.autoIncrement)
field.defaultType = 'autoincrement';
else if (field.default === null)
field.defaultType = 'noval';
else if (field.default === 'NULL')
field.defaultType = 'null';
else if (isNaN(+field.default) && field.default.charAt(0) !== '\'')
field.defaultType = 'expression';
else {
field.defaultType = 'custom';
if (isNaN(+field.default) && !field.default.includes('\''))
field.default = `'${field.default}'`;
}
return { ...field, _id: uidGen() };
});
this.localFields = JSON.parse(JSON.stringify(this.originalFields));
@@ -433,11 +456,11 @@ export default {
_id: uidGen(),
name: `${this.$tc('word.field', 1)}_${++this.newFieldsCounter}`,
key: '',
type: 'int',
type: this.workspace.dataTypes[0].types[0].name,
schema: this.schema,
table: this.table,
numPrecision: null,
numLength: 11,
numLength: this.workspace.dataTypes[0].types[0].length,
datePrecision: null,
charLength: null,
nullable: false,
@@ -457,6 +480,20 @@ export default {
scrollable.scrollTop = scrollable.scrollHeight + 30;
}, 20);
},
renameField (payload) {
this.localIndexes = this.localIndexes.map(index => {
const fi = index.fields.findIndex(field => field === payload.old);
if (fi !== -1)
index.fields[fi] = payload.new;
return index;
});
this.localKeyUsage = this.localKeyUsage.map(key => {
if (key.field === payload.old)
key.field = payload.new;
return key;
});
},
removeField (uid) {
this.localFields = this.localFields.filter(field => field._id !== uid);
},
@@ -504,6 +541,15 @@ export default {
},
foreignsUpdate (foreigns) {
this.localKeyUsage = foreigns;
},
onKey (e) {
if (this.isSelected) {
e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
if (this.isChanged)
this.saveChanges();
}
}
}
}
};

View File

@@ -7,6 +7,7 @@
class="btn btn-primary btn-sm"
:disabled="!isChanged"
:class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges"
>
<span>{{ $t('word.save') }}</span>
@@ -47,7 +48,7 @@
<BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ $t('message.functionBody') }}</label>
<QueryEditor
v-if="isSelected"
v-show="isSelected"
ref="queryEditor"
:value.sync="localFunction.sql"
:workspace="workspace"
@@ -73,6 +74,7 @@
<ModalAskParameters
v-if="isAskingParameters"
:local-routine="localFunction"
:client="workspace.client"
@confirm="runFunction"
@close="hideAskParamsModal"
/>
@@ -119,13 +121,14 @@ export default {
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
isSelected () {
return this.workspace.selected_tab === 'prop';
return this.workspace.selected_tab === 'prop' && this.selectedWorkspace === this.workspace.uid && this.function;
},
schema () {
return this.workspace.breadcrumbs.schema;
@@ -170,6 +173,12 @@ export default {
destroyed () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
created () {
window.addEventListener('keydown', this.onKey);
},
beforeDestroy () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
@@ -280,9 +289,11 @@ export default {
switch (this.connection.client) { // TODO: move in a better place
case 'maria':
case 'mysql':
case 'pg':
sql = `SELECT \`${this.originalFunction.name}\` (${params.join(',')})`;
break;
case 'pg':
sql = `SELECT ${this.originalFunction.name}(${params.join(',')})`;
break;
case 'mssql':
sql = `SELECT ${this.originalFunction.name} ${params.join(',')}`;
break;
@@ -309,6 +320,15 @@ export default {
},
hideAskParamsModal () {
this.isAskingParameters = false;
},
onKey (e) {
if (this.isSelected) {
e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
if (this.isChanged)
this.saveChanges();
}
}
}
}
};

View File

@@ -7,6 +7,7 @@
class="btn btn-primary btn-sm"
:disabled="!isChanged"
:class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges"
>
<span>{{ $t('word.save') }}</span>
@@ -74,6 +75,7 @@
<ModalAskParameters
v-if="isAskingParameters"
:local-routine="localRoutine"
:client="workspace.client"
@confirm="runRoutine"
@close="hideAskParamsModal"
/>
@@ -120,13 +122,14 @@ export default {
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
isSelected () {
return this.workspace.selected_tab === 'prop';
return this.workspace.selected_tab === 'prop' && this.selectedWorkspace === this.workspace.uid && this.routine;
},
schema () {
return this.workspace.breadcrumbs.schema;
@@ -171,6 +174,12 @@ export default {
destroyed () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
created () {
window.addEventListener('keydown', this.onKey);
},
beforeDestroy () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
@@ -281,13 +290,13 @@ export default {
case 'maria':
case 'mysql':
case 'pg':
sql = `CALL \`${this.originalRoutine.name}\` (${params.join(',')})`;
sql = `CALL ${this.originalRoutine.name}(${params.join(',')})`;
break;
case 'mssql':
sql = `EXEC ${this.originalRoutine.name} ${params.join(',')}`;
break;
default:
sql = `CALL \`${this.originalRoutine.name}\` (${params.join(',')})`;
sql = `CALL \`${this.originalRoutine.name}\`(${params.join(',')})`;
}
this.newTab({ uid: this.connection.uid, content: sql, autorun: true });
@@ -309,6 +318,15 @@ export default {
},
hideAskParamsModal () {
this.isAskingParameters = false;
},
onKey (e) {
if (this.isSelected) {
e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
if (this.isChanged)
this.saveChanges();
}
}
}
}
};

View File

@@ -7,6 +7,7 @@
class="btn btn-primary btn-sm"
:disabled="!isChanged"
:class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges"
>
<span>{{ $t('word.save') }}</span>
@@ -169,13 +170,14 @@ export default {
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
isSelected () {
return this.workspace.selected_tab === 'prop';
return this.workspace.selected_tab === 'prop' && this.selectedWorkspace === this.workspace.uid && this.scheduler;
},
schema () {
return this.workspace.breadcrumbs.schema;
@@ -220,6 +222,12 @@ export default {
destroyed () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
created () {
window.addEventListener('keydown', this.onKey);
},
beforeDestroy () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
@@ -310,6 +318,15 @@ export default {
},
timingUpdate (options) {
this.localScheduler = options;
},
onKey (e) {
if (this.isSelected) {
e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
if (this.isChanged)
this.saveChanges();
}
}
}
}
};

View File

@@ -7,6 +7,7 @@
class="btn btn-primary btn-sm"
:disabled="!isChanged"
:class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges"
>
<span>{{ $t('word.save') }}</span>
@@ -140,13 +141,14 @@ export default {
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
isSelected () {
return this.workspace.selected_tab === 'prop';
return this.workspace.selected_tab === 'prop' && this.selectedWorkspace === this.workspace.uid && this.trigger;
},
schema () {
return this.workspace.breadcrumbs.schema;
@@ -191,6 +193,12 @@ export default {
destroyed () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
created () {
window.addEventListener('keydown', this.onKey);
},
beforeDestroy () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
@@ -274,6 +282,15 @@ export default {
this.editorHeight = size;
this.$refs.queryEditor.editor.resize();
}
},
onKey (e) {
if (this.isSelected) {
e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
if (this.isChanged)
this.saveChanges();
}
}
}
}
};

View File

@@ -7,6 +7,7 @@
class="btn btn-primary btn-sm"
:disabled="!isChanged"
:class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges"
>
<span>{{ $t('word.save') }}</span>
@@ -37,7 +38,7 @@
</div>
</div>
<div class="column col-auto">
<div class="form-group">
<div v-if="workspace.customizations.definer" class="form-group">
<label class="form-label">{{ $t('word.definer') }}</label>
<select
v-if="workspace.users.length"
@@ -68,7 +69,7 @@
</div>
<div class="columns">
<div class="column col-auto mr-2">
<div class="form-group">
<div v-if="workspace.customizations.viewSqlSecurity" class="form-group">
<label class="form-label">{{ $t('message.sqlSecurity') }}</label>
<label class="form-radio">
<input
@@ -91,7 +92,7 @@
</div>
</div>
<div class="column col-auto mr-2">
<div class="form-group">
<div v-if="workspace.customizations.viewAlgorithm" class="form-group">
<label class="form-label">{{ $t('word.algorithm') }}</label>
<label class="form-radio">
<input
@@ -122,7 +123,7 @@
</label>
</div>
</div>
<div class="column col-auto mr-2">
<div v-if="workspace.customizations.viewUpdateOption" class="column col-auto mr-2">
<div class="form-group">
<label class="form-label">{{ $t('message.updateOption') }}</label>
<label class="form-radio">
@@ -201,13 +202,14 @@ export default {
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
isSelected () {
return this.workspace.selected_tab === 'prop';
return this.workspace.selected_tab === 'prop' && this.selectedWorkspace === this.workspace.uid && this.view;
},
schema () {
return this.workspace.breadcrumbs.schema;
@@ -245,6 +247,12 @@ export default {
destroyed () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
created () {
window.addEventListener('keydown', this.onKey);
},
beforeDestroy () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
@@ -327,6 +335,15 @@ export default {
this.editorHeight = size;
this.$refs.queryEditor.editor.resize();
}
},
onKey (e) {
if (this.isSelected) {
e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
if (this.isChanged)
this.saveChanges();
}
}
}
}
};

View File

@@ -42,6 +42,13 @@
</div>
</div>
</div>
<div v-if="customizations.tableArray" class="th">
<div class="column-resizable">
<div class="table-column-title">
{{ $t('word.array') }}
</div>
</div>
</div>
<div class="th">
<div class="column-resizable">
<div class="table-column-title">
@@ -49,21 +56,21 @@
</div>
</div>
</div>
<div class="th">
<div v-if="customizations.unsigned" class="th">
<div class="column-resizable">
<div class="table-column-title">
{{ $t('word.unsigned') }}
</div>
</div>
</div>
<div class="th">
<div v-if="customizations.nullable" class="th">
<div class="column-resizable">
<div class="table-column-title">
{{ $t('message.allowNull') }}
</div>
</div>
</div>
<div class="th">
<div v-if="customizations.zerofill" class="th">
<div class="column-resizable">
<div class="table-column-title">
{{ $t('message.zeroFill') }}
@@ -77,14 +84,14 @@
</div>
</div>
</div>
<div class="th">
<div v-if="customizations.comment" class="th">
<div class="column-resizable">
<div class="table-column-title">
{{ $t('word.comment') }}
</div>
</div>
</div>
<div class="th">
<div v-if="customizations.collation" class="th">
<div class="column-resizable min-100">
<div class="table-column-title">
{{ $t('word.collation') }}
@@ -106,7 +113,9 @@
:indexes="getIndexes(row.name)"
:foreigns="getForeigns(row.name)"
:data-types="dataTypes"
:customizations="customizations"
@contextmenu="contextMenu"
@rename-field="$emit('rename-field', $event)"
/>
</draggable>
</div>
@@ -154,6 +163,9 @@ export default {
workspaceSchema () {
return this.getWorkspace(this.connUid).breadcrumbs.schema;
},
customizations () {
return this.getWorkspace(this.connUid).customizations;
},
dataTypes () {
return this.getWorkspace(this.connUid).dataTypes;
},

View File

@@ -1,8 +1,8 @@
<template>
<div class="tr" @contextmenu.prevent="$emit('contextmenu', $event, localRow._id)">
<div class="td" tabindex="0">
<div class="row-draggable">
<i class="mdi mdi-drag-horizontal row-draggable-icon" />
<div :class="customizations.sortableFields ? 'row-draggable' : 'text-center'">
<i v-if="customizations.sortableFields" class="mdi mdi-drag-horizontal row-draggable-icon" />
{{ localRow.order }}
</div>
</div>
@@ -60,6 +60,9 @@
class="form-select editable-field small-select text-uppercase"
@blur="editOFF"
>
<option v-if="!isInDataTypes">
{{ row.type }}
</option>
<optgroup
v-for="group in dataTypes"
:key="group.group"
@@ -68,7 +71,7 @@
<option
v-for="type in group.types"
:key="type.name"
:selected="localRow.type.toUpperCase() === type.name"
:selected="localRow.type === type.name"
:value="type.name"
>
{{ type.name }}
@@ -76,6 +79,16 @@
</optgroup>
</select>
</div>
<div
v-if="customizations.tableArray"
class="td"
tabindex="0"
>
<label class="form-checkbox">
<input v-model="localRow.isArray" type="checkbox">
<i class="form-icon" />
</label>
</div>
<div class="td type-int" tabindex="0">
<template v-if="fieldType.length">
<span
@@ -83,8 +96,22 @@
class="cell-content"
@dblclick="editON($event, localLength, 'length')"
>
{{ localLength }}
<span v-if="localRow.enumValues">
{{ localRow.enumValues }}
</span>
<span v-else>
{{ localLength }}
</span>
</span>
<input
v-else-if="localRow.enumValues"
ref="editField"
v-model="editingContent"
type="text"
autofocus
class="editable-field px-2"
@blur="editOFF"
>
<input
v-else
ref="editField"
@@ -96,7 +123,11 @@
>
</template>
</div>
<div class="td" tabindex="0">
<div
v-if="customizations.unsigned"
class="td"
tabindex="0"
>
<label class="form-checkbox">
<input
v-model="localRow.unsigned"
@@ -106,7 +137,11 @@
<i class="form-icon" />
</label>
</div>
<div class="td" tabindex="0">
<div
v-if="customizations.nullable"
class="td"
tabindex="0"
>
<label class="form-checkbox">
<input
v-model="localRow.nullable"
@@ -116,7 +151,11 @@
<i class="form-icon" />
</label>
</div>
<div class="td" tabindex="0">
<div
v-if="customizations.zerofill"
class="td"
tabindex="0"
>
<label class="form-checkbox">
<input
v-model="localRow.zerofill"
@@ -131,7 +170,11 @@
{{ fieldDefault }}
</span>
</div>
<div class="td type-varchar" tabindex="0">
<div
v-if="customizations.comment"
class="td type-varchar"
tabindex="0"
>
<span
v-if="!isInlineEditor.comment"
class="cell-content"
@@ -149,7 +192,11 @@
@blur="editOFF"
>
</div>
<div class="td" tabindex="0">
<div
v-if="customizations.collation"
class="td"
tabindex="0"
>
<template v-if="fieldType.collation">
<span
v-if="!isInlineEditor.collation"
@@ -220,7 +267,7 @@
</div>
</div>
</div>
<div class="mb-2">
<div v-if="customizations.nullable" class="mb-2">
<label class="form-radio form-inline">
<input
v-model="defaultValue.type"
@@ -230,7 +277,7 @@
><i class="form-icon" /> NULL
</label>
</div>
<div class="mb-2">
<div v-if="customizations.autoIncrement" class="mb-2">
<label class="form-radio form-inline">
<input
v-model="defaultValue.type"
@@ -261,7 +308,7 @@
</div>
</div>
</div>
<div>
<div v-if="customizations.onUpdate">
<div class="form-group">
<label class="form-label col-4">
{{ $t('message.onUpdate') }}
@@ -294,7 +341,8 @@ export default {
row: Object,
dataTypes: Array,
indexes: Array,
foreigns: Array
foreigns: Array,
customizations: Object
},
data () {
return {
@@ -343,6 +391,18 @@ export default {
},
isNullable () {
return !this.indexes.some(index => ['PRIMARY'].includes(index.type));
},
isInDataTypes () {
let typeNames = [];
for (const group of this.dataTypes) {
const groupTypeNames = group.types.reduce((acc, curr) => {
acc.push(curr.name);
return acc;
}, []);
typeNames = [...groupTypeNames, ...typeNames];
}
return typeNames.includes(this.row.type);
}
},
watch: {
@@ -389,40 +449,35 @@ export default {
});
this.defaultValue.onUpdate = this.localRow.onUpdate;
if (this.localRow.autoIncrement)
this.defaultValue.type = 'autoincrement';
else if (this.localRow.default === null)
this.defaultValue.type = 'noval';
else if (this.localRow.default === 'NULL')
this.defaultValue.type = 'null';
else if (this.localRow.default.match(/^'.*'$/g)) {
this.defaultValue.type = 'custom';
this.defaultValue.custom = this.localRow.default.replace(/(^')|('$)/g, '');
this.defaultValue.type = this.localRow.defaultType;
if (this.defaultValue.type === 'custom') {
this.defaultValue.custom = this.localRow.default
? this.localRow.default.includes('\'')
? this.localRow.default.split('\'')[1]
: this.localRow.default
: '';
}
else if (!isNaN(this.localRow.default.replace(/[:.-\s]/g, ''))) {
this.defaultValue.type = 'custom';
this.defaultValue.custom = this.localRow.default;
}
else {
this.defaultValue.type = 'expression';
if (this.defaultValue.type === 'expression')
this.defaultValue.expression = this.localRow.default;
}
},
updateRow () {
this.$emit('input', this.localRow);
},
editON (event, content, field) {
if (field === 'length') {
if (['integer', 'float', 'binary', 'spatial'].includes(this.fieldType.group)) this.editingField = 'numLength';
if (['string', 'other'].includes(this.fieldType.group)) this.editingField = 'charLength';
if (['time'].includes(this.fieldType.group)) this.editingField = 'datePrecision';
else if (['string', 'unknown'].includes(this.fieldType.group)) this.editingField = 'charLength';
else if (['other'].includes(this.fieldType.group)) this.editingField = 'enumValues';
else if (['time'].includes(this.fieldType.group)) this.editingField = 'datePrecision';
}
else
this.editingField = field;
this.editingContent = content;
this.originalContent = content;
if (this.localRow.enumValues && field === 'length') {
this.editingContent = this.localRow.enumValues;
this.originalContent = this.localRow.enumValues;
}
else {
this.editingContent = content;
this.originalContent = content;
}
const obj = { [field]: true };
this.isInlineEditor = { ...this.isInlineEditor, ...obj };
@@ -438,17 +493,22 @@ export default {
}
},
editOFF () {
if (this.editingField === 'name')
this.$emit('rename-field', { old: this.localRow[this.editingField], new: this.editingContent });
this.localRow[this.editingField] = this.editingContent;
if (this.editingField === 'type' && this.editingContent !== this.originalContent) {
this.localRow.numLength = null;
this.localRow.charLength = null;
this.localRow.datePrecision = null;
this.localRow.enumValues = '';
if (this.fieldType.length) {
if (['integer', 'float', 'binary', 'spatial'].includes(this.fieldType.group)) this.localRow.numLength = 11;
if (['string', 'other'].includes(this.fieldType.group)) this.localRow.charLength = 15;
if (['string'].includes(this.fieldType.group)) this.localRow.charLength = 15;
if (['time'].includes(this.fieldType.group)) this.localRow.datePrecision = 0;
if (['other'].includes(this.fieldType.group)) this.localRow.enumValues = '\'valA\',\'valB\'';
}
if (!this.fieldType.collation)
@@ -460,8 +520,7 @@ export default {
if (!this.fieldType.zerofill)
this.localRow.zerofill = false;
}
if (this.editingField === 'default') {
else if (this.editingField === 'default') {
switch (this.defaultValue.type) {
case 'autoincrement':
this.localRow.autoIncrement = true;
@@ -476,7 +535,7 @@ export default {
break;
case 'custom':
this.localRow.autoIncrement = false;
this.localRow.default = `'${this.defaultValue.custom}'`;
this.localRow.default = Number.isNaN(+this.defaultValue.custom) ? `'${this.defaultValue.custom}'` : this.defaultValue.custom;
break;
case 'expression':
this.localRow.autoIncrement = false;

View File

@@ -1,5 +1,12 @@
<template>
<div v-show="isSelected" class="workspace-query-tab column col-12 columns col-gapless">
<div
v-show="isSelected"
class="workspace-query-tab column col-12 columns col-gapless no-outline"
tabindex="0"
@keydown.116="runQuery(query)"
@keydown.ctrl.87="clear"
@keydown.ctrl.119="beautify"
>
<div class="workspace-query-runner column col-12">
<QueryEditor
v-show="isSelected"
@@ -24,6 +31,43 @@
<span>{{ $t('word.run') }}</span>
<i class="mdi mdi-24px mdi-play" />
</button>
<div class="dropdown export-dropdown pr-2">
<button
:disabled="!results.length || isQuering"
class="btn btn-dark btn-sm dropdown-toggle mr-0 pr-0"
tabindex="0"
>
<span>{{ $t('word.export') }}</span>
<i class="mdi mdi-24px mdi-file-export ml-1" />
<i class="mdi mdi-24px mdi-menu-down" />
</button>
<ul class="menu text-left">
<li class="menu-item">
<a class="c-hand" @click="downloadTable('json')">JSON</a>
</li>
<li class="menu-item">
<a class="c-hand" @click="downloadTable('csv')">CSV</a>
</li>
</ul>
</div>
<button
class="btn btn-dark btn-sm"
:disabled="!query || isQuering"
title="CTRL+F8"
@click="beautify()"
>
<span>{{ $t('word.format') }}</span>
<i class="mdi mdi-24px mdi-brush pl-1" />
</button>
<button
class="btn btn-link btn-sm"
:disabled="!query || isQuering"
title="CTRL+W"
@click="clear()"
>
<span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep pl-1" />
</button>
</div>
<div class="workspace-query-info">
<div
@@ -68,6 +112,7 @@
</template>
<script>
import { format } from 'sql-formatter';
import Schema from '@/ipc-api/Schema';
import QueryEditor from '@/components/QueryEditor';
import BaseLoader from '@/components/BaseLoader';
@@ -192,12 +237,36 @@ export default {
if (this.$refs.queryEditor)
this.$refs.queryEditor.editor.resize();
},
onKey (e) {
if (this.isSelected && this.isWorkspaceSelected) {
e.stopPropagation();
if (e.key === 'F5')
this.runQuery(this.query);
beautify () {
if (this.$refs.queryEditor) {
let language = 'sql';
switch (this.workspace.client) {
case 'mysql':
language = 'mysql';
break;
case 'maria':
language = 'mariadb';
break;
case 'pg':
language = 'postgresql';
break;
}
const formattedQuery = format(this.query, {
language,
uppercase: true
});
this.$refs.queryEditor.editor.session.setValue(formattedQuery);
}
},
clear () {
if (this.$refs.queryEditor)
this.$refs.queryEditor.editor.session.setValue('');
this.clearTabData();
},
downloadTable (format) {
this.$refs.queryTable.downloadTable(format, `${this.tab.type}-${this.tab.index}`);
}
}
};
@@ -232,7 +301,6 @@ export default {
.btn {
display: flex;
align-self: center;
color: $body-font-color;
margin-right: 0.4rem;
}
}
@@ -252,4 +320,9 @@ export default {
}
}
.export-dropdown {
.menu {
min-width: 100%;
}
}
</style>

View File

@@ -1,16 +1,18 @@
<template>
<div
ref="tableWrapper"
class="vscroll"
class="vscroll no-outline"
tabindex="0"
:style="{'height': resultsSize+'px'}"
@keyup.46="showDeleteConfirmModal"
>
<TableContext
v-if="isContext"
:context-event="contextEvent"
:selected-rows="selectedRows"
@delete-selected="deleteSelected"
@show-delete-modal="showDeleteConfirmModal"
@set-null="setNull"
@close-context="isContext = false"
@close-context="closeContext"
/>
<ul v-if="resultsWithRows.length > 1" class="tab tab-block result-tabs">
<li
@@ -75,6 +77,23 @@
</template>
</BaseVirtualScroll>
</div>
<ConfirmModal
v-if="isDeleteConfirmModal"
@confirm="deleteSelected"
@hide="hideDeleteConfirmModal"
>
<template :slot="'header'">
<div class="d-flex">
<i class="mdi mdi-24px mdi-delete mr-1" /> {{ $tc('message.deleteRows', selectedRows.length) }}
</div>
</template>
<div :slot="'body'">
<div class="mb-2">
{{ $tc('message.confirmToDeleteRows', selectedRows.length) }}
</div>
</div>
</ConfirmModal>
</div>
</template>
@@ -85,14 +104,17 @@ import { TEXT, LONG_TEXT, BLOB } from 'common/fieldTypes';
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
import WorkspaceQueryTableRow from '@/components/WorkspaceQueryTableRow';
import TableContext from '@/components/WorkspaceQueryTableContext';
import ConfirmModal from '@/components/BaseConfirmModal';
import { mapActions, mapGetters } from 'vuex';
import moment from 'moment';
export default {
name: 'WorkspaceQueryTable',
components: {
BaseVirtualScroll,
WorkspaceQueryTableRow,
TableContext
TableContext,
ConfirmModal
},
props: {
results: Array,
@@ -105,6 +127,7 @@ export default {
resultsSize: 1000,
localResults: [],
isContext: false,
isDeleteConfirmModal: false,
contextEvent: null,
selectedCell: null,
selectedRows: [],
@@ -116,24 +139,26 @@ export default {
},
computed: {
...mapGetters({
getWorkspace: 'workspaces/getWorkspace'
getWorkspace: 'workspaces/getWorkspace',
pageSize: 'settings/getDataTabLimit'
}),
workspaceSchema () {
return this.getWorkspace(this.connUid).breadcrumbs.schema;
},
primaryField () {
const primaryFields = this.fields.filter(field => ['pri', 'uni'].includes(field.key));
const primaryFields = this.fields.filter(field => field.key === 'pri');
const uniqueFields = this.fields.filter(field => field.key === 'uni');
if (primaryFields.length > 1 || !primaryFields.length)
if ((primaryFields.length > 1 || !primaryFields.length) && (uniqueFields.length > 1 || !uniqueFields.length))
return false;
return primaryFields[0];
return primaryFields[0] || uniqueFields[0];
},
isSortable () {
return this.fields.every(field => field.name);
},
isHardSort () {
return this.mode === 'table' && this.localResults.length === 1000;
return this.mode === 'table' && this.localResults.length === this.pageSize;
},
sortedResults () {
if (this.currentSort && !this.isHardSort) {
@@ -295,8 +320,17 @@ export default {
},
updateField (payload, row) {
const orgRow = this.localResults.find(lr => lr._id === row._id);
delete row._id;
delete orgRow._id;
Object.keys(orgRow).forEach(key => { // remap the row
if (orgRow[key] instanceof Date && moment(orgRow[key]).isValid()) { // if datetime
let datePrecision = '';
const precision = this.fields.find(field => field.name === key).datePrecision;
for (let i = 0; i < precision; i++)
datePrecision += i === 0 ? '.S' : 'S';
orgRow[key] = moment(orgRow[key]).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
}
});
const params = {
primary: this.primaryField.name,
@@ -309,7 +343,19 @@ export default {
};
this.$emit('update-field', params);
},
closeContext () {
this.isContext = false;
},
showDeleteConfirmModal (e) {
if (e && e.path && ['INPUT', 'TEXTAREA', 'SELECT'].includes(e.path[0].tagName))
return;
this.isDeleteConfirmModal = true;
},
hideDeleteConfirmModal () {
this.isDeleteConfirmModal = false;
},
deleteSelected () {
this.closeContext();
const rows = this.localResults.filter(row => this.selectedRows.includes(row._id)).map(row => {
delete row._id;
return row;
@@ -325,7 +371,6 @@ export default {
},
setNull () {
const row = this.localResults.find(row => this.selectedRows.includes(row._id));
delete row._id;
const params = {
primary: this.primaryField.name,
@@ -363,15 +408,15 @@ export default {
this.selectedRows.push(row);
else {
const lastID = this.selectedRows.slice(-1)[0];
const lastIndex = this.localResults.findIndex(el => el._id === lastID);
const clickedIndex = this.localResults.findIndex(el => el._id === row);
const lastIndex = this.sortedResults.findIndex(el => el._id === lastID);
const clickedIndex = this.sortedResults.findIndex(el => el._id === row);
if (lastIndex > clickedIndex) {
for (let i = clickedIndex; i < lastIndex; i++)
this.selectedRows.push(this.localResults[i]._id);
this.selectedRows.push(this.sortedResults[i]._id);
}
else if (lastIndex < clickedIndex) {
for (let i = clickedIndex; i > lastIndex; i--)
this.selectedRows.push(this.localResults[i]._id);
this.selectedRows.push(this.sortedResults[i]._id);
}
}
}
@@ -390,6 +435,8 @@ export default {
sort (field) {
if (!this.isSortable) return;
this.selectedRows = [];
if (this.mode === 'query')
field = `${this.getTable(this.resultsetIndex)}.${field}`;

View File

@@ -17,61 +17,30 @@
<i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ $tc('message.deleteRows', selectedRows.length) }}
</span>
</div>
<ConfirmModal
v-if="isConfirmModal"
@confirm="deleteRows"
@hide="hideConfirmModal"
>
<template :slot="'header'">
<div class="d-flex">
<i class="mdi mdi-24px mdi-delete mr-1" /> {{ $tc('message.deleteRows', selectedRows.length) }}
</div>
</template>
<div :slot="'body'">
<div class="mb-2">
{{ $tc('message.confirmToDeleteRows', selectedRows.length) }}
</div>
</div>
</ConfirmModal>
</BaseContextMenu>
</template>
<script>
import BaseContextMenu from '@/components/BaseContextMenu';
import ConfirmModal from '@/components/BaseConfirmModal';
export default {
name: 'WorkspaceQueryTableContext',
components: {
BaseContextMenu,
ConfirmModal
BaseContextMenu
},
props: {
contextEvent: MouseEvent,
selectedRows: Array
},
data () {
return {
isConfirmModal: false
};
},
computed: {
},
methods: {
showConfirmModal () {
this.isConfirmModal = true;
},
hideConfirmModal () {
this.isConfirmModal = false;
this.$emit('show-delete-modal');
},
closeContext () {
this.$emit('close-context');
},
deleteRows () {
this.$emit('delete-selected');
this.closeContext();
},
setNull () {
this.$emit('set-null');
this.closeContext();

View File

@@ -43,6 +43,16 @@
<option>true</option>
<option>false</option>
</select>
<select
v-else-if="enumArray"
v-model="editingContent"
class="form-select small-select editable-field"
@blur="editOFF"
>
<option v-for="value in enumArray" :key="value">
{{ value }}
</option>
</select>
<input
v-else
ref="editField"
@@ -182,7 +192,7 @@ import { mimeFromHex } from 'common/libs/mimeFromHex';
import { formatBytes } from 'common/libs/formatBytes';
import { bufferToBase64 } from 'common/libs/bufferToBase64';
import hexToBinary from 'common/libs/hexToBinary';
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BOOLEAN, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BOOLEAN, DATE, TIME, DATETIME, BLOB, BIT, HAS_TIMEZONE } from 'common/fieldTypes';
import { VueMaskDirective } from 'v-mask';
import ConfirmModal from '@/components/BaseConfirmModal';
import TextEditor from '@/components/BaseTextEditor';
@@ -286,6 +296,9 @@ export default {
for (let i = 0; i < precision; i++)
timeMask += i === 0 ? '.#' : '#';
if (HAS_TIMEZONE.includes(this.editingType))
timeMask += 'X##';
return { type: 'text', mask: timeMask };
}
@@ -299,6 +312,9 @@ export default {
for (let i = 0; i < precision; i++)
datetimeMask += i === 0 ? '.#' : '#';
if (HAS_TIMEZONE.includes(this.editingType))
datetimeMask += 'X##';
return { type: 'text', mask: datetimeMask };
}
@@ -330,6 +346,11 @@ export default {
}
return false;
},
enumArray () {
if (this.fields[this.editingField].enumValues)
return this.fields[this.editingField].enumValues.replaceAll('\'', '').split(',');
return false;
}
},
watch: {

View File

@@ -32,6 +32,29 @@
</div>
</div>
</div>
<div class="btn-group">
<button
class="btn btn-dark btn-sm mr-0"
:disabled="isQuering || page === 1"
title="CTRL+ᐊ"
@click="pageChange('prev')"
>
<i class="mdi mdi-24px mdi-skip-previous" />
</button>
<div class="btn btn-dark btn-sm mr-0 text-bold c-auto px-2">
{{ page }}
</div>
<button
class="btn btn-dark btn-sm mr-0"
:disabled="isQuering || (results.length && results[0].rows.length < limit)"
title="CTRL+ᐅ"
@click="pageChange('next')"
>
<i class="mdi mdi-24px mdi-skip-next" />
</button>
</div>
<div class="divider-vert py-3" />
<button
v-if="isTable"
@@ -74,7 +97,7 @@
<div v-if="results.length && results[0].rows">
{{ $t('word.results') }}: <b>{{ results[0].rows.length.toLocaleString() }}</b>
</div>
<div v-if="results.length && results[0].rows && tableInfo && results[0].rows.length < tableInfo.rows">
<div v-if="hasApproximately || page > 1">
{{ $t('word.total') }}: <b>{{ tableInfo.rows.toLocaleString() }}</b> <small>({{ $t('word.approximately') }})</small>
</div>
<div v-if="workspace.breadcrumbs.database">
@@ -149,13 +172,15 @@ export default {
isFakerModal: false,
autorefreshTimer: 0,
refreshInterval: null,
sortParams: {}
sortParams: {},
page: 1
};
},
computed: {
...mapGetters({
getWorkspace: 'workspaces/getWorkspace',
selectedWorkspace: 'workspaces/getSelected'
selectedWorkspace: 'workspaces/getSelected',
limit: 'settings/getDataTabLimit'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
@@ -179,6 +204,13 @@ export default {
catch (err) {
return { rows: 0 };
}
},
hasApproximately () {
return this.results.length &&
this.results[0].rows &&
this.tableInfo &&
this.results[0].rows.length === this.limit &&
this.results[0].rows.length < this.tableInfo.rows;
}
},
watch: {
@@ -190,6 +222,9 @@ export default {
this.$refs.queryTable.resetSort();
}
},
page () {
this.getTableData();
},
isSelected (val) {
if (val && this.lastTable !== this.table) {
this.getTableData();
@@ -221,6 +256,8 @@ export default {
uid: this.connection.uid,
schema: this.schema,
table: this.workspace.breadcrumbs.table || this.workspace.breadcrumbs.view,
limit: this.limit,
page: this.page,
sortParams
};
@@ -248,6 +285,14 @@ export default {
this.sortParams = sortParams;
this.getTableData(sortParams);
},
pageChange (direction) {
if (this.isQuering) return;
if (direction === 'next' && (this.results.length && this.results[0].rows.length === this.limit))
this.page++;
else if (direction === 'prev' && this.page > 1)
this.page--;
},
showAddModal () {
this.isAddModal = true;
},
@@ -266,6 +311,13 @@ export default {
e.stopPropagation();
if (e.key === 'F5')
this.reloadTable();
if (e.ctrlKey) {
if (e.key === 'ArrowRight')
this.pageChange('next');
if (e.key === 'ArrowLeft')
this.pageChange('prev');
}
}
},
setRefreshInterval () {
@@ -289,10 +341,6 @@ export default {
.export-dropdown {
.menu {
min-width: 100%;
.menu-item a:hover {
background: $bg-color-gray;
}
}
}
</style>

View File

@@ -101,7 +101,11 @@ module.exports = {
tools: 'Tools',
variables: 'Variables',
processes: 'Processes',
database: 'Database'
database: 'Database',
scratchpad: 'Scratchpad',
array: 'Array',
changelog: 'Changelog',
format: 'Format'
},
message: {
appWelcome: 'Welcome to Antares SQL Client!',
@@ -204,7 +208,10 @@ module.exports = {
createNewSchema: 'Create new schema',
schemaName: 'Schema name',
editSchema: 'Edit schema',
deleteSchema: 'Delete schema'
deleteSchema: 'Delete schema',
markdownSupported: 'Markdown supported',
plantATree: 'Plant a Tree',
dataTabPageSize: 'DATA tab page size'
},
faker: {
address: 'Address',

View File

@@ -9,7 +9,8 @@ const i18n = new VueI18n({
'it-IT': require('./it-IT'),
'ar-SA': require('./ar-SA'),
'es-ES': require('./es-ES'),
'fr-FR': require('./fr-FR')
'fr-FR': require('./fr-FR'),
'pt-BR': require('./pt-BR')
}
});
export default i18n;

View File

@@ -39,7 +39,73 @@ module.exports = {
data: 'Dati',
properties: 'Proprietà',
insert: 'Inserisci',
connecting: 'Connessione in corso'
connecting: 'Connessione in corso',
name: 'Nome',
collation: 'Confronto',
clear: 'Scarta',
options: 'Opzioni',
autoRefresh: 'Auto-aggiorna',
indexes: 'Indici',
foreignKeys: 'Chiavi esterne',
length: 'Lunghezza',
unsigned: 'Senza segno',
default: 'Default',
comment: 'Commento',
key: 'Chiave | Chiavi',
order: 'Ordine',
expression: 'Espressione',
autoIncrement: 'Auto Incremento',
engine: 'Motore',
field: 'Campo | Campi',
approximately: 'Approssimativamente',
total: 'Totali',
table: 'Tabella',
discard: 'Scarta',
stay: 'Resta',
author: 'Autore',
light: 'Chiaro',
dark: 'Scuro',
autoCompletion: 'Auto Completamento',
application: 'Applicazione',
editor: 'Editor',
view: 'Vista',
definer: 'Definer',
algorithm: 'Algoritmo',
trigger: 'Trigger | Triggers',
storedRoutine: 'Stored routine | Stored routines',
scheduler: 'Scheduler | Schedulers',
event: 'Evento',
parameters: 'Parametri',
function: 'Funzione | Funzioni',
deterministic: 'Deterministico',
context: 'Contesto',
export: 'Esporta',
returns: 'Ritorna',
timing: 'Temporizzazione',
state: 'Stato',
execution: 'Esecuzione',
starts: 'Inizia',
ends: 'Finisce',
ssl: 'SSL',
privateKey: 'Chiave privata',
certificate: 'Certificato',
caCertificate: 'Certificato CA',
ciphers: 'Ciphers',
upload: 'Carica',
browse: 'Sfoglia',
faker: 'Faker',
content: 'Contenuto',
cut: 'Taglia',
copy: 'Copia',
paste: 'Incolla',
tools: 'Strumenti',
variables: 'Variabili',
processes: 'Processi',
database: 'Database',
scratchpad: 'Blocco appunti',
array: 'Array',
changelog: 'Changelog',
format: 'Formatta'
},
message: {
appWelcome: 'Benvenuto in Antares SQL Client!',
@@ -71,6 +137,243 @@ module.exports = {
addNewRow: 'Aggiungi nuova riga',
numberOfInserts: 'Numero di insert',
openNewTab: 'Apri nuova scheda',
affectedRows: 'Righe interessate'
affectedRows: 'Righe interessate',
createNewDatabase: 'Crea nuovo database',
databaseName: 'Nome database',
serverDefault: 'Default del server',
deleteDatabase: 'Cancella database',
editDatabase: 'Modifica database',
clearChanges: 'Scarta modifiche',
addNewField: 'Aggiungi nuovo campo',
manageIndexes: 'Gestisci indici',
manageForeignKeys: 'Gestisci chiavi esterne',
allowNull: 'Permetti NULL',
zeroFill: 'Riempimento con zero',
customValue: 'Varore personalizzato',
onUpdate: 'All\'aggiornamento',
deleteField: 'Cancella campo',
createNewIndex: 'Crea nuovo indice',
addToIndex: 'Aggiungi a indice',
createNewTable: 'Crea nuova tabella',
emptyTable: 'Svuota tabella',
deleteTable: 'Cancella tabella',
emptyCorfirm: 'Confermi di voler svuotare',
unsavedChanges: 'Modifiche non salvate',
discardUnsavedChanges: 'Hai modifiche non salvate. Lasciando questa scheda le modifiche saranno scartate.',
thereAreNoIndexes: 'Non ci sono indici',
thereAreNoForeign: 'Non ci sono chiavi esterne',
createNewForeign: 'Crea nuova chiave esterna',
referenceTable: 'Tabella di rif.',
referenceField: 'Campo di rif.',
foreignFields: 'Campi esterni',
invalidDefault: 'Default non valido',
onDelete: 'All\'eliminazione',
applicationTheme: 'Tema applicazione',
editorTheme: 'Tema editor',
wrapLongLines: 'A capo righe lunghe',
selectStatement: 'Dichiarazione select',
triggerStatement: 'Dichiarazione trigger',
sqlSecurity: 'Sicurezza SQL',
updateOption: 'Update option',
deleteView: 'Elimina vista',
createNewView: 'Crea nuova vista',
deleteTrigger: 'Elimina trigger',
createNewTrigger: 'Crea nuovo trigger',
currentUser: 'Utente attuale',
routineBody: 'Corpo della routine',
dataAccess: 'Accesso dati',
thereAreNoParameters: 'Non ci sono parametri',
createNewParameter: 'Crea nuovo parametro',
createNewRoutine: 'Crea nuova stored routine',
deleteRoutine: 'Elimina stored routine',
functionBody: 'Corpo della funzione',
createNewFunction: 'Crea nuova funzione',
deleteFunction: 'Elimina funzione',
schedulerBody: 'Corpo dello scheduler',
createNewScheduler: 'Crea nuovo scheduler',
deleteScheduler: 'Elimina scheduler',
preserveOnCompletion: 'Preserva al completamento',
enableSsl: 'Abilita SSL',
manualValue: 'Valore manuale',
tableFiller: 'Riempitore Tabella',
fakeDataLanguage: 'Lingua dati falsi',
searchForElements: 'Cerca elementi',
selectAll: 'Seleziona tutto',
queryDuration: 'Durata query',
includeBetaUpdates: 'Includi aggiornamenti beta',
setNull: 'Imposta NULL',
processesList: 'Lista processi',
processInfo: 'Info processo',
manageUsers: 'Gestisci utenti',
createNewSchema: 'Crea nuovo schema',
schemaName: 'Nome schema',
editSchema: 'Modifica schema',
deleteSchema: 'Elimina schema',
markdownSupported: 'Markdown supportato',
plantATree: 'Pianta un albero'
},
faker: {
address: 'Indirizzo',
commerce: 'Commercio',
company: 'Compagnia',
database: 'Database',
date: 'Data',
finance: 'Finanza',
git: 'Git',
hacker: 'Hacker',
internet: 'Internet',
lorem: 'Lorem',
name: 'Nome',
music: 'Musica',
phone: 'Telefono',
random: 'Casuale',
system: 'Sistema',
time: 'Tempo',
vehicle: 'Veicolo',
zipCode: 'Codice zip',
zipCodeByState: 'Codice zip per stato',
city: 'Città',
cityPrefix: 'Prefisso città',
citySuffix: 'Suffisso città',
streetName: 'Nome strada',
streetAddress: 'Indirizzo strada',
streetSuffix: 'Suffisso strada',
streetPrefix: 'Prefisso strada',
secondaryAddress: 'Indirizzo secondario',
county: 'Contea',
country: 'Nazione',
countryCode: 'Codice nazione',
state: 'Stato',
stateAbbr: 'Abbreviazione stato',
latitude: 'Latitudine',
longitude: 'Longitudine',
direction: 'Direzione',
cardinalDirection: 'Direzione cardinale',
ordinalDirection: 'Direzione ordinale',
nearbyGPSCoordinate: 'Coordinate GPS vicine',
timeZone: 'Time zone',
color: 'Colore',
department: 'Dipartimento',
productName: 'Nome prodotto',
price: 'Prezzo',
productAdjective: 'Aggettivo prodotto',
productMaterial: 'Materiale prodotto',
product: 'Prodotto',
productDescription: 'Descrizione prodotto',
suffixes: 'Suffissi',
companyName: 'Nome compagnia',
companySuffix: 'Suffisso compagnia',
catchPhrase: 'Slogan',
bs: 'BS',
catchPhraseAdjective: 'Aggettivo slogan',
catchPhraseDescriptor: 'Descrittore slogan',
catchPhraseNoun: 'Sostantivo slogan',
bsAdjective: 'Aggettivo BS',
bsBuzz: 'Buzz BS',
bsNoun: 'Sostantivo BS',
column: 'Colonna',
type: 'Tipo',
collation: 'Confronto',
engine: 'Motore',
past: 'Passato',
future: 'Futuro',
between: 'Tra',
recent: 'Recente',
soon: 'Presto',
month: 'Mese',
weekday: 'Giorno della settimana',
account: 'Account',
accountName: 'Nome account',
routingNumber: 'Numero di instradamento',
mask: 'Maschera',
amount: 'Ammontare',
transactionType: 'Tipo transazione',
currencyCode: 'Codice valuta',
currencyName: 'Nome valuta',
currencySymbol: 'Simbolo valuta',
bitcoinAddress: 'Indirizzo Bitcoin',
litecoinAddress: 'Indirizzo Litecoin',
creditCardNumber: 'Numero carta di credito',
creditCardCVV: 'CVV carta di credito',
ethereumAddress: 'Indirizzo Ethereum',
iban: 'Iban',
bic: 'Bic',
transactionDescription: 'Descrizione transazione',
branch: 'Ramo',
commitEntry: 'Commit entry',
commitMessage: 'Messaggio di commit',
commitSha: 'SHA del commit',
shortSha: 'SHA breve',
abbreviation: 'Abbreviazione',
adjective: 'Aggettivo',
noun: 'Sostantivo',
verb: 'Verbo',
ingverb: 'Ingverb',
phrase: 'Frase',
avatar: 'Avatar',
email: 'Email',
exampleEmail: 'Email di esempio',
userName: 'Username',
protocol: 'Protocollo',
url: 'Url',
domainName: 'Nome dominio',
domainSuffix: 'Suffisso dominio',
domainWord: 'Parola dominio',
ip: 'Ip',
ipv6: 'Ipv6',
userAgent: 'User agent',
mac: 'Mac',
password: 'Password',
word: 'Parola',
words: 'Parole',
sentence: 'Sentenza',
slug: 'Slug',
sentences: 'Sentenze',
paragraph: 'Paragrafo',
paragraphs: 'Paragrafi',
text: 'Testo',
lines: 'Righe',
genre: 'Genere',
firstName: 'Nome',
lastName: 'Cognome',
middleName: 'Secondo nome',
findName: 'Nome completo',
jobTitle: 'Titolo di lavoro',
gender: 'Genere',
prefix: 'Prefisso',
suffix: 'Suffisso',
title: 'Titolo',
jobDescriptor: 'Descrittore del lavoro',
jobArea: 'Area di lavoro',
jobType: 'Tipo di lavoro',
phoneNumber: 'Numero di telefono',
phoneNumberFormat: 'Formato numeri di telefono',
phoneFormats: 'Formati di telefono',
number: 'Numero',
float: 'Float',
arrayElement: 'Elemento array',
arrayElements: 'Elementi array',
objectElement: 'Elemento object',
uuid: 'Uuid',
boolean: 'Booleano',
image: 'Immagine',
locale: 'Localizzazione',
alpha: 'Alfabetico',
alphaNumeric: 'Alfanumerico',
hexaDecimal: 'Esadecimale',
fileName: 'Nome file',
commonFileName: 'Nome file comune',
mimeType: 'Mime type',
commonFileType: 'Tipo file comune',
commonFileExt: 'Estensione file comune',
fileType: 'Tipo file',
fileExt: 'Estensione file',
directoryPath: 'Percorso directory',
filePath: 'Percorso file',
semver: 'Semver',
manufacturer: 'Produttore',
model: 'Modello',
fuel: 'Carburante',
vin: 'Vin'
}
};

379
src/renderer/i18n/pt-BR.js Normal file
View File

@@ -0,0 +1,379 @@
module.exports = {
word: {
edit: 'Editar',
save: 'Salvar',
close: 'Fechar',
delete: 'Apagar',
confirm: 'Confirmar',
cancel: 'Cancelar',
send: 'Enviar',
connectionName: 'Nome da Conexão',
client: 'Cliente',
hostName: 'Nome do Host',
port: 'Porta',
user: 'Usuário',
password: 'Senha',
credentials: 'Credenciais',
connect: 'Conectar',
connected: 'Conectado',
disconnect: 'Desconectar',
disconnected: 'Desconectado',
refresh: 'Atualizar',
settings: 'Opções',
general: 'Geral',
themes: 'Temas',
update: 'Atualizar',
about: 'Sobre',
language: 'Linguagem',
version: 'Versão',
donate: 'Doação',
run: 'Executar',
schema: 'Schema',
results: 'Resultados',
size: 'Tamanho',
seconds: 'Segundos',
type: 'Tipo',
mimeType: 'Mime-Type',
download: 'Download',
add: 'Adicionar',
data: 'Data',
properties: 'Propriedades',
insert: 'Inserção',
connecting: 'Connectando',
name: 'Nome',
collation: 'Collation',
clear: 'Limpar',
options: 'Opções',
autoRefresh: 'Atualização Automática',
indexes: 'Indices',
foreignKeys: 'Chaves Estrangeiras',
length: 'Tamanho',
unsigned: 'Sem sinal (Unsigned)',
default: 'Padrão',
comment: 'Comente',
key: 'Chave | Chaves',
order: 'Ordem',
expression: 'Expressão',
autoIncrement: 'Auto Incremental',
engine: 'Engine',
field: 'Campo | Campos',
approximately: 'Aproximadamente',
total: 'Total',
table: 'Tabela',
discard: 'Descartar',
stay: 'Stay',
author: 'Autor',
light: 'Claro',
dark: 'Escuro',
autoCompletion: 'Auto Complemento',
application: 'Aplicação',
editor: 'Editor',
view: 'Visão',
definer: 'Definidor',
algorithm: 'Algoritmo',
trigger: 'Trigger | Triggers',
storedRoutine: 'Stored routine | Stored routines',
scheduler: 'Scheduler | Schedulers',
event: 'Event',
parameters: 'Parametros',
function: 'Função | Funções',
deterministic: 'Deterministico',
context: 'Contexto',
export: 'Exportar',
returns: 'Retornos',
timing: 'Tempo',
state: 'Estado',
execution: 'Execução',
starts: 'Começa',
ends: 'Termina',
ssl: 'SSL',
privateKey: 'Chave Privada',
certificate: 'Certificado',
caCertificate: 'Certificado CA',
ciphers: 'Cifras (ciphers)',
upload: 'Upload',
browse: 'Navegar',
faker: 'Faker',
content: 'Conteúdo',
cut: 'Cortar',
copy: 'Copiar',
paste: 'Colar',
tools: 'Ferramentas',
variables: 'Variaveis',
processes: 'Processos',
database: 'Banco de Dados',
scratchpad: 'Rascunho',
array: 'Array',
changelog: 'Logs de alteração',
format: 'Formato'
},
message: {
appWelcome: 'Bem vindo ao Antares SQL Client!',
appFirstStep: 'Seu primeiro passo: criar uma nova conexão de banco de dados.',
addConnection: 'Adicionar Conexão',
createConnection: 'Criar Conexão',
createNewConnection: 'Criar Nova Conexão',
askCredentials: 'Pedir Credenciais',
testConnection: 'Testar Conexão',
editConnection: 'Editar Conexão',
deleteConnection: 'Apagar Conexão',
deleteCorfirm: 'Você confirma o cancelamento de',
connectionSuccessfullyMade: 'Conexão feita com sucesso!',
madeWithJS: 'Feito com 💛 e JavaScript!',
checkForUpdates: 'Verificar se há novas atualizações',
noUpdatesAvailable: 'Sem atualizações disponíveis',
checkingForUpdate: 'Verificando se há novas atualizações',
checkFailure: 'Erro na verificação, por favor, tente mais tarde',
updateAvailable: 'Atualização disponível',
downloadingUpdate: 'Baixando a atualização',
updateDownloaded: 'Atualização baixada',
restartToInstall: 'Reinicie o Antares para instalar',
unableEditFieldWithoutPrimary: 'Indisponível a edição de um campo sem a chave primária na tabela de resultados',
editCell: 'Editar celula',
deleteRows: 'Apgar linha | Apagar {count} linhas',
confirmToDeleteRows: 'Você confirma a exclusão de uma linha? | Você confirma a exclusão de {count} linhas?',
notificationsTimeout: 'Notificações de timeout',
uploadFile: 'Upload de arquivo',
addNewRow: 'Adicionar nova linha',
numberOfInserts: 'Número de inserções',
openNewTab: 'Abrir nova aba',
affectedRows: 'Linhas afetadas',
createNewDatabase: 'Criar novo banco de dados',
databaseName: 'Nome do banco de dados',
serverDefault: 'Servidor padrão',
deleteDatabase: 'Apagar banco de dados',
editDatabase: 'Editar banco de dados',
clearChanges: 'Limpar alterações',
addNewField: 'Adicionar novo campo',
manageIndexes: 'Gerenciar índices',
manageForeignKeys: 'Gerenciar chaes estrangeiras',
allowNull: 'Permitir NULL',
zeroFill: 'Preenchimento zero',
customValue: 'Valor personalizado',
onUpdate: 'Quando atualizar',
deleteField: 'Apagar campo',
createNewIndex: 'Criar novo índice',
addToIndex: 'Adicionar ao índice',
createNewTable: 'Criar nova tabela',
emptyTable: 'Tabela vazia',
deleteTable: 'Apagar tabela',
emptyCorfirm: 'Você confirma o esvaziamento',
unsavedChanges: 'Alterações não salvas',
discardUnsavedChanges: 'Você tem algumas alterações não salvas. Ao sair desta guia, essas alterações serão descartadas.',
thereAreNoIndexes: 'Não há índices',
thereAreNoForeign: 'Não há chaves estrangeiras',
createNewForeign: 'Criar nova chave estrangeira',
referenceTable: 'Ref. tabela',
referenceField: 'Ref. campo',
foreignFields: 'Campos estrangeiros',
invalidDefault: 'Padrão inválido',
onDelete: 'Quando apagar',
applicationTheme: 'Tema da aplicação',
editorTheme: 'Editor de tema',
wrapLongLines: 'Quebrar linhas longas',
selectStatement: 'Select statement',
triggerStatement: 'Trigger statement',
sqlSecurity: 'Segurança SQL',
updateOption: 'Opção de atualização',
deleteView: 'Apagar view',
createNewView: 'Criar nova view',
deleteTrigger: 'Apgar trigger',
createNewTrigger: 'Criar nova trigger',
currentUser: 'Usuário atual',
routineBody: 'Corpo da rotina',
dataAccess: 'Acesso de dados',
thereAreNoParameters: 'Não há parametros',
createNewParameter: 'Criar novo parametro',
createNewRoutine: 'Criar nova stored routine',
deleteRoutine: 'Apgar stored routine',
functionBody: 'Corpo da função',
createNewFunction: 'Criar nova função',
deleteFunction: 'Apagar função',
schedulerBody: 'Corpo do agendador',
createNewScheduler: 'Criar novo agendador',
deleteScheduler: 'Apagar agendador',
preserveOnCompletion: 'Preservar na conclusão',
enableSsl: 'Habilitar SSL',
manualValue: 'Valor manual',
tableFiller: 'Preenchedor de tabela',
fakeDataLanguage: 'Linguagem de dados fake',
searchForElements: 'Buscar por elementos',
selectAll: 'Selecionar todos',
queryDuration: 'Tempo de Consulta',
includeBetaUpdates: 'Incluir atualizações beta',
setNull: 'Setar NULL',
processesList: 'Lista de processos',
processInfo: 'Informação de processos',
manageUsers: 'Gerenciar usuários',
createNewSchema: 'Criar novo schema',
schemaName: 'Nome schema',
editSchema: 'Editar schema',
deleteSchema: 'Apagar schema',
markdownSupported: 'Markdown suportado',
plantATree: 'Plante uma árvore'
},
faker: {
address: 'Endereço',
commerce: 'Comércio',
company: 'Empresa',
database: 'Banco de dados',
date: 'Data',
finance: 'Finança',
git: 'Git',
hacker: 'Hacker',
internet: 'Internet',
lorem: 'Lorem',
name: 'Nome',
music: 'Música',
phone: 'Telefone',
random: 'Randomico',
system: 'Sistema',
time: 'Tempo',
vehicle: 'Veículo',
zipCode: 'Código postal',
zipCodeByState: 'Código postal por estado',
city: 'Cidade',
cityPrefix: 'Cidade prefixo',
citySuffix: 'Cidade sufixo',
streetName: 'Nome da rua',
streetAddress: 'Endereço rua',
streetSuffix: 'Rua sufixo',
streetPrefix: 'Rua prefixo',
secondaryAddress: 'Endereço secundário',
county: 'Município',
country: 'País',
countryCode: 'Código do País',
state: 'Estado',
stateAbbr: 'Sigla estado',
latitude: 'Latitude',
longitude: 'Longitude',
direction: 'Direção',
cardinalDirection: 'Direção cardinal',
ordinalDirection: 'Direção Ordinal',
nearbyGPSCoordinate: 'Coordenadas de GPS próximas',
timeZone: 'Fuso Horário',
color: 'Cor',
department: 'Departamento',
productName: 'Nome produto',
price: 'Preço',
productAdjective: 'Adjetivo produto',
productMaterial: 'Material de produto',
product: 'Produto',
productDescription: 'Descrição do produto',
suffixes: 'Sufixos',
companyName: 'Nome da empresa',
companySuffix: 'Sufixo empresa',
catchPhrase: 'Frase de efeito',
bs: 'BS',
catchPhraseAdjective: 'Adjetivo frase de efeito',
catchPhraseDescriptor: 'Descritor de frase de efeito',
catchPhraseNoun: 'Frase de efeito sinônimo',
bsAdjective: 'BS adjetivo',
bsBuzz: 'Rumor BS',
bsNoun: 'Sinônimo BS',
column: 'Coluna',
type: 'Tipo',
collation: 'Colação',
engine: 'Engine',
past: 'Passado',
future: 'Futuro',
between: 'Entre',
recent: 'Recente',
soon: 'Em breve',
month: 'Mês',
weekday: 'Semana',
account: 'Conta',
accountName: 'Nome conta',
routingNumber: 'Número de roteamento',
mask: 'Máscara',
amount: 'Soma',
transactionType: 'Tipo transição',
currencyCode: 'Código moeda',
currencyName: 'Código nome',
currencySymbol: 'Código simbolo',
bitcoinAddress: 'Endereço Bitcoin',
litecoinAddress: 'Endereço Litecoin',
creditCardNumber: 'Número cartão de crédito',
creditCardCVV: 'CVV cartão de crédito',
ethereumAddress: 'Endereço Ethereum',
iban: 'Iban',
bic: 'Bic',
transactionDescription: 'Descrição transação',
branch: 'Branch',
commitEntry: 'Inserção commit',
commitMessage: 'Mensagem commit',
commitSha: 'SHA Commit',
shortSha: 'SHA Curto',
abbreviation: 'Abreviação',
adjective: 'Adjetivo',
noun: 'Sinônimo',
verb: 'Verbo',
ingverb: 'Ingverb',
phrase: 'Frase',
avatar: 'Avatar',
email: 'Email',
exampleEmail: 'Exemplo email',
userName: 'Nome de usuário',
protocol: 'Protocolo',
url: 'Url',
domainName: 'Nome domínio',
domainSuffix: 'Sufixo domíno',
domainWord: 'Palavra de domínio',
ip: 'Ip',
ipv6: 'Ipv6',
userAgent: 'Agente usuário',
mac: 'Mac',
password: 'Senha',
word: 'Palavra',
words: 'Palavras',
sentence: 'Sentença',
slug: 'Slug',
sentences: 'Sentenças',
paragraph: 'Paragrafo',
paragraphs: 'Paragrafos',
text: 'Texto',
lines: 'Linhas',
genre: 'Genero',
firstName: 'Primeiro nome',
lastName: 'Ultimo nome',
middleName: 'Nome do meio',
findName: 'Nome completo',
jobTitle: 'Cargo',
gender: 'Genero',
prefix: 'Prefixo',
suffix: 'Sufixo',
title: 'Titulo',
jobDescriptor: 'Descrição cargo',
jobArea: 'Area de trabalho',
jobType: 'Tipo de trabalho',
phoneNumber: 'Número telefone',
phoneNumberFormat: 'Formato número de telefone',
phoneFormats: 'Formatos de telefone',
number: 'Número',
float: 'Flutuante',
arrayElement: 'Elemento array',
arrayElements: 'Elementos Array',
objectElement: 'Elemento object',
uuid: 'Uuid',
boolean: 'Booleano',
image: 'Imagem',
locale: 'Localidade',
alpha: 'Alpha',
alphaNumeric: 'Alphanumerico',
hexaDecimal: 'Hexadecimal',
fileName: 'Nome arquivo',
commonFileName: 'Nome de arquivo comum',
mimeType: 'Mime type',
commonFileType: 'Tipo de arquivo comum',
commonFileExt: 'Extensão de arquivo comum',
fileType: 'Tipo de arquivo',
fileExt: 'Extensão de arquivo',
directoryPath: 'Caminho do diretório',
filePath: 'Caminho do arquivo',
semver: 'Semver',
manufacturer: 'Fabricante',
model: 'Modelo',
fuel: 'Combusível',
vin: 'Vin'
}
};

View File

@@ -3,5 +3,6 @@ export default {
'it-IT': 'Italiano',
'ar-SA': 'العربية',
'es-ES': 'Español',
'fr-FR': 'Français'
'fr-FR': 'Français',
'pt-BR': 'Português (Brasil)'
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 889 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 172 KiB

View File

@@ -1,6 +1,154 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 210 210">
<defs/>
<path fill="#e36929" d="M180.088 107.51a72.967 69.474 0 01-72.858 69.473 72.967 69.474 0 01-73.075-69.266 72.967 69.474 0 0172.639-69.68 72.967 69.474 0 0173.292 69.056"/>
<path fill="#f8d163" d="M82.985 102.48a17.02 21.735 21.455 01-23.767 14.012 17.02 21.735 21.455 01-7.938-26.403 17.02 21.735 21.455 0123.744-14.091 17.02 21.735 21.455 018.009 26.36m74.797 16.204a17.472 11.51-52.488 01-1.695 20.917 17.472 11.51-52.488 01-19.742 6.678 17.472 11.51-52.488 011.635-20.897 17.472 11.51-52.488 0119.747-6.74"/>
<path fill="#fbfcf7" d="M72.979 98.717a9.143 10.562 0 01-9.13 10.562 9.143 10.562 0 01-9.156-10.53 9.143 10.562 0 019.102-10.594 9.143 10.562 0 019.184 10.499m82.68 32.078a6.21 6.986 0 01-6.2 6.987 6.21 6.986 0 01-6.22-6.966 6.21 6.986 0 016.182-7.007 6.21 6.986 0 016.238 6.945"/>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1024 1024" style="enable-background:new 0 0 1024 1024;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill:url(#XMLID_15_);}
.st2{opacity:0.5;fill:#FFBC00;}
.st3{fill:#FFBC00;}
.st4{fill:url(#XMLID_16_);}
.st5{fill:url(#XMLID_17_);}
.st6{fill:url(#XMLID_18_);}
.st7{fill:url(#XMLID_19_);}
.st8{fill:url(#XMLID_20_);}
.st9{fill:url(#XMLID_21_);}
.st10{opacity:0.46;}
.st11{fill:url(#XMLID_23_);}
.st12{fill:url(#XMLID_24_);}
.st13{fill:url(#XMLID_25_);}
.st14{fill:url(#XMLID_27_);}
.st15{fill:url(#XMLID_28_);}
.st16{fill:url(#XMLID_29_);}
.st17{fill:url(#XMLID_30_);}
.st18{opacity:0.75;}
.st19{opacity:0.44;clip-path:url(#XMLID_31_);}
.st20{fill:url(#XMLID_32_);}
.st21{fill:url(#XMLID_33_);}
.st22{fill:url(#XMLID_34_);}
.st23{fill:url(#XMLID_35_);}
.st24{fill:url(#XMLID_37_);}
.st25{fill:url(#XMLID_38_);}
.st26{opacity:0.43;fill:#FFBC00;}
.st27{opacity:0.58;fill:#FFBC00;}
.st28{fill:#FFBE06;}
.st29{fill:none;}
.st30{fill:url(#XMLID_42_);}
.st31{fill:url(#XMLID_43_);}
.st32{fill:url(#XMLID_44_);}
.st33{fill:url(#XMLID_45_);}
.st34{fill:url(#XMLID_46_);}
.st35{fill:url(#SVGID_4_);}
.st36{fill:url(#SVGID_5_);}
.st37{fill:url(#SVGID_6_);}
.st38{fill:url(#SVGID_7_);}
.st39{fill:url(#SVGID_8_);}
.st40{fill:url(#SVGID_11_);}
.st41{fill:url(#SVGID_12_);}
.st42{fill:url(#SVGID_13_);}
.st43{fill:url(#SVGID_14_);}
.st44{fill:#C68D00;}
.st45{fill:#CE000F;}
</style>
<g>
<radialGradient id="XMLID_15_" cx="358.2692" cy="227.2655" r="830.0055" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#F6971E"/>
<stop offset="0.6338" style="stop-color:#F4592D"/>
<stop offset="0.7025" style="stop-color:#EF4F29"/>
<stop offset="0.8178" style="stop-color:#E1351D"/>
<stop offset="0.9647" style="stop-color:#CA0B0B"/>
<stop offset="1" style="stop-color:#C40006"/>
</radialGradient>
<circle id="XMLID_124_" class="st1" cx="510.3" cy="511.9" r="502.8"/>
<linearGradient id="XMLID_16_" gradientUnits="userSpaceOnUse" x1="505.4734" y1="-52.674" x2="505.4734" y2="155.1105">
<stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.4"/>
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
</linearGradient>
<path id="XMLID_59_" class="st4" d="M861.5,198.8c0,87.2-170.7-23.4-344.5-23.4S149.4,294.2,149.4,207S336.5,9,510.3,9
S861.5,111.7,861.5,198.8z"/>
<linearGradient id="XMLID_17_" gradientUnits="userSpaceOnUse" x1="503.3253" y1="-583.7885" x2="503.3253" y2="-376.4571" gradientTransform="matrix(-1 0 0 -1 1017 456.5313)">
<stop offset="5.263158e-03" style="stop-color:#9E3A1D;stop-opacity:0.4"/>
<stop offset="1" style="stop-color:#9E3A1D;stop-opacity:0"/>
</linearGradient>
<path id="XMLID_61_" class="st5" d="M149.4,825.2c0-87.2,170.7,23.4,344.5,23.4s384.1-118.2,384.1-31S674.4,1015,500.7,1015
S149.4,912.3,149.4,825.2z"/>
<linearGradient id="XMLID_18_" gradientUnits="userSpaceOnUse" x1="506.1886" y1="-38.7551" x2="506.1886" y2="169.0294" gradientTransform="matrix(4.489700e-11 1 -1 4.489700e-11 1026.6101 -2.3899)">
<stop offset="5.263158e-03" style="stop-color:#9E3A1D;stop-opacity:0.4"/>
<stop offset="1" style="stop-color:#9E3A1D;stop-opacity:0"/>
</linearGradient>
<path id="XMLID_63_" class="st6" d="M826.8,859.9c-87.2,0,23.4-170.7,23.4-344.5s-118.8-367.7-31.6-367.7s198,187.1,198,360.9
S914,859.9,826.8,859.9z"/>
<linearGradient id="XMLID_19_" gradientUnits="userSpaceOnUse" x1="502.6101" y1="-660.7308" x2="502.6101" y2="-450.373" gradientTransform="matrix(-4.489700e-11 -1 1 -4.489700e-11 570.0789 1014.6101)">
<stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.4"/>
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
</linearGradient>
<path id="XMLID_62_" class="st7" d="M200.5,147.7c87.2,0-23.4,170.7-23.4,344.5s118.2,384.1,31,384.1S11.3,701,7.4,512
C3.9,338.3,113.3,147.7,200.5,147.7z"/>
<g id="XMLID_39_" class="st10">
<defs>
<ellipse id="XMLID_36_" transform="matrix(0.8019 -0.5974 0.5974 0.8019 -204.724 406.2339)" class="st10" cx="510.3" cy="511.9" rx="502.8" ry="502.8"/>
</defs>
<clipPath id="XMLID_20_">
<use xlink:href="#XMLID_36_" style="overflow:visible;"/>
</clipPath>
</g>
<g id="XMLID_41_">
<linearGradient id="XMLID_21_" gradientUnits="userSpaceOnUse" x1="64.4989" y1="234.8705" x2="401.1502" y2="480.7042">
<stop offset="0" style="stop-color:#F4592D"/>
<stop offset="1" style="stop-color:#FFD900"/>
</linearGradient>
<path id="XMLID_7_" class="st9" d="M20.9,474.4c15.5-101.8,66.6-201.8,147-275.9c45.6-41.1,90.9-61.6,137.3-67.8
c81.7-8,131.9,63.4,129.6,168.8c0.2,3.6,1.9,4.5,3.6,5.4c8.1-23.2,13-44.6,13.2-65.2c-0.1-117-75.3-177.7-169-155.4
c39.7-9.8,77-7.1,110.5,10.7c27.1,17.9,46.5,42,61.9,77.7c10.4,33,14.3,66.1,10.8,108.9c-8.1,92-54.7,187.5-124.6,250
c-28.9,25.9-56.8,42-84.7,58l0.2,3.6c22.7,1.8,44.7-7.1,69.5-21.4c89.7-55.3,157.3-174.1,178.5-286.6
c-9.2,53.6-26.5,106.2-55,159.8c-25.9,44.6-52.4,82.1-90.3,117c-59.4,50.9-120.5,76.8-176.3,64.3C122.4,611,90.4,545.8,92.6,461
c-0.2-3.6-0.5-7.1-3.6-5.4c-1.4,2.7-4.3,8-5.7,10.7C69,517.3,77.1,562.8,91,601.2c25.5,65.2,86.8,87.5,153.7,75
c-39.7,9.8-77,7.1-110.5-10.7c-27.1-17.9-49.6-40.2-63.6-78.6c-14.7-49.1-19.9-100-0.2-165.2c24.9-83,69.1-166.1,136.9-212.5
c20-13.4,38.7-24.1,60.4-36.6c3.1-1.8,2.9-5.4,1.2-6.2C159.4,170,46.5,333.3,20.9,474.4L20.9,474.4z"/>
<linearGradient id="XMLID_22_" gradientUnits="userSpaceOnUse" x1="438.1351" y1="490.0881" x2="196.6566" y2="356.1772">
<stop offset="0" style="stop-color:#F64626"/>
<stop offset="1" style="stop-color:#FFD900"/>
</linearGradient>
<path id="XMLID_40_" style="fill:url(#XMLID_22_);" d="M86.7,460.9C98,387.2,135,314.8,193.2,261.1c33-29.7,65.9-44.6,99.4-49.1
c59.2-5.8,95.5,45.9,93.8,122.2c0.2,2.6,1.4,3.2,2.6,3.9c5.8-16.8,9.4-32.3,9.6-47.2c-0.1-84.7-54.6-128.7-122.4-112.5
c28.7-7.1,55.7-5.2,80,7.8c19.6,12.9,33.7,30.4,44.8,56.3c7.5,23.9,10.3,47.9,7.8,78.9c-5.9,66.6-39.6,135.8-90.2,181.1
c-20.9,18.8-41.1,30.4-61.4,42l0.2,2.6c16.5,1.3,32.4-5.2,50.3-15.5c65-40.1,113.9-126.1,129.3-207.6
c-6.6,38.8-19.2,77-39.9,115.8c-18.8,32.3-37.9,59.5-65.4,84.7c-43,36.9-87.3,55.6-127.7,46.6c-44-11-67.2-58.2-65.6-119.6
c-0.2-2.6-0.4-5.2-2.6-3.9c-1,1.9-3.1,5.8-4.1,7.8c-10.3,36.9-4.5,69.8,5.6,97.7c18.5,47.2,62.9,63.4,111.3,54.3
c-28.7,7.1-55.7,5.2-80-7.8c-19.6-12.9-35.9-29.1-46-56.9c-10.7-35.6-14.4-72.4-0.2-119.6c18-60.1,50.1-120.3,99.1-153.9
c14.5-9.7,28-17.5,43.7-26.5c2.2-1.3,2.1-3.9,0.9-4.5C187.1,240.4,105.3,358.8,86.7,460.9L86.7,460.9z"/>
</g>
<g id="XMLID_11_">
<linearGradient id="XMLID_23_" gradientUnits="userSpaceOnUse" x1="-316.9261" y1="9.5862" x2="-92.0354" y2="173.8087" gradientTransform="matrix(-0.9998 -2.148304e-02 2.148304e-02 -0.9998 625.9278 811.8477)">
<stop offset="0" style="stop-color:#F4592D"/>
<stop offset="1" style="stop-color:#FFD900"/>
</linearGradient>
<path id="XMLID_13_" class="st11" d="M975.6,649.7c-11.8,67.8-47.4,133.8-102.1,182.1c-31,26.8-61.6,39.8-92.7,43.3
c-54.7,4.2-87.2-44.2-84.1-114.6c-0.1-2.4-1.2-3-2.3-3.6c-5.7,15.4-9.3,29.6-9.8,43.3c-1.6,78.1,47.8,119.8,110.6,106.2
c-26.6,6-51.5,3.7-73.6-8.7c-17.8-12.3-30.5-28.7-40.2-52.8c-6.5-22.2-8.6-44.3-5.7-72.9c6.7-61.3,39.2-124.4,86.8-165.2
c19.6-16.9,38.5-27.2,57.4-37.5l-0.1-2.4c-15.1-1.5-29.9,4.1-46.7,13.3c-60.7,35.7-107.6,114-123.3,188.8
c6.9-35.6,19.2-70.6,39-105.9c18-29.4,36.2-54.1,62-76.8c40.4-33.1,81.6-49.5,118.6-40.4c40.4,11,60.8,55,58.1,111.6
c0.1,2.4,0.2,4.8,2.3,3.6c1-1.8,3-5.3,4-7.1c10.2-33.8,5.5-64.3-3.3-90.2c-16.1-43.9-56.7-59.7-101.6-52.3
c26.6-6,51.5-3.7,73.6,8.7c17.8,12.3,32.6,27.5,41.3,53.4c9.1,33,11.8,67.1-2.2,110.3c-17.8,55.1-48.5,109.9-94.5,140
c-13.6,8.7-26.2,15.5-40.9,23.6c-2.1,1.1-2,3.5-0.9,4.2C878.7,851.1,956.4,743.6,975.6,649.7L975.6,649.7z"/>
<linearGradient id="XMLID_24_" gradientUnits="userSpaceOnUse" x1="-52.9013" y1="188.0779" x2="-214.2144" y2="98.6225" gradientTransform="matrix(-0.9998 -2.148304e-02 2.148304e-02 -0.9998 625.9278 811.8477)">
<stop offset="0" style="stop-color:#F42C2D"/>
<stop offset="1" style="stop-color:#FFD900"/>
</linearGradient>
<path id="XMLID_12_" class="st12" d="M931.4,657.8c-8.6,49.1-34.3,96.9-74,131.9c-22.5,19.4-44.6,28.9-67.1,31.4
c-39.6,3-63.2-32-60.9-83c-0.1-1.7-0.9-2.2-1.7-2.6c-4.1,11.1-6.8,21.5-7.1,31.4c-1.2,56.6,34.6,86.7,80.1,76.9
c-19.3,4.3-37.3,2.7-53.3-6.3c-12.9-8.9-22.1-20.8-29.1-38.2c-4.7-16.1-6.2-32.1-4.1-52.8c4.9-44.4,28.4-90.1,62.9-119.6
c14.2-12.2,27.9-19.7,41.6-27.2l-0.1-1.7c-11-1.1-21.7,3-33.8,9.6c-44,25.8-77.9,82.6-89.3,136.8c5-25.8,13.9-51.1,28.3-76.7
c13-21.3,26.2-39.2,44.9-55.6c29.3-24,59.1-35.9,85.9-29.3c29.2,8,44,39.8,42.1,80.8c0.1,1.7,0.2,3.5,1.7,2.6
c0.7-1.3,2.2-3.8,2.9-5.1c7.4-24.5,4-46.6-2.4-65.3c-11.7-31.8-41.1-43.2-73.6-37.9c19.3-4.3,37.3-2.7,53.3,6.3
c12.9,8.9,23.6,20,29.9,38.7c6.6,23.9,8.6,48.6-1.6,79.9c-12.9,39.9-35.2,79.6-68.4,101.4c-9.8,6.3-19,11.3-29.6,17.1
c-1.5,0.8-1.4,2.6-0.6,3C861.2,803.6,917.5,725.7,931.4,657.8L931.4,657.8z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 880 B

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@@ -42,6 +42,7 @@ export default {
try {
const { status, response } = await Tables.deleteTableRows(params);
this.isQuering = false;
if (status === 'success')
this.reloadTable();
@@ -50,9 +51,8 @@ export default {
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
this.isQuering = false;
}
this.isQuering = false;
}
}
};

View File

@@ -30,6 +30,7 @@
"macaddr": $string-color,
"macaddr8": $string-color,
"uuid": $string-color,
"regproc": $string-color,
"int": $number-color,
"tinyint": $number-color,
"smallint": $number-color,
@@ -76,6 +77,7 @@
"tsvector": $array-color,
"tsquery": $array-color,
"pg_node_tree": $array-color,
"aclitem": $array-color,
"unknown": $unknown-color,
)
);

View File

@@ -27,7 +27,6 @@
.td,
.th {
border-bottom: $border-width solid $border-color;
padding: $unit-3 $unit-2;
display: table-cell;
}
@@ -35,37 +34,4 @@
.th {
border-bottom-width: $border-width-lg;
}
&,
&.table-striped {
.tbody {
.tr {
&.selected {
background: #333 !important;
}
&.active {
background: $bg-color-dark;
}
}
}
}
&.table-hover {
.tbody {
.tr {
&:hover {
background: $bg-color-dark;
}
}
}
}
&.table-striped {
.tbody {
.tr:nth-of-type(odd) {
background: $bg-color;
}
}
}
}

View File

@@ -30,6 +30,12 @@
animation: jump-down-in 0.2s reverse;
}
.pulse {
animation-name: pulse;
animation-duration: 2s;
animation-iteration-count: infinite;
}
@keyframes jump-down-in {
0% {
transform: scale(0);
@@ -39,3 +45,17 @@
transform: scale(1);
}
}
@keyframes pulse {
0% {
opacity: 0;
}
50% {
opacity: 1;
}
100% {
opacity: 0;
}
}

View File

@@ -1,9 +1,12 @@
/* Colors */
$body-bg: #1d1d1d;
$body-font-color: #fff;
$bg-color: #1d1d1d;
$bg-color-light: #3f3f3f;
$body-bg: #fdfdfd;
$body-bg-dark: #1d1d1d;
$body-font-color-dark: #fff;
$bg-color-dark: #1d1d1d;
$bg-color-light-dark: #3f3f3f;
$bg-color-gray: #272727;
$bg-color-light-gray: #f1f1f1;
$light-color: #fdfdfd;
$primary-color: #e36929;
$success-color: #32b643;
$error-color: #de3b28;
@@ -14,8 +17,8 @@ $number-color: cornflowerblue;
$date-color: coral;
$bit-color: lightskyblue;
$blob-color: darkorchid;
$array-color: greenyellow;
$enum-color: gold;
$array-color: yellowgreen;
$enum-color: goldenrod;
$unknown-color: gray;
/* Sizes */

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