mirror of
https://github.com/Fabio286/antares.git
synced 2025-06-05 21:59:22 +02:00
Compare commits
325 Commits
v0.0.1-alp
...
v0.0.18
| Author | SHA1 | Date | |
|---|---|---|---|
| 4494e637f7 | |||
| 219da0aba4 | |||
| 7e8167154f | |||
| 3aa2159a1a | |||
| 76d92cd106 | |||
| c8545a250b | |||
| dbab06fcb8 | |||
| b54fefbf25 | |||
| 9a1bf32128 | |||
| 110b0b414c | |||
| 2f58007af4 | |||
| 9b60bfff8d | |||
| 3b37b7432e | |||
| 7c4ca999ce | |||
| 94c4952319 | |||
| 014257147e | |||
| 970de4962b | |||
| b5a828309f | |||
| 5b21d17f3a | |||
| 2c6e35288f | |||
| bcadac6e95 | |||
|
|
18a93ef1aa | ||
| 6c62052b47 | |||
| 9d5ebefdce | |||
| 34ebc6b72d | |||
| 288ff4c1a1 | |||
| a176174b8d | |||
| 0f69d1dbb7 | |||
| 0386bbac50 | |||
| b0576acdf6 | |||
| 9a190854fe | |||
| 2aace28e80 | |||
| 02c03e3d26 | |||
| a0d85520fb | |||
| ede6fe81ce | |||
| 4e72bb1587 | |||
| 15417e8a77 | |||
| 88ab7c5a62 | |||
| 5940b0b842 | |||
| 574d493908 | |||
| af96647603 | |||
| ab70ff239e | |||
| bacf458936 | |||
| 379d6c169a | |||
|
|
36352f8028 | ||
| 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 | |||
| 7470bddd70 | |||
| 33d1fa2290 | |||
| a4122b4eaa | |||
| e6602d1bfa | |||
| f8cf90a89e | |||
| 41505bde65 | |||
| 8ebc3bce92 | |||
| 45e9cdc591 | |||
| 3cbfc0e148 | |||
| a47e9e1b1f | |||
| e95d29c7c3 | |||
| e954f04828 | |||
|
|
85c800f85b | ||
|
|
e0482244d7 | ||
| 27769f204f | |||
| dfb24c65f3 | |||
| 0fe71572a5 | |||
| db577bfef0 | |||
| 0805b96a75 | |||
| e49823f5c4 | |||
| 76351005b4 | |||
| 3e5770f7de | |||
| 242ddec744 | |||
| 07654039b6 | |||
| 249926b8e0 | |||
| ae47a978c1 | |||
|
|
bc53b0b332 | ||
| d4175bcbda | |||
| c9ba2e5962 | |||
| 2e49d86677 | |||
| c393f86947 | |||
| a0b96aa06c | |||
| 2dc16e8ea8 | |||
| ee183886f6 | |||
| cef6f681c8 | |||
| ea9b489f5f | |||
| 1658432fd3 | |||
| a8cd17748f | |||
| 580105e9f3 | |||
| 0626f6f775 | |||
| 12f5e479f3 | |||
| 04804b07c7 | |||
| 053418ee90 | |||
| 426628f268 | |||
| d4ecaf65e5 | |||
| 27d114beef | |||
| 936de04cd3 | |||
| b7c779eef6 | |||
| d560c384f5 | |||
| 9ecd88870d | |||
| 07d1e82325 | |||
| ce25cd0a31 | |||
| 319d9beef1 | |||
|
|
4272efe73b | ||
| ed5cf0a8e4 | |||
| c70e5b422c | |||
| 0bf2c8dc9d | |||
| d563cec70d | |||
| 6ee4ef4b8b | |||
| 5fc4df426c | |||
| 799a5fef5b | |||
| 54717e1f6a | |||
| 4288a1fd33 | |||
| 52449e0420 | |||
| 96f38297c1 | |||
| 3e737cba62 | |||
| 3d0a83f2cf | |||
| c1cdd03938 | |||
| 437e41bff0 | |||
|
|
4516783b13 | ||
| 43c7072c1c | |||
| 530d1bd43f | |||
| 530907d097 | |||
| ac4941fa5e | |||
| 5334a44271 | |||
| 4991291c2c | |||
| 2554444322 | |||
| cff4f537de | |||
| 12fbe8c1a0 | |||
| 10b426b90b | |||
| b29e07c3b7 | |||
| ffef312b44 | |||
|
|
264829bec0 | ||
|
|
89c3dc9fed | ||
|
|
fe3d741601 | ||
| 1b04b216b2 | |||
| 78965d23e3 | |||
| 9b76c8eae0 | |||
|
|
c94ae8c9bc | ||
| ad0bad8486 | |||
| 8e71f42a28 | |||
| 967a0aa6e3 | |||
| ddc7d1ea26 | |||
| 4684b4114b | |||
| 3e08ba221d | |||
| 1d87ca959f | |||
| 023c6a633a | |||
| 48f77bae01 | |||
| 3385744260 | |||
| 86aec4f5e4 | |||
| ba73d677b5 | |||
| 51ccce3da4 | |||
| a1a6f51f2f | |||
| 801a0de186 | |||
| 264de9c568 | |||
| 8390f8aa55 | |||
| af7c0e90b8 | |||
|
|
da33e77361 | ||
|
|
a4841ab63b | ||
| de3f36a3fe | |||
| 8dc74ef2c3 | |||
| 256ec76588 | |||
| 196a3e0185 | |||
| bc54fef0aa | |||
| a5b478e53d | |||
|
|
2e235ad2fe | ||
|
|
950bb17b1e | ||
| 3a6ea76b93 | |||
| d7ed00f4a3 | |||
| fd6d5177ef | |||
| 9599b43f78 | |||
| 2cfb223ff6 | |||
| 69def94c88 | |||
| e8141b6321 | |||
| 0b6a188d19 | |||
| dca625fe5a | |||
|
|
a4b94bc19c | ||
| 744728a14f | |||
| 6d0724dc90 | |||
| 59e4a79f42 | |||
| 7bc10092fe | |||
| eb348b3095 | |||
| 3c6e818ba0 | |||
| 2f1dfdc654 | |||
| 128a6cd9e8 | |||
| 5473858323 | |||
| 7651d05b37 | |||
| c89c1ce83c | |||
| 771f8a2d68 | |||
| 13b0816837 | |||
| a15e6249e1 | |||
| bbde2bd994 | |||
| 949f7add8f | |||
| 968ec1edf7 | |||
| a9d3a57281 | |||
|
|
f787439009 | ||
| b03d461e21 | |||
| 5c05e3e9e9 | |||
| 0089c0cbac | |||
| 4fd72ec9e7 | |||
| 712fe9f00d | |||
| 092e8a0732 | |||
| 70908eb076 | |||
| f8a0783769 | |||
|
|
cdf964ef07 | ||
| 413b56916c | |||
| acd3310228 | |||
|
|
e014f81a9c | ||
| 530361144e | |||
| d69e411581 | |||
| 9a3a0513e2 | |||
|
|
be8fa96c93 | ||
| 587116bd20 | |||
|
|
145d1dd1ad | ||
| 457410b65a | |||
|
|
b183eacae1 | ||
| 76cafdb69a | |||
|
|
2d63ddb9c7 | ||
| f12f00dbb7 | |||
| 1ecb6d892c | |||
| fcd83f35d8 | |||
|
|
7a4d8286a6 | ||
|
|
dd5ec2c661 | ||
| 3f0e5d3512 | |||
| ac01511c10 | |||
| 4a83ae7e75 | |||
| 60132c94a1 | |||
| fdf5bef5ad | |||
| 67f55fbeb9 | |||
|
|
fd5a8548c7 | ||
| 425ecf838d | |||
| 1a8a49eceb | |||
|
|
e9fffcc37e | ||
| bba7c4af6f | |||
|
|
376d74c7dc | ||
| 307a32aff6 | |||
| d1aaad276b | |||
|
|
b23b4f18b9 | ||
| 334cfa9047 | |||
|
|
2f6d16c730 | ||
| b8e09e7003 | |||
| 9caf424331 | |||
|
|
9e5e545478 | ||
|
|
c9ab731bb4 | ||
| 57832c43aa | |||
|
|
184363369b | ||
| b221dd12ff | |||
|
|
1324464aa3 | ||
| 187b4f50f9 | |||
| 262a476f50 | |||
|
|
f00c2600e5 | ||
| 75a7db9c05 | |||
| 50cd852d01 | |||
| bb8cfa533b | |||
|
|
1ddc8b5dca | ||
| 8a4c628128 | |||
| 0076f146fa | |||
| 098d12f462 | |||
| b619285d94 | |||
|
|
a5fed1bb64 | ||
| 55ec03bd8e | |||
|
|
d1977fbd75 | ||
| 3c20c0733c | |||
| 1d4a353d5c | |||
| db71777cbc | |||
| f350fe8203 | |||
| 28c3f87dd8 | |||
| cc8dbb8df7 | |||
| 85ac1bc85f | |||
| baea5c26e2 |
@@ -21,7 +21,7 @@
|
||||
],
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"windows"
|
||||
"unix"
|
||||
],
|
||||
"brace-style": [
|
||||
"error",
|
||||
|
||||
6
.gitattributes
vendored
Normal file
6
.gitattributes
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
* text eol=lf
|
||||
*.jpg binary
|
||||
*.png binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.icns binary
|
||||
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [fabio286]
|
||||
patreon: fabio286
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: ['https://paypal.me/fabiodistasio']
|
||||
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
57
.github/workflows/codeql-analysis.yml
vendored
Normal file
57
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [master]
|
||||
schedule:
|
||||
- cron: '0 15 * * 0'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Override automatic language detection by changing the below list
|
||||
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
|
||||
language: ['javascript']
|
||||
# Learn more...
|
||||
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,4 +6,4 @@ thumbs.db
|
||||
.vscode
|
||||
TODO.md
|
||||
*.txt
|
||||
dev-app-update.yml
|
||||
package-lock.json
|
||||
14
.stylelintrc
Normal file
14
.stylelintrc
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": [
|
||||
"stylelint-config-standard"
|
||||
],
|
||||
"fix": true,
|
||||
"formatter": "verbose",
|
||||
"plugins": [
|
||||
"stylelint-scss"
|
||||
],
|
||||
"rules": {
|
||||
"at-rule-no-unknown": null
|
||||
},
|
||||
"syntax": "scss"
|
||||
}
|
||||
52
.travis.yml
Normal file
52
.travis.yml
Normal file
@@ -0,0 +1,52 @@
|
||||
language: node_js
|
||||
node_js: 12
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- app/node_modules
|
||||
- $HOME/.cache/electron
|
||||
- $HOME/.cache/electron-builder
|
||||
- $HOME/.npm/_prebuilds
|
||||
|
||||
env:
|
||||
global:
|
||||
- ELECTRON_CACHE=$HOME/.cache/electron
|
||||
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: Test
|
||||
before_install:
|
||||
- sudo apt-get install libsecret-1-dev
|
||||
- npm install
|
||||
script:
|
||||
- npm test
|
||||
|
||||
- stage: Deploy Linux & Windows
|
||||
if: tag IS present
|
||||
os: linux
|
||||
services: docker
|
||||
before_install:
|
||||
- sudo apt-get install libsecret-1-dev
|
||||
- npm install
|
||||
script:
|
||||
- docker run --rm --env-file <(env | grep -iE 'DEBUG|NODE_|ELECTRON_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|_TOKEN|_KEY|AWS_|STRIP|BUILD_') -v ${PWD}:/project -v ~/.cache/electron:/root/.cache/electron -v ~/.cache/electron-builder:/root/.cache/electron-builder electronuserland/builder:wine /bin/bash -c "npm run build -- --linux --win -p always"
|
||||
before_cache:
|
||||
- rm -rf $HOME/.cache/electron-builder/wine
|
||||
|
||||
- stage: Deploy Mac
|
||||
if: tag IS present
|
||||
os: osx
|
||||
before_install:
|
||||
- npm install
|
||||
osx_image: xcode10.2
|
||||
script:
|
||||
- npm run build -- -p always
|
||||
|
||||
# - stage: Deploy ARM Linux
|
||||
# if: tag IS present
|
||||
# os: linux
|
||||
# arch: arm64
|
||||
# script:
|
||||
# - npm run build -- --linux AppImage -p always
|
||||
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"}
|
||||
]
|
||||
}
|
||||
300
CHANGELOG.md
300
CHANGELOG.md
@@ -1,7 +1,303 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.1-alpha]() - Coming Soon
|
||||
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.18](https://github.com/Fabio286/antares/compare/v0.0.17...v0.0.18) (2021-02-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
- **Initial release:**
|
||||
* **UI:** context menu for input and textarea tags ([b54fefb](https://github.com/Fabio286/antares/commit/b54fefbf255c91f3cdd0c52b917b239ea40dae16))
|
||||
* **UI:** html, xml, json, svg and yaml editor modes in long text fields edit ([9a1bf32](https://github.com/Fabio286/antares/commit/9a1bf3212850d3783ac6382f8a980eb25c4f4226))
|
||||
* **UI:** run procedures/functions from sidebar context menu ([219da0a](https://github.com/Fabio286/antares/commit/219da0aba46839295807a01056199d42584e0af9))
|
||||
* **UI:** run routines/functions from settings tab ([7e81671](https://github.com/Fabio286/antares/commit/7e8167154ffd64e46d78a50cd13cf90cee61370c))
|
||||
* **UI:** search filter in explore bar ([2f58007](https://github.com/Fabio286/antares/commit/2f58007af4f32207fe4be9fc15bb43916e8e0abc))
|
||||
* **UI:** sticky schema names in explore bar ([110b0b4](https://github.com/Fabio286/antares/commit/110b0b414cef25395ae28493032c2048307e4783))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **MySQL:** issue obtaining routine/function parameters ([3aa2159](https://github.com/Fabio286/antares/commit/3aa2159a1ae3b6785646fc7ac1e866cb0277d087))
|
||||
* issue managing function/routine parameters ([76d92cd](https://github.com/Fabio286/antares/commit/76d92cd106b0409e8752c43d7d587d09dd3e1e32))
|
||||
* **UI:** data tab opened when non-table element is selected ([dbab06f](https://github.com/Fabio286/antares/commit/dbab06fcb80ccdae85215ceae9ba24af3e7ff263))
|
||||
* **UI:** elements from previous selected schemas in query suggestions ([c8545a2](https://github.com/Fabio286/antares/commit/c8545a250bc696c339e418a69a2942d5bac10cbf))
|
||||
* disabled sort for fields without a name property ([3b37b74](https://github.com/Fabio286/antares/commit/3b37b7432e969e6315ebd6ed74905ec3980a049c))
|
||||
* prevents F5 shortcut to run in non-selected workspaces ([7c4ca99](https://github.com/Fabio286/antares/commit/7c4ca999ce64af8077d29e419f430d5beaaead93))
|
||||
* support of bit fields in table filler ([94c4952](https://github.com/Fabio286/antares/commit/94c49523197284510361ebbb6984816a3bb1b243))
|
||||
|
||||
### [0.0.17](https://github.com/Fabio286/antares/compare/v0.0.16...v0.0.17) (2021-02-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Added french language ([18a93ef](https://github.com/Fabio286/antares/commit/18a93ef1aae530e69c9062bbeb08a3beec206eda))
|
||||
* fake table data generator ([a176174](https://github.com/Fabio286/antares/commit/a176174b8d7dc232920f4cd7c5e3f8e4c58d51a0))
|
||||
* min and max option for random floats and numbers ([6c62052](https://github.com/Fabio286/antares/commit/6c62052b4764731c774fef90784342447b36deb7))
|
||||
* support to fake data locales ([970de49](https://github.com/Fabio286/antares/commit/970de4962b3bffec271bfa6d4747e6fb9d408ba6))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **UI:** file uploader in table filler ([b5a8283](https://github.com/Fabio286/antares/commit/b5a828309f067636eae2120032f07e01233706e2))
|
||||
* **UI:** no foreign key select editing query results ([5b21d17](https://github.com/Fabio286/antares/commit/5b21d17f3a8f2482c3aafe85f26c34cd3c0a1fcf))
|
||||
* cut faker text based on field length ([288ff4c](https://github.com/Fabio286/antares/commit/288ff4c1a1c77f4b8b86b24649d805836366fdd3))
|
||||
* wrong date or time detection in field default ([9d5ebef](https://github.com/Fabio286/antares/commit/9d5ebefdced999af595f3d8dc2fac2b18fa8258b))
|
||||
* **UI:** better text on ssl file selectors ([9a19085](https://github.com/Fabio286/antares/commit/9a190854fe3c73ed4e7f89545ff259e15ee9f947))
|
||||
* **UI:** wrong length for char fields on table header ([0f69d1d](https://github.com/Fabio286/antares/commit/0f69d1dbb7958e45059b6b738c845abea1ad3225))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **core:** bulk inserts support ([b0576ac](https://github.com/Fabio286/antares/commit/b0576acdf65d41c1c8e0b0bca6cf6522dcb372be))
|
||||
|
||||
### [0.0.16](https://github.com/Fabio286/antares/compare/v0.0.15...v0.0.16) (2021-02-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* MySQL and MariaDB auto detection ([02c03e3](https://github.com/Fabio286/antares/commit/02c03e3d266052872ac8edeb159ec8182f41c6a5))
|
||||
* **UI:** enanched file upload input ([a0d8552](https://github.com/Fabio286/antares/commit/a0d85520fb0669d5eec4475f5e255adb1e2a0159))
|
||||
* support to ssl connections ([4e72bb1](https://github.com/Fabio286/antares/commit/4e72bb15874324214aa0f5b89057cdd565744468))
|
||||
* **UI:** database version in app footer ([15417e8](https://github.com/Fabio286/antares/commit/15417e8a776c6aa9f90762d198d70b26163bb2df))
|
||||
* **UI:** resize query editor area ([88ab7c5](https://github.com/Fabio286/antares/commit/88ab7c5a62654c6a6026b63464224226d33b1950))
|
||||
* delete rows from tables without a primary key ([574d493](https://github.com/Fabio286/antares/commit/574d4939083577ffcb8e7c65f572c364eb8415fb))
|
||||
* edit rows from tables without a primary key ([5940b0b](https://github.com/Fabio286/antares/commit/5940b0b84207093da141b5c617c41e0a18fcb1d7))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* compatibility with electron-store 7 ([bacf458](https://github.com/Fabio286/antares/commit/bacf45893676cb0744907703f6534ffd472bd1dd))
|
||||
* edit bit fields ([ede6fe8](https://github.com/Fabio286/antares/commit/ede6fe81cefc91cdce2bbb0cd7cd6f85bbca99b8))
|
||||
|
||||
### [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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* approximate totals in table tata tab ([e95d29c](https://github.com/Fabio286/antares/commit/e95d29c7c37e24e7cc14b466f9b539fa667042c2))
|
||||
* create new tables ([e6602d1](https://github.com/Fabio286/antares/commit/e6602d1bfa9ca10c6bb078ee80ddc94fb338763d))
|
||||
* display all keys in properties tab ([27769f2](https://github.com/Fabio286/antares/commit/27769f204f731d20c7ba2f838c02b7c2f28fa0c3))
|
||||
* drop and truncate tables ([a4122b4](https://github.com/Fabio286/antares/commit/a4122b4eaaa5b30d97ba5a93df8c9d21c30bc40b))
|
||||
* index management ([41505bd](https://github.com/Fabio286/antares/commit/41505bde6547c0af3c3413248ad8a0d182838bb1))
|
||||
* tables options edit ([0805b96](https://github.com/Fabio286/antares/commit/0805b96a75e439a7d65e8341ecc86fa938679a9f))
|
||||
* unsaved changes reminder ([33d1fa2](https://github.com/Fabio286/antares/commit/33d1fa22905f477924292135b0dcfefe168ee641))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* index deletion issue ([f8cf90a](https://github.com/Fabio286/antares/commit/f8cf90a89e7367c95e164b7dc669506df392b700))
|
||||
* some problems with properties and data tabs when changing database from sidebar ([0fe7157](https://github.com/Fabio286/antares/commit/0fe71572a5e74c17a5c66237351bb0b02c33e824))
|
||||
* sqlEscaper function wrong quotes conversion ([dfb24c6](https://github.com/Fabio286/antares/commit/dfb24c65f3c395d78d27a2f29e9aa8eeb427cff7))
|
||||
|
||||
### [0.0.9](https://github.com/EStarium/antares/compare/v0.0.8...v0.0.9) (2020-11-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* ability to edit table fields ([249926b](https://github.com/EStarium/antares/commit/249926b8e040d62d50244362b7f999f26337b93c))
|
||||
* support to aliased tables ([1658432](https://github.com/EStarium/antares/commit/1658432fd30073ba3bffb39b5c4ca69194ae1330))
|
||||
* table fields addition ([0765403](https://github.com/EStarium/antares/commit/07654039b6a99f3115c378b53d659593e5c81f35))
|
||||
* table fields deletion ([242ddec](https://github.com/EStarium/antares/commit/242ddec744814d15657db1ca88b2d865045ea219))
|
||||
* **ui:** display table properties tab ([2dc16e8](https://github.com/EStarium/antares/commit/2dc16e8ea8d6d9b79288335888e155ff180eebf5))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* duplicate header fields on join result tables ([ea9b489](https://github.com/EStarium/antares/commit/ea9b489f5f45cffa4a7ac87873fff070205e88c4))
|
||||
* F9 key shortcut refresh all query tabs instead of just selected one ([c9ba2e5](https://github.com/EStarium/antares/commit/c9ba2e5962eae1afc46daf35e55fb0ea5c3af5a4))
|
||||
* issue with tabs horizontal scroll with wheel ([c393f86](https://github.com/EStarium/antares/commit/c393f86947d1f65f896cadaa39d53f13e0a1f4eb))
|
||||
* zero fill field option was not saved ([3e5770f](https://github.com/EStarium/antares/commit/3e5770f7de51bdf2bc0f1f38c7ceb9ef0f4dcd00))
|
||||
* **mysql:** error getting foreign key list ([ee18388](https://github.com/EStarium/antares/commit/ee183886f64947305cc4f0d38dbdf7919953ec01))
|
||||
* wrong result fields type and order with some queries ([a8cd177](https://github.com/EStarium/antares/commit/a8cd17748f4ac7d75092f65ae7ca5f96a8a9e8c5))
|
||||
|
||||
### [0.0.8](https://github.com/EStarium/antares/compare/v0.0.7...v0.0.8) (2020-10-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **render:** field type and length on table header mouse hover ([04804b0](https://github.com/EStarium/antares/commit/04804b07c71cec271c31ace13bd41b2c7415e892))
|
||||
* close modals pressing ESC ([d563cec](https://github.com/EStarium/antares/commit/d563cec70d996f66c4f724bba7de618fc8678e66))
|
||||
* data table autorefresh, closes [#36](https://github.com/EStarium/antares/issues/36) ([9ecd888](https://github.com/EStarium/antares/commit/9ecd88870d1fcf32bb2c970a1506206c477810a0))
|
||||
* pie chart with table size in database explore bar ([426628f](https://github.com/EStarium/antares/commit/426628f268c77496a13b3498f03fd7b11fee299a))
|
||||
* query and data tabs keyboard shortcuts (F5, F9) ([0bf2c8d](https://github.com/EStarium/antares/commit/0bf2c8dc9dd9bdf7a8f48bed61eed7f1f1aacf71))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* context menu outside window when near bottom or right edge ([d4ecaf6](https://github.com/EStarium/antares/commit/d4ecaf65e56044170139bac61c3ee69efc35a8f0))
|
||||
* disable cell editor for not editable results ([b7c779e](https://github.com/EStarium/antares/commit/b7c779eef63c257c166e7128ea643bdd6142aa88))
|
||||
* missing connection name when "ask for crendential" selected ([c70e5b4](https://github.com/EStarium/antares/commit/c70e5b422c3534e92a64a0b534eb58663621489c))
|
||||
* missing header for some query results ([d560c38](https://github.com/EStarium/antares/commit/d560c384f5aed58ea975935975843c3b9061dd85))
|
||||
* no connection passed to connection's edit modal ([ce25cd0](https://github.com/EStarium/antares/commit/ce25cd0a3130db486ea4da24dd393d45c2ef9e0d))
|
||||
|
||||
### [0.0.7](https://github.com/EStarium/antares/compare/v0.0.6...v0.0.7) (2020-10-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Database creation ([3d0a83f](https://github.com/EStarium/antares/commit/3d0a83f2cf68c4dd412fd7679c39d63f081b7c19))
|
||||
* Database deletion ([4288a1f](https://github.com/EStarium/antares/commit/4288a1fd331f4a28de2e756f898d208a6a6599c4))
|
||||
* Edit database collation ([54717e1](https://github.com/EStarium/antares/commit/54717e1f6a36ec0b3dd096d0e1e747512f6dda09))
|
||||
* Field comment on mouse over a table field name ([2554444](https://github.com/EStarium/antares/commit/2554444322b59a6b1ab3ff05ccf8604bf6f8c8b8))
|
||||
* Support to multiple queries in the same tab ([48f77ba](https://github.com/EStarium/antares/commit/48f77bae01efbff40bd0f5ce8c66e2619f44bf3a))
|
||||
* Update italian translation ([89c3dc9](https://github.com/EStarium/antares/commit/89c3dc9fede63c77eb22b48df1a375ea44830306))
|
||||
* **Spanish translation** thanks to
|
||||
[hongkfui](https://github.com/hongkfui) ([#32](https://github.com/EStarium/antares/pull/32))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Cell update soft reload doesn't apply changes ([1b04b21](https://github.com/EStarium/antares/commit/1b04b216b21b697e47062a9366bc1b6a040a1a72))
|
||||
* Empty databases not shown in explore bar ([3e737cb](https://github.com/EStarium/antares/commit/3e737cba62f795f225e944939c6bff04b27fa3d4))
|
||||
* Glitch on table data tab ([10b426b](https://github.com/EStarium/antares/commit/10b426b90b6b9461cfffce3026c982463f6e0599))
|
||||
* Lack of loading progressbar when an update is available ([86aec4f](https://github.com/EStarium/antares/commit/86aec4f5e41c059e88066a01f0d85155de99a5ee))
|
||||
* Missing schema when queryng INFORMATION_SCHEMA ([530d1bd](https://github.com/EStarium/antares/commit/530d1bd43fa95de05f594b9b5cae2f4b397f96e0))
|
||||
* Prevent multiple app instances ([12fbe8c](https://github.com/EStarium/antares/commit/12fbe8c1a03259648554f2a5c69b5abbedc18a48))
|
||||
* Several fix on data and query tabs ([530907d](https://github.com/EStarium/antares/commit/530907d097ac4d995e1bfcb02e6c890fd6007e21))
|
||||
* Unable to obtain fields informations for some queries ([43c7072](https://github.com/EStarium/antares/commit/43c7072c1c83a2455ae48a37be69b444b3eb6560))
|
||||
* Unable to obtain keyUsage informations when adding new row ([023c6a6](https://github.com/EStarium/antares/commit/023c6a633a7f268b1a97b748ad08d2416cc30ffe))
|
||||
* Value overridden when join tables with fields with same name ([78965d2](https://github.com/EStarium/antares/commit/78965d23e3efb7d8d6d110d79142966e57200757))
|
||||
* Wrong field names when join tables ([ad0bad8](https://github.com/EStarium/antares/commit/ad0bad8486c3d67ec14ec1aed3d8aff6cce9df87))
|
||||
* Wrong italian translation ([b29e07c](https://github.com/EStarium/antares/commit/b29e07c3b722aec7e78f3cef2e357a53cbcac474))
|
||||
* Wrong schema fetching table fields and key usage ([8e71f42](https://github.com/EStarium/antares/commit/8e71f42a28060fdfeeb81502b0759d0d11f5bcfd))
|
||||
* Wrong table and schema when more than one query in a tab ([4684b41](https://github.com/EStarium/antares/commit/4684b4114b9c9c253120292d7d164d7676011f86))
|
||||
|
||||
### [0.0.6](https://github.com/EStarium/antares/compare/v0.0.5...v0.0.6) (2020-09-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Aliases support ([264de9c](https://github.com/EStarium/antares/commit/264de9c5686fb3a2ef22d96171f45b915ba1b34b))
|
||||
* Middle click to close tabs ([256ec76](https://github.com/EStarium/antares/commit/256ec765883fcf247355190827e943c76e95f13b))
|
||||
* Monaco-editor as query editor ([196a3e0](https://github.com/EStarium/antares/commit/196a3e0185a3d68b7c4ade8dbf187d2b216cc00b))
|
||||
* Sql suggestions in query editor ([8dc74ef](https://github.com/EStarium/antares/commit/8dc74ef2c335e8ae4a69f5d2651df65939139b1b))
|
||||
* Support to multiple query tabs ([d7ed00f](https://github.com/EStarium/antares/commit/d7ed00f4a3613da9015c9fc48c4d8062d292e416))
|
||||
* Tabs horizontal scroll with mouse wheel ([3a6ea76](https://github.com/EStarium/antares/commit/3a6ea76b93682ebd50908df7368c62c2c1e27958))
|
||||
* **Arabic translation** thanks to [Mohd-PH](https://github.com/Mohd-PH) ([#29](https://github.com/EStarium/antares/pull/29))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Error when launching queries without a result from query tabs ([a1a6f51](https://github.com/EStarium/antares/commit/a1a6f51f2fba5140f5e3bd9cd6557c8a13dfaa2c))
|
||||
* Field name displayed instead of alias ([801a0de](https://github.com/EStarium/antares/commit/801a0de1865dea2a59ff057b7c2cc988cc9c87ed))
|
||||
* Wrong table height calc in some cases ([fd6d517](https://github.com/EStarium/antares/commit/fd6d5177efb6161aab01f9e108eda60df6c7d8c4))
|
||||
|
||||
### [0.0.5](https://github.com/EStarium/antares/compare/v0.0.4...v0.0.5) (2020-08-17)
|
||||
|
||||
### Features
|
||||
|
||||
* Badge on setting icon and update tab when update is available ([e8141b6](https://github.com/EStarium/antares/commit/e8141b632154f765ca73fa50b9b7120dc592ead0))
|
||||
* Foreign key support in add/edit row ([0b6a188](https://github.com/EStarium/antares/commit/0b6a188d1959b80b4a66946cc79d2dd3853a428b))
|
||||
* Option to insert table rows ([2f1dfdc](https://github.com/EStarium/antares/commit/2f1dfdc6543b4a6c1d595f0daa00c0832be49c77))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Insert files via add row option ([3c6e818](https://github.com/EStarium/antares/commit/3c6e818ba06f1b8b5db0ecf80c3b7498d6d2a841))
|
||||
* Newline replaced with undefined inside queries ([59e4a79](https://github.com/EStarium/antares/commit/59e4a79f42076b3fce98a764e9ad6a01c674555b))
|
||||
* Query result header didn't show just selected fields ([7bc1009](https://github.com/EStarium/antares/commit/7bc10092fe4823e03133e69e0a7bf86e44fde43b))
|
||||
* Table header not fixed on top when fast scrolling ([13b0816](https://github.com/EStarium/antares/commit/13b0816837461119eaab79fdb7e92223e0950630))
|
||||
* Time and datetime precision ([771f8a2](https://github.com/EStarium/antares/commit/771f8a2d682c64105231e3fef199f05150596298))
|
||||
* Update a row with a string key value ([eb348b3](https://github.com/EStarium/antares/commit/eb348b3095b6905321b62eed6cea228374ebc3d1))
|
||||
* Window title not perfectly centered ([7651d05](https://github.com/EStarium/antares/commit/7651d05b37970574d6ae4bdf75c20c69d59c1e6d))
|
||||
* Wrong schema passed in query tab when a different database was selected ([6d0724d](https://github.com/EStarium/antares/commit/6d0724dc90cdebb10e0342d2c472bdd07aa345f8))
|
||||
|
||||
### [0.0.4](https://github.com/EStarium/antares/compare/v0.0.3-alpha...v0.0.4) (2020-08-06)
|
||||
|
||||
### Features
|
||||
|
||||
* Blob fields edit/view/download ([712fe9f](https://github.com/EStarium/antares/commit/712fe9f00d210db0f2317eca61e7fb648383e3fe))
|
||||
* Window title in app title bar ([0089c0c](https://github.com/EStarium/antares/commit/0089c0cbac6caf0a6fd195849099f18713580228))
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2017 Jakub Szwacz
|
||||
Copyright (c) 2020 Fabio Di Stasio
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
||||
104
README.md
104
README.md
@@ -1,7 +1,105 @@
|
||||
<p align="center">
|
||||
<img width="256" src="https://raw.githubusercontent.com/Fabio286/antares/master/docs/logo.png">
|
||||
<img width="800" src="https://raw.githubusercontent.com/Fabio286/antares/master/docs/screen-alpha.png">
|
||||
</p>
|
||||
|
||||
# Antares
|
||||
# Antares SQL Client
|
||||
|
||||
🚧 Work in progress! 🚧
|
||||
 [](https://travis-ci.com/Fabio286/antares)  
|
||||
|
||||
Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers.
|
||||
My target is to support as many databases as possible, and all major operating systems, including the ARM versions.
|
||||
|
||||
**At the moment this application is an alpha, it lacks many features** and supports only MySQL.
|
||||
Most of its current features might be enough for basic MySQL use, so give it a chance and send me your feedback, I would really appreciate it.
|
||||
I'm actively working on it (yes, i'm a lone dev), hoping to provide cool features and fixes as soon as possible.
|
||||
|
||||
🔗 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.
|
||||
🌟 Don't forget to **leave a star** if you appreciate this project.
|
||||
|
||||
## Philosophy
|
||||
|
||||
Why am I developing an SQL client when there are a lot of them on the market?
|
||||
The main goal is to develop a totally free, full featured, cross platform and open source alternative, empowered by JavaScript's ecosystem.
|
||||
An application created with minimalism and semplicity in mind, with features in the right places, not hundreds of tiny buttons or submenu.
|
||||
|
||||
## How to contribute
|
||||
|
||||
- [Translate Antares](https://github.com/Fabio286/antares/wiki/Translate-Antares)
|
||||
|
||||
## Current main features
|
||||
|
||||
- Multiple database connections at same time.
|
||||
- Database management (add/edit/delete).
|
||||
- Full tables management, including indexes and foreign keys.
|
||||
- Views, triggers, stored routines, functions and schedulers management (add/edit/delete).
|
||||
- Fake table data filler.
|
||||
- Run queries on multiple tabs.
|
||||
- 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.
|
||||
|
||||
- Support for other databases.
|
||||
- Database tools (variables, process list...).
|
||||
- SSH tunnel support.
|
||||
- Users management (add/edit/delete).
|
||||
- UI/UX improvements.
|
||||
- Query history.
|
||||
- More context menu shortcuts.
|
||||
- More keyboard shortcuts.
|
||||
- Query logs console.
|
||||
- Import/export and migration.
|
||||
- Light theme.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### **Linux**
|
||||
|
||||
With KDE may need necessary installation of the additional `gnome-keyring` package.
|
||||
Depending on your distribution, you will need to run the following command:
|
||||
|
||||
- Debian/Ubuntu: `sudo apt-get install gnome-keyring`
|
||||
- Red Hat-based: `sudo yum install gnome-keyring`
|
||||
- Arch Linux: `sudo pacman -S gnome-keyring`
|
||||
|
||||
## Currently supported
|
||||
|
||||
### Databases
|
||||
|
||||
- [x] MySQL/MariaDB
|
||||
- [ ] PostgreSQL
|
||||
- [ ] SQLite
|
||||
- [ ] MSSQL
|
||||
- [ ] OracleDB
|
||||
- [ ] More...
|
||||
|
||||
### Operating Systems
|
||||
|
||||
#### • x64
|
||||
|
||||
- [x] Windows
|
||||
- [x] Linux
|
||||
- [x] MacOS (i need feedbacks)
|
||||
|
||||
#### • ARM
|
||||
|
||||
- [ ] Windows
|
||||
- [ ] Linux
|
||||
- [ ] MacOS
|
||||
|
||||
## Translations
|
||||
|
||||
**Italian Translation** (46%) / [Giuseppe Gigliotti](https://github.com/ReverbOD) [[#20](https://github.com/Fabio286/antares/pull/20)]
|
||||
**Arabic Translation** (45%) / [Mohd-PH](https://github.com/Mohd-PH) [[#29](https://github.com/Fabio286/antares/pull/29)]
|
||||
**Spanish Translation** (46%) / [hongkfui](https://github.com/hongkfui) [[#32](https://github.com/Fabio286/antares/pull/32)]
|
||||
**French Translation** (100%) / [MrAnyx](https://github.com/MrAnyx) [[#44](https://github.com/Fabio286/antares/pull/44)]
|
||||
|
||||
## Reviews
|
||||
|
||||
<a target="_blank" href="https://www.softx64.com/windows/antares-sql-client.html" title="Antares SQL Client review"><img src="https://www.softx64.com/softx64-review.png" alt="Antares SQL Client review" /></a>
|
||||
|
||||
BIN
build/icon.icns
Normal file
BIN
build/icon.icns
Normal file
Binary file not shown.
BIN
build/icon.ico
Normal file
BIN
build/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
build/icon.png
BIN
build/icon.png
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB |
BIN
docs/screen-alpha.png
Normal file
BIN
docs/screen-alpha.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 260 KiB |
5
jsconfig.json
Normal file
5
jsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"include": [
|
||||
"./src/renderer/**/*"
|
||||
]
|
||||
}
|
||||
13749
package-lock.json
generated
13749
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
103
package.json
103
package.json
@@ -1,69 +1,96 @@
|
||||
{
|
||||
"name": "antares",
|
||||
"productName": "Antares",
|
||||
"version": "0.0.1-alpha",
|
||||
"version": "0.0.18",
|
||||
"description": "A cross-platform easy to use SQL client.",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/Fabio286/antares.git",
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development electron-webpack dev",
|
||||
"compile": "electron-webpack",
|
||||
"dist": "cross-env NODE_ENV=production npm run compile && electron-builder",
|
||||
"dist:dir": "cross-env NODE_ENV=production npm run dist --dir -c.compression=store -c.mac.identity=null",
|
||||
"publish": "build -p always"
|
||||
"build": "cross-env NODE_ENV=production npm run compile && electron-builder",
|
||||
"release": "standard-version",
|
||||
"release:pre": "npm run release -- --prerelease alpha",
|
||||
"test": "npm run lint",
|
||||
"lint": "eslint . --ext .js,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
|
||||
"lint:fix": "eslint . --ext .js,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix"
|
||||
},
|
||||
"author": "Fabio Di Stasio <fabio286@gmail.com>",
|
||||
"build": {
|
||||
"appId": "com.estarium.antares",
|
||||
"appId": "com.fabio286.antares",
|
||||
"artifactName": "${productName}-${version}-${os}_${arch}.${ext}",
|
||||
"files": [
|
||||
"static/*"
|
||||
]
|
||||
"dmg": {
|
||||
"contents": [
|
||||
{
|
||||
"x": 130,
|
||||
"y": 220
|
||||
},
|
||||
{
|
||||
"x": 410,
|
||||
"y": 220,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
}
|
||||
]
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
"deb",
|
||||
"AppImage"
|
||||
],
|
||||
"category": "Development"
|
||||
},
|
||||
"appImage": {
|
||||
"license": "./LICENSE",
|
||||
"category": "Development"
|
||||
}
|
||||
},
|
||||
"electronWebpack": {
|
||||
"whiteListedModules": [
|
||||
"codemirror"
|
||||
],
|
||||
"renderer": {
|
||||
"webpackConfig": "webpack.config.js"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"codemirror": "^5.54.0",
|
||||
"electron-log": "^4.2.1",
|
||||
"electron-updater": "^4.3.1",
|
||||
"lodash": "^4.17.15",
|
||||
"material-design-icons": "^3.0.1",
|
||||
"moment": "^2.26.0",
|
||||
"mssql": "^6.2.0",
|
||||
"@mdi/font": "^5.9.55",
|
||||
"ace-builds": "^1.4.12",
|
||||
"electron-log": "^4.3.0",
|
||||
"electron-store": "^7.0.0",
|
||||
"electron-updater": "^4.3.5",
|
||||
"faker": "^5.3.1",
|
||||
"keytar": "^7.3.0",
|
||||
"moment": "^2.29.1",
|
||||
"mssql": "^6.2.3",
|
||||
"mysql": "^2.18.1",
|
||||
"pg": "^8.2.1",
|
||||
"pg": "^8.5.1",
|
||||
"source-map-support": "^0.5.16",
|
||||
"spectre.css": "^0.5.8",
|
||||
"vue-click-outside": "^1.1.0",
|
||||
"vue-i18n": "^8.18.2",
|
||||
"vuedraggable": "^2.23.2",
|
||||
"vuex": "^3.4.0",
|
||||
"vuex-persist": "^2.2.0"
|
||||
"spectre.css": "^0.5.9",
|
||||
"vue-i18n": "^8.22.4",
|
||||
"vue-the-mask": "^0.11.1",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuex": "^3.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^10.1.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"electron": "^8.3.0",
|
||||
"electron-builder": "^22.7.0",
|
||||
"electron-devtools-installer": "^3.0.0",
|
||||
"electron": "^11.3.0",
|
||||
"electron-builder": "^22.9.1",
|
||||
"electron-devtools-installer": "^3.1.1",
|
||||
"electron-webpack": "^2.8.2",
|
||||
"electron-webpack-vue": "^2.4.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-standard": "^14.1.1",
|
||||
"eslint-plugin-import": "^2.21.1",
|
||||
"eslint": "^7.20.0",
|
||||
"eslint-config-standard": "^16.0.2",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.0.1",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"node-sass": "^4.14.1",
|
||||
"sass-loader": "^8.0.2",
|
||||
"vue": "^2.6.11",
|
||||
"webpack": "^4.43.0"
|
||||
"eslint-plugin-promise": "^4.3.1",
|
||||
"eslint-plugin-vue": "^7.6.0",
|
||||
"node-sass": "^5.0.0",
|
||||
"sass-loader": "^10.1.1",
|
||||
"standard-version": "^9.1.0",
|
||||
"stylelint": "^13.9.0",
|
||||
"stylelint-config-standard": "^20.0.0",
|
||||
"stylelint-scss": "^3.19.0",
|
||||
"vue": "^2.6.12",
|
||||
"vue-template-compiler": "^2.6.12",
|
||||
"webpack": "^4.46.0"
|
||||
}
|
||||
}
|
||||
|
||||
217
src/common/FakerMethods.js
Normal file
217
src/common/FakerMethods.js
Normal file
@@ -0,0 +1,217 @@
|
||||
export default class {
|
||||
static get _methods () {
|
||||
return [
|
||||
{ name: 'zipCode', group: 'address', types: ['string'] },
|
||||
{ name: 'zipCodeByState', group: 'address', types: ['string'] },
|
||||
{ name: 'city', group: 'address', types: ['string'] },
|
||||
{ name: 'cityPrefix', group: 'address', types: ['string'] },
|
||||
{ name: 'citySuffix', group: 'address', types: ['string'] },
|
||||
{ name: 'streetName', group: 'address', types: ['string'] },
|
||||
{ name: 'streetAddress', group: 'address', types: ['string'] },
|
||||
{ name: 'streetSuffix', group: 'address', types: ['string'] },
|
||||
{ name: 'streetPrefix', group: 'address', types: ['string'] },
|
||||
{ name: 'secondaryAddress', group: 'address', types: ['string'] },
|
||||
{ name: 'county', group: 'address', types: ['string'] },
|
||||
{ name: 'country', group: 'address', types: ['string'] },
|
||||
{ name: 'countryCode', group: 'address', types: ['string'] },
|
||||
{ name: 'state', group: 'address', types: ['string'] },
|
||||
{ name: 'stateAbbr', group: 'address', types: ['string'] },
|
||||
{ name: 'latitude', group: 'address', types: ['string'] },
|
||||
{ name: 'longitude', group: 'address', types: ['string'] },
|
||||
{ name: 'direction', group: 'address', types: ['string'] },
|
||||
{ name: 'cardinalDirection', group: 'address', types: ['string'] },
|
||||
{ name: 'ordinalDirection', group: 'address', types: ['string'] },
|
||||
// { name: 'nearbyGPSCoordinate', group: 'address', types: ['string'] },
|
||||
{ name: 'timeZone', group: 'address', types: ['string'] },
|
||||
|
||||
{ name: 'color', group: 'commerce', types: ['string'] },
|
||||
{ name: 'department', group: 'commerce', types: ['string'] },
|
||||
{ name: 'productName', group: 'commerce', types: ['string'] },
|
||||
{ name: 'price', group: 'commerce', types: ['string', 'float'] },
|
||||
{ name: 'productAdjective', group: 'commerce', types: ['string'] },
|
||||
{ name: 'productMaterial', group: 'commerce', types: ['string'] },
|
||||
{ name: 'product', group: 'commerce', types: ['string'] },
|
||||
{ name: 'productDescription', group: 'commerce', types: ['string'] },
|
||||
|
||||
{ name: 'suffixes', group: 'company', types: ['string'] },
|
||||
{ name: 'companyName', group: 'company', types: ['string'] },
|
||||
{ name: 'companySuffix', group: 'company', types: ['string'] },
|
||||
{ name: 'catchPhrase', group: 'company', types: ['string'] },
|
||||
{ name: 'bs', group: 'company', types: ['string'] },
|
||||
{ name: 'catchPhraseAdjective', group: 'company', types: ['string'] },
|
||||
{ name: 'catchPhraseDescriptor', group: 'company', types: ['string'] },
|
||||
{ name: 'catchPhraseNoun', group: 'company', types: ['string'] },
|
||||
{ name: 'bsAdjective', group: 'company', types: ['string'] },
|
||||
{ name: 'bsBuzz', group: 'company', types: ['string'] },
|
||||
{ name: 'bsNoun', group: 'company', types: ['string'] },
|
||||
|
||||
{ name: 'column', group: 'database', types: ['string'] },
|
||||
{ name: 'type', group: 'database', types: ['string'] },
|
||||
{ name: 'collation', group: 'database', types: ['string'] },
|
||||
{ name: 'engine', group: 'database', types: ['string'] },
|
||||
|
||||
{ name: 'past', group: 'date', types: ['string', 'datetime'] },
|
||||
{ name: 'future', group: 'date', types: ['string', 'datetime'] },
|
||||
// { name: 'between', group: 'date', types: ['string'] },
|
||||
{ name: 'recent', group: 'date', types: ['string', 'datetime'] },
|
||||
{ name: 'soon', group: 'date', types: ['string', 'datetime'] },
|
||||
{ name: 'month', group: 'date', types: ['string'] },
|
||||
{ name: 'weekday', group: 'date', types: ['string'] },
|
||||
|
||||
{ name: 'account', group: 'finance', types: ['string', 'number'] },
|
||||
{ name: 'accountName', group: 'finance', types: ['string'] },
|
||||
{ name: 'routingNumber', group: 'finance', types: ['string', 'number'] },
|
||||
{ name: 'mask', group: 'finance', types: ['string', 'number'] },
|
||||
{ name: 'amount', group: 'finance', types: ['string', 'float'] },
|
||||
{ name: 'transactionType', group: 'finance', types: ['string'] },
|
||||
{ name: 'currencyCode', group: 'finance', types: ['string'] },
|
||||
{ name: 'currencyName', group: 'finance', types: ['string'] },
|
||||
{ name: 'currencySymbol', group: 'finance', types: ['string'] },
|
||||
{ name: 'bitcoinAddress', group: 'finance', types: ['string'] },
|
||||
{ name: 'litecoinAddress', group: 'finance', types: ['string'] },
|
||||
{ name: 'creditCardNumber', group: 'finance', types: ['string'] },
|
||||
{ name: 'creditCardCVV', group: 'finance', types: ['string', 'number'] },
|
||||
{ name: 'ethereumAddress', group: 'finance', types: ['string'] },
|
||||
{ name: 'iban', group: 'finance', types: ['string'] },
|
||||
{ name: 'bic', group: 'finance', types: ['string'] },
|
||||
{ name: 'transactionDescription', group: 'finance', types: ['string'] },
|
||||
|
||||
{ name: 'branch', group: 'git', types: ['string'] },
|
||||
{ name: 'commitEntry', group: 'git', types: ['string'] },
|
||||
{ name: 'commitMessage', group: 'git', types: ['string'] },
|
||||
{ name: 'commitSha', group: 'git', types: ['string'] },
|
||||
{ name: 'shortSha', group: 'git', types: ['string'] },
|
||||
|
||||
{ name: 'abbreviation', group: 'hacker', types: ['string'] },
|
||||
{ name: 'adjective', group: 'hacker', types: ['string'] },
|
||||
{ name: 'noun', group: 'hacker', types: ['string'] },
|
||||
{ name: 'verb', group: 'hacker', types: ['string'] },
|
||||
{ name: 'ingverb', group: 'hacker', types: ['string'] },
|
||||
{ name: 'phrase', group: 'hacker', types: ['string'] },
|
||||
|
||||
// { name: 'avatar', group: 'internet', types: ['string'] },
|
||||
{ name: 'email', group: 'internet', types: ['string'] },
|
||||
{ name: 'exampleEmail', group: 'internet', types: ['string'] },
|
||||
{ name: 'userName', group: 'internet', types: ['string'] },
|
||||
{ name: 'protocol', group: 'internet', types: ['string'] },
|
||||
{ name: 'url', group: 'internet', types: ['string'] },
|
||||
{ name: 'domainName', group: 'internet', types: ['string'] },
|
||||
{ name: 'domainSuffix', group: 'internet', types: ['string'] },
|
||||
{ name: 'domainWord', group: 'internet', types: ['string'] },
|
||||
{ name: 'ip', group: 'internet', types: ['string'] },
|
||||
{ name: 'ipv6', group: 'internet', types: ['string'] },
|
||||
{ name: 'userAgent', group: 'internet', types: ['string'] },
|
||||
{ name: 'color', group: 'internet', types: ['string'] },
|
||||
{ name: 'mac', group: 'internet', types: ['string'] },
|
||||
{ name: 'password', group: 'internet', types: ['string'] },
|
||||
|
||||
{ name: 'word', group: 'lorem', types: ['string'] },
|
||||
{ name: 'words', group: 'lorem', types: ['string'] },
|
||||
{ name: 'sentence', group: 'lorem', types: ['string'] },
|
||||
{ name: 'slug', group: 'lorem', types: ['string'] },
|
||||
{ name: 'sentences', group: 'lorem', types: ['string'] },
|
||||
{ name: 'paragraph', group: 'lorem', types: ['string'] },
|
||||
{ name: 'paragraphs', group: 'lorem', types: ['string'] },
|
||||
{ name: 'text', group: 'lorem', types: ['string'] },
|
||||
{ name: 'lines', group: 'lorem', types: ['string'] },
|
||||
|
||||
{ name: 'genre', group: 'music', types: ['string'] },
|
||||
|
||||
{ name: 'firstName', group: 'name', types: ['string'] },
|
||||
{ name: 'lastName', group: 'name', types: ['string'] },
|
||||
{ name: 'middleName', group: 'name', types: ['string'] },
|
||||
{ name: 'findName', group: 'name', types: ['string'] },
|
||||
{ name: 'jobTitle', group: 'name', types: ['string'] },
|
||||
{ name: 'gender', group: 'name', types: ['string'] },
|
||||
{ name: 'prefix', group: 'name', types: ['string'] },
|
||||
{ name: 'suffix', group: 'name', types: ['string'] },
|
||||
{ name: 'title', group: 'name', types: ['string'] },
|
||||
{ name: 'jobDescriptor', group: 'name', types: ['string'] },
|
||||
{ name: 'jobArea', group: 'name', types: ['string'] },
|
||||
{ name: 'jobType', group: 'name', types: ['string'] },
|
||||
|
||||
{ name: 'phoneNumber', group: 'phone', types: ['string'] },
|
||||
{ name: 'phoneNumberFormat', group: 'phone', types: ['string'] },
|
||||
{ name: 'phoneFormats', group: 'phone', types: ['string'] },
|
||||
|
||||
{ name: 'number', group: 'random', types: ['string', 'number'], params: ['min', 'max'] },
|
||||
{ name: 'float', group: 'random', types: ['string', 'float'], params: ['min', 'max'] },
|
||||
{ name: 'arrayElement', group: 'random', types: ['string'] },
|
||||
{ name: 'arrayElements', group: 'random', types: ['string'] },
|
||||
{ name: 'objectElement', group: 'random', types: ['string'] },
|
||||
{ name: 'uuid', group: 'random', types: ['string'] },
|
||||
{ name: 'boolean', group: 'random', types: ['string'] },
|
||||
{ name: 'word', group: 'random', types: ['string'] },
|
||||
{ name: 'words', group: 'random', types: ['string'] },
|
||||
// { name: 'image', group: 'random', types: ['string'] },
|
||||
{ name: 'locale', group: 'random', types: ['string'] },
|
||||
{ name: 'alpha', group: 'random', types: ['string'] },
|
||||
{ name: 'alphaNumeric', group: 'random', types: ['string'] },
|
||||
{ name: 'hexaDecimal', group: 'random', types: ['string'] },
|
||||
|
||||
{ name: 'fileName', group: 'system', types: ['string'] },
|
||||
{ name: 'commonFileName', group: 'system', types: ['string'] },
|
||||
{ name: 'mimeType', group: 'system', types: ['string'] },
|
||||
{ name: 'commonFileType', group: 'system', types: ['string'] },
|
||||
{ name: 'commonFileExt', group: 'system', types: ['string'] },
|
||||
{ name: 'fileType', group: 'system', types: ['string'] },
|
||||
{ name: 'fileExt', group: 'system', types: ['string'] },
|
||||
{ name: 'directoryPath', group: 'system', types: ['string'] },
|
||||
{ name: 'filePath', group: 'system', types: ['string'] },
|
||||
{ name: 'semver', group: 'system', types: ['string'] },
|
||||
|
||||
{ name: 'recent', group: 'time', types: ['string', 'time'] },
|
||||
|
||||
{ name: 'vehicle', group: 'vehicle', types: ['string'] },
|
||||
{ name: 'manufacturer', group: 'vehicle', types: ['string'] },
|
||||
{ name: 'model', group: 'vehicle', types: ['string'] },
|
||||
{ name: 'type', group: 'vehicle', types: ['string'] },
|
||||
{ name: 'fuel', group: 'vehicle', types: ['string'] },
|
||||
{ name: 'vin', group: 'vehicle', types: ['string'] },
|
||||
{ name: 'color', group: 'vehicle', types: ['string'] }
|
||||
];
|
||||
}
|
||||
|
||||
static getGroups () {
|
||||
const groupsObj = this._methods.reduce((acc, curr) => {
|
||||
if (curr.group in acc)
|
||||
curr.types.forEach(type => acc[curr.group].add(type));
|
||||
else
|
||||
acc[curr.group] = new Set(curr.types);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const groupsArr = [];
|
||||
|
||||
for (const key in groupsObj)
|
||||
groupsArr.push({ name: key, types: [...groupsObj[key]] });
|
||||
|
||||
return groupsArr.sort((a, b) => {
|
||||
if (a.name < b.name)
|
||||
return -1;
|
||||
|
||||
if (b.name > a.name)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}); ;
|
||||
}
|
||||
|
||||
static getGroupsByType (type) {
|
||||
if (!type) return [];
|
||||
return this.getGroups().filter(group => group.types.includes(type));
|
||||
}
|
||||
|
||||
static getMethods ({ type, group }) {
|
||||
return this._methods.filter(method => method.group === group && method.types.includes(type)).sort((a, b) => {
|
||||
if (a.name < b.name)
|
||||
return -1;
|
||||
|
||||
if (b.name > a.name)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
303
src/common/data-types/mysql.js
Normal file
303
src/common/data-types/mysql.js
Normal file
@@ -0,0 +1,303 @@
|
||||
module.exports = [
|
||||
{
|
||||
group: 'integer',
|
||||
types: [
|
||||
{
|
||||
name: 'TINYINT',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: true,
|
||||
zerofill: true
|
||||
},
|
||||
{
|
||||
name: 'SMALLINT',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: true,
|
||||
zerofill: true
|
||||
},
|
||||
{
|
||||
name: 'INT',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: true,
|
||||
zerofill: true
|
||||
},
|
||||
{
|
||||
name: 'MEDIUMINT',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: true,
|
||||
zerofill: true
|
||||
},
|
||||
{
|
||||
name: 'BIGINT',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: true,
|
||||
zerofill: true
|
||||
},
|
||||
{
|
||||
name: 'BIT',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: true,
|
||||
zerofill: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'float',
|
||||
types: [
|
||||
{
|
||||
name: 'FLOAT',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'DOUBLE',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'DECIMAL',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'string',
|
||||
types: [
|
||||
{
|
||||
name: 'CHAR',
|
||||
length: true,
|
||||
collation: true,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'VARCHAR',
|
||||
length: true,
|
||||
collation: true,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'TINYTEXT',
|
||||
length: false,
|
||||
collation: true,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'MEDIUMTEXT',
|
||||
length: false,
|
||||
collation: true,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'TEXT',
|
||||
length: false,
|
||||
collation: true,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'LONGTEXT',
|
||||
length: false,
|
||||
collation: true,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'JSON',
|
||||
length: false,
|
||||
collation: true,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'binary',
|
||||
types: [
|
||||
{
|
||||
name: 'BINARY',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'VARBINARY',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'TINYBLOB',
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'BLOB',
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'MEDIUMBLOB',
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'LONGBLOB',
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'time',
|
||||
types: [
|
||||
{
|
||||
name: 'DATE',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'TIME',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'YEAR',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'DATETIME',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'TIMESTAMP',
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'spatial',
|
||||
types: [
|
||||
{
|
||||
name: 'POINT',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'LINESTRING',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'POLYGON',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'GEOMETRY',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'MULTIPOINT',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'MULTILINESTRING',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'MULTIPOLYGON',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'GEOMETRYCOLLECTION',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'other',
|
||||
types: [
|
||||
{
|
||||
name: 'UNKNOWN',
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'ENUM',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
},
|
||||
{
|
||||
name: 'SET',
|
||||
length: true,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
1
src/common/db-properties/mysql.js
Normal file
1
src/common/db-properties/mysql.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = [];
|
||||
13
src/common/fieldTypes.js
Normal file
13
src/common/fieldTypes.js
Normal file
@@ -0,0 +1,13 @@
|
||||
export const TEXT = ['CHAR', 'VARCHAR'];
|
||||
export const LONG_TEXT = ['TEXT', 'MEDIUMTEXT', 'LONGTEXT'];
|
||||
|
||||
export const NUMBER = ['INT', 'TINYINT', 'SMALLINT', 'MEDIUMINT', 'BIGINT', 'DECIMAL', 'BOOL'];
|
||||
export const FLOAT = ['FLOAT', 'DOUBLE'];
|
||||
|
||||
export const DATE = ['DATE'];
|
||||
export const TIME = ['TIME'];
|
||||
export const DATETIME = ['DATETIME', 'TIMESTAMP'];
|
||||
|
||||
export const BLOB = ['BLOB', 'TINYBLOB', 'MEDIUMBLOB', 'LONGBLOB'];
|
||||
|
||||
export const BIT = ['BIT'];
|
||||
6
src/common/index-types/mysql.js
Normal file
6
src/common/index-types/mysql.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = [
|
||||
'PRIMARY',
|
||||
'INDEX',
|
||||
'UNIQUE',
|
||||
'FULLTEXT'
|
||||
];
|
||||
7
src/common/libs/bufferToBase64.js
Normal file
7
src/common/libs/bufferToBase64.js
Normal file
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
export function bufferToBase64 (buf) {
|
||||
const binstr = Array.prototype.map.call(buf, ch => {
|
||||
return String.fromCharCode(ch);
|
||||
}).join('');
|
||||
return btoa(binstr);
|
||||
}
|
||||
12
src/common/libs/formatBytes.js
Normal file
12
src/common/libs/formatBytes.js
Normal file
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
export function formatBytes (bytes, decimals = 2) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||||
}
|
||||
@@ -1,13 +1,10 @@
|
||||
export function uidGen () {
|
||||
return Math.random().toString(36).substr(2, 9).toUpperCase();
|
||||
};
|
||||
|
||||
'use strict';
|
||||
export function mimeFromHex (hex) {
|
||||
switch (hex.substring(0, 4)) { // 2 bytes
|
||||
case '424D':
|
||||
return { ext: 'bmp', mime: 'image/bmp' };
|
||||
case '1F8B':
|
||||
return { ext: 'gz', mime: 'application/gzip' };
|
||||
return { ext: 'tar.gz', mime: 'application/gzip' };
|
||||
case '0B77':
|
||||
return { ext: 'ac3', mime: 'audio/vnd.dolby.dd-raw' };
|
||||
case '7801':
|
||||
@@ -20,7 +17,7 @@ export function mimeFromHex (hex) {
|
||||
default:
|
||||
switch (hex.substring(0, 6)) { // 3 bytes
|
||||
case 'FFD8FF':
|
||||
return { ext: 'jpj', mime: 'image/jpeg' };
|
||||
return { ext: 'jpg', mime: 'image/jpeg' };
|
||||
case '4949BC':
|
||||
return { ext: 'jxr', mime: 'image/vnd.ms-photo' };
|
||||
case '425A68':
|
||||
@@ -39,21 +36,11 @@ export function mimeFromHex (hex) {
|
||||
return { ext: 'bpg', mime: 'image/bpg' };
|
||||
case '4D4D002A':
|
||||
return { ext: 'tif', mime: 'image/tiff' };
|
||||
case '00000100':
|
||||
return { ext: 'ico', mime: 'image/x-icon' };
|
||||
default:
|
||||
return { ext: '???', mime: 'unknown ' + hex };
|
||||
return { ext: '', mime: 'unknown ' + hex };
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function formatBytes (bytes, decimals = 2) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||||
}
|
||||
20
src/common/libs/sqlEscaper.js
Normal file
20
src/common/libs/sqlEscaper.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/* eslint-disable no-useless-escape */
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const pattern = /[\0\x08\x09\x1a\n\r"'\\\%]/gm;
|
||||
const regex = new RegExp(pattern);
|
||||
|
||||
/**
|
||||
* Escapes a string
|
||||
*
|
||||
* @param {String} string
|
||||
* @returns {String}
|
||||
*/
|
||||
function sqlEscaper (string) {
|
||||
return string.replace(regex, char => {
|
||||
const m = ['\\0', '\\x08', '\\x09', '\\x1a', '\\n', '\\r', '\'', '\"', '\\', '\\\\', '%'];
|
||||
const r = ['\\\\0', '\\\\b', '\\\\t', '\\\\z', '\\\\n', '\\\\r', '\\\'', '\\\"', '\\\\', '\\\\\\\\', '\\%'];
|
||||
return r[m.indexOf(char)] || char;
|
||||
});
|
||||
}
|
||||
|
||||
export { sqlEscaper };
|
||||
8
src/common/libs/uidGen.js
Normal file
8
src/common/libs/uidGen.js
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* @export
|
||||
* @param {String} [prefix]
|
||||
* @returns {String} Unique ID
|
||||
*/
|
||||
export function uidGen (prefix) {
|
||||
return (prefix ? `${prefix}:` : '') + Math.random().toString(36).substr(2, 9).toUpperCase();
|
||||
};
|
||||
@@ -2,45 +2,46 @@
|
||||
|
||||
import { app, BrowserWindow, nativeImage } from 'electron';
|
||||
import * as path from 'path';
|
||||
import crypto from 'crypto';
|
||||
import { format as formatUrl } from 'url';
|
||||
import keytar from 'keytar';
|
||||
import Store from 'electron-store';
|
||||
|
||||
import ipcHandlers from './ipc-handlers';
|
||||
|
||||
Store.initRenderer();
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
|
||||
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
|
||||
|
||||
// global reference to mainWindow (necessary to prevent window from being garbage collected)
|
||||
let mainWindow;
|
||||
|
||||
function createMainWindow () {
|
||||
async function createMainWindow () {
|
||||
const icon = require('../renderer/images/logo-32.png');
|
||||
const window = new BrowserWindow({
|
||||
width: 1600,
|
||||
height: 1000,
|
||||
minHeight: 550,
|
||||
width: 1024,
|
||||
height: 800,
|
||||
minWidth: 900,
|
||||
minHeight: 550,
|
||||
title: 'Antares',
|
||||
autoHideMenuBar: true,
|
||||
icon: nativeImage.createFromDataURL(icon.default),
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
'web-security': false
|
||||
'web-security': false,
|
||||
enableRemoteModule: true,
|
||||
spellcheck: false
|
||||
},
|
||||
frame: false,
|
||||
backgroundColor: '#1d1d1d'
|
||||
});
|
||||
|
||||
if (isDevelopment)
|
||||
window.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`);
|
||||
else {
|
||||
window.loadURL(formatUrl({
|
||||
pathname: path.join(__dirname, 'index.html'),
|
||||
protocol: 'file',
|
||||
slashes: true
|
||||
}));
|
||||
}
|
||||
|
||||
if (isDevelopment) {
|
||||
await window.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`);
|
||||
|
||||
const { default: installExtension, VUEJS_DEVTOOLS } = require('electron-devtools-installer');
|
||||
window.webContents.openDevTools();
|
||||
|
||||
@@ -52,6 +53,13 @@ function createMainWindow () {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
else {
|
||||
await window.loadURL(formatUrl({
|
||||
pathname: path.join(__dirname, 'index.html'),
|
||||
protocol: 'file',
|
||||
slashes: true
|
||||
}));
|
||||
}
|
||||
|
||||
window.on('closed', () => {
|
||||
mainWindow = null;
|
||||
@@ -64,26 +72,37 @@ function createMainWindow () {
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize ipcHandlers
|
||||
ipcHandlers();
|
||||
|
||||
return window;
|
||||
};
|
||||
|
||||
// quit application when all windows are closed
|
||||
app.on('window-all-closed', () => {
|
||||
// on macOS it is common for applications to stay open until the user explicitly quits
|
||||
if (process.platform !== 'darwin')
|
||||
app.quit();
|
||||
});
|
||||
if (!gotTheLock)
|
||||
app.quit();
|
||||
else {
|
||||
// Initialize ipcHandlers
|
||||
ipcHandlers();
|
||||
|
||||
// quit application when all windows are closed
|
||||
app.on('window-all-closed', () => {
|
||||
// on macOS it is common for applications to stay open until the user explicitly quits
|
||||
if (process.platform !== 'darwin')
|
||||
app.quit();
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
// on macOS it is common to re-create a window even after all windows have been closed
|
||||
if (mainWindow === null)
|
||||
mainWindow = createMainWindow();
|
||||
});
|
||||
|
||||
// create main BrowserWindow when electron is 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);
|
||||
}
|
||||
|
||||
app.on('activate', () => {
|
||||
// on macOS it is common to re-create a window even after all windows have been closed
|
||||
if (mainWindow === null)
|
||||
mainWindow = createMainWindow();
|
||||
});
|
||||
|
||||
// create main BrowserWindow when electron is ready
|
||||
app.on('ready', () => {
|
||||
mainWindow = createMainWindow();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
13
src/main/ipc-handlers/application.js
Normal file
13
src/main/ipc-handlers/application.js
Normal file
@@ -0,0 +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;
|
||||
});
|
||||
};
|
||||
@@ -1,25 +1,35 @@
|
||||
|
||||
import fs from 'fs';
|
||||
import { ipcMain } from 'electron';
|
||||
import { AntaresConnector } from '../libs/AntaresConnector';
|
||||
import InformationSchema from '../models/InformationSchema';
|
||||
import Generic from '../models/Generic';
|
||||
import { ClientsFactory } from '../libs/ClientsFactory';
|
||||
|
||||
export default (connections) => {
|
||||
ipcMain.handle('testConnection', async (event, conn) => {
|
||||
const Connection = new AntaresConnector({
|
||||
export default connections => {
|
||||
ipcMain.handle('test-connection', async (event, conn) => {
|
||||
const params = {
|
||||
host: conn.host,
|
||||
port: +conn.port,
|
||||
user: conn.user,
|
||||
password: conn.password
|
||||
};
|
||||
|
||||
if (conn.ssl) {
|
||||
params.ssl = {
|
||||
key: conn.key ? fs.readFileSync(conn.key) : null,
|
||||
cert: conn.cert ? fs.readFileSync(conn.cert) : null,
|
||||
ca: conn.ca ? fs.readFileSync(conn.ca) : null,
|
||||
ciphers: conn.ciphers
|
||||
};
|
||||
}
|
||||
|
||||
const connection = ClientsFactory.getConnection({
|
||||
client: conn.client,
|
||||
params: {
|
||||
host: conn.host,
|
||||
port: +conn.port,
|
||||
user: conn.user,
|
||||
password: conn.password
|
||||
}
|
||||
params
|
||||
});
|
||||
|
||||
await Connection.connect();
|
||||
await connection.connect();
|
||||
|
||||
try {
|
||||
await InformationSchema.testConnection(Connection);
|
||||
await connection.select('1+1').run();
|
||||
connection.destroy();
|
||||
|
||||
return { status: 'success' };
|
||||
}
|
||||
@@ -28,27 +38,40 @@ export default (connections) => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('checkConnection', async (event, uid) => {
|
||||
ipcMain.handle('check-connection', async (event, uid) => {
|
||||
return uid in connections;
|
||||
});
|
||||
|
||||
ipcMain.handle('connect', async (event, conn) => {
|
||||
const Connection = new AntaresConnector({
|
||||
const params = {
|
||||
host: conn.host,
|
||||
port: +conn.port,
|
||||
user: conn.user,
|
||||
password: conn.password
|
||||
};
|
||||
|
||||
if (conn.ssl) {
|
||||
params.ssl = {
|
||||
key: conn.key ? fs.readFileSync(conn.key) : null,
|
||||
cert: conn.cert ? fs.readFileSync(conn.cert) : null,
|
||||
ca: conn.ca ? fs.readFileSync(conn.ca) : null,
|
||||
ciphers: conn.ciphers
|
||||
};
|
||||
}
|
||||
|
||||
const connection = ClientsFactory.getConnection({
|
||||
client: conn.client,
|
||||
params: {
|
||||
host: conn.host,
|
||||
port: +conn.port,
|
||||
user: conn.user,
|
||||
password: conn.password
|
||||
},
|
||||
poolSize: 3
|
||||
params,
|
||||
poolSize: 1
|
||||
});
|
||||
|
||||
try {
|
||||
await Connection.connect();
|
||||
await connection.connect();
|
||||
|
||||
const structure = await connection.getStructure(new Set());
|
||||
|
||||
connections[conn.uid] = connection;
|
||||
|
||||
const { rows: structure } = await InformationSchema.getStructure(Connection);
|
||||
connections[conn.uid] = Connection;
|
||||
return { status: 'success', response: structure };
|
||||
}
|
||||
catch (err) {
|
||||
@@ -60,25 +83,4 @@ export default (connections) => {
|
||||
connections[uid].destroy();
|
||||
delete connections[uid];
|
||||
});
|
||||
|
||||
ipcMain.handle('refresh', async (event, uid) => {
|
||||
try {
|
||||
const { rows: structure } = await InformationSchema.getStructure(connections[uid]);
|
||||
return { status: 'success', response: structure };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('rawQuery', async (event, { uid, query, schema }) => {
|
||||
if (!query) return;
|
||||
try {
|
||||
const result = await Generic.raw(connections[uid], query, schema);
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
132
src/main/ipc-handlers/database.js
Normal file
132
src/main/ipc-handlers/database.js
Normal file
@@ -0,0 +1,132 @@
|
||||
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
export default connections => {
|
||||
ipcMain.handle('create-database', async (event, params) => {
|
||||
try {
|
||||
const query = `CREATE DATABASE \`${params.name}\` COLLATE ${params.collation}`;
|
||||
await connections[params.uid].raw(query);
|
||||
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('update-database', async (event, params) => {
|
||||
try {
|
||||
const query = `ALTER DATABASE \`${params.name}\` COLLATE ${params.collation}`;
|
||||
await connections[params.uid].raw(query);
|
||||
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('delete-database', async (event, params) => {
|
||||
try {
|
||||
const query = `DROP DATABASE \`${params.database}\``;
|
||||
await connections[params.uid].raw(query);
|
||||
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-database-collation', async (event, params) => { // TODO: move to mysql class
|
||||
try {
|
||||
const query = `SELECT \`DEFAULT_COLLATION_NAME\` FROM \`information_schema\`.\`SCHEMATA\` WHERE \`SCHEMA_NAME\`='${params.database}'`;
|
||||
const collation = await connections[params.uid].raw(query);
|
||||
|
||||
return { status: 'success', response: collation.rows.length ? collation.rows[0].DEFAULT_COLLATION_NAME : '' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-structure', async (event, params) => {
|
||||
try {
|
||||
const structure = await connections[params.uid].getStructure(params.schemas);
|
||||
|
||||
return { status: 'success', response: structure };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-collations', async (event, uid) => {
|
||||
try {
|
||||
const result = await connections[uid].getCollations();
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-variables', async (event, uid) => {
|
||||
try {
|
||||
const result = await connections[uid].getVariables();
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-engines', async (event, uid) => {
|
||||
try {
|
||||
const result = await connections[uid].getEngines();
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-version', async (event, uid) => {
|
||||
try {
|
||||
const result = await connections[uid].getVersion();
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('use-schema', async (event, { uid, schema }) => {
|
||||
if (!schema) return;
|
||||
|
||||
try {
|
||||
await connections[uid].use(schema);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('raw-query', async (event, { uid, query, schema }) => {
|
||||
if (!query) return;
|
||||
|
||||
try {
|
||||
const result = await connections[uid].raw(query, { nest: true, details: true });
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
||||
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,11 +1,27 @@
|
||||
import connection from './connection';
|
||||
import structure from './structure';
|
||||
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);
|
||||
structure(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() };
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -1,27 +0,0 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import InformationSchema from '../models/InformationSchema';
|
||||
import Generic from '../models/Generic';
|
||||
|
||||
// TODO: remap objects based on client
|
||||
|
||||
export default (connections) => {
|
||||
ipcMain.handle('getTableColumns', async (event, { uid, schema, table }) => {
|
||||
try {
|
||||
const result = await InformationSchema.getTableColumns(connections[uid], schema, table);
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('getTableData', async (event, { uid, schema, table }) => {
|
||||
try {
|
||||
const result = await Generic.getTableData(connections[uid], schema, table);
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
||||
346
src/main/ipc-handlers/tables.js
Normal file
346
src/main/ipc-handlers/tables.js
Normal file
@@ -0,0 +1,346 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import faker from 'faker';
|
||||
import moment from 'moment';
|
||||
import { sqlEscaper } from 'common/libs/sqlEscaper';
|
||||
import { TEXT, LONG_TEXT, NUMBER, FLOAT, BLOB, BIT, DATE, DATETIME } from 'common/fieldTypes';
|
||||
import fs from 'fs';
|
||||
|
||||
export default (connections) => {
|
||||
ipcMain.handle('get-table-columns', async (event, params) => {
|
||||
try {
|
||||
const result = await connections[params.uid].getTableColumns(params);
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-table-data', async (event, { uid, schema, table, sortParams }) => {
|
||||
try {
|
||||
const query = connections[uid]
|
||||
.select('*')
|
||||
.schema(schema)
|
||||
.from(table)
|
||||
.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 };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-table-indexes', async (event, params) => {
|
||||
try {
|
||||
const result = await connections[params.uid].getTableIndexes(params);
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-key-usage', async (event, params) => {
|
||||
try {
|
||||
const result = await connections[params.uid].getKeyUsage(params);
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('update-table-cell', async (event, params) => {
|
||||
try {
|
||||
let escapedParam;
|
||||
let reload = false;
|
||||
const id = typeof params.id === 'number' ? params.id : `"${params.id}"`;
|
||||
|
||||
if ([...NUMBER, ...FLOAT].includes(params.type))
|
||||
escapedParam = params.content;
|
||||
else if ([...TEXT, ...LONG_TEXT].includes(params.type))
|
||||
escapedParam = `"${sqlEscaper(params.content)}"`;
|
||||
else if (BLOB.includes(params.type)) {
|
||||
if (params.content) {
|
||||
const fileBlob = fs.readFileSync(params.content);
|
||||
escapedParam = `0x${fileBlob.toString('hex')}`;
|
||||
reload = true;
|
||||
}
|
||||
else
|
||||
escapedParam = '""';
|
||||
}
|
||||
else if ([...BIT].includes(params.type)) {
|
||||
escapedParam = `b'${sqlEscaper(params.content)}'`;
|
||||
reload = true;
|
||||
}
|
||||
else
|
||||
escapedParam = `"${sqlEscaper(params.content)}"`;
|
||||
|
||||
if (params.primary) {
|
||||
await connections[params.uid]
|
||||
.update({ [params.field]: `= ${escapedParam}` })
|
||||
.schema(params.schema)
|
||||
.from(params.table)
|
||||
.where({ [params.primary]: `= ${id}` })
|
||||
.run();
|
||||
}
|
||||
else {
|
||||
const { row } = params;
|
||||
reload = true;
|
||||
|
||||
for (const key in row) {
|
||||
if (typeof row[key] === 'string')
|
||||
row[key] = `'${row[key]}'`;
|
||||
|
||||
row[key] = `= ${row[key]}`;
|
||||
}
|
||||
|
||||
await connections[params.uid]
|
||||
.schema(params.schema)
|
||||
.update({ [params.field]: `= ${escapedParam}` })
|
||||
.from(params.table)
|
||||
.where(row)
|
||||
.limit(1)
|
||||
.run();
|
||||
}
|
||||
|
||||
return { status: 'success', response: { reload } };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('delete-table-rows', async (event, params) => {
|
||||
if (params.primary) {
|
||||
const idString = params.rows.map(row => typeof row[params.primary] === 'string'
|
||||
? `"${row[params.primary]}"`
|
||||
: row[params.primary]).join(',');
|
||||
|
||||
try {
|
||||
const result = await connections[params.uid]
|
||||
.schema(params.schema)
|
||||
.delete(params.table)
|
||||
.where({ [params.primary]: `IN (${idString})` })
|
||||
.run();
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
for (const row of params.rows) {
|
||||
for (const key in row) {
|
||||
if (typeof row[key] === 'string')
|
||||
row[key] = `'${row[key]}'`;
|
||||
|
||||
row[key] = `= ${row[key]}`;
|
||||
}
|
||||
|
||||
await connections[params.uid]
|
||||
.schema(params.schema)
|
||||
.delete(params.table)
|
||||
.where(row)
|
||||
.limit(1)
|
||||
.run();
|
||||
}
|
||||
|
||||
return { status: 'success', response: [] };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('insert-table-rows', async (event, params) => {
|
||||
try {
|
||||
const insertObj = {};
|
||||
for (const key in params.row) {
|
||||
const type = params.fields[key];
|
||||
let escapedParam;
|
||||
|
||||
if (params.row[key] === null)
|
||||
escapedParam = 'NULL';
|
||||
else if ([...NUMBER, ...FLOAT].includes(type))
|
||||
escapedParam = params.row[key];
|
||||
else if ([...TEXT, ...LONG_TEXT].includes(type))
|
||||
escapedParam = `"${sqlEscaper(params.row[key])}"`;
|
||||
else if (BLOB.includes(type)) {
|
||||
if (params.row[key]) {
|
||||
const fileBlob = fs.readFileSync(params.row[key]);
|
||||
escapedParam = `0x${fileBlob.toString('hex')}`;
|
||||
}
|
||||
else
|
||||
escapedParam = '""';
|
||||
}
|
||||
else
|
||||
escapedParam = `"${sqlEscaper(params.row[key])}"`;
|
||||
|
||||
insertObj[key] = escapedParam;
|
||||
}
|
||||
|
||||
const rows = new Array(+params.repeat).fill(insertObj);
|
||||
|
||||
await connections[params.uid]
|
||||
.schema(params.schema)
|
||||
.into(params.table)
|
||||
.insert(rows)
|
||||
.run();
|
||||
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('insert-table-fake-rows', async (event, params) => {
|
||||
try {
|
||||
const rows = [];
|
||||
|
||||
for (let i = 0; i < +params.repeat; i++) {
|
||||
const insertObj = {};
|
||||
|
||||
for (const key in params.row) {
|
||||
const type = params.fields[key];
|
||||
let escapedParam;
|
||||
|
||||
if (!('group' in params.row[key]) || params.row[key].group === 'manual') { // Manual value
|
||||
if (params.row[key].value === null || params.row[key].value === undefined)
|
||||
escapedParam = 'NULL';
|
||||
else if ([...NUMBER, ...FLOAT].includes(type))
|
||||
escapedParam = params.row[key].value;
|
||||
else if ([...TEXT, ...LONG_TEXT].includes(type))
|
||||
escapedParam = `"${sqlEscaper(params.row[key].value)}"`;
|
||||
else if (BLOB.includes(type)) {
|
||||
if (params.row[key].value) {
|
||||
const fileBlob = fs.readFileSync(params.row[key].value);
|
||||
escapedParam = `0x${fileBlob.toString('hex')}`;
|
||||
}
|
||||
else
|
||||
escapedParam = '""';
|
||||
}
|
||||
else if (BIT.includes(type))
|
||||
escapedParam = `b'${sqlEscaper(params.row[key].value)}'`;
|
||||
else
|
||||
escapedParam = `"${sqlEscaper(params.row[key].value)}"`;
|
||||
|
||||
insertObj[key] = escapedParam;
|
||||
}
|
||||
else { // Faker value
|
||||
const parsedParams = {};
|
||||
let fakeValue;
|
||||
|
||||
if (params.locale)
|
||||
faker.locale = params.locale;
|
||||
|
||||
if (Object.keys(params.row[key].params).length) {
|
||||
Object.keys(params.row[key].params).forEach(param => {
|
||||
if (!isNaN(params.row[key].params[param]))
|
||||
parsedParams[param] = +params.row[key].params[param];
|
||||
});
|
||||
fakeValue = faker[params.row[key].group][params.row[key].method](parsedParams);
|
||||
}
|
||||
else
|
||||
fakeValue = faker[params.row[key].group][params.row[key].method]();
|
||||
|
||||
if (typeof fakeValue === 'string') {
|
||||
if (params.row[key].length)
|
||||
fakeValue = fakeValue.substr(0, params.row[key].length);
|
||||
fakeValue = `"${sqlEscaper(fakeValue)}"`;
|
||||
}
|
||||
else if ([...DATE, ...DATETIME].includes(type))
|
||||
fakeValue = `"${moment(fakeValue).format('YYYY-MM-DD HH:mm:ss.SSSSSS')}"`;
|
||||
|
||||
insertObj[key] = fakeValue;
|
||||
}
|
||||
}
|
||||
|
||||
rows.push(insertObj);
|
||||
}
|
||||
|
||||
await connections[params.uid]
|
||||
.schema(params.schema)
|
||||
.into(params.table)
|
||||
.insert(rows)
|
||||
.run();
|
||||
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-foreign-list', async (event, { uid, schema, table, column, description }) => {
|
||||
try {
|
||||
const query = connections[uid]
|
||||
.select(`${column} AS foreignColumn`)
|
||||
.schema(schema)
|
||||
.from(table)
|
||||
.orderBy('foreignColumn ASC');
|
||||
|
||||
if (description)
|
||||
query.select(`LEFT(${description}, 20) AS foreignDescription`);
|
||||
|
||||
const results = await query.run();
|
||||
|
||||
return { status: 'success', response: results };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('create-table', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].createTable(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('alter-table', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].alterTable(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('truncate-table', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].truncateTable(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('drop-table', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].dropTable(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
||||
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() };
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -2,39 +2,40 @@ import { ipcMain } from 'electron';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
|
||||
let mainWindow;
|
||||
autoUpdater.allowPrerelease = true;
|
||||
|
||||
export default () => {
|
||||
ipcMain.on('checkForUpdates', event => {
|
||||
ipcMain.on('check-for-updates', event => {
|
||||
mainWindow = event;
|
||||
|
||||
autoUpdater.checkForUpdatesAndNotify().catch(() => {
|
||||
mainWindow.reply('checkFailed');
|
||||
mainWindow.reply('check-failed');
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.on('restartToUpdate', () => {
|
||||
ipcMain.on('restart-to-update', () => {
|
||||
autoUpdater.quitAndInstall();
|
||||
});
|
||||
|
||||
// auto-updater events
|
||||
autoUpdater.on('checking-for-update', () => {
|
||||
mainWindow.reply('checkingForUpdate');
|
||||
mainWindow.reply('checking-for-update');
|
||||
});
|
||||
|
||||
autoUpdater.on('update-available', () => {
|
||||
mainWindow.reply('updateAvailable');
|
||||
mainWindow.reply('update-available');
|
||||
});
|
||||
|
||||
autoUpdater.on('update-not-available', () => {
|
||||
mainWindow.reply('updateNotAvailable');
|
||||
mainWindow.reply('update-not-available');
|
||||
});
|
||||
|
||||
autoUpdater.on('download-progress', (data) => {
|
||||
mainWindow.reply('downloadProgress', data);
|
||||
autoUpdater.on('download-progress', data => {
|
||||
mainWindow.reply('download-progress', data);
|
||||
});
|
||||
|
||||
autoUpdater.on('update-downloaded', () => {
|
||||
mainWindow.reply('updateDownloaded');
|
||||
mainWindow.reply('update-downloaded');
|
||||
});
|
||||
|
||||
autoUpdater.logger = require('electron-log');
|
||||
|
||||
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() };
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -1,268 +0,0 @@
|
||||
'use strict';
|
||||
import mysql from 'mysql';
|
||||
import mssql from 'mssql';
|
||||
// import pg from 'pg'; TODO: PostgreSQL
|
||||
|
||||
/**
|
||||
* As Simple As Possible Query Builder
|
||||
*
|
||||
* @export
|
||||
* @class AntaresConnector
|
||||
*/
|
||||
export class AntaresConnector {
|
||||
/**
|
||||
*Creates an instance of AntaresConnector.
|
||||
* @param {Object} args connection params
|
||||
* @memberof AntaresConnector
|
||||
*/
|
||||
constructor (args) {
|
||||
this._client = args.client;
|
||||
this._params = args.params;
|
||||
this._poolSize = args.poolSize || false;
|
||||
this._connection = null;
|
||||
|
||||
this._queryDefaults = {
|
||||
schema: '',
|
||||
select: [],
|
||||
from: '',
|
||||
where: [],
|
||||
groupBy: [],
|
||||
orderBy: [],
|
||||
limit: [],
|
||||
join: [],
|
||||
update: [],
|
||||
insert: [],
|
||||
delete: []
|
||||
};
|
||||
this._query = Object.assign({}, this._queryDefaults);
|
||||
}
|
||||
|
||||
_reducer (acc, curr) {
|
||||
const type = typeof curr;
|
||||
|
||||
switch (type) {
|
||||
case 'number':
|
||||
case 'string':
|
||||
return [...acc, curr];
|
||||
case 'object':
|
||||
if (Array.isArray(curr))
|
||||
return [...acc, ...curr];
|
||||
else {
|
||||
const clausoles = [];
|
||||
for (const key in curr)
|
||||
clausoles.push(`${key} ${curr[key]}`);
|
||||
|
||||
return clausoles;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the query object after a query
|
||||
*
|
||||
* @memberof AntaresConnector
|
||||
*/
|
||||
_resetQuery () {
|
||||
this._query = Object.assign({}, this._queryDefaults);
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof AntaresConnector
|
||||
*/
|
||||
async connect () {
|
||||
switch (this._client) {
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
if (!this._poolSize) {
|
||||
const connection = mysql.createConnection(this._params);
|
||||
this._connection = connection.promise();
|
||||
}
|
||||
else
|
||||
this._connection = mysql.createPool({ ...this._params, connectionLimit: this._poolSize });
|
||||
// this._connection = pool.promise();
|
||||
|
||||
break;
|
||||
case 'mssql': {
|
||||
const mssqlParams = {
|
||||
user: this._params.user,
|
||||
password: this._params.password,
|
||||
server: this._params.host
|
||||
};
|
||||
this._connection = await mssql.connect(mssqlParams);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
schema (schema) {
|
||||
this._query.schema = schema;
|
||||
return this;
|
||||
}
|
||||
|
||||
select (...args) {
|
||||
this._query.select = [...this._query.select, ...args];
|
||||
return this;
|
||||
}
|
||||
|
||||
from (table) {
|
||||
this._query.from = table;
|
||||
return this;
|
||||
}
|
||||
|
||||
where (...args) {
|
||||
this._query.where = [...this._query.where, ...args];
|
||||
return this;
|
||||
}
|
||||
|
||||
groupBy (...args) {
|
||||
this._query.groupBy = [...this._query.groupBy, ...args];
|
||||
return this;
|
||||
}
|
||||
|
||||
orderBy (...args) {
|
||||
this._query.orderBy = [...this._query.orderBy, ...args];
|
||||
return this;
|
||||
}
|
||||
|
||||
limit (...args) {
|
||||
this._query.limit = args;
|
||||
return this;
|
||||
}
|
||||
|
||||
use (schema) {
|
||||
let sql;
|
||||
|
||||
switch (this._client) {
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
sql = `USE \`${schema}\``;
|
||||
break;
|
||||
case 'mssql':
|
||||
sql = `USE "${schema}"`;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return this.raw(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} SQL string
|
||||
* @memberof AntaresConnector
|
||||
*/
|
||||
getSQL () {
|
||||
// SELECT
|
||||
const selectArray = this._query.select.reduce(this._reducer, []);
|
||||
let selectRaw;
|
||||
switch (this._client) {
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
selectRaw = selectArray.length ? `SELECT ${selectArray.join(', ')} ` : 'SELECT * ';
|
||||
break;
|
||||
case 'mssql': {
|
||||
const topRaw = this._query.limit.length ? ` TOP (${this._query.limit[0]}) ` : '';
|
||||
selectRaw = selectArray.length ? `SELECT${topRaw} ${selectArray.join(', ')} ` : 'SELECT * ';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// FROM
|
||||
let fromRaw;
|
||||
switch (this._client) {
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
fromRaw = this._query.from ? `FROM ${this._query.schema ? `\`${this._query.schema}\`.` : ''}\`${this._query.from}\` ` : '';
|
||||
break;
|
||||
case 'mssql':
|
||||
fromRaw = this._query.from ? `FROM ${this._query.schema ? `${this._query.schema}.` : ''}${this._query.from} ` : '';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const whereArray = this._query.where.reduce(this._reducer, []);
|
||||
const whereRaw = whereArray.length ? `WHERE ${whereArray.join(' AND ')} ` : '';
|
||||
const groupByArray = this._query.groupBy.reduce(this._reducer, []);
|
||||
const groupByRaw = groupByArray.length ? `GROUP BY ${groupByArray.join(', ')} ` : '';
|
||||
const orderByArray = this._query.orderBy.reduce(this._reducer, []);
|
||||
const orderByRaw = orderByArray.length ? `ORDER BY ${orderByArray.join(', ')} ` : '';
|
||||
|
||||
// LIMIT
|
||||
let limitRaw;
|
||||
switch (this._client) {
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
limitRaw = this._query.limit.length ? `LIMIT ${this._query.limit.join(', ')} ` : '';
|
||||
break;
|
||||
case 'mssql':
|
||||
limitRaw = '';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return `${selectRaw}${fromRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise}
|
||||
* @memberof AntaresConnector
|
||||
*/
|
||||
async run () {
|
||||
const rawQuery = this.getSQL();
|
||||
this._resetQuery();
|
||||
return this.raw(rawQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} sql raw SQL query
|
||||
* @returns {Promise}
|
||||
* @memberof AntaresConnector
|
||||
*/
|
||||
async raw (sql) {
|
||||
if (process.env.NODE_ENV === 'development') console.log(sql);
|
||||
|
||||
switch (this._client) {
|
||||
case 'maria':
|
||||
case 'mysql': {
|
||||
const { rows, fields } = await new Promise((resolve, reject) => {
|
||||
this._connection.query(sql, (err, rows, fields) => {
|
||||
if (err)
|
||||
reject(err);
|
||||
else
|
||||
resolve({ rows, fields });
|
||||
});
|
||||
});
|
||||
return { rows, fields };
|
||||
}
|
||||
case 'mssql': {
|
||||
const results = await this._connection.request().query(sql);
|
||||
return { rows: results.recordsets[0] };// TODO: fields
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof AntaresConnector
|
||||
*/
|
||||
destroy () {
|
||||
switch (this._client) {
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
this._connection.end();
|
||||
break;
|
||||
case 'mssql':
|
||||
this._connection.close();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
142
src/main/libs/AntaresCore.js
Normal file
142
src/main/libs/AntaresCore.js
Normal file
@@ -0,0 +1,142 @@
|
||||
'use strict';
|
||||
/**
|
||||
* As Simple As Possible Query Builder Core
|
||||
*
|
||||
* @class AntaresCore
|
||||
*/
|
||||
export class AntaresCore {
|
||||
/**
|
||||
* Creates an instance of AntaresCore.
|
||||
*
|
||||
* @param {Object} args connection params
|
||||
* @memberof AntaresCore
|
||||
*/
|
||||
constructor (args) {
|
||||
this._client = args.client;
|
||||
this._params = args.params;
|
||||
this._poolSize = args.poolSize || false;
|
||||
this._connection = null;
|
||||
this._logger = args.logger || console.log;
|
||||
|
||||
this._queryDefaults = {
|
||||
schema: '',
|
||||
select: [],
|
||||
from: '',
|
||||
where: [],
|
||||
groupBy: [],
|
||||
orderBy: [],
|
||||
limit: [],
|
||||
join: [],
|
||||
update: [],
|
||||
insert: [],
|
||||
delete: false
|
||||
};
|
||||
this._query = Object.assign({}, this._queryDefaults);
|
||||
}
|
||||
|
||||
_reducer (acc, curr) {
|
||||
const type = typeof curr;
|
||||
|
||||
switch (type) {
|
||||
case 'number':
|
||||
case 'string':
|
||||
return [...acc, curr];
|
||||
case 'object':
|
||||
if (Array.isArray(curr))
|
||||
return [...acc, ...curr];
|
||||
else {
|
||||
const clausoles = [];
|
||||
for (const key in curr)
|
||||
clausoles.push(`${key} ${curr[key]}`);
|
||||
|
||||
return clausoles;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the query object after a query
|
||||
*
|
||||
* @memberof AntaresCore
|
||||
*/
|
||||
_resetQuery () {
|
||||
this._query = Object.assign({}, this._queryDefaults);
|
||||
}
|
||||
|
||||
schema (schema) {
|
||||
this._query.schema = schema;
|
||||
return this;
|
||||
}
|
||||
|
||||
select (...args) {
|
||||
this._query.select = [...this._query.select, ...args];
|
||||
return this;
|
||||
}
|
||||
|
||||
from (table) {
|
||||
this._query.from = table;
|
||||
return this;
|
||||
}
|
||||
|
||||
into (table) {
|
||||
this._query.from = table;
|
||||
return this;
|
||||
}
|
||||
|
||||
delete (table) {
|
||||
this._query.delete = true;
|
||||
this.from(table);
|
||||
return this;
|
||||
}
|
||||
|
||||
where (...args) {
|
||||
this._query.where = [...this._query.where, ...args];
|
||||
return this;
|
||||
}
|
||||
|
||||
groupBy (...args) {
|
||||
this._query.groupBy = [...this._query.groupBy, ...args];
|
||||
return this;
|
||||
}
|
||||
|
||||
orderBy (...args) {
|
||||
this._query.orderBy = [...this._query.orderBy, ...args];
|
||||
return this;
|
||||
}
|
||||
|
||||
limit (...args) {
|
||||
this._query.limit = args;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String | Array} args field = value
|
||||
* @returns
|
||||
* @memberof AntaresCore
|
||||
*/
|
||||
update (...args) {
|
||||
this._query.update = [...this._query.update, ...args];
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array} arr Array of row objects
|
||||
* @returns
|
||||
* @memberof AntaresCore
|
||||
*/
|
||||
insert (arr) {
|
||||
this._query.insert = [...this._query.insert, ...arr];
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @returns {Promise}
|
||||
* @memberof AntaresCore
|
||||
*/
|
||||
async run (args) {
|
||||
const rawQuery = this.getSQL();
|
||||
this._resetQuery();
|
||||
return this.raw(rawQuery, args);
|
||||
}
|
||||
}
|
||||
27
src/main/libs/ClientsFactory.js
Normal file
27
src/main/libs/ClientsFactory.js
Normal file
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
import { MySQLClient } from './clients/MySQLClient';
|
||||
|
||||
export class ClientsFactory {
|
||||
/**
|
||||
* Returns a database connection based on received args.
|
||||
*
|
||||
* @param {Object} args
|
||||
* @param {String} args.client
|
||||
* @param {Object} args.params
|
||||
* @param {String} args.params.host
|
||||
* @param {Number} args.params.port
|
||||
* @param {String} args.params.password
|
||||
* @param {Number=} args.poolSize
|
||||
* @returns Database Connection
|
||||
* @memberof ClientsFactory
|
||||
*/
|
||||
static getConnection (args) {
|
||||
switch (args.client) {
|
||||
case 'mysql':
|
||||
case 'maria':
|
||||
return new MySQLClient(args);
|
||||
default:
|
||||
return new Error(`Unknown database client: ${args.client}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
1294
src/main/libs/clients/MySQLClient.js
Normal file
1294
src/main/libs/clients/MySQLClient.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,23 +0,0 @@
|
||||
'use strict';
|
||||
export default class {
|
||||
static async raw (connection, query, schema) {
|
||||
if (schema) {
|
||||
try {
|
||||
await connection.use(schema);
|
||||
}
|
||||
catch (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return connection.raw(query);
|
||||
}
|
||||
|
||||
static async getTableData (connection, schema, table) {
|
||||
return connection
|
||||
.select('*')
|
||||
.schema(schema)
|
||||
.from(table)
|
||||
.limit(1000)
|
||||
.run();
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
'use strict';
|
||||
export default class {
|
||||
static testConnection (connection) {
|
||||
return connection.select('1+1').run();
|
||||
}
|
||||
|
||||
static getStructure (connection) {
|
||||
return connection
|
||||
.select('*')
|
||||
.schema('information_schema')
|
||||
.from('TABLES')
|
||||
.orderBy({ TABLE_SCHEMA: 'ASC', TABLE_NAME: 'ASC' })
|
||||
.run();
|
||||
}
|
||||
|
||||
static getTableColumns (connection, schema, table) {
|
||||
return connection
|
||||
.select('*')
|
||||
.schema('information_schema')
|
||||
.from('COLUMNS')
|
||||
.where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'` })
|
||||
.orderBy({ ORDINAL_POSITION: 'ASC' })
|
||||
.run();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<div id="window-content">
|
||||
<TheSettingBar />
|
||||
<div id="main-content" class="container">
|
||||
<TheAppWelcome v-if="!connections.length" @newConn="showNewConnModal" />
|
||||
<TheAppWelcome v-if="!connections.length" @new-conn="showNewConnModal" />
|
||||
<div v-else class="columns col-gapless">
|
||||
<Workspace
|
||||
v-for="connection in connections"
|
||||
@@ -16,15 +16,15 @@
|
||||
<TheFooter />
|
||||
<TheNotificationsBoard />
|
||||
<ModalNewConnection v-if="isNewConnModal" />
|
||||
<ModalEditConnection v-if="isEditModal" />
|
||||
<ModalSettings v-if="isSettingModal" />
|
||||
<ModalDiscardChanges v-if="isUnsavedDiscardModal" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { ipcRenderer, remote } from 'electron';
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
@@ -36,12 +36,11 @@ export default {
|
||||
TheAppWelcome: () => import(/* webpackChunkName: "TheAppWelcome" */'@/components/TheAppWelcome'),
|
||||
Workspace: () => import(/* webpackChunkName: "Workspace" */'@/components/Workspace'),
|
||||
ModalNewConnection: () => import(/* webpackChunkName: "ModalNewConnection" */'@/components/ModalNewConnection'),
|
||||
ModalEditConnection: () => import(/* webpackChunkName: "ModalEditConnection" */'@/components/ModalEditConnection'),
|
||||
ModalSettings: () => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings')
|
||||
ModalSettings: () => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings'),
|
||||
ModalDiscardChanges: () => import(/* webpackChunkName: "ModalDiscardChanges" */'@/components/ModalDiscardChanges')
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
};
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
@@ -49,11 +48,51 @@ export default {
|
||||
isNewConnModal: 'application/isNewModal',
|
||||
isEditModal: 'application/isEditModal',
|
||||
isSettingModal: 'application/isSettingModal',
|
||||
connections: 'connections/getConnections'
|
||||
connections: 'connections/getConnections',
|
||||
isUnsavedDiscardModal: 'workspaces/isUnsavedDiscardModal'
|
||||
})
|
||||
},
|
||||
mounted () {
|
||||
ipcRenderer.send('checkForUpdates');
|
||||
ipcRenderer.send('check-for-updates');
|
||||
|
||||
const Menu = remote.Menu;
|
||||
|
||||
const InputMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: this.$t('word.cut'),
|
||||
role: 'cut'
|
||||
},
|
||||
{
|
||||
label: this.$t('word.copy'),
|
||||
role: 'copy'
|
||||
},
|
||||
{
|
||||
label: this.$t('word.paste'),
|
||||
role: 'paste'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: this.$t('message.selectAll'),
|
||||
role: 'selectall'
|
||||
}
|
||||
]);
|
||||
|
||||
document.body.addEventListener('contextmenu', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
let node = e.target;
|
||||
|
||||
while (node) {
|
||||
if (node.nodeName.match(/^(input|textarea)$/i) || node.isContentEditable) {
|
||||
InputMenu.popup(remote.getCurrentWindow());
|
||||
break;
|
||||
}
|
||||
node = node.parentNode;
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
@@ -64,30 +103,30 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
html,
|
||||
body{
|
||||
height: 100%;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#wrapper{
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
#wrapper {
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#window-content{
|
||||
display: flex;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
#window-content {
|
||||
display: flex;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#main-content {
|
||||
padding: 0;
|
||||
justify-content: flex-start;
|
||||
height: calc(100vh - #{$excluding-size});
|
||||
width: calc(100% - #{$settingbar-width});
|
||||
#main-content {
|
||||
padding: 0;
|
||||
justify-content: flex-start;
|
||||
height: calc(100vh - #{$excluding-size});
|
||||
width: calc(100% - #{$settingbar-width});
|
||||
|
||||
> .columns{
|
||||
height: calc(100vh - #{$footer-height});
|
||||
}
|
||||
}
|
||||
> .columns {
|
||||
height: calc(100vh - #{$footer-height});
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="modal modal-sm active">
|
||||
<div class="modal active" :class="modalSizeClass">
|
||||
<a class="modal-overlay" @click="hideModal" />
|
||||
<div class="modal-container">
|
||||
<div v-if="hasHeader" class="modal-header">
|
||||
<div v-if="hasHeader" class="modal-header pl-2">
|
||||
<div class="modal-title h6">
|
||||
<slot name="header" />
|
||||
</div>
|
||||
@@ -24,18 +24,18 @@
|
||||
<slot name="body" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="modal-footer pt-0">
|
||||
<button
|
||||
class="btn btn-primary mr-2"
|
||||
@click="confirmModal"
|
||||
@click.stop="confirmModal"
|
||||
>
|
||||
{{ $t('word.confirm') }}
|
||||
{{ confirmText || $t('word.confirm') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-link"
|
||||
@click="hideModal"
|
||||
>
|
||||
{{ $t('word.cancel') }}
|
||||
{{ cancelText || $t('word.cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -45,6 +45,15 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'BaseConfirmModal',
|
||||
props: {
|
||||
size: {
|
||||
type: String,
|
||||
validator: prop => ['small', 'medium', '400', 'large'].includes(prop),
|
||||
default: 'small'
|
||||
},
|
||||
confirmText: String,
|
||||
cancelText: String
|
||||
},
|
||||
computed: {
|
||||
hasHeader () {
|
||||
return !!this.$slots.header;
|
||||
@@ -54,6 +63,15 @@ export default {
|
||||
},
|
||||
hasDefault () {
|
||||
return !!this.$slots.default;
|
||||
},
|
||||
modalSizeClass () {
|
||||
if (this.size === 'small')
|
||||
return 'modal-sm';
|
||||
if (this.size === '400')
|
||||
return 'modal-400';
|
||||
else if (this.size === 'large')
|
||||
return 'modal-lg';
|
||||
else return '';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -70,7 +88,12 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal.modal-sm .modal-container{
|
||||
padding: 0;
|
||||
}
|
||||
.modal-400 .modal-container {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.modal.modal-sm .modal-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<template>
|
||||
<div class="context">
|
||||
<a
|
||||
class="context-overlay"
|
||||
@click="close"
|
||||
@contextmenu="close"
|
||||
/>
|
||||
<div
|
||||
v-click-outside="close"
|
||||
ref="contextContent"
|
||||
class="context-container"
|
||||
:style="position"
|
||||
>
|
||||
@@ -11,83 +16,134 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ClickOutside from 'vue-click-outside';
|
||||
|
||||
export default {
|
||||
name: 'BaseContextMenu',
|
||||
directives: {
|
||||
ClickOutside
|
||||
},
|
||||
props: {
|
||||
contextEvent: MouseEvent
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
contextSize: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
position () {
|
||||
return { // TODO: calc direction if near corners
|
||||
top: this.contextEvent.clientY + 5 + 'px',
|
||||
left: this.contextEvent.clientX + 5 + 'px'
|
||||
const { clientY, clientX } = this.contextEvent;
|
||||
let topCord = `${clientY + 5}px`;
|
||||
let leftCord = `${clientX + 5}px`;
|
||||
|
||||
if (this.contextSize) {
|
||||
if (clientY + this.contextSize.height + 5 >= window.innerHeight)
|
||||
topCord = `${clientY - this.contextSize.height}px`;
|
||||
|
||||
if (clientX + this.contextSize.width + 5 >= window.innerWidth)
|
||||
leftCord = `${clientX - this.contextSize.width}px`;
|
||||
}
|
||||
|
||||
return {
|
||||
top: topCord,
|
||||
left: leftCord
|
||||
};
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
mounted () {
|
||||
this.contextSize = this.$refs.contextContent.getBoundingClientRect();
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
this.$emit('closeContext');
|
||||
this.$emit('close-context');
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
</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;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
pointer-events: initial;
|
||||
|
||||
.context-element {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
z-index: 400;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
padding: 0.4rem;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
pointer-events: none;
|
||||
padding: 0.1rem 0.3rem;
|
||||
cursor: pointer;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
|
||||
.context-container{
|
||||
min-width: 100px;
|
||||
max-width: 150px;
|
||||
z-index: 1;
|
||||
box-shadow: 0px 0px 1px 0px #000;
|
||||
padding: 0;
|
||||
background: #1d1d1d;
|
||||
border-radius: 0.1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
pointer-events: initial;
|
||||
|
||||
.context-element{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: .1rem .3rem;
|
||||
cursor: pointer;
|
||||
|
||||
&: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;
|
||||
}
|
||||
|
||||
.context-overlay{
|
||||
background: transparent;
|
||||
bottom: 0;
|
||||
cursor: default;
|
||||
display: block;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
&:hover {
|
||||
background: $primary-color;
|
||||
|
||||
.context-submenu {
|
||||
display: block;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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,14 +1,15 @@
|
||||
<template>
|
||||
<div class="toast mt-2" :class="notificationStatus.className">
|
||||
<span class="p-vcentered text-left" :class="{'expanded': isExpanded}">
|
||||
<i class="material-icons mr-1">{{ notificationStatus.iconName }}</i>
|
||||
<i class="mdi mdi-24px mr-2" :class="notificationStatus.iconName" />
|
||||
<span class="notification-message">{{ message }}</span>
|
||||
</span>
|
||||
<i
|
||||
v-if="isExpandable"
|
||||
class="material-icons c-hand"
|
||||
class="mdi mdi-24px c-hand expand-btn"
|
||||
:class="isExpanded ? 'mdi-chevron-up' : 'mdi-chevron-down'"
|
||||
@click="toggleExpand"
|
||||
>{{ isExpanded ? 'expand_less' : 'expand_more' }}</i>
|
||||
/>
|
||||
<button class="btn btn-clear ml-2" @click="hideToast" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -38,19 +39,19 @@ export default {
|
||||
switch (this.status) {
|
||||
case 'success':
|
||||
className = 'toast-success';
|
||||
iconName = 'done';
|
||||
iconName = 'mdi-check';
|
||||
break;
|
||||
case 'error':
|
||||
className = 'toast-error';
|
||||
iconName = 'error';
|
||||
iconName = 'mdi-alert-rhombus';
|
||||
break;
|
||||
case 'warning':
|
||||
className = 'toast-warning';
|
||||
iconName = 'warning';
|
||||
iconName = 'mdi-alert';
|
||||
break;
|
||||
case 'primary':
|
||||
className = 'toast-primary';
|
||||
iconName = 'info_outline';
|
||||
iconName = 'mdi-information-outline';
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -71,25 +72,28 @@ export default {
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.toast{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
user-select: text;
|
||||
word-break: break-all;
|
||||
width: fit-content;
|
||||
margin-left: auto;
|
||||
}
|
||||
.toast {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
user-select: text;
|
||||
word-break: break-all;
|
||||
width: fit-content;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.notification-message{
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: inline-block;
|
||||
max-width: 30rem;
|
||||
user-select: none;
|
||||
}
|
||||
.notification-message {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: inline-block;
|
||||
max-width: 30rem;
|
||||
}
|
||||
|
||||
.expanded .notification-message{
|
||||
white-space: initial;
|
||||
}
|
||||
.expand-btn {
|
||||
align-items: initial;
|
||||
}
|
||||
|
||||
.expanded .notification-message {
|
||||
white-space: initial;
|
||||
}
|
||||
</style>
|
||||
|
||||
129
src/renderer/components/BaseTextEditor.vue
Normal file
129
src/renderer/components/BaseTextEditor.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<div class="editor-wrapper">
|
||||
<div
|
||||
:id="`editor-${id}`"
|
||||
class="editor"
|
||||
:class="editorClass"
|
||||
:style="{height: `${height}px`}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as ace from 'ace-builds';
|
||||
import 'ace-builds/webpack-resolver';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'BaseTextEditor',
|
||||
props: {
|
||||
value: String,
|
||||
mode: { type: String, default: 'text' },
|
||||
editorClass: { type: String, default: '' },
|
||||
autoFocus: { type: Boolean, default: false },
|
||||
readOnly: { type: Boolean, default: false },
|
||||
height: { type: Number, default: 200 }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
editor: null,
|
||||
id: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
editorTheme: 'settings/getEditorTheme',
|
||||
autoComplete: 'settings/getAutoComplete',
|
||||
lineWrap: 'settings/getLineWrap'
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
mode () {
|
||||
if (this.editor)
|
||||
this.editor.session.setMode(`ace/mode/${this.mode}`);
|
||||
},
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.id = this._uid;
|
||||
},
|
||||
mounted () {
|
||||
this.editor = ace.edit(`editor-${this.id}`, {
|
||||
mode: `ace/mode/${this.mode}`,
|
||||
theme: `ace/theme/${this.editorTheme}`,
|
||||
value: this.value,
|
||||
fontSize: '14px',
|
||||
printMargin: false,
|
||||
readOnly: this.readOnly
|
||||
});
|
||||
|
||||
this.editor.setOptions({
|
||||
enableBasicAutocompletion: false,
|
||||
wrap: this.lineWrap,
|
||||
enableSnippets: false,
|
||||
enableLiveAutocompletion: false
|
||||
});
|
||||
|
||||
this.editor.session.on('change', () => {
|
||||
const content = this.editor.getValue();
|
||||
this.$emit('update:value', content);
|
||||
});
|
||||
|
||||
if (this.autoFocus) {
|
||||
setTimeout(() => {
|
||||
this.editor.focus();
|
||||
this.editor.resize();
|
||||
}, 20);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.editor.resize();
|
||||
}, 20);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.editor-wrapper {
|
||||
border-bottom: 1px solid #444;
|
||||
|
||||
.editor {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.ace_.mdi {
|
||||
display: inline-block;
|
||||
width: 17px;
|
||||
}
|
||||
|
||||
.ace_dark.ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {
|
||||
background-color: #c9561a99;
|
||||
}
|
||||
|
||||
.ace_dark.ace_editor.ace_autocomplete .ace_marker-layer .ace_line-hover {
|
||||
background-color: #c9571a33;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.ace_dark.ace_editor.ace_autocomplete .ace_completion-highlight {
|
||||
color: #e0d00c;
|
||||
}
|
||||
</style>
|
||||
@@ -34,19 +34,19 @@ export default {
|
||||
switch (this.status) {
|
||||
case 'success':
|
||||
className = 'toast-success';
|
||||
iconTag = '<i class="material-icons mr-1">done</i>';
|
||||
iconTag = '<i class="mdi mdi-24px mdi-check mr-1"></i>';
|
||||
break;
|
||||
case 'error':
|
||||
className = 'toast-error';
|
||||
iconTag = '<i class="material-icons mr-1">error</i>';
|
||||
iconTag = '<i class="mdi mdi-24px mdi-alert-rhombus mr-1"></i>';
|
||||
break;
|
||||
case 'warning':
|
||||
className = 'toast-warning';
|
||||
iconTag = '<i class="material-icons mr-1">warning</i>';
|
||||
iconTag = '<i class="mdi mdi-24px mdi-alert mr-1"></i>';
|
||||
break;
|
||||
case 'primary':
|
||||
className = 'toast-primary';
|
||||
iconTag = '<i class="material-icons mr-1">info_outline</i>';
|
||||
iconTag = '<i class="mdi mdi-24px mdi-information-outline mr-1"></i>';
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -70,10 +70,10 @@ export default {
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.toast{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
user-select: text;
|
||||
word-break: break-all;
|
||||
}
|
||||
.toast {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
user-select: text;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
|
||||
112
src/renderer/components/BaseUploadInput.vue
Normal file
112
src/renderer/components/BaseUploadInput.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<label :for="`id_${id}`" class="file-uploader">
|
||||
<span class="file-uploader-message">
|
||||
<i class="mdi mdi-folder-open mr-1" />{{ message }}
|
||||
</span>
|
||||
<span class="text-ellipsis file-uploader-value">
|
||||
{{ value | lastPart }}
|
||||
</span>
|
||||
<i
|
||||
v-if="value.length"
|
||||
class="file-uploader-reset mdi mdi-close"
|
||||
@click.prevent="clear"
|
||||
/>
|
||||
<form :ref="`form_${id}`">
|
||||
<input
|
||||
:id="`id_${id}`"
|
||||
class="file-uploader-input"
|
||||
type="file"
|
||||
@change="$emit('change', $event)"
|
||||
>
|
||||
</form>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BaseUploadInput',
|
||||
filters: {
|
||||
lastPart (string) {
|
||||
if (!string) return '';
|
||||
|
||||
string = string.split(/[/\\]+/).pop();
|
||||
if (string.length >= 19)
|
||||
string = `...${string.slice(-19)}`;
|
||||
return string;
|
||||
}
|
||||
},
|
||||
props: {
|
||||
message: {
|
||||
default: 'Browse',
|
||||
type: String
|
||||
},
|
||||
value: {
|
||||
default: '',
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
id: null
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.id = this._uid;
|
||||
},
|
||||
methods: {
|
||||
clear () {
|
||||
this.$emit('clear');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.file-uploader {
|
||||
border: 0.05rem solid $bg-color-light;
|
||||
border-radius: 0.1rem;
|
||||
height: 1.8rem;
|
||||
line-height: 1.2rem;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
background-color: $bg-color-gray;
|
||||
transition: background 0.2s, border 0.2s, box-shadow 0.2s, color 0.2s;
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
|
||||
> span {
|
||||
padding: 0.25rem 0.4rem;
|
||||
}
|
||||
|
||||
.file-uploader-message {
|
||||
display: flex;
|
||||
border-right: 0.05rem solid $bg-color-light;
|
||||
background-color: $bg-color;
|
||||
}
|
||||
|
||||
.file-uploader-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.file-uploader-value {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.file-uploader-reset {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: calc(50% - 8px);
|
||||
}
|
||||
}
|
||||
|
||||
:disabled {
|
||||
.file-uploader {
|
||||
cursor: not-allowed;
|
||||
background-color: #151515;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -21,47 +21,50 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// credits: https://github.com/xrado 👼
|
||||
export default {
|
||||
name: 'BaseVirtualScroll',
|
||||
props: {
|
||||
items: Array,
|
||||
itemHeight: Number
|
||||
itemHeight: Number,
|
||||
visibleHeight: Number,
|
||||
scrollElement: {
|
||||
type: HTMLDivElement,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
topHeight: 0,
|
||||
bottomHeight: 0,
|
||||
visibleItems: []
|
||||
visibleItems: [],
|
||||
renderTimeout: null,
|
||||
localScrollElement: null
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this._checkScrollPosition = this.checkScrollPosition.bind(this);
|
||||
this.checkScrollPosition();
|
||||
this.$el.addEventListener('scroll', this._checkScrollPosition);
|
||||
this.$el.addEventListener('wheel', this._checkScrollPosition);
|
||||
this.localScrollElement = this.scrollElement ? this.scrollElement : this.$el;
|
||||
this.updateWindow();
|
||||
this.localScrollElement.addEventListener('scroll', this._checkScrollPosition);
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$el.removeEventListener('scroll', this._checkScrollPosition);
|
||||
this.$el.removeEventListener('wheel', this._checkScrollPosition);
|
||||
this.localScrollElement.removeEventListener('scroll', this._checkScrollPosition);
|
||||
},
|
||||
methods: {
|
||||
checkScrollPosition (e = {}) {
|
||||
const el = this.$el;
|
||||
checkScrollPosition (e) {
|
||||
clearTimeout(this.renderTimeout);
|
||||
|
||||
// prevent parent scroll
|
||||
if ((el.scrollTop === 0 && e.deltaY < 0) || (Math.abs(el.scrollTop - (el.scrollHeight - el.clientHeight)) <= 1 && e.deltaY > 0))
|
||||
e.preventDefault();
|
||||
|
||||
this.updateWindow(e);
|
||||
this.renderTimeout = setTimeout(() => {
|
||||
this.updateWindow(e);
|
||||
}, 200);
|
||||
},
|
||||
|
||||
updateWindow (e) {
|
||||
const visibleItemsCount = Math.ceil(this.$el.clientHeight / this.itemHeight);
|
||||
const visibleItemsCount = Math.ceil(this.visibleHeight / this.itemHeight);
|
||||
const totalScrollHeight = this.items.length * this.itemHeight;
|
||||
const offset = 50;
|
||||
|
||||
const scrollTop = this.localScrollElement.scrollTop;
|
||||
|
||||
const scrollTop = this.$el.scrollTop;
|
||||
const offset = 5;
|
||||
const firstVisibleIndex = Math.floor(scrollTop / this.itemHeight);
|
||||
const lastVisibleIndex = firstVisibleIndex + visibleItemsCount;
|
||||
const firstCutIndex = Math.max(firstVisibleIndex - offset, 0);
|
||||
|
||||
235
src/renderer/components/FakerSelect.vue
Normal file
235
src/renderer/components/FakerSelect.vue
Normal file
@@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<fieldset class="input-group mb-0">
|
||||
<select
|
||||
v-model="selectedGroup"
|
||||
class="form-select"
|
||||
:disabled="!isChecked"
|
||||
style="flex-grow: 0;"
|
||||
@change="onChange"
|
||||
>
|
||||
<option value="manual">
|
||||
{{ $t('message.manualValue') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="group in fakerGroups"
|
||||
:key="group.name"
|
||||
:value="group.name"
|
||||
>
|
||||
{{ $t(`faker.${group.name}`) }}
|
||||
</option>
|
||||
</select>
|
||||
<select
|
||||
v-if="selectedGroup !== 'manual'"
|
||||
v-model="selectedMethod"
|
||||
class="form-select"
|
||||
:disabled="!isChecked"
|
||||
@change="onChange"
|
||||
>
|
||||
<option
|
||||
v-for="method in fakerMethods"
|
||||
:key="method.name"
|
||||
:value="method.name"
|
||||
>
|
||||
{{ $t(`faker.${method.name}`) }}
|
||||
</option>
|
||||
</select>
|
||||
<ForeignKeySelect
|
||||
v-else-if="foreignKeys.includes(field.name)"
|
||||
ref="formInput"
|
||||
class="form-select"
|
||||
:value.sync="selectedValue"
|
||||
:key-usage="getKeyUsage(field.name)"
|
||||
:disabled="!isChecked"
|
||||
/>
|
||||
<input
|
||||
v-else-if="inputProps().mask"
|
||||
ref="formInput"
|
||||
v-model="selectedValue"
|
||||
v-mask="inputProps().mask"
|
||||
class="form-input"
|
||||
:type="inputProps().type"
|
||||
:disabled="!isChecked"
|
||||
>
|
||||
<BaseUploadInput
|
||||
v-else-if="inputProps().type === 'file'"
|
||||
:value="selectedValue"
|
||||
:message="$t('word.browse')"
|
||||
@clear="clearValue"
|
||||
@change="filesChange($event)"
|
||||
/>
|
||||
<input
|
||||
v-else-if="inputProps().type === 'number'"
|
||||
ref="formInput"
|
||||
v-model="selectedValue"
|
||||
class="form-input"
|
||||
step="any"
|
||||
:type="inputProps().type"
|
||||
:disabled="!isChecked"
|
||||
>
|
||||
<input
|
||||
v-else
|
||||
ref="formInput"
|
||||
v-model="selectedValue"
|
||||
class="form-input"
|
||||
:type="inputProps().type"
|
||||
:disabled="!isChecked"
|
||||
>
|
||||
<template v-if="methodData && 'params' in methodData" class="columns">
|
||||
<input
|
||||
v-for="(option, key) in methodData.params"
|
||||
:key="key"
|
||||
v-model="methodParams[option]"
|
||||
class="form-input column"
|
||||
:type="inputProps().type"
|
||||
:disabled="!isChecked"
|
||||
:placeholder="option"
|
||||
>
|
||||
</template>
|
||||
<slot />
|
||||
</fieldset>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mask } from 'vue-the-mask';
|
||||
import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
|
||||
import BaseUploadInput from '@/components/BaseUploadInput';
|
||||
import ForeignKeySelect from '@/components/ForeignKeySelect';
|
||||
import FakerMethods from 'common/FakerMethods';
|
||||
|
||||
export default {
|
||||
name: 'FakerSelect',
|
||||
components: {
|
||||
ForeignKeySelect,
|
||||
BaseUploadInput
|
||||
},
|
||||
directives: {
|
||||
mask
|
||||
},
|
||||
props: {
|
||||
type: String,
|
||||
field: Object,
|
||||
isChecked: Boolean,
|
||||
foreignKeys: Array,
|
||||
keyUsage: Array,
|
||||
fieldLength: Number,
|
||||
fieldObj: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
localType: null,
|
||||
selectedGroup: 'manual',
|
||||
selectedMethod: '',
|
||||
selectedValue: '',
|
||||
debounceTimeout: null,
|
||||
methodParams: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
fakerGroups () {
|
||||
if ([...TEXT, ...LONG_TEXT].includes(this.type))
|
||||
this.localType = 'string';
|
||||
else if (NUMBER.includes(this.type))
|
||||
this.localType = 'number';
|
||||
else if (FLOAT.includes(this.type))
|
||||
this.localType = 'float';
|
||||
else if ([...DATE, ...DATETIME].includes(this.type))
|
||||
this.localType = 'datetime';
|
||||
else if (TIME.includes(this.type))
|
||||
this.localType = 'time';
|
||||
else
|
||||
this.localType = 'none';
|
||||
|
||||
return FakerMethods.getGroupsByType(this.localType);
|
||||
},
|
||||
fakerMethods () {
|
||||
return FakerMethods.getMethods({ type: this.localType, group: this.selectedGroup });
|
||||
},
|
||||
methodData () {
|
||||
return this.fakerMethods.find(method => method.name === this.selectedMethod);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
fieldObj () {
|
||||
if (this.fieldObj)
|
||||
this.selectedValue = this.fieldObj.value;
|
||||
},
|
||||
selectedGroup () {
|
||||
if (this.fakerMethods.length)
|
||||
this.selectedMethod = this.fakerMethods[0].name;
|
||||
else
|
||||
this.selectedMethod = '';
|
||||
},
|
||||
selectedMethod () {
|
||||
this.onChange();
|
||||
},
|
||||
selectedValue () {
|
||||
clearTimeout(this.debounceTimeout);
|
||||
this.debounceTimeout = null;
|
||||
this.debounceTimeout = setTimeout(() => {
|
||||
this.onChange();
|
||||
}, 200);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
inputProps () {
|
||||
if ([...TEXT, ...LONG_TEXT].includes(this.type))
|
||||
return { type: 'text', mask: false };
|
||||
|
||||
if ([...NUMBER, ...FLOAT].includes(this.type))
|
||||
return { type: 'number', mask: false };
|
||||
|
||||
if (TIME.includes(this.type)) {
|
||||
let timeMask = '##:##:##';
|
||||
const precision = this.fieldLength;
|
||||
|
||||
for (let i = 0; i < precision; i++)
|
||||
timeMask += i === 0 ? '.#' : '#';
|
||||
|
||||
return { type: 'text', mask: timeMask };
|
||||
}
|
||||
|
||||
if (DATE.includes(this.type))
|
||||
return { type: 'text', mask: '####-##-##' };
|
||||
|
||||
if (DATETIME.includes(this.type)) {
|
||||
let datetimeMask = '####-##-## ##:##:##';
|
||||
const precision = this.fieldLength;
|
||||
|
||||
for (let i = 0; i < precision; i++)
|
||||
datetimeMask += i === 0 ? '.#' : '#';
|
||||
|
||||
return { type: 'text', mask: datetimeMask };
|
||||
}
|
||||
|
||||
if (BLOB.includes(this.type))
|
||||
return { type: 'file', mask: false };
|
||||
|
||||
if (BIT.includes(this.type))
|
||||
return { type: 'text', mask: false };
|
||||
|
||||
return { type: 'text', mask: false };
|
||||
},
|
||||
getKeyUsage (keyName) {
|
||||
return this.keyUsage.find(key => key.field === keyName);
|
||||
},
|
||||
filesChange (event) {
|
||||
const { files } = event.target;
|
||||
if (!files.length) return;
|
||||
|
||||
this.selectedValue = files[0].path;
|
||||
},
|
||||
clearValue () {
|
||||
this.selectedValue = '';
|
||||
},
|
||||
onChange () {
|
||||
this.$emit('update:value', {
|
||||
group: this.selectedGroup,
|
||||
method: this.selectedMethod,
|
||||
params: this.methodParams,
|
||||
value: this.selectedValue,
|
||||
length: this.fieldLength
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
101
src/renderer/components/ForeignKeySelect.vue
Normal file
101
src/renderer/components/ForeignKeySelect.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<select
|
||||
ref="editField"
|
||||
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"
|
||||
:value="row.foreignColumn"
|
||||
:selected="row.foreignColumn === value"
|
||||
>
|
||||
{{ row.foreignColumn }} {{ 'foreignDescription' in row ? ` - ${row.foreignDescription}` : '' | cutText }}
|
||||
</option>
|
||||
</select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import { TEXT, LONG_TEXT } from 'common/fieldTypes';
|
||||
export default {
|
||||
name: 'ForeignKeySelect',
|
||||
filters: {
|
||||
cutText (val) {
|
||||
if (typeof val !== 'string') return val;
|
||||
return val.length > 15 ? `${val.substring(0, 15)}...` : val;
|
||||
}
|
||||
},
|
||||
props: {
|
||||
value: [String, Number],
|
||||
keyUsage: Object,
|
||||
size: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
foreignList: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected'
|
||||
}),
|
||||
isValidDefault () {
|
||||
if (!this.foreignList.length) return true;
|
||||
return this.foreignList.some(foreign => foreign.foreignColumn.toString() === this.value.toString());
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
let firstTextField;
|
||||
const params = {
|
||||
uid: this.selectedWorkspace,
|
||||
schema: this.keyUsage.refSchema,
|
||||
table: this.keyUsage.refTable
|
||||
};
|
||||
|
||||
try { // Field data
|
||||
const { status, response } = await Tables.getTableColumns(params);
|
||||
if (status === 'success')
|
||||
firstTextField = response.find(field => [...TEXT, ...LONG_TEXT].includes(field.type)).name || false;
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
try { // Foregn list
|
||||
const { status, response } = await Tables.getForeignList({
|
||||
...params,
|
||||
column: this.keyUsage.refField,
|
||||
description: firstTextField
|
||||
});
|
||||
|
||||
if (status === 'success')
|
||||
this.foreignList = response.rows;
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification'
|
||||
}),
|
||||
onChange () {
|
||||
this.$emit('update:value', this.$refs.editField.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -2,21 +2,24 @@
|
||||
<div class="modal active modal-sm">
|
||||
<a class="modal-overlay" />
|
||||
<div class="modal-container p-0">
|
||||
<div class="modal-header">
|
||||
<div class="modal-header pl-2">
|
||||
<div class="modal-title h6">
|
||||
{{ $t('word.credentials') }}
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-key-variant mr-1" /> {{ $t('word.credentials') }}
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-body pb-0">
|
||||
<div class="content">
|
||||
<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"
|
||||
@@ -25,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
|
||||
@@ -61,9 +64,14 @@ export default {
|
||||
}
|
||||
};
|
||||
},
|
||||
created () {
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
methods: {
|
||||
closeModal () {
|
||||
this.$emit('closeAsking');
|
||||
this.$emit('close-asking');
|
||||
},
|
||||
sendCredentials () {
|
||||
this.$emit('credentials', this.credentials);
|
||||
@@ -71,7 +79,3 @@ export default {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
||||
102
src/renderer/components/ModalAskParameters.vue
Normal file
102
src/renderer/components/ModalAskParameters.vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.run')"
|
||||
:cancel-text="$t('word.cancel')"
|
||||
size="400"
|
||||
@confirm="runRoutine"
|
||||
@hide="closeModal"
|
||||
>
|
||||
<template slot="header">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-play mr-1" /> {{ $t('word.parameters') }}: {{ localRoutine.name }}
|
||||
</div>
|
||||
</template>
|
||||
<div slot="body">
|
||||
<div class="content">
|
||||
<form class="form-horizontal">
|
||||
<div
|
||||
v-for="(parameter, i) in localRoutine.parameters"
|
||||
:key="parameter._id"
|
||||
class="form-group"
|
||||
>
|
||||
<div class="col-3">
|
||||
<label class="form-label">{{ parameter.name }}</label>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<div class="input-group">
|
||||
<input
|
||||
:ref="i === 0 ? 'firstInput' : ''"
|
||||
v-model="values[parameter.name]"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
<span class="input-group-addon field-type" :class="`type-${parameter.type.toLowerCase()}`">
|
||||
{{ parameter.type }} {{ parameter.length | wrapNumber }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'ModalAskParameters',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
filters: {
|
||||
wrapNumber (num) {
|
||||
if (!num) return '';
|
||||
return `(${num})`;
|
||||
}
|
||||
},
|
||||
props: {
|
||||
localRoutine: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
values: {}
|
||||
};
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput[0].focus();
|
||||
}, 20);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
runRoutine () {
|
||||
const valArr = Object.keys(this.values).reduce((acc, curr) => {
|
||||
const value = isNaN(this.values[curr]) ? `"${this.values[curr]}"` : this.values[curr];
|
||||
acc.push(value);
|
||||
return acc;
|
||||
}, []);
|
||||
this.$emit('confirm', valArr);
|
||||
},
|
||||
closeModal () {
|
||||
this.$emit('close');
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.field-type {
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
</style>
|
||||
57
src/renderer/components/ModalDiscardChanges.vue
Normal file
57
src/renderer/components/ModalDiscardChanges.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.discard')"
|
||||
:cancel-text="$t('word.stay')"
|
||||
@confirm="discardUnsavedChanges"
|
||||
@hide="closeUnsavedChangesModal"
|
||||
>
|
||||
<template slot="header">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-content-save-alert mr-1" /> {{ $t('message.unsavedChanges') }}
|
||||
</div>
|
||||
</template>
|
||||
<div slot="body">
|
||||
<div>
|
||||
{{ $t('message.discardUnsavedChanges') }}
|
||||
</div>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'ModalDiscardChanges',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
discardUnsavedChanges: 'workspaces/discardUnsavedChanges',
|
||||
closeUnsavedChangesModal: 'workspaces/closeUnsavedChangesModal'
|
||||
}),
|
||||
closeModal () {
|
||||
this.$emit('close');
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-container {
|
||||
max-width: 360px;
|
||||
}
|
||||
</style>
|
||||
@@ -2,41 +2,64 @@
|
||||
<div class="modal active">
|
||||
<a class="modal-overlay c-hand" @click="closeModal" />
|
||||
<div class="modal-container">
|
||||
<div class="modal-header">
|
||||
<div class="modal-header pl-2">
|
||||
<div class="modal-title h6">
|
||||
{{ $t('message.editConnection') }}
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-server mr-1" /> {{ $t('message.editConnection') }}
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click="closeModal" />
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="content">
|
||||
<form class="form-horizontal">
|
||||
<fieldset class="m-0" :disabled="isTesting">
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.connectionName') }}:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.client') }}:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<select v-model="localConnection.client" class="form-select">
|
||||
<option value="mysql">
|
||||
MySQL
|
||||
</option>
|
||||
<option value="maria">
|
||||
MariaDB
|
||||
</option>
|
||||
<option value="mssql">
|
||||
<div class="modal-body p-0">
|
||||
<div class="panel">
|
||||
<div class="panel-nav">
|
||||
<ul class="tab tab-block">
|
||||
<li
|
||||
class="tab-item"
|
||||
:class="{'active': selectedTab === 'general'}"
|
||||
@click="selectTab('general')"
|
||||
>
|
||||
<a class="c-hand">{{ $t('word.general') }}</a>
|
||||
</li>
|
||||
<li
|
||||
class="tab-item"
|
||||
:class="{'active': selectedTab === 'ssl'}"
|
||||
@click="selectTab('ssl')"
|
||||
>
|
||||
<a class="c-hand">{{ $t('word.ssl') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="selectedTab === 'general'" class="panel-body py-0">
|
||||
<div class="container">
|
||||
<form class="form-horizontal">
|
||||
<fieldset class="m-0" :disabled="isTesting">
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.connectionName') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="localConnection.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.client') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<select v-model="localConnection.client" class="form-select">
|
||||
<option value="mysql">
|
||||
MySQL
|
||||
</option>
|
||||
<option value="maria">
|
||||
MariaDB
|
||||
</option>
|
||||
<!-- <option value="mssql">
|
||||
Microsoft SQL
|
||||
</option>
|
||||
<option value="pg">
|
||||
@@ -44,114 +67,200 @@
|
||||
</option>
|
||||
<option value="oracledb">
|
||||
Oracle DB
|
||||
</option>
|
||||
</select>
|
||||
</option> -->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.hostName') }}/IP</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.host"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.port') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.port"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.user') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.user"
|
||||
class="form-input"
|
||||
type="text"
|
||||
:disabled="localConnection.ask"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.password') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.password"
|
||||
class="form-input"
|
||||
type="password"
|
||||
:disabled="localConnection.ask"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12" />
|
||||
<div class="col-8 col-sm-12">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="localConnection.ask" type="checkbox"><i class="form-icon" /> {{ $t('message.askCredentials') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
<BaseToast
|
||||
class="mb-2"
|
||||
:message="toast.message"
|
||||
:status="toast.status"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="selectedTab === 'ssl'" class="panel-body py-0">
|
||||
<div class="container">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">
|
||||
{{ $t('message.enableSsl') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<label class="form-switch d-inline-block" @click.prevent="toggleSsl">
|
||||
<input type="checkbox" :checked="localConnection.ssl">
|
||||
<i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.hostName') }}/IP:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.host"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.port') }}:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.port"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.user') }}:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.user"
|
||||
class="form-input"
|
||||
type="text"
|
||||
:disabled="localConnection.ask"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.password') }}:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.password"
|
||||
class="form-input"
|
||||
type="password"
|
||||
:disabled="localConnection.ask"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12" />
|
||||
<div class="col-8 col-sm-12">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="localConnection.ask" type="checkbox"><i class="form-icon" /> {{ $t('message.askCredentials') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
<fieldset class="m-0" :disabled="isTesting || !localConnection.ssl">
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.privateKey') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:value="localConnection.key"
|
||||
:message="$t('word.browse')"
|
||||
@clear="pathClear('key')"
|
||||
@change="pathSelection($event, 'key')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.certificate') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:value="localConnection.cert"
|
||||
:message="$t('word.browse')"
|
||||
@clear="pathClear('cert')"
|
||||
@change="pathSelection($event, 'cert')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.caCertificate') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:value="localConnection.ca"
|
||||
:message="$t('word.browse')"
|
||||
@clear="pathClear('ca')"
|
||||
@change="pathSelection($event, 'ca')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.ciphers') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="localConnection.ciphers"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
<BaseToast
|
||||
class="mb-2"
|
||||
:message="toast.message"
|
||||
:status="toast.status"
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-footer text-light">
|
||||
<button
|
||||
class="btn btn-gray mr-2"
|
||||
:class="{'loading': isTesting}"
|
||||
@click="startTest"
|
||||
>
|
||||
{{ $t('message.testConnection') }}
|
||||
</button>
|
||||
<button class="btn btn-primary mr-2" @click="saveEditConnection">
|
||||
{{ $t('word.save') }}
|
||||
</button>
|
||||
<button class="btn btn-link" @click="closeModal">
|
||||
{{ $t('word.close') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer text-light">
|
||||
<BaseToast
|
||||
class="mb-2"
|
||||
:message="toast.message"
|
||||
:status="toast.status"
|
||||
<ModalAskCredentials
|
||||
v-if="isAsking"
|
||||
@close-asking="closeAsking"
|
||||
@credentials="continueTest"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-gray mr-2"
|
||||
:class="{'loading': isTesting}"
|
||||
@click="startTest"
|
||||
>
|
||||
{{ $t('message.testConnection') }}
|
||||
</button>
|
||||
<button class="btn btn-primary mr-2" @click="saveEditConnection">
|
||||
{{ $t('word.save') }}
|
||||
</button>
|
||||
<button class="btn btn-link" @click="closeModal">
|
||||
{{ $t('word.close') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ModalAskCredentials
|
||||
v-if="isAsking"
|
||||
@closeAsking="closeAsking"
|
||||
@credentials="continueTest"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import { mapActions } from 'vuex';
|
||||
import Connection from '@/ipc-api/Connection';
|
||||
import ModalAskCredentials from '@/components/ModalAskCredentials';
|
||||
import BaseToast from '@/components/BaseToast';
|
||||
import BaseUploadInput from '@/components/BaseUploadInput';
|
||||
|
||||
export default {
|
||||
name: 'ModalEditConnection',
|
||||
components: {
|
||||
ModalAskCredentials,
|
||||
BaseToast
|
||||
BaseToast,
|
||||
BaseUploadInput
|
||||
},
|
||||
props: {
|
||||
connection: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -161,20 +270,23 @@ export default {
|
||||
},
|
||||
isTesting: false,
|
||||
isAsking: false,
|
||||
localConnection: null
|
||||
localConnection: null,
|
||||
selectedTab: 'general'
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
connection: 'application/getSelectedConnection'
|
||||
})
|
||||
},
|
||||
created () {
|
||||
this.localConnection = Object.assign({}, this.connection);
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
closeModal: 'application/hideEditConnModal',
|
||||
editConnection: 'connections/editConnection'
|
||||
}),
|
||||
async startTest () {
|
||||
@@ -224,13 +336,38 @@ export default {
|
||||
closeAsking () {
|
||||
this.isTesting = false;
|
||||
this.isAsking = false;
|
||||
},
|
||||
closeModal () {
|
||||
this.$emit('close');
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
},
|
||||
selectTab (tab) {
|
||||
this.selectedTab = tab;
|
||||
},
|
||||
toggleSsl () {
|
||||
this.localConnection.ssl = !this.localConnection.ssl;
|
||||
},
|
||||
pathSelection (event, name) {
|
||||
const { files } = event.target;
|
||||
if (!files.length) return;
|
||||
|
||||
this.localConnection[name] = files[0].path;
|
||||
},
|
||||
pathClear (name) {
|
||||
this.localConnection[name] = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-container{
|
||||
max-width: 450px;
|
||||
}
|
||||
.modal-container {
|
||||
position: absolute;
|
||||
max-width: 450px;
|
||||
top: 17.5vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
169
src/renderer/components/ModalEditDatabase.vue
Normal file
169
src/renderer/components/ModalEditDatabase.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<div class="modal active">
|
||||
<a class="modal-overlay" @click.stop="closeModal" />
|
||||
<div class="modal-container p-0">
|
||||
<div class="modal-header pl-2">
|
||||
<div class="modal-title h6">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-database-edit mr-1" /> {{ $t('message.editDatabase') }}
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||
</div>
|
||||
<div class="modal-body pb-0">
|
||||
<div class="content">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-3">
|
||||
<label class="form-label">{{ $t('word.name') }}</label>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<input
|
||||
v-model="database.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
required
|
||||
:placeholder="$t('message.databaseName')"
|
||||
readonly
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-3">
|
||||
<label class="form-label">{{ $t('word.collation') }}</label>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<select
|
||||
ref="firstInput"
|
||||
v-model="database.collation"
|
||||
class="form-select"
|
||||
>
|
||||
<option
|
||||
v-for="collation in collations"
|
||||
:key="collation.id"
|
||||
:value="collation.collation"
|
||||
>
|
||||
{{ collation.collation }}
|
||||
</option>
|
||||
</select>
|
||||
<small>{{ $t('message.serverDefault') }}: {{ defaultCollation }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer text-light">
|
||||
<button class="btn btn-primary mr-2" @click.stop="updateDatabase">
|
||||
{{ $t('word.update') }}
|
||||
</button>
|
||||
<button class="btn btn-link" @click.stop="closeModal">
|
||||
{{ $t('word.close') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import Database from '@/ipc-api/Database';
|
||||
|
||||
export default {
|
||||
name: 'ModalEditDatabase',
|
||||
props: {
|
||||
selectedDatabase: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
database: {
|
||||
name: '',
|
||||
prevName: '',
|
||||
collation: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
getWorkspace: 'workspaces/getWorkspace',
|
||||
getDatabaseVariable: 'workspaces/getDatabaseVariable'
|
||||
}),
|
||||
collations () {
|
||||
return this.getWorkspace(this.selectedWorkspace).collations;
|
||||
},
|
||||
defaultCollation () {
|
||||
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value || '';
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
let actualCollation;
|
||||
try {
|
||||
const { status, response } = await Database.getDatabaseCollation({ uid: this.selectedWorkspace, database: this.selectedDatabase });
|
||||
|
||||
if (status === 'success')
|
||||
actualCollation = response;
|
||||
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.database = {
|
||||
name: this.selectedDatabase,
|
||||
prevName: this.selectedDatabase,
|
||||
collation: actualCollation || this.defaultCollation,
|
||||
prevCollation: actualCollation || this.defaultCollation
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification'
|
||||
}),
|
||||
async updateDatabase () {
|
||||
if (this.database.collation !== this.database.prevCollation) {
|
||||
try {
|
||||
const { status, response } = await Database.updateDatabase({
|
||||
uid: this.selectedWorkspace,
|
||||
...this.database
|
||||
});
|
||||
|
||||
if (status === 'success')
|
||||
this.closeModal();
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
}
|
||||
else
|
||||
this.closeModal();
|
||||
},
|
||||
closeModal () {
|
||||
this.$emit('close');
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-container {
|
||||
max-width: 360px;
|
||||
}
|
||||
</style>
|
||||
391
src/renderer/components/ModalFakerRows.vue
Normal file
391
src/renderer/components/ModalFakerRows.vue
Normal file
@@ -0,0 +1,391 @@
|
||||
<template>
|
||||
<div class="modal active">
|
||||
<a class="modal-overlay" @click.stop="closeModal" />
|
||||
<div class="modal-container p-0">
|
||||
<div class="modal-header pl-2">
|
||||
<div class="modal-title h6">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-playlist-plus mr-1" /> {{ $t('message.tableFiller') }}
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||
</div>
|
||||
<div class="modal-body pb-0">
|
||||
<div class="content">
|
||||
<form class="form-horizontal">
|
||||
<fieldset :disabled="isInserting">
|
||||
<div
|
||||
v-for="field in fields"
|
||||
:key="field.name"
|
||||
class="form-group"
|
||||
>
|
||||
<div class="col-3 col-sm-12">
|
||||
<label class="form-label" :title="field.name">{{ field.name }}</label>
|
||||
</div>
|
||||
<div class="column columns col-sm-12">
|
||||
<FakerSelect
|
||||
:type="field.type"
|
||||
class="column columns pr-0"
|
||||
:is-checked="!fieldsToExclude.includes(field.name)"
|
||||
:foreign-keys="foreignKeys"
|
||||
:key-usage="keyUsage"
|
||||
:field="field"
|
||||
:field-length="fieldLength(field)"
|
||||
:field-obj="localRow[field.name]"
|
||||
:value.sync="localRow[field.name]"
|
||||
>
|
||||
<span class="input-group-addon field-type" :class="`type-${field.type.toLowerCase()}`">
|
||||
{{ field.type }} {{ fieldLength(field) | wrapNumber }}
|
||||
</span>
|
||||
<label class="form-checkbox ml-3" :title="$t('word.insert')">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="!field.autoIncrement"
|
||||
@change.prevent="toggleFields($event, field)"
|
||||
><i class="form-icon" />
|
||||
</label>
|
||||
</FakerSelect>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer text-light columns">
|
||||
<div class="column d-flex" :class="hasFakes ? 'col-4' : 'col-2'">
|
||||
<div class="input-group tooltip tooltip-right" :data-tooltip="$t('message.numberOfInserts')">
|
||||
<input
|
||||
v-model="nInserts"
|
||||
type="number"
|
||||
class="form-input"
|
||||
min="1"
|
||||
:disabled="isInserting"
|
||||
>
|
||||
<span class="input-group-addon">
|
||||
<i class="mdi mdi-24px mdi-repeat" />
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="hasFakes"
|
||||
class="tooltip tooltip-right ml-2"
|
||||
:data-tooltip="$t('message.fakeDataLanguage')"
|
||||
>
|
||||
<select v-model="fakerLocale" class="form-select">
|
||||
<option value="ar">
|
||||
Arabic
|
||||
</option><option value="az">
|
||||
Azerbaijani
|
||||
</option><option value="zh_CN">
|
||||
Chinese
|
||||
</option><option value="zh_TW">
|
||||
Chinese (Taiwan)
|
||||
</option><option value="cz">
|
||||
Czech
|
||||
</option><option value="nl">
|
||||
Dutch
|
||||
</option><option value="nl_BE">
|
||||
Dutch (Belgium)
|
||||
</option><option value="en">
|
||||
English
|
||||
</option><option value="en_AU_ocker">
|
||||
English (Australia Ocker)
|
||||
</option><option value="en_AU">
|
||||
English (Australia)
|
||||
</option><option value="en_BORK">
|
||||
English (Bork)
|
||||
</option><option value="en_CA">
|
||||
English (Canada)
|
||||
</option><option value="en_GB">
|
||||
English (Great Britain)
|
||||
</option><option value="en_IND">
|
||||
English (India)
|
||||
</option><option value="en_IE">
|
||||
English (Ireland)
|
||||
</option><option value="en_ZA">
|
||||
English (South Africa)
|
||||
</option><option value="en_US">
|
||||
English (United States)
|
||||
</option><option value="fa">
|
||||
Farsi
|
||||
</option><option value="fi">
|
||||
Finnish
|
||||
</option><option value="fr">
|
||||
French
|
||||
</option><option value="fr_CA">
|
||||
French (Canada)
|
||||
</option><option value="fr_CH">
|
||||
French (Switzerland)
|
||||
</option><option value="ge">
|
||||
Georgian
|
||||
</option><option value="de">
|
||||
German
|
||||
</option><option value="de_AT">
|
||||
German (Austria)
|
||||
</option><option value="de_CH">
|
||||
German (Switzerland)
|
||||
</option><option value="hr">
|
||||
Hrvatski
|
||||
</option><option value="id_ID">
|
||||
Indonesia
|
||||
</option><option value="it">
|
||||
Italian
|
||||
</option><option value="ja">
|
||||
Japanese
|
||||
</option><option value="ko">
|
||||
Korean
|
||||
</option><option value="nep">
|
||||
Nepalese
|
||||
</option><option value="nb_NO">
|
||||
Norwegian
|
||||
</option><option value="pl">
|
||||
Polish
|
||||
</option><option value="pt_BR">
|
||||
Portuguese (Brazil)
|
||||
</option><option value="pt_PT">
|
||||
Portuguese (Portugal)
|
||||
</option><option value="ro">
|
||||
Romanian
|
||||
</option><option value="ru">
|
||||
Russian
|
||||
</option><option value="sk">
|
||||
Slovakian
|
||||
</option><option value="es">
|
||||
Spanish
|
||||
</option><option value="es_MX">
|
||||
Spanish (Mexico)
|
||||
</option><option value="sv">
|
||||
Swedish
|
||||
</option><option value="tr">
|
||||
Turkish
|
||||
</option><option value="uk">
|
||||
Ukrainian
|
||||
</option><option value="vi">
|
||||
Vietnamese
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-auto">
|
||||
<button
|
||||
class="btn btn-primary mr-2"
|
||||
:class="{'loading': isInserting}"
|
||||
@click.stop="insertRows"
|
||||
>
|
||||
{{ $t('word.insert') }}
|
||||
</button>
|
||||
<button class="btn btn-link" @click.stop="closeModal">
|
||||
{{ $t('word.close') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
|
||||
import { mask } from 'vue-the-mask';
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
import FakerSelect from '@/components/FakerSelect';
|
||||
|
||||
export default {
|
||||
name: 'ModalFakerRows',
|
||||
components: {
|
||||
FakerSelect
|
||||
},
|
||||
directives: {
|
||||
mask
|
||||
},
|
||||
filters: {
|
||||
wrapNumber (num) {
|
||||
if (!num) return '';
|
||||
return `(${num})`;
|
||||
}
|
||||
},
|
||||
props: {
|
||||
tabUid: [String, Number],
|
||||
fields: Array,
|
||||
keyUsage: Array
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
localRow: {},
|
||||
fieldsToExclude: [],
|
||||
nInserts: 1,
|
||||
isInserting: false,
|
||||
fakerLocale: 'en'
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
getWorkspace: 'workspaces/getWorkspace',
|
||||
getWorkspaceTab: 'workspaces/getWorkspaceTab'
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
},
|
||||
foreignKeys () {
|
||||
return this.keyUsage.map(key => key.field);
|
||||
},
|
||||
hasFakes () {
|
||||
return Object.keys(this.localRow).some(field => 'group' in this.localRow[field] && this.localRow[field].group !== 'manual');
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
nInserts (val) {
|
||||
if (!val || val < 1)
|
||||
this.nInserts = 1;
|
||||
else if (val > 1000)
|
||||
this.nInserts = 1000;
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
mounted () {
|
||||
const rowObj = {};
|
||||
|
||||
for (const field of this.fields) {
|
||||
let fieldDefault;
|
||||
|
||||
if (field.default === 'NULL') fieldDefault = null;
|
||||
else {
|
||||
if ([...NUMBER, ...FLOAT].includes(field.type))
|
||||
fieldDefault = +field.default;
|
||||
|
||||
if ([...TEXT, ...LONG_TEXT].includes(field.type))
|
||||
fieldDefault = field.default ? field.default.substring(1, field.default.length - 1) : '';
|
||||
|
||||
if ([...TIME, ...DATE].includes(field.type))
|
||||
fieldDefault = field.default;
|
||||
|
||||
if (BIT.includes(field.type))
|
||||
fieldDefault = field.default.replaceAll('\'', '').replaceAll('b', '');
|
||||
|
||||
if (DATETIME.includes(field.type)) {
|
||||
if (field.default && field.default.toLowerCase().includes('current_timestamp')) {
|
||||
let datePrecision = '';
|
||||
for (let i = 0; i < field.datePrecision; i++)
|
||||
datePrecision += i === 0 ? '.S' : 'S';
|
||||
fieldDefault = moment().format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rowObj[field.name] = { value: fieldDefault };
|
||||
|
||||
if (field.autoIncrement)// Disable by default auto increment fields
|
||||
this.fieldsToExclude = [...this.fieldsToExclude, field.name];
|
||||
}
|
||||
|
||||
this.localRow = { ...rowObj };
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification'
|
||||
}),
|
||||
async insertRows () {
|
||||
this.isInserting = true;
|
||||
const rowToInsert = this.localRow;
|
||||
|
||||
Object.keys(rowToInsert).forEach(key => {
|
||||
if (this.fieldsToExclude.includes(key))
|
||||
delete rowToInsert[key];
|
||||
|
||||
if (typeof rowToInsert[key] === 'undefined')
|
||||
delete rowToInsert[key];
|
||||
});
|
||||
|
||||
const fieldTypes = {};
|
||||
this.fields.forEach(field => {
|
||||
fieldTypes[field.name] = field.type;
|
||||
});
|
||||
|
||||
try {
|
||||
const { status, response } = await Tables.insertTableFakeRows({
|
||||
uid: this.selectedWorkspace,
|
||||
schema: this.workspace.breadcrumbs.schema,
|
||||
table: this.workspace.breadcrumbs.table,
|
||||
row: rowToInsert,
|
||||
repeat: this.nInserts,
|
||||
fields: fieldTypes,
|
||||
locale: this.fakerLocale
|
||||
});
|
||||
|
||||
if (status === 'success') {
|
||||
this.closeModal();
|
||||
this.$emit('reload');
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isInserting = false;
|
||||
},
|
||||
closeModal () {
|
||||
this.$emit('hide');
|
||||
},
|
||||
fieldLength (field) {
|
||||
if ([...BLOB, ...LONG_TEXT].includes(field.type)) return null;
|
||||
else if (TEXT.includes(field.type)) return field.charLength;
|
||||
return field.length;
|
||||
},
|
||||
toggleFields (event, field) {
|
||||
if (event.target.checked)
|
||||
this.fieldsToExclude = this.fieldsToExclude.filter(f => f !== field.name);
|
||||
else
|
||||
this.fieldsToExclude = [...this.fieldsToExclude, field.name];
|
||||
},
|
||||
filesChange (event, field) {
|
||||
const { files } = event.target;
|
||||
if (!files.length) return;
|
||||
|
||||
this.localRow[field] = files[0].path;
|
||||
},
|
||||
getKeyUsage (keyName) {
|
||||
return this.keyUsage.find(key => key.field === keyName);
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-container {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
overflow: hidden;
|
||||
white-space: normal;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.input-group-addon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.field-type {
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
</style>
|
||||
@@ -2,126 +2,230 @@
|
||||
<div class="modal active">
|
||||
<a class="modal-overlay c-hand" @click="closeModal" />
|
||||
<div class="modal-container">
|
||||
<div class="modal-header">
|
||||
<div class="modal-header pl-2">
|
||||
<div class="modal-title h6">
|
||||
{{ $t('message.createNewConnection') }}
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-server-plus mr-1" /> {{ $t('message.createNewConnection') }}
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click="closeModal" />
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="content">
|
||||
<form class="form-horizontal">
|
||||
<fieldset class="m-0" :disabled="isTesting">
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.connectionName') }}:</label>
|
||||
<div class="modal-body p-0">
|
||||
<div class="panel">
|
||||
<div class="panel-nav">
|
||||
<ul class="tab tab-block">
|
||||
<li
|
||||
class="tab-item"
|
||||
:class="{'active': selectedTab === 'general'}"
|
||||
@click="selectTab('general')"
|
||||
>
|
||||
<a class="c-hand">{{ $t('word.general') }}</a>
|
||||
</li>
|
||||
<li
|
||||
class="tab-item"
|
||||
:class="{'active': selectedTab === 'ssl'}"
|
||||
@click="selectTab('ssl')"
|
||||
>
|
||||
<a class="c-hand">{{ $t('word.ssl') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="selectedTab === 'general'" class="panel-body py-0">
|
||||
<div class="container">
|
||||
<form class="form-horizontal">
|
||||
<fieldset class="m-0" :disabled="isTesting">
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.connectionName') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="connection.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.client') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<select
|
||||
v-model="connection.client"
|
||||
class="form-select"
|
||||
@change="setDefaults"
|
||||
>
|
||||
<option value="mysql">
|
||||
MySQL
|
||||
</option>
|
||||
<option value="maria">
|
||||
MariaDB
|
||||
</option>
|
||||
<!-- <option value="mssql">
|
||||
Microsoft SQL
|
||||
</option>
|
||||
<option value="pg">
|
||||
PostgreSQL
|
||||
</option>
|
||||
<option value="oracledb">
|
||||
Oracle DB
|
||||
</option> -->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.hostName') }}/IP</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="connection.host"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.port') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="connection.port"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.user') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="connection.user"
|
||||
class="form-input"
|
||||
type="text"
|
||||
:disabled="connection.ask"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.password') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="connection.password"
|
||||
class="form-input"
|
||||
type="password"
|
||||
:disabled="connection.ask"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12" />
|
||||
<div class="col-8 col-sm-12">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="connection.ask" type="checkbox"><i class="form-icon" /> {{ $t('message.askCredentials') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
<BaseToast
|
||||
class="mb-2"
|
||||
:message="toast.message"
|
||||
:status="toast.status"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="selectedTab === 'ssl'" class="panel-body py-0">
|
||||
<div class="container">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">
|
||||
{{ $t('message.enableSsl') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<label class="form-switch d-inline-block" @click.prevent="toggleSsl">
|
||||
<input type="checkbox" :checked="connection.ssl">
|
||||
<i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="connection.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.client') }}:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<select
|
||||
v-model="connection.client"
|
||||
class="form-select"
|
||||
@change="setDefaults"
|
||||
>
|
||||
<option value="mysql">
|
||||
MySQL
|
||||
</option>
|
||||
<option value="maria">
|
||||
MariaDB
|
||||
</option>
|
||||
<option value="mssql">
|
||||
Microsoft SQL
|
||||
</option>
|
||||
<option value="pg">
|
||||
PostgreSQL
|
||||
</option>
|
||||
<option value="oracledb">
|
||||
Oracle DB
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.hostName') }}/IP:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="connection.host"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.port') }}:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="connection.port"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.user') }}:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="connection.user"
|
||||
class="form-input"
|
||||
type="text"
|
||||
:disabled="connection.ask"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.password') }}:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="connection.password"
|
||||
class="form-input"
|
||||
type="password"
|
||||
:disabled="connection.ask"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12" />
|
||||
<div class="col-8 col-sm-12">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="connection.ask" type="checkbox"><i class="form-icon" /> {{ $t('message.askCredentials') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
<fieldset class="m-0" :disabled="isTesting || !connection.ssl">
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.privateKey') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:value="connection.key"
|
||||
:message="$t('word.browse')"
|
||||
@clear="pathClear('key')"
|
||||
@change="pathSelection($event, 'key')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.certificate') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:value="connection.cert"
|
||||
:message="$t('word.browse')"
|
||||
@clear="pathClear('cert')"
|
||||
@change="pathSelection($event, 'cert')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.caCertificate') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:value="connection.ca"
|
||||
:message="$t('word.browse')"
|
||||
@clear="pathClear('ca')"
|
||||
@change="pathSelection($event, 'ca')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.ciphers') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="connection.ciphers"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
<BaseToast
|
||||
class="mb-2"
|
||||
:message="toast.message"
|
||||
:status="toast.status"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer text-light">
|
||||
<BaseToast
|
||||
class="mb-2"
|
||||
:message="toast.message"
|
||||
:status="toast.status"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-gray mr-2"
|
||||
:class="{'loading': isTesting}"
|
||||
@@ -139,7 +243,7 @@
|
||||
</div>
|
||||
<ModalAskCredentials
|
||||
v-if="isAsking"
|
||||
@closeAsking="closeAsking"
|
||||
@close-asking="closeAsking"
|
||||
@credentials="continueTest"
|
||||
/>
|
||||
</div>
|
||||
@@ -148,15 +252,17 @@
|
||||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import Connection from '@/ipc-api/Connection';
|
||||
import { uidGen } from 'common/libs/utilities';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import ModalAskCredentials from '@/components/ModalAskCredentials';
|
||||
import BaseToast from '@/components/BaseToast';
|
||||
import BaseUploadInput from '@/components/BaseUploadInput';
|
||||
|
||||
export default {
|
||||
name: 'ModalNewConnection',
|
||||
components: {
|
||||
ModalAskCredentials,
|
||||
BaseToast
|
||||
BaseToast,
|
||||
BaseUploadInput
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -168,16 +274,33 @@ export default {
|
||||
user: 'root',
|
||||
password: '',
|
||||
ask: false,
|
||||
uid: uidGen()
|
||||
uid: uidGen('C'),
|
||||
ssl: false,
|
||||
cert: '',
|
||||
key: '',
|
||||
ca: '',
|
||||
ciphers: ''
|
||||
|
||||
},
|
||||
toast: {
|
||||
status: '',
|
||||
message: ''
|
||||
},
|
||||
isTesting: false,
|
||||
isAsking: false
|
||||
isAsking: false,
|
||||
selectedTab: 'general'
|
||||
};
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
closeModal: 'application/hideNewConnModal',
|
||||
@@ -247,13 +370,35 @@ export default {
|
||||
closeAsking () {
|
||||
this.isAsking = false;
|
||||
this.isTesting = false;
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
},
|
||||
selectTab (tab) {
|
||||
this.selectedTab = tab;
|
||||
},
|
||||
toggleSsl () {
|
||||
this.connection.ssl = !this.connection.ssl;
|
||||
},
|
||||
pathSelection (event, name) {
|
||||
const { files } = event.target;
|
||||
if (!files.length) return;
|
||||
|
||||
this.connection[name] = files[0].path;
|
||||
},
|
||||
pathClear (name) {
|
||||
this.connection[name] = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-container{
|
||||
max-width: 450px;
|
||||
}
|
||||
.modal-container {
|
||||
position: absolute;
|
||||
max-width: 450px;
|
||||
top: 17.5vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
138
src/renderer/components/ModalNewDatabase.vue
Normal file
138
src/renderer/components/ModalNewDatabase.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<div class="modal active">
|
||||
<a class="modal-overlay" @click.stop="closeModal" />
|
||||
<div class="modal-container p-0">
|
||||
<div class="modal-header pl-2">
|
||||
<div class="modal-title h6">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-database-plus mr-1" /> {{ $t('message.createNewDatabase') }}
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||
</div>
|
||||
<div class="modal-body pb-0">
|
||||
<div class="content">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-3">
|
||||
<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"
|
||||
required
|
||||
:placeholder="$t('message.databaseName')"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-3">
|
||||
<label class="form-label">{{ $t('word.collation') }}</label>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<select v-model="database.collation" class="form-select">
|
||||
<option
|
||||
v-for="collation in collations"
|
||||
:key="collation.id"
|
||||
:value="collation.collation"
|
||||
>
|
||||
{{ collation.collation }}
|
||||
</option>
|
||||
</select>
|
||||
<small>{{ $t('message.serverDefault') }}: {{ defaultCollation }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer text-light">
|
||||
<button class="btn btn-primary mr-2" @click.stop="createDatabase">
|
||||
{{ $t('word.add') }}
|
||||
</button>
|
||||
<button class="btn btn-link" @click.stop="closeModal">
|
||||
{{ $t('word.close') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import Database from '@/ipc-api/Database';
|
||||
|
||||
export default {
|
||||
name: 'ModalNewDatabase',
|
||||
data () {
|
||||
return {
|
||||
database: {
|
||||
name: '',
|
||||
collation: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
getWorkspace: 'workspaces/getWorkspace',
|
||||
getDatabaseVariable: 'workspaces/getDatabaseVariable'
|
||||
}),
|
||||
collations () {
|
||||
return this.getWorkspace(this.selectedWorkspace).collations;
|
||||
},
|
||||
defaultCollation () {
|
||||
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value || '';
|
||||
}
|
||||
},
|
||||
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);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification'
|
||||
}),
|
||||
async createDatabase () {
|
||||
try {
|
||||
const { status, response } = await Database.createDatabase({
|
||||
uid: this.selectedWorkspace,
|
||||
...this.database
|
||||
});
|
||||
|
||||
if (status === 'success') {
|
||||
this.closeModal();
|
||||
this.$emit('reload');
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
},
|
||||
closeModal () {
|
||||
this.$emit('close');
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-container {
|
||||
max-width: 360px;
|
||||
}
|
||||
</style>
|
||||
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>
|
||||
126
src/renderer/components/ModalNewTable.vue
Normal file
126
src/renderer/components/ModalNewTable.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.confirm')"
|
||||
size="400"
|
||||
@confirm="confirmOptionsChange"
|
||||
@hide="$emit('close')"
|
||||
>
|
||||
<template :slot="'header'">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-table-plus mr-1" /> {{ $t('message.createNewTable') }}
|
||||
</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="localOptions.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.comment') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
v-model="localOptions.comment"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.collation') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="localOptions.collation" class="form-select">
|
||||
<option
|
||||
v-for="collation in workspace.collations"
|
||||
:key="collation.id"
|
||||
:value="collation.collation"
|
||||
>
|
||||
{{ collation.collation }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.engine') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="localOptions.engine" class="form-select">
|
||||
<option
|
||||
v-for="engine in workspace.engines"
|
||||
:key="engine.name"
|
||||
:value="engine.name"
|
||||
>
|
||||
{{ engine.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'ModalNewTable',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
workspace: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
localOptions: {
|
||||
name: '',
|
||||
comment: '',
|
||||
collation: '',
|
||||
engine: ''
|
||||
},
|
||||
isOptionsChanging: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
getDatabaseVariable: 'workspaces/getDatabaseVariable'
|
||||
}),
|
||||
defaultCollation () {
|
||||
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value || '';
|
||||
},
|
||||
defaultEngine () {
|
||||
return this.workspace.engines.find(engine => engine.isDefault).name;
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.localOptions.collation = this.defaultCollation;
|
||||
this.localOptions.engine = this.defaultEngine;
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
methods: {
|
||||
confirmOptionsChange () {
|
||||
this.$emit('open-create-table-editor', this.localOptions);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
354
src/renderer/components/ModalNewTableRow.vue
Normal file
354
src/renderer/components/ModalNewTableRow.vue
Normal file
@@ -0,0 +1,354 @@
|
||||
<template>
|
||||
<div class="modal active">
|
||||
<a class="modal-overlay" @click.stop="closeModal" />
|
||||
<div class="modal-container p-0">
|
||||
<div class="modal-header pl-2">
|
||||
<div class="modal-title h6">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-playlist-plus mr-1" /> {{ $t('message.addNewRow') }}
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||
</div>
|
||||
<div class="modal-body pb-0">
|
||||
<div class="content">
|
||||
<form class="form-horizontal">
|
||||
<fieldset :disabled="isInserting">
|
||||
<div
|
||||
v-for="(field, key) in fields"
|
||||
:key="field.name"
|
||||
class="form-group"
|
||||
>
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label" :title="field.name">{{ field.name }}</label>
|
||||
</div>
|
||||
<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)"
|
||||
:disabled="fieldsToExclude.includes(field.name)"
|
||||
/>
|
||||
<input
|
||||
v-else-if="inputProps(field).mask"
|
||||
ref="formInput"
|
||||
v-model="localRow[field.name]"
|
||||
v-mask="inputProps(field).mask"
|
||||
class="form-input"
|
||||
:type="inputProps(field).type"
|
||||
:disabled="fieldsToExclude.includes(field.name)"
|
||||
:tabindex="key+1"
|
||||
>
|
||||
<input
|
||||
v-else-if="inputProps(field).type === 'file'"
|
||||
ref="formInput"
|
||||
class="form-input"
|
||||
type="file"
|
||||
:disabled="fieldsToExclude.includes(field.name)"
|
||||
:tabindex="key+1"
|
||||
@change="filesChange($event,field.name)"
|
||||
>
|
||||
<input
|
||||
v-else-if="inputProps(field).type === 'number'"
|
||||
ref="formInput"
|
||||
v-model="localRow[field.name]"
|
||||
class="form-input"
|
||||
step="any"
|
||||
:type="inputProps(field).type"
|
||||
:disabled="fieldsToExclude.includes(field.name)"
|
||||
:tabindex="key+1"
|
||||
>
|
||||
<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.toLowerCase()}`">
|
||||
{{ field.type }} {{ fieldLength(field) | wrapNumber }}
|
||||
</span>
|
||||
<label class="form-checkbox ml-3" :title="$t('word.insert')">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="!field.autoIncrement"
|
||||
@change.prevent="toggleFields($event, field)"
|
||||
><i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer text-light">
|
||||
<div class="input-group col-3 tooltip tooltip-right" :data-tooltip="$t('message.numberOfInserts')">
|
||||
<input
|
||||
v-model="nInserts"
|
||||
type="number"
|
||||
class="form-input"
|
||||
min="1"
|
||||
:disabled="isInserting"
|
||||
>
|
||||
<span class="input-group-addon">
|
||||
<i class="mdi mdi-24px mdi-repeat" />
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-primary mr-2"
|
||||
:class="{'loading': isInserting}"
|
||||
@click.stop="insertRows"
|
||||
>
|
||||
{{ $t('word.insert') }}
|
||||
</button>
|
||||
<button class="btn btn-link" @click.stop="closeModal">
|
||||
{{ $t('word.close') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
|
||||
import { mask } from 'vue-the-mask';
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
import ForeignKeySelect from '@/components/ForeignKeySelect';
|
||||
|
||||
export default {
|
||||
name: 'ModalNewTableRow',
|
||||
components: {
|
||||
ForeignKeySelect
|
||||
},
|
||||
directives: {
|
||||
mask
|
||||
},
|
||||
filters: {
|
||||
wrapNumber (num) {
|
||||
if (!num) return '';
|
||||
return `(${num})`;
|
||||
}
|
||||
},
|
||||
props: {
|
||||
tabUid: [String, Number],
|
||||
fields: Array,
|
||||
keyUsage: Array
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
localRow: {},
|
||||
fieldsToExclude: [],
|
||||
nInserts: 1,
|
||||
isInserting: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
getWorkspace: 'workspaces/getWorkspace',
|
||||
getWorkspaceTab: 'workspaces/getWorkspaceTab'
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
},
|
||||
foreignKeys () {
|
||||
return this.keyUsage.map(key => key.field);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
nInserts (val) {
|
||||
if (!val || val < 1)
|
||||
this.nInserts = 1;
|
||||
else if (val > 1000)
|
||||
this.nInserts = 1000;
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
mounted () {
|
||||
const rowObj = {};
|
||||
|
||||
for (const field of this.fields) {
|
||||
let fieldDefault;
|
||||
|
||||
if (field.default === 'NULL') fieldDefault = null;
|
||||
else {
|
||||
if ([...NUMBER, ...FLOAT].includes(field.type))
|
||||
fieldDefault = +field.default;
|
||||
|
||||
if ([...TEXT, ...LONG_TEXT].includes(field.type))
|
||||
fieldDefault = field.default ? field.default.substring(1, field.default.length - 1) : '';
|
||||
|
||||
if ([...TIME, ...DATE].includes(field.type))
|
||||
fieldDefault = field.default;
|
||||
|
||||
if (DATETIME.includes(field.type)) {
|
||||
if (field.default && field.default.toLowerCase().includes('current_timestamp')) {
|
||||
let datePrecision = '';
|
||||
for (let i = 0; i < field.datePrecision; i++)
|
||||
datePrecision += i === 0 ? '.S' : 'S';
|
||||
fieldDefault = moment().format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rowObj[field.name] = fieldDefault;
|
||||
|
||||
if (field.autoIncrement)// Disable by default auto increment fields
|
||||
this.fieldsToExclude = [...this.fieldsToExclude, field.name];
|
||||
}
|
||||
|
||||
this.localRow = { ...rowObj };
|
||||
|
||||
// Auto focus
|
||||
setTimeout(() => {
|
||||
const firstSelectableInput = this.$refs.formInput.find(input => !input.disabled);
|
||||
firstSelectableInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification'
|
||||
}),
|
||||
async insertRows () {
|
||||
this.isInserting = true;
|
||||
const rowToInsert = this.localRow;
|
||||
Object.keys(rowToInsert).forEach(key => {
|
||||
if (this.fieldsToExclude.includes(key))
|
||||
delete rowToInsert[key];
|
||||
if (typeof rowToInsert[key] === 'undefined')
|
||||
delete rowToInsert[key];
|
||||
});
|
||||
|
||||
const fieldTypes = {};
|
||||
this.fields.forEach(field => {
|
||||
fieldTypes[field.name] = field.type;
|
||||
});
|
||||
|
||||
try {
|
||||
const { status, response } = await Tables.insertTableRows({
|
||||
uid: this.selectedWorkspace,
|
||||
schema: this.workspace.breadcrumbs.schema,
|
||||
table: this.workspace.breadcrumbs.table,
|
||||
row: rowToInsert,
|
||||
repeat: this.nInserts,
|
||||
fields: fieldTypes
|
||||
});
|
||||
|
||||
if (status === 'success') {
|
||||
this.closeModal();
|
||||
this.$emit('reload');
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isInserting = false;
|
||||
},
|
||||
closeModal () {
|
||||
this.$emit('hide');
|
||||
},
|
||||
fieldLength (field) {
|
||||
if ([...BLOB, ...LONG_TEXT].includes(field.type)) return null;
|
||||
else if (TEXT.includes(field.type)) return field.charLength;
|
||||
return field.length;
|
||||
},
|
||||
inputProps (field) {
|
||||
if ([...TEXT, ...LONG_TEXT].includes(field.type))
|
||||
return { type: 'text', mask: false };
|
||||
|
||||
if ([...NUMBER, ...FLOAT].includes(field.type))
|
||||
return { type: 'number', mask: false };
|
||||
|
||||
if (TIME.includes(field.type)) {
|
||||
let timeMask = '##:##:##';
|
||||
const precision = this.fieldLength(field);
|
||||
|
||||
for (let i = 0; i < precision; i++)
|
||||
timeMask += i === 0 ? '.#' : '#';
|
||||
|
||||
return { type: 'text', mask: timeMask };
|
||||
}
|
||||
|
||||
if (DATE.includes(field.type))
|
||||
return { type: 'text', mask: '####-##-##' };
|
||||
|
||||
if (DATETIME.includes(field.type)) {
|
||||
let datetimeMask = '####-##-## ##:##:##';
|
||||
const precision = this.fieldLength(field);
|
||||
|
||||
for (let i = 0; i < precision; i++)
|
||||
datetimeMask += i === 0 ? '.#' : '#';
|
||||
|
||||
return { type: 'text', mask: datetimeMask };
|
||||
}
|
||||
|
||||
if (BLOB.includes(field.type))
|
||||
return { type: 'file', mask: false };
|
||||
|
||||
if (BIT.includes(field.type))
|
||||
return { type: 'text', mask: false };
|
||||
|
||||
return { type: 'text', mask: false };
|
||||
},
|
||||
toggleFields (event, field) {
|
||||
if (event.target.checked)
|
||||
this.fieldsToExclude = this.fieldsToExclude.filter(f => f !== field.name);
|
||||
else
|
||||
this.fieldsToExclude = [...this.fieldsToExclude, field.name];
|
||||
},
|
||||
filesChange (event, field) {
|
||||
const { files } = event.target;
|
||||
if (!files.length) return;
|
||||
|
||||
this.localRow[field] = files[0].path;
|
||||
},
|
||||
getKeyUsage (keyName) {
|
||||
return this.keyUsage.find(key => key.field === keyName);
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-container {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
overflow: hidden;
|
||||
white-space: normal;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.input-group-addon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
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>
|
||||
@@ -2,9 +2,12 @@
|
||||
<div id="settings" class="modal active">
|
||||
<a class="modal-overlay c-hand" @click="closeModal" />
|
||||
<div class="modal-container">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title h5">
|
||||
{{ $t('word.settings') }}
|
||||
<div class="modal-header pl-2">
|
||||
<div class="modal-title h6">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-cog mr-1" />
|
||||
{{ $t('word.settings') }}
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click="closeModal" />
|
||||
</div>
|
||||
@@ -31,7 +34,7 @@
|
||||
:class="{'active': selectedTab === 'update'}"
|
||||
@click="selectTab('update')"
|
||||
>
|
||||
<a class="c-hand" :class="{'badge': isUpdate}">{{ $t('word.update') }}</a>
|
||||
<a class="c-hand" :class="{'badge badge-update': hasUpdates}">{{ $t('word.update') }}</a>
|
||||
</li>
|
||||
<li
|
||||
class="tab-item"
|
||||
@@ -42,39 +45,155 @@
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedTab === 'general'" class="panel-body py-4">
|
||||
<form class="form-horizontal">
|
||||
<div class="col-6 col-sm-12">
|
||||
<div class="form-group">
|
||||
<div class="col-6 col-sm-12">
|
||||
<label class="form-label">
|
||||
<i class="material-icons md-18 mr-1">translate</i>
|
||||
{{ $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>
|
||||
</form>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<div v-if="selectedTab === 'update'" class="panel-body py-4">
|
||||
@@ -86,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/Fabio286/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>
|
||||
@@ -102,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 {
|
||||
isUpdate: false,
|
||||
localLocale: null,
|
||||
selectedTab: 'general'
|
||||
localTimeout: null,
|
||||
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: {
|
||||
@@ -121,7 +293,15 @@ export default {
|
||||
appName: 'application/appName',
|
||||
appVersion: 'application/appVersion',
|
||||
selectedSettingTab: 'application/selectedSettingTab',
|
||||
selectedLocale: 'settings/getLocale'
|
||||
selectedLocale: 'settings/getLocale',
|
||||
selectedAutoComplete: 'settings/getAutoComplete',
|
||||
selectedLineWrap: 'settings/getLineWrap',
|
||||
notificationsTimeout: 'settings/getNotificationsTimeout',
|
||||
applicationTheme: 'settings/getApplicationTheme',
|
||||
editorTheme: 'settings/getEditorTheme',
|
||||
updateStatus: 'application/getUpdateStatus',
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
locales () {
|
||||
const locales = [];
|
||||
@@ -129,46 +309,128 @@ export default {
|
||||
locales.push({ code: locale, name: localesNames[locale] });
|
||||
|
||||
return locales;
|
||||
},
|
||||
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);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
closeModal: 'application/hideSettingModal',
|
||||
changeLocale: 'settings/changeLocale'
|
||||
changeLocale: 'settings/changeLocale',
|
||||
changeAutoComplete: 'settings/changeAutoComplete',
|
||||
changeLineWrap: 'settings/changeLineWrap',
|
||||
changeEditorTheme: 'settings/changeEditorTheme',
|
||||
updateNotificationsTimeout: 'settings/updateNotificationsTimeout'
|
||||
}),
|
||||
selectTab (tab) {
|
||||
this.selectedTab = tab;
|
||||
},
|
||||
openOutside (link) {
|
||||
shell.openExternal(link);
|
||||
},
|
||||
checkNotificationsTimeout () {
|
||||
if (!this.localTimeout)
|
||||
this.localTimeout = 10;
|
||||
|
||||
this.updateNotificationsTimeout(+this.localTimeout);
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
},
|
||||
toggleAutoComplete () {
|
||||
this.changeAutoComplete(!this.selectedAutoComplete);
|
||||
},
|
||||
toggleLineWrap () {
|
||||
this.changeLineWrap(!this.selectedLineWrap);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#settings{
|
||||
.modal-body{
|
||||
overflow: hidden;
|
||||
#settings {
|
||||
.modal-body {
|
||||
overflow: hidden;
|
||||
|
||||
.panel-body{
|
||||
height: calc(70vh - 70px);
|
||||
overflow: auto;
|
||||
.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{
|
||||
background: #32b643;
|
||||
}
|
||||
.badge::after {
|
||||
background: #32b643;
|
||||
}
|
||||
|
||||
.form-label{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
.badge-update::after {
|
||||
bottom: initial;
|
||||
background: $primary-color;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="empty">
|
||||
<div class="empty-icon">
|
||||
<i class="material-icons md-48">system_update_alt</i>
|
||||
<i class="mdi mdi-48px mdi-cloud-download" />
|
||||
</div>
|
||||
<p class="empty-title h5">
|
||||
{{ updateMessage }}
|
||||
@@ -16,6 +16,9 @@
|
||||
{{ downloadPercentage }}%
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="updateStatus === 'available'">
|
||||
<progress class="progress" max="100" />
|
||||
</div>
|
||||
<div class="empty-action">
|
||||
<button
|
||||
v-if="['noupdate', 'checking', 'nocheck'].includes(updateStatus)"
|
||||
@@ -68,17 +71,17 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
checkForUpdates () {
|
||||
ipcRenderer.send('checkForUpdates');
|
||||
ipcRenderer.send('check-for-updates');
|
||||
},
|
||||
restartToUpdate () {
|
||||
ipcRenderer.send('restartToUpdate');
|
||||
ipcRenderer.send('restart-to-update');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.empty{
|
||||
color: $body-font-color;
|
||||
.empty {
|
||||
color: $body-font-color;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,89 +1,311 @@
|
||||
<template>
|
||||
<div class="editor-wrapper">
|
||||
<textarea
|
||||
ref="codemirror"
|
||||
:options="cmOptions"
|
||||
<div
|
||||
:id="`editor-${id}`"
|
||||
class="editor"
|
||||
:style="{height: `${height}px`}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CodeMirror from 'codemirror';
|
||||
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
import 'codemirror/theme/material-darker.css';
|
||||
import 'codemirror/mode/sql/sql';
|
||||
import 'codemirror/addon/edit/closebrackets';
|
||||
import 'codemirror/addon/selection/active-line';
|
||||
import 'codemirror/addon/hint/show-hint';
|
||||
import 'codemirror/addon/hint/show-hint.css';
|
||||
import 'codemirror/addon/hint/sql-hint';
|
||||
|
||||
CodeMirror.defineOption('sql-hint');
|
||||
import * as ace from 'ace-builds';
|
||||
import 'ace-builds/webpack-resolver';
|
||||
import '../libs/ext-language_tools';
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
|
||||
export default {
|
||||
name: 'QueryEditor',
|
||||
props: {
|
||||
value: String
|
||||
value: String,
|
||||
workspace: Object,
|
||||
isSelected: Boolean,
|
||||
schema: { type: String, default: '' },
|
||||
autoFocus: { type: Boolean, default: false },
|
||||
readOnly: { type: Boolean, default: false },
|
||||
height: { type: Number, default: 200 }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
cminstance: null,
|
||||
content: '',
|
||||
cmOptions: {
|
||||
tabSize: 3,
|
||||
smartIndent: true,
|
||||
styleActiveLine: true,
|
||||
lineNumbers: true,
|
||||
line: true,
|
||||
mode: 'text/x-sql',
|
||||
theme: 'material-darker',
|
||||
extraKeys: {
|
||||
'Ctrl-Space': 'autocomplete'
|
||||
},
|
||||
hintOptions: {
|
||||
tables: {
|
||||
users: ['name', 'score', 'birthDate'],
|
||||
countries: ['name', 'population', 'size']
|
||||
}
|
||||
},
|
||||
autoCloseBrackets: true
|
||||
}
|
||||
editor: null,
|
||||
fields: [],
|
||||
customCompleter: [],
|
||||
id: null,
|
||||
lastSchema: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
editorTheme: 'settings/getEditorTheme',
|
||||
autoComplete: 'settings/getAutoComplete',
|
||||
lineWrap: 'settings/getLineWrap',
|
||||
baseCompleter: 'application/getBaseCompleter'
|
||||
}),
|
||||
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
|
||||
});
|
||||
}
|
||||
},
|
||||
isSelected () {
|
||||
if (this.isSelected)
|
||||
this.lastSchema = this.schema;
|
||||
},
|
||||
lastSchema () {
|
||||
if (this.editor) {
|
||||
this.editor.completers = this.baseCompleter.map(el => Object.assign({}, el));
|
||||
this.setCustomCompleter();
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.id = this._uid;
|
||||
this.lastSchema = this.schema;
|
||||
},
|
||||
mounted () {
|
||||
this.initialize();
|
||||
this.editor = ace.edit(`editor-${this.id}`, {
|
||||
mode: `ace/mode/${this.mode}`,
|
||||
theme: `ace/theme/${this.editorTheme}`,
|
||||
value: this.value,
|
||||
fontSize: '14px',
|
||||
printMargin: false,
|
||||
readOnly: this.readOnly
|
||||
});
|
||||
|
||||
this.editor.setOptions({
|
||||
enableBasicAutocompletion: true,
|
||||
wrap: this.lineWrap,
|
||||
enableSnippets: true,
|
||||
enableLiveAutocompletion: this.autoComplete
|
||||
});
|
||||
|
||||
if (!this.baseCompleter.length)
|
||||
this.setBaseCompleters(this.editor.completers.map(el => Object.assign({}, el)));
|
||||
|
||||
this.setCustomCompleter();
|
||||
|
||||
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.customCompleter;
|
||||
}
|
||||
else
|
||||
this.editor.completers = this.customCompleter;
|
||||
}
|
||||
else
|
||||
this.editor.completers = this.customCompleter;
|
||||
}
|
||||
});
|
||||
|
||||
this.editor.session.on('change', () => {
|
||||
const content = this.editor.getValue();
|
||||
this.$emit('update:value', content);
|
||||
});
|
||||
|
||||
if (this.autoFocus) {
|
||||
setTimeout(() => {
|
||||
this.editor.focus();
|
||||
this.editor.resize();
|
||||
}, 20);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.editor.resize();
|
||||
}, 20);
|
||||
},
|
||||
methods: {
|
||||
initialize () {
|
||||
this.cminstance = CodeMirror.fromTextArea(this.$refs.codemirror, this.cmOptions);
|
||||
this.cminstance.setValue(this.value || this.content);
|
||||
|
||||
this.cminstance.on('change', cm => {
|
||||
this.content = cm.getValue();
|
||||
this.$emit('input', this.content);
|
||||
...mapActions({
|
||||
setBaseCompleters: 'application/setBaseCompleter'
|
||||
}),
|
||||
setCustomCompleter () {
|
||||
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.customCompleter = this.editor.completers;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.editor-wrapper{
|
||||
border-bottom: 1px solid #444444;
|
||||
}
|
||||
.editor-wrapper {
|
||||
border-bottom: 1px solid #444;
|
||||
|
||||
.CodeMirror{
|
||||
height: 200px;
|
||||
.editor {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.CodeMirror-scroll{
|
||||
max-width: 100%;
|
||||
}
|
||||
.ace_.mdi {
|
||||
display: inline-block;
|
||||
width: 17px;
|
||||
}
|
||||
|
||||
.CodeMirror-line {
|
||||
word-break: break-word!important;
|
||||
white-space: pre-wrap!important;
|
||||
word-break: normal;
|
||||
}
|
||||
}
|
||||
.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>
|
||||
|
||||
@@ -1,26 +1,33 @@
|
||||
<template>
|
||||
<BaseContextMenu
|
||||
:context-event="contextEvent"
|
||||
@closeContext="$emit('closeContext')"
|
||||
@close-context="$emit('close-context')"
|
||||
>
|
||||
<div class="context-element" @click="showEditModal(contextConnection)">
|
||||
<i class="material-icons md-18 text-light pr-1">edit</i> {{ $t('word.edit') }}
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-pencil text-light pr-1" /> {{ $t('word.edit') }}</span>
|
||||
</div>
|
||||
<div class="context-element" @click="showConfirmModal">
|
||||
<i class="material-icons md-18 text-light pr-1">delete</i> {{ $t('word.delete') }}
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ $t('word.delete') }}</span>
|
||||
</div>
|
||||
|
||||
<ModalEditConnection
|
||||
v-if="isEditModal"
|
||||
:connection="contextConnection"
|
||||
@close="hideEditModal"
|
||||
/>
|
||||
<ConfirmModal
|
||||
v-if="isConfirmModal"
|
||||
@confirm="deleteConnection(contextConnection)"
|
||||
@confirm="confirmDeleteConnection"
|
||||
@hide="hideConfirmModal"
|
||||
>
|
||||
<template :slot="'header'">
|
||||
{{ $t('message.deleteConnection') }}
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-server-remove mr-1" /> {{ $t('message.deleteConnection') }}
|
||||
</div>
|
||||
</template>
|
||||
<div :slot="'body'">
|
||||
<div class="mb-2">
|
||||
{{ $t('message.deleteConnectionCorfirm') }} <b>{{ connectionName }}</b>?
|
||||
{{ $t('message.deleteCorfirm') }} <b>{{ connectionName }}</b>?
|
||||
</div>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
@@ -31,11 +38,13 @@
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import BaseContextMenu from '@/components/BaseContextMenu';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
import ModalEditConnection from '@/components/ModalEditConnection';
|
||||
|
||||
export default {
|
||||
name: 'SettingBarContext',
|
||||
components: {
|
||||
BaseContextMenu,
|
||||
ModalEditConnection,
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
@@ -44,7 +53,8 @@ export default {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isConfirmModal: false
|
||||
isConfirmModal: false,
|
||||
isEditModal: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -57,19 +67,29 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
deleteConnection: 'connections/deleteConnection',
|
||||
showEditModal: 'application/showEditConnModal'
|
||||
deleteConnection: 'connections/deleteConnection'
|
||||
}),
|
||||
confirmDeleteConnection () {
|
||||
this.deleteConnection(this.contextConnection);
|
||||
this.closeContext();
|
||||
},
|
||||
showEditModal () {
|
||||
this.isEditModal = true;
|
||||
},
|
||||
hideEditModal () {
|
||||
this.isEditModal = false;
|
||||
this.closeContext();
|
||||
},
|
||||
showConfirmModal () {
|
||||
this.isConfirmModal = true;
|
||||
},
|
||||
hideConfirmModal () {
|
||||
this.isConfirmModal = false;
|
||||
this.closeContext();
|
||||
},
|
||||
closeContext () {
|
||||
this.$emit('close-context');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="columns">
|
||||
<div class="column col-12 empty text-light">
|
||||
<div class="empty-icon">
|
||||
<i class="material-icons md-48">mood</i>
|
||||
<i class="mdi mdi-48px mdi-emoticon" />
|
||||
</div>
|
||||
<p class="empty-title h5">
|
||||
{{ $t('message.appWelcome') }}
|
||||
@@ -11,7 +11,7 @@
|
||||
{{ $t('message.appFirstStep') }}
|
||||
</p>
|
||||
<div class="empty-action">
|
||||
<button class="btn btn-primary" @click="$emit('newConn')">
|
||||
<button class="btn btn-primary" @click="$emit('new-conn')">
|
||||
{{ $t('message.createConnection') }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -26,12 +26,12 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.empty{
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
.empty {
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,23 +3,23 @@
|
||||
<div class="footer-left-elements">
|
||||
<ul class="footer-elements">
|
||||
<li class="footer-element">
|
||||
<i class="material-icons md-18 mr-1">memory</i>
|
||||
<small>{{ appVersion }}</small>
|
||||
<i class="mdi mdi-18px mdi-database mr-1" />
|
||||
<small>{{ versionString }}</small>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-right-elements">
|
||||
<ul class="footer-elements">
|
||||
<li class="footer-element footer-link">
|
||||
<i class="material-icons md-18 mr-1">favorite</i>
|
||||
<li class="footer-element footer-link" @click="openOutside('https://github.com/sponsors/Fabio286')">
|
||||
<i class="mdi mdi-18px mdi-coffee mr-1" />
|
||||
<small>{{ $t('word.donate') }}</small>
|
||||
</li>
|
||||
<li class="footer-element footer-link">
|
||||
<i class="material-icons md-18">bug_report</i>
|
||||
<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')">
|
||||
<i class="material-icons md-18">info_outline</i>
|
||||
<i class="mdi mdi-18px mdi-information-outline" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -28,59 +28,72 @@
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
const { shell } = require('electron');
|
||||
|
||||
export default {
|
||||
name: 'TheFooter',
|
||||
computed: {
|
||||
...mapGetters({
|
||||
appName: 'application/appName',
|
||||
workspace: 'workspaces/getSelected',
|
||||
getWorkspace: 'workspaces/getWorkspace',
|
||||
appVersion: 'application/appVersion'
|
||||
})
|
||||
}),
|
||||
version () {
|
||||
return this.getWorkspace(this.workspace) ? this.getWorkspace(this.workspace).version : null;
|
||||
},
|
||||
versionString () {
|
||||
if (this.version)
|
||||
return `${this.version.name} ${this.version.number} (${this.version.arch} ${this.version.os})`;
|
||||
return '';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
showSettingModal: 'application/showSettingModal'
|
||||
})
|
||||
}),
|
||||
openOutside (link) {
|
||||
shell.openExternal(link);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#footer{
|
||||
height: $footer-height;
|
||||
#footer {
|
||||
height: $footer-height;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: $primary-color;
|
||||
padding: 0 0.2rem;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
box-shadow: 0 0 1px 0 #000;
|
||||
|
||||
.footer-elements {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: $primary-color;
|
||||
padding: 0 .2rem;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
box-shadow: 0 0 1px 0px #000;
|
||||
|
||||
.footer-elements{
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.footer-element {
|
||||
height: $footer-height;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 0.4rem;
|
||||
margin: 0;
|
||||
|
||||
.footer-element{
|
||||
height: $footer-height;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 .4rem;
|
||||
margin: 0;
|
||||
&.footer-link {
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
|
||||
&.footer-link{
|
||||
cursor: pointer;
|
||||
transition: background .2s;
|
||||
|
||||
&:hover{
|
||||
background: rgba($color: #fff, $alpha: .1);
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
background: rgba($color: #fff, $alpha: 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<div id="notifications-board">
|
||||
<div
|
||||
id="notifications-board"
|
||||
@mouseenter="clearTimeouts"
|
||||
@mouseleave="rearmTimeouts"
|
||||
>
|
||||
<transition-group name="slide-fade">
|
||||
<BaseNotification
|
||||
v-for="notification in latestNotifications"
|
||||
@@ -21,27 +25,60 @@ export default {
|
||||
components: {
|
||||
BaseNotification
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
timeouts: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
notifications: 'notifications/getNotifications'
|
||||
notifications: 'notifications/getNotifications',
|
||||
notificationsTimeout: 'settings/getNotificationsTimeout'
|
||||
}),
|
||||
latestNotifications () {
|
||||
return this.notifications.slice(0, 10);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
notifications: {
|
||||
deep: true,
|
||||
handler: function (notification) {
|
||||
if (notification.length) {
|
||||
this.timeouts[notification[0].uid] = setTimeout(() => {
|
||||
this.removeNotification(notification[0].uid);
|
||||
delete this.timeouts[notification.uid];
|
||||
}, this.notificationsTimeout * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
removeNotification: 'notifications/removeNotification'
|
||||
})
|
||||
}),
|
||||
clearTimeouts () {
|
||||
for (const uid in this.timeouts) {
|
||||
clearTimeout(this.timeouts[uid]);
|
||||
delete this.timeouts[uid];
|
||||
}
|
||||
},
|
||||
rearmTimeouts () {
|
||||
for (const notification of this.notifications) {
|
||||
this.timeouts[notification.uid] = setTimeout(() => {
|
||||
this.removeNotification(notification.uid);
|
||||
delete this.timeouts[notification.uid];
|
||||
}, this.notificationsTimeout * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#notifications-board{
|
||||
position: absolute;
|
||||
z-index: 9;
|
||||
right: 1rem;
|
||||
bottom: 1rem;
|
||||
}
|
||||
#notifications-board {
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
right: 1rem;
|
||||
bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
v-if="isContext"
|
||||
:context-event="contextEvent"
|
||||
:context-connection="contextConnection"
|
||||
@closeContext="isContext = false"
|
||||
@close-context="isContext = false"
|
||||
/>
|
||||
<ul class="settingbar-elements">
|
||||
<draggable v-model="connections">
|
||||
@@ -28,7 +28,7 @@
|
||||
@click="showNewConnModal"
|
||||
@mouseover.self="tooltipPosition"
|
||||
>
|
||||
<i class="settingbar-element-icon material-icons text-light">add</i>
|
||||
<i class="settingbar-element-icon mdi mdi-24px mdi-plus text-light" />
|
||||
<span class="ex-tooltip-content">{{ $t('message.addConnection') }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -37,7 +37,7 @@
|
||||
<div class="settingbar-bottom-elements">
|
||||
<ul class="settingbar-elements">
|
||||
<li class="settingbar-element btn btn-link ex-tooltip" @click="showSettingModal('general')">
|
||||
<i class="settingbar-element-icon material-icons text-light">settings</i>
|
||||
<i class="settingbar-element-icon mdi mdi-24px mdi-cog text-light" :class="{' badge badge-update': hasUpdates}" />
|
||||
<span class="ex-tooltip-content">{{ $t('word.settings') }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -70,7 +70,8 @@ export default {
|
||||
getConnections: 'connections/getConnections',
|
||||
getConnectionName: 'connections/getConnectionName',
|
||||
connected: 'workspaces/getConnected',
|
||||
selectedWorkspace: 'workspaces/getSelected'
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
updateStatus: 'application/getUpdateStatus'
|
||||
}),
|
||||
connections: {
|
||||
get () {
|
||||
@@ -79,6 +80,9 @@ export default {
|
||||
set (value) {
|
||||
this.updateConnections(value);
|
||||
}
|
||||
},
|
||||
hasUpdates () {
|
||||
return ['available', 'downloading', 'downloaded'].includes(this.updateStatus);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -106,101 +110,104 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#settingbar{
|
||||
width: $settingbar-width;
|
||||
height: calc(100vh - #{$excluding-size});
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
#settingbar {
|
||||
width: $settingbar-width;
|
||||
height: calc(100vh - #{$excluding-size});
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: $bg-color-light;
|
||||
padding: 0;
|
||||
box-shadow: 0 0 1px 0 #000;
|
||||
z-index: 9;
|
||||
|
||||
.settingbar-top-elements {
|
||||
overflow-x: hidden;
|
||||
overflow-y: overlay;
|
||||
max-height: calc((100vh - 3.5rem) - #{$excluding-size});
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.settingbar-bottom-elements {
|
||||
padding-top: 0.5rem;
|
||||
background: $bg-color-light;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.settingbar-elements {
|
||||
list-style: none;
|
||||
text-align: center;
|
||||
width: $settingbar-width;
|
||||
padding: 0;
|
||||
box-shadow: 0 0 1px 0px #000;
|
||||
z-index: 9;
|
||||
margin: 0;
|
||||
|
||||
.settingbar-top-elements{
|
||||
overflow-x: hidden;
|
||||
overflow-y: overlay;
|
||||
max-height: calc((100vh - 3.5rem) - #{$excluding-size});
|
||||
.settingbar-element {
|
||||
height: $settingbar-width;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
border-left: 3px solid transparent;
|
||||
opacity: 0.5;
|
||||
transition: opacity 0.2s;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 3px;
|
||||
}
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-left-color: $body-font-color;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.settingbar-element-icon {
|
||||
&.badge::after {
|
||||
bottom: -10px;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
background: $success-color;
|
||||
}
|
||||
|
||||
&.badge-update::after {
|
||||
bottom: initial;
|
||||
background: $primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.settingbar-bottom-elements{
|
||||
padding-top: .5rem;
|
||||
background: $bg-color-light;
|
||||
z-index: 1;
|
||||
}
|
||||
.ex-tooltip {// Because both overflow-x: visible and overflow-y:auto are evil!!!
|
||||
.ex-tooltip-content {
|
||||
z-index: 999;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
display: block;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
margin: 0 0 0 calc(#{$settingbar-width} - 5px);
|
||||
left: 0;
|
||||
padding: 0.2rem 0.4rem;
|
||||
font-size: 0.7rem;
|
||||
background: rgba(48, 55, 66, 0.95);
|
||||
border-radius: 0.1rem;
|
||||
color: #fff;
|
||||
max-width: 320px;
|
||||
pointer-events: none;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.settingbar-elements{
|
||||
list-style: none;
|
||||
text-align: center;
|
||||
width: $settingbar-width;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
.settingbar-element{
|
||||
height: $settingbar-width;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
border-left: 3px solid transparent;
|
||||
opacity: .5;
|
||||
transition: opacity .2s;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
|
||||
&:hover{
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.selected{
|
||||
border-left-color: $body-font-color;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.settingbar-element-icon{
|
||||
|
||||
&.badge::after{
|
||||
bottom: -10px;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
background: $success-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.ex-tooltip{// Because both overflow-x: visible and overflow-y:auto are evil!!!
|
||||
.ex-tooltip-content{
|
||||
z-index: 999;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
display:block;
|
||||
position:absolute;
|
||||
background-color:#feffe1;
|
||||
text-align: center;
|
||||
margin:.0 0 0 calc(#{$settingbar-width} - 5px);
|
||||
left: 0;
|
||||
padding: .2rem .4rem;
|
||||
font-size: .7rem;
|
||||
background: rgba(48,55,66,.95);
|
||||
border-radius: .1rem;
|
||||
color: #fff;
|
||||
max-width: 320px;
|
||||
pointer-events: none;
|
||||
text-overflow: ellipsis;
|
||||
transition: opacity .2s;
|
||||
}
|
||||
|
||||
&:hover .ex-tooltip-content{
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
&:hover .ex-tooltip-content {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<div class="titlebar-elements">
|
||||
<img class="titlebar-logo" :src="require('@/images/logo.svg').default">
|
||||
</div>
|
||||
<div class="titlebar-elements">
|
||||
<!-- -->
|
||||
<div class="titlebar-elements titlebar-title">
|
||||
{{ windowTitle }}
|
||||
</div>
|
||||
<div class="titlebar-elements">
|
||||
<div
|
||||
@@ -13,31 +13,32 @@
|
||||
class="titlebar-element"
|
||||
@click="openDevTools"
|
||||
>
|
||||
<i class="material-icons">code</i>
|
||||
<i class="mdi mdi-24px mdi-code-tags" />
|
||||
</div>
|
||||
<div
|
||||
v-if="isDevelopment"
|
||||
class="titlebar-element"
|
||||
@click="reload"
|
||||
>
|
||||
<i class="material-icons">refresh</i>
|
||||
<i class="mdi mdi-24px mdi-refresh" />
|
||||
</div>
|
||||
<div class="titlebar-element" @click="minimizeApp">
|
||||
<i class="material-icons">remove</i>
|
||||
<i class="mdi mdi-24px mdi-minus" />
|
||||
</div>
|
||||
<div class="titlebar-element" @click="toggleFullScreen">
|
||||
<i v-if="isMaximized" class="material-icons">fullscreen_exit</i>
|
||||
<i v-else class="material-icons">fullscreen</i>
|
||||
<i v-if="isMaximized" class="mdi mdi-24px mdi-fullscreen-exit" />
|
||||
<i v-else class="mdi mdi-24px mdi-fullscreen" />
|
||||
</div>
|
||||
<div class="titlebar-element close-button" @click="closeApp">
|
||||
<i class="material-icons">close</i>
|
||||
<i class="mdi mdi-24px mdi-close" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { remote } from 'electron';
|
||||
import { remote, ipcRenderer } from 'electron';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'TheTitleBar',
|
||||
@@ -48,6 +49,22 @@ export default {
|
||||
isDevelopment: process.env.NODE_ENV === 'development'
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
getConnectionName: 'connections/getConnectionName',
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
windowTitle () {
|
||||
if (!this.selectedWorkspace) return '';
|
||||
|
||||
const connectionName = this.getConnectionName(this.selectedWorkspace);
|
||||
const workspace = this.getWorkspace(this.selectedWorkspace);
|
||||
const breadcrumbs = Object.values(workspace.breadcrumbs).filter(breadcrumb => breadcrumb);
|
||||
|
||||
return [connectionName, ...breadcrumbs].join(' • ');
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('resize', this.onResize);
|
||||
},
|
||||
@@ -56,7 +73,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
closeApp () {
|
||||
this.w.close();
|
||||
ipcRenderer.send('close-app');
|
||||
},
|
||||
minimizeApp () {
|
||||
this.w.minimize();
|
||||
@@ -81,55 +98,64 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#titlebar{
|
||||
#titlebar {
|
||||
display: flex;
|
||||
position: relative;
|
||||
justify-content: space-between;
|
||||
background: $bg-color-light;
|
||||
align-items: center;
|
||||
height: $titlebar-height;
|
||||
-webkit-app-region: drag;
|
||||
user-select: none;
|
||||
box-shadow: 0 0 1px 0 #000;
|
||||
z-index: 9999;
|
||||
|
||||
.titlebar-resizer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
z-index: 999;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.titlebar-elements {
|
||||
display: flex;
|
||||
position: relative;
|
||||
justify-content: space-between;
|
||||
background: $bg-color-light;
|
||||
align-items: center;
|
||||
height: $titlebar-height;
|
||||
-webkit-app-region: drag;
|
||||
user-select: none;
|
||||
box-shadow: 0 0 1px 0px #000;
|
||||
z-index: 9999;
|
||||
|
||||
.titlebar-resizer{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
z-index: 999;
|
||||
-webkit-app-region: no-drag;
|
||||
&.titlebar-title {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.titlebar-elements{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.titlebar-logo{
|
||||
height: $titlebar-height;
|
||||
padding: 0 .4rem;
|
||||
}
|
||||
|
||||
.titlebar-element{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: $titlebar-height;
|
||||
line-height: 0;
|
||||
padding: 0 .7rem;
|
||||
opacity: .7;
|
||||
transition: opacity .2s;
|
||||
-webkit-app-region: no-drag;
|
||||
|
||||
&:hover{
|
||||
opacity: 1;
|
||||
background: rgba($color: #fff, $alpha: .2);
|
||||
}
|
||||
|
||||
&.close-button:hover{
|
||||
background: red;
|
||||
}
|
||||
}
|
||||
.titlebar-logo {
|
||||
height: $titlebar-height;
|
||||
padding: 0 0.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.titlebar-element {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: $titlebar-height;
|
||||
line-height: 0;
|
||||
padding: 0 0.7rem;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s;
|
||||
-webkit-app-region: no-drag;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
background: rgba($color: #fff, $alpha: 0.2);
|
||||
}
|
||||
|
||||
&.close-button:hover {
|
||||
background: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,37 +2,115 @@
|
||||
<div v-show="isSelected" class="workspace column columns col-gapless">
|
||||
<WorkspaceExploreBar :connection="connection" :is-selected="isSelected" />
|
||||
<div v-if="workspace.connected" class="workspace-tabs column columns col-gapless">
|
||||
<ul class="tab tab-block column col-12">
|
||||
<li
|
||||
v-if="workspace.breadcrumbs.table"
|
||||
class="tab-item"
|
||||
:class="{'active': selectedTab === 1}"
|
||||
@click="selectTab({uid: workspace.uid, tab: 1})"
|
||||
>
|
||||
<a class="tab-link">
|
||||
<i class="material-icons md-18 mr-1">grid_on</i>
|
||||
<span :title="workspace.breadcrumbs.table">{{ workspace.breadcrumbs.table }}</span>
|
||||
<ul
|
||||
id="tabWrap"
|
||||
ref="tabWrap"
|
||||
class="tab tab-block column col-12"
|
||||
>
|
||||
<li class="tab-item d-none">
|
||||
<a class="tab-link workspace-tools-link">
|
||||
<i class="mdi mdi-24px mdi-tools" />
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
v-for="(tab, key) of queryTabs"
|
||||
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="schemaChild">{{ $t('word.settings').toUpperCase() }}: {{ schemaChild }}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
v-if="workspace.breadcrumbs.table || workspace.breadcrumbs.view"
|
||||
class="tab-item"
|
||||
:class="{'active': selectedTab === 'data'}"
|
||||
@click="selectTab({uid: workspace.uid, tab: 'data'})"
|
||||
>
|
||||
<a class="tab-link">
|
||||
<i class="mdi mdi-18px mr-1" :class="workspace.breadcrumbs.table ? 'mdi-table' : 'mdi-table-eye'" />
|
||||
<span :title="schemaChild">{{ $t('word.data').toUpperCase() }}: {{ schemaChild }}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
v-for="tab of queryTabs"
|
||||
:key="tab.uid"
|
||||
class="tab-item"
|
||||
:class="{'active': selectedTab === tab.uid}"
|
||||
@click="selectTab({uid: workspace.uid, tab: tab.uid})"
|
||||
@mouseup.middle="closeTab(tab.uid)"
|
||||
>
|
||||
<a><span>Query #{{ key+1 }} <span v-if="queryTabs.length > 1" class="btn btn-clear" /></span></a>
|
||||
<a class="tab-link">
|
||||
<i class="mdi mdi-18px mdi-code-tags mr-1" />
|
||||
<span>
|
||||
Query #{{ tab.index }}
|
||||
<span
|
||||
v-if="queryTabs.length > 1"
|
||||
class="btn btn-clear"
|
||||
:title="$t('word.close')"
|
||||
@click.stop="closeTab(tab.uid)"
|
||||
/>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="tab-item">
|
||||
<a
|
||||
class="tab-add"
|
||||
:title="$t('message.openNewTab')"
|
||||
@click="addTab"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-plus" />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<WorkspaceTableTab
|
||||
v-show="selectedTab === 1"
|
||||
<WorkspacePropsTab
|
||||
v-show="selectedTab === 'prop' && workspace.breadcrumbs.table"
|
||||
:is-selected="selectedTab === 'prop'"
|
||||
:connection="connection"
|
||||
:table="workspace.breadcrumbs.table"
|
||||
/>
|
||||
<WorkspacePropsTabView
|
||||
v-show="selectedTab === 'prop' && workspace.breadcrumbs.view"
|
||||
:is-selected="selectedTab === 'prop'"
|
||||
:connection="connection"
|
||||
:view="workspace.breadcrumbs.view"
|
||||
/>
|
||||
<WorkspacePropsTabTrigger
|
||||
v-show="selectedTab === 'prop' && workspace.breadcrumbs.trigger"
|
||||
:is-selected="selectedTab === 'prop'"
|
||||
:connection="connection"
|
||||
:trigger="workspace.breadcrumbs.trigger"
|
||||
/>
|
||||
<WorkspacePropsTabRoutine
|
||||
v-show="selectedTab === 'prop' && workspace.breadcrumbs.procedure"
|
||||
:is-selected="selectedTab === 'prop'"
|
||||
:connection="connection"
|
||||
:routine="workspace.breadcrumbs.procedure"
|
||||
/>
|
||||
<WorkspacePropsTabFunction
|
||||
v-show="selectedTab === 'prop' && workspace.breadcrumbs.function"
|
||||
:is-selected="selectedTab === 'prop'"
|
||||
:connection="connection"
|
||||
:function="workspace.breadcrumbs.function"
|
||||
/>
|
||||
<WorkspacePropsTabScheduler
|
||||
v-show="selectedTab === 'prop' && workspace.breadcrumbs.scheduler"
|
||||
:is-selected="selectedTab === 'prop'"
|
||||
:connection="connection"
|
||||
:scheduler="workspace.breadcrumbs.scheduler"
|
||||
/>
|
||||
<WorkspaceTableTab
|
||||
v-show="selectedTab === 'data'"
|
||||
:connection="connection"
|
||||
:table="workspace.breadcrumbs.table || workspace.breadcrumbs.view"
|
||||
/>
|
||||
<WorkspaceQueryTab
|
||||
v-for="tab of queryTabs"
|
||||
v-show="selectedTab === tab.uid"
|
||||
:key="tab.uid"
|
||||
:tab="tab"
|
||||
:is-selected="selectedTab === tab.uid"
|
||||
:connection="connection"
|
||||
/>
|
||||
</div>
|
||||
@@ -45,17 +123,34 @@ import Connection from '@/ipc-api/Connection';
|
||||
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',
|
||||
components: {
|
||||
WorkspaceExploreBar,
|
||||
WorkspaceQueryTab,
|
||||
WorkspaceTableTab
|
||||
WorkspaceTableTab,
|
||||
WorkspacePropsTab,
|
||||
WorkspacePropsTabView,
|
||||
WorkspacePropsTabTrigger,
|
||||
WorkspacePropsTabRoutine,
|
||||
WorkspacePropsTabFunction,
|
||||
WorkspacePropsTabScheduler
|
||||
},
|
||||
props: {
|
||||
connection: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
hasWheelEvent: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
@@ -68,10 +163,38 @@ export default {
|
||||
return this.selectedWorkspace === this.connection.uid;
|
||||
},
|
||||
selectedTab () {
|
||||
return this.workspace.selected_tab || this.queryTabs[0].uid;
|
||||
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)
|
||||
) ||
|
||||
(
|
||||
this.workspace.breadcrumbs.table === null &&
|
||||
this.workspace.breadcrumbs.view === null &&
|
||||
this.workspace.selected_tab === 'data'
|
||||
)
|
||||
)
|
||||
return this.queryTabs[0].uid;
|
||||
|
||||
return this.queryTabs.find(tab => tab.uid === this.workspace.selected_tab) ||
|
||||
['data', 'prop'].includes(this.workspace.selected_tab)
|
||||
? this.workspace.selected_tab
|
||||
: this.queryTabs[0].uid;
|
||||
},
|
||||
queryTabs () {
|
||||
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 () {
|
||||
@@ -80,102 +203,155 @@ export default {
|
||||
if (isInitiated)
|
||||
this.connectWorkspace(this.connection);
|
||||
},
|
||||
mounted () {
|
||||
if (this.$refs.tabWrap) {
|
||||
this.$refs.tabWrap.addEventListener('wheel', e => {
|
||||
if (e.deltaY > 0) this.$refs.tabWrap.scrollLeft += 50;
|
||||
else this.$refs.tabWrap.scrollLeft -= 50;
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
addWorkspace: 'workspaces/addWorkspace',
|
||||
connectWorkspace: 'workspaces/connectWorkspace',
|
||||
removeConnected: 'workspaces/removeConnected',
|
||||
selectTab: 'workspaces/selectTab'
|
||||
})
|
||||
selectTab: 'workspaces/selectTab',
|
||||
newTab: 'workspaces/newTab',
|
||||
removeTab: 'workspaces/removeTab'
|
||||
}),
|
||||
addTab () {
|
||||
this.newTab({ uid: this.connection.uid });
|
||||
|
||||
if (!this.hasWheelEvent) {
|
||||
this.$refs.tabWrap.addEventListener('wheel', e => {
|
||||
if (e.deltaY > 0) this.$refs.tabWrap.scrollLeft += 50;
|
||||
else this.$refs.tabWrap.scrollLeft -= 50;
|
||||
});
|
||||
this.hasWheelEvent = true;
|
||||
}
|
||||
},
|
||||
closeTab (tUid) {
|
||||
if (this.queryTabs.length === 1) return;
|
||||
this.removeTab({ uid: this.connection.uid, tab: tUid });
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.workspace{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
.workspace {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
.workspace-tabs{
|
||||
.workspace-tabs {
|
||||
overflow: hidden;
|
||||
height: calc(100vh - #{$excluding-size});
|
||||
|
||||
.tab-block {
|
||||
background: $bg-color-light;
|
||||
margin-top: 0;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
flex-wrap: nowrap;
|
||||
overflow: auto;
|
||||
height: calc(100vh - #{$excluding-size});
|
||||
margin-bottom: 0;
|
||||
|
||||
.tab-block{
|
||||
background: $bg-color-light;
|
||||
margin-top: 0;
|
||||
|
||||
.tab-item{
|
||||
max-width: 12rem;
|
||||
width: fit-content;
|
||||
flex: initial;
|
||||
|
||||
&.active a{
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
> a{
|
||||
padding: .2rem .8rem;
|
||||
color: $body-font-color;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
opacity: .7;
|
||||
transition: opacity .2s;
|
||||
|
||||
&:hover{
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
> span {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.workspace-query-results{
|
||||
overflow: auto;
|
||||
white-space: nowrap;
|
||||
.tab-item {
|
||||
max-width: 12rem;
|
||||
width: fit-content;
|
||||
flex: initial;
|
||||
|
||||
.table{
|
||||
width: auto;
|
||||
border-collapse: separate;
|
||||
> a {
|
||||
padding: 0.2rem 0.8rem;
|
||||
color: $body-font-color;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
.th{
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: $bg-color;
|
||||
border: 1px solid;
|
||||
border-left: none;
|
||||
border-bottom-width: 2px;
|
||||
border-color: $bg-color-light;
|
||||
padding: .1rem .4rem;
|
||||
font-weight: 700;
|
||||
font-size: .7rem;
|
||||
}
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.td{
|
||||
border-right: 1px solid;
|
||||
border-bottom: 1px solid;
|
||||
border-color: $bg-color-light;
|
||||
padding: 0 .4rem;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 200px;
|
||||
white-space: nowrap;
|
||||
&.tab-add {
|
||||
padding: 0.2rem 0.4rem;
|
||||
margin-top: 2px;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
> span {
|
||||
overflow: hidden;
|
||||
font-size: .7rem;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
padding: 0 0.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus{
|
||||
box-shadow:inset 0px 0px 0px 1px $body-font-color;
|
||||
background: rgba($color: #000000, $alpha: .3);
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
&.active a {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.workspace-tools-link {
|
||||
padding-bottom: 0;
|
||||
padding-top: 0.3rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.workspace-query-results {
|
||||
overflow: auto;
|
||||
white-space: nowrap;
|
||||
|
||||
.table {
|
||||
width: auto;
|
||||
border-collapse: separate;
|
||||
|
||||
.th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: $bg-color;
|
||||
border: 1px solid;
|
||||
border-left: none;
|
||||
border-bottom-width: 2px;
|
||||
border-color: $bg-color-light;
|
||||
padding: 0;
|
||||
font-weight: 700;
|
||||
font-size: 0.7rem;
|
||||
z-index: 1;
|
||||
|
||||
> div {
|
||||
padding: 0.1rem 0.4rem;
|
||||
min-width: -webkit-fill-available;
|
||||
}
|
||||
}
|
||||
|
||||
.td {
|
||||
border-right: 1px solid;
|
||||
border-bottom: 1px solid;
|
||||
border-color: $bg-color-light;
|
||||
padding: 0 0.4rem;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 200px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
font-size: 0.7rem;
|
||||
position: relative;
|
||||
|
||||
&:focus {
|
||||
box-shadow: inset 0 0 0 1px $body-font-color;
|
||||
background: rgba($color: #000, $alpha: 0.3);
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<div class="columns">
|
||||
<div class="column col-12 empty text-light">
|
||||
<div class="empty-icon">
|
||||
<i class="material-icons md-48">cloud_off</i>
|
||||
<i class="mdi mdi-48px mdi-power-plug-off" />
|
||||
</div>
|
||||
<p class="empty-title h5">
|
||||
{{ $t('word.disconnected') }}
|
||||
{{ isConnecting ? $t('word.connecting') : $t('word.disconnected') }}
|
||||
</p>
|
||||
<div class="empty-action">
|
||||
<button
|
||||
@@ -19,7 +19,7 @@
|
||||
</div>
|
||||
<ModalAskCredentials
|
||||
v-if="isAsking"
|
||||
@closeAsking="closeAsking"
|
||||
@close-asking="closeAsking"
|
||||
@credentials="continueTest"
|
||||
/>
|
||||
</div>
|
||||
@@ -45,7 +45,6 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
connectWorkspace: 'workspaces/connectWorkspace'
|
||||
}),
|
||||
async startConnection () {
|
||||
@@ -73,11 +72,11 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.empty{
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.empty {
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,18 +10,34 @@
|
||||
<span class="workspace-explorebar-title">{{ connectionName }}</span>
|
||||
<span v-if="workspace.connected" class="workspace-explorebar-tools">
|
||||
<i
|
||||
class="material-icons md-18 c-hand"
|
||||
class="mdi mdi-18px mdi-database-plus c-hand mr-2"
|
||||
:title="$t('message.createNewDatabase')"
|
||||
@click="showNewDBModal"
|
||||
/>
|
||||
<i
|
||||
class="mdi mdi-18px mdi-refresh c-hand mr-2"
|
||||
:class="{'rotate':isRefreshing}"
|
||||
:title="$t('word.refresh')"
|
||||
@click="refresh"
|
||||
>refresh</i>
|
||||
/>
|
||||
<i
|
||||
class="material-icons md-18 c-hand mr-1 ml-2"
|
||||
class="mdi mdi-18px mdi-power-plug-off c-hand"
|
||||
:title="$t('word.disconnect')"
|
||||
@click="disconnectWorkspace(connection.uid)"
|
||||
>exit_to_app</i>
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="workspace-explorebar-search">
|
||||
<div v-if="workspace.connected" class="has-icon-right">
|
||||
<input
|
||||
v-model="searchTerm"
|
||||
class="form-input input-sm"
|
||||
type="text"
|
||||
:placeholder="$t('message.searchForElements')"
|
||||
>
|
||||
<i class="form-icon mdi mdi-magnify mdi-18px" />
|
||||
</div>
|
||||
</div>
|
||||
<WorkspaceConnectPanel
|
||||
v-if="!workspace.connected"
|
||||
class="workspace-explorebar-body"
|
||||
@@ -33,23 +49,121 @@
|
||||
:key="db.name"
|
||||
:database="db"
|
||||
:connection="connection"
|
||||
@show-database-context="openDatabaseContext"
|
||||
@show-table-context="openTableContext"
|
||||
@show-misc-context="openMiscContext"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ModalNewDatabase
|
||||
v-if="isNewDBModal"
|
||||
@close="hideNewDBModal"
|
||||
@reload="refresh"
|
||||
/>
|
||||
<ModalNewTable
|
||||
v-if="isNewTableModal"
|
||||
:workspace="workspace"
|
||||
@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
|
||||
v-if="isTableContext"
|
||||
:selected-table="selectedTable"
|
||||
:context-event="tableContextEvent"
|
||||
@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 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',
|
||||
components: {
|
||||
WorkspaceConnectPanel,
|
||||
WorkspaceExploreBarDatabase
|
||||
WorkspaceExploreBarDatabase,
|
||||
DatabaseContext,
|
||||
TableContext,
|
||||
MiscContext,
|
||||
ModalNewDatabase,
|
||||
ModalNewTable,
|
||||
ModalNewView,
|
||||
ModalNewTrigger,
|
||||
ModalNewRoutine,
|
||||
ModalNewFunction,
|
||||
ModalNewScheduler
|
||||
},
|
||||
props: {
|
||||
connection: Object,
|
||||
@@ -58,7 +172,30 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
isRefreshing: false,
|
||||
localWidth: null
|
||||
|
||||
isNewDBModal: false,
|
||||
isNewTableModal: false,
|
||||
isNewViewModal: false,
|
||||
isNewTriggerModal: false,
|
||||
isNewRoutineModal: false,
|
||||
isNewFunctionModal: false,
|
||||
isNewSchedulerModal: false,
|
||||
|
||||
localWidth: null,
|
||||
explorebarWidthInterval: null,
|
||||
searchTermInterval: null,
|
||||
isDatabaseContext: false,
|
||||
isTableContext: false,
|
||||
isMiscContext: false,
|
||||
|
||||
databaseContextEvent: null,
|
||||
tableContextEvent: null,
|
||||
miscContextEvent: null,
|
||||
|
||||
selectedDatabase: '',
|
||||
selectedTable: null,
|
||||
selectedMisc: null,
|
||||
searchTerm: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -75,11 +212,22 @@ export default {
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
localWidth: _.debounce(function (val) {
|
||||
this.changeExplorebarSize(val);
|
||||
}, 500),
|
||||
localWidth (val) {
|
||||
clearTimeout(this.explorebarWidthInterval);
|
||||
|
||||
this.explorebarWidthInterval = setTimeout(() => {
|
||||
this.changeExplorebarSize(val);
|
||||
}, 500);
|
||||
},
|
||||
isSelected (val) {
|
||||
if (val) this.localWidth = this.explorebarSize;
|
||||
},
|
||||
searchTerm () {
|
||||
clearTimeout(this.searchTermInterval);
|
||||
|
||||
this.searchTermInterval = setTimeout(() => {
|
||||
this.setSearchTerm(this.searchTerm);
|
||||
}, 200);
|
||||
}
|
||||
},
|
||||
created () {
|
||||
@@ -88,7 +236,7 @@ export default {
|
||||
mounted () {
|
||||
const resizer = this.$refs.resizer;
|
||||
|
||||
resizer.addEventListener('mousedown', (e) => {
|
||||
resizer.addEventListener('mousedown', e => {
|
||||
e.preventDefault();
|
||||
|
||||
window.addEventListener('mousemove', this.resize);
|
||||
@@ -99,6 +247,10 @@ export default {
|
||||
...mapActions({
|
||||
disconnectWorkspace: 'workspaces/removeConnected',
|
||||
refreshStructure: 'workspaces/refreshStructure',
|
||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
|
||||
selectTab: 'workspaces/selectTab',
|
||||
setSearchTerm: 'workspaces/setSearchTerm',
|
||||
addNotification: 'notifications/addNotification',
|
||||
changeExplorebarSize: 'settings/changeExplorebarSize'
|
||||
}),
|
||||
async refresh () {
|
||||
@@ -117,76 +269,275 @@ export default {
|
||||
},
|
||||
stopResize () {
|
||||
window.removeEventListener('mousemove', this.resize);
|
||||
},
|
||||
showNewDBModal () {
|
||||
this.isNewDBModal = true;
|
||||
},
|
||||
hideNewDBModal () {
|
||||
this.isNewDBModal = false;
|
||||
},
|
||||
showCreateTableModal () {
|
||||
this.closeDatabaseContext();
|
||||
this.isNewTableModal = true;
|
||||
},
|
||||
hideCreateTableModal () {
|
||||
this.isNewTableModal = false;
|
||||
},
|
||||
async openCreateTableEditor (payload) {
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
...payload
|
||||
};
|
||||
|
||||
const { status, response } = await Tables.createTable(params);
|
||||
|
||||
if (status === 'success') {
|
||||
await this.refresh();
|
||||
this.changeBreadcrumbs({ schema: this.selectedDatabase, table: payload.name });
|
||||
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
},
|
||||
openDatabaseContext (payload) {
|
||||
this.selectedDatabase = payload.database;
|
||||
this.databaseContextEvent = payload.event;
|
||||
this.isDatabaseContext = true;
|
||||
},
|
||||
closeDatabaseContext () {
|
||||
this.isDatabaseContext = false;
|
||||
},
|
||||
openTableContext (payload) {
|
||||
this.selectedTable = payload.table;
|
||||
this.tableContextEvent = payload.event;
|
||||
this.isTableContext = true;
|
||||
},
|
||||
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 });
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.workspace-explorebar-resizer{
|
||||
position: absolute;
|
||||
width: 4px;
|
||||
right: -2px;
|
||||
top: 0;
|
||||
height: calc(100vh - #{$excluding-size});
|
||||
cursor: ew-resize;
|
||||
z-index: 99;
|
||||
}
|
||||
.workspace-explorebar-resizer {
|
||||
position: absolute;
|
||||
width: 4px;
|
||||
right: -2px;
|
||||
top: 0;
|
||||
height: calc(100vh - #{$excluding-size});
|
||||
cursor: ew-resize;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.workspace-explorebar{
|
||||
width: $explorebar-width;
|
||||
.workspace-explorebar {
|
||||
width: $explorebar-width;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
background: $bg-color-gray;
|
||||
box-shadow: 0 0 1px 0 #000;
|
||||
z-index: 8;
|
||||
flex: initial;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
|
||||
.workspace-explorebar-header {
|
||||
width: 100%;
|
||||
padding: 0.3rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
background: $bg-color-gray;
|
||||
box-shadow: 0 0 1px 0px #000;
|
||||
z-index: 8;
|
||||
flex: initial;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
justify-content: space-between;
|
||||
font-size: 0.6rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
|
||||
.workspace-explorebar-header{
|
||||
width: 100%;
|
||||
padding: .3rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: .6rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
|
||||
.workspace-explorebar-title{
|
||||
width: 80%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.workspace-explorebar-tools {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> i{
|
||||
opacity: .6;
|
||||
transition: opacity .2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:hover{
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.workspace-explorebar-title {
|
||||
width: 80%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.workspace-explorebar-body{
|
||||
width: 100%;
|
||||
height: calc((100vh - 30px) - #{$excluding-size});
|
||||
overflow: overlay;
|
||||
padding: 0 .1rem;
|
||||
.workspace-explorebar-tools {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> i {
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.workspace-explorebar-search {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.6rem;
|
||||
height: 28px;
|
||||
|
||||
.has-icon-right {
|
||||
width: 100%;
|
||||
padding: 0.1rem;
|
||||
|
||||
.form-icon {
|
||||
opacity: 0.5;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
height: 1.2rem;
|
||||
padding-left: 0.2rem;
|
||||
|
||||
&:focus + .form-icon {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.workspace-explorebar-body {
|
||||
width: 100%;
|
||||
height: calc((100vh - 58px) - #{$excluding-size});
|
||||
overflow: overlay;
|
||||
padding: 0 0.1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,37 +1,160 @@
|
||||
<template>
|
||||
<details class="accordion workspace-explorebar-database">
|
||||
<summary
|
||||
class="accordion-header database-name pb-0"
|
||||
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 material-icons md-18 mr-1">navigate_next</i>
|
||||
<i class="material-icons md-18 mr-1">view_agenda</i>
|
||||
<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>
|
||||
<div class="accordion-body">
|
||||
<div class="database-tables">
|
||||
<ul class="menu menu-nav pt-0">
|
||||
<li
|
||||
v-for="table of database.tables"
|
||||
:key="table.TABLE_NAME"
|
||||
v-for="table of filteredTables"
|
||||
:key="table.name"
|
||||
class="menu-item"
|
||||
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.table === table.TABLE_NAME}"
|
||||
@click="changeBreadcrumbs({schema: database.name, table: table.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="material-icons md-18 mr-1">grid_on</i>
|
||||
<span>{{ table.TABLE_NAME }}</span>
|
||||
<i class="table-icon mdi mdi-18px mr-1" :class="table.type === 'view' ? 'mdi-table-eye' : 'mdi-table'" />
|
||||
<span v-html="highlightWord(table.name)" />
|
||||
</a>
|
||||
<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="filteredTriggers.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 filteredTriggers"
|
||||
: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 v-html="highlightWord(trigger.name)" />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div v-if="filteredProcedures.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 filteredProcedures"
|
||||
: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 v-html="highlightWord(procedure.name)" />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div v-if="filteredFunctions.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 filteredFunctions"
|
||||
: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 v-html="highlightWord(func.name)" />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div v-if="filteredSchedulers.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 filteredSchedulers"
|
||||
: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 v-html="highlightWord(scheduler.name)" />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import { formatBytes } from 'common/libs/formatBytes';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceExploreBarDatabase',
|
||||
@@ -39,52 +162,217 @@ export default {
|
||||
database: Object,
|
||||
connection: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isLoading: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
getLoadedSchemas: 'workspaces/getLoadedSchemas',
|
||||
getWorkspace: 'workspaces/getWorkspace',
|
||||
getSearchTerm: 'workspaces/getSearchTerm'
|
||||
}),
|
||||
searchTerm () {
|
||||
return this.getSearchTerm(this.connection.uid);
|
||||
},
|
||||
filteredTables () {
|
||||
return this.database.tables.filter(table => table.name.search(this.searchTerm) >= 0);
|
||||
},
|
||||
filteredTriggers () {
|
||||
return this.database.triggers.filter(trigger => trigger.name.search(this.searchTerm) >= 0);
|
||||
},
|
||||
filteredProcedures () {
|
||||
return this.database.procedures.filter(procedure => procedure.name.search(this.searchTerm) >= 0);
|
||||
},
|
||||
filteredFunctions () {
|
||||
return this.database.functions.filter(func => func.name.search(this.searchTerm) >= 0);
|
||||
},
|
||||
filteredSchedulers () {
|
||||
return this.database.schedulers.filter(scheduler => scheduler.name.search(this.searchTerm) >= 0);
|
||||
},
|
||||
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;
|
||||
return acc;
|
||||
}, 0);
|
||||
},
|
||||
totalSize () {
|
||||
return this.database.tables.reduce((acc, curr) => acc + curr.size, 0);
|
||||
}
|
||||
},
|
||||
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)` };
|
||||
},
|
||||
setBreadcrumbs (payload) {
|
||||
if (this.breadcrumbs.schema === payload.schema && this.breadcrumbs.table === payload.table) return;
|
||||
this.changeBreadcrumbs(payload);
|
||||
},
|
||||
highlightWord (string) {
|
||||
if (this.searchTerm) {
|
||||
const regexp = new RegExp(`(${this.searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
|
||||
return string.replace(regexp, '<span class="text-primary">$1</span>');
|
||||
}
|
||||
else
|
||||
return string;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.workspace-explorebar-database{
|
||||
.database-name,
|
||||
a.table-name{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: .1rem;
|
||||
cursor: pointer;
|
||||
font-size: .7rem;
|
||||
.workspace-explorebar-database {
|
||||
.database-name {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: $bg-color-gray;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
> span{
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.database-name,
|
||||
.misc-name,
|
||||
a.table-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.1rem 1rem 0.1rem 0.1rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.7rem;
|
||||
|
||||
&:hover{
|
||||
color: $body-font-color;
|
||||
background: rgba($color: #FFF, $alpha: .05);
|
||||
border-radius: 2px;
|
||||
}
|
||||
> span {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.database-icon,
|
||||
.table-icon,
|
||||
.misc-icon {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.loading {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
|
||||
&::after {
|
||||
height: 0.6rem;
|
||||
width: 0.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-item{
|
||||
line-height: 1.2;
|
||||
}
|
||||
.misc-name {
|
||||
line-height: 1;
|
||||
padding: 0.1rem 1rem 0.1rem 0.1rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.database-tables{
|
||||
margin-left: 1.2rem;
|
||||
}
|
||||
}
|
||||
.database-name,
|
||||
.misc-name {
|
||||
&:hover {
|
||||
color: $body-font-color;
|
||||
background: $bg-color-light;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
a.table-name {
|
||||
&:hover {
|
||||
color: inherit;
|
||||
background: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
line-height: 1.2;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
color: $body-font-color;
|
||||
background: rgba($color: #fff, $alpha: 0.05);
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.database-tables {
|
||||
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;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
opacity: 0.2;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&::after {
|
||||
font-weight: 400;
|
||||
font-size: 0.5rem;
|
||||
}
|
||||
|
||||
.pie {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
161
src/renderer/components/WorkspaceExploreBarDatabaseContext.vue
Normal file
161
src/renderer/components/WorkspaceExploreBarDatabaseContext.vue
Normal file
@@ -0,0 +1,161 @@
|
||||
<template>
|
||||
<BaseContextMenu
|
||||
:context-event="contextEvent"
|
||||
@close-context="closeContext"
|
||||
>
|
||||
<div class="context-element">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-plus text-light pr-1" /> {{ $t('word.add') }}</span>
|
||||
<i class="mdi mdi-18px mdi-chevron-right text-light pl-1" />
|
||||
<div class="context-submenu">
|
||||
<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">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-database-edit text-light pr-1" /> {{ $t('word.edit') }}</span>
|
||||
</div>
|
||||
<div class="context-element" @click="showDeleteModal">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-database-remove text-light pr-1" /> {{ $t('word.delete') }}</span>
|
||||
</div>
|
||||
|
||||
<ConfirmModal
|
||||
v-if="isDeleteModal"
|
||||
@confirm="deleteDatabase"
|
||||
@hide="hideDeleteModal"
|
||||
>
|
||||
<template slot="header">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-database-remove mr-1" /> {{ $t('message.deleteDatabase') }}
|
||||
</div>
|
||||
</template>
|
||||
<div slot="body">
|
||||
<div class="mb-2">
|
||||
{{ $t('message.deleteCorfirm') }} "<b>{{ selectedDatabase }}</b>"?
|
||||
</div>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
<ModalEditDatabase
|
||||
v-if="isEditModal"
|
||||
:selected-database="selectedDatabase"
|
||||
@close="hideEditModal"
|
||||
/>
|
||||
</BaseContextMenu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import BaseContextMenu from '@/components/BaseContextMenu';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
import ModalEditDatabase from '@/components/ModalEditDatabase';
|
||||
import Database from '@/ipc-api/Database';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceExploreBarDatabaseContext',
|
||||
components: {
|
||||
BaseContextMenu,
|
||||
ConfirmModal,
|
||||
ModalEditDatabase
|
||||
},
|
||||
props: {
|
||||
contextEvent: MouseEvent,
|
||||
selectedDatabase: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isDeleteModal: false,
|
||||
isEditModal: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
|
||||
}),
|
||||
showCreateTableModal () {
|
||||
this.$emit('show-create-table-modal');
|
||||
},
|
||||
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;
|
||||
},
|
||||
hideDeleteModal () {
|
||||
this.isDeleteModal = false;
|
||||
},
|
||||
showEditModal () {
|
||||
this.isEditModal = true;
|
||||
},
|
||||
hideEditModal () {
|
||||
this.isEditModal = false;
|
||||
this.closeContext();
|
||||
},
|
||||
closeContext () {
|
||||
this.$emit('close-context');
|
||||
},
|
||||
async deleteDatabase () {
|
||||
try {
|
||||
const { status, response } = await Database.deleteDatabase({
|
||||
uid: this.selectedWorkspace,
|
||||
database: this.selectedDatabase
|
||||
});
|
||||
|
||||
if (status === 'success') {
|
||||
if (this.selectedDatabase === this.workspace.breadcrumbs.schema)
|
||||
this.changeBreadcrumbs({ schema: null });
|
||||
|
||||
this.closeContext();
|
||||
this.$emit('reload');
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.context-submenu {
|
||||
min-width: 150px !important;
|
||||
}
|
||||
</style>
|
||||
265
src/renderer/components/WorkspaceExploreBarMiscContext.vue
Normal file
265
src/renderer/components/WorkspaceExploreBarMiscContext.vue
Normal file
@@ -0,0 +1,265 @@
|
||||
<template>
|
||||
<BaseContextMenu
|
||||
:context-event="contextEvent"
|
||||
@close-context="closeContext"
|
||||
>
|
||||
<div
|
||||
v-if="['procedure', 'function'].includes(selectedMisc.type)"
|
||||
class="context-element"
|
||||
@click="runElementCheck"
|
||||
>
|
||||
<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>
|
||||
<ModalAskParameters
|
||||
v-if="isAskingParameters"
|
||||
:local-routine="localElement"
|
||||
@confirm="runElement"
|
||||
@close="hideAskParamsModal"
|
||||
/>
|
||||
</BaseContextMenu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import BaseContextMenu from '@/components/BaseContextMenu';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
import ModalAskParameters from '@/components/ModalAskParameters';
|
||||
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,
|
||||
ModalAskParameters
|
||||
},
|
||||
props: {
|
||||
contextEvent: MouseEvent,
|
||||
selectedMisc: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isDeleteModal: false,
|
||||
isRunModal: false,
|
||||
isAskingParameters: false,
|
||||
localElement: {}
|
||||
};
|
||||
},
|
||||
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',
|
||||
newTab: 'workspaces/newTab'
|
||||
}),
|
||||
showCreateTableModal () {
|
||||
this.$emit('show-create-table-modal');
|
||||
},
|
||||
showDeleteModal () {
|
||||
this.isDeleteModal = true;
|
||||
},
|
||||
hideDeleteModal () {
|
||||
this.isDeleteModal = false;
|
||||
},
|
||||
showAskParamsModal () {
|
||||
this.isAskingParameters = true;
|
||||
},
|
||||
hideAskParamsModal () {
|
||||
this.isAskingParameters = false;
|
||||
this.closeContext();
|
||||
},
|
||||
closeContext () {
|
||||
this.$emit('close-context');
|
||||
},
|
||||
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 });
|
||||
}
|
||||
},
|
||||
runElementCheck () {
|
||||
if (this.selectedMisc.type === 'procedure')
|
||||
this.runRoutineCheck();
|
||||
else if (this.selectedMisc.type === 'function')
|
||||
this.runFunctionCheck();
|
||||
},
|
||||
runElement (params) {
|
||||
if (this.selectedMisc.type === 'procedure')
|
||||
this.runRoutine(params);
|
||||
else if (this.selectedMisc.type === 'function')
|
||||
this.runFunction(params);
|
||||
},
|
||||
async runRoutineCheck () {
|
||||
const params = {
|
||||
uid: this.selectedWorkspace,
|
||||
schema: this.workspace.breadcrumbs.schema,
|
||||
routine: this.workspace.breadcrumbs.procedure
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Routines.getRoutineInformations(params);
|
||||
if (status === 'success')
|
||||
this.localElement = response;
|
||||
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
if (this.localElement.parameters.length)
|
||||
this.showAskParamsModal();
|
||||
else
|
||||
this.runRoutine();
|
||||
},
|
||||
runRoutine (params) {
|
||||
if (!params) params = [];
|
||||
|
||||
let sql;
|
||||
switch (this.workspace.client) { // TODO: move in a better place
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
case 'pg':
|
||||
sql = `CALL \`${this.localElement.name}\` (${params.join(',')})`;
|
||||
break;
|
||||
case 'mssql':
|
||||
sql = `EXEC ${this.localElement.name} ${params.join(',')}`;
|
||||
break;
|
||||
default:
|
||||
sql = `CALL \`${this.localElement.name}\` (${params.join(',')})`;
|
||||
}
|
||||
|
||||
this.newTab({ uid: this.workspace.uid, content: sql, autorun: true });
|
||||
this.closeContext();
|
||||
},
|
||||
async runFunctionCheck () {
|
||||
const params = {
|
||||
uid: this.selectedWorkspace,
|
||||
schema: this.workspace.breadcrumbs.schema,
|
||||
func: this.workspace.breadcrumbs.function
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Functions.getFunctionInformations(params);
|
||||
if (status === 'success')
|
||||
this.localElement = response;
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
if (this.localElement.parameters.length)
|
||||
this.showAskParamsModal();
|
||||
else
|
||||
this.runFunction();
|
||||
},
|
||||
runFunction (params) {
|
||||
if (!params) params = [];
|
||||
|
||||
let sql;
|
||||
switch (this.workspace.client) { // TODO: move in a better place
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
case 'pg':
|
||||
sql = `SELECT \`${this.localElement.name}\` (${params.join(',')})`;
|
||||
break;
|
||||
case 'mssql':
|
||||
sql = `SELECT ${this.localElement.name} ${params.join(',')}`;
|
||||
break;
|
||||
default:
|
||||
sql = `SELECT \`${this.localElement.name}\` (${params.join(',')})`;
|
||||
}
|
||||
|
||||
this.newTab({ uid: this.workspace.uid, content: sql, autorun: true });
|
||||
this.closeContext();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
163
src/renderer/components/WorkspaceExploreBarTableContext.vue
Normal file
163
src/renderer/components/WorkspaceExploreBarTableContext.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<BaseContextMenu
|
||||
:context-event="contextEvent"
|
||||
@close-context="closeContext"
|
||||
>
|
||||
<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">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-table-remove text-light pr-1" /> {{ $t('word.delete') }}</span>
|
||||
</div>
|
||||
|
||||
<ConfirmModal
|
||||
v-if="isEmptyModal"
|
||||
@confirm="emptyTable"
|
||||
@hide="hideEmptyModal"
|
||||
>
|
||||
<template slot="header">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-table-off mr-1" /> {{ $t('message.emptyTable') }}
|
||||
</div>
|
||||
</template>
|
||||
<div slot="body">
|
||||
<div class="mb-2">
|
||||
{{ $t('message.emptyCorfirm') }} "<b>{{ selectedTable.name }}</b>"?
|
||||
</div>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
<ConfirmModal
|
||||
v-if="isDeleteModal"
|
||||
@confirm="deleteTable"
|
||||
@hide="hideDeleteModal"
|
||||
>
|
||||
<template slot="header">
|
||||
<div class="d-flex">
|
||||
<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.name }}</b>"?
|
||||
</div>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</BaseContextMenu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
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',
|
||||
components: {
|
||||
BaseContextMenu,
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
contextEvent: MouseEvent,
|
||||
selectedTable: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isDeleteModal: false,
|
||||
isEmptyModal: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
|
||||
}),
|
||||
showCreateTableModal () {
|
||||
this.$emit('show-create-table-modal');
|
||||
},
|
||||
showDeleteModal () {
|
||||
this.isDeleteModal = true;
|
||||
},
|
||||
hideDeleteModal () {
|
||||
this.isDeleteModal = false;
|
||||
},
|
||||
showEmptyModal () {
|
||||
this.isEmptyModal = true;
|
||||
},
|
||||
hideEmptyModal () {
|
||||
this.isEmptyModal = false;
|
||||
},
|
||||
closeContext () {
|
||||
this.$emit('close-context');
|
||||
},
|
||||
async emptyTable () {
|
||||
try {
|
||||
const { status, response } = await Tables.truncateTable({
|
||||
uid: this.selectedWorkspace,
|
||||
table: this.selectedTable.name
|
||||
});
|
||||
|
||||
if (status === 'success') {
|
||||
if (this.selectedTable.name === this.workspace.breadcrumbs.table)
|
||||
this.changeBreadcrumbs({ table: null });
|
||||
|
||||
this.closeContext();
|
||||
this.$emit('reload');
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
},
|
||||
async deleteTable () {
|
||||
try {
|
||||
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.name === this.workspace.breadcrumbs.table || this.selectedTable.name === this.workspace.breadcrumbs.view)
|
||||
this.changeBreadcrumbs({ table: null, view: null });
|
||||
|
||||
this.closeContext();
|
||||
this.$emit('reload');
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
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>
|
||||
272
src/renderer/components/WorkspacePropsFunctionParamsModal.vue
Normal file
272
src/renderer/components/WorkspacePropsFunctionParamsModal.vue
Normal file
@@ -0,0 +1,272 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.confirm')"
|
||||
size="medium"
|
||||
@confirm="confirmParametersChange"
|
||||
@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._id"
|
||||
class="tile tile-centered c-hand mb-1 p-1"
|
||||
:class="{'selected-param': selectedParam === param._id}"
|
||||
@click="selectParameter($event, param._id)"
|
||||
>
|
||||
<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._id)"
|
||||
>
|
||||
<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 { uidGen } from 'common/libs/uidGen';
|
||||
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._id === 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: {
|
||||
confirmParametersChange () {
|
||||
this.$emit('parameters-update', this.parametersProxy);
|
||||
},
|
||||
selectParameter (event, uid) {
|
||||
if (this.selectedParam !== uid && !event.target.classList.contains('remove-field'))
|
||||
this.selectedParam = uid;
|
||||
},
|
||||
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, {
|
||||
_id: uidGen(),
|
||||
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 (uid) {
|
||||
this.parametersProxy = this.parametersProxy.filter(param => param._id !== uid);
|
||||
|
||||
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]._id : '';
|
||||
}
|
||||
}
|
||||
};
|
||||
</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>
|
||||
284
src/renderer/components/WorkspacePropsIndexesModal.vue
Normal file
284
src/renderer/components/WorkspacePropsIndexesModal.vue
Normal file
@@ -0,0 +1,284 @@
|
||||
<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-key mdi-rotate-45 mr-1" /> {{ $t('word.indexes') }} "{{ 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="addIndex">
|
||||
<span>{{ $t('word.add') }}</span>
|
||||
<i class="mdi mdi-24px mdi-key-plus ml-1" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark btn-sm d-flex ml-2 mr-0"
|
||||
: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="index in indexesProxy"
|
||||
:key="index._id"
|
||||
class="tile tile-centered c-hand mb-1 p-1"
|
||||
:class="{'selected-index': selectedIndexID === index._id}"
|
||||
@click="selectIndex($event, index._id)"
|
||||
>
|
||||
<div class="tile-icon">
|
||||
<div>
|
||||
<i class="mdi mdi-key mdi-24px column-key" :class="`key-${index.type}`" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile-content">
|
||||
<div class="tile-title">
|
||||
{{ index.name }}
|
||||
</div>
|
||||
<small class="tile-subtitle text-gray">{{ index.type }} · {{ index.fields.length }} {{ $tc('word.field', index.fields.length) }}</small>
|
||||
</div>
|
||||
<div class="tile-action">
|
||||
<button
|
||||
class="btn btn-link remove-field p-0 mr-2"
|
||||
:title="$t('word.delete')"
|
||||
@click.prevent="removeIndex(index._id)"
|
||||
>
|
||||
<i class="mdi mdi-close" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column col-7 pl-2 editor-col">
|
||||
<form
|
||||
v-if="selectedIndexObj"
|
||||
: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="selectedIndexObj.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="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 col-3">
|
||||
{{ $tc('word.field', fields.length) }}
|
||||
</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="selectedIndexObj.fields.some(f => f === field.name)">
|
||||
<i class="form-icon" /> {{ field.name }}
|
||||
</label>
|
||||
</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>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsIndexesModal',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
localIndexes: Array,
|
||||
table: String,
|
||||
fields: Array,
|
||||
workspace: Object,
|
||||
indexTypes: Array
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
indexesProxy: [],
|
||||
isOptionsChanging: false,
|
||||
selectedIndexID: '',
|
||||
modalInnerHeight: 400
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selectedIndexObj () {
|
||||
return this.indexesProxy.find(index => index._id === this.selectedIndexID);
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.localIndexes) !== JSON.stringify(this.indexesProxy);
|
||||
},
|
||||
hasPrimary () {
|
||||
return this.indexesProxy.some(index => index.type === 'PRIMARY');
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.indexesProxy = JSON.parse(JSON.stringify(this.localIndexes));
|
||||
|
||||
if (this.indexesProxy.length)
|
||||
this.resetSelectedID();
|
||||
|
||||
this.getModalInnerHeight();
|
||||
window.addEventListener('resize', this.getModalInnerHeight);
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('resize', this.getModalInnerHeight);
|
||||
},
|
||||
methods: {
|
||||
confirmIndexesChange () {
|
||||
this.$emit('indexes-update', this.indexesProxy);
|
||||
},
|
||||
selectIndex (event, id) {
|
||||
if (this.selectedIndexID !== id && !event.target.classList.contains('remove-field'))
|
||||
this.selectedIndexID = id;
|
||||
},
|
||||
getModalInnerHeight () {
|
||||
const modalBody = document.querySelector('.modal-body');
|
||||
if (modalBody)
|
||||
this.modalInnerHeight = modalBody.clientHeight - (parseFloat(getComputedStyle(modalBody).paddingTop) + parseFloat(getComputedStyle(modalBody).paddingBottom));
|
||||
},
|
||||
addIndex () {
|
||||
this.indexesProxy = [...this.indexesProxy, {
|
||||
_id: uidGen(),
|
||||
name: 'NEW_INDEX',
|
||||
fields: [],
|
||||
type: 'INDEX',
|
||||
comment: '',
|
||||
indexType: 'BTREE',
|
||||
indexComment: '',
|
||||
cardinality: 0
|
||||
}];
|
||||
|
||||
if (this.indexesProxy.length === 1)
|
||||
this.resetSelectedID();
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.indexesPanel.scrollTop = this.$refs.indexesPanel.scrollHeight + 60;
|
||||
}, 20);
|
||||
},
|
||||
removeIndex (id) {
|
||||
this.indexesProxy = this.indexesProxy.filter(index => index._id !== id);
|
||||
|
||||
if (this.selectedIndexID === id && this.indexesProxy.length)
|
||||
this.resetSelectedID();
|
||||
},
|
||||
clearChanges () {
|
||||
this.indexesProxy = JSON.parse(JSON.stringify(this.localIndexes));
|
||||
if (!this.indexesProxy.some(index => index._id === this.selectedIndexID))
|
||||
this.resetSelectedID();
|
||||
},
|
||||
toggleField (field) {
|
||||
this.indexesProxy = this.indexesProxy.map(index => {
|
||||
if (index._id === this.selectedIndexID) {
|
||||
if (index.fields.includes(field))
|
||||
index.fields = index.fields.filter(f => f !== field);
|
||||
else
|
||||
index.fields.push(field);
|
||||
}
|
||||
return index;
|
||||
});
|
||||
},
|
||||
resetSelectedID () {
|
||||
this.selectedIndexID = this.indexesProxy.length ? this.indexesProxy[0]._id : '';
|
||||
}
|
||||
}
|
||||
};
|
||||
</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-index {
|
||||
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>
|
||||
130
src/renderer/components/WorkspacePropsOptionsModal.vue
Normal file
130
src/renderer/components/WorkspacePropsOptionsModal.vue
Normal file
@@ -0,0 +1,130 @@
|
||||
<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') }} "{{ table }}"
|
||||
</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.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('word.autoIncrement') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
v-model="optionsProxy.autoIncrement"
|
||||
class="form-input"
|
||||
type="number"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.collation') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="optionsProxy.collation" class="form-select">
|
||||
<option
|
||||
v-for="collation in workspace.collations"
|
||||
:key="collation.id"
|
||||
:value="collation.collation"
|
||||
>
|
||||
{{ collation.collation }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.engine') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="optionsProxy.engine" class="form-select">
|
||||
<option
|
||||
v-for="engine in workspace.engines"
|
||||
:key="engine.name"
|
||||
:value="engine.name"
|
||||
>
|
||||
{{ engine.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsOptionsModal',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
localOptions: Object,
|
||||
table: String,
|
||||
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>
|
||||
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>
|
||||
303
src/renderer/components/WorkspacePropsRoutineParamsModal.vue
Normal file
303
src/renderer/components/WorkspacePropsRoutineParamsModal.vue
Normal file
@@ -0,0 +1,303 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.confirm')"
|
||||
size="medium"
|
||||
@confirm="confirmParametersChange"
|
||||
@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._id"
|
||||
class="tile tile-centered c-hand mb-1 p-1"
|
||||
:class="{'selected-param': selectedParam === param._id}"
|
||||
@click="selectParameter($event, param._id)"
|
||||
>
|
||||
<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._id)"
|
||||
>
|
||||
<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 { uidGen } from 'common/libs/uidGen';
|
||||
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._id === 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: {
|
||||
confirmParametersChange () {
|
||||
this.$emit('parameters-update', this.parametersProxy);
|
||||
},
|
||||
selectParameter (event, uid) {
|
||||
if (this.selectedParam !== uid && !event.target.classList.contains('remove-field'))
|
||||
this.selectedParam = uid;
|
||||
},
|
||||
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, {
|
||||
_id: uidGen(),
|
||||
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 (uid) {
|
||||
this.parametersProxy = this.parametersProxy.filter(param => param._id !== uid);
|
||||
|
||||
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]._id : '';
|
||||
}
|
||||
}
|
||||
};
|
||||
</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>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user