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

Compare commits

..

78 Commits

Author SHA1 Message Date
14c64c537c chore(release): 0.3.2 2021-08-06 14:44:45 +02:00
97b3563e25 chore: update dependencies 2021-08-06 14:38:25 +02:00
e834fe31ac feat(UI): automatic scroll on selected tab 2021-08-05 13:44:48 +02:00
065de3a0a2 feat(UI): query tab name based on content 2021-08-05 13:30:33 +02:00
1573de5b1f feat(UI): button to clear sidebar search input 2021-08-05 12:47:24 +02:00
04fc1bbee0 feat(UI): automatic scroll to selected tab element in left bar 2021-08-05 12:09:54 +02:00
dea378014d perf: approximate table total updated on table refresh 2021-08-04 15:52:26 +02:00
3abff36136 feat: contextual menu option to duplicate table fields 2021-08-04 09:59:50 +02:00
70354aa828 feat(UI): shortcuts info on empty query tab 2021-08-03 17:59:15 +02:00
372049ae64 perf(UI): loading animation on tables and table context menu improvements 2021-08-03 15:43:13 +02:00
5d271be062 chore: update README.md 2021-08-02 19:26:56 +02:00
07ee1ae828 fix: tab selected when clicking closing cross 2021-07-29 16:45:28 +02:00
cbe0e2980a perf: update italian translation 2021-07-27 17:31:51 +02:00
0fba50c6ec chore: update README.md 2021-07-27 17:18:11 +02:00
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
77 changed files with 4185 additions and 1868 deletions

View File

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

View File

@@ -2,6 +2,102 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.3.2](https://github.com/Fabio286/antares/compare/v0.3.1...v0.3.2) (2021-08-06)
### Features
* contextual menu option to duplicate table fields ([3abff36](https://github.com/Fabio286/antares/commit/3abff3613618ddc86e1d6c898f83bcb360e8e5d9))
* **UI:** automatic scroll on selected tab ([e834fe3](https://github.com/Fabio286/antares/commit/e834fe31ac8b73f249e3ad8654e97da834b28cfe))
* **UI:** automatic scroll to selected tab element in left bar ([04fc1bb](https://github.com/Fabio286/antares/commit/04fc1bbee0cdc3b02288ff5d3b2c402633cae1f1))
* **UI:** button to clear sidebar search input ([1573de5](https://github.com/Fabio286/antares/commit/1573de5b1f545328523aaaaac541f5a8046617be))
* **UI:** query tab name based on content ([065de3a](https://github.com/Fabio286/antares/commit/065de3a0a28a9e6667df4e41ac1c5fa5561d2171))
* **UI:** shortcuts info on empty query tab ([70354aa](https://github.com/Fabio286/antares/commit/70354aa828121004f70e91d65cb9f0a4811dce57))
### Bug Fixes
* tab selected when clicking closing cross ([07ee1ae](https://github.com/Fabio286/antares/commit/07ee1ae828bb5f9971a263f2e39e73c760fed50a))
### Improvements
* approximate table total updated on table refresh ([dea3780](https://github.com/Fabio286/antares/commit/dea378014dd45bf90f51a599b4a801e0bc22059f))
* **UI:** loading animation on tables and table context menu improvements ([372049a](https://github.com/Fabio286/antares/commit/372049ae64232e8e61a974edc8be5b319c5c0811))
* update italian translation ([cbe0e29](https://github.com/Fabio286/antares/commit/cbe0e2980a9d4562941aec37a57cc936a46f41d8))
### [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) ## [0.2.0](https://github.com/Fabio286/antares/compare/v0.1.13...v0.2.0) (2021-07-03)

View File

@@ -7,68 +7,78 @@
![GitHub package.json version](https://img.shields.io/github/package-json/v/fabio286/antares) [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Ffabio286%2Fantares%2Fbadge&style=flat)](https://actions-badge.atrox.dev/fabio286/antares/goto) ![GitHub All Releases](https://img.shields.io/github/downloads/fabio286/antares/total) ![GitHub](https://img.shields.io/github/license/fabio286/antares) [![antares](https://snapcraft.io/antares/badge.svg)](https://snapcraft.io/antares) [![antares](https://snapcraft.io/antares/trending.svg?name=0)](https://snapcraft.io/antares) [![Twitter Follow](https://img.shields.io/twitter/follow/AntaresSQL?style=social)](https://twitter.com/AntaresSQL) [![Plant a Tree](https://raw.githubusercontent.com/Fabio286/treedom-badge/master/svg/plant-a-tree.svg)](https://www.treedom.net/en/user/fabio-di-stasio/event/antares-for-the-planet) ![GitHub package.json version](https://img.shields.io/github/package-json/v/fabio286/antares) [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Ffabio286%2Fantares%2Fbadge&style=flat)](https://actions-badge.atrox.dev/fabio286/antares/goto) ![GitHub All Releases](https://img.shields.io/github/downloads/fabio286/antares/total) ![GitHub](https://img.shields.io/github/license/fabio286/antares) [![antares](https://snapcraft.io/antares/badge.svg)](https://snapcraft.io/antares) [![antares](https://snapcraft.io/antares/trending.svg?name=0)](https://snapcraft.io/antares) [![Twitter Follow](https://img.shields.io/twitter/follow/AntaresSQL?style=social)](https://twitter.com/AntaresSQL) [![Plant a Tree](https://raw.githubusercontent.com/Fabio286/treedom-badge/master/svg/plant-a-tree.svg)](https://www.treedom.net/en/user/fabio-di-stasio/event/antares-for-the-planet)
Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers. 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. Our target is to support as many databases as possible, and all major operating systems, including the ARM versions.
**At the moment this application is in development state, many features will come in future updates**, and supports only MySQL/MariaDB and PostgreSQL (partially). **At the moment this application is in development state, many features will come in future updates**, and supports only MySQL/MariaDB and PostgreSQL.
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. 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.
I'm actively working on it, hoping to provide cool features and fixes as soon as possible. We are actively working on it, hoping to provide new cool features, improvements and fixes as soon as possible.
🔗 If you are curious to try Antares you can download and install the [latest release](https://github.com/Fabio286/antares/releases/latest). 🔗 If you are curious to try Antares you can download and install the [latest release](https://github.com/Fabio286/antares/releases/latest).
👁 To stay tuned for new releases [follow Antares SQL](https://twitter.com/AntaresSQL) on Twitter. 👁 To stay tuned for new releases [follow Antares SQL](https://twitter.com/AntaresSQL) on Twitter.
🌟 Don't forget to **leave a star** if you appreciate this project. 🌟 Don't forget to **leave a star** if you appreciate this project.
## Philosophy ## Current key features
Why am I developing an SQL client when there are a lot of them on the market?
The main goal is to develop a totally free, full featured, cross platform and open source alternative, empowered by JavaScript's ecosystem.
A modern application created with minimalism and semplicity in mind, with features in the right places, not hundreds of tiny buttons, tabs or submenu.
## Download
[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/antares) [![Get it from AUR](https://raw.githubusercontent.com/Fabio286/antares/3e00c4bae6e036300c752c1a40c5a038fea9c169/docs/aur-badge.svg)](https://aur.archlinux.org/packages/antares-sql/) [![Get it from Microsoft Store](https://raw.githubusercontent.com/Fabio286/antares/gh-pages/src/assets/ms-store.png)](https://www.microsoft.com/p/antares-sql-client/9nhtb9sq51r1?cid=storebadge&ocid=badge&rtc=1&activetab=pivot:overviewtab)
🚀 **[Other Downloads](https://github.com/Fabio286/antares/releases/latest)**
## How to contribute
- 🌍 [Translate Antares](https://github.com/Fabio286/antares/wiki/Translate-Antares)
- 📖 [Contributors Guide](https://github.com/Fabio286/antares/wiki/Contributors-Guide)
- 🚧 [Project Board](https://github.com/users/Fabio286/projects/1)
## Current main features
- Multiple database connections at same time. - Multiple database connections at same time.
- Database management (add/edit/delete). - Database management (add/edit/delete).
- Full tables management, including indexes and foreign keys. - Full tables management, including indexes and foreign keys.
- Views, triggers, stored routines, functions and schedulers management (add/edit/delete). - Views, triggers, stored routines, functions and schedulers management (add/edit/delete).
- Fake table data filler. - A modern and friendly tab system; keep open every kind of tab you need in your workspace.
- Run queries on multiple tabs. - Fake table data filler to generate tons of data for test purpose.
- Query suggestions and auto complete. - Query suggestions and auto complete.
- SSH tunnel support.
- Dark and light theme. - Dark and light theme.
- Editor themes.
- Scratchpad. - Scratchpad.
- Multi language.
- Secure password storage. - Secure password storage.
## Philosophy
Why are we developing an SQL client when there are a lot of them on the market?
The main goal is to develop a totally free, full featured, cross platform and open source alternative, empowered by JavaScript's ecosystem.
A modern application created with minimalism and semplicity in mind, with features in the right places, not hundreds of tiny buttons, nested tabs or submenu; productivity comes first.
## Installation
Based on your operating system you can have one or more distribution formats to choose based on your preferences.
Since Antares SQL is a free software we haven't a budget to spend in annual licenses or certificates. This can result that on some platforms you need some additional passages to install this app.
### Linux
On Linux you can simply download and run `.AppImage` distributions, install from Snap Store or from AUR.
### Windows
On Windows you can choose between Microsoft Store and download `.exe` distribution. The latter lacks of a certificate, so to install you need to click on "More info" and then "Run anyway" on SmartScreen prompt.
### MacOS
On macOS you can run `.dmg` distribution following [this guide](https://support.apple.com/guide/mac-help/mh40616/mac) to install apps from unknown developers.
## Download
[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/antares) [![Get it from AUR](https://raw.githubusercontent.com/Fabio286/antares/3e00c4bae6e036300c752c1a40c5a038fea9c169/docs/aur-badge.svg)](https://aur.archlinux.org/packages/antares-sql/) [![Get it from Microsoft Store](https://raw.githubusercontent.com/Fabio286/antares/gh-pages/src/assets/ms-store.png)](https://www.microsoft.com/p/antares-sql-client/9nhtb9sq51r1?cid=storebadge&ocid=badge&rtc=1&activetab=pivot:overviewtab)
🚀 **[Other Downloads](https://github.com/Fabio286/antares/releases/latest)**
## Coming soon ## Coming soon
This is a roadmap with major features will come in near future. This is a roadmap with major features will come in near future.
- Support for other databases. - Support for other databases.
- Database tools. - Database tools.
- SSH tunnel support.
- Users management (add/edit/delete). - Users management (add/edit/delete).
- UI/UX improvements. - Query history and bookmarks.
- Query history.
- More context menu shortcuts. - More context menu shortcuts.
- More keyboard shortcuts. - More keyboard shortcuts.
- Query logs console.
- Import/export and migration. - Import/export and migration.
- Apple Silicon distribution
## Currently supported ## Currently supported
### Databases ### Databases
- [x] MySQL/MariaDB - [x] MySQL/MariaDB
- [x] PostgreSQL (partially, work in progress) - [x] PostgreSQL
- [ ] SQLite - [ ] SQLite
- [ ] MSSQL - [ ] MSSQL
- [ ] OracleDB - [ ] OracleDB
@@ -80,7 +90,7 @@ This is a roadmap with major features will come in near future.
- [x] Windows - [x] Windows
- [x] Linux - [x] Linux
- [x] MacOS (not tested due lack of hardware) - [x] MacOS
#### • ARM #### • ARM
@@ -88,15 +98,17 @@ This is a roadmap with major features will come in near future.
- [x] Linux - [x] Linux
- [ ] MacOS - [ ] MacOS
## How to contribute
- 🌍 [Translate Antares](https://github.com/Fabio286/antares/wiki/Translate-Antares)
- 📖 [Contributors Guide](https://github.com/Fabio286/antares/wiki/Contributors-Guide)
- 🚧 [Project Board](https://github.com/users/Fabio286/projects/1)
## Translations ## Translations
**Italian Translation** / [Giuseppe Gigliotti](https://github.com/ReverbOD) [[#20](https://github.com/Fabio286/antares/pull/20)] **Italian Translation** / [Giuseppe Gigliotti](https://github.com/ReverbOD) [[#20](https://github.com/Fabio286/antares/pull/20)]
**Arabic Translation** / [Mohd-PH](https://github.com/Mohd-PH) [[#29](https://github.com/Fabio286/antares/pull/29)] **Arabic Translation** (needs updates) / [Mohd-PH](https://github.com/Mohd-PH) [[#29](https://github.com/Fabio286/antares/pull/29)]
**Spanish Translation** / [hongkfui](https://github.com/hongkfui) [[#32](https://github.com/Fabio286/antares/pull/32)] **Spanish Translation** (needs updates) / [hongkfui](https://github.com/hongkfui) [[#32](https://github.com/Fabio286/antares/pull/32)]
**French Translation** / [MrAnyx](https://github.com/MrAnyx) [[#44](https://github.com/Fabio286/antares/pull/44)] **French Translation** (needs updates) / [MrAnyx](https://github.com/MrAnyx) [[#44](https://github.com/Fabio286/antares/pull/44)]
**Portugues (Brasil)** / [Daniel Eduardo](https://github.com/daeleduardo) [[#54](https://github.com/Fabio286/antares/pull/54)] **Portugues (Brasil)** / [Daniel Eduardo](https://github.com/daeleduardo) [[#54](https://github.com/Fabio286/antares/pull/54)]
**Deutsch (Deutschland)** / [Christian Ratz](https://github.com/digitalgopnik) [[#74](https://github.com/Fabio286/antares/pull/74)] **Deutsch (Deutschland)** / [Christian Ratz](https://github.com/digitalgopnik) [[#74](https://github.com/Fabio286/antares/pull/74)]
## Reviews
<a target="_blank" href="https://www.softx64.com/windows/antares-sql-client.html" title="Antares SQL Client review"><img src="https://www.softx64.com/softx64-review.png" alt="Antares SQL Client review" /></a>

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", "name": "antares",
"productName": "Antares", "productName": "Antares",
"version": "0.2.0", "version": "0.3.2",
"description": "A cross-platform easy to use SQL client.", "description": "A cross-platform easy to use SQL client.",
"license": "MIT", "license": "MIT",
"repository": "https://github.com/Fabio286/antares.git", "repository": "https://github.com/Fabio286/antares.git",
@@ -87,46 +87,47 @@
} }
}, },
"dependencies": { "dependencies": {
"@electron/remote": "^1.2.0", "@electron/remote": "^1.2.1",
"@mdi/font": "^5.9.55", "@mdi/font": "^5.9.55",
"ace-builds": "^1.4.12", "ace-builds": "^1.4.12",
"electron-log": "^4.3.5", "electron-log": "^4.4.1",
"electron-store": "^8.0.0", "electron-store": "^8.0.0",
"electron-updater": "^4.3.9", "electron-updater": "^4.3.9",
"faker": "^5.5.3", "faker": "^5.5.3",
"marked": "^2.1.1", "marked": "^2.1.1",
"moment": "^2.29.1", "moment": "^2.29.1",
"mysql2": "^2.2.5", "mysql2": "^2.3.0",
"pg": "^8.5.1", "pg": "^8.7.1",
"pgsql-ast-parser": "^7.2.1", "pgsql-ast-parser": "^7.2.1",
"source-map-support": "^0.5.16", "source-map-support": "^0.5.16",
"spectre.css": "^0.5.9", "spectre.css": "^0.5.9",
"sql-formatter": "^4.0.2", "sql-formatter": "^4.0.2",
"ssh2-promise": "^0.2.0",
"v-mask": "^2.2.4", "v-mask": "^2.2.4",
"vue-i18n": "^8.24.4", "vue-i18n": "^8.24.4",
"vuedraggable": "^2.24.3", "vuedraggable": "^2.24.3",
"vuex": "^3.6.2" "vuex": "^3.6.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/eslint-parser": "^7.14.5", "@babel/eslint-parser": "^7.15.0",
"cross-env": "^7.0.2", "cross-env": "^7.0.2",
"electron": "^13.1.2", "electron": "^13.1.8",
"electron-builder": "^22.11.7", "electron-builder": "^22.11.7",
"electron-devtools-installer": "^3.2.0", "electron-devtools-installer": "^3.2.0",
"electron-webpack": "^2.8.2", "electron-webpack": "^2.8.2",
"electron-webpack-vue": "^2.4.0", "electron-webpack-vue": "^2.4.0",
"eslint": "^7.29.0", "eslint": "^7.32.0",
"eslint-config-standard": "^16.0.3", "eslint-config-standard": "^16.0.3",
"eslint-plugin-import": "^2.23.4", "eslint-plugin-import": "^2.23.4",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0", "eslint-plugin-promise": "^5.1.0",
"eslint-plugin-vue": "^7.11.1", "eslint-plugin-vue": "^7.15.1",
"sass": "^1.35.1", "sass": "^1.37.5",
"sass-loader": "^10.2.0", "sass-loader": "^10.2.0",
"standard-version": "^9.3.0", "standard-version": "^9.3.1",
"stylelint": "^13.13.1", "stylelint": "^13.13.1",
"stylelint-config-standard": "^22.0.0", "stylelint-config-standard": "^22.0.0",
"stylelint-scss": "^3.19.0", "stylelint-scss": "^3.20.1",
"vue": "^2.6.14", "vue": "^2.6.14",
"vue-template-compiler": "^2.6.14", "vue-template-compiler": "^2.6.14",
"webpack": "^4.46.0" "webpack": "^4.46.0"

View File

@@ -4,9 +4,7 @@ summary: Open source SQL client made to be simple and complete.
description: | description: |
Antares is an SQL client that aims to become an useful and complete tool, especially for developers. Antares is an SQL client that aims to become an useful and complete tool, especially for developers.
The target is to support as many databases as possible, and all major operating systems, including the ARM versions. The target is to support as many databases as possible, and all major operating systems, including the ARM versions.
At the moment this application is an alpha and supports only MySQL and x86 architecture. base: core20
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
grade: stable grade: stable
confinement: strict confinement: strict
@@ -60,6 +58,7 @@ parts:
- fcitx-frontend-gtk3 - fcitx-frontend-gtk3
- libappindicator3-1 - libappindicator3-1
- libasound2 - libasound2
- libcurl4
- libgconf-2-4 - libgconf-2-4
- libgtk-3-0 - libgtk-3-0
- libnotify4 - libnotify4
@@ -71,13 +70,59 @@ parts:
- libsecret-1-0 - libsecret-1-0
- libxtst6 - libxtst6
- libxkbfile1 - 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: cleanup:
after: [antares] after: [antares]
plugin: nil plugin: nil
build-snaps: [gnome-3-28-1804] build-snaps: [gnome-3-38-2004]
override-prime: | override-prime: |
set -eux 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/{} \; find . -type f,l -exec rm -f $SNAPCRAFT_PRIME/{} \;
mdns-lookup: mdns-lookup:
@@ -91,7 +136,7 @@ parts:
- libnss-mdns - libnss-mdns
override-prime: | override-prime: |
set -eux set -eux
sed -Ee 's/^\s*hosts:(\s+)files/hosts:\1files mdns4_minimal \[NOTFOUND=return\]/' /snap/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 snapcraftctl prime
prime: prime:
- lib/$SNAPCRAFT_ARCH_TRIPLET/libnss_mdns4_minimal* - lib/$SNAPCRAFT_ARCH_TRIPLET/libnss_mdns4_minimal*
@@ -101,7 +146,7 @@ apps:
antares: antares:
command: opt/Antares/antares --no-sandbox command: opt/Antares/antares --no-sandbox
desktop: usr/share/applications/antares.desktop desktop: usr/share/applications/antares.desktop
extensions: [gnome-3-28] extensions: [gnome-3-38]
environment: environment:
# Fallback to XWayland if running in a Wayland session. # Fallback to XWayland if running in a Wayland session.
DISABLE_WAYLAND: 1 DISABLE_WAYLAND: 1

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
'use strict'; 'use strict';
import { app, BrowserWindow, /* session, */ nativeImage } from 'electron'; import { app, BrowserWindow, /* session, */ nativeImage, Menu } from 'electron';
import * as path from 'path'; import * as path from 'path';
import Store from 'electron-store'; import Store from 'electron-store';
@@ -96,6 +96,7 @@ else {
// create main BrowserWindow when electron is ready // create main BrowserWindow when electron is ready
app.on('ready', async () => { app.on('ready', async () => {
mainWindow = await createMainWindow(); mainWindow = await createMainWindow();
Menu.setApplicationMenu(null);
if (isDevelopment) if (isDevelopment)
mainWindow.webContents.openDevTools(); mainWindow.webContents.openDevTools();
}); });

View File

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

View File

@@ -128,7 +128,12 @@ export default connections => {
if (!query) return; if (!query) return;
try { 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 }; return { status: 'success', response: result };
} }

View File

@@ -29,7 +29,7 @@ export default (connections) => {
if (sortParams && sortParams.field && sortParams.dir) if (sortParams && sortParams.field && sortParams.dir)
query.orderBy({ [sortParams.field]: sortParams.dir.toUpperCase() }); 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 }; return { status: 'success', response: result };
} }
@@ -38,6 +38,16 @@ export default (connections) => {
} }
}); });
ipcMain.handle('get-table-count', async (event, params) => {
try {
const result = await connections[params.uid].getTableApproximateCount(params);
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('get-table-indexes', async (event, params) => { ipcMain.handle('get-table-indexes', async (event, params) => {
try { try {
const result = await connections[params.uid].getTableIndexes(params); const result = await connections[params.uid].getTableIndexes(params);

View File

@@ -12,6 +12,12 @@ export class ClientsFactory {
* @param {String} args.params.host * @param {String} args.params.host
* @param {Number} args.params.port * @param {Number} args.params.port
* @param {String} args.params.password * @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 * @param {Number=} args.poolSize
* @returns Database Connection * @returns Database Connection
* @memberof ClientsFactory * @memberof ClientsFactory

View File

@@ -2,6 +2,7 @@
import mysql from 'mysql2/promise'; import mysql from 'mysql2/promise';
import { AntaresCore } from '../AntaresCore'; import { AntaresCore } from '../AntaresCore';
import dataTypes from 'common/data-types/mysql'; import dataTypes from 'common/data-types/mysql';
import * as SSH2Promise from 'ssh2-promise';
export class MySQLClient extends AntaresCore { export class MySQLClient extends AntaresCore {
constructor (args) { constructor (args) {
@@ -104,11 +105,33 @@ export class MySQLClient extends AntaresCore {
async connect () { async connect () {
delete this._params.application_name; 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) if (!this._poolSize)
this._connection = await mysql.createConnection(this._params); this._connection = await mysql.createConnection(dbConfig);
else { else {
this._connection = mysql.createPool({ this._connection = mysql.createPool({
...this._params, ...dbConfig,
connectionLimit: this._poolSize, connectionLimit: this._poolSize,
typeCast: (field, next) => { typeCast: (field, next) => {
if (field.type === 'DATETIME') if (field.type === 'DATETIME')
@@ -125,6 +148,7 @@ export class MySQLClient extends AntaresCore {
*/ */
destroy () { destroy () {
this._connection.end(); this._connection.end();
if (this._ssh) this._ssh.close();
} }
/** /**
@@ -145,6 +169,12 @@ export class MySQLClient extends AntaresCore {
*/ */
async getStructure (schemas) { async getStructure (schemas) {
const { rows: databases } = await this.raw('SHOW DATABASES'); 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: functions } = await this.raw('SHOW FUNCTION STATUS');
const { rows: procedures } = await this.raw('SHOW PROCEDURE STATUS'); const { rows: procedures } = await this.raw('SHOW PROCEDURE STATUS');
const { rows: schedulers } = await this.raw('SELECT *, EVENT_SCHEMA AS `Db`, EVENT_NAME AS `Name` FROM information_schema.`EVENTS`'); 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 tablesArr = [];
const triggersArr = []; const triggersArr = [];
for (const db of databases) { for (const db of filteredDatabases) {
if (!schemas.has(db.Database)) continue; if (!schemas.has(db.Database)) continue;
let { rows: tables } = await this.raw(`SHOW TABLE STATUS FROM \`${db.Database}\``); 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)) { if (schemas.has(db.Database)) {
// TABLES // TABLES
const remappedTables = tablesArr.filter(table => table.Db === db.Database).map(table => { const remappedTables = tablesArr.filter(table => table.Db === db.Database).map(table => {
@@ -305,11 +335,11 @@ export class MySQLClient extends AntaresCore {
.select('*') .select('*')
.schema('information_schema') .schema('information_schema')
.from('COLUMNS') .from('COLUMNS')
.where({ TABLE_SCHEMA: `= '${this._schema || schema}'`, TABLE_NAME: `= '${table}'` }) .where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'` })
.orderBy({ ORDINAL_POSITION: 'ASC' }) .orderBy({ ORDINAL_POSITION: 'ASC' })
.run(); .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 => { const remappedFields = fields.map(row => {
if (!row['Create Table']) return false; if (!row['Create Table']) return false;
@@ -333,15 +363,14 @@ export class MySQLClient extends AntaresCore {
const details = fieldArr.slice(2).join(' '); const details = fieldArr.slice(2).join(' ');
let defaultValue = null; let defaultValue = null;
if (details.includes('DEFAULT')) { if (details.includes('DEFAULT'))
defaultValue = details.match(/(?<=DEFAULT ).*?$/gs)[0].split(' COMMENT')[0]; defaultValue = details.match(/(?<=DEFAULT ).*?$/gs)[0].split(' COMMENT')[0];
const defaultValueArr = defaultValue.split(''); // const defaultValueArr = defaultValue.split('');
if (defaultValueArr[0] === '\'') { // if (defaultValueArr[0] === '\'') {
defaultValueArr.shift(); // defaultValueArr.shift();
defaultValueArr.pop(); // defaultValueArr.pop();
defaultValue = defaultValueArr.join(''); // defaultValue = defaultValueArr.join('');
} // }
}
const typeAndLength = nameAndType[1].replace(')', '').split('('); const typeAndLength = nameAndType[1].replace(')', '').split('(');
@@ -395,6 +424,19 @@ export class MySQLClient extends AntaresCore {
}); });
} }
/**
* @param {Object} params
* @param {String} params.schema
* @param {String} params.table
* @returns {Object} table row count
* @memberof MySQLClient
*/
async getTableApproximateCount ({ schema, table }) {
const { rows } = await this.raw(`SELECT table_rows "count" FROM information_schema.tables WHERE table_name = "${table}" AND table_schema = "${schema}"`);
return rows.length ? rows[0].count : 0;
}
/** /**
* @param {Object} params * @param {Object} params
* @param {String} params.schema * @param {String} params.schema
@@ -544,7 +586,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async dropView (params) { async dropView (params) {
const sql = `DROP VIEW \`${this._schema}\`.\`${params.view}\``; const sql = `DROP VIEW \`${params.schema}\`.\`${params.view}\``;
return await this.raw(sql); return await this.raw(sql);
} }
@@ -556,10 +598,10 @@ export class MySQLClient extends AntaresCore {
*/ */
async alterView (params) { async alterView (params) {
const { view } = 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) 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); return await this.raw(sql);
} }
@@ -570,8 +612,8 @@ export class MySQLClient extends AntaresCore {
* @returns {Array.<Object>} parameters * @returns {Array.<Object>} parameters
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async createView (view) { async createView (params) {
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` : ''}`; 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); return await this.raw(sql);
} }
@@ -604,7 +646,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async dropTrigger (params) { async dropTrigger (params) {
const sql = `DROP TRIGGER \`${this._schema}\`.\`${params.trigger}\``; const sql = `DROP TRIGGER \`${params.schema}\`.\`${params.trigger}\``;
return await this.raw(sql); return await this.raw(sql);
} }
@@ -621,8 +663,8 @@ export class MySQLClient extends AntaresCore {
try { try {
await this.createTrigger(tempTrigger); await this.createTrigger(tempTrigger);
await this.dropTrigger({ trigger: tempTrigger.name }); await this.dropTrigger({ schema: trigger.schema, trigger: tempTrigger.name });
await this.dropTrigger({ trigger: trigger.oldName }); await this.dropTrigger({ schema: trigger.schema, trigger: trigger.oldName });
await this.createTrigger(trigger); await this.createTrigger(trigger);
} }
catch (err) { catch (err) {
@@ -636,8 +678,8 @@ export class MySQLClient extends AntaresCore {
* @returns {Array.<Object>} parameters * @returns {Array.<Object>} parameters
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async createTrigger (trigger) { async createTrigger (params) {
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}`; 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 }); return await this.raw(sql, { split: false });
} }
@@ -711,7 +753,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async dropRoutine (params) { async dropRoutine (params) {
const sql = `DROP PROCEDURE \`${this._schema}\`.\`${params.routine}\``; const sql = `DROP PROCEDURE \`${params.schema}\`.\`${params.routine}\``;
return await this.raw(sql); return await this.raw(sql);
} }
@@ -728,8 +770,8 @@ export class MySQLClient extends AntaresCore {
try { try {
await this.createRoutine(tempProcedure); await this.createRoutine(tempProcedure);
await this.dropRoutine({ routine: tempProcedure.name }); await this.dropRoutine({ schema: routine.schema, routine: tempProcedure.name });
await this.dropRoutine({ routine: routine.oldName }); await this.dropRoutine({ schema: routine.schema, routine: routine.oldName });
await this.createRoutine(routine); await this.createRoutine(routine);
} }
catch (err) { catch (err) {
@@ -743,21 +785,21 @@ export class MySQLClient extends AntaresCore {
* @returns {Array.<Object>} parameters * @returns {Array.<Object>} parameters
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async createRoutine (routine) { async createRoutine (params) {
const parameters = 'parameters' in routine const parameters = 'parameters' in params
? routine.parameters.reduce((acc, curr) => { ? params.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; return acc;
}, []).join(',') }, []).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 LANGUAGE SQL
${routine.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'} ${params.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'}
${routine.dataAccess} ${params.dataAccess}
SQL SECURITY ${routine.security} SQL SECURITY ${params.security}
COMMENT '${routine.comment}' COMMENT '${params.comment}'
${routine.sql}`; ${params.sql}`;
return await this.raw(sql, { split: false }); return await this.raw(sql, { split: false });
} }
@@ -838,7 +880,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async dropFunction (params) { async dropFunction (params) {
const sql = `DROP FUNCTION \`${this._schema}\`.\`${params.func}\``; const sql = `DROP FUNCTION \`${params.schema}\`.\`${params.func}\``;
return await this.raw(sql); return await this.raw(sql);
} }
@@ -855,8 +897,8 @@ export class MySQLClient extends AntaresCore {
try { try {
await this.createFunction(tempProcedure); await this.createFunction(tempProcedure);
await this.dropFunction({ func: tempProcedure.name }); await this.dropFunction({ schema: func.schema, func: tempProcedure.name });
await this.dropFunction({ func: func.oldName }); await this.dropFunction({ schema: func.schema, func: func.oldName });
await this.createFunction(func); await this.createFunction(func);
} }
catch (err) { catch (err) {
@@ -870,20 +912,20 @@ export class MySQLClient extends AntaresCore {
* @returns {Array.<Object>} parameters * @returns {Array.<Object>} parameters
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async createFunction (func) { async createFunction (params) {
const parameters = func.parameters.reduce((acc, curr) => { const parameters = params.parameters.reduce((acc, curr) => {
acc.push(`\`${curr.name}\` ${curr.type}${curr.length ? `(${curr.length})` : ''}`); acc.push(`\`${curr.name}\` ${curr.type}${curr.length ? `(${curr.length})` : ''}`);
return acc; return acc;
}, []).join(','); }, []).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 LANGUAGE SQL
${func.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'} ${params.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'}
${func.dataAccess} ${params.dataAccess}
SQL SECURITY ${func.security} SQL SECURITY ${params.security}
COMMENT '${func.comment}' COMMENT '${params.comment}'
${body}`; ${body}`;
return await this.raw(sql, { split: false }); return await this.raw(sql, { split: false });
@@ -930,7 +972,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async dropEvent (params) { async dropEvent (params) {
const sql = `DROP EVENT \`${this._schema}\`.\`${params.scheduler}\``; const sql = `DROP EVENT \`${params.schema}\`.\`${params.scheduler}\``;
return await this.raw(sql); return await this.raw(sql);
} }
@@ -946,13 +988,13 @@ export class MySQLClient extends AntaresCore {
if (scheduler.execution === 'EVERY' && scheduler.every[0].includes('-')) if (scheduler.execution === 'EVERY' && scheduler.every[0].includes('-'))
scheduler.every[0] = `'${scheduler.every[0]}'`; 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 ON SCHEDULE
${scheduler.execution === 'EVERY' ${scheduler.execution === 'EVERY'
? `EVERY ${scheduler.every.join(' ')}${scheduler.starts ? ` STARTS '${scheduler.starts}'` : ''}${scheduler.ends ? ` ENDS '${scheduler.ends}'` : ''}` ? `EVERY ${scheduler.every.join(' ')}${scheduler.starts ? ` STARTS '${scheduler.starts}'` : ''}${scheduler.ends ? ` ENDS '${scheduler.ends}'` : ''}`
: `AT '${scheduler.at}'`} : `AT '${scheduler.at}'`}
ON COMPLETION${!scheduler.preserve ? ' NOT' : ''} PRESERVE 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} ${scheduler.state}
COMMENT '${scheduler.comment}' COMMENT '${scheduler.comment}'
DO ${scheduler.sql}`; DO ${scheduler.sql}`;
@@ -966,16 +1008,16 @@ export class MySQLClient extends AntaresCore {
* @returns {Array.<Object>} parameters * @returns {Array.<Object>} parameters
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async createEvent (scheduler) { async createEvent (params) {
const sql = `CREATE ${scheduler.definer ? ` DEFINER=${scheduler.definer}` : ''} EVENT \`${this._schema}\`.\`${scheduler.name}\` const sql = `CREATE ${params.definer ? ` DEFINER=${params.definer}` : ''} EVENT \`${params.schema}\`.\`${params.name}\`
ON SCHEDULE ON SCHEDULE
${scheduler.execution === 'EVERY' ${params.execution === 'EVERY'
? `EVERY ${scheduler.every.join(' ')}${scheduler.starts ? ` STARTS '${scheduler.starts}'` : ''}${scheduler.ends ? ` ENDS '${scheduler.ends}'` : ''}` ? `EVERY ${params.every.join(' ')}${params.starts ? ` STARTS '${params.starts}'` : ''}${params.ends ? ` ENDS '${params.ends}'` : ''}`
: `AT '${scheduler.at}'`} : `AT '${params.at}'`}
ON COMPLETION${!scheduler.preserve ? ' NOT' : ''} PRESERVE ON COMPLETION${!params.preserve ? ' NOT' : ''} PRESERVE
${scheduler.state} ${params.state}
COMMENT '${scheduler.comment}' COMMENT '${params.comment}'
DO ${scheduler.sql}`; DO ${params.sql}`;
return await this.raw(sql, { split: false }); return await this.raw(sql, { split: false });
} }
@@ -1097,14 +1139,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async createTable (params) { async createTable (params) {
const { const sql = `CREATE TABLE \`${params.schema}\`.\`${params.name}\` (\`${params.name}_ID\` INT NULL) COMMENT='${params.comment}', COLLATE='${params.collation}', ENGINE=${params.engine}`;
name,
collation,
comment,
engine
} = params;
const sql = `CREATE TABLE \`${this._schema}\`.\`${name}\` (\`${name}_ID\` INT NULL) COMMENT='${comment}', COLLATE='${collation}', ENGINE=${engine}`;
return await this.raw(sql); return await this.raw(sql);
} }
@@ -1118,6 +1153,7 @@ export class MySQLClient extends AntaresCore {
async alterTable (params) { async alterTable (params) {
const { const {
table, table,
schema,
additions, additions,
deletions, deletions,
changes, changes,
@@ -1126,7 +1162,7 @@ export class MySQLClient extends AntaresCore {
options options
} = params; } = params;
let sql = `ALTER TABLE \`${this._schema || params.options.schema}\`.\`${table}\` `; let sql = `ALTER TABLE \`${schema}\`.\`${table}\` `;
const alterColumns = []; const alterColumns = [];
// OPTIONS // OPTIONS
@@ -1238,7 +1274,7 @@ export class MySQLClient extends AntaresCore {
sql += alterColumns.join(', '); sql += alterColumns.join(', ');
// RENAME // 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); return await this.raw(sql);
} }
@@ -1250,7 +1286,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async duplicateTable (params) { 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); return await this.raw(sql);
} }
@@ -1261,7 +1297,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async truncateTable (params) { async truncateTable (params) {
const sql = `TRUNCATE TABLE \`${this._schema}\`.\`${params.table}\``; const sql = `TRUNCATE TABLE \`${params.schema}\`.\`${params.table}\``;
return await this.raw(sql); return await this.raw(sql);
} }
@@ -1272,7 +1308,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async dropTable (params) { async dropTable (params) {
const sql = `DROP TABLE \`${this._schema}\`.\`${params.table}\``; const sql = `DROP TABLE \`${params.schema}\`.\`${params.table}\``;
return await this.raw(sql); return await this.raw(sql);
} }
@@ -1343,16 +1379,19 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async raw (sql, args) { 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 if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder
args = { args = {
nest: false, nest: false,
details: false, details: false,
split: true, split: true,
comments: true,
...args ...args
}; };
if (!args.comments)
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments
const nestTables = args.nest ? '.' : false; const nestTables = args.nest ? '.' : false;
const resultsArr = []; const resultsArr = [];
let paramsArr = []; let paramsArr = [];
@@ -1389,7 +1428,7 @@ export class MySQLClient extends AntaresCore {
name: field.orgName, name: field.orgName,
alias: field.name, alias: field.name,
orgName: field.orgName, orgName: field.orgName,
schema: field.schema, schema: args.schema || field.schema,
table: field.table, table: field.table,
tableAlias: field.table, tableAlias: field.table,
orgTable: field.orgTable, orgTable: field.orgTable,

View File

@@ -3,6 +3,7 @@ import { Pool, Client, types } from 'pg';
import { parse } from 'pgsql-ast-parser'; import { parse } from 'pgsql-ast-parser';
import { AntaresCore } from '../AntaresCore'; import { AntaresCore } from '../AntaresCore';
import dataTypes from 'common/data-types/postgresql'; import dataTypes from 'common/data-types/postgresql';
import * as SSH2Promise from 'ssh2-promise';
function pgToString (value) { function pgToString (value) {
return value.toString(); return value.toString();
@@ -51,13 +52,35 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async connect () { 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) { if (!this._poolSize) {
const client = new Client(this._params); const client = new Client(dbConfig);
await client.connect(); await client.connect();
this._connection = client; this._connection = client;
} }
else { else {
const pool = new Pool({ ...this._params, max: this._poolSize }); const pool = new Pool({ ...dbConfig, max: this._poolSize });
this._connection = pool; this._connection = pool;
} }
} }
@@ -67,6 +90,7 @@ export class PostgreSQLClient extends AntaresCore {
*/ */
destroy () { destroy () {
this._connection.end(); this._connection.end();
if (this._ssh) this._ssh.close();
} }
/** /**
@@ -269,6 +293,19 @@ export class PostgreSQLClient extends AntaresCore {
}); });
} }
/**
* @param {Object} params
* @param {String} params.schema
* @param {String} params.table
* @returns {Object} table row count
* @memberof PostgreSQLClient
*/
async getTableApproximateCount ({ schema, table }) {
const { rows } = await this.raw(`SELECT reltuples AS count FROM pg_class WHERE relname = '${table}'`);
return rows.length ? rows[0].count : 0;
}
/** /**
* @param {Object} params * @param {Object} params
* @param {String} params.schema * @param {String} params.schema
@@ -459,7 +496,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async dropView (params) { async dropView (params) {
const sql = `DROP VIEW ${this._schema}.${params.view}`; const sql = `DROP VIEW ${params.schema}.${params.view}`;
return await this.raw(sql); return await this.raw(sql);
} }
@@ -471,10 +508,10 @@ export class PostgreSQLClient extends AntaresCore {
*/ */
async alterView (params) { async alterView (params) {
const { view } = 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) 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); return await this.raw(sql);
} }
@@ -485,8 +522,8 @@ export class PostgreSQLClient extends AntaresCore {
* @returns {Array.<Object>} parameters * @returns {Array.<Object>} parameters
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async createView (view) { async createView (params) {
const sql = `CREATE VIEW ${this._schema}.${view.name} AS ${view.sql}`; const sql = `CREATE VIEW ${params.schema}.${params.name} AS ${params.sql}`;
return await this.raw(sql); return await this.raw(sql);
} }
@@ -536,7 +573,7 @@ export class PostgreSQLClient extends AntaresCore {
*/ */
async dropTrigger (params) { async dropTrigger (params) {
const triggerParts = params.trigger.split('.'); 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); return await this.raw(sql);
} }
@@ -553,8 +590,8 @@ export class PostgreSQLClient extends AntaresCore {
try { try {
await this.createTrigger(tempTrigger); await this.createTrigger(tempTrigger);
await this.dropTrigger({ trigger: `${tempTrigger.table}.${tempTrigger.name}` }); await this.dropTrigger({ schema: trigger.schema, trigger: `${tempTrigger.table}.${tempTrigger.name}` });
await this.dropTrigger({ trigger: `${trigger.table}.${trigger.oldName}` }); await this.dropTrigger({ schema: trigger.schema, trigger: `${trigger.table}.${trigger.oldName}` });
await this.createTrigger(trigger); await this.createTrigger(trigger);
} }
catch (err) { catch (err) {
@@ -568,9 +605,9 @@ export class PostgreSQLClient extends AntaresCore {
* @returns {Array.<Object>} parameters * @returns {Array.<Object>} parameters
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async createTrigger (trigger) { async createTrigger (params) {
const eventsString = Array.isArray(trigger.event) ? trigger.event.join(' OR ') : trigger.event; const eventsString = Array.isArray(params.event) ? params.event.join(' OR ') : params.event;
const sql = `CREATE TRIGGER "${trigger.name}" ${trigger.activation} ${eventsString} ON "${trigger.table}" FOR EACH ROW ${trigger.sql}`; 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 }); return await this.raw(sql, { split: false });
} }
@@ -613,6 +650,7 @@ export class PostgreSQLClient extends AntaresCore {
AND proc.routine_type = 'PROCEDURE' AND proc.routine_type = 'PROCEDURE'
AND proc.routine_name = '${routine}' AND proc.routine_name = '${routine}'
AND proc.specific_schema = '${schema}' AND proc.specific_schema = '${schema}'
AND args.data_type != NULL
ORDER BY procedure_schema, ORDER BY procedure_schema,
specific_name, specific_name,
procedure_name, procedure_name,
@@ -651,7 +689,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async dropRoutine (params) { async dropRoutine (params) {
const sql = `DROP PROCEDURE ${this._schema}.${params.routine}`; const sql = `DROP PROCEDURE "${params.schema}"."${params.routine}"`;
return await this.raw(sql); return await this.raw(sql);
} }
@@ -668,8 +706,8 @@ export class PostgreSQLClient extends AntaresCore {
try { try {
await this.createRoutine(tempProcedure); await this.createRoutine(tempProcedure);
await this.dropRoutine({ routine: tempProcedure.name }); await this.dropRoutine({ schema: routine.schema, routine: tempProcedure.name });
await this.dropRoutine({ routine: routine.oldName }); await this.dropRoutine({ schema: routine.schema, routine: routine.oldName });
await this.createRoutine(routine); await this.createRoutine(routine);
} }
catch (err) { catch (err) {
@@ -691,10 +729,10 @@ export class PostgreSQLClient extends AntaresCore {
}, []).join(',') }, []).join(',')
: ''; : '';
if (this._schema !== 'public') if (routine.schema !== 'public')
await this.use(this._schema); 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} LANGUAGE ${routine.language}
SECURITY ${routine.security} SECURITY ${routine.security}
AS ${routine.sql}`; AS ${routine.sql}`;
@@ -780,7 +818,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async dropFunction (params) { async dropFunction (params) {
const sql = `DROP FUNCTION ${this._schema}.${params.func}`; const sql = `DROP FUNCTION "${params.schema}"."${params.func}"`;
return await this.raw(sql); return await this.raw(sql);
} }
@@ -797,8 +835,8 @@ export class PostgreSQLClient extends AntaresCore {
try { try {
await this.createFunction(tempProcedure); await this.createFunction(tempProcedure);
await this.dropFunction({ func: tempProcedure.name }); await this.dropFunction({ schema: func.schema, func: tempProcedure.name });
await this.dropFunction({ func: func.oldName }); await this.dropFunction({ schema: func.schema, func: func.oldName });
await this.createFunction(func); await this.createFunction(func);
} }
catch (err) { catch (err) {
@@ -815,17 +853,17 @@ export class PostgreSQLClient extends AntaresCore {
async createFunction (func) { async createFunction (func) {
const parameters = 'parameters' in func const parameters = 'parameters' in func
? func.parameters.reduce((acc, curr) => { ? 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; return acc;
}, []).join(',') }, []).join(',')
: ''; : '';
if (this._schema !== 'public') if (func.schema !== 'public')
await this.use(this._schema); await this.use(func.schema);
const body = func.returns ? func.sql : '$function$\n$function$'; 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'} RETURNS ${func.returns || 'void'}
LANGUAGE ${func.language} LANGUAGE ${func.language}
SECURITY ${func.security} SECURITY ${func.security}
@@ -843,12 +881,12 @@ export class PostgreSQLClient extends AntaresCore {
async alterTriggerFunction (params) { async alterTriggerFunction (params) {
const { func } = params; const { func } = params;
if (this._schema !== 'public') if (func.schema !== 'public')
await this.use(this._schema); await this.use(func.schema);
const body = func.returns ? func.sql : '$function$\n$function$'; 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 RETURNS TRIGGER
LANGUAGE ${func.language} LANGUAGE ${func.language}
AS ${body}`; AS ${body}`;
@@ -863,12 +901,12 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async createTriggerFunction (func) { async createTriggerFunction (func) {
if (this._schema !== 'public') if (func.schema !== 'public')
await this.use(this._schema); await this.use(func.schema);
const body = func.returns ? func.sql : '$function$\r\nBEGIN\r\n\r\nEND\r\n$function$'; 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 RETURNS TRIGGER
LANGUAGE ${func.language} LANGUAGE ${func.language}
AS ${body}`; AS ${body}`;
@@ -964,11 +1002,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async createTable (params) { async createTable (params) {
const { const sql = `CREATE TABLE "${params.schema}"."${params.name}" (${params.name}_id INTEGER NULL); ALTER TABLE "${params.schema}"."${params.name}" DROP COLUMN ${params.name}_id`;
name
} = params;
const sql = `CREATE TABLE ${this._schema}.${name} (${name}_id INTEGER NULL); ALTER TABLE ${this._schema}.${name} DROP COLUMN ${name}_id`;
return await this.raw(sql); return await this.raw(sql);
} }
@@ -982,6 +1016,7 @@ export class PostgreSQLClient extends AntaresCore {
async alterTable (params) { async alterTable (params) {
const { const {
table, table,
schema,
additions, additions,
deletions, deletions,
changes, changes,
@@ -990,8 +1025,8 @@ export class PostgreSQLClient extends AntaresCore {
options options
} = params; } = params;
if (this._schema !== 'public') if (schema !== 'public')
await this.use(this._schema); await this.use(schema);
let sql = ''; let sql = '';
const alterColumns = []; const alterColumns = [];
@@ -1032,7 +1067,7 @@ export class PostgreSQLClient extends AntaresCore {
else if (type === 'UNIQUE') else if (type === 'UNIQUE')
alterColumns.push(`ADD CONSTRAINT ${addition.name} UNIQUE (${fields})`); alterColumns.push(`ADD CONSTRAINT ${addition.name} UNIQUE (${fields})`);
else 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 // ADD FOREIGN KEYS
@@ -1070,7 +1105,7 @@ export class PostgreSQLClient extends AntaresCore {
} }
if (change.orgName !== change.name) 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 // CHANGE INDEX
@@ -1088,7 +1123,7 @@ export class PostgreSQLClient extends AntaresCore {
else if (type === 'UNIQUE') else if (type === 'UNIQUE')
alterColumns.push(`ADD CONSTRAINT ${change.name} UNIQUE (${fields})`); alterColumns.push(`ADD CONSTRAINT ${change.name} UNIQUE (${fields})`);
else 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 // CHANGE FOREIGN KEYS
@@ -1115,10 +1150,10 @@ export class PostgreSQLClient extends AntaresCore {
alterColumns.push(`DROP CONSTRAINT ${deletion.constraintName}`); 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 (createSequences.length) sql = `${createSequences.join(';')}; ${sql}`;
if (manageIndexes.length) sql = `${manageIndexes.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 // RENAME
if (renameColumns.length) sql = `${renameColumns.join(';')}; ${sql}`; if (renameColumns.length) sql = `${renameColumns.join(';')}; ${sql}`;
@@ -1133,7 +1168,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async duplicateTable (params) { 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); return await this.raw(sql);
} }
@@ -1144,7 +1179,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async truncateTable (params) { async truncateTable (params) {
const sql = `TRUNCATE TABLE ${this._schema}.${params.table}`; const sql = `TRUNCATE TABLE ${params.schema}.${params.table}`;
return await this.raw(sql); return await this.raw(sql);
} }
@@ -1155,7 +1190,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async dropTable (params) { async dropTable (params) {
const sql = `DROP TABLE ${this._schema}.${params.table}`; const sql = `DROP TABLE ${params.schema}.${params.table}`;
return await this.raw(sql); return await this.raw(sql);
} }
@@ -1226,18 +1261,20 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async raw (sql, args) { async raw (sql, args) {
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');
args = { args = {
nest: false, nest: false,
details: false, details: false,
split: true, split: true,
comments: true,
...args ...args
}; };
if (args.schema && args.schema !== 'public') if (args.schema && args.schema !== 'public')
await this.use(args.schema); await this.use(args.schema);
if (!args.comments)
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments
const resultsArr = []; const resultsArr = [];
let paramsArr = []; let paramsArr = [];
const queries = args.split const queries = args.split

View File

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

View File

@@ -29,18 +29,23 @@ export default {
}, },
computed: { computed: {
position () { position () {
const { clientY, clientX } = this.contextEvent; let topCord = 0;
let topCord = `${clientY + 2}px`; let leftCord = 0;
let leftCord = `${clientX + 5}px`;
if (this.contextSize) { if (this.contextEvent) {
if (clientY + (this.contextSize.height < 200 ? 200 : this.contextSize.height) + 5 >= window.innerHeight) { const { clientY, clientX } = this.contextEvent;
topCord = `${clientY + 3 - this.contextSize.height}px`; topCord = `${clientY + 2}px`;
this.isBottom = true; leftCord = `${clientX + 5}px`;
if (this.contextSize) {
if (clientY + (this.contextSize.height < 200 ? 200 : this.contextSize.height) + 5 >= window.innerHeight) {
topCord = `${clientY + 3 - this.contextSize.height}px`;
this.isBottom = true;
}
if (clientX + this.contextSize.width + 5 >= window.innerWidth)
leftCord = `${clientX - this.contextSize.width}px`;
} }
if (clientX + this.contextSize.width + 5 >= window.innerWidth)
leftCord = `${clientX - this.contextSize.width}px`;
} }
return { return {
@@ -53,7 +58,8 @@ export default {
window.addEventListener('keydown', this.onKey); window.addEventListener('keydown', this.onKey);
}, },
mounted () { mounted () {
this.contextSize = this.$refs.contextContent.getBoundingClientRect(); if (this.$refs.contextContent)
this.contextSize = this.$refs.contextContent.getBoundingClientRect();
}, },
beforeDestroy () { beforeDestroy () {
window.removeEventListener('keydown', this.onKey); window.removeEventListener('keydown', this.onKey);

View File

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

View File

@@ -2,8 +2,8 @@
<ConfirmModal <ConfirmModal
:confirm-text="$t('word.discard')" :confirm-text="$t('word.discard')"
:cancel-text="$t('word.stay')" :cancel-text="$t('word.stay')"
@confirm="discardUnsavedChanges" @confirm="$emit('confirm')"
@hide="closeUnsavedChangesModal" @hide="$emit('close')"
> >
<template slot="header"> <template slot="header">
<div class="d-flex"> <div class="d-flex">
@@ -19,7 +19,6 @@
</template> </template>
<script> <script>
import { mapActions } from 'vuex';
import ConfirmModal from '@/components/BaseConfirmModal'; import ConfirmModal from '@/components/BaseConfirmModal';
export default { export default {
@@ -34,13 +33,6 @@ export default {
window.removeEventListener('keydown', this.onKey); window.removeEventListener('keydown', this.onKey);
}, },
methods: { methods: {
...mapActions({
discardUnsavedChanges: 'workspaces/discardUnsavedChanges',
closeUnsavedChangesModal: 'workspaces/closeUnsavedChangesModal'
}),
closeModal () {
this.$emit('close');
},
onKey (e) { onKey (e) {
e.stopPropagation(); e.stopPropagation();
if (e.key === 'Escape') 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> </form>
</div> </div>
</div> </div>
<div class="modal-footer text-light"> <div class="modal-footer">
<button class="btn btn-primary mr-2" @click.stop="updateSchema"> <button class="btn btn-primary mr-2" @click.stop="updateSchema">
{{ $t('word.update') }} {{ $t('word.update') }}
</button> </button>
@@ -72,7 +72,7 @@ import Schema from '@/ipc-api/Schema';
export default { export default {
name: 'ModalEditSchema', name: 'ModalEditSchema',
props: { props: {
selectedDatabase: String selectedSchema: String
}, },
data () { data () {
return { return {
@@ -99,7 +99,7 @@ export default {
async created () { async created () {
let actualCollation; let actualCollation;
try { 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') if (status === 'success')
actualCollation = response; actualCollation = response;
@@ -112,8 +112,8 @@ export default {
} }
this.database = { this.database = {
name: this.selectedDatabase, name: this.selectedSchema,
prevName: this.selectedDatabase, prevName: this.selectedSchema,
collation: actualCollation || this.defaultCollation, collation: actualCollation || this.defaultCollation,
prevCollation: actualCollation || this.defaultCollation prevCollation: actualCollation || this.defaultCollation
}; };

View File

@@ -52,7 +52,7 @@
</form> </form>
</div> </div>
</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="column d-flex" :class="hasFakes ? 'col-4' : 'col-2'">
<div class="input-group tooltip tooltip-right" :data-tooltip="$t('message.numberOfInserts')"> <div class="input-group tooltip tooltip-right" :data-tooltip="$t('message.numberOfInserts')">
<input <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> </form>
</div> </div>
</div> </div>
<div class="modal-footer text-light"> <div class="modal-footer">
<button <button
class="btn btn-primary mr-2" class="btn btn-primary mr-2"
:class="{'loading': isLoading}" :class="{'loading': isLoading}"

View File

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

View File

@@ -20,9 +20,9 @@
title="F5" title="F5"
@click="getProcessesList" @click="getProcessesList"
> >
<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> <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> </button>
<div class="btn btn-dark btn-sm dropdown-toggle pl-0 pr-0" tabindex="0"> <div class="btn btn-dark btn-sm dropdown-toggle pl-0 pr-0" tabindex="0">
<i class="mdi mdi-24px mdi-menu-down" /> <i class="mdi mdi-24px mdi-menu-down" />

View File

@@ -59,15 +59,15 @@
<div class="column col-12 h6 text-uppercase mb-1"> <div class="column col-12 h6 text-uppercase mb-1">
{{ $t('word.application') }} {{ $t('word.application') }}
</div> </div>
<div class="column col-8 col-sm-12 mb-2"> <div class="column col-9 col-sm-12 mb-2">
<div class="form-group mb-4"> <div class="form-group">
<div class="col-6 col-sm-12"> <div class="col-7 col-sm-12">
<label class="form-label"> <label class="form-label">
<i class="mdi mdi-18px mdi-translate mr-1" /> <i class="mdi mdi-18px mdi-translate mr-1" />
{{ $t('word.language') }} {{ $t('word.language') }}
</label> </label>
</div> </div>
<div class="col-6 col-sm-12"> <div class="col-5 col-sm-12">
<select <select
v-model="localLocale" v-model="localLocale"
class="form-select" class="form-select"
@@ -84,12 +84,12 @@
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="col-6 col-sm-12"> <div class="col-7 col-sm-12">
<label class="form-label"> <label class="form-label">
{{ $t('message.dataTabPageSize') }} {{ $t('message.dataTabPageSize') }}
</label> </label>
</div> </div>
<div class="col-6 col-sm-12"> <div class="col-5 col-sm-12">
<select <select
v-model="localPageSize" v-model="localPageSize"
class="form-select" class="form-select"
@@ -104,13 +104,26 @@
</select> </select>
</div> </div>
</div> </div>
<div class="form-group mb-0">
<div class="col-7 col-sm-12">
<label class="form-label">
{{ $t('message.restorePreviourSession') }}
</label>
</div>
<div class="col-5 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="form-group">
<div class="col-6 col-sm-12"> <div class="col-7 col-sm-12">
<label class="form-label"> <label class="form-label">
{{ $t('message.notificationsTimeout') }} {{ $t('message.notificationsTimeout') }}
</label> </label>
</div> </div>
<div class="col-6 col-sm-12"> <div class="col-5 col-sm-12">
<div class="input-group"> <div class="input-group">
<input <input
v-model="localTimeout" v-model="localTimeout"
@@ -128,29 +141,27 @@
<div class="column col-12 h6 mt-4 text-uppercase mb-1"> <div class="column col-12 h6 mt-4 text-uppercase mb-1">
{{ $t('word.editor') }} {{ $t('word.editor') }}
</div> </div>
<div class="column col-8 col-sm-12"> <div class="column col-9 col-sm-12">
<div class="form-group"> <div class="form-group mb-0">
<div class="col-6 col-sm-12"> <div class="col-7 col-sm-12">
<label class="form-label"> <label class="form-label">
{{ $t('word.autoCompletion') }} {{ $t('word.autoCompletion') }}
</label> </label>
</div> </div>
<div class="col-6 col-sm-12"> <div class="col-5 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleAutoComplete"> <label class="form-switch d-inline-block" @click.prevent="toggleAutoComplete">
<input type="checkbox" :checked="selectedAutoComplete"> <input type="checkbox" :checked="selectedAutoComplete">
<i class="form-icon" /> <i class="form-icon" />
</label> </label>
</div> </div>
</div> </div>
</div> <div class="form-group mb-0">
<div class="column col-8 col-sm-12"> <div class="col-7 col-sm-12">
<div class="form-group">
<div class="col-6 col-sm-12">
<label class="form-label"> <label class="form-label">
{{ $t('message.wrapLongLines') }} {{ $t('message.wrapLongLines') }}
</label> </label>
</div> </div>
<div class="col-6 col-sm-12"> <div class="col-5 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleLineWrap"> <label class="form-switch d-inline-block" @click.prevent="toggleLineWrap">
<input type="checkbox" :checked="selectedLineWrap"> <input type="checkbox" :checked="selectedLineWrap">
<i class="form-icon" /> <i class="form-icon" />
@@ -307,7 +318,7 @@ export default {
localTimeout: null, localTimeout: null,
localEditorTheme: null, localEditorTheme: null,
selectedTab: 'general', selectedTab: 'general',
pageSizes: [40, 100, 250, 500, 1000], pageSizes: [30, 40, 100, 250, 500, 1000],
editorThemes: [ editorThemes: [
{ {
group: this.$t('word.light'), group: this.$t('word.light'),
@@ -369,6 +380,7 @@ export default {
selectedAutoComplete: 'settings/getAutoComplete', selectedAutoComplete: 'settings/getAutoComplete',
selectedLineWrap: 'settings/getLineWrap', selectedLineWrap: 'settings/getLineWrap',
notificationsTimeout: 'settings/getNotificationsTimeout', notificationsTimeout: 'settings/getNotificationsTimeout',
restoreTabs: 'settings/getRestoreTabs',
applicationTheme: 'settings/getApplicationTheme', applicationTheme: 'settings/getApplicationTheme',
editorTheme: 'settings/getEditorTheme', editorTheme: 'settings/getEditorTheme',
editorFontSize: 'settings/getEditorFontSize', editorFontSize: 'settings/getEditorFontSize',
@@ -423,6 +435,7 @@ ORDER BY
closeModal: 'application/hideSettingModal', closeModal: 'application/hideSettingModal',
changeLocale: 'settings/changeLocale', changeLocale: 'settings/changeLocale',
changePageSize: 'settings/changePageSize', changePageSize: 'settings/changePageSize',
changeRestoreTabs: 'settings/changeRestoreTabs',
changeAutoComplete: 'settings/changeAutoComplete', changeAutoComplete: 'settings/changeAutoComplete',
changeLineWrap: 'settings/changeLineWrap', changeLineWrap: 'settings/changeLineWrap',
changeApplicationTheme: 'settings/changeApplicationTheme', changeApplicationTheme: 'settings/changeApplicationTheme',
@@ -447,6 +460,9 @@ ORDER BY
if (e.key === 'Escape') if (e.key === 'Escape')
this.closeModal(); this.closeModal();
}, },
toggleRestoreSession () {
this.changeRestoreTabs(!this.restoreTabs);
},
toggleAutoComplete () { toggleAutoComplete () {
this.changeAutoComplete(!this.selectedAutoComplete); this.changeAutoComplete(!this.selectedAutoComplete);
}, },

View File

@@ -3,18 +3,13 @@
:context-event="contextEvent" :context-event="contextEvent"
@close-context="$emit('close-context')" @close-context="$emit('close-context')"
> >
<div class="context-element" @click="showEditModal(contextConnection)"> <div class="context-element" @click="duplicateConnection">
<span class="d-flex"><i class="mdi mdi-18px mdi-pencil text-light pr-1" /> {{ $t('word.edit') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-content-duplicate text-light pr-1" /> {{ $t('word.duplicate') }}</span>
</div> </div>
<div class="context-element" @click="showConfirmModal"> <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> <span class="d-flex"><i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ $t('word.delete') }}</span>
</div> </div>
<ModalEditConnection
v-if="isEditModal"
:connection="contextConnection"
@close="hideEditModal"
/>
<ConfirmModal <ConfirmModal
v-if="isConfirmModal" v-if="isConfirmModal"
@confirm="confirmDeleteConnection" @confirm="confirmDeleteConnection"
@@ -36,15 +31,14 @@
<script> <script>
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import { uidGen } from 'common/libs/uidGen';
import BaseContextMenu from '@/components/BaseContextMenu'; import BaseContextMenu from '@/components/BaseContextMenu';
import ConfirmModal from '@/components/BaseConfirmModal'; import ConfirmModal from '@/components/BaseConfirmModal';
import ModalEditConnection from '@/components/ModalEditConnection';
export default { export default {
name: 'SettingBarContext', name: 'SettingBarContext',
components: { components: {
BaseContextMenu, BaseContextMenu,
ModalEditConnection,
ConfirmModal ConfirmModal
}, },
props: { props: {
@@ -59,7 +53,8 @@ export default {
}, },
computed: { computed: {
...mapGetters({ ...mapGetters({
getConnectionName: 'connections/getConnectionName' getConnectionName: 'connections/getConnectionName',
selectedWorkspace: 'workspaces/getSelected'
}), }),
connectionName () { connectionName () {
return this.getConnectionName(this.contextConnection.uid); return this.getConnectionName(this.contextConnection.uid);
@@ -67,17 +62,25 @@ export default {
}, },
methods: { methods: {
...mapActions({ ...mapActions({
deleteConnection: 'connections/deleteConnection' addConnection: 'connections/addConnection',
deleteConnection: 'connections/deleteConnection',
selectWorkspace: 'workspaces/selectWorkspace'
}), }),
confirmDeleteConnection () { confirmDeleteConnection () {
if (this.selectedWorkspace === this.contextConnection.uid)
this.selectWorkspace();
this.deleteConnection(this.contextConnection); this.deleteConnection(this.contextConnection);
this.closeContext(); this.closeContext();
}, },
showEditModal () { duplicateConnection () {
this.isEditModal = true; let connectionCopy = Object.assign({}, this.contextConnection);
}, connectionCopy = {
hideEditModal () { ...connectionCopy,
this.isEditModal = false; uid: uidGen('C'),
name: connectionCopy.name ? `${connectionCopy.name}_copy` : ''
};
this.addConnection(connectionCopy);
this.closeContext(); this.closeContext();
}, },
showConfirmModal () { 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" @close-context="isContext = false"
/> />
<ul class="settingbar-elements"> <ul class="settingbar-elements">
<draggable v-model="connections"> <Draggable
v-model="connections"
@start="isDragging = true"
@end="dragStop"
>
<li <li
v-for="connection in connections" v-for="connection in connections"
:key="connection.uid" :key="connection.uid"
draggable="true" draggable="true"
class="settingbar-element btn btn-link ex-tooltip" class="settingbar-element btn btn-link ex-tooltip"
:class="{'selected': connection.uid === selectedWorkspace}" :class="{'selected': connection.uid === selectedWorkspace}"
@click="selectWorkspace(connection.uid)" @click.stop="selectWorkspace(connection.uid)"
@contextmenu.prevent="contextMenu($event, connection)" @contextmenu.prevent="contextMenu($event, connection)"
@mouseover.self="tooltipPosition" @mouseover.self="tooltipPosition"
> >
<i class="settingbar-element-icon dbi" :class="`dbi-${connection.client} ${getStatusBadge(connection.uid)}`" /> <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> </li>
</draggable> </Draggable>
<li <li
class="settingbar-element btn btn-link ex-tooltip" class="settingbar-element btn btn-link ex-tooltip"
@click="showNewConnModal" :class="{'selected': 'NEW' === selectedWorkspace}"
@click="selectWorkspace('NEW')"
@mouseover.self="tooltipPosition" @mouseover.self="tooltipPosition"
> >
<i class="settingbar-element-icon mdi mdi-24px mdi-plus text-light" /> <i class="settingbar-element-icon mdi mdi-24px mdi-plus text-light" />
@@ -51,19 +56,20 @@
<script> <script>
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import draggable from 'vuedraggable'; import Draggable from 'vuedraggable';
import SettingBarContext from '@/components/SettingBarContext'; import SettingBarContext from '@/components/SettingBarContext';
export default { export default {
name: 'TheSettingBar', name: 'TheSettingBar',
components: { components: {
draggable, Draggable,
SettingBarContext SettingBarContext
}, },
data () { data () {
return { return {
dragElement: null, dragElement: null,
isContext: false, isContext: false,
isDragging: false,
contextEvent: null, contextEvent: null,
contextConnection: {}, contextConnection: {},
scale: 0 scale: 0
@@ -92,7 +98,6 @@ export default {
methods: { methods: {
...mapActions({ ...mapActions({
updateConnections: 'connections/updateConnections', updateConnections: 'connections/updateConnections',
showNewConnModal: 'application/showNewConnModal',
showSettingModal: 'application/showSettingModal', showSettingModal: 'application/showSettingModal',
showScratchpad: 'application/showScratchpad', showScratchpad: 'application/showScratchpad',
selectWorkspace: 'workspaces/selectWorkspace' selectWorkspace: 'workspaces/selectWorkspace'
@@ -106,13 +111,13 @@ export default {
return connection.ask ? '' : `${connection.user + '@'}${connection.host}:${connection.port}`; return connection.ask ? '' : `${connection.user + '@'}${connection.host}:${connection.port}`;
}, },
tooltipPosition (e) { tooltipPosition (e) {
const el = e.target; const el = e.target ? e.target : e;
const fromTop = window.pageYOffset + el.getBoundingClientRect().top - (el.offsetHeight / 4); const fromTop = window.pageYOffset + el.getBoundingClientRect().top - (el.offsetHeight / 4);
el.querySelector('.ex-tooltip-content').style.top = `${fromTop}px`; el.querySelector('.ex-tooltip-content').style.top = `${fromTop}px`;
}, },
getStatusBadge (uid) { getStatusBadge (uid) {
if (this.getWorkspace(uid)) { if (this.getWorkspace(uid)) {
const status = this.getWorkspace(uid).connection_status; const status = this.getWorkspace(uid).connectionStatus;
switch (status) { switch (status) {
case 'connected': case 'connected':
@@ -125,6 +130,13 @@ export default {
return ''; return '';
} }
} }
},
dragStop (e) {
this.isDragging = false;
setTimeout(() => {
this.tooltipPosition(e.originalEvent.target.parentNode);
}, 200);
} }
} }
}; };
@@ -167,14 +179,13 @@ export default {
height: $settingbar-width; height: $settingbar-width;
width: 100%; width: 100%;
margin: 0; margin: 0;
border-left: 3px solid transparent;
opacity: 0.5; opacity: 0.5;
transition: opacity 0.2s; transition: opacity 0.2s;
display: flex; display: flex;
align-content: center; align-items: center;
justify-content: center; justify-content: flex-start;
flex-direction: column;
border-radius: 0; border-radius: 0;
padding: 0;
&:hover { &:hover {
opacity: 1; opacity: 1;
@@ -194,12 +205,12 @@ export default {
width: 3px; width: 3px;
transition: height 0.2s; transition: height 0.2s;
background-color: $primary-color; background-color: $primary-color;
position: absolute;
left: 0;
border-radius: $border-radius; border-radius: $border-radius;
} }
.settingbar-element-icon { .settingbar-element-icon {
margin: 0 auto;
&.badge::after { &.badge::after {
bottom: -10px; bottom: -10px;
right: 0; right: 0;
@@ -236,7 +247,13 @@ export default {
transition: opacity 0.2s; transition: opacity 0.2s;
} }
&:hover .ex-tooltip-content { &.sortable-chosen {
.ex-tooltip-content {
opacity: 0 !important;
}
}
&:hover:not(.selected) .ex-tooltip-content {
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
} }

View File

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

View File

@@ -1,13 +1,143 @@
<template> <template>
<div v-show="isSelected" class="workspace column columns col-gapless"> <div v-show="isSelected" class="workspace column columns col-gapless">
<WorkspaceExploreBar :connection="connection" :is-selected="isSelected" /> <WorkspaceExploreBar
<div v-if="workspace.connection_status === 'connected'" class="workspace-tabs column columns col-gapless"> v-if="workspace.connectionStatus === 'connected'"
<ul :connection="connection"
id="tabWrap" :is-selected="isSelected"
/>
<div v-if="workspace.connectionStatus === 'connected'" class="workspace-tabs column columns col-gapless">
<Draggable
ref="tabWrap" ref="tabWrap"
v-model="draggableTabs"
tag="ul"
group="tabs"
class="tab tab-block column col-12" 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"
:ref="selectedTab === tab.uid ? 'tab-selected' : ''"
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>
<span>{{ tab.content || 'Query' | cutText }} #{{ tab.index }}</span>
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@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()}: ${$tc(`word.${tab.elementType}`)}`">
<span class=" text-italic">{{ tab.elementName | cutText }}</span>
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@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()}: ${$tc(`word.${tab.elementType}`)}`">
{{ tab.elementName | cutText }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@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()}: ${$tc(`word.${tab.elementType}`)}`">
{{ tab.elementName | cutText }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@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()}: ${$tc(`word.${tab.elementType}`)}`">
{{ tab.elementName | cutText }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@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()}: ${$tc(`word.${tab.elementType}`)}`">
<span class=" text-italic">{{ tab.elementName | cutText }}</span>
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@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()}: ${$tc(`word.${tab.elementType}`)}`">
{{ tab.elementName | cutText }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(tab)"
/>
</span>
</a>
</li>
<li slot="header" class="tab-item dropdown tools-dropdown">
<a <a
class="tab-link workspace-tools-link dropdown-toggle" class="tab-link workspace-tools-link dropdown-toggle"
tabindex="0" tabindex="0"
@@ -44,151 +174,153 @@
</li> </li>
</ul> </ul>
</li> </li>
<li <li slot="footer" class="tab-item">
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">
<a <a
class="tab-add" class="tab-add"
:title="$t('message.openNewTab')" :title="$t('message.openNewTab')"
@click="addTab" @click="addQueryTab"
> >
<i class="mdi mdi-24px mdi-plus" /> <i class="mdi mdi-24px mdi-plus" />
</a> </a>
</li> </li>
</ul> </Draggable>
<WorkspacePropsTab <WorkspaceEmptyState v-if="!workspace.tabs.length" @new-tab="addQueryTab" />
v-show="selectedTab === 'prop' && workspace.breadcrumbs.table" <template v-for="tab of workspace.tabs">
:is-selected="selectedTab === 'prop'" <WorkspaceQueryTab
:connection="connection" v-if="tab.type==='query'"
:table="workspace.breadcrumbs.table" :key="tab.uid"
/> :tab="tab"
<WorkspacePropsTabView :is-selected="selectedTab === tab.uid"
v-show="selectedTab === 'prop' && workspace.breadcrumbs.view" :connection="connection"
:is-selected="selectedTab === 'prop'" />
:connection="connection" <WorkspaceTableTab
:view="workspace.breadcrumbs.view" v-else-if="['temp-data', 'data'].includes(tab.type)"
/> :key="tab.uid"
<WorkspacePropsTabTrigger :connection="connection"
v-show="selectedTab === 'prop' && workspace.breadcrumbs.trigger" :is-selected="selectedTab === tab.uid"
:is-selected="selectedTab === 'prop'" :table="tab.elementName"
:connection="connection" :schema="tab.schema"
:trigger="workspace.breadcrumbs.trigger" :element-type="tab.elementType"
/> />
<WorkspacePropsTabRoutine <WorkspacePropsTab
v-show="selectedTab === 'prop' && workspace.breadcrumbs.procedure" v-else-if="tab.type === 'table-props'"
:is-selected="selectedTab === 'prop'" :key="tab.uid"
:connection="connection" :connection="connection"
:routine="workspace.breadcrumbs.procedure" :is-selected="selectedTab === tab.uid"
/> :table="tab.elementName"
<WorkspacePropsTabFunction :schema="tab.schema"
v-show="selectedTab === 'prop' && workspace.breadcrumbs.function" />
:is-selected="selectedTab === 'prop'" <WorkspacePropsTabView
:connection="connection" v-else-if="tab.type === 'view-props'"
:function="workspace.breadcrumbs.function" :key="tab.uid"
/> :is-selected="selectedTab === tab.uid"
<WorkspacePropsTabTriggerFunction :connection="connection"
v-show="selectedTab === 'prop' && workspace.breadcrumbs.triggerFunction" :view="tab.elementName"
:is-selected="selectedTab === 'prop'" :schema="tab.schema"
:connection="connection" />
:function="workspace.breadcrumbs.triggerFunction" <WorkspacePropsTabTrigger
/> v-else-if="['temp-trigger-props', 'trigger-props'].includes(tab.type)"
<WorkspacePropsTabScheduler :key="tab.uid"
v-show="selectedTab === 'prop' && workspace.breadcrumbs.scheduler" :connection="connection"
:is-selected="selectedTab === 'prop'" :is-selected="selectedTab === tab.uid"
:connection="connection" :trigger="tab.elementName"
:scheduler="workspace.breadcrumbs.scheduler" :schema="tab.schema"
/> />
<WorkspaceTableTab <WorkspacePropsTabTriggerFunction
v-show="selectedTab === 'data'" v-else-if="['temp-trigger-function-props', 'trigger-function-props'].includes(tab.type)"
:connection="connection" :key="tab.uid"
:table="workspace.breadcrumbs.table || workspace.breadcrumbs.view" :connection="connection"
/> :is-selected="selectedTab === tab.uid"
<WorkspaceQueryTab :function="tab.elementName"
v-for="tab of queryTabs" :schema="tab.schema"
:key="tab.uid" />
:tab="tab" <WorkspacePropsTabRoutine
:is-selected="selectedTab === tab.uid" v-else-if="['temp-routine-props', 'routine-props'].includes(tab.type)"
:connection="connection" :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> </div>
<WorkspaceEditConnectionPanel v-else :connection="connection" />
<ModalProcessesList <ModalProcessesList
v-if="isProcessesModal" v-if="isProcessesModal"
:connection="connection" :connection="connection"
@close="hideProcessesModal" @close="hideProcessesModal"
/> />
<ModalDiscardChanges
v-if="unsavedTab"
@confirm="closeTab(unsavedTab, true)"
@close="unsavedTab = null"
/>
</div> </div>
</template> </template>
<script> <script>
import { mapGetters, mapActions } from 'vuex'; import { mapGetters, mapActions } from 'vuex';
import Draggable from 'vuedraggable';
import Connection from '@/ipc-api/Connection'; import Connection from '@/ipc-api/Connection';
import WorkspaceEmptyState from '@/components/WorkspaceEmptyState';
import WorkspaceExploreBar from '@/components/WorkspaceExploreBar'; import WorkspaceExploreBar from '@/components/WorkspaceExploreBar';
import WorkspaceEditConnectionPanel from '@/components/WorkspaceEditConnectionPanel';
import WorkspaceQueryTab from '@/components/WorkspaceQueryTab'; import WorkspaceQueryTab from '@/components/WorkspaceQueryTab';
import WorkspaceTableTab from '@/components/WorkspaceTableTab'; import WorkspaceTableTab from '@/components/WorkspaceTableTab';
import WorkspacePropsTab from '@/components/WorkspacePropsTab'; import WorkspacePropsTab from '@/components/WorkspacePropsTab';
import WorkspacePropsTabView from '@/components/WorkspacePropsTabView'; import WorkspacePropsTabView from '@/components/WorkspacePropsTabView';
import WorkspacePropsTabTrigger from '@/components/WorkspacePropsTabTrigger'; import WorkspacePropsTabTrigger from '@/components/WorkspacePropsTabTrigger';
import WorkspacePropsTabTriggerFunction from '@/components/WorkspacePropsTabTriggerFunction';
import WorkspacePropsTabRoutine from '@/components/WorkspacePropsTabRoutine'; import WorkspacePropsTabRoutine from '@/components/WorkspacePropsTabRoutine';
import WorkspacePropsTabFunction from '@/components/WorkspacePropsTabFunction'; import WorkspacePropsTabFunction from '@/components/WorkspacePropsTabFunction';
import WorkspacePropsTabTriggerFunction from '@/components/WorkspacePropsTabTriggerFunction';
import WorkspacePropsTabScheduler from '@/components/WorkspacePropsTabScheduler'; import WorkspacePropsTabScheduler from '@/components/WorkspacePropsTabScheduler';
import ModalProcessesList from '@/components/ModalProcessesList'; import ModalProcessesList from '@/components/ModalProcessesList';
import ModalDiscardChanges from '@/components/ModalDiscardChanges';
export default { export default {
name: 'Workspace', name: 'Workspace',
components: { components: {
Draggable,
WorkspaceEmptyState,
WorkspaceExploreBar, WorkspaceExploreBar,
WorkspaceEditConnectionPanel,
WorkspaceQueryTab, WorkspaceQueryTab,
WorkspaceTableTab, WorkspaceTableTab,
WorkspacePropsTab, WorkspacePropsTab,
WorkspacePropsTabView, WorkspacePropsTabView,
WorkspacePropsTabTrigger, WorkspacePropsTabTrigger,
WorkspacePropsTabTriggerFunction,
WorkspacePropsTabRoutine, WorkspacePropsTabRoutine,
WorkspacePropsTabFunction, WorkspacePropsTabFunction,
WorkspacePropsTabTriggerFunction,
WorkspacePropsTabScheduler, WorkspacePropsTabScheduler,
ModalProcessesList ModalProcessesList,
ModalDiscardChanges
},
filters: {
cutText (string) {
const limit = 20;
const escapedString = string.replace(/\s{2,}/g, ' ');
if (escapedString.length > limit)
return `${escapedString.substr(0, limit)}...`;
return escapedString;
}
}, },
props: { props: {
connection: Object connection: Object
@@ -196,7 +328,8 @@ export default {
data () { data () {
return { return {
hasWheelEvent: false, hasWheelEvent: false,
isProcessesModal: false isProcessesModal: false,
unsavedTab: null
}; };
}, },
computed: { computed: {
@@ -207,6 +340,14 @@ export default {
workspace () { workspace () {
return this.getWorkspace(this.connection.uid); return this.getWorkspace(this.connection.uid);
}, },
draggableTabs: {
get () {
return this.workspace.tabs;
},
set (val) {
this.updateTabs({ uid: this.connection.uid, tabs: val });
}
},
isSelected () { isSelected () {
return this.selectedWorkspace === this.connection.uid; return this.selectedWorkspace === this.connection.uid;
}, },
@@ -221,29 +362,7 @@ export default {
return false; return false;
}, },
selectedTab () { selectedTab () {
if ( return this.workspace ? this.workspace.selectedTab : null;
(
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;
}, },
queryTabs () { queryTabs () {
return this.workspace.tabs.filter(tab => tab.type === 'query'); return this.workspace.tabs.filter(tab => tab.type === 'query');
@@ -256,20 +375,26 @@ export default {
return false; return false;
} }
}, },
watch: {
selectedTab (newVal, oldVal) {
if (newVal !== oldVal) {
setTimeout(() => {
const element = this.$refs['tab-selected'] ? this.$refs['tab-selected'][0] : null;
if (element) {
element.setAttribute('tabindex', '-1');
element.focus();
element.removeAttribute('tabindex');
}
}, 50);
}
}
},
async created () { async created () {
await this.addWorkspace(this.connection.uid); await this.addWorkspace(this.connection.uid);
const isInitiated = await Connection.checkConnection(this.connection.uid); const isInitiated = await Connection.checkConnection(this.connection.uid);
if (isInitiated) if (isInitiated)
this.connectWorkspace(this.connection); 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: { methods: {
...mapActions({ ...mapActions({
addWorkspace: 'workspaces/addWorkspace', addWorkspace: 'workspaces/addWorkspace',
@@ -277,28 +402,55 @@ export default {
removeConnected: 'workspaces/removeConnected', removeConnected: 'workspaces/removeConnected',
selectTab: 'workspaces/selectTab', selectTab: 'workspaces/selectTab',
newTab: 'workspaces/newTab', newTab: 'workspaces/newTab',
removeTab: 'workspaces/removeTab' removeTab: 'workspaces/removeTab',
updateTabs: 'workspaces/updateTabs'
}), }),
addTab () { addQueryTab () {
this.newTab({ uid: this.connection.uid }); this.newTab({ uid: this.connection.uid, type: 'query' });
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;
}
}, },
closeTab (tUid) { openAsPermanentTab (tab) {
if (this.queryTabs.length === 1) return; const permanentTabs = {
this.removeTab({ uid: this.connection.uid, tab: tUid }); 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 () { showProcessesModal () {
this.isProcessesModal = true; this.isProcessesModal = true;
}, },
hideProcessesModal () { hideProcessesModal () {
this.isProcessesModal = false; 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 +479,35 @@ export default {
} }
.tab-item { .tab-item {
max-width: 12rem;
width: fit-content; width: fit-content;
flex: initial; flex: initial;
> a { > a {
padding: 0.2rem 0.8rem; padding: 0.2rem 0.6rem;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
opacity: 0.7; opacity: 0.7;
transition: opacity 0.2s; 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 { &:hover {
opacity: 1; opacity: 1;
.btn-clear {
opacity: 1;
}
} }
&.tab-add { &.tab-add {
@@ -359,9 +526,15 @@ export default {
&.active a { &.active a {
opacity: 1; opacity: 1;
.btn-clear {
opacity: 1;
}
} }
&.tools-dropdown { &.tools-dropdown {
height: 34px;
.tab-link:focus { .tab-link:focus {
opacity: 1; opacity: 1;
outline: 0; 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

@@ -8,7 +8,7 @@
> >
<div class="workspace-explorebar-header"> <div class="workspace-explorebar-header">
<span class="workspace-explorebar-title">{{ connectionName }}</span> <span class="workspace-explorebar-title">{{ connectionName }}</span>
<span v-if="workspace.connection_status === 'connected'" class="workspace-explorebar-tools"> <span v-if="workspace.connectionStatus === 'connected'" class="workspace-explorebar-tools">
<i <i
class="mdi mdi-18px mdi-database-plus c-hand mr-2" class="mdi mdi-18px mdi-database-plus c-hand mr-2"
:title="$t('message.createNewSchema')" :title="$t('message.createNewSchema')"
@@ -21,29 +21,29 @@
@click="refresh" @click="refresh"
/> />
<i <i
class="mdi mdi-18px mdi-power-plug-off c-hand" class="mdi mdi-18px mdi-power c-hand"
:title="$t('word.disconnect')" :title="$t('word.disconnect')"
@click="disconnectWorkspace(connection.uid)" @click="disconnectWorkspace(connection.uid)"
/> />
</span> </span>
</div> </div>
<div class="workspace-explorebar-search"> <div class="workspace-explorebar-search">
<div v-if="workspace.connection_status === 'connected'" class="has-icon-right"> <div v-if="workspace.connectionStatus === 'connected'" class="has-icon-right">
<input <input
v-model="searchTerm" v-model="searchTerm"
class="form-input input-sm" class="form-input input-sm"
type="text" type="text"
:placeholder="$t('message.searchForElements')" :placeholder="$t('message.searchForElements')"
> >
<i class="form-icon mdi mdi-magnify mdi-18px" /> <i v-if="!searchTerm" class="form-icon mdi mdi-magnify mdi-18px" />
<i
v-else
class="form-icon c-hand mdi mdi-backspace mdi-18px pr-1"
@click="searchTerm = ''"
/>
</div> </div>
</div> </div>
<WorkspaceConnectPanel <div class="workspace-explorebar-body">
v-if="workspace.connection_status !== 'connected'"
class="workspace-explorebar-body"
:connection="connection"
/>
<div v-else class="workspace-explorebar-body">
<WorkspaceExploreBarSchema <WorkspaceExploreBarSchema
v-for="db of workspace.structure" v-for="db of workspace.structure"
:key="db.name" :key="db.name"
@@ -52,6 +52,7 @@
@show-schema-context="openSchemaContext" @show-schema-context="openSchemaContext"
@show-table-context="openTableContext" @show-table-context="openTableContext"
@show-misc-context="openMiscContext" @show-misc-context="openMiscContext"
@show-misc-folder-context="openMiscFolderContext"
/> />
</div> </div>
</div> </div>
@@ -104,7 +105,7 @@
/> />
<DatabaseContext <DatabaseContext
v-if="isDatabaseContext" v-if="isDatabaseContext"
:selected-database="selectedDatabase" :selected-schema="selectedSchema"
:context-event="databaseContextEvent" :context-event="databaseContextEvent"
@close-context="closeDatabaseContext" @close-context="closeDatabaseContext"
@show-create-table-modal="showCreateTableModal" @show-create-table-modal="showCreateTableModal"
@@ -117,7 +118,8 @@
@reload="refresh" @reload="refresh"
/> />
<TableContext <TableContext
v-if="isTableContext" v-show="isTableContext"
:selected-schema="selectedSchema"
:selected-table="selectedTable" :selected-table="selectedTable"
:context-event="tableContextEvent" :context-event="tableContextEvent"
@close-context="closeTableContext" @close-context="closeTableContext"
@@ -126,10 +128,24 @@
<MiscContext <MiscContext
v-if="isMiscContext" v-if="isMiscContext"
:selected-misc="selectedMisc" :selected-misc="selectedMisc"
:selected-schema="selectedSchema"
:context-event="miscContextEvent" :context-event="miscContextEvent"
@close-context="closeMiscContext" @close-context="closeMiscContext"
@reload="refresh" @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> </div>
</template> </template>
@@ -143,11 +159,11 @@ import Routines from '@/ipc-api/Routines';
import Functions from '@/ipc-api/Functions'; import Functions from '@/ipc-api/Functions';
import Schedulers from '@/ipc-api/Schedulers'; import Schedulers from '@/ipc-api/Schedulers';
import WorkspaceConnectPanel from '@/components/WorkspaceConnectPanel';
import WorkspaceExploreBarSchema from '@/components/WorkspaceExploreBarSchema'; import WorkspaceExploreBarSchema from '@/components/WorkspaceExploreBarSchema';
import DatabaseContext from '@/components/WorkspaceExploreBarSchemaContext'; import DatabaseContext from '@/components/WorkspaceExploreBarSchemaContext';
import TableContext from '@/components/WorkspaceExploreBarTableContext'; import TableContext from '@/components/WorkspaceExploreBarTableContext';
import MiscContext from '@/components/WorkspaceExploreBarMiscContext'; import MiscContext from '@/components/WorkspaceExploreBarMiscContext';
import MiscFolderContext from '@/components/WorkspaceExploreBarMiscFolderContext';
import ModalNewSchema from '@/components/ModalNewSchema'; import ModalNewSchema from '@/components/ModalNewSchema';
import ModalNewTable from '@/components/ModalNewTable'; import ModalNewTable from '@/components/ModalNewTable';
import ModalNewView from '@/components/ModalNewView'; import ModalNewView from '@/components/ModalNewView';
@@ -160,11 +176,11 @@ import ModalNewScheduler from '@/components/ModalNewScheduler';
export default { export default {
name: 'WorkspaceExploreBar', name: 'WorkspaceExploreBar',
components: { components: {
WorkspaceConnectPanel,
WorkspaceExploreBarSchema, WorkspaceExploreBarSchema,
DatabaseContext, DatabaseContext,
TableContext, TableContext,
MiscContext, MiscContext,
MiscFolderContext,
ModalNewSchema, ModalNewSchema,
ModalNewTable, ModalNewTable,
ModalNewView, ModalNewView,
@@ -197,12 +213,13 @@ export default {
isDatabaseContext: false, isDatabaseContext: false,
isTableContext: false, isTableContext: false,
isMiscContext: false, isMiscContext: false,
isMiscFolderContext: false,
databaseContextEvent: null, databaseContextEvent: null,
tableContextEvent: null, tableContextEvent: null,
miscContextEvent: null, miscContextEvent: null,
selectedDatabase: '', selectedSchema: '',
selectedTable: null, selectedTable: null,
selectedMisc: null, selectedMisc: null,
searchTerm: '' searchTerm: ''
@@ -262,6 +279,7 @@ export default {
refreshStructure: 'workspaces/refreshStructure', refreshStructure: 'workspaces/refreshStructure',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs', changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
selectTab: 'workspaces/selectTab', selectTab: 'workspaces/selectTab',
newTab: 'workspaces/newTab',
setSearchTerm: 'workspaces/setSearchTerm', setSearchTerm: 'workspaces/setSearchTerm',
addNotification: 'notifications/addNotification', addNotification: 'notifications/addNotification',
changeExplorebarSize: 'settings/changeExplorebarSize' changeExplorebarSize: 'settings/changeExplorebarSize'
@@ -299,6 +317,7 @@ export default {
async openCreateTableEditor (payload) { async openCreateTableEditor (payload) {
const params = { const params = {
uid: this.connection.uid, uid: this.connection.uid,
schema: this.selectedSchema,
...payload ...payload
}; };
@@ -306,14 +325,19 @@ export default {
if (status === 'success') { if (status === 'success') {
await this.refresh(); await this.refresh();
this.changeBreadcrumbs({ schema: this.selectedDatabase, table: payload.name }); this.newTab({
this.selectTab({ uid: this.workspace.uid, tab: 'prop' }); uid: this.workspace.uid,
schema: this.selectedSchema,
elementName: payload.name,
elementType: 'table',
type: 'table-props'
});
} }
else else
this.addNotification({ status: 'error', message: response }); this.addNotification({ status: 'error', message: response });
}, },
openSchemaContext (payload) { openSchemaContext (payload) {
this.selectedDatabase = payload.schema; this.selectedSchema = payload.schema;
this.databaseContextEvent = payload.event; this.databaseContextEvent = payload.event;
this.isDatabaseContext = true; this.isDatabaseContext = true;
}, },
@@ -322,6 +346,7 @@ export default {
}, },
openTableContext (payload) { openTableContext (payload) {
this.selectedTable = payload.table; this.selectedTable = payload.table;
this.selectedSchema = payload.schema;
this.tableContextEvent = payload.event; this.tableContextEvent = payload.event;
this.isTableContext = true; this.isTableContext = true;
}, },
@@ -330,14 +355,25 @@ export default {
}, },
openMiscContext (payload) { openMiscContext (payload) {
this.selectedMisc = payload.misc; this.selectedMisc = payload.misc;
this.selectedSchema = payload.schema;
this.miscContextEvent = payload.event; this.miscContextEvent = payload.event;
this.isMiscContext = true; this.isMiscContext = true;
}, },
openMiscFolderContext (payload) {
this.selectedMisc = payload.type;
this.selectedSchema = payload.schema;
this.miscContextEvent = payload.event;
this.isMiscFolderContext = true;
},
closeMiscContext () { closeMiscContext () {
this.isMiscContext = false; this.isMiscContext = false;
}, },
closeMiscFolderContext () {
this.isMiscFolderContext = false;
},
showCreateViewModal () { showCreateViewModal () {
this.closeDatabaseContext(); this.closeDatabaseContext();
this.closeMiscFolderContext();
this.isNewViewModal = true; this.isNewViewModal = true;
}, },
hideCreateViewModal () { hideCreateViewModal () {
@@ -346,6 +382,7 @@ export default {
async openCreateViewEditor (payload) { async openCreateViewEditor (payload) {
const params = { const params = {
uid: this.connection.uid, uid: this.connection.uid,
schema: this.selectedSchema,
...payload ...payload
}; };
@@ -353,14 +390,22 @@ export default {
if (status === 'success') { if (status === 'success') {
await this.refresh(); await this.refresh();
this.changeBreadcrumbs({ schema: this.selectedDatabase, view: payload.name }); this.changeBreadcrumbs({ schema: this.selectedSchema, view: payload.name });
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
this.newTab({
uid: this.workspace.uid,
schema: this.selectedSchema,
elementName: payload.name,
elementType: 'view',
type: 'view-props'
});
} }
else else
this.addNotification({ status: 'error', message: response }); this.addNotification({ status: 'error', message: response });
}, },
showCreateTriggerModal () { showCreateTriggerModal () {
this.closeDatabaseContext(); this.closeDatabaseContext();
this.closeMiscFolderContext();
this.isNewTriggerModal = true; this.isNewTriggerModal = true;
}, },
hideCreateTriggerModal () { hideCreateTriggerModal () {
@@ -369,6 +414,7 @@ export default {
async openCreateTriggerEditor (payload) { async openCreateTriggerEditor (payload) {
const params = { const params = {
uid: this.connection.uid, uid: this.connection.uid,
schema: this.selectedSchema,
...payload ...payload
}; };
@@ -377,14 +423,22 @@ export default {
if (status === 'success') { if (status === 'success') {
await this.refresh(); await this.refresh();
const triggerName = this.customizations.triggerTableInName ? `${payload.table}.${payload.name}` : payload.name; const triggerName = this.customizations.triggerTableInName ? `${payload.table}.${payload.name}` : payload.name;
this.changeBreadcrumbs({ schema: this.selectedDatabase, trigger: triggerName }); this.changeBreadcrumbs({ schema: this.selectedSchema, trigger: triggerName });
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
this.newTab({
uid: this.workspace.uid,
schema: this.selectedSchema,
elementName: triggerName,
elementType: 'trigger',
type: 'trigger-props'
});
} }
else else
this.addNotification({ status: 'error', message: response }); this.addNotification({ status: 'error', message: response });
}, },
showCreateRoutineModal () { showCreateRoutineModal () {
this.closeDatabaseContext(); this.closeDatabaseContext();
this.closeMiscFolderContext();
this.isNewRoutineModal = true; this.isNewRoutineModal = true;
}, },
hideCreateRoutineModal () { hideCreateRoutineModal () {
@@ -393,6 +447,7 @@ export default {
async openCreateRoutineEditor (payload) { async openCreateRoutineEditor (payload) {
const params = { const params = {
uid: this.connection.uid, uid: this.connection.uid,
schema: this.selectedSchema,
...payload ...payload
}; };
@@ -400,14 +455,22 @@ export default {
if (status === 'success') { if (status === 'success') {
await this.refresh(); await this.refresh();
this.changeBreadcrumbs({ schema: this.selectedDatabase, procedure: payload.name }); this.changeBreadcrumbs({ schema: this.selectedSchema, routine: payload.name });
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
this.newTab({
uid: this.workspace.uid,
schema: this.selectedSchema,
elementName: payload.name,
elementType: 'routine',
type: 'routine-props'
});
} }
else else
this.addNotification({ status: 'error', message: response }); this.addNotification({ status: 'error', message: response });
}, },
showCreateFunctionModal () { showCreateFunctionModal () {
this.closeDatabaseContext(); this.closeDatabaseContext();
this.closeMiscFolderContext();
this.isNewFunctionModal = true; this.isNewFunctionModal = true;
}, },
hideCreateFunctionModal () { hideCreateFunctionModal () {
@@ -415,6 +478,7 @@ export default {
}, },
showCreateTriggerFunctionModal () { showCreateTriggerFunctionModal () {
this.closeDatabaseContext(); this.closeDatabaseContext();
this.closeMiscFolderContext();
this.isNewTriggerFunctionModal = true; this.isNewTriggerFunctionModal = true;
}, },
hideCreateTriggerFunctionModal () { hideCreateTriggerFunctionModal () {
@@ -422,6 +486,7 @@ export default {
}, },
showCreateSchedulerModal () { showCreateSchedulerModal () {
this.closeDatabaseContext(); this.closeDatabaseContext();
this.closeMiscFolderContext();
this.isNewSchedulerModal = true; this.isNewSchedulerModal = true;
}, },
hideCreateSchedulerModal () { hideCreateSchedulerModal () {
@@ -430,6 +495,7 @@ export default {
async openCreateFunctionEditor (payload) { async openCreateFunctionEditor (payload) {
const params = { const params = {
uid: this.connection.uid, uid: this.connection.uid,
schema: this.selectedSchema,
...payload ...payload
}; };
@@ -437,8 +503,15 @@ export default {
if (status === 'success') { if (status === 'success') {
await this.refresh(); await this.refresh();
this.changeBreadcrumbs({ schema: this.selectedDatabase, function: payload.name }); this.changeBreadcrumbs({ schema: this.selectedSchema, function: payload.name });
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
this.newTab({
uid: this.workspace.uid,
schema: this.selectedSchema,
elementName: payload.name,
elementType: 'function',
type: 'function-props'
});
} }
else else
this.addNotification({ status: 'error', message: response }); this.addNotification({ status: 'error', message: response });
@@ -446,6 +519,7 @@ export default {
async openCreateTriggerFunctionEditor (payload) { async openCreateTriggerFunctionEditor (payload) {
const params = { const params = {
uid: this.connection.uid, uid: this.connection.uid,
schema: this.selectedSchema,
...payload ...payload
}; };
@@ -453,8 +527,15 @@ export default {
if (status === 'success') { if (status === 'success') {
await this.refresh(); await this.refresh();
this.changeBreadcrumbs({ schema: this.selectedDatabase, triggerFunction: payload.name }); this.changeBreadcrumbs({ schema: this.selectedSchema, triggerFunction: payload.name });
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
this.newTab({
uid: this.workspace.uid,
schema: this.selectedSchema,
elementName: payload.name,
elementType: 'triggerFunction',
type: 'trigger-function-props'
});
} }
else else
this.addNotification({ status: 'error', message: response }); this.addNotification({ status: 'error', message: response });
@@ -462,6 +543,7 @@ export default {
async openCreateSchedulerEditor (payload) { async openCreateSchedulerEditor (payload) {
const params = { const params = {
uid: this.connection.uid, uid: this.connection.uid,
schema: this.selectedSchema,
...payload ...payload
}; };
@@ -469,8 +551,15 @@ export default {
if (status === 'success') { if (status === 'success') {
await this.refresh(); await this.refresh();
this.changeBreadcrumbs({ schema: this.selectedDatabase, scheduler: payload.name }); this.changeBreadcrumbs({ schema: this.selectedSchema, scheduler: payload.name });
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
this.newTab({
uid: this.workspace.uid,
schema: this.selectedSchema,
elementName: payload.name,
elementType: 'scheduler',
type: 'scheduler-props'
});
} }
else else
this.addNotification({ status: 'error', message: response }); this.addNotification({ status: 'error', message: response });

View File

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

@@ -16,14 +16,21 @@
<ul class="menu menu-nav pt-0"> <ul class="menu menu-nav pt-0">
<li <li
v-for="table of filteredTables" v-for="table of filteredTables"
:ref="breadcrumbs.schema === database.name && [breadcrumbs.table, breadcrumbs.view].includes(table.name) ? 'explorebar-selected' : ''"
:key="table.name" :key="table.name"
class="menu-item" class="menu-item"
:class="{'text-bold': breadcrumbs.schema === database.name && [breadcrumbs.table, breadcrumbs.view].includes(table.name)}" :class="{'selected': 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)" @contextmenu.prevent="showTableContext($event, table)"
> >
<a class="table-name"> <a class="table-name">
<i class="table-icon mdi mdi-18px mr-1" :class="table.type === 'view' ? 'mdi-table-eye' : 'mdi-table'" /> <div v-if="checkLoadingStatus(table.name, 'table')" class="icon loading mr-1" />
<i
v-else
class="table-icon mdi mdi-18px mr-1"
:class="table.type === 'view' ? 'mdi-table-eye' : 'mdi-table'"
/>
<span v-html="highlightWord(table.name)" /> <span v-html="highlightWord(table.name)" />
</a> </a>
<div <div
@@ -39,7 +46,11 @@
<div v-if="filteredTriggers.length && customizations.triggers" class="database-misc"> <div v-if="filteredTriggers.length && customizations.triggers" class="database-misc">
<details class="accordion"> <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" /> <i class="misc-icon mdi mdi-18px mdi-folder-cog mr-1" />
{{ $tc('word.trigger', 2) }} {{ $tc('word.trigger', 2) }}
</summary> </summary>
@@ -49,9 +60,11 @@
<li <li
v-for="trigger of filteredTriggers" v-for="trigger of filteredTriggers"
:key="trigger.name" :key="trigger.name"
:ref="breadcrumbs.schema === database.name && breadcrumbs.trigger === trigger.name ? 'explorebar-selected' : ''"
class="menu-item" class="menu-item"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.trigger === trigger.name}" :class="{'selected': 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'})" @contextmenu.prevent="showMiscContext($event, {...trigger, type: 'trigger'})"
> >
<a class="table-name"> <a class="table-name">
@@ -67,7 +80,11 @@
<div v-if="filteredProcedures.length && customizations.routines" class="database-misc"> <div v-if="filteredProcedures.length && customizations.routines" class="database-misc">
<details class="accordion"> <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" /> <i class="misc-icon mdi mdi-18px mdi-folder-sync mr-1" />
{{ $tc('word.storedRoutine', 2) }} {{ $tc('word.storedRoutine', 2) }}
</summary> </summary>
@@ -77,9 +94,11 @@
<li <li
v-for="(procedure, i) of filteredProcedures" v-for="(procedure, i) of filteredProcedures"
:key="`${procedure.name}-${i}`" :key="`${procedure.name}-${i}`"
:ref="breadcrumbs.schema === database.name && breadcrumbs.routine === procedure.name ? 'explorebar-selected' : ''"
class="menu-item" class="menu-item"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.procedure === procedure.name}" :class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.routine === procedure.name}"
@click="setBreadcrumbs({schema: database.name, procedure: 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'})" @contextmenu.prevent="showMiscContext($event, {...procedure, type: 'procedure'})"
> >
<a class="table-name"> <a class="table-name">
@@ -95,7 +114,11 @@
<div v-if="filteredTriggerFunctions.length && customizations.triggerFunctions" class="database-misc"> <div v-if="filteredTriggerFunctions.length && customizations.triggerFunctions" class="database-misc">
<details class="accordion"> <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" /> <i class="misc-icon mdi mdi-18px mdi-folder-refresh mr-1" />
{{ $tc('word.triggerFunction', 2) }} {{ $tc('word.triggerFunction', 2) }}
</summary> </summary>
@@ -105,9 +128,11 @@
<li <li
v-for="(func, i) of filteredTriggerFunctions" v-for="(func, i) of filteredTriggerFunctions"
:key="`${func.name}-${i}`" :key="`${func.name}-${i}`"
:ref="breadcrumbs.schema === database.name && breadcrumbs.triggerFunction === func.name ? 'explorebar-selected' : ''"
class="menu-item" class="menu-item"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.triggerFunction === func.name}" :class="{'selected': 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'})" @contextmenu.prevent="showMiscContext($event, {...func, type: 'triggerFunction'})"
> >
<a class="table-name"> <a class="table-name">
@@ -123,7 +148,11 @@
<div v-if="filteredFunctions.length && customizations.functions" class="database-misc"> <div v-if="filteredFunctions.length && customizations.functions" class="database-misc">
<details class="accordion"> <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" /> <i class="misc-icon mdi mdi-18px mdi-folder-move mr-1" />
{{ $tc('word.function', 2) }} {{ $tc('word.function', 2) }}
</summary> </summary>
@@ -133,9 +162,11 @@
<li <li
v-for="(func, i) of filteredFunctions" v-for="(func, i) of filteredFunctions"
:key="`${func.name}-${i}`" :key="`${func.name}-${i}`"
:ref="breadcrumbs.schema === database.name && breadcrumbs.function === func.name ? 'explorebar-selected' : ''"
class="menu-item" class="menu-item"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.function === func.name}" :class="{'selected': 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'})" @contextmenu.prevent="showMiscContext($event, {...func, type: 'function'})"
> >
<a class="table-name"> <a class="table-name">
@@ -151,7 +182,11 @@
<div v-if="filteredSchedulers.length && customizations.schedulers" class="database-misc"> <div v-if="filteredSchedulers.length && customizations.schedulers" class="database-misc">
<details class="accordion"> <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" /> <i class="misc-icon mdi mdi-18px mdi-folder-clock mr-1" />
{{ $tc('word.scheduler', 2) }} {{ $tc('word.scheduler', 2) }}
</summary> </summary>
@@ -161,9 +196,11 @@
<li <li
v-for="scheduler of filteredSchedulers" v-for="scheduler of filteredSchedulers"
:key="scheduler.name" :key="scheduler.name"
:ref="breadcrumbs.schema === database.name && breadcrumbs.scheduler === scheduler.name ? 'explorebar-selected' : ''"
class="menu-item" class="menu-item"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.scheduler === scheduler.name}" :class="{'selected': 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'})" @contextmenu.prevent="showMiscContext($event, {...scheduler, type: 'scheduler'})"
> >
<a class="table-name"> <a class="table-name">
@@ -225,11 +262,14 @@ export default {
filteredSchedulers () { filteredSchedulers () {
return this.database.schedulers.filter(scheduler => scheduler.name.search(this.searchTerm) >= 0); return this.database.schedulers.filter(scheduler => scheduler.name.search(this.searchTerm) >= 0);
}, },
workspace () {
return this.getWorkspace(this.connection.uid);
},
breadcrumbs () { breadcrumbs () {
return this.getWorkspace(this.connection.uid).breadcrumbs; return this.workspace.breadcrumbs;
}, },
customizations () { customizations () {
return this.getWorkspace(this.connection.uid).customizations; return this.workspace.customizations;
}, },
loadedSchemas () { loadedSchemas () {
return this.getLoadedSchemas(this.connection.uid); return this.getLoadedSchemas(this.connection.uid);
@@ -244,32 +284,101 @@ export default {
return this.database.tables.reduce((acc, curr) => acc + curr.size, 0); return this.database.tables.reduce((acc, curr) => acc + curr.size, 0);
} }
}, },
watch: {
breadcrumbs (newVal, oldVal) {
if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
setTimeout(() => {
const element = this.$refs['explorebar-selected'] ? this.$refs['explorebar-selected'][0] : null;
if (element) {
const rect = element.getBoundingClientRect();
const elemTop = rect.top;
const elemBottom = rect.bottom;
const isVisible = (elemTop >= 0) && (elemBottom <= window.innerHeight);
if (!isVisible) {
element.setAttribute('tabindex', '-1');
element.focus();
element.removeAttribute('tabindex');
}
}
}, 50);
}
}
},
methods: { methods: {
...mapActions({ ...mapActions({
changeBreadcrumbs: 'workspaces/changeBreadcrumbs', changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
addLoadedSchema: 'workspaces/addLoadedSchema',
newTab: 'workspaces/newTab',
refreshSchema: 'workspaces/refreshSchema' refreshSchema: 'workspaces/refreshSchema'
}), }),
formatBytes, formatBytes,
async selectSchema (schema) { async selectSchema (schema) {
if (!this.loadedSchemas.has(schema)) { if (!this.loadedSchemas.has(schema) && !this.isLoading) {
this.isLoading = true; this.isLoading = true;
await this.refreshSchema({ uid: this.connection.uid, schema }); await this.refreshSchema({ uid: this.connection.uid, schema });
this.addLoadedSchema(schema);
this.isLoading = false; this.isLoading = false;
} }
this.changeBreadcrumbs({ schema, table: null }); 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) { showSchemaContext (event, schema) {
this.selectSchema(schema);
this.$emit('show-schema-context', { event, schema }); this.$emit('show-schema-context', { event, schema });
}, },
showTableContext (event, table) { showTableContext (event, table) {
this.setBreadcrumbs({ schema: this.database.name, [table.type]: table.name }); this.$emit('show-table-context', { event, schema: this.database.name, table });
this.$emit('show-table-context', { event, table });
}, },
showMiscContext (event, misc) { showMiscContext (event, misc) {
this.setBreadcrumbs({ schema: this.database.name, [misc.type]: misc.name }); this.$emit('show-misc-context', { event, schema: this.database.name, misc });
this.$emit('show-misc-context', { event, misc }); },
showMiscFolderContext (event, type) {
this.$emit('show-misc-folder-context', { event, schema: this.database.name, type });
}, },
piePercentage (val) { piePercentage (val) {
const perc = val / this.maxSize * 100; const perc = val / this.maxSize * 100;
@@ -283,12 +392,20 @@ export default {
this.changeBreadcrumbs(payload); this.changeBreadcrumbs(payload);
}, },
highlightWord (string) { highlightWord (string) {
string = string.replaceAll('<', '&lt;').replaceAll('>', '&gt;');
if (this.searchTerm) { if (this.searchTerm) {
const regexp = new RegExp(`(${this.searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'); const regexp = new RegExp(`(${this.searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
return string.replace(regexp, '<span class="text-primary">$1</span>'); return string.replace(regexp, '<span class="text-primary">$1</span>');
} }
else else
return string; return string;
},
checkLoadingStatus (name, type) {
return this.workspace.loadingElements.some(el =>
el.name === name &&
el.type === type &&
el.schema === this.database.name);
} }
} }
}; };
@@ -296,6 +413,10 @@ export default {
<style lang="scss"> <style lang="scss">
.workspace-explorebar-database { .workspace-explorebar-database {
.selected {
font-weight: 700;
}
.database-name { .database-name {
position: sticky; position: sticky;
top: 0; top: 0;
@@ -352,7 +473,8 @@ export default {
line-height: 1.2; line-height: 1.2;
position: relative; position: relative;
&:hover { &:hover,
&.selected {
border-radius: $border-radius; border-radius: $border-radius;
} }
} }

View File

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

View File

@@ -4,14 +4,28 @@
@close-context="closeContext" @close-context="closeContext"
> >
<div <div
v-if="selectedTable.type === 'table'" v-if="selectedTable && selectedTable.type === 'table' && 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 && selectedTable.type === 'view' && 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 && selectedTable.type === 'table'"
class="context-element" class="context-element"
@click="duplicateTable" @click="duplicateTable"
> >
<span class="d-flex"><i class="mdi mdi-18px mdi-table-multiple text-light pr-1" /> {{ $t('message.duplicateTable') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-table-multiple text-light pr-1" /> {{ $t('message.duplicateTable') }}</span>
</div> </div>
<div <div
v-if="selectedTable.type === 'table'" v-if="selectedTable && selectedTable.type === 'table'"
class="context-element" class="context-element"
@click="showEmptyModal" @click="showEmptyModal"
> >
@@ -72,7 +86,8 @@ export default {
}, },
props: { props: {
contextEvent: MouseEvent, contextEvent: MouseEvent,
selectedTable: Object selectedTable: Object,
selectedSchema: String
}, },
data () { data () {
return { return {
@@ -87,11 +102,18 @@ export default {
}), }),
workspace () { workspace () {
return this.getWorkspace(this.selectedWorkspace); return this.getWorkspace(this.selectedWorkspace);
},
customizations () {
return this.workspace && this.workspace.customizations ? this.workspace.customizations : {};
} }
}, },
methods: { methods: {
...mapActions({ ...mapActions({
addNotification: 'notifications/addNotification', addNotification: 'notifications/addNotification',
newTab: 'workspaces/newTab',
removeTabs: 'workspaces/removeTabs',
addLoadingElement: 'workspaces/addLoadingElement',
removeLoadingElement: 'workspaces/removeLoadingElement',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs' changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
}), }),
showCreateTableModal () { showCreateTableModal () {
@@ -112,69 +134,137 @@ export default {
closeContext () { closeContext () {
this.$emit('close-context'); 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 () { async duplicateTable () {
this.closeContext();
this.addLoadingElement({
name: this.selectedTable.name,
schema: this.selectedSchema,
type: 'table'
});
try { try {
const { status, response } = await Tables.duplicateTable({ const { status, response } = await Tables.duplicateTable({
uid: this.selectedWorkspace, uid: this.selectedWorkspace,
table: this.selectedTable.name table: this.selectedTable.name,
schema: this.selectedSchema
}); });
if (status === 'success') { if (status === 'success')
this.closeContext();
this.$emit('reload'); this.$emit('reload');
}
else else
this.addNotification({ status: 'error', message: response }); this.addNotification({ status: 'error', message: response });
} }
catch (err) { catch (err) {
this.addNotification({ status: 'error', message: err.stack }); this.addNotification({ status: 'error', message: err.stack });
} }
this.removeLoadingElement({
name: this.selectedTable.name,
schema: this.selectedSchema,
type: 'table'
});
}, },
async emptyTable () { async emptyTable () {
this.closeContext();
this.addLoadingElement({
name: this.selectedTable.name,
schema: this.selectedSchema,
type: 'table'
});
try { try {
const { status, response } = await Tables.truncateTable({ const { status, response } = await Tables.truncateTable({
uid: this.selectedWorkspace, uid: this.selectedWorkspace,
table: this.selectedTable.name table: this.selectedTable.name,
schema: this.selectedSchema
}); });
if (status === 'success') { if (status === 'success')
if (this.selectedTable.name === this.workspace.breadcrumbs.table)
this.changeBreadcrumbs({ table: null });
this.closeContext();
this.$emit('reload'); this.$emit('reload');
}
else else
this.addNotification({ status: 'error', message: response }); this.addNotification({ status: 'error', message: response });
} }
catch (err) { catch (err) {
this.addNotification({ status: 'error', message: err.stack }); this.addNotification({ status: 'error', message: err.stack });
} }
this.removeLoadingElement({
name: this.selectedTable.name,
schema: this.selectedSchema,
type: 'table'
});
}, },
async deleteTable () { async deleteTable () {
this.closeContext();
this.addLoadingElement({
name: this.selectedTable.name,
schema: this.selectedSchema,
type: 'table'
});
try { try {
let res; let res;
if (this.selectedTable.type === 'table') { if (this.selectedTable.type === 'table') {
res = await Tables.dropTable({ res = await Tables.dropTable({
uid: this.selectedWorkspace, uid: this.selectedWorkspace,
table: this.selectedTable.name table: this.selectedTable.name,
schema: this.selectedSchema
}); });
} }
else if (this.selectedTable.type === 'view') { else if (this.selectedTable.type === 'view') {
res = await Views.dropView({ res = await Views.dropView({
uid: this.selectedWorkspace, uid: this.selectedWorkspace,
view: this.selectedTable.name view: this.selectedTable.name,
schema: this.selectedSchema
}); });
} }
const { status, response } = res; const { status, response } = res;
if (status === 'success') { if (status === 'success') {
if (this.selectedTable.name === this.workspace.breadcrumbs.table || this.selectedTable.name === this.workspace.breadcrumbs.view) this.removeTabs({
this.changeBreadcrumbs({ table: null, view: null }); uid: this.selectedWorkspace,
elementName: this.selectedTable.name,
elementType: this.selectedTable.type,
schema: this.selectedSchema
});
this.closeContext();
this.$emit('reload'); this.$emit('reload');
} }
else else
@@ -183,6 +273,12 @@ export default {
catch (err) { catch (err) {
this.addNotification({ status: 'error', message: err.stack }); this.addNotification({ status: 'error', message: err.stack });
} }
this.removeLoadingElement({
name: this.selectedTable.name,
schema: this.selectedSchema,
type: 'table'
});
} }
} }
}; };

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
<template> <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 column col-12">
<div class="workspace-query-runner-footer"> <div class="workspace-query-runner-footer">
<div class="workspace-query-buttons"> <div class="workspace-query-buttons">
@@ -10,46 +10,61 @@
title="CTRL+S" title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span> <span>{{ $t('word.save') }}</span>
<i class="mdi mdi-24px mdi-content-save ml-1" />
</button> </button>
<button <button
:disabled="!isChanged" :disabled="!isChanged || isSaving"
class="btn btn-link btn-sm mr-0" class="btn btn-link btn-sm mr-0"
:title="$t('message.clearChanges')" :title="$t('message.clearChanges')"
@click="clearChanges" @click="clearChanges"
> >
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button> </button>
<div class="divider-vert py-3" /> <div class="divider-vert py-3" />
<button <button
:disabled="isSaving"
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
:title="$t('message.addNewField')" :title="$t('message.addNewField')"
@click="addField" @click="addField"
> >
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
<span>{{ $t('word.add') }}</span> <span>{{ $t('word.add') }}</span>
<i class="mdi mdi-24px mdi-playlist-plus ml-1" />
</button> </button>
<button <button
:disabled="isSaving"
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
:title="$t('message.manageIndexes')" :title="$t('message.manageIndexes')"
@click="showIntdexesModal" @click="showIntdexesModal"
> >
<i class="mdi mdi-24px mdi-key mdi-rotate-45 mr-1" />
<span>{{ $t('word.indexes') }}</span> <span>{{ $t('word.indexes') }}</span>
<i class="mdi mdi-24px mdi-key mdi-rotate-45 ml-1" />
</button> </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> <span>{{ $t('word.foreignKeys') }}</span>
<i class="mdi mdi-24px mdi-key-link ml-1" />
</button> </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> <span>{{ $t('word.options') }}</span>
<i class="mdi mdi-24px mdi-cogs ml-1" />
</button> </button>
</div> </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> </div>
<div class="workspace-query-results column col-12 p-relative"> <div class="workspace-query-results column col-12 p-relative">
@@ -66,6 +81,7 @@
:table="table" :table="table"
:schema="schema" :schema="schema"
mode="table" mode="table"
@duplicate-field="duplicateField"
@remove-field="removeField" @remove-field="removeField"
@add-new-index="addNewIndex" @add-new-index="addNewIndex"
@add-to-index="addToIndex" @add-to-index="addToIndex"
@@ -126,11 +142,12 @@ export default {
}, },
props: { props: {
connection: Object, connection: Object,
table: String isSelected: Boolean,
table: String,
schema: String
}, },
data () { data () {
return { return {
tabUid: 'prop',
isLoading: false, isLoading: false,
isSaving: false, isSaving: false,
isOptionsModal: false, isOptionsModal: false,
@@ -157,6 +174,9 @@ export default {
workspace () { workspace () {
return this.getWorkspace(this.connection.uid); return this.getWorkspace(this.connection.uid);
}, },
tabUid () {
return this.$vnode.key;
},
tableOptions () { tableOptions () {
const db = this.workspace.structure.find(db => db.name === this.schema); const db = this.workspace.structure.find(db => db.name === this.schema);
return db && this.table ? db.tables.find(table => table.name === this.table) : {}; return db && this.table ? db.tables.find(table => table.name === this.table) : {};
@@ -164,12 +184,6 @@ export default {
defaultEngine () { defaultEngine () {
return this.getDatabaseVariable(this.connection.uid, 'default_storage_engine').value || ''; 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 () { schemaTables () {
const schemaTables = this.workspace.structure const schemaTables = this.workspace.structure
.filter(schema => schema.name === this.schema) .filter(schema => schema.name === this.schema)
@@ -185,6 +199,12 @@ export default {
} }
}, },
watch: { watch: {
schema () {
if (this.isSelected) {
this.getFieldsData();
this.lastTable = this.table;
}
},
table () { table () {
if (this.isSelected) { if (this.isSelected) {
this.getFieldsData(); this.getFieldsData();
@@ -192,17 +212,19 @@ export default {
} }
}, },
isSelected (val) { isSelected (val) {
if (val && this.lastTable !== this.table) { if (val) {
this.getFieldsData(); this.changeBreadcrumbs({ schema: this.schema, table: this.table });
this.lastTable = this.table;
if (this.lastTable !== this.table)
this.getFieldsData();
} }
}, },
isChanged (val) { isChanged (val) {
if (this.isSelected && this.lastTable === this.table && this.table !== null) this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
this.setUnsavedChanges(val);
} }
}, },
created () { created () {
this.getFieldsData();
window.addEventListener('keydown', this.onKey); window.addEventListener('keydown', this.onKey);
}, },
beforeDestroy () { beforeDestroy () {
@@ -213,20 +235,25 @@ export default {
addNotification: 'notifications/addNotification', addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure', refreshStructure: 'workspaces/refreshStructure',
setUnsavedChanges: 'workspaces/setUnsavedChanges', setUnsavedChanges: 'workspaces/setUnsavedChanges',
renameTabs: 'workspaces/renameTabs',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs' changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
}), }),
async getFieldsData () { async getFieldsData () {
if (!this.table) return; if (!this.table) return;
this.localFields = []; this.localFields = [];
this.lastTable = this.table;
this.newFieldsCounter = 0; this.newFieldsCounter = 0;
this.isLoading = true; this.isLoading = true;
this.localOptions = JSON.parse(JSON.stringify(this.tableOptions)); try {
this.localOptions = JSON.parse(JSON.stringify(this.tableOptions));
}
catch (err) {}
const params = { const params = {
uid: this.connection.uid, uid: this.connection.uid,
schema: this.schema, schema: this.schema,
table: this.workspace.breadcrumbs.table table: this.table
}; };
try { // Columns data try { // Columns data
@@ -410,7 +437,7 @@ export default {
const params = { const params = {
uid: this.connection.uid, uid: this.connection.uid,
schema: this.schema, schema: this.schema,
table: this.workspace.breadcrumbs.table, table: this.table,
additions, additions,
changes, changes,
deletions, deletions,
@@ -428,11 +455,18 @@ export default {
await this.refreshStructure(this.connection.uid); await this.refreshStructure(this.connection.uid);
if (oldName !== this.localOptions.name) { 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.changeBreadcrumbs({ schema: this.schema, table: this.localOptions.name });
} }
else
this.getFieldsData(); this.getFieldsData();
} }
else else
this.addNotification({ status: 'error', message: response }); this.addNotification({ status: 'error', message: response });
@@ -494,6 +528,13 @@ export default {
return key; return key;
}); });
}, },
duplicateField (uid) {
const fieldToClone = Object.assign({}, this.localFields.find(field => field._id === uid));
fieldToClone._id = uidGen();
fieldToClone.name = `${fieldToClone.name}_copy`;
fieldToClone.order = this.localFields.length + 1;
this.localFields = [...this.localFields, fieldToClone];
},
removeField (uid) { removeField (uid) {
this.localFields = this.localFields.filter(field => field._id !== uid); this.localFields = this.localFields.filter(field => field._id !== uid);
}, },

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,6 +11,7 @@
:index-types="indexTypes" :index-types="indexTypes"
:indexes="indexes" :indexes="indexes"
@delete-selected="removeField" @delete-selected="removeField"
@duplicate-selected="duplicateField"
@close-context="isContext = false" @close-context="isContext = false"
@add-new-index="$emit('add-new-index', $event)" @add-new-index="$emit('add-new-index', $event)"
@add-to-index="$emit('add-to-index', $event)" @add-to-index="$emit('add-to-index', $event)"
@@ -100,7 +101,7 @@
</div> </div>
</div> </div>
</div> </div>
<draggable <Draggable
ref="resultTable" ref="resultTable"
:list="fields" :list="fields"
class="tbody" class="tbody"
@@ -117,14 +118,14 @@
@contextmenu="contextMenu" @contextmenu="contextMenu"
@rename-field="$emit('rename-field', $event)" @rename-field="$emit('rename-field', $event)"
/> />
</draggable> </Draggable>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import draggable from 'vuedraggable'; import Draggable from 'vuedraggable';
import TableRow from '@/components/WorkspacePropsTableRow'; import TableRow from '@/components/WorkspacePropsTableRow';
import TableContext from '@/components/WorkspacePropsTableContext'; import TableContext from '@/components/WorkspacePropsTableContext';
@@ -133,7 +134,7 @@ export default {
components: { components: {
TableRow, TableRow,
TableContext, TableContext,
draggable Draggable
}, },
props: { props: {
fields: Array, fields: Array,
@@ -220,6 +221,9 @@ export default {
this.contextEvent = event; this.contextEvent = event;
this.isContext = true; this.isContext = true;
}, },
duplicateField () {
this.$emit('duplicate-field', this.selectedField._id);
},
removeField () { removeField () {
this.$emit('remove-field', this.selectedField._id); this.$emit('remove-field', this.selectedField._id);
}, },

View File

@@ -33,6 +33,9 @@
</div> </div>
</div> </div>
</div> </div>
<div class="context-element" @click="duplicateField">
<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="deleteField"> <div class="context-element" @click="deleteField">
<span class="d-flex"><i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ $t('message.deleteField') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ $t('message.deleteField') }}</span>
</div> </div>
@@ -62,6 +65,10 @@ export default {
closeContext () { closeContext () {
this.$emit('close-context'); this.$emit('close-context');
}, },
duplicateField () {
this.$emit('duplicate-selected');
this.closeContext();
},
deleteField () { deleteField () {
this.$emit('delete-selected'); this.$emit('delete-selected');
this.closeContext(); this.closeContext();

View File

@@ -450,7 +450,7 @@ export default {
}); });
this.defaultValue.onUpdate = this.localRow.onUpdate; this.defaultValue.onUpdate = this.localRow.onUpdate;
this.defaultValue.type = this.localRow.defaultType; this.defaultValue.type = this.localRow.defaultType || 'noval';
if (this.defaultValue.type === 'custom') { if (this.defaultValue.type === 'custom') {
this.defaultValue.custom = this.localRow.default this.defaultValue.custom = this.localRow.default
? this.localRow.default.includes('\'') ? this.localRow.default.includes('\'')
@@ -458,8 +458,12 @@ export default {
: this.localRow.default : this.localRow.default
: ''; : '';
} }
if (this.defaultValue.type === 'expression') else if (this.defaultValue.type === 'expression') {
this.defaultValue.expression = 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) { editON (event, content, field) {
if (field === 'length') { if (field === 'length') {

View File

@@ -0,0 +1,41 @@
<template>
<div class="container">
<div class="columns">
<div class="column col-16 text-right">
<div class="mb-4">
{{ $t('message.runQuery') }}
</div>
<div class="mb-4">
{{ $t('word.format') }}
</div>
<div class="mb-4">
{{ $t('word.clear') }}
</div>
</div>
<div class="column col-16">
<div class="mb-4">
<code>F5</code>
</div>
<div class="mb-4">
<code>CTRL</code> + <code>F8</code>
</div>
<div class="mb-4">
<code>CTRL</code> + <code>W</code>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'WorkspaceQueryEmptyState'
};
</script>
<style scoped>
.container {
padding-top: 15vh;
opacity: 0.6;
}
</style>

View File

@@ -1,7 +1,7 @@
<template> <template>
<div <div
v-show="isSelected" 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" tabindex="0"
@keydown.116="runQuery(query)" @keydown.116="runQuery(query)"
@keydown.ctrl.87="clear" @keydown.ctrl.87="clear"
@@ -14,7 +14,7 @@
:auto-focus="true" :auto-focus="true"
:value.sync="query" :value.sync="query"
:workspace="workspace" :workspace="workspace"
:schema="schema" :schema="breadcrumbsSchema"
:is-selected="isSelected" :is-selected="isSelected"
:height="editorHeight" :height="editorHeight"
/> />
@@ -28,8 +28,8 @@
title="F5" title="F5"
@click="runQuery(query)" @click="runQuery(query)"
> >
<i class="mdi mdi-24px mdi-play pr-1" />
<span>{{ $t('word.run') }}</span> <span>{{ $t('word.run') }}</span>
<i class="mdi mdi-24px mdi-play" />
</button> </button>
<button <button
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
@@ -37,8 +37,8 @@
title="CTRL+F8" title="CTRL+F8"
@click="beautify()" @click="beautify()"
> >
<i class="mdi mdi-24px mdi-brush pr-1" />
<span>{{ $t('word.format') }}</span> <span>{{ $t('word.format') }}</span>
<i class="mdi mdi-24px mdi-brush pl-1" />
</button> </button>
<button <button
class="btn btn-link btn-sm" class="btn btn-link btn-sm"
@@ -46,8 +46,8 @@
title="CTRL+W" title="CTRL+W"
@click="clear()" @click="clear()"
> >
<i class="mdi mdi-24px mdi-delete-sweep pr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep pl-1" />
</button> </button>
<div class="divider-vert py-3" /> <div class="divider-vert py-3" />
@@ -58,8 +58,8 @@
class="btn btn-dark btn-sm dropdown-toggle mr-0 pr-0" class="btn btn-dark btn-sm dropdown-toggle mr-0 pr-0"
tabindex="0" tabindex="0"
> >
<i class="mdi mdi-24px mdi-file-export mr-1" />
<span>{{ $t('word.export') }}</span> <span>{{ $t('word.export') }}</span>
<i class="mdi mdi-24px mdi-file-export ml-1" />
<i class="mdi mdi-24px mdi-menu-down" /> <i class="mdi mdi-24px mdi-menu-down" />
</button> </button>
<ul class="menu text-left"> <ul class="menu text-left">
@@ -86,16 +86,21 @@
<div v-if="affectedCount"> <div v-if="affectedCount">
{{ $t('message.affectedRows') }}: <b>{{ affectedCount }}</b> {{ $t('message.affectedRows') }}: <b>{{ affectedCount }}</b>
</div> </div>
<div <div class="input-group" :title="$t('word.schema')">
v-if="workspace.breadcrumbs.schema" <i class="input-group-addon addon-sm mdi mdi-24px mdi-database" />
class="d-flex" <select v-model="selectedSchema" class="form-select select-sm text-bold">
:title="$t('word.schema')" <option :value="null">
> {{ $t('message.noSchema') }}
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ workspace.breadcrumbs.schema }}</b> </option>
<option v-for="schemaName in databaseSchemas" :key="schemaName">
{{ schemaName }}
</option>
</select>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<WorkspaceQueryEmptyState v-if="!results.length && !isQuering" />
<div class="workspace-query-results p-relative column col-12"> <div class="workspace-query-results p-relative column col-12">
<BaseLoader v-if="isQuering" /> <BaseLoader v-if="isQuering" />
<WorkspaceQueryTable <WorkspaceQueryTable
@@ -120,6 +125,7 @@ import Schema from '@/ipc-api/Schema';
import QueryEditor from '@/components/QueryEditor'; import QueryEditor from '@/components/QueryEditor';
import BaseLoader from '@/components/BaseLoader'; import BaseLoader from '@/components/BaseLoader';
import WorkspaceQueryTable from '@/components/WorkspaceQueryTable'; import WorkspaceQueryTable from '@/components/WorkspaceQueryTable';
import WorkspaceQueryEmptyState from '@/components/WorkspaceQueryEmptyState';
import { mapGetters, mapActions } from 'vuex'; import { mapGetters, mapActions } from 'vuex';
import tableTabs from '@/mixins/tableTabs'; import tableTabs from '@/mixins/tableTabs';
@@ -128,7 +134,8 @@ export default {
components: { components: {
BaseLoader, BaseLoader,
QueryEditor, QueryEditor,
WorkspaceQueryTable WorkspaceQueryTable,
WorkspaceQueryEmptyState
}, },
mixins: [tableTabs], mixins: [tableTabs],
props: { props: {
@@ -142,6 +149,7 @@ export default {
lastQuery: '', lastQuery: '',
isQuering: false, isQuering: false,
results: [], results: [],
selectedSchema: null,
resultsCount: 0, resultsCount: 0,
durationsCount: 0, durationsCount: 0,
affectedCount: 0, affectedCount: 0,
@@ -156,12 +164,32 @@ export default {
workspace () { workspace () {
return this.getWorkspace(this.connection.uid); 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 () { isWorkspaceSelected () {
return this.workspace.uid === this.selectedWorkspace; 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 () { created () {
this.query = this.tab.content; 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); window.addEventListener('keydown', this.onKey);
}, },
@@ -183,7 +211,9 @@ export default {
}, },
methods: { methods: {
...mapActions({ ...mapActions({
addNotification: 'notifications/addNotification' addNotification: 'notifications/addNotification',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
updateTabContent: 'workspaces/updateTabContent'
}), }),
async runQuery (query) { async runQuery (query) {
if (!query || this.isQuering) return; if (!query || this.isQuering) return;
@@ -194,7 +224,7 @@ export default {
try { // Query Data try { // Query Data
const params = { const params = {
uid: this.connection.uid, uid: this.connection.uid,
schema: this.schema, schema: this.selectedSchema,
query query
}; };
@@ -205,6 +235,8 @@ export default {
this.resultsCount += this.results.reduce((acc, curr) => acc + (curr.rows ? curr.rows.length : 0), 0); 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.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.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 else
this.addNotification({ status: 'error', message: response }); this.addNotification({ status: 'error', message: response });
@@ -303,8 +335,10 @@ export default {
align-items: center; align-items: center;
height: 42px; height: 42px;
.workspace-query-buttons { .workspace-query-buttons,
.workspace-query-info {
display: flex; display: flex;
align-items: center;
.btn { .btn {
display: flex; display: flex;
@@ -314,7 +348,7 @@ export default {
} }
.workspace-query-info { .workspace-query-info {
display: flex; overflow: visible;
> div + div { > div + div {
padding-left: 0.6rem; padding-left: 0.6rem;

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
<template> <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 column col-12">
<div class="workspace-query-runner-footer"> <div class="workspace-query-runner-footer">
<div class="workspace-query-buttons"> <div class="workspace-query-buttons">
@@ -11,9 +11,9 @@
title="F5" title="F5"
@click="reloadTable" @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> <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> </button>
<div class="btn btn-dark btn-sm dropdown-toggle pl-0 pr-0" tabindex="0"> <div class="btn btn-dark btn-sm dropdown-toggle pl-0 pr-0" tabindex="0">
<i class="mdi mdi-24px mdi-menu-down" /> <i class="mdi mdi-24px mdi-menu-down" />
@@ -77,8 +77,8 @@
:disabled="isQuering" :disabled="isQuering"
@click="showFakerModal" @click="showFakerModal"
> >
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
<span>{{ $t('message.tableFiller') }}</span> <span>{{ $t('message.tableFiller') }}</span>
<i class="mdi mdi-24px mdi-playlist-plus ml-1" />
</button> </button>
<div class="dropdown table-dropdown pr-2"> <div class="dropdown table-dropdown pr-2">
@@ -87,8 +87,8 @@
class="btn btn-dark btn-sm dropdown-toggle mr-0 pr-0" class="btn btn-dark btn-sm dropdown-toggle mr-0 pr-0"
tabindex="0" tabindex="0"
> >
<i class="mdi mdi-24px mdi-file-export mr-1" />
<span>{{ $t('word.export') }}</span> <span>{{ $t('word.export') }}</span>
<i class="mdi mdi-24px mdi-file-export ml-1" />
<i class="mdi mdi-24px mdi-menu-down" /> <i class="mdi mdi-24px mdi-menu-down" />
</button> </button>
<ul class="menu text-left"> <ul class="menu text-left">
@@ -112,11 +112,11 @@
<div v-if="results.length && results[0].rows"> <div v-if="results.length && results[0].rows">
{{ $t('word.results') }}: <b>{{ results[0].rows.length | localeString }}</b> {{ $t('word.results') }}: <b>{{ results[0].rows.length | localeString }}</b>
</div> </div>
<div v-if="hasApproximately || (page > 1 && tableInfo.rows)"> <div v-if="hasApproximately || (page > 1 && approximateCount)">
{{ $t('word.total') }}: <b>{{ tableInfo.rows | localeString }}</b> <small>({{ $t('word.approximately') }})</small> {{ $t('word.total') }}: <b :title="$t('word.approximately')"> {{ approximateCount | localeString }}</b>
</div> </div>
<div v-if="workspace.breadcrumbs.database"> <div class="d-flex" :title="$t('word.schema')">
{{ $t('word.schema') }}: <b>{{ workspace.breadcrumbs.database }}</b> <i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div> </div>
</div> </div>
</div> </div>
@@ -131,6 +131,7 @@
:conn-uid="connection.uid" :conn-uid="connection.uid"
:is-selected="isSelected" :is-selected="isSelected"
mode="table" mode="table"
:element-type="elementType"
@update-field="updateField" @update-field="updateField"
@delete-selected="deleteSelected" @delete-selected="deleteSelected"
@hard-sort="hardSort" @hard-sort="hardSort"
@@ -174,18 +175,21 @@ export default {
}, },
filters: { filters: {
localeString (val) { localeString (val) {
if (val) if (val !== null)
return val.toLocaleString(); return val.toLocaleString();
} }
}, },
mixins: [tableTabs], mixins: [tableTabs],
props: { props: {
connection: Object, connection: Object,
table: String isSelected: Boolean,
table: String,
schema: String,
elementType: String
}, },
data () { data () {
return { return {
tabUid: 'data', tabUid: 'data', // ???
isQuering: false, isQuering: false,
isPageMenu: false, isPageMenu: false,
results: [], results: [],
@@ -196,7 +200,8 @@ export default {
refreshInterval: null, refreshInterval: null,
sortParams: {}, sortParams: {},
page: 1, page: 1,
pageProxy: 1 pageProxy: 1,
approximateCount: 0
}; };
}, },
computed: { computed: {
@@ -208,9 +213,6 @@ export default {
workspace () { workspace () {
return this.getWorkspace(this.connection.uid); return this.getWorkspace(this.connection.uid);
}, },
isSelected () {
return this.workspace.selected_tab === 'data' && this.workspace.uid === this.selectedWorkspace;
},
isTable () { isTable () {
return !!this.workspace.breadcrumbs.table; return !!this.workspace.breadcrumbs.table;
}, },
@@ -231,15 +233,25 @@ export default {
hasApproximately () { hasApproximately () {
return this.results.length && return this.results.length &&
this.results[0].rows && this.results[0].rows &&
this.tableInfo &&
this.results[0].rows.length === this.limit && this.results[0].rows.length === this.limit &&
this.results[0].rows.length < this.tableInfo.rows; this.results[0].rows.length < this.approximateCount;
} }
}, },
watch: { watch: {
schema () {
if (this.isSelected) {
this.page = 1;
this.approximateCount = 0;
this.sortParams = {};
this.getTableData();
this.lastTable = this.table;
this.$refs.queryTable.resetSort();
}
},
table () { table () {
if (this.isSelected) { if (this.isSelected) {
this.page = 1; this.page = 1;
this.approximateCount = 0;
this.sortParams = {}; this.sortParams = {};
this.getTableData(); this.getTableData();
this.lastTable = this.table; this.lastTable = this.table;
@@ -253,9 +265,11 @@ export default {
} }
}, },
isSelected (val) { isSelected (val) {
if (val && this.lastTable !== this.table) { if (val) {
this.getTableData(); this.changeBreadcrumbs({ schema: this.schema, [this.elementType]: this.table });
this.lastTable = this.table;
if (this.lastTable !== this.table)
this.getTableData();
} }
} }
}, },
@@ -269,9 +283,10 @@ export default {
}, },
methods: { methods: {
...mapActions({ ...mapActions({
addNotification: 'notifications/addNotification' addNotification: 'notifications/addNotification',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
}), }),
async getTableData (sortParams) { async getTableData () {
if (!this.table) return; if (!this.table) return;
this.isQuering = true; this.isQuering = true;
@@ -279,13 +294,15 @@ export default {
if (this.lastTable !== this.table) if (this.lastTable !== this.table)
this.results = []; this.results = [];
this.lastTable = this.table;
const params = { const params = {
uid: this.connection.uid, uid: this.connection.uid,
schema: this.schema, schema: this.schema,
table: this.workspace.breadcrumbs.table || this.workspace.breadcrumbs.view, table: this.table,
limit: this.limit, limit: this.limit,
page: this.page, page: this.page,
sortParams sortParams: this.sortParams
}; };
try { // Table data try { // Table data
@@ -300,20 +317,34 @@ export default {
this.addNotification({ status: 'error', message: err.stack }); this.addNotification({ status: 'error', message: err.stack });
} }
if (this.results.length && this.results[0].rows.length === this.limit) {
try { // Table approximate count
const { status, response } = await Tables.getTableApproximateCount(params);
if (status === 'success')
this.approximateCount = response;
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
}
this.isQuering = false; this.isQuering = false;
}, },
getTable () { getTable () {
return this.table; return this.table;
}, },
reloadTable () { reloadTable () {
this.getTableData(this.sortParams); this.getTableData();
}, },
hardSort (sortParams) { hardSort (sortParams) {
this.sortParams = sortParams; this.sortParams = sortParams;
this.getTableData(sortParams); this.getTableData();
}, },
openPageMenu () { 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; this.isPageMenu = true;
if (this.isPageMenu) if (this.isPageMenu)

View File

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

View File

@@ -106,13 +106,17 @@ module.exports = {
array: 'Array', array: 'Array',
changelog: 'Changelog', changelog: 'Changelog',
format: 'Format', format: 'Format',
sshTunnel: 'SSH tunnel',
structure: 'Structure', structure: 'Structure',
small: 'Small', small: 'Small',
medium: 'Medium', medium: 'Medium',
large: 'Large', large: 'Large',
row: 'Row | Rows', row: 'Row | Rows',
cell: 'Cell | Cells', cell: 'Cell | Cells',
triggerFunction: 'Trigger function | Trigger functions' triggerFunction: 'Trigger function | Trigger functions',
all: 'All',
duplicate: 'Duplicate',
routine: 'Routine'
}, },
message: { message: {
appWelcome: 'Welcome to Antares SQL Client!', appWelcome: 'Welcome to Antares SQL Client!',
@@ -166,7 +170,7 @@ module.exports = {
deleteTable: 'Delete table', deleteTable: 'Delete table',
emptyCorfirm: 'Do you confirm to empty', emptyCorfirm: 'Do you confirm to empty',
unsavedChanges: 'Unsaved changes', 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', thereAreNoIndexes: 'There are no indexes',
thereAreNoForeign: 'There are no foreign keys', thereAreNoForeign: 'There are no foreign keys',
createNewForeign: 'Create new foreign key', createNewForeign: 'Create new foreign key',
@@ -219,8 +223,13 @@ module.exports = {
markdownSupported: 'Markdown supported', markdownSupported: 'Markdown supported',
plantATree: 'Plant a Tree', plantATree: 'Plant a Tree',
dataTabPageSize: 'DATA tab page size', dataTabPageSize: 'DATA tab page size',
enableSsh: 'Enable SSH',
pageNumber: 'Page number', 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',
runQuery: 'Run query'
}, },
faker: { faker: {
address: 'Address', address: 'Address',

View File

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

View File

@@ -105,7 +105,17 @@ module.exports = {
scratchpad: 'Blocco appunti', scratchpad: 'Blocco appunti',
array: 'Array', array: 'Array',
changelog: 'Changelog', changelog: 'Changelog',
format: 'Formatta' format: 'Formatta',
sshTunnel: 'SSH tunnel',
structure: 'Structure',
small: 'Piccolo',
medium: 'Medio',
large: 'Largo',
row: 'Riga | Righe',
cell: 'Cella | Celle',
triggerFunction: 'Funzione di trigger | Funzioni di trigger',
all: 'Tutto',
duplicate: 'Duplica'
}, },
message: { message: {
appWelcome: 'Benvenuto in Antares SQL Client!', appWelcome: 'Benvenuto in Antares SQL Client!',
@@ -210,7 +220,14 @@ module.exports = {
editSchema: 'Modifica schema', editSchema: 'Modifica schema',
deleteSchema: 'Elimina schema', deleteSchema: 'Elimina schema',
markdownSupported: 'Markdown supportato', markdownSupported: 'Markdown supportato',
plantATree: 'Pianta un albero' plantATree: 'Pianta un albero',
dataTabPageSize: 'Grandezza pagina tab DATI',
enableSsh: 'Abilita SSH',
pageNumber: 'Numero pagina',
duplicateTable: 'Duplica tabella',
noOpenTabs: 'Non ci sono tab aperte, naviga nella barra sinistra o:',
noSchema: 'Nessuno schema',
restorePreviourSession: 'Ripristina sessione precedente'
}, },
faker: { faker: {
address: 'Indirizzo', address: 'Indirizzo',

View File

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

@@ -10,6 +10,10 @@ export default class {
return ipcRenderer.invoke('get-table-data', params); return ipcRenderer.invoke('get-table-data', params);
} }
static getTableApproximateCount (params) {
return ipcRenderer.invoke('get-table-count', params);
}
static getTableIndexes (params) { static getTableIndexes (params) {
return ipcRenderer.invoke('get-table-indexes', params); return ipcRenderer.invoke('get-table-indexes', params);
} }

View File

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

View File

@@ -63,6 +63,12 @@
background-color: $primary-color; background-color: $primary-color;
} }
} }
&.btn-clear {
&:hover {
background: rgba($light-color, 20%);
}
}
} }
.modal { .modal {
@@ -129,6 +135,11 @@
background: $bg-color-light-dark; background: $bg-color-light-dark;
} }
code {
background-color: #000;
color: $body-font-color-dark;
}
// Antares // Antares
.workspace { .workspace {
.workspace-explorebar { .workspace-explorebar {
@@ -156,7 +167,8 @@
} }
.menu-item { .menu-item {
&:hover { &:hover,
&.selected {
color: $body-font-color-dark; color: $body-font-color-dark;
background: rgba($color: #fff, $alpha: 0.05); background: rgba($color: #fff, $alpha: 0.05);
} }
@@ -191,19 +203,8 @@
color: $body-font-color-dark; color: $body-font-color-dark;
} }
& &.tools-dropdown { &.tools-dropdown {
.tab-link:focus { background-color: $bg-color-light-dark;
color: $primary-color;
}
.menu {
.menu-item a {
&:hover {
color: $primary-color;
background: $bg-color-gray;
}
}
}
} }
} }
@@ -214,9 +215,17 @@
} }
} }
.connection-panel {
.panel {
background: rgba($bg-color-light-dark, 50%);
}
}
.bg-checkered { .bg-checkered {
background-image: linear-gradient(to right, rgba(192, 192, 192, 0.75), rgba(192, 192, 192, 0.75)), background-image:
linear-gradient(to right, black 50%, white 50%), linear-gradient(to bottom, black 50%, white 50%); 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-blend-mode: normal, difference, normal;
background-size: 2em 2em; background-size: 2em 2em;
} }
@@ -384,11 +393,6 @@
background: rgba(48, 55, 66, 0.95); background: rgba(48, 55, 66, 0.95);
color: #fff; color: #fff;
} }
&:hover .ex-tooltip-content {
visibility: visible;
opacity: 1;
}
} }
#footer { #footer {

View File

@@ -18,20 +18,11 @@
background: #ababab; background: #ababab;
} }
#titlebar { .menu {
background: $bg-color-light; .menu-item a {
box-shadow: 0 0 1px 0 #000; &:hover {
color: $body-font-color;
.titlebar-elements { background: rgba($color: #000, $alpha: 0.1);
.titlebar-element {
&:hover {
opacity: 1;
background: rgba($color: rgb(172, 172, 172), $alpha: 0.2);
}
&.close-button:hover {
background: red;
}
} }
} }
} }
@@ -111,6 +102,24 @@
} }
} }
#titlebar {
background: $bg-color-light;
box-shadow: 0 0 1px 0 #000;
.titlebar-elements {
.titlebar-element {
&:hover {
opacity: 1;
background: rgba($color: rgb(172, 172, 172), $alpha: 0.2);
}
&.close-button:hover {
background: red;
}
}
}
}
#settingbar { #settingbar {
width: $settingbar-width; width: $settingbar-width;
height: calc(100vh - #{$excluding-size}); height: calc(100vh - #{$excluding-size});
@@ -168,11 +177,6 @@
background: rgba(48, 55, 66, 0.95); background: rgba(48, 55, 66, 0.95);
color: #fff; color: #fff;
} }
&:hover .ex-tooltip-content {
visibility: visible;
opacity: 1;
}
} }
.workspace { .workspace {
@@ -185,12 +189,29 @@
background: $bg-color-light-gray; background: $bg-color-light-gray;
} }
.menu-item {
&:hover,
&.selected {
background: rgba($color: #000, $alpha: 0.05);
}
}
.table-size { .table-size {
opacity: 0.4; opacity: 0.4;
} }
} }
} }
.workspace-tabs {
.tab-block {
.tab-item {
&.tools-dropdown {
background-color: $body-bg;
}
}
}
}
.workspace-query-results { .workspace-query-results {
.table { .table {
.th { .th {
@@ -205,6 +226,12 @@
} }
} }
.connection-panel {
.panel {
background: rgba($bg-color-light-gray, 100%);
}
}
.context { .context {
color: $body-font-color-dark; color: $body-font-color-dark;

View File

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

View File

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

View File

@@ -1,26 +1,24 @@
'use strict'; 'use strict';
import Store from 'electron-store';
import Connection from '@/ipc-api/Connection'; import Connection from '@/ipc-api/Connection';
import Schema from '@/ipc-api/Schema'; import Schema from '@/ipc-api/Schema';
import Users from '@/ipc-api/Users'; import Users from '@/ipc-api/Users';
import { uidGen } from 'common/libs/uidGen'; import { uidGen } from 'common/libs/uidGen';
const persistentStore = new Store({ name: 'tabs' });
const tabIndex = []; const tabIndex = [];
let lastBreadcrumbs = {};
export default { export default {
namespaced: true, namespaced: true,
strict: true, strict: true,
state: { state: {
workspaces: [], workspaces: [],
selected_workspace: null, selected_workspace: null
has_unsaved_changes: false,
is_unsaved_discard_modal: false,
pending_breadcrumbs: {}
}, },
getters: { getters: {
getSelected: state => { getSelected: state => {
if (state.selected_workspace) return state.selected_workspace; if (state.selected_workspace) return state.selected_workspace;
if (state.workspaces.length) return state.workspaces[0].uid; if (state.workspaces.length) return state.workspaces[0].uid;
return null; return 'NEW';
}, },
getWorkspace: state => uid => { getWorkspace: state => uid => {
return state.workspaces.find(workspace => workspace.uid === uid); return state.workspaces.find(workspace => workspace.uid === uid);
@@ -37,26 +35,35 @@ export default {
}, },
getConnected: state => { getConnected: state => {
return state.workspaces return state.workspaces
.filter(workspace => workspace.connection_status === 'connected') .filter(workspace => workspace.connectionStatus === 'connected')
.map(workspace => workspace.uid); .map(workspace => workspace.uid);
}, },
getLoadedSchemas: state => uid => { getLoadedSchemas: state => uid => {
return state.workspaces.find(workspace => workspace.uid === uid).loaded_schemas; return state.workspaces.find(workspace => workspace.uid === uid).loadedSchemas;
}, },
getSearchTerm: state => uid => { getSearchTerm: state => uid => {
return state.workspaces.find(workspace => workspace.uid === uid).search_term; return state.workspaces.find(workspace => workspace.uid === uid).searchTerm;
},
isUnsavedDiscardModal: state => {
return state.is_unsaved_discard_modal;
} }
}, },
mutations: { mutations: {
SELECT_WORKSPACE (state, uid) { 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) { SET_CONNECTED (state, payload) {
const { uid, client, dataTypes, indexTypes, customizations, structure, version } = 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 state.workspaces = state.workspaces.map(workspace => workspace.uid === uid
? { ? {
...workspace, ...workspace,
@@ -65,7 +72,9 @@ export default {
indexTypes, indexTypes,
customizations, customizations,
structure, structure,
connection_status: 'connected', connectionStatus: 'connected',
tabs: cachedTabs,
selectedTab: cachedTabs.length ? cachedTabs[0].uid : null,
version version
} }
: workspace); : workspace);
@@ -76,8 +85,8 @@ export default {
...workspace, ...workspace,
structure: {}, structure: {},
breadcrumbs: {}, breadcrumbs: {},
loaded_schemas: new Set(), loadedSchemas: new Set(),
connection_status: 'connecting' connectionStatus: 'connecting'
} }
: workspace); : workspace);
}, },
@@ -87,8 +96,8 @@ export default {
...workspace, ...workspace,
structure: {}, structure: {},
breadcrumbs: {}, breadcrumbs: {},
loaded_schemas: new Set(), loadedSchemas: new Set(),
connection_status: 'failed' connectionStatus: 'failed'
} }
: workspace); : workspace);
}, },
@@ -98,8 +107,8 @@ export default {
...workspace, ...workspace,
structure: {}, structure: {},
breadcrumbs: {}, breadcrumbs: {},
loaded_schemas: new Set(), loadedSchemas: new Set(),
connection_status: 'disconnected' connectionStatus: 'disconnected'
} }
: workspace); : workspace);
}, },
@@ -171,22 +180,28 @@ export default {
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid state.workspaces = state.workspaces.map(workspace => workspace.uid === uid
? { ? {
...workspace, ...workspace,
search_term: term searchTerm: term
} }
: workspace); : workspace);
}, },
NEW_TAB (state, { uid, tab, content, autorun }) { NEW_TAB (state, { uid, tab, content, type, autorun, schema, elementName, elementType }) {
tabIndex[uid] = tabIndex[uid] ? ++tabIndex[uid] : 1; if (type === 'query')
tabIndex[uid] = tabIndex[uid] ? ++tabIndex[uid] : 1;
const newTab = { const newTab = {
uid: tab, uid: tab,
index: tabIndex[uid], index: type === 'query' ? tabIndex[uid] : null,
selected: false, selected: false,
type: 'query', type,
schema,
elementName,
elementType,
fields: [], fields: [],
keyUsage: [], keyUsage: [],
content: content || '', content: content || '',
autorun: !!autorun autorun: !!autorun
}; };
state.workspaces = state.workspaces.map(workspace => { state.workspaces = state.workspaces.map(workspace => {
if (workspace.uid === uid) { if (workspace.uid === uid) {
return { return {
@@ -197,6 +212,8 @@ export default {
else else
return workspace; return workspace;
}); });
persistentStore.set(uid, state.workspaces.find(workspace => workspace.uid === uid).tabs);
}, },
REMOVE_TAB (state, { uid, tab: tUid }) { REMOVE_TAB (state, { uid, tab: tUid }) {
state.workspaces = state.workspaces.map(workspace => { state.workspaces = state.workspaces.map(workspace => {
@@ -209,9 +226,77 @@ export default {
else else
return workspace; 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 }) { SELECT_TAB (state, { uid, tab }) {
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid ? { ...workspace, selected_tab: tab } : workspace); state.workspaces = state.workspaces.map(workspace => workspace.uid === uid ? { ...workspace, selectedTab: 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 }) { SET_TAB_FIELDS (state, { cUid, tUid, fields }) {
state.workspaces = state.workspaces.map(workspace => { state.workspaces = state.workspaces.map(workspace => {
@@ -229,6 +314,8 @@ export default {
else else
return workspace; return workspace;
}); });
persistentStore.set(uid, state.workspaces.find(workspace => workspace.uid === uid).tabs);
}, },
SET_TAB_KEY_USAGE (state, { cUid, tUid, keyUsage }) { SET_TAB_KEY_USAGE (state, { cUid, tUid, keyUsage }) {
state.workspaces = state.workspaces.map(workspace => { state.workspaces = state.workspaces.map(workspace => {
@@ -246,20 +333,51 @@ export default {
else else
return workspace; return workspace;
}); });
persistentStore.set(uid, state.workspaces.find(workspace => workspace.uid === uid).tabs);
}, },
SET_UNSAVED_CHANGES (state, val) { SET_UNSAVED_CHANGES (state, { uid, tUid, isChanged }) {
state.has_unsaved_changes = !!val; state.workspaces = state.workspaces.map(workspace => {
}, if (workspace.uid === uid) {
SET_UNSAVED_DISCARD_MODAL (state, val) { return {
state.is_unsaved_discard_modal = !!val; ...workspace,
}, tabs: workspace.tabs.map(tab => {
SET_PENDING_BREADCRUMBS (state, payload) { if (tab.uid === tUid)
state.pending_breadcrumbs = payload; return { ...tab, isChanged };
return tab;
})
};
}
else
return workspace;
});
}, },
ADD_LOADED_SCHEMA (state, payload) { ADD_LOADED_SCHEMA (state, payload) {
state.workspaces = state.workspaces.map(workspace => { state.workspaces = state.workspaces.map(workspace => {
if (workspace.uid === payload.uid) if (workspace.uid === payload.uid)
workspace.loaded_schemas.add(payload.schema); workspace.loadedSchemas.add(payload.schema);
return workspace;
});
},
ADD_LOADING_ELEMENT (state, payload) {
state.workspaces = state.workspaces.map(workspace => {
if (workspace.uid === payload.uid)
workspace.loadingElements.push(payload.element);
return workspace;
});
},
REMOVE_LOADING_ELEMENT (state, payload) {
state.workspaces = state.workspaces.map(workspace => {
if (workspace.uid === payload.uid) {
const loadingElements = workspace.loadingElements.filter(el =>
el.schema !== payload.element.schema &&
el.name !== payload.element.name &&
el.type !== payload.element.type
);
workspace = { ...workspace, loadingElements };
}
return workspace; return workspace;
}); });
} }
@@ -268,7 +386,7 @@ export default {
selectWorkspace ({ commit }, uid) { selectWorkspace ({ commit }, uid) {
commit('SELECT_WORKSPACE', uid); commit('SELECT_WORKSPACE', uid);
}, },
async connectWorkspace ({ dispatch, commit }, connection) { async connectWorkspace ({ dispatch, commit, getters, rootGetters }, connection) {
commit('SET_CONNECTING', connection.uid); commit('SET_CONNECTING', connection.uid);
try { try {
@@ -301,6 +419,7 @@ export default {
if (status === 'error') if (status === 'error')
dispatch('notifications/addNotification', { status, message: version }, { root: true }); dispatch('notifications/addNotification', { status, message: version }, { root: true });
// Check if Maria or MySQL
const isMySQL = version.name.includes('MySQL'); const isMySQL = version.name.includes('MySQL');
if (isMySQL && connection.client !== 'mysql') { if (isMySQL && connection.client !== 'mysql') {
@@ -321,7 +440,8 @@ export default {
indexTypes, indexTypes,
customizations, customizations,
structure: response, structure: response,
version version,
restoreTabs: rootGetters['settings/getRestoreTabs']
}); });
dispatch('refreshCollations', connection.uid); dispatch('refreshCollations', connection.uid);
dispatch('refreshVariables', connection.uid); dispatch('refreshVariables', connection.uid);
@@ -411,91 +531,234 @@ export default {
commit('SET_DISCONNECTED', uid); commit('SET_DISCONNECTED', uid);
commit('SELECT_TAB', { uid, tab: 0 }); commit('SELECT_TAB', { uid, tab: 0 });
}, },
addWorkspace ({ commit, dispatch, getters }, uid) { addWorkspace ({ commit }, uid) {
const workspace = { const workspace = {
uid, uid,
connection_status: 'disconnected', connectionStatus: 'disconnected',
selected_tab: 0, selectedTab: 0,
search_term: '', searchTerm: '',
tabs: [], tabs: [],
structure: {}, structure: {},
variables: [], variables: [],
collations: [], collations: [],
users: [], users: [],
breadcrumbs: {}, breadcrumbs: {},
loaded_schemas: new Set() loadingElements: [],
loadedSchemas: new Set()
}; };
commit('ADD_WORKSPACE', workspace); commit('ADD_WORKSPACE', workspace);
if (getters.getWorkspace(uid).tabs.length < 3)
dispatch('newTab', { uid });
dispatch('setUnsavedChanges', false);
}, },
changeBreadcrumbs ({ state, commit, getters }, payload) { changeBreadcrumbs ({ commit, getters }, payload) {
if (state.has_unsaved_changes) {
commit('SET_UNSAVED_DISCARD_MODAL', true);
commit('SET_PENDING_BREADCRUMBS', payload);
return;
}
const breadcrumbsObj = { const breadcrumbsObj = {
schema: null, schema: null,
table: null, table: null,
trigger: null, trigger: null,
triggerFunction: null,
procedure: null, procedure: null,
function: null, function: null,
scheduler: 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 } }); commit('CHANGE_BREADCRUMBS', { uid: getters.getSelected, breadcrumbs: { ...breadcrumbsObj, ...payload } });
lastBreadcrumbs = { ...breadcrumbsObj, ...payload }; },
addLoadedSchema ({ commit, getters }, schema) {
if (payload.schema) commit('ADD_LOADED_SCHEMA', { uid: getters.getSelected, schema });
commit('ADD_LOADED_SCHEMA', { uid: getters.getSelected, schema: payload.schema }); },
addLoadingElement ({ commit, getters }, element) {
commit('ADD_LOADING_ELEMENT', { uid: getters.getSelected, element });
},
removeLoadingElement ({ commit, getters }, element) {
commit('REMOVE_LOADING_ELEMENT', { uid: getters.getSelected, element });
}, },
setSearchTerm ({ commit, getters }, term) { setSearchTerm ({ commit, getters }, term) {
commit('SET_SEARCH_TERM', { uid: getters.getSelected, term }); commit('SET_SEARCH_TERM', { uid: getters.getSelected, term });
}, },
newTab ({ commit }, { uid, content, autorun }) { newTab ({ state, commit }, { uid, content, type, autorun, schema, elementName, elementType }) {
const tab = uidGen('T'); let tabUid;
const workspaceTabs = state.workspaces.find(workspace => workspace.uid === uid);
commit('NEW_TAB', { uid, tab, content, autorun }); switch (type) {
commit('SELECT_TAB', { uid, tab }); 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.selectedTab)
: 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); commit('REMOVE_TAB', payload);
dispatch('checkSelectedTabExists', payload.uid);
},
removeTabs ({ commit, dispatch }, payload) {
commit('REMOVE_TABS', payload);
dispatch('checkSelectedTabExists', payload.uid);
}, },
selectTab ({ commit }, payload) { selectTab ({ commit }, payload) {
commit('SELECT_TAB', payload); commit('SELECT_TAB', payload);
}, },
updateTabs ({ commit }, payload) {
commit('UPDATE_TABS', payload);
},
setTabFields ({ commit }, payload) { setTabFields ({ commit }, payload) {
commit('SET_TAB_FIELDS', payload); commit('SET_TAB_FIELDS', payload);
}, },
setTabKeyUsage ({ commit }, payload) { setTabKeyUsage ({ commit }, payload) {
commit('SET_TAB_KEY_USAGE', payload); commit('SET_TAB_KEY_USAGE', payload);
}, },
setUnsavedChanges ({ commit }, val) { setUnsavedChanges ({ commit }, payload) {
commit('SET_UNSAVED_CHANGES', val); commit('SET_UNSAVED_CHANGES', payload);
},
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);
} }
} }
}; };