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

Compare commits

..

27 Commits

Author SHA1 Message Date
dependabot[bot]
67e849bc66 chore(deps): bump webpack from 5.91.0 to 5.98.0
Bumps [webpack](https://github.com/webpack/webpack) from 5.91.0 to 5.98.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.91.0...v5.98.0)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-01 19:25:34 +00: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
47 changed files with 948 additions and 442 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,62 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.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) ### [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)

684
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "antares", "name": "antares",
"productName": "Antares", "productName": "Antares",
"version": "0.7.31-beta.2", "version": "0.7.33",
"description": "A modern, fast and productivity driven SQL client with a focus in UX.", "description": "A modern, fast and productivity driven SQL client with a focus in UX.",
"license": "MIT", "license": "MIT",
"repository": "https://github.com/antares-sql/antares.git", "repository": "https://github.com/antares-sql/antares.git",
@@ -131,6 +131,7 @@
"babel-loader": "~8.2.3", "babel-loader": "~8.2.3",
"better-sqlite3": "~10.0.0", "better-sqlite3": "~10.0.0",
"chalk": "~4.1.2", "chalk": "~4.1.2",
"ciaplu": "^2.2.0",
"cpu-features": "^0.0.10", "cpu-features": "^0.0.10",
"cross-env": "~7.0.2", "cross-env": "~7.0.2",
"css-loader": "~6.5.0", "css-loader": "~6.5.0",
@@ -173,7 +174,7 @@
"vue-i18n": "~9.13.1", "vue-i18n": "~9.13.1",
"vue-loader": "~16.8.3", "vue-loader": "~16.8.3",
"vuedraggable": "~4.1.0", "vuedraggable": "~4.1.0",
"webpack": "^5.91.0", "webpack": "^5.98.0",
"webpack-cli": "~4.9.1" "webpack-cli": "~4.9.1"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,5 +1,5 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck // @ts-check
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const https = require('https'); const https = require('https');
@@ -7,13 +7,18 @@ const unzip = require('unzip-crx-3');
const { antares } = require('../package.json'); const { antares } = require('../package.json');
const extensionID = antares.devtoolsId; const extensionID = antares.devtoolsId;
const chromiumVersion = '124';
const destFolder = path.resolve(__dirname, `../misc/${extensionID}`); const destFolder = path.resolve(__dirname, `../misc/${extensionID}`);
const filePath = path.resolve(__dirname, `${destFolder}${extensionID}.crx`); 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 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 fileStream = fs.createWriteStream(filePath);
const downloadFile = url => { const downloadFile = url => {
return new Promise((resolve, reject) => { return /** @type {Promise<void>} */(new Promise((resolve, reject) => {
const request = https.get(url); const request = https.get(url);
request.on('response', response => { request.on('response', response => {
@@ -33,7 +38,7 @@ const downloadFile = url => {
}); });
request.on('error', reject); request.on('error', reject);
request.end(); request.end();
}); }));
}; };
(async () => { (async () => {

View File

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

View File

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

View File

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

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, username: conn.sshUser,
password: conn.sshPass, password: conn.sshPass,
port: conn.sshPort ? conn.sshPort : 22, 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, 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' }; return { status: 'success' };
} }
catch (err) { catch (error) {
clearInterval(abortChecker); clearInterval(abortChecker);
if (error instanceof AggregateError)
if (!isLocalAborted) throw new Error(error.errors.reduce((acc, curr) => acc +' | '+ curr.message, ''));
return { status: 'error', response: err.toString() }; else if (!isLocalAborted)
return { status: 'error', response: error.toString() };
else else
return { status: 'abort', response: 'Connection aborted' }; 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 mysql from 'mysql2/promise';
import * as pg from 'pg'; import * as pg from 'pg';
import SSH2Promise = require('@fabio286/ssh2-promise'); import SSH2Promise = require('@fabio286/ssh2-promise');
import { querySplitter } from 'common/libs/querySplitter';
export type LoggerLevel = 'query' | 'error' import { ipcLogger, LoggerLevel } from '../misc/ipcLogger';
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);
}
};
/** /**
* As Simple As Possible Query Builder Core * As Simple As Possible Query Builder Core
@@ -34,6 +16,7 @@ export abstract class BaseClient {
protected _poolSize: number; protected _poolSize: number;
protected _ssh?: SSH2Promise; protected _ssh?: SSH2Promise;
protected _logger: (args: {content: string; cUid: string; level: LoggerLevel}) => void; protected _logger: (args: {content: string; cUid: string; level: LoggerLevel}) => void;
protected _querySplitter: (sql: string, client: antares.ClientCode) => string[];
protected _queryDefaults: antares.QueryBuilderObject; protected _queryDefaults: antares.QueryBuilderObject;
protected _query: antares.QueryBuilderObject; protected _query: antares.QueryBuilderObject;
@@ -43,6 +26,7 @@ export abstract class BaseClient {
this._params = args.params; this._params = args.params;
this._poolSize = args.poolSize || undefined; this._poolSize = args.poolSize || undefined;
this._logger = args.logger || ipcLogger; this._logger = args.logger || ipcLogger;
this._querySplitter = args.querySplitter || querySplitter;
this._queryDefaults = { this._queryDefaults = {
schema: '', schema: '',

View File

@@ -245,10 +245,10 @@ export class FirebirdSQLClient extends BaseClient {
name: db.name, name: db.name,
size: schemaSize, size: schemaSize,
tables: remappedTables, tables: remappedTables,
functions: [], functions: [] as null[],
procedures: remappedProcedures, procedures: remappedProcedures,
triggers: remappedTriggers, triggers: remappedTriggers,
schedulers: [] schedulers: [] as null[]
}; };
}); });
} }
@@ -337,7 +337,7 @@ export class FirebirdSQLClient extends BaseClient {
return { return {
name: field.FIELD_NAME.trim(), name: field.FIELD_NAME.trim(),
key: null, key: null as null,
type: fieldType, type: fieldType,
schema: schema, schema: schema,
table: table, table: table,
@@ -346,14 +346,14 @@ export class FirebirdSQLClient extends BaseClient {
datePrecision: field.FIELD_NAME.trim() === 'TIMESTAMP' ? 4 : null, datePrecision: field.FIELD_NAME.trim() === 'TIMESTAMP' ? 4 : null,
charLength: ![...NUMBER, ...FLOAT].includes(fieldType) ? field.FIELD_LENGTH : null, charLength: ![...NUMBER, ...FLOAT].includes(fieldType) ? field.FIELD_LENGTH : null,
nullable: !field.NOT_NULL, nullable: !field.NOT_NULL,
unsigned: null, unsigned: null as null,
zerofill: null, zerofill: null as null,
order: field.FIELD_POSITION+1, order: field.FIELD_POSITION+1,
default: defaultValue, default: defaultValue,
charset: field.CHARSET, charset: field.CHARSET,
collation: null, collation: null as null,
autoIncrement: false, autoIncrement: false,
onUpdate: null, onUpdate: null as null,
comment: field.DESCRIPTION?.trim() comment: field.DESCRIPTION?.trim()
}; };
}); });
@@ -457,7 +457,7 @@ export class FirebirdSQLClient extends BaseClient {
table: table, table: table,
field: field.FKCOLUMN_NAME.trim(), field: field.FKCOLUMN_NAME.trim(),
position: field.KEY_SEQ, position: field.KEY_SEQ,
constraintPosition: null, constraintPosition: null as null,
constraintName: field.FK_NAME.trim(), constraintName: field.FK_NAME.trim(),
refSchema: schema, refSchema: schema,
refTable: field.PKTABLE_NAME.trim(), refTable: field.PKTABLE_NAME.trim(),
@@ -1041,9 +1041,7 @@ export class FirebirdSQLClient extends BaseClient {
const resultsArr = []; const resultsArr = [];
let paramsArr = []; let paramsArr = [];
const queries = args.split const queries = args.split
? sql.split(/((?:[^;'"]*(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*')[^;'"]*)+)|;/gm) ? this._querySplitter(sql, 'firebird')
.filter(Boolean)
.map(q => q.trim())
: [sql]; : [sql];
let connection: firebird.Database | firebird.Transaction; 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 antares from 'common/interfaces/antares';
import * as mysql from 'mysql2/promise'; import * as mysql from 'mysql2/promise';
import * as EncodingToCharset from '../../../../node_modules/mysql2/lib/constants/encoding_charset.js';
import { BaseClient } from './BaseClient'; 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 { export class MySQLClient extends BaseClient {
private _schema?: string; private _schema?: string;
@@ -171,13 +173,13 @@ export class MySQLClient extends BaseClient {
remotePort: this._params.port remotePort: this._params.port
}); });
dbConfig.host = (this._ssh.config as SSHConfig[] & { host: string }).host; dbConfig.host = undefined;
dbConfig.port = tunnel.localPort; dbConfig.port = tunnel.localPort;
} }
catch (err) { catch (err) {
if (this._ssh) { if (this._ssh) {
this._ssh.close();
this._ssh.closeTunnel(); this._ssh.closeTunnel();
this._ssh.close();
} }
throw err; throw err;
} }
@@ -225,8 +227,8 @@ export class MySQLClient extends BaseClient {
clearInterval(this._keepaliveTimer); clearInterval(this._keepaliveTimer);
this._keepaliveTimer = undefined; this._keepaliveTimer = undefined;
if (this._ssh) { if (this._ssh) {
this._ssh.close();
this._ssh.closeTunnel(); this._ssh.closeTunnel();
this._ssh.close();
} }
} }
@@ -300,6 +302,8 @@ export class MySQLClient extends BaseClient {
await this.connect(); await this.connect();
return this.getConnection(args, true); return this.getConnection(args, true);
} }
else if (error instanceof AggregateError)
throw new Error(error.errors.reduce((acc, curr) => acc +' | '+ curr.message, ''));
else else
throw new Error(error.message); throw new Error(error.message);
} }
@@ -1753,9 +1757,7 @@ export class MySQLClient extends BaseClient {
const resultsArr: antares.QueryResult[] = []; const resultsArr: antares.QueryResult[] = [];
let paramsArr = []; let paramsArr = [];
const queries = args.split const queries = args.split
? sql.split(/((?:[^;'"]*(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*')[^;'"]*)+)|;/gm) ? this._querySplitter(sql, 'mysql')
.filter(Boolean)
.map(q => q.trim())
: [sql]; : [sql];
const connection = await this.getConnection(args); const connection = await this.getConnection(args);

View File

@@ -179,7 +179,7 @@ export class PostgreSQLClient extends BaseClient {
remotePort: this._params.port remotePort: this._params.port
}); });
dbConfig.host = (this._ssh.config as SSHConfig[] & { host: string }).host; dbConfig.host = undefined;
dbConfig.port = tunnel.localPort; dbConfig.port = tunnel.localPort;
} }
catch (err) { catch (err) {
@@ -348,7 +348,7 @@ export class PostgreSQLClient extends BaseClient {
matviewowner AS owner, matviewowner AS owner,
ispopulated AS is_populated, ispopulated AS is_populated,
definition, definition,
'materializedview' AS table_type 'materializedView' AS table_type
FROM pg_matviews FROM pg_matviews
WHERE schemaname = '${db.database}' WHERE schemaname = '${db.database}'
ORDER BY schema_name, ORDER BY schema_name,
@@ -408,8 +408,8 @@ export class PostgreSQLClient extends BaseClient {
name: table.table_name, name: table.table_name,
type: table.table_type === 'VIEW' type: table.table_type === 'VIEW'
? 'view' ? 'view'
: table.table_type === 'materializedview' : table.table_type === 'materializedView'
? 'materializedview' ? 'materializedView'
: 'table', : 'table',
rows: table.reltuples, rows: table.reltuples,
size: tableSize, size: tableSize,
@@ -466,7 +466,7 @@ export class PostgreSQLClient extends BaseClient {
procedures: remappedProcedures, procedures: remappedProcedures,
triggers: remappedTriggers, triggers: remappedTriggers,
triggerFunctions: remappedTriggerFunctions, triggerFunctions: remappedTriggerFunctions,
schedulers: [] schedulers: [] as null[]
}; };
} }
else { else {
@@ -532,7 +532,7 @@ export class PostgreSQLClient extends BaseClient {
return { return {
name: field.column_name, name: field.column_name,
key: null, key: null as null,
type: type.toUpperCase(), type: type.toUpperCase(),
isArray, isArray,
schema: field.table_schema, schema: field.table_schema,
@@ -542,14 +542,14 @@ export class PostgreSQLClient extends BaseClient {
datePrecision: field.datetime_precision, datePrecision: field.datetime_precision,
charLength: field.character_maximum_length, charLength: field.character_maximum_length,
nullable: field.is_nullable.includes('YES'), nullable: field.is_nullable.includes('YES'),
unsigned: null, unsigned: null as null,
zerofill: null, zerofill: null as null,
order: field.ordinal_position, order: field.ordinal_position,
default: field.column_default, default: field.column_default,
charset: field.character_set_name, charset: field.character_set_name,
collation: field.collation_name, collation: field.collation_name,
autoIncrement: false, autoIncrement: false,
onUpdate: null, onUpdate: null as null,
comment: field.column_comment comment: field.column_comment
}; };
}); });
@@ -1252,9 +1252,9 @@ export class PostgreSQLClient extends BaseClient {
return results.rows.map(async row => { return results.rows.map(async row => {
if (!row.pg_get_functiondef) { if (!row.pg_get_functiondef) {
return { return {
definer: null, definer: null as null,
sql: '', sql: '',
parameters: [], parameters: [] as null[],
name: routine, name: routine,
comment: '', comment: '',
security: 'DEFINER', security: 'DEFINER',
@@ -1303,8 +1303,8 @@ export class PostgreSQLClient extends BaseClient {
name: routine, name: routine,
comment: '', comment: '',
security: row.pg_get_functiondef.includes('SECURITY DEFINER') ? 'DEFINER' : 'INVOKER', security: row.pg_get_functiondef.includes('SECURITY DEFINER') ? 'DEFINER' : 'INVOKER',
deterministic: null, deterministic: null as null,
dataAccess: null, dataAccess: null as null,
language: row.pg_get_functiondef.match(/(?<=LANGUAGE )(.*)(?<=[\S+\n\r\s])/gm)[0] language: row.pg_get_functiondef.match(/(?<=LANGUAGE )(.*)(?<=[\S+\n\r\s])/gm)[0]
}; };
})[0]; })[0];
@@ -1368,9 +1368,9 @@ export class PostgreSQLClient extends BaseClient {
return results.rows.map(async row => { return results.rows.map(async row => {
if (!row.pg_get_functiondef) { if (!row.pg_get_functiondef) {
return { return {
definer: null, definer: null as null,
sql: '', sql: '',
parameters: [], parameters: [] as null[],
name: func, name: func,
comment: '', comment: '',
security: 'DEFINER', security: 'DEFINER',
@@ -1418,8 +1418,8 @@ export class PostgreSQLClient extends BaseClient {
name: func, name: func,
comment: '', comment: '',
security: row.pg_get_functiondef.includes('SECURITY DEFINER') ? 'DEFINER' : 'INVOKER', security: row.pg_get_functiondef.includes('SECURITY DEFINER') ? 'DEFINER' : 'INVOKER',
deterministic: null, deterministic: null as null,
dataAccess: null, dataAccess: null as null,
language: row.pg_get_functiondef.match(/(?<=LANGUAGE )(.*)(?<=[\S+\n\r\s])/gm)[0], 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() 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[] = []; const resultsArr: antares.QueryResult[] = [];
let paramsArr = []; let paramsArr = [];
const queries = args.split const queries = args.split
? sql.split(/(?!\B'[^']*);(?![^']*'\B)/gm) ? this._querySplitter(sql, 'pg')
.filter(Boolean)
.map(q => q.trim())
: [sql]; : [sql];
let connection: pg.Client | pg.PoolClient; let connection: pg.Client | pg.PoolClient;

View File

@@ -124,10 +124,10 @@ export class SQLiteClient extends BaseClient {
name: db.name, name: db.name,
size: schemaSize, size: schemaSize,
tables: remappedTables, tables: remappedTables,
functions: [], functions: [] as null[],
procedures: [], procedures: [] as null[],
triggers: remappedTriggers, triggers: remappedTriggers,
schedulers: [] schedulers: [] as null[]
}; };
} }
else { else {
@@ -166,22 +166,22 @@ export class SQLiteClient extends BaseClient {
return { return {
name: field.name, name: field.name,
key: null, key: null as null,
type: type.trim(), type: type.trim(),
schema: schema, schema: schema,
table: table, table: table,
numLength: [...NUMBER, ...FLOAT].includes(type) ? length : null, numLength: [...NUMBER, ...FLOAT].includes(type) ? length : null,
datePrecision: null, datePrecision: null as null,
charLength: ![...NUMBER, ...FLOAT].includes(type) ? length : null, charLength: ![...NUMBER, ...FLOAT].includes(type) ? length : null,
nullable: !field.notnull, nullable: !field.notnull,
unsigned: null, unsigned: null as null,
zerofill: null, zerofill: null as null,
order: typeof field.cid === 'string' ? +field.cid + 1 : field.cid + 1, order: typeof field.cid === 'string' ? +field.cid + 1 : field.cid + 1,
default: field.dflt_value, default: field.dflt_value,
charset: null, charset: null as null,
collation: null, collation: null as null,
autoIncrement: false, autoIncrement: false,
onUpdate: null, onUpdate: null as null,
comment: '' comment: ''
}; };
}); });
@@ -267,7 +267,7 @@ export class SQLiteClient extends BaseClient {
table: table, table: table,
field: field.from, field: field.from,
position: field.id + 1, position: field.id + 1,
constraintPosition: null, constraintPosition: null as null,
constraintName: field.id, constraintName: field.id,
refSchema: schema, refSchema: schema,
refTable: field.table, refTable: field.table,
@@ -629,9 +629,7 @@ export class SQLiteClient extends BaseClient {
const resultsArr = []; const resultsArr = [];
let paramsArr = []; let paramsArr = [];
const queries = args.split const queries = args.split
? sql.split(/((?:[^;'"]*(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*')[^;'"]*)+)|;/gm) ? this._querySplitter(sql, 'sqlite')
.filter(Boolean)
.map(q => q.trim())
: [sql]; : [sql];
let connection: sqlite.Database; 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 spellcheck: false
}, },
autoHideMenuBar: true, autoHideMenuBar: true,
titleBarStyle: isLinux ? 'default' :'hidden', frame: !isLinux,
titleBarStyle: 'hidden',
titleBarOverlay: isWindows titleBarOverlay: isWindows
? { ? {
color: appTheme === 'dark' ? '#3f3f3f' : '#fff', color: appTheme === 'dark' ? '#3f3f3f' : '#fff',
@@ -127,15 +128,25 @@ app.on('ready', async () => {
if (isWindows) if (isWindows)
mainWindow.show(); mainWindow.show();
if (isDevelopment && !isWindows)// Because on Windows you can open devtools from title-bar // if (isDevelopment && !isWindows)
mainWindow.webContents.openDevTools(); // mainWindow.webContents.openDevTools();
process.on('uncaughtException', error => { 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 => { 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' default: () => 'mdi'
}, },
flip: { flip: {
type: String as PropType<'horizontal' | 'vertical' | 'both'>, type: String as PropType<'horizontal' | 'vertical' | 'both' | null>,
default: () => null default: () => null
}, },
rotate: { rotate: {
type: Number, type: Number as PropType<number | null>,
default: () => null default: () => null
} }
}); });
@@ -55,8 +55,7 @@ const iconPath = computed(() => {
const base64 = getIconByUid(props.iconName)?.base64; const base64 = getIconByUid(props.iconName)?.base64;
const svgString = Buffer const svgString = Buffer
.from(base64, 'base64') .from(base64, 'base64')
.toString('utf-8') .toString('utf-8');
.replaceAll(/width="[^"]*"|height="[^"]*"/g, '');
return svgString; return svgString;
} }

View File

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

View File

@@ -131,8 +131,10 @@ import Application from '@/ipc-api/Application';
import { camelize } from '@/libs/camelize'; import { camelize } from '@/libs/camelize';
import { unproxify } from '@/libs/unproxify'; import { unproxify } from '@/libs/unproxify';
import { SidebarElement, useConnectionsStore } from '@/stores/connections'; import { SidebarElement, useConnectionsStore } from '@/stores/connections';
import { useNotificationsStore } from '@/stores/notifications';
const connectionsStore = useConnectionsStore(); const connectionsStore = useConnectionsStore();
const { addNotification } = useNotificationsStore();
const { addIcon, removeIcon, updateConnectionOrder, getConnectionName } = connectionsStore; const { addIcon, removeIcon, updateConnectionOrder, getConnectionName } = connectionsStore;
const { customIcons } = storeToRefs(connectionsStore); const { customIcons } = storeToRefs(connectionsStore);
@@ -225,12 +227,56 @@ const removeIconHandler = () => {
isContext.value = false; 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 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) { if (result && !result.canceled) {
const file = result.filePaths[0]; const file = result.filePaths[0];
const content = await Application.readFile({ filePath: file, encoding: 'base64url' }); let content = await Application.readFile({ filePath: file, encoding: 'utf-8' });
addIcon(content);
content = adjustSVGContent(content);
const base64Content = Buffer.from(content).toString('base64');
addIcon(base64Content);
} }
}; };

View File

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

View File

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

View File

@@ -64,7 +64,7 @@
> >
<BaseIcon <BaseIcon
class="mt-1 mr-1" 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" :size="18"
/> />
<span :title="`${t('general.data').toUpperCase()}: ${t(`database.${element.elementType}`)}`"> <span :title="`${t('general.data').toUpperCase()}: ${t(`database.${element.elementType}`)}`">
@@ -81,7 +81,7 @@
<a v-else-if="element.type === 'data'" class="tab-link"> <a v-else-if="element.type === 'data'" class="tab-link">
<BaseIcon <BaseIcon
class="mt-1 mr-1" 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" :size="18"
/> />
<span :title="`${t('general.data').toUpperCase()}: ${t(`database.${element.elementType}`)}`"> <span :title="`${t('general.data').toUpperCase()}: ${t(`database.${element.elementType}`)}`">

View File

@@ -143,7 +143,7 @@
:selected-schema="selectedSchema" :selected-schema="selectedSchema"
:context-event="miscContextEvent" :context-event="miscContextEvent"
@open-create-view-tab="openCreateElementTab('view')" @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-tab="openCreateElementTab('trigger')"
@open-create-trigger-function-tab="openCreateElementTab('trigger-function')" @open-create-trigger-function-tab="openCreateElementTab('trigger-function')"
@open-create-routine-tab="openCreateElementTab('routine')" @open-create-routine-tab="openCreateElementTab('routine')"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -538,7 +538,7 @@ const closeContext = () => {
}; };
const showDeleteConfirmModal = (e: any) => { 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)) if (e && e.path && ['INPUT', 'TEXTAREA', 'SELECT'].includes(e.path[0].tagName))
return; return;
if (selectedRows.value.length === 0) return; if (selectedRows.value.length === 0) return;
@@ -563,6 +563,7 @@ const deleteSelected = () => {
table: getTable(resultsetIndex.value), table: getTable(resultsetIndex.value),
rows rows
}; };
console.log(params);
emit('delete-selected', params); emit('delete-selected', params);
}; };

View File

@@ -440,7 +440,7 @@ const editON = async (field: string) => {
return; return;
} }
if (BLOB.includes(type)) { if (BLOB.includes(type) && props.fields[field].key !== 'pri') {
isBlobEditor.value = true; isBlobEditor.value = true;
editingContent.value = content || ''; editingContent.value = content || '';
fileToUpload.value = null; fileToUpload.value = null;
@@ -458,9 +458,12 @@ const editON = async (field: string) => {
}; };
} }
} }
emit('start-editing', field); emit('start-editing', field);
return; 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 // Inline editable fields
editingContent.value = originalContent.value; editingContent.value = originalContent.value;

View File

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

View File

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

View File

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

View File

@@ -141,7 +141,7 @@ export const esES = {
total: 'Total', total: 'Total',
table: 'Tabla | Tablas', table: 'Tabla | Tablas',
view: 'Vista | Vistas', view: 'Vista | Vistas',
materializedview: 'Vista Materializada | Vistas Materializadas', materializedView: 'Vista Materializada | Vistas Materializadas',
definer: 'Definidor', definer: 'Definidor',
algorithm: 'Algoritmo', algorithm: 'Algoritmo',
trigger: 'Disparador | Disparadores', trigger: 'Disparador | Disparadores',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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