mirror of
https://github.com/Fabio286/antares.git
synced 2025-06-05 21:59:22 +02:00
Compare commits
35 Commits
Author | SHA1 | Date | |
---|---|---|---|
2aace28e80 | |||
02c03e3d26 | |||
a0d85520fb | |||
ede6fe81ce | |||
4e72bb1587 | |||
15417e8a77 | |||
88ab7c5a62 | |||
5940b0b842 | |||
574d493908 | |||
af96647603 | |||
ab70ff239e | |||
bacf458936 | |||
379d6c169a | |||
|
36352f8028 | ||
04fcaf2f6e | |||
8b914446b0 | |||
a11bac504c | |||
b9ed8dd610 | |||
1cf6485896 | |||
4bc9bbfb34 | |||
4923128236 | |||
8ff6e70145 | |||
afcf1c86ed | |||
0200ae4a0f | |||
dbe7b9dd23 | |||
ceab4ef243 | |||
1e7d4ca347 | |||
c0a32c040e | |||
f150508547 | |||
49d71722e2 | |||
59a50bc014 | |||
41d75b127c | |||
0cbea9d100 | |||
e351c903a8 | |||
6e55f27b23 |
7
.versionrc.json
Normal file
7
.versionrc.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"types": [
|
||||
{"type":"feat","section":"Features"},
|
||||
{"type":"perf","section":"Improvements"},
|
||||
{"type":"fix","section":"Bug Fixes"}
|
||||
]
|
||||
}
|
59
CHANGELOG.md
59
CHANGELOG.md
@@ -2,6 +2,65 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [0.0.16](https://github.com/Fabio286/antares/compare/v0.0.15...v0.0.16) (2021-02-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* MySQL and MariaDB auto detection ([02c03e3](https://github.com/Fabio286/antares/commit/02c03e3d266052872ac8edeb159ec8182f41c6a5))
|
||||
* **UI:** enanched file upload input ([a0d8552](https://github.com/Fabio286/antares/commit/a0d85520fb0669d5eec4475f5e255adb1e2a0159))
|
||||
* support to ssl connections ([4e72bb1](https://github.com/Fabio286/antares/commit/4e72bb15874324214aa0f5b89057cdd565744468))
|
||||
* **UI:** database version in app footer ([15417e8](https://github.com/Fabio286/antares/commit/15417e8a776c6aa9f90762d198d70b26163bb2df))
|
||||
* **UI:** resize query editor area ([88ab7c5](https://github.com/Fabio286/antares/commit/88ab7c5a62654c6a6026b63464224226d33b1950))
|
||||
* delete rows from tables without a primary key ([574d493](https://github.com/Fabio286/antares/commit/574d4939083577ffcb8e7c65f572c364eb8415fb))
|
||||
* edit rows from tables without a primary key ([5940b0b](https://github.com/Fabio286/antares/commit/5940b0b84207093da141b5c617c41e0a18fcb1d7))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* compatibility with electron-store 7 ([bacf458](https://github.com/Fabio286/antares/commit/bacf45893676cb0744907703f6534ffd472bd1dd))
|
||||
* edit bit fields ([ede6fe8](https://github.com/Fabio286/antares/commit/ede6fe81cefc91cdce2bbb0cd7cd6f85bbca99b8))
|
||||
|
||||
### [0.0.15](https://github.com/Fabio286/antares/compare/v0.0.14...v0.0.15) (2021-01-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* functions and schedulers in query suggestions ([8ff6e70](https://github.com/Fabio286/antares/commit/8ff6e70145ed2a207ae8b23a2c688258382a5d74))
|
||||
* loading animation in properties tabs ([1cf6485](https://github.com/Fabio286/antares/commit/1cf64858964f4894913db42f7c268013bb06e40b))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* error retriving dato of some schedulers ([b9ed8dd](https://github.com/Fabio286/antares/commit/b9ed8dd610e3be1489e01cf53f7d632cb1bd6ac5))
|
||||
* unable to call stored routines from query tabs ([4923128](https://github.com/Fabio286/antares/commit/4923128236131482ca948ae8052c294bd9269ed0))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* better fields type detection ([4bc9bbf](https://github.com/Fabio286/antares/commit/4bc9bbfb34ebdc51061f718cdf9cbca8507fa0f4))
|
||||
* big performance improvement in database structure loading ([a11bac5](https://github.com/Fabio286/antares/commit/a11bac504cd4ee865ea6c614a15ee809dc38202e))
|
||||
|
||||
### [0.0.14](https://github.com/Fabio286/antares/compare/v0.0.13...v0.0.14) (2021-01-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* export data tables to json or csv file ([0cbea9d](https://github.com/Fabio286/antares/commit/0cbea9d1007304a5b9cf893d165b4b4104266651))
|
||||
* functions creation ([49d7172](https://github.com/Fabio286/antares/commit/49d71722e26172232f7b54c6568e1e588ce0d049))
|
||||
* functions delete ([59a50bc](https://github.com/Fabio286/antares/commit/59a50bc014facc9643f9153cff61dc9d5a8605a9))
|
||||
* functions edit ([41d75b1](https://github.com/Fabio286/antares/commit/41d75b127cbcf1481fd259a14e6e7688638e18a4))
|
||||
* scheduler edit ([ceab4ef](https://github.com/Fabio286/antares/commit/ceab4ef243881ba64517fb95320844a21fce4849))
|
||||
* schedulers creation ([dbe7b9d](https://github.com/Fabio286/antares/commit/dbe7b9dd239248e806377ae6236b477456f175a3))
|
||||
* schedulers delete ([1e7d4ca](https://github.com/Fabio286/antares/commit/1e7d4ca347f4b9337ff266ec78bb4bbc6dd20d4d))
|
||||
* triggers and stored routines in sql suggestions ([e351c90](https://github.com/Fabio286/antares/commit/e351c903a8a8d7e908d6a7d54c0491438ac6f024))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* error with empty functions/procedures ([f150508](https://github.com/Fabio286/antares/commit/f1505085477a760a768a7d245c9517a858c1379c))
|
||||
* removed internal row _id from exported files ([c0a32c0](https://github.com/Fabio286/antares/commit/c0a32c040e653729ef80d580d6dd1796d1b2adcd))
|
||||
|
||||
### [0.0.13](https://github.com/Fabio286/antares/compare/v0.0.12...v0.0.13) (2021-01-06)
|
||||
|
||||
|
||||
|
22
README.md
22
README.md
@@ -32,6 +32,7 @@ An application created with minimalism and semplicity in mind, with features in
|
||||
- Multiple database connections at same time.
|
||||
- Database management (add/edit/delete).
|
||||
- Full tables management, including indexes and foreign keys.
|
||||
- Views, triggers, stored routines, functions and schedulers management (add/edit/delete).
|
||||
- Run queries on multiple tabs.
|
||||
- Query suggestions and auto complete.
|
||||
- Native dark theme.
|
||||
@@ -43,11 +44,10 @@ An application created with minimalism and semplicity in mind, with features in
|
||||
|
||||
This is a roadmap with major features will come in near future.
|
||||
|
||||
- Stored procedures, views, schedulers and triggers support.
|
||||
- Users management (add/edit/delete).
|
||||
- Database tools (variables, process list...).
|
||||
- SSL and SSH tunnel support.
|
||||
- Support for other databases.
|
||||
- Database tools (variables, process list...).
|
||||
- SSH tunnel support.
|
||||
- Users management (add/edit/delete).
|
||||
- UI/UX improvements.
|
||||
- Query history.
|
||||
- More context menu shortcuts.
|
||||
@@ -55,7 +55,7 @@ This is a roadmap with major features will come in near future.
|
||||
- Query logs console.
|
||||
- Fake data filler.
|
||||
- Import/export and migration.
|
||||
- Themes.
|
||||
- Light theme.
|
||||
|
||||
## Currently supported
|
||||
|
||||
@@ -74,7 +74,7 @@ This is a roadmap with major features will come in near future.
|
||||
|
||||
- [x] Windows
|
||||
- [x] Linux
|
||||
- [x] MacOS (needs tests)
|
||||
- [x] MacOS (i need feedbacks)
|
||||
|
||||
#### • ARM
|
||||
|
||||
@@ -84,6 +84,10 @@ This is a roadmap with major features will come in near future.
|
||||
|
||||
## Translations
|
||||
|
||||
[Giuseppe Gigliotti](https://github.com/ReverbOD) / [Italian Translation](https://github.com/Fabio286/antares/pull/20)
|
||||
[Mohd-PH](https://github.com/Mohd-PH) / [Arabic Translation](https://github.com/Fabio286/antares/pull/29)
|
||||
[hongkfui](https://github.com/hongkfui) / [Spanish Translation](https://github.com/Fabio286/antares/pull/32)
|
||||
**Italian Translation** (46%) / [Giuseppe Gigliotti](https://github.com/ReverbOD) [[#20](https://github.com/Fabio286/antares/pull/20)]
|
||||
**Arabic Translation** (45%) / [Mohd-PH](https://github.com/Mohd-PH) [[#29](https://github.com/Fabio286/antares/pull/29)]
|
||||
**Spanish Translation** (46%) / [hongkfui](https://github.com/hongkfui) [[#32](https://github.com/Fabio286/antares/pull/32)]
|
||||
|
||||
## Reviews
|
||||
|
||||
<a target="_blank" href="https://www.softx64.com/windows/antares-sql-client.html" title="Antares SQL Client review"><img src="https://www.softx64.com/softx64-review.png" alt="Antares SQL Client review" /></a>
|
||||
|
29
package.json
29
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "antares",
|
||||
"productName": "Antares",
|
||||
"version": "0.0.13",
|
||||
"version": "0.0.16",
|
||||
"description": "A cross-platform easy to use SQL client.",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/Fabio286/antares.git",
|
||||
@@ -39,6 +39,10 @@
|
||||
"AppImage"
|
||||
],
|
||||
"category": "Development"
|
||||
},
|
||||
"appImage": {
|
||||
"license": "./LICENSE",
|
||||
"category": "Development"
|
||||
}
|
||||
},
|
||||
"electronWebpack": {
|
||||
@@ -47,10 +51,10 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdi/font": "^5.8.55",
|
||||
"@mdi/font": "^5.9.55",
|
||||
"ace-builds": "^1.4.12",
|
||||
"electron-log": "^4.3.0",
|
||||
"electron-store": "^6.0.1",
|
||||
"electron-store": "^7.0.0",
|
||||
"electron-updater": "^4.3.5",
|
||||
"keytar": "^7.3.0",
|
||||
"lodash": "^4.17.20",
|
||||
@@ -60,34 +64,33 @@
|
||||
"pg": "^8.5.1",
|
||||
"source-map-support": "^0.5.16",
|
||||
"spectre.css": "^0.5.9",
|
||||
"vue-i18n": "^8.22.2",
|
||||
"vue-i18n": "^8.22.4",
|
||||
"vue-the-mask": "^0.11.1",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuex": "^3.6.0",
|
||||
"vuex-persist": "^3.1.3"
|
||||
"vuex": "^3.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^10.1.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"electron": "^11.0.2",
|
||||
"electron": "^11.2.1",
|
||||
"electron-builder": "^22.9.1",
|
||||
"electron-devtools-installer": "^3.1.1",
|
||||
"electron-webpack": "^2.8.2",
|
||||
"electron-webpack-vue": "^2.4.0",
|
||||
"eslint": "^7.14.0",
|
||||
"eslint": "^7.18.0",
|
||||
"eslint-config-standard": "^16.0.2",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-vue": "^7.1.0",
|
||||
"eslint-plugin-vue": "^7.5.0",
|
||||
"node-sass": "^5.0.0",
|
||||
"sass-loader": "^10.1.0",
|
||||
"standard-version": "^9.0.0",
|
||||
"stylelint": "^13.8.0",
|
||||
"sass-loader": "^10.1.1",
|
||||
"standard-version": "^9.1.0",
|
||||
"stylelint": "^13.9.0",
|
||||
"stylelint-config-standard": "^20.0.0",
|
||||
"stylelint-scss": "^3.18.0",
|
||||
"vue": "^2.6.12",
|
||||
"vue-template-compiler": "^2.6.12",
|
||||
"webpack": "^4.44.2"
|
||||
"webpack": "^4.46.0"
|
||||
}
|
||||
}
|
||||
|
@@ -91,7 +91,7 @@ module.exports = [
|
||||
},
|
||||
{
|
||||
name: 'TINYTEXT',
|
||||
length: true,
|
||||
length: false,
|
||||
collation: true,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
@@ -119,7 +119,7 @@ module.exports = [
|
||||
},
|
||||
{
|
||||
name: 'JSON',
|
||||
length: true,
|
||||
length: false,
|
||||
collation: true,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
@@ -279,7 +279,7 @@ module.exports = [
|
||||
types: [
|
||||
{
|
||||
name: 'UNKNOWN',
|
||||
length: true,
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
|
@@ -7,6 +7,6 @@ export const DATE = ['DATE'];
|
||||
export const TIME = ['TIME'];
|
||||
export const DATETIME = ['DATETIME', 'TIMESTAMP'];
|
||||
|
||||
export const BLOB = ['BLOB', 'MEDIUMBLOB', 'LONGBLOB'];
|
||||
export const BLOB = ['BLOB', 'TINYBLOB', 'MEDIUMBLOB', 'LONGBLOB'];
|
||||
|
||||
export const BIT = ['BIT'];
|
||||
|
@@ -5,8 +5,12 @@ import * as path from 'path';
|
||||
import crypto from 'crypto';
|
||||
import { format as formatUrl } from 'url';
|
||||
import keytar from 'keytar';
|
||||
import Store from 'electron-store';
|
||||
|
||||
import ipcHandlers from './ipc-handlers';
|
||||
|
||||
Store.initRenderer();
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
|
||||
|
@@ -1,17 +1,28 @@
|
||||
|
||||
import fs from 'fs';
|
||||
import { ipcMain } from 'electron';
|
||||
import { ClientsFactory } from '../libs/ClientsFactory';
|
||||
|
||||
export default connections => {
|
||||
ipcMain.handle('test-connection', async (event, conn) => {
|
||||
const params = {
|
||||
host: conn.host,
|
||||
port: +conn.port,
|
||||
user: conn.user,
|
||||
password: conn.password
|
||||
};
|
||||
|
||||
if (conn.ssl) {
|
||||
params.ssl = {
|
||||
key: conn.key ? fs.readFileSync(conn.key) : null,
|
||||
cert: conn.cert ? fs.readFileSync(conn.cert) : null,
|
||||
ca: conn.ca ? fs.readFileSync(conn.ca) : null,
|
||||
ciphers: conn.ciphers
|
||||
};
|
||||
}
|
||||
|
||||
const connection = ClientsFactory.getConnection({
|
||||
client: conn.client,
|
||||
params: {
|
||||
host: conn.host,
|
||||
port: +conn.port,
|
||||
user: conn.user,
|
||||
password: conn.password
|
||||
}
|
||||
params
|
||||
});
|
||||
|
||||
await connection.connect();
|
||||
@@ -32,21 +43,32 @@ export default connections => {
|
||||
});
|
||||
|
||||
ipcMain.handle('connect', async (event, conn) => {
|
||||
const params = {
|
||||
host: conn.host,
|
||||
port: +conn.port,
|
||||
user: conn.user,
|
||||
password: conn.password
|
||||
};
|
||||
|
||||
if (conn.ssl) {
|
||||
params.ssl = {
|
||||
key: conn.key ? fs.readFileSync(conn.key) : null,
|
||||
cert: conn.cert ? fs.readFileSync(conn.cert) : null,
|
||||
ca: conn.ca ? fs.readFileSync(conn.ca) : null,
|
||||
ciphers: conn.ciphers
|
||||
};
|
||||
}
|
||||
|
||||
const connection = ClientsFactory.getConnection({
|
||||
client: conn.client,
|
||||
params: {
|
||||
host: conn.host,
|
||||
port: +conn.port,
|
||||
user: conn.user,
|
||||
password: conn.password
|
||||
},
|
||||
params,
|
||||
poolSize: 1
|
||||
});
|
||||
|
||||
try {
|
||||
await connection.connect();
|
||||
|
||||
const structure = await connection.getStructure();
|
||||
const structure = await connection.getStructure(new Set());
|
||||
|
||||
connections[conn.uid] = connection;
|
||||
|
||||
|
@@ -50,9 +50,9 @@ export default connections => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-structure', async (event, uid) => {
|
||||
ipcMain.handle('get-structure', async (event, params) => {
|
||||
try {
|
||||
const structure = await connections[uid].getStructure();
|
||||
const structure = await connections[params.uid].getStructure(params.schemas);
|
||||
|
||||
return { status: 'success', response: structure };
|
||||
}
|
||||
@@ -94,6 +94,17 @@ export default connections => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-version', async (event, uid) => {
|
||||
try {
|
||||
const result = await connections[uid].getVersion();
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('use-schema', async (event, { uid, schema }) => {
|
||||
if (!schema) return;
|
||||
|
||||
|
43
src/main/ipc-handlers/functions.js
Normal file
43
src/main/ipc-handlers/functions.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
export default (connections) => {
|
||||
ipcMain.handle('get-function-informations', async (event, params) => {
|
||||
try {
|
||||
const result = await connections[params.uid].getFunctionInformations(params);
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('drop-function', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].dropFunction(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('alter-function', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].alterFunction(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('create-function', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].createFunction(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
@@ -3,6 +3,8 @@ import tables from './tables';
|
||||
import views from './views';
|
||||
import triggers from './triggers';
|
||||
import routines from './routines';
|
||||
import functions from './functions';
|
||||
import schedulers from './schedulers';
|
||||
import updates from './updates';
|
||||
import application from './application';
|
||||
import database from './database';
|
||||
@@ -16,6 +18,8 @@ export default () => {
|
||||
views(connections);
|
||||
triggers(connections);
|
||||
routines(connections);
|
||||
functions(connections);
|
||||
schedulers(connections);
|
||||
database(connections);
|
||||
users(connections);
|
||||
updates();
|
||||
|
43
src/main/ipc-handlers/schedulers.js
Normal file
43
src/main/ipc-handlers/schedulers.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
export default (connections) => {
|
||||
ipcMain.handle('get-scheduler-informations', async (event, params) => {
|
||||
try {
|
||||
const result = await connections[params.uid].getEventInformations(params);
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('drop-scheduler', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].dropEvent(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('alter-scheduler', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].alterEvent(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('create-scheduler', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].createEvent(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
@@ -1,6 +1,6 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import { sqlEscaper } from 'common/libs/sqlEscaper';
|
||||
import { TEXT, LONG_TEXT, NUMBER, BLOB } from 'common/fieldTypes';
|
||||
import { TEXT, LONG_TEXT, NUMBER, BLOB, BIT } from 'common/fieldTypes';
|
||||
import fs from 'fs';
|
||||
|
||||
export default (connections) => {
|
||||
@@ -75,15 +75,40 @@ export default (connections) => {
|
||||
else
|
||||
escapedParam = '""';
|
||||
}
|
||||
else if ([...BIT].includes(params.type)) {
|
||||
escapedParam = `b'${sqlEscaper(params.content)}'`;
|
||||
reload = true;
|
||||
}
|
||||
else
|
||||
escapedParam = `"${sqlEscaper(params.content)}"`;
|
||||
|
||||
await connections[params.uid]
|
||||
.update({ [params.field]: `= ${escapedParam}` })
|
||||
.schema(params.schema)
|
||||
.from(params.table)
|
||||
.where({ [params.primary]: `= ${id}` })
|
||||
.run();
|
||||
if (params.primary) {
|
||||
await connections[params.uid]
|
||||
.update({ [params.field]: `= ${escapedParam}` })
|
||||
.schema(params.schema)
|
||||
.from(params.table)
|
||||
.where({ [params.primary]: `= ${id}` })
|
||||
.run();
|
||||
}
|
||||
else {
|
||||
const { row } = params;
|
||||
reload = true;
|
||||
|
||||
for (const key in row) {
|
||||
if (typeof row[key] === 'string')
|
||||
row[key] = `'${row[key]}'`;
|
||||
|
||||
row[key] = `= ${row[key]}`;
|
||||
}
|
||||
|
||||
await connections[params.uid]
|
||||
.schema(params.schema)
|
||||
.update({ [params.field]: `= ${escapedParam}` })
|
||||
.from(params.table)
|
||||
.where(row)
|
||||
.limit(1)
|
||||
.run();
|
||||
}
|
||||
|
||||
return { status: 'success', response: { reload } };
|
||||
}
|
||||
@@ -93,24 +118,47 @@ export default (connections) => {
|
||||
});
|
||||
|
||||
ipcMain.handle('delete-table-rows', async (event, params) => {
|
||||
let idString;
|
||||
if (params.primary) {
|
||||
const idString = params.rows.map(row => typeof row[params.primary] === 'string'
|
||||
? `"${row[params.primary]}"`
|
||||
: row[params.primary]).join(',');
|
||||
|
||||
if (typeof params.rows[0] === 'string')
|
||||
idString = params.rows.map(row => `"${row}"`).join(',');
|
||||
else
|
||||
idString = params.rows.join(',');
|
||||
try {
|
||||
const result = await connections[params.uid]
|
||||
.schema(params.schema)
|
||||
.delete(params.table)
|
||||
.where({ [params.primary]: `IN (${idString})` })
|
||||
.run();
|
||||
|
||||
try {
|
||||
const result = await connections[params.uid]
|
||||
.schema(params.schema)
|
||||
.delete(params.table)
|
||||
.where({ [params.primary]: `IN (${idString})` })
|
||||
.run();
|
||||
|
||||
return { status: 'success', response: result };
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
else {
|
||||
try {
|
||||
for (const row of params.rows) {
|
||||
for (const key in row) {
|
||||
if (typeof row[key] === 'string')
|
||||
row[key] = `'${row[key]}'`;
|
||||
|
||||
row[key] = `= ${row[key]}`;
|
||||
}
|
||||
|
||||
await connections[params.uid]
|
||||
.schema(params.schema)
|
||||
.delete(params.table)
|
||||
.where(row)
|
||||
.limit(1)
|
||||
.run();
|
||||
}
|
||||
|
||||
return { status: 'success', response: [] };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -1,8 +1,97 @@
|
||||
'use strict';
|
||||
import mysql from 'mysql';
|
||||
import { AntaresCore } from '../AntaresCore';
|
||||
import dataTypes from 'common/data-types/mysql';
|
||||
|
||||
export class MySQLClient extends AntaresCore {
|
||||
constructor (args) {
|
||||
super(args);
|
||||
|
||||
this.types = {
|
||||
0: 'DECIMAL',
|
||||
1: 'TINYINT',
|
||||
2: 'SMALLINT',
|
||||
3: 'INT',
|
||||
4: 'FLOAT',
|
||||
5: 'DOUBLE',
|
||||
6: 'NULL',
|
||||
7: 'TIMESTAMP',
|
||||
8: 'BIGINT',
|
||||
9: 'MEDIUMINT',
|
||||
10: 'DATE',
|
||||
11: 'TIME',
|
||||
12: 'DATETIME',
|
||||
13: 'YEAR',
|
||||
14: 'NEWDATE',
|
||||
15: 'VARCHAR',
|
||||
16: 'BIT',
|
||||
17: 'TIMESTAMP2',
|
||||
18: 'DATETIME2',
|
||||
19: 'TIME2',
|
||||
245: 'JSON',
|
||||
246: 'NEWDECIMAL',
|
||||
247: 'ENUM',
|
||||
248: 'SET',
|
||||
249: 'TINY_BLOB',
|
||||
250: 'MEDIUM_BLOB',
|
||||
251: 'LONG_BLOB',
|
||||
252: 'BLOB',
|
||||
253: 'VARCHAR',
|
||||
254: 'CHAR',
|
||||
255: 'GEOMETRY'
|
||||
};
|
||||
}
|
||||
|
||||
_getType (field) {
|
||||
let name = this.types[field.type];
|
||||
let length = field.length;
|
||||
|
||||
if (['DATE', 'TIME', 'YEAR', 'DATETIME'].includes(name))
|
||||
length = field.decimals;
|
||||
|
||||
if (name === 'CHAR' && field.charsetNr === 63)// if binary
|
||||
name = 'BINARY';
|
||||
|
||||
if (name === 'VARCHAR' && field.charsetNr === 63)// if binary
|
||||
name = 'VARBINARY';
|
||||
|
||||
if (name === 'BLOB') {
|
||||
switch (length) {
|
||||
case 765:
|
||||
name = 'TYNITEXT';
|
||||
break;
|
||||
case 196605:
|
||||
name = 'TEXT';
|
||||
break;
|
||||
case 50331645:
|
||||
name = 'MEDIUMTEXT';
|
||||
break;
|
||||
case 4294967295:
|
||||
name = field.charsetNr === 63 ? 'LONGBLOB' : 'LONGTEXT';
|
||||
break;
|
||||
case 255:
|
||||
name = 'TINYBLOB';
|
||||
break;
|
||||
case 65535:
|
||||
name = 'BLOB';
|
||||
break;
|
||||
case 16777215:
|
||||
name = 'MEDIUMBLOB';
|
||||
break;
|
||||
default:
|
||||
name = field.charsetNr === 63 ? 'BLOB' : 'TEXT';
|
||||
}
|
||||
}
|
||||
|
||||
return { name, length };
|
||||
}
|
||||
|
||||
_getTypeInfo (type) {
|
||||
return dataTypes
|
||||
.reduce((acc, group) => [...acc, ...group.types], [])
|
||||
.filter(_type => _type.name === type.toUpperCase())[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
@@ -31,10 +120,11 @@ export class MySQLClient extends AntaresCore {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array} schemas list
|
||||
* @returns {Array.<Object>} databases scructure
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async getStructure () {
|
||||
async getStructure (schemas) {
|
||||
const { rows: databases } = await this.raw('SHOW DATABASES');
|
||||
const { rows: functions } = await this.raw('SHOW FUNCTION STATUS');
|
||||
const { rows: procedures } = await this.raw('SHOW PROCEDURE STATUS');
|
||||
@@ -44,6 +134,8 @@ export class MySQLClient extends AntaresCore {
|
||||
const triggersArr = [];
|
||||
|
||||
for (const db of databases) {
|
||||
if (!schemas.has(db.Database)) continue;
|
||||
|
||||
let { rows: tables } = await this.raw(`SHOW TABLE STATUS FROM \`${db.Database}\``);
|
||||
if (tables.length) {
|
||||
tables = tables.map(table => {
|
||||
@@ -64,109 +156,121 @@ export class MySQLClient extends AntaresCore {
|
||||
}
|
||||
|
||||
return databases.map(db => {
|
||||
// TABLES
|
||||
const remappedTables = tablesArr.filter(table => table.Db === db.Database).map(table => {
|
||||
let tableType;
|
||||
switch (table.Comment) {
|
||||
case 'VIEW':
|
||||
tableType = 'view';
|
||||
break;
|
||||
default:
|
||||
tableType = 'table';
|
||||
break;
|
||||
}
|
||||
if (schemas.has(db.Database)) {
|
||||
// TABLES
|
||||
const remappedTables = tablesArr.filter(table => table.Db === db.Database).map(table => {
|
||||
let tableType;
|
||||
switch (table.Comment) {
|
||||
case 'VIEW':
|
||||
tableType = 'view';
|
||||
break;
|
||||
default:
|
||||
tableType = 'table';
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
name: table.Name,
|
||||
type: tableType,
|
||||
rows: table.Rows,
|
||||
created: table.Create_time,
|
||||
updated: table.Update_time,
|
||||
engine: table.Engine,
|
||||
comment: table.Comment,
|
||||
size: table.Data_length + table.Index_length,
|
||||
autoIncrement: table.Auto_increment,
|
||||
collation: table.Collation
|
||||
};
|
||||
});
|
||||
|
||||
// PROCEDURES
|
||||
const remappedProcedures = procedures.filter(procedure => procedure.Db === db.Database).map(procedure => {
|
||||
return {
|
||||
name: procedure.Name,
|
||||
type: procedure.Type,
|
||||
definer: procedure.Definer,
|
||||
created: procedure.Created,
|
||||
updated: procedure.Modified,
|
||||
comment: procedure.Comment,
|
||||
charset: procedure.character_set_client,
|
||||
security: procedure.Security_type
|
||||
};
|
||||
});
|
||||
|
||||
// FUNCTIONS
|
||||
const remappedFunctions = functions.filter(func => func.Db === db.Database).map(func => {
|
||||
return {
|
||||
name: func.Name,
|
||||
type: func.Type,
|
||||
definer: func.Definer,
|
||||
created: func.Created,
|
||||
updated: func.Modified,
|
||||
comment: func.Comment,
|
||||
charset: func.character_set_client,
|
||||
security: func.Security_type
|
||||
};
|
||||
});
|
||||
|
||||
// SCHEDULERS
|
||||
const remappedSchedulers = schedulers.filter(scheduler => scheduler.Db === db.Database).map(scheduler => {
|
||||
return {
|
||||
name: scheduler.EVENT_NAME,
|
||||
definition: scheduler.EVENT_DEFINITION,
|
||||
type: scheduler.EVENT_TYPE,
|
||||
definer: scheduler.DEFINER,
|
||||
body: scheduler.EVENT_BODY,
|
||||
starts: scheduler.STARTS,
|
||||
ends: scheduler.ENDS,
|
||||
status: scheduler.STATUS,
|
||||
executeAt: scheduler.EXECUTE_AT,
|
||||
intervalField: scheduler.INTERVAL_FIELD,
|
||||
intervalValue: scheduler.INTERVAL_VALUE,
|
||||
onCompletion: scheduler.ON_COMPLETION,
|
||||
originator: scheduler.ORIGINATOR,
|
||||
sqlMode: scheduler.SQL_MODE,
|
||||
created: scheduler.CREATED,
|
||||
updated: scheduler.LAST_ALTERED,
|
||||
lastExecuted: scheduler.LAST_EXECUTED,
|
||||
comment: scheduler.EVENT_COMMENT,
|
||||
charset: scheduler.CHARACTER_SET_CLIENT,
|
||||
timezone: scheduler.TIME_ZONE
|
||||
};
|
||||
});
|
||||
|
||||
// TRIGGERS
|
||||
const remappedTriggers = triggersArr.filter(trigger => trigger.Db === db.Database).map(trigger => {
|
||||
return {
|
||||
name: trigger.Trigger,
|
||||
statement: trigger.Statement,
|
||||
timing: trigger.Timing,
|
||||
definer: trigger.Definer,
|
||||
event: trigger.Event,
|
||||
table: trigger.Table,
|
||||
sqlMode: trigger.sql_mode,
|
||||
created: trigger.Created,
|
||||
charset: trigger.character_set_client
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
name: table.Name,
|
||||
type: tableType,
|
||||
rows: table.Rows,
|
||||
created: table.Create_time,
|
||||
updated: table.Update_time,
|
||||
engine: table.Engine,
|
||||
comment: table.Comment,
|
||||
size: table.Data_length + table.Index_length,
|
||||
autoIncrement: table.Auto_increment,
|
||||
collation: table.Collation
|
||||
name: db.Database,
|
||||
tables: remappedTables,
|
||||
functions: remappedFunctions,
|
||||
procedures: remappedProcedures,
|
||||
triggers: remappedTriggers,
|
||||
schedulers: remappedSchedulers
|
||||
};
|
||||
});
|
||||
|
||||
// PROCEDURES
|
||||
const remappedProcedures = procedures.filter(procedure => procedure.Db === db.Database).map(procedure => {
|
||||
}
|
||||
else {
|
||||
return {
|
||||
name: procedure.Name,
|
||||
type: procedure.Type,
|
||||
definer: procedure.Definer,
|
||||
created: procedure.Created,
|
||||
updated: procedure.Modified,
|
||||
comment: procedure.Comment,
|
||||
charset: procedure.character_set_client,
|
||||
security: procedure.Security_type
|
||||
name: db.Database,
|
||||
tables: [],
|
||||
functions: [],
|
||||
procedures: [],
|
||||
triggers: [],
|
||||
schedulers: []
|
||||
};
|
||||
});
|
||||
|
||||
// FUNCTIONS
|
||||
const remappedFunctions = functions.filter(func => func.Db === db.Database).map(func => {
|
||||
return {
|
||||
name: func.Name,
|
||||
type: func.Type,
|
||||
definer: func.Definer,
|
||||
created: func.Created,
|
||||
updated: func.Modified,
|
||||
comment: func.Comment,
|
||||
charset: func.character_set_client,
|
||||
security: func.Security_type
|
||||
};
|
||||
});
|
||||
|
||||
// SCHEDULERS
|
||||
const remappedSchedulers = schedulers.filter(scheduler => scheduler.Db === db.Database).map(scheduler => {
|
||||
return {
|
||||
name: scheduler.EVENT_NAME,
|
||||
definition: scheduler.EVENT_DEFINITION,
|
||||
type: scheduler.EVENT_TYPE,
|
||||
definer: scheduler.DEFINER,
|
||||
body: scheduler.EVENT_BODY,
|
||||
starts: scheduler.STARTS,
|
||||
ends: scheduler.ENDS,
|
||||
status: scheduler.STATUS,
|
||||
executeAt: scheduler.EXECUTE_AT,
|
||||
intervalField: scheduler.INTERVAL_FIELD,
|
||||
intervalValue: scheduler.INTERVAL_VALUE,
|
||||
onCompletion: scheduler.ON_COMPLETION,
|
||||
originator: scheduler.ORIGINATOR,
|
||||
sqlMode: scheduler.SQL_MODE,
|
||||
created: scheduler.CREATED,
|
||||
updated: scheduler.LAST_ALTERED,
|
||||
lastExecuted: scheduler.LAST_EXECUTED,
|
||||
comment: scheduler.EVENT_COMMENT,
|
||||
charset: scheduler.CHARACTER_SET_CLIENT,
|
||||
timezone: scheduler.TIME_ZONE
|
||||
};
|
||||
});
|
||||
|
||||
// TRIGGERS
|
||||
const remappedTriggers = triggersArr.filter(trigger => trigger.Db === db.Database).map(trigger => {
|
||||
return {
|
||||
name: trigger.Trigger,
|
||||
statement: trigger.Statement,
|
||||
timing: trigger.Timing,
|
||||
definer: trigger.Definer,
|
||||
event: trigger.Event,
|
||||
table: trigger.Table,
|
||||
sqlMode: trigger.sql_mode,
|
||||
created: trigger.Created,
|
||||
charset: trigger.character_set_client
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
name: db.Database,
|
||||
tables: remappedTables,
|
||||
functions: remappedFunctions,
|
||||
procedures: remappedProcedures,
|
||||
triggers: remappedTriggers,
|
||||
schedulers: remappedSchedulers
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -279,13 +383,13 @@ export class MySQLClient extends AntaresCore {
|
||||
}
|
||||
|
||||
/**
|
||||
* SELECT `user`, `host`, IF(LENGTH(password)>0, password, authentication_string) AS `password` FROM `mysql`.`user`
|
||||
* SELECT `user`, `host`, authentication_string) AS `password` FROM `mysql`.`user`
|
||||
*
|
||||
* @returns {Array.<Object>} users list
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async getUsers () {
|
||||
const { rows } = await this.raw('SELECT `user`, `host`, IF(LENGTH(password)>0, password, authentication_string) AS `password` FROM `mysql`.`user`');
|
||||
const { rows } = await this.raw('SELECT `user`, `host`, authentication_string AS `password` FROM `mysql`.`user`');
|
||||
|
||||
return rows.map(row => {
|
||||
return {
|
||||
@@ -433,6 +537,19 @@ export class MySQLClient extends AntaresCore {
|
||||
const results = await this.raw(sql);
|
||||
|
||||
return results.rows.map(row => {
|
||||
if (!row['Create Procedure']) {
|
||||
return {
|
||||
definer: null,
|
||||
sql: '',
|
||||
parameters: [],
|
||||
name: row.Procedure,
|
||||
comment: '',
|
||||
security: 'DEFINER',
|
||||
deterministic: false,
|
||||
dataAccess: 'CONTAINS SQL'
|
||||
};
|
||||
}
|
||||
|
||||
const parameters = row['Create Procedure']
|
||||
.match(/(?<=\().*?(?=\))/s)[0]
|
||||
.replaceAll('\r', '')
|
||||
@@ -527,6 +644,220 @@ export class MySQLClient extends AntaresCore {
|
||||
return await this.raw(sql, { split: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* SHOW CREATE FUNCTION
|
||||
*
|
||||
* @returns {Array.<Object>} view informations
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async getFunctionInformations ({ schema, func }) {
|
||||
const sql = `SHOW CREATE FUNCTION \`${schema}\`.\`${func}\``;
|
||||
const results = await this.raw(sql);
|
||||
|
||||
return results.rows.map(row => {
|
||||
if (!row['Create Function']) {
|
||||
return {
|
||||
definer: null,
|
||||
sql: '',
|
||||
parameters: [],
|
||||
name: row.Procedure,
|
||||
comment: '',
|
||||
security: 'DEFINER',
|
||||
deterministic: false,
|
||||
dataAccess: 'CONTAINS SQL',
|
||||
returns: 'INT',
|
||||
returnsLength: null
|
||||
};
|
||||
}
|
||||
|
||||
const parameters = row['Create Function']
|
||||
.match(/(?<=\().*?(?=\))/s)[0]
|
||||
.replaceAll('\r', '')
|
||||
.replaceAll('\t', '')
|
||||
.split(',')
|
||||
.map(el => {
|
||||
const param = el.split(' ');
|
||||
const type = param[1] ? param[1].replace(')', '').split('(') : ['', null];
|
||||
|
||||
return {
|
||||
name: param[0] ? param[0].replaceAll('`', '') : '',
|
||||
type: type[0],
|
||||
length: +type[1]
|
||||
};
|
||||
}).filter(el => el.name);
|
||||
|
||||
let dataAccess = 'CONTAINS SQL';
|
||||
if (row['Create Function'].includes('NO SQL'))
|
||||
dataAccess = 'NO SQL';
|
||||
if (row['Create Function'].includes('READS SQL DATA'))
|
||||
dataAccess = 'READS SQL DATA';
|
||||
if (row['Create Function'].includes('MODIFIES SQL DATA'))
|
||||
dataAccess = 'MODIFIES SQL DATA';
|
||||
|
||||
const output = row['Create Function'].match(/(?<=RETURNS ).*?(?=\s)/gs).length ? row['Create Function'].match(/(?<=RETURNS ).*?(?=\s)/gs)[0].replace(')', '').split('(') : ['', null];
|
||||
|
||||
return {
|
||||
definer: row['Create Function'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0],
|
||||
sql: row['Create Function'].match(/(BEGIN|begin)(.*)(END|end)/gs)[0],
|
||||
parameters,
|
||||
name: row.Function,
|
||||
comment: row['Create Function'].match(/(?<=COMMENT ').*?(?=')/gs) ? row['Create Function'].match(/(?<=COMMENT ').*?(?=')/gs)[0] : '',
|
||||
security: row['Create Function'].includes('SQL SECURITY INVOKER') ? 'INVOKER' : 'DEFINER',
|
||||
deterministic: row['Create Function'].includes('DETERMINISTIC'),
|
||||
dataAccess,
|
||||
returns: output[0].toUpperCase(),
|
||||
returnsLength: +output[1]
|
||||
};
|
||||
})[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* DROP FUNCTION
|
||||
*
|
||||
* @returns {Array.<Object>} parameters
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async dropFunction (params) {
|
||||
const sql = `DROP FUNCTION \`${params.func}\``;
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* ALTER FUNCTION
|
||||
*
|
||||
* @returns {Array.<Object>} parameters
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async alterFunction (params) {
|
||||
const { func } = params;
|
||||
const tempProcedure = Object.assign({}, func);
|
||||
tempProcedure.name = `Antares_${tempProcedure.name}_tmp`;
|
||||
|
||||
try {
|
||||
await this.createFunction(tempProcedure);
|
||||
await this.dropFunction({ func: tempProcedure.name });
|
||||
await this.dropFunction({ func: func.oldName });
|
||||
await this.createFunction(func);
|
||||
}
|
||||
catch (err) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CREATE FUNCTION
|
||||
*
|
||||
* @returns {Array.<Object>} parameters
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async createFunction (func) {
|
||||
const parameters = func.parameters.reduce((acc, curr) => {
|
||||
acc.push(`\`${curr.name}\` ${curr.type}${curr.length ? `(${curr.length})` : ''}`);
|
||||
return acc;
|
||||
}, []).join(',');
|
||||
|
||||
const sql = `CREATE ${func.definer ? `DEFINER=${func.definer} ` : ''}FUNCTION \`${func.name}\`(${parameters}) RETURNS ${func.returns}${func.returnsLength ? `(${func.returnsLength})` : ''}
|
||||
LANGUAGE SQL
|
||||
${func.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'}
|
||||
${func.dataAccess}
|
||||
SQL SECURITY ${func.security}
|
||||
COMMENT '${func.comment}'
|
||||
${func.sql}`;
|
||||
|
||||
return await this.raw(sql, { split: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* SHOW CREATE EVENT
|
||||
*
|
||||
* @returns {Array.<Object>} view informations
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async getEventInformations ({ schema, scheduler }) {
|
||||
const sql = `SHOW CREATE EVENT \`${schema}\`.\`${scheduler}\``;
|
||||
const results = await this.raw(sql);
|
||||
|
||||
return results.rows.map(row => {
|
||||
const schedule = row['Create Event'];
|
||||
const execution = schedule.includes('EVERY') ? 'EVERY' : 'ONCE';
|
||||
const every = execution === 'EVERY' ? row['Create Event'].match(/(?<=EVERY )(\s*([^\s]+)){0,2}/gs)[0].replaceAll('\'', '').split(' ') : [];
|
||||
const starts = execution === 'EVERY' && schedule.includes('STARTS') ? schedule.match(/(?<=STARTS ').*?(?='\s)/gs)[0] : '';
|
||||
const ends = execution === 'EVERY' && schedule.includes('ENDS') ? schedule.match(/(?<=ENDS ').*?(?='\s)/gs)[0] : '';
|
||||
const at = execution === 'ONCE' && schedule.includes('AT') ? schedule.match(/(?<=AT ').*?(?='\s)/gs)[0] : '';
|
||||
|
||||
return {
|
||||
definer: row['Create Event'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0],
|
||||
sql: row['Create Event'].match(/(?<=DO )(.*)/gs)[0],
|
||||
name: row.Event,
|
||||
comment: row['Create Event'].match(/(?<=COMMENT ').*?(?=')/gs) ? row['Create Event'].match(/(?<=COMMENT ').*?(?=')/gs)[0] : '',
|
||||
state: row['Create Event'].includes('ENABLE') ? 'ENABLE' : row['Create Event'].includes('DISABLE ON SLAVE') ? 'DISABLE ON SLAVE' : 'DISABLE',
|
||||
preserve: row['Create Event'].includes('ON COMPLETION PRESERVE'),
|
||||
execution,
|
||||
every,
|
||||
starts,
|
||||
ends,
|
||||
at
|
||||
};
|
||||
})[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* DROP EVENT
|
||||
*
|
||||
* @returns {Array.<Object>} parameters
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async dropEvent (params) {
|
||||
const sql = `DROP EVENT \`${params.scheduler}\``;
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* ALTER EVENT
|
||||
*
|
||||
* @returns {Array.<Object>} parameters
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async alterEvent (params) {
|
||||
const { scheduler } = params;
|
||||
|
||||
if (scheduler.execution === 'EVERY' && scheduler.every[0].includes('-'))
|
||||
scheduler.every[0] = `'${scheduler.every[0]}'`;
|
||||
|
||||
const sql = `ALTER ${scheduler.definer ? ` DEFINER=${scheduler.definer}` : ''} EVENT \`${scheduler.oldName}\`
|
||||
ON SCHEDULE
|
||||
${scheduler.execution === 'EVERY'
|
||||
? `EVERY ${scheduler.every.join(' ')}${scheduler.starts ? ` STARTS '${scheduler.starts}'` : ''}${scheduler.ends ? ` ENDS '${scheduler.ends}'` : ''}`
|
||||
: `AT '${scheduler.at}'`}
|
||||
ON COMPLETION${!scheduler.preserve ? ' NOT' : ''} PRESERVE
|
||||
${scheduler.name !== scheduler.oldName ? `RENAME TO \`${scheduler.name}\`` : ''}
|
||||
${scheduler.state}
|
||||
COMMENT '${scheduler.comment}'
|
||||
DO ${scheduler.sql}`;
|
||||
|
||||
return await this.raw(sql, { split: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* CREATE EVENT
|
||||
*
|
||||
* @returns {Array.<Object>} parameters
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async createEvent (scheduler) {
|
||||
const sql = `CREATE ${scheduler.definer ? ` DEFINER=${scheduler.definer}` : ''} EVENT \`${scheduler.name}\`
|
||||
ON SCHEDULE
|
||||
${scheduler.execution === 'EVERY'
|
||||
? `EVERY ${scheduler.every.join(' ')}${scheduler.starts ? ` STARTS '${scheduler.starts}'` : ''}${scheduler.ends ? ` ENDS '${scheduler.ends}'` : ''}`
|
||||
: `AT '${scheduler.at}'`}
|
||||
ON COMPLETION${!scheduler.preserve ? ' NOT' : ''} PRESERVE
|
||||
${scheduler.state}
|
||||
COMMENT '${scheduler.comment}'
|
||||
DO ${scheduler.sql}`;
|
||||
|
||||
return await this.raw(sql, { split: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* SHOW COLLATION
|
||||
*
|
||||
@@ -589,6 +920,35 @@ export class MySQLClient extends AntaresCore {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* SHOW VARIABLES LIKE '%vers%'
|
||||
*
|
||||
* @returns {Array.<Object>} version parameters
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async getVersion () {
|
||||
const sql = 'SHOW VARIABLES LIKE "%vers%"';
|
||||
const { rows } = await this.raw(sql);
|
||||
|
||||
return rows.reduce((acc, curr) => {
|
||||
switch (curr.Variable_name) {
|
||||
case 'version':
|
||||
acc.number = curr.Value.split('-')[0];
|
||||
break;
|
||||
case 'version_comment':
|
||||
acc.name = curr.Value.replace('(GPL)', '');
|
||||
break;
|
||||
case 'version_compile_machine':
|
||||
acc.arch = curr.Value;
|
||||
break;
|
||||
case 'version_compile_os':
|
||||
acc.os = curr.Value;
|
||||
break;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* CREATE TABLE
|
||||
*
|
||||
@@ -636,7 +996,8 @@ export class MySQLClient extends AntaresCore {
|
||||
|
||||
// ADD FIELDS
|
||||
additions.forEach(addition => {
|
||||
const length = addition.numLength || addition.charLength || addition.datePrecision;
|
||||
const typeInfo = this._getTypeInfo(addition.type);
|
||||
const length = typeInfo.length ? addition.numLength || addition.charLength || addition.datePrecision : false;
|
||||
|
||||
alterColumns.push(`ADD COLUMN \`${addition.name}\`
|
||||
${addition.type.toUpperCase()}${length ? `(${length})` : ''}
|
||||
@@ -673,7 +1034,8 @@ export class MySQLClient extends AntaresCore {
|
||||
|
||||
// CHANGE FIELDS
|
||||
changes.forEach(change => {
|
||||
const length = change.numLength || change.charLength || change.datePrecision;
|
||||
const typeInfo = this._getTypeInfo(change.type);
|
||||
const length = typeInfo.length ? change.numLength || change.charLength || change.datePrecision : false;
|
||||
|
||||
alterColumns.push(`CHANGE COLUMN \`${change.orgName}\` \`${change.name}\`
|
||||
${change.type.toUpperCase()}${length ? `(${length})` : ''}
|
||||
@@ -842,14 +1204,12 @@ export class MySQLClient extends AntaresCore {
|
||||
const nestTables = args.nest ? '.' : false;
|
||||
const resultsArr = [];
|
||||
let paramsArr = [];
|
||||
let selectedFields = [];
|
||||
const queries = args.split ? sql.split(';') : [sql];
|
||||
|
||||
if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder
|
||||
|
||||
for (const query of queries) {
|
||||
if (!query) continue;
|
||||
let fieldsArr = [];
|
||||
let keysArr = [];
|
||||
|
||||
const { rows, report, fields, keys } = await new Promise((resolve, reject) => {
|
||||
@@ -859,32 +1219,34 @@ export class MySQLClient extends AntaresCore {
|
||||
if (err)
|
||||
reject(err);
|
||||
else {
|
||||
const remappedFields = fields
|
||||
let remappedFields = fields
|
||||
? fields.map(field => {
|
||||
if (!field || Array.isArray(field))
|
||||
return false;
|
||||
|
||||
const type = this._getType(field);
|
||||
|
||||
return {
|
||||
name: field.name,
|
||||
name: field.orgName,
|
||||
alias: field.name,
|
||||
orgName: field.orgName,
|
||||
schema: field.db,
|
||||
table: field.table,
|
||||
tableAlias: field.table,
|
||||
zerofill: field.zerofill,
|
||||
orgTable: field.orgTable,
|
||||
type: 'VARCHAR'
|
||||
type: type.name,
|
||||
length: type.length
|
||||
};
|
||||
})
|
||||
}).filter(Boolean)
|
||||
: [];
|
||||
|
||||
if (args.details) {
|
||||
let cachedTable;
|
||||
|
||||
if (remappedFields.length) {
|
||||
selectedFields = remappedFields.map(field => {
|
||||
return {
|
||||
name: field.orgName || field.name,
|
||||
table: field.orgTable || field.table
|
||||
};
|
||||
});
|
||||
|
||||
paramsArr = remappedFields.map(field => {
|
||||
if (field.table) cachedTable = field.table;// Needed for some queries on information_schema
|
||||
if (field.orgTable) cachedTable = field.orgTable;// Needed for some queries on information_schema
|
||||
return {
|
||||
table: field.orgTable || cachedTable,
|
||||
schema: field.schema || 'INFORMATION_SCHEMA'
|
||||
@@ -892,43 +1254,16 @@ export class MySQLClient extends AntaresCore {
|
||||
}).filter((val, i, arr) => arr.findIndex(el => el.schema === val.schema && el.table === val.table) === i);
|
||||
|
||||
for (const paramObj of paramsArr) {
|
||||
try { // Table data
|
||||
if (!paramObj.table || !paramObj.schema) continue;
|
||||
|
||||
try { // Column details
|
||||
const response = await this.getTableColumns(paramObj);
|
||||
|
||||
let detailedFields = response.length
|
||||
? selectedFields.map(selField => {
|
||||
return response.find(field => field.name.toLowerCase() === selField.name.toLowerCase() && field.table === selField.table);
|
||||
}).filter(el => !!el)
|
||||
: [];
|
||||
|
||||
if (selectedFields.length) {
|
||||
detailedFields = detailedFields.map(field => {
|
||||
const aliasObj = remappedFields.find(resField => resField.orgName === field.name && resField.orgTable === field.table);
|
||||
return {
|
||||
...field,
|
||||
alias: aliasObj.name || field.name,
|
||||
tableAlias: aliasObj.table || field.table
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (!detailedFields.length) {
|
||||
detailedFields = remappedFields.map(field => {
|
||||
const isInFields = fieldsArr.some(f => field.name.toLowerCase() === f.name.toLowerCase() && field.table === f.table);
|
||||
|
||||
if (!isInFields) {
|
||||
return {
|
||||
...field,
|
||||
alias: field.name,
|
||||
tableAlias: field.table
|
||||
};
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}).filter(Boolean);
|
||||
}
|
||||
|
||||
fieldsArr = fieldsArr ? [...fieldsArr, ...detailedFields] : detailedFields;
|
||||
remappedFields = remappedFields.map(field => {
|
||||
const detailedField = response.find(f => f.name === field.name);
|
||||
if (detailedField && field.orgTable === paramObj.table && field.schema === paramObj.schema && detailedField.name === field.orgName)
|
||||
field = { ...detailedField, ...field };
|
||||
return field;
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
reject(err);
|
||||
@@ -946,9 +1281,9 @@ export class MySQLClient extends AntaresCore {
|
||||
}
|
||||
|
||||
resolve({
|
||||
rows: Array.isArray(queryResult) ? queryResult : false,
|
||||
rows: Array.isArray(queryResult) ? queryResult.some(el => Array.isArray(el)) ? [] : queryResult : false,
|
||||
report: !Array.isArray(queryResult) ? queryResult : false,
|
||||
fields: fieldsArr.length ? fieldsArr : remappedFields,
|
||||
fields: remappedFields,
|
||||
keys: keysArr
|
||||
});
|
||||
}
|
||||
|
22
src/renderer/components/BaseLoader.vue
Normal file
22
src/renderer/components/BaseLoader.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div class="empty">
|
||||
<div class="loading loading-lg" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BaseLoader'
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.empty {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
left: 0;
|
||||
justify-content: center;
|
||||
right: 0;
|
||||
}
|
||||
</style>
|
111
src/renderer/components/BaseUploadInput.vue
Normal file
111
src/renderer/components/BaseUploadInput.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<label :for="`id_${id}`" class="file-uploader">
|
||||
<span class="file-uploader-message">
|
||||
<i class="mdi mdi-upload mr-1" />{{ message }}
|
||||
</span>
|
||||
<span class="text-ellipsis file-uploader-value">
|
||||
{{ value | lastPart }}
|
||||
</span>
|
||||
<i
|
||||
v-if="value.length"
|
||||
class="file-uploader-reset mdi mdi-close"
|
||||
@click.prevent="clear"
|
||||
/>
|
||||
<form :ref="`form_${id}`">
|
||||
<input
|
||||
:id="`id_${id}`"
|
||||
class="file-uploader-input"
|
||||
type="file"
|
||||
@change="$emit('change', $event)"
|
||||
>
|
||||
</form>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BaseUploadInput',
|
||||
filters: {
|
||||
lastPart (string) {
|
||||
if (!string) return '';
|
||||
|
||||
string = string.split(/[/\\]+/).pop();
|
||||
if (string.length >= 19)
|
||||
string = `...${string.slice(-19)}`;
|
||||
return string;
|
||||
}
|
||||
},
|
||||
props: {
|
||||
message: {
|
||||
default: 'Upload',
|
||||
type: String
|
||||
},
|
||||
value: {
|
||||
default: '',
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
id: null
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.id = this._uid;
|
||||
},
|
||||
methods: {
|
||||
clear () {
|
||||
this.$emit('clear');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.file-uploader {
|
||||
border: 0.05rem solid $bg-color-light;
|
||||
border-radius: 0.1rem;
|
||||
height: 1.8rem;
|
||||
line-height: 1.2rem;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
background-color: $bg-color-gray;
|
||||
transition: background 0.2s, border 0.2s, box-shadow 0.2s, color 0.2s;
|
||||
position: relative;
|
||||
|
||||
> span {
|
||||
padding: 0.25rem 0.4rem;
|
||||
}
|
||||
|
||||
.file-uploader-message {
|
||||
display: flex;
|
||||
border-right: 0.05rem solid $bg-color-light;
|
||||
background-color: $bg-color;
|
||||
}
|
||||
|
||||
.file-uploader-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.file-uploader-value {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.file-uploader-reset {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
:disabled {
|
||||
.file-uploader {
|
||||
cursor: not-allowed;
|
||||
background-color: #151515;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -15,7 +15,7 @@
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-3">
|
||||
<label class="form-label">{{ $t('word.user') }}:</label>
|
||||
<label class="form-label">{{ $t('word.user') }}</label>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<input
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-3">
|
||||
<label class="form-label">{{ $t('word.password') }}:</label>
|
||||
<label class="form-label">{{ $t('word.password') }}</label>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<input
|
||||
|
@@ -10,36 +10,56 @@
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click="closeModal" />
|
||||
</div>
|
||||
<div class="modal-body pb-0">
|
||||
<div class="content">
|
||||
<form class="form-horizontal">
|
||||
<fieldset class="m-0" :disabled="isTesting">
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.connectionName') }}:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="localConnection.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.client') }}:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<select v-model="localConnection.client" class="form-select">
|
||||
<option value="mysql">
|
||||
MySQL
|
||||
</option>
|
||||
<option value="maria">
|
||||
MariaDB
|
||||
</option>
|
||||
<!-- <option value="mssql">
|
||||
<div class="modal-body p-0">
|
||||
<div class="panel">
|
||||
<div class="panel-nav">
|
||||
<ul class="tab tab-block">
|
||||
<li
|
||||
class="tab-item"
|
||||
:class="{'active': selectedTab === 'general'}"
|
||||
@click="selectTab('general')"
|
||||
>
|
||||
<a class="c-hand">{{ $t('word.general') }}</a>
|
||||
</li>
|
||||
<li
|
||||
class="tab-item"
|
||||
:class="{'active': selectedTab === 'ssl'}"
|
||||
@click="selectTab('ssl')"
|
||||
>
|
||||
<a class="c-hand">{{ $t('word.ssl') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="selectedTab === 'general'" class="panel-body py-0">
|
||||
<div class="container">
|
||||
<form class="form-horizontal">
|
||||
<fieldset class="m-0" :disabled="isTesting">
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.connectionName') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="localConnection.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.client') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<select v-model="localConnection.client" class="form-select">
|
||||
<option value="mysql">
|
||||
MySQL
|
||||
</option>
|
||||
<option value="maria">
|
||||
MariaDB
|
||||
</option>
|
||||
<!-- <option value="mssql">
|
||||
Microsoft SQL
|
||||
</option>
|
||||
<option value="pg">
|
||||
@@ -48,99 +68,177 @@
|
||||
<option value="oracledb">
|
||||
Oracle DB
|
||||
</option> -->
|
||||
</select>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.hostName') }}/IP</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.host"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.port') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.port"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.user') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.user"
|
||||
class="form-input"
|
||||
type="text"
|
||||
:disabled="localConnection.ask"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.password') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.password"
|
||||
class="form-input"
|
||||
type="password"
|
||||
:disabled="localConnection.ask"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12" />
|
||||
<div class="col-8 col-sm-12">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="localConnection.ask" type="checkbox"><i class="form-icon" /> {{ $t('message.askCredentials') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
<BaseToast
|
||||
class="mb-2"
|
||||
:message="toast.message"
|
||||
:status="toast.status"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="selectedTab === 'ssl'" class="panel-body py-0">
|
||||
<div class="container">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">
|
||||
{{ $t('message.enableSsl') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<label class="form-switch d-inline-block" @click.prevent="toggleSsl">
|
||||
<input type="checkbox" :checked="localConnection.ssl">
|
||||
<i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.hostName') }}/IP:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.host"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.port') }}:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.port"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.user') }}:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.user"
|
||||
class="form-input"
|
||||
type="text"
|
||||
:disabled="localConnection.ask"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.password') }}:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="localConnection.password"
|
||||
class="form-input"
|
||||
type="password"
|
||||
:disabled="localConnection.ask"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12" />
|
||||
<div class="col-8 col-sm-12">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="localConnection.ask" type="checkbox"><i class="form-icon" /> {{ $t('message.askCredentials') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
<fieldset class="m-0" :disabled="isTesting || !localConnection.ssl">
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.privateKey') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:value="localConnection.key"
|
||||
@clear="pathClear('key')"
|
||||
@change="pathSelection($event, 'key')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.certificate') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:value="localConnection.cert"
|
||||
@clear="pathClear('cert')"
|
||||
@change="pathSelection($event, 'cert')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.caCertificate') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:value="localConnection.ca"
|
||||
@clear="pathClear('ca')"
|
||||
@change="pathSelection($event, 'ca')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.ciphers') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="localConnection.ciphers"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
<BaseToast
|
||||
class="mb-2"
|
||||
:message="toast.message"
|
||||
:status="toast.status"
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-footer text-light">
|
||||
<button
|
||||
class="btn btn-gray mr-2"
|
||||
:class="{'loading': isTesting}"
|
||||
@click="startTest"
|
||||
>
|
||||
{{ $t('message.testConnection') }}
|
||||
</button>
|
||||
<button class="btn btn-primary mr-2" @click="saveEditConnection">
|
||||
{{ $t('word.save') }}
|
||||
</button>
|
||||
<button class="btn btn-link" @click="closeModal">
|
||||
{{ $t('word.close') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<BaseToast
|
||||
class="mb-2"
|
||||
:message="toast.message"
|
||||
:status="toast.status"
|
||||
<ModalAskCredentials
|
||||
v-if="isAsking"
|
||||
@close-asking="closeAsking"
|
||||
@credentials="continueTest"
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-footer text-light">
|
||||
<button
|
||||
class="btn btn-gray mr-2"
|
||||
:class="{'loading': isTesting}"
|
||||
@click="startTest"
|
||||
>
|
||||
{{ $t('message.testConnection') }}
|
||||
</button>
|
||||
<button class="btn btn-primary mr-2" @click="saveEditConnection">
|
||||
{{ $t('word.save') }}
|
||||
</button>
|
||||
<button class="btn btn-link" @click="closeModal">
|
||||
{{ $t('word.close') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ModalAskCredentials
|
||||
v-if="isAsking"
|
||||
@close-asking="closeAsking"
|
||||
@credentials="continueTest"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -149,12 +247,14 @@ import { mapActions } from 'vuex';
|
||||
import Connection from '@/ipc-api/Connection';
|
||||
import ModalAskCredentials from '@/components/ModalAskCredentials';
|
||||
import BaseToast from '@/components/BaseToast';
|
||||
import BaseUploadInput from '@/components/BaseUploadInput';
|
||||
|
||||
export default {
|
||||
name: 'ModalEditConnection',
|
||||
components: {
|
||||
ModalAskCredentials,
|
||||
BaseToast
|
||||
BaseToast,
|
||||
BaseUploadInput
|
||||
},
|
||||
props: {
|
||||
connection: Object
|
||||
@@ -167,7 +267,8 @@ export default {
|
||||
},
|
||||
isTesting: false,
|
||||
isAsking: false,
|
||||
localConnection: null
|
||||
localConnection: null,
|
||||
selectedTab: 'general'
|
||||
};
|
||||
},
|
||||
created () {
|
||||
@@ -240,6 +341,21 @@ export default {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
},
|
||||
selectTab (tab) {
|
||||
this.selectedTab = tab;
|
||||
},
|
||||
toggleSsl () {
|
||||
this.localConnection.ssl = !this.localConnection.ssl;
|
||||
},
|
||||
pathSelection (event, name) {
|
||||
const { files } = event.target;
|
||||
if (!files.length) return;
|
||||
|
||||
this.localConnection[name] = files[0].path;
|
||||
},
|
||||
pathClear (name) {
|
||||
this.localConnection[name] = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -247,6 +363,8 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
.modal-container {
|
||||
position: absolute;
|
||||
max-width: 450px;
|
||||
top: 17.5vh;
|
||||
}
|
||||
</style>
|
||||
|
@@ -15,7 +15,7 @@
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-3">
|
||||
<label class="form-label">{{ $t('word.name') }}:</label>
|
||||
<label class="form-label">{{ $t('word.name') }}</label>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<input
|
||||
@@ -30,7 +30,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-3">
|
||||
<label class="form-label">{{ $t('word.collation') }}:</label>
|
||||
<label class="form-label">{{ $t('word.collation') }}</label>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<select
|
||||
|
@@ -10,119 +10,217 @@
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click="closeModal" />
|
||||
</div>
|
||||
<div class="modal-body pb-0">
|
||||
<div class="content">
|
||||
<form class="form-horizontal">
|
||||
<fieldset class="m-0" :disabled="isTesting">
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.connectionName') }}:</label>
|
||||
<div class="modal-body p-0">
|
||||
<div class="panel">
|
||||
<div class="panel-nav">
|
||||
<ul class="tab tab-block">
|
||||
<li
|
||||
class="tab-item"
|
||||
:class="{'active': selectedTab === 'general'}"
|
||||
@click="selectTab('general')"
|
||||
>
|
||||
<a class="c-hand">{{ $t('word.general') }}</a>
|
||||
</li>
|
||||
<li
|
||||
class="tab-item"
|
||||
:class="{'active': selectedTab === 'ssl'}"
|
||||
@click="selectTab('ssl')"
|
||||
>
|
||||
<a class="c-hand">{{ $t('word.ssl') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="selectedTab === 'general'" class="panel-body py-0">
|
||||
<div class="container">
|
||||
<form class="form-horizontal">
|
||||
<fieldset class="m-0" :disabled="isTesting">
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.connectionName') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="connection.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.client') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<select
|
||||
v-model="connection.client"
|
||||
class="form-select"
|
||||
@change="setDefaults"
|
||||
>
|
||||
<option value="mysql">
|
||||
MySQL
|
||||
</option>
|
||||
<option value="maria">
|
||||
MariaDB
|
||||
</option>
|
||||
<!-- <option value="mssql">
|
||||
Microsoft SQL
|
||||
</option>
|
||||
<option value="pg">
|
||||
PostgreSQL
|
||||
</option>
|
||||
<option value="oracledb">
|
||||
Oracle DB
|
||||
</option> -->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.hostName') }}/IP</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="connection.host"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.port') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="connection.port"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.user') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="connection.user"
|
||||
class="form-input"
|
||||
type="text"
|
||||
:disabled="connection.ask"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.password') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="connection.password"
|
||||
class="form-input"
|
||||
type="password"
|
||||
:disabled="connection.ask"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12" />
|
||||
<div class="col-8 col-sm-12">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="connection.ask" type="checkbox"><i class="form-icon" /> {{ $t('message.askCredentials') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
<BaseToast
|
||||
class="mb-2"
|
||||
:message="toast.message"
|
||||
:status="toast.status"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="selectedTab === 'ssl'" class="panel-body py-0">
|
||||
<div class="container">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">
|
||||
{{ $t('message.enableSsl') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<label class="form-switch d-inline-block" @click.prevent="toggleSsl">
|
||||
<input type="checkbox" :checked="connection.ssl">
|
||||
<i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="connection.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.client') }}:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<select
|
||||
v-model="connection.client"
|
||||
class="form-select"
|
||||
@change="setDefaults"
|
||||
>
|
||||
<option value="mysql">
|
||||
MySQL
|
||||
</option>
|
||||
<option value="maria">
|
||||
MariaDB
|
||||
</option>
|
||||
<!-- <option value="mssql">
|
||||
Microsoft SQL
|
||||
</option>
|
||||
<option value="pg">
|
||||
PostgreSQL
|
||||
</option>
|
||||
<option value="oracledb">
|
||||
Oracle DB
|
||||
</option> -->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.hostName') }}/IP:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="connection.host"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.port') }}:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="connection.port"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.user') }}:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="connection.user"
|
||||
class="form-input"
|
||||
type="text"
|
||||
:disabled="connection.ask"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.password') }}:</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
v-model="connection.password"
|
||||
class="form-input"
|
||||
type="password"
|
||||
:disabled="connection.ask"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12" />
|
||||
<div class="col-8 col-sm-12">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="connection.ask" type="checkbox"><i class="form-icon" /> {{ $t('message.askCredentials') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
<fieldset class="m-0" :disabled="isTesting || !connection.ssl">
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.privateKey') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:value="connection.key"
|
||||
@clear="pathClear('key')"
|
||||
@change="pathSelection($event, 'key')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.certificate') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:value="connection.cert"
|
||||
@clear="pathClear('cert')"
|
||||
@change="pathSelection($event, 'cert')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.caCertificate') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:value="connection.ca"
|
||||
@clear="pathClear('ca')"
|
||||
@change="pathSelection($event, 'ca')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-4 col-sm-12">
|
||||
<label class="form-label">{{ $t('word.ciphers') }}</label>
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="connection.ciphers"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
<BaseToast
|
||||
class="mb-2"
|
||||
:message="toast.message"
|
||||
:status="toast.status"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<BaseToast
|
||||
class="mb-2"
|
||||
:message="toast.message"
|
||||
:status="toast.status"
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-footer text-light">
|
||||
<button
|
||||
@@ -154,12 +252,14 @@ import Connection from '@/ipc-api/Connection';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import ModalAskCredentials from '@/components/ModalAskCredentials';
|
||||
import BaseToast from '@/components/BaseToast';
|
||||
import BaseUploadInput from '@/components/BaseUploadInput';
|
||||
|
||||
export default {
|
||||
name: 'ModalNewConnection',
|
||||
components: {
|
||||
ModalAskCredentials,
|
||||
BaseToast
|
||||
BaseToast,
|
||||
BaseUploadInput
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -171,14 +271,21 @@ export default {
|
||||
user: 'root',
|
||||
password: '',
|
||||
ask: false,
|
||||
uid: uidGen('C')
|
||||
uid: uidGen('C'),
|
||||
ssl: false,
|
||||
cert: '',
|
||||
key: '',
|
||||
ca: '',
|
||||
ciphers: ''
|
||||
|
||||
},
|
||||
toast: {
|
||||
status: '',
|
||||
message: ''
|
||||
},
|
||||
isTesting: false,
|
||||
isAsking: false
|
||||
isAsking: false,
|
||||
selectedTab: 'general'
|
||||
};
|
||||
},
|
||||
created () {
|
||||
@@ -265,6 +372,21 @@ export default {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
},
|
||||
selectTab (tab) {
|
||||
this.selectedTab = tab;
|
||||
},
|
||||
toggleSsl () {
|
||||
this.connection.ssl = !this.connection.ssl;
|
||||
},
|
||||
pathSelection (event, name) {
|
||||
const { files } = event.target;
|
||||
if (!files.length) return;
|
||||
|
||||
this.connection[name] = files[0].path;
|
||||
},
|
||||
pathClear (name) {
|
||||
this.connection[name] = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -272,6 +394,8 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
.modal-container {
|
||||
position: absolute;
|
||||
max-width: 450px;
|
||||
top: 17.5vh;
|
||||
}
|
||||
</style>
|
||||
|
@@ -15,7 +15,7 @@
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-3">
|
||||
<label class="form-label">{{ $t('word.name') }}:</label>
|
||||
<label class="form-label">{{ $t('word.name') }}</label>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<input
|
||||
@@ -30,7 +30,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-3">
|
||||
<label class="form-label">{{ $t('word.collation') }}:</label>
|
||||
<label class="form-label">{{ $t('word.collation') }}</label>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<select v-model="database.collation" class="form-select">
|
||||
|
184
src/renderer/components/ModalNewFunction.vue
Normal file
184
src/renderer/components/ModalNewFunction.vue
Normal file
@@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.confirm')"
|
||||
size="400"
|
||||
@confirm="confirmNewFunction"
|
||||
@hide="$emit('close')"
|
||||
>
|
||||
<template :slot="'header'">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-plus mr-1" /> {{ $t('message.createNewRoutine') }}
|
||||
</div>
|
||||
</template>
|
||||
<div :slot="'body'">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.name') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="localFunction.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.definer') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select
|
||||
v-if="workspace.users.length"
|
||||
v-model="localFunction.definer"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="user in workspace.users"
|
||||
:key="`${user.name}@${user.host}`"
|
||||
:value="`\`${user.name}\`@\`${user.host}\``"
|
||||
>
|
||||
{{ user.name }}@{{ user.host }}
|
||||
</option>
|
||||
</select>
|
||||
<select v-if="!workspace.users.length" class="form-select">
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.returns') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<div class="input-group">
|
||||
<select
|
||||
v-model="localFunction.returns"
|
||||
class="form-select text-uppercase"
|
||||
style="width: 0;"
|
||||
>
|
||||
<optgroup
|
||||
v-for="group in workspace.dataTypes"
|
||||
:key="group.group"
|
||||
:label="group.group"
|
||||
>
|
||||
<option
|
||||
v-for="type in group.types"
|
||||
:key="type.name"
|
||||
:selected="localFunction.returns === type.name"
|
||||
:value="type.name"
|
||||
>
|
||||
{{ type.name }}
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<input
|
||||
v-model="localFunction.returnsLength"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="0"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.comment') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
v-model="localFunction.comment"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('message.sqlSecurity') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="localFunction.security" class="form-select">
|
||||
<option>DEFINER</option>
|
||||
<option>INVOKER</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('message.dataAccess') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="localFunction.dataAccess" class="form-select">
|
||||
<option>CONTAINS SQL</option>
|
||||
<option>NO SQL</option>
|
||||
<option>READS SQL DATA</option>
|
||||
<option>MODIFIES SQL DATA</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4" />
|
||||
<div class="column">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="localFunction.deterministic" type="checkbox"><i class="form-icon" /> {{ $t('word.deterministic') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'ModalNewFunction',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
workspace: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
localFunction: {
|
||||
definer: '',
|
||||
sql: 'BEGIN\r\n RETURN NULL;\r\nEND',
|
||||
parameters: [],
|
||||
name: '',
|
||||
comment: '',
|
||||
returns: 'INT',
|
||||
returnsLength: 10,
|
||||
security: 'DEFINER',
|
||||
deterministic: false,
|
||||
dataAccess: 'CONTAINS SQL'
|
||||
},
|
||||
isOptionsChanging: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
methods: {
|
||||
confirmNewFunction () {
|
||||
this.$emit('open-create-function-editor', this.localFunction);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
109
src/renderer/components/ModalNewScheduler.vue
Normal file
109
src/renderer/components/ModalNewScheduler.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.confirm')"
|
||||
size="400"
|
||||
@confirm="confirmNewTrigger"
|
||||
@hide="$emit('close')"
|
||||
>
|
||||
<template :slot="'header'">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-plus mr-1" /> {{ $t('message.createNewScheduler') }}
|
||||
</div>
|
||||
</template>
|
||||
<div :slot="'body'">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.name') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="localScheduler.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.definer') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select
|
||||
v-if="workspace.users.length"
|
||||
v-model="localScheduler.definer"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="user in workspace.users"
|
||||
:key="`${user.name}@${user.host}`"
|
||||
:value="`\`${user.name}\`@\`${user.host}\``"
|
||||
>
|
||||
{{ user.name }}@{{ user.host }}
|
||||
</option>
|
||||
</select>
|
||||
<select v-if="!workspace.users.length" class="form-select">
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.comment') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
v-model="localScheduler.comment"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'ModalNewScheduler',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
workspace: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
localScheduler: {
|
||||
definer: '',
|
||||
sql: 'BEGIN\r\n\r\nEND',
|
||||
name: '',
|
||||
comment: '',
|
||||
execution: 'EVERY',
|
||||
every: ['1', 'DAY'],
|
||||
preserve: true,
|
||||
state: 'DISABLE'
|
||||
}
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
methods: {
|
||||
confirmNewTrigger () {
|
||||
this.$emit('open-create-scheduler-editor', this.localScheduler);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -253,7 +253,7 @@ export default {
|
||||
},
|
||||
fieldLength (field) {
|
||||
if ([...BLOB, ...LONG_TEXT].includes(field.type)) return null;
|
||||
return field.numLength || field.datePrecision || field.charLength || 0;
|
||||
return field.length;
|
||||
},
|
||||
inputProps (field) {
|
||||
if ([...TEXT, ...LONG_TEXT].includes(field.type))
|
||||
|
@@ -56,7 +56,7 @@
|
||||
<div class="col-6 col-sm-12">
|
||||
<label class="form-label">
|
||||
<i class="mdi mdi-18px mdi-translate mr-1" />
|
||||
{{ $t('word.language') }}:
|
||||
{{ $t('word.language') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-6 col-sm-12">
|
||||
@@ -78,7 +78,7 @@
|
||||
<div class="form-group">
|
||||
<div class="col-6 col-sm-12">
|
||||
<label class="form-label">
|
||||
{{ $t('message.notificationsTimeout') }}:
|
||||
{{ $t('message.notificationsTimeout') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-6 col-sm-12">
|
||||
@@ -103,7 +103,7 @@
|
||||
<div class="form-group">
|
||||
<div class="col-6 col-sm-12">
|
||||
<label class="form-label">
|
||||
{{ $t('word.autoCompletion') }}:
|
||||
{{ $t('word.autoCompletion') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-6 col-sm-12">
|
||||
@@ -118,7 +118,7 @@
|
||||
<div class="form-group">
|
||||
<div class="col-6 col-sm-12">
|
||||
<label class="form-label">
|
||||
{{ $t('message.wrapLongLines') }}:
|
||||
{{ $t('message.wrapLongLines') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-6 col-sm-12">
|
||||
@@ -205,9 +205,9 @@
|
||||
<img :src="require('@/images/logo.svg').default" width="128">
|
||||
<h4>{{ appName }}</h4>
|
||||
<p>
|
||||
{{ $t('word.version') }}: {{ appVersion }}<br>
|
||||
<a class="c-hand" @click="openOutside('https://github.com/Fabio286/antares')">GitHub</a><br>
|
||||
<small>{{ $t('word.author') }}: <a class="c-hand" @click="openOutside('https://github.com/Fabio286')">Fabio Di Stasio</a></small><br>
|
||||
{{ $t('word.version') }} {{ appVersion }}<br>
|
||||
<a class="c-hand" @click="openOutside('https://github.com/Fabio286/antares')">GitHub</a> | <a class="c-hand" @click="openOutside('https://github.com/Fabio286/antares/blob/master/CHANGELOG.md')">CHANGELOG</a><br>
|
||||
<small>{{ $t('word.author') }} <a class="c-hand" @click="openOutside('https://github.com/Fabio286')">Fabio Di Stasio</a></small><br>
|
||||
<small>{{ $t('message.madeWithJS') }}</small>
|
||||
</p>
|
||||
</div>
|
||||
|
@@ -47,13 +47,68 @@ export default {
|
||||
}, []).map(table => {
|
||||
return {
|
||||
name: table.name,
|
||||
comment: table.comment,
|
||||
type: table.type,
|
||||
fields: []
|
||||
};
|
||||
})
|
||||
: [];
|
||||
},
|
||||
triggers () {
|
||||
return this.workspace
|
||||
? this.workspace.structure.filter(schema => schema.name === this.schema)
|
||||
.reduce((acc, curr) => {
|
||||
acc.push(...curr.triggers);
|
||||
return acc;
|
||||
}, []).map(trigger => {
|
||||
return {
|
||||
name: trigger.name,
|
||||
type: 'trigger'
|
||||
};
|
||||
})
|
||||
: [];
|
||||
},
|
||||
procedures () {
|
||||
return this.workspace
|
||||
? this.workspace.structure.filter(schema => schema.name === this.schema)
|
||||
.reduce((acc, curr) => {
|
||||
acc.push(...curr.procedures);
|
||||
return acc;
|
||||
}, []).map(procedure => {
|
||||
return {
|
||||
name: `${procedure.name}()`,
|
||||
type: 'routine'
|
||||
};
|
||||
})
|
||||
: [];
|
||||
},
|
||||
functions () {
|
||||
return this.workspace
|
||||
? this.workspace.structure.filter(schema => schema.name === this.schema)
|
||||
.reduce((acc, curr) => {
|
||||
acc.push(...curr.functions);
|
||||
return acc;
|
||||
}, []).map(func => {
|
||||
return {
|
||||
name: `${func.name}()`,
|
||||
type: 'function'
|
||||
};
|
||||
})
|
||||
: [];
|
||||
},
|
||||
schedulers () {
|
||||
return this.workspace
|
||||
? this.workspace.structure.filter(schema => schema.name === this.schema)
|
||||
.reduce((acc, curr) => {
|
||||
acc.push(...curr.schedulers);
|
||||
return acc;
|
||||
}, []).map(scheduler => {
|
||||
return {
|
||||
name: scheduler.name,
|
||||
type: 'scheduler'
|
||||
};
|
||||
})
|
||||
: [];
|
||||
},
|
||||
mode () {
|
||||
switch (this.workspace.client) {
|
||||
case 'mysql':
|
||||
@@ -130,11 +185,16 @@ export default {
|
||||
this.editor.completers.push({
|
||||
getCompletions: (editor, session, pos, prefix, callback) => {
|
||||
const completions = [];
|
||||
this.tables.forEach(table => {
|
||||
[
|
||||
...this.tables,
|
||||
...this.triggers,
|
||||
...this.procedures,
|
||||
...this.functions,
|
||||
...this.schedulers
|
||||
].forEach(el => {
|
||||
completions.push({
|
||||
value: table.name,
|
||||
meta: table.type,
|
||||
caption: table.comment
|
||||
value: el.name,
|
||||
meta: el.type
|
||||
});
|
||||
});
|
||||
callback(null, completions);
|
||||
|
@@ -2,9 +2,9 @@
|
||||
<div id="footer" class="text-light">
|
||||
<div class="footer-left-elements">
|
||||
<ul class="footer-elements">
|
||||
<li class="footer-element" :title="$t('word.version')">
|
||||
<i class="mdi mdi-18px mdi-memory mr-1" />
|
||||
<small>{{ appVersion }}</small>
|
||||
<li class="footer-element">
|
||||
<i class="mdi mdi-18px mdi-database mr-1" />
|
||||
<small>{{ versionString }}</small>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -34,9 +34,18 @@ export default {
|
||||
name: 'TheFooter',
|
||||
computed: {
|
||||
...mapGetters({
|
||||
appName: 'application/appName',
|
||||
workspace: 'workspaces/getSelected',
|
||||
getWorkspace: 'workspaces/getWorkspace',
|
||||
appVersion: 'application/appVersion'
|
||||
})
|
||||
}),
|
||||
version () {
|
||||
return this.getWorkspace(this.workspace) ? this.getWorkspace(this.workspace).version : null;
|
||||
},
|
||||
versionString () {
|
||||
if (this.version)
|
||||
return `${this.version.name} ${this.version.number} (${this.version.arch} ${this.version.os})`;
|
||||
return '';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
|
@@ -20,7 +20,7 @@
|
||||
>
|
||||
<a class="tab-link">
|
||||
<i class="mdi mdi-18px mdi-tune mr-1" />
|
||||
<span :title="schemaChild">{{ $t('word.properties').toUpperCase() }}: {{ schemaChild }}</span>
|
||||
<span :title="schemaChild">{{ $t('word.settings').toUpperCase() }}: {{ schemaChild }}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
@@ -89,6 +89,18 @@
|
||||
:connection="connection"
|
||||
:routine="workspace.breadcrumbs.procedure"
|
||||
/>
|
||||
<WorkspacePropsTabFunction
|
||||
v-show="selectedTab === 'prop' && workspace.breadcrumbs.function"
|
||||
:is-selected="selectedTab === 'prop'"
|
||||
:connection="connection"
|
||||
:function="workspace.breadcrumbs.function"
|
||||
/>
|
||||
<WorkspacePropsTabScheduler
|
||||
v-show="selectedTab === 'prop' && workspace.breadcrumbs.scheduler"
|
||||
:is-selected="selectedTab === 'prop'"
|
||||
:connection="connection"
|
||||
:scheduler="workspace.breadcrumbs.scheduler"
|
||||
/>
|
||||
<WorkspaceTableTab
|
||||
v-show="selectedTab === 'data'"
|
||||
:connection="connection"
|
||||
@@ -115,6 +127,8 @@ import WorkspacePropsTab from '@/components/WorkspacePropsTab';
|
||||
import WorkspacePropsTabView from '@/components/WorkspacePropsTabView';
|
||||
import WorkspacePropsTabTrigger from '@/components/WorkspacePropsTabTrigger';
|
||||
import WorkspacePropsTabRoutine from '@/components/WorkspacePropsTabRoutine';
|
||||
import WorkspacePropsTabFunction from '@/components/WorkspacePropsTabFunction';
|
||||
import WorkspacePropsTabScheduler from '@/components/WorkspacePropsTabScheduler';
|
||||
|
||||
export default {
|
||||
name: 'Workspace',
|
||||
@@ -125,7 +139,9 @@ export default {
|
||||
WorkspacePropsTab,
|
||||
WorkspacePropsTabView,
|
||||
WorkspacePropsTabTrigger,
|
||||
WorkspacePropsTabRoutine
|
||||
WorkspacePropsTabRoutine,
|
||||
WorkspacePropsTabFunction,
|
||||
WorkspacePropsTabScheduler
|
||||
},
|
||||
props: {
|
||||
connection: Object
|
||||
|
@@ -73,6 +73,18 @@
|
||||
@close="hideCreateRoutineModal"
|
||||
@open-create-routine-editor="openCreateRoutineEditor"
|
||||
/>
|
||||
<ModalNewFunction
|
||||
v-if="isNewFunctionModal"
|
||||
:workspace="workspace"
|
||||
@close="hideCreateFunctionModal"
|
||||
@open-create-function-editor="openCreateFunctionEditor"
|
||||
/>
|
||||
<ModalNewScheduler
|
||||
v-if="isNewSchedulerModal"
|
||||
:workspace="workspace"
|
||||
@close="hideCreateSchedulerModal"
|
||||
@open-create-scheduler-editor="openCreateSchedulerEditor"
|
||||
/>
|
||||
<DatabaseContext
|
||||
v-if="isDatabaseContext"
|
||||
:selected-database="selectedDatabase"
|
||||
@@ -82,6 +94,8 @@
|
||||
@show-create-view-modal="showCreateViewModal"
|
||||
@show-create-trigger-modal="showCreateTriggerModal"
|
||||
@show-create-routine-modal="showCreateRoutineModal"
|
||||
@show-create-function-modal="showCreateFunctionModal"
|
||||
@show-create-scheduler-modal="showCreateSchedulerModal"
|
||||
@reload="refresh"
|
||||
/>
|
||||
<TableContext
|
||||
@@ -109,6 +123,8 @@ import Tables from '@/ipc-api/Tables';
|
||||
import Views from '@/ipc-api/Views';
|
||||
import Triggers from '@/ipc-api/Triggers';
|
||||
import Routines from '@/ipc-api/Routines';
|
||||
import Functions from '@/ipc-api/Functions';
|
||||
import Schedulers from '@/ipc-api/Schedulers';
|
||||
|
||||
import WorkspaceConnectPanel from '@/components/WorkspaceConnectPanel';
|
||||
import WorkspaceExploreBarDatabase from '@/components/WorkspaceExploreBarDatabase';
|
||||
@@ -120,6 +136,8 @@ import ModalNewTable from '@/components/ModalNewTable';
|
||||
import ModalNewView from '@/components/ModalNewView';
|
||||
import ModalNewTrigger from '@/components/ModalNewTrigger';
|
||||
import ModalNewRoutine from '@/components/ModalNewRoutine';
|
||||
import ModalNewFunction from '@/components/ModalNewFunction';
|
||||
import ModalNewScheduler from '@/components/ModalNewScheduler';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceExploreBar',
|
||||
@@ -133,7 +151,9 @@ export default {
|
||||
ModalNewTable,
|
||||
ModalNewView,
|
||||
ModalNewTrigger,
|
||||
ModalNewRoutine
|
||||
ModalNewRoutine,
|
||||
ModalNewFunction,
|
||||
ModalNewScheduler
|
||||
},
|
||||
props: {
|
||||
connection: Object,
|
||||
@@ -148,6 +168,8 @@ export default {
|
||||
isNewViewModal: false,
|
||||
isNewTriggerModal: false,
|
||||
isNewRoutineModal: false,
|
||||
isNewFunctionModal: false,
|
||||
isNewSchedulerModal: false,
|
||||
|
||||
localWidth: null,
|
||||
isDatabaseContext: false,
|
||||
@@ -190,7 +212,7 @@ export default {
|
||||
mounted () {
|
||||
const resizer = this.$refs.resizer;
|
||||
|
||||
resizer.addEventListener('mousedown', (e) => {
|
||||
resizer.addEventListener('mousedown', e => {
|
||||
e.preventDefault();
|
||||
|
||||
window.addEventListener('mousemove', this.resize);
|
||||
@@ -344,6 +366,52 @@ export default {
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
},
|
||||
showCreateFunctionModal () {
|
||||
this.closeDatabaseContext();
|
||||
this.isNewFunctionModal = true;
|
||||
},
|
||||
hideCreateFunctionModal () {
|
||||
this.isNewFunctionModal = false;
|
||||
},
|
||||
showCreateSchedulerModal () {
|
||||
this.closeDatabaseContext();
|
||||
this.isNewSchedulerModal = true;
|
||||
},
|
||||
hideCreateSchedulerModal () {
|
||||
this.isNewSchedulerModal = false;
|
||||
},
|
||||
async openCreateFunctionEditor (payload) {
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
...payload
|
||||
};
|
||||
|
||||
const { status, response } = await Functions.createFunction(params);
|
||||
|
||||
if (status === 'success') {
|
||||
await this.refresh();
|
||||
this.changeBreadcrumbs({ schema: this.selectedDatabase, function: payload.name });
|
||||
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
},
|
||||
async openCreateSchedulerEditor (payload) {
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
...payload
|
||||
};
|
||||
|
||||
const { status, response } = await Schedulers.createScheduler(params);
|
||||
|
||||
if (status === 'success') {
|
||||
await this.refresh();
|
||||
this.changeBreadcrumbs({ schema: this.selectedDatabase, scheduler: payload.name });
|
||||
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -3,10 +3,11 @@
|
||||
<summary
|
||||
class="accordion-header database-name"
|
||||
:class="{'text-bold': breadcrumbs.schema === database.name}"
|
||||
@click="changeBreadcrumbs({schema: database.name, table: null})"
|
||||
@click="selectSchema(database.name)"
|
||||
@contextmenu.prevent="showDatabaseContext($event, database.name)"
|
||||
>
|
||||
<i class="icon mdi mdi-18px mdi-chevron-right" />
|
||||
<div v-if="isLoading" class="icon loading" />
|
||||
<i v-else class="icon mdi mdi-18px mdi-chevron-right" />
|
||||
<i class="database-icon mdi mdi-18px mdi-database mr-1" />
|
||||
<span>{{ database.name }}</span>
|
||||
</summary>
|
||||
@@ -161,13 +162,22 @@ export default {
|
||||
database: Object,
|
||||
connection: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isLoading: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
getLoadedSchemas: 'workspaces/getLoadedSchemas',
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
breadcrumbs () {
|
||||
return this.getWorkspace(this.connection.uid).breadcrumbs;
|
||||
},
|
||||
loadedSchemas () {
|
||||
return this.getLoadedSchemas(this.connection.uid);
|
||||
},
|
||||
maxSize () {
|
||||
return this.database.tables.reduce((acc, curr) => {
|
||||
if (curr.size > acc) acc = curr.size;
|
||||
@@ -180,9 +190,19 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
|
||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
|
||||
refreshSchema: 'workspaces/refreshSchema'
|
||||
}),
|
||||
formatBytes,
|
||||
async selectSchema (schema) {
|
||||
if (!this.loadedSchemas.has(schema)) {
|
||||
this.isLoading = true;
|
||||
await this.refreshSchema({ uid: this.connection.uid, schema });
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
||||
this.changeBreadcrumbs({ schema, table: null });
|
||||
},
|
||||
showDatabaseContext (event, database) {
|
||||
this.changeBreadcrumbs({ schema: database, table: null });
|
||||
this.$emit('show-database-context', { event, database });
|
||||
@@ -230,6 +250,16 @@ export default {
|
||||
.misc-icon {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.loading {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
|
||||
&::after {
|
||||
height: 0.6rem;
|
||||
width: 0.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.misc-name {
|
||||
|
@@ -19,10 +19,10 @@
|
||||
<div class="context-element" @click="showCreateRoutineModal">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-sync-circle pr-1" /> {{ $tc('word.storedRoutine', 1) }}</span>
|
||||
</div>
|
||||
<div class="context-element disabled" @click="false">
|
||||
<div class="context-element" @click="showCreateFunctionModal">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-arrow-right-bold-box pr-1" /> {{ $tc('word.function', 1) }}</span>
|
||||
</div>
|
||||
<div class="context-element disabled" @click="false">
|
||||
<div class="context-element" @click="showCreateSchedulerModal">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-calendar-clock text-light pr-1" /> {{ $tc('word.scheduler', 1) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -108,6 +108,12 @@ export default {
|
||||
showCreateRoutineModal () {
|
||||
this.$emit('show-create-routine-modal');
|
||||
},
|
||||
showCreateFunctionModal () {
|
||||
this.$emit('show-create-function-modal');
|
||||
},
|
||||
showCreateSchedulerModal () {
|
||||
this.$emit('show-create-scheduler-modal');
|
||||
},
|
||||
showDeleteModal () {
|
||||
this.isDeleteModal = true;
|
||||
},
|
||||
|
@@ -4,7 +4,7 @@
|
||||
@close-context="closeContext"
|
||||
>
|
||||
<div
|
||||
v-if="selectedMisc.type === 'procedure'"
|
||||
v-if="['procedure', 'function'].includes(selectedMisc.type)"
|
||||
class="context-element disabled"
|
||||
@click="showRunModal"
|
||||
>
|
||||
@@ -38,6 +38,8 @@ import BaseContextMenu from '@/components/BaseContextMenu';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
import Triggers from '@/ipc-api/Triggers';
|
||||
import Routines from '@/ipc-api/Routines';
|
||||
import Functions from '@/ipc-api/Functions';
|
||||
import Schedulers from '@/ipc-api/Schedulers';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceExploreBarMiscContext',
|
||||
@@ -69,6 +71,10 @@ export default {
|
||||
return this.$t('message.deleteTrigger');
|
||||
case 'procedure':
|
||||
return this.$t('message.deleteRoutine');
|
||||
case 'function':
|
||||
return this.$t('message.deleteFunction');
|
||||
case 'scheduler':
|
||||
return this.$t('message.deleteScheduler');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
@@ -114,12 +120,18 @@ export default {
|
||||
routine: this.selectedMisc.name
|
||||
});
|
||||
break;
|
||||
// case 'schedules':
|
||||
// res = await Tables.dropScheduler({
|
||||
// uid: this.selectedWorkspace,
|
||||
// scheduler: this.selectedMisc.name
|
||||
// });
|
||||
// break;
|
||||
case 'function':
|
||||
res = await Functions.dropFunction({
|
||||
uid: this.selectedWorkspace,
|
||||
func: this.selectedMisc.name
|
||||
});
|
||||
break;
|
||||
case 'scheduler':
|
||||
res = await Schedulers.dropScheduler({
|
||||
uid: this.selectedWorkspace,
|
||||
scheduler: this.selectedMisc.name
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
const { status, response } = res;
|
||||
|
180
src/renderer/components/WorkspacePropsFunctionOptionsModal.vue
Normal file
180
src/renderer/components/WorkspacePropsFunctionOptionsModal.vue
Normal file
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.confirm')"
|
||||
size="400"
|
||||
@confirm="confirmOptionsChange"
|
||||
@hide="$emit('hide')"
|
||||
>
|
||||
<template :slot="'header'">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-cogs mr-1" /> {{ $t('word.options') }} "{{ localOptions.name }}"
|
||||
</div>
|
||||
</template>
|
||||
<div :slot="'body'">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.name') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="optionsProxy.name"
|
||||
class="form-input"
|
||||
:class="{'is-error': !isTableNameValid}"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.definer') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select
|
||||
v-if="workspace.users.length"
|
||||
v-model="optionsProxy.definer"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="user in workspace.users"
|
||||
:key="`${user.name}@${user.host}`"
|
||||
:value="`\`${user.name}\`@\`${user.host}\``"
|
||||
>
|
||||
{{ user.name }}@{{ user.host }}
|
||||
</option>
|
||||
</select>
|
||||
<select v-if="!workspace.users.length" class="form-select">
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.returns') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<div class="input-group">
|
||||
<select
|
||||
v-model="optionsProxy.returns"
|
||||
class="form-select text-uppercase"
|
||||
style="width: 0;"
|
||||
>
|
||||
<optgroup
|
||||
v-for="group in workspace.dataTypes"
|
||||
:key="group.group"
|
||||
:label="group.group"
|
||||
>
|
||||
<option
|
||||
v-for="type in group.types"
|
||||
:key="type.name"
|
||||
:selected="optionsProxy.returns === type.name"
|
||||
:value="type.name"
|
||||
>
|
||||
{{ type.name }}
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<input
|
||||
v-model="optionsProxy.returnsLength"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="0"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.comment') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
v-model="optionsProxy.comment"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('message.sqlSecurity') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="optionsProxy.security" class="form-select">
|
||||
<option>DEFINER</option>
|
||||
<option>INVOKER</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('message.dataAccess') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="optionsProxy.dataAccess" class="form-select">
|
||||
<option>CONTAINS SQL</option>
|
||||
<option>NO SQL</option>
|
||||
<option>READS SQL DATA</option>
|
||||
<option>MODIFIES SQL DATA</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4" />
|
||||
<div class="column">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="optionsProxy.deterministic" type="checkbox"><i class="form-icon" /> {{ $t('word.deterministic') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsFunctionOptionsModal',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
localOptions: Object,
|
||||
workspace: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
optionsProxy: {},
|
||||
isOptionsChanging: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isTableNameValid () {
|
||||
return this.optionsProxy.name !== '';
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.optionsProxy = JSON.parse(JSON.stringify(this.localOptions));
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
methods: {
|
||||
confirmOptionsChange () {
|
||||
if (!this.isTableNameValid)
|
||||
this.optionsProxy.name = this.localOptions.name;
|
||||
|
||||
this.$emit('options-update', this.optionsProxy);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
270
src/renderer/components/WorkspacePropsFunctionParamsModal.vue
Normal file
270
src/renderer/components/WorkspacePropsFunctionParamsModal.vue
Normal file
@@ -0,0 +1,270 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.confirm')"
|
||||
size="medium"
|
||||
@confirm="confirmIndexesChange"
|
||||
@hide="$emit('hide')"
|
||||
>
|
||||
<template :slot="'header'">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" /> {{ $t('word.parameters') }} "{{ func }}"
|
||||
</div>
|
||||
</template>
|
||||
<div :slot="'body'">
|
||||
<div class="columns col-gapless">
|
||||
<div class="column col-5">
|
||||
<div class="panel" :style="{ height: modalInnerHeight + 'px'}">
|
||||
<div class="panel-header pt-0 pl-0">
|
||||
<div class="d-flex">
|
||||
<button class="btn btn-dark btn-sm d-flex" @click="addParameter">
|
||||
<span>{{ $t('word.add') }}</span>
|
||||
<i class="mdi mdi-24px mdi-plus ml-1" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark btn-sm d-flex ml-2 mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
:disabled="!isChanged"
|
||||
@click.prevent="clearChanges"
|
||||
>
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="parametersPanel" class="panel-body p-0 pr-1">
|
||||
<div
|
||||
v-for="param in parametersProxy"
|
||||
:key="param.name"
|
||||
class="tile tile-centered c-hand mb-1 p-1"
|
||||
:class="{'selected-param': selectedParam === param.name}"
|
||||
@click="selectParameter($event, param.name)"
|
||||
>
|
||||
<div class="tile-icon">
|
||||
<div>
|
||||
<i class="mdi mdi-hexagon mdi-24px" :class="`type-${param.type.toLowerCase()}`" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile-content">
|
||||
<div class="tile-title">
|
||||
{{ param.name }}
|
||||
</div>
|
||||
<small class="tile-subtitle text-gray">{{ param.type }}{{ param.length ? `(${param.length})` : '' }}</small>
|
||||
</div>
|
||||
<div class="tile-action">
|
||||
<button
|
||||
class="btn btn-link remove-field p-0 mr-2"
|
||||
:title="$t('word.delete')"
|
||||
@click.prevent="removeParameter(param.name)"
|
||||
>
|
||||
<i class="mdi mdi-close" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column col-7 pl-2 editor-col">
|
||||
<form
|
||||
v-if="selectedParamObj"
|
||||
:style="{ height: modalInnerHeight + 'px'}"
|
||||
class="form-horizontal"
|
||||
>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.name') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
v-model="selectedParamObj.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.type') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="selectedParamObj.type" class="form-select text-uppercase">
|
||||
<optgroup
|
||||
v-for="group in workspace.dataTypes"
|
||||
:key="group.group"
|
||||
:label="group.group"
|
||||
>
|
||||
<option
|
||||
v-for="type in group.types"
|
||||
:key="type.name"
|
||||
:selected="selectedParamObj.type.toUpperCase() === type.name"
|
||||
:value="type.name"
|
||||
>
|
||||
{{ type.name }}
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.length') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
v-model="selectedParamObj.length"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="0"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div v-if="!parametersProxy.length" class="empty">
|
||||
<div class="empty-icon">
|
||||
<i class="mdi mdi-dots-horizontal mdi-48px" />
|
||||
</div>
|
||||
<p class="empty-title h5">
|
||||
{{ $t('message.thereAreNoParameters') }}
|
||||
</p>
|
||||
<div class="empty-action">
|
||||
<button class="btn btn-primary" @click="addParameter">
|
||||
{{ $t('message.createNewParameter') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsRoutineParamsModal',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
localParameters: Array,
|
||||
func: String,
|
||||
workspace: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
parametersProxy: [],
|
||||
isOptionsChanging: false,
|
||||
selectedParam: '',
|
||||
modalInnerHeight: 400,
|
||||
i: 1
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selectedParamObj () {
|
||||
return this.parametersProxy.find(param => param.name === this.selectedParam);
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.localParameters) !== JSON.stringify(this.parametersProxy);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.parametersProxy = JSON.parse(JSON.stringify(this.localParameters));
|
||||
this.i = this.parametersProxy.length + 1;
|
||||
|
||||
if (this.parametersProxy.length)
|
||||
this.resetSelectedID();
|
||||
|
||||
this.getModalInnerHeight();
|
||||
window.addEventListener('resize', this.getModalInnerHeight);
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('resize', this.getModalInnerHeight);
|
||||
},
|
||||
methods: {
|
||||
confirmIndexesChange () {
|
||||
this.$emit('parameters-update', this.parametersProxy);
|
||||
},
|
||||
selectParameter (event, name) {
|
||||
if (this.selectedParam !== name && !event.target.classList.contains('remove-field'))
|
||||
this.selectedParam = name;
|
||||
},
|
||||
getModalInnerHeight () {
|
||||
const modalBody = document.querySelector('.modal-body');
|
||||
if (modalBody)
|
||||
this.modalInnerHeight = modalBody.clientHeight - (parseFloat(getComputedStyle(modalBody).paddingTop) + parseFloat(getComputedStyle(modalBody).paddingBottom));
|
||||
},
|
||||
addParameter () {
|
||||
this.parametersProxy = [...this.parametersProxy, {
|
||||
name: `Param${this.i++}`,
|
||||
type: 'INT',
|
||||
context: 'IN',
|
||||
length: 10
|
||||
}];
|
||||
|
||||
if (this.parametersProxy.length === 1)
|
||||
this.resetSelectedID();
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.parametersPanel.scrollTop = this.$refs.parametersPanel.scrollHeight + 60;
|
||||
}, 20);
|
||||
},
|
||||
removeParameter (name) {
|
||||
this.parametersProxy = this.parametersProxy.filter(param => param.name !== name);
|
||||
|
||||
if (this.selectedParam === name && this.parametersProxy.length)
|
||||
this.resetSelectedID();
|
||||
},
|
||||
clearChanges () {
|
||||
this.parametersProxy = JSON.parse(JSON.stringify(this.localParameters));
|
||||
this.i = this.parametersProxy.length + 1;
|
||||
|
||||
if (!this.parametersProxy.some(param => param.name === this.selectedParam))
|
||||
this.resetSelectedID();
|
||||
},
|
||||
resetSelectedID () {
|
||||
this.selectedParam = this.parametersProxy.length ? this.parametersProxy[0].name : '';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tile {
|
||||
border-radius: 2px;
|
||||
opacity: 0.5;
|
||||
transition: background 0.2s;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
.tile-action {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $bg-color-light;
|
||||
|
||||
.tile-action {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected-param {
|
||||
background: $bg-color-light;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-col {
|
||||
border-left: 2px solid $bg-color-light;
|
||||
}
|
||||
|
||||
.fields-list {
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.remove-field .mdi {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
@@ -114,6 +114,7 @@
|
||||
v-model="selectedParamObj.length"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="0"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
192
src/renderer/components/WorkspacePropsSchedulerTimingModal.vue
Normal file
192
src/renderer/components/WorkspacePropsSchedulerTimingModal.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.confirm')"
|
||||
size="400"
|
||||
@confirm="confirmOptionsChange"
|
||||
@hide="$emit('hide')"
|
||||
>
|
||||
<template :slot="'header'">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-timer mr-1" /> {{ $t('word.timing') }} "{{ localOptions.name }}"
|
||||
</div>
|
||||
</template>
|
||||
<div :slot="'body'">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.execution') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select
|
||||
ref="firstInput"
|
||||
v-model="optionsProxy.execution"
|
||||
class="form-select"
|
||||
>
|
||||
<option>EVERY</option>
|
||||
<option>ONCE</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="optionsProxy.execution === 'EVERY'">
|
||||
<div class="form-group">
|
||||
<div class="col-4" />
|
||||
<div class="column">
|
||||
<div class="input-group">
|
||||
<input
|
||||
v-model="optionsProxy.every[0]"
|
||||
class="form-input"
|
||||
type="text"
|
||||
@keypress="isNumberOrMinus($event)"
|
||||
>
|
||||
<select
|
||||
v-model="optionsProxy.every[1]"
|
||||
class="form-select text-uppercase"
|
||||
style="width: 0;"
|
||||
>
|
||||
<option>YEAR</option>
|
||||
<option>QUARTER</option>
|
||||
<option>MONTH</option>
|
||||
<option>WEEK</option>
|
||||
<option>DAY</option>
|
||||
<option>HOUR</option>
|
||||
<option>MINUTE</option>
|
||||
<option>SECOND</option>
|
||||
<option>YEAR_MONTH</option>
|
||||
<option>DAY_HOUR</option>
|
||||
<option>DAY_MINUTE</option>
|
||||
<option>DAY_SECOND</option>
|
||||
<option>HOUR_MINUTE</option>
|
||||
<option>HOUR_SECOND</option>
|
||||
<option>MINUTE_SECOND</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.starts') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<div class="input-group">
|
||||
<label class="form-checkbox">
|
||||
<input v-model="hasStart" type="checkbox"><i class="form-icon" />
|
||||
</label>
|
||||
<input
|
||||
v-model="optionsProxy.starts"
|
||||
v-mask="'####-##-## ##:##:##'"
|
||||
type="text"
|
||||
class="form-input"
|
||||
>
|
||||
<span class="input-group-addon p-vcentered">
|
||||
<i class="form-icon mdi mdi-calendar" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.ends') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<div class="input-group">
|
||||
<label class="form-checkbox">
|
||||
<input v-model="hasEnd" type="checkbox"><i class="form-icon" />
|
||||
</label>
|
||||
<input
|
||||
v-model="optionsProxy.ends"
|
||||
v-mask="'####-##-## ##:##:##'"
|
||||
type="text"
|
||||
class="form-input"
|
||||
>
|
||||
<span class="input-group-addon p-vcentered">
|
||||
<i class="form-icon mdi mdi-calendar" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="form-group">
|
||||
<div class="col-4" />
|
||||
<div class="column">
|
||||
<div class="input-group">
|
||||
<input
|
||||
v-model="optionsProxy.at"
|
||||
v-mask="'####-##-## ##:##:##'"
|
||||
type="text"
|
||||
class="form-input"
|
||||
>
|
||||
<span class="input-group-addon p-vcentered">
|
||||
<i class="form-icon mdi mdi-calendar" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-4" />
|
||||
<div class="column">
|
||||
<label class="form-checkbox form-inline mt-2">
|
||||
<input v-model="optionsProxy.preserve" type="checkbox"><i class="form-icon" /> {{ $t('message.preserveOnCompletion') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
import { mask } from 'vue-the-mask';
|
||||
import moment from 'moment';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsSchedulerTimingModal',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
directives: {
|
||||
mask
|
||||
},
|
||||
props: {
|
||||
localOptions: Object,
|
||||
workspace: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
optionsProxy: {},
|
||||
isOptionsChanging: false,
|
||||
hasStart: false,
|
||||
hasEnd: false
|
||||
};
|
||||
},
|
||||
created () {
|
||||
this.optionsProxy = JSON.parse(JSON.stringify(this.localOptions));
|
||||
|
||||
this.hasStart = !!this.optionsProxy.starts;
|
||||
this.hasEnd = !!this.optionsProxy.ends;
|
||||
|
||||
if (!this.optionsProxy.at) this.optionsProxy.at = moment().format('YYYY-MM-DD HH:mm:ss');
|
||||
if (!this.optionsProxy.starts) this.optionsProxy.starts = moment().format('YYYY-MM-DD HH:mm:ss');
|
||||
if (!this.optionsProxy.ends) this.optionsProxy.ends = moment().format('YYYY-MM-DD HH:mm:ss');
|
||||
if (!this.optionsProxy.every.length) this.optionsProxy.every = ['1', 'DAY'];
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
methods: {
|
||||
confirmOptionsChange () {
|
||||
if (!this.hasStart) this.optionsProxy.starts = '';
|
||||
if (!this.hasEnd) this.optionsProxy.ends = '';
|
||||
|
||||
this.$emit('options-update', this.optionsProxy);
|
||||
},
|
||||
isNumberOrMinus (event) {
|
||||
if (!/\d/.test(event.key) && event.key !== '-')
|
||||
return event.preventDefault();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -51,7 +51,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workspace-query-results column col-12">
|
||||
<div class="workspace-query-results column col-12 p-relative">
|
||||
<BaseLoader v-if="isLoading" />
|
||||
<WorkspacePropsTable
|
||||
v-if="localFields"
|
||||
ref="indexTable"
|
||||
@@ -106,6 +107,7 @@
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import WorkspacePropsTable from '@/components/WorkspacePropsTable';
|
||||
import WorkspacePropsOptionsModal from '@/components/WorkspacePropsOptionsModal';
|
||||
import WorkspacePropsIndexesModal from '@/components/WorkspacePropsIndexesModal';
|
||||
@@ -114,6 +116,7 @@ import WorkspacePropsForeignModal from '@/components/WorkspacePropsForeignModal'
|
||||
export default {
|
||||
name: 'WorkspacePropsTab',
|
||||
components: {
|
||||
BaseLoader,
|
||||
WorkspacePropsTable,
|
||||
WorkspacePropsOptionsModal,
|
||||
WorkspacePropsIndexesModal,
|
||||
@@ -126,7 +129,7 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
tabUid: 'prop',
|
||||
isQuering: false,
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
isOptionsModal: false,
|
||||
isIndexesModal: false,
|
||||
@@ -205,8 +208,10 @@ export default {
|
||||
}),
|
||||
async getFieldsData () {
|
||||
if (!this.table) return;
|
||||
|
||||
this.localFields = [];
|
||||
this.newFieldsCounter = 0;
|
||||
this.isQuering = true;
|
||||
this.isLoading = true;
|
||||
this.localOptions = JSON.parse(JSON.stringify(this.tableOptions));
|
||||
|
||||
const params = {
|
||||
@@ -281,7 +286,7 @@ export default {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isQuering = false;
|
||||
this.isLoading = false;
|
||||
},
|
||||
async saveChanges () {
|
||||
if (this.isSaving) return;
|
||||
|
267
src/renderer/components/WorkspacePropsTabFunction.vue
Normal file
267
src/renderer/components/WorkspacePropsTabFunction.vue
Normal file
@@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<div class="workspace-query-tab column col-12 columns col-gapless">
|
||||
<div class="workspace-query-runner column col-12">
|
||||
<div class="workspace-query-runner-footer">
|
||||
<div class="workspace-query-buttons">
|
||||
<button
|
||||
class="btn btn-primary btn-sm"
|
||||
:disabled="!isChanged"
|
||||
:class="{'loading':isSaving}"
|
||||
@click="saveChanges"
|
||||
>
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
<i class="mdi mdi-24px mdi-content-save ml-1" />
|
||||
</button>
|
||||
<button
|
||||
:disabled="!isChanged"
|
||||
class="btn btn-link btn-sm mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
@click="clearChanges"
|
||||
>
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
|
||||
</button>
|
||||
|
||||
<div class="divider-vert py-3" />
|
||||
|
||||
<button
|
||||
class="btn btn-dark btn-sm disabled"
|
||||
:disabled="isChanged"
|
||||
@click="false"
|
||||
>
|
||||
<span>{{ $t('word.run') }}</span>
|
||||
<i class="mdi mdi-24px mdi-play ml-1" />
|
||||
</button>
|
||||
<button class="btn btn-dark btn-sm" @click="showParamsModal">
|
||||
<span>{{ $t('word.parameters') }}</span>
|
||||
<i class="mdi mdi-24px mdi-dots-horizontal ml-1" />
|
||||
</button>
|
||||
<button class="btn btn-dark btn-sm" @click="showOptionsModal">
|
||||
<span>{{ $t('word.options') }}</span>
|
||||
<i class="mdi mdi-24px mdi-cogs ml-1" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||
<BaseLoader v-if="isLoading" />
|
||||
<label class="form-label ml-2">{{ $t('message.functionBody') }}</label>
|
||||
<QueryEditor
|
||||
v-if="isSelected"
|
||||
ref="queryEditor"
|
||||
:value.sync="localFunction.sql"
|
||||
:workspace="workspace"
|
||||
:schema="schema"
|
||||
:height="editorHeight"
|
||||
/>
|
||||
</div>
|
||||
<WorkspacePropsFunctionOptionsModal
|
||||
v-if="isOptionsModal"
|
||||
:local-options="localFunction"
|
||||
:workspace="workspace"
|
||||
@hide="hideOptionsModal"
|
||||
@options-update="optionsUpdate"
|
||||
/>
|
||||
<WorkspacePropsFunctionParamsModal
|
||||
v-if="isParamsModal"
|
||||
:local-parameters="localFunction.parameters"
|
||||
:workspace="workspace"
|
||||
:func="localFunction.name"
|
||||
@hide="hideParamsModal"
|
||||
@parameters-update="parametersUpdate"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import WorkspacePropsFunctionOptionsModal from '@/components/WorkspacePropsFunctionOptionsModal';
|
||||
import WorkspacePropsFunctionParamsModal from '@/components/WorkspacePropsFunctionParamsModal';
|
||||
import Functions from '@/ipc-api/Functions';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsTabFunction',
|
||||
components: {
|
||||
BaseLoader,
|
||||
QueryEditor,
|
||||
WorkspacePropsFunctionOptionsModal,
|
||||
WorkspacePropsFunctionParamsModal
|
||||
},
|
||||
props: {
|
||||
connection: Object,
|
||||
function: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
tabUid: 'prop',
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
isOptionsModal: false,
|
||||
isParamsModal: false,
|
||||
originalFunction: null,
|
||||
localFunction: { sql: '' },
|
||||
lastFunction: null,
|
||||
sqlProxy: '',
|
||||
editorHeight: 300
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
isSelected () {
|
||||
return this.workspace.selected_tab === 'prop';
|
||||
},
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.originalFunction) !== JSON.stringify(this.localFunction);
|
||||
},
|
||||
isDefinerInUsers () {
|
||||
return this.originalFunction ? this.workspace.users.some(user => this.originalFunction.definer === `\`${user.name}\`@\`${user.host}\``) : true;
|
||||
},
|
||||
schemaTables () {
|
||||
const schemaTables = this.workspace.structure
|
||||
.filter(schema => schema.name === this.schema)
|
||||
.map(schema => schema.tables);
|
||||
|
||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
async function () {
|
||||
if (this.isSelected) {
|
||||
await this.getFunctionData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
|
||||
this.lastFunction = this.function;
|
||||
}
|
||||
},
|
||||
async isSelected (val) {
|
||||
if (val && this.lastFunction !== this.function) {
|
||||
await this.getFunctionData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
|
||||
this.lastFunction = this.function;
|
||||
}
|
||||
},
|
||||
isChanged (val) {
|
||||
if (this.isSelected && this.lastFunction === this.function && this.function !== null)
|
||||
this.setUnsavedChanges(val);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
window.addEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
refreshStructure: 'workspaces/refreshStructure',
|
||||
setUnsavedChanges: 'workspaces/setUnsavedChanges',
|
||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
|
||||
}),
|
||||
async getFunctionData () {
|
||||
if (!this.function) return;
|
||||
|
||||
this.isLoading = true;
|
||||
this.localFunction = { sql: '' };
|
||||
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
func: this.workspace.breadcrumbs.function
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Functions.getFunctionInformations(params);
|
||||
if (status === 'success') {
|
||||
this.originalFunction = response;
|
||||
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction));
|
||||
this.sqlProxy = this.localFunction.sql;
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.resizeQueryEditor();
|
||||
this.isLoading = false;
|
||||
},
|
||||
async saveChanges () {
|
||||
if (this.isSaving) return;
|
||||
this.isSaving = true;
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
func: {
|
||||
...this.localFunction,
|
||||
oldName: this.originalFunction.name
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Functions.alterFunction(params);
|
||||
|
||||
if (status === 'success') {
|
||||
const oldName = this.originalFunction.name;
|
||||
|
||||
await this.refreshStructure(this.connection.uid);
|
||||
|
||||
if (oldName !== this.localFunction.name) {
|
||||
this.setUnsavedChanges(false);
|
||||
this.changeBreadcrumbs({ schema: this.schema, function: this.localFunction.name });
|
||||
}
|
||||
|
||||
this.getFunctionData();
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isSaving = false;
|
||||
},
|
||||
clearChanges () {
|
||||
this.localFunction = JSON.parse(JSON.stringify(this.originalFunction));
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localFunction.sql);
|
||||
},
|
||||
resizeQueryEditor () {
|
||||
if (this.$refs.queryEditor) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
this.editorHeight = size;
|
||||
this.$refs.queryEditor.editor.resize();
|
||||
}
|
||||
},
|
||||
optionsUpdate (options) {
|
||||
this.localFunction = options;
|
||||
},
|
||||
parametersUpdate (parameters) {
|
||||
this.localFunction = { ...this.localFunction, parameters };
|
||||
},
|
||||
showOptionsModal () {
|
||||
this.isOptionsModal = true;
|
||||
},
|
||||
hideOptionsModal () {
|
||||
this.isOptionsModal = false;
|
||||
},
|
||||
showParamsModal () {
|
||||
this.isParamsModal = true;
|
||||
},
|
||||
hideParamsModal () {
|
||||
this.isParamsModal = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -43,7 +43,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workspace-query-results column col-12 mt-2">
|
||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||
<BaseLoader v-if="isLoading" />
|
||||
<label class="form-label ml-2">{{ $t('message.routineBody') }}</label>
|
||||
<QueryEditor
|
||||
v-if="isSelected"
|
||||
@@ -75,6 +76,7 @@
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import WorkspacePropsRoutineOptionsModal from '@/components/WorkspacePropsRoutineOptionsModal';
|
||||
import WorkspacePropsRoutineParamsModal from '@/components/WorkspacePropsRoutineParamsModal';
|
||||
import Routines from '@/ipc-api/Routines';
|
||||
@@ -83,6 +85,7 @@ export default {
|
||||
name: 'WorkspacePropsTabRoutine',
|
||||
components: {
|
||||
QueryEditor,
|
||||
BaseLoader,
|
||||
WorkspacePropsRoutineOptionsModal,
|
||||
WorkspacePropsRoutineParamsModal
|
||||
},
|
||||
@@ -93,7 +96,7 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
tabUid: 'prop',
|
||||
isQuering: false,
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
isOptionsModal: false,
|
||||
isParamsModal: false,
|
||||
@@ -166,7 +169,8 @@ export default {
|
||||
}),
|
||||
async getRoutineData () {
|
||||
if (!this.routine) return;
|
||||
this.isQuering = true;
|
||||
this.localRoutine = { sql: '' };
|
||||
this.isLoading = true;
|
||||
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
@@ -189,7 +193,7 @@ export default {
|
||||
}
|
||||
|
||||
this.resizeQueryEditor();
|
||||
this.isQuering = false;
|
||||
this.isLoading = false;
|
||||
},
|
||||
async saveChanges () {
|
||||
if (this.isSaving) return;
|
||||
|
316
src/renderer/components/WorkspacePropsTabScheduler.vue
Normal file
316
src/renderer/components/WorkspacePropsTabScheduler.vue
Normal file
@@ -0,0 +1,316 @@
|
||||
<template>
|
||||
<div class="workspace-query-tab column col-12 columns col-gapless">
|
||||
<div class="workspace-query-runner column col-12">
|
||||
<div class="workspace-query-runner-footer">
|
||||
<div class="workspace-query-buttons">
|
||||
<button
|
||||
class="btn btn-primary btn-sm"
|
||||
:disabled="!isChanged"
|
||||
:class="{'loading':isSaving}"
|
||||
@click="saveChanges"
|
||||
>
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
<i class="mdi mdi-24px mdi-content-save ml-1" />
|
||||
</button>
|
||||
<button
|
||||
:disabled="!isChanged"
|
||||
class="btn btn-link btn-sm mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
@click="clearChanges"
|
||||
>
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
|
||||
</button>
|
||||
|
||||
<div class="divider-vert py-3" />
|
||||
<button class="btn btn-dark btn-sm" @click="showTimingModal">
|
||||
<span>{{ $t('word.timing') }}</span>
|
||||
<i class="mdi mdi-24px mdi-timer ml-1" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="columns mb-4">
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.name') }}</label>
|
||||
<input
|
||||
v-model="localScheduler.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.definer') }}</label>
|
||||
<select
|
||||
v-if="workspace.users.length"
|
||||
v-model="localScheduler.definer"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
<option v-if="!isDefinerInUsers" :value="originalScheduler.definer">
|
||||
{{ originalScheduler.definer.replaceAll('`', '') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="user in workspace.users"
|
||||
:key="`${user.name}@${user.host}`"
|
||||
:value="`\`${user.name}\`@\`${user.host}\``"
|
||||
>
|
||||
{{ user.name }}@{{ user.host }}
|
||||
</option>
|
||||
</select>
|
||||
<select v-if="!workspace.users.length" class="form-select">
|
||||
<option value="">
|
||||
{{ $t('message.currentUser') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-4">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.comment') }}</label>
|
||||
<input
|
||||
v-model="localScheduler.comment"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div class="form-group">
|
||||
<label class="form-label mr-2">{{ $t('word.state') }}</label>
|
||||
<label class="form-radio form-inline">
|
||||
<input
|
||||
v-model="localScheduler.state"
|
||||
type="radio"
|
||||
name="state"
|
||||
value="ENABLE"
|
||||
><i class="form-icon" /> ENABLE
|
||||
</label>
|
||||
<label class="form-radio form-inline">
|
||||
<input
|
||||
v-model="localScheduler.state"
|
||||
type="radio"
|
||||
name="state"
|
||||
value="DISABLE"
|
||||
><i class="form-icon" /> DISABLE
|
||||
</label>
|
||||
<label class="form-radio form-inline">
|
||||
<input
|
||||
v-model="localScheduler.state"
|
||||
type="radio"
|
||||
name="state"
|
||||
value="DISABLE ON SLAVE"
|
||||
><i class="form-icon" /> DISABLE ON SLAVE
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||
<BaseLoader v-if="isLoading" />
|
||||
<label class="form-label ml-2">{{ $t('message.schedulerBody') }}</label>
|
||||
<QueryEditor
|
||||
v-if="isSelected"
|
||||
ref="queryEditor"
|
||||
:value.sync="localScheduler.sql"
|
||||
:workspace="workspace"
|
||||
:schema="schema"
|
||||
:height="editorHeight"
|
||||
/>
|
||||
</div>
|
||||
<WorkspacePropsSchedulerTimingModal
|
||||
v-if="isTimingModal"
|
||||
:local-options="localScheduler"
|
||||
:workspace="workspace"
|
||||
@hide="hideTimingModal"
|
||||
@options-update="timingUpdate"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import WorkspacePropsSchedulerTimingModal from '@/components/WorkspacePropsSchedulerTimingModal';
|
||||
import Schedulers from '@/ipc-api/Schedulers';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsTabScheduler',
|
||||
components: {
|
||||
BaseLoader,
|
||||
QueryEditor,
|
||||
WorkspacePropsSchedulerTimingModal
|
||||
},
|
||||
props: {
|
||||
connection: Object,
|
||||
scheduler: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
tabUid: 'prop',
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
isTimingModal: false,
|
||||
originalScheduler: null,
|
||||
localScheduler: { sql: '' },
|
||||
lastScheduler: null,
|
||||
sqlProxy: '',
|
||||
editorHeight: 300
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
isSelected () {
|
||||
return this.workspace.selected_tab === 'prop';
|
||||
},
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.originalScheduler) !== JSON.stringify(this.localScheduler);
|
||||
},
|
||||
isDefinerInUsers () {
|
||||
return this.originalScheduler ? this.workspace.users.some(user => this.originalScheduler.definer === `\`${user.name}\`@\`${user.host}\``) : true;
|
||||
},
|
||||
schemaTables () {
|
||||
const schemaTables = this.workspace.structure
|
||||
.filter(schema => schema.name === this.schema)
|
||||
.map(schema => schema.tables);
|
||||
|
||||
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
async scheduler () {
|
||||
if (this.isSelected) {
|
||||
await this.getSchedulerData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localScheduler.sql);
|
||||
this.lastScheduler = this.scheduler;
|
||||
}
|
||||
},
|
||||
async isSelected (val) {
|
||||
if (val && this.lastScheduler !== this.scheduler) {
|
||||
await this.getSchedulerData();
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localScheduler.sql);
|
||||
this.lastScheduler = this.scheduler;
|
||||
}
|
||||
},
|
||||
isChanged (val) {
|
||||
if (this.isSelected && this.lastScheduler === this.scheduler && this.scheduler !== null)
|
||||
this.setUnsavedChanges(val);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
window.addEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
refreshStructure: 'workspaces/refreshStructure',
|
||||
setUnsavedChanges: 'workspaces/setUnsavedChanges',
|
||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
|
||||
}),
|
||||
async getSchedulerData () {
|
||||
if (!this.scheduler) return;
|
||||
this.isLoading = true;
|
||||
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
scheduler: this.workspace.breadcrumbs.scheduler
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Schedulers.getSchedulerInformations(params);
|
||||
if (status === 'success') {
|
||||
this.originalScheduler = response;
|
||||
this.localScheduler = JSON.parse(JSON.stringify(this.originalScheduler));
|
||||
this.sqlProxy = this.localScheduler.sql;
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.resizeQueryEditor();
|
||||
this.isLoading = false;
|
||||
},
|
||||
async saveChanges () {
|
||||
if (this.isSaving) return;
|
||||
this.isSaving = true;
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
scheduler: {
|
||||
...this.localScheduler,
|
||||
oldName: this.originalScheduler.name
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Schedulers.alterScheduler(params);
|
||||
|
||||
if (status === 'success') {
|
||||
const oldName = this.originalScheduler.name;
|
||||
|
||||
await this.refreshStructure(this.connection.uid);
|
||||
|
||||
if (oldName !== this.localScheduler.name) {
|
||||
this.setUnsavedChanges(false);
|
||||
this.changeBreadcrumbs({ schema: this.schema, scheduler: this.localScheduler.name });
|
||||
}
|
||||
|
||||
this.getSchedulerData();
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isSaving = false;
|
||||
},
|
||||
clearChanges () {
|
||||
this.localScheduler = JSON.parse(JSON.stringify(this.originalScheduler));
|
||||
this.$refs.queryEditor.editor.session.setValue(this.localScheduler.sql);
|
||||
},
|
||||
resizeQueryEditor () {
|
||||
if (this.$refs.queryEditor) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
this.editorHeight = size;
|
||||
this.$refs.queryEditor.editor.resize();
|
||||
}
|
||||
},
|
||||
showTimingModal () {
|
||||
this.isTimingModal = true;
|
||||
},
|
||||
hideTimingModal () {
|
||||
this.isTimingModal = false;
|
||||
},
|
||||
timingUpdate (options) {
|
||||
this.localScheduler = options;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="columns mb-4">
|
||||
<div class="column col-3">
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.name') }}</label>
|
||||
<input
|
||||
@@ -36,7 +36,7 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-3">
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.definer') }}</label>
|
||||
<select
|
||||
@@ -67,7 +67,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column col-3">
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.table') }}</label>
|
||||
<select v-model="localTrigger.table" class="form-select">
|
||||
@@ -77,7 +77,7 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-3">
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.event') }}</label>
|
||||
<div class="input-group">
|
||||
@@ -95,7 +95,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workspace-query-results column col-12 mt-2">
|
||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||
<BaseLoader v-if="isLoading" />
|
||||
<label class="form-label ml-2">{{ $t('message.triggerStatement') }}</label>
|
||||
<QueryEditor
|
||||
v-if="isSelected"
|
||||
@@ -110,13 +111,15 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import Triggers from '@/ipc-api/Triggers';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsTabTrigger',
|
||||
components: {
|
||||
BaseLoader,
|
||||
QueryEditor
|
||||
},
|
||||
props: {
|
||||
@@ -126,7 +129,7 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
tabUid: 'prop',
|
||||
isQuering: false,
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
originalTrigger: null,
|
||||
localTrigger: { sql: '' },
|
||||
@@ -197,7 +200,9 @@ export default {
|
||||
}),
|
||||
async getTriggerData () {
|
||||
if (!this.trigger) return;
|
||||
this.isQuering = true;
|
||||
|
||||
this.localTrigger = { sql: '' };
|
||||
this.isLoading = true;
|
||||
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
@@ -220,7 +225,7 @@ export default {
|
||||
}
|
||||
|
||||
this.resizeQueryEditor();
|
||||
this.isQuering = false;
|
||||
this.isLoading = false;
|
||||
},
|
||||
async saveChanges () {
|
||||
if (this.isSaving) return;
|
||||
|
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="columns mb-4">
|
||||
<div class="column col-3">
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.name') }}</label>
|
||||
<input
|
||||
@@ -36,7 +36,7 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-3">
|
||||
<div class="column col-auto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.definer') }}</label>
|
||||
<select
|
||||
@@ -67,7 +67,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column col-2">
|
||||
<div class="column col-auto mr-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('message.sqlSecurity') }}</label>
|
||||
<label class="form-radio">
|
||||
@@ -90,7 +90,7 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-2">
|
||||
<div class="column col-auto mr-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('word.algorithm') }}</label>
|
||||
<label class="form-radio">
|
||||
@@ -122,7 +122,7 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-2">
|
||||
<div class="column col-auto mr-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('message.updateOption') }}</label>
|
||||
<label class="form-radio">
|
||||
@@ -156,7 +156,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workspace-query-results column col-12 mt-2">
|
||||
<div class="workspace-query-results column col-12 mt-2 p-relative">
|
||||
<BaseLoader v-if="isLoading" />
|
||||
<label class="form-label ml-2">{{ $t('message.selectStatement') }}</label>
|
||||
<QueryEditor
|
||||
v-if="isSelected"
|
||||
@@ -172,12 +173,14 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import Views from '@/ipc-api/Views';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsTabView',
|
||||
components: {
|
||||
BaseLoader,
|
||||
QueryEditor
|
||||
},
|
||||
props: {
|
||||
@@ -187,7 +190,7 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
tabUid: 'prop',
|
||||
isQuering: false,
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
originalView: null,
|
||||
localView: { sql: '' },
|
||||
@@ -251,7 +254,8 @@ export default {
|
||||
}),
|
||||
async getViewData () {
|
||||
if (!this.view) return;
|
||||
this.isQuering = true;
|
||||
this.isLoading = true;
|
||||
this.localView = { sql: '' };
|
||||
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
@@ -274,7 +278,7 @@ export default {
|
||||
}
|
||||
|
||||
this.resizeQueryEditor();
|
||||
this.isQuering = false;
|
||||
this.isLoading = false;
|
||||
},
|
||||
async saveChanges () {
|
||||
if (this.isSaving) return;
|
||||
|
@@ -318,7 +318,7 @@ export default {
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
localLength () {
|
||||
return this.localRow.numLength || this.localRow.charLength || this.localRow.datePrecision || 0;
|
||||
return this.localRow.numLength || this.localRow.charLength || this.localRow.datePrecision || this.localRow.numPrecision || 0;
|
||||
},
|
||||
fieldType () {
|
||||
const fieldType = this.dataTypes.reduce((acc, group) => [...acc, ...group.types], []).filter(type =>
|
||||
|
@@ -3,11 +3,14 @@
|
||||
<div class="workspace-query-runner column col-12">
|
||||
<QueryEditor
|
||||
v-if="isSelected"
|
||||
ref="queryEditor"
|
||||
:auto-focus="true"
|
||||
:value.sync="query"
|
||||
:workspace="workspace"
|
||||
:schema="schema"
|
||||
:height="editorHeight"
|
||||
/>
|
||||
<div ref="resizer" class="query-area-resizer" />
|
||||
<div class="workspace-query-runner-footer">
|
||||
<div class="workspace-query-buttons">
|
||||
<button
|
||||
@@ -80,7 +83,8 @@ export default {
|
||||
isQuering: false,
|
||||
results: [],
|
||||
resultsCount: 0,
|
||||
affectedCount: 0
|
||||
affectedCount: 0,
|
||||
editorHeight: 200
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -94,6 +98,16 @@ export default {
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
mounted () {
|
||||
const resizer = this.$refs.resizer;
|
||||
|
||||
resizer.addEventListener('mousedown', e => {
|
||||
e.preventDefault();
|
||||
|
||||
window.addEventListener('mousemove', this.resize);
|
||||
window.addEventListener('mouseup', this.stopResize);
|
||||
});
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
@@ -139,6 +153,21 @@ export default {
|
||||
this.resultsCount = 0;
|
||||
this.affectedCount = 0;
|
||||
},
|
||||
resize (e) {
|
||||
const el = this.$refs.queryEditor.$el;
|
||||
let editorHeight = e.pageY - el.getBoundingClientRect().top;
|
||||
if (editorHeight > 400) editorHeight = 400;
|
||||
if (editorHeight < 50) editorHeight = 50;
|
||||
this.editorHeight = editorHeight;
|
||||
},
|
||||
stopResize () {
|
||||
window.removeEventListener('mousemove', this.resize);
|
||||
if (this.$refs.queryTable && this.results.length)
|
||||
this.$refs.queryTable.resizeResults();
|
||||
|
||||
if (this.$refs.queryEditor)
|
||||
this.$refs.queryEditor.editor.resize();
|
||||
},
|
||||
onKey (e) {
|
||||
if (this.isSelected) {
|
||||
e.stopPropagation();
|
||||
@@ -155,11 +184,23 @@ export default {
|
||||
align-content: baseline;
|
||||
|
||||
.workspace-query-runner {
|
||||
position: relative;
|
||||
|
||||
.query-area-resizer {
|
||||
position: absolute;
|
||||
height: 5px;
|
||||
bottom: 40px;
|
||||
width: 100%;
|
||||
cursor: ns-resize;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.workspace-query-runner-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.3rem 0.6rem 0.4rem;
|
||||
align-items: center;
|
||||
height: 42px;
|
||||
|
||||
.workspace-query-buttons {
|
||||
display: flex;
|
||||
|
@@ -69,7 +69,7 @@
|
||||
class="tr"
|
||||
:class="{'selected': selectedRows.includes(row._id)}"
|
||||
@select-row="selectRow($event, row._id)"
|
||||
@update-field="updateField($event, getPrimaryValue(row))"
|
||||
@update-field="updateField($event, row)"
|
||||
@contextmenu="contextMenu"
|
||||
/>
|
||||
</template>
|
||||
@@ -80,6 +80,7 @@
|
||||
|
||||
<script>
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import arrayToFile from '../libs/arrayToFile';
|
||||
import { LONG_TEXT, BLOB } from 'common/fieldTypes';
|
||||
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
|
||||
import WorkspaceQueryTableRow from '@/components/WorkspaceQueryTableRow';
|
||||
@@ -195,7 +196,7 @@ export default {
|
||||
},
|
||||
fieldLength (field) {
|
||||
if ([...BLOB, ...LONG_TEXT].includes(field.type)) return null;
|
||||
return field.numLength || field.datePrecision || field.charLength || 0;
|
||||
return field.length;
|
||||
},
|
||||
keyName (key) {
|
||||
switch (key) {
|
||||
@@ -252,37 +253,32 @@ export default {
|
||||
refreshScroller () {
|
||||
this.resizeResults();
|
||||
},
|
||||
updateField (payload, id) {
|
||||
if (!this.primaryField)
|
||||
this.addNotification({ status: 'warning', message: this.$t('message.unableEditFieldWithoutPrimary') });
|
||||
else {
|
||||
const params = {
|
||||
primary: this.primaryField.name,
|
||||
schema: this.getSchema(this.resultsetIndex),
|
||||
table: this.getTable(this.resultsetIndex),
|
||||
id,
|
||||
...payload
|
||||
};
|
||||
this.$emit('update-field', params);
|
||||
}
|
||||
updateField (payload, row) {
|
||||
delete row._id;
|
||||
|
||||
const params = {
|
||||
primary: this.primaryField.name,
|
||||
schema: this.getSchema(this.resultsetIndex),
|
||||
table: this.getTable(this.resultsetIndex),
|
||||
id: this.getPrimaryValue(row),
|
||||
row,
|
||||
...payload
|
||||
};
|
||||
this.$emit('update-field', params);
|
||||
},
|
||||
deleteSelected () {
|
||||
if (!this.primaryField)
|
||||
this.addNotification({ status: 'warning', message: this.$t('message.unableEditFieldWithoutPrimary') });
|
||||
else {
|
||||
const rowIDs = this.localResults.filter(row => this.selectedRows.includes(row._id)).map(row =>
|
||||
row[this.primaryField.name] ||
|
||||
row[`${this.primaryField.table}.${this.primaryField.name}`] ||
|
||||
row[`${this.primaryField.tableAlias}.${this.primaryField.name}`]
|
||||
);
|
||||
const params = {
|
||||
primary: this.primaryField.name,
|
||||
schema: this.getSchema(this.resultsetIndex),
|
||||
table: this.getTable(this.resultsetIndex),
|
||||
rows: rowIDs
|
||||
};
|
||||
this.$emit('delete-selected', params);
|
||||
}
|
||||
const rows = this.localResults.filter(row => this.selectedRows.includes(row._id)).map(row => {
|
||||
delete row._id;
|
||||
return row;
|
||||
});
|
||||
|
||||
const params = {
|
||||
primary: this.primaryField.name,
|
||||
schema: this.getSchema(this.resultsetIndex),
|
||||
table: this.getTable(this.resultsetIndex),
|
||||
rows
|
||||
};
|
||||
this.$emit('delete-selected', params);
|
||||
},
|
||||
applyUpdate (params) {
|
||||
const { primary, id, field, table, content } = params;
|
||||
@@ -354,6 +350,20 @@ export default {
|
||||
},
|
||||
selectResultset (index) {
|
||||
this.resultsetIndex = index;
|
||||
},
|
||||
downloadTable (format, filename) {
|
||||
if (!this.sortedResults) return;
|
||||
|
||||
const rows = [...this.sortedResults].map(row => {
|
||||
delete row._id;
|
||||
return row;
|
||||
});
|
||||
|
||||
arrayToFile({
|
||||
type: format,
|
||||
content: rows,
|
||||
filename
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -32,13 +32,30 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-dark btn-sm"
|
||||
@click="showAddModal"
|
||||
>
|
||||
|
||||
<button class="btn btn-dark btn-sm" @click="showAddModal">
|
||||
<span>{{ $t('word.add') }}</span>
|
||||
<i class="mdi mdi-24px mdi-playlist-plus ml-1" />
|
||||
</button>
|
||||
<div class="dropdown export-dropdown">
|
||||
<button
|
||||
:disabled="isQuering"
|
||||
class="btn btn-dark btn-sm dropdown-toggle mr-0 pr-0"
|
||||
tabindex="0"
|
||||
>
|
||||
<span>{{ $t('word.export') }}</span>
|
||||
<i class="mdi mdi-24px mdi-file-export ml-1" />
|
||||
<i class="mdi mdi-24px mdi-menu-down" />
|
||||
</button>
|
||||
<ul class="menu text-left">
|
||||
<li class="menu-item">
|
||||
<a class="c-hand" @click="downloadTable('json')">JSON</a>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a class="c-hand" @click="downloadTable('csv')">CSV</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workspace-query-info">
|
||||
<div v-if="results.length && results[0].rows">
|
||||
@@ -222,7 +239,21 @@ export default {
|
||||
this.reloadTable();
|
||||
}, this.autorefreshTimer * 1000);
|
||||
}
|
||||
},
|
||||
downloadTable (format) {
|
||||
this.$refs.queryTable.downloadTable(format, this.table);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.export-dropdown {
|
||||
.menu {
|
||||
min-width: 100%;
|
||||
|
||||
.menu-item a:hover {
|
||||
background: $bg-color-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -78,7 +78,19 @@ module.exports = {
|
||||
parameters: 'Parameters',
|
||||
function: 'Function | Functions',
|
||||
deterministic: 'Deterministic',
|
||||
context: 'Context'
|
||||
context: 'Context',
|
||||
export: 'Export',
|
||||
returns: 'Returns',
|
||||
timing: 'Timing',
|
||||
state: 'State',
|
||||
execution: 'Execution',
|
||||
starts: 'Starts',
|
||||
ends: 'Ends',
|
||||
ssl: 'SSL',
|
||||
privateKey: 'Private key',
|
||||
certificate: 'Certificate',
|
||||
caCertificate: 'CA certificate',
|
||||
ciphers: 'Ciphers'
|
||||
},
|
||||
message: {
|
||||
appWelcome: 'Welcome to Antares SQL Client!',
|
||||
@@ -158,7 +170,15 @@ module.exports = {
|
||||
thereAreNoParameters: 'There are no parameters',
|
||||
createNewParameter: 'Create new parameter',
|
||||
createNewRoutine: 'Create new stored routine',
|
||||
deleteRoutine: 'Delete stored routine'
|
||||
deleteRoutine: 'Delete stored routine',
|
||||
functionBody: 'Function body',
|
||||
createNewFunction: 'Create new function',
|
||||
deleteFunction: 'Delete function',
|
||||
schedulerBody: 'Scheduler body',
|
||||
createNewScheduler: 'Create new scheduler',
|
||||
deleteScheduler: 'Delete scheduler',
|
||||
preserveOnCompletion: 'Preserve on completion',
|
||||
enableSsl: 'Enable SSL'
|
||||
},
|
||||
// Date and Time
|
||||
short: {
|
||||
|
@@ -18,8 +18,8 @@ export default class {
|
||||
return ipcRenderer.invoke('delete-database', params);
|
||||
}
|
||||
|
||||
static getStructure (uid) {
|
||||
return ipcRenderer.invoke('get-structure', uid);
|
||||
static getStructure (params) {
|
||||
return ipcRenderer.invoke('get-structure', params);
|
||||
}
|
||||
|
||||
static getCollations (uid) {
|
||||
@@ -34,6 +34,10 @@ export default class {
|
||||
return ipcRenderer.invoke('get-engines', uid);
|
||||
}
|
||||
|
||||
static getVersion (uid) {
|
||||
return ipcRenderer.invoke('get-version', uid);
|
||||
}
|
||||
|
||||
static useSchema (params) {
|
||||
return ipcRenderer.invoke('use-schema', params);
|
||||
}
|
||||
|
20
src/renderer/ipc-api/Functions.js
Normal file
20
src/renderer/ipc-api/Functions.js
Normal file
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
export default class {
|
||||
static getFunctionInformations (params) {
|
||||
return ipcRenderer.invoke('get-function-informations', params);
|
||||
}
|
||||
|
||||
static dropFunction (params) {
|
||||
return ipcRenderer.invoke('drop-function', params);
|
||||
}
|
||||
|
||||
static alterFunction (params) {
|
||||
return ipcRenderer.invoke('alter-function', params);
|
||||
}
|
||||
|
||||
static createFunction (params) {
|
||||
return ipcRenderer.invoke('create-function', params);
|
||||
}
|
||||
}
|
20
src/renderer/ipc-api/Schedulers.js
Normal file
20
src/renderer/ipc-api/Schedulers.js
Normal file
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
export default class {
|
||||
static getSchedulerInformations (params) {
|
||||
return ipcRenderer.invoke('get-scheduler-informations', params);
|
||||
}
|
||||
|
||||
static dropScheduler (params) {
|
||||
return ipcRenderer.invoke('drop-scheduler', params);
|
||||
}
|
||||
|
||||
static alterScheduler (params) {
|
||||
return ipcRenderer.invoke('alter-scheduler', params);
|
||||
}
|
||||
|
||||
static createScheduler (params) {
|
||||
return ipcRenderer.invoke('create-scheduler', params);
|
||||
}
|
||||
}
|
37
src/renderer/libs/arrayToFile.js
Normal file
37
src/renderer/libs/arrayToFile.js
Normal file
@@ -0,0 +1,37 @@
|
||||
const arrayToFile = args => {
|
||||
let mime;
|
||||
let content;
|
||||
|
||||
switch (args.type) {
|
||||
case 'csv': {
|
||||
mime = 'text/csv';
|
||||
const csv = [];
|
||||
|
||||
if (args.content.length)
|
||||
csv.push(Object.keys(args.content[0]).join(';'));
|
||||
|
||||
for (const row of args.content)
|
||||
csv.push(Object.values(row).map(col => typeof col === 'string' ? `"${col}"` : col).join(';'));
|
||||
|
||||
content = csv.join('\n');
|
||||
break;
|
||||
}
|
||||
case 'json':
|
||||
mime = 'application/json';
|
||||
content = JSON.stringify(args.content, null, 3);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const file = new Blob([content], { type: mime });
|
||||
const downloadLink = document.createElement('a');
|
||||
downloadLink.download = `${args.filename}.${args.type}`;
|
||||
downloadLink.href = window.URL.createObjectURL(file);
|
||||
downloadLink.style.display = 'none';
|
||||
document.body.appendChild(downloadLink);
|
||||
downloadLink.click();
|
||||
downloadLink.remove();
|
||||
};
|
||||
|
||||
export default arrayToFile;
|
@@ -1239,6 +1239,18 @@ ace.define('ace/autocomplete/popup', ['require', 'exports', 'module', 'ace/virtu
|
||||
case 'view':
|
||||
iconClass = 'mdi-table-eye';
|
||||
break;
|
||||
case 'trigger':
|
||||
iconClass = 'mdi-table-cog';
|
||||
break;
|
||||
case 'routine':
|
||||
iconClass = 'mdi-sync-circle';
|
||||
break;
|
||||
case 'function':
|
||||
iconClass = 'mdi-arrow-right-bold-box';
|
||||
break;
|
||||
case 'scheduler':
|
||||
iconClass = 'mdi-calendar-clock';
|
||||
break;
|
||||
case 'keyword':
|
||||
iconClass = 'mdi-cube';
|
||||
break;
|
||||
|
@@ -210,6 +210,10 @@ body {
|
||||
border-color: $primary-color;
|
||||
}
|
||||
|
||||
.form-input[type="file"] {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.input-group .input-group-addon {
|
||||
border-color: #3f3f3f;
|
||||
z-index: 1;
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import VuexPersist from 'vuex-persist';
|
||||
|
||||
import application from './modules/application.store';
|
||||
import settings from './modules/settings.store';
|
||||
@@ -12,15 +11,6 @@ import notifications from './modules/notifications.store';
|
||||
|
||||
import ipcUpdates from './plugins/ipcUpdates';
|
||||
|
||||
const vuexLocalStorage = new VuexPersist({
|
||||
key: 'application', // The key to store the state on in the storage provider.
|
||||
storage: window.localStorage,
|
||||
reducer: state => ({
|
||||
connections: state.connections,
|
||||
settings: state.settings
|
||||
})
|
||||
});
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
export default new Vuex.Store({
|
||||
@@ -33,7 +23,6 @@ export default new Vuex.Store({
|
||||
notifications
|
||||
},
|
||||
plugins: [
|
||||
vuexLocalStorage.plugin,
|
||||
ipcUpdates
|
||||
]
|
||||
});
|
||||
|
@@ -40,6 +40,9 @@ export default {
|
||||
.filter(workspace => workspace.connected)
|
||||
.map(workspace => workspace.uid);
|
||||
},
|
||||
getLoadedSchemas: state => uid => {
|
||||
return state.workspaces.find(workspace => workspace.uid === uid).loaded_schemas;
|
||||
},
|
||||
isUnsavedDiscardModal: state => {
|
||||
return state.is_unsaved_discard_modal;
|
||||
}
|
||||
@@ -48,7 +51,9 @@ export default {
|
||||
SELECT_WORKSPACE (state, uid) {
|
||||
state.selected_workspace = uid;
|
||||
},
|
||||
ADD_CONNECTED (state, { uid, client, dataTypes, indexTypes, structure }) {
|
||||
ADD_CONNECTED (state, payload) {
|
||||
const { uid, client, dataTypes, indexTypes, structure, version } = payload;
|
||||
|
||||
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid
|
||||
? {
|
||||
...workspace,
|
||||
@@ -56,7 +61,8 @@ export default {
|
||||
dataTypes,
|
||||
indexTypes,
|
||||
structure,
|
||||
connected: true
|
||||
connected: true,
|
||||
version
|
||||
}
|
||||
: workspace);
|
||||
},
|
||||
@@ -65,6 +71,8 @@ export default {
|
||||
? {
|
||||
...workspace,
|
||||
structure: {},
|
||||
breadcrumbs: {},
|
||||
loaded_schemas: new Set(),
|
||||
connected: false
|
||||
}
|
||||
: workspace);
|
||||
@@ -77,6 +85,19 @@ export default {
|
||||
}
|
||||
: workspace);
|
||||
},
|
||||
REFRESH_SCHEMA (state, { uid, schema, schemaElements }) {
|
||||
state.workspaces = state.workspaces.map(workspace => {
|
||||
if (workspace.uid === uid) {
|
||||
const schemaIndex = workspace.structure.findIndex(s => s.name === schema);
|
||||
|
||||
if (schemaIndex !== -1)
|
||||
workspace.structure[schemaIndex] = schemaElements;
|
||||
else
|
||||
workspace.structure.push(schemaElements);
|
||||
}
|
||||
return workspace;
|
||||
});
|
||||
},
|
||||
REFRESH_COLLATIONS (state, { uid, collations }) {
|
||||
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid
|
||||
? {
|
||||
@@ -198,6 +219,13 @@ export default {
|
||||
},
|
||||
SET_PENDING_BREADCRUMBS (state, payload) {
|
||||
state.pending_breadcrumbs = payload;
|
||||
},
|
||||
ADD_LOADED_SCHEMA (state, payload) {
|
||||
state.workspaces = state.workspaces.map(workspace => {
|
||||
if (workspace.uid === payload.uid)
|
||||
workspace.loaded_schemas.add(payload.schema);
|
||||
return workspace;
|
||||
});
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
@@ -220,12 +248,32 @@ export default {
|
||||
indexTypes = require('common/index-types/mysql');
|
||||
break;
|
||||
}
|
||||
|
||||
const { status, response: version } = await Database.getVersion(connection.uid);
|
||||
|
||||
if (status === 'error')
|
||||
dispatch('notifications/addNotification', { status, message: version }, { root: true });
|
||||
|
||||
const isMySQL = version.name.includes('MySQL');
|
||||
|
||||
if (isMySQL && connection.client !== 'mysql') {
|
||||
const connProxy = Object.assign({}, connection);
|
||||
connProxy.client = 'mysql';
|
||||
dispatch('connections/editConnection', connProxy, { root: true });
|
||||
}
|
||||
else if (!isMySQL && connection.client === 'mysql') {
|
||||
const connProxy = Object.assign({}, connection);
|
||||
connProxy.client = 'maria';
|
||||
dispatch('connections/editConnection', connProxy, { root: true });
|
||||
}
|
||||
|
||||
commit('ADD_CONNECTED', {
|
||||
uid: connection.uid,
|
||||
client: connection.client,
|
||||
dataTypes,
|
||||
indexTypes,
|
||||
structure: response
|
||||
structure: response,
|
||||
version
|
||||
});
|
||||
dispatch('refreshCollations', connection.uid);
|
||||
dispatch('refreshVariables', connection.uid);
|
||||
@@ -237,9 +285,10 @@ export default {
|
||||
dispatch('notifications/addNotification', { status: 'error', message: err.stack }, { root: true });
|
||||
}
|
||||
},
|
||||
async refreshStructure ({ dispatch, commit }, uid) {
|
||||
async refreshStructure ({ dispatch, commit, getters }, uid) {
|
||||
try {
|
||||
const { status, response } = await Database.getStructure(uid);
|
||||
const { status, response } = await Database.getStructure({ uid, schemas: getters.getLoadedSchemas(uid) });
|
||||
|
||||
if (status === 'error')
|
||||
dispatch('notifications/addNotification', { status, message: response }, { root: true });
|
||||
else
|
||||
@@ -249,6 +298,18 @@ export default {
|
||||
dispatch('notifications/addNotification', { status: 'error', message: err.stack }, { root: true });
|
||||
}
|
||||
},
|
||||
async refreshSchema ({ dispatch, commit }, { uid, schema }) {
|
||||
try {
|
||||
const { status, response } = await Database.getStructure({ uid, schemas: new Set([schema]) });
|
||||
if (status === 'error')
|
||||
dispatch('notifications/addNotification', { status, message: response }, { root: true });
|
||||
else
|
||||
commit('REFRESH_SCHEMA', { uid, schema, schemaElements: response.find(_schema => _schema.name === schema) });
|
||||
}
|
||||
catch (err) {
|
||||
dispatch('notifications/addNotification', { status: 'error', message: err.stack }, { root: true });
|
||||
}
|
||||
},
|
||||
async refreshCollations ({ dispatch, commit }, uid) {
|
||||
try {
|
||||
const { status, response } = await Database.getCollations(uid);
|
||||
@@ -307,23 +368,13 @@ export default {
|
||||
uid,
|
||||
connected: false,
|
||||
selected_tab: 0,
|
||||
tabs: [{
|
||||
uid: 'data',
|
||||
type: 'table',
|
||||
fields: [],
|
||||
keyUsage: []
|
||||
},
|
||||
{
|
||||
uid: 'prop',
|
||||
type: 'table',
|
||||
fields: [],
|
||||
keyUsage: []
|
||||
}],
|
||||
tabs: [],
|
||||
structure: {},
|
||||
variables: [],
|
||||
collations: [],
|
||||
users: [],
|
||||
breadcrumbs: {}
|
||||
breadcrumbs: {},
|
||||
loaded_schemas: new Set()
|
||||
};
|
||||
|
||||
commit('ADD_WORKSPACE', workspace);
|
||||
@@ -360,6 +411,9 @@ export default {
|
||||
|
||||
commit('CHANGE_BREADCRUMBS', { uid: getters.getSelected, breadcrumbs: { ...breadcrumbsObj, ...payload } });
|
||||
lastBreadcrumbs = { ...breadcrumbsObj, ...payload };
|
||||
|
||||
if (payload.schema)
|
||||
commit('ADD_LOADED_SCHEMA', { uid: getters.getSelected, schema: payload.schema });
|
||||
},
|
||||
newTab ({ commit }, uid) {
|
||||
const tab = uidGen('T');
|
||||
|
Reference in New Issue
Block a user