Compare commits
78 Commits
Author | SHA1 | Date | |
---|---|---|---|
60e5556a3e | |||
b3f10220b3 | |||
d19f475fc2 | |||
795db96319 | |||
b5fee79e90 | |||
|
5532ddbda9 | ||
3369d3dc2d | |||
fd25f881f9 | |||
5ca3a22dc5 | |||
5c668249cf | |||
39b9a59143 | |||
534659f9ae | |||
c00fd1381f | |||
17c6686163 | |||
bc0c5a76ba | |||
2b16c0ece4 | |||
|
ba1416dce2 | ||
13adf0a767 | |||
cacab55f55 | |||
|
3189d625e3 | ||
|
f71ed39b88 | ||
729aa9781a | |||
cd1ebacf89 | |||
a08074b446 | |||
0cd182546b | |||
89fdd210ca | |||
|
e2cf6e8c21 | ||
|
430490ad93 | ||
|
a35566f273 | ||
3679121c25 | |||
|
7657d05edf | ||
|
1ddf8f0dbe | ||
9506bb862a | |||
4cfab365c2 | |||
8d0aa73d1e | |||
30b487c37f | |||
aef17be36c | |||
ea65d8eee7 | |||
|
7dc33c78aa | ||
|
69cd083054 | ||
|
91788054e6 | ||
|
968a67ce3d | ||
f9ee7d0450 | |||
|
0e15c39797 | ||
adf407c1ba | |||
8a86344484 | |||
d2d0c3ca41 | |||
|
9046b858b1 | ||
46987faea8 | |||
|
dd25827e40 | ||
4069ade36d | |||
c8594c0549 | |||
7359c3b5bd | |||
4195b8416f | |||
9407a29922 | |||
2fcd080bd4 | |||
26446fb7ed | |||
2480c76a08 | |||
1f0ec57789 | |||
|
76e5849c78 | ||
db1641b74f | |||
165c54f663 | |||
|
a5ca3ea204 | ||
|
add95292ad | ||
7a63608f54 | |||
7ea7b369ab | |||
258fffa958 | |||
0849c5131f | |||
|
cce59d0ca8 | ||
94a53fec6c | |||
13aa47cd44 | |||
85f625daf7 | |||
7de3bb9346 | |||
e43a0ba0b4 | |||
|
638cd4bfb7 | ||
3959333662 | |||
abd46aa322 | |||
d4888ad8fb |
117
.all-contributorsrc
Normal file
@@ -0,0 +1,117 @@
|
||||
{
|
||||
"projectName": "Antares",
|
||||
"projectOwner": "Fabio286",
|
||||
"repoType": "github",
|
||||
"repoHost": "https://github.com",
|
||||
"files": [
|
||||
"README.md"
|
||||
],
|
||||
"imageSize": 100,
|
||||
"commit": false,
|
||||
"commitConvention": "angular",
|
||||
"contributors": [
|
||||
{
|
||||
"login": "Fabio286",
|
||||
"name": "Fabio Di Stasio",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/31471771?v=4",
|
||||
"profile": "https://fabiodistasio.it/",
|
||||
"contributions": [
|
||||
"code",
|
||||
"translation",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "toriphes",
|
||||
"name": "Giulio Ganci",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/4192159?v=4",
|
||||
"profile": "https://www.linkedin.com/in/giulioganci/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "digitalgopnik",
|
||||
"name": "Christian Ratz",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/2630316?v=4",
|
||||
"profile": "https://christianratz.de/",
|
||||
"contributions": [
|
||||
"code",
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "reverb6821",
|
||||
"name": "Giuseppe Gigliotti",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/55198803?v=4",
|
||||
"profile": "https://reverb6821.github.io/",
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Mohd-PH",
|
||||
"name": "Mohd-PH",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/9362157?v=4",
|
||||
"profile": "https://github.com/Mohd-PH",
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "hongkfui",
|
||||
"name": "hongkfui",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/37477191?v=4",
|
||||
"profile": "https://github.com/hongkfui",
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "MrAnyx",
|
||||
"name": "Robin",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/44176707?v=4",
|
||||
"profile": "https://github.com/MrAnyx",
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "daeleduardo",
|
||||
"name": "Daniel Eduardo",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/8599078?v=4",
|
||||
"profile": "https://github.com/daeleduardo",
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "datlechin",
|
||||
"name": "Ngô Quốc Đạt",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/56961917?v=4",
|
||||
"profile": "https://ngoquocdat.com/",
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "IsamuSugi",
|
||||
"name": "Isamu Sugiura",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7746658?v=4",
|
||||
"profile": "https://github.com/IsamuSugi",
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Occhioverde",
|
||||
"name": "Riccardo Sacchetto",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/18429412?v=4",
|
||||
"profile": "http://rsacchetto.nexxontech.it/",
|
||||
"contributions": [
|
||||
"platform"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
/node_modules
|
||||
/assets/vendor
|
||||
/out
|
||||
/dist
|
||||
node_modules
|
||||
assets
|
||||
out
|
||||
dist
|
@@ -45,7 +45,9 @@
|
||||
"no-console": "off",
|
||||
"no-undef": "off",
|
||||
"vue/no-side-effects-in-computed-properties": "off",
|
||||
"vue/multi-word-component-names": "off",
|
||||
"vue/require-default-prop": "off",
|
||||
"vue/comment-directive": "off",
|
||||
"vue/no-v-html": "off",
|
||||
"vue/html-indent": [
|
||||
"error",
|
||||
@@ -60,10 +62,11 @@
|
||||
"vue/max-attributes-per-line": [
|
||||
"error",
|
||||
{
|
||||
"singleline": 2,
|
||||
"singleline": {
|
||||
"max": 2
|
||||
},
|
||||
"multiline": {
|
||||
"max": 1,
|
||||
"allowFirstLine": false
|
||||
"max": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
|
7
.gitignore
vendored
@@ -1,9 +1,8 @@
|
||||
.DS_Store
|
||||
dist/
|
||||
node_modules/
|
||||
dist
|
||||
build
|
||||
node_modules
|
||||
thumbs.db
|
||||
.idea/
|
||||
.vscode
|
||||
NOTES.md
|
||||
*.txt
|
||||
package-lock.json
|
29
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Electron: Main",
|
||||
"port": 9222,
|
||||
"protocol": "inspector",
|
||||
"request": "attach",
|
||||
"sourceMaps": true,
|
||||
"type": "node",
|
||||
"timeout": 1000000
|
||||
},
|
||||
{
|
||||
"name": "Electron: Renderer",
|
||||
"port": 9223,
|
||||
"request": "attach",
|
||||
"sourceMaps": true,
|
||||
"type": "chrome",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Electron: All",
|
||||
"configurations": ["Electron: Main", "Electron: Renderer"]
|
||||
}
|
||||
]
|
||||
}
|
9
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"conventionalCommits.scopes": [
|
||||
"UI",
|
||||
"core",
|
||||
"MySQL",
|
||||
"PostgreSQL"
|
||||
],
|
||||
"svg.preview.background": "transparent"
|
||||
}
|
82
CHANGELOG.md
@@ -2,6 +2,88 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [0.3.9](https://github.com/Fabio286/antares/compare/v0.3.8...v0.3.9) (2021-11-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* added macos basic shortcusts and menu ([430490a](https://github.com/Fabio286/antares/commit/430490ad93f3148962ced1f13a5330c79cd86b3b))
|
||||
* **MySQL:** enable/disable schedulers from contextual menu ([5ca3a22](https://github.com/Fabio286/antares/commit/5ca3a22dc538b27a4bf6402f1288c4b9f5bc5a90))
|
||||
* **MySQL:** scheduler status indicator in explore bar ([5c66824](https://github.com/Fabio286/antares/commit/5c668249cf102cd9d601f9f7b4943c7155775217))
|
||||
* **PostgreSQL:** enable/disable triggers from contextual menu ([534659f](https://github.com/Fabio286/antares/commit/534659f9aee12eb5ac477f91bfe5d764387dc17e))
|
||||
* schema size in explore bar ([fd25f88](https://github.com/Fabio286/antares/commit/fd25f881f95779709156cbad93a41d6b391f1a45))
|
||||
* **UI:** double click on the title bar will toggle window fullscreen size ([a35566f](https://github.com/Fabio286/antares/commit/a35566f273322602abe434b8bd30817ba8885900))
|
||||
* **UI:** improved topbar look&feel on MacOS ([7657d05](https://github.com/Fabio286/antares/commit/7657d05edfbeaed6a14eb337fc562da5126e6ba0))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* copy&paste and basic usability on macOS ([1ddf8f0](https://github.com/Fabio286/antares/commit/1ddf8f0dbe22f94d6bffddf70636706d2d142ecf))
|
||||
* **PostgreSQL:** bigint fetched as string instead of number, closes [#134](https://github.com/Fabio286/antares/issues/134) ([39b9a59](https://github.com/Fabio286/antares/commit/39b9a59143b457a96f0711a3b8588c92dd80e28d))
|
||||
* row selection problems after a deletion fail, closes [#128](https://github.com/Fabio286/antares/issues/128) ([89fdd21](https://github.com/Fabio286/antares/commit/89fdd210ca48fc9ae399b195ea796c8523619627))
|
||||
* temporary solution on MacOS for unsigned app updates ([c00fd13](https://github.com/Fabio286/antares/commit/c00fd1381f451ba7aace7047b28b904ddcaf18f0))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **UI:** improved function and routine parameters modals ([d19f475](https://github.com/Fabio286/antares/commit/d19f475fc28c0367ada569cb634769fa618b48b4))
|
||||
|
||||
### [0.3.8](https://github.com/Fabio286/antares/compare/v0.3.7...v0.3.8) (2021-10-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **UI:** CTRL+A to select all result rows ([4069ade](https://github.com/Fabio286/antares/commit/4069ade36df43e58f198dd1305778b5811824315))
|
||||
* **UI:** ctrl|cmd+t, ctrl|cmd+w shortcut to open/close workspace tabs ([9046b85](https://github.com/Fabio286/antares/commit/9046b858b1e4608af4c01bc4d69e1a49d4009c07))
|
||||
* **UI:** hide filter bar if there are no more rows in it ([9178805](https://github.com/Fabio286/antares/commit/91788054e6302e83cb4a7501ad6c3f72809cb3bb))
|
||||
* **UI:** multi column table filters ([0e15c39](https://github.com/Fabio286/antares/commit/0e15c39797fe34f7a649f85ee62204682d45c98a))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **PostgreSQL:** issue with uppercase characters in table field names ([aef17be](https://github.com/Fabio286/antares/commit/aef17be36cfcf3a6325a954e80f973623e250405))
|
||||
* query failure when a filter with a numeric value is used ([69cd083](https://github.com/Fabio286/antares/commit/69cd083054cae50d64475b9f1f5d7ebd39093e39))
|
||||
* regression during resize results table on filters change ([7dc33c7](https://github.com/Fabio286/antares/commit/7dc33c78aa4152264cc6833437be9af9b8621867))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **UI:** resize results table on filters change ([f9ee7d0](https://github.com/Fabio286/antares/commit/f9ee7d0450a1386800223d7b96849e06ae02aece))
|
||||
|
||||
### [0.3.7](https://github.com/Fabio286/antares/compare/v0.3.6...v0.3.7) (2021-10-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* support to SSH private keys with passphrase, closes [#118](https://github.com/Fabio286/antares/issues/118) ([9407a29](https://github.com/Fabio286/antares/commit/9407a29922812ab6aa3cf67569ba2f509433657c))
|
||||
* **UI:** auto detect system theme as default app theme ([7a63608](https://github.com/Fabio286/antares/commit/7a63608f54e387d45e655855666041f5602b54b1))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* closing ask credential modal during a connection doesn't stops loading, closes [#114](https://github.com/Fabio286/antares/issues/114) ([26446fb](https://github.com/Fabio286/antares/commit/26446fb7ed04216283736072d442786350252dbb))
|
||||
* **PostgreSQL:** issue with uppercase characters in table and field names, closes [#116](https://github.com/Fabio286/antares/issues/116) ([2fcd080](https://github.com/Fabio286/antares/commit/2fcd080bd47367a21590ea5a754410a975959bdd))
|
||||
* **UI:** window reload pressing enter in schema creation modal, closes [#113](https://github.com/Fabio286/antares/issues/113) ([db1641b](https://github.com/Fabio286/antares/commit/db1641b74fcd218f1f1d24163cba70b024cc6bd7))
|
||||
|
||||
### [0.3.6](https://github.com/Fabio286/antares/compare/v0.3.5...v0.3.6) (2021-09-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* copy cell/row or kill connections on context menu from processes list ([85f625d](https://github.com/Fabio286/antares/commit/85f625daf7026815dac6223a29c5a6479830edbb))
|
||||
* processes list exportation ([13aa47c](https://github.com/Fabio286/antares/commit/13aa47cd4441aa47c93038dbd91d6a0e54f6a60c))
|
||||
* workspace query history ([3959333](https://github.com/Fabio286/antares/commit/39593336626e6d9f3d3b65d2a4081388900e37d6))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* adding a connection default values not change when switching clients, closes [#101](https://github.com/Fabio286/antares/issues/101) ([d4888ad](https://github.com/Fabio286/antares/commit/d4888ad8fba3c8e8ec2d6b6d9a78bb212d83eeed))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **core:** better communication of internal exceptions ([abd46aa](https://github.com/Fabio286/antares/commit/abd46aa32256f822e52eaac2fc698da378b8163f))
|
||||
|
||||
### [0.3.5](https://github.com/Fabio286/antares/compare/v0.3.4...v0.3.5) (2021-09-13)
|
||||
|
||||
|
||||
|
107
CONTRIBUTING.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Contributors Guide
|
||||
|
||||
Antares SQL is an application based on [Electron.js](https://www.electronjs.org/) that uses [Vue.js](https://vuejs.org/) and [Spectre.css](https://picturepan2.github.io/spectre/) as frontend frameworks.
|
||||
For the build process it takes advantage of [electron-builder](https://www.electron.build/).
|
||||
This application uses [Vuex](https://vuex.vuejs.org/) as application state manager and [electron-store](https://github.com/sindresorhus/electron-store) to save the various settings on disc.
|
||||
This guide aims to provide useful information and guidelines to everyone wants to contribute with this open-source project.
|
||||
For every other question related to this project please [contact me](https://github.com/Fabio286).
|
||||
|
||||
## Project Structure
|
||||
|
||||
The main files of the application are located inside `src` folder and are groupped in three subfolders.
|
||||
|
||||
### `common`
|
||||
|
||||
This folder contains small libraries, classes and objects. The purpose of `common` folder is to group together utilities used by **renderer** and **main** processes.
|
||||
Noteworthy is the `customizations` folder that contains clients related customizations. Those settings are merged with `default.js` that lists every option.
|
||||
Client related customizations are stored on Vuex and can be accessed by `customizations` property of current workspace object, or importing `common/customizations`.
|
||||
|
||||
An use case of customizations object can be the following:
|
||||
|
||||
```js
|
||||
computed: {
|
||||
defaultEngine () {
|
||||
if (this.workspace.customizations.engines)
|
||||
return this.workspace.engines.find(engine => engine.isDefault).name;
|
||||
return '';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this case the computed property `defaultEngine` returns the default engine for MySQL client, or an empty string with PostgreSQL that doesn't have engines.
|
||||
Customization properties are also useful **if some features are ready for one client but not others**.
|
||||
|
||||
### `main`
|
||||
|
||||
Inside this folder are located all files required by main process.
|
||||
`ipc-handlers` subfolder includes all IPC handlers for events sent from renderer process.
|
||||
`libs` subfolder includes classes related to clients and **query and connection logics**.
|
||||
**Everything above client's class level should be "client agnostic"** with a neutral and uniformed api interface
|
||||
|
||||
### `renderer`
|
||||
|
||||
In this folder is located the structure of Vue frontend application.
|
||||
|
||||
## Build
|
||||
|
||||
The command to build Antares SQL locally is `npm run build:local`.
|
||||
`build` command (without `:local`) is used exclusively by the GitHub Action.
|
||||
|
||||
## Conventions
|
||||
|
||||
### Electron
|
||||
|
||||
- **kebab-case** for IPC event names.
|
||||
|
||||
### Vue
|
||||
|
||||
- **PascalCase** for file names (with .vue extension) and including components inside others (`<MyComponent/>`).
|
||||
- "**Base**" prefix for [base component names](https://vuejs.org/v2/style-guide/#Base-component-names-strongly-recommended).
|
||||
- "**The**" prefix for [single-instance component names](https://vuejs.org/v2/style-guide/#Single-instance-component-names-strongly-recommended).
|
||||
- [Tightly coupled component names](https://vuejs.org/v2/style-guide/#Tightly-coupled-component-names-strongly-recommended).
|
||||
- [Order of words in component names](https://vuejs.org/v2/style-guide/#Order-of-words-in-component-names-strongly-recommended).
|
||||
- **kebab-case** in templates for property and event names.
|
||||
|
||||
### Vuex
|
||||
|
||||
- **snake_case** for state names.
|
||||
- **camelCase** for getter and action names.
|
||||
- **SNAKE_CASE (all caps)** for mutation names.
|
||||
|
||||
### Code Style
|
||||
|
||||
The project includes [ESlint](https://eslint.org/) and [StyleLint](https://stylelint.io/) config files with style rules. I recommend to set the lint on-save option in your code editor.
|
||||
Alternatively you can launch following commands to lint the project.
|
||||
|
||||
Check if all the style rules have been followed:
|
||||
|
||||
```console
|
||||
npm run lint
|
||||
```
|
||||
|
||||
Apply style rules globally if possible:
|
||||
|
||||
```console
|
||||
npm run lint:fix
|
||||
```
|
||||
|
||||
### Other recommendations
|
||||
|
||||
Please, use if possible **template literals** to compose strings and **avoid unnecessary dependencies**.
|
||||
|
||||
### Commits
|
||||
|
||||
The commit style adopted for this project is [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
|
||||
Basicly it's important to have **single scoped commits with a prefix** that follows this style because Antares SQL uses [standard-version](https://github.com/conventional-changelog/standard-version) to generate new releases and [CHANGELOG.md](https://github.com/Fabio286/antares/blob/master/CHANGELOG.md) file to track all notable changes.
|
||||
For Visual Studio Code users may be useful [Conventional Commits](https://marketplace.visualstudio.com/items?itemName=vivaxy.vscode-conventional-commits) extension.
|
||||
|
||||
## Debug
|
||||
|
||||
**Debug mode**:
|
||||
|
||||
```console
|
||||
npm run debug
|
||||
```
|
||||
|
||||
After running the debug mode Antares will listen on port 9222 (main process) for a debugger.
|
||||
On **Visual Studio Code** just launch "*Electron: Main*" configurations after running Antares in debug mode.
|
42
README.md
@@ -1,6 +1,9 @@
|
||||
|
||||
<!-- markdownlint-disable -->
|
||||
<p align="center">
|
||||
<img width="800" src="https://raw.githubusercontent.com/Fabio286/antares/master/docs/gh-logo.png">
|
||||
</p>
|
||||
<!-- markdownlint-restore -->
|
||||
|
||||
# Antares SQL Client
|
||||
|
||||
@@ -26,6 +29,7 @@ We are actively working on it, hoping to provide new cool features, improvements
|
||||
- A modern and friendly tab system; keep open every kind of tab you need in your workspace.
|
||||
- Fake table data filler to generate tons of data for test purpose.
|
||||
- Query suggestions and auto complete.
|
||||
- Query history: search through the last 1000 queries.
|
||||
- SSH tunnel support.
|
||||
- Dark and light theme.
|
||||
- Editor themes.
|
||||
@@ -67,7 +71,6 @@ This is a roadmap with major features will come in near future.
|
||||
- Support for other databases.
|
||||
- Database tools.
|
||||
- Users management (add/edit/delete).
|
||||
- Query history and bookmarks.
|
||||
- More context menu shortcuts.
|
||||
- More keyboard shortcuts.
|
||||
- Import/export and migration.
|
||||
@@ -104,11 +107,34 @@ This is a roadmap with major features will come in near future.
|
||||
- 📖 [Contributors Guide](https://github.com/Fabio286/antares/wiki/Contributors-Guide)
|
||||
- 🚧 [Project Board](https://github.com/users/Fabio286/projects/1)
|
||||
|
||||
## Translations
|
||||
## Contributors ✨
|
||||
|
||||
**Italian Translation** / [Giuseppe Gigliotti](https://github.com/ReverbOD) [[#20](https://github.com/Fabio286/antares/pull/20)]
|
||||
**Arabic Translation** (needs updates) / [Mohd-PH](https://github.com/Mohd-PH) [[#29](https://github.com/Fabio286/antares/pull/29)]
|
||||
**Spanish Translation** (needs updates) / [hongkfui](https://github.com/hongkfui) [[#32](https://github.com/Fabio286/antares/pull/32)]
|
||||
**French Translation** (needs updates) / [MrAnyx](https://github.com/MrAnyx) [[#44](https://github.com/Fabio286/antares/pull/44)]
|
||||
**Portugues (Brasil)** / [Daniel Eduardo](https://github.com/daeleduardo) [[#54](https://github.com/Fabio286/antares/pull/54)]
|
||||
**Deutsch (Deutschland)** / [Christian Ratz](https://github.com/digitalgopnik) [[#74](https://github.com/Fabio286/antares/pull/74)]
|
||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="https://fabiodistasio.it/"><img src="https://avatars.githubusercontent.com/u/31471771?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fabio Di Stasio</b></sub></a><br /><a href="https://github.com/Fabio286/Antares/commits?author=Fabio286" title="Code">💻</a> <a href="#translation-Fabio286" title="Translation">🌍</a> <a href="https://github.com/Fabio286/Antares/commits?author=Fabio286" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/giulioganci/"><img src="https://avatars.githubusercontent.com/u/4192159?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Giulio Ganci</b></sub></a><br /><a href="https://github.com/Fabio286/Antares/commits?author=toriphes" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://christianratz.de/"><img src="https://avatars.githubusercontent.com/u/2630316?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Christian Ratz</b></sub></a><br /><a href="https://github.com/Fabio286/Antares/commits?author=digitalgopnik" title="Code">💻</a> <a href="#translation-digitalgopnik" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://reverb6821.github.io/"><img src="https://avatars.githubusercontent.com/u/55198803?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Giuseppe Gigliotti</b></sub></a><br /><a href="#translation-reverb6821" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/Mohd-PH"><img src="https://avatars.githubusercontent.com/u/9362157?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mohd-PH</b></sub></a><br /><a href="#translation-Mohd-PH" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/hongkfui"><img src="https://avatars.githubusercontent.com/u/37477191?v=4?s=100" width="100px;" alt=""/><br /><sub><b>hongkfui</b></sub></a><br /><a href="#translation-hongkfui" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/MrAnyx"><img src="https://avatars.githubusercontent.com/u/44176707?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Robin</b></sub></a><br /><a href="#translation-MrAnyx" title="Translation">🌍</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/daeleduardo"><img src="https://avatars.githubusercontent.com/u/8599078?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Daniel Eduardo</b></sub></a><br /><a href="#translation-daeleduardo" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://ngoquocdat.com/"><img src="https://avatars.githubusercontent.com/u/56961917?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ngô Quốc Đạt</b></sub></a><br /><a href="#translation-datlechin" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/IsamuSugi"><img src="https://avatars.githubusercontent.com/u/7746658?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Isamu Sugiura</b></sub></a><br /><a href="#translation-IsamuSugi" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="http://rsacchetto.nexxontech.it/"><img src="https://avatars.githubusercontent.com/u/18429412?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Riccardo Sacchetto</b></sub></a><br /><a href="#platform-Occhioverde" title="Packaging/porting to new platform">📦</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"include": [
|
||||
"./src/renderer/**/*"
|
||||
]
|
||||
}
|
87
package.json
@@ -1,27 +1,45 @@
|
||||
{
|
||||
"name": "antares",
|
||||
"productName": "Antares",
|
||||
"version": "0.3.5",
|
||||
"description": "A cross-platform easy to use SQL client.",
|
||||
"version": "0.3.9",
|
||||
"description": "A modern, fast and productivity driven SQL client with a focus in UX.",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/Fabio286/antares.git",
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development electron-webpack dev",
|
||||
"compile": "electron-webpack",
|
||||
"debug": "npm run rebuild:electron && npm run debug-runner",
|
||||
"debug-runner": "node scripts/devRunner.js --remote-debug",
|
||||
"compile": "npm run compile:main && npm run compile:renderer",
|
||||
"compile:main": "webpack --mode=production --config webpack.main.config.js",
|
||||
"compile:renderer": "webpack --mode=production --config webpack.renderer.config.js",
|
||||
"build": "cross-env NODE_ENV=production npm run compile",
|
||||
"build:local": "npm run build && electron-builder",
|
||||
"build:appx": "npm run build:local -- --win appx",
|
||||
"rebuild:electron": "npm run postinstall && electron-rebuild",
|
||||
"release": "standard-version",
|
||||
"release:pre": "npm run release -- --prerelease alpha",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"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",
|
||||
"postinstall": "electron-builder install-app-deps"
|
||||
"contributors:add": "all-contributors add",
|
||||
"contributors:generate": "all-contributors generate"
|
||||
},
|
||||
"author": "Fabio Di Stasio <fabio286@gmail.com>",
|
||||
"main": "./dist/main.js",
|
||||
"build": {
|
||||
"appId": "com.fabio286.antares",
|
||||
"artifactName": "${productName}-${version}-${os}_${arch}.${ext}",
|
||||
"asar": true,
|
||||
"directories": {
|
||||
"output": "build",
|
||||
"buildResources": "assets"
|
||||
},
|
||||
"asarUnpack": "**\\*.{node,dll}",
|
||||
"files": [
|
||||
"dist/**/*",
|
||||
"node_modules",
|
||||
"package.json"
|
||||
],
|
||||
"win": {
|
||||
"target": [
|
||||
"nsis",
|
||||
@@ -61,7 +79,8 @@
|
||||
"artifactName": "${productName}-${version}-portable.exe"
|
||||
},
|
||||
"appx": {
|
||||
"displayName": "Antares SQL Client",
|
||||
"displayName": "Antares SQL",
|
||||
"backgroundColor": "transparent",
|
||||
"identityName": "62514FabioDiStasio.AntaresSQLClient",
|
||||
"publisher": "CN=1A2729ED-865C-41D2-9038-39AE2A63AA52",
|
||||
"applicationId": "FabioDiStasio.AntaresSQLClient"
|
||||
@@ -81,55 +100,65 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"electronWebpack": {
|
||||
"renderer": {
|
||||
"webpackConfig": "webpack.config.js"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron/remote": "^1.2.1",
|
||||
"@electron/remote": "^2.0.1",
|
||||
"@mdi/font": "^6.1.95",
|
||||
"ace-builds": "^1.4.12",
|
||||
"ace-builds": "^1.4.13",
|
||||
"electron-log": "^4.4.1",
|
||||
"electron-store": "^8.0.0",
|
||||
"electron-store": "^8.0.1",
|
||||
"electron-updater": "^4.3.9",
|
||||
"faker": "^5.5.3",
|
||||
"marked": "^3.0.3",
|
||||
"marked": "^4.0.0",
|
||||
"moment": "^2.29.1",
|
||||
"mysql2": "^2.3.0",
|
||||
"mysql2": "^2.3.2",
|
||||
"pg": "^8.7.1",
|
||||
"pgsql-ast-parser": "^7.2.1",
|
||||
"source-map-support": "^0.5.20",
|
||||
"spectre.css": "^0.5.9",
|
||||
"sql-formatter": "^4.0.2",
|
||||
"ssh2-promise": "^0.2.0",
|
||||
"v-mask": "^2.2.4",
|
||||
"vue-i18n": "^8.24.4",
|
||||
"ssh2-promise": "^1.0.2",
|
||||
"v-mask": "^2.3.0",
|
||||
"vue-i18n": "^8.26.5",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuex": "^3.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.15.4",
|
||||
"@babel/eslint-parser": "^7.15.7",
|
||||
"@babel/preset-env": "^7.15.8",
|
||||
"all-contributors-cli": "^6.20.0",
|
||||
"babel-loader": "^8.2.3",
|
||||
"chalk": "^4.1.2",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"electron": "^14.0.0",
|
||||
"electron-builder": "^22.11.7",
|
||||
"css-loader": "^6.5.0",
|
||||
"electron": "^15.3.0",
|
||||
"electron-builder": "^22.13.1",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-webpack": "^2.8.2",
|
||||
"electron-webpack-vue": "^2.4.0",
|
||||
"electron-rebuild": "^3.2.3",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
"eslint-plugin-import": "^2.24.2",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-vue": "^7.17.0",
|
||||
"sass": "^1.39.2",
|
||||
"sass-loader": "^10.2.0",
|
||||
"eslint-plugin-vue": "^8.0.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"mini-css-extract-plugin": "^2.4.3",
|
||||
"node-loader": "^2.0.0",
|
||||
"progress-webpack-plugin": "^1.0.12",
|
||||
"sass": "^1.42.1",
|
||||
"sass-loader": "^12.3.0",
|
||||
"standard-version": "^9.3.1",
|
||||
"style-loader": "^3.3.1",
|
||||
"stylelint": "^13.13.1",
|
||||
"stylelint-config-standard": "^22.0.0",
|
||||
"stylelint-scss": "^3.20.1",
|
||||
"stylelint-scss": "^3.21.0",
|
||||
"tree-kill": "^1.2.2",
|
||||
"vue": "^2.6.14",
|
||||
"vue-loader": "^15.9.8",
|
||||
"vue-template-compiler": "^2.6.14",
|
||||
"webpack": "^4.46.0"
|
||||
"webpack": "^5.60.0",
|
||||
"webpack-cli": "^4.9.1",
|
||||
"webpack-dev-server": "^4.4.0"
|
||||
}
|
||||
}
|
||||
|
131
scripts/devRunner.js
Normal file
@@ -0,0 +1,131 @@
|
||||
process.env.NODE_ENV = 'development';
|
||||
// process.env.ELECTRON_ENABLE_LOGGING = true
|
||||
|
||||
const chalk = require('chalk');
|
||||
const electron = require('electron');
|
||||
const webpack = require('webpack');
|
||||
const WebpackDevServer = require('webpack-dev-server');
|
||||
const kill = require('tree-kill');
|
||||
|
||||
const path = require('path');
|
||||
const { spawn } = require('child_process');
|
||||
|
||||
const mainConfig = require('../webpack.main.config');
|
||||
const rendererConfig = require('../webpack.renderer.config');
|
||||
// const workersConfig = require('../webpack.workers.config');
|
||||
|
||||
let electronProcess = null;
|
||||
let manualRestart = null;
|
||||
const remoteDebugging = process.argv.includes('--remote-debug');
|
||||
|
||||
if (remoteDebugging) {
|
||||
// disable dvtools open in electron
|
||||
process.env.RENDERER_REMOTE_DEBUGGING = true;
|
||||
}
|
||||
|
||||
async function killElectron (pid) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (pid) {
|
||||
kill(pid, 'SIGKILL', err => {
|
||||
if (err) reject(err);
|
||||
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
else
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
async function restartElectron () {
|
||||
console.log(chalk.gray('\nStarting electron...'));
|
||||
|
||||
const { pid } = electronProcess || {};
|
||||
await killElectron(pid);
|
||||
|
||||
electronProcess = spawn(electron, [
|
||||
path.join(__dirname, '../dist/main.js'),
|
||||
// '--enable-logging', // Enable to show logs from all electron processes
|
||||
remoteDebugging ? '--inspect=9222' : '',
|
||||
remoteDebugging ? '--remote-debugging-port=9223' : ''
|
||||
]);
|
||||
|
||||
electronProcess.stdout.on('data', data => {
|
||||
console.log(chalk.white(data.toString()));
|
||||
});
|
||||
|
||||
electronProcess.stderr.on('data', data => {
|
||||
console.error(chalk.red(data.toString()));
|
||||
});
|
||||
|
||||
electronProcess.on('exit', (code, signal) => {
|
||||
if (!manualRestart) process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
function startMain () {
|
||||
const webpackSetup = webpack(mainConfig);
|
||||
|
||||
webpackSetup.compilers.forEach((compiler) => {
|
||||
const { name } = compiler;
|
||||
|
||||
switch (name) {
|
||||
case 'workers':
|
||||
compiler.hooks.afterEmit.tap('afterEmit', async () => {
|
||||
console.log(chalk.gray(`\nCompiled ${name} script!`));
|
||||
console.log(
|
||||
chalk.gray(`\nWatching file changes for ${name} script...`)
|
||||
);
|
||||
});
|
||||
break;
|
||||
case 'main':
|
||||
default:
|
||||
compiler.hooks.afterEmit.tap('afterEmit', async () => {
|
||||
console.log(chalk.gray(`\nCompiled ${name} script!`));
|
||||
|
||||
manualRestart = true;
|
||||
await restartElectron();
|
||||
|
||||
setTimeout(() => {
|
||||
manualRestart = false;
|
||||
}, 2500);
|
||||
|
||||
console.log(
|
||||
chalk.gray(`\nWatching file changes for ${name} script...`)
|
||||
);
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
webpackSetup.watch({ aggregateTimeout: 500 }, err => {
|
||||
if (err) console.error(chalk.red(err));
|
||||
});
|
||||
}
|
||||
|
||||
function startRenderer (callback) {
|
||||
const compiler = webpack(rendererConfig);
|
||||
const { name } = compiler;
|
||||
|
||||
compiler.hooks.afterEmit.tap('afterEmit', () => {
|
||||
console.log(chalk.gray(`\nCompiled ${name} script!`));
|
||||
console.log(chalk.gray(`\nWatching file changes for ${name} script...`));
|
||||
});
|
||||
|
||||
const server = new WebpackDevServer(compiler, {
|
||||
hot: true,
|
||||
port: 9080,
|
||||
client: {
|
||||
overlay: true,
|
||||
logging: 'warn'
|
||||
}
|
||||
});
|
||||
|
||||
server.startCallback(err => {
|
||||
if (err) console.error(chalk.red(err));
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
startRenderer(startMain);
|
@@ -22,6 +22,8 @@ module.exports = {
|
||||
functions: false,
|
||||
schedulers: false,
|
||||
// Settings
|
||||
elementsWrapper: '',
|
||||
stringsWrapper: '"',
|
||||
tableAdd: false,
|
||||
viewAdd: false,
|
||||
triggerAdd: false,
|
||||
@@ -70,6 +72,7 @@ module.exports = {
|
||||
triggerTableInName: false,
|
||||
triggerUpdateColumns: false,
|
||||
triggerOnlyRename: false,
|
||||
triggerEnableDisable: false,
|
||||
triggerFunctionSql: false,
|
||||
triggerFunctionlanguages: false,
|
||||
parametersLength: false,
|
||||
|
@@ -21,6 +21,8 @@ module.exports = {
|
||||
functions: true,
|
||||
schedulers: true,
|
||||
// Settings
|
||||
elementsWrapper: '',
|
||||
stringsWrapper: '"',
|
||||
tableAdd: true,
|
||||
viewAdd: true,
|
||||
triggerAdd: true,
|
||||
|
@@ -18,6 +18,8 @@ module.exports = {
|
||||
routines: true,
|
||||
functions: true,
|
||||
// Settings
|
||||
elementsWrapper: '"',
|
||||
stringsWrapper: '\'',
|
||||
tableAdd: true,
|
||||
viewAdd: true,
|
||||
triggerAdd: true,
|
||||
@@ -48,5 +50,6 @@ module.exports = {
|
||||
triggerMultipleEvents: true,
|
||||
triggerTableInName: true,
|
||||
triggerOnlyRename: false,
|
||||
triggerEnableDisable: true,
|
||||
languages: ['sql', 'plpgsql', 'c', 'internal']
|
||||
};
|
||||
|
@@ -4,6 +4,7 @@ export const TEXT = [
|
||||
'CHARACTER',
|
||||
'CHARACTER VARYING'
|
||||
];
|
||||
|
||||
export const LONG_TEXT = [
|
||||
'TEXT',
|
||||
'MEDIUMTEXT',
|
||||
@@ -50,6 +51,7 @@ export const BOOLEAN = [
|
||||
];
|
||||
|
||||
export const DATE = ['DATE'];
|
||||
|
||||
export const TIME = [
|
||||
'TIME',
|
||||
'TIME WITH TIME ZONE'
|
||||
|
@@ -1,103 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
import { app, BrowserWindow, /* session, */ nativeImage, Menu } from 'electron';
|
||||
import * as path from 'path';
|
||||
import Store from 'electron-store';
|
||||
|
||||
import ipcHandlers from './ipc-handlers';
|
||||
|
||||
Store.initRenderer();
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
|
||||
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
|
||||
|
||||
// global reference to mainWindow (necessary to prevent window from being garbage collected)
|
||||
let mainWindow;
|
||||
|
||||
async function createMainWindow () {
|
||||
const icon = require('../renderer/images/logo-32.png');
|
||||
const window = new BrowserWindow({
|
||||
width: 1024,
|
||||
height: 800,
|
||||
minWidth: 900,
|
||||
minHeight: 550,
|
||||
title: 'Antares',
|
||||
autoHideMenuBar: true,
|
||||
icon: nativeImage.createFromDataURL(icon.default),
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
'web-security': false,
|
||||
enableRemoteModule: true,
|
||||
spellcheck: false
|
||||
},
|
||||
frame: false,
|
||||
backgroundColor: '#1d1d1d'
|
||||
});
|
||||
|
||||
try {
|
||||
if (isDevelopment) { //
|
||||
await window.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`);
|
||||
|
||||
// const { default: installExtension, VUEJS3_DEVTOOLS } = require('electron-devtools-installer');
|
||||
|
||||
// const oldDevToolsID = session.defaultSession.getAllExtensions().find(ext => ext.name === 'Vue.js devtools').id;
|
||||
// session.defaultSession.removeExtension(oldDevToolsID);
|
||||
// const toolName = await installExtension(VUEJS3_DEVTOOLS);
|
||||
// console.log(toolName, 'installed');
|
||||
}
|
||||
else
|
||||
await window.loadURL(new URL(`file:///${path.join(__dirname, 'index.html')}`).href);
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
window.on('closed', () => {
|
||||
mainWindow = null;
|
||||
});
|
||||
|
||||
window.webContents.on('devtools-opened', () => {
|
||||
window.focus();
|
||||
setImmediate(() => {
|
||||
window.focus();
|
||||
});
|
||||
});
|
||||
|
||||
return window;
|
||||
};
|
||||
|
||||
if (!gotTheLock)
|
||||
app.quit();
|
||||
else {
|
||||
require('@electron/remote/main').initialize();
|
||||
|
||||
// Initialize ipcHandlers
|
||||
ipcHandlers();
|
||||
|
||||
// quit application when all windows are closed
|
||||
app.on('window-all-closed', () => {
|
||||
// on macOS it is common for applications to stay open until the user explicitly quits
|
||||
if (process.platform !== 'darwin')
|
||||
app.quit();
|
||||
});
|
||||
|
||||
app.on('activate', async () => {
|
||||
// on macOS it is common to re-create a window even after all windows have been closed
|
||||
if (mainWindow === null) {
|
||||
mainWindow = await createMainWindow();
|
||||
if (isDevelopment)
|
||||
mainWindow.webContents.openDevTools();
|
||||
}
|
||||
});
|
||||
|
||||
// create main BrowserWindow when electron is ready
|
||||
app.on('ready', async () => {
|
||||
mainWindow = await createMainWindow();
|
||||
Menu.setApplicationMenu(null);
|
||||
if (isDevelopment)
|
||||
mainWindow.webContents.openDevTools();
|
||||
});
|
||||
}
|
@@ -30,7 +30,8 @@ export default connections => {
|
||||
username: conn.sshUser,
|
||||
password: conn.sshPass,
|
||||
port: conn.sshPort ? conn.sshPort : 22,
|
||||
identity: conn.sshKey
|
||||
privateKey: conn.sshKey ? fs.readFileSync(conn.sshKey) : null,
|
||||
passphrase: conn.sshPassphrase
|
||||
};
|
||||
}
|
||||
|
||||
@@ -85,7 +86,8 @@ export default connections => {
|
||||
username: conn.sshUser,
|
||||
password: conn.sshPass,
|
||||
port: conn.sshPort ? conn.sshPort : 22,
|
||||
identity: conn.sshKey
|
||||
privateKey: conn.sshKey ? fs.readFileSync(conn.sshKey) : null,
|
||||
passphrase: conn.sshPassphrase
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -40,4 +40,17 @@ export default (connections) => {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('toggle-scheduler', async (event, params) => {
|
||||
try {
|
||||
if (!params.enabled)
|
||||
await connections[params.uid].enableEvent({ ...params });
|
||||
else
|
||||
await connections[params.uid].disableEvent({ ...params });
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@@ -112,6 +112,17 @@ export default connections => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('kill-process', async (event, { uid, pid }) => {
|
||||
try {
|
||||
const result = await connections[uid].killProcess(pid);
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('use-schema', async (event, { uid, schema }) => {
|
||||
if (!schema) return;
|
||||
|
||||
|
@@ -16,7 +16,7 @@ export default (connections) => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-table-data', async (event, { uid, schema, table, limit, page, sortParams }) => {
|
||||
ipcMain.handle('get-table-data', async (event, { uid, schema, table, limit, page, sortParams, where }) => {
|
||||
try {
|
||||
const offset = (page - 1) * limit;
|
||||
const query = connections[uid]
|
||||
@@ -29,6 +29,9 @@ export default (connections) => {
|
||||
if (sortParams && sortParams.field && sortParams.dir)
|
||||
query.orderBy({ [sortParams.field]: sortParams.dir.toUpperCase() });
|
||||
|
||||
if (where)
|
||||
query.where(where);
|
||||
|
||||
const result = await query.run({ details: true, schema });
|
||||
|
||||
return { status: 'success', response: result };
|
||||
@@ -81,7 +84,7 @@ export default (connections) => {
|
||||
});
|
||||
|
||||
ipcMain.handle('update-table-cell', async (event, params) => {
|
||||
delete params.row._id;
|
||||
delete params.row._antares_id;
|
||||
|
||||
try { // TODO: move to client classes
|
||||
let escapedParam;
|
||||
|
@@ -40,4 +40,17 @@ export default (connections) => {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('toggle-trigger', async (event, params) => {
|
||||
try {
|
||||
if (!params.enabled)
|
||||
await connections[params.uid].enableTrigger(params);
|
||||
else
|
||||
await connections[params.uid].disableTrigger(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@@ -2,6 +2,7 @@ import { ipcMain } from 'electron';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
import Store from 'electron-store';
|
||||
const persistentStore = new Store({ name: 'settings' });
|
||||
const isMacOS = process.platform === 'darwin';
|
||||
|
||||
let mainWindow;
|
||||
autoUpdater.allowPrerelease = persistentStore.get('allow_prerelease', true);
|
||||
@@ -11,6 +12,9 @@ export default () => {
|
||||
mainWindow = event;
|
||||
if (process.windowsStore || (process.platform === 'linux' && !process.env.APPIMAGE))
|
||||
mainWindow.reply('no-auto-update');
|
||||
else if (isMacOS) { // Temporary solution on MacOS for unsigned app updates
|
||||
autoUpdater.autoDownload = false;
|
||||
}
|
||||
else {
|
||||
autoUpdater.checkForUpdatesAndNotify().catch(() => {
|
||||
mainWindow.reply('check-failed');
|
||||
@@ -28,7 +32,10 @@ export default () => {
|
||||
});
|
||||
|
||||
autoUpdater.on('update-available', () => {
|
||||
mainWindow.reply('update-available');
|
||||
if (isMacOS)
|
||||
mainWindow.reply('link-to-download');
|
||||
else
|
||||
mainWindow.reply('update-available');
|
||||
});
|
||||
|
||||
autoUpdater.on('update-not-available', () => {
|
||||
|
@@ -16,6 +16,7 @@ export class AntaresCore {
|
||||
this._params = args.params;
|
||||
this._poolSize = args.poolSize || false;
|
||||
this._connection = null;
|
||||
this._ssh = null;
|
||||
this._logger = args.logger || console.log;
|
||||
|
||||
this._queryDefaults = {
|
||||
|
@@ -118,13 +118,19 @@ export class MySQLClient extends AntaresCore {
|
||||
if (this._params.ssl) dbConfig.ssl = { ...this._params.ssl };
|
||||
|
||||
if (this._params.ssh) {
|
||||
this._ssh = new SSH2Promise({ ...this._params.ssh });
|
||||
try {
|
||||
this._ssh = new SSH2Promise({ ...this._params.ssh });
|
||||
|
||||
this._tunnel = await this._ssh.addTunnel({
|
||||
remoteAddr: this._params.host,
|
||||
remotePort: this._params.port
|
||||
});
|
||||
dbConfig.port = this._tunnel.localPort;
|
||||
const tunnel = await this._ssh.addTunnel({
|
||||
remoteAddr: this._params.host,
|
||||
remotePort: this._params.port
|
||||
});
|
||||
dbConfig.port = tunnel.localPort;
|
||||
}
|
||||
catch (err) {
|
||||
if (this._ssh) this._ssh.close();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this._poolSize)
|
||||
@@ -181,6 +187,7 @@ export class MySQLClient extends AntaresCore {
|
||||
|
||||
const tablesArr = [];
|
||||
const triggersArr = [];
|
||||
let schemaSize = 0;
|
||||
|
||||
for (const db of filteredDatabases) {
|
||||
if (!schemas.has(db.Database)) continue;
|
||||
@@ -218,6 +225,9 @@ export class MySQLClient extends AntaresCore {
|
||||
break;
|
||||
}
|
||||
|
||||
const tableSize = table.Data_length + table.Index_length;
|
||||
schemaSize += tableSize;
|
||||
|
||||
return {
|
||||
name: table.Name,
|
||||
type: tableType,
|
||||
@@ -226,7 +236,7 @@ export class MySQLClient extends AntaresCore {
|
||||
updated: table.Update_time,
|
||||
engine: table.Engine,
|
||||
comment: table.Comment,
|
||||
size: table.Data_length + table.Index_length,
|
||||
size: tableSize,
|
||||
autoIncrement: table.Auto_increment,
|
||||
collation: table.Collation
|
||||
};
|
||||
@@ -270,7 +280,7 @@ export class MySQLClient extends AntaresCore {
|
||||
body: scheduler.EVENT_BODY,
|
||||
starts: scheduler.STARTS,
|
||||
ends: scheduler.ENDS,
|
||||
status: scheduler.STATUS,
|
||||
enabled: scheduler.STATUS === 'ENABLED',
|
||||
executeAt: scheduler.EXECUTE_AT,
|
||||
intervalField: scheduler.INTERVAL_FIELD,
|
||||
intervalValue: scheduler.INTERVAL_VALUE,
|
||||
@@ -303,6 +313,7 @@ export class MySQLClient extends AntaresCore {
|
||||
|
||||
return {
|
||||
name: db.Database,
|
||||
size: schemaSize,
|
||||
tables: remappedTables,
|
||||
functions: remappedFunctions,
|
||||
procedures: remappedProcedures,
|
||||
@@ -313,6 +324,7 @@ export class MySQLClient extends AntaresCore {
|
||||
else {
|
||||
return {
|
||||
name: db.Database,
|
||||
size: 0,
|
||||
tables: [],
|
||||
functions: [],
|
||||
procedures: [],
|
||||
@@ -1066,6 +1078,16 @@ export class MySQLClient extends AntaresCore {
|
||||
return await this.raw(sql, { split: false });
|
||||
}
|
||||
|
||||
async enableEvent ({ schema, scheduler }) {
|
||||
const sql = `ALTER EVENT \`${schema}\`.\`${scheduler}\` ENABLE`;
|
||||
return await this.raw(sql, { split: false });
|
||||
}
|
||||
|
||||
async disableEvent ({ schema, scheduler }) {
|
||||
const sql = `ALTER EVENT \`${schema}\`.\`${scheduler}\` DISABLE`;
|
||||
return await this.raw(sql, { split: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* SHOW COLLATION
|
||||
*
|
||||
@@ -1176,6 +1198,10 @@ export class MySQLClient extends AntaresCore {
|
||||
});
|
||||
}
|
||||
|
||||
async killProcess (id) {
|
||||
return await this.raw(`KILL ${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* CREATE TABLE
|
||||
*
|
||||
|
@@ -9,6 +9,7 @@ function pgToString (value) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
types.setTypeParser(20, a => parseInt(a));// bigint string to number
|
||||
types.setTypeParser(1082, pgToString); // date
|
||||
types.setTypeParser(1083, pgToString); // time
|
||||
types.setTypeParser(1114, pgToString); // timestamp
|
||||
@@ -36,6 +37,26 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
};
|
||||
}
|
||||
|
||||
_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_getTypeInfo (type) {
|
||||
return dataTypes
|
||||
.reduce((acc, group) => [...acc, ...group.types], [])
|
||||
@@ -65,13 +86,19 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
if (this._params.ssl) dbConfig.ssl = { ...this._params.ssl };
|
||||
|
||||
if (this._params.ssh) {
|
||||
this._ssh = new SSH2Promise({ ...this._params.ssh });
|
||||
try {
|
||||
this._ssh = new SSH2Promise({ ...this._params.ssh });
|
||||
|
||||
this._tunnel = await this._ssh.addTunnel({
|
||||
remoteAddr: this._params.host,
|
||||
remotePort: this._params.port
|
||||
});
|
||||
dbConfig.port = this._tunnel.localPort;
|
||||
const tunnel = await this._ssh.addTunnel({
|
||||
remoteAddr: this._params.host,
|
||||
remotePort: this._params.port
|
||||
});
|
||||
dbConfig.port = tunnel.localPort;
|
||||
}
|
||||
catch (err) {
|
||||
if (this._ssh) this._ssh.close();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this._poolSize) {
|
||||
@@ -102,7 +129,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
use (schema) {
|
||||
this._schema = schema;
|
||||
if (schema)
|
||||
return this.raw(`SET search_path TO ${schema}`);
|
||||
return this.raw(`SET search_path TO "${schema}"`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,6 +144,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
|
||||
const tablesArr = [];
|
||||
const triggersArr = [];
|
||||
let schemaSize = 0;
|
||||
|
||||
for (const db of databases) {
|
||||
if (!schemas.has(db.database)) continue;
|
||||
@@ -142,19 +170,20 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
}
|
||||
|
||||
let { rows: triggers } = await this.raw(`
|
||||
SELECT event_object_schema AS table_schema,
|
||||
event_object_table AS table_name,
|
||||
trigger_schema,
|
||||
trigger_name,
|
||||
string_agg(event_manipulation, ',') AS event,
|
||||
action_timing AS activation,
|
||||
action_condition AS condition,
|
||||
action_statement AS definition
|
||||
FROM information_schema.triggers
|
||||
SELECT
|
||||
pg_class.relname AS table_name,
|
||||
pg_trigger.tgname AS trigger_name,
|
||||
pg_namespace.nspname AS trigger_schema,
|
||||
(pg_trigger.tgenabled != 'D')::bool AS enabled
|
||||
FROM pg_trigger
|
||||
JOIN pg_class ON pg_trigger.tgrelid = pg_class.oid
|
||||
JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace
|
||||
JOIN information_schema.triggers ON information_schema.triggers.trigger_schema = pg_namespace.nspname
|
||||
AND information_schema.triggers.event_object_table = pg_class.relname
|
||||
AND information_schema.triggers.trigger_name = pg_trigger.tgname
|
||||
WHERE trigger_schema = '${db.database}'
|
||||
GROUP BY 1,2,3,4,6,7,8
|
||||
ORDER BY table_schema,
|
||||
table_name
|
||||
GROUP BY 1, 2, 3, 4
|
||||
ORDER BY table_name
|
||||
`);
|
||||
|
||||
if (triggers.length) {
|
||||
@@ -170,11 +199,14 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
if (schemas.has(db.database)) {
|
||||
// TABLES
|
||||
const remappedTables = tablesArr.filter(table => table.Db === db.database).map(table => {
|
||||
const tableSize = +table.data_length + table.index_length;
|
||||
schemaSize += tableSize;
|
||||
|
||||
return {
|
||||
name: table.table_name,
|
||||
type: table.table_type === 'VIEW' ? 'view' : 'table',
|
||||
rows: table.reltuples,
|
||||
size: +table.data_length + +table.index_length,
|
||||
size: tableSize,
|
||||
collation: table.Collation,
|
||||
comment: table.comment,
|
||||
engine: ''
|
||||
@@ -213,17 +245,16 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
return {
|
||||
name: `${trigger.table_name}.${trigger.trigger_name}`,
|
||||
orgName: trigger.trigger_name,
|
||||
timing: trigger.activation,
|
||||
definer: '',
|
||||
definition: trigger.definition,
|
||||
event: trigger.event,
|
||||
table: trigger.table_name,
|
||||
sqlMode: ''
|
||||
sqlMode: '',
|
||||
enabled: trigger.enabled
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
name: db.database,
|
||||
size: schemaSize,
|
||||
tables: remappedTables,
|
||||
functions: remappedFunctions,
|
||||
procedures: remappedProcedures,
|
||||
@@ -235,6 +266,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
else {
|
||||
return {
|
||||
name: db.database,
|
||||
size: 0,
|
||||
tables: [],
|
||||
functions: [],
|
||||
procedures: [],
|
||||
@@ -530,7 +562,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
* @memberof PostgreSQLClient
|
||||
*/
|
||||
async dropView (params) {
|
||||
const sql = `DROP VIEW ${params.schema}.${params.view}`;
|
||||
const sql = `DROP VIEW "${params.schema}"."${params.view}"`;
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
@@ -542,10 +574,10 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
*/
|
||||
async alterView (params) {
|
||||
const { view } = params;
|
||||
let sql = `CREATE OR REPLACE VIEW ${view.schema}.${view.oldName} AS ${view.sql}`;
|
||||
let sql = `CREATE OR REPLACE VIEW "${view.schema}"."${view.oldName}" AS ${view.sql}`;
|
||||
|
||||
if (view.name !== view.oldName)
|
||||
sql += `; ALTER VIEW ${view.schema}.${view.oldName} RENAME TO ${view.name}`;
|
||||
sql += `; ALTER VIEW "${view.schema}"."${view.oldName}" RENAME TO "${view.name}"`;
|
||||
|
||||
return await this.raw(sql);
|
||||
}
|
||||
@@ -557,7 +589,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
* @memberof PostgreSQLClient
|
||||
*/
|
||||
async createView (params) {
|
||||
const sql = `CREATE VIEW ${params.schema}.${params.name} AS ${params.sql}`;
|
||||
const sql = `CREATE VIEW "${params.schema}"."${params.name}" AS ${params.sql}`;
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
@@ -571,19 +603,25 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
const [table, triggerName] = trigger.split('.');
|
||||
|
||||
const results = await this.raw(`
|
||||
SELECT event_object_schema AS table_schema,
|
||||
event_object_table AS table_name,
|
||||
trigger_schema,
|
||||
trigger_name,
|
||||
string_agg(event_manipulation, ',') AS event,
|
||||
SELECT
|
||||
information_schema.triggers.event_object_schema AS table_schema,
|
||||
information_schema.triggers.event_object_table AS table_name,
|
||||
information_schema.triggers.trigger_schema,
|
||||
information_schema.triggers.trigger_name,
|
||||
string_agg(event_manipulation, ',') AS EVENT,
|
||||
action_timing AS activation,
|
||||
action_condition AS condition,
|
||||
action_statement AS definition
|
||||
FROM information_schema.triggers
|
||||
action_statement AS definition,
|
||||
(pg_trigger.tgenabled != 'D')::bool AS enabled
|
||||
FROM pg_trigger
|
||||
JOIN pg_class ON pg_trigger.tgrelid = pg_class.oid
|
||||
JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace
|
||||
JOIN information_schema.triggers ON pg_namespace.nspname = information_schema.triggers.trigger_schema
|
||||
AND pg_class.relname = information_schema.triggers.event_object_table
|
||||
WHERE trigger_schema = '${schema}'
|
||||
AND trigger_name = '${triggerName}'
|
||||
AND event_object_table = '${table}'
|
||||
GROUP BY 1,2,3,4,6,7,8
|
||||
GROUP BY 1,2,3,4,6,7,8,9
|
||||
ORDER BY table_schema,
|
||||
table_name
|
||||
`);
|
||||
@@ -593,7 +631,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
sql: row.definition,
|
||||
name: row.trigger_name,
|
||||
table: row.table_name,
|
||||
event: row.event.split(','),
|
||||
event: [...new Set(row.event.split(','))],
|
||||
activation: row.activation
|
||||
};
|
||||
})[0];
|
||||
@@ -645,6 +683,18 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
return await this.raw(sql, { split: false });
|
||||
}
|
||||
|
||||
async enableTrigger ({ schema, trigger }) {
|
||||
const [table, triggerName] = trigger.split('.');
|
||||
const sql = `ALTER TABLE "${schema}"."${table}" ENABLE TRIGGER "${triggerName}"`;
|
||||
return await this.raw(sql, { split: false });
|
||||
}
|
||||
|
||||
async disableTrigger ({ schema, trigger }) {
|
||||
const [table, triggerName] = trigger.split('.');
|
||||
const sql = `ALTER TABLE "${schema}"."${table}" DISABLE TRIGGER "${triggerName}"`;
|
||||
return await this.raw(sql, { split: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* SHOW CREATE PROCEDURE
|
||||
*
|
||||
@@ -1029,6 +1079,10 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
});
|
||||
}
|
||||
|
||||
async killProcess (id) {
|
||||
return await this.raw(`SELECT pg_terminate_backend(${id})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* CREATE TABLE
|
||||
*
|
||||
@@ -1055,7 +1109,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
const typeInfo = this._getTypeInfo(field.type);
|
||||
const length = typeInfo.length ? field.enumValues || field.numLength || field.charLength || field.datePrecision : false;
|
||||
|
||||
newColumns.push(`${field.name}
|
||||
newColumns.push(`"${field.name}"
|
||||
${field.type.toUpperCase()}${length ? `(${length})` : ''}
|
||||
${field.unsigned ? 'UNSIGNED' : ''}
|
||||
${field.zerofill ? 'ZEROFILL' : ''}
|
||||
@@ -1072,14 +1126,14 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
if (type === 'PRIMARY')
|
||||
newIndexes.push(`PRIMARY KEY (${fields})`);
|
||||
else if (type === 'UNIQUE')
|
||||
newIndexes.push(`CONSTRAINT ${index.name} UNIQUE (${fields})`);
|
||||
newIndexes.push(`CONSTRAINT "${index.name}" UNIQUE (${fields})`);
|
||||
else
|
||||
manageIndexes.push(`CREATE INDEX ${index.name} ON "${schema}"."${options.name}" (${fields})`);
|
||||
manageIndexes.push(`CREATE INDEX "${index.name}" ON "${schema}"."${options.name}" (${fields})`);
|
||||
});
|
||||
|
||||
// ADD FOREIGN KEYS
|
||||
foreigns.forEach(foreign => {
|
||||
newForeigns.push(`CONSTRAINT ${foreign.constraintName} FOREIGN KEY (${foreign.field}) REFERENCES "${schema}"."${foreign.refTable}" (${foreign.refField}) ON UPDATE ${foreign.onUpdate} ON DELETE ${foreign.onDelete}`);
|
||||
newForeigns.push(`CONSTRAINT "${foreign.constraintName}" FOREIGN KEY ("${foreign.field}") REFERENCES "${schema}"."${foreign.refTable}" ("${foreign.refField}") ON UPDATE ${foreign.onUpdate} ON DELETE ${foreign.onDelete}`);
|
||||
});
|
||||
|
||||
sql = `${sql} (${[...newColumns, ...newIndexes, ...newForeigns].join(', ')})`;
|
||||
@@ -1119,7 +1173,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
const typeInfo = this._getTypeInfo(addition.type);
|
||||
const length = typeInfo.length ? addition.numLength || addition.charLength || addition.datePrecision : false;
|
||||
|
||||
alterColumns.push(`ADD COLUMN ${addition.name}
|
||||
alterColumns.push(`ADD COLUMN "${addition.name}"
|
||||
${addition.type.toUpperCase()}${length ? `(${length})` : ''}${addition.isArray ? '[]' : ''}
|
||||
${addition.unsigned ? 'UNSIGNED' : ''}
|
||||
${addition.zerofill ? 'ZEROFILL' : ''}
|
||||
@@ -1130,20 +1184,20 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
|
||||
// ADD INDEX
|
||||
indexChanges.additions.forEach(addition => {
|
||||
const fields = addition.fields.map(field => `${field}`).join(',');
|
||||
const fields = addition.fields.map(field => `"${field}"`).join(',');
|
||||
const type = addition.type;
|
||||
|
||||
if (type === 'PRIMARY')
|
||||
alterColumns.push(`ADD PRIMARY KEY (${fields})`);
|
||||
else if (type === 'UNIQUE')
|
||||
alterColumns.push(`ADD CONSTRAINT ${addition.name} UNIQUE (${fields})`);
|
||||
alterColumns.push(`ADD CONSTRAINT "${addition.name}" UNIQUE (${fields})`);
|
||||
else
|
||||
manageIndexes.push(`CREATE INDEX ${addition.name} ON "${schema}"."${table}" (${fields})`);
|
||||
manageIndexes.push(`CREATE INDEX "${addition.name}" ON "${schema}"."${table}" (${fields})`);
|
||||
});
|
||||
|
||||
// ADD FOREIGN KEYS
|
||||
foreignChanges.additions.forEach(addition => {
|
||||
alterColumns.push(`ADD CONSTRAINT ${addition.constraintName} FOREIGN KEY (${addition.field}) REFERENCES "${schema}"."${addition.refTable}" (${addition.refField}) ON UPDATE ${addition.onUpdate} ON DELETE ${addition.onDelete}`);
|
||||
alterColumns.push(`ADD CONSTRAINT "${addition.constraintName}" FOREIGN KEY ("${addition.field}") REFERENCES "${schema}"."${addition.refTable}" (${addition.refField}) ON UPDATE ${addition.onUpdate} ON DELETE ${addition.onDelete}`);
|
||||
});
|
||||
|
||||
// CHANGE FIELDS
|
||||
@@ -1169,6 +1223,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
alterColumns.push(`ALTER COLUMN "${change.name}" TYPE ${localType}${length ? `(${length})` : ''}${change.isArray ? '[]' : ''} USING "${change.name}"::${localType}`);
|
||||
alterColumns.push(`ALTER COLUMN "${change.name}" ${change.nullable ? 'DROP NOT NULL' : 'SET NOT NULL'}`);
|
||||
alterColumns.push(`ALTER COLUMN "${change.name}" ${change.default ? `SET DEFAULT ${change.default}` : 'DROP DEFAULT'}`);
|
||||
|
||||
if (['SERIAL', 'SMALLSERIAL', 'BIGSERIAL'].includes(change.type)) {
|
||||
const sequenceName = `${table}_${change.name}_seq`.replace(' ', '_');
|
||||
createSequences.push(`CREATE SEQUENCE IF NOT EXISTS ${sequenceName} OWNED BY "${table}"."${change.name}"`);
|
||||
@@ -1186,39 +1241,39 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
else
|
||||
manageIndexes.push(`DROP INDEX ${change.oldName}`);
|
||||
|
||||
const fields = change.fields.map(field => `${field}`).join(',');
|
||||
const fields = change.fields.map(field => `"${field}"`).join(',');
|
||||
const type = change.type;
|
||||
|
||||
if (type === 'PRIMARY')
|
||||
alterColumns.push(`ADD PRIMARY KEY (${fields})`);
|
||||
else if (type === 'UNIQUE')
|
||||
alterColumns.push(`ADD CONSTRAINT ${change.name} UNIQUE (${fields})`);
|
||||
alterColumns.push(`ADD CONSTRAINT "${change.name}" UNIQUE (${fields})`);
|
||||
else
|
||||
manageIndexes.push(`CREATE INDEX ${change.name} ON "${schema}"."${table}" (${fields})`);
|
||||
manageIndexes.push(`CREATE INDEX "${change.name}" ON "${schema}"."${table}" (${fields})`);
|
||||
});
|
||||
|
||||
// CHANGE FOREIGN KEYS
|
||||
foreignChanges.changes.forEach(change => {
|
||||
alterColumns.push(`DROP CONSTRAINT ${change.oldName}`);
|
||||
alterColumns.push(`ADD CONSTRAINT ${change.constraintName} FOREIGN KEY (${change.field}) REFERENCES "${schema}"."${change.refTable}" (${change.refField}) ON UPDATE ${change.onUpdate} ON DELETE ${change.onDelete}`);
|
||||
alterColumns.push(`DROP CONSTRAINT "${change.oldName}"`);
|
||||
alterColumns.push(`ADD CONSTRAINT "${change.constraintName}" FOREIGN KEY (${change.field}) REFERENCES "${schema}"."${change.refTable}" ("${change.refField}") ON UPDATE ${change.onUpdate} ON DELETE ${change.onDelete}`);
|
||||
});
|
||||
|
||||
// DROP FIELDS
|
||||
deletions.forEach(deletion => {
|
||||
alterColumns.push(`DROP COLUMN ${deletion.name}`);
|
||||
alterColumns.push(`DROP COLUMN "${deletion.name}"`);
|
||||
});
|
||||
|
||||
// DROP INDEX
|
||||
indexChanges.deletions.forEach(deletion => {
|
||||
if (['PRIMARY', 'UNIQUE'].includes(deletion.type))
|
||||
alterColumns.push(`DROP CONSTRAINT ${deletion.name}`);
|
||||
alterColumns.push(`DROP CONSTRAINT "${deletion.name}"`);
|
||||
else
|
||||
manageIndexes.push(`DROP INDEX ${deletion.name}`);
|
||||
manageIndexes.push(`DROP INDEX "${deletion.name}"`);
|
||||
});
|
||||
|
||||
// DROP FOREIGN KEYS
|
||||
foreignChanges.deletions.forEach(deletion => {
|
||||
alterColumns.push(`DROP CONSTRAINT ${deletion.constraintName}`);
|
||||
alterColumns.push(`DROP CONSTRAINT "${deletion.constraintName}"`);
|
||||
});
|
||||
|
||||
if (alterColumns.length) sql += `ALTER TABLE "${schema}"."${table}" ${alterColumns.join(', ')}; `;
|
||||
@@ -1239,7 +1294,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
* @memberof PostgreSQLClient
|
||||
*/
|
||||
async duplicateTable (params) {
|
||||
const sql = `CREATE TABLE ${params.schema}.${params.table}_copy (LIKE ${params.schema}.${params.table} INCLUDING ALL)`;
|
||||
const sql = `CREATE TABLE "${params.schema}"."${params.table}_copy" (LIKE "${params.schema}"."${params.table}" INCLUDING ALL)`;
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
@@ -1250,7 +1305,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
* @memberof PostgreSQLClient
|
||||
*/
|
||||
async truncateTable (params) {
|
||||
const sql = `TRUNCATE TABLE ${params.schema}.${params.table}`;
|
||||
const sql = `TRUNCATE TABLE "${params.schema}"."${params.table}"`;
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
@@ -1261,7 +1316,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
* @memberof PostgreSQLClient
|
||||
*/
|
||||
async dropTable (params) {
|
||||
const sql = `DROP TABLE ${params.schema}.${params.table}`;
|
||||
const sql = `DROP TABLE "${params.schema}"."${params.table}"`;
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
@@ -1285,7 +1340,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
else if (Object.keys(this._query.insert).length)
|
||||
fromRaw = 'INTO';
|
||||
|
||||
fromRaw += this._query.from ? ` ${this._query.schema ? `${this._query.schema}.` : ''}${this._query.from} ` : '';
|
||||
fromRaw += this._query.from ? ` ${this._query.schema ? `"${this._query.schema}".` : ''}"${this._query.from}" ` : '';
|
||||
|
||||
// WHERE
|
||||
const whereArray = this._query.where.reduce(this._reducer, []);
|
||||
|
162
src/main/main.js
Normal file
@@ -0,0 +1,162 @@
|
||||
'use strict';
|
||||
|
||||
import { app, BrowserWindow, /* session, */ nativeImage, Menu } from 'electron';
|
||||
import * as path from 'path';
|
||||
import Store from 'electron-store';
|
||||
import * as remoteMain from '@electron/remote/main';
|
||||
|
||||
import ipcHandlers from './ipc-handlers';
|
||||
|
||||
// remoteMain.initialize();
|
||||
Store.initRenderer();
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||
const isMacOS = process.platform === 'darwin';
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
|
||||
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
|
||||
|
||||
// global reference to mainWindow (necessary to prevent window from being garbage collected)
|
||||
let mainWindow;
|
||||
|
||||
async function createMainWindow () {
|
||||
const icon = require('../renderer/images/logo-32.png');
|
||||
const window = new BrowserWindow({
|
||||
width: 1024,
|
||||
height: 800,
|
||||
minWidth: 900,
|
||||
minHeight: 550,
|
||||
title: 'Antares SQL',
|
||||
autoHideMenuBar: true,
|
||||
icon: nativeImage.createFromDataURL(icon.default),
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
'web-security': false,
|
||||
spellcheck: false
|
||||
},
|
||||
frame: false,
|
||||
titleBarStyle: isMacOS ? 'hidden' : 'default',
|
||||
trafficLightPosition: isMacOS ? { x: 10, y: 8 } : undefined,
|
||||
backgroundColor: '#1d1d1d'
|
||||
});
|
||||
|
||||
remoteMain.enable(window.webContents);
|
||||
|
||||
try {
|
||||
if (isDevelopment) {
|
||||
const { default: installExtension, VUEJS3_DEVTOOLS } = require('electron-devtools-installer');
|
||||
const options = {
|
||||
loadExtensionOptions: { allowFileAccess: true }
|
||||
};
|
||||
|
||||
try {
|
||||
const name = await installExtension(VUEJS3_DEVTOOLS, options);
|
||||
console.log(`Added Extension: ${name}`);
|
||||
}
|
||||
catch (err) {
|
||||
console.log('An error occurred: ', err);
|
||||
}
|
||||
|
||||
await window.loadURL('http://localhost:9080');
|
||||
}
|
||||
else {
|
||||
const indexPath = path.resolve(__dirname, 'index.html');
|
||||
await window.loadFile(indexPath);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
window.on('closed', () => {
|
||||
mainWindow = null;
|
||||
});
|
||||
|
||||
window.webContents.on('devtools-opened', () => {
|
||||
window.focus();
|
||||
setImmediate(() => {
|
||||
window.focus();
|
||||
});
|
||||
});
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
if (!gotTheLock) app.quit();
|
||||
else {
|
||||
require('@electron/remote/main').initialize();
|
||||
|
||||
// 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 (isMacOS) app.quit();
|
||||
});
|
||||
|
||||
app.on('activate', async () => {
|
||||
// on macOS it is common to re-create a window even after all windows have been closed
|
||||
if (mainWindow === null)
|
||||
mainWindow = await createMainWindow();
|
||||
});
|
||||
|
||||
// create main BrowserWindow when electron is ready
|
||||
app.on('ready', async () => {
|
||||
mainWindow = await createMainWindow();
|
||||
createAppMenu();
|
||||
|
||||
if (isDevelopment)
|
||||
mainWindow.webContents.openDevTools();
|
||||
|
||||
process.on('uncaughtException', (error) => {
|
||||
mainWindow.webContents.send('unhandled-exception', error);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (error) => {
|
||||
mainWindow.webContents.send('unhandled-exception', error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createAppMenu () {
|
||||
let menu = null;
|
||||
|
||||
if (isMacOS) {
|
||||
menu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: app.name,
|
||||
submenu: [
|
||||
{ role: 'about' },
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Check for Updates...',
|
||||
click: (_menuItem, win) => win.webContents.send('open-updates-preferences')
|
||||
},
|
||||
{
|
||||
label: 'Preferences',
|
||||
click: (_menuItem, win) => win.webContents.send('toggle-preferences'),
|
||||
accelerator: 'CmdOrCtrl+,'
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{ role: 'hide' },
|
||||
{ role: 'hideOthers' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'quit' }
|
||||
]
|
||||
},
|
||||
{
|
||||
role: 'editMenu'
|
||||
},
|
||||
{
|
||||
role: 'viewMenu'
|
||||
},
|
||||
{
|
||||
role: 'windowMenu'
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
Menu.setApplicationMenu(menu);
|
||||
}
|
@@ -89,7 +89,7 @@
|
||||
:type="inputProps().type"
|
||||
:disabled="!isChecked"
|
||||
>
|
||||
<template v-if="methodData && 'params' in methodData" class="columns">
|
||||
<template v-if="methodData && 'params' in methodData">
|
||||
<input
|
||||
v-for="(option, key) in methodData.params"
|
||||
:key="key"
|
||||
|
@@ -17,7 +17,7 @@
|
||||
<form class="form-horizontal">
|
||||
<div
|
||||
v-for="(parameter, i) in inParameters"
|
||||
:key="parameter._id"
|
||||
:key="parameter._antares_id"
|
||||
class="form-group"
|
||||
>
|
||||
<div class="col-4">
|
||||
|
283
src/renderer/components/ModalHistory.vue
Normal file
@@ -0,0 +1,283 @@
|
||||
<template>
|
||||
<div class="modal active">
|
||||
<a class="modal-overlay" @click.stop="closeModal" />
|
||||
<div class="modal-container p-0 pb-4">
|
||||
<div class="modal-header pl-2">
|
||||
<div class="modal-title h6">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-history mr-1" />
|
||||
<span class="cut-text">{{ $t('word.history') }}: {{ connectionName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||
</div>
|
||||
<div class="modal-body p-0 workspace-query-results">
|
||||
<div
|
||||
v-if="history.length"
|
||||
ref="searchForm"
|
||||
class="form-group has-icon-right p-2 m-0"
|
||||
>
|
||||
<input
|
||||
v-model="searchTerm"
|
||||
class="form-input"
|
||||
type="text"
|
||||
:placeholder="$t('message.searchForQueries')"
|
||||
>
|
||||
<i v-if="!searchTerm" class="form-icon mdi mdi-magnify mdi-18px pr-4" />
|
||||
<i
|
||||
v-else
|
||||
class="form-icon c-hand mdi mdi-backspace mdi-18px pr-4"
|
||||
@click="searchTerm = ''"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="history.length"
|
||||
ref="tableWrapper"
|
||||
class="vscroll px-1 "
|
||||
:style="{'height': resultsSize+'px'}"
|
||||
>
|
||||
<div ref="table">
|
||||
<BaseVirtualScroll
|
||||
ref="resultTable"
|
||||
:items="filteredHistory"
|
||||
:item-height="66"
|
||||
:visible-height="resultsSize"
|
||||
:scroll-element="scrollElement"
|
||||
>
|
||||
<template slot-scope="{ items }">
|
||||
<div
|
||||
v-for="query in items"
|
||||
:key="query.uid"
|
||||
class="tile my-2"
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="tile-icon">
|
||||
<i class="mdi mdi-code-tags pr-1" />
|
||||
</div>
|
||||
<div class="tile-content">
|
||||
<div class="tile-title">
|
||||
<code
|
||||
class="cut-text"
|
||||
:title="query.sql"
|
||||
v-html="highlightWord(query.sql)"
|
||||
/>
|
||||
</div>
|
||||
<div class="tile-bottom-content">
|
||||
<small class="tile-subtitle">{{ query.schema }} · {{ formatDate(query.date) }}</small>
|
||||
<div class="tile-history-buttons">
|
||||
<button class="btn btn-link pl-1" @click.stop="$emit('select-query', query.sql)">
|
||||
<i class="mdi mdi-open-in-app pr-1" /> {{ $t('word.select') }}
|
||||
</button>
|
||||
<button class="btn btn-link pl-1" @click="copyQuery(query.sql)">
|
||||
<i class="mdi mdi-content-copy pr-1" /> {{ $t('word.copy') }}
|
||||
</button>
|
||||
<button class="btn btn-link pl-1" @click="deleteQuery(query)">
|
||||
<i class="mdi mdi-delete-forever pr-1" /> {{ $t('word.delete') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</BaseVirtualScroll>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="empty">
|
||||
<div class="empty-icon">
|
||||
<i class="mdi mdi-history mdi-48px" />
|
||||
</div>
|
||||
<p class="empty-title h5">
|
||||
{{ $t('message.thereIsNoQueriesYet') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
|
||||
|
||||
export default {
|
||||
name: 'ModalHistory',
|
||||
components: {
|
||||
BaseVirtualScroll
|
||||
},
|
||||
props: {
|
||||
connection: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
resultsSize: 1000,
|
||||
isQuering: false,
|
||||
scrollElement: null,
|
||||
searchTermInterval: null,
|
||||
searchTerm: '',
|
||||
localSearchTerm: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
getConnectionName: 'connections/getConnectionName',
|
||||
getHistoryByWorkspace: 'history/getHistoryByWorkspace'
|
||||
}),
|
||||
connectionName () {
|
||||
return this.getConnectionName(this.connection.uid);
|
||||
},
|
||||
history () {
|
||||
return this.getHistoryByWorkspace(this.connection.uid) || [];
|
||||
},
|
||||
filteredHistory () {
|
||||
return this.history.filter(q => q.sql.toLowerCase().search(this.searchTerm.toLowerCase()) >= 0);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
searchTerm () {
|
||||
clearTimeout(this.searchTermInterval);
|
||||
|
||||
this.searchTermInterval = setTimeout(() => {
|
||||
this.localSearchTerm = this.searchTerm;
|
||||
}, 200);
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey, { capture: true });
|
||||
},
|
||||
updated () {
|
||||
if (this.$refs.table)
|
||||
this.refreshScroller();
|
||||
|
||||
if (this.$refs.tableWrapper)
|
||||
this.scrollElement = this.$refs.tableWrapper;
|
||||
},
|
||||
mounted () {
|
||||
this.resizeResults();
|
||||
window.addEventListener('resize', this.resizeResults);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey, { capture: true });
|
||||
window.removeEventListener('resize', this.resizeResults);
|
||||
clearInterval(this.refreshInterval);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
deleteQueryFromHistory: 'history/deleteQueryFromHistory'
|
||||
}),
|
||||
copyQuery (sql) {
|
||||
navigator.clipboard.writeText(sql);
|
||||
},
|
||||
deleteQuery (query) {
|
||||
this.deleteQueryFromHistory({
|
||||
workspace: this.connection.uid,
|
||||
...query
|
||||
});
|
||||
},
|
||||
resizeResults () {
|
||||
if (this.$refs.resultTable) {
|
||||
const el = this.$refs.tableWrapper.parentElement;
|
||||
|
||||
if (el)
|
||||
this.resultsSize = el.offsetHeight - this.$refs.searchForm.offsetHeight;
|
||||
|
||||
this.$refs.resultTable.updateWindow();
|
||||
}
|
||||
},
|
||||
formatDate (date) {
|
||||
return moment(date).isValid() ? moment(date).format('HH:mm:ss - YYYY/MM/DD') : date;
|
||||
},
|
||||
refreshScroller () {
|
||||
this.resizeResults();
|
||||
},
|
||||
closeModal () {
|
||||
this.$emit('close');
|
||||
},
|
||||
highlightWord (string) {
|
||||
string = string.replaceAll('<', '<').replaceAll('>', '>');
|
||||
|
||||
if (this.searchTerm) {
|
||||
const regexp = new RegExp(`(${this.searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
|
||||
return string.replace(regexp, '<span class="text-primary text-bold">$1</span>');
|
||||
}
|
||||
else
|
||||
return string;
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vscroll {
|
||||
height: 1000px;
|
||||
overflow: auto;
|
||||
overflow-anchor: none;
|
||||
}
|
||||
|
||||
.tile {
|
||||
border-radius: $border-radius;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
.tile-content {
|
||||
.tile-bottom-content {
|
||||
.tile-history-buttons {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tile-icon {
|
||||
font-size: 1.2rem;
|
||||
margin-left: 0.3rem;
|
||||
width: 28px;
|
||||
}
|
||||
|
||||
.tile-content {
|
||||
padding: 0.3rem;
|
||||
padding-left: 0.1rem;
|
||||
max-width: calc(100% - 30px);
|
||||
|
||||
code {
|
||||
max-width: 100%;
|
||||
display: inline-block;
|
||||
font-size: 100%;
|
||||
// color: $primary-color;
|
||||
opacity: 0.8;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tile-subtitle {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.tile-bottom-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.tile-history-buttons {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
button {
|
||||
font-size: 0.7rem;
|
||||
height: 1rem;
|
||||
line-height: 1rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -13,7 +13,7 @@
|
||||
</div>
|
||||
<div class="modal-body pb-0">
|
||||
<div class="content">
|
||||
<form class="form-horizontal">
|
||||
<form class="form-horizontal" @submit.prevent="createSchema">
|
||||
<div class="form-group">
|
||||
<div class="col-3">
|
||||
<label class="form-label">{{ $t('word.name') }}</label>
|
||||
|
@@ -1,5 +1,15 @@
|
||||
<template>
|
||||
<div class="modal active">
|
||||
<ModalProcessesListContext
|
||||
v-if="isContext"
|
||||
:context-event="contextEvent"
|
||||
:selected-row="selectedRow"
|
||||
:selected-cell="selectedCell"
|
||||
@copy-cell="copyCell"
|
||||
@copy-row="copyRow"
|
||||
@kill-process="killProcess"
|
||||
@close-context="closeContext"
|
||||
/>
|
||||
<a class="modal-overlay" @click.stop="closeModal" />
|
||||
<div class="modal-container p-0 pb-4">
|
||||
<div class="modal-header pl-2">
|
||||
@@ -12,34 +22,54 @@
|
||||
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||
</div>
|
||||
<div class="processes-toolbar py-2 px-4">
|
||||
<div class="dropdown">
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-dark btn-sm mr-0 pr-1 d-flex"
|
||||
:class="{'loading':isQuering}"
|
||||
title="F5"
|
||||
@click="getProcessesList"
|
||||
>
|
||||
<i v-if="!+autorefreshTimer" class="mdi mdi-24px mdi-refresh mr-1" />
|
||||
<i v-else class="mdi mdi-24px mdi-history mdi-flip-h mr-1" />
|
||||
<span>{{ $t('word.refresh') }}</span>
|
||||
</button>
|
||||
<div class="btn btn-dark btn-sm dropdown-toggle pl-0 pr-0" tabindex="0">
|
||||
<i class="mdi mdi-24px mdi-menu-down" />
|
||||
</div>
|
||||
<div class="menu px-3">
|
||||
<span>{{ $t('word.autoRefresh') }}: <b>{{ +autorefreshTimer ? `${autorefreshTimer}s` : 'OFF' }}</b></span>
|
||||
<input
|
||||
v-model="autorefreshTimer"
|
||||
class="slider no-border"
|
||||
type="range"
|
||||
min="0"
|
||||
max="15"
|
||||
step="0.5"
|
||||
@change="setRefreshInterval"
|
||||
<div class="workspace-query-buttons">
|
||||
<div class="dropdown pr-1">
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-dark btn-sm mr-0 pr-1 d-flex"
|
||||
:class="{'loading':isQuering}"
|
||||
:title="`${$t('word.refresh')} (F5)`"
|
||||
@click="getProcessesList"
|
||||
>
|
||||
<i v-if="!+autorefreshTimer" class="mdi mdi-24px mdi-refresh mr-1" />
|
||||
<i v-else class="mdi mdi-24px mdi-history mdi-flip-h mr-1" />
|
||||
</button>
|
||||
<div class="btn btn-dark btn-sm dropdown-toggle pl-0 pr-0" tabindex="0">
|
||||
<i class="mdi mdi-24px mdi-menu-down" />
|
||||
</div>
|
||||
<div class="menu px-3">
|
||||
<span>{{ $t('word.autoRefresh') }}: <b>{{ +autorefreshTimer ? `${autorefreshTimer}s` : 'OFF' }}</b></span>
|
||||
<input
|
||||
v-model="autorefreshTimer"
|
||||
class="slider no-border"
|
||||
type="range"
|
||||
min="0"
|
||||
max="15"
|
||||
step="0.5"
|
||||
@change="setRefreshInterval"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown table-dropdown">
|
||||
<button
|
||||
:disabled="isQuering"
|
||||
class="btn btn-dark btn-sm dropdown-toggle d-flex mr-0 pr-0"
|
||||
tabindex="0"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-file-export mr-1" />
|
||||
<span>{{ $t('word.export') }}</span>
|
||||
<i class="mdi mdi-24px mdi-menu-down" />
|
||||
</button>
|
||||
<ul class="menu text-left">
|
||||
<li class="menu-item">
|
||||
<a class="c-hand" @click="downloadTable('json')">JSON</a>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a class="c-hand" @click="downloadTable('csv')">CSV</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workspace-query-info">
|
||||
<div v-if="sortedResults.length">
|
||||
@@ -85,9 +115,10 @@
|
||||
<template slot-scope="{ items }">
|
||||
<ModalProcessesListRow
|
||||
v-for="row in items"
|
||||
:key="row._id"
|
||||
:key="row.id"
|
||||
class="process-row"
|
||||
:row="row"
|
||||
@select-row="selectRow(row.id)"
|
||||
@contextmenu="contextMenu"
|
||||
@stop-refresh="stopRefresh"
|
||||
/>
|
||||
@@ -102,15 +133,18 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import arrayToFile from '../libs/arrayToFile';
|
||||
import Schema from '@/ipc-api/Schema';
|
||||
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
|
||||
import ModalProcessesListRow from '@/components/ModalProcessesListRow';
|
||||
import ModalProcessesListContext from '@/components/ModalProcessesListContext';
|
||||
|
||||
export default {
|
||||
name: 'ModalProcessesList',
|
||||
components: {
|
||||
BaseVirtualScroll,
|
||||
ModalProcessesListRow
|
||||
ModalProcessesListRow,
|
||||
ModalProcessesListContext
|
||||
},
|
||||
props: {
|
||||
connection: Object
|
||||
@@ -119,8 +153,12 @@ export default {
|
||||
return {
|
||||
resultsSize: 1000,
|
||||
isQuering: false,
|
||||
isContext: false,
|
||||
autorefreshTimer: 0,
|
||||
refreshInterval: null,
|
||||
contextEvent: null,
|
||||
selectedCell: null,
|
||||
selectedRow: null,
|
||||
results: [],
|
||||
fields: [],
|
||||
currentSort: '',
|
||||
@@ -245,10 +283,55 @@ export default {
|
||||
this.autorefreshTimer = 0;
|
||||
this.clearRefresh();
|
||||
},
|
||||
contextMenu () {},
|
||||
selectRow (row) {
|
||||
this.selectedRow = row;
|
||||
},
|
||||
contextMenu (event, cell) {
|
||||
if (event.target.localName === 'input') return;
|
||||
this.stopRefresh();
|
||||
|
||||
this.selectedCell = cell;
|
||||
this.selectedRow = cell.id;
|
||||
this.contextEvent = event;
|
||||
this.isContext = true;
|
||||
},
|
||||
async killProcess () {
|
||||
try { // Table data
|
||||
const { status, response } = await Schema.killProcess({ uid: this.connection.uid, pid: this.selectedRow });
|
||||
|
||||
if (status === 'success')
|
||||
this.getProcessesList();
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
},
|
||||
closeContext () {
|
||||
this.isContext = false;
|
||||
},
|
||||
copyCell () {
|
||||
const row = this.results.find(row => row.id === this.selectedRow);
|
||||
const valueToCopy = row[this.selectedCell.field];
|
||||
navigator.clipboard.writeText(valueToCopy);
|
||||
},
|
||||
copyRow () {
|
||||
const row = this.results.find(row => row.id === this.selectedRow);
|
||||
const rowToCopy = JSON.parse(JSON.stringify(row));
|
||||
navigator.clipboard.writeText(JSON.stringify(rowToCopy));
|
||||
},
|
||||
closeModal () {
|
||||
this.$emit('close');
|
||||
},
|
||||
downloadTable (format) {
|
||||
if (!this.sortedResults) return;
|
||||
arrayToFile({
|
||||
type: format,
|
||||
content: this.sortedResults,
|
||||
filename: 'processes'
|
||||
});
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
|
75
src/renderer/components/ModalProcessesListContext.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<BaseContextMenu
|
||||
:context-event="contextEvent"
|
||||
@close-context="closeContext"
|
||||
>
|
||||
<div v-if="selectedRow" class="context-element">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-content-copy text-light pr-1" /> {{ $t('word.copy') }}</span>
|
||||
<i class="mdi mdi-18px mdi-chevron-right text-light pl-1" />
|
||||
<div class="context-submenu">
|
||||
<div
|
||||
v-if="selectedRow"
|
||||
class="context-element"
|
||||
@click="copyCell"
|
||||
>
|
||||
<span class="d-flex">
|
||||
<i class="mdi mdi-18px mdi-numeric-0 mdi-rotate-90 text-light pr-1" /> {{ $tc('word.cell', 1) }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedRow"
|
||||
class="context-element"
|
||||
@click="copyRow"
|
||||
>
|
||||
<span class="d-flex">
|
||||
<i class="mdi mdi-18px mdi-table-row text-light pr-1" /> {{ $tc('word.row', 1) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedRow"
|
||||
class="context-element"
|
||||
@click="killProcess"
|
||||
>
|
||||
<span class="d-flex">
|
||||
<i class="mdi mdi-18px mdi-close-circle-outline text-light pr-1" /> {{ $t('message.killProcess') }}
|
||||
</span>
|
||||
</div>
|
||||
</BaseContextMenu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseContextMenu from '@/components/BaseContextMenu';
|
||||
|
||||
export default {
|
||||
name: 'ModalProcessesListContext',
|
||||
components: {
|
||||
BaseContextMenu
|
||||
},
|
||||
props: {
|
||||
contextEvent: MouseEvent,
|
||||
selectedRow: Number,
|
||||
selectedCell: Object
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
closeContext () {
|
||||
this.$emit('close-context');
|
||||
},
|
||||
copyCell () {
|
||||
this.$emit('copy-cell');
|
||||
this.closeContext();
|
||||
},
|
||||
copyRow () {
|
||||
this.$emit('copy-row');
|
||||
this.closeContext();
|
||||
},
|
||||
killProcess () {
|
||||
this.$emit('kill-process');
|
||||
this.closeContext();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -1,21 +1,18 @@
|
||||
<template>
|
||||
<div class="tr" @click="selectRow($event, row._id)">
|
||||
<div class="tr" @click="selectRow()">
|
||||
<div
|
||||
v-for="(col, cKey) in row"
|
||||
v-show="cKey !== '_id'"
|
||||
:key="cKey"
|
||||
class="td p-0"
|
||||
tabindex="0"
|
||||
@contextmenu.prevent="openContext($event, { id: row._id, field: cKey })"
|
||||
@contextmenu.prevent="openContext($event, { id: row.id, field: cKey })"
|
||||
>
|
||||
<template v-if="cKey !== '_id'">
|
||||
<span
|
||||
v-if="!isInlineEditor[cKey]"
|
||||
class="cell-content"
|
||||
:class="`${isNull(col)} type-${typeof col === 'number' ? 'int' : 'varchar'}`"
|
||||
@dblclick="dblClick(cKey)"
|
||||
>{{ col | cutText }}</span>
|
||||
</template>
|
||||
<span
|
||||
v-if="!isInlineEditor[cKey]"
|
||||
class="cell-content"
|
||||
:class="`${isNull(col)} type-${typeof col === 'number' ? 'int' : 'varchar'}`"
|
||||
@dblclick="dblClick(cKey)"
|
||||
>{{ col | cutText }}</span>
|
||||
</div>
|
||||
<ConfirmModal
|
||||
v-if="isInfoModal"
|
||||
@@ -73,25 +70,15 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {},
|
||||
watch: {
|
||||
fields () {
|
||||
Object.keys(this.fields).forEach(field => {
|
||||
this.isInlineEditor[field.name] = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isNull (value) {
|
||||
return value === null ? ' is-null' : '';
|
||||
},
|
||||
selectRow (event, row) {
|
||||
this.$emit('select-row', event, row);
|
||||
selectRow () {
|
||||
this.$emit('select-row');
|
||||
},
|
||||
openContext (event, payload) {
|
||||
if (this.isEditable) {
|
||||
payload.field = this.fields[payload.field].name;// Ensures field name only
|
||||
this.$emit('contextmenu', event, payload);
|
||||
}
|
||||
this.$emit('contextmenu', event, payload);
|
||||
},
|
||||
hideInfoModal () {
|
||||
this.isInfoModal = false;
|
||||
|
@@ -184,7 +184,7 @@
|
||||
:class="{'selected': applicationTheme === 'dark'}"
|
||||
@click="changeApplicationTheme('dark')"
|
||||
>
|
||||
<img :src="require('@/images/dark.png').default" class="img-responsive img-fit-cover s-rounded">
|
||||
<img src="../images/dark.png" class="img-responsive img-fit-cover s-rounded">
|
||||
<div class="theme-name text-light">
|
||||
<i class="mdi mdi-moon-waning-crescent mdi-48px" />
|
||||
<div class="h6 mt-4">
|
||||
@@ -197,7 +197,7 @@
|
||||
:class="{'selected': applicationTheme === 'light'}"
|
||||
@click="changeApplicationTheme('light')"
|
||||
>
|
||||
<img :src="require('@/images/light.png').default" class="img-responsive img-fit-cover s-rounded">
|
||||
<img src="../images/light.png" class="img-responsive img-fit-cover s-rounded">
|
||||
<div class="theme-name text-dark">
|
||||
<i class="mdi mdi-white-balance-sunny mdi-48px" />
|
||||
<div class="h6 mt-4">
|
||||
@@ -280,7 +280,7 @@
|
||||
|
||||
<div v-show="selectedTab === 'about'" class="panel-body py-4">
|
||||
<div class="text-center">
|
||||
<img :src="require('@/images/logo.svg').default" width="128">
|
||||
<img src="../images/logo.svg" width="128">
|
||||
<h4>{{ appName }}</h4>
|
||||
<p>
|
||||
{{ $t('word.version') }} {{ appVersion }}<br>
|
||||
@@ -396,7 +396,7 @@ export default {
|
||||
return locales;
|
||||
},
|
||||
hasUpdates () {
|
||||
return ['available', 'downloading', 'downloaded'].includes(this.updateStatus);
|
||||
return ['available', 'downloading', 'downloaded', 'link'].includes(this.updateStatus);
|
||||
},
|
||||
workspace () {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
|
@@ -16,7 +16,7 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import marked from 'marked';
|
||||
import { marked } from 'marked';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
|
||||
export default {
|
||||
|
@@ -29,12 +29,19 @@
|
||||
{{ $t('message.checkForUpdates') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="updateStatus === 'downloaded'"
|
||||
v-else-if="updateStatus === 'downloaded'"
|
||||
class="btn btn-primary"
|
||||
@click="restartToUpdate"
|
||||
>
|
||||
{{ $t('message.restartToInstall') }}
|
||||
</button>
|
||||
<button
|
||||
v-else-if="updateStatus === 'link'"
|
||||
class="btn btn-primary"
|
||||
@click="openOutside('https://antares-sql.app/download.html')"
|
||||
>
|
||||
{{ $t('message.goToDownloadPage') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-group mt-4">
|
||||
<label class="form-switch d-inline-block disabled" @click.prevent="toggleAllowPrerelease">
|
||||
@@ -47,7 +54,7 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { ipcRenderer, shell } from 'electron';
|
||||
|
||||
export default {
|
||||
name: 'ModalSettingsUpdate',
|
||||
@@ -71,6 +78,8 @@ export default {
|
||||
return this.$t('message.downloadingUpdate');
|
||||
case 'downloaded':
|
||||
return this.$t('message.updateDownloaded');
|
||||
case 'link':
|
||||
return this.$t('message.updateAvailable');
|
||||
default:
|
||||
return this.updateStatus;
|
||||
}
|
||||
@@ -80,6 +89,9 @@ export default {
|
||||
...mapActions({
|
||||
changeAllowPrerelease: 'settings/changeAllowPrerelease'
|
||||
}),
|
||||
openOutside (link) {
|
||||
shell.openExternal(link);
|
||||
},
|
||||
checkForUpdates () {
|
||||
ipcRenderer.send('check-for-updates');
|
||||
},
|
||||
|
@@ -92,7 +92,7 @@ export default {
|
||||
}
|
||||
},
|
||||
hasUpdates () {
|
||||
return ['available', 'downloading', 'downloaded'].includes(this.updateStatus);
|
||||
return ['available', 'downloading', 'downloaded', 'link'].includes(this.updateStatus);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@@ -1,8 +1,12 @@
|
||||
<template>
|
||||
<div id="titlebar">
|
||||
<div id="titlebar" @dblclick="toggleFullScreen">
|
||||
<div class="titlebar-resizer" />
|
||||
<div class="titlebar-elements">
|
||||
<img class="titlebar-logo" :src="require('@/images/logo.svg').default">
|
||||
<img
|
||||
v-if="!isMacOS"
|
||||
class="titlebar-logo"
|
||||
src="@/images/logo.svg"
|
||||
>
|
||||
</div>
|
||||
<div class="titlebar-elements titlebar-title">
|
||||
{{ windowTitle }}
|
||||
@@ -22,14 +26,26 @@
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-refresh" />
|
||||
</div>
|
||||
<div class="titlebar-element" @click="minimizeApp">
|
||||
<div
|
||||
v-if="!isMacOS"
|
||||
class="titlebar-element"
|
||||
@click="minimizeApp"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-minus" />
|
||||
</div>
|
||||
<div class="titlebar-element" @click="toggleFullScreen">
|
||||
<div
|
||||
v-if="!isMacOS"
|
||||
class="titlebar-element"
|
||||
@click="toggleFullScreen"
|
||||
>
|
||||
<i v-if="isMaximized" class="mdi mdi-24px mdi-fullscreen-exit" />
|
||||
<i v-else class="mdi mdi-24px mdi-fullscreen" />
|
||||
</div>
|
||||
<div class="titlebar-element close-button" @click="closeApp">
|
||||
<div
|
||||
v-if="!isMacOS"
|
||||
class="titlebar-element close-button"
|
||||
@click="closeApp"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-close" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -47,7 +63,8 @@ export default {
|
||||
return {
|
||||
w: getCurrentWindow(),
|
||||
isMaximized: getCurrentWindow().isMaximized(),
|
||||
isDevelopment: process.env.NODE_ENV === 'development'
|
||||
isDevelopment: process.env.NODE_ENV === 'development',
|
||||
isMacOS: process.platform === 'darwin'
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@@ -586,11 +586,15 @@ export default {
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
await this.addWorkspace(this.connection.uid);
|
||||
const isInitiated = await Connection.checkConnection(this.connection.uid);
|
||||
if (isInitiated)
|
||||
this.connectWorkspace(this.connection);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addWorkspace: 'workspaces/addWorkspace',
|
||||
@@ -604,6 +608,25 @@ export default {
|
||||
addQueryTab () {
|
||||
this.newTab({ uid: this.connection.uid, type: 'query' });
|
||||
},
|
||||
getSelectedTab () {
|
||||
return this.workspace.tabs.find(tab => tab.uid === this.selectedTab);
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
|
||||
if (!this.isSelected)
|
||||
return;
|
||||
|
||||
if ((e.ctrlKey || e.metaKey) && e.keyCode === 84 && !e.altKey) { // CTRL|Command + t
|
||||
this.addQueryTab();
|
||||
}
|
||||
|
||||
if ((e.ctrlKey || e.metaKey) && e.keyCode === 87 && !e.altKey) { // CTRL|Command + w
|
||||
const currentTab = this.getSelectedTab();
|
||||
if (currentTab)
|
||||
this.closeTab(currentTab);
|
||||
}
|
||||
},
|
||||
openAsPermanentTab (tab) {
|
||||
const permanentTabs = {
|
||||
table: 'data',
|
||||
|
@@ -309,6 +309,18 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.passphrase') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
v-model="connection.sshPassphrase"
|
||||
class="form-input"
|
||||
type="password"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
@@ -393,6 +405,13 @@ export default {
|
||||
return this.isConnecting || this.isTesting;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'connection.client' () {
|
||||
this.connection.user = this.customizations.defaultUser;
|
||||
this.connection.port = this.customizations.defaultPort;
|
||||
this.connection.database = this.customizations.defaultDatabase;
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.setDefaults();
|
||||
|
||||
@@ -432,7 +451,7 @@ export default {
|
||||
try {
|
||||
const res = await Connection.makeTest(this.connection);
|
||||
if (res.status === 'error')
|
||||
this.addNotification({ status: 'error', message: res.response.message });
|
||||
this.addNotification({ status: 'error', message: res.response.message || res.response.toString() });
|
||||
else
|
||||
this.addNotification({ status: 'success', message: this.$t('message.connectionSuccessfullyMade') });
|
||||
}
|
||||
@@ -455,7 +474,7 @@ export default {
|
||||
else {
|
||||
const res = await Connection.makeTest(params);
|
||||
if (res.status === 'error')
|
||||
this.addNotification({ status: 'error', message: res.response.message });
|
||||
this.addNotification({ status: 'error', message: res.response.message || res.response.toString() });
|
||||
else
|
||||
this.addNotification({ status: 'success', message: this.$t('message.connectionSuccessfullyMade') });
|
||||
}
|
||||
|
@@ -303,6 +303,18 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.passphrase') }}</label>
|
||||
</div>
|
||||
<div class="column col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.sshPassphrase"
|
||||
class="form-input"
|
||||
type="password"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
@@ -414,7 +426,7 @@ export default {
|
||||
try {
|
||||
const res = await Connection.makeTest(this.localConnection);
|
||||
if (res.status === 'error')
|
||||
this.addNotification({ status: 'error', message: res.response.message });
|
||||
this.addNotification({ status: 'error', message: res.response.message || res.response.toString() });
|
||||
else
|
||||
this.addNotification({ status: 'success', message: this.$t('message.connectionSuccessfullyMade') });
|
||||
}
|
||||
@@ -437,7 +449,7 @@ export default {
|
||||
else {
|
||||
const res = await Connection.makeTest(params);
|
||||
if (res.status === 'error')
|
||||
this.addNotification({ status: 'error', message: res.response.message });
|
||||
this.addNotification({ status: 'error', message: res.response.message || res.response.toString() });
|
||||
else
|
||||
this.addNotification({ status: 'success', message: this.$t('message.connectionSuccessfullyMade') });
|
||||
}
|
||||
@@ -454,6 +466,7 @@ export default {
|
||||
closeAsking () {
|
||||
this.isTesting = false;
|
||||
this.isAsking = false;
|
||||
this.isConnecting = false;
|
||||
},
|
||||
selectTab (tab) {
|
||||
this.selectedTab = tab;
|
||||
|
@@ -1,7 +1,16 @@
|
||||
<template>
|
||||
<div class="column col-12 empty">
|
||||
<div class="empty-icon">
|
||||
<img :src="require(`@/images/logo-${applicationTheme}.svg`).default" width="200">
|
||||
<img
|
||||
v-if="applicationTheme === 'dark'"
|
||||
src="../images/logo-dark.svg"
|
||||
width="200"
|
||||
>
|
||||
<img
|
||||
v-if="applicationTheme === 'light'"
|
||||
src="../images/logo-light.svg"
|
||||
width="200"
|
||||
>
|
||||
</div>
|
||||
<p class="h6 empty-subtitle">
|
||||
{{ $t('message.noOpenTabs') }}
|
||||
|
@@ -10,6 +10,30 @@
|
||||
>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-play text-light pr-1" /> {{ $t('word.run') }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedMisc.type === 'trigger' && customizations.triggerEnableDisable"
|
||||
class="context-element"
|
||||
@click="toggleTrigger"
|
||||
>
|
||||
<span v-if="!selectedMisc.enabled" class="d-flex">
|
||||
<i class="mdi mdi-18px mdi-play text-light pr-1" /> {{ $t('word.enable') }}
|
||||
</span>
|
||||
<span v-else class="d-flex">
|
||||
<i class="mdi mdi-18px mdi-pause text-light pr-1" /> {{ $t('word.disable') }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedMisc.type === 'scheduler'"
|
||||
class="context-element"
|
||||
@click="toggleScheduler"
|
||||
>
|
||||
<span v-if="!selectedMisc.enabled" class="d-flex">
|
||||
<i class="mdi mdi-18px mdi-play text-light pr-1" /> {{ $t('word.enable') }}
|
||||
</span>
|
||||
<span v-else class="d-flex">
|
||||
<i class="mdi mdi-18px mdi-pause text-light pr-1" /> {{ $t('word.disable') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="context-element" @click="showDeleteModal">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-table-remove text-light pr-1" /> {{ $t('word.delete') }}</span>
|
||||
</div>
|
||||
@@ -78,6 +102,9 @@ export default {
|
||||
workspace () {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
},
|
||||
customizations () {
|
||||
return this.getWorkspace(this.selectedWorkspace).customizations;
|
||||
},
|
||||
deleteMessage () {
|
||||
switch (this.selectedMisc.type) {
|
||||
case 'trigger':
|
||||
@@ -98,6 +125,8 @@ export default {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
|
||||
addLoadingElement: 'workspaces/addLoadingElement',
|
||||
removeLoadingElement: 'workspaces/removeLoadingElement',
|
||||
removeTabs: 'workspaces/removeTabs',
|
||||
newTab: 'workspaces/newTab'
|
||||
}),
|
||||
@@ -273,6 +302,68 @@ export default {
|
||||
|
||||
this.newTab({ uid: this.workspace.uid, content: sql, type: 'query', autorun: true });
|
||||
this.closeContext();
|
||||
},
|
||||
async toggleTrigger () {
|
||||
this.addLoadingElement({
|
||||
name: this.selectedMisc.name,
|
||||
schema: this.selectedSchema,
|
||||
type: 'trigger'
|
||||
});
|
||||
|
||||
try {
|
||||
const { status, response } = await Triggers.toggleTrigger({
|
||||
uid: this.selectedWorkspace,
|
||||
schema: this.selectedSchema,
|
||||
trigger: this.selectedMisc.name,
|
||||
enabled: this.selectedMisc.enabled
|
||||
});
|
||||
|
||||
if (status !== 'success')
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.removeLoadingElement({
|
||||
name: this.selectedMisc.name,
|
||||
schema: this.selectedSchema,
|
||||
type: 'trigger'
|
||||
});
|
||||
|
||||
this.closeContext();
|
||||
this.$emit('reload');
|
||||
},
|
||||
async toggleScheduler () {
|
||||
this.addLoadingElement({
|
||||
name: this.selectedMisc.name,
|
||||
schema: this.selectedSchema,
|
||||
type: 'scheduler'
|
||||
});
|
||||
|
||||
try {
|
||||
const { status, response } = await Schedulers.toggleScheduler({
|
||||
uid: this.selectedWorkspace,
|
||||
schema: this.selectedSchema,
|
||||
scheduler: this.selectedMisc.name,
|
||||
enabled: this.selectedMisc.enabled
|
||||
});
|
||||
|
||||
if (status !== 'success')
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.removeLoadingElement({
|
||||
name: this.selectedMisc.name,
|
||||
schema: this.selectedSchema,
|
||||
type: 'scheduler'
|
||||
});
|
||||
|
||||
this.closeContext();
|
||||
this.$emit('reload');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -9,7 +9,16 @@
|
||||
<div v-if="isLoading" class="icon loading" />
|
||||
<i v-else class="icon mdi mdi-18px mdi-chevron-right" />
|
||||
<i class="database-icon mdi mdi-18px mdi-database mr-1" />
|
||||
<span>{{ database.name }}</span>
|
||||
<div class="">
|
||||
<span>{{ database.name }}</span>
|
||||
<div
|
||||
v-if="database.size"
|
||||
class="schema-size tooltip tooltip-left mr-1"
|
||||
:data-tooltip="formatBytes(database.size)"
|
||||
>
|
||||
<i class="mdi mdi-information-outline pr-2" />
|
||||
</div>
|
||||
</div>
|
||||
</summary>
|
||||
<div class="accordion-body">
|
||||
<div class="database-tables">
|
||||
@@ -68,9 +77,17 @@
|
||||
@contextmenu.prevent="showMiscContext($event, {...trigger, type: 'trigger'})"
|
||||
>
|
||||
<a class="table-name">
|
||||
<i class="table-icon mdi mdi-table-cog mdi-18px mr-1" />
|
||||
<div v-if="checkLoadingStatus(trigger.name, 'trigger')" class="icon loading mr-1" />
|
||||
<i v-else class="table-icon mdi mdi-table-cog mdi-18px mr-1" />
|
||||
<span v-html="highlightWord(trigger.name)" />
|
||||
</a>
|
||||
<div
|
||||
v-if="trigger.enabled === false"
|
||||
class="tooltip tooltip-left disabled-indicator"
|
||||
:data-tooltip="$t('word.disabled')"
|
||||
>
|
||||
<i class="table-icon mdi mdi-pause mdi-18px mr-1" />
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -204,9 +221,17 @@
|
||||
@contextmenu.prevent="showMiscContext($event, {...scheduler, type: 'scheduler'})"
|
||||
>
|
||||
<a class="table-name">
|
||||
<i class="table-icon mdi mdi-calendar-clock mdi-18px mr-1" />
|
||||
<div v-if="checkLoadingStatus(scheduler.name, 'scheduler')" class="icon loading mr-1" />
|
||||
<i v-else class="table-icon mdi mdi-calendar-clock mdi-18px mr-1" />
|
||||
<span v-html="highlightWord(scheduler.name)" />
|
||||
</a>
|
||||
<div
|
||||
v-if="scheduler.enabled === false"
|
||||
class="tooltip tooltip-left disabled-indicator"
|
||||
:data-tooltip="$t('word.disabled')"
|
||||
>
|
||||
<i class="table-icon mdi mdi-pause mdi-18px mr-1" />
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -426,6 +451,11 @@ export default {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
|
||||
.schema-size{
|
||||
visibility: hidden;
|
||||
width: 22.5px;
|
||||
}
|
||||
}
|
||||
|
||||
.database-name,
|
||||
@@ -471,6 +501,10 @@ export default {
|
||||
.misc-name {
|
||||
&:hover {
|
||||
border-radius: $border-radius;
|
||||
|
||||
.schema-size{
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,7 +534,9 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.table-size {
|
||||
.schema-size,
|
||||
.table-size,
|
||||
.disabled-indicator {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
|
@@ -344,7 +344,7 @@ export default {
|
||||
},
|
||||
addField () {
|
||||
this.localFields.push({
|
||||
_id: uidGen(),
|
||||
_antares_id: uidGen(),
|
||||
name: `${this.$tc('word.field', 1)}_${++this.newFieldsCounter}`,
|
||||
key: '',
|
||||
type: this.workspace.dataTypes[0].types[0].name,
|
||||
@@ -385,8 +385,8 @@ export default {
|
||||
});
|
||||
},
|
||||
duplicateField (uid) {
|
||||
const fieldToClone = Object.assign({}, this.localFields.find(field => field._id === uid));
|
||||
fieldToClone._id = uidGen();
|
||||
const fieldToClone = Object.assign({}, this.localFields.find(field => field._antares_id === uid));
|
||||
fieldToClone._antares_id = uidGen();
|
||||
fieldToClone.name = `${fieldToClone.name}_copy`;
|
||||
fieldToClone.order = this.localFields.length + 1;
|
||||
this.localFields = [...this.localFields, fieldToClone];
|
||||
@@ -397,11 +397,11 @@ export default {
|
||||
}, 20);
|
||||
},
|
||||
removeField (uid) {
|
||||
this.localFields = this.localFields.filter(field => field._id !== uid);
|
||||
this.localFields = this.localFields.filter(field => field._antares_id !== uid);
|
||||
},
|
||||
addNewIndex (payload) {
|
||||
this.localIndexes = [...this.localIndexes, {
|
||||
_id: uidGen(),
|
||||
_antares_id: uidGen(),
|
||||
name: payload.index === 'PRIMARY' ? 'PRIMARY' : payload.field,
|
||||
fields: [payload.field],
|
||||
type: payload.index,
|
||||
@@ -413,7 +413,7 @@ export default {
|
||||
},
|
||||
addToIndex (payload) {
|
||||
this.localIndexes = this.localIndexes.map(index => {
|
||||
if (index._id === payload.index) index.fields.push(payload.field);
|
||||
if (index._antares_id === payload.index) index.fields.push(payload.field);
|
||||
return index;
|
||||
});
|
||||
},
|
||||
|
@@ -373,7 +373,7 @@ export default {
|
||||
this.originalFunction = response;
|
||||
|
||||
this.originalFunction.parameters = [...this.originalFunction.parameters.map(param => {
|
||||
param._id = uidGen();
|
||||
param._antares_id = uidGen();
|
||||
return param;
|
||||
})];
|
||||
|
||||
|
@@ -36,10 +36,10 @@
|
||||
<div ref="parametersPanel" class="panel-body p-0 pr-1">
|
||||
<div
|
||||
v-for="param in parametersProxy"
|
||||
:key="param._id"
|
||||
:key="param._antares_id"
|
||||
class="tile tile-centered c-hand mb-1 p-1"
|
||||
:class="{'selected-element': selectedParam === param._id}"
|
||||
@click="selectParameter($event, param._id)"
|
||||
:class="{'selected-element': selectedParam === param._antares_id}"
|
||||
@click="selectParameter($event, param._antares_id)"
|
||||
>
|
||||
<div class="tile-icon">
|
||||
<div>
|
||||
@@ -56,7 +56,7 @@
|
||||
<button
|
||||
class="btn btn-link remove-field p-0 mr-2"
|
||||
:title="$t('word.delete')"
|
||||
@click.prevent="removeParameter(param._id)"
|
||||
@click.prevent="removeParameter(param._antares_id)"
|
||||
>
|
||||
<i class="mdi mdi-close" />
|
||||
</button>
|
||||
@@ -196,7 +196,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
selectedParamObj () {
|
||||
return this.parametersProxy.find(param => param._id === this.selectedParam);
|
||||
return this.parametersProxy.find(param => param._antares_id === this.selectedParam);
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.localParameters) !== JSON.stringify(this.parametersProxy);
|
||||
@@ -237,10 +237,11 @@ export default {
|
||||
this.modalInnerHeight = modalBody.clientHeight - (parseFloat(getComputedStyle(modalBody).paddingTop) + parseFloat(getComputedStyle(modalBody).paddingBottom));
|
||||
},
|
||||
addParameter () {
|
||||
const newUid = uidGen();
|
||||
this.parametersProxy = [...this.parametersProxy, {
|
||||
_id: uidGen(),
|
||||
name: `Param${this.i++}`,
|
||||
type: 'INT',
|
||||
_antares_id: newUid,
|
||||
name: `param${this.i++}`,
|
||||
type: this.workspace.dataTypes[0].types[0].name,
|
||||
context: 'IN',
|
||||
length: ''
|
||||
}];
|
||||
@@ -250,12 +251,13 @@ export default {
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.parametersPanel.scrollTop = this.$refs.parametersPanel.scrollHeight + 60;
|
||||
this.selectedParam = newUid;
|
||||
}, 20);
|
||||
},
|
||||
removeParameter (uid) {
|
||||
this.parametersProxy = this.parametersProxy.filter(param => param._id !== uid);
|
||||
this.parametersProxy = this.parametersProxy.filter(param => param._antares_id !== uid);
|
||||
|
||||
if (this.selectedParam === name && this.parametersProxy.length)
|
||||
if (this.parametersProxy.length && this.selectedParam === uid)
|
||||
this.resetSelectedID();
|
||||
},
|
||||
clearChanges () {
|
||||
@@ -266,7 +268,7 @@ export default {
|
||||
this.resetSelectedID();
|
||||
},
|
||||
resetSelectedID () {
|
||||
this.selectedParam = this.parametersProxy.length ? this.parametersProxy[0]._id : '';
|
||||
this.selectedParam = this.parametersProxy.length ? this.parametersProxy[0]._antares_id : '';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -318,7 +318,7 @@ export default {
|
||||
this.originalRoutine = response;
|
||||
|
||||
this.originalRoutine.parameters = [...this.originalRoutine.parameters.map(param => {
|
||||
param._id = uidGen();
|
||||
param._antares_id = uidGen();
|
||||
return param;
|
||||
})];
|
||||
|
||||
|
@@ -36,10 +36,10 @@
|
||||
<div ref="parametersPanel" class="panel-body p-0 pr-1">
|
||||
<div
|
||||
v-for="param in parametersProxy"
|
||||
:key="param._id"
|
||||
:key="param._antares_id"
|
||||
class="tile tile-centered c-hand mb-1 p-1"
|
||||
:class="{'selected-element': selectedParam === param._id}"
|
||||
@click="selectParameter($event, param._id)"
|
||||
:class="{'selected-element': selectedParam === param._antares_id}"
|
||||
@click="selectParameter($event, param._antares_id)"
|
||||
>
|
||||
<div class="tile-icon">
|
||||
<div>
|
||||
@@ -56,7 +56,7 @@
|
||||
<button
|
||||
class="btn btn-link remove-field p-0 mr-2"
|
||||
:title="$t('word.delete')"
|
||||
@click.prevent="removeParameter(param._id)"
|
||||
@click.prevent="removeParameter(param._antares_id)"
|
||||
>
|
||||
<i class="mdi mdi-close" />
|
||||
</button>
|
||||
@@ -196,7 +196,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
selectedParamObj () {
|
||||
return this.parametersProxy.find(param => param._id === this.selectedParam);
|
||||
return this.parametersProxy.find(param => param._antares_id === this.selectedParam);
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.localParameters) !== JSON.stringify(this.parametersProxy);
|
||||
@@ -237,8 +237,9 @@ export default {
|
||||
this.modalInnerHeight = modalBody.clientHeight - (parseFloat(getComputedStyle(modalBody).paddingTop) + parseFloat(getComputedStyle(modalBody).paddingBottom));
|
||||
},
|
||||
addParameter () {
|
||||
const newUid = uidGen();
|
||||
this.parametersProxy = [...this.parametersProxy, {
|
||||
_id: uidGen(),
|
||||
_antares_id: newUid,
|
||||
name: `param${this.i++}`,
|
||||
type: this.workspace.dataTypes[0].types[0].name,
|
||||
context: 'IN',
|
||||
@@ -250,12 +251,13 @@ export default {
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.parametersPanel.scrollTop = this.$refs.parametersPanel.scrollHeight + 60;
|
||||
this.selectedParam = newUid;
|
||||
}, 20);
|
||||
},
|
||||
removeParameter (uid) {
|
||||
this.parametersProxy = this.parametersProxy.filter(param => param._id !== uid);
|
||||
this.parametersProxy = this.parametersProxy.filter(param => param._antares_id !== uid);
|
||||
|
||||
if (this.selectedParam === name && this.parametersProxy.length)
|
||||
if (this.parametersProxy.length && this.selectedParam === uid)
|
||||
this.resetSelectedID();
|
||||
},
|
||||
clearChanges () {
|
||||
@@ -266,7 +268,7 @@ export default {
|
||||
this.resetSelectedID();
|
||||
},
|
||||
resetSelectedID () {
|
||||
this.selectedParam = this.parametersProxy.length ? this.parametersProxy[0]._id : '';
|
||||
this.selectedParam = this.parametersProxy.length ? this.parametersProxy[0]._antares_id : '';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -342,7 +342,7 @@ export default {
|
||||
field.default = `'${field.default}'`;
|
||||
}
|
||||
|
||||
return { ...field, _id: uidGen() };
|
||||
return { ...field, _antares_id: uidGen() };
|
||||
});
|
||||
this.localFields = JSON.parse(JSON.stringify(this.originalFields));
|
||||
}
|
||||
@@ -365,7 +365,7 @@ export default {
|
||||
|
||||
this.originalIndexes = Object.keys(indexesObj).map(index => {
|
||||
return {
|
||||
_id: uidGen(),
|
||||
_antares_id: uidGen(),
|
||||
name: index,
|
||||
fields: indexesObj[index].map(field => field.column),
|
||||
type: indexesObj[index][0].type,
|
||||
@@ -391,7 +391,7 @@ export default {
|
||||
if (status === 'success') {
|
||||
this.originalKeyUsage = response.map(foreign => {
|
||||
return {
|
||||
_id: uidGen(),
|
||||
_antares_id: uidGen(),
|
||||
...foreign
|
||||
};
|
||||
});
|
||||
@@ -411,25 +411,25 @@ export default {
|
||||
this.isSaving = true;
|
||||
|
||||
// FIELDS
|
||||
const originalIDs = this.originalFields.reduce((acc, curr) => [...acc, curr._id], []);
|
||||
const localIDs = this.localFields.reduce((acc, curr) => [...acc, curr._id], []);
|
||||
const originalIDs = this.originalFields.reduce((acc, curr) => [...acc, curr._antares_id], []);
|
||||
const localIDs = this.localFields.reduce((acc, curr) => [...acc, curr._antares_id], []);
|
||||
|
||||
// Fields Additions
|
||||
const additions = this.localFields.filter((field, i) => !originalIDs.includes(field._id)).map(field => {
|
||||
const lI = this.localFields.findIndex(localField => localField._id === field._id);
|
||||
const additions = this.localFields.filter((field, i) => !originalIDs.includes(field._antares_id)).map(field => {
|
||||
const lI = this.localFields.findIndex(localField => localField._antares_id === field._antares_id);
|
||||
const after = lI > 0 ? this.localFields[lI - 1].name : false;
|
||||
return { ...field, after };
|
||||
});
|
||||
|
||||
// Fields Deletions
|
||||
const deletions = this.originalFields.filter(field => !localIDs.includes(field._id));
|
||||
const deletions = this.originalFields.filter(field => !localIDs.includes(field._antares_id));
|
||||
|
||||
// Fields Changes
|
||||
const changes = [];
|
||||
this.originalFields.forEach((originalField, oI) => {
|
||||
const lI = this.localFields.findIndex(localField => localField._id === originalField._id);
|
||||
const originalSibling = oI > 0 ? this.originalFields[oI - 1]._id : false;
|
||||
const localSibling = lI > 0 ? this.localFields[lI - 1]._id : false;
|
||||
const lI = this.localFields.findIndex(localField => localField._antares_id === originalField._antares_id);
|
||||
const originalSibling = oI > 0 ? this.originalFields[oI - 1]._antares_id : false;
|
||||
const localSibling = lI > 0 ? this.localFields[lI - 1]._antares_id : false;
|
||||
const after = lI > 0 ? this.localFields[lI - 1].name : false;
|
||||
const orgName = originalField.name;
|
||||
|
||||
@@ -450,15 +450,15 @@ export default {
|
||||
changes: [],
|
||||
deletions: []
|
||||
};
|
||||
const originalIndexIDs = this.originalIndexes.reduce((acc, curr) => [...acc, curr._id], []);
|
||||
const localIndexIDs = this.localIndexes.reduce((acc, curr) => [...acc, curr._id], []);
|
||||
const originalIndexIDs = this.originalIndexes.reduce((acc, curr) => [...acc, curr._antares_id], []);
|
||||
const localIndexIDs = this.localIndexes.reduce((acc, curr) => [...acc, curr._antares_id], []);
|
||||
|
||||
// Index Additions
|
||||
indexChanges.additions = this.localIndexes.filter(index => !originalIndexIDs.includes(index._id));
|
||||
indexChanges.additions = this.localIndexes.filter(index => !originalIndexIDs.includes(index._antares_id));
|
||||
|
||||
// Index Changes
|
||||
this.originalIndexes.forEach(originalIndex => {
|
||||
const lI = this.localIndexes.findIndex(localIndex => localIndex._id === originalIndex._id);
|
||||
const lI = this.localIndexes.findIndex(localIndex => localIndex._antares_id === originalIndex._antares_id);
|
||||
if (JSON.stringify(originalIndex) !== JSON.stringify(this.localIndexes[lI])) {
|
||||
if (this.localIndexes[lI]) {
|
||||
indexChanges.changes.push({
|
||||
@@ -471,7 +471,7 @@ export default {
|
||||
});
|
||||
|
||||
// Index Deletions
|
||||
indexChanges.deletions = this.originalIndexes.filter(index => !localIndexIDs.includes(index._id));
|
||||
indexChanges.deletions = this.originalIndexes.filter(index => !localIndexIDs.includes(index._antares_id));
|
||||
|
||||
// FOREIGN KEYS
|
||||
const foreignChanges = {
|
||||
@@ -479,15 +479,15 @@ export default {
|
||||
changes: [],
|
||||
deletions: []
|
||||
};
|
||||
const originalForeignIDs = this.originalKeyUsage.reduce((acc, curr) => [...acc, curr._id], []);
|
||||
const localForeignIDs = this.localKeyUsage.reduce((acc, curr) => [...acc, curr._id], []);
|
||||
const originalForeignIDs = this.originalKeyUsage.reduce((acc, curr) => [...acc, curr._antares_id], []);
|
||||
const localForeignIDs = this.localKeyUsage.reduce((acc, curr) => [...acc, curr._antares_id], []);
|
||||
|
||||
// Foreigns Additions
|
||||
foreignChanges.additions = this.localKeyUsage.filter(foreign => !originalForeignIDs.includes(foreign._id));
|
||||
foreignChanges.additions = this.localKeyUsage.filter(foreign => !originalForeignIDs.includes(foreign._antares_id));
|
||||
|
||||
// Foreigns Changes
|
||||
this.originalKeyUsage.forEach(originalForeign => {
|
||||
const lI = this.localKeyUsage.findIndex(localForeign => localForeign._id === originalForeign._id);
|
||||
const lI = this.localKeyUsage.findIndex(localForeign => localForeign._antares_id === originalForeign._antares_id);
|
||||
if (JSON.stringify(originalForeign) !== JSON.stringify(this.localKeyUsage[lI])) {
|
||||
if (this.localKeyUsage[lI]) {
|
||||
foreignChanges.changes.push({
|
||||
@@ -499,7 +499,7 @@ export default {
|
||||
});
|
||||
|
||||
// Foreigns Deletions
|
||||
foreignChanges.deletions = this.originalKeyUsage.filter(foreign => !localForeignIDs.includes(foreign._id));
|
||||
foreignChanges.deletions = this.originalKeyUsage.filter(foreign => !localForeignIDs.includes(foreign._antares_id));
|
||||
|
||||
// ALTER
|
||||
const params = {
|
||||
@@ -555,7 +555,7 @@ export default {
|
||||
},
|
||||
addField () {
|
||||
this.localFields.push({
|
||||
_id: uidGen(),
|
||||
_antares_id: uidGen(),
|
||||
name: `${this.$tc('word.field', 1)}_${++this.newFieldsCounter}`,
|
||||
key: '',
|
||||
type: this.workspace.dataTypes[0].types[0].name,
|
||||
@@ -597,8 +597,8 @@ export default {
|
||||
});
|
||||
},
|
||||
duplicateField (uid) {
|
||||
const fieldToClone = Object.assign({}, this.localFields.find(field => field._id === uid));
|
||||
fieldToClone._id = uidGen();
|
||||
const fieldToClone = Object.assign({}, this.localFields.find(field => field._antares_id === uid));
|
||||
fieldToClone._antares_id = uidGen();
|
||||
fieldToClone.name = `${fieldToClone.name}_copy`;
|
||||
fieldToClone.order = this.localFields.length + 1;
|
||||
this.localFields = [...this.localFields, fieldToClone];
|
||||
@@ -609,11 +609,11 @@ export default {
|
||||
}, 20);
|
||||
},
|
||||
removeField (uid) {
|
||||
this.localFields = this.localFields.filter(field => field._id !== uid);
|
||||
this.localFields = this.localFields.filter(field => field._antares_id !== uid);
|
||||
},
|
||||
addNewIndex (payload) {
|
||||
this.localIndexes = [...this.localIndexes, {
|
||||
_id: uidGen(),
|
||||
_antares_id: uidGen(),
|
||||
name: payload.index === 'PRIMARY' ? 'PRIMARY' : payload.field,
|
||||
fields: [payload.field],
|
||||
type: payload.index,
|
||||
@@ -625,7 +625,7 @@ export default {
|
||||
},
|
||||
addToIndex (payload) {
|
||||
this.localIndexes = this.localIndexes.map(index => {
|
||||
if (index._id === payload.index) index.fields.push(payload.field);
|
||||
if (index._antares_id === payload.index) index.fields.push(payload.field);
|
||||
return index;
|
||||
});
|
||||
},
|
||||
|
@@ -27,7 +27,7 @@
|
||||
:key="index.name"
|
||||
class="context-element"
|
||||
:class="{'disabled': index.fields.includes(selectedField.name)}"
|
||||
@click="addToIndex(index._id)"
|
||||
@click="addToIndex(index._antares_id)"
|
||||
>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-key column-key pr-1" :class="`key-${index.type}`" /> {{ index.name }}</span>
|
||||
</div>
|
||||
|
@@ -109,7 +109,7 @@
|
||||
>
|
||||
<TableRow
|
||||
v-for="row in fields"
|
||||
:key="row._id"
|
||||
:key="row._antares_id"
|
||||
:row="row"
|
||||
:indexes="getIndexes(row.name)"
|
||||
:foreigns="getForeigns(row.name)"
|
||||
@@ -217,15 +217,15 @@ export default {
|
||||
this.resizeResults();
|
||||
},
|
||||
contextMenu (event, uid) {
|
||||
this.selectedField = this.fields.find(field => field._id === uid);
|
||||
this.selectedField = this.fields.find(field => field._antares_id === uid);
|
||||
this.contextEvent = event;
|
||||
this.isContext = true;
|
||||
},
|
||||
duplicateField () {
|
||||
this.$emit('duplicate-field', this.selectedField._id);
|
||||
this.$emit('duplicate-field', this.selectedField._antares_id);
|
||||
},
|
||||
removeField () {
|
||||
this.$emit('remove-field', this.selectedField._id);
|
||||
this.$emit('remove-field', this.selectedField._antares_id);
|
||||
},
|
||||
getIndexes (field) {
|
||||
return this.indexes.reduce((acc, curr) => {
|
||||
|
@@ -36,10 +36,10 @@
|
||||
<div ref="indexesPanel" class="panel-body p-0 pr-1">
|
||||
<div
|
||||
v-for="foreign in foreignProxy"
|
||||
:key="foreign._id"
|
||||
:key="foreign._antares_id"
|
||||
class="tile tile-centered c-hand mb-1 p-1"
|
||||
:class="{'selected-element': selectedForeignID === foreign._id}"
|
||||
@click="selectForeign($event, foreign._id)"
|
||||
:class="{'selected-element': selectedForeignID === foreign._antares_id}"
|
||||
@click="selectForeign($event, foreign._antares_id)"
|
||||
>
|
||||
<div class="tile-icon">
|
||||
<div>
|
||||
@@ -68,7 +68,7 @@
|
||||
<button
|
||||
class="btn btn-link remove-field p-0 mr-2"
|
||||
:title="$t('word.delete')"
|
||||
@click.prevent="removeIndex(foreign._id)"
|
||||
@click.prevent="removeIndex(foreign._antares_id)"
|
||||
>
|
||||
<i class="mdi mdi-close" />
|
||||
</button>
|
||||
@@ -238,7 +238,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
selectedForeignObj () {
|
||||
return this.foreignProxy.find(foreign => foreign._id === this.selectedForeignID);
|
||||
return this.foreignProxy.find(foreign => foreign._antares_id === this.selectedForeignID);
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.localKeyUsage) !== JSON.stringify(this.foreignProxy);
|
||||
@@ -288,7 +288,7 @@ export default {
|
||||
},
|
||||
addForeign () {
|
||||
this.foreignProxy = [...this.foreignProxy, {
|
||||
_id: uidGen(),
|
||||
_antares_id: uidGen(),
|
||||
constraintName: `FK_${this.foreignProxy.length + 1}`,
|
||||
refSchema: this.schema,
|
||||
table: this.table,
|
||||
@@ -307,19 +307,19 @@ export default {
|
||||
}, 20);
|
||||
},
|
||||
removeIndex (id) {
|
||||
this.foreignProxy = this.foreignProxy.filter(foreign => foreign._id !== id);
|
||||
this.foreignProxy = this.foreignProxy.filter(foreign => foreign._antares_id !== id);
|
||||
|
||||
if (this.selectedForeignID === id && this.foreignProxy.length)
|
||||
this.resetSelectedID();
|
||||
},
|
||||
clearChanges () {
|
||||
this.foreignProxy = JSON.parse(JSON.stringify(this.localKeyUsage));
|
||||
if (!this.foreignProxy.some(foreign => foreign._id === this.selectedForeignID))
|
||||
if (!this.foreignProxy.some(foreign => foreign._antares_id === this.selectedForeignID))
|
||||
this.resetSelectedID();
|
||||
},
|
||||
toggleField (field) {
|
||||
this.foreignProxy = this.foreignProxy.map(foreign => {
|
||||
if (foreign._id === this.selectedForeignID)
|
||||
if (foreign._antares_id === this.selectedForeignID)
|
||||
foreign.field = field;
|
||||
|
||||
return foreign;
|
||||
@@ -327,14 +327,14 @@ export default {
|
||||
},
|
||||
toggleRefField (field) {
|
||||
this.foreignProxy = this.foreignProxy.map(foreign => {
|
||||
if (foreign._id === this.selectedForeignID)
|
||||
if (foreign._antares_id === this.selectedForeignID)
|
||||
foreign.refField = field;
|
||||
|
||||
return foreign;
|
||||
});
|
||||
},
|
||||
resetSelectedID () {
|
||||
this.selectedForeignID = this.foreignProxy.length ? this.foreignProxy[0]._id : '';
|
||||
this.selectedForeignID = this.foreignProxy.length ? this.foreignProxy[0]._antares_id : '';
|
||||
},
|
||||
async getRefFields () {
|
||||
if (!this.selectedForeignObj.refTable) return;
|
||||
|
@@ -36,10 +36,10 @@
|
||||
<div ref="indexesPanel" class="panel-body p-0 pr-1">
|
||||
<div
|
||||
v-for="index in indexesProxy"
|
||||
:key="index._id"
|
||||
:key="index._antares_id"
|
||||
class="tile tile-centered c-hand mb-1 p-1"
|
||||
:class="{'selected-element': selectedIndexID === index._id}"
|
||||
@click="selectIndex($event, index._id)"
|
||||
:class="{'selected-element': selectedIndexID === index._antares_id}"
|
||||
@click="selectIndex($event, index._antares_id)"
|
||||
>
|
||||
<div class="tile-icon">
|
||||
<div>
|
||||
@@ -56,7 +56,7 @@
|
||||
<button
|
||||
class="btn btn-link remove-field p-0 mr-2"
|
||||
:title="$t('word.delete')"
|
||||
@click.prevent="removeIndex(index._id)"
|
||||
@click.prevent="removeIndex(index._antares_id)"
|
||||
>
|
||||
<i class="mdi mdi-close" />
|
||||
</button>
|
||||
@@ -163,7 +163,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
selectedIndexObj () {
|
||||
return this.indexesProxy.find(index => index._id === this.selectedIndexID);
|
||||
return this.indexesProxy.find(index => index._antares_id === this.selectedIndexID);
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.localIndexes) !== JSON.stringify(this.indexesProxy);
|
||||
@@ -200,7 +200,7 @@ export default {
|
||||
},
|
||||
addIndex () {
|
||||
this.indexesProxy = [...this.indexesProxy, {
|
||||
_id: uidGen(),
|
||||
_antares_id: uidGen(),
|
||||
name: 'NEW_INDEX',
|
||||
fields: [],
|
||||
type: 'INDEX',
|
||||
@@ -218,19 +218,19 @@ export default {
|
||||
}, 20);
|
||||
},
|
||||
removeIndex (id) {
|
||||
this.indexesProxy = this.indexesProxy.filter(index => index._id !== id);
|
||||
this.indexesProxy = this.indexesProxy.filter(index => index._antares_id !== id);
|
||||
|
||||
if (this.selectedIndexID === id && this.indexesProxy.length)
|
||||
this.resetSelectedID();
|
||||
},
|
||||
clearChanges () {
|
||||
this.indexesProxy = JSON.parse(JSON.stringify(this.localIndexes));
|
||||
if (!this.indexesProxy.some(index => index._id === this.selectedIndexID))
|
||||
if (!this.indexesProxy.some(index => index._antares_id === this.selectedIndexID))
|
||||
this.resetSelectedID();
|
||||
},
|
||||
toggleField (field) {
|
||||
this.indexesProxy = this.indexesProxy.map(index => {
|
||||
if (index._id === this.selectedIndexID) {
|
||||
if (index._antares_id === this.selectedIndexID) {
|
||||
if (index.fields.includes(field))
|
||||
index.fields = index.fields.filter(f => f !== field);
|
||||
else
|
||||
@@ -240,7 +240,7 @@ export default {
|
||||
});
|
||||
},
|
||||
resetSelectedID () {
|
||||
this.selectedIndexID = this.indexesProxy.length ? this.indexesProxy[0]._id : '';
|
||||
this.selectedIndexID = this.indexesProxy.length ? this.indexesProxy[0]._antares_id : '';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="tr" @contextmenu.prevent="$emit('contextmenu', $event, localRow._id)">
|
||||
<div class="tr" @contextmenu.prevent="$emit('contextmenu', $event, localRow._antares_id)">
|
||||
<div class="td p-0" tabindex="0">
|
||||
<div :class="customizations.sortableFields ? 'row-draggable' : 'text-center'">
|
||||
<i v-if="customizations.sortableFields" class="mdi mdi-drag-horizontal row-draggable-icon" />
|
||||
|
@@ -238,7 +238,7 @@ export default {
|
||||
this.originalFunction = response;
|
||||
|
||||
this.originalFunction.parameters = [...this.originalFunction.parameters.map(param => {
|
||||
param._id = uidGen();
|
||||
param._antares_id = uidGen();
|
||||
return param;
|
||||
})];
|
||||
|
||||
|
@@ -4,8 +4,9 @@
|
||||
class="workspace-query-tab column col-12 columns col-gapless no-outline p-0"
|
||||
tabindex="0"
|
||||
@keydown.116="runQuery(query)"
|
||||
@keydown.ctrl.87="clear"
|
||||
@keydown.ctrl.119="beautify"
|
||||
@keydown.ctrl.alt.87="clear"
|
||||
@keydown.ctrl.66="beautify"
|
||||
@keydown.ctrl.71="openHistoryModal"
|
||||
>
|
||||
<div class="workspace-query-runner column col-12">
|
||||
<QueryEditor
|
||||
@@ -32,16 +33,7 @@
|
||||
<span>{{ $t('word.run') }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark btn-sm"
|
||||
:disabled="!query || isQuering"
|
||||
title="CTRL+F8"
|
||||
@click="beautify()"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-brush pr-1" />
|
||||
<span>{{ $t('word.format') }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-link btn-sm"
|
||||
class="btn btn-link btn-sm mr-0"
|
||||
:disabled="!query || isQuering"
|
||||
title="CTRL+W"
|
||||
@click="clear()"
|
||||
@@ -52,6 +44,24 @@
|
||||
|
||||
<div class="divider-vert py-3" />
|
||||
|
||||
<button
|
||||
class="btn btn-dark btn-sm"
|
||||
:disabled="!query || isQuering"
|
||||
title="CTRL+B"
|
||||
@click="beautify()"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-brush pr-1" />
|
||||
<span>{{ $t('word.format') }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark btn-sm"
|
||||
:disabled="isQuering"
|
||||
title="CTRL+G"
|
||||
@click="openHistoryModal()"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-history pr-1" />
|
||||
<span>{{ $t('word.history') }}</span>
|
||||
</button>
|
||||
<div class="dropdown table-dropdown pr-2">
|
||||
<button
|
||||
:disabled="!results.length || isQuering"
|
||||
@@ -116,17 +126,24 @@
|
||||
@delete-selected="deleteSelected"
|
||||
/>
|
||||
</div>
|
||||
<ModalHistory
|
||||
v-if="isHistoryOpen"
|
||||
:connection="connection"
|
||||
@select-query="selectQuery"
|
||||
@close="isHistoryOpen = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { format } from 'sql-formatter';
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import Schema from '@/ipc-api/Schema';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import WorkspaceTabQueryTable from '@/components/WorkspaceTabQueryTable';
|
||||
import WorkspaceTabQueryEmptyState from '@/components/WorkspaceTabQueryEmptyState';
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import ModalHistory from '@/components/ModalHistory';
|
||||
import tableTabs from '@/mixins/tableTabs';
|
||||
|
||||
export default {
|
||||
@@ -135,7 +152,8 @@ export default {
|
||||
BaseLoader,
|
||||
QueryEditor,
|
||||
WorkspaceTabQueryTable,
|
||||
WorkspaceTabQueryEmptyState
|
||||
WorkspaceTabQueryEmptyState,
|
||||
ModalHistory
|
||||
},
|
||||
mixins: [tableTabs],
|
||||
props: {
|
||||
@@ -153,13 +171,15 @@ export default {
|
||||
resultsCount: 0,
|
||||
durationsCount: 0,
|
||||
affectedCount: 0,
|
||||
editorHeight: 200
|
||||
editorHeight: 200,
|
||||
isHistoryOpen: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
getWorkspace: 'workspaces/getWorkspace',
|
||||
selectedWorkspace: 'workspaces/getSelected'
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
getHistoryByWorkspace: 'history/getHistoryByWorkspace'
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
@@ -175,6 +195,9 @@ export default {
|
||||
},
|
||||
isWorkspaceSelected () {
|
||||
return this.workspace.uid === this.selectedWorkspace;
|
||||
},
|
||||
history () {
|
||||
return this.getHistoryByWorkspace(this.connection.uid) || [];
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -189,7 +212,6 @@ export default {
|
||||
created () {
|
||||
this.query = this.tab.content;
|
||||
this.selectedSchema = this.tab.schema || this.breadcrumbsSchema;
|
||||
// this.changeBreadcrumbs({ schema: this.selectedSchema, query: `Query #${this.tab.index}` });
|
||||
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
@@ -213,7 +235,8 @@ export default {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
|
||||
updateTabContent: 'workspaces/updateTabContent'
|
||||
updateTabContent: 'workspaces/updateTabContent',
|
||||
saveHistory: 'history/saveHistory'
|
||||
}),
|
||||
async runQuery (query) {
|
||||
if (!query || this.isQuering) return;
|
||||
@@ -236,7 +259,14 @@ export default {
|
||||
this.durationsCount += this.results.reduce((acc, curr) => acc + curr.duration, 0);
|
||||
this.affectedCount += this.results.reduce((acc, curr) => acc + (curr.report ? curr.report.affectedRows : 0), 0);
|
||||
|
||||
this.updateTabContent({ uid: this.connection.uid, tab: this.tab.uid, type: 'query', schema: this.selectedSchema, content: query });
|
||||
this.updateTabContent({
|
||||
uid: this.connection.uid,
|
||||
tab: this.tab.uid,
|
||||
type: 'query',
|
||||
schema: this.selectedSchema,
|
||||
content: query
|
||||
});
|
||||
this.saveHistory(params);
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
@@ -295,6 +325,15 @@ export default {
|
||||
this.$refs.queryEditor.editor.session.setValue(formattedQuery);
|
||||
}
|
||||
},
|
||||
openHistoryModal () {
|
||||
this.isHistoryOpen = true;
|
||||
},
|
||||
selectQuery (sql) {
|
||||
if (this.$refs.queryEditor)
|
||||
this.$refs.queryEditor.editor.session.setValue(sql);
|
||||
|
||||
this.isHistoryOpen = false;
|
||||
},
|
||||
clear () {
|
||||
if (this.$refs.queryEditor)
|
||||
this.$refs.queryEditor.editor.session.setValue('');
|
||||
|
@@ -11,13 +11,31 @@
|
||||
<div class="mb-4">
|
||||
{{ $t('word.clear') }}
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
{{ $t('word.history') }}
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
{{ $t('message.openNewTab') }}
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
{{ $t('message.closeTab') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-16">
|
||||
<div class="mb-4">
|
||||
<code>F5</code>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<code>CTRL</code> + <code>F8</code>
|
||||
<code>CTRL</code> + <code>B</code>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<code>CTRL</code> + <code>ALT</code> + <code>W</code>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<code>CTRL</code> + <code>G</code>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<code>CTRL</code> + <code>T</code>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<code>CTRL</code> + <code>W</code>
|
||||
|
@@ -5,6 +5,8 @@
|
||||
tabindex="0"
|
||||
:style="{'height': resultsSize+'px'}"
|
||||
@keyup.46="showDeleteConfirmModal"
|
||||
@keydown.ctrl.65="selectAllRows"
|
||||
@keydown.esc="deselectRows"
|
||||
>
|
||||
<TableContext
|
||||
v-if="isContext"
|
||||
@@ -60,7 +62,7 @@
|
||||
v-if="resultsWithRows[resultsetIndex] && resultsWithRows[resultsetIndex].rows"
|
||||
ref="resultTable"
|
||||
:items="sortedResults"
|
||||
:item-height="22"
|
||||
:item-height="23"
|
||||
class="tbody"
|
||||
:visible-height="resultsSize"
|
||||
:scroll-element="scrollElement"
|
||||
@@ -68,13 +70,13 @@
|
||||
<template slot-scope="{ items }">
|
||||
<WorkspaceTabQueryTableRow
|
||||
v-for="row in items"
|
||||
:key="row._id"
|
||||
:key="row._antares_id"
|
||||
:row="row"
|
||||
:fields="fieldsObj"
|
||||
:key-usage="keyUsage"
|
||||
:element-type="elementType"
|
||||
:class="{'selected': selectedRows.includes(row._id)}"
|
||||
@select-row="selectRow($event, row._id)"
|
||||
:class="{'selected': selectedRows.includes(row._antares_id)}"
|
||||
@select-row="selectRow($event, row._antares_id)"
|
||||
@update-field="updateField($event, row)"
|
||||
@contextmenu="contextMenu"
|
||||
/>
|
||||
@@ -194,7 +196,7 @@ export default {
|
||||
if (this.sortedResults.length) {
|
||||
const fieldsObj = {};
|
||||
for (const key in this.sortedResults[0]) {
|
||||
if (key === '_id') continue;
|
||||
if (key === '_antares_id') continue;
|
||||
|
||||
const fieldObj = this.fields.find(field => {
|
||||
let fieldNames = [
|
||||
@@ -308,7 +310,7 @@ export default {
|
||||
setLocalResults () {
|
||||
this.localResults = this.resultsWithRows[this.resultsetIndex] && this.resultsWithRows[this.resultsetIndex].rows
|
||||
? this.resultsWithRows[this.resultsetIndex].rows.map(item => {
|
||||
return { ...item, _id: uidGen() };
|
||||
return { ...item, _antares_id: uidGen() };
|
||||
})
|
||||
: [];
|
||||
},
|
||||
@@ -328,7 +330,7 @@ export default {
|
||||
this.resizeResults();
|
||||
},
|
||||
updateField (payload, row) {
|
||||
const orgRow = this.localResults.find(lr => lr._id === row._id);
|
||||
const orgRow = this.localResults.find(lr => lr._antares_id === row._antares_id);
|
||||
|
||||
Object.keys(orgRow).forEach(key => { // remap the row
|
||||
if (orgRow[key] instanceof Date && moment(orgRow[key]).isValid()) { // if datetime
|
||||
@@ -365,8 +367,8 @@ export default {
|
||||
},
|
||||
deleteSelected () {
|
||||
this.closeContext();
|
||||
const rows = this.localResults.filter(row => this.selectedRows.includes(row._id)).map(row => {
|
||||
delete row._id;
|
||||
const rows = JSON.parse(JSON.stringify(this.localResults)).filter(row => this.selectedRows.includes(row._antares_id)).map(row => {
|
||||
delete row._antares_id;
|
||||
return row;
|
||||
});
|
||||
|
||||
@@ -379,7 +381,7 @@ export default {
|
||||
this.$emit('delete-selected', params);
|
||||
},
|
||||
setNull () {
|
||||
const row = this.localResults.find(row => this.selectedRows.includes(row._id));
|
||||
const row = this.localResults.find(row => this.selectedRows.includes(row._antares_id));
|
||||
|
||||
const params = {
|
||||
primary: this.primaryField.name,
|
||||
@@ -394,7 +396,7 @@ export default {
|
||||
this.$emit('update-field', params);
|
||||
},
|
||||
copyCell () {
|
||||
const row = this.localResults.find(row => this.selectedRows.includes(row._id));
|
||||
const row = this.localResults.find(row => this.selectedRows.includes(row._antares_id));
|
||||
const cellName = Object.keys(row).find(prop => [
|
||||
this.selectedCell.field,
|
||||
`${this.fields[0].table}.${this.selectedCell.field}`,
|
||||
@@ -404,9 +406,9 @@ export default {
|
||||
navigator.clipboard.writeText(valueToCopy);
|
||||
},
|
||||
copyRow () {
|
||||
const row = this.localResults.find(row => this.selectedRows.includes(row._id));
|
||||
const row = this.localResults.find(row => this.selectedRows.includes(row._antares_id));
|
||||
const rowToCopy = JSON.parse(JSON.stringify(row));
|
||||
delete rowToCopy._id;
|
||||
delete rowToCopy._antares_id;
|
||||
navigator.clipboard.writeText(JSON.stringify(rowToCopy));
|
||||
},
|
||||
applyUpdate (params) {
|
||||
@@ -433,21 +435,30 @@ export default {
|
||||
this.selectedRows.push(row);
|
||||
else {
|
||||
const lastID = this.selectedRows.slice(-1)[0];
|
||||
const lastIndex = this.sortedResults.findIndex(el => el._id === lastID);
|
||||
const clickedIndex = this.sortedResults.findIndex(el => el._id === row);
|
||||
const lastIndex = this.sortedResults.findIndex(el => el._antares_id === lastID);
|
||||
const clickedIndex = this.sortedResults.findIndex(el => el._antares_id === row);
|
||||
if (lastIndex > clickedIndex) {
|
||||
for (let i = clickedIndex; i < lastIndex; i++)
|
||||
this.selectedRows.push(this.sortedResults[i]._id);
|
||||
this.selectedRows.push(this.sortedResults[i]._antares_id);
|
||||
}
|
||||
else if (lastIndex < clickedIndex) {
|
||||
for (let i = clickedIndex; i > lastIndex; i--)
|
||||
this.selectedRows.push(this.sortedResults[i]._id);
|
||||
this.selectedRows.push(this.sortedResults[i]._antares_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
this.selectedRows = [row];
|
||||
},
|
||||
selectAllRows () {
|
||||
this.selectedRows = this.localResults.reduce((acc, curr) => {
|
||||
acc.push(curr._antares_id);
|
||||
return acc;
|
||||
}, []);
|
||||
},
|
||||
deselectRows () {
|
||||
this.selectedRows = [];
|
||||
},
|
||||
contextMenu (event, cell) {
|
||||
if (event.target.localName === 'input') return;
|
||||
|
||||
@@ -490,7 +501,7 @@ export default {
|
||||
if (!this.sortedResults) return;
|
||||
|
||||
const rows = JSON.parse(JSON.stringify(this.sortedResults)).map(row => {
|
||||
delete row._id;
|
||||
delete row._antares_id;
|
||||
return row;
|
||||
});
|
||||
|
||||
|
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<div class="tr" @click="selectRow($event, row._id)">
|
||||
<div class="tr" @click="selectRow($event, row._antares_id)">
|
||||
<div
|
||||
v-for="(col, cKey) in row"
|
||||
v-show="cKey !== '_id'"
|
||||
v-show="cKey !== '_antares_id'"
|
||||
:key="cKey"
|
||||
class="td p-0"
|
||||
tabindex="0"
|
||||
@contextmenu.prevent="openContext($event, { id: row._id, field: cKey })"
|
||||
@contextmenu.prevent="openContext($event, { id: row._antares_id, field: cKey })"
|
||||
>
|
||||
<template v-if="cKey !== '_id'">
|
||||
<template v-if="cKey !== '_antares_id'">
|
||||
<span
|
||||
v-if="!isInlineEditor[cKey] && fields[cKey]"
|
||||
class="cell-content"
|
||||
|
@@ -8,12 +8,11 @@
|
||||
<button
|
||||
class="btn btn-dark btn-sm mr-0 pr-1"
|
||||
:class="{'loading':isQuering}"
|
||||
title="F5"
|
||||
:title="`${$t('word.refresh')} (F5)`"
|
||||
@click="reloadTable"
|
||||
>
|
||||
<i v-if="!+autorefreshTimer" class="mdi mdi-24px mdi-refresh mr-1" />
|
||||
<i v-else class="mdi mdi-24px mdi-history mdi-flip-h mr-1" />
|
||||
<span>{{ $t('word.refresh') }}</span>
|
||||
</button>
|
||||
<div class="btn btn-dark btn-sm dropdown-toggle pl-0 pr-0" tabindex="0">
|
||||
<i class="mdi mdi-24px mdi-menu-down" />
|
||||
@@ -71,6 +70,14 @@
|
||||
|
||||
<div class="divider-vert py-3" />
|
||||
|
||||
<button
|
||||
class="btn btn-sm"
|
||||
:title="`${$t('word.filter')} (CTRL+F)`"
|
||||
:class="{'btn-primary': isSearch, 'btn-dark': !isSearch}"
|
||||
@click="isSearch = !isSearch"
|
||||
>
|
||||
<i class="mdi mdi-24px mdi-magnify" />
|
||||
</button>
|
||||
<button
|
||||
v-if="isTable"
|
||||
class="btn btn-dark btn-sm"
|
||||
@@ -121,6 +128,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<WorkspaceTabTableFilters
|
||||
v-if="isSearch"
|
||||
:fields="fields"
|
||||
:conn-client="connection.client"
|
||||
@filter="updateFilters"
|
||||
@filter-change="onFilterChange"
|
||||
/>
|
||||
<div class="workspace-query-results p-relative column col-12">
|
||||
<BaseLoader v-if="isQuering" />
|
||||
<WorkspaceTabQueryTable
|
||||
@@ -160,6 +174,7 @@
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import WorkspaceTabQueryTable from '@/components/WorkspaceTabQueryTable';
|
||||
import WorkspaceTabTableFilters from '@/components/WorkspaceTabTableFilters';
|
||||
import ModalNewTableRow from '@/components/ModalNewTableRow';
|
||||
import ModalFakerRows from '@/components/ModalFakerRows';
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
@@ -170,6 +185,7 @@ export default {
|
||||
components: {
|
||||
BaseLoader,
|
||||
WorkspaceTabQueryTable,
|
||||
WorkspaceTabTableFilters,
|
||||
ModalNewTableRow,
|
||||
ModalFakerRows
|
||||
},
|
||||
@@ -192,6 +208,7 @@ export default {
|
||||
tabUid: 'data', // ???
|
||||
isQuering: false,
|
||||
isPageMenu: false,
|
||||
isSearch: false,
|
||||
results: [],
|
||||
lastTable: null,
|
||||
isAddModal: false,
|
||||
@@ -199,6 +216,7 @@ export default {
|
||||
autorefreshTimer: 0,
|
||||
refreshInterval: null,
|
||||
sortParams: {},
|
||||
filters: [],
|
||||
page: 1,
|
||||
pageProxy: 1,
|
||||
approximateCount: 0
|
||||
@@ -271,6 +289,13 @@ export default {
|
||||
if (this.lastTable !== this.table)
|
||||
this.getTableData();
|
||||
}
|
||||
},
|
||||
isSearch (val) {
|
||||
if (this.filters.length > 0 && !val) {
|
||||
this.filters = [];
|
||||
this.getTableData();
|
||||
}
|
||||
this.resizeScroller();
|
||||
}
|
||||
},
|
||||
created () {
|
||||
@@ -302,7 +327,8 @@ export default {
|
||||
table: this.table,
|
||||
limit: this.limit,
|
||||
page: this.page,
|
||||
sortParams: this.sortParams
|
||||
sortParams: this.sortParams,
|
||||
where: this.filters || []
|
||||
};
|
||||
|
||||
try { // Table data
|
||||
@@ -389,11 +415,13 @@ export default {
|
||||
if (e.key === 'F5')
|
||||
this.reloadTable();
|
||||
|
||||
if (e.ctrlKey) {
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
if (e.key === 'ArrowRight')
|
||||
this.pageChange('next');
|
||||
if (e.key === 'ArrowLeft')
|
||||
this.pageChange('prev');
|
||||
if (e.keyCode === 70) // f
|
||||
this.isSearch = !this.isSearch;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -410,6 +438,18 @@ export default {
|
||||
},
|
||||
downloadTable (format) {
|
||||
this.$refs.queryTable.downloadTable(format, this.table);
|
||||
},
|
||||
onFilterChange (clausoles) {
|
||||
this.resizeScroller();
|
||||
if (clausoles.length === 0)
|
||||
this.isSearch = false;
|
||||
},
|
||||
resizeScroller () {
|
||||
setTimeout(() => this.$refs.queryTable.refreshScroller(), 1);
|
||||
},
|
||||
updateFilters (clausoles) {
|
||||
this.filters = clausoles;
|
||||
this.getTableData();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
175
src/renderer/components/WorkspaceTabTableFilters.vue
Normal file
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<form class="workspace-table-filters" @submit.prevent="doFilter">
|
||||
<div
|
||||
v-for="(row, index) of rows"
|
||||
:key="index"
|
||||
class="workspace-table-filters-row"
|
||||
>
|
||||
<label class="form-checkbox my-0">
|
||||
<input
|
||||
v-model="row.active"
|
||||
type="checkbox"
|
||||
@change="doFilter"
|
||||
><i class="form-icon" />
|
||||
</label>
|
||||
<select v-model="row.field" class="form-select col-auto select-sm">
|
||||
<option
|
||||
v-for="(item, j) of fields"
|
||||
:key="j"
|
||||
:value="item.name"
|
||||
>
|
||||
{{ item.name }}
|
||||
</option>
|
||||
</select>
|
||||
<select v-model="row.op" class="form-select ml-2 col-auto select-sm">
|
||||
<option
|
||||
v-for="(operator, k) of operators"
|
||||
:key="k"
|
||||
:value="operator"
|
||||
>
|
||||
{{ operator }}
|
||||
</option>
|
||||
</select>
|
||||
<div class="workspace-table-filters-row-value ml-2">
|
||||
<input
|
||||
v-if="!row.op.includes('NULL')"
|
||||
v-model="row.value"
|
||||
type="text"
|
||||
class="form-input input-sm"
|
||||
>
|
||||
<input
|
||||
v-if="row.op === 'BETWEEN'"
|
||||
v-model="row.value2"
|
||||
type="text"
|
||||
class="form-input ml-2 input-sm"
|
||||
>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-sm btn-dark mr-0 ml-2"
|
||||
type="button"
|
||||
@click="removeRow(index)"
|
||||
>
|
||||
<i class="mdi mdi-minus-circle-outline" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="workspace-table-filters-buttons">
|
||||
<button
|
||||
class="btn btn-sm btn-primary mr-0 ml-2"
|
||||
type="submit"
|
||||
>
|
||||
{{ $t('word.filter') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-dark mr-0 ml-2"
|
||||
type="button"
|
||||
@click="addRow"
|
||||
>
|
||||
<i class="mdi mdi-plus-circle-outline" />
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import customizations from 'common/customizations';
|
||||
import { NUMBER, FLOAT } from 'common/fieldTypes';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
fields: Array,
|
||||
connClient: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
rows: [],
|
||||
operators: [
|
||||
'=', '!=', '>', '<', '>=', '<=', 'IN', 'NOT IN', 'LIKE', 'BETWEEN', 'IS NULL', 'IS NOT NULL'
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
customizations () {
|
||||
return customizations[this.connClient];
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.addRow();
|
||||
},
|
||||
methods: {
|
||||
addRow () {
|
||||
this.rows.push({ active: true, field: this.fields[0].name, op: '=', value: '', value2: '' });
|
||||
this.$emit('filter-change', this.rows);
|
||||
},
|
||||
removeRow (i) {
|
||||
this.rows = this.rows.filter((_, idx) => idx !== i);
|
||||
this.$emit('filter-change', this.rows);
|
||||
},
|
||||
doFilter () {
|
||||
const clausoles = this.rows.filter(el => el.active).map(el => this.createClausole(el));
|
||||
this.$emit('filter', clausoles);
|
||||
},
|
||||
createClausole (filter) {
|
||||
const field = this.fields.find(field => field.name === filter.field);
|
||||
const isNumeric = [...NUMBER, ...FLOAT].includes(field.type);
|
||||
const { elementsWrapper: ew, stringsWrapper: sw } = this.customizations;
|
||||
let value;
|
||||
|
||||
switch (filter.op) {
|
||||
case '=':
|
||||
case '!=':
|
||||
value = isNumeric ? filter.value : `${sw}${filter.value}${sw}`;
|
||||
break;
|
||||
case 'BETWEEN':
|
||||
value = isNumeric ? filter.value : `${sw}${filter.value}${sw}`;
|
||||
value += ' AND ';
|
||||
value += isNumeric ? filter.value2 : `${sw}${filter.value2}${sw}`;
|
||||
break;
|
||||
case 'IN':
|
||||
case 'NOT IN':
|
||||
value = filter.value.split(',').map(val => {
|
||||
val = val.trim();
|
||||
return isNumeric ? val : `${sw}${val}${sw}`;
|
||||
}).join(',');
|
||||
value = `(${filter.value})`;
|
||||
break;
|
||||
case 'IS NULL':
|
||||
case 'IS NOT NULL':
|
||||
value = '';
|
||||
break;
|
||||
default:
|
||||
value = `${sw}${filter.value}${sw}`;
|
||||
}
|
||||
|
||||
if (isNumeric && !value.length && !['IS NULL', 'IS NOT NULL'].includes(filter.op))
|
||||
value = `${sw}${sw}`;
|
||||
|
||||
return `${ew}${filter.field}${ew} ${filter.op} ${value}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.workspace-table-filters {
|
||||
padding: 0 0.6rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.workspace-table-filters-buttons {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
padding-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.workspace-table-filters-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.workspace-table-filters-row-value {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
@@ -117,7 +117,14 @@ module.exports = {
|
||||
all: 'All',
|
||||
duplicate: 'Duplicate',
|
||||
routine: 'Routine',
|
||||
new: 'New'
|
||||
new: 'New',
|
||||
history: 'History',
|
||||
select: 'Select',
|
||||
passphrase: 'Passphrase',
|
||||
filter: 'Filter',
|
||||
disabled: 'Disabled',
|
||||
enable: 'Enable',
|
||||
disable: 'Disable'
|
||||
},
|
||||
message: {
|
||||
appWelcome: 'Welcome to Antares SQL Client!',
|
||||
@@ -238,7 +245,12 @@ module.exports = {
|
||||
newRoutine: 'New routine',
|
||||
newFunction: 'New function',
|
||||
newScheduler: 'New scheduler',
|
||||
newTriggerFunction: 'New trigger function'
|
||||
newTriggerFunction: 'New trigger function',
|
||||
thereIsNoQueriesYet: 'There is no queries yet',
|
||||
searchForQueries: 'Search for queries',
|
||||
killProcess: 'Kill process',
|
||||
closeTab: 'Close tab',
|
||||
goToDownloadPage: 'Go to download page'
|
||||
},
|
||||
faker: {
|
||||
address: 'Address',
|
||||
|
@@ -11,7 +11,9 @@ const i18n = new VueI18n({
|
||||
'es-ES': require('./es-ES'),
|
||||
'fr-FR': require('./fr-FR'),
|
||||
'pt-BR': require('./pt-BR'),
|
||||
'de-DE': require('./de-DE')
|
||||
'de-DE': require('./de-DE'),
|
||||
'vi-VN': require('./vi-VN'),
|
||||
'ja-JP': require('./ja-JP')
|
||||
}
|
||||
});
|
||||
export default i18n;
|
||||
|
@@ -115,7 +115,13 @@ module.exports = {
|
||||
cell: 'Cella | Celle',
|
||||
triggerFunction: 'Funzione di trigger | Funzioni di trigger',
|
||||
all: 'Tutto',
|
||||
duplicate: 'Duplica'
|
||||
duplicate: 'Duplica',
|
||||
routine: 'Routine',
|
||||
new: 'Nuovo',
|
||||
history: 'Cronologia',
|
||||
select: 'Seleziona',
|
||||
passphrase: 'Passphrase',
|
||||
filter: 'Filtra'
|
||||
},
|
||||
message: {
|
||||
appWelcome: 'Benvenuto in Antares SQL Client!',
|
||||
|
412
src/renderer/i18n/ja-JP.js
Normal file
@@ -0,0 +1,412 @@
|
||||
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: 'スキーマ',
|
||||
results: '結果',
|
||||
size: 'サイズ',
|
||||
seconds: '秒数',
|
||||
type: 'タイプ',
|
||||
mimeType: 'マイムタイプ',
|
||||
download: 'ダウンロード',
|
||||
add: '追加',
|
||||
data: 'データ',
|
||||
properties: 'プロパティ',
|
||||
insert: '挿入',
|
||||
connecting: '接続',
|
||||
name: '名称',
|
||||
collation: '照合',
|
||||
clear: 'クリア',
|
||||
options: 'オプション',
|
||||
autoRefresh: 'オートリフレシュ',
|
||||
indexes: 'インデックス',
|
||||
foreignKeys: '外部キー',
|
||||
length: '長さ',
|
||||
unsigned: '符号なし',
|
||||
default: 'デフォルト',
|
||||
comment: 'コメント',
|
||||
key: 'キー | キー',
|
||||
order: '順序',
|
||||
expression: '表現',
|
||||
autoIncrement: 'オートインクリメント',
|
||||
engine: 'エンジン',
|
||||
field: 'フィールド | フィールド',
|
||||
approximately: '約',
|
||||
total: '合計',
|
||||
table: 'テーブル',
|
||||
discard: '破棄',
|
||||
stay: 'ステイ',
|
||||
author: '作者',
|
||||
light: 'ライト',
|
||||
dark: 'ダーク',
|
||||
autoCompletion: 'オートコンプリート',
|
||||
application: 'アプリケーション',
|
||||
editor: 'エディター',
|
||||
view: 'ビュー',
|
||||
definer: 'デファイナー',
|
||||
algorithm: 'アルゴリズム',
|
||||
trigger: 'トリガー | トリガー',
|
||||
storedRoutine: 'ストアド・ルーチン | ストアド・ルーチン',
|
||||
scheduler: 'スケジューラー | スケジューラー',
|
||||
event: 'イベント',
|
||||
parameters: 'パラメータ',
|
||||
function: '関数 | 関数',
|
||||
// deterministic: '決定論的',
|
||||
context: 'コンテキスト',
|
||||
export: 'エクスポート',
|
||||
returns: '戻り値',
|
||||
timing: 'タイミング',
|
||||
state: '状態',
|
||||
execution: '実行',
|
||||
starts: '開始',
|
||||
ends: '終了',
|
||||
ssl: 'SSL',
|
||||
privateKey: '秘密鍵',
|
||||
certificate: '証明書',
|
||||
caCertificate: 'CA 証明書',
|
||||
ciphers: '暗号',
|
||||
upload: 'アップロード',
|
||||
browse: '閲覧',
|
||||
faker: 'フェイカー',
|
||||
content: 'コンテンツ',
|
||||
cut: 'カット',
|
||||
copy: 'コピー',
|
||||
paste: '貼り付け',
|
||||
tools: 'ツール',
|
||||
variables: '変数',
|
||||
processes: 'プロセス',
|
||||
database: 'データベース',
|
||||
scratchpad: 'スクラッチパッド',
|
||||
array: '配列',
|
||||
changelog: '変更履歴',
|
||||
format: 'フォーマット',
|
||||
sshTunnel: 'SSH トンネル',
|
||||
structure: '構造',
|
||||
// small: '小規模',
|
||||
// medium: '中型',
|
||||
// large: 'ラージ',
|
||||
row: 'ロウ | ロウ',
|
||||
cell: 'セル | セル',
|
||||
triggerFunction: 'トリガー関数 | トリガー関数',
|
||||
all: 'すべて',
|
||||
duplicate: 'デュプリケート',
|
||||
routine: 'ルーチン',
|
||||
// new: '新機能',
|
||||
history: '履歴',
|
||||
select: '選択'
|
||||
},
|
||||
message: {
|
||||
appWelcome: 'Antares SQL Client へようこそ!',
|
||||
appFirstStep: '最初のステップは、新しいデータベース接続を作成することです。',
|
||||
addConnection: '接続の追加',
|
||||
createConnection: '接続の作成',
|
||||
createNewConnection: '新しい接続の作成',
|
||||
askCredentials: '認証情報の入力',
|
||||
testConnection: '接続のテスト',
|
||||
editConnection: '接続の編集',
|
||||
deleteConnection: '接続の削除',
|
||||
deleteCorfirm: 'のキャンセルを確認しますか?',
|
||||
connectionSuccessfullyMade: '接続に成功しました。',
|
||||
madeWithJS: '💛 と JavaScript で作られています。',
|
||||
checkForUpdates: '更新情報の確認',
|
||||
noUpdatesAvailable: 'アップデートがありません',
|
||||
checkingForUpdate: 'アップデートを確認中',
|
||||
checkFailure: 'チェックに失敗しました、後で試してください',
|
||||
updateAvailable: 'アップデートが利用可能です',
|
||||
downloadingUpdate: 'アップデートのダウンロード',
|
||||
updateDownloaded: 'アップデートのダウンロード',
|
||||
restartToInstall: 'Antares を再起動してインストールしてください',
|
||||
unableEditFieldWithoutPrimary: '主キーのないフィールドを結果セットで編集できない',
|
||||
editCell: 'セルの編集',
|
||||
deleteRows: '行の削除 | {count} 行の削除',
|
||||
confirmToDeleteRows: '1つの行を削除することを確認しますか? | {count} 行を削除することを確認しますか?',
|
||||
notificationsTimeout: '通知のタイムアウト',
|
||||
uploadFile: 'ファイルのアップロード',
|
||||
addNewRow: '新しい行の追加',
|
||||
numberOfInserts: 'インサート数',
|
||||
openNewTab: '新しいタブを開く',
|
||||
affectedRows: '影響を受ける行',
|
||||
createNewDatabase: '新規データベースの作成',
|
||||
databaseName: 'データベース名',
|
||||
serverDefault: 'サーバーのデフォルト',
|
||||
deleteDatabase: 'データベースの削除',
|
||||
editDatabase: 'データベースの編集',
|
||||
clearChanges: '変更の消去',
|
||||
addNewField: '新しいフィールドの追加',
|
||||
manageIndexes: 'インデックスの管理',
|
||||
manageForeignKeys: '外部キーの管理',
|
||||
allowNull: 'NULL を許可する',
|
||||
zeroFill: 'ゼロフィル',
|
||||
customValue: 'カスタム値',
|
||||
onUpdate: '更新時',
|
||||
deleteField: 'フィールドの削除',
|
||||
createNewIndex: '新しいインデックスの作成',
|
||||
addToIndex: 'インデックスへの追加',
|
||||
createNewTable: '新しいテーブルの作成',
|
||||
emptyTable: '空のテーブル',
|
||||
deleteTable: 'テーブルの削除',
|
||||
emptyCorfirm: '空にすることを確認しますか?',
|
||||
unsavedChanges: '保存されていない変更',
|
||||
discardUnsavedChanges: '保存されていない変更があります。このタブを閉じると、これらの変更は破棄されます。',
|
||||
thereAreNoIndexes: 'インデックスがありません',
|
||||
thereAreNoForeign: '外部キーがありません。',
|
||||
createNewForeign: '新しい外部キーの作成',
|
||||
referenceTable: '参照テーブル',
|
||||
referenceField: '参照フィールド',
|
||||
foreignFields: '外部フィールド',
|
||||
invalidDefault: '無効なデフォルト',
|
||||
onDelete: '削除時',
|
||||
applicationTheme: 'アプリケーションテーマ',
|
||||
editorTheme: 'エディターテーマ',
|
||||
wrapLongLines: '長い行の折り返し',
|
||||
selectStatement: '選択文',
|
||||
triggerStatement: 'トリガー文',
|
||||
sqlSecurity: 'SQL セキュリティ',
|
||||
updateOption: '更新オプション',
|
||||
deleteView: 'ビューの削除',
|
||||
createNewView: '新規ビューの作成',
|
||||
deleteTrigger: 'トリガーの削除',
|
||||
createNewTrigger: '新しいトリガの作成',
|
||||
currentUser: '現在のユーザー',
|
||||
routineBody: 'ルーチン本体',
|
||||
dataAccess: 'データアクセス',
|
||||
thereAreNoParameters: 'パラメータはありません',
|
||||
createNewParameter: '新しいパラメータの作成',
|
||||
createNewRoutine: 'ストアド・ルーチンの新規作成',
|
||||
deleteRoutine: 'ストアド・ルーチンの削除',
|
||||
functionBody: '関数本体',
|
||||
createNewFunction: '新しい関数の作成',
|
||||
deleteFunction: '関数の削除',
|
||||
schedulerBody: 'スケジューラ本体',
|
||||
createNewScheduler: 'スケジューラの新規作成',
|
||||
deleteScheduler: 'スケジューラの削除',
|
||||
preserveOnCompletion: '完了時に保存する',
|
||||
enableSsl: 'SSL 対応',
|
||||
manualValue: 'マニュアル値',
|
||||
tableFiller: 'テーブルフィラー',
|
||||
fakeDataLanguage: 'フェイクデータの言語',
|
||||
searchForElements: '要素の検索',
|
||||
selectAll: 'すべてを選択する',
|
||||
queryDuration: '問い合わせ期間',
|
||||
includeBetaUpdates: 'ベータ版アップデートを含む',
|
||||
setNull: 'NULL の設定',
|
||||
processesList: 'プロセス一覧',
|
||||
processInfo: 'プロセス情報',
|
||||
manageUsers: 'ユーザーの管理',
|
||||
createNewSchema: '新しいスキーマの作成',
|
||||
schemaName: 'スキーマ名',
|
||||
editSchema: 'スキーマの編集',
|
||||
deleteSchema: 'スキーマの削除',
|
||||
markdownSupported: 'マークダウン対応',
|
||||
// plantATree: '木を植える',
|
||||
dataTabPageSize: 'DATA タブのページサイズ',
|
||||
enableSsh: 'SSH を有効にする',
|
||||
pageNumber: 'ページ番号',
|
||||
duplicateTable: 'テーブルを複製する',
|
||||
noOpenTabs: '開いているタブがありません。左のバーでナビゲートするか',
|
||||
noSchema: 'スキーマなし',
|
||||
restorePreviourSession: '前のセッションに戻す',
|
||||
runQuery: 'クエリの実行',
|
||||
thereAreNoTableFields: 'テーブルのフィールドがありません',
|
||||
newTable: '新しいテーブル',
|
||||
newView: '新しいビュー',
|
||||
newTrigger: '新しいトリガー',
|
||||
newRoutine: '新しいルーチン',
|
||||
newFunction: '新しい関数',
|
||||
newScheduler: '新規スケジューラ',
|
||||
newTriggerFunction: '新しいトリガー機能',
|
||||
thereIsNoQueriesYet: 'まだ問い合わせはありません',
|
||||
searchForQueries: 'クエリの検索',
|
||||
killProcess: 'プロセスの停止'
|
||||
},
|
||||
faker: {
|
||||
address: '住所',
|
||||
commerce: 'コマース',
|
||||
company: '会社名',
|
||||
database: 'データベース',
|
||||
date: '日付',
|
||||
finance: 'ファイナンス',
|
||||
// git: 'ギット',
|
||||
hacker: 'ハッカー',
|
||||
internet: 'インターネット',
|
||||
// lorem: 'ローレム',
|
||||
name: '名前',
|
||||
music: '音楽',
|
||||
phone: '電話',
|
||||
random: 'ランダム',
|
||||
system: 'システム',
|
||||
time: '時間',
|
||||
vehicle: '車',
|
||||
zipCode: '郵便番号',
|
||||
zipCodeByState: '都道府県別郵便番号',
|
||||
city: '都市名',
|
||||
cityPrefix: '市のプレフィックス',
|
||||
citySuffix: '市の接尾辞',
|
||||
streetName: '通りの名前',
|
||||
streetAddress: 'ストリートアドレス',
|
||||
streetSuffix: '通りの接尾辞',
|
||||
streetPrefix: 'ストリートプレフィックス',
|
||||
secondaryAddress: '副住所',
|
||||
county: '郡',
|
||||
country: '国名',
|
||||
countryCode: '国コード',
|
||||
state: '州',
|
||||
stateAbbr: '州の略語',
|
||||
latitude: '緯度',
|
||||
longitude: '経度',
|
||||
direction: '方向',
|
||||
cardinalDirection: '枢機卿の方向',
|
||||
ordinalDirection: '序列方向',
|
||||
nearbyGPSCoordinate: '近くのGPS座標',
|
||||
timeZone: 'タイムゾーン',
|
||||
color: '色',
|
||||
department: '部門',
|
||||
productName: '商品名',
|
||||
price: '価格',
|
||||
productAdjective: '製品の形容詞',
|
||||
productMaterial: '製品の素材',
|
||||
product: '製品',
|
||||
productDescription: '製品の説明',
|
||||
suffixes: 'サフィックス',
|
||||
companyName: '会社名',
|
||||
companySuffix: '会社のサフィックス',
|
||||
catchPhrase: 'キャッチフレーズ',
|
||||
// bs: 'BS',
|
||||
catchPhraseAdjective: 'キャッチフレーズ形容詞',
|
||||
catchPhraseDescriptor: 'キャッチフレーズの説明文',
|
||||
catchPhraseNoun: 'キャッチフレーズの名詞',
|
||||
bsAdjective: 'BS 形容詞',
|
||||
bsBuzz: 'BS の話題',
|
||||
bsNoun: 'BS の名詞',
|
||||
column: 'コラム',
|
||||
type: 'タイプ',
|
||||
collation: '照合',
|
||||
engine: 'エンジン',
|
||||
past: '過去',
|
||||
future: '未来',
|
||||
between: '間',
|
||||
recent: '最近',
|
||||
soon: 'すぐ',
|
||||
month: '月',
|
||||
weekday: '曜日',
|
||||
account: 'アカウント',
|
||||
accountName: '口座名',
|
||||
routingNumber: 'ルーティング番号',
|
||||
mask: 'マスク',
|
||||
amount: '金額',
|
||||
transactionType: '取引の種類',
|
||||
currencyCode: '通貨コード',
|
||||
currencyName: '通貨名',
|
||||
currencySymbol: '通貨記号',
|
||||
bitcoinAddress: 'Bitcoin アドレス',
|
||||
litecoinAddress: 'ライトコインのアドレス',
|
||||
creditCardNumber: 'クレジットカード番号',
|
||||
creditCardCVV: 'クレジットカードの CVV',
|
||||
ethereumAddress: 'イーサリアムのアドレス',
|
||||
iban: 'アイバン',
|
||||
bic: 'ビック',
|
||||
transactionDescription: '取引内容',
|
||||
branch: 'ブランチ',
|
||||
commitEntry: 'コミットエントリ',
|
||||
commitMessage: 'コミットメッセージ',
|
||||
commitSha: 'コミット SHA',
|
||||
shortSha: 'ショート SHA',
|
||||
abbreviation: '省略形',
|
||||
adjective: '形容詞',
|
||||
noun: '名詞',
|
||||
verb: '動詞',
|
||||
ingverb: '動詞',
|
||||
phrase: 'フレーズ',
|
||||
avatar: 'アバター',
|
||||
email: 'メール',
|
||||
exampleEmail: 'メールの例',
|
||||
userName: 'ユーザー名',
|
||||
protocol: 'プロトコル',
|
||||
url: 'URL',
|
||||
domainName: 'ドメイン名',
|
||||
domainSuffix: 'ドメインのサフィックス',
|
||||
domainWord: 'ドメイン名',
|
||||
ip: 'Ip',
|
||||
ipv6: 'Ipv6',
|
||||
userAgent: 'ユーザーエージェント',
|
||||
// mac: 'Mac',
|
||||
password: 'パスワード',
|
||||
word: 'ワード',
|
||||
words: '単語',
|
||||
sentence: '文章',
|
||||
slug: 'スラッグ',
|
||||
sentences: 'センテンス',
|
||||
paragraph: 'パラグラフ',
|
||||
paragraphs: 'パラグラフ',
|
||||
text: 'テキスト',
|
||||
lines: '行',
|
||||
genre: 'ジャンル',
|
||||
firstName: 'ファーストネーム',
|
||||
lastName: '苗字',
|
||||
middleName: 'ミドルネーム',
|
||||
findName: 'フルネーム',
|
||||
jobTitle: '役職名',
|
||||
gender: '性別',
|
||||
prefix: 'プレフィックス',
|
||||
suffix: 'サフィックス',
|
||||
title: '役職名',
|
||||
jobDescriptor: '職務記述書',
|
||||
jobArea: '職務領域',
|
||||
jobType: '仕事の種類',
|
||||
phoneNumber: '電話番号',
|
||||
phoneNumberFormat: '電話番号のフォーマット',
|
||||
phoneFormats: '電話番号のフォーマット',
|
||||
// number: '番号',
|
||||
// float: 'フロート',
|
||||
arrayElement: '配列要素',
|
||||
arrayElements: '配列要素',
|
||||
objectElement: 'オブジェクトの要素',
|
||||
// uuid: 'Uuid',
|
||||
// boolean: 'ブール',
|
||||
image: '画像',
|
||||
locale: 'ロケール',
|
||||
alpha: '英字',
|
||||
alphaNumeric: '英数字',
|
||||
hexaDecimal: '16進法',
|
||||
fileName: 'ファイル名',
|
||||
commonFileName: '一般的なファイル名',
|
||||
mimeType: 'Mimeタイプ',
|
||||
commonFileType: '共通のファイルタイプ',
|
||||
commonFileExt: '共通のファイル拡張子',
|
||||
fileType: 'ファイルタイプ',
|
||||
fileExt: 'ファイル拡張子',
|
||||
directoryPath: 'ディレクトリパス',
|
||||
filePath: 'ファイルパス',
|
||||
// semver: 'セムバー',
|
||||
manufacturer: 'メーカー名',
|
||||
model: 'モデル',
|
||||
fuel: '燃料'
|
||||
// vin: 'Vin'
|
||||
}
|
||||
};
|
@@ -5,5 +5,7 @@ export default {
|
||||
'es-ES': 'Español',
|
||||
'fr-FR': 'Français',
|
||||
'pt-BR': 'Português (Brasil)',
|
||||
'de-DE': 'Deutsch (Deutschland)'
|
||||
'de-DE': 'Deutsch (Deutschland)',
|
||||
'vi-VN': 'Tiếng Việt',
|
||||
'ja-JP': '日本語'
|
||||
};
|
||||
|
413
src/renderer/i18n/vi-VN.js
Normal file
@@ -0,0 +1,413 @@
|
||||
module.exports = {
|
||||
word: {
|
||||
edit: 'Chỉnh sửa',
|
||||
save: 'Lưu',
|
||||
close: 'Đóng',
|
||||
delete: 'Xoá',
|
||||
confirm: 'Xác nhận',
|
||||
cancel: 'Huỷ',
|
||||
send: 'Gửi',
|
||||
connectionName: 'Tên kết nối',
|
||||
client: 'Client',
|
||||
hostName: 'Tên máy chủ',
|
||||
port: 'Cổng',
|
||||
user: 'Người dùng',
|
||||
password: 'Mật khẩu',
|
||||
credentials: 'Thông tin xác thực',
|
||||
connect: 'Kết nối',
|
||||
connected: 'Đã kết nối',
|
||||
disconnect: 'Ngắt kết nối',
|
||||
disconnected: 'Đã ngắt kết nối',
|
||||
refresh: 'Làm mới',
|
||||
settings: 'Cài đặt',
|
||||
general: 'Chung',
|
||||
themes: 'Giao diện',
|
||||
update: 'Cập nhật',
|
||||
about: 'Giới thiệu',
|
||||
language: 'Ngôn ngữ',
|
||||
version: 'Phiên bản',
|
||||
donate: 'Ủng hộ',
|
||||
run: 'Chạy',
|
||||
schema: 'Schema',
|
||||
results: 'Kết quả',
|
||||
size: 'Kích thước',
|
||||
seconds: 'Giây',
|
||||
type: 'Kiểu',
|
||||
mimeType: 'Mime-Type',
|
||||
download: 'Tải xuống',
|
||||
add: 'Thêm',
|
||||
data: 'Dữ liệu',
|
||||
properties: 'Thuộc tính',
|
||||
insert: 'Nhập',
|
||||
connecting: 'Đang kết nối',
|
||||
name: 'Tên',
|
||||
collation: 'Đối chiếu',
|
||||
clear: 'Xoá',
|
||||
options: 'Tuỳ chọn',
|
||||
autoRefresh: 'Tự động làm mới',
|
||||
indexes: 'Index',
|
||||
foreignKeys: 'Khoá ngoại',
|
||||
length: 'Độ dài',
|
||||
unsigned: 'Unsigned',
|
||||
default: 'Mặc định',
|
||||
comment: 'Nhận xét',
|
||||
key: 'Khoá | Khoá',
|
||||
order: 'Sắp xếp',
|
||||
expression: 'Biểu hiện',
|
||||
autoIncrement: 'Tự động tăng',
|
||||
engine: 'Engine',
|
||||
field: 'Trường | Trường',
|
||||
approximately: 'Khoảng',
|
||||
total: 'Toàn bộ',
|
||||
table: 'Bảng',
|
||||
discard: 'Bỏ',
|
||||
stay: 'Ở lại',
|
||||
author: 'Tác giả',
|
||||
light: 'Sáng',
|
||||
dark: 'Tối',
|
||||
autoCompletion: 'Tự động hoàn thành',
|
||||
application: 'Ứng dụng',
|
||||
editor: 'Người chỉnh sửa',
|
||||
view: 'Xem',
|
||||
definer: 'Định nghĩa',
|
||||
algorithm: 'Thuật toán',
|
||||
trigger: 'Kích hoạt | Kích hoạt',
|
||||
storedRoutine: 'Quy trình đã lưu | Quy trình đã lưu',
|
||||
scheduler: 'Lập lịch trình | Lập lịch trình',
|
||||
event: 'Sự kiện',
|
||||
parameters: 'Tham số',
|
||||
function: 'Chức năng | Chức năng',
|
||||
deterministic: 'Xác định',
|
||||
context: 'Context',
|
||||
export: 'Xuất',
|
||||
returns: 'Returns',
|
||||
timing: 'Thời gian',
|
||||
state: 'Trạng thái',
|
||||
execution: 'Thực thi',
|
||||
starts: 'Bắt đầu',
|
||||
ends: 'Kết thúc',
|
||||
ssl: 'SSL',
|
||||
privateKey: 'Mã khoá riêng tư',
|
||||
certificate: 'Chứng chỉ',
|
||||
caCertificate: 'Chứng chỉ CA',
|
||||
ciphers: 'Ciphers',
|
||||
upload: 'Tải lên',
|
||||
browse: 'Duyệt',
|
||||
faker: 'Faker',
|
||||
content: 'Nội dung',
|
||||
cut: 'Cắt',
|
||||
copy: 'Sao chép',
|
||||
paste: 'Dán',
|
||||
tools: 'Công cụ',
|
||||
variables: 'Biến',
|
||||
processes: 'Quá trình',
|
||||
database: 'Cơ sở dữ liệu',
|
||||
scratchpad: 'Scratchpad',
|
||||
array: 'Mảng',
|
||||
changelog: 'Nhật ký thay đổi',
|
||||
format: 'Định dạng',
|
||||
sshTunnel: 'SSH tunnel',
|
||||
structure: 'Structure',
|
||||
small: 'Nhỏ',
|
||||
medium: 'Vừa',
|
||||
large: 'Lớn',
|
||||
row: 'Hàng | Hàng',
|
||||
cell: 'Ô | Ô',
|
||||
triggerFunction: 'Trigger function | Trigger functions',
|
||||
all: 'Tất cả',
|
||||
duplicate: 'Bản sao',
|
||||
routine: 'Routine',
|
||||
new: 'Mới',
|
||||
history: 'Lịch sử',
|
||||
select: 'Chọn',
|
||||
passphrase: 'Cụm mật khẩu'
|
||||
},
|
||||
message: {
|
||||
appWelcome: 'Chào bạn đến với Antares SQL Client!',
|
||||
appFirstStep: 'Bước đầu tiên: tạo một kết nối tới cơ sở dữ liệu.',
|
||||
addConnection: 'Thêm kết nối',
|
||||
createConnection: 'Tạo kết nối',
|
||||
createNewConnection: 'Tạo kết nối mới',
|
||||
askCredentials: 'Yêu cầu thông tin đăng nhập',
|
||||
testConnection: 'Chạy thử kết nối',
|
||||
editConnection: 'Sửa kết nối',
|
||||
deleteConnection: 'Xoá kết nối',
|
||||
deleteCorfirm: 'Bạn có xác nhận việc hủy bỏ',
|
||||
connectionSuccessfullyMade: 'Kết nối được thực hiện thành công!',
|
||||
madeWithJS: 'Được tạo bằng 💛 và JavaScript!',
|
||||
checkForUpdates: 'Kiểm tra cập nhật',
|
||||
noUpdatesAvailable: 'Không có bản cập nhật nào',
|
||||
checkingForUpdate: 'Đang kiểm tra cập nhật',
|
||||
checkFailure: 'Kiểm tra thất bại, vui lòng thử lại sau',
|
||||
updateAvailable: 'Cập nhật có sẵn',
|
||||
downloadingUpdate: 'Đang tải bản cập nhật',
|
||||
updateDownloaded: 'Đã tải bản cập nhạt',
|
||||
restartToInstall: 'Khởi động lại Antares để cài đặt',
|
||||
unableEditFieldWithoutPrimary: 'Không thể chỉnh sửa trường mà không có khóa chính trong kết quả',
|
||||
editCell: 'Sửa ô',
|
||||
deleteRows: 'Xoá hàng | Xoá {count} hàng',
|
||||
confirmToDeleteRows: 'Bạn có xác nhận xóa một hàng không? | Bạn có xác nhận xóa {count} hàng không?',
|
||||
notificationsTimeout: 'Thông báo hết giờ',
|
||||
uploadFile: 'Tải lên tệp',
|
||||
addNewRow: 'Thêm hàng mới',
|
||||
numberOfInserts: 'Số lần nhập',
|
||||
openNewTab: 'Mở trong tab mới',
|
||||
affectedRows: 'Các hàng bị ảnh hưởng',
|
||||
createNewDatabase: 'Tạo Cơ sở dữ liệu mới',
|
||||
databaseName: 'Tên cơ sở dữ liệu',
|
||||
serverDefault: 'Máy chủ mặc định',
|
||||
deleteDatabase: 'Xoá cơ sở dữ liệu',
|
||||
editDatabase: 'Sửa cơ sở dữ liệu',
|
||||
clearChanges: 'Xóa các thay đổi',
|
||||
addNewField: 'Thêm trường mới',
|
||||
manageIndexes: 'Quản lý index',
|
||||
manageForeignKeys: 'Quản lý khoá ngoại',
|
||||
allowNull: 'Cho phép NULL',
|
||||
zeroFill: 'Không điền',
|
||||
customValue: 'Tuỳ chỉnh giá trị',
|
||||
onUpdate: 'Đang cập nhật',
|
||||
deleteField: 'Xoá trường',
|
||||
createNewIndex: 'Tạo index mới',
|
||||
addToIndex: 'Thêm vào index',
|
||||
createNewTable: 'Tạo bảng mới',
|
||||
emptyTable: 'Bảng trống',
|
||||
deleteTable: 'Xoá bảng',
|
||||
emptyCorfirm: 'Bạn có xác nhận để làm trống không',
|
||||
unsavedChanges: 'Chưa lưu lại thay đổi',
|
||||
discardUnsavedChanges: 'Bạn có một số thay đổi chưa được lưu. Đóng tab này, những thay đổi này sẽ bị huỷ bỏ.',
|
||||
thereAreNoIndexes: 'Không có index',
|
||||
thereAreNoForeign: 'Không có khoá ngoại',
|
||||
createNewForeign: 'Tạo khoá ngoại mới',
|
||||
referenceTable: 'Tham khảo bảng',
|
||||
referenceField: 'Tham khảo trường',
|
||||
foreignFields: 'Trường ngoại',
|
||||
invalidDefault: 'Mặc định không hợp lệ',
|
||||
onDelete: 'Đang xoá',
|
||||
applicationTheme: 'Chủ đề ứng dụng',
|
||||
editorTheme: 'Trình chỉnh sửa chủ đề',
|
||||
wrapLongLines: 'Wrap long lines',
|
||||
selectStatement: 'Chọn câu lệnh',
|
||||
triggerStatement: 'Kích hoạt câu lệnh',
|
||||
sqlSecurity: 'Bảo mật SQL',
|
||||
updateOption: 'Cập nhật tuỳ chọn',
|
||||
deleteView: 'Xóa chế độ xem',
|
||||
createNewView: 'Tạo chế độ xem mới',
|
||||
deleteTrigger: 'Xóa trình kích hoạt',
|
||||
createNewTrigger: 'Tạo trình kích hoạt mới',
|
||||
currentUser: 'Người dùng hiện tại',
|
||||
routineBody: 'Body quy trình',
|
||||
dataAccess: 'Truy cập dữ liệu',
|
||||
thereAreNoParameters: 'Không có tham số',
|
||||
createNewParameter: 'Tạo tham số mới',
|
||||
createNewRoutine: 'Tạo quy trình lưu trữ mới',
|
||||
deleteRoutine: 'Xoá quy trình lưu trữ',
|
||||
functionBody: 'Body chức năng',
|
||||
createNewFunction: 'Tạo chức năng mới',
|
||||
deleteFunction: 'Xoá chức năng',
|
||||
schedulerBody: 'Body trình lập lịch',
|
||||
createNewScheduler: 'Tạo lịch trình mới',
|
||||
deleteScheduler: 'Xóa trình lên lịch',
|
||||
preserveOnCompletion: 'Bảo tồn khi hoàn thành',
|
||||
enableSsl: 'Bật SSL',
|
||||
manualValue: 'Giá trị thủ công',
|
||||
tableFiller: 'Bộ lọc bảng',
|
||||
fakeDataLanguage: 'Ngôn ngữ dữ liệu giả mạo',
|
||||
searchForElements: 'Tìm kiếm yếu tố',
|
||||
selectAll: 'Chọn tất cả',
|
||||
queryDuration: 'Thời lượng truy vấn',
|
||||
includeBetaUpdates: 'Bao gồm các bản cập nhật beta',
|
||||
setNull: 'Đặt NULL',
|
||||
processesList: 'Danh sách quy trình',
|
||||
processInfo: 'Thông tin quá trình',
|
||||
manageUsers: 'Quản lý người dùng',
|
||||
createNewSchema: 'Tạo schema mới',
|
||||
schemaName: 'Tên schema',
|
||||
editSchema: 'Sửa schema',
|
||||
deleteSchema: 'Xoá schema',
|
||||
markdownSupported: 'Hỗ trợ Markdown',
|
||||
plantATree: 'Trồng cây',
|
||||
dataTabPageSize: 'Kích thước trang tab DATA',
|
||||
enableSsh: 'Bật SSH',
|
||||
pageNumber: 'Số trang',
|
||||
duplicateTable: 'Sao chép bản',
|
||||
noOpenTabs: 'Không có tab nào đang mở, điều hướng trên thanh bên trái hoặc:',
|
||||
noSchema: 'Không có schema',
|
||||
restorePreviourSession: 'Khôi phục phiên trước',
|
||||
runQuery: 'Chạy truy vấn',
|
||||
thereAreNoTableFields: 'Không có trường bảng',
|
||||
newTable: 'Bảng mới',
|
||||
newView: 'Chế độ xem mới',
|
||||
newTrigger: 'Trình kích hoạt mới',
|
||||
newRoutine: 'Quy trình mới',
|
||||
newFunction: 'Chức năng mới',
|
||||
newScheduler: 'Bộ lập lịch mới',
|
||||
newTriggerFunction: 'Chức năng kích hoạt mới',
|
||||
thereIsNoQueriesYet: 'Không có truy vấn nào',
|
||||
searchForQueries: 'Tìm kiếm truy vấn',
|
||||
killProcess: 'Huỷ quá trình'
|
||||
},
|
||||
faker: {
|
||||
address: 'Địa chỉ',
|
||||
commerce: 'Thương mại',
|
||||
company: 'Công ty',
|
||||
database: 'Cơ sở dữ liệu',
|
||||
date: 'Ngày',
|
||||
finance: 'Tài chánh',
|
||||
git: 'Git',
|
||||
hacker: 'Tin tặc',
|
||||
internet: 'Mạng Internet',
|
||||
lorem: 'Lorem',
|
||||
name: 'Tên',
|
||||
music: 'Âm nhạc',
|
||||
phone: 'Số điện thoại',
|
||||
random: 'Ngẫu nhiên',
|
||||
system: 'Hệ thống',
|
||||
time: 'Thời gian',
|
||||
vehicle: 'Phương tiện giao thông',
|
||||
zipCode: 'Mã Bưu Chính',
|
||||
zipCodeByState: 'Mã Bưu Chính theo tiểu bang',
|
||||
city: 'Thành phố',
|
||||
cityPrefix: 'Tiền tố thành phố',
|
||||
citySuffix: 'Hậu tố thành phố',
|
||||
streetName: 'Tên đường',
|
||||
streetAddress: 'Địa chỉ đường',
|
||||
streetSuffix: 'Hậu tố đường',
|
||||
streetPrefix: 'Tiền tố đường',
|
||||
secondaryAddress: 'Địa chỉ phụ',
|
||||
county: 'Quận',
|
||||
country: 'Quốc gia',
|
||||
countryCode: 'Mã quốc gia',
|
||||
state: 'Tiểu bang',
|
||||
stateAbbr: 'Viết tắt của tiểu bang',
|
||||
latitude: 'Vĩ độ',
|
||||
longitude: 'Kinh độ',
|
||||
direction: 'Hướng',
|
||||
cardinalDirection: 'Hướng cốt yếu',
|
||||
ordinalDirection: 'Hướng thứ tự',
|
||||
nearbyGPSCoordinate: 'Tọa độ GPS lân cận',
|
||||
timeZone: 'Múi giờ',
|
||||
color: 'Màu',
|
||||
department: 'Phòng',
|
||||
productName: 'Tên sản phẩm',
|
||||
price: 'Giá',
|
||||
productAdjective: 'Tính từ sản phẩm',
|
||||
productMaterial: 'Chất liệu sản phẩm',
|
||||
product: 'Sản phẩm',
|
||||
productDescription: 'Mô tả sản phẩm',
|
||||
suffixes: 'Hậu tố',
|
||||
companyName: 'Tên công ty',
|
||||
companySuffix: 'Hậu tố công ty',
|
||||
catchPhrase: 'Khẩu hiệu',
|
||||
bs: 'BS',
|
||||
catchPhraseAdjective: 'Bắt cụm từ tính từ',
|
||||
catchPhraseDescriptor: 'Bắt bộ mô tả cụm từ',
|
||||
catchPhraseNoun: 'Bắt cụm từ danh từ',
|
||||
bsAdjective: 'BS tính từ',
|
||||
bsBuzz: 'BS buzz',
|
||||
bsNoun: 'BS danh từ',
|
||||
column: 'Cột',
|
||||
type: 'Loại',
|
||||
collation: 'Đối chiếu',
|
||||
engine: 'Engine',
|
||||
past: 'Quá khứ',
|
||||
future: 'Tương lai',
|
||||
between: 'Giữa',
|
||||
recent: 'Gần đây',
|
||||
soon: 'Sớm',
|
||||
month: 'Tháng',
|
||||
weekday: 'Ngày trong tuần',
|
||||
account: 'Tài khoản',
|
||||
accountName: 'Tên tài khoản',
|
||||
routingNumber: 'Số định tuyến',
|
||||
mask: 'Mặt nạ',
|
||||
amount: 'Số tiền',
|
||||
transactionType: 'Loại giao dịch',
|
||||
currencyCode: 'Mã tiền tệ',
|
||||
currencyName: 'Tên tiền tệ',
|
||||
currencySymbol: 'Ký hiệu tiền tệ',
|
||||
bitcoinAddress: 'Địa chỉ Bitcoin',
|
||||
litecoinAddress: 'Địa chỉ Litecoin',
|
||||
creditCardNumber: 'Số thẻ tín dụng',
|
||||
creditCardCVV: 'CVV thẻ tín dụng',
|
||||
ethereumAddress: 'Địa chỉ Ethereum',
|
||||
iban: 'Iban',
|
||||
bic: 'Bic',
|
||||
transactionDescription: 'Mô tả giao dịch',
|
||||
branch: 'Nhánh',
|
||||
commitEntry: 'Nhập cam kết',
|
||||
commitMessage: 'Thông báo cam kết',
|
||||
commitSha: 'Cam kết SHA',
|
||||
shortSha: 'SHA ngắn',
|
||||
abbreviation: 'Viết tắt',
|
||||
adjective: 'Tính từ',
|
||||
noun: 'Danh từ',
|
||||
verb: 'Động từ',
|
||||
ingverb: 'Động từ ing',
|
||||
phrase: 'Cụm từ',
|
||||
avatar: 'Ảnh đại diện',
|
||||
email: 'Email',
|
||||
exampleEmail: 'Email ví dụ',
|
||||
userName: 'Tên người dùng',
|
||||
protocol: 'Giao thức',
|
||||
url: 'Url',
|
||||
domainName: 'Tên miền',
|
||||
domainSuffix: 'Hậu tố miền',
|
||||
domainWord: 'Từ miền',
|
||||
ip: 'Ip',
|
||||
ipv6: 'Ipv6',
|
||||
userAgent: 'User agent',
|
||||
mac: 'Mac',
|
||||
password: 'Mật khẩu',
|
||||
word: 'Từ',
|
||||
words: 'Từ',
|
||||
sentence: 'Câu',
|
||||
slug: 'Slug',
|
||||
sentences: 'Câu',
|
||||
paragraph: 'Đoạn văn',
|
||||
paragraphs: 'Đoạn văn',
|
||||
text: 'Văn bản',
|
||||
lines: 'Dòng',
|
||||
genre: 'Thể loại',
|
||||
firstName: 'Tên',
|
||||
lastName: 'Họ',
|
||||
middleName: 'Tên đệm',
|
||||
findName: 'Tên đầy đủ',
|
||||
jobTitle: 'Chức vụ',
|
||||
gender: 'Giới tính',
|
||||
prefix: 'Tiền tố',
|
||||
suffix: 'Hậu tố',
|
||||
title: 'Tiêu đề',
|
||||
jobDescriptor: 'Mô tả công việc',
|
||||
jobArea: 'Lĩnh vực việc làm',
|
||||
jobType: 'Loại công việc',
|
||||
phoneNumber: 'Số điện thoại',
|
||||
phoneNumberFormat: 'Định dạng số điện thoại',
|
||||
phoneFormats: 'Định dạng điện thoại',
|
||||
number: 'Số',
|
||||
float: 'Float',
|
||||
arrayElement: 'Phân tử array',
|
||||
arrayElements: 'Phân tử array',
|
||||
objectElement: 'Phần tử object',
|
||||
uuid: 'Uuid',
|
||||
boolean: 'Boolean',
|
||||
image: 'Hình ảnh',
|
||||
locale: 'Ngôn ngữ',
|
||||
alpha: 'Alpha',
|
||||
alphaNumeric: 'Chữ và số',
|
||||
hexaDecimal: 'Hệ thập lục phân',
|
||||
fileName: 'File name',
|
||||
commonFileName: 'Tên tệp chung',
|
||||
mimeType: 'Kiểu mine',
|
||||
commonFileType: 'Loại tệp chung',
|
||||
commonFileExt: 'Phần mở rộng tệp chung',
|
||||
fileType: 'Loại tệp',
|
||||
fileExt: 'Phần mở rộng tệp',
|
||||
directoryPath: 'Đường dẫn thư mục',
|
||||
filePath: 'Đường dẫn tệp',
|
||||
semver: 'Semver',
|
||||
manufacturer: 'Manufacturer',
|
||||
model: 'Model',
|
||||
fuel: 'Fuel',
|
||||
vin: 'Vin'
|
||||
}
|
||||
};
|
22
src/renderer/index.ejs
Normal file
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title></title>
|
||||
<script>
|
||||
global = globalThis
|
||||
</script>
|
||||
<% if (htmlWebpackPlugin.options.nodeModules) { %>
|
||||
<script>
|
||||
require('module').globalPaths.push(
|
||||
`<%= htmlWebpackPlugin.options.nodeModules.replace(/\\/g, '\\\\') %>`
|
||||
)
|
||||
</script>
|
||||
<% } %>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<!-- webpack builds are automatically injected -->
|
||||
</body>
|
||||
</html>
|
@@ -17,4 +17,8 @@ export default class {
|
||||
static createScheduler (params) {
|
||||
return ipcRenderer.invoke('create-scheduler', params);
|
||||
}
|
||||
|
||||
static toggleScheduler (params) {
|
||||
return ipcRenderer.invoke('toggle-scheduler', params);
|
||||
}
|
||||
}
|
||||
|
@@ -42,6 +42,10 @@ export default class {
|
||||
return ipcRenderer.invoke('get-processes', uid);
|
||||
}
|
||||
|
||||
static killProcess (params) {
|
||||
return ipcRenderer.invoke('kill-process', params);
|
||||
}
|
||||
|
||||
static useSchema (params) {
|
||||
return ipcRenderer.invoke('use-schema', params);
|
||||
}
|
||||
|
@@ -17,4 +17,8 @@ export default class {
|
||||
static createTrigger (params) {
|
||||
return ipcRenderer.invoke('create-trigger', params);
|
||||
}
|
||||
|
||||
static toggleTrigger (params) {
|
||||
return ipcRenderer.invoke('toggle-trigger', params);
|
||||
}
|
||||
}
|
||||
|
@@ -70,6 +70,7 @@
|
||||
"bytea": $blob-color,
|
||||
"enum": $enum-color,
|
||||
"set": $enum-color,
|
||||
"bool": $enum-color,
|
||||
"boolean": $enum-color,
|
||||
"interval": $array-color,
|
||||
"array": $array-color,
|
||||
|
@@ -142,7 +142,7 @@
|
||||
|
||||
code {
|
||||
background-color: #000;
|
||||
color: $body-font-color-dark;
|
||||
color: rgba($body-font-color-dark, 0.7);
|
||||
}
|
||||
|
||||
// Antares
|
||||
@@ -283,6 +283,12 @@
|
||||
}
|
||||
|
||||
.tile {
|
||||
transition: background 0.2s;
|
||||
|
||||
&:focus {
|
||||
background: rgba($bg-color-light-dark, 60%);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $bg-color-light-dark;
|
||||
}
|
||||
|
@@ -79,6 +79,12 @@
|
||||
}
|
||||
|
||||
.tile {
|
||||
transition: background 0.2s;
|
||||
|
||||
&:focus {
|
||||
background: rgba($bg-color-light-gray, 70%);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $bg-color-light-gray;
|
||||
}
|
||||
|
@@ -5,12 +5,15 @@ import Vuex from 'vuex';
|
||||
|
||||
import application from './modules/application.store';
|
||||
import settings from './modules/settings.store';
|
||||
import history from './modules/history.store';
|
||||
import scratchpad from './modules/scratchpad.store';
|
||||
import connections from './modules/connections.store';
|
||||
import workspaces from './modules/workspaces.store';
|
||||
import notifications from './modules/notifications.store';
|
||||
|
||||
import ipcUpdates from './plugins/ipcUpdates';
|
||||
import ipcExceptions from './plugins/ipcExceptions';
|
||||
import ipcShortcuts from './plugins/ipcShortcuts';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
@@ -19,12 +22,15 @@ export default new Vuex.Store({
|
||||
modules: {
|
||||
application,
|
||||
settings,
|
||||
history,
|
||||
scratchpad,
|
||||
connections,
|
||||
workspaces,
|
||||
notifications
|
||||
},
|
||||
plugins: [
|
||||
ipcUpdates
|
||||
ipcUpdates,
|
||||
ipcExceptions,
|
||||
ipcShortcuts
|
||||
]
|
||||
});
|
||||
|
54
src/renderer/store/modules/history.store.js
Normal file
@@ -0,0 +1,54 @@
|
||||
'use strict';
|
||||
import Store from 'electron-store';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
const persistentStore = new Store({ name: 'history' });
|
||||
const historySize = 1000;
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
strict: true,
|
||||
state: {
|
||||
history: persistentStore.get('history', {}),
|
||||
favorites: persistentStore.get('favorites', {})
|
||||
},
|
||||
getters: {
|
||||
getHistoryByWorkspace: state => uid => state.history[uid]
|
||||
},
|
||||
mutations: {
|
||||
SET_HISTORY (state, args) {
|
||||
if (!(args.uid in state.history))
|
||||
state.history[args.uid] = [];
|
||||
|
||||
state.history[args.uid] = [
|
||||
{
|
||||
uid: uidGen('H'),
|
||||
sql: args.query,
|
||||
date: new Date(),
|
||||
schema: args.schema
|
||||
},
|
||||
...state.history[args.uid]
|
||||
];
|
||||
|
||||
if (state.history[args.uid].length > historySize)
|
||||
state.history[args.uid] = state.history[args.uid].slice(0, historySize);
|
||||
|
||||
persistentStore.set('history', state.history);
|
||||
},
|
||||
DELETE_QUERY_FROM_HISTORY (state, query) {
|
||||
state.history[query.workspace] = state.history[query.workspace].filter(q => q.uid !== query.uid);
|
||||
persistentStore.set('history', state.history);
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
saveHistory ({ commit, getters }, args) {
|
||||
if (getters.getHistoryByWorkspace(args.uid) &&
|
||||
getters.getHistoryByWorkspace(args.uid).length &&
|
||||
getters.getHistoryByWorkspace(args.uid)[0].sql === args.query
|
||||
) return;
|
||||
commit('SET_HISTORY', args);
|
||||
},
|
||||
deleteQueryFromHistory ({ commit }, query) {
|
||||
commit('DELETE_QUERY_FROM_HISTORY', query);
|
||||
}
|
||||
}
|
||||
};
|
@@ -2,6 +2,9 @@
|
||||
import i18n from '@/i18n';
|
||||
import Store from 'electron-store';
|
||||
const persistentStore = new Store({ name: 'settings' });
|
||||
const isDarkTheme = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const defaultAppTheme = isDarkTheme.matches ? 'dark' : 'light';
|
||||
const defaultEditorTheme = isDarkTheme.matches ? 'twilight' : 'sqlserver';
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
@@ -14,8 +17,8 @@ export default {
|
||||
data_tab_limit: persistentStore.get('data_tab_limit', 1000),
|
||||
auto_complete: persistentStore.get('auto_complete', true),
|
||||
line_wrap: persistentStore.get('line_wrap', true),
|
||||
application_theme: persistentStore.get('application_theme', 'dark'),
|
||||
editor_theme: persistentStore.get('editor_theme', 'twilight'),
|
||||
application_theme: persistentStore.get('application_theme', defaultAppTheme),
|
||||
editor_theme: persistentStore.get('editor_theme', defaultEditorTheme),
|
||||
editor_font_size: persistentStore.get('editor_font_size', 'medium'),
|
||||
restore_tabs: persistentStore.get('restore_tabs', true)
|
||||
},
|
||||
|
7
src/renderer/store/plugins/ipcExceptions.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
export default store => {
|
||||
ipcRenderer.on('unhandled-exception', (event, error) => {
|
||||
store.dispatch('notifications/addNotification', { status: 'error', message: error.message });
|
||||
});
|
||||
};
|
12
src/renderer/store/plugins/ipcShortcuts.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
export default store => {
|
||||
ipcRenderer.on('toggle-preferences', (event, error) => {
|
||||
store.dispatch('application/showSettingModal', 'general');
|
||||
});
|
||||
|
||||
ipcRenderer.on('open-updates-preferences', (event, error) => {
|
||||
store.dispatch('application/showSettingModal', 'update');
|
||||
ipcRenderer.send('check-for-updates');
|
||||
});
|
||||
};
|
@@ -4,23 +4,33 @@ export default store => {
|
||||
ipcRenderer.on('checking-for-update', () => {
|
||||
store.commit('application/CHANGE_UPDATE_STATUS', 'checking');
|
||||
});
|
||||
|
||||
ipcRenderer.on('update-available', () => {
|
||||
store.commit('application/CHANGE_UPDATE_STATUS', 'available');
|
||||
});
|
||||
|
||||
ipcRenderer.on('update-not-available', () => {
|
||||
store.commit('application/CHANGE_UPDATE_STATUS', 'noupdate');
|
||||
});
|
||||
|
||||
ipcRenderer.on('check-failed', () => {
|
||||
store.commit('application/CHANGE_UPDATE_STATUS', 'nocheck');
|
||||
});
|
||||
|
||||
ipcRenderer.on('no-auto-update', () => {
|
||||
store.commit('application/CHANGE_UPDATE_STATUS', 'disabled');
|
||||
});
|
||||
|
||||
ipcRenderer.on('download-progress', (event, data) => {
|
||||
store.commit('application/CHANGE_UPDATE_STATUS', 'downloading');
|
||||
store.commit('application/CHANGE_PROGRESS_PERCENTAGE', data.percent);
|
||||
});
|
||||
|
||||
ipcRenderer.on('update-downloaded', () => {
|
||||
store.commit('application/CHANGE_UPDATE_STATUS', 'downloaded');
|
||||
});
|
||||
|
||||
ipcRenderer.on('link-to-download', () => {
|
||||
store.commit('application/CHANGE_UPDATE_STATUS', 'link');
|
||||
});
|
||||
};
|
||||
|
@@ -1,27 +0,0 @@
|
||||
const webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
stats: 'errors-warnings',
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
PACKAGE_VERSION: JSON.stringify(require('./package.json').version)
|
||||
}
|
||||
})
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
additionalData: '@import "@/scss/_variables.scss";'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
70
webpack.main.config.js
Normal file
@@ -0,0 +1,70 @@
|
||||
const path = require('path');
|
||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
const ProgressPlugin = require('progress-webpack-plugin');
|
||||
|
||||
const { dependencies, devDependencies } = require('./package.json');
|
||||
|
||||
const externals = Object.keys(dependencies).concat(Object.keys(devDependencies));
|
||||
const isDevMode = process.env.NODE_ENV === 'development';
|
||||
const whiteListedModules = [];
|
||||
|
||||
module.exports = [
|
||||
{ // Main
|
||||
name: 'main',
|
||||
mode: process.env.NODE_ENV,
|
||||
devtool: isDevMode ? 'eval-source-map' : false,
|
||||
entry: {
|
||||
main: path.join(__dirname, './src/main/main.js')
|
||||
},
|
||||
target: 'electron-main',
|
||||
output: {
|
||||
libraryTarget: 'commonjs2',
|
||||
path: path.join(__dirname, 'dist'),
|
||||
filename: '[name].js'
|
||||
},
|
||||
node: {
|
||||
global: true,
|
||||
__dirname: isDevMode,
|
||||
__filename: isDevMode
|
||||
},
|
||||
externals: externals.filter((d) => !whiteListedModules.includes(d)),
|
||||
resolve: {
|
||||
extensions: ['.js', '.json'],
|
||||
alias: {
|
||||
src: path.join(__dirname, 'src/'),
|
||||
common: path.resolve(__dirname, 'src/common')
|
||||
},
|
||||
fallback: {
|
||||
'pg-native': false,
|
||||
'cpu-features': false,
|
||||
cardinal: false
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new ProgressPlugin(true),
|
||||
new CleanWebpackPlugin({ root: path.join(__dirname, 'dist') })
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.node$/,
|
||||
loader: 'node-loader',
|
||||
options: {
|
||||
name: '[path][name].[ext]'
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader'
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif)$/,
|
||||
use: [{
|
||||
loader: 'file-loader'
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
];
|
147
webpack.renderer.config.js
Normal file
@@ -0,0 +1,147 @@
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const { VueLoaderPlugin } = require('vue-loader');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const ProgressPlugin = require('progress-webpack-plugin');
|
||||
|
||||
const { dependencies, devDependencies, version } = require('./package.json');
|
||||
|
||||
const externals = Object.keys(dependencies).concat(Object.keys(devDependencies));
|
||||
const isDevMode = process.env.NODE_ENV === 'development';
|
||||
const whiteListedModules = ['vue'];
|
||||
|
||||
const config = {
|
||||
name: 'renderer',
|
||||
mode: process.env.NODE_ENV,
|
||||
devtool: isDevMode ? 'eval-source-map' : false,
|
||||
entry: {
|
||||
renderer: path.join(__dirname, './src/renderer/index.js')
|
||||
},
|
||||
target: 'electron-renderer',
|
||||
output: {
|
||||
libraryTarget: 'commonjs2',
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: '[name].js',
|
||||
publicPath: ''
|
||||
},
|
||||
node: {
|
||||
global: true,
|
||||
__dirname: isDevMode,
|
||||
__filename: isDevMode
|
||||
},
|
||||
externals: externals.filter((d) => !whiteListedModules.includes(d)),
|
||||
resolve: {
|
||||
alias: {
|
||||
vue$: 'vue/dist/vue.common.js',
|
||||
common: path.resolve(__dirname, 'src/common'),
|
||||
'@': path.resolve(__dirname, 'src/renderer')
|
||||
},
|
||||
extensions: ['', '.js', '.vue', '.json'],
|
||||
fallback: {
|
||||
fs: false,
|
||||
path: false,
|
||||
util: false,
|
||||
crypto: false,
|
||||
assert: false,
|
||||
os: false
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new ProgressPlugin(true),
|
||||
new HtmlWebpackPlugin({
|
||||
excludeChunks: ['processTaskWorker'],
|
||||
filename: 'index.html',
|
||||
template: path.resolve(__dirname, 'src/renderer/index.ejs'),
|
||||
nodeModules: isDevMode
|
||||
? path.resolve(__dirname, '../node_modules')
|
||||
: false
|
||||
}),
|
||||
new MiniCssExtractPlugin({
|
||||
filename: '[name].css',
|
||||
chunkFilename: '[id].css'
|
||||
}),
|
||||
new VueLoaderPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
PACKAGE_VERSION: `"${version}"`
|
||||
}
|
||||
})
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: 'babel-loader',
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.node$/,
|
||||
use: 'node-loader'
|
||||
},
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader'
|
||||
},
|
||||
{
|
||||
test: /\.s(c|a)ss$/,
|
||||
use: [
|
||||
{ loader: MiniCssExtractPlugin.loader },
|
||||
{ loader: 'css-loader' },
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
additionalData: '@import "@/scss/_variables.scss";',
|
||||
sassOptions: { quietDeps: true }
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
{
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
options: {
|
||||
publicPath: ''
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
url: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|tif?f|bmp|webp|svg)(\?.*)?$/,
|
||||
type: 'asset/resource',
|
||||
generator: {
|
||||
filename: 'images/[hash][ext][query]'
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(woff|woff2|ttf|eot)$/,
|
||||
type: 'asset',
|
||||
parser: {
|
||||
dataUrlCondition: {
|
||||
maxSize: 8 * 1024
|
||||
}
|
||||
},
|
||||
generator: {
|
||||
filename: 'fonts/[hash][ext][query]'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
if (isDevMode) {
|
||||
// any dev only config
|
||||
config.plugins.push(
|
||||
new webpack.HotModuleReplacementPlugin()
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = config;
|