mirror of
https://github.com/Fabio286/antares.git
synced 2025-06-05 21:59:22 +02:00
Compare commits
18 Commits
Author | SHA1 | Date | |
---|---|---|---|
cc941dfc04 | |||
1d151e9349 | |||
addd9fba28 | |||
a00c19d300 | |||
9551afbd2d | |||
1ead76c028 | |||
d3da15aa13 | |||
f3b5de38c4 | |||
|
d4b6d2e9d1 | ||
|
e2c106e4e0 | ||
eb60899e6e | |||
1d367d468d | |||
8ecaedbf6c | |||
|
dd1eebd4ec | ||
8c83b3f144 | |||
985e5d3527 | |||
78902639eb | |||
cb038b374a |
22
CHANGELOG.md
22
CHANGELOG.md
@@ -2,6 +2,28 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||||
|
|
||||||
|
### [0.5.12](https://github.com/antares-sql/antares/compare/v0.5.11...v0.5.12) (2022-07-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* ability to copy multiple selected rows ([9551afb](https://github.com/antares-sql/antares/commit/9551afbd2d7e525c81f28e98e788b92609ce9de4))
|
||||||
|
* context menu option to duplicate a table row ([985e5d3](https://github.com/antares-sql/antares/commit/985e5d352793d1b3e1981d004b6f494bfbb049bf))
|
||||||
|
* copy row as SQL INSERT ([d3da15a](https://github.com/antares-sql/antares/commit/d3da15aa1377dcba73927047563f1d0c2d1284ca))
|
||||||
|
* execute selected query ([7890263](https://github.com/antares-sql/antares/commit/78902639ebb29a8c53f8aa0d2045c74e0646febc))
|
||||||
|
* export table content as SQL INSERT ([f3b5de3](https://github.com/antares-sql/antares/commit/f3b5de38c4abfd2c1d738e179fc22e6c8b6f9080))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* disable ctrl+alt+(left/right) shortcut on linux ([8ecaedb](https://github.com/antares-sql/antares/commit/8ecaedbf6c2fc0dc56ff2177a87dd6ede74bdd22))
|
||||||
|
* error on schema export ([1d151e9](https://github.com/antares-sql/antares/commit/1d151e9349fd97576ccd8ef7f88ca789a1f28b65))
|
||||||
|
* issue with logger on import/export ([cb038b3](https://github.com/antares-sql/antares/commit/cb038b374a4fe85ad569e42eee7af123c925e775))
|
||||||
|
* missing defaults on insert row window ([1ead76c](https://github.com/antares-sql/antares/commit/1ead76c02889f48bd91cae702820b082ca2ff54b))
|
||||||
|
* missing table on insert new records on session restored tabs ([8c83b3f](https://github.com/antares-sql/antares/commit/8c83b3f1447354ec63b2a308db05ad4d54659aa7))
|
||||||
|
* **MySQL:** missing quoted identifier for column names in table filter, closes [#380](https://github.com/antares-sql/antares/issues/380) ([eb60899](https://github.com/antares-sql/antares/commit/eb60899e6e17879c79a7ee7108061e9aca8596f7))
|
||||||
|
* prevent ctrl+a in console ([a00c19d](https://github.com/antares-sql/antares/commit/a00c19d3003cd43d3ee6e3132728122bb2b24c97))
|
||||||
|
|
||||||
### [0.5.11](https://github.com/antares-sql/antares/compare/v0.5.10...v0.5.11) (2022-07-19)
|
### [0.5.11](https://github.com/antares-sql/antares/compare/v0.5.10...v0.5.11) (2022-07-19)
|
||||||
|
|
||||||
|
|
||||||
|
10
README.md
10
README.md
@@ -13,7 +13,7 @@ Antares is an SQL client based on [Electron.js](https://github.com/electron/elec
|
|||||||
Our target is to support as many databases as possible, and all major operating systems, including the ARM versions.
|
Our target is to support as many databases as possible, and all major operating systems, including the ARM versions.
|
||||||
|
|
||||||
**At the moment this application is in development state, many features will come in future updates**, and supports only MySQL/MariaDB, PostgreSQL and SQLite.
|
**At the moment this application is in development state, many features will come in future updates**, and supports only MySQL/MariaDB, PostgreSQL and SQLite.
|
||||||
At the moment, however, there are all the features necessary to have a pleasant database management experience, so give it a chance and send us your feedback, we would really appreciate it.
|
However, there are all the features necessary to have a pleasant database management experience, so give it a chance and send us your feedback, we would really appreciate it.
|
||||||
We are actively working on it, hoping to provide new cool features, improvements and fixes as soon as possible.
|
We are actively working on it, hoping to provide new cool features, improvements and fixes as soon as possible.
|
||||||
|
|
||||||
🔗 If you are curious to try Antares you can download and install the [latest release](https://github.com/Fabio286/antares/releases/latest).
|
🔗 If you are curious to try Antares you can download and install the [latest release](https://github.com/Fabio286/antares/releases/latest).
|
||||||
@@ -40,20 +40,20 @@ We are actively working on it, hoping to provide new cool features, improvements
|
|||||||
|
|
||||||
Why are we developing an SQL client when there are a lot of them on the market?
|
Why are we developing an SQL client when there are a lot of them on the market?
|
||||||
The main goal is to develop a **forever 100% free (without paid premium feature)**, full featured, as possible community driven, cross platform and open source alternative, empowered by JavaScript ecosystem.
|
The main goal is to develop a **forever 100% free (without paid premium feature)**, full featured, as possible community driven, cross platform and open source alternative, empowered by JavaScript ecosystem.
|
||||||
A modern application created with minimalism and semplicity in mind, with features in the right places, not hundreds of tiny buttons, nested tabs or submenu; productivity comes first.
|
A modern application created with minimalism and simplicity in mind, with features in the right places, not hundreds of tiny buttons, nested tabs or submenues; productivity comes first.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Based on your operating system you can have one or more distribution formats to choose based on your preferences.
|
Based on your operating system you can have one or more distribution formats to choose based on your preferences.
|
||||||
Since Antares SQL is a free software we haven't a budget to spend in annual licenses or certificates. This can result that on some platforms you need some additional passages to install this app.
|
Since Antares SQL is a free software we don't have a budget to spend on annual licenses or certificates. This can result that on some platforms you might need to put in some additional work to install this app.
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
On Linux you can simply download and run `.AppImage` distributions, install from Snap Store or from AUR.
|
On Linux you can simply download and run the `.AppImage` distribution, install from Snap Store or from AUR.
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
On Windows you can choose between Microsoft Store and download `.exe` distribution. The latter lacks of a certificate, so to install you need to click on "More info" and then "Run anyway" on SmartScreen prompt.
|
On Windows you can choose between downloading the app from Microsoft Store or downloading the `.exe` from our [website](https://antares-sql.app/download.html) or [this github repo](https://github.com/Fabio286/antares/releases/latest). Distributions that are not from Microsoft Store are not signed with a certificate, so to install you need to click on "More info" and then "Run anyway" on SmartScreen prompt.
|
||||||
|
|
||||||
### MacOS
|
### MacOS
|
||||||
|
|
||||||
|
18068
package-lock.json
generated
18068
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "antares",
|
"name": "antares",
|
||||||
"productName": "Antares",
|
"productName": "Antares",
|
||||||
"version": "0.5.11",
|
"version": "0.5.12",
|
||||||
"description": "A modern, fast and productivity driven SQL client with a focus in UX.",
|
"description": "A modern, fast and productivity driven SQL client with a focus in UX.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": "https://github.com/antares-sql/antares.git",
|
"repository": "https://github.com/antares-sql/antares.git",
|
||||||
@@ -120,7 +120,7 @@
|
|||||||
"@mdi/font": "~6.9.96",
|
"@mdi/font": "~6.9.96",
|
||||||
"@turf/helpers": "~6.5.0",
|
"@turf/helpers": "~6.5.0",
|
||||||
"@vueuse/core": "~8.7.5",
|
"@vueuse/core": "~8.7.5",
|
||||||
"ace-builds": "~1.4.13",
|
"ace-builds": "~1.8.1",
|
||||||
"better-sqlite3": "~7.5.1",
|
"better-sqlite3": "~7.5.1",
|
||||||
"electron-log": "~4.4.1",
|
"electron-log": "~4.4.1",
|
||||||
"electron-store": "~8.0.1",
|
"electron-store": "~8.0.1",
|
||||||
|
@@ -25,7 +25,7 @@ export const customizations: Customizations = {
|
|||||||
functions: true,
|
functions: true,
|
||||||
schedulers: true,
|
schedulers: true,
|
||||||
// Settings
|
// Settings
|
||||||
elementsWrapper: '',
|
elementsWrapper: '`',
|
||||||
stringsWrapper: '"',
|
stringsWrapper: '"',
|
||||||
tableAdd: true,
|
tableAdd: true,
|
||||||
tableTruncateDisableFKCheck: true,
|
tableTruncateDisableFKCheck: true,
|
||||||
|
@@ -1,14 +0,0 @@
|
|||||||
/* eslint-disable no-useless-escape */
|
|
||||||
// eslint-disable-next-line no-control-regex
|
|
||||||
const pattern = /[\0\x08\x09\x1a\n\r"'\\\%]/gm;
|
|
||||||
const regex = new RegExp(pattern);
|
|
||||||
|
|
||||||
function sqlEscaper (string: string) {
|
|
||||||
return string.replace(regex, char => {
|
|
||||||
const m = ['\\0', '\\x08', '\\x09', '\\x1a', '\\n', '\\r', '\'', '\"', '\\', '\\\\', '%'];
|
|
||||||
const r = ['\\\\0', '\\\\b', '\\\\t', '\\\\z', '\\\\n', '\\\\r', '\\\'', '\\\"', '\\\\', '\\\\\\\\', '\%'];
|
|
||||||
return r[m.indexOf(char)] || char;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export { sqlEscaper };
|
|
162
src/common/libs/sqlUtils.ts
Normal file
162
src/common/libs/sqlUtils.ts
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
/* eslint-disable no-useless-escape */
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import { lineString, point, polygon } from '@turf/helpers';
|
||||||
|
import customizations from '../customizations';
|
||||||
|
import { ClientCode } from '../interfaces/antares';
|
||||||
|
import { BLOB, BIT, DATE, DATETIME, FLOAT, SPATIAL, IS_MULTI_SPATIAL, NUMBER, TEXT_SEARCH } from 'common/fieldTypes';
|
||||||
|
import hexToBinary, { HexChar } from './hexToBinary';
|
||||||
|
import { getArrayDepth } from './getArrayDepth';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes a string fo SQL use
|
||||||
|
*
|
||||||
|
* @param { String } string
|
||||||
|
* @returns { String } Escaped string
|
||||||
|
*/
|
||||||
|
export const sqlEscaper = (string: string): string => {
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
|
const pattern = /[\0\x08\x09\x1a\n\r"'\\\%]/gm;
|
||||||
|
const regex = new RegExp(pattern);
|
||||||
|
return string.replace(regex, char => {
|
||||||
|
const m = ['\\0', '\\x08', '\\x09', '\\x1a', '\\n', '\\r', '\'', '\"', '\\', '\\\\', '%'];
|
||||||
|
const r = ['\\\\0', '\\\\b', '\\\\t', '\\\\z', '\\\\n', '\\\\r', '\\\'', '\\\"', '\\\\', '\\\\\\\\', '\%'];
|
||||||
|
return r[m.indexOf(char)] || char;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const objectToGeoJSON = (val: any) => {
|
||||||
|
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: any, curr: any) => [...acc, [curr.x, curr.y]], [])));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return point([val.x, val.y]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const escapeAndQuote = (val: string, client: ClientCode) => {
|
||||||
|
const { stringsWrapper: sw } = customizations[client];
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
|
const CHARS_TO_ESCAPE = /[\0\b\t\n\r\x1a"'\\]/g;
|
||||||
|
const CHARS_ESCAPE_MAP: {[key: string]: string} = {
|
||||||
|
'\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 `${sw}${val}${sw}`;
|
||||||
|
|
||||||
|
if (chunkIndex < val.length)
|
||||||
|
return `${sw}${escapedVal + val.slice(chunkIndex)}${sw}`;
|
||||||
|
|
||||||
|
return `${sw}${escapedVal}${sw}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const valueToSqlString = (args: {
|
||||||
|
val: any;
|
||||||
|
client: ClientCode;
|
||||||
|
field: {type: string; datePrecision: number};
|
||||||
|
}): string => {
|
||||||
|
let parsedValue;
|
||||||
|
const { val, client, field } = args;
|
||||||
|
const { stringsWrapper: sw } = customizations[client];
|
||||||
|
|
||||||
|
if (val === null)
|
||||||
|
parsedValue = 'NULL';
|
||||||
|
else if (DATE.includes(field.type)) {
|
||||||
|
parsedValue = moment(val).isValid()
|
||||||
|
? escapeAndQuote(moment(val).format('YYYY-MM-DD'), client)
|
||||||
|
: val;
|
||||||
|
}
|
||||||
|
else if (DATETIME.includes(field.type)) {
|
||||||
|
let datePrecision = '';
|
||||||
|
for (let i = 0; i < field.datePrecision; i++)
|
||||||
|
datePrecision += i === 0 ? '.S' : 'S';
|
||||||
|
|
||||||
|
parsedValue = moment(val).isValid()
|
||||||
|
? escapeAndQuote(moment(val).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`), client)
|
||||||
|
: escapeAndQuote(val, client);
|
||||||
|
}
|
||||||
|
else if ('isArray' in field) {
|
||||||
|
let localVal;
|
||||||
|
if (Array.isArray(val))
|
||||||
|
localVal = JSON.stringify(val).replaceAll('[', '{').replaceAll(']', '}');
|
||||||
|
else
|
||||||
|
localVal = typeof val === 'string' ? val.replaceAll('[', '{').replaceAll(']', '}') : '';
|
||||||
|
parsedValue = `'${localVal}'`;
|
||||||
|
}
|
||||||
|
else if (TEXT_SEARCH.includes(field.type))
|
||||||
|
parsedValue = `'${val.replaceAll('\'', '\'\'')}'`;
|
||||||
|
else if (BIT.includes(field.type))
|
||||||
|
parsedValue = `b'${hexToBinary(Buffer.from(val).toString('hex') as undefined as HexChar[])}'`;
|
||||||
|
else if (BLOB.includes(field.type)) {
|
||||||
|
if (['mysql', 'maria'].includes(client))
|
||||||
|
parsedValue = `X'${val.toString('hex').toUpperCase()}'`;
|
||||||
|
else if (client === 'pg')
|
||||||
|
parsedValue = `decode('${val.toString('hex').toUpperCase()}', 'hex')`;
|
||||||
|
}
|
||||||
|
else if (NUMBER.includes(field.type))
|
||||||
|
parsedValue = val;
|
||||||
|
else if (FLOAT.includes(field.type))
|
||||||
|
parsedValue = parseFloat(val);
|
||||||
|
else if (SPATIAL.includes(field.type)) {
|
||||||
|
let geoJson;
|
||||||
|
if (IS_MULTI_SPATIAL.includes(field.type)) {
|
||||||
|
const features = [];
|
||||||
|
for (const element of val)
|
||||||
|
features.push(objectToGeoJSON(element));
|
||||||
|
|
||||||
|
geoJson = {
|
||||||
|
type: 'FeatureCollection',
|
||||||
|
features
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
geoJson = objectToGeoJSON(val);
|
||||||
|
|
||||||
|
parsedValue = `ST_GeomFromGeoJSON('${JSON.stringify(geoJson)}')`;
|
||||||
|
}
|
||||||
|
else if (val === '') parsedValue = `${sw}${sw}`;
|
||||||
|
else {
|
||||||
|
parsedValue = typeof val === 'string'
|
||||||
|
? escapeAndQuote(val, client)
|
||||||
|
: typeof val === 'object'
|
||||||
|
? escapeAndQuote(JSON.stringify(val), client)
|
||||||
|
: val;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const jsonToSqlInsert = (args: {
|
||||||
|
json: { [key: string]: any};
|
||||||
|
client: ClientCode;
|
||||||
|
fields: { [key: string]: {type: string; datePrecision: number}};
|
||||||
|
table: string;
|
||||||
|
}) => {
|
||||||
|
const { client, json, fields, table } = args;
|
||||||
|
const { elementsWrapper: ew } = customizations[client];
|
||||||
|
const fieldNames = Object.keys(json).map(key => `${ew}${key}${ew}`);
|
||||||
|
const values = Object.keys(json).map(key => (
|
||||||
|
valueToSqlString({ val: json[key], client, field: fields[key] })
|
||||||
|
));
|
||||||
|
|
||||||
|
return `INSERT INTO ${ew}${table}${ew} (${fieldNames.join(', ')}) VALUES (${values.join(', ')});`;
|
||||||
|
};
|
@@ -2,38 +2,57 @@ interface ShortcutRecord {
|
|||||||
event: string;
|
event: string;
|
||||||
keys: Electron.Accelerator[];
|
keys: Electron.Accelerator[];
|
||||||
description: string;
|
description: string;
|
||||||
|
os: NodeJS.Platform[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const shortcuts: ShortcutRecord[] = [
|
const shortcuts: ShortcutRecord[] = [
|
||||||
{
|
{
|
||||||
event: 'open-new-tab',
|
event: 'open-new-tab',
|
||||||
keys: ['CommandOrControl+T'],
|
keys: ['CommandOrControl+T'],
|
||||||
description: 'Open a new query tab'
|
description: 'Open a new query tab',
|
||||||
|
os: ['darwin', 'linux', 'win32']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: 'close-tab',
|
event: 'close-tab',
|
||||||
keys: ['CommandOrControl+W'],
|
keys: ['CommandOrControl+W'],
|
||||||
description: 'Close tab'
|
description: 'Close tab',
|
||||||
|
os: ['darwin', 'linux', 'win32']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: 'next-tab',
|
event: 'next-tab',
|
||||||
keys: ['Alt+CommandOrControl+Right', 'CommandOrControl+PageDown'],
|
keys: ['Alt+CommandOrControl+Right', 'CommandOrControl+PageDown'],
|
||||||
description: 'Next tab'
|
description: 'Next tab',
|
||||||
|
os: ['darwin', 'win32']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: 'prev-tab',
|
event: 'prev-tab',
|
||||||
keys: ['Alt+CommandOrControl+Left', 'CommandOrControl+PageUp'],
|
keys: ['Alt+CommandOrControl+Left', 'CommandOrControl+PageUp'],
|
||||||
description: 'Previous tab'
|
description: 'Previous tab',
|
||||||
|
os: ['darwin', 'win32']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event: 'next-tab',
|
||||||
|
keys: ['CommandOrControl+PageDown'],
|
||||||
|
description: 'Next tab',
|
||||||
|
os: ['linux']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event: 'prev-tab',
|
||||||
|
keys: ['CommandOrControl+PageUp'],
|
||||||
|
description: 'Previous tab',
|
||||||
|
os: ['linux']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: 'open-connections-modal',
|
event: 'open-connections-modal',
|
||||||
keys: ['Shift+CommandOrControl+Space'],
|
keys: ['Shift+CommandOrControl+Space'],
|
||||||
description: 'Show all connections'
|
description: 'Show all connections',
|
||||||
|
os: ['darwin', 'linux', 'win32']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: 'toggle-console',
|
event: 'toggle-console',
|
||||||
keys: ['CommandOrControl+F12', 'CommandOrControl+`'],
|
keys: ['CommandOrControl+F12', 'CommandOrControl+`'],
|
||||||
description: 'Toggle console'
|
description: 'Toggle console',
|
||||||
|
os: ['darwin', 'linux', 'win32']
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -42,8 +61,9 @@ for (let i = 1; i <= 9; i++) {
|
|||||||
{
|
{
|
||||||
event: `select-tab-${i}`,
|
event: `select-tab-${i}`,
|
||||||
keys: [`CommandOrControl+${i}`],
|
keys: [`CommandOrControl+${i}`],
|
||||||
description: `Select tab number ${i}`
|
description: `Select tab number ${i}`,
|
||||||
|
os: ['darwin', 'linux', 'win32']
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export { shortcuts };
|
export { shortcuts, ShortcutRecord };
|
||||||
|
@@ -4,7 +4,7 @@ import { InsertRowsParams } from 'common/interfaces/tableApis';
|
|||||||
import { ipcMain } from 'electron';
|
import { ipcMain } from 'electron';
|
||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { sqlEscaper } from 'common/libs/sqlEscaper';
|
import { sqlEscaper } from 'common/libs/sqlUtils';
|
||||||
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BLOB, BIT, DATE, DATETIME } from 'common/fieldTypes';
|
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BLOB, BIT, DATE, DATETIME } from 'common/fieldTypes';
|
||||||
import customizations from 'common/customizations';
|
import customizations from 'common/customizations';
|
||||||
|
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import * as antares from 'common/interfaces/antares';
|
import * as antares from 'common/interfaces/antares';
|
||||||
import { webContents } from 'electron';
|
|
||||||
import mysql from 'mysql2/promise';
|
import mysql from 'mysql2/promise';
|
||||||
import * as pg from 'pg';
|
import * as pg from 'pg';
|
||||||
import SSH2Promise from 'ssh2-promise';
|
import SSH2Promise from 'ssh2-promise';
|
||||||
@@ -7,8 +6,10 @@ import SSH2Promise from 'ssh2-promise';
|
|||||||
const queryLogger = ({ sql, cUid }: {sql: string; cUid: string}) => {
|
const queryLogger = ({ sql, cUid }: {sql: string; cUid: string}) => {
|
||||||
// Remove comments, newlines and multiple spaces
|
// Remove comments, newlines and multiple spaces
|
||||||
const escapedSql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' ');
|
const escapedSql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' ');
|
||||||
const mainWindow = webContents.fromId(1);
|
if (process.type !== undefined) {
|
||||||
mainWindow.send('query-log', { cUid, sql: escapedSql, date: new Date() });
|
const mainWindow = require('electron').webContents.fromId(1);
|
||||||
|
mainWindow.send('query-log', { cUid, sql: escapedSql, date: new Date() });
|
||||||
|
}
|
||||||
if (process.env.NODE_ENV === 'development') console.log(escapedSql);
|
if (process.env.NODE_ENV === 'development') console.log(escapedSql);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,12 +1,8 @@
|
|||||||
import * as exporter from 'common/interfaces/exporter';
|
import * as exporter from 'common/interfaces/exporter';
|
||||||
import * as mysql from 'mysql2/promise';
|
import * as mysql from 'mysql2/promise';
|
||||||
import { SqlExporter } from './SqlExporter';
|
import { SqlExporter } from './SqlExporter';
|
||||||
import { BLOB, BIT, DATE, DATETIME, FLOAT, SPATIAL, IS_MULTI_SPATIAL, NUMBER } from 'common/fieldTypes';
|
|
||||||
import hexToBinary, { HexChar } from 'common/libs/hexToBinary';
|
|
||||||
import { getArrayDepth } from 'common/libs/getArrayDepth';
|
|
||||||
import * as moment from 'moment';
|
|
||||||
import { lineString, point, polygon } from '@turf/helpers';
|
|
||||||
import { MySQLClient } from '../../clients/MySQLClient';
|
import { MySQLClient } from '../../clients/MySQLClient';
|
||||||
|
import { valueToSqlString } from 'common/libs/sqlUtils';
|
||||||
|
|
||||||
export default class MysqlExporter extends SqlExporter {
|
export default class MysqlExporter extends SqlExporter {
|
||||||
protected _client: MySQLClient;
|
protected _client: MySQLClient;
|
||||||
@@ -122,54 +118,7 @@ ${footer}
|
|||||||
const column = notGeneratedColumns[i];
|
const column = notGeneratedColumns[i];
|
||||||
const val = row[column.name];
|
const val = row[column.name];
|
||||||
|
|
||||||
if (val === null) sqlInsertString += 'NULL';
|
sqlInsertString += valueToSqlString({ val, client: 'mysql', field: column });
|
||||||
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.datePrecision; 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') as undefined as HexChar[])}'`;
|
|
||||||
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._getGeoJSON(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)
|
if (parseInt(i) !== notGeneratedColumns.length - 1)
|
||||||
sqlInsertString += ', ';
|
sqlInsertString += ', ';
|
||||||
@@ -435,17 +384,4 @@ CREATE TABLE \`${view.Name}\`(
|
|||||||
|
|
||||||
return `'${escapedVal}'`;
|
return `'${escapedVal}'`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
_getGeoJSON (val: any) {
|
|
||||||
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: any, curr: any) => [...acc, [curr.x, curr.y]], [])));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return point([val.x, val.y]);
|
|
||||||
}
|
|
||||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
||||||
}
|
}
|
||||||
|
@@ -1,13 +1,11 @@
|
|||||||
import * as antares from 'common/interfaces/antares';
|
import * as antares from 'common/interfaces/antares';
|
||||||
import * as exporter from 'common/interfaces/exporter';
|
import * as exporter from 'common/interfaces/exporter';
|
||||||
import { SqlExporter } from './SqlExporter';
|
import { SqlExporter } from './SqlExporter';
|
||||||
import { BLOB, BIT, DATE, DATETIME, FLOAT, NUMBER, TEXT_SEARCH } from 'common/fieldTypes';
|
|
||||||
import hexToBinary, { HexChar } from 'common/libs/hexToBinary';
|
|
||||||
import * as moment from 'moment';
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import * as QueryStream from 'pg-query-stream';
|
import * as QueryStream from 'pg-query-stream';
|
||||||
import { PostgreSQLClient } from '../../clients/PostgreSQLClient';
|
import { PostgreSQLClient } from '../../clients/PostgreSQLClient';
|
||||||
|
import { valueToSqlString } from 'common/libs/sqlUtils';
|
||||||
|
|
||||||
export default class PostgreSQLExporter extends SqlExporter {
|
export default class PostgreSQLExporter extends SqlExporter {
|
||||||
constructor (client: PostgreSQLClient, tables: exporter.TableParams[], options: exporter.ExportOptions) {
|
constructor (client: PostgreSQLClient, tables: exporter.TableParams[], options: exporter.ExportOptions) {
|
||||||
@@ -223,47 +221,7 @@ SET row_security = off;\n\n\n`;
|
|||||||
const column = columns[i];
|
const column = columns[i];
|
||||||
const val = row[column.name];
|
const val = row[column.name];
|
||||||
|
|
||||||
if (val === null) sqlInsertString += 'NULL';
|
sqlInsertString += valueToSqlString({ val, client: 'pg', field: column });
|
||||||
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.datePrecision; 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 ('isArray' in column) {
|
|
||||||
let parsedVal;
|
|
||||||
if (Array.isArray(val))
|
|
||||||
parsedVal = JSON.stringify(val).replaceAll('[', '{').replaceAll(']', '}');
|
|
||||||
else
|
|
||||||
parsedVal = typeof val === 'string' ? val.replaceAll('[', '{').replaceAll(']', '}') : '';
|
|
||||||
sqlInsertString += `'${parsedVal}'`;
|
|
||||||
}
|
|
||||||
else if (TEXT_SEARCH.includes(column.type))
|
|
||||||
sqlInsertString += `'${val.replaceAll('\'', '\'\'')}'`;
|
|
||||||
else if (BIT.includes(column.type))
|
|
||||||
sqlInsertString += `b'${hexToBinary(Buffer.from(val).toString('hex') as undefined as HexChar[])}'`;
|
|
||||||
else if (BLOB.includes(column.type))
|
|
||||||
sqlInsertString += `decode('${val.toString('hex').toUpperCase()}', 'hex')`;
|
|
||||||
else if (NUMBER.includes(column.type))
|
|
||||||
sqlInsertString += val;
|
|
||||||
else if (FLOAT.includes(column.type))
|
|
||||||
sqlInsertString += parseFloat(val);
|
|
||||||
else if (val === '') sqlInsertString += '\'\'';
|
|
||||||
else {
|
|
||||||
sqlInsertString += typeof val === 'string'
|
|
||||||
? this.escapeAndQuote(val)
|
|
||||||
: typeof val === 'object'
|
|
||||||
? this.escapeAndQuote(JSON.stringify(val))
|
|
||||||
: val;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parseInt(i) !== columns.length - 1)
|
if (parseInt(i) !== columns.length - 1)
|
||||||
sqlInsertString += ', ';
|
sqlInsertString += ', ';
|
||||||
|
@@ -147,11 +147,13 @@ else {
|
|||||||
app.on('browser-window-focus', () => {
|
app.on('browser-window-focus', () => {
|
||||||
// Send registered shortcut events to window
|
// Send registered shortcut events to window
|
||||||
for (const shortcut of shortcuts) {
|
for (const shortcut of shortcuts) {
|
||||||
for (const key of shortcut.keys) {
|
if (shortcut.os.includes(process.platform)) {
|
||||||
globalShortcut.register(key, () => {
|
for (const key of shortcut.keys) {
|
||||||
mainWindow.webContents.send(shortcut.event);
|
globalShortcut.register(key, () => {
|
||||||
if (isDevelopment) console.log('EVENT:', shortcut);
|
mainWindow.webContents.send(shortcut.event);
|
||||||
});
|
if (isDevelopment) console.log('EVENT:', shortcut);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,7 +10,7 @@
|
|||||||
:key="connection.uid"
|
:key="connection.uid"
|
||||||
:connection="connection"
|
:connection="connection"
|
||||||
/>
|
/>
|
||||||
<div class="connection-panel-wrapper">
|
<div class="connection-panel-wrapper p-relative">
|
||||||
<WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" />
|
<WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -13,7 +13,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, Ref, ref } from 'vue';
|
import { computed, Ref, ref, watch } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import Tables from '@/ipc-api/Tables';
|
import Tables from '@/ipc-api/Tables';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
@@ -40,12 +40,12 @@ const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
|||||||
|
|
||||||
const editField: Ref<HTMLSelectElement> = ref(null);
|
const editField: Ref<HTMLSelectElement> = ref(null);
|
||||||
const foreignList = ref([]);
|
const foreignList = ref([]);
|
||||||
const currentValue = ref(props.modelValue);
|
const currentValue = ref(null);
|
||||||
|
|
||||||
const isValidDefault = computed(() => {
|
const isValidDefault = computed(() => {
|
||||||
if (!foreignList.value.length) return true;
|
if (!foreignList.value.length) return true;
|
||||||
if (props.modelValue === null) return false;
|
if (props.modelValue === null) return false;
|
||||||
return foreignList.value.some(foreign => foreign.foreign_column.toString() === props.modelValue.toString());
|
return foreignList.value.some(foreign => foreign.foreign_column.toString() === props.modelValue?.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
const foreigns = computed(() => {
|
const foreigns = computed(() => {
|
||||||
@@ -66,6 +66,10 @@ const cutText = (val: string) => {
|
|||||||
return val.length > 15 ? `${val.substring(0, 15)}...` : val;
|
return val.length > 15 ? `${val.substring(0, 15)}...` : val;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
watch(() => props.modelValue, () => {
|
||||||
|
currentValue.value = props.modelValue;
|
||||||
|
});
|
||||||
|
|
||||||
let foreignDesc: string | false;
|
let foreignDesc: string | false;
|
||||||
const params = {
|
const params = {
|
||||||
uid: selectedWorkspace.value,
|
uid: selectedWorkspace.value,
|
||||||
|
@@ -112,7 +112,11 @@ import BaseSelect from '@/components/BaseSelect.vue';
|
|||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
tabUid: [String, Number],
|
tabUid: [String, Number],
|
||||||
|
schema: String,
|
||||||
|
table: String,
|
||||||
fields: Array as Prop<TableField[]>,
|
fields: Array as Prop<TableField[]>,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
rowToDuplicate: Object as Prop<any>,
|
||||||
keyUsage: Array as Prop<TableForeign[]>
|
keyUsage: Array as Prop<TableForeign[]>
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -123,8 +127,6 @@ const workspacesStore = useWorkspacesStore();
|
|||||||
|
|
||||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||||
|
|
||||||
const { getWorkspace } = workspacesStore;
|
|
||||||
|
|
||||||
const { trapRef } = useFocusTrap({ disableAutofocus: true });
|
const { trapRef } = useFocusTrap({ disableAutofocus: true });
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@@ -134,7 +136,6 @@ const nInserts = ref(1);
|
|||||||
const isInserting = ref(false);
|
const isInserting = ref(false);
|
||||||
const fakerLocale = ref('en');
|
const fakerLocale = ref('en');
|
||||||
|
|
||||||
const workspace = computed(() => getWorkspace(selectedWorkspace.value));
|
|
||||||
const foreignKeys = computed(() => props.keyUsage.map(key => key.field));
|
const foreignKeys = computed(() => props.keyUsage.map(key => key.field));
|
||||||
const hasFakes = computed(() => Object.keys(localRow.value).some(field => 'group' in localRow.value[field] && localRow.value[field].group !== 'manual'));
|
const hasFakes = computed(() => Object.keys(localRow.value).some(field => 'group' in localRow.value[field] && localRow.value[field].group !== 'manual'));
|
||||||
|
|
||||||
@@ -220,8 +221,8 @@ const insertRows = async () => {
|
|||||||
try {
|
try {
|
||||||
const { status, response } = await Tables.insertTableFakeRows({
|
const { status, response } = await Tables.insertTableFakeRows({
|
||||||
uid: selectedWorkspace.value,
|
uid: selectedWorkspace.value,
|
||||||
schema: workspace.value.breadcrumbs.schema,
|
schema: props.schema,
|
||||||
table: workspace.value.breadcrumbs.table,
|
table: props.table,
|
||||||
row: rowToInsert,
|
row: rowToInsert,
|
||||||
repeat: nInserts.value,
|
repeat: nInserts.value,
|
||||||
fields: fieldTypes,
|
fields: fieldTypes,
|
||||||
@@ -284,44 +285,57 @@ onMounted(() => {
|
|||||||
|
|
||||||
const rowObj: {[key: string]: unknown} = {};
|
const rowObj: {[key: string]: unknown} = {};
|
||||||
|
|
||||||
for (const field of props.fields) {
|
if (!props.rowToDuplicate) {
|
||||||
let fieldDefault;
|
// Set default values
|
||||||
|
for (const field of props.fields) {
|
||||||
|
let fieldDefault;
|
||||||
|
|
||||||
if (field.default === 'NULL') fieldDefault = null;
|
if (field.default === 'NULL') fieldDefault = null;
|
||||||
else {
|
else {
|
||||||
if ([...NUMBER, ...FLOAT].includes(field.type))
|
if ([...NUMBER, ...FLOAT].includes(field.type))
|
||||||
fieldDefault = !field.default || Number.isNaN(+field.default.replaceAll('\'', '')) ? null : +field.default.replaceAll('\'', '');
|
fieldDefault = !field.default || Number.isNaN(+field.default.replaceAll('\'', '')) ? null : +field.default.replaceAll('\'', '');
|
||||||
else if ([...TEXT, ...LONG_TEXT].includes(field.type)) {
|
else if ([...TEXT, ...LONG_TEXT].includes(field.type)) {
|
||||||
fieldDefault = field.default
|
fieldDefault = field.default
|
||||||
? field.default.includes('\'')
|
? field.default.includes('\'')
|
||||||
? field.default.split('\'')[1]
|
? field.default.split('\'')[1]
|
||||||
: field.default
|
: field.default
|
||||||
: '';
|
: '';
|
||||||
}
|
|
||||||
else if ([...TIME, ...DATE].includes(field.type))
|
|
||||||
fieldDefault = field.default;
|
|
||||||
else if (BIT.includes(field.type))
|
|
||||||
fieldDefault = field.default?.replaceAll('\'', '').replaceAll('b', '');
|
|
||||||
else if (DATETIME.includes(field.type)) {
|
|
||||||
if (field.default && ['current_timestamp', 'now()'].some(term => field.default.toLowerCase().includes(term))) {
|
|
||||||
let datePrecision = '';
|
|
||||||
for (let i = 0; i < field.datePrecision; i++)
|
|
||||||
datePrecision += i === 0 ? '.S' : 'S';
|
|
||||||
fieldDefault = moment().format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
|
|
||||||
}
|
}
|
||||||
|
else if ([...TIME, ...DATE].includes(field.type))
|
||||||
|
fieldDefault = field.default;
|
||||||
|
else if (BIT.includes(field.type))
|
||||||
|
fieldDefault = field.default?.replaceAll('\'', '').replaceAll('b', '');
|
||||||
|
else if (DATETIME.includes(field.type)) {
|
||||||
|
if (field.default && ['current_timestamp', 'now()'].some(term => field.default.toLowerCase().includes(term))) {
|
||||||
|
let datePrecision = '';
|
||||||
|
for (let i = 0; i < field.datePrecision; i++)
|
||||||
|
datePrecision += i === 0 ? '.S' : 'S';
|
||||||
|
fieldDefault = moment().format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
fieldDefault = field.default;
|
||||||
|
}
|
||||||
|
else if (field.enumValues)
|
||||||
|
fieldDefault = field.enumValues.replaceAll('\'', '').split(',');
|
||||||
else
|
else
|
||||||
fieldDefault = field.default;
|
fieldDefault = field.default;
|
||||||
}
|
}
|
||||||
else if (field.enumValues)
|
|
||||||
fieldDefault = field.enumValues.replaceAll('\'', '').split(',');
|
rowObj[field.name] = { value: fieldDefault };
|
||||||
else
|
|
||||||
fieldDefault = field.default;
|
if (field.autoIncrement || !!field.onUpdate)// Disable by default auto increment or "on update" fields
|
||||||
|
fieldsToExclude.value = [...fieldsToExclude.value, field.name];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Set values to duplicate
|
||||||
|
for (const field of props.fields) {
|
||||||
|
if (typeof props.rowToDuplicate[field.name] !== 'object')
|
||||||
|
rowObj[field.name] = { value: props.rowToDuplicate[field.name] };
|
||||||
|
|
||||||
rowObj[field.name] = { value: fieldDefault };
|
if (field.autoIncrement || !!field.onUpdate)// Disable by default auto increment or "on update" fields
|
||||||
|
fieldsToExclude.value = [...fieldsToExclude.value, field.name];
|
||||||
if (field.autoIncrement || !!field.onUpdate)// Disable by default auto increment or "on update" fields
|
}
|
||||||
fieldsToExclude.value = [...fieldsToExclude.value, field.name];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
localRow.value = { ...rowObj };
|
localRow.value = { ...rowObj };
|
||||||
|
@@ -136,7 +136,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Component, computed, onBeforeUnmount, onMounted, onUpdated, Prop, Ref, ref } from 'vue';
|
import { Component, computed, onBeforeUnmount, onMounted, onUpdated, Prop, Ref, ref } from 'vue';
|
||||||
import { ConnectionParams } from 'common/interfaces/antares';
|
import { ConnectionParams } from 'common/interfaces/antares';
|
||||||
import { arrayToFile } from '../libs/arrayToFile';
|
import { exportRows } from '../libs/exportRows';
|
||||||
import { useNotificationsStore } from '@/stores/notifications';
|
import { useNotificationsStore } from '@/stores/notifications';
|
||||||
import { useFocusTrap } from '@/composables/useFocusTrap';
|
import { useFocusTrap } from '@/composables/useFocusTrap';
|
||||||
import Schema from '@/ipc-api/Schema';
|
import Schema from '@/ipc-api/Schema';
|
||||||
@@ -312,10 +312,10 @@ const closeModal = () => emit('close');
|
|||||||
|
|
||||||
const downloadTable = (format: 'csv' | 'json') => {
|
const downloadTable = (format: 'csv' | 'json') => {
|
||||||
if (!sortedResults.value) return;
|
if (!sortedResults.value) return;
|
||||||
arrayToFile({
|
exportRows({
|
||||||
type: format,
|
type: format,
|
||||||
content: sortedResults.value,
|
content: sortedResults.value,
|
||||||
filename: 'processes'
|
table: 'processes'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -12,7 +12,7 @@
|
|||||||
<div class="footer-right-elements">
|
<div class="footer-right-elements">
|
||||||
<ul class="footer-elements">
|
<ul class="footer-elements">
|
||||||
<li
|
<li
|
||||||
v-if="workspace.connectionStatus === 'connected' "
|
v-if="workspace?.connectionStatus === 'connected' "
|
||||||
class="footer-element footer-link"
|
class="footer-element footer-link"
|
||||||
@click="toggleConsole()"
|
@click="toggleConsole()"
|
||||||
>
|
>
|
||||||
|
@@ -560,7 +560,8 @@ setTimeout(() => {
|
|||||||
.connection-panel {
|
.connection-panel {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: .5rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
min-width: 450px;
|
min-width: 450px;
|
||||||
|
@@ -167,7 +167,9 @@ onMounted(() => {
|
|||||||
font-size: 95%;
|
font-size: 95%;
|
||||||
opacity: .8;
|
opacity: .8;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
user-select: text;
|
&:hover {
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -112,6 +112,9 @@
|
|||||||
<li class="menu-item">
|
<li class="menu-item">
|
||||||
<a class="c-hand" @click="downloadTable('csv')">CSV</a>
|
<a class="c-hand" @click="downloadTable('csv')">CSV</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="menu-item">
|
||||||
|
<a class="c-hand" @click="downloadTable('sql')">SQL INSERT</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group pr-2" :title="t('message.commitMode')">
|
<div class="input-group pr-2" :title="t('message.commitMode')">
|
||||||
@@ -294,6 +297,10 @@ watch(selectedSchema, () => {
|
|||||||
const runQuery = async (query: string) => {
|
const runQuery = async (query: string) => {
|
||||||
if (!query || isQuering.value) return;
|
if (!query || isQuering.value) return;
|
||||||
isQuering.value = true;
|
isQuering.value = true;
|
||||||
|
|
||||||
|
const selectedQuery = queryEditor.value.editor.getSelectedText();
|
||||||
|
if (selectedQuery) query = selectedQuery;
|
||||||
|
|
||||||
clearTabData();
|
clearTabData();
|
||||||
queryTable.value.resetSort();
|
queryTable.value.resetSort();
|
||||||
|
|
||||||
@@ -442,7 +449,7 @@ const clear = () => {
|
|||||||
clearTabData();
|
clearTabData();
|
||||||
};
|
};
|
||||||
|
|
||||||
const downloadTable = (format: 'csv' | 'json') => {
|
const downloadTable = (format: 'csv' | 'json' | 'sql') => {
|
||||||
queryTable.value.downloadTable(format, `${props.tab.type}-${props.tab.index}`);
|
queryTable.value.downloadTable(format, `${props.tab.type}-${props.tab.index}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -14,10 +14,12 @@
|
|||||||
:context-event="contextEvent"
|
:context-event="contextEvent"
|
||||||
:selected-rows="selectedRows"
|
:selected-rows="selectedRows"
|
||||||
:selected-cell="selectedCell"
|
:selected-cell="selectedCell"
|
||||||
|
:mode="mode"
|
||||||
@show-delete-modal="showDeleteConfirmModal"
|
@show-delete-modal="showDeleteConfirmModal"
|
||||||
@set-null="setNull"
|
@set-null="setNull"
|
||||||
@copy-cell="copyCell"
|
@copy-cell="copyCell"
|
||||||
@copy-row="copyRow"
|
@copy-row="copyRow"
|
||||||
|
@duplicate-row="duplicateRow"
|
||||||
@close-context="closeContext"
|
@close-context="closeContext"
|
||||||
/>
|
/>
|
||||||
<ul v-if="resultsWithRows.length > 1" class="tab tab-block result-tabs">
|
<ul v-if="resultsWithRows.length > 1" class="tab tab-block result-tabs">
|
||||||
@@ -119,7 +121,7 @@ import { uidGen } from 'common/libs/uidGen';
|
|||||||
import { useSettingsStore } from '@/stores/settings';
|
import { useSettingsStore } from '@/stores/settings';
|
||||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||||
import { useConsoleStore } from '@/stores/console';
|
import { useConsoleStore } from '@/stores/console';
|
||||||
import { arrayToFile } from '../libs/arrayToFile';
|
import { exportRows } from '../libs/exportRows';
|
||||||
import { TEXT, LONG_TEXT, BLOB } from 'common/fieldTypes';
|
import { TEXT, LONG_TEXT, BLOB } from 'common/fieldTypes';
|
||||||
import BaseVirtualScroll from '@/components/BaseVirtualScroll.vue';
|
import BaseVirtualScroll from '@/components/BaseVirtualScroll.vue';
|
||||||
import WorkspaceTabQueryTableRow from '@/components/WorkspaceTabQueryTableRow.vue';
|
import WorkspaceTabQueryTableRow from '@/components/WorkspaceTabQueryTableRow.vue';
|
||||||
@@ -129,6 +131,8 @@ import * as moment from 'moment';
|
|||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { TableField, QueryResult } from 'common/interfaces/antares';
|
import { TableField, QueryResult } from 'common/interfaces/antares';
|
||||||
import { TableUpdateParams } from 'common/interfaces/tableApis';
|
import { TableUpdateParams } from 'common/interfaces/tableApis';
|
||||||
|
import { jsonToSqlInsert } from 'common/libs/sqlUtils';
|
||||||
|
import { unproxify } from '@/libs/unproxify';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
@@ -143,12 +147,17 @@ const { consoleHeight } = storeToRefs(consoleStore);
|
|||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
results: Array as Prop<QueryResult[]>,
|
results: Array as Prop<QueryResult[]>,
|
||||||
connUid: String,
|
connUid: String,
|
||||||
mode: String,
|
mode: String as Prop<'table' | 'query'>,
|
||||||
isSelected: Boolean,
|
isSelected: Boolean,
|
||||||
elementType: { type: String, default: 'table' }
|
elementType: { type: String, default: 'table' }
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['update-field', 'delete-selected', 'hard-sort']);
|
const emit = defineEmits([
|
||||||
|
'update-field',
|
||||||
|
'delete-selected',
|
||||||
|
'hard-sort',
|
||||||
|
'duplicate-row'
|
||||||
|
]);
|
||||||
|
|
||||||
const resultTable: Ref<Component & {updateWindow: () => void}> = ref(null);
|
const resultTable: Ref<Component & {updateWindow: () => void}> = ref(null);
|
||||||
const tableWrapper: Ref<HTMLDivElement> = ref(null);
|
const tableWrapper: Ref<HTMLDivElement> = ref(null);
|
||||||
@@ -170,6 +179,7 @@ const selectedField = ref(null);
|
|||||||
const isEditingRow = ref(false);
|
const isEditingRow = ref(false);
|
||||||
|
|
||||||
const workspaceSchema = computed(() => getWorkspace(props.connUid).breadcrumbs.schema);
|
const workspaceSchema = computed(() => getWorkspace(props.connUid).breadcrumbs.schema);
|
||||||
|
const workspaceClient = computed(() => getWorkspace(props.connUid).client);
|
||||||
|
|
||||||
const primaryField = computed(() => {
|
const primaryField = computed(() => {
|
||||||
const primaryFields = fields.value.filter(field => field.key === 'pri');
|
const primaryFields = fields.value.filter(field => field.key === 'pri');
|
||||||
@@ -405,11 +415,47 @@ const copyCell = () => {
|
|||||||
navigator.clipboard.writeText(valueToCopy);
|
navigator.clipboard.writeText(valueToCopy);
|
||||||
};
|
};
|
||||||
|
|
||||||
const copyRow = () => {
|
const copyRow = (format: string) => {
|
||||||
|
let contentToCopy;
|
||||||
|
|
||||||
|
if (selectedRows.value.length === 1) {
|
||||||
|
const row = localResults.value.find((row: any) => selectedRows.value.includes(row._antares_id));
|
||||||
|
const rowToCopy = unproxify(row);
|
||||||
|
delete rowToCopy._antares_id;
|
||||||
|
contentToCopy = rowToCopy;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
contentToCopy = unproxify(localResults.value).filter((row: any) => selectedRows.value.includes(row._antares_id)).map((row: any) => {
|
||||||
|
delete row._antares_id;
|
||||||
|
return row;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format === 'json')
|
||||||
|
navigator.clipboard.writeText(JSON.stringify(contentToCopy));
|
||||||
|
else if (format === 'sql') {
|
||||||
|
const sqlInserts = [];
|
||||||
|
if (!Array.isArray(contentToCopy)) contentToCopy = [contentToCopy];
|
||||||
|
|
||||||
|
for (const row of contentToCopy) {
|
||||||
|
sqlInserts.push(jsonToSqlInsert({
|
||||||
|
json: row,
|
||||||
|
client: workspaceClient.value,
|
||||||
|
fields: fieldsObj.value as {
|
||||||
|
[key: string]: {type: string; datePrecision: number};
|
||||||
|
},
|
||||||
|
table: getTable(resultsetIndex.value)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
navigator.clipboard.writeText(sqlInserts.join('\n'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const duplicateRow = () => {
|
||||||
const row = localResults.value.find((row: any) => selectedRows.value.includes(row._antares_id));
|
const row = localResults.value.find((row: any) => selectedRows.value.includes(row._antares_id));
|
||||||
const rowToCopy = JSON.parse(JSON.stringify(row));
|
const rowToDuplicate = JSON.parse(JSON.stringify(row));
|
||||||
delete rowToCopy._antares_id;
|
delete rowToDuplicate._antares_id;
|
||||||
navigator.clipboard.writeText(JSON.stringify(rowToCopy));
|
emit('duplicate-row', rowToDuplicate);
|
||||||
};
|
};
|
||||||
|
|
||||||
const applyUpdate = (params: TableUpdateParams) => {
|
const applyUpdate = (params: TableUpdateParams) => {
|
||||||
@@ -520,7 +566,7 @@ const selectResultset = (index: number) => {
|
|||||||
resultsetIndex.value = index;
|
resultsetIndex.value = index;
|
||||||
};
|
};
|
||||||
|
|
||||||
const downloadTable = (format: 'csv' | 'json', filename: string) => {
|
const downloadTable = (format: 'csv' | 'json' | 'sql', table: string) => {
|
||||||
if (!sortedResults.value) return;
|
if (!sortedResults.value) return;
|
||||||
|
|
||||||
const rows = JSON.parse(JSON.stringify(sortedResults.value)).map((row: any) => {
|
const rows = JSON.parse(JSON.stringify(sortedResults.value)).map((row: any) => {
|
||||||
@@ -528,10 +574,14 @@ const downloadTable = (format: 'csv' | 'json', filename: string) => {
|
|||||||
return row;
|
return row;
|
||||||
});
|
});
|
||||||
|
|
||||||
arrayToFile({
|
exportRows({
|
||||||
type: format,
|
type: format,
|
||||||
content: rows,
|
content: rows,
|
||||||
filename
|
fields: fieldsObj.value as {
|
||||||
|
[key: string]: {type: string; datePrecision: number};
|
||||||
|
},
|
||||||
|
client: workspaceClient.value,
|
||||||
|
table
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
:context-event="contextEvent"
|
:context-event="contextEvent"
|
||||||
@close-context="closeContext"
|
@close-context="closeContext"
|
||||||
>
|
>
|
||||||
<div v-if="selectedRows.length === 1" class="context-element">
|
<div class="context-element">
|
||||||
<span class="d-flex"><i class="mdi mdi-18px mdi-content-copy text-light pr-1" /> {{ t('word.copy') }}</span>
|
<span class="d-flex"><i class="mdi mdi-18px mdi-content-copy text-light pr-1" /> {{ t('word.copy') }}</span>
|
||||||
<i class="mdi mdi-18px mdi-chevron-right text-light pl-1" />
|
<i class="mdi mdi-18px mdi-chevron-right text-light pl-1" />
|
||||||
<div class="context-submenu">
|
<div class="context-submenu">
|
||||||
@@ -16,17 +16,27 @@
|
|||||||
<i class="mdi mdi-18px mdi-numeric-0 mdi-rotate-90 text-light pr-1" /> {{ t('word.cell', 1) }}
|
<i class="mdi mdi-18px mdi-numeric-0 mdi-rotate-90 text-light pr-1" /> {{ t('word.cell', 1) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="context-element" @click="copyRow('json')">
|
||||||
v-if="selectedRows.length === 1"
|
|
||||||
class="context-element"
|
|
||||||
@click="copyRow"
|
|
||||||
>
|
|
||||||
<span class="d-flex">
|
<span class="d-flex">
|
||||||
<i class="mdi mdi-18px mdi-table-row text-light pr-1" /> {{ t('word.row', 1) }}
|
<i class="mdi mdi-18px mdi-table-row text-light pr-1" /> {{ t('word.row', selectedRows.length) }} (JSON)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="context-element" @click="copyRow('sql')">
|
||||||
|
<span class="d-flex">
|
||||||
|
<i class="mdi mdi-18px mdi-table-row text-light pr-1" /> {{ t('word.row', selectedRows.length) }} (SQL INSERT)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="selectedRows.length === 1 && selectedCell.isEditable && mode === 'table'"
|
||||||
|
class="context-element"
|
||||||
|
@click="duplicateRow"
|
||||||
|
>
|
||||||
|
<span class="d-flex">
|
||||||
|
<i class="mdi mdi-18px mdi-content-duplicate text-light pr-1" /> {{ t('word.duplicate') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="selectedRows.length === 1 && selectedCell.isEditable"
|
v-if="selectedRows.length === 1 && selectedCell.isEditable"
|
||||||
class="context-element"
|
class="context-element"
|
||||||
@@ -49,6 +59,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { Prop } from 'vue';
|
||||||
import BaseContextMenu from '@/components/BaseContextMenu.vue';
|
import BaseContextMenu from '@/components/BaseContextMenu.vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
@@ -57,10 +68,18 @@ const { t } = useI18n();
|
|||||||
defineProps({
|
defineProps({
|
||||||
contextEvent: MouseEvent,
|
contextEvent: MouseEvent,
|
||||||
selectedRows: Array,
|
selectedRows: Array,
|
||||||
selectedCell: Object
|
selectedCell: Object,
|
||||||
|
mode: String as Prop<'table' | 'query'>
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['show-delete-modal', 'close-context', 'set-null', 'copy-cell', 'copy-row']);
|
const emit = defineEmits([
|
||||||
|
'show-delete-modal',
|
||||||
|
'close-context',
|
||||||
|
'set-null',
|
||||||
|
'copy-cell',
|
||||||
|
'copy-row',
|
||||||
|
'duplicate-row'
|
||||||
|
]);
|
||||||
|
|
||||||
const showConfirmModal = () => {
|
const showConfirmModal = () => {
|
||||||
emit('show-delete-modal');
|
emit('show-delete-modal');
|
||||||
@@ -80,8 +99,13 @@ const copyCell = () => {
|
|||||||
closeContext();
|
closeContext();
|
||||||
};
|
};
|
||||||
|
|
||||||
const copyRow = () => {
|
const copyRow = (format: string) => {
|
||||||
emit('copy-row');
|
emit('copy-row', format);
|
||||||
|
closeContext();
|
||||||
|
};
|
||||||
|
|
||||||
|
const duplicateRow = () => {
|
||||||
|
emit('duplicate-row');
|
||||||
closeContext();
|
closeContext();
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@@ -82,7 +82,7 @@
|
|||||||
v-if="isTable"
|
v-if="isTable"
|
||||||
class="btn btn-dark btn-sm"
|
class="btn btn-dark btn-sm"
|
||||||
:disabled="isQuering"
|
:disabled="isQuering"
|
||||||
@click="showFakerModal"
|
@click="showFakerModal()"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
|
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
|
||||||
<span>{{ $tc('message.insertRow', 2) }}</span>
|
<span>{{ $tc('message.insertRow', 2) }}</span>
|
||||||
@@ -105,6 +105,9 @@
|
|||||||
<li class="menu-item">
|
<li class="menu-item">
|
||||||
<a class="c-hand" @click="downloadTable('csv')">CSV</a>
|
<a class="c-hand" @click="downloadTable('csv')">CSV</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="menu-item">
|
||||||
|
<a class="c-hand" @click="downloadTable('sql')">SQL INSERT</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -153,14 +156,18 @@
|
|||||||
:element-type="elementType"
|
:element-type="elementType"
|
||||||
@update-field="updateField"
|
@update-field="updateField"
|
||||||
@delete-selected="deleteSelected"
|
@delete-selected="deleteSelected"
|
||||||
|
@duplicate-row="showFakerModal"
|
||||||
@hard-sort="hardSort"
|
@hard-sort="hardSort"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ModalFakerRows
|
<ModalFakerRows
|
||||||
v-if="isFakerModal"
|
v-if="isFakerModal"
|
||||||
:fields="fields"
|
:fields="fields"
|
||||||
|
:row-to-duplicate="rowToDuplicate"
|
||||||
:key-usage="keyUsage"
|
:key-usage="keyUsage"
|
||||||
:tab-uid="tabUid"
|
:tab-uid="tabUid"
|
||||||
|
:schema="schema"
|
||||||
|
:table="table"
|
||||||
@hide="hideFakerModal"
|
@hide="hideFakerModal"
|
||||||
@reload="reloadTable"
|
@reload="reloadTable"
|
||||||
/>
|
/>
|
||||||
@@ -224,6 +231,7 @@ const filters = ref([]);
|
|||||||
const page = ref(1);
|
const page = ref(1);
|
||||||
const pageProxy = ref(1);
|
const pageProxy = ref(1);
|
||||||
const approximateCount = ref(0);
|
const approximateCount = ref(0);
|
||||||
|
const rowToDuplicate = ref(null);
|
||||||
|
|
||||||
const workspace = computed(() => {
|
const workspace = computed(() => {
|
||||||
return getWorkspace(props.connection.uid);
|
return getWorkspace(props.connection.uid);
|
||||||
@@ -234,7 +242,7 @@ const customizations = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const isTable = computed(() => {
|
const isTable = computed(() => {
|
||||||
return !!workspace.value.breadcrumbs.table;
|
return !workspace.value.breadcrumbs.view;
|
||||||
});
|
});
|
||||||
|
|
||||||
const fields = computed(() => {
|
const fields = computed(() => {
|
||||||
@@ -329,13 +337,17 @@ const pageChange = (direction: 'prev' | 'next') => {
|
|||||||
page.value--;
|
page.value--;
|
||||||
};
|
};
|
||||||
|
|
||||||
const showFakerModal = () => {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const showFakerModal = (row?: any) => {
|
||||||
|
console.log(row);
|
||||||
if (isQuering.value) return;
|
if (isQuering.value) return;
|
||||||
isFakerModal.value = true;
|
isFakerModal.value = true;
|
||||||
|
rowToDuplicate.value = row;
|
||||||
};
|
};
|
||||||
|
|
||||||
const hideFakerModal = () => {
|
const hideFakerModal = () => {
|
||||||
isFakerModal.value = false;
|
isFakerModal.value = false;
|
||||||
|
rowToDuplicate.value = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onKey = (e: KeyboardEvent) => {
|
const onKey = (e: KeyboardEvent) => {
|
||||||
@@ -349,7 +361,7 @@ const onKey = (e: KeyboardEvent) => {
|
|||||||
pageChange('next');
|
pageChange('next');
|
||||||
if (e.key === 'ArrowLeft')
|
if (e.key === 'ArrowLeft')
|
||||||
pageChange('prev');
|
pageChange('prev');
|
||||||
if (e.key === 'f') // f
|
if (e.key === 'f')
|
||||||
isSearch.value = !isSearch.value;
|
isSearch.value = !isSearch.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -367,7 +379,7 @@ const setRefreshInterval = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const downloadTable = (format: 'csv' | 'json') => {
|
const downloadTable = (format: 'csv' | 'json' | 'sql') => {
|
||||||
queryTable.value.downloadTable(format, props.table);
|
queryTable.value.downloadTable(format, props.table);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -97,7 +97,9 @@ const removeRow = (i: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const doFilter = () => {
|
const doFilter = () => {
|
||||||
const clausoles = rows.value.filter(el => el.active).map(el => createClausole(el));
|
const clausoles = rows.value
|
||||||
|
.filter(el => el.active)
|
||||||
|
.map(el => createClausole(el));
|
||||||
emit('filter', clausoles);
|
emit('filter', clausoles);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,7 +1,14 @@
|
|||||||
export const arrayToFile = (args: {
|
import { ClientCode } from 'common/interfaces/antares';
|
||||||
type: 'csv' | 'json';
|
import { jsonToSqlInsert } from 'common/libs/sqlUtils';
|
||||||
|
|
||||||
|
export const exportRows = (args: {
|
||||||
|
type: 'csv' | 'json'| 'sql';
|
||||||
content: object[];
|
content: object[];
|
||||||
filename: string;
|
table: string;
|
||||||
|
client?: ClientCode;
|
||||||
|
fields?: {
|
||||||
|
[key: string]: {type: string; datePrecision: number};
|
||||||
|
};
|
||||||
}) => {
|
}) => {
|
||||||
let mime;
|
let mime;
|
||||||
let content;
|
let content;
|
||||||
@@ -20,6 +27,23 @@ export const arrayToFile = (args: {
|
|||||||
content = csv.join('\n');
|
content = csv.join('\n');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'sql': {
|
||||||
|
mime = 'text/sql';
|
||||||
|
const sql = [];
|
||||||
|
|
||||||
|
for (const row of args.content) {
|
||||||
|
sql.push(jsonToSqlInsert({
|
||||||
|
json: row,
|
||||||
|
client:
|
||||||
|
args.client,
|
||||||
|
fields: args.fields,
|
||||||
|
table: args.table
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
content = sql.join('\n');
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'json':
|
case 'json':
|
||||||
mime = 'application/json';
|
mime = 'application/json';
|
||||||
content = JSON.stringify(args.content, null, 3);
|
content = JSON.stringify(args.content, null, 3);
|
||||||
@@ -30,7 +54,7 @@ export const arrayToFile = (args: {
|
|||||||
|
|
||||||
const file = new Blob([content], { type: mime });
|
const file = new Blob([content], { type: mime });
|
||||||
const downloadLink = document.createElement('a');
|
const downloadLink = document.createElement('a');
|
||||||
downloadLink.download = `${args.filename}.${args.type}`;
|
downloadLink.download = `${args.table}.${args.type}`;
|
||||||
downloadLink.href = window.URL.createObjectURL(file);
|
downloadLink.href = window.URL.createObjectURL(file);
|
||||||
downloadLink.style.display = 'none';
|
downloadLink.style.display = 'none';
|
||||||
document.body.appendChild(downloadLink);
|
document.body.appendChild(downloadLink);
|
@@ -4,7 +4,7 @@
|
|||||||
"./src/main/**/*",
|
"./src/main/**/*",
|
||||||
"./src/renderer/**/*",
|
"./src/renderer/**/*",
|
||||||
"./src/common/interfaces/antares.ts"
|
"./src/common/interfaces/antares.ts"
|
||||||
],
|
, "src/common/libs/jsonToSql.ts" ],
|
||||||
"exclude": ["./src/renderer/libs/ext-language_tools.js"],
|
"exclude": ["./src/renderer/libs/ext-language_tools.js"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
|
Reference in New Issue
Block a user