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

Compare commits

..

40 Commits

Author SHA1 Message Date
a95a76480c chore(release): 0.7.35-beta.0 2025-04-04 16:43:07 +02:00
1d1be55d3d fix: custom connection icon disappears during connection, fixes #939 2025-03-31 14:24:34 +02:00
d912faa850 fix: improved handling of query comments, fixes #963 and #580 2025-03-31 13:57:41 +02:00
ba63b049a3 fix: escape SQL parameters in update and delete for where clauses, fixes #964 2025-03-31 13:03:49 +02:00
fcd7e404ba Merge branch 'master' of https://github.com/antares-sql/antares into develop 2025-03-31 11:00:33 +02:00
8eb4d2e114 chore: update GitHub Actions runner to Ubuntu 22.04 2025-03-31 11:00:01 +02:00
acea18e6f0 perf(translation): update Spanish translations, closes #962 2025-03-31 10:49:14 +02:00
973b0fc4be fix: use custom elements wrapper for foreign column and description in query 2025-03-28 12:50:01 +01:00
4adbc575c2 chore(release): 0.7.34 2025-02-14 20:33:55 +01:00
eb706c3e51 fix: issue with some SSH connections, definitely 2025-02-14 20:30:47 +01:00
971df3a989 chore(release): 0.7.33 2025-02-14 18:03:57 +01:00
3129bf4baa fix: issue with some SSH connections, fixes #947 2025-02-14 17:58:49 +01:00
c6d67cef01 chore(release): 0.7.32 2025-02-14 09:12:05 +01:00
1d7053ce03 fix: black background with light theme, fixes #945 2025-02-14 09:08:45 +01:00
41e797f9e2 fix(PostgreSQL): error with materialized view tabs 2025-02-13 18:01:19 +01:00
704f70819b fix: improve error handling in SSH connection 2025-02-12 18:10:27 +01:00
49a3589536 fix: enhance SVG support in connection customization, fixes #939 2025-02-12 18:09:11 +01:00
49ada059bc chore(release): 0.7.31 2025-02-11 18:22:37 +01:00
8003d3eb1e chore(release): 0.7.31-beta.5 2025-02-09 10:19:09 +01:00
48cfa67889 perf: improve button styles of notes 2025-02-09 10:17:38 +01:00
9cda38e9d1 perf(MySQL): long loading in table settings when no checks present 2025-02-06 18:25:27 +01:00
72f8d4249f fix: improve BLOB primary fields management, fixes #938 2025-02-06 13:28:12 +01:00
0f93d70417 fix: unable to delete rows from context menu 2025-02-06 13:27:02 +01:00
580bef76ba fix: replace 'this.addNotification' with 'addNotification' in useResultTables.ts 2025-02-06 13:25:57 +01:00
7595e89223 fix(devtoolsInstaller): improve file path handling and increase chromium version 2025-02-06 12:50:45 +01:00
7af44d4a2c refactor: add ciaplu for pattern matching in language detection and MIME type resolution 2025-02-05 15:34:01 +01:00
0479e5307c fix(Linux): restored AppImage auto updates 2025-02-03 18:14:38 +01:00
d03c1b90ce chore(release): 0.7.31-beta.4 2025-01-31 18:06:58 +01:00
d34e56a517 fix(Linux): missing window management icons 2025-01-31 18:06:16 +01:00
0f35814ca0 chore(release): 0.7.31-beta.3 2025-01-31 17:54:25 +01:00
96ae09feca feat: implement a better query splitter for SQL queries, fixes #926 2025-01-31 17:28:58 +01:00
e3b30359bf refactor: disable auto opening dev tools in development mode 2025-01-31 13:33:42 +01:00
f3c3284fd1 ci: add GitHub Actions workflow for creating Windows APPX artifacts 2025-01-31 13:32:44 +01:00
27387f18a1 fix(MySQL): adjust utf8mb3 encoding to resolve compatibility issue, fixes #646 2025-01-31 13:32:06 +01:00
8e54f7b801 feat(Linux): update title bar for better Linux experience 2025-01-30 18:01:56 +01:00
70aae2f194 chore(release): 0.7.31-beta.2 2025-01-30 16:16:14 +01:00
592d7b3517 feat: add developer tools and refresh buttons to console in development mode 2025-01-23 18:10:37 +01:00
2b743a2c79 Merge pull request #925 from dyaskur/develop
fix: fail to fill cell to datetime column(Postgre) fixes #924
2025-01-23 09:17:40 +01:00
️Yaskur Dyas⚔⚔️⚔
e493db5112 fix: reorder condition when format the update data 2025-01-23 08:16:52 +07:00
️Yaskur Dyas⚔⚔️⚔
d3d7ab38c0 fix: fail to fill cell to datetime column(Postgre) fixes #924 2025-01-23 06:32:22 +07:00
54 changed files with 710 additions and 319 deletions

View File

@@ -5,7 +5,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Check out Git repository
uses: actions/checkout@v4

View File

@@ -0,0 +1,32 @@
name: Create artifact [WINDOWS APPX]
on:
workflow_dispatch: {}
jobs:
build:
runs-on: windows-2022
steps:
- name: Check out Git repository
uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm i
- name: "Build"
run: npm run build:appx
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: windows-build
retention-days: 3
path: |
build
!build/*-unpacked
!build/.icon-ico

View File

@@ -2,6 +2,97 @@
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.7.35-beta.0](https://github.com/antares-sql/antares/compare/v0.7.34...v0.7.35-beta.0) (2025-04-04)
### Bug Fixes
* custom connection icon disappears during connection, fixes [#939](https://github.com/antares-sql/antares/issues/939) ([1d1be55](https://github.com/antares-sql/antares/commit/1d1be55d3d4ea621364c37e75de616046371feeb))
* escape SQL parameters in update and delete for where clauses, fixes [#964](https://github.com/antares-sql/antares/issues/964) ([ba63b04](https://github.com/antares-sql/antares/commit/ba63b049a3a059e77256141dc7b761efbbbf8c1e))
* improved handling of query comments, fixes [#963](https://github.com/antares-sql/antares/issues/963) and [#580](https://github.com/antares-sql/antares/issues/580) ([d912faa](https://github.com/antares-sql/antares/commit/d912faa85042219315c9c5658d7f20fda560af44))
* use custom elements wrapper for foreign column and description in query ([973b0fc](https://github.com/antares-sql/antares/commit/973b0fc4be1dac25757e430e4520d6fc2212f93b))
### Improvements
* **translation:** update Spanish translations, closes [#962](https://github.com/antares-sql/antares/issues/962) ([acea18e](https://github.com/antares-sql/antares/commit/acea18e6f061adab7e79d1249e0e68555a620db5))
### [0.7.34](https://github.com/antares-sql/antares/compare/v0.7.33...v0.7.34) (2025-02-14)
### Bug Fixes
* issue with some SSH connections, definitely ([eb706c3](https://github.com/antares-sql/antares/commit/eb706c3e51e9cb7577febd291a33594c0650a34a))
### [0.7.33](https://github.com/antares-sql/antares/compare/v0.7.32...v0.7.33) (2025-02-14)
### Bug Fixes
* issue with some SSH connections, fixes [#947](https://github.com/antares-sql/antares/issues/947) ([3129bf4](https://github.com/antares-sql/antares/commit/3129bf4baa5e72b1d79df986605fd5fad1dce291))
### [0.7.32](https://github.com/antares-sql/antares/compare/v0.7.31...v0.7.32) (2025-02-14)
### Bug Fixes
* black background with light theme, fixes [#945](https://github.com/antares-sql/antares/issues/945) ([1d7053c](https://github.com/antares-sql/antares/commit/1d7053ce032efec8377d9500f2e24618f6381ab4))
* enhance SVG support in connection customization, fixes [#939](https://github.com/antares-sql/antares/issues/939) ([49a3589](https://github.com/antares-sql/antares/commit/49a3589536d2e75a14125be7b874e29b60fb56c4))
* improve error handling in SSH connection ([704f708](https://github.com/antares-sql/antares/commit/704f70819b21a42194d8f68cf9b58ba337f1ada7))
* **PostgreSQL:** error with materialized view tabs ([41e797f](https://github.com/antares-sql/antares/commit/41e797f9e27db66370d3ae7750c057f708af76f9))
### [0.7.31](https://github.com/antares-sql/antares/compare/v0.7.31-beta.5...v0.7.31) (2025-02-11)
### [0.7.31-beta.5](https://github.com/antares-sql/antares/compare/v0.7.31-beta.4...v0.7.31-beta.5) (2025-02-09)
### Bug Fixes
* **devtoolsInstaller:** improve file path handling and increase chromium version ([7595e89](https://github.com/antares-sql/antares/commit/7595e892238d2a93c454e9c1f236915fb458eed1))
* improve BLOB primary fields management, fixes [#938](https://github.com/antares-sql/antares/issues/938) ([72f8d42](https://github.com/antares-sql/antares/commit/72f8d4249f7f587d3e92b46cf7709ddab42107d4))
* **Linux:** restored AppImage auto updates ([0479e53](https://github.com/antares-sql/antares/commit/0479e5307c9a9d5f791e1c61fa772d331f6f7f1f))
* replace 'this.addNotification' with 'addNotification' in useResultTables.ts ([580bef7](https://github.com/antares-sql/antares/commit/580bef76ba390fc85df0892265f31392b80301bd))
* unable to delete rows from context menu ([0f93d70](https://github.com/antares-sql/antares/commit/0f93d70417871f02f9f64f203f6654fa1bf2004b))
### Improvements
* improve button styles of notes ([48cfa67](https://github.com/antares-sql/antares/commit/48cfa67889bd83228c109b7966c4acea4e542fc6))
* **MySQL:** long loading in table settings when no checks present ([9cda38e](https://github.com/antares-sql/antares/commit/9cda38e9d10e3000473863560d8be8f426a5ed17))
### [0.7.31-beta.4](https://github.com/antares-sql/antares/compare/v0.7.31-beta.3...v0.7.31-beta.4) (2025-01-31)
### Bug Fixes
* **Linux:** missing window management icons ([d34e56a](https://github.com/antares-sql/antares/commit/d34e56a517784dea16a7a53bc2249072a3b96596))
### [0.7.31-beta.3](https://github.com/antares-sql/antares/compare/v0.7.31-beta.2...v0.7.31-beta.3) (2025-01-31)
### Features
* implement a better query splitter for SQL queries, fixes [#926](https://github.com/antares-sql/antares/issues/926) ([96ae09f](https://github.com/antares-sql/antares/commit/96ae09fecad0c1fc8926d5dcf64cc779abe5ed49))
* **Linux:** update title bar for better Linux experience ([8e54f7b](https://github.com/antares-sql/antares/commit/8e54f7b80135768a33934bc9336239dee38401a5))
### Bug Fixes
* **MySQL:** adjust utf8mb3 encoding to resolve compatibility issue, fixes [#646](https://github.com/antares-sql/antares/issues/646) ([27387f1](https://github.com/antares-sql/antares/commit/27387f18a107fc6c09afec5f85134496ce764355))
### [0.7.31-beta.2](https://github.com/antares-sql/antares/compare/v0.7.31-beta.1...v0.7.31-beta.2) (2025-01-30)
### Features
* add developer tools and refresh buttons to console in development mode ([592d7b3](https://github.com/antares-sql/antares/commit/592d7b35170f8437ebc15221c97985e889fccb40))
### Bug Fixes
* fail to fill cell to datetime column(Postgre) fixes [#924](https://github.com/antares-sql/antares/issues/924) ([d3d7ab3](https://github.com/antares-sql/antares/commit/d3d7ab38c029fc5ec23767c6c86c49a96e4e329c))
* reorder condition when format the update data ([e493db5](https://github.com/antares-sql/antares/commit/e493db5112478ff121e4e77f69c21747c5d2e032))
### [0.7.31-beta.1](https://github.com/antares-sql/antares/compare/v0.7.31-beta.0...v0.7.31-beta.1) (2025-01-22)

16
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "antares",
"version": "0.7.31-beta.1",
"version": "0.7.35-beta.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "antares",
"version": "0.7.31-beta.1",
"version": "0.7.35-beta.0",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -22,6 +22,7 @@
"babel-loader": "~8.2.3",
"better-sqlite3": "~10.0.0",
"chalk": "~4.1.2",
"ciaplu": "^2.2.0",
"cpu-features": "^0.0.10",
"cross-env": "~7.0.2",
"css-loader": "~6.5.0",
@@ -5608,6 +5609,12 @@
"node": ">=8"
}
},
"node_modules/ciaplu": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/ciaplu/-/ciaplu-2.2.0.tgz",
"integrity": "sha512-7y8s0GMFpIKqX2kwiOEYbaX3P9tPIbX4x41uw8GZjkZ+y0QZrpY3PFjE2Ed6BOeFxcCWi7b85MYHeiRrVrlSOQ==",
"license": "MIT"
},
"node_modules/clean-css": {
"version": "5.3.2",
"license": "MIT",
@@ -20345,6 +20352,11 @@
"version": "3.9.0",
"dev": true
},
"ciaplu": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/ciaplu/-/ciaplu-2.2.0.tgz",
"integrity": "sha512-7y8s0GMFpIKqX2kwiOEYbaX3P9tPIbX4x41uw8GZjkZ+y0QZrpY3PFjE2Ed6BOeFxcCWi7b85MYHeiRrVrlSOQ=="
},
"clean-css": {
"version": "5.3.2",
"requires": {

View File

@@ -1,7 +1,7 @@
{
"name": "antares",
"productName": "Antares",
"version": "0.7.31-beta.1",
"version": "0.7.35-beta.0",
"description": "A modern, fast and productivity driven SQL client with a focus in UX.",
"license": "MIT",
"repository": "https://github.com/antares-sql/antares.git",
@@ -131,6 +131,7 @@
"babel-loader": "~8.2.3",
"better-sqlite3": "~10.0.0",
"chalk": "~4.1.2",
"ciaplu": "^2.2.0",
"cpu-features": "^0.0.10",
"cross-env": "~7.0.2",
"css-loader": "~6.5.0",

View File

@@ -1,5 +1,5 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
// @ts-check
const fs = require('fs');
const path = require('path');
const https = require('https');
@@ -7,13 +7,18 @@ const unzip = require('unzip-crx-3');
const { antares } = require('../package.json');
const extensionID = antares.devtoolsId;
const chromiumVersion = '124';
const destFolder = path.resolve(__dirname, `../misc/${extensionID}`);
const filePath = path.resolve(__dirname, `${destFolder}${extensionID}.crx`);
const fileUrl = `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${extensionID}%26uc&prodversion=32`;
const filePath = path.resolve(__dirname, `${destFolder}/${extensionID}.crx`);
const fileUrl = `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${extensionID}%26uc&prodversion=${chromiumVersion}`;
if (!fs.existsSync(destFolder))
fs.mkdirSync(destFolder, { recursive: true });
const fileStream = fs.createWriteStream(filePath);
const downloadFile = url => {
return new Promise((resolve, reject) => {
return /** @type {Promise<void>} */(new Promise((resolve, reject) => {
const request = https.get(url);
request.on('response', response => {
@@ -33,7 +38,7 @@ const downloadFile = url => {
});
request.on('error', reject);
request.end();
});
}));
};
(async () => {

View File

@@ -34,6 +34,7 @@ export interface ClientParams {
| { databasePath: string; readonly: boolean };
poolSize?: number;
logger?: () => void;
querySplitter?: (sql: string, clieng?: string) => string[];
}
/**

View File

@@ -1,3 +1,5 @@
import { match } from 'ciaplu';
function isJSON (str: string) {
try {
if (!['{', '['].includes(str.trim()[0]))
@@ -176,17 +178,13 @@ function isMD (str: string) {
}
export function langDetector (str: string) {
if (!str || !str.trim().length)
return 'text';
if (isJSON(str))
return 'json';
if (isHTML(str))
return 'html';
if (isSVG(str))
return 'svg';
if (isXML(str))
return 'xml';
if (isMD(str))
return 'markdown';
return 'text';
return match(str)
.when(() => !str || !str.trim().length, () => 'text')
.when(isJSON, () => 'json')
.when(isHTML, () => 'html')
.when(isSVG, () => 'svg')
.when(isXML, () => 'xml')
.when(isMD, () => 'markdown')
.otherwise(() => 'text')
.return();
}

View File

@@ -1,45 +1,25 @@
import { match } from 'ciaplu';
export function mimeFromHex (hex: string) {
switch (hex.substring(0, 4)) { // 2 bytes
case '424D':
return { ext: 'bmp', mime: 'image/bmp' };
case '1F8B':
return { ext: 'tar.gz', mime: 'application/gzip' };
case '0B77':
return { ext: 'ac3', mime: 'audio/vnd.dolby.dd-raw' };
case '7801':
return { ext: 'dmg', mime: 'application/x-apple-diskimage' };
case '4D5A':
return { ext: 'exe', mime: 'application/x-msdownload' };
case '1FA0':
case '1F9D':
return { ext: 'Z', mime: 'application/x-compress' };
default:
switch (hex.substring(0, 6)) { // 3 bytes
case 'FFD8FF':
return { ext: 'jpg', mime: 'image/jpeg' };
case '4949BC':
return { ext: 'jxr', mime: 'image/vnd.ms-photo' };
case '425A68':
return { ext: 'bz2', mime: 'application/x-bzip2' };
default:
switch (hex) { // 4 bites
case '89504E47':
return { ext: 'png', mime: 'image/png' };
case '47494638':
return { ext: 'gif', mime: 'image/gif' };
case '25504446':
return { ext: 'pdf', mime: 'application/pdf' };
case '504B0304':
return { ext: 'zip', mime: 'application/zip' };
case '425047FB':
return { ext: 'bpg', mime: 'image/bpg' };
case '4D4D002A':
return { ext: 'tif', mime: 'image/tiff' };
case '00000100':
return { ext: 'ico', mime: 'image/x-icon' };
default:
return { ext: '', mime: 'unknown ' + hex };
}
}
}
return match(hex.substring(0, 4)) // 2 bytes
.with('424D', () => ({ ext: 'bmp', mime: 'image/bmp' }))
.with('1F8B', () => ({ ext: 'tar.gz', mime: 'application/gzip' }))
.with('0B77', () => ({ ext: 'ac3', mime: 'audio/vnd.dolby.dd-raw' }))
.with('7801', () => ({ ext: 'dmg', mime: 'application/x-apple-diskimage' }))
.with('4D5A', () => ({ ext: 'exe', mime: 'application/x-msdownload' }))
.when((val) => ['1FA0', '1F9D'].includes(val), () => ({ ext: 'Z', mime: 'application/x-compress' }))
.extracting(() => hex.substring(0, 6)) // 3 bytes
.with('FFD8FF', () => ({ ext: 'jpg', mime: 'image/jpeg' }))
.with('4949BC', () => ({ ext: 'jxr', mime: 'image/vnd.ms-photo' }))
.with('425A68', () => ({ ext: 'bz2', mime: 'application/x-bzip2' }))
.extracting(() => hex) // 4 bytes
.with('89504E47', () => ({ ext: 'png', mime: 'image/png' }))
.with('47494638', () => ({ ext: 'gif', mime: 'image/gif' }))
.with('25504446', () => ({ ext: 'pdf', mime: 'application/pdf' }))
.with('504B0304', () => ({ ext: 'zip', mime: 'application/zip' }))
.with('425047FB', () => ({ ext: 'bpg', mime: 'image/bpg' }))
.with('4D4D002A', () => ({ ext: 'tif', mime: 'image/tiff' }))
.with('00000100', () => ({ ext: 'ico', mime: 'image/x-icon' }))
.otherwise(() => ({ ext: '', mime: 'unknown ' + hex }))
.return();
}

View File

@@ -3,18 +3,161 @@
import { lineString, point, polygon } from '@turf/helpers';
import { BIT, BLOB, DATE, DATETIME, FLOAT, IS_MULTI_SPATIAL, NUMBER, SPATIAL, TEXT_SEARCH } from 'common/fieldTypes';
import * as antares from 'common/interfaces/antares';
import { ClientCode } from 'common/interfaces/antares';
import * as moment from 'moment';
import customizations from '../customizations';
import { ClientCode } from '../interfaces/antares';
import { getArrayDepth } from './getArrayDepth';
import hexToBinary, { HexChar } from './hexToBinary';
/**
* Escapes a string fo SQL use
* Splits a SQL string into multiple queries based on semicolons (;).
* Handles BEGIN-END blocks, strings, comments, and PostgreSQL dollar-quoted tags.
*
* @param { String } string
* @returns { String } Escaped string
* @param {string} sql - The SQL string to split.
* @param {ClientCode} dbType - The database type (e.g., 'pg', 'mysql').
* @returns {string[]} - An array of separated SQL queries.
*/
export const querySplitter =(sql: string, dbType: ClientCode): string[] => {
const queries: string[] = [];
let currentQuery = '';
let insideBlock = false;
let insideString = false;
let stringDelimiter: string | null = null;
let insideDollarTag = false;
let dollarTagDelimiter: string | null = null;
// Regex patterns for BEGIN-END blocks, dollar tags in PostgreSQL, and semicolons
const beginRegex = /\bBEGIN\b/i;
const endRegex = /\bEND\b;/i;
const dollarTagRegex = /\$(\w+)?\$/; // Matches $tag$ or $$
// Split on semicolons, keeping semicolons attached to the lines
const lines = sql.split(/(?<=;)/);
for (let line of lines) {
line = line.trim();
if (!line) continue;
for (let i = 0; i < line.length; i++) {
const char = line[i];
// Handle string boundaries
if ((char === '\'' || char === '"') && (!insideString || char === stringDelimiter)) {
if (!insideString) {
insideString = true;
stringDelimiter = char;
}
else {
insideString = false;
stringDelimiter = null;
}
}
currentQuery += char;
if (dbType === 'pg') {
// Handle dollar-quoted blocks in PostgreSQL
if (!insideString && line.slice(i).match(dollarTagRegex)) {
const match = line.slice(i).match(dollarTagRegex);
if (match) {
const tag = match[0];
if (!insideDollarTag) {
insideDollarTag = true;
dollarTagDelimiter = tag;
currentQuery += tag;
i += tag.length - 1;
}
else if (dollarTagDelimiter === tag) {
insideDollarTag = false;
dollarTagDelimiter = null;
currentQuery += tag;
i += tag.length - 1;
}
}
}
}
// Check BEGIN-END blocks
if (!insideString && !insideDollarTag) {
if (beginRegex.test(line))
insideBlock = true;
if (insideBlock && endRegex.test(line))
insideBlock = false;
}
}
// Append the query if we encounter a semicolon outside a BEGIN-END block, outside a string, and outside dollar tags
if (!insideBlock && !insideString && !insideDollarTag && /;\s*$/.test(line)) {
queries.push(currentQuery.trim());
currentQuery = '';
}
}
// Add any remaining query
if (currentQuery.trim())
queries.push(currentQuery.trim());
return queries;
};
/**
* Removes all comments (both single-line and multi-line) from a SQL string.
*
* @param {string} sql - The SQL string to process.
* @returns {string} - The SQL string without comments.
*/
export const removeComments = (sql: string): string => {
let result = '';
let insideSingleLineComment = false;
let insideMultiLineComment = false;
for (let i = 0; i < sql.length; i++) {
const char = sql[i];
const nextChar = sql[i + 1] || '';
// Handle single-line comments (--)
if (!insideMultiLineComment && char === '-' && nextChar === '-')
insideSingleLineComment = true;
// Handle multi-line comments (/* */)
if (!insideSingleLineComment && char === '/' && nextChar === '*') {
insideMultiLineComment = true;
i++; // Skip the '*' character
continue;
}
if (insideMultiLineComment && char === '*' && nextChar === '/') {
insideMultiLineComment = false;
i++; // Skip the '/' character
continue;
}
// Skip characters inside comments
if (insideSingleLineComment) {
if (char === '\n')
insideSingleLineComment = false;
continue;
}
if (insideMultiLineComment)
continue;
// Append non-comment characters to the result
result += char;
}
return result;
};
/**
* Escapes a string for safe use in SQL queries.
*
* @param {string} string - The string to escape.
* @returns {string} - The escaped string.
*/
export const sqlEscaper = (string: string): string => {
// eslint-disable-next-line no-control-regex
@@ -27,6 +170,12 @@ export const sqlEscaper = (string: string): string => {
});
};
/**
* Converts a value into a GeoJSON object based on its type.
*
* @param {any} val - The value to convert.
* @returns {object} - The generated GeoJSON object.
*/
export const objectToGeoJSON = (val: any) => {
if (Array.isArray(val)) {
if (getArrayDepth(val) === 1)
@@ -38,6 +187,13 @@ export const objectToGeoJSON = (val: any) => {
return point([val.x, val.y]);
};
/**
* Escapes and wraps a string in quotes for safe use in SQL queries.
*
* @param {string} val - The string to process.
* @param {ClientCode} client - The database type (e.g., 'pg', 'mysql').
* @returns {string} - The escaped and quoted string.
*/
export const escapeAndQuote = (val: string, client: ClientCode) => {
const { stringsWrapper: sw } = customizations[client];
// eslint-disable-next-line no-control-regex
@@ -74,11 +230,17 @@ export const escapeAndQuote = (val: string, client: ClientCode) => {
return `${sw}${escapedVal}${sw}`;
};
/**
* Converts a value into a SQL string based on the field type and database type.
*
* @param {object} args - Arguments containing the value, database type, and field type.
* @returns {string} - The generated SQL string.
*/
export const valueToSqlString = (args: {
val: any;
client: ClientCode;
field: {type: string; datePrecision?: number; precision?: number | false; isArray?: boolean};
}): string => {
val: any;
client: ClientCode;
field: { type: string; datePrecision?: number; precision?: number | false; isArray?: boolean };
}): string => {
let parsedValue;
const { val, client, field } = args;
const { stringsWrapper: sw } = customizations[client];
@@ -165,13 +327,19 @@ export const valueToSqlString = (args: {
return parsedValue;
};
/**
* Converts a JSON array into an SQL INSERT query.
*
* @param {object} args - Arguments containing the JSON data, database type, fields, and options.
* @returns {string} - The generated SQL INSERT query.
*/
export const jsonToSqlInsert = (args: {
json: Record<string, any>[];
client: ClientCode;
fields: Record<string, {type: string; datePrecision: number}>;
table: string;
options?: {sqlInsertAfter: number; sqlInsertDivider: 'bytes' | 'rows'};
}) => {
json: Record<string, any>[];
client: ClientCode;
fields: Record<string, { type: string; datePrecision: number }>;
table: string;
options?: { sqlInsertAfter: number; sqlInsertDivider: 'bytes' | 'rows' };
}) => {
const { client, json, fields, table, options } = args;
const sqlInsertAfter = options && options.sqlInsertAfter ? options.sqlInsertAfter : 1;
const sqlInsertDivider = options && options.sqlInsertDivider ? options.sqlInsertDivider : 'rows';
@@ -193,7 +361,7 @@ export const jsonToSqlInsert = (args: {
(sqlInsertDivider === 'bytes' && queryLength >= sqlInsertAfter * 1024) ||
(sqlInsertDivider === 'rows' && rowsWritten === sqlInsertAfter)
) {
insertsString += insertStmt+';';
insertsString += insertStmt + ';';
insertStmt = `\nINSERT INTO ${ew}${table}${ew} (${fieldNames.join(', ')}) VALUES `;
rowsWritten = 0;
}
@@ -206,11 +374,18 @@ export const jsonToSqlInsert = (args: {
}
if (rowsWritten > 0)
insertsString += insertStmt+';';
insertsString += insertStmt + ';';
return insertsString;
};
/**
* Formats a JSON value for use in an SQL WHERE clause.
*
* @param {object} jsonValue - The JSON value to format.
* @param {ClientCode} clientType - The database type (e.g., 'pg', 'mysql').
* @returns {string} - The formatted SQL WHERE clause.
*/
export const formatJsonForSqlWhere = (jsonValue: object, clientType: antares.ClientCode) => {
const formattedValue = JSON.stringify(jsonValue);

View File

@@ -64,9 +64,9 @@ export default (connections: Record<string, antares.Client>) => {
username: conn.sshUser,
password: conn.sshPass,
port: conn.sshPort ? conn.sshPort : 22,
privateKey: conn.sshKey ? fs.readFileSync(conn.sshKey).toString() : null,
privateKey: conn.sshKey ? fs.readFileSync(conn.sshKey).toString() : undefined,
passphrase: conn.sshPassphrase,
keepaliveInterval: conn.sshKeepAliveInterval ? conn.sshKeepAliveInterval*1000 : null
keepaliveInterval: conn.sshKeepAliveInterval ? conn.sshKeepAliveInterval*1000 : undefined
};
}
@@ -90,11 +90,12 @@ export default (connections: Record<string, antares.Client>) => {
return { status: 'success' };
}
catch (err) {
catch (error) {
clearInterval(abortChecker);
if (!isLocalAborted)
return { status: 'error', response: err.toString() };
if (error instanceof AggregateError)
throw new Error(error.errors.reduce((acc, curr) => acc +' | '+ curr.message, ''));
else if (!isLocalAborted)
return { status: 'error', response: error.toString() };
else
return { status: 'abort', response: 'Connection aborted' };
}

View File

@@ -183,6 +183,7 @@ export default (connections: Record<string, antares.Client>) => {
const result = await connections[uid].raw(query, {
nest: true,
details: true,
comments: false,
schema,
tabUid,
autocommit

View File

@@ -221,7 +221,7 @@ export default (connections: Record<string, antares.Client>) => {
.update({ [params.field]: `= ${escapedParam}` })
.schema(params.schema)
.from(params.table)
.where({ [params.primary]: `= ${id}` })
.where({ [params.primary]: `= ${sqlEscaper(id)}` })
.limit(1)
.run();
}
@@ -233,7 +233,7 @@ export default (connections: Record<string, antares.Client>) => {
for (const key in orgRow) {
if (typeof orgRow[key] === 'string')
orgRow[key] = ` = '${orgRow[key]}'`;
orgRow[key] = ` = '${sqlEscaper(orgRow[key])}'`;
else if (typeof orgRow[key] === 'object' && orgRow[key] !== null)
orgRow[key] = formatJsonForSqlWhere(orgRow[key], connections[params.uid]._client);
else if (orgRow[key] === null)
@@ -290,7 +290,7 @@ export default (connections: Record<string, antares.Client>) => {
for (const row of params.rows) {
for (const key in row) {
if (typeof row[key] === 'string')
row[key] = `'${row[key]}'`;
row[key] = `'${sqlEscaper(row[key])}'`;
if (row[key] === null)
row[key] = 'IS NULL';
@@ -440,16 +440,17 @@ export default (connections: Record<string, antares.Client>) => {
ipcMain.handle('get-foreign-list', async (event, { uid, schema, table, column, description }) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
const { elementsWrapper: ew } = customizations[connections[uid]._client];
try {
const query = connections[uid]
.select(`${column} AS foreign_column`)
.select(`${ew}${column}${ew} AS foreign_column`)
.schema(schema)
.from(table)
.orderBy('foreign_column ASC');
if (description)
query.select(`LEFT(${description}, 20) AS foreign_description`);
query.select(`LEFT(${ew}${description}${ew}, 20) AS foreign_description`);
const results = await query.run<Record<string, string>>();

View File

@@ -1,28 +1,10 @@
import * as antares from 'common/interfaces/antares';
import { querySplitter } from 'common/libs/sqlUtils';
import mysql from 'mysql2/promise';
import * as pg from 'pg';
import SSH2Promise = require('@fabio286/ssh2-promise');
export type LoggerLevel = 'query' | 'error'
const ipcLogger = ({ content, cUid, level }: {content: string; cUid: string; level: LoggerLevel}) => {
if (level === 'error') {
if (process.type !== undefined) {
const mainWindow = require('electron').webContents.fromId(1);
mainWindow.send('non-blocking-exception', { cUid, message: content, date: new Date() });
}
if (process.env.NODE_ENV === 'development' && process.type === 'browser') console.log(content);
}
else if (level === 'query') {
// Remove comments, newlines and multiple spaces
const escapedSql = content.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' ');
if (process.type !== undefined) {
const mainWindow = require('electron').webContents.fromId(1);
mainWindow.send('query-log', { cUid, sql: escapedSql, date: new Date() });
}
if (process.env.NODE_ENV === 'development' && process.type === 'browser') console.log(escapedSql);
}
};
import { ipcLogger, LoggerLevel } from '../misc/ipcLogger';
/**
* As Simple As Possible Query Builder Core
@@ -34,6 +16,7 @@ export abstract class BaseClient {
protected _poolSize: number;
protected _ssh?: SSH2Promise;
protected _logger: (args: {content: string; cUid: string; level: LoggerLevel}) => void;
protected _querySplitter: (sql: string, client: antares.ClientCode) => string[];
protected _queryDefaults: antares.QueryBuilderObject;
protected _query: antares.QueryBuilderObject;
@@ -43,6 +26,7 @@ export abstract class BaseClient {
this._params = args.params;
this._poolSize = args.poolSize || undefined;
this._logger = args.logger || ipcLogger;
this._querySplitter = args.querySplitter || querySplitter;
this._queryDefaults = {
schema: '',

View File

@@ -1,6 +1,7 @@
import dataTypes from 'common/data-types/firebird';
import { FLOAT, NUMBER } from 'common/fieldTypes';
import * as antares from 'common/interfaces/antares';
import { removeComments } from 'common/libs/sqlUtils';
import * as firebird from 'node-firebird';
import * as path from 'path';
@@ -245,10 +246,10 @@ export class FirebirdSQLClient extends BaseClient {
name: db.name,
size: schemaSize,
tables: remappedTables,
functions: [],
functions: [] as null[],
procedures: remappedProcedures,
triggers: remappedTriggers,
schedulers: []
schedulers: [] as null[]
};
});
}
@@ -337,7 +338,7 @@ export class FirebirdSQLClient extends BaseClient {
return {
name: field.FIELD_NAME.trim(),
key: null,
key: null as null,
type: fieldType,
schema: schema,
table: table,
@@ -346,14 +347,14 @@ export class FirebirdSQLClient extends BaseClient {
datePrecision: field.FIELD_NAME.trim() === 'TIMESTAMP' ? 4 : null,
charLength: ![...NUMBER, ...FLOAT].includes(fieldType) ? field.FIELD_LENGTH : null,
nullable: !field.NOT_NULL,
unsigned: null,
zerofill: null,
unsigned: null as null,
zerofill: null as null,
order: field.FIELD_POSITION+1,
default: defaultValue,
charset: field.CHARSET,
collation: null,
collation: null as null,
autoIncrement: false,
onUpdate: null,
onUpdate: null as null,
comment: field.DESCRIPTION?.trim()
};
});
@@ -457,7 +458,7 @@ export class FirebirdSQLClient extends BaseClient {
table: table,
field: field.FKCOLUMN_NAME.trim(),
position: field.KEY_SEQ,
constraintPosition: null,
constraintPosition: null as null,
constraintName: field.FK_NAME.trim(),
refSchema: schema,
refTable: field.PKTABLE_NAME.trim(),
@@ -1036,14 +1037,12 @@ export class FirebirdSQLClient extends BaseClient {
};
if (!args.comments)
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments
sql = removeComments(sql);
const resultsArr = [];
let paramsArr = [];
const queries = args.split
? sql.split(/((?:[^;'"]*(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*')[^;'"]*)+)|;/gm)
.filter(Boolean)
.map(q => q.trim())
? this._querySplitter(sql, 'firebird')
: [sql];
let connection: firebird.Database | firebird.Transaction;

View File

@@ -2,9 +2,12 @@ import SSH2Promise = require('@fabio286/ssh2-promise');
import SSHConfig from '@fabio286/ssh2-promise/lib/sshConfig';
import dataTypes from 'common/data-types/mysql';
import * as antares from 'common/interfaces/antares';
import { removeComments } from 'common/libs/sqlUtils';
import * as mysql from 'mysql2/promise';
import * as EncodingToCharset from '../../../../node_modules/mysql2/lib/constants/encoding_charset.js';
import { BaseClient } from './BaseClient';
EncodingToCharset.utf8mb3 = 192; // To fix https://github.com/sidorares/node-mysql2/issues/1398 until not included in mysql2
export class MySQLClient extends BaseClient {
private _schema?: string;
@@ -171,13 +174,13 @@ export class MySQLClient extends BaseClient {
remotePort: this._params.port
});
dbConfig.host = (this._ssh.config as SSHConfig[] & { host: string }).host;
dbConfig.host = undefined;
dbConfig.port = tunnel.localPort;
}
catch (err) {
if (this._ssh) {
this._ssh.close();
this._ssh.closeTunnel();
this._ssh.close();
}
throw err;
}
@@ -225,8 +228,8 @@ export class MySQLClient extends BaseClient {
clearInterval(this._keepaliveTimer);
this._keepaliveTimer = undefined;
if (this._ssh) {
this._ssh.close();
this._ssh.closeTunnel();
this._ssh.close();
}
}
@@ -300,6 +303,8 @@ export class MySQLClient extends BaseClient {
await this.connect();
return this.getConnection(args, true);
}
else if (error instanceof AggregateError)
throw new Error(error.errors.reduce((acc, curr) => acc +' | '+ curr.message, ''));
else
throw new Error(error.message);
}
@@ -1747,15 +1752,13 @@ export class MySQLClient extends BaseClient {
};
if (!args.comments)
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments
sql = removeComments(sql);
const nestTables = args.nest ? '.' : false;
const resultsArr: antares.QueryResult[] = [];
let paramsArr = [];
const queries = args.split
? sql.split(/((?:[^;'"]*(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*')[^;'"]*)+)|;/gm)
.filter(Boolean)
.map(q => q.trim())
? this._querySplitter(sql, 'mysql')
: [sql];
const connection = await this.getConnection(args);

View File

@@ -2,6 +2,7 @@ import SSH2Promise = require('@fabio286/ssh2-promise');
import SSHConfig from '@fabio286/ssh2-promise/lib/sshConfig';
import dataTypes from 'common/data-types/postgresql';
import * as antares from 'common/interfaces/antares';
import { removeComments } from 'common/libs/sqlUtils';
import * as pg from 'pg';
import * as pgAst from 'pgsql-ast-parser';
import { ConnectionOptions } from 'tls';
@@ -179,7 +180,7 @@ export class PostgreSQLClient extends BaseClient {
remotePort: this._params.port
});
dbConfig.host = (this._ssh.config as SSHConfig[] & { host: string }).host;
dbConfig.host = undefined;
dbConfig.port = tunnel.localPort;
}
catch (err) {
@@ -348,7 +349,7 @@ export class PostgreSQLClient extends BaseClient {
matviewowner AS owner,
ispopulated AS is_populated,
definition,
'materializedview' AS table_type
'materializedView' AS table_type
FROM pg_matviews
WHERE schemaname = '${db.database}'
ORDER BY schema_name,
@@ -408,8 +409,8 @@ export class PostgreSQLClient extends BaseClient {
name: table.table_name,
type: table.table_type === 'VIEW'
? 'view'
: table.table_type === 'materializedview'
? 'materializedview'
: table.table_type === 'materializedView'
? 'materializedView'
: 'table',
rows: table.reltuples,
size: tableSize,
@@ -466,7 +467,7 @@ export class PostgreSQLClient extends BaseClient {
procedures: remappedProcedures,
triggers: remappedTriggers,
triggerFunctions: remappedTriggerFunctions,
schedulers: []
schedulers: [] as null[]
};
}
else {
@@ -532,7 +533,7 @@ export class PostgreSQLClient extends BaseClient {
return {
name: field.column_name,
key: null,
key: null as null,
type: type.toUpperCase(),
isArray,
schema: field.table_schema,
@@ -542,14 +543,14 @@ export class PostgreSQLClient extends BaseClient {
datePrecision: field.datetime_precision,
charLength: field.character_maximum_length,
nullable: field.is_nullable.includes('YES'),
unsigned: null,
zerofill: null,
unsigned: null as null,
zerofill: null as null,
order: field.ordinal_position,
default: field.column_default,
charset: field.character_set_name,
collation: field.collation_name,
autoIncrement: false,
onUpdate: null,
onUpdate: null as null,
comment: field.column_comment
};
});
@@ -1252,9 +1253,9 @@ export class PostgreSQLClient extends BaseClient {
return results.rows.map(async row => {
if (!row.pg_get_functiondef) {
return {
definer: null,
definer: null as null,
sql: '',
parameters: [],
parameters: [] as null[],
name: routine,
comment: '',
security: 'DEFINER',
@@ -1303,8 +1304,8 @@ export class PostgreSQLClient extends BaseClient {
name: routine,
comment: '',
security: row.pg_get_functiondef.includes('SECURITY DEFINER') ? 'DEFINER' : 'INVOKER',
deterministic: null,
dataAccess: null,
deterministic: null as null,
dataAccess: null as null,
language: row.pg_get_functiondef.match(/(?<=LANGUAGE )(.*)(?<=[\S+\n\r\s])/gm)[0]
};
})[0];
@@ -1368,9 +1369,9 @@ export class PostgreSQLClient extends BaseClient {
return results.rows.map(async row => {
if (!row.pg_get_functiondef) {
return {
definer: null,
definer: null as null,
sql: '',
parameters: [],
parameters: [] as null[],
name: func,
comment: '',
security: 'DEFINER',
@@ -1418,8 +1419,8 @@ export class PostgreSQLClient extends BaseClient {
name: func,
comment: '',
security: row.pg_get_functiondef.includes('SECURITY DEFINER') ? 'DEFINER' : 'INVOKER',
deterministic: null,
dataAccess: null,
deterministic: null as null,
dataAccess: null as null,
language: row.pg_get_functiondef.match(/(?<=LANGUAGE )(.*)(?<=[\S+\n\r\s])/gm)[0],
returns: row.pg_get_functiondef.match(/(?<=RETURNS )(.*)(?<=[\S+\n\r\s])/gm)[0].replace('SETOF ', '').toUpperCase()
};
@@ -1660,14 +1661,12 @@ export class PostgreSQLClient extends BaseClient {
};
if (!args.comments)
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments
sql = removeComments(sql);
const resultsArr: antares.QueryResult[] = [];
let paramsArr = [];
const queries = args.split
? sql.split(/(?!\B'[^']*);(?![^']*'\B)/gm)
.filter(Boolean)
.map(q => q.trim())
? this._querySplitter(sql, 'pg')
: [sql];
let connection: pg.Client | pg.PoolClient;

View File

@@ -2,6 +2,7 @@ import * as sqlite from 'better-sqlite3';
import dataTypes from 'common/data-types/sqlite';
import { DATETIME, FLOAT, NUMBER, TIME } from 'common/fieldTypes';
import * as antares from 'common/interfaces/antares';
import { removeComments } from 'common/libs/sqlUtils';
import { BaseClient } from './BaseClient';
@@ -124,10 +125,10 @@ export class SQLiteClient extends BaseClient {
name: db.name,
size: schemaSize,
tables: remappedTables,
functions: [],
procedures: [],
functions: [] as null[],
procedures: [] as null[],
triggers: remappedTriggers,
schedulers: []
schedulers: [] as null[]
};
}
else {
@@ -166,22 +167,22 @@ export class SQLiteClient extends BaseClient {
return {
name: field.name,
key: null,
key: null as null,
type: type.trim(),
schema: schema,
table: table,
numLength: [...NUMBER, ...FLOAT].includes(type) ? length : null,
datePrecision: null,
datePrecision: null as null,
charLength: ![...NUMBER, ...FLOAT].includes(type) ? length : null,
nullable: !field.notnull,
unsigned: null,
zerofill: null,
unsigned: null as null,
zerofill: null as null,
order: typeof field.cid === 'string' ? +field.cid + 1 : field.cid + 1,
default: field.dflt_value,
charset: null,
collation: null,
charset: null as null,
collation: null as null,
autoIncrement: false,
onUpdate: null,
onUpdate: null as null,
comment: ''
};
});
@@ -267,7 +268,7 @@ export class SQLiteClient extends BaseClient {
table: table,
field: field.from,
position: field.id + 1,
constraintPosition: null,
constraintPosition: null as null,
constraintName: field.id,
refSchema: schema,
refTable: field.table,
@@ -624,14 +625,12 @@ export class SQLiteClient extends BaseClient {
};
if (!args.comments)
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments
sql = removeComments(sql);
const resultsArr = [];
let paramsArr = [];
const queries = args.split
? sql.split(/((?:[^;'"]*(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*')[^;'"]*)+)|;/gm)
.filter(Boolean)
.map(q => q.trim())
? this._querySplitter(sql, 'sqlite')
: [sql];
let connection: sqlite.Database;

View File

@@ -0,0 +1,20 @@
export type LoggerLevel = 'query' | 'error'
export const ipcLogger = ({ content, cUid, level }: {content: string; cUid: string; level: LoggerLevel}) => {
if (level === 'error') {
if (process.type !== undefined) {
const mainWindow = require('electron').webContents.fromId(1);
mainWindow.send('non-blocking-exception', { cUid, message: content, date: new Date() });
}
if (process.env.NODE_ENV === 'development' && process.type === 'browser') console.log(content);
}
else if (level === 'query') {
// Remove comments, newlines and multiple spaces
const escapedSql = content.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' ');
if (process.type !== undefined) {
const mainWindow = require('electron').webContents.fromId(1);
mainWindow.send('query-log', { cUid, sql: escapedSql, date: new Date() });
}
if (process.env.NODE_ENV === 'development' && process.type === 'browser') console.log(escapedSql);
}
};

View File

@@ -43,7 +43,8 @@ async function createMainWindow () {
spellcheck: false
},
autoHideMenuBar: true,
titleBarStyle: isLinux ? 'default' :'hidden',
frame: !isLinux,
titleBarStyle: 'hidden',
titleBarOverlay: isWindows
? {
color: appTheme === 'dark' ? '#3f3f3f' : '#fff',
@@ -127,15 +128,25 @@ app.on('ready', async () => {
if (isWindows)
mainWindow.show();
if (isDevelopment && !isWindows)// Because on Windows you can open devtools from title-bar
mainWindow.webContents.openDevTools();
// if (isDevelopment && !isWindows)
// mainWindow.webContents.openDevTools();
process.on('uncaughtException', error => {
mainWindow.webContents.send('unhandled-exception', error);
if (error instanceof AggregateError) {
for (const e of error.errors)
mainWindow.webContents.send('unhandled-exception', e);
}
else
mainWindow.webContents.send('unhandled-exception', error);
});
process.on('unhandledRejection', error => {
mainWindow.webContents.send('unhandled-exception', error);
if (error instanceof AggregateError) {
for (const e of error.errors)
mainWindow.webContents.send('unhandled-exception', e);
}
else
mainWindow.webContents.send('unhandled-exception', error);
});
});

View File

@@ -39,11 +39,11 @@ const props = defineProps({
default: () => 'mdi'
},
flip: {
type: String as PropType<'horizontal' | 'vertical' | 'both'>,
type: String as PropType<'horizontal' | 'vertical' | 'both' | null>,
default: () => null
},
rotate: {
type: Number,
type: Number as PropType<number | null>,
default: () => null
}
});
@@ -55,8 +55,7 @@ const iconPath = computed(() => {
const base64 = getIconByUid(props.iconName)?.base64;
const svgString = Buffer
.from(base64, 'base64')
.toString('utf-8')
.replaceAll(/width="[^"]*"|height="[^"]*"/g, '');
.toString('utf-8');
return svgString;
}

View File

@@ -21,7 +21,23 @@
<a class="tab-link" @click="selectedTab = 'debug'">{{ t('application.debugConsole') }}</a>
</li>
</ul>
<button class="btn btn-clear mr-1" @click="resizeConsole(0)" />
<div class="d-flex">
<div
v-if="isDevelopment"
class="c-hand mr-2"
@click="openDevTools()"
>
<BaseIcon icon-name="mdiBugPlayOutline" :size="22" />
</div>
<div
v-if="isDevelopment"
class="c-hand mr-2"
@click="reload()"
>
<BaseIcon icon-name="mdiRefresh" :size="22" />
</div>
<button class="btn btn-clear mr-1" @click="resizeConsole(0)" />
</div>
</div>
<div
v-show="selectedTab === 'query'"
@@ -71,6 +87,7 @@
</BaseContextMenu>
</template>
<script setup lang="ts">
import { getCurrentWindow } from '@electron/remote';
import * as moment from 'moment';
import { storeToRefs } from 'pinia';
import { highlight } from 'sql-highlight';
@@ -112,6 +129,8 @@ const isHover = ref(false);
const isContext = ref(false);
const contextContent: Ref<string> = ref(null);
const contextEvent: Ref<MouseEvent> = ref(null);
const w = ref(getCurrentWindow());
const isDevelopment = ref(process.env.NODE_ENV === 'development');
const resize = (e: MouseEvent) => {
const el = queryConsole.value;
@@ -142,6 +161,14 @@ const copyLog = () => {
isContext.value = false;
};
const openDevTools = () => {
w.value.webContents.openDevTools();
};
const reload = () => {
w.value.reload();
};
watch(workspaceQueryLogs, async () => {
if (!isHover.value) {
await nextTick();

View File

@@ -127,7 +127,7 @@ const fakerGroups = computed(() => {
localType.value = 'datetime';
else if (TIME.includes(props.type))
localType.value = 'time';
else if (UUID.includes(props.type))
else if (UUID.includes(props.type) || (BLOB.includes(props.type) && props.field.key === 'pri'))
localType.value = 'uuid';
else
localType.value = 'none';
@@ -177,7 +177,7 @@ const inputProps = () => {
return { type: 'text', mask: datetimeMask };
}
if (BLOB.includes(props.type))
if (BLOB.includes(props.type) && props.field.key !== 'pri')
return { type: 'file', mask: false };
if (BIT.includes(props.type))

View File

@@ -131,8 +131,10 @@ import Application from '@/ipc-api/Application';
import { camelize } from '@/libs/camelize';
import { unproxify } from '@/libs/unproxify';
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
import { useNotificationsStore } from '@/stores/notifications';
const connectionsStore = useConnectionsStore();
const { addNotification } = useNotificationsStore();
const { addIcon, removeIcon, updateConnectionOrder, getConnectionName } = connectionsStore;
const { customIcons } = storeToRefs(connectionsStore);
@@ -225,12 +227,56 @@ const removeIconHandler = () => {
isContext.value = false;
};
const adjustSVGContent = (svgContent: string) => {
try {
const parser = new DOMParser();
const doc = parser.parseFromString(svgContent, 'image/svg+xml');
const parseError = doc.querySelector('parsererror');
if (parseError) {
addNotification({ status: 'error', message: parseError.textContent });
return null;
}
const svg = doc.documentElement;
if (svg.tagName.toLowerCase() !== 'svg') {
addNotification({ status: 'error', message: t('application.invalidFIle') });
return null;
}
if (!svg.hasAttribute('viewBox')) {
const width = svg.getAttribute('width') || '36';
const height = svg.getAttribute('height') || '36';
svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
}
svg.removeAttribute('width');
svg.removeAttribute('height');
const serializer = new XMLSerializer();
return serializer.serializeToString(svg);
}
catch (error) {
addNotification({ status: 'error', message: error.stack });
return null;
}
};
const openFile = async () => {
const result = await Application.showOpenDialog({ properties: ['openFile'], filters: [{ name: '"SVG"', extensions: ['svg'] }] });
const result = await Application.showOpenDialog({
properties: ['openFile'],
filters: [{ name: '"SVG"', extensions: ['svg'] }]
});
if (result && !result.canceled) {
const file = result.filePaths[0];
const content = await Application.readFile({ filePath: file, encoding: 'base64url' });
addIcon(content);
let content = await Application.readFile({ filePath: file, encoding: 'utf-8' });
content = adjustSVGContent(content);
const base64Content = Buffer.from(content).toString('base64');
addIcon(base64Content);
}
};

View File

@@ -47,65 +47,50 @@
<div class="tile-history-buttons">
<button
v-if="note.type === 'todo' && !note.isArchived"
class="btn btn-link pl-1"
class="btn btn-dark tooltip tooltip-left"
:data-tooltip="t('general.archive')"
@click.stop="$emit('archive-note', note.uid)"
>
<BaseIcon
icon-name="mdiCheck"
class="pr-1"
:size="22"
/> {{ t('general.archive') }}
<BaseIcon icon-name="mdiCheck" :size="22" />
</button>
<button
v-if="note.type === 'todo' && note.isArchived"
class="btn btn-link pl-1"
class="btn btn-dark tooltip tooltip-left"
:data-tooltip="t('general.undo')"
@click.stop="$emit('restore-note', note.uid)"
>
<BaseIcon
icon-name="mdiRestore"
class="pr-1"
:size="22"
/> {{ t('general.undo') }}
<BaseIcon icon-name="mdiRestore" :size="22" />
</button>
<button
v-if="note.type === 'query'"
class="btn btn-link pl-1"
class="btn btn-dark tooltip tooltip-left"
:data-tooltip="t('general.select')"
@click.stop="$emit('select-query', note.note)"
>
<BaseIcon
icon-name="mdiOpenInApp"
class="pr-1"
:size="22"
/> {{ t('general.select') }}
<BaseIcon icon-name="mdiOpenInApp" :size="22" />
</button>
<button
v-if="note.type === 'query'"
class="btn btn-link pl-1"
class="btn btn-dark tooltip tooltip-left"
:data-tooltip="t('general.copy')"
@click.stop="copyText(note.note)"
>
<BaseIcon
icon-name="mdiContentCopy"
class="pr-1"
:size="22"
/> {{ t('general.copy') }}
<BaseIcon icon-name="mdiContentCopy" :size="18" />
</button>
<button
v-if=" !note.isArchived"
class="btn btn-link pl-1"
class="btn btn-dark tooltip tooltip-left"
:data-tooltip="t('general.edit')"
@click.stop="$emit('edit-note')"
>
<BaseIcon
icon-name="mdiPencil"
class="pr-1"
:size="22"
/> {{ t('general.edit') }}
<BaseIcon icon-name="mdiPencil" :size="22" />
</button>
<button class="btn btn-link pl-1" @click.stop="$emit('delete-note', note.uid)">
<BaseIcon
icon-name="mdiDeleteForever"
class="pr-1"
:size="22"
/> {{ t('general.delete') }}
<button
class="btn btn-dark tooltip tooltip-left"
:data-tooltip="t('general.delete')"
@click.stop="$emit('delete-note', note.uid)"
>
<BaseIcon icon-name="mdiDeleteForever" :size="22" />
</button>
</div>
</div>
@@ -278,11 +263,14 @@ const highlightWord = (string: string) => {
button {
font-size: 0.7rem;
height: 1rem;
line-height: 1rem;
display: inline-flex;
align-items: center;
justify-content: center;
margin: 0 5px;
padding: 0;
height: 24px;
width: 24px;
}
}
}

View File

@@ -1,6 +1,5 @@
<template>
<div
v-if="!isLinux"
id="titlebar"
@dblclick="toggleFullScreen"
>
@@ -21,16 +20,27 @@
class="titlebar-element"
@click="openDevTools"
>
<BaseIcon icon-name="mdiBugPlayOutline" :size="24" />
<BaseIcon icon-name="mdiBugPlayOutline" :size="18" />
</div>
<div
v-if="isDevelopment"
class="titlebar-element"
@click="reload"
>
<BaseIcon icon-name="mdiRefresh" :size="24" />
<BaseIcon icon-name="mdiRefresh" :size="18" />
</div>
<div v-if="isWindows" :style="'width: 140px;'" />
<div v-if="isLinux" class="d-flex">
<div class="titlebar-element" @click="minimize">
<BaseIcon icon-name="mdiWindowMinimize" :size="18" />
</div>
<div class="titlebar-element" @click="toggleFullScreen">
<BaseIcon :icon-name="isMaximized ? 'mdiWindowRestore' : 'mdiWindowMaximize'" :size="18" />
</div>
<div class="titlebar-element" @click="closeApp">
<BaseIcon icon-name="mdiClose" :size="18" />
</div>
</div>
</div>
</div>
</template>
@@ -74,6 +84,18 @@ const windowTitle = computed(() => {
return [connectionName, ...breadcrumbs].join(' • ');
});
const openDevTools = () => {
w.value.webContents.openDevTools();
};
const reload = () => {
w.value.reload();
};
const minimize = () => {
w.value.minimize();
};
const toggleFullScreen = () => {
if (isMaximized.value)
w.value.unmaximize();
@@ -81,12 +103,8 @@ const toggleFullScreen = () => {
w.value.maximize();
};
const openDevTools = () => {
w.value.webContents.openDevTools();
};
const reload = () => {
w.value.reload();
const closeApp = () => {
ipcRenderer.send('close-app');
};
const onResize = () => {

View File

@@ -64,7 +64,7 @@
>
<BaseIcon
class="mt-1 mr-1"
:icon-name="['view', 'materializedview'].includes(element.elementType) ? 'mdiTableEye' : 'mdiTable'"
:icon-name="['view', 'materializedView'].includes(element.elementType) ? 'mdiTableEye' : 'mdiTable'"
:size="18"
/>
<span :title="`${t('general.data').toUpperCase()}: ${t(`database.${element.elementType}`)}`">
@@ -81,7 +81,7 @@
<a v-else-if="element.type === 'data'" class="tab-link">
<BaseIcon
class="mt-1 mr-1"
:icon-name="['view', 'materializedview'].includes(element.elementType) ? 'mdiTableEye' : 'mdiTable'"
:icon-name="['view', 'materializedView'].includes(element.elementType) ? 'mdiTableEye' : 'mdiTable'"
:size="18"
/>
<span :title="`${t('general.data').toUpperCase()}: ${t(`database.${element.elementType}`)}`">

View File

@@ -527,14 +527,14 @@ watch(() => props.connection, () => {
localConnection.value = JSON.parse(JSON.stringify(props.connection));
});
const startConnection = async () => {
const startConnection = async (): Promise<void> => {
await saveConnection();
isConnecting.value = true;
if (localConnection.value.ask)
isAsking.value = true;
else {
await connectWorkspace(localConnection.value, { signal: abortController.value.signal }).catch(() => undefined);
await connectWorkspace(localConnection.value, { signal: abortController.value.signal }).catch((): void => undefined);
isConnecting.value = false;
}
};
@@ -582,7 +582,7 @@ const continueTest = async (credentials: {user: string; password: string }) => {
try {
if (isConnecting.value) {
const params = Object.assign({}, props.connection, credentials);
await connectWorkspace(params, { signal: abortController.value.signal }).catch(() => undefined);
await connectWorkspace(params, { signal: abortController.value.signal }).catch((): void => undefined);
isConnecting.value = false;
}
else {

View File

@@ -143,7 +143,7 @@
:selected-schema="selectedSchema"
:context-event="miscContextEvent"
@open-create-view-tab="openCreateElementTab('view')"
@open-create-materializedview-tab="openCreateElementTab('materialized-view')"
@open-create-materializedView-tab="openCreateElementTab('materialized-view')"
@open-create-trigger-tab="openCreateElementTab('trigger')"
@open-create-trigger-function-tab="openCreateElementTab('trigger-function')"
@open-create-routine-tab="openCreateElementTab('routine')"

View File

@@ -16,9 +16,9 @@
/> {{ t('database.createNewView') }}</span>
</div>
<div
v-if="props.selectedMisc === 'materializedview'"
v-if="props.selectedMisc === 'materializedView'"
class="context-element"
@click="emit('open-create-materializedview-tab')"
@click="emit('open-create-materializedView-tab')"
>
<span class="d-flex">
<BaseIcon
@@ -106,7 +106,7 @@ const props = defineProps({
const emit = defineEmits([
'open-create-view-tab',
'open-create-materializedview-tab',
'open-create-materializedView-tab',
'open-create-trigger-tab',
'open-create-routine-tab',
'open-create-function-tab',

View File

@@ -121,7 +121,7 @@
<summary
class="accordion-header misc-name"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.trigger}"
@contextmenu.prevent="showMiscFolderContext($event, 'materializedview')"
@contextmenu.prevent="showMiscFolderContext($event, 'materializedView')"
>
<BaseIcon
class="misc-icon mr-1"
@@ -133,7 +133,7 @@
icon-name="mdiFolderOpen"
:size="18"
/>
{{ t('database.materializedview', 2) }}
{{ t('database.materializedView', 2) }}
</summary>
<div class="accordion-body">
<div>
@@ -496,9 +496,9 @@ const filteredViews = computed(() => {
const filteredMatViews = computed(() => {
if (props.searchMethod === 'elements')
return props.database.tables.filter(table => table.name.search(searchTerm.value) >= 0 && table.type === 'materializedview');
return props.database.tables.filter(table => table.name.search(searchTerm.value) >= 0 && table.type === 'materializedView');
else
return props.database.tables.filter(table => table.type === 'materializedview');
return props.database.tables.filter(table => table.type === 'materializedView');
});
const filteredTriggers = computed(() => {

View File

@@ -50,7 +50,7 @@
class="text-light mt-1 mr-1"
icon-name="mdiTableEye"
:size="18"
/> {{ t('database.materializedview') }}</span>
/> {{ t('database.materializedView') }}</span>
</div>
<div
v-if="workspace.customizations.triggerAdd"

View File

@@ -48,7 +48,7 @@
/> {{ t('application.settings') }}</span>
</div>
<div
v-if="selectedTable && selectedTable.type === 'materializedview' && customizations.materializedViewSettings"
v-if="selectedTable && selectedTable.type === 'materializedView' && customizations.materializedViewSettings"
class="context-element"
@click="openMaterializedViewSettingTab"
>

View File

@@ -195,7 +195,7 @@ const saveChanges = async () => {
uid: props.connection.uid,
schema: props.schema,
elementName: localView.value.name,
elementType: 'materializedview',
elementType: 'materializedView',
type: 'materialized-view-props'
});

View File

@@ -227,7 +227,7 @@ const saveChanges = async () => {
schema: props.schema,
elementName: oldName,
elementNewName: localView.value.name,
elementType: 'materializedview'
elementType: 'materializedView'
});
changeBreadcrumbs({ schema: props.schema, view: localView.value.name });

View File

@@ -458,6 +458,8 @@ const getFieldsData = async () => {
addNotification({ status: 'error', message: err.stack });
}
isLoading.value = false;
if (workspace.value.customizations.tableCheck) {
try { // Table checks
const { status, response } = await Tables.getTableChecks(params);
@@ -478,8 +480,6 @@ const getFieldsData = async () => {
addNotification({ status: 'error', message: err.stack });
}
}
isLoading.value = false;
};
const saveChanges = async () => {

View File

@@ -538,7 +538,7 @@ const closeContext = () => {
};
const showDeleteConfirmModal = (e: any) => {
if (e.code !== 'Delete') return;
if (e && e.code !== 'Delete') return;
if (e && e.path && ['INPUT', 'TEXTAREA', 'SELECT'].includes(e.path[0].tagName))
return;
if (selectedRows.value.length === 0) return;
@@ -563,6 +563,7 @@ const deleteSelected = () => {
table: getTable(resultsetIndex.value),
rows
};
console.log(params);
emit('delete-selected', params);
};
@@ -696,15 +697,15 @@ const fillCell = (event: { name: string; group: string; type: string }) => {
}
fakeValue = (fakerCustom as any)[event.group][event.name]();
if (['string', 'number'].includes(typeof fakeValue)) {
const isDateType = [...DATE, ...DATETIME].includes(selectedCell.value.type);
if (isDateType)
fakeValue = moment(fakeValue).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
else if (['string', 'number'].includes(typeof fakeValue)) {
if (typeof fakeValue === 'number')
fakeValue = String(fakeValue);
if (selectedCell.value.length)
fakeValue = fakeValue.substring(0, selectedCell.value.length < 1024 ? Number(selectedCell.value.length) : 1024);
}
else if ([...DATE, ...DATETIME].includes(selectedCell.value.type))
fakeValue = moment(fakeValue).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
else if (TIME.includes(selectedCell.value.type))
fakeValue = moment(fakeValue).format(`HH:mm:ss${datePrecision}`);

View File

@@ -440,7 +440,7 @@ const editON = async (field: string) => {
return;
}
if (BLOB.includes(type)) {
if (BLOB.includes(type) && props.fields[field].key !== 'pri') {
isBlobEditor.value = true;
editingContent.value = content || '';
fileToUpload.value = null;
@@ -458,9 +458,12 @@ const editON = async (field: string) => {
};
}
}
emit('start-editing', field);
return;
}
else if (BLOB.includes(type) && props.fields[field].key === 'pri')// Disable edit on BLOB primary until we are sure it's not problematic
return;
// Inline editable fields
editingContent.value = originalContent.value;

View File

@@ -56,10 +56,10 @@ export function useResultTables (uid: string, reloadTable: () => void) {
if (status === 'success')
reloadTable();
else
this.addNotification({ status: 'error', message: response });
addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
addNotification({ status: 'error', message: err.stack });
isQuering.value = false;
}
}

View File

@@ -141,7 +141,7 @@ export const csCZ = {
total: 'Celkem',
table: 'Tabulka | Tabulky',
view: 'Pohled | Pohledy',
materializedview: 'Materializovaný pohled',
materializedView: 'Materializovaný pohled',
definer: 'Definér',
algorithm: 'Algoritmus',
trigger: 'Trigger | Triggery',

View File

@@ -141,7 +141,7 @@ export const enUS = {
total: 'Total',
table: 'Table | Tables',
view: 'View | Views',
materializedview: 'Materialized view | Materialized views',
materializedView: 'Materialized view | Materialized views',
definer: 'Definer',
algorithm: 'Algorithm',
trigger: 'Trigger | Triggers',
@@ -401,6 +401,7 @@ export const enUS = {
ignoreDuplicates: 'Ignore duplicates',
wrongImportPassword: 'Wrong import password',
wrongFileFormat: 'Wrong file format',
invalidFile: 'Invalid file',
dataImportSuccess: 'Data successfully imported',
note: 'Note | Notes',
thereAreNoNotesYet: 'There are no notes yet',

View File

@@ -36,7 +36,7 @@ export const esES = {
stay: 'Mantener',
author: 'Autor',
upload: 'Subir',
browse: 'Navegar',
browse: 'Explorar',
content: 'Contenido',
cut: 'Cortar',
copy: 'Copiar',
@@ -91,7 +91,7 @@ export const esES = {
user: 'Usuario',
password: 'Contraseña',
credentials: 'Credenciales',
connect: 'Connectar',
connect: 'Conectar',
connected: 'Conectado',
disconnect: 'Desconectar',
disconnected: 'Desconectado',
@@ -117,7 +117,7 @@ export const esES = {
readOnlyMode: 'Solo lectura',
allConnections: 'Todas las conexiones',
searchForConnections: 'Buscar por conexiones',
keepAliveInterval: 'Tiempo de mantenimiento de conexión',
keepAliveInterval: 'Mantenimiento de conexión',
singleConnection: 'Conexión única'
},
database: { // Database related terms
@@ -141,7 +141,7 @@ export const esES = {
total: 'Total',
table: 'Tabla | Tablas',
view: 'Vista | Vistas',
materializedview: 'Vista Materializada | Vistas Materializadas',
materializedView: 'Vista Materializada | Vistas Materializadas',
definer: 'Definidor',
algorithm: 'Algoritmo',
trigger: 'Disparador | Disparadores',
@@ -198,11 +198,11 @@ export const esES = {
onUpdate: 'On UPDATE',
deleteField: 'Eliminar campo',
createNewIndex: 'Crear nuevo índice',
createNewCheck: 'Crear nueva verificación',
createNewCheck: 'Crear nueva validación',
checkClause: 'Comprobar cláusula',
addToIndex: 'Añadir al índice',
createNewTable: 'Crear nueva tabla',
emptyTable: 'Tabla vacía',
emptyTable: 'Vaciar tabla',
duplicateTable: 'Duplicar tabla',
deleteTable: 'Eliminar tabla',
exportTable: 'Exportar tabla',
@@ -376,10 +376,10 @@ export const esES = {
newFolder: 'Crear nueva carpeta',
outOfFolder: 'Fuera de la carpeta',
editConnectionAppearance: 'Modificar apariencia de conexión',
defaultCopyType: 'Default copy type',
defaultCopyType: 'Copiar fichero por defecto',
showTableSize: 'Mostrar tamaño de tabla en la barra lateral',
showTableSizeDescription: 'Solo para MySQL/MariaDB. Habilitar esta opción puede afectar al rendimiento en esquemas con muchas tablas.',
switchSearchMethod: 'Switch search method',
switchSearchMethod: 'Cambiar método de búsqueda',
phpArray: 'Array de PHP',
closeAllTabs: 'Cerrar todas las pestañas',
closeOtherTabs: 'Cerrar las otras pestañas',
@@ -396,27 +396,32 @@ export const esES = {
importDataExplanation: 'Importará un fichero con extensión .antares que contiene conexiones. Necesitará la contraseña con la que se encriptó el mismo.',
includeConnectionPasswords: 'Incluir contraseñas de conexión',
includeFolders: 'Incluir carpetas',
encryptionPassword: 'Encryption password',
encryptionPasswordError: 'The encryption password must be at least 8 characters long.',
ignoreDuplicates: 'Ignore duplicates',
wrongImportPassword: 'Wrong import password',
wrongFileFormat: 'Wrong file format',
dataImportSuccess: 'Data successfully imported',
note: 'Note | Notes',
thereAreNoNotesYet: 'There are no notes yet',
addNote: 'Add note',
editNote: 'Edit note',
saveAsNote: 'Save as note',
showArchivedNotes: 'Show archived notes',
hideArchivedNotes: 'Hide archived notes',
tag: 'Tag', // Note tag,
saveFile: 'Save file',
saveFileAs: 'Save file as',
openFile: 'Open file',
openNotes: 'Open notes',
debugConsole: 'Debug console', // <- console tab name
executedQueries: 'Executed queries', // <- console tab name
sizeLimitError: 'Maximum size of {size} exceeded'
encryptionPassword: 'Contraseña de encriptado',
encryptionPasswordError: 'La contraseña de encriptado debe tener al menos 8 caracteres',
ignoreDuplicates: 'Ignorar duplicados',
wrongImportPassword: 'Contraseña de importación errónea',
wrongFileFormat: 'Formato de fichero erróneo',
invalidFile: 'Fichero no válido',
dataImportSuccess: 'Datos importados correctamente',
note: 'Nota | Notas',
thereAreNoNotesYet: 'No hay notas',
addNote: 'Nueva nota',
editNote: 'Modificar nota',
saveAsNote: 'Guardar como nota',
showArchivedNotes: 'Mostrar notas archivadas',
hideArchivedNotes: 'Ocultar notas archivadas',
tag: 'Etiqueta', // Note tag,
saveFile: 'Guardar fichero',
saveFileAs: 'Guardar fichero como...',
openFile: 'Abrir fichero',
openNotes: 'Abrir notas',
debugConsole: 'Consola de Depuración', // <- console tab name
executedQueries: 'Consultas realizadas', // <- console tab name
sizeLimitError: 'Tamaño maximo de {size} excedido',
fullScreen: 'Pantalla completa',
zoomIn: 'Mas zoom',
zoomOut: 'Menos zoom',
zoomReset: 'Restablecer zoom'
},
faker: { // Faker.js methods, used in random generated content
address: 'Dirección',
@@ -569,12 +574,12 @@ export const esES = {
alphaNumeric: 'Alfanumérico',
hexaDecimal: 'Hexadecimal',
fileName: 'Nombre de fichero',
commonFileName: 'Common file name',
commonFileName: 'Nombre de fichero común',
mimeType: 'Mime-Type',
commonFileType: 'Common file type',
commonFileExt: 'Common file extension',
commonFileType: 'Tipo de fichero común',
commonFileExt: 'Extensión de fichero común',
fileType: 'Tipo de fichero',
fileExt: 'Extension de fichero',
fileExt: 'Extensión de fichero',
directoryPath: 'Ruta de directorio',
filePath: 'Ruta de fichero',
semver: 'SemVer',

View File

@@ -140,7 +140,7 @@ export const heIL = {
total: 'סך הכל',
table: 'טבלה | טבלאות',
view: 'תצוגה | תצוגות',
materializedview: 'תצוגה ממומשת | תצוגות ממומשות',
materializedView: 'תצוגה ממומשת | תצוגות ממומשות',
definer: 'מגדיר',
algorithm: 'אלגוריתם',
trigger: 'טריגר | טריגרים',

View File

@@ -275,7 +275,7 @@ export const nlNL = {
savedQueries: 'Opgeslagen queries',
searchForElements: 'Zoek naar elementen',
searchForSchemas: 'Zoek naar schema\'s',
materializedview: 'Materialized view | Materialized views',
materializedView: 'Materialized view | Materialized views',
createNewMaterializedView: 'Materialized view maken',
newMaterializedView: 'Nieuwe materialized view'
},

View File

@@ -270,7 +270,7 @@ export const ruRU = {
importQueryErrors: 'Внимание: {n} ошибка возникла | Внимание: {n} ошибок произошло',
executedQueries: '{n} запрос выполнен | {n} запросов выполнено',
insert: 'Вставить',
materializedview: 'Материализованное представление | Материализованные представления',
materializedView: 'Материализованное представление | Материализованные представления',
exportTable: 'Экспорт таблицы',
createNewMaterializedView: 'Создать новое материализованное представление',
newMaterializedView: 'Новое материализованное представление',

View File

@@ -270,7 +270,7 @@ export const uzUZ = {
importQueryErrors: 'Diqqat: {n} xato yuz berdi | Diqqat: {n} xatolar yuz berdi',
executedQueries: '{n} soʻrov bajarildi | {n} soʻrovlar bajarildi',
insert: 'Kiritish',
materializedview: 'Materializatsiya qilingan korinish | Materializatsiya qilingan korinishlar',
materializedView: 'Materializatsiya qilingan korinish | Materializatsiya qilingan korinishlar',
exportTable: 'Jadvalni eksport qilish',
createNewMaterializedView: 'Yangi materializatsiya qilingan korinish yaratish',
newMaterializedView: 'Yangi materializatsiya qilingan korinish',

View File

@@ -132,7 +132,7 @@ export const zhCN = {
total: '总计',
table: '表 | 表',
view: '视图 | 视图',
materializedview: '实体化视图 | 实体化视图',
materializedView: '实体化视图 | 实体化视图',
definer: '定义者',
algorithm: '算法',
trigger: '触发器 | 触发器',

View File

@@ -29,13 +29,7 @@ $explorebar-width: 14rem;
$footer-height: 1.5rem;
@function get-excluding-size() {
@if $platform == linux {
@return $footer-height;
}
@else {
@return $footer-height + $titlebar-height;
}
@return $footer-height + $titlebar-height;
}
/* stylelint-disable-next-line function-no-unknown */

View File

@@ -20,6 +20,7 @@
body {
user-select: none;
background-color: #000;
}
a {

View File

@@ -1,5 +1,7 @@
/* stylelint-disable function-no-unknown */
.theme-light {
background: $body-bg;
::-webkit-scrollbar-track {
background: #fff;
}

View File

@@ -163,7 +163,8 @@ export const useConnectionsStore = defineStore('connections', {
uid: connection.uid,
client: connection.client,
icon: conn.icon,
name: conn.name
name: conn.name,
hasCustomIcon: conn.hasCustomIcon
};
}
return conn;

View File

@@ -151,7 +151,7 @@ export const useWorkspacesStore = defineStore('workspaces', {
this.workspaces = (this.workspaces as Workspace[]).map(workspace => workspace.uid === connection.uid
? {
...workspace,
structure: [],
structure: [] as WorkspaceStructure[],
breadcrumbs: {},
loadedSchemas: new Set(),
database: connection.database,
@@ -167,7 +167,7 @@ export const useWorkspacesStore = defineStore('workspaces', {
this.workspaces = (this.workspaces as Workspace[]).map(workspace => workspace.uid === connection.uid
? {
...workspace,
structure: [],
structure: [] as WorkspaceStructure[],
breadcrumbs: {},
loadedSchemas: new Set(),
connectionStatus: 'disconnected'
@@ -187,7 +187,7 @@ export const useWorkspacesStore = defineStore('workspaces', {
this.workspaces = (this.workspaces as Workspace[]).map(workspace => workspace.uid === connection.uid
? {
...workspace,
structure: [],
structure: [] as WorkspaceStructure[],
breadcrumbs: {},
loadedSchemas: new Set(),
connectionStatus: 'failed'
@@ -200,9 +200,7 @@ export const useWorkspacesStore = defineStore('workspaces', {
return reject(new Error('Connection aborted by user'));
else {
let clientCustomizations: Customizations;
const { updateLastConnection } = connectionsStore;
updateLastConnection(connection.uid);
connectionsStore.updateLastConnection(connection.uid);
switch (connection.client) {
case 'mysql':
@@ -418,7 +416,7 @@ export const useWorkspacesStore = defineStore('workspaces', {
this.workspaces = (this.workspaces as Workspace[]).map(workspace => workspace.uid === uid
? {
...workspace,
structure: [],
structure: [] as WorkspaceStructure[],
breadcrumbs: {},
loadedSchemas: new Set(),
connectionStatus: 'disconnected'

View File

@@ -3,6 +3,7 @@
declare module '@/App.vue';
declare module 'v-mask';
declare module 'json2php';
declare module '*/encoding_charset.js';
declare module 'vuedraggable' {// <- to export as default
const draggableComponent: import('vue').DefineComponent<{
list: {

View File

@@ -1,8 +1,7 @@
const path = require('path');
const webpack = require('webpack');
const ProgressPlugin = require('progress-webpack-plugin');
const { dependencies, devDependencies, version } = require('./package.json');
const { dependencies, devDependencies } = require('./package.json');
const externals = Object.keys(dependencies).concat(Object.keys(devDependencies));
const isDevMode = process.env.NODE_ENV === 'development';
@@ -48,13 +47,7 @@ module.exports = { // Main
}
},
plugins: [
new ProgressPlugin(true),
new webpack.DefinePlugin({
'process.env': {
PACKAGE_VERSION: `"${version}"`,
DISTRIBUTION: `"${process.env.DISTRIBUTION || 'none'}"`
}
})
new ProgressPlugin(true)
],
module: {
rules: [