mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-28 17:36:00 +00:00
Merge branch 'master' into develop
This commit is contained in:
@@ -8,6 +8,13 @@ Builds:
|
|||||||
- linux - application for linux
|
- linux - application for linux
|
||||||
- win - application for Windows
|
- win - application for Windows
|
||||||
|
|
||||||
|
### 5.0.6
|
||||||
|
- ADDED: Search in columns
|
||||||
|
- CHANGED: Upgraded mongodb driver
|
||||||
|
- ADDED: Ability to reset view, when data load fails
|
||||||
|
- FIXED: Filtering works for complex types (geography, xml under MSSQL)
|
||||||
|
- FIXED: Fixed some NPM package problems
|
||||||
|
|
||||||
### 5.0.5
|
### 5.0.5
|
||||||
- ADDED: Visualisation geographics objects on map #288
|
- ADDED: Visualisation geographics objects on map #288
|
||||||
- ADDED: Support for native SQL as default value inside yaml files #296
|
- ADDED: Support for native SQL as default value inside yaml files #296
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "5.0.6-beta.6",
|
"version": "5.0.7-beta.4",
|
||||||
"name": "dbgate-all",
|
"name": "dbgate-all",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*",
|
"packages/*",
|
||||||
@@ -10,6 +10,10 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start:api": "yarn workspace dbgate-api start",
|
"start:api": "yarn workspace dbgate-api start",
|
||||||
"start:app": "cd app && yarn start",
|
"start:app": "cd app && yarn start",
|
||||||
|
"start:api:debug": "cross-env DEBUG=* yarn workspace dbgate-api start",
|
||||||
|
"start:app:debug": "cd app && cross-env DEBUG=* yarn start",
|
||||||
|
"start:api:debug:ssh": "cross-env DEBUG=ssh yarn workspace dbgate-api start",
|
||||||
|
"start:app:debug:ssh": "cd app && cross-env DEBUG=ssh yarn start",
|
||||||
"start:api:portal": "yarn workspace dbgate-api start:portal",
|
"start:api:portal": "yarn workspace dbgate-api start:portal",
|
||||||
"start:api:singledb": "yarn workspace dbgate-api start:singledb",
|
"start:api:singledb": "yarn workspace dbgate-api start:singledb",
|
||||||
"start:web": "yarn workspace dbgate-web dev",
|
"start:web": "yarn workspace dbgate-web dev",
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"dbgate-query-splitter": "^4.9.0",
|
"dbgate-query-splitter": "^4.9.0",
|
||||||
"dbgate-sqltree": "^5.0.0-alpha.1",
|
"dbgate-sqltree": "^5.0.0-alpha.1",
|
||||||
"dbgate-tools": "^5.0.0-alpha.1",
|
"dbgate-tools": "^5.0.0-alpha.1",
|
||||||
|
"debug": "^4.3.4",
|
||||||
"diff": "^5.0.0",
|
"diff": "^5.0.0",
|
||||||
"diff2html": "^3.4.13",
|
"diff2html": "^3.4.13",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
@@ -45,9 +46,9 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"ncp": "^2.0.0",
|
"ncp": "^2.0.0",
|
||||||
"node-cron": "^2.0.3",
|
"node-cron": "^2.0.3",
|
||||||
"node-ssh-forward": "^0.7.2",
|
|
||||||
"portfinder": "^1.0.28",
|
"portfinder": "^1.0.28",
|
||||||
"simple-encryptor": "^4.0.0",
|
"simple-encryptor": "^4.0.0",
|
||||||
|
"ssh2": "^1.11.0",
|
||||||
"tar": "^6.0.5",
|
"tar": "^6.0.5",
|
||||||
"uuid": "^3.4.0"
|
"uuid": "^3.4.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -59,13 +59,10 @@ module.exports = {
|
|||||||
|
|
||||||
getSettings_meta: true,
|
getSettings_meta: true,
|
||||||
async getSettings() {
|
async getSettings() {
|
||||||
try {
|
const res = await lock.acquire('settings', async () => {
|
||||||
return this.fillMissingSettings(
|
return await this.loadSettings();
|
||||||
JSON.parse(await fs.readFile(path.join(datadir(), 'settings.json'), { encoding: 'utf-8' }))
|
});
|
||||||
);
|
return res;
|
||||||
} catch (err) {
|
|
||||||
return this.fillMissingSettings({});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
fillMissingSettings(value) {
|
fillMissingSettings(value) {
|
||||||
@@ -79,12 +76,21 @@ module.exports = {
|
|||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async loadSettings() {
|
||||||
|
try {
|
||||||
|
const settingsText = await fs.readFile(path.join(datadir(), 'settings.json'), { encoding: 'utf-8' });
|
||||||
|
return this.fillMissingSettings(JSON.parse(settingsText));
|
||||||
|
} catch (err) {
|
||||||
|
return this.fillMissingSettings({});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
updateSettings_meta: true,
|
updateSettings_meta: true,
|
||||||
async updateSettings(values, req) {
|
async updateSettings(values, req) {
|
||||||
if (!hasPermission(`settings/change`, req)) return false;
|
if (!hasPermission(`settings/change`, req)) return false;
|
||||||
|
|
||||||
const res = await lock.acquire('update', async () => {
|
const res = await lock.acquire('settings', async () => {
|
||||||
const currentValue = await this.getSettings();
|
const currentValue = await this.loadSettings();
|
||||||
try {
|
try {
|
||||||
const updated = {
|
const updated = {
|
||||||
...currentValue,
|
...currentValue,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const platformInfo = require('../utility/platformInfo');
|
const platformInfo = require('../utility/platformInfo');
|
||||||
const childProcessChecker = require('../utility/childProcessChecker');
|
const childProcessChecker = require('../utility/childProcessChecker');
|
||||||
const { SSHConnection } = require('node-ssh-forward');
|
|
||||||
const { handleProcessCommunication } = require('../utility/processComm');
|
const { handleProcessCommunication } = require('../utility/processComm');
|
||||||
|
const { SSHConnection } = require('../utility/SSHConnection');
|
||||||
|
|
||||||
async function getSshConnection(connection) {
|
async function getSshConnection(connection) {
|
||||||
const sshConfig = {
|
const sshConfig = {
|
||||||
@@ -35,6 +35,8 @@ async function handleStart({ connection, tunnelConfig }) {
|
|||||||
tunnelConfig,
|
tunnelConfig,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.log('Error creating SSH tunnel connection:', err.message);
|
||||||
|
|
||||||
process.send({
|
process.send({
|
||||||
msgtype: 'error',
|
msgtype: 'error',
|
||||||
connection,
|
connection,
|
||||||
|
|||||||
251
packages/api/src/utility/SSHConnection.js
Normal file
251
packages/api/src/utility/SSHConnection.js
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 Stocard GmbH.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { Client } = require('ssh2');
|
||||||
|
const net = require('net');
|
||||||
|
const fs = require('fs');
|
||||||
|
const os = require('os');
|
||||||
|
const path = require('path');
|
||||||
|
const debug = require('debug');
|
||||||
|
|
||||||
|
// interface Options {
|
||||||
|
// username?: string;
|
||||||
|
// password?: string;
|
||||||
|
// privateKey?: string | Buffer;
|
||||||
|
// agentForward?: boolean;
|
||||||
|
// bastionHost?: string;
|
||||||
|
// passphrase?: string;
|
||||||
|
// endPort?: number;
|
||||||
|
// endHost: string;
|
||||||
|
// agentSocket?: string;
|
||||||
|
// skipAutoPrivateKey?: boolean;
|
||||||
|
// noReadline?: boolean;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// interface ForwardingOptions {
|
||||||
|
// fromPort: number;
|
||||||
|
// toPort: number;
|
||||||
|
// toHost?: string;
|
||||||
|
// }
|
||||||
|
|
||||||
|
class SSHConnection {
|
||||||
|
constructor(options) {
|
||||||
|
this.options = options;
|
||||||
|
this.debug = debug('ssh');
|
||||||
|
this.connections = [];
|
||||||
|
this.isWindows = process.platform === 'win32';
|
||||||
|
if (!options.username) {
|
||||||
|
this.options.username = process.env['SSH_USERNAME'] || process.env['USER'];
|
||||||
|
}
|
||||||
|
if (!options.endPort) {
|
||||||
|
this.options.endPort = 22;
|
||||||
|
}
|
||||||
|
if (!options.privateKey && !options.agentForward && !options.skipAutoPrivateKey) {
|
||||||
|
const defaultFilePath = path.join(os.homedir(), '.ssh', 'id_rsa');
|
||||||
|
if (fs.existsSync(defaultFilePath)) {
|
||||||
|
this.options.privateKey = fs.readFileSync(defaultFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async shutdown() {
|
||||||
|
this.debug('Shutdown connections');
|
||||||
|
for (const connection of this.connections) {
|
||||||
|
connection.removeAllListeners();
|
||||||
|
connection.end();
|
||||||
|
}
|
||||||
|
return new Promise(resolve => {
|
||||||
|
if (this.server) {
|
||||||
|
this.server.close(resolve);
|
||||||
|
}
|
||||||
|
return resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async tty() {
|
||||||
|
const connection = await this.establish();
|
||||||
|
this.debug('Opening tty');
|
||||||
|
await this.shell(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeCommand(command) {
|
||||||
|
const connection = await this.establish();
|
||||||
|
this.debug('Executing command "%s"', command);
|
||||||
|
await this.shell(connection, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
async shell(connection, command) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
connection.shell((err, stream) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
stream
|
||||||
|
.on('close', async () => {
|
||||||
|
stream.end();
|
||||||
|
process.stdin.unpipe(stream);
|
||||||
|
process.stdin.destroy();
|
||||||
|
connection.end();
|
||||||
|
await this.shutdown();
|
||||||
|
return resolve();
|
||||||
|
})
|
||||||
|
.stderr.on('data', data => {
|
||||||
|
return reject(data);
|
||||||
|
});
|
||||||
|
stream.pipe(process.stdout);
|
||||||
|
|
||||||
|
if (command) {
|
||||||
|
stream.end(`${command}\nexit\n`);
|
||||||
|
} else {
|
||||||
|
process.stdin.pipe(stream);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async establish() {
|
||||||
|
let connection;
|
||||||
|
if (this.options.bastionHost) {
|
||||||
|
connection = await this.connectViaBastion(this.options.bastionHost);
|
||||||
|
} else {
|
||||||
|
connection = await this.connect(this.options.endHost);
|
||||||
|
}
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
async connectViaBastion(bastionHost) {
|
||||||
|
this.debug('Connecting to bastion host "%s"', bastionHost);
|
||||||
|
const connectionToBastion = await this.connect(bastionHost);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
connectionToBastion.forwardOut(
|
||||||
|
'127.0.0.1',
|
||||||
|
22,
|
||||||
|
this.options.endHost,
|
||||||
|
this.options.endPort || 22,
|
||||||
|
async (err, stream) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
const connection = await this.connect(this.options.endHost, stream);
|
||||||
|
return resolve(connection);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect(host, stream) {
|
||||||
|
this.debug('Connecting to "%s"', host);
|
||||||
|
const connection = new Client();
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
const options = {
|
||||||
|
host,
|
||||||
|
port: this.options.endPort,
|
||||||
|
username: this.options.username,
|
||||||
|
password: this.options.password,
|
||||||
|
privateKey: this.options.privateKey,
|
||||||
|
};
|
||||||
|
if (this.options.agentForward) {
|
||||||
|
options['agentForward'] = true;
|
||||||
|
|
||||||
|
// see https://github.com/mscdex/ssh2#client for agents on Windows
|
||||||
|
// guaranteed to give the ssh agent sock if the agent is running (posix)
|
||||||
|
let agentDefault = process.env['SSH_AUTH_SOCK'];
|
||||||
|
if (this.isWindows) {
|
||||||
|
// null or undefined
|
||||||
|
if (agentDefault == null) {
|
||||||
|
agentDefault = 'pageant';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const agentSock = this.options.agentSocket ? this.options.agentSocket : agentDefault;
|
||||||
|
if (agentSock == null) {
|
||||||
|
throw new Error('SSH Agent Socket is not provided, or is not set in the SSH_AUTH_SOCK env variable');
|
||||||
|
}
|
||||||
|
options['agent'] = agentSock;
|
||||||
|
}
|
||||||
|
if (stream) {
|
||||||
|
options['sock'] = stream;
|
||||||
|
}
|
||||||
|
// PPK private keys can be encrypted, but won't contain the word 'encrypted'
|
||||||
|
// in fact they always contain a `encryption` header, so we can't do a simple check
|
||||||
|
options['passphrase'] = this.options.passphrase;
|
||||||
|
const looksEncrypted = this.options.privateKey
|
||||||
|
? this.options.privateKey.toString().toLowerCase().includes('encrypted')
|
||||||
|
: false;
|
||||||
|
if (looksEncrypted && !options['passphrase'] && !this.options.noReadline) {
|
||||||
|
// options['passphrase'] = await this.getPassphrase();
|
||||||
|
}
|
||||||
|
connection.on('ready', () => {
|
||||||
|
this.connections.push(connection);
|
||||||
|
return resolve(connection);
|
||||||
|
});
|
||||||
|
|
||||||
|
connection.on('error', error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
connection.connect(options);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// private async getPassphrase() {
|
||||||
|
// return new Promise(resolve => {
|
||||||
|
// const rl = readline.createInterface({
|
||||||
|
// input: process.stdin,
|
||||||
|
// output: process.stdout,
|
||||||
|
// });
|
||||||
|
// rl.question('Please type in the passphrase for your private key: ', answer => {
|
||||||
|
// return resolve(answer);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
async forward(options) {
|
||||||
|
const connection = await this.establish();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.server = net
|
||||||
|
.createServer(socket => {
|
||||||
|
this.debug(
|
||||||
|
'Forwarding connection from "localhost:%d" to "%s:%d"',
|
||||||
|
options.fromPort,
|
||||||
|
options.toHost,
|
||||||
|
options.toPort
|
||||||
|
);
|
||||||
|
connection.forwardOut(
|
||||||
|
'localhost',
|
||||||
|
options.fromPort,
|
||||||
|
options.toHost || 'localhost',
|
||||||
|
options.toPort,
|
||||||
|
(error, stream) => {
|
||||||
|
if (error) {
|
||||||
|
return reject(error);
|
||||||
|
}
|
||||||
|
socket.pipe(stream);
|
||||||
|
stream.pipe(socket);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.listen(options.fromPort, 'localhost', () => {
|
||||||
|
return resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { SSHConnection };
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
const { SSHConnection } = require('node-ssh-forward');
|
|
||||||
const portfinder = require('portfinder');
|
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const { decryptConnection } = require('./crypting');
|
const { decryptConnection } = require('./crypting');
|
||||||
const { getSshTunnel } = require('./sshTunnel');
|
|
||||||
const { getSshTunnelProxy } = require('./sshTunnelProxy');
|
const { getSshTunnelProxy } = require('./sshTunnelProxy');
|
||||||
const platformInfo = require('../utility/platformInfo');
|
const platformInfo = require('../utility/platformInfo');
|
||||||
const connections = require('../controllers/connections');
|
const connections = require('../controllers/connections');
|
||||||
|
|||||||
279
packages/filterparser/src/datetimeParser.ts
Normal file
279
packages/filterparser/src/datetimeParser.ts
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
import P from 'parsimmon';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { FilterType } from './types';
|
||||||
|
import { Condition } from 'dbgate-sqltree';
|
||||||
|
import { TransformType } from 'dbgate-types';
|
||||||
|
import { interpretEscapes, token, word, whitespace } from './common';
|
||||||
|
|
||||||
|
const compoudCondition = conditionType => conditions => {
|
||||||
|
if (conditions.length == 1) return conditions[0];
|
||||||
|
return {
|
||||||
|
[conditionType]: conditions,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function getTransformCondition(transform: TransformType, value) {
|
||||||
|
return {
|
||||||
|
conditionType: 'binary',
|
||||||
|
operator: '=',
|
||||||
|
left: {
|
||||||
|
exprType: 'transform',
|
||||||
|
transform,
|
||||||
|
expr: {
|
||||||
|
exprType: 'placeholder',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
exprType: 'value',
|
||||||
|
value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const yearCondition = () => value => {
|
||||||
|
return getTransformCondition('YEAR', value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const yearMonthCondition = () => value => {
|
||||||
|
const m = value.match(/(\d\d\d\d)-(\d\d?)/);
|
||||||
|
|
||||||
|
return {
|
||||||
|
conditionType: 'and',
|
||||||
|
conditions: [getTransformCondition('YEAR', m[1]), getTransformCondition('MONTH', m[2])],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const yearMonthDayCondition = () => value => {
|
||||||
|
const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)/);
|
||||||
|
|
||||||
|
return {
|
||||||
|
conditionType: 'and',
|
||||||
|
conditions: [
|
||||||
|
getTransformCondition('YEAR', m[1]),
|
||||||
|
getTransformCondition('MONTH', m[2]),
|
||||||
|
getTransformCondition('DAY', m[3]),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const yearEdge = edgeFunction => value => {
|
||||||
|
return moment(new Date(parseInt(value), 0, 1))
|
||||||
|
[edgeFunction]('year')
|
||||||
|
.format('YYYY-MM-DDTHH:mm:ss.SSS');
|
||||||
|
};
|
||||||
|
|
||||||
|
const yearMonthEdge = edgeFunction => value => {
|
||||||
|
const m = value.match(/(\d\d\d\d)-(\d\d?)/);
|
||||||
|
|
||||||
|
return moment(new Date(parseInt(m[1]), parseInt(m[2]) - 1, 1))
|
||||||
|
[edgeFunction]('month')
|
||||||
|
.format('YYYY-MM-DDTHH:mm:ss.SSS');
|
||||||
|
};
|
||||||
|
|
||||||
|
const yearMonthDayEdge = edgeFunction => value => {
|
||||||
|
const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)/);
|
||||||
|
|
||||||
|
return moment(new Date(parseInt(m[1]), parseInt(m[2]) - 1, parseInt(m[3])))
|
||||||
|
[edgeFunction]('day')
|
||||||
|
.format('YYYY-MM-DDTHH:mm:ss.SSS');
|
||||||
|
};
|
||||||
|
|
||||||
|
const yearMonthDayMinuteEdge = edgeFunction => value => {
|
||||||
|
const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)\s+(\d\d?):(\d\d?)/);
|
||||||
|
const year = m[1];
|
||||||
|
const month = m[2];
|
||||||
|
const day = m[3];
|
||||||
|
const hour = m[4];
|
||||||
|
const minute = m[5];
|
||||||
|
const dateObject = new Date(year, month - 1, day, hour, minute);
|
||||||
|
|
||||||
|
return moment(dateObject)[edgeFunction]('minute').format('YYYY-MM-DDTHH:mm:ss.SSS');
|
||||||
|
};
|
||||||
|
|
||||||
|
const yearMonthDayMinuteSecondEdge = edgeFunction => value => {
|
||||||
|
const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)(T|\s+)(\d\d?):(\d\d?):(\d\d?)/);
|
||||||
|
const year = m[1];
|
||||||
|
const month = m[2];
|
||||||
|
const day = m[3];
|
||||||
|
const hour = m[5];
|
||||||
|
const minute = m[6];
|
||||||
|
const second = m[7];
|
||||||
|
const dateObject = new Date(year, month - 1, day, hour, minute, second);
|
||||||
|
|
||||||
|
return moment(dateObject)[edgeFunction]('second').format('YYYY-MM-DDTHH:mm:ss.SSS');
|
||||||
|
};
|
||||||
|
|
||||||
|
const createIntervalCondition = (start, end) => {
|
||||||
|
return {
|
||||||
|
conditionType: 'and',
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
conditionType: 'binary',
|
||||||
|
operator: '>=',
|
||||||
|
left: {
|
||||||
|
exprType: 'placeholder',
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
exprType: 'value',
|
||||||
|
value: start,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
conditionType: 'binary',
|
||||||
|
operator: '<=',
|
||||||
|
left: {
|
||||||
|
exprType: 'placeholder',
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
exprType: 'value',
|
||||||
|
value: end,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const createDateIntervalCondition = (start, end) => {
|
||||||
|
return createIntervalCondition(start.format('YYYY-MM-DDTHH:mm:ss.SSS'), end.format('YYYY-MM-DDTHH:mm:ss.SSS'));
|
||||||
|
};
|
||||||
|
|
||||||
|
const fixedMomentIntervalCondition = (intervalType, diff) => () => {
|
||||||
|
return createDateIntervalCondition(
|
||||||
|
moment().add(intervalType, diff).startOf(intervalType),
|
||||||
|
moment().add(intervalType, diff).endOf(intervalType)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const yearMonthDayMinuteCondition = () => value => {
|
||||||
|
const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)\s+(\d\d?):(\d\d?)/);
|
||||||
|
const year = m[1];
|
||||||
|
const month = m[2];
|
||||||
|
const day = m[3];
|
||||||
|
const hour = m[4];
|
||||||
|
const minute = m[5];
|
||||||
|
const dateObject = new Date(year, month - 1, day, hour, minute);
|
||||||
|
|
||||||
|
return createDateIntervalCondition(moment(dateObject).startOf('minute'), moment(dateObject).endOf('minute'));
|
||||||
|
};
|
||||||
|
|
||||||
|
const yearMonthDaySecondCondition = () => value => {
|
||||||
|
const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)(T|\s+)(\d\d?):(\d\d?):(\d\d?)/);
|
||||||
|
const year = m[1];
|
||||||
|
const month = m[2];
|
||||||
|
const day = m[3];
|
||||||
|
const hour = m[5];
|
||||||
|
const minute = m[6];
|
||||||
|
const second = m[7];
|
||||||
|
const dateObject = new Date(year, month - 1, day, hour, minute, second);
|
||||||
|
|
||||||
|
return createDateIntervalCondition(moment(dateObject).startOf('second'), moment(dateObject).endOf('second'));
|
||||||
|
};
|
||||||
|
|
||||||
|
const binaryCondition = operator => value => ({
|
||||||
|
conditionType: 'binary',
|
||||||
|
operator,
|
||||||
|
left: {
|
||||||
|
exprType: 'placeholder',
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
exprType: 'value',
|
||||||
|
value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const createParser = () => {
|
||||||
|
const langDef = {
|
||||||
|
comma: () => word(','),
|
||||||
|
|
||||||
|
yearNum: () => P.regexp(/\d\d\d\d/).map(yearCondition()),
|
||||||
|
yearMonthNum: () => P.regexp(/\d\d\d\d-\d\d?/).map(yearMonthCondition()),
|
||||||
|
yearMonthDayNum: () => P.regexp(/\d\d\d\d-\d\d?-\d\d?/).map(yearMonthDayCondition()),
|
||||||
|
yearMonthDayMinute: () => P.regexp(/\d\d\d\d-\d\d?-\d\d?\s+\d\d?:\d\d?/).map(yearMonthDayMinuteCondition()),
|
||||||
|
yearMonthDaySecond: () =>
|
||||||
|
P.regexp(/\d\d\d\d-\d\d?-\d\d?(\s+|T)\d\d?:\d\d?:\d\d?/).map(yearMonthDaySecondCondition()),
|
||||||
|
|
||||||
|
yearNumStart: () => P.regexp(/\d\d\d\d/).map(yearEdge('startOf')),
|
||||||
|
yearNumEnd: () => P.regexp(/\d\d\d\d/).map(yearEdge('endOf')),
|
||||||
|
yearMonthStart: () => P.regexp(/\d\d\d\d-\d\d?/).map(yearMonthEdge('startOf')),
|
||||||
|
yearMonthEnd: () => P.regexp(/\d\d\d\d-\d\d?/).map(yearMonthEdge('endOf')),
|
||||||
|
yearMonthDayStart: () => P.regexp(/\d\d\d\d-\d\d?-\d\d?/).map(yearMonthDayEdge('startOf')),
|
||||||
|
yearMonthDayEnd: () => P.regexp(/\d\d\d\d-\d\d?-\d\d?/).map(yearMonthDayEdge('endOf')),
|
||||||
|
yearMonthDayMinuteStart: () =>
|
||||||
|
P.regexp(/\d\d\d\d-\d\d?-\d\d?\s+\d\d?:\d\d?/).map(yearMonthDayMinuteEdge('startOf')),
|
||||||
|
yearMonthDayMinuteEnd: () => P.regexp(/\d\d\d\d-\d\d?-\d\d?\s+\d\d?:\d\d?/).map(yearMonthDayMinuteEdge('endOf')),
|
||||||
|
yearMonthDayMinuteSecondStart: () =>
|
||||||
|
P.regexp(/\d\d\d\d-\d\d?-\d\d?(\s+|T)\d\d?:\d\d?:\d\d?/).map(yearMonthDayMinuteSecondEdge('startOf')),
|
||||||
|
yearMonthDayMinuteSecondEnd: () =>
|
||||||
|
P.regexp(/\d\d\d\d-\d\d?-\d\d?(\s+|T)\d\d?:\d\d?:\d\d?/).map(yearMonthDayMinuteSecondEdge('endOf')),
|
||||||
|
|
||||||
|
this: () => word('THIS'),
|
||||||
|
last: () => word('LAST'),
|
||||||
|
next: () => word('NEXT'),
|
||||||
|
week: () => word('WEEK'),
|
||||||
|
month: () => word('MONTH'),
|
||||||
|
year: () => word('YEAR'),
|
||||||
|
|
||||||
|
yesterday: () => word('YESTERDAY').map(fixedMomentIntervalCondition('day', -1)),
|
||||||
|
today: () => word('TODAY').map(fixedMomentIntervalCondition('day', 0)),
|
||||||
|
tomorrow: () => word('TOMORROW').map(fixedMomentIntervalCondition('day', 1)),
|
||||||
|
|
||||||
|
lastWeek: r => r.last.then(r.week).map(fixedMomentIntervalCondition('week', -1)),
|
||||||
|
thisWeek: r => r.this.then(r.week).map(fixedMomentIntervalCondition('week', 0)),
|
||||||
|
nextWeek: r => r.next.then(r.week).map(fixedMomentIntervalCondition('week', 1)),
|
||||||
|
|
||||||
|
lastMonth: r => r.last.then(r.month).map(fixedMomentIntervalCondition('month', -1)),
|
||||||
|
thisMonth: r => r.this.then(r.month).map(fixedMomentIntervalCondition('month', 0)),
|
||||||
|
nextMonth: r => r.next.then(r.month).map(fixedMomentIntervalCondition('month', 1)),
|
||||||
|
|
||||||
|
lastYear: r => r.last.then(r.year).map(fixedMomentIntervalCondition('year', -1)),
|
||||||
|
thisYear: r => r.this.then(r.year).map(fixedMomentIntervalCondition('year', 0)),
|
||||||
|
nextYear: r => r.next.then(r.year).map(fixedMomentIntervalCondition('year', 1)),
|
||||||
|
|
||||||
|
valueStart: r =>
|
||||||
|
P.alt(
|
||||||
|
r.yearMonthDayMinuteSecondStart,
|
||||||
|
r.yearMonthDayMinuteStart,
|
||||||
|
r.yearMonthDayStart,
|
||||||
|
r.yearMonthStart,
|
||||||
|
r.yearNumStart
|
||||||
|
),
|
||||||
|
valueEnd: r =>
|
||||||
|
P.alt(r.yearMonthDayMinuteSecondEnd, r.yearMonthDayMinuteEnd, r.yearMonthDayEnd, r.yearMonthEnd, r.yearNumEnd),
|
||||||
|
|
||||||
|
le: r => word('<=').then(r.valueEnd).map(binaryCondition('<=')),
|
||||||
|
ge: r => word('>=').then(r.valueStart).map(binaryCondition('>=')),
|
||||||
|
lt: r => word('<').then(r.valueStart).map(binaryCondition('<')),
|
||||||
|
gt: r => word('>').then(r.valueEnd).map(binaryCondition('>')),
|
||||||
|
|
||||||
|
element: r =>
|
||||||
|
P.alt(
|
||||||
|
r.yearMonthDaySecond,
|
||||||
|
r.yearMonthDayMinute,
|
||||||
|
r.yearMonthDayNum,
|
||||||
|
r.yearMonthNum,
|
||||||
|
r.yearNum,
|
||||||
|
r.yesterday,
|
||||||
|
r.today,
|
||||||
|
r.tomorrow,
|
||||||
|
r.lastWeek,
|
||||||
|
r.thisWeek,
|
||||||
|
r.nextWeek,
|
||||||
|
r.lastMonth,
|
||||||
|
r.thisMonth,
|
||||||
|
r.nextMonth,
|
||||||
|
r.lastYear,
|
||||||
|
r.thisYear,
|
||||||
|
r.nextYear,
|
||||||
|
r.le,
|
||||||
|
r.lt,
|
||||||
|
r.ge,
|
||||||
|
r.gt
|
||||||
|
).trim(whitespace),
|
||||||
|
factor: r => r.element.sepBy(whitespace).map(compoudCondition('$and')),
|
||||||
|
list: r => r.factor.sepBy(r.comma).map(compoudCondition('$or')),
|
||||||
|
};
|
||||||
|
|
||||||
|
return P.createLanguage(langDef);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const datetimeParser = createParser();
|
||||||
@@ -5,6 +5,7 @@ import { Condition } from 'dbgate-sqltree';
|
|||||||
import { TransformType } from 'dbgate-types';
|
import { TransformType } from 'dbgate-types';
|
||||||
import { interpretEscapes, token, word, whitespace } from './common';
|
import { interpretEscapes, token, word, whitespace } from './common';
|
||||||
import { mongoParser } from './mongoParser';
|
import { mongoParser } from './mongoParser';
|
||||||
|
import { datetimeParser } from './datetimeParser';
|
||||||
|
|
||||||
const binaryCondition = operator => value => ({
|
const binaryCondition = operator => value => ({
|
||||||
conditionType: 'binary',
|
conditionType: 'binary',
|
||||||
@@ -67,116 +68,6 @@ const negateCondition = condition => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function getTransformCondition(transform: TransformType, value) {
|
|
||||||
return {
|
|
||||||
conditionType: 'binary',
|
|
||||||
operator: '=',
|
|
||||||
left: {
|
|
||||||
exprType: 'transform',
|
|
||||||
transform,
|
|
||||||
expr: {
|
|
||||||
exprType: 'placeholder',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
right: {
|
|
||||||
exprType: 'value',
|
|
||||||
value,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const yearCondition = () => value => {
|
|
||||||
return getTransformCondition('YEAR', value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const yearMonthCondition = () => value => {
|
|
||||||
const m = value.match(/(\d\d\d\d)-(\d\d?)/);
|
|
||||||
|
|
||||||
return {
|
|
||||||
conditionType: 'and',
|
|
||||||
conditions: [getTransformCondition('YEAR', m[1]), getTransformCondition('MONTH', m[2])],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const yearMonthDayCondition = () => value => {
|
|
||||||
const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)/);
|
|
||||||
|
|
||||||
return {
|
|
||||||
conditionType: 'and',
|
|
||||||
conditions: [
|
|
||||||
getTransformCondition('YEAR', m[1]),
|
|
||||||
getTransformCondition('MONTH', m[2]),
|
|
||||||
getTransformCondition('DAY', m[3]),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const createIntervalCondition = (start, end) => {
|
|
||||||
return {
|
|
||||||
conditionType: 'and',
|
|
||||||
conditions: [
|
|
||||||
{
|
|
||||||
conditionType: 'binary',
|
|
||||||
operator: '>=',
|
|
||||||
left: {
|
|
||||||
exprType: 'placeholder',
|
|
||||||
},
|
|
||||||
right: {
|
|
||||||
exprType: 'value',
|
|
||||||
value: start,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
conditionType: 'binary',
|
|
||||||
operator: '<=',
|
|
||||||
left: {
|
|
||||||
exprType: 'placeholder',
|
|
||||||
},
|
|
||||||
right: {
|
|
||||||
exprType: 'value',
|
|
||||||
value: end,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const createDateIntervalCondition = (start, end) => {
|
|
||||||
return createIntervalCondition(start.format('YYYY-MM-DDTHH:mm:ss.SSS'), end.format('YYYY-MM-DDTHH:mm:ss.SSS'));
|
|
||||||
};
|
|
||||||
|
|
||||||
const fixedMomentIntervalCondition = (intervalType, diff) => () => {
|
|
||||||
return createDateIntervalCondition(
|
|
||||||
moment().add(intervalType, diff).startOf(intervalType),
|
|
||||||
moment().add(intervalType, diff).endOf(intervalType)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const yearMonthDayMinuteCondition = () => value => {
|
|
||||||
const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)\s+(\d\d?):(\d\d?)/);
|
|
||||||
const year = m[1];
|
|
||||||
const month = m[2];
|
|
||||||
const day = m[3];
|
|
||||||
const hour = m[4];
|
|
||||||
const minute = m[5];
|
|
||||||
const dateObject = new Date(year, month - 1, day, hour, minute);
|
|
||||||
|
|
||||||
return createDateIntervalCondition(moment(dateObject).startOf('minute'), moment(dateObject).endOf('minute'));
|
|
||||||
};
|
|
||||||
|
|
||||||
const yearMonthDaySecondCondition = () => value => {
|
|
||||||
const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)(T|\s+)(\d\d?):(\d\d?):(\d\d?)/);
|
|
||||||
const year = m[1];
|
|
||||||
const month = m[2];
|
|
||||||
const day = m[3];
|
|
||||||
const hour = m[5];
|
|
||||||
const minute = m[6];
|
|
||||||
const second = m[7];
|
|
||||||
const dateObject = new Date(year, month - 1, day, hour, minute, second);
|
|
||||||
|
|
||||||
return createDateIntervalCondition(moment(dateObject).startOf('second'), moment(dateObject).endOf('second'));
|
|
||||||
};
|
|
||||||
|
|
||||||
const createParser = (filterType: FilterType) => {
|
const createParser = (filterType: FilterType) => {
|
||||||
const langDef = {
|
const langDef = {
|
||||||
string1: () =>
|
string1: () =>
|
||||||
@@ -206,13 +97,6 @@ const createParser = (filterType: FilterType) => {
|
|||||||
|
|
||||||
noQuotedString: () => P.regexp(/[^\s^,^'^"]+/).desc('string unquoted'),
|
noQuotedString: () => P.regexp(/[^\s^,^'^"]+/).desc('string unquoted'),
|
||||||
|
|
||||||
yearNum: () => P.regexp(/\d\d\d\d/).map(yearCondition()),
|
|
||||||
yearMonthNum: () => P.regexp(/\d\d\d\d-\d\d?/).map(yearMonthCondition()),
|
|
||||||
yearMonthDayNum: () => P.regexp(/\d\d\d\d-\d\d?-\d\d?/).map(yearMonthDayCondition()),
|
|
||||||
yearMonthDayMinute: () => P.regexp(/\d\d\d\d-\d\d?-\d\d?\s+\d\d?:\d\d?/).map(yearMonthDayMinuteCondition()),
|
|
||||||
yearMonthDaySecond: () =>
|
|
||||||
P.regexp(/\d\d\d\d-\d\d?-\d\d?(\s+|T)\d\d?:\d\d?:\d\d?/).map(yearMonthDaySecondCondition()),
|
|
||||||
|
|
||||||
value: r => P.alt(...allowedValues.map(x => r[x])),
|
value: r => P.alt(...allowedValues.map(x => r[x])),
|
||||||
valueTestEq: r => r.value.map(binaryCondition('=')),
|
valueTestEq: r => r.value.map(binaryCondition('=')),
|
||||||
valueTestStr: r => r.value.map(likeCondition('like', '%#VALUE#%')),
|
valueTestStr: r => r.value.map(likeCondition('like', '%#VALUE#%')),
|
||||||
@@ -228,29 +112,6 @@ const createParser = (filterType: FilterType) => {
|
|||||||
trueNum: () => word('1').map(binaryFixedValueCondition('1')),
|
trueNum: () => word('1').map(binaryFixedValueCondition('1')),
|
||||||
falseNum: () => word('0').map(binaryFixedValueCondition('0')),
|
falseNum: () => word('0').map(binaryFixedValueCondition('0')),
|
||||||
|
|
||||||
this: () => word('THIS'),
|
|
||||||
last: () => word('LAST'),
|
|
||||||
next: () => word('NEXT'),
|
|
||||||
week: () => word('WEEK'),
|
|
||||||
month: () => word('MONTH'),
|
|
||||||
year: () => word('YEAR'),
|
|
||||||
|
|
||||||
yesterday: () => word('YESTERDAY').map(fixedMomentIntervalCondition('day', -1)),
|
|
||||||
today: () => word('TODAY').map(fixedMomentIntervalCondition('day', 0)),
|
|
||||||
tomorrow: () => word('TOMORROW').map(fixedMomentIntervalCondition('day', 1)),
|
|
||||||
|
|
||||||
lastWeek: r => r.last.then(r.week).map(fixedMomentIntervalCondition('week', -1)),
|
|
||||||
thisWeek: r => r.this.then(r.week).map(fixedMomentIntervalCondition('week', 0)),
|
|
||||||
nextWeek: r => r.next.then(r.week).map(fixedMomentIntervalCondition('week', 1)),
|
|
||||||
|
|
||||||
lastMonth: r => r.last.then(r.month).map(fixedMomentIntervalCondition('month', -1)),
|
|
||||||
thisMonth: r => r.this.then(r.month).map(fixedMomentIntervalCondition('month', 0)),
|
|
||||||
nextMonth: r => r.next.then(r.month).map(fixedMomentIntervalCondition('month', 1)),
|
|
||||||
|
|
||||||
lastYear: r => r.last.then(r.year).map(fixedMomentIntervalCondition('year', -1)),
|
|
||||||
thisYear: r => r.this.then(r.year).map(fixedMomentIntervalCondition('year', 0)),
|
|
||||||
nextYear: r => r.next.then(r.year).map(fixedMomentIntervalCondition('year', 1)),
|
|
||||||
|
|
||||||
eq: r => word('=').then(r.value).map(binaryCondition('=')),
|
eq: r => word('=').then(r.value).map(binaryCondition('=')),
|
||||||
ne: r => word('!=').then(r.value).map(binaryCondition('<>')),
|
ne: r => word('!=').then(r.value).map(binaryCondition('<>')),
|
||||||
ne2: r => word('<>').then(r.value).map(binaryCondition('<>')),
|
ne2: r => word('<>').then(r.value).map(binaryCondition('<>')),
|
||||||
@@ -294,27 +155,7 @@ const createParser = (filterType: FilterType) => {
|
|||||||
if (filterType == 'eval') {
|
if (filterType == 'eval') {
|
||||||
allowedElements.push('true', 'false');
|
allowedElements.push('true', 'false');
|
||||||
}
|
}
|
||||||
if (filterType == 'datetime') {
|
|
||||||
allowedElements.push(
|
|
||||||
'yearMonthDaySecond',
|
|
||||||
'yearMonthDayMinute',
|
|
||||||
'yearMonthDayNum',
|
|
||||||
'yearMonthNum',
|
|
||||||
'yearNum',
|
|
||||||
'yesterday',
|
|
||||||
'today',
|
|
||||||
'tomorrow',
|
|
||||||
'lastWeek',
|
|
||||||
'thisWeek',
|
|
||||||
'nextWeek',
|
|
||||||
'lastMonth',
|
|
||||||
'thisMonth',
|
|
||||||
'nextMonth',
|
|
||||||
'lastYear',
|
|
||||||
'thisYear',
|
|
||||||
'nextYear'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// must be last
|
// must be last
|
||||||
if (filterType == 'string' || filterType == 'eval') {
|
if (filterType == 'string' || filterType == 'eval') {
|
||||||
allowedElements.push('valueTestStr');
|
allowedElements.push('valueTestStr');
|
||||||
@@ -328,10 +169,10 @@ const createParser = (filterType: FilterType) => {
|
|||||||
const parsers = {
|
const parsers = {
|
||||||
number: createParser('number'),
|
number: createParser('number'),
|
||||||
string: createParser('string'),
|
string: createParser('string'),
|
||||||
datetime: createParser('datetime'),
|
|
||||||
logical: createParser('logical'),
|
logical: createParser('logical'),
|
||||||
eval: createParser('eval'),
|
eval: createParser('eval'),
|
||||||
mongo: mongoParser,
|
mongo: mongoParser,
|
||||||
|
datetime: datetimeParser,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function parseFilter(value: string, filterType: FilterType): Condition {
|
export function parseFilter(value: string, filterType: FilterType): Condition {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
export let command;
|
export let command;
|
||||||
export let component = ToolStripButton;
|
export let component = ToolStripButton;
|
||||||
export let hideDisabled = false;
|
export let hideDisabled = false;
|
||||||
|
export let buttonLabel = null;
|
||||||
|
|
||||||
$: cmd = Object.values($commandsCustomized).find((x: any) => x.id == command) as any;
|
$: cmd = Object.values($commandsCustomized).find((x: any) => x.id == command) as any;
|
||||||
</script>
|
</script>
|
||||||
@@ -29,6 +30,6 @@
|
|||||||
disabled={!cmd.enabled}
|
disabled={!cmd.enabled}
|
||||||
{...$$restProps}
|
{...$$restProps}
|
||||||
>
|
>
|
||||||
{cmd.toolbarName || cmd.name}
|
{buttonLabel || cmd.toolbarName || cmd.name}
|
||||||
</svelte:component>
|
</svelte:component>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -5,7 +5,16 @@
|
|||||||
import ToolStripSplitDropDownButton from './ToolStripSplitDropDownButton.svelte';
|
import ToolStripSplitDropDownButton from './ToolStripSplitDropDownButton.svelte';
|
||||||
|
|
||||||
export let commands;
|
export let commands;
|
||||||
$: menu = _.compact(commands).map(command => ({ command }));
|
export let hideDisabled = false;
|
||||||
|
export let buttonLabel = null;
|
||||||
|
|
||||||
|
$: menu = _.compact(commands).map(command => (_.isString(command) ? { command } : command));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ToolStripCommandButton command={commands[0]} component={ToolStripSplitDropDownButton} {menu} />
|
<ToolStripCommandButton
|
||||||
|
command={commands[0]}
|
||||||
|
component={ToolStripSplitDropDownButton}
|
||||||
|
{menu}
|
||||||
|
{hideDisabled}
|
||||||
|
{buttonLabel}
|
||||||
|
/>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
id: 'dataGrid.refresh',
|
id: 'dataGrid.refresh',
|
||||||
category: 'Data grid',
|
category: 'Data grid',
|
||||||
name: 'Refresh',
|
name: 'Refresh',
|
||||||
keyText: 'F5',
|
keyText: 'F5 | CtrlOrCommand+R',
|
||||||
toolbar: true,
|
toolbar: true,
|
||||||
isRelatedToTab: true,
|
isRelatedToTab: true,
|
||||||
icon: 'icon reload',
|
icon: 'icon reload',
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
id: 'dataGrid.revertRowChanges',
|
id: 'dataGrid.revertRowChanges',
|
||||||
category: 'Data grid',
|
category: 'Data grid',
|
||||||
name: 'Revert row changes',
|
name: 'Revert row changes',
|
||||||
keyText: 'CtrlOrCommand+R',
|
keyText: 'CtrlOrCommand+U',
|
||||||
testEnabled: () => getCurrentDataGrid()?.getGrider()?.containsChanges,
|
testEnabled: () => getCurrentDataGrid()?.getGrider()?.containsChanges,
|
||||||
onClick: () => getCurrentDataGrid().revertRowChanges(),
|
onClick: () => getCurrentDataGrid().revertRowChanges(),
|
||||||
});
|
});
|
||||||
@@ -52,6 +52,16 @@
|
|||||||
onClick: () => getCurrentDataGrid().insertNewRow(),
|
onClick: () => getCurrentDataGrid().insertNewRow(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
registerCommand({
|
||||||
|
id: 'dataGrid.cloneRows',
|
||||||
|
category: 'Data grid',
|
||||||
|
name: 'Clone rows',
|
||||||
|
toolbarName: 'Clone',
|
||||||
|
keyText: 'CtrlOrCommand+Shift+C',
|
||||||
|
testEnabled: () => getCurrentDataGrid()?.getGrider()?.editable,
|
||||||
|
onClick: () => getCurrentDataGrid().cloneRows(),
|
||||||
|
});
|
||||||
|
|
||||||
registerCommand({
|
registerCommand({
|
||||||
id: 'dataGrid.setNull',
|
id: 'dataGrid.setNull',
|
||||||
category: 'Data grid',
|
category: 'Data grid',
|
||||||
@@ -418,16 +428,44 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function insertNewRow() {
|
export async function insertNewRow() {
|
||||||
if (grider.canInsert) {
|
if (!grider.canInsert) return;
|
||||||
const rowIndex = grider.insertRow();
|
const rowIndex = grider.insertRow();
|
||||||
const cell = [rowIndex, (currentCell && currentCell[1]) || 0];
|
const cell = [rowIndex, (currentCell && currentCell[1]) || 0];
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
currentCell = cell;
|
currentCell = cell;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
selectedCells = [cell];
|
selectedCells = [cell];
|
||||||
await tick();
|
await tick();
|
||||||
scrollIntoView(cell);
|
scrollIntoView(cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function cloneRows() {
|
||||||
|
if (!grider.canInsert) return;
|
||||||
|
|
||||||
|
let rowIndex = null;
|
||||||
|
grider.beginUpdate();
|
||||||
|
for (const index of _.sortBy(getSelectedRowIndexes(), x => x)) {
|
||||||
|
if (_.isNumber(index)) {
|
||||||
|
rowIndex = grider.insertRow();
|
||||||
|
|
||||||
|
for (const column of display.columns) {
|
||||||
|
if (column.uniquePath.length > 1) continue;
|
||||||
|
if (column.autoIncrement) continue;
|
||||||
|
|
||||||
|
grider.setCellValue(rowIndex, column.uniqueName, grider.getRowData(index)[column.uniqueName]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
grider.endUpdate();
|
||||||
|
|
||||||
|
if (rowIndex == null) return;
|
||||||
|
const cell = [rowIndex, (currentCell && currentCell[1]) || 0];
|
||||||
|
// @ts-ignore
|
||||||
|
currentCell = cell;
|
||||||
|
// @ts-ignore
|
||||||
|
selectedCells = [cell];
|
||||||
|
await tick();
|
||||||
|
scrollIntoView(cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setFixedValue(value) {
|
export function setFixedValue(value) {
|
||||||
@@ -1171,7 +1209,20 @@
|
|||||||
|
|
||||||
handleCursorMove(event);
|
handleCursorMove(event);
|
||||||
|
|
||||||
if (event.shiftKey && event.keyCode != keycodes.shift && event.keyCode != keycodes.tab) {
|
if (
|
||||||
|
event.shiftKey &&
|
||||||
|
event.keyCode != keycodes.shift &&
|
||||||
|
event.keyCode != keycodes.tab &&
|
||||||
|
event.keyCode != keycodes.ctrl &&
|
||||||
|
event.keyCode != keycodes.leftWindowKey &&
|
||||||
|
event.keyCode != keycodes.rightWindowKey &&
|
||||||
|
!(
|
||||||
|
(event.keyCode >= keycodes.a && event.keyCode <= keycodes.z) ||
|
||||||
|
(event.keyCode >= keycodes.n0 && event.keyCode <= keycodes.n9) ||
|
||||||
|
(event.keyCode >= keycodes.numPad0 && event.keyCode <= keycodes.numPad9) ||
|
||||||
|
event.keyCode == keycodes.dash
|
||||||
|
)
|
||||||
|
) {
|
||||||
selectedCells = getCellRange(shiftDragStartCell || currentCell, currentCell);
|
selectedCells = getCellRange(shiftDragStartCell || currentCell, currentCell);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1432,6 +1483,7 @@
|
|||||||
{ command: 'dataGrid.revertAllChanges', hideDisabled: true },
|
{ command: 'dataGrid.revertAllChanges', hideDisabled: true },
|
||||||
{ command: 'dataGrid.deleteSelectedRows' },
|
{ command: 'dataGrid.deleteSelectedRows' },
|
||||||
{ command: 'dataGrid.insertNewRow' },
|
{ command: 'dataGrid.insertNewRow' },
|
||||||
|
{ command: 'dataGrid.cloneRows' },
|
||||||
{ command: 'dataGrid.setNull' },
|
{ command: 'dataGrid.setNull' },
|
||||||
{ placeTag: 'edit' },
|
{ placeTag: 'edit' },
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
@@ -1498,12 +1550,16 @@
|
|||||||
<div>
|
<div>
|
||||||
<ErrorInfo
|
<ErrorInfo
|
||||||
alignTop
|
alignTop
|
||||||
message="No rows loaded, check filter or add new documents. You could copy documents from ohter collections/tables with Copy advanved/Copy as JSON command."
|
message={grider.editable
|
||||||
|
? 'No rows loaded, check filter or add new documents. You could copy documents from ohter collections/tables with Copy advanved/Copy as JSON command.'
|
||||||
|
: 'No rows loaded'}
|
||||||
/>
|
/>
|
||||||
{#if display.filterCount > 0}
|
{#if display.filterCount > 0}
|
||||||
<FormStyledButton value="Reset filter" on:click={() => display.clearFilters()} />
|
<FormStyledButton value="Reset filter" on:click={() => display.clearFilters()} />
|
||||||
{/if}
|
{/if}
|
||||||
<FormStyledButton value="Add document" on:click={addJsonDocument} />
|
{#if grider.editable}
|
||||||
|
<FormStyledButton value="Add document" on:click={addJsonDocument} />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else if grider.errors && grider.errors.length > 0}
|
{:else if grider.errors && grider.errors.length > 0}
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
id: 'dataForm.refresh',
|
id: 'dataForm.refresh',
|
||||||
category: 'Data form',
|
category: 'Data form',
|
||||||
name: 'Refresh',
|
name: 'Refresh',
|
||||||
keyText: 'F5',
|
keyText: 'F5 | CtrlOrCommand+R',
|
||||||
toolbar: true,
|
toolbar: true,
|
||||||
isRelatedToTab: true,
|
isRelatedToTab: true,
|
||||||
icon: 'icon reload',
|
icon: 'icon reload',
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
id: 'dataForm.revertRowChanges',
|
id: 'dataForm.revertRowChanges',
|
||||||
category: 'Data form',
|
category: 'Data form',
|
||||||
name: 'Revert row changes',
|
name: 'Revert row changes',
|
||||||
keyText: 'CtrlOrCommand+R',
|
keyText: 'CtrlOrCommand+U',
|
||||||
testEnabled: () => getCurrentDataForm()?.getFormer()?.containsChanges,
|
testEnabled: () => getCurrentDataForm()?.getFormer()?.containsChanges,
|
||||||
onClick: () => getCurrentDataForm().getFormer().revertRowChanges(),
|
onClick: () => getCurrentDataForm().getFormer().revertRowChanges(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -168,6 +168,10 @@
|
|||||||
<FormCheckboxField label="Is read only" name="isReadOnly" disabled={isConnected} />
|
<FormCheckboxField label="Is read only" name="isReadOnly" disabled={isConnected} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if driver?.showConnectionField('trustServerCertificate', $values)}
|
||||||
|
<FormCheckboxField label="Trust server certificate" name="trustServerCertificate" disabled={isConnected} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if driver?.showConnectionField('defaultDatabase', $values)}
|
{#if driver?.showConnectionField('defaultDatabase', $values)}
|
||||||
<FormTextField label="Default database" name="defaultDatabase" disabled={isConnected} />
|
<FormTextField label="Default database" name="defaultDatabase" disabled={isConnected} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -91,6 +91,12 @@ ORDER BY
|
|||||||
|
|
||||||
<FormCheckboxField name="dataGrid.thousandsSeparator" label="Use thousands separator for numbers" />
|
<FormCheckboxField name="dataGrid.thousandsSeparator" label="Use thousands separator for numbers" />
|
||||||
|
|
||||||
|
<FormTextField
|
||||||
|
name="dataGrid.defaultAutoRefreshInterval"
|
||||||
|
label="Default grid auto refresh interval in seconds"
|
||||||
|
defaultValue="10"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="heading">Connection</div>
|
<div class="heading">Connection</div>
|
||||||
<FormCheckboxField
|
<FormCheckboxField
|
||||||
name="connection.autoRefresh"
|
name="connection.autoRefresh"
|
||||||
@@ -99,7 +105,7 @@ ORDER BY
|
|||||||
/>
|
/>
|
||||||
<FormTextField
|
<FormTextField
|
||||||
name="connection.autoRefreshInterval"
|
name="connection.autoRefreshInterval"
|
||||||
label="Interval between automatic refreshes in seconds"
|
label="Interval between automatic DB structure reloads in seconds"
|
||||||
defaultValue="30"
|
defaultValue="30"
|
||||||
disabled={values['connection.autoRefresh'] === false}
|
disabled={values['connection.autoRefresh'] === false}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -29,7 +29,11 @@ export function writableWithStorage<T>(defaultValue: T, storageName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function writableSettingsValue<T>(defaultValue: T, storageName) {
|
export function writableSettingsValue<T>(defaultValue: T, storageName) {
|
||||||
const res = derived(useSettings(), $settings => ($settings || {})[storageName] ?? defaultValue);
|
const res = derived(useSettings(), $settings => {
|
||||||
|
const obj = $settings || {};
|
||||||
|
// console.log('GET SETTINGS', $settings, storageName, obj[storageName]);
|
||||||
|
return obj[storageName] ?? defaultValue;
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
...res,
|
...res,
|
||||||
set: value => apiCall('config/update-settings', { [storageName]: value }),
|
set: value => apiCall('config/update-settings', { [storageName]: value }),
|
||||||
|
|||||||
@@ -167,7 +167,7 @@
|
|||||||
|
|
||||||
$: isConnected = $openedConnections.includes($values._id) || $openedSingleDatabaseConnections.includes($values._id);
|
$: isConnected = $openedConnections.includes($values._id) || $openedSingleDatabaseConnections.includes($values._id);
|
||||||
|
|
||||||
$: console.log('CONN VALUES', $values);
|
// $: console.log('CONN VALUES', $values);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FormProviderCore template={FormFieldTemplateLarge} {values}>
|
<FormProviderCore template={FormFieldTemplateLarge} {values}>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
const getCurrentEditor = () => getActiveComponent('TableDataTab');
|
const getCurrentEditor = () => getActiveComponent('TableDataTab');
|
||||||
|
const INTERVALS = [5, 10, 15, 13, 60];
|
||||||
|
|
||||||
registerCommand({
|
registerCommand({
|
||||||
id: 'tableData.save',
|
id: 'tableData.save',
|
||||||
@@ -14,6 +15,46 @@
|
|||||||
onClick: () => getCurrentEditor().save(),
|
onClick: () => getCurrentEditor().save(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
registerCommand({
|
||||||
|
id: 'tableData.setAutoRefresh.1',
|
||||||
|
category: 'Data grid',
|
||||||
|
name: 'Refresh every 1 second',
|
||||||
|
isRelatedToTab: true,
|
||||||
|
testEnabled: () => !!getCurrentEditor(),
|
||||||
|
onClick: () => getCurrentEditor().setAutoRefresh(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const seconds of INTERVALS) {
|
||||||
|
registerCommand({
|
||||||
|
id: `tableData.setAutoRefresh.${seconds}`,
|
||||||
|
category: 'Data grid',
|
||||||
|
name: `Refresh every ${seconds} seconds`,
|
||||||
|
isRelatedToTab: true,
|
||||||
|
testEnabled: () => !!getCurrentEditor(),
|
||||||
|
onClick: () => getCurrentEditor().setAutoRefresh(seconds),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
registerCommand({
|
||||||
|
id: 'tableData.stopAutoRefresh',
|
||||||
|
category: 'Data grid',
|
||||||
|
name: 'Stop auto refresh',
|
||||||
|
isRelatedToTab: true,
|
||||||
|
keyText: 'CtrlOrCommand+Shift+R',
|
||||||
|
testEnabled: () => getCurrentEditor()?.isAutoRefresh() === true,
|
||||||
|
onClick: () => getCurrentEditor().stopAutoRefresh(null),
|
||||||
|
});
|
||||||
|
|
||||||
|
registerCommand({
|
||||||
|
id: 'tableData.startAutoRefresh',
|
||||||
|
category: 'Data grid',
|
||||||
|
name: 'Start auto refresh',
|
||||||
|
isRelatedToTab: true,
|
||||||
|
keyText: 'CtrlOrCommand+Shift+R',
|
||||||
|
testEnabled: () => getCurrentEditor()?.isAutoRefresh() === false,
|
||||||
|
onClick: () => getCurrentEditor().startAutoRefresh(),
|
||||||
|
});
|
||||||
|
|
||||||
export const matchingProps = ['conid', 'database', 'schemaName', 'pureName'];
|
export const matchingProps = ['conid', 'database', 'schemaName', 'pureName'];
|
||||||
export const allowAddToFavorites = props => true;
|
export const allowAddToFavorites = props => true;
|
||||||
</script>
|
</script>
|
||||||
@@ -50,12 +91,14 @@
|
|||||||
import { showSnackbarSuccess } from '../utility/snackbar';
|
import { showSnackbarSuccess } from '../utility/snackbar';
|
||||||
import StatusBarTabItem from '../widgets/StatusBarTabItem.svelte';
|
import StatusBarTabItem from '../widgets/StatusBarTabItem.svelte';
|
||||||
import openNewTab from '../utility/openNewTab';
|
import openNewTab from '../utility/openNewTab';
|
||||||
import { setContext } from 'svelte';
|
import { onDestroy, setContext } from 'svelte';
|
||||||
import { apiCall } from '../utility/api';
|
import { apiCall } from '../utility/api';
|
||||||
import { getLocalStorage, setLocalStorage } from '../utility/storageCache';
|
import { getLocalStorage, setLocalStorage } from '../utility/storageCache';
|
||||||
import ToolStripContainer from '../buttons/ToolStripContainer.svelte';
|
import ToolStripContainer from '../buttons/ToolStripContainer.svelte';
|
||||||
import ToolStripCommandButton from '../buttons/ToolStripCommandButton.svelte';
|
import ToolStripCommandButton from '../buttons/ToolStripCommandButton.svelte';
|
||||||
import ToolStripExportButton, { createQuickExportHandlerRef } from '../buttons/ToolStripExportButton.svelte';
|
import ToolStripExportButton, { createQuickExportHandlerRef } from '../buttons/ToolStripExportButton.svelte';
|
||||||
|
import ToolStripCommandSplitButton from '../buttons/ToolStripCommandSplitButton.svelte';
|
||||||
|
import { getIntSettingsValue } from '../settings/settingsTools';
|
||||||
|
|
||||||
export let tabid;
|
export let tabid;
|
||||||
export let conid;
|
export let conid;
|
||||||
@@ -68,6 +111,11 @@
|
|||||||
const config = useGridConfig(tabid);
|
const config = useGridConfig(tabid);
|
||||||
const cache = writable(createGridCache());
|
const cache = writable(createGridCache());
|
||||||
const dbinfo = useDatabaseInfo({ conid, database });
|
const dbinfo = useDatabaseInfo({ conid, database });
|
||||||
|
|
||||||
|
let autoRefreshInterval = getIntSettingsValue('dataGrid.defaultAutoRefreshInterval', 10, 1, 3600);
|
||||||
|
let autoRefreshStarted = false;
|
||||||
|
let autoRefreshTimer = null;
|
||||||
|
|
||||||
$: connection = useConnectionInfo({ conid });
|
$: connection = useConnectionInfo({ conid });
|
||||||
|
|
||||||
const [changeSetStore, dispatchChangeSet] = createUndoReducer(createChangeSet());
|
const [changeSetStore, dispatchChangeSet] = createUndoReducer(createChangeSet());
|
||||||
@@ -106,6 +154,38 @@
|
|||||||
return changeSetContainsChanges($changeSetStore?.value);
|
return changeSetContainsChanges($changeSetStore?.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setAutoRefresh(interval) {
|
||||||
|
autoRefreshInterval = interval;
|
||||||
|
startAutoRefresh();
|
||||||
|
invalidateCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isAutoRefresh() {
|
||||||
|
return autoRefreshStarted;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function startAutoRefresh() {
|
||||||
|
closeRefreshTimer();
|
||||||
|
autoRefreshTimer = setInterval(() => {
|
||||||
|
cache.update(reloadDataCacheFunc);
|
||||||
|
}, autoRefreshInterval * 1000);
|
||||||
|
autoRefreshStarted = true;
|
||||||
|
invalidateCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stopAutoRefresh() {
|
||||||
|
closeRefreshTimer();
|
||||||
|
autoRefreshStarted = false;
|
||||||
|
invalidateCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeRefreshTimer() {
|
||||||
|
if (autoRefreshTimer) {
|
||||||
|
clearInterval(autoRefreshTimer);
|
||||||
|
autoRefreshTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
$changeSetStore;
|
$changeSetStore;
|
||||||
invalidateCommands();
|
invalidateCommands();
|
||||||
@@ -117,7 +197,21 @@
|
|||||||
setContext('collapsedLeftColumnStore', collapsedLeftColumnStore);
|
setContext('collapsedLeftColumnStore', collapsedLeftColumnStore);
|
||||||
$: setLocalStorage('dataGrid_collapsedLeftColumn', $collapsedLeftColumnStore);
|
$: setLocalStorage('dataGrid_collapsedLeftColumn', $collapsedLeftColumnStore);
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
closeRefreshTimer();
|
||||||
|
});
|
||||||
|
|
||||||
const quickExportHandlerRef = createQuickExportHandlerRef();
|
const quickExportHandlerRef = createQuickExportHandlerRef();
|
||||||
|
|
||||||
|
function createAutoRefreshMenu() {
|
||||||
|
return [
|
||||||
|
{ divider: true },
|
||||||
|
{ command: 'tableData.stopAutoRefresh', hideDisabled: true },
|
||||||
|
{ command: 'tableData.startAutoRefresh', hideDisabled: true },
|
||||||
|
'tableData.setAutoRefresh.1',
|
||||||
|
...INTERVALS.map(seconds => ({ command: `tableData.setAutoRefresh.${seconds}`, text: `...${seconds} seconds` })),
|
||||||
|
];
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ToolStripContainer>
|
<ToolStripContainer>
|
||||||
@@ -134,8 +228,19 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<svelte:fragment slot="toolstrip">
|
<svelte:fragment slot="toolstrip">
|
||||||
<ToolStripCommandButton command="dataGrid.refresh" hideDisabled />
|
<ToolStripCommandSplitButton
|
||||||
<ToolStripCommandButton command="dataForm.refresh" hideDisabled />
|
buttonLabel={autoRefreshStarted ? `Refresh (every ${autoRefreshInterval}s)` : null}
|
||||||
|
commands={['dataGrid.refresh', ...createAutoRefreshMenu()]}
|
||||||
|
hideDisabled
|
||||||
|
/>
|
||||||
|
<ToolStripCommandSplitButton
|
||||||
|
buttonLabel={autoRefreshStarted ? `Refresh (every ${autoRefreshInterval}s)` : null}
|
||||||
|
commands={['dataForm.refresh', ...createAutoRefreshMenu()]}
|
||||||
|
hideDisabled
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- <ToolStripCommandButton command="dataGrid.refresh" hideDisabled />
|
||||||
|
<ToolStripCommandButton command="dataForm.refresh" hideDisabled /> -->
|
||||||
<ToolStripCommandButton command="tableData.save" />
|
<ToolStripCommandButton command="tableData.save" />
|
||||||
<ToolStripCommandButton command="dataGrid.insertNewRow" hideDisabled />
|
<ToolStripCommandButton command="dataGrid.insertNewRow" hideDisabled />
|
||||||
<ToolStripCommandButton command="dataGrid.deleteSelectedRows" hideDisabled />
|
<ToolStripCommandButton command="dataGrid.deleteSelectedRows" hideDisabled />
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ export default {
|
|||||||
y: 89,
|
y: 89,
|
||||||
z: 90,
|
z: 90,
|
||||||
leftWindowKey: 91,
|
leftWindowKey: 91,
|
||||||
rightWindowKey: 92,
|
rightWindowKey: 93,
|
||||||
selectKey: 93,
|
// selectKey: 93,
|
||||||
numPad0: 96,
|
numPad0: 96,
|
||||||
numPad1: 97,
|
numPad1: 97,
|
||||||
numPad2: 98,
|
numPad2: 98,
|
||||||
|
|||||||
@@ -189,25 +189,29 @@ async function getCore(loader, args) {
|
|||||||
function useCore(loader, args) {
|
function useCore(loader, args) {
|
||||||
const { url, params, reloadTrigger, transform, onLoaded } = loader(args);
|
const { url, params, reloadTrigger, transform, onLoaded } = loader(args);
|
||||||
const cacheKey = stableStringify({ url, ...params });
|
const cacheKey = stableStringify({ url, ...params });
|
||||||
let closed = false;
|
let openedCount = 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe: onChange => {
|
subscribe: onChange => {
|
||||||
async function handleReload() {
|
async function handleReload() {
|
||||||
const res = await getCore(loader, args);
|
const res = await getCore(loader, args);
|
||||||
if (!closed) {
|
if (openedCount > 0) {
|
||||||
onChange(res);
|
onChange(res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
openedCount += 1;
|
||||||
handleReload();
|
handleReload();
|
||||||
|
|
||||||
if (reloadTrigger) {
|
if (reloadTrigger) {
|
||||||
subscribeCacheChange(reloadTrigger, cacheKey, handleReload);
|
subscribeCacheChange(reloadTrigger, cacheKey, handleReload);
|
||||||
return () => {
|
return () => {
|
||||||
closed = true;
|
openedCount -= 1;
|
||||||
unsubscribeCacheChange(reloadTrigger, cacheKey, handleReload);
|
unsubscribeCacheChange(reloadTrigger, cacheKey, handleReload);
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
return () => {
|
||||||
|
openedCount -= 1;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,19 +5,17 @@ const driverBase = require('../frontend/driver');
|
|||||||
const Analyser = require('./Analyser');
|
const Analyser = require('./Analyser');
|
||||||
const MongoClient = require('mongodb').MongoClient;
|
const MongoClient = require('mongodb').MongoClient;
|
||||||
const ObjectId = require('mongodb').ObjectId;
|
const ObjectId = require('mongodb').ObjectId;
|
||||||
const Cursor = require('mongodb').Cursor;
|
const AbstractCursor = require('mongodb').AbstractCursor;
|
||||||
const createBulkInsertStream = require('./createBulkInsertStream');
|
const createBulkInsertStream = require('./createBulkInsertStream');
|
||||||
|
|
||||||
function transformMongoData(row) {
|
function transformMongoData(row) {
|
||||||
return _.mapValues(row, (v) => (v && v.constructor == ObjectId ? { $oid: v.toString() } : v));
|
return _.mapValues(row, (v) => (v && v.constructor == ObjectId ? { $oid: v.toString() } : v));
|
||||||
}
|
}
|
||||||
|
|
||||||
function readCursor(cursor, options) {
|
async function readCursor(cursor, options) {
|
||||||
return new Promise((resolve) => {
|
options.recordset({ __isDynamicStructure: true });
|
||||||
options.recordset({ __isDynamicStructure: true });
|
await cursor.forEach((row) => {
|
||||||
|
options.row(transformMongoData(row));
|
||||||
cursor.on('data', (data) => options.row(transformMongoData(data)));
|
|
||||||
cursor.on('end', () => resolve());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +116,7 @@ const driver = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exprValue instanceof Cursor) {
|
if (exprValue instanceof AbstractCursor) {
|
||||||
await readCursor(exprValue, options);
|
await readCursor(exprValue, options);
|
||||||
} else if (isPromise(exprValue)) {
|
} else if (isPromise(exprValue)) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ function extractTediousColumns(columns, addDriverNativeColumn = false) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function tediousConnect({ server, port, user, password, database, ssl }) {
|
async function tediousConnect({ server, port, user, password, database, ssl, trustServerCertificate }) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const connectionOptions = {
|
const connectionOptions = {
|
||||||
encrypt: !!ssl,
|
encrypt: !!ssl,
|
||||||
@@ -32,6 +32,7 @@ async function tediousConnect({ server, port, user, password, database, ssl }) {
|
|||||||
validateBulkLoadParameters: false,
|
validateBulkLoadParameters: false,
|
||||||
requestTimeout: 1000 * 3600,
|
requestTimeout: 1000 * 3600,
|
||||||
port: port ? parseInt(port) : undefined,
|
port: port ? parseInt(port) : undefined,
|
||||||
|
trustServerCertificate: !!trustServerCertificate,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (database) {
|
if (database) {
|
||||||
|
|||||||
@@ -126,7 +126,8 @@ const driver = {
|
|||||||
showConnectionField: (field, values) =>
|
showConnectionField: (field, values) =>
|
||||||
['authType', 'server', 'port', 'user', 'password', 'defaultDatabase', 'singleDatabase', 'isReadOnly'].includes(
|
['authType', 'server', 'port', 'user', 'password', 'defaultDatabase', 'singleDatabase', 'isReadOnly'].includes(
|
||||||
field
|
field
|
||||||
),
|
) ||
|
||||||
|
(field == 'trustServerCertificate' && values.authType != 'sql' && values.authType != 'sspi'),
|
||||||
getQuerySplitterOptions: () => mssqlSplitterOptions,
|
getQuerySplitterOptions: () => mssqlSplitterOptions,
|
||||||
|
|
||||||
engine: 'mssql@dbgate-plugin-mssql',
|
engine: 'mssql@dbgate-plugin-mssql',
|
||||||
|
|||||||
65
yarn.lock
65
yarn.lock
@@ -1941,7 +1941,14 @@ asn1.js@^4.0.0:
|
|||||||
inherits "^2.0.1"
|
inherits "^2.0.1"
|
||||||
minimalistic-assert "^1.0.0"
|
minimalistic-assert "^1.0.0"
|
||||||
|
|
||||||
asn1@~0.2.0, asn1@~0.2.3:
|
asn1@^0.2.4:
|
||||||
|
version "0.2.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d"
|
||||||
|
integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==
|
||||||
|
dependencies:
|
||||||
|
safer-buffer "~2.1.0"
|
||||||
|
|
||||||
|
asn1@~0.2.3:
|
||||||
version "0.2.4"
|
version "0.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
|
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
|
||||||
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
|
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
|
||||||
@@ -2471,6 +2478,11 @@ bufferutil@^4.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
node-gyp-build "~3.7.0"
|
node-gyp-build "~3.7.0"
|
||||||
|
|
||||||
|
buildcheck@0.0.3:
|
||||||
|
version "0.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/buildcheck/-/buildcheck-0.0.3.tgz#70451897a95d80f7807e68fc412eb2e7e35ff4d5"
|
||||||
|
integrity sha512-pziaA+p/wdVImfcbsZLNF32EiWyujlQLwolMqUQE8xpKNOH7KmZQaY8sXN7DGOEzPAElo9QTaeNRfGnf3iOJbA==
|
||||||
|
|
||||||
builtin-modules@^3.1.0:
|
builtin-modules@^3.1.0:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887"
|
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887"
|
||||||
@@ -3090,6 +3102,14 @@ cors@^2.8.5:
|
|||||||
object-assign "^4"
|
object-assign "^4"
|
||||||
vary "^1"
|
vary "^1"
|
||||||
|
|
||||||
|
cpu-features@~0.0.4:
|
||||||
|
version "0.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.4.tgz#0023475bb4f4c525869c162e4108099e35bf19d8"
|
||||||
|
integrity sha512-fKiZ/zp1mUwQbnzb9IghXtHtDoTMtNeb8oYGx6kX2SYfhnG0HNdBEBIzB9b5KlXu5DQPhfy3mInbBxFcgwAr3A==
|
||||||
|
dependencies:
|
||||||
|
buildcheck "0.0.3"
|
||||||
|
nan "^2.15.0"
|
||||||
|
|
||||||
crc-32@~1.2.0:
|
crc-32@~1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.0.tgz#cb2db6e29b88508e32d9dd0ec1693e7b41a18208"
|
resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.0.tgz#cb2db6e29b88508e32d9dd0ec1693e7b41a18208"
|
||||||
@@ -3353,6 +3373,13 @@ debug@^4.3.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms "2.1.2"
|
ms "2.1.2"
|
||||||
|
|
||||||
|
debug@^4.3.4:
|
||||||
|
version "4.3.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||||
|
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||||
|
dependencies:
|
||||||
|
ms "2.1.2"
|
||||||
|
|
||||||
decamelize-keys@^1.1.0:
|
decamelize-keys@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9"
|
resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9"
|
||||||
@@ -7794,6 +7821,11 @@ nan@^2.15.0:
|
|||||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee"
|
resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee"
|
||||||
integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==
|
integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==
|
||||||
|
|
||||||
|
nan@^2.16.0:
|
||||||
|
version "2.16.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/nan/-/nan-2.16.0.tgz#664f43e45460fb98faf00edca0bb0d7b8dce7916"
|
||||||
|
integrity sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==
|
||||||
|
|
||||||
nanomatch@^1.2.9:
|
nanomatch@^1.2.9:
|
||||||
version "1.2.13"
|
version "1.2.13"
|
||||||
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
|
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
|
||||||
@@ -7978,14 +8010,6 @@ node-releases@^1.1.71:
|
|||||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe"
|
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe"
|
||||||
integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==
|
integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==
|
||||||
|
|
||||||
node-ssh-forward@^0.7.2:
|
|
||||||
version "0.7.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/node-ssh-forward/-/node-ssh-forward-0.7.2.tgz#a5103ba9ae0e044156b0568a5084304e7d5b4a2a"
|
|
||||||
integrity sha512-dQGhwT9emJJ0PymZGXdwHue18oc+bnAeqSGUCi4+YufGLaTSfA4Ft04T97WGkVxmBUySy/O6lvKIfpJM0a8lgA==
|
|
||||||
dependencies:
|
|
||||||
debug "^4.1.1"
|
|
||||||
ssh2 "^0.8.9"
|
|
||||||
|
|
||||||
node-xml-stream-parser@^1.0.12:
|
node-xml-stream-parser@^1.0.12:
|
||||||
version "1.0.12"
|
version "1.0.12"
|
||||||
resolved "https://registry.yarnpkg.com/node-xml-stream-parser/-/node-xml-stream-parser-1.0.12.tgz#2d97cc91147bbcdffa0a89cf7dfcc129db2aa789"
|
resolved "https://registry.yarnpkg.com/node-xml-stream-parser/-/node-xml-stream-parser-1.0.12.tgz#2d97cc91147bbcdffa0a89cf7dfcc129db2aa789"
|
||||||
@@ -10141,21 +10165,16 @@ ssf@~0.11.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
frac "~1.1.2"
|
frac "~1.1.2"
|
||||||
|
|
||||||
ssh2-streams@~0.4.10:
|
ssh2@^1.11.0:
|
||||||
version "0.4.10"
|
version "1.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/ssh2-streams/-/ssh2-streams-0.4.10.tgz#48ef7e8a0e39d8f2921c30521d56dacb31d23a34"
|
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.11.0.tgz#ce60186216971e12f6deb553dcf82322498fe2e4"
|
||||||
integrity sha512-8pnlMjvnIZJvmTzUIIA5nT4jr2ZWNNVHwyXfMGdRJbug9TpI3kd99ffglgfSWqujVv/0gxwMsDn9j9RVst8yhQ==
|
integrity sha512-nfg0wZWGSsfUe/IBJkXVll3PEZ//YH2guww+mP88gTpuSU4FtZN7zu9JoeTGOyCNx2dTDtT9fOpWwlzyj4uOOw==
|
||||||
dependencies:
|
dependencies:
|
||||||
asn1 "~0.2.0"
|
asn1 "^0.2.4"
|
||||||
bcrypt-pbkdf "^1.0.2"
|
bcrypt-pbkdf "^1.0.2"
|
||||||
streamsearch "~0.1.2"
|
optionalDependencies:
|
||||||
|
cpu-features "~0.0.4"
|
||||||
ssh2@^0.8.9:
|
nan "^2.16.0"
|
||||||
version "0.8.9"
|
|
||||||
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.8.9.tgz#54da3a6c4ba3daf0d8477a538a481326091815f3"
|
|
||||||
integrity sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw==
|
|
||||||
dependencies:
|
|
||||||
ssh2-streams "~0.4.10"
|
|
||||||
|
|
||||||
sshpk@^1.7.0:
|
sshpk@^1.7.0:
|
||||||
version "1.16.1"
|
version "1.16.1"
|
||||||
@@ -10265,7 +10284,7 @@ stream-transform@^2.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mixme "^0.5.0"
|
mixme "^0.5.0"
|
||||||
|
|
||||||
streamsearch@0.1.2, streamsearch@~0.1.2:
|
streamsearch@0.1.2:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
|
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
|
||||||
integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
|
integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
|
||||||
|
|||||||
Reference in New Issue
Block a user