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

Compare commits

..

17 Commits

Author SHA1 Message Date
2918c3cb92 chore(release): 0.3.3 2021-08-22 10:41:48 +02:00
3ad190b18c Merge branch 'master' of https://github.com/Fabio286/antares 2021-08-22 10:37:27 +02:00
d5b2bde2ea fix(UI): no round borders on left of file upload inputs 2021-08-22 10:37:23 +02:00
a42348ef5c chore: deletion of wrong file name case component 2021-08-20 13:42:29 +02:00
8b93c49778 feat: new create view tabs 2021-08-18 17:28:41 +02:00
0842e00098 feat: new table empty state 2021-08-17 18:54:23 +02:00
6cef02bebb Merge pull request #93 from Fabio286/dependabot/npm_and_yarn/marked-3.0.0
build(deps): bump marked from 2.1.3 to 3.0.0
2021-08-17 12:04:22 +02:00
dependabot[bot]
334c7a31d2 build(deps): bump marked from 2.1.3 to 3.0.0
Bumps [marked](https://github.com/markedjs/marked) from 2.1.3 to 3.0.0.
- [Release notes](https://github.com/markedjs/marked/releases)
- [Changelog](https://github.com/markedjs/marked/blob/master/release.config.js)
- [Commits](https://github.com/markedjs/marked/compare/v2.1.3...v3.0.0)

---
updated-dependencies:
- dependency-name: marked
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-16 19:05:09 +00:00
bc82289d54 perf(UI): visual improvements of tables 2021-08-16 19:44:55 +02:00
c9fa941578 feat: new create table tabs 2021-08-13 16:50:59 +02:00
4048df3c7b fix(MySQL): editing a view causes error for missing database in some conditions 2021-08-12 10:29:13 +02:00
198368605b perf(UI): improved view setting tab 2021-08-12 10:07:31 +02:00
8f0ac26b69 refactor: better component names 2021-08-12 09:54:13 +02:00
b35fc5b78b fix(UI): multiple temp tabs opened switching to tables from other elements 2021-08-11 18:04:14 +02:00
622b519cbb fix: table options not loaded on restored setting tabs at startup 2021-08-11 16:16:58 +02:00
71e2c911ae perf(UI): element options in setting tabs accessible directly 2021-08-10 18:12:13 +02:00
756d49b259 perf(UI): primary app color on selected text backgrouns 2021-08-10 13:14:48 +02:00
61 changed files with 1791 additions and 1481 deletions

2
.gitignore vendored
View File

@@ -4,6 +4,6 @@ node_modules/
thumbs.db
.idea/
.vscode
TODO.md
NOTES.md
*.txt
package-lock.json

View File

@@ -2,6 +2,31 @@
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.3.3](https://github.com/Fabio286/antares/compare/v0.3.2...v0.3.3) (2021-08-22)
### Features
* new create table tabs ([c9fa941](https://github.com/Fabio286/antares/commit/c9fa9415787c3953043db5876a99b3664c69c071))
* new create view tabs ([8b93c49](https://github.com/Fabio286/antares/commit/8b93c497784ea431f9747c5afb53f6ef075ea9d6))
* new table empty state ([0842e00](https://github.com/Fabio286/antares/commit/0842e00098ba420412937aa52276ee33bda53693))
### Bug Fixes
* **MySQL:** editing a view causes error for missing database in some conditions ([4048df3](https://github.com/Fabio286/antares/commit/4048df3c7bc2d42a60f7a57c9a4b8b5b445fcd43))
* table options not loaded on restored setting tabs at startup ([622b519](https://github.com/Fabio286/antares/commit/622b519cbb5fbe4e38a4baffb8eab169b21eed21))
* **UI:** multiple temp tabs opened switching to tables from other elements ([b35fc5b](https://github.com/Fabio286/antares/commit/b35fc5b78bdbeff1422ef088441b17c8b0df663c))
* **UI:** no round borders on left of file upload inputs ([d5b2bde](https://github.com/Fabio286/antares/commit/d5b2bde2eaf8ff3e14f49cc26acdcb201b4cb15c))
### Improvements
* **UI:** element options in setting tabs accessible directly ([71e2c91](https://github.com/Fabio286/antares/commit/71e2c911ae23e86543b2af1fa885981ff271777d))
* **UI:** improved view setting tab ([1983686](https://github.com/Fabio286/antares/commit/198368605b084bd58fd6f7ca0b19895ba23a45e6))
* **UI:** primary app color on selected text backgrouns ([756d49b](https://github.com/Fabio286/antares/commit/756d49b2596dea58376c6afa8e0bad0cd62b146c))
* **UI:** visual improvements of tables ([bc82289](https://github.com/Fabio286/antares/commit/bc82289d54550a93300fe66d7a660aa70db2fd23))
### [0.3.2](https://github.com/Fabio286/antares/compare/v0.3.1...v0.3.2) (2021-08-06)

View File

@@ -4,7 +4,7 @@
# Antares SQL Client
![GitHub package.json version](https://img.shields.io/github/package-json/v/fabio286/antares) [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Ffabio286%2Fantares%2Fbadge&style=flat)](https://actions-badge.atrox.dev/fabio286/antares/goto) ![GitHub All Releases](https://img.shields.io/github/downloads/fabio286/antares/total) ![GitHub](https://img.shields.io/github/license/fabio286/antares) [![antares](https://snapcraft.io/antares/badge.svg)](https://snapcraft.io/antares) [![antares](https://snapcraft.io/antares/trending.svg?name=0)](https://snapcraft.io/antares) [![Twitter Follow](https://img.shields.io/twitter/follow/AntaresSQL?style=social)](https://twitter.com/AntaresSQL) [![Plant a Tree](https://raw.githubusercontent.com/Fabio286/treedom-badge/master/svg/plant-a-tree.svg)](https://www.treedom.net/en/user/fabio-di-stasio/event/antares-for-the-planet)
![GitHub package.json version](https://img.shields.io/github/package-json/v/fabio286/antares) ![GitHub](https://img.shields.io/github/license/fabio286/antares) [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Ffabio286%2Fantares%2Fbadge&style=flat)](https://actions-badge.atrox.dev/fabio286/antares/goto) [![antares](https://snapcraft.io/antares/badge.svg)](https://snapcraft.io/antares) [![antares](https://snapcraft.io/antares/trending.svg?name=0)](https://snapcraft.io/antares) [![Twitter Follow](https://img.shields.io/twitter/follow/AntaresSQL?style=social)](https://twitter.com/AntaresSQL) [![Plant a Tree](https://raw.githubusercontent.com/Fabio286/treedom-badge/master/svg/plant-a-tree.svg)](https://www.treedom.net/en/user/fabio-di-stasio/event/antares-for-the-planet)
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.
Our target is to support as many databases as possible, and all major operating systems, including the ARM versions.

View File

@@ -1,7 +1,7 @@
{
"name": "antares",
"productName": "Antares",
"version": "0.3.2",
"version": "0.3.3",
"description": "A cross-platform easy to use SQL client.",
"license": "MIT",
"repository": "https://github.com/Fabio286/antares.git",
@@ -94,7 +94,7 @@
"electron-store": "^8.0.0",
"electron-updater": "^4.3.9",
"faker": "^5.5.3",
"marked": "^2.1.1",
"marked": "^3.0.0",
"moment": "^2.29.1",
"mysql2": "^2.3.0",
"pg": "^8.7.1",

View File

@@ -44,6 +44,7 @@ module.exports = {
unsigned: false,
nullable: false,
zerofill: false,
tableOptions: false,
autoIncrement: false,
comment: false,
collation: false,

View File

@@ -40,6 +40,7 @@ module.exports = {
unsigned: true,
nullable: true,
zerofill: true,
tableOptions: true,
autoIncrement: true,
comment: true,
collation: true,

View File

@@ -48,6 +48,16 @@ export default (connections) => {
}
});
ipcMain.handle('get-table-options', async (event, params) => {
try {
const result = await connections[params.uid].getTableOptions(params);
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('get-table-indexes', async (event, params) => {
try {
const result = await connections[params.uid].getTableIndexes(params);

View File

@@ -9,7 +9,7 @@ autoUpdater.allowPrerelease = persistentStore.get('allow_prerelease', true);
export default () => {
ipcMain.on('check-for-updates', event => {
mainWindow = event;
if (process.windowsStore)
if (process.windowsStore || (process.platform === 'linux' && !process.env.APPIMAGE))
mainWindow.reply('no-auto-update');
else {
autoUpdater.checkForUpdatesAndNotify().catch(() => {

View File

@@ -2,6 +2,12 @@
import { MySQLClient } from './clients/MySQLClient';
import { PostgreSQLClient } from './clients/PostgreSQLClient';
const queryLogger = sql => {
// Remove comments, newlines and multiple spaces
const escapedSql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' ');
console.log(escapedSql);
};
export class ClientsFactory {
/**
* Returns a database connection based on received args.
@@ -23,6 +29,8 @@ export class ClientsFactory {
* @memberof ClientsFactory
*/
static getConnection (args) {
args.logger = queryLogger;
switch (args.client) {
case 'mysql':
case 'maria':

View File

@@ -437,6 +437,43 @@ export class MySQLClient extends AntaresCore {
return rows.length ? rows[0].count : 0;
}
/**
* @param {Object} params
* @param {String} params.schema
* @param {String} params.table
* @returns {Object} table options
* @memberof MySQLClient
*/
async getTableOptions ({ schema, table }) {
const { rows } = await this.raw(`SHOW TABLE STATUS FROM \`${schema}\` WHERE Name = '${table}'`);
if (rows.length) {
let tableType;
switch (rows[0].Comment) {
case 'VIEW':
tableType = 'view';
break;
default:
tableType = 'table';
break;
}
return {
name: rows[0].Name,
type: tableType,
rows: rows[0].Rows,
created: rows[0].Create_time,
updated: rows[0].Update_time,
engine: rows[0].Engine,
comment: rows[0].Comment,
size: rows[0].Data_length + rows[0].Index_length,
autoIncrement: rows[0].Auto_increment,
collation: rows[0].Collation
};
};
return {};
}
/**
* @param {Object} params
* @param {String} params.schema
@@ -598,7 +635,12 @@ export class MySQLClient extends AntaresCore {
*/
async alterView (params) {
const { view } = params;
let sql = `ALTER ALGORITHM = ${view.algorithm}${view.definer ? ` DEFINER=${view.definer}` : ''} SQL SECURITY ${view.security} VIEW \`${view.schema}\`.\`${view.oldName}\` AS ${view.sql} ${view.updateOption ? `WITH ${view.updateOption} CHECK OPTION` : ''}`;
let sql = `
USE \`${view.schema}\`;
ALTER ALGORITHM = ${view.algorithm}${view.definer ? ` DEFINER=${view.definer}` : ''}
SQL SECURITY ${view.security}
VIEW \`${view.schema}\`.\`${view.oldName}\` AS ${view.sql} ${view.updateOption ? `WITH ${view.updateOption} CHECK OPTION` : ''}
`;
if (view.name !== view.oldName)
sql += `; RENAME TABLE \`${view.schema}\`.\`${view.oldName}\` TO \`${view.schema}\`.\`${view.name}\``;
@@ -1139,7 +1181,57 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient
*/
async createTable (params) {
const sql = `CREATE TABLE \`${params.schema}\`.\`${params.name}\` (\`${params.name}_ID\` INT NULL) COMMENT='${params.comment}', COLLATE='${params.collation}', ENGINE=${params.engine}`;
const {
schema,
fields,
foreigns,
indexes,
options
} = params;
const newColumns = [];
const newIndexes = [];
const newForeigns = [];
let sql = `CREATE TABLE \`${schema}\`.\`${options.name}\``;
// ADD FIELDS
fields.forEach(field => {
const typeInfo = this._getTypeInfo(field.type);
const length = typeInfo.length ? field.enumValues || field.numLength || field.charLength || field.datePrecision : false;
newColumns.push(`\`${field.name}\`
${field.type.toUpperCase()}${length ? `(${length})` : ''}
${field.unsigned ? 'UNSIGNED' : ''}
${field.zerofill ? 'ZEROFILL' : ''}
${field.nullable ? 'NULL' : 'NOT NULL'}
${field.autoIncrement ? 'AUTO_INCREMENT' : ''}
${field.default ? `DEFAULT ${field.default}` : ''}
${field.comment ? `COMMENT '${field.comment}'` : ''}
${field.collation ? `COLLATE ${field.collation}` : ''}
${field.onUpdate ? `ON UPDATE ${field.onUpdate}` : ''}`);
});
// ADD INDEX
indexes.forEach(index => {
const fields = index.fields.map(field => `\`${field}\``).join(',');
let type = index.type;
if (type === 'PRIMARY')
newIndexes.push(`PRIMARY KEY (${fields})`);
else {
if (type === 'UNIQUE')
type = 'UNIQUE INDEX';
newIndexes.push(`${type} \`${index.name}\` (${fields})`);
}
});
// ADD FOREIGN KEYS
foreigns.forEach(foreign => {
newForeigns.push(`CONSTRAINT \`${foreign.constraintName}\` FOREIGN KEY (\`${foreign.field}\`) REFERENCES \`${foreign.refTable}\` (\`${foreign.refField}\`) ON UPDATE ${foreign.onUpdate} ON DELETE ${foreign.onDelete}`);
});
sql = `${sql} (${[...newColumns, ...newIndexes, ...newForeigns].join(', ')}) COMMENT='${options.comment}', COLLATE='${options.collation}', ENGINE=${options.engine}`;
return await this.raw(sql);
}

View File

@@ -94,7 +94,7 @@ export class PostgreSQLClient extends AntaresCore {
}
/**
* Executes an USE query
* Executes an "USE" query
*
* @param {String} schema
* @memberof PostgreSQLClient
@@ -306,6 +306,40 @@ export class PostgreSQLClient extends AntaresCore {
return rows.length ? rows[0].count : 0;
}
/**
* @param {Object} params
* @param {String} params.schema
* @param {String} params.table
* @returns {Object} table options
* @memberof MySQLClient
*/
async getTableOptions ({ schema, table }) {
const { rows } = await this.raw(`
SELECT *,
pg_table_size(QUOTE_IDENT(t.TABLE_SCHEMA) || '.' || QUOTE_IDENT(t.TABLE_NAME))::bigint AS data_length,
pg_relation_size(QUOTE_IDENT(t.TABLE_SCHEMA) || '.' || QUOTE_IDENT(t.TABLE_NAME))::bigint AS index_length,
c.reltuples, obj_description(c.oid) AS comment
FROM "information_schema"."tables" AS t
LEFT JOIN "pg_namespace" n ON t.table_schema = n.nspname
LEFT JOIN "pg_class" c ON n.oid = c.relnamespace AND c.relname=t.table_name
WHERE t."table_schema" = '${schema}'
AND table_name = '${table}'
`);
if (rows.length) {
return {
name: rows[0].table_name,
type: rows[0].table_type === 'VIEW' ? 'view' : 'table',
rows: rows[0].reltuples,
size: +rows[0].data_length + +rows[0].index_length,
collation: rows[0].Collation,
comment: rows[0].comment,
engine: ''
};
};
return {};
}
/**
* @param {Object} params
* @param {String} params.schema
@@ -1002,8 +1036,54 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async createTable (params) {
const sql = `CREATE TABLE "${params.schema}"."${params.name}" (${params.name}_id INTEGER NULL); ALTER TABLE "${params.schema}"."${params.name}" DROP COLUMN ${params.name}_id`;
const {
schema,
fields,
foreigns,
indexes,
options
} = params;
const newColumns = [];
const newIndexes = [];
const manageIndexes = [];
const newForeigns = [];
let sql = `CREATE TABLE "${schema}"."${options.name}"`;
// ADD FIELDS
fields.forEach(field => {
const typeInfo = this._getTypeInfo(field.type);
const length = typeInfo.length ? field.enumValues || field.numLength || field.charLength || field.datePrecision : false;
newColumns.push(`${field.name}
${field.type.toUpperCase()}${length ? `(${length})` : ''}
${field.unsigned ? 'UNSIGNED' : ''}
${field.zerofill ? 'ZEROFILL' : ''}
${field.nullable ? 'NULL' : 'NOT NULL'}
${field.default ? `DEFAULT ${field.default}` : ''}
${field.onUpdate ? `ON UPDATE ${field.onUpdate}` : ''}`);
});
// ADD INDEX
indexes.forEach(index => {
const fields = index.fields.map(field => `${field}`).join(',');
const type = index.type;
if (type === 'PRIMARY')
newIndexes.push(`PRIMARY KEY (${fields})`);
else if (type === 'UNIQUE')
newIndexes.push(`CONSTRAINT ${index.name} UNIQUE (${fields})`);
else
manageIndexes.push(`CREATE INDEX ${index.name} ON "${schema}"."${options.name}" (${fields})`);
});
// ADD FOREIGN KEYS
foreigns.forEach(foreign => {
newForeigns.push(`CONSTRAINT ${foreign.constraintName} FOREIGN KEY (${foreign.field}) REFERENCES "${schema}"."${foreign.refTable}" (${foreign.refField}) ON UPDATE ${foreign.onUpdate} ON DELETE ${foreign.onDelete}`);
});
sql = `${sql} (${[...newColumns, ...newIndexes, ...newForeigns].join(', ')})`;
if (manageIndexes.length) sql = `${sql}; ${manageIndexes.join(';')}`;
return await this.raw(sql);
}
@@ -1034,12 +1114,6 @@ export class PostgreSQLClient extends AntaresCore {
const createSequences = [];
const manageIndexes = [];
// OPTIONS
if ('comment' in options) alterColumns.push(`COMMENT='${options.comment}'`);
if ('engine' in options) alterColumns.push(`ENGINE=${options.engine}`);
if ('autoIncrement' in options) alterColumns.push(`AUTO_INCREMENT=${+options.autoIncrement}`);
if ('collation' in options) alterColumns.push(`COLLATE='${options.collation}'`);
// ADD FIELDS
additions.forEach(addition => {
const typeInfo = this._getTypeInfo(addition.type);
@@ -1050,10 +1124,7 @@ export class PostgreSQLClient extends AntaresCore {
${addition.unsigned ? 'UNSIGNED' : ''}
${addition.zerofill ? 'ZEROFILL' : ''}
${addition.nullable ? 'NULL' : 'NOT NULL'}
${addition.autoIncrement ? 'AUTO_INCREMENT' : ''}
${addition.default ? `DEFAULT ${addition.default}` : ''}
${addition.comment ? `COMMENT '${addition.comment}'` : ''}
${addition.collation ? `COLLATE ${addition.collation}` : ''}
${addition.onUpdate ? `ON UPDATE ${addition.onUpdate}` : ''}`);
});
@@ -1072,7 +1143,7 @@ export class PostgreSQLClient extends AntaresCore {
// ADD FOREIGN KEYS
foreignChanges.additions.forEach(addition => {
alterColumns.push(`ADD CONSTRAINT ${addition.constraintName} FOREIGN KEY (${addition.field}) REFERENCES ${addition.refTable} (${addition.refField}) ON UPDATE ${addition.onUpdate} ON DELETE ${addition.onDelete}`);
alterColumns.push(`ADD CONSTRAINT ${addition.constraintName} FOREIGN KEY (${addition.field}) REFERENCES "${schema}"."${addition.refTable}" (${addition.refField}) ON UPDATE ${addition.onUpdate} ON DELETE ${addition.onDelete}`);
});
// CHANGE FIELDS
@@ -1129,7 +1200,7 @@ export class PostgreSQLClient extends AntaresCore {
// CHANGE FOREIGN KEYS
foreignChanges.changes.forEach(change => {
alterColumns.push(`DROP CONSTRAINT ${change.oldName}`);
alterColumns.push(`ADD CONSTRAINT ${change.constraintName} FOREIGN KEY (${change.field}) REFERENCES ${change.refTable} (${change.refField}) ON UPDATE ${change.onUpdate} ON DELETE ${change.onDelete}`);
alterColumns.push(`ADD CONSTRAINT ${change.constraintName} FOREIGN KEY (${change.field}) REFERENCES "${schema}"."${change.refTable}" (${change.refField}) ON UPDATE ${change.onUpdate} ON DELETE ${change.onDelete}`);
});
// DROP FIELDS

View File

@@ -78,6 +78,7 @@ export default {
.file-uploader-message {
display: flex;
border-radius: $border-radius 0 0 $border-radius;
}
.file-uploader-input {

View File

@@ -31,7 +31,11 @@
class="form-input"
type="text"
>
<span class="input-group-addon field-type" :class="typeClass(parameter.type)">
<span
:title="`${parameter.type} ${parameter.length}`"
class="input-group-addon field-type cut-text"
:class="typeClass(parameter.type)"
>
{{ parameter.type }} {{ parameter.length | wrapNumber }}
</span>
</div>
@@ -127,4 +131,8 @@ export default {
.field-type {
font-size: 0.6rem;
}
.input-group-addon {
max-width: 100px;
}
</style>

View File

@@ -251,7 +251,7 @@ export default {
if (field.default === 'NULL') fieldDefault = null;
else {
if ([...NUMBER, ...FLOAT].includes(field.type))
fieldDefault = Number.isNaN(+field.default) ? null : +field.default;
fieldDefault = !field.default || Number.isNaN(+field.default.replaceAll('\'', '')) ? null : +field.default.replaceAll('\'', '');
else if ([...TEXT, ...LONG_TEXT].includes(field.type)) {
fieldDefault = field.default
? field.default.includes('\'')

View File

@@ -1,130 +0,0 @@
<template>
<ConfirmModal
:confirm-text="$t('word.confirm')"
size="400"
@confirm="confirmOptionsChange"
@hide="$emit('close')"
>
<template :slot="'header'">
<div class="d-flex">
<i class="mdi mdi-24px mdi-table-plus mr-1" /> {{ $t('message.createNewTable') }}
</div>
</template>
<div :slot="'body'">
<form class="form-horizontal">
<div class="form-group">
<label class="form-label col-4">
{{ $t('word.name') }}
</label>
<div class="column">
<input
ref="firstInput"
v-model="localOptions.name"
class="form-input"
type="text"
>
</div>
</div>
<div v-if="workspace.customizations.comment" class="form-group">
<label class="form-label col-4">
{{ $t('word.comment') }}
</label>
<div class="column">
<input
v-model="localOptions.comment"
class="form-input"
type="text"
>
</div>
</div>
<div v-if="workspace.customizations.collations" class="form-group">
<label class="form-label col-4">
{{ $t('word.collation') }}
</label>
<div class="column">
<select v-model="localOptions.collation" class="form-select">
<option
v-for="collation in workspace.collations"
:key="collation.id"
:value="collation.collation"
>
{{ collation.collation }}
</option>
</select>
</div>
</div>
<div v-if="workspace.customizations.engines" class="form-group">
<label class="form-label col-4">
{{ $t('word.engine') }}
</label>
<div class="column">
<select v-model="localOptions.engine" class="form-select">
<option
v-for="engine in workspace.engines"
:key="engine.name"
:value="engine.name"
>
{{ engine.name }}
</option>
</select>
</div>
</div>
</form>
</div>
</ConfirmModal>
</template>
<script>
import { mapGetters } from 'vuex';
import ConfirmModal from '@/components/BaseConfirmModal';
export default {
name: 'ModalNewTable',
components: {
ConfirmModal
},
props: {
workspace: Object
},
data () {
return {
localOptions: {
name: '',
comment: '',
collation: '',
engine: ''
},
isOptionsChanging: false
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getDatabaseVariable: 'workspaces/getDatabaseVariable'
}),
defaultCollation () {
if (this.workspace.customizations.collations)
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value || '';
return '';
},
defaultEngine () {
if (this.workspace.customizations.engines)
return this.workspace.engines.find(engine => engine.isDefault).name;
return '';
}
},
mounted () {
this.localOptions.collation = this.defaultCollation;
this.localOptions.engine = this.defaultEngine;
setTimeout(() => {
this.$refs.firstInput.focus();
}, 20);
},
methods: {
confirmOptionsChange () {
this.$emit('open-create-table-editor', this.localOptions);
}
}
};
</script>

View File

@@ -1,192 +0,0 @@
<template>
<ConfirmModal
:confirm-text="$t('word.confirm')"
size="medium"
@confirm="confirmOptionsChange"
@hide="$emit('close')"
>
<template :slot="'header'">
<div class="d-flex">
<i class="mdi mdi-24px mdi-eye-plus mr-1" /> {{ $t('message.createNewView') }}
</div>
</template>
<div :slot="'body'">
<div class="container">
<div class="columns mb-4">
<div class="column col-6">
<div class="form-group">
<label class="form-label">{{ $t('word.name') }}</label>
<input
ref="firstInput"
v-model="localView.name"
class="form-input"
type="text"
>
</div>
</div>
<div class="column col-6">
<div v-if="workspace.customizations.definer" class="form-group">
<label class="form-label">{{ $t('word.definer') }}</label>
<select v-model="localView.definer" class="form-select">
<option value="">
{{ $t('message.currentUser') }}
</option>
<option
v-for="user in workspace.users"
:key="`${user.name}@${user.host}`"
:value="`\`${user.name}\`@\`${user.host}\``"
>
{{ user.name }}@{{ user.host }}
</option>
</select>
</div>
</div>
</div>
<div class="columns">
<div class="column col-4">
<div v-if="workspace.customizations.viewSqlSecurity" class="form-group">
<label class="form-label">{{ $t('message.sqlSecurity') }}</label>
<label class="form-radio">
<input
v-model="localView.security"
type="radio"
name="security"
value="DEFINER"
>
<i class="form-icon" /> DEFINER
</label>
<label class="form-radio">
<input
v-model="localView.security"
type="radio"
name="security"
value="INVOKER"
>
<i class="form-icon" /> INVOKER
</label>
</div>
</div>
<div class="column col-4">
<div v-if="workspace.customizations.viewAlgorithm" class="form-group">
<label class="form-label">{{ $t('word.algorithm') }}</label>
<label class="form-radio">
<input
v-model="localView.algorithm"
type="radio"
name="algorithm"
value="UNDEFINED"
>
<i class="form-icon" /> UNDEFINED
</label>
<label class="form-radio">
<input
v-model="localView.algorithm"
type="radio"
value="MERGE"
name="algorithm"
>
<i class="form-icon" /> MERGE
</label>
<label class="form-radio">
<input
v-model="localView.algorithm"
type="radio"
value="TEMPTABLE"
name="algorithm"
>
<i class="form-icon" /> TEMPTABLE
</label>
</div>
</div>
<div class="column col-4">
<div v-if="workspace.customizations.viewUpdateOption" class="form-group">
<label class="form-label">{{ $t('message.updateOption') }}</label>
<label class="form-radio">
<input
v-model="localView.updateOption"
type="radio"
name="update"
value=""
>
<i class="form-icon" /> None
</label>
<label class="form-radio">
<input
v-model="localView.updateOption"
type="radio"
name="update"
value="CASCADED"
>
<i class="form-icon" /> CASCADED
</label>
<label class="form-radio">
<input
v-model="localView.updateOption"
type="radio"
name="update"
value="LOCAL"
>
<i class="form-icon" /> LOCAL
</label>
</div>
</div>
</div>
</div>
<div class="workspace-query-results column col-12 mt-2">
<label class="form-label ml-2">{{ $t('message.selectStatement') }}</label>
<QueryEditor
ref="queryEditor"
:value.sync="localView.sql"
:workspace="workspace"
:schema="schema"
:height="editorHeight"
/>
</div>
</div>
</ConfirmModal>
</template>
<script>
import ConfirmModal from '@/components/BaseConfirmModal';
import QueryEditor from '@/components/QueryEditor';
export default {
name: 'ModalNewView',
components: {
ConfirmModal,
QueryEditor
},
props: {
workspace: Object
},
data () {
return {
localView: {
algorithm: 'UNDEFINED',
definer: '',
security: 'DEFINER',
updateOption: '',
sql: '',
name: ''
},
isOptionsChanging: false,
editorHeight: 300
};
},
computed: {
schema () {
return this.workspace.breadcrumbs.schema;
}
},
mounted () {
setTimeout(() => {
this.$refs.firstInput.focus();
}, 20);
},
methods: {
confirmOptionsChange () {
this.$emit('open-create-view-editor', this.localView);
}
}
};
</script>

View File

@@ -83,7 +83,7 @@
:scroll-element="scrollElement"
>
<template slot-scope="{ items }">
<ProcessesListRow
<ModalProcessesListRow
v-for="row in items"
:key="row._id"
class="process-row"
@@ -104,13 +104,13 @@
import { mapGetters, mapActions } from 'vuex';
import Schema from '@/ipc-api/Schema';
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
import ProcessesListRow from '@/components/ProcessesListRow';
import ModalProcessesListRow from '@/components/ModalProcessesListRow';
export default {
name: 'ModalProcessesList',
components: {
BaseVirtualScroll,
ProcessesListRow
ModalProcessesListRow
},
props: {
connection: Object

View File

@@ -11,7 +11,7 @@
<template v-if="cKey !== '_id'">
<span
v-if="!isInlineEditor[cKey]"
class="cell-content px-2"
class="cell-content"
:class="`${isNull(col)} type-${typeof col === 'number' ? 'int' : 'varchar'}`"
@dblclick="dblClick(cKey)"
>{{ col | cutText }}</span>
@@ -51,7 +51,7 @@ import ConfirmModal from '@/components/BaseConfirmModal';
import TextEditor from '@/components/BaseTextEditor';
export default {
name: 'ProcessesListRow',
name: 'ModalProcessesListRow',
components: {
ConfirmModal,
TextEditor
@@ -126,6 +126,7 @@ export default {
.cell-content {
display: block;
padding: 0 0.2rem;
min-height: 0.8rem;
text-overflow: ellipsis;
white-space: nowrap;

View File

@@ -68,6 +68,23 @@
</span>
</a>
<a
v-else-if="tab.type === 'new-table'"
class="tab-link"
:class="{'badge': tab.isChanged}"
>
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${$t('word.new').toUpperCase()}: ${$tc(`word.${tab.elementType}`)}`">
{{ $t('message.newTable') }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(tab)"
/>
</span>
</a>
<a
v-else-if="tab.type === 'table-props'"
class="tab-link"
@@ -85,13 +102,30 @@
</span>
</a>
<a
v-else-if="tab.type === 'new-view'"
class="tab-link"
:class="{'badge': tab.isChanged}"
>
<i class="mdi mdi-shape-square-plus mdi-18px mr-1" />
<span :title="`${$t('word.new').toUpperCase()}: ${$tc(`word.${tab.elementType}`)}`">
{{ $t('message.newView') }}
<span
class="btn btn-clear"
:title="$t('word.close')"
@mousedown.left.stop
@click.stop="closeTab(tab)"
/>
</span>
</a>
<a
v-else-if="tab.type === 'view-props'"
class="tab-link"
:class="{'badge': tab.isChanged}"
>
<i class="mdi mdi-tune-vertical-variant mdi-18px mr-1" />
<span :title="`${$t('word.settings').toUpperCase()}: ${$tc(`word.${tab.elementType}`)}`">
<span :title="`${$t('word.settings').toUpperCase()}: ${$tc(`word.view`)}`">
{{ tab.elementName | cutText }}
<span
class="btn btn-clear"
@@ -186,14 +220,14 @@
</Draggable>
<WorkspaceEmptyState v-if="!workspace.tabs.length" @new-tab="addQueryTab" />
<template v-for="tab of workspace.tabs">
<WorkspaceQueryTab
<WorkspaceTabQuery
v-if="tab.type==='query'"
:key="tab.uid"
:tab="tab"
:is-selected="selectedTab === tab.uid"
:connection="connection"
/>
<WorkspaceTableTab
<WorkspaceTabTable
v-else-if="['temp-data', 'data'].includes(tab.type)"
:key="tab.uid"
:connection="connection"
@@ -202,7 +236,15 @@
:schema="tab.schema"
:element-type="tab.elementType"
/>
<WorkspacePropsTab
<WorkspaceTabNewTable
v-else-if="tab.type === 'new-table'"
:key="tab.uid"
:tab="tab"
:connection="connection"
:is-selected="selectedTab === tab.uid"
:schema="tab.schema"
/>
<WorkspaceTabPropsTable
v-else-if="tab.type === 'table-props'"
:key="tab.uid"
:connection="connection"
@@ -210,7 +252,15 @@
:table="tab.elementName"
:schema="tab.schema"
/>
<WorkspacePropsTabView
<WorkspaceTabNewView
v-else-if="tab.type === 'new-view'"
:key="tab.uid"
:tab="tab"
:connection="connection"
:is-selected="selectedTab === tab.uid"
:schema="tab.schema"
/>
<WorkspaceTabPropsView
v-else-if="tab.type === 'view-props'"
:key="tab.uid"
:is-selected="selectedTab === tab.uid"
@@ -218,7 +268,7 @@
:view="tab.elementName"
:schema="tab.schema"
/>
<WorkspacePropsTabTrigger
<WorkspaceTabPropsTrigger
v-else-if="['temp-trigger-props', 'trigger-props'].includes(tab.type)"
:key="tab.uid"
:connection="connection"
@@ -226,7 +276,7 @@
:trigger="tab.elementName"
:schema="tab.schema"
/>
<WorkspacePropsTabTriggerFunction
<WorkspaceTabPropsTriggerFunction
v-else-if="['temp-trigger-function-props', 'trigger-function-props'].includes(tab.type)"
:key="tab.uid"
:connection="connection"
@@ -234,7 +284,7 @@
:function="tab.elementName"
:schema="tab.schema"
/>
<WorkspacePropsTabRoutine
<WorkspaceTabPropsRoutine
v-else-if="['temp-routine-props', 'routine-props'].includes(tab.type)"
:key="tab.uid"
:connection="connection"
@@ -242,7 +292,7 @@
:routine="tab.elementName"
:schema="tab.schema"
/>
<WorkspacePropsTabFunction
<WorkspaceTabPropsFunction
v-else-if="['temp-function-props', 'function-props'].includes(tab.type)"
:key="tab.uid"
:connection="connection"
@@ -250,7 +300,7 @@
:function="tab.elementName"
:schema="tab.schema"
/>
<WorkspacePropsTabScheduler
<WorkspaceTabPropsScheduler
v-else-if="['temp-scheduler-props', 'scheduler-props'].includes(tab.type)"
:key="tab.uid"
:connection="connection"
@@ -282,15 +332,19 @@ import Connection from '@/ipc-api/Connection';
import WorkspaceEmptyState from '@/components/WorkspaceEmptyState';
import WorkspaceExploreBar from '@/components/WorkspaceExploreBar';
import WorkspaceEditConnectionPanel from '@/components/WorkspaceEditConnectionPanel';
import WorkspaceQueryTab from '@/components/WorkspaceQueryTab';
import WorkspaceTableTab from '@/components/WorkspaceTableTab';
import WorkspacePropsTab from '@/components/WorkspacePropsTab';
import WorkspacePropsTabView from '@/components/WorkspacePropsTabView';
import WorkspacePropsTabTrigger from '@/components/WorkspacePropsTabTrigger';
import WorkspacePropsTabTriggerFunction from '@/components/WorkspacePropsTabTriggerFunction';
import WorkspacePropsTabRoutine from '@/components/WorkspacePropsTabRoutine';
import WorkspacePropsTabFunction from '@/components/WorkspacePropsTabFunction';
import WorkspacePropsTabScheduler from '@/components/WorkspacePropsTabScheduler';
import WorkspaceTabQuery from '@/components/WorkspaceTabQuery';
import WorkspaceTabTable from '@/components/WorkspaceTabTable';
import WorkspaceTabNewTable from '@/components/WorkspaceTabNewTable';
import WorkspaceTabNewView from '@/components/WorkspaceTabNewView';
import WorkspaceTabPropsTable from '@/components/WorkspaceTabPropsTable';
import WorkspaceTabPropsView from '@/components/WorkspaceTabPropsView';
import WorkspaceTabPropsTrigger from '@/components/WorkspaceTabPropsTrigger';
import WorkspaceTabPropsTriggerFunction from '@/components/WorkspaceTabPropsTriggerFunction';
import WorkspaceTabPropsRoutine from '@/components/WorkspaceTabPropsRoutine';
import WorkspaceTabPropsFunction from '@/components/WorkspaceTabPropsFunction';
import WorkspaceTabPropsScheduler from '@/components/WorkspaceTabPropsScheduler';
import ModalProcessesList from '@/components/ModalProcessesList';
import ModalDiscardChanges from '@/components/ModalDiscardChanges';
@@ -301,15 +355,17 @@ export default {
WorkspaceEmptyState,
WorkspaceExploreBar,
WorkspaceEditConnectionPanel,
WorkspaceQueryTab,
WorkspaceTableTab,
WorkspacePropsTab,
WorkspacePropsTabView,
WorkspacePropsTabTrigger,
WorkspacePropsTabTriggerFunction,
WorkspacePropsTabRoutine,
WorkspacePropsTabFunction,
WorkspacePropsTabScheduler,
WorkspaceTabQuery,
WorkspaceTabTable,
WorkspaceTabNewTable,
WorkspaceTabNewView,
WorkspaceTabPropsTable,
WorkspaceTabPropsView,
WorkspaceTabPropsTrigger,
WorkspaceTabPropsTriggerFunction,
WorkspaceTabPropsRoutine,
WorkspaceTabPropsFunction,
WorkspaceTabPropsScheduler,
ModalProcessesList,
ModalDiscardChanges
},
@@ -589,7 +645,7 @@ export default {
.th {
position: sticky;
top: 0;
border: 1px solid;
border: 2px solid;
border-left: none;
border-bottom-width: 2px;
padding: 0;
@@ -598,15 +654,15 @@ export default {
z-index: 1;
> div {
padding: 0.1rem 0.4rem;
padding: 0.1rem 0.2rem;
min-width: -webkit-fill-available;
}
}
.td {
border-right: 1px solid;
border-bottom: 1px solid;
padding: 0 0.4rem;
border-right: 2px solid;
border-bottom: 2px solid;
padding: 0 0.2rem;
text-overflow: ellipsis;
max-width: 200px;
white-space: nowrap;

View File

@@ -61,18 +61,6 @@
@close="hideNewDBModal"
@reload="refresh"
/>
<ModalNewTable
v-if="isNewTableModal"
:workspace="workspace"
@close="hideCreateTableModal"
@open-create-table-editor="openCreateTableEditor"
/>
<ModalNewView
v-if="isNewViewModal"
:workspace="workspace"
@close="hideCreateViewModal"
@open-create-view-editor="openCreateViewEditor"
/>
<ModalNewTrigger
v-if="isNewTriggerModal"
:workspace="workspace"
@@ -108,8 +96,8 @@
:selected-schema="selectedSchema"
:context-event="databaseContextEvent"
@close-context="closeDatabaseContext"
@show-create-table-modal="showCreateTableModal"
@show-create-view-modal="showCreateViewModal"
@open-create-table-tab="openCreateElementTab('table')"
@open-create-view-tab="openCreateElementTab('view')"
@show-create-trigger-modal="showCreateTriggerModal"
@show-create-routine-modal="showCreateRoutineModal"
@show-create-function-modal="showCreateFunctionModal"
@@ -152,8 +140,6 @@
<script>
import { mapGetters, mapActions } from 'vuex';
import Tables from '@/ipc-api/Tables';
import Views from '@/ipc-api/Views';
import Triggers from '@/ipc-api/Triggers';
import Routines from '@/ipc-api/Routines';
import Functions from '@/ipc-api/Functions';
@@ -165,8 +151,6 @@ import TableContext from '@/components/WorkspaceExploreBarTableContext';
import MiscContext from '@/components/WorkspaceExploreBarMiscContext';
import MiscFolderContext from '@/components/WorkspaceExploreBarMiscFolderContext';
import ModalNewSchema from '@/components/ModalNewSchema';
import ModalNewTable from '@/components/ModalNewTable';
import ModalNewView from '@/components/ModalNewView';
import ModalNewTrigger from '@/components/ModalNewTrigger';
import ModalNewRoutine from '@/components/ModalNewRoutine';
import ModalNewFunction from '@/components/ModalNewFunction';
@@ -182,8 +166,6 @@ export default {
MiscContext,
MiscFolderContext,
ModalNewSchema,
ModalNewTable,
ModalNewView,
ModalNewTrigger,
ModalNewRoutine,
ModalNewFunction,
@@ -199,7 +181,6 @@ export default {
isRefreshing: false,
isNewDBModal: false,
isNewTableModal: false,
isNewViewModal: false,
isNewTriggerModal: false,
isNewRoutineModal: false,
@@ -307,34 +288,16 @@ export default {
hideNewDBModal () {
this.isNewDBModal = false;
},
showCreateTableModal () {
openCreateElementTab (element) {
this.closeDatabaseContext();
this.isNewTableModal = true;
},
hideCreateTableModal () {
this.isNewTableModal = false;
},
async openCreateTableEditor (payload) {
const params = {
uid: this.connection.uid,
this.newTab({
uid: this.workspace.uid,
schema: this.selectedSchema,
...payload
};
const { status, response } = await Tables.createTable(params);
if (status === 'success') {
await this.refresh();
this.newTab({
uid: this.workspace.uid,
schema: this.selectedSchema,
elementName: payload.name,
elementType: 'table',
type: 'table-props'
});
}
else
this.addNotification({ status: 'error', message: response });
elementName: '',
elementType: element,
type: `new-${element}`
});
},
openSchemaContext (payload) {
this.selectedSchema = payload.schema;
@@ -371,38 +334,6 @@ export default {
closeMiscFolderContext () {
this.isMiscFolderContext = false;
},
showCreateViewModal () {
this.closeDatabaseContext();
this.closeMiscFolderContext();
this.isNewViewModal = true;
},
hideCreateViewModal () {
this.isNewViewModal = false;
},
async openCreateViewEditor (payload) {
const params = {
uid: this.connection.uid,
schema: this.selectedSchema,
...payload
};
const { status, response } = await Views.createView(params);
if (status === 'success') {
await this.refresh();
this.changeBreadcrumbs({ schema: this.selectedSchema, view: payload.name });
this.newTab({
uid: this.workspace.uid,
schema: this.selectedSchema,
elementName: payload.name,
elementType: 'view',
type: 'view-props'
});
}
else
this.addNotification({ status: 'error', message: response });
},
showCreateTriggerModal () {
this.closeDatabaseContext();
this.closeMiscFolderContext();

View File

@@ -101,9 +101,6 @@ export default {
removeTabs: 'workspaces/removeTabs',
newTab: 'workspaces/newTab'
}),
showCreateTableModal () {
this.$emit('show-create-table-modal');
},
showDeleteModal () {
this.isDeleteModal = true;
},

View File

@@ -74,9 +74,6 @@ export default {
addNotification: 'notifications/addNotification',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
}),
showCreateTableModal () {
this.$emit('show-create-table-modal');
},
showDeleteModal () {
this.isDeleteModal = true;
},

View File

@@ -63,7 +63,7 @@
:ref="breadcrumbs.schema === database.name && breadcrumbs.trigger === trigger.name ? 'explorebar-selected' : ''"
class="menu-item"
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.trigger === trigger.name}"
@mousedown="selectMisc({schema: database.name, misc: trigger, type: 'trigger'})"
@mousedown.left="selectMisc({schema: database.name, misc: trigger, type: 'trigger'})"
@dblclick="openMiscPermanentTab({schema: database.name, misc: trigger, type: 'trigger'})"
@contextmenu.prevent="showMiscContext($event, {...trigger, type: 'trigger'})"
>
@@ -97,7 +97,7 @@
:ref="breadcrumbs.schema === database.name && breadcrumbs.routine === procedure.name ? 'explorebar-selected' : ''"
class="menu-item"
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.routine === procedure.name}"
@mousedown="selectMisc({schema: database.name, misc: procedure, type: 'routine'})"
@mousedown.left="selectMisc({schema: database.name, misc: procedure, type: 'routine'})"
@dblclick="openMiscPermanentTab({schema: database.name, misc: procedure, type: 'routine'})"
@contextmenu.prevent="showMiscContext($event, {...procedure, type: 'procedure'})"
>
@@ -131,7 +131,7 @@
:ref="breadcrumbs.schema === database.name && breadcrumbs.triggerFunction === func.name ? 'explorebar-selected' : ''"
class="menu-item"
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.triggerFunction === func.name}"
@mousedown="selectMisc({schema: database.name, misc: func, type: 'triggerFunction'})"
@mousedown.left="selectMisc({schema: database.name, misc: func, type: 'triggerFunction'})"
@dblclick="openMiscPermanentTab({schema: database.name, misc: func, type: 'triggerFunction'})"
@contextmenu.prevent="showMiscContext($event, {...func, type: 'triggerFunction'})"
>
@@ -165,7 +165,7 @@
:ref="breadcrumbs.schema === database.name && breadcrumbs.function === func.name ? 'explorebar-selected' : ''"
class="menu-item"
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.function === func.name}"
@mousedown="selectMisc({schema: database.name, misc: func, type: 'function'})"
@mousedown.left="selectMisc({schema: database.name, misc: func, type: 'function'})"
@dblclick="openMiscPermanentTab({schema: database.name, misc: func, type: 'function'})"
@contextmenu.prevent="showMiscContext($event, {...func, type: 'function'})"
>
@@ -199,7 +199,7 @@
:ref="breadcrumbs.schema === database.name && breadcrumbs.scheduler === scheduler.name ? 'explorebar-selected' : ''"
class="menu-item"
:class="{'selected': breadcrumbs.schema === database.name && breadcrumbs.scheduler === scheduler.name}"
@mousedown="selectMisc({schema: database.name, misc: scheduler, type: 'scheduler'})"
@mousedown.left="selectMisc({schema: database.name, misc: scheduler, type: 'scheduler'})"
@dblclick="openMiscPermanentTab({schema: database.name, misc: scheduler, type: 'scheduler'})"
@contextmenu.prevent="showMiscContext($event, {...scheduler, type: 'scheduler'})"
>
@@ -320,11 +320,16 @@ export default {
this.addLoadedSchema(schema);
this.isLoading = false;
}
this.changeBreadcrumbs({ schema, table: null });
},
selectTable ({ schema, table }) {
this.newTab({ uid: this.connection.uid, elementName: table.name, schema: this.database.name, type: 'temp-data', elementType: table.type });
this.newTab({
uid: this.connection.uid,
elementName: table.name,
schema: this.database.name,
type: 'temp-data',
elementType: table.type
});
this.setBreadcrumbs({ schema, [table.type]: table.name });
},
selectMisc ({ schema, misc, type }) {

View File

@@ -10,14 +10,14 @@
<div
v-if="workspace.customizations.tableAdd"
class="context-element"
@click="showCreateTableModal"
@click="openCreateTableTab"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-table text-light pr-1" /> {{ $t('word.table') }}</span>
</div>
<div
v-if="workspace.customizations.viewAdd"
class="context-element"
@click="showCreateViewModal"
@click="openCreateViewTab"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-table-eye text-light pr-1" /> {{ $t('word.view') }}</span>
</div>
@@ -132,11 +132,11 @@ export default {
addNotification: 'notifications/addNotification',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
}),
showCreateTableModal () {
this.$emit('show-create-table-modal');
openCreateTableTab () {
this.$emit('open-create-table-tab');
},
showCreateViewModal () {
this.$emit('show-create-view-modal');
openCreateViewTab () {
this.$emit('open-create-view-tab');
},
showCreateTriggerModal () {
this.$emit('show-create-trigger-modal');

View File

@@ -116,9 +116,6 @@ export default {
removeLoadingElement: 'workspaces/removeLoadingElement',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
}),
showCreateTableModal () {
this.$emit('show-create-table-modal');
},
showDeleteModal () {
this.isDeleteModal = true;
},

View File

@@ -1,213 +0,0 @@
<template>
<ConfirmModal
:confirm-text="$t('word.confirm')"
size="400"
@confirm="confirmOptionsChange"
@hide="$emit('hide')"
>
<template :slot="'header'">
<div class="d-flex">
<i class="mdi mdi-24px mdi-cogs mr-1" />
<span class="cut-text">{{ $t('word.options') }} "{{ localOptions.name }}"</span>
</div>
</template>
<div :slot="'body'">
<form class="form-horizontal">
<div class="form-group">
<label class="form-label col-4">
{{ $t('word.name') }}
</label>
<div class="column">
<input
ref="firstInput"
v-model="optionsProxy.name"
class="form-input"
:class="{'is-error': !isTableNameValid}"
type="text"
>
</div>
</div>
<div v-if="customizations.languages" class="form-group">
<label class="form-label col-4">
{{ $t('word.language') }}
</label>
<div class="column">
<select v-model="optionsProxy.language" class="form-select">
<option v-for="language in customizations.languages" :key="language">
{{ language }}
</option>
</select>
</div>
</div>
<div v-if="customizations.definer" class="form-group">
<label class="form-label col-4">
{{ $t('word.definer') }}
</label>
<div class="column">
<select
v-if="workspace.users.length"
v-model="optionsProxy.definer"
class="form-select"
>
<option value="">
{{ $t('message.currentUser') }}
</option>
<option
v-for="user in workspace.users"
:key="`${user.name}@${user.host}`"
:value="`\`${user.name}\`@\`${user.host}\``"
>
{{ user.name }}@{{ user.host }}
</option>
</select>
<select v-if="!workspace.users.length" class="form-select">
<option value="">
{{ $t('message.currentUser') }}
</option>
</select>
</div>
</div>
<div class="form-group">
<label class="form-label col-4">
{{ $t('word.returns') }}
</label>
<div class="column">
<div class="input-group">
<select
v-model="optionsProxy.returns"
class="form-select text-uppercase"
style="width: 0;"
>
<option v-if="localOptions.returns === 'VOID'">
VOID
</option>
<option v-if="!isInDataTypes">
{{ localOptions.returns }}
</option>
<optgroup
v-for="group in workspace.dataTypes"
:key="group.group"
:label="group.group"
>
<option
v-for="type in group.types"
:key="type.name"
:selected="optionsProxy.returns === type.name"
:value="type.name"
>
{{ type.name }}
</option>
</optgroup>
</select>
<input
v-if="customizations.parametersLength"
v-model="optionsProxy.returnsLength"
class="form-input"
type="number"
min="0"
>
</div>
</div>
</div>
<div v-if="customizations.comment" class="form-group">
<label class="form-label col-4">
{{ $t('word.comment') }}
</label>
<div class="column">
<input
v-model="optionsProxy.comment"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group">
<label class="form-label col-4">
{{ $t('message.sqlSecurity') }}
</label>
<div class="column">
<select v-model="optionsProxy.security" class="form-select">
<option>DEFINER</option>
<option>INVOKER</option>
</select>
</div>
</div>
<div v-if="customizations.functionDataAccess" class="form-group">
<label class="form-label col-4">
{{ $t('message.dataAccess') }}
</label>
<div class="column">
<select v-model="optionsProxy.dataAccess" class="form-select">
<option>CONTAINS SQL</option>
<option>NO SQL</option>
<option>READS SQL DATA</option>
<option>MODIFIES SQL DATA</option>
</select>
</div>
</div>
<div v-if="customizations.functionDeterministic" class="form-group">
<div class="col-4" />
<div class="column">
<label class="form-checkbox form-inline">
<input v-model="optionsProxy.deterministic" type="checkbox"><i class="form-icon" /> {{ $t('word.deterministic') }}
</label>
</div>
</div>
</form>
</div>
</ConfirmModal>
</template>
<script>
import ConfirmModal from '@/components/BaseConfirmModal';
export default {
name: 'WorkspacePropsFunctionOptionsModal',
components: {
ConfirmModal
},
props: {
localOptions: Object,
workspace: Object
},
data () {
return {
optionsProxy: {},
isOptionsChanging: false
};
},
computed: {
isTableNameValid () {
return this.optionsProxy.name !== '';
},
customizations () {
return this.workspace.customizations;
},
isInDataTypes () {
let typeNames = [];
for (const group of this.workspace.dataTypes) {
typeNames = group.types.reduce((acc, curr) => {
acc.push(curr.name);
return acc;
}, []);
}
return typeNames.includes(this.localOptions.returns);
}
},
created () {
this.optionsProxy = JSON.parse(JSON.stringify(this.localOptions));
setTimeout(() => {
this.$refs.firstInput.focus();
}, 20);
},
methods: {
confirmOptionsChange () {
if (!this.isTableNameValid)
this.optionsProxy.name = this.localOptions.name;
this.$emit('options-update', this.optionsProxy);
}
}
};
</script>

View File

@@ -1,132 +0,0 @@
<template>
<ConfirmModal
:confirm-text="$t('word.confirm')"
size="400"
@confirm="confirmOptionsChange"
@hide="$emit('hide')"
>
<template :slot="'header'">
<div class="d-flex">
<i class="mdi mdi-24px mdi-cogs mr-1" />
<span class="cut-text">{{ $t('word.options') }} "{{ table }}"</span>
</div>
</template>
<div :slot="'body'">
<form class="form-horizontal">
<div class="form-group">
<label class="form-label col-4">
{{ $t('word.name') }}
</label>
<div class="column">
<input
ref="firstInput"
v-model="optionsProxy.name"
class="form-input"
:class="{'is-error': !isTableNameValid}"
type="text"
>
</div>
</div>
<div v-if="workspace.customizations.comment" class="form-group">
<label class="form-label col-4">
{{ $t('word.comment') }}
</label>
<div class="column">
<input
v-model="optionsProxy.comment"
class="form-input"
type="text"
>
</div>
</div>
<div v-if="workspace.customizations.autoIncrement" class="form-group">
<label class="form-label col-4">
{{ $t('word.autoIncrement') }}
</label>
<div class="column">
<input
v-model="optionsProxy.autoIncrement"
class="form-input"
type="number"
:disabled="optionsProxy.autoIncrement === null"
>
</div>
</div>
<div v-if="workspace.customizations.collations" class="form-group">
<label class="form-label col-4">
{{ $t('word.collation') }}
</label>
<div class="column">
<select v-model="optionsProxy.collation" class="form-select">
<option
v-for="collation in workspace.collations"
:key="collation.id"
:value="collation.collation"
>
{{ collation.collation }}
</option>
</select>
</div>
</div>
<div v-if="workspace.customizations.engines" class="form-group">
<label class="form-label col-4">
{{ $t('word.engine') }}
</label>
<div class="column">
<select v-model="optionsProxy.engine" class="form-select">
<option
v-for="engine in workspace.engines"
:key="engine.name"
:value="engine.name"
>
{{ engine.name }}
</option>
</select>
</div>
</div>
</form>
</div>
</ConfirmModal>
</template>
<script>
import ConfirmModal from '@/components/BaseConfirmModal';
export default {
name: 'WorkspacePropsOptionsModal',
components: {
ConfirmModal
},
props: {
localOptions: Object,
table: String,
workspace: Object
},
data () {
return {
optionsProxy: {},
isOptionsChanging: false
};
},
computed: {
isTableNameValid () {
return this.optionsProxy.name !== '';
}
},
created () {
this.optionsProxy = JSON.parse(JSON.stringify(this.localOptions));
setTimeout(() => {
this.$refs.firstInput.focus();
}, 20);
},
methods: {
confirmOptionsChange () {
if (!this.isTableNameValid)
this.optionsProxy.name = this.localOptions.name;
this.$emit('options-update', this.optionsProxy);
}
}
};
</script>

View File

@@ -1,161 +0,0 @@
<template>
<ConfirmModal
:confirm-text="$t('word.confirm')"
size="400"
@confirm="confirmOptionsChange"
@hide="$emit('hide')"
>
<template :slot="'header'">
<div class="d-flex">
<i class="mdi mdi-24px mdi-cogs mr-1" />
<span class="cut-text">{{ $t('word.options') }} "{{ localOptions.name }}"</span>
</div>
</template>
<div :slot="'body'">
<form class="form-horizontal">
<div class="form-group">
<label class="form-label col-4">
{{ $t('word.name') }}
</label>
<div class="column">
<input
ref="firstInput"
v-model="optionsProxy.name"
class="form-input"
:class="{'is-error': !isTableNameValid}"
type="text"
>
</div>
</div>
<div v-if="customizations.languages" class="form-group">
<label class="form-label col-4">
{{ $t('word.language') }}
</label>
<div class="column">
<select v-model="optionsProxy.language" class="form-select">
<option v-for="language in customizations.languages" :key="language">
{{ language }}
</option>
</select>
</div>
</div>
<div v-if="customizations.definer" class="form-group">
<label class="form-label col-4">
{{ $t('word.definer') }}
</label>
<div class="column">
<select
v-if="workspace.users.length"
v-model="optionsProxy.definer"
class="form-select"
>
<option value="">
{{ $t('message.currentUser') }}
</option>
<option
v-for="user in workspace.users"
:key="`${user.name}@${user.host}`"
:value="`\`${user.name}\`@\`${user.host}\``"
>
{{ user.name }}@{{ user.host }}
</option>
</select>
<select v-if="!workspace.users.length" class="form-select">
<option value="">
{{ $t('message.currentUser') }}
</option>
</select>
</div>
</div>
<div v-if="customizations.comment" class="form-group">
<label class="form-label col-4">
{{ $t('word.comment') }}
</label>
<div class="column">
<input
v-model="optionsProxy.comment"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group">
<label class="form-label col-4">
{{ $t('message.sqlSecurity') }}
</label>
<div class="column">
<select v-model="optionsProxy.security" class="form-select">
<option>DEFINER</option>
<option>INVOKER</option>
</select>
</div>
</div>
<div v-if="customizations.procedureDataAccess" class="form-group">
<label class="form-label col-4">
{{ $t('message.dataAccess') }}
</label>
<div class="column">
<select v-model="optionsProxy.dataAccess" class="form-select">
<option>CONTAINS SQL</option>
<option>NO SQL</option>
<option>READS SQL DATA</option>
<option>MODIFIES SQL DATA</option>
</select>
</div>
</div>
<div v-if="customizations.procedureDeterministic" class="form-group">
<div class="col-4" />
<div class="column">
<label class="form-checkbox form-inline">
<input v-model="optionsProxy.deterministic" type="checkbox"><i class="form-icon" /> {{ $t('word.deterministic') }}
</label>
</div>
</div>
</form>
</div>
</ConfirmModal>
</template>
<script>
import ConfirmModal from '@/components/BaseConfirmModal';
export default {
name: 'WorkspacePropsRoutineOptionsModal',
components: {
ConfirmModal
},
props: {
localOptions: Object,
workspace: Object
},
data () {
return {
optionsProxy: {},
isOptionsChanging: false
};
},
computed: {
isTableNameValid () {
return this.optionsProxy.name !== '';
},
customizations () {
return this.workspace.customizations;
}
},
created () {
this.optionsProxy = JSON.parse(JSON.stringify(this.localOptions));
setTimeout(() => {
this.$refs.firstInput.focus();
}, 20);
},
methods: {
confirmOptionsChange () {
if (!this.isTableNameValid)
this.optionsProxy.name = this.localOptions.name;
this.$emit('options-update', this.optionsProxy);
}
}
};
</script>

View File

@@ -1,125 +0,0 @@
<template>
<ConfirmModal
:confirm-text="$t('word.confirm')"
size="400"
@confirm="confirmOptionsChange"
@hide="$emit('hide')"
>
<template :slot="'header'">
<div class="d-flex">
<i class="mdi mdi-24px mdi-cogs mr-1" />
<span class="cut-text">{{ $t('word.options') }} "{{ localOptions.name }}"</span>
</div>
</template>
<div :slot="'body'">
<form class="form-horizontal">
<div v-if="customizations.triggerFunctionlanguages" class="form-group">
<label class="form-label col-4">
{{ $t('word.language') }}
</label>
<div class="column">
<select v-model="optionsProxy.language" class="form-select">
<option v-for="language in customizations.triggerFunctionlanguages" :key="language">
{{ language }}
</option>
</select>
</div>
</div>
<div v-if="customizations.definer" class="form-group">
<label class="form-label col-4">
{{ $t('word.definer') }}
</label>
<div class="column">
<select
v-if="workspace.users.length"
v-model="optionsProxy.definer"
class="form-select"
>
<option value="">
{{ $t('message.currentUser') }}
</option>
<option
v-for="user in workspace.users"
:key="`${user.name}@${user.host}`"
:value="`\`${user.name}\`@\`${user.host}\``"
>
{{ user.name }}@{{ user.host }}
</option>
</select>
<select v-if="!workspace.users.length" class="form-select">
<option value="">
{{ $t('message.currentUser') }}
</option>
</select>
</div>
</div>
<div v-if="customizations.comment" class="form-group">
<label class="form-label col-4">
{{ $t('word.comment') }}
</label>
<div class="column">
<input
v-model="optionsProxy.comment"
class="form-input"
type="text"
>
</div>
</div>
</form>
</div>
</ConfirmModal>
</template>
<script>
import ConfirmModal from '@/components/BaseConfirmModal';
export default {
name: 'WorkspacePropsTriggerFunctionOptionsModal',
components: {
ConfirmModal
},
props: {
localOptions: Object,
workspace: Object
},
data () {
return {
optionsProxy: {},
isOptionsChanging: false
};
},
computed: {
isTableNameValid () {
return this.optionsProxy.name !== '';
},
customizations () {
return this.workspace.customizations;
},
isInDataTypes () {
let typeNames = [];
for (const group of this.workspace.dataTypes) {
typeNames = group.types.reduce((acc, curr) => {
acc.push(curr.name);
return acc;
}, []);
}
return typeNames.includes(this.localOptions.returns);
}
},
created () {
this.optionsProxy = JSON.parse(JSON.stringify(this.localOptions));
setTimeout(() => {
this.$refs.firstInput.focus();
}, 20);
},
methods: {
confirmOptionsChange () {
if (!this.isTableNameValid)
this.optionsProxy.name = this.localOptions.name;
this.$emit('options-update', this.optionsProxy);
}
}
};
</script>

View File

@@ -0,0 +1,451 @@
<template>
<div v-show="isSelected" class="workspace-query-tab column col-12 columns col-gapless">
<div class="workspace-query-runner column col-12">
<div class="workspace-query-runner-footer">
<div class="workspace-query-buttons">
<button
class="btn btn-primary btn-sm"
:disabled="!isChanged"
:class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span>
</button>
<button
:disabled="!isChanged || isSaving"
class="btn btn-link btn-sm mr-0"
:title="$t('message.clearChanges')"
@click="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span>
</button>
<div class="divider-vert py-3" />
<button
:disabled="isSaving"
class="btn btn-dark btn-sm"
:title="$t('message.addNewField')"
@click="addField"
>
<i class="mdi mdi-24px mdi-playlist-plus mr-1" />
<span>{{ $t('word.add') }}</span>
</button>
<button
:disabled="isSaving || !localFields.length"
class="btn btn-dark btn-sm"
:title="$t('message.manageIndexes')"
@click="showIntdexesModal"
>
<i class="mdi mdi-24px mdi-key mdi-rotate-45 mr-1" />
<span>{{ $t('word.indexes') }}</span>
</button>
<button
class="btn btn-dark btn-sm"
:disabled="isSaving || !localFields.length"
@click="showForeignModal"
>
<i class="mdi mdi-24px mdi-key-link mr-1" />
<span>{{ $t('word.foreignKeys') }}</span>
</button>
</div>
<div class="workspace-query-info">
<div class="d-flex" :title="$t('word.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div>
</div>
</div>
</div>
<div class="container">
<div class="columns mb-4">
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ $t('word.name') }}</label>
<input
ref="firstInput"
v-model="localOptions.name"
class="form-input"
type="text"
>
</div>
</div>
<div v-if="workspace.customizations.comment" class="column">
<div class="form-group">
<label class="form-label">{{ $t('word.comment') }}</label>
<input
v-model="localOptions.comment"
class="form-input"
type="text"
>
</div>
</div>
<div v-if="workspace.customizations.collations" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ $t('word.collation') }}
</label>
<select v-model="localOptions.collation" class="form-select">
<option
v-for="collation in workspace.collations"
:key="collation.id"
:value="collation.collation"
>
{{ collation.collation }}
</option>
</select>
</div>
</div>
<div v-if="workspace.customizations.engines" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ $t('word.engine') }}
</label>
<select v-model="localOptions.engine" class="form-select">
<option
v-for="engine in workspace.engines"
:key="engine.name"
:value="engine.name"
>
{{ engine.name }}
</option>
</select>
</div>
</div>
</div>
</div>
<div class="workspace-query-results column col-12 p-relative">
<BaseLoader v-if="isLoading" />
<WorkspaceTabNewTableEmptyState v-if="!localFields.length" @new-field="addField" />
<WorkspaceTabPropsTableFields
v-if="localFields.length"
ref="indexTable"
:fields="localFields"
:indexes="localIndexes"
:foreigns="localKeyUsage"
:tab-uid="tabUid"
:conn-uid="connection.uid"
:index-types="workspace.indexTypes"
table="new"
:schema="schema"
mode="table"
@duplicate-field="duplicateField"
@remove-field="removeField"
@add-new-index="addNewIndex"
@add-to-index="addToIndex"
@rename-field="renameField"
/>
</div>
<WorkspaceTabPropsTableIndexesModal
v-if="isIndexesModal"
:local-indexes="localIndexes"
table="new"
:fields="localFields"
:index-types="workspace.indexTypes"
:workspace="workspace"
@hide="hideIndexesModal"
@indexes-update="indexesUpdate"
/>
<WorkspaceTabPropsTableForeignModal
v-if="isForeignModal"
:local-key-usage="localKeyUsage"
:connection="connection"
table="new"
:schema="schema"
:schema-tables="schemaTables"
:fields="localFields"
:workspace="workspace"
@hide="hideForeignModal"
@foreigns-update="foreignsUpdate"
/>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { uidGen } from 'common/libs/uidGen';
import Tables from '@/ipc-api/Tables';
import BaseLoader from '@/components/BaseLoader';
import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFields';
import WorkspaceTabPropsTableIndexesModal from '@/components/WorkspaceTabPropsTableIndexesModal';
import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTableForeignModal';
import WorkspaceTabNewTableEmptyState from '@/components/WorkspaceTabNewTableEmptyState';
export default {
name: 'WorkspaceTabNewTable',
components: {
BaseLoader,
WorkspaceTabPropsTableFields,
WorkspaceTabPropsTableIndexesModal,
WorkspaceTabPropsTableForeignModal,
WorkspaceTabNewTableEmptyState
},
props: {
connection: Object,
tab: Object,
isSelected: Boolean,
schema: String
},
data () {
return {
isLoading: false,
isSaving: false,
isIndexesModal: false,
isForeignModal: false,
isOptionsChanging: false,
originalFields: [],
localFields: [],
originalKeyUsage: [],
localKeyUsage: [],
originalIndexes: [],
localIndexes: [],
tableOptions: {},
localOptions: {},
lastTable: null,
newFieldsCounter: 0
};
},
computed: {
...mapGetters({
getWorkspace: 'workspaces/getWorkspace',
selectedWorkspace: 'workspaces/getSelected',
getDatabaseVariable: 'workspaces/getDatabaseVariable'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
tabUid () {
return this.$vnode.key;
},
defaultCollation () {
if (this.workspace.customizations.collations)
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value || '';
return '';
},
defaultEngine () {
if (this.workspace.customizations.engines)
return this.workspace.engines.find(engine => engine.isDefault).name;
return '';
},
schemaTables () {
const schemaTables = this.workspace.structure
.filter(schema => schema.name === this.schema)
.map(schema => schema.tables);
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
},
isChanged () {
return JSON.stringify(this.originalFields) !== JSON.stringify(this.localFields) ||
JSON.stringify(this.originalKeyUsage) !== JSON.stringify(this.localKeyUsage) ||
JSON.stringify(this.originalIndexes) !== JSON.stringify(this.localIndexes) ||
JSON.stringify(this.tableOptions) !== JSON.stringify(this.localOptions);
}
},
watch: {
isSelected (val) {
if (val)
this.changeBreadcrumbs({ schema: this.schema });
},
isChanged (val) {
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
}
},
created () {
this.tableOptions = {
name: '',
type: 'table',
engine: this.defaultEngine,
comment: '',
collation: this.defaultCollation
};
this.localOptions = JSON.parse(JSON.stringify(this.tableOptions));
window.addEventListener('keydown', this.onKey);
},
mounted () {
if (this.isSelected)
this.changeBreadcrumbs({ schema: this.schema });
setTimeout(() => {
this.$refs.firstInput.focus();
}, 100);
},
beforeDestroy () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
setUnsavedChanges: 'workspaces/setUnsavedChanges',
newTab: 'workspaces/newTab',
renameTabs: 'workspaces/renameTabs',
removeTab: 'workspaces/removeTab',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
}),
async saveChanges () {
if (this.isSaving) return;
this.isSaving = true;
const params = {
uid: this.connection.uid,
schema: this.schema,
fields: this.localFields,
foreigns: this.localKeyUsage,
indexes: this.localIndexes,
options: this.localOptions
};
try {
const { status, response } = await Tables.createTable(params);
if (status === 'success') {
await this.refreshStructure(this.connection.uid);
this.newTab({
uid: this.connection.uid,
schema: this.schema,
elementName: this.localOptions.name,
elementType: 'table',
type: 'table-props'
});
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid });
}
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isSaving = false;
this.newFieldsCounter = 0;
},
clearChanges () {
this.localFields = JSON.parse(JSON.stringify(this.originalFields));
this.localIndexes = JSON.parse(JSON.stringify(this.originalIndexes));
this.localKeyUsage = JSON.parse(JSON.stringify(this.originalKeyUsage));
this.tableOptions = {
name: '',
type: 'table',
engine: this.defaultEngine,
comment: '',
collation: this.defaultCollation
};
this.localOptions = JSON.parse(JSON.stringify(this.tableOptions));
this.newFieldsCounter = 0;
},
addField () {
this.localFields.push({
_id: uidGen(),
name: `${this.$tc('word.field', 1)}_${++this.newFieldsCounter}`,
key: '',
type: this.workspace.dataTypes[0].types[0].name,
schema: this.schema,
numPrecision: null,
numLength: this.workspace.dataTypes[0].types[0].length,
datePrecision: null,
charLength: null,
nullable: false,
unsigned: false,
zerofill: false,
order: this.localFields.length + 1,
default: null,
charset: null,
collation: null,
autoIncrement: false,
onUpdate: '',
comment: ''
});
setTimeout(() => {
const scrollable = this.$refs.indexTable.$refs.tableWrapper;
scrollable.scrollTop = scrollable.scrollHeight + 30;
}, 20);
},
renameField (payload) {
this.localIndexes = this.localIndexes.map(index => {
const fi = index.fields.findIndex(field => field === payload.old);
if (fi !== -1)
index.fields[fi] = payload.new;
return index;
});
this.localKeyUsage = this.localKeyUsage.map(key => {
if (key.field === payload.old)
key.field = payload.new;
return key;
});
},
duplicateField (uid) {
const fieldToClone = Object.assign({}, this.localFields.find(field => field._id === uid));
fieldToClone._id = uidGen();
fieldToClone.name = `${fieldToClone.name}_copy`;
fieldToClone.order = this.localFields.length + 1;
this.localFields = [...this.localFields, fieldToClone];
setTimeout(() => {
const scrollable = this.$refs.indexTable.$refs.tableWrapper;
scrollable.scrollTop = scrollable.scrollHeight + 30;
}, 20);
},
removeField (uid) {
this.localFields = this.localFields.filter(field => field._id !== uid);
},
addNewIndex (payload) {
this.localIndexes = [...this.localIndexes, {
_id: uidGen(),
name: payload.index === 'PRIMARY' ? 'PRIMARY' : payload.field,
fields: [payload.field],
type: payload.index,
comment: '',
indexType: 'BTREE',
indexComment: '',
cardinality: 0
}];
},
addToIndex (payload) {
this.localIndexes = this.localIndexes.map(index => {
if (index._id === payload.index) index.fields.push(payload.field);
return index;
});
},
optionsUpdate (options) {
this.localOptions = options;
},
showIntdexesModal () {
this.isIndexesModal = true;
},
hideIndexesModal () {
this.isIndexesModal = false;
},
indexesUpdate (indexes) {
this.localIndexes = indexes;
},
showForeignModal () {
this.isForeignModal = true;
},
hideForeignModal () {
this.isForeignModal = false;
},
foreignsUpdate (foreigns) {
this.localKeyUsage = foreigns;
},
onKey (e) {
if (this.isSelected) {
e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
if (this.isChanged)
this.saveChanges();
}
}
}
}
};
</script>

View File

@@ -0,0 +1,33 @@
<template>
<div class="column col-12 empty">
<p class="h6 empty-subtitle">
{{ $t('message.thereAreNoTableFields') }}
</p>
<div class="empty-action">
<button class="btn btn-gray d-flex" @click="$emit('new-field')">
<i class="mdi mdi-24px mdi-playlist-plus mr-2" />
{{ $t('message.addNewField') }}
</button>
</div>
</div>
</template>
<script>
export default {
name: 'WorkspaceTabNewTableEmptyState'
};
</script>
<style scoped>
.empty {
border-radius: 0;
background: transparent;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-left: auto;
margin-right: auto;
z-index: 9;
}
</style>

View File

@@ -0,0 +1,286 @@
<template>
<div v-show="isSelected" class="workspace-query-tab column col-12 columns col-gapless">
<div class="workspace-query-runner column col-12">
<div class="workspace-query-runner-footer">
<div class="workspace-query-buttons">
<button
class="btn btn-primary btn-sm"
:disabled="!isChanged"
:class="{'loading':isSaving}"
title="CTRL+S"
@click="saveChanges"
>
<i class="mdi mdi-24px mdi-content-save mr-1" />
<span>{{ $t('word.save') }}</span>
</button>
<button
:disabled="!isChanged"
class="btn btn-link btn-sm mr-0"
:title="$t('message.clearChanges')"
@click="clearChanges"
>
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span>
</button>
</div>
<div class="workspace-query-info">
<div class="d-flex" :title="$t('word.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
</div>
</div>
</div>
</div>
<div class="container">
<div class="columns">
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ $t('word.name') }}</label>
<input
ref="firstInput"
v-model="localView.name"
class="form-input"
type="text"
>
</div>
</div>
<div class="column col-auto">
<div v-if="workspace.customizations.definer" class="form-group">
<label class="form-label">{{ $t('word.definer') }}</label>
<select
v-if="workspace.users.length"
v-model="localView.definer"
class="form-select"
>
<option value="">
{{ $t('message.currentUser') }}
</option>
<option v-if="!isDefinerInUsers" :value="originalView.definer">
{{ originalView.definer.replaceAll('`', '') }}
</option>
<option
v-for="user in workspace.users"
:key="`${user.name}@${user.host}`"
:value="`\`${user.name}\`@\`${user.host}\``"
>
{{ user.name }}@{{ user.host }}
</option>
</select>
<select v-if="!workspace.users.length" class="form-select">
<option value="">
{{ $t('message.currentUser') }}
</option>
</select>
</div>
</div>
<div class="column col-auto mr-2">
<div v-if="workspace.customizations.viewSqlSecurity" class="form-group">
<label class="form-label">{{ $t('message.sqlSecurity') }}</label>
<select v-model="localView.security" class="form-select">
<option>DEFINER</option>
<option>INVOKER</option>
</select>
</div>
</div>
<div class="column col-auto mr-2">
<div v-if="workspace.customizations.viewAlgorithm" class="form-group">
<label class="form-label">{{ $t('word.algorithm') }}</label>
<select v-model="localView.algorithm" class="form-select">
<option>UNDEFINED</option>
<option>MERGE</option>
<option>TEMPTABLE</option>
</select>
</div>
</div>
<div v-if="workspace.customizations.viewUpdateOption" class="column col-auto mr-2">
<div class="form-group">
<label class="form-label">{{ $t('message.updateOption') }}</label>
<select v-model="localView.updateOption" class="form-select">
<option value="">
None
</option>
<option>CASCADED</option>
<option>LOCAL</option>
</select>
</div>
</div>
</div>
</div>
<div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ $t('message.selectStatement') }}</label>
<QueryEditor
v-show="isSelected"
ref="queryEditor"
:value.sync="localView.sql"
:workspace="workspace"
:schema="schema"
:height="editorHeight"
/>
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import BaseLoader from '@/components/BaseLoader';
import QueryEditor from '@/components/QueryEditor';
import Views from '@/ipc-api/Views';
export default {
name: 'WorkspaceTabNewView',
components: {
BaseLoader,
QueryEditor
},
props: {
connection: Object,
tab: Object,
isSelected: Boolean,
schema: String
},
data () {
return {
isLoading: false,
isSaving: false,
originalView: {},
localView: {},
lastView: null,
sqlProxy: '',
editorHeight: 300
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
},
tabUid () {
return this.$vnode.key;
},
isChanged () {
return JSON.stringify(this.originalView) !== JSON.stringify(this.localView);
},
isDefinerInUsers () {
return this.originalView ? this.workspace.users.some(user => this.originalView.definer === `\`${user.name}\`@\`${user.host}\``) : true;
}
},
watch: {
isSelected (val) {
if (val) {
this.changeBreadcrumbs({ schema: this.schema, view: this.view });
setTimeout(() => {
this.resizeQueryEditor();
}, 50);
}
},
isChanged (val) {
this.setUnsavedChanges({ uid: this.connection.uid, tUid: this.tabUid, isChanged: val });
}
},
async created () {
this.originalView = {
algorithm: 'UNDEFINED',
definer: '',
security: 'DEFINER',
updateOption: '',
sql: '',
name: ''
};
this.localView = JSON.parse(JSON.stringify(this.originalView));
setTimeout(() => {
this.resizeQueryEditor();
}, 50);
window.addEventListener('keydown', this.onKey);
},
mounted () {
if (this.isSelected)
this.changeBreadcrumbs({ schema: this.schema });
setTimeout(() => {
this.$refs.firstInput.focus();
}, 100);
window.addEventListener('resize', this.resizeQueryEditor);
},
destroyed () {
window.removeEventListener('resize', this.resizeQueryEditor);
},
beforeDestroy () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
refreshStructure: 'workspaces/refreshStructure',
setUnsavedChanges: 'workspaces/setUnsavedChanges',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
newTab: 'workspaces/newTab',
removeTab: 'workspaces/removeTab',
renameTabs: 'workspaces/renameTabs'
}),
async saveChanges () {
if (this.isSaving) return;
this.isSaving = true;
const params = {
uid: this.connection.uid,
schema: this.schema,
...this.localView
};
try {
const { status, response } = await Views.createView(params);
if (status === 'success') {
await this.refreshStructure(this.connection.uid);
this.newTab({
uid: this.connection.uid,
schema: this.schema,
elementName: this.localView.name,
elementType: 'view',
type: 'view-props'
});
this.removeTab({ uid: this.connection.uid, tab: this.tab.uid });
}
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isSaving = false;
},
clearChanges () {
this.localView = JSON.parse(JSON.stringify(this.originalView));
this.$refs.queryEditor.editor.session.setValue(this.localView.sql);
},
resizeQueryEditor () {
if (this.$refs.queryEditor) {
const footer = document.getElementById('footer');
const size = window.innerHeight - this.$refs.queryEditor.$el.getBoundingClientRect().top - footer.offsetHeight;
this.editorHeight = size;
this.$refs.queryEditor.editor.resize();
}
},
onKey (e) {
if (this.isSelected) {
e.stopPropagation();
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
if (this.isChanged)
this.saveChanges();
}
}
}
}
};
</script>

View File

@@ -37,10 +37,6 @@
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
<span>{{ $t('word.parameters') }}</span>
</button>
<button class="btn btn-dark btn-sm" @click="showOptionsModal">
<i class="mdi mdi-24px mdi-cogs mr-1" />
<span>{{ $t('word.options') }}</span>
</button>
</div>
<div class="workspace-query-info">
<div class="d-flex" :title="$t('word.schema')">
@@ -49,6 +45,152 @@
</div>
</div>
</div>
<div class="container">
<div class="columns">
<div class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ $t('word.name') }}
</label>
<input
ref="firstInput"
v-model="localFunction.name"
class="form-input"
:class="{'is-error': !isTableNameValid}"
type="text"
>
</div>
</div>
<div v-if="customizations.languages" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ $t('word.language') }}
</label>
<select v-model="localFunction.language" class="form-select">
<option v-for="language in customizations.languages" :key="language">
{{ language }}
</option>
</select>
</div>
</div>
<div v-if="customizations.definer" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ $t('word.definer') }}
</label>
<select
v-if="workspace.users.length"
v-model="localFunction.definer"
class="form-select"
>
<option value="">
{{ $t('message.currentUser') }}
</option>
<option
v-for="user in workspace.users"
:key="`${user.name}@${user.host}`"
:value="`\`${user.name}\`@\`${user.host}\``"
>
{{ user.name }}@{{ user.host }}
</option>
</select>
<select v-if="!workspace.users.length" class="form-select">
<option value="">
{{ $t('message.currentUser') }}
</option>
</select>
</div>
</div>
<div class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ $t('word.returns') }}
</label>
<div class="input-group">
<select
v-model="localFunction.returns"
class="form-select text-uppercase"
style="max-width: 150px;"
>
<option v-if="localFunction.returns === 'VOID'">
VOID
</option>
<option v-if="!isInDataTypes">
{{ localFunction.returns }}
</option>
<optgroup
v-for="group in workspace.dataTypes"
:key="group.group"
:label="group.group"
>
<option
v-for="type in group.types"
:key="type.name"
:selected="localFunction.returns === type.name"
:value="type.name"
>
{{ type.name }}
</option>
</optgroup>
</select>
<input
v-if="customizations.parametersLength"
v-model="localFunction.returnsLength"
style="max-width: 150px;"
class="form-input"
type="number"
min="0"
:placeholder="$t('word.length')"
>
</div>
</div>
</div>
<div v-if="customizations.comment" class="column">
<div class="form-group">
<label class="form-label">
{{ $t('word.comment') }}
</label>
<input
v-model="localFunction.comment"
class="form-input"
type="text"
>
</div>
</div>
<div class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ $t('message.sqlSecurity') }}
</label>
<select v-model="localFunction.security" class="form-select">
<option>DEFINER</option>
<option>INVOKER</option>
</select>
</div>
</div>
<div v-if="customizations.functionDataAccess" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ $t('message.dataAccess') }}
</label>
<select v-model="localFunction.dataAccess" class="form-select">
<option>CONTAINS SQL</option>
<option>NO SQL</option>
<option>READS SQL DATA</option>
<option>MODIFIES SQL DATA</option>
</select>
</div>
</div>
<div v-if="customizations.functionDeterministic" class="column col-auto">
<div class="form-group">
<label class="form-label d-invisible">.</label>
<label class="form-checkbox form-inline">
<input v-model="localFunction.deterministic" type="checkbox"><i class="form-icon" /> {{ $t('word.deterministic') }}
</label>
</div>
</div>
</div>
</div>
<div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ $t('message.functionBody') }}</label>
@@ -61,14 +203,7 @@
:height="editorHeight"
/>
</div>
<WorkspacePropsFunctionOptionsModal
v-if="isOptionsModal"
:local-options="localFunction"
:workspace="workspace"
@hide="hideOptionsModal"
@options-update="optionsUpdate"
/>
<WorkspacePropsFunctionParamsModal
<WorkspaceTabPropsFunctionParamsModal
v-if="isParamsModal"
:local-parameters="localFunction.parameters"
:workspace="workspace"
@@ -91,18 +226,16 @@ import { mapGetters, mapActions } from 'vuex';
import { uidGen } from 'common/libs/uidGen';
import BaseLoader from '@/components/BaseLoader';
import QueryEditor from '@/components/QueryEditor';
import WorkspacePropsFunctionOptionsModal from '@/components/WorkspacePropsFunctionOptionsModal';
import WorkspacePropsFunctionParamsModal from '@/components/WorkspacePropsFunctionParamsModal';
import WorkspaceTabPropsFunctionParamsModal from '@/components/WorkspaceTabPropsFunctionParamsModal';
import ModalAskParameters from '@/components/ModalAskParameters';
import Functions from '@/ipc-api/Functions';
export default {
name: 'WorkspacePropsTabFunction',
name: 'WorkspaceTabPropsFunction',
components: {
BaseLoader,
QueryEditor,
WorkspacePropsFunctionOptionsModal,
WorkspacePropsFunctionParamsModal,
WorkspaceTabPropsFunctionParamsModal,
ModalAskParameters
},
props: {
@@ -115,7 +248,6 @@ export default {
return {
isLoading: false,
isSaving: false,
isOptionsModal: false,
isParamsModal: false,
isAskingParameters: false,
originalFunction: null,
@@ -133,6 +265,9 @@ export default {
workspace () {
return this.getWorkspace(this.connection.uid);
},
customizations () {
return this.workspace.customizations;
},
tabUid () {
return this.$vnode.key;
},
@@ -144,6 +279,19 @@ export default {
? this.workspace.users.some(user => this.originalFunction.definer === `\`${user.name}\`@\`${user.host}\``)
: true;
},
isTableNameValid () {
return this.localFunction.name !== '';
},
isInDataTypes () {
let typeNames = [];
for (const group of this.workspace.dataTypes) {
typeNames = group.types.reduce((acc, curr) => {
acc.push(curr.name);
return acc;
}, []);
}
return typeNames.includes(this.localFunction.returns);
},
schemaTables () {
const schemaTables = this.workspace.structure
.filter(schema => schema.name === this.schema)
@@ -330,12 +478,6 @@ export default {
this.newTab({ uid: this.connection.uid, content: sql, type: 'query', autorun: true });
},
showOptionsModal () {
this.isOptionsModal = true;
},
hideOptionsModal () {
this.isOptionsModal = false;
},
showParamsModal () {
this.isParamsModal = true;
},

View File

@@ -176,7 +176,7 @@ import { uidGen } from 'common/libs/uidGen';
import ConfirmModal from '@/components/BaseConfirmModal';
export default {
name: 'WorkspacePropsRoutineParamsModal',
name: 'WorkspaceTabPropsRoutineParamsModal',
components: {
ConfirmModal
},

View File

@@ -37,10 +37,6 @@
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
<span>{{ $t('word.parameters') }}</span>
</button>
<button class="btn btn-dark btn-sm" @click="showOptionsModal">
<i class="mdi mdi-24px mdi-cogs mr-1" />
<span>{{ $t('word.options') }}</span>
</button>
</div>
<div class="workspace-query-info">
<div class="d-flex" :title="$t('word.schema')">
@@ -49,6 +45,108 @@
</div>
</div>
</div>
<div class="container">
<div class="columns">
<div class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ $t('word.name') }}
</label>
<input
ref="firstInput"
v-model="localRoutine.name"
class="form-input"
:class="{'is-error': !isTableNameValid}"
type="text"
>
</div>
</div>
<div v-if="customizations.languages" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ $t('word.language') }}
</label>
<select v-model="localRoutine.language" class="form-select">
<option v-for="language in customizations.languages" :key="language">
{{ language }}
</option>
</select>
</div>
</div>
<div v-if="customizations.definer" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ $t('word.definer') }}
</label>
<select
v-if="workspace.users.length"
v-model="localRoutine.definer"
class="form-select"
>
<option value="">
{{ $t('message.currentUser') }}
</option>
<option
v-for="user in workspace.users"
:key="`${user.name}@${user.host}`"
:value="`\`${user.name}\`@\`${user.host}\``"
>
{{ user.name }}@{{ user.host }}
</option>
</select>
<select v-if="!workspace.users.length" class="form-select">
<option value="">
{{ $t('message.currentUser') }}
</option>
</select>
</div>
</div>
<div v-if="customizations.comment" class="column">
<div class="form-group">
<label class="form-label">
{{ $t('word.comment') }}
</label>
<input
v-model="localRoutine.comment"
class="form-input"
type="text"
>
</div>
</div>
<div class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ $t('message.sqlSecurity') }}
</label>
<select v-model="localRoutine.security" class="form-select">
<option>DEFINER</option>
<option>INVOKER</option>
</select>
</div>
</div>
<div v-if="customizations.procedureDataAccess" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ $t('message.dataAccess') }}
</label>
<select v-model="localRoutine.dataAccess" class="form-select">
<option>CONTAINS SQL</option>
<option>NO SQL</option>
<option>READS SQL DATA</option>
<option>MODIFIES SQL DATA</option>
</select>
</div>
</div>
<div v-if="customizations.procedureDeterministic" class="column col-auto">
<div class="form-group">
<label class="form-label d-invisible">.</label>
<label class="form-checkbox form-inline">
<input v-model="localRoutine.deterministic" type="checkbox"><i class="form-icon" /> {{ $t('word.deterministic') }}
</label>
</div>
</div>
</div>
</div>
<div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" />
<label class="form-label ml-2">{{ $t('message.routineBody') }}</label>
@@ -62,14 +160,7 @@
:height="editorHeight"
/>
</div>
<WorkspacePropsRoutineOptionsModal
v-if="isOptionsModal"
:local-options="localRoutine"
:workspace="workspace"
@hide="hideOptionsModal"
@options-update="optionsUpdate"
/>
<WorkspacePropsRoutineParamsModal
<WorkspaceTabPropsRoutineParamsModal
v-if="isParamsModal"
:local-parameters="localRoutine.parameters"
:workspace="workspace"
@@ -92,18 +183,16 @@ import { mapGetters, mapActions } from 'vuex';
import { uidGen } from 'common/libs/uidGen';
import QueryEditor from '@/components/QueryEditor';
import BaseLoader from '@/components/BaseLoader';
import WorkspacePropsRoutineOptionsModal from '@/components/WorkspacePropsRoutineOptionsModal';
import WorkspacePropsRoutineParamsModal from '@/components/WorkspacePropsRoutineParamsModal';
import WorkspaceTabPropsRoutineParamsModal from '@/components/WorkspaceTabPropsRoutineParamsModal';
import ModalAskParameters from '@/components/ModalAskParameters';
import Routines from '@/ipc-api/Routines';
export default {
name: 'WorkspacePropsTabRoutine',
name: 'WorkspaceTabPropsRoutine',
components: {
QueryEditor,
BaseLoader,
WorkspacePropsRoutineOptionsModal,
WorkspacePropsRoutineParamsModal,
WorkspaceTabPropsRoutineParamsModal,
ModalAskParameters
},
props: {
@@ -116,7 +205,6 @@ export default {
return {
isLoading: false,
isSaving: false,
isOptionsModal: false,
isParamsModal: false,
isAskingParameters: false,
originalRoutine: null,
@@ -134,6 +222,9 @@ export default {
workspace () {
return this.getWorkspace(this.connection.uid);
},
customizations () {
return this.workspace.customizations;
},
tabUid () {
return this.$vnode.key;
},
@@ -143,6 +234,9 @@ export default {
isDefinerInUsers () {
return this.originalRoutine ? this.workspace.users.some(user => this.originalRoutine.definer === `\`${user.name}\`@\`${user.host}\``) : true;
},
isTableNameValid () {
return this.localRoutine.name !== '';
},
schemaTables () {
const schemaTables = this.workspace.structure
.filter(schema => schema.name === this.schema)
@@ -327,12 +421,6 @@ export default {
this.newTab({ uid: this.connection.uid, content: sql, type: 'query', autorun: true });
},
showOptionsModal () {
this.isOptionsModal = true;
},
hideOptionsModal () {
this.isOptionsModal = false;
},
showParamsModal () {
this.isParamsModal = true;
},

View File

@@ -176,7 +176,7 @@ import { uidGen } from 'common/libs/uidGen';
import ConfirmModal from '@/components/BaseConfirmModal';
export default {
name: 'WorkspacePropsRoutineParamsModal',
name: 'WorkspaceTabPropsRoutineParamsModal',
components: {
ConfirmModal
},

View File

@@ -37,7 +37,7 @@
</div>
</div>
<div class="container">
<div class="columns mb-4">
<div class="columns">
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ $t('word.name') }}</label>
@@ -77,7 +77,7 @@
</select>
</div>
</div>
<div class="column col-4">
<div class="column">
<div class="form-group">
<label class="form-label">{{ $t('word.comment') }}</label>
<input
@@ -87,8 +87,6 @@
>
</div>
</div>
</div>
<div class="columns">
<div class="column">
<div class="form-group">
<label class="form-label mr-2">{{ $t('word.state') }}</label>
@@ -132,7 +130,7 @@
:height="editorHeight"
/>
</div>
<WorkspacePropsSchedulerTimingModal
<WorkspaceTabPropsSchedulerTimingModal
v-if="isTimingModal"
:local-options="localScheduler"
:workspace="workspace"
@@ -146,15 +144,15 @@
import { mapGetters, mapActions } from 'vuex';
import BaseLoader from '@/components/BaseLoader';
import QueryEditor from '@/components/QueryEditor';
import WorkspacePropsSchedulerTimingModal from '@/components/WorkspacePropsSchedulerTimingModal';
import WorkspaceTabPropsSchedulerTimingModal from '@/components/WorkspaceTabPropsSchedulerTimingModal';
import Schedulers from '@/ipc-api/Schedulers';
export default {
name: 'WorkspacePropsTabScheduler',
name: 'WorkspaceTabPropsScheduler',
components: {
BaseLoader,
QueryEditor,
WorkspacePropsSchedulerTimingModal
WorkspaceTabPropsSchedulerTimingModal
},
props: {
connection: Object,

View File

@@ -143,7 +143,7 @@ import { VueMaskDirective } from 'v-mask';
import moment from 'moment';
export default {
name: 'WorkspacePropsSchedulerTimingModal',
name: 'WorkspaceTabPropsSchedulerTimingModal',
components: {
ConfirmModal
},

View File

@@ -51,14 +51,6 @@
<i class="mdi mdi-24px mdi-key-link mr-1" />
<span>{{ $t('word.foreignKeys') }}</span>
</button>
<button
class="btn btn-dark btn-sm"
:disabled="isSaving"
@click="showOptionsModal"
>
<i class="mdi mdi-24px mdi-cogs mr-1" />
<span>{{ $t('word.options') }}</span>
</button>
</div>
<div class="workspace-query-info">
<div class="d-flex" :title="$t('word.schema')">
@@ -67,9 +59,80 @@
</div>
</div>
</div>
<div class="container">
<div class="columns mb-4">
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ $t('word.name') }}</label>
<input
v-model="localOptions.name"
class="form-input"
type="text"
>
</div>
</div>
<div v-if="workspace.customizations.comment" class="column">
<div class="form-group">
<label class="form-label">{{ $t('word.comment') }}</label>
<input
v-model="localOptions.comment"
class="form-input"
type="text"
>
</div>
</div>
<div v-if="workspace.customizations.autoIncrement" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ $t('word.autoIncrement') }}
</label>
<input
ref="firstInput"
v-model="localOptions.autoIncrement"
class="form-input"
type="number"
:disabled="localOptions.autoIncrement === null"
>
</div>
</div>
<div v-if="workspace.customizations.collations" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ $t('word.collation') }}
</label>
<select v-model="localOptions.collation" class="form-select">
<option
v-for="collation in workspace.collations"
:key="collation.id"
:value="collation.collation"
>
{{ collation.collation }}
</option>
</select>
</div>
</div>
<div v-if="workspace.customizations.engines" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ $t('word.engine') }}
</label>
<select v-model="localOptions.engine" class="form-select">
<option
v-for="engine in workspace.engines"
:key="engine.name"
:value="engine.name"
>
{{ engine.name }}
</option>
</select>
</div>
</div>
</div>
</div>
<div class="workspace-query-results column col-12 p-relative">
<BaseLoader v-if="isLoading" />
<WorkspacePropsTable
<WorkspaceTabPropsTableFields
v-if="localFields"
ref="indexTable"
:fields="localFields"
@@ -88,15 +151,7 @@
@rename-field="renameField"
/>
</div>
<WorkspacePropsOptionsModal
v-if="isOptionsModal"
:local-options="localOptions"
:table="table"
:workspace="workspace"
@hide="hideOptionsModal"
@options-update="optionsUpdate"
/>
<WorkspacePropsIndexesModal
<WorkspaceTabPropsTableIndexesModal
v-if="isIndexesModal"
:local-indexes="localIndexes"
:table="table"
@@ -106,7 +161,7 @@
@hide="hideIndexesModal"
@indexes-update="indexesUpdate"
/>
<WorkspacePropsForeignModal
<WorkspaceTabPropsTableForeignModal
v-if="isForeignModal"
:local-key-usage="localKeyUsage"
:connection="connection"
@@ -126,19 +181,17 @@ import { mapGetters, mapActions } from 'vuex';
import { uidGen } from 'common/libs/uidGen';
import Tables from '@/ipc-api/Tables';
import BaseLoader from '@/components/BaseLoader';
import WorkspacePropsTable from '@/components/WorkspacePropsTable';
import WorkspacePropsOptionsModal from '@/components/WorkspacePropsOptionsModal';
import WorkspacePropsIndexesModal from '@/components/WorkspacePropsIndexesModal';
import WorkspacePropsForeignModal from '@/components/WorkspacePropsForeignModal';
import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFields';
import WorkspaceTabPropsTableIndexesModal from '@/components/WorkspaceTabPropsTableIndexesModal';
import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTableForeignModal';
export default {
name: 'WorkspacePropsTab',
name: 'WorkspaceTabPropsTable',
components: {
BaseLoader,
WorkspacePropsTable,
WorkspacePropsOptionsModal,
WorkspacePropsIndexesModal,
WorkspacePropsForeignModal
WorkspaceTabPropsTableFields,
WorkspaceTabPropsTableIndexesModal,
WorkspaceTabPropsTableForeignModal
},
props: {
connection: Object,
@@ -150,7 +203,6 @@ export default {
return {
isLoading: false,
isSaving: false,
isOptionsModal: false,
isIndexesModal: false,
isForeignModal: false,
isOptionsChanging: false,
@@ -160,6 +212,7 @@ export default {
localKeyUsage: [],
originalIndexes: [],
localIndexes: [],
tableOptions: {},
localOptions: {},
lastTable: null,
newFieldsCounter: 0
@@ -177,12 +230,9 @@ export default {
tabUid () {
return this.$vnode.key;
},
tableOptions () {
const db = this.workspace.structure.find(db => db.name === this.schema);
return db && this.table ? db.tables.find(table => table.name === this.table) : {};
},
defaultEngine () {
return this.getDatabaseVariable(this.connection.uid, 'default_storage_engine').value || '';
const engine = this.getDatabaseVariable(this.connection.uid, 'default_storage_engine');
return engine ? engine.value : '';
},
schemaTables () {
const schemaTables = this.workspace.structure
@@ -238,6 +288,20 @@ export default {
renameTabs: 'workspaces/renameTabs',
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
}),
async getTableOptions (params) {
const db = this.workspace.structure.find(db => db.name === this.schema);
if (db && db.tables.length && this.table)
this.tableOptions = db.tables.find(table => table.name === this.table);
else {
const { status, response } = await Tables.getTableOptions(params);
if (status === 'success')
this.tableOptions = response;
else
this.addNotification({ status: 'error', message: response });
}
},
async getFieldsData () {
if (!this.table) return;
@@ -245,10 +309,6 @@ export default {
this.lastTable = this.table;
this.newFieldsCounter = 0;
this.isLoading = true;
try {
this.localOptions = JSON.parse(JSON.stringify(this.tableOptions));
}
catch (err) {}
const params = {
uid: this.connection.uid,
@@ -256,6 +316,14 @@ export default {
table: this.table
};
try {
await this.getTableOptions(params);
this.localOptions = JSON.parse(JSON.stringify(this.tableOptions));
}
catch (err) {
console.error(err);
}
try { // Columns data
const { status, response } = await Tables.getTableColumns(params);
if (status === 'success') {
@@ -534,6 +602,11 @@ export default {
fieldToClone.name = `${fieldToClone.name}_copy`;
fieldToClone.order = this.localFields.length + 1;
this.localFields = [...this.localFields, fieldToClone];
setTimeout(() => {
const scrollable = this.$refs.indexTable.$refs.tableWrapper;
scrollable.scrollTop = scrollable.scrollHeight + 30;
}, 20);
},
removeField (uid) {
this.localFields = this.localFields.filter(field => field._id !== uid);
@@ -556,12 +629,6 @@ export default {
return index;
});
},
showOptionsModal () {
this.isOptionsModal = true;
},
hideOptionsModal () {
this.isOptionsModal = false;
},
optionsUpdate (options) {
this.localOptions = options;
},

View File

@@ -46,7 +46,7 @@
import BaseContextMenu from '@/components/BaseContextMenu';
export default {
name: 'WorkspaceQueryTableContext',
name: 'WorkspaceTabQueryTableContext',
components: {
BaseContextMenu
},

View File

@@ -126,11 +126,11 @@
<script>
import { mapActions, mapGetters } from 'vuex';
import Draggable from 'vuedraggable';
import TableRow from '@/components/WorkspacePropsTableRow';
import TableContext from '@/components/WorkspacePropsTableContext';
import TableRow from '@/components/WorkspaceTabPropsTableRow';
import TableContext from '@/components/WorkspaceTabPropsTableContext';
export default {
name: 'WorkspacePropsTable',
name: 'WorkspaceTabPropsTableFields',
components: {
TableRow,
TableContext,

View File

@@ -208,7 +208,7 @@ import Tables from '@/ipc-api/Tables';
import ConfirmModal from '@/components/BaseConfirmModal';
export default {
name: 'WorkspacePropsForeignModal',
name: 'WorkspaceTabPropsTableForeignModal',
components: {
ConfirmModal
},

View File

@@ -142,7 +142,7 @@ import { uidGen } from 'common/libs/uidGen';
import ConfirmModal from '@/components/BaseConfirmModal';
export default {
name: 'WorkspacePropsIndexesModal',
name: 'WorkspaceTabPropsTableIndexesModal',
components: {
ConfirmModal
},

View File

@@ -1,12 +1,12 @@
<template>
<div class="tr" @contextmenu.prevent="$emit('contextmenu', $event, localRow._id)">
<div class="td" tabindex="0">
<div class="td p-0" tabindex="0">
<div :class="customizations.sortableFields ? 'row-draggable' : 'text-center'">
<i v-if="customizations.sortableFields" class="mdi mdi-drag-horizontal row-draggable-icon" />
{{ localRow.order }}
</div>
</div>
<div class="td" tabindex="0">
<div class="td p-0" tabindex="0">
<div class="text-center">
<i
v-for="(index, i) in indexes"
@@ -23,7 +23,7 @@
/>
</div>
</div>
<div class="td" tabindex="0">
<div class="td p-0" tabindex="0">
<span
v-if="!isInlineEditor.name"
class="cell-content"
@@ -37,12 +37,12 @@
v-model="editingContent"
type="text"
autofocus
class="editable-field px-2"
class="editable-field form-input input-sm px-1"
@blur="editOFF"
>
</div>
<div
class="td text-uppercase"
class="td p-0 text-uppercase"
tabindex="0"
>
<span
@@ -57,7 +57,7 @@
v-else
ref="editField"
v-model="editingContent"
class="form-select editable-field small-select text-uppercase"
class="form-select editable-field pl-1 pr-4 small-select text-uppercase"
@blur="editOFF"
>
<option v-if="!isInDataTypes">
@@ -81,7 +81,7 @@
</div>
<div
v-if="customizations.tableArray"
class="td"
class="td p-0"
tabindex="0"
>
<label class="form-checkbox">
@@ -89,7 +89,7 @@
<i class="form-icon" />
</label>
</div>
<div class="td type-int" tabindex="0">
<div class="td p-0 type-int" tabindex="0">
<template v-if="fieldType.length">
<span
v-if="!isInlineEditor.length"
@@ -109,7 +109,7 @@
v-model="editingContent"
type="text"
autofocus
class="editable-field px-2"
class="editable-field form-input input-sm px-1"
@blur="editOFF"
>
<input
@@ -118,14 +118,14 @@
v-model="editingContent"
type="number"
autofocus
class="editable-field px-2"
class="editable-field form-input input-sm px-1"
@blur="editOFF"
>
</template>
</div>
<div
v-if="customizations.unsigned"
class="td"
class="td p-0"
tabindex="0"
>
<label class="form-checkbox">
@@ -139,7 +139,7 @@
</div>
<div
v-if="customizations.nullable"
class="td"
class="td p-0"
tabindex="0"
>
<label class="form-checkbox">
@@ -153,7 +153,7 @@
</div>
<div
v-if="customizations.zerofill"
class="td"
class="td p-0"
tabindex="0"
>
<label class="form-checkbox">
@@ -165,14 +165,14 @@
<i class="form-icon" />
</label>
</div>
<div class="td" tabindex="0">
<div class="td p-0" tabindex="0">
<span class="cell-content" @dblclick="editON($event, localRow.default, 'default')">
{{ fieldDefault }}
</span>
</div>
<div
v-if="customizations.comment"
class="td type-varchar"
class="td p-0 type-varchar"
tabindex="0"
>
<span
@@ -188,13 +188,13 @@
v-model="editingContent"
type="text"
autofocus
class="editable-field px-2"
class="editable-field form-input input-sm px-1"
@blur="editOFF"
>
</div>
<div
v-if="customizations.collation"
class="td"
class="td p-0"
tabindex="0"
>
<template v-if="fieldType.collation">
@@ -209,7 +209,7 @@
v-else
ref="editField"
v-model="editingContent"
class="form-select small-select editable-field"
class="form-select small-select pl-1 pr-4 editable-field"
@blur="editOFF"
>
<option
@@ -334,7 +334,7 @@ import { mapGetters } from 'vuex';
import ConfirmModal from '@/components/BaseConfirmModal';
export default {
name: 'WorkspacePropsTableRow',
name: 'WorkspaceTabPropsTableRow',
components: {
ConfirmModal
},
@@ -575,12 +575,16 @@ export default {
position: absolute;
left: 0;
right: 0;
max-height: 21px;
border-radius: 3px;
font-size: 0.7rem;
}
.row-draggable {
position: relative;
text-align: right;
padding-left: 28px;
padding-right: 2px;
cursor: grab;
.row-draggable-icon {
@@ -609,6 +613,7 @@ export default {
.cell-content {
display: block;
padding: 0 0.2rem;
min-height: 0.8rem;
text-overflow: ellipsis;
white-space: nowrap;

View File

@@ -31,7 +31,7 @@
</div>
</div>
<div class="container">
<div class="columns mb-4">
<div class="columns">
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ $t('word.name') }}</label>
@@ -71,49 +71,49 @@
</select>
</div>
</div>
</div>
<fieldset class="columns" :disabled="customizations.triggerOnlyRename">
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ $t('word.table') }}</label>
<select v-model="localTrigger.table" class="form-select">
<option v-for="table in schemaTables" :key="table.name">
{{ table.name }}
</option>
</select>
</div>
</div>
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ $t('word.event') }}</label>
<div class="input-group">
<select v-model="localTrigger.activation" class="form-select">
<option>BEFORE</option>
<option>AFTER</option>
</select>
<select
v-if="!customizations.triggerMultipleEvents"
v-model="localTrigger.event"
class="form-select"
>
<option v-for="event in Object.keys(localEvents)" :key="event">
{{ event }}
<fieldset class="column columns mb-0" :disabled="customizations.triggerOnlyRename">
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ $t('word.table') }}</label>
<select v-model="localTrigger.table" class="form-select">
<option v-for="table in schemaTables" :key="table.name">
{{ table.name }}
</option>
</select>
<div v-if="customizations.triggerMultipleEvents" class="px-4">
<label
v-for="event in Object.keys(localEvents)"
:key="event"
class="form-checkbox form-inline"
@change.prevent="changeEvents(event)"
</div>
</div>
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ $t('word.event') }}</label>
<div class="input-group">
<select v-model="localTrigger.activation" class="form-select">
<option>BEFORE</option>
<option>AFTER</option>
</select>
<select
v-if="!customizations.triggerMultipleEvents"
v-model="localTrigger.event"
class="form-select"
>
<input :checked="localEvents[event]" type="checkbox"><i class="form-icon" /> {{ event }}
</label>
<option v-for="event in Object.keys(localEvents)" :key="event">
{{ event }}
</option>
</select>
<div v-if="customizations.triggerMultipleEvents" class="px-4">
<label
v-for="event in Object.keys(localEvents)"
:key="event"
class="form-checkbox form-inline"
@change.prevent="changeEvents(event)"
>
<input :checked="localEvents[event]" type="checkbox"><i class="form-icon" /> {{ event }}
</label>
</div>
</div>
</div>
</div>
</div>
</fieldset>
</fieldset>
</div>
</div>
<div class="workspace-query-results column col-12 mt-2 p-relative">
<BaseLoader v-if="isLoading" />
@@ -137,7 +137,7 @@ import BaseLoader from '@/components/BaseLoader';
import Triggers from '@/ipc-api/Triggers';
export default {
name: 'WorkspacePropsTabTrigger',
name: 'WorkspaceTabPropsTrigger',
components: {
BaseLoader,
QueryEditor

View File

@@ -22,13 +22,60 @@
<i class="mdi mdi-24px mdi-delete-sweep mr-1" />
<span>{{ $t('word.clear') }}</span>
</button>
<div class="divider-vert py-3" />
<button class="btn btn-dark btn-sm" @click="showOptionsModal">
<i class="mdi mdi-24px mdi-cogs mr-1" />
<span>{{ $t('word.options') }}</span>
</button>
</div>
</div>
</div>
<div class="container">
<div class="columns">
<div v-if="customizations.triggerFunctionlanguages" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ $t('word.language') }}
</label>
<select v-model="localFunction.language" class="form-select">
<option v-for="language in customizations.triggerFunctionlanguages" :key="language">
{{ language }}
</option>
</select>
</div>
</div>
<div v-if="customizations.definer" class="column col-auto">
<div class="form-group">
<label class="form-label">
{{ $t('word.definer') }}
</label>
<select
v-if="workspace.users.length"
v-model="localFunction.definer"
class="form-select"
>
<option value="">
{{ $t('message.currentUser') }}
</option>
<option
v-for="user in workspace.users"
:key="`${user.name}@${user.host}`"
:value="`\`${user.name}\`@\`${user.host}\``"
>
{{ user.name }}@{{ user.host }}
</option>
</select>
<select v-if="!workspace.users.length" class="form-select">
<option value="">
{{ $t('message.currentUser') }}
</option>
</select>
</div>
</div>
<div v-if="customizations.comment" class="form-group">
<label class="form-label">
{{ $t('word.comment') }}
</label>
<input
v-model="localFunction.comment"
class="form-input"
type="text"
>
</div>
</div>
</div>
@@ -44,13 +91,6 @@
:height="editorHeight"
/>
</div>
<WorkspacePropsTriggerFunctionOptionsModal
v-if="isOptionsModal"
:local-options="localFunction"
:workspace="workspace"
@hide="hideOptionsModal"
@options-update="optionsUpdate"
/>
<ModalAskParameters
v-if="isAskingParameters"
:local-routine="localFunction"
@@ -66,16 +106,14 @@ import { mapGetters, mapActions } from 'vuex';
import { uidGen } from 'common/libs/uidGen';
import BaseLoader from '@/components/BaseLoader';
import QueryEditor from '@/components/QueryEditor';
import WorkspacePropsTriggerFunctionOptionsModal from '@/components/WorkspacePropsTriggerFunctionOptionsModal';
import ModalAskParameters from '@/components/ModalAskParameters';
import Functions from '@/ipc-api/Functions';
export default {
name: 'WorkspacePropsTabTriggerFunction',
name: 'WorkspaceTabPropsTriggerFunction',
components: {
BaseLoader,
QueryEditor,
WorkspacePropsTriggerFunctionOptionsModal,
ModalAskParameters
},
props: {
@@ -88,7 +126,6 @@ export default {
return {
isLoading: false,
isSaving: false,
isOptionsModal: false,
isParamsModal: false,
isAskingParameters: false,
originalFunction: null,
@@ -106,6 +143,9 @@ export default {
workspace () {
return this.getWorkspace(this.connection.uid);
},
customizations () {
return this.workspace.customizations;
},
tabUid () {
return this.$vnode.key;
},
@@ -303,12 +343,6 @@ export default {
this.newTab({ uid: this.connection.uid, content: sql, type: 'query', autorun: true });
},
showOptionsModal () {
this.isOptionsModal = true;
},
hideOptionsModal () {
this.isOptionsModal = false;
},
showParamsModal () {
this.isParamsModal = true;
},

View File

@@ -31,7 +31,7 @@
</div>
</div>
<div class="container">
<div class="columns mb-4">
<div class="columns">
<div class="column col-auto">
<div class="form-group">
<label class="form-label">{{ $t('word.name') }}</label>
@@ -71,93 +71,35 @@
</select>
</div>
</div>
</div>
<div class="columns">
<div class="column col-auto mr-2">
<div v-if="workspace.customizations.viewSqlSecurity" class="form-group">
<label class="form-label">{{ $t('message.sqlSecurity') }}</label>
<label class="form-radio">
<input
v-model="localView.security"
type="radio"
name="security"
value="DEFINER"
>
<i class="form-icon" /> DEFINER
</label>
<label class="form-radio">
<input
v-model="localView.security"
type="radio"
name="security"
value="INVOKER"
>
<i class="form-icon" /> INVOKER
</label>
<select v-model="localView.security" class="form-select">
<option>DEFINER</option>
<option>INVOKER</option>
</select>
</div>
</div>
<div class="column col-auto mr-2">
<div v-if="workspace.customizations.viewAlgorithm" class="form-group">
<label class="form-label">{{ $t('word.algorithm') }}</label>
<label class="form-radio">
<input
v-model="localView.algorithm"
type="radio"
name="algorithm"
value="UNDEFINED"
>
<i class="form-icon" /> UNDEFINED
</label>
<label class="form-radio">
<input
v-model="localView.algorithm"
type="radio"
value="MERGE"
name="algorithm"
>
<i class="form-icon" /> MERGE
</label>
<label class="form-radio">
<input
v-model="localView.algorithm"
type="radio"
value="TEMPTABLE"
name="algorithm"
>
<i class="form-icon" /> TEMPTABLE
</label>
<select v-model="localView.algorithm" class="form-select">
<option>UNDEFINED</option>
<option>MERGE</option>
<option>TEMPTABLE</option>
</select>
</div>
</div>
<div v-if="workspace.customizations.viewUpdateOption" class="column col-auto mr-2">
<div class="form-group">
<label class="form-label">{{ $t('message.updateOption') }}</label>
<label class="form-radio">
<input
v-model="localView.updateOption"
type="radio"
name="update"
value=""
>
<i class="form-icon" /> None
</label>
<label class="form-radio">
<input
v-model="localView.updateOption"
type="radio"
name="update"
value="CASCADED"
>
<i class="form-icon" /> CASCADED
</label>
<label class="form-radio">
<input
v-model="localView.updateOption"
type="radio"
name="update"
value="LOCAL"
>
<i class="form-icon" /> LOCAL
</label>
<select v-model="localView.updateOption" class="form-select">
<option value="">
None
</option>
<option>CASCADED</option>
<option>LOCAL</option>
</select>
</div>
</div>
</div>
@@ -184,7 +126,7 @@ import QueryEditor from '@/components/QueryEditor';
import Views from '@/ipc-api/Views';
export default {
name: 'WorkspacePropsTabView',
name: 'WorkspaceTabPropsView',
components: {
BaseLoader,
QueryEditor

View File

@@ -100,10 +100,10 @@
</div>
</div>
</div>
<WorkspaceQueryEmptyState v-if="!results.length && !isQuering" />
<WorkspaceTabQueryEmptyState v-if="!results.length && !isQuering" />
<div class="workspace-query-results p-relative column col-12">
<BaseLoader v-if="isQuering" />
<WorkspaceQueryTable
<WorkspaceTabQueryTable
v-if="results"
v-show="!isQuering"
ref="queryTable"
@@ -124,18 +124,18 @@ import { format } from 'sql-formatter';
import Schema from '@/ipc-api/Schema';
import QueryEditor from '@/components/QueryEditor';
import BaseLoader from '@/components/BaseLoader';
import WorkspaceQueryTable from '@/components/WorkspaceQueryTable';
import WorkspaceQueryEmptyState from '@/components/WorkspaceQueryEmptyState';
import WorkspaceTabQueryTable from '@/components/WorkspaceTabQueryTable';
import WorkspaceTabQueryEmptyState from '@/components/WorkspaceTabQueryEmptyState';
import { mapGetters, mapActions } from 'vuex';
import tableTabs from '@/mixins/tableTabs';
export default {
name: 'WorkspaceQueryTab',
name: 'WorkspaceTabQuery',
components: {
BaseLoader,
QueryEditor,
WorkspaceQueryTable,
WorkspaceQueryEmptyState
WorkspaceTabQueryTable,
WorkspaceTabQueryEmptyState
},
mixins: [tableTabs],
props: {

View File

@@ -29,7 +29,7 @@
<script>
export default {
name: 'WorkspaceQueryEmptyState'
name: 'WorkspaceTabQueryEmptyState'
};
</script>

View File

@@ -66,7 +66,7 @@
:scroll-element="scrollElement"
>
<template slot-scope="{ items }">
<WorkspaceQueryTableRow
<WorkspaceTabQueryTableRow
v-for="row in items"
:key="row._id"
:row="row"
@@ -107,17 +107,17 @@ import { uidGen } from 'common/libs/uidGen';
import arrayToFile from '../libs/arrayToFile';
import { TEXT, LONG_TEXT, BLOB } from 'common/fieldTypes';
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
import WorkspaceQueryTableRow from '@/components/WorkspaceQueryTableRow';
import TableContext from '@/components/WorkspaceQueryTableContext';
import WorkspaceTabQueryTableRow from '@/components/WorkspaceTabQueryTableRow';
import TableContext from '@/components/WorkspaceTabQueryTableContext';
import ConfirmModal from '@/components/BaseConfirmModal';
import { mapActions, mapGetters } from 'vuex';
import moment from 'moment';
export default {
name: 'WorkspaceQueryTable',
name: 'WorkspaceTabQueryTable',
components: {
BaseVirtualScroll,
WorkspaceQueryTableRow,
WorkspaceTabQueryTableRow,
TableContext,
ConfirmModal
},

View File

@@ -52,7 +52,7 @@
import BaseContextMenu from '@/components/BaseContextMenu';
export default {
name: 'WorkspaceQueryTableContext',
name: 'WorkspaceTabQueryTableContext',
components: {
BaseContextMenu
},

View File

@@ -11,7 +11,7 @@
<template v-if="cKey !== '_id'">
<span
v-if="!isInlineEditor[cKey] && fields[cKey]"
class="cell-content px-2"
class="cell-content"
:class="`${isNull(col)} ${typeClass(fields[cKey].type)}`"
@dblclick="editON($event, col, cKey)"
>{{ col | typeFormat(fields[cKey].type.toLowerCase(), fields[cKey].length) | cutText }}</span>
@@ -31,7 +31,7 @@
v-mask="inputProps.mask"
:type="inputProps.type"
autofocus
class="editable-field px-2"
class="editable-field form-input input-sm px-1"
@blur="editOFF"
>
<select
@@ -59,7 +59,7 @@
v-model="editingContent"
:type="inputProps.type"
autofocus
class="editable-field px-2"
class="editable-field form-input input-sm px-1"
@blur="editOFF"
>
</template>
@@ -200,7 +200,7 @@ import TextEditor from '@/components/BaseTextEditor';
import ForeignKeySelect from '@/components/ForeignKeySelect';
export default {
name: 'WorkspaceQueryTableRow',
name: 'WorkspaceTabQueryTableRow',
components: {
ConfirmModal,
TextEditor,
@@ -528,6 +528,9 @@ export default {
border: none;
line-height: 1;
width: 100%;
max-height: 21px;
border-radius: 3px;
font-size: 0.7rem;
position: absolute;
left: 0;
right: 0;
@@ -535,6 +538,7 @@ export default {
.cell-content {
display: block;
padding: 0 0.2rem;
min-height: 0.8rem;
text-overflow: ellipsis;
white-space: nowrap;

View File

@@ -123,7 +123,7 @@
</div>
<div class="workspace-query-results p-relative column col-12">
<BaseLoader v-if="isQuering" />
<WorkspaceQueryTable
<WorkspaceTabQueryTable
v-if="results"
ref="queryTable"
:results="results"
@@ -159,17 +159,17 @@
<script>
import Tables from '@/ipc-api/Tables';
import BaseLoader from '@/components/BaseLoader';
import WorkspaceQueryTable from '@/components/WorkspaceQueryTable';
import WorkspaceTabQueryTable from '@/components/WorkspaceTabQueryTable';
import ModalNewTableRow from '@/components/ModalNewTableRow';
import ModalFakerRows from '@/components/ModalFakerRows';
import { mapGetters, mapActions } from 'vuex';
import tableTabs from '@/mixins/tableTabs';
export default {
name: 'WorkspaceTableTab',
name: 'WorkspaceTabTable',
components: {
BaseLoader,
WorkspaceQueryTable,
WorkspaceTabQueryTable,
ModalNewTableRow,
ModalFakerRows
},

View File

@@ -116,7 +116,8 @@ module.exports = {
triggerFunction: 'Trigger function | Trigger functions',
all: 'All',
duplicate: 'Duplicate',
routine: 'Routine'
routine: 'Routine',
new: 'New'
},
message: {
appWelcome: 'Welcome to Antares SQL Client!',
@@ -229,7 +230,10 @@ module.exports = {
noOpenTabs: 'There are no open tabs, navigate on the left bar or:',
noSchema: 'No schema',
restorePreviourSession: 'Restore previous session',
runQuery: 'Run query'
runQuery: 'Run query',
thereAreNoTableFields: 'There are no table fields',
newTable: 'New table',
newView: 'New view'
},
faker: {
address: 'Address',

View File

@@ -14,6 +14,10 @@ export default class {
return ipcRenderer.invoke('get-table-count', params);
}
static getTableOptions (params) {
return ipcRenderer.invoke('get-table-options', params);
}
static getTableIndexes (params) {
return ipcRenderer.invoke('get-table-indexes', params);
}

View File

@@ -29,6 +29,7 @@
.th {
padding: $unit-3 $unit-2;
display: table-cell;
border-radius: 3px;
}
.th {

View File

@@ -17,7 +17,7 @@
&.key-mul,
&.key-INDEX,
&.key-KEY {
color: palegreen;
color: limegreen;
}
&.key-FULLTEXT {

View File

@@ -15,6 +15,15 @@ body {
user-select: none;
}
::selection,
option:hover,
option:focus,
option:active,
option:checked {
background-color: $primary-color;
color: $light-color;
}
/* Additions */
@include margin-variant(3, $unit-3);
@include margin-variant(4, $unit-4);
@@ -200,7 +209,7 @@ body {
cursor: pointer;
&.small-select {
height: 1rem;
height: 21px;
font-size: 0.7rem;
padding: 1px 0.4rem 0;
}

View File

@@ -20,6 +20,11 @@
}
}
option,
optgroup {
background-color: $bg-color-gray;
}
// Override Spectre.css
.menu {
background: $bg-color-light-dark;
@@ -179,16 +184,29 @@
.workspace-query-results {
.table {
.th {
background: $bg-color-dark;
border-color: $bg-color-light-dark;
border-color: darken($bg-color-light-gray, 80%);
background-color: $body-bg-dark;
}
.td {
border-color: $bg-color-light-dark;
.tr {
background-color: darken($bg-color-light-gray, 80%);
&:focus {
box-shadow: inset 0 0 0 1px $body-font-color-dark;
background: rgba($color: #000, $alpha: 0.3);
.td:first-child {
border-left: 2px solid $body-bg-dark;
}
.td {
border-color: $body-bg-dark;
&:focus {
box-shadow: inset 0 0 0 2px darken($body-font-color-dark, 40%);
background-color: rgba($color: #000, $alpha: 0.3);
}
.editable-field {
box-shadow: inset 0 0 0 2px darken($body-font-color-dark, 40%);
background-color: rgba($color: #000, $alpha: 0.3);
}
}
}
}

View File

@@ -216,11 +216,29 @@
.table {
.th {
background: $body-bg;
border-color: rgba($bg-color-light-dark, 0.5);
border-color: lighten($bg-color-light-gray, 2%);
}
.td {
border-color: rgba($bg-color-light-dark, 0.5);
.tr {
background-color: lighten($bg-color-light-gray, 2%);
.td:first-child {
border-left: 2px solid $body-bg;
}
.td {
border-color: $body-bg;
&:focus {
box-shadow: inset 0 0 0 2px lighten($body-font-color, 10%);
background-color: $body-font-color-dark;
}
.editable-field {
box-shadow: inset 0 0 0 2px lighten($body-font-color, 10%);
background-color: $body-font-color-dark;
}
}
}
}
}

View File

@@ -581,70 +581,25 @@ export default {
const workspaceTabs = state.workspaces.find(workspace => workspace.uid === uid);
switch (type) {
case 'temp-data': {
const existentTab = workspaceTabs
? workspaceTabs.tabs.find(tab =>
tab.schema === schema &&
tab.elementName === elementName &&
tab.elementType === elementType &&
['temp-data', 'data'].includes(tab.type))
: false;
if (existentTab) { // if data tab exists
tabUid = existentTab.uid;
}
else {
const tempTabs = workspaceTabs ? workspaceTabs.tabs.filter(tab => tab.type === 'temp-data') : false;
if (tempTabs && tempTabs.length) { // if temp table already opened
for (const tab of tempTabs) {
commit('REPLACE_TAB', { uid, tab: tab.uid, type, schema, elementName, elementType });
tabUid = tab.uid;
}
}
else {
tabUid = uidGen('T');
commit('NEW_TAB', { uid, tab: tabUid, content, type, autorun, schema, elementName, elementType });
}
}
}
break;
case 'data': {
const existentTab = workspaceTabs
? workspaceTabs.tabs.find(tab =>
tab.schema === schema &&
tab.elementName === elementName &&
tab.elementType === elementType &&
['temp-data', 'data'].includes(tab.type))
: false;
if (existentTab) {
commit('REPLACE_TAB', { uid, tab: existentTab.uid, type, schema, elementName, elementType });
tabUid = existentTab.uid;
}
else {
tabUid = uidGen('T');
commit('NEW_TAB', { uid, tab: tabUid, content, type, autorun, schema, elementName, elementType });
}
}
break;
case 'table-props': {
const existentTab = workspaceTabs
? workspaceTabs.tabs.find(tab =>
tab.elementName === elementName &&
tab.elementType === elementType &&
tab.type === type)
: false;
if (existentTab) {
commit('REPLACE_TAB', { uid, tab: existentTab.uid, type, schema, elementName, elementType });
tabUid = existentTab.uid;
}
else {
tabUid = uidGen('T');
commit('NEW_TAB', { uid, tab: tabUid, content, type, autorun, schema, elementName, elementType });
}
}
case 'new-table':
case 'new-trigger':
case 'new-trigger-function':
case 'new-function':
case 'new-routine':
case 'new-scheduler':
tabUid = uidGen('T');
commit('NEW_TAB', {
uid,
tab: tabUid,
content,
type,
autorun,
schema,
elementName,
elementType
});
break;
case 'temp-data':
case 'temp-trigger-props':
case 'temp-trigger-function-props':
case 'temp-function-props':
@@ -663,6 +618,7 @@ export default {
}
else {
const tempTabs = workspaceTabs ? workspaceTabs.tabs.filter(tab => tab.type.includes('temp-')) : false;
if (tempTabs && tempTabs.length) { // if temp tab already opened
for (const tab of tempTabs) {
if (tab.isChanged) {
@@ -691,6 +647,8 @@ export default {
}
}
break;
case 'data':
case 'table-props':
case 'trigger-props':
case 'trigger-function-props':
case 'function-props':