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

Compare commits

..

21 Commits

Author SHA1 Message Date
020ce36312 chore(release): 0.4.2 2022-01-10 08:50:00 +01:00
b4545b178f feat(UI): textarea autofocus selecting a query tab, closes #166 2022-01-09 12:28:01 +01:00
d9a3eab015 perf(MySQL): support to ANSI_QUOTES sql_mode, closes #158 2022-01-05 18:23:31 +01:00
2ab49c218d Merge pull request #164 from toriphes/feat/keep-window-state
feat: keep window state
2021-12-28 19:30:51 +01:00
Giulio Ganci
8f9385d508 feat: save window state
open the main window in the last used position of the screen
2021-12-28 17:12:10 +01:00
0c002918eb feat(PostgreSQL): ability to cancel queries 2021-12-26 21:13:02 +01:00
3d56ec7b49 Merge pull request #157 from Fabio286/dependabot/npm_and_yarn/eslint-plugin-promise-6.0.0
build(deps-dev): bump eslint-plugin-promise from 5.2.0 to 6.0.0
2021-12-24 18:04:58 +01:00
48c3e6afc4 perf: hash for foreign key default names 2021-12-23 11:47:17 +01:00
dependabot[bot]
50a5c8fe1e build(deps-dev): bump eslint-plugin-promise from 5.2.0 to 6.0.0
Bumps [eslint-plugin-promise](https://github.com/xjamundx/eslint-plugin-promise) from 5.2.0 to 6.0.0.
- [Release notes](https://github.com/xjamundx/eslint-plugin-promise/releases)
- [Changelog](https://github.com/xjamundx/eslint-plugin-promise/blob/development/CHANGELOG.md)
- [Commits](https://github.com/xjamundx/eslint-plugin-promise/commits)

---
updated-dependencies:
- dependency-name: eslint-plugin-promise
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-20 19:03:06 +00:00
b0d1f115c9 ci: update actions/checkout to v2 2021-12-20 09:27:36 +01:00
a59f77f618 feat(MySQL): ability to cancel queries 2021-12-19 11:59:09 +01:00
e7a1858091 fix(SQLite): exception with some fields 2021-12-16 09:16:15 +01:00
648a51efe8 Merge pull request #154 from Fabio286/all-contributors/add-wenj91
docs: add wenj91 as a contributor for code
2021-12-14 09:49:24 +01:00
38962c4807 Merge pull request #153 from wenj91/master
[TypeError: Cannot read properties of undefined (reading 'type') #152] bugfix
2021-12-14 09:48:58 +01:00
allcontributors[bot]
43e4cdd4cf docs: update .all-contributorsrc [skip ci] 2021-12-14 08:48:00 +00:00
allcontributors[bot]
91046c1ac1 docs: update README.md [skip ci] 2021-12-14 08:47:59 +00:00
文杰
63f8b9b6a1 Merge branch 'Fabio286:master' into master 2021-12-14 09:40:42 +08:00
文杰
ed476d9b5c Merge pull request #1 from wenj91:152-bugfix
[TypeError: Cannot read properties of undefined (reading 'type') #152] bugfix
2021-12-14 09:40:02 +08:00
文杰
f41d8c0480 [TypeError: Cannot read properties of undefined (reading 'type') #152] bugfix 2021-12-14 01:37:59 +00:00
e3b54a8be1 Merge pull request #151 from datlechin/master
Update vi-VN translation
2021-12-13 08:08:30 +01:00
Ngo Quoc Dat
c2c0394624 Update vi-VN translation 2021-12-13 08:48:07 +07:00
26 changed files with 333 additions and 69 deletions

View File

@@ -120,6 +120,15 @@
"contributions": [
"code"
]
},
{
"login": "wenj91",
"name": "文杰",
"avatar_url": "https://avatars.githubusercontent.com/u/12549338?v=4",
"profile": "https://github.com/wenj91",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,

View File

@@ -12,7 +12,7 @@ jobs:
steps:
- name: Check out Git repository
uses: actions/checkout@v1
uses: actions/checkout@v2
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1

View File

@@ -12,7 +12,7 @@ jobs:
steps:
- name: Check out Git repository
uses: actions/checkout@v1
uses: actions/checkout@v2
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1

View File

@@ -12,7 +12,7 @@ jobs:
steps:
- name: Check out Git repository
uses: actions/checkout@v1
uses: actions/checkout@v2
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1

View File

@@ -2,6 +2,27 @@
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.4.2](https://github.com/Fabio286/antares/compare/v0.4.1...v0.4.2) (2022-01-10)
### Features
* **MySQL:** ability to cancel queries ([a59f77f](https://github.com/Fabio286/antares/commit/a59f77f618aea6156fc80fb832d3efcb9848411f))
* **PostgreSQL:** ability to cancel queries ([0c00291](https://github.com/Fabio286/antares/commit/0c002918eb0226f6b3f21ed62117495f86396fb1))
* save window state ([8f9385d](https://github.com/Fabio286/antares/commit/8f9385d50815635d091758ecd5d00884e3297ca0))
* **UI:** textarea autofocus selecting a query tab, closes [#166](https://github.com/Fabio286/antares/issues/166) ([b4545b1](https://github.com/Fabio286/antares/commit/b4545b178f795712c781a3f4fc35eec31b5ad902))
### Bug Fixes
* **SQLite:** exception with some fields ([e7a1858](https://github.com/Fabio286/antares/commit/e7a18580915e7739bfa97948c6a0c4fc90a7e78a))
### Improvements
* hash for foreign key default names ([48c3e6a](https://github.com/Fabio286/antares/commit/48c3e6afc43c51f70a16703f1a71194f43da7a3e))
* **MySQL:** support to ANSI_QUOTES sql_mode, closes [#158](https://github.com/Fabio286/antares/issues/158) ([d9a3eab](https://github.com/Fabio286/antares/commit/d9a3eab015302e9f23112f659658073ab3242191))
### [0.4.1](https://github.com/Fabio286/antares/compare/v0.4.0...v0.4.1) (2021-12-11)

View File

@@ -130,6 +130,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center"><a href="https://github.com/IsamuSugi"><img src="https://avatars.githubusercontent.com/u/7746658?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Isamu Sugiura</b></sub></a><br /><a href="#translation-IsamuSugi" title="Translation">🌍</a></td>
<td align="center"><a href="http://rsacchetto.nexxontech.it/"><img src="https://avatars.githubusercontent.com/u/18429412?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Riccardo Sacchetto</b></sub></a><br /><a href="#platform-Occhioverde" title="Packaging/porting to new platform">📦</a></td>
<td align="center"><a href="https://kilianstallinger.com"><img src="https://avatars.githubusercontent.com/u/5290318?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kilian Stallinger</b></sub></a><br /><a href="https://github.com/Fabio286/antares/commits?author=kilianstallz" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/wenj91"><img src="https://avatars.githubusercontent.com/u/12549338?v=4?s=100" width="100px;" alt=""/><br /><sub><b>文杰</b></sub></a><br /><a href="https://github.com/Fabio286/antares/commits?author=wenj91" title="Code">💻</a></td>
</tr>
</table>

View File

@@ -1,7 +1,7 @@
{
"name": "antares",
"productName": "Antares",
"version": "0.4.1",
"version": "0.4.2",
"description": "A modern, fast and productivity driven SQL client with a focus in UX.",
"license": "MIT",
"repository": "https://github.com/Fabio286/antares.git",
@@ -111,6 +111,7 @@
"electron-log": "^4.4.1",
"electron-store": "^8.0.1",
"electron-updater": "^4.3.9",
"electron-window-state": "^5.0.3",
"faker": "^5.5.3",
"marked": "^4.0.0",
"moment": "^2.29.1",
@@ -136,13 +137,13 @@
"cross-env": "^7.0.2",
"css-loader": "^6.5.0",
"electron": "^16.0.1",
"electron-builder": "^22.14.10",
"electron-builder": "^22.14.11",
"electron-devtools-installer": "^3.2.0",
"eslint": "^7.32.0",
"eslint-config-standard": "^16.0.3",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-vue": "^8.0.3",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0",

View File

@@ -11,6 +11,7 @@ module.exports = {
sslConnection: false,
sshConnection: false,
fileConnection: false,
cancelQueries: false,
// Tools
processesList: false,
usersManagement: false,

View File

@@ -12,6 +12,7 @@ module.exports = {
engines: true,
sslConnection: true,
sshConnection: true,
cancelQueries: true,
// Tools
processesList: true,
// Structure

View File

@@ -10,6 +10,7 @@ module.exports = {
database: true,
sslConnection: true,
sshConnection: true,
cancelQueries: true,
// Tools
processesList: true,
// Structure

View File

@@ -135,7 +135,7 @@ export default connections => {
}
});
ipcMain.handle('raw-query', async (event, { uid, query, schema }) => {
ipcMain.handle('raw-query', async (event, { uid, query, schema, tabUid }) => {
if (!query) return;
try {
@@ -143,6 +143,7 @@ export default connections => {
nest: true,
details: true,
schema,
tabUid,
comments: false
});
@@ -152,4 +153,16 @@ export default connections => {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('kill-tab-query', async (event, { uid, tabUid }) => {
if (!tabUid) return;
try {
await connections[uid].killTabQuery(tabUid);
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
};

View File

@@ -9,6 +9,7 @@ export class MySQLClient extends AntaresCore {
super(args);
this._schema = null;
this._runningConnections = new Map();
this.types = {
0: 'DECIMAL',
@@ -136,8 +137,16 @@ export class MySQLClient extends AntaresCore {
if (!this._poolSize) {
this._connection = await mysql.createConnection(dbConfig);
// ANSI_QUOTES check
const res = await this.getVariable('sql_mode', 'global');
const sqlMode = res?.value.split(',');
const hasAnsiQuotes = sqlMode.includes('ANSI_QUOTES');
if (this._params.readonly)
await this.raw('SET SESSION TRANSACTION READ ONLY');
if (hasAnsiQuotes)
await this.raw(`SET SESSION sql_mode = "${sqlMode.filter(m => m !== 'ANSI_QUOTES').join(',')}"`);
}
else {
this._connection = mysql.createPool({
@@ -151,11 +160,21 @@ export class MySQLClient extends AntaresCore {
}
});
if (this._params.readonly) {
this._connection.on('connection', connection => {
// ANSI_QUOTES check
const res = await this.getVariable('sql_mode', 'global');
const sqlMode = res?.value.split(',');
const hasAnsiQuotes = sqlMode.includes('ANSI_QUOTES');
if (hasAnsiQuotes)
await this._connection.query(`SET SESSION sql_mode = "${sqlMode.filter(m => m !== 'ANSI_QUOTES').join(',')}"`);
this._connection.on('connection', connection => {
if (this._params.readonly)
connection.query('SET SESSION TRANSACTION READ ONLY');
});
}
if (hasAnsiQuotes)
connection.query(`SET SESSION sql_mode = "${sqlMode.filter(m => m !== 'ANSI_QUOTES').join(',')}"`);
});
}
}
@@ -424,7 +443,7 @@ export class MySQLClient extends AntaresCore {
return {
name: field.COLUMN_NAME,
key: field.COLUMN_KEY.toLowerCase(),
type: remappedFields ? remappedFields[field.COLUMN_NAME].type : field.DATA_TYPE,
type: (remappedFields && remappedFields[field.COLUMN_NAME]) ? remappedFields[field.COLUMN_NAME].type : field.DATA_TYPE,
schema: field.TABLE_SCHEMA,
table: field.TABLE_NAME,
numPrecision: field.NUMERIC_PRECISION,
@@ -436,7 +455,7 @@ export class MySQLClient extends AntaresCore {
unsigned: field.COLUMN_TYPE.includes('unsigned'),
zerofill: field.COLUMN_TYPE.includes('zerofill'),
order: field.ORDINAL_POSITION,
default: remappedFields ? remappedFields[field.COLUMN_NAME].default : field.COLUMN_DEFAULT,
default: (remappedFields && remappedFields[field.COLUMN_NAME]) ? remappedFields[field.COLUMN_NAME].default : field.COLUMN_DEFAULT,
charset: field.CHARACTER_SET_NAME,
collation: field.COLLATION_NAME,
autoIncrement: field.EXTRA.includes('auto_increment'),
@@ -1139,6 +1158,26 @@ export class MySQLClient extends AntaresCore {
});
}
/**
* SHOW VARIABLES LIKE %variable%
*
* @param {String} variable
* @param {'global'|'session'|null} level
* @returns {Object} variable
* @memberof MySQLClient
*/
async getVariable (variable, level) {
const sql = `SHOW${level ? ' ' + level.toUpperCase() : ''} VARIABLES LIKE '%${variable}%'`;
const results = await this.raw(sql);
if (results.rows.length) {
return {
name: results.rows[0].Variable_name,
value: results.rows[0].Value
};
}
}
/**
* SHOW ENGINES
*
@@ -1210,10 +1249,26 @@ export class MySQLClient extends AntaresCore {
});
}
/**
*
* @param {number} id
* @returns {Promise<null>}
*/
async killProcess (id) {
return await this.raw(`KILL ${id}`);
}
/**
*
* @param {string} tabUid
* @returns {Promise<null>}
*/
async killTabQuery (tabUid) {
const id = this._runningConnections.get(tabUid);
if (id)
return await this.killProcess(id);
}
/**
* CREATE TABLE
*
@@ -1535,6 +1590,9 @@ export class MySQLClient extends AntaresCore {
const isPool = typeof this._connection.getConnection === 'function';
const connection = isPool ? await this._connection.getConnection() : this._connection;
if (args.tabUid && isPool)
this._runningConnections.set(args.tabUid, connection.connection.connectionId);
if (args.schema)
await connection.query(`USE \`${args.schema}\``);
@@ -1595,7 +1653,10 @@ export class MySQLClient extends AntaresCore {
});
}
catch (err) {
if (isPool) connection.release();
if (isPool) {
connection.release();
this._runningConnections.delete(args.tabUid);
}
reject(err);
}
@@ -1604,7 +1665,10 @@ export class MySQLClient extends AntaresCore {
keysArr = keysArr ? [...keysArr, ...response] : response;
}
catch (err) {
if (isPool) connection.release();
if (isPool) {
connection.release();
this._runningConnections.delete(args.tabUid);
}
reject(err);
}
}
@@ -1619,7 +1683,10 @@ export class MySQLClient extends AntaresCore {
keys: keysArr
});
}).catch((err) => {
if (isPool) connection.release();
if (isPool) {
connection.release();
this._runningConnections.delete(args.tabUid);
}
reject(err);
});
});
@@ -1627,7 +1694,10 @@ export class MySQLClient extends AntaresCore {
resultsArr.push({ rows, report, fields, keys, duration });
}
if (isPool) connection.release();
if (isPool) {
connection.release();
this._runningConnections.delete(args.tabUid);
}
return resultsArr.length === 1 ? resultsArr[0] : resultsArr;
}

View File

@@ -21,6 +21,7 @@ export class PostgreSQLClient extends AntaresCore {
super(args);
this._schema = null;
this._runningConnections = new Map();
this.types = {};
for (const key in types.builtins)
@@ -1088,10 +1089,26 @@ export class PostgreSQLClient extends AntaresCore {
});
}
/**
*
* @param {number} id
* @returns {Promise<null>}
*/
async killProcess (id) {
return await this.raw(`SELECT pg_terminate_backend(${id})`);
}
/**
*
* @param {string} tabUid
* @returns {Promise<null>}
*/
async killTabQuery (tabUid) {
const id = this._runningConnections.get(tabUid);
if (id)
return await this.raw(`SELECT pg_cancel_backend(${id})`);
}
/**
* CREATE TABLE
*
@@ -1396,6 +1413,8 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async raw (sql, args) {
if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder
args = {
nest: false,
details: false,
@@ -1404,9 +1423,6 @@ export class PostgreSQLClient extends AntaresCore {
...args
};
if (args.schema && args.schema !== 'public')
await this.use(args.schema);
if (!args.comments)
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments
@@ -1418,7 +1434,14 @@ export class PostgreSQLClient extends AntaresCore {
.map(q => q.trim())
: [sql];
if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder
const isPool = this._connection instanceof Pool;
const connection = isPool ? await this._connection.connect() : this._connection;
if (args.tabUid && isPool)
this._runningConnections.set(args.tabUid, connection.processID);
if (args.schema && args.schema !== 'public')
await this.use(args.schema);
for (const query of queries) {
if (!query) continue;
@@ -1428,15 +1451,12 @@ export class PostgreSQLClient extends AntaresCore {
let keysArr = [];
const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => {
this._connection.query({
rowMode: args.nest ? 'array' : null,
text: query
}, async (err, res) => {
timeStop = new Date();
(async () => {
try {
const res = await connection.query({ rowMode: args.nest ? 'array' : null, text: query });
timeStop = new Date();
if (err)
reject(err);
else {
let ast;
try {
@@ -1525,6 +1545,10 @@ export class PostgreSQLClient extends AntaresCore {
});
}
catch (err) {
if (isPool) {
connection.release();
this._runningConnections.delete(args.tabUid);
}
reject(err);
}
@@ -1533,6 +1557,10 @@ export class PostgreSQLClient extends AntaresCore {
keysArr = keysArr ? [...keysArr, ...response] : response;
}
catch (err) {
if (isPool) {
connection.release();
this._runningConnections.delete(args.tabUid);
}
reject(err);
}
}
@@ -1547,12 +1575,24 @@ export class PostgreSQLClient extends AntaresCore {
keys: keysArr
});
}
});
catch (err) {
if (isPool) {
connection.release();
this._runningConnections.delete(args.tabUid);
}
reject(err);
}
})();
});
resultsArr.push({ rows, report, fields, keys, duration });
}
if (isPool) {
connection.release();
this._runningConnections.delete(args.tabUid);
}
return resultsArr.length === 1 ? resultsArr[0] : resultsArr;
}
}

View File

@@ -1,7 +1,7 @@
'use strict';
import sqlite from 'better-sqlite3';
import { AntaresCore } from '../AntaresCore';
import dataTypes from 'common/data-types/mysql';
import dataTypes from 'common/data-types/sqlite';
import { NUMBER, FLOAT, TIME, DATETIME } from 'common/fieldTypes';
export class SQLiteClient extends AntaresCore {
@@ -732,7 +732,7 @@ export class SQLiteClient extends AntaresCore {
if ([...TIME, ...DATETIME].includes(parsedType)) {
const firstNotNull = queryResult.find(res => res[field.name] !== null);
if (firstNotNull[field.name].includes('.'))
if (firstNotNull && firstNotNull[field.name].includes('.'))
length = firstNotNull[field.name].split('.').pop().length;
}

View File

@@ -3,6 +3,7 @@
import { app, BrowserWindow, /* session, */ nativeImage, Menu } from 'electron';
import * as path from 'path';
import Store from 'electron-store';
import * as windowStateKeeper from 'electron-window-state';
import * as remoteMain from '@electron/remote/main';
import ipcHandlers from './ipc-handlers';
@@ -18,12 +19,15 @@ process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
// global reference to mainWindow (necessary to prevent window from being garbage collected)
let mainWindow;
let mainWindowState;
async function createMainWindow () {
const icon = require('../renderer/images/logo-32.png');
const window = new BrowserWindow({
width: 1024,
height: 800,
width: mainWindowState.width,
height: mainWindowState.height,
x: mainWindowState.x,
y: mainWindowState.y,
minWidth: 900,
minHeight: 550,
title: 'Antares SQL',
@@ -41,6 +45,9 @@ async function createMainWindow () {
backgroundColor: '#1d1d1d'
});
mainWindowState.manage(window);
window.on('moved', saveWindowState);
remoteMain.enable(window.webContents);
try {
@@ -70,6 +77,7 @@ async function createMainWindow () {
}
window.on('closed', () => {
window.removeListener('moved', saveWindowState);
mainWindow = null;
});
@@ -104,6 +112,11 @@ else {
// create main BrowserWindow when electron is ready
app.on('ready', async () => {
mainWindowState = windowStateKeeper({
defaultWidth: 1024,
defaultHeight: 800
});
mainWindow = await createMainWindow();
createAppMenu();
@@ -160,3 +173,7 @@ function createAppMenu () {
Menu.setApplicationMenu(menu);
}
function saveWindowState () {
mainWindowState.saveState(mainWindow);
}

View File

@@ -200,7 +200,7 @@ export default {
}
&::before {
content: '';
content: "";
height: 0;
width: 3px;
transition: height 0.2s;

View File

@@ -18,7 +18,6 @@
<li
v-for="(tab, i) of draggableTabs"
:key="i"
:ref="selectedTab === tab.uid ? 'tab-selected' : ''"
class="tab-item tab-draggable"
draggable="true"
:class="{'active': selectedTab === tab.uid}"
@@ -568,7 +567,7 @@ export default {
return this.workspace ? this.workspace.selectedTab : null;
},
queryTabs () {
return this.workspace.tabs.filter(tab => tab.type === 'query');
return this.workspace ? this.workspace.tabs.filter(tab => tab.type === 'query') : [];
},
schemaChild () {
for (const key in this.workspace.breadcrumbs) {
@@ -584,16 +583,12 @@ export default {
}
},
watch: {
selectedTab (newVal, oldVal) {
if (newVal !== oldVal) {
queryTabs: function (newVal, oldVal) {
if (newVal.length > oldVal.length) {
setTimeout(() => {
const element = this.$refs['tab-selected'] ? this.$refs['tab-selected'][0] : null;
if (element) {
element.setAttribute('tabindex', '-1');
element.focus();
element.removeAttribute('tabindex');
}
}, 50);
const scroller = this.$refs.tabWrap;
if (scroller) scroller.$el.scrollLeft = scroller.$el.scrollWidth;
}, 0);
}
}
},

View File

@@ -452,9 +452,9 @@ export default {
top: 0;
z-index: 2;
.schema-size{
visibility: hidden;
width: 22.5px;
.schema-size {
visibility: hidden;
width: 22.5px;
}
}
@@ -502,8 +502,8 @@ export default {
&:hover {
border-radius: $border-radius;
.schema-size{
visibility: visible;
.schema-size {
visibility: visible;
}
}
}

View File

@@ -289,7 +289,7 @@ export default {
addForeign () {
this.foreignProxy = [...this.foreignProxy, {
_antares_id: uidGen(),
constraintName: `FK_${this.foreignProxy.length + 1}`,
constraintName: `FK_${uidGen()}`,
refSchema: this.schema,
table: this.table,
refTable: '',

View File

@@ -4,6 +4,7 @@
class="workspace-query-tab column col-12 columns col-gapless no-outline p-0"
tabindex="0"
@keydown.116="runQuery(query)"
@keydown.75="killTabQuery"
@keydown.ctrl.alt.87="clear"
@keydown.ctrl.66="beautify"
@keydown.ctrl.71="openHistoryModal"
@@ -22,16 +23,29 @@
<div ref="resizer" class="query-area-resizer" />
<div class="workspace-query-runner-footer">
<div class="workspace-query-buttons">
<button
class="btn btn-primary btn-sm"
:class="{'loading':isQuering}"
:disabled="!query"
title="F5"
@click="runQuery(query)"
>
<i class="mdi mdi-24px mdi-play pr-1" />
<span>{{ $t('word.run') }}</span>
</button>
<div @mouseenter="setCancelButtonVisibility(true)" @mouseleave="setCancelButtonVisibility(false)">
<button
v-if="showCancel && isQuering"
class="btn btn-primary btn-sm cancellable"
:disabled="!query"
:title="$t('word.cancel')"
@click="killTabQuery()"
>
<i class="mdi mdi-24px mdi-window-close" />
<span class="d-invisible pr-1">{{ $t('word.run') }}</span>
</button>
<button
v-else
class="btn btn-primary btn-sm"
:class="{'loading':isQuering}"
:disabled="!query"
title="F5"
@click="runQuery(query)"
>
<i class="mdi mdi-24px mdi-play pr-1" />
<span>{{ $t('word.run') }}</span>
</button>
</div>
<button
class="btn btn-link btn-sm mr-0"
:disabled="!query || isQuering"
@@ -110,7 +124,7 @@
</div>
</div>
</div>
<WorkspaceTabQueryEmptyState v-if="!results.length && !isQuering" />
<WorkspaceTabQueryEmptyState v-if="!results.length && !isQuering" :customizations="workspace.customizations" />
<div class="workspace-query-results p-relative column col-12">
<BaseLoader v-if="isQuering" />
<WorkspaceTabQueryTable
@@ -166,6 +180,8 @@ export default {
query: '',
lastQuery: '',
isQuering: false,
isCancelling: false,
showCancel: false,
results: [],
selectedSchema: null,
resultsCount: 0,
@@ -202,8 +218,13 @@ export default {
},
watch: {
isSelected (val) {
if (val)
if (val) {
this.changeBreadcrumbs({ schema: this.selectedSchema, query: `Query #${this.tab.index}` });
setTimeout(() => {
if (this.$refs.queryEditor)
this.$refs.queryEditor.editor.focus();
}, 0);
}
},
selectedSchema () {
this.changeBreadcrumbs({ schema: this.selectedSchema, query: `Query #${this.tab.index}` });
@@ -248,6 +269,7 @@ export default {
const params = {
uid: this.connection.uid,
schema: this.selectedSchema,
tabUid: this.tab.uid,
query
};
@@ -283,6 +305,29 @@ export default {
this.isQuering = false;
this.lastQuery = query;
},
async killTabQuery () {
if (this.isCancelling) return;
this.isCancelling = true;
try {
const params = {
uid: this.connection.uid,
tabUid: this.tab.uid
};
await Schema.killTabQuery(params);
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isCancelling = false;
},
setCancelButtonVisibility (val) {
if (this.workspace.customizations.cancelQueries)
this.showCancel = val;
},
reloadTable () {
this.runQuery(this.lastQuery);
},

View File

@@ -5,6 +5,9 @@
<div class="mb-4">
{{ $t('message.runQuery') }}
</div>
<div v-if="customizations.cancelQueries" class="mb-4">
{{ $t('message.killQuery') }}
</div>
<div class="mb-4">
{{ $t('word.format') }}
</div>
@@ -25,6 +28,9 @@
<div class="mb-4">
<code>F5</code>
</div>
<div v-if="customizations.cancelQueries" class="mb-4">
<code>CTRL</code> + <code>K</code>
</div>
<div class="mb-4">
<code>CTRL</code> + <code>B</code>
</div>
@@ -47,7 +53,10 @@
<script>
export default {
name: 'WorkspaceTabQueryEmptyState'
name: 'WorkspaceTabQueryEmptyState',
props: {
customizations: Object
}
};
</script>

View File

@@ -251,7 +251,8 @@ module.exports = {
killProcess: 'Kill process',
closeTab: 'Close tab',
goToDownloadPage: 'Go to download page',
readOnlyMode: 'Read-only mode'
readOnlyMode: 'Read-only mode',
killQuery: 'Kill query'
},
faker: {
address: 'Address',

View File

@@ -120,7 +120,11 @@ module.exports = {
new: 'Mới',
history: 'Lịch sử',
select: 'Chọn',
passphrase: 'Cụm mật khẩu'
passphrase: 'Cụm mật khẩu',
filter: 'Bộ lọc',
disabled: 'Đã tắt',
enable: 'Bật',
disable: 'Tắt'
},
message: {
appWelcome: 'Chào bạn đến với Antares SQL Client!',
@@ -244,7 +248,10 @@ module.exports = {
newTriggerFunction: 'Chức năng kích hoạt mới',
thereIsNoQueriesYet: 'Không có truy vấn nào',
searchForQueries: 'Tìm kiếm truy vấn',
killProcess: 'Huỷ quá trình'
killProcess: 'Huỷ quá trình',
closeTab: 'Đóng tab',
goToDownloadPage: 'Tới trang tải về',
readOnlyMode: 'Chế độ chỉ đọc'
},
faker: {
address: 'Địa chỉ',

View File

@@ -46,6 +46,10 @@ export default class {
return ipcRenderer.invoke('kill-process', params);
}
static killTabQuery (params) {
return ipcRenderer.invoke('kill-tab-query', params);
}
static useSchema (params) {
return ipcRenderer.invoke('use-schema', params);
}

View File

@@ -20,7 +20,7 @@
background-image: url("../images/svg/pg.svg");
}
&.dbi-sqlite {
&.dbi-sqlite {
background-image: url("../images/svg/sqlite.svg");
}

View File

@@ -59,6 +59,34 @@ option:checked {
text-overflow: ellipsis;
}
.cancellable {
color: transparent !important;
min-height: 0.8rem;
position: relative;
> .mdi,
> .span {
visibility: hidden;
}
&::after {
content: "\2715";
color: $light-color;
font-weight: 700;
top: 36%;
display: block;
height: 0.8rem;
left: 50%;
margin-left: -0.4rem;
margin-top: -0.4rem;
opacity: 1;
padding: 0;
position: absolute;
width: 0.8rem;
z-index: 1;
}
}
.workspace-tabs {
align-content: baseline;