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

Compare commits

...

64 Commits

Author SHA1 Message Date
420255cdd4 chore(release): 0.3.1 2021-07-27 10:27:02 +02:00
a8a47ed5f7 fix(UI): tabs or explorebar elements selected with mouse wheel or right button 2021-07-23 22:41:53 +02:00
425f0663f9 chore(release): 0.3.0 2021-07-23 17:38:31 +02:00
e106d100b5 refactor: mousedown instead click on tabs and explorebar elements 2021-07-23 15:45:06 +02:00
d7fdf53932 fix: wrong editor height with some conditions 2021-07-23 10:56:41 +02:00
62f7e57d0c fix: manual page input not disabled when only one page is available 2021-07-23 09:01:50 +02:00
14577d14bb fix: sort order of tables is lost switching pages 2021-07-22 18:22:47 +02:00
f5f2a697e8 ci: update snap store config 2021-07-22 17:30:07 +02:00
a0105cf1c3 fix(UI): not disabled buttons during save table setting tabs 2021-07-22 13:13:26 +02:00
77c5d28032 fix: new field default value unknown instead 'noval' 2021-07-22 13:06:54 +02:00
d1d8592f79 build: downgrade to ss2-promise 0.2.0 due build problems 2021-07-22 11:34:21 +02:00
1f828f69a0 Merge pull request #87 from Fabio286/new-tab-system
New tab system
2021-07-22 10:57:43 +02:00
adc5477673 feat: option to restore session on startup 2021-07-22 10:41:06 +02:00
1e543aa6b0 fix: reload twice after element rename 2021-07-21 18:50:22 +02:00
c41e059b0b fix: wrong loaded schema change 2021-07-21 17:56:55 +02:00
e6ef5ffa56 refactor: improved the way how schema is passed to client classes 2021-07-21 14:40:29 +02:00
d67d122270 Merge branch 'master' of https://github.com/Fabio286/antares into new-tab-system 2021-07-21 09:11:06 +02:00
b837e2fc68 Merge pull request #86 from Fabio286/dependabot/npm_and_yarn/ssh2-promise-1.0.0
build(deps): bump ssh2-promise from 0.1.9 to 1.0.0
2021-07-21 09:10:35 +02:00
a73a2f483e feat: option to select schema in query tabs 2021-07-20 19:19:54 +02:00
0a9983d30d feat: new function, procedure and scheduler tabs 2021-07-20 16:59:59 +02:00
58b91ebfe0 feat: new trigger function tabs 2021-07-19 22:38:56 +02:00
e78ca2417e fix(UI): multiple trigger tabs open on single click on explore bar 2021-07-19 11:28:11 +02:00
e1855a262d feat(UI): empty workspace view 2021-07-18 16:10:36 +02:00
6b725b1d40 fix: issues with trigger temp tabs 2021-07-17 16:09:57 +02:00
f6faad98f8 feat: new trigger setting tabs 2021-07-17 13:10:54 +02:00
320aa8ba04 feat: close tabs if element deleted 2021-07-17 10:46:24 +02:00
9f0280b991 Merge branch 'master' of https://github.com/Fabio286/antares into new-tab-system 2021-07-17 10:00:46 +02:00
04fa320820 fix: clear empty indexes and foreign keys on confirm respective modals 2021-07-17 09:59:45 +02:00
f7c3aa883d fix: tab won't open after table or view creation 2021-07-17 09:57:49 +02:00
f7a74df009 feat: new unsaved change reminder 2021-07-16 23:24:55 +02:00
003c02b1fb feat: new view setting tabs 2021-07-16 18:52:18 +02:00
ef21ea7448 feat: rename tabs if element is renamed 2021-07-16 17:27:37 +02:00
525c964c62 fix: enabled copy context on non editable rows 2021-07-16 17:09:02 +02:00
7845e3e501 feat(UI): new table settings tabs 2021-07-15 19:51:18 +02:00
0c29e0d566 perf(UI): improvements in setting bar connections sort 2021-07-15 18:31:44 +02:00
d38097d056 feat(UI): sortable tabs 2021-07-14 20:30:54 +02:00
c87b8dc738 refactor: passing schema from table context options 2021-07-14 18:15:13 +02:00
ed6e7fa72d refactor(UI): improved breadcrumbs and tabs 2021-07-14 16:10:34 +02:00
f0fa7c81b7 fix(UI): table icon in view data tabs 2021-07-14 12:33:26 +02:00
5bb4e496f2 feat(MySQL): improved schema detection for queries 2021-07-14 12:31:37 +02:00
01057332b0 feat(UI): display schema in data tabs 2021-07-14 12:31:12 +02:00
ab382dfbcd feat: new data tabs 2021-07-13 19:23:02 +02:00
88c4cdc8e2 feat(UI): close temp data tabs 2021-07-13 16:53:47 +02:00
15ff211a41 Merge branch 'master' of https://github.com/Fabio286/antares into new-tab-system 2021-07-13 09:19:26 +02:00
5c855a520a fix: solved a vulnerability in table names 2021-07-13 09:17:22 +02:00
7488bc7a17 refactor(core): better way to obtain schema 2021-07-13 09:09:25 +02:00
dependabot[bot]
fd85cf43a2 build(deps): bump ssh2-promise from 0.1.9 to 1.0.0
Bumps [ssh2-promise](https://github.com/sanketbajoria/ssh2-promise) from 0.1.9 to 1.0.0.
- [Release notes](https://github.com/sanketbajoria/ssh2-promise/releases)
- [Commits](https://github.com/sanketbajoria/ssh2-promise/commits/1.0.0)

---
updated-dependencies:
- dependency-name: ssh2-promise
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-12 19:05:27 +00:00
a87079cd17 feat(UI): temporary table data tabs 2021-07-12 19:18:29 +02:00
14d5842056 chore(release): 0.2.1 2021-07-09 16:07:07 +02:00
f19f9e23a2 refactor(UI): changed buttons icon position 2021-07-09 15:51:02 +02:00
0252a064d9 feat(UI): contextual menu shortcuts to create new elements on folders 2021-07-09 15:12:16 +02:00
439356a019 feat: context menu option to duplicate connections 2021-07-09 11:18:40 +02:00
c6897af22d feat(MySQL): possibility to set a default schema in connection parameters 2021-07-09 10:26:16 +02:00
7e40dbfba3 Merge pull request #84 from Fabio286/new-commection-mode
feat: new connection mode
2021-07-08 18:50:23 +02:00
27a153ef43 refactor(UI): improved new connection panels appearence 2021-07-08 18:43:31 +02:00
56fcc2650b fix: clear corrupted configurations to avoid exceptions 2021-07-08 18:42:19 +02:00
9622dbec6b chore: update rules 2021-07-08 18:41:17 +02:00
a0ab63bdb5 fix(UI): connection tab indicator when scrolling 2021-07-08 17:58:43 +02:00
8cd76e711c feat(UI): new connection add panel 2021-07-08 17:43:33 +02:00
9af71a6e34 feat(UI): new connection edit panel 2021-07-08 15:06:20 +02:00
e7b3c28826 chore: update README.md 2021-07-07 12:59:27 +02:00
7570b0add8 fix: avoid to trigger schema loading multiple times 2021-07-06 09:36:35 +02:00
Christian Ratz
1801bef019 feat: SSH Tunnel functionality (#81)
* added ssh-tunnel-functionality for mysql-connections

* remove autoformat-stuff

* added identity for using ssh-key

* added identity to mysqlclient to use sshkey

* removed debug console.log

* added ssh-tunnel-functionality for postgresqlclient

* changed naming to sshKey for sshKey-input

* refactoring code

* fix lint

* set dbConfig.ssl to null initially
2021-07-05 09:30:52 +02:00
0db5ebd7bf chore: update README logo 2021-07-03 13:47:37 +02:00
71 changed files with 3735 additions and 1731 deletions

View File

@@ -9,7 +9,8 @@
],
"rules": {
"at-rule-no-unknown": null,
"no-descending-specificity": null
"no-descending-specificity": null,
"declaration-colon-newline-after": "always-multi-line"
},
"syntax": "scss"
}

View File

@@ -2,6 +2,78 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.3.1](https://github.com/Fabio286/antares/compare/v0.3.0...v0.3.1) (2021-07-27)
### Bug Fixes
* **UI:** tabs or explorebar elements selected with mouse wheel or right button ([a8a47ed](https://github.com/Fabio286/antares/commit/a8a47ed5f7d5d8cbbdd6c33ef8307d9dce5b193b))
## [0.3.0](https://github.com/Fabio286/antares/compare/v0.2.1...v0.3.0) (2021-07-23)
### Features
* close tabs if element deleted ([320aa8b](https://github.com/Fabio286/antares/commit/320aa8ba04d464f628b938b812165cd1ec786492))
* new data tabs ([ab382df](https://github.com/Fabio286/antares/commit/ab382dfbcd89f8ab38a8cbdb4d87e1705618fd0d))
* new function, procedure and scheduler tabs ([0a9983d](https://github.com/Fabio286/antares/commit/0a9983d30d6aeb9aee1c68b31a31f8c605deea45))
* new trigger function tabs ([58b91eb](https://github.com/Fabio286/antares/commit/58b91ebfe0ab200d76d4cfd442b9f2970ae97384))
* new trigger setting tabs ([f6faad9](https://github.com/Fabio286/antares/commit/f6faad98f88222500dfb7265b74c1be914b894cd))
* new unsaved change reminder ([f7a74df](https://github.com/Fabio286/antares/commit/f7a74df0097867a82a2a2d8b3c278a81897d7898))
* new view setting tabs ([003c02b](https://github.com/Fabio286/antares/commit/003c02b1fbe5aba7f53f3faa5610b0c2f7706793))
* option to restore session on startup ([adc5477](https://github.com/Fabio286/antares/commit/adc5477673603cd63fabf77a53db5397e3774e0e))
* option to select schema in query tabs ([a73a2f4](https://github.com/Fabio286/antares/commit/a73a2f483ef6927def4ba635e306277c34ae4b53))
* **UI:** empty workspace view ([e1855a2](https://github.com/Fabio286/antares/commit/e1855a262dc24363fc143a38a70154938308bd71))
* rename tabs if element is renamed ([ef21ea7](https://github.com/Fabio286/antares/commit/ef21ea74481839ba67ca79d40d5f47e1c12aebc0))
* **MySQL:** improved schema detection for queries ([5bb4e49](https://github.com/Fabio286/antares/commit/5bb4e496f289f3ae8a46f24d1a2fbb896d9da86e))
* **UI:** close temp data tabs ([88c4cdc](https://github.com/Fabio286/antares/commit/88c4cdc8e2a60ffbe26b0f831184c3a6dd9a1637))
* **UI:** display schema in data tabs ([0105733](https://github.com/Fabio286/antares/commit/01057332b090997c107bf395bb1fc3b9195e8218))
* **UI:** new table settings tabs ([7845e3e](https://github.com/Fabio286/antares/commit/7845e3e501bb7f890d07b42d4f3eb5f9f4bc8586))
* **UI:** sortable tabs ([d38097d](https://github.com/Fabio286/antares/commit/d38097d0567020b5265b5a0b347f5e1f38e0b1d4))
* **UI:** temporary table data tabs ([a87079c](https://github.com/Fabio286/antares/commit/a87079cd179033cebb6fd228ad7f1b991f3b6c46))
### Bug Fixes
* clear empty indexes and foreign keys on confirm respective modals ([04fa320](https://github.com/Fabio286/antares/commit/04fa320820df1f70a4ef05e4a6e4cbcd4081d047))
* enabled copy context on non editable rows ([525c964](https://github.com/Fabio286/antares/commit/525c964c623e7574f6abe732a2b315b8805d5eee))
* issues with trigger temp tabs ([6b725b1](https://github.com/Fabio286/antares/commit/6b725b1d40753ad89bfad6f1df57c6fb737e5262))
* manual page input not disabled when only one page is available ([62f7e57](https://github.com/Fabio286/antares/commit/62f7e57d0ccf6041b5469bec6a1864aa5b045609))
* new field default value unknown instead 'noval' ([77c5d28](https://github.com/Fabio286/antares/commit/77c5d280325792e20befc845f3a6834837131e39))
* reload twice after element rename ([1e543aa](https://github.com/Fabio286/antares/commit/1e543aa6b0b63e9134bf544682a26bd98573b794))
* solved a vulnerability in table names ([5c855a5](https://github.com/Fabio286/antares/commit/5c855a520a6bc66cc00b0b8afc6d2c03c75c0fab))
* sort order of tables is lost switching pages ([14577d1](https://github.com/Fabio286/antares/commit/14577d14bb337898a1fd0fdee1c3760812b9d21f))
* wrong editor height with some conditions ([d7fdf53](https://github.com/Fabio286/antares/commit/d7fdf53932a4d0609a88641c40d451c71b12c242))
* **UI:** multiple trigger tabs open on single click on explore bar ([e78ca24](https://github.com/Fabio286/antares/commit/e78ca2417e8f996fa0507c5a5586a75393dfe8ee))
* **UI:** not disabled buttons during save table setting tabs ([a0105cf](https://github.com/Fabio286/antares/commit/a0105cf1c37144ea27b212029d39275918f4f95e))
* tab won't open after table or view creation ([f7c3aa8](https://github.com/Fabio286/antares/commit/f7c3aa883dfcad0a28222c53cf22ab49d381559c))
* wrong loaded schema change ([c41e059](https://github.com/Fabio286/antares/commit/c41e059b0ba6d8c6d545c3b86a21a9a2abb6f537))
* **UI:** table icon in view data tabs ([f0fa7c8](https://github.com/Fabio286/antares/commit/f0fa7c81b7aa2a05833ce0b243afed39db98d66b))
### Improvements
* **UI:** improvements in setting bar connections sort ([0c29e0d](https://github.com/Fabio286/antares/commit/0c29e0d566c792ffd1b2b7045124e170b9c51985))
### [0.2.1](https://github.com/Fabio286/antares/compare/v0.2.0...v0.2.1) (2021-07-09)
### Features
* **UI:** contextual menu shortcuts to create new elements on folders ([0252a06](https://github.com/Fabio286/antares/commit/0252a064d99df09b01c020c7d10c76dd43d4ede8))
* context menu option to duplicate connections ([439356a](https://github.com/Fabio286/antares/commit/439356a01993a6a248f5a7d7df305ac5e0e63775))
* **MySQL:** possibility to set a default schema in connection parameters ([c6897af](https://github.com/Fabio286/antares/commit/c6897af22d04ed930289a55124b3e8d080fbae3a))
* **UI:** new connection add panel ([8cd76e7](https://github.com/Fabio286/antares/commit/8cd76e711c9328254d498b4f9afa221311f5a487))
* **UI:** new connection edit panel ([9af71a6](https://github.com/Fabio286/antares/commit/9af71a6e343deda1bf79d8410511cf861af3c304))
* SSH Tunnel functionality ([#81](https://github.com/Fabio286/antares/issues/81)) ([1801bef](https://github.com/Fabio286/antares/commit/1801bef019cee77a99df7e3822145ad952465abb))
### Bug Fixes
* clear corrupted configurations to avoid exceptions ([56fcc26](https://github.com/Fabio286/antares/commit/56fcc2650b93ece398118f39f027dc9520dd8a6a))
* **UI:** connection tab indicator when scrolling ([a0ab63b](https://github.com/Fabio286/antares/commit/a0ab63bdb533aac9b2bdc2ee07a3b1f2b70ea227))
* avoid to trigger schema loading multiple times ([7570b0a](https://github.com/Fabio286/antares/commit/7570b0add8cb9130f15cf8cb807a96dbfd2837d0))
## [0.2.0](https://github.com/Fabio286/antares/compare/v0.1.13...v0.2.0) (2021-07-03)

View File

@@ -9,9 +9,9 @@
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 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.
**At the moment this application is in development state, many features will come in future updates**, and supports only MySQL/MariaDB and PostgreSQL.
At the moment, however, there are all the features necessary to have a pleasant database management experience, so give it a chance and send us your feedback, we would really appreciate it.
We are actively working on it, hoping to provide new cool features, improvements and fixes as soon as possible.
🔗 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.
@@ -19,9 +19,9 @@ I'm actively working on it, hoping to provide cool features and fixes as soon as
## Philosophy
Why am I developing an SQL client when there are a lot of them on the market?
Why are we developing an SQL client when there are a lot of them on the market?
The main goal is to develop a totally free, full featured, cross platform and open source alternative, empowered by JavaScript's ecosystem.
A modern application created with minimalism and semplicity in mind, with features in the right places, not hundreds of tiny buttons, tabs or submenu.
A modern application created with minimalism and semplicity in mind, with features in the right places, not hundreds of tiny buttons, nested tabs or submenu; productivity comes first.
## Download
@@ -43,6 +43,7 @@ A modern application created with minimalism and semplicity in mind, with featur
- Fake table data filler.
- Run queries on multiple tabs.
- Query suggestions and auto complete.
- SSH tunnel support.
- Dark and light theme.
- Scratchpad.
- Multi language.
@@ -54,13 +55,10 @@ This is a roadmap with major features will come in near future.
- Support for other databases.
- Database tools.
- SSH tunnel support.
- Users management (add/edit/delete).
- UI/UX improvements.
- Query history.
- Query history and bookmarks.
- More context menu shortcuts.
- More keyboard shortcuts.
- Query logs console.
- Import/export and migration.
## Currently supported
@@ -68,7 +66,7 @@ This is a roadmap with major features will come in near future.
### Databases
- [x] MySQL/MariaDB
- [x] PostgreSQL (partially, work in progress)
- [x] PostgreSQL
- [ ] SQLite
- [ ] MSSQL
- [ ] OracleDB

BIN
docs/gh-logo-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 304 KiB

View File

@@ -1,7 +1,7 @@
{
"name": "antares",
"productName": "Antares",
"version": "0.2.0",
"version": "0.3.1",
"description": "A cross-platform easy to use SQL client.",
"license": "MIT",
"repository": "https://github.com/Fabio286/antares.git",
@@ -102,6 +102,7 @@
"source-map-support": "^0.5.16",
"spectre.css": "^0.5.9",
"sql-formatter": "^4.0.2",
"ssh2-promise": "^0.2.0",
"v-mask": "^2.2.4",
"vue-i18n": "^8.24.4",
"vuedraggable": "^2.24.3",
@@ -126,7 +127,7 @@
"standard-version": "^9.3.0",
"stylelint": "^13.13.1",
"stylelint-config-standard": "^22.0.0",
"stylelint-scss": "^3.19.0",
"stylelint-scss": "^3.20.1",
"vue": "^2.6.14",
"vue-template-compiler": "^2.6.14",
"webpack": "^4.46.0"

View File

@@ -4,9 +4,7 @@ summary: Open source SQL client made to be simple and complete.
description: |
Antares is an SQL client that aims to become an useful and complete tool, especially for developers.
The target is to support as many databases as possible, and all major operating systems, including the ARM versions.
At the moment this application is an alpha and supports only MySQL and x86 architecture.
Most of its current features might be enough for MySQL management, so give it a chance and send us your feedback, we would really appreciate it.
base: core18
base: core20
grade: stable
confinement: strict
@@ -60,6 +58,7 @@ parts:
- fcitx-frontend-gtk3
- libappindicator3-1
- libasound2
- libcurl4
- libgconf-2-4
- libgtk-3-0
- libnotify4
@@ -71,13 +70,59 @@ parts:
- libsecret-1-0
- libxtst6
- libxkbfile1
- gcc-10-base
- libapparmor1
- libblkid1
- libbsd0
- libcom-err2
- libcrypt1
- libdb5.3
- libdbus-1-3
- libexpat1
- libffi7
- libgcc-s1
- libgcrypt20
- libglib2.0-0
- libgmp10
- libgnutls30
- libgpg-error0
- libgssapi-krb5-2
- libhogweed5
- libidn2-0
- libjson-c4
- libk5crypto3
- libkeyutils1
- libkrb5-3
- libkrb5support0
- liblz4-1
- liblzma5
- libmount1
- libnettle7
- libp11-kit0
- libpcre2-8-0
- libselinux1
- libsqlite3-0
- libssl1.1
- libstdc++6
- libsystemd0
- libtasn1-6
- libudev1
- libunistring2
- libuuid1
- libwrap0
- libzstd1
- zlib1g
- libx11-xcb1
- libdrm2
- libgbm1
- libxcb-dri3-0
cleanup:
after: [antares]
plugin: nil
build-snaps: [gnome-3-28-1804]
build-snaps: [gnome-3-38-2004]
override-prime: |
set -eux
cd /snap/gnome-3-28-1804/current
cd /snap/gnome-3-38-2004/current
find . -type f,l -exec rm -f $SNAPCRAFT_PRIME/{} \;
mdns-lookup:
@@ -91,7 +136,7 @@ parts:
- 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
sed -Ee 's/^\s*hosts:(\s+)files/hosts:\1files mdns4_minimal \[NOTFOUND=return\]/' /snap/core20/current/etc/nsswitch.conf > $SNAPCRAFT_STAGE/etc/nsswitch.conf
snapcraftctl prime
prime:
- lib/$SNAPCRAFT_ARCH_TRIPLET/libnss_mdns4_minimal*
@@ -101,7 +146,7 @@ apps:
antares:
command: opt/Antares/antares --no-sandbox
desktop: usr/share/applications/antares.desktop
extensions: [gnome-3-28]
extensions: [gnome-3-38]
environment:
# Fallback to XWayland if running in a Wayland session.
DISABLE_WAYLAND: 1

View File

@@ -7,6 +7,7 @@ module.exports = {
database: false,
collations: false,
engines: false,
connectionSchema: false,
// Tools
processesList: false,
usersManagement: false,

View File

@@ -7,6 +7,7 @@ module.exports = {
defaultUser: 'root',
defaultDatabase: null,
// Core
connectionSchema: true,
collations: true,
engines: true,
// Tools

View File

@@ -24,13 +24,23 @@ export default connections => {
};
}
const connection = ClientsFactory.getConnection({
client: conn.client,
params
});
if (conn.ssh) {
params.ssh = {
host: conn.sshHost,
username: conn.sshUser,
password: conn.sshPass,
port: conn.sshPort ? conn.sshPort : 22,
identity: conn.sshKey
};
}
try {
const connection = await ClientsFactory.getConnection({
client: conn.client,
params
});
await connection.connect();
await connection.select('1+1').run();
connection.destroy();
@@ -57,6 +67,9 @@ export default connections => {
if (conn.database)
params.database = conn.database;
if (conn.schema)
params.schema = conn.schema;
if (conn.ssl) {
params.ssl = {
key: conn.key ? fs.readFileSync(conn.key) : null,
@@ -66,6 +79,16 @@ export default connections => {
};
}
if (conn.ssh) {
params.ssh = {
host: conn.sshHost,
username: conn.sshUser,
password: conn.sshPass,
port: conn.sshPort ? conn.sshPort : 22,
identity: conn.sshKey
};
}
try {
const connection = ClientsFactory.getConnection({
client: conn.client,

View File

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

View File

@@ -29,7 +29,7 @@ export default (connections) => {
if (sortParams && sortParams.field && sortParams.dir)
query.orderBy({ [sortParams.field]: sortParams.dir.toUpperCase() });
const result = await query.run({ details: true });
const result = await query.run({ details: true, schema });
return { status: 'success', response: result };
}

View File

@@ -12,6 +12,12 @@ export class ClientsFactory {
* @param {String} args.params.host
* @param {Number} args.params.port
* @param {String} args.params.password
* @param {String=} args.params.database
* @param {String=} args.params.schema
* @param {String} args.params.ssh.host
* @param {String} args.params.ssh.username
* @param {String} args.params.ssh.password
* @param {Number} args.params.ssh.port
* @param {Number=} args.poolSize
* @returns Database Connection
* @memberof ClientsFactory

View File

@@ -2,6 +2,7 @@
import mysql from 'mysql2/promise';
import { AntaresCore } from '../AntaresCore';
import dataTypes from 'common/data-types/mysql';
import * as SSH2Promise from 'ssh2-promise';
export class MySQLClient extends AntaresCore {
constructor (args) {
@@ -104,11 +105,33 @@ export class MySQLClient extends AntaresCore {
async connect () {
delete this._params.application_name;
const dbConfig = {
host: this._params.host,
port: this._params.port,
user: this._params.user,
password: this._params.password,
ssl: null
};
if (this._params.schema?.length) dbConfig.database = this._params.schema;
if (this._params.ssl) dbConfig.ssl = { ...this._params.ssl };
if (this._params.ssh) {
this._ssh = new SSH2Promise({ ...this._params.ssh });
this._tunnel = await this._ssh.addTunnel({
remoteAddr: this._params.host,
remotePort: this._params.port
});
dbConfig.port = this._tunnel.localPort;
}
if (!this._poolSize)
this._connection = await mysql.createConnection(this._params);
this._connection = await mysql.createConnection(dbConfig);
else {
this._connection = mysql.createPool({
...this._params,
...dbConfig,
connectionLimit: this._poolSize,
typeCast: (field, next) => {
if (field.type === 'DATETIME')
@@ -125,6 +148,7 @@ export class MySQLClient extends AntaresCore {
*/
destroy () {
this._connection.end();
if (this._ssh) this._ssh.close();
}
/**
@@ -145,6 +169,12 @@ export class MySQLClient extends AntaresCore {
*/
async getStructure (schemas) {
const { rows: databases } = await this.raw('SHOW DATABASES');
let filteredDatabases = databases;
if (this._params.schema)
filteredDatabases = filteredDatabases.filter(db => db.Database === this._params.schema);
const { rows: functions } = await this.raw('SHOW FUNCTION STATUS');
const { rows: procedures } = await this.raw('SHOW PROCEDURE STATUS');
const { rows: schedulers } = await this.raw('SELECT *, EVENT_SCHEMA AS `Db`, EVENT_NAME AS `Name` FROM information_schema.`EVENTS`');
@@ -152,7 +182,7 @@ export class MySQLClient extends AntaresCore {
const tablesArr = [];
const triggersArr = [];
for (const db of databases) {
for (const db of filteredDatabases) {
if (!schemas.has(db.Database)) continue;
let { rows: tables } = await this.raw(`SHOW TABLE STATUS FROM \`${db.Database}\``);
@@ -174,7 +204,7 @@ export class MySQLClient extends AntaresCore {
}
}
return databases.map(db => {
return filteredDatabases.map(db => {
if (schemas.has(db.Database)) {
// TABLES
const remappedTables = tablesArr.filter(table => table.Db === db.Database).map(table => {
@@ -305,11 +335,11 @@ export class MySQLClient extends AntaresCore {
.select('*')
.schema('information_schema')
.from('COLUMNS')
.where({ TABLE_SCHEMA: `= '${this._schema || schema}'`, TABLE_NAME: `= '${table}'` })
.where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'` })
.orderBy({ ORDINAL_POSITION: 'ASC' })
.run();
const { rows: fields } = await this.raw(`SHOW CREATE TABLE \`${this._schema || schema}\`.\`${table}\``);
const { rows: fields } = await this.raw(`SHOW CREATE TABLE \`${schema}\`.\`${table}\``);
const remappedFields = fields.map(row => {
if (!row['Create Table']) return false;
@@ -333,15 +363,14 @@ export class MySQLClient extends AntaresCore {
const details = fieldArr.slice(2).join(' ');
let defaultValue = null;
if (details.includes('DEFAULT')) {
if (details.includes('DEFAULT'))
defaultValue = details.match(/(?<=DEFAULT ).*?$/gs)[0].split(' COMMENT')[0];
const defaultValueArr = defaultValue.split('');
if (defaultValueArr[0] === '\'') {
defaultValueArr.shift();
defaultValueArr.pop();
defaultValue = defaultValueArr.join('');
}
}
// const defaultValueArr = defaultValue.split('');
// if (defaultValueArr[0] === '\'') {
// defaultValueArr.shift();
// defaultValueArr.pop();
// defaultValue = defaultValueArr.join('');
// }
const typeAndLength = nameAndType[1].replace(')', '').split('(');
@@ -544,7 +573,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
async dropView (params) {
const sql = `DROP VIEW \`${this._schema}\`.\`${params.view}\``;
const sql = `DROP VIEW \`${params.schema}\`.\`${params.view}\``;
return await this.raw(sql);
}
@@ -556,10 +585,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 \`${this._schema}\`.\`${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 \`${view.schema}\`.\`${view.oldName}\` AS ${view.sql} ${view.updateOption ? `WITH ${view.updateOption} CHECK OPTION` : ''}`;
if (view.name !== view.oldName)
sql += `; RENAME TABLE \`${this._schema}\`.\`${view.oldName}\` TO \`${this._schema}\`.\`${view.name}\``;
sql += `; RENAME TABLE \`${view.schema}\`.\`${view.oldName}\` TO \`${view.schema}\`.\`${view.name}\``;
return await this.raw(sql);
}
@@ -570,8 +599,8 @@ export class MySQLClient extends AntaresCore {
* @returns {Array.<Object>} parameters
* @memberof MySQLClient
*/
async createView (view) {
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` : ''}`;
async createView (params) {
const sql = `CREATE ALGORITHM = ${params.algorithm} ${params.definer ? `DEFINER=${params.definer} ` : ''}SQL SECURITY ${params.security} VIEW \`${params.schema}\`.\`${params.name}\` AS ${params.sql} ${params.updateOption ? `WITH ${params.updateOption} CHECK OPTION` : ''}`;
return await this.raw(sql);
}
@@ -604,7 +633,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
async dropTrigger (params) {
const sql = `DROP TRIGGER \`${this._schema}\`.\`${params.trigger}\``;
const sql = `DROP TRIGGER \`${params.schema}\`.\`${params.trigger}\``;
return await this.raw(sql);
}
@@ -621,8 +650,8 @@ export class MySQLClient extends AntaresCore {
try {
await this.createTrigger(tempTrigger);
await this.dropTrigger({ trigger: tempTrigger.name });
await this.dropTrigger({ trigger: trigger.oldName });
await this.dropTrigger({ schema: trigger.schema, trigger: tempTrigger.name });
await this.dropTrigger({ schema: trigger.schema, trigger: trigger.oldName });
await this.createTrigger(trigger);
}
catch (err) {
@@ -636,8 +665,8 @@ export class MySQLClient extends AntaresCore {
* @returns {Array.<Object>} parameters
* @memberof MySQLClient
*/
async createTrigger (trigger) {
const sql = `CREATE ${trigger.definer ? `DEFINER=${trigger.definer} ` : ''}TRIGGER \`${this._schema}\`.\`${trigger.name}\` ${trigger.activation} ${trigger.event} ON \`${trigger.table}\` FOR EACH ROW ${trigger.sql}`;
async createTrigger (params) {
const sql = `CREATE ${params.definer ? `DEFINER=${params.definer} ` : ''}TRIGGER \`${params.schema}\`.\`${params.name}\` ${params.activation} ${params.event} ON \`${params.table}\` FOR EACH ROW ${params.sql}`;
return await this.raw(sql, { split: false });
}
@@ -711,7 +740,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
async dropRoutine (params) {
const sql = `DROP PROCEDURE \`${this._schema}\`.\`${params.routine}\``;
const sql = `DROP PROCEDURE \`${params.schema}\`.\`${params.routine}\``;
return await this.raw(sql);
}
@@ -728,8 +757,8 @@ export class MySQLClient extends AntaresCore {
try {
await this.createRoutine(tempProcedure);
await this.dropRoutine({ routine: tempProcedure.name });
await this.dropRoutine({ routine: routine.oldName });
await this.dropRoutine({ schema: routine.schema, routine: tempProcedure.name });
await this.dropRoutine({ schema: routine.schema, routine: routine.oldName });
await this.createRoutine(routine);
}
catch (err) {
@@ -743,21 +772,21 @@ export class MySQLClient extends AntaresCore {
* @returns {Array.<Object>} parameters
* @memberof MySQLClient
*/
async createRoutine (routine) {
const parameters = 'parameters' in routine
? routine.parameters.reduce((acc, curr) => {
async createRoutine (params) {
const parameters = 'parameters' in params
? params.parameters.reduce((acc, curr) => {
acc.push(`${curr.context} \`${curr.name}\` ${curr.type}${curr.length ? `(${curr.length})` : ''}`);
return acc;
}, []).join(',')
: '';
const sql = `CREATE ${routine.definer ? `DEFINER=${routine.definer} ` : ''}PROCEDURE \`${this._schema}\`.\`${routine.name}\`(${parameters})
const sql = `CREATE ${params.definer ? `DEFINER=${params.definer} ` : ''}PROCEDURE \`${params.schema}\`.\`${params.name}\`(${parameters})
LANGUAGE SQL
${routine.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'}
${routine.dataAccess}
SQL SECURITY ${routine.security}
COMMENT '${routine.comment}'
${routine.sql}`;
${params.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'}
${params.dataAccess}
SQL SECURITY ${params.security}
COMMENT '${params.comment}'
${params.sql}`;
return await this.raw(sql, { split: false });
}
@@ -838,7 +867,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
async dropFunction (params) {
const sql = `DROP FUNCTION \`${this._schema}\`.\`${params.func}\``;
const sql = `DROP FUNCTION \`${params.schema}\`.\`${params.func}\``;
return await this.raw(sql);
}
@@ -855,8 +884,8 @@ export class MySQLClient extends AntaresCore {
try {
await this.createFunction(tempProcedure);
await this.dropFunction({ func: tempProcedure.name });
await this.dropFunction({ func: func.oldName });
await this.dropFunction({ schema: func.schema, func: tempProcedure.name });
await this.dropFunction({ schema: func.schema, func: func.oldName });
await this.createFunction(func);
}
catch (err) {
@@ -870,20 +899,20 @@ export class MySQLClient extends AntaresCore {
* @returns {Array.<Object>} parameters
* @memberof MySQLClient
*/
async createFunction (func) {
const parameters = func.parameters.reduce((acc, curr) => {
async createFunction (params) {
const parameters = params.parameters.reduce((acc, curr) => {
acc.push(`\`${curr.name}\` ${curr.type}${curr.length ? `(${curr.length})` : ''}`);
return acc;
}, []).join(',');
const body = func.returns ? func.sql : 'BEGIN\n RETURN 0;\nEND';
const body = params.returns ? params.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})` : ''}
const sql = `CREATE ${params.definer ? `DEFINER=${params.definer} ` : ''}FUNCTION \`${params.schema}\`.\`${params.name}\`(${parameters}) RETURNS ${params.returns || 'SMALLINT'}${params.returnsLength ? `(${params.returnsLength})` : ''}
LANGUAGE SQL
${func.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'}
${func.dataAccess}
SQL SECURITY ${func.security}
COMMENT '${func.comment}'
${params.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'}
${params.dataAccess}
SQL SECURITY ${params.security}
COMMENT '${params.comment}'
${body}`;
return await this.raw(sql, { split: false });
@@ -930,7 +959,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
async dropEvent (params) {
const sql = `DROP EVENT \`${this._schema}\`.\`${params.scheduler}\``;
const sql = `DROP EVENT \`${params.schema}\`.\`${params.scheduler}\``;
return await this.raw(sql);
}
@@ -946,13 +975,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 \`${this._schema}\`.\`${scheduler.oldName}\`
const sql = `ALTER ${scheduler.definer ? ` DEFINER=${scheduler.definer}` : ''} EVENT \`${scheduler.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 \`${this._schema}\`.\`${scheduler.name}\`` : ''}
${scheduler.name !== scheduler.oldName ? `RENAME TO \`${scheduler.schema}\`.\`${scheduler.name}\`` : ''}
${scheduler.state}
COMMENT '${scheduler.comment}'
DO ${scheduler.sql}`;
@@ -966,16 +995,16 @@ export class MySQLClient extends AntaresCore {
* @returns {Array.<Object>} parameters
* @memberof MySQLClient
*/
async createEvent (scheduler) {
const sql = `CREATE ${scheduler.definer ? ` DEFINER=${scheduler.definer}` : ''} EVENT \`${this._schema}\`.\`${scheduler.name}\`
async createEvent (params) {
const sql = `CREATE ${params.definer ? ` DEFINER=${params.definer}` : ''} EVENT \`${params.schema}\`.\`${params.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}`;
${params.execution === 'EVERY'
? `EVERY ${params.every.join(' ')}${params.starts ? ` STARTS '${params.starts}'` : ''}${params.ends ? ` ENDS '${params.ends}'` : ''}`
: `AT '${params.at}'`}
ON COMPLETION${!params.preserve ? ' NOT' : ''} PRESERVE
${params.state}
COMMENT '${params.comment}'
DO ${params.sql}`;
return await this.raw(sql, { split: false });
}
@@ -1097,14 +1126,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
async createTable (params) {
const {
name,
collation,
comment,
engine
} = params;
const sql = `CREATE TABLE \`${this._schema}\`.\`${name}\` (\`${name}_ID\` INT NULL) COMMENT='${comment}', COLLATE='${collation}', ENGINE=${engine}`;
const sql = `CREATE TABLE \`${params.schema}\`.\`${params.name}\` (\`${params.name}_ID\` INT NULL) COMMENT='${params.comment}', COLLATE='${params.collation}', ENGINE=${params.engine}`;
return await this.raw(sql);
}
@@ -1118,6 +1140,7 @@ export class MySQLClient extends AntaresCore {
async alterTable (params) {
const {
table,
schema,
additions,
deletions,
changes,
@@ -1126,7 +1149,7 @@ export class MySQLClient extends AntaresCore {
options
} = params;
let sql = `ALTER TABLE \`${this._schema || params.options.schema}\`.\`${table}\` `;
let sql = `ALTER TABLE \`${schema}\`.\`${table}\` `;
const alterColumns = [];
// OPTIONS
@@ -1238,7 +1261,7 @@ export class MySQLClient extends AntaresCore {
sql += alterColumns.join(', ');
// RENAME
if (options.name) sql += `; RENAME TABLE \`${this._schema}\`.\`${table}\` TO \`${this._schema}\`.\`${options.name}\``;
if (options.name) sql += `; RENAME TABLE \`${schema}\`.\`${table}\` TO \`${schema}\`.\`${options.name}\``;
return await this.raw(sql);
}
@@ -1250,7 +1273,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
async duplicateTable (params) {
const sql = `CREATE TABLE \`${this._schema}\`.\`${params.table}_copy\` LIKE \`${this._schema}\`.\`${params.table}\``;
const sql = `CREATE TABLE \`${params.schema}\`.\`${params.table}_copy\` LIKE \`${params.schema}\`.\`${params.table}\``;
return await this.raw(sql);
}
@@ -1261,7 +1284,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
async truncateTable (params) {
const sql = `TRUNCATE TABLE \`${this._schema}\`.\`${params.table}\``;
const sql = `TRUNCATE TABLE \`${params.schema}\`.\`${params.table}\``;
return await this.raw(sql);
}
@@ -1272,7 +1295,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
async dropTable (params) {
const sql = `DROP TABLE \`${this._schema}\`.\`${params.table}\``;
const sql = `DROP TABLE \`${params.schema}\`.\`${params.table}\``;
return await this.raw(sql);
}
@@ -1343,16 +1366,19 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
async raw (sql, args) {
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');
if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder
args = {
nest: false,
details: false,
split: true,
comments: true,
...args
};
if (!args.comments)
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments
const nestTables = args.nest ? '.' : false;
const resultsArr = [];
let paramsArr = [];
@@ -1389,7 +1415,7 @@ export class MySQLClient extends AntaresCore {
name: field.orgName,
alias: field.name,
orgName: field.orgName,
schema: field.schema,
schema: args.schema || field.schema,
table: field.table,
tableAlias: field.table,
orgTable: field.orgTable,

View File

@@ -3,6 +3,7 @@ import { Pool, Client, types } from 'pg';
import { parse } from 'pgsql-ast-parser';
import { AntaresCore } from '../AntaresCore';
import dataTypes from 'common/data-types/postgresql';
import * as SSH2Promise from 'ssh2-promise';
function pgToString (value) {
return value.toString();
@@ -51,13 +52,35 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async connect () {
const dbConfig = {
host: this._params.host,
port: this._params.port,
user: this._params.user,
password: this._params.password,
ssl: null
};
if (this._params.database?.length) dbConfig.database = this._params.database;
if (this._params.ssl) dbConfig.ssl = { ...this._params.ssl };
if (this._params.ssh) {
this._ssh = new SSH2Promise({ ...this._params.ssh });
this._tunnel = await this._ssh.addTunnel({
remoteAddr: this._params.host,
remotePort: this._params.port
});
dbConfig.port = this._tunnel.localPort;
}
if (!this._poolSize) {
const client = new Client(this._params);
const client = new Client(dbConfig);
await client.connect();
this._connection = client;
}
else {
const pool = new Pool({ ...this._params, max: this._poolSize });
const pool = new Pool({ ...dbConfig, max: this._poolSize });
this._connection = pool;
}
}
@@ -67,6 +90,7 @@ export class PostgreSQLClient extends AntaresCore {
*/
destroy () {
this._connection.end();
if (this._ssh) this._ssh.close();
}
/**
@@ -459,7 +483,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async dropView (params) {
const sql = `DROP VIEW ${this._schema}.${params.view}`;
const sql = `DROP VIEW ${params.schema}.${params.view}`;
return await this.raw(sql);
}
@@ -471,10 +495,10 @@ export class PostgreSQLClient extends AntaresCore {
*/
async alterView (params) {
const { view } = params;
let sql = `CREATE OR REPLACE VIEW ${this._schema}.${view.oldName} AS ${view.sql}`;
let sql = `CREATE OR REPLACE VIEW ${view.schema}.${view.oldName} AS ${view.sql}`;
if (view.name !== view.oldName)
sql += `; ALTER VIEW ${this._schema}.${view.oldName} RENAME TO ${view.name}`;
sql += `; ALTER VIEW ${view.schema}.${view.oldName} RENAME TO ${view.name}`;
return await this.raw(sql);
}
@@ -485,8 +509,8 @@ export class PostgreSQLClient extends AntaresCore {
* @returns {Array.<Object>} parameters
* @memberof PostgreSQLClient
*/
async createView (view) {
const sql = `CREATE VIEW ${this._schema}.${view.name} AS ${view.sql}`;
async createView (params) {
const sql = `CREATE VIEW ${params.schema}.${params.name} AS ${params.sql}`;
return await this.raw(sql);
}
@@ -536,7 +560,7 @@ export class PostgreSQLClient extends AntaresCore {
*/
async dropTrigger (params) {
const triggerParts = params.trigger.split('.');
const sql = `DROP TRIGGER "${triggerParts[1]}" ON "${triggerParts[0]}"`;
const sql = `DROP TRIGGER "${triggerParts[1]}" ON "${params.schema}"."${triggerParts[0]}"`;
return await this.raw(sql);
}
@@ -553,8 +577,8 @@ export class PostgreSQLClient extends AntaresCore {
try {
await this.createTrigger(tempTrigger);
await this.dropTrigger({ trigger: `${tempTrigger.table}.${tempTrigger.name}` });
await this.dropTrigger({ trigger: `${trigger.table}.${trigger.oldName}` });
await this.dropTrigger({ schema: trigger.schema, trigger: `${tempTrigger.table}.${tempTrigger.name}` });
await this.dropTrigger({ schema: trigger.schema, trigger: `${trigger.table}.${trigger.oldName}` });
await this.createTrigger(trigger);
}
catch (err) {
@@ -568,9 +592,9 @@ export class PostgreSQLClient extends AntaresCore {
* @returns {Array.<Object>} parameters
* @memberof PostgreSQLClient
*/
async createTrigger (trigger) {
const eventsString = Array.isArray(trigger.event) ? trigger.event.join(' OR ') : trigger.event;
const sql = `CREATE TRIGGER "${trigger.name}" ${trigger.activation} ${eventsString} ON "${trigger.table}" FOR EACH ROW ${trigger.sql}`;
async createTrigger (params) {
const eventsString = Array.isArray(params.event) ? params.event.join(' OR ') : params.event;
const sql = `CREATE TRIGGER "${params.name}" ${params.activation} ${eventsString} ON "${params.schema}"."${params.table}" FOR EACH ROW ${params.sql}`;
return await this.raw(sql, { split: false });
}
@@ -613,6 +637,7 @@ export class PostgreSQLClient extends AntaresCore {
AND proc.routine_type = 'PROCEDURE'
AND proc.routine_name = '${routine}'
AND proc.specific_schema = '${schema}'
AND args.data_type != NULL
ORDER BY procedure_schema,
specific_name,
procedure_name,
@@ -651,7 +676,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async dropRoutine (params) {
const sql = `DROP PROCEDURE ${this._schema}.${params.routine}`;
const sql = `DROP PROCEDURE "${params.schema}"."${params.routine}"`;
return await this.raw(sql);
}
@@ -668,8 +693,8 @@ export class PostgreSQLClient extends AntaresCore {
try {
await this.createRoutine(tempProcedure);
await this.dropRoutine({ routine: tempProcedure.name });
await this.dropRoutine({ routine: routine.oldName });
await this.dropRoutine({ schema: routine.schema, routine: tempProcedure.name });
await this.dropRoutine({ schema: routine.schema, routine: routine.oldName });
await this.createRoutine(routine);
}
catch (err) {
@@ -691,10 +716,10 @@ export class PostgreSQLClient extends AntaresCore {
}, []).join(',')
: '';
if (this._schema !== 'public')
await this.use(this._schema);
if (routine.schema !== 'public')
await this.use(routine.schema);
const sql = `CREATE PROCEDURE ${this._schema}.${routine.name}(${parameters})
const sql = `CREATE PROCEDURE "${routine.schema}"."${routine.name}"(${parameters})
LANGUAGE ${routine.language}
SECURITY ${routine.security}
AS ${routine.sql}`;
@@ -780,7 +805,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async dropFunction (params) {
const sql = `DROP FUNCTION ${this._schema}.${params.func}`;
const sql = `DROP FUNCTION "${params.schema}"."${params.func}"`;
return await this.raw(sql);
}
@@ -797,8 +822,8 @@ export class PostgreSQLClient extends AntaresCore {
try {
await this.createFunction(tempProcedure);
await this.dropFunction({ func: tempProcedure.name });
await this.dropFunction({ func: func.oldName });
await this.dropFunction({ schema: func.schema, func: tempProcedure.name });
await this.dropFunction({ schema: func.schema, func: func.oldName });
await this.createFunction(func);
}
catch (err) {
@@ -815,17 +840,17 @@ export class PostgreSQLClient extends AntaresCore {
async createFunction (func) {
const parameters = 'parameters' in func
? func.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(',')
: '';
if (this._schema !== 'public')
await this.use(this._schema);
if (func.schema !== 'public')
await this.use(func.schema);
const body = func.returns ? func.sql : '$function$\n$function$';
const sql = `CREATE FUNCTION ${this._schema}.${func.name}(${parameters})
const sql = `CREATE FUNCTION "${func.schema}"."${func.name}" (${parameters})
RETURNS ${func.returns || 'void'}
LANGUAGE ${func.language}
SECURITY ${func.security}
@@ -843,12 +868,12 @@ export class PostgreSQLClient extends AntaresCore {
async alterTriggerFunction (params) {
const { func } = params;
if (this._schema !== 'public')
await this.use(this._schema);
if (func.schema !== 'public')
await this.use(func.schema);
const body = func.returns ? func.sql : '$function$\n$function$';
const sql = `CREATE OR REPLACE FUNCTION ${this._schema}.${func.name}()
const sql = `CREATE OR REPLACE FUNCTION "${func.schema}"."${func.name}" ()
RETURNS TRIGGER
LANGUAGE ${func.language}
AS ${body}`;
@@ -863,12 +888,12 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async createTriggerFunction (func) {
if (this._schema !== 'public')
await this.use(this._schema);
if (func.schema !== 'public')
await this.use(func.schema);
const body = func.returns ? func.sql : '$function$\r\nBEGIN\r\n\r\nEND\r\n$function$';
const sql = `CREATE FUNCTION ${this._schema}.${func.name}()
const sql = `CREATE FUNCTION "${func.schema}"."${func.name}" ()
RETURNS TRIGGER
LANGUAGE ${func.language}
AS ${body}`;
@@ -964,11 +989,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async createTable (params) {
const {
name
} = params;
const sql = `CREATE TABLE ${this._schema}.${name} (${name}_id INTEGER NULL); ALTER TABLE ${this._schema}.${name} DROP COLUMN ${name}_id`;
const sql = `CREATE TABLE "${params.schema}"."${params.name}" (${params.name}_id INTEGER NULL); ALTER TABLE "${params.schema}"."${params.name}" DROP COLUMN ${params.name}_id`;
return await this.raw(sql);
}
@@ -982,6 +1003,7 @@ export class PostgreSQLClient extends AntaresCore {
async alterTable (params) {
const {
table,
schema,
additions,
deletions,
changes,
@@ -990,8 +1012,8 @@ export class PostgreSQLClient extends AntaresCore {
options
} = params;
if (this._schema !== 'public')
await this.use(this._schema);
if (schema !== 'public')
await this.use(schema);
let sql = '';
const alterColumns = [];
@@ -1032,7 +1054,7 @@ export class PostgreSQLClient extends AntaresCore {
else if (type === 'UNIQUE')
alterColumns.push(`ADD CONSTRAINT ${addition.name} UNIQUE (${fields})`);
else
manageIndexes.push(`CREATE INDEX ${addition.name} ON ${this._schema}.${table}(${fields})`);
manageIndexes.push(`CREATE INDEX ${addition.name} ON "${schema}"."${table}" (${fields})`);
});
// ADD FOREIGN KEYS
@@ -1070,7 +1092,7 @@ export class PostgreSQLClient extends AntaresCore {
}
if (change.orgName !== change.name)
renameColumns.push(`ALTER TABLE "${this._schema}"."${table}" RENAME COLUMN "${change.orgName}" TO "${change.name}"`);
renameColumns.push(`ALTER TABLE "${schema}"."${table}" RENAME COLUMN "${change.orgName}" TO "${change.name}"`);
});
// CHANGE INDEX
@@ -1088,7 +1110,7 @@ export class PostgreSQLClient extends AntaresCore {
else if (type === 'UNIQUE')
alterColumns.push(`ADD CONSTRAINT ${change.name} UNIQUE (${fields})`);
else
manageIndexes.push(`CREATE INDEX ${change.name} ON ${this._schema}.${table}(${fields})`);
manageIndexes.push(`CREATE INDEX ${change.name} ON "${schema}"."${table}" (${fields})`);
});
// CHANGE FOREIGN KEYS
@@ -1115,10 +1137,10 @@ export class PostgreSQLClient extends AntaresCore {
alterColumns.push(`DROP CONSTRAINT ${deletion.constraintName}`);
});
if (alterColumns.length) sql += `ALTER TABLE "${this._schema}"."${table}" ${alterColumns.join(', ')}; `;
if (alterColumns.length) sql += `ALTER TABLE "${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}"; `;
if (options.name) sql += `ALTER TABLE "${schema}"."${table}" RENAME TO "${options.name}"; `;
// RENAME
if (renameColumns.length) sql = `${renameColumns.join(';')}; ${sql}`;
@@ -1133,7 +1155,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async duplicateTable (params) {
const sql = `CREATE TABLE ${this._schema}.${params.table}_copy (LIKE ${this._schema}.${params.table} INCLUDING ALL)`;
const sql = `CREATE TABLE ${params.schema}.${params.table}_copy (LIKE ${params.schema}.${params.table} INCLUDING ALL)`;
return await this.raw(sql);
}
@@ -1144,7 +1166,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async truncateTable (params) {
const sql = `TRUNCATE TABLE ${this._schema}.${params.table}`;
const sql = `TRUNCATE TABLE ${params.schema}.${params.table}`;
return await this.raw(sql);
}
@@ -1155,7 +1177,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async dropTable (params) {
const sql = `DROP TABLE ${this._schema}.${params.table}`;
const sql = `DROP TABLE ${params.schema}.${params.table}`;
return await this.raw(sql);
}
@@ -1226,18 +1248,20 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async raw (sql, args) {
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');
args = {
nest: false,
details: false,
split: true,
comments: true,
...args
};
if (args.schema && args.schema !== 'public')
await this.use(args.schema);
if (!args.comments)
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments
const resultsArr = [];
let paramsArr = [];
const queries = args.split

View File

@@ -4,22 +4,20 @@
<div id="window-content">
<TheSettingBar />
<div id="main-content" class="container">
<TheAppWelcome v-if="!connections.length" @new-conn="showNewConnModal" />
<div v-else class="columns col-gapless">
<div class="columns col-gapless">
<Workspace
v-for="connection in connections"
:key="connection.uid"
:connection="connection"
/>
<WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" />
</div>
<TheFooter />
<TheNotificationsBoard />
<TheScratchpad v-if="isScratchpad" />
<ModalSettings v-if="isSettingModal" />
<BaseTextEditor class="d-none" value="" />
</div>
<TheFooter />
<TheNotificationsBoard />
<ModalNewConnection v-if="isNewConnModal" />
<TheScratchpad v-if="isScratchpad" />
<ModalSettings v-if="isSettingModal" />
<ModalDiscardChanges v-if="isUnsavedDiscardModal" />
<BaseTextEditor class="d-none" value="" />
</div>
</div>
</template>
@@ -36,12 +34,10 @@ export default {
TheSettingBar: () => import(/* webpackChunkName: "TheSettingBar" */'@/components/TheSettingBar'),
TheFooter: () => import(/* webpackChunkName: "TheFooter" */'@/components/TheFooter'),
TheNotificationsBoard: () => import(/* webpackChunkName: "TheNotificationsBoard" */'@/components/TheNotificationsBoard'),
TheAppWelcome: () => import(/* webpackChunkName: "TheAppWelcome" */'@/components/TheAppWelcome'),
Workspace: () => import(/* webpackChunkName: "Workspace" */'@/components/Workspace'),
ModalNewConnection: () => import(/* webpackChunkName: "ModalNewConnection" */'@/components/ModalNewConnection'),
WorkspaceAddConnectionPanel: () => import(/* webpackChunkName: "WorkspaceAddConnectionPanel" */'@/components/WorkspaceAddConnectionPanel'),
ModalSettings: () => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings'),
TheScratchpad: () => import(/* webpackChunkName: "TheScratchpad" */'@/components/TheScratchpad'),
ModalDiscardChanges: () => import(/* webpackChunkName: "ModalDiscardChanges" */'@/components/ModalDiscardChanges'),
BaseTextEditor: () => import(/* webpackChunkName: "BaseTextEditor" */'@/components/BaseTextEditor')
},
data () {
@@ -49,9 +45,8 @@ export default {
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
isLoading: 'application/isLoading',
isNewConnModal: 'application/isNewModal',
isEditModal: 'application/isEditModal',
isSettingModal: 'application/isSettingModal',
isScratchpad: 'application/isScratchpad',
connections: 'connections/getConnections',

View File

@@ -41,7 +41,7 @@
</form>
</div>
</div>
<div class="modal-footer text-light">
<div class="modal-footer">
<button class="btn btn-primary mr-2" @click.stop="sendCredentials">
{{ $t('word.send') }}
</button>

View File

@@ -2,8 +2,8 @@
<ConfirmModal
:confirm-text="$t('word.discard')"
:cancel-text="$t('word.stay')"
@confirm="discardUnsavedChanges"
@hide="closeUnsavedChangesModal"
@confirm="$emit('confirm')"
@hide="$emit('close')"
>
<template slot="header">
<div class="d-flex">
@@ -19,7 +19,6 @@
</template>
<script>
import { mapActions } from 'vuex';
import ConfirmModal from '@/components/BaseConfirmModal';
export default {
@@ -34,13 +33,6 @@ export default {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
discardUnsavedChanges: 'workspaces/discardUnsavedChanges',
closeUnsavedChangesModal: 'workspaces/closeUnsavedChangesModal'
}),
closeModal () {
this.$emit('close');
},
onKey (e) {
e.stopPropagation();
if (e.key === 'Escape')

View File

@@ -1,391 +0,0 @@
<template>
<div class="modal active">
<a class="modal-overlay c-hand" @click="closeModal" />
<div class="modal-container">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-server mr-1" /> {{ $t('message.editConnection') }}
</div>
</div>
<a class="btn btn-clear c-hand" @click="closeModal" />
</div>
<div class="modal-body p-0">
<div class="panel">
<div class="panel-nav">
<ul class="tab tab-block">
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'general'}"
@click="selectTab('general')"
>
<a class="tab-link">{{ $t('word.general') }}</a>
</li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'ssl'}"
@click="selectTab('ssl')"
>
<a class="tab-link">{{ $t('word.ssl') }}</a>
</li>
</ul>
</div>
<div v-if="selectedTab === 'general'" class="panel-body py-0">
<div class="container">
<form class="form-horizontal">
<fieldset class="m-0" :disabled="isTesting">
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.connectionName') }}</label>
</div>
<div class="col-8 col-sm-12">
<input
ref="firstInput"
v-model="localConnection.name"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.client') }}</label>
</div>
<div class="col-8 col-sm-12">
<select v-model="localConnection.client" class="form-select">
<option value="mysql">
MySQL
</option>
<option value="maria">
MariaDB
</option>
<option value="pg">
PostgreSQL
</option>
<!-- <option value="mssql">
Microsoft SQL
</option>
<option value="oracledb">
Oracle DB
</option> -->
</select>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.hostName') }}/IP</label>
</div>
<div class="col-8 col-sm-12">
<input
v-model="localConnection.host"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.port') }}</label>
</div>
<div class="col-8 col-sm-12">
<input
v-model="localConnection.port"
class="form-input"
type="number"
min="1"
max="65535"
>
</div>
</div>
<div v-if="customizations.database" class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.database') }}</label>
</div>
<div class="col-8 col-sm-12">
<input
v-model="localConnection.database"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.user') }}</label>
</div>
<div class="col-8 col-sm-12">
<input
v-model="localConnection.user"
class="form-input"
type="text"
:disabled="localConnection.ask"
>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.password') }}</label>
</div>
<div class="col-8 col-sm-12">
<input
v-model="localConnection.password"
class="form-input"
type="password"
:disabled="localConnection.ask"
>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12" />
<div class="col-8 col-sm-12">
<label class="form-checkbox form-inline">
<input v-model="localConnection.ask" type="checkbox"><i class="form-icon" /> {{ $t('message.askCredentials') }}
</label>
</div>
</div>
</fieldset>
</form>
</div>
<BaseToast
class="mb-2"
:message="toast.message"
:status="toast.status"
/>
</div>
<div v-if="selectedTab === 'ssl'" class="panel-body py-0">
<div class="container">
<form class="form-horizontal">
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">
{{ $t('message.enableSsl') }}
</label>
</div>
<div class="col-8 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleSsl">
<input type="checkbox" :checked="localConnection.ssl">
<i class="form-icon" />
</label>
</div>
</div>
<fieldset class="m-0" :disabled="isTesting || !localConnection.ssl">
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.privateKey') }}</label>
</div>
<div class="col-8 col-sm-12">
<BaseUploadInput
:value="localConnection.key"
:message="$t('word.browse')"
@clear="pathClear('key')"
@change="pathSelection($event, 'key')"
/>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.certificate') }}</label>
</div>
<div class="col-8 col-sm-12">
<BaseUploadInput
:value="localConnection.cert"
:message="$t('word.browse')"
@clear="pathClear('cert')"
@change="pathSelection($event, 'cert')"
/>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.caCertificate') }}</label>
</div>
<div class="col-8 col-sm-12">
<BaseUploadInput
:value="localConnection.ca"
:message="$t('word.browse')"
@clear="pathClear('ca')"
@change="pathSelection($event, 'ca')"
/>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.ciphers') }}</label>
</div>
<div class="col-8 col-sm-12">
<input
ref="firstInput"
v-model="localConnection.ciphers"
class="form-input"
type="text"
>
</div>
</div>
</fieldset>
</form>
</div>
<BaseToast
class="mb-2"
:message="toast.message"
:status="toast.status"
/>
</div>
<div class="modal-footer text-light">
<button
class="btn btn-gray mr-2"
:class="{'loading': isTesting}"
@click="startTest"
>
{{ $t('message.testConnection') }}
</button>
<button class="btn btn-primary mr-2" @click="saveEditConnection">
{{ $t('word.save') }}
</button>
<button class="btn btn-link" @click="closeModal">
{{ $t('word.close') }}
</button>
</div>
</div>
<ModalAskCredentials
v-if="isAsking"
@close-asking="closeAsking"
@credentials="continueTest"
/>
</div>
</div>
</div>
</template>
<script>
import { mapActions } from 'vuex';
import customizations from 'common/customizations';
import Connection from '@/ipc-api/Connection';
import ModalAskCredentials from '@/components/ModalAskCredentials';
import BaseToast from '@/components/BaseToast';
import BaseUploadInput from '@/components/BaseUploadInput';
export default {
name: 'ModalEditConnection',
components: {
ModalAskCredentials,
BaseToast,
BaseUploadInput
},
props: {
connection: Object
},
data () {
return {
toast: {
status: '',
message: ''
},
isTesting: false,
isAsking: false,
localConnection: null,
selectedTab: 'general'
};
},
computed: {
customizations () {
return customizations[this.connection.client];
}
},
created () {
this.localConnection = Object.assign({}, this.connection);
window.addEventListener('keydown', this.onKey);
setTimeout(() => {
this.$refs.firstInput.focus();
}, 20);
},
beforeDestroy () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
editConnection: 'connections/editConnection'
}),
async startTest () {
this.isTesting = true;
this.toast = {
status: '',
message: ''
};
if (this.localConnection.ask)
this.isAsking = true;
else {
try {
const res = await Connection.makeTest(this.localConnection);
if (res.status === 'error')
this.toast = { status: 'error', message: res.response.message };
else
this.toast = { status: 'success', message: this.$t('message.connectionSuccessfullyMade') };
}
catch (err) {
this.toast = { status: 'error', message: err.stack };
}
this.isTesting = false;
}
},
async continueTest (credentials) { // if "Ask for credentials" is true
this.isAsking = false;
const params = Object.assign({}, this.localConnection, credentials);
try {
const res = await Connection.makeTest(params);
if (res.status === 'error')
this.toast = { status: 'error', message: res.response.message };
else
this.toast = { status: 'success', message: this.$t('message.connectionSuccessfullyMade') };
}
catch (err) {
this.toast = { status: 'error', message: err.stack };
}
this.isTesting = false;
},
saveEditConnection () {
this.editConnection(this.localConnection);
this.closeModal();
},
closeAsking () {
this.isTesting = false;
this.isAsking = false;
},
closeModal () {
this.$emit('close');
},
onKey (e) {
e.stopPropagation();
if (e.key === 'Escape')
this.closeModal();
},
selectTab (tab) {
this.selectedTab = tab;
},
toggleSsl () {
this.localConnection.ssl = !this.localConnection.ssl;
},
pathSelection (event, name) {
const { files } = event.target;
if (!files.length) return;
this.localConnection[name] = files[0].path;
},
pathClear (name) {
this.localConnection[name] = '';
}
}
};
</script>
<style scoped>
.modal-container {
position: absolute;
max-width: 450px;
top: 17.5vh;
}
</style>

View File

@@ -53,7 +53,7 @@
</form>
</div>
</div>
<div class="modal-footer text-light">
<div class="modal-footer">
<button class="btn btn-primary mr-2" @click.stop="updateSchema">
{{ $t('word.update') }}
</button>
@@ -72,7 +72,7 @@ import Schema from '@/ipc-api/Schema';
export default {
name: 'ModalEditSchema',
props: {
selectedDatabase: String
selectedSchema: String
},
data () {
return {
@@ -99,7 +99,7 @@ export default {
async created () {
let actualCollation;
try {
const { status, response } = await Schema.getDatabaseCollation({ uid: this.selectedWorkspace, database: this.selectedDatabase });
const { status, response } = await Schema.getDatabaseCollation({ uid: this.selectedWorkspace, database: this.selectedSchema });
if (status === 'success')
actualCollation = response;
@@ -112,8 +112,8 @@ export default {
}
this.database = {
name: this.selectedDatabase,
prevName: this.selectedDatabase,
name: this.selectedSchema,
prevName: this.selectedSchema,
collation: actualCollation || this.defaultCollation,
prevCollation: actualCollation || this.defaultCollation
};

View File

@@ -52,7 +52,7 @@
</form>
</div>
</div>
<div class="modal-footer text-light columns">
<div class="modal-footer columns">
<div class="column d-flex" :class="hasFakes ? 'col-4' : 'col-2'">
<div class="input-group tooltip tooltip-right" :data-tooltip="$t('message.numberOfInserts')">
<input

View File

@@ -1,415 +0,0 @@
<template>
<div class="modal active">
<a class="modal-overlay c-hand" @click="closeModal" />
<div class="modal-container">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-server-plus mr-1" />
<span class="cut-text">{{ $t('message.createNewConnection') }}</span>
</div>
</div>
<a class="btn btn-clear c-hand" @click="closeModal" />
</div>
<div class="modal-body p-0">
<div class="panel">
<div class="panel-nav">
<ul class="tab tab-block">
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'general'}"
@click="selectTab('general')"
>
<a class="tab-link">{{ $t('word.general') }}</a>
</li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'ssl'}"
@click="selectTab('ssl')"
>
<a class="tab-link">{{ $t('word.ssl') }}</a>
</li>
</ul>
</div>
<div v-if="selectedTab === 'general'" class="panel-body py-0">
<div class="container">
<form class="form-horizontal">
<fieldset class="m-0" :disabled="isTesting">
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.connectionName') }}</label>
</div>
<div class="col-8 col-sm-12">
<input
ref="firstInput"
v-model="connection.name"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.client') }}</label>
</div>
<div class="col-8 col-sm-12">
<select
v-model="connection.client"
class="form-select"
@change="setDefaults"
>
<option value="mysql">
MySQL
</option>
<option value="maria">
MariaDB
</option>
<option value="pg">
PostgreSQL
</option>
<!-- <option value="mssql">
Microsoft SQL
</option>
<option value="oracledb">
Oracle DB
</option> -->
</select>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.hostName') }}/IP</label>
</div>
<div class="col-8 col-sm-12">
<input
v-model="connection.host"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.port') }}</label>
</div>
<div class="col-8 col-sm-12">
<input
v-model="connection.port"
class="form-input"
type="number"
min="1"
max="65535"
>
</div>
</div>
<div v-if="customizations.database" class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.database') }}</label>
</div>
<div class="col-8 col-sm-12">
<input
v-model="connection.database"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.user') }}</label>
</div>
<div class="col-8 col-sm-12">
<input
v-model="connection.user"
class="form-input"
type="text"
:disabled="connection.ask"
>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.password') }}</label>
</div>
<div class="col-8 col-sm-12">
<input
v-model="connection.password"
class="form-input"
type="password"
:disabled="connection.ask"
>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12" />
<div class="col-8 col-sm-12">
<label class="form-checkbox form-inline">
<input v-model="connection.ask" type="checkbox"><i class="form-icon" /> {{ $t('message.askCredentials') }}
</label>
</div>
</div>
</fieldset>
</form>
</div>
<BaseToast
class="mb-2"
:message="toast.message"
:status="toast.status"
/>
</div>
<div v-if="selectedTab === 'ssl'" class="panel-body py-0">
<div class="container">
<form class="form-horizontal">
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">
{{ $t('message.enableSsl') }}
</label>
</div>
<div class="col-8 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleSsl">
<input type="checkbox" :checked="connection.ssl">
<i class="form-icon" />
</label>
</div>
</div>
<fieldset class="m-0" :disabled="isTesting || !connection.ssl">
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.privateKey') }}</label>
</div>
<div class="col-8 col-sm-12">
<BaseUploadInput
:value="connection.key"
:message="$t('word.browse')"
@clear="pathClear('key')"
@change="pathSelection($event, 'key')"
/>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.certificate') }}</label>
</div>
<div class="col-8 col-sm-12">
<BaseUploadInput
:value="connection.cert"
:message="$t('word.browse')"
@clear="pathClear('cert')"
@change="pathSelection($event, 'cert')"
/>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.caCertificate') }}</label>
</div>
<div class="col-8 col-sm-12">
<BaseUploadInput
:value="connection.ca"
:message="$t('word.browse')"
@clear="pathClear('ca')"
@change="pathSelection($event, 'ca')"
/>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.ciphers') }}</label>
</div>
<div class="col-8 col-sm-12">
<input
ref="firstInput"
v-model="connection.ciphers"
class="form-input"
type="text"
>
</div>
</div>
</fieldset>
</form>
</div>
<BaseToast
class="mb-2"
:message="toast.message"
:status="toast.status"
/>
</div>
</div>
</div>
<div class="modal-footer text-light">
<button
class="btn btn-gray mr-2"
:class="{'loading': isTesting}"
@click="startTest"
>
{{ $t('message.testConnection') }}
</button>
<button class="btn btn-primary mr-2" @click="saveNewConnection">
{{ $t('word.save') }}
</button>
<button class="btn btn-link" @click="closeModal">
{{ $t('word.close') }}
</button>
</div>
</div>
<ModalAskCredentials
v-if="isAsking"
@close-asking="closeAsking"
@credentials="continueTest"
/>
</div>
</template>
<script>
import { mapActions } from 'vuex';
import customizations from 'common/customizations';
import Connection from '@/ipc-api/Connection';
import { uidGen } from 'common/libs/uidGen';
import ModalAskCredentials from '@/components/ModalAskCredentials';
import BaseToast from '@/components/BaseToast';
import BaseUploadInput from '@/components/BaseUploadInput';
export default {
name: 'ModalNewConnection',
components: {
ModalAskCredentials,
BaseToast,
BaseUploadInput
},
data () {
return {
connection: {
name: '',
client: 'mysql',
host: '127.0.0.1',
database: null,
port: null,
user: null,
password: '',
ask: false,
uid: uidGen('C'),
ssl: false,
cert: '',
key: '',
ca: '',
ciphers: ''
},
toast: {
status: '',
message: ''
},
isTesting: false,
isAsking: false,
selectedTab: 'general'
};
},
computed: {
customizations () {
return customizations[this.connection.client];
}
},
created () {
this.setDefaults();
window.addEventListener('keydown', this.onKey);
setTimeout(() => {
this.$refs.firstInput.focus();
}, 20);
},
beforeDestroy () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
closeModal: 'application/hideNewConnModal',
addConnection: 'connections/addConnection'
}),
setDefaults () {
this.connection.user = this.customizations.defaultUser;
this.connection.port = this.customizations.defaultPort;
this.connection.database = this.customizations.defaultDatabase;
},
async startTest () {
this.isTesting = true;
this.toast = {
status: '',
message: ''
};
if (this.connection.ask)
this.isAsking = true;
else {
try {
const res = await Connection.makeTest(this.connection);
console.log(res.response);
if (res.status === 'error')
this.toast = { status: 'error', message: res.response.message };
else
this.toast = { status: 'success', message: this.$t('message.connectionSuccessfullyMade') };
}
catch (err) {
this.toast = { status: 'error', message: err.stack };
}
this.isTesting = false;
}
},
async continueTest (credentials) { // if "Ask for credentials" is true
this.isAsking = false;
const params = Object.assign({}, this.connection, credentials);
try {
const res = await Connection.makeTest(params);
if (res.status === 'error')
this.toast = { status: 'error', message: res.response.message };
else
this.toast = { status: 'success', message: this.$t('message.connectionSuccessfullyMade') };
}
catch (err) {
this.toast = { status: 'error', message: err.stack };
}
this.isTesting = false;
},
saveNewConnection () {
this.addConnection(this.connection);
this.closeModal();
},
closeAsking () {
this.isAsking = false;
this.isTesting = false;
},
onKey (e) {
e.stopPropagation();
if (e.key === 'Escape')
this.closeModal();
},
selectTab (tab) {
this.selectedTab = tab;
},
toggleSsl () {
this.connection.ssl = !this.connection.ssl;
},
pathSelection (event, name) {
const { files } = event.target;
if (!files.length) return;
this.connection[name] = files[0].path;
},
pathClear (name) {
this.connection[name] = '';
}
}
};
</script>
<style scoped>
.modal-container {
position: absolute;
max-width: 450px;
top: 17.5vh;
}
</style>

View File

@@ -49,7 +49,7 @@
</form>
</div>
</div>
<div class="modal-footer text-light">
<div class="modal-footer">
<button
class="btn btn-primary mr-2"
:class="{'loading': isLoading}"

View File

@@ -86,7 +86,7 @@
</form>
</div>
</div>
<div class="modal-footer text-light">
<div class="modal-footer">
<div class="input-group col-3 tooltip tooltip-right" :data-tooltip="$t('message.numberOfInserts')">
<input
v-model="nInserts"

View File

@@ -60,7 +60,7 @@
{{ $t('word.application') }}
</div>
<div class="column col-8 col-sm-12 mb-2">
<div class="form-group mb-4">
<div class="form-group">
<div class="col-6 col-sm-12">
<label class="form-label">
<i class="mdi mdi-18px mdi-translate mr-1" />
@@ -104,6 +104,19 @@
</select>
</div>
</div>
<div class="form-group">
<div class="col-6 col-sm-12">
<label class="form-label">
{{ $t('message.restorePreviourSession') }}
</label>
</div>
<div class="col-6 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleRestoreSession">
<input type="checkbox" :checked="restoreTabs">
<i class="form-icon" />
</label>
</div>
</div>
<div class="form-group">
<div class="col-6 col-sm-12">
<label class="form-label">
@@ -369,6 +382,7 @@ export default {
selectedAutoComplete: 'settings/getAutoComplete',
selectedLineWrap: 'settings/getLineWrap',
notificationsTimeout: 'settings/getNotificationsTimeout',
restoreTabs: 'settings/getRestoreTabs',
applicationTheme: 'settings/getApplicationTheme',
editorTheme: 'settings/getEditorTheme',
editorFontSize: 'settings/getEditorFontSize',
@@ -423,6 +437,7 @@ ORDER BY
closeModal: 'application/hideSettingModal',
changeLocale: 'settings/changeLocale',
changePageSize: 'settings/changePageSize',
changeRestoreTabs: 'settings/changeRestoreTabs',
changeAutoComplete: 'settings/changeAutoComplete',
changeLineWrap: 'settings/changeLineWrap',
changeApplicationTheme: 'settings/changeApplicationTheme',
@@ -447,6 +462,9 @@ ORDER BY
if (e.key === 'Escape')
this.closeModal();
},
toggleRestoreSession () {
this.changeRestoreTabs(!this.restoreTabs);
},
toggleAutoComplete () {
this.changeAutoComplete(!this.selectedAutoComplete);
},

View File

@@ -3,18 +3,13 @@
:context-event="contextEvent"
@close-context="$emit('close-context')"
>
<div class="context-element" @click="showEditModal(contextConnection)">
<span class="d-flex"><i class="mdi mdi-18px mdi-pencil text-light pr-1" /> {{ $t('word.edit') }}</span>
<div class="context-element" @click="duplicateConnection">
<span class="d-flex"><i class="mdi mdi-18px mdi-content-duplicate text-light pr-1" /> {{ $t('word.duplicate') }}</span>
</div>
<div class="context-element" @click="showConfirmModal">
<span class="d-flex"><i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ $t('word.delete') }}</span>
</div>
<ModalEditConnection
v-if="isEditModal"
:connection="contextConnection"
@close="hideEditModal"
/>
<ConfirmModal
v-if="isConfirmModal"
@confirm="confirmDeleteConnection"
@@ -36,15 +31,14 @@
<script>
import { mapActions, mapGetters } from 'vuex';
import { uidGen } from 'common/libs/uidGen';
import BaseContextMenu from '@/components/BaseContextMenu';
import ConfirmModal from '@/components/BaseConfirmModal';
import ModalEditConnection from '@/components/ModalEditConnection';
export default {
name: 'SettingBarContext',
components: {
BaseContextMenu,
ModalEditConnection,
ConfirmModal
},
props: {
@@ -59,7 +53,8 @@ export default {
},
computed: {
...mapGetters({
getConnectionName: 'connections/getConnectionName'
getConnectionName: 'connections/getConnectionName',
selectedWorkspace: 'workspaces/getSelected'
}),
connectionName () {
return this.getConnectionName(this.contextConnection.uid);
@@ -67,17 +62,25 @@ export default {
},
methods: {
...mapActions({
deleteConnection: 'connections/deleteConnection'
addConnection: 'connections/addConnection',
deleteConnection: 'connections/deleteConnection',
selectWorkspace: 'workspaces/selectWorkspace'
}),
confirmDeleteConnection () {
if (this.selectedWorkspace === this.contextConnection.uid)
this.selectWorkspace();
this.deleteConnection(this.contextConnection);
this.closeContext();
},
showEditModal () {
this.isEditModal = true;
},
hideEditModal () {
this.isEditModal = false;
duplicateConnection () {
let connectionCopy = Object.assign({}, this.contextConnection);
connectionCopy = {
...connectionCopy,
uid: uidGen('C'),
name: connectionCopy.name ? `${connectionCopy.name}_copy` : ''
};
this.addConnection(connectionCopy);
this.closeContext();
},
showConfirmModal () {

View File

@@ -1,37 +0,0 @@
<template>
<div class="columns">
<div class="column col-12 empty text-light">
<div class="empty-icon">
<i class="mdi mdi-48px mdi-emoticon" />
</div>
<p class="empty-title h5">
{{ $t('message.appWelcome') }}
</p>
<p class="empty-subtitle">
{{ $t('message.appFirstStep') }}
</p>
<div class="empty-action">
<button class="btn btn-primary" @click="$emit('new-conn')">
{{ $t('message.createConnection') }}
</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'TheAppWelcome'
};
</script>
<style scoped>
.empty {
height: 100%;
border-radius: 0;
background: transparent;
display: flex;
flex-direction: column;
justify-content: center;
}
</style>

View File

@@ -8,24 +8,29 @@
@close-context="isContext = false"
/>
<ul class="settingbar-elements">
<draggable v-model="connections">
<Draggable
v-model="connections"
@start="isDragging = true"
@end="dragStop"
>
<li
v-for="connection in connections"
:key="connection.uid"
draggable="true"
class="settingbar-element btn btn-link ex-tooltip"
:class="{'selected': connection.uid === selectedWorkspace}"
@click="selectWorkspace(connection.uid)"
@click.stop="selectWorkspace(connection.uid)"
@contextmenu.prevent="contextMenu($event, connection)"
@mouseover.self="tooltipPosition"
>
<i class="settingbar-element-icon dbi" :class="`dbi-${connection.client} ${getStatusBadge(connection.uid)}`" />
<span class="ex-tooltip-content">{{ getConnectionName(connection.uid) }}</span>
<span v-if="!isDragging" class="ex-tooltip-content">{{ getConnectionName(connection.uid) }}</span>
</li>
</draggable>
</Draggable>
<li
class="settingbar-element btn btn-link ex-tooltip"
@click="showNewConnModal"
:class="{'selected': 'NEW' === selectedWorkspace}"
@click="selectWorkspace('NEW')"
@mouseover.self="tooltipPosition"
>
<i class="settingbar-element-icon mdi mdi-24px mdi-plus text-light" />
@@ -51,19 +56,20 @@
<script>
import { mapActions, mapGetters } from 'vuex';
import draggable from 'vuedraggable';
import Draggable from 'vuedraggable';
import SettingBarContext from '@/components/SettingBarContext';
export default {
name: 'TheSettingBar',
components: {
draggable,
Draggable,
SettingBarContext
},
data () {
return {
dragElement: null,
isContext: false,
isDragging: false,
contextEvent: null,
contextConnection: {},
scale: 0
@@ -92,7 +98,6 @@ export default {
methods: {
...mapActions({
updateConnections: 'connections/updateConnections',
showNewConnModal: 'application/showNewConnModal',
showSettingModal: 'application/showSettingModal',
showScratchpad: 'application/showScratchpad',
selectWorkspace: 'workspaces/selectWorkspace'
@@ -106,7 +111,7 @@ export default {
return connection.ask ? '' : `${connection.user + '@'}${connection.host}:${connection.port}`;
},
tooltipPosition (e) {
const el = e.target;
const el = e.target ? e.target : e;
const fromTop = window.pageYOffset + el.getBoundingClientRect().top - (el.offsetHeight / 4);
el.querySelector('.ex-tooltip-content').style.top = `${fromTop}px`;
},
@@ -125,6 +130,13 @@ export default {
return '';
}
}
},
dragStop (e) {
this.isDragging = false;
setTimeout(() => {
this.tooltipPosition(e.originalEvent.target.parentNode);
}, 200);
}
}
};
@@ -167,14 +179,13 @@ export default {
height: $settingbar-width;
width: 100%;
margin: 0;
border-left: 3px solid transparent;
opacity: 0.5;
transition: opacity 0.2s;
display: flex;
align-content: center;
justify-content: center;
flex-direction: column;
align-items: center;
justify-content: flex-start;
border-radius: 0;
padding: 0;
&:hover {
opacity: 1;
@@ -194,12 +205,12 @@ export default {
width: 3px;
transition: height 0.2s;
background-color: $primary-color;
position: absolute;
left: 0;
border-radius: $border-radius;
}
.settingbar-element-icon {
margin: 0 auto;
&.badge::after {
bottom: -10px;
right: 0;
@@ -236,7 +247,13 @@ export default {
transition: opacity 0.2s;
}
&:hover .ex-tooltip-content {
&.sortable-chosen {
.ex-tooltip-content {
opacity: 0 !important;
}
}
&:hover:not(.selected) .ex-tooltip-content {
visibility: visible;
opacity: 1;
}

View File

@@ -58,6 +58,7 @@ export default {
}),
windowTitle () {
if (!this.selectedWorkspace) return '';
if (this.selectedWorkspace === 'NEW') return this.$t('message.createNewConnection');
const connectionName = this.getConnectionName(this.selectedWorkspace);
const workspace = this.getWorkspace(this.selectedWorkspace);

View File

@@ -1,13 +1,168 @@
<template>
<div v-show="isSelected" class="workspace column columns col-gapless">
<WorkspaceExploreBar :connection="connection" :is-selected="isSelected" />
<WorkspaceExploreBar
v-if="workspace.connection_status === 'connected'"
:connection="connection"
:is-selected="isSelected"
/>
<div v-if="workspace.connection_status === 'connected'" class="workspace-tabs column columns col-gapless">
<ul
id="tabWrap"
<Draggable
ref="tabWrap"
v-model="draggableTabs"
tag="ul"
group="tabs"
class="tab tab-block column col-12"
draggable=".tab-draggable"
@mouseover.native="addWheelEvent"
>
<li class="tab-item dropdown tools-dropdown">
<li
v-for="(tab, i) of draggableTabs"
:key="i"
class="tab-item tab-draggable"
draggable="true"
:class="{'active': selectedTab === tab.uid}"
@mousedown.left="selectTab({uid: workspace.uid, tab: tab.uid})"
@mouseup.middle="closeTab(tab)"
>
<a v-if="tab.type === 'query'" class="tab-link">
<i class="mdi mdi-18px mdi-code-tags mr-1" />
<span>
Query #{{ tab.index }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@click.stop="closeTab(tab)"
/>
</span>
</a>
<a
v-else-if="tab.type === 'temp-data'"
class="tab-link"
@dblclick="openAsPermanentTab(tab)"
>
<i class="mdi mdi-18px mr-1" :class="tab.elementType === 'view' ? 'mdi-table-eye' : 'mdi-table'" />
<span :title="`${$t('word.data').toUpperCase()}: ${tab.elementType}`">
<span class=" text-italic">{{ tab.elementName }}</span>
<span
class="btn btn-clear"
:title="$t('word.close')"
@click.stop="closeTab(tab)"
/>
</span>
</a>
<a v-else-if="tab.type === 'data'" class="tab-link">
<i class="mdi mdi-18px mr-1" :class="tab.elementType === 'view' ? 'mdi-table-eye' : 'mdi-table'" />
<span :title="`${$t('word.data').toUpperCase()}: ${tab.elementType}`">
{{ tab.elementName }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@click.stop="closeTab(tab)"
/>
</span>
</a>
<a
v-else-if="tab.type === 'table-props'"
class="tab-link"
:class="{'badge': tab.isChanged}"
>
<i class="mdi mdi-tune-vertical-variant mdi-18px mr-1" />
<span :title="`${$t('word.settings').toUpperCase()}: ${tab.elementType}`">
{{ tab.elementName }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@click.stop="closeTab(tab)"
/>
</span>
</a>
<a
v-else-if="tab.type === 'view-props'"
class="tab-link"
:class="{'badge': tab.isChanged}"
>
<i class="mdi mdi-tune-vertical-variant mdi-18px mr-1" />
<span :title="`${$t('word.settings').toUpperCase()}: ${tab.elementType}`">
{{ tab.elementName }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@click.stop="closeTab(tab)"
/>
</span>
</a>
<a
v-else-if="tab.type.includes('temp-')"
class="tab-link"
:class="{'badge': tab.isChanged}"
@dblclick="openAsPermanentTab(tab)"
>
<i class="mdi mdi-18px mdi-tune-vertical-variant mr-1" />
<span :title="`${$t('word.settings').toUpperCase()}: ${tab.elementType}`">
<span class=" text-italic">{{ tab.elementName }}</span>
<span
class="btn btn-clear"
:title="$t('word.close')"
@click.stop="closeTab(tab)"
/>
</span>
</a>
<a
v-else
class="tab-link"
:class="{'badge': tab.isChanged}"
>
<i class="mdi mdi-18px mdi-tune-vertical-variant mr-1" />
<span :title="`${$t('word.settings').toUpperCase()}: ${tab.elementType}`">
{{ tab.elementName }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@click.stop="closeTab(tab)"
/>
</span>
</a>
<!-- <a
v-else-if="tab.type === 'temp-trigger-function-props'"
class="tab-link"
:class="{'badge': tab.isChanged}"
@dblclick="openAsPermanentTab(tab)"
>
<i class="mdi mdi-18px mdi-tune-vertical-variant mr-1" />
<span :title="`${$t('word.settings').toUpperCase()}: ${tab.elementType}`">
<span class=" text-italic">{{ tab.elementName }}</span>
<span
class="btn btn-clear"
:title="$t('word.close')"
@click.stop="closeTab(tab)"
/>
</span>
</a>
<a
v-else-if="tab.type === 'trigger-function-props'"
class="tab-link"
:class="{'badge': tab.isChanged}"
>
<i class="mdi mdi-18px mdi-tune-vertical-variant mr-1" />
<span :title="`${$t('word.settings').toUpperCase()}: ${tab.elementType}`">
{{ tab.elementName }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@click.stop="closeTab(tab)"
/>
</span>
</a> -->
</li>
<li slot="header" class="tab-item dropdown tools-dropdown">
<a
class="tab-link workspace-tools-link dropdown-toggle"
tabindex="0"
@@ -44,151 +199,150 @@
</li>
</ul>
</li>
<li
v-if="schemaChild && isSettingSupported"
class="tab-item"
:class="{'active': selectedTab === 'prop'}"
@click="selectTab({uid: workspace.uid, tab: 'prop'})"
>
<a class="tab-link">
<i class="mdi mdi-18px mdi-tune-vertical-variant mr-1" />
<span :title="schemaChild">{{ $t('word.settings').toUpperCase() }}: {{ schemaChild }}</span>
</a>
</li>
<li
v-if="workspace.breadcrumbs.table || workspace.breadcrumbs.view"
class="tab-item"
:class="{'active': selectedTab === 'data'}"
@click="selectTab({uid: workspace.uid, tab: 'data'})"
>
<a class="tab-link">
<i class="mdi mdi-18px mr-1" :class="workspace.breadcrumbs.table ? 'mdi-table' : 'mdi-table-eye'" />
<span :title="schemaChild">{{ $t('word.data').toUpperCase() }}: {{ schemaChild }}</span>
</a>
</li>
<li
v-for="tab of queryTabs"
:key="tab.uid"
class="tab-item"
:class="{'active': selectedTab === tab.uid}"
@click="selectTab({uid: workspace.uid, tab: tab.uid})"
@mouseup.middle="closeTab(tab.uid)"
>
<a class="tab-link">
<i class="mdi mdi-18px mdi-code-tags mr-1" />
<span>
Query #{{ tab.index }}
<span
v-if="queryTabs.length > 1"
class="btn btn-clear"
:title="$t('word.close')"
@click.stop="closeTab(tab.uid)"
/>
</span>
</a>
</li>
<li class="tab-item">
<li slot="footer" class="tab-item">
<a
class="tab-add"
:title="$t('message.openNewTab')"
@click="addTab"
@click="addQueryTab"
>
<i class="mdi mdi-24px mdi-plus" />
</a>
</li>
</ul>
<WorkspacePropsTab
v-show="selectedTab === 'prop' && workspace.breadcrumbs.table"
:is-selected="selectedTab === 'prop'"
:connection="connection"
:table="workspace.breadcrumbs.table"
/>
<WorkspacePropsTabView
v-show="selectedTab === 'prop' && workspace.breadcrumbs.view"
:is-selected="selectedTab === 'prop'"
:connection="connection"
:view="workspace.breadcrumbs.view"
/>
<WorkspacePropsTabTrigger
v-show="selectedTab === 'prop' && workspace.breadcrumbs.trigger"
:is-selected="selectedTab === 'prop'"
:connection="connection"
:trigger="workspace.breadcrumbs.trigger"
/>
<WorkspacePropsTabRoutine
v-show="selectedTab === 'prop' && workspace.breadcrumbs.procedure"
:is-selected="selectedTab === 'prop'"
:connection="connection"
:routine="workspace.breadcrumbs.procedure"
/>
<WorkspacePropsTabFunction
v-show="selectedTab === 'prop' && workspace.breadcrumbs.function"
:is-selected="selectedTab === 'prop'"
:connection="connection"
:function="workspace.breadcrumbs.function"
/>
<WorkspacePropsTabTriggerFunction
v-show="selectedTab === 'prop' && workspace.breadcrumbs.triggerFunction"
:is-selected="selectedTab === 'prop'"
:connection="connection"
:function="workspace.breadcrumbs.triggerFunction"
/>
<WorkspacePropsTabScheduler
</Draggable>
<!--<WorkspacePropsTabScheduler
v-show="selectedTab === 'prop' && workspace.breadcrumbs.scheduler"
:is-selected="selectedTab === 'prop'"
:connection="connection"
:scheduler="workspace.breadcrumbs.scheduler"
/>
<WorkspaceTableTab
v-show="selectedTab === 'data'"
:connection="connection"
:table="workspace.breadcrumbs.table || workspace.breadcrumbs.view"
/>
<WorkspaceQueryTab
v-for="tab of queryTabs"
:key="tab.uid"
:tab="tab"
:is-selected="selectedTab === tab.uid"
:connection="connection"
/>
/> -->
<WorkspaceEmptyState v-if="!workspace.tabs.length" @new-tab="addQueryTab" />
<template v-for="tab of workspace.tabs">
<WorkspaceQueryTab
v-if="tab.type==='query'"
:key="tab.uid"
:tab="tab"
:is-selected="selectedTab === tab.uid"
:connection="connection"
/>
<WorkspaceTableTab
v-else-if="['temp-data', 'data'].includes(tab.type)"
:key="tab.uid"
:connection="connection"
:is-selected="selectedTab === tab.uid"
:table="tab.elementName"
:schema="tab.schema"
:element-type="tab.elementType"
/>
<WorkspacePropsTab
v-else-if="tab.type === 'table-props'"
:key="tab.uid"
:connection="connection"
:is-selected="selectedTab === tab.uid"
:table="tab.elementName"
:schema="tab.schema"
/>
<WorkspacePropsTabView
v-else-if="tab.type === 'view-props'"
:key="tab.uid"
:is-selected="selectedTab === tab.uid"
:connection="connection"
:view="tab.elementName"
:schema="tab.schema"
/>
<WorkspacePropsTabTrigger
v-else-if="['temp-trigger-props', 'trigger-props'].includes(tab.type)"
:key="tab.uid"
:connection="connection"
:is-selected="selectedTab === tab.uid"
:trigger="tab.elementName"
:schema="tab.schema"
/>
<WorkspacePropsTabTriggerFunction
v-else-if="['temp-trigger-function-props', 'trigger-function-props'].includes(tab.type)"
:key="tab.uid"
:connection="connection"
:is-selected="selectedTab === tab.uid"
:function="tab.elementName"
:schema="tab.schema"
/>
<WorkspacePropsTabRoutine
v-else-if="['temp-routine-props', 'routine-props'].includes(tab.type)"
:key="tab.uid"
:connection="connection"
:is-selected="selectedTab === tab.uid"
:routine="tab.elementName"
:schema="tab.schema"
/>
<WorkspacePropsTabFunction
v-else-if="['temp-function-props', 'function-props'].includes(tab.type)"
:key="tab.uid"
:connection="connection"
:is-selected="selectedTab === tab.uid"
:function="tab.elementName"
:schema="tab.schema"
/>
<WorkspacePropsTabScheduler
v-else-if="['temp-scheduler-props', 'scheduler-props'].includes(tab.type)"
:key="tab.uid"
:connection="connection"
:is-selected="selectedTab === tab.uid"
:scheduler="tab.elementName"
:schema="tab.schema"
/>
</template>
</div>
<WorkspaceEditConnectionPanel v-else :connection="connection" />
<ModalProcessesList
v-if="isProcessesModal"
:connection="connection"
@close="hideProcessesModal"
/>
<ModalDiscardChanges
v-if="unsavedTab"
@confirm="closeTab(unsavedTab, true)"
@close="unsavedTab = null"
/>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import Draggable from 'vuedraggable';
import Connection from '@/ipc-api/Connection';
import WorkspaceEmptyState from '@/components/WorkspaceEmptyState';
import WorkspaceExploreBar from '@/components/WorkspaceExploreBar';
import WorkspaceEditConnectionPanel from '@/components/WorkspaceEditConnectionPanel';
import WorkspaceQueryTab from '@/components/WorkspaceQueryTab';
import WorkspaceTableTab from '@/components/WorkspaceTableTab';
import WorkspacePropsTab from '@/components/WorkspacePropsTab';
import WorkspacePropsTabView from '@/components/WorkspacePropsTabView';
import WorkspacePropsTabTrigger from '@/components/WorkspacePropsTabTrigger';
import WorkspacePropsTabTriggerFunction from '@/components/WorkspacePropsTabTriggerFunction';
import WorkspacePropsTabRoutine from '@/components/WorkspacePropsTabRoutine';
import WorkspacePropsTabFunction from '@/components/WorkspacePropsTabFunction';
import WorkspacePropsTabTriggerFunction from '@/components/WorkspacePropsTabTriggerFunction';
import WorkspacePropsTabScheduler from '@/components/WorkspacePropsTabScheduler';
import ModalProcessesList from '@/components/ModalProcessesList';
import ModalDiscardChanges from '@/components/ModalDiscardChanges';
export default {
name: 'Workspace',
components: {
Draggable,
WorkspaceEmptyState,
WorkspaceExploreBar,
WorkspaceEditConnectionPanel,
WorkspaceQueryTab,
WorkspaceTableTab,
WorkspacePropsTab,
WorkspacePropsTabView,
WorkspacePropsTabTrigger,
WorkspacePropsTabTriggerFunction,
WorkspacePropsTabRoutine,
WorkspacePropsTabFunction,
WorkspacePropsTabTriggerFunction,
WorkspacePropsTabScheduler,
ModalProcessesList
ModalProcessesList,
ModalDiscardChanges
},
props: {
connection: Object
@@ -196,7 +350,8 @@ export default {
data () {
return {
hasWheelEvent: false,
isProcessesModal: false
isProcessesModal: false,
unsavedTab: null
};
},
computed: {
@@ -207,6 +362,14 @@ export default {
workspace () {
return this.getWorkspace(this.connection.uid);
},
draggableTabs: {
get () {
return this.workspace.tabs;
},
set (val) {
this.updateTabs({ uid: this.connection.uid, tabs: val });
}
},
isSelected () {
return this.selectedWorkspace === this.connection.uid;
},
@@ -221,29 +384,7 @@ export default {
return false;
},
selectedTab () {
if (
(
this.workspace.breadcrumbs.table === null &&
this.workspace.breadcrumbs.view === null &&
this.workspace.breadcrumbs.trigger === null &&
this.workspace.breadcrumbs.procedure === null &&
this.workspace.breadcrumbs.function === null &&
this.workspace.breadcrumbs.triggerFunction === null &&
this.workspace.breadcrumbs.scheduler === null &&
['data', 'prop'].includes(this.workspace.selected_tab)
) ||
(
this.workspace.breadcrumbs.table === null &&
this.workspace.breadcrumbs.view === null &&
this.workspace.selected_tab === 'data'
)
)
return this.queryTabs[0].uid;
return this.queryTabs.find(tab => tab.uid === this.workspace.selected_tab) ||
['data', 'prop'].includes(this.workspace.selected_tab)
? this.workspace.selected_tab
: this.queryTabs[0].uid;
return this.workspace.selected_tab;
},
queryTabs () {
return this.workspace.tabs.filter(tab => tab.type === 'query');
@@ -262,14 +403,6 @@ export default {
if (isInitiated)
this.connectWorkspace(this.connection);
},
mounted () {
if (this.$refs.tabWrap) {
this.$refs.tabWrap.addEventListener('wheel', e => {
if (e.deltaY > 0) this.$refs.tabWrap.scrollLeft += 50;
else this.$refs.tabWrap.scrollLeft -= 50;
});
}
},
methods: {
...mapActions({
addWorkspace: 'workspaces/addWorkspace',
@@ -277,28 +410,55 @@ export default {
removeConnected: 'workspaces/removeConnected',
selectTab: 'workspaces/selectTab',
newTab: 'workspaces/newTab',
removeTab: 'workspaces/removeTab'
removeTab: 'workspaces/removeTab',
updateTabs: 'workspaces/updateTabs'
}),
addTab () {
this.newTab({ uid: this.connection.uid });
if (!this.hasWheelEvent) {
this.$refs.tabWrap.addEventListener('wheel', e => {
if (e.deltaY > 0) this.$refs.tabWrap.scrollLeft += 50;
else this.$refs.tabWrap.scrollLeft -= 50;
});
this.hasWheelEvent = true;
}
addQueryTab () {
this.newTab({ uid: this.connection.uid, type: 'query' });
},
closeTab (tUid) {
if (this.queryTabs.length === 1) return;
this.removeTab({ uid: this.connection.uid, tab: tUid });
openAsPermanentTab (tab) {
const permanentTabs = {
table: 'data',
view: 'data',
trigger: 'trigger-props',
triggerFunction: 'trigger-function-props',
function: 'function-props',
routine: 'routine-props',
scheduler: 'scheduler-props'
};
this.newTab({
uid: this.connection.uid,
schema: tab.schema,
elementName: tab.elementName,
type: permanentTabs[tab.elementType],
elementType: tab.elementType
});
},
closeTab (tab, force) {
this.unsavedTab = null;
// if (tab.type === 'query' && this.queryTabs.length === 1) return;
if (!force && tab.isChanged) {
this.unsavedTab = tab;
return;
}
this.removeTab({ uid: this.connection.uid, tab: tab.uid });
},
showProcessesModal () {
this.isProcessesModal = true;
},
hideProcessesModal () {
this.isProcessesModal = false;
},
addWheelEvent () {
if (!this.hasWheelEvent) {
this.$refs.tabWrap.$el.addEventListener('wheel', e => {
if (e.deltaY > 0) this.$refs.tabWrap.$el.scrollLeft += 50;
else this.$refs.tabWrap.$el.scrollLeft -= 50;
});
this.hasWheelEvent = true;
}
}
}
};
@@ -327,20 +487,35 @@ export default {
}
.tab-item {
max-width: 12rem;
width: fit-content;
flex: initial;
> a {
padding: 0.2rem 0.8rem;
padding: 0.2rem 0.6rem;
cursor: pointer;
display: flex;
align-items: center;
opacity: 0.7;
transition: opacity 0.2s;
&.badge::after {
position: absolute;
right: 35px;
top: 25px;
}
.btn-clear {
margin-left: 0.5rem;
opacity: 0;
transition: opacity 0.2s;
}
&:hover {
opacity: 1;
.btn-clear {
opacity: 1;
}
}
&.tab-add {
@@ -359,9 +534,15 @@ export default {
&.active a {
opacity: 1;
.btn-clear {
opacity: 1;
}
}
&.tools-dropdown {
height: 34px;
.tab-link:focus {
opacity: 1;
outline: 0;

View File

@@ -0,0 +1,519 @@
<template>
<div class="connection-panel">
<div class="panel">
<div class="panel-nav">
<ul class="tab tab-block">
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'general'}"
@click="selectTab('general')"
>
<a class="tab-link">{{ $t('word.general') }}</a>
</li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'ssl'}"
@click="selectTab('ssl')"
>
<a class="tab-link">{{ $t('word.ssl') }}</a>
</li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'ssh'}"
@click="selectTab('ssh')"
>
<a class="tab-link">{{ $t('word.sshTunnel') }}</a>
</li>
</ul>
</div>
<div v-if="selectedTab === 'general'" class="panel-body py-0">
<div>
<form class="form-horizontal">
<fieldset class="m-0" :disabled="isBusy">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.connectionName') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
ref="firstInput"
v-model="connection.name"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.client') }}</label>
</div>
<div class="column col-8 col-sm-12">
<select v-model="connection.client" class="form-select">
<option value="mysql">
MySQL
</option>
<option value="maria">
MariaDB
</option>
<option value="pg">
PostgreSQL
</option>
<!-- <option value="mssql">
Microsoft SQL
</option>
<option value="oracledb">
Oracle DB
</option> -->
</select>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.hostName') }}/IP</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="connection.host"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.port') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="connection.port"
class="form-input"
type="number"
min="1"
max="65535"
>
</div>
</div>
<div v-if="customizations.database" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.database') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="connection.database"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.user') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="connection.user"
class="form-input"
type="text"
:disabled="connection.ask"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.password') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="connection.password"
class="form-input"
type="password"
:disabled="connection.ask"
>
</div>
</div>
<div v-if="customizations.connectionSchema" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.schema') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="connection.schema"
class="form-input"
type="text"
:placeholder="$t('word.all')"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12" />
<div class="column col-8 col-sm-12">
<label class="form-checkbox form-inline">
<input v-model="connection.ask" type="checkbox"><i class="form-icon" /> {{ $t('message.askCredentials') }}
</label>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div v-if="selectedTab === 'ssl'" class="panel-body py-0">
<div>
<form class="form-horizontal">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">
{{ $t('message.enableSsl') }}
</label>
</div>
<div class="column col-8 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleSsl">
<input type="checkbox" :checked="connection.ssl">
<i class="form-icon" />
</label>
</div>
</div>
<fieldset class="m-0" :disabled="isBusy || !connection.ssl">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.privateKey') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="connection.key"
:message="$t('word.browse')"
@clear="pathClear('key')"
@change="pathSelection($event, 'key')"
/>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.certificate') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="connection.cert"
:message="$t('word.browse')"
@clear="pathClear('cert')"
@change="pathSelection($event, 'cert')"
/>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.caCertificate') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="connection.ca"
:message="$t('word.browse')"
@clear="pathClear('ca')"
@change="pathSelection($event, 'ca')"
/>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.ciphers') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
ref="firstInput"
v-model="connection.ciphers"
class="form-input"
type="text"
>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div v-if="selectedTab === 'ssh'" class="panel-body py-0">
<div>
<form class="form-horizontal">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">
{{ $t('message.enableSsh') }}
</label>
</div>
<div class="column col-8 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleSsh">
<input type="checkbox" :checked="connection.ssh">
<i class="form-icon" />
</label>
</div>
</div>
<fieldset class="m-0" :disabled="isBusy || !connection.ssh">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.hostName') }}/IP</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="connection.sshHost"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.user') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="connection.sshUser"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.password') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="connection.sshPass"
class="form-input"
type="password"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.port') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="connection.sshPort"
class="form-input"
type="number"
min="1"
max="65535"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.privateKey') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="connection.sshKey"
:message="$t('word.browse')"
@clear="pathClear('sshKey')"
@change="pathSelection($event, 'sshKey')"
/>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div class="panel-footer">
<button
class="btn btn-gray mr-2 d-flex"
:class="{'loading': isTesting}"
:disabled="isBusy"
@click="startTest"
>
<i class="mdi mdi-24px mdi-lightning-bolt mr-1" />
{{ $t('message.testConnection') }}
</button>
<button
class="btn btn-primary mr-2 d-flex"
:disabled="isBusy"
@click="saveConnection"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
{{ $t('word.save') }}
</button>
</div>
</div>
<ModalAskCredentials
v-if="isAsking"
@close-asking="closeAsking"
@credentials="continueTest"
/>
</div>
</template>
<script>
import { mapActions } from 'vuex';
import customizations from 'common/customizations';
import Connection from '@/ipc-api/Connection';
import { uidGen } from 'common/libs/uidGen';
import ModalAskCredentials from '@/components/ModalAskCredentials';
import BaseUploadInput from '@/components/BaseUploadInput';
export default {
name: 'WorkspaceAddConnectionPanel',
components: {
ModalAskCredentials,
BaseUploadInput
},
data () {
return {
connection: {
name: '',
client: 'mysql',
host: '127.0.0.1',
database: null,
port: null,
user: null,
password: '',
ask: false,
uid: uidGen('C'),
ssl: false,
cert: '',
key: '',
ca: '',
ciphers: '',
ssh: false,
sshHost: '',
sshUser: '',
sshPass: '',
sshKey: '',
sshPort: 22
},
isConnecting: false,
isTesting: false,
isAsking: false,
selectedTab: 'general'
};
},
computed: {
customizations () {
return customizations[this.connection.client];
},
isBusy () {
return this.isConnecting || this.isTesting;
}
},
created () {
this.setDefaults();
setTimeout(() => {
if (this.$refs.firstInput) this.$refs.firstInput.focus();
}, 20);
},
methods: {
...mapActions({
addConnection: 'connections/addConnection',
connectWorkspace: 'workspaces/connectWorkspace',
addNotification: 'notifications/addNotification',
selectWorkspace: 'workspaces/selectWorkspace'
}),
setDefaults () {
this.connection.user = this.customizations.defaultUser;
this.connection.port = this.customizations.defaultPort;
this.connection.database = this.customizations.defaultDatabase;
},
async startConnection () {
await this.saveConnection();
this.isConnecting = true;
if (this.connection.ask)
this.isAsking = true;
else {
await this.connectWorkspace(this.connection);
this.isConnecting = false;
}
},
async startTest () {
this.isTesting = true;
if (this.connection.ask)
this.isAsking = true;
else {
try {
const res = await Connection.makeTest(this.connection);
if (res.status === 'error')
this.addNotification({ status: 'error', message: res.response.message });
else
this.addNotification({ status: 'success', message: this.$t('message.connectionSuccessfullyMade') });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isTesting = false;
}
},
async continueTest (credentials) { // if "Ask for credentials" is true
this.isAsking = false;
const params = Object.assign({}, this.connection, credentials);
try {
if (this.isConnecting) {
const params = Object.assign({}, this.connection, credentials);
await this.connectWorkspace(params);
this.isConnecting = false;
}
else {
const res = await Connection.makeTest(params);
if (res.status === 'error')
this.addNotification({ status: 'error', message: res.response.message });
else
this.addNotification({ status: 'success', message: this.$t('message.connectionSuccessfullyMade') });
}
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isTesting = false;
},
saveConnection () {
this.selectWorkspace(this.connection.uid);
return this.addConnection(this.connection);
},
closeAsking () {
this.isTesting = false;
this.isAsking = false;
},
selectTab (tab) {
this.selectedTab = tab;
},
toggleSsl () {
this.connection.ssl = !this.connection.ssl;
},
toggleSsh () {
this.connection.ssh = !this.connection.ssh;
},
pathSelection (event, name) {
const { files } = event.target;
if (!files.length) return;
this.connection[name] = files[0].path;
},
pathClear (name) {
this.connection[name] = '';
}
}
};
</script>
<style lang="scss" scoped>
.connection-panel {
margin-top: 15vh;
margin-left: auto;
margin-right: auto;
.panel {
width: 450px;
border-radius: $border-radius;
.panel-body {
flex: initial;
}
.panel-footer {
display: flex;
justify-content: flex-end;
}
}
}
</style>

View File

@@ -1,82 +0,0 @@
<template>
<div class="columns">
<div class="column col-12 empty">
<div class="empty-icon">
<i class="mdi mdi-48px mdi-power-plug-off" />
</div>
<p class="empty-title h5">
{{ isConnecting ? $t('word.connecting') : $t('word.disconnected') }}
</p>
<div class="empty-action">
<button
class="btn btn-success"
:class="{'loading': isConnecting}"
@click="startConnection"
>
{{ $t('word.connect') }}
</button>
</div>
</div>
<ModalAskCredentials
v-if="isAsking"
@close-asking="closeAsking"
@credentials="continueTest"
/>
</div>
</template>
<script>
import { mapActions } from 'vuex';
import ModalAskCredentials from '@/components/ModalAskCredentials';
export default {
name: 'WorkspaceConnectPanel',
components: {
ModalAskCredentials
},
props: {
connection: Object
},
data () {
return {
isConnecting: false,
isAsking: false
};
},
methods: {
...mapActions({
connectWorkspace: 'workspaces/connectWorkspace'
}),
async startConnection () {
this.isConnecting = true;
if (this.connection.ask)
this.isAsking = true;
else {
await this.connectWorkspace(this.connection);
this.isConnecting = false;
}
},
async continueTest (credentials) { // if "Ask for credentials" is true
this.isAsking = false;
const params = Object.assign({}, this.connection, credentials);
await this.connectWorkspace(params);
this.isConnecting = false;
},
closeAsking () {
this.isAsking = false;
this.isConnecting = false;
}
}
};
</script>
<style scoped>
.empty {
height: 100%;
border-radius: 0;
background: transparent;
display: flex;
flex-direction: column;
}
</style>

View File

@@ -0,0 +1,500 @@
<template>
<div class="connection-panel">
<div class="panel">
<div class="panel-nav">
<ul class="tab tab-block">
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'general'}"
@click="selectTab('general')"
>
<a class="tab-link">{{ $t('word.general') }}</a>
</li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'ssl'}"
@click="selectTab('ssl')"
>
<a class="tab-link">{{ $t('word.ssl') }}</a>
</li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'ssh'}"
@click="selectTab('ssh')"
>
<a class="tab-link">{{ $t('word.sshTunnel') }}</a>
</li>
</ul>
</div>
<div v-if="selectedTab === 'general'" class="panel-body py-0">
<div>
<form class="form-horizontal">
<fieldset class="m-0" :disabled="isBusy">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.connectionName') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
ref="firstInput"
v-model="localConnection.name"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.client') }}</label>
</div>
<div class="column col-8 col-sm-12">
<select v-model="localConnection.client" class="form-select">
<option value="mysql">
MySQL
</option>
<option value="maria">
MariaDB
</option>
<option value="pg">
PostgreSQL
</option>
</select>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.hostName') }}/IP</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="localConnection.host"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.port') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="localConnection.port"
class="form-input"
type="number"
min="1"
max="65535"
>
</div>
</div>
<div v-if="customizations.database" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.database') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="localConnection.database"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.user') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="localConnection.user"
class="form-input"
type="text"
:disabled="localConnection.ask"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.password') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="localConnection.password"
class="form-input"
type="password"
:disabled="localConnection.ask"
>
</div>
</div>
<div v-if="customizations.connectionSchema" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.schema') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="localConnection.schema"
class="form-input"
type="text"
:placeholder="$t('word.all')"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12" />
<div class="column col-8 col-sm-12">
<label class="form-checkbox form-inline">
<input v-model="localConnection.ask" type="checkbox"><i class="form-icon" /> {{ $t('message.askCredentials') }}
</label>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div v-if="selectedTab === 'ssl'" class="panel-body py-0">
<div>
<form class="form-horizontal">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">
{{ $t('message.enableSsl') }}
</label>
</div>
<div class="column col-8 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleSsl">
<input type="checkbox" :checked="localConnection.ssl">
<i class="form-icon" />
</label>
</div>
</div>
<fieldset class="m-0" :disabled="isBusy || !localConnection.ssl">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.privateKey') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="localConnection.key"
:message="$t('word.browse')"
@clear="pathClear('key')"
@change="pathSelection($event, 'key')"
/>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.certificate') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="localConnection.cert"
:message="$t('word.browse')"
@clear="pathClear('cert')"
@change="pathSelection($event, 'cert')"
/>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.caCertificate') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="localConnection.ca"
:message="$t('word.browse')"
@clear="pathClear('ca')"
@change="pathSelection($event, 'ca')"
/>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.ciphers') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
ref="firstInput"
v-model="localConnection.ciphers"
class="form-input"
type="text"
>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div v-if="selectedTab === 'ssh'" class="panel-body py-0">
<div>
<form class="form-horizontal">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">
{{ $t('message.enableSsh') }}
</label>
</div>
<div class="column col-8 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleSsh">
<input type="checkbox" :checked="localConnection.ssh">
<i class="form-icon" />
</label>
</div>
</div>
<fieldset class="m-0" :disabled="isBusy || !localConnection.ssh">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.hostName') }}/IP</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="localConnection.sshHost"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.user') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="localConnection.sshUser"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.password') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="localConnection.sshPass"
class="form-input"
type="password"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.port') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="localConnection.sshPort"
class="form-input"
type="number"
min="1"
max="65535"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.privateKey') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="localConnection.sshKey"
:message="$t('word.browse')"
@clear="pathClear('sshKey')"
@change="pathSelection($event, 'sshKey')"
/>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div class="panel-footer">
<button
class="btn btn-gray mr-2 d-flex"
:class="{'loading': isTesting}"
:disabled="isBusy"
@click="startTest"
>
<i class="mdi mdi-24px mdi-lightning-bolt mr-1" />
{{ $t('message.testConnection') }}
</button>
<button
class="btn btn-primary mr-2 d-flex"
:disabled="isBusy || !hasChanges"
@click="saveConnection"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
{{ $t('word.save') }}
</button>
<button
class="btn btn-success d-flex"
:class="{'loading': isConnecting}"
:disabled="isBusy"
@click="startConnection"
>
<i class="mdi mdi-24px mdi-connection mr-1" />
{{ $t('word.connect') }}
</button>
</div>
</div>
<ModalAskCredentials
v-if="isAsking"
@close-asking="closeAsking"
@credentials="continueTest"
/>
</div>
</template>
<script>
import { mapActions } from 'vuex';
import customizations from 'common/customizations';
import Connection from '@/ipc-api/Connection';
import ModalAskCredentials from '@/components/ModalAskCredentials';
import BaseUploadInput from '@/components/BaseUploadInput';
export default {
name: 'WorkspaceEditConnectionPanel',
components: {
ModalAskCredentials,
BaseUploadInput
},
props: {
connection: Object
},
data () {
return {
isConnecting: false,
isTesting: false,
isAsking: false,
localConnection: null,
selectedTab: 'general'
};
},
computed: {
customizations () {
return customizations[this.connection.client];
},
isBusy () {
return this.isConnecting || this.isTesting;
},
hasChanges () {
return JSON.stringify(this.connection) !== JSON.stringify(this.localConnection);
}
},
watch: {
connection () {
this.localConnection = JSON.parse(JSON.stringify(this.connection));
}
},
created () {
this.localConnection = JSON.parse(JSON.stringify(this.connection));
},
methods: {
...mapActions({
editConnection: 'connections/editConnection',
connectWorkspace: 'workspaces/connectWorkspace',
addNotification: 'notifications/addNotification'
}),
async startConnection () {
await this.saveConnection();
this.isConnecting = true;
if (this.localConnection.ask)
this.isAsking = true;
else {
await this.connectWorkspace(this.localConnection);
this.isConnecting = false;
}
},
async startTest () {
this.isTesting = true;
if (this.localConnection.ask)
this.isAsking = true;
else {
try {
const res = await Connection.makeTest(this.localConnection);
if (res.status === 'error')
this.addNotification({ status: 'error', message: res.response.message });
else
this.addNotification({ status: 'success', message: this.$t('message.connectionSuccessfullyMade') });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isTesting = false;
}
},
async continueTest (credentials) { // if "Ask for credentials" is true
this.isAsking = false;
const params = Object.assign({}, this.localConnection, credentials);
try {
if (this.isConnecting) {
const params = Object.assign({}, this.connection, credentials);
await this.connectWorkspace(params);
this.isConnecting = false;
}
else {
const res = await Connection.makeTest(params);
if (res.status === 'error')
this.addNotification({ status: 'error', message: res.response.message });
else
this.addNotification({ status: 'success', message: this.$t('message.connectionSuccessfullyMade') });
}
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isTesting = false;
},
saveConnection () {
return this.editConnection(this.localConnection);
},
closeAsking () {
this.isTesting = false;
this.isAsking = false;
},
selectTab (tab) {
this.selectedTab = tab;
},
toggleSsl () {
this.localConnection.ssl = !this.localConnection.ssl;
},
toggleSsh () {
this.localConnection.ssh = !this.localConnection.ssh;
},
pathSelection (event, name) {
const { files } = event.target;
if (!files.length) return;
this.localConnection[name] = files[0].path;
},
pathClear (name) {
this.localConnection[name] = '';
}
}
};
</script>
<style lang="scss" scoped>
.connection-panel {
margin-top: 15vh;
margin-left: auto;
margin-right: auto;
.panel {
width: 450px;
border-radius: $border-radius;
.panel-body {
flex: initial;
}
.panel-footer {
display: flex;
justify-content: flex-end;
}
}
}
</style>

View File

@@ -0,0 +1,55 @@
<template>
<div class="column col-12 empty">
<div class="empty-icon">
<img :src="require(`@/images/logo-${applicationTheme}.svg`).default" width="200">
</div>
<p class="h6 empty-subtitle">
{{ $t('message.noOpenTabs') }}
</p>
<div class="empty-action">
<button class="btn btn-gray d-flex" @click="$emit('new-tab')">
<i class="mdi mdi-24px mdi-tab-plus mr-2" />
{{ $t('message.openNewTab') }}
</button>
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
export default {
name: 'WorkspaceEmptyState',
computed: {
...mapGetters({
applicationTheme: 'settings/getApplicationTheme',
getWorkspace: 'workspaces/getWorkspace',
selectedWorkspace: 'workspaces/getSelected'
}),
workspace () {
return this.getWorkspace(this.selectedWorkspace);
}
},
created () {
this.changeBreadcrumbs({ schema: this.workspace.breadcrumbs.schema });
},
methods: {
...mapActions({
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
})
}
};
</script>
<style scoped>
.empty {
height: 100%;
border-radius: 0;
background: transparent;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-left: auto;
margin-right: auto;
}
</style>

View File

@@ -21,7 +21,7 @@
@click="refresh"
/>
<i
class="mdi mdi-18px mdi-power-plug-off c-hand"
class="mdi mdi-18px mdi-power c-hand"
:title="$t('word.disconnect')"
@click="disconnectWorkspace(connection.uid)"
/>
@@ -38,12 +38,7 @@
<i class="form-icon mdi mdi-magnify mdi-18px" />
</div>
</div>
<WorkspaceConnectPanel
v-if="workspace.connection_status !== 'connected'"
class="workspace-explorebar-body"
:connection="connection"
/>
<div v-else class="workspace-explorebar-body">
<div class="workspace-explorebar-body">
<WorkspaceExploreBarSchema
v-for="db of workspace.structure"
:key="db.name"
@@ -52,6 +47,7 @@
@show-schema-context="openSchemaContext"
@show-table-context="openTableContext"
@show-misc-context="openMiscContext"
@show-misc-folder-context="openMiscFolderContext"
/>
</div>
</div>
@@ -104,7 +100,7 @@
/>
<DatabaseContext
v-if="isDatabaseContext"
:selected-database="selectedDatabase"
:selected-schema="selectedSchema"
:context-event="databaseContextEvent"
@close-context="closeDatabaseContext"
@show-create-table-modal="showCreateTableModal"
@@ -118,6 +114,7 @@
/>
<TableContext
v-if="isTableContext"
:selected-schema="selectedSchema"
:selected-table="selectedTable"
:context-event="tableContextEvent"
@close-context="closeTableContext"
@@ -126,10 +123,24 @@
<MiscContext
v-if="isMiscContext"
:selected-misc="selectedMisc"
:selected-schema="selectedSchema"
:context-event="miscContextEvent"
@close-context="closeMiscContext"
@reload="refresh"
/>
<MiscFolderContext
v-if="isMiscFolderContext"
:selected-misc="selectedMisc"
:selected-schema="selectedSchema"
:context-event="miscContextEvent"
@show-create-trigger-modal="showCreateTriggerModal"
@show-create-routine-modal="showCreateRoutineModal"
@show-create-function-modal="showCreateFunctionModal"
@show-create-trigger-function-modal="showCreateTriggerFunctionModal"
@show-create-scheduler-modal="showCreateSchedulerModal"
@close-context="closeMiscFolderContext"
@reload="refresh"
/>
</div>
</template>
@@ -143,11 +154,11 @@ import Routines from '@/ipc-api/Routines';
import Functions from '@/ipc-api/Functions';
import Schedulers from '@/ipc-api/Schedulers';
import WorkspaceConnectPanel from '@/components/WorkspaceConnectPanel';
import WorkspaceExploreBarSchema from '@/components/WorkspaceExploreBarSchema';
import DatabaseContext from '@/components/WorkspaceExploreBarSchemaContext';
import TableContext from '@/components/WorkspaceExploreBarTableContext';
import MiscContext from '@/components/WorkspaceExploreBarMiscContext';
import MiscFolderContext from '@/components/WorkspaceExploreBarMiscFolderContext';
import ModalNewSchema from '@/components/ModalNewSchema';
import ModalNewTable from '@/components/ModalNewTable';
import ModalNewView from '@/components/ModalNewView';
@@ -160,11 +171,11 @@ import ModalNewScheduler from '@/components/ModalNewScheduler';
export default {
name: 'WorkspaceExploreBar',
components: {
WorkspaceConnectPanel,
WorkspaceExploreBarSchema,
DatabaseContext,
TableContext,
MiscContext,
MiscFolderContext,
ModalNewSchema,
ModalNewTable,
ModalNewView,
@@ -197,12 +208,13 @@ export default {
isDatabaseContext: false,
isTableContext: false,
isMiscContext: false,
isMiscFolderContext: false,
databaseContextEvent: null,
tableContextEvent: null,
miscContextEvent: null,
selectedDatabase: '',
selectedSchema: '',
selectedTable: null,
selectedMisc: null,
searchTerm: ''
@@ -262,6 +274,7 @@ export default {
refreshStructure: 'workspaces/refreshStructure',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
selectTab: 'workspaces/selectTab',
newTab: 'workspaces/newTab',
setSearchTerm: 'workspaces/setSearchTerm',
addNotification: 'notifications/addNotification',
changeExplorebarSize: 'settings/changeExplorebarSize'
@@ -299,6 +312,7 @@ export default {
async openCreateTableEditor (payload) {
const params = {
uid: this.connection.uid,
schema: this.selectedSchema,
...payload
};
@@ -306,14 +320,19 @@ export default {
if (status === 'success') {
await this.refresh();
this.changeBreadcrumbs({ schema: this.selectedDatabase, table: payload.name });
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
this.newTab({
uid: this.workspace.uid,
schema: this.selectedSchema,
elementName: payload.name,
elementType: 'table',
type: 'table-props'
});
}
else
this.addNotification({ status: 'error', message: response });
},
openSchemaContext (payload) {
this.selectedDatabase = payload.schema;
this.selectedSchema = payload.schema;
this.databaseContextEvent = payload.event;
this.isDatabaseContext = true;
},
@@ -322,6 +341,7 @@ export default {
},
openTableContext (payload) {
this.selectedTable = payload.table;
this.selectedSchema = payload.schema;
this.tableContextEvent = payload.event;
this.isTableContext = true;
},
@@ -330,14 +350,25 @@ export default {
},
openMiscContext (payload) {
this.selectedMisc = payload.misc;
this.selectedSchema = payload.schema;
this.miscContextEvent = payload.event;
this.isMiscContext = true;
},
openMiscFolderContext (payload) {
this.selectedMisc = payload.type;
this.selectedSchema = payload.schema;
this.miscContextEvent = payload.event;
this.isMiscFolderContext = true;
},
closeMiscContext () {
this.isMiscContext = false;
},
closeMiscFolderContext () {
this.isMiscFolderContext = false;
},
showCreateViewModal () {
this.closeDatabaseContext();
this.closeMiscFolderContext();
this.isNewViewModal = true;
},
hideCreateViewModal () {
@@ -346,6 +377,7 @@ export default {
async openCreateViewEditor (payload) {
const params = {
uid: this.connection.uid,
schema: this.selectedSchema,
...payload
};
@@ -353,14 +385,22 @@ export default {
if (status === 'success') {
await this.refresh();
this.changeBreadcrumbs({ schema: this.selectedDatabase, view: payload.name });
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
this.changeBreadcrumbs({ schema: this.selectedSchema, view: payload.name });
this.newTab({
uid: this.workspace.uid,
schema: this.selectedSchema,
elementName: payload.name,
elementType: 'view',
type: 'view-props'
});
}
else
this.addNotification({ status: 'error', message: response });
},
showCreateTriggerModal () {
this.closeDatabaseContext();
this.closeMiscFolderContext();
this.isNewTriggerModal = true;
},
hideCreateTriggerModal () {
@@ -369,6 +409,7 @@ export default {
async openCreateTriggerEditor (payload) {
const params = {
uid: this.connection.uid,
schema: this.selectedSchema,
...payload
};
@@ -377,14 +418,22 @@ export default {
if (status === 'success') {
await this.refresh();
const triggerName = this.customizations.triggerTableInName ? `${payload.table}.${payload.name}` : payload.name;
this.changeBreadcrumbs({ schema: this.selectedDatabase, trigger: triggerName });
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
this.changeBreadcrumbs({ schema: this.selectedSchema, trigger: triggerName });
this.newTab({
uid: this.workspace.uid,
schema: this.selectedSchema,
elementName: triggerName,
elementType: 'trigger',
type: 'trigger-props'
});
}
else
this.addNotification({ status: 'error', message: response });
},
showCreateRoutineModal () {
this.closeDatabaseContext();
this.closeMiscFolderContext();
this.isNewRoutineModal = true;
},
hideCreateRoutineModal () {
@@ -393,6 +442,7 @@ export default {
async openCreateRoutineEditor (payload) {
const params = {
uid: this.connection.uid,
schema: this.selectedSchema,
...payload
};
@@ -400,14 +450,22 @@ export default {
if (status === 'success') {
await this.refresh();
this.changeBreadcrumbs({ schema: this.selectedDatabase, procedure: payload.name });
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
this.changeBreadcrumbs({ schema: this.selectedSchema, routine: payload.name });
this.newTab({
uid: this.workspace.uid,
schema: this.selectedSchema,
elementName: payload.name,
elementType: 'routine',
type: 'routine-props'
});
}
else
this.addNotification({ status: 'error', message: response });
},
showCreateFunctionModal () {
this.closeDatabaseContext();
this.closeMiscFolderContext();
this.isNewFunctionModal = true;
},
hideCreateFunctionModal () {
@@ -415,6 +473,7 @@ export default {
},
showCreateTriggerFunctionModal () {
this.closeDatabaseContext();
this.closeMiscFolderContext();
this.isNewTriggerFunctionModal = true;
},
hideCreateTriggerFunctionModal () {
@@ -422,6 +481,7 @@ export default {
},
showCreateSchedulerModal () {
this.closeDatabaseContext();
this.closeMiscFolderContext();
this.isNewSchedulerModal = true;
},
hideCreateSchedulerModal () {
@@ -430,6 +490,7 @@ export default {
async openCreateFunctionEditor (payload) {
const params = {
uid: this.connection.uid,
schema: this.selectedSchema,
...payload
};
@@ -437,8 +498,15 @@ export default {
if (status === 'success') {
await this.refresh();
this.changeBreadcrumbs({ schema: this.selectedDatabase, function: payload.name });
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
this.changeBreadcrumbs({ schema: this.selectedSchema, function: payload.name });
this.newTab({
uid: this.workspace.uid,
schema: this.selectedSchema,
elementName: payload.name,
elementType: 'function',
type: 'function-props'
});
}
else
this.addNotification({ status: 'error', message: response });
@@ -446,6 +514,7 @@ export default {
async openCreateTriggerFunctionEditor (payload) {
const params = {
uid: this.connection.uid,
schema: this.selectedSchema,
...payload
};
@@ -453,8 +522,15 @@ export default {
if (status === 'success') {
await this.refresh();
this.changeBreadcrumbs({ schema: this.selectedDatabase, triggerFunction: payload.name });
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
this.changeBreadcrumbs({ schema: this.selectedSchema, triggerFunction: payload.name });
this.newTab({
uid: this.workspace.uid,
schema: this.selectedSchema,
elementName: payload.name,
elementType: 'triggerFunction',
type: 'trigger-function-props'
});
}
else
this.addNotification({ status: 'error', message: response });
@@ -462,6 +538,7 @@ export default {
async openCreateSchedulerEditor (payload) {
const params = {
uid: this.connection.uid,
schema: this.selectedSchema,
...payload
};
@@ -469,8 +546,15 @@ export default {
if (status === 'success') {
await this.refresh();
this.changeBreadcrumbs({ schema: this.selectedDatabase, scheduler: payload.name });
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
this.changeBreadcrumbs({ schema: this.selectedSchema, scheduler: payload.name });
this.newTab({
uid: this.workspace.uid,
schema: this.selectedSchema,
elementName: payload.name,
elementType: 'scheduler',
type: 'scheduler-props'
});
}
else
this.addNotification({ status: 'error', message: response });

View File

@@ -59,7 +59,8 @@ export default {
},
props: {
contextEvent: MouseEvent,
selectedMisc: Object
selectedMisc: Object,
selectedSchema: String
},
data () {
return {
@@ -97,6 +98,7 @@ export default {
...mapActions({
addNotification: 'notifications/addNotification',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
removeTabs: 'workspaces/removeTabs',
newTab: 'workspaces/newTab'
}),
showCreateTableModal () {
@@ -126,12 +128,14 @@ export default {
case 'trigger':
res = await Triggers.dropTrigger({
uid: this.selectedWorkspace,
schema: this.selectedSchema,
trigger: this.selectedMisc.name
});
break;
case 'procedure':
res = await Routines.dropRoutine({
uid: this.selectedWorkspace,
schema: this.selectedSchema,
routine: this.selectedMisc.name
});
break;
@@ -139,12 +143,14 @@ export default {
case 'triggerFunction':
res = await Functions.dropFunction({
uid: this.selectedWorkspace,
schema: this.selectedSchema,
func: this.selectedMisc.name
});
break;
case 'scheduler':
res = await Schedulers.dropScheduler({
uid: this.selectedWorkspace,
schema: this.selectedSchema,
scheduler: this.selectedMisc.name
});
break;
@@ -153,7 +159,12 @@ export default {
const { status, response } = res;
if (status === 'success') {
this.changeBreadcrumbs({ [this.selectedMisc.type]: null });
this.removeTabs({
uid: this.selectedWorkspace,
elementName: this.selectedMisc.name,
elementType: this.selectedMisc.type,
schema: this.selectedSchema
});
this.closeContext();
this.$emit('reload');
@@ -180,8 +191,8 @@ export default {
async runRoutineCheck () {
const params = {
uid: this.selectedWorkspace,
schema: this.workspace.breadcrumbs.schema,
routine: this.workspace.breadcrumbs.procedure
schema: this.selectedSchema,
routine: this.selectedMisc.name
};
try {
@@ -218,14 +229,14 @@ export default {
sql = `CALL \`${this.localElement.name}\`(${params.join(',')})`;
}
this.newTab({ uid: this.workspace.uid, content: sql, autorun: true });
this.newTab({ uid: this.workspace.uid, content: sql, type: 'query', autorun: true });
this.closeContext();
},
async runFunctionCheck () {
const params = {
uid: this.selectedWorkspace,
schema: this.workspace.breadcrumbs.schema,
func: this.workspace.breadcrumbs.function
schema: this.selectedSchema,
func: this.selectedMisc.name
};
try {
@@ -263,7 +274,7 @@ export default {
sql = `SELECT \`${this.localElement.name}\` (${params.join(',')})`;
}
this.newTab({ uid: this.workspace.uid, content: sql, autorun: true });
this.newTab({ uid: this.workspace.uid, content: sql, type: 'query', autorun: true });
this.closeContext();
}
}

View File

@@ -0,0 +1,98 @@
<template>
<BaseContextMenu
:context-event="contextEvent"
@close-context="closeContext"
>
<div
v-if="selectedMisc === 'trigger'"
class="context-element"
@click="$emit('show-create-trigger-modal')"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-table-cog text-light pr-1" /> {{ $t('message.createNewTrigger') }}</span>
</div>
<div
v-if="selectedMisc === 'procedure'"
class="context-element"
@click="$emit('show-create-routine-modal')"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-sync-circle text-light pr-1" /> {{ $t('message.createNewRoutine') }}</span>
</div>
<div
v-if="selectedMisc === 'function'"
class="context-element"
@click="$emit('show-create-function-modal')"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-arrow-right-bold-box text-light pr-1" /> {{ $t('message.createNewFunction') }}</span>
</div>
<div
v-if="selectedMisc === 'triggerFunction'"
class="context-element"
@click="$emit('show-create-trigger-function-modal')"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-cog-clockwise text-light pr-1" /> {{ $t('message.createNewFunction') }}</span>
</div>
<div
v-if="selectedMisc === 'scheduler'"
class="context-element"
@click="$emit('show-create-scheduler-modal')"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-calendar-clock text-light pr-1" /> {{ $t('message.createNewScheduler') }}</span>
</div>
</BaseContextMenu>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import BaseContextMenu from '@/components/BaseContextMenu';
export default {
name: 'WorkspaceExploreBarMiscContext',
components: {
BaseContextMenu
},
props: {
contextEvent: MouseEvent,
selectedMisc: String,
selectedSchema: String
},
data () {
return {
localElement: {}
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.selectedWorkspace);
}
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
}),
showCreateTableModal () {
this.$emit('show-create-table-modal');
},
showDeleteModal () {
this.isDeleteModal = true;
},
hideDeleteModal () {
this.isDeleteModal = false;
},
showAskParamsModal () {
this.isAskingParameters = true;
},
hideAskParamsModal () {
this.isAskingParameters = false;
this.closeContext();
},
closeContext () {
this.$emit('close-context');
}
}
};
</script>

View File

@@ -19,7 +19,8 @@
:key="table.name"
class="menu-item"
:class="{'text-bold': breadcrumbs.schema === database.name && [breadcrumbs.table, breadcrumbs.view].includes(table.name)}"
@click="setBreadcrumbs({schema: database.name, [table.type]: table.name})"
@mousedown.left="selectTable({schema: database.name, table})"
@dblclick="openDataTab({schema: database.name, table})"
@contextmenu.prevent="showTableContext($event, table)"
>
<a class="table-name">
@@ -39,7 +40,11 @@
<div v-if="filteredTriggers.length && customizations.triggers" class="database-misc">
<details class="accordion">
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.trigger}">
<summary
class="accordion-header misc-name"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.trigger}"
@contextmenu.prevent="showMiscFolderContext($event, 'trigger')"
>
<i class="misc-icon mdi mdi-18px mdi-folder-cog mr-1" />
{{ $tc('word.trigger', 2) }}
</summary>
@@ -51,7 +56,8 @@
:key="trigger.name"
class="menu-item"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.trigger === trigger.name}"
@click="setBreadcrumbs({schema: database.name, trigger: trigger.name})"
@mousedown="selectMisc({schema: database.name, misc: trigger, type: 'trigger'})"
@dblclick="openMiscPermanentTab({schema: database.name, misc: trigger, type: 'trigger'})"
@contextmenu.prevent="showMiscContext($event, {...trigger, type: 'trigger'})"
>
<a class="table-name">
@@ -67,7 +73,11 @@
<div v-if="filteredProcedures.length && customizations.routines" class="database-misc">
<details class="accordion">
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.procedure}">
<summary
class="accordion-header misc-name"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.routine}"
@contextmenu.prevent="showMiscFolderContext($event, 'procedure')"
>
<i class="misc-icon mdi mdi-18px mdi-folder-sync mr-1" />
{{ $tc('word.storedRoutine', 2) }}
</summary>
@@ -78,8 +88,9 @@
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})"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.routine === procedure.name}"
@mousedown="selectMisc({schema: database.name, misc: procedure, type: 'routine'})"
@dblclick="openMiscPermanentTab({schema: database.name, misc: procedure, type: 'routine'})"
@contextmenu.prevent="showMiscContext($event, {...procedure, type: 'procedure'})"
>
<a class="table-name">
@@ -95,7 +106,11 @@
<div v-if="filteredTriggerFunctions.length && customizations.triggerFunctions" class="database-misc">
<details class="accordion">
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.triggerFunction}">
<summary
class="accordion-header misc-name"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.triggerFunction}"
@contextmenu.prevent="showMiscFolderContext($event, 'triggerFunction')"
>
<i class="misc-icon mdi mdi-18px mdi-folder-refresh mr-1" />
{{ $tc('word.triggerFunction', 2) }}
</summary>
@@ -107,7 +122,8 @@
:key="`${func.name}-${i}`"
class="menu-item"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.triggerFunction === func.name}"
@click="setBreadcrumbs({schema: database.name, triggerFunction: func.name})"
@mousedown="selectMisc({schema: database.name, misc: func, type: 'triggerFunction'})"
@dblclick="openMiscPermanentTab({schema: database.name, misc: func, type: 'triggerFunction'})"
@contextmenu.prevent="showMiscContext($event, {...func, type: 'triggerFunction'})"
>
<a class="table-name">
@@ -123,7 +139,11 @@
<div v-if="filteredFunctions.length && customizations.functions" class="database-misc">
<details class="accordion">
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.function}">
<summary
class="accordion-header misc-name"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.function}"
@contextmenu.prevent="showMiscFolderContext($event, 'function')"
>
<i class="misc-icon mdi mdi-18px mdi-folder-move mr-1" />
{{ $tc('word.function', 2) }}
</summary>
@@ -135,7 +155,8 @@
: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})"
@mousedown="selectMisc({schema: database.name, misc: func, type: 'function'})"
@dblclick="openMiscPermanentTab({schema: database.name, misc: func, type: 'function'})"
@contextmenu.prevent="showMiscContext($event, {...func, type: 'function'})"
>
<a class="table-name">
@@ -151,7 +172,11 @@
<div v-if="filteredSchedulers.length && customizations.schedulers" class="database-misc">
<details class="accordion">
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.scheduler}">
<summary
class="accordion-header misc-name"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.scheduler}"
@contextmenu.prevent="showMiscFolderContext($event, 'scheduler')"
>
<i class="misc-icon mdi mdi-18px mdi-folder-clock mr-1" />
{{ $tc('word.scheduler', 2) }}
</summary>
@@ -163,7 +188,8 @@
:key="scheduler.name"
class="menu-item"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.scheduler === scheduler.name}"
@click="setBreadcrumbs({schema: database.name, scheduler: scheduler.name})"
@mousedown="selectMisc({schema: database.name, misc: scheduler, type: 'scheduler'})"
@dblclick="openMiscPermanentTab({schema: database.name, misc: scheduler, type: 'scheduler'})"
@contextmenu.prevent="showMiscContext($event, {...scheduler, type: 'scheduler'})"
>
<a class="table-name">
@@ -247,29 +273,77 @@ export default {
methods: {
...mapActions({
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
addLoadedSchema: 'workspaces/addLoadedSchema',
newTab: 'workspaces/newTab',
refreshSchema: 'workspaces/refreshSchema'
}),
formatBytes,
async selectSchema (schema) {
if (!this.loadedSchemas.has(schema)) {
if (!this.loadedSchemas.has(schema) && !this.isLoading) {
this.isLoading = true;
await this.refreshSchema({ uid: this.connection.uid, schema });
this.addLoadedSchema(schema);
this.isLoading = false;
}
this.changeBreadcrumbs({ schema, table: null });
},
selectTable ({ schema, table }) {
this.newTab({ uid: this.connection.uid, elementName: table.name, schema: this.database.name, type: 'temp-data', elementType: table.type });
this.setBreadcrumbs({ schema, [table.type]: table.name });
},
selectMisc ({ schema, misc, type }) {
const miscTempTabs = {
trigger: 'temp-trigger-props',
triggerFunction: 'temp-trigger-function-props',
function: 'temp-function-props',
routine: 'temp-routine-props',
scheduler: 'temp-scheduler-props'
};
this.newTab({
uid: this.connection.uid,
elementName: misc.name,
schema: this.database.name,
type: miscTempTabs[type],
elementType: type
});
this.setBreadcrumbs({ schema, [type]: misc.name });
},
openDataTab ({ schema, table }) {
this.newTab({ uid: this.connection.uid, elementName: table.name, schema: this.database.name, type: 'data', elementType: table.type });
this.setBreadcrumbs({ schema, [table.type]: table.name });
},
openMiscPermanentTab ({ schema, misc, type }) {
const miscTabs = {
trigger: 'trigger-props',
triggerFunction: 'trigger-function-props',
function: 'function-props',
routine: 'routine-props',
scheduler: 'scheduler-props'
};
this.newTab({
uid: this.connection.uid,
elementName: misc.name,
schema: this.database.name,
type: miscTabs[type],
elementType: type
});
this.setBreadcrumbs({ schema, [type]: misc.name });
},
showSchemaContext (event, schema) {
this.selectSchema(schema);
this.$emit('show-schema-context', { event, schema });
},
showTableContext (event, table) {
this.setBreadcrumbs({ schema: this.database.name, [table.type]: table.name });
this.$emit('show-table-context', { event, table });
this.$emit('show-table-context', { event, schema: this.database.name, table });
},
showMiscContext (event, misc) {
this.setBreadcrumbs({ schema: this.database.name, [misc.type]: misc.name });
this.$emit('show-misc-context', { event, misc });
this.$emit('show-misc-context', { event, schema: this.database.name, misc });
},
showMiscFolderContext (event, type) {
this.$emit('show-misc-folder-context', { event, schema: this.database.name, type });
},
piePercentage (val) {
const perc = val / this.maxSize * 100;
@@ -283,6 +357,8 @@ export default {
this.changeBreadcrumbs(payload);
},
highlightWord (string) {
string = string.replaceAll('<', '&lt;').replaceAll('>', '&gt;');
if (this.searchTerm) {
const regexp = new RegExp(`(${this.searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
return string.replace(regexp, '<span class="text-primary">$1</span>');

View File

@@ -82,13 +82,13 @@
</template>
<div slot="body">
<div class="mb-2">
{{ $t('message.deleteCorfirm') }} "<b>{{ selectedDatabase }}</b>"?
{{ $t('message.deleteCorfirm') }} "<b>{{ selectedSchema }}</b>"?
</div>
</div>
</ConfirmModal>
<ModalEditSchema
v-if="isEditModal"
:selected-database="selectedDatabase"
:selected-schema="selectedSchema"
@close="hideEditModal"
/>
</BaseContextMenu>
@@ -110,7 +110,7 @@ export default {
},
props: {
contextEvent: MouseEvent,
selectedDatabase: String
selectedSchema: String
},
data () {
return {
@@ -173,11 +173,11 @@ export default {
try {
const { status, response } = await Schema.deleteSchema({
uid: this.selectedWorkspace,
database: this.selectedDatabase
database: this.selectedSchema
});
if (status === 'success') {
if (this.selectedDatabase === this.workspace.breadcrumbs.schema)
if (this.selectedSchema === this.workspace.breadcrumbs.schema)
this.changeBreadcrumbs({ schema: null });
this.closeContext();

View File

@@ -3,6 +3,20 @@
:context-event="contextEvent"
@close-context="closeContext"
>
<div
v-if="selectedTable.type === 'table' && workspace.customizations.tableSettings"
class="context-element"
@click="openTableSettingTab"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-tune-vertical-variant text-light pr-1" /> {{ $t('word.settings') }}</span>
</div>
<div
v-if="selectedTable.type === 'view' && workspace.customizations.viewSettings"
class="context-element"
@click="openViewSettingTab"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-tune-vertical-variant text-light pr-1" /> {{ $t('word.settings') }}</span>
</div>
<div
v-if="selectedTable.type === 'table'"
class="context-element"
@@ -72,7 +86,8 @@ export default {
},
props: {
contextEvent: MouseEvent,
selectedTable: Object
selectedTable: Object,
selectedSchema: String
},
data () {
return {
@@ -92,6 +107,8 @@ export default {
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
newTab: 'workspaces/newTab',
removeTabs: 'workspaces/removeTabs',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
}),
showCreateTableModal () {
@@ -112,11 +129,44 @@ export default {
closeContext () {
this.$emit('close-context');
},
openTableSettingTab () {
this.newTab({
uid: this.selectedWorkspace,
elementName: this.selectedTable.name,
schema: this.selectedSchema,
type: 'table-props',
elementType: 'table'
});
this.changeBreadcrumbs({
schema: this.selectedSchema,
table: this.selectedTable.name
});
this.closeContext();
},
openViewSettingTab () {
this.newTab({
uid: this.selectedWorkspace,
elementType: 'table',
elementName: this.selectedTable.name,
schema: this.selectedSchema,
type: 'view-props'
});
this.changeBreadcrumbs({
schema: this.selectedSchema,
view: this.selectedTable.name
});
this.closeContext();
},
async duplicateTable () {
try {
const { status, response } = await Tables.duplicateTable({
uid: this.selectedWorkspace,
table: this.selectedTable.name
table: this.selectedTable.name,
schema: this.selectedSchema
});
if (status === 'success') {
@@ -134,13 +184,11 @@ export default {
try {
const { status, response } = await Tables.truncateTable({
uid: this.selectedWorkspace,
table: this.selectedTable.name
table: this.selectedTable.name,
schema: this.selectedSchema
});
if (status === 'success') {
if (this.selectedTable.name === this.workspace.breadcrumbs.table)
this.changeBreadcrumbs({ table: null });
this.closeContext();
this.$emit('reload');
}
@@ -158,21 +206,27 @@ export default {
if (this.selectedTable.type === 'table') {
res = await Tables.dropTable({
uid: this.selectedWorkspace,
table: this.selectedTable.name
table: this.selectedTable.name,
schema: this.selectedSchema
});
}
else if (this.selectedTable.type === 'view') {
res = await Views.dropView({
uid: this.selectedWorkspace,
view: this.selectedTable.name
view: this.selectedTable.name,
schema: this.selectedSchema
});
}
const { status, response } = res;
if (status === 'success') {
if (this.selectedTable.name === this.workspace.breadcrumbs.table || this.selectedTable.name === this.workspace.breadcrumbs.view)
this.changeBreadcrumbs({ table: null, view: null });
this.removeTabs({
uid: this.selectedWorkspace,
elementName: this.selectedTable.name,
elementType: this.selectedTable.type,
schema: this.selectedSchema
});
this.closeContext();
this.$emit('reload');

View File

@@ -19,8 +19,8 @@
<div class="panel-header pt-0 pl-0">
<div class="d-flex">
<button class="btn btn-dark btn-sm d-flex" @click="addForeign">
<i class="mdi mdi-24px mdi-link-plus mr-1" />
<span>{{ $t('word.add') }}</span>
<i class="mdi mdi-24px mdi-link-plus ml-1" />
</button>
<button
class="btn btn-dark btn-sm d-flex ml-2 mr-0"
@@ -28,8 +28,8 @@
:disabled="!isChanged"
@click.prevent="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button>
</div>
</div>
@@ -267,6 +267,12 @@ export default {
addNotification: 'notifications/addNotification'
}),
confirmForeignsChange () {
this.foreignProxy = this.foreignProxy.filter(foreign =>
foreign.field &&
foreign.refField &&
foreign.table &&
foreign.refTable
);
this.$emit('foreigns-update', this.foreignProxy);
},
selectForeign (event, id) {
@@ -331,6 +337,8 @@ export default {
this.selectedForeignID = this.foreignProxy.length ? this.foreignProxy[0]._id : '';
},
async getRefFields () {
if (!this.selectedForeignObj.refTable) return;
const params = {
uid: this.connection.uid,
schema: this.selectedForeignObj.refSchema,

View File

@@ -19,8 +19,8 @@
<div class="panel-header pt-0 pl-0">
<div class="d-flex">
<button class="btn btn-dark btn-sm d-flex" @click="addParameter">
<i class="mdi mdi-24px mdi-plus mr-1" />
<span>{{ $t('word.add') }}</span>
<i class="mdi mdi-24px mdi-plus ml-1" />
</button>
<button
class="btn btn-dark btn-sm d-flex ml-2 mr-0"
@@ -28,8 +28,8 @@
:disabled="!isChanged"
@click.prevent="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button>
</div>
</div>

View File

@@ -19,8 +19,8 @@
<div class="panel-header pt-0 pl-0">
<div class="d-flex">
<button class="btn btn-dark btn-sm d-flex" @click="addIndex">
<i class="mdi mdi-24px mdi-key-plus mr-1" />
<span>{{ $t('word.add') }}</span>
<i class="mdi mdi-24px mdi-key-plus ml-1" />
</button>
<button
class="btn btn-dark btn-sm d-flex ml-2 mr-0"
@@ -28,8 +28,8 @@
:disabled="!isChanged"
@click.prevent="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button>
</div>
</div>
@@ -186,6 +186,7 @@ export default {
},
methods: {
confirmIndexesChange () {
this.indexesProxy = this.indexesProxy.filter(index => index.fields.length);
this.$emit('indexes-update', this.indexesProxy);
},
selectIndex (event, id) {

View File

@@ -19,8 +19,8 @@
<div class="panel-header pt-0 pl-0">
<div class="d-flex">
<button class="btn btn-dark btn-sm d-flex" @click="addParameter">
<i class="mdi mdi-24px mdi-plus mr-1" />
<span>{{ $t('word.add') }}</span>
<i class="mdi mdi-24px mdi-plus ml-1" />
</button>
<button
class="btn btn-dark btn-sm d-flex ml-2 mr-0"
@@ -28,8 +28,8 @@
:disabled="!isChanged"
@click.prevent="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button>
</div>
</div>

View File

@@ -1,5 +1,5 @@
<template>
<div class="workspace-query-tab column col-12 columns col-gapless">
<div v-show="isSelected" class="workspace-query-tab column col-12 columns col-gapless">
<div class="workspace-query-runner column col-12">
<div class="workspace-query-runner-footer">
<div class="workspace-query-buttons">
@@ -10,46 +10,61 @@
title="CTRL+S"
@click="saveChanges"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span>
<i class="mdi mdi-24px mdi-content-save ml-1" />
</button>
<button
:disabled="!isChanged"
:disabled="!isChanged || isSaving"
class="btn btn-link btn-sm mr-0"
:title="$t('message.clearChanges')"
@click="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button>
<div class="divider-vert py-3" />
<button
:disabled="isSaving"
class="btn btn-dark btn-sm"
:title="$t('message.addNewField')"
@click="addField"
>
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
<span>{{ $t('word.add') }}</span>
<i class="mdi mdi-24px mdi-playlist-plus ml-1" />
</button>
<button
:disabled="isSaving"
class="btn btn-dark btn-sm"
:title="$t('message.manageIndexes')"
@click="showIntdexesModal"
>
<i class="mdi mdi-24px mdi-key mdi-rotate-45 mr-1" />
<span>{{ $t('word.indexes') }}</span>
<i class="mdi mdi-24px mdi-key mdi-rotate-45 ml-1" />
</button>
<button class="btn btn-dark btn-sm" @click="showForeignModal">
<button
class="btn btn-dark btn-sm"
:disabled="isSaving"
@click="showForeignModal"
>
<i class="mdi mdi-24px mdi-key-link mr-1" />
<span>{{ $t('word.foreignKeys') }}</span>
<i class="mdi mdi-24px mdi-key-link ml-1" />
</button>
<button class="btn btn-dark btn-sm" @click="showOptionsModal">
<button
class="btn btn-dark btn-sm"
:disabled="isSaving"
@click="showOptionsModal"
>
<i class="mdi mdi-24px mdi-cogs mr-1" />
<span>{{ $t('word.options') }}</span>
<i class="mdi mdi-24px mdi-cogs ml-1" />
</button>
</div>
<div class="workspace-query-info">
<div class="d-flex" :title="$t('word.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div>
</div>
</div>
</div>
<div class="workspace-query-results column col-12 p-relative">
@@ -126,11 +141,12 @@ export default {
},
props: {
connection: Object,
table: String
isSelected: Boolean,
table: String,
schema: String
},
data () {
return {
tabUid: 'prop',
isLoading: false,
isSaving: false,
isOptionsModal: false,
@@ -157,6 +173,9 @@ export default {
workspace () {
return this.getWorkspace(this.connection.uid);
},
tabUid () {
return this.$vnode.key;
},
tableOptions () {
const db = this.workspace.structure.find(db => db.name === this.schema);
return db && this.table ? db.tables.find(table => table.name === this.table) : {};
@@ -164,12 +183,6 @@ export default {
defaultEngine () {
return this.getDatabaseVariable(this.connection.uid, 'default_storage_engine').value || '';
},
isSelected () {
return this.workspace.selected_tab === 'prop' && this.selectedWorkspace === this.workspace.uid && this.table;
},
schema () {
return this.workspace.breadcrumbs.schema;
},
schemaTables () {
const schemaTables = this.workspace.structure
.filter(schema => schema.name === this.schema)
@@ -185,6 +198,12 @@ export default {
}
},
watch: {
schema () {
if (this.isSelected) {
this.getFieldsData();
this.lastTable = this.table;
}
},
table () {
if (this.isSelected) {
this.getFieldsData();
@@ -192,17 +211,19 @@ export default {
}
},
isSelected (val) {
if (val && this.lastTable !== this.table) {
this.getFieldsData();
this.lastTable = this.table;
if (val) {
this.changeBreadcrumbs({ schema: this.schema, table: this.table });
if (this.lastTable !== this.table)
this.getFieldsData();
}
},
isChanged (val) {
if (this.isSelected && this.lastTable === this.table && this.table !== null)
this.setUnsavedChanges(val);
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
}
},
created () {
this.getFieldsData();
window.addEventListener('keydown', this.onKey);
},
beforeDestroy () {
@@ -213,20 +234,25 @@ export default {
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
setUnsavedChanges: 'workspaces/setUnsavedChanges',
renameTabs: 'workspaces/renameTabs',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
}),
async getFieldsData () {
if (!this.table) return;
this.localFields = [];
this.lastTable = this.table;
this.newFieldsCounter = 0;
this.isLoading = true;
this.localOptions = JSON.parse(JSON.stringify(this.tableOptions));
try {
this.localOptions = JSON.parse(JSON.stringify(this.tableOptions));
}
catch (err) {}
const params = {
uid: this.connection.uid,
schema: this.schema,
table: this.workspace.breadcrumbs.table
table: this.table
};
try { // Columns data
@@ -410,7 +436,7 @@ export default {
const params = {
uid: this.connection.uid,
schema: this.schema,
table: this.workspace.breadcrumbs.table,
table: this.table,
additions,
changes,
deletions,
@@ -428,11 +454,18 @@ export default {
await this.refreshStructure(this.connection.uid);
if (oldName !== this.localOptions.name) {
this.setUnsavedChanges(false);
this.renameTabs({
uid: this.connection.uid,
schema: this.schema,
elementName: oldName,
elementNewName: this.localOptions.name,
elementType: 'table'
});
this.changeBreadcrumbs({ schema: this.schema, table: this.localOptions.name });
}
this.getFieldsData();
else
this.getFieldsData();
}
else
this.addNotification({ status: 'error', message: response });

View File

@@ -1,5 +1,5 @@
<template>
<div class="workspace-query-tab column col-12 columns col-gapless">
<div v-show="isSelected" class="workspace-query-tab column col-12 columns col-gapless">
<div class="workspace-query-runner column col-12">
<div class="workspace-query-runner-footer">
<div class="workspace-query-buttons">
@@ -10,8 +10,8 @@
title="CTRL+S"
@click="saveChanges"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span>
<i class="mdi mdi-24px mdi-content-save ml-1" />
</button>
<button
:disabled="!isChanged"
@@ -19,8 +19,8 @@
:title="$t('message.clearChanges')"
@click="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button>
<div class="divider-vert py-3" />
@@ -30,18 +30,23 @@
:disabled="isChanged"
@click="runFunctionCheck"
>
<i class="mdi mdi-24px mdi-play mr-1" />
<span>{{ $t('word.run') }}</span>
<i class="mdi mdi-24px mdi-play ml-1" />
</button>
<button class="btn btn-dark btn-sm" @click="showParamsModal">
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
<span>{{ $t('word.parameters') }}</span>
<i class="mdi mdi-24px mdi-dots-horizontal ml-1" />
</button>
<button class="btn btn-dark btn-sm" @click="showOptionsModal">
<i class="mdi mdi-24px mdi-cogs mr-1" />
<span>{{ $t('word.options') }}</span>
<i class="mdi mdi-24px mdi-cogs ml-1" />
</button>
</div>
<div class="workspace-query-info">
<div class="d-flex" :title="$t('word.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div>
</div>
</div>
</div>
<div class="workspace-query-results column col-12 mt-2 p-relative">
@@ -102,11 +107,12 @@ export default {
},
props: {
connection: Object,
function: String
function: String,
isSelected: Boolean,
schema: String
},
data () {
return {
tabUid: 'prop',
isLoading: false,
isSaving: false,
isOptionsModal: false,
@@ -127,11 +133,8 @@ export default {
workspace () {
return this.getWorkspace(this.connection.uid);
},
isSelected () {
return this.workspace.selected_tab === 'prop' && this.selectedWorkspace === this.workspace.uid && this.function;
},
schema () {
return this.workspace.breadcrumbs.schema;
tabUid () {
return this.$vnode.key;
},
isChanged () {
return JSON.stringify(this.originalFunction) !== JSON.stringify(this.localFunction);
@@ -150,6 +153,13 @@ export default {
}
},
watch: {
async schema () {
if (this.isSelected) {
await this.getFunctionData();
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
this.lastFunction = this.function;
}
},
async function () {
if (this.isSelected) {
await this.getFunctionData();
@@ -158,26 +168,32 @@ export default {
}
},
async isSelected (val) {
if (val && this.lastFunction !== this.function) {
await this.getFunctionData();
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
this.lastFunction = this.function;
if (val) {
this.changeBreadcrumbs({ schema: this.schema, function: this.function });
setTimeout(() => {
this.resizeQueryEditor();
}, 200);
if (this.lastFunction !== this.function)
this.getRoutineData();
}
},
isChanged (val) {
if (this.isSelected && this.lastFunction === this.function && this.function !== null)
this.setUnsavedChanges(val);
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
}
},
async created () {
await this.getFunctionData();
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
window.addEventListener('keydown', this.onKey);
},
mounted () {
window.addEventListener('resize', this.resizeQueryEditor);
},
destroyed () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
created () {
window.addEventListener('keydown', this.onKey);
},
beforeDestroy () {
window.removeEventListener('keydown', this.onKey);
},
@@ -185,20 +201,22 @@ export default {
...mapActions({
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
setUnsavedChanges: 'workspaces/setUnsavedChanges',
renameTabs: 'workspaces/renameTabs',
newTab: 'workspaces/newTab',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
newTab: 'workspaces/newTab'
setUnsavedChanges: 'workspaces/setUnsavedChanges'
}),
async getFunctionData () {
if (!this.function) return;
this.isLoading = true;
this.localFunction = { sql: '' };
this.lastFunction = this.function;
const params = {
uid: this.connection.uid,
schema: this.schema,
func: this.workspace.breadcrumbs.function
func: this.function
};
try {
@@ -229,9 +247,9 @@ export default {
this.isSaving = true;
const params = {
uid: this.connection.uid,
schema: this.schema,
func: {
...this.localFunction,
schema: this.schema,
oldName: this.originalFunction.name
}
};
@@ -245,11 +263,18 @@ export default {
await this.refreshStructure(this.connection.uid);
if (oldName !== this.localFunction.name) {
this.setUnsavedChanges(false);
this.renameTabs({
uid: this.connection.uid,
schema: this.schema,
elementName: oldName,
elementNewName: this.localFunction.name,
elementType: 'function'
});
this.changeBreadcrumbs({ schema: this.schema, function: this.localFunction.name });
}
this.getFunctionData();
else
this.getFunctionData();
}
else
this.addNotification({ status: 'error', message: response });
@@ -303,7 +328,7 @@ export default {
sql = `SELECT \`${this.originalFunction.name}\` (${params.join(',')})`;
}
this.newTab({ uid: this.connection.uid, content: sql, autorun: true });
this.newTab({ uid: this.connection.uid, content: sql, type: 'query', autorun: true });
},
showOptionsModal () {
this.isOptionsModal = true;

View File

@@ -1,5 +1,5 @@
<template>
<div class="workspace-query-tab column col-12 columns col-gapless">
<div v-show="isSelected" class="workspace-query-tab column col-12 columns col-gapless">
<div class="workspace-query-runner column col-12">
<div class="workspace-query-runner-footer">
<div class="workspace-query-buttons">
@@ -10,8 +10,8 @@
title="CTRL+S"
@click="saveChanges"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span>
<i class="mdi mdi-24px mdi-content-save ml-1" />
</button>
<button
:disabled="!isChanged"
@@ -19,8 +19,8 @@
:title="$t('message.clearChanges')"
@click="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button>
<div class="divider-vert py-3" />
@@ -30,18 +30,23 @@
:disabled="isChanged"
@click="runRoutineCheck"
>
<i class="mdi mdi-24px mdi-play mr-1" />
<span>{{ $t('word.run') }}</span>
<i class="mdi mdi-24px mdi-play ml-1" />
</button>
<button class="btn btn-dark btn-sm" @click="showParamsModal">
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
<span>{{ $t('word.parameters') }}</span>
<i class="mdi mdi-24px mdi-dots-horizontal ml-1" />
</button>
<button class="btn btn-dark btn-sm" @click="showOptionsModal">
<i class="mdi mdi-24px mdi-cogs mr-1" />
<span>{{ $t('word.options') }}</span>
<i class="mdi mdi-24px mdi-cogs ml-1" />
</button>
</div>
<div class="workspace-query-info">
<div class="d-flex" :title="$t('word.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div>
</div>
</div>
</div>
<div class="workspace-query-results column col-12 mt-2 p-relative">
@@ -103,11 +108,12 @@ export default {
},
props: {
connection: Object,
routine: String
routine: String,
isSelected: Boolean,
schema: String
},
data () {
return {
tabUid: 'prop',
isLoading: false,
isSaving: false,
isOptionsModal: false,
@@ -128,11 +134,8 @@ export default {
workspace () {
return this.getWorkspace(this.connection.uid);
},
isSelected () {
return this.workspace.selected_tab === 'prop' && this.selectedWorkspace === this.workspace.uid && this.routine;
},
schema () {
return this.workspace.breadcrumbs.schema;
tabUid () {
return this.$vnode.key;
},
isChanged () {
return JSON.stringify(this.originalRoutine) !== JSON.stringify(this.localRoutine);
@@ -149,6 +152,13 @@ export default {
}
},
watch: {
async schema () {
if (this.isSelected) {
await this.getRoutineData();
this.$refs.queryEditor.editor.session.setValue(this.localRoutine.sql);
this.lastRoutine = this.routine;
}
},
async routine () {
if (this.isSelected) {
await this.getRoutineData();
@@ -157,26 +167,32 @@ export default {
}
},
async isSelected (val) {
if (val && this.lastRoutine !== this.routine) {
await this.getRoutineData();
this.$refs.queryEditor.editor.session.setValue(this.localRoutine.sql);
this.lastRoutine = this.routine;
if (val) {
this.changeBreadcrumbs({ schema: this.schema, routine: this.routine });
setTimeout(() => {
this.resizeQueryEditor();
}, 200);
if (this.lastRoutine !== this.routine)
this.getRoutineData();
}
},
isChanged (val) {
if (this.isSelected && this.lastRoutine === this.routine && this.routine !== null)
this.setUnsavedChanges(val);
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
}
},
async created () {
await this.getRoutineData();
this.$refs.queryEditor.editor.session.setValue(this.localRoutine.sql);
window.addEventListener('keydown', this.onKey);
},
mounted () {
window.addEventListener('resize', this.resizeQueryEditor);
},
destroyed () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
created () {
window.addEventListener('keydown', this.onKey);
},
beforeDestroy () {
window.removeEventListener('keydown', this.onKey);
},
@@ -184,19 +200,22 @@ export default {
...mapActions({
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
setUnsavedChanges: 'workspaces/setUnsavedChanges',
renameTabs: 'workspaces/renameTabs',
newTab: 'workspaces/newTab',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
newTab: 'workspaces/newTab'
setUnsavedChanges: 'workspaces/setUnsavedChanges'
}),
async getRoutineData () {
if (!this.routine) return;
this.localRoutine = { sql: '' };
this.isLoading = true;
this.lastRoutine = this.routine;
const params = {
uid: this.connection.uid,
schema: this.schema,
routine: this.workspace.breadcrumbs.procedure
routine: this.routine
};
try {
@@ -227,9 +246,9 @@ export default {
this.isSaving = true;
const params = {
uid: this.connection.uid,
schema: this.schema,
routine: {
...this.localRoutine,
schema: this.schema,
oldName: this.originalRoutine.name
}
};
@@ -243,11 +262,18 @@ export default {
await this.refreshStructure(this.connection.uid);
if (oldName !== this.localRoutine.name) {
this.setUnsavedChanges(false);
this.renameTabs({
uid: this.connection.uid,
schema: this.schema,
elementName: oldName,
elementNewName: this.localRoutine.name,
elementType: 'procedure'
});
this.changeBreadcrumbs({ schema: this.schema, procedure: this.localRoutine.name });
}
this.getRoutineData();
else
this.getRoutineData();
}
else
this.addNotification({ status: 'error', message: response });
@@ -299,7 +325,7 @@ export default {
sql = `CALL \`${this.originalRoutine.name}\`(${params.join(',')})`;
}
this.newTab({ uid: this.connection.uid, content: sql, autorun: true });
this.newTab({ uid: this.connection.uid, content: sql, type: 'query', autorun: true });
},
showOptionsModal () {
this.isOptionsModal = true;

View File

@@ -1,5 +1,5 @@
<template>
<div class="workspace-query-tab column col-12 columns col-gapless">
<div v-show="isSelected" class="workspace-query-tab column col-12 columns col-gapless">
<div class="workspace-query-runner column col-12">
<div class="workspace-query-runner-footer">
<div class="workspace-query-buttons">
@@ -10,8 +10,8 @@
title="CTRL+S"
@click="saveChanges"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span>
<i class="mdi mdi-24px mdi-content-save ml-1" />
</button>
<button
:disabled="!isChanged"
@@ -19,16 +19,21 @@
:title="$t('message.clearChanges')"
@click="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button>
<div class="divider-vert py-3" />
<button class="btn btn-dark btn-sm" @click="showTimingModal">
<i class="mdi mdi-24px mdi-timer mr-1" />
<span>{{ $t('word.timing') }}</span>
<i class="mdi mdi-24px mdi-timer ml-1" />
</button>
</div>
<div class="workspace-query-info">
<div class="d-flex" :title="$t('word.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div>
</div>
</div>
</div>
<div class="container">
@@ -153,11 +158,12 @@ export default {
},
props: {
connection: Object,
scheduler: String
scheduler: String,
isSelected: Boolean,
schema: String
},
data () {
return {
tabUid: 'prop',
isLoading: false,
isSaving: false,
isTimingModal: false,
@@ -176,11 +182,8 @@ export default {
workspace () {
return this.getWorkspace(this.connection.uid);
},
isSelected () {
return this.workspace.selected_tab === 'prop' && this.selectedWorkspace === this.workspace.uid && this.scheduler;
},
schema () {
return this.workspace.breadcrumbs.schema;
tabUid () {
return this.$vnode.key;
},
isChanged () {
return JSON.stringify(this.originalScheduler) !== JSON.stringify(this.localScheduler);
@@ -197,6 +200,13 @@ export default {
}
},
watch: {
async schema () {
if (this.isSelected) {
await this.getSchedulerData();
this.$refs.queryEditor.editor.session.setValue(this.localScheduler.sql);
this.lastScheduler = this.scheduler;
}
},
async scheduler () {
if (this.isSelected) {
await this.getSchedulerData();
@@ -205,26 +215,32 @@ export default {
}
},
async isSelected (val) {
if (val && this.lastScheduler !== this.scheduler) {
await this.getSchedulerData();
this.$refs.queryEditor.editor.session.setValue(this.localScheduler.sql);
this.lastScheduler = this.scheduler;
if (val) {
this.changeBreadcrumbs({ schema: this.schema, scheduler: this.scheduler });
setTimeout(() => {
this.resizeQueryEditor();
}, 200);
if (this.lastScheduler !== this.scheduler)
this.getSchedulerData();
}
},
isChanged (val) {
if (this.isSelected && this.lastScheduler === this.scheduler && this.scheduler !== null)
this.setUnsavedChanges(val);
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
}
},
async created () {
await this.getSchedulerData();
this.$refs.queryEditor.editor.session.setValue(this.localScheduler.sql);
window.addEventListener('keydown', this.onKey);
},
mounted () {
window.addEventListener('resize', this.resizeQueryEditor);
},
destroyed () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
created () {
window.addEventListener('keydown', this.onKey);
},
beforeDestroy () {
window.removeEventListener('keydown', this.onKey);
},
@@ -232,17 +248,21 @@ export default {
...mapActions({
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
setUnsavedChanges: 'workspaces/setUnsavedChanges',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
renameTabs: 'workspaces/renameTabs',
newTab: 'workspaces/newTab',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
setUnsavedChanges: 'workspaces/setUnsavedChanges'
}),
async getSchedulerData () {
if (!this.scheduler) return;
this.isLoading = true;
this.lastScheduler = this.scheduler;
const params = {
uid: this.connection.uid,
schema: this.schema,
scheduler: this.workspace.breadcrumbs.scheduler
scheduler: this.scheduler
};
try {
@@ -267,9 +287,9 @@ export default {
this.isSaving = true;
const params = {
uid: this.connection.uid,
schema: this.schema,
scheduler: {
...this.localScheduler,
schema: this.schema,
oldName: this.originalScheduler.name
}
};
@@ -283,11 +303,18 @@ export default {
await this.refreshStructure(this.connection.uid);
if (oldName !== this.localScheduler.name) {
this.setUnsavedChanges(false);
this.renameTabs({
uid: this.connection.uid,
schema: this.schema,
elementName: oldName,
elementNewName: this.localScheduler.name,
elementType: 'scheduler'
});
this.changeBreadcrumbs({ schema: this.schema, scheduler: this.localScheduler.name });
}
this.getSchedulerData();
else
this.getSchedulerData();
}
else
this.addNotification({ status: 'error', message: response });

View File

@@ -1,5 +1,5 @@
<template>
<div class="workspace-query-tab column col-12 columns col-gapless">
<div v-show="isSelected" class="workspace-query-tab column col-12 columns col-gapless">
<div class="workspace-query-runner column col-12">
<div class="workspace-query-runner-footer">
<div class="workspace-query-buttons">
@@ -10,8 +10,8 @@
title="CTRL+S"
@click="saveChanges"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span>
<i class="mdi mdi-24px mdi-content-save ml-1" />
</button>
<button
:disabled="!isChanged"
@@ -19,10 +19,15 @@
:title="$t('message.clearChanges')"
@click="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button>
</div>
<div class="workspace-query-info">
<div class="d-flex" :title="$t('word.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div>
</div>
</div>
</div>
<div class="container">
@@ -139,11 +144,12 @@ export default {
},
props: {
connection: Object,
trigger: String
trigger: String,
isSelected: Boolean,
schema: String
},
data () {
return {
tabUid: 'prop',
isLoading: false,
isSaving: false,
originalTrigger: null,
@@ -162,15 +168,12 @@ export default {
workspace () {
return this.getWorkspace(this.connection.uid);
},
tabUid () {
return this.$vnode.key;
},
customizations () {
return this.workspace.customizations;
},
isSelected () {
return this.workspace.selected_tab === 'prop' && this.selectedWorkspace === this.workspace.uid && this.trigger;
},
schema () {
return this.workspace.breadcrumbs.schema;
},
isChanged () {
return JSON.stringify(this.originalTrigger) !== JSON.stringify(this.localTrigger);
},
@@ -186,6 +189,13 @@ export default {
}
},
watch: {
async schema () {
if (this.isSelected) {
await this.getTriggerData();
this.$refs.queryEditor.editor.session.setValue(this.localTrigger.sql);
this.lastTrigger = this.trigger;
}
},
async trigger () {
if (this.isSelected) {
await this.getTriggerData();
@@ -194,26 +204,32 @@ export default {
}
},
async isSelected (val) {
if (val && this.lastTrigger !== this.trigger) {
await this.getTriggerData();
this.$refs.queryEditor.editor.session.setValue(this.localTrigger.sql);
this.lastTrigger = this.trigger;
if (val) {
this.changeBreadcrumbs({ schema: this.schema, trigger: this.trigger });
setTimeout(() => {
this.resizeQueryEditor();
}, 200);
if (this.lastTrigger !== this.trigger)
this.getTriggerData();
}
},
isChanged (val) {
if (this.isSelected && this.lastTrigger === this.trigger && this.trigger !== null)
this.setUnsavedChanges(val);
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
}
},
async created () {
await this.getTriggerData();
this.$refs.queryEditor.editor.session.setValue(this.localTrigger.sql);
window.addEventListener('keydown', this.onKey);
},
mounted () {
window.addEventListener('resize', this.resizeQueryEditor);
},
destroyed () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
created () {
window.addEventListener('keydown', this.onKey);
},
beforeDestroy () {
window.removeEventListener('keydown', this.onKey);
},
@@ -221,8 +237,10 @@ export default {
...mapActions({
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
setUnsavedChanges: 'workspaces/setUnsavedChanges',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
renameTabs: 'workspaces/renameTabs',
newTab: 'workspaces/newTab',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
setUnsavedChanges: 'workspaces/setUnsavedChanges'
}),
async getTriggerData () {
if (!this.trigger) return;
@@ -233,11 +251,12 @@ export default {
this.localTrigger = { sql: '' };
this.isLoading = true;
this.lastTrigger = this.trigger;
const params = {
uid: this.connection.uid,
schema: this.schema,
trigger: this.workspace.breadcrumbs.trigger
trigger: this.trigger
};
try {
@@ -278,9 +297,9 @@ export default {
this.isSaving = true;
const params = {
uid: this.connection.uid,
schema: this.schema,
trigger: {
...this.localTrigger,
schema: this.schema,
oldName: this.originalTrigger.name
}
};
@@ -289,17 +308,24 @@ export default {
const { status, response } = await Triggers.alterTrigger(params);
if (status === 'success') {
const oldName = this.originalTrigger.name;
await this.refreshStructure(this.connection.uid);
if (oldName !== this.localTrigger.name) {
this.setUnsavedChanges(false);
if (this.originalTrigger.name !== this.localTrigger.name) {
const triggerName = this.customizations.triggerTableInName ? `${this.localTrigger.table}.${this.localTrigger.name}` : this.localTrigger.name;
const triggerOldName = this.customizations.triggerTableInName ? `${this.originalTrigger.table}.${this.originalTrigger.name}` : this.originalTrigger.name;
this.renameTabs({
uid: this.connection.uid,
schema: this.schema,
elementName: triggerOldName,
elementNewName: triggerName,
elementType: 'trigger'
});
this.changeBreadcrumbs({ schema: this.schema, trigger: triggerName });
}
this.getTriggerData();
else
this.getTriggerData();
}
else
this.addNotification({ status: 'error', message: response });

View File

@@ -1,5 +1,5 @@
<template>
<div class="workspace-query-tab column col-12 columns col-gapless">
<div v-show="isSelected" class="workspace-query-tab column col-12 columns col-gapless">
<div class="workspace-query-runner column col-12">
<div class="workspace-query-runner-footer">
<div class="workspace-query-buttons">
@@ -10,8 +10,8 @@
title="CTRL+S"
@click="saveChanges"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span>
<i class="mdi mdi-24px mdi-content-save ml-1" />
</button>
<button
:disabled="!isChanged"
@@ -19,15 +19,15 @@
:title="$t('message.clearChanges')"
@click="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button>
<div class="divider-vert py-3" />
<button class="btn btn-dark btn-sm" @click="showOptionsModal">
<i class="mdi mdi-24px mdi-cogs mr-1" />
<span>{{ $t('word.options') }}</span>
<i class="mdi mdi-24px mdi-cogs ml-1" />
</button>
</div>
</div>
@@ -80,11 +80,12 @@ export default {
},
props: {
connection: Object,
function: String
function: String,
isSelected: Boolean,
schema: String
},
data () {
return {
tabUid: 'prop',
isLoading: false,
isSaving: false,
isOptionsModal: false,
@@ -105,11 +106,8 @@ export default {
workspace () {
return this.getWorkspace(this.connection.uid);
},
isSelected () {
return this.workspace.selected_tab === 'prop' && this.selectedWorkspace === this.workspace.uid && this.function;
},
schema () {
return this.workspace.breadcrumbs.schema;
tabUid () {
return this.$vnode.key;
},
isChanged () {
return JSON.stringify(this.originalFunction) !== JSON.stringify(this.localFunction);
@@ -128,6 +126,13 @@ export default {
}
},
watch: {
async schema () {
if (this.isSelected) {
await this.getFunctionData();
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
this.lastFunction = this.function;
}
},
async function () {
if (this.isSelected) {
await this.getFunctionData();
@@ -136,26 +141,32 @@ export default {
}
},
async isSelected (val) {
if (val && this.lastFunction !== this.function) {
await this.getFunctionData();
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
this.lastFunction = this.function;
if (val) {
this.changeBreadcrumbs({ schema: this.schema, triggerFunction: this.function });
setTimeout(() => {
this.resizeQueryEditor();
}, 200);
if (this.lastFunction !== this.function)
await this.getFunctionData();
}
},
isChanged (val) {
if (this.isSelected && this.lastFunction === this.function && this.function !== null)
this.setUnsavedChanges(val);
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
}
},
async created () {
await this.getFunctionData();
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
window.addEventListener('keydown', this.onKey);
},
mounted () {
window.addEventListener('resize', this.resizeQueryEditor);
},
destroyed () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
created () {
window.addEventListener('keydown', this.onKey);
},
beforeDestroy () {
window.removeEventListener('keydown', this.onKey);
},
@@ -163,20 +174,22 @@ export default {
...mapActions({
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
setUnsavedChanges: 'workspaces/setUnsavedChanges',
renameTabs: 'workspaces/renameTabs',
newTab: 'workspaces/newTab',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
newTab: 'workspaces/newTab'
setUnsavedChanges: 'workspaces/setUnsavedChanges'
}),
async getFunctionData () {
if (!this.function) return;
this.isLoading = true;
this.localFunction = { sql: '' };
this.lastFunction = this.function;
const params = {
uid: this.connection.uid,
schema: this.schema,
func: this.workspace.breadcrumbs.triggerFunction
func: this.function
};
try {
@@ -207,9 +220,9 @@ export default {
this.isSaving = true;
const params = {
uid: this.connection.uid,
schema: this.schema,
func: {
...this.localFunction,
schema: this.schema,
oldName: this.originalFunction.name
}
};
@@ -223,11 +236,18 @@ export default {
await this.refreshStructure(this.connection.uid);
if (oldName !== this.localFunction.name) {
this.setUnsavedChanges(false);
this.changeBreadcrumbs({ schema: this.schema, function: this.localFunction.name });
}
this.renameTabs({
uid: this.connection.uid,
schema: this.schema,
elementName: oldName,
elementNewName: this.localFunction.name,
elementType: 'triggerFunction'
});
this.getFunctionData();
this.changeBreadcrumbs({ schema: this.schema, triggerFunction: this.localFunction.name });
}
else
this.getFunctionData();
}
else
this.addNotification({ status: 'error', message: response });
@@ -281,7 +301,7 @@ export default {
sql = `SELECT \`${this.originalFunction.name}\` (${params.join(',')})`;
}
this.newTab({ uid: this.connection.uid, content: sql, autorun: true });
this.newTab({ uid: this.connection.uid, content: sql, type: 'query', autorun: true });
},
showOptionsModal () {
this.isOptionsModal = true;

View File

@@ -1,5 +1,5 @@
<template>
<div class="workspace-query-tab column col-12 columns col-gapless">
<div v-show="isSelected" class="workspace-query-tab column col-12 columns col-gapless">
<div class="workspace-query-runner column col-12">
<div class="workspace-query-runner-footer">
<div class="workspace-query-buttons">
@@ -10,8 +10,8 @@
title="CTRL+S"
@click="saveChanges"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span>
<i class="mdi mdi-24px mdi-content-save ml-1" />
</button>
<button
:disabled="!isChanged"
@@ -19,10 +19,15 @@
:title="$t('message.clearChanges')"
@click="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button>
</div>
<div class="workspace-query-info">
<div class="d-flex" :title="$t('word.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div>
</div>
</div>
</div>
<div class="container">
@@ -161,7 +166,7 @@
<BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ $t('message.selectStatement') }}</label>
<QueryEditor
v-if="isSelected"
v-show="isSelected"
ref="queryEditor"
:value.sync="localView.sql"
:workspace="workspace"
@@ -186,11 +191,12 @@ export default {
},
props: {
connection: Object,
isSelected: Boolean,
schema: String,
view: String
},
data () {
return {
tabUid: 'prop',
isLoading: false,
isSaving: false,
originalView: null,
@@ -208,11 +214,8 @@ export default {
workspace () {
return this.getWorkspace(this.connection.uid);
},
isSelected () {
return this.workspace.selected_tab === 'prop' && this.selectedWorkspace === this.workspace.uid && this.view;
},
schema () {
return this.workspace.breadcrumbs.schema;
tabUid () {
return this.$vnode.key;
},
isChanged () {
return JSON.stringify(this.originalView) !== JSON.stringify(this.localView);
@@ -222,6 +225,13 @@ export default {
}
},
watch: {
async schema () {
if (this.isSelected) {
await this.getViewData();
this.$refs.queryEditor.editor.session.setValue(this.localView.sql);
this.lastView = this.view;
}
},
async view () {
if (this.isSelected) {
await this.getViewData();
@@ -229,27 +239,33 @@ export default {
this.lastView = this.view;
}
},
async isSelected (val) {
if (val && this.lastView !== this.view) {
await this.getViewData();
this.$refs.queryEditor.editor.session.setValue(this.localView.sql);
this.lastView = this.view;
isSelected (val) {
if (val) {
this.changeBreadcrumbs({ schema: this.schema, view: this.view });
setTimeout(() => {
this.resizeQueryEditor();
}, 200);
if (this.lastView !== this.view)
this.getViewData();
}
},
isChanged (val) {
if (this.isSelected && this.lastView === this.view && this.view !== null)
this.setUnsavedChanges(val);
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
}
},
async created () {
await this.getViewData();
this.$refs.queryEditor.editor.session.setValue(this.localView.sql);
window.addEventListener('keydown', this.onKey);
},
mounted () {
window.addEventListener('resize', this.resizeQueryEditor);
},
destroyed () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
created () {
window.addEventListener('keydown', this.onKey);
},
beforeDestroy () {
window.removeEventListener('keydown', this.onKey);
},
@@ -258,17 +274,19 @@ export default {
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
setUnsavedChanges: 'workspaces/setUnsavedChanges',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
renameTabs: 'workspaces/renameTabs'
}),
async getViewData () {
if (!this.view) return;
this.isLoading = true;
this.localView = { sql: '' };
this.lastView = this.view;
const params = {
uid: this.connection.uid,
schema: this.schema,
view: this.workspace.breadcrumbs.view
view: this.view
};
try {
@@ -293,9 +311,9 @@ export default {
this.isSaving = true;
const params = {
uid: this.connection.uid,
schema: this.schema,
view: {
...this.localView,
schema: this.schema,
oldName: this.originalView.name
}
};
@@ -309,11 +327,18 @@ export default {
await this.refreshStructure(this.connection.uid);
if (oldName !== this.localView.name) {
this.setUnsavedChanges(false);
this.renameTabs({
uid: this.connection.uid,
schema: this.schema,
elementName: oldName,
elementNewName: this.localView.name,
elementType: 'view'
});
this.changeBreadcrumbs({ schema: this.schema, view: this.localView.name });
}
this.getViewData();
else
this.getViewData();
}
else
this.addNotification({ status: 'error', message: response });

View File

@@ -100,7 +100,7 @@
</div>
</div>
</div>
<draggable
<Draggable
ref="resultTable"
:list="fields"
class="tbody"
@@ -117,14 +117,14 @@
@contextmenu="contextMenu"
@rename-field="$emit('rename-field', $event)"
/>
</draggable>
</Draggable>
</div>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import draggable from 'vuedraggable';
import Draggable from 'vuedraggable';
import TableRow from '@/components/WorkspacePropsTableRow';
import TableContext from '@/components/WorkspacePropsTableContext';
@@ -133,7 +133,7 @@ export default {
components: {
TableRow,
TableContext,
draggable
Draggable
},
props: {
fields: Array,

View File

@@ -450,7 +450,7 @@ export default {
});
this.defaultValue.onUpdate = this.localRow.onUpdate;
this.defaultValue.type = this.localRow.defaultType;
this.defaultValue.type = this.localRow.defaultType || 'noval';
if (this.defaultValue.type === 'custom') {
this.defaultValue.custom = this.localRow.default
? this.localRow.default.includes('\'')
@@ -458,8 +458,13 @@ export default {
: this.localRow.default
: '';
}
if (this.defaultValue.type === 'expression')
this.defaultValue.expression = this.localRow.default;
else if (this.defaultValue.type === 'expression') {
console.log(this.localRow.default);
if (this.localRow.default.toUpperCase().includes('ON UPDATE'))
this.defaultValue.expression = this.localRow.default.replace(/ on update.*$/i, '');
else
this.defaultValue.expression = this.localRow.default;
}
},
editON (event, content, field) {
if (field === 'length') {

View File

@@ -1,7 +1,7 @@
<template>
<div
v-show="isSelected"
class="workspace-query-tab column col-12 columns col-gapless no-outline"
class="workspace-query-tab column col-12 columns col-gapless no-outline p-0"
tabindex="0"
@keydown.116="runQuery(query)"
@keydown.ctrl.87="clear"
@@ -14,7 +14,7 @@
:auto-focus="true"
:value.sync="query"
:workspace="workspace"
:schema="schema"
:schema="breadcrumbsSchema"
:is-selected="isSelected"
:height="editorHeight"
/>
@@ -28,8 +28,8 @@
title="F5"
@click="runQuery(query)"
>
<i class="mdi mdi-24px mdi-play pr-1" />
<span>{{ $t('word.run') }}</span>
<i class="mdi mdi-24px mdi-play" />
</button>
<button
class="btn btn-dark btn-sm"
@@ -37,8 +37,8 @@
title="CTRL+F8"
@click="beautify()"
>
<i class="mdi mdi-24px mdi-brush pr-1" />
<span>{{ $t('word.format') }}</span>
<i class="mdi mdi-24px mdi-brush pl-1" />
</button>
<button
class="btn btn-link btn-sm"
@@ -46,8 +46,8 @@
title="CTRL+W"
@click="clear()"
>
<i class="mdi mdi-24px mdi-delete-sweep pr-1" />
<span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep pl-1" />
</button>
<div class="divider-vert py-3" />
@@ -58,8 +58,8 @@
class="btn btn-dark btn-sm dropdown-toggle mr-0 pr-0"
tabindex="0"
>
<i class="mdi mdi-24px mdi-file-export mr-1" />
<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">
@@ -86,12 +86,16 @@
<div v-if="affectedCount">
{{ $t('message.affectedRows') }}: <b>{{ affectedCount }}</b>
</div>
<div
v-if="workspace.breadcrumbs.schema"
class="d-flex"
:title="$t('word.schema')"
>
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ workspace.breadcrumbs.schema }}</b>
<div class="input-group" :title="$t('word.schema')">
<i class="input-group-addon addon-sm mdi mdi-24px mdi-database" />
<select v-model="selectedSchema" class="form-select select-sm text-bold">
<option :value="null">
{{ $t('message.noSchema') }}
</option>
<option v-for="schemaName in databaseSchemas" :key="schemaName">
{{ schemaName }}
</option>
</select>
</div>
</div>
</div>
@@ -142,6 +146,7 @@ export default {
lastQuery: '',
isQuering: false,
results: [],
selectedSchema: null,
resultsCount: 0,
durationsCount: 0,
affectedCount: 0,
@@ -156,12 +161,32 @@ export default {
workspace () {
return this.getWorkspace(this.connection.uid);
},
breadcrumbsSchema () {
return this.workspace.breadcrumbs.schema || null;
},
databaseSchemas () {
return this.workspace.structure.reduce((acc, curr) => {
acc.push(curr.name);
return acc;
}, []);
},
isWorkspaceSelected () {
return this.workspace.uid === this.selectedWorkspace;
}
},
watch: {
isSelected (val) {
if (val)
this.changeBreadcrumbs({ schema: this.selectedSchema, query: `Query #${this.tab.index}` });
},
selectedSchema () {
this.changeBreadcrumbs({ schema: this.selectedSchema, query: `Query #${this.tab.index}` });
}
},
created () {
this.query = this.tab.content;
this.selectedSchema = this.tab.schema || this.breadcrumbsSchema;
// this.changeBreadcrumbs({ schema: this.selectedSchema, query: `Query #${this.tab.index}` });
window.addEventListener('keydown', this.onKey);
},
@@ -183,7 +208,9 @@ export default {
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification'
addNotification: 'notifications/addNotification',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
updateTabContent: 'workspaces/updateTabContent'
}),
async runQuery (query) {
if (!query || this.isQuering) return;
@@ -194,7 +221,7 @@ export default {
try { // Query Data
const params = {
uid: this.connection.uid,
schema: this.schema,
schema: this.selectedSchema,
query
};
@@ -205,6 +232,8 @@ export default {
this.resultsCount += this.results.reduce((acc, curr) => acc + (curr.rows ? curr.rows.length : 0), 0);
this.durationsCount += this.results.reduce((acc, curr) => acc + curr.duration, 0);
this.affectedCount += this.results.reduce((acc, curr) => acc + (curr.report ? curr.report.affectedRows : 0), 0);
this.updateTabContent({ uid: this.connection.uid, tab: this.tab.uid, type: 'query', schema: this.selectedSchema, content: query });
}
else
this.addNotification({ status: 'error', message: response });
@@ -303,8 +332,10 @@ export default {
align-items: center;
height: 42px;
.workspace-query-buttons {
.workspace-query-buttons,
.workspace-query-info {
display: flex;
align-items: center;
.btn {
display: flex;
@@ -314,7 +345,7 @@ export default {
}
.workspace-query-info {
display: flex;
overflow: visible;
> div + div {
padding-left: 0.6rem;

View File

@@ -10,6 +10,7 @@
v-if="isContext"
:context-event="contextEvent"
:selected-rows="selectedRows"
:selected-cell="selectedCell"
@show-delete-modal="showDeleteConfirmModal"
@set-null="setNull"
@copy-cell="copyCell"
@@ -71,6 +72,7 @@
:row="row"
:fields="fieldsObj"
:key-usage="keyUsage"
:element-type="elementType"
:class="{'selected': selectedRows.includes(row._id)}"
@select-row="selectRow($event, row._id)"
@update-field="updateField($event, row)"
@@ -123,7 +125,8 @@ export default {
results: Array,
connUid: String,
mode: String,
isSelected: Boolean
isSelected: Boolean,
elementType: { type: String, default: 'table' }
},
data () {
return {
@@ -226,6 +229,9 @@ export default {
},
resultsetIndex () {
this.setLocalResults();
},
isSelected (val) {
if (val) this.refreshScroller();
}
},
updated () {

View File

@@ -28,7 +28,7 @@
</div>
</div>
<div
v-if="selectedRows.length === 1"
v-if="selectedRows.length === 1 && selectedCell.isEditable"
class="context-element"
@click="setNull"
>
@@ -36,7 +36,11 @@
<i class="mdi mdi-18px mdi-null text-light pr-1" /> {{ $t('message.setNull') }}
</span>
</div>
<div class="context-element" @click="showConfirmModal">
<div
v-if="selectedCell.isEditable"
class="context-element"
@click="showConfirmModal"
>
<span class="d-flex">
<i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ $tc('message.deleteRows', selectedRows.length) }}
</span>
@@ -54,7 +58,8 @@ export default {
},
props: {
contextEvent: MouseEvent,
selectedRows: Array
selectedRows: Array,
selectedCell: Object
},
computed: {
},

View File

@@ -260,7 +260,8 @@ export default {
props: {
row: Object,
fields: Object,
keyUsage: Array
keyUsage: Array,
elementType: { type: String, default: 'table' }
},
data () {
return {
@@ -334,6 +335,8 @@ export default {
return this.keyUsage.map(key => key.field);
},
isEditable () {
if (this.elementType === 'view') return false;
if (this.fields) {
const nElements = Object.keys(this.fields).reduce((acc, curr) => {
acc.add(this.fields[curr].table);
@@ -503,10 +506,9 @@ export default {
return this.keyUsage.find(key => key.field === keyName);
},
openContext (event, payload) {
if (this.isEditable) {
payload.field = this.fields[payload.field].name;// Ensures field name only
this.$emit('contextmenu', event, payload);
}
payload.field = this.fields[payload.field].name;// Ensures field name only
payload.isEditable = this.isEditable;
this.$emit('contextmenu', event, payload);
},
onKey (e) {
e.stopPropagation();

View File

@@ -1,5 +1,5 @@
<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 p-0">
<div class="workspace-query-runner column col-12">
<div class="workspace-query-runner-footer">
<div class="workspace-query-buttons">
@@ -11,9 +11,9 @@
title="F5"
@click="reloadTable"
>
<i v-if="!+autorefreshTimer" class="mdi mdi-24px mdi-refresh mr-1" />
<i v-else class="mdi mdi-24px mdi-history mdi-flip-h mr-1" />
<span>{{ $t('word.refresh') }}</span>
<i v-if="!+autorefreshTimer" class="mdi mdi-24px mdi-refresh ml-1" />
<i v-else class="mdi mdi-24px mdi-history mdi-flip-h ml-1" />
</button>
<div class="btn btn-dark btn-sm dropdown-toggle pl-0 pr-0" tabindex="0">
<i class="mdi mdi-24px mdi-menu-down" />
@@ -77,8 +77,8 @@
:disabled="isQuering"
@click="showFakerModal"
>
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
<span>{{ $t('message.tableFiller') }}</span>
<i class="mdi mdi-24px mdi-playlist-plus ml-1" />
</button>
<div class="dropdown table-dropdown pr-2">
@@ -87,8 +87,8 @@
class="btn btn-dark btn-sm dropdown-toggle mr-0 pr-0"
tabindex="0"
>
<i class="mdi mdi-24px mdi-file-export mr-1" />
<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">
@@ -115,8 +115,8 @@
<div v-if="hasApproximately || (page > 1 && tableInfo.rows)">
{{ $t('word.total') }}: <b>{{ tableInfo.rows | localeString }}</b> <small>({{ $t('word.approximately') }})</small>
</div>
<div v-if="workspace.breadcrumbs.database">
{{ $t('word.schema') }}: <b>{{ workspace.breadcrumbs.database }}</b>
<div class="d-flex" :title="$t('word.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div>
</div>
</div>
@@ -131,6 +131,7 @@
:conn-uid="connection.uid"
:is-selected="isSelected"
mode="table"
:element-type="elementType"
@update-field="updateField"
@delete-selected="deleteSelected"
@hard-sort="hardSort"
@@ -181,11 +182,14 @@ export default {
mixins: [tableTabs],
props: {
connection: Object,
table: String
isSelected: Boolean,
table: String,
schema: String,
elementType: String
},
data () {
return {
tabUid: 'data',
tabUid: 'data', // ???
isQuering: false,
isPageMenu: false,
results: [],
@@ -208,9 +212,6 @@ export default {
workspace () {
return this.getWorkspace(this.connection.uid);
},
isSelected () {
return this.workspace.selected_tab === 'data' && this.workspace.uid === this.selectedWorkspace;
},
isTable () {
return !!this.workspace.breadcrumbs.table;
},
@@ -237,6 +238,15 @@ export default {
}
},
watch: {
schema () {
if (this.isSelected) {
this.page = 1;
this.sortParams = {};
this.getTableData();
this.lastTable = this.table;
this.$refs.queryTable.resetSort();
}
},
table () {
if (this.isSelected) {
this.page = 1;
@@ -253,9 +263,11 @@ export default {
}
},
isSelected (val) {
if (val && this.lastTable !== this.table) {
this.getTableData();
this.lastTable = this.table;
if (val) {
this.changeBreadcrumbs({ schema: this.schema, [this.elementType]: this.table });
if (this.lastTable !== this.table)
this.getTableData();
}
}
},
@@ -269,9 +281,10 @@ export default {
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification'
addNotification: 'notifications/addNotification',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
}),
async getTableData (sortParams) {
async getTableData () {
if (!this.table) return;
this.isQuering = true;
@@ -279,13 +292,15 @@ export default {
if (this.lastTable !== this.table)
this.results = [];
this.lastTable = this.table;
const params = {
uid: this.connection.uid,
schema: this.schema,
table: this.workspace.breadcrumbs.table || this.workspace.breadcrumbs.view,
table: this.table,
limit: this.limit,
page: this.page,
sortParams
sortParams: this.sortParams
};
try { // Table data
@@ -306,14 +321,14 @@ export default {
return this.table;
},
reloadTable () {
this.getTableData(this.sortParams);
this.getTableData();
},
hardSort (sortParams) {
this.sortParams = sortParams;
this.getTableData(sortParams);
this.getTableData();
},
openPageMenu () {
if (this.isQuering) return;
if (this.isQuering || (this.results.length && this.results[0].rows.length < this.limit && this.page === 1)) return;
this.isPageMenu = true;
if (this.isPageMenu)

View File

@@ -104,7 +104,8 @@ module.exports = {
database: 'Datenbank',
scratchpad: 'Scratchpad',
array: 'Array',
format: 'Formatierung'
format: 'Formatierung',
sshTunnel: 'SSH Tunnel'
},
message: {
appWelcome: 'Willkommen im Antares SQL Client!',
@@ -210,7 +211,8 @@ module.exports = {
deleteSchema: 'Schema löschen',
markdownSupported: 'Unterstützt Markdown',
plantATree: 'Pflanze einen Baum',
dataTabPageSize: 'Einträge pro Tab / Seite'
dataTabPageSize: 'Einträge pro Tab / Seite',
enableSsh: 'Aktiviere SSH'
},
faker: {
address: 'Adresse',

View File

@@ -106,13 +106,16 @@ module.exports = {
array: 'Array',
changelog: 'Changelog',
format: 'Format',
sshTunnel: 'SSH tunnel',
structure: 'Structure',
small: 'Small',
medium: 'Medium',
large: 'Large',
row: 'Row | Rows',
cell: 'Cell | Cells',
triggerFunction: 'Trigger function | Trigger functions'
triggerFunction: 'Trigger function | Trigger functions',
all: 'All',
duplicate: 'Duplicate'
},
message: {
appWelcome: 'Welcome to Antares SQL Client!',
@@ -166,7 +169,7 @@ module.exports = {
deleteTable: 'Delete table',
emptyCorfirm: 'Do you confirm to empty',
unsavedChanges: 'Unsaved changes',
discardUnsavedChanges: 'You have some unsaved changes. By leaving this tab these changes will be discarded.',
discardUnsavedChanges: 'You have some unsaved changes. Closing this tab these changes will be discarded.',
thereAreNoIndexes: 'There are no indexes',
thereAreNoForeign: 'There are no foreign keys',
createNewForeign: 'Create new foreign key',
@@ -219,8 +222,12 @@ module.exports = {
markdownSupported: 'Markdown supported',
plantATree: 'Plant a Tree',
dataTabPageSize: 'DATA tab page size',
enableSsh: 'Enable SSH',
pageNumber: 'Page number',
duplicateTable: 'Duplicate table'
duplicateTable: 'Duplicate table',
noOpenTabs: 'There are no open tabs, navigate on the left bar or:',
noSchema: 'No schema',
restorePreviourSession: 'Restore previous session'
},
faker: {
address: 'Address',

View File

@@ -93,7 +93,8 @@ module.exports = {
ciphers: 'Chiffrement',
upload: 'Charger',
browse: 'Parcourir',
faker: 'Faker'
faker: 'Faker',
sshTunnel: 'SSH tunnel'
},
message: {
appWelcome: 'Bienvenu sur le client SQL Antares!',
@@ -183,7 +184,8 @@ module.exports = {
preserveOnCompletion: 'Préserver à l\'achèvement',
enableSsl: 'Activer le SSL',
manualValue: 'Valeur manuelle',
tableFiller: 'Remplisseur de table'
tableFiller: 'Remplisseur de table',
enableSsh: 'Activer le SSH'
},
faker: {
address: 'Adresse',

View File

@@ -105,7 +105,8 @@ module.exports = {
scratchpad: 'Blocco appunti',
array: 'Array',
changelog: 'Changelog',
format: 'Formatta'
format: 'Formatta',
sshTunnel: 'SSH tunnel'
},
message: {
appWelcome: 'Benvenuto in Antares SQL Client!',
@@ -210,7 +211,8 @@ module.exports = {
editSchema: 'Modifica schema',
deleteSchema: 'Elimina schema',
markdownSupported: 'Markdown supportato',
plantATree: 'Pianta un albero'
plantATree: 'Pianta un albero',
enableSsh: 'Abilita SSH'
},
faker: {
address: 'Indirizzo',

View File

@@ -105,7 +105,8 @@ module.exports = {
scratchpad: 'Rascunho',
array: 'Array',
changelog: 'Logs de alteração',
format: 'Formato'
format: 'Formato',
sshTunnel: 'SSH túnel'
},
message: {
appWelcome: 'Bem vindo ao Antares SQL Client!',
@@ -210,7 +211,8 @@ module.exports = {
editSchema: 'Editar schema',
deleteSchema: 'Apagar schema',
markdownSupported: 'Markdown suportado',
plantATree: 'Plante uma árvore'
plantATree: 'Plante uma árvore',
enableSsh: 'Habilitar SSH'
},
faker: {
address: 'Endereço',

View File

@@ -0,0 +1,301 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 1024 1024"
style="enable-background:new 0 0 1024 1024;"
xml:space="preserve"
sodipodi:docname="Antares-shape-2.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs80" /><sodipodi:namedview
id="namedview78"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="0.66015625"
inkscape:cx="210.55621"
inkscape:cy="511.2426"
inkscape:window-width="2560"
inkscape:window-height="1009"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="g75" />
<style
type="text/css"
id="style2">
.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
id="g75">
<radialGradient
id="XMLID_15_"
cx="358.2692"
cy="227.2655"
r="830.0055"
gradientUnits="userSpaceOnUse">
<stop
offset="0"
style="stop-color:#F6971E"
id="stop4" />
<stop
offset="0.6338"
style="stop-color:#F4592D"
id="stop6" />
<stop
offset="0.7025"
style="stop-color:#EF4F29"
id="stop8" />
<stop
offset="0.8178"
style="stop-color:#E1351D"
id="stop10" />
<stop
offset="0.9647"
style="stop-color:#CA0B0B"
id="stop12" />
<stop
offset="1"
style="stop-color:#C40006"
id="stop14" />
</radialGradient>
<path
id="XMLID_124_"
style="fill:#ffffff;fill-opacity:0.15000001"
class="st1"
d="M 510.30078 9.0996094 A 502.79999 502.79999 0 0 0 7.5 511.90039 A 502.79999 502.79999 0 0 0 510.30078 1014.6992 A 502.79999 502.79999 0 0 0 1013.0996 511.90039 A 502.79999 502.79999 0 0 0 510.30078 9.0996094 z M 326.17578 78.685547 C 349.74023 78.648438 372.16211 83.875 393.09961 95 C 420.19961 112.9 439.6 136.99922 455 172.69922 C 465.4 205.69922 469.30078 238.79961 465.80078 281.59961 C 457.70078 373.59961 411.09922 469.09961 341.19922 531.59961 C 312.29922 557.49961 284.4 573.59961 256.5 589.59961 L 256.69922 593.19922 C 279.39922 594.99922 301.39922 586.10078 326.19922 571.80078 C 415.89922 516.50078 483.49922 397.69922 504.69922 285.19922 C 495.49922 338.79922 478.19922 391.4 449.69922 445 C 423.79922 489.6 397.30039 527.1 359.40039 562 C 300.00039 612.9 238.89961 638.80078 183.09961 626.30078 C 122.39961 611.00078 90.399609 545.8 92.599609 461 C 92.399609 457.4 92.1 453.89961 89 455.59961 C 87.6 458.29961 84.700781 463.60078 83.300781 466.30078 C 69.000781 517.30078 77.1 562.79922 91 601.19922 C 116.5 666.39922 177.79922 688.69922 244.69922 676.19922 C 204.99922 685.99922 167.69922 683.3 134.19922 665.5 C 107.09922 647.6 84.599609 625.30039 70.599609 586.90039 C 55.899609 537.80039 50.700391 486.89922 70.400391 421.69922 C 95.300391 338.69922 139.50078 255.59922 207.30078 209.19922 C 227.30078 195.79922 245.99922 185.09961 267.69922 172.59961 C 270.79922 170.79961 270.60039 167.20039 268.90039 166.40039 C 159.40039 170.00039 46.500391 333.30039 20.900391 474.40039 C 36.400391 372.60039 87.500391 272.6 167.90039 198.5 C 213.50039 157.4 258.79922 136.89922 305.19922 130.69922 C 386.89922 122.69922 437.10078 194.1 434.80078 299.5 C 435.00078 303.1 436.70039 304.00039 438.40039 304.90039 C 446.50039 281.70039 451.39961 260.29922 451.59961 239.69922 C 451.50293 126.5894 381.21362 66.112098 291.87891 82.363281 C 288.79216 82.977349 285.71662 83.531343 282.59961 84.300781 C 285.71508 83.559319 288.80642 82.922208 291.87891 82.363281 C 303.5351 80.044429 314.99734 78.703151 326.17578 78.685547 z M 858.04883 508.3457 C 873.78027 508.64453 888.6875 512.44922 902.5 520.19922 C 920.3 532.49922 935.10078 547.69961 943.80078 573.59961 C 952.90078 606.59961 955.59961 640.70039 941.59961 683.90039 C 923.79961 739.00039 893.09961 793.80039 847.09961 823.90039 C 833.49961 832.60039 820.89922 839.4 806.19922 847.5 C 804.09922 848.6 804.20078 850.99922 805.30078 851.69922 C 878.70078 851.09922 956.39961 743.59922 975.59961 649.69922 C 963.79961 717.49922 928.2 783.50078 873.5 831.80078 C 842.5 858.60078 811.90078 871.59961 780.80078 875.09961 C 726.10078 879.29961 693.59922 830.9 696.69922 760.5 C 696.59922 758.1 695.50039 757.50039 694.40039 756.90039 C 688.70039 772.30039 685.09961 786.49922 684.59961 800.19922 C 683.15311 870.80636 723.40104 911.63389 777.53906 908.77344 C 757.57984 910.46578 738.70856 907.29881 721.59961 897.69922 C 703.79961 885.39922 691.10039 869.00039 681.40039 844.90039 C 674.90039 822.70039 672.79922 800.6 675.69922 772 C 682.39922 710.7 714.9 647.60078 762.5 606.80078 C 782.1 589.90078 801.00039 579.60078 819.90039 569.30078 L 819.80078 566.90039 C 804.70078 565.40039 789.89961 570.99922 773.09961 580.19922 C 712.39961 615.89922 665.50078 694.2 649.80078 769 C 656.70078 733.4 669.00078 698.39961 688.80078 663.09961 C 706.80078 633.69961 725.00078 609.00078 750.80078 586.30078 C 791.20078 553.20078 832.40039 536.80039 869.40039 545.90039 C 909.80039 556.90039 930.2 600.9 927.5 657.5 C 927.6 659.9 927.70078 662.29961 929.80078 661.09961 C 930.80078 659.29961 932.80078 655.8 933.80078 654 C 944.00078 620.2 939.3 589.70078 930.5 563.80078 C 914.4 519.90078 873.80039 504.1 828.90039 511.5 C 838.87539 509.25 848.60996 508.16641 858.04883 508.3457 z " />
<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"
id="stop18" />
<stop
offset="1"
style="stop-color:#FFFFFF;stop-opacity:0"
id="stop20" />
</linearGradient>
<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"
id="stop24" />
<stop
offset="1"
style="stop-color:#9E3A1D;stop-opacity:0"
id="stop26" />
</linearGradient>
<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"
id="stop30" />
<stop
offset="1"
style="stop-color:#9E3A1D;stop-opacity:0"
id="stop32" />
</linearGradient>
<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"
id="stop36" />
<stop
offset="1"
style="stop-color:#FFFFFF;stop-opacity:0"
id="stop38" />
</linearGradient>
<g
id="XMLID_39_"
class="st10">
<defs
id="defs43">
<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"
ry="502.8"
rx="502.8" />
</defs>
<clipPath
id="XMLID_20_">
<use
xlink:href="#XMLID_36_"
style="overflow:visible;"
id="use45" />
</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"
id="stop49" />
<stop
offset="1"
style="stop-color:#FFD900"
id="stop51" />
</linearGradient>
<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"
id="stop55" />
<stop
offset="1"
style="stop-color:#FFD900"
id="stop57" />
</linearGradient>
</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"
id="stop62" />
<stop
offset="1"
style="stop-color:#FFD900"
id="stop64" />
</linearGradient>
<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"
id="stop68" />
<stop
offset="1"
style="stop-color:#FFD900"
id="stop70" />
</linearGradient>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,301 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 1024 1024"
style="enable-background:new 0 0 1024 1024;"
xml:space="preserve"
sodipodi:docname="Antares-shape-1.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs80" /><sodipodi:namedview
id="namedview78"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="0.66015625"
inkscape:cx="512"
inkscape:cy="511.2426"
inkscape:window-width="2560"
inkscape:window-height="1009"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="g75" />
<style
type="text/css"
id="style2">
.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
id="g75">
<radialGradient
id="XMLID_15_"
cx="358.2692"
cy="227.2655"
r="830.0055"
gradientUnits="userSpaceOnUse">
<stop
offset="0"
style="stop-color:#F6971E"
id="stop4" />
<stop
offset="0.6338"
style="stop-color:#F4592D"
id="stop6" />
<stop
offset="0.7025"
style="stop-color:#EF4F29"
id="stop8" />
<stop
offset="0.8178"
style="stop-color:#E1351D"
id="stop10" />
<stop
offset="0.9647"
style="stop-color:#CA0B0B"
id="stop12" />
<stop
offset="1"
style="stop-color:#C40006"
id="stop14" />
</radialGradient>
<path
id="XMLID_124_"
style="fill:#000000;fill-opacity:0.5"
class="st1"
d="M 510.30078 9.0996094 A 502.79999 502.79999 0 0 0 7.5 511.90039 A 502.79999 502.79999 0 0 0 510.30078 1014.6992 A 502.79999 502.79999 0 0 0 1013.0996 511.90039 A 502.79999 502.79999 0 0 0 510.30078 9.0996094 z M 326.17578 78.685547 C 349.74023 78.648438 372.16211 83.875 393.09961 95 C 420.19961 112.9 439.6 136.99922 455 172.69922 C 465.4 205.69922 469.30078 238.79961 465.80078 281.59961 C 457.70078 373.59961 411.09922 469.09961 341.19922 531.59961 C 312.29922 557.49961 284.4 573.59961 256.5 589.59961 L 256.69922 593.19922 C 279.39922 594.99922 301.39922 586.10078 326.19922 571.80078 C 415.89922 516.50078 483.49922 397.69922 504.69922 285.19922 C 495.49922 338.79922 478.19922 391.4 449.69922 445 C 423.79922 489.6 397.30039 527.1 359.40039 562 C 300.00039 612.9 238.89961 638.80078 183.09961 626.30078 C 122.39961 611.00078 90.399609 545.8 92.599609 461 C 92.399609 457.4 92.1 453.89961 89 455.59961 C 87.6 458.29961 84.700781 463.60078 83.300781 466.30078 C 69.000781 517.30078 77.1 562.79922 91 601.19922 C 116.5 666.39922 177.79922 688.69922 244.69922 676.19922 C 204.99922 685.99922 167.69922 683.3 134.19922 665.5 C 107.09922 647.6 84.599609 625.30039 70.599609 586.90039 C 55.899609 537.80039 50.700391 486.89922 70.400391 421.69922 C 95.300391 338.69922 139.50078 255.59922 207.30078 209.19922 C 227.30078 195.79922 245.99922 185.09961 267.69922 172.59961 C 270.79922 170.79961 270.60039 167.20039 268.90039 166.40039 C 159.40039 170.00039 46.500391 333.30039 20.900391 474.40039 C 36.400391 372.60039 87.500391 272.6 167.90039 198.5 C 213.50039 157.4 258.79922 136.89922 305.19922 130.69922 C 386.89922 122.69922 437.10078 194.1 434.80078 299.5 C 435.00078 303.1 436.70039 304.00039 438.40039 304.90039 C 446.50039 281.70039 451.39961 260.29922 451.59961 239.69922 C 451.50293 126.5894 381.21362 66.112098 291.87891 82.363281 C 288.79216 82.977349 285.71662 83.531343 282.59961 84.300781 C 285.71508 83.559319 288.80642 82.922208 291.87891 82.363281 C 303.5351 80.044429 314.99734 78.703151 326.17578 78.685547 z M 858.04883 508.3457 C 873.78027 508.64453 888.6875 512.44922 902.5 520.19922 C 920.3 532.49922 935.10078 547.69961 943.80078 573.59961 C 952.90078 606.59961 955.59961 640.70039 941.59961 683.90039 C 923.79961 739.00039 893.09961 793.80039 847.09961 823.90039 C 833.49961 832.60039 820.89922 839.4 806.19922 847.5 C 804.09922 848.6 804.20078 850.99922 805.30078 851.69922 C 878.70078 851.09922 956.39961 743.59922 975.59961 649.69922 C 963.79961 717.49922 928.2 783.50078 873.5 831.80078 C 842.5 858.60078 811.90078 871.59961 780.80078 875.09961 C 726.10078 879.29961 693.59922 830.9 696.69922 760.5 C 696.59922 758.1 695.50039 757.50039 694.40039 756.90039 C 688.70039 772.30039 685.09961 786.49922 684.59961 800.19922 C 683.15311 870.80636 723.40104 911.63389 777.53906 908.77344 C 757.57984 910.46578 738.70856 907.29881 721.59961 897.69922 C 703.79961 885.39922 691.10039 869.00039 681.40039 844.90039 C 674.90039 822.70039 672.79922 800.6 675.69922 772 C 682.39922 710.7 714.9 647.60078 762.5 606.80078 C 782.1 589.90078 801.00039 579.60078 819.90039 569.30078 L 819.80078 566.90039 C 804.70078 565.40039 789.89961 570.99922 773.09961 580.19922 C 712.39961 615.89922 665.50078 694.2 649.80078 769 C 656.70078 733.4 669.00078 698.39961 688.80078 663.09961 C 706.80078 633.69961 725.00078 609.00078 750.80078 586.30078 C 791.20078 553.20078 832.40039 536.80039 869.40039 545.90039 C 909.80039 556.90039 930.2 600.9 927.5 657.5 C 927.6 659.9 927.70078 662.29961 929.80078 661.09961 C 930.80078 659.29961 932.80078 655.8 933.80078 654 C 944.00078 620.2 939.3 589.70078 930.5 563.80078 C 914.4 519.90078 873.80039 504.1 828.90039 511.5 C 838.87539 509.25 848.60996 508.16641 858.04883 508.3457 z " />
<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"
id="stop18" />
<stop
offset="1"
style="stop-color:#FFFFFF;stop-opacity:0"
id="stop20" />
</linearGradient>
<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"
id="stop24" />
<stop
offset="1"
style="stop-color:#9E3A1D;stop-opacity:0"
id="stop26" />
</linearGradient>
<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"
id="stop30" />
<stop
offset="1"
style="stop-color:#9E3A1D;stop-opacity:0"
id="stop32" />
</linearGradient>
<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"
id="stop36" />
<stop
offset="1"
style="stop-color:#FFFFFF;stop-opacity:0"
id="stop38" />
</linearGradient>
<g
id="XMLID_39_"
class="st10">
<defs
id="defs43">
<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"
ry="502.8"
rx="502.8" />
</defs>
<clipPath
id="XMLID_20_">
<use
xlink:href="#XMLID_36_"
style="overflow:visible;"
id="use45" />
</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"
id="stop49" />
<stop
offset="1"
style="stop-color:#FFD900"
id="stop51" />
</linearGradient>
<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"
id="stop55" />
<stop
offset="1"
style="stop-color:#FFD900"
id="stop57" />
</linearGradient>
</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"
id="stop62" />
<stop
offset="1"
style="stop-color:#FFD900"
id="stop64" />
</linearGradient>
<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"
id="stop68" />
<stop
offset="1"
style="stop-color:#FFD900"
id="stop70" />
</linearGradient>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -1,11 +1,6 @@
import Tables from '@/ipc-api/Tables';
export default {
computed: {
schema () {
return this.workspace.breadcrumbs.schema;
}
},
methods: {
async updateField (payload) {
this.isQuering = true;

View File

@@ -63,6 +63,12 @@
background-color: $primary-color;
}
}
&.btn-clear {
&:hover {
background: rgba($light-color, 20%);
}
}
}
.modal {
@@ -191,19 +197,8 @@
color: $body-font-color-dark;
}
& &.tools-dropdown {
.tab-link:focus {
color: $primary-color;
}
.menu {
.menu-item a {
&:hover {
color: $primary-color;
background: $bg-color-gray;
}
}
}
&.tools-dropdown {
background-color: $bg-color-light-dark;
}
}
@@ -214,9 +209,17 @@
}
}
.connection-panel {
.panel {
background: rgba($bg-color-light-dark, 50%);
}
}
.bg-checkered {
background-image: linear-gradient(to right, rgba(192, 192, 192, 0.75), rgba(192, 192, 192, 0.75)),
linear-gradient(to right, black 50%, white 50%), linear-gradient(to bottom, black 50%, white 50%);
background-image:
linear-gradient(to right, rgba(192, 192, 192, 0.75), rgba(192, 192, 192, 0.75)),
linear-gradient(to right, black 50%, white 50%),
linear-gradient(to bottom, black 50%, white 50%);
background-blend-mode: normal, difference, normal;
background-size: 2em 2em;
}
@@ -384,11 +387,6 @@
background: rgba(48, 55, 66, 0.95);
color: #fff;
}
&:hover .ex-tooltip-content {
visibility: visible;
opacity: 1;
}
}
#footer {

View File

@@ -168,11 +168,6 @@
background: rgba(48, 55, 66, 0.95);
color: #fff;
}
&:hover .ex-tooltip-content {
visibility: visible;
opacity: 1;
}
}
.workspace {
@@ -191,6 +186,16 @@
}
}
.workspace-tabs {
.tab-block {
.tab-item {
&.tools-dropdown {
background-color: $body-bg;
}
}
}
}
.workspace-query-results {
.table {
.th {
@@ -205,6 +210,12 @@
}
}
.connection-panel {
.panel {
background: rgba($bg-color-light-gray, 100%);
}
}
.context {
color: $body-font-color-dark;

View File

@@ -10,7 +10,8 @@ else
const persistentStore = new Store({
name: 'connections',
encryptionKey: key
encryptionKey: key,
clearInvalidConfig: true
});
export default {
@@ -23,6 +24,7 @@ export default {
getConnections: state => state.connections,
getConnectionName: state => uid => {
const connection = state.connections.filter(connection => connection.uid === uid)[0];
if (!connection) return '';
return connection.name
? connection.name
: connection.ask

View File

@@ -16,7 +16,8 @@ export default {
line_wrap: persistentStore.get('line_wrap', true),
application_theme: persistentStore.get('application_theme', 'dark'),
editor_theme: persistentStore.get('editor_theme', 'twilight'),
editor_font_size: persistentStore.get('editor_font_size', 'medium')
editor_font_size: persistentStore.get('editor_font_size', 'medium'),
restore_tabs: persistentStore.get('restore_tabs', true)
},
getters: {
getLocale: state => state.locale,
@@ -28,7 +29,8 @@ export default {
getLineWrap: state => state.line_wrap,
getApplicationTheme: state => state.application_theme,
getEditorTheme: state => state.editor_theme,
getEditorFontSize: state => state.editor_font_size
getEditorFontSize: state => state.editor_font_size,
getRestoreTabs: state => state.restore_tabs
},
mutations: {
SET_LOCALE (state, locale) {
@@ -71,6 +73,10 @@ export default {
SET_EDITOR_FONT_SIZE (state, size) {
state.editor_font_size = size;
persistentStore.set('editor_font_size', state.editor_font_size);
},
SET_RESTORE_TABS (state, val) {
state.restore_tabs = val;
persistentStore.set('restore_tabs', state.restore_tabs);
}
},
actions: {
@@ -103,6 +109,9 @@ export default {
},
changeEditorFontSize ({ commit }, size) {
commit('SET_EDITOR_FONT_SIZE', size);
},
changeRestoreTabs ({ commit }, size) {
commit('SET_RESTORE_TABS', size);
}
}
};

View File

@@ -1,26 +1,24 @@
'use strict';
import Store from 'electron-store';
import Connection from '@/ipc-api/Connection';
import Schema from '@/ipc-api/Schema';
import Users from '@/ipc-api/Users';
import { uidGen } from 'common/libs/uidGen';
const persistentStore = new Store({ name: 'tabs' });
const tabIndex = [];
let lastBreadcrumbs = {};
export default {
namespaced: true,
strict: true,
state: {
workspaces: [],
selected_workspace: null,
has_unsaved_changes: false,
is_unsaved_discard_modal: false,
pending_breadcrumbs: {}
selected_workspace: null
},
getters: {
getSelected: state => {
if (state.selected_workspace) return state.selected_workspace;
if (state.workspaces.length) return state.workspaces[0].uid;
return null;
return 'NEW';
},
getWorkspace: state => uid => {
return state.workspaces.find(workspace => workspace.uid === uid);
@@ -45,18 +43,27 @@ export default {
},
getSearchTerm: state => uid => {
return state.workspaces.find(workspace => workspace.uid === uid).search_term;
},
isUnsavedDiscardModal: state => {
return state.is_unsaved_discard_modal;
}
},
mutations: {
SELECT_WORKSPACE (state, uid) {
state.selected_workspace = uid;
if (!uid)
state.selected_workspace = state.workspaces.length ? state.workspaces[0].uid : 'NEW';
else
state.selected_workspace = uid;
},
SET_CONNECTED (state, payload) {
const { uid, client, dataTypes, indexTypes, customizations, structure, version } = payload;
const cachedTabs = payload.restoreTabs ? persistentStore.get(uid, []) : [];
if (cachedTabs.length) {
tabIndex[uid] = cachedTabs.reduce((acc, curr) => {
if (curr.index > acc) acc = curr.index;
return acc;
}, null);
}
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid
? {
...workspace,
@@ -66,6 +73,8 @@ export default {
customizations,
structure,
connection_status: 'connected',
tabs: cachedTabs,
selected_tab: cachedTabs.length ? cachedTabs[0].uid : null,
version
}
: workspace);
@@ -175,18 +184,24 @@ export default {
}
: workspace);
},
NEW_TAB (state, { uid, tab, content, autorun }) {
tabIndex[uid] = tabIndex[uid] ? ++tabIndex[uid] : 1;
NEW_TAB (state, { uid, tab, content, type, autorun, schema, elementName, elementType }) {
if (type === 'query')
tabIndex[uid] = tabIndex[uid] ? ++tabIndex[uid] : 1;
const newTab = {
uid: tab,
index: tabIndex[uid],
index: type === 'query' ? tabIndex[uid] : null,
selected: false,
type: 'query',
type,
schema,
elementName,
elementType,
fields: [],
keyUsage: [],
content: content || '',
autorun: !!autorun
};
state.workspaces = state.workspaces.map(workspace => {
if (workspace.uid === uid) {
return {
@@ -197,6 +212,8 @@ export default {
else
return workspace;
});
persistentStore.set(uid, state.workspaces.find(workspace => workspace.uid === uid).tabs);
},
REMOVE_TAB (state, { uid, tab: tUid }) {
state.workspaces = state.workspaces.map(workspace => {
@@ -209,10 +226,78 @@ export default {
else
return workspace;
});
persistentStore.set(uid, state.workspaces.find(workspace => workspace.uid === uid).tabs);
},
REMOVE_TABS (state, { uid, schema, elementName, elementType }) { // Multiple tabs based on schema and element name
if (elementType === 'procedure') elementType = 'routine'; // TODO: pass directly "routine"
state.workspaces = state.workspaces.map(workspace => {
if (workspace.uid === uid) {
return {
...workspace,
tabs: workspace.tabs.filter(tab =>
tab.schema !== schema ||
tab.elementName !== elementName ||
tab.elementType !== elementType
)
};
}
else
return workspace;
});
persistentStore.set(uid, state.workspaces.find(workspace => workspace.uid === uid).tabs);
},
REPLACE_TAB (state, { uid, tab: tUid, type, schema, content, elementName, elementType }) {
state.workspaces = state.workspaces.map(workspace => {
if (workspace.uid === uid) {
return {
...workspace,
tabs: workspace.tabs.map(tab => {
if (tab.uid === tUid)
return { ...tab, type, schema, content, elementName, elementType };
return tab;
})
};
}
else
return workspace;
});
persistentStore.set(uid, state.workspaces.find(workspace => workspace.uid === uid).tabs);
},
RENAME_TABS (state, { uid, schema, elementName, elementType, elementNewName }) {
state.workspaces = state.workspaces.map(workspace => {
if (workspace.uid === uid) {
return {
...workspace,
tabs: workspace.tabs.map(tab => {
if (tab.elementName === elementName && tab.schema === schema) {
return {
...tab,
elementName: elementNewName
};
}
return tab;
})
};
}
else
return workspace;
});
persistentStore.set(uid, state.workspaces.find(workspace => workspace.uid === uid).tabs);
},
SELECT_TAB (state, { uid, tab }) {
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid ? { ...workspace, selected_tab: tab } : workspace);
},
UPDATE_TABS (state, { uid, tabs }) {
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid ? { ...workspace, tabs } : workspace);
persistentStore.set(uid, state.workspaces.find(workspace => workspace.uid === uid).tabs);
},
SET_TAB_FIELDS (state, { cUid, tUid, fields }) {
state.workspaces = state.workspaces.map(workspace => {
if (workspace.uid === cUid) {
@@ -229,6 +314,8 @@ export default {
else
return workspace;
});
persistentStore.set(uid, state.workspaces.find(workspace => workspace.uid === uid).tabs);
},
SET_TAB_KEY_USAGE (state, { cUid, tUid, keyUsage }) {
state.workspaces = state.workspaces.map(workspace => {
@@ -246,15 +333,25 @@ export default {
else
return workspace;
});
persistentStore.set(uid, state.workspaces.find(workspace => workspace.uid === uid).tabs);
},
SET_UNSAVED_CHANGES (state, val) {
state.has_unsaved_changes = !!val;
},
SET_UNSAVED_DISCARD_MODAL (state, val) {
state.is_unsaved_discard_modal = !!val;
},
SET_PENDING_BREADCRUMBS (state, payload) {
state.pending_breadcrumbs = payload;
SET_UNSAVED_CHANGES (state, { uid, tUid, isChanged }) {
state.workspaces = state.workspaces.map(workspace => {
if (workspace.uid === uid) {
return {
...workspace,
tabs: workspace.tabs.map(tab => {
if (tab.uid === tUid)
return { ...tab, isChanged };
return tab;
})
};
}
else
return workspace;
});
},
ADD_LOADED_SCHEMA (state, payload) {
state.workspaces = state.workspaces.map(workspace => {
@@ -268,7 +365,7 @@ export default {
selectWorkspace ({ commit }, uid) {
commit('SELECT_WORKSPACE', uid);
},
async connectWorkspace ({ dispatch, commit }, connection) {
async connectWorkspace ({ dispatch, commit, getters, rootGetters }, connection) {
commit('SET_CONNECTING', connection.uid);
try {
@@ -301,6 +398,7 @@ export default {
if (status === 'error')
dispatch('notifications/addNotification', { status, message: version }, { root: true });
// Check if Maria or MySQL
const isMySQL = version.name.includes('MySQL');
if (isMySQL && connection.client !== 'mysql') {
@@ -321,7 +419,8 @@ export default {
indexTypes,
customizations,
structure: response,
version
version,
restoreTabs: rootGetters['settings/getRestoreTabs']
});
dispatch('refreshCollations', connection.uid);
dispatch('refreshVariables', connection.uid);
@@ -411,7 +510,7 @@ export default {
commit('SET_DISCONNECTED', uid);
commit('SELECT_TAB', { uid, tab: 0 });
},
addWorkspace ({ commit, dispatch, getters }, uid) {
addWorkspace ({ commit }, uid) {
const workspace = {
uid,
connection_status: 'disconnected',
@@ -427,75 +526,211 @@ export default {
};
commit('ADD_WORKSPACE', workspace);
if (getters.getWorkspace(uid).tabs.length < 3)
dispatch('newTab', { uid });
dispatch('setUnsavedChanges', false);
},
changeBreadcrumbs ({ state, commit, getters }, payload) {
if (state.has_unsaved_changes) {
commit('SET_UNSAVED_DISCARD_MODAL', true);
commit('SET_PENDING_BREADCRUMBS', payload);
return;
}
changeBreadcrumbs ({ commit, getters }, payload) {
const breadcrumbsObj = {
schema: null,
table: null,
trigger: null,
triggerFunction: null,
procedure: null,
function: null,
scheduler: null,
view: null
view: null,
query: null
};
const hasLastChildren = Object.keys(lastBreadcrumbs).filter(b => b !== 'schema').some(b => lastBreadcrumbs[b]);
const hasChildren = Object.keys(payload).filter(b => b !== 'schema').some(b => payload[b]);
if (lastBreadcrumbs.schema === payload.schema && hasLastChildren && !hasChildren) return;
if (lastBreadcrumbs.schema !== payload.schema)
Schema.useSchema({ uid: getters.getSelected, schema: payload.schema });
commit('CHANGE_BREADCRUMBS', { uid: getters.getSelected, breadcrumbs: { ...breadcrumbsObj, ...payload } });
lastBreadcrumbs = { ...breadcrumbsObj, ...payload };
if (payload.schema)
commit('ADD_LOADED_SCHEMA', { uid: getters.getSelected, schema: payload.schema });
},
addLoadedSchema ({ commit, getters }, schema) {
commit('ADD_LOADED_SCHEMA', { uid: getters.getSelected, schema });
},
setSearchTerm ({ commit, getters }, term) {
commit('SET_SEARCH_TERM', { uid: getters.getSelected, term });
},
newTab ({ commit }, { uid, content, autorun }) {
const tab = uidGen('T');
newTab ({ state, commit }, { uid, content, type, autorun, schema, elementName, elementType }) {
let tabUid;
const workspaceTabs = state.workspaces.find(workspace => workspace.uid === uid);
commit('NEW_TAB', { uid, tab, content, autorun });
commit('SELECT_TAB', { uid, tab });
switch (type) {
case 'temp-data': {
const existentTab = workspaceTabs
? workspaceTabs.tabs.find(tab =>
tab.schema === schema &&
tab.elementName === elementName &&
tab.elementType === elementType &&
['temp-data', 'data'].includes(tab.type))
: false;
if (existentTab) { // if data tab exists
tabUid = existentTab.uid;
}
else {
const tempTabs = workspaceTabs ? workspaceTabs.tabs.filter(tab => tab.type === 'temp-data') : false;
if (tempTabs && tempTabs.length) { // if temp table already opened
for (const tab of tempTabs) {
commit('REPLACE_TAB', { uid, tab: tab.uid, type, schema, elementName, elementType });
tabUid = tab.uid;
}
}
else {
tabUid = uidGen('T');
commit('NEW_TAB', { uid, tab: tabUid, content, type, autorun, schema, elementName, elementType });
}
}
}
break;
case 'data': {
const existentTab = workspaceTabs
? workspaceTabs.tabs.find(tab =>
tab.schema === schema &&
tab.elementName === elementName &&
tab.elementType === elementType &&
['temp-data', 'data'].includes(tab.type))
: false;
if (existentTab) {
commit('REPLACE_TAB', { uid, tab: existentTab.uid, type, schema, elementName, elementType });
tabUid = existentTab.uid;
}
else {
tabUid = uidGen('T');
commit('NEW_TAB', { uid, tab: tabUid, content, type, autorun, schema, elementName, elementType });
}
}
break;
case 'table-props': {
const existentTab = workspaceTabs
? workspaceTabs.tabs.find(tab =>
tab.elementName === elementName &&
tab.elementType === elementType &&
tab.type === type)
: false;
if (existentTab) {
commit('REPLACE_TAB', { uid, tab: existentTab.uid, type, schema, elementName, elementType });
tabUid = existentTab.uid;
}
else {
tabUid = uidGen('T');
commit('NEW_TAB', { uid, tab: tabUid, content, type, autorun, schema, elementName, elementType });
}
}
break;
case 'temp-trigger-props':
case 'temp-trigger-function-props':
case 'temp-function-props':
case 'temp-routine-props':
case 'temp-scheduler-props': {
const existentTab = workspaceTabs
? workspaceTabs.tabs.find(tab =>
tab.schema === schema &&
tab.elementName === elementName &&
tab.elementType === elementType &&
[type, type.replace('temp-', '')].includes(tab.type))
: false;
if (existentTab) { // if tab exists
tabUid = existentTab.uid;
}
else {
const tempTabs = workspaceTabs ? workspaceTabs.tabs.filter(tab => tab.type.includes('temp-')) : false;
if (tempTabs && tempTabs.length) { // if temp tab already opened
for (const tab of tempTabs) {
if (tab.isChanged) {
commit('REPLACE_TAB', { // make permanent a temp table with unsaved changes
uid,
tab: tab.uid,
type: tab.type.replace('temp-', ''),
schema: tab.schema,
elementName: tab.elementName,
elementType: tab.elementType
});
tabUid = uidGen('T');
commit('NEW_TAB', { uid, tab: tabUid, content, type, autorun, schema, elementName, elementType });
}
else {
commit('REPLACE_TAB', { uid, tab: tab.uid, type, schema, elementName, elementType });
tabUid = tab.uid;
}
}
}
else {
tabUid = uidGen('T');
commit('NEW_TAB', { uid, tab: tabUid, content, type, autorun, schema, elementName, elementType });
}
}
}
break;
case 'trigger-props':
case 'trigger-function-props':
case 'function-props':
case 'routine-props':
case 'scheduler-props': {
const existentTab = workspaceTabs
? workspaceTabs.tabs.find(tab =>
tab.schema === schema &&
tab.elementName === elementName &&
tab.elementType === elementType &&
[`temp-${type}`, type].includes(tab.type))
: false;
if (existentTab) {
commit('REPLACE_TAB', { uid, tab: existentTab.uid, type, schema, elementName, elementType });
tabUid = existentTab.uid;
}
else {
tabUid = uidGen('T');
commit('NEW_TAB', { uid, tab: tabUid, content, type, autorun, schema, elementName, elementType });
}
}
break;
default:
tabUid = uidGen('T');
commit('NEW_TAB', { uid, tab: tabUid, content, type, autorun, schema, elementName, elementType });
break;
}
commit('SELECT_TAB', { uid, tab: tabUid });
},
removeTab ({ commit }, payload) {
checkSelectedTabExists ({ state, commit }, uid) {
const workspace = state.workspaces.find(workspace => workspace.uid === uid);
const isSelectedExistent = workspace
? workspace.tabs.some(tab => tab.uid === workspace.selected_tab)
: false;
if (!isSelectedExistent && workspace.tabs.length)
commit('SELECT_TAB', { uid, tab: workspace.tabs[workspace.tabs.length - 1].uid });
},
updateTabContent ({ commit }, { uid, tab, type, schema, content }) {
commit('REPLACE_TAB', { uid, tab, type, schema, content });
},
renameTabs ({ commit }, payload) {
commit('RENAME_TABS', payload);
},
removeTab ({ commit, dispatch }, payload) {
commit('REMOVE_TAB', payload);
dispatch('checkSelectedTabExists', payload.uid);
},
removeTabs ({ commit, dispatch }, payload) {
commit('REMOVE_TABS', payload);
dispatch('checkSelectedTabExists', payload.uid);
},
selectTab ({ commit }, payload) {
commit('SELECT_TAB', payload);
},
updateTabs ({ commit }, payload) {
commit('UPDATE_TABS', payload);
},
setTabFields ({ commit }, payload) {
commit('SET_TAB_FIELDS', payload);
},
setTabKeyUsage ({ commit }, payload) {
commit('SET_TAB_KEY_USAGE', payload);
},
setUnsavedChanges ({ commit }, val) {
commit('SET_UNSAVED_CHANGES', val);
},
discardUnsavedChanges ({ state, commit, dispatch }) {
dispatch('setUnsavedChanges', false);
dispatch('changeBreadcrumbs', state.pending_breadcrumbs);
commit('SET_UNSAVED_DISCARD_MODAL', false);
commit('SET_PENDING_BREADCRUMBS', {});
},
closeUnsavedChangesModal ({ commit }) {
commit('SET_UNSAVED_DISCARD_MODAL', false);
setUnsavedChanges ({ commit }, payload) {
commit('SET_UNSAVED_CHANGES', payload);
}
}
};