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

Compare commits

..

30 Commits

Author SHA1 Message Date
dea5ec7513 chore(release): 0.1.2 2021-04-11 12:39:56 +02:00
be816e8588 perf(UI): improved setting modal rendering 2021-04-11 12:38:50 +02:00
1e938adc5d feat: in-app last release changelog 2021-04-11 12:35:16 +02:00
8735a0c5f9 feat(PostgreSQL): edit timezone in cell editor 2021-04-11 10:55:22 +02:00
3dde1c109e feat(PostgreSQL): procedures management 2021-04-10 20:38:46 +02:00
d0b3e1b1b8 feat(PostgreSQL): support of arrays in table settings 2021-04-09 19:31:41 +02:00
c20bff7bcb fix: deletion of rows from query results 2021-04-08 21:49:38 +02:00
9f5ec0276c fix: no foreign key select when cell value is NULL, closes #50 2021-04-08 18:02:16 +02:00
55932fe115 fix: cell edit doesn't properly use primary or unique index to update if both present, closes #51 2021-04-08 17:47:10 +02:00
d374372e20 fix: wrong datetime conversion when updating a row without an unique key 2021-04-07 15:05:11 +02:00
bb5f44681f fix(UI): white readonly inputs with dark theme 2021-04-07 09:20:11 +02:00
49a4e1cb7b fix(PostgreSQL): issue with selected schema different than public 2021-04-06 12:48:40 +02:00
c2f76e490a chore(release): 0.1.1 2021-04-03 12:21:34 +02:00
e349dd5eab feat: scratchpad to save persistent notes 2021-04-03 12:17:40 +02:00
280697698e feat(UI): light theme 2021-04-03 11:21:58 +02:00
0783f8b57e chore: update README.md 2021-04-02 15:27:29 +02:00
c981244d7a fix(UI): editor theme preview not properly loaded in some cases 2021-04-01 15:26:49 +02:00
dcb135dd01 fix: hide update tab for Windows Store distributions 2021-04-01 14:12:49 +02:00
99f7511c4d feat(PostgreSQL): views management 2021-03-31 16:54:06 +02:00
fe4c8e12b3 feat(PostgreSQL): foreign keys management 2021-03-31 15:57:23 +02:00
21728a663d chore: update README.md 2021-03-31 10:59:19 +02:00
9ca03f4625 feat(PostgreSQL): indexes management 2021-03-30 19:07:04 +02:00
affb7288b0 chore: appx logos 2021-03-30 19:06:48 +02:00
614e0d3275 feat(PostgreSQL): unique keys management 2021-03-29 20:18:44 +02:00
feef5e30ee feat(PostgreSQL): tables addition 2021-03-28 11:55:15 +02:00
82c25711b6 ci: moving to GitHub actions 2021-03-26 18:03:44 +01:00
2ca2988832 chore: appx build configuration 2021-03-26 16:52:49 +01:00
e3f259c6e8 feat(PostgreSQL): table fields edit 2021-03-25 18:33:29 +01:00
e7401cc96e fix: fields of ref. table not automatically loaded in foreign keys modal 2021-03-22 18:04:19 +01:00
13b9840f3d chore: update README.md 2021-03-21 14:39:11 +01:00
73 changed files with 1683 additions and 660 deletions

26
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Build/release
on: push
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
steps:
- name: Check out Git repository
uses: actions/checkout@v1
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1
with:
node-version: 10
- name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1
with:
github_token: ${{ secrets.github_token }}
release: ${{ startsWith(github.ref, 'refs/tags/v') }}

View File

@@ -8,7 +8,8 @@
"stylelint-scss"
],
"rules": {
"at-rule-no-unknown": null
"at-rule-no-unknown": null,
"no-descending-specificity": null
},
"syntax": "scss"
}

View File

@@ -2,6 +2,52 @@
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.1.2](https://github.com/Fabio286/antares/compare/v0.1.1...v0.1.2) (2021-04-11)
### Features
* in-app last release changelog ([1e938ad](https://github.com/Fabio286/antares/commit/1e938adc5d8eb5ad16ab16342375eecd88f68d20))
* **PostgreSQL:** edit timezone in cell editor ([8735a0c](https://github.com/Fabio286/antares/commit/8735a0c5f9e5b6b3bcaadf37ce158aa7beae2c48))
* **PostgreSQL:** procedures management ([3dde1c1](https://github.com/Fabio286/antares/commit/3dde1c109e23342d94362626ef7350dc123ea859))
* **PostgreSQL:** support of arrays in table settings ([d0b3e1b](https://github.com/Fabio286/antares/commit/d0b3e1b1b8be9d2c038d70e16d4478671315de8f))
### Bug Fixes
* cell edit doesn't properly use primary or unique index to update if both present, closes [#51](https://github.com/Fabio286/antares/issues/51) ([55932fe](https://github.com/Fabio286/antares/commit/55932fe11583bd5ff48f82b8408965adba4f5071))
* deletion of rows from query results ([c20bff7](https://github.com/Fabio286/antares/commit/c20bff7bcbe340ac99ebcacaba3359edd61c068a))
* no foreign key select when cell value is NULL, closes [#50](https://github.com/Fabio286/antares/issues/50) ([9f5ec02](https://github.com/Fabio286/antares/commit/9f5ec0276c92904975fdaea34b4c845c92bfe8d4))
* wrong datetime conversion when updating a row without an unique key ([d374372](https://github.com/Fabio286/antares/commit/d374372e208318d7e50b258a8041145bdf7992c5))
* **PostgreSQL:** issue with selected schema different than public ([49a4e1c](https://github.com/Fabio286/antares/commit/49a4e1cb7b24642641265d5830d3fee370cceeb4))
* **UI:** white readonly inputs with dark theme ([bb5f446](https://github.com/Fabio286/antares/commit/bb5f44681f87aacf2cd2f60a6d958c5732289790))
### Improvements
* **UI:** improved setting modal rendering ([be816e8](https://github.com/Fabio286/antares/commit/be816e85888b4f3d26cbb9caac0adbc4dde0ea94))
### [0.1.1](https://github.com/Fabio286/antares/compare/v0.1.0...v0.1.1) (2021-04-03)
### Features
* scratchpad to save persistent notes ([e349dd5](https://github.com/Fabio286/antares/commit/e349dd5eaba608591257f2799b830805e4936c27))
* **PostgreSQL:** foreign keys management ([fe4c8e1](https://github.com/Fabio286/antares/commit/fe4c8e12b39dd3cdfc233f07e3fe2ff0676252b0))
* **PostgreSQL:** indexes management ([9ca03f4](https://github.com/Fabio286/antares/commit/9ca03f462560b634970a19d3d97b878d60509acc))
* **PostgreSQL:** table fields edit ([e3f259c](https://github.com/Fabio286/antares/commit/e3f259c6e8327d71bd7dd0a9c33d957dc6ca1fb8))
* **PostgreSQL:** tables addition ([feef5e3](https://github.com/Fabio286/antares/commit/feef5e30eec915cbb219223cc428bd4e98d2e9c5))
* **PostgreSQL:** unique keys management ([614e0d3](https://github.com/Fabio286/antares/commit/614e0d32758c13b59139d349d4682a5bafc3ca88))
* **PostgreSQL:** views management ([99f7511](https://github.com/Fabio286/antares/commit/99f7511c4d5fab4030b30d5134cd03248167faea))
* **UI:** light theme ([2806976](https://github.com/Fabio286/antares/commit/280697698ea5fae6d54326970c823878888c196c))
### Bug Fixes
* **UI:** editor theme preview not properly loaded in some cases ([c981244](https://github.com/Fabio286/antares/commit/c981244d7aa93ca18ca2de44bf8df06f253b9d20))
* fields of ref. table not automatically loaded in foreign keys modal ([e7401cc](https://github.com/Fabio286/antares/commit/e7401cc96e76e00100a88eea9f40541fd8027adb))
* hide update tab for Windows Store distributions ([dcb135d](https://github.com/Fabio286/antares/commit/dcb135dd015b8f8c6cfb44021211bb8cf3089192))
## [0.1.0](https://github.com/Fabio286/antares/compare/v0.0.20...v0.1.0) (2021-03-21)

View File

@@ -9,11 +9,11 @@
Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers.
My target is to support as many databases as possible, and all major operating systems, including the ARM versions.
**At the moment this application is an alpha, it lacks many features** and supports only MySQL.
Most of its current features might be enough for basic MySQL use, so give it a chance and send me your feedback, I would really appreciate it.
I'm actively working on it (yes, i'm a lone dev), hoping to provide cool features and fixes as soon as possible.
**At the moment this application is in development state, many features will come in future updates**, and supports only MySQL/MariaDB and PostgreSQL (partially).
Many of its current features are enough to have a pleasant user experience with MySQL/MariaDB, and basic functionalites with PostgreSQL, so give it a chance and send me your feedback, I would really appreciate it.
I'm actively working on it, hoping to provide cool features and fixes as soon as possible.
🔗 If you are curious to try this early state of Antares you can download and install the [latest release](https://github.com/Fabio286/antares/releases).
🔗 If you are curious to try Antares you can download and install the [latest release](https://github.com/Fabio286/antares/releases/latest).
👁 To stay tuned for new releases watch this repo on **Release only** channel.
🌟 Don't forget to **leave a star** if you appreciate this project.
@@ -21,7 +21,12 @@ I'm actively working on it (yes, i'm a lone dev), hoping to provide cool feature
Why am I developing an SQL client when there are a lot of them on the market?
The main goal is to develop a totally free, full featured, cross platform and open source alternative, empowered by JavaScript's ecosystem.
An application created with minimalism and semplicity in mind, with features in the right places, not hundreds of tiny buttons or submenu.
A modern application created with minimalism and semplicity in mind, with features in the right places, not hundreds of tiny buttons, tabs or submenu.
## Download
[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/antares) [![Get it from Microsoft Store](https://raw.githubusercontent.com/Fabio286/antares/gh-pages/src/assets/ms-store.png)](https://www.microsoft.com/p/antares-sql-client/9nhtb9sq51r1?cid=storebadge&ocid=badge&rtc=1&activetab=pivot:overviewtab)
🚀 **[Other Downloads](https://github.com/Fabio286/antares/releases/latest)**
## How to contribute
@@ -36,7 +41,8 @@ An application created with minimalism and semplicity in mind, with features in
- Fake table data filler.
- Run queries on multiple tabs.
- Query suggestions and auto complete.
- Native dark theme.
- Dark and light theme.
- Scratchpad.
- Multi language.
- Secure password storage.
- Auto updates.
@@ -46,7 +52,7 @@ An application created with minimalism and semplicity in mind, with features in
This is a roadmap with major features will come in near future.
- Support for other databases.
- Database tools (variables, process list...).
- Database tools.
- SSH tunnel support.
- Users management (add/edit/delete).
- UI/UX improvements.
@@ -55,7 +61,6 @@ This is a roadmap with major features will come in near future.
- More keyboard shortcuts.
- Query logs console.
- Import/export and migration.
- Light theme.
## Troubleshooting
@@ -73,7 +78,7 @@ Depending on your distribution, you will need to run the following command:
### Databases
- [x] MySQL/MariaDB
- [ ] PostgreSQL
- [x] PostgreSQL (partially, work in progress)
- [ ] SQLite
- [ ] MSSQL
- [ ] OracleDB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
build/appx/StoreLogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,7 +1,7 @@
{
"name": "antares",
"productName": "Antares",
"version": "0.1.0",
"version": "0.1.2",
"description": "A cross-platform easy to use SQL client.",
"license": "MIT",
"repository": "https://github.com/Fabio286/antares.git",
@@ -9,9 +9,9 @@
"dev": "cross-env NODE_ENV=development electron-webpack dev",
"compile": "electron-webpack",
"build": "cross-env NODE_ENV=production npm run compile && electron-builder",
"build:appx": "npm run build -- --win appx",
"release": "standard-version",
"release:pre": "npm run release -- --prerelease alpha",
"release:snap": "npm run build -- --linux snap && snapcraft upload --release=edge ./dist/Antares-${npm_package_version}-linux_amd64.snap",
"test": "npm run lint",
"lint": "eslint . --ext .js,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
"lint:fix": "eslint . --ext .js,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix"
@@ -53,6 +53,12 @@
},
"portable": {
"artifactName": "${productName}-${version}-portable.exe"
},
"appx": {
"displayName": "Antares SQL Client",
"identityName": "62514FabioDiStasio.AntaresSQLClient",
"publisher": "CN=1A2729ED-865C-41D2-9038-39AE2A63AA52",
"applicationId": "FabioDiStasio.AntaresSQLClient"
}
},
"electronWebpack": {
@@ -68,6 +74,7 @@
"electron-updater": "^4.3.5",
"faker": "^5.3.1",
"keytar": "^7.3.0",
"marked": "^2.0.2",
"moment": "^2.29.1",
"mssql": "^6.2.3",
"mysql2": "^2.2.5",

View File

@@ -37,5 +37,20 @@ module.exports = {
indexes: false,
foreigns: false,
sortableFields: false,
zerofill: false
unsigned: false,
nullable: false,
zerofill: false,
autoIncrement: false,
comment: false,
collation: false,
definer: false,
onUpdate: false,
tableArray: false,
viewAlgorithm: false,
viewSqlSecurity: false,
viewUpdateOption: false,
procedureDeterministic: false,
procedureDataAccess: false,
procedureSql: false,
parametersLength: false
};

View File

@@ -36,5 +36,19 @@ module.exports = {
indexes: true,
foreigns: true,
sortableFields: true,
zerofill: true
unsigned: true,
nullable: true,
zerofill: true,
autoIncrement: true,
comment: true,
collation: true,
definer: true,
onUpdate: true,
viewAlgorithm: true,
viewSqlSecurity: true,
viewUpdateOption: true,
procedureDeterministic: true,
procedureDataAccess: true,
procedureSql: 'BEGIN\r\n\r\nEND',
parametersLength: true
};

View File

@@ -12,20 +12,28 @@ module.exports = {
processesList: true,
// Structure
tables: true,
views: false,
views: true,
triggers: false,
routines: false,
routines: true,
functions: false,
schedulers: false,
// Settings
tableAdd: true,
viewAdd: true,
triggerAdd: false,
routineAdd: true,
functionAdd: false,
databaseEdit: false,
tableSettings: false,
viewSettings: false,
tableSettings: true,
viewSettings: true,
triggerSettings: false,
routineSettings: false,
routineSettings: true,
functionSettings: false,
schedulerSettings: false,
indexes: true,
foreigns: true,
sortableFields: false
sortableFields: false,
nullable: true,
tableArray: true,
procedureSql: '$BODY$\r\n\r\n$BODY$'
};

View File

@@ -4,7 +4,7 @@ module.exports = [
types: [
{
name: 'TINYINT',
length: true,
length: 4,
collation: false,
unsigned: true,
zerofill: true

View File

@@ -4,22 +4,22 @@ module.exports = [
types: [
{
name: 'SMALLINT',
length: true,
length: false,
unsigned: true
},
{
name: 'INTEGER',
length: true,
length: false,
unsigned: true
},
{
name: 'BIGINT',
length: true,
length: false,
unsigned: true
},
{
name: 'DECIMAL',
length: true,
length: false,
unsigned: true
},
{
@@ -29,17 +29,17 @@ module.exports = [
},
{
name: 'SMALLSERIAL',
length: true,
length: false,
unsigned: true
},
{
name: 'SERIAL',
length: true,
length: false,
unsigned: true
},
{
name: 'BIGSERIAL',
length: true,
length: false,
unsigned: true
}
]
@@ -49,12 +49,12 @@ module.exports = [
types: [
{
name: 'REAL',
length: true,
length: false,
unsigned: true
},
{
name: 'DOUBLE PRECISION',
length: true,
length: false,
unsigned: true
}
]
@@ -64,7 +64,7 @@ module.exports = [
types: [
{
name: 'money',
length: true,
length: false,
unsigned: true
}
]
@@ -77,14 +77,9 @@ module.exports = [
length: true,
unsigned: false
},
{
name: 'CHAR',
length: false,
unsigned: false
},
{
name: 'CHARACTER',
length: false,
length: true,
unsigned: false
},
{
@@ -109,7 +104,7 @@ module.exports = [
types: [
{
name: 'BYTEA',
length: true,
length: false,
unsigned: false
}
]
@@ -129,17 +124,17 @@ module.exports = [
},
{
name: 'DATE',
length: true,
length: false,
unsigned: false
},
{
name: 'TIME',
length: true,
name: 'TIME WITHOUT TIME ZONE',
length: false,
unsigned: false
},
{
name: 'TIME WITH TIME ZONE',
length: true,
length: false,
unsigned: false
},
{
@@ -229,12 +224,12 @@ module.exports = [
types: [
{
name: 'BIT',
length: false,
length: true,
unsigned: false
},
{
name: 'BIT VARYING',
length: false,
length: true,
unsigned: false
}
]

View File

@@ -54,6 +54,7 @@ export const TIME = [
'TIME',
'TIME WITH TIME ZONE'
];
export const DATETIME = [
'DATETIME',
'TIMESTAMP',
@@ -61,6 +62,12 @@ export const DATETIME = [
'TIMESTAMP WITH TIME ZONE'
];
// Used to check datetime fields only
export const HAS_TIMEZONE = [
'TIMESTAMP WITH TIME ZONE',
'TIME WITH TIME ZONE'
];
export const BLOB = [
'BLOB',
'TINYBLOB',

View File

@@ -1,5 +1,5 @@
module.exports = [
'PRIMARY',
'KEY',
'INDEX',
'UNIQUE'
];

View File

@@ -8,7 +8,8 @@ export default connections => {
host: conn.host,
port: +conn.port,
user: conn.user,
password: conn.password
password: conn.password,
application_name: 'Antares SQL'
};
if (conn.database)
@@ -50,7 +51,8 @@ export default connections => {
host: conn.host,
port: +conn.port,
user: conn.user,
password: conn.password
password: conn.password,
application_name: 'Antares SQL'
};
if (conn.database)

View File

@@ -157,9 +157,13 @@ export default (connections) => {
ipcMain.handle('delete-table-rows', async (event, params) => {
if (params.primary) {
const idString = params.rows.map(row => typeof row[params.primary] === 'string'
? `"${row[params.primary]}"`
: row[params.primary]).join(',');
const idString = params.rows.map(row => {
const fieldName = Object.keys(row)[0].includes('.') ? `${params.table}.${params.primary}` : params.primary;
return typeof row[fieldName] === 'string'
? `"${row[fieldName]}"`
: row[fieldName];
}).join(',');
try {
const result = await connections[params.uid]

View File

@@ -9,10 +9,13 @@ autoUpdater.allowPrerelease = persistentStore.get('allow_prerelease', true);
export default () => {
ipcMain.on('check-for-updates', event => {
mainWindow = event;
autoUpdater.checkForUpdatesAndNotify().catch(() => {
mainWindow.reply('check-failed');
});
if (process.windowsStore)
mainWindow.reply('no-auto-update');
else {
autoUpdater.checkForUpdatesAndNotify().catch(() => {
mainWindow.reply('check-failed');
});
}
});
ipcMain.on('restart-to-update', () => {

View File

@@ -7,6 +7,8 @@ export class MySQLClient extends AntaresCore {
constructor (args) {
super(args);
this._schema = null;
this.types = {
0: 'DECIMAL',
1: 'TINYINT',
@@ -120,6 +122,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
use (schema) {
this._schema = schema;
return this.raw(`USE \`${schema}\``);
}
@@ -1049,7 +1052,7 @@ export class MySQLClient extends AntaresCore {
options
} = params;
let sql = `ALTER TABLE \`${table}\` `;
let sql = `ALTER TABLE \`${this._schema}\`.\`${table}\` `;
const alterColumns = [];
// OPTIONS

View File

@@ -1,5 +1,5 @@
'use strict';
import pg, { Pool, Client, types } from 'pg';
import { Pool, Client, types } from 'pg';
import { parse } from 'pgsql-ast-parser';
import { AntaresCore } from '../AntaresCore';
import dataTypes from 'common/data-types/postgresql';
@@ -8,11 +8,11 @@ function pgToString (value) {
return value.toString();
}
pg.types.setTypeParser(1082, pgToString); // date
pg.types.setTypeParser(1083, pgToString); // time
pg.types.setTypeParser(1114, pgToString); // timestamp
pg.types.setTypeParser(1184, pgToString); // timestamptz
pg.types.setTypeParser(1266, pgToString); // timetz
types.setTypeParser(1082, pgToString); // date
types.setTypeParser(1083, pgToString); // time
types.setTypeParser(1114, pgToString); // timestamp
types.setTypeParser(1184, pgToString); // timestamptz
types.setTypeParser(1266, pgToString); // timetz
export class PostgreSQLClient extends AntaresCore {
constructor (args) {
@@ -23,54 +23,16 @@ export class PostgreSQLClient extends AntaresCore {
this.types = {};
for (const key in types.builtins)
this.types[types.builtins[key]] = key;
}
_getType (field) {
let name = this.types[field.columnType];
let length = field.columnLength;
if (['DATE', 'TIME', 'YEAR', 'DATETIME'].includes(name))
length = field.decimals;
if (name === 'TIMESTAMP')
length = 0;
if (field.charsetNr === 63) { // if binary
if (name === 'CHAR')
name = 'BINARY';
else if (name === 'VARCHAR')
name = 'VARBINARY';
}
if (name === 'BLOB') {
switch (length) {
case 765:
name = 'TYNITEXT';
break;
case 196605:
name = 'TEXT';
break;
case 50331645:
name = 'MEDIUMTEXT';
break;
case 4294967295:
name = field.charsetNr === 63 ? 'LONGBLOB' : 'LONGTEXT';
break;
case 255:
name = 'TINYBLOB';
break;
case 65535:
name = 'BLOB';
break;
case 16777215:
name = 'MEDIUMBLOB';
break;
default:
name = field.charsetNr === 63 ? 'BLOB' : 'TEXT';
}
}
return { name, length };
this._arrayTypes = {
_int2: 'SMALLINT',
_int4: 'INTEGER',
_int8: 'BIGINT',
_float4: 'REAL',
_float8: 'DOUBLE PRECISION',
_char: '"CHAR"',
_varchar: 'CHARACTER VARYING'
};
}
_getTypeInfo (type) {
@@ -79,6 +41,12 @@ export class PostgreSQLClient extends AntaresCore {
.filter(_type => _type.name === type.toUpperCase())[0];
}
_getArrayType (type) {
if (Object.keys(this._arrayTypes).includes(type))
return this._arrayTypes[type];
return type.replace('_', '');
}
/**
* @memberof PostgreSQLClient
*/
@@ -109,7 +77,7 @@ export class PostgreSQLClient extends AntaresCore {
*/
use (schema) {
this._schema = schema;
return this.raw(`SET search_path TO '${schema}', '$user'`);
return this.raw(`SET search_path TO ${schema}`);
}
/**
@@ -189,16 +157,11 @@ export class PostgreSQLClient extends AntaresCore {
});
// PROCEDURES
const remappedProcedures = procedures.filter(procedure => procedure.Db === db.database).map(procedure => {
const remappedProcedures = procedures.filter(procedure => procedure.routine_schema === db.database).map(procedure => {
return {
name: procedure.Name,
type: procedure.Type,
definer: procedure.Definer,
created: procedure.Created,
updated: procedure.Modified,
comment: procedure.Comment,
charset: procedure.character_set_client,
security: procedure.Security_type
name: procedure.routine_name,
type: procedure.routine_type,
security: procedure.security_type
};
});
@@ -256,7 +219,7 @@ export class PostgreSQLClient extends AntaresCore {
* @returns {Object} table scructure
* @memberof PostgreSQLClient
*/
async getTableColumns ({ schema, table }) {
async getTableColumns ({ schema, table }, arrayRemap = true) {
const { rows } = await this
.select('*')
.schema('information_schema')
@@ -266,10 +229,17 @@ export class PostgreSQLClient extends AntaresCore {
.run();
return rows.map(field => {
let type = field.data_type;
const isArray = type === 'ARRAY';
if (isArray && arrayRemap)
type = this._getArrayType(field.udt_name);
return {
name: field.column_name,
key: null,
type: field.data_type.toUpperCase(),
type: type.toUpperCase(),
isArray,
schema: field.table_schema,
table: field.table_name,
numPrecision: field.numeric_precision,
@@ -282,7 +252,7 @@ export class PostgreSQLClient extends AntaresCore {
default: field.column_default,
charset: field.character_set_name,
collation: field.collation_name,
autoIncrement: null,
autoIncrement: false,
onUpdate: null,
comment: ''
};
@@ -297,11 +267,14 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async getTableIndexes ({ schema, table }) {
if (schema !== 'public')
this.use(schema);
const { rows } = await this.raw(`WITH ndx_list AS (
SELECT pg_index.indexrelid, pg_class.oid
FROM pg_index, pg_class
WHERE pg_class.relname = '${table}' AND pg_class.oid = pg_index.indrelid), ndx_cols AS (
SELECT pg_class.relname, UNNEST(i.indkey) AS col_ndx, CASE i.indisprimary WHEN TRUE THEN 'PRIMARY' ELSE CASE i.indisunique WHEN TRUE THEN 'UNIQUE' ELSE 'KEY' END END AS CONSTRAINT_TYPE, pg_class.oid
SELECT pg_class.relname, UNNEST(i.indkey) AS col_ndx, CASE i.indisprimary WHEN TRUE THEN 'PRIMARY' ELSE CASE i.indisunique WHEN TRUE THEN 'UNIQUE' ELSE 'INDEX' END END AS CONSTRAINT_TYPE, pg_class.oid
FROM pg_class
JOIN pg_index i ON (pg_class.oid = i.indexrelid)
JOIN ndx_list ON (pg_class.oid = ndx_list.indexrelid)
@@ -317,7 +290,10 @@ export class PostgreSQLClient extends AntaresCore {
name: row.constraint_name,
column: row.column_name,
indexType: null,
type: row.constraint_type
type: row.constraint_type,
cardinality: null,
comment: '',
indexComment: ''
};
});
}
@@ -359,6 +335,8 @@ export class PostgreSQLClient extends AntaresCore {
tc.constraint_name,
tc.table_name,
kcu.column_name,
kcu.position_in_unique_constraint,
kcu.ordinal_position,
ccu.table_schema AS foreign_table_schema,
ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name,
@@ -449,17 +427,17 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async getViewInformations ({ schema, view }) {
const sql = `SHOW CREATE VIEW \`${schema}\`.\`${view}\``;
const sql = `SELECT "definition" FROM "pg_views" WHERE "viewname"='${view}' AND "schemaname"='${schema}'`;
const results = await this.raw(sql);
return results.rows.map(row => {
return {
algorithm: row['Create View'].match(/(?<=CREATE ALGORITHM=).*?(?=\s)/gs)[0],
definer: row['Create View'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0],
security: row['Create View'].match(/(?<=SQL SECURITY ).*?(?=\s)/gs)[0],
updateOption: row['Create View'].match(/(?<=WITH ).*?(?=\s)/gs) ? row['Create View'].match(/(?<=WITH ).*?(?=\s)/gs)[0] : '',
sql: row['Create View'].match(/(?<=AS ).*?$/gs)[0],
name: row.View
algorithm: '',
definer: '',
security: '',
updateOption: '',
sql: row.definition,
name: view
};
})[0];
}
@@ -471,7 +449,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async dropView (params) {
const sql = `DROP VIEW \`${params.view}\``;
const sql = `DROP VIEW ${this._schema}.${params.view}`;
return await this.raw(sql);
}
@@ -483,10 +461,10 @@ export class PostgreSQLClient extends AntaresCore {
*/
async alterView (params) {
const { view } = params;
let sql = `ALTER ALGORITHM = ${view.algorithm}${view.definer ? ` DEFINER=${view.definer}` : ''} SQL SECURITY ${view.security} VIEW \`${view.oldName}\` AS ${view.sql} ${view.updateOption ? `WITH ${view.updateOption} CHECK OPTION` : ''}`;
let sql = `CREATE OR REPLACE VIEW ${this._schema}.${view.oldName} AS ${view.sql}`;
if (view.name !== view.oldName)
sql += `; RENAME TABLE \`${view.oldName}\` TO \`${view.name}\``;
sql += `; ALTER VIEW ${this._schema}.${view.oldName} RENAME TO ${view.name}`;
return await this.raw(sql);
}
@@ -498,7 +476,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async createView (view) {
const sql = `CREATE ALGORITHM = ${view.algorithm} ${view.definer ? `DEFINER=${view.definer} ` : ''}SQL SECURITY ${view.security} VIEW \`${view.name}\` AS ${view.sql} ${view.updateOption ? `WITH ${view.updateOption} CHECK OPTION` : ''}`;
const sql = `CREATE VIEW ${this._schema}.${view.name} AS ${view.sql}`;
return await this.raw(sql);
}
@@ -575,16 +553,16 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async getRoutineInformations ({ schema, routine }) {
const sql = `SHOW CREATE PROCEDURE \`${schema}\`.\`${routine}\``;
const sql = `SELECT pg_get_functiondef((SELECT oid FROM pg_proc WHERE proname = '${routine}'));`;
const results = await this.raw(sql);
return results.rows.map(row => {
if (!row['Create Procedure']) {
return results.rows.map(async row => {
if (!row.pg_get_functiondef) {
return {
definer: null,
sql: '',
parameters: [],
name: row.Procedure,
name: routine,
comment: '',
security: 'DEFINER',
deterministic: false,
@@ -592,40 +570,48 @@ export class PostgreSQLClient extends AntaresCore {
};
}
const parameters = row['Create Procedure']
.match(/(\([^()]*(?:(?:\([^()]*\))[^()]*)*\)\s*)/s)[0]
.replaceAll('\r', '')
.replaceAll('\t', '')
.slice(1, -1)
.split(',')
.map(el => {
const param = el.split(' ');
const type = param[2] ? param[2].replace(')', '').split('(') : ['', null];
return {
name: param[1] ? param[1].replaceAll('`', '') : '',
type: type[0].replaceAll('\n', ''),
length: +type[1] ? +type[1].replace(/\D/g, '') : '',
context: param[0] ? param[0].replace('\n', '') : ''
};
}).filter(el => el.name);
const sql = `SELECT proc.specific_schema AS procedure_schema,
proc.specific_name,
proc.routine_name AS procedure_name,
proc.external_language,
args.parameter_name,
args.parameter_mode,
args.data_type
FROM information_schema.routines proc
LEFT JOIN information_schema.parameters args
ON proc.specific_schema = args.specific_schema
AND proc.specific_name = args.specific_name
WHERE proc.routine_schema not in ('pg_catalog', 'information_schema')
AND proc.routine_type = 'PROCEDURE'
AND proc.routine_name = '${routine}'
AND proc.specific_schema = '${schema}'
ORDER BY procedure_schema,
specific_name,
procedure_name,
args.ordinal_position
`;
let dataAccess = 'CONTAINS SQL';
if (row['Create Procedure'].includes('NO SQL'))
dataAccess = 'NO SQL';
if (row['Create Procedure'].includes('READS SQL DATA'))
dataAccess = 'READS SQL DATA';
if (row['Create Procedure'].includes('MODIFIES SQL DATA'))
dataAccess = 'MODIFIES SQL DATA';
const results = await this.raw(sql);
const parameters = results.rows.map(row => {
return {
name: row.parameter_name,
type: row.data_type.toUpperCase(),
length: '',
context: row.parameter_mode
};
});
return {
definer: row['Create Procedure'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0],
sql: row['Create Procedure'].match(/(BEGIN|begin)(.*)(END|end)/gs)[0],
definer: '',
sql: row.pg_get_functiondef.match(/(\$(.*)\$)(.*)(\$(.*)\$)/gs)[0],
parameters: parameters || [],
name: row.Procedure,
comment: row['Create Procedure'].match(/(?<=COMMENT ').*?(?=')/gs) ? row['Create Procedure'].match(/(?<=COMMENT ').*?(?=')/gs)[0] : '',
security: row['Create Procedure'].includes('SQL SECURITY INVOKER') ? 'INVOKER' : 'DEFINER',
deterministic: row['Create Procedure'].includes('DETERMINISTIC'),
dataAccess
name: routine,
comment: '',
security: row.pg_get_functiondef.includes('SECURITY DEFINER') ? 'DEFINER' : 'INVOKER',
deterministic: null,
dataAccess: null,
language: row.pg_get_functiondef.match(/(?<=LANGUAGE )(.*)(?<=[\S+\n\r\s])/gm)[0]
};
})[0];
}
@@ -637,7 +623,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async dropRoutine (params) {
const sql = `DROP PROCEDURE \`${params.routine}\``;
const sql = `DROP PROCEDURE ${this._schema}.${params.routine}`;
return await this.raw(sql);
}
@@ -672,18 +658,18 @@ export class PostgreSQLClient extends AntaresCore {
async createRoutine (routine) {
const parameters = 'parameters' in routine
? routine.parameters.reduce((acc, curr) => {
acc.push(`${curr.context} \`${curr.name}\` ${curr.type}${curr.length ? `(${curr.length})` : ''}`);
acc.push(`${curr.context} ${curr.name} ${curr.type}${curr.length ? `(${curr.length})` : ''}`);
return acc;
}, []).join(',')
: '';
const sql = `CREATE ${routine.definer ? `DEFINER=${routine.definer} ` : ''}PROCEDURE \`${routine.name}\`(${parameters})
if (this._schema !== 'public')
this.use(this._schema);
const sql = `CREATE PROCEDURE ${this._schema}.${routine.name}(${parameters})
LANGUAGE SQL
${routine.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'}
${routine.dataAccess}
SQL SECURITY ${routine.security}
COMMENT '${routine.comment}'
${routine.sql}`;
SECURITY ${routine.security}
AS ${routine.sql}`;
return await this.raw(sql, { split: false });
}
@@ -818,33 +804,33 @@ export class PostgreSQLClient extends AntaresCore {
* @returns {Array.<Object>} view informations
* @memberof PostgreSQLClient
*/
async getEventInformations ({ schema, scheduler }) {
const sql = `SHOW CREATE EVENT \`${schema}\`.\`${scheduler}\``;
const results = await this.raw(sql);
// async getEventInformations ({ schema, scheduler }) {
// const sql = `SHOW CREATE EVENT \`${schema}\`.\`${scheduler}\``;
// const results = await this.raw(sql);
return results.rows.map(row => {
const schedule = row['Create Event'];
const execution = schedule.includes('EVERY') ? 'EVERY' : 'ONCE';
const every = execution === 'EVERY' ? row['Create Event'].match(/(?<=EVERY )(\s*([^\s]+)){0,2}/gs)[0].replaceAll('\'', '').split(' ') : [];
const starts = execution === 'EVERY' && schedule.includes('STARTS') ? schedule.match(/(?<=STARTS ').*?(?='\s)/gs)[0] : '';
const ends = execution === 'EVERY' && schedule.includes('ENDS') ? schedule.match(/(?<=ENDS ').*?(?='\s)/gs)[0] : '';
const at = execution === 'ONCE' && schedule.includes('AT') ? schedule.match(/(?<=AT ').*?(?='\s)/gs)[0] : '';
// return results.rows.map(row => {
// const schedule = row['Create Event'];
// const execution = schedule.includes('EVERY') ? 'EVERY' : 'ONCE';
// const every = execution === 'EVERY' ? row['Create Event'].match(/(?<=EVERY )(\s*([^\s]+)){0,2}/gs)[0].replaceAll('\'', '').split(' ') : [];
// const starts = execution === 'EVERY' && schedule.includes('STARTS') ? schedule.match(/(?<=STARTS ').*?(?='\s)/gs)[0] : '';
// const ends = execution === 'EVERY' && schedule.includes('ENDS') ? schedule.match(/(?<=ENDS ').*?(?='\s)/gs)[0] : '';
// const at = execution === 'ONCE' && schedule.includes('AT') ? schedule.match(/(?<=AT ').*?(?='\s)/gs)[0] : '';
return {
definer: row['Create Event'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0],
sql: row['Create Event'].match(/(?<=DO )(.*)/gs)[0],
name: row.Event,
comment: row['Create Event'].match(/(?<=COMMENT ').*?(?=')/gs) ? row['Create Event'].match(/(?<=COMMENT ').*?(?=')/gs)[0] : '',
state: row['Create Event'].includes('ENABLE') ? 'ENABLE' : row['Create Event'].includes('DISABLE ON SLAVE') ? 'DISABLE ON SLAVE' : 'DISABLE',
preserve: row['Create Event'].includes('ON COMPLETION PRESERVE'),
execution,
every,
starts,
ends,
at
};
})[0];
}
// return {
// definer: row['Create Event'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0],
// sql: row['Create Event'].match(/(?<=DO )(.*)/gs)[0],
// name: row.Event,
// comment: row['Create Event'].match(/(?<=COMMENT ').*?(?=')/gs) ? row['Create Event'].match(/(?<=COMMENT ').*?(?=')/gs)[0] : '',
// state: row['Create Event'].includes('ENABLE') ? 'ENABLE' : row['Create Event'].includes('DISABLE ON SLAVE') ? 'DISABLE ON SLAVE' : 'DISABLE',
// preserve: row['Create Event'].includes('ON COMPLETION PRESERVE'),
// execution,
// every,
// starts,
// ends,
// at
// };
// })[0];
// }
/**
* DROP EVENT
@@ -852,10 +838,10 @@ export class PostgreSQLClient extends AntaresCore {
* @returns {Array.<Object>} parameters
* @memberof PostgreSQLClient
*/
async dropEvent (params) {
const sql = `DROP EVENT \`${params.scheduler}\``;
return await this.raw(sql);
}
// async dropEvent (params) {
// const sql = `DROP EVENT \`${params.scheduler}\``;
// return await this.raw(sql);
// }
/**
* ALTER EVENT
@@ -863,25 +849,25 @@ export class PostgreSQLClient extends AntaresCore {
* @returns {Array.<Object>} parameters
* @memberof PostgreSQLClient
*/
async alterEvent (params) {
const { scheduler } = params;
// async alterEvent (params) {
// const { scheduler } = params;
if (scheduler.execution === 'EVERY' && scheduler.every[0].includes('-'))
scheduler.every[0] = `'${scheduler.every[0]}'`;
// if (scheduler.execution === 'EVERY' && scheduler.every[0].includes('-'))
// scheduler.every[0] = `'${scheduler.every[0]}'`;
const sql = `ALTER ${scheduler.definer ? ` DEFINER=${scheduler.definer}` : ''} EVENT \`${scheduler.oldName}\`
ON SCHEDULE
${scheduler.execution === 'EVERY'
? `EVERY ${scheduler.every.join(' ')}${scheduler.starts ? ` STARTS '${scheduler.starts}'` : ''}${scheduler.ends ? ` ENDS '${scheduler.ends}'` : ''}`
: `AT '${scheduler.at}'`}
ON COMPLETION${!scheduler.preserve ? ' NOT' : ''} PRESERVE
${scheduler.name !== scheduler.oldName ? `RENAME TO \`${scheduler.name}\`` : ''}
${scheduler.state}
COMMENT '${scheduler.comment}'
DO ${scheduler.sql}`;
// const sql = `ALTER ${scheduler.definer ? ` DEFINER=${scheduler.definer}` : ''} EVENT \`${scheduler.oldName}\`
// ON SCHEDULE
// ${scheduler.execution === 'EVERY'
// ? `EVERY ${scheduler.every.join(' ')}${scheduler.starts ? ` STARTS '${scheduler.starts}'` : ''}${scheduler.ends ? ` ENDS '${scheduler.ends}'` : ''}`
// : `AT '${scheduler.at}'`}
// ON COMPLETION${!scheduler.preserve ? ' NOT' : ''} PRESERVE
// ${scheduler.name !== scheduler.oldName ? `RENAME TO \`${scheduler.name}\`` : ''}
// ${scheduler.state}
// COMMENT '${scheduler.comment}'
// DO ${scheduler.sql}`;
return await this.raw(sql, { split: false });
}
// return await this.raw(sql, { split: false });
// }
/**
* CREATE EVENT
@@ -889,19 +875,19 @@ export class PostgreSQLClient extends AntaresCore {
* @returns {Array.<Object>} parameters
* @memberof PostgreSQLClient
*/
async createEvent (scheduler) {
const sql = `CREATE ${scheduler.definer ? ` DEFINER=${scheduler.definer}` : ''} EVENT \`${scheduler.name}\`
ON SCHEDULE
${scheduler.execution === 'EVERY'
? `EVERY ${scheduler.every.join(' ')}${scheduler.starts ? ` STARTS '${scheduler.starts}'` : ''}${scheduler.ends ? ` ENDS '${scheduler.ends}'` : ''}`
: `AT '${scheduler.at}'`}
ON COMPLETION${!scheduler.preserve ? ' NOT' : ''} PRESERVE
${scheduler.state}
COMMENT '${scheduler.comment}'
DO ${scheduler.sql}`;
// async createEvent (scheduler) {
// const sql = `CREATE ${scheduler.definer ? ` DEFINER=${scheduler.definer}` : ''} EVENT \`${scheduler.name}\`
// ON SCHEDULE
// ${scheduler.execution === 'EVERY'
// ? `EVERY ${scheduler.every.join(' ')}${scheduler.starts ? ` STARTS '${scheduler.starts}'` : ''}${scheduler.ends ? ` ENDS '${scheduler.ends}'` : ''}`
// : `AT '${scheduler.at}'`}
// ON COMPLETION${!scheduler.preserve ? ' NOT' : ''} PRESERVE
// ${scheduler.state}
// COMMENT '${scheduler.comment}'
// DO ${scheduler.sql}`;
return await this.raw(sql, { split: false });
}
// return await this.raw(sql, { split: false });
// }
/**
* SELECT * FROM pg_collation
@@ -992,13 +978,10 @@ export class PostgreSQLClient extends AntaresCore {
*/
async createTable (params) {
const {
name,
collation,
comment,
engine
name
} = params;
const sql = `CREATE TABLE \`${name}\` (\`${name}_ID\` INT NULL) COMMENT='${comment}', COLLATE='${collation}', ENGINE=${engine}`;
const sql = `CREATE TABLE ${this._schema}.${name} (${name}_id INTEGER NULL); ALTER TABLE ${this._schema}.${name} DROP COLUMN ${name}_id`;
return await this.raw(sql);
}
@@ -1020,8 +1003,11 @@ export class PostgreSQLClient extends AntaresCore {
options
} = params;
let sql = `ALTER TABLE \`${table}\` `;
let sql = '';
const alterColumns = [];
const renameColumns = [];
const createSequences = [];
const manageIndexes = [];
// OPTIONS
if ('comment' in options) alterColumns.push(`COMMENT='${options.comment}'`);
@@ -1034,8 +1020,8 @@ export class PostgreSQLClient extends AntaresCore {
const typeInfo = this._getTypeInfo(addition.type);
const length = typeInfo.length ? addition.numLength || addition.charLength || addition.datePrecision : false;
alterColumns.push(`ADD COLUMN \`${addition.name}\`
${addition.type.toUpperCase()}${length ? `(${length})` : ''}
alterColumns.push(`ADD COLUMN ${addition.name}
${addition.type.toUpperCase()}${length ? `(${length})` : ''}${addition.isArray ? '[]' : ''}
${addition.unsigned ? 'UNSIGNED' : ''}
${addition.zerofill ? 'ZEROFILL' : ''}
${addition.nullable ? 'NULL' : 'NOT NULL'}
@@ -1043,96 +1029,111 @@ export class PostgreSQLClient extends AntaresCore {
${addition.default ? `DEFAULT ${addition.default}` : ''}
${addition.comment ? `COMMENT '${addition.comment}'` : ''}
${addition.collation ? `COLLATE ${addition.collation}` : ''}
${addition.onUpdate ? `ON UPDATE ${addition.onUpdate}` : ''}
${addition.after ? `AFTER \`${addition.after}\`` : 'FIRST'}`);
${addition.onUpdate ? `ON UPDATE ${addition.onUpdate}` : ''}`);
});
// ADD INDEX
indexChanges.additions.forEach(addition => {
const fields = addition.fields.map(field => `\`${field}\``).join(',');
let type = addition.type;
const fields = addition.fields.map(field => `${field}`).join(',');
const type = addition.type;
if (type === 'PRIMARY')
alterColumns.push(`ADD PRIMARY KEY (${fields})`);
else {
if (type === 'UNIQUE')
type = 'UNIQUE INDEX';
alterColumns.push(`ADD ${type} \`${addition.name}\` (${fields})`);
}
else if (type === 'UNIQUE')
alterColumns.push(`ADD CONSTRAINT ${addition.name} UNIQUE (${fields})`);
else
manageIndexes.push(`CREATE INDEX ${addition.name} ON ${table}(${fields})`);
});
// ADD FOREIGN KEYS
foreignChanges.additions.forEach(addition => {
alterColumns.push(`ADD CONSTRAINT \`${addition.constraintName}\` FOREIGN KEY (\`${addition.field}\`) REFERENCES \`${addition.refTable}\` (\`${addition.refField}\`) ON UPDATE ${addition.onUpdate} ON DELETE ${addition.onDelete}`);
alterColumns.push(`ADD CONSTRAINT ${addition.constraintName} FOREIGN KEY (${addition.field}) REFERENCES ${addition.refTable} (${addition.refField}) ON UPDATE ${addition.onUpdate} ON DELETE ${addition.onDelete}`);
});
// CHANGE FIELDS
changes.forEach(change => {
const typeInfo = this._getTypeInfo(change.type);
const length = typeInfo.length ? change.numLength || change.charLength || change.datePrecision : false;
let localType;
alterColumns.push(`CHANGE COLUMN \`${change.orgName}\` \`${change.name}\`
${change.type.toUpperCase()}${length ? `(${length})` : ''}
${change.unsigned ? 'UNSIGNED' : ''}
${change.zerofill ? 'ZEROFILL' : ''}
${change.nullable ? 'NULL' : 'NOT NULL'}
${change.autoIncrement ? 'AUTO_INCREMENT' : ''}
${change.default ? `DEFAULT ${change.default}` : ''}
${change.comment ? `COMMENT '${change.comment}'` : ''}
${change.collation ? `COLLATE ${change.collation}` : ''}
${change.onUpdate ? `ON UPDATE ${change.onUpdate}` : ''}
${change.after ? `AFTER \`${change.after}\`` : 'FIRST'}`);
switch (change.type) {
case 'SERIAL':
localType = 'integer';
break;
case 'SMALLSERIAL':
localType = 'smallint';
break;
case 'BIGSERIAL':
localType = 'bigint';
break;
default:
localType = change.type.toLowerCase();
}
alterColumns.push(`ALTER COLUMN "${change.orgName}" TYPE ${localType}${length ? `(${length})` : ''}${change.isArray ? '[]' : ''} USING "${change.orgName}"::${localType}`);
alterColumns.push(`ALTER COLUMN "${change.orgName}" ${change.nullable ? 'DROP NOT NULL' : 'SET NOT NULL'}`);
alterColumns.push(`ALTER COLUMN "${change.orgName}" ${change.default ? `SET DEFAULT ${change.default}` : 'DROP DEFAULT'}`);
if (['SERIAL', 'SMALLSERIAL', 'BIGSERIAL'].includes(change.type)) {
const sequenceName = `${table}_${change.name}_seq`.replace(' ', '_');
createSequences.push(`CREATE SEQUENCE IF NOT EXISTS ${sequenceName} OWNED BY "${table}"."${change.orgName}"`);
alterColumns.push(`ALTER COLUMN "${change.orgName}" SET DEFAULT nextval('${sequenceName}')`);
}
if (change.orgName !== change.name)
renameColumns.push(`ALTER TABLE "${this._schema}"."${table}" RENAME COLUMN "${change.orgName}" TO "${change.name}"`);
});
// CHANGE INDEX
indexChanges.changes.forEach(change => {
if (change.oldType === 'PRIMARY')
alterColumns.push('DROP PRIMARY KEY');
else if (change.oldType === 'UNIQUE')
alterColumns.push(`DROP CONSTRAINT ${change.oldName}`);
else
alterColumns.push(`DROP INDEX \`${change.oldName}\``);
manageIndexes.push(`DROP INDEX ${change.oldName}`);
const fields = change.fields.map(field => `\`${field}\``).join(',');
let type = change.type;
const fields = change.fields.map(field => `${field}`).join(',');
const type = change.type;
if (type === 'PRIMARY')
alterColumns.push(`ADD PRIMARY KEY (${fields})`);
else {
if (type === 'UNIQUE')
type = 'UNIQUE INDEX';
alterColumns.push(`ADD ${type} \`${change.name}\` (${fields})`);
}
else if (type === 'UNIQUE')
alterColumns.push(`ADD CONSTRAINT ${change.name} UNIQUE (${fields})`);
else
manageIndexes.push(`CREATE INDEX ${change.name} ON ${table}(${fields})`);
});
// CHANGE FOREIGN KEYS
foreignChanges.changes.forEach(change => {
alterColumns.push(`DROP FOREIGN KEY \`${change.oldName}\``);
alterColumns.push(`ADD CONSTRAINT \`${change.constraintName}\` FOREIGN KEY (\`${change.field}\`) REFERENCES \`${change.refTable}\` (\`${change.refField}\`) ON UPDATE ${change.onUpdate} ON DELETE ${change.onDelete}`);
alterColumns.push(`DROP CONSTRAINT ${change.oldName}`);
alterColumns.push(`ADD CONSTRAINT ${change.constraintName} FOREIGN KEY (${change.field}) REFERENCES ${change.refTable} (${change.refField}) ON UPDATE ${change.onUpdate} ON DELETE ${change.onDelete}`);
});
// DROP FIELDS
deletions.forEach(deletion => {
alterColumns.push(`DROP COLUMN \`${deletion.name}\``);
alterColumns.push(`DROP COLUMN ${deletion.name}`);
});
// DROP INDEX
indexChanges.deletions.forEach(deletion => {
if (deletion.type === 'PRIMARY')
alterColumns.push('DROP PRIMARY KEY');
if (['PRIMARY', 'UNIQUE'].includes(deletion.type))
alterColumns.push(`DROP CONSTRAINT ${deletion.name}`);
else
alterColumns.push(`DROP INDEX \`${deletion.name}\``);
manageIndexes.push(`DROP INDEX ${deletion.name}`);
});
// DROP FOREIGN KEYS
foreignChanges.deletions.forEach(deletion => {
alterColumns.push(`DROP FOREIGN KEY \`${deletion.constraintName}\``);
alterColumns.push(`DROP CONSTRAINT ${deletion.constraintName}`);
});
sql += alterColumns.join(', ');
if (alterColumns.length) sql += `ALTER TABLE "${this._schema}"."${table}" ${alterColumns.join(', ')}; `;
// RENAME
if (options.name) sql += `; RENAME TABLE \`${table}\` TO \`${options.name}\``;
if (renameColumns.length) sql += `${renameColumns.join(';')}; `;
if (createSequences.length) sql = `${createSequences.join(';')}; ${sql}`;
if (manageIndexes.length) sql = `${manageIndexes.join(';')}; ${sql}`;
if (options.name) sql += `ALTER TABLE "${this._schema}"."${table}" RENAME TO "${options.name}"; `;
return await this.raw(sql);
}
@@ -1144,7 +1145,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async truncateTable (params) {
const sql = `TRUNCATE TABLE ${params.table}`;
const sql = `TRUNCATE TABLE ${this._schema}.${params.table}`;
return await this.raw(sql);
}
@@ -1155,7 +1156,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async dropTable (params) {
const sql = `DROP TABLE ${params.table}`;
const sql = `DROP TABLE ${this._schema}.${params.table}`;
return await this.raw(sql);
}
@@ -1229,6 +1230,10 @@ export class PostgreSQLClient extends AntaresCore {
split: true,
...args
};
if (args.nest && this._schema !== 'public')
this.use(this._schema);
const resultsArr = [];
let paramsArr = [];
const queries = args.split ? sql.split(';') : [sql];
@@ -1318,7 +1323,7 @@ export class PostgreSQLClient extends AntaresCore {
if (!paramObj.table || !paramObj.schema) continue;
try { // Column details
const columns = await this.getTableColumns(paramObj);
const columns = await this.getTableColumns(paramObj, false);
const indexes = await this.getTableIndexes(paramObj);
remappedFields = remappedFields.map(field => {

View File

@@ -1,5 +1,5 @@
<template>
<div id="wrapper">
<div id="wrapper" :class="`theme-${applicationTheme}`">
<TheTitleBar />
<div id="window-content">
<TheSettingBar />
@@ -16,6 +16,7 @@
<TheFooter />
<TheNotificationsBoard />
<ModalNewConnection v-if="isNewConnModal" />
<TheScratchpad v-if="isScratchpad" />
<ModalSettings v-if="isSettingModal" />
<ModalDiscardChanges v-if="isUnsavedDiscardModal" />
</div>
@@ -37,6 +38,7 @@ export default {
Workspace: () => import(/* webpackChunkName: "Workspace" */'@/components/Workspace'),
ModalNewConnection: () => import(/* webpackChunkName: "ModalNewConnection" */'@/components/ModalNewConnection'),
ModalSettings: () => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings'),
TheScratchpad: () => import(/* webpackChunkName: "TheScratchpad" */'@/components/TheScratchpad'),
ModalDiscardChanges: () => import(/* webpackChunkName: "ModalDiscardChanges" */'@/components/ModalDiscardChanges')
},
data () {
@@ -48,12 +50,15 @@ export default {
isNewConnModal: 'application/isNewModal',
isEditModal: 'application/isEditModal',
isSettingModal: 'application/isSettingModal',
isScratchpad: 'application/isScratchpad',
connections: 'connections/getConnections',
applicationTheme: 'settings/getApplicationTheme',
isUnsavedDiscardModal: 'workspaces/isUnsavedDiscardModal'
})
},
mounted () {
ipcRenderer.send('check-for-updates');
this.checkVersionUpdate();
const Menu = remote.Menu;
@@ -96,7 +101,8 @@ export default {
},
methods: {
...mapActions({
showNewConnModal: 'application/showNewConnModal'
showNewConnModal: 'application/showNewConnModal',
checkVersionUpdate: 'application/checkVersionUpdate'
})
}
};

View File

@@ -71,7 +71,6 @@ export default {
<style lang="scss">
.context {
display: flex;
color: $body-font-color;
font-size: 16px;
z-index: 400;
justify-content: center;
@@ -87,7 +86,6 @@ export default {
.context-container {
min-width: 100px;
z-index: 10;
box-shadow: 0 0 2px 0 #000;
padding: 0;
background: #1d1d1d;
border-radius: 0.1rem;
@@ -111,14 +109,10 @@ export default {
position: absolute;
left: 100%;
top: 0;
background: #1d1d1d;
box-shadow: 0 0 2px 0 #000;
min-width: 100px;
}
&:hover {
background: $primary-color;
.context-submenu {
display: block;
visibility: visible;

View File

@@ -22,6 +22,7 @@ export default {
editorClass: { type: String, default: '' },
autoFocus: { type: Boolean, default: false },
readOnly: { type: Boolean, default: false },
showLineNumbers: { type: Boolean, default: true },
height: { type: Number, default: 200 }
},
data () {
@@ -68,10 +69,12 @@ export default {
this.editor = ace.edit(`editor-${this.id}`, {
mode: `ace/mode/${this.mode}`,
theme: `ace/theme/${this.editorTheme}`,
value: this.value,
value: this.value || '',
fontSize: '14px',
printMargin: false,
readOnly: this.readOnly
readOnly: this.readOnly,
showLineNumbers: this.showLineNumbers,
showGutter: this.showLineNumbers
});
this.editor.setOptions({
@@ -102,8 +105,6 @@ export default {
<style lang="scss" scoped>
.editor-wrapper {
border-bottom: 1px solid #444;
.editor {
width: 100%;
}
@@ -113,17 +114,4 @@ export default {
display: inline-block;
width: 17px;
}
.ace_dark.ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {
background-color: #c9561a99;
}
.ace_dark.ace_editor.ace_autocomplete .ace_marker-layer .ace_line-hover {
background-color: #c9571a33;
border: none;
}
.ace_dark.ace_editor.ace_autocomplete .ace_completion-highlight {
color: #e0d00c;
}
</style>

View File

@@ -63,13 +63,11 @@ export default {
<style lang="scss" scoped>
.file-uploader {
border: 0.05rem solid $bg-color-light;
border-radius: 0.1rem;
height: 1.8rem;
line-height: 1.2rem;
display: flex;
cursor: pointer;
background-color: $bg-color-gray;
transition: background 0.2s, border 0.2s, box-shadow 0.2s, color 0.2s;
position: relative;
flex: 1 1 auto;
@@ -80,8 +78,6 @@ export default {
.file-uploader-message {
display: flex;
border-right: 0.05rem solid $bg-color-light;
background-color: $bg-color;
}
.file-uploader-input {
@@ -105,7 +101,6 @@ export default {
:disabled {
.file-uploader {
cursor: not-allowed;
background-color: #151515;
opacity: 0.5;
}
}

View File

@@ -51,7 +51,7 @@ export default {
}),
isValidDefault () {
if (!this.foreignList.length) return true;
return this.foreignList.some(foreign => foreign.foreign_column.toString() === this.value.toString());
return this.value === null || this.foreignList.some(foreign => foreign.foreign_column.toString() === this.value.toString());
}
},
async created () {

View File

@@ -43,6 +43,7 @@
</template>
<script>
import { NUMBER, FLOAT } from 'common/fieldTypes';
import ConfirmModal from '@/components/BaseConfirmModal';
export default {
@@ -57,7 +58,8 @@ export default {
}
},
props: {
localRoutine: Object
localRoutine: Object,
client: String
},
data () {
return {
@@ -82,7 +84,22 @@ export default {
},
runRoutine () {
const valArr = Object.keys(this.values).reduce((acc, curr) => {
const value = isNaN(this.values[curr]) ? `"${this.values[curr]}"` : this.values[curr];
let qc;
switch (this.client) {
case 'maria':
case 'mysql':
qc = '"';
break;
case 'pg':
qc = '\'';
break;
default:
qc = '"';
}
const param = this.localRoutine.parameters.find(param => param.name === curr);
const value = [...NUMBER, ...FLOAT].includes(param.type) ? this.values[curr] : `${qc}${this.values[curr]}${qc}`;
acc.push(value);
return acc;
}, []);

View File

@@ -25,7 +25,7 @@
>
</div>
</div>
<div class="form-group">
<div v-if="customizations.definer" class="form-group">
<label class="form-label col-4">
{{ $t('word.definer') }}
</label>
@@ -53,7 +53,7 @@
</select>
</div>
</div>
<div class="form-group">
<div v-if="customizations.comment" class="form-group">
<label class="form-label col-4">
{{ $t('word.comment') }}
</label>
@@ -76,7 +76,7 @@
</select>
</div>
</div>
<div class="form-group">
<div v-if="customizations.comment" class="form-group">
<label class="form-label col-4">
{{ $t('message.dataAccess') }}
</label>
@@ -89,7 +89,7 @@
</select>
</div>
</div>
<div class="form-group">
<div v-if="customizations.procedureDeterministic" class="form-group">
<div class="col-4" />
<div class="column">
<label class="form-checkbox form-inline">
@@ -117,7 +117,7 @@ export default {
return {
localRoutine: {
definer: '',
sql: 'BEGIN\r\n\r\nEND',
sql: '',
parameters: [],
name: '',
comment: '',
@@ -131,9 +131,14 @@ export default {
computed: {
schema () {
return this.workspace.breadcrumbs.schema;
},
customizations () {
return this.workspace.customizations;
}
},
mounted () {
if (this.customizations.procedureSql)
this.localRoutine.sql = this.customizations.procedureSql;
setTimeout(() => {
this.$refs.firstInput.focus();
}, 20);

View File

@@ -25,7 +25,7 @@
>
</div>
</div>
<div class="form-group">
<div v-if="workspace.customizations.comment" class="form-group">
<label class="form-label col-4">
{{ $t('word.comment') }}
</label>
@@ -37,7 +37,7 @@
>
</div>
</div>
<div class="form-group">
<div v-if="workspace.customizations.collations" class="form-group">
<label class="form-label col-4">
{{ $t('word.collation') }}
</label>
@@ -53,7 +53,7 @@
</select>
</div>
</div>
<div class="form-group">
<div v-if="workspace.customizations.engines" class="form-group">
<label class="form-label col-4">
{{ $t('word.engine') }}
</label>
@@ -103,10 +103,14 @@ export default {
getDatabaseVariable: 'workspaces/getDatabaseVariable'
}),
defaultCollation () {
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value || '';
if (this.workspace.customizations.collations)
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value || '';
return '';
},
defaultEngine () {
return this.workspace.engines.find(engine => engine.isDefault).name;
if (this.workspace.customizations.engines)
return this.workspace.engines.find(engine => engine.isDefault).name;
return '';
}
},
mounted () {

View File

@@ -25,7 +25,7 @@
</div>
</div>
<div class="column col-6">
<div class="form-group">
<div v-if="workspace.customizations.definer" class="form-group">
<label class="form-label">{{ $t('word.definer') }}</label>
<select v-model="localView.definer" class="form-select">
<option value="">
@@ -44,7 +44,7 @@
</div>
<div class="columns">
<div class="column col-4">
<div class="form-group">
<div v-if="workspace.customizations.viewSqlSecurity" class="form-group">
<label class="form-label">{{ $t('message.sqlSecurity') }}</label>
<label class="form-radio">
<input
@@ -67,7 +67,7 @@
</div>
</div>
<div class="column col-4">
<div class="form-group">
<div v-if="workspace.customizations.viewAlgorithm" class="form-group">
<label class="form-label">{{ $t('word.algorithm') }}</label>
<label class="form-radio">
<input
@@ -99,7 +99,7 @@
</div>
</div>
<div class="column col-4">
<div class="form-group">
<div v-if="workspace.customizations.viewUpdateOption" class="form-group">
<label class="form-label">{{ $t('message.updateOption') }}</label>
<label class="form-radio">
<input

View File

@@ -30,12 +30,21 @@
<a class="c-hand">{{ $t('word.themes') }}</a>
</li>
<li
v-if="updateStatus !== 'disabled'"
class="tab-item"
:class="{'active': selectedTab === 'update'}"
@click="selectTab('update')"
>
<a class="c-hand" :class="{'badge badge-update': hasUpdates}">{{ $t('word.update') }}</a>
</li>
<li
v-if="updateStatus !== 'disabled'"
class="tab-item"
:class="{'active': selectedTab === 'changelog'}"
@click="selectTab('changelog')"
>
<a class="c-hand">{{ $t('word.changelog') }}</a>
</li>
<li
class="tab-item"
:class="{'active': selectedTab === 'about'}"
@@ -45,7 +54,7 @@
</li>
</ul>
</div>
<div v-if="selectedTab === 'general'" class="panel-body py-4">
<div v-show="selectedTab === 'general'" class="panel-body py-4">
<div class="container">
<form class="form-horizontal columns">
<div class="column col-12 h6 text-uppercase mb-1">
@@ -133,26 +142,35 @@
</div>
</div>
<div v-if="selectedTab === 'themes'" class="panel-body py-4">
<div v-show="selectedTab === 'themes'" class="panel-body py-4">
<div class="container">
<div class="columns">
<div class="column col-12 h6 text-uppercase mb-2">
{{ $t('message.applicationTheme') }}
</div>
<div class="column col-6 c-hand theme-block" :class="{'selected': applicationTheme === 'dark'}">
<div
class="column col-6 c-hand theme-block"
:class="{'selected': applicationTheme === 'dark'}"
@click="changeApplicationTheme('dark')"
>
<img :src="require('@/images/dark.png').default" class="img-responsive img-fit-cover s-rounded">
<div class="theme-name">
<div class="theme-name text-light">
<i class="mdi mdi-moon-waning-crescent mdi-48px" />
<div class="h6 mt-4">
{{ $t('word.dark') }}
</div>
</div>
</div>
<div class="column col-6 theme-block disabled" :class="{'selected': applicationTheme === 'light'}">
<div class="theme-name">
<div
class="column col-6 c-hand theme-block"
:class="{'selected': applicationTheme === 'light'}"
@click="changeApplicationTheme('light')"
>
<img :src="require('@/images/light.png').default" class="img-responsive img-fit-cover s-rounded">
<div class="theme-name text-dark">
<i class="mdi mdi-white-balance-sunny mdi-48px" />
<div class="h6 mt-4">
{{ $t('word.light') }} (Coming)
{{ $t('word.light') }}
</div>
</div>
</div>
@@ -185,8 +203,9 @@
</select>
</div>
<div class="column col-12">
<QueryEditor
<BaseTextEditor
:value="exampleQuery"
mode="sql"
:workspace="workspace"
:read-only="true"
:height="270"
@@ -196,11 +215,14 @@
</div>
</div>
<div v-if="selectedTab === 'update'" class="panel-body py-4">
<div v-show="selectedTab === 'update'" class="panel-body py-4">
<ModalSettingsUpdate />
</div>
<div v-show="selectedTab === 'changelog'" class="panel-body py-4">
<ModalSettingsChangelog />
</div>
<div v-if="selectedTab === 'about'" class="panel-body py-4">
<div v-show="selectedTab === 'about'" class="panel-body py-4">
<div class="text-center">
<img :src="require('@/images/logo.svg').default" width="128">
<h4>{{ appName }}</h4>
@@ -222,14 +244,16 @@
import { mapActions, mapGetters } from 'vuex';
import localesNames from '@/i18n/supported-locales';
import ModalSettingsUpdate from '@/components/ModalSettingsUpdate';
import QueryEditor from '@/components/QueryEditor';
import ModalSettingsChangelog from '@/components/ModalSettingsChangelog';
import BaseTextEditor from '@/components/BaseTextEditor';
const { shell } = require('electron');
export default {
name: 'ModalSettings',
components: {
ModalSettingsUpdate,
QueryEditor
ModalSettingsChangelog,
BaseTextEditor
},
data () {
return {
@@ -350,6 +374,7 @@ ORDER BY
changeLocale: 'settings/changeLocale',
changeAutoComplete: 'settings/changeAutoComplete',
changeLineWrap: 'settings/changeLineWrap',
changeApplicationTheme: 'settings/changeApplicationTheme',
changeEditorTheme: 'settings/changeEditorTheme',
updateNotificationsTimeout: 'settings/updateNotificationsTimeout'
}),
@@ -382,54 +407,58 @@ ORDER BY
<style lang="scss">
#settings {
.modal-body {
overflow: hidden;
.modal-container {
position: absolute;
top: 17.5vh;
.panel-body {
height: calc(70vh - 70px);
overflow: auto;
.modal-body {
overflow: hidden;
.theme-block {
position: relative;
text-align: center;
.panel-body {
min-height: calc(25vh - 70px);
overflow: auto;
&.selected {
img {
box-shadow: 0 0 0 3px $primary-color;
.theme-block {
position: relative;
text-align: center;
&.selected {
img {
box-shadow: 0 0 0 3px $primary-color;
}
}
&.disabled {
cursor: not-allowed;
opacity: 0.5;
}
.theme-name {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
top: 0;
height: 100%;
width: 100%;
}
}
&.disabled {
cursor: not-allowed;
opacity: 0.5;
}
.theme-name {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
top: 0;
height: 100%;
width: 100%;
text-shadow: 0 0 8px #000;
}
}
}
.badge::after {
background: #32b643;
}
.badge::after {
background: #32b643;
}
.badge-update::after {
bottom: initial;
background: $primary-color;
}
.badge-update::after {
bottom: initial;
background: $primary-color;
}
.form-label {
display: flex;
align-items: center;
.form-label {
display: flex;
align-items: center;
}
}
}
}

View File

@@ -0,0 +1,78 @@
<template>
<div class="p-relative">
<BaseLoader v-if="isLoading" />
<div
id="changelog"
class="container"
v-html="changelog"
/>
<div v-if="isError" class="empty">
<div class="empty-icon">
<i class="mdi mdi-48px mdi-alert-outline" />
</div>
</div>
</div>
</template>
<script>
import marked from 'marked';
import BaseLoader from '@/components/BaseLoader';
export default {
name: 'ModalSettingsChangelog',
components: {
BaseLoader
},
data () {
return {
changelog: '',
isLoading: true,
error: '',
isError: false
};
},
created () {
this.getChangelog();
},
methods: {
async getChangelog () {
try {
const apiRes = await fetch('https://api.github.com/repos/Fabio286/antares/releases/latest', {
method: 'GET'
});
const { body } = await apiRes.json();
const markdown = body.substr(0, body.indexOf('### Download'));
const renderer = {
link (href, title, text) {
return text;
},
listitem (text) {
return `<li>${text.replace(/ *\([^)]*\) */g, '')}</li>`;
}
};
marked.use({ renderer });
this.changelog = marked(markdown);
}
catch (err) {
this.error = err.message;
this.isError = true;
}
this.isLoading = false;
}
}
};
</script>
<style lang="scss">
#changelog {
h3 {
font-size: 1rem;
}
li {
margin-top: 0;
}
}
</style>

View File

@@ -92,9 +92,3 @@ export default {
}
};
</script>
<style lang="scss">
.empty {
color: $body-font-color;
}
</style>

View File

@@ -312,19 +312,6 @@ export default {
width: 17px;
}
.ace_dark.ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {
background-color: #c9561a99;
}
.ace_dark.ace_editor.ace_autocomplete .ace_marker-layer .ace_line-hover {
background-color: #c9571a33;
border: none;
}
.ace_dark.ace_editor.ace_autocomplete .ace_completion-highlight {
color: #e0d00c;
}
.ace_gutter-cell.ace_breakpoint {
&::before {
content: '\F0403';

View File

@@ -64,13 +64,11 @@ export default {
display: flex;
justify-content: space-between;
align-items: center;
background: $primary-color;
padding: 0 0.2rem;
position: fixed;
bottom: 0;
left: 0;
right: 0;
box-shadow: 0 0 1px 0 #000;
.footer-elements {
list-style: none;
@@ -88,10 +86,6 @@ export default {
&.footer-link {
cursor: pointer;
transition: background 0.2s;
&:hover {
background: rgba($color: #fff, $alpha: 0.1);
}
}
}
}

View File

@@ -0,0 +1,75 @@
<template>
<ConfirmModal
:confirm-text="$t('word.update')"
:cancel-text="$t('word.close')"
size="large"
:hide-footer="true"
@hide="hideScratchpad"
>
<template :slot="'header'">
<div class="d-flex">
<i class="mdi mdi-24px mdi-notebook-edit-outline mr-1" /> {{ $t('word.scratchpad') }}
</div>
</template>
<div :slot="'body'">
<div>
<div>
<TextEditor
:value.sync="localNotes"
editor-class="textarea-editor"
mode="markdown"
:auto-focus="true"
:show-line-numbers="false"
/>
</div>
<small class="text-gray">{{ $t('message.markdownSupported') }}</small>
</div>
</div>
</ConfirmModal>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import ConfirmModal from '@/components/BaseConfirmModal';
import TextEditor from '@/components/BaseTextEditor';
export default {
name: 'TheScratchpad',
components: {
ConfirmModal,
TextEditor
},
data () {
return {
localNotes: '',
debounceTimeout: null
};
},
computed: {
...mapGetters({
notes: 'scratchpad/getNotes'
})
},
watch: {
localNotes () {
clearTimeout(this.debounceTimeout);
this.debounceTimeout = setTimeout(() => {
this.changeNotes(this.localNotes);
}, 200);
}
},
created () {
this.localNotes = this.notes;
},
methods: {
...mapActions({
hideScratchpad: 'application/hideScratchpad',
changeNotes: 'scratchpad/changeNotes'
}),
hideModal () {
this.$emit('hide');
}
}
};
</script>

View File

@@ -36,6 +36,10 @@
<div class="settingbar-bottom-elements">
<ul class="settingbar-elements">
<li class="settingbar-element btn btn-link ex-tooltip" @click="showScratchpad">
<i class="settingbar-element-icon mdi mdi-24px mdi-notebook-edit-outline text-light" />
<span class="ex-tooltip-content">{{ $t('word.scratchpad') }}</span>
</li>
<li class="settingbar-element btn btn-link ex-tooltip" @click="showSettingModal('general')">
<i class="settingbar-element-icon mdi mdi-24px mdi-cog text-light" :class="{' badge badge-update': hasUpdates}" />
<span class="ex-tooltip-content">{{ $t('word.settings') }}</span>
@@ -90,6 +94,7 @@ export default {
updateConnections: 'connections/updateConnections',
showNewConnModal: 'application/showNewConnModal',
showSettingModal: 'application/showSettingModal',
showScratchpad: 'application/showScratchpad',
selectWorkspace: 'workspaces/selectWorkspace'
}),
contextMenu (event, connection) {
@@ -117,9 +122,7 @@ export default {
flex-direction: column;
justify-content: space-between;
align-items: center;
background: $bg-color-light;
padding: 0;
box-shadow: 0 0 1px 0 #000;
z-index: 9;
.settingbar-top-elements {
@@ -134,7 +137,6 @@ export default {
.settingbar-bottom-elements {
padding-top: 0.5rem;
background: $bg-color-light;
z-index: 1;
}
@@ -162,7 +164,6 @@ export default {
}
&.selected {
border-left-color: $body-font-color;
opacity: 1;
}
@@ -171,12 +172,10 @@ export default {
bottom: -10px;
right: 0;
position: absolute;
background: $success-color;
}
&.badge-update::after {
bottom: initial;
background: $primary-color;
}
}
}

View File

@@ -102,12 +102,10 @@ export default {
display: flex;
position: relative;
justify-content: space-between;
background: $bg-color-light;
align-items: center;
height: $titlebar-height;
-webkit-app-region: drag;
user-select: none;
box-shadow: 0 0 1px 0 #000;
z-index: 9999;
.titlebar-resizer {
@@ -149,11 +147,6 @@ export default {
&:hover {
opacity: 1;
background: rgba($color: #fff, $alpha: 0.2);
}
&.close-button:hover {
background: red;
}
}
}

View File

@@ -304,7 +304,6 @@ export default {
height: calc(100vh - #{$excluding-size});
.tab-block {
background: $bg-color-light;
margin-top: 0;
flex-direction: row;
align-items: flex-start;
@@ -324,7 +323,6 @@ export default {
> a {
padding: 0.2rem 0.8rem;
color: $body-font-color;
cursor: pointer;
display: flex;
align-items: center;
@@ -355,7 +353,6 @@ export default {
&.tools-dropdown {
.tab-link:focus {
color: $primary-color;
opacity: 1;
outline: 0;
box-shadow: none;
@@ -374,11 +371,6 @@ export default {
white-space: nowrap;
border: 0;
&:hover {
color: $primary-color;
background: $bg-color-gray;
}
.tool-icon {
line-height: 1;
display: inline-block;
@@ -414,11 +406,9 @@ export default {
.th {
position: sticky;
top: 0;
background: $bg-color;
border: 1px solid;
border-left: none;
border-bottom-width: 2px;
border-color: $bg-color-light;
padding: 0;
font-weight: 700;
font-size: 0.7rem;
@@ -433,7 +423,6 @@ export default {
.td {
border-right: 1px solid;
border-bottom: 1px solid;
border-color: $bg-color-light;
padding: 0 0.4rem;
text-overflow: ellipsis;
max-width: 200px;
@@ -443,8 +432,6 @@ export default {
position: relative;
&:focus {
box-shadow: inset 0 0 0 1px $body-font-color;
background: rgba($color: #000, $alpha: 0.3);
outline: none;
}
}

View File

@@ -1,6 +1,6 @@
<template>
<div class="columns">
<div class="column col-12 empty text-light">
<div class="column col-12 empty">
<div class="empty-icon">
<i class="mdi mdi-48px mdi-power-plug-off" />
</div>

View File

@@ -460,8 +460,6 @@ export default {
justify-content: flex-start;
align-items: center;
text-align: left;
background: $bg-color-gray;
box-shadow: 0 0 1px 0 #000;
z-index: 8;
flex: initial;
position: relative;

View File

@@ -32,6 +32,7 @@
<ModalAskParameters
v-if="isAskingParameters"
:local-routine="localElement"
:client="workspace.client"
@confirm="runElement"
@close="hideAskParamsModal"
/>
@@ -205,13 +206,13 @@ export default {
case 'maria':
case 'mysql':
case 'pg':
sql = `CALL \`${this.localElement.name}\` (${params.join(',')})`;
sql = `CALL ${this.localElement.name}(${params.join(',')})`;
break;
case 'mssql':
sql = `EXEC ${this.localElement.name} ${params.join(',')}`;
break;
default:
sql = `CALL \`${this.localElement.name}\` (${params.join(',')})`;
sql = `CALL \`${this.localElement.name}\`(${params.join(',')})`;
}
this.newTab({ uid: this.workspace.uid, content: sql, autorun: true });

View File

@@ -171,7 +171,8 @@ export default {
...mapGetters({
getLoadedSchemas: 'workspaces/getLoadedSchemas',
getWorkspace: 'workspaces/getWorkspace',
getSearchTerm: 'workspaces/getSearchTerm'
getSearchTerm: 'workspaces/getSearchTerm',
applicationTheme: 'settings/getApplicationTheme'
}),
searchTerm () {
return this.getSearchTerm(this.connection.uid);
@@ -239,7 +240,10 @@ export default {
},
piePercentage (val) {
const perc = val / this.maxSize * 100;
return { background: `conic-gradient(lime ${perc}%, white 0)` };
if (this.applicationTheme === 'dark')
return { background: `conic-gradient(lime ${perc}%, white 0)` };
else
return { background: `conic-gradient(teal ${perc}%, silver 0)` };
},
setBreadcrumbs (payload) {
if (this.breadcrumbs.schema === payload.schema && this.breadcrumbs.table === payload.table) return;
@@ -262,7 +266,6 @@ export default {
.database-name {
position: sticky;
top: 0;
background: $bg-color-gray;
z-index: 2;
}
@@ -308,26 +311,15 @@ export default {
.database-name,
.misc-name {
&:hover {
color: $body-font-color;
background: $bg-color-light;
border-radius: 2px;
}
}
a.table-name {
&:hover {
color: inherit;
background: inherit;
}
}
.menu-item {
line-height: 1.2;
position: relative;
&:hover {
color: $body-font-color;
background: rgba($color: #fff, $alpha: 0.05);
border-radius: 2px;
}
}

View File

@@ -2,6 +2,7 @@
<ConfirmModal
:confirm-text="$t('word.confirm')"
size="medium"
class="options-modal"
@confirm="confirmForeignsChange"
@hide="$emit('hide')"
>
@@ -36,7 +37,7 @@
v-for="foreign in foreignProxy"
:key="foreign._id"
class="tile tile-centered c-hand mb-1 p-1"
:class="{'selected-foreign': selectedForeignID === foreign._id}"
:class="{'selected-element': selectedForeignID === foreign._id}"
@click="selectForeign($event, foreign._id)"
>
<div class="tile-icon">
@@ -268,8 +269,10 @@ export default {
this.$emit('foreigns-update', this.foreignProxy);
},
selectForeign (event, id) {
if (this.selectedForeignID !== id && !event.target.classList.contains('remove-field'))
if (this.selectedForeignID !== id && !event.target.classList.contains('remove-field')) {
this.selectedForeignID = id;
this.getRefFields();
}
},
getModalInnerHeight () {
const modalBody = document.querySelector('.modal-body');
@@ -369,23 +372,16 @@ export default {
}
&:hover {
background: $bg-color-light;
.tile-action {
opacity: 1;
}
}
&.selected-foreign {
background: $bg-color-light;
&.selected-element {
opacity: 1;
}
}
.editor-col {
border-left: 2px solid $bg-color-light;
}
.fields-list {
max-height: 80px;
overflow: auto;

View File

@@ -2,6 +2,7 @@
<ConfirmModal
:confirm-text="$t('word.confirm')"
size="medium"
class="options-modal"
@confirm="confirmParametersChange"
@hide="$emit('hide')"
>
@@ -36,7 +37,7 @@
v-for="param in parametersProxy"
:key="param._id"
class="tile tile-centered c-hand mb-1 p-1"
:class="{'selected-param': selectedParam === param._id}"
:class="{'selected-element': selectedParam === param._id}"
@click="selectParameter($event, param._id)"
>
<div class="tile-icon">
@@ -249,23 +250,16 @@ export default {
}
&:hover {
background: $bg-color-light;
.tile-action {
opacity: 1;
}
}
&.selected-param {
background: $bg-color-light;
&.selected-element {
opacity: 1;
}
}
.editor-col {
border-left: 2px solid $bg-color-light;
}
.fields-list {
max-height: 300px;
overflow: auto;

View File

@@ -2,6 +2,7 @@
<ConfirmModal
:confirm-text="$t('word.confirm')"
size="medium"
class="options-modal"
@confirm="confirmIndexesChange"
@hide="$emit('hide')"
>
@@ -36,7 +37,7 @@
v-for="index in indexesProxy"
:key="index._id"
class="tile tile-centered c-hand mb-1 p-1"
:class="{'selected-index': selectedIndexID === index._id}"
:class="{'selected-element': selectedIndexID === index._id}"
@click="selectIndex($event, index._id)"
>
<div class="tile-icon">
@@ -256,23 +257,16 @@ export default {
}
&:hover {
background: $bg-color-light;
.tile-action {
opacity: 1;
}
}
&.selected-index {
background: $bg-color-light;
&.selected-element {
opacity: 1;
}
}
.editor-col {
border-left: 2px solid $bg-color-light;
}
.fields-list {
max-height: 300px;
overflow: auto;

View File

@@ -26,7 +26,7 @@
>
</div>
</div>
<div class="form-group">
<div v-if="workspace.customizations.comment" class="form-group">
<label class="form-label col-4">
{{ $t('word.comment') }}
</label>
@@ -38,7 +38,7 @@
>
</div>
</div>
<div class="form-group">
<div v-if="workspace.customizations.autoIncrement" class="form-group">
<label class="form-label col-4">
{{ $t('word.autoIncrement') }}
</label>
@@ -50,7 +50,7 @@
>
</div>
</div>
<div class="form-group">
<div v-if="workspace.customizations.collations" class="form-group">
<label class="form-label col-4">
{{ $t('word.collation') }}
</label>
@@ -66,7 +66,7 @@
</select>
</div>
</div>
<div class="form-group">
<div v-if="workspace.customizations.engines" class="form-group">
<label class="form-label col-4">
{{ $t('word.engine') }}
</label>

View File

@@ -26,7 +26,7 @@
>
</div>
</div>
<div class="form-group">
<div v-if="customizations.definer" class="form-group">
<label class="form-label col-4">
{{ $t('word.definer') }}
</label>
@@ -54,7 +54,7 @@
</select>
</div>
</div>
<div class="form-group">
<div v-if="customizations.comment" class="form-group">
<label class="form-label col-4">
{{ $t('word.comment') }}
</label>
@@ -77,7 +77,7 @@
</select>
</div>
</div>
<div class="form-group">
<div v-if="customizations.procedureDataAccess" class="form-group">
<label class="form-label col-4">
{{ $t('message.dataAccess') }}
</label>
@@ -90,7 +90,7 @@
</select>
</div>
</div>
<div class="form-group">
<div v-if="customizations.procedureDeterministic" class="form-group">
<div class="col-4" />
<div class="column">
<label class="form-checkbox form-inline">
@@ -124,6 +124,9 @@ export default {
computed: {
isTableNameValid () {
return this.optionsProxy.name !== '';
},
customizations () {
return this.workspace.customizations;
}
},
created () {

View File

@@ -2,6 +2,7 @@
<ConfirmModal
:confirm-text="$t('word.confirm')"
size="medium"
class="options-modal"
@confirm="confirmParametersChange"
@hide="$emit('hide')"
>
@@ -36,7 +37,7 @@
v-for="param in parametersProxy"
:key="param._id"
class="tile tile-centered c-hand mb-1 p-1"
:class="{'selected-param': selectedParam === param._id}"
:class="{'selected-element': selectedParam === param._id}"
@click="selectParameter($event, param._id)"
>
<div class="tile-icon">
@@ -105,7 +106,7 @@
</select>
</div>
</div>
<div class="form-group">
<div v-if="customizations.parametersLength" class="form-group">
<label class="form-label col-3">
{{ $t('word.length') }}
</label>
@@ -198,6 +199,9 @@ export default {
},
isChanged () {
return JSON.stringify(this.localParameters) !== JSON.stringify(this.parametersProxy);
},
customizations () {
return this.workspace.customizations;
}
},
mounted () {
@@ -234,10 +238,10 @@ export default {
addParameter () {
this.parametersProxy = [...this.parametersProxy, {
_id: uidGen(),
name: `Param${this.i++}`,
type: 'INT',
name: `param${this.i++}`,
type: this.workspace.dataTypes[0].types[0].name,
context: 'IN',
length: 10
length: ''
}];
if (this.parametersProxy.length === 1)
@@ -280,23 +284,16 @@ export default {
}
&:hover {
background: $bg-color-light;
.tile-action {
opacity: 1;
}
}
&.selected-param {
background: $bg-color-light;
&.selected-element {
opacity: 1;
}
}
.editor-col {
border-left: 2px solid $bg-color-light;
}
.fields-list {
max-height: 300px;
overflow: auto;

View File

@@ -433,11 +433,11 @@ export default {
_id: uidGen(),
name: `${this.$tc('word.field', 1)}_${++this.newFieldsCounter}`,
key: '',
type: 'int',
type: this.workspace.dataTypes[0].types[0].name,
schema: this.schema,
table: this.table,
numPrecision: null,
numLength: 11,
numLength: this.workspace.dataTypes[0].types[0].length,
datePrecision: null,
charLength: null,
nullable: false,

View File

@@ -73,6 +73,7 @@
<ModalAskParameters
v-if="isAskingParameters"
:local-routine="localFunction"
:client="workspace.client"
@confirm="runFunction"
@close="hideAskParamsModal"
/>

View File

@@ -74,6 +74,7 @@
<ModalAskParameters
v-if="isAskingParameters"
:local-routine="localRoutine"
:client="workspace.client"
@confirm="runRoutine"
@close="hideAskParamsModal"
/>
@@ -281,13 +282,13 @@ export default {
case 'maria':
case 'mysql':
case 'pg':
sql = `CALL \`${this.originalRoutine.name}\` (${params.join(',')})`;
sql = `CALL ${this.originalRoutine.name}(${params.join(',')})`;
break;
case 'mssql':
sql = `EXEC ${this.originalRoutine.name} ${params.join(',')}`;
break;
default:
sql = `CALL \`${this.originalRoutine.name}\` (${params.join(',')})`;
sql = `CALL \`${this.originalRoutine.name}\`(${params.join(',')})`;
}
this.newTab({ uid: this.connection.uid, content: sql, autorun: true });

View File

@@ -37,7 +37,7 @@
</div>
</div>
<div class="column col-auto">
<div class="form-group">
<div v-if="workspace.customizations.definer" class="form-group">
<label class="form-label">{{ $t('word.definer') }}</label>
<select
v-if="workspace.users.length"
@@ -68,7 +68,7 @@
</div>
<div class="columns">
<div class="column col-auto mr-2">
<div class="form-group">
<div v-if="workspace.customizations.viewSqlSecurity" class="form-group">
<label class="form-label">{{ $t('message.sqlSecurity') }}</label>
<label class="form-radio">
<input
@@ -91,7 +91,7 @@
</div>
</div>
<div class="column col-auto mr-2">
<div class="form-group">
<div v-if="workspace.customizations.viewAlgorithm" class="form-group">
<label class="form-label">{{ $t('word.algorithm') }}</label>
<label class="form-radio">
<input
@@ -122,7 +122,7 @@
</label>
</div>
</div>
<div class="column col-auto mr-2">
<div v-if="workspace.customizations.viewUpdateOption" class="column col-auto mr-2">
<div class="form-group">
<label class="form-label">{{ $t('message.updateOption') }}</label>
<label class="form-radio">

View File

@@ -42,6 +42,13 @@
</div>
</div>
</div>
<div v-if="customizations.tableArray" class="th">
<div class="column-resizable">
<div class="table-column-title">
{{ $t('word.array') }}
</div>
</div>
</div>
<div class="th">
<div class="column-resizable">
<div class="table-column-title">
@@ -49,21 +56,21 @@
</div>
</div>
</div>
<div class="th">
<div v-if="customizations.unsigned" class="th">
<div class="column-resizable">
<div class="table-column-title">
{{ $t('word.unsigned') }}
</div>
</div>
</div>
<div class="th">
<div v-if="customizations.nullable" class="th">
<div class="column-resizable">
<div class="table-column-title">
{{ $t('message.allowNull') }}
</div>
</div>
</div>
<div class="th">
<div v-if="customizations.zerofill" class="th">
<div class="column-resizable">
<div class="table-column-title">
{{ $t('message.zeroFill') }}
@@ -77,14 +84,14 @@
</div>
</div>
</div>
<div class="th">
<div v-if="customizations.comment" class="th">
<div class="column-resizable">
<div class="table-column-title">
{{ $t('word.comment') }}
</div>
</div>
</div>
<div class="th">
<div v-if="customizations.collation" class="th">
<div class="column-resizable min-100">
<div class="table-column-title">
{{ $t('word.collation') }}
@@ -106,6 +113,7 @@
:indexes="getIndexes(row.name)"
:foreigns="getForeigns(row.name)"
:data-types="dataTypes"
:customizations="customizations"
@contextmenu="contextMenu"
/>
</draggable>
@@ -154,6 +162,9 @@ export default {
workspaceSchema () {
return this.getWorkspace(this.connUid).breadcrumbs.schema;
},
customizations () {
return this.getWorkspace(this.connUid).customizations;
},
dataTypes () {
return this.getWorkspace(this.connUid).dataTypes;
},

View File

@@ -1,8 +1,8 @@
<template>
<div class="tr" @contextmenu.prevent="$emit('contextmenu', $event, localRow._id)">
<div class="td" tabindex="0">
<div class="row-draggable">
<i class="mdi mdi-drag-horizontal row-draggable-icon" />
<div :class="customizations.sortableFields ? 'row-draggable' : 'text-center'">
<i v-if="customizations.sortableFields" class="mdi mdi-drag-horizontal row-draggable-icon" />
{{ localRow.order }}
</div>
</div>
@@ -76,6 +76,16 @@
</optgroup>
</select>
</div>
<div
v-if="customizations.tableArray"
class="td"
tabindex="0"
>
<label class="form-checkbox">
<input v-model="localRow.isArray" type="checkbox">
<i class="form-icon" />
</label>
</div>
<div class="td type-int" tabindex="0">
<template v-if="fieldType.length">
<span
@@ -96,7 +106,11 @@
>
</template>
</div>
<div class="td" tabindex="0">
<div
v-if="customizations.unsigned"
class="td"
tabindex="0"
>
<label class="form-checkbox">
<input
v-model="localRow.unsigned"
@@ -106,7 +120,11 @@
<i class="form-icon" />
</label>
</div>
<div class="td" tabindex="0">
<div
v-if="customizations.nullable"
class="td"
tabindex="0"
>
<label class="form-checkbox">
<input
v-model="localRow.nullable"
@@ -116,7 +134,11 @@
<i class="form-icon" />
</label>
</div>
<div class="td" tabindex="0">
<div
v-if="customizations.zerofill"
class="td"
tabindex="0"
>
<label class="form-checkbox">
<input
v-model="localRow.zerofill"
@@ -131,7 +153,11 @@
{{ fieldDefault }}
</span>
</div>
<div class="td type-varchar" tabindex="0">
<div
v-if="customizations.comment"
class="td type-varchar"
tabindex="0"
>
<span
v-if="!isInlineEditor.comment"
class="cell-content"
@@ -149,7 +175,11 @@
@blur="editOFF"
>
</div>
<div class="td" tabindex="0">
<div
v-if="customizations.collation"
class="td"
tabindex="0"
>
<template v-if="fieldType.collation">
<span
v-if="!isInlineEditor.collation"
@@ -220,7 +250,7 @@
</div>
</div>
</div>
<div class="mb-2">
<div v-if="customizations.nullable" class="mb-2">
<label class="form-radio form-inline">
<input
v-model="defaultValue.type"
@@ -230,7 +260,7 @@
><i class="form-icon" /> NULL
</label>
</div>
<div class="mb-2">
<div v-if="customizations.autoIncrement" class="mb-2">
<label class="form-radio form-inline">
<input
v-model="defaultValue.type"
@@ -261,7 +291,7 @@
</div>
</div>
</div>
<div>
<div v-if="customizations.onUpdate">
<div class="form-group">
<label class="form-label col-4">
{{ $t('message.onUpdate') }}
@@ -294,7 +324,8 @@ export default {
row: Object,
dataTypes: Array,
indexes: Array,
foreigns: Array
foreigns: Array,
customizations: Object
},
data () {
return {

View File

@@ -232,7 +232,6 @@ export default {
.btn {
display: flex;
align-self: center;
color: $body-font-color;
margin-right: 0.4rem;
}
}

View File

@@ -86,6 +86,7 @@ import BaseVirtualScroll from '@/components/BaseVirtualScroll';
import WorkspaceQueryTableRow from '@/components/WorkspaceQueryTableRow';
import TableContext from '@/components/WorkspaceQueryTableContext';
import { mapActions, mapGetters } from 'vuex';
import moment from 'moment';
export default {
name: 'WorkspaceQueryTable',
@@ -122,12 +123,13 @@ export default {
return this.getWorkspace(this.connUid).breadcrumbs.schema;
},
primaryField () {
const primaryFields = this.fields.filter(field => ['pri', 'uni'].includes(field.key));
const primaryFields = this.fields.filter(field => field.key === 'pri');
const uniqueFields = this.fields.filter(field => field.key === 'uni');
if (primaryFields.length > 1 || !primaryFields.length)
if ((primaryFields.length > 1 || !primaryFields.length) && (uniqueFields.length > 1 || !uniqueFields.length))
return false;
return primaryFields[0];
return primaryFields[0] || uniqueFields[0];
},
isSortable () {
return this.fields.every(field => field.name);
@@ -298,6 +300,17 @@ export default {
delete row._id;
delete orgRow._id;
Object.keys(orgRow).forEach(key => { // remap the row
if (orgRow[key] instanceof Date && moment(orgRow[key]).isValid()) { // if datetime
let datePrecision = '';
const precision = this.fields.find(field => field.name === key).datePrecision;
for (let i = 0; i < precision; i++)
datePrecision += i === 0 ? '.S' : 'S';
orgRow[key] = moment(orgRow[key]).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
}
});
const params = {
primary: this.primaryField.name,
schema: this.getSchema(this.resultsetIndex),

View File

@@ -182,7 +182,7 @@ import { mimeFromHex } from 'common/libs/mimeFromHex';
import { formatBytes } from 'common/libs/formatBytes';
import { bufferToBase64 } from 'common/libs/bufferToBase64';
import hexToBinary from 'common/libs/hexToBinary';
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BOOLEAN, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BOOLEAN, DATE, TIME, DATETIME, BLOB, BIT, HAS_TIMEZONE } from 'common/fieldTypes';
import { VueMaskDirective } from 'v-mask';
import ConfirmModal from '@/components/BaseConfirmModal';
import TextEditor from '@/components/BaseTextEditor';
@@ -286,6 +286,9 @@ export default {
for (let i = 0; i < precision; i++)
timeMask += i === 0 ? '.#' : '#';
if (HAS_TIMEZONE.includes(this.editingType))
timeMask += 'X##';
return { type: 'text', mask: timeMask };
}
@@ -299,6 +302,9 @@ export default {
for (let i = 0; i < precision; i++)
datetimeMask += i === 0 ? '.#' : '#';
if (HAS_TIMEZONE.includes(this.editingType))
datetimeMask += 'X##';
return { type: 'text', mask: datetimeMask };
}

View File

@@ -289,10 +289,6 @@ export default {
.export-dropdown {
.menu {
min-width: 100%;
.menu-item a:hover {
background: $bg-color-gray;
}
}
}
</style>

View File

@@ -101,7 +101,10 @@ module.exports = {
tools: 'Tools',
variables: 'Variables',
processes: 'Processes',
database: 'Database'
database: 'Database',
scratchpad: 'Scratchpad',
array: 'Array',
changelog: 'Changelog'
},
message: {
appWelcome: 'Welcome to Antares SQL Client!',
@@ -204,7 +207,8 @@ module.exports = {
createNewSchema: 'Create new schema',
schemaName: 'Schema name',
editSchema: 'Edit schema',
deleteSchema: 'Delete schema'
deleteSchema: 'Delete schema',
markdownSupported: 'Markdown supported'
},
faker: {
address: 'Address',

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@@ -42,6 +42,7 @@ export default {
try {
const { status, response } = await Tables.deleteTableRows(params);
this.isQuering = false;
if (status === 'success')
this.reloadTable();
@@ -50,9 +51,8 @@ export default {
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
this.isQuering = false;
}
this.isQuering = false;
}
}
};

View File

@@ -30,6 +30,7 @@
"macaddr": $string-color,
"macaddr8": $string-color,
"uuid": $string-color,
"regproc": $string-color,
"int": $number-color,
"tinyint": $number-color,
"smallint": $number-color,
@@ -76,6 +77,7 @@
"tsvector": $array-color,
"tsquery": $array-color,
"pg_node_tree": $array-color,
"aclitem": $array-color,
"unknown": $unknown-color,
)
);

View File

@@ -27,7 +27,6 @@
.td,
.th {
border-bottom: $border-width solid $border-color;
padding: $unit-3 $unit-2;
display: table-cell;
}
@@ -35,37 +34,4 @@
.th {
border-bottom-width: $border-width-lg;
}
&,
&.table-striped {
.tbody {
.tr {
&.selected {
background: #333 !important;
}
&.active {
background: $bg-color-dark;
}
}
}
}
&.table-hover {
.tbody {
.tr {
&:hover {
background: $bg-color-dark;
}
}
}
}
&.table-striped {
.tbody {
.tr:nth-of-type(odd) {
background: $bg-color;
}
}
}
}

View File

@@ -1,9 +1,12 @@
/* Colors */
$body-bg: #1d1d1d;
$body-font-color: #fff;
$bg-color: #1d1d1d;
$bg-color-light: #3f3f3f;
$body-bg: #fdfdfd;
$body-bg-dark: #1d1d1d;
$body-font-color-dark: #fff;
$bg-color-dark: #1d1d1d;
$bg-color-light-dark: #3f3f3f;
$bg-color-gray: #272727;
$bg-color-light-gray: #f1f1f1;
$light-color: #fdfdfd;
$primary-color: #e36929;
$success-color: #32b643;
$error-color: #de3b28;
@@ -14,8 +17,8 @@ $number-color: cornflowerblue;
$date-color: coral;
$bit-color: lightskyblue;
$blob-color: darkorchid;
$array-color: greenyellow;
$enum-color: gold;
$array-color: yellowgreen;
$enum-color: goldenrod;
$unknown-color: gray;
/* Sizes */

View File

@@ -6,6 +6,8 @@
@import "fake-tables";
@import "mdi-additions";
@import "db-icons";
@import "themes/dark-theme";
@import "themes/light-theme";
@import "~spectre.css/src/spectre";
@import "~spectre.css/src/spectre-exp";
@@ -19,27 +21,6 @@ body {
@include padding-variant(3, $unit-3);
@include padding-variant(4, $unit-4);
.btn {
&.btn-gray {
color: #fff;
background: $bg-color-gray;
&:hover {
background: $bg-color;
}
}
&.btn-dark {
color: #fff;
background: $bg-color-light;
border-color: $bg-color-light;
&:hover {
background: $bg-color-gray;
}
}
}
.p-vcentered {
display: flex !important;
align-items: center;
@@ -55,15 +36,6 @@ body {
box-shadow: none !important;
}
.bg-checkered {
background-image:
linear-gradient(to right, rgba(192, 192, 192, 0.75), rgba(192, 192, 192, 0.75)),
linear-gradient(to right, black 50%, white 50%),
linear-gradient(to bottom, black 50%, white 50%);
background-blend-mode: normal, difference, normal;
background-size: 2em 2em;
}
.workspace-tabs {
align-content: baseline;
@@ -80,13 +52,14 @@ body {
.btn {
display: flex;
align-self: center;
color: $body-font-color;
margin-right: 0.4rem;
}
}
.workspace-query-info {
display: flex;
overflow: hidden;
white-space: nowrap;
> div + div {
padding-left: 0.6rem;
@@ -106,18 +79,6 @@ body {
height: 10px;
}
::-webkit-scrollbar-track {
background: $bg-color-light;
}
::-webkit-scrollbar-thumb {
background: rgba($color: #fff, $alpha: 0.5);
&:hover {
background: rgba($color: #fff, $alpha: 1);
}
}
// Animations
@keyframes rotation {
from {
@@ -135,32 +96,21 @@ body {
/* Override */
.modal {
.modal-overlay,
&.active .modal-overlay {
background: rgba(255, 255, 255, 0.15);
}
.modal-container,
.modal-sm .modal-container {
box-shadow: 0 0 1px 0 #000;
padding: 0;
background: $bg-color;
.modal-header {
padding: 0.4rem 0.8rem;
text-transform: uppercase;
background: $bg-color-gray;
display: flex;
justify-content: space-between;
align-items: center;
color: #fff;
}
}
}
.tab {
border-color: #272727;
.tab-item {
.btn-clear {
margin-top: -0.1rem;
@@ -192,34 +142,11 @@ body {
}
}
.form-select,
.form-input,
.form-select:not([multiple]):not([size]),
.form-checkbox .form-icon,
.form-radio .form-icon {
border-color: $bg-color-light;
background-color: $bg-color-gray;
}
.form-input.is-error,
.form-select.is-error {
background-color: $bg-color-gray;
}
.form-input:not(:placeholder-shown):invalid:focus {
background: $bg-color-gray;
}
.form-select:not([multiple]):not([size]):focus {
border-color: $primary-color;
}
.form-input[type="file"] {
overflow: hidden;
}
.input-group .input-group-addon {
border-color: #3f3f3f;
z-index: 1;
}
@@ -243,7 +170,3 @@ body {
visibility: hidden;
}
}
.empty {
color: $body-font-color;
}

View File

@@ -0,0 +1,440 @@
.theme-dark {
color: $body-font-color-dark;
background: $body-bg-dark;
::-webkit-scrollbar-track {
background: $bg-color-light-dark;
}
::-webkit-scrollbar-thumb {
background: rgba($color: #fff, $alpha: 0.5);
&:hover {
background: rgba($color: #fff, $alpha: 1);
}
}
:disabled {
.file-uploader {
background-color: #151515;
}
}
// Override Spectre.css
.menu {
background: $bg-color-light-dark;
.menu-item a {
&:hover {
color: $primary-color;
background: $bg-color-gray;
}
}
}
.btn {
&.btn-link {
color: rgba($body-font-color-dark, 0.8);
&:hover {
color: $body-font-color-dark;
}
}
&.btn-gray {
color: #fff;
background: $bg-color-gray;
&:hover {
background: $bg-color-dark;
}
}
&.btn-dark {
color: #fff;
background: $bg-color-light-dark;
border-color: $bg-color-light-dark;
&:hover {
background: $bg-color-gray;
}
}
}
.modal {
.modal-overlay,
&.active .modal-overlay {
background: rgba(255, 255, 255, 0.15);
}
.modal-container,
.modal-sm .modal-container {
box-shadow: 0 0 1px 0 #000;
background: $bg-color-dark;
.modal-header {
background: $bg-color-gray;
color: #fff;
}
}
}
.tab {
border-color: #272727;
}
.form-select,
.form-input,
.form-select:not([multiple]):not([size]),
.form-checkbox .form-icon,
.form-radio .form-icon {
border-color: $bg-color-light-dark;
background-color: $bg-color-gray;
color: $body-font-color-dark;
}
.form-input.is-error,
.form-select.is-error {
background-color: $bg-color-gray;
}
.form-input:not(:placeholder-shown):invalid:focus {
background: $bg-color-gray;
}
.form-select:not([multiple]):not([size]):focus {
border-color: $primary-color;
}
.form-input[readonly] {
background-color: $bg-color-dark;
cursor: default;
}
.input-group .input-group-addon {
border-color: #3f3f3f;
background: $bg-color-dark;
}
.empty {
color: $body-font-color-dark;
background: transparent;
}
.form-switch .form-icon::before {
background: $bg-color-light-dark;
}
// Antares
.workspace {
.workspace-explorebar {
background: $bg-color-gray;
box-shadow: 0 0 1px 0 #000;
.workspace-explorebar-database {
.database-name {
background: $bg-color-gray;
}
.database-name,
.misc-name {
&:hover {
color: $body-font-color-dark;
background: $bg-color-light-dark;
}
}
a.table-name {
&:hover {
color: inherit;
background: inherit;
}
}
.menu-item {
&:hover {
color: $body-font-color-dark;
background: rgba($color: #fff, $alpha: 0.05);
}
}
}
}
.workspace-query-results {
.table {
.th {
background: $bg-color-dark;
border-color: $bg-color-light-dark;
}
.td {
border-color: $bg-color-light-dark;
&:focus {
box-shadow: inset 0 0 0 1px $body-font-color-dark;
background: rgba($color: #000, $alpha: 0.3);
}
}
}
}
.workspace-tabs {
.tab-block {
background: $bg-color-light-dark;
.tab-item {
> a {
color: $body-font-color-dark;
}
& &.tools-dropdown {
.tab-link:focus {
color: $primary-color;
}
.menu {
.menu-item a {
&:hover {
color: $primary-color;
background: $bg-color-gray;
}
}
}
}
}
.workspace-query-runner .workspace-query-runner-footer .workspace-query-buttons .btn {
color: $body-font-color-dark;
}
}
}
}
.bg-checkered {
background-image:
linear-gradient(to right, rgba(192, 192, 192, 0.75), rgba(192, 192, 192, 0.75)),
linear-gradient(to right, black 50%, white 50%),
linear-gradient(to bottom, black 50%, white 50%);
background-blend-mode: normal, difference, normal;
background-size: 2em 2em;
}
.context {
color: $body-font-color-dark;
.context-container {
box-shadow: 0 0 2px 0 #000;
background: #1d1d1d;
.context-element {
.context-submenu {
background: #1d1d1d;
box-shadow: 0 0 2px 0 #000;
}
&:hover {
background: $primary-color;
}
}
}
}
.editor-wrapper {
border-bottom: 1px solid #444;
}
.file-uploader {
border: 0.05rem solid $bg-color-light-dark;
background-color: $bg-color-gray;
.file-uploader-message {
border-right: 0.05rem solid $bg-color-light-dark;
background-color: $bg-color-dark;
}
}
.tile {
&:hover {
background: $bg-color-light-dark;
}
&.selected-element {
background: $bg-color-light-dark;
}
}
.editor-col {
border-left: 2px solid $bg-color-light-dark;
}
.table {
.td,
.th {
border-bottom: $border-width solid $border-color;
}
&,
&.table-striped {
.tbody {
.tr {
&.selected {
background: #333 !important;
}
&.active {
background: $bg-color-dark;
}
}
}
}
&.table-hover {
.tbody {
.tr {
&:hover {
background: #151515;
}
}
}
}
&.table-striped {
.tbody {
.tr:nth-of-type(odd) {
background: $bg-color;
}
}
}
}
#titlebar {
background: $bg-color-light-dark;
box-shadow: 0 0 1px 0 #000;
.titlebar-elements {
.titlebar-element {
&:hover {
opacity: 1;
background: rgba($color: #fff, $alpha: 0.2);
}
&.close-button:hover {
background: red;
}
}
}
}
#settingbar {
width: $settingbar-width;
height: calc(100vh - #{$excluding-size});
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
background: $bg-color-light-dark;
padding: 0;
box-shadow: 0 0 1px 0 #000;
z-index: 9;
.settingbar-top-elements {
overflow-x: hidden;
overflow-y: overlay;
max-height: calc((100vh - 3.5rem) - #{$excluding-size});
&::-webkit-scrollbar {
width: 3px;
}
}
.settingbar-bottom-elements {
padding-top: 0.5rem;
background: $bg-color-light-dark;
z-index: 1;
}
.settingbar-elements {
list-style: none;
text-align: center;
width: $settingbar-width;
padding: 0;
margin: 0;
.settingbar-element {
height: $settingbar-width;
width: 100%;
margin: 0;
border-left: 3px solid transparent;
opacity: 0.5;
transition: opacity 0.2s;
display: flex;
align-content: center;
justify-content: center;
flex-direction: column;
&:hover {
opacity: 1;
}
&.selected {
border-left-color: $body-font-color-dark;
opacity: 1;
}
.settingbar-element-icon {
&.badge::after {
bottom: -10px;
right: 0;
position: absolute;
background: $success-color;
}
&.badge-update::after {
bottom: initial;
background: $primary-color;
}
}
}
}
}
.ex-tooltip {
.ex-tooltip-content {
background: rgba(48, 55, 66, 0.95);
color: #fff;
}
&:hover .ex-tooltip-content {
visibility: visible;
opacity: 1;
}
}
#footer {
background: $primary-color;
box-shadow: 0 0 1px 0 #000;
.footer-elements {
.footer-element {
&.footer-link {
&:hover {
background: rgba($color: #fff, $alpha: 0.1);
}
}
}
}
}
}
.ace_dark.ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {
background-color: #c9561a99;
}
.ace_dark.ace_editor.ace_autocomplete .ace_marker-layer .ace_line-hover {
background-color: #c9571a33;
border: none;
}
.ace_dark.ace_editor.ace_autocomplete .ace_completion-highlight {
color: #e0d00c;
}

View File

@@ -0,0 +1,299 @@
.theme-light {
::-webkit-scrollbar-track {
background: #fff;
}
::-webkit-scrollbar-thumb {
background: rgba($color: $bg-color-light-dark, $alpha: 0.5);
&:hover {
background: rgba($color: $bg-color-light-dark, $alpha: 1);
}
}
.form-input:disabled,
.form-input.disabled,
.form-select:disabled,
.form-select.disabled {
background: #ababab;
}
#titlebar {
background: $bg-color-light;
box-shadow: 0 0 1px 0 #000;
.titlebar-elements {
.titlebar-element {
&:hover {
opacity: 1;
background: rgba($color: rgb(172, 172, 172), $alpha: 0.2);
}
&.close-button:hover {
background: red;
}
}
}
}
.btn {
&.btn-link {
color: rgba($body-font-color, 0.8);
&:hover {
color: $body-font-color;
}
}
&.btn-gray {
color: #fff;
background: $bg-color-gray;
&:hover {
background: $bg-color-dark;
}
}
&.btn-dark {
color: #fff;
background: lighten($bg-color-light-dark, 20%);
border-color: lighten($bg-color-light-dark, 20%);
&:hover {
background: $bg-color-gray;
}
}
}
.modal {
color: $body-font-color;
&:target .modal-overlay,
&.active .modal-overlay {
background: rgba($bg-color-dark, 0.75);
}
.modal-container .modal-header {
background: $bg-color-light-dark;
color: #fff;
}
}
.empty {
background: transparent;
}
.tile {
&:hover {
background: $bg-color-light-gray;
}
&.selected-element {
background: $bg-color-light-gray;
}
}
.editor-col {
border-left: 2px solid darken($bg-color-light-gray, 15%);
}
.file-uploader {
border: 0.05rem solid $border-color-dark;
background-color: $bg-color-light;
.file-uploader-message {
border-right: 0.05rem solid $border-color-dark;
background-color: $bg-color-light;
}
}
#settingbar {
width: $settingbar-width;
height: calc(100vh - #{$excluding-size});
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
background: $bg-color-light-dark;
padding: 0;
box-shadow: 0 0 1px 0 #000;
z-index: 9;
.settingbar-top-elements {
overflow-x: hidden;
overflow-y: overlay;
max-height: calc((100vh - 3.5rem) - #{$excluding-size});
&::-webkit-scrollbar {
width: 3px;
}
}
.settingbar-bottom-elements {
padding-top: 0.5rem;
background: $bg-color-light-dark;
z-index: 1;
}
.settingbar-elements {
list-style: none;
text-align: center;
width: $settingbar-width;
padding: 0;
margin: 0;
.settingbar-element {
height: $settingbar-width;
width: 100%;
margin: 0;
border-left: 3px solid transparent;
opacity: 0.5;
transition: opacity 0.2s;
display: flex;
align-content: center;
justify-content: center;
flex-direction: column;
&:hover {
opacity: 1;
}
&.selected {
border-left-color: $body-font-color-dark;
opacity: 1;
}
.settingbar-element-icon {
&.badge::after {
bottom: -10px;
right: 0;
position: absolute;
background: $success-color;
}
&.badge-update::after {
bottom: initial;
background: $primary-color;
}
}
}
}
}
.ex-tooltip {
.ex-tooltip-content {
background: rgba(48, 55, 66, 0.95);
color: #fff;
}
&:hover .ex-tooltip-content {
visibility: visible;
opacity: 1;
}
}
.workspace {
.workspace-explorebar {
background: $bg-color-light-gray;
box-shadow: 0 0 1px 0 #000;
.workspace-explorebar-database {
.database-name {
background: $bg-color-light-gray;
}
.table-size {
opacity: 0.4;
}
}
}
.workspace-query-results {
.table {
.th {
background: $body-bg;
border-color: rgba($bg-color-light-dark, 0.5);
}
.td {
border-color: rgba($bg-color-light-dark, 0.5);
}
}
}
}
.context {
color: $body-font-color-dark;
.context-container {
box-shadow: 0 0 2px 0 #000;
background: #1d1d1d;
.context-element {
.context-submenu {
background: #1d1d1d;
box-shadow: 0 0 2px 0 #000;
}
&:hover {
background: $primary-color;
}
}
}
}
.table {
.td,
.th {
border-bottom: $border-width solid $border-color;
}
&,
&.table-striped {
.tbody {
.tr {
&.selected {
background: rgba($bg-color-gray, 0.2) !important;
}
&.active {
background: $bg-color;
}
}
}
}
&.table-hover {
.tbody {
.tr {
&:hover {
background: $bg-color-light-gray;
}
}
}
}
&.table-striped {
.tbody {
.tr:nth-of-type(odd) {
background: $bg-color;
}
}
}
}
#footer {
background: $primary-color;
box-shadow: 0 0 1px 0 #000;
.footer-elements {
.footer-element {
&.footer-link {
&:hover {
background: rgba($color: #fff, $alpha: 0.1);
}
}
}
}
}
}

View File

@@ -5,6 +5,7 @@ import Vuex from 'vuex';
import application from './modules/application.store';
import settings from './modules/settings.store';
import scratchpad from './modules/scratchpad.store';
import connections from './modules/connections.store';
import workspaces from './modules/workspaces.store';
import notifications from './modules/notifications.store';
@@ -18,6 +19,7 @@ export default new Vuex.Store({
modules: {
application,
settings,
scratchpad,
connections,
workspaces,
notifications

View File

@@ -1,16 +1,21 @@
'use strict';
import Store from 'electron-store';
const persistentStore = new Store({ name: 'settings' });
export default {
namespaced: true,
strict: true,
state: {
app_name: 'Antares - SQL Client',
app_version: process.env.PACKAGE_VERSION || 0,
cached_version: persistentStore.get('cached_version', 0),
is_loading: false,
is_new_modal: false,
is_setting_modal: false,
is_scratchpad: false,
selected_setting_tab: 'general',
selected_conection: {},
update_status: 'noupdate', // noupdate, available, checking, nocheck, downloading, downloaded
update_status: 'noupdate', // noupdate, available, checking, nocheck, downloading, downloaded, disabled
download_progress: 0,
base_completer: [] // Needed to reset ace editor, due global-only ace completer
},
@@ -18,10 +23,12 @@ export default {
isLoading: state => state.is_loading,
appName: state => state.app_name,
appVersion: state => state.app_version,
cachedVersion: state => state.cached_version,
getBaseCompleter: state => state.base_completer,
getSelectedConnection: state => state.selected_conection,
isNewModal: state => state.is_new_modal,
isSettingModal: state => state.is_setting_modal,
isScratchpad: state => state.is_scratchpad,
selectedSettingTab: state => state.selected_setting_tab,
getUpdateStatus: state => state.update_status,
getDownloadProgress: state => Number(state.download_progress.toFixed(1))
@@ -46,6 +53,16 @@ export default {
HIDE_SETTING_MODAL (state) {
state.is_setting_modal = false;
},
SHOW_SCRATCHPAD (state) {
state.is_scratchpad = true;
},
HIDE_SCRATCHPAD (state) {
state.is_scratchpad = false;
},
CHANGE_CACHED_VERSION (state) {
state.cached_version = state.app_version;
persistentStore.set('cached_version', state.cached_version);
},
CHANGE_UPDATE_STATUS (state, status) {
state.update_status = status;
},
@@ -54,6 +71,12 @@ export default {
}
},
actions: {
checkVersionUpdate ({ getters, commit, dispatch }) {
if (getters.appVersion !== getters.cachedVersion) {
dispatch('showSettingModal', 'changelog');
commit('CHANGE_CACHED_VERSION');
}
},
setLoadingStatus ({ commit }, payload) {
commit('SET_LOADING_STATUS', payload);
},
@@ -72,6 +95,12 @@ export default {
},
hideSettingModal ({ commit }) {
commit('HIDE_SETTING_MODAL');
},
showScratchpad ({ commit }) {
commit('SHOW_SCRATCHPAD');
},
hideScratchpad ({ commit }) {
commit('HIDE_SCRATCHPAD');
}
}
};

View File

@@ -0,0 +1,25 @@
'use strict';
import Store from 'electron-store';
const persistentStore = new Store({ name: 'notes' });
export default {
namespaced: true,
strict: true,
state: {
notes: persistentStore.get('notes', '# HOW TO SUPPORT ANTARES\n\n- [ ] Leave a star to Antares [GitHub repo](https://github.com/Fabio286/antares)\n- [ ] Send feedbacks and advices\n- [ ] Report for bugs\n- [ ] If you enjoy, share Antares with friends\n\n# ABOUT SCRATCHPAD\n\nThis is a scratchpad where you can save your **personal notes**. It supports `markdown` format, but you are free to use plain text.\nThis content is just a placeholder, feel free to clear it to make space for your notes.\n')
},
getters: {
getNotes: state => state.notes
},
mutations: {
SET_NOTES (state, notes) {
state.notes = notes;
persistentStore.set('notes', state.notes);
}
},
actions: {
changeNotes ({ commit }, notes) {
commit('SET_NOTES', notes);
}
}
};

View File

@@ -52,8 +52,13 @@ export default {
state.explorebar_size = size;
persistentStore.set('explorebar_size', state.explorebar_size);
},
SET_APPLICATION_THEME (state, theme) {
state.application_theme = theme;
persistentStore.set('application_theme', state.application_theme);
},
SET_EDITOR_THEME (state, theme) {
state.editor_theme = theme;
persistentStore.set('editor_theme', state.editor_theme);
}
},
actions: {
@@ -75,6 +80,9 @@ export default {
changeLineWrap ({ commit }, val) {
commit('SET_LINE_WRAP', val);
},
changeApplicationTheme ({ commit }, theme) {
commit('SET_APPLICATION_THEME', theme);
},
changeEditorTheme ({ commit }, theme) {
commit('SET_EDITOR_THEME', theme);
}

View File

@@ -13,6 +13,9 @@ export default store => {
ipcRenderer.on('check-failed', () => {
store.commit('application/CHANGE_UPDATE_STATUS', 'nocheck');
});
ipcRenderer.on('no-auto-update', () => {
store.commit('application/CHANGE_UPDATE_STATUS', 'disabled');
});
ipcRenderer.on('download-progress', (event, data) => {
store.commit('application/CHANGE_UPDATE_STATUS', 'downloading');
store.commit('application/CHANGE_PROGRESS_PERCENTAGE', data.percent);