mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-19 01:26:01 +00:00
Merge branch 'master' into sqlite
This commit is contained in:
47
.github/workflows/build-docker-beta.yaml
vendored
Normal file
47
.github/workflows/build-docker-beta.yaml
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
name: Docker image
|
||||||
|
|
||||||
|
# on: [push]
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-18.04]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Context
|
||||||
|
env:
|
||||||
|
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||||
|
run: echo "$GITHUB_CONTEXT"
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
- name: Use Node.js 10.x
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 10.x
|
||||||
|
- name: yarn install
|
||||||
|
run: |
|
||||||
|
yarn install
|
||||||
|
- name: setCurrentVersion
|
||||||
|
run: |
|
||||||
|
yarn setCurrentVersion
|
||||||
|
- name: Prepare docker image
|
||||||
|
run: |
|
||||||
|
yarn run prepare:docker
|
||||||
|
- name: Build docker image
|
||||||
|
run: |
|
||||||
|
docker build ./docker -t dbgate
|
||||||
|
- name: Push docker image
|
||||||
|
run: |
|
||||||
|
docker tag dbgate dbgate/dbgate:beta
|
||||||
|
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
docker push dbgate/dbgate:beta
|
||||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,5 +1,31 @@
|
|||||||
# ChangeLog
|
# ChangeLog
|
||||||
|
|
||||||
|
### 4.1.11
|
||||||
|
- FIX: fixed processing postgre query containing $$
|
||||||
|
- FIX: fixed postgre analysing procedures & functions
|
||||||
|
- FIX: patched svelte crash #105
|
||||||
|
- ADDED: ability to disbale background DB model updates
|
||||||
|
- ADDED: Duplicate connection
|
||||||
|
- ADDED: Duplicate tab
|
||||||
|
- FIX: SSH tunnel connection using keyfile auth #106
|
||||||
|
- FIX: All tables button fix in export #109
|
||||||
|
- CHANGED: Add to favorites moved from toolbar to tab context menu
|
||||||
|
- CHANGED: Toolbar design - current tab related commands are delimited
|
||||||
|
|
||||||
|
### 4.1.10
|
||||||
|
- ADDED: Default database option in connectin settings #96 #92
|
||||||
|
- FIX: Bundle size optimalization for Windows #97
|
||||||
|
- FIX: Popup menu placement on smaller displays #94
|
||||||
|
- ADDED: Browse table data with SQL Server 2008 #93
|
||||||
|
- FIX: Prevented malicious origins / DNS rebinding #91
|
||||||
|
- ADDED: Handle JSON fields in data editor (eg. jsonb field in Postgres) #90
|
||||||
|
- FIX: Fixed crash on Windows with Hyper-V #86
|
||||||
|
- ADDED: Show database server version in status bar
|
||||||
|
- ADDED: Show detailed info about error, when connect to database fails
|
||||||
|
- ADDED: Portable ZIP distribution for Windows #84
|
||||||
|
### 4.1.9
|
||||||
|
- FIX: Incorrect row count info in query result #83
|
||||||
|
|
||||||
### 4.1.1
|
### 4.1.1
|
||||||
- CHANGED: Default plugins are now part of installation
|
- CHANGED: Default plugins are now part of installation
|
||||||
### 4.1.0
|
### 4.1.0
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "4.1.10-beta.5",
|
"version": "4.1.11",
|
||||||
"name": "dbgate-all",
|
"name": "dbgate-all",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*",
|
"packages/*",
|
||||||
|
|||||||
@@ -9,6 +9,16 @@ const currentVersion = require('../currentVersion');
|
|||||||
const platformInfo = require('../utility/platformInfo');
|
const platformInfo = require('../utility/platformInfo');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
settingsValue: {},
|
||||||
|
|
||||||
|
async _init() {
|
||||||
|
try {
|
||||||
|
this.settingsValue = JSON.parse(await fs.readFile(path.join(datadir(), 'settings.json'), { encoding: 'utf-8' }));
|
||||||
|
} catch (err) {
|
||||||
|
this.settingsValue = {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
get_meta: 'get',
|
get_meta: 'get',
|
||||||
async get() {
|
async get() {
|
||||||
// const toolbarButtons = process.env.TOOLBAR;
|
// const toolbarButtons = process.env.TOOLBAR;
|
||||||
@@ -47,23 +57,19 @@ module.exports = {
|
|||||||
|
|
||||||
getSettings_meta: 'get',
|
getSettings_meta: 'get',
|
||||||
async getSettings() {
|
async getSettings() {
|
||||||
try {
|
return this.settingsValue;
|
||||||
return JSON.parse(await fs.readFile(path.join(datadir(), 'settings.json'), { encoding: 'utf-8' }));
|
|
||||||
} catch (err) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updateSettings_meta: 'post',
|
updateSettings_meta: 'post',
|
||||||
async updateSettings(values) {
|
async updateSettings(values) {
|
||||||
if (!hasPermission(`settings/change`)) return false;
|
if (!hasPermission(`settings/change`)) return false;
|
||||||
const oldSettings = await this.getSettings();
|
|
||||||
try {
|
try {
|
||||||
const updated = {
|
const updated = {
|
||||||
...oldSettings,
|
...this.settingsValue,
|
||||||
...values,
|
...values,
|
||||||
};
|
};
|
||||||
await fs.writeFile(path.join(datadir(), 'settings.json'), JSON.stringify(updated, undefined, 2));
|
await fs.writeFile(path.join(datadir(), 'settings.json'), JSON.stringify(updated, undefined, 2));
|
||||||
|
this.settingsValue = updated;
|
||||||
socket.emitChanged(`settings-changed`);
|
socket.emitChanged(`settings-changed`);
|
||||||
return updated;
|
return updated;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const socket = require('../utility/socket');
|
|||||||
const { fork } = require('child_process');
|
const { fork } = require('child_process');
|
||||||
const { DatabaseAnalyser } = require('dbgate-tools');
|
const { DatabaseAnalyser } = require('dbgate-tools');
|
||||||
const { handleProcessCommunication } = require('../utility/processComm');
|
const { handleProcessCommunication } = require('../utility/processComm');
|
||||||
|
const config = require('./config');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
/** @type {import('dbgate-types').OpenedDatabaseConnection[]} */
|
/** @type {import('dbgate-types').OpenedDatabaseConnection[]} */
|
||||||
@@ -79,6 +80,7 @@ module.exports = {
|
|||||||
msgtype: 'connect',
|
msgtype: 'connect',
|
||||||
connection: { ...connection, database },
|
connection: { ...connection, database },
|
||||||
structure: lastClosed ? lastClosed.structure : null,
|
structure: lastClosed ? lastClosed.structure : null,
|
||||||
|
globalSettings: config.settingsValue
|
||||||
});
|
});
|
||||||
return newOpened;
|
return newOpened;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ module.exports = {
|
|||||||
const res = [];
|
const res = [];
|
||||||
for (const packageName of _.union(files1, files2)) {
|
for (const packageName of _.union(files1, files2)) {
|
||||||
if (!/^dbgate-plugin-.*$/.test(packageName)) continue;
|
if (!/^dbgate-plugin-.*$/.test(packageName)) continue;
|
||||||
|
try {
|
||||||
const isPackaged = files1.includes(packageName);
|
const isPackaged = files1.includes(packageName);
|
||||||
const manifest = await fs
|
const manifest = await fs
|
||||||
.readFile(path.join(isPackaged ? packagedPluginsDir() : pluginsdir(), packageName, 'package.json'), {
|
.readFile(path.join(isPackaged ? packagedPluginsDir() : pluginsdir(), packageName, 'package.json'), {
|
||||||
@@ -77,6 +78,9 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
manifest.isPackaged = isPackaged;
|
manifest.isPackaged = isPackaged;
|
||||||
res.push(manifest);
|
res.push(manifest);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`Skipped plugin ${packageName}, error:`, err.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const _ = require('lodash');
|
|||||||
const AsyncLock = require('async-lock');
|
const AsyncLock = require('async-lock');
|
||||||
const { handleProcessCommunication } = require('../utility/processComm');
|
const { handleProcessCommunication } = require('../utility/processComm');
|
||||||
const lock = new AsyncLock();
|
const lock = new AsyncLock();
|
||||||
|
const config = require('./config');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
opened: [],
|
opened: [],
|
||||||
@@ -65,7 +66,7 @@ module.exports = {
|
|||||||
if (newOpened.disconnected) return;
|
if (newOpened.disconnected) return;
|
||||||
this.close(conid, false);
|
this.close(conid, false);
|
||||||
});
|
});
|
||||||
subprocess.send({ msgtype: 'connect', ...connection });
|
subprocess.send({ msgtype: 'connect', ...connection, globalSettings: config.settingsValue });
|
||||||
return newOpened;
|
return newOpened;
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ const scheduler = require('./controllers/scheduler');
|
|||||||
const { rundir } = require('./utility/directories');
|
const { rundir } = require('./utility/directories');
|
||||||
const platformInfo = require('./utility/platformInfo');
|
const platformInfo = require('./utility/platformInfo');
|
||||||
const processArgs = require('./utility/processArgs');
|
const processArgs = require('./utility/processArgs');
|
||||||
|
const timingSafeCheckToken = require('./utility/timingSafeCheckToken');
|
||||||
|
|
||||||
let authorization = null;
|
let authorization = null;
|
||||||
let checkLocalhostOrigin = null;
|
let checkLocalhostOrigin = null;
|
||||||
@@ -56,7 +57,7 @@ function start() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.use(function (req, res, next) {
|
app.use(function (req, res, next) {
|
||||||
if (authorization && req.headers.authorization != authorization) {
|
if (authorization && !timingSafeCheckToken(req.headers.authorization, authorization)) {
|
||||||
return res.status(403).json({ error: 'Not authorized!' });
|
return res.status(403).json({ error: 'Not authorized!' });
|
||||||
}
|
}
|
||||||
if (checkLocalhostOrigin) {
|
if (checkLocalhostOrigin) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const stableStringify = require('json-stable-stringify');
|
const stableStringify = require('json-stable-stringify');
|
||||||
const childProcessChecker = require('../utility/childProcessChecker');
|
const childProcessChecker = require('../utility/childProcessChecker');
|
||||||
|
const { extractBoolSettingsValue, extractIntSettingsValue } = require('dbgate-tools');
|
||||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||||
const connectUtility = require('../utility/connectUtility');
|
const connectUtility = require('../utility/connectUtility');
|
||||||
const { handleProcessCommunication } = require('../utility/processComm');
|
const { handleProcessCommunication } = require('../utility/processComm');
|
||||||
@@ -29,6 +30,7 @@ async function checkedAsyncCall(promise) {
|
|||||||
|
|
||||||
async function handleFullRefresh() {
|
async function handleFullRefresh() {
|
||||||
const driver = requireEngineDriver(storedConnection);
|
const driver = requireEngineDriver(storedConnection);
|
||||||
|
setStatusName('loadStructure');
|
||||||
analysedStructure = await checkedAsyncCall(driver.analyseFull(systemConnection));
|
analysedStructure = await checkedAsyncCall(driver.analyseFull(systemConnection));
|
||||||
process.send({ msgtype: 'structure', structure: analysedStructure });
|
process.send({ msgtype: 'structure', structure: analysedStructure });
|
||||||
setStatusName('ok');
|
setStatusName('ok');
|
||||||
@@ -36,6 +38,7 @@ async function handleFullRefresh() {
|
|||||||
|
|
||||||
async function handleIncrementalRefresh() {
|
async function handleIncrementalRefresh() {
|
||||||
const driver = requireEngineDriver(storedConnection);
|
const driver = requireEngineDriver(storedConnection);
|
||||||
|
setStatusName('checkStructure');
|
||||||
const newStructure = await checkedAsyncCall(driver.analyseIncremental(systemConnection, analysedStructure));
|
const newStructure = await checkedAsyncCall(driver.analyseIncremental(systemConnection, analysedStructure));
|
||||||
if (newStructure != null) {
|
if (newStructure != null) {
|
||||||
analysedStructure = newStructure;
|
analysedStructure = newStructure;
|
||||||
@@ -62,7 +65,7 @@ async function readVersion() {
|
|||||||
process.send({ msgtype: 'version', version });
|
process.send({ msgtype: 'version', version });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleConnect({ connection, structure }) {
|
async function handleConnect({ connection, structure, globalSettings }) {
|
||||||
storedConnection = connection;
|
storedConnection = connection;
|
||||||
lastPing = new Date().getTime();
|
lastPing = new Date().getTime();
|
||||||
|
|
||||||
@@ -76,7 +79,14 @@ async function handleConnect({ connection, structure }) {
|
|||||||
} else {
|
} else {
|
||||||
handleFullRefresh();
|
handleFullRefresh();
|
||||||
}
|
}
|
||||||
setInterval(handleIncrementalRefresh, 30 * 1000);
|
|
||||||
|
if (extractBoolSettingsValue(globalSettings, 'connection.autoRefresh', true)) {
|
||||||
|
setInterval(
|
||||||
|
handleIncrementalRefresh,
|
||||||
|
extractIntSettingsValue(globalSettings, 'connection.autoRefreshInterval', 30, 3, 3600) * 1000
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for (const [resolve] of afterConnectCallbacks) {
|
for (const [resolve] of afterConnectCallbacks) {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const stableStringify = require('json-stable-stringify');
|
const stableStringify = require('json-stable-stringify');
|
||||||
|
const { extractBoolSettingsValue, extractIntSettingsValue } = require('dbgate-tools');
|
||||||
const childProcessChecker = require('../utility/childProcessChecker');
|
const childProcessChecker = require('../utility/childProcessChecker');
|
||||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||||
const { decryptConnection } = require('../utility/crypting');
|
const { decryptConnection } = require('../utility/crypting');
|
||||||
@@ -51,6 +52,7 @@ function setStatusName(name) {
|
|||||||
|
|
||||||
async function handleConnect(connection) {
|
async function handleConnect(connection) {
|
||||||
storedConnection = connection;
|
storedConnection = connection;
|
||||||
|
const { globalSettings } = storedConnection;
|
||||||
setStatusName('pending');
|
setStatusName('pending');
|
||||||
lastPing = new Date().getTime();
|
lastPing = new Date().getTime();
|
||||||
|
|
||||||
@@ -59,7 +61,9 @@ async function handleConnect(connection) {
|
|||||||
systemConnection = await connectUtility(driver, storedConnection);
|
systemConnection = await connectUtility(driver, storedConnection);
|
||||||
readVersion();
|
readVersion();
|
||||||
handleRefresh();
|
handleRefresh();
|
||||||
setInterval(handleRefresh, 30 * 1000);
|
if (extractBoolSettingsValue(globalSettings, 'connection.autoRefresh', true)) {
|
||||||
|
setInterval(handleRefresh, extractIntSettingsValue(globalSettings, 'connection.autoRefreshInterval', 30, 5, 3600) * 1000);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setStatus({
|
setStatus({
|
||||||
name: 'error',
|
name: 'error',
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ async function getSshConnection(connection) {
|
|||||||
password: connection.sshMode == 'userPassword' ? connection.sshPassword : undefined,
|
password: connection.sshMode == 'userPassword' ? connection.sshPassword : undefined,
|
||||||
agentSocket: connection.sshMode == 'agent' ? platformInfo.sshAuthSock : undefined,
|
agentSocket: connection.sshMode == 'agent' ? platformInfo.sshAuthSock : undefined,
|
||||||
privateKey:
|
privateKey:
|
||||||
connection.sshMode == 'keyFile' && connection.sshKeyFile ? await fs.readFile(connection.sshKeyFile) : undefined,
|
connection.sshMode == 'keyFile' && connection.sshKeyfile ? await fs.readFile(connection.sshKeyfile) : undefined,
|
||||||
skipAutoPrivateKey: true,
|
skipAutoPrivateKey: true,
|
||||||
noReadline: true,
|
noReadline: true,
|
||||||
};
|
};
|
||||||
|
|||||||
9
packages/api/src/utility/timingSafeCheckToken.js
Normal file
9
packages/api/src/utility/timingSafeCheckToken.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
function timingSafeCheckToken(a, b) {
|
||||||
|
if (!a || !b) return false;
|
||||||
|
if (a.length != b.length) return false;
|
||||||
|
return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = timingSafeCheckToken;
|
||||||
@@ -7,6 +7,6 @@ export * from './DatabaseAnalyser';
|
|||||||
export * from './driverBase';
|
export * from './driverBase';
|
||||||
export * from './SqlDumper';
|
export * from './SqlDumper';
|
||||||
export * from './testPermission';
|
export * from './testPermission';
|
||||||
export * from './splitPostgresQuery';
|
|
||||||
export * from './SqlGenerator';
|
export * from './SqlGenerator';
|
||||||
export * from './structureTools';
|
export * from './structureTools';
|
||||||
|
export * from './settingsExtractors';
|
||||||
|
|||||||
20
packages/tools/src/settingsExtractors.ts
Normal file
20
packages/tools/src/settingsExtractors.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
export function extractIntSettingsValue(settings, name, defaultValue, min = null, max = null) {
|
||||||
|
const parsed = parseInt(settings[name]);
|
||||||
|
if (_.isNaN(parsed)) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
if (_.isNumber(parsed)) {
|
||||||
|
if (min != null && parsed < min) return min;
|
||||||
|
if (max != null && parsed > max) return max;
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractBoolSettingsValue(settings, name, defaultValue) {
|
||||||
|
const res = settings[name];
|
||||||
|
if (res == null) return defaultValue;
|
||||||
|
return !!res;
|
||||||
|
}
|
||||||
@@ -1,292 +0,0 @@
|
|||||||
const SINGLE_QUOTE = "'";
|
|
||||||
const DOUBLE_QUOTE = '"';
|
|
||||||
// const BACKTICK = '`';
|
|
||||||
const DOUBLE_DASH_COMMENT_START = '--';
|
|
||||||
const HASH_COMMENT_START = '#';
|
|
||||||
const C_STYLE_COMMENT_START = '/*';
|
|
||||||
const SEMICOLON = ';';
|
|
||||||
const LINE_FEED = '\n';
|
|
||||||
const DELIMITER_KEYWORD = 'DELIMITER';
|
|
||||||
|
|
||||||
export interface SplitOptions {
|
|
||||||
multipleStatements?: boolean;
|
|
||||||
retainComments?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SqlStatement {
|
|
||||||
value: string;
|
|
||||||
supportMulti: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SplitExecutionContext extends Required<SplitOptions> {
|
|
||||||
unread: string;
|
|
||||||
currentDelimiter: string;
|
|
||||||
currentStatement: SqlStatement;
|
|
||||||
output: SqlStatement[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FindExpResult {
|
|
||||||
expIndex: number;
|
|
||||||
exp: string | null;
|
|
||||||
nextIndex: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const regexEscapeSetRegex = /[-/\\^$*+?.()|[\]{}]/g;
|
|
||||||
const singleQuoteStringEndRegex = /(?<!\\)'/;
|
|
||||||
const doubleQuoteStringEndRegex = /(?<!\\)"/;
|
|
||||||
// const backtickQuoteEndRegex = /(?<!`)`(?!`)/;
|
|
||||||
const doubleDashCommentStartRegex = /--[ \f\n\r\t\v]/;
|
|
||||||
const cStyleCommentStartRegex = /\/\*/;
|
|
||||||
const cStyleCommentEndRegex = /(?<!\/)\*\//;
|
|
||||||
const newLineRegex = /(?:[\r\n]+|$)/;
|
|
||||||
const delimiterStartRegex = /(?:^|[\n\r]+)[ \f\t\v]*DELIMITER[ \t]+/i;
|
|
||||||
// Best effort only, unable to find a syntax specification on delimiter
|
|
||||||
const delimiterTokenRegex = /^(?:'(.+)'|"(.+)"|`(.+)`|([^\s]+))/;
|
|
||||||
const semicolonKeyTokenRegex = buildKeyTokenRegex(SEMICOLON);
|
|
||||||
const quoteEndRegexDict: Record<string, RegExp> = {
|
|
||||||
[SINGLE_QUOTE]: singleQuoteStringEndRegex,
|
|
||||||
[DOUBLE_QUOTE]: doubleQuoteStringEndRegex,
|
|
||||||
// [BACKTICK]: backtickQuoteEndRegex,
|
|
||||||
};
|
|
||||||
|
|
||||||
function escapeRegex(value: string): string {
|
|
||||||
return value.replace(regexEscapeSetRegex, '\\$&');
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildKeyTokenRegex(delimiter: string): RegExp {
|
|
||||||
return new RegExp(
|
|
||||||
'(?:' +
|
|
||||||
[
|
|
||||||
escapeRegex(delimiter),
|
|
||||||
SINGLE_QUOTE,
|
|
||||||
DOUBLE_QUOTE,
|
|
||||||
// BACKTICK,
|
|
||||||
doubleDashCommentStartRegex.source,
|
|
||||||
HASH_COMMENT_START,
|
|
||||||
cStyleCommentStartRegex.source,
|
|
||||||
delimiterStartRegex.source,
|
|
||||||
].join('|') +
|
|
||||||
')',
|
|
||||||
'i'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function findExp(content: string, regex: RegExp): FindExpResult {
|
|
||||||
const match = content.match(regex);
|
|
||||||
let result: FindExpResult;
|
|
||||||
if (match?.index !== undefined) {
|
|
||||||
result = {
|
|
||||||
expIndex: match.index,
|
|
||||||
exp: match[0],
|
|
||||||
nextIndex: match.index + match[0].length,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
result = {
|
|
||||||
expIndex: -1,
|
|
||||||
exp: null,
|
|
||||||
nextIndex: content.length,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function findKeyToken(content: string, currentDelimiter: string): FindExpResult {
|
|
||||||
let regex;
|
|
||||||
if (currentDelimiter === SEMICOLON) {
|
|
||||||
regex = semicolonKeyTokenRegex;
|
|
||||||
} else {
|
|
||||||
regex = buildKeyTokenRegex(currentDelimiter);
|
|
||||||
}
|
|
||||||
return findExp(content, regex);
|
|
||||||
}
|
|
||||||
|
|
||||||
function findEndQuote(content: string, quote: string): FindExpResult {
|
|
||||||
if (!(quote in quoteEndRegexDict)) {
|
|
||||||
throw new TypeError(`Incorrect quote ${quote} supplied`);
|
|
||||||
}
|
|
||||||
return findExp(content, quoteEndRegexDict[quote]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function read(
|
|
||||||
context: SplitExecutionContext,
|
|
||||||
readToIndex: number,
|
|
||||||
nextUnreadIndex?: number,
|
|
||||||
checkSemicolon?: boolean
|
|
||||||
): void {
|
|
||||||
if (checkSemicolon === undefined) {
|
|
||||||
checkSemicolon = true;
|
|
||||||
}
|
|
||||||
const readContent = context.unread.slice(0, readToIndex);
|
|
||||||
if (checkSemicolon && readContent.includes(SEMICOLON)) {
|
|
||||||
context.currentStatement.supportMulti = false;
|
|
||||||
}
|
|
||||||
context.currentStatement.value += readContent;
|
|
||||||
if (nextUnreadIndex !== undefined && nextUnreadIndex > 0) {
|
|
||||||
context.unread = context.unread.slice(nextUnreadIndex);
|
|
||||||
} else {
|
|
||||||
context.unread = context.unread.slice(readToIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function readTillNewLine(context: SplitExecutionContext, checkSemicolon?: boolean): void {
|
|
||||||
const findResult = findExp(context.unread, newLineRegex);
|
|
||||||
read(context, findResult.expIndex, findResult.expIndex, checkSemicolon);
|
|
||||||
}
|
|
||||||
|
|
||||||
function discard(context: SplitExecutionContext, nextUnreadIndex: number): void {
|
|
||||||
if (nextUnreadIndex > 0) {
|
|
||||||
context.unread = context.unread.slice(nextUnreadIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function discardTillNewLine(context: SplitExecutionContext): void {
|
|
||||||
const findResult = findExp(context.unread, newLineRegex);
|
|
||||||
discard(context, findResult.expIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
function publishStatementInMultiMode(splitOutput: SqlStatement[], currentStatement: SqlStatement): void {
|
|
||||||
if (splitOutput.length === 0) {
|
|
||||||
splitOutput.push({
|
|
||||||
value: '',
|
|
||||||
supportMulti: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const lastSplitResult = splitOutput[splitOutput.length - 1];
|
|
||||||
if (currentStatement.supportMulti) {
|
|
||||||
if (lastSplitResult.supportMulti) {
|
|
||||||
if (lastSplitResult.value !== '' && !lastSplitResult.value.endsWith(LINE_FEED)) {
|
|
||||||
lastSplitResult.value += LINE_FEED;
|
|
||||||
}
|
|
||||||
lastSplitResult.value += currentStatement.value + SEMICOLON;
|
|
||||||
} else {
|
|
||||||
splitOutput.push({
|
|
||||||
value: currentStatement.value + SEMICOLON,
|
|
||||||
supportMulti: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
splitOutput.push({
|
|
||||||
value: currentStatement.value,
|
|
||||||
supportMulti: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function publishStatement(context: SplitExecutionContext): void {
|
|
||||||
const trimmed = context.currentStatement.value.trim();
|
|
||||||
if (trimmed !== '') {
|
|
||||||
if (!context.multipleStatements) {
|
|
||||||
context.output.push({
|
|
||||||
value: trimmed,
|
|
||||||
supportMulti: context.currentStatement.supportMulti,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
context.currentStatement.value = trimmed;
|
|
||||||
publishStatementInMultiMode(context.output, context.currentStatement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
context.currentStatement.value = '';
|
|
||||||
context.currentStatement.supportMulti = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleKeyTokenFindResult(context: SplitExecutionContext, findResult: FindExpResult): void {
|
|
||||||
switch (findResult.exp?.trim()) {
|
|
||||||
case context.currentDelimiter:
|
|
||||||
read(context, findResult.expIndex, findResult.nextIndex);
|
|
||||||
publishStatement(context);
|
|
||||||
break;
|
|
||||||
// case BACKTICK:
|
|
||||||
case SINGLE_QUOTE:
|
|
||||||
case DOUBLE_QUOTE: {
|
|
||||||
read(context, findResult.nextIndex);
|
|
||||||
const findQuoteResult = findEndQuote(context.unread, findResult.exp);
|
|
||||||
read(context, findQuoteResult.nextIndex, undefined, false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DOUBLE_DASH_COMMENT_START: {
|
|
||||||
if (context.retainComments) {
|
|
||||||
read(context, findResult.nextIndex);
|
|
||||||
readTillNewLine(context, false);
|
|
||||||
} else {
|
|
||||||
read(context, findResult.expIndex, findResult.expIndex + DOUBLE_DASH_COMMENT_START.length);
|
|
||||||
discardTillNewLine(context);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case HASH_COMMENT_START: {
|
|
||||||
if (context.retainComments) {
|
|
||||||
read(context, findResult.nextIndex);
|
|
||||||
readTillNewLine(context, false);
|
|
||||||
} else {
|
|
||||||
read(context, findResult.expIndex, findResult.nextIndex);
|
|
||||||
discardTillNewLine(context);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case C_STYLE_COMMENT_START: {
|
|
||||||
if (['!', '+'].includes(context.unread[findResult.nextIndex]) || context.retainComments) {
|
|
||||||
// Should not be skipped, see https://dev.mysql.com/doc/refman/5.7/en/comments.html
|
|
||||||
read(context, findResult.nextIndex);
|
|
||||||
const findCommentResult = findExp(context.unread, cStyleCommentEndRegex);
|
|
||||||
read(context, findCommentResult.nextIndex);
|
|
||||||
} else {
|
|
||||||
read(context, findResult.expIndex, findResult.nextIndex);
|
|
||||||
const findCommentResult = findExp(context.unread, cStyleCommentEndRegex);
|
|
||||||
discard(context, findCommentResult.nextIndex);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DELIMITER_KEYWORD: {
|
|
||||||
read(context, findResult.expIndex, findResult.nextIndex);
|
|
||||||
// MySQL client will return `DELIMITER cannot contain a backslash character` if backslash is used
|
|
||||||
// Shall we reject backslash as well?
|
|
||||||
const matched = context.unread.match(delimiterTokenRegex);
|
|
||||||
if (matched?.index !== undefined) {
|
|
||||||
context.currentDelimiter = matched[0].trim();
|
|
||||||
discard(context, matched[0].length);
|
|
||||||
}
|
|
||||||
discardTillNewLine(context);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case undefined:
|
|
||||||
case null:
|
|
||||||
read(context, findResult.nextIndex);
|
|
||||||
publishStatement(context);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// This should never happen
|
|
||||||
throw new Error(`Unknown token '${findResult.exp ?? '(null)'}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function splitPostgresQuery(sql: string, options?: SplitOptions): string[] {
|
|
||||||
options = options ?? {};
|
|
||||||
const context: SplitExecutionContext = {
|
|
||||||
multipleStatements: options.multipleStatements ?? false,
|
|
||||||
retainComments: options.retainComments ?? false,
|
|
||||||
unread: sql,
|
|
||||||
currentDelimiter: SEMICOLON,
|
|
||||||
currentStatement: {
|
|
||||||
value: '',
|
|
||||||
supportMulti: true,
|
|
||||||
},
|
|
||||||
output: [],
|
|
||||||
};
|
|
||||||
let findResult: FindExpResult = {
|
|
||||||
expIndex: -1,
|
|
||||||
exp: null,
|
|
||||||
nextIndex: 0,
|
|
||||||
};
|
|
||||||
let lastUnreadLength;
|
|
||||||
do {
|
|
||||||
lastUnreadLength = context.unread.length;
|
|
||||||
findResult = findKeyToken(context.unread, context.currentDelimiter);
|
|
||||||
handleKeyTokenFindResult(context, findResult);
|
|
||||||
// Prevent infinite loop by returning incorrect result
|
|
||||||
if (lastUnreadLength === context.unread.length) {
|
|
||||||
read(context, context.unread.length);
|
|
||||||
}
|
|
||||||
} while (context.unread !== '');
|
|
||||||
publishStatement(context);
|
|
||||||
return context.output.map(v => v.value);
|
|
||||||
}
|
|
||||||
@@ -19,6 +19,13 @@
|
|||||||
onConfirm: () => axiosInstance.post('connections/delete', data),
|
onConfirm: () => axiosInstance.post('connections/delete', data),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
const handleDuplicate = () => {
|
||||||
|
axiosInstance.post('connections/save', {
|
||||||
|
...data,
|
||||||
|
_id: undefined,
|
||||||
|
displayName: `${data.displayName || data.server} - copy`,
|
||||||
|
});
|
||||||
|
};
|
||||||
const handleCreateDatabase = () => {
|
const handleCreateDatabase = () => {
|
||||||
showModal(InputTextModal, {
|
showModal(InputTextModal, {
|
||||||
header: 'Create database',
|
header: 'Create database',
|
||||||
@@ -54,6 +61,10 @@
|
|||||||
text: 'Delete',
|
text: 'Delete',
|
||||||
onClick: handleDelete,
|
onClick: handleDelete,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: 'Duplicate',
|
||||||
|
onClick: handleDuplicate,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
!data.singleDatabase && [
|
!data.singleDatabase && [
|
||||||
!$openedConnections.includes(data._id) && {
|
!$openedConnections.includes(data._id) && {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export interface GlobalCommand {
|
|||||||
menuName?: string;
|
menuName?: string;
|
||||||
toolbarOrder?: number;
|
toolbarOrder?: number;
|
||||||
disableHandleKeyText?: string;
|
disableHandleKeyText?: string;
|
||||||
|
isRelatedToTab?: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function registerCommand(command: GlobalCommand) {
|
export default function registerCommand(command: GlobalCommand) {
|
||||||
|
|||||||
@@ -251,6 +251,7 @@ export function registerFileCommands({
|
|||||||
// keyText: 'Ctrl+S',
|
// keyText: 'Ctrl+S',
|
||||||
icon: 'icon save',
|
icon: 'icon save',
|
||||||
toolbar: true,
|
toolbar: true,
|
||||||
|
isRelatedToTab: true,
|
||||||
testEnabled: () => getCurrentEditor() != null,
|
testEnabled: () => getCurrentEditor() != null,
|
||||||
onClick: () => saveTabFile(getCurrentEditor(), false, folder, format, fileExtension),
|
onClick: () => saveTabFile(getCurrentEditor(), false, folder, format, fileExtension),
|
||||||
});
|
});
|
||||||
@@ -271,6 +272,7 @@ export function registerFileCommands({
|
|||||||
name: 'Execute',
|
name: 'Execute',
|
||||||
icon: 'icon run',
|
icon: 'icon run',
|
||||||
toolbar: true,
|
toolbar: true,
|
||||||
|
isRelatedToTab: true,
|
||||||
keyText: 'F5 | Ctrl+Enter',
|
keyText: 'F5 | Ctrl+Enter',
|
||||||
testEnabled: () => getCurrentEditor() != null && !getCurrentEditor()?.isBusy(),
|
testEnabled: () => getCurrentEditor() != null && !getCurrentEditor()?.isBusy(),
|
||||||
onClick: () => getCurrentEditor().execute(),
|
onClick: () => getCurrentEditor().execute(),
|
||||||
@@ -281,6 +283,7 @@ export function registerFileCommands({
|
|||||||
name: 'Kill',
|
name: 'Kill',
|
||||||
icon: 'icon close',
|
icon: 'icon close',
|
||||||
toolbar: true,
|
toolbar: true,
|
||||||
|
isRelatedToTab: true,
|
||||||
testEnabled: () => getCurrentEditor()?.canKill && getCurrentEditor().canKill(),
|
testEnabled: () => getCurrentEditor()?.canKill && getCurrentEditor().canKill(),
|
||||||
onClick: () => getCurrentEditor().kill(),
|
onClick: () => getCurrentEditor().kill(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
name: 'Refresh',
|
name: 'Refresh',
|
||||||
keyText: 'F5',
|
keyText: 'F5',
|
||||||
toolbar: true,
|
toolbar: true,
|
||||||
|
isRelatedToTab: true,
|
||||||
icon: 'icon reload',
|
icon: 'icon reload',
|
||||||
testEnabled: () => getCurrentDataGrid()?.getDisplay()?.supportsReload,
|
testEnabled: () => getCurrentDataGrid()?.getDisplay()?.supportsReload,
|
||||||
onClick: () => getCurrentDataGrid().refresh(),
|
onClick: () => getCurrentDataGrid().refresh(),
|
||||||
@@ -63,6 +64,7 @@
|
|||||||
group: 'undo',
|
group: 'undo',
|
||||||
icon: 'icon undo',
|
icon: 'icon undo',
|
||||||
toolbar: true,
|
toolbar: true,
|
||||||
|
isRelatedToTab: true,
|
||||||
testEnabled: () => getCurrentDataGrid()?.getGrider()?.canUndo,
|
testEnabled: () => getCurrentDataGrid()?.getGrider()?.canUndo,
|
||||||
onClick: () => getCurrentDataGrid().undo(),
|
onClick: () => getCurrentDataGrid().undo(),
|
||||||
});
|
});
|
||||||
@@ -74,6 +76,7 @@
|
|||||||
group: 'redo',
|
group: 'redo',
|
||||||
icon: 'icon redo',
|
icon: 'icon redo',
|
||||||
toolbar: true,
|
toolbar: true,
|
||||||
|
isRelatedToTab: true,
|
||||||
testEnabled: () => getCurrentDataGrid()?.getGrider()?.canRedo,
|
testEnabled: () => getCurrentDataGrid()?.getGrider()?.canRedo,
|
||||||
onClick: () => getCurrentDataGrid().redo(),
|
onClick: () => getCurrentDataGrid().redo(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -54,6 +54,9 @@ export function countColumnSizes(grider: Grider, columns, containerWidth, displa
|
|||||||
context.font = '14px Helvetica';
|
context.font = '14px Helvetica';
|
||||||
for (let rowIndex = 0; rowIndex < Math.min(grider.rowCount, 20); rowIndex += 1) {
|
for (let rowIndex = 0; rowIndex < Math.min(grider.rowCount, 20); rowIndex += 1) {
|
||||||
const row = grider.getRowData(rowIndex);
|
const row = grider.getRowData(rowIndex);
|
||||||
|
if (!row) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
for (let colIndex = 0; colIndex < columns.length; colIndex++) {
|
for (let colIndex = 0; colIndex < columns.length; colIndex++) {
|
||||||
const uqName = columns[colIndex].uniqueName;
|
const uqName = columns[colIndex].uniqueName;
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
name: 'Refresh',
|
name: 'Refresh',
|
||||||
keyText: 'F5',
|
keyText: 'F5',
|
||||||
toolbar: true,
|
toolbar: true,
|
||||||
|
isRelatedToTab: true,
|
||||||
icon: 'icon reload',
|
icon: 'icon reload',
|
||||||
testEnabled: () => getCurrentDataForm() != null,
|
testEnabled: () => getCurrentDataForm() != null,
|
||||||
onClick: () => getCurrentDataForm().refresh(),
|
onClick: () => getCurrentDataForm().refresh(),
|
||||||
@@ -58,6 +59,7 @@
|
|||||||
group: 'undo',
|
group: 'undo',
|
||||||
icon: 'icon undo',
|
icon: 'icon undo',
|
||||||
toolbar: true,
|
toolbar: true,
|
||||||
|
isRelatedToTab: true,
|
||||||
testEnabled: () => getCurrentDataForm()?.getFormer()?.canUndo,
|
testEnabled: () => getCurrentDataForm()?.getFormer()?.canUndo,
|
||||||
onClick: () => getCurrentDataForm().getFormer().undo(),
|
onClick: () => getCurrentDataForm().getFormer().undo(),
|
||||||
});
|
});
|
||||||
@@ -69,6 +71,7 @@
|
|||||||
group: 'redo',
|
group: 'redo',
|
||||||
icon: 'icon redo',
|
icon: 'icon redo',
|
||||||
toolbar: true,
|
toolbar: true,
|
||||||
|
isRelatedToTab: true,
|
||||||
testEnabled: () => getCurrentDataForm()?.getFormer()?.canRedo,
|
testEnabled: () => getCurrentDataForm()?.getFormer()?.canRedo,
|
||||||
onClick: () => getCurrentDataForm().getFormer().redo(),
|
onClick: () => getCurrentDataForm().getFormer().redo(),
|
||||||
});
|
});
|
||||||
@@ -104,6 +107,7 @@
|
|||||||
name: 'First',
|
name: 'First',
|
||||||
keyText: 'Ctrl+Home',
|
keyText: 'Ctrl+Home',
|
||||||
toolbar: true,
|
toolbar: true,
|
||||||
|
isRelatedToTab: true,
|
||||||
icon: 'icon arrow-begin',
|
icon: 'icon arrow-begin',
|
||||||
testEnabled: () => getCurrentDataForm() != null,
|
testEnabled: () => getCurrentDataForm() != null,
|
||||||
onClick: () => getCurrentDataForm().navigate('begin'),
|
onClick: () => getCurrentDataForm().navigate('begin'),
|
||||||
@@ -115,6 +119,7 @@
|
|||||||
name: 'Previous',
|
name: 'Previous',
|
||||||
keyText: 'Ctrl+ArrowUp',
|
keyText: 'Ctrl+ArrowUp',
|
||||||
toolbar: true,
|
toolbar: true,
|
||||||
|
isRelatedToTab: true,
|
||||||
icon: 'icon arrow-left',
|
icon: 'icon arrow-left',
|
||||||
testEnabled: () => getCurrentDataForm() != null,
|
testEnabled: () => getCurrentDataForm() != null,
|
||||||
onClick: () => getCurrentDataForm().navigate('previous'),
|
onClick: () => getCurrentDataForm().navigate('previous'),
|
||||||
@@ -126,6 +131,7 @@
|
|||||||
name: 'Next',
|
name: 'Next',
|
||||||
keyText: 'Ctrl+ArrowDown',
|
keyText: 'Ctrl+ArrowDown',
|
||||||
toolbar: true,
|
toolbar: true,
|
||||||
|
isRelatedToTab: true,
|
||||||
icon: 'icon arrow-right',
|
icon: 'icon arrow-right',
|
||||||
testEnabled: () => getCurrentDataForm() != null,
|
testEnabled: () => getCurrentDataForm() != null,
|
||||||
onClick: () => getCurrentDataForm().navigate('next'),
|
onClick: () => getCurrentDataForm().navigate('next'),
|
||||||
@@ -137,6 +143,7 @@
|
|||||||
name: 'Last',
|
name: 'Last',
|
||||||
keyText: 'Ctrl+End',
|
keyText: 'Ctrl+End',
|
||||||
toolbar: true,
|
toolbar: true,
|
||||||
|
isRelatedToTab: true,
|
||||||
icon: 'icon arrow-end',
|
icon: 'icon arrow-end',
|
||||||
testEnabled: () => getCurrentDataForm() != null,
|
testEnabled: () => getCurrentDataForm() != null,
|
||||||
onClick: () => getCurrentDataForm().navigate('end'),
|
onClick: () => getCurrentDataForm().navigate('end'),
|
||||||
|
|||||||
@@ -15,11 +15,7 @@
|
|||||||
const { values, setFieldValue } = getFormContext();
|
const { values, setFieldValue } = getFormContext();
|
||||||
$: dbinfo = useDatabaseInfo({ conid: $values[conidName], database: $values[databaseName] });
|
$: dbinfo = useDatabaseInfo({ conid: $values[conidName], database: $values[databaseName] });
|
||||||
|
|
||||||
$: tablesOptions = [
|
$: tablesOptions = _.compact([...($dbinfo?.tables || []), ...($dbinfo?.views || []), ...($dbinfo?.collections || [])])
|
||||||
...(($dbinfo && $dbinfo.tables) || []),
|
|
||||||
...(($dbinfo && $dbinfo.views) || []),
|
|
||||||
...(($dbinfo && $dbinfo.collections) || []),
|
|
||||||
]
|
|
||||||
.filter(x => !$values[schemaName] || x.schemaName == $values[schemaName])
|
.filter(x => !$values[schemaName] || x.schemaName == $values[schemaName])
|
||||||
.map(x => ({
|
.map(x => ({
|
||||||
value: x.pureName,
|
value: x.pureName,
|
||||||
@@ -31,18 +27,20 @@
|
|||||||
<FormSelectField {...$$restProps} {name} options={tablesOptions} isMulti templateProps={{ noMargin: true }} />
|
<FormSelectField {...$$restProps} {name} options={tablesOptions} isMulti templateProps={{ noMargin: true }} />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
{#each ['tables', 'views', 'collections'] as field}
|
||||||
|
{#if $dbinfo && $dbinfo[field]?.length > 0}
|
||||||
<FormStyledButton
|
<FormStyledButton
|
||||||
type="button"
|
type="button"
|
||||||
value="All tables"
|
value={`All ${field}`}
|
||||||
on:click={() =>
|
on:click={() =>
|
||||||
setFieldValue(name, _.uniq([...($values[name] || []), ...($dbinfo && $dbinfo.tables.map(x => x.pureName))]))}
|
setFieldValue(
|
||||||
/>
|
name,
|
||||||
<FormStyledButton
|
_.compact(_.uniq([...($values[name] || []), ...($dbinfo[field]?.map(x => x.pureName) || [])]))
|
||||||
type="button"
|
)}
|
||||||
value="All views"
|
|
||||||
on:click={() =>
|
|
||||||
setFieldValue(name, _.uniq([...($values[name] || []), ...($dbinfo && $dbinfo.views.map(x => x.pureName))]))}
|
|
||||||
/>
|
/>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
|
||||||
<FormStyledButton type="button" value="Remove all" on:click={() => setFieldValue(name, [])} />
|
<FormStyledButton type="button" value="Remove all" on:click={() => setFieldValue(name, [])} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#each plugins as packageManifest (packageManifest.name)}
|
{#each plugins || [] as packageManifest (packageManifest.name)}
|
||||||
<div class="wrapper" on:click={() => openPlugin(packageManifest)}>
|
<div class="wrapper" on:click={() => openPlugin(packageManifest)}>
|
||||||
<img class="icon" src={extractPluginIcon(packageManifest)} />
|
<img class="icon" src={extractPluginIcon(packageManifest)} />
|
||||||
<div class="ml-2">
|
<div class="ml-2">
|
||||||
|
|||||||
@@ -11,10 +11,15 @@
|
|||||||
import 'ace-builds/src-noconflict/mode-json';
|
import 'ace-builds/src-noconflict/mode-json';
|
||||||
import 'ace-builds/src-noconflict/mode-javascript';
|
import 'ace-builds/src-noconflict/mode-javascript';
|
||||||
import 'ace-builds/src-noconflict/mode-markdown';
|
import 'ace-builds/src-noconflict/mode-markdown';
|
||||||
import 'ace-builds/src-noconflict/theme-github';
|
|
||||||
import 'ace-builds/src-noconflict/theme-twilight';
|
|
||||||
import 'ace-builds/src-noconflict/ext-searchbox';
|
import 'ace-builds/src-noconflict/ext-searchbox';
|
||||||
import 'ace-builds/src-noconflict/ext-language_tools';
|
import 'ace-builds/src-noconflict/ext-language_tools';
|
||||||
|
|
||||||
|
import 'ace-builds/src-noconflict/theme-github';
|
||||||
|
// import 'ace-builds/src-noconflict/theme-sqlserver';
|
||||||
|
|
||||||
|
import 'ace-builds/src-noconflict/theme-twilight';
|
||||||
|
// import 'ace-builds/src-noconflict/theme-monokai';
|
||||||
|
|
||||||
import { currentDropDownMenu, currentThemeDefinition } from '../stores';
|
import { currentDropDownMenu, currentThemeDefinition } from '../stores';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { handleCommandKeyDown } from '../commands/CommandListener.svelte';
|
import { handleCommandKeyDown } from '../commands/CommandListener.svelte';
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ function getParsedLocalStorage(key) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const saveHandlersList = [];
|
||||||
|
|
||||||
export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = null, onInitialData = null }) {
|
export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = null, onInitialData = null }) {
|
||||||
const localStorageKey = `tabdata_editor_${tabid}`;
|
const localStorageKey = `tabdata_editor_${tabid}`;
|
||||||
let changeCounter = 0;
|
let changeCounter = 0;
|
||||||
@@ -90,6 +92,11 @@ export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = n
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const saveToStorageIfNeeded = async () => {
|
||||||
|
if (savedCounter == changeCounter) return; // all saved
|
||||||
|
await saveToStorage();
|
||||||
|
};
|
||||||
|
|
||||||
const saveToStorage = async () => {
|
const saveToStorage = async () => {
|
||||||
if (value == null) return;
|
if (value == null) return;
|
||||||
try {
|
try {
|
||||||
@@ -128,11 +135,13 @@ export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = n
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
window.addEventListener('beforeunload', saveToStorageSync);
|
window.addEventListener('beforeunload', saveToStorageSync);
|
||||||
initialLoad();
|
initialLoad();
|
||||||
|
saveHandlersList.push(saveToStorageIfNeeded);
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
saveToStorage();
|
saveToStorage();
|
||||||
window.removeEventListener('beforeunload', saveToStorageSync);
|
window.removeEventListener('beforeunload', saveToStorageSync);
|
||||||
|
_.remove(saveHandlersList, x => x == saveToStorageIfNeeded);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -144,3 +153,9 @@ export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = n
|
|||||||
initialLoad,
|
initialLoad,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function saveAllPendingEditorData() {
|
||||||
|
for (const item of saveHandlersList) {
|
||||||
|
await item();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
import FormProvider from '../forms/FormProvider.svelte';
|
import FormProvider from '../forms/FormProvider.svelte';
|
||||||
import FormSubmit from '../forms/FormSubmit.svelte';
|
import FormSubmit from '../forms/FormSubmit.svelte';
|
||||||
import FormTextField from '../forms/FormTextField.svelte';
|
import FormTextField from '../forms/FormTextField.svelte';
|
||||||
|
import FormValues from '../forms/FormValues.svelte';
|
||||||
|
|
||||||
import ModalBase from '../modals/ModalBase.svelte';
|
import ModalBase from '../modals/ModalBase.svelte';
|
||||||
import { closeCurrentModal } from '../modals/modalTools';
|
import { closeCurrentModal } from '../modals/modalTools';
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
<ModalBase {...$$restProps}>
|
<ModalBase {...$$restProps}>
|
||||||
<div slot="header">Settings</div>
|
<div slot="header">Settings</div>
|
||||||
|
|
||||||
|
<FormValues let:values>
|
||||||
<div class="heading">Appearance</div>
|
<div class="heading">Appearance</div>
|
||||||
<FormCheckboxField name=":visibleToolbar" label="Show toolbar" defaultValue={true} />
|
<FormCheckboxField name=":visibleToolbar" label="Show toolbar" defaultValue={true} />
|
||||||
|
|
||||||
@@ -44,6 +46,20 @@
|
|||||||
/>
|
/>
|
||||||
<FormCheckboxField name="dataGrid.showHintColumns" label="Show foreign key hints" defaultValue={true} />
|
<FormCheckboxField name="dataGrid.showHintColumns" label="Show foreign key hints" defaultValue={true} />
|
||||||
|
|
||||||
|
<div class="heading">Connection</div>
|
||||||
|
<FormCheckboxField
|
||||||
|
name="connection.autoRefresh"
|
||||||
|
label="Automatic refresh of database model on background"
|
||||||
|
defaultValue={true}
|
||||||
|
/>
|
||||||
|
<FormTextField
|
||||||
|
name="connection.autoRefreshInterval"
|
||||||
|
label="Interval between automatic refreshes in seconds"
|
||||||
|
defaultValue="30"
|
||||||
|
disabled={values['connection.autoRefresh'] === false}
|
||||||
|
/>
|
||||||
|
</FormValues>
|
||||||
|
|
||||||
<div slot="footer">
|
<div slot="footer">
|
||||||
<FormSubmit value="OK" on:click={handleOk} />
|
<FormSubmit value="OK" on:click={handleOk} />
|
||||||
<FormButton value="Cancel" on:click={closeCurrentModal} />
|
<FormButton value="Cancel" on:click={closeCurrentModal} />
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
name: 'Save',
|
name: 'Save',
|
||||||
// keyText: 'Ctrl+S',
|
// keyText: 'Ctrl+S',
|
||||||
toolbar: true,
|
toolbar: true,
|
||||||
|
isRelatedToTab: true,
|
||||||
icon: 'icon save',
|
icon: 'icon save',
|
||||||
testEnabled: () => getCurrentEditor()?.canSave(),
|
testEnabled: () => getCurrentEditor()?.canSave(),
|
||||||
onClick: () => getCurrentEditor().save(),
|
onClick: () => getCurrentEditor().save(),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
name: 'Save',
|
name: 'Save',
|
||||||
// keyText: 'Ctrl+S',
|
// keyText: 'Ctrl+S',
|
||||||
toolbar: true,
|
toolbar: true,
|
||||||
|
isRelatedToTab: true,
|
||||||
icon: 'icon save',
|
icon: 'icon save',
|
||||||
testEnabled: () => getCurrentEditor() != null,
|
testEnabled: () => getCurrentEditor() != null,
|
||||||
onClick: () => getCurrentEditor().save(),
|
onClick: () => getCurrentEditor().save(),
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
name: 'Preview',
|
name: 'Preview',
|
||||||
icon: 'icon run',
|
icon: 'icon run',
|
||||||
toolbar: true,
|
toolbar: true,
|
||||||
|
isRelatedToTab: true,
|
||||||
keyText: 'F5 | Ctrl+Enter',
|
keyText: 'F5 | Ctrl+Enter',
|
||||||
testEnabled: () => getCurrentEditor() != null,
|
testEnabled: () => getCurrentEditor() != null,
|
||||||
onClick: () => getCurrentEditor().preview(),
|
onClick: () => getCurrentEditor().preview(),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
name: 'Save',
|
name: 'Save',
|
||||||
// keyText: 'Ctrl+S',
|
// keyText: 'Ctrl+S',
|
||||||
toolbar: true,
|
toolbar: true,
|
||||||
|
isRelatedToTab: true,
|
||||||
icon: 'icon save',
|
icon: 'icon save',
|
||||||
testEnabled: () => getCurrentEditor()?.canSave(),
|
testEnabled: () => getCurrentEditor()?.canSave(),
|
||||||
onClick: () => getCurrentEditor().save(),
|
onClick: () => getCurrentEditor().save(),
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
console.log('CRASH DETECTED!!!');
|
console.log('CRASH DETECTED!!!');
|
||||||
const lastDbGateCrashJson = localStorage.getItem('lastDbGateCrash');
|
const lastDbGateCrashJson = localStorage.getItem('lastDbGateCrash');
|
||||||
const lastDbGateCrash = lastDbGateCrashJson ? JSON.parse(lastDbGateCrashJson) : null;
|
const lastDbGateCrash = lastDbGateCrashJson ? JSON.parse(lastDbGateCrashJson) : null;
|
||||||
|
// let detail = e?.reason?.stack || '';
|
||||||
|
// if (detail) detail = '\n\n' + detail;
|
||||||
|
|
||||||
if (lastDbGateCrash && new Date().getTime() - lastDbGateCrash < 30 * 1000) {
|
if (lastDbGateCrash && new Date().getTime() - lastDbGateCrash < 30 * 1000) {
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import tabs from '../tabs';
|
|||||||
import { setSelectedTabFunc } from './common';
|
import { setSelectedTabFunc } from './common';
|
||||||
import localforage from 'localforage';
|
import localforage from 'localforage';
|
||||||
import stableStringify from 'json-stable-stringify';
|
import stableStringify from 'json-stable-stringify';
|
||||||
|
import { saveAllPendingEditorData } from '../query/useEditorData';
|
||||||
|
|
||||||
function findFreeNumber(numbers: number[]) {
|
function findFreeNumber(numbers: number[]) {
|
||||||
if (numbers.length == 0) return 1;
|
if (numbers.length == 0) return 1;
|
||||||
@@ -74,9 +75,9 @@ export default async function openNewTab(newTab, initialData = undefined, option
|
|||||||
openedTabs.update(files => [
|
openedTabs.update(files => [
|
||||||
...(files || []).map(x => ({ ...x, selected: false })),
|
...(files || []).map(x => ({ ...x, selected: false })),
|
||||||
{
|
{
|
||||||
|
...newTab,
|
||||||
tabid,
|
tabid,
|
||||||
selected: true,
|
selected: true,
|
||||||
...newTab,
|
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -91,3 +92,35 @@ export default async function openNewTab(newTab, initialData = undefined, option
|
|||||||
// },
|
// },
|
||||||
// ]);
|
// ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function duplicateTab(tab) {
|
||||||
|
await saveAllPendingEditorData();
|
||||||
|
|
||||||
|
let title = tab.title;
|
||||||
|
const mtitle = title.match(/^(.*#)[\d]+$/);
|
||||||
|
if (mtitle) title = mtitle[1];
|
||||||
|
|
||||||
|
const keyRegex = /^tabdata_([^_]+)_([^_]+)$/;
|
||||||
|
const initialData = {};
|
||||||
|
for (let i = 0; i < localStorage.length; i++) {
|
||||||
|
const key = localStorage.key(i);
|
||||||
|
const m = key.match(keyRegex);
|
||||||
|
if (m && m[2] == tab.tabid) {
|
||||||
|
initialData[m[1]] = JSON.parse(localStorage.getItem(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const key of await localforage.keys()) {
|
||||||
|
const m = key.match(keyRegex);
|
||||||
|
if (m && m[2] == tab.tabid) {
|
||||||
|
initialData[m[1]] = await localforage.getItem(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
openNewTab(
|
||||||
|
{
|
||||||
|
..._.omit(tab, ['tabid']),
|
||||||
|
title,
|
||||||
|
},
|
||||||
|
initialData,
|
||||||
|
{ forceNewTab: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
<ErrorInfo message={$status.message} icon="img error" />
|
<ErrorInfo message={$status.message} icon="img error" />
|
||||||
<InlineButton on:click={handleRefreshDatabase}>Refresh</InlineButton>
|
<InlineButton on:click={handleRefreshDatabase}>Refresh</InlineButton>
|
||||||
</WidgetsInnerContainer>
|
</WidgetsInnerContainer>
|
||||||
{:else if objectList.length == 0 && $status && $status.name != 'pending' && $objects}
|
{:else if objectList.length == 0 && $status && $status.name != 'pending' && $status.name != 'checkStructure' && $status.name != 'loadStructure' && $objects}
|
||||||
<WidgetsInnerContainer>
|
<WidgetsInnerContainer>
|
||||||
<ErrorInfo
|
<ErrorInfo
|
||||||
message={`Database ${database} is empty or structure is not loaded, press Refresh button to reload structure`}
|
message={`Database ${database} is empty or structure is not loaded, press Refresh button to reload structure`}
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
<InlineButton on:click={handleRefreshDatabase}>Refresh</InlineButton>
|
<InlineButton on:click={handleRefreshDatabase}>Refresh</InlineButton>
|
||||||
</SearchBoxWrapper>
|
</SearchBoxWrapper>
|
||||||
<WidgetsInnerContainer>
|
<WidgetsInnerContainer>
|
||||||
{#if ($status && $status.name == 'pending' && $objects) || !$objects}
|
{#if ($status && ($status.name == 'pending' || $status.name == 'checkStructure' || $status.name == 'loadStructure') && $objects) || !$objects}
|
||||||
<LoadingInfo message="Loading database structure" />
|
<LoadingInfo message="Loading database structure" />
|
||||||
{:else}
|
{:else}
|
||||||
<AppObjectList
|
<AppObjectList
|
||||||
|
|||||||
@@ -49,6 +49,10 @@
|
|||||||
<div class="item">
|
<div class="item">
|
||||||
{#if $status.name == 'pending'}
|
{#if $status.name == 'pending'}
|
||||||
<FontIcon icon="icon loading" /> Loading
|
<FontIcon icon="icon loading" /> Loading
|
||||||
|
{:else if $status.name == 'checkStructure'}
|
||||||
|
<FontIcon icon="icon loading" /> Checking model
|
||||||
|
{:else if $status.name == 'loadStructure'}
|
||||||
|
<FontIcon icon="icon loading" /> Loading model
|
||||||
{:else if $status.name == 'ok'}
|
{:else if $status.name == 'ok'}
|
||||||
<FontIcon icon="img ok-inv" /> Connected
|
<FontIcon icon="img ok-inv" /> Connected
|
||||||
{:else if $status.name == 'error'}
|
{:else if $status.name == 'error'}
|
||||||
|
|||||||
@@ -87,9 +87,9 @@
|
|||||||
registerCommand({
|
registerCommand({
|
||||||
id: 'tabs.addToFavorites',
|
id: 'tabs.addToFavorites',
|
||||||
category: 'Tabs',
|
category: 'Tabs',
|
||||||
name: 'Favorites',
|
name: 'Add current tab to favorites',
|
||||||
icon: 'icon favorite',
|
// icon: 'icon favorite',
|
||||||
toolbar: true,
|
// toolbar: true,
|
||||||
testEnabled: () =>
|
testEnabled: () =>
|
||||||
getActiveTab()?.tabComponent &&
|
getActiveTab()?.tabComponent &&
|
||||||
tabs[getActiveTab()?.tabComponent] &&
|
tabs[getActiveTab()?.tabComponent] &&
|
||||||
@@ -113,6 +113,7 @@
|
|||||||
import { setSelectedTab } from '../utility/common';
|
import { setSelectedTab } from '../utility/common';
|
||||||
import contextMenu from '../utility/contextMenu';
|
import contextMenu from '../utility/contextMenu';
|
||||||
import { getConnectionInfo } from '../utility/metadataLoaders';
|
import { getConnectionInfo } from '../utility/metadataLoaders';
|
||||||
|
import { duplicateTab } from '../utility/openNewTab';
|
||||||
|
|
||||||
$: currentDbKey =
|
$: currentDbKey =
|
||||||
$currentDatabase && $currentDatabase.name && $currentDatabase.connection
|
$currentDatabase && $currentDatabase.name && $currentDatabase.connection
|
||||||
@@ -146,9 +147,10 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getContextMenu = (tabid, props) => () => {
|
const getContextMenu = tab => () => {
|
||||||
|
const { tabid, props, tabComponent } = tab;
|
||||||
const { conid, database } = props || {};
|
const { conid, database } = props || {};
|
||||||
const res = [
|
return [
|
||||||
{
|
{
|
||||||
text: 'Close',
|
text: 'Close',
|
||||||
onClick: () => closeTab(tabid),
|
onClick: () => closeTab(tabid),
|
||||||
@@ -161,9 +163,23 @@
|
|||||||
text: 'Close others',
|
text: 'Close others',
|
||||||
onClick: () => closeOthers(tabid),
|
onClick: () => closeOthers(tabid),
|
||||||
},
|
},
|
||||||
];
|
{
|
||||||
if (conid && database) {
|
text: 'Duplicate',
|
||||||
res.push(
|
onClick: () => duplicateTab(tab),
|
||||||
|
},
|
||||||
|
tabComponent &&
|
||||||
|
tabs[tabComponent] &&
|
||||||
|
tabs[tabComponent].allowAddToFavorites &&
|
||||||
|
tabs[tabComponent].allowAddToFavorites(props) && [
|
||||||
|
{ divider: true },
|
||||||
|
{
|
||||||
|
text: 'Add to favorites',
|
||||||
|
onClick: () => showModal(FavoriteModal, { savingTab: tab }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
conid &&
|
||||||
|
database && [
|
||||||
|
{ divider: true },
|
||||||
{
|
{
|
||||||
text: `Close with same DB - ${database}`,
|
text: `Close with same DB - ${database}`,
|
||||||
onClick: () => closeWithSameDb(tabid),
|
onClick: () => closeWithSameDb(tabid),
|
||||||
@@ -171,10 +187,9 @@
|
|||||||
{
|
{
|
||||||
text: `Close with other DB than ${database}`,
|
text: `Close with other DB than ${database}`,
|
||||||
onClick: () => closeWithOtherDb(tabid),
|
onClick: () => closeWithOtherDb(tabid),
|
||||||
}
|
},
|
||||||
);
|
],
|
||||||
}
|
];
|
||||||
return res;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSetDb = async props => {
|
const handleSetDb = async props => {
|
||||||
@@ -216,7 +231,7 @@
|
|||||||
class:selected={tab.selected}
|
class:selected={tab.selected}
|
||||||
on:click={e => handleTabClick(e, tab.tabid)}
|
on:click={e => handleTabClick(e, tab.tabid)}
|
||||||
on:mouseup={e => handleMouseUp(e, tab.tabid)}
|
on:mouseup={e => handleMouseUp(e, tab.tabid)}
|
||||||
use:contextMenu={getContextMenu(tab.tabid, tab.props)}
|
use:contextMenu={getContextMenu(tab)}
|
||||||
>
|
>
|
||||||
<FontIcon icon={tab.busy ? 'icon loading' : tab.icon} />
|
<FontIcon icon={tab.busy ? 'icon loading' : tab.icon} />
|
||||||
<span class="file-name">
|
<span class="file-name">
|
||||||
|
|||||||
@@ -10,7 +10,8 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { openFavorite } from '../appobj/FavoriteFileAppObject.svelte';
|
import { openFavorite } from '../appobj/FavoriteFileAppObject.svelte';
|
||||||
import runCommand from '../commands/runCommand';
|
import runCommand from '../commands/runCommand';
|
||||||
import { commands, commandsCustomized } from '../stores';
|
import FontIcon from '../icons/FontIcon.svelte';
|
||||||
|
import { activeTab, commands, commandsCustomized } from '../stores';
|
||||||
import getElectron from '../utility/getElectron';
|
import getElectron from '../utility/getElectron';
|
||||||
import { useFavorites } from '../utility/metadataLoaders';
|
import { useFavorites } from '../utility/metadataLoaders';
|
||||||
import ToolbarButton from './ToolbarButton.svelte';
|
import ToolbarButton from './ToolbarButton.svelte';
|
||||||
@@ -25,7 +26,8 @@
|
|||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="root">
|
||||||
|
<div class="container">
|
||||||
{#if !electron}
|
{#if !electron}
|
||||||
<ToolbarButton externalImage="logo192.png" on:click={() => runCommand('about.show')} />
|
<ToolbarButton externalImage="logo192.png" on:click={() => runCommand('about.show')} />
|
||||||
{/if}
|
{/if}
|
||||||
@@ -35,7 +37,7 @@
|
|||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
{#each list as command}
|
{#each list.filter(x => !x.isRelatedToTab) as command}
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
icon={command.icon}
|
icon={command.icon}
|
||||||
on:click={command.onClick}
|
on:click={command.onClick}
|
||||||
@@ -45,6 +47,27 @@
|
|||||||
{command.toolbarName || command.name}
|
{command.toolbarName || command.name}
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
{/each}
|
{/each}
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
{#if $activeTab && list.filter(x => x.isRelatedToTab).length > 0}
|
||||||
|
<div class="activeTab">
|
||||||
|
<div class="activeTabInner">
|
||||||
|
<FontIcon icon={$activeTab.icon} />
|
||||||
|
{$activeTab.title}:
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#each list.filter(x => x.isRelatedToTab) as command}
|
||||||
|
<ToolbarButton
|
||||||
|
icon={command.icon}
|
||||||
|
on:click={command.onClick}
|
||||||
|
disabled={!command.enabled}
|
||||||
|
title={getCommandTitle(command)}
|
||||||
|
>
|
||||||
|
{command.toolbarName || command.name}
|
||||||
|
</ToolbarButton>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -54,4 +77,21 @@
|
|||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
height: var(--dim-toolbar-height);
|
height: var(--dim-toolbar-height);
|
||||||
}
|
}
|
||||||
|
.root {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activeTab {
|
||||||
|
background-color: var(--theme-bg-2);
|
||||||
|
white-space: nowrap;
|
||||||
|
display: flex;
|
||||||
|
padding-left: 15px;
|
||||||
|
padding-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activeTabInner {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
13
patches/svelte+3.35.0.patch
Normal file
13
patches/svelte+3.35.0.patch
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
diff --git a/node_modules/svelte/internal/index.js b/node_modules/svelte/internal/index.js
|
||||||
|
index ee20a17..7b6fff8 100644
|
||||||
|
--- a/node_modules/svelte/internal/index.js
|
||||||
|
+++ b/node_modules/svelte/internal/index.js
|
||||||
|
@@ -200,7 +200,7 @@ function insert(target, node, anchor) {
|
||||||
|
target.insertBefore(node, anchor || null);
|
||||||
|
}
|
||||||
|
function detach(node) {
|
||||||
|
- node.parentNode.removeChild(node);
|
||||||
|
+ if (node.parentNode) node.parentNode.removeChild(node);
|
||||||
|
}
|
||||||
|
function destroy_each(iterations, detaching) {
|
||||||
|
for (let i = 0; i < iterations.length; i += 1) {
|
||||||
@@ -31,11 +31,12 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"dbgate-plugin-tools": "^1.0.7",
|
"dbgate-plugin-tools": "^1.0.7",
|
||||||
"webpack": "^4.42.0",
|
|
||||||
"webpack-cli": "^3.3.11",
|
|
||||||
"dbgate-tools": "^4.1.1",
|
"dbgate-tools": "^4.1.1",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"pg": "^7.17.0",
|
"pg": "^7.17.0",
|
||||||
"pg-query-stream": "^3.1.1"
|
"pg-query-stream": "^3.1.1",
|
||||||
|
"webpack": "^4.42.0",
|
||||||
|
"webpack-cli": "^3.3.11",
|
||||||
|
"sql-query-identifier": "^2.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const stream = require('stream');
|
const stream = require('stream');
|
||||||
|
const { identify } = require('sql-query-identifier');
|
||||||
|
|
||||||
const driverBase = require('../frontend/driver');
|
const driverBase = require('../frontend/driver');
|
||||||
const Analyser = require('./Analyser');
|
const Analyser = require('./Analyser');
|
||||||
const pg = require('pg');
|
const pg = require('pg');
|
||||||
const pgQueryStream = require('pg-query-stream');
|
const pgQueryStream = require('pg-query-stream');
|
||||||
const { createBulkInsertStreamBase, splitPostgresQuery, makeUniqueColumnNames } = require('dbgate-tools');
|
const { createBulkInsertStreamBase, makeUniqueColumnNames } = require('dbgate-tools');
|
||||||
|
|
||||||
function extractPostgresColumns(result) {
|
function extractPostgresColumns(result) {
|
||||||
if (!result || !result.fields) return [];
|
if (!result || !result.fields) return [];
|
||||||
@@ -119,10 +121,10 @@ const driver = {
|
|||||||
return { rows: res.rows.map(row => zipDataRow(row, columns)), columns };
|
return { rows: res.rows.map(row => zipDataRow(row, columns)), columns };
|
||||||
},
|
},
|
||||||
async stream(client, sql, options) {
|
async stream(client, sql, options) {
|
||||||
const sqlSplitted = splitPostgresQuery(sql);
|
const sqlSplitted = identify(sql, { dialect: 'psql' });
|
||||||
|
|
||||||
for (const sqlItem of sqlSplitted) {
|
for (const sqlItem of sqlSplitted) {
|
||||||
await runStreamItem(client, sqlItem, options);
|
await runStreamItem(client, sqlItem.text, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
options.done();
|
options.done();
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ select
|
|||||||
from
|
from
|
||||||
information_schema.routines where routine_schema != 'information_schema' and routine_schema != 'pg_catalog'
|
information_schema.routines where routine_schema != 'information_schema' and routine_schema != 'pg_catalog'
|
||||||
and (
|
and (
|
||||||
(routine_type = 'PROCEDURE' and ('procedures:' || routine_schema || '.' || routine_schema) =OBJECT_ID_CONDITION)
|
(routine_type = 'PROCEDURE' and ('procedures:' || routine_schema || '.' || routine_name) =OBJECT_ID_CONDITION)
|
||||||
or
|
or
|
||||||
(routine_type = 'FUNCTION' and ('functions:' || routine_schema || '.' || routine_schema) =OBJECT_ID_CONDITION)
|
(routine_type = 'FUNCTION' and ('functions:' || routine_schema || '.' || routine_name) =OBJECT_ID_CONDITION)
|
||||||
)
|
)
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -8251,6 +8251,11 @@ sql-formatter@^2.3.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lodash "^4.16.0"
|
lodash "^4.16.0"
|
||||||
|
|
||||||
|
sql-query-identifier@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/sql-query-identifier/-/sql-query-identifier-2.1.0.tgz#dbf0f34b11bc14c8ade44de13350271047eb566e"
|
||||||
|
integrity sha512-DcC+inWZvN6fiTyxv+9uhFoTRC9C8LTeApVl1N7JJTTCzto6yhuaI423DzPPqDk10z4naL2mF9g/eNhUfxuMpA==
|
||||||
|
|
||||||
sqlstring@^2.3.2:
|
sqlstring@^2.3.2:
|
||||||
version "2.3.2"
|
version "2.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.2.tgz#cdae7169389a1375b18e885f2e60b3e460809514"
|
resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.2.tgz#cdae7169389a1375b18e885f2e60b3e460809514"
|
||||||
|
|||||||
Reference in New Issue
Block a user