mirror of
https://github.com/Fabio286/antares.git
synced 2025-06-05 21:59:22 +02:00
Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
8cafade8b1 | |||
d13b708377 | |||
206597e5b8 | |||
c5458159d1 | |||
1476e899d1 | |||
797ab70e7c | |||
f81312aeb0 | |||
3ed5ea023e | |||
9291a7a7b4 | |||
5cfdc9b92d | |||
15b08d7ea8 | |||
acebe435ff | |||
5712b80022 | |||
d38583262e | |||
e0e2131981 | |||
7470bddd70 | |||
33d1fa2290 | |||
a4122b4eaa | |||
e6602d1bfa | |||
f8cf90a89e | |||
41505bde65 | |||
8ebc3bce92 | |||
45e9cdc591 | |||
3cbfc0e148 | |||
a47e9e1b1f | |||
e95d29c7c3 | |||
e954f04828 | |||
|
85c800f85b | ||
|
e0482244d7 | ||
27769f204f | |||
dfb24c65f3 | |||
0fe71572a5 | |||
db577bfef0 | |||
0805b96a75 |
12
.travis.yml
12
.travis.yml
@@ -39,9 +39,9 @@ jobs:
|
||||
script:
|
||||
- npm run build -- -p always
|
||||
|
||||
- stage: Deploy ARM Linux
|
||||
if: tag IS present
|
||||
os: linux
|
||||
arch: arm64
|
||||
script:
|
||||
- npm run build -- --linux AppImage -p always
|
||||
# - stage: Deploy ARM Linux
|
||||
# if: tag IS present
|
||||
# os: linux
|
||||
# arch: arm64
|
||||
# script:
|
||||
# - npm run build -- --linux AppImage -p always
|
41
CHANGELOG.md
41
CHANGELOG.md
@@ -2,6 +2,47 @@
|
||||
|
||||
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.0.11](https://github.com/Fabio286/antares/compare/v0.0.10...v0.0.11) (2020-12-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* auto focus on first input in modals ([1476e89](https://github.com/Fabio286/antares/commit/1476e899d164562f12342ced8c76903b9bdcfa55))
|
||||
* foreign keys management ([206597e](https://github.com/Fabio286/antares/commit/206597e5b891e13e6f7635075bd11599355ab778))
|
||||
* improved data table sorts ([5712b80](https://github.com/Fabio286/antares/commit/5712b8002203b32027f0e820f98a61e2ec965e79))
|
||||
* query tabs auto focus ([f81312a](https://github.com/Fabio286/antares/commit/f81312aeb02ef55affd2ae9e81a9b4cb4c2e6da2))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* data tab sort not maintained at refresh ([15b08d7](https://github.com/Fabio286/antares/commit/15b08d7ea858cb28111c7b548af9a13b1bf0da91))
|
||||
* deletion of rows with non-numeric ID ([d385832](https://github.com/Fabio286/antares/commit/d38583262e672a2b47c5ad0aca0f13c129830a7b))
|
||||
* file field editor not show ([9291a7a](https://github.com/Fabio286/antares/commit/9291a7a7b41e7aeb9b65c7f32e496f523e482272))
|
||||
* improved changes dedection in props tab ([acebe43](https://github.com/Fabio286/antares/commit/acebe435ff6fa1581692fbf7ee1bc23b334e3947))
|
||||
* some properties do not reset after fields changes ([3ed5ea0](https://github.com/Fabio286/antares/commit/3ed5ea023e1852d724b2b59ab156f8722876f85b))
|
||||
* unable to switch tabs when no table selected ([c545815](https://github.com/Fabio286/antares/commit/c5458159d1e30cecf6615086c074d98b4b599637))
|
||||
* wrong field type detection ([5cfdc9b](https://github.com/Fabio286/antares/commit/5cfdc9b92d4b778a7863b02fd64e6445236c89bc))
|
||||
|
||||
### [0.0.10](https://github.com/Fabio286/antares/compare/v0.0.9...v0.0.10) (2020-12-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* approximate totals in table tata tab ([e95d29c](https://github.com/Fabio286/antares/commit/e95d29c7c37e24e7cc14b466f9b539fa667042c2))
|
||||
* create new tables ([e6602d1](https://github.com/Fabio286/antares/commit/e6602d1bfa9ca10c6bb078ee80ddc94fb338763d))
|
||||
* display all keys in properties tab ([27769f2](https://github.com/Fabio286/antares/commit/27769f204f731d20c7ba2f838c02b7c2f28fa0c3))
|
||||
* drop and truncate tables ([a4122b4](https://github.com/Fabio286/antares/commit/a4122b4eaaa5b30d97ba5a93df8c9d21c30bc40b))
|
||||
* index management ([41505bd](https://github.com/Fabio286/antares/commit/41505bde6547c0af3c3413248ad8a0d182838bb1))
|
||||
* tables options edit ([0805b96](https://github.com/Fabio286/antares/commit/0805b96a75e439a7d65e8341ecc86fa938679a9f))
|
||||
* unsaved changes reminder ([33d1fa2](https://github.com/Fabio286/antares/commit/33d1fa22905f477924292135b0dcfefe168ee641))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* index deletion issue ([f8cf90a](https://github.com/Fabio286/antares/commit/f8cf90a89e7367c95e164b7dc669506df392b700))
|
||||
* some problems with properties and data tabs when changing database from sidebar ([0fe7157](https://github.com/Fabio286/antares/commit/0fe71572a5e74c17a5c66237351bb0b02c33e824))
|
||||
* sqlEscaper function wrong quotes conversion ([dfb24c6](https://github.com/Fabio286/antares/commit/dfb24c65f3c395d78d27a2f29e9aa8eeb427cff7))
|
||||
|
||||
### [0.0.9](https://github.com/EStarium/antares/compare/v0.0.8...v0.0.9) (2020-11-13)
|
||||
|
||||
|
||||
|
27
README.md
27
README.md
@@ -4,14 +4,16 @@
|
||||
|
||||
# Antares SQL Client
|
||||
|
||||
 [](https://travis-ci.com/EStarium/antares)  
|
||||
 [](https://travis-ci.com/Fabio286/antares)  
|
||||
|
||||
Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers.
|
||||
My target is to support as many databases as possible, and all major operating systems, including the ARM versions.
|
||||
|
||||
**At the moment this application is an alpha, it lacks many features, and isn't ready as a main SQL client**. However i'm actively working on it (yes, i'm a lone dev), hoping to provide all essential features as soon as possible.
|
||||
**At the moment this application is an alpha, it lacks many features** and supports only MySQL.
|
||||
Most of its current features might be enough for basic MySQL use, so give it a chance and send me your feedback, I would really appreciate it.
|
||||
I'm actively working on it (yes, i'm a lone dev), hoping to provide cool features and fixes as soon as possible.
|
||||
|
||||
🔗 If you are curious to try this early state of Antares you can download and install the [latest release](https://github.com/EStarium/antares/releases).
|
||||
🔗 If you are curious to try this early state of Antares you can download and install the [latest release](https://github.com/Fabio286/antares/releases).
|
||||
👁 To stay tuned for new releases watch this repo on **Release only** channel.
|
||||
🌟 Don't forget to **leave a star** if you appreciate this project.
|
||||
|
||||
@@ -23,14 +25,13 @@ An application created with minimalism and semplicity in mind, with features in
|
||||
|
||||
## How to contribute
|
||||
|
||||
- [Translate Antares](https://github.com/EStarium/antares/wiki/Translate-Antares)
|
||||
- [Translate Antares](https://github.com/Fabio286/antares/wiki/Translate-Antares)
|
||||
|
||||
## Current main features
|
||||
|
||||
- Multiple database connections at same time.
|
||||
- Database management (add/edit/delete).
|
||||
- Tables fields management (add/edit/delete).
|
||||
- Tables content management (add/edit/delete).
|
||||
- Full tables management, including indexes and foreign keys.
|
||||
- Run queries on multiple tabs.
|
||||
- Query suggestions.
|
||||
- Native dark theme.
|
||||
@@ -41,12 +42,13 @@ An application created with minimalism and semplicity in mind, with features in
|
||||
|
||||
This is a roadmap with major features will come in near future.
|
||||
|
||||
- Tables management (add/edit/delete).
|
||||
- Users management (add/edit/delete).
|
||||
- Stored procedures, views, schedulers and triggers support.
|
||||
- Users management (add/edit/delete).
|
||||
- More secure password storage.
|
||||
- Database tools (variables, process list...).
|
||||
- SSL and SSH tunnel support.
|
||||
- Support for other databases.
|
||||
- UI/UX improvements.
|
||||
- Improvements of query editor area.
|
||||
- Improvements of query suggestions.
|
||||
- Query history.
|
||||
@@ -55,7 +57,6 @@ This is a roadmap with major features will come in near future.
|
||||
- Query logs console.
|
||||
- Fake data filler.
|
||||
- Import/export and migration.
|
||||
- SSL and SSH tunnel support.
|
||||
- Themes.
|
||||
|
||||
## Currently supported
|
||||
@@ -80,11 +81,11 @@ This is a roadmap with major features will come in near future.
|
||||
#### • ARM
|
||||
|
||||
- [ ] Windows
|
||||
- [x] Linux
|
||||
- [ ] Linux
|
||||
- [ ] MacOS
|
||||
|
||||
## Translations
|
||||
|
||||
[Giuseppe Gigliotti](https://github.com/ReverbOD) / [Italian Translation](https://github.com/EStarium/antares/pull/20)
|
||||
[Mohd-PH](https://github.com/Mohd-PH) / [Arabic Translation](https://github.com/EStarium/antares/pull/29)
|
||||
[hongkfui](https://github.com/hongkfui) / [Spanish Translation](https://github.com/EStarium/antares/pull/32)
|
||||
[Giuseppe Gigliotti](https://github.com/ReverbOD) / [Italian Translation](https://github.com/Fabio286/antares/pull/20)
|
||||
[Mohd-PH](https://github.com/Mohd-PH) / [Arabic Translation](https://github.com/Fabio286/antares/pull/29)
|
||||
[hongkfui](https://github.com/hongkfui) / [Spanish Translation](https://github.com/Fabio286/antares/pull/32)
|
||||
|
5
jsconfig.json
Normal file
5
jsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"include": [
|
||||
"./src/renderer/**/*"
|
||||
]
|
||||
}
|
19
package.json
19
package.json
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "antares",
|
||||
"productName": "Antares",
|
||||
"version": "0.0.9",
|
||||
"version": "0.0.11",
|
||||
"description": "A cross-platform easy to use SQL client.",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/EStarium/antares.git",
|
||||
"repository": "https://github.com/Fabio286/antares.git",
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development electron-webpack dev",
|
||||
"compile": "electron-webpack",
|
||||
@@ -17,7 +17,7 @@
|
||||
},
|
||||
"author": "Fabio Di Stasio <fabio286@gmail.com>",
|
||||
"build": {
|
||||
"appId": "com.estarium.antares",
|
||||
"appId": "com.fabio286.antares",
|
||||
"artifactName": "${productName}-${version}-${os}_${arch}.${ext}",
|
||||
"dmg": {
|
||||
"contents": [
|
||||
@@ -58,32 +58,31 @@
|
||||
"pg": "^8.5.1",
|
||||
"source-map-support": "^0.5.16",
|
||||
"spectre.css": "^0.5.9",
|
||||
"vue-i18n": "^8.22.1",
|
||||
"vue-i18n": "^8.22.2",
|
||||
"vue-the-mask": "^0.11.1",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuex": "^3.5.1",
|
||||
"vuex": "^3.6.0",
|
||||
"vuex-persist": "^3.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^10.1.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"electron": "^10.1.5",
|
||||
"electron": "^11.0.2",
|
||||
"electron-builder": "^22.9.1",
|
||||
"electron-devtools-installer": "^3.1.1",
|
||||
"electron-webpack": "^2.8.2",
|
||||
"electron-webpack-vue": "^2.4.0",
|
||||
"eslint": "^7.13.0",
|
||||
"eslint-config-standard": "^16.0.1",
|
||||
"eslint": "^7.14.0",
|
||||
"eslint-config-standard": "^16.0.2",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.1.0",
|
||||
"eslint-plugin-vue": "^7.1.0",
|
||||
"monaco-editor-webpack-plugin": "^1.9.1",
|
||||
"node-sass": "^5.0.0",
|
||||
"sass-loader": "^10.1.0",
|
||||
"standard-version": "^9.0.0",
|
||||
"stylelint": "^13.7.2",
|
||||
"stylelint": "^13.8.0",
|
||||
"stylelint-config-standard": "^20.0.0",
|
||||
"stylelint-scss": "^3.18.0",
|
||||
"vue": "^2.6.12",
|
||||
|
@@ -206,7 +206,7 @@ module.exports = [
|
||||
},
|
||||
{
|
||||
name: 'TIMESTAMP',
|
||||
length: true,
|
||||
length: false,
|
||||
collation: false,
|
||||
unsigned: false,
|
||||
zerofill: false
|
||||
|
@@ -1,12 +1,12 @@
|
||||
export const TEXT = ['char', 'varchar'];
|
||||
export const LONG_TEXT = ['text', 'mediumtext', 'longtext'];
|
||||
export const TEXT = ['CHAR', 'VARCHAR'];
|
||||
export const LONG_TEXT = ['TEXT', 'MEDIUMTEXT', 'longtext'];
|
||||
|
||||
export const NUMBER = ['int', 'tinyint', 'smallint', 'mediumint', 'bigint', 'float', 'double', 'decimal', 'bool'];
|
||||
export const NUMBER = ['INT', 'TINYINT', 'SMALLINT', 'MEDIUMINT', 'BIGINT', 'FLOAT', 'DOUBLE', 'DECIMAL', 'BOOL'];
|
||||
|
||||
export const DATE = ['date'];
|
||||
export const TIME = ['time'];
|
||||
export const DATETIME = ['datetime', 'timestamp'];
|
||||
export const DATE = ['DATE'];
|
||||
export const TIME = ['TIME'];
|
||||
export const DATETIME = ['DATETIME', 'TIMESTAMP'];
|
||||
|
||||
export const BLOB = ['blob', 'mediumblob', 'longblob'];
|
||||
export const BLOB = ['BLOB', 'MEDIUMBLOB', 'LONGBLOB'];
|
||||
|
||||
export const BIT = ['bit'];
|
||||
export const BIT = ['BIT'];
|
||||
|
6
src/common/index-types/mysql.js
Normal file
6
src/common/index-types/mysql.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = [
|
||||
'PRIMARY',
|
||||
'INDEX',
|
||||
'UNIQUE',
|
||||
'FULLTEXT'
|
||||
];
|
@@ -11,8 +11,8 @@ const regex = new RegExp(pattern);
|
||||
*/
|
||||
function sqlEscaper (string) {
|
||||
return string.replace(regex, char => {
|
||||
const m = ['\\0', '\\x08', '\\x09', '\\x1a', '\\n', '\\r', '\'', '"', '\\', '\\\\', '%'];
|
||||
const r = ['\\\\0', '\\\\b', '\\\\t', '\\\\z', '\\\\n', '\\\\r', '\'\'', '""', '\\\\', '\\\\\\\\', '\\%'];
|
||||
const m = ['\\0', '\\x08', '\\x09', '\\x1a', '\\n', '\\r', '\'', '\"', '\\', '\\\\', '%'];
|
||||
const r = ['\\\\0', '\\\\b', '\\\\t', '\\\\z', '\\\\n', '\\\\r', '\\\'', '\\\"', '\\\\', '\\\\\\\\', '\\%'];
|
||||
return r[m.indexOf(char)] || char;
|
||||
});
|
||||
}
|
||||
|
@@ -83,6 +83,17 @@ export default connections => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-engines', async (event, uid) => {
|
||||
try {
|
||||
const result = await connections[uid].getEngines();
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('use-schema', async (event, { uid, schema }) => {
|
||||
if (!schema) return;
|
||||
|
||||
|
@@ -14,14 +14,29 @@ export default (connections) => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-table-data', async (event, { uid, schema, table }) => {
|
||||
ipcMain.handle('get-table-data', async (event, { uid, schema, table, sortParams }) => {
|
||||
try {
|
||||
const result = await connections[uid]
|
||||
const query = connections[uid]
|
||||
.select('*')
|
||||
.schema(schema)
|
||||
.from(table)
|
||||
.limit(1000)
|
||||
.run({ details: true });
|
||||
.limit(1000);
|
||||
|
||||
if (sortParams && sortParams.field && sortParams.dir)
|
||||
query.orderBy({ [sortParams.field]: sortParams.dir.toUpperCase() });
|
||||
|
||||
const result = await query.run({ details: true });
|
||||
|
||||
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);
|
||||
|
||||
return { status: 'success', response: result };
|
||||
}
|
||||
@@ -78,11 +93,18 @@ export default (connections) => {
|
||||
});
|
||||
|
||||
ipcMain.handle('delete-table-rows', async (event, params) => {
|
||||
let idString;
|
||||
|
||||
if (typeof params.rows[0] === 'string')
|
||||
idString = params.rows.map(row => `"${row}"`).join(',');
|
||||
else
|
||||
idString = params.rows.join(',');
|
||||
|
||||
try {
|
||||
const result = await connections[params.uid]
|
||||
.schema(params.schema)
|
||||
.delete(params.table)
|
||||
.where({ [params.primary]: `IN (${params.rows.join(',')})` })
|
||||
.where({ [params.primary]: `IN (${idString})` })
|
||||
.run();
|
||||
|
||||
return { status: 'success', response: result };
|
||||
@@ -154,6 +176,16 @@ export default (connections) => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('create-table', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].createTable(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('alter-table', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].alterTable(params);
|
||||
@@ -163,4 +195,24 @@ export default (connections) => {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('truncate-table', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].truncateTable(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('drop-table', async (event, params) => {
|
||||
try {
|
||||
await connections[params.uid].dropTable(params);
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
return { status: 'error', response: err.toString() };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@@ -177,7 +177,7 @@ export class MySQLClient extends AntaresCore {
|
||||
return {
|
||||
name: field.COLUMN_NAME,
|
||||
key: field.COLUMN_KEY.toLowerCase(),
|
||||
type: field.DATA_TYPE,
|
||||
type: field.DATA_TYPE.toUpperCase(),
|
||||
schema: field.TABLE_SCHEMA,
|
||||
table: field.TABLE_NAME,
|
||||
numPrecision: field.NUMERIC_PRECISION,
|
||||
@@ -198,6 +198,30 @@ export class MySQLClient extends AntaresCore {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} params
|
||||
* @param {String} params.schema
|
||||
* @param {String} params.table
|
||||
* @returns {Object} table indexes
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async getTableIndexes ({ schema, table }) {
|
||||
const { rows } = await this.raw(`SHOW INDEXES FROM \`${table}\` FROM \`${schema}\``);
|
||||
|
||||
return rows.map(row => {
|
||||
return {
|
||||
unique: !row.Non_unique,
|
||||
name: row.Key_name,
|
||||
column: row.Column_name,
|
||||
indexType: row.Index_type,
|
||||
type: row.Key_name === 'PRIMARY' ? 'PRIMARY' : !row.Non_unique ? 'UNIQUE' : row.Index_type === 'FULLTEXT' ? 'FULLTEXT' : 'INDEX',
|
||||
cardinality: row.Cardinality,
|
||||
comment: row.Comment,
|
||||
indexComment: row.Index_comment
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} params
|
||||
* @param {String} params.schema
|
||||
@@ -213,17 +237,27 @@ export class MySQLClient extends AntaresCore {
|
||||
.where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'`, REFERENCED_TABLE_NAME: 'IS NOT NULL' })
|
||||
.run();
|
||||
|
||||
const { rows: extras } = await this
|
||||
.select('*')
|
||||
.schema('information_schema')
|
||||
.from('REFERENTIAL_CONSTRAINTS')
|
||||
.where({ CONSTRAINT_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'`, REFERENCED_TABLE_NAME: 'IS NOT NULL' })
|
||||
.run();
|
||||
|
||||
return rows.map(field => {
|
||||
const extra = extras.find(x => x.CONSTRAINT_NAME === field.CONSTRAINT_NAME);
|
||||
return {
|
||||
schema: field.TABLE_SCHEMA,
|
||||
table: field.TABLE_NAME,
|
||||
column: field.COLUMN_NAME,
|
||||
field: field.COLUMN_NAME,
|
||||
position: field.ORDINAL_POSITION,
|
||||
constraintPosition: field.POSITION_IN_UNIQUE_CONSTRAINT,
|
||||
constraintName: field.CONSTRAINT_NAME,
|
||||
refSchema: field.REFERENCED_TABLE_SCHEMA,
|
||||
refTable: field.REFERENCED_TABLE_NAME,
|
||||
refColumn: field.REFERENCED_COLUMN_NAME
|
||||
refField: field.REFERENCED_COLUMN_NAME,
|
||||
onUpdate: extra.UPDATE_RULE,
|
||||
onDelete: extra.DELETE_RULE
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -267,6 +301,48 @@ export class MySQLClient extends AntaresCore {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* SHOW ENGINES
|
||||
*
|
||||
* @returns {Array.<Object>} engines list
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async getEngines () {
|
||||
const sql = 'SHOW ENGINES';
|
||||
const results = await this.raw(sql);
|
||||
|
||||
return results.rows.map(row => {
|
||||
return {
|
||||
name: row.Engine,
|
||||
support: row.Support,
|
||||
comment: row.Comment,
|
||||
transactions: row.Transactions,
|
||||
xa: row.XA,
|
||||
savepoints: row.Savepoints,
|
||||
isDefault: row.Support.includes('DEFAULT')
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* CREATE TABLE
|
||||
*
|
||||
* @returns {Array.<Object>} parameters
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async createTable (params) {
|
||||
const {
|
||||
name,
|
||||
collation,
|
||||
comment,
|
||||
engine
|
||||
} = params;
|
||||
|
||||
const sql = `CREATE TABLE \`${name}\` (\`${name}_ID\` INT NULL) COMMENT='${comment}', COLLATE='${collation}', ENGINE=${engine}`;
|
||||
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* ALTER TABLE
|
||||
*
|
||||
@@ -278,13 +354,22 @@ export class MySQLClient extends AntaresCore {
|
||||
table,
|
||||
additions,
|
||||
deletions,
|
||||
changes
|
||||
changes,
|
||||
indexChanges,
|
||||
foreignChanges,
|
||||
options
|
||||
} = params;
|
||||
|
||||
let sql = `ALTER TABLE \`${table}\` `;
|
||||
const alterColumns = [];
|
||||
|
||||
// ADD
|
||||
// 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 length = addition.numLength || addition.charLength || addition.datePrecision;
|
||||
|
||||
@@ -301,7 +386,27 @@ export class MySQLClient extends AntaresCore {
|
||||
${addition.after ? `AFTER \`${addition.after}\`` : 'FIRST'}`);
|
||||
});
|
||||
|
||||
// CHANGE
|
||||
// ADD INDEX
|
||||
indexChanges.additions.forEach(addition => {
|
||||
const fields = addition.fields.map(field => `\`${field}\``).join(',');
|
||||
let type = addition.type;
|
||||
|
||||
if (type === 'PRIMARY')
|
||||
alterColumns.push(`ADD PRIMARY KEY (${fields})`);
|
||||
else {
|
||||
if (type === 'UNIQUE')
|
||||
type = 'UNIQUE INDEX';
|
||||
|
||||
alterColumns.push(`ADD ${type} \`${addition.name}\` (${fields})`);
|
||||
}
|
||||
});
|
||||
|
||||
// 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}`);
|
||||
});
|
||||
|
||||
// CHANGE FIELDS
|
||||
changes.forEach(change => {
|
||||
const length = change.numLength || change.charLength || change.datePrecision;
|
||||
|
||||
@@ -318,13 +423,77 @@ export class MySQLClient extends AntaresCore {
|
||||
${change.after ? `AFTER \`${change.after}\`` : 'FIRST'}`);
|
||||
});
|
||||
|
||||
// DROP
|
||||
// CHANGE INDEX
|
||||
indexChanges.changes.forEach(change => {
|
||||
if (change.oldType === 'PRIMARY')
|
||||
alterColumns.push('DROP PRIMARY KEY');
|
||||
else
|
||||
alterColumns.push(`DROP INDEX \`${change.oldName}\``);
|
||||
|
||||
const fields = change.fields.map(field => `\`${field}\``).join(',');
|
||||
let type = change.type;
|
||||
|
||||
if (type === 'PRIMARY')
|
||||
alterColumns.push(`ADD PRIMARY KEY (${fields})`);
|
||||
else {
|
||||
if (type === 'UNIQUE')
|
||||
type = 'UNIQUE INDEX';
|
||||
|
||||
alterColumns.push(`ADD ${type} \`${change.name}\` (${fields})`);
|
||||
}
|
||||
});
|
||||
|
||||
// CHANGE FOREIGN KEYS
|
||||
foreignChanges.changes.forEach(change => {
|
||||
alterColumns.push(`DROP FOREIGN KEY \`${change.oldName}\``);
|
||||
alterColumns.push(`ADD CONSTRAINT \`${change.constraintName}\` FOREIGN KEY (\`${change.field}\`) REFERENCES \`${change.refTable}\` (\`${change.refField}\`) ON UPDATE ${change.onUpdate} ON DELETE ${change.onDelete}`);
|
||||
});
|
||||
|
||||
// DROP FIELDS
|
||||
deletions.forEach(deletion => {
|
||||
alterColumns.push(`DROP COLUMN \`${deletion.name}\``);
|
||||
});
|
||||
|
||||
// DROP INDEX
|
||||
indexChanges.deletions.forEach(deletion => {
|
||||
if (deletion.type === 'PRIMARY')
|
||||
alterColumns.push('DROP PRIMARY KEY');
|
||||
else
|
||||
alterColumns.push(`DROP INDEX \`${deletion.name}\``);
|
||||
});
|
||||
|
||||
// DROP FOREIGN KEYS
|
||||
foreignChanges.deletions.forEach(deletion => {
|
||||
alterColumns.push(`DROP FOREIGN KEY \`${deletion.constraintName}\``);
|
||||
});
|
||||
|
||||
sql += alterColumns.join(', ');
|
||||
|
||||
// RENAME
|
||||
if (options.name) sql += `; RENAME TABLE \`${table}\` TO \`${options.name}\``;
|
||||
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* TRUNCATE TABLE
|
||||
*
|
||||
* @returns {Array.<Object>} parameters
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async truncateTable (params) {
|
||||
const sql = `TRUNCATE TABLE \`${params.table}\``;
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* DROP TABLE
|
||||
*
|
||||
* @returns {Array.<Object>} parameters
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
async dropTable (params) {
|
||||
const sql = `DROP TABLE \`${params.table}\``;
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
|
@@ -17,6 +17,7 @@
|
||||
<TheNotificationsBoard />
|
||||
<ModalNewConnection v-if="isNewConnModal" />
|
||||
<ModalSettings v-if="isSettingModal" />
|
||||
<ModalDiscardChanges v-if="isUnsavedDiscardModal" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -35,7 +36,8 @@ export default {
|
||||
TheAppWelcome: () => import(/* webpackChunkName: "TheAppWelcome" */'@/components/TheAppWelcome'),
|
||||
Workspace: () => import(/* webpackChunkName: "Workspace" */'@/components/Workspace'),
|
||||
ModalNewConnection: () => import(/* webpackChunkName: "ModalNewConnection" */'@/components/ModalNewConnection'),
|
||||
ModalSettings: () => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings')
|
||||
ModalSettings: () => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings'),
|
||||
ModalDiscardChanges: () => import(/* webpackChunkName: "ModalDiscardChanges" */'@/components/ModalDiscardChanges')
|
||||
},
|
||||
data () {
|
||||
return {};
|
||||
@@ -46,7 +48,8 @@ export default {
|
||||
isNewConnModal: 'application/isNewModal',
|
||||
isEditModal: 'application/isEditModal',
|
||||
isSettingModal: 'application/isSettingModal',
|
||||
connections: 'connections/getConnections'
|
||||
connections: 'connections/getConnections',
|
||||
isUnsavedDiscardModal: 'workspaces/isUnsavedDiscardModal'
|
||||
})
|
||||
},
|
||||
mounted () {
|
||||
|
@@ -86,9 +86,8 @@ export default {
|
||||
|
||||
.context-container {
|
||||
min-width: 100px;
|
||||
max-width: 150px;
|
||||
z-index: 10;
|
||||
box-shadow: 0 0 1px 0 #000;
|
||||
box-shadow: 0 0 2px 0 #000;
|
||||
padding: 0;
|
||||
background: #1d1d1d;
|
||||
border-radius: 0.1rem;
|
||||
@@ -103,9 +102,28 @@ export default {
|
||||
padding: 0.1rem 0.3rem;
|
||||
cursor: pointer;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
|
||||
.context-submenu {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.2s;
|
||||
position: absolute;
|
||||
left: 100%;
|
||||
top: 0;
|
||||
background: #1d1d1d;
|
||||
box-shadow: 0 0 2px 0 #000;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $primary-color;
|
||||
|
||||
.context-submenu {
|
||||
display: block;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,14 @@
|
||||
<template>
|
||||
<select
|
||||
ref="editField"
|
||||
class="px-1"
|
||||
class="form-select pl-1 pr-4"
|
||||
:class="{'small-select': size === 'small'}"
|
||||
@change="onChange"
|
||||
@blur="$emit('blur')"
|
||||
>
|
||||
<option v-if="!isValidDefault" :value="value">
|
||||
{{ value }} - {{ $t('message.invalidDefault') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="row in foreignList"
|
||||
:key="row.foreignColumn"
|
||||
@@ -30,7 +34,11 @@ export default {
|
||||
},
|
||||
props: {
|
||||
value: [String, Number],
|
||||
keyUsage: Object
|
||||
keyUsage: Object,
|
||||
size: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -40,7 +48,10 @@ export default {
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected'
|
||||
})
|
||||
}),
|
||||
isValidDefault () {
|
||||
return this.foreignList.some(foreign => foreign.foreignColumn.toString() === this.value.toString());
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
let firstTextField;
|
||||
@@ -64,7 +75,7 @@ export default {
|
||||
try { // Foregn list
|
||||
const { status, response } = await Tables.getForeignList({
|
||||
...params,
|
||||
column: this.keyUsage.refColumn,
|
||||
column: this.keyUsage.refField,
|
||||
description: firstTextField
|
||||
});
|
||||
|
||||
|
@@ -19,6 +19,7 @@
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="credentials.user"
|
||||
class="form-input"
|
||||
type="text"
|
||||
@@ -63,6 +64,11 @@ export default {
|
||||
}
|
||||
};
|
||||
},
|
||||
created () {
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
methods: {
|
||||
closeModal () {
|
||||
this.$emit('close-asking');
|
||||
|
57
src/renderer/components/ModalDiscardChanges.vue
Normal file
57
src/renderer/components/ModalDiscardChanges.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.discard')"
|
||||
:cancel-text="$t('word.stay')"
|
||||
@confirm="discardUnsavedChanges"
|
||||
@hide="closeUnsavedChangesModal"
|
||||
>
|
||||
<template slot="header">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-content-save-alert mr-1" /> {{ $t('message.unsavedChanges') }}
|
||||
</div>
|
||||
</template>
|
||||
<div slot="body">
|
||||
<div>
|
||||
{{ $t('message.discardUnsavedChanges') }}
|
||||
</div>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'ModalDiscardChanges',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
discardUnsavedChanges: 'workspaces/discardUnsavedChanges',
|
||||
closeUnsavedChangesModal: 'workspaces/closeUnsavedChangesModal'
|
||||
}),
|
||||
closeModal () {
|
||||
this.$emit('close');
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
this.closeModal();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-container {
|
||||
max-width: 360px;
|
||||
}
|
||||
</style>
|
@@ -20,6 +20,7 @@
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="localConnection.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
@@ -172,6 +173,10 @@ export default {
|
||||
created () {
|
||||
this.localConnection = Object.assign({}, this.connection);
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
|
@@ -33,7 +33,11 @@
|
||||
<label class="form-label">{{ $t('word.collation') }}:</label>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<select v-model="database.collation" class="form-select">
|
||||
<select
|
||||
ref="firstInput"
|
||||
v-model="database.collation"
|
||||
class="form-select"
|
||||
>
|
||||
<option
|
||||
v-for="collation in collations"
|
||||
:key="collation.id"
|
||||
@@ -114,6 +118,10 @@ export default {
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
|
@@ -20,6 +20,7 @@
|
||||
</div>
|
||||
<div class="col-8 col-sm-12">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="connection.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
@@ -182,6 +183,10 @@ export default {
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
|
@@ -19,6 +19,7 @@
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="database.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
@@ -89,6 +90,9 @@ export default {
|
||||
created () {
|
||||
this.database = { ...this.database, collation: this.defaultCollation };
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
|
127
src/renderer/components/ModalNewTable.vue
Normal file
127
src/renderer/components/ModalNewTable.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<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 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 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 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: {
|
||||
table: String,
|
||||
workspace: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
localOptions: {
|
||||
name: '',
|
||||
comment: '',
|
||||
collation: '',
|
||||
engine: ''
|
||||
},
|
||||
isOptionsChanging: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
getDatabaseVariable: 'workspaces/getDatabaseVariable'
|
||||
}),
|
||||
defaultCollation () {
|
||||
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value || '';
|
||||
},
|
||||
defaultEngine () {
|
||||
return this.workspace.engines.find(engine => engine.isDefault).name;
|
||||
}
|
||||
},
|
||||
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>
|
@@ -25,6 +25,7 @@
|
||||
<div class="input-group col-8 col-sm-12">
|
||||
<ForeignKeySelect
|
||||
v-if="foreignKeys.includes(field.name)"
|
||||
ref="formInput"
|
||||
class="form-select"
|
||||
:value.sync="localRow[field.name]"
|
||||
:key-usage="getKeyUsage(field.name)"
|
||||
@@ -32,6 +33,7 @@
|
||||
/>
|
||||
<input
|
||||
v-else-if="inputProps(field).mask"
|
||||
ref="formInput"
|
||||
v-model="localRow[field.name]"
|
||||
v-mask="inputProps(field).mask"
|
||||
class="form-input"
|
||||
@@ -41,6 +43,7 @@
|
||||
>
|
||||
<input
|
||||
v-else-if="inputProps(field).type === 'file'"
|
||||
ref="formInput"
|
||||
class="form-input"
|
||||
type="file"
|
||||
:disabled="fieldsToExclude.includes(field.name)"
|
||||
@@ -49,13 +52,14 @@
|
||||
>
|
||||
<input
|
||||
v-else
|
||||
ref="formInput"
|
||||
v-model="localRow[field.name]"
|
||||
class="form-input"
|
||||
:type="inputProps(field).type"
|
||||
:disabled="fieldsToExclude.includes(field.name)"
|
||||
:tabindex="key+1"
|
||||
>
|
||||
<span class="input-group-addon" :class="`type-${field.type}`">
|
||||
<span class="input-group-addon" :class="`type-${field.type.toLowerCase()}`">
|
||||
{{ field.type }} {{ fieldLength(field) | wrapNumber }}
|
||||
</span>
|
||||
<label class="form-checkbox ml-3" :title="$t('word.insert')">
|
||||
@@ -146,7 +150,7 @@ export default {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
},
|
||||
foreignKeys () {
|
||||
return this.keyUsage.map(key => key.column);
|
||||
return this.keyUsage.map(key => key.field);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -192,6 +196,12 @@ export default {
|
||||
}
|
||||
|
||||
this.localRow = { ...rowObj };
|
||||
|
||||
// Auto focus
|
||||
setTimeout(() => {
|
||||
const firstSelectableInput = this.$refs.formInput.find(input => !input.disabled);
|
||||
firstSelectableInput.focus();
|
||||
}, 20);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
@@ -296,7 +306,7 @@ export default {
|
||||
this.localRow[field] = files[0].path;
|
||||
},
|
||||
getKeyUsage (keyName) {
|
||||
return this.keyUsage.find(key => key.column === keyName);
|
||||
return this.keyUsage.find(key => key.field === keyName);
|
||||
},
|
||||
onKey (e) {
|
||||
e.stopPropagation();
|
||||
|
@@ -111,7 +111,8 @@
|
||||
<h4>{{ appName }}</h4>
|
||||
<p>
|
||||
{{ $t('word.version') }}: {{ appVersion }}<br>
|
||||
<a class="c-hand" @click="openOutside('https://github.com/EStarium/antares')">GitHub</a><br>
|
||||
<a class="c-hand" @click="openOutside('https://github.com/Fabio286/antares')">GitHub</a><br>
|
||||
<small>{{ $t('word.author') }}: <a class="c-hand" @click="openOutside('https://github.com/Fabio286')">Fabio Di Stasio</a></small><br>
|
||||
<small>{{ $t('message.madeWithJS') }}</small>
|
||||
</p>
|
||||
</div>
|
||||
|
@@ -14,7 +14,8 @@ monaco.languages.registerCompletionItemProvider('sql', completionItemProvider(mo
|
||||
export default {
|
||||
name: 'QueryEditor',
|
||||
props: {
|
||||
value: String
|
||||
value: String,
|
||||
autoFocus: { type: Boolean, default: false }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -40,6 +41,12 @@ export default {
|
||||
const content = this.editor.getValue();
|
||||
this.$emit('update:value', content);
|
||||
});
|
||||
|
||||
if (this.autoFocus) {
|
||||
setTimeout(() => {
|
||||
this.editor.focus();
|
||||
}, 20);
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.editor && this.editor.dispose();
|
||||
|
@@ -15,7 +15,7 @@
|
||||
<i class="mdi mdi-18px mdi-coffee mr-1" />
|
||||
<small>{{ $t('word.donate') }}</small>
|
||||
</li>
|
||||
<li class="footer-element footer-link" @click="openOutside('https://github.com/EStarium/antares/issues')">
|
||||
<li class="footer-element footer-link" @click="openOutside('https://github.com/Fabio286/antares/issues')">
|
||||
<i class="mdi mdi-18px mdi-bug" />
|
||||
</li>
|
||||
<li class="footer-element footer-link" @click="showSettingModal('about')">
|
||||
|
@@ -67,6 +67,7 @@
|
||||
</ul>
|
||||
<WorkspacePropsTab
|
||||
v-show="selectedTab === 'prop'"
|
||||
:is-selected="selectedTab === 'prop'"
|
||||
:connection="connection"
|
||||
:table="workspace.breadcrumbs.table"
|
||||
/>
|
||||
@@ -122,7 +123,13 @@ export default {
|
||||
return this.selectedWorkspace === this.connection.uid;
|
||||
},
|
||||
selectedTab () {
|
||||
return this.queryTabs.find(tab => tab.uid === this.workspace.selected_tab) || ['data', 'prop'].includes(this.workspace.selected_tab) ? this.workspace.selected_tab : this.queryTabs[0].uid;
|
||||
if (this.workspace.breadcrumbs.table === null && ['data', 'prop'].includes(this.workspace.selected_tab))
|
||||
return this.queryTabs[0].uid;
|
||||
|
||||
return this.queryTabs.find(tab => tab.uid === this.workspace.selected_tab) ||
|
||||
['data', 'prop'].includes(this.workspace.selected_tab)
|
||||
? this.workspace.selected_tab
|
||||
: this.queryTabs[0].uid;
|
||||
},
|
||||
queryTabs () {
|
||||
return this.workspace.tabs.filter(tab => tab.type === 'query');
|
||||
|
@@ -39,6 +39,7 @@
|
||||
:database="db"
|
||||
:connection="connection"
|
||||
@show-database-context="openDatabaseContext"
|
||||
@show-table-context="openTableContext"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -47,11 +48,25 @@
|
||||
@close="hideNewDBModal"
|
||||
@reload="refresh"
|
||||
/>
|
||||
<ModalNewTable
|
||||
v-if="isNewTableModal"
|
||||
:workspace="workspace"
|
||||
@close="hideCreateTableModal"
|
||||
@open-create-table-editor="openCreateTableEditor"
|
||||
/>
|
||||
<DatabaseContext
|
||||
v-if="isDatabaseContext"
|
||||
:selected-database="selectedDatabase"
|
||||
:context-event="databaseContextEvent"
|
||||
@close-context="closeDatabaseContext"
|
||||
@show-create-table-modal="showCreateTableModal"
|
||||
@reload="refresh"
|
||||
/>
|
||||
<TableContext
|
||||
v-if="isTableContext"
|
||||
:selected-table="selectedTable"
|
||||
:context-event="tableContextEvent"
|
||||
@close-context="closeTableContext"
|
||||
@reload="refresh"
|
||||
/>
|
||||
</div>
|
||||
@@ -60,10 +75,13 @@
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import _ from 'lodash';
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
import WorkspaceConnectPanel from '@/components/WorkspaceConnectPanel';
|
||||
import WorkspaceExploreBarDatabase from '@/components/WorkspaceExploreBarDatabase';
|
||||
import DatabaseContext from '@/components/WorkspaceExploreBarDatabaseContext';
|
||||
import TableContext from '@/components/WorkspaceExploreBarTableContext';
|
||||
import ModalNewDatabase from '@/components/ModalNewDatabase';
|
||||
import ModalNewTable from '@/components/ModalNewTable';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceExploreBar',
|
||||
@@ -71,7 +89,9 @@ export default {
|
||||
WorkspaceConnectPanel,
|
||||
WorkspaceExploreBarDatabase,
|
||||
DatabaseContext,
|
||||
ModalNewDatabase
|
||||
TableContext,
|
||||
ModalNewDatabase,
|
||||
ModalNewTable
|
||||
},
|
||||
props: {
|
||||
connection: Object,
|
||||
@@ -81,6 +101,7 @@ export default {
|
||||
return {
|
||||
isRefreshing: false,
|
||||
isNewDBModal: false,
|
||||
isNewTableModal: false,
|
||||
localWidth: null,
|
||||
isDatabaseContext: false,
|
||||
isTableContext: false,
|
||||
@@ -128,6 +149,9 @@ export default {
|
||||
...mapActions({
|
||||
disconnectWorkspace: 'workspaces/removeConnected',
|
||||
refreshStructure: 'workspaces/refreshStructure',
|
||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
|
||||
selectTab: 'workspaces/selectTab',
|
||||
addNotification: 'notifications/addNotification',
|
||||
changeExplorebarSize: 'settings/changeExplorebarSize'
|
||||
}),
|
||||
async refresh () {
|
||||
@@ -153,15 +177,44 @@ export default {
|
||||
hideNewDBModal () {
|
||||
this.isNewDBModal = false;
|
||||
},
|
||||
showCreateTableModal () {
|
||||
this.closeDatabaseContext();
|
||||
this.isNewTableModal = true;
|
||||
},
|
||||
hideCreateTableModal () {
|
||||
this.isNewTableModal = false;
|
||||
},
|
||||
async openCreateTableEditor (payload) {
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
...payload
|
||||
};
|
||||
|
||||
const { status, response } = await Tables.createTable(params);
|
||||
|
||||
if (status === 'success') {
|
||||
await this.refresh();
|
||||
this.changeBreadcrumbs({ schema: this.selectedDatabase, table: payload.name });
|
||||
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
},
|
||||
openDatabaseContext (payload) {
|
||||
this.isTableContext = false;
|
||||
this.selectedDatabase = payload.database;
|
||||
this.databaseContextEvent = payload.event;
|
||||
this.isDatabaseContext = true;
|
||||
},
|
||||
closeDatabaseContext () {
|
||||
this.isDatabaseContext = false;
|
||||
this.selectedDatabase = '';
|
||||
},
|
||||
openTableContext (payload) {
|
||||
this.selectedTable = payload.table;
|
||||
this.tableContextEvent = payload.event;
|
||||
this.isTableContext = true;
|
||||
},
|
||||
closeTableContext () {
|
||||
this.isTableContext = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -18,7 +18,7 @@
|
||||
:key="table.name"
|
||||
class="menu-item"
|
||||
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.table === table.name}"
|
||||
@click="changeBreadcrumbs({schema: database.name, table: table.name})"
|
||||
@click="setBreadcrumbs({schema: database.name, table: table.name})"
|
||||
@contextmenu.prevent="showTableContext($event, table.name)"
|
||||
>
|
||||
<a class="table-name">
|
||||
@@ -70,12 +70,16 @@ export default {
|
||||
showDatabaseContext (event, database) {
|
||||
this.$emit('show-database-context', { event, database });
|
||||
},
|
||||
showTableContext (table) {
|
||||
this.$emit('show-table-context', table);
|
||||
showTableContext (event, table) {
|
||||
this.$emit('show-table-context', { event, table });
|
||||
},
|
||||
piePercentage (val) {
|
||||
const perc = val / this.maxSize * 100;
|
||||
return { background: `conic-gradient(lime ${perc}%, white 0)` };
|
||||
},
|
||||
setBreadcrumbs (payload) {
|
||||
if (this.breadcrumbs.schema === payload.schema && this.breadcrumbs.table === payload.table) return;
|
||||
this.changeBreadcrumbs(payload);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -3,15 +3,20 @@
|
||||
:context-event="contextEvent"
|
||||
@close-context="closeContext"
|
||||
>
|
||||
<!-- <div class="context-element">
|
||||
<div class="context-element">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-plus text-light pr-1" /> {{ $t('word.add') }}</span>
|
||||
<i class="mdi mdi-18px mdi-chevron-right text-light pl-1" />
|
||||
</div> -->
|
||||
<div class="context-submenu">
|
||||
<div class="context-element" @click="showCreateTableModal">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-table text-light pr-1" /> {{ $t('word.table') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="context-element" @click="showEditModal">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-pencil text-light pr-1" /> {{ $t('word.edit') }}</span>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-database-edit text-light pr-1" /> {{ $t('word.edit') }}</span>
|
||||
</div>
|
||||
<div class="context-element" @click="showDeleteModal">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ $t('word.delete') }}</span>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-database-remove text-light pr-1" /> {{ $t('word.delete') }}</span>
|
||||
</div>
|
||||
|
||||
<ConfirmModal
|
||||
@@ -64,15 +69,21 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected'
|
||||
})
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
deleteConnection: 'connections/deleteConnection',
|
||||
showEditModal: 'application/showEditConnModal',
|
||||
addNotification: 'notifications/addNotification'
|
||||
addNotification: 'notifications/addNotification',
|
||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
|
||||
}),
|
||||
showCreateTableModal () {
|
||||
this.$emit('show-create-table-modal');
|
||||
},
|
||||
showDeleteModal () {
|
||||
this.isDeleteModal = true;
|
||||
},
|
||||
@@ -97,6 +108,9 @@ export default {
|
||||
});
|
||||
|
||||
if (status === 'success') {
|
||||
if (this.selectedDatabase === this.workspace.breadcrumbs.schema)
|
||||
this.changeBreadcrumbs({ schema: null });
|
||||
|
||||
this.closeContext();
|
||||
this.$emit('reload');
|
||||
}
|
||||
|
146
src/renderer/components/WorkspaceExploreBarTableContext.vue
Normal file
146
src/renderer/components/WorkspaceExploreBarTableContext.vue
Normal file
@@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<BaseContextMenu
|
||||
:context-event="contextEvent"
|
||||
@close-context="closeContext"
|
||||
>
|
||||
<div class="context-element" @click="showEmptyModal">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-table-off text-light pr-1" /> {{ $t('message.emptyTable') }}</span>
|
||||
</div>
|
||||
<div class="context-element" @click="showDeleteModal">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-table-remove text-light pr-1" /> {{ $t('word.delete') }}</span>
|
||||
</div>
|
||||
|
||||
<ConfirmModal
|
||||
v-if="isEmptyModal"
|
||||
@confirm="emptyTable"
|
||||
@hide="hideEmptyModal"
|
||||
>
|
||||
<template slot="header">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-table-off mr-1" /> {{ $t('message.emptyTable') }}
|
||||
</div>
|
||||
</template>
|
||||
<div slot="body">
|
||||
<div class="mb-2">
|
||||
{{ $t('message.emptyCorfirm') }} "<b>{{ selectedTable }}</b>"?
|
||||
</div>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
<ConfirmModal
|
||||
v-if="isDeleteModal"
|
||||
@confirm="deleteTable"
|
||||
@hide="hideDeleteModal"
|
||||
>
|
||||
<template slot="header">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-table-remove mr-1" /> {{ $t('message.deleteTable') }}
|
||||
</div>
|
||||
</template>
|
||||
<div slot="body">
|
||||
<div class="mb-2">
|
||||
{{ $t('message.deleteCorfirm') }} "<b>{{ selectedTable }}</b>"?
|
||||
</div>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</BaseContextMenu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import BaseContextMenu from '@/components/BaseContextMenu';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceExploreBarTableContext',
|
||||
components: {
|
||||
BaseContextMenu,
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
contextEvent: MouseEvent,
|
||||
selectedTable: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isDeleteModal: false,
|
||||
isEmptyModal: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.selectedWorkspace);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
|
||||
}),
|
||||
showCreateTableModal () {
|
||||
this.$emit('show-create-table-modal');
|
||||
},
|
||||
showDeleteModal () {
|
||||
this.isDeleteModal = true;
|
||||
},
|
||||
hideDeleteModal () {
|
||||
this.isDeleteModal = false;
|
||||
},
|
||||
showEmptyModal () {
|
||||
this.isEmptyModal = true;
|
||||
},
|
||||
hideEmptyModal () {
|
||||
this.isEmptyModal = false;
|
||||
},
|
||||
closeContext () {
|
||||
this.$emit('close-context');
|
||||
},
|
||||
async emptyTable () {
|
||||
try {
|
||||
const { status, response } = await Tables.truncateTable({
|
||||
uid: this.selectedWorkspace,
|
||||
table: this.selectedTable
|
||||
});
|
||||
|
||||
if (status === 'success') {
|
||||
if (this.selectedTable === this.workspace.breadcrumbs.table)
|
||||
this.changeBreadcrumbs({ table: null });
|
||||
|
||||
this.closeContext();
|
||||
this.$emit('reload');
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
},
|
||||
async deleteTable () {
|
||||
try {
|
||||
const { status, response } = await Tables.dropTable({
|
||||
uid: this.selectedWorkspace,
|
||||
table: this.selectedTable
|
||||
});
|
||||
|
||||
if (status === 'success') {
|
||||
if (this.selectedTable === this.workspace.breadcrumbs.table)
|
||||
this.changeBreadcrumbs({ table: null });
|
||||
|
||||
this.closeContext();
|
||||
this.$emit('reload');
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
416
src/renderer/components/WorkspacePropsForeignModal.vue
Normal file
416
src/renderer/components/WorkspacePropsForeignModal.vue
Normal file
@@ -0,0 +1,416 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.confirm')"
|
||||
size="medium"
|
||||
@confirm="confirmForeignsChange"
|
||||
@hide="$emit('hide')"
|
||||
>
|
||||
<template :slot="'header'">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-key-link mr-1" /> {{ $t('word.foreignKeys') }} "{{ table }}"
|
||||
</div>
|
||||
</template>
|
||||
<div :slot="'body'">
|
||||
<div class="columns col-gapless">
|
||||
<div class="column col-5">
|
||||
<div class="panel" :style="{ height: modalInnerHeight + 'px'}">
|
||||
<div class="panel-header pt-0 pl-0">
|
||||
<div class="d-flex">
|
||||
<button class="btn btn-dark btn-sm d-flex" @click="addForeign">
|
||||
<span>{{ $t('word.add') }}</span>
|
||||
<i class="mdi mdi-24px mdi-link-plus ml-1" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark btn-sm d-flex ml-2 mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
:disabled="!isChanged"
|
||||
@click.prevent="clearChanges"
|
||||
>
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="indexesPanel" class="panel-body p-0 pr-1">
|
||||
<div
|
||||
v-for="foreign in foreignProxy"
|
||||
:key="foreign._id"
|
||||
class="tile tile-centered c-hand mb-1 p-1"
|
||||
:class="{'selected-foreign': selectedForeignID === foreign._id}"
|
||||
@click="selectForeign($event, foreign._id)"
|
||||
>
|
||||
<div class="tile-icon">
|
||||
<div>
|
||||
<i class="mdi mdi-key-link mdi-24px" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile-content">
|
||||
<div class="tile-title">
|
||||
{{ foreign.constraintName }}
|
||||
</div>
|
||||
<small class="tile-subtitle text-gray d-flex">
|
||||
<i class="mdi mdi-link-variant mr-1" />
|
||||
<div class="fk-details-wrapper">
|
||||
<span v-if="foreign.table !== ''" class="fk-details">
|
||||
<i class="mdi mdi-table mr-1" />
|
||||
<span>{{ foreign.table }}.{{ foreign.field }}</span>
|
||||
</span>
|
||||
<span v-if="foreign.refTable !== ''" class="fk-details">
|
||||
<i class="mdi mdi-table mr-1" />
|
||||
<span>{{ foreign.refTable }}.{{ foreign.refField }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</small>
|
||||
</div>
|
||||
<div class="tile-action">
|
||||
<button
|
||||
class="btn btn-link remove-field p-0 mr-2"
|
||||
:title="$t('word.delete')"
|
||||
@click.prevent="removeIndex(foreign._id)"
|
||||
>
|
||||
<i class="mdi mdi-close" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column col-7 pl-2 editor-col">
|
||||
<form
|
||||
v-if="selectedForeignObj"
|
||||
:style="{ height: modalInnerHeight + 'px'}"
|
||||
class="form-horizontal"
|
||||
>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.name') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
v-model="selectedForeignObj.constraintName"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-4">
|
||||
<label class="form-label col-3">
|
||||
{{ $tc('word.field', 1) }}
|
||||
</label>
|
||||
<div class="fields-list column pt-1">
|
||||
<label
|
||||
v-for="(field, i) in fields"
|
||||
:key="`${field.name}-${i}`"
|
||||
class="form-checkbox m-0"
|
||||
@click.prevent="toggleField(field.name)"
|
||||
>
|
||||
<input type="checkbox" :checked="selectedForeignObj.field === field.name">
|
||||
<i class="form-icon" /> {{ field.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3 pt-0">
|
||||
{{ $t('message.referenceTable') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select
|
||||
v-model="selectedForeignObj.refTable"
|
||||
class="form-select"
|
||||
@change="reloadRefFields"
|
||||
>
|
||||
<option
|
||||
v-for="schemaTable in schemaTables"
|
||||
:key="schemaTable.name"
|
||||
:value="schemaTable.name"
|
||||
>
|
||||
{{ schemaTable.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-4">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('message.referenceField') }}
|
||||
</label>
|
||||
<div class="fields-list column pt-1">
|
||||
<label
|
||||
v-for="(field, i) in refFields[selectedForeignID]"
|
||||
:key="`${field.name}-${i}`"
|
||||
class="form-checkbox m-0"
|
||||
@click.prevent="toggleRefField(field.name)"
|
||||
>
|
||||
<input type="checkbox" :checked="selectedForeignObj.refField === field.name && selectedForeignObj.refTable === field.table">
|
||||
<i class="form-icon" /> {{ field.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('message.onUpdate') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="selectedForeignObj.onUpdate" class="form-select">
|
||||
<option
|
||||
v-for="action in foreignActions"
|
||||
:key="action"
|
||||
:value="action"
|
||||
>
|
||||
{{ action }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('message.onDelete') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="selectedForeignObj.onDelete" class="form-select">
|
||||
<option
|
||||
v-for="action in foreignActions"
|
||||
:key="action"
|
||||
:value="action"
|
||||
>
|
||||
{{ action }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div v-if="!foreignProxy.length" class="empty">
|
||||
<div class="empty-icon">
|
||||
<i class="mdi mdi-key-link mdi-48px" />
|
||||
</div>
|
||||
<p class="empty-title h5">
|
||||
{{ $t('message.thereAreNoForeign') }}
|
||||
</p>
|
||||
<div class="empty-action">
|
||||
<button class="btn btn-primary" @click="addForeign">
|
||||
{{ $t('message.createNewForeign') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsForeignModal',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
localKeyUsage: Array,
|
||||
connection: Object,
|
||||
table: String,
|
||||
schema: String,
|
||||
schemaTables: Array,
|
||||
fields: Array,
|
||||
workspace: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
foreignProxy: [],
|
||||
isOptionsChanging: false,
|
||||
selectedForeignID: '',
|
||||
modalInnerHeight: 400,
|
||||
refFields: {},
|
||||
foreignActions: [
|
||||
'RESTRICT',
|
||||
'CASCADE',
|
||||
'SET NULL',
|
||||
'NO ACTION'
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selectedForeignObj () {
|
||||
return this.foreignProxy.find(foreign => foreign._id === this.selectedForeignID);
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.localKeyUsage) !== JSON.stringify(this.foreignProxy);
|
||||
},
|
||||
hasPrimary () {
|
||||
return this.foreignProxy.some(foreign => foreign.type === 'PRIMARY');
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.foreignProxy = JSON.parse(JSON.stringify(this.localKeyUsage));
|
||||
|
||||
if (this.foreignProxy.length)
|
||||
this.resetSelectedID();
|
||||
|
||||
if (this.selectedForeignObj)
|
||||
this.getRefFields();
|
||||
|
||||
this.getModalInnerHeight();
|
||||
window.addEventListener('resize', this.getModalInnerHeight);
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('resize', this.getModalInnerHeight);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification'
|
||||
}),
|
||||
confirmForeignsChange () {
|
||||
this.$emit('foreigns-update', this.foreignProxy);
|
||||
},
|
||||
selectForeign (event, id) {
|
||||
if (this.selectedForeignID !== id && !event.target.classList.contains('remove-field'))
|
||||
this.selectedForeignID = id;
|
||||
},
|
||||
getModalInnerHeight () {
|
||||
const modalBody = document.querySelector('.modal-body');
|
||||
if (modalBody)
|
||||
this.modalInnerHeight = modalBody.clientHeight - (parseFloat(getComputedStyle(modalBody).paddingTop) + parseFloat(getComputedStyle(modalBody).paddingBottom));
|
||||
},
|
||||
addForeign () {
|
||||
this.foreignProxy = [...this.foreignProxy, {
|
||||
_id: uidGen(),
|
||||
constraintName: `FK_${this.foreignProxy.length + 1}`,
|
||||
refSchema: this.schema,
|
||||
table: this.table,
|
||||
refTable: '',
|
||||
field: '',
|
||||
refField: '',
|
||||
onUpdate: this.foreignActions[0],
|
||||
onDelete: this.foreignActions[0]
|
||||
}];
|
||||
|
||||
if (this.foreignProxy.length === 1)
|
||||
this.resetSelectedID();
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.indexesPanel.scrollTop = this.$refs.indexesPanel.scrollHeight + 60;
|
||||
}, 20);
|
||||
},
|
||||
removeIndex (id) {
|
||||
this.foreignProxy = this.foreignProxy.filter(foreign => foreign._id !== id);
|
||||
|
||||
if (this.selectedForeignID === id && this.foreignProxy.length)
|
||||
this.resetSelectedID();
|
||||
},
|
||||
clearChanges () {
|
||||
this.foreignProxy = JSON.parse(JSON.stringify(this.localKeyUsage));
|
||||
if (!this.foreignProxy.some(foreign => foreign._id === this.selectedForeignID))
|
||||
this.resetSelectedID();
|
||||
},
|
||||
toggleField (field) {
|
||||
this.foreignProxy = this.foreignProxy.map(foreign => {
|
||||
if (foreign._id === this.selectedForeignID)
|
||||
foreign.field = field;
|
||||
|
||||
return foreign;
|
||||
});
|
||||
},
|
||||
toggleRefField (field) {
|
||||
this.foreignProxy = this.foreignProxy.map(foreign => {
|
||||
if (foreign._id === this.selectedForeignID)
|
||||
foreign.refField = field;
|
||||
|
||||
return foreign;
|
||||
});
|
||||
},
|
||||
resetSelectedID () {
|
||||
this.selectedForeignID = this.foreignProxy.length ? this.foreignProxy[0]._id : '';
|
||||
},
|
||||
async getRefFields () {
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.selectedForeignObj.refSchema,
|
||||
table: this.selectedForeignObj.refTable
|
||||
};
|
||||
|
||||
try { // Field data
|
||||
const { status, response } = await Tables.getTableColumns(params);
|
||||
if (status === 'success') {
|
||||
this.refFields = {
|
||||
...this.refFields,
|
||||
[this.selectedForeignID]: response
|
||||
};
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
},
|
||||
reloadRefFields () {
|
||||
this.selectedForeignObj.refField = '';
|
||||
this.getRefFields();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tile {
|
||||
border-radius: 2px;
|
||||
opacity: 0.5;
|
||||
transition: background 0.2s;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
.tile-action {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $bg-color-light;
|
||||
|
||||
.tile-action {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected-foreign {
|
||||
background: $bg-color-light;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-col {
|
||||
border-left: 2px solid $bg-color-light;
|
||||
}
|
||||
|
||||
.fields-list {
|
||||
max-height: 80px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.remove-field .mdi {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.fk-details-wrapper {
|
||||
max-width: calc(100% - 1rem);
|
||||
|
||||
.fk-details {
|
||||
display: flex;
|
||||
line-height: 1;
|
||||
align-items: baseline;
|
||||
|
||||
> span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
284
src/renderer/components/WorkspacePropsIndexesModal.vue
Normal file
284
src/renderer/components/WorkspacePropsIndexesModal.vue
Normal file
@@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
:confirm-text="$t('word.confirm')"
|
||||
size="medium"
|
||||
@confirm="confirmIndexesChange"
|
||||
@hide="$emit('hide')"
|
||||
>
|
||||
<template :slot="'header'">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-key mdi-rotate-45 mr-1" /> {{ $t('word.indexes') }} "{{ table }}"
|
||||
</div>
|
||||
</template>
|
||||
<div :slot="'body'">
|
||||
<div class="columns col-gapless">
|
||||
<div class="column col-5">
|
||||
<div class="panel" :style="{ height: modalInnerHeight + 'px'}">
|
||||
<div class="panel-header pt-0 pl-0">
|
||||
<div class="d-flex">
|
||||
<button class="btn btn-dark btn-sm d-flex" @click="addIndex">
|
||||
<span>{{ $t('word.add') }}</span>
|
||||
<i class="mdi mdi-24px mdi-key-plus ml-1" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark btn-sm d-flex ml-2 mr-0"
|
||||
:title="$t('message.clearChanges')"
|
||||
:disabled="!isChanged"
|
||||
@click.prevent="clearChanges"
|
||||
>
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="indexesPanel" class="panel-body p-0 pr-1">
|
||||
<div
|
||||
v-for="index in indexesProxy"
|
||||
:key="index._id"
|
||||
class="tile tile-centered c-hand mb-1 p-1"
|
||||
:class="{'selected-index': selectedIndexID === index._id}"
|
||||
@click="selectIndex($event, index._id)"
|
||||
>
|
||||
<div class="tile-icon">
|
||||
<div>
|
||||
<i class="mdi mdi-key mdi-24px column-key" :class="`key-${index.type}`" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile-content">
|
||||
<div class="tile-title">
|
||||
{{ index.name }}
|
||||
</div>
|
||||
<small class="tile-subtitle text-gray">{{ index.type }} · {{ index.fields.length }} {{ $tc('word.field', index.fields.length) }}</small>
|
||||
</div>
|
||||
<div class="tile-action">
|
||||
<button
|
||||
class="btn btn-link remove-field p-0 mr-2"
|
||||
:title="$t('word.delete')"
|
||||
@click.prevent="removeIndex(index._id)"
|
||||
>
|
||||
<i class="mdi mdi-close" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column col-7 pl-2 editor-col">
|
||||
<form
|
||||
v-if="selectedIndexObj"
|
||||
:style="{ height: modalInnerHeight + 'px'}"
|
||||
class="form-horizontal"
|
||||
>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.name') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
v-model="selectedIndexObj.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.type') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="selectedIndexObj.type" class="form-select">
|
||||
<option
|
||||
v-for="index in indexTypes"
|
||||
:key="index"
|
||||
:value="index"
|
||||
:disabled="index === 'PRIMARY' && hasPrimary"
|
||||
>
|
||||
{{ index }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $tc('word.field', fields.length) }}
|
||||
</label>
|
||||
<div class="fields-list column pt-1">
|
||||
<label
|
||||
v-for="(field, i) in fields"
|
||||
:key="`${field.name}-${i}`"
|
||||
class="form-checkbox m-0"
|
||||
@click.prevent="toggleField(field.name)"
|
||||
>
|
||||
<input type="checkbox" :checked="selectedIndexObj.fields.some(f => f === field.name)">
|
||||
<i class="form-icon" /> {{ field.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div v-if="!indexesProxy.length" class="empty">
|
||||
<div class="empty-icon">
|
||||
<i class="mdi mdi-key-outline mdi-48px" />
|
||||
</div>
|
||||
<p class="empty-title h5">
|
||||
{{ $t('message.thereAreNoIndexes') }}
|
||||
</p>
|
||||
<div class="empty-action">
|
||||
<button class="btn btn-primary" @click="addIndex">
|
||||
{{ $t('message.createNewIndex') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsIndexesModal',
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
localIndexes: Array,
|
||||
table: String,
|
||||
fields: Array,
|
||||
workspace: Object,
|
||||
indexTypes: Array
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
indexesProxy: [],
|
||||
isOptionsChanging: false,
|
||||
selectedIndexID: '',
|
||||
modalInnerHeight: 400
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selectedIndexObj () {
|
||||
return this.indexesProxy.find(index => index._id === this.selectedIndexID);
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.localIndexes) !== JSON.stringify(this.indexesProxy);
|
||||
},
|
||||
hasPrimary () {
|
||||
return this.indexesProxy.some(index => index.type === 'PRIMARY');
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.indexesProxy = JSON.parse(JSON.stringify(this.localIndexes));
|
||||
|
||||
if (this.indexesProxy.length)
|
||||
this.resetSelectedID();
|
||||
|
||||
this.getModalInnerHeight();
|
||||
window.addEventListener('resize', this.getModalInnerHeight);
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('resize', this.getModalInnerHeight);
|
||||
},
|
||||
methods: {
|
||||
confirmIndexesChange () {
|
||||
this.$emit('indexes-update', this.indexesProxy);
|
||||
},
|
||||
selectIndex (event, id) {
|
||||
if (this.selectedIndexID !== id && !event.target.classList.contains('remove-field'))
|
||||
this.selectedIndexID = id;
|
||||
},
|
||||
getModalInnerHeight () {
|
||||
const modalBody = document.querySelector('.modal-body');
|
||||
if (modalBody)
|
||||
this.modalInnerHeight = modalBody.clientHeight - (parseFloat(getComputedStyle(modalBody).paddingTop) + parseFloat(getComputedStyle(modalBody).paddingBottom));
|
||||
},
|
||||
addIndex () {
|
||||
this.indexesProxy = [...this.indexesProxy, {
|
||||
_id: uidGen(),
|
||||
name: 'NEW_INDEX',
|
||||
fields: [],
|
||||
type: 'INDEX',
|
||||
comment: '',
|
||||
indexType: 'BTREE',
|
||||
indexComment: '',
|
||||
cardinality: 0
|
||||
}];
|
||||
|
||||
if (this.indexesProxy.length === 1)
|
||||
this.resetSelectedID();
|
||||
|
||||
setTimeout(() => {
|
||||
this.$refs.indexesPanel.scrollTop = this.$refs.indexesPanel.scrollHeight + 60;
|
||||
}, 20);
|
||||
},
|
||||
removeIndex (id) {
|
||||
this.indexesProxy = this.indexesProxy.filter(index => index._id !== id);
|
||||
|
||||
if (this.selectedIndexID === id && this.indexesProxy.length)
|
||||
this.resetSelectedID();
|
||||
},
|
||||
clearChanges () {
|
||||
this.indexesProxy = JSON.parse(JSON.stringify(this.localIndexes));
|
||||
if (!this.indexesProxy.some(index => index._id === this.selectedIndexID))
|
||||
this.resetSelectedID();
|
||||
},
|
||||
toggleField (field) {
|
||||
this.indexesProxy = this.indexesProxy.map(index => {
|
||||
if (index._id === this.selectedIndexID) {
|
||||
if (index.fields.includes(field))
|
||||
index.fields = index.fields.filter(f => f !== field);
|
||||
else
|
||||
index.fields.push(field);
|
||||
}
|
||||
return index;
|
||||
});
|
||||
},
|
||||
resetSelectedID () {
|
||||
this.selectedIndexID = this.indexesProxy.length ? this.indexesProxy[0]._id : '';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tile {
|
||||
border-radius: 2px;
|
||||
opacity: 0.5;
|
||||
transition: background 0.2s;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
.tile-action {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $bg-color-light;
|
||||
|
||||
.tile-action {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected-index {
|
||||
background: $bg-color-light;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-col {
|
||||
border-left: 2px solid $bg-color-light;
|
||||
}
|
||||
|
||||
.fields-list {
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.remove-field .mdi {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
130
src/renderer/components/WorkspacePropsOptionsModal.vue
Normal file
130
src/renderer/components/WorkspacePropsOptionsModal.vue
Normal file
@@ -0,0 +1,130 @@
|
||||
<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" /> {{ $t('word.options') }} "{{ table }}"
|
||||
</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 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('word.autoIncrement') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<input
|
||||
v-model="optionsProxy.autoIncrement"
|
||||
class="form-input"
|
||||
type="number"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div 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 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>
|
@@ -32,15 +32,19 @@
|
||||
<span>{{ $t('word.add') }}</span>
|
||||
<i class="mdi mdi-24px mdi-playlist-plus ml-1" />
|
||||
</button>
|
||||
<button class="btn btn-dark btn-sm d-none">
|
||||
<button
|
||||
class="btn btn-dark btn-sm"
|
||||
:title="$t('message.manageIndexes')"
|
||||
@click="showIntdexesModal"
|
||||
>
|
||||
<span>{{ $t('word.indexes') }}</span>
|
||||
<i class="mdi mdi-24px mdi-key mdi-rotate-45 ml-1" />
|
||||
</button>
|
||||
<button class="btn btn-dark btn-sm d-none">
|
||||
<button class="btn btn-dark btn-sm" @click="showForeignModal">
|
||||
<span>{{ $t('word.foreignKeys') }}</span>
|
||||
<i class="mdi mdi-24px mdi-key-link ml-1" />
|
||||
</button>
|
||||
<button class="btn btn-dark btn-sm d-none">
|
||||
<button class="btn btn-dark btn-sm" @click="showOptionsModal">
|
||||
<span>{{ $t('word.options') }}</span>
|
||||
<i class="mdi mdi-24px mdi-cogs ml-1" />
|
||||
</button>
|
||||
@@ -50,16 +54,51 @@
|
||||
<div class="workspace-query-results column col-12">
|
||||
<WorkspacePropsTable
|
||||
v-if="localFields"
|
||||
ref="queryTable"
|
||||
ref="indexTable"
|
||||
:fields="localFields"
|
||||
:indexes="localIndexes"
|
||||
:foreigns="localKeyUsage"
|
||||
:tab-uid="tabUid"
|
||||
:conn-uid="connection.uid"
|
||||
:index-types="workspace.indexTypes"
|
||||
:table="table"
|
||||
:schema="schema"
|
||||
mode="table"
|
||||
@remove-field="removeField"
|
||||
@add-new-index="addNewIndex"
|
||||
@add-to-index="addToIndex"
|
||||
/>
|
||||
</div>
|
||||
<WorkspacePropsOptionsModal
|
||||
v-if="isOptionsModal"
|
||||
:local-options="localOptions"
|
||||
:table="table"
|
||||
:workspace="workspace"
|
||||
@hide="hideOptionsModal"
|
||||
@options-update="optionsUpdate"
|
||||
/>
|
||||
<WorkspacePropsIndexesModal
|
||||
v-if="isIndexesModal"
|
||||
:local-indexes="localIndexes"
|
||||
:table="table"
|
||||
:fields="localFields"
|
||||
:index-types="workspace.indexTypes"
|
||||
:workspace="workspace"
|
||||
@hide="hideIndexesModal"
|
||||
@indexes-update="indexesUpdate"
|
||||
/>
|
||||
<WorkspacePropsForeignModal
|
||||
v-if="isForeignModal"
|
||||
:local-key-usage="localKeyUsage"
|
||||
:connection="connection"
|
||||
:table="table"
|
||||
:schema="schema"
|
||||
:schema-tables="schemaTables"
|
||||
:fields="localFields"
|
||||
:workspace="workspace"
|
||||
@hide="hideForeignModal"
|
||||
@foreigns-update="foreignsUpdate"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -68,11 +107,17 @@ import { mapGetters, mapActions } from 'vuex';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
import WorkspacePropsTable from '@/components/WorkspacePropsTable';
|
||||
import WorkspacePropsOptionsModal from '@/components/WorkspacePropsOptionsModal';
|
||||
import WorkspacePropsIndexesModal from '@/components/WorkspacePropsIndexesModal';
|
||||
import WorkspacePropsForeignModal from '@/components/WorkspacePropsForeignModal';
|
||||
|
||||
export default {
|
||||
name: 'WorkspacePropsTab',
|
||||
components: {
|
||||
WorkspacePropsTable
|
||||
WorkspacePropsTable,
|
||||
WorkspacePropsOptionsModal,
|
||||
WorkspacePropsIndexesModal,
|
||||
WorkspacePropsForeignModal
|
||||
},
|
||||
props: {
|
||||
connection: Object,
|
||||
@@ -83,29 +128,54 @@ export default {
|
||||
tabUid: 'prop',
|
||||
isQuering: false,
|
||||
isSaving: false,
|
||||
isOptionsModal: false,
|
||||
isIndexesModal: false,
|
||||
isForeignModal: false,
|
||||
isOptionsChanging: false,
|
||||
originalFields: [],
|
||||
localFields: [],
|
||||
originalKeyUsage: [],
|
||||
localKeyUsage: [],
|
||||
originalIndexes: [],
|
||||
localIndexes: [],
|
||||
localOptions: {},
|
||||
lastTable: null,
|
||||
isAddModal: false
|
||||
newFieldsCounter: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
getWorkspace: 'workspaces/getWorkspace',
|
||||
getDatabaseVariable: 'workspaces/getDatabaseVariable'
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
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 || '';
|
||||
},
|
||||
isSelected () {
|
||||
return this.workspace.selected_tab === 'prop';
|
||||
},
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
},
|
||||
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);
|
||||
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: {
|
||||
@@ -120,15 +190,23 @@ export default {
|
||||
this.getFieldsData();
|
||||
this.lastTable = this.table;
|
||||
}
|
||||
},
|
||||
isChanged (val) {
|
||||
if (this.isSelected && this.lastTable === this.table && this.table !== null)
|
||||
this.setUnsavedChanges(val);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification'
|
||||
addNotification: 'notifications/addNotification',
|
||||
refreshStructure: 'workspaces/refreshStructure',
|
||||
setUnsavedChanges: 'workspaces/setUnsavedChanges'
|
||||
}),
|
||||
async getFieldsData () {
|
||||
if (!this.table) return;
|
||||
this.newFieldsCounter = 0;
|
||||
this.isQuering = true;
|
||||
this.localOptions = JSON.parse(JSON.stringify(this.tableOptions));
|
||||
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
@@ -151,12 +229,49 @@ export default {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
try { // Indexes
|
||||
const { status, response } = await Tables.getTableIndexes(params);
|
||||
|
||||
if (status === 'success') {
|
||||
const indexesObj = response.reduce((acc, curr) => {
|
||||
acc[curr.name] = acc[curr.name] || [];
|
||||
acc[curr.name].push(curr);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
this.originalIndexes = Object.keys(indexesObj).map(index => {
|
||||
return {
|
||||
_id: uidGen(),
|
||||
name: index,
|
||||
fields: indexesObj[index].map(field => field.column),
|
||||
type: indexesObj[index][0].type,
|
||||
comment: indexesObj[index][0].comment,
|
||||
indexType: indexesObj[index][0].indexType,
|
||||
indexComment: indexesObj[index][0].indexComment,
|
||||
cardinality: indexesObj[index][0].cardinality
|
||||
};
|
||||
});
|
||||
|
||||
this.localIndexes = JSON.parse(JSON.stringify(this.originalIndexes));
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
try { // Key usage (foreign keys)
|
||||
const { status, response } = await Tables.getKeyUsage(params);
|
||||
|
||||
if (status === 'success') {
|
||||
this.originalKeyUsage = response;
|
||||
this.localKeyUsage = JSON.parse(JSON.stringify(response));
|
||||
this.originalKeyUsage = response.map(foreign => {
|
||||
return {
|
||||
_id: uidGen(),
|
||||
...foreign
|
||||
};
|
||||
});
|
||||
this.localKeyUsage = JSON.parse(JSON.stringify(this.originalKeyUsage));
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
@@ -171,20 +286,21 @@ export default {
|
||||
if (this.isSaving) return;
|
||||
this.isSaving = true;
|
||||
|
||||
// FIELDS
|
||||
const originalIDs = this.originalFields.reduce((acc, curr) => [...acc, curr._id], []);
|
||||
const localIDs = this.localFields.reduce((acc, curr) => [...acc, curr._id], []);
|
||||
|
||||
// Additions
|
||||
// Fields Additions
|
||||
const additions = this.localFields.filter((field, i) => !originalIDs.includes(field._id)).map(field => {
|
||||
const lI = this.localFields.findIndex(localField => localField._id === field._id);
|
||||
const after = lI > 0 ? this.localFields[lI - 1].name : false;
|
||||
return { ...field, after };
|
||||
});
|
||||
|
||||
// Deletions
|
||||
// Fields Deletions
|
||||
const deletions = this.originalFields.filter(field => !localIDs.includes(field._id));
|
||||
|
||||
// Changes
|
||||
// Fields Changes
|
||||
const changes = [];
|
||||
this.originalFields.forEach((originalField, oI) => {
|
||||
const lI = this.localFields.findIndex(localField => localField._id === originalField._id);
|
||||
@@ -197,20 +313,90 @@ export default {
|
||||
if (this.localFields[lI]) changes.push({ ...this.localFields[lI], after, orgName });
|
||||
});
|
||||
|
||||
// OPTIONS
|
||||
const options = Object.keys(this.localOptions).reduce((acc, option) => {
|
||||
if (this.localOptions[option] !== this.tableOptions[option])
|
||||
acc[option] = this.localOptions[option];
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// INDEXES
|
||||
const indexChanges = {
|
||||
additions: [],
|
||||
changes: [],
|
||||
deletions: []
|
||||
};
|
||||
const originalIndexIDs = this.originalIndexes.reduce((acc, curr) => [...acc, curr._id], []);
|
||||
const localIndexIDs = this.localIndexes.reduce((acc, curr) => [...acc, curr._id], []);
|
||||
|
||||
// Index Additions
|
||||
indexChanges.additions = this.localIndexes.filter(index => !originalIndexIDs.includes(index._id));
|
||||
|
||||
// Index Changes
|
||||
this.originalIndexes.forEach(originalIndex => {
|
||||
const lI = this.localIndexes.findIndex(localIndex => localIndex._id === originalIndex._id);
|
||||
if (JSON.stringify(originalIndex) !== JSON.stringify(this.localIndexes[lI])) {
|
||||
if (this.localIndexes[lI]) {
|
||||
indexChanges.changes.push({
|
||||
...this.localIndexes[lI],
|
||||
oldName: originalIndex.name,
|
||||
oldType: originalIndex.type
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Index Deletions
|
||||
indexChanges.deletions = this.originalIndexes.filter(index => !localIndexIDs.includes(index._id));
|
||||
|
||||
// FOREIGN KEYS
|
||||
const foreignChanges = {
|
||||
additions: [],
|
||||
changes: [],
|
||||
deletions: []
|
||||
};
|
||||
const originalForeignIDs = this.originalKeyUsage.reduce((acc, curr) => [...acc, curr._id], []);
|
||||
const localForeignIDs = this.localKeyUsage.reduce((acc, curr) => [...acc, curr._id], []);
|
||||
|
||||
// Foreigns Additions
|
||||
foreignChanges.additions = this.localKeyUsage.filter(foreign => !originalForeignIDs.includes(foreign._id));
|
||||
|
||||
// Foreigns Changes
|
||||
this.originalKeyUsage.forEach(originalForeign => {
|
||||
const lI = this.localKeyUsage.findIndex(localForeign => localForeign._id === originalForeign._id);
|
||||
if (JSON.stringify(originalForeign) !== JSON.stringify(this.localKeyUsage[lI])) {
|
||||
if (this.localKeyUsage[lI]) {
|
||||
foreignChanges.changes.push({
|
||||
...this.localKeyUsage[lI],
|
||||
oldName: originalForeign.constraintName
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Foreigns Deletions
|
||||
foreignChanges.deletions = this.originalKeyUsage.filter(foreign => !localForeignIDs.includes(foreign._id));
|
||||
|
||||
// ALTER
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
table: this.workspace.breadcrumbs.table,
|
||||
additions,
|
||||
changes,
|
||||
deletions
|
||||
deletions,
|
||||
indexChanges,
|
||||
foreignChanges,
|
||||
options
|
||||
};
|
||||
|
||||
try { // Key usage (foreign keys)
|
||||
try {
|
||||
const { status, response } = await Tables.alterTable(params);
|
||||
|
||||
if (status === 'success')
|
||||
if (status === 'success') {
|
||||
await this.refreshStructure(this.connection.uid);
|
||||
this.getFieldsData();
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
@@ -219,15 +405,19 @@ export default {
|
||||
}
|
||||
|
||||
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.localOptions = JSON.parse(JSON.stringify(this.tableOptions));
|
||||
this.newFieldsCounter = 0;
|
||||
},
|
||||
addField () {
|
||||
this.localFields.push({
|
||||
_id: uidGen(),
|
||||
name: '',
|
||||
name: `${this.$tc('word.field', 1)}_${++this.newFieldsCounter}`,
|
||||
key: '',
|
||||
type: 'int',
|
||||
schema: this.schema,
|
||||
@@ -247,15 +437,59 @@ export default {
|
||||
onUpdate: '',
|
||||
comment: ''
|
||||
});
|
||||
|
||||
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);
|
||||
},
|
||||
showAddModal () {
|
||||
this.isAddModal = true;
|
||||
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
|
||||
}];
|
||||
},
|
||||
hideAddModal () {
|
||||
this.isAddModal = false;
|
||||
addToIndex (payload) {
|
||||
this.localIndexes = this.localIndexes.map(index => {
|
||||
if (index._id === payload.index) index.fields.push(payload.field);
|
||||
return index;
|
||||
});
|
||||
},
|
||||
showOptionsModal () {
|
||||
this.isOptionsModal = true;
|
||||
},
|
||||
hideOptionsModal () {
|
||||
this.isOptionsModal = false;
|
||||
},
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -8,8 +8,12 @@
|
||||
v-if="isContext"
|
||||
:context-event="contextEvent"
|
||||
:selected-field="selectedField"
|
||||
:index-types="indexTypes"
|
||||
:indexes="indexes"
|
||||
@delete-selected="removeField"
|
||||
@close-context="isContext = false"
|
||||
@add-new-index="$emit('add-new-index', $event)"
|
||||
@add-to-index="$emit('add-to-index', $event)"
|
||||
/>
|
||||
<div ref="propTable" class="table table-hover">
|
||||
<div class="thead">
|
||||
@@ -25,14 +29,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="th">
|
||||
<div class="column-resizable">
|
||||
<div class="column-resizable min-100">
|
||||
<div class="table-column-title">
|
||||
{{ $t('word.name') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="th">
|
||||
<div class="column-resizable">
|
||||
<div class="column-resizable min-100">
|
||||
<div class="table-column-title">
|
||||
{{ $t('word.type') }}
|
||||
</div>
|
||||
@@ -81,7 +85,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="th">
|
||||
<div class="column-resizable">
|
||||
<div class="column-resizable min-100">
|
||||
<div class="table-column-title">
|
||||
{{ $t('word.collation') }}
|
||||
</div>
|
||||
@@ -99,6 +103,8 @@
|
||||
v-for="row in fields"
|
||||
:key="row._id"
|
||||
:row="row"
|
||||
:indexes="getIndexes(row.name)"
|
||||
:foreigns="getForeigns(row.name)"
|
||||
:data-types="dataTypes"
|
||||
@contextmenu="contextMenu"
|
||||
/>
|
||||
@@ -122,6 +128,9 @@ export default {
|
||||
},
|
||||
props: {
|
||||
fields: Array,
|
||||
indexes: Array,
|
||||
foreigns: Array,
|
||||
indexTypes: Array,
|
||||
tabUid: [String, Number],
|
||||
connUid: String,
|
||||
table: String,
|
||||
@@ -131,7 +140,6 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
resultsSize: 1000,
|
||||
localResults: [],
|
||||
isContext: false,
|
||||
contextEvent: null,
|
||||
selectedField: null,
|
||||
@@ -154,6 +162,14 @@ export default {
|
||||
},
|
||||
tabProperties () {
|
||||
return this.getWorkspaceTab(this.tabUid);
|
||||
},
|
||||
fieldsLength () {
|
||||
return this.fields.length;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
fieldsLength () {
|
||||
this.refreshScroller();
|
||||
}
|
||||
},
|
||||
updated () {
|
||||
@@ -182,19 +198,31 @@ export default {
|
||||
const size = window.innerHeight - el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
this.resultsSize = size;
|
||||
}
|
||||
// this.$refs.resultTable.updateWindow();
|
||||
}
|
||||
},
|
||||
refreshScroller () {
|
||||
this.resizeResults();
|
||||
},
|
||||
contextMenu (event, uid) {
|
||||
this.selectedField = uid;
|
||||
this.selectedField = this.fields.find(field => field._id === uid);
|
||||
this.contextEvent = event;
|
||||
this.isContext = true;
|
||||
},
|
||||
removeField () {
|
||||
this.$emit('remove-field', this.selectedField);
|
||||
this.$emit('remove-field', this.selectedField._id);
|
||||
},
|
||||
getIndexes (field) {
|
||||
return this.indexes.reduce((acc, curr) => {
|
||||
acc.push(...curr.fields.map(f => ({ name: f, type: curr.type })));
|
||||
return acc;
|
||||
}, []).filter(f => f.name === field);
|
||||
},
|
||||
getForeigns (field) {
|
||||
return this.foreigns.reduce((acc, curr) => {
|
||||
if (curr.field === field)
|
||||
acc.push(`${curr.refTable}.${curr.refField}`);
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -208,4 +236,12 @@ export default {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.vscroll {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.min-100 {
|
||||
min-width: 100px !important;
|
||||
}
|
||||
</style>
|
||||
|
@@ -3,6 +3,36 @@
|
||||
:context-event="contextEvent"
|
||||
@close-context="closeContext"
|
||||
>
|
||||
<div class="context-element">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-key-plus text-light pr-1" /> {{ $t('message.createNewIndex') }}</span>
|
||||
<i class="mdi mdi-18px mdi-chevron-right text-light pl-1" />
|
||||
<div class="context-submenu">
|
||||
<div
|
||||
v-for="index in indexTypes"
|
||||
:key="index"
|
||||
class="context-element"
|
||||
:class="{'disabled': index === 'PRIMARY' && hasPrimary}"
|
||||
@click="addNewIndex(index)"
|
||||
>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-key column-key pr-1" :class="`key-${index}`" /> {{ index }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="indexes.length" class="context-element">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-key-arrow-right text-light pr-1" /> {{ $t('message.addToIndex') }}</span>
|
||||
<i class="mdi mdi-18px mdi-chevron-right text-light pl-1" />
|
||||
<div class="context-submenu">
|
||||
<div
|
||||
v-for="index in indexes"
|
||||
:key="index.name"
|
||||
class="context-element"
|
||||
:class="{'disabled': index.fields.includes(selectedField.name)}"
|
||||
@click="addToIndex(index._id)"
|
||||
>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-key column-key pr-1" :class="`key-${index.type}`" /> {{ index.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="context-element" @click="deleteField">
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ $t('message.deleteField') }}</span>
|
||||
</div>
|
||||
@@ -19,14 +49,14 @@ export default {
|
||||
},
|
||||
props: {
|
||||
contextEvent: MouseEvent,
|
||||
selectedField: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isConfirmModal: false
|
||||
};
|
||||
indexes: Array,
|
||||
indexTypes: Array,
|
||||
selectedField: Object
|
||||
},
|
||||
computed: {
|
||||
hasPrimary () {
|
||||
return this.indexes.some(index => index.type === 'PRIMARY');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
closeContext () {
|
||||
@@ -35,7 +65,23 @@ export default {
|
||||
deleteField () {
|
||||
this.$emit('delete-selected');
|
||||
this.closeContext();
|
||||
},
|
||||
addNewIndex (index) {
|
||||
this.$emit('add-new-index', { field: this.selectedField.name, index });
|
||||
this.closeContext();
|
||||
},
|
||||
addToIndex (index) {
|
||||
this.$emit('add-to-index', { field: this.selectedField.name, index });
|
||||
this.closeContext();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.disabled {
|
||||
pointer-events: none;
|
||||
filter: grayscale(100%);
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,20 +1,29 @@
|
||||
<template>
|
||||
<div class="tr" @contextmenu.prevent="$emit('contextmenu', $event, localRow._id)">
|
||||
<div class="td">
|
||||
<div class="td" tabindex="0">
|
||||
<div class="row-draggable">
|
||||
<i class="mdi mdi-drag-horizontal row-draggable-icon" />
|
||||
{{ localRow.order }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="td" tabindex="0">
|
||||
<i
|
||||
v-if="localRow.key"
|
||||
:title="keyName(localRow.key)"
|
||||
class="mdi mdi-key column-key c-help pl-1"
|
||||
:class="`key-${localRow.key}`"
|
||||
/>
|
||||
<div class="text-center">
|
||||
<i
|
||||
v-for="(index, i) in indexes"
|
||||
:key="`${index.name}-${i}`"
|
||||
:title="index.type"
|
||||
class="d-inline-block mdi mdi-key column-key c-help"
|
||||
:class="`key-${index.type}`"
|
||||
/>
|
||||
<i
|
||||
v-for="foreign in foreigns"
|
||||
:key="foreign"
|
||||
:title="foreign"
|
||||
class="d-inline-block mdi mdi-key-link c-help"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="td">
|
||||
<div class="td" tabindex="0">
|
||||
<span
|
||||
v-if="!isInlineEditor.name"
|
||||
class="cell-content"
|
||||
@@ -32,11 +41,15 @@
|
||||
@blur="editOFF"
|
||||
>
|
||||
</div>
|
||||
<div class="td text-uppercase text-left" :class="`type-${lowerCase(localRow.type)}`">
|
||||
<div
|
||||
class="td text-uppercase"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
v-if="!isInlineEditor.type"
|
||||
class="cell-content"
|
||||
@dblclick="editON($event, localRow.type.toUpperCase(), 'type')"
|
||||
class="cell-content text-left"
|
||||
:class="`type-${lowerCase(localRow.type)}`"
|
||||
@click="editON($event, localRow.type.toUpperCase(), 'type')"
|
||||
>
|
||||
{{ localRow.type }}
|
||||
</span>
|
||||
@@ -44,7 +57,7 @@
|
||||
v-else
|
||||
ref="editField"
|
||||
v-model="editingContent"
|
||||
class="editable-field px-1 text-uppercase"
|
||||
class="form-select editable-field small-select text-uppercase"
|
||||
@blur="editOFF"
|
||||
>
|
||||
<optgroup
|
||||
@@ -63,7 +76,7 @@
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
<div class="td type-int">
|
||||
<div class="td type-int" tabindex="0">
|
||||
<template v-if="fieldType.length">
|
||||
<span
|
||||
v-if="!isInlineEditor.length"
|
||||
@@ -83,7 +96,7 @@
|
||||
>
|
||||
</template>
|
||||
</div>
|
||||
<div class="td">
|
||||
<div class="td" tabindex="0">
|
||||
<label class="form-checkbox">
|
||||
<input
|
||||
v-model="localRow.unsigned"
|
||||
@@ -93,17 +106,17 @@
|
||||
<i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="td">
|
||||
<div class="td" tabindex="0">
|
||||
<label class="form-checkbox">
|
||||
<input
|
||||
v-model="localRow.nullable"
|
||||
type="checkbox"
|
||||
:disabled="localRow.key === 'pri'"
|
||||
:disabled="!isNullable"
|
||||
>
|
||||
<i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="td">
|
||||
<div class="td" tabindex="0">
|
||||
<label class="form-checkbox">
|
||||
<input
|
||||
v-model="localRow.zerofill"
|
||||
@@ -113,12 +126,12 @@
|
||||
<i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="td">
|
||||
<div class="td" tabindex="0">
|
||||
<span class="cell-content" @dblclick="editON($event, localRow.default, 'default')">
|
||||
{{ fieldDefault }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="td type-varchar">
|
||||
<div class="td type-varchar" tabindex="0">
|
||||
<span
|
||||
v-if="!isInlineEditor.comment"
|
||||
class="cell-content"
|
||||
@@ -136,12 +149,12 @@
|
||||
@blur="editOFF"
|
||||
>
|
||||
</div>
|
||||
<div class="td">
|
||||
<div class="td" tabindex="0">
|
||||
<template v-if="fieldType.collation">
|
||||
<span
|
||||
v-if="!isInlineEditor.collation"
|
||||
class="cell-content"
|
||||
@dblclick="editON($event, localRow.collation, 'collation')"
|
||||
@click="editON($event, localRow.collation, 'collation')"
|
||||
>
|
||||
{{ localRow.collation }}
|
||||
</span>
|
||||
@@ -149,7 +162,7 @@
|
||||
v-else
|
||||
ref="editField"
|
||||
v-model="editingContent"
|
||||
class="editable-field px-1"
|
||||
class="form-select small-select editable-field"
|
||||
@blur="editOFF"
|
||||
>
|
||||
<option
|
||||
@@ -221,7 +234,7 @@
|
||||
<label class="form-radio form-inline">
|
||||
<input
|
||||
v-model="defaultValue.type"
|
||||
:disabled="localRow.key !== 'pri'"
|
||||
:disabled="!canAutoincrement"
|
||||
type="radio"
|
||||
name="default"
|
||||
value="autoincrement"
|
||||
@@ -279,7 +292,9 @@ export default {
|
||||
},
|
||||
props: {
|
||||
row: Object,
|
||||
dataTypes: Array
|
||||
dataTypes: Array,
|
||||
indexes: Array,
|
||||
foreigns: Array
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -293,6 +308,7 @@ export default {
|
||||
onUpdate: ''
|
||||
},
|
||||
editingContent: null,
|
||||
originalContent: null,
|
||||
editingField: null
|
||||
};
|
||||
},
|
||||
@@ -321,6 +337,12 @@ export default {
|
||||
},
|
||||
collations () {
|
||||
return this.getWorkspace(this.selectedWorkspace).collations;
|
||||
},
|
||||
canAutoincrement () {
|
||||
return this.indexes.some(index => ['PRIMARY', 'UNIQUE'].includes(index.type));
|
||||
},
|
||||
isNullable () {
|
||||
return !this.indexes.some(index => ['PRIMARY'].includes(index.type));
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -329,6 +351,13 @@ export default {
|
||||
},
|
||||
row () {
|
||||
this.localRow = this.row;
|
||||
},
|
||||
indexes () {
|
||||
if (!this.canAutoincrement)
|
||||
this.localRow.autoIncrement = false;
|
||||
|
||||
if (!this.isNullable)
|
||||
this.localRow.nullable = false;
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
@@ -393,6 +422,7 @@ export default {
|
||||
this.editingField = field;
|
||||
|
||||
this.editingContent = content;
|
||||
this.originalContent = content;
|
||||
|
||||
const obj = { [field]: true };
|
||||
this.isInlineEditor = { ...this.isInlineEditor, ...obj };
|
||||
@@ -410,10 +440,10 @@ export default {
|
||||
editOFF () {
|
||||
this.localRow[this.editingField] = this.editingContent;
|
||||
|
||||
if (this.editingField === 'type') {
|
||||
this.localRow.numLength = false;
|
||||
this.localRow.charLength = false;
|
||||
this.localRow.datePrecision = false;
|
||||
if (this.editingField === 'type' && this.editingContent !== this.originalContent) {
|
||||
this.localRow.numLength = null;
|
||||
this.localRow.charLength = null;
|
||||
this.localRow.datePrecision = null;
|
||||
|
||||
if (this.fieldType.length) {
|
||||
if (['integer', 'float', 'binary', 'spatial'].includes(this.fieldType.group)) this.localRow.numLength = 11;
|
||||
@@ -423,6 +453,12 @@ export default {
|
||||
|
||||
if (!this.fieldType.collation)
|
||||
this.localRow.collation = null;
|
||||
|
||||
if (!this.fieldType.unsigned)
|
||||
this.localRow.unsigned = false;
|
||||
|
||||
if (!this.fieldType.zerofill)
|
||||
this.localRow.zerofill = false;
|
||||
}
|
||||
|
||||
if (this.editingField === 'default') {
|
||||
@@ -456,6 +492,7 @@ export default {
|
||||
});
|
||||
|
||||
this.editingContent = null;
|
||||
this.originalContent = null;
|
||||
this.editingField = null;
|
||||
},
|
||||
hideDefaultModal () {
|
||||
|
@@ -1,7 +1,11 @@
|
||||
<template>
|
||||
<div v-show="isSelected" class="workspace-query-tab column col-12 columns col-gapless">
|
||||
<div class="workspace-query-runner column col-12">
|
||||
<QueryEditor v-if="isSelected" :value.sync="query" />
|
||||
<QueryEditor
|
||||
v-if="isSelected"
|
||||
:auto-focus="true"
|
||||
:value.sync="query"
|
||||
/>
|
||||
<div class="workspace-query-runner-footer">
|
||||
<div class="workspace-query-buttons">
|
||||
<button
|
||||
@@ -17,7 +21,7 @@
|
||||
</div>
|
||||
<div class="workspace-query-info">
|
||||
<div v-if="resultsCount">
|
||||
{{ $t('word.results') }}: <b>{{ resultsCount }}</b>
|
||||
{{ $t('word.results') }}: <b>{{ resultsCount.toLocaleString() }}</b>
|
||||
</div>
|
||||
<div v-if="affectedCount">
|
||||
{{ $t('message.affectedRows') }}: <b>{{ affectedCount }}</b>
|
||||
@@ -99,6 +103,7 @@ export default {
|
||||
if (!query || this.isQuering) return;
|
||||
this.isQuering = true;
|
||||
this.clearTabData();
|
||||
this.$refs.queryTable.resetSort();
|
||||
|
||||
try { // Query Data
|
||||
const params = {
|
||||
|
@@ -123,8 +123,11 @@ export default {
|
||||
primaryField () {
|
||||
return this.fields.filter(field => ['pri', 'uni'].includes(field.key))[0] || false;
|
||||
},
|
||||
isHardSort () {
|
||||
return this.mode === 'table' && this.localResults.length === 1000;
|
||||
},
|
||||
sortedResults () {
|
||||
if (this.currentSort) {
|
||||
if (this.currentSort && !this.isHardSort) {
|
||||
return [...this.localResults].sort((a, b) => {
|
||||
let modifier = 1;
|
||||
const valA = typeof a[this.currentSort] === 'string' ? a[this.currentSort].toLowerCase() : a[this.currentSort];
|
||||
@@ -228,7 +231,6 @@ export default {
|
||||
return row[primaryFieldName];
|
||||
},
|
||||
setLocalResults () {
|
||||
this.resetSort();
|
||||
this.localResults = this.resultsWithRows[this.resultsetIndex] && this.resultsWithRows[this.resultsetIndex].rows
|
||||
? this.resultsWithRows[this.resultsetIndex].rows.map(item => {
|
||||
return { ...item, _id: uidGen() };
|
||||
@@ -342,6 +344,9 @@ export default {
|
||||
this.currentSortDir = 'asc';
|
||||
this.currentSort = field;
|
||||
}
|
||||
|
||||
if (this.isHardSort)
|
||||
this.$emit('hard-sort', { field: this.currentSort, dir: this.currentSortDir });
|
||||
},
|
||||
resetSort () {
|
||||
this.currentSort = '';
|
||||
|
@@ -20,6 +20,7 @@
|
||||
class="editable-field"
|
||||
:value.sync="editingContent"
|
||||
:key-usage="getKeyUsage(cKey)"
|
||||
size="small"
|
||||
@blur="editOFF"
|
||||
/>
|
||||
<template v-else>
|
||||
@@ -157,6 +158,8 @@ export default {
|
||||
typeFormat (val, type, precision) {
|
||||
if (!val) return val;
|
||||
|
||||
type = type.toUpperCase();
|
||||
|
||||
if (DATE.includes(type))
|
||||
return moment(val).isValid() ? moment(val).format('YYYY-MM-DD') : val;
|
||||
|
||||
@@ -254,7 +257,7 @@ export default {
|
||||
return ['gif', 'jpg', 'png', 'bmp', 'ico', 'tif'].includes(this.contentInfo.ext);
|
||||
},
|
||||
foreignKeys () {
|
||||
return this.keyUsage.map(key => key.column);
|
||||
return this.keyUsage.map(key => key.field);
|
||||
},
|
||||
isEditable () {
|
||||
return this.fields ? !!(this.fields[0].schema && this.fields[0].table) : false;
|
||||
@@ -274,7 +277,7 @@ export default {
|
||||
if (field)
|
||||
type = field.type;
|
||||
|
||||
return type;
|
||||
return type.toLowerCase();
|
||||
},
|
||||
getFieldPrecision (cKey) {
|
||||
let length = 0;
|
||||
@@ -313,7 +316,7 @@ export default {
|
||||
editON (event, content, field) {
|
||||
if (!this.isEditable) return;
|
||||
|
||||
const type = this.getFieldType(field);
|
||||
const type = this.getFieldType(field).toUpperCase(); ;
|
||||
this.originalContent = content;
|
||||
this.editingType = type;
|
||||
this.editingField = field;
|
||||
@@ -420,7 +423,7 @@ export default {
|
||||
this.$emit('select-row', event, row);
|
||||
},
|
||||
getKeyUsage (keyName) {
|
||||
return this.keyUsage.find(key => key.column === keyName);
|
||||
return this.keyUsage.find(key => key.field === keyName);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -42,7 +42,10 @@
|
||||
</div>
|
||||
<div class="workspace-query-info">
|
||||
<div v-if="results.length && results[0].rows">
|
||||
{{ $t('word.results') }}: <b>{{ results[0].rows.length }}</b>
|
||||
{{ $t('word.results') }}: <b>{{ results[0].rows.length.toLocaleString() }}</b>
|
||||
</div>
|
||||
<div v-if="results.length && results[0].rows && tableInfo && results[0].rows.length < tableInfo.rows">
|
||||
{{ $t('word.total') }}: <b>{{ tableInfo.rows.toLocaleString() }}</b> <small>({{ $t('word.approximately') }})</small>
|
||||
</div>
|
||||
<div v-if="workspace.breadcrumbs.database">
|
||||
{{ $t('word.schema') }}: <b>{{ workspace.breadcrumbs.database }}</b>
|
||||
@@ -60,6 +63,7 @@
|
||||
mode="table"
|
||||
@update-field="updateField"
|
||||
@delete-selected="deleteSelected"
|
||||
@hard-sort="hardSort"
|
||||
/>
|
||||
</div>
|
||||
<ModalNewTableRow
|
||||
@@ -99,7 +103,8 @@ export default {
|
||||
lastTable: null,
|
||||
isAddModal: false,
|
||||
autorefreshTimer: 0,
|
||||
refreshInterval: null
|
||||
refreshInterval: null,
|
||||
sortParams: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -117,13 +122,23 @@ export default {
|
||||
},
|
||||
keyUsage () {
|
||||
return this.results.length ? this.results[0].keys : [];
|
||||
},
|
||||
tableInfo () {
|
||||
try {
|
||||
return this.workspace.structure.find(db => db.name === this.schema).tables.find(table => table.name === this.table);
|
||||
}
|
||||
catch (err) {
|
||||
return { rows: 0 };
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
table () {
|
||||
if (this.isSelected) {
|
||||
this.sortParams = {};
|
||||
this.getTableData();
|
||||
this.lastTable = this.table;
|
||||
this.$refs.queryTable.resetSort();
|
||||
}
|
||||
},
|
||||
isSelected (val) {
|
||||
@@ -145,7 +160,7 @@ export default {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification'
|
||||
}),
|
||||
async getTableData () {
|
||||
async getTableData (sortParams) {
|
||||
if (!this.table) return;
|
||||
this.isQuering = true;
|
||||
|
||||
@@ -156,7 +171,8 @@ export default {
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
schema: this.schema,
|
||||
table: this.workspace.breadcrumbs.table
|
||||
table: this.workspace.breadcrumbs.table,
|
||||
sortParams
|
||||
};
|
||||
|
||||
try { // Table data
|
||||
@@ -177,7 +193,11 @@ export default {
|
||||
return this.table;
|
||||
},
|
||||
reloadTable () {
|
||||
this.getTableData();
|
||||
this.getTableData(this.sortParams);
|
||||
},
|
||||
hardSort (sortParams) {
|
||||
this.sortParams = sortParams;
|
||||
this.getTableData(sortParams);
|
||||
},
|
||||
showAddModal () {
|
||||
this.isAddModal = true;
|
||||
|
@@ -53,7 +53,16 @@ module.exports = {
|
||||
comment: 'Comment',
|
||||
key: 'Key | Keys',
|
||||
order: 'Order',
|
||||
expression: 'Expression'
|
||||
expression: 'Expression',
|
||||
autoIncrement: 'Auto Increment',
|
||||
engine: 'Engine',
|
||||
field: 'Field | Fields',
|
||||
approximately: 'Approximately',
|
||||
total: 'Total',
|
||||
table: 'Table',
|
||||
discard: 'Discard',
|
||||
stay: 'Stay',
|
||||
author: 'Author'
|
||||
},
|
||||
message: {
|
||||
appWelcome: 'Welcome to Antares SQL Client!',
|
||||
@@ -99,7 +108,23 @@ module.exports = {
|
||||
zeroFill: 'Zero fill',
|
||||
customValue: 'Custom value',
|
||||
onUpdate: 'On update',
|
||||
deleteField: 'Delete field'
|
||||
deleteField: 'Delete field',
|
||||
createNewIndex: 'Create new index',
|
||||
addToIndex: 'Add to index',
|
||||
createNewTable: 'Create new table',
|
||||
emptyTable: 'Empty table',
|
||||
deleteTable: 'Delete table',
|
||||
emptyCorfirm: 'Do you confirm to empty',
|
||||
unsavedChanges: 'Unsaved changes',
|
||||
discardUnsavedChanges: 'You have some unsaved changes. By leaving this tab these changes will be discarded.',
|
||||
thereAreNoIndexes: 'There are no indexes',
|
||||
thereAreNoForeign: 'There are no foreign keys',
|
||||
createNewForeign: 'Create new foreign key',
|
||||
referenceTable: 'Ref. table',
|
||||
referenceField: 'Ref. field',
|
||||
foreignFields: 'Foreign fields',
|
||||
invalidDefault: 'Invalid default',
|
||||
onDelete: 'On delete'
|
||||
},
|
||||
// Date and Time
|
||||
short: {
|
||||
|
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 34 32"><path fill="#444" d="M21.576 3.59c-1.115-.994-2.465-.595-3.798.588a9.407 9.407 0 00-.591.579c-2.279 2.418-4.395 6.897-5.053 10.318.256.519.456 1.182.588 1.688.034.13.064.252.089.355.058.245.089.405.089.405s-.02-.077-.104-.321l-.055-.158a1.44 1.44 0 00-.035-.087c-.149-.346-.56-1.075-.741-1.393-.155.457-.292.884-.406 1.271.523.956.841 2.595.841 2.595s-.028-.106-.159-.477c-.117-.328-.697-1.345-.835-1.583-.235.869-.329 1.455-.244 1.598.164.277.32.754.457 1.282.309 1.189.524 2.637.524 2.637l.019.244c-.043.999-.017 2.034.06 2.97.103 1.239.295 2.303.541 2.873l.167-.091c-.361-1.122-.508-2.593-.444-4.289.097-2.593.694-5.719 1.796-8.978 1.863-4.919 4.447-8.866 6.811-10.751-2.155 1.947-5.073 8.248-5.946 10.581-.978 2.613-1.671 5.065-2.088 7.414.721-2.202 3.05-3.149 3.05-3.149s1.143-1.409 2.478-3.422c-.8.182-2.113.495-2.553.68-.649.272-.824.365-.824.365s2.102-1.28 3.905-1.86c2.48-3.906 5.182-9.456 2.461-11.884z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 34 32"><path fill="#fff" d="M21.576 3.59c-1.115-.994-2.465-.595-3.798.588a9.407 9.407 0 00-.591.579c-2.279 2.418-4.395 6.897-5.053 10.318.256.519.456 1.182.588 1.688.034.13.064.252.089.355.058.245.089.405.089.405s-.02-.077-.104-.321l-.055-.158a1.44 1.44 0 00-.035-.087c-.149-.346-.56-1.075-.741-1.393-.155.457-.292.884-.406 1.271.523.956.841 2.595.841 2.595s-.028-.106-.159-.477c-.117-.328-.697-1.345-.835-1.583-.235.869-.329 1.455-.244 1.598.164.277.32.754.457 1.282.309 1.189.524 2.637.524 2.637l.019.244c-.043.999-.017 2.034.06 2.97.103 1.239.295 2.303.541 2.873l.167-.091c-.361-1.122-.508-2.593-.444-4.289.097-2.593.694-5.719 1.796-8.978 1.863-4.919 4.447-8.866 6.811-10.751-2.155 1.947-5.073 8.248-5.946 10.581-.978 2.613-1.671 5.065-2.088 7.414.721-2.202 3.05-3.149 3.05-3.149s1.143-1.409 2.478-3.422c-.8.182-2.113.495-2.553.68-.649.272-.824.365-.824.365s2.102-1.28 3.905-1.86c2.48-3.906 5.182-9.456 2.461-11.884z"/></svg>
|
Before Width: | Height: | Size: 1004 B After Width: | Height: | Size: 1004 B |
@@ -30,6 +30,10 @@ export default class {
|
||||
return ipcRenderer.invoke('get-variables', uid);
|
||||
}
|
||||
|
||||
static getEngines (uid) {
|
||||
return ipcRenderer.invoke('get-engines', uid);
|
||||
}
|
||||
|
||||
static useSchema (params) {
|
||||
return ipcRenderer.invoke('use-schema', params);
|
||||
}
|
||||
|
@@ -10,6 +10,10 @@ export default class {
|
||||
return ipcRenderer.invoke('get-table-data', params);
|
||||
}
|
||||
|
||||
static getTableIndexes (params) {
|
||||
return ipcRenderer.invoke('get-table-indexes', params);
|
||||
}
|
||||
|
||||
static getKeyUsage (params) {
|
||||
return ipcRenderer.invoke('get-key-usage', params);
|
||||
}
|
||||
@@ -30,7 +34,19 @@ export default class {
|
||||
return ipcRenderer.invoke('get-foreign-list', params);
|
||||
}
|
||||
|
||||
static createTable (params) {
|
||||
return ipcRenderer.invoke('create-table', params);
|
||||
}
|
||||
|
||||
static alterTable (params) {
|
||||
return ipcRenderer.invoke('alter-table', params);
|
||||
}
|
||||
|
||||
static truncateTable (params) {
|
||||
return ipcRenderer.invoke('truncate-table', params);
|
||||
}
|
||||
|
||||
static dropTable (params) {
|
||||
return ipcRenderer.invoke('drop-table', params);
|
||||
}
|
||||
}
|
||||
|
@@ -4,15 +4,22 @@
|
||||
line-height: 1.5;
|
||||
margin-right: 0.2rem;
|
||||
|
||||
&.key-pri {
|
||||
&.key-pri,
|
||||
&.key-PRIMARY {
|
||||
color: goldenrod;
|
||||
}
|
||||
|
||||
&.key-uni {
|
||||
&.key-uni,
|
||||
&.key-UNIQUE {
|
||||
color: deepskyblue;
|
||||
}
|
||||
|
||||
&.key-mul {
|
||||
&.key-mul,
|
||||
&.key-INDEX {
|
||||
color: palegreen;
|
||||
}
|
||||
|
||||
&.key-FULLTEXT {
|
||||
color: mediumvioletred;
|
||||
}
|
||||
}
|
||||
|
@@ -180,6 +180,12 @@ body {
|
||||
|
||||
.form-select {
|
||||
cursor: pointer;
|
||||
|
||||
&.small-select {
|
||||
height: 1rem;
|
||||
font-size: 0.7rem;
|
||||
padding: 1px 0.4rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-select,
|
||||
@@ -191,6 +197,11 @@ body {
|
||||
background-color: $bg-color-gray;
|
||||
}
|
||||
|
||||
.form-input.is-error,
|
||||
.form-select.is-error {
|
||||
background-color: $bg-color-gray;
|
||||
}
|
||||
|
||||
.form-input:not(:placeholder-shown):invalid:focus {
|
||||
background: $bg-color-gray;
|
||||
}
|
||||
@@ -223,3 +234,7 @@ body {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
color: $body-font-color;
|
||||
}
|
||||
|
@@ -3,14 +3,17 @@ import Connection from '@/ipc-api/Connection';
|
||||
import Database from '@/ipc-api/Database';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
const tabIndex = [];
|
||||
let lastSchema = '';
|
||||
let lastBreadcrumbs = {};
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
strict: true,
|
||||
state: {
|
||||
workspaces: [],
|
||||
selected_workspace: null
|
||||
selected_workspace: null,
|
||||
has_unsaved_changes: false,
|
||||
is_unsaved_discard_modal: false,
|
||||
pending_breadcrumbs: {}
|
||||
},
|
||||
getters: {
|
||||
getSelected: state => {
|
||||
@@ -35,18 +38,22 @@ export default {
|
||||
return state.workspaces
|
||||
.filter(workspace => workspace.connected)
|
||||
.map(workspace => workspace.uid);
|
||||
},
|
||||
isUnsavedDiscardModal: state => {
|
||||
return state.is_unsaved_discard_modal;
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
SELECT_WORKSPACE (state, uid) {
|
||||
state.selected_workspace = uid;
|
||||
},
|
||||
ADD_CONNECTED (state, { uid, client, dataTypes, structure }) {
|
||||
ADD_CONNECTED (state, { uid, client, dataTypes, indexTypes, structure }) {
|
||||
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid
|
||||
? {
|
||||
...workspace,
|
||||
client,
|
||||
dataTypes,
|
||||
indexTypes,
|
||||
structure,
|
||||
connected: true
|
||||
}
|
||||
@@ -85,6 +92,14 @@ export default {
|
||||
}
|
||||
: workspace);
|
||||
},
|
||||
REFRESH_ENGINES (state, { uid, engines }) {
|
||||
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid
|
||||
? {
|
||||
...workspace,
|
||||
engines
|
||||
}
|
||||
: workspace);
|
||||
},
|
||||
ADD_WORKSPACE (state, workspace) {
|
||||
state.workspaces.push(workspace);
|
||||
},
|
||||
@@ -96,11 +111,10 @@ export default {
|
||||
}
|
||||
: workspace);
|
||||
},
|
||||
NEW_TAB (state, uid) {
|
||||
NEW_TAB (state, { uid, tab }) {
|
||||
tabIndex[uid] = tabIndex[uid] ? ++tabIndex[uid] : 1;
|
||||
|
||||
const newTab = {
|
||||
uid: uidGen('T'),
|
||||
uid: tab,
|
||||
index: tabIndex[uid],
|
||||
selected: false,
|
||||
type: 'query',
|
||||
@@ -166,6 +180,15 @@ export default {
|
||||
else
|
||||
return workspace;
|
||||
});
|
||||
},
|
||||
SET_UNSAVED_CHANGES (state, val) {
|
||||
state.has_unsaved_changes = !!val;
|
||||
},
|
||||
SET_UNSAVED_DISCARD_MODAL (state, val) {
|
||||
state.is_unsaved_discard_modal = !!val;
|
||||
},
|
||||
SET_PENDING_BREADCRUMBS (state, payload) {
|
||||
state.pending_breadcrumbs = payload;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
@@ -179,21 +202,25 @@ export default {
|
||||
dispatch('notifications/addNotification', { status, message: response }, { root: true });
|
||||
else {
|
||||
let dataTypes = [];
|
||||
let indexTypes = [];
|
||||
|
||||
switch (connection.client) {
|
||||
case 'mysql':
|
||||
case 'maria':
|
||||
dataTypes = require('common/data-types/mysql');
|
||||
indexTypes = require('common/index-types/mysql');
|
||||
break;
|
||||
}
|
||||
commit('ADD_CONNECTED', {
|
||||
uid: connection.uid,
|
||||
client: connection.client,
|
||||
dataTypes,
|
||||
indexTypes,
|
||||
structure: response
|
||||
});
|
||||
dispatch('refreshCollations', connection.uid);
|
||||
dispatch('refreshVariables', connection.uid);
|
||||
dispatch('refreshEngines', connection.uid);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
@@ -236,6 +263,18 @@ export default {
|
||||
dispatch('notifications/addNotification', { status: 'error', message: err.stack }, { root: true });
|
||||
}
|
||||
},
|
||||
async refreshEngines ({ dispatch, commit }, uid) {
|
||||
try {
|
||||
const { status, response } = await Database.getEngines(uid);
|
||||
if (status === 'error')
|
||||
dispatch('notifications/addNotification', { status, message: response }, { root: true });
|
||||
else
|
||||
commit('REFRESH_ENGINES', { uid, engines: response });
|
||||
}
|
||||
catch (err) {
|
||||
dispatch('notifications/addNotification', { status: 'error', message: err.stack }, { root: true });
|
||||
}
|
||||
},
|
||||
removeConnected ({ commit }, uid) {
|
||||
Connection.disconnect(uid);
|
||||
commit('REMOVE_CONNECTED', uid);
|
||||
@@ -268,17 +307,40 @@ export default {
|
||||
|
||||
if (getters.getWorkspace(uid).tabs.length < 3)
|
||||
dispatch('newTab', uid);
|
||||
|
||||
dispatch('setUnsavedChanges', false);
|
||||
},
|
||||
changeBreadcrumbs ({ commit, getters }, payload) {
|
||||
if (lastSchema !== payload.schema) {
|
||||
Database.useSchema({ uid: getters.getSelected, schema: payload.schema });
|
||||
lastSchema = payload.schema;
|
||||
changeBreadcrumbs ({ state, commit, getters }, payload) {
|
||||
if (state.has_unsaved_changes) {
|
||||
commit('SET_UNSAVED_DISCARD_MODAL', true);
|
||||
commit('SET_PENDING_BREADCRUMBS', payload);
|
||||
return;
|
||||
}
|
||||
|
||||
commit('CHANGE_BREADCRUMBS', { uid: getters.getSelected, breadcrumbs: payload });
|
||||
const breadcrumbsObj = {
|
||||
schema: null,
|
||||
table: null,
|
||||
trigger: null,
|
||||
procedure: null,
|
||||
scheduler: null
|
||||
};
|
||||
|
||||
const hasLastChildren = Object.keys(lastBreadcrumbs).filter(b => b !== 'schema').some(b => lastBreadcrumbs[b]);
|
||||
const hasChildren = Object.keys(payload).filter(b => b !== 'schema').some(b => payload[b]);
|
||||
|
||||
if (lastBreadcrumbs.schema === payload.schema && hasLastChildren && !hasChildren) return;
|
||||
|
||||
if (lastBreadcrumbs.schema !== payload.schema)
|
||||
Database.useSchema({ uid: getters.getSelected, schema: payload.schema });
|
||||
|
||||
commit('CHANGE_BREADCRUMBS', { uid: getters.getSelected, breadcrumbs: { ...breadcrumbsObj, ...payload } });
|
||||
lastBreadcrumbs = { ...breadcrumbsObj, ...payload };
|
||||
},
|
||||
newTab ({ commit }, uid) {
|
||||
commit('NEW_TAB', uid);
|
||||
const tab = uidGen('T');
|
||||
|
||||
commit('NEW_TAB', { uid, tab });
|
||||
commit('SELECT_TAB', { uid, tab });
|
||||
},
|
||||
removeTab ({ commit }, payload) {
|
||||
commit('REMOVE_TAB', payload);
|
||||
@@ -291,6 +353,18 @@ export default {
|
||||
},
|
||||
setTabKeyUsage ({ commit }, payload) {
|
||||
commit('SET_TAB_KEY_USAGE', payload);
|
||||
},
|
||||
setUnsavedChanges ({ commit }, val) {
|
||||
commit('SET_UNSAVED_CHANGES', val);
|
||||
},
|
||||
discardUnsavedChanges ({ state, commit, dispatch }) {
|
||||
dispatch('setUnsavedChanges', false);
|
||||
dispatch('changeBreadcrumbs', state.pending_breadcrumbs);
|
||||
commit('SET_UNSAVED_DISCARD_MODAL', false);
|
||||
commit('SET_PENDING_BREADCRUMBS', {});
|
||||
},
|
||||
closeUnsavedChangesModal ({ commit }) {
|
||||
commit('SET_UNSAVED_DISCARD_MODAL', false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user