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

Compare commits

..

16 Commits

Author SHA1 Message Date
14d5842056 chore(release): 0.2.1 2021-07-09 16:07:07 +02:00
f19f9e23a2 refactor(UI): changed buttons icon position 2021-07-09 15:51:02 +02:00
0252a064d9 feat(UI): contextual menu shortcuts to create new elements on folders 2021-07-09 15:12:16 +02:00
439356a019 feat: context menu option to duplicate connections 2021-07-09 11:18:40 +02:00
c6897af22d feat(MySQL): possibility to set a default schema in connection parameters 2021-07-09 10:26:16 +02:00
7e40dbfba3 Merge pull request #84 from Fabio286/new-commection-mode
feat: new connection mode
2021-07-08 18:50:23 +02:00
27a153ef43 refactor(UI): improved new connection panels appearence 2021-07-08 18:43:31 +02:00
56fcc2650b fix: clear corrupted configurations to avoid exceptions 2021-07-08 18:42:19 +02:00
9622dbec6b chore: update rules 2021-07-08 18:41:17 +02:00
a0ab63bdb5 fix(UI): connection tab indicator when scrolling 2021-07-08 17:58:43 +02:00
8cd76e711c feat(UI): new connection add panel 2021-07-08 17:43:33 +02:00
9af71a6e34 feat(UI): new connection edit panel 2021-07-08 15:06:20 +02:00
e7b3c28826 chore: update README.md 2021-07-07 12:59:27 +02:00
7570b0add8 fix: avoid to trigger schema loading multiple times 2021-07-06 09:36:35 +02:00
Christian Ratz
1801bef019 feat: SSH Tunnel functionality (#81)
* added ssh-tunnel-functionality for mysql-connections

* remove autoformat-stuff

* added identity for using ssh-key

* added identity to mysqlclient to use sshkey

* removed debug console.log

* added ssh-tunnel-functionality for postgresqlclient

* changed naming to sshKey for sshKey-input

* refactoring code

* fix lint

* set dbConfig.ssl to null initially
2021-07-05 09:30:52 +02:00
0db5ebd7bf chore: update README logo 2021-07-03 13:47:37 +02:00
49 changed files with 1642 additions and 135 deletions

View File

@@ -9,7 +9,8 @@
], ],
"rules": { "rules": {
"at-rule-no-unknown": null, "at-rule-no-unknown": null,
"no-descending-specificity": null "no-descending-specificity": null,
"declaration-colon-newline-after": "always-multi-line"
}, },
"syntax": "scss" "syntax": "scss"
} }

View File

@@ -2,6 +2,25 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.2.1](https://github.com/Fabio286/antares/compare/v0.2.0...v0.2.1) (2021-07-09)
### Features
* **UI:** contextual menu shortcuts to create new elements on folders ([0252a06](https://github.com/Fabio286/antares/commit/0252a064d99df09b01c020c7d10c76dd43d4ede8))
* context menu option to duplicate connections ([439356a](https://github.com/Fabio286/antares/commit/439356a01993a6a248f5a7d7df305ac5e0e63775))
* **MySQL:** possibility to set a default schema in connection parameters ([c6897af](https://github.com/Fabio286/antares/commit/c6897af22d04ed930289a55124b3e8d080fbae3a))
* **UI:** new connection add panel ([8cd76e7](https://github.com/Fabio286/antares/commit/8cd76e711c9328254d498b4f9afa221311f5a487))
* **UI:** new connection edit panel ([9af71a6](https://github.com/Fabio286/antares/commit/9af71a6e343deda1bf79d8410511cf861af3c304))
* SSH Tunnel functionality ([#81](https://github.com/Fabio286/antares/issues/81)) ([1801bef](https://github.com/Fabio286/antares/commit/1801bef019cee77a99df7e3822145ad952465abb))
### Bug Fixes
* clear corrupted configurations to avoid exceptions ([56fcc26](https://github.com/Fabio286/antares/commit/56fcc2650b93ece398118f39f027dc9520dd8a6a))
* **UI:** connection tab indicator when scrolling ([a0ab63b](https://github.com/Fabio286/antares/commit/a0ab63bdb533aac9b2bdc2ee07a3b1f2b70ea227))
* avoid to trigger schema loading multiple times ([7570b0a](https://github.com/Fabio286/antares/commit/7570b0add8cb9130f15cf8cb807a96dbfd2837d0))
## [0.2.0](https://github.com/Fabio286/antares/compare/v0.1.13...v0.2.0) (2021-07-03) ## [0.2.0](https://github.com/Fabio286/antares/compare/v0.1.13...v0.2.0) (2021-07-03)

View File

@@ -9,9 +9,9 @@
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. 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. 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 in development state, many features will come in future updates**, and supports only MySQL/MariaDB and PostgreSQL (partially). **At the moment this application is in development state, many features will come in future updates**, and supports only MySQL/MariaDB and PostgreSQL.
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. At the moment, however, there are all the features necessary to have a pleasant database management experience, so give it a chance and send us your feedback, we would really appreciate it.
I'm actively working on it, hoping to provide cool features and fixes as soon as possible. We are actively working on it, hoping to provide new cool features, improvements and fixes as soon as possible.
🔗 If you are curious to try Antares you can download and install the [latest release](https://github.com/Fabio286/antares/releases/latest). 🔗 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 [follow Antares SQL](https://twitter.com/AntaresSQL) on Twitter. 👁 To stay tuned for new releases [follow Antares SQL](https://twitter.com/AntaresSQL) on Twitter.
@@ -19,9 +19,9 @@ I'm actively working on it, hoping to provide cool features and fixes as soon as
## Philosophy ## Philosophy
Why am I developing an SQL client when there are a lot of them on the market? Why are we 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. The main goal is to develop a totally free, full featured, cross platform and open source alternative, empowered by JavaScript's ecosystem.
A modern application created with minimalism and semplicity in mind, with features in the right places, not hundreds of tiny buttons, tabs or submenu. A modern application created with minimalism and semplicity in mind, with features in the right places, not hundreds of tiny buttons, nested tabs or submenu; productivity comes first.
## Download ## Download
@@ -43,6 +43,7 @@ A modern application created with minimalism and semplicity in mind, with featur
- Fake table data filler. - Fake table data filler.
- Run queries on multiple tabs. - Run queries on multiple tabs.
- Query suggestions and auto complete. - Query suggestions and auto complete.
- SSH tunnel support.
- Dark and light theme. - Dark and light theme.
- Scratchpad. - Scratchpad.
- Multi language. - Multi language.
@@ -54,13 +55,10 @@ This is a roadmap with major features will come in near future.
- Support for other databases. - Support for other databases.
- Database tools. - Database tools.
- SSH tunnel support.
- Users management (add/edit/delete). - Users management (add/edit/delete).
- UI/UX improvements. - Query history and bookmarks.
- Query history.
- More context menu shortcuts. - More context menu shortcuts.
- More keyboard shortcuts. - More keyboard shortcuts.
- Query logs console.
- Import/export and migration. - Import/export and migration.
## Currently supported ## Currently supported
@@ -68,7 +66,7 @@ This is a roadmap with major features will come in near future.
### Databases ### Databases
- [x] MySQL/MariaDB - [x] MySQL/MariaDB
- [x] PostgreSQL (partially, work in progress) - [x] PostgreSQL
- [ ] SQLite - [ ] SQLite
- [ ] MSSQL - [ ] MSSQL
- [ ] OracleDB - [ ] OracleDB

BIN
docs/gh-logo-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 304 KiB

View File

@@ -1,7 +1,7 @@
{ {
"name": "antares", "name": "antares",
"productName": "Antares", "productName": "Antares",
"version": "0.2.0", "version": "0.2.1",
"description": "A cross-platform easy to use SQL client.", "description": "A cross-platform easy to use SQL client.",
"license": "MIT", "license": "MIT",
"repository": "https://github.com/Fabio286/antares.git", "repository": "https://github.com/Fabio286/antares.git",
@@ -102,6 +102,7 @@
"source-map-support": "^0.5.16", "source-map-support": "^0.5.16",
"spectre.css": "^0.5.9", "spectre.css": "^0.5.9",
"sql-formatter": "^4.0.2", "sql-formatter": "^4.0.2",
"ssh2-promise": "^0.1.7",
"v-mask": "^2.2.4", "v-mask": "^2.2.4",
"vue-i18n": "^8.24.4", "vue-i18n": "^8.24.4",
"vuedraggable": "^2.24.3", "vuedraggable": "^2.24.3",

View File

@@ -7,6 +7,7 @@ module.exports = {
database: false, database: false,
collations: false, collations: false,
engines: false, engines: false,
connectionSchema: false,
// Tools // Tools
processesList: false, processesList: false,
usersManagement: false, usersManagement: false,

View File

@@ -7,6 +7,7 @@ module.exports = {
defaultUser: 'root', defaultUser: 'root',
defaultDatabase: null, defaultDatabase: null,
// Core // Core
connectionSchema: true,
collations: true, collations: true,
engines: true, engines: true,
// Tools // Tools

View File

@@ -24,13 +24,23 @@ export default connections => {
}; };
} }
const connection = ClientsFactory.getConnection({ if (conn.ssh) {
client: conn.client, params.ssh = {
params host: conn.sshHost,
}); username: conn.sshUser,
password: conn.sshPass,
port: conn.sshPort ? conn.sshPort : 22,
identity: conn.sshKey
};
}
try { try {
const connection = await ClientsFactory.getConnection({
client: conn.client,
params
});
await connection.connect(); await connection.connect();
await connection.select('1+1').run(); await connection.select('1+1').run();
connection.destroy(); connection.destroy();
@@ -57,6 +67,9 @@ export default connections => {
if (conn.database) if (conn.database)
params.database = conn.database; params.database = conn.database;
if (conn.schema)
params.schema = conn.schema;
if (conn.ssl) { if (conn.ssl) {
params.ssl = { params.ssl = {
key: conn.key ? fs.readFileSync(conn.key) : null, key: conn.key ? fs.readFileSync(conn.key) : null,
@@ -66,6 +79,16 @@ export default connections => {
}; };
} }
if (conn.ssh) {
params.ssh = {
host: conn.sshHost,
username: conn.sshUser,
password: conn.sshPass,
port: conn.sshPort ? conn.sshPort : 22,
identity: conn.sshKey
};
}
try { try {
const connection = ClientsFactory.getConnection({ const connection = ClientsFactory.getConnection({
client: conn.client, client: conn.client,

View File

@@ -12,6 +12,12 @@ export class ClientsFactory {
* @param {String} args.params.host * @param {String} args.params.host
* @param {Number} args.params.port * @param {Number} args.params.port
* @param {String} args.params.password * @param {String} args.params.password
* @param {String=} args.params.database
* @param {String=} args.params.schema
* @param {String} args.params.ssh.host
* @param {String} args.params.ssh.username
* @param {String} args.params.ssh.password
* @param {Number} args.params.ssh.port
* @param {Number=} args.poolSize * @param {Number=} args.poolSize
* @returns Database Connection * @returns Database Connection
* @memberof ClientsFactory * @memberof ClientsFactory

View File

@@ -2,6 +2,7 @@
import mysql from 'mysql2/promise'; import mysql from 'mysql2/promise';
import { AntaresCore } from '../AntaresCore'; import { AntaresCore } from '../AntaresCore';
import dataTypes from 'common/data-types/mysql'; import dataTypes from 'common/data-types/mysql';
import * as SSH2Promise from 'ssh2-promise';
export class MySQLClient extends AntaresCore { export class MySQLClient extends AntaresCore {
constructor (args) { constructor (args) {
@@ -104,11 +105,33 @@ export class MySQLClient extends AntaresCore {
async connect () { async connect () {
delete this._params.application_name; delete this._params.application_name;
const dbConfig = {
host: this._params.host,
port: this._params.port,
user: this._params.user,
password: this._params.password,
ssl: null
};
if (this._params.schema?.length) dbConfig.database = this._params.schema;
if (this._params.ssl) dbConfig.ssl = { ...this._params.ssl };
if (this._params.ssh) {
this._ssh = new SSH2Promise({ ...this._params.ssh });
this._tunnel = await this._ssh.addTunnel({
remoteAddr: this._params.host,
remotePort: this._params.port
});
dbConfig.port = this._tunnel.localPort;
}
if (!this._poolSize) if (!this._poolSize)
this._connection = await mysql.createConnection(this._params); this._connection = await mysql.createConnection(dbConfig);
else { else {
this._connection = mysql.createPool({ this._connection = mysql.createPool({
...this._params, ...dbConfig,
connectionLimit: this._poolSize, connectionLimit: this._poolSize,
typeCast: (field, next) => { typeCast: (field, next) => {
if (field.type === 'DATETIME') if (field.type === 'DATETIME')
@@ -125,6 +148,7 @@ export class MySQLClient extends AntaresCore {
*/ */
destroy () { destroy () {
this._connection.end(); this._connection.end();
if (this._ssh) this._ssh.close();
} }
/** /**
@@ -145,6 +169,12 @@ export class MySQLClient extends AntaresCore {
*/ */
async getStructure (schemas) { async getStructure (schemas) {
const { rows: databases } = await this.raw('SHOW DATABASES'); const { rows: databases } = await this.raw('SHOW DATABASES');
let filteredDatabases = databases;
if (this._params.schema)
filteredDatabases = filteredDatabases.filter(db => db.Database === this._params.schema);
const { rows: functions } = await this.raw('SHOW FUNCTION STATUS'); const { rows: functions } = await this.raw('SHOW FUNCTION STATUS');
const { rows: procedures } = await this.raw('SHOW PROCEDURE STATUS'); const { rows: procedures } = await this.raw('SHOW PROCEDURE STATUS');
const { rows: schedulers } = await this.raw('SELECT *, EVENT_SCHEMA AS `Db`, EVENT_NAME AS `Name` FROM information_schema.`EVENTS`'); const { rows: schedulers } = await this.raw('SELECT *, EVENT_SCHEMA AS `Db`, EVENT_NAME AS `Name` FROM information_schema.`EVENTS`');
@@ -152,7 +182,7 @@ export class MySQLClient extends AntaresCore {
const tablesArr = []; const tablesArr = [];
const triggersArr = []; const triggersArr = [];
for (const db of databases) { for (const db of filteredDatabases) {
if (!schemas.has(db.Database)) continue; if (!schemas.has(db.Database)) continue;
let { rows: tables } = await this.raw(`SHOW TABLE STATUS FROM \`${db.Database}\``); let { rows: tables } = await this.raw(`SHOW TABLE STATUS FROM \`${db.Database}\``);
@@ -174,7 +204,7 @@ export class MySQLClient extends AntaresCore {
} }
} }
return databases.map(db => { return filteredDatabases.map(db => {
if (schemas.has(db.Database)) { if (schemas.has(db.Database)) {
// TABLES // TABLES
const remappedTables = tablesArr.filter(table => table.Db === db.Database).map(table => { const remappedTables = tablesArr.filter(table => table.Db === db.Database).map(table => {

View File

@@ -3,6 +3,7 @@ import { Pool, Client, types } from 'pg';
import { parse } from 'pgsql-ast-parser'; import { parse } from 'pgsql-ast-parser';
import { AntaresCore } from '../AntaresCore'; import { AntaresCore } from '../AntaresCore';
import dataTypes from 'common/data-types/postgresql'; import dataTypes from 'common/data-types/postgresql';
import * as SSH2Promise from 'ssh2-promise';
function pgToString (value) { function pgToString (value) {
return value.toString(); return value.toString();
@@ -51,13 +52,35 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async connect () { async connect () {
const dbConfig = {
host: this._params.host,
port: this._params.port,
user: this._params.user,
password: this._params.password,
ssl: null
};
if (this._params.database?.length) dbConfig.database = this._params.database;
if (this._params.ssl) dbConfig.ssl = { ...this._params.ssl };
if (this._params.ssh) {
this._ssh = new SSH2Promise({ ...this._params.ssh });
this._tunnel = await this._ssh.addTunnel({
remoteAddr: this._params.host,
remotePort: this._params.port
});
dbConfig.port = this._tunnel.localPort;
}
if (!this._poolSize) { if (!this._poolSize) {
const client = new Client(this._params); const client = new Client(dbConfig);
await client.connect(); await client.connect();
this._connection = client; this._connection = client;
} }
else { else {
const pool = new Pool({ ...this._params, max: this._poolSize }); const pool = new Pool({ ...dbConfig, max: this._poolSize });
this._connection = pool; this._connection = pool;
} }
} }
@@ -67,6 +90,7 @@ export class PostgreSQLClient extends AntaresCore {
*/ */
destroy () { destroy () {
this._connection.end(); this._connection.end();
if (this._ssh) this._ssh.close();
} }
/** /**

View File

@@ -4,22 +4,21 @@
<div id="window-content"> <div id="window-content">
<TheSettingBar /> <TheSettingBar />
<div id="main-content" class="container"> <div id="main-content" class="container">
<TheAppWelcome v-if="!connections.length" @new-conn="showNewConnModal" /> <div class="columns col-gapless">
<div v-else class="columns col-gapless">
<Workspace <Workspace
v-for="connection in connections" v-for="connection in connections"
:key="connection.uid" :key="connection.uid"
:connection="connection" :connection="connection"
/> />
<WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" />
</div> </div>
<TheFooter />
<TheNotificationsBoard />
<TheScratchpad v-if="isScratchpad" />
<ModalSettings v-if="isSettingModal" />
<ModalDiscardChanges v-if="isUnsavedDiscardModal" />
<BaseTextEditor class="d-none" value="" />
</div> </div>
<TheFooter />
<TheNotificationsBoard />
<ModalNewConnection v-if="isNewConnModal" />
<TheScratchpad v-if="isScratchpad" />
<ModalSettings v-if="isSettingModal" />
<ModalDiscardChanges v-if="isUnsavedDiscardModal" />
<BaseTextEditor class="d-none" value="" />
</div> </div>
</div> </div>
</template> </template>
@@ -36,9 +35,8 @@ export default {
TheSettingBar: () => import(/* webpackChunkName: "TheSettingBar" */'@/components/TheSettingBar'), TheSettingBar: () => import(/* webpackChunkName: "TheSettingBar" */'@/components/TheSettingBar'),
TheFooter: () => import(/* webpackChunkName: "TheFooter" */'@/components/TheFooter'), TheFooter: () => import(/* webpackChunkName: "TheFooter" */'@/components/TheFooter'),
TheNotificationsBoard: () => import(/* webpackChunkName: "TheNotificationsBoard" */'@/components/TheNotificationsBoard'), TheNotificationsBoard: () => import(/* webpackChunkName: "TheNotificationsBoard" */'@/components/TheNotificationsBoard'),
TheAppWelcome: () => import(/* webpackChunkName: "TheAppWelcome" */'@/components/TheAppWelcome'),
Workspace: () => import(/* webpackChunkName: "Workspace" */'@/components/Workspace'), Workspace: () => import(/* webpackChunkName: "Workspace" */'@/components/Workspace'),
ModalNewConnection: () => import(/* webpackChunkName: "ModalNewConnection" */'@/components/ModalNewConnection'), WorkspaceAddConnectionPanel: () => import(/* webpackChunkName: "WorkspaceAddConnectionPanel" */'@/components/WorkspaceAddConnectionPanel'),
ModalSettings: () => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings'), ModalSettings: () => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings'),
TheScratchpad: () => import(/* webpackChunkName: "TheScratchpad" */'@/components/TheScratchpad'), TheScratchpad: () => import(/* webpackChunkName: "TheScratchpad" */'@/components/TheScratchpad'),
ModalDiscardChanges: () => import(/* webpackChunkName: "ModalDiscardChanges" */'@/components/ModalDiscardChanges'), ModalDiscardChanges: () => import(/* webpackChunkName: "ModalDiscardChanges" */'@/components/ModalDiscardChanges'),
@@ -49,9 +47,8 @@ export default {
}, },
computed: { computed: {
...mapGetters({ ...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
isLoading: 'application/isLoading', isLoading: 'application/isLoading',
isNewConnModal: 'application/isNewModal',
isEditModal: 'application/isEditModal',
isSettingModal: 'application/isSettingModal', isSettingModal: 'application/isSettingModal',
isScratchpad: 'application/isScratchpad', isScratchpad: 'application/isScratchpad',
connections: 'connections/getConnections', connections: 'connections/getConnections',

View File

@@ -60,7 +60,7 @@
{{ $t('word.application') }} {{ $t('word.application') }}
</div> </div>
<div class="column col-8 col-sm-12 mb-2"> <div class="column col-8 col-sm-12 mb-2">
<div class="form-group mb-4"> <div class="form-group">
<div class="col-6 col-sm-12"> <div class="col-6 col-sm-12">
<label class="form-label"> <label class="form-label">
<i class="mdi mdi-18px mdi-translate mr-1" /> <i class="mdi mdi-18px mdi-translate mr-1" />

View File

@@ -3,18 +3,13 @@
:context-event="contextEvent" :context-event="contextEvent"
@close-context="$emit('close-context')" @close-context="$emit('close-context')"
> >
<div class="context-element" @click="showEditModal(contextConnection)"> <div class="context-element" @click="duplicateConnection">
<span class="d-flex"><i class="mdi mdi-18px mdi-pencil text-light pr-1" /> {{ $t('word.edit') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-content-duplicate text-light pr-1" /> {{ $t('word.duplicate') }}</span>
</div> </div>
<div class="context-element" @click="showConfirmModal"> <div class="context-element" @click="showConfirmModal">
<span class="d-flex"><i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ $t('word.delete') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ $t('word.delete') }}</span>
</div> </div>
<ModalEditConnection
v-if="isEditModal"
:connection="contextConnection"
@close="hideEditModal"
/>
<ConfirmModal <ConfirmModal
v-if="isConfirmModal" v-if="isConfirmModal"
@confirm="confirmDeleteConnection" @confirm="confirmDeleteConnection"
@@ -36,15 +31,14 @@
<script> <script>
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import { uidGen } from 'common/libs/uidGen';
import BaseContextMenu from '@/components/BaseContextMenu'; import BaseContextMenu from '@/components/BaseContextMenu';
import ConfirmModal from '@/components/BaseConfirmModal'; import ConfirmModal from '@/components/BaseConfirmModal';
import ModalEditConnection from '@/components/ModalEditConnection';
export default { export default {
name: 'SettingBarContext', name: 'SettingBarContext',
components: { components: {
BaseContextMenu, BaseContextMenu,
ModalEditConnection,
ConfirmModal ConfirmModal
}, },
props: { props: {
@@ -59,7 +53,8 @@ export default {
}, },
computed: { computed: {
...mapGetters({ ...mapGetters({
getConnectionName: 'connections/getConnectionName' getConnectionName: 'connections/getConnectionName',
selectedWorkspace: 'workspaces/getSelected'
}), }),
connectionName () { connectionName () {
return this.getConnectionName(this.contextConnection.uid); return this.getConnectionName(this.contextConnection.uid);
@@ -67,17 +62,25 @@ export default {
}, },
methods: { methods: {
...mapActions({ ...mapActions({
deleteConnection: 'connections/deleteConnection' addConnection: 'connections/addConnection',
deleteConnection: 'connections/deleteConnection',
selectWorkspace: 'workspaces/selectWorkspace'
}), }),
confirmDeleteConnection () { confirmDeleteConnection () {
if (this.selectedWorkspace === this.contextConnection.uid)
this.selectWorkspace();
this.deleteConnection(this.contextConnection); this.deleteConnection(this.contextConnection);
this.closeContext(); this.closeContext();
}, },
showEditModal () { duplicateConnection () {
this.isEditModal = true; let connectionCopy = Object.assign({}, this.contextConnection);
}, connectionCopy = {
hideEditModal () { ...connectionCopy,
this.isEditModal = false; uid: uidGen('C'),
name: connectionCopy.name ? `${connectionCopy.name}_copy` : ''
};
this.addConnection(connectionCopy);
this.closeContext(); this.closeContext();
}, },
showConfirmModal () { showConfirmModal () {

View File

@@ -25,7 +25,8 @@
</draggable> </draggable>
<li <li
class="settingbar-element btn btn-link ex-tooltip" class="settingbar-element btn btn-link ex-tooltip"
@click="showNewConnModal" :class="{'selected': 'NEW' === selectedWorkspace}"
@click="selectWorkspace('NEW')"
@mouseover.self="tooltipPosition" @mouseover.self="tooltipPosition"
> >
<i class="settingbar-element-icon mdi mdi-24px mdi-plus text-light" /> <i class="settingbar-element-icon mdi mdi-24px mdi-plus text-light" />
@@ -92,7 +93,6 @@ export default {
methods: { methods: {
...mapActions({ ...mapActions({
updateConnections: 'connections/updateConnections', updateConnections: 'connections/updateConnections',
showNewConnModal: 'application/showNewConnModal',
showSettingModal: 'application/showSettingModal', showSettingModal: 'application/showSettingModal',
showScratchpad: 'application/showScratchpad', showScratchpad: 'application/showScratchpad',
selectWorkspace: 'workspaces/selectWorkspace' selectWorkspace: 'workspaces/selectWorkspace'
@@ -167,14 +167,13 @@ export default {
height: $settingbar-width; height: $settingbar-width;
width: 100%; width: 100%;
margin: 0; margin: 0;
border-left: 3px solid transparent;
opacity: 0.5; opacity: 0.5;
transition: opacity 0.2s; transition: opacity 0.2s;
display: flex; display: flex;
align-content: center; align-items: center;
justify-content: center; justify-content: flex-start;
flex-direction: column;
border-radius: 0; border-radius: 0;
padding: 0;
&:hover { &:hover {
opacity: 1; opacity: 1;
@@ -194,12 +193,12 @@ export default {
width: 3px; width: 3px;
transition: height 0.2s; transition: height 0.2s;
background-color: $primary-color; background-color: $primary-color;
position: absolute;
left: 0;
border-radius: $border-radius; border-radius: $border-radius;
} }
.settingbar-element-icon { .settingbar-element-icon {
margin: 0 auto;
&.badge::after { &.badge::after {
bottom: -10px; bottom: -10px;
right: 0; right: 0;

View File

@@ -58,6 +58,7 @@ export default {
}), }),
windowTitle () { windowTitle () {
if (!this.selectedWorkspace) return ''; if (!this.selectedWorkspace) return '';
if (this.selectedWorkspace === 'NEW') return this.$t('message.createNewConnection');
const connectionName = this.getConnectionName(this.selectedWorkspace); const connectionName = this.getConnectionName(this.selectedWorkspace);
const workspace = this.getWorkspace(this.selectedWorkspace); const workspace = this.getWorkspace(this.selectedWorkspace);

View File

@@ -1,6 +1,10 @@
<template> <template>
<div v-show="isSelected" class="workspace column columns col-gapless"> <div v-show="isSelected" class="workspace column columns col-gapless">
<WorkspaceExploreBar :connection="connection" :is-selected="isSelected" /> <WorkspaceExploreBar
v-if="workspace.connection_status === 'connected'"
:connection="connection"
:is-selected="isSelected"
/>
<div v-if="workspace.connection_status === 'connected'" class="workspace-tabs column columns col-gapless"> <div v-if="workspace.connection_status === 'connected'" class="workspace-tabs column columns col-gapless">
<ul <ul
id="tabWrap" id="tabWrap"
@@ -152,6 +156,7 @@
:connection="connection" :connection="connection"
/> />
</div> </div>
<WorkspaceEditConnectionPanel v-else :connection="connection" />
<ModalProcessesList <ModalProcessesList
v-if="isProcessesModal" v-if="isProcessesModal"
:connection="connection" :connection="connection"
@@ -164,6 +169,7 @@
import { mapGetters, mapActions } from 'vuex'; import { mapGetters, mapActions } from 'vuex';
import Connection from '@/ipc-api/Connection'; import Connection from '@/ipc-api/Connection';
import WorkspaceExploreBar from '@/components/WorkspaceExploreBar'; import WorkspaceExploreBar from '@/components/WorkspaceExploreBar';
import WorkspaceEditConnectionPanel from '@/components/WorkspaceEditConnectionPanel';
import WorkspaceQueryTab from '@/components/WorkspaceQueryTab'; import WorkspaceQueryTab from '@/components/WorkspaceQueryTab';
import WorkspaceTableTab from '@/components/WorkspaceTableTab'; import WorkspaceTableTab from '@/components/WorkspaceTableTab';
import WorkspacePropsTab from '@/components/WorkspacePropsTab'; import WorkspacePropsTab from '@/components/WorkspacePropsTab';
@@ -179,6 +185,7 @@ export default {
name: 'Workspace', name: 'Workspace',
components: { components: {
WorkspaceExploreBar, WorkspaceExploreBar,
WorkspaceEditConnectionPanel,
WorkspaceQueryTab, WorkspaceQueryTab,
WorkspaceTableTab, WorkspaceTableTab,
WorkspacePropsTab, WorkspacePropsTab,

View File

@@ -0,0 +1,519 @@
<template>
<div class="connection-panel">
<div class="panel">
<div class="panel-nav">
<ul class="tab tab-block">
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'general'}"
@click="selectTab('general')"
>
<a class="tab-link">{{ $t('word.general') }}</a>
</li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'ssl'}"
@click="selectTab('ssl')"
>
<a class="tab-link">{{ $t('word.ssl') }}</a>
</li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'ssh'}"
@click="selectTab('ssh')"
>
<a class="tab-link">{{ $t('word.sshTunnel') }}</a>
</li>
</ul>
</div>
<div v-if="selectedTab === 'general'" class="panel-body py-0">
<div>
<form class="form-horizontal">
<fieldset class="m-0" :disabled="isBusy">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.connectionName') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
ref="firstInput"
v-model="connection.name"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.client') }}</label>
</div>
<div class="column col-8 col-sm-12">
<select v-model="connection.client" class="form-select">
<option value="mysql">
MySQL
</option>
<option value="maria">
MariaDB
</option>
<option value="pg">
PostgreSQL
</option>
<!-- <option value="mssql">
Microsoft SQL
</option>
<option value="oracledb">
Oracle DB
</option> -->
</select>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.hostName') }}/IP</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="connection.host"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.port') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="connection.port"
class="form-input"
type="number"
min="1"
max="65535"
>
</div>
</div>
<div v-if="customizations.database" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.database') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="connection.database"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.user') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="connection.user"
class="form-input"
type="text"
:disabled="connection.ask"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.password') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="connection.password"
class="form-input"
type="password"
:disabled="connection.ask"
>
</div>
</div>
<div v-if="customizations.connectionSchema" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.schema') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="connection.schema"
class="form-input"
type="text"
:placeholder="$t('word.all')"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12" />
<div class="column col-8 col-sm-12">
<label class="form-checkbox form-inline">
<input v-model="connection.ask" type="checkbox"><i class="form-icon" /> {{ $t('message.askCredentials') }}
</label>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div v-if="selectedTab === 'ssl'" class="panel-body py-0">
<div>
<form class="form-horizontal">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">
{{ $t('message.enableSsl') }}
</label>
</div>
<div class="column col-8 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleSsl">
<input type="checkbox" :checked="connection.ssl">
<i class="form-icon" />
</label>
</div>
</div>
<fieldset class="m-0" :disabled="isBusy || !connection.ssl">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.privateKey') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="connection.key"
:message="$t('word.browse')"
@clear="pathClear('key')"
@change="pathSelection($event, 'key')"
/>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.certificate') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="connection.cert"
:message="$t('word.browse')"
@clear="pathClear('cert')"
@change="pathSelection($event, 'cert')"
/>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.caCertificate') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="connection.ca"
:message="$t('word.browse')"
@clear="pathClear('ca')"
@change="pathSelection($event, 'ca')"
/>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.ciphers') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
ref="firstInput"
v-model="connection.ciphers"
class="form-input"
type="text"
>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div v-if="selectedTab === 'ssh'" class="panel-body py-0">
<div>
<form class="form-horizontal">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">
{{ $t('message.enableSsh') }}
</label>
</div>
<div class="column col-8 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleSsh">
<input type="checkbox" :checked="connection.ssh">
<i class="form-icon" />
</label>
</div>
</div>
<fieldset class="m-0" :disabled="isBusy || !connection.ssh">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.hostName') }}/IP</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="connection.sshHost"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.user') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="connection.sshUser"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.password') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="connection.sshPass"
class="form-input"
type="password"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.port') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="connection.sshPort"
class="form-input"
type="number"
min="1"
max="65535"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.privateKey') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="connection.sshKey"
:message="$t('word.browse')"
@clear="pathClear('sshKey')"
@change="pathSelection($event, 'sshKey')"
/>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div class="panel-footer">
<button
class="btn btn-gray mr-2 d-flex"
:class="{'loading': isTesting}"
:disabled="isBusy"
@click="startTest"
>
<i class="mdi mdi-24px mdi-lightning-bolt mr-1" />
{{ $t('message.testConnection') }}
</button>
<button
class="btn btn-primary mr-2 d-flex"
:disabled="isBusy"
@click="saveConnection"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
{{ $t('word.save') }}
</button>
</div>
</div>
<ModalAskCredentials
v-if="isAsking"
@close-asking="closeAsking"
@credentials="continueTest"
/>
</div>
</template>
<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';
import BaseUploadInput from '@/components/BaseUploadInput';
export default {
name: 'WorkspaceAddConnectionPanel',
components: {
ModalAskCredentials,
BaseUploadInput
},
data () {
return {
connection: {
name: '',
client: 'mysql',
host: '127.0.0.1',
database: null,
port: null,
user: null,
password: '',
ask: false,
uid: uidGen('C'),
ssl: false,
cert: '',
key: '',
ca: '',
ciphers: '',
ssh: false,
sshHost: '',
sshUser: '',
sshPass: '',
sshKey: '',
sshPort: 22
},
isConnecting: false,
isTesting: false,
isAsking: false,
selectedTab: 'general'
};
},
computed: {
customizations () {
return customizations[this.connection.client];
},
isBusy () {
return this.isConnecting || this.isTesting;
}
},
created () {
this.setDefaults();
setTimeout(() => {
if (this.$refs.firstInput) this.$refs.firstInput.focus();
}, 20);
},
methods: {
...mapActions({
addConnection: 'connections/addConnection',
connectWorkspace: 'workspaces/connectWorkspace',
addNotification: 'notifications/addNotification',
selectWorkspace: 'workspaces/selectWorkspace'
}),
setDefaults () {
this.connection.user = this.customizations.defaultUser;
this.connection.port = this.customizations.defaultPort;
this.connection.database = this.customizations.defaultDatabase;
},
async startConnection () {
await this.saveConnection();
this.isConnecting = true;
if (this.connection.ask)
this.isAsking = true;
else {
await this.connectWorkspace(this.connection);
this.isConnecting = false;
}
},
async startTest () {
this.isTesting = true;
if (this.connection.ask)
this.isAsking = true;
else {
try {
const res = await Connection.makeTest(this.connection);
if (res.status === 'error')
this.addNotification({ status: 'error', message: res.response.message });
else
this.addNotification({ status: 'success', message: this.$t('message.connectionSuccessfullyMade') });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isTesting = false;
}
},
async continueTest (credentials) { // if "Ask for credentials" is true
this.isAsking = false;
const params = Object.assign({}, this.connection, credentials);
try {
if (this.isConnecting) {
const params = Object.assign({}, this.connection, credentials);
await this.connectWorkspace(params);
this.isConnecting = false;
}
else {
const res = await Connection.makeTest(params);
if (res.status === 'error')
this.addNotification({ status: 'error', message: res.response.message });
else
this.addNotification({ status: 'success', message: this.$t('message.connectionSuccessfullyMade') });
}
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isTesting = false;
},
saveConnection () {
this.selectWorkspace(this.connection.uid);
return this.addConnection(this.connection);
},
closeAsking () {
this.isTesting = false;
this.isAsking = false;
},
selectTab (tab) {
this.selectedTab = tab;
},
toggleSsl () {
this.connection.ssl = !this.connection.ssl;
},
toggleSsh () {
this.connection.ssh = !this.connection.ssh;
},
pathSelection (event, name) {
const { files } = event.target;
if (!files.length) return;
this.connection[name] = files[0].path;
},
pathClear (name) {
this.connection[name] = '';
}
}
};
</script>
<style lang="scss" scoped>
.connection-panel {
margin-top: 15vh;
margin-left: auto;
margin-right: auto;
.panel {
width: 450px;
border-radius: $border-radius;
.panel-body {
flex: initial;
}
.panel-footer {
display: flex;
justify-content: flex-end;
}
}
}
</style>

View File

@@ -0,0 +1,500 @@
<template>
<div class="connection-panel">
<div class="panel">
<div class="panel-nav">
<ul class="tab tab-block">
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'general'}"
@click="selectTab('general')"
>
<a class="tab-link">{{ $t('word.general') }}</a>
</li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'ssl'}"
@click="selectTab('ssl')"
>
<a class="tab-link">{{ $t('word.ssl') }}</a>
</li>
<li
class="tab-item c-hand"
:class="{'active': selectedTab === 'ssh'}"
@click="selectTab('ssh')"
>
<a class="tab-link">{{ $t('word.sshTunnel') }}</a>
</li>
</ul>
</div>
<div v-if="selectedTab === 'general'" class="panel-body py-0">
<div>
<form class="form-horizontal">
<fieldset class="m-0" :disabled="isBusy">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.connectionName') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
ref="firstInput"
v-model="localConnection.name"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.client') }}</label>
</div>
<div class="column col-8 col-sm-12">
<select v-model="localConnection.client" class="form-select">
<option value="mysql">
MySQL
</option>
<option value="maria">
MariaDB
</option>
<option value="pg">
PostgreSQL
</option>
</select>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.hostName') }}/IP</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="localConnection.host"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.port') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="localConnection.port"
class="form-input"
type="number"
min="1"
max="65535"
>
</div>
</div>
<div v-if="customizations.database" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.database') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="localConnection.database"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.user') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="localConnection.user"
class="form-input"
type="text"
:disabled="localConnection.ask"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.password') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="localConnection.password"
class="form-input"
type="password"
:disabled="localConnection.ask"
>
</div>
</div>
<div v-if="customizations.connectionSchema" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.schema') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="localConnection.schema"
class="form-input"
type="text"
:placeholder="$t('word.all')"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12" />
<div class="column col-8 col-sm-12">
<label class="form-checkbox form-inline">
<input v-model="localConnection.ask" type="checkbox"><i class="form-icon" /> {{ $t('message.askCredentials') }}
</label>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div v-if="selectedTab === 'ssl'" class="panel-body py-0">
<div>
<form class="form-horizontal">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">
{{ $t('message.enableSsl') }}
</label>
</div>
<div class="column col-8 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleSsl">
<input type="checkbox" :checked="localConnection.ssl">
<i class="form-icon" />
</label>
</div>
</div>
<fieldset class="m-0" :disabled="isBusy || !localConnection.ssl">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.privateKey') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="localConnection.key"
:message="$t('word.browse')"
@clear="pathClear('key')"
@change="pathSelection($event, 'key')"
/>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.certificate') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="localConnection.cert"
:message="$t('word.browse')"
@clear="pathClear('cert')"
@change="pathSelection($event, 'cert')"
/>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.caCertificate') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="localConnection.ca"
:message="$t('word.browse')"
@clear="pathClear('ca')"
@change="pathSelection($event, 'ca')"
/>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.ciphers') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
ref="firstInput"
v-model="localConnection.ciphers"
class="form-input"
type="text"
>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div v-if="selectedTab === 'ssh'" class="panel-body py-0">
<div>
<form class="form-horizontal">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">
{{ $t('message.enableSsh') }}
</label>
</div>
<div class="column col-8 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleSsh">
<input type="checkbox" :checked="localConnection.ssh">
<i class="form-icon" />
</label>
</div>
</div>
<fieldset class="m-0" :disabled="isBusy || !localConnection.ssh">
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.hostName') }}/IP</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="localConnection.sshHost"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.user') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="localConnection.sshUser"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.password') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="localConnection.sshPass"
class="form-input"
type="password"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.port') }}</label>
</div>
<div class="column col-8 col-sm-12">
<input
v-model="localConnection.sshPort"
class="form-input"
type="number"
min="1"
max="65535"
>
</div>
</div>
<div class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.privateKey') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="localConnection.sshKey"
:message="$t('word.browse')"
@clear="pathClear('sshKey')"
@change="pathSelection($event, 'sshKey')"
/>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div class="panel-footer">
<button
class="btn btn-gray mr-2 d-flex"
:class="{'loading': isTesting}"
:disabled="isBusy"
@click="startTest"
>
<i class="mdi mdi-24px mdi-lightning-bolt mr-1" />
{{ $t('message.testConnection') }}
</button>
<button
class="btn btn-primary mr-2 d-flex"
:disabled="isBusy || !hasChanges"
@click="saveConnection"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
{{ $t('word.save') }}
</button>
<button
class="btn btn-success d-flex"
:class="{'loading': isConnecting}"
:disabled="isBusy"
@click="startConnection"
>
<i class="mdi mdi-24px mdi-connection mr-1" />
{{ $t('word.connect') }}
</button>
</div>
</div>
<ModalAskCredentials
v-if="isAsking"
@close-asking="closeAsking"
@credentials="continueTest"
/>
</div>
</template>
<script>
import { mapActions } from 'vuex';
import customizations from 'common/customizations';
import Connection from '@/ipc-api/Connection';
import ModalAskCredentials from '@/components/ModalAskCredentials';
import BaseUploadInput from '@/components/BaseUploadInput';
export default {
name: 'WorkspaceEditConnectionPanel',
components: {
ModalAskCredentials,
BaseUploadInput
},
props: {
connection: Object
},
data () {
return {
isConnecting: false,
isTesting: false,
isAsking: false,
localConnection: null,
selectedTab: 'general'
};
},
computed: {
customizations () {
return customizations[this.connection.client];
},
isBusy () {
return this.isConnecting || this.isTesting;
},
hasChanges () {
return JSON.stringify(this.connection) !== JSON.stringify(this.localConnection);
}
},
watch: {
connection () {
this.localConnection = JSON.parse(JSON.stringify(this.connection));
}
},
created () {
this.localConnection = JSON.parse(JSON.stringify(this.connection));
},
methods: {
...mapActions({
editConnection: 'connections/editConnection',
connectWorkspace: 'workspaces/connectWorkspace',
addNotification: 'notifications/addNotification'
}),
async startConnection () {
await this.saveConnection();
this.isConnecting = true;
if (this.localConnection.ask)
this.isAsking = true;
else {
await this.connectWorkspace(this.localConnection);
this.isConnecting = false;
}
},
async startTest () {
this.isTesting = true;
if (this.localConnection.ask)
this.isAsking = true;
else {
try {
const res = await Connection.makeTest(this.localConnection);
if (res.status === 'error')
this.addNotification({ status: 'error', message: res.response.message });
else
this.addNotification({ status: 'success', message: this.$t('message.connectionSuccessfullyMade') });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isTesting = false;
}
},
async continueTest (credentials) { // if "Ask for credentials" is true
this.isAsking = false;
const params = Object.assign({}, this.localConnection, credentials);
try {
if (this.isConnecting) {
const params = Object.assign({}, this.connection, credentials);
await this.connectWorkspace(params);
this.isConnecting = false;
}
else {
const res = await Connection.makeTest(params);
if (res.status === 'error')
this.addNotification({ status: 'error', message: res.response.message });
else
this.addNotification({ status: 'success', message: this.$t('message.connectionSuccessfullyMade') });
}
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isTesting = false;
},
saveConnection () {
return this.editConnection(this.localConnection);
},
closeAsking () {
this.isTesting = false;
this.isAsking = false;
},
selectTab (tab) {
this.selectedTab = tab;
},
toggleSsl () {
this.localConnection.ssl = !this.localConnection.ssl;
},
toggleSsh () {
this.localConnection.ssh = !this.localConnection.ssh;
},
pathSelection (event, name) {
const { files } = event.target;
if (!files.length) return;
this.localConnection[name] = files[0].path;
},
pathClear (name) {
this.localConnection[name] = '';
}
}
};
</script>
<style lang="scss" scoped>
.connection-panel {
margin-top: 15vh;
margin-left: auto;
margin-right: auto;
.panel {
width: 450px;
border-radius: $border-radius;
.panel-body {
flex: initial;
}
.panel-footer {
display: flex;
justify-content: flex-end;
}
}
}
</style>

View File

@@ -38,12 +38,7 @@
<i class="form-icon mdi mdi-magnify mdi-18px" /> <i class="form-icon mdi mdi-magnify mdi-18px" />
</div> </div>
</div> </div>
<WorkspaceConnectPanel <div class="workspace-explorebar-body">
v-if="workspace.connection_status !== 'connected'"
class="workspace-explorebar-body"
:connection="connection"
/>
<div v-else class="workspace-explorebar-body">
<WorkspaceExploreBarSchema <WorkspaceExploreBarSchema
v-for="db of workspace.structure" v-for="db of workspace.structure"
:key="db.name" :key="db.name"
@@ -52,6 +47,7 @@
@show-schema-context="openSchemaContext" @show-schema-context="openSchemaContext"
@show-table-context="openTableContext" @show-table-context="openTableContext"
@show-misc-context="openMiscContext" @show-misc-context="openMiscContext"
@show-misc-folder-context="openMiscFolderContext"
/> />
</div> </div>
</div> </div>
@@ -130,6 +126,18 @@
@close-context="closeMiscContext" @close-context="closeMiscContext"
@reload="refresh" @reload="refresh"
/> />
<MiscFolderContext
v-if="isMiscFolderContext"
:selected-misc="selectedMisc"
:context-event="miscContextEvent"
@show-create-trigger-modal="showCreateTriggerModal"
@show-create-routine-modal="showCreateRoutineModal"
@show-create-function-modal="showCreateFunctionModal"
@show-create-trigger-function-modal="showCreateTriggerFunctionModal"
@show-create-scheduler-modal="showCreateSchedulerModal"
@close-context="closeMiscFolderContext"
@reload="refresh"
/>
</div> </div>
</template> </template>
@@ -143,11 +151,11 @@ import Routines from '@/ipc-api/Routines';
import Functions from '@/ipc-api/Functions'; import Functions from '@/ipc-api/Functions';
import Schedulers from '@/ipc-api/Schedulers'; import Schedulers from '@/ipc-api/Schedulers';
import WorkspaceConnectPanel from '@/components/WorkspaceConnectPanel';
import WorkspaceExploreBarSchema from '@/components/WorkspaceExploreBarSchema'; import WorkspaceExploreBarSchema from '@/components/WorkspaceExploreBarSchema';
import DatabaseContext from '@/components/WorkspaceExploreBarSchemaContext'; import DatabaseContext from '@/components/WorkspaceExploreBarSchemaContext';
import TableContext from '@/components/WorkspaceExploreBarTableContext'; import TableContext from '@/components/WorkspaceExploreBarTableContext';
import MiscContext from '@/components/WorkspaceExploreBarMiscContext'; import MiscContext from '@/components/WorkspaceExploreBarMiscContext';
import MiscFolderContext from '@/components/WorkspaceExploreBarMiscFolderContext';
import ModalNewSchema from '@/components/ModalNewSchema'; import ModalNewSchema from '@/components/ModalNewSchema';
import ModalNewTable from '@/components/ModalNewTable'; import ModalNewTable from '@/components/ModalNewTable';
import ModalNewView from '@/components/ModalNewView'; import ModalNewView from '@/components/ModalNewView';
@@ -160,11 +168,11 @@ import ModalNewScheduler from '@/components/ModalNewScheduler';
export default { export default {
name: 'WorkspaceExploreBar', name: 'WorkspaceExploreBar',
components: { components: {
WorkspaceConnectPanel,
WorkspaceExploreBarSchema, WorkspaceExploreBarSchema,
DatabaseContext, DatabaseContext,
TableContext, TableContext,
MiscContext, MiscContext,
MiscFolderContext,
ModalNewSchema, ModalNewSchema,
ModalNewTable, ModalNewTable,
ModalNewView, ModalNewView,
@@ -197,6 +205,7 @@ export default {
isDatabaseContext: false, isDatabaseContext: false,
isTableContext: false, isTableContext: false,
isMiscContext: false, isMiscContext: false,
isMiscFolderContext: false,
databaseContextEvent: null, databaseContextEvent: null,
tableContextEvent: null, tableContextEvent: null,
@@ -333,11 +342,20 @@ export default {
this.miscContextEvent = payload.event; this.miscContextEvent = payload.event;
this.isMiscContext = true; this.isMiscContext = true;
}, },
openMiscFolderContext (payload) {
this.selectedMisc = payload.type;
this.miscContextEvent = payload.event;
this.isMiscFolderContext = true;
},
closeMiscContext () { closeMiscContext () {
this.isMiscContext = false; this.isMiscContext = false;
}, },
closeMiscFolderContext () {
this.isMiscFolderContext = false;
},
showCreateViewModal () { showCreateViewModal () {
this.closeDatabaseContext(); this.closeDatabaseContext();
this.closeMiscFolderContext();
this.isNewViewModal = true; this.isNewViewModal = true;
}, },
hideCreateViewModal () { hideCreateViewModal () {
@@ -361,6 +379,7 @@ export default {
}, },
showCreateTriggerModal () { showCreateTriggerModal () {
this.closeDatabaseContext(); this.closeDatabaseContext();
this.closeMiscFolderContext();
this.isNewTriggerModal = true; this.isNewTriggerModal = true;
}, },
hideCreateTriggerModal () { hideCreateTriggerModal () {
@@ -385,6 +404,7 @@ export default {
}, },
showCreateRoutineModal () { showCreateRoutineModal () {
this.closeDatabaseContext(); this.closeDatabaseContext();
this.closeMiscFolderContext();
this.isNewRoutineModal = true; this.isNewRoutineModal = true;
}, },
hideCreateRoutineModal () { hideCreateRoutineModal () {
@@ -408,6 +428,7 @@ export default {
}, },
showCreateFunctionModal () { showCreateFunctionModal () {
this.closeDatabaseContext(); this.closeDatabaseContext();
this.closeMiscFolderContext();
this.isNewFunctionModal = true; this.isNewFunctionModal = true;
}, },
hideCreateFunctionModal () { hideCreateFunctionModal () {
@@ -415,6 +436,7 @@ export default {
}, },
showCreateTriggerFunctionModal () { showCreateTriggerFunctionModal () {
this.closeDatabaseContext(); this.closeDatabaseContext();
this.closeMiscFolderContext();
this.isNewTriggerFunctionModal = true; this.isNewTriggerFunctionModal = true;
}, },
hideCreateTriggerFunctionModal () { hideCreateTriggerFunctionModal () {
@@ -422,6 +444,7 @@ export default {
}, },
showCreateSchedulerModal () { showCreateSchedulerModal () {
this.closeDatabaseContext(); this.closeDatabaseContext();
this.closeMiscFolderContext();
this.isNewSchedulerModal = true; this.isNewSchedulerModal = true;
}, },
hideCreateSchedulerModal () { hideCreateSchedulerModal () {

View File

@@ -0,0 +1,97 @@
<template>
<BaseContextMenu
:context-event="contextEvent"
@close-context="closeContext"
>
<div
v-if="selectedMisc === 'trigger'"
class="context-element"
@click="$emit('show-create-trigger-modal')"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-table-cog text-light pr-1" /> {{ $t('message.createNewTrigger') }}</span>
</div>
<div
v-if="selectedMisc === 'procedure'"
class="context-element"
@click="$emit('show-create-routine-modal')"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-sync-circle text-light pr-1" /> {{ $t('message.createNewRoutine') }}</span>
</div>
<div
v-if="selectedMisc === 'function'"
class="context-element"
@click="$emit('show-create-function-modal')"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-arrow-right-bold-box text-light pr-1" /> {{ $t('message.createNewFunction') }}</span>
</div>
<div
v-if="selectedMisc === 'triggerFunction'"
class="context-element"
@click="$emit('show-create-trigger-function-modal')"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-cog-clockwise text-light pr-1" /> {{ $t('message.createNewFunction') }}</span>
</div>
<div
v-if="selectedMisc === 'scheduler'"
class="context-element"
@click="$emit('show-create-scheduler-modal')"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-calendar-clock text-light pr-1" /> {{ $t('message.createNewScheduler') }}</span>
</div>
</BaseContextMenu>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import BaseContextMenu from '@/components/BaseContextMenu';
export default {
name: 'WorkspaceExploreBarMiscContext',
components: {
BaseContextMenu
},
props: {
contextEvent: MouseEvent,
selectedMisc: String
},
data () {
return {
localElement: {}
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.selectedWorkspace);
}
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
}),
showCreateTableModal () {
this.$emit('show-create-table-modal');
},
showDeleteModal () {
this.isDeleteModal = true;
},
hideDeleteModal () {
this.isDeleteModal = false;
},
showAskParamsModal () {
this.isAskingParameters = true;
},
hideAskParamsModal () {
this.isAskingParameters = false;
this.closeContext();
},
closeContext () {
this.$emit('close-context');
}
}
};
</script>

View File

@@ -39,7 +39,11 @@
<div v-if="filteredTriggers.length && customizations.triggers" class="database-misc"> <div v-if="filteredTriggers.length && customizations.triggers" class="database-misc">
<details class="accordion"> <details class="accordion">
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.trigger}"> <summary
class="accordion-header misc-name"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.trigger}"
@contextmenu.prevent="showMiscFolderContext($event, 'trigger')"
>
<i class="misc-icon mdi mdi-18px mdi-folder-cog mr-1" /> <i class="misc-icon mdi mdi-18px mdi-folder-cog mr-1" />
{{ $tc('word.trigger', 2) }} {{ $tc('word.trigger', 2) }}
</summary> </summary>
@@ -67,7 +71,11 @@
<div v-if="filteredProcedures.length && customizations.routines" class="database-misc"> <div v-if="filteredProcedures.length && customizations.routines" class="database-misc">
<details class="accordion"> <details class="accordion">
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.procedure}"> <summary
class="accordion-header misc-name"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.procedure}"
@contextmenu.prevent="showMiscFolderContext($event, 'procedure')"
>
<i class="misc-icon mdi mdi-18px mdi-folder-sync mr-1" /> <i class="misc-icon mdi mdi-18px mdi-folder-sync mr-1" />
{{ $tc('word.storedRoutine', 2) }} {{ $tc('word.storedRoutine', 2) }}
</summary> </summary>
@@ -95,7 +103,11 @@
<div v-if="filteredTriggerFunctions.length && customizations.triggerFunctions" class="database-misc"> <div v-if="filteredTriggerFunctions.length && customizations.triggerFunctions" class="database-misc">
<details class="accordion"> <details class="accordion">
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.triggerFunction}"> <summary
class="accordion-header misc-name"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.triggerFunction}"
@contextmenu.prevent="showMiscFolderContext($event, 'triggerFunction')"
>
<i class="misc-icon mdi mdi-18px mdi-folder-refresh mr-1" /> <i class="misc-icon mdi mdi-18px mdi-folder-refresh mr-1" />
{{ $tc('word.triggerFunction', 2) }} {{ $tc('word.triggerFunction', 2) }}
</summary> </summary>
@@ -123,7 +135,11 @@
<div v-if="filteredFunctions.length && customizations.functions" class="database-misc"> <div v-if="filteredFunctions.length && customizations.functions" class="database-misc">
<details class="accordion"> <details class="accordion">
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.function}"> <summary
class="accordion-header misc-name"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.function}"
@contextmenu.prevent="showMiscFolderContext($event, 'function')"
>
<i class="misc-icon mdi mdi-18px mdi-folder-move mr-1" /> <i class="misc-icon mdi mdi-18px mdi-folder-move mr-1" />
{{ $tc('word.function', 2) }} {{ $tc('word.function', 2) }}
</summary> </summary>
@@ -151,7 +167,11 @@
<div v-if="filteredSchedulers.length && customizations.schedulers" class="database-misc"> <div v-if="filteredSchedulers.length && customizations.schedulers" class="database-misc">
<details class="accordion"> <details class="accordion">
<summary class="accordion-header misc-name" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.scheduler}"> <summary
class="accordion-header misc-name"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.scheduler}"
@contextmenu.prevent="showMiscFolderContext($event, 'scheduler')"
>
<i class="misc-icon mdi mdi-18px mdi-folder-clock mr-1" /> <i class="misc-icon mdi mdi-18px mdi-folder-clock mr-1" />
{{ $tc('word.scheduler', 2) }} {{ $tc('word.scheduler', 2) }}
</summary> </summary>
@@ -251,7 +271,7 @@ export default {
}), }),
formatBytes, formatBytes,
async selectSchema (schema) { async selectSchema (schema) {
if (!this.loadedSchemas.has(schema)) { if (!this.loadedSchemas.has(schema) && !this.isLoading) {
this.isLoading = true; this.isLoading = true;
await this.refreshSchema({ uid: this.connection.uid, schema }); await this.refreshSchema({ uid: this.connection.uid, schema });
this.isLoading = false; this.isLoading = false;
@@ -271,6 +291,11 @@ export default {
this.setBreadcrumbs({ schema: this.database.name, [misc.type]: misc.name }); this.setBreadcrumbs({ schema: this.database.name, [misc.type]: misc.name });
this.$emit('show-misc-context', { event, misc }); this.$emit('show-misc-context', { event, misc });
}, },
showMiscFolderContext (event, type) {
this.selectSchema(this.database.name);
this.setBreadcrumbs({ schema: this.database.name, type });
this.$emit('show-misc-folder-context', { event, type });
},
piePercentage (val) { piePercentage (val) {
const perc = val / this.maxSize * 100; const perc = val / this.maxSize * 100;
if (this.applicationTheme === 'dark') if (this.applicationTheme === 'dark')

View File

@@ -19,8 +19,8 @@
<div class="panel-header pt-0 pl-0"> <div class="panel-header pt-0 pl-0">
<div class="d-flex"> <div class="d-flex">
<button class="btn btn-dark btn-sm d-flex" @click="addForeign"> <button class="btn btn-dark btn-sm d-flex" @click="addForeign">
<i class="mdi mdi-24px mdi-link-plus mr-1" />
<span>{{ $t('word.add') }}</span> <span>{{ $t('word.add') }}</span>
<i class="mdi mdi-24px mdi-link-plus ml-1" />
</button> </button>
<button <button
class="btn btn-dark btn-sm d-flex ml-2 mr-0" class="btn btn-dark btn-sm d-flex ml-2 mr-0"
@@ -28,8 +28,8 @@
:disabled="!isChanged" :disabled="!isChanged"
@click.prevent="clearChanges" @click.prevent="clearChanges"
> >
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button> </button>
</div> </div>
</div> </div>

View File

@@ -19,8 +19,8 @@
<div class="panel-header pt-0 pl-0"> <div class="panel-header pt-0 pl-0">
<div class="d-flex"> <div class="d-flex">
<button class="btn btn-dark btn-sm d-flex" @click="addParameter"> <button class="btn btn-dark btn-sm d-flex" @click="addParameter">
<i class="mdi mdi-24px mdi-plus mr-1" />
<span>{{ $t('word.add') }}</span> <span>{{ $t('word.add') }}</span>
<i class="mdi mdi-24px mdi-plus ml-1" />
</button> </button>
<button <button
class="btn btn-dark btn-sm d-flex ml-2 mr-0" class="btn btn-dark btn-sm d-flex ml-2 mr-0"
@@ -28,8 +28,8 @@
:disabled="!isChanged" :disabled="!isChanged"
@click.prevent="clearChanges" @click.prevent="clearChanges"
> >
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button> </button>
</div> </div>
</div> </div>

View File

@@ -19,8 +19,8 @@
<div class="panel-header pt-0 pl-0"> <div class="panel-header pt-0 pl-0">
<div class="d-flex"> <div class="d-flex">
<button class="btn btn-dark btn-sm d-flex" @click="addIndex"> <button class="btn btn-dark btn-sm d-flex" @click="addIndex">
<i class="mdi mdi-24px mdi-key-plus mr-1" />
<span>{{ $t('word.add') }}</span> <span>{{ $t('word.add') }}</span>
<i class="mdi mdi-24px mdi-key-plus ml-1" />
</button> </button>
<button <button
class="btn btn-dark btn-sm d-flex ml-2 mr-0" class="btn btn-dark btn-sm d-flex ml-2 mr-0"
@@ -28,8 +28,8 @@
:disabled="!isChanged" :disabled="!isChanged"
@click.prevent="clearChanges" @click.prevent="clearChanges"
> >
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button> </button>
</div> </div>
</div> </div>

View File

@@ -19,8 +19,8 @@
<div class="panel-header pt-0 pl-0"> <div class="panel-header pt-0 pl-0">
<div class="d-flex"> <div class="d-flex">
<button class="btn btn-dark btn-sm d-flex" @click="addParameter"> <button class="btn btn-dark btn-sm d-flex" @click="addParameter">
<i class="mdi mdi-24px mdi-plus mr-1" />
<span>{{ $t('word.add') }}</span> <span>{{ $t('word.add') }}</span>
<i class="mdi mdi-24px mdi-plus ml-1" />
</button> </button>
<button <button
class="btn btn-dark btn-sm d-flex ml-2 mr-0" class="btn btn-dark btn-sm d-flex ml-2 mr-0"
@@ -28,8 +28,8 @@
:disabled="!isChanged" :disabled="!isChanged"
@click.prevent="clearChanges" @click.prevent="clearChanges"
> >
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button> </button>
</div> </div>
</div> </div>

View File

@@ -10,8 +10,8 @@
title="CTRL+S" title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span> <span>{{ $t('word.save') }}</span>
<i class="mdi mdi-24px mdi-content-save ml-1" />
</button> </button>
<button <button
:disabled="!isChanged" :disabled="!isChanged"
@@ -19,8 +19,8 @@
:title="$t('message.clearChanges')" :title="$t('message.clearChanges')"
@click="clearChanges" @click="clearChanges"
> >
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button> </button>
<div class="divider-vert py-3" /> <div class="divider-vert py-3" />
@@ -30,24 +30,24 @@
:title="$t('message.addNewField')" :title="$t('message.addNewField')"
@click="addField" @click="addField"
> >
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
<span>{{ $t('word.add') }}</span> <span>{{ $t('word.add') }}</span>
<i class="mdi mdi-24px mdi-playlist-plus ml-1" />
</button> </button>
<button <button
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
:title="$t('message.manageIndexes')" :title="$t('message.manageIndexes')"
@click="showIntdexesModal" @click="showIntdexesModal"
> >
<i class="mdi mdi-24px mdi-key mdi-rotate-45 mr-1" />
<span>{{ $t('word.indexes') }}</span> <span>{{ $t('word.indexes') }}</span>
<i class="mdi mdi-24px mdi-key mdi-rotate-45 ml-1" />
</button> </button>
<button class="btn btn-dark btn-sm" @click="showForeignModal"> <button class="btn btn-dark btn-sm" @click="showForeignModal">
<i class="mdi mdi-24px mdi-key-link mr-1" />
<span>{{ $t('word.foreignKeys') }}</span> <span>{{ $t('word.foreignKeys') }}</span>
<i class="mdi mdi-24px mdi-key-link ml-1" />
</button> </button>
<button class="btn btn-dark btn-sm" @click="showOptionsModal"> <button class="btn btn-dark btn-sm" @click="showOptionsModal">
<i class="mdi mdi-24px mdi-cogs mr-1" />
<span>{{ $t('word.options') }}</span> <span>{{ $t('word.options') }}</span>
<i class="mdi mdi-24px mdi-cogs ml-1" />
</button> </button>
</div> </div>
</div> </div>

View File

@@ -10,8 +10,8 @@
title="CTRL+S" title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span> <span>{{ $t('word.save') }}</span>
<i class="mdi mdi-24px mdi-content-save ml-1" />
</button> </button>
<button <button
:disabled="!isChanged" :disabled="!isChanged"
@@ -19,8 +19,8 @@
:title="$t('message.clearChanges')" :title="$t('message.clearChanges')"
@click="clearChanges" @click="clearChanges"
> >
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button> </button>
<div class="divider-vert py-3" /> <div class="divider-vert py-3" />
@@ -30,16 +30,16 @@
:disabled="isChanged" :disabled="isChanged"
@click="runFunctionCheck" @click="runFunctionCheck"
> >
<i class="mdi mdi-24px mdi-play mr-1" />
<span>{{ $t('word.run') }}</span> <span>{{ $t('word.run') }}</span>
<i class="mdi mdi-24px mdi-play ml-1" />
</button> </button>
<button class="btn btn-dark btn-sm" @click="showParamsModal"> <button class="btn btn-dark btn-sm" @click="showParamsModal">
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
<span>{{ $t('word.parameters') }}</span> <span>{{ $t('word.parameters') }}</span>
<i class="mdi mdi-24px mdi-dots-horizontal ml-1" />
</button> </button>
<button class="btn btn-dark btn-sm" @click="showOptionsModal"> <button class="btn btn-dark btn-sm" @click="showOptionsModal">
<i class="mdi mdi-24px mdi-cogs mr-1" />
<span>{{ $t('word.options') }}</span> <span>{{ $t('word.options') }}</span>
<i class="mdi mdi-24px mdi-cogs ml-1" />
</button> </button>
</div> </div>
</div> </div>

View File

@@ -10,8 +10,8 @@
title="CTRL+S" title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span> <span>{{ $t('word.save') }}</span>
<i class="mdi mdi-24px mdi-content-save ml-1" />
</button> </button>
<button <button
:disabled="!isChanged" :disabled="!isChanged"
@@ -19,8 +19,8 @@
:title="$t('message.clearChanges')" :title="$t('message.clearChanges')"
@click="clearChanges" @click="clearChanges"
> >
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button> </button>
<div class="divider-vert py-3" /> <div class="divider-vert py-3" />
@@ -30,16 +30,16 @@
:disabled="isChanged" :disabled="isChanged"
@click="runRoutineCheck" @click="runRoutineCheck"
> >
<i class="mdi mdi-24px mdi-play mr-1" />
<span>{{ $t('word.run') }}</span> <span>{{ $t('word.run') }}</span>
<i class="mdi mdi-24px mdi-play ml-1" />
</button> </button>
<button class="btn btn-dark btn-sm" @click="showParamsModal"> <button class="btn btn-dark btn-sm" @click="showParamsModal">
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
<span>{{ $t('word.parameters') }}</span> <span>{{ $t('word.parameters') }}</span>
<i class="mdi mdi-24px mdi-dots-horizontal ml-1" />
</button> </button>
<button class="btn btn-dark btn-sm" @click="showOptionsModal"> <button class="btn btn-dark btn-sm" @click="showOptionsModal">
<i class="mdi mdi-24px mdi-cogs mr-1" />
<span>{{ $t('word.options') }}</span> <span>{{ $t('word.options') }}</span>
<i class="mdi mdi-24px mdi-cogs ml-1" />
</button> </button>
</div> </div>
</div> </div>

View File

@@ -10,8 +10,8 @@
title="CTRL+S" title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span> <span>{{ $t('word.save') }}</span>
<i class="mdi mdi-24px mdi-content-save ml-1" />
</button> </button>
<button <button
:disabled="!isChanged" :disabled="!isChanged"
@@ -19,14 +19,14 @@
:title="$t('message.clearChanges')" :title="$t('message.clearChanges')"
@click="clearChanges" @click="clearChanges"
> >
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button> </button>
<div class="divider-vert py-3" /> <div class="divider-vert py-3" />
<button class="btn btn-dark btn-sm" @click="showTimingModal"> <button class="btn btn-dark btn-sm" @click="showTimingModal">
<i class="mdi mdi-24px mdi-timer mr-1" />
<span>{{ $t('word.timing') }}</span> <span>{{ $t('word.timing') }}</span>
<i class="mdi mdi-24px mdi-timer ml-1" />
</button> </button>
</div> </div>
</div> </div>

View File

@@ -10,8 +10,8 @@
title="CTRL+S" title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span> <span>{{ $t('word.save') }}</span>
<i class="mdi mdi-24px mdi-content-save ml-1" />
</button> </button>
<button <button
:disabled="!isChanged" :disabled="!isChanged"
@@ -19,8 +19,8 @@
:title="$t('message.clearChanges')" :title="$t('message.clearChanges')"
@click="clearChanges" @click="clearChanges"
> >
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button> </button>
</div> </div>
</div> </div>

View File

@@ -10,8 +10,8 @@
title="CTRL+S" title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span> <span>{{ $t('word.save') }}</span>
<i class="mdi mdi-24px mdi-content-save ml-1" />
</button> </button>
<button <button
:disabled="!isChanged" :disabled="!isChanged"
@@ -19,15 +19,15 @@
:title="$t('message.clearChanges')" :title="$t('message.clearChanges')"
@click="clearChanges" @click="clearChanges"
> >
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button> </button>
<div class="divider-vert py-3" /> <div class="divider-vert py-3" />
<button class="btn btn-dark btn-sm" @click="showOptionsModal"> <button class="btn btn-dark btn-sm" @click="showOptionsModal">
<i class="mdi mdi-24px mdi-cogs mr-1" />
<span>{{ $t('word.options') }}</span> <span>{{ $t('word.options') }}</span>
<i class="mdi mdi-24px mdi-cogs ml-1" />
</button> </button>
</div> </div>
</div> </div>

View File

@@ -10,8 +10,8 @@
title="CTRL+S" title="CTRL+S"
@click="saveChanges" @click="saveChanges"
> >
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span> <span>{{ $t('word.save') }}</span>
<i class="mdi mdi-24px mdi-content-save ml-1" />
</button> </button>
<button <button
:disabled="!isChanged" :disabled="!isChanged"
@@ -19,8 +19,8 @@
:title="$t('message.clearChanges')" :title="$t('message.clearChanges')"
@click="clearChanges" @click="clearChanges"
> >
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
</button> </button>
</div> </div>
</div> </div>

View File

@@ -28,8 +28,8 @@
title="F5" title="F5"
@click="runQuery(query)" @click="runQuery(query)"
> >
<i class="mdi mdi-24px mdi-play pr-1" />
<span>{{ $t('word.run') }}</span> <span>{{ $t('word.run') }}</span>
<i class="mdi mdi-24px mdi-play" />
</button> </button>
<button <button
class="btn btn-dark btn-sm" class="btn btn-dark btn-sm"
@@ -37,8 +37,8 @@
title="CTRL+F8" title="CTRL+F8"
@click="beautify()" @click="beautify()"
> >
<i class="mdi mdi-24px mdi-brush pr-1" />
<span>{{ $t('word.format') }}</span> <span>{{ $t('word.format') }}</span>
<i class="mdi mdi-24px mdi-brush pl-1" />
</button> </button>
<button <button
class="btn btn-link btn-sm" class="btn btn-link btn-sm"
@@ -46,8 +46,8 @@
title="CTRL+W" title="CTRL+W"
@click="clear()" @click="clear()"
> >
<i class="mdi mdi-24px mdi-delete-sweep pr-1" />
<span>{{ $t('word.clear') }}</span> <span>{{ $t('word.clear') }}</span>
<i class="mdi mdi-24px mdi-delete-sweep pl-1" />
</button> </button>
<div class="divider-vert py-3" /> <div class="divider-vert py-3" />
@@ -58,8 +58,8 @@
class="btn btn-dark btn-sm dropdown-toggle mr-0 pr-0" class="btn btn-dark btn-sm dropdown-toggle mr-0 pr-0"
tabindex="0" tabindex="0"
> >
<i class="mdi mdi-24px mdi-file-export mr-1" />
<span>{{ $t('word.export') }}</span> <span>{{ $t('word.export') }}</span>
<i class="mdi mdi-24px mdi-file-export ml-1" />
<i class="mdi mdi-24px mdi-menu-down" /> <i class="mdi mdi-24px mdi-menu-down" />
</button> </button>
<ul class="menu text-left"> <ul class="menu text-left">

View File

@@ -11,9 +11,9 @@
title="F5" title="F5"
@click="reloadTable" @click="reloadTable"
> >
<i v-if="!+autorefreshTimer" class="mdi mdi-24px mdi-refresh mr-1" />
<i v-else class="mdi mdi-24px mdi-history mdi-flip-h mr-1" />
<span>{{ $t('word.refresh') }}</span> <span>{{ $t('word.refresh') }}</span>
<i v-if="!+autorefreshTimer" class="mdi mdi-24px mdi-refresh ml-1" />
<i v-else class="mdi mdi-24px mdi-history mdi-flip-h ml-1" />
</button> </button>
<div class="btn btn-dark btn-sm dropdown-toggle pl-0 pr-0" tabindex="0"> <div class="btn btn-dark btn-sm dropdown-toggle pl-0 pr-0" tabindex="0">
<i class="mdi mdi-24px mdi-menu-down" /> <i class="mdi mdi-24px mdi-menu-down" />
@@ -77,8 +77,8 @@
:disabled="isQuering" :disabled="isQuering"
@click="showFakerModal" @click="showFakerModal"
> >
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
<span>{{ $t('message.tableFiller') }}</span> <span>{{ $t('message.tableFiller') }}</span>
<i class="mdi mdi-24px mdi-playlist-plus ml-1" />
</button> </button>
<div class="dropdown table-dropdown pr-2"> <div class="dropdown table-dropdown pr-2">
@@ -87,8 +87,8 @@
class="btn btn-dark btn-sm dropdown-toggle mr-0 pr-0" class="btn btn-dark btn-sm dropdown-toggle mr-0 pr-0"
tabindex="0" tabindex="0"
> >
<i class="mdi mdi-24px mdi-file-export mr-1" />
<span>{{ $t('word.export') }}</span> <span>{{ $t('word.export') }}</span>
<i class="mdi mdi-24px mdi-file-export ml-1" />
<i class="mdi mdi-24px mdi-menu-down" /> <i class="mdi mdi-24px mdi-menu-down" />
</button> </button>
<ul class="menu text-left"> <ul class="menu text-left">

View File

@@ -28,6 +28,13 @@
> >
<a class="tab-link">{{ $t('word.ssl') }}</a> <a class="tab-link">{{ $t('word.ssl') }}</a>
</li> </li>
<li
class="tab-item"
:class="{'active': selectedTab === 'ssh'}"
@click="selectTab('ssh')"
>
<a class="c-hand">{{ $t('word.sshTunnel') }}</a>
</li>
</ul> </ul>
</div> </div>
<div v-if="selectedTab === 'general'" class="panel-body py-0"> <div v-if="selectedTab === 'general'" class="panel-body py-0">
@@ -208,7 +215,6 @@
/> />
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="col-4 col-sm-12"> <div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.ciphers') }}</label> <label class="form-label">{{ $t('word.ciphers') }}</label>
@@ -231,6 +237,95 @@
:status="toast.status" :status="toast.status"
/> />
</div> </div>
<div v-if="selectedTab === 'ssh'" class="panel-body py-0">
<div class="container">
<form class="form-horizontal">
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">
{{ $t('message.enableSsh') }}
</label>
</div>
<div class="col-8 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleSsh">
<input type="checkbox" :checked="localConnection.ssh">
<i class="form-icon" />
</label>
</div>
</div>
<fieldset class="m-0" :disabled="isTesting || !localConnection.ssh">
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.hostName') }}/IP</label>
</div>
<div class="col-8 col-sm-12">
<input
v-model="localConnection.sshHost"
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>
</div>
<div class="col-8 col-sm-12">
<input
v-model="localConnection.sshUser"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.password') }}</label>
</div>
<div class="col-8 col-sm-12">
<input
v-model="localConnection.sshPass"
class="form-input"
type="password"
>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.port') }}</label>
</div>
<div class="col-8 col-sm-12">
<input
v-model="localConnection.sshPort"
class="form-input"
type="number"
min="1"
max="65535"
>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.privateKey') }}</label>
</div>
<div class="col-8 col-sm-12">
<BaseUploadInput
:value="localConnection.sshKey"
:message="$t('word.browse')"
@clear="pathClear('sshKey')"
@change="pathSelection($event, 'sshKey')"
/>
</div>
</div>
</fieldset>
</form>
</div>
<BaseToast
class="mb-2"
:message="toast.message"
:status="toast.status"
/>
</div>
<div class="modal-footer text-light"> <div class="modal-footer text-light">
<button <button
class="btn btn-gray mr-2" class="btn btn-gray mr-2"
@@ -369,6 +464,9 @@ export default {
toggleSsl () { toggleSsl () {
this.localConnection.ssl = !this.localConnection.ssl; this.localConnection.ssl = !this.localConnection.ssl;
}, },
toggleSsh () {
this.localConnection.ssh = !this.localConnection.ssh;
},
pathSelection (event, name) { pathSelection (event, name) {
const { files } = event.target; const { files } = event.target;
if (!files.length) return; if (!files.length) return;

View File

@@ -29,6 +29,13 @@
> >
<a class="tab-link">{{ $t('word.ssl') }}</a> <a class="tab-link">{{ $t('word.ssl') }}</a>
</li> </li>
<li
class="tab-item"
:class="{'active': selectedTab === 'ssh'}"
@click="selectTab('ssh')"
>
<a class="c-hand">{{ $t('word.sshTunnel') }}</a>
</li>
</ul> </ul>
</div> </div>
<div v-if="selectedTab === 'general'" class="panel-body py-0"> <div v-if="selectedTab === 'general'" class="panel-body py-0">
@@ -213,7 +220,6 @@
/> />
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="col-4 col-sm-12"> <div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.ciphers') }}</label> <label class="form-label">{{ $t('word.ciphers') }}</label>
@@ -236,6 +242,95 @@
:status="toast.status" :status="toast.status"
/> />
</div> </div>
<div v-if="selectedTab === 'ssh'" class="panel-body py-0">
<div class="container">
<form class="form-horizontal">
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">
{{ $t('message.enableSsh') }}
</label>
</div>
<div class="col-8 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleSsh">
<input type="checkbox" :checked="connection.ssh">
<i class="form-icon" />
</label>
</div>
</div>
<fieldset class="m-0" :disabled="isTesting || !connection.ssh">
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.hostName') }}/IP</label>
</div>
<div class="col-8 col-sm-12">
<input
v-model="connection.sshHost"
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>
</div>
<div class="col-8 col-sm-12">
<input
v-model="connection.sshUser"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.password') }}</label>
</div>
<div class="col-8 col-sm-12">
<input
v-model="connection.sshPass"
class="form-input"
type="password"
>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.port') }}</label>
</div>
<div class="col-8 col-sm-12">
<input
v-model="connection.sshPort"
class="form-input"
type="number"
min="1"
max="65535"
>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.privateKey') }}</label>
</div>
<div class="col-8 col-sm-12">
<BaseUploadInput
:value="connection.sshKey"
:message="$t('word.browse')"
@clear="pathClear('sshKey')"
@change="pathSelection($event, 'sshKey')"
/>
</div>
</div>
</fieldset>
</form>
</div>
<BaseToast
class="mb-2"
:message="toast.message"
:status="toast.status"
/>
</div>
</div> </div>
</div> </div>
<div class="modal-footer text-light"> <div class="modal-footer text-light">
@@ -294,8 +389,13 @@ export default {
cert: '', cert: '',
key: '', key: '',
ca: '', ca: '',
ciphers: '' ciphers: '',
ssh: false,
sshHost: '',
sshUser: '',
sshPass: '',
sshKey: '',
sshPort: 22
}, },
toast: { toast: {
status: '', status: '',
@@ -393,6 +493,9 @@ export default {
toggleSsl () { toggleSsl () {
this.connection.ssl = !this.connection.ssl; this.connection.ssl = !this.connection.ssl;
}, },
toggleSsh () {
this.connection.ssh = !this.connection.ssh;
},
pathSelection (event, name) { pathSelection (event, name) {
const { files } = event.target; const { files } = event.target;
if (!files.length) return; if (!files.length) return;

View File

@@ -104,7 +104,8 @@ module.exports = {
database: 'Datenbank', database: 'Datenbank',
scratchpad: 'Scratchpad', scratchpad: 'Scratchpad',
array: 'Array', array: 'Array',
format: 'Formatierung' format: 'Formatierung',
sshTunnel: 'SSH Tunnel'
}, },
message: { message: {
appWelcome: 'Willkommen im Antares SQL Client!', appWelcome: 'Willkommen im Antares SQL Client!',
@@ -210,7 +211,8 @@ module.exports = {
deleteSchema: 'Schema löschen', deleteSchema: 'Schema löschen',
markdownSupported: 'Unterstützt Markdown', markdownSupported: 'Unterstützt Markdown',
plantATree: 'Pflanze einen Baum', plantATree: 'Pflanze einen Baum',
dataTabPageSize: 'Einträge pro Tab / Seite' dataTabPageSize: 'Einträge pro Tab / Seite',
enableSsh: 'Aktiviere SSH'
}, },
faker: { faker: {
address: 'Adresse', address: 'Adresse',

View File

@@ -106,13 +106,16 @@ module.exports = {
array: 'Array', array: 'Array',
changelog: 'Changelog', changelog: 'Changelog',
format: 'Format', format: 'Format',
sshTunnel: 'SSH tunnel',
structure: 'Structure', structure: 'Structure',
small: 'Small', small: 'Small',
medium: 'Medium', medium: 'Medium',
large: 'Large', large: 'Large',
row: 'Row | Rows', row: 'Row | Rows',
cell: 'Cell | Cells', cell: 'Cell | Cells',
triggerFunction: 'Trigger function | Trigger functions' triggerFunction: 'Trigger function | Trigger functions',
all: 'All',
duplicate: 'Duplicate'
}, },
message: { message: {
appWelcome: 'Welcome to Antares SQL Client!', appWelcome: 'Welcome to Antares SQL Client!',
@@ -219,6 +222,7 @@ module.exports = {
markdownSupported: 'Markdown supported', markdownSupported: 'Markdown supported',
plantATree: 'Plant a Tree', plantATree: 'Plant a Tree',
dataTabPageSize: 'DATA tab page size', dataTabPageSize: 'DATA tab page size',
enableSsh: 'Enable SSH',
pageNumber: 'Page number', pageNumber: 'Page number',
duplicateTable: 'Duplicate table' duplicateTable: 'Duplicate table'
}, },

View File

@@ -93,7 +93,8 @@ module.exports = {
ciphers: 'Chiffrement', ciphers: 'Chiffrement',
upload: 'Charger', upload: 'Charger',
browse: 'Parcourir', browse: 'Parcourir',
faker: 'Faker' faker: 'Faker',
sshTunnel: 'SSH tunnel'
}, },
message: { message: {
appWelcome: 'Bienvenu sur le client SQL Antares!', appWelcome: 'Bienvenu sur le client SQL Antares!',
@@ -183,7 +184,8 @@ module.exports = {
preserveOnCompletion: 'Préserver à l\'achèvement', preserveOnCompletion: 'Préserver à l\'achèvement',
enableSsl: 'Activer le SSL', enableSsl: 'Activer le SSL',
manualValue: 'Valeur manuelle', manualValue: 'Valeur manuelle',
tableFiller: 'Remplisseur de table' tableFiller: 'Remplisseur de table',
enableSsh: 'Activer le SSH'
}, },
faker: { faker: {
address: 'Adresse', address: 'Adresse',

View File

@@ -105,7 +105,8 @@ module.exports = {
scratchpad: 'Blocco appunti', scratchpad: 'Blocco appunti',
array: 'Array', array: 'Array',
changelog: 'Changelog', changelog: 'Changelog',
format: 'Formatta' format: 'Formatta',
sshTunnel: 'SSH tunnel'
}, },
message: { message: {
appWelcome: 'Benvenuto in Antares SQL Client!', appWelcome: 'Benvenuto in Antares SQL Client!',
@@ -210,7 +211,8 @@ module.exports = {
editSchema: 'Modifica schema', editSchema: 'Modifica schema',
deleteSchema: 'Elimina schema', deleteSchema: 'Elimina schema',
markdownSupported: 'Markdown supportato', markdownSupported: 'Markdown supportato',
plantATree: 'Pianta un albero' plantATree: 'Pianta un albero',
enableSsh: 'Abilita SSH'
}, },
faker: { faker: {
address: 'Indirizzo', address: 'Indirizzo',

View File

@@ -105,7 +105,8 @@ module.exports = {
scratchpad: 'Rascunho', scratchpad: 'Rascunho',
array: 'Array', array: 'Array',
changelog: 'Logs de alteração', changelog: 'Logs de alteração',
format: 'Formato' format: 'Formato',
sshTunnel: 'SSH túnel'
}, },
message: { message: {
appWelcome: 'Bem vindo ao Antares SQL Client!', appWelcome: 'Bem vindo ao Antares SQL Client!',
@@ -210,7 +211,8 @@ module.exports = {
editSchema: 'Editar schema', editSchema: 'Editar schema',
deleteSchema: 'Apagar schema', deleteSchema: 'Apagar schema',
markdownSupported: 'Markdown suportado', markdownSupported: 'Markdown suportado',
plantATree: 'Plante uma árvore' plantATree: 'Plante uma árvore',
enableSsh: 'Habilitar SSH'
}, },
faker: { faker: {
address: 'Endereço', address: 'Endereço',

View File

@@ -214,9 +214,17 @@
} }
} }
.connection-panel {
.panel {
background: rgba($bg-color-light-dark, 50%);
}
}
.bg-checkered { .bg-checkered {
background-image: linear-gradient(to right, rgba(192, 192, 192, 0.75), rgba(192, 192, 192, 0.75)), background-image:
linear-gradient(to right, black 50%, white 50%), linear-gradient(to bottom, black 50%, white 50%); 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-blend-mode: normal, difference, normal;
background-size: 2em 2em; background-size: 2em 2em;
} }

View File

@@ -205,6 +205,12 @@
} }
} }
.connection-panel {
.panel {
background: rgba($bg-color-light-gray, 100%);
}
}
.context { .context {
color: $body-font-color-dark; color: $body-font-color-dark;

View File

@@ -10,7 +10,8 @@ else
const persistentStore = new Store({ const persistentStore = new Store({
name: 'connections', name: 'connections',
encryptionKey: key encryptionKey: key,
clearInvalidConfig: true
}); });
export default { export default {
@@ -23,6 +24,7 @@ export default {
getConnections: state => state.connections, getConnections: state => state.connections,
getConnectionName: state => uid => { getConnectionName: state => uid => {
const connection = state.connections.filter(connection => connection.uid === uid)[0]; const connection = state.connections.filter(connection => connection.uid === uid)[0];
if (!connection) return '';
return connection.name return connection.name
? connection.name ? connection.name
: connection.ask : connection.ask

View File

@@ -20,7 +20,7 @@ export default {
getSelected: state => { getSelected: state => {
if (state.selected_workspace) return state.selected_workspace; if (state.selected_workspace) return state.selected_workspace;
if (state.workspaces.length) return state.workspaces[0].uid; if (state.workspaces.length) return state.workspaces[0].uid;
return null; return 'NEW';
}, },
getWorkspace: state => uid => { getWorkspace: state => uid => {
return state.workspaces.find(workspace => workspace.uid === uid); return state.workspaces.find(workspace => workspace.uid === uid);
@@ -52,7 +52,10 @@ export default {
}, },
mutations: { mutations: {
SELECT_WORKSPACE (state, uid) { SELECT_WORKSPACE (state, uid) {
state.selected_workspace = uid; if (!uid)
state.selected_workspace = state.workspaces.length ? state.workspaces[0].uid : 'NEW';
else
state.selected_workspace = uid;
}, },
SET_CONNECTED (state, payload) { SET_CONNECTED (state, payload) {
const { uid, client, dataTypes, indexTypes, customizations, structure, version } = payload; const { uid, client, dataTypes, indexTypes, customizations, structure, version } = payload;