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

Compare commits

...

28 Commits

Author SHA1 Message Date
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
48 changed files with 511 additions and 248 deletions

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,68 @@
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.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.32",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "antares",
"version": "0.7.31-beta.1",
"version": "0.7.32",
"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.32",
"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

@@ -0,0 +1,86 @@
import { ClientCode } from 'common/interfaces/antares';
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;
};

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

@@ -2,27 +2,9 @@ import * as antares from 'common/interfaces/antares';
import mysql from 'mysql2/promise';
import * as pg from 'pg';
import SSH2Promise = require('@fabio286/ssh2-promise');
import { querySplitter } from 'common/libs/querySplitter';
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

@@ -245,10 +245,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 +337,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 +346,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 +457,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(),
@@ -1041,9 +1041,7 @@ export class FirebirdSQLClient extends BaseClient {
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

@@ -4,7 +4,9 @@ import dataTypes from 'common/data-types/mysql';
import * as antares from 'common/interfaces/antares';
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,7 +173,7 @@ export class MySQLClient extends BaseClient {
remotePort: this._params.port
});
dbConfig.host = (this._ssh.config as SSHConfig[] & { host: string }).host;
dbConfig.host = this._ssh.config[0].host;
dbConfig.port = tunnel.localPort;
}
catch (err) {
@@ -300,6 +302,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);
}
@@ -1753,9 +1757,7 @@ export class MySQLClient extends BaseClient {
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

@@ -179,7 +179,7 @@ export class PostgreSQLClient extends BaseClient {
remotePort: this._params.port
});
dbConfig.host = (this._ssh.config as SSHConfig[] & { host: string }).host;
dbConfig.host = this._ssh.config[0].host;
dbConfig.port = tunnel.localPort;
}
catch (err) {
@@ -348,7 +348,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 +408,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 +466,7 @@ export class PostgreSQLClient extends BaseClient {
procedures: remappedProcedures,
triggers: remappedTriggers,
triggerFunctions: remappedTriggerFunctions,
schedulers: []
schedulers: [] as null[]
};
}
else {
@@ -532,7 +532,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 +542,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 +1252,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 +1303,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 +1368,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 +1418,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()
};
@@ -1665,9 +1665,7 @@ export class PostgreSQLClient extends BaseClient {
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

@@ -124,10 +124,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 +166,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 +267,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,
@@ -629,9 +629,7 @@ export class SQLiteClient extends BaseClient {
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

@@ -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

@@ -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',

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

@@ -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: [