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

Compare commits

...

6 Commits

Author SHA1 Message Date
db71777cbc FIelds edit implemented 2020-06-28 15:31:16 +02:00
f350fe8203 Partial implementation of fields edit 2020-06-27 15:14:08 +02:00
28c3f87dd8 Create FUNDING.yml 2020-06-27 13:40:26 +02:00
cc8dbb8df7 Start implementing fields edit 2020-06-26 18:14:16 +02:00
85ac1bc85f Changed readme screenshot size 2020-06-20 22:00:10 +02:00
baea5c26e2 Text connection fix 2020-06-20 21:58:57 +02:00
21 changed files with 432 additions and 140 deletions

12
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: fabio286
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ['https://paypal.me/fabiodistasio']

View File

@@ -1,6 +1,17 @@
# Changelog
## [0.0.1-alpha]() - Coming Soon
## [0.0.2-alpha](https://github.com/Fabio286/antares/releases/tag/v0.0.2-alpha) - 2020-06-26
### Added
- **Edit table fields:** You can edit fields from tables by double click. For now is available only on numeric and textual values.
- Various improvements under the hood.
### Fixed
- Connection test.
## [0.0.1-alpha](https://github.com/Fabio286/antares/releases/tag/v0.0.1-alpha) - 2020-06-19
### Features

View File

@@ -1,5 +1,5 @@
<p align="center">
<img width="256" src="https://raw.githubusercontent.com/Fabio286/antares/master/docs/logo.png">
<img width="800" src="https://raw.githubusercontent.com/Fabio286/antares/master/docs/screen-alpha.png">
</p>
# Antares

BIN
docs/screen-alpha.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

99
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "antares",
"version": "0.0.0-alpha",
"version": "0.0.1-alpha",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -3047,9 +3047,9 @@
"dev": true
},
"codemirror": {
"version": "5.54.0",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.54.0.tgz",
"integrity": "sha512-Pgf3surv4zvw+KaW3doUU7pGjF0BPU8/sj7eglWJjzni46U/DDW8pu3nZY0QgQKUcICDXRkq8jZmq0y6KhxM3Q=="
"version": "5.55.0",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.55.0.tgz",
"integrity": "sha512-TumikSANlwiGkdF/Blnu/rqovZ0Y3Jh8yy9TqrPbSM0xxSucq3RgnpVDQ+mD9q6JERJEIT2FMuF/fBGfkhIR/g=="
},
"collection-visit": {
"version": "1.0.0",
@@ -4034,9 +4034,9 @@
}
},
"electron": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/electron/-/electron-8.3.0.tgz",
"integrity": "sha512-XRjiIJICZCgUr2vKSUI2PTkfP0gPFqCtqJUaTJSfCTuE3nTrxBKOUNeRMuCzEqspKkpFQU3SB3MdbMSHmZARlQ==",
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/electron/-/electron-8.3.4.tgz",
"integrity": "sha512-aSYXBV0PxYHmXhjGFpR0x38zbO7UTDex2JrT5tcqJpUZTY+sKdfo9PA1TpiyrHNjA5+Q8UseRUsydRedOTeZQA==",
"dev": true,
"requires": {
"@electron/get": "^1.0.1",
@@ -4116,9 +4116,9 @@
}
},
"electron-devtools-installer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/electron-devtools-installer/-/electron-devtools-installer-3.0.0.tgz",
"integrity": "sha512-zll3w/8PvnPiGmL5tBtgSSoSjWnUljsOjJYsYYU12PKLljzWyfD6S75LKTZFn21VYxVbae2OwmjM5uFStLp6nQ==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/electron-devtools-installer/-/electron-devtools-installer-3.1.0.tgz",
"integrity": "sha512-qZd1Aoya8YOK6QauNX92V5qyKGtb4lbs238bP+qtMBkXts24xJ/1PtOVBPvdg5w3Ts9L5o6I9sDErKuzHeJFDA==",
"dev": true,
"requires": {
"rimraf": "^3.0.2",
@@ -4138,9 +4138,9 @@
}
},
"electron-log": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/electron-log/-/electron-log-4.2.1.tgz",
"integrity": "sha512-tUI9w3kUP3qhwXJ92RDUPFVZfwtBwKCy/1TsSHndXYLlNCB/7+vkiQG0uxv9D2Leuxc/DJKfYyrdEBpzi/vyZg=="
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/electron-log/-/electron-log-4.2.2.tgz",
"integrity": "sha512-lBpLh1Q8qayrTxFIrTPcNjSHsosvUfOYyZ8glhiLcx7zCNPDGuj8+nXlEaaSS6LRiQQbLgLG+wKpuvztNzBIrA=="
},
"electron-publish": {
"version": "22.7.0",
@@ -4560,9 +4560,9 @@
"dev": true
},
"escape-string-regexp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true,
"optional": true
},
@@ -4706,9 +4706,9 @@
"dev": true
},
"eslint-import-resolver-node": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz",
"integrity": "sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg==",
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz",
"integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==",
"dev": true,
"requires": {
"debug": "^2.6.9",
@@ -4845,9 +4845,9 @@
}
},
"eslint-plugin-import": {
"version": "2.21.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.21.1.tgz",
"integrity": "sha512-qYOOsgUv63vHof7BqbzuD+Ud34bXHxFJxntuAC1ZappFZXYbRIek3aJ7jc9i2dHDGDyZ/0zlO0cpioES265Lsw==",
"version": "2.22.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz",
"integrity": "sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg==",
"dev": true,
"requires": {
"array-includes": "^3.1.1",
@@ -6442,19 +6442,19 @@
}
},
"global-agent": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.1.8.tgz",
"integrity": "sha512-VpBe/rhY6Rw2VDOTszAMNambg+4Qv8j0yiTNDYEXXXxkUNGWLHp8A3ztK4YDBbFNcWF4rgsec6/5gPyryya/+A==",
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.1.12.tgz",
"integrity": "sha512-caAljRMS/qcDo69X9BfkgrihGUgGx44Fb4QQToNQjsiWh+YlQ66uqYVAdA8Olqit+5Ng0nkz09je3ZzANMZcjg==",
"dev": true,
"optional": true,
"requires": {
"boolean": "^3.0.0",
"core-js": "^3.6.4",
"boolean": "^3.0.1",
"core-js": "^3.6.5",
"es6-error": "^4.1.1",
"matcher": "^2.1.0",
"roarr": "^2.15.2",
"semver": "^7.1.2",
"serialize-error": "^5.0.0"
"matcher": "^3.0.0",
"roarr": "^2.15.3",
"semver": "^7.3.2",
"serialize-error": "^7.0.1"
}
},
"global-dirs": {
@@ -7767,9 +7767,9 @@
}
},
"jszip": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.4.0.tgz",
"integrity": "sha512-gZAOYuPl4EhPTXT0GjhI3o+ZAz3su6EhLrKUoAivcKqyqC7laS5JEv4XWZND9BgcDcF83vI85yGbDmDR6UhrIg==",
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz",
"integrity": "sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA==",
"dev": true,
"requires": {
"lie": "~3.3.0",
@@ -8073,13 +8073,13 @@
}
},
"matcher": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/matcher/-/matcher-2.1.0.tgz",
"integrity": "sha512-o+nZr+vtJtgPNklyeUKkkH42OsK8WAfdgaJE2FNxcjLPg+5QbeEoT6vRj8Xq/iv18JlQ9cmKsEu0b94ixWf1YQ==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
"integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
"dev": true,
"optional": true,
"requires": {
"escape-string-regexp": "^2.0.0"
"escape-string-regexp": "^4.0.0"
}
},
"material-design-icons": {
@@ -8476,9 +8476,9 @@
}
},
"moment": {
"version": "2.26.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.26.0.tgz",
"integrity": "sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw=="
"version": "2.27.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz",
"integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ=="
},
"move-concurrently": {
"version": "1.0.1",
@@ -10991,13 +10991,22 @@
}
},
"serialize-error": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-5.0.0.tgz",
"integrity": "sha512-/VtpuyzYf82mHYTtI4QKtwHa79vAdU5OQpNPAmE/0UDdlGT0ZxHwC+J6gXkw29wwoVI8fMPsfcVHOwXtUQYYQA==",
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
"integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
"dev": true,
"optional": true,
"requires": {
"type-fest": "^0.8.0"
"type-fest": "^0.13.1"
},
"dependencies": {
"type-fest": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
"integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
"dev": true,
"optional": true
}
}
},
"serialize-javascript": {

View File

@@ -1,16 +1,16 @@
{
"name": "antares",
"productName": "Antares",
"version": "0.0.1-alpha",
"version": "0.0.2-alpha",
"description": "A cross-platform easy to use SQL client.",
"license": "MIT",
"repository": "https://github.com/Fabio286/antares.git",
"repository": "https://github.com/EStarium/antares.git",
"scripts": {
"dev": "cross-env NODE_ENV=development electron-webpack dev",
"compile": "electron-webpack",
"dist": "cross-env NODE_ENV=production npm run compile && electron-builder",
"dist:dir": "cross-env NODE_ENV=production npm run dist --dir -c.compression=store -c.mac.identity=null",
"publish": "build -p always"
"publish": "cross-env NODE_ENV=production npm run dist -p always"
},
"author": "Fabio Di Stasio <fabio286@gmail.com>",
"build": {
@@ -29,12 +29,12 @@
}
},
"dependencies": {
"codemirror": "^5.54.0",
"electron-log": "^4.2.1",
"codemirror": "^5.55.0",
"electron-log": "^4.2.2",
"electron-updater": "^4.3.1",
"lodash": "^4.17.15",
"material-design-icons": "^3.0.1",
"moment": "^2.26.0",
"moment": "^2.27.0",
"mssql": "^6.2.0",
"mysql": "^2.18.1",
"pg": "^8.2.1",
@@ -49,14 +49,14 @@
"devDependencies": {
"babel-eslint": "^10.1.0",
"cross-env": "^7.0.2",
"electron": "^8.3.0",
"electron": "^8.3.4",
"electron-builder": "^22.7.0",
"electron-devtools-installer": "^3.0.0",
"electron-devtools-installer": "^3.1.0",
"electron-webpack": "^2.8.2",
"electron-webpack-vue": "^2.4.0",
"eslint": "^6.8.0",
"eslint-config-standard": "^14.1.1",
"eslint-plugin-import": "^2.21.1",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",

View File

@@ -24,4 +24,14 @@ export default (connections) => {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('updateTableCell', async (event, params) => {
try {
const result = await Generic.updateTableCell(connections[params.uid], params);
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
};

View File

@@ -73,14 +73,10 @@ export class AntaresConnector {
switch (this._client) {
case 'maria':
case 'mysql':
if (!this._poolSize) {
const connection = mysql.createConnection(this._params);
this._connection = connection.promise();
}
if (!this._poolSize)
this._connection = mysql.createConnection(this._params);
else
this._connection = mysql.createPool({ ...this._params, connectionLimit: this._poolSize });
// this._connection = pool.promise();
break;
case 'mssql': {
const mssqlParams = {
@@ -149,6 +145,11 @@ export class AntaresConnector {
return this.raw(sql);
}
update (...args) {
this._query.update = [...this._query.update, ...args];
return this;
}
/**
* @returns {string} SQL string
* @memberof AntaresConnector
@@ -156,30 +157,35 @@ export class AntaresConnector {
getSQL () {
// SELECT
const selectArray = this._query.select.reduce(this._reducer, []);
let selectRaw;
switch (this._client) {
case 'maria':
case 'mysql':
selectRaw = selectArray.length ? `SELECT ${selectArray.join(', ')} ` : 'SELECT * ';
break;
case 'mssql': {
const topRaw = this._query.limit.length ? ` TOP (${this._query.limit[0]}) ` : '';
selectRaw = selectArray.length ? `SELECT${topRaw} ${selectArray.join(', ')} ` : 'SELECT * ';
let selectRaw = '';
if (selectArray.length) {
switch (this._client) {
case 'maria':
case 'mysql':
selectRaw = selectArray.length ? `SELECT ${selectArray.join(', ')} ` : 'SELECT * ';
break;
case 'mssql': {
const topRaw = this._query.limit.length ? ` TOP (${this._query.limit[0]}) ` : '';
selectRaw = selectArray.length ? `SELECT${topRaw} ${selectArray.join(', ')} ` : 'SELECT * ';
}
break;
default:
break;
}
break;
default:
break;
}
// FROM
let fromRaw;
let fromRaw = '';
if (!this._query.update.length)
fromRaw = 'FROM';
switch (this._client) {
case 'maria':
case 'mysql':
fromRaw = this._query.from ? `FROM ${this._query.schema ? `\`${this._query.schema}\`.` : ''}\`${this._query.from}\` ` : '';
fromRaw += this._query.from ? ` ${this._query.schema ? `\`${this._query.schema}\`.` : ''}\`${this._query.from}\` ` : '';
break;
case 'mssql':
fromRaw = this._query.from ? `FROM ${this._query.schema ? `${this._query.schema}.` : ''}${this._query.from} ` : '';
fromRaw += this._query.from ? ` ${this._query.schema ? `${this._query.schema}.` : ''}${this._query.from} ` : '';
break;
default:
break;
@@ -187,8 +193,13 @@ export class AntaresConnector {
const whereArray = this._query.where.reduce(this._reducer, []);
const whereRaw = whereArray.length ? `WHERE ${whereArray.join(' AND ')} ` : '';
const updateArray = this._query.update.reduce(this._reducer, []);
const updateRaw = updateArray.length ? `SET ${updateArray.join(', ')} ` : '';
const groupByArray = this._query.groupBy.reduce(this._reducer, []);
const groupByRaw = groupByArray.length ? `GROUP BY ${groupByArray.join(', ')} ` : '';
const orderByArray = this._query.orderBy.reduce(this._reducer, []);
const orderByRaw = orderByArray.length ? `ORDER BY ${orderByArray.join(', ')} ` : '';
@@ -206,7 +217,7 @@ export class AntaresConnector {
break;
}
return `${selectRaw}${fromRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}`;
return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}`;
}
/**
@@ -227,7 +238,7 @@ export class AntaresConnector {
async raw (sql) {
if (process.env.NODE_ENV === 'development') console.log(sql);
switch (this._client) {
switch (this._client) { // TODO: uniform fields with every client type, needed table name and fields array
case 'maria':
case 'mysql': {
const { rows, fields } = await new Promise((resolve, reject) => {

View File

@@ -20,4 +20,13 @@ export default class {
.limit(1000)
.run();
}
static async updateTableCell (connection, params) { // TODO: Handle different field types
return connection
.update({ [params.field]: `= "${params.content}"` })
.schema(params.schema)
.from(params.table)
.where({ [params.primary]: `= ${params.id}` })
.run();
}
}

View File

@@ -74,7 +74,9 @@
</div>
<div v-if="selectedTab === 'themes'" class="panel-body py-4">
<!-- -->
<div class="text-center">
<p>In future releases</p>
</div>
</div>
<div v-if="selectedTab === 'update'" class="panel-body py-4">
@@ -87,7 +89,7 @@
<h4>{{ appName }}</h4>
<p>
{{ $t('word.version') }}: {{ appVersion }}<br>
<a class="c-hand" @click="openOutside('https://github.com/Fabio286/antares')">GitHub</a><br>
<a class="c-hand" @click="openOutside('https://github.com/EStarium/antares')">GitHub</a><br>
<small>{{ $t('message.madeWithJS') }}</small>
</p>
</div>

View File

@@ -11,11 +11,11 @@
<div class="footer-right-elements">
<ul class="footer-elements">
<li class="footer-element footer-link">
<li class="footer-element footer-link" @click="openOutside('https://www.patreon.com/fabio286')">
<i class="material-icons md-18 mr-1">favorite</i>
<small>{{ $t('word.donate') }}</small>
</li>
<li class="footer-element footer-link">
<li class="footer-element footer-link" @click="openOutside('https://github.com/EStarium/antares/issues')">
<i class="material-icons md-18">bug_report</i>
</li>
<li class="footer-element footer-link" @click="showSettingModal('about')">
@@ -28,6 +28,7 @@
<script>
import { mapActions, mapGetters } from 'vuex';
const { shell } = require('electron');
export default {
name: 'TheFooter',
@@ -40,7 +41,10 @@ export default {
methods: {
...mapActions({
showSettingModal: 'application/showSettingModal'
})
}),
openOutside (link) {
shell.openExternal(link);
}
}
};
</script>

View File

@@ -82,7 +82,6 @@ export default {
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
addWorkspace: 'workspaces/addWorkspace',
connectWorkspace: 'workspaces/connectWorkspace',
removeConnected: 'workspaces/removeConnected',

View File

@@ -45,7 +45,6 @@ export default {
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
connectWorkspace: 'workspaces/connectWorkspace'
}),
async startConnection () {

View File

@@ -31,8 +31,10 @@
<div class="workspace-query-results column col-12">
<WorkspaceQueryTable
v-if="results"
ref="queryTable"
:results="results"
:fields="resultsFields"
@updateField="updateField"
/>
</div>
</div>
@@ -40,6 +42,7 @@
<script>
import Connection from '@/ipc-api/Connection';
import Structure from '@/ipc-api/Structure';
import QueryEditor from '@/components/QueryEditor';
import WorkspaceQueryTable from '@/components/WorkspaceQueryTable';
import { mapGetters, mapActions } from 'vuex';
@@ -57,7 +60,8 @@ export default {
return {
query: '',
isQuering: false,
results: {}
results: {},
fields: []
};
},
computed: {
@@ -68,9 +72,27 @@ export default {
return this.getWorkspace(this.connection.uid);
},
resultsFields () {
return this.results.rows && this.results.rows.length ? Object.keys(this.results.rows[0]).map(field => {
return { name: field, key: '', type: '' }; // TODO: extract getting table name from query
}) : [];
if (this.results) {
return this.fields.map(field => { // TODO: move to main process
return {
name: field.COLUMN_NAME,
key: field.COLUMN_KEY.toLowerCase(),
type: field.DATA_TYPE
};
}).filter(field => {
if (this.results.fields) {
const queryFields = this.results.fields.map(field => field.name);
if (queryFields.includes(field.name)) return field;
}
});
}
else
return [];
},
table () {
if (this.results.fields.length)
return this.results.fields[0].orgTable;
return '';
}
},
methods: {
@@ -99,7 +121,43 @@ export default {
this.addNotification({ status: 'error', message: err.stack });
}
try {
const params = {
uid: this.connection.uid,
schema: this.workspace.breadcrumbs.schema,
table: this.table
};
const { status, response } = await Structure.getTableColumns(params);
if (status === 'success')
this.fields = response.rows;
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isQuering = false;
},
async updateField (payload) {
const params = {
uid: this.connection.uid,
schema: this.workspace.breadcrumbs.schema,
table: this.workspace.breadcrumbs.table,
...payload
};
try {
const { status, response } = await Structure.updateTableCell(params);
if (status === 'success')
this.$refs.queryTable.applyUpdate(payload);
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
}
}
};

View File

@@ -34,16 +34,14 @@
:key="row._id"
class="tr"
>
<div
<WorkspaceQueryTableCell
v-for="(col, cKey) in row"
:key="cKey"
class="td"
:class="`type-${fieldType(cKey)}${isNull(col)}`"
:style="{'display': cKey === '_id' ? 'none' : ''}"
tabindex="0"
>
{{ col | typeFormat(fieldType(cKey)) }}
</div>
:content="col"
:field="cKey"
:type="fieldType(cKey)"
@updateField="updateField($event, row[primaryField.name])"
/>
</div>
</div>
</div>
@@ -52,48 +50,16 @@
</template>
<script>
import { uidGen, mimeFromHex, formatBytes } from 'common/libs/utilities';
import hexToBinary from 'common/libs/hexToBinary';
import moment from 'moment';
import { uidGen } from 'common/libs/utilities';
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
import WorkspaceQueryTableCell from '@/components/WorkspaceQueryTableCell';
import { mapActions } from 'vuex';
export default {
name: 'WorkspaceQueryTable',
components: {
BaseVirtualScroll
},
filters: {
typeFormat (val, type) {
if (!val) return val;
switch (type) {
case 'char':
case 'varchar':
case 'text':
case 'mediumtext':
return val.substring(0, 128);
case 'date':
return moment(val).format('YYYY-MM-DD');
case 'datetime':
case 'timestamp':
return moment(val).format('YYYY-MM-DD HH:mm:ss.SSS');
case 'blob':
case 'mediumblob':
case 'longblob': {
const buff = Buffer.from(val);
if (!buff.length) return '';
const hex = buff.toString('hex').substring(0, 8).toUpperCase();
return `${mimeFromHex(hex).mime} (${formatBytes(buff.length)})`;
}
case 'bit': {
const hex = Buffer.from(val).toString('hex');
return hexToBinary(hex);
}
default:
return val;
}
}
BaseVirtualScroll,
WorkspaceQueryTableCell
},
props: {
results: Object,
@@ -105,6 +71,11 @@ export default {
localResults: []
};
},
computed: {
primaryField () {
return this.fields.filter(field => field.key === 'pri')[0] || false;
}
},
watch: {
results () {
this.localResults = this.results.rows ? this.results.rows.map(item => {
@@ -123,6 +94,9 @@ export default {
window.removeEventListener('resize', this.resizeResults);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification'
}),
fieldType (cKey) {
let type = 'unknown';
const field = this.fields.filter(field => field.name === cKey)[0];
@@ -131,9 +105,6 @@ export default {
return type;
},
isNull (col) {
return col === null ? ' is-null' : '';
},
keyName (key) {
switch (key) {
case 'pri':
@@ -156,6 +127,27 @@ export default {
this.resultsSize = size;
}
}
},
updateField (event, id) {
if (!this.primaryField)
this.addNotification({ status: 'warning', message: this.$t('message.unableEditFieldWithoutPrimary') });
else {
const params = {
primary: this.primaryField.name,
id,
...event
};
this.$emit('updateField', params);
}
},
applyUpdate (params) {
const { primary, id, field, content } = params;
this.localResults = this.localResults.map(row => {
if (row[primary] === id)
row[field] = content;
return row;
});
}
}
};

View File

@@ -0,0 +1,149 @@
<template>
<div
v-if="field !== '_id'"
ref="cell"
class="td"
:class="`type-${type} p-0`"
tabindex="0"
>
<span
v-if="!isEditing"
class="cell-content px-2"
:class="isNull(content)"
@dblclick="editON"
>{{ content | typeFormat(type) }}</span>
<input
v-else
ref="editField"
v-model="localContent"
:type="inputType"
autofocus
class="editable-field px-2"
@blur="editOFF"
>
</div>
</template>
<script>
import moment from 'moment';
import { mimeFromHex, formatBytes } from 'common/libs/utilities';
import hexToBinary from 'common/libs/hexToBinary';
export default {
name: 'WorkspaceQueryTableCell',
filters: {
typeFormat (val, type) {
if (!val) return val;
switch (type) {
case 'char':
case 'varchar':
case 'text':
case 'mediumtext':
return val.substring(0, 128);
case 'date':
return moment(val).format('YYYY-MM-DD');
case 'datetime':
case 'timestamp':
return moment(val).format('YYYY-MM-DD HH:mm:ss.SSS');
case 'blob':
case 'mediumblob':
case 'longblob': {
const buff = Buffer.from(val);
if (!buff.length) return '';
const hex = buff.toString('hex').substring(0, 8).toUpperCase();
return `${mimeFromHex(hex).mime} (${formatBytes(buff.length)})`;
}
case 'bit': {
const hex = Buffer.from(val).toString('hex');
return hexToBinary(hex);
}
default:
return val;
}
}
},
props: {
type: String,
field: String,
content: [String, Number, Object, Date, Uint8Array]
},
data () {
return {
isEditing: false,
localContent: ''
};
},
computed: {
inputType () {
switch (this.type) {
case 'char':
case 'varchar':
case 'text':
case 'mediumtext':
return 'text';
case 'int':
case 'tinyint':
case 'smallint':
case 'mediumint':
return 'number';
case 'date':
return 'date';
case 'datetime':
case 'timestamp':
return 'datetime-local';
// TODO: file uploader/viewer or bit field
case 'blob':
case 'mediumblob':
case 'longblob':
case 'bit':
default:
return 'hidden';
}
}
},
methods: {
isNull (value) {
return value === null ? ' is-null' : '';
},
editON () {
if (!['number', 'text'].includes(this.inputType)) return;// TODO: remove temporary block
this.$nextTick(() => {
this.$refs.cell.blur();
this.$nextTick(() => this.$refs.editField.focus());
});
this.localContent = this.$options.filters.typeFormat(this.content, this.type);
this.isEditing = true;
},
editOFF () {
this.isEditing = false;
if (this.localContent === this.content) return;
const { field, type, localContent: content } = this;
this.$emit('updateField', { field, type, content });
}
}
};
</script>
<style lang="scss">
.editable-field{
margin: 0;
border: none;
line-height: 1;
width: 100%;
max-width: 200px;
}
.cell-content{
display: block;
min-height: .8rem;
text-overflow: ellipsis;
max-width: 200px;
white-space: nowrap;
overflow: hidden;
}
</style>

View File

@@ -29,8 +29,10 @@
<div class="workspace-query-results column col-12">
<WorkspaceQueryTable
v-if="results"
ref="queryTable"
:results="results"
:fields="resultsFields"
@updateField="updateField"
/>
</div>
</div>
@@ -134,6 +136,25 @@ export default {
}
this.isQuering = false;
},
async updateField (payload) {
const params = {
uid: this.connection.uid,
schema: this.workspace.breadcrumbs.schema,
table: this.workspace.breadcrumbs.table,
...payload
};
try {
const { status, response } = await Structure.updateTableCell(params);
if (status === 'success')
this.$refs.queryTable.applyUpdate(payload);
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
}
}
};

View File

@@ -51,7 +51,8 @@ module.exports = {
updateAvailable: 'Update available',
downloadingUpdate: 'Downloading update',
updateDownloaded: 'Update downloaded',
restartToInstall: 'Restart Antares to install'
restartToInstall: 'Restart Antares to install',
unableEditFieldWithoutPrimary: 'Unable to edit a field without a primary key in resultset'
},
// Date and Time
short: {

View File

@@ -9,4 +9,8 @@ export default class {
static getTableData (params) {
return ipcRenderer.invoke('getTableData', params);
}
static updateTableCell (params) {
return ipcRenderer.invoke('updateTableCell', params);
}
}

View File

@@ -7,6 +7,7 @@ $bg-color-gray: #272727;
$primary-color: #e36929;
$success-color: #32b643;
$error-color: #de3b28;
$warning-color: #e0a40c;
/*Sizes*/
$titlebar-height: 1.5rem;

View File

@@ -24,7 +24,7 @@ export default {
isSettingModal: state => state.is_setting_modal,
selectedSettingTab: state => state.selected_setting_tab,
getUpdateStatus: state => state.update_status,
getDownloadProgress: state => state.download_progress
getDownloadProgress: state => Number(state.download_progress.toFixed(1))
},
mutations: {
SET_LOADING_STATUS (state, payload) {