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

Compare commits

..

28 Commits

Author SHA1 Message Date
d20414b692 chore(release): 0.1.0 2021-03-21 13:01:35 +01:00
22a8c25717 fix: update or delete rows with more than one primary key 2021-03-21 13:00:27 +01:00
db47b4040a fix(PostgreSQL): issue getting foreign keys informations 2021-03-21 11:51:22 +01:00
e89911b185 fix: remove last char from datetime and time if is a dot 2021-03-20 16:29:56 +01:00
fccfe92453 fix(PostgreSQL): various issues in query results 2021-03-19 18:49:26 +01:00
d465e18dba feat(PostgreSQL): support to microseconds 2021-03-18 15:56:52 +01:00
ffb1712a59 feat(UI): support to boolean fields 2021-03-18 12:59:46 +01:00
9f6a183d9b fix(PostgreSQL): single quote escape 2021-03-18 12:30:06 +01:00
1f80a64fe1 feat(PostgreSQL): insert and edit blob fields 2021-03-18 11:09:50 +01:00
fc651149b9 feat(PostgreSQL): edit array and text search fields 2021-03-17 18:06:17 +01:00
964570247f feat(PostgreSQL): database in connection parameters 2021-03-17 16:51:26 +01:00
8a6c59f7ce fix: schema content not loaded if selected with right click 2021-03-17 11:57:47 +01:00
4d844fe2c9 refactor: rename database to schema 2021-03-17 11:15:14 +01:00
d892fa6fb3 feat(PostgreSQL): partial postgre implementation 2021-03-16 18:42:03 +01:00
8c9e4f6e96 chore: update issue templates 2021-03-16 15:55:11 +01:00
966ca60c89 chore: update README.md 2021-03-16 15:51:21 +01:00
9bbe218f90 chore: update README.md 2021-03-14 15:38:55 +01:00
a1c6be372b fix(MySQL): handle NEWDECIMAL data type 2021-03-14 15:04:20 +01:00
7d0c929fb8 chore(release): 0.0.20 2021-03-13 19:30:57 +01:00
25d72e3952 fix(UI): row mark not applied on first click 2021-03-10 15:55:34 +01:00
b232a3bb5f feat(UI): loader layers on query and data tabs 2021-03-09 19:14:02 +01:00
e9a26c1bc0 fix(UI): avoid unnecessary updates when cell content not change 2021-03-09 18:08:57 +01:00
76c5c0c680 fix(UI): table rows lose internal id after an update 2021-03-09 18:07:48 +01:00
ddfb713124 feat(UI): row markers in sql editors 2021-03-08 18:11:00 +01:00
0081a4167c refactor: moving from keytar to local storage due issues on Linux 2021-03-08 17:35:43 +01:00
239cb4488f ci: snap store config 2021-03-08 12:40:56 +01:00
fb5adbe676 chore: update package.json to support Windows portable build 2021-03-06 17:29:00 +01:00
9cd51c8d8b chore: update package.json 2021-03-06 17:11:03 +01:00
55 changed files with 2620 additions and 235 deletions

View File

@@ -3,7 +3,7 @@ name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
assignees: Fabio286
---
@@ -25,13 +25,6 @@ If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**

View File

@@ -3,7 +3,7 @@ name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
assignees: Fabio286
---

View File

@@ -2,6 +2,44 @@
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.0](https://github.com/Fabio286/antares/compare/v0.0.20...v0.1.0) (2021-03-21)
### Features
* **PostgreSQL:** database in connection parameters ([9645702](https://github.com/Fabio286/antares/commit/964570247ff5b7b8317419730eec5bed4f0f0580))
* **PostgreSQL:** edit array and text search fields ([fc65114](https://github.com/Fabio286/antares/commit/fc651149b95399c52d2d63e946731e9c1b0303a9))
* **PostgreSQL:** insert and edit blob fields ([1f80a64](https://github.com/Fabio286/antares/commit/1f80a64fe1400baacca26f1a762c5aeb4ef6350d))
* **PostgreSQL:** partial postgre implementation ([d892fa6](https://github.com/Fabio286/antares/commit/d892fa6fb3e86fbb96887d8eb67319ae855260a1))
* **PostgreSQL:** support to microseconds ([d465e18](https://github.com/Fabio286/antares/commit/d465e18dba8ea3aa00726e33f9b1f70ca4c0683c))
* **UI:** support to boolean fields ([ffb1712](https://github.com/Fabio286/antares/commit/ffb1712a593d1421793011e48a17369b884ea3c0))
### Bug Fixes
* update or delete rows with more than one primary key ([22a8c25](https://github.com/Fabio286/antares/commit/22a8c25717a4d4b285855426098a3a2846ce7448))
* **MySQL:** handle NEWDECIMAL data type ([a1c6be3](https://github.com/Fabio286/antares/commit/a1c6be372b570cf13e89ef7ecf9b7a7c033a9293))
* **PostgreSQL:** issue getting foreign keys informations ([db47b40](https://github.com/Fabio286/antares/commit/db47b4040a5282a6ac0711b1926c4c2ac867999e))
* remove last char from datetime and time if is a dot ([e89911b](https://github.com/Fabio286/antares/commit/e89911b1851c19813d4acf2c79adfbc2ac7c1112))
* **PostgreSQL:** single quote escape ([9f6a183](https://github.com/Fabio286/antares/commit/9f6a183d9b293dfe9ad9f3759f2375f05f37db8e))
* **PostgreSQL:** various issues in query results ([fccfe92](https://github.com/Fabio286/antares/commit/fccfe92453325cd54c0331cc5670af0a56822c5b))
* schema content not loaded if selected with right click ([8a6c59f](https://github.com/Fabio286/antares/commit/8a6c59f7ce7d051315b04cea38a96e4739b5b9d3))
### [0.0.20](https://github.com/Fabio286/antares/compare/v0.0.19...v0.0.20) (2021-03-13)
### Features
* **UI:** loader layers on query and data tabs ([b232a3b](https://github.com/Fabio286/antares/commit/b232a3bb5ff7e38c83aa33e8b96ec7202bc4881e))
* **UI:** row markers in sql editors ([ddfb713](https://github.com/Fabio286/antares/commit/ddfb7131248a47fa2055ccfb72e223986a17f986))
### Bug Fixes
* **UI:** avoid unnecessary updates when cell content not change ([e9a26c1](https://github.com/Fabio286/antares/commit/e9a26c1bc0641b7087d8143cc948405850d7552f))
* **UI:** row mark not applied on first click ([25d72e3](https://github.com/Fabio286/antares/commit/25d72e39529884c09cf1286ff64ba00c8a5c7b24))
* **UI:** table rows lose internal id after an update ([76c5c0c](https://github.com/Fabio286/antares/commit/76c5c0c680521b4a20de1f12bab25314ac084d5c))
### [0.0.19](https://github.com/Fabio286/antares/compare/v0.0.18...v0.0.19) (2021-03-05)

View File

@@ -1,10 +1,10 @@
<p align="center">
<img width="800" src="https://raw.githubusercontent.com/Fabio286/antares/master/docs/screen-alpha.png">
<img width="800" src="https://raw.githubusercontent.com/Fabio286/antares/master/docs/gh-logo.png">
</p>
# Antares SQL Client
![GitHub package.json version](https://img.shields.io/github/package-json/v/fabio286/antares) [![Build Status](https://travis-ci.com/Fabio286/antares.svg?branch=master)](https://travis-ci.com/Fabio286/antares) ![GitHub All Releases](https://img.shields.io/github/downloads/fabio286/antares/total) ![GitHub](https://img.shields.io/github/license/fabio286/antares)
![GitHub package.json version](https://img.shields.io/github/package-json/v/fabio286/antares) [![Build Status](https://travis-ci.com/Fabio286/antares.svg?branch=master)](https://travis-ci.com/Fabio286/antares) ![GitHub All Releases](https://img.shields.io/github/downloads/fabio286/antares/total) ![GitHub](https://img.shields.io/github/license/fabio286/antares) [![antares](https://snapcraft.io/antares/badge.svg)](https://snapcraft.io/antares) [![antares](https://snapcraft.io/antares/trending.svg?name=0)](https://snapcraft.io/antares)
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.
@@ -85,7 +85,7 @@ Depending on your distribution, you will need to run the following command:
- [x] Windows
- [x] Linux
- [x] MacOS (i need feedbacks)
- [x] MacOS (not tested due lack of hardware)
#### • ARM

BIN
docs/gh-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -1,7 +1,7 @@
{
"name": "antares",
"productName": "Antares",
"version": "0.0.19",
"version": "0.1.0",
"description": "A cross-platform easy to use SQL client.",
"license": "MIT",
"repository": "https://github.com/Fabio286/antares.git",
@@ -11,6 +11,7 @@
"build": "cross-env NODE_ENV=production npm run compile && electron-builder",
"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"
@@ -19,6 +20,12 @@
"build": {
"appId": "com.fabio286.antares",
"artifactName": "${productName}-${version}-${os}_${arch}.${ext}",
"win": {
"target": [
"nsis",
"portable"
]
},
"dmg": {
"contents": [
{
@@ -43,6 +50,9 @@
"appImage": {
"license": "./LICENSE",
"category": "Development"
},
"portable": {
"artifactName": "${productName}-${version}-portable.exe"
}
},
"electronWebpack": {
@@ -61,7 +71,9 @@
"moment": "^2.29.1",
"mssql": "^6.2.3",
"mysql2": "^2.2.5",
"node-sql-parser": "^3.1.0",
"pg": "^8.5.1",
"pgsql-ast-parser": "^7.0.2",
"source-map-support": "^0.5.16",
"spectre.css": "^0.5.9",
"v-mask": "^2.2.4",

96
snap/snapcraft.yaml Normal file
View File

@@ -0,0 +1,96 @@
name: antares
adopt-info: antares
summary: Open source SQL client made to be simple and complete.
description: |
Antares is an SQL client that aims to become an useful and complete tool, especially for developers.
The 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 and supports only MySQL and x86 architecture.
Most of its current features might be enough for MySQL management, so give it a chance and send us your feedback, we would really appreciate it.
base: core18
grade: stable
confinement: strict
architectures:
- build-on: amd64
compression: lzo
parts:
antares:
plugin: dump
source: .
override-build: |
snapcraftctl build
ARCHITECTURE=$(dpkg --print-architecture)
if [ "${ARCHITECTURE}" = "amd64" ]; then
FILTER="amd64.deb"
else
echo "ERROR! Antares only produces debs for amd64. Failing the build here."
exit 1
fi
# Get the latest releases json
echo "Get GitHub releases..."
wget --quiet https://api.github.com/repos/fabio286/antares/releases/latest -O releases.json
# Get the version from the tag_name and the download URL.
VERSION=$(jq . releases.json | grep tag_name | cut -d'"' -f4 | sed s'/release-//')
DEB_URL=$(cat releases.json | jq -r ".assets[] | select(.name | test(\"${FILTER}\")) | .browser_download_url")
DEB=$(basename "${DEB_URL}")
echo "Downloading ${DEB_URL}..."
wget --quiet "${DEB_URL}" -O "${SNAPCRAFT_PART_INSTALL}/${DEB}"
echo "Unpacking ${DEB}..."
dpkg -x "${SNAPCRAFT_PART_INSTALL}/${DEB}" ${SNAPCRAFT_PART_INSTALL}
rm -f releases.json 2>/dev/null
rm -f "${SNAPCRAFT_PART_INSTALL}/${DEB}" 2>/dev/null
echo $VERSION > $SNAPCRAFT_STAGE/version
# Correct path to icon.
sed -i 's|Icon=antares|Icon=/usr/share/icons/hicolor/256x256/apps/antares\.png|g' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/antares.desktop
# Delete usr/bin/antares, it's a broken symlink pointing outside the snap.
rm -f ${SNAPCRAFT_PART_INSTALL}/usr/bin/antares
chmod -s ${SNAPCRAFT_PART_INSTALL}/opt/Antares/chrome-sandbox
snapcraftctl set-version "$(echo $VERSION)"
build-packages:
- dpkg
- jq
- sed
- wget
stage-packages:
- fcitx-frontend-gtk3
- libappindicator3-1
- libasound2
- libgconf-2-4
- libgtk-3-0
- libnotify4
- libnspr4
- libnss3
- libpcre3
- libpulse0
- libxss1
- libsecret-1-0
- libxtst6
- libxkbfile1
cleanup:
after: [antares]
plugin: nil
build-snaps: [ gnome-3-28-1804 ]
override-prime: |
set -eux
cd /snap/gnome-3-28-1804/current
find . -type f,l -exec rm -f $SNAPCRAFT_PRIME/{} \;
apps:
antares:
command: opt/Antares/antares --no-sandbox
desktop: usr/share/applications/antares.desktop
extensions: [gnome-3-28]
environment:
# Fallback to XWayland if running in a Wayland session.
DISABLE_WAYLAND: 1
plugs:
- browser-support
- cups-control
- home
- network
- opengl
- pulseaudio
- removable-media
- unity7

View File

@@ -0,0 +1,41 @@
module.exports = {
// Defaults
defaultPort: null,
defaultUser: null,
defaultDatabase: null,
// Core
database: false,
collations: false,
engines: false,
// Tools
processesList: false,
usersManagement: false,
variables: false,
// Structure
schemas: false,
tables: false,
views: false,
triggers: false,
routines: false,
functions: false,
schedulers: false,
// Settings
tableAdd: false,
viewAdd: false,
triggerAdd: false,
routineAdd: false,
functionAdd: false,
schedulerAdd: false,
databaseEdit: false,
schemaEdit: false,
tableSettings: false,
viewSettings: false,
triggerSettings: false,
routineSettings: false,
functionSettings: false,
schedulerSettings: false,
indexes: false,
foreigns: false,
sortableFields: false,
zerofill: false
};

View File

@@ -0,0 +1,5 @@
module.exports = {
maria: require('./mysql'),
mysql: require('./mysql'),
pg: require('./postgresql')
};

View File

@@ -0,0 +1,40 @@
const defaults = require('./defaults');
module.exports = {
...defaults,
// Defaults
defaultPort: 3306,
defaultUser: 'root',
defaultDatabase: null,
// Core
collations: true,
engines: true,
// Tools
processesList: true,
// Structure
schemas: true,
tables: true,
views: true,
triggers: true,
routines: true,
functions: true,
schedulers: true,
// Settings
tableAdd: true,
viewAdd: true,
triggerAdd: true,
routineAdd: true,
functionAdd: true,
schedulerAdd: true,
schemaEdit: true,
tableSettings: true,
viewSettings: true,
triggerSettings: true,
routineSettings: true,
functionSettings: true,
schedulerSettings: true,
indexes: true,
foreigns: true,
sortableFields: true,
zerofill: true
};

View File

@@ -0,0 +1,31 @@
const defaults = require('./defaults');
module.exports = {
...defaults,
// Defaults
defaultPort: 5432,
defaultUser: 'postgres',
defaultDatabase: 'postgres',
// Core
database: true,
// Tools
processesList: true,
// Structure
tables: true,
views: false,
triggers: false,
routines: false,
functions: false,
schedulers: false,
// Settings
databaseEdit: false,
tableSettings: false,
viewSettings: false,
triggerSettings: false,
routineSettings: false,
functionSettings: false,
schedulerSettings: false,
indexes: true,
foreigns: true,
sortableFields: false
};

View File

@@ -0,0 +1,297 @@
module.exports = [
{
group: 'integer',
types: [
{
name: 'SMALLINT',
length: true,
unsigned: true
},
{
name: 'INTEGER',
length: true,
unsigned: true
},
{
name: 'BIGINT',
length: true,
unsigned: true
},
{
name: 'DECIMAL',
length: true,
unsigned: true
},
{
name: 'NUMERIC',
length: true,
unsigned: true
},
{
name: 'SMALLSERIAL',
length: true,
unsigned: true
},
{
name: 'SERIAL',
length: true,
unsigned: true
},
{
name: 'BIGSERIAL',
length: true,
unsigned: true
}
]
},
{
group: 'float',
types: [
{
name: 'REAL',
length: true,
unsigned: true
},
{
name: 'DOUBLE PRECISION',
length: true,
unsigned: true
}
]
},
{
group: 'monetary',
types: [
{
name: 'money',
length: true,
unsigned: true
}
]
},
{
group: 'string',
types: [
{
name: 'CHARACTER VARYING',
length: true,
unsigned: false
},
{
name: 'CHAR',
length: false,
unsigned: false
},
{
name: 'CHARACTER',
length: false,
unsigned: false
},
{
name: 'TEXT',
length: false,
unsigned: false
},
{
name: '"CHAR"',
length: false,
unsigned: false
},
{
name: 'NAME',
length: false,
unsigned: false
}
]
},
{
group: 'binary',
types: [
{
name: 'BYTEA',
length: true,
unsigned: false
}
]
},
{
group: 'time',
types: [
{
name: 'TIMESTAMP WITHOUT TIME ZONE',
length: false,
unsigned: false
},
{
name: 'TIMESTAMP WITH TIME ZONE',
length: false,
unsigned: false
},
{
name: 'DATE',
length: true,
unsigned: false
},
{
name: 'TIME',
length: true,
unsigned: false
},
{
name: 'TIME WITH TIME ZONE',
length: true,
unsigned: false
},
{
name: 'INTERVAL',
length: false,
unsigned: false
}
]
},
{
group: 'boolean',
types: [
{
name: 'BOOLEAN',
length: false,
unsigned: false
}
]
},
{
group: 'geometric',
types: [
{
name: 'POINT',
length: false,
unsigned: false
},
{
name: 'LINE',
length: false,
unsigned: false
},
{
name: 'LSEG',
length: false,
unsigned: false
},
{
name: 'BOX',
length: false,
unsigned: false
},
{
name: 'PATH',
length: false,
unsigned: false
},
{
name: 'POLYGON',
length: false,
unsigned: false
},
{
name: 'CIRCLE',
length: false,
unsigned: false
}
]
},
{
group: 'network',
types: [
{
name: 'CIDR',
length: false,
unsigned: false
},
{
name: 'INET',
length: false,
unsigned: false
},
{
name: 'MACADDR',
length: false,
unsigned: false
},
{
name: 'MACADDR8',
length: false,
unsigned: false
}
]
},
{
group: 'bit',
types: [
{
name: 'BIT',
length: false,
unsigned: false
},
{
name: 'BIT VARYING',
length: false,
unsigned: false
}
]
},
{
group: 'text search',
types: [
{
name: 'TSVECTOR',
length: false,
unsigned: false
},
{
name: 'TSQUERY',
length: false,
unsigned: false
}
]
},
{
group: 'uuid',
types: [
{
name: 'UUID',
length: false,
unsigned: false
}
]
},
{
group: 'xml',
types: [
{
name: 'XML',
length: false,
unsigned: false
}
]
},
{
group: 'json',
types: [
{
name: 'JSON',
length: false,
unsigned: false
},
{
name: 'JSONB',
length: false,
unsigned: false
},
{
name: 'JSONPATH',
length: false,
unsigned: false
}
]
}
];

View File

@@ -1 +0,0 @@
module.exports = [];

View File

@@ -1,13 +1,75 @@
export const TEXT = ['CHAR', 'VARCHAR'];
export const LONG_TEXT = ['TEXT', 'MEDIUMTEXT', 'LONGTEXT'];
export const TEXT = [
'CHAR',
'VARCHAR',
'CHARACTER',
'CHARACTER VARYING'
];
export const LONG_TEXT = [
'TEXT',
'MEDIUMTEXT',
'LONGTEXT'
];
export const NUMBER = ['INT', 'TINYINT', 'SMALLINT', 'MEDIUMINT', 'BIGINT', 'DECIMAL', 'BOOL'];
export const FLOAT = ['FLOAT', 'DOUBLE'];
export const ARRAY = [
'ARRAY',
'ANYARRAY'
];
export const TEXT_SEARCH = [
'TSVECTOR',
'TSQUERY'
];
export const NUMBER = [
'INT',
'TINYINT',
'SMALLINT',
'MEDIUMINT',
'BIGINT',
'DECIMAL',
'NUMERIC',
'INTEGER',
'SMALLSERIAL',
'SERIAL',
'BIGSERIAL',
'OID',
'XID'
];
export const FLOAT = [
'FLOAT',
'DOUBLE',
'REAL',
'DOUBLE PRECISION',
'MONEY'
];
export const BOOLEAN = [
'BOOL',
'BOOLEAN'
];
export const DATE = ['DATE'];
export const TIME = ['TIME'];
export const DATETIME = ['DATETIME', 'TIMESTAMP'];
export const TIME = [
'TIME',
'TIME WITH TIME ZONE'
];
export const DATETIME = [
'DATETIME',
'TIMESTAMP',
'TIMESTAMP WITHOUT TIME ZONE',
'TIMESTAMP WITH TIME ZONE'
];
export const BLOB = ['BLOB', 'TINYBLOB', 'MEDIUMBLOB', 'LONGBLOB'];
export const BLOB = [
'BLOB',
'TINYBLOB',
'MEDIUMBLOB',
'LONGBLOB',
'BYTEA'
];
export const BIT = ['BIT'];
export const BIT = [
'BIT',
'BIT VARYING'
];

View File

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

View File

@@ -96,11 +96,16 @@ else {
// create main BrowserWindow when electron is ready
app.on('ready', async () => {
let key = await keytar.getPassword('antares', 'user');
try {
let key = await keytar.getPassword('antares', 'user');
if (!key) {
key = crypto.randomBytes(16).toString('hex');
keytar.setPassword('antares', 'user', key);
if (!key) {
key = crypto.randomBytes(16).toString('hex');
keytar.setPassword('antares', 'user', key);
}
}
catch (err) {
console.log(err);
}
mainWindow = createMainWindow();

View File

@@ -7,7 +7,14 @@ export default () => {
});
ipcMain.on('get-key', async event => {
const key = await keytar.getPassword('antares', 'user');
let key = false;
try {
key = await keytar.getPassword('antares', 'user');
}
catch (err) {
console.log(err);
}
event.returnValue = key;
});
};

View File

@@ -11,6 +11,9 @@ export default connections => {
password: conn.password
};
if (conn.database)
params.database = conn.database;
if (conn.ssl) {
params.ssl = {
key: conn.key ? fs.readFileSync(conn.key) : null,
@@ -50,6 +53,9 @@ export default connections => {
password: conn.password
};
if (conn.database)
params.database = conn.database;
if (conn.ssl) {
params.ssl = {
key: conn.key ? fs.readFileSync(conn.key) : null,
@@ -59,13 +65,13 @@ export default connections => {
};
}
const connection = ClientsFactory.getConnection({
client: conn.client,
params,
poolSize: 1
});
try {
const connection = ClientsFactory.getConnection({
client: conn.client,
params,
poolSize: 1
});
await connection.connect();
const structure = await connection.getStructure(new Set());

View File

@@ -7,7 +7,7 @@ import functions from './functions';
import schedulers from './schedulers';
import updates from './updates';
import application from './application';
import database from './database';
import schema from './schema';
import users from './users';
const connections = {};
@@ -20,7 +20,7 @@ export default () => {
routines(connections);
functions(connections);
schedulers(connections);
database(connections);
schema(connections);
users(connections);
updates();
application();

View File

@@ -2,10 +2,9 @@
import { ipcMain } from 'electron';
export default connections => {
ipcMain.handle('create-database', async (event, params) => {
ipcMain.handle('create-schema', async (event, params) => {
try {
const query = `CREATE DATABASE \`${params.name}\` COLLATE ${params.collation}`;
await connections[params.uid].raw(query);
await connections[params.uid].createSchema(params);
return { status: 'success' };
}
@@ -14,10 +13,9 @@ export default connections => {
}
});
ipcMain.handle('update-database', async (event, params) => {
ipcMain.handle('update-schema', async (event, params) => {
try {
const query = `ALTER DATABASE \`${params.name}\` COLLATE ${params.collation}`;
await connections[params.uid].raw(query);
await connections[params.uid].alterSchema(params);
return { status: 'success' };
}
@@ -26,10 +24,9 @@ export default connections => {
}
});
ipcMain.handle('delete-database', async (event, params) => {
ipcMain.handle('delete-schema', async (event, params) => {
try {
const query = `DROP DATABASE \`${params.database}\``;
await connections[params.uid].raw(query);
await connections[params.uid].dropSchema(params);
return { status: 'success' };
}
@@ -38,10 +35,9 @@ export default connections => {
}
});
ipcMain.handle('get-database-collation', async (event, params) => { // TODO: move to mysql class
ipcMain.handle('get-schema-collation', async (event, params) => {
try {
const query = `SELECT \`DEFAULT_COLLATION_NAME\` FROM \`information_schema\`.\`SCHEMATA\` WHERE \`SCHEMA_NAME\`='${params.database}'`;
const collation = await connections[params.uid].raw(query);
const collation = await connections[params.uid].getDatabaseCollation(params);
return { status: 'success', response: collation.rows.length ? collation.rows[0].DEFAULT_COLLATION_NAME : '' };
}

View File

@@ -2,7 +2,7 @@ import { ipcMain } from 'electron';
import faker from 'faker';
import moment from 'moment';
import { sqlEscaper } from 'common/libs/sqlEscaper';
import { TEXT, LONG_TEXT, NUMBER, FLOAT, BLOB, BIT, DATE, DATETIME } from 'common/fieldTypes';
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BLOB, BIT, DATE, DATETIME } from 'common/fieldTypes';
import fs from 'fs';
export default (connections) => {
@@ -59,23 +59,56 @@ export default (connections) => {
});
ipcMain.handle('update-table-cell', async (event, params) => {
try {
try { // TODO: move to client classes
let escapedParam;
let reload = false;
const id = typeof params.id === 'number' ? params.id : `"${params.id}"`;
if ([...NUMBER, ...FLOAT].includes(params.type))
escapedParam = params.content;
else if ([...TEXT, ...LONG_TEXT].includes(params.type))
escapedParam = `"${sqlEscaper(params.content)}"`;
else if ([...TEXT, ...LONG_TEXT].includes(params.type)) {
switch (connections[params.uid]._client) {
case 'mysql':
case 'maria':
escapedParam = `"${sqlEscaper(params.content)}"`;
break;
case 'pg':
escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`;
break;
}
}
else if (ARRAY.includes(params.type))
escapedParam = `'${params.content}'`;
else if (TEXT_SEARCH.includes(params.type))
escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`;
else if (BLOB.includes(params.type)) {
if (params.content) {
const fileBlob = fs.readFileSync(params.content);
escapedParam = `0x${fileBlob.toString('hex')}`;
let fileBlob;
switch (connections[params.uid]._client) {
case 'mysql':
case 'maria':
fileBlob = fs.readFileSync(params.content);
escapedParam = `0x${fileBlob.toString('hex')}`;
break;
case 'pg':
fileBlob = fs.readFileSync(params.content);
escapedParam = `decode('${fileBlob.toString('hex')}', 'hex')`;
break;
}
reload = true;
}
else
escapedParam = '""';
else {
switch (connections[params.uid]._client) {
case 'mysql':
case 'maria':
escapedParam = '\'\'';
break;
case 'pg':
escapedParam = 'decode(\'\', \'hex\')';
break;
}
}
}
else if ([...BIT].includes(params.type)) {
escapedParam = `b'${sqlEscaper(params.content)}'`;
@@ -84,32 +117,33 @@ export default (connections) => {
else if (params.content === null)
escapedParam = 'NULL';
else
escapedParam = `"${sqlEscaper(params.content)}"`;
escapedParam = `'${sqlEscaper(params.content)}'`;
if (params.primary) {
if (params.primary) { // TODO: handle multiple primary
await connections[params.uid]
.update({ [params.field]: `= ${escapedParam}` })
.schema(params.schema)
.from(params.table)
.where({ [params.primary]: `= ${id}` })
.limit(1)
.run();
}
else {
const { row } = params;
const { orgRow } = params;
reload = true;
for (const key in row) {
if (typeof row[key] === 'string')
row[key] = `'${row[key]}'`;
for (const key in orgRow) {
if (typeof orgRow[key] === 'string')
orgRow[key] = `'${orgRow[key]}'`;
row[key] = `= ${row[key]}`;
orgRow[key] = `= ${orgRow[key]}`;
}
await connections[params.uid]
.schema(params.schema)
.update({ [params.field]: `= ${escapedParam}` })
.from(params.table)
.where(row)
.where(orgRow)
.limit(1)
.run();
}
@@ -167,7 +201,7 @@ export default (connections) => {
});
ipcMain.handle('insert-table-rows', async (event, params) => {
try {
try { // TODO: move to client classes
const insertObj = {};
for (const key in params.row) {
const type = params.fields[key];
@@ -176,19 +210,46 @@ export default (connections) => {
if (params.row[key] === null)
escapedParam = 'NULL';
else if ([...NUMBER, ...FLOAT].includes(type))
escapedParam = params.row[key];
else if ([...TEXT, ...LONG_TEXT].includes(type))
escapedParam = `"${sqlEscaper(params.row[key])}"`;
else if (BLOB.includes(type)) {
if (params.row[key]) {
const fileBlob = fs.readFileSync(params.row[key]);
escapedParam = `0x${fileBlob.toString('hex')}`;
escapedParam = +params.row[key];
else if ([...TEXT, ...LONG_TEXT].includes(type)) {
switch (connections[params.uid]._client) {
case 'mysql':
case 'maria':
escapedParam = `"${sqlEscaper(params.row[key].value)}"`;
break;
case 'pg':
escapedParam = `'${params.row[key].value.replaceAll('\'', '\'\'')}'`;
break;
}
}
else if (BLOB.includes(type)) {
if (params.row[key].value) {
let fileBlob;
switch (connections[params.uid]._client) {
case 'mysql':
case 'maria':
fileBlob = fs.readFileSync(params.row[key].value);
escapedParam = `0x${fileBlob.toString('hex')}`;
break;
case 'pg':
fileBlob = fs.readFileSync(params.row[key].value);
escapedParam = `decode('${fileBlob.toString('hex')}', 'hex')`;
break;
}
}
else {
switch (connections[params.uid]._client) {
case 'mysql':
case 'maria':
escapedParam = '""';
break;
case 'pg':
escapedParam = 'decode(\'\', \'hex\')';
break;
}
}
else
escapedParam = '""';
}
else
escapedParam = `"${sqlEscaper(params.row[key])}"`;
insertObj[key] = escapedParam;
}
@@ -209,7 +270,7 @@ export default (connections) => {
});
ipcMain.handle('insert-table-fake-rows', async (event, params) => {
try {
try { // TODO: move to client classes
const rows = [];
for (let i = 0; i < +params.repeat; i++) {
@@ -224,20 +285,49 @@ export default (connections) => {
escapedParam = 'NULL';
else if ([...NUMBER, ...FLOAT].includes(type))
escapedParam = params.row[key].value;
else if ([...TEXT, ...LONG_TEXT].includes(type))
escapedParam = `"${sqlEscaper(params.row[key].value)}"`;
else if ([...TEXT, ...LONG_TEXT].includes(type)) {
switch (connections[params.uid]._client) {
case 'mysql':
case 'maria':
escapedParam = `"${sqlEscaper(params.row[key].value)}"`;
break;
case 'pg':
escapedParam = `'${params.row[key].value.replaceAll('\'', '\'\'')}'`;
break;
}
}
else if (BLOB.includes(type)) {
if (params.row[key].value) {
const fileBlob = fs.readFileSync(params.row[key].value);
escapedParam = `0x${fileBlob.toString('hex')}`;
let fileBlob;
switch (connections[params.uid]._client) {
case 'mysql':
case 'maria':
fileBlob = fs.readFileSync(params.row[key].value);
escapedParam = `0x${fileBlob.toString('hex')}`;
break;
case 'pg':
fileBlob = fs.readFileSync(params.row[key].value);
escapedParam = `decode('${fileBlob.toString('hex')}', 'hex')`;
break;
}
}
else {
switch (connections[params.uid]._client) {
case 'mysql':
case 'maria':
escapedParam = '""';
break;
case 'pg':
escapedParam = 'decode(\'\', \'hex\')';
break;
}
}
else
escapedParam = '""';
}
else if (BIT.includes(type))
escapedParam = `b'${sqlEscaper(params.row[key].value)}'`;
else
escapedParam = `"${sqlEscaper(params.row[key].value)}"`;
escapedParam = `'${sqlEscaper(params.row[key].value)}'`;
insertObj[key] = escapedParam;
}
@@ -261,10 +351,10 @@ export default (connections) => {
if (typeof fakeValue === 'string') {
if (params.row[key].length)
fakeValue = fakeValue.substr(0, params.row[key].length);
fakeValue = `"${sqlEscaper(fakeValue)}"`;
fakeValue = `'${sqlEscaper(fakeValue)}'`;
}
else if ([...DATE, ...DATETIME].includes(type))
fakeValue = `"${moment(fakeValue).format('YYYY-MM-DD HH:mm:ss.SSSSSS')}"`;
fakeValue = `'${moment(fakeValue).format('YYYY-MM-DD HH:mm:ss.SSSSSS')}'`;
insertObj[key] = fakeValue;
}
@@ -289,13 +379,13 @@ export default (connections) => {
ipcMain.handle('get-foreign-list', async (event, { uid, schema, table, column, description }) => {
try {
const query = connections[uid]
.select(`${column} AS foreignColumn`)
.select(`${column} AS foreign_column`)
.schema(schema)
.from(table)
.orderBy('foreignColumn ASC');
.orderBy('foreign_column ASC');
if (description)
query.select(`LEFT(${description}, 20) AS foreignDescription`);
query.select(`LEFT(${description}, 20) AS foreign_description`);
const results = await query.run();

View File

@@ -1,5 +1,6 @@
'use strict';
import { MySQLClient } from './clients/MySQLClient';
import { PostgreSQLClient } from './clients/PostgreSQLClient';
export class ClientsFactory {
/**
@@ -20,8 +21,10 @@ export class ClientsFactory {
case 'mysql':
case 'maria':
return new MySQLClient(args);
case 'pg':
return new PostgreSQLClient(args);
default:
return new Error(`Unknown database client: ${args.client}`);
throw new Error(`Unknown database client: ${args.client}`);
}
}
}

View File

@@ -404,6 +404,44 @@ export class MySQLClient extends AntaresCore {
});
}
/**
* CREATE DATABASE
*
* @returns {Array.<Object>} parameters
* @memberof MySQLClient
*/
async createSchema (params) {
return await this.raw(`CREATE DATABASE \`${params.name}\` COLLATE ${params.collation}`);
}
/**
* ALTER DATABASE
*
* @returns {Array.<Object>} parameters
* @memberof MySQLClient
*/
async alterSchema (params) {
return await this.raw(`ALTER DATABASE \`${params.name}\` COLLATE ${params.collation}`);
}
/**
* DROP DATABASE
*
* @returns {Array.<Object>} parameters
* @memberof MySQLClient
*/
async dropSchema (params) {
return await this.raw(`DROP DATABASE \`${params.database}\``);
}
/**
* @returns {Array.<Object>} parameters
* @memberof MySQLClient
*/
async getDatabaseCollation (params) {
return await this.raw(`SELECT \`DEFAULT_COLLATION_NAME\` FROM \`information_schema\`.\`SCHEMATA\` WHERE \`SCHEMA_NAME\`='${params.database}'`);
}
/**
* SHOW CREATE VIEW
*
@@ -1281,7 +1319,7 @@ export class MySQLClient extends AntaresCore {
const response = await this.getTableColumns(paramObj);
remappedFields = remappedFields.map(field => {
const detailedField = response.find(f => f.name === field.name);
if (detailedField && field.orgTable === paramObj.table && field.schema === paramObj.schema && detailedField.name === field.orgName)
if (detailedField && field.orgTable === paramObj.table && field.schema === paramObj.schema)
field = { ...detailedField, ...field };
return field;
});

File diff suppressed because it is too large Load Diff

View File

@@ -11,11 +11,11 @@
</option>
<option
v-for="row in foreignList"
:key="row.foreignColumn"
:value="row.foreignColumn"
:selected="row.foreignColumn === value"
:key="row.foreign_column"
:value="row.foreign_column"
:selected="row.foreign_column === value"
>
{{ row.foreignColumn }} {{ 'foreignDescription' in row ? ` - ${row.foreignDescription}` : '' | cutText }}
{{ row.foreign_column }} {{ 'foreign_description' in row ? ` - ${row.foreign_description}` : '' | cutText }}
</option>
</select>
</template>
@@ -51,11 +51,11 @@ export default {
}),
isValidDefault () {
if (!this.foreignList.length) return true;
return this.foreignList.some(foreign => foreign.foreignColumn.toString() === this.value.toString());
return this.foreignList.some(foreign => foreign.foreign_column.toString() === this.value.toString());
}
},
async created () {
let firstTextField;
let foreignDesc;
const params = {
uid: this.selectedWorkspace,
schema: this.keyUsage.refSchema,
@@ -64,8 +64,10 @@ export default {
try { // Field data
const { status, response } = await Tables.getTableColumns(params);
if (status === 'success')
firstTextField = response.find(field => [...TEXT, ...LONG_TEXT].includes(field.type)).name || false;
if (status === 'success') {
const textField = response.find(field => [...TEXT, ...LONG_TEXT].includes(field.type));
foreignDesc = textField ? textField.name : false;
}
else
this.addNotification({ status: 'error', message: response });
}
@@ -77,7 +79,7 @@ export default {
const { status, response } = await Tables.getForeignList({
...params,
column: this.keyUsage.refField,
description: firstTextField
description: foreignDesc
});
if (status === 'success')

View File

@@ -30,7 +30,7 @@
class="form-input"
type="text"
>
<span class="input-group-addon field-type" :class="`type-${parameter.type.toLowerCase()}`">
<span class="input-group-addon field-type" :class="typeClass(parameter.type)">
{{ parameter.type }} {{ parameter.length | wrapNumber }}
</span>
</div>
@@ -75,6 +75,11 @@ export default {
window.removeEventListener('keydown', this.onKey);
},
methods: {
typeClass (type) {
if (type)
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
return '';
},
runRoutine () {
const valArr = Object.keys(this.values).reduce((acc, curr) => {
const value = isNaN(this.values[curr]) ? `"${this.values[curr]}"` : this.values[curr];

View File

@@ -59,15 +59,15 @@
<option value="maria">
MariaDB
</option>
<option value="pg">
PostgreSQL
</option>
<!-- <option value="mssql">
Microsoft SQL
</option>
<option value="pg">
PostgreSQL
</option>
<option value="oracledb">
Oracle DB
</option> -->
Microsoft SQL
</option>
<option value="oracledb">
Oracle DB
</option> -->
</select>
</div>
</div>
@@ -97,6 +97,18 @@
>
</div>
</div>
<div v-if="customizations.database" class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.database') }}</label>
</div>
<div class="col-8 col-sm-12">
<input
v-model="localConnection.database"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.user') }}</label>
@@ -247,6 +259,7 @@
<script>
import { mapActions } from 'vuex';
import customizations from 'common/customizations';
import Connection from '@/ipc-api/Connection';
import ModalAskCredentials from '@/components/ModalAskCredentials';
import BaseToast from '@/components/BaseToast';
@@ -274,6 +287,11 @@ export default {
selectedTab: 'general'
};
},
computed: {
customizations () {
return customizations[this.connection.client];
}
},
created () {
this.localConnection = Object.assign({}, this.connection);
window.addEventListener('keydown', this.onKey);

View File

@@ -5,7 +5,7 @@
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-database-edit mr-1" /> {{ $t('message.editDatabase') }}
<i class="mdi mdi-24px mdi-database-edit mr-1" /> {{ $t('message.editSchema') }}
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -23,7 +23,7 @@
class="form-input"
type="text"
required
:placeholder="$t('message.databaseName')"
:placeholder="$t('message.schemaName')"
readonly
>
</div>
@@ -53,7 +53,7 @@
</div>
</div>
<div class="modal-footer text-light">
<button class="btn btn-primary mr-2" @click.stop="updateDatabase">
<button class="btn btn-primary mr-2" @click.stop="updateSchema">
{{ $t('word.update') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
@@ -66,10 +66,10 @@
<script>
import { mapGetters, mapActions } from 'vuex';
import Database from '@/ipc-api/Database';
import Schema from '@/ipc-api/Schema';
export default {
name: 'ModalEditDatabase',
name: 'ModalEditSchema',
props: {
selectedDatabase: String
},
@@ -98,7 +98,7 @@ export default {
async created () {
let actualCollation;
try {
const { status, response } = await Database.getDatabaseCollation({ uid: this.selectedWorkspace, database: this.selectedDatabase });
const { status, response } = await Schema.getDatabaseCollation({ uid: this.selectedWorkspace, database: this.selectedDatabase });
if (status === 'success')
actualCollation = response;
@@ -130,10 +130,10 @@ export default {
...mapActions({
addNotification: 'notifications/addNotification'
}),
async updateDatabase () {
async updateSchema () {
if (this.database.collation !== this.database.prevCollation) {
try {
const { status, response } = await Database.updateDatabase({
const { status, response } = await Schema.updateSchema({
uid: this.selectedWorkspace,
...this.database
});

View File

@@ -34,7 +34,7 @@
:field-obj="localRow[field.name]"
:value.sync="localRow[field.name]"
>
<span class="input-group-addon field-type" :class="`type-${field.type.toLowerCase()}`">
<span class="input-group-addon field-type" :class="typeClass(field.type)">
{{ field.type }} {{ fieldLength(field) | wrapNumber }}
</span>
<label class="form-checkbox ml-3" :title="$t('word.insert')">
@@ -286,6 +286,11 @@ export default {
...mapActions({
addNotification: 'notifications/addNotification'
}),
typeClass (type) {
if (type)
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
return '';
},
async insertRows () {
this.isInserting = true;
const rowToInsert = this.localRow;

View File

@@ -63,12 +63,12 @@
<option value="maria">
MariaDB
</option>
<option value="pg">
PostgreSQL
</option>
<!-- <option value="mssql">
Microsoft SQL
</option>
<option value="pg">
PostgreSQL
</option>
<option value="oracledb">
Oracle DB
</option> -->
@@ -101,6 +101,18 @@
>
</div>
</div>
<div v-if="customizations.database" class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.database') }}</label>
</div>
<div class="col-8 col-sm-12">
<input
v-model="connection.database"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.user') }}</label>
@@ -251,6 +263,7 @@
<script>
import { mapActions } from 'vuex';
import customizations from 'common/customizations';
import Connection from '@/ipc-api/Connection';
import { uidGen } from 'common/libs/uidGen';
import ModalAskCredentials from '@/components/ModalAskCredentials';
@@ -270,8 +283,9 @@ export default {
name: '',
client: 'mysql',
host: '127.0.0.1',
port: '3306',
user: 'root',
database: null,
port: null,
user: null,
password: '',
ask: false,
uid: uidGen('C'),
@@ -291,7 +305,13 @@ export default {
selectedTab: 'general'
};
},
computed: {
customizations () {
return customizations[this.connection.client];
}
},
created () {
this.setDefaults();
window.addEventListener('keydown', this.onKey);
setTimeout(() => {
@@ -307,20 +327,9 @@ export default {
addConnection: 'connections/addConnection'
}),
setDefaults () {
switch (this.connection.client) {
case 'mysql':
this.connection.port = '3306';
break;
case 'mssql':
this.connection.port = '1433';
break;
case 'pg':
this.connection.port = '5432';
break;
case 'oracledb':
this.connection.port = '1521';
break;
}
this.connection.user = this.customizations.defaultUser;
this.connection.port = this.customizations.defaultPort;
this.connection.database = this.customizations.defaultDatabase;
},
async startTest () {
this.isTesting = true;

View File

@@ -5,7 +5,7 @@
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-database-plus mr-1" /> {{ $t('message.createNewDatabase') }}
<i class="mdi mdi-24px mdi-database-plus mr-1" /> {{ $t('message.createNewSchema') }}
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
@@ -24,11 +24,11 @@
class="form-input"
type="text"
required
:placeholder="$t('message.databaseName')"
:placeholder="$t('message.schemaName')"
>
</div>
</div>
<div class="form-group">
<div v-if="customizations.collations" class="form-group">
<div class="col-3">
<label class="form-label">{{ $t('word.collation') }}</label>
</div>
@@ -49,7 +49,11 @@
</div>
</div>
<div class="modal-footer text-light">
<button class="btn btn-primary mr-2" @click.stop="createDatabase">
<button
class="btn btn-primary mr-2"
:class="{'loading': isLoading}"
@click.stop="createSchema"
>
{{ $t('word.add') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
@@ -62,12 +66,13 @@
<script>
import { mapGetters, mapActions } from 'vuex';
import Database from '@/ipc-api/Database';
import Schema from '@/ipc-api/Schema';
export default {
name: 'ModalNewDatabase',
name: 'ModalNewSchema',
data () {
return {
isLoading: false,
database: {
name: '',
collation: ''
@@ -83,8 +88,11 @@ export default {
collations () {
return this.getWorkspace(this.selectedWorkspace).collations;
},
customizations () {
return this.getWorkspace(this.selectedWorkspace).customizations;
},
defaultCollation () {
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value || '';
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server') ? this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value : '';
}
},
created () {
@@ -101,9 +109,10 @@ export default {
...mapActions({
addNotification: 'notifications/addNotification'
}),
async createDatabase () {
async createSchema () {
this.isLoading = true;
try {
const { status, response } = await Database.createDatabase({
const { status, response } = await Schema.createSchema({
uid: this.selectedWorkspace,
...this.database
});
@@ -118,6 +127,7 @@ export default {
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isLoading = false;
},
closeModal () {
this.$emit('close');

View File

@@ -69,7 +69,7 @@
:disabled="fieldsToExclude.includes(field.name)"
:tabindex="key+1"
>
<span class="input-group-addon" :class="`type-${field.type.toLowerCase()}`">
<span class="input-group-addon" :class="typeCLass(field.type)">
{{ field.type }} {{ fieldLength(field) | wrapNumber }}
</span>
<label class="form-checkbox ml-3" :title="$t('word.insert')">
@@ -222,6 +222,11 @@ export default {
...mapActions({
addNotification: 'notifications/addNotification'
}),
typeClass (type) {
if (type)
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
return '';
},
async insertRows () {
this.isInserting = true;
const rowToInsert = this.localRow;

View File

@@ -101,7 +101,7 @@
<script>
import { mapGetters, mapActions } from 'vuex';
import Database from '@/ipc-api/Database';
import Schema from '@/ipc-api/Schema';
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
import ProcessesListRow from '@/components/ProcessesListRow';
@@ -181,7 +181,7 @@ export default {
this.results = [];
try { // Table data
const { status, response } = await Database.getProcesses(this.connection.uid);
const { status, response } = await Schema.getProcesses(this.connection.uid);
if (status === 'success') {
this.results = response;

View File

@@ -241,6 +241,22 @@ export default {
this.$emit('update:value', content);
});
this.editor.on('guttermousedown', e => {
const target = e.domEvent.target;
if (target.className.indexOf('ace_gutter-cell') === -1)
return;
if (e.clientX > 25 + target.getBoundingClientRect().left)
return;
const row = e.getDocumentPosition().row;
const breakpoints = e.editor.session.getBreakpoints(row, 0);
if (typeof breakpoints[row] === typeof undefined)
e.editor.session.setBreakpoint(row);
else
e.editor.session.clearBreakpoint(row);
e.stop();
});
if (this.autoFocus) {
setTimeout(() => {
this.editor.focus();
@@ -308,4 +324,21 @@ export default {
.ace_dark.ace_editor.ace_autocomplete .ace_completion-highlight {
color: #e0d00c;
}
.ace_gutter-cell.ace_breakpoint {
&::before {
content: '\F0403';
position: absolute;
left: 3px;
top: 2px;
color: $primary-color;
display: inline-block;
font: normal normal normal 24px/1 "Material Design Icons", sans-serif;
font-size: inherit;
text-rendering: auto;
line-height: inherit;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
}
</style>

View File

@@ -16,19 +16,27 @@
<i class="mdi mdi-24px mdi-tools" />
</a>
<ul class="menu text-left text-uppercase">
<li class="menu-item">
<li v-if="workspace.customizations.processesList" class="menu-item">
<a class="c-hand p-vcentered" @click="showProcessesModal">
<i class="mdi mdi-memory mr-1 tool-icon" />
<span>{{ $t('message.processesList') }}</span>
</a>
</li>
<li class="menu-item" title="Coming...">
<li
v-if="workspace.customizations.variables"
class="menu-item"
title="Coming..."
>
<a class="c-hand p-vcentered disabled">
<i class="mdi mdi-shape mr-1 tool-icon" />
<span>{{ $t('word.variables') }}</span>
</a>
</li>
<li class="menu-item" title="Coming...">
<li
v-if="workspace.customizations.usersManagement"
class="menu-item"
title="Coming..."
>
<a class="c-hand p-vcentered disabled">
<i class="mdi mdi-account-group mr-1 tool-icon" />
<span>{{ $t('message.manageUsers') }}</span>
@@ -37,7 +45,7 @@
</ul>
</li>
<li
v-if="schemaChild"
v-if="schemaChild && isSettingSupported"
class="tab-item"
:class="{'active': selectedTab === 'prop'}"
@click="selectTab({uid: workspace.uid, tab: 'prop'})"
@@ -194,6 +202,15 @@ export default {
isSelected () {
return this.selectedWorkspace === this.connection.uid;
},
isSettingSupported () {
if (this.workspace.breadcrumbs.table && this.workspace.customizations.tableSettings) return true;
if (this.workspace.breadcrumbs.view && this.workspace.customizations.viewSettings) return true;
if (this.workspace.breadcrumbs.trigger && this.workspace.customizations.triggerSettings) return true;
if (this.workspace.breadcrumbs.procedure && this.workspace.customizations.routineSettings) return true;
if (this.workspace.breadcrumbs.function && this.workspace.customizations.functionSettings) return true;
if (this.workspace.breadcrumbs.scheduler && this.workspace.customizations.schedulerSettings) return true;
return false;
},
selectedTab () {
if (
(

View File

@@ -11,7 +11,7 @@
<span v-if="workspace.connected" class="workspace-explorebar-tools">
<i
class="mdi mdi-18px mdi-database-plus c-hand mr-2"
:title="$t('message.createNewDatabase')"
:title="$t('message.createNewSchema')"
@click="showNewDBModal"
/>
<i
@@ -44,18 +44,18 @@
:connection="connection"
/>
<div v-else class="workspace-explorebar-body">
<WorkspaceExploreBarDatabase
<WorkspaceExploreBarSchema
v-for="db of workspace.structure"
:key="db.name"
:database="db"
:connection="connection"
@show-database-context="openDatabaseContext"
@show-schema-context="openSchemaContext"
@show-table-context="openTableContext"
@show-misc-context="openMiscContext"
/>
</div>
</div>
<ModalNewDatabase
<ModalNewSchema
v-if="isNewDBModal"
@close="hideNewDBModal"
@reload="refresh"
@@ -137,11 +137,11 @@ import Functions from '@/ipc-api/Functions';
import Schedulers from '@/ipc-api/Schedulers';
import WorkspaceConnectPanel from '@/components/WorkspaceConnectPanel';
import WorkspaceExploreBarDatabase from '@/components/WorkspaceExploreBarDatabase';
import DatabaseContext from '@/components/WorkspaceExploreBarDatabaseContext';
import WorkspaceExploreBarSchema from '@/components/WorkspaceExploreBarSchema';
import DatabaseContext from '@/components/WorkspaceExploreBarSchemaContext';
import TableContext from '@/components/WorkspaceExploreBarTableContext';
import MiscContext from '@/components/WorkspaceExploreBarMiscContext';
import ModalNewDatabase from '@/components/ModalNewDatabase';
import ModalNewSchema from '@/components/ModalNewSchema';
import ModalNewTable from '@/components/ModalNewTable';
import ModalNewView from '@/components/ModalNewView';
import ModalNewTrigger from '@/components/ModalNewTrigger';
@@ -153,11 +153,11 @@ export default {
name: 'WorkspaceExploreBar',
components: {
WorkspaceConnectPanel,
WorkspaceExploreBarDatabase,
WorkspaceExploreBarSchema,
DatabaseContext,
TableContext,
MiscContext,
ModalNewDatabase,
ModalNewSchema,
ModalNewTable,
ModalNewView,
ModalNewTrigger,
@@ -299,8 +299,8 @@ export default {
else
this.addNotification({ status: 'error', message: response });
},
openDatabaseContext (payload) {
this.selectedDatabase = payload.database;
openSchemaContext (payload) {
this.selectedDatabase = payload.schema;
this.databaseContextEvent = payload.event;
this.isDatabaseContext = true;
},

View File

@@ -4,7 +4,7 @@
class="accordion-header database-name"
:class="{'text-bold': breadcrumbs.schema === database.name}"
@click="selectSchema(database.name)"
@contextmenu.prevent="showDatabaseContext($event, database.name)"
@contextmenu.prevent="showSchemaContext($event, database.name)"
>
<div v-if="isLoading" class="icon loading" />
<i v-else class="icon mdi mdi-18px mdi-chevron-right" />
@@ -37,7 +37,7 @@
</ul>
</div>
<div v-if="filteredTriggers.length" class="database-misc">
<div v-if="filteredTriggers.length && customizations.triggers" class="database-misc">
<details class="accordion">
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.trigger}">
<i class="misc-icon mdi mdi-18px mdi-folder-cog mr-1" />
@@ -65,7 +65,7 @@
</details>
</div>
<div v-if="filteredProcedures.length" class="database-misc">
<div v-if="filteredProcedures.length && customizations.routines" class="database-misc">
<details class="accordion">
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.procedure}">
<i class="misc-icon mdi mdi-18px mdi-folder-sync mr-1" />
@@ -93,7 +93,7 @@
</details>
</div>
<div v-if="filteredFunctions.length" class="database-misc">
<div v-if="filteredFunctions.length && customizations.functions" class="database-misc">
<details class="accordion">
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.function}">
<i class="misc-icon mdi mdi-18px mdi-folder-move mr-1" />
@@ -121,7 +121,7 @@
</details>
</div>
<div v-if="filteredSchedulers.length" class="database-misc">
<div v-if="filteredSchedulers.length && customizations.schedulers" class="database-misc">
<details class="accordion">
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.scheduler}">
<i class="misc-icon mdi mdi-18px mdi-folder-clock mr-1" />
@@ -157,7 +157,7 @@ import { mapActions, mapGetters } from 'vuex';
import { formatBytes } from 'common/libs/formatBytes';
export default {
name: 'WorkspaceExploreBarDatabase',
name: 'WorkspaceExploreBarSchema',
props: {
database: Object,
connection: Object
@@ -194,6 +194,9 @@ export default {
breadcrumbs () {
return this.getWorkspace(this.connection.uid).breadcrumbs;
},
customizations () {
return this.getWorkspace(this.connection.uid).customizations;
},
loadedSchemas () {
return this.getLoadedSchemas(this.connection.uid);
},
@@ -222,9 +225,9 @@ export default {
this.changeBreadcrumbs({ schema, table: null });
},
showDatabaseContext (event, database) {
this.changeBreadcrumbs({ schema: database, table: null });
this.$emit('show-database-context', { event, database });
showSchemaContext (event, schema) {
this.selectSchema(schema);
this.$emit('show-schema-context', { event, schema });
},
showTableContext (event, table) {
this.setBreadcrumbs({ schema: this.database.name, [table.type]: table.name });

View File

@@ -7,27 +7,55 @@
<span class="d-flex"><i class="mdi mdi-18px mdi-plus text-light pr-1" /> {{ $t('word.add') }}</span>
<i class="mdi mdi-18px mdi-chevron-right text-light pl-1" />
<div class="context-submenu">
<div class="context-element" @click="showCreateTableModal">
<div
v-if="workspace.customizations.tableAdd"
class="context-element"
@click="showCreateTableModal"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-table text-light pr-1" /> {{ $t('word.table') }}</span>
</div>
<div class="context-element" @click="showCreateViewModal">
<div
v-if="workspace.customizations.viewAdd"
class="context-element"
@click="showCreateViewModal"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-table-eye text-light pr-1" /> {{ $t('word.view') }}</span>
</div>
<div class="context-element" @click="showCreateTriggerModal">
<div
v-if="workspace.customizations.triggerAdd"
class="context-element"
@click="showCreateTriggerModal"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-table-cog text-light pr-1" /> {{ $tc('word.trigger', 1) }}</span>
</div>
<div class="context-element" @click="showCreateRoutineModal">
<div
v-if="workspace.customizations.routineAdd"
class="context-element"
@click="showCreateRoutineModal"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-sync-circle pr-1" /> {{ $tc('word.storedRoutine', 1) }}</span>
</div>
<div class="context-element" @click="showCreateFunctionModal">
<div
v-if="workspace.customizations.functionAdd"
class="context-element"
@click="showCreateFunctionModal"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-arrow-right-bold-box pr-1" /> {{ $tc('word.function', 1) }}</span>
</div>
<div class="context-element" @click="showCreateSchedulerModal">
<div
v-if="workspace.customizations.schedulerAdd"
class="context-element"
@click="showCreateSchedulerModal"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-calendar-clock text-light pr-1" /> {{ $tc('word.scheduler', 1) }}</span>
</div>
</div>
</div>
<div class="context-element" @click="showEditModal">
<div
v-if="workspace.customizations.schemaEdit"
class="context-element"
@click="showEditModal"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-database-edit text-light pr-1" /> {{ $t('word.edit') }}</span>
</div>
<div class="context-element" @click="showDeleteModal">
@@ -36,12 +64,12 @@
<ConfirmModal
v-if="isDeleteModal"
@confirm="deleteDatabase"
@confirm="deleteSchema"
@hide="hideDeleteModal"
>
<template slot="header">
<div class="d-flex">
<i class="mdi mdi-24px mdi-database-remove mr-1" /> {{ $t('message.deleteDatabase') }}
<i class="mdi mdi-24px mdi-database-remove mr-1" /> {{ $t('message.deleteSchema') }}
</div>
</template>
<div slot="body">
@@ -50,7 +78,7 @@
</div>
</div>
</ConfirmModal>
<ModalEditDatabase
<ModalEditSchema
v-if="isEditModal"
:selected-database="selectedDatabase"
@close="hideEditModal"
@@ -62,15 +90,15 @@
import { mapGetters, mapActions } from 'vuex';
import BaseContextMenu from '@/components/BaseContextMenu';
import ConfirmModal from '@/components/BaseConfirmModal';
import ModalEditDatabase from '@/components/ModalEditDatabase';
import Database from '@/ipc-api/Database';
import ModalEditSchema from '@/components/ModalEditSchema';
import Schema from '@/ipc-api/Schema';
export default {
name: 'WorkspaceExploreBarDatabaseContext',
name: 'WorkspaceExploreBarSchemaContext',
components: {
BaseContextMenu,
ConfirmModal,
ModalEditDatabase
ModalEditSchema
},
props: {
contextEvent: MouseEvent,
@@ -130,9 +158,9 @@ export default {
closeContext () {
this.$emit('close-context');
},
async deleteDatabase () {
async deleteSchema () {
try {
const { status, response } = await Database.deleteDatabase({
const { status, response } = await Schema.deleteSchema({
uid: this.selectedWorkspace,
database: this.selectedDatabase
});

View File

@@ -41,7 +41,7 @@
>
<div class="tile-icon">
<div>
<i class="mdi mdi-hexagon mdi-24px" :class="`type-${param.type.toLowerCase()}`" />
<i class="mdi mdi-hexagon mdi-24px" :class="typeClass(param.type)" />
</div>
</div>
<div class="tile-content">
@@ -183,6 +183,11 @@ export default {
window.removeEventListener('resize', this.getModalInnerHeight);
},
methods: {
typeClass (type) {
if (type)
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
return '';
},
confirmParametersChange () {
this.$emit('parameters-update', this.parametersProxy);
},

View File

@@ -41,7 +41,7 @@
>
<div class="tile-icon">
<div>
<i class="mdi mdi-hexagon mdi-24px" :class="`type-${param.type.toLowerCase()}`" />
<i class="mdi mdi-hexagon mdi-24px" :class="typeClass(param.type)" />
</div>
</div>
<div class="tile-content">
@@ -214,6 +214,11 @@ export default {
window.removeEventListener('resize', this.getModalInnerHeight);
},
methods: {
typeClass (type) {
if (type)
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
return '';
},
confirmParametersChange () {
this.$emit('parameters-update', this.parametersProxy);
},

View File

@@ -47,7 +47,8 @@
<BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ $t('message.routineBody') }}</label>
<QueryEditor
v-if="isSelected"
v-show="isSelected"
:key="`${routine}-${_uid}`"
ref="queryEditor"
:value.sync="localRoutine.sql"
:workspace="workspace"

View File

@@ -118,7 +118,7 @@
<BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ $t('message.schedulerBody') }}</label>
<QueryEditor
v-if="isSelected"
v-show="isSelected"
ref="queryEditor"
:value.sync="localScheduler.sql"
:workspace="workspace"

View File

@@ -99,7 +99,7 @@
<BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ $t('message.triggerStatement') }}</label>
<QueryEditor
v-if="isSelected"
v-show="isSelected"
ref="queryEditor"
:value.sync="localTrigger.sql"
:workspace="workspace"

View File

@@ -48,7 +48,7 @@
<span
v-if="!isInlineEditor.type"
class="cell-content text-left"
:class="`type-${lowerCase(localRow.type)}`"
:class="typeClass(localRow.type)"
@click="editON($event, localRow.type.toUpperCase(), 'type')"
>
{{ localRow.type }}
@@ -378,10 +378,10 @@ export default {
return 'UNKNOWN ' + key;
}
},
lowerCase (val) {
if (val)
return val.toLowerCase();
return val;
typeClass (type) {
if (type)
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
return '';
},
initLocalRow () {
Object.keys(this.localRow).forEach(key => {

View File

@@ -49,7 +49,8 @@
</div>
</div>
</div>
<div class="workspace-query-results column col-12">
<div class="workspace-query-results p-relative column col-12">
<BaseLoader v-if="isQuering" />
<WorkspaceQueryTable
v-if="results"
v-show="!isQuering"
@@ -67,8 +68,9 @@
</template>
<script>
import Database from '@/ipc-api/Database';
import Schema from '@/ipc-api/Schema';
import QueryEditor from '@/components/QueryEditor';
import BaseLoader from '@/components/BaseLoader';
import WorkspaceQueryTable from '@/components/WorkspaceQueryTable';
import { mapGetters, mapActions } from 'vuex';
import tableTabs from '@/mixins/tableTabs';
@@ -76,6 +78,7 @@ import tableTabs from '@/mixins/tableTabs';
export default {
name: 'WorkspaceQueryTab',
components: {
BaseLoader,
QueryEditor,
WorkspaceQueryTable
},
@@ -147,7 +150,7 @@ export default {
query
};
const { status, response } = await Database.rawQuery(params);
const { status, response } = await Schema.rawQuery(params);
if (status === 'success') {
this.results = Array.isArray(response) ? response : [response];
@@ -243,6 +246,10 @@ export default {
}
}
}
.workspace-query-results {
min-height: 200px;
}
}
</style>

View File

@@ -122,7 +122,12 @@ export default {
return this.getWorkspace(this.connUid).breadcrumbs.schema;
},
primaryField () {
return this.fields.filter(field => ['pri', 'uni'].includes(field.key))[0] || false;
const primaryFields = this.fields.filter(field => ['pri', 'uni'].includes(field.key));
if (primaryFields.length > 1 || !primaryFields.length)
return false;
return primaryFields[0];
},
isSortable () {
return this.fields.every(field => field.name);
@@ -289,14 +294,17 @@ export default {
this.resizeResults();
},
updateField (payload, row) {
const orgRow = this.localResults.find(lr => lr._id === row._id);
delete row._id;
delete orgRow._id;
const params = {
primary: this.primaryField.name,
schema: this.getSchema(this.resultsetIndex),
table: this.getTable(this.resultsetIndex),
id: this.getPrimaryValue(row),
id: this.getPrimaryValue(orgRow),
row,
orgRow,
...payload
};
this.$emit('update-field', params);
@@ -325,6 +333,7 @@ export default {
table: this.getTable(this.resultsetIndex),
id: this.getPrimaryValue(row),
row,
orgRow: row,
field: this.selectedCell.field,
content: null
};

View File

@@ -12,7 +12,7 @@
<span
v-if="!isInlineEditor[cKey]"
class="cell-content px-2"
:class="`${isNull(col)} type-${fields[cKey].type.toLowerCase()}`"
:class="`${isNull(col)} ${typeClass(fields[cKey].type)}`"
@dblclick="editON($event, col, cKey)"
>{{ col | typeFormat(fields[cKey].type.toLowerCase(), fields[cKey].length) | cutText }}</span>
<ForeignKeySelect
@@ -34,6 +34,15 @@
class="editable-field px-2"
@blur="editOFF"
>
<select
v-else-if="inputProps.type === 'boolean'"
v-model="editingContent"
class="form-select small-select editable-field"
@blur="editOFF"
>
<option>true</option>
<option>false</option>
</select>
<input
v-else
ref="editField"
@@ -173,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, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BOOLEAN, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
import { VueMaskDirective } from 'v-mask';
import ConfirmModal from '@/components/BaseConfirmModal';
import TextEditor from '@/components/BaseTextEditor';
@@ -204,6 +213,9 @@ export default {
return moment(val).isValid() ? moment(val).format('YYYY-MM-DD') : val;
if (DATETIME.includes(type)) {
if (typeof val === 'string')
return val;
let datePrecision = '';
for (let i = 0; i < precision; i++)
datePrecision += i === 0 ? '.S' : 'S';
@@ -225,6 +237,12 @@ export default {
return hexToBinary(hex);
}
if (ARRAY.includes(type)) {
if (Array.isArray(val))
return JSON.stringify(val).replaceAll('[', '{').replaceAll(']', '}');
return val;
}
return val;
}
},
@@ -243,6 +261,7 @@ export default {
editingContent: null,
editingType: null,
editingField: null,
editingLength: null,
editorMode: 'text',
contentInfo: {
ext: '',
@@ -286,8 +305,8 @@ export default {
if (BLOB.includes(this.editingType))
return { type: 'file', mask: false };
if (BIT.includes(this.editingType))
return { type: 'text', mask: false };
if (BOOLEAN.includes(this.editingType))
return { type: 'boolean', mask: false };
return { type: 'text', mask: false };
},
@@ -330,6 +349,11 @@ export default {
isNull (value) {
return value === null ? ' is-null' : '';
},
typeClass (type) {
if (type)
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
return '';
},
bufferToBase64 (val) {
return bufferToBase64(val);
},
@@ -339,23 +363,24 @@ export default {
window.addEventListener('keydown', this.onKey);
const type = this.fields[field].type.toUpperCase(); ;
this.originalContent = content;
this.originalContent = this.$options.filters.typeFormat(content, type, this.fields[field].length);
this.editingType = type;
this.editingField = field;
this.editingLength = this.fields[field].length;
if (LONG_TEXT.includes(type)) {
if ([...LONG_TEXT, ...ARRAY, ...TEXT_SEARCH].includes(type)) {
this.isTextareaEditor = true;
this.editingContent = this.$options.filters.typeFormat(this.originalContent, type);
this.editingContent = this.$options.filters.typeFormat(content, type);
return;
}
if (BLOB.includes(type)) {
this.isBlobEditor = true;
this.editingContent = this.originalContent || '';
this.editingContent = content || '';
this.fileToUpload = null;
this.willBeDeleted = false;
if (this.originalContent !== null) {
if (content !== null) {
const buff = Buffer.from(this.editingContent);
if (buff.length) {
const hex = buff.toString('hex').substring(0, 8).toUpperCase();
@@ -371,7 +396,7 @@ export default {
}
// Inline editable fields
this.editingContent = this.$options.filters.typeFormat(this.originalContent, type, this.fields[field].length);
this.editingContent = this.originalContent;
this.$nextTick(() => { // Focus on input
event.target.blur();
@@ -387,7 +412,13 @@ export default {
this.isInlineEditor[this.editingField] = false;
let content;
if (!BLOB.includes(this.editingType)) {
if (this.editingContent === this.$options.filters.typeFormat(this.originalContent, this.editingType)) return;// If not changed
if ([...DATETIME, ...TIME].includes(this.editingType)) {
if (this.editingContent.substring(this.editingContent.length - 1) === '.')
this.editingContent = this.editingContent.slice(0, -1);
}
if (this.editingContent === this.$options.filters.typeFormat(this.originalContent, this.editingType, this.editingLength)) return;// If not changed
content = this.editingContent;
}
else { // Handle file upload

View File

@@ -83,7 +83,8 @@
</div>
</div>
</div>
<div class="workspace-query-results column col-12">
<div class="workspace-query-results p-relative column col-12">
<BaseLoader v-if="isQuering" />
<WorkspaceQueryTable
v-if="results"
ref="queryTable"
@@ -118,6 +119,7 @@
<script>
import Tables from '@/ipc-api/Tables';
import BaseLoader from '@/components/BaseLoader';
import WorkspaceQueryTable from '@/components/WorkspaceQueryTable';
import ModalNewTableRow from '@/components/ModalNewTableRow';
import ModalFakerRows from '@/components/ModalFakerRows';
@@ -127,6 +129,7 @@ import tableTabs from '@/mixins/tableTabs';
export default {
name: 'WorkspaceTableTab',
components: {
BaseLoader,
WorkspaceQueryTable,
ModalNewTableRow,
ModalFakerRows

View File

@@ -100,7 +100,8 @@ module.exports = {
paste: 'Paste',
tools: 'Tools',
variables: 'Variables',
processes: 'Processes'
processes: 'Processes',
database: 'Database'
},
message: {
appWelcome: 'Welcome to Antares SQL Client!',
@@ -199,7 +200,11 @@ module.exports = {
setNull: 'Set NULL',
processesList: 'Processes list',
processInfo: 'Process info',
manageUsers: 'Manage users'
manageUsers: 'Manage users',
createNewSchema: 'Create new schema',
schemaName: 'Schema name',
editSchema: 'Edit schema',
deleteSchema: 'Delete schema'
},
faker: {
address: 'Address',

View File

@@ -2,20 +2,20 @@
import { ipcRenderer } from 'electron';
export default class {
static createDatabase (params) {
return ipcRenderer.invoke('create-database', params);
static createSchema (params) {
return ipcRenderer.invoke('create-schema', params);
}
static updateDatabase (params) {
return ipcRenderer.invoke('update-database', params);
static updateSchema (params) {
return ipcRenderer.invoke('update-schema', params);
}
static getDatabaseCollation (params) {
return ipcRenderer.invoke('get-database-collation', params);
return ipcRenderer.invoke('get-schema-collation', params);
}
static deleteDatabase (params) {
return ipcRenderer.invoke('delete-database', params);
static deleteSchema (params) {
return ipcRenderer.invoke('delete-schema', params);
}
static getStructure (params) {

View File

@@ -22,6 +22,14 @@
"mediumtext": $string-color,
"longtext": $string-color,
"json": $string-color,
"name": $string-color,
"character": $string-color,
"character_varying": $string-color,
"cidr": $string-color,
"inet": $string-color,
"macaddr": $string-color,
"macaddr8": $string-color,
"uuid": $string-color,
"int": $number-color,
"tinyint": $number-color,
"smallint": $number-color,
@@ -31,12 +39,26 @@
"decimal": $number-color,
"bigint": $number-color,
"newdecimal": $number-color,
"integer": $number-color,
"numeric": $number-color,
"smallserial": $number-color,
"serial": $number-color,
"bigserial": $number-color,
"real": $number-color,
"double_precision": $number-color,
"oid": $number-color,
"xid": $number-color,
"money": $number-color,
"datetime": $date-color,
"date": $date-color,
"time": $date-color,
"time_with_time_zone": $date-color,
"year": $date-color,
"timestamp": $date-color,
"timestamp_without_time_zone": $date-color,
"timestamp_with_time_zone": $date-color,
"bit": $bit-color,
"bit_varying": $bit-color,
"binary": $blob-color,
"varbinary": $blob-color,
"blob": $blob-color,
@@ -44,8 +66,16 @@
"mediumblob": $blob-color,
"medium_blob": $blob-color,
"longblob": $blob-color,
"bytea": $blob-color,
"enum": $enum-color,
"set": $enum-color,
"boolean": $enum-color,
"interval": $array-color,
"array": $array-color,
"anyarray": $array-color,
"tsvector": $array-color,
"tsquery": $array-color,
"pg_node_tree": $array-color,
"unknown": $unknown-color,
)
);

View File

@@ -15,7 +15,8 @@
}
&.key-mul,
&.key-INDEX {
&.key-INDEX,
&.key-KEY {
color: palegreen;
}

View File

@@ -14,6 +14,7 @@ $number-color: cornflowerblue;
$date-color: coral;
$bit-color: lightskyblue;
$blob-color: darkorchid;
$array-color: greenyellow;
$enum-color: gold;
$unknown-color: gray;

View File

@@ -1,7 +1,13 @@
'use strict';
import Store from 'electron-store';
import crypto from 'crypto';
import Application from '../../ipc-api/Application';
const key = Application.getKey();
const key = Application.getKey() || localStorage.getItem('key');
if (!key)
localStorage.setItem('key', crypto.randomBytes(16).toString('hex'));
else
localStorage.setItem('key', key);
const persistentStore = new Store({
name: 'connections',

View File

@@ -1,6 +1,6 @@
'use strict';
import Connection from '@/ipc-api/Connection';
import Database from '@/ipc-api/Database';
import Schema from '@/ipc-api/Schema';
import Users from '@/ipc-api/Users';
import { uidGen } from 'common/libs/uidGen';
const tabIndex = [];
@@ -55,7 +55,7 @@ export default {
state.selected_workspace = uid;
},
ADD_CONNECTED (state, payload) {
const { uid, client, dataTypes, indexTypes, structure, version } = payload;
const { uid, client, dataTypes, indexTypes, customizations, structure, version } = payload;
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid
? {
@@ -63,6 +63,7 @@ export default {
client,
dataTypes,
indexTypes,
customizations,
structure,
connected: true,
version
@@ -253,16 +254,23 @@ export default {
else {
let dataTypes = [];
let indexTypes = [];
let customizations = {};
switch (connection.client) {
case 'mysql':
case 'maria':
dataTypes = require('common/data-types/mysql');
indexTypes = require('common/index-types/mysql');
customizations = require('common/customizations/mysql');
break;
case 'pg':
dataTypes = require('common/data-types/postgresql');
indexTypes = require('common/index-types/postgresql');
customizations = require('common/customizations/postgresql');
break;
}
const { status, response: version } = await Database.getVersion(connection.uid);
const { status, response: version } = await Schema.getVersion(connection.uid);
if (status === 'error')
dispatch('notifications/addNotification', { status, message: version }, { root: true });
@@ -285,6 +293,7 @@ export default {
client: connection.client,
dataTypes,
indexTypes,
customizations,
structure: response,
version
});
@@ -300,7 +309,7 @@ export default {
},
async refreshStructure ({ dispatch, commit, getters }, uid) {
try {
const { status, response } = await Database.getStructure({ uid, schemas: getters.getLoadedSchemas(uid) });
const { status, response } = await Schema.getStructure({ uid, schemas: getters.getLoadedSchemas(uid) });
if (status === 'error')
dispatch('notifications/addNotification', { status, message: response }, { root: true });
@@ -313,7 +322,7 @@ export default {
},
async refreshSchema ({ dispatch, commit }, { uid, schema }) {
try {
const { status, response } = await Database.getStructure({ uid, schemas: new Set([schema]) });
const { status, response } = await Schema.getStructure({ uid, schemas: new Set([schema]) });
if (status === 'error')
dispatch('notifications/addNotification', { status, message: response }, { root: true });
else
@@ -325,7 +334,7 @@ export default {
},
async refreshCollations ({ dispatch, commit }, uid) {
try {
const { status, response } = await Database.getCollations(uid);
const { status, response } = await Schema.getCollations(uid);
if (status === 'error')
dispatch('notifications/addNotification', { status, message: response }, { root: true });
else
@@ -337,7 +346,7 @@ export default {
},
async refreshVariables ({ dispatch, commit }, uid) {
try {
const { status, response } = await Database.getVariables(uid);
const { status, response } = await Schema.getVariables(uid);
if (status === 'error')
dispatch('notifications/addNotification', { status, message: response }, { root: true });
else
@@ -349,7 +358,7 @@ export default {
},
async refreshEngines ({ dispatch, commit }, uid) {
try {
const { status, response } = await Database.getEngines(uid);
const { status, response } = await Schema.getEngines(uid);
if (status === 'error')
dispatch('notifications/addNotification', { status, message: response }, { root: true });
else
@@ -421,7 +430,7 @@ export default {
if (lastBreadcrumbs.schema === payload.schema && hasLastChildren && !hasChildren) return;
if (lastBreadcrumbs.schema !== payload.schema)
Database.useSchema({ uid: getters.getSelected, schema: payload.schema });
Schema.useSchema({ uid: getters.getSelected, schema: payload.schema });
commit('CHANGE_BREADCRUMBS', { uid: getters.getSelected, breadcrumbs: { ...breadcrumbsObj, ...payload } });
lastBreadcrumbs = { ...breadcrumbsObj, ...payload };