1
1
mirror of https://github.com/Fabio286/antares.git synced 2025-06-05 21:59:22 +02:00

feat: initial db export implementation

This commit is contained in:
Giulio Ganci
2021-10-29 12:58:37 +02:00
parent 3679121c25
commit 0de2321920
15 changed files with 833 additions and 11 deletions

View File

@ -30,6 +30,7 @@ module.exports = {
functionAdd: true, functionAdd: true,
schedulerAdd: true, schedulerAdd: true,
schemaEdit: true, schemaEdit: true,
schemaExport: true,
tableSettings: true, tableSettings: true,
viewSettings: true, viewSettings: true,
triggerSettings: true, triggerSettings: true,

View File

@ -27,6 +27,7 @@ module.exports = {
routineAdd: true, routineAdd: true,
functionAdd: true, functionAdd: true,
databaseEdit: false, databaseEdit: false,
schemaExport: true,
tableSettings: true, tableSettings: true,
viewSettings: true, viewSettings: true,
triggerSettings: true, triggerSettings: true,

View File

@ -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');
});
}; };

View File

@ -1,7 +1,12 @@
import { ipcMain, dialog, Notification } from 'electron';
import path from 'path';
import fs from 'fs';
import { ipcMain } from 'electron'; import MysqlExporter from '../libs/exporters/sql/MysqlExporter';
export default connections => { export default connections => {
let exporter = 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 +42,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 +60,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 };
} }
@ -152,4 +166,98 @@ export default connections => {
return { status: 'error', response: err.toString() }; return { status: 'error', response: err.toString() };
} }
}); });
ipcMain.handle('export', async (event, { uid, ...rest }) => {
if (exporter !== null) return;
const type = connections[uid]._client;
switch (type) {
case 'mysql':
exporter = new MysqlExporter(connections[uid], rest);
break;
default:
return {
status: 'error',
response: `${type} exporter not aviable`
};
}
const outputFileName = path.basename(rest.outputFile);
if (fs.existsSync(rest.outputFile)) {
const result = await dialog.showMessageBox({
type: 'warning',
message: `File ${outputFileName} 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) {
exporter = null;
return { status: 'error', response: 'Operation aborted' };
}
}
return new Promise((resolve, reject) => {
exporter.once('error', err => {
reject(err);
});
exporter.once('end', () => {
resolve({ cancelled: exporter.isCancelled });
});
exporter.on('progress', state => {
event.sender.send('export-progress', state);
});
exporter.run();
})
.then(response => {
if (!response.cancelled) {
new Notification({
title: 'Export finished',
body: `Finished exporting to ${outputFileName}`
}).show();
}
return { status: 'success', response };
})
.catch(err => {
new Notification({
title: 'Export error',
body: err.toString()
}).show();
return { status: 'error', response: err.toString() };
})
.finally(() => {
exporter.removeAllListeners();
exporter = null;
});
});
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.cancel();
}
}
return { status: 'success', response: { willAbort } };
});
}; };

View File

@ -0,0 +1,73 @@
import fs from 'fs';
import path from 'path';
import EventEmitter from 'events';
export class BaseExporter extends EventEmitter {
constructor (options) {
super();
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;
}
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');
}
}

View File

@ -0,0 +1,35 @@
import { MysqlExporter } from './sql/MysqlExporter';
export class ExporterFactory {
/**
* Returns a data exporter class instance.
*
* @param {Object} args
* @param {String} args.client
* @param {Object} args.params
* @param {String} args.params.host
* @param {Number} args.params.port
* @param {String} args.params.password
* @param {String=} args.params.database
* @param {String=} args.params.schema
* @param {String} args.params.ssh.host
* @param {String} args.params.ssh.username
* @param {String} args.params.ssh.password
* @param {Number} args.params.ssh.port
* @param {Number=} args.poolSize
* @returns Exporter Instance
* @memberof ExporterFactory
*/
static get (args) {
switch (type) {
case 'mysql':
exporter = new MysqlExporter(connections[uid], rest);
break;
default:
return {
status: 'error',
response: `${type} exporter not aviable`
};
}
}
}

View File

@ -0,0 +1,108 @@
import { SqlExporter } from './SqlExporter';
import { BLOB, BIT } from 'common/fieldTypes';
import hexToBinary from 'common/libs/hexToBinary';
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='NO_AUTO_VALUE_ON_ZERO', SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;`;
return dump;
}
async 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 */;`;
}
async getCreateTable (tableName) {
const { rows } = await this._client.raw(`SHOW CREATE TABLE \`${this.schemaName}\`.\`${tableName}\``);
if (rows.length !== 1)
return '';
return rows[0]['Create Table'] + ';';
}
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) {
const columns = await this._client.getTableColumns({ table: tableName, schema: this.schemaName });
const columnNames = columns.map(col => '`' + col.name + '`');
const insertStmt = `INSERT INTO \`${tableName}\` (${columnNames.join(', ')}) VALUES`;
const tableResult = await this._client.raw(`SELECT ${columnNames.join(', ')} FROM \`${this.schemaName}\`.\`${tableName}\``);
sqlStr += `LOCK TABLES \`${tableName}\` WRITE;\n`;
sqlStr += `/*!40000 ALTER TABLE \`${tableName}\` DISABLE KEYS */;`;
sqlStr += '\n\n';
sqlStr += insertStmt;
sqlStr += '\n';
for (const row of tableResult.rows) {
sqlStr += '\t(';
for (const i in columns) {
const column = columns[i];
const val = row[column.name];
if (val === null)
sqlStr += 'NULL';
else if (BIT.includes(column.type))
sqlStr += `b'${hexToBinary(Buffer.from(val).toString('hex'))}'`;
else if (BLOB.includes(column.type))
sqlStr += `X'${val.toString('hex').toUpperCase()}'`;
else if (val === '')
sqlStr += '\'\'';
else
sqlStr += typeof val === 'string' ? this.escapeAndQuote(val) : val;
if (parseInt(i) !== columns.length - 1)
sqlStr += ', ';
}
sqlStr += '),\n';
}
sqlStr += '\n';
sqlStr += `/*!40000 ALTER TABLE \`${tableName}\` ENABLE KEYS */;\n`;
sqlStr += 'UNLOCK TABLES;';
}
return sqlStr;
}
escapeAndQuote (value) {
if (!value) return null;
return `'${value.replaceAll(/'/g, '\'\'')}'`;
}
}

View File

@ -0,0 +1,122 @@
import { app } from 'electron';
import moment from 'moment';
import { BaseExporter } from '../BaseExporter';
export class SqlExporter extends BaseExporter {
constructor (client, options) {
super(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 exportState = {
totalItems: this._options.items.length,
currentItemIndex: 0,
currentItem: '',
op: ''
};
const header = await this.getSqlHeader();
this.writeString(header);
this.writeString('\n\n\n');
for (const item of this._options.items) {
// 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 = 'PROCESSING';
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 = 'FETCH';
this.emitUpdate(exportState);
const tableInsertSyntax = await this.getTableInsert(item.table);
exportState.op = 'WRITE';
this.emitUpdate(exportState);
this.writeString(tableInsertSyntax);
this.writeString('\n\n');
}
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 ${app.getVersion()}
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 '';
}
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');
}
}

View File

@ -0,0 +1,19 @@
import { Worker, isMainThread, workerData, parentPort } from 'worker_threads';
import
if (isMainThread) {
module.exports = function run (workerData) {
return new Promise((resolve, reject) => {
const worker = new Worker(__filename, { workerData });
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0)
reject(new Error(`Worker stopped with exit code ${code}`));
});
});
};
}
else {
}

View File

@ -0,0 +1,304 @@
<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 class="columns mb-2">
<div class="column col-auto d-flex p-0 text-italic ">
<i class="mdi mdi-file-document-outline mr-2" />
{{ filename }}
</div>
<div class="column col-auto col-ml-auto p-0">
<button class="btn btn-dark btn-sm" @click="uncheckAllTables">
<i class="mdi mdi-file-tree-outline" />
</button>
<button class="btn btn-dark btn-sm" @click="checkAllTables">
<i class="mdi mdi-file-tree" />
</button>
</div>
</div>
</div>
<div class="workspace-query-results">
<div ref="table" class="table table-hover">
<div class="thead">
<div class="tr">
<div class="th c-hand" style="width: 50%;">
<div class="table-column-title">
<span>Table</span>
</div>
</div>
<div class="th c-hand">
<div class="table-column-title">
<span>Structure</span>
</div>
</div>
<div class="th c-hand">
<div class="table-column-title">
<span>Content</span>
</div>
</div>
<div class="th c-hand">
<div class="table-column-title">
<span>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">
<label class="form-checkbox m-0 px-2">
<input
v-model="item.includeStructure"
type="checkbox"
><i class="form-icon" />
</label>
</div>
<div class="td">
<label class="form-checkbox m-0 px-2">
<input
v-model="item.includeContent"
type="checkbox"
><i class="form-icon" />
</label>
</div>
<div class="td">
<label class="form-checkbox m-0 px-2">
<input
v-model="item.includeDropStatement"
type="checkbox"
><i class="form-icon" />
</label>
</div>
</div>
</div>
</div>
</div>
<div v-if="progressPercentage > 0">
<progress
class="progress"
:value="progressPercentage"
max="100"
/>
<p class="empty-subtitle">
{{ progressPercentage }}% - {{ progressStatus }}
</p>
</div>
</div>
<div class="modal-footer">
<button
class="btn btn-primary mr-2"
:class="{'loading': isExporting}"
:disabled="isExporting"
@click.stop="startExport"
>
{{ $t('word.export') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }}
</button>
</div>
</div>
</div>
</template>
<script>
import { ipcRenderer } from 'electron';
import { mapActions, mapGetters } from 'vuex';
import moment from 'moment';
import Application from '@/ipc-api/Application';
import Schema from '@/ipc-api/Schema';
export default {
name: 'ModalExportSchema',
props: {
selectedSchema: String
},
data () {
return {
isExporting: false,
progressPercentage: 0,
progressStatus: '',
tables: [],
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}`;
}
},
async created () {
await this.refreshSchema({ uid: this.currentWorkspace.uid, schema: this.selectedSchema });
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
}));
ipcRenderer.on('export-progress', this.updateProgress);
},
beforeDestroy () {
window.removeEventListener('keydown', this.onKey);
ipcRenderer.off('export-progress', this.updateProgress);
},
methods: {
...mapActions({
refreshSchema: 'workspaces/refreshSchema'
}),
async startExport () {
this.isExporting = true;
const { uid } = this.currentWorkspace;
const params = {
uid,
schema: this.selectedSchema,
outputFile: this.dumpFilePath,
items: [...this.tables]
};
const result = await Schema.export(params);
if (result) {
if (result.status === 'success')
this.progressStatus = result.response.cancelled ? 'Aborted' : 'Completed!';
else
this.progressStatus = result.response;
}
this.isExporting = false;
},
updateProgress (event, state) {
this.progressPercentage = Number((state.currentItemIndex / state.totalItems * 100).toFixed(1));
this.progressStatus = state.op + ' ' + state.currentItem;
},
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 => ({ table: item.table, includeStructure: true, includeContent: true, includeDropStatement: true }));
},
uncheckAllTables () {
this.tables = this.tables.map(item => ({ table: item.table, includeStructure: false, includeContent: false, includeDropStatement: false }));
},
async openPathDialog () {
const result = await Application.showOpenDialog({ properties: ['openDirectory'] });
if (result && !result.canceled)
this.basePath = result.filePaths[0];
}
}
};
</script>
<style lang="scss" scoped>
.workspace-query-results {
flex: 1 1 auto;
.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 {
height: 60vh;
display: flex;
flex-direction: column;
}
}
}
</style>

View File

@ -58,6 +58,13 @@
</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 <div
v-if="workspace.customizations.schemaEdit" v-if="workspace.customizations.schemaEdit"
class="context-element" class="context-element"
@ -91,6 +98,11 @@
:selected-schema="selectedSchema" :selected-schema="selectedSchema"
@close="hideEditModal" @close="hideEditModal"
/> />
<ModalExportSchema
v-if="isExportSchemaModal"
:selected-schema="selectedSchema"
@close="hideExportSchemaModal"
/>
</BaseContextMenu> </BaseContextMenu>
</template> </template>
@ -99,6 +111,7 @@ 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 Schema from '@/ipc-api/Schema'; import Schema from '@/ipc-api/Schema';
export default { export default {
@ -106,7 +119,8 @@ export default {
components: { components: {
BaseContextMenu, BaseContextMenu,
ConfirmModal, ConfirmModal,
ModalEditSchema ModalEditSchema,
ModalExportSchema
}, },
props: { props: {
contextEvent: MouseEvent, contextEvent: MouseEvent,
@ -115,7 +129,8 @@ export default {
data () { data () {
return { return {
isDeleteModal: false, isDeleteModal: false,
isEditModal: false isEditModal: false,
isExportSchemaModal: false
}; };
}, },
computed: { computed: {
@ -166,6 +181,12 @@ export default {
this.isEditModal = false; this.isEditModal = false;
this.closeContext(); this.closeContext();
}, },
showExportSchemaModal () {
this.isExportSchemaModal = true;
},
hideExportSchemaModal () {
this.isExportSchemaModal = false;
},
closeContext () { closeContext () {
this.$emit('close-context'); this.$emit('close-context');
}, },

View File

@ -121,7 +121,8 @@ module.exports = {
history: 'History', history: 'History',
select: 'Select', select: 'Select',
passphrase: 'Passphrase', passphrase: 'Passphrase',
filter: 'Filter' filter: 'Filter',
change: 'Change'
}, },
message: { message: {
appWelcome: 'Welcome to Antares SQL Client!', appWelcome: 'Welcome to Antares SQL Client!',
@ -246,7 +247,9 @@ module.exports = {
thereIsNoQueriesYet: 'There is no queries yet', thereIsNoQueriesYet: 'There is no queries yet',
searchForQueries: 'Search for queries', searchForQueries: 'Search for queries',
killProcess: 'Kill process', killProcess: 'Kill process',
closeTab: 'Close tab' closeTab: 'Close tab',
exportSchema: 'Export schema',
directoryPath: 'Directory path'
}, },
faker: { faker: {
address: 'Address', address: 'Address',

View File

@ -121,7 +121,8 @@ module.exports = {
history: 'Cronologia', history: 'Cronologia',
select: 'Seleziona', select: 'Seleziona',
passphrase: 'Passphrase', passphrase: 'Passphrase',
filter: 'Filtra' filter: 'Filtra',
change: 'Cambia'
}, },
message: { message: {
appWelcome: 'Benvenuto in Antares SQL Client!', appWelcome: 'Benvenuto in Antares SQL Client!',
@ -233,7 +234,9 @@ module.exports = {
duplicateTable: 'Duplica tabella', duplicateTable: 'Duplica tabella',
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',
directoryPath: 'Percorso directory'
}, },
faker: { faker: {
address: 'Indirizzo', address: 'Indirizzo',

View File

@ -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');
}
} }

View File

@ -53,4 +53,12 @@ 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');
}
} }