mirror of
https://github.com/Fabio286/antares.git
synced 2025-06-05 21:59:22 +02:00
Compare commits
60 Commits
Author | SHA1 | Date | |
---|---|---|---|
04fcaf2f6e | |||
8b914446b0 | |||
a11bac504c | |||
b9ed8dd610 | |||
1cf6485896 | |||
4bc9bbfb34 | |||
4923128236 | |||
8ff6e70145 | |||
afcf1c86ed | |||
0200ae4a0f | |||
dbe7b9dd23 | |||
ceab4ef243 | |||
1e7d4ca347 | |||
c0a32c040e | |||
f150508547 | |||
49d71722e2 | |||
59a50bc014 | |||
41d75b127c | |||
0cbea9d100 | |||
e351c903a8 | |||
6e55f27b23 | |||
27fd9ec203 | |||
0ec2710872 | |||
3bcd02fc4e | |||
aa33850286 | |||
82fdc0bcd7 | |||
d695c9f8d2 | |||
b32132ad84 | |||
3126625461 | |||
ab307f82b1 | |||
0df2b836b1 | |||
6611aad840 | |||
8c4aaec167 | |||
b7053bdf80 | |||
b6b7be098a | |||
56f2a27f00 | |||
dcf469ebed | |||
d94b49febf | |||
3b4f1475df | |||
155154b43d | |||
a95b8d188c | |||
cb1fce6f99 | |||
0014f48079 | |||
fc35f271d7 | |||
65ad0e954d | |||
8cafade8b1 | |||
d13b708377 | |||
206597e5b8 | |||
c5458159d1 | |||
1476e899d1 | |||
797ab70e7c | |||
f81312aeb0 | |||
3ed5ea023e | |||
9291a7a7b4 | |||
5cfdc9b92d | |||
15b08d7ea8 | |||
acebe435ff | |||
5712b80022 | |||
d38583262e | |||
e0e2131981 |
7
.gitattributes
vendored
7
.gitattributes
vendored
@@ -1 +1,6 @@
|
||||
* text eol=lf
|
||||
* text eol=lf
|
||||
*.jpg binary
|
||||
*.png binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.icns binary
|
5
.github/workflows/codeql-analysis.yml
vendored
5
.github/workflows/codeql-analysis.yml
vendored
@@ -31,11 +31,6 @@ jobs:
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
|
11
.travis.yml
11
.travis.yml
@@ -1,9 +1,6 @@
|
||||
language: node_js
|
||||
node_js: 12
|
||||
|
||||
before_install:
|
||||
- npm install
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
@@ -20,6 +17,9 @@ env:
|
||||
jobs:
|
||||
include:
|
||||
- stage: Test
|
||||
before_install:
|
||||
- sudo apt-get install libsecret-1-dev
|
||||
- npm install
|
||||
script:
|
||||
- npm test
|
||||
|
||||
@@ -27,6 +27,9 @@ jobs:
|
||||
if: tag IS present
|
||||
os: linux
|
||||
services: docker
|
||||
before_install:
|
||||
- sudo apt-get install libsecret-1-dev
|
||||
- npm install
|
||||
script:
|
||||
- docker run --rm --env-file <(env | grep -iE 'DEBUG|NODE_|ELECTRON_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|_TOKEN|_KEY|AWS_|STRIP|BUILD_') -v ${PWD}:/project -v ~/.cache/electron:/root/.cache/electron -v ~/.cache/electron-builder:/root/.cache/electron-builder electronuserland/builder:wine /bin/bash -c "npm run build -- --linux --win -p always"
|
||||
before_cache:
|
||||
@@ -35,6 +38,8 @@ jobs:
|
||||
- stage: Deploy Mac
|
||||
if: tag IS present
|
||||
os: osx
|
||||
before_install:
|
||||
- npm install
|
||||
osx_image: xcode10.2
|
||||
script:
|
||||
- npm run build -- -p always
|
||||
|
7
.versionrc.json
Normal file
7
.versionrc.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"types": [
|
||||
{"type":"feat","section":"Features"},
|
||||
{"type":"perf","section":"Improvements"},
|
||||
{"type":"fix","section":"Bug Fixes"}
|
||||
]
|
||||
}
|
96
CHANGELOG.md
96
CHANGELOG.md
@@ -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.
|
||||
|
||||
### [0.0.15](https://github.com/Fabio286/antares/compare/v0.0.14...v0.0.15) (2021-01-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* functions and schedulers in query suggestions ([8ff6e70](https://github.com/Fabio286/antares/commit/8ff6e70145ed2a207ae8b23a2c688258382a5d74))
|
||||
* loading animation in properties tabs ([1cf6485](https://github.com/Fabio286/antares/commit/1cf64858964f4894913db42f7c268013bb06e40b))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* error retriving dato of some schedulers ([b9ed8dd](https://github.com/Fabio286/antares/commit/b9ed8dd610e3be1489e01cf53f7d632cb1bd6ac5))
|
||||
* unable to call stored routines from query tabs ([4923128](https://github.com/Fabio286/antares/commit/4923128236131482ca948ae8052c294bd9269ed0))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* better fields type detection ([4bc9bbf](https://github.com/Fabio286/antares/commit/4bc9bbfb34ebdc51061f718cdf9cbca8507fa0f4))
|
||||
* big performance improvement in database structure loading ([a11bac5](https://github.com/Fabio286/antares/commit/a11bac504cd4ee865ea6c614a15ee809dc38202e))
|
||||
|
||||
### [0.0.14](https://github.com/Fabio286/antares/compare/v0.0.13...v0.0.14) (2021-01-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* export data tables to json or csv file ([0cbea9d](https://github.com/Fabio286/antares/commit/0cbea9d1007304a5b9cf893d165b4b4104266651))
|
||||
* functions creation ([49d7172](https://github.com/Fabio286/antares/commit/49d71722e26172232f7b54c6568e1e588ce0d049))
|
||||
* functions delete ([59a50bc](https://github.com/Fabio286/antares/commit/59a50bc014facc9643f9153cff61dc9d5a8605a9))
|
||||
* functions edit ([41d75b1](https://github.com/Fabio286/antares/commit/41d75b127cbcf1481fd259a14e6e7688638e18a4))
|
||||
* scheduler edit ([ceab4ef](https://github.com/Fabio286/antares/commit/ceab4ef243881ba64517fb95320844a21fce4849))
|
||||
* schedulers creation ([dbe7b9d](https://github.com/Fabio286/antares/commit/dbe7b9dd239248e806377ae6236b477456f175a3))
|
||||
* schedulers delete ([1e7d4ca](https://github.com/Fabio286/antares/commit/1e7d4ca347f4b9337ff266ec78bb4bbc6dd20d4d))
|
||||
* triggers and stored routines in sql suggestions ([e351c90](https://github.com/Fabio286/antares/commit/e351c903a8a8d7e908d6a7d54c0491438ac6f024))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* error with empty functions/procedures ([f150508](https://github.com/Fabio286/antares/commit/f1505085477a760a768a7d245c9517a858c1379c))
|
||||
* removed internal row _id from exported files ([c0a32c0](https://github.com/Fabio286/antares/commit/c0a32c040e653729ef80d580d6dd1796d1b2adcd))
|
||||
|
||||
### [0.0.13](https://github.com/Fabio286/antares/compare/v0.0.12...v0.0.13) (2021-01-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* option to toggle line wrap mode ([d94b49f](https://github.com/Fabio286/antares/commit/d94b49febf54b0200127859f2a8ed7ef591e56ab))
|
||||
* select definer in view creation/edit ([ab307f8](https://github.com/Fabio286/antares/commit/ab307f82b1d78c5f9571233090b6678a964bd674))
|
||||
* stored routines creation ([3bcd02f](https://github.com/Fabio286/antares/commit/3bcd02fc4ea9a4b780305212f906d6d78c7a8dae))
|
||||
* stored routines delete ([aa33850](https://github.com/Fabio286/antares/commit/aa3385028685417860b3ce985cc7a74f9da377ad))
|
||||
* stored routines edit ([82fdc0b](https://github.com/Fabio286/antares/commit/82fdc0bcd7514b321c1c9852a773adacf81baf87))
|
||||
* triggers creation ([d695c9f](https://github.com/Fabio286/antares/commit/d695c9f8d2418a6a4523a7a242fa1a8cba80e035))
|
||||
* triggers delete ([b32132a](https://github.com/Fabio286/antares/commit/b32132ad84d5798555b80eec3c624b681c37c339))
|
||||
* triggers edit ([3126625](https://github.com/Fabio286/antares/commit/3126625461f4b6d68d641b6b0eda8fcd390bb636))
|
||||
* views creation ([8c4aaec](https://github.com/Fabio286/antares/commit/8c4aaec167f58333a343b52927205b68137ad408))
|
||||
* views deletion ([dcf469e](https://github.com/Fabio286/antares/commit/dcf469ebed6252b4a496800206e0c34cd83b1f5e))
|
||||
* views edit ([56f2a27](https://github.com/Fabio286/antares/commit/56f2a27f0059cc10316204210db078a97408973c))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* breadcrumb not change after table rename ([b6b7be0](https://github.com/Fabio286/antares/commit/b6b7be098ad5ab4d55bfe05a7f862f045c1f54da))
|
||||
* unable to rename views ([b7053bd](https://github.com/Fabio286/antares/commit/b7053bdf8036d027e1685d6b5080d6b927a80e08))
|
||||
* wrong new stored routine modal icon ([0ec2710](https://github.com/Fabio286/antares/commit/0ec2710872c692b3feac076a3250d3b760af4009))
|
||||
* wrong or duplicate fields in some queries ([0df2b83](https://github.com/Fabio286/antares/commit/0df2b836b15436a2397d6a2202bd049b5cd53de4))
|
||||
|
||||
### [0.0.12](https://github.com/Fabio286/antares/compare/v0.0.11...v0.0.12) (2020-12-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* better security connections credentials storage ([fc35f27](https://github.com/Fabio286/antares/commit/fc35f271d7fe384cd786ce33547c0ef17135ddd8))
|
||||
* option to change editor theme ([a95b8d1](https://github.com/Fabio286/antares/commit/a95b8d188cfcc8f563ad73b4f0b676d068775d36))
|
||||
* option to toggle editor auto completion ([155154b](https://github.com/Fabio286/antares/commit/155154b43d0cd02ae875ded3ce865a37a999da5c))
|
||||
* query editor auto-completer for tables and columns ([cb1fce6](https://github.com/Fabio286/antares/commit/cb1fce6f998ea7332886820910e245ab19416a9d))
|
||||
|
||||
### [0.0.11](https://github.com/Fabio286/antares/compare/v0.0.10...v0.0.11) (2020-12-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* auto focus on first input in modals ([1476e89](https://github.com/Fabio286/antares/commit/1476e899d164562f12342ced8c76903b9bdcfa55))
|
||||
* foreign keys management ([206597e](https://github.com/Fabio286/antares/commit/206597e5b891e13e6f7635075bd11599355ab778))
|
||||
* improved data table sorts ([5712b80](https://github.com/Fabio286/antares/commit/5712b8002203b32027f0e820f98a61e2ec965e79))
|
||||
* query tabs auto focus ([f81312a](https://github.com/Fabio286/antares/commit/f81312aeb02ef55affd2ae9e81a9b4cb4c2e6da2))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* data tab sort not maintained at refresh ([15b08d7](https://github.com/Fabio286/antares/commit/15b08d7ea858cb28111c7b548af9a13b1bf0da91))
|
||||
* deletion of rows with non-numeric ID ([d385832](https://github.com/Fabio286/antares/commit/d38583262e672a2b47c5ad0aca0f13c129830a7b))
|
||||
* file field editor not show ([9291a7a](https://github.com/Fabio286/antares/commit/9291a7a7b41e7aeb9b65c7f32e496f523e482272))
|
||||
* improved changes dedection in props tab ([acebe43](https://github.com/Fabio286/antares/commit/acebe435ff6fa1581692fbf7ee1bc23b334e3947))
|
||||
* some properties do not reset after fields changes ([3ed5ea0](https://github.com/Fabio286/antares/commit/3ed5ea023e1852d724b2b59ab156f8722876f85b))
|
||||
* unable to switch tabs when no table selected ([c545815](https://github.com/Fabio286/antares/commit/c5458159d1e30cecf6615086c074d98b4b599637))
|
||||
* wrong field type detection ([5cfdc9b](https://github.com/Fabio286/antares/commit/5cfdc9b92d4b778a7863b02fd64e6445236c89bc))
|
||||
|
||||
### [0.0.10](https://github.com/Fabio286/antares/compare/v0.0.9...v0.0.10) (2020-12-04)
|
||||
|
||||
|
||||
|
37
README.md
37
README.md
@@ -9,7 +9,9 @@
|
||||
Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers.
|
||||
My target is to support as many databases as possible, and all major operating systems, including the ARM versions.
|
||||
|
||||
**At the moment this application is an alpha, it lacks many features, and isn't ready as a main SQL client**. However i'm actively working on it (yes, i'm a lone dev), hoping to provide all essential features as soon as possible.
|
||||
**At the moment this application is an alpha, it lacks many features** and supports only MySQL.
|
||||
Most of its current features might be enough for basic MySQL use, so give it a chance and send me your feedback, I would really appreciate it.
|
||||
I'm actively working on it (yes, i'm a lone dev), hoping to provide cool features and fixes as soon as possible.
|
||||
|
||||
🔗 If you are curious to try this early state of Antares you can download and install the [latest release](https://github.com/Fabio286/antares/releases).
|
||||
👁 To stay tuned for new releases watch this repo on **Release only** channel.
|
||||
@@ -29,34 +31,31 @@ An application created with minimalism and semplicity in mind, with features in
|
||||
|
||||
- Multiple database connections at same time.
|
||||
- Database management (add/edit/delete).
|
||||
- Tables fields management (add/edit/delete).
|
||||
- Tables content management (add/edit/delete).
|
||||
- Full tables management, including indexes and foreign keys.
|
||||
- Views, triggers, stored routines, functions and schedulers management (add/edit/delete).
|
||||
- Run queries on multiple tabs.
|
||||
- Query suggestions.
|
||||
- Query suggestions and auto complete.
|
||||
- Native dark theme.
|
||||
- Multi language.
|
||||
- Secure password storage.
|
||||
- Auto updates.
|
||||
|
||||
## Coming soon
|
||||
|
||||
This is a roadmap with major features will come in near future.
|
||||
|
||||
- Tables management (add/edit/delete).
|
||||
- Users management (add/edit/delete).
|
||||
- Stored procedures, views, schedulers and triggers support.
|
||||
- More secure password storage.
|
||||
- Database tools (variables, process list...).
|
||||
- Support for other databases.
|
||||
- Improvements of query editor area.
|
||||
- Improvements of query suggestions.
|
||||
- Database tools (variables, process list...).
|
||||
- SSL and SSH tunnel support.
|
||||
- Users management (add/edit/delete).
|
||||
- UI/UX improvements.
|
||||
- Query history.
|
||||
- More context menu shortcuts.
|
||||
- More keyboard shortcuts.
|
||||
- Query logs console.
|
||||
- Fake data filler.
|
||||
- Import/export and migration.
|
||||
- SSL and SSH tunnel support.
|
||||
- Themes.
|
||||
- Light theme.
|
||||
|
||||
## Currently supported
|
||||
|
||||
@@ -75,7 +74,7 @@ This is a roadmap with major features will come in near future.
|
||||
|
||||
- [x] Windows
|
||||
- [x] Linux
|
||||
- [x] MacOS (needs tests)
|
||||
- [x] MacOS (i need feedbacks)
|
||||
|
||||
#### • ARM
|
||||
|
||||
@@ -85,6 +84,10 @@ This is a roadmap with major features will come in near future.
|
||||
|
||||
## Translations
|
||||
|
||||
[Giuseppe Gigliotti](https://github.com/ReverbOD) / [Italian Translation](https://github.com/Fabio286/antares/pull/20)
|
||||
[Mohd-PH](https://github.com/Mohd-PH) / [Arabic Translation](https://github.com/Fabio286/antares/pull/29)
|
||||
[hongkfui](https://github.com/hongkfui) / [Spanish Translation](https://github.com/Fabio286/antares/pull/32)
|
||||
**Italian Translation** (46%) / [Giuseppe Gigliotti](https://github.com/ReverbOD) [[#20](https://github.com/Fabio286/antares/pull/20)]
|
||||
**Arabic Translation** (45%) / [Mohd-PH](https://github.com/Mohd-PH) [[#29](https://github.com/Fabio286/antares/pull/29)]
|
||||
**Spanish Translation** (46%) / [hongkfui](https://github.com/hongkfui) [[#32](https://github.com/Fabio286/antares/pull/32)]
|
||||
|
||||
## 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>
|
||||
|
5
jsconfig.json
Normal file
5
jsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"include": [
|
||||
"./src/renderer/**/*"
|
||||
]
|
||||
}
|
14
package.json
14
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "antares",
|
||||
"productName": "Antares",
|
||||
"version": "0.0.10",
|
||||
"version": "0.0.15",
|
||||
"description": "A cross-platform easy to use SQL client.",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/Fabio286/antares.git",
|
||||
@@ -39,6 +39,10 @@
|
||||
"AppImage"
|
||||
],
|
||||
"category": "Development"
|
||||
},
|
||||
"appImage": {
|
||||
"license": "./LICENSE",
|
||||
"category": "Development"
|
||||
}
|
||||
},
|
||||
"electronWebpack": {
|
||||
@@ -48,11 +52,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdi/font": "^5.8.55",
|
||||
"ace-builds": "^1.4.12",
|
||||
"electron-log": "^4.3.0",
|
||||
"electron-store": "^6.0.1",
|
||||
"electron-updater": "^4.3.5",
|
||||
"keytar": "^7.3.0",
|
||||
"lodash": "^4.17.20",
|
||||
"moment": "^2.29.1",
|
||||
"monaco-editor": "^0.20.0",
|
||||
"mssql": "^6.2.3",
|
||||
"mysql": "^2.18.1",
|
||||
"pg": "^8.5.1",
|
||||
@@ -61,8 +67,7 @@
|
||||
"vue-i18n": "^8.22.2",
|
||||
"vue-the-mask": "^0.11.1",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuex": "^3.6.0",
|
||||
"vuex-persist": "^3.1.3"
|
||||
"vuex": "^3.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^10.1.0",
|
||||
@@ -78,7 +83,6 @@
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-vue": "^7.1.0",
|
||||
"monaco-editor-webpack-plugin": "^1.9.1",
|
||||
"node-sass": "^5.0.0",
|
||||
"sass-loader": "^10.1.0",
|
||||
"standard-version": "^9.0.0",
|
||||
|
@@ -91,7 +91,7 @@ module.exports = [
|
||||
},
|
||||
{
|
||||
name: 'TINYTEXT',
|
||||
length: true,
|
||||
length: false,
|
||||
collation: true,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
@@ -119,7 +119,7 @@ module.exports = [
|
||||
},
|
||||
{
|
||||
name: 'JSON',
|
||||
length: true,
|
||||
length: false,
|
||||
collation: true,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
@@ -206,7 +206,7 @@ module.exports = [
|
||||
},
|
||||
{
|
||||
name: 'TIMESTAMP',
|
||||
length: true,
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
@@ -279,7 +279,7 @@ module.exports = [
|
||||
types: [
|
||||
{
|
||||
name: 'UNKNOWN',
|
||||
length: true,
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
|
@@ -1,12 +1,12 @@
|
||||
export const TEXT = ['char', 'varchar'];
|
||||
export const LONG_TEXT = ['text', 'mediumtext', 'longtext'];
|
||||
export const TEXT = ['CHAR', 'VARCHAR'];
|
||||
export const LONG_TEXT = ['TEXT', 'MEDIUMTEXT', 'LONGTEXT'];
|
||||
|
||||
export const NUMBER = ['int', 'tinyint', 'smallint', 'mediumint', 'bigint', 'float', 'double', 'decimal', 'bool'];
|
||||
export const NUMBER = ['INT', 'TINYINT', 'SMALLINT', 'MEDIUMINT', 'BIGINT', 'FLOAT', 'DOUBLE', 'DECIMAL', 'BOOL'];
|
||||
|
||||
export const DATE = ['date'];
|
||||
export const TIME = ['time'];
|
||||
export const DATETIME = ['datetime', 'timestamp'];
|
||||
export const DATE = ['DATE'];
|
||||
export const TIME = ['TIME'];
|
||||
export const DATETIME = ['DATETIME', 'TIMESTAMP'];
|
||||
|
||||
export const BLOB = ['blob', 'mediumblob', 'longblob'];
|
||||
export const BLOB = ['BLOB', 'TINYBLOB', 'MEDIUMBLOB', 'LONGBLOB'];
|
||||
|
||||
export const BIT = ['bit'];
|
||||
export const BIT = ['BIT'];
|
||||
|
@@ -2,7 +2,9 @@
|
||||
|
||||
import { app, BrowserWindow, nativeImage } from 'electron';
|
||||
import * as path from 'path';
|
||||
import crypto from 'crypto';
|
||||
import { format as formatUrl } from 'url';
|
||||
import keytar from 'keytar';
|
||||
import ipcHandlers from './ipc-handlers';
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||
@@ -89,7 +91,14 @@ else {
|
||||
});
|
||||
|
||||
// create main BrowserWindow when electron is ready
|
||||
app.on('ready', () => {
|
||||
app.on('ready', async () => {
|
||||
let key = await keytar.getPassword('antares', 'user');
|
||||
|
||||
if (!key) {
|
||||
key = crypto.randomBytes(16).toString('hex');
|
||||
keytar.setPassword('antares', 'user', key);
|
||||
}
|
||||
|
||||
mainWindow = createMainWindow();
|
||||
});
|
||||
}
|
||||
|
@@ -1,7 +1,13 @@
|
||||
import keytar from 'keytar';
|
||||
import { app, ipcMain } from 'electron';
|
||||
|
||||
export default () => {
|
||||
ipcMain.on('close-app', () => {
|
||||
app.exit();
|
||||
});
|
||||
|
||||
ipcMain.on('get-key', async event => {
|
||||
const key = await keytar.getPassword('antares', 'user');
|
||||
event.returnValue = key;
|
||||
});
|
||||
};
|
||||
|
@@ -46,7 +46,7 @@ export default connections => {
|
||||
try {
|
||||
await connection.connect();
|
||||
|
||||
const structure = await connection.getStructure();
|
||||
const structure = await connection.getStructure(new Set());
|
||||
|
||||
connections[conn.uid] = connection;
|
||||
|
||||
|
@@ -50,9 +50,9 @@ export default connections => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-structure', async (event, uid) => {
|
||||
ipcMain.handle('get-structure', async (event, params) => {
|
||||
try {
|
||||
const structure = await connections[uid].getStructure();
|
||||
const structure = await connections[params.uid].getStructure(params.schemas);
|
||||
|
||||
return { status: 'success', response: structure };
|
||||
}
|
||||
|
43
src/main/ipc-handlers/functions.js
Normal file
43
src/main/ipc-handlers/functions.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
export default (connections) => {
|
||||
ipcMain.handle('get-function-informations', async (event, params) => {
|
||||
try {
|
||||
const result = await connections[params.uid].getFunctionInformations(params);
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('drop-function', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].dropFunction(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('alter-function', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].alterFunction(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('create-function', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].createFunction(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
@@ -1,15 +1,27 @@
|
||||
import connection from './connection';
|
||||
import tables from './tables';
|
||||
import views from './views';
|
||||
import triggers from './triggers';
|
||||
import routines from './routines';
|
||||
import functions from './functions';
|
||||
import schedulers from './schedulers';
|
||||
import updates from './updates';
|
||||
import application from './application';
|
||||
import database from './database';
|
||||
import users from './users';
|
||||
|
||||
const connections = {};
|
||||
|
||||
export default () => {
|
||||
connection(connections);
|
||||
tables(connections);
|
||||
views(connections);
|
||||
triggers(connections);
|
||||
routines(connections);
|
||||
functions(connections);
|
||||
schedulers(connections);
|
||||
database(connections);
|
||||
users(connections);
|
||||
updates();
|
||||
application();
|
||||
};
|
||||
|
43
src/main/ipc-handlers/routines.js
Normal file
43
src/main/ipc-handlers/routines.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
export default (connections) => {
|
||||
ipcMain.handle('get-routine-informations', async (event, params) => {
|
||||
try {
|
||||
const result = await connections[params.uid].getRoutineInformations(params);
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('drop-routine', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].dropRoutine(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('alter-routine', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].alterRoutine(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('create-routine', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].createRoutine(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
43
src/main/ipc-handlers/schedulers.js
Normal file
43
src/main/ipc-handlers/schedulers.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
export default (connections) => {
|
||||
ipcMain.handle('get-scheduler-informations', async (event, params) => {
|
||||
try {
|
||||
const result = await connections[params.uid].getEventInformations(params);
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('drop-scheduler', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].dropEvent(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('alter-scheduler', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].alterEvent(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('create-scheduler', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].createEvent(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
@@ -14,14 +14,18 @@ export default (connections) => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-table-data', async (event, { uid, schema, table }) => {
|
||||
ipcMain.handle('get-table-data', async (event, { uid, schema, table, sortParams }) => {
|
||||
try {
|
||||
const result = await connections[uid]
|
||||
const query = connections[uid]
|
||||
.select('*')
|
||||
.schema(schema)
|
||||
.from(table)
|
||||
.limit(1000)
|
||||
.run({ details: true });
|
||||
.limit(1000);
|
||||
|
||||
if (sortParams && sortParams.field && sortParams.dir)
|
||||
query.orderBy({ [sortParams.field]: sortParams.dir.toUpperCase() });
|
||||
|
||||
const result = await query.run({ details: true });
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
@@ -89,11 +93,18 @@ export default (connections) => {
|
||||
});
|
||||
|
||||
ipcMain.handle('delete-table-rows', async (event, params) => {
|
||||
let idString;
|
||||
|
||||
if (typeof params.rows[0] === 'string')
|
||||
idString = params.rows.map(row => `"${row}"`).join(',');
|
||||
else
|
||||
idString = params.rows.join(',');
|
||||
|
||||
try {
|
||||
const result = await connections[params.uid]
|
||||
.schema(params.schema)
|
||||
.delete(params.table)
|
||||
.where({ [params.primary]: `IN (${params.rows.join(',')})` })
|
||||
.where({ [params.primary]: `IN (${idString})` })
|
||||
.run();
|
||||
|
||||
return { status: 'success', response: result };
|
||||
|
43
src/main/ipc-handlers/triggers.js
Normal file
43
src/main/ipc-handlers/triggers.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
export default (connections) => {
|
||||
ipcMain.handle('get-trigger-informations', async (event, params) => {
|
||||
try {
|
||||
const result = await connections[params.uid].getTriggerInformations(params);
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('drop-trigger', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].dropTrigger(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('alter-trigger', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].alterTrigger(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('create-trigger', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].createTrigger(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
15
src/main/ipc-handlers/users.js
Normal file
15
src/main/ipc-handlers/users.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
export default (connections) => {
|
||||
ipcMain.handle('get-users', async (event, uid) => {
|
||||
try {
|
||||
const result = await connections[uid].getUsers();
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
if (err.code === 'ER_TABLEACCESS_DENIED_ERROR')
|
||||
return { status: 'success', response: [] };
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
43
src/main/ipc-handlers/views.js
Normal file
43
src/main/ipc-handlers/views.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
export default (connections) => {
|
||||
ipcMain.handle('get-view-informations', async (event, params) => {
|
||||
try {
|
||||
const result = await connections[params.uid].getViewInformations(params);
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('drop-view', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].dropView(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('alter-view', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].alterView(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('create-view', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].createView(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@@ -69,75 +69,81 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.context {
|
||||
.context {
|
||||
display: flex;
|
||||
color: $body-font-color;
|
||||
font-size: 16px;
|
||||
z-index: 400;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
height: 100vh;
|
||||
right: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
|
||||
.context-container {
|
||||
min-width: 100px;
|
||||
z-index: 10;
|
||||
box-shadow: 0 0 2px 0 #000;
|
||||
padding: 0;
|
||||
background: #1d1d1d;
|
||||
border-radius: 0.1rem;
|
||||
display: flex;
|
||||
color: $body-font-color;
|
||||
font-size: 16px;
|
||||
z-index: 400;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
height: 100vh;
|
||||
right: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
pointer-events: initial;
|
||||
|
||||
.context-container {
|
||||
min-width: 100px;
|
||||
z-index: 10;
|
||||
box-shadow: 0 0 2px 0 #000;
|
||||
padding: 0;
|
||||
background: #1d1d1d;
|
||||
border-radius: 0.1rem;
|
||||
.context-element {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
pointer-events: initial;
|
||||
align-items: center;
|
||||
padding: 0.1rem 0.3rem;
|
||||
cursor: pointer;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
|
||||
.context-element {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.1rem 0.3rem;
|
||||
cursor: pointer;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
.context-submenu {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.2s;
|
||||
position: absolute;
|
||||
left: 100%;
|
||||
top: 0;
|
||||
background: #1d1d1d;
|
||||
box-shadow: 0 0 2px 0 #000;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $primary-color;
|
||||
|
||||
.context-submenu {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.2s;
|
||||
position: absolute;
|
||||
left: 100%;
|
||||
top: 0;
|
||||
background: #1d1d1d;
|
||||
box-shadow: 0 0 2px 0 #000;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $primary-color;
|
||||
|
||||
.context-submenu {
|
||||
display: block;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
display: block;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.context-overlay {
|
||||
background: transparent;
|
||||
bottom: 0;
|
||||
cursor: default;
|
||||
display: block;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.context-overlay {
|
||||
background: transparent;
|
||||
bottom: 0;
|
||||
cursor: default;
|
||||
display: block;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.disabled {
|
||||
pointer-events: none;
|
||||
filter: grayscale(100%);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
22
src/renderer/components/BaseLoader.vue
Normal file
22
src/renderer/components/BaseLoader.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div class="empty">
|
||||
<div class="loading loading-lg" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BaseLoader'
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.empty {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
left: 0;
|
||||
justify-content: center;
|
||||
right: 0;
|
||||
}
|
||||
</style>
|
@@ -1,10 +1,14 @@
|
||||
<template>
|
||||
<select
|
||||
ref="editField"
|
||||
class="px-1"
|
||||
class="form-select pl-1 pr-4"
|
||||
:class="{'small-select': size === 'small'}"
|
||||
@change="onChange"
|
||||
@blur="$emit('blur')"
|
||||
>
|
||||
<option v-if="!isValidDefault" :value="value">
|
||||
{{ value }} - {{ $t('message.invalidDefault') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="row in foreignList"
|
||||
:key="row.foreignColumn"
|
||||
@@ -30,7 +34,11 @@ export default {
|
||||
},
|
||||
props: {
|
||||
value: [String, Number],
|
||||
keyUsage: Object
|
||||
keyUsage: Object,
|
||||
size: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -40,7 +48,10 @@ export default {
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected'
|
||||
})
|
||||
}),
|
||||
isValidDefault () {
|
||||
return this.foreignList.some(foreign => foreign.foreignColumn.toString() === this.value.toString());
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
let firstTextField;
|
||||
@@ -64,7 +75,7 @@ export default {
|
||||
try { // Foregn list
|
||||
const { status, response } = await Tables.getForeignList({
|
||||
...params,
|
||||
column: this.keyUsage.refColumn,
|
||||
column: this.keyUsage.refField,
|
||||
description: firstTextField
|
||||
});
|
||||
|
||||
|
@@ -15,10 +15,11 @@
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-3">
|
||||
<label class="form-label">{{ $t('word.user') }}:</label>
|
||||
<label class="form-label">{{ $t('word.user') }}</label>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="credentials.user"
|
||||
class="form-input"
|
||||
type="text"
|
||||
@@ -27,7 +28,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-3">
|
||||
<label class="form-label">{{ $t('word.password') }}:</label>
|
||||
<label class="form-label">{{ $t('word.password') }}</label>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<input
|
||||
@@ -63,6 +64,11 @@ export default {
|
||||
}
|
||||
};
|
||||
},
|
||||
created () {
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
methods: {
|
||||
closeModal () {
|
||||
this.$emit('close-asking');
|
||||
|
@@ -16,10 +16,11 @@
|
||||
<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>
|
||||
<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"
|
||||
@@ -28,7 +29,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.client') }}:</label>
|
||||
<label class="form-label">{{ $t('word.client') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<select v-model="localConnection.client" class="form-select">
|
||||
@@ -52,7 +53,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.hostName') }}/IP:</label>
|
||||
<label class="form-label">{{ $t('word.hostName') }}/IP</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
@@ -64,7 +65,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.port') }}:</label>
|
||||
<label class="form-label">{{ $t('word.port') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
@@ -78,7 +79,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.user') }}:</label>
|
||||
<label class="form-label">{{ $t('word.user') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
@@ -91,7 +92,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.password') }}:</label>
|
||||
<label class="form-label">{{ $t('word.password') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
@@ -172,6 +173,10 @@ export default {
|
||||
created () {
|
||||
this.localConnection = Object.assign({}, this.connection);
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
|
@@ -15,7 +15,7 @@
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-3">
|
||||
<label class="form-label">{{ $t('word.name') }}:</label>
|
||||
<label class="form-label">{{ $t('word.name') }}</label>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<input
|
||||
@@ -30,10 +30,14 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-3">
|
||||
<label class="form-label">{{ $t('word.collation') }}:</label>
|
||||
<label class="form-label">{{ $t('word.collation') }}</label>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<select v-model="database.collation" class="form-select">
|
||||
<select
|
||||
ref="firstInput"
|
||||
v-model="database.collation"
|
||||
class="form-select"
|
||||
>
|
||||
<option
|
||||
v-for="collation in collations"
|
||||
:key="collation.id"
|
||||
@@ -114,6 +118,10 @@ export default {
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
|
@@ -16,10 +16,11 @@
|
||||
<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>
|
||||
<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"
|
||||
@@ -28,7 +29,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.client') }}:</label>
|
||||
<label class="form-label">{{ $t('word.client') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<select
|
||||
@@ -56,7 +57,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.hostName') }}/IP:</label>
|
||||
<label class="form-label">{{ $t('word.hostName') }}/IP</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
@@ -68,7 +69,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.port') }}:</label>
|
||||
<label class="form-label">{{ $t('word.port') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
@@ -82,7 +83,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.user') }}:</label>
|
||||
<label class="form-label">{{ $t('word.user') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
@@ -95,7 +96,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.password') }}:</label>
|
||||
<label class="form-label">{{ $t('word.password') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
@@ -182,6 +183,10 @@ export default {
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
|
@@ -15,10 +15,11 @@
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-3">
|
||||
<label class="form-label">{{ $t('word.name') }}:</label>
|
||||
<label class="form-label">{{ $t('word.name') }}</label>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="database.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
@@ -29,7 +30,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-3">
|
||||
<label class="form-label">{{ $t('word.collation') }}:</label>
|
||||
<label class="form-label">{{ $t('word.collation') }}</label>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<select v-model="database.collation" class="form-select">
|
||||
@@ -89,6 +90,9 @@ export default {
|
||||
created () {
|
||||
this.database = { ...this.database, collation: this.defaultCollation };
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
|
184
src/renderer/components/ModalNewFunction.vue
Normal file
184
src/renderer/components/ModalNewFunction.vue
Normal file
@@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.confirm')"
|
||||
size="400"
|
||||
@confirm="confirmNewFunction"
|
||||
@hide="$emit('close')"
|
||||
>
|
||||
<template :slot="'header'">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-plus mr-1" /> {{ $t('message.createNewRoutine') }}
|
||||
</div>
|
||||
</template>
|
||||
<div :slot="'body'">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.name') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="localFunction.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.definer') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select
|
||||
v-if="workspace.users.length"
|
||||
v-model="localFunction.definer"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="user in workspace.users"
|
||||
:key="`${user.name}@${user.host}`"
|
||||
:value="`\`${user.name}\`@\`${user.host}\``"
|
||||
>
|
||||
{{ user.name }}@{{ user.host }}
|
||||
</option>
|
||||
</select>
|
||||
<select v-if="!workspace.users.length" class="form-select">
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.returns') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<div class="input-group">
|
||||
<select
|
||||
v-model="localFunction.returns"
|
||||
class="form-select text-uppercase"
|
||||
style="width: 0;"
|
||||
>
|
||||
<optgroup
|
||||
v-for="group in workspace.dataTypes"
|
||||
:key="group.group"
|
||||
:label="group.group"
|
||||
>
|
||||
<option
|
||||
v-for="type in group.types"
|
||||
:key="type.name"
|
||||
:selected="localFunction.returns === type.name"
|
||||
:value="type.name"
|
||||
>
|
||||
{{ type.name }}
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<input
|
||||
v-model="localFunction.returnsLength"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="0"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.comment') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
v-model="localFunction.comment"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('message.sqlSecurity') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="localFunction.security" class="form-select">
|
||||
<option>DEFINER</option>
|
||||
<option>INVOKER</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('message.dataAccess') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="localFunction.dataAccess" class="form-select">
|
||||
<option>CONTAINS SQL</option>
|
||||
<option>NO SQL</option>
|
||||
<option>READS SQL DATA</option>
|
||||
<option>MODIFIES SQL DATA</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4" />
|
||||
<div class="column">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="localFunction.deterministic" type="checkbox"><i class="form-icon" /> {{ $t('word.deterministic') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'ModalNewFunction',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
workspace: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
localFunction: {
|
||||
definer: '',
|
||||
sql: 'BEGIN\r\n RETURN NULL;\r\nEND',
|
||||
parameters: [],
|
||||
name: '',
|
||||
comment: '',
|
||||
returns: 'INT',
|
||||
returnsLength: 10,
|
||||
security: 'DEFINER',
|
||||
deterministic: false,
|
||||
dataAccess: 'CONTAINS SQL'
|
||||
},
|
||||
isOptionsChanging: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
methods: {
|
||||
confirmNewFunction () {
|
||||
this.$emit('open-create-function-editor', this.localFunction);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
147
src/renderer/components/ModalNewRoutine.vue
Normal file
147
src/renderer/components/ModalNewRoutine.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.confirm')"
|
||||
size="400"
|
||||
@confirm="confirmNewRoutine"
|
||||
@hide="$emit('close')"
|
||||
>
|
||||
<template :slot="'header'">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-plus mr-1" /> {{ $t('message.createNewRoutine') }}
|
||||
</div>
|
||||
</template>
|
||||
<div :slot="'body'">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.name') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="localRoutine.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.definer') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select
|
||||
v-if="workspace.users.length"
|
||||
v-model="localRoutine.definer"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="user in workspace.users"
|
||||
:key="`${user.name}@${user.host}`"
|
||||
:value="`\`${user.name}\`@\`${user.host}\``"
|
||||
>
|
||||
{{ user.name }}@{{ user.host }}
|
||||
</option>
|
||||
</select>
|
||||
<select v-if="!workspace.users.length" class="form-select">
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.comment') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
v-model="localRoutine.comment"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('message.sqlSecurity') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="localRoutine.security" class="form-select">
|
||||
<option>DEFINER</option>
|
||||
<option>INVOKER</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('message.dataAccess') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="localRoutine.dataAccess" class="form-select">
|
||||
<option>CONTAINS SQL</option>
|
||||
<option>NO SQL</option>
|
||||
<option>READS SQL DATA</option>
|
||||
<option>MODIFIES SQL DATA</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4" />
|
||||
<div class="column">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="localRoutine.deterministic" type="checkbox"><i class="form-icon" /> {{ $t('word.deterministic') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'ModalNewRoutine',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
workspace: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
localRoutine: {
|
||||
definer: '',
|
||||
sql: 'BEGIN\r\n\r\nEND',
|
||||
parameters: [],
|
||||
name: '',
|
||||
comment: '',
|
||||
security: 'DEFINER',
|
||||
deterministic: false,
|
||||
dataAccess: 'CONTAINS SQL'
|
||||
},
|
||||
isOptionsChanging: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
methods: {
|
||||
confirmNewRoutine () {
|
||||
this.$emit('open-create-routine-editor', this.localRoutine);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
109
src/renderer/components/ModalNewScheduler.vue
Normal file
109
src/renderer/components/ModalNewScheduler.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.confirm')"
|
||||
size="400"
|
||||
@confirm="confirmNewTrigger"
|
||||
@hide="$emit('close')"
|
||||
>
|
||||
<template :slot="'header'">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-plus mr-1" /> {{ $t('message.createNewScheduler') }}
|
||||
</div>
|
||||
</template>
|
||||
<div :slot="'body'">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.name') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="localScheduler.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.definer') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select
|
||||
v-if="workspace.users.length"
|
||||
v-model="localScheduler.definer"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="user in workspace.users"
|
||||
:key="`${user.name}@${user.host}`"
|
||||
:value="`\`${user.name}\`@\`${user.host}\``"
|
||||
>
|
||||
{{ user.name }}@{{ user.host }}
|
||||
</option>
|
||||
</select>
|
||||
<select v-if="!workspace.users.length" class="form-select">
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.comment') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
v-model="localScheduler.comment"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'ModalNewScheduler',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
workspace: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
localScheduler: {
|
||||
definer: '',
|
||||
sql: 'BEGIN\r\n\r\nEND',
|
||||
name: '',
|
||||
comment: '',
|
||||
execution: 'EVERY',
|
||||
every: ['1', 'DAY'],
|
||||
preserve: true,
|
||||
state: 'DISABLE'
|
||||
}
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
methods: {
|
||||
confirmNewTrigger () {
|
||||
this.$emit('open-create-scheduler-editor', this.localScheduler);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -18,6 +18,7 @@
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="localOptions.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
@@ -83,7 +84,6 @@ export default {
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
table: String,
|
||||
workspace: Object
|
||||
},
|
||||
data () {
|
||||
@@ -112,6 +112,10 @@ export default {
|
||||
mounted () {
|
||||
this.localOptions.collation = this.defaultCollation;
|
||||
this.localOptions.engine = this.defaultEngine;
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
methods: {
|
||||
confirmOptionsChange () {
|
||||
|
@@ -25,6 +25,7 @@
|
||||
<div class="input-group col-8 col-sm-12">
|
||||
<ForeignKeySelect
|
||||
v-if="foreignKeys.includes(field.name)"
|
||||
ref="formInput"
|
||||
class="form-select"
|
||||
:value.sync="localRow[field.name]"
|
||||
:key-usage="getKeyUsage(field.name)"
|
||||
@@ -32,6 +33,7 @@
|
||||
/>
|
||||
<input
|
||||
v-else-if="inputProps(field).mask"
|
||||
ref="formInput"
|
||||
v-model="localRow[field.name]"
|
||||
v-mask="inputProps(field).mask"
|
||||
class="form-input"
|
||||
@@ -41,6 +43,7 @@
|
||||
>
|
||||
<input
|
||||
v-else-if="inputProps(field).type === 'file'"
|
||||
ref="formInput"
|
||||
class="form-input"
|
||||
type="file"
|
||||
:disabled="fieldsToExclude.includes(field.name)"
|
||||
@@ -49,13 +52,14 @@
|
||||
>
|
||||
<input
|
||||
v-else
|
||||
ref="formInput"
|
||||
v-model="localRow[field.name]"
|
||||
class="form-input"
|
||||
:type="inputProps(field).type"
|
||||
:disabled="fieldsToExclude.includes(field.name)"
|
||||
:tabindex="key+1"
|
||||
>
|
||||
<span class="input-group-addon" :class="`type-${field.type}`">
|
||||
<span class="input-group-addon" :class="`type-${field.type.toLowerCase()}`">
|
||||
{{ field.type }} {{ fieldLength(field) | wrapNumber }}
|
||||
</span>
|
||||
<label class="form-checkbox ml-3" :title="$t('word.insert')">
|
||||
@@ -146,7 +150,7 @@ export default {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
},
|
||||
foreignKeys () {
|
||||
return this.keyUsage.map(key => key.column);
|
||||
return this.keyUsage.map(key => key.field);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -192,6 +196,12 @@ export default {
|
||||
}
|
||||
|
||||
this.localRow = { ...rowObj };
|
||||
|
||||
// Auto focus
|
||||
setTimeout(() => {
|
||||
const firstSelectableInput = this.$refs.formInput.find(input => !input.disabled);
|
||||
firstSelectableInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
@@ -243,7 +253,7 @@ export default {
|
||||
},
|
||||
fieldLength (field) {
|
||||
if ([...BLOB, ...LONG_TEXT].includes(field.type)) return null;
|
||||
return field.numLength || field.datePrecision || field.charLength || 0;
|
||||
return field.length;
|
||||
},
|
||||
inputProps (field) {
|
||||
if ([...TEXT, ...LONG_TEXT].includes(field.type))
|
||||
@@ -296,7 +306,7 @@ export default {
|
||||
this.localRow[field] = files[0].path;
|
||||
},
|
||||
getKeyUsage (keyName) {
|
||||
return this.keyUsage.find(key => key.column === keyName);
|
||||
return this.keyUsage.find(key => key.field === keyName);
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
|
140
src/renderer/components/ModalNewTrigger.vue
Normal file
140
src/renderer/components/ModalNewTrigger.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.confirm')"
|
||||
size="400"
|
||||
@confirm="confirmNewTrigger"
|
||||
@hide="$emit('close')"
|
||||
>
|
||||
<template :slot="'header'">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-plus mr-1" /> {{ $t('message.createNewTrigger') }}
|
||||
</div>
|
||||
</template>
|
||||
<div :slot="'body'">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.name') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="localTrigger.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.definer') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select
|
||||
v-if="workspace.users.length"
|
||||
v-model="localTrigger.definer"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="user in workspace.users"
|
||||
:key="`${user.name}@${user.host}`"
|
||||
:value="`\`${user.name}\`@\`${user.host}\``"
|
||||
>
|
||||
{{ user.name }}@{{ user.host }}
|
||||
</option>
|
||||
</select>
|
||||
<select v-if="!workspace.users.length" class="form-select">
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.table') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="localTrigger.table" class="form-select">
|
||||
<option v-for="table in schemaTables" :key="table.name">
|
||||
{{ table.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.event') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<div class="input-group">
|
||||
<select v-model="localTrigger.event1" class="form-select">
|
||||
<option>BEFORE</option>
|
||||
<option>AFTER</option>
|
||||
</select>
|
||||
<select v-model="localTrigger.event2" class="form-select">
|
||||
<option>INSERT</option>
|
||||
<option>UPDATE</option>
|
||||
<option>DELETE</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'ModalNewTrigger',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
workspace: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
localTrigger: {
|
||||
definer: '',
|
||||
sql: 'BEGIN\r\n\r\nEND',
|
||||
name: '',
|
||||
table: '',
|
||||
event1: 'BEFORE',
|
||||
event2: 'INSERT'
|
||||
},
|
||||
isOptionsChanging: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
},
|
||||
schemaTables () {
|
||||
const schemaTables = this.workspace.structure
|
||||
.filter(schema => schema.name === this.schema)
|
||||
.map(schema => schema.tables);
|
||||
|
||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.localTrigger.table = this.schemaTables.length ? this.schemaTables[0].name : '';
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
methods: {
|
||||
confirmNewTrigger () {
|
||||
this.$emit('open-create-trigger-editor', this.localTrigger);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
192
src/renderer/components/ModalNewView.vue
Normal file
192
src/renderer/components/ModalNewView.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.confirm')"
|
||||
size="medium"
|
||||
@confirm="confirmOptionsChange"
|
||||
@hide="$emit('close')"
|
||||
>
|
||||
<template :slot="'header'">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-eye-plus mr-1" /> {{ $t('message.createNewView') }}
|
||||
</div>
|
||||
</template>
|
||||
<div :slot="'body'">
|
||||
<div class="container">
|
||||
<div class="columns mb-4">
|
||||
<div class="column col-6">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.name') }}</label>
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="localView.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-6">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.definer') }}</label>
|
||||
<select v-model="localView.definer" class="form-select">
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="user in workspace.users"
|
||||
:key="`${user.name}@${user.host}`"
|
||||
:value="`\`${user.name}\`@\`${user.host}\``"
|
||||
>
|
||||
{{ user.name }}@{{ user.host }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column col-4">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('message.sqlSecurity') }}</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.security"
|
||||
type="radio"
|
||||
name="security"
|
||||
value="DEFINER"
|
||||
>
|
||||
<i class="form-icon" /> DEFINER
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.security"
|
||||
type="radio"
|
||||
name="security"
|
||||
value="INVOKER"
|
||||
>
|
||||
<i class="form-icon" /> INVOKER
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-4">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.algorithm') }}</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.algorithm"
|
||||
type="radio"
|
||||
name="algorithm"
|
||||
value="UNDEFINED"
|
||||
>
|
||||
<i class="form-icon" /> UNDEFINED
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.algorithm"
|
||||
type="radio"
|
||||
value="MERGE"
|
||||
name="algorithm"
|
||||
>
|
||||
<i class="form-icon" /> MERGE
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.algorithm"
|
||||
type="radio"
|
||||
value="TEMPTABLE"
|
||||
name="algorithm"
|
||||
>
|
||||
<i class="form-icon" /> TEMPTABLE
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-4">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('message.updateOption') }}</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.updateOption"
|
||||
type="radio"
|
||||
name="update"
|
||||
value=""
|
||||
>
|
||||
<i class="form-icon" /> None
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.updateOption"
|
||||
type="radio"
|
||||
name="update"
|
||||
value="CASCADED"
|
||||
>
|
||||
<i class="form-icon" /> CASCADED
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.updateOption"
|
||||
type="radio"
|
||||
name="update"
|
||||
value="LOCAL"
|
||||
>
|
||||
<i class="form-icon" /> LOCAL
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workspace-query-results column col-12 mt-2">
|
||||
<label class="form-label ml-2">{{ $t('message.selectStatement') }}</label>
|
||||
<QueryEditor
|
||||
ref="queryEditor"
|
||||
:value.sync="localView.sql"
|
||||
:workspace="workspace"
|
||||
:schema="schema"
|
||||
:height="editorHeight"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
|
||||
export default {
|
||||
name: 'ModalNewView',
|
||||
components: {
|
||||
ConfirmModal,
|
||||
QueryEditor
|
||||
},
|
||||
props: {
|
||||
workspace: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
localView: {
|
||||
algorithm: 'UNDEFINED',
|
||||
definer: '',
|
||||
security: 'DEFINER',
|
||||
updateOption: '',
|
||||
sql: '',
|
||||
name: ''
|
||||
},
|
||||
isOptionsChanging: false,
|
||||
editorHeight: 300
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
methods: {
|
||||
confirmOptionsChange () {
|
||||
this.$emit('open-create-view-editor', this.localView);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -45,59 +45,154 @@
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedTab === 'general'" class="panel-body py-4">
|
||||
<form class="form-horizontal">
|
||||
<div class="col-8 col-sm-12">
|
||||
<div class="form-group mb-4">
|
||||
<div class="col-6 col-sm-12">
|
||||
<label class="form-label">
|
||||
<i class="mdi mdi-18px mdi-translate mr-1" />
|
||||
{{ $t('word.language') }}:
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-6 col-sm-12">
|
||||
<select
|
||||
v-model="localLocale"
|
||||
class="form-select"
|
||||
@change="changeLocale(localLocale)"
|
||||
>
|
||||
<option
|
||||
v-for="(locale, key) in locales"
|
||||
:key="key"
|
||||
:value="locale.code"
|
||||
<div class="container">
|
||||
<form class="form-horizontal columns">
|
||||
<div class="column col-12 h6 text-uppercase mb-1">
|
||||
{{ $t('word.application') }}
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12 mb-2">
|
||||
<div class="form-group mb-4">
|
||||
<div class="col-6 col-sm-12">
|
||||
<label class="form-label">
|
||||
<i class="mdi mdi-18px mdi-translate mr-1" />
|
||||
{{ $t('word.language') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-6 col-sm-12">
|
||||
<select
|
||||
v-model="localLocale"
|
||||
class="form-select"
|
||||
@change="changeLocale(localLocale)"
|
||||
>
|
||||
{{ locale.name }}
|
||||
</option>
|
||||
</select>
|
||||
<option
|
||||
v-for="(locale, key) in locales"
|
||||
:key="key"
|
||||
:value="locale.code"
|
||||
>
|
||||
{{ locale.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-6 col-sm-12">
|
||||
<label class="form-label">
|
||||
{{ $t('message.notificationsTimeout') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-6 col-sm-12">
|
||||
<div class="input-group">
|
||||
<input
|
||||
v-model="localTimeout"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="1"
|
||||
@focusout="checkNotificationsTimeout"
|
||||
>
|
||||
<span class="input-group-addon">{{ $t('word.seconds') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-6 col-sm-12">
|
||||
<label class="form-label">
|
||||
{{ $t('message.notificationsTimeout') }}:
|
||||
</label>
|
||||
|
||||
<div class="column col-12 h6 mt-4 text-uppercase mb-1">
|
||||
{{ $t('word.editor') }}
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<div class="form-group">
|
||||
<div class="col-6 col-sm-12">
|
||||
<label class="form-label">
|
||||
{{ $t('word.autoCompletion') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-6 col-sm-12">
|
||||
<label class="form-switch d-inline-block" @click.prevent="toggleAutoComplete">
|
||||
<input type="checkbox" :checked="selectedAutoComplete">
|
||||
<i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-sm-12">
|
||||
<div class="input-group">
|
||||
<input
|
||||
v-model="localTimeout"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="1"
|
||||
@focusout="checkNotificationsTimeout"
|
||||
>
|
||||
<span class="input-group-addon">{{ $t('word.seconds') }}</span>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<div class="form-group">
|
||||
<div class="col-6 col-sm-12">
|
||||
<label class="form-label">
|
||||
{{ $t('message.wrapLongLines') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-6 col-sm-12">
|
||||
<label class="form-switch d-inline-block" @click.prevent="toggleLineWrap">
|
||||
<input type="checkbox" :checked="selectedLineWrap">
|
||||
<i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedTab === 'themes'" class="panel-body py-4">
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
<div class="column col-12 h6 text-uppercase mb-2">
|
||||
{{ $t('message.applicationTheme') }}
|
||||
</div>
|
||||
<div class="column col-6 c-hand theme-block" :class="{'selected': applicationTheme === 'dark'}">
|
||||
<img :src="require('@/images/dark.png').default" class="img-responsive img-fit-cover s-rounded">
|
||||
<div class="theme-name">
|
||||
<i class="mdi mdi-moon-waning-crescent mdi-48px" />
|
||||
<div class="h6 mt-4">
|
||||
{{ $t('word.dark') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-6 theme-block disabled" :class="{'selected': applicationTheme === 'light'}">
|
||||
<div class="theme-name">
|
||||
<i class="mdi mdi-white-balance-sunny mdi-48px" />
|
||||
<div class="h6 mt-4">
|
||||
{{ $t('word.light') }} (Coming)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedTab === 'themes'" class="panel-body py-4">
|
||||
<div class="text-center">
|
||||
<p>In future releases</p>
|
||||
<div class="columns mt-4">
|
||||
<div class="column col-12 h6 text-uppercase mb-2 mt-4">
|
||||
{{ $t('message.editorTheme') }}
|
||||
</div>
|
||||
<div class="column col-6 h5 mb-4">
|
||||
<select
|
||||
v-model="localEditorTheme"
|
||||
class="form-select"
|
||||
@change="changeEditorTheme(localEditorTheme)"
|
||||
>
|
||||
<optgroup
|
||||
v-for="group in editorThemes"
|
||||
:key="group.group"
|
||||
:label="group.group"
|
||||
>
|
||||
<option
|
||||
v-for="theme in group.themes"
|
||||
:key="theme.name"
|
||||
:value="theme.code"
|
||||
:selected="editorTheme === theme.code"
|
||||
>
|
||||
{{ theme.name }}
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
<div class="column col-12">
|
||||
<QueryEditor
|
||||
:value="exampleQuery"
|
||||
:workspace="workspace"
|
||||
:read-only="true"
|
||||
:height="270"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -110,8 +205,9 @@
|
||||
<img :src="require('@/images/logo.svg').default" width="128">
|
||||
<h4>{{ appName }}</h4>
|
||||
<p>
|
||||
{{ $t('word.version') }}: {{ appVersion }}<br>
|
||||
<a class="c-hand" @click="openOutside('https://github.com/EStarium/antares')">GitHub</a><br>
|
||||
{{ $t('word.version') }} {{ appVersion }}<br>
|
||||
<a class="c-hand" @click="openOutside('https://github.com/Fabio286/antares')">GitHub</a> | <a class="c-hand" @click="openOutside('https://github.com/Fabio286/antares/blob/master/CHANGELOG.md')">CHANGELOG</a><br>
|
||||
<small>{{ $t('word.author') }} <a class="c-hand" @click="openOutside('https://github.com/Fabio286')">Fabio Di Stasio</a></small><br>
|
||||
<small>{{ $t('message.madeWithJS') }}</small>
|
||||
</p>
|
||||
</div>
|
||||
@@ -126,18 +222,70 @@
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import localesNames from '@/i18n/supported-locales';
|
||||
import ModalSettingsUpdate from '@/components/ModalSettingsUpdate';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
const { shell } = require('electron');
|
||||
|
||||
export default {
|
||||
name: 'ModalSettings',
|
||||
components: {
|
||||
ModalSettingsUpdate
|
||||
ModalSettingsUpdate,
|
||||
QueryEditor
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
localLocale: null,
|
||||
localTimeout: null,
|
||||
selectedTab: 'general'
|
||||
localEditorTheme: null,
|
||||
selectedTab: 'general',
|
||||
editorThemes: [
|
||||
{
|
||||
group: this.$t('word.light'),
|
||||
themes: [
|
||||
{ code: 'chrome', name: 'Chrome' },
|
||||
{ code: 'clouds', name: 'Clouds' },
|
||||
{ code: 'crimson_editor', name: 'Crimson Editor' },
|
||||
{ code: 'dawn', name: 'Dawn' },
|
||||
{ code: 'dreamweaver', name: 'Dreamweaver' },
|
||||
{ code: 'eclupse', name: 'Eclipse' },
|
||||
{ code: 'github', name: 'GitHub' },
|
||||
{ code: 'iplastic', name: 'IPlastic' },
|
||||
{ code: 'solarized_light', name: 'Solarized Light' },
|
||||
{ code: 'textmate', name: 'TextMate' },
|
||||
{ code: 'tomorrow', name: 'Tomorrow' },
|
||||
{ code: 'xcode', name: 'Xcode' },
|
||||
{ code: 'kuroir', name: 'Kuroir' },
|
||||
{ code: 'katzenmilch', name: 'KatzenMilch' },
|
||||
{ code: 'sqlserver', name: 'SQL Server' }
|
||||
]
|
||||
},
|
||||
{
|
||||
group: this.$t('word.dark'),
|
||||
themes: [
|
||||
{ code: 'ambiance', name: 'Ambiance' },
|
||||
{ code: 'chaos', name: 'Chaos' },
|
||||
{ code: 'clouds_midnight', name: 'Clouds Midnight' },
|
||||
{ code: 'dracula', name: 'Dracula' },
|
||||
{ code: 'cobalt', name: 'Cobalt' },
|
||||
{ code: 'gruvbox', name: 'Gruvbox' },
|
||||
{ code: 'gob', name: 'Green on Black' },
|
||||
{ code: 'idle_fingers', name: 'Idle Fingers' },
|
||||
{ code: 'kr_theme', name: 'krTheme' },
|
||||
{ code: 'merbivore', name: 'Merbivore' },
|
||||
{ code: 'mono_industrial', name: 'Mono Industrial' },
|
||||
{ code: 'monokai', name: 'Monokai' },
|
||||
{ code: 'nord_dark', name: 'Nord Dark' },
|
||||
{ code: 'pastel_on_dark', name: 'Pastel on Dark' },
|
||||
{ code: 'solarized_dark', name: 'Solarized Dark' },
|
||||
{ code: 'terminal', name: 'Terminal' },
|
||||
{ code: 'tomorrow_night', name: 'Tomorrow Night' },
|
||||
{ code: 'tomorrow_night_blue', name: 'Tomorrow Night Blue' },
|
||||
{ code: 'tomorrow_night_bright', name: 'Tomorrow Night Bright' },
|
||||
{ code: 'tomorrow_night_eighties', name: 'Tomorrow Night 80s' },
|
||||
{ code: 'twilight', name: 'Twilight' },
|
||||
{ code: 'vibrant_ink', name: 'Vibrant Ink' }
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -146,8 +294,14 @@ export default {
|
||||
appVersion: 'application/appVersion',
|
||||
selectedSettingTab: 'application/selectedSettingTab',
|
||||
selectedLocale: 'settings/getLocale',
|
||||
selectedAutoComplete: 'settings/getAutoComplete',
|
||||
selectedLineWrap: 'settings/getLineWrap',
|
||||
notificationsTimeout: 'settings/getNotificationsTimeout',
|
||||
updateStatus: 'application/getUpdateStatus'
|
||||
applicationTheme: 'settings/getApplicationTheme',
|
||||
editorTheme: 'settings/getEditorTheme',
|
||||
updateStatus: 'application/getUpdateStatus',
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
locales () {
|
||||
const locales = [];
|
||||
@@ -158,11 +312,32 @@ export default {
|
||||
},
|
||||
hasUpdates () {
|
||||
return ['available', 'downloading', 'downloaded'].includes(this.updateStatus);
|
||||
},
|
||||
workspace () {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
},
|
||||
exampleQuery () {
|
||||
return `-- This is an example
|
||||
SELECT
|
||||
employee.id,
|
||||
employee.first_name,
|
||||
employee.last_name,
|
||||
SUM(DATEDIFF("SECOND", call.start, call.end)) AS call_duration
|
||||
FROM call
|
||||
INNER JOIN employee ON call.employee_id = employee.id
|
||||
GROUP BY
|
||||
employee.id,
|
||||
employee.first_name,
|
||||
employee.last_name
|
||||
ORDER BY
|
||||
employee.id ASC;
|
||||
`;
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.localLocale = this.selectedLocale;
|
||||
this.localTimeout = this.notificationsTimeout;
|
||||
this.localEditorTheme = this.editorTheme;
|
||||
this.selectedTab = this.selectedSettingTab;
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
@@ -173,6 +348,9 @@ export default {
|
||||
...mapActions({
|
||||
closeModal: 'application/hideSettingModal',
|
||||
changeLocale: 'settings/changeLocale',
|
||||
changeAutoComplete: 'settings/changeAutoComplete',
|
||||
changeLineWrap: 'settings/changeLineWrap',
|
||||
changeEditorTheme: 'settings/changeEditorTheme',
|
||||
updateNotificationsTimeout: 'settings/updateNotificationsTimeout'
|
||||
}),
|
||||
selectTab (tab) {
|
||||
@@ -191,6 +369,12 @@ export default {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
},
|
||||
toggleAutoComplete () {
|
||||
this.changeAutoComplete(!this.selectedAutoComplete);
|
||||
},
|
||||
toggleLineWrap () {
|
||||
this.changeLineWrap(!this.selectedLineWrap);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -204,6 +388,34 @@ export default {
|
||||
.panel-body {
|
||||
height: calc(70vh - 70px);
|
||||
overflow: auto;
|
||||
|
||||
.theme-block {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
&.selected {
|
||||
img {
|
||||
box-shadow: 0 0 0 3px $primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.theme-name {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
text-shadow: 0 0 8px #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.badge::after {
|
||||
|
@@ -1,70 +1,282 @@
|
||||
<template>
|
||||
<div class="editor-wrapper">
|
||||
<div ref="editor" class="editor" />
|
||||
<div
|
||||
ref="editor"
|
||||
class="editor"
|
||||
:style="{height: `${height}px`}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
import { completionItemProvider } from '@/suggestions/sql';
|
||||
|
||||
monaco.languages.registerCompletionItemProvider('sql', completionItemProvider(monaco));
|
||||
import * as ace from 'ace-builds';
|
||||
import 'ace-builds/webpack-resolver';
|
||||
import '../libs/ext-language_tools';
|
||||
import { mapGetters } from 'vuex';
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
|
||||
export default {
|
||||
name: 'QueryEditor',
|
||||
props: {
|
||||
value: String
|
||||
value: String,
|
||||
workspace: Object,
|
||||
schema: { type: String, default: '' },
|
||||
autoFocus: { type: Boolean, default: false },
|
||||
readOnly: { type: Boolean, default: false },
|
||||
height: { type: Number, default: 200 }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
editor: null
|
||||
editor: null,
|
||||
fields: [],
|
||||
baseCompleter: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
editorTheme: 'settings/getEditorTheme',
|
||||
autoComplete: 'settings/getAutoComplete',
|
||||
lineWrap: 'settings/getLineWrap'
|
||||
}),
|
||||
tables () {
|
||||
return this.workspace
|
||||
? this.workspace.structure.filter(schema => schema.name === this.schema)
|
||||
.reduce((acc, curr) => {
|
||||
acc.push(...curr.tables);
|
||||
return acc;
|
||||
}, []).map(table => {
|
||||
return {
|
||||
name: table.name,
|
||||
type: table.type,
|
||||
fields: []
|
||||
};
|
||||
})
|
||||
: [];
|
||||
},
|
||||
triggers () {
|
||||
return this.workspace
|
||||
? this.workspace.structure.filter(schema => schema.name === this.schema)
|
||||
.reduce((acc, curr) => {
|
||||
acc.push(...curr.triggers);
|
||||
return acc;
|
||||
}, []).map(trigger => {
|
||||
return {
|
||||
name: trigger.name,
|
||||
type: 'trigger'
|
||||
};
|
||||
})
|
||||
: [];
|
||||
},
|
||||
procedures () {
|
||||
return this.workspace
|
||||
? this.workspace.structure.filter(schema => schema.name === this.schema)
|
||||
.reduce((acc, curr) => {
|
||||
acc.push(...curr.procedures);
|
||||
return acc;
|
||||
}, []).map(procedure => {
|
||||
return {
|
||||
name: `${procedure.name}()`,
|
||||
type: 'routine'
|
||||
};
|
||||
})
|
||||
: [];
|
||||
},
|
||||
functions () {
|
||||
return this.workspace
|
||||
? this.workspace.structure.filter(schema => schema.name === this.schema)
|
||||
.reduce((acc, curr) => {
|
||||
acc.push(...curr.functions);
|
||||
return acc;
|
||||
}, []).map(func => {
|
||||
return {
|
||||
name: `${func.name}()`,
|
||||
type: 'function'
|
||||
};
|
||||
})
|
||||
: [];
|
||||
},
|
||||
schedulers () {
|
||||
return this.workspace
|
||||
? this.workspace.structure.filter(schema => schema.name === this.schema)
|
||||
.reduce((acc, curr) => {
|
||||
acc.push(...curr.schedulers);
|
||||
return acc;
|
||||
}, []).map(scheduler => {
|
||||
return {
|
||||
name: scheduler.name,
|
||||
type: 'scheduler'
|
||||
};
|
||||
})
|
||||
: [];
|
||||
},
|
||||
mode () {
|
||||
switch (this.workspace.client) {
|
||||
case 'mysql':
|
||||
case 'maria':
|
||||
return 'mysql';
|
||||
case 'mssql':
|
||||
return 'sqlserver';
|
||||
case 'pg':
|
||||
return 'pgsql';
|
||||
default:
|
||||
return 'sql';
|
||||
}
|
||||
},
|
||||
lastWord () {
|
||||
const words = this.value.split(' ');
|
||||
return words[words.length - 1];
|
||||
},
|
||||
isLastWordATable () {
|
||||
return /\w+\.\w*/gm.test(this.lastWord);
|
||||
},
|
||||
fieldsCompleter () {
|
||||
return {
|
||||
getCompletions: (editor, session, pos, prefix, callback) => {
|
||||
const completions = [];
|
||||
this.fields.forEach(field => {
|
||||
completions.push({
|
||||
value: field,
|
||||
meta: 'column',
|
||||
score: 1000
|
||||
});
|
||||
});
|
||||
callback(null, completions);
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
editorTheme () {
|
||||
if (this.editor)
|
||||
this.editor.setTheme(`ace/theme/${this.editorTheme}`);
|
||||
},
|
||||
autoComplete () {
|
||||
if (this.editor) {
|
||||
this.editor.setOptions({
|
||||
enableLiveAutocompletion: this.autoComplete
|
||||
});
|
||||
}
|
||||
},
|
||||
lineWrap () {
|
||||
if (this.editor) {
|
||||
this.editor.setOptions({
|
||||
wrap: this.lineWrap
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.editor = monaco.editor.create(this.$refs.editor, {
|
||||
this.editor = ace.edit(this.$refs.editor, {
|
||||
mode: `ace/mode/${this.mode}`,
|
||||
theme: `ace/theme/${this.editorTheme}`,
|
||||
value: this.value,
|
||||
language: 'sql',
|
||||
theme: 'vs-dark',
|
||||
autoIndent: true,
|
||||
minimap: {
|
||||
enabled: false
|
||||
},
|
||||
contextmenu: false,
|
||||
wordBasedSuggestions: true,
|
||||
acceptSuggestionOnEnter: 'smart',
|
||||
quickSuggestions: true
|
||||
fontSize: '14px',
|
||||
printMargin: false,
|
||||
readOnly: this.readOnly
|
||||
});
|
||||
|
||||
this.editor.onDidChangeModelContent(e => {
|
||||
this.editor.setOptions({
|
||||
enableBasicAutocompletion: true,
|
||||
wrap: this.lineWrap,
|
||||
enableSnippets: true,
|
||||
enableLiveAutocompletion: this.autoComplete
|
||||
});
|
||||
|
||||
this.editor.completers.push({
|
||||
getCompletions: (editor, session, pos, prefix, callback) => {
|
||||
const completions = [];
|
||||
[
|
||||
...this.tables,
|
||||
...this.triggers,
|
||||
...this.procedures,
|
||||
...this.functions,
|
||||
...this.schedulers
|
||||
].forEach(el => {
|
||||
completions.push({
|
||||
value: el.name,
|
||||
meta: el.type
|
||||
});
|
||||
});
|
||||
callback(null, completions);
|
||||
}
|
||||
});
|
||||
|
||||
this.baseCompleter = this.editor.completers;
|
||||
|
||||
this.editor.commands.on('afterExec', e => {
|
||||
if (['insertstring', 'backspace', 'del'].includes(e.command.name)) {
|
||||
if (this.isLastWordATable || e.args === '.') {
|
||||
if (e.args !== ' ') {
|
||||
const table = this.tables.find(t => t.name === this.lastWord.split('.').pop());
|
||||
|
||||
if (table) {
|
||||
const params = {
|
||||
uid: this.workspace.uid,
|
||||
schema: this.schema,
|
||||
table: table.name
|
||||
};
|
||||
|
||||
Tables.getTableColumns(params).then(res => {
|
||||
if (res.response.length)
|
||||
this.fields = res.response.map(field => field.name);
|
||||
this.editor.completers = [this.fieldsCompleter];
|
||||
this.editor.execCommand('startAutocomplete');
|
||||
}).catch(console.log);
|
||||
}
|
||||
else
|
||||
this.editor.completers = this.baseCompleter;
|
||||
}
|
||||
else
|
||||
this.editor.completers = this.baseCompleter;
|
||||
}
|
||||
else
|
||||
this.editor.completers = this.baseCompleter;
|
||||
}
|
||||
});
|
||||
|
||||
this.editor.session.on('change', () => {
|
||||
const content = this.editor.getValue();
|
||||
this.$emit('update:value', content);
|
||||
});
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.editor && this.editor.dispose();
|
||||
|
||||
if (this.autoFocus) {
|
||||
setTimeout(() => {
|
||||
this.editor.focus();
|
||||
this.editor.resize();
|
||||
}, 20);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.editor.resize();
|
||||
}, 20);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.editor-wrapper {
|
||||
border-bottom: 1px solid #444;
|
||||
.editor-wrapper {
|
||||
border-bottom: 1px solid #444;
|
||||
|
||||
.editor {
|
||||
height: 200px;
|
||||
width: 100%;
|
||||
}
|
||||
.editor {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
.CodeMirror-scroll {
|
||||
max-width: 100%;
|
||||
}
|
||||
.ace_.mdi {
|
||||
display: inline-block;
|
||||
width: 17px;
|
||||
}
|
||||
|
||||
.CodeMirror-line {
|
||||
word-break: break-word !important;
|
||||
white-space: pre-wrap !important;
|
||||
}
|
||||
}
|
||||
.ace_dark.ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {
|
||||
background-color: #c9561a99;
|
||||
}
|
||||
|
||||
.ace_dark.ace_editor.ace_autocomplete .ace_marker-layer .ace_line-hover {
|
||||
background-color: #c9571a33;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.ace_dark.ace_editor.ace_autocomplete .ace_completion-highlight {
|
||||
color: #e0d00c;
|
||||
}
|
||||
</style>
|
||||
|
@@ -15,7 +15,7 @@
|
||||
<i class="mdi mdi-18px mdi-coffee mr-1" />
|
||||
<small>{{ $t('word.donate') }}</small>
|
||||
</li>
|
||||
<li class="footer-element footer-link" @click="openOutside('https://github.com/EStarium/antares/issues')">
|
||||
<li class="footer-element footer-link" @click="openOutside('https://github.com/Fabio286/antares/issues')">
|
||||
<i class="mdi mdi-18px mdi-bug" />
|
||||
</li>
|
||||
<li class="footer-element footer-link" @click="showSettingModal('about')">
|
||||
|
@@ -13,25 +13,25 @@
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
v-if="workspace.breadcrumbs.table"
|
||||
v-if="schemaChild"
|
||||
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 mr-1" />
|
||||
<span :title="workspace.breadcrumbs.table">{{ $t('word.properties').toUpperCase() }}: {{ workspace.breadcrumbs.table }}</span>
|
||||
<span :title="schemaChild">{{ $t('word.properties').toUpperCase() }}: {{ schemaChild }}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
v-if="workspace.breadcrumbs.table"
|
||||
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 mdi-table mr-1" />
|
||||
<span :title="workspace.breadcrumbs.table">{{ $t('word.data').toUpperCase() }}: {{ workspace.breadcrumbs.table }}</span>
|
||||
<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
|
||||
@@ -66,15 +66,45 @@
|
||||
</li>
|
||||
</ul>
|
||||
<WorkspacePropsTab
|
||||
v-show="selectedTab === 'prop'"
|
||||
v-show="selectedTab === 'prop' && workspace.breadcrumbs.table"
|
||||
:is-selected="selectedTab === 'prop'"
|
||||
:connection="connection"
|
||||
:table="workspace.breadcrumbs.table"
|
||||
/>
|
||||
<WorkspacePropsTabView
|
||||
v-show="selectedTab === 'prop' && workspace.breadcrumbs.view"
|
||||
:is-selected="selectedTab === 'prop'"
|
||||
:connection="connection"
|
||||
:view="workspace.breadcrumbs.view"
|
||||
/>
|
||||
<WorkspacePropsTabTrigger
|
||||
v-show="selectedTab === 'prop' && workspace.breadcrumbs.trigger"
|
||||
:is-selected="selectedTab === 'prop'"
|
||||
:connection="connection"
|
||||
:trigger="workspace.breadcrumbs.trigger"
|
||||
/>
|
||||
<WorkspacePropsTabRoutine
|
||||
v-show="selectedTab === 'prop' && workspace.breadcrumbs.procedure"
|
||||
:is-selected="selectedTab === 'prop'"
|
||||
:connection="connection"
|
||||
:routine="workspace.breadcrumbs.procedure"
|
||||
/>
|
||||
<WorkspacePropsTabFunction
|
||||
v-show="selectedTab === 'prop' && workspace.breadcrumbs.function"
|
||||
:is-selected="selectedTab === 'prop'"
|
||||
:connection="connection"
|
||||
:function="workspace.breadcrumbs.function"
|
||||
/>
|
||||
<WorkspacePropsTabScheduler
|
||||
v-show="selectedTab === 'prop' && workspace.breadcrumbs.scheduler"
|
||||
:is-selected="selectedTab === 'prop'"
|
||||
:connection="connection"
|
||||
:scheduler="workspace.breadcrumbs.scheduler"
|
||||
/>
|
||||
<WorkspaceTableTab
|
||||
v-show="selectedTab === 'data'"
|
||||
:connection="connection"
|
||||
:table="workspace.breadcrumbs.table"
|
||||
:table="workspace.breadcrumbs.table || workspace.breadcrumbs.view"
|
||||
/>
|
||||
<WorkspaceQueryTab
|
||||
v-for="tab of queryTabs"
|
||||
@@ -94,6 +124,11 @@ import WorkspaceExploreBar from '@/components/WorkspaceExploreBar';
|
||||
import WorkspaceQueryTab from '@/components/WorkspaceQueryTab';
|
||||
import WorkspaceTableTab from '@/components/WorkspaceTableTab';
|
||||
import WorkspacePropsTab from '@/components/WorkspacePropsTab';
|
||||
import WorkspacePropsTabView from '@/components/WorkspacePropsTabView';
|
||||
import WorkspacePropsTabTrigger from '@/components/WorkspacePropsTabTrigger';
|
||||
import WorkspacePropsTabRoutine from '@/components/WorkspacePropsTabRoutine';
|
||||
import WorkspacePropsTabFunction from '@/components/WorkspacePropsTabFunction';
|
||||
import WorkspacePropsTabScheduler from '@/components/WorkspacePropsTabScheduler';
|
||||
|
||||
export default {
|
||||
name: 'Workspace',
|
||||
@@ -101,7 +136,12 @@ export default {
|
||||
WorkspaceExploreBar,
|
||||
WorkspaceQueryTab,
|
||||
WorkspaceTableTab,
|
||||
WorkspacePropsTab
|
||||
WorkspacePropsTab,
|
||||
WorkspacePropsTabView,
|
||||
WorkspacePropsTabTrigger,
|
||||
WorkspacePropsTabRoutine,
|
||||
WorkspacePropsTabFunction,
|
||||
WorkspacePropsTabScheduler
|
||||
},
|
||||
props: {
|
||||
connection: Object
|
||||
@@ -123,7 +163,15 @@ export default {
|
||||
return this.selectedWorkspace === this.connection.uid;
|
||||
},
|
||||
selectedTab () {
|
||||
if (this.workspace.breadcrumbs.table === null)
|
||||
if (
|
||||
this.workspace.breadcrumbs.table === null &&
|
||||
this.workspace.breadcrumbs.view === null &&
|
||||
this.workspace.breadcrumbs.trigger === null &&
|
||||
this.workspace.breadcrumbs.procedure === null &&
|
||||
this.workspace.breadcrumbs.function === null &&
|
||||
this.workspace.breadcrumbs.scheduler === null &&
|
||||
['data', 'prop'].includes(this.workspace.selected_tab)
|
||||
)
|
||||
return this.queryTabs[0].uid;
|
||||
|
||||
return this.queryTabs.find(tab => tab.uid === this.workspace.selected_tab) ||
|
||||
@@ -133,6 +181,13 @@ export default {
|
||||
},
|
||||
queryTabs () {
|
||||
return this.workspace.tabs.filter(tab => tab.type === 'query');
|
||||
},
|
||||
schemaChild () {
|
||||
for (const key in this.workspace.breadcrumbs) {
|
||||
if (key === 'schema') continue;
|
||||
if (this.workspace.breadcrumbs[key]) return this.workspace.breadcrumbs[key];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
@@ -193,6 +248,7 @@ export default {
|
||||
align-items: flex-start;
|
||||
flex-wrap: nowrap;
|
||||
overflow: auto;
|
||||
margin-bottom: 0;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 2px;
|
||||
|
@@ -40,6 +40,7 @@
|
||||
:connection="connection"
|
||||
@show-database-context="openDatabaseContext"
|
||||
@show-table-context="openTableContext"
|
||||
@show-misc-context="openMiscContext"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -54,12 +55,47 @@
|
||||
@close="hideCreateTableModal"
|
||||
@open-create-table-editor="openCreateTableEditor"
|
||||
/>
|
||||
<ModalNewView
|
||||
v-if="isNewViewModal"
|
||||
:workspace="workspace"
|
||||
@close="hideCreateViewModal"
|
||||
@open-create-view-editor="openCreateViewEditor"
|
||||
/>
|
||||
<ModalNewTrigger
|
||||
v-if="isNewTriggerModal"
|
||||
:workspace="workspace"
|
||||
@close="hideCreateTriggerModal"
|
||||
@open-create-trigger-editor="openCreateTriggerEditor"
|
||||
/>
|
||||
<ModalNewRoutine
|
||||
v-if="isNewRoutineModal"
|
||||
:workspace="workspace"
|
||||
@close="hideCreateRoutineModal"
|
||||
@open-create-routine-editor="openCreateRoutineEditor"
|
||||
/>
|
||||
<ModalNewFunction
|
||||
v-if="isNewFunctionModal"
|
||||
:workspace="workspace"
|
||||
@close="hideCreateFunctionModal"
|
||||
@open-create-function-editor="openCreateFunctionEditor"
|
||||
/>
|
||||
<ModalNewScheduler
|
||||
v-if="isNewSchedulerModal"
|
||||
:workspace="workspace"
|
||||
@close="hideCreateSchedulerModal"
|
||||
@open-create-scheduler-editor="openCreateSchedulerEditor"
|
||||
/>
|
||||
<DatabaseContext
|
||||
v-if="isDatabaseContext"
|
||||
:selected-database="selectedDatabase"
|
||||
:context-event="databaseContextEvent"
|
||||
@close-context="closeDatabaseContext"
|
||||
@show-create-table-modal="showCreateTableModal"
|
||||
@show-create-view-modal="showCreateViewModal"
|
||||
@show-create-trigger-modal="showCreateTriggerModal"
|
||||
@show-create-routine-modal="showCreateRoutineModal"
|
||||
@show-create-function-modal="showCreateFunctionModal"
|
||||
@show-create-scheduler-modal="showCreateSchedulerModal"
|
||||
@reload="refresh"
|
||||
/>
|
||||
<TableContext
|
||||
@@ -69,19 +105,39 @@
|
||||
@close-context="closeTableContext"
|
||||
@reload="refresh"
|
||||
/>
|
||||
<MiscContext
|
||||
v-if="isMiscContext"
|
||||
:selected-misc="selectedMisc"
|
||||
:context-event="miscContextEvent"
|
||||
@close-context="closeMiscContext"
|
||||
@reload="refresh"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import _ from 'lodash';
|
||||
import _ from 'lodash';// TODO: remove
|
||||
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
import Views from '@/ipc-api/Views';
|
||||
import Triggers from '@/ipc-api/Triggers';
|
||||
import Routines from '@/ipc-api/Routines';
|
||||
import Functions from '@/ipc-api/Functions';
|
||||
import Schedulers from '@/ipc-api/Schedulers';
|
||||
|
||||
import WorkspaceConnectPanel from '@/components/WorkspaceConnectPanel';
|
||||
import WorkspaceExploreBarDatabase from '@/components/WorkspaceExploreBarDatabase';
|
||||
import DatabaseContext from '@/components/WorkspaceExploreBarDatabaseContext';
|
||||
import TableContext from '@/components/WorkspaceExploreBarTableContext';
|
||||
import MiscContext from '@/components/WorkspaceExploreBarMiscContext';
|
||||
import ModalNewDatabase from '@/components/ModalNewDatabase';
|
||||
import ModalNewTable from '@/components/ModalNewTable';
|
||||
import ModalNewView from '@/components/ModalNewView';
|
||||
import ModalNewTrigger from '@/components/ModalNewTrigger';
|
||||
import ModalNewRoutine from '@/components/ModalNewRoutine';
|
||||
import ModalNewFunction from '@/components/ModalNewFunction';
|
||||
import ModalNewScheduler from '@/components/ModalNewScheduler';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceExploreBar',
|
||||
@@ -90,8 +146,14 @@ export default {
|
||||
WorkspaceExploreBarDatabase,
|
||||
DatabaseContext,
|
||||
TableContext,
|
||||
MiscContext,
|
||||
ModalNewDatabase,
|
||||
ModalNewTable
|
||||
ModalNewTable,
|
||||
ModalNewView,
|
||||
ModalNewTrigger,
|
||||
ModalNewRoutine,
|
||||
ModalNewFunction,
|
||||
ModalNewScheduler
|
||||
},
|
||||
props: {
|
||||
connection: Object,
|
||||
@@ -100,15 +162,27 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
isRefreshing: false,
|
||||
|
||||
isNewDBModal: false,
|
||||
isNewTableModal: false,
|
||||
isNewViewModal: false,
|
||||
isNewTriggerModal: false,
|
||||
isNewRoutineModal: false,
|
||||
isNewFunctionModal: false,
|
||||
isNewSchedulerModal: false,
|
||||
|
||||
localWidth: null,
|
||||
isDatabaseContext: false,
|
||||
isTableContext: false,
|
||||
isMiscContext: false,
|
||||
|
||||
databaseContextEvent: null,
|
||||
tableContextEvent: null,
|
||||
miscContextEvent: null,
|
||||
|
||||
selectedDatabase: '',
|
||||
selectedTable: ''
|
||||
selectedTable: null,
|
||||
selectedMisc: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -215,6 +289,129 @@ export default {
|
||||
},
|
||||
closeTableContext () {
|
||||
this.isTableContext = false;
|
||||
},
|
||||
openMiscContext (payload) {
|
||||
this.selectedMisc = payload.misc;
|
||||
this.miscContextEvent = payload.event;
|
||||
this.isMiscContext = true;
|
||||
},
|
||||
closeMiscContext () {
|
||||
this.isMiscContext = false;
|
||||
},
|
||||
showCreateViewModal () {
|
||||
this.closeDatabaseContext();
|
||||
this.isNewViewModal = true;
|
||||
},
|
||||
hideCreateViewModal () {
|
||||
this.isNewViewModal = false;
|
||||
},
|
||||
async openCreateViewEditor (payload) {
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
...payload
|
||||
};
|
||||
|
||||
const { status, response } = await Views.createView(params);
|
||||
|
||||
if (status === 'success') {
|
||||
await this.refresh();
|
||||
this.changeBreadcrumbs({ schema: this.selectedDatabase, view: payload.name });
|
||||
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
},
|
||||
showCreateTriggerModal () {
|
||||
this.closeDatabaseContext();
|
||||
this.isNewTriggerModal = true;
|
||||
},
|
||||
hideCreateTriggerModal () {
|
||||
this.isNewTriggerModal = false;
|
||||
},
|
||||
async openCreateTriggerEditor (payload) {
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
...payload
|
||||
};
|
||||
|
||||
const { status, response } = await Triggers.createTrigger(params);
|
||||
|
||||
if (status === 'success') {
|
||||
await this.refresh();
|
||||
this.changeBreadcrumbs({ schema: this.selectedDatabase, trigger: payload.name });
|
||||
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
},
|
||||
showCreateRoutineModal () {
|
||||
this.closeDatabaseContext();
|
||||
this.isNewRoutineModal = true;
|
||||
},
|
||||
hideCreateRoutineModal () {
|
||||
this.isNewRoutineModal = false;
|
||||
},
|
||||
async openCreateRoutineEditor (payload) {
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
...payload
|
||||
};
|
||||
|
||||
const { status, response } = await Routines.createRoutine(params);
|
||||
|
||||
if (status === 'success') {
|
||||
await this.refresh();
|
||||
this.changeBreadcrumbs({ schema: this.selectedDatabase, procedure: payload.name });
|
||||
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
},
|
||||
showCreateFunctionModal () {
|
||||
this.closeDatabaseContext();
|
||||
this.isNewFunctionModal = true;
|
||||
},
|
||||
hideCreateFunctionModal () {
|
||||
this.isNewFunctionModal = false;
|
||||
},
|
||||
showCreateSchedulerModal () {
|
||||
this.closeDatabaseContext();
|
||||
this.isNewSchedulerModal = true;
|
||||
},
|
||||
hideCreateSchedulerModal () {
|
||||
this.isNewSchedulerModal = false;
|
||||
},
|
||||
async openCreateFunctionEditor (payload) {
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
...payload
|
||||
};
|
||||
|
||||
const { status, response } = await Functions.createFunction(params);
|
||||
|
||||
if (status === 'success') {
|
||||
await this.refresh();
|
||||
this.changeBreadcrumbs({ schema: this.selectedDatabase, function: payload.name });
|
||||
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
},
|
||||
async openCreateSchedulerEditor (payload) {
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
...payload
|
||||
};
|
||||
|
||||
const { status, response } = await Schedulers.createScheduler(params);
|
||||
|
||||
if (status === 'success') {
|
||||
await this.refresh();
|
||||
this.changeBreadcrumbs({ schema: this.selectedDatabase, scheduler: payload.name });
|
||||
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -3,10 +3,11 @@
|
||||
<summary
|
||||
class="accordion-header database-name"
|
||||
:class="{'text-bold': breadcrumbs.schema === database.name}"
|
||||
@click="changeBreadcrumbs({schema: database.name, table: null})"
|
||||
@click="selectSchema(database.name)"
|
||||
@contextmenu.prevent="showDatabaseContext($event, database.name)"
|
||||
>
|
||||
<i class="icon mdi mdi-18px mdi-chevron-right" />
|
||||
<div v-if="isLoading" class="icon loading" />
|
||||
<i v-else class="icon mdi mdi-18px mdi-chevron-right" />
|
||||
<i class="database-icon mdi mdi-18px mdi-database mr-1" />
|
||||
<span>{{ database.name }}</span>
|
||||
</summary>
|
||||
@@ -17,20 +18,136 @@
|
||||
v-for="table of database.tables"
|
||||
:key="table.name"
|
||||
class="menu-item"
|
||||
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.table === table.name}"
|
||||
@click="setBreadcrumbs({schema: database.name, table: table.name})"
|
||||
@contextmenu.prevent="showTableContext($event, table.name)"
|
||||
:class="{'text-bold': breadcrumbs.schema === database.name && [breadcrumbs.table, breadcrumbs.view].includes(table.name)}"
|
||||
@click="setBreadcrumbs({schema: database.name, [table.type]: table.name})"
|
||||
@contextmenu.prevent="showTableContext($event, table)"
|
||||
>
|
||||
<a class="table-name">
|
||||
<i class="table-icon mdi mdi-18px mr-1" :class="table.type === 'view' ? 'mdi-table-eye' : 'mdi-table'" />
|
||||
<span>{{ table.name }}</span>
|
||||
</a>
|
||||
<div class="table-size tooltip tooltip-left mr-1" :data-tooltip="formatBytes(table.size)">
|
||||
<div
|
||||
v-if="table.type === 'table'"
|
||||
class="table-size tooltip tooltip-left mr-1"
|
||||
:data-tooltip="formatBytes(table.size)"
|
||||
>
|
||||
<div class="pie" :style="piePercentage(table.size)" />
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-if="database.triggers.length" class="database-misc">
|
||||
<details class="accordion">
|
||||
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.trigger}">
|
||||
<i class="misc-icon mdi mdi-18px mdi-folder-cog mr-1" />
|
||||
{{ $tc('word.trigger', 2) }}
|
||||
</summary>
|
||||
<div class="accordion-body">
|
||||
<div>
|
||||
<ul class="menu menu-nav pt-0">
|
||||
<li
|
||||
v-for="trigger of database.triggers"
|
||||
:key="trigger.name"
|
||||
class="menu-item"
|
||||
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.trigger === trigger.name}"
|
||||
@click="setBreadcrumbs({schema: database.name, trigger: trigger.name})"
|
||||
@contextmenu.prevent="showMiscContext($event, {...trigger, type: 'trigger'})"
|
||||
>
|
||||
<a class="table-name">
|
||||
<i class="table-icon mdi mdi-table-cog mdi-18px mr-1" />
|
||||
<span>{{ trigger.name }}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div v-if="database.procedures.length" class="database-misc">
|
||||
<details class="accordion">
|
||||
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.procedure}">
|
||||
<i class="misc-icon mdi mdi-18px mdi-folder-sync mr-1" />
|
||||
{{ $tc('word.storedRoutine', 2) }}
|
||||
</summary>
|
||||
<div class="accordion-body">
|
||||
<div>
|
||||
<ul class="menu menu-nav pt-0">
|
||||
<li
|
||||
v-for="procedure of database.procedures"
|
||||
:key="procedure.name"
|
||||
class="menu-item"
|
||||
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.procedure === procedure.name}"
|
||||
@click="setBreadcrumbs({schema: database.name, procedure: procedure.name})"
|
||||
@contextmenu.prevent="showMiscContext($event, {...procedure, type: 'procedure'})"
|
||||
>
|
||||
<a class="table-name">
|
||||
<i class="table-icon mdi mdi-sync-circle mdi-18px mr-1" />
|
||||
<span>{{ procedure.name }}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div v-if="database.functions.length" class="database-misc">
|
||||
<details class="accordion">
|
||||
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.function}">
|
||||
<i class="misc-icon mdi mdi-18px mdi-folder-move mr-1" />
|
||||
{{ $tc('word.function', 2) }}
|
||||
</summary>
|
||||
<div class="accordion-body">
|
||||
<div>
|
||||
<ul class="menu menu-nav pt-0">
|
||||
<li
|
||||
v-for="func of database.functions"
|
||||
:key="func.name"
|
||||
class="menu-item"
|
||||
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.function === func.name}"
|
||||
@click="setBreadcrumbs({schema: database.name, function: func.name})"
|
||||
@contextmenu.prevent="showMiscContext($event, {...func, type: 'function'})"
|
||||
>
|
||||
<a class="table-name">
|
||||
<i class="table-icon mdi mdi-arrow-right-bold-box mdi-18px mr-1" />
|
||||
<span>{{ func.name }}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div v-if="database.schedulers.length" class="database-misc">
|
||||
<details class="accordion">
|
||||
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.scheduler}">
|
||||
<i class="misc-icon mdi mdi-18px mdi-folder-clock mr-1" />
|
||||
{{ $tc('word.scheduler', 2) }}
|
||||
</summary>
|
||||
<div class="accordion-body">
|
||||
<div>
|
||||
<ul class="menu menu-nav pt-0">
|
||||
<li
|
||||
v-for="scheduler of database.schedulers"
|
||||
:key="scheduler.name"
|
||||
class="menu-item"
|
||||
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.scheduler === scheduler.name}"
|
||||
@click="setBreadcrumbs({schema: database.name, scheduler: scheduler.name})"
|
||||
@contextmenu.prevent="showMiscContext($event, {...scheduler, type: 'scheduler'})"
|
||||
>
|
||||
<a class="table-name">
|
||||
<i class="table-icon mdi mdi-calendar-clock mdi-18px mr-1" />
|
||||
<span>{{ scheduler.name }}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</template>
|
||||
@@ -45,13 +162,22 @@ export default {
|
||||
database: Object,
|
||||
connection: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isLoading: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
getLoadedSchemas: 'workspaces/getLoadedSchemas',
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
breadcrumbs () {
|
||||
return this.getWorkspace(this.connection.uid).breadcrumbs;
|
||||
},
|
||||
loadedSchemas () {
|
||||
return this.getLoadedSchemas(this.connection.uid);
|
||||
},
|
||||
maxSize () {
|
||||
return this.database.tables.reduce((acc, curr) => {
|
||||
if (curr.size > acc) acc = curr.size;
|
||||
@@ -64,15 +190,31 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
|
||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
|
||||
refreshSchema: 'workspaces/refreshSchema'
|
||||
}),
|
||||
formatBytes,
|
||||
async selectSchema (schema) {
|
||||
if (!this.loadedSchemas.has(schema)) {
|
||||
this.isLoading = true;
|
||||
await this.refreshSchema({ uid: this.connection.uid, schema });
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
||||
this.changeBreadcrumbs({ schema, table: null });
|
||||
},
|
||||
showDatabaseContext (event, database) {
|
||||
this.changeBreadcrumbs({ schema: database, table: null });
|
||||
this.$emit('show-database-context', { event, database });
|
||||
},
|
||||
showTableContext (event, table) {
|
||||
this.setBreadcrumbs({ schema: this.database.name, [table.type]: table.name });
|
||||
this.$emit('show-table-context', { event, table });
|
||||
},
|
||||
showMiscContext (event, misc) {
|
||||
this.setBreadcrumbs({ schema: this.database.name, [misc.type]: misc.name });
|
||||
this.$emit('show-misc-context', { event, misc });
|
||||
},
|
||||
piePercentage (val) {
|
||||
const perc = val / this.maxSize * 100;
|
||||
return { background: `conic-gradient(lime ${perc}%, white 0)` };
|
||||
@@ -88,6 +230,7 @@ export default {
|
||||
<style lang="scss">
|
||||
.workspace-explorebar-database {
|
||||
.database-name,
|
||||
.misc-name,
|
||||
a.table-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -103,12 +246,30 @@ export default {
|
||||
}
|
||||
|
||||
.database-icon,
|
||||
.table-icon {
|
||||
.table-icon,
|
||||
.misc-icon {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.loading {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
|
||||
&::after {
|
||||
height: 0.6rem;
|
||||
width: 0.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.database-name {
|
||||
.misc-name {
|
||||
line-height: 1;
|
||||
padding: 0.1rem 1rem 0.1rem 0.1rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.database-name,
|
||||
.misc-name {
|
||||
&:hover {
|
||||
color: $body-font-color;
|
||||
background: rgba($color: #fff, $alpha: 0.05);
|
||||
@@ -138,6 +299,18 @@ export default {
|
||||
margin-left: 1.2rem;
|
||||
}
|
||||
|
||||
.database-misc {
|
||||
margin-left: 1.6rem;
|
||||
|
||||
.accordion[open] .accordion-header > .misc-icon:first-child::before {
|
||||
content: "\F0770";
|
||||
}
|
||||
|
||||
.accordion-body {
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.table-size {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
|
@@ -10,6 +10,21 @@
|
||||
<div class="context-element" @click="showCreateTableModal">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-table text-light pr-1" /> {{ $t('word.table') }}</span>
|
||||
</div>
|
||||
<div class="context-element" @click="showCreateViewModal">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-table-eye text-light pr-1" /> {{ $t('word.view') }}</span>
|
||||
</div>
|
||||
<div class="context-element" @click="showCreateTriggerModal">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-table-cog text-light pr-1" /> {{ $tc('word.trigger', 1) }}</span>
|
||||
</div>
|
||||
<div class="context-element" @click="showCreateRoutineModal">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-sync-circle pr-1" /> {{ $tc('word.storedRoutine', 1) }}</span>
|
||||
</div>
|
||||
<div class="context-element" @click="showCreateFunctionModal">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-arrow-right-bold-box pr-1" /> {{ $tc('word.function', 1) }}</span>
|
||||
</div>
|
||||
<div class="context-element" @click="showCreateSchedulerModal">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-calendar-clock text-light pr-1" /> {{ $tc('word.scheduler', 1) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="context-element" @click="showEditModal">
|
||||
@@ -84,6 +99,21 @@ export default {
|
||||
showCreateTableModal () {
|
||||
this.$emit('show-create-table-modal');
|
||||
},
|
||||
showCreateViewModal () {
|
||||
this.$emit('show-create-view-modal');
|
||||
},
|
||||
showCreateTriggerModal () {
|
||||
this.$emit('show-create-trigger-modal');
|
||||
},
|
||||
showCreateRoutineModal () {
|
||||
this.$emit('show-create-routine-modal');
|
||||
},
|
||||
showCreateFunctionModal () {
|
||||
this.$emit('show-create-function-modal');
|
||||
},
|
||||
showCreateSchedulerModal () {
|
||||
this.$emit('show-create-scheduler-modal');
|
||||
},
|
||||
showDeleteModal () {
|
||||
this.isDeleteModal = true;
|
||||
},
|
||||
@@ -124,3 +154,8 @@ export default {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.context-submenu {
|
||||
min-width: 150px !important;
|
||||
}
|
||||
</style>
|
||||
|
154
src/renderer/components/WorkspaceExploreBarMiscContext.vue
Normal file
154
src/renderer/components/WorkspaceExploreBarMiscContext.vue
Normal file
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<BaseContextMenu
|
||||
:context-event="contextEvent"
|
||||
@close-context="closeContext"
|
||||
>
|
||||
<div
|
||||
v-if="['procedure', 'function'].includes(selectedMisc.type)"
|
||||
class="context-element disabled"
|
||||
@click="showRunModal"
|
||||
>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-play text-light pr-1" /> {{ $t('word.run') }}</span>
|
||||
</div>
|
||||
<div class="context-element" @click="showDeleteModal">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-table-remove text-light pr-1" /> {{ $t('word.delete') }}</span>
|
||||
</div>
|
||||
<ConfirmModal
|
||||
v-if="isDeleteModal"
|
||||
@confirm="deleteMisc"
|
||||
@hide="hideDeleteModal"
|
||||
>
|
||||
<template slot="header">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-delete mr-1" /> {{ deleteMessage }}
|
||||
</div>
|
||||
</template>
|
||||
<div slot="body">
|
||||
<div class="mb-2">
|
||||
{{ $t('message.deleteCorfirm') }} "<b>{{ selectedMisc.name }}</b>"?
|
||||
</div>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</BaseContextMenu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import BaseContextMenu from '@/components/BaseContextMenu';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
import Triggers from '@/ipc-api/Triggers';
|
||||
import Routines from '@/ipc-api/Routines';
|
||||
import Functions from '@/ipc-api/Functions';
|
||||
import Schedulers from '@/ipc-api/Schedulers';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceExploreBarMiscContext',
|
||||
components: {
|
||||
BaseContextMenu,
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
contextEvent: MouseEvent,
|
||||
selectedMisc: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isDeleteModal: false,
|
||||
isRunModal: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
},
|
||||
deleteMessage () {
|
||||
switch (this.selectedMisc.type) {
|
||||
case 'trigger':
|
||||
return this.$t('message.deleteTrigger');
|
||||
case 'procedure':
|
||||
return this.$t('message.deleteRoutine');
|
||||
case 'function':
|
||||
return this.$t('message.deleteFunction');
|
||||
case 'scheduler':
|
||||
return this.$t('message.deleteScheduler');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
|
||||
}),
|
||||
showCreateTableModal () {
|
||||
this.$emit('show-create-table-modal');
|
||||
},
|
||||
showDeleteModal () {
|
||||
this.isDeleteModal = true;
|
||||
},
|
||||
hideDeleteModal () {
|
||||
this.isDeleteModal = false;
|
||||
},
|
||||
showRunModal () {
|
||||
this.isRunModal = true;
|
||||
},
|
||||
hideRunModal () {
|
||||
this.isRunModal = false;
|
||||
},
|
||||
closeContext () {
|
||||
this.$emit('close-context');
|
||||
},
|
||||
async deleteMisc () {
|
||||
try {
|
||||
let res;
|
||||
|
||||
switch (this.selectedMisc.type) {
|
||||
case 'trigger':
|
||||
res = await Triggers.dropTrigger({
|
||||
uid: this.selectedWorkspace,
|
||||
trigger: this.selectedMisc.name
|
||||
});
|
||||
break;
|
||||
case 'procedure':
|
||||
res = await Routines.dropRoutine({
|
||||
uid: this.selectedWorkspace,
|
||||
routine: this.selectedMisc.name
|
||||
});
|
||||
break;
|
||||
case 'function':
|
||||
res = await Functions.dropFunction({
|
||||
uid: this.selectedWorkspace,
|
||||
func: this.selectedMisc.name
|
||||
});
|
||||
break;
|
||||
case 'scheduler':
|
||||
res = await Schedulers.dropScheduler({
|
||||
uid: this.selectedWorkspace,
|
||||
scheduler: this.selectedMisc.name
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
const { status, response } = res;
|
||||
|
||||
if (status === 'success') {
|
||||
this.changeBreadcrumbs({ [this.selectedMisc.type]: null });
|
||||
|
||||
this.closeContext();
|
||||
this.$emit('reload');
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -3,7 +3,11 @@
|
||||
:context-event="contextEvent"
|
||||
@close-context="closeContext"
|
||||
>
|
||||
<div class="context-element" @click="showEmptyModal">
|
||||
<div
|
||||
v-if="selectedTable.type === 'table'"
|
||||
class="context-element"
|
||||
@click="showEmptyModal"
|
||||
>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-table-off text-light pr-1" /> {{ $t('message.emptyTable') }}</span>
|
||||
</div>
|
||||
<div class="context-element" @click="showDeleteModal">
|
||||
@@ -22,7 +26,7 @@
|
||||
</template>
|
||||
<div slot="body">
|
||||
<div class="mb-2">
|
||||
{{ $t('message.emptyCorfirm') }} "<b>{{ selectedTable }}</b>"?
|
||||
{{ $t('message.emptyCorfirm') }} "<b>{{ selectedTable.name }}</b>"?
|
||||
</div>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
@@ -33,12 +37,12 @@
|
||||
>
|
||||
<template slot="header">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-table-remove mr-1" /> {{ $t('message.deleteTable') }}
|
||||
<i class="mdi mdi-24px mdi-table-remove mr-1" /> {{ selectedTable.type === 'table' ? $t('message.deleteTable') : $t('message.deleteView') }}
|
||||
</div>
|
||||
</template>
|
||||
<div slot="body">
|
||||
<div class="mb-2">
|
||||
{{ $t('message.deleteCorfirm') }} "<b>{{ selectedTable }}</b>"?
|
||||
{{ $t('message.deleteCorfirm') }} "<b>{{ selectedTable.name }}</b>"?
|
||||
</div>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
@@ -50,6 +54,7 @@ import { mapGetters, mapActions } from 'vuex';
|
||||
import BaseContextMenu from '@/components/BaseContextMenu';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
import Views from '@/ipc-api/Views';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceExploreBarTableContext',
|
||||
@@ -59,7 +64,7 @@ export default {
|
||||
},
|
||||
props: {
|
||||
contextEvent: MouseEvent,
|
||||
selectedTable: String
|
||||
selectedTable: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -103,11 +108,11 @@ export default {
|
||||
try {
|
||||
const { status, response } = await Tables.truncateTable({
|
||||
uid: this.selectedWorkspace,
|
||||
table: this.selectedTable
|
||||
table: this.selectedTable.name
|
||||
});
|
||||
|
||||
if (status === 'success') {
|
||||
if (this.selectedTable === this.workspace.breadcrumbs.table)
|
||||
if (this.selectedTable.name === this.workspace.breadcrumbs.table)
|
||||
this.changeBreadcrumbs({ table: null });
|
||||
|
||||
this.closeContext();
|
||||
@@ -122,14 +127,26 @@ export default {
|
||||
},
|
||||
async deleteTable () {
|
||||
try {
|
||||
const { status, response } = await Tables.dropTable({
|
||||
uid: this.selectedWorkspace,
|
||||
table: this.selectedTable
|
||||
});
|
||||
let res;
|
||||
|
||||
if (this.selectedTable.type === 'table') {
|
||||
res = await Tables.dropTable({
|
||||
uid: this.selectedWorkspace,
|
||||
table: this.selectedTable.name
|
||||
});
|
||||
}
|
||||
else if (this.selectedTable.type === 'view') {
|
||||
res = await Views.dropView({
|
||||
uid: this.selectedWorkspace,
|
||||
view: this.selectedTable.name
|
||||
});
|
||||
}
|
||||
|
||||
const { status, response } = res;
|
||||
|
||||
if (status === 'success') {
|
||||
if (this.selectedTable === this.workspace.breadcrumbs.table)
|
||||
this.changeBreadcrumbs({ table: null });
|
||||
if (this.selectedTable.name === this.workspace.breadcrumbs.table || this.selectedTable.name === this.workspace.breadcrumbs.view)
|
||||
this.changeBreadcrumbs({ table: null, view: null });
|
||||
|
||||
this.closeContext();
|
||||
this.$emit('reload');
|
||||
|
416
src/renderer/components/WorkspacePropsForeignModal.vue
Normal file
416
src/renderer/components/WorkspacePropsForeignModal.vue
Normal file
@@ -0,0 +1,416 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.confirm')"
|
||||
size="medium"
|
||||
@confirm="confirmForeignsChange"
|
||||
@hide="$emit('hide')"
|
||||
>
|
||||
<template :slot="'header'">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-key-link mr-1" /> {{ $t('word.foreignKeys') }} "{{ table }}"
|
||||
</div>
|
||||
</template>
|
||||
<div :slot="'body'">
|
||||
<div class="columns col-gapless">
|
||||
<div class="column col-5">
|
||||
<div class="panel" :style="{ height: modalInnerHeight + 'px'}">
|
||||
<div class="panel-header pt-0 pl-0">
|
||||
<div class="d-flex">
|
||||
<button class="btn btn-dark btn-sm d-flex" @click="addForeign">
|
||||
<span>{{ $t('word.add') }}</span>
|
||||
<i class="mdi mdi-24px mdi-link-plus ml-1" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark btn-sm d-flex ml-2 mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
:disabled="!isChanged"
|
||||
@click.prevent="clearChanges"
|
||||
>
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="indexesPanel" class="panel-body p-0 pr-1">
|
||||
<div
|
||||
v-for="foreign in foreignProxy"
|
||||
:key="foreign._id"
|
||||
class="tile tile-centered c-hand mb-1 p-1"
|
||||
:class="{'selected-foreign': selectedForeignID === foreign._id}"
|
||||
@click="selectForeign($event, foreign._id)"
|
||||
>
|
||||
<div class="tile-icon">
|
||||
<div>
|
||||
<i class="mdi mdi-key-link mdi-24px" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile-content">
|
||||
<div class="tile-title">
|
||||
{{ foreign.constraintName }}
|
||||
</div>
|
||||
<small class="tile-subtitle text-gray d-flex">
|
||||
<i class="mdi mdi-link-variant mr-1" />
|
||||
<div class="fk-details-wrapper">
|
||||
<span v-if="foreign.table !== ''" class="fk-details">
|
||||
<i class="mdi mdi-table mr-1" />
|
||||
<span>{{ foreign.table }}.{{ foreign.field }}</span>
|
||||
</span>
|
||||
<span v-if="foreign.refTable !== ''" class="fk-details">
|
||||
<i class="mdi mdi-table mr-1" />
|
||||
<span>{{ foreign.refTable }}.{{ foreign.refField }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</small>
|
||||
</div>
|
||||
<div class="tile-action">
|
||||
<button
|
||||
class="btn btn-link remove-field p-0 mr-2"
|
||||
:title="$t('word.delete')"
|
||||
@click.prevent="removeIndex(foreign._id)"
|
||||
>
|
||||
<i class="mdi mdi-close" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column col-7 pl-2 editor-col">
|
||||
<form
|
||||
v-if="selectedForeignObj"
|
||||
:style="{ height: modalInnerHeight + 'px'}"
|
||||
class="form-horizontal"
|
||||
>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.name') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
v-model="selectedForeignObj.constraintName"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-4">
|
||||
<label class="form-label col-3">
|
||||
{{ $tc('word.field', 1) }}
|
||||
</label>
|
||||
<div class="fields-list column pt-1">
|
||||
<label
|
||||
v-for="(field, i) in fields"
|
||||
:key="`${field.name}-${i}`"
|
||||
class="form-checkbox m-0"
|
||||
@click.prevent="toggleField(field.name)"
|
||||
>
|
||||
<input type="checkbox" :checked="selectedForeignObj.field === field.name">
|
||||
<i class="form-icon" /> {{ field.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3 pt-0">
|
||||
{{ $t('message.referenceTable') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select
|
||||
v-model="selectedForeignObj.refTable"
|
||||
class="form-select"
|
||||
@change="reloadRefFields"
|
||||
>
|
||||
<option
|
||||
v-for="schemaTable in schemaTables"
|
||||
:key="schemaTable.name"
|
||||
:value="schemaTable.name"
|
||||
>
|
||||
{{ schemaTable.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-4">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('message.referenceField') }}
|
||||
</label>
|
||||
<div class="fields-list column pt-1">
|
||||
<label
|
||||
v-for="(field, i) in refFields[selectedForeignID]"
|
||||
:key="`${field.name}-${i}`"
|
||||
class="form-checkbox m-0"
|
||||
@click.prevent="toggleRefField(field.name)"
|
||||
>
|
||||
<input type="checkbox" :checked="selectedForeignObj.refField === field.name && selectedForeignObj.refTable === field.table">
|
||||
<i class="form-icon" /> {{ field.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('message.onUpdate') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="selectedForeignObj.onUpdate" class="form-select">
|
||||
<option
|
||||
v-for="action in foreignActions"
|
||||
:key="action"
|
||||
:value="action"
|
||||
>
|
||||
{{ action }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('message.onDelete') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="selectedForeignObj.onDelete" class="form-select">
|
||||
<option
|
||||
v-for="action in foreignActions"
|
||||
:key="action"
|
||||
:value="action"
|
||||
>
|
||||
{{ action }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div v-if="!foreignProxy.length" class="empty">
|
||||
<div class="empty-icon">
|
||||
<i class="mdi mdi-key-link mdi-48px" />
|
||||
</div>
|
||||
<p class="empty-title h5">
|
||||
{{ $t('message.thereAreNoForeign') }}
|
||||
</p>
|
||||
<div class="empty-action">
|
||||
<button class="btn btn-primary" @click="addForeign">
|
||||
{{ $t('message.createNewForeign') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsForeignModal',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
localKeyUsage: Array,
|
||||
connection: Object,
|
||||
table: String,
|
||||
schema: String,
|
||||
schemaTables: Array,
|
||||
fields: Array,
|
||||
workspace: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
foreignProxy: [],
|
||||
isOptionsChanging: false,
|
||||
selectedForeignID: '',
|
||||
modalInnerHeight: 400,
|
||||
refFields: {},
|
||||
foreignActions: [
|
||||
'RESTRICT',
|
||||
'CASCADE',
|
||||
'SET NULL',
|
||||
'NO ACTION'
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selectedForeignObj () {
|
||||
return this.foreignProxy.find(foreign => foreign._id === this.selectedForeignID);
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.localKeyUsage) !== JSON.stringify(this.foreignProxy);
|
||||
},
|
||||
hasPrimary () {
|
||||
return this.foreignProxy.some(foreign => foreign.type === 'PRIMARY');
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.foreignProxy = JSON.parse(JSON.stringify(this.localKeyUsage));
|
||||
|
||||
if (this.foreignProxy.length)
|
||||
this.resetSelectedID();
|
||||
|
||||
if (this.selectedForeignObj)
|
||||
this.getRefFields();
|
||||
|
||||
this.getModalInnerHeight();
|
||||
window.addEventListener('resize', this.getModalInnerHeight);
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('resize', this.getModalInnerHeight);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification'
|
||||
}),
|
||||
confirmForeignsChange () {
|
||||
this.$emit('foreigns-update', this.foreignProxy);
|
||||
},
|
||||
selectForeign (event, id) {
|
||||
if (this.selectedForeignID !== id && !event.target.classList.contains('remove-field'))
|
||||
this.selectedForeignID = id;
|
||||
},
|
||||
getModalInnerHeight () {
|
||||
const modalBody = document.querySelector('.modal-body');
|
||||
if (modalBody)
|
||||
this.modalInnerHeight = modalBody.clientHeight - (parseFloat(getComputedStyle(modalBody).paddingTop) + parseFloat(getComputedStyle(modalBody).paddingBottom));
|
||||
},
|
||||
addForeign () {
|
||||
this.foreignProxy = [...this.foreignProxy, {
|
||||
_id: uidGen(),
|
||||
constraintName: `FK_${this.foreignProxy.length + 1}`,
|
||||
refSchema: this.schema,
|
||||
table: this.table,
|
||||
refTable: '',
|
||||
field: '',
|
||||
refField: '',
|
||||
onUpdate: this.foreignActions[0],
|
||||
onDelete: this.foreignActions[0]
|
||||
}];
|
||||
|
||||
if (this.foreignProxy.length === 1)
|
||||
this.resetSelectedID();
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.indexesPanel.scrollTop = this.$refs.indexesPanel.scrollHeight + 60;
|
||||
}, 20);
|
||||
},
|
||||
removeIndex (id) {
|
||||
this.foreignProxy = this.foreignProxy.filter(foreign => foreign._id !== id);
|
||||
|
||||
if (this.selectedForeignID === id && this.foreignProxy.length)
|
||||
this.resetSelectedID();
|
||||
},
|
||||
clearChanges () {
|
||||
this.foreignProxy = JSON.parse(JSON.stringify(this.localKeyUsage));
|
||||
if (!this.foreignProxy.some(foreign => foreign._id === this.selectedForeignID))
|
||||
this.resetSelectedID();
|
||||
},
|
||||
toggleField (field) {
|
||||
this.foreignProxy = this.foreignProxy.map(foreign => {
|
||||
if (foreign._id === this.selectedForeignID)
|
||||
foreign.field = field;
|
||||
|
||||
return foreign;
|
||||
});
|
||||
},
|
||||
toggleRefField (field) {
|
||||
this.foreignProxy = this.foreignProxy.map(foreign => {
|
||||
if (foreign._id === this.selectedForeignID)
|
||||
foreign.refField = field;
|
||||
|
||||
return foreign;
|
||||
});
|
||||
},
|
||||
resetSelectedID () {
|
||||
this.selectedForeignID = this.foreignProxy.length ? this.foreignProxy[0]._id : '';
|
||||
},
|
||||
async getRefFields () {
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.selectedForeignObj.refSchema,
|
||||
table: this.selectedForeignObj.refTable
|
||||
};
|
||||
|
||||
try { // Field data
|
||||
const { status, response } = await Tables.getTableColumns(params);
|
||||
if (status === 'success') {
|
||||
this.refFields = {
|
||||
...this.refFields,
|
||||
[this.selectedForeignID]: response
|
||||
};
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
},
|
||||
reloadRefFields () {
|
||||
this.selectedForeignObj.refField = '';
|
||||
this.getRefFields();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tile {
|
||||
border-radius: 2px;
|
||||
opacity: 0.5;
|
||||
transition: background 0.2s;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
.tile-action {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $bg-color-light;
|
||||
|
||||
.tile-action {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected-foreign {
|
||||
background: $bg-color-light;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-col {
|
||||
border-left: 2px solid $bg-color-light;
|
||||
}
|
||||
|
||||
.fields-list {
|
||||
max-height: 80px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.remove-field .mdi {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.fk-details-wrapper {
|
||||
max-width: calc(100% - 1rem);
|
||||
|
||||
.fk-details {
|
||||
display: flex;
|
||||
line-height: 1;
|
||||
align-items: baseline;
|
||||
|
||||
> span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
180
src/renderer/components/WorkspacePropsFunctionOptionsModal.vue
Normal file
180
src/renderer/components/WorkspacePropsFunctionOptionsModal.vue
Normal file
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.confirm')"
|
||||
size="400"
|
||||
@confirm="confirmOptionsChange"
|
||||
@hide="$emit('hide')"
|
||||
>
|
||||
<template :slot="'header'">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-cogs mr-1" /> {{ $t('word.options') }} "{{ localOptions.name }}"
|
||||
</div>
|
||||
</template>
|
||||
<div :slot="'body'">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.name') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="optionsProxy.name"
|
||||
class="form-input"
|
||||
:class="{'is-error': !isTableNameValid}"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.definer') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select
|
||||
v-if="workspace.users.length"
|
||||
v-model="optionsProxy.definer"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="user in workspace.users"
|
||||
:key="`${user.name}@${user.host}`"
|
||||
:value="`\`${user.name}\`@\`${user.host}\``"
|
||||
>
|
||||
{{ user.name }}@{{ user.host }}
|
||||
</option>
|
||||
</select>
|
||||
<select v-if="!workspace.users.length" class="form-select">
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.returns') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<div class="input-group">
|
||||
<select
|
||||
v-model="optionsProxy.returns"
|
||||
class="form-select text-uppercase"
|
||||
style="width: 0;"
|
||||
>
|
||||
<optgroup
|
||||
v-for="group in workspace.dataTypes"
|
||||
:key="group.group"
|
||||
:label="group.group"
|
||||
>
|
||||
<option
|
||||
v-for="type in group.types"
|
||||
:key="type.name"
|
||||
:selected="optionsProxy.returns === type.name"
|
||||
:value="type.name"
|
||||
>
|
||||
{{ type.name }}
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<input
|
||||
v-model="optionsProxy.returnsLength"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="0"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.comment') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
v-model="optionsProxy.comment"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('message.sqlSecurity') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="optionsProxy.security" class="form-select">
|
||||
<option>DEFINER</option>
|
||||
<option>INVOKER</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('message.dataAccess') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="optionsProxy.dataAccess" class="form-select">
|
||||
<option>CONTAINS SQL</option>
|
||||
<option>NO SQL</option>
|
||||
<option>READS SQL DATA</option>
|
||||
<option>MODIFIES SQL DATA</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4" />
|
||||
<div class="column">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="optionsProxy.deterministic" type="checkbox"><i class="form-icon" /> {{ $t('word.deterministic') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsFunctionOptionsModal',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
localOptions: Object,
|
||||
workspace: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
optionsProxy: {},
|
||||
isOptionsChanging: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isTableNameValid () {
|
||||
return this.optionsProxy.name !== '';
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.optionsProxy = JSON.parse(JSON.stringify(this.localOptions));
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
methods: {
|
||||
confirmOptionsChange () {
|
||||
if (!this.isTableNameValid)
|
||||
this.optionsProxy.name = this.localOptions.name;
|
||||
|
||||
this.$emit('options-update', this.optionsProxy);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
270
src/renderer/components/WorkspacePropsFunctionParamsModal.vue
Normal file
270
src/renderer/components/WorkspacePropsFunctionParamsModal.vue
Normal file
@@ -0,0 +1,270 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.confirm')"
|
||||
size="medium"
|
||||
@confirm="confirmIndexesChange"
|
||||
@hide="$emit('hide')"
|
||||
>
|
||||
<template :slot="'header'">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" /> {{ $t('word.parameters') }} "{{ func }}"
|
||||
</div>
|
||||
</template>
|
||||
<div :slot="'body'">
|
||||
<div class="columns col-gapless">
|
||||
<div class="column col-5">
|
||||
<div class="panel" :style="{ height: modalInnerHeight + 'px'}">
|
||||
<div class="panel-header pt-0 pl-0">
|
||||
<div class="d-flex">
|
||||
<button class="btn btn-dark btn-sm d-flex" @click="addParameter">
|
||||
<span>{{ $t('word.add') }}</span>
|
||||
<i class="mdi mdi-24px mdi-plus ml-1" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark btn-sm d-flex ml-2 mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
:disabled="!isChanged"
|
||||
@click.prevent="clearChanges"
|
||||
>
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="parametersPanel" class="panel-body p-0 pr-1">
|
||||
<div
|
||||
v-for="param in parametersProxy"
|
||||
:key="param.name"
|
||||
class="tile tile-centered c-hand mb-1 p-1"
|
||||
:class="{'selected-param': selectedParam === param.name}"
|
||||
@click="selectParameter($event, param.name)"
|
||||
>
|
||||
<div class="tile-icon">
|
||||
<div>
|
||||
<i class="mdi mdi-hexagon mdi-24px" :class="`type-${param.type.toLowerCase()}`" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile-content">
|
||||
<div class="tile-title">
|
||||
{{ param.name }}
|
||||
</div>
|
||||
<small class="tile-subtitle text-gray">{{ param.type }}{{ param.length ? `(${param.length})` : '' }}</small>
|
||||
</div>
|
||||
<div class="tile-action">
|
||||
<button
|
||||
class="btn btn-link remove-field p-0 mr-2"
|
||||
:title="$t('word.delete')"
|
||||
@click.prevent="removeParameter(param.name)"
|
||||
>
|
||||
<i class="mdi mdi-close" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column col-7 pl-2 editor-col">
|
||||
<form
|
||||
v-if="selectedParamObj"
|
||||
:style="{ height: modalInnerHeight + 'px'}"
|
||||
class="form-horizontal"
|
||||
>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.name') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
v-model="selectedParamObj.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.type') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="selectedParamObj.type" class="form-select text-uppercase">
|
||||
<optgroup
|
||||
v-for="group in workspace.dataTypes"
|
||||
:key="group.group"
|
||||
:label="group.group"
|
||||
>
|
||||
<option
|
||||
v-for="type in group.types"
|
||||
:key="type.name"
|
||||
:selected="selectedParamObj.type.toUpperCase() === type.name"
|
||||
:value="type.name"
|
||||
>
|
||||
{{ type.name }}
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.length') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
v-model="selectedParamObj.length"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="0"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div v-if="!parametersProxy.length" class="empty">
|
||||
<div class="empty-icon">
|
||||
<i class="mdi mdi-dots-horizontal mdi-48px" />
|
||||
</div>
|
||||
<p class="empty-title h5">
|
||||
{{ $t('message.thereAreNoParameters') }}
|
||||
</p>
|
||||
<div class="empty-action">
|
||||
<button class="btn btn-primary" @click="addParameter">
|
||||
{{ $t('message.createNewParameter') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsRoutineParamsModal',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
localParameters: Array,
|
||||
func: String,
|
||||
workspace: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
parametersProxy: [],
|
||||
isOptionsChanging: false,
|
||||
selectedParam: '',
|
||||
modalInnerHeight: 400,
|
||||
i: 1
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selectedParamObj () {
|
||||
return this.parametersProxy.find(param => param.name === this.selectedParam);
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.localParameters) !== JSON.stringify(this.parametersProxy);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.parametersProxy = JSON.parse(JSON.stringify(this.localParameters));
|
||||
this.i = this.parametersProxy.length + 1;
|
||||
|
||||
if (this.parametersProxy.length)
|
||||
this.resetSelectedID();
|
||||
|
||||
this.getModalInnerHeight();
|
||||
window.addEventListener('resize', this.getModalInnerHeight);
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('resize', this.getModalInnerHeight);
|
||||
},
|
||||
methods: {
|
||||
confirmIndexesChange () {
|
||||
this.$emit('parameters-update', this.parametersProxy);
|
||||
},
|
||||
selectParameter (event, name) {
|
||||
if (this.selectedParam !== name && !event.target.classList.contains('remove-field'))
|
||||
this.selectedParam = name;
|
||||
},
|
||||
getModalInnerHeight () {
|
||||
const modalBody = document.querySelector('.modal-body');
|
||||
if (modalBody)
|
||||
this.modalInnerHeight = modalBody.clientHeight - (parseFloat(getComputedStyle(modalBody).paddingTop) + parseFloat(getComputedStyle(modalBody).paddingBottom));
|
||||
},
|
||||
addParameter () {
|
||||
this.parametersProxy = [...this.parametersProxy, {
|
||||
name: `Param${this.i++}`,
|
||||
type: 'INT',
|
||||
context: 'IN',
|
||||
length: 10
|
||||
}];
|
||||
|
||||
if (this.parametersProxy.length === 1)
|
||||
this.resetSelectedID();
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.parametersPanel.scrollTop = this.$refs.parametersPanel.scrollHeight + 60;
|
||||
}, 20);
|
||||
},
|
||||
removeParameter (name) {
|
||||
this.parametersProxy = this.parametersProxy.filter(param => param.name !== name);
|
||||
|
||||
if (this.selectedParam === name && this.parametersProxy.length)
|
||||
this.resetSelectedID();
|
||||
},
|
||||
clearChanges () {
|
||||
this.parametersProxy = JSON.parse(JSON.stringify(this.localParameters));
|
||||
this.i = this.parametersProxy.length + 1;
|
||||
|
||||
if (!this.parametersProxy.some(param => param.name === this.selectedParam))
|
||||
this.resetSelectedID();
|
||||
},
|
||||
resetSelectedID () {
|
||||
this.selectedParam = this.parametersProxy.length ? this.parametersProxy[0].name : '';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tile {
|
||||
border-radius: 2px;
|
||||
opacity: 0.5;
|
||||
transition: background 0.2s;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
.tile-action {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $bg-color-light;
|
||||
|
||||
.tile-action {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected-param {
|
||||
background: $bg-color-light;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-col {
|
||||
border-left: 2px solid $bg-color-light;
|
||||
}
|
||||
|
||||
.fields-list {
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.remove-field .mdi {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
@@ -65,37 +65,45 @@
|
||||
</div>
|
||||
|
||||
<div class="column col-7 pl-2 editor-col">
|
||||
<form v-if="selectedIndexObj" :style="{ height: modalInnerHeight + 'px'}">
|
||||
<form
|
||||
v-if="selectedIndexObj"
|
||||
:style="{ height: modalInnerHeight + 'px'}"
|
||||
class="form-horizontal"
|
||||
>
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.name') }}
|
||||
</label>
|
||||
<input
|
||||
v-model="selectedIndexObj.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
<div class="column">
|
||||
<input
|
||||
v-model="selectedIndexObj.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.type') }}
|
||||
</label>
|
||||
<select v-model="selectedIndexObj.type" class="form-select">
|
||||
<option
|
||||
v-for="index in indexTypes"
|
||||
:key="index"
|
||||
:value="index"
|
||||
:disabled="index === 'PRIMARY' && hasPrimary"
|
||||
>
|
||||
{{ index }}
|
||||
</option>
|
||||
</select>
|
||||
<div class="column">
|
||||
<select v-model="selectedIndexObj.type" class="form-select">
|
||||
<option
|
||||
v-for="index in indexTypes"
|
||||
:key="index"
|
||||
:value="index"
|
||||
:disabled="index === 'PRIMARY' && hasPrimary"
|
||||
>
|
||||
{{ index }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
<label class="form-label col-3">
|
||||
{{ $tc('word.field', fields.length) }}
|
||||
</label>
|
||||
<div class="fields-list">
|
||||
<div class="fields-list column pt-1">
|
||||
<label
|
||||
v-for="(field, i) in fields"
|
||||
:key="`${field.name}-${i}`"
|
||||
@@ -108,6 +116,19 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div v-if="!indexesProxy.length" class="empty">
|
||||
<div class="empty-icon">
|
||||
<i class="mdi mdi-key-outline mdi-48px" />
|
||||
</div>
|
||||
<p class="empty-title h5">
|
||||
{{ $t('message.thereAreNoIndexes') }}
|
||||
</p>
|
||||
<div class="empty-action">
|
||||
<button class="btn btn-primary" @click="addIndex">
|
||||
{{ $t('message.createNewIndex') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -216,7 +237,7 @@ export default {
|
||||
});
|
||||
},
|
||||
resetSelectedID () {
|
||||
this.selectedIndexID = this.indexesProxy[0]._id;
|
||||
this.selectedIndexID = this.indexesProxy.length ? this.indexesProxy[0]._id : '';
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -253,7 +274,7 @@ export default {
|
||||
}
|
||||
|
||||
.fields-list {
|
||||
max-height: 200px;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
@@ -18,6 +18,7 @@
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="optionsProxy.name"
|
||||
class="form-input"
|
||||
:class="{'is-error': !isTableNameValid}"
|
||||
@@ -112,6 +113,10 @@ export default {
|
||||
},
|
||||
created () {
|
||||
this.optionsProxy = JSON.parse(JSON.stringify(this.localOptions));
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
methods: {
|
||||
confirmOptionsChange () {
|
||||
|
145
src/renderer/components/WorkspacePropsRoutineOptionsModal.vue
Normal file
145
src/renderer/components/WorkspacePropsRoutineOptionsModal.vue
Normal file
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.confirm')"
|
||||
size="400"
|
||||
@confirm="confirmOptionsChange"
|
||||
@hide="$emit('hide')"
|
||||
>
|
||||
<template :slot="'header'">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-cogs mr-1" /> {{ $t('word.options') }} "{{ localOptions.name }}"
|
||||
</div>
|
||||
</template>
|
||||
<div :slot="'body'">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.name') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="optionsProxy.name"
|
||||
class="form-input"
|
||||
:class="{'is-error': !isTableNameValid}"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.definer') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select
|
||||
v-if="workspace.users.length"
|
||||
v-model="optionsProxy.definer"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="user in workspace.users"
|
||||
:key="`${user.name}@${user.host}`"
|
||||
:value="`\`${user.name}\`@\`${user.host}\``"
|
||||
>
|
||||
{{ user.name }}@{{ user.host }}
|
||||
</option>
|
||||
</select>
|
||||
<select v-if="!workspace.users.length" class="form-select">
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.comment') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
v-model="optionsProxy.comment"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('message.sqlSecurity') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="optionsProxy.security" class="form-select">
|
||||
<option>DEFINER</option>
|
||||
<option>INVOKER</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('message.dataAccess') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="optionsProxy.dataAccess" class="form-select">
|
||||
<option>CONTAINS SQL</option>
|
||||
<option>NO SQL</option>
|
||||
<option>READS SQL DATA</option>
|
||||
<option>MODIFIES SQL DATA</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4" />
|
||||
<div class="column">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="optionsProxy.deterministic" type="checkbox"><i class="form-icon" /> {{ $t('word.deterministic') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsRoutineOptionsModal',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
localOptions: Object,
|
||||
workspace: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
optionsProxy: {},
|
||||
isOptionsChanging: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isTableNameValid () {
|
||||
return this.optionsProxy.name !== '';
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.optionsProxy = JSON.parse(JSON.stringify(this.localOptions));
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
methods: {
|
||||
confirmOptionsChange () {
|
||||
if (!this.isTableNameValid)
|
||||
this.optionsProxy.name = this.localOptions.name;
|
||||
|
||||
this.$emit('options-update', this.optionsProxy);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
301
src/renderer/components/WorkspacePropsRoutineParamsModal.vue
Normal file
301
src/renderer/components/WorkspacePropsRoutineParamsModal.vue
Normal file
@@ -0,0 +1,301 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.confirm')"
|
||||
size="medium"
|
||||
@confirm="confirmIndexesChange"
|
||||
@hide="$emit('hide')"
|
||||
>
|
||||
<template :slot="'header'">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" /> {{ $t('word.parameters') }} "{{ routine }}"
|
||||
</div>
|
||||
</template>
|
||||
<div :slot="'body'">
|
||||
<div class="columns col-gapless">
|
||||
<div class="column col-5">
|
||||
<div class="panel" :style="{ height: modalInnerHeight + 'px'}">
|
||||
<div class="panel-header pt-0 pl-0">
|
||||
<div class="d-flex">
|
||||
<button class="btn btn-dark btn-sm d-flex" @click="addParameter">
|
||||
<span>{{ $t('word.add') }}</span>
|
||||
<i class="mdi mdi-24px mdi-plus ml-1" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark btn-sm d-flex ml-2 mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
:disabled="!isChanged"
|
||||
@click.prevent="clearChanges"
|
||||
>
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="parametersPanel" class="panel-body p-0 pr-1">
|
||||
<div
|
||||
v-for="param in parametersProxy"
|
||||
:key="param.name"
|
||||
class="tile tile-centered c-hand mb-1 p-1"
|
||||
:class="{'selected-param': selectedParam === param.name}"
|
||||
@click="selectParameter($event, param.name)"
|
||||
>
|
||||
<div class="tile-icon">
|
||||
<div>
|
||||
<i class="mdi mdi-hexagon mdi-24px" :class="`type-${param.type.toLowerCase()}`" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile-content">
|
||||
<div class="tile-title">
|
||||
{{ param.name }}
|
||||
</div>
|
||||
<small class="tile-subtitle text-gray">{{ param.type }}{{ param.length ? `(${param.length})` : '' }} · {{ param.context }}</small>
|
||||
</div>
|
||||
<div class="tile-action">
|
||||
<button
|
||||
class="btn btn-link remove-field p-0 mr-2"
|
||||
:title="$t('word.delete')"
|
||||
@click.prevent="removeParameter(param.name)"
|
||||
>
|
||||
<i class="mdi mdi-close" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column col-7 pl-2 editor-col">
|
||||
<form
|
||||
v-if="selectedParamObj"
|
||||
:style="{ height: modalInnerHeight + 'px'}"
|
||||
class="form-horizontal"
|
||||
>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.name') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
v-model="selectedParamObj.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.type') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="selectedParamObj.type" class="form-select text-uppercase">
|
||||
<optgroup
|
||||
v-for="group in workspace.dataTypes"
|
||||
:key="group.group"
|
||||
:label="group.group"
|
||||
>
|
||||
<option
|
||||
v-for="type in group.types"
|
||||
:key="type.name"
|
||||
:selected="selectedParamObj.type.toUpperCase() === type.name"
|
||||
:value="type.name"
|
||||
>
|
||||
{{ type.name }}
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.length') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
v-model="selectedParamObj.length"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="0"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.context') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="selectedParamObj.context"
|
||||
type="radio"
|
||||
name="context"
|
||||
value="IN"
|
||||
> <i class="form-icon" /> IN
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="selectedParamObj.context"
|
||||
type="radio"
|
||||
value="OUT"
|
||||
name="context"
|
||||
> <i class="form-icon" /> OUT
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="selectedParamObj.context"
|
||||
type="radio"
|
||||
value="INOUT"
|
||||
name="context"
|
||||
> <i class="form-icon" /> INOUT
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div v-if="!parametersProxy.length" class="empty">
|
||||
<div class="empty-icon">
|
||||
<i class="mdi mdi-dots-horizontal mdi-48px" />
|
||||
</div>
|
||||
<p class="empty-title h5">
|
||||
{{ $t('message.thereAreNoParameters') }}
|
||||
</p>
|
||||
<div class="empty-action">
|
||||
<button class="btn btn-primary" @click="addParameter">
|
||||
{{ $t('message.createNewParameter') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsRoutineParamsModal',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
localParameters: Array,
|
||||
routine: String,
|
||||
workspace: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
parametersProxy: [],
|
||||
isOptionsChanging: false,
|
||||
selectedParam: '',
|
||||
modalInnerHeight: 400,
|
||||
i: 1
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selectedParamObj () {
|
||||
return this.parametersProxy.find(param => param.name === this.selectedParam);
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.localParameters) !== JSON.stringify(this.parametersProxy);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.parametersProxy = JSON.parse(JSON.stringify(this.localParameters));
|
||||
this.i = this.parametersProxy.length + 1;
|
||||
|
||||
if (this.parametersProxy.length)
|
||||
this.resetSelectedID();
|
||||
|
||||
this.getModalInnerHeight();
|
||||
window.addEventListener('resize', this.getModalInnerHeight);
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('resize', this.getModalInnerHeight);
|
||||
},
|
||||
methods: {
|
||||
confirmIndexesChange () {
|
||||
this.$emit('parameters-update', this.parametersProxy);
|
||||
},
|
||||
selectParameter (event, name) {
|
||||
if (this.selectedParam !== name && !event.target.classList.contains('remove-field'))
|
||||
this.selectedParam = name;
|
||||
},
|
||||
getModalInnerHeight () {
|
||||
const modalBody = document.querySelector('.modal-body');
|
||||
if (modalBody)
|
||||
this.modalInnerHeight = modalBody.clientHeight - (parseFloat(getComputedStyle(modalBody).paddingTop) + parseFloat(getComputedStyle(modalBody).paddingBottom));
|
||||
},
|
||||
addParameter () {
|
||||
this.parametersProxy = [...this.parametersProxy, {
|
||||
name: `Param${this.i++}`,
|
||||
type: 'INT',
|
||||
context: 'IN',
|
||||
length: 10
|
||||
}];
|
||||
|
||||
if (this.parametersProxy.length === 1)
|
||||
this.resetSelectedID();
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.parametersPanel.scrollTop = this.$refs.parametersPanel.scrollHeight + 60;
|
||||
}, 20);
|
||||
},
|
||||
removeParameter (name) {
|
||||
this.parametersProxy = this.parametersProxy.filter(param => param.name !== name);
|
||||
|
||||
if (this.selectedParam === name && this.parametersProxy.length)
|
||||
this.resetSelectedID();
|
||||
},
|
||||
clearChanges () {
|
||||
this.parametersProxy = JSON.parse(JSON.stringify(this.localParameters));
|
||||
this.i = this.parametersProxy.length + 1;
|
||||
|
||||
if (!this.parametersProxy.some(param => param.name === this.selectedParam))
|
||||
this.resetSelectedID();
|
||||
},
|
||||
resetSelectedID () {
|
||||
this.selectedParam = this.parametersProxy.length ? this.parametersProxy[0].name : '';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tile {
|
||||
border-radius: 2px;
|
||||
opacity: 0.5;
|
||||
transition: background 0.2s;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
.tile-action {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $bg-color-light;
|
||||
|
||||
.tile-action {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected-param {
|
||||
background: $bg-color-light;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-col {
|
||||
border-left: 2px solid $bg-color-light;
|
||||
}
|
||||
|
||||
.fields-list {
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.remove-field .mdi {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
192
src/renderer/components/WorkspacePropsSchedulerTimingModal.vue
Normal file
192
src/renderer/components/WorkspacePropsSchedulerTimingModal.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.confirm')"
|
||||
size="400"
|
||||
@confirm="confirmOptionsChange"
|
||||
@hide="$emit('hide')"
|
||||
>
|
||||
<template :slot="'header'">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-timer mr-1" /> {{ $t('word.timing') }} "{{ localOptions.name }}"
|
||||
</div>
|
||||
</template>
|
||||
<div :slot="'body'">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.execution') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select
|
||||
ref="firstInput"
|
||||
v-model="optionsProxy.execution"
|
||||
class="form-select"
|
||||
>
|
||||
<option>EVERY</option>
|
||||
<option>ONCE</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="optionsProxy.execution === 'EVERY'">
|
||||
<div class="form-group">
|
||||
<div class="col-4" />
|
||||
<div class="column">
|
||||
<div class="input-group">
|
||||
<input
|
||||
v-model="optionsProxy.every[0]"
|
||||
class="form-input"
|
||||
type="text"
|
||||
@keypress="isNumberOrMinus($event)"
|
||||
>
|
||||
<select
|
||||
v-model="optionsProxy.every[1]"
|
||||
class="form-select text-uppercase"
|
||||
style="width: 0;"
|
||||
>
|
||||
<option>YEAR</option>
|
||||
<option>QUARTER</option>
|
||||
<option>MONTH</option>
|
||||
<option>WEEK</option>
|
||||
<option>DAY</option>
|
||||
<option>HOUR</option>
|
||||
<option>MINUTE</option>
|
||||
<option>SECOND</option>
|
||||
<option>YEAR_MONTH</option>
|
||||
<option>DAY_HOUR</option>
|
||||
<option>DAY_MINUTE</option>
|
||||
<option>DAY_SECOND</option>
|
||||
<option>HOUR_MINUTE</option>
|
||||
<option>HOUR_SECOND</option>
|
||||
<option>MINUTE_SECOND</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.starts') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<div class="input-group">
|
||||
<label class="form-checkbox">
|
||||
<input v-model="hasStart" type="checkbox"><i class="form-icon" />
|
||||
</label>
|
||||
<input
|
||||
v-model="optionsProxy.starts"
|
||||
v-mask="'####-##-## ##:##:##'"
|
||||
type="text"
|
||||
class="form-input"
|
||||
>
|
||||
<span class="input-group-addon p-vcentered">
|
||||
<i class="form-icon mdi mdi-calendar" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.ends') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<div class="input-group">
|
||||
<label class="form-checkbox">
|
||||
<input v-model="hasEnd" type="checkbox"><i class="form-icon" />
|
||||
</label>
|
||||
<input
|
||||
v-model="optionsProxy.ends"
|
||||
v-mask="'####-##-## ##:##:##'"
|
||||
type="text"
|
||||
class="form-input"
|
||||
>
|
||||
<span class="input-group-addon p-vcentered">
|
||||
<i class="form-icon mdi mdi-calendar" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="form-group">
|
||||
<div class="col-4" />
|
||||
<div class="column">
|
||||
<div class="input-group">
|
||||
<input
|
||||
v-model="optionsProxy.at"
|
||||
v-mask="'####-##-## ##:##:##'"
|
||||
type="text"
|
||||
class="form-input"
|
||||
>
|
||||
<span class="input-group-addon p-vcentered">
|
||||
<i class="form-icon mdi mdi-calendar" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4" />
|
||||
<div class="column">
|
||||
<label class="form-checkbox form-inline mt-2">
|
||||
<input v-model="optionsProxy.preserve" type="checkbox"><i class="form-icon" /> {{ $t('message.preserveOnCompletion') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
import { mask } from 'vue-the-mask';
|
||||
import moment from 'moment';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsSchedulerTimingModal',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
directives: {
|
||||
mask
|
||||
},
|
||||
props: {
|
||||
localOptions: Object,
|
||||
workspace: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
optionsProxy: {},
|
||||
isOptionsChanging: false,
|
||||
hasStart: false,
|
||||
hasEnd: false
|
||||
};
|
||||
},
|
||||
created () {
|
||||
this.optionsProxy = JSON.parse(JSON.stringify(this.localOptions));
|
||||
|
||||
this.hasStart = !!this.optionsProxy.starts;
|
||||
this.hasEnd = !!this.optionsProxy.ends;
|
||||
|
||||
if (!this.optionsProxy.at) this.optionsProxy.at = moment().format('YYYY-MM-DD HH:mm:ss');
|
||||
if (!this.optionsProxy.starts) this.optionsProxy.starts = moment().format('YYYY-MM-DD HH:mm:ss');
|
||||
if (!this.optionsProxy.ends) this.optionsProxy.ends = moment().format('YYYY-MM-DD HH:mm:ss');
|
||||
if (!this.optionsProxy.every.length) this.optionsProxy.every = ['1', 'DAY'];
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
methods: {
|
||||
confirmOptionsChange () {
|
||||
if (!this.hasStart) this.optionsProxy.starts = '';
|
||||
if (!this.hasEnd) this.optionsProxy.ends = '';
|
||||
|
||||
this.$emit('options-update', this.optionsProxy);
|
||||
},
|
||||
isNumberOrMinus (event) {
|
||||
if (!/\d/.test(event.key) && event.key !== '-')
|
||||
return event.preventDefault();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -40,7 +40,7 @@
|
||||
<span>{{ $t('word.indexes') }}</span>
|
||||
<i class="mdi mdi-24px mdi-key mdi-rotate-45 ml-1" />
|
||||
</button>
|
||||
<button class="btn btn-dark btn-sm d-none">
|
||||
<button class="btn btn-dark btn-sm" @click="showForeignModal">
|
||||
<span>{{ $t('word.foreignKeys') }}</span>
|
||||
<i class="mdi mdi-24px mdi-key-link ml-1" />
|
||||
</button>
|
||||
@@ -51,12 +51,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workspace-query-results column col-12">
|
||||
<div class="workspace-query-results column col-12 p-relative">
|
||||
<BaseLoader v-if="isLoading" />
|
||||
<WorkspacePropsTable
|
||||
v-if="localFields"
|
||||
ref="indexTable"
|
||||
:fields="localFields"
|
||||
:indexes="localIndexes"
|
||||
:foreigns="localKeyUsage"
|
||||
:tab-uid="tabUid"
|
||||
:conn-uid="connection.uid"
|
||||
:index-types="workspace.indexTypes"
|
||||
@@ -86,6 +88,18 @@
|
||||
@hide="hideIndexesModal"
|
||||
@indexes-update="indexesUpdate"
|
||||
/>
|
||||
<WorkspacePropsForeignModal
|
||||
v-if="isForeignModal"
|
||||
:local-key-usage="localKeyUsage"
|
||||
:connection="connection"
|
||||
:table="table"
|
||||
:schema="schema"
|
||||
:schema-tables="schemaTables"
|
||||
:fields="localFields"
|
||||
:workspace="workspace"
|
||||
@hide="hideForeignModal"
|
||||
@foreigns-update="foreignsUpdate"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -93,16 +107,20 @@
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import WorkspacePropsTable from '@/components/WorkspacePropsTable';
|
||||
import WorkspacePropsOptionsModal from '@/components/WorkspacePropsOptionsModal';
|
||||
import WorkspacePropsIndexesModal from '@/components/WorkspacePropsIndexesModal';
|
||||
import WorkspacePropsForeignModal from '@/components/WorkspacePropsForeignModal';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsTab',
|
||||
components: {
|
||||
BaseLoader,
|
||||
WorkspacePropsTable,
|
||||
WorkspacePropsOptionsModal,
|
||||
WorkspacePropsIndexesModal
|
||||
WorkspacePropsIndexesModal,
|
||||
WorkspacePropsForeignModal
|
||||
},
|
||||
props: {
|
||||
connection: Object,
|
||||
@@ -111,10 +129,11 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
tabUid: 'prop',
|
||||
isQuering: false,
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
isOptionsModal: false,
|
||||
isIndexesModal: false,
|
||||
isForeignModal: false,
|
||||
isOptionsChanging: false,
|
||||
originalFields: [],
|
||||
localFields: [],
|
||||
@@ -148,6 +167,13 @@ export default {
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
},
|
||||
schemaTables () {
|
||||
const schemaTables = this.workspace.structure
|
||||
.filter(schema => schema.name === this.schema)
|
||||
.map(schema => schema.tables);
|
||||
|
||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.originalFields) !== JSON.stringify(this.localFields) ||
|
||||
JSON.stringify(this.originalKeyUsage) !== JSON.stringify(this.localKeyUsage) ||
|
||||
@@ -177,12 +203,15 @@ export default {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
refreshStructure: 'workspaces/refreshStructure',
|
||||
setUnsavedChanges: 'workspaces/setUnsavedChanges'
|
||||
setUnsavedChanges: 'workspaces/setUnsavedChanges',
|
||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
|
||||
}),
|
||||
async getFieldsData () {
|
||||
if (!this.table) return;
|
||||
|
||||
this.localFields = [];
|
||||
this.newFieldsCounter = 0;
|
||||
this.isQuering = true;
|
||||
this.isLoading = true;
|
||||
this.localOptions = JSON.parse(JSON.stringify(this.tableOptions));
|
||||
|
||||
const params = {
|
||||
@@ -242,8 +271,13 @@ export default {
|
||||
const { status, response } = await Tables.getKeyUsage(params);
|
||||
|
||||
if (status === 'success') {
|
||||
this.originalKeyUsage = response;
|
||||
this.localKeyUsage = JSON.parse(JSON.stringify(response));
|
||||
this.originalKeyUsage = response.map(foreign => {
|
||||
return {
|
||||
_id: uidGen(),
|
||||
...foreign
|
||||
};
|
||||
});
|
||||
this.localKeyUsage = JSON.parse(JSON.stringify(this.originalKeyUsage));
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
@@ -252,7 +286,7 @@ export default {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isQuering = false;
|
||||
this.isLoading = false;
|
||||
},
|
||||
async saveChanges () {
|
||||
if (this.isSaving) return;
|
||||
@@ -321,6 +355,35 @@ export default {
|
||||
// Index Deletions
|
||||
indexChanges.deletions = this.originalIndexes.filter(index => !localIndexIDs.includes(index._id));
|
||||
|
||||
// FOREIGN KEYS
|
||||
const foreignChanges = {
|
||||
additions: [],
|
||||
changes: [],
|
||||
deletions: []
|
||||
};
|
||||
const originalForeignIDs = this.originalKeyUsage.reduce((acc, curr) => [...acc, curr._id], []);
|
||||
const localForeignIDs = this.localKeyUsage.reduce((acc, curr) => [...acc, curr._id], []);
|
||||
|
||||
// Foreigns Additions
|
||||
foreignChanges.additions = this.localKeyUsage.filter(foreign => !originalForeignIDs.includes(foreign._id));
|
||||
|
||||
// Foreigns Changes
|
||||
this.originalKeyUsage.forEach(originalForeign => {
|
||||
const lI = this.localKeyUsage.findIndex(localForeign => localForeign._id === originalForeign._id);
|
||||
if (JSON.stringify(originalForeign) !== JSON.stringify(this.localKeyUsage[lI])) {
|
||||
if (this.localKeyUsage[lI]) {
|
||||
foreignChanges.changes.push({
|
||||
...this.localKeyUsage[lI],
|
||||
oldName: originalForeign.constraintName
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Foreigns Deletions
|
||||
foreignChanges.deletions = this.originalKeyUsage.filter(foreign => !localForeignIDs.includes(foreign._id));
|
||||
|
||||
// ALTER
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
@@ -329,14 +392,23 @@ export default {
|
||||
changes,
|
||||
deletions,
|
||||
indexChanges,
|
||||
foreignChanges,
|
||||
options
|
||||
};
|
||||
|
||||
try { // Key usage (foreign keys)
|
||||
try {
|
||||
const { status, response } = await Tables.alterTable(params);
|
||||
|
||||
if (status === 'success') {
|
||||
const oldName = this.tableOptions.name;
|
||||
|
||||
await this.refreshStructure(this.connection.uid);
|
||||
|
||||
if (oldName !== this.localOptions.name) {
|
||||
this.setUnsavedChanges(false);
|
||||
this.changeBreadcrumbs({ schema: this.schema, table: this.localOptions.name });
|
||||
}
|
||||
|
||||
this.getFieldsData();
|
||||
}
|
||||
else
|
||||
@@ -423,6 +495,15 @@ export default {
|
||||
},
|
||||
indexesUpdate (indexes) {
|
||||
this.localIndexes = indexes;
|
||||
},
|
||||
showForeignModal () {
|
||||
this.isForeignModal = true;
|
||||
},
|
||||
hideForeignModal () {
|
||||
this.isForeignModal = false;
|
||||
},
|
||||
foreignsUpdate (foreigns) {
|
||||
this.localKeyUsage = foreigns;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
267
src/renderer/components/WorkspacePropsTabFunction.vue
Normal file
267
src/renderer/components/WorkspacePropsTabFunction.vue
Normal file
@@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<div class="workspace-query-tab column col-12 columns col-gapless">
|
||||
<div class="workspace-query-runner column col-12">
|
||||
<div class="workspace-query-runner-footer">
|
||||
<div class="workspace-query-buttons">
|
||||
<button
|
||||
class="btn btn-primary btn-sm"
|
||||
:disabled="!isChanged"
|
||||
:class="{'loading':isSaving}"
|
||||
@click="saveChanges"
|
||||
>
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
<i class="mdi mdi-24px mdi-content-save ml-1" />
|
||||
</button>
|
||||
<button
|
||||
:disabled="!isChanged"
|
||||
class="btn btn-link btn-sm mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
@click="clearChanges"
|
||||
>
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
|
||||
</button>
|
||||
|
||||
<div class="divider-vert py-3" />
|
||||
|
||||
<button
|
||||
class="btn btn-dark btn-sm disabled"
|
||||
:disabled="isChanged"
|
||||
@click="false"
|
||||
>
|
||||
<span>{{ $t('word.run') }}</span>
|
||||
<i class="mdi mdi-24px mdi-play ml-1" />
|
||||
</button>
|
||||
<button class="btn btn-dark btn-sm" @click="showParamsModal">
|
||||
<span>{{ $t('word.parameters') }}</span>
|
||||
<i class="mdi mdi-24px mdi-dots-horizontal ml-1" />
|
||||
</button>
|
||||
<button class="btn btn-dark btn-sm" @click="showOptionsModal">
|
||||
<span>{{ $t('word.options') }}</span>
|
||||
<i class="mdi mdi-24px mdi-cogs ml-1" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||
<BaseLoader v-if="isLoading" />
|
||||
<label class="form-label ml-2">{{ $t('message.functionBody') }}</label>
|
||||
<QueryEditor
|
||||
v-if="isSelected"
|
||||
ref="queryEditor"
|
||||
:value.sync="localFunction.sql"
|
||||
:workspace="workspace"
|
||||
:schema="schema"
|
||||
:height="editorHeight"
|
||||
/>
|
||||
</div>
|
||||
<WorkspacePropsFunctionOptionsModal
|
||||
v-if="isOptionsModal"
|
||||
:local-options="localFunction"
|
||||
:workspace="workspace"
|
||||
@hide="hideOptionsModal"
|
||||
@options-update="optionsUpdate"
|
||||
/>
|
||||
<WorkspacePropsFunctionParamsModal
|
||||
v-if="isParamsModal"
|
||||
:local-parameters="localFunction.parameters"
|
||||
:workspace="workspace"
|
||||
:func="localFunction.name"
|
||||
@hide="hideParamsModal"
|
||||
@parameters-update="parametersUpdate"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import WorkspacePropsFunctionOptionsModal from '@/components/WorkspacePropsFunctionOptionsModal';
|
||||
import WorkspacePropsFunctionParamsModal from '@/components/WorkspacePropsFunctionParamsModal';
|
||||
import Functions from '@/ipc-api/Functions';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsTabFunction',
|
||||
components: {
|
||||
BaseLoader,
|
||||
QueryEditor,
|
||||
WorkspacePropsFunctionOptionsModal,
|
||||
WorkspacePropsFunctionParamsModal
|
||||
},
|
||||
props: {
|
||||
connection: Object,
|
||||
function: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
tabUid: 'prop',
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
isOptionsModal: false,
|
||||
isParamsModal: false,
|
||||
originalFunction: null,
|
||||
localFunction: { sql: '' },
|
||||
lastFunction: null,
|
||||
sqlProxy: '',
|
||||
editorHeight: 300
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
isSelected () {
|
||||
return this.workspace.selected_tab === 'prop';
|
||||
},
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.originalFunction) !== JSON.stringify(this.localFunction);
|
||||
},
|
||||
isDefinerInUsers () {
|
||||
return this.originalFunction ? this.workspace.users.some(user => this.originalFunction.definer === `\`${user.name}\`@\`${user.host}\``) : true;
|
||||
},
|
||||
schemaTables () {
|
||||
const schemaTables = this.workspace.structure
|
||||
.filter(schema => schema.name === this.schema)
|
||||
.map(schema => schema.tables);
|
||||
|
||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
async function () {
|
||||
if (this.isSelected) {
|
||||
await this.getFunctionData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
|
||||
this.lastFunction = this.function;
|
||||
}
|
||||
},
|
||||
async isSelected (val) {
|
||||
if (val && this.lastFunction !== this.function) {
|
||||
await this.getFunctionData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
|
||||
this.lastFunction = this.function;
|
||||
}
|
||||
},
|
||||
isChanged (val) {
|
||||
if (this.isSelected && this.lastFunction === this.function && this.function !== null)
|
||||
this.setUnsavedChanges(val);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
window.addEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
refreshStructure: 'workspaces/refreshStructure',
|
||||
setUnsavedChanges: 'workspaces/setUnsavedChanges',
|
||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
|
||||
}),
|
||||
async getFunctionData () {
|
||||
if (!this.function) return;
|
||||
|
||||
this.isLoading = true;
|
||||
this.localFunction = { sql: '' };
|
||||
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
func: this.workspace.breadcrumbs.function
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Functions.getFunctionInformations(params);
|
||||
if (status === 'success') {
|
||||
this.originalFunction = response;
|
||||
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction));
|
||||
this.sqlProxy = this.localFunction.sql;
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.resizeQueryEditor();
|
||||
this.isLoading = false;
|
||||
},
|
||||
async saveChanges () {
|
||||
if (this.isSaving) return;
|
||||
this.isSaving = true;
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
func: {
|
||||
...this.localFunction,
|
||||
oldName: this.originalFunction.name
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Functions.alterFunction(params);
|
||||
|
||||
if (status === 'success') {
|
||||
const oldName = this.originalFunction.name;
|
||||
|
||||
await this.refreshStructure(this.connection.uid);
|
||||
|
||||
if (oldName !== this.localFunction.name) {
|
||||
this.setUnsavedChanges(false);
|
||||
this.changeBreadcrumbs({ schema: this.schema, function: this.localFunction.name });
|
||||
}
|
||||
|
||||
this.getFunctionData();
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isSaving = false;
|
||||
},
|
||||
clearChanges () {
|
||||
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction));
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
|
||||
},
|
||||
resizeQueryEditor () {
|
||||
if (this.$refs.queryEditor) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
this.editorHeight = size;
|
||||
this.$refs.queryEditor.editor.resize();
|
||||
}
|
||||
},
|
||||
optionsUpdate (options) {
|
||||
this.localFunction = options;
|
||||
},
|
||||
parametersUpdate (parameters) {
|
||||
this.localFunction = { ...this.localFunction, parameters };
|
||||
},
|
||||
showOptionsModal () {
|
||||
this.isOptionsModal = true;
|
||||
},
|
||||
hideOptionsModal () {
|
||||
this.isOptionsModal = false;
|
||||
},
|
||||
showParamsModal () {
|
||||
this.isParamsModal = true;
|
||||
},
|
||||
hideParamsModal () {
|
||||
this.isParamsModal = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
266
src/renderer/components/WorkspacePropsTabRoutine.vue
Normal file
266
src/renderer/components/WorkspacePropsTabRoutine.vue
Normal file
@@ -0,0 +1,266 @@
|
||||
<template>
|
||||
<div class="workspace-query-tab column col-12 columns col-gapless">
|
||||
<div class="workspace-query-runner column col-12">
|
||||
<div class="workspace-query-runner-footer">
|
||||
<div class="workspace-query-buttons">
|
||||
<button
|
||||
class="btn btn-primary btn-sm"
|
||||
:disabled="!isChanged"
|
||||
:class="{'loading':isSaving}"
|
||||
@click="saveChanges"
|
||||
>
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
<i class="mdi mdi-24px mdi-content-save ml-1" />
|
||||
</button>
|
||||
<button
|
||||
:disabled="!isChanged"
|
||||
class="btn btn-link btn-sm mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
@click="clearChanges"
|
||||
>
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
|
||||
</button>
|
||||
|
||||
<div class="divider-vert py-3" />
|
||||
|
||||
<button
|
||||
class="btn btn-dark btn-sm disabled"
|
||||
:disabled="isChanged"
|
||||
@click="false"
|
||||
>
|
||||
<span>{{ $t('word.run') }}</span>
|
||||
<i class="mdi mdi-24px mdi-play ml-1" />
|
||||
</button>
|
||||
<button class="btn btn-dark btn-sm" @click="showParamsModal">
|
||||
<span>{{ $t('word.parameters') }}</span>
|
||||
<i class="mdi mdi-24px mdi-dots-horizontal ml-1" />
|
||||
</button>
|
||||
<button class="btn btn-dark btn-sm" @click="showOptionsModal">
|
||||
<span>{{ $t('word.options') }}</span>
|
||||
<i class="mdi mdi-24px mdi-cogs ml-1" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||
<BaseLoader v-if="isLoading" />
|
||||
<label class="form-label ml-2">{{ $t('message.routineBody') }}</label>
|
||||
<QueryEditor
|
||||
v-if="isSelected"
|
||||
ref="queryEditor"
|
||||
:value.sync="localRoutine.sql"
|
||||
:workspace="workspace"
|
||||
:schema="schema"
|
||||
:height="editorHeight"
|
||||
/>
|
||||
</div>
|
||||
<WorkspacePropsRoutineOptionsModal
|
||||
v-if="isOptionsModal"
|
||||
:local-options="localRoutine"
|
||||
:workspace="workspace"
|
||||
@hide="hideOptionsModal"
|
||||
@options-update="optionsUpdate"
|
||||
/>
|
||||
<WorkspacePropsRoutineParamsModal
|
||||
v-if="isParamsModal"
|
||||
:local-parameters="localRoutine.parameters"
|
||||
:workspace="workspace"
|
||||
:routine="localRoutine.name"
|
||||
@hide="hideParamsModal"
|
||||
@parameters-update="parametersUpdate"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import WorkspacePropsRoutineOptionsModal from '@/components/WorkspacePropsRoutineOptionsModal';
|
||||
import WorkspacePropsRoutineParamsModal from '@/components/WorkspacePropsRoutineParamsModal';
|
||||
import Routines from '@/ipc-api/Routines';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsTabRoutine',
|
||||
components: {
|
||||
QueryEditor,
|
||||
BaseLoader,
|
||||
WorkspacePropsRoutineOptionsModal,
|
||||
WorkspacePropsRoutineParamsModal
|
||||
},
|
||||
props: {
|
||||
connection: Object,
|
||||
routine: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
tabUid: 'prop',
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
isOptionsModal: false,
|
||||
isParamsModal: false,
|
||||
originalRoutine: null,
|
||||
localRoutine: { sql: '' },
|
||||
lastRoutine: null,
|
||||
sqlProxy: '',
|
||||
editorHeight: 300
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
isSelected () {
|
||||
return this.workspace.selected_tab === 'prop';
|
||||
},
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.originalRoutine) !== JSON.stringify(this.localRoutine);
|
||||
},
|
||||
isDefinerInUsers () {
|
||||
return this.originalRoutine ? this.workspace.users.some(user => this.originalRoutine.definer === `\`${user.name}\`@\`${user.host}\``) : true;
|
||||
},
|
||||
schemaTables () {
|
||||
const schemaTables = this.workspace.structure
|
||||
.filter(schema => schema.name === this.schema)
|
||||
.map(schema => schema.tables);
|
||||
|
||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
async routine () {
|
||||
if (this.isSelected) {
|
||||
await this.getRoutineData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localRoutine.sql);
|
||||
this.lastRoutine = this.routine;
|
||||
}
|
||||
},
|
||||
async isSelected (val) {
|
||||
if (val && this.lastRoutine !== this.routine) {
|
||||
await this.getRoutineData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localRoutine.sql);
|
||||
this.lastRoutine = this.routine;
|
||||
}
|
||||
},
|
||||
isChanged (val) {
|
||||
if (this.isSelected && this.lastRoutine === this.routine && this.routine !== null)
|
||||
this.setUnsavedChanges(val);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
window.addEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
refreshStructure: 'workspaces/refreshStructure',
|
||||
setUnsavedChanges: 'workspaces/setUnsavedChanges',
|
||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
|
||||
}),
|
||||
async getRoutineData () {
|
||||
if (!this.routine) return;
|
||||
this.localRoutine = { sql: '' };
|
||||
this.isLoading = true;
|
||||
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
routine: this.workspace.breadcrumbs.procedure
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Routines.getRoutineInformations(params);
|
||||
if (status === 'success') {
|
||||
this.originalRoutine = response;
|
||||
this.localRoutine = JSON.parse(JSON.stringify(this.originalRoutine));
|
||||
this.sqlProxy = this.localRoutine.sql;
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.resizeQueryEditor();
|
||||
this.isLoading = false;
|
||||
},
|
||||
async saveChanges () {
|
||||
if (this.isSaving) return;
|
||||
this.isSaving = true;
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
routine: {
|
||||
...this.localRoutine,
|
||||
oldName: this.originalRoutine.name
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Routines.alterRoutine(params);
|
||||
|
||||
if (status === 'success') {
|
||||
const oldName = this.originalRoutine.name;
|
||||
|
||||
await this.refreshStructure(this.connection.uid);
|
||||
|
||||
if (oldName !== this.localRoutine.name) {
|
||||
this.setUnsavedChanges(false);
|
||||
this.changeBreadcrumbs({ schema: this.schema, procedure: this.localRoutine.name });
|
||||
}
|
||||
|
||||
this.getRoutineData();
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isSaving = false;
|
||||
},
|
||||
clearChanges () {
|
||||
this.localRoutine = JSON.parse(JSON.stringify(this.originalRoutine));
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localRoutine.sql);
|
||||
},
|
||||
resizeQueryEditor () {
|
||||
if (this.$refs.queryEditor) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
this.editorHeight = size;
|
||||
this.$refs.queryEditor.editor.resize();
|
||||
}
|
||||
},
|
||||
optionsUpdate (options) {
|
||||
this.localRoutine = options;
|
||||
},
|
||||
parametersUpdate (parameters) {
|
||||
this.localRoutine = { ...this.localRoutine, parameters };
|
||||
},
|
||||
showOptionsModal () {
|
||||
this.isOptionsModal = true;
|
||||
},
|
||||
hideOptionsModal () {
|
||||
this.isOptionsModal = false;
|
||||
},
|
||||
showParamsModal () {
|
||||
this.isParamsModal = true;
|
||||
},
|
||||
hideParamsModal () {
|
||||
this.isParamsModal = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
316
src/renderer/components/WorkspacePropsTabScheduler.vue
Normal file
316
src/renderer/components/WorkspacePropsTabScheduler.vue
Normal file
@@ -0,0 +1,316 @@
|
||||
<template>
|
||||
<div class="workspace-query-tab column col-12 columns col-gapless">
|
||||
<div class="workspace-query-runner column col-12">
|
||||
<div class="workspace-query-runner-footer">
|
||||
<div class="workspace-query-buttons">
|
||||
<button
|
||||
class="btn btn-primary btn-sm"
|
||||
:disabled="!isChanged"
|
||||
:class="{'loading':isSaving}"
|
||||
@click="saveChanges"
|
||||
>
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
<i class="mdi mdi-24px mdi-content-save ml-1" />
|
||||
</button>
|
||||
<button
|
||||
:disabled="!isChanged"
|
||||
class="btn btn-link btn-sm mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
@click="clearChanges"
|
||||
>
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
|
||||
</button>
|
||||
|
||||
<div class="divider-vert py-3" />
|
||||
<button class="btn btn-dark btn-sm" @click="showTimingModal">
|
||||
<span>{{ $t('word.timing') }}</span>
|
||||
<i class="mdi mdi-24px mdi-timer ml-1" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="columns mb-4">
|
||||
<div class="column col-3">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.name') }}</label>
|
||||
<input
|
||||
v-model="localScheduler.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-3">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.definer') }}</label>
|
||||
<select
|
||||
v-if="workspace.users.length"
|
||||
v-model="localScheduler.definer"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
<option v-if="!isDefinerInUsers" :value="originalScheduler.definer">
|
||||
{{ originalScheduler.definer.replaceAll('`', '') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="user in workspace.users"
|
||||
:key="`${user.name}@${user.host}`"
|
||||
:value="`\`${user.name}\`@\`${user.host}\``"
|
||||
>
|
||||
{{ user.name }}@{{ user.host }}
|
||||
</option>
|
||||
</select>
|
||||
<select v-if="!workspace.users.length" class="form-select">
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-6">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.comment') }}</label>
|
||||
<input
|
||||
v-model="localScheduler.comment"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div class="form-group">
|
||||
<label class="form-label mr-2">{{ $t('word.state') }}</label>
|
||||
<label class="form-radio form-inline">
|
||||
<input
|
||||
v-model="localScheduler.state"
|
||||
type="radio"
|
||||
name="state"
|
||||
value="ENABLE"
|
||||
><i class="form-icon" /> ENABLE
|
||||
</label>
|
||||
<label class="form-radio form-inline">
|
||||
<input
|
||||
v-model="localScheduler.state"
|
||||
type="radio"
|
||||
name="state"
|
||||
value="DISABLE"
|
||||
><i class="form-icon" /> DISABLE
|
||||
</label>
|
||||
<label class="form-radio form-inline">
|
||||
<input
|
||||
v-model="localScheduler.state"
|
||||
type="radio"
|
||||
name="state"
|
||||
value="DISABLE ON SLAVE"
|
||||
><i class="form-icon" /> DISABLE ON SLAVE
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||
<BaseLoader v-if="isLoading" />
|
||||
<label class="form-label ml-2">{{ $t('message.schedulerBody') }}</label>
|
||||
<QueryEditor
|
||||
v-if="isSelected"
|
||||
ref="queryEditor"
|
||||
:value.sync="localScheduler.sql"
|
||||
:workspace="workspace"
|
||||
:schema="schema"
|
||||
:height="editorHeight"
|
||||
/>
|
||||
</div>
|
||||
<WorkspacePropsSchedulerTimingModal
|
||||
v-if="isTimingModal"
|
||||
:local-options="localScheduler"
|
||||
:workspace="workspace"
|
||||
@hide="hideTimingModal"
|
||||
@options-update="timingUpdate"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import WorkspacePropsSchedulerTimingModal from '@/components/WorkspacePropsSchedulerTimingModal';
|
||||
import Schedulers from '@/ipc-api/Schedulers';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsTabScheduler',
|
||||
components: {
|
||||
BaseLoader,
|
||||
QueryEditor,
|
||||
WorkspacePropsSchedulerTimingModal
|
||||
},
|
||||
props: {
|
||||
connection: Object,
|
||||
scheduler: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
tabUid: 'prop',
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
isTimingModal: false,
|
||||
originalScheduler: null,
|
||||
localScheduler: { sql: '' },
|
||||
lastScheduler: null,
|
||||
sqlProxy: '',
|
||||
editorHeight: 300
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
isSelected () {
|
||||
return this.workspace.selected_tab === 'prop';
|
||||
},
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.originalScheduler) !== JSON.stringify(this.localScheduler);
|
||||
},
|
||||
isDefinerInUsers () {
|
||||
return this.originalScheduler ? this.workspace.users.some(user => this.originalScheduler.definer === `\`${user.name}\`@\`${user.host}\``) : true;
|
||||
},
|
||||
schemaTables () {
|
||||
const schemaTables = this.workspace.structure
|
||||
.filter(schema => schema.name === this.schema)
|
||||
.map(schema => schema.tables);
|
||||
|
||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
async scheduler () {
|
||||
if (this.isSelected) {
|
||||
await this.getSchedulerData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localScheduler.sql);
|
||||
this.lastScheduler = this.scheduler;
|
||||
}
|
||||
},
|
||||
async isSelected (val) {
|
||||
if (val && this.lastScheduler !== this.scheduler) {
|
||||
await this.getSchedulerData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localScheduler.sql);
|
||||
this.lastScheduler = this.scheduler;
|
||||
}
|
||||
},
|
||||
isChanged (val) {
|
||||
if (this.isSelected && this.lastScheduler === this.scheduler && this.scheduler !== null)
|
||||
this.setUnsavedChanges(val);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
window.addEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
refreshStructure: 'workspaces/refreshStructure',
|
||||
setUnsavedChanges: 'workspaces/setUnsavedChanges',
|
||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
|
||||
}),
|
||||
async getSchedulerData () {
|
||||
if (!this.scheduler) return;
|
||||
this.isLoading = true;
|
||||
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
scheduler: this.workspace.breadcrumbs.scheduler
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Schedulers.getSchedulerInformations(params);
|
||||
if (status === 'success') {
|
||||
this.originalScheduler = response;
|
||||
this.localScheduler = JSON.parse(JSON.stringify(this.originalScheduler));
|
||||
this.sqlProxy = this.localScheduler.sql;
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.resizeQueryEditor();
|
||||
this.isLoading = false;
|
||||
},
|
||||
async saveChanges () {
|
||||
if (this.isSaving) return;
|
||||
this.isSaving = true;
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
scheduler: {
|
||||
...this.localScheduler,
|
||||
oldName: this.originalScheduler.name
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Schedulers.alterScheduler(params);
|
||||
|
||||
if (status === 'success') {
|
||||
const oldName = this.originalScheduler.name;
|
||||
|
||||
await this.refreshStructure(this.connection.uid);
|
||||
|
||||
if (oldName !== this.localScheduler.name) {
|
||||
this.setUnsavedChanges(false);
|
||||
this.changeBreadcrumbs({ schema: this.schema, scheduler: this.localScheduler.name });
|
||||
}
|
||||
|
||||
this.getSchedulerData();
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isSaving = false;
|
||||
},
|
||||
clearChanges () {
|
||||
this.localScheduler = JSON.parse(JSON.stringify(this.originalScheduler));
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localScheduler.sql);
|
||||
},
|
||||
resizeQueryEditor () {
|
||||
if (this.$refs.queryEditor) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
this.editorHeight = size;
|
||||
this.$refs.queryEditor.editor.resize();
|
||||
}
|
||||
},
|
||||
showTimingModal () {
|
||||
this.isTimingModal = true;
|
||||
},
|
||||
hideTimingModal () {
|
||||
this.isTimingModal = false;
|
||||
},
|
||||
timingUpdate (options) {
|
||||
this.localScheduler = options;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
280
src/renderer/components/WorkspacePropsTabTrigger.vue
Normal file
280
src/renderer/components/WorkspacePropsTabTrigger.vue
Normal file
@@ -0,0 +1,280 @@
|
||||
<template>
|
||||
<div class="workspace-query-tab column col-12 columns col-gapless">
|
||||
<div class="workspace-query-runner column col-12">
|
||||
<div class="workspace-query-runner-footer">
|
||||
<div class="workspace-query-buttons">
|
||||
<button
|
||||
class="btn btn-primary btn-sm"
|
||||
:disabled="!isChanged"
|
||||
:class="{'loading':isSaving}"
|
||||
@click="saveChanges"
|
||||
>
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
<i class="mdi mdi-24px mdi-content-save ml-1" />
|
||||
</button>
|
||||
<button
|
||||
:disabled="!isChanged"
|
||||
class="btn btn-link btn-sm mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
@click="clearChanges"
|
||||
>
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="columns mb-4">
|
||||
<div class="column col-3">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.name') }}</label>
|
||||
<input
|
||||
v-model="localTrigger.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-3">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.definer') }}</label>
|
||||
<select
|
||||
v-if="workspace.users.length"
|
||||
v-model="localTrigger.definer"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
<option v-if="!isDefinerInUsers" :value="originalTrigger.definer">
|
||||
{{ originalTrigger.definer.replaceAll('`', '') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="user in workspace.users"
|
||||
:key="`${user.name}@${user.host}`"
|
||||
:value="`\`${user.name}\`@\`${user.host}\``"
|
||||
>
|
||||
{{ user.name }}@{{ user.host }}
|
||||
</option>
|
||||
</select>
|
||||
<select v-if="!workspace.users.length" class="form-select">
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column col-3">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.table') }}</label>
|
||||
<select v-model="localTrigger.table" class="form-select">
|
||||
<option v-for="table in schemaTables" :key="table.name">
|
||||
{{ table.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-3">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.event') }}</label>
|
||||
<div class="input-group">
|
||||
<select v-model="localTrigger.event1" class="form-select">
|
||||
<option>BEFORE</option>
|
||||
<option>AFTER</option>
|
||||
</select>
|
||||
<select v-model="localTrigger.event2" class="form-select">
|
||||
<option>INSERT</option>
|
||||
<option>UPDATE</option>
|
||||
<option>DELETE</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||
<BaseLoader v-if="isLoading" />
|
||||
<label class="form-label ml-2">{{ $t('message.triggerStatement') }}</label>
|
||||
<QueryEditor
|
||||
v-if="isSelected"
|
||||
ref="queryEditor"
|
||||
:value.sync="localTrigger.sql"
|
||||
:workspace="workspace"
|
||||
:schema="schema"
|
||||
:height="editorHeight"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import Triggers from '@/ipc-api/Triggers';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsTabTrigger',
|
||||
components: {
|
||||
BaseLoader,
|
||||
QueryEditor
|
||||
},
|
||||
props: {
|
||||
connection: Object,
|
||||
trigger: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
tabUid: 'prop',
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
originalTrigger: null,
|
||||
localTrigger: { sql: '' },
|
||||
lastTrigger: null,
|
||||
sqlProxy: '',
|
||||
editorHeight: 300
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
isSelected () {
|
||||
return this.workspace.selected_tab === 'prop';
|
||||
},
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.originalTrigger) !== JSON.stringify(this.localTrigger);
|
||||
},
|
||||
isDefinerInUsers () {
|
||||
return this.originalTrigger ? this.workspace.users.some(user => this.originalTrigger.definer === `\`${user.name}\`@\`${user.host}\``) : true;
|
||||
},
|
||||
schemaTables () {
|
||||
const schemaTables = this.workspace.structure
|
||||
.filter(schema => schema.name === this.schema)
|
||||
.map(schema => schema.tables);
|
||||
|
||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
async trigger () {
|
||||
if (this.isSelected) {
|
||||
await this.getTriggerData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localTrigger.sql);
|
||||
this.lastTrigger = this.trigger;
|
||||
}
|
||||
},
|
||||
async isSelected (val) {
|
||||
if (val && this.lastTrigger !== this.trigger) {
|
||||
await this.getTriggerData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localTrigger.sql);
|
||||
this.lastTrigger = this.trigger;
|
||||
}
|
||||
},
|
||||
isChanged (val) {
|
||||
if (this.isSelected && this.lastTrigger === this.trigger && this.trigger !== null)
|
||||
this.setUnsavedChanges(val);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
window.addEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
refreshStructure: 'workspaces/refreshStructure',
|
||||
setUnsavedChanges: 'workspaces/setUnsavedChanges',
|
||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
|
||||
}),
|
||||
async getTriggerData () {
|
||||
if (!this.trigger) return;
|
||||
|
||||
this.localTrigger = { sql: '' };
|
||||
this.isLoading = true;
|
||||
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
trigger: this.workspace.breadcrumbs.trigger
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Triggers.getTriggerInformations(params);
|
||||
if (status === 'success') {
|
||||
this.originalTrigger = response;
|
||||
this.localTrigger = JSON.parse(JSON.stringify(this.originalTrigger));
|
||||
this.sqlProxy = this.localTrigger.sql;
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.resizeQueryEditor();
|
||||
this.isLoading = false;
|
||||
},
|
||||
async saveChanges () {
|
||||
if (this.isSaving) return;
|
||||
this.isSaving = true;
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
trigger: {
|
||||
...this.localTrigger,
|
||||
oldName: this.originalTrigger.name
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Triggers.alterTrigger(params);
|
||||
|
||||
if (status === 'success') {
|
||||
const oldName = this.originalTrigger.name;
|
||||
|
||||
await this.refreshStructure(this.connection.uid);
|
||||
|
||||
if (oldName !== this.localTrigger.name) {
|
||||
this.setUnsavedChanges(false);
|
||||
this.changeBreadcrumbs({ schema: this.schema, trigger: this.localTrigger.name });
|
||||
}
|
||||
|
||||
this.getTriggerData();
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isSaving = false;
|
||||
},
|
||||
clearChanges () {
|
||||
this.localTrigger = JSON.parse(JSON.stringify(this.originalTrigger));
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localTrigger.sql);
|
||||
},
|
||||
resizeQueryEditor () {
|
||||
if (this.$refs.queryEditor) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
this.editorHeight = size;
|
||||
this.$refs.queryEditor.editor.resize();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
333
src/renderer/components/WorkspacePropsTabView.vue
Normal file
333
src/renderer/components/WorkspacePropsTabView.vue
Normal file
@@ -0,0 +1,333 @@
|
||||
<template>
|
||||
<div class="workspace-query-tab column col-12 columns col-gapless">
|
||||
<div class="workspace-query-runner column col-12">
|
||||
<div class="workspace-query-runner-footer">
|
||||
<div class="workspace-query-buttons">
|
||||
<button
|
||||
class="btn btn-primary btn-sm"
|
||||
:disabled="!isChanged"
|
||||
:class="{'loading':isSaving}"
|
||||
@click="saveChanges"
|
||||
>
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
<i class="mdi mdi-24px mdi-content-save ml-1" />
|
||||
</button>
|
||||
<button
|
||||
:disabled="!isChanged"
|
||||
class="btn btn-link btn-sm mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
@click="clearChanges"
|
||||
>
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="columns mb-4">
|
||||
<div class="column col-3">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.name') }}</label>
|
||||
<input
|
||||
v-model="localView.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-3">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.definer') }}</label>
|
||||
<select
|
||||
v-if="workspace.users.length"
|
||||
v-model="localView.definer"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
<option v-if="!isDefinerInUsers" :value="originalView.definer">
|
||||
{{ originalView.definer.replaceAll('`', '') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="user in workspace.users"
|
||||
:key="`${user.name}@${user.host}`"
|
||||
:value="`\`${user.name}\`@\`${user.host}\``"
|
||||
>
|
||||
{{ user.name }}@{{ user.host }}
|
||||
</option>
|
||||
</select>
|
||||
<select v-if="!workspace.users.length" class="form-select">
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column col-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('message.sqlSecurity') }}</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.security"
|
||||
type="radio"
|
||||
name="security"
|
||||
value="DEFINER"
|
||||
>
|
||||
<i class="form-icon" /> DEFINER
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.security"
|
||||
type="radio"
|
||||
name="security"
|
||||
value="INVOKER"
|
||||
>
|
||||
<i class="form-icon" /> INVOKER
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.algorithm') }}</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.algorithm"
|
||||
type="radio"
|
||||
name="algorithm"
|
||||
value="UNDEFINED"
|
||||
>
|
||||
<i class="form-icon" /> UNDEFINED
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.algorithm"
|
||||
type="radio"
|
||||
value="MERGE"
|
||||
name="algorithm"
|
||||
>
|
||||
<i class="form-icon" /> MERGE
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.algorithm"
|
||||
type="radio"
|
||||
value="TEMPTABLE"
|
||||
name="algorithm"
|
||||
>
|
||||
<i class="form-icon" /> TEMPTABLE
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('message.updateOption') }}</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.updateOption"
|
||||
type="radio"
|
||||
name="update"
|
||||
value=""
|
||||
>
|
||||
<i class="form-icon" /> None
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.updateOption"
|
||||
type="radio"
|
||||
name="update"
|
||||
value="CASCADED"
|
||||
>
|
||||
<i class="form-icon" /> CASCADED
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="localView.updateOption"
|
||||
type="radio"
|
||||
name="update"
|
||||
value="LOCAL"
|
||||
>
|
||||
<i class="form-icon" /> LOCAL
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||
<BaseLoader v-if="isLoading" />
|
||||
<label class="form-label ml-2">{{ $t('message.selectStatement') }}</label>
|
||||
<QueryEditor
|
||||
v-if="isSelected"
|
||||
ref="queryEditor"
|
||||
:value.sync="localView.sql"
|
||||
:workspace="workspace"
|
||||
:schema="schema"
|
||||
:height="editorHeight"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import Views from '@/ipc-api/Views';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsTabView',
|
||||
components: {
|
||||
BaseLoader,
|
||||
QueryEditor
|
||||
},
|
||||
props: {
|
||||
connection: Object,
|
||||
view: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
tabUid: 'prop',
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
originalView: null,
|
||||
localView: { sql: '' },
|
||||
lastView: null,
|
||||
sqlProxy: '',
|
||||
editorHeight: 300
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
isSelected () {
|
||||
return this.workspace.selected_tab === 'prop';
|
||||
},
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.originalView) !== JSON.stringify(this.localView);
|
||||
},
|
||||
isDefinerInUsers () {
|
||||
return this.originalView ? this.workspace.users.some(user => this.originalView.definer === `\`${user.name}\`@\`${user.host}\``) : true;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
async view () {
|
||||
if (this.isSelected) {
|
||||
await this.getViewData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localView.sql);
|
||||
this.lastView = this.view;
|
||||
}
|
||||
},
|
||||
async isSelected (val) {
|
||||
if (val && this.lastView !== this.view) {
|
||||
await this.getViewData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localView.sql);
|
||||
this.lastView = this.view;
|
||||
}
|
||||
},
|
||||
isChanged (val) {
|
||||
if (this.isSelected && this.lastView === this.view && this.view !== null)
|
||||
this.setUnsavedChanges(val);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
window.addEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
refreshStructure: 'workspaces/refreshStructure',
|
||||
setUnsavedChanges: 'workspaces/setUnsavedChanges',
|
||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
|
||||
}),
|
||||
async getViewData () {
|
||||
if (!this.view) return;
|
||||
this.isLoading = true;
|
||||
this.localView = { sql: '' };
|
||||
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
view: this.workspace.breadcrumbs.view
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Views.getViewInformations(params);
|
||||
if (status === 'success') {
|
||||
this.originalView = response;
|
||||
this.localView = JSON.parse(JSON.stringify(this.originalView));
|
||||
this.sqlProxy = this.localView.sql;
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.resizeQueryEditor();
|
||||
this.isLoading = false;
|
||||
},
|
||||
async saveChanges () {
|
||||
if (this.isSaving) return;
|
||||
this.isSaving = true;
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
view: {
|
||||
...this.localView,
|
||||
oldName: this.originalView.name
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Views.alterView(params);
|
||||
|
||||
if (status === 'success') {
|
||||
const oldName = this.originalView.name;
|
||||
|
||||
await this.refreshStructure(this.connection.uid);
|
||||
|
||||
if (oldName !== this.localView.name) {
|
||||
this.setUnsavedChanges(false);
|
||||
this.changeBreadcrumbs({ schema: this.schema, view: this.localView.name });
|
||||
}
|
||||
|
||||
this.getViewData();
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isSaving = false;
|
||||
},
|
||||
clearChanges () {
|
||||
this.localView = JSON.parse(JSON.stringify(this.originalView));
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localView.sql);
|
||||
},
|
||||
resizeQueryEditor () {
|
||||
if (this.$refs.queryEditor) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
this.editorHeight = size;
|
||||
this.$refs.queryEditor.editor.resize();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -29,14 +29,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="th">
|
||||
<div class="column-resizable">
|
||||
<div class="column-resizable min-100">
|
||||
<div class="table-column-title">
|
||||
{{ $t('word.name') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="th">
|
||||
<div class="column-resizable">
|
||||
<div class="column-resizable min-100">
|
||||
<div class="table-column-title">
|
||||
{{ $t('word.type') }}
|
||||
</div>
|
||||
@@ -85,7 +85,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="th">
|
||||
<div class="column-resizable">
|
||||
<div class="column-resizable min-100">
|
||||
<div class="table-column-title">
|
||||
{{ $t('word.collation') }}
|
||||
</div>
|
||||
@@ -104,6 +104,7 @@
|
||||
:key="row._id"
|
||||
:row="row"
|
||||
:indexes="getIndexes(row.name)"
|
||||
:foreigns="getForeigns(row.name)"
|
||||
:data-types="dataTypes"
|
||||
@contextmenu="contextMenu"
|
||||
/>
|
||||
@@ -128,6 +129,7 @@ export default {
|
||||
props: {
|
||||
fields: Array,
|
||||
indexes: Array,
|
||||
foreigns: Array,
|
||||
indexTypes: Array,
|
||||
tabUid: [String, Number],
|
||||
connUid: String,
|
||||
@@ -214,6 +216,13 @@ export default {
|
||||
acc.push(...curr.fields.map(f => ({ name: f, type: curr.type })));
|
||||
return acc;
|
||||
}, []).filter(f => f.name === field);
|
||||
},
|
||||
getForeigns (field) {
|
||||
return this.foreigns.reduce((acc, curr) => {
|
||||
if (curr.field === field)
|
||||
acc.push(`${curr.refTable}.${curr.refField}`);
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -231,4 +240,8 @@ export default {
|
||||
.vscroll {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.min-100 {
|
||||
min-width: 100px !important;
|
||||
}
|
||||
</style>
|
||||
|
@@ -77,11 +77,3 @@ export default {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.disabled {
|
||||
pointer-events: none;
|
||||
filter: grayscale(100%);
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="tr" @contextmenu.prevent="$emit('contextmenu', $event, localRow._id)">
|
||||
<div class="td">
|
||||
<div class="td" tabindex="0">
|
||||
<div class="row-draggable">
|
||||
<i class="mdi mdi-drag-horizontal row-draggable-icon" />
|
||||
{{ localRow.order }}
|
||||
@@ -15,9 +15,15 @@
|
||||
class="d-inline-block mdi mdi-key column-key c-help"
|
||||
:class="`key-${index.type}`"
|
||||
/>
|
||||
<i
|
||||
v-for="foreign in foreigns"
|
||||
:key="foreign"
|
||||
:title="foreign"
|
||||
class="d-inline-block mdi mdi-key-link c-help"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="td">
|
||||
<div class="td" tabindex="0">
|
||||
<span
|
||||
v-if="!isInlineEditor.name"
|
||||
class="cell-content"
|
||||
@@ -35,11 +41,15 @@
|
||||
@blur="editOFF"
|
||||
>
|
||||
</div>
|
||||
<div class="td text-uppercase text-left" :class="`type-${lowerCase(localRow.type)}`">
|
||||
<div
|
||||
class="td text-uppercase"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
v-if="!isInlineEditor.type"
|
||||
class="cell-content"
|
||||
@dblclick="editON($event, localRow.type.toUpperCase(), 'type')"
|
||||
class="cell-content text-left"
|
||||
:class="`type-${lowerCase(localRow.type)}`"
|
||||
@click="editON($event, localRow.type.toUpperCase(), 'type')"
|
||||
>
|
||||
{{ localRow.type }}
|
||||
</span>
|
||||
@@ -47,7 +57,7 @@
|
||||
v-else
|
||||
ref="editField"
|
||||
v-model="editingContent"
|
||||
class="editable-field px-1 text-uppercase"
|
||||
class="form-select editable-field small-select text-uppercase"
|
||||
@blur="editOFF"
|
||||
>
|
||||
<optgroup
|
||||
@@ -66,7 +76,7 @@
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
<div class="td type-int">
|
||||
<div class="td type-int" tabindex="0">
|
||||
<template v-if="fieldType.length">
|
||||
<span
|
||||
v-if="!isInlineEditor.length"
|
||||
@@ -86,7 +96,7 @@
|
||||
>
|
||||
</template>
|
||||
</div>
|
||||
<div class="td">
|
||||
<div class="td" tabindex="0">
|
||||
<label class="form-checkbox">
|
||||
<input
|
||||
v-model="localRow.unsigned"
|
||||
@@ -96,17 +106,17 @@
|
||||
<i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="td">
|
||||
<div class="td" tabindex="0">
|
||||
<label class="form-checkbox">
|
||||
<input
|
||||
v-model="localRow.nullable"
|
||||
type="checkbox"
|
||||
:disabled="localRow.key === 'pri'"
|
||||
:disabled="!isNullable"
|
||||
>
|
||||
<i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="td">
|
||||
<div class="td" tabindex="0">
|
||||
<label class="form-checkbox">
|
||||
<input
|
||||
v-model="localRow.zerofill"
|
||||
@@ -116,12 +126,12 @@
|
||||
<i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="td">
|
||||
<div class="td" tabindex="0">
|
||||
<span class="cell-content" @dblclick="editON($event, localRow.default, 'default')">
|
||||
{{ fieldDefault }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="td type-varchar">
|
||||
<div class="td type-varchar" tabindex="0">
|
||||
<span
|
||||
v-if="!isInlineEditor.comment"
|
||||
class="cell-content"
|
||||
@@ -139,12 +149,12 @@
|
||||
@blur="editOFF"
|
||||
>
|
||||
</div>
|
||||
<div class="td">
|
||||
<div class="td" tabindex="0">
|
||||
<template v-if="fieldType.collation">
|
||||
<span
|
||||
v-if="!isInlineEditor.collation"
|
||||
class="cell-content"
|
||||
@dblclick="editON($event, localRow.collation, 'collation')"
|
||||
@click="editON($event, localRow.collation, 'collation')"
|
||||
>
|
||||
{{ localRow.collation }}
|
||||
</span>
|
||||
@@ -152,7 +162,7 @@
|
||||
v-else
|
||||
ref="editField"
|
||||
v-model="editingContent"
|
||||
class="editable-field px-1"
|
||||
class="form-select small-select editable-field"
|
||||
@blur="editOFF"
|
||||
>
|
||||
<option
|
||||
@@ -224,7 +234,7 @@
|
||||
<label class="form-radio form-inline">
|
||||
<input
|
||||
v-model="defaultValue.type"
|
||||
:disabled="localRow.key !== 'pri'"
|
||||
:disabled="!canAutoincrement"
|
||||
type="radio"
|
||||
name="default"
|
||||
value="autoincrement"
|
||||
@@ -283,7 +293,8 @@ export default {
|
||||
props: {
|
||||
row: Object,
|
||||
dataTypes: Array,
|
||||
indexes: Array
|
||||
indexes: Array,
|
||||
foreigns: Array
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -297,6 +308,7 @@ export default {
|
||||
onUpdate: ''
|
||||
},
|
||||
editingContent: null,
|
||||
originalContent: null,
|
||||
editingField: null
|
||||
};
|
||||
},
|
||||
@@ -325,6 +337,12 @@ export default {
|
||||
},
|
||||
collations () {
|
||||
return this.getWorkspace(this.selectedWorkspace).collations;
|
||||
},
|
||||
canAutoincrement () {
|
||||
return this.indexes.some(index => ['PRIMARY', 'UNIQUE'].includes(index.type));
|
||||
},
|
||||
isNullable () {
|
||||
return !this.indexes.some(index => ['PRIMARY'].includes(index.type));
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -333,6 +351,13 @@ export default {
|
||||
},
|
||||
row () {
|
||||
this.localRow = this.row;
|
||||
},
|
||||
indexes () {
|
||||
if (!this.canAutoincrement)
|
||||
this.localRow.autoIncrement = false;
|
||||
|
||||
if (!this.isNullable)
|
||||
this.localRow.nullable = false;
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
@@ -397,6 +422,7 @@ export default {
|
||||
this.editingField = field;
|
||||
|
||||
this.editingContent = content;
|
||||
this.originalContent = content;
|
||||
|
||||
const obj = { [field]: true };
|
||||
this.isInlineEditor = { ...this.isInlineEditor, ...obj };
|
||||
@@ -414,10 +440,10 @@ export default {
|
||||
editOFF () {
|
||||
this.localRow[this.editingField] = this.editingContent;
|
||||
|
||||
if (this.editingField === 'type') {
|
||||
this.localRow.numLength = false;
|
||||
this.localRow.charLength = false;
|
||||
this.localRow.datePrecision = false;
|
||||
if (this.editingField === 'type' && this.editingContent !== this.originalContent) {
|
||||
this.localRow.numLength = null;
|
||||
this.localRow.charLength = null;
|
||||
this.localRow.datePrecision = null;
|
||||
|
||||
if (this.fieldType.length) {
|
||||
if (['integer', 'float', 'binary', 'spatial'].includes(this.fieldType.group)) this.localRow.numLength = 11;
|
||||
@@ -427,6 +453,12 @@ export default {
|
||||
|
||||
if (!this.fieldType.collation)
|
||||
this.localRow.collation = null;
|
||||
|
||||
if (!this.fieldType.unsigned)
|
||||
this.localRow.unsigned = false;
|
||||
|
||||
if (!this.fieldType.zerofill)
|
||||
this.localRow.zerofill = false;
|
||||
}
|
||||
|
||||
if (this.editingField === 'default') {
|
||||
@@ -460,6 +492,7 @@ export default {
|
||||
});
|
||||
|
||||
this.editingContent = null;
|
||||
this.originalContent = null;
|
||||
this.editingField = null;
|
||||
},
|
||||
hideDefaultModal () {
|
||||
|
@@ -1,7 +1,13 @@
|
||||
<template>
|
||||
<div v-show="isSelected" class="workspace-query-tab column col-12 columns col-gapless">
|
||||
<div class="workspace-query-runner column col-12">
|
||||
<QueryEditor v-if="isSelected" :value.sync="query" />
|
||||
<QueryEditor
|
||||
v-if="isSelected"
|
||||
:auto-focus="true"
|
||||
:value.sync="query"
|
||||
:workspace="workspace"
|
||||
:schema="schema"
|
||||
/>
|
||||
<div class="workspace-query-runner-footer">
|
||||
<div class="workspace-query-buttons">
|
||||
<button
|
||||
@@ -99,6 +105,7 @@ export default {
|
||||
if (!query || this.isQuering) return;
|
||||
this.isQuering = true;
|
||||
this.clearTabData();
|
||||
this.$refs.queryTable.resetSort();
|
||||
|
||||
try { // Query Data
|
||||
const params = {
|
||||
|
@@ -80,6 +80,7 @@
|
||||
|
||||
<script>
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import arrayToFile from '../libs/arrayToFile';
|
||||
import { LONG_TEXT, BLOB } from 'common/fieldTypes';
|
||||
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
|
||||
import WorkspaceQueryTableRow from '@/components/WorkspaceQueryTableRow';
|
||||
@@ -123,8 +124,11 @@ export default {
|
||||
primaryField () {
|
||||
return this.fields.filter(field => ['pri', 'uni'].includes(field.key))[0] || false;
|
||||
},
|
||||
isHardSort () {
|
||||
return this.mode === 'table' && this.localResults.length === 1000;
|
||||
},
|
||||
sortedResults () {
|
||||
if (this.currentSort) {
|
||||
if (this.currentSort && !this.isHardSort) {
|
||||
return [...this.localResults].sort((a, b) => {
|
||||
let modifier = 1;
|
||||
const valA = typeof a[this.currentSort] === 'string' ? a[this.currentSort].toLowerCase() : a[this.currentSort];
|
||||
@@ -192,7 +196,7 @@ export default {
|
||||
},
|
||||
fieldLength (field) {
|
||||
if ([...BLOB, ...LONG_TEXT].includes(field.type)) return null;
|
||||
return field.numLength || field.datePrecision || field.charLength || 0;
|
||||
return field.length;
|
||||
},
|
||||
keyName (key) {
|
||||
switch (key) {
|
||||
@@ -228,7 +232,6 @@ export default {
|
||||
return row[primaryFieldName];
|
||||
},
|
||||
setLocalResults () {
|
||||
this.resetSort();
|
||||
this.localResults = this.resultsWithRows[this.resultsetIndex] && this.resultsWithRows[this.resultsetIndex].rows
|
||||
? this.resultsWithRows[this.resultsetIndex].rows.map(item => {
|
||||
return { ...item, _id: uidGen() };
|
||||
@@ -342,6 +345,9 @@ export default {
|
||||
this.currentSortDir = 'asc';
|
||||
this.currentSort = field;
|
||||
}
|
||||
|
||||
if (this.isHardSort)
|
||||
this.$emit('hard-sort', { field: this.currentSort, dir: this.currentSortDir });
|
||||
},
|
||||
resetSort () {
|
||||
this.currentSort = '';
|
||||
@@ -349,6 +355,20 @@ export default {
|
||||
},
|
||||
selectResultset (index) {
|
||||
this.resultsetIndex = index;
|
||||
},
|
||||
downloadTable (format, filename) {
|
||||
if (!this.sortedResults) return;
|
||||
|
||||
const rows = [...this.sortedResults].map(row => {
|
||||
delete row._id;
|
||||
return row;
|
||||
});
|
||||
|
||||
arrayToFile({
|
||||
type: format,
|
||||
content: rows,
|
||||
filename
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -20,6 +20,7 @@
|
||||
class="editable-field"
|
||||
:value.sync="editingContent"
|
||||
:key-usage="getKeyUsage(cKey)"
|
||||
size="small"
|
||||
@blur="editOFF"
|
||||
/>
|
||||
<template v-else>
|
||||
@@ -157,6 +158,8 @@ export default {
|
||||
typeFormat (val, type, precision) {
|
||||
if (!val) return val;
|
||||
|
||||
type = type.toUpperCase();
|
||||
|
||||
if (DATE.includes(type))
|
||||
return moment(val).isValid() ? moment(val).format('YYYY-MM-DD') : val;
|
||||
|
||||
@@ -254,7 +257,7 @@ export default {
|
||||
return ['gif', 'jpg', 'png', 'bmp', 'ico', 'tif'].includes(this.contentInfo.ext);
|
||||
},
|
||||
foreignKeys () {
|
||||
return this.keyUsage.map(key => key.column);
|
||||
return this.keyUsage.map(key => key.field);
|
||||
},
|
||||
isEditable () {
|
||||
return this.fields ? !!(this.fields[0].schema && this.fields[0].table) : false;
|
||||
@@ -274,7 +277,7 @@ export default {
|
||||
if (field)
|
||||
type = field.type;
|
||||
|
||||
return type;
|
||||
return type.toLowerCase();
|
||||
},
|
||||
getFieldPrecision (cKey) {
|
||||
let length = 0;
|
||||
@@ -313,7 +316,7 @@ export default {
|
||||
editON (event, content, field) {
|
||||
if (!this.isEditable) return;
|
||||
|
||||
const type = this.getFieldType(field);
|
||||
const type = this.getFieldType(field).toUpperCase(); ;
|
||||
this.originalContent = content;
|
||||
this.editingType = type;
|
||||
this.editingField = field;
|
||||
@@ -420,7 +423,7 @@ export default {
|
||||
this.$emit('select-row', event, row);
|
||||
},
|
||||
getKeyUsage (keyName) {
|
||||
return this.keyUsage.find(key => key.column === keyName);
|
||||
return this.keyUsage.find(key => key.field === keyName);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -32,13 +32,30 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-dark btn-sm"
|
||||
@click="showAddModal"
|
||||
>
|
||||
|
||||
<button class="btn btn-dark btn-sm" @click="showAddModal">
|
||||
<span>{{ $t('word.add') }}</span>
|
||||
<i class="mdi mdi-24px mdi-playlist-plus ml-1" />
|
||||
</button>
|
||||
<div class="dropdown export-dropdown">
|
||||
<button
|
||||
:disabled="isQuering"
|
||||
class="btn btn-dark btn-sm dropdown-toggle mr-0 pr-0"
|
||||
tabindex="0"
|
||||
>
|
||||
<span>{{ $t('word.export') }}</span>
|
||||
<i class="mdi mdi-24px mdi-file-export ml-1" />
|
||||
<i class="mdi mdi-24px mdi-menu-down" />
|
||||
</button>
|
||||
<ul class="menu text-left">
|
||||
<li class="menu-item">
|
||||
<a class="c-hand" @click="downloadTable('json')">JSON</a>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a class="c-hand" @click="downloadTable('csv')">CSV</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workspace-query-info">
|
||||
<div v-if="results.length && results[0].rows">
|
||||
@@ -63,6 +80,7 @@
|
||||
mode="table"
|
||||
@update-field="updateField"
|
||||
@delete-selected="deleteSelected"
|
||||
@hard-sort="hardSort"
|
||||
/>
|
||||
</div>
|
||||
<ModalNewTableRow
|
||||
@@ -102,7 +120,8 @@ export default {
|
||||
lastTable: null,
|
||||
isAddModal: false,
|
||||
autorefreshTimer: 0,
|
||||
refreshInterval: null
|
||||
refreshInterval: null,
|
||||
sortParams: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -133,8 +152,10 @@ export default {
|
||||
watch: {
|
||||
table () {
|
||||
if (this.isSelected) {
|
||||
this.sortParams = {};
|
||||
this.getTableData();
|
||||
this.lastTable = this.table;
|
||||
this.$refs.queryTable.resetSort();
|
||||
}
|
||||
},
|
||||
isSelected (val) {
|
||||
@@ -156,7 +177,7 @@ export default {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification'
|
||||
}),
|
||||
async getTableData () {
|
||||
async getTableData (sortParams) {
|
||||
if (!this.table) return;
|
||||
this.isQuering = true;
|
||||
|
||||
@@ -167,7 +188,8 @@ export default {
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
table: this.workspace.breadcrumbs.table
|
||||
table: this.workspace.breadcrumbs.table || this.workspace.breadcrumbs.view,
|
||||
sortParams
|
||||
};
|
||||
|
||||
try { // Table data
|
||||
@@ -188,7 +210,11 @@ export default {
|
||||
return this.table;
|
||||
},
|
||||
reloadTable () {
|
||||
this.getTableData();
|
||||
this.getTableData(this.sortParams);
|
||||
},
|
||||
hardSort (sortParams) {
|
||||
this.sortParams = sortParams;
|
||||
this.getTableData(sortParams);
|
||||
},
|
||||
showAddModal () {
|
||||
this.isAddModal = true;
|
||||
@@ -213,7 +239,21 @@ export default {
|
||||
this.reloadTable();
|
||||
}, this.autorefreshTimer * 1000);
|
||||
}
|
||||
},
|
||||
downloadTable (format) {
|
||||
this.$refs.queryTable.downloadTable(format, this.table);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.export-dropdown {
|
||||
.menu {
|
||||
min-width: 100%;
|
||||
|
||||
.menu-item a:hover {
|
||||
background: $bg-color-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -61,7 +61,31 @@ module.exports = {
|
||||
total: 'Total',
|
||||
table: 'Table',
|
||||
discard: 'Discard',
|
||||
stay: 'Stay'
|
||||
stay: 'Stay',
|
||||
author: 'Author',
|
||||
light: 'Light',
|
||||
dark: 'Dark',
|
||||
autoCompletion: 'Auto Completion',
|
||||
application: 'Application',
|
||||
editor: 'Editor',
|
||||
view: 'View',
|
||||
definer: 'Definer',
|
||||
algorithm: 'Algorithm',
|
||||
trigger: 'Trigger | Triggers',
|
||||
storedRoutine: 'Stored routine | Stored routines',
|
||||
scheduler: 'Scheduler | Schedulers',
|
||||
event: 'Event',
|
||||
parameters: 'Parameters',
|
||||
function: 'Function | Functions',
|
||||
deterministic: 'Deterministic',
|
||||
context: 'Context',
|
||||
export: 'Export',
|
||||
returns: 'Returns',
|
||||
timing: 'Timing',
|
||||
state: 'State',
|
||||
execution: 'Execution',
|
||||
starts: 'Starts',
|
||||
ends: 'Ends'
|
||||
},
|
||||
message: {
|
||||
appWelcome: 'Welcome to Antares SQL Client!',
|
||||
@@ -115,7 +139,40 @@ module.exports = {
|
||||
deleteTable: 'Delete table',
|
||||
emptyCorfirm: 'Do you confirm to empty',
|
||||
unsavedChanges: 'Unsaved changes',
|
||||
discardUnsavedChanges: 'You have some unsaved changes. By leaving this tab these changes will be discarded.'
|
||||
discardUnsavedChanges: 'You have some unsaved changes. By leaving this tab these changes will be discarded.',
|
||||
thereAreNoIndexes: 'There are no indexes',
|
||||
thereAreNoForeign: 'There are no foreign keys',
|
||||
createNewForeign: 'Create new foreign key',
|
||||
referenceTable: 'Ref. table',
|
||||
referenceField: 'Ref. field',
|
||||
foreignFields: 'Foreign fields',
|
||||
invalidDefault: 'Invalid default',
|
||||
onDelete: 'On delete',
|
||||
applicationTheme: 'Application Theme',
|
||||
editorTheme: 'Editor Theme',
|
||||
wrapLongLines: 'Wrap long lines',
|
||||
selectStatement: 'Select statement',
|
||||
triggerStatement: 'Trigger statement',
|
||||
sqlSecurity: 'SQL security',
|
||||
updateOption: 'Update option',
|
||||
deleteView: 'Delete view',
|
||||
createNewView: 'Create new view',
|
||||
deleteTrigger: 'Delete trigger',
|
||||
createNewTrigger: 'Create new trigger',
|
||||
currentUser: 'Current user',
|
||||
routineBody: 'Routine body',
|
||||
dataAccess: 'Data access',
|
||||
thereAreNoParameters: 'There are no parameters',
|
||||
createNewParameter: 'Create new parameter',
|
||||
createNewRoutine: 'Create new stored routine',
|
||||
deleteRoutine: 'Delete stored routine',
|
||||
functionBody: 'Function body',
|
||||
createNewFunction: 'Create new function',
|
||||
deleteFunction: 'Delete function',
|
||||
schedulerBody: 'Scheduler body',
|
||||
createNewScheduler: 'Create new scheduler',
|
||||
deleteScheduler: 'Delete scheduler',
|
||||
preserveOnCompletion: 'Preserve on completion'
|
||||
},
|
||||
// Date and Time
|
||||
short: {
|
||||
|
BIN
src/renderer/images/dark.png
Normal file
BIN
src/renderer/images/dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
8
src/renderer/ipc-api/Application.js
Normal file
8
src/renderer/ipc-api/Application.js
Normal file
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
export default class {
|
||||
static getKey (params) {
|
||||
return ipcRenderer.sendSync('get-key', params);
|
||||
}
|
||||
}
|
@@ -18,8 +18,8 @@ export default class {
|
||||
return ipcRenderer.invoke('delete-database', params);
|
||||
}
|
||||
|
||||
static getStructure (uid) {
|
||||
return ipcRenderer.invoke('get-structure', uid);
|
||||
static getStructure (params) {
|
||||
return ipcRenderer.invoke('get-structure', params);
|
||||
}
|
||||
|
||||
static getCollations (uid) {
|
||||
|
20
src/renderer/ipc-api/Functions.js
Normal file
20
src/renderer/ipc-api/Functions.js
Normal file
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
export default class {
|
||||
static getFunctionInformations (params) {
|
||||
return ipcRenderer.invoke('get-function-informations', params);
|
||||
}
|
||||
|
||||
static dropFunction (params) {
|
||||
return ipcRenderer.invoke('drop-function', params);
|
||||
}
|
||||
|
||||
static alterFunction (params) {
|
||||
return ipcRenderer.invoke('alter-function', params);
|
||||
}
|
||||
|
||||
static createFunction (params) {
|
||||
return ipcRenderer.invoke('create-function', params);
|
||||
}
|
||||
}
|
20
src/renderer/ipc-api/Routines.js
Normal file
20
src/renderer/ipc-api/Routines.js
Normal file
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
export default class {
|
||||
static getRoutineInformations (params) {
|
||||
return ipcRenderer.invoke('get-routine-informations', params);
|
||||
}
|
||||
|
||||
static dropRoutine (params) {
|
||||
return ipcRenderer.invoke('drop-routine', params);
|
||||
}
|
||||
|
||||
static alterRoutine (params) {
|
||||
return ipcRenderer.invoke('alter-routine', params);
|
||||
}
|
||||
|
||||
static createRoutine (params) {
|
||||
return ipcRenderer.invoke('create-routine', params);
|
||||
}
|
||||
}
|
20
src/renderer/ipc-api/Schedulers.js
Normal file
20
src/renderer/ipc-api/Schedulers.js
Normal file
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
export default class {
|
||||
static getSchedulerInformations (params) {
|
||||
return ipcRenderer.invoke('get-scheduler-informations', params);
|
||||
}
|
||||
|
||||
static dropScheduler (params) {
|
||||
return ipcRenderer.invoke('drop-scheduler', params);
|
||||
}
|
||||
|
||||
static alterScheduler (params) {
|
||||
return ipcRenderer.invoke('alter-scheduler', params);
|
||||
}
|
||||
|
||||
static createScheduler (params) {
|
||||
return ipcRenderer.invoke('create-scheduler', params);
|
||||
}
|
||||
}
|
20
src/renderer/ipc-api/Triggers.js
Normal file
20
src/renderer/ipc-api/Triggers.js
Normal file
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
export default class {
|
||||
static getTriggerInformations (params) {
|
||||
return ipcRenderer.invoke('get-trigger-informations', params);
|
||||
}
|
||||
|
||||
static dropTrigger (params) {
|
||||
return ipcRenderer.invoke('drop-trigger', params);
|
||||
}
|
||||
|
||||
static alterTrigger (params) {
|
||||
return ipcRenderer.invoke('alter-trigger', params);
|
||||
}
|
||||
|
||||
static createTrigger (params) {
|
||||
return ipcRenderer.invoke('create-trigger', params);
|
||||
}
|
||||
}
|
8
src/renderer/ipc-api/Users.js
Normal file
8
src/renderer/ipc-api/Users.js
Normal file
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
export default class {
|
||||
static getUsers (params) {
|
||||
return ipcRenderer.invoke('get-users', params);
|
||||
}
|
||||
}
|
20
src/renderer/ipc-api/Views.js
Normal file
20
src/renderer/ipc-api/Views.js
Normal file
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
export default class {
|
||||
static getViewInformations (params) {
|
||||
return ipcRenderer.invoke('get-view-informations', params);
|
||||
}
|
||||
|
||||
static dropView (params) {
|
||||
return ipcRenderer.invoke('drop-view', params);
|
||||
}
|
||||
|
||||
static alterView (params) {
|
||||
return ipcRenderer.invoke('alter-view', params);
|
||||
}
|
||||
|
||||
static createView (params) {
|
||||
return ipcRenderer.invoke('create-view', params);
|
||||
}
|
||||
}
|
37
src/renderer/libs/arrayToFile.js
Normal file
37
src/renderer/libs/arrayToFile.js
Normal file
@@ -0,0 +1,37 @@
|
||||
const arrayToFile = args => {
|
||||
let mime;
|
||||
let content;
|
||||
|
||||
switch (args.type) {
|
||||
case 'csv': {
|
||||
mime = 'text/csv';
|
||||
const csv = [];
|
||||
|
||||
if (args.content.length)
|
||||
csv.push(Object.keys(args.content[0]).join(';'));
|
||||
|
||||
for (const row of args.content)
|
||||
csv.push(Object.values(row).map(col => typeof col === 'string' ? `"${col}"` : col).join(';'));
|
||||
|
||||
content = csv.join('\n');
|
||||
break;
|
||||
}
|
||||
case 'json':
|
||||
mime = 'application/json';
|
||||
content = JSON.stringify(args.content, null, 3);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const file = new Blob([content], { type: mime });
|
||||
const downloadLink = document.createElement('a');
|
||||
downloadLink.download = `${args.filename}.${args.type}`;
|
||||
downloadLink.href = window.URL.createObjectURL(file);
|
||||
downloadLink.style.display = 'none';
|
||||
document.body.appendChild(downloadLink);
|
||||
downloadLink.click();
|
||||
downloadLink.remove();
|
||||
};
|
||||
|
||||
export default arrayToFile;
|
2275
src/renderer/libs/ext-language_tools.js
Normal file
2275
src/renderer/libs/ext-language_tools.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -180,6 +180,12 @@ body {
|
||||
|
||||
.form-select {
|
||||
cursor: pointer;
|
||||
|
||||
&.small-select {
|
||||
height: 1rem;
|
||||
font-size: 0.7rem;
|
||||
padding: 1px 0.4rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-select,
|
||||
@@ -206,6 +212,7 @@ body {
|
||||
|
||||
.input-group .input-group-addon {
|
||||
border-color: #3f3f3f;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.menu {
|
||||
@@ -219,7 +226,7 @@ body {
|
||||
}
|
||||
|
||||
.accordion-body {
|
||||
max-height: 500rem !important;
|
||||
max-height: 5000rem !important;
|
||||
}
|
||||
|
||||
.btn.loading {
|
||||
@@ -228,3 +235,7 @@ body {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
color: $body-font-color;
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import VuexPersist from 'vuex-persist';
|
||||
|
||||
import application from './modules/application.store';
|
||||
import settings from './modules/settings.store';
|
||||
@@ -12,15 +11,6 @@ import notifications from './modules/notifications.store';
|
||||
|
||||
import ipcUpdates from './plugins/ipcUpdates';
|
||||
|
||||
const vuexLocalStorage = new VuexPersist({
|
||||
key: 'application', // The key to store the state on in the storage provider.
|
||||
storage: window.localStorage,
|
||||
reducer: state => ({
|
||||
connections: state.connections,
|
||||
settings: state.settings
|
||||
})
|
||||
});
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
export default new Vuex.Store({
|
||||
@@ -33,7 +23,6 @@ export default new Vuex.Store({
|
||||
notifications
|
||||
},
|
||||
plugins: [
|
||||
vuexLocalStorage.plugin,
|
||||
ipcUpdates
|
||||
]
|
||||
});
|
||||
|
@@ -1,10 +1,18 @@
|
||||
'use strict';
|
||||
import Store from 'electron-store';
|
||||
import Application from '../../ipc-api/Application';
|
||||
const key = Application.getKey();
|
||||
|
||||
const persistentStore = new Store({
|
||||
name: 'connections',
|
||||
encryptionKey: key
|
||||
});
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
strict: true,
|
||||
state: {
|
||||
connections: []
|
||||
connections: persistentStore.get('connections') || []
|
||||
},
|
||||
getters: {
|
||||
getConnections: state => state.connections,
|
||||
@@ -20,9 +28,11 @@ export default {
|
||||
mutations: {
|
||||
ADD_CONNECTION (state, connection) {
|
||||
state.connections.push(connection);
|
||||
persistentStore.set('connections', state.connections);
|
||||
},
|
||||
DELETE_CONNECTION (state, connection) {
|
||||
state.connections = state.connections.filter(el => el.uid !== connection.uid);
|
||||
persistentStore.set('connections', state.connections);
|
||||
},
|
||||
EDIT_CONNECTION (state, connection) {
|
||||
const editedConnections = state.connections.map(conn => {
|
||||
@@ -31,9 +41,11 @@ export default {
|
||||
});
|
||||
state.connections = editedConnections;
|
||||
state.selected_conection = {};
|
||||
persistentStore.set('connections', state.connections);
|
||||
},
|
||||
UPDATE_CONNECTIONS (state, connections) {
|
||||
state.connections = connections;
|
||||
persistentStore.set('connections', state.connections);
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
|
@@ -1,29 +1,53 @@
|
||||
'use strict';
|
||||
import i18n from '@/i18n';
|
||||
import Store from 'electron-store';
|
||||
const persistentStore = new Store({ name: 'settings' });
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
strict: true,
|
||||
state: {
|
||||
locale: 'en-US',
|
||||
explorebar_size: null,
|
||||
notifications_timeout: 5
|
||||
locale: persistentStore.get('locale') || 'en-US',
|
||||
explorebar_size: persistentStore.get('explorebar_size') || null,
|
||||
notifications_timeout: persistentStore.get('notifications_timeout') || 5,
|
||||
auto_complete: persistentStore.get('auto_complete') || true,
|
||||
line_wrap: persistentStore.get('line_wrap') || true,
|
||||
application_theme: persistentStore.get('application_theme') || 'dark',
|
||||
editor_theme: persistentStore.get('editor_theme') || 'twilight'
|
||||
},
|
||||
getters: {
|
||||
getLocale: state => state.locale,
|
||||
getExplorebarSize: state => state.explorebar_size,
|
||||
getNotificationsTimeout: state => state.notifications_timeout
|
||||
getNotificationsTimeout: state => state.notifications_timeout,
|
||||
getAutoComplete: state => state.auto_complete,
|
||||
getLineWrap: state => state.line_wrap,
|
||||
getApplicationTheme: state => state.application_theme,
|
||||
getEditorTheme: state => state.editor_theme
|
||||
},
|
||||
mutations: {
|
||||
SET_LOCALE (state, locale) {
|
||||
state.locale = locale;
|
||||
i18n.locale = locale;
|
||||
persistentStore.set('locale', state.locale);
|
||||
},
|
||||
SET_NOTIFICATIONS_TIMEOUT (state, timeout) {
|
||||
state.notifications_timeout = timeout;
|
||||
persistentStore.set('notifications_timeout', state.notifications_timeout);
|
||||
},
|
||||
SET_AUTO_COMPLETE (state, val) {
|
||||
state.auto_complete = val;
|
||||
persistentStore.set('auto_complete', state.auto_complete);
|
||||
},
|
||||
SET_LINE_WRAP (state, val) {
|
||||
state.line_wrap = val;
|
||||
persistentStore.set('line_wrap', state.line_wrap);
|
||||
},
|
||||
SET_EXPLOREBAR_SIZE (state, size) {
|
||||
state.explorebar_size = size;
|
||||
persistentStore.set('explorebar_size', state.explorebar_size);
|
||||
},
|
||||
SET_EDITOR_THEME (state, theme) {
|
||||
state.editor_theme = theme;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
@@ -35,6 +59,15 @@ export default {
|
||||
},
|
||||
changeExplorebarSize ({ commit }, size) {
|
||||
commit('SET_EXPLOREBAR_SIZE', size);
|
||||
},
|
||||
changeAutoComplete ({ commit }, val) {
|
||||
commit('SET_AUTO_COMPLETE', val);
|
||||
},
|
||||
changeLineWrap ({ commit }, val) {
|
||||
commit('SET_LINE_WRAP', val);
|
||||
},
|
||||
changeEditorTheme ({ commit }, theme) {
|
||||
commit('SET_EDITOR_THEME', theme);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
import Connection from '@/ipc-api/Connection';
|
||||
import Database from '@/ipc-api/Database';
|
||||
import Users from '@/ipc-api/Users';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
const tabIndex = [];
|
||||
let lastBreadcrumbs = {};
|
||||
@@ -39,6 +40,9 @@ export default {
|
||||
.filter(workspace => workspace.connected)
|
||||
.map(workspace => workspace.uid);
|
||||
},
|
||||
getLoadedSchemas: state => uid => {
|
||||
return state.workspaces.find(workspace => workspace.uid === uid).loaded_schemas;
|
||||
},
|
||||
isUnsavedDiscardModal: state => {
|
||||
return state.is_unsaved_discard_modal;
|
||||
}
|
||||
@@ -64,6 +68,8 @@ export default {
|
||||
? {
|
||||
...workspace,
|
||||
structure: {},
|
||||
breadcrumbs: {},
|
||||
loaded_schemas: new Set(),
|
||||
connected: false
|
||||
}
|
||||
: workspace);
|
||||
@@ -76,6 +82,19 @@ export default {
|
||||
}
|
||||
: workspace);
|
||||
},
|
||||
REFRESH_SCHEMA (state, { uid, schema, schemaElements }) {
|
||||
state.workspaces = state.workspaces.map(workspace => {
|
||||
if (workspace.uid === uid) {
|
||||
const schemaIndex = workspace.structure.findIndex(s => s.name === schema);
|
||||
|
||||
if (schemaIndex !== -1)
|
||||
workspace.structure[schemaIndex] = schemaElements;
|
||||
else
|
||||
workspace.structure.push(schemaElements);
|
||||
}
|
||||
return workspace;
|
||||
});
|
||||
},
|
||||
REFRESH_COLLATIONS (state, { uid, collations }) {
|
||||
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid
|
||||
? {
|
||||
@@ -100,6 +119,14 @@ export default {
|
||||
}
|
||||
: workspace);
|
||||
},
|
||||
REFRESH_USERS (state, { uid, users }) {
|
||||
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid
|
||||
? {
|
||||
...workspace,
|
||||
users
|
||||
}
|
||||
: workspace);
|
||||
},
|
||||
ADD_WORKSPACE (state, workspace) {
|
||||
state.workspaces.push(workspace);
|
||||
},
|
||||
@@ -111,11 +138,10 @@ export default {
|
||||
}
|
||||
: workspace);
|
||||
},
|
||||
NEW_TAB (state, uid) {
|
||||
NEW_TAB (state, { uid, tab }) {
|
||||
tabIndex[uid] = tabIndex[uid] ? ++tabIndex[uid] : 1;
|
||||
|
||||
const newTab = {
|
||||
uid: uidGen('T'),
|
||||
uid: tab,
|
||||
index: tabIndex[uid],
|
||||
selected: false,
|
||||
type: 'query',
|
||||
@@ -190,6 +216,13 @@ export default {
|
||||
},
|
||||
SET_PENDING_BREADCRUMBS (state, payload) {
|
||||
state.pending_breadcrumbs = payload;
|
||||
},
|
||||
ADD_LOADED_SCHEMA (state, payload) {
|
||||
state.workspaces = state.workspaces.map(workspace => {
|
||||
if (workspace.uid === payload.uid)
|
||||
workspace.loaded_schemas.add(payload.schema);
|
||||
return workspace;
|
||||
});
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
@@ -222,15 +255,17 @@ export default {
|
||||
dispatch('refreshCollations', connection.uid);
|
||||
dispatch('refreshVariables', connection.uid);
|
||||
dispatch('refreshEngines', connection.uid);
|
||||
dispatch('refreshUsers', connection.uid);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
dispatch('notifications/addNotification', { status: 'error', message: err.stack }, { root: true });
|
||||
}
|
||||
},
|
||||
async refreshStructure ({ dispatch, commit }, uid) {
|
||||
async refreshStructure ({ dispatch, commit, getters }, uid) {
|
||||
try {
|
||||
const { status, response } = await Database.getStructure(uid);
|
||||
const { status, response } = await Database.getStructure({ uid, schemas: getters.getLoadedSchemas(uid) });
|
||||
|
||||
if (status === 'error')
|
||||
dispatch('notifications/addNotification', { status, message: response }, { root: true });
|
||||
else
|
||||
@@ -240,6 +275,18 @@ export default {
|
||||
dispatch('notifications/addNotification', { status: 'error', message: err.stack }, { root: true });
|
||||
}
|
||||
},
|
||||
async refreshSchema ({ dispatch, commit }, { uid, schema }) {
|
||||
try {
|
||||
const { status, response } = await Database.getStructure({ uid, schemas: new Set([schema]) });
|
||||
if (status === 'error')
|
||||
dispatch('notifications/addNotification', { status, message: response }, { root: true });
|
||||
else
|
||||
commit('REFRESH_SCHEMA', { uid, schema, schemaElements: response.find(_schema => _schema.name === schema) });
|
||||
}
|
||||
catch (err) {
|
||||
dispatch('notifications/addNotification', { status: 'error', message: err.stack }, { root: true });
|
||||
}
|
||||
},
|
||||
async refreshCollations ({ dispatch, commit }, uid) {
|
||||
try {
|
||||
const { status, response } = await Database.getCollations(uid);
|
||||
@@ -276,6 +323,18 @@ export default {
|
||||
dispatch('notifications/addNotification', { status: 'error', message: err.stack }, { root: true });
|
||||
}
|
||||
},
|
||||
async refreshUsers ({ dispatch, commit }, uid) {
|
||||
try {
|
||||
const { status, response } = await Users.getUsers(uid);
|
||||
if (status === 'error')
|
||||
dispatch('notifications/addNotification', { status, message: response }, { root: true });
|
||||
else
|
||||
commit('REFRESH_USERS', { uid, users: response });
|
||||
}
|
||||
catch (err) {
|
||||
dispatch('notifications/addNotification', { status: 'error', message: err.stack }, { root: true });
|
||||
}
|
||||
},
|
||||
removeConnected ({ commit }, uid) {
|
||||
Connection.disconnect(uid);
|
||||
commit('REMOVE_CONNECTED', uid);
|
||||
@@ -286,22 +345,13 @@ export default {
|
||||
uid,
|
||||
connected: false,
|
||||
selected_tab: 0,
|
||||
tabs: [{
|
||||
uid: 'data',
|
||||
type: 'table',
|
||||
fields: [],
|
||||
keyUsage: []
|
||||
},
|
||||
{
|
||||
uid: 'prop',
|
||||
type: 'table',
|
||||
fields: [],
|
||||
keyUsage: []
|
||||
}],
|
||||
tabs: [],
|
||||
structure: {},
|
||||
variables: [],
|
||||
collations: [],
|
||||
breadcrumbs: {}
|
||||
users: [],
|
||||
breadcrumbs: {},
|
||||
loaded_schemas: new Set()
|
||||
};
|
||||
|
||||
commit('ADD_WORKSPACE', workspace);
|
||||
@@ -323,7 +373,9 @@ export default {
|
||||
table: null,
|
||||
trigger: null,
|
||||
procedure: null,
|
||||
scheduler: null
|
||||
function: null,
|
||||
scheduler: null,
|
||||
view: null
|
||||
};
|
||||
|
||||
const hasLastChildren = Object.keys(lastBreadcrumbs).filter(b => b !== 'schema').some(b => lastBreadcrumbs[b]);
|
||||
@@ -336,9 +388,15 @@ export default {
|
||||
|
||||
commit('CHANGE_BREADCRUMBS', { uid: getters.getSelected, breadcrumbs: { ...breadcrumbsObj, ...payload } });
|
||||
lastBreadcrumbs = { ...breadcrumbsObj, ...payload };
|
||||
|
||||
if (payload.schema)
|
||||
commit('ADD_LOADED_SCHEMA', { uid: getters.getSelected, schema: payload.schema });
|
||||
},
|
||||
newTab ({ commit }, uid) {
|
||||
commit('NEW_TAB', uid);
|
||||
const tab = uidGen('T');
|
||||
|
||||
commit('NEW_TAB', { uid, tab });
|
||||
commit('SELECT_TAB', { uid, tab });
|
||||
},
|
||||
removeTab ({ commit }, payload) {
|
||||
commit('REMOVE_TAB', payload);
|
||||
|
@@ -1,12 +0,0 @@
|
||||
import { functions } from '@/suggestions/sql/sql-functions';
|
||||
import { keywords } from '@/suggestions/sql/sql-keywords';
|
||||
import { operators } from '@/suggestions/sql/sql-operators';
|
||||
import { variables } from '@/suggestions/sql/sql-variables';
|
||||
|
||||
export const completionItemProvider = (monaco) => {
|
||||
return {
|
||||
provideCompletionItems () {
|
||||
return { suggestions: [...functions(monaco), ...keywords(monaco), ...operators(monaco), ...variables(monaco)] };
|
||||
}
|
||||
};
|
||||
};
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,142 +0,0 @@
|
||||
export const operators = (monaco) => {
|
||||
return [{
|
||||
label: 'ALL',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'ALL'
|
||||
},
|
||||
{
|
||||
label: 'AND',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'AND'
|
||||
},
|
||||
{
|
||||
label: 'ANY',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'ANY'
|
||||
},
|
||||
{
|
||||
label: 'BETWEEN',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'BETWEEN'
|
||||
},
|
||||
{
|
||||
label: 'EXISTS',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'EXISTS'
|
||||
},
|
||||
{
|
||||
label: 'IN',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'IN'
|
||||
},
|
||||
{
|
||||
label: 'LIKE',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'LIKE'
|
||||
},
|
||||
{
|
||||
label: 'NOT',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'NOT'
|
||||
},
|
||||
{
|
||||
label: 'OR',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'OR'
|
||||
},
|
||||
{
|
||||
label: 'SOME',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'SOME'
|
||||
},
|
||||
{
|
||||
label: 'EXCEPT',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'EXCEPT'
|
||||
},
|
||||
{
|
||||
label: 'INTERSECT',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'INTERSECT'
|
||||
},
|
||||
{
|
||||
label: 'UNION',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'UNION'
|
||||
},
|
||||
{
|
||||
label: 'APPLY',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'APPLY'
|
||||
},
|
||||
{
|
||||
label: 'CROSS',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'CROSS'
|
||||
},
|
||||
{
|
||||
label: 'FULL',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'FULL'
|
||||
},
|
||||
{
|
||||
label: 'INNER',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'INNER'
|
||||
},
|
||||
{
|
||||
label: 'JOIN',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'JOIN'
|
||||
},
|
||||
{
|
||||
label: 'LEFT',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'LEFT'
|
||||
},
|
||||
{
|
||||
label: 'OUTER',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'OUTER'
|
||||
},
|
||||
{
|
||||
label: 'RIGHT',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'RIGHT'
|
||||
},
|
||||
{
|
||||
label: 'CONTAINS',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'CONTAINS'
|
||||
},
|
||||
{
|
||||
label: 'FREETEXT',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'FREETEXT'
|
||||
},
|
||||
{
|
||||
label: 'IS',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'IS'
|
||||
},
|
||||
{
|
||||
label: 'NULL',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'NULL'
|
||||
},
|
||||
{
|
||||
label: 'PIVOT',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'PIVOT'
|
||||
},
|
||||
{
|
||||
label: 'UNPIVOT',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'UNPIVOT'
|
||||
},
|
||||
{
|
||||
label: 'MATCHED',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'MATCHED'
|
||||
}];
|
||||
};
|
@@ -1,172 +0,0 @@
|
||||
export const variables = (monaco) => {
|
||||
return [{
|
||||
label: '@@DATEFIRST',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@DATEFIRST'
|
||||
},
|
||||
{
|
||||
label: '@@DBTS',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@DBTS'
|
||||
},
|
||||
{
|
||||
label: '@@LANGID',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@LANGID'
|
||||
},
|
||||
{
|
||||
label: '@@LANGUAGE',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@LANGUAGE'
|
||||
},
|
||||
{
|
||||
label: '@@LOCK_TIMEOUT',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@LOCK_TIMEOUT'
|
||||
},
|
||||
{
|
||||
label: '@@MAX_CONNECTIONS',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@MAX_CONNECTIONS'
|
||||
},
|
||||
{
|
||||
label: '@@MAX_PRECISION',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@MAX_PRECISION'
|
||||
},
|
||||
{
|
||||
label: '@@NESTLEVEL',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@NESTLEVEL'
|
||||
},
|
||||
{
|
||||
label: '@@OPTIONS',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@OPTIONS'
|
||||
},
|
||||
{
|
||||
label: '@@REMSERVER',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@REMSERVER'
|
||||
},
|
||||
{
|
||||
label: '@@SERVERNAME',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@SERVERNAME'
|
||||
},
|
||||
{
|
||||
label: '@@SERVICENAME',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@SERVICENAME'
|
||||
},
|
||||
{
|
||||
label: '@@SPID',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@SPID'
|
||||
},
|
||||
{
|
||||
label: '@@TEXTSIZE',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@TEXTSIZE'
|
||||
},
|
||||
{
|
||||
label: '@@VERSION',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@VERSION'
|
||||
},
|
||||
{
|
||||
label: '@@CURSOR_ROWS',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@CURSOR_ROWS'
|
||||
},
|
||||
{
|
||||
label: '@@FETCH_STATUS',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@FETCH_STATUS'
|
||||
},
|
||||
{
|
||||
label: '@@DATEFIRST',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@DATEFIRST'
|
||||
},
|
||||
{
|
||||
label: '@@PROCID',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@PROCID'
|
||||
},
|
||||
{
|
||||
label: '@@ERROR',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@ERROR'
|
||||
},
|
||||
{
|
||||
label: '@@IDENTITY',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@IDENTITY'
|
||||
},
|
||||
{
|
||||
label: '@@ROWCOUNT',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@ROWCOUNT'
|
||||
},
|
||||
{
|
||||
label: '@@TRANCOUNT',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@TRANCOUNT'
|
||||
},
|
||||
{
|
||||
label: '@@CONNECTIONS',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@CONNECTIONS'
|
||||
},
|
||||
{
|
||||
label: '@@CPU_BUSY',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@CPU_BUSY'
|
||||
},
|
||||
{
|
||||
label: '@@IDLE',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@IDLE'
|
||||
},
|
||||
{
|
||||
label: '@@IO_BUSY',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@IO_BUSY'
|
||||
},
|
||||
{
|
||||
label: '@@PACKET_ERRORS',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@PACKET_ERRORS'
|
||||
},
|
||||
{
|
||||
label: '@@PACK_RECEIVED',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@PACK_RECEIVED'
|
||||
},
|
||||
{
|
||||
label: '@@PACK_SENT',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@PACK_SENT'
|
||||
},
|
||||
{
|
||||
label: '@@TIMETICKS',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@TIMETICKS'
|
||||
},
|
||||
{
|
||||
label: '@@TOTAL_ERRORS',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@TOTAL_ERRORS'
|
||||
},
|
||||
{
|
||||
label: '@@TOTAL_READ',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@TOTAL_READ'
|
||||
},
|
||||
{
|
||||
label: '@@TOTAL_WRITE',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@TOTAL_WRITE'
|
||||
}];
|
||||
};
|
@@ -1,12 +1,8 @@
|
||||
const webpack = require('webpack');
|
||||
const MonacoEditorPlugin = require('monaco-editor-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
stats: 'errors-warnings',
|
||||
plugins: [
|
||||
new MonacoEditorPlugin({
|
||||
languages: ['sql']
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
PACKAGE_VERSION: JSON.stringify(require('./package.json').version)
|
||||
|
Reference in New Issue
Block a user