mirror of
https://github.com/Fabio286/antares.git
synced 2025-06-05 21:59:22 +02:00
Merge pull request #129 from toriphes/feat/db-import-export
feat(MySQL): db import export
This commit is contained in:
commit
dd070d008d
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,3 +6,4 @@ thumbs.db
|
|||||||
NOTES.md
|
NOTES.md
|
||||||
*.txt
|
*.txt
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
*.heapsnapshot
|
14
.vscode/launch.json
vendored
14
.vscode/launch.json
vendored
@ -2,8 +2,8 @@
|
|||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"cwd": "${workspaceFolder}",
|
|
||||||
"name": "Electron: Main",
|
"name": "Electron: Main",
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
"port": 9222,
|
"port": 9222,
|
||||||
"protocol": "inspector",
|
"protocol": "inspector",
|
||||||
"request": "attach",
|
"request": "attach",
|
||||||
@ -18,7 +18,17 @@
|
|||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"type": "chrome",
|
"type": "chrome",
|
||||||
"webRoot": "${workspaceFolder}"
|
"webRoot": "${workspaceFolder}"
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"name": "Electron: Worker",
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"port": 9224,
|
||||||
|
"protocol": "inspector",
|
||||||
|
"request": "attach",
|
||||||
|
"sourceMaps": true,
|
||||||
|
"type": "node",
|
||||||
|
"timeout": 1000000
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"compounds": [
|
"compounds": [
|
||||||
{
|
{
|
||||||
|
@ -8,13 +8,14 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"debug": "npm run rebuild:electron && npm run debug-runner",
|
"debug": "npm run rebuild:electron && npm run debug-runner",
|
||||||
"debug-runner": "node scripts/devRunner.js --remote-debug",
|
"debug-runner": "node scripts/devRunner.js --remote-debug",
|
||||||
"compile": "npm run compile:main && npm run compile:renderer",
|
"compile": "npm run compile:main && npm run compile:workers && npm run compile:renderer",
|
||||||
"compile:main": "webpack --mode=production --config webpack.main.config.js",
|
"compile:main": "webpack --mode=production --config webpack.main.config.js",
|
||||||
|
"compile:workers": "webpack --mode=production --config webpack.workers.config.js",
|
||||||
"compile:renderer": "webpack --mode=production --config webpack.renderer.config.js",
|
"compile:renderer": "webpack --mode=production --config webpack.renderer.config.js",
|
||||||
"build": "cross-env NODE_ENV=production npm run compile",
|
"build": "cross-env NODE_ENV=production npm run compile",
|
||||||
"build:local": "npm run build && electron-builder",
|
"build:local": "npm run build && electron-builder",
|
||||||
"build:appx": "npm run build:local -- --win appx",
|
"build:appx": "npm run build:local -- --win appx",
|
||||||
"rebuild:electron": "npm run postinstall",
|
"rebuild:electron": "rimraf ./dist && npm run postinstall",
|
||||||
"release": "standard-version",
|
"release": "standard-version",
|
||||||
"release:pre": "npm run release -- --prerelease alpha",
|
"release:pre": "npm run release -- --prerelease alpha",
|
||||||
"postinstall": "electron-builder install-app-deps",
|
"postinstall": "electron-builder install-app-deps",
|
||||||
@ -136,7 +137,6 @@
|
|||||||
"all-contributors-cli": "^6.20.0",
|
"all-contributors-cli": "^6.20.0",
|
||||||
"babel-loader": "^8.2.3",
|
"babel-loader": "^8.2.3",
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
"clean-webpack-plugin": "^4.0.0",
|
|
||||||
"cross-env": "^7.0.2",
|
"cross-env": "^7.0.2",
|
||||||
"css-loader": "^6.5.0",
|
"css-loader": "^6.5.0",
|
||||||
"electron": "^17.0.1",
|
"electron": "^17.0.1",
|
||||||
@ -154,6 +154,7 @@
|
|||||||
"node-loader": "^2.0.0",
|
"node-loader": "^2.0.0",
|
||||||
"playwright": "^1.18.1",
|
"playwright": "^1.18.1",
|
||||||
"progress-webpack-plugin": "^1.0.12",
|
"progress-webpack-plugin": "^1.0.12",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
"sass": "^1.42.1",
|
"sass": "^1.42.1",
|
||||||
"sass-loader": "^12.3.0",
|
"sass-loader": "^12.3.0",
|
||||||
"standard-version": "^9.3.1",
|
"standard-version": "^9.3.1",
|
||||||
|
@ -12,7 +12,7 @@ const { spawn } = require('child_process');
|
|||||||
|
|
||||||
const mainConfig = require('../webpack.main.config');
|
const mainConfig = require('../webpack.main.config');
|
||||||
const rendererConfig = require('../webpack.renderer.config');
|
const rendererConfig = require('../webpack.renderer.config');
|
||||||
// const workersConfig = require('../webpack.workers.config');
|
const workersConfig = require('../webpack.workers.config');
|
||||||
|
|
||||||
let electronProcess = null;
|
let electronProcess = null;
|
||||||
let manualRestart = null;
|
let manualRestart = null;
|
||||||
@ -64,7 +64,7 @@ async function restartElectron () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function startMain () {
|
function startMain () {
|
||||||
const webpackSetup = webpack(mainConfig);
|
const webpackSetup = webpack([mainConfig, workersConfig]);
|
||||||
|
|
||||||
webpackSetup.compilers.forEach((compiler) => {
|
webpackSetup.compilers.forEach((compiler) => {
|
||||||
const { name } = compiler;
|
const { name } = compiler;
|
||||||
|
@ -134,7 +134,7 @@ export default class {
|
|||||||
{ name: 'phoneNumberFormat', group: 'phone', types: ['string'] },
|
{ name: 'phoneNumberFormat', group: 'phone', types: ['string'] },
|
||||||
{ name: 'phoneFormats', group: 'phone', types: ['string'] },
|
{ name: 'phoneFormats', group: 'phone', types: ['string'] },
|
||||||
|
|
||||||
{ name: 'number', group: 'random', types: ['string', 'number'], params: ['min', 'max'] },
|
{ name: 'number', group: 'datatype', types: ['string', 'number'], params: ['min', 'max'] },
|
||||||
{ name: 'float', group: 'random', types: ['string', 'float'], params: ['min', 'max'] },
|
{ name: 'float', group: 'random', types: ['string', 'float'], params: ['min', 'max'] },
|
||||||
{ name: 'arrayElement', group: 'random', types: ['string'] },
|
{ name: 'arrayElement', group: 'random', types: ['string'] },
|
||||||
{ name: 'arrayElements', group: 'random', types: ['string'] },
|
{ name: 'arrayElements', group: 'random', types: ['string'] },
|
||||||
|
@ -38,6 +38,8 @@ module.exports = {
|
|||||||
databaseEdit: false,
|
databaseEdit: false,
|
||||||
schemaEdit: false,
|
schemaEdit: false,
|
||||||
schemaDrop: false,
|
schemaDrop: false,
|
||||||
|
schemaExport: false,
|
||||||
|
schemaImport: false,
|
||||||
tableSettings: false,
|
tableSettings: false,
|
||||||
tableOptions: false,
|
tableOptions: false,
|
||||||
tableArray: false,
|
tableArray: false,
|
||||||
|
@ -34,6 +34,8 @@ module.exports = {
|
|||||||
schedulerAdd: true,
|
schedulerAdd: true,
|
||||||
schemaEdit: true,
|
schemaEdit: true,
|
||||||
schemaDrop: true,
|
schemaDrop: true,
|
||||||
|
schemaExport: true,
|
||||||
|
schemaImport: true,
|
||||||
tableSettings: true,
|
tableSettings: true,
|
||||||
viewSettings: true,
|
viewSettings: true,
|
||||||
triggerSettings: true,
|
triggerSettings: true,
|
||||||
|
@ -32,6 +32,8 @@ module.exports = {
|
|||||||
functionAdd: true,
|
functionAdd: true,
|
||||||
schemaDrop: true,
|
schemaDrop: true,
|
||||||
databaseEdit: false,
|
databaseEdit: false,
|
||||||
|
schemaExport: false,
|
||||||
|
schemaImport: false,
|
||||||
tableSettings: true,
|
tableSettings: true,
|
||||||
viewSettings: true,
|
viewSettings: true,
|
||||||
triggerSettings: true,
|
triggerSettings: true,
|
||||||
|
@ -41,6 +41,7 @@ export const NUMBER = [
|
|||||||
|
|
||||||
export const FLOAT = [
|
export const FLOAT = [
|
||||||
'FLOAT',
|
'FLOAT',
|
||||||
|
'DECIMAL',
|
||||||
'DOUBLE',
|
'DOUBLE',
|
||||||
'REAL',
|
'REAL',
|
||||||
'DOUBLE PRECISION',
|
'DOUBLE PRECISION',
|
||||||
|
@ -33,7 +33,7 @@ const lookup = {
|
|||||||
*/
|
*/
|
||||||
export default function hexToBinary (hex) {
|
export default function hexToBinary (hex) {
|
||||||
let binary = '';
|
let binary = '';
|
||||||
for (let i = 0, len = hex.length; i < len; i++)
|
for (let i = 0; i < hex.length; i++)
|
||||||
binary += lookup[hex[i]];
|
binary += lookup[hex[i]];
|
||||||
|
|
||||||
return binary;
|
return binary;
|
||||||
|
@ -23,7 +23,7 @@ export function mimeFromHex (hex) {
|
|||||||
case '425A68':
|
case '425A68':
|
||||||
return { ext: 'bz2', mime: 'application/x-bzip2' };
|
return { ext: 'bz2', mime: 'application/x-bzip2' };
|
||||||
default:
|
default:
|
||||||
switch (hex) { // 4 bites
|
switch (hex) { // 4 bytes
|
||||||
case '89504E47':
|
case '89504E47':
|
||||||
return { ext: 'png', mime: 'image/png' };
|
return { ext: 'png', mime: 'image/png' };
|
||||||
case '47494638':
|
case '47494638':
|
||||||
|
93
src/common/libs/sqlParser.js
Normal file
93
src/common/libs/sqlParser.js
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import { Transform } from 'stream';
|
||||||
|
|
||||||
|
export default class SqlParser extends Transform {
|
||||||
|
constructor (opts) {
|
||||||
|
opts = {
|
||||||
|
delimiter: ';',
|
||||||
|
encoding: 'utf8',
|
||||||
|
writableObjectMode: true,
|
||||||
|
readableObjectMode: true,
|
||||||
|
...opts
|
||||||
|
};
|
||||||
|
super(opts);
|
||||||
|
this._buffer = '';
|
||||||
|
this._lastChar = '';
|
||||||
|
this._last9Chars = '';
|
||||||
|
this.encoding = opts.encoding;
|
||||||
|
this.delimiter = opts.delimiter;
|
||||||
|
|
||||||
|
this.isEscape = false;
|
||||||
|
this.currentQuote = null;
|
||||||
|
this.isDelimiter = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_transform (chunk, encoding, next) {
|
||||||
|
for (const char of chunk.toString(this.encoding)) {
|
||||||
|
this.checkEscape();
|
||||||
|
this._buffer += char;
|
||||||
|
this._lastChar = char;
|
||||||
|
this._last9Chars += char.trim().toLocaleLowerCase();
|
||||||
|
|
||||||
|
if (this._last9Chars.length > 9)
|
||||||
|
this._last9Chars = this._last9Chars.slice(-9);
|
||||||
|
|
||||||
|
this.checkNewDelimiter(char);
|
||||||
|
this.checkQuote(char);
|
||||||
|
const query = this.getQuery();
|
||||||
|
|
||||||
|
if (query)
|
||||||
|
this.push(query);
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
checkEscape () {
|
||||||
|
if (this._buffer.length > 0) {
|
||||||
|
this.isEscape = this._lastChar === '\\'
|
||||||
|
? !this.isEscape
|
||||||
|
: false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkNewDelimiter (char) {
|
||||||
|
if (this.currentQuote === null && this._last9Chars === 'delimiter') {
|
||||||
|
this.isDelimiter = true;
|
||||||
|
this._buffer = '';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const isNewLine = char === '\n' || char === '\r';
|
||||||
|
if (isNewLine && this.isDelimiter) {
|
||||||
|
this.isDelimiter = false;
|
||||||
|
this.delimiter = this._buffer.trim();
|
||||||
|
this._buffer = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkQuote (char) {
|
||||||
|
const isQuote = !this.isEscape && (char === '\'' || char === '"');
|
||||||
|
if (isQuote && this.currentQuote === char)
|
||||||
|
this.currentQuote = null;
|
||||||
|
|
||||||
|
else if (isQuote && this.currentQuote === null)
|
||||||
|
this.currentQuote = char;
|
||||||
|
}
|
||||||
|
|
||||||
|
getQuery () {
|
||||||
|
if (this.isDelimiter)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
let query = false;
|
||||||
|
let demiliterFound = false;
|
||||||
|
if (this.currentQuote === null && this._buffer.length >= this.delimiter.length)
|
||||||
|
demiliterFound = this._last9Chars.slice(-this.delimiter.length) === this.delimiter;
|
||||||
|
|
||||||
|
if (demiliterFound) {
|
||||||
|
const parsedStr = this._buffer.trim();
|
||||||
|
query = parsedStr.slice(0, parsedStr.length - this.delimiter.length);
|
||||||
|
this._buffer = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { app, ipcMain } from 'electron';
|
import { app, ipcMain, dialog } from 'electron';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
ipcMain.on('close-app', () => {
|
ipcMain.on('close-app', () => {
|
||||||
@ -9,4 +9,12 @@ export default () => {
|
|||||||
const key = false;
|
const key = false;
|
||||||
event.returnValue = key;
|
event.returnValue = key;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('showOpenDialog', (event, options) => {
|
||||||
|
return dialog.showOpenDialog(options);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('get-download-dir-path', () => {
|
||||||
|
return app.getPath('downloads');
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -40,7 +40,7 @@ export default connections => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const connection = await ClientsFactory.getConnection({
|
const connection = await ClientsFactory.getClient({
|
||||||
client: conn.client,
|
client: conn.client,
|
||||||
params
|
params
|
||||||
});
|
});
|
||||||
@ -100,7 +100,7 @@ export default connections => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const connection = ClientsFactory.getConnection({
|
const connection = ClientsFactory.getClient({
|
||||||
client: conn.client,
|
client: conn.client,
|
||||||
params,
|
params,
|
||||||
poolSize: 5
|
poolSize: 5
|
||||||
|
@ -1,7 +1,15 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { fork } from 'child_process';
|
||||||
|
import { ipcMain, dialog } from 'electron';
|
||||||
|
|
||||||
import { ipcMain } from 'electron';
|
// @TODO: need some factories
|
||||||
|
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||||
|
|
||||||
export default connections => {
|
export default connections => {
|
||||||
|
let exporter = null;
|
||||||
|
let importer = null;
|
||||||
|
|
||||||
ipcMain.handle('create-schema', async (event, params) => {
|
ipcMain.handle('create-schema', async (event, params) => {
|
||||||
try {
|
try {
|
||||||
await connections[params.uid].createSchema(params);
|
await connections[params.uid].createSchema(params);
|
||||||
@ -37,9 +45,16 @@ export default connections => {
|
|||||||
|
|
||||||
ipcMain.handle('get-schema-collation', async (event, params) => {
|
ipcMain.handle('get-schema-collation', async (event, params) => {
|
||||||
try {
|
try {
|
||||||
const collation = await connections[params.uid].getDatabaseCollation(params);
|
const collation = await connections[params.uid].getDatabaseCollation(
|
||||||
|
params
|
||||||
|
);
|
||||||
|
|
||||||
return { status: 'success', response: collation.rows.length ? collation.rows[0].DEFAULT_COLLATION_NAME : '' };
|
return {
|
||||||
|
status: 'success',
|
||||||
|
response: collation.rows.length
|
||||||
|
? collation.rows[0].DEFAULT_COLLATION_NAME
|
||||||
|
: ''
|
||||||
|
};
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
return { status: 'error', response: err.toString() };
|
return { status: 'error', response: err.toString() };
|
||||||
@ -48,7 +63,9 @@ export default connections => {
|
|||||||
|
|
||||||
ipcMain.handle('get-structure', async (event, params) => {
|
ipcMain.handle('get-structure', async (event, params) => {
|
||||||
try {
|
try {
|
||||||
const structure = await connections[params.uid].getStructure(params.schemas);
|
const structure = await connections[params.uid].getStructure(
|
||||||
|
params.schemas
|
||||||
|
);
|
||||||
|
|
||||||
return { status: 'success', response: structure };
|
return { status: 'success', response: structure };
|
||||||
}
|
}
|
||||||
@ -155,6 +172,169 @@ export default connections => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('export', (event, { uid, type, tables, ...rest }) => {
|
||||||
|
if (exporter !== null) return;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
(async () => {
|
||||||
|
if (fs.existsSync(rest.outputFile)) { // If file exists ask for replace
|
||||||
|
const result = await dialog.showMessageBox({
|
||||||
|
type: 'warning',
|
||||||
|
message: `File ${rest.outputFile} already exists. Do you want to replace it?`,
|
||||||
|
detail: 'A file with the same name already exists in the target folder. Replacing it will overwrite its current contents.',
|
||||||
|
buttons: ['Cancel', 'Replace'],
|
||||||
|
defaultId: 0,
|
||||||
|
cancelId: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.response !== 1) {
|
||||||
|
resolve({
|
||||||
|
type: 'error',
|
||||||
|
response: 'Operation aborted'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init exporter process
|
||||||
|
exporter = fork(isDevelopment ? './dist/exporter.js' : path.resolve(__dirname, './exporter.js'), [], {
|
||||||
|
execArgv: isDevelopment ? ['--inspect=9224'] : undefined
|
||||||
|
});
|
||||||
|
exporter.send({
|
||||||
|
type: 'init',
|
||||||
|
client: {
|
||||||
|
name: type,
|
||||||
|
config: await connections[uid].getDbConfig()
|
||||||
|
},
|
||||||
|
tables,
|
||||||
|
options: rest
|
||||||
|
});
|
||||||
|
|
||||||
|
// Exporter message listener
|
||||||
|
exporter.on('message', ({ type, payload }) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'export-progress':
|
||||||
|
event.sender.send('export-progress', payload);
|
||||||
|
break;
|
||||||
|
case 'end':
|
||||||
|
setTimeout(() => { // Ensures that writing process has finished
|
||||||
|
exporter.kill();
|
||||||
|
exporter = null;
|
||||||
|
}, 2000);
|
||||||
|
resolve({ status: 'success', response: payload });
|
||||||
|
break;
|
||||||
|
case 'cancel':
|
||||||
|
exporter.kill();
|
||||||
|
exporter = null;
|
||||||
|
resolve({ status: 'error', response: 'Operation cancelled' });
|
||||||
|
break;
|
||||||
|
case 'error':
|
||||||
|
exporter.kill();
|
||||||
|
exporter = null;
|
||||||
|
resolve({ status: 'error', response: payload });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
exporter.on('exit', code => {
|
||||||
|
exporter = null;
|
||||||
|
resolve({ status: 'error', response: `Operation ended with code: ${code}` });
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('abort-export', async event => {
|
||||||
|
let willAbort = false;
|
||||||
|
|
||||||
|
if (exporter) {
|
||||||
|
const result = await dialog.showMessageBox({
|
||||||
|
type: 'warning',
|
||||||
|
message: 'Are you sure you want to abort the export',
|
||||||
|
buttons: ['Cancel', 'Abort'],
|
||||||
|
defaultId: 0,
|
||||||
|
cancelId: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.response === 1) {
|
||||||
|
willAbort = true;
|
||||||
|
exporter.send({ type: 'cancel' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { status: 'success', response: { willAbort } };
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('import-sql', async (event, options) => {
|
||||||
|
if (importer !== null) return;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
(async () => {
|
||||||
|
const dbConfig = await connections[options.uid].getDbConfig();
|
||||||
|
|
||||||
|
// Init importer process
|
||||||
|
importer = fork(isDevelopment ? './dist/importer.js' : path.resolve(__dirname, './importer.js'), [], {
|
||||||
|
execArgv: isDevelopment ? ['--inspect=9224'] : undefined
|
||||||
|
});
|
||||||
|
importer.send({
|
||||||
|
type: 'init',
|
||||||
|
dbConfig,
|
||||||
|
options
|
||||||
|
});
|
||||||
|
|
||||||
|
// Importer message listener
|
||||||
|
importer.on('message', ({ type, payload }) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'import-progress':
|
||||||
|
event.sender.send('import-progress', payload);
|
||||||
|
break;
|
||||||
|
case 'query-error':
|
||||||
|
event.sender.send('query-error', payload);
|
||||||
|
break;
|
||||||
|
case 'end':
|
||||||
|
setTimeout(() => { // Ensures that writing process has finished
|
||||||
|
importer?.kill();
|
||||||
|
importer = null;
|
||||||
|
}, 2000);
|
||||||
|
resolve({ status: 'success', response: payload });
|
||||||
|
break;
|
||||||
|
case 'cancel':
|
||||||
|
importer.kill();
|
||||||
|
importer = null;
|
||||||
|
resolve({ status: 'error', response: 'Operation cancelled' });
|
||||||
|
break;
|
||||||
|
case 'error':
|
||||||
|
importer.kill();
|
||||||
|
importer = null;
|
||||||
|
resolve({ status: 'error', response: payload });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('abort-import-sql', async event => {
|
||||||
|
let willAbort = false;
|
||||||
|
|
||||||
|
if (importer) {
|
||||||
|
const result = await dialog.showMessageBox({
|
||||||
|
type: 'warning',
|
||||||
|
message: 'Are you sure you want to abort the import',
|
||||||
|
buttons: ['Cancel', 'Abort'],
|
||||||
|
defaultId: 0,
|
||||||
|
cancelId: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.response === 1) {
|
||||||
|
willAbort = true;
|
||||||
|
importer.send({ type: 'cancel' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { status: 'success', response: { willAbort } };
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle('kill-tab-query', async (event, { uid, tabUid }) => {
|
ipcMain.handle('kill-tab-query', async (event, { uid, tabUid }) => {
|
||||||
if (!tabUid) return;
|
if (!tabUid) return;
|
||||||
|
|
||||||
|
@ -149,7 +149,7 @@ export default (connections) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ([...BIT].includes(params.type)) {
|
else if (BIT.includes(params.type)) {
|
||||||
escapedParam = `b'${sqlEscaper(params.content)}'`;
|
escapedParam = `b'${sqlEscaper(params.content)}'`;
|
||||||
reload = true;
|
reload = true;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
const queryLogger = sql => {
|
||||||
|
// Remove comments, newlines and multiple spaces
|
||||||
|
const escapedSql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' ');
|
||||||
|
console.log(escapedSql);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* As Simple As Possible Query Builder Core
|
* As Simple As Possible Query Builder Core
|
||||||
*
|
*
|
||||||
@ -17,7 +23,7 @@ export class AntaresCore {
|
|||||||
this._poolSize = args.poolSize || false;
|
this._poolSize = args.poolSize || false;
|
||||||
this._connection = null;
|
this._connection = null;
|
||||||
this._ssh = null;
|
this._ssh = null;
|
||||||
this._logger = args.logger || console.log;
|
this._logger = args.logger || queryLogger;
|
||||||
|
|
||||||
this._queryDefaults = {
|
this._queryDefaults = {
|
||||||
schema: '',
|
schema: '',
|
||||||
|
@ -2,13 +2,6 @@
|
|||||||
import { MySQLClient } from './clients/MySQLClient';
|
import { MySQLClient } from './clients/MySQLClient';
|
||||||
import { PostgreSQLClient } from './clients/PostgreSQLClient';
|
import { PostgreSQLClient } from './clients/PostgreSQLClient';
|
||||||
import { SQLiteClient } from './clients/SQLiteClient';
|
import { SQLiteClient } from './clients/SQLiteClient';
|
||||||
|
|
||||||
const queryLogger = sql => {
|
|
||||||
// Remove comments, newlines and multiple spaces
|
|
||||||
const escapedSql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' ');
|
|
||||||
console.log(escapedSql);
|
|
||||||
};
|
|
||||||
|
|
||||||
export class ClientsFactory {
|
export class ClientsFactory {
|
||||||
/**
|
/**
|
||||||
* Returns a database connection based on received args.
|
* Returns a database connection based on received args.
|
||||||
@ -29,9 +22,7 @@ export class ClientsFactory {
|
|||||||
* @returns Database Connection
|
* @returns Database Connection
|
||||||
* @memberof ClientsFactory
|
* @memberof ClientsFactory
|
||||||
*/
|
*/
|
||||||
static getConnection (args) {
|
static getClient (args) {
|
||||||
args.logger = queryLogger;
|
|
||||||
|
|
||||||
switch (args.client) {
|
switch (args.client) {
|
||||||
case 'mysql':
|
case 'mysql':
|
||||||
case 'maria':
|
case 'maria':
|
||||||
|
@ -101,6 +101,26 @@ export class MySQLClient extends AntaresCore {
|
|||||||
.filter(_type => _type.name === type.toUpperCase())[0];
|
.filter(_type => _type.name === type.toUpperCase())[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_reducer (acc, curr) {
|
||||||
|
const type = typeof curr;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'number':
|
||||||
|
case 'string':
|
||||||
|
return [...acc, curr];
|
||||||
|
case 'object':
|
||||||
|
if (Array.isArray(curr))
|
||||||
|
return [...acc, ...curr];
|
||||||
|
else {
|
||||||
|
const clausoles = [];
|
||||||
|
for (const key in curr)
|
||||||
|
clausoles.push(`\`${key}\` ${curr[key]}`);
|
||||||
|
|
||||||
|
return clausoles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @returns dbConfig
|
* @returns dbConfig
|
||||||
@ -498,6 +518,7 @@ export class MySQLClient extends AntaresCore {
|
|||||||
charset: field.CHARACTER_SET_NAME,
|
charset: field.CHARACTER_SET_NAME,
|
||||||
collation: field.COLLATION_NAME,
|
collation: field.COLLATION_NAME,
|
||||||
autoIncrement: field.EXTRA.includes('auto_increment'),
|
autoIncrement: field.EXTRA.includes('auto_increment'),
|
||||||
|
generated: field.EXTRA.toLowerCase().includes('generated'),
|
||||||
onUpdate: field.EXTRA.toLowerCase().includes('on update')
|
onUpdate: field.EXTRA.toLowerCase().includes('on update')
|
||||||
? field.EXTRA.substr(field.EXTRA.indexOf('on update') + 9, field.EXTRA.length).trim()
|
? field.EXTRA.substr(field.EXTRA.indexOf('on update') + 9, field.EXTRA.length).trim()
|
||||||
: '',
|
: '',
|
||||||
@ -1602,7 +1623,7 @@ export class MySQLClient extends AntaresCore {
|
|||||||
let insertRaw = '';
|
let insertRaw = '';
|
||||||
|
|
||||||
if (this._query.insert.length) {
|
if (this._query.insert.length) {
|
||||||
const fieldsList = Object.keys(this._query.insert[0]);
|
const fieldsList = Object.keys(this._query.insert[0]).map(col => '`' + col + '`');
|
||||||
const rowsList = this._query.insert.map(el => `(${Object.values(el).join(', ')})`);
|
const rowsList = this._query.insert.map(el => `(${Object.values(el).join(', ')})`);
|
||||||
|
|
||||||
insertRaw = `(${fieldsList.join(', ')}) VALUES ${rowsList.join(', ')} `;
|
insertRaw = `(${fieldsList.join(', ')}) VALUES ${rowsList.join(', ')} `;
|
||||||
|
77
src/main/libs/exporters/BaseExporter.js
Normal file
77
src/main/libs/exporters/BaseExporter.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import EventEmitter from 'events';
|
||||||
|
|
||||||
|
export class BaseExporter extends EventEmitter {
|
||||||
|
constructor (tables, options) {
|
||||||
|
super();
|
||||||
|
this._tables = tables;
|
||||||
|
this._options = options;
|
||||||
|
this._isCancelled = false;
|
||||||
|
this._outputStream = fs.createWriteStream(this._options.outputFile, {
|
||||||
|
flags: 'w'
|
||||||
|
});
|
||||||
|
this._state = {};
|
||||||
|
|
||||||
|
this._outputStream.once('error', err => {
|
||||||
|
this._isCancelled = true;
|
||||||
|
this.emit('error', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run () {
|
||||||
|
try {
|
||||||
|
this.emit('start', this);
|
||||||
|
await this.dump();
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.emit('error', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this._outputStream.end();
|
||||||
|
this.emit('end');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get isCancelled () {
|
||||||
|
return this._isCancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
get outputFile () {
|
||||||
|
return this._options.outputFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputFileExists () {
|
||||||
|
return fs.existsSync(this._options.outputFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel () {
|
||||||
|
this._isCancelled = true;
|
||||||
|
this.emit('cancel');
|
||||||
|
this.emitUpdate({ op: 'cancelling' });
|
||||||
|
}
|
||||||
|
|
||||||
|
emitUpdate (state) {
|
||||||
|
this.emit('progress', { ...this._state, ...state });
|
||||||
|
}
|
||||||
|
|
||||||
|
writeString (data) {
|
||||||
|
if (this._isCancelled) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.accessSync(this._options.outputFile);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this._isCancelled = true;
|
||||||
|
|
||||||
|
const fileName = path.basename(this._options.outputFile);
|
||||||
|
this.emit('error', `The file ${fileName} is not accessible`);
|
||||||
|
}
|
||||||
|
this._outputStream.write(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
dump () {
|
||||||
|
throw new Error('Exporter must implement the "dump" method');
|
||||||
|
}
|
||||||
|
}
|
412
src/main/libs/exporters/sql/MysqlExporter.js
Normal file
412
src/main/libs/exporters/sql/MysqlExporter.js
Normal file
@ -0,0 +1,412 @@
|
|||||||
|
import { SqlExporter } from './SqlExporter';
|
||||||
|
import { BLOB, BIT, DATE, DATETIME, FLOAT, SPATIAL, IS_MULTI_SPATIAL, NUMBER } from 'common/fieldTypes';
|
||||||
|
import hexToBinary from 'common/libs/hexToBinary';
|
||||||
|
import { getArrayDepth } from 'common/libs/getArrayDepth';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { lineString, point, polygon } from '@turf/helpers';
|
||||||
|
|
||||||
|
export default class MysqlExporter extends SqlExporter {
|
||||||
|
async getSqlHeader () {
|
||||||
|
let dump = await super.getSqlHeader();
|
||||||
|
dump += `
|
||||||
|
|
||||||
|
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||||
|
SET NAMES utf8mb4;
|
||||||
|
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||||
|
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||||
|
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;`;
|
||||||
|
|
||||||
|
return dump;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFooter () {
|
||||||
|
const footer = await super.getFooter();
|
||||||
|
|
||||||
|
return `/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||||
|
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||||
|
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||||
|
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||||
|
|
||||||
|
${footer}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCreateTable (tableName) {
|
||||||
|
const { rows } = await this._client.raw(
|
||||||
|
`SHOW CREATE TABLE \`${this.schemaName}\`.\`${tableName}\``
|
||||||
|
);
|
||||||
|
|
||||||
|
if (rows.length !== 1) return '';
|
||||||
|
|
||||||
|
const col = 'Create View' in rows[0] ? 'Create View' : 'Create Table';
|
||||||
|
|
||||||
|
return rows[0][col] + ';';
|
||||||
|
}
|
||||||
|
|
||||||
|
getDropTable (tableName) {
|
||||||
|
return `DROP TABLE IF EXISTS \`${tableName}\`;`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async * getTableInsert (tableName) {
|
||||||
|
let rowCount = 0;
|
||||||
|
let sqlStr = '';
|
||||||
|
|
||||||
|
const countResults = await this._client.raw(`SELECT COUNT(1) as count FROM \`${this.schemaName}\`.\`${tableName}\``);
|
||||||
|
if (countResults.rows.length === 1) rowCount = countResults.rows[0].count;
|
||||||
|
|
||||||
|
if (rowCount > 0) {
|
||||||
|
let queryLength = 0;
|
||||||
|
let rowsWritten = 0;
|
||||||
|
let rowIndex = 0;
|
||||||
|
const { sqlInsertDivider, sqlInsertAfter } = this._options;
|
||||||
|
const columns = await this._client.getTableColumns({
|
||||||
|
table: tableName,
|
||||||
|
schema: this.schemaName
|
||||||
|
});
|
||||||
|
|
||||||
|
const notGeneratedColumns = columns.filter(col => !col.generated);
|
||||||
|
const columnNames = notGeneratedColumns.map(col => '`' + col.name + '`');
|
||||||
|
const insertStmt = `INSERT INTO \`${tableName}\` (${columnNames.join(
|
||||||
|
', '
|
||||||
|
)}) VALUES`;
|
||||||
|
|
||||||
|
sqlStr += `LOCK TABLES \`${tableName}\` WRITE;\n`;
|
||||||
|
sqlStr += `/*!40000 ALTER TABLE \`${tableName}\` DISABLE KEYS */;`;
|
||||||
|
sqlStr += '\n\n';
|
||||||
|
yield sqlStr;
|
||||||
|
|
||||||
|
yield insertStmt;
|
||||||
|
|
||||||
|
const stream = await this._queryStream(
|
||||||
|
`SELECT ${columnNames.join(', ')} FROM \`${this.schemaName}\`.\`${tableName}\``
|
||||||
|
);
|
||||||
|
|
||||||
|
for await (const row of stream) {
|
||||||
|
if (this.isCancelled) {
|
||||||
|
stream.destroy();
|
||||||
|
yield null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sqlInsertString = '';
|
||||||
|
|
||||||
|
if (
|
||||||
|
(sqlInsertDivider === 'bytes' && queryLength >= sqlInsertAfter * 1024) ||
|
||||||
|
(sqlInsertDivider === 'rows' && rowsWritten === sqlInsertAfter)
|
||||||
|
) {
|
||||||
|
sqlInsertString += `;\n${insertStmt}\n\t(`;
|
||||||
|
queryLength = 0;
|
||||||
|
rowsWritten = 0;
|
||||||
|
}
|
||||||
|
else if (parseInt(rowIndex) === 0) sqlInsertString += '\n\t(';
|
||||||
|
else sqlInsertString += ',\n\t(';
|
||||||
|
|
||||||
|
for (const i in notGeneratedColumns) {
|
||||||
|
const column = notGeneratedColumns[i];
|
||||||
|
const val = row[column.name];
|
||||||
|
|
||||||
|
if (val === null) sqlInsertString += 'NULL';
|
||||||
|
else if (DATE.includes(column.type)) {
|
||||||
|
sqlInsertString += moment(val).isValid()
|
||||||
|
? this.escapeAndQuote(moment(val).format('YYYY-MM-DD'))
|
||||||
|
: val;
|
||||||
|
}
|
||||||
|
else if (DATETIME.includes(column.type)) {
|
||||||
|
let datePrecision = '';
|
||||||
|
for (let i = 0; i < column.precision; i++)
|
||||||
|
datePrecision += i === 0 ? '.S' : 'S';
|
||||||
|
|
||||||
|
sqlInsertString += moment(val).isValid()
|
||||||
|
? this.escapeAndQuote(moment(val).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`))
|
||||||
|
: this.escapeAndQuote(val);
|
||||||
|
}
|
||||||
|
else if (BIT.includes(column.type))
|
||||||
|
sqlInsertString += `b'${hexToBinary(Buffer.from(val).toString('hex'))}'`;
|
||||||
|
else if (BLOB.includes(column.type))
|
||||||
|
sqlInsertString += `X'${val.toString('hex').toUpperCase()}'`;
|
||||||
|
else if (NUMBER.includes(column.type))
|
||||||
|
sqlInsertString += val;
|
||||||
|
else if (FLOAT.includes(column.type))
|
||||||
|
sqlInsertString += parseFloat(val);
|
||||||
|
else if (SPATIAL.includes(column.type)) {
|
||||||
|
let geoJson;
|
||||||
|
if (IS_MULTI_SPATIAL.includes(column.type)) {
|
||||||
|
const features = [];
|
||||||
|
for (const element of val)
|
||||||
|
features.push(this.getMarkers(element));
|
||||||
|
|
||||||
|
geoJson = {
|
||||||
|
type: 'FeatureCollection',
|
||||||
|
features
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
geoJson = this._getGeoJSON(val);
|
||||||
|
|
||||||
|
sqlInsertString += `ST_GeomFromGeoJSON('${JSON.stringify(geoJson)}')`;
|
||||||
|
}
|
||||||
|
else if (val === '') sqlInsertString += '\'\'';
|
||||||
|
else {
|
||||||
|
sqlInsertString += typeof val === 'string'
|
||||||
|
? this.escapeAndQuote(val)
|
||||||
|
: typeof val === 'object'
|
||||||
|
? this.escapeAndQuote(JSON.stringify(val))
|
||||||
|
: val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parseInt(i) !== notGeneratedColumns.length - 1)
|
||||||
|
sqlInsertString += ', ';
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlInsertString += ')';
|
||||||
|
|
||||||
|
queryLength += sqlInsertString.length;
|
||||||
|
rowsWritten++;
|
||||||
|
rowIndex++;
|
||||||
|
yield sqlInsertString;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlStr = ';\n\n';
|
||||||
|
sqlStr += `/*!40000 ALTER TABLE \`${tableName}\` ENABLE KEYS */;\n`;
|
||||||
|
sqlStr += 'UNLOCK TABLES;';
|
||||||
|
|
||||||
|
yield sqlStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getViews () {
|
||||||
|
const { rows: views } = await this._client.raw(
|
||||||
|
`SHOW TABLE STATUS FROM \`${this.schemaName}\` WHERE Comment = 'VIEW'`
|
||||||
|
);
|
||||||
|
let sqlString = '';
|
||||||
|
|
||||||
|
for (const view of views) {
|
||||||
|
sqlString += `DROP VIEW IF EXISTS \`${view.Name}\`;\n`;
|
||||||
|
const viewSyntax = await this.getCreateTable(view.Name);
|
||||||
|
sqlString += viewSyntax.replaceAll('`' + this.schemaName + '`.', '');
|
||||||
|
sqlString += '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
return sqlString;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTriggers () {
|
||||||
|
const { rows: triggers } = await this._client.raw(
|
||||||
|
`SHOW TRIGGERS FROM \`${this.schemaName}\``
|
||||||
|
);
|
||||||
|
const generatedTables = this._tables
|
||||||
|
.filter(t => t.includeStructure)
|
||||||
|
.map(t => t.table);
|
||||||
|
|
||||||
|
let sqlString = '';
|
||||||
|
|
||||||
|
for (const trigger of triggers) {
|
||||||
|
const {
|
||||||
|
Trigger: name,
|
||||||
|
Timing: timing,
|
||||||
|
Event: event,
|
||||||
|
Table: table,
|
||||||
|
Statement: statement,
|
||||||
|
sql_mode: sqlMode
|
||||||
|
} = trigger;
|
||||||
|
|
||||||
|
if (!generatedTables.includes(table)) continue;
|
||||||
|
|
||||||
|
const definer = this.getEscapedDefiner(trigger.Definer);
|
||||||
|
sqlString += '/*!50003 SET @OLD_SQL_MODE=@@SQL_MODE*/;;\n';
|
||||||
|
sqlString += `/*!50003 SET SQL_MODE="${sqlMode}" */;\n`;
|
||||||
|
sqlString += 'DELIMITER ;;\n';
|
||||||
|
sqlString += '/*!50003 CREATE*/ ';
|
||||||
|
sqlString += `/*!50017 DEFINER=${definer}*/ `;
|
||||||
|
sqlString += `/*!50003 TRIGGER \`${name}\` ${timing} ${event} ON \`${table}\` FOR EACH ROW ${statement}*/;;\n`;
|
||||||
|
sqlString += 'DELIMITER ;\n';
|
||||||
|
sqlString += '/*!50003 SET SQL_MODE=@OLD_SQL_MODE */;\n\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
return sqlString;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSchedulers () {
|
||||||
|
const { rows: schedulers } = await this._client.raw(
|
||||||
|
`SELECT *, EVENT_SCHEMA AS \`Db\`, EVENT_NAME AS \`Name\` FROM information_schema.\`EVENTS\` WHERE EVENT_SCHEMA = '${this.schemaName}'`
|
||||||
|
);
|
||||||
|
let sqlString = '';
|
||||||
|
|
||||||
|
for (const scheduler of schedulers) {
|
||||||
|
const {
|
||||||
|
EVENT_NAME: name,
|
||||||
|
SQL_MODE: sqlMode,
|
||||||
|
EVENT_TYPE: type,
|
||||||
|
INTERVAL_VALUE: intervalValue,
|
||||||
|
INTERVAL_FIELD: intervalField,
|
||||||
|
STARTS: starts,
|
||||||
|
ENDS: ends,
|
||||||
|
EXECUTE_AT: at,
|
||||||
|
ON_COMPLETION: onCompletion,
|
||||||
|
STATUS: status,
|
||||||
|
EVENT_DEFINITION: definition
|
||||||
|
} = scheduler;
|
||||||
|
|
||||||
|
const definer = this.getEscapedDefiner(scheduler.DEFINER);
|
||||||
|
const comment = this.escapeAndQuote(scheduler.EVENT_COMMENT);
|
||||||
|
|
||||||
|
sqlString += `/*!50106 DROP EVENT IF EXISTS \`${name}\` */;\n`;
|
||||||
|
sqlString += '/*!50003 SET @OLD_SQL_MODE=@@SQL_MODE*/;;\n';
|
||||||
|
sqlString += `/*!50003 SET SQL_MODE='${sqlMode}' */;\n`;
|
||||||
|
sqlString += 'DELIMITER ;;\n';
|
||||||
|
sqlString += '/*!50106 CREATE*/ ';
|
||||||
|
sqlString += `/*!50117 DEFINER=${definer}*/ `;
|
||||||
|
sqlString += `/*!50106 EVENT \`${name}\` ON SCHEDULE `;
|
||||||
|
if (type === 'RECURRING') {
|
||||||
|
sqlString += `EVERY ${intervalValue} ${intervalField} STARTS '${starts}' `;
|
||||||
|
|
||||||
|
if (ends) sqlString += `ENDS '${ends}' `;
|
||||||
|
}
|
||||||
|
else sqlString += `AT '${at}' `;
|
||||||
|
sqlString += `ON COMPLETION ${onCompletion} ${
|
||||||
|
status === 'disabled' ? 'DISABLE' : 'ENABLE'
|
||||||
|
} COMMENT ${comment || '\'\''} DO ${definition}*/;;\n`;
|
||||||
|
sqlString += 'DELIMITER ;\n';
|
||||||
|
sqlString += '/*!50003 SET SQL_MODE=@OLD_SQL_MODE*/;;\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
return sqlString;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFunctions () {
|
||||||
|
const { rows: functions } = await this._client.raw(
|
||||||
|
`SHOW FUNCTION STATUS WHERE \`Db\` = '${this.schemaName}';`
|
||||||
|
);
|
||||||
|
|
||||||
|
let sqlString = '';
|
||||||
|
|
||||||
|
for (const func of functions) {
|
||||||
|
const definer = this.getEscapedDefiner(func.Definer);
|
||||||
|
sqlString += await this.getRoutineSyntax(
|
||||||
|
func.Name,
|
||||||
|
func.Type,
|
||||||
|
definer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sqlString;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRoutines () {
|
||||||
|
const { rows: routines } = await this._client.raw(
|
||||||
|
`SHOW PROCEDURE STATUS WHERE \`Db\` = '${this.schemaName}';`
|
||||||
|
);
|
||||||
|
|
||||||
|
let sqlString = '';
|
||||||
|
|
||||||
|
for (const routine of routines) {
|
||||||
|
const definer = this.getEscapedDefiner(routine.Definer);
|
||||||
|
|
||||||
|
sqlString += await this.getRoutineSyntax(
|
||||||
|
routine.Name,
|
||||||
|
routine.Type,
|
||||||
|
definer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sqlString;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRoutineSyntax (name, type, definer) {
|
||||||
|
const { rows: routines } = await this._client.raw(
|
||||||
|
`SHOW CREATE ${type} \`${this.schemaName}\`.\`${name}\``
|
||||||
|
);
|
||||||
|
|
||||||
|
if (routines.length === 0) return '';
|
||||||
|
|
||||||
|
const routine = routines[0];
|
||||||
|
|
||||||
|
const fieldName = `Create ${type === 'PROCEDURE' ? 'Procedure' : 'Function'}`;
|
||||||
|
const sqlMode = routine.sql_mode;
|
||||||
|
const createProcedure = routine[fieldName];
|
||||||
|
let sqlString = '';
|
||||||
|
|
||||||
|
if (createProcedure) { // If procedure body not empty
|
||||||
|
const startOffset = createProcedure.indexOf(type);
|
||||||
|
const procedureBody = createProcedure.substring(startOffset);
|
||||||
|
|
||||||
|
sqlString += `/*!50003 DROP ${type} IF EXISTS ${name}*/;;\n`;
|
||||||
|
sqlString += '/*!50003 SET @OLD_SQL_MODE=@@SQL_MODE*/;;\n';
|
||||||
|
sqlString += `/*!50003 SET SQL_MODE="${sqlMode}"*/;;\n`;
|
||||||
|
sqlString += 'DELIMITER ;;\n';
|
||||||
|
sqlString += `/*!50003 CREATE*/ /*!50020 DEFINER=${definer}*/ /*!50003 ${procedureBody}*/;;\n`;
|
||||||
|
sqlString += 'DELIMITER ;\n';
|
||||||
|
sqlString += '/*!50003 SET SQL_MODE=@OLD_SQL_MODE*/;\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
return sqlString;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _queryStream (sql) {
|
||||||
|
if (process.env.NODE_ENV === 'development') console.log('EXPORTER:', sql);
|
||||||
|
const isPool = typeof this._client._connection.getConnection === 'function';
|
||||||
|
const connection = isPool ? await this._client._connection.getConnection() : this._client._connection;
|
||||||
|
const stream = connection.connection.query(sql).stream();
|
||||||
|
const dispose = () => connection.destroy();
|
||||||
|
|
||||||
|
stream.on('end', dispose);
|
||||||
|
stream.on('error', dispose);
|
||||||
|
stream.on('close', dispose);
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEscapedDefiner (definer) {
|
||||||
|
return definer
|
||||||
|
.split('@')
|
||||||
|
.map(part => '`' + part + '`')
|
||||||
|
.join('@');
|
||||||
|
}
|
||||||
|
|
||||||
|
escapeAndQuote (val) {
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
|
const CHARS_TO_ESCAPE = /[\0\b\t\n\r\x1a"'\\]/g;
|
||||||
|
const CHARS_ESCAPE_MAP = {
|
||||||
|
'\0': '\\0',
|
||||||
|
'\b': '\\b',
|
||||||
|
'\t': '\\t',
|
||||||
|
'\n': '\\n',
|
||||||
|
'\r': '\\r',
|
||||||
|
'\x1a': '\\Z',
|
||||||
|
'"': '\\"',
|
||||||
|
'\'': '\\\'',
|
||||||
|
'\\': '\\\\'
|
||||||
|
};
|
||||||
|
let chunkIndex = CHARS_TO_ESCAPE.lastIndex = 0;
|
||||||
|
let escapedVal = '';
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = CHARS_TO_ESCAPE.exec(val))) {
|
||||||
|
escapedVal += val.slice(chunkIndex, match.index) + CHARS_ESCAPE_MAP[match[0]];
|
||||||
|
chunkIndex = CHARS_TO_ESCAPE.lastIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunkIndex === 0)
|
||||||
|
return `'${val}'`;
|
||||||
|
|
||||||
|
if (chunkIndex < val.length)
|
||||||
|
return `'${escapedVal + val.slice(chunkIndex)}'`;
|
||||||
|
|
||||||
|
return `'${escapedVal}'`;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getGeoJSON (val) {
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
if (getArrayDepth(val) === 1)
|
||||||
|
return lineString(val.reduce((acc, curr) => [...acc, [curr.x, curr.y]], []));
|
||||||
|
else
|
||||||
|
return polygon(val.map(arr => arr.reduce((acc, curr) => [...acc, [curr.x, curr.y]], [])));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return point([val.x, val.y]);
|
||||||
|
}
|
||||||
|
}
|
162
src/main/libs/exporters/sql/SqlExporter.js
Normal file
162
src/main/libs/exporters/sql/SqlExporter.js
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
import { BaseExporter } from '../BaseExporter';
|
||||||
|
|
||||||
|
export class SqlExporter extends BaseExporter {
|
||||||
|
constructor (client, tables, options) {
|
||||||
|
super(tables, options);
|
||||||
|
this._client = client;
|
||||||
|
this._commentChar = '#';
|
||||||
|
}
|
||||||
|
|
||||||
|
get schemaName () {
|
||||||
|
return this._options.schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
get host () {
|
||||||
|
return this._client._params.host;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getServerVersion () {
|
||||||
|
const version = await this._client.getVersion();
|
||||||
|
return `${version.name} ${version.number}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async dump () {
|
||||||
|
const { includes } = this._options;
|
||||||
|
const extraItems = Object.keys(includes).filter(key => includes[key]);
|
||||||
|
const totalTableToProcess = this._tables.filter(
|
||||||
|
t => t.includeStructure || t.includeContent || t.includeDropStatement
|
||||||
|
).length;
|
||||||
|
const processingItemCount = totalTableToProcess + extraItems.length;
|
||||||
|
|
||||||
|
const exportState = {
|
||||||
|
totalItems: processingItemCount,
|
||||||
|
currentItemIndex: 0,
|
||||||
|
currentItem: '',
|
||||||
|
op: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
const header = await this.getSqlHeader();
|
||||||
|
this.writeString(header);
|
||||||
|
this.writeString('\n\n\n');
|
||||||
|
|
||||||
|
for (const item of this._tables) {
|
||||||
|
// user abort operation
|
||||||
|
if (this.isCancelled) return;
|
||||||
|
|
||||||
|
// skip item if not set to output any detail for them
|
||||||
|
if (
|
||||||
|
!item.includeStructure &&
|
||||||
|
!item.includeContent &&
|
||||||
|
!item.includeDropStatement
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
exportState.currentItemIndex++;
|
||||||
|
exportState.currentItem = item.table;
|
||||||
|
exportState.op = 'FETCH';
|
||||||
|
|
||||||
|
this.emitUpdate(exportState);
|
||||||
|
|
||||||
|
const tableHeader = this.buildComment(
|
||||||
|
`Dump of table ${item.table}\n------------------------------------------------------------`
|
||||||
|
);
|
||||||
|
this.writeString(tableHeader);
|
||||||
|
this.writeString('\n\n');
|
||||||
|
|
||||||
|
if (item.includeDropStatement) {
|
||||||
|
const dropTableSyntax = this.getDropTable(item.table);
|
||||||
|
this.writeString(dropTableSyntax);
|
||||||
|
this.writeString('\n\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.includeStructure) {
|
||||||
|
const createTableSyntax = await this.getCreateTable(item.table);
|
||||||
|
this.writeString(createTableSyntax);
|
||||||
|
this.writeString('\n\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.includeContent) {
|
||||||
|
exportState.op = 'WRITE';
|
||||||
|
this.emitUpdate(exportState);
|
||||||
|
for await (const sqlStr of this.getTableInsert(item.table)) {
|
||||||
|
if (this.isCancelled) return;
|
||||||
|
this.writeString(sqlStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.writeString('\n\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.writeString('\n\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of extraItems) {
|
||||||
|
const processingMethod = `get${item.charAt(0).toUpperCase() + item.slice(1)}`;
|
||||||
|
exportState.currentItemIndex++;
|
||||||
|
exportState.currentItem = item;
|
||||||
|
exportState.op = 'PROCESSING';
|
||||||
|
this.emitUpdate(exportState);
|
||||||
|
|
||||||
|
if (this[processingMethod]) {
|
||||||
|
const data = await this[processingMethod]();
|
||||||
|
if (data !== '') {
|
||||||
|
const header =
|
||||||
|
this.buildComment(
|
||||||
|
`Dump of ${item}\n------------------------------------------------------------`
|
||||||
|
) + '\n\n';
|
||||||
|
|
||||||
|
this.writeString(header);
|
||||||
|
this.writeString(data);
|
||||||
|
this.writeString('\n\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const footer = await this.getFooter();
|
||||||
|
this.writeString(footer);
|
||||||
|
}
|
||||||
|
|
||||||
|
buildComment (text) {
|
||||||
|
return text
|
||||||
|
.split('\n')
|
||||||
|
.map(txt => `${this._commentChar} ${txt}`)
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSqlHeader () {
|
||||||
|
const serverVersion = await this.getServerVersion();
|
||||||
|
const header = `************************************************************
|
||||||
|
Antares - SQL Client
|
||||||
|
Version ${process.env.PACKAGE_VERSION}
|
||||||
|
|
||||||
|
https://antares-sql.app/
|
||||||
|
https://github.com/Fabio286/antares
|
||||||
|
|
||||||
|
Host: ${this.host} (${serverVersion})
|
||||||
|
Database: ${this.schemaName}
|
||||||
|
Generation time: ${moment().format()}
|
||||||
|
************************************************************`;
|
||||||
|
|
||||||
|
return this.buildComment(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFooter () {
|
||||||
|
return this.buildComment(`Dump completed on ${moment().format()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCreateTable (tableName) {
|
||||||
|
throw new Error(
|
||||||
|
'Sql Exporter must implement the "getCreateTable" method'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDropTable (tableName) {
|
||||||
|
throw new Error('Sql Exporter must implement the "getDropTable" method');
|
||||||
|
}
|
||||||
|
|
||||||
|
getTableInsert (tableName) {
|
||||||
|
throw new Error(
|
||||||
|
'Sql Exporter must implement the "getTableInsert" method'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
53
src/main/libs/importers/BaseImporter.js
Normal file
53
src/main/libs/importers/BaseImporter.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import EventEmitter from 'events';
|
||||||
|
|
||||||
|
export class BaseImporter extends EventEmitter {
|
||||||
|
constructor (options) {
|
||||||
|
super();
|
||||||
|
this._options = options;
|
||||||
|
this._isCancelled = false;
|
||||||
|
this._fileHandler = fs.createReadStream(this._options.file, {
|
||||||
|
flags: 'r',
|
||||||
|
highWaterMark: 4 * 1024
|
||||||
|
});
|
||||||
|
this._state = {};
|
||||||
|
|
||||||
|
this._fileHandler.once('error', err => {
|
||||||
|
this._isCancelled = true;
|
||||||
|
this.emit('error', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run () {
|
||||||
|
try {
|
||||||
|
this.emit('start', this);
|
||||||
|
await this.import();
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.emit('error', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this._fileHandler.close();
|
||||||
|
this.emit('end');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get isCancelled () {
|
||||||
|
return this._isCancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel () {
|
||||||
|
this._isCancelled = true;
|
||||||
|
this.emit('cancel');
|
||||||
|
this.emitUpdate({ op: 'cancelling' });
|
||||||
|
}
|
||||||
|
|
||||||
|
emitUpdate (state) {
|
||||||
|
this.emit('progress', { ...this._state, ...state });
|
||||||
|
}
|
||||||
|
|
||||||
|
import () {
|
||||||
|
throw new Error('Exporter must implement the "import" method');
|
||||||
|
}
|
||||||
|
}
|
85
src/main/libs/importers/sql/MysqlImporter.js
Normal file
85
src/main/libs/importers/sql/MysqlImporter.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import fs from 'fs/promises';
|
||||||
|
import SqlParser from '../../../../common/libs/sqlParser';
|
||||||
|
import { BaseImporter } from '../BaseImporter';
|
||||||
|
|
||||||
|
export default class MysqlImporter extends BaseImporter {
|
||||||
|
constructor (client, options) {
|
||||||
|
super(options);
|
||||||
|
this._client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
async import () {
|
||||||
|
try {
|
||||||
|
const { size: totalFileSize } = await fs.stat(this._options.file);
|
||||||
|
const parser = new SqlParser();
|
||||||
|
let readPosition = 0;
|
||||||
|
let queryCount = 0;
|
||||||
|
|
||||||
|
this.emitUpdate({
|
||||||
|
fileSize: totalFileSize,
|
||||||
|
readPosition: 0,
|
||||||
|
percentage: 0,
|
||||||
|
queryCount: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
// 1. detect file encoding
|
||||||
|
// 2. set fh encoding
|
||||||
|
// 3. detect sql mode
|
||||||
|
// 4. restore sql mode in case of exception
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this._fileHandler.pipe(parser);
|
||||||
|
|
||||||
|
parser.on('error', reject);
|
||||||
|
|
||||||
|
parser.on('close', async () => {
|
||||||
|
console.log('TOTAL QUERIES', queryCount);
|
||||||
|
console.log('import end');
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
parser.on('data', async (query) => {
|
||||||
|
queryCount++;
|
||||||
|
parser.pause();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this._client.query(query);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
this.emit('query-error', {
|
||||||
|
sql: query,
|
||||||
|
message: error.sqlMessage,
|
||||||
|
sqlSnippet: error.sql,
|
||||||
|
time: new Date().getTime()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emitUpdate({
|
||||||
|
queryCount,
|
||||||
|
readPosition,
|
||||||
|
percentage: readPosition / totalFileSize * 100
|
||||||
|
});
|
||||||
|
this._fileHandler.pipe(parser);
|
||||||
|
parser.resume();
|
||||||
|
});
|
||||||
|
|
||||||
|
parser.on('pause', () => {
|
||||||
|
this._fileHandler.unpipe(parser);
|
||||||
|
this._fileHandler.readableFlowing = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
this._fileHandler.on('data', (chunk) => {
|
||||||
|
readPosition += chunk.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
this._fileHandler.on('error', (err) => {
|
||||||
|
console.log(err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
60
src/main/workers/exporter.js
Normal file
60
src/main/workers/exporter.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { ClientsFactory } from '../libs/ClientsFactory';
|
||||||
|
import MysqlExporter from '../libs/exporters/sql/MysqlExporter.js';
|
||||||
|
import fs from 'fs';
|
||||||
|
let exporter;
|
||||||
|
|
||||||
|
process.on('message', async ({ type, client, tables, options }) => {
|
||||||
|
if (type === 'init') {
|
||||||
|
const connection = await ClientsFactory.getClient({
|
||||||
|
client: client.name,
|
||||||
|
params: client.config,
|
||||||
|
poolSize: 5
|
||||||
|
});
|
||||||
|
await connection.connect();
|
||||||
|
|
||||||
|
switch (client.name) {
|
||||||
|
case 'mysql':
|
||||||
|
case 'maria':
|
||||||
|
exporter = new MysqlExporter(connection, tables, options);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
process.send({
|
||||||
|
type: 'error',
|
||||||
|
payload: `"${client.name}" exporter not aviable`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
exporter.once('error', err => {
|
||||||
|
console.error(err);
|
||||||
|
process.send({
|
||||||
|
type: 'error',
|
||||||
|
payload: err.toString()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
exporter.once('end', () => {
|
||||||
|
process.send({
|
||||||
|
type: 'end',
|
||||||
|
payload: { cancelled: exporter.isCancelled }
|
||||||
|
});
|
||||||
|
connection.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
exporter.once('cancel', () => {
|
||||||
|
fs.unlinkSync(exporter.outputFile);
|
||||||
|
process.send({ type: 'cancel' });
|
||||||
|
});
|
||||||
|
|
||||||
|
exporter.on('progress', state => {
|
||||||
|
process.send({
|
||||||
|
type: 'export-progress',
|
||||||
|
payload: state
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
exporter.run();
|
||||||
|
}
|
||||||
|
else if (type === 'cancel')
|
||||||
|
exporter.cancel();
|
||||||
|
});
|
68
src/main/workers/importer.js
Normal file
68
src/main/workers/importer.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { ClientsFactory } from '../libs/ClientsFactory';
|
||||||
|
import MysqlImporter from '../libs/importers/sql/MysqlImporter';
|
||||||
|
let importer;
|
||||||
|
|
||||||
|
process.on('message', async ({ type, dbConfig, options }) => {
|
||||||
|
if (type === 'init') {
|
||||||
|
const connection = await ClientsFactory.getClient({
|
||||||
|
client: options.type,
|
||||||
|
params: {
|
||||||
|
...dbConfig,
|
||||||
|
schema: options.schema
|
||||||
|
},
|
||||||
|
poolSize: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
const pool = await connection.getConnectionPool();
|
||||||
|
|
||||||
|
switch (options.type) {
|
||||||
|
case 'mysql':
|
||||||
|
case 'maria':
|
||||||
|
importer = new MysqlImporter(pool, options);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
process.send({
|
||||||
|
type: 'error',
|
||||||
|
payload: `"${options.type}" importer not aviable`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
importer.once('error', err => {
|
||||||
|
console.error(err);
|
||||||
|
process.send({
|
||||||
|
type: 'error',
|
||||||
|
payload: err.toString()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
importer.once('end', () => {
|
||||||
|
process.send({
|
||||||
|
type: 'end',
|
||||||
|
payload: { cancelled: importer.isCancelled }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
importer.once('cancel', () => {
|
||||||
|
process.send({ type: 'cancel' });
|
||||||
|
});
|
||||||
|
|
||||||
|
importer.on('progress', state => {
|
||||||
|
process.send({
|
||||||
|
type: 'import-progress',
|
||||||
|
payload: state
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
importer.on('query-error', state => {
|
||||||
|
process.send({
|
||||||
|
type: 'query-error',
|
||||||
|
payload: state
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
importer.run();
|
||||||
|
}
|
||||||
|
else if (type === 'cancel')
|
||||||
|
importer.cancel();
|
||||||
|
});
|
@ -110,5 +110,4 @@ export default {
|
|||||||
.modal.modal-sm .modal-container {
|
.modal.modal-sm .modal-container {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
491
src/renderer/components/ModalExportSchema.vue
Normal file
491
src/renderer/components/ModalExportSchema.vue
Normal file
@ -0,0 +1,491 @@
|
|||||||
|
<template>
|
||||||
|
<div class="modal active">
|
||||||
|
<a class="modal-overlay" @click.stop="closeModal" />
|
||||||
|
<div class="modal-container p-0">
|
||||||
|
<div class="modal-header pl-2">
|
||||||
|
<div class="modal-title h6">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="mdi mdi-24px mdi-database-arrow-down mr-1" />
|
||||||
|
<span class="cut-text">{{ $t('message.exportSchema') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||||
|
</div>
|
||||||
|
<div class="modal-body pb-0">
|
||||||
|
<div class="container">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="col-3">
|
||||||
|
<label class="form-label">{{ $t('message.directoryPath') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-9">
|
||||||
|
<fieldset class="input-group">
|
||||||
|
<input
|
||||||
|
v-model="basePath"
|
||||||
|
class="form-input"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
readonly
|
||||||
|
:placeholder="$t('message.schemaName')"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary input-group-btn"
|
||||||
|
@click.prevent="openPathDialog"
|
||||||
|
>
|
||||||
|
{{ $t('word.change') }}
|
||||||
|
</button>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="columns export-options">
|
||||||
|
<div class="column col-8 left">
|
||||||
|
<div class="columns mb-2">
|
||||||
|
<div class="column col-auto d-flex text-italic ">
|
||||||
|
<i class="mdi mdi-file-document-outline mr-2" />
|
||||||
|
{{ filename }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column col-auto col-ml-auto ">
|
||||||
|
<button
|
||||||
|
class="btn btn-dark btn-sm"
|
||||||
|
:title="$t('word.refresh')"
|
||||||
|
@click="refresh"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-database-refresh" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-dark btn-sm"
|
||||||
|
:title="$t('message.uncheckAllTables')"
|
||||||
|
:disabled="isRefreshing"
|
||||||
|
@click="uncheckAllTables"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-file-tree-outline" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-dark btn-sm"
|
||||||
|
:title="$t('message.checkAllTables')"
|
||||||
|
:disabled="isRefreshing"
|
||||||
|
@click="checkAllTables"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-file-tree" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="workspace-query-results">
|
||||||
|
<div ref="table" class="table table-hover">
|
||||||
|
<div class="thead">
|
||||||
|
<div class="tr text-center">
|
||||||
|
<div class="th no-border" style="width: 50%;" />
|
||||||
|
<div class="th no-border">
|
||||||
|
<label
|
||||||
|
class="form-checkbox m-0 px-2 form-inline"
|
||||||
|
@click.prevent="toggleAllTablesOption('includeStructure')"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:indeterminate.prop="includeStructureStatus === 2"
|
||||||
|
:checked.prop="!!includeStructureStatus"
|
||||||
|
>
|
||||||
|
<i class="form-icon" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="th no-border">
|
||||||
|
<label
|
||||||
|
class="form-checkbox m-0 px-2 form-inline"
|
||||||
|
@click.prevent="toggleAllTablesOption('includeContent')"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:indeterminate.prop="includeContentStatus === 2"
|
||||||
|
:checked.prop="!!includeContentStatus"
|
||||||
|
>
|
||||||
|
<i class="form-icon" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="th no-border">
|
||||||
|
<label
|
||||||
|
class="form-checkbox m-0 px-2 form-inline"
|
||||||
|
@click.prevent="toggleAllTablesOption('includeDropStatement')"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:indeterminate.prop="includeDropStatementStatus === 2"
|
||||||
|
:checked.prop="!!includeDropStatementStatus"
|
||||||
|
>
|
||||||
|
<i class="form-icon" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tr">
|
||||||
|
<div class="th" style="width: 50%;">
|
||||||
|
<div class="table-column-title">
|
||||||
|
<span>{{ $t('word.table') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="th text-center">
|
||||||
|
<div class="table-column-title">
|
||||||
|
<span>{{ $t('word.structure') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="th text-center">
|
||||||
|
<div class="table-column-title">
|
||||||
|
<span>{{ $t('word.content') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="th text-center">
|
||||||
|
<div class="table-column-title">
|
||||||
|
<span>{{ $t('word.drop') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tbody">
|
||||||
|
<div
|
||||||
|
v-for="item in tables"
|
||||||
|
:key="item.name"
|
||||||
|
class="tr"
|
||||||
|
>
|
||||||
|
<div class="td">
|
||||||
|
{{ item.table }}
|
||||||
|
</div>
|
||||||
|
<div class="td text-center">
|
||||||
|
<label class="form-checkbox m-0 px-2 form-inline">
|
||||||
|
<input
|
||||||
|
v-model="item.includeStructure"
|
||||||
|
type="checkbox"
|
||||||
|
><i class="form-icon" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="td text-center">
|
||||||
|
<label class="form-checkbox m-0 px-2 form-inline">
|
||||||
|
<input
|
||||||
|
v-model="item.includeContent"
|
||||||
|
type="checkbox"
|
||||||
|
><i class="form-icon" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="td text-center">
|
||||||
|
<label class="form-checkbox m-0 px-2 form-inline">
|
||||||
|
<input
|
||||||
|
v-model="item.includeDropStatement"
|
||||||
|
type="checkbox"
|
||||||
|
><i class="form-icon" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column col-4">
|
||||||
|
<h4>
|
||||||
|
{{ $t('word.options') }}
|
||||||
|
</h4>
|
||||||
|
<span>{{ $t('word.includes') }}:</span>
|
||||||
|
|
||||||
|
<label
|
||||||
|
v-for="(_, key) in options.includes"
|
||||||
|
:key="key"
|
||||||
|
class="form-checkbox"
|
||||||
|
>
|
||||||
|
<input v-model="options.includes[key]" type="checkbox"><i class="form-icon" /> {{ $t(`word.${key}`) }}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="mt-4 mb-2">
|
||||||
|
{{ $t('message.newInserStmtEvery') }}:
|
||||||
|
</div>
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column col-6">
|
||||||
|
<input
|
||||||
|
v-model.number="options.sqlInsertAfter"
|
||||||
|
type="number"
|
||||||
|
class="form-input"
|
||||||
|
value="250"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="column col-6">
|
||||||
|
<select v-model="options.sqlInsertDivider" class="form-select">
|
||||||
|
<option value="bytes">
|
||||||
|
KiB
|
||||||
|
</option>
|
||||||
|
<option value="rows">
|
||||||
|
{{ $tc('word.row', 2) }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer columns">
|
||||||
|
<div class="column col modal-progress-wrapper text-left">
|
||||||
|
<div v-if="progressPercentage > 0" class="export-progress">
|
||||||
|
<span class="progress-status">
|
||||||
|
{{ progressPercentage }}% - {{ progressStatus }}
|
||||||
|
</span>
|
||||||
|
<progress
|
||||||
|
class="progress d-block"
|
||||||
|
:value="progressPercentage"
|
||||||
|
max="100"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column col-auto px-0">
|
||||||
|
<button class="btn btn-link" @click.stop="closeModal">
|
||||||
|
{{ $t('word.close') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-primary mr-2"
|
||||||
|
:class="{'loading': isExporting}"
|
||||||
|
:disabled="isExporting || isRefreshing"
|
||||||
|
autofocus
|
||||||
|
@click.prevent="startExport"
|
||||||
|
>
|
||||||
|
{{ $t('word.export') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { ipcRenderer } from 'electron';
|
||||||
|
import { mapActions, mapGetters } from 'vuex';
|
||||||
|
import moment from 'moment';
|
||||||
|
import customizations from 'common/customizations';
|
||||||
|
import Application from '@/ipc-api/Application';
|
||||||
|
import Schema from '@/ipc-api/Schema';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ModalExportSchema',
|
||||||
|
|
||||||
|
props: {
|
||||||
|
selectedSchema: String
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
isExporting: false,
|
||||||
|
isRefreshing: false,
|
||||||
|
progressPercentage: 0,
|
||||||
|
progressStatus: '',
|
||||||
|
tables: [],
|
||||||
|
options: {
|
||||||
|
includes: {},
|
||||||
|
sqlInsertAfter: 250,
|
||||||
|
sqlInsertDivider: 'bytes'
|
||||||
|
},
|
||||||
|
basePath: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
selectedWorkspace: 'workspaces/getSelected',
|
||||||
|
getWorkspace: 'workspaces/getWorkspace',
|
||||||
|
getDatabaseVariable: 'workspaces/getDatabaseVariable'
|
||||||
|
}),
|
||||||
|
currentWorkspace () {
|
||||||
|
return this.getWorkspace(this.selectedWorkspace);
|
||||||
|
},
|
||||||
|
schemaItems () {
|
||||||
|
const db = this.currentWorkspace.structure.find(db => db.name === this.selectedSchema);
|
||||||
|
if (db)
|
||||||
|
return db.tables.filter(table => table.type === 'table');
|
||||||
|
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
filename () {
|
||||||
|
const date = moment().format('YYYY-MM-DD');
|
||||||
|
return `${this.selectedSchema}_${date}.sql`;
|
||||||
|
},
|
||||||
|
dumpFilePath () {
|
||||||
|
return `${this.basePath}/${this.filename}`;
|
||||||
|
},
|
||||||
|
includeStructureStatus () {
|
||||||
|
if (this.tables.every(item => item.includeStructure)) return 1;
|
||||||
|
else if (this.tables.some(item => item.includeStructure)) return 2;
|
||||||
|
else return 0;
|
||||||
|
},
|
||||||
|
includeContentStatus () {
|
||||||
|
if (this.tables.every(item => item.includeContent)) return 1;
|
||||||
|
else if (this.tables.some(item => item.includeContent)) return 2;
|
||||||
|
else return 0;
|
||||||
|
},
|
||||||
|
includeDropStatementStatus () {
|
||||||
|
if (this.tables.every(item => item.includeDropStatement)) return 1;
|
||||||
|
else if (this.tables.some(item => item.includeDropStatement)) return 2;
|
||||||
|
else return 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async created () {
|
||||||
|
if (!this.schemaItems.length) await this.refresh();
|
||||||
|
|
||||||
|
window.addEventListener('keydown', this.onKey);
|
||||||
|
|
||||||
|
this.basePath = await Application.getDownloadPathDirectory();
|
||||||
|
this.tables = this.schemaItems.map(item => ({
|
||||||
|
table: item.name,
|
||||||
|
includeStructure: true,
|
||||||
|
includeContent: true,
|
||||||
|
includeDropStatement: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
const structure = ['views', 'triggers', 'routines', 'functions', 'schedulers', 'triggerFunctions'];
|
||||||
|
|
||||||
|
structure.forEach(feat => {
|
||||||
|
const val = customizations[this.currentWorkspace.client][feat];
|
||||||
|
if (val)
|
||||||
|
this.$set(this.options.includes, feat, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('export-progress', this.updateProgress);
|
||||||
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
window.removeEventListener('keydown', this.onKey);
|
||||||
|
ipcRenderer.off('export-progress', this.updateProgress);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions({
|
||||||
|
addNotification: 'notifications/addNotification',
|
||||||
|
refreshSchema: 'workspaces/refreshSchema'
|
||||||
|
}),
|
||||||
|
async startExport () {
|
||||||
|
this.isExporting = true;
|
||||||
|
const { uid, client } = this.currentWorkspace;
|
||||||
|
const params = {
|
||||||
|
uid,
|
||||||
|
type: client,
|
||||||
|
schema: this.selectedSchema,
|
||||||
|
outputFile: this.dumpFilePath,
|
||||||
|
tables: [...this.tables],
|
||||||
|
...this.options
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { status, response } = await Schema.export(params);
|
||||||
|
if (status === 'success')
|
||||||
|
this.progressStatus = response.cancelled ? this.$t('word.aborted') : this.$t('word.completed');
|
||||||
|
else {
|
||||||
|
this.progressStatus = response;
|
||||||
|
this.addNotification({ status: 'error', message: response });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isExporting = false;
|
||||||
|
},
|
||||||
|
updateProgress (event, state) {
|
||||||
|
this.progressPercentage = Number((state.currentItemIndex / state.totalItems * 100).toFixed(1));
|
||||||
|
switch (state.op) {
|
||||||
|
case 'PROCESSING':
|
||||||
|
this.progressStatus = this.$t('message.processingTableExport', { table: state.currentItem });
|
||||||
|
break;
|
||||||
|
case 'FETCH':
|
||||||
|
this.progressStatus = this.$t('message.fechingTableExport', { table: state.currentItem });
|
||||||
|
break;
|
||||||
|
case 'WRITE':
|
||||||
|
this.progressStatus = this.$t('message.writingTableExport', { table: state.currentItem });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async closeModal () {
|
||||||
|
let willClose = true;
|
||||||
|
if (this.isExporting) {
|
||||||
|
willClose = false;
|
||||||
|
const { response } = await Schema.abortExport();
|
||||||
|
willClose = response.willAbort;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (willClose)
|
||||||
|
this.$emit('close');
|
||||||
|
},
|
||||||
|
onKey (e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (e.key === 'Escape')
|
||||||
|
this.closeModal();
|
||||||
|
},
|
||||||
|
checkAllTables () {
|
||||||
|
this.tables = this.tables.map(item => ({ ...item, includeStructure: true, includeContent: true, includeDropStatement: true }));
|
||||||
|
},
|
||||||
|
uncheckAllTables () {
|
||||||
|
this.tables = this.tables.map(item => ({ ...item, includeStructure: false, includeContent: false, includeDropStatement: false }));
|
||||||
|
},
|
||||||
|
toggleAllTablesOption (option) {
|
||||||
|
const options = ['includeStructure', 'includeContent', 'includeDropStatement'];
|
||||||
|
if (!options.includes(option)) return;
|
||||||
|
|
||||||
|
if (this[`${option}Status`] !== 1)
|
||||||
|
this.tables = this.tables.map(item => ({ ...item, [option]: true }));
|
||||||
|
else
|
||||||
|
this.tables = this.tables.map(item => ({ ...item, [option]: false }));
|
||||||
|
},
|
||||||
|
async refresh () {
|
||||||
|
this.isRefreshing = true;
|
||||||
|
await this.refreshSchema({ uid: this.currentWorkspace.uid, schema: this.selectedSchema });
|
||||||
|
this.isRefreshing = false;
|
||||||
|
},
|
||||||
|
async openPathDialog () {
|
||||||
|
const result = await Application.showOpenDialog({ properties: ['openDirectory'] });
|
||||||
|
if (result && !result.canceled)
|
||||||
|
this.basePath = result.filePaths[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.export-options {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-query-results {
|
||||||
|
flex: 1 0 1px;
|
||||||
|
.table {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-checkbox {
|
||||||
|
min-height: 0.8rem;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.form-icon {
|
||||||
|
top: 0.1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
|
||||||
|
.modal-container {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
max-height: 60vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-status {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
181
src/renderer/components/ModalImportSchema.vue
Normal file
181
src/renderer/components/ModalImportSchema.vue
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
<template>
|
||||||
|
<div class="modal active">
|
||||||
|
<a class="modal-overlay" @click.stop="closeModal" />
|
||||||
|
<div class="modal-container p-0">
|
||||||
|
<div class="modal-header pl-2">
|
||||||
|
<div class="modal-title h6">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="mdi mdi-24px mdi-database-arrow-up mr-1" />
|
||||||
|
<span class="cut-text">{{ $t('message.importSchema') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||||
|
</div>
|
||||||
|
<div class="modal-body pb-0">
|
||||||
|
{{ sqlFile }}
|
||||||
|
<div v-if="queryErrors.length > 0" class="mt-2">
|
||||||
|
<label>{{ $tc('message.importQueryErrors', queryErrors.length) }}</label>
|
||||||
|
<textarea
|
||||||
|
v-model="formattedQueryErrors"
|
||||||
|
class="form-input"
|
||||||
|
rows="5"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer columns">
|
||||||
|
<div class="column col modal-progress-wrapper text-left">
|
||||||
|
<div class="import-progress">
|
||||||
|
<span class="progress-status">
|
||||||
|
{{ progressPercentage }}% - {{ progressStatus }} - {{ $tc('message.executedQueries', queryCount) }}
|
||||||
|
</span>
|
||||||
|
<progress
|
||||||
|
class="progress d-block"
|
||||||
|
:value="progressPercentage"
|
||||||
|
max="100"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column col-auto px-0">
|
||||||
|
<button class="btn btn-link" @click.stop="closeModal">
|
||||||
|
{{ completed ? $t('word.close') : $t('word.cancel') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { ipcRenderer } from 'electron';
|
||||||
|
import { mapActions, mapGetters } from 'vuex';
|
||||||
|
import moment from 'moment';
|
||||||
|
import Schema from '@/ipc-api/Schema';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ModalImportSchema',
|
||||||
|
|
||||||
|
props: {
|
||||||
|
selectedSchema: String
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
sqlFile: '',
|
||||||
|
isImporting: false,
|
||||||
|
progressPercentage: 0,
|
||||||
|
queryCount: 0,
|
||||||
|
completed: false,
|
||||||
|
progressStatus: 'Reading',
|
||||||
|
queryErrors: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
selectedWorkspace: 'workspaces/getSelected',
|
||||||
|
getWorkspace: 'workspaces/getWorkspace'
|
||||||
|
}),
|
||||||
|
currentWorkspace () {
|
||||||
|
return this.getWorkspace(this.selectedWorkspace);
|
||||||
|
},
|
||||||
|
formattedQueryErrors () {
|
||||||
|
return this.queryErrors.map(err =>
|
||||||
|
`Time: ${moment(err.time).format('HH:mm:ss.S')} (${err.time})\nError: ${err.message}`
|
||||||
|
).join('\n\n');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async created () {
|
||||||
|
window.addEventListener('keydown', this.onKey);
|
||||||
|
|
||||||
|
ipcRenderer.on('import-progress', this.updateProgress);
|
||||||
|
ipcRenderer.on('query-error', this.handleQueryError);
|
||||||
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
window.removeEventListener('keydown', this.onKey);
|
||||||
|
ipcRenderer.off('import-progress', this.updateProgress);
|
||||||
|
ipcRenderer.off('query-error', this.handleQueryError);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions({
|
||||||
|
addNotification: 'notifications/addNotification',
|
||||||
|
refreshSchema: 'workspaces/refreshSchema'
|
||||||
|
}),
|
||||||
|
async startImport (sqlFile) {
|
||||||
|
this.isImporting = true;
|
||||||
|
this.sqlFile = sqlFile;
|
||||||
|
|
||||||
|
const { uid, client } = this.currentWorkspace;
|
||||||
|
const params = {
|
||||||
|
uid,
|
||||||
|
type: client,
|
||||||
|
schema: this.selectedSchema,
|
||||||
|
file: sqlFile
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.completed = false;
|
||||||
|
const { status, response } = await Schema.import(params);
|
||||||
|
if (status === 'success')
|
||||||
|
this.progressStatus = response.cancelled ? this.$t('word.aborted') : this.$t('word.completed');
|
||||||
|
else {
|
||||||
|
this.progressStatus = response;
|
||||||
|
this.addNotification({ status: 'error', message: response });
|
||||||
|
}
|
||||||
|
this.refreshSchema({ uid, schema: this.selectedSchema });
|
||||||
|
this.completed = true;
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isImporting = false;
|
||||||
|
},
|
||||||
|
updateProgress (event, state) {
|
||||||
|
this.progressPercentage = Number(state.percentage).toFixed(1);
|
||||||
|
this.queryCount = Number(state.queryCount);
|
||||||
|
},
|
||||||
|
handleQueryError (event, err) {
|
||||||
|
this.queryErrors.push(err);
|
||||||
|
},
|
||||||
|
async closeModal () {
|
||||||
|
let willClose = true;
|
||||||
|
if (this.isImporting) {
|
||||||
|
willClose = false;
|
||||||
|
const { response } = await Schema.abortImport();
|
||||||
|
willClose = response.willAbort;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (willClose)
|
||||||
|
this.$emit('close');
|
||||||
|
},
|
||||||
|
onKey (e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (e.key === 'Escape')
|
||||||
|
this.closeModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.modal {
|
||||||
|
|
||||||
|
.modal-container {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
max-height: 60vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-status {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
</style>
|
@ -58,6 +58,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="workspace.customizations.schemaExport"
|
||||||
|
class="context-element"
|
||||||
|
@click="showExportSchemaModal"
|
||||||
|
>
|
||||||
|
<span class="d-flex"><i class="mdi mdi-18px mdi-database-arrow-down text-light pr-1" /> {{ $t('word.export') }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="workspace.customizations.schemaImport"
|
||||||
|
class="context-element"
|
||||||
|
@click="initImport"
|
||||||
|
>
|
||||||
|
<span class="d-flex"><i class="mdi mdi-18px mdi-database-arrow-up text-light pr-1" /> {{ $t('word.import') }}</span>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="workspace.customizations.schemaEdit"
|
v-if="workspace.customizations.schemaEdit"
|
||||||
class="context-element"
|
class="context-element"
|
||||||
@ -95,6 +109,17 @@
|
|||||||
:selected-schema="selectedSchema"
|
:selected-schema="selectedSchema"
|
||||||
@close="hideEditModal"
|
@close="hideEditModal"
|
||||||
/>
|
/>
|
||||||
|
<ModalExportSchema
|
||||||
|
v-if="isExportSchemaModal"
|
||||||
|
:selected-schema="selectedSchema"
|
||||||
|
@close="hideExportSchemaModal"
|
||||||
|
/>
|
||||||
|
<ModalImportSchema
|
||||||
|
v-if="isImportSchemaModal"
|
||||||
|
ref="importModalRef"
|
||||||
|
:selected-schema="selectedSchema"
|
||||||
|
@close="hideImportSchemaModal"
|
||||||
|
/>
|
||||||
</BaseContextMenu>
|
</BaseContextMenu>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -103,14 +128,19 @@ import { mapGetters, mapActions } from 'vuex';
|
|||||||
import BaseContextMenu from '@/components/BaseContextMenu';
|
import BaseContextMenu from '@/components/BaseContextMenu';
|
||||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||||
import ModalEditSchema from '@/components/ModalEditSchema';
|
import ModalEditSchema from '@/components/ModalEditSchema';
|
||||||
|
import ModalExportSchema from '@/components/ModalExportSchema';
|
||||||
|
import ModalImportSchema from '@/components/ModalImportSchema';
|
||||||
import Schema from '@/ipc-api/Schema';
|
import Schema from '@/ipc-api/Schema';
|
||||||
|
import Application from '@/ipc-api/Application';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'WorkspaceExploreBarSchemaContext',
|
name: 'WorkspaceExploreBarSchemaContext',
|
||||||
components: {
|
components: {
|
||||||
BaseContextMenu,
|
BaseContextMenu,
|
||||||
ConfirmModal,
|
ConfirmModal,
|
||||||
ModalEditSchema
|
ModalEditSchema,
|
||||||
|
ModalExportSchema,
|
||||||
|
ModalImportSchema
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
contextEvent: MouseEvent,
|
contextEvent: MouseEvent,
|
||||||
@ -119,7 +149,9 @@ export default {
|
|||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isDeleteModal: false,
|
isDeleteModal: false,
|
||||||
isEditModal: false
|
isEditModal: false,
|
||||||
|
isExportSchemaModal: false,
|
||||||
|
isImportSchemaModal: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -170,6 +202,30 @@ export default {
|
|||||||
this.isEditModal = false;
|
this.isEditModal = false;
|
||||||
this.closeContext();
|
this.closeContext();
|
||||||
},
|
},
|
||||||
|
showExportSchemaModal () {
|
||||||
|
this.isExportSchemaModal = true;
|
||||||
|
},
|
||||||
|
hideExportSchemaModal () {
|
||||||
|
this.isExportSchemaModal = false;
|
||||||
|
this.closeContext();
|
||||||
|
},
|
||||||
|
showImportSchemaModal () {
|
||||||
|
this.isImportSchemaModal = true;
|
||||||
|
},
|
||||||
|
hideImportSchemaModal () {
|
||||||
|
this.isImportSchemaModal = false;
|
||||||
|
this.closeContext();
|
||||||
|
},
|
||||||
|
async initImport () {
|
||||||
|
const result = await Application.showOpenDialog({ properties: ['openFile'], filters: [{ name: 'SQL', extensions: ['sql'] }] });
|
||||||
|
if (result && !result.canceled) {
|
||||||
|
const file = result.filePaths[0];
|
||||||
|
this.showImportSchemaModal();
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.importModalRef.startImport(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
closeContext () {
|
closeContext () {
|
||||||
this.$emit('close-context');
|
this.$emit('close-context');
|
||||||
},
|
},
|
||||||
|
@ -80,6 +80,7 @@ module.exports = {
|
|||||||
deterministic: 'Deterministic',
|
deterministic: 'Deterministic',
|
||||||
context: 'Context',
|
context: 'Context',
|
||||||
export: 'Export',
|
export: 'Export',
|
||||||
|
import: 'Import',
|
||||||
returns: 'Returns',
|
returns: 'Returns',
|
||||||
timing: 'Timing',
|
timing: 'Timing',
|
||||||
state: 'State',
|
state: 'State',
|
||||||
@ -122,6 +123,16 @@ module.exports = {
|
|||||||
select: 'Select',
|
select: 'Select',
|
||||||
passphrase: 'Passphrase',
|
passphrase: 'Passphrase',
|
||||||
filter: 'Filter',
|
filter: 'Filter',
|
||||||
|
change: 'Change',
|
||||||
|
views: 'Views',
|
||||||
|
triggers: 'Triggers',
|
||||||
|
routines: 'Routines',
|
||||||
|
functions: 'Functions',
|
||||||
|
schedulers: 'Schedulers',
|
||||||
|
includes: 'Includes',
|
||||||
|
drop: 'Drop',
|
||||||
|
completed: 'Completed',
|
||||||
|
aborted: 'Aborted',
|
||||||
disabled: 'Disabled',
|
disabled: 'Disabled',
|
||||||
enable: 'Enable',
|
enable: 'Enable',
|
||||||
disable: 'Disable',
|
disable: 'Disable',
|
||||||
@ -254,6 +265,15 @@ module.exports = {
|
|||||||
searchForQueries: 'Search for queries',
|
searchForQueries: 'Search for queries',
|
||||||
killProcess: 'Kill process',
|
killProcess: 'Kill process',
|
||||||
closeTab: 'Close tab',
|
closeTab: 'Close tab',
|
||||||
|
exportSchema: 'Export schema',
|
||||||
|
importSchema: 'Import schema',
|
||||||
|
directoryPath: 'Directory path',
|
||||||
|
newInserStmtEvery: 'New INSERT statement every',
|
||||||
|
processingTableExport: 'Processing {table}',
|
||||||
|
fechingTableExport: 'Fetching {table} data',
|
||||||
|
writingTableExport: 'Writing {table} data',
|
||||||
|
checkAllTables: 'Check all tables',
|
||||||
|
uncheckAllTables: 'Uncheck all tables',
|
||||||
goToDownloadPage: 'Go to download page',
|
goToDownloadPage: 'Go to download page',
|
||||||
readOnlyMode: 'Read-only mode',
|
readOnlyMode: 'Read-only mode',
|
||||||
killQuery: 'Kill query',
|
killQuery: 'Kill query',
|
||||||
@ -261,7 +281,9 @@ module.exports = {
|
|||||||
commitMode: 'Commit mode',
|
commitMode: 'Commit mode',
|
||||||
autoCommit: 'Auto commit',
|
autoCommit: 'Auto commit',
|
||||||
manualCommit: 'Manual commit',
|
manualCommit: 'Manual commit',
|
||||||
actionSuccessful: '{action} successful'
|
actionSuccessful: '{action} successful',
|
||||||
|
importQueryErrors: 'Warning: {n} error has accurrend | Warning: {n} errors occurred',
|
||||||
|
executedQueries: '{n} query executed | {n} queries executed'
|
||||||
},
|
},
|
||||||
faker: {
|
faker: {
|
||||||
address: 'Address',
|
address: 'Address',
|
||||||
|
@ -80,6 +80,7 @@ module.exports = {
|
|||||||
deterministic: 'Deterministico',
|
deterministic: 'Deterministico',
|
||||||
context: 'Contesto',
|
context: 'Contesto',
|
||||||
export: 'Esporta',
|
export: 'Esporta',
|
||||||
|
import: 'Importa',
|
||||||
returns: 'Ritorna',
|
returns: 'Ritorna',
|
||||||
timing: 'Temporizzazione',
|
timing: 'Temporizzazione',
|
||||||
state: 'Stato',
|
state: 'Stato',
|
||||||
@ -122,6 +123,16 @@ module.exports = {
|
|||||||
select: 'Seleziona',
|
select: 'Seleziona',
|
||||||
passphrase: 'Passphrase',
|
passphrase: 'Passphrase',
|
||||||
filter: 'Filtra',
|
filter: 'Filtra',
|
||||||
|
change: 'Cambia',
|
||||||
|
views: 'Viste',
|
||||||
|
triggers: 'Trigger',
|
||||||
|
routines: 'Routine',
|
||||||
|
functions: 'Function',
|
||||||
|
schedulers: 'Scheduler',
|
||||||
|
includes: 'Includi',
|
||||||
|
drop: 'Drop',
|
||||||
|
completed: 'Completato',
|
||||||
|
aborted: 'Annullato',
|
||||||
disabled: 'Disabilitato',
|
disabled: 'Disabilitato',
|
||||||
enable: 'Abilita',
|
enable: 'Abilita',
|
||||||
disable: 'Disabilita'
|
disable: 'Disabilita'
|
||||||
@ -237,6 +248,15 @@ module.exports = {
|
|||||||
noOpenTabs: 'Non ci sono tab aperte, naviga nella barra sinistra o:',
|
noOpenTabs: 'Non ci sono tab aperte, naviga nella barra sinistra o:',
|
||||||
noSchema: 'Nessuno schema',
|
noSchema: 'Nessuno schema',
|
||||||
restorePreviourSession: 'Ripristina sessione precedente',
|
restorePreviourSession: 'Ripristina sessione precedente',
|
||||||
|
exportSchema: 'Esporta schema',
|
||||||
|
importSchema: 'Importa schema',
|
||||||
|
directoryPath: 'Percorso directory',
|
||||||
|
newInserStmtEvery: 'Nuova istruzione INSERT ogni',
|
||||||
|
processingTableExport: 'Processo {table}',
|
||||||
|
fechingTableExport: 'Ricavo i dati {table}',
|
||||||
|
writingTableExport: 'Scrittura dati {table}',
|
||||||
|
checkAllTables: 'Seleziona tutte le tabelle',
|
||||||
|
uncheckAllTables: 'Deseleziona tutte le tabelle',
|
||||||
runQuery: 'Esegui query',
|
runQuery: 'Esegui query',
|
||||||
thereAreNoTableFields: 'Non ci sono campi della tabella',
|
thereAreNoTableFields: 'Non ci sono campi della tabella',
|
||||||
newTable: 'Nuova tabella',
|
newTable: 'Nuova tabella',
|
||||||
@ -251,7 +271,9 @@ module.exports = {
|
|||||||
killProcess: 'Uccidi processo',
|
killProcess: 'Uccidi processo',
|
||||||
closeTab: 'Chiudi tab',
|
closeTab: 'Chiudi tab',
|
||||||
goToDownloadPage: 'Vai alla pagina di download',
|
goToDownloadPage: 'Vai alla pagina di download',
|
||||||
readOnlyMode: 'Modalità sola lettura'
|
readOnlyMode: 'Modalità sola lettura',
|
||||||
|
importQueryErrors: 'Attenzione: si è verificato un errore | Attenzione si sono verificati {n} errori',
|
||||||
|
executedQueries: '{n} query eseguite | {n} query eseguite'
|
||||||
},
|
},
|
||||||
faker: {
|
faker: {
|
||||||
address: 'Indirizzo',
|
address: 'Indirizzo',
|
||||||
|
@ -5,4 +5,12 @@ export default class {
|
|||||||
static getKey (params) {
|
static getKey (params) {
|
||||||
return ipcRenderer.sendSync('get-key', params);
|
return ipcRenderer.sendSync('get-key', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static showOpenDialog (options) {
|
||||||
|
return ipcRenderer.invoke('showOpenDialog', options);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDownloadPathDirectory () {
|
||||||
|
return ipcRenderer.invoke('get-download-dir-path');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,4 +69,20 @@ export default class {
|
|||||||
static rawQuery (params) {
|
static rawQuery (params) {
|
||||||
return ipcRenderer.invoke('raw-query', params);
|
return ipcRenderer.invoke('raw-query', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static export (params) {
|
||||||
|
return ipcRenderer.invoke('export', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static abortExport () {
|
||||||
|
return ipcRenderer.invoke('abort-export');
|
||||||
|
}
|
||||||
|
|
||||||
|
static import (params) {
|
||||||
|
return ipcRenderer.invoke('import-sql', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static abortImport () {
|
||||||
|
return ipcRenderer.invoke('abort-import-sql');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -165,6 +165,15 @@ option:checked {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-overlay{
|
||||||
|
background: rgba( 255, 255, 255, 0.1 );
|
||||||
|
box-shadow: 0 8px 32px 0 rgba( 31, 38, 135, 0.37 );
|
||||||
|
backdrop-filter: blur( 4px );
|
||||||
|
-webkit-backdrop-filter: blur( 4px );
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid rgba( 255, 255, 255, 0.18 );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab {
|
.tab {
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
const webpack = require('webpack');
|
||||||
const ProgressPlugin = require('progress-webpack-plugin');
|
const ProgressPlugin = require('progress-webpack-plugin');
|
||||||
|
|
||||||
const { dependencies, devDependencies } = require('./package.json');
|
const { dependencies, devDependencies, version } = require('./package.json');
|
||||||
|
|
||||||
const externals = Object.keys(dependencies).concat(Object.keys(devDependencies));
|
const externals = Object.keys(dependencies).concat(Object.keys(devDependencies));
|
||||||
const isDevMode = process.env.NODE_ENV === 'development';
|
const isDevMode = process.env.NODE_ENV === 'development';
|
||||||
const whiteListedModules = [];
|
const whiteListedModules = [];
|
||||||
|
|
||||||
module.exports = [
|
module.exports = { // Main
|
||||||
{ // Main
|
|
||||||
name: 'main',
|
name: 'main',
|
||||||
mode: process.env.NODE_ENV,
|
mode: process.env.NODE_ENV,
|
||||||
devtool: isDevMode ? 'eval-source-map' : false,
|
devtool: isDevMode ? 'eval-source-map' : false,
|
||||||
@ -42,7 +41,11 @@ module.exports = [
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new ProgressPlugin(true),
|
new ProgressPlugin(true),
|
||||||
new CleanWebpackPlugin({ root: path.join(__dirname, 'dist') })
|
new webpack.DefinePlugin({
|
||||||
|
'process.env': {
|
||||||
|
PACKAGE_VERSION: `"${version}"`
|
||||||
|
}
|
||||||
|
})
|
||||||
],
|
],
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@ -66,5 +69,4 @@ module.exports = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
];
|
|
||||||
|
81
webpack.workers.config.js
Normal file
81
webpack.workers.config.js
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const ProgressPlugin = require('progress-webpack-plugin');
|
||||||
|
|
||||||
|
const { dependencies, devDependencies, version } = require('./package.json');
|
||||||
|
|
||||||
|
const externals = Object.keys(dependencies).concat(Object.keys(devDependencies));
|
||||||
|
const isDevMode = process.env.NODE_ENV === 'development';
|
||||||
|
const whiteListedModules = [];
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
name: 'workers',
|
||||||
|
mode: process.env.NODE_ENV,
|
||||||
|
devtool: isDevMode ? 'eval-source-map' : false,
|
||||||
|
entry: {
|
||||||
|
exporter: path.join(__dirname, './src/main/workers/exporter.js'),
|
||||||
|
importer: path.join(__dirname, './src/main/workers/importer.js')
|
||||||
|
},
|
||||||
|
target: 'node',
|
||||||
|
output: {
|
||||||
|
libraryTarget: 'commonjs2',
|
||||||
|
path: path.join(__dirname, 'dist'),
|
||||||
|
filename: '[name].js'
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
global: true,
|
||||||
|
__dirname: isDevMode,
|
||||||
|
__filename: isDevMode
|
||||||
|
},
|
||||||
|
externals: externals.filter((d) => !whiteListedModules.includes(d)),
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
use: 'babel-loader',
|
||||||
|
exclude: /node_modules/
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.node$/,
|
||||||
|
use: 'node-loader'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.js', '.json'],
|
||||||
|
alias: {
|
||||||
|
src: path.join(__dirname, 'src/'),
|
||||||
|
common: path.resolve(__dirname, 'src/common')
|
||||||
|
},
|
||||||
|
fallback: {
|
||||||
|
'pg-native': false,
|
||||||
|
'cpu-features': false,
|
||||||
|
cardinal: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new ProgressPlugin(true),
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.env': {
|
||||||
|
PACKAGE_VERSION: `"${version}"`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjust rendererConfig for production settings
|
||||||
|
*/
|
||||||
|
if (isDevMode) {
|
||||||
|
// any dev only config
|
||||||
|
config.plugins.push(new webpack.HotModuleReplacementPlugin());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
config.plugins.push(
|
||||||
|
new webpack.LoaderOptionsPlugin({
|
||||||
|
minimize: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = config;
|
Loading…
x
Reference in New Issue
Block a user