Compare commits
63 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 |
@@ -21,7 +21,7 @@
|
||||
],
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"windows"
|
||||
"unix"
|
||||
],
|
||||
"brace-style": [
|
||||
"error",
|
||||
|
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
@@ -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.
|
62
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
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
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
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
|
77
CHANGELOG.md
@@ -2,31 +2,78 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [0.0.5](https://github.com/EStarium/antares/compare/v0.0.4...v0.0.5) (2020-08-17)
|
||||
### [0.0.7](https://github.com/EStarium/antares/compare/v0.0.6...v0.0.7) (2020-10-03)
|
||||
|
||||
|
||||
### 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))
|
||||
* database creation ([3d0a83f](https://github.com/EStarium/antares/commit/3d0a83f2cf68c4dd412fd7679c39d63f081b7c19))
|
||||
* databases 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))
|
||||
* Update italian translation ([fe3d741](https://github.com/EStarium/antares/commit/fe3d7416013c44a4974471ab59b7c9a98afb7255))
|
||||
|
||||
|
||||
### 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 table 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 selected a different database ([6d0724d](https://github.com/EStarium/antares/commit/6d0724dc90cdebb10e0342d2c472bdd07aa345f8))
|
||||
* 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.4](https://github.com/EStarium/antares/compare/v0.0.3-alpha...v0.0.4) (2020-08-06)
|
||||
### [0.0.6](https://github.com/EStarium/antares/compare/v0.0.5...v0.0.6) (2020-09-03)
|
||||
|
||||
|
||||
### 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))
|
||||
* 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))
|
||||
|
28
README.md
@@ -9,13 +9,17 @@
|
||||
Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers.
|
||||
My target is to support as many databases as possible, and all major operating systems, including the ARM versions.
|
||||
|
||||
**At the moment this application is in a development state, it lacks many features, and is''t ready as a main SQL client**. However i'm actively working on it, hoping to provide all essential features as soon as possible.
|
||||
**At the moment this application is an alpha, it lacks many features, and isn't ready as a main SQL client**. However i'm actively working on it, hoping to provide all essential features 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/EStarium/antares/releases), and stay tuned for updates. At moment i'm testing only on Windows.
|
||||
🔗 If you are curious to try this early state of Antares you can download and install the [latest release](https://github.com/EStarium/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
|
||||
## Philosophy
|
||||
|
||||
Why am I developing an SQL client when there are a lot of thom on the market?-->
|
||||
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, 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
|
||||
|
||||
@@ -26,8 +30,9 @@ Why am I developing an SQL client when there are a lot of thom on the market?-->
|
||||
This is a roadmap with major features will come in near future.
|
||||
|
||||
- Improvements of query editor area.
|
||||
- Multiple query tabs.
|
||||
- Database management (add/edit/delete).
|
||||
- Tables management (add/edit/delete).
|
||||
- Users management (add/edit/delete).
|
||||
- Stored procedures, views, schedulers and trigger support.
|
||||
- Database tools.
|
||||
- Context menu shortcuts.
|
||||
@@ -36,6 +41,7 @@ This is a roadmap with major features will come in near future.
|
||||
- Query logs console.
|
||||
- Fake data filler.
|
||||
- Import/export and migration.
|
||||
- SSH tunnel.
|
||||
- Themes.
|
||||
|
||||
## Currently supported
|
||||
@@ -51,10 +57,20 @@ This is a roadmap with major features will come in near future.
|
||||
|
||||
### Operating Systems
|
||||
|
||||
#### • x86
|
||||
|
||||
- [x] Windows
|
||||
- [x] Linux
|
||||
- [x] MacOS (needs tests)
|
||||
|
||||
#### • ARM
|
||||
|
||||
- [ ] Windows
|
||||
- [ ] Linux
|
||||
- [ ] MacOS
|
||||
|
||||
## Translations
|
||||
|
||||
[Giuseppe Gigliotti](https://github.com/ReverbOD) / [Italian Translation](https://github.com/EStarium/antares/pull/20)
|
||||
[Giuseppe Gigliotti](https://github.com/ReverbOD) / [Italian Translation](https://github.com/EStarium/antares/pull/20)
|
||||
[Mohd-PH](https://github.com/Mohd-PH) / [Arabic Translation](https://github.com/EStarium/antares/pull/29)
|
||||
[hongkfui](https://github.com/hongkfui) / [Spanish Translation](https://github.com/EStarium/antares/pull/32)
|
||||
|
BIN
build/icon.icns
Normal file
BIN
build/icon.ico
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
build/icon.png
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 260 KiB |
47
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "antares",
|
||||
"productName": "Antares",
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.7",
|
||||
"description": "A cross-platform easy to use SQL client.",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/EStarium/antares.git",
|
||||
@@ -11,7 +11,9 @@
|
||||
"build": "cross-env NODE_ENV=production npm run compile && electron-builder",
|
||||
"release": "standard-version",
|
||||
"release:pre": "npm run release -- --prerelease alpha",
|
||||
"lint": "eslint ."
|
||||
"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": {
|
||||
@@ -40,54 +42,53 @@
|
||||
}
|
||||
},
|
||||
"electronWebpack": {
|
||||
"whiteListedModules": [
|
||||
"codemirror"
|
||||
],
|
||||
"renderer": {
|
||||
"webpackConfig": "webpack.config.js"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdi/font": "^5.5.55",
|
||||
"codemirror": "^5.56.0",
|
||||
"electron-log": "^4.2.2",
|
||||
"electron-updater": "^4.3.4",
|
||||
"lodash": "^4.17.19",
|
||||
"moment": "^2.27.0",
|
||||
"mssql": "^6.2.1",
|
||||
"@mdi/font": "^5.6.55",
|
||||
"electron-log": "^4.2.4",
|
||||
"electron-updater": "^4.3.5",
|
||||
"lodash": "^4.17.20",
|
||||
"moment": "^2.29.0",
|
||||
"monaco-editor": "^0.20.0",
|
||||
"mssql": "^6.2.2",
|
||||
"mysql": "^2.18.1",
|
||||
"pg": "^8.3.0",
|
||||
"pg": "^8.3.3",
|
||||
"source-map-support": "^0.5.16",
|
||||
"spectre.css": "^0.5.9",
|
||||
"vue-click-outside": "^1.1.0",
|
||||
"vue-i18n": "^8.20.0",
|
||||
"vue-i18n": "^8.21.0",
|
||||
"vue-the-mask": "^0.11.1",
|
||||
"vuedraggable": "^2.24.0",
|
||||
"vuedraggable": "^2.24.1",
|
||||
"vuex": "^3.5.1",
|
||||
"vuex-persist": "^2.2.0"
|
||||
"vuex-persist": "^3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^10.1.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"electron": "^9.1.2",
|
||||
"electron-builder": "^22.8.0",
|
||||
"electron": "^10.1.0",
|
||||
"electron-builder": "^22.8.1",
|
||||
"electron-devtools-installer": "^3.1.1",
|
||||
"electron-webpack": "^2.8.2",
|
||||
"electron-webpack-vue": "^2.4.0",
|
||||
"eslint": "^7.6.0",
|
||||
"eslint": "^7.8.1",
|
||||
"eslint-config-standard": "^14.1.1",
|
||||
"eslint-plugin-import": "^2.22.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.0.1",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"monaco-editor-webpack-plugin": "^1.9.1",
|
||||
"node-sass": "^4.14.1",
|
||||
"sass-loader": "^9.0.3",
|
||||
"sass-loader": "^10.0.2",
|
||||
"standard-version": "^9.0.0",
|
||||
"stylelint": "^13.6.1",
|
||||
"stylelint": "^13.7.0",
|
||||
"stylelint-config-standard": "^20.0.0",
|
||||
"stylelint-scss": "^3.18.0",
|
||||
"vue": "^2.6.11",
|
||||
"webpack": "^4.44.1"
|
||||
"vue": "^2.6.12",
|
||||
"vue-template-compiler": "^2.6.12",
|
||||
"webpack": "^4.44.2"
|
||||
}
|
||||
}
|
||||
|
@@ -6,6 +6,8 @@ import { format as formatUrl } from 'url';
|
||||
import ipcHandlers from './ipc-handlers';
|
||||
|
||||
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)
|
||||
@@ -67,23 +69,27 @@ async function createMainWindow () {
|
||||
return window;
|
||||
};
|
||||
|
||||
// Initialize ipcHandlers
|
||||
ipcHandlers();
|
||||
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();
|
||||
});
|
||||
// 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)
|
||||
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();
|
||||
});
|
||||
|
||||
// create main BrowserWindow when electron is ready
|
||||
app.on('ready', () => {
|
||||
mainWindow = createMainWindow();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { app, ipcMain } from 'electron';
|
||||
|
||||
export default () => {
|
||||
ipcMain.on('closeApp', () => {
|
||||
ipcMain.on('close-app', () => {
|
||||
app.exit();
|
||||
});
|
||||
};
|
||||
|
@@ -1,12 +1,10 @@
|
||||
|
||||
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 connection = ClientsFactory.getConnection({
|
||||
client: conn.client,
|
||||
params: {
|
||||
host: conn.host,
|
||||
@@ -16,10 +14,11 @@ export default (connections) => {
|
||||
}
|
||||
});
|
||||
|
||||
await Connection.connect();
|
||||
await connection.connect();
|
||||
|
||||
try {
|
||||
await InformationSchema.testConnection(Connection);
|
||||
await connection.select('1+1').run();
|
||||
connection.destroy();
|
||||
|
||||
return { status: 'success' };
|
||||
}
|
||||
@@ -28,12 +27,12 @@ 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 connection = ClientsFactory.getConnection({
|
||||
client: conn.client,
|
||||
params: {
|
||||
host: conn.host,
|
||||
@@ -41,14 +40,16 @@ export default (connections) => {
|
||||
user: conn.user,
|
||||
password: conn.password
|
||||
},
|
||||
poolSize: 3
|
||||
poolSize: 1
|
||||
});
|
||||
|
||||
try {
|
||||
await Connection.connect();
|
||||
await connection.connect();
|
||||
|
||||
const structure = await connection.getStructure();
|
||||
|
||||
connections[conn.uid] = connection;
|
||||
|
||||
const { rows: structure } = await InformationSchema.getStructure(Connection);
|
||||
connections[conn.uid] = Connection;
|
||||
return { status: 'success', response: structure };
|
||||
}
|
||||
catch (err) {
|
||||
@@ -60,25 +61,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() };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
110
src/main/ipc-handlers/database.js
Normal file
@@ -0,0 +1,110 @@
|
||||
|
||||
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) => {
|
||||
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, uid) => {
|
||||
try {
|
||||
const structure = await connections[uid].getStructure();
|
||||
|
||||
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('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, true);
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
@@ -2,12 +2,14 @@ import connection from './connection';
|
||||
import tables from './tables';
|
||||
import updates from './updates';
|
||||
import application from './application';
|
||||
import database from './database';
|
||||
|
||||
const connections = {};
|
||||
|
||||
export default () => {
|
||||
connection(connections);
|
||||
tables(connections);
|
||||
database(connections);
|
||||
updates();
|
||||
application();
|
||||
};
|
||||
|
@@ -1,13 +1,39 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import InformationSchema from '../models/InformationSchema';
|
||||
import Tables from '../models/Tables';
|
||||
import { sqlEscaper } from 'common/libs/sqlEscaper';
|
||||
import { TEXT, LONG_TEXT, NUMBER, BLOB } from 'common/fieldTypes';
|
||||
import fs from 'fs';
|
||||
|
||||
// TODO: remap objects based on client
|
||||
|
||||
export default (connections) => {
|
||||
ipcMain.handle('getTableColumns', async (event, { uid, schema, table }) => {
|
||||
ipcMain.handle('get-table-columns', async (event, { uid, schema, table }) => {
|
||||
try {
|
||||
const result = await InformationSchema.getTableColumns(connections[uid], schema, table);// TODO: uniform column properties
|
||||
const { rows } = await connections[uid]
|
||||
.select('*')
|
||||
.schema('information_schema')
|
||||
.from('COLUMNS')
|
||||
.where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'` })
|
||||
.orderBy({ ORDINAL_POSITION: 'ASC' })
|
||||
.run();
|
||||
|
||||
const result = rows.map(field => {
|
||||
return {
|
||||
name: field.COLUMN_NAME,
|
||||
key: field.COLUMN_KEY.toLowerCase(),
|
||||
type: field.DATA_TYPE,
|
||||
schema: field.TABLE_SCHEMA,
|
||||
table: field.TABLE_NAME,
|
||||
numPrecision: field.NUMERIC_PRECISION,
|
||||
datePrecision: field.DATETIME_PRECISION,
|
||||
charLength: field.CHARACTER_MAXIMUM_LENGTH,
|
||||
isNullable: field.IS_NULLABLE,
|
||||
default: field.COLUMN_DEFAULT,
|
||||
charset: field.CHARACTER_SET_NAME,
|
||||
collation: field.COLLATION_NAME,
|
||||
autoIncrement: field.EXTRA.includes('auto_increment'),
|
||||
comment: field.COLUMN_COMMENT
|
||||
};
|
||||
});
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
@@ -15,9 +41,15 @@ export default (connections) => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('getTableData', async (event, { uid, schema, table }) => {
|
||||
ipcMain.handle('get-table-data', async (event, { uid, schema, table }) => {
|
||||
try {
|
||||
const result = await Tables.getTableData(connections[uid], schema, table);
|
||||
const result = await connections[uid]
|
||||
.select('*')
|
||||
.schema(schema)
|
||||
.from(table)
|
||||
.limit(1000)
|
||||
.run();
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
@@ -27,7 +59,27 @@ export default (connections) => {
|
||||
|
||||
ipcMain.handle('get-key-usage', async (event, { uid, schema, table }) => {
|
||||
try {
|
||||
const result = await InformationSchema.getKeyUsage(connections[uid], schema, table);
|
||||
const { rows } = await connections[uid]
|
||||
.select('*')
|
||||
.schema('information_schema')
|
||||
.from('KEY_COLUMN_USAGE')
|
||||
.where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'`, REFERENCED_TABLE_NAME: 'IS NOT NULL' })
|
||||
.run();
|
||||
|
||||
const result = rows.map(field => {
|
||||
return {
|
||||
schema: field.TABLE_SCHEMA,
|
||||
table: field.TABLE_NAME,
|
||||
column: field.COLUMN_NAME,
|
||||
position: field.ORDINAL_POSITION,
|
||||
constraintPosition: field.POSITION_IN_UNIQUE_CONSTRAINT,
|
||||
constraintName: field.CONSTRAINT_NAME,
|
||||
refSchema: field.REFERENCED_TABLE_SCHEMA,
|
||||
refTable: field.REFERENCED_TABLE_NAME,
|
||||
refColumn: field.REFERENCED_COLUMN_NAME
|
||||
};
|
||||
});
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
@@ -35,9 +87,50 @@ export default (connections) => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('updateTableCell', async (event, params) => {
|
||||
ipcMain.handle('update-table-cell', async (event, params) => {
|
||||
try {
|
||||
const result = await Tables.updateTableCell(connections[params.uid], params);
|
||||
let escapedParam;
|
||||
let reload = false;
|
||||
const id = typeof params.id === 'number' ? params.id : `"${params.id}"`;
|
||||
|
||||
if (NUMBER.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
|
||||
escapedParam = `"${sqlEscaper(params.content)}"`;
|
||||
|
||||
await connections[params.uid]
|
||||
.update({ [params.field]: `= ${escapedParam}` })
|
||||
.schema(params.schema)
|
||||
.from(params.table)
|
||||
.where({ [params.primary]: `= ${id}` })
|
||||
.run();
|
||||
|
||||
return { status: 'success', response: { reload } };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('delete-table-rows', async (event, params) => {
|
||||
try {
|
||||
const result = await connections[params.uid]
|
||||
.schema(params.schema)
|
||||
.delete(params.table)
|
||||
.where({ [params.primary]: `IN (${params.rows.join(',')})` })
|
||||
.run();
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
@@ -45,19 +138,41 @@ export default (connections) => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('deleteTableRows', async (event, params) => {
|
||||
ipcMain.handle('insert-table-rows', async (event, params) => {
|
||||
try {
|
||||
const result = await Tables.deleteTableRows(connections[params.uid], params);
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
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.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;
|
||||
}
|
||||
|
||||
for (let i = 0; i < params.repeat; i++) {
|
||||
await connections[params.uid]
|
||||
.schema(params.schema)
|
||||
.into(params.table)
|
||||
.insert(insertObj)
|
||||
.run();
|
||||
}
|
||||
|
||||
ipcMain.handle('insertTableRows', async (event, params) => {
|
||||
try {
|
||||
await Tables.insertTableRows(connections[params.uid], params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
@@ -67,7 +182,17 @@ export default (connections) => {
|
||||
|
||||
ipcMain.handle('get-foreign-list', async (event, params) => {
|
||||
try {
|
||||
const results = await Tables.getForeignList(connections[params.uid], params);
|
||||
const query = connections[params.uid]
|
||||
.select(`${params.column} AS foreignColumn`)
|
||||
.schema(params.schema)
|
||||
.from(params.table)
|
||||
.orderBy('foreignColumn ASC');
|
||||
|
||||
if (params.description)
|
||||
query.select(`LEFT(${params.description}, 20) AS foreignDescription`);
|
||||
|
||||
const results = await query.run();
|
||||
|
||||
return { status: 'success', response: results };
|
||||
}
|
||||
catch (err) {
|
||||
|
@@ -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');
|
||||
|
@@ -1,323 +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._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 AntaresConnector
|
||||
*/
|
||||
_resetQuery () {
|
||||
this._query = Object.assign({}, this._queryDefaults);
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof AntaresConnector
|
||||
*/
|
||||
async connect () {
|
||||
switch (this._client) {
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
if (!this._poolSize)
|
||||
this._connection = mysql.createConnection(this._params);
|
||||
else
|
||||
this._connection = mysql.createPool({ ...this._params, connectionLimit: this._poolSize });
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String | Array} args field = value
|
||||
* @returns
|
||||
* @memberof AntaresConnector
|
||||
*/
|
||||
update (...args) {
|
||||
this._query.update = [...this._query.update, ...args];
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} obj field: value
|
||||
* @returns
|
||||
* @memberof AntaresConnector
|
||||
*/
|
||||
insert (obj) {
|
||||
this._query.insert = { ...this._query.insert, ...obj };
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} SQL string
|
||||
* @memberof AntaresConnector
|
||||
*/
|
||||
getSQL () {
|
||||
// SELECT
|
||||
const selectArray = this._query.select.reduce(this._reducer, []);
|
||||
let selectRaw = '';
|
||||
if (selectArray.length) {
|
||||
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 = '';
|
||||
if (!this._query.update.length && !Object.keys(this._query.insert).length && !!this._query.from)
|
||||
fromRaw = 'FROM';
|
||||
else if (Object.keys(this._query.insert).length)
|
||||
fromRaw = 'INTO';
|
||||
|
||||
switch (this._client) {
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
fromRaw += this._query.from ? ` ${this._query.schema ? `\`${this._query.schema}\`.` : ''}\`${this._query.from}\` ` : '';
|
||||
break;
|
||||
case 'mssql':
|
||||
fromRaw += this._query.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 updateArray = this._query.update.reduce(this._reducer, []);
|
||||
const updateRaw = updateArray.length ? `SET ${updateArray.join(', ')} ` : '';
|
||||
|
||||
let insertRaw = '';
|
||||
if (Object.keys(this._query.insert).length) {
|
||||
const fieldsList = [];
|
||||
const valueList = [];
|
||||
const fields = this._query.insert;
|
||||
|
||||
for (const key in fields) {
|
||||
if (fields[key] === null) continue;
|
||||
fieldsList.push(key);
|
||||
valueList.push(fields[key]);
|
||||
}
|
||||
|
||||
insertRaw = `(${fieldsList.join(', ')}) VALUES (${valueList.join(', ')}) `;
|
||||
}
|
||||
|
||||
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}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${insertRaw}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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') this._logger(sql);// TODO: replace BLOB content with a placeholder
|
||||
|
||||
switch (this._client) { // TODO: uniform fields with every client type, needed table name and fields array
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
141
src/main/libs/AntaresCore.js
Normal file
@@ -0,0 +1,141 @@
|
||||
'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 {Object} obj field: value
|
||||
* @returns
|
||||
* @memberof AntaresCore
|
||||
*/
|
||||
insert (obj) {
|
||||
this._query.insert = { ...this._query.insert, ...obj };
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise}
|
||||
* @memberof AntaresCore
|
||||
*/
|
||||
async run () {
|
||||
const rawQuery = this.getSQL();
|
||||
this._resetQuery();
|
||||
return this.raw(rawQuery);
|
||||
}
|
||||
}
|
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}`);
|
||||
}
|
||||
}
|
||||
}
|
210
src/main/libs/clients/MySQLClient.js
Normal file
@@ -0,0 +1,210 @@
|
||||
'use strict';
|
||||
import mysql from 'mysql';
|
||||
import { AntaresCore } from '../AntaresCore';
|
||||
|
||||
export class MySQLClient extends AntaresCore {
|
||||
/**
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async connect () {
|
||||
if (!this._poolSize)
|
||||
this._connection = mysql.createConnection(this._params);
|
||||
else
|
||||
this._connection = mysql.createPool({ ...this._params, connectionLimit: this._poolSize });
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
destroy () {
|
||||
this._connection.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an USE query
|
||||
*
|
||||
* @param {String} schema
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
use (schema) {
|
||||
return this.raw(`USE \`${schema}\``);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Array.<Object>} databases scructure
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async getStructure () {
|
||||
const { rows: databases } = await this.raw('SHOW DATABASES');
|
||||
// TODO: SHOW TABLE STATUS FROM `{DATABASE_NAME}`;
|
||||
|
||||
const { rows: tables } = await this
|
||||
.select('*')
|
||||
.schema('information_schema')
|
||||
.from('TABLES')
|
||||
.orderBy({ TABLE_SCHEMA: 'ASC', TABLE_NAME: 'ASC' })
|
||||
.run();
|
||||
|
||||
const { rows: functions } = await this.raw('SHOW FUNCTION STATUS');
|
||||
const { rows: procedures } = await this.raw('SHOW PROCEDURE STATUS');
|
||||
const { rows: schedulers } = await this.raw('SELECT *, EVENT_SCHEMA AS `Db`, EVENT_NAME AS `Name` FROM information_schema.`EVENTS`');
|
||||
|
||||
const triggersArr = [];
|
||||
for (const db of databases) {
|
||||
let { rows: triggers } = await this.raw(`SHOW TRIGGERS FROM \`${db.Database}\``);
|
||||
if (triggers.length) {
|
||||
triggers = triggers.map(trigger => {
|
||||
trigger.Db = db.Database;
|
||||
return trigger;
|
||||
});
|
||||
triggersArr.push(...triggers);
|
||||
}
|
||||
}
|
||||
|
||||
return databases.map(db => { // TODO: remap all objects,
|
||||
return {
|
||||
name: db.Database,
|
||||
tables: tables.filter(table => table.TABLE_SCHEMA === db.Database),
|
||||
functions: functions.filter(func => func.Db === db.Database),
|
||||
procedures: procedures.filter(procedure => procedure.Db === db.Database),
|
||||
triggers: triggersArr.filter(trigger => trigger.Db === db.Database),
|
||||
schedulers: schedulers.filter(scheduler => scheduler.Db === db.Database)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* SHOW COLLATION
|
||||
*
|
||||
* @returns {Array.<Object>} collations list
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async getCollations () {
|
||||
const results = await this.raw('SHOW COLLATION');
|
||||
|
||||
return results.rows.map(row => {
|
||||
return {
|
||||
charset: row.Charset,
|
||||
collation: row.Collation,
|
||||
compiled: row.Compiled.includes('Yes'),
|
||||
default: row.Default.includes('Yes'),
|
||||
id: row.Id,
|
||||
sortLen: row.Sortlen
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* SHOW VARIABLES
|
||||
*
|
||||
* @returns {Array.<Object>} variables list
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async getVariables () {
|
||||
const sql = 'SHOW VARIABLES';
|
||||
const results = await this.raw(sql);
|
||||
|
||||
return results.rows.map(row => {
|
||||
return {
|
||||
name: row.Variable_name,
|
||||
value: row.Value
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {String} SQL string
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
getSQL () {
|
||||
// SELECT
|
||||
const selectArray = this._query.select.reduce(this._reducer, []);
|
||||
let selectRaw = '';
|
||||
|
||||
if (selectArray.length)
|
||||
selectRaw = selectArray.length ? `SELECT ${selectArray.join(', ')} ` : 'SELECT * ';
|
||||
|
||||
// FROM
|
||||
let fromRaw = '';
|
||||
|
||||
if (!this._query.update.length && !Object.keys(this._query.insert).length && !!this._query.from)
|
||||
fromRaw = 'FROM';
|
||||
else if (Object.keys(this._query.insert).length)
|
||||
fromRaw = 'INTO';
|
||||
|
||||
fromRaw += this._query.from ? ` ${this._query.schema ? `\`${this._query.schema}\`.` : ''}\`${this._query.from}\` ` : '';
|
||||
|
||||
// WHERE
|
||||
const whereArray = this._query.where.reduce(this._reducer, []);
|
||||
const whereRaw = whereArray.length ? `WHERE ${whereArray.join(' AND ')} ` : '';
|
||||
|
||||
// UPDATE
|
||||
const updateArray = this._query.update.reduce(this._reducer, []);
|
||||
const updateRaw = updateArray.length ? `SET ${updateArray.join(', ')} ` : '';
|
||||
|
||||
// INSERT
|
||||
let insertRaw = '';
|
||||
|
||||
if (Object.keys(this._query.insert).length) {
|
||||
const fieldsList = [];
|
||||
const valueList = [];
|
||||
const fields = this._query.insert;
|
||||
|
||||
for (const key in fields) {
|
||||
if (fields[key] === null) continue;
|
||||
fieldsList.push(key);
|
||||
valueList.push(fields[key]);
|
||||
}
|
||||
|
||||
insertRaw = `(${fieldsList.join(', ')}) VALUES (${valueList.join(', ')}) `;
|
||||
}
|
||||
|
||||
// GROUP BY
|
||||
const groupByArray = this._query.groupBy.reduce(this._reducer, []);
|
||||
const groupByRaw = groupByArray.length ? `GROUP BY ${groupByArray.join(', ')} ` : '';
|
||||
|
||||
// ORDER BY
|
||||
const orderByArray = this._query.orderBy.reduce(this._reducer, []);
|
||||
const orderByRaw = orderByArray.length ? `ORDER BY ${orderByArray.join(', ')} ` : '';
|
||||
|
||||
// LIMIT
|
||||
const limitRaw = this._query.limit.length ? `LIMIT ${this._query.limit.join(', ')} ` : '';
|
||||
|
||||
return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${insertRaw}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} sql raw SQL query
|
||||
* @param {boolean} [nest]
|
||||
* @returns {Promise}
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async raw (sql, nest) {
|
||||
const nestTables = nest ? '.' : false;
|
||||
const resultsArr = [];
|
||||
const queries = sql.split(';');
|
||||
|
||||
if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder
|
||||
|
||||
for (const query of queries) {
|
||||
if (!query) continue;
|
||||
|
||||
const { rows, report, fields } = await new Promise((resolve, reject) => {
|
||||
this._connection.query({ sql: query, nestTables }, (err, response, fields) => {
|
||||
if (err)
|
||||
reject(err);
|
||||
else {
|
||||
resolve({
|
||||
rows: Array.isArray(response) ? response : false,
|
||||
report: !Array.isArray(response) ? response : false,
|
||||
fields
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
resultsArr.push({ rows, report, fields });
|
||||
}
|
||||
|
||||
return resultsArr.length === 1 ? resultsArr[0] : resultsArr;
|
||||
}
|
||||
}
|
@@ -1,14 +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);
|
||||
}
|
||||
}
|
@@ -1,64 +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 async getTableColumns (connection, schema, table) {
|
||||
const { rows } = await connection
|
||||
.select('*')
|
||||
.schema('information_schema')
|
||||
.from('COLUMNS')
|
||||
.where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'` })
|
||||
.orderBy({ ORDINAL_POSITION: 'ASC' })
|
||||
.run();
|
||||
|
||||
return rows.map(field => {
|
||||
return {
|
||||
name: field.COLUMN_NAME,
|
||||
key: field.COLUMN_KEY.toLowerCase(),
|
||||
type: field.DATA_TYPE,
|
||||
numPrecision: field.NUMERIC_PRECISION,
|
||||
datePrecision: field.DATETIME_PRECISION,
|
||||
charLength: field.CHARACTER_MAXIMUM_LENGTH,
|
||||
isNullable: field.IS_NULLABLE,
|
||||
default: field.COLUMN_DEFAULT,
|
||||
charset: field.CHARACTER_SET_NAME,
|
||||
collation: field.COLLATION_NAME,
|
||||
autoIncrement: field.EXTRA.includes('auto_increment')
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
static async getKeyUsage (connection, schema, table) {
|
||||
const { rows } = await connection
|
||||
.select('*')
|
||||
.schema('information_schema')
|
||||
.from('KEY_COLUMN_USAGE')
|
||||
.where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'`, REFERENCED_TABLE_NAME: 'IS NOT NULL' })
|
||||
.run();
|
||||
|
||||
return rows.map(field => {
|
||||
return {
|
||||
schema: field.TABLE_SCHEMA,
|
||||
table: field.TABLE_NAME,
|
||||
column: field.COLUMN_NAME,
|
||||
position: field.ORDINAL_POSITION,
|
||||
constraintPosition: field.POSITION_IN_UNIQUE_CONSTRAINT,
|
||||
constraintName: field.CONSTRAINT_NAME,
|
||||
refSchema: field.REFERENCED_TABLE_SCHEMA,
|
||||
refTable: field.REFERENCED_TABLE_NAME,
|
||||
refColumn: field.REFERENCED_COLUMN_NAME
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,102 +0,0 @@
|
||||
'use strict';
|
||||
import { sqlEscaper } from 'common/libs/sqlEscaper';
|
||||
import { TEXT, LONG_TEXT, NUMBER, BLOB } from 'common/fieldTypes';
|
||||
import fs from 'fs';
|
||||
|
||||
export default class {
|
||||
static async getTableData (connection, schema, table) {
|
||||
return connection
|
||||
.select('*')
|
||||
.schema(schema)
|
||||
.from(table)
|
||||
.limit(1000)
|
||||
.run();
|
||||
}
|
||||
|
||||
static async updateTableCell (connection, params) {
|
||||
let escapedParam;
|
||||
let reload = false;
|
||||
const id = typeof params.id === 'number' ? params.id : `"${params.id}"`;
|
||||
|
||||
if (NUMBER.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
|
||||
escapedParam = `"${sqlEscaper(params.content)}"`;
|
||||
|
||||
await connection
|
||||
.update({ [params.field]: `= ${escapedParam}` })
|
||||
.schema(params.schema)
|
||||
.from(params.table)
|
||||
.where({ [params.primary]: `= ${id}` })
|
||||
.run();
|
||||
|
||||
return { reload };
|
||||
}
|
||||
|
||||
static async deleteTableRows (connection, params) {
|
||||
return connection
|
||||
.schema(params.schema)
|
||||
.delete(params.table)
|
||||
.where({ [params.primary]: `IN (${params.rows.join(',')})` })
|
||||
.run();
|
||||
}
|
||||
|
||||
static async insertTableRows (connection, params) {
|
||||
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.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;
|
||||
}
|
||||
|
||||
for (let i = 0; i < params.repeat; i++) {
|
||||
await connection
|
||||
.schema(params.schema)
|
||||
.into(params.table)
|
||||
.insert(insertObj)
|
||||
.run();
|
||||
}
|
||||
}
|
||||
|
||||
static async getForeignList (connection, params) {
|
||||
const query = connection
|
||||
.select(`${params.column} AS foreignColumn`)
|
||||
.schema(params.schema)
|
||||
.from(params.table)
|
||||
.orderBy('foreignColumn ASC');
|
||||
|
||||
if (params.description)
|
||||
query.select(`LEFT(${params.description}, 20) AS foreignDescription`);
|
||||
|
||||
return query.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"
|
||||
@@ -52,7 +52,7 @@ export default {
|
||||
})
|
||||
},
|
||||
mounted () {
|
||||
ipcRenderer.send('checkForUpdates');
|
||||
ipcRenderer.send('check-for-updates');
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
|
@@ -27,7 +27,7 @@
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
class="btn btn-primary mr-2"
|
||||
@click="confirmModal"
|
||||
@click.stop="confirmModal"
|
||||
>
|
||||
{{ confirmText || $t('word.confirm') }}
|
||||
</button>
|
||||
|
@@ -46,8 +46,8 @@ export default {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
padding: 0.4rem;
|
||||
position: fixed;
|
||||
height: 100vh;
|
||||
right: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@@ -72,6 +72,7 @@ export default {
|
||||
align-items: center;
|
||||
padding: 0.1rem 0.3rem;
|
||||
cursor: pointer;
|
||||
justify-content: space-between;
|
||||
|
||||
&:hover {
|
||||
background: $primary-color;
|
||||
|
@@ -10,7 +10,7 @@
|
||||
</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">
|
||||
@@ -65,7 +65,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
closeModal () {
|
||||
this.$emit('closeAsking');
|
||||
this.$emit('close-asking');
|
||||
},
|
||||
sendCredentials () {
|
||||
this.$emit('credentials', this.credentials);
|
||||
|
@@ -10,7 +10,7 @@
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click="closeModal" />
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-body pb-0">
|
||||
<div class="content">
|
||||
<form class="form-horizontal">
|
||||
<fieldset class="m-0" :disabled="isTesting">
|
||||
@@ -113,13 +113,13 @@
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer text-light">
|
||||
<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}"
|
||||
@@ -137,7 +137,7 @@
|
||||
</div>
|
||||
<ModalAskCredentials
|
||||
v-if="isAsking"
|
||||
@closeAsking="closeAsking"
|
||||
@close-asking="closeAsking"
|
||||
@credentials="continueTest"
|
||||
/>
|
||||
</div>
|
||||
|
151
src/renderer/components/ModalEditDatabase.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<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 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
|
||||
};
|
||||
},
|
||||
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');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-container {
|
||||
max-width: 360px;
|
||||
}
|
||||
</style>
|
@@ -10,7 +10,7 @@
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click="closeModal" />
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-body pb-0">
|
||||
<div class="content">
|
||||
<form class="form-horizontal">
|
||||
<fieldset class="m-0" :disabled="isTesting">
|
||||
@@ -117,13 +117,13 @@
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer text-light">
|
||||
<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}"
|
||||
@@ -141,7 +141,7 @@
|
||||
</div>
|
||||
<ModalAskCredentials
|
||||
v-if="isAsking"
|
||||
@closeAsking="closeAsking"
|
||||
@close-asking="closeAsking"
|
||||
@credentials="continueTest"
|
||||
/>
|
||||
</div>
|
||||
|
125
src/renderer/components/ModalNewDatabase.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<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
|
||||
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 };
|
||||
},
|
||||
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');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-container {
|
||||
max-width: 360px;
|
||||
}
|
||||
</style>
|
@@ -10,7 +10,7 @@
|
||||
</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">
|
||||
<fieldset :disabled="isInserting">
|
||||
@@ -148,10 +148,10 @@ export default {
|
||||
return this.keyUsage.map(key => key.column);
|
||||
},
|
||||
fields () {
|
||||
return this.getWorkspaceTab(this.tabUid) ? this.getWorkspaceTab(this.tabUid).fields : [];
|
||||
return this.getWorkspaceTab(this.tabUid) ? this.getWorkspaceTab(this.tabUid).fields[0] : [];
|
||||
},
|
||||
keyUsage () {
|
||||
return this.getWorkspaceTab(this.tabUid) ? this.getWorkspaceTab(this.tabUid).keyUsage : [];
|
||||
return this.getWorkspaceTab(this.tabUid) ? this.getWorkspaceTab(this.tabUid).keyUsage[0] : [];
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
@@ -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,10 +71,10 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
checkForUpdates () {
|
||||
ipcRenderer.send('checkForUpdates');
|
||||
ipcRenderer.send('check-for-updates');
|
||||
},
|
||||
restartToUpdate () {
|
||||
ipcRenderer.send('restartToUpdate');
|
||||
ipcRenderer.send('restart-to-update');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,25 +1,15 @@
|
||||
<template>
|
||||
<div class="editor-wrapper">
|
||||
<textarea
|
||||
ref="codemirror"
|
||||
:options="cmOptions"
|
||||
/>
|
||||
<div ref="editor" class="editor" />
|
||||
</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';
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
import { completionItemProvider } from '@/suggestions/sql';
|
||||
|
||||
CodeMirror.defineOption('sql-hint');
|
||||
monaco.languages.registerCompletionItemProvider('sql', completionItemProvider(monaco));
|
||||
|
||||
export default {
|
||||
name: 'QueryEditor',
|
||||
@@ -28,42 +18,31 @@ export default {
|
||||
},
|
||||
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
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.initialize();
|
||||
},
|
||||
methods: {
|
||||
initialize () {
|
||||
this.cminstance = CodeMirror.fromTextArea(this.$refs.codemirror, this.cmOptions);
|
||||
this.cminstance.setValue(this.value || this.content);
|
||||
this.editor = monaco.editor.create(this.$refs.editor, {
|
||||
value: this.value,
|
||||
language: 'sql',
|
||||
theme: 'vs-dark',
|
||||
autoIndent: true,
|
||||
minimap: {
|
||||
enabled: false
|
||||
},
|
||||
contextmenu: false,
|
||||
wordBasedSuggestions: true,
|
||||
acceptSuggestionOnEnter: 'smart',
|
||||
quickSuggestions: true
|
||||
});
|
||||
|
||||
this.cminstance.on('change', cm => {
|
||||
this.content = cm.getValue();
|
||||
this.$emit('input', this.content);
|
||||
});
|
||||
}
|
||||
this.editor.onDidChangeModelContent(e => {
|
||||
const content = this.editor.getValue();
|
||||
this.$emit('update:value', content);
|
||||
});
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.editor && this.editor.dispose();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -71,11 +50,14 @@ export default {
|
||||
<style lang="scss">
|
||||
.editor-wrapper {
|
||||
border-bottom: 1px solid #444;
|
||||
|
||||
.editor {
|
||||
height: 200px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
height: 200px;
|
||||
|
||||
.CodeMirror-scroll {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
@@ -4,10 +4,10 @@
|
||||
@close-context="$emit('close-context')"
|
||||
>
|
||||
<div class="context-element" @click="showEditModal(contextConnection)">
|
||||
<i class="mdi mdi-18px mdi-pencil text-light pr-1" /> {{ $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="mdi mdi-18px mdi-delete text-light pr-1" /> {{ $t('word.delete') }}
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ $t('word.delete') }}</span>
|
||||
</div>
|
||||
|
||||
<ConfirmModal
|
||||
@@ -22,7 +22,7 @@
|
||||
</template>
|
||||
<div :slot="'body'">
|
||||
<div class="mb-2">
|
||||
{{ $t('message.deleteConnectionCorfirm') }} <b>{{ connectionName }}</b>?
|
||||
{{ $t('message.deleteCorfirm') }} <b>{{ connectionName }}</b>?
|
||||
</div>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
|
@@ -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>
|
||||
|
@@ -11,7 +11,7 @@
|
||||
|
||||
<div class="footer-right-elements">
|
||||
<ul class="footer-elements">
|
||||
<li class="footer-element footer-link" @click="openOutside('https://www.patreon.com/fabio286')">
|
||||
<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>
|
||||
|
@@ -201,6 +201,7 @@ export default {
|
||||
max-width: 320px;
|
||||
pointer-events: none;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
|
@@ -73,7 +73,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
closeApp () {
|
||||
ipcRenderer.send('closeApp');
|
||||
ipcRenderer.send('close-app');
|
||||
},
|
||||
minimizeApp () {
|
||||
this.w.minimize();
|
||||
|
@@ -2,21 +2,23 @@
|
||||
<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
|
||||
<ul ref="tabWrap" class="tab tab-block column col-12">
|
||||
<li
|
||||
v-if="workspace.breadcrumbs.table"
|
||||
class="tab-item"
|
||||
:class="{'active': selectedTab === 'prop'}"
|
||||
@click="selectTab({uid: workspace.uid, tab: 'prop'})"
|
||||
>
|
||||
<a class="tab-link">
|
||||
<i class="mdi mdi-18px mdi-tune mr-1" />
|
||||
<span :title="workspace.breadcrumbs.table">{{ $t('word.properties').toUpperCase() }}: {{ workspace.breadcrumbs.table }}</span>
|
||||
</a>
|
||||
</li> -->
|
||||
</li>
|
||||
<li
|
||||
v-if="workspace.breadcrumbs.table"
|
||||
class="tab-item"
|
||||
:class="{'active': selectedTab === 1}"
|
||||
@click="selectTab({uid: workspace.uid, tab: 1})"
|
||||
:class="{'active': selectedTab === 'data'}"
|
||||
@click="selectTab({uid: workspace.uid, tab: 'data'})"
|
||||
>
|
||||
<a class="tab-link">
|
||||
<i class="mdi mdi-18px mdi-table mr-1" />
|
||||
@@ -24,25 +26,50 @@
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
v-for="(tab, key) of queryTabs"
|
||||
v-for="tab of queryTabs"
|
||||
:key="tab.uid"
|
||||
class="tab-item"
|
||||
:class="{'active': selectedTab === tab.uid}"
|
||||
@click="selectTab({uid: workspace.uid, tab: tab.uid})"
|
||||
@mousedown.middle="closeTab(tab.uid)"
|
||||
>
|
||||
<a><span>Query #{{ key+1 }} <span v-if="queryTabs.length > 1" class="btn btn-clear" /></span></a>
|
||||
<a>
|
||||
<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>
|
||||
<div v-show="selectedTab === 'prop'" class="column col-12">
|
||||
<p class="px-2">
|
||||
In future releases
|
||||
</p>
|
||||
</div>
|
||||
<WorkspaceTableTab
|
||||
v-show="selectedTab === 1"
|
||||
v-show="selectedTab === 'data'"
|
||||
:connection="connection"
|
||||
:table="workspace.breadcrumbs.table"
|
||||
/>
|
||||
<WorkspaceQueryTab
|
||||
v-for="tab of queryTabs"
|
||||
v-show="selectedTab === tab.uid"
|
||||
:key="tab.uid"
|
||||
:tab-uid="tab.uid"
|
||||
:is-selected="selectedTab === tab.uid"
|
||||
:connection="connection"
|
||||
/>
|
||||
</div>
|
||||
@@ -78,7 +105,7 @@ export default {
|
||||
return this.selectedWorkspace === this.connection.uid;
|
||||
},
|
||||
selectedTab () {
|
||||
return this.workspace.selected_tab || 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');
|
||||
@@ -90,13 +117,30 @@ export default {
|
||||
if (isInitiated)
|
||||
this.connectWorkspace(this.connection);
|
||||
},
|
||||
mounted () {
|
||||
if (this.$refs.tabWrap) {
|
||||
this.$refs.tabWrap.addEventListener('wheel', e => {
|
||||
if (e.deltaY > 0) this.$refs.tabWrap.scrollLeft += 50;
|
||||
else this.$refs.tabWrap.scrollLeft -= 50;
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addWorkspace: 'workspaces/addWorkspace',
|
||||
connectWorkspace: 'workspaces/connectWorkspace',
|
||||
removeConnected: 'workspaces/removeConnected',
|
||||
selectTab: 'workspaces/selectTab'
|
||||
})
|
||||
selectTab: 'workspaces/selectTab',
|
||||
newTab: 'workspaces/newTab',
|
||||
removeTab: 'workspaces/removeTab'
|
||||
}),
|
||||
addTab () {
|
||||
this.newTab(this.connection.uid);
|
||||
},
|
||||
closeTab (tUid) {
|
||||
if (this.queryTabs.length === 1) return;
|
||||
this.removeTab({ uid: this.connection.uid, tab: tUid });
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -107,12 +151,21 @@ export default {
|
||||
margin: 0;
|
||||
|
||||
.workspace-tabs {
|
||||
overflow: auto;
|
||||
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;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
max-width: 12rem;
|
||||
@@ -132,10 +185,17 @@ export default {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.tab-add {
|
||||
padding: 0.2rem 0.4rem;
|
||||
margin-top: 2px;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
> span {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
padding: 0 0.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -5,7 +5,7 @@
|
||||
<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>
|
||||
|
@@ -10,13 +10,18 @@
|
||||
<span class="workspace-explorebar-title">{{ connectionName }}</span>
|
||||
<span v-if="workspace.connected" class="workspace-explorebar-tools">
|
||||
<i
|
||||
class="mdi mdi-18px mdi-refresh 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"
|
||||
/>
|
||||
<i
|
||||
class="mdi mdi-18px mdi-power-plug-off c-hand mr-1 ml-2"
|
||||
class="mdi mdi-18px mdi-power-plug-off c-hand"
|
||||
:title="$t('word.disconnect')"
|
||||
@click="disconnectWorkspace(connection.uid)"
|
||||
/>
|
||||
@@ -33,9 +38,22 @@
|
||||
:key="db.name"
|
||||
:database="db"
|
||||
:connection="connection"
|
||||
@show-database-context="openDatabaseContext"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ModalNewDatabase
|
||||
v-if="isNewDBModal"
|
||||
@close="hideNewDBModal"
|
||||
@reload="refresh"
|
||||
/>
|
||||
<DatabaseContext
|
||||
v-if="isDatabaseContext"
|
||||
:selected-database="selectedDatabase"
|
||||
:context-event="databaseContextEvent"
|
||||
@close-context="closeDatabaseContext"
|
||||
@reload="refresh"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -44,12 +62,16 @@ import { mapGetters, mapActions } from 'vuex';
|
||||
import _ from 'lodash';
|
||||
import WorkspaceConnectPanel from '@/components/WorkspaceConnectPanel';
|
||||
import WorkspaceExploreBarDatabase from '@/components/WorkspaceExploreBarDatabase';
|
||||
import DatabaseContext from '@/components/WorkspaceExploreBarDatabaseContext';
|
||||
import ModalNewDatabase from '@/components/ModalNewDatabase';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceExploreBar',
|
||||
components: {
|
||||
WorkspaceConnectPanel,
|
||||
WorkspaceExploreBarDatabase
|
||||
WorkspaceExploreBarDatabase,
|
||||
DatabaseContext,
|
||||
ModalNewDatabase
|
||||
},
|
||||
props: {
|
||||
connection: Object,
|
||||
@@ -58,7 +80,14 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
isRefreshing: false,
|
||||
localWidth: null
|
||||
isNewDBModal: false,
|
||||
localWidth: null,
|
||||
isDatabaseContext: false,
|
||||
isTableContext: false,
|
||||
databaseContextEvent: null,
|
||||
tableContextEvent: null,
|
||||
selectedDatabase: '',
|
||||
selectedTable: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -117,6 +146,22 @@ export default {
|
||||
},
|
||||
stopResize () {
|
||||
window.removeEventListener('mousemove', this.resize);
|
||||
},
|
||||
showNewDBModal () {
|
||||
this.isNewDBModal = true;
|
||||
},
|
||||
hideNewDBModal () {
|
||||
this.isNewDBModal = false;
|
||||
},
|
||||
openDatabaseContext (payload) {
|
||||
this.isTableContext = false;
|
||||
this.selectedDatabase = payload.database;
|
||||
this.databaseContextEvent = payload.event;
|
||||
this.isDatabaseContext = true;
|
||||
},
|
||||
closeDatabaseContext () {
|
||||
this.isDatabaseContext = false;
|
||||
this.selectedDatabase = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -4,6 +4,7 @@
|
||||
class="accordion-header database-name pb-0"
|
||||
:class="{'text-bold': breadcrumbs.schema === database.name}"
|
||||
@click="changeBreadcrumbs({schema: database.name, table:null})"
|
||||
@contextmenu.prevent="showDatabaseContext($event, database.name)"
|
||||
>
|
||||
<i class="icon mdi mdi-18px mdi-chevron-right" />
|
||||
<i class="database-icon mdi mdi-18px mdi-database mr-1" />
|
||||
@@ -18,6 +19,7 @@
|
||||
class="menu-item"
|
||||
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.table === table.TABLE_NAME}"
|
||||
@click="changeBreadcrumbs({schema: database.name, table: table.TABLE_NAME})"
|
||||
@contextmenu.prevent="showTableContext($event, table.TABLE_NAME)"
|
||||
>
|
||||
<a class="table-name">
|
||||
<i class="table-icon mdi mdi-18px mdi-table mr-1" />
|
||||
@@ -50,7 +52,13 @@ export default {
|
||||
methods: {
|
||||
...mapActions({
|
||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
|
||||
})
|
||||
}),
|
||||
showDatabaseContext (event, database) {
|
||||
this.$emit('show-database-context', { event, database });
|
||||
},
|
||||
showTableContext (table) {
|
||||
this.$emit('show-table-context', table);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
112
src/renderer/components/WorkspaceExploreBarDatabaseContext.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<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> -->
|
||||
<div class="context-element" @click="showEditModal">
|
||||
<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="showDeleteModal">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-delete 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'
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
deleteConnection: 'connections/deleteConnection',
|
||||
showEditModal: 'application/showEditConnModal',
|
||||
addNotification: 'notifications/addNotification'
|
||||
}),
|
||||
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') {
|
||||
this.closeContext();
|
||||
this.$emit('reload');
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="workspace-query-tab column col-12 columns col-gapless">
|
||||
<div v-show="isSelected" class="workspace-query-tab column col-12 columns col-gapless">
|
||||
<div class="workspace-query-runner column col-12">
|
||||
<QueryEditor v-model="query" />
|
||||
<QueryEditor v-if="isSelected" :value.sync="query" />
|
||||
<div class="workspace-query-runner-footer">
|
||||
<div class="workspace-query-buttons">
|
||||
<button
|
||||
@@ -15,8 +15,11 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="workspace-query-info">
|
||||
<div v-if="results.rows">
|
||||
{{ $t('word.results') }}: <b>{{ results.rows.length }}</b>
|
||||
<div v-if="resultsCount !== false">
|
||||
{{ $t('word.results') }}: <b>{{ resultsCount }}</b>
|
||||
</div>
|
||||
<div v-if="affectedCount !== false">
|
||||
{{ $t('message.affectedRows') }}: <b>{{ affectedCount }}</b>
|
||||
</div>
|
||||
<div v-if="workspace.breadcrumbs.schema">
|
||||
{{ $t('word.schema') }}: <b>{{ workspace.breadcrumbs.schema }}</b>
|
||||
@@ -31,15 +34,17 @@
|
||||
ref="queryTable"
|
||||
:results="results"
|
||||
:tab-uid="tabUid"
|
||||
@updateField="updateField"
|
||||
@deleteSelected="deleteSelected"
|
||||
:conn-uid="connection.uid"
|
||||
mode="query"
|
||||
@update-field="updateField"
|
||||
@delete-selected="deleteSelected"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Connection from '@/ipc-api/Connection';
|
||||
import Database from '@/ipc-api/Database';
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import WorkspaceQueryTable from '@/components/WorkspaceQueryTable';
|
||||
@@ -55,15 +60,17 @@ export default {
|
||||
mixins: [tableTabs],
|
||||
props: {
|
||||
connection: Object,
|
||||
tabUid: String
|
||||
tabUid: String,
|
||||
isSelected: Boolean
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
query: '',
|
||||
lastQuery: '',
|
||||
isQuering: false,
|
||||
results: {},
|
||||
selectedFields: []
|
||||
results: [],
|
||||
resultsCount: false,
|
||||
affectedCount: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -72,16 +79,6 @@ export default {
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
table () {
|
||||
if ('fields' in this.results && this.results.fields.length)
|
||||
return this.results.fields[0].orgTable;
|
||||
return '';
|
||||
},
|
||||
schema () {
|
||||
if ('fields' in this.results && this.results.fields.length)
|
||||
return this.results.fields[0].db;
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -90,11 +87,25 @@ export default {
|
||||
setTabFields: 'workspaces/setTabFields',
|
||||
setTabKeyUsage: 'workspaces/setTabKeyUsage'
|
||||
}),
|
||||
getResultParams (index) {
|
||||
const resultsWithRows = this.results.filter(result => result.rows);
|
||||
let cachedTable;
|
||||
|
||||
if (resultsWithRows[index] && resultsWithRows[index].fields && resultsWithRows[index].fields.length) {
|
||||
return resultsWithRows[index].fields.map(field => {
|
||||
if (field.orgTable) cachedTable = field.orgTable;// Needed for some queries on information_schema
|
||||
return {
|
||||
table: field.orgTable || cachedTable,
|
||||
schema: field.db || 'INFORMATION_SCHEMA'
|
||||
};
|
||||
}).filter((val, i, arr) => arr.findIndex(el => el.schema === val.schema && el.table === val.table) === i);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
async runQuery (query) {
|
||||
if (!query) return;
|
||||
this.isQuering = true;
|
||||
this.results = {};
|
||||
this.setTabFields({ cUid: this.connection.uid, tUid: this.tabUid, fields: [] });
|
||||
this.clearTabData();
|
||||
|
||||
try { // Query Data
|
||||
const params = {
|
||||
@@ -103,10 +114,90 @@ export default {
|
||||
query
|
||||
};
|
||||
|
||||
const { status, response } = await Connection.rawQuery(params);
|
||||
const { status, response } = await Database.rawQuery(params);
|
||||
|
||||
if (status === 'success') {
|
||||
this.results = response;
|
||||
this.selectedFields = response.fields.map(field => field.orgName);
|
||||
this.results = Array.isArray(response) ? response : [response];
|
||||
|
||||
let selectedFields = [];
|
||||
const fieldsArr = [];
|
||||
const keysArr = [];
|
||||
let qI = 0;// queries index
|
||||
|
||||
for (const result of this.results) { // cycle queries
|
||||
let fI = 0;// fields index
|
||||
|
||||
if (result.rows) { // if is a select
|
||||
const paramsArr = this.getResultParams(qI);
|
||||
|
||||
selectedFields = result.fields.map(field => field.orgName);
|
||||
this.resultsCount += result.rows.length;
|
||||
|
||||
for (const paramObj of paramsArr) {
|
||||
try { // Table data
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
...paramObj
|
||||
};
|
||||
|
||||
const { status, response } = await Tables.getTableColumns(params);
|
||||
|
||||
if (status === 'success') {
|
||||
let fields = response.filter(field => selectedFields.includes(field.name));
|
||||
if (selectedFields.length) {
|
||||
fields = fields.map(field => {
|
||||
return { ...field, alias: result.fields[fI++].name };
|
||||
});
|
||||
}
|
||||
if (!fields.length) {
|
||||
fields = response.map(field => {
|
||||
return { ...field, alias: result.fields[fI++].name };
|
||||
});
|
||||
}
|
||||
|
||||
fieldsArr[qI] = fieldsArr[qI] ? [...fieldsArr[qI], ...fields] : fields;
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
try { // Key usage (foreign keys)
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
...paramObj
|
||||
};
|
||||
|
||||
const { status, response } = await Tables.getKeyUsage(params);
|
||||
if (status === 'success')
|
||||
keysArr[qI] = keysArr[qI] ? [...keysArr[qI], ...response] : response;
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (result.report) { // if is a query without output
|
||||
this.affectedCount += result.report.affectedRows;
|
||||
}
|
||||
|
||||
qI++;
|
||||
}
|
||||
|
||||
this.setTabFields({
|
||||
cUid: this.connection.uid,
|
||||
tUid: this.tabUid,
|
||||
fields: fieldsArr
|
||||
});
|
||||
this.setTabKeyUsage({
|
||||
cUid: this.connection.uid,
|
||||
tUid: this.tabUid,
|
||||
keyUsage: keysArr
|
||||
});
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
@@ -115,47 +206,17 @@ export default {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
try { // Table data
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
table: this.table
|
||||
};
|
||||
|
||||
const { status, response } = await Tables.getTableColumns(params);
|
||||
if (status === 'success') {
|
||||
const fields = response.filter(field => this.selectedFields.includes(field.name));
|
||||
this.setTabFields({ cUid: this.connection.uid, tUid: this.tabUid, fields });
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
try { // Key usage (foreign keys)
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
table: this.table
|
||||
};
|
||||
|
||||
const { status, response } = await Tables.getKeyUsage(params);
|
||||
if (status === 'success')
|
||||
this.setTabKeyUsage({ cUid: this.connection.uid, tUid: this.tabUid, keyUsage: response });
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isQuering = false;
|
||||
this.lastQuery = query;
|
||||
},
|
||||
reloadTable () {
|
||||
this.runQuery(this.lastQuery);
|
||||
},
|
||||
clearTabData () {
|
||||
this.results = [];
|
||||
this.resultsCount = false;
|
||||
this.affectedCount = false;
|
||||
this.setTabFields({ cUid: this.connection.uid, tUid: this.tabUid, fields: [] });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -8,16 +8,28 @@
|
||||
v-if="isContext"
|
||||
:context-event="contextEvent"
|
||||
:selected-rows="selectedRows"
|
||||
@deleteSelected="deleteSelected"
|
||||
@delete-selected="deleteSelected"
|
||||
@close-context="isContext = false"
|
||||
/>
|
||||
<ul v-if="resultsWithRows.length > 1" class="tab tab-block result-tabs">
|
||||
<li
|
||||
v-for="(result, index) in resultsWithRows"
|
||||
:key="index"
|
||||
class="tab-item"
|
||||
:class="{'active': resultsetIndex === index}"
|
||||
@click="selectResultset(index)"
|
||||
>
|
||||
<a>{{ result.fields ? result.fields[0].orgTable : '' }} ({{ result.rows.length }})</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div ref="table" class="table table-hover">
|
||||
<div class="thead">
|
||||
<div class="tr">
|
||||
<div
|
||||
v-for="field in fields"
|
||||
:key="field.name"
|
||||
v-for="(field, index) in fields"
|
||||
:key="index"
|
||||
class="th c-hand"
|
||||
:title="field.comment ? field.comment : false"
|
||||
>
|
||||
<div ref="columnResize" class="column-resizable">
|
||||
<div class="table-column-title" @click="sort(field.name)">
|
||||
@@ -27,9 +39,9 @@
|
||||
:class="`key-${field.key}`"
|
||||
:title="keyName(field.key)"
|
||||
/>
|
||||
<span>{{ field.name }}</span>
|
||||
<span>{{ field.alias || field.name }}</span>
|
||||
<i
|
||||
v-if="currentSort === field.name"
|
||||
v-if="currentSort === field.name || currentSort === `${field.table}.${field.name}`"
|
||||
class="mdi sort-icon"
|
||||
:class="currentSortDir === 'asc' ? 'mdi-sort-ascending':'mdi-sort-descending'"
|
||||
/>
|
||||
@@ -39,7 +51,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<BaseVirtualScroll
|
||||
v-if="results.rows"
|
||||
v-if="resultsWithRows[resultsetIndex] && resultsWithRows[resultsetIndex].rows"
|
||||
ref="resultTable"
|
||||
:items="sortedResults"
|
||||
:item-height="22"
|
||||
@@ -56,8 +68,8 @@
|
||||
:key-usage="keyUsage"
|
||||
class="tr"
|
||||
:class="{'selected': selectedRows.includes(row._id)}"
|
||||
@selectRow="selectRow($event, row._id)"
|
||||
@updateField="updateField($event, row[primaryField.name])"
|
||||
@select-row="selectRow($event, row._id)"
|
||||
@update-field="updateField($event, getPrimaryValue(row))"
|
||||
@contextmenu="contextMenu"
|
||||
/>
|
||||
</template>
|
||||
@@ -81,8 +93,10 @@ export default {
|
||||
TableContext
|
||||
},
|
||||
props: {
|
||||
results: Object,
|
||||
tabUid: [String, Number]
|
||||
results: Array,
|
||||
tabUid: [String, Number],
|
||||
connUid: String,
|
||||
mode: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -93,13 +107,19 @@ export default {
|
||||
selectedCell: null,
|
||||
selectedRows: [],
|
||||
currentSort: '',
|
||||
currentSortDir: 'asc'
|
||||
currentSortDir: 'asc',
|
||||
resultsetIndex: 0,
|
||||
scrollElement: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
getWorkspaceTab: 'workspaces/getWorkspaceTab'
|
||||
getWorkspaceTab: 'workspaces/getWorkspaceTab',
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
workspaceSchema () {
|
||||
return this.getWorkspace(this.connUid).breadcrumbs.schema;
|
||||
},
|
||||
primaryField () {
|
||||
return this.fields.filter(field => ['pri', 'uni'].includes(field.key))[0] || false;
|
||||
},
|
||||
@@ -118,27 +138,34 @@ export default {
|
||||
else
|
||||
return this.localResults;
|
||||
},
|
||||
resultsWithRows () {
|
||||
return this.results.filter(result => result.rows);
|
||||
},
|
||||
tabProperties () {
|
||||
return this.getWorkspaceTab(this.tabUid);
|
||||
},
|
||||
fields () {
|
||||
return this.getWorkspaceTab(this.tabUid) ? this.getWorkspaceTab(this.tabUid).fields : [];
|
||||
return this.tabProperties && this.tabProperties.fields[this.resultsetIndex] ? this.tabProperties.fields[this.resultsetIndex] : [];
|
||||
},
|
||||
keyUsage () {
|
||||
return this.getWorkspaceTab(this.tabUid) ? this.getWorkspaceTab(this.tabUid).keyUsage : [];
|
||||
},
|
||||
scrollElement () {
|
||||
return this.$refs.tableWrapper;
|
||||
return this.tabProperties && this.tabProperties.keyUsage[this.resultsetIndex] ? this.tabProperties.keyUsage[this.resultsetIndex] : [];
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
results () {
|
||||
this.resetSort();
|
||||
this.localResults = this.results.rows ? this.results.rows.map(item => {
|
||||
return { ...item, _id: uidGen() };
|
||||
}) : [];
|
||||
this.setLocalResults();
|
||||
this.resultsetIndex = 0;
|
||||
},
|
||||
resultsetIndex () {
|
||||
this.setLocalResults();
|
||||
}
|
||||
},
|
||||
updated () {
|
||||
if (this.$refs.table)
|
||||
this.refreshScroller();
|
||||
|
||||
if (this.$refs.tableWrapper)
|
||||
this.scrollElement = this.$refs.tableWrapper;
|
||||
},
|
||||
mounted () {
|
||||
window.addEventListener('resize', this.resizeResults);
|
||||
@@ -178,9 +205,34 @@ export default {
|
||||
return 'UNKNOWN ' + key;
|
||||
}
|
||||
},
|
||||
getTable (index) {
|
||||
if (this.resultsWithRows[index] && this.resultsWithRows[index].fields && this.resultsWithRows[index].fields.length)
|
||||
return this.resultsWithRows[index].fields[0].orgTable;
|
||||
return '';
|
||||
},
|
||||
getSchema (index) {
|
||||
if (this.resultsWithRows[index] && this.resultsWithRows[index].fields && this.resultsWithRows[index].fields.length)
|
||||
return this.resultsWithRows[index].fields[0].db;
|
||||
return this.workspaceSchema;
|
||||
},
|
||||
getPrimaryValue (row) {
|
||||
const primaryFieldName = Object.keys(row).find(prop => [
|
||||
this.primaryField.alias,
|
||||
this.primaryField.name,
|
||||
`${this.primaryField.table}.${this.primaryField.alias}`,
|
||||
`${this.primaryField.table}.${this.primaryField.name}`
|
||||
].includes(prop));
|
||||
return row[primaryFieldName];
|
||||
},
|
||||
setLocalResults () {
|
||||
this.resetSort();
|
||||
this.localResults = this.resultsWithRows[this.resultsetIndex] && this.resultsWithRows[this.resultsetIndex].rows ? this.resultsWithRows[this.resultsetIndex].rows.map(item => {
|
||||
return { ...item, _id: uidGen() };
|
||||
}) : [];
|
||||
},
|
||||
resizeResults () {
|
||||
if (this.$refs.resultTable) {
|
||||
const el = this.$refs.table;
|
||||
const el = this.$refs.tableWrapper;
|
||||
|
||||
if (el) {
|
||||
const footer = document.getElementById('footer');
|
||||
@@ -199,10 +251,12 @@ export default {
|
||||
else {
|
||||
const params = {
|
||||
primary: this.primaryField.name,
|
||||
schema: this.getSchema(this.resultsetIndex),
|
||||
table: this.getTable(this.resultsetIndex),
|
||||
id,
|
||||
...payload
|
||||
};
|
||||
this.$emit('updateField', params);
|
||||
this.$emit('update-field', params);
|
||||
}
|
||||
},
|
||||
deleteSelected () {
|
||||
@@ -212,16 +266,21 @@ export default {
|
||||
const rowIDs = this.localResults.filter(row => this.selectedRows.includes(row._id)).map(row => row[this.primaryField.name]);
|
||||
const params = {
|
||||
primary: this.primaryField.name,
|
||||
schema: this.getSchema(this.resultsetIndex),
|
||||
table: this.getTable(this.resultsetIndex),
|
||||
rows: rowIDs
|
||||
};
|
||||
this.$emit('deleteSelected', params);
|
||||
this.$emit('delete-selected', params);
|
||||
}
|
||||
},
|
||||
applyUpdate (params) {
|
||||
const { primary, id, field, content } = params;
|
||||
const { primary, id, field, table, content } = params;
|
||||
|
||||
this.localResults = this.localResults.map(row => {
|
||||
if (row[primary] === id)
|
||||
if (row[primary] === id)// only fieldName
|
||||
row[field] = content;
|
||||
else if (row[`${table}.${primary}`] === id)// table.fieldName
|
||||
row[`${table}.${field}`] = content;
|
||||
|
||||
return row;
|
||||
});
|
||||
@@ -261,6 +320,9 @@ export default {
|
||||
this.isContext = true;
|
||||
},
|
||||
sort (field) {
|
||||
if (this.mode === 'query')
|
||||
field = `${this.getTable(this.resultsetIndex)}.${field}`;
|
||||
|
||||
if (field === this.currentSort) {
|
||||
if (this.currentSortDir === 'asc')
|
||||
this.currentSortDir = 'desc';
|
||||
@@ -275,12 +337,15 @@ export default {
|
||||
resetSort () {
|
||||
this.currentSort = '';
|
||||
this.currentSortDir = 'asc';
|
||||
},
|
||||
selectResultset (index) {
|
||||
this.resultsetIndex = index;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
.vscroll {
|
||||
height: 1000px;
|
||||
overflow: auto;
|
||||
@@ -305,4 +370,9 @@ export default {
|
||||
line-height: 1;
|
||||
margin-left: 0.2rem;
|
||||
}
|
||||
|
||||
.result-tabs {
|
||||
background: transparent !important;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
@@ -4,7 +4,7 @@
|
||||
@close-context="closeContext"
|
||||
>
|
||||
<div class="context-element" @click="showConfirmModal">
|
||||
<i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ $tc('message.deleteRows', selectedRows.length) }}
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ $tc('message.deleteRows', selectedRows.length) }}</span>
|
||||
</div>
|
||||
|
||||
<ConfirmModal
|
||||
@@ -63,7 +63,7 @@ export default {
|
||||
this.$emit('close-context');
|
||||
},
|
||||
deleteRows () {
|
||||
this.$emit('deleteSelected');
|
||||
this.$emit('delete-selected');
|
||||
this.closeContext();
|
||||
}
|
||||
}
|
||||
|
@@ -7,15 +7,14 @@
|
||||
class="td p-0"
|
||||
tabindex="0"
|
||||
@contextmenu.prevent="$emit('contextmenu', $event, {id: row._id, field: cKey})"
|
||||
@updateField="updateField($event, row[primaryField.name])"
|
||||
>
|
||||
<template v-if="cKey !== '_id'">
|
||||
<span
|
||||
v-if="!isInlineEditor[cKey]"
|
||||
class="cell-content px-2"
|
||||
:class="`${isNull(col)} type-${fieldType(cKey)}`"
|
||||
:class="`${isNull(col)} type-${getFieldType(cKey)}`"
|
||||
@dblclick="editON($event, col, cKey)"
|
||||
>{{ col | typeFormat(fieldType(cKey), fieldPrecision(cKey)) | cutText }}</span>
|
||||
>{{ col | typeFormat(getFieldType(cKey), getFieldPrecision(cKey)) | cutText }}</span>
|
||||
<ForeignKeySelect
|
||||
v-else-if="foreignKeys.includes(cKey)"
|
||||
class="editable-field"
|
||||
@@ -187,7 +186,10 @@ export default {
|
||||
},
|
||||
props: {
|
||||
row: Object,
|
||||
fields: Array,
|
||||
fields: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
keyUsage: Array
|
||||
},
|
||||
data () {
|
||||
@@ -218,7 +220,7 @@ export default {
|
||||
|
||||
if (TIME.includes(this.editingType)) {
|
||||
let timeMask = '##:##:##';
|
||||
const precision = this.fieldPrecision(this.editingField);
|
||||
const precision = this.getFieldPrecision(this.editingField);
|
||||
|
||||
for (let i = 0; i < precision; i++)
|
||||
timeMask += i === 0 ? '.#' : '#';
|
||||
@@ -231,7 +233,7 @@ export default {
|
||||
|
||||
if (DATETIME.includes(this.editingType)) {
|
||||
let datetimeMask = '####-##-## ##:##:##';
|
||||
const precision = this.fieldPrecision(this.editingField);
|
||||
const precision = this.getFieldPrecision(this.editingField);
|
||||
|
||||
for (let i = 0; i < precision; i++)
|
||||
datetimeMask += i === 0 ? '.#' : '#';
|
||||
@@ -254,28 +256,39 @@ export default {
|
||||
return this.keyUsage.map(key => key.column);
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.fields.forEach(field => {
|
||||
this.isInlineEditor[field.name] = false;
|
||||
});
|
||||
watch: {
|
||||
fields () {
|
||||
this.fields.forEach(field => {
|
||||
this.isInlineEditor[field.name] = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fieldType (cKey) {
|
||||
getFieldType (cKey) {
|
||||
let type = 'unknown';
|
||||
const field = this.fields.filter(field => field.name === cKey)[0];
|
||||
const field = this.getFieldObj(cKey);
|
||||
if (field)
|
||||
type = field.type;
|
||||
|
||||
return type;
|
||||
},
|
||||
fieldPrecision (cKey) {
|
||||
getFieldPrecision (cKey) {
|
||||
let length = 0;
|
||||
const field = this.fields.filter(field => field.name === cKey)[0];
|
||||
const field = this.getFieldObj(cKey);
|
||||
if (field)
|
||||
length = field.datePrecision;
|
||||
|
||||
return length;
|
||||
},
|
||||
getFieldObj (cKey) {
|
||||
return this.fields.filter(field =>
|
||||
field.name === cKey ||
|
||||
field.alias === cKey ||
|
||||
`${field.table}.${field.name}` === cKey ||
|
||||
`${field.table}.${field.alias}` === cKey ||
|
||||
`${field.table.toLowerCase()}.${field.name}` === cKey ||
|
||||
`${field.table.toLowerCase()}.${field.alias}` === cKey)[0];
|
||||
},
|
||||
isNull (value) {
|
||||
return value === null ? ' is-null' : '';
|
||||
},
|
||||
@@ -283,7 +296,7 @@ export default {
|
||||
return bufferToBase64(val);
|
||||
},
|
||||
editON (event, content, field) {
|
||||
const type = this.fieldType(field);
|
||||
const type = this.getFieldType(field);
|
||||
this.originalContent = content;
|
||||
this.editingType = type;
|
||||
this.editingField = field;
|
||||
@@ -316,7 +329,7 @@ export default {
|
||||
}
|
||||
|
||||
// Inline editable fields
|
||||
this.editingContent = this.$options.filters.typeFormat(this.originalContent, type, this.fieldPrecision(field));
|
||||
this.editingContent = this.$options.filters.typeFormat(this.originalContent, type, this.getFieldPrecision(field));
|
||||
this.$nextTick(() => { // Focus on input
|
||||
event.target.blur();
|
||||
|
||||
@@ -346,8 +359,8 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
this.$emit('updateField', {
|
||||
field: this.editingField,
|
||||
this.$emit('update-field', {
|
||||
field: this.getFieldObj(this.editingField).name,
|
||||
type: this.editingType,
|
||||
content
|
||||
});
|
||||
@@ -385,14 +398,11 @@ export default {
|
||||
};
|
||||
this.willBeDeleted = true;
|
||||
},
|
||||
updateField (event, id) {
|
||||
this.$emit('updateField', event, id);
|
||||
},
|
||||
contextMenu (event, cell) {
|
||||
this.$emit('updateField', event, cell);
|
||||
this.$emit('update-field', event, cell);
|
||||
},
|
||||
selectRow (event, row) {
|
||||
this.$emit('selectRow', event, row);
|
||||
this.$emit('select-row', event, row);
|
||||
},
|
||||
getKeyUsage (keyName) {
|
||||
return this.keyUsage.find(key => key.column === keyName);
|
||||
|
@@ -21,8 +21,8 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="workspace-query-info">
|
||||
<div v-if="results.rows">
|
||||
{{ $t('word.results') }}: <b>{{ results.rows.length }}</b>
|
||||
<div v-if="results.length && results[0].rows">
|
||||
{{ $t('word.results') }}: <b>{{ results[0].rows.length }}</b>
|
||||
</div>
|
||||
<div v-if="workspace.breadcrumbs.database">
|
||||
{{ $t('word.schema') }}: <b>{{ workspace.breadcrumbs.database }}</b>
|
||||
@@ -33,11 +33,14 @@
|
||||
<div class="workspace-query-results column col-12">
|
||||
<WorkspaceQueryTable
|
||||
v-if="results"
|
||||
v-show="!isQuering"
|
||||
ref="queryTable"
|
||||
:results="results"
|
||||
:tab-uid="tabUid"
|
||||
@updateField="updateField"
|
||||
@deleteSelected="deleteSelected"
|
||||
:conn-uid="connection.uid"
|
||||
mode="table"
|
||||
@update-field="updateField"
|
||||
@delete-selected="deleteSelected"
|
||||
/>
|
||||
</div>
|
||||
<ModalNewTableRow
|
||||
@@ -69,9 +72,9 @@ export default {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
tabUid: 1,
|
||||
tabUid: 'data',
|
||||
isQuering: false,
|
||||
results: {},
|
||||
results: [],
|
||||
fields: [],
|
||||
keyUsage: [],
|
||||
lastTable: null,
|
||||
@@ -86,7 +89,7 @@ export default {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
isSelected () {
|
||||
return this.workspace.selected_tab === 1;
|
||||
return this.workspace.selected_tab === 'data';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -115,12 +118,14 @@ export default {
|
||||
async getTableData () {
|
||||
if (!this.table) return;
|
||||
this.isQuering = true;
|
||||
this.results = {};
|
||||
this.results = [];
|
||||
const fieldsArr = [];
|
||||
const keysArr = [];
|
||||
this.setTabFields({ cUid: this.connection.uid, tUid: this.tabUid, fields: [] });
|
||||
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.workspace.breadcrumbs.schema,
|
||||
schema: this.schema,
|
||||
table: this.workspace.breadcrumbs.table
|
||||
};
|
||||
|
||||
@@ -128,7 +133,7 @@ export default {
|
||||
const { status, response } = await Tables.getTableColumns(params);
|
||||
if (status === 'success') {
|
||||
this.fields = response;// Needed to add new rows
|
||||
this.setTabFields({ cUid: this.connection.uid, tUid: this.tabUid, fields: response });
|
||||
fieldsArr.push(response);
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
@@ -141,7 +146,7 @@ export default {
|
||||
const { status, response } = await Tables.getTableData(params);
|
||||
|
||||
if (status === 'success')
|
||||
this.results = response;
|
||||
this.results = [response];
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
@@ -151,9 +156,10 @@ export default {
|
||||
|
||||
try { // Key usage (foreign keys)
|
||||
const { status, response } = await Tables.getKeyUsage(params);
|
||||
|
||||
if (status === 'success') {
|
||||
this.keyUsage = response;// Needed to add new rows
|
||||
this.setTabKeyUsage({ cUid: this.connection.uid, tUid: this.tabUid, keyUsage: response });
|
||||
keysArr.push(response);
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
@@ -162,8 +168,13 @@ export default {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.setTabFields({ cUid: this.connection.uid, tUid: this.tabUid, fields: fieldsArr });
|
||||
this.setTabKeyUsage({ cUid: this.connection.uid, tUid: this.tabUid, keyUsage: keysArr });
|
||||
this.isQuering = false;
|
||||
},
|
||||
getTable () {
|
||||
return this.table;
|
||||
},
|
||||
reloadTable () {
|
||||
this.getTableData();
|
||||
},
|
||||
|
87
src/renderer/i18n/ar-SA.js
Normal file
@@ -0,0 +1,87 @@
|
||||
module.exports = {
|
||||
word: {
|
||||
edit: 'تعديل',
|
||||
save: 'حفظ',
|
||||
close: 'إغلاق',
|
||||
delete: 'حفظ',
|
||||
confirm: 'تأكيد',
|
||||
cancel: 'إلغاء',
|
||||
send: 'إرسال',
|
||||
connectionName: 'إسم الإتصال',
|
||||
client: 'العميل',
|
||||
hostName: 'إسم المستضيف',
|
||||
port: 'المنفذ',
|
||||
user: 'المستخدم',
|
||||
password: 'الرقم السري',
|
||||
credentials: 'بيانات الدخول',
|
||||
connect: 'إتصال',
|
||||
connected: 'متصل',
|
||||
disconnect: 'إلغاء الإتصال',
|
||||
disconnected: 'غير متصل',
|
||||
refresh: 'تحديث',
|
||||
settings: 'الإعدادات',
|
||||
general: 'عام',
|
||||
themes: 'الأنماط',
|
||||
update: 'تحديث',
|
||||
about: 'حول',
|
||||
language: 'اللغة',
|
||||
version: 'النسخة',
|
||||
donate: 'إدعم',
|
||||
run: 'شغل',
|
||||
schema: 'Schema',
|
||||
results: 'النتائج',
|
||||
size: 'الحجم',
|
||||
seconds: 'ثواني',
|
||||
type: 'نوع',
|
||||
mimeType: 'نوع الميديا',
|
||||
download: 'تحميل',
|
||||
add: 'أضف',
|
||||
data: 'بيانات',
|
||||
properties: 'خصائص',
|
||||
insert: 'أدرج'
|
||||
},
|
||||
message: {
|
||||
appWelcome: 'مرحبا بك في عميل الSQL انتاريس!',
|
||||
appFirstStep: 'خطوتك الأولى قم بإنشاء إتصال جديد بقاعدة بيانات.',
|
||||
addConnection: 'إضافة إتصال',
|
||||
createConnection: 'إنشاء إتصال',
|
||||
createNewConnection: 'إنشاء إتصال جديد',
|
||||
askCredentials: 'إطلب بيانات الدخول',
|
||||
testConnection: 'إختبر الإتصال',
|
||||
editConnection: 'عدل الإتصال',
|
||||
deleteConnection: 'إحذف الإتصال',
|
||||
deleteCorfirm: 'هل أنت متأكد من حذف الإتصال؟',
|
||||
connectionSuccessfullyMade: 'تم الإتصال بنجاح!',
|
||||
madeWithJS: 'بني بـ 💛 و جافاسكربت!',
|
||||
checkForUpdates: 'تأكد من التحديثات',
|
||||
noUpdatesAvailable: 'لا توجد تحديثات',
|
||||
checkingForUpdate: 'البحث عن تحديثات',
|
||||
checkFailure: 'فشل البحث, نرجوا المحاولة في وقت لاحق',
|
||||
updateAvailable: 'تحديث جديد متوفر',
|
||||
downloadingUpdate: 'جاري تحميل التحديث',
|
||||
updateDownloaded: 'تم تحميل التحديث',
|
||||
restartToInstall: 'قم بإعادة تشغيل انتاريس للتحديث',
|
||||
unableEditFieldWithoutPrimary: 'لا يمكن تعديل الخانة بدون وجود مفتاح رئيسي في النتائج',
|
||||
editCell: 'تعديل الخلية',
|
||||
deleteRows: 'حذف صف | حذف {count} صفوف',
|
||||
confirmToDeleteRows: 'هل أنت متأكد من حذف صف واحد؟? | هل أنت متأكد من حذف {count} صف?',
|
||||
notificationsTimeout: 'إنتهاء التنبيهات',
|
||||
uploadFile: 'رفع ملف',
|
||||
addNewRow: 'إضافة صف جديد',
|
||||
numberOfInserts: 'عدد الإدراجات'
|
||||
},
|
||||
// Date and Time
|
||||
short: {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
},
|
||||
long: {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
weekday: 'short',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric'
|
||||
}
|
||||
};
|
@@ -38,7 +38,10 @@ module.exports = {
|
||||
add: 'Add',
|
||||
data: 'Data',
|
||||
properties: 'Properties',
|
||||
insert: 'Insert'
|
||||
insert: 'Insert',
|
||||
connecting: 'Connecting',
|
||||
name: 'Name',
|
||||
collation: 'Collation'
|
||||
},
|
||||
message: {
|
||||
appWelcome: 'Welcome to Antares SQL Client!',
|
||||
@@ -50,7 +53,7 @@ module.exports = {
|
||||
testConnection: 'Test connection',
|
||||
editConnection: 'Edit connection',
|
||||
deleteConnection: 'Delete connection',
|
||||
deleteConnectionCorfirm: 'Do you confirm the cancellation of',
|
||||
deleteCorfirm: 'Do you confirm the cancellation of',
|
||||
connectionSuccessfullyMade: 'Connection successfully made!',
|
||||
madeWithJS: 'Made with 💛 and JavaScript!',
|
||||
checkForUpdates: 'Check for updates',
|
||||
@@ -68,7 +71,14 @@ module.exports = {
|
||||
notificationsTimeout: 'Notifications timeout',
|
||||
uploadFile: 'Upload file',
|
||||
addNewRow: 'Add new row',
|
||||
numberOfInserts: 'Number of inserts'
|
||||
numberOfInserts: 'Number of inserts',
|
||||
openNewTab: 'Open a new tab',
|
||||
affectedRows: 'Affected rows',
|
||||
createNewDatabase: 'Create new Database',
|
||||
databaseName: 'Database name',
|
||||
serverDefault: 'Server default',
|
||||
deleteDatabase: 'Delete database',
|
||||
editDatabase: 'Edit database'
|
||||
},
|
||||
// Date and Time
|
||||
short: {
|
||||
|
90
src/renderer/i18n/es-ES.js
Normal file
@@ -0,0 +1,90 @@
|
||||
module.exports = {
|
||||
word: {
|
||||
edit: 'Editar',
|
||||
save: 'Guardar',
|
||||
close: 'Cerrar',
|
||||
delete: 'Eliminar',
|
||||
confirm: 'Confirmar',
|
||||
cancel: 'Cancelar',
|
||||
send: 'Enviar',
|
||||
connectionName: 'Nombre de la conexión',
|
||||
client: 'Cliente',
|
||||
hostName: 'Servidor',
|
||||
port: 'Puerto',
|
||||
user: 'Usuario',
|
||||
password: 'Contraseña',
|
||||
credentials: 'Credenciales',
|
||||
connect: 'Connectar',
|
||||
connected: 'Conectado',
|
||||
disconnect: 'Desconectar',
|
||||
disconnected: 'Desconectado',
|
||||
refresh: 'Refrescar',
|
||||
settings: 'Configuración',
|
||||
general: 'General',
|
||||
themes: 'Temas',
|
||||
update: 'Actualizar',
|
||||
about: 'Sobre',
|
||||
language: 'Idioma',
|
||||
version: 'Versión',
|
||||
donate: 'Donar',
|
||||
run: 'Ejecutar',
|
||||
schema: 'Esquema',
|
||||
results: 'Resultados',
|
||||
size: 'Tamaño',
|
||||
seconds: 'Segundos',
|
||||
type: 'Tipo',
|
||||
mimeType: 'Mime-Type',
|
||||
download: 'Descargar',
|
||||
add: 'Añadir',
|
||||
data: 'Datos',
|
||||
properties: 'Propiedades',
|
||||
insert: 'Insertar',
|
||||
connecting: 'Conectando'
|
||||
},
|
||||
message: {
|
||||
appWelcome: 'Bienvenido a Antares Cliente SQL!',
|
||||
appFirstStep: 'Primer paso: Crear una conexión a una Base de Datos.',
|
||||
addConnection: 'Añadir conexión',
|
||||
createConnection: 'Crear conexión',
|
||||
createNewConnection: 'Crear nueva conexión',
|
||||
askCredentials: 'Preguntar credenciales',
|
||||
testConnection: 'Comprobar conexión',
|
||||
editConnection: 'Editar conexión',
|
||||
deleteConnection: 'Eliminar conexión',
|
||||
deleteCorfirm: 'Confirmas la cancelación de',
|
||||
connectionSuccessfullyMade: 'Conexión realizada correctamente!',
|
||||
madeWithJS: 'Hecho con 💛 y JavaScript!',
|
||||
checkForUpdates: 'Comprobar actualizaciones',
|
||||
noUpdatesAvailable: 'No hay actualizaciones',
|
||||
checkingForUpdate: 'Comprobando actualizaciones',
|
||||
checkFailure: 'Error en la comprobación, por favor pruebe más tarde',
|
||||
updateAvailable: 'Actualización disponible',
|
||||
downloadingUpdate: 'Descargando actualización',
|
||||
updateDownloaded: 'Descargada actualización',
|
||||
restartToInstall: 'Reiniciar Antares para instalar',
|
||||
unableEditFieldWithoutPrimary: 'No se puede editar una campo sin Llave Primaria en el registro',
|
||||
editCell: 'Editar celda',
|
||||
deleteRows: 'Eliminar fila | Eliminar {count} filas',
|
||||
confirmToDeleteRows: '¿Quiere realmente eliminar una fila? | ¿Quiere realmente eliminar {count} filas?',
|
||||
notificationsTimeout: 'Tiempo de espera',
|
||||
uploadFile: 'Cargar fichero',
|
||||
addNewRow: 'Añadir nueva fila',
|
||||
numberOfInserts: 'Numero de inserciones',
|
||||
openNewTab: 'Abrir nueva pestaña',
|
||||
affectedRows: 'Filas afectadas'
|
||||
},
|
||||
// Date and Time
|
||||
short: {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
},
|
||||
long: {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
weekday: 'short',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric'
|
||||
}
|
||||
};
|
@@ -6,7 +6,9 @@ Vue.use(VueI18n);
|
||||
const i18n = new VueI18n({
|
||||
messages: {
|
||||
'en-US': require('./en-US'),
|
||||
'it-IT': require('./it-IT')
|
||||
'it-IT': require('./it-IT'),
|
||||
'ar-SA': require('./ar-SA'),
|
||||
'es-ES': require('./es-ES')
|
||||
}
|
||||
});
|
||||
export default i18n;
|
||||
|
@@ -29,7 +29,17 @@ module.exports = {
|
||||
donate: 'Dona',
|
||||
run: 'Esegui',
|
||||
schema: 'Schema',
|
||||
results: 'Results'
|
||||
results: 'Risultati',
|
||||
size: 'Dimensioni',
|
||||
seconds: 'Secondi',
|
||||
type: 'Tipo',
|
||||
mimeType: 'Mime-Type',
|
||||
download: 'Scarica',
|
||||
add: 'Aggiungi',
|
||||
data: 'Dati',
|
||||
properties: 'Proprietà',
|
||||
insert: 'Inserisci',
|
||||
connecting: 'Connessione in corso'
|
||||
},
|
||||
message: {
|
||||
appWelcome: 'Benvenuto in Antares SQL Client!',
|
||||
@@ -41,7 +51,7 @@ module.exports = {
|
||||
testConnection: 'Testa connessione',
|
||||
editConnection: 'Modifica connessione',
|
||||
deleteConnection: 'Elimina connessione',
|
||||
deleteConnectionCorfirm: 'Confermi l\'eliminazione di',
|
||||
deleteCorfirm: 'Confermi l\'eliminazione di',
|
||||
connectionSuccessfullyMade: 'Connessione avvenuta con successo!',
|
||||
madeWithJS: 'Fatto con 💛 e JavaScript!',
|
||||
checkForUpdates: 'Cerca aggiornamenti',
|
||||
@@ -55,7 +65,13 @@ module.exports = {
|
||||
unableEditFieldWithoutPrimary: 'Impossibile modificare il campo senza una primary key nel resultset',
|
||||
editCell: 'Modifica cella',
|
||||
deleteRows: 'Elimina riga | Elimina {count} righe',
|
||||
confirmToDeleteRows: 'Confermi di voler cancellare una riga? | Confermi di voler cancellare {count} righe?'
|
||||
confirmToDeleteRows: 'Confermi di voler cancellare una riga? | Confermi di voler cancellare {count} righe?',
|
||||
notificationsTimeout: 'Timeout Notifiche',
|
||||
uploadFile: 'Carica file',
|
||||
addNewRow: 'Aggiungi nuova riga',
|
||||
numberOfInserts: 'Numero di insert',
|
||||
openNewTab: 'Apri nuova scheda',
|
||||
affectedRows: 'Righe interessate'
|
||||
},
|
||||
// Date and Time
|
||||
short: {
|
||||
|
@@ -1,4 +1,6 @@
|
||||
export default {
|
||||
'en-US': 'English',
|
||||
'it-IT': 'Italiano'
|
||||
'it-IT': 'Italiano',
|
||||
'ar-SA': 'العربية',
|
||||
'es-ES': 'Español'
|
||||
};
|
||||
|
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 19 KiB |
@@ -1 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 210 210" height="210mm" width="210mm"><path d="M180.088 107.51a72.967 69.474 0 01-72.858 69.473 72.967 69.474 0 01-73.075-69.266 72.967 69.474 0 0172.639-69.68 72.967 69.474 0 0173.292 69.056" fill="#e36929"/><path d="M82.985 102.48a17.02 21.735 21.455 01-23.767 14.012 17.02 21.735 21.455 01-7.938-26.403 17.02 21.735 21.455 0123.744-14.091 17.02 21.735 21.455 018.009 26.36M157.83 118.562a17.472 11.51-52.488 01-1.695 20.917 17.472 11.51-52.488 01-19.742 6.678 17.472 11.51-52.488 011.635-20.897 17.472 11.51-52.488 0119.747-6.74" fill="#f8d163"/><path d="M72.979 98.717a9.143 10.562 0 01-9.13 10.562 9.143 10.562 0 01-9.156-10.53 9.143 10.562 0 019.102-10.594 9.143 10.562 0 019.184 10.499M155.659 130.732a6.21 6.986 0 01-6.2 6.987 6.21 6.986 0 01-6.22-6.966 6.21 6.986 0 016.182-7.007 6.21 6.986 0 016.238 6.945" fill="#fbfcf7"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 210 210">
|
||||
<defs/>
|
||||
<path fill="#e36929" d="M180.088 107.51a72.967 69.474 0 01-72.858 69.473 72.967 69.474 0 01-73.075-69.266 72.967 69.474 0 0172.639-69.68 72.967 69.474 0 0173.292 69.056"/>
|
||||
<path fill="#f8d163" d="M82.985 102.48a17.02 21.735 21.455 01-23.767 14.012 17.02 21.735 21.455 01-7.938-26.403 17.02 21.735 21.455 0123.744-14.091 17.02 21.735 21.455 018.009 26.36m74.797 16.204a17.472 11.51-52.488 01-1.695 20.917 17.472 11.51-52.488 01-19.742 6.678 17.472 11.51-52.488 011.635-20.897 17.472 11.51-52.488 0119.747-6.74"/>
|
||||
<path fill="#fbfcf7" d="M72.979 98.717a9.143 10.562 0 01-9.13 10.562 9.143 10.562 0 01-9.156-10.53 9.143 10.562 0 019.102-10.594 9.143 10.562 0 019.184 10.499m82.68 32.078a6.21 6.986 0 01-6.2 6.987 6.21 6.986 0 01-6.22-6.966 6.21 6.986 0 016.182-7.007 6.21 6.986 0 016.238 6.945"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 892 B After Width: | Height: | Size: 880 B |
@@ -3,11 +3,11 @@ import { ipcRenderer } from 'electron';
|
||||
|
||||
export default class {
|
||||
static makeTest (params) {
|
||||
return ipcRenderer.invoke('testConnection', params);
|
||||
return ipcRenderer.invoke('test-connection', params);
|
||||
}
|
||||
|
||||
static checkConnection (params) {
|
||||
return ipcRenderer.invoke('checkConnection', params);
|
||||
return ipcRenderer.invoke('check-connection', params);
|
||||
}
|
||||
|
||||
static connect (params) {
|
||||
@@ -17,12 +17,4 @@ export default class {
|
||||
static disconnect (uid) {
|
||||
return ipcRenderer.invoke('disconnect', uid);
|
||||
}
|
||||
|
||||
static refresh (uid) {
|
||||
return ipcRenderer.invoke('refresh', uid);
|
||||
}
|
||||
|
||||
static rawQuery (params) {
|
||||
return ipcRenderer.invoke('rawQuery', params);
|
||||
}
|
||||
}
|
||||
|
40
src/renderer/ipc-api/Database.js
Normal file
@@ -0,0 +1,40 @@
|
||||
'use strict';
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
export default class {
|
||||
static createDatabase (params) {
|
||||
return ipcRenderer.invoke('create-database', params);
|
||||
}
|
||||
|
||||
static updateDatabase (params) {
|
||||
return ipcRenderer.invoke('update-database', params);
|
||||
}
|
||||
|
||||
static getDatabaseCollation (params) {
|
||||
return ipcRenderer.invoke('get-database-collation', params);
|
||||
}
|
||||
|
||||
static deleteDatabase (params) {
|
||||
return ipcRenderer.invoke('delete-database', params);
|
||||
}
|
||||
|
||||
static getStructure (uid) {
|
||||
return ipcRenderer.invoke('get-structure', uid);
|
||||
}
|
||||
|
||||
static getCollations (uid) {
|
||||
return ipcRenderer.invoke('get-collations', uid);
|
||||
}
|
||||
|
||||
static getVariables (uid) {
|
||||
return ipcRenderer.invoke('get-variables', uid);
|
||||
}
|
||||
|
||||
static useSchema (params) {
|
||||
return ipcRenderer.invoke('use-schema', params);
|
||||
}
|
||||
|
||||
static rawQuery (params) {
|
||||
return ipcRenderer.invoke('raw-query', params);
|
||||
}
|
||||
}
|
@@ -3,11 +3,11 @@ import { ipcRenderer } from 'electron';
|
||||
|
||||
export default class {
|
||||
static getTableColumns (params) {
|
||||
return ipcRenderer.invoke('getTableColumns', params);
|
||||
return ipcRenderer.invoke('get-table-columns', params);
|
||||
}
|
||||
|
||||
static getTableData (params) {
|
||||
return ipcRenderer.invoke('getTableData', params);
|
||||
return ipcRenderer.invoke('get-table-data', params);
|
||||
}
|
||||
|
||||
static getKeyUsage (params) {
|
||||
@@ -15,15 +15,15 @@ export default class {
|
||||
}
|
||||
|
||||
static updateTableCell (params) {
|
||||
return ipcRenderer.invoke('updateTableCell', params);
|
||||
return ipcRenderer.invoke('update-table-cell', params);
|
||||
}
|
||||
|
||||
static deleteTableRows (params) {
|
||||
return ipcRenderer.invoke('deleteTableRows', params);
|
||||
return ipcRenderer.invoke('delete-table-rows', params);
|
||||
}
|
||||
|
||||
static insertTableRows (params) {
|
||||
return ipcRenderer.invoke('insertTableRows', params);
|
||||
return ipcRenderer.invoke('insert-table-rows', params);
|
||||
}
|
||||
|
||||
static getForeignList (params) {
|
||||
|
@@ -1,14 +1,17 @@
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async updateField (payload) {
|
||||
this.isQuering = true;
|
||||
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.workspace.breadcrumbs.schema,
|
||||
table: this.table,
|
||||
...payload
|
||||
};
|
||||
|
||||
@@ -34,18 +37,14 @@ export default {
|
||||
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.workspace.breadcrumbs.schema,
|
||||
table: this.workspace.breadcrumbs.table,
|
||||
...payload
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Tables.deleteTableRows(params);
|
||||
if (status === 'success') {
|
||||
const { primary, rows } = params;
|
||||
this.results = { ...this.results, rows: this.results.rows.filter(row => !rows.includes(row[primary])) };
|
||||
this.$refs.queryTable.refreshScroller();// Necessary to re-render virtual scroller
|
||||
}
|
||||
|
||||
if (status === 'success')
|
||||
this.reloadTable();
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
|
@@ -33,6 +33,8 @@
|
||||
"blob": darkorchid,
|
||||
"mediumblob": darkorchid,
|
||||
"longblob": darkorchid,
|
||||
"enum": gold,
|
||||
"set": gold,
|
||||
"unknown": gray,
|
||||
)
|
||||
);
|
||||
|
@@ -106,6 +106,13 @@ body {
|
||||
|
||||
.tab {
|
||||
border-color: #272727;
|
||||
|
||||
.tab-item {
|
||||
.btn-clear {
|
||||
margin-top: -0.1rem;
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.panel {
|
||||
@@ -126,12 +133,12 @@ body {
|
||||
}
|
||||
|
||||
.form-select,
|
||||
.form-select:not([multiple]):not([size]),
|
||||
.form-input,
|
||||
.form-select:not([multiple]):not([size]),
|
||||
.form-checkbox .form-icon,
|
||||
.form-radio .form-icon {
|
||||
border-color: $bg-color-light;
|
||||
background: $bg-color-gray;
|
||||
background-color: $bg-color-gray;
|
||||
}
|
||||
|
||||
.form-input:not(:placeholder-shown):invalid:focus {
|
||||
|
@@ -3,7 +3,7 @@ export default {
|
||||
namespaced: true,
|
||||
strict: true,
|
||||
state: {
|
||||
app_name: 'Antares - Database Client',
|
||||
app_name: 'Antares - SQL Client',
|
||||
app_version: process.env.PACKAGE_VERSION || 0,
|
||||
is_loading: false,
|
||||
is_new_modal: false,
|
||||
|
@@ -7,7 +7,7 @@ export default {
|
||||
state: {
|
||||
locale: 'en-US',
|
||||
explorebar_size: null,
|
||||
notifications_timeout: 10
|
||||
notifications_timeout: 5
|
||||
},
|
||||
getters: {
|
||||
getLocale: state => state.locale,
|
||||
|
@@ -1,18 +1,9 @@
|
||||
'use strict';
|
||||
import Connection from '@/ipc-api/Connection';
|
||||
import Database from '@/ipc-api/Database';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
|
||||
function remapStructure (structure) { // TODO: move to main process and add fields (for autocomplete purpose)
|
||||
const databases = structure.map(table => table.TABLE_SCHEMA)
|
||||
.filter((value, index, self) => self.indexOf(value) === index);
|
||||
|
||||
return databases.map(db => {
|
||||
return {
|
||||
name: db,
|
||||
tables: structure.filter(table => table.TABLE_SCHEMA === db)
|
||||
};
|
||||
});
|
||||
}
|
||||
const tabIndex = [];
|
||||
let lastSchema = '';
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
@@ -30,6 +21,9 @@ export default {
|
||||
getWorkspace: state => uid => {
|
||||
return state.workspaces.find(workspace => workspace.uid === uid);
|
||||
},
|
||||
getDatabaseVariable: state => (uid, name) => {
|
||||
return state.workspaces.find(workspace => workspace.uid === uid).variables.find(variable => variable.name === name);
|
||||
},
|
||||
getWorkspaceTab: (state, getters) => tUid => {
|
||||
if (!getters.getSelected) return;
|
||||
const workspace = state.workspaces.find(workspace => workspace.uid === getters.getSelected);
|
||||
@@ -56,6 +50,12 @@ export default {
|
||||
REFRESH_STRUCTURE (state, { uid, structure }) {
|
||||
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid ? { ...workspace, structure } : workspace);
|
||||
},
|
||||
REFRESH_COLLATIONS (state, { uid, collations }) {
|
||||
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid ? { ...workspace, collations } : workspace);
|
||||
},
|
||||
REFRESH_VARIABLES (state, { uid, variables }) {
|
||||
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid ? { ...workspace, variables } : workspace);
|
||||
},
|
||||
ADD_WORKSPACE (state, workspace) {
|
||||
state.workspaces.push(workspace);
|
||||
},
|
||||
@@ -63,8 +63,11 @@ export default {
|
||||
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid ? { ...workspace, breadcrumbs } : workspace);
|
||||
},
|
||||
NEW_TAB (state, uid) {
|
||||
tabIndex[uid] = tabIndex[uid] ? ++tabIndex[uid] : 1;
|
||||
|
||||
const newTab = {
|
||||
uid: uidGen('T'),
|
||||
index: tabIndex[uid],
|
||||
selected: false,
|
||||
type: 'query',
|
||||
fields: [],
|
||||
@@ -81,6 +84,18 @@ export default {
|
||||
return workspace;
|
||||
});
|
||||
},
|
||||
REMOVE_TAB (state, { uid, tab: tUid }) {
|
||||
state.workspaces = state.workspaces.map(workspace => {
|
||||
if (workspace.uid === uid) {
|
||||
return {
|
||||
...workspace,
|
||||
tabs: workspace.tabs.filter(tab => tab.uid !== tUid)
|
||||
};
|
||||
}
|
||||
else
|
||||
return workspace;
|
||||
});
|
||||
},
|
||||
SELECT_TAB (state, { uid, tab }) {
|
||||
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid ? { ...workspace, selected_tab: tab } : workspace);
|
||||
},
|
||||
@@ -120,7 +135,7 @@ export default {
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
selectWorkspace ({ commit }, uid) {
|
||||
selectWorkspace ({ commit, dispatch }, uid) {
|
||||
commit('SELECT_WORKSPACE', uid);
|
||||
},
|
||||
async connectWorkspace ({ dispatch, commit }, connection) {
|
||||
@@ -128,8 +143,11 @@ export default {
|
||||
const { status, response } = await Connection.connect(connection);
|
||||
if (status === 'error')
|
||||
dispatch('notifications/addNotification', { status, message: response }, { root: true });
|
||||
else
|
||||
commit('ADD_CONNECTED', { uid: connection.uid, structure: remapStructure(response) });
|
||||
else {
|
||||
commit('ADD_CONNECTED', { uid: connection.uid, structure: response });
|
||||
dispatch('refreshCollations', connection.uid);
|
||||
dispatch('refreshVariables', connection.uid);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
dispatch('notifications/addNotification', { status: 'error', message: err.stack }, { root: true });
|
||||
@@ -137,11 +155,35 @@ export default {
|
||||
},
|
||||
async refreshStructure ({ dispatch, commit }, uid) {
|
||||
try {
|
||||
const { status, response } = await Connection.refresh(uid);
|
||||
const { status, response } = await Database.getStructure(uid);
|
||||
if (status === 'error')
|
||||
dispatch('notifications/addNotification', { status, message: response }, { root: true });
|
||||
else
|
||||
commit('REFRESH_STRUCTURE', { uid, structure: remapStructure(response) });
|
||||
commit('REFRESH_STRUCTURE', { uid, structure: response });
|
||||
}
|
||||
catch (err) {
|
||||
dispatch('notifications/addNotification', { status: 'error', message: err.stack }, { root: true });
|
||||
}
|
||||
},
|
||||
async refreshCollations ({ dispatch, commit }, uid) {
|
||||
try {
|
||||
const { status, response } = await Database.getCollations(uid);
|
||||
if (status === 'error')
|
||||
dispatch('notifications/addNotification', { status, message: response }, { root: true });
|
||||
else
|
||||
commit('REFRESH_COLLATIONS', { uid, collations: response });
|
||||
}
|
||||
catch (err) {
|
||||
dispatch('notifications/addNotification', { status: 'error', message: err.stack }, { root: true });
|
||||
}
|
||||
},
|
||||
async refreshVariables ({ dispatch, commit }, uid) {
|
||||
try {
|
||||
const { status, response } = await Database.getVariables(uid);
|
||||
if (status === 'error')
|
||||
dispatch('notifications/addNotification', { status, message: response }, { root: true });
|
||||
else
|
||||
commit('REFRESH_VARIABLES', { uid, variables: response });
|
||||
}
|
||||
catch (err) {
|
||||
dispatch('notifications/addNotification', { status: 'error', message: err.stack }, { root: true });
|
||||
@@ -158,26 +200,42 @@ export default {
|
||||
connected: false,
|
||||
selected_tab: 0,
|
||||
tabs: [{
|
||||
uid: 1,
|
||||
uid: 'data',
|
||||
type: 'table',
|
||||
fields: [],
|
||||
keyUsage: []
|
||||
},
|
||||
{
|
||||
uid: 'prop',
|
||||
type: 'table',
|
||||
fields: [],
|
||||
keyUsage: []
|
||||
}],
|
||||
structure: {},
|
||||
variables: [],
|
||||
collations: [],
|
||||
breadcrumbs: {}
|
||||
};
|
||||
|
||||
commit('ADD_WORKSPACE', workspace);
|
||||
|
||||
if (getters.getWorkspace(uid).tabs.length < 2)
|
||||
if (getters.getWorkspace(uid).tabs.length < 3)
|
||||
dispatch('newTab', uid);
|
||||
},
|
||||
changeBreadcrumbs ({ commit, getters }, payload) {
|
||||
if (lastSchema !== payload.schema) {
|
||||
Database.useSchema({ uid: getters.getSelected, schema: payload.schema });
|
||||
lastSchema = payload.schema;
|
||||
}
|
||||
|
||||
commit('CHANGE_BREADCRUMBS', { uid: getters.getSelected, breadcrumbs: payload });
|
||||
},
|
||||
newTab ({ commit }, uid) {
|
||||
commit('NEW_TAB', uid);
|
||||
},
|
||||
removeTab ({ commit }, payload) {
|
||||
commit('REMOVE_TAB', payload);
|
||||
},
|
||||
selectTab ({ commit }, payload) {
|
||||
commit('SELECT_TAB', payload);
|
||||
},
|
||||
|
@@ -1,23 +1,23 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
export default store => {
|
||||
ipcRenderer.on('checkingForUpdate', () => {
|
||||
ipcRenderer.on('checking-for-update', () => {
|
||||
store.commit('application/CHANGE_UPDATE_STATUS', 'checking');
|
||||
});
|
||||
ipcRenderer.on('updateAvailable', () => {
|
||||
ipcRenderer.on('update-available', () => {
|
||||
store.commit('application/CHANGE_UPDATE_STATUS', 'available');
|
||||
});
|
||||
ipcRenderer.on('updateNotAvailable', () => {
|
||||
ipcRenderer.on('update-not-available', () => {
|
||||
store.commit('application/CHANGE_UPDATE_STATUS', 'noupdate');
|
||||
});
|
||||
ipcRenderer.on('checkFailed', () => {
|
||||
ipcRenderer.on('check-failed', () => {
|
||||
store.commit('application/CHANGE_UPDATE_STATUS', 'nocheck');
|
||||
});
|
||||
ipcRenderer.on('downloadProgress', (event, data) => {
|
||||
ipcRenderer.on('download-progress', (event, data) => {
|
||||
store.commit('application/CHANGE_UPDATE_STATUS', 'downloading');
|
||||
store.commit('application/CHANGE_PROGRESS_PERCENTAGE', data.percent);
|
||||
});
|
||||
ipcRenderer.on('updateDownloaded', () => {
|
||||
ipcRenderer.on('update-downloaded', () => {
|
||||
store.commit('application/CHANGE_UPDATE_STATUS', 'downloaded');
|
||||
});
|
||||
};
|
||||
|
12
src/renderer/suggestions/sql.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { functions } from '@/suggestions/sql/sql-functions';
|
||||
import { keywords } from '@/suggestions/sql/sql-keywords';
|
||||
import { operators } from '@/suggestions/sql/sql-operators';
|
||||
import { variables } from '@/suggestions/sql/sql-variables';
|
||||
|
||||
export const completionItemProvider = (monaco) => {
|
||||
return {
|
||||
provideCompletionItems () {
|
||||
return { suggestions: [...functions(monaco), ...keywords(monaco), ...operators(monaco), ...variables(monaco)] };
|
||||
}
|
||||
};
|
||||
};
|
1267
src/renderer/suggestions/sql/sql-functions.js
Normal file
4547
src/renderer/suggestions/sql/sql-keywords.js
Normal file
142
src/renderer/suggestions/sql/sql-operators.js
Normal file
@@ -0,0 +1,142 @@
|
||||
export const operators = (monaco) => {
|
||||
return [{
|
||||
label: 'ALL',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'ALL'
|
||||
},
|
||||
{
|
||||
label: 'AND',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'AND'
|
||||
},
|
||||
{
|
||||
label: 'ANY',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'ANY'
|
||||
},
|
||||
{
|
||||
label: 'BETWEEN',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'BETWEEN'
|
||||
},
|
||||
{
|
||||
label: 'EXISTS',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'EXISTS'
|
||||
},
|
||||
{
|
||||
label: 'IN',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'IN'
|
||||
},
|
||||
{
|
||||
label: 'LIKE',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'LIKE'
|
||||
},
|
||||
{
|
||||
label: 'NOT',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'NOT'
|
||||
},
|
||||
{
|
||||
label: 'OR',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'OR'
|
||||
},
|
||||
{
|
||||
label: 'SOME',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'SOME'
|
||||
},
|
||||
{
|
||||
label: 'EXCEPT',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'EXCEPT'
|
||||
},
|
||||
{
|
||||
label: 'INTERSECT',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'INTERSECT'
|
||||
},
|
||||
{
|
||||
label: 'UNION',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'UNION'
|
||||
},
|
||||
{
|
||||
label: 'APPLY',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'APPLY'
|
||||
},
|
||||
{
|
||||
label: 'CROSS',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'CROSS'
|
||||
},
|
||||
{
|
||||
label: 'FULL',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'FULL'
|
||||
},
|
||||
{
|
||||
label: 'INNER',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'INNER'
|
||||
},
|
||||
{
|
||||
label: 'JOIN',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'JOIN'
|
||||
},
|
||||
{
|
||||
label: 'LEFT',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'LEFT'
|
||||
},
|
||||
{
|
||||
label: 'OUTER',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'OUTER'
|
||||
},
|
||||
{
|
||||
label: 'RIGHT',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'RIGHT'
|
||||
},
|
||||
{
|
||||
label: 'CONTAINS',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'CONTAINS'
|
||||
},
|
||||
{
|
||||
label: 'FREETEXT',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'FREETEXT'
|
||||
},
|
||||
{
|
||||
label: 'IS',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'IS'
|
||||
},
|
||||
{
|
||||
label: 'NULL',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'NULL'
|
||||
},
|
||||
{
|
||||
label: 'PIVOT',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'PIVOT'
|
||||
},
|
||||
{
|
||||
label: 'UNPIVOT',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'UNPIVOT'
|
||||
},
|
||||
{
|
||||
label: 'MATCHED',
|
||||
kind: monaco.languages.CompletionItemKind.Operator,
|
||||
insertText: 'MATCHED'
|
||||
}];
|
||||
};
|
172
src/renderer/suggestions/sql/sql-variables.js
Normal file
@@ -0,0 +1,172 @@
|
||||
export const variables = (monaco) => {
|
||||
return [{
|
||||
label: '@@DATEFIRST',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@DATEFIRST'
|
||||
},
|
||||
{
|
||||
label: '@@DBTS',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@DBTS'
|
||||
},
|
||||
{
|
||||
label: '@@LANGID',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@LANGID'
|
||||
},
|
||||
{
|
||||
label: '@@LANGUAGE',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@LANGUAGE'
|
||||
},
|
||||
{
|
||||
label: '@@LOCK_TIMEOUT',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@LOCK_TIMEOUT'
|
||||
},
|
||||
{
|
||||
label: '@@MAX_CONNECTIONS',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@MAX_CONNECTIONS'
|
||||
},
|
||||
{
|
||||
label: '@@MAX_PRECISION',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@MAX_PRECISION'
|
||||
},
|
||||
{
|
||||
label: '@@NESTLEVEL',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@NESTLEVEL'
|
||||
},
|
||||
{
|
||||
label: '@@OPTIONS',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@OPTIONS'
|
||||
},
|
||||
{
|
||||
label: '@@REMSERVER',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@REMSERVER'
|
||||
},
|
||||
{
|
||||
label: '@@SERVERNAME',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@SERVERNAME'
|
||||
},
|
||||
{
|
||||
label: '@@SERVICENAME',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@SERVICENAME'
|
||||
},
|
||||
{
|
||||
label: '@@SPID',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@SPID'
|
||||
},
|
||||
{
|
||||
label: '@@TEXTSIZE',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@TEXTSIZE'
|
||||
},
|
||||
{
|
||||
label: '@@VERSION',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@VERSION'
|
||||
},
|
||||
{
|
||||
label: '@@CURSOR_ROWS',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@CURSOR_ROWS'
|
||||
},
|
||||
{
|
||||
label: '@@FETCH_STATUS',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@FETCH_STATUS'
|
||||
},
|
||||
{
|
||||
label: '@@DATEFIRST',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@DATEFIRST'
|
||||
},
|
||||
{
|
||||
label: '@@PROCID',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@PROCID'
|
||||
},
|
||||
{
|
||||
label: '@@ERROR',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@ERROR'
|
||||
},
|
||||
{
|
||||
label: '@@IDENTITY',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@IDENTITY'
|
||||
},
|
||||
{
|
||||
label: '@@ROWCOUNT',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@ROWCOUNT'
|
||||
},
|
||||
{
|
||||
label: '@@TRANCOUNT',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@TRANCOUNT'
|
||||
},
|
||||
{
|
||||
label: '@@CONNECTIONS',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@CONNECTIONS'
|
||||
},
|
||||
{
|
||||
label: '@@CPU_BUSY',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@CPU_BUSY'
|
||||
},
|
||||
{
|
||||
label: '@@IDLE',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@IDLE'
|
||||
},
|
||||
{
|
||||
label: '@@IO_BUSY',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@IO_BUSY'
|
||||
},
|
||||
{
|
||||
label: '@@PACKET_ERRORS',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@PACKET_ERRORS'
|
||||
},
|
||||
{
|
||||
label: '@@PACK_RECEIVED',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@PACK_RECEIVED'
|
||||
},
|
||||
{
|
||||
label: '@@PACK_SENT',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@PACK_SENT'
|
||||
},
|
||||
{
|
||||
label: '@@TIMETICKS',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@TIMETICKS'
|
||||
},
|
||||
{
|
||||
label: '@@TOTAL_ERRORS',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@TOTAL_ERRORS'
|
||||
},
|
||||
{
|
||||
label: '@@TOTAL_READ',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@TOTAL_READ'
|
||||
},
|
||||
{
|
||||
label: '@@TOTAL_WRITE',
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: '@@TOTAL_WRITE'
|
||||
}];
|
||||
};
|
@@ -1,8 +1,12 @@
|
||||
|
||||
const webpack = require('webpack');
|
||||
const MonacoEditorPlugin = require('monaco-editor-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
stats: 'errors-warnings',
|
||||
plugins: [
|
||||
new MonacoEditorPlugin({
|
||||
languages: ['sql']
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
PACKAGE_VERSION: JSON.stringify(require('./package.json').version)
|
||||
|