mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-05-01 02:43:59 +00:00
Merge branch 'askpassword'
This commit is contained in:
@@ -21,6 +21,7 @@ module.exports = ({ editMenu }) => [
|
|||||||
{ divider: true },
|
{ divider: true },
|
||||||
{ command: 'file.exit', hideDisabled: true },
|
{ command: 'file.exit', hideDisabled: true },
|
||||||
{ command: 'app.logout', hideDisabled: true, skipInApp: true },
|
{ command: 'app.logout', hideDisabled: true, skipInApp: true },
|
||||||
|
{ command: 'app.disconnect', hideDisabled: true, skipInApp: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"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:api:auth": "yarn workspace dbgate-api start:auth",
|
"start:api:auth": "yarn workspace dbgate-api start:auth",
|
||||||
|
"start:api:dblogin": "yarn workspace dbgate-api start:dblogin",
|
||||||
"start:web": "yarn workspace dbgate-web dev",
|
"start:web": "yarn workspace dbgate-web dev",
|
||||||
"start:sqltree": "yarn workspace dbgate-sqltree start",
|
"start:sqltree": "yarn workspace dbgate-sqltree start",
|
||||||
"start:tools": "yarn workspace dbgate-tools start",
|
"start:tools": "yarn workspace dbgate-tools start",
|
||||||
|
|||||||
14
packages/api/env/dblogin/.env
vendored
Normal file
14
packages/api/env/dblogin/.env
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
DEVMODE=1
|
||||||
|
|
||||||
|
CONNECTIONS=mysql
|
||||||
|
SINGLE_CONNECTION=mysql
|
||||||
|
# SINGLE_DATABASE=Chinook
|
||||||
|
|
||||||
|
LABEL_mysql=MySql localhost
|
||||||
|
SERVER_mysql=localhost
|
||||||
|
# USER_mysql=root
|
||||||
|
PORT_mysql=3306
|
||||||
|
# PASSWORD_mysql=Pwd2020Db
|
||||||
|
ENGINE_mysql=mysql@dbgate-plugin-mysql
|
||||||
|
# PASSWORD_MODE_mysql=askPassword
|
||||||
|
PASSWORD_MODE_mysql=askUser
|
||||||
4
packages/api/env/singledb/.env
vendored
4
packages/api/env/singledb/.env
vendored
@@ -5,8 +5,8 @@ CONNECTIONS=mysql
|
|||||||
LABEL_mysql=MySql localhost
|
LABEL_mysql=MySql localhost
|
||||||
SERVER_mysql=localhost
|
SERVER_mysql=localhost
|
||||||
USER_mysql=root
|
USER_mysql=root
|
||||||
PASSWORD_mysql=test
|
PASSWORD_mysql=Pwd2020Db
|
||||||
PORT_mysql=3307
|
PORT_mysql=3306
|
||||||
ENGINE_mysql=mysql@dbgate-plugin-mysql
|
ENGINE_mysql=mysql@dbgate-plugin-mysql
|
||||||
DBCONFIG_mysql=[{"name":"Chinook","connectionColor":"cyan"}]
|
DBCONFIG_mysql=[{"name":"Chinook","connectionColor":"cyan"}]
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,7 @@
|
|||||||
"start:portal": "env-cmd -f env/portal/.env node src/index.js --listen-api",
|
"start:portal": "env-cmd -f env/portal/.env node src/index.js --listen-api",
|
||||||
"start:singledb": "env-cmd -f env/singledb/.env node src/index.js --listen-api",
|
"start:singledb": "env-cmd -f env/singledb/.env node src/index.js --listen-api",
|
||||||
"start:auth": "env-cmd -f env/auth/.env node src/index.js --listen-api",
|
"start:auth": "env-cmd -f env/auth/.env node src/index.js --listen-api",
|
||||||
|
"start:dblogin": "env-cmd -f env/dblogin/.env node src/index.js --listen-api",
|
||||||
"start:filedb": "env-cmd node src/index.js /home/jena/test/chinook/Chinook.db --listen-api",
|
"start:filedb": "env-cmd node src/index.js /home/jena/test/chinook/Chinook.db --listen-api",
|
||||||
"start:singleconn": "env-cmd node src/index.js --server localhost --user root --port 3307 --engine mysql@dbgate-plugin-mysql --password test --listen-api",
|
"start:singleconn": "env-cmd node src/index.js --server localhost --user root --port 3307 --engine mysql@dbgate-plugin-mysql --password test --listen-api",
|
||||||
"ts": "tsc",
|
"ts": "tsc",
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ module.exports = {
|
|||||||
|
|
||||||
refreshFiles_meta: true,
|
refreshFiles_meta: true,
|
||||||
async refreshFiles({ folder }) {
|
async refreshFiles({ folder }) {
|
||||||
socket.emitChanged(`app-files-changed-${folder}`);
|
socket.emitChanged('app-files-changed', { app: folder });
|
||||||
},
|
},
|
||||||
|
|
||||||
refreshFolders_meta: true,
|
refreshFolders_meta: true,
|
||||||
@@ -69,7 +69,7 @@ module.exports = {
|
|||||||
deleteFile_meta: true,
|
deleteFile_meta: true,
|
||||||
async deleteFile({ folder, file, fileType }) {
|
async deleteFile({ folder, file, fileType }) {
|
||||||
await fs.unlink(path.join(appdir(), folder, `${file}.${fileType}`));
|
await fs.unlink(path.join(appdir(), folder, `${file}.${fileType}`));
|
||||||
socket.emitChanged(`app-files-changed-${folder}`);
|
socket.emitChanged('app-files-changed', { app: folder });
|
||||||
this.emitChangedDbApp(folder);
|
this.emitChangedDbApp(folder);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ module.exports = {
|
|||||||
path.join(path.join(appdir(), folder), `${file}.${fileType}`),
|
path.join(path.join(appdir(), folder), `${file}.${fileType}`),
|
||||||
path.join(path.join(appdir(), folder), `${newFile}.${fileType}`)
|
path.join(path.join(appdir(), folder), `${newFile}.${fileType}`)
|
||||||
);
|
);
|
||||||
socket.emitChanged(`app-files-changed-${folder}`);
|
socket.emitChanged('app-files-changed', { app: folder });
|
||||||
this.emitChangedDbApp(folder);
|
this.emitChangedDbApp(folder);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ module.exports = {
|
|||||||
if (!folder) throw new Error('Missing folder parameter');
|
if (!folder) throw new Error('Missing folder parameter');
|
||||||
await fs.rmdir(path.join(appdir(), folder), { recursive: true });
|
await fs.rmdir(path.join(appdir(), folder), { recursive: true });
|
||||||
socket.emitChanged(`app-folders-changed`);
|
socket.emitChanged(`app-folders-changed`);
|
||||||
socket.emitChanged(`app-files-changed-${folder}`);
|
socket.emitChanged('app-files-changed', { app: folder });
|
||||||
socket.emitChanged('used-apps-changed');
|
socket.emitChanged('used-apps-changed');
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -219,7 +219,7 @@ module.exports = {
|
|||||||
|
|
||||||
await fs.writeFile(file, JSON.stringify(json, undefined, 2));
|
await fs.writeFile(file, JSON.stringify(json, undefined, 2));
|
||||||
|
|
||||||
socket.emitChanged(`app-files-changed-${appFolder}`);
|
socket.emitChanged('app-files-changed', { app: appFolder });
|
||||||
socket.emitChanged('used-apps-changed');
|
socket.emitChanged('used-apps-changed');
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -271,7 +271,7 @@ module.exports = {
|
|||||||
const file = path.join(appdir(), appFolder, fileName);
|
const file = path.join(appdir(), appFolder, fileName);
|
||||||
if (!(await fs.exists(file))) {
|
if (!(await fs.exists(file))) {
|
||||||
await fs.writeFile(file, JSON.stringify(content, undefined, 2));
|
await fs.writeFile(file, JSON.stringify(content, undefined, 2));
|
||||||
socket.emitChanged(`app-files-changed-${appFolder}`);
|
socket.emitChanged('app-files-changed', { app: appFolder });
|
||||||
socket.emitChanged('used-apps-changed');
|
socket.emitChanged('used-apps-changed');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ module.exports = {
|
|||||||
|
|
||||||
refreshFiles_meta: true,
|
refreshFiles_meta: true,
|
||||||
async refreshFiles({ folder }) {
|
async refreshFiles({ folder }) {
|
||||||
socket.emitChanged(`archive-files-changed-${folder}`);
|
socket.emitChanged('archive-files-changed', { folder });
|
||||||
},
|
},
|
||||||
|
|
||||||
refreshFolders_meta: true,
|
refreshFolders_meta: true,
|
||||||
@@ -86,7 +86,7 @@ module.exports = {
|
|||||||
deleteFile_meta: true,
|
deleteFile_meta: true,
|
||||||
async deleteFile({ folder, file, fileType }) {
|
async deleteFile({ folder, file, fileType }) {
|
||||||
await fs.unlink(path.join(resolveArchiveFolder(folder), `${file}.${fileType}`));
|
await fs.unlink(path.join(resolveArchiveFolder(folder), `${file}.${fileType}`));
|
||||||
socket.emitChanged(`archive-files-changed-${folder}`);
|
socket.emitChanged(`archive-files-changed`, { folder });
|
||||||
},
|
},
|
||||||
|
|
||||||
renameFile_meta: true,
|
renameFile_meta: true,
|
||||||
@@ -95,7 +95,7 @@ module.exports = {
|
|||||||
path.join(resolveArchiveFolder(folder), `${file}.${fileType}`),
|
path.join(resolveArchiveFolder(folder), `${file}.${fileType}`),
|
||||||
path.join(resolveArchiveFolder(folder), `${newFile}.${fileType}`)
|
path.join(resolveArchiveFolder(folder), `${newFile}.${fileType}`)
|
||||||
);
|
);
|
||||||
socket.emitChanged(`archive-files-changed-${folder}`);
|
socket.emitChanged(`archive-files-changed`, { folder });
|
||||||
},
|
},
|
||||||
|
|
||||||
renameFolder_meta: true,
|
renameFolder_meta: true,
|
||||||
@@ -119,7 +119,7 @@ module.exports = {
|
|||||||
saveFreeTable_meta: true,
|
saveFreeTable_meta: true,
|
||||||
async saveFreeTable({ folder, file, data }) {
|
async saveFreeTable({ folder, file, data }) {
|
||||||
await saveFreeTableData(path.join(resolveArchiveFolder(folder), `${file}.jsonl`), data);
|
await saveFreeTableData(path.join(resolveArchiveFolder(folder), `${file}.jsonl`), data);
|
||||||
socket.emitChanged(`archive-files-changed-${folder}`);
|
socket.emitChanged(`archive-files-changed`, { folder });
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ module.exports = {
|
|||||||
saveText_meta: true,
|
saveText_meta: true,
|
||||||
async saveText({ folder, file, text }) {
|
async saveText({ folder, file, text }) {
|
||||||
await fs.writeFile(path.join(resolveArchiveFolder(folder), `${file}.jsonl`), text);
|
await fs.writeFile(path.join(resolveArchiveFolder(folder), `${file}.jsonl`), text);
|
||||||
socket.emitChanged(`archive-files-changed-${folder}`);
|
socket.emitChanged(`archive-files-changed`, { folder });
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@ module.exports = {
|
|||||||
const source = getJslFileName(jslid);
|
const source = getJslFileName(jslid);
|
||||||
const target = path.join(resolveArchiveFolder(folder), `${file}.jsonl`);
|
const target = path.join(resolveArchiveFolder(folder), `${file}.jsonl`);
|
||||||
await fs.copyFile(source, target);
|
await fs.copyFile(source, target);
|
||||||
socket.emitChanged(`archive-files-changed-${folder}`);
|
socket.emitChanged(`archive-files-changed`, { folder });
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ module.exports = {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
runAsPortal: !!connections.portalConnections,
|
runAsPortal: !!connections.portalConnections,
|
||||||
singleDatabase: connections.singleDatabase,
|
singleDbConnection: connections.singleDbConnection,
|
||||||
|
singleConnection: connections.singleConnection,
|
||||||
// hideAppEditor: !!process.env.HIDE_APP_EDITOR,
|
// hideAppEditor: !!process.env.HIDE_APP_EDITOR,
|
||||||
allowShellConnection: platformInfo.allowShellConnection,
|
allowShellConnection: platformInfo.allowShellConnection,
|
||||||
allowShellScripting: platformInfo.allowShellScripting,
|
allowShellScripting: platformInfo.allowShellScripting,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ const path = require('path');
|
|||||||
const { fork } = require('child_process');
|
const { fork } = require('child_process');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
const { datadir, filesdir } = require('../utility/directories');
|
const { datadir, filesdir } = require('../utility/directories');
|
||||||
const socket = require('../utility/socket');
|
const socket = require('../utility/socket');
|
||||||
@@ -15,6 +16,8 @@ const { safeJsonParse } = require('dbgate-tools');
|
|||||||
const platformInfo = require('../utility/platformInfo');
|
const platformInfo = require('../utility/platformInfo');
|
||||||
const { connectionHasPermission, testConnectionPermission } = require('../utility/hasPermission');
|
const { connectionHasPermission, testConnectionPermission } = require('../utility/hasPermission');
|
||||||
|
|
||||||
|
let volatileConnections = {};
|
||||||
|
|
||||||
function getNamedArgs() {
|
function getNamedArgs() {
|
||||||
const res = {};
|
const res = {};
|
||||||
for (let i = 0; i < process.argv.length; i++) {
|
for (let i = 0; i < process.argv.length; i++) {
|
||||||
@@ -49,6 +52,7 @@ function getPortalCollections() {
|
|||||||
server: process.env[`SERVER_${id}`],
|
server: process.env[`SERVER_${id}`],
|
||||||
user: process.env[`USER_${id}`],
|
user: process.env[`USER_${id}`],
|
||||||
password: process.env[`PASSWORD_${id}`],
|
password: process.env[`PASSWORD_${id}`],
|
||||||
|
passwordMode: process.env[`PASSWORD_MODE_${id}`],
|
||||||
port: process.env[`PORT_${id}`],
|
port: process.env[`PORT_${id}`],
|
||||||
databaseUrl: process.env[`URL_${id}`],
|
databaseUrl: process.env[`URL_${id}`],
|
||||||
useDatabaseUrl: !!process.env[`URL_${id}`],
|
useDatabaseUrl: !!process.env[`URL_${id}`],
|
||||||
@@ -126,9 +130,10 @@ function getPortalCollections() {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const portalConnections = getPortalCollections();
|
const portalConnections = getPortalCollections();
|
||||||
|
|
||||||
function getSingleDatabase() {
|
function getSingleDbConnection() {
|
||||||
if (process.env.SINGLE_CONNECTION && process.env.SINGLE_DATABASE) {
|
if (process.env.SINGLE_CONNECTION && process.env.SINGLE_DATABASE) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const connection = portalConnections.find(x => x._id == process.env.SINGLE_CONNECTION);
|
const connection = portalConnections.find(x => x._id == process.env.SINGLE_CONNECTION);
|
||||||
@@ -152,12 +157,31 @@ function getSingleDatabase() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const singleDatabase = getSingleDatabase();
|
function getSingleConnection() {
|
||||||
|
if (getSingleDbConnection()) return null;
|
||||||
|
if (process.env.SINGLE_CONNECTION) {
|
||||||
|
// @ts-ignore
|
||||||
|
const connection = portalConnections.find(x => x._id == process.env.SINGLE_CONNECTION);
|
||||||
|
if (connection) {
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
const arg0 = (portalConnections || []).find(x => x._id == 'argv');
|
||||||
|
if (arg0) {
|
||||||
|
return arg0;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const singleDbConnection = getSingleDbConnection();
|
||||||
|
const singleConnection = getSingleConnection();
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
datastore: null,
|
datastore: null,
|
||||||
opened: [],
|
opened: [],
|
||||||
singleDatabase,
|
singleDbConnection,
|
||||||
|
singleConnection,
|
||||||
portalConnections,
|
portalConnections,
|
||||||
|
|
||||||
async _init() {
|
async _init() {
|
||||||
@@ -199,6 +223,36 @@ module.exports = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
saveVolatile_meta: true,
|
||||||
|
async saveVolatile({ conid, user, password, test }) {
|
||||||
|
const old = await this.getCore({ conid });
|
||||||
|
const res = {
|
||||||
|
...old,
|
||||||
|
_id: crypto.randomUUID(),
|
||||||
|
password,
|
||||||
|
passwordMode: undefined,
|
||||||
|
unsaved: true,
|
||||||
|
};
|
||||||
|
if (old.passwordMode == 'askUser') {
|
||||||
|
res.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (test) {
|
||||||
|
const testRes = await this.test(res);
|
||||||
|
if (testRes.msgtype == 'connected') {
|
||||||
|
volatileConnections[res._id] = res;
|
||||||
|
return {
|
||||||
|
...res,
|
||||||
|
msgtype: 'connected',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return testRes;
|
||||||
|
} else {
|
||||||
|
volatileConnections[res._id] = res;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
save_meta: true,
|
save_meta: true,
|
||||||
async save(connection) {
|
async save(connection) {
|
||||||
if (portalConnections) return;
|
if (portalConnections) return;
|
||||||
@@ -258,6 +312,10 @@ module.exports = {
|
|||||||
|
|
||||||
async getCore({ conid, mask = false }) {
|
async getCore({ conid, mask = false }) {
|
||||||
if (!conid) return null;
|
if (!conid) return null;
|
||||||
|
const volatile = volatileConnections[conid];
|
||||||
|
if (volatile) {
|
||||||
|
return volatile;
|
||||||
|
}
|
||||||
if (portalConnections) {
|
if (portalConnections) {
|
||||||
const res = portalConnections.find(x => x._id == conid) || null;
|
const res = portalConnections.find(x => x._id == conid) || null;
|
||||||
return mask && !platformInfo.allowShellConnection ? maskConnection(res) : res;
|
return mask && !platformInfo.allowShellConnection ? maskConnection(res) : res;
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const { createTwoFilesPatch } = require('diff');
|
|||||||
const diff2htmlPage = require('../utility/diff2htmlPage');
|
const diff2htmlPage = require('../utility/diff2htmlPage');
|
||||||
const processArgs = require('../utility/processArgs');
|
const processArgs = require('../utility/processArgs');
|
||||||
const { testConnectionPermission } = require('../utility/hasPermission');
|
const { testConnectionPermission } = require('../utility/hasPermission');
|
||||||
|
const { MissingCredentialsError } = require('../utility/exceptions');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
/** @type {import('dbgate-types').OpenedDatabaseConnection[]} */
|
/** @type {import('dbgate-types').OpenedDatabaseConnection[]} */
|
||||||
@@ -42,19 +43,19 @@ module.exports = {
|
|||||||
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
||||||
if (!existing) return;
|
if (!existing) return;
|
||||||
existing.structure = structure;
|
existing.structure = structure;
|
||||||
socket.emitChanged(`database-structure-changed-${conid}-${database}`);
|
socket.emitChanged('database-structure-changed', { conid, database });
|
||||||
},
|
},
|
||||||
handle_structureTime(conid, database, { analysedTime }) {
|
handle_structureTime(conid, database, { analysedTime }) {
|
||||||
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
||||||
if (!existing) return;
|
if (!existing) return;
|
||||||
existing.analysedTime = analysedTime;
|
existing.analysedTime = analysedTime;
|
||||||
socket.emitChanged(`database-status-changed-${conid}-${database}`);
|
socket.emitChanged(`database-status-changed`, { conid, database });
|
||||||
},
|
},
|
||||||
handle_version(conid, database, { version }) {
|
handle_version(conid, database, { version }) {
|
||||||
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
||||||
if (!existing) return;
|
if (!existing) return;
|
||||||
existing.serverVersion = version;
|
existing.serverVersion = version;
|
||||||
socket.emitChanged(`database-server-version-changed-${conid}-${database}`);
|
socket.emitChanged(`database-server-version-changed`, { conid, database });
|
||||||
},
|
},
|
||||||
|
|
||||||
handle_error(conid, database, props) {
|
handle_error(conid, database, props) {
|
||||||
@@ -72,7 +73,7 @@ module.exports = {
|
|||||||
if (!existing) return;
|
if (!existing) return;
|
||||||
if (existing.status && status && existing.status.counter > status.counter) return;
|
if (existing.status && status && existing.status.counter > status.counter) return;
|
||||||
existing.status = status;
|
existing.status = status;
|
||||||
socket.emitChanged(`database-status-changed-${conid}-${database}`);
|
socket.emitChanged(`database-status-changed`, { conid, database });
|
||||||
},
|
},
|
||||||
|
|
||||||
handle_ping() {},
|
handle_ping() {},
|
||||||
@@ -81,6 +82,9 @@ module.exports = {
|
|||||||
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
||||||
if (existing) return existing;
|
if (existing) return existing;
|
||||||
const connection = await connections.getCore({ conid });
|
const connection = await connections.getCore({ conid });
|
||||||
|
if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
|
||||||
|
throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
|
||||||
|
}
|
||||||
const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
|
const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
|
||||||
'--is-forked-api',
|
'--is-forked-api',
|
||||||
'--start-process',
|
'--start-process',
|
||||||
@@ -313,7 +317,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
structure: existing.structure,
|
structure: existing.structure,
|
||||||
};
|
};
|
||||||
socket.emitChanged(`database-status-changed-${conid}-${database}`);
|
socket.emitChanged(`database-status-changed`, { conid, database });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ module.exports = {
|
|||||||
async delete({ folder, file }, req) {
|
async delete({ folder, file }, req) {
|
||||||
if (!hasPermission(`files/${folder}/write`, req)) return false;
|
if (!hasPermission(`files/${folder}/write`, req)) return false;
|
||||||
await fs.unlink(path.join(filesdir(), folder, file));
|
await fs.unlink(path.join(filesdir(), folder, file));
|
||||||
socket.emitChanged(`files-changed-${folder}`);
|
socket.emitChanged(`files-changed`, { folder });
|
||||||
socket.emitChanged(`all-files-changed`);
|
socket.emitChanged(`all-files-changed`);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@@ -58,7 +58,7 @@ module.exports = {
|
|||||||
async rename({ folder, file, newFile }, req) {
|
async rename({ folder, file, newFile }, req) {
|
||||||
if (!hasPermission(`files/${folder}/write`, req)) return false;
|
if (!hasPermission(`files/${folder}/write`, req)) return false;
|
||||||
await fs.rename(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile));
|
await fs.rename(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile));
|
||||||
socket.emitChanged(`files-changed-${folder}`);
|
socket.emitChanged(`files-changed`, { folder });
|
||||||
socket.emitChanged(`all-files-changed`);
|
socket.emitChanged(`all-files-changed`);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@@ -66,7 +66,7 @@ module.exports = {
|
|||||||
refresh_meta: true,
|
refresh_meta: true,
|
||||||
async refresh({ folders }, req) {
|
async refresh({ folders }, req) {
|
||||||
for (const folder of folders) {
|
for (const folder of folders) {
|
||||||
socket.emitChanged(`files-changed-${folder}`);
|
socket.emitChanged(`files-changed`, { folder });
|
||||||
socket.emitChanged(`all-files-changed`);
|
socket.emitChanged(`all-files-changed`);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -76,7 +76,7 @@ module.exports = {
|
|||||||
async copy({ folder, file, newFile }, req) {
|
async copy({ folder, file, newFile }, req) {
|
||||||
if (!hasPermission(`files/${folder}/write`, req)) return false;
|
if (!hasPermission(`files/${folder}/write`, req)) return false;
|
||||||
await fs.copyFile(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile));
|
await fs.copyFile(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile));
|
||||||
socket.emitChanged(`files-changed-${folder}`);
|
socket.emitChanged(`files-changed`, { folder });
|
||||||
socket.emitChanged(`all-files-changed`);
|
socket.emitChanged(`all-files-changed`);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@@ -112,13 +112,13 @@ module.exports = {
|
|||||||
if (!hasPermission(`archive/write`, req)) return false;
|
if (!hasPermission(`archive/write`, req)) return false;
|
||||||
const dir = resolveArchiveFolder(folder.substring('archive:'.length));
|
const dir = resolveArchiveFolder(folder.substring('archive:'.length));
|
||||||
await fs.writeFile(path.join(dir, file), serialize(format, data));
|
await fs.writeFile(path.join(dir, file), serialize(format, data));
|
||||||
socket.emitChanged(`archive-files-changed-${folder.substring('archive:'.length)}`);
|
socket.emitChanged(`archive-files-changed`, { folder: folder.substring('archive:'.length) });
|
||||||
return true;
|
return true;
|
||||||
} else if (folder.startsWith('app:')) {
|
} else if (folder.startsWith('app:')) {
|
||||||
if (!hasPermission(`apps/write`, req)) return false;
|
if (!hasPermission(`apps/write`, req)) return false;
|
||||||
const app = folder.substring('app:'.length);
|
const app = folder.substring('app:'.length);
|
||||||
await fs.writeFile(path.join(appdir(), app, file), serialize(format, data));
|
await fs.writeFile(path.join(appdir(), app, file), serialize(format, data));
|
||||||
socket.emitChanged(`app-files-changed-${app}`);
|
socket.emitChanged(`app-files-changed`, { app });
|
||||||
socket.emitChanged('used-apps-changed');
|
socket.emitChanged('used-apps-changed');
|
||||||
apps.emitChangedDbApp(folder);
|
apps.emitChangedDbApp(folder);
|
||||||
return true;
|
return true;
|
||||||
@@ -129,7 +129,7 @@ module.exports = {
|
|||||||
await fs.mkdir(dir);
|
await fs.mkdir(dir);
|
||||||
}
|
}
|
||||||
await fs.writeFile(path.join(dir, file), serialize(format, data));
|
await fs.writeFile(path.join(dir, file), serialize(format, data));
|
||||||
socket.emitChanged(`files-changed-${folder}`);
|
socket.emitChanged(`files-changed`, { folder });
|
||||||
socket.emitChanged(`all-files-changed`);
|
socket.emitChanged(`all-files-changed`);
|
||||||
if (folder == 'shell') {
|
if (folder == 'shell') {
|
||||||
scheduler.reload();
|
scheduler.reload();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const lock = new AsyncLock();
|
|||||||
const config = require('./config');
|
const config = require('./config');
|
||||||
const processArgs = require('../utility/processArgs');
|
const processArgs = require('../utility/processArgs');
|
||||||
const { testConnectionPermission } = require('../utility/hasPermission');
|
const { testConnectionPermission } = require('../utility/hasPermission');
|
||||||
|
const { MissingCredentialsError } = require('../utility/exceptions');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
opened: [],
|
opened: [],
|
||||||
@@ -20,13 +21,13 @@ module.exports = {
|
|||||||
const existing = this.opened.find(x => x.conid == conid);
|
const existing = this.opened.find(x => x.conid == conid);
|
||||||
if (!existing) return;
|
if (!existing) return;
|
||||||
existing.databases = databases;
|
existing.databases = databases;
|
||||||
socket.emitChanged(`database-list-changed-${conid}`);
|
socket.emitChanged(`database-list-changed`, { conid });
|
||||||
},
|
},
|
||||||
handle_version(conid, { version }) {
|
handle_version(conid, { version }) {
|
||||||
const existing = this.opened.find(x => x.conid == conid);
|
const existing = this.opened.find(x => x.conid == conid);
|
||||||
if (!existing) return;
|
if (!existing) return;
|
||||||
existing.version = version;
|
existing.version = version;
|
||||||
socket.emitChanged(`server-version-changed-${conid}`);
|
socket.emitChanged(`server-version-changed`, { conid });
|
||||||
},
|
},
|
||||||
handle_status(conid, { status }) {
|
handle_status(conid, { status }) {
|
||||||
const existing = this.opened.find(x => x.conid == conid);
|
const existing = this.opened.find(x => x.conid == conid);
|
||||||
@@ -46,6 +47,9 @@ module.exports = {
|
|||||||
const existing = this.opened.find(x => x.conid == conid);
|
const existing = this.opened.find(x => x.conid == conid);
|
||||||
if (existing) return existing;
|
if (existing) return existing;
|
||||||
const connection = await connections.getCore({ conid });
|
const connection = await connections.getCore({ conid });
|
||||||
|
if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
|
||||||
|
throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
|
||||||
|
}
|
||||||
const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
|
const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
|
||||||
'--is-forked-api',
|
'--is-forked-api',
|
||||||
'--start-process',
|
'--start-process',
|
||||||
@@ -127,9 +131,9 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
ping_meta: true,
|
ping_meta: true,
|
||||||
async ping({ connections }) {
|
async ping({ conidArray }) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
_.uniq(connections).map(async conid => {
|
_.uniq(conidArray).map(async conid => {
|
||||||
const last = this.lastPinged[conid];
|
const last = this.lastPinged[conid];
|
||||||
if (last && new Date().getTime() - last < 30 * 1000) {
|
if (last && new Date().getTime() - last < 30 * 1000) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
|||||||
9
packages/api/src/utility/exceptions.js
Normal file
9
packages/api/src/utility/exceptions.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
class MissingCredentialsError {
|
||||||
|
constructor(detail) {
|
||||||
|
this.detail = detail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
MissingCredentialsError,
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
const stableStringify = require('json-stable-stringify');
|
||||||
|
|
||||||
const sseResponses = [];
|
const sseResponses = [];
|
||||||
let electronSender = null;
|
let electronSender = null;
|
||||||
@@ -27,12 +28,12 @@ module.exports = {
|
|||||||
electronSender.send(message, data == null ? null : data);
|
electronSender.send(message, data == null ? null : data);
|
||||||
}
|
}
|
||||||
for (const res of sseResponses) {
|
for (const res of sseResponses) {
|
||||||
res.write(`event: ${message}\ndata: ${JSON.stringify(data == null ? null : data)}\n\n`);
|
res.write(`event: ${message}\ndata: ${stableStringify(data == null ? null : data)}\n\n`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emitChanged(key) {
|
emitChanged(key, params = undefined) {
|
||||||
// console.log('EMIT CHANGED', key);
|
// console.log('EMIT CHANGED', key);
|
||||||
this.emit('changed-cache', key);
|
this.emit('changed-cache', { key, ...params });
|
||||||
// this.emit(key);
|
// this.emit(key);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const getExpressPath = require('./getExpressPath');
|
const getExpressPath = require('./getExpressPath');
|
||||||
|
const { MissingCredentialsError } = require('./exceptions');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} route
|
* @param {string} route
|
||||||
@@ -37,6 +38,13 @@ module.exports = function useController(app, electron, route, controller) {
|
|||||||
if (data === undefined) return null;
|
if (data === undefined) return null;
|
||||||
return data;
|
return data;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (err instanceof MissingCredentialsError) {
|
||||||
|
return {
|
||||||
|
missingCredentials: true,
|
||||||
|
apiErrorMessage: 'Missing credentials',
|
||||||
|
detail: err.detail,
|
||||||
|
};
|
||||||
|
}
|
||||||
return { apiErrorMessage: err.message };
|
return { apiErrorMessage: err.message };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -69,7 +77,15 @@ module.exports = function useController(app, electron, route, controller) {
|
|||||||
res.json(data);
|
res.json(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
res.status(500).json({ apiErrorMessage: e.message });
|
if (e instanceof MissingCredentialsError) {
|
||||||
|
res.json({
|
||||||
|
missingCredentials: true,
|
||||||
|
apiErrorMessage: 'Missing credentials',
|
||||||
|
detail: e.detail,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ apiErrorMessage: e.message });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
const electron = getElectron();
|
const electron = getElectron();
|
||||||
const currentDb = getCurrentDatabase();
|
const currentDb = getCurrentDatabase();
|
||||||
openedConnections.update(list => list.filter(x => x != conid));
|
openedConnections.update(list => list.filter(x => x != conid));
|
||||||
|
removeVolatileMapping(conid);
|
||||||
if (electron) {
|
if (electron) {
|
||||||
apiCall('server-connections/disconnect', { conid });
|
apiCall('server-connections/disconnect', { conid });
|
||||||
}
|
}
|
||||||
@@ -100,7 +101,7 @@
|
|||||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||||
import { getDatabaseList, useUsedApps } from '../utility/metadataLoaders';
|
import { getDatabaseList, useUsedApps } from '../utility/metadataLoaders';
|
||||||
import { getLocalStorage } from '../utility/storageCache';
|
import { getLocalStorage } from '../utility/storageCache';
|
||||||
import { apiCall } from '../utility/api';
|
import { apiCall, removeVolatileMapping } from '../utility/api';
|
||||||
import ImportDatabaseDumpModal from '../modals/ImportDatabaseDumpModal.svelte';
|
import ImportDatabaseDumpModal from '../modals/ImportDatabaseDumpModal.svelte';
|
||||||
import { closeMultipleTabs } from '../widgets/TabsPanel.svelte';
|
import { closeMultipleTabs } from '../widgets/TabsPanel.svelte';
|
||||||
import AboutModal from '../modals/AboutModal.svelte';
|
import AboutModal from '../modals/AboutModal.svelte';
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
for (const connection of connectionList || []) {
|
for (const connection of connectionList || []) {
|
||||||
const conid = connection._id;
|
const conid = connection._id;
|
||||||
if (connection.singleDatabase) continue;
|
if (connection.singleDatabase) continue;
|
||||||
if (getCurrentConfig()?.singleDatabase) continue;
|
if (getCurrentConfig()?.singleDbConnection) continue;
|
||||||
const databases = getLocalStorage(`database_list_${conid}`) || [];
|
const databases = getLocalStorage(`database_list_${conid}`) || [];
|
||||||
for (const db of databases) {
|
for (const db of databases) {
|
||||||
databaseList.push({
|
databaseList.push({
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import { openWebLink } from '../utility/exportFileTools';
|
|||||||
import { getSettings } from '../utility/metadataLoaders';
|
import { getSettings } from '../utility/metadataLoaders';
|
||||||
import { isMac } from '../utility/common';
|
import { isMac } from '../utility/common';
|
||||||
import { doLogout, internalRedirectTo } from '../clientAuth';
|
import { doLogout, internalRedirectTo } from '../clientAuth';
|
||||||
|
import { disconnectServerConnection } from '../appobj/ConnectionAppObject.svelte';
|
||||||
|
|
||||||
// function themeCommand(theme: ThemeDefinition) {
|
// function themeCommand(theme: ThemeDefinition) {
|
||||||
// return {
|
// return {
|
||||||
@@ -552,6 +553,14 @@ registerCommand({
|
|||||||
onClick: doLogout,
|
onClick: doLogout,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
registerCommand({
|
||||||
|
id: 'app.disconnect',
|
||||||
|
category: 'App',
|
||||||
|
name: 'Disconnect',
|
||||||
|
testEnabled: () => getCurrentConfig()?.singleConnection != null,
|
||||||
|
onClick: () => disconnectServerConnection(getCurrentConfig()?.singleConnection?._id),
|
||||||
|
});
|
||||||
|
|
||||||
export function registerFileCommands({
|
export function registerFileCommands({
|
||||||
idPrefix,
|
idPrefix,
|
||||||
category,
|
category,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
export let name;
|
export let name;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
|
export let saveOnInput = false;
|
||||||
|
|
||||||
const { values, setFieldValue } = getFormContext();
|
const { values, setFieldValue } = getFormContext();
|
||||||
|
|
||||||
@@ -23,6 +24,11 @@
|
|||||||
{disabled}
|
{disabled}
|
||||||
value={isCrypted ? '' : value}
|
value={isCrypted ? '' : value}
|
||||||
on:change={e => setFieldValue(name, e.target['value'])}
|
on:change={e => setFieldValue(name, e.target['value'])}
|
||||||
|
on:input={e => {
|
||||||
|
if (saveOnInput) {
|
||||||
|
setFieldValue(name, e.target['value']);
|
||||||
|
}
|
||||||
|
}}
|
||||||
placeholder={isCrypted ? '(Password is encrypted)' : undefined}
|
placeholder={isCrypted ? '(Password is encrypted)' : undefined}
|
||||||
type={isCrypted || showPassword ? 'text' : 'password'}
|
type={isCrypted || showPassword ? 'text' : 'password'}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
export let name;
|
export let name;
|
||||||
export let defaultValue;
|
export let defaultValue;
|
||||||
|
export let saveOnInput = false;
|
||||||
|
|
||||||
const { values, setFieldValue } = getFormContext();
|
const { values, setFieldValue } = getFormContext();
|
||||||
</script>
|
</script>
|
||||||
@@ -12,4 +13,9 @@
|
|||||||
{...$$restProps}
|
{...$$restProps}
|
||||||
value={$values[name] ?? defaultValue}
|
value={$values[name] ?? defaultValue}
|
||||||
on:input={e => setFieldValue(name, e.target['value'])}
|
on:input={e => setFieldValue(name, e.target['value'])}
|
||||||
|
on:input={e => {
|
||||||
|
if (saveOnInput) {
|
||||||
|
setFieldValue(name, e.target['value']);
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -40,8 +40,8 @@
|
|||||||
'icon columns': 'mdi mdi-view-column',
|
'icon columns': 'mdi mdi-view-column',
|
||||||
'icon columns-outline': 'mdi mdi-view-column-outline',
|
'icon columns-outline': 'mdi mdi-view-column-outline',
|
||||||
|
|
||||||
'icon single-database-mode': 'mdi mdi-database-lock',
|
'icon locked-database-mode': 'mdi mdi-database-lock',
|
||||||
'icon multi-database-mode': 'mdi mdi-database-eye',
|
'icon unlocked-database-mode': 'mdi mdi-database-eye',
|
||||||
|
|
||||||
'icon database': 'mdi mdi-database',
|
'icon database': 'mdi mdi-database',
|
||||||
'icon server': 'mdi mdi-server',
|
'icon server': 'mdi mdi-server',
|
||||||
|
|||||||
137
packages/web/src/modals/DatabaseLoginModal.svelte
Normal file
137
packages/web/src/modals/DatabaseLoginModal.svelte
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
<script lang="ts" context="module">
|
||||||
|
let currentModalConid = null;
|
||||||
|
|
||||||
|
export function isDatabaseLoginVisible() {
|
||||||
|
return !!currentModalConid;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { onDestroy, onMount } from 'svelte';
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
import FormStyledButton from '../buttons/FormStyledButton.svelte';
|
||||||
|
import Link from '../elements/Link.svelte';
|
||||||
|
import FormPasswordField from '../forms/FormPasswordField.svelte';
|
||||||
|
import FormProviderCore from '../forms/FormProviderCore.svelte';
|
||||||
|
import FormSubmit from '../forms/FormSubmit.svelte';
|
||||||
|
import FormTextField from '../forms/FormTextField.svelte';
|
||||||
|
import FontIcon from '../icons/FontIcon.svelte';
|
||||||
|
import { apiCall, setVolatileConnectionRemapping } from '../utility/api';
|
||||||
|
import { batchDispatchCacheTriggers, dispatchCacheChange } from '../utility/cache';
|
||||||
|
import createRef from '../utility/createRef';
|
||||||
|
|
||||||
|
import { getConnectionInfo } from '../utility/metadataLoaders';
|
||||||
|
import ErrorMessageModal from './ErrorMessageModal.svelte';
|
||||||
|
import ModalBase from './ModalBase.svelte';
|
||||||
|
import { closeCurrentModal, showModal } from './modalTools';
|
||||||
|
|
||||||
|
export let conid;
|
||||||
|
export let passwordMode;
|
||||||
|
|
||||||
|
const values = writable({});
|
||||||
|
let connection;
|
||||||
|
|
||||||
|
let isTesting;
|
||||||
|
let sqlConnectResult;
|
||||||
|
const testIdRef = createRef(0);
|
||||||
|
|
||||||
|
currentModalConid = conid;
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
connection = await getConnectionInfo({ conid });
|
||||||
|
if (passwordMode == 'askPassword') {
|
||||||
|
$values = {
|
||||||
|
...$values,
|
||||||
|
user: connection.user,
|
||||||
|
server: connection.server,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
currentModalConid = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleCancelTest() {
|
||||||
|
testIdRef.update(x => x + 1); // invalidate current test
|
||||||
|
isTesting = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSubmit(ev) {
|
||||||
|
isTesting = true;
|
||||||
|
testIdRef.update(x => x + 1);
|
||||||
|
const testid = testIdRef.get();
|
||||||
|
const resp = await apiCall('connections/save-volatile', {
|
||||||
|
conid,
|
||||||
|
user: $values['user'],
|
||||||
|
password: $values['password'],
|
||||||
|
test: true,
|
||||||
|
});
|
||||||
|
if (testIdRef.get() != testid) return;
|
||||||
|
isTesting = false;
|
||||||
|
if (resp.msgtype == 'connected') {
|
||||||
|
setVolatileConnectionRemapping(conid, resp._id);
|
||||||
|
dispatchCacheChange({ key: `server-status-changed` });
|
||||||
|
batchDispatchCacheTriggers(x => x.conid == conid);
|
||||||
|
closeCurrentModal();
|
||||||
|
} else {
|
||||||
|
sqlConnectResult = resp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<FormProviderCore {values}>
|
||||||
|
<ModalBase {...$$restProps} simple>
|
||||||
|
<svelte:fragment slot="header">Database Log In</svelte:fragment>
|
||||||
|
|
||||||
|
<FormTextField label="Server" name="server" disabled />
|
||||||
|
<FormTextField
|
||||||
|
label="Username"
|
||||||
|
name="user"
|
||||||
|
autocomplete="username"
|
||||||
|
disabled={passwordMode == 'askPassword'}
|
||||||
|
focused={passwordMode == 'askUser'}
|
||||||
|
saveOnInput
|
||||||
|
/>
|
||||||
|
<FormPasswordField
|
||||||
|
label="Password"
|
||||||
|
name="password"
|
||||||
|
autocomplete="current-password"
|
||||||
|
focused={passwordMode == 'askPassword'}
|
||||||
|
saveOnInput
|
||||||
|
/>
|
||||||
|
|
||||||
|
{#if isTesting}
|
||||||
|
<div>
|
||||||
|
<FontIcon icon="icon loading" /> Testing connection
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if !isTesting && sqlConnectResult && sqlConnectResult.msgtype == 'error'}
|
||||||
|
<div class="error-result">
|
||||||
|
Connect failed: <FontIcon icon="img error" />
|
||||||
|
{sqlConnectResult.error}
|
||||||
|
<Link
|
||||||
|
onClick={() =>
|
||||||
|
showModal(ErrorMessageModal, {
|
||||||
|
message: sqlConnectResult.detail,
|
||||||
|
showAsCode: true,
|
||||||
|
title: 'Database connection error',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Show detail
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<svelte:fragment slot="footer">
|
||||||
|
{#if isTesting}
|
||||||
|
<FormStyledButton value="Stop connecting" on:click={handleCancelTest} />
|
||||||
|
{:else}
|
||||||
|
<FormSubmit value="Connect" on:click={handleSubmit} />
|
||||||
|
{/if}
|
||||||
|
<FormStyledButton value="Close" on:click={closeCurrentModal} />
|
||||||
|
</svelte:fragment>
|
||||||
|
</ModalBase>
|
||||||
|
</FormProviderCore>
|
||||||
@@ -28,8 +28,12 @@
|
|||||||
$: driver = $extensions.drivers.find(x => x.engine == engine);
|
$: driver = $extensions.drivers.find(x => x.engine == engine);
|
||||||
$: defaultDatabase = $values.defaultDatabase;
|
$: defaultDatabase = $values.defaultDatabase;
|
||||||
|
|
||||||
$: showUser = driver?.showConnectionField('user', $values);
|
$: showUser = driver?.showConnectionField('user', $values) && $values.passwordMode != 'askUser';
|
||||||
$: showPassword = driver?.showConnectionField('password', $values);
|
$: showPassword =
|
||||||
|
driver?.showConnectionField('password', $values) &&
|
||||||
|
$values.passwordMode != 'askPassword' &&
|
||||||
|
$values.passwordMode != 'askUser';
|
||||||
|
$: showPasswordMode = driver?.showConnectionField('password', $values);
|
||||||
$: isConnected = $openedConnections.includes($values._id) || $openedSingleDatabaseConnections.includes($values._id);
|
$: isConnected = $openedConnections.includes($values._id) || $openedSingleDatabaseConnections.includes($values._id);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -159,7 +163,7 @@
|
|||||||
<FormPasswordField label="Password" name="password" disabled={isConnected || disabledFields.includes('password')} />
|
<FormPasswordField label="Password" name="password" disabled={isConnected || disabledFields.includes('password')} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if !disabledFields.includes('password') && showPassword}
|
{#if !disabledFields.includes('password') && showPasswordMode}
|
||||||
<FormSelectField
|
<FormSelectField
|
||||||
label="Password mode"
|
label="Password mode"
|
||||||
isNative
|
isNative
|
||||||
@@ -169,6 +173,8 @@
|
|||||||
options={[
|
options={[
|
||||||
{ value: 'saveEncrypted', label: 'Save and encrypt' },
|
{ value: 'saveEncrypted', label: 'Save and encrypt' },
|
||||||
{ value: 'saveRaw', label: 'Save raw (UNSAFE!!)' },
|
{ value: 'saveRaw', label: 'Save raw (UNSAFE!!)' },
|
||||||
|
{ value: 'askPassword', label: "Don't save, ask for password" },
|
||||||
|
{ value: 'askUser', label: "Don't save, ask for login and password" },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
currentEditorTheme,
|
currentEditorTheme,
|
||||||
extensions,
|
extensions,
|
||||||
selectedWidget,
|
selectedWidget,
|
||||||
singleDatabaseMode,
|
lockedDatabaseMode,
|
||||||
visibleWidgetSideBar,
|
visibleWidgetSideBar,
|
||||||
} from '../stores';
|
} from '../stores';
|
||||||
import { isMac } from '../utility/common';
|
import { isMac } from '../utility/common';
|
||||||
@@ -115,11 +115,11 @@ ORDER BY
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
labelProps={{
|
labelProps={{
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
$singleDatabaseMode = !$singleDatabaseMode;
|
$lockedDatabaseMode = !$lockedDatabaseMode;
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CheckboxField checked={$singleDatabaseMode} on:change={e => ($singleDatabaseMode = e.target.checked)} />
|
<CheckboxField checked={$lockedDatabaseMode} on:change={e => ($lockedDatabaseMode = e.target.checked)} />
|
||||||
</FormFieldTemplateLarge>
|
</FormFieldTemplateLarge>
|
||||||
|
|
||||||
<FormCheckboxField
|
<FormCheckboxField
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ function subscribeCssVariable(store, transform, cssVariable) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const selectedWidget = writableWithStorage('database', 'selectedWidget');
|
export const selectedWidget = writableWithStorage('database', 'selectedWidget');
|
||||||
export const singleDatabaseMode = writableWithStorage<boolean>(false, 'singleDatabaseMode');
|
export const lockedDatabaseMode = writableWithStorage<boolean>(false, 'lockedDatabaseMode');
|
||||||
export const visibleWidgetSideBar = writableWithStorage(true, 'visibleWidgetSideBar');
|
export const visibleWidgetSideBar = writableWithStorage(true, 'visibleWidgetSideBar');
|
||||||
export const visibleSelectedWidget = derived(
|
export const visibleSelectedWidget = derived(
|
||||||
[selectedWidget, visibleWidgetSideBar],
|
[selectedWidget, visibleWidgetSideBar],
|
||||||
@@ -138,7 +138,7 @@ subscribeCssVariable(visibleSelectedWidget, x => (x ? 1 : 0), '--dim-visible-lef
|
|||||||
// subscribeCssVariable(visibleToolbar, x => (x ? 1 : 0), '--dim-visible-toolbar');
|
// subscribeCssVariable(visibleToolbar, x => (x ? 1 : 0), '--dim-visible-toolbar');
|
||||||
subscribeCssVariable(leftPanelWidth, x => `${x}px`, '--dim-left-panel-width');
|
subscribeCssVariable(leftPanelWidth, x => `${x}px`, '--dim-left-panel-width');
|
||||||
subscribeCssVariable(visibleTitleBar, x => (x ? 1 : 0), '--dim-visible-titlebar');
|
subscribeCssVariable(visibleTitleBar, x => (x ? 1 : 0), '--dim-visible-titlebar');
|
||||||
subscribeCssVariable(singleDatabaseMode, x => (x ? 0 : 1), '--dim-visible-tabs-databases');
|
subscribeCssVariable(lockedDatabaseMode, x => (x ? 0 : 1), '--dim-visible-tabs-databases');
|
||||||
|
|
||||||
let activeTabIdValue = null;
|
let activeTabIdValue = null;
|
||||||
activeTabId.subscribe(value => {
|
activeTabId.subscribe(value => {
|
||||||
@@ -200,11 +200,11 @@ pinnedDatabases.subscribe(value => {
|
|||||||
});
|
});
|
||||||
export const getPinnedDatabases = () => _.compact(pinnedDatabasesValue);
|
export const getPinnedDatabases = () => _.compact(pinnedDatabasesValue);
|
||||||
|
|
||||||
let singleDatabaseModeValue = null;
|
let lockedDatabaseModeValue = null;
|
||||||
singleDatabaseMode.subscribe(value => {
|
lockedDatabaseMode.subscribe(value => {
|
||||||
singleDatabaseModeValue = value;
|
lockedDatabaseModeValue = value;
|
||||||
});
|
});
|
||||||
export const getSingleDatabaseMode = () => singleDatabaseModeValue;
|
export const getLockedDatabaseMode = () => lockedDatabaseModeValue;
|
||||||
|
|
||||||
let currentDatabaseValue = null;
|
let currentDatabaseValue = null;
|
||||||
currentDatabase.subscribe(value => {
|
currentDatabase.subscribe(value => {
|
||||||
@@ -246,8 +246,8 @@ export function subscribeApiDependendStores() {
|
|||||||
useConfig().subscribe(value => {
|
useConfig().subscribe(value => {
|
||||||
currentConfigValue = value;
|
currentConfigValue = value;
|
||||||
invalidateCommands();
|
invalidateCommands();
|
||||||
if (value.singleDatabase) {
|
if (value.singleDbConnection) {
|
||||||
currentDatabase.set(value.singleDatabase);
|
currentDatabase.set(value.singleDbConnection);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import getElectron from './getElectron';
|
|||||||
// import socket from './socket';
|
// import socket from './socket';
|
||||||
import { showSnackbarError } from '../utility/snackbar';
|
import { showSnackbarError } from '../utility/snackbar';
|
||||||
import { isOauthCallback, redirectToLogin } from '../clientAuth';
|
import { isOauthCallback, redirectToLogin } from '../clientAuth';
|
||||||
|
import { showModal } from '../modals/modalTools';
|
||||||
|
import DatabaseLoginModal, { isDatabaseLoginVisible } from '../modals/DatabaseLoginModal.svelte';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
let eventSource;
|
let eventSource;
|
||||||
let apiLogging = false;
|
let apiLogging = false;
|
||||||
@@ -12,6 +15,9 @@ let apiLogging = false;
|
|||||||
let apiDisabled = false;
|
let apiDisabled = false;
|
||||||
const disabledOnOauth = isOauthCallback();
|
const disabledOnOauth = isOauthCallback();
|
||||||
|
|
||||||
|
const volatileConnectionMap = {};
|
||||||
|
const volatileConnectionMapInv = {};
|
||||||
|
|
||||||
export function disableApi() {
|
export function disableApi() {
|
||||||
apiDisabled = true;
|
apiDisabled = true;
|
||||||
}
|
}
|
||||||
@@ -20,6 +26,27 @@ export function enableApi() {
|
|||||||
apiDisabled = false;
|
apiDisabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setVolatileConnectionRemapping(existingConnectionId, volatileConnectionId) {
|
||||||
|
volatileConnectionMap[existingConnectionId] = volatileConnectionId;
|
||||||
|
volatileConnectionMapInv[volatileConnectionId] = existingConnectionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getVolatileRemapping(conid) {
|
||||||
|
return volatileConnectionMap[conid] || conid;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getVolatileRemappingInv(conid) {
|
||||||
|
return volatileConnectionMapInv[conid] || conid;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeVolatileMapping(conid) {
|
||||||
|
const mapped = volatileConnectionMap[conid];
|
||||||
|
if (mapped) {
|
||||||
|
delete volatileConnectionMap[conid];
|
||||||
|
delete volatileConnectionMapInv[mapped];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function wantEventSource() {
|
function wantEventSource() {
|
||||||
if (!eventSource) {
|
if (!eventSource) {
|
||||||
eventSource = new EventSource(`${resolveApi()}/stream`);
|
eventSource = new EventSource(`${resolveApi()}/stream`);
|
||||||
@@ -32,7 +59,16 @@ function processApiResponse(route, args, resp) {
|
|||||||
// console.log('<<< API RESPONSE', route, args, resp);
|
// console.log('<<< API RESPONSE', route, args, resp);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if (resp?.apiErrorMessage) {
|
if (resp?.missingCredentials) {
|
||||||
|
if (!isDatabaseLoginVisible()) {
|
||||||
|
showModal(DatabaseLoginModal, resp.detail);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
// return {
|
||||||
|
// errorMessage: resp.apiErrorMessage,
|
||||||
|
// missingCredentials: true,
|
||||||
|
// };
|
||||||
|
} else if (resp?.apiErrorMessage) {
|
||||||
showSnackbarError('API error:' + resp?.apiErrorMessage);
|
showSnackbarError('API error:' + resp?.apiErrorMessage);
|
||||||
return {
|
return {
|
||||||
errorMessage: resp.apiErrorMessage,
|
errorMessage: resp.apiErrorMessage,
|
||||||
@@ -42,6 +78,22 @@ function processApiResponse(route, args, resp) {
|
|||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function transformApiArgs(args) {
|
||||||
|
return _.mapValues(args, (v, k) => {
|
||||||
|
if (k == 'conid' && v && volatileConnectionMap[v]) return volatileConnectionMap[v];
|
||||||
|
if (k == 'conidArray' && _.isArray(v)) return v.map(x => volatileConnectionMap[x] || x);
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformApiArgsInv(args) {
|
||||||
|
return _.mapValues(args, (v, k) => {
|
||||||
|
if (k == 'conid' && v && volatileConnectionMapInv[v]) return volatileConnectionMapInv[v];
|
||||||
|
if (k == 'conidArray' && _.isArray(v)) return v.map(x => volatileConnectionMapInv[x] || x);
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function apiCall(route: string, args: {} = undefined) {
|
export async function apiCall(route: string, args: {} = undefined) {
|
||||||
if (apiLogging) {
|
if (apiLogging) {
|
||||||
console.log('>>> API CALL', route, args);
|
console.log('>>> API CALL', route, args);
|
||||||
@@ -55,6 +107,8 @@ export async function apiCall(route: string, args: {} = undefined) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
args = transformApiArgs(args);
|
||||||
|
|
||||||
const electron = getElectron();
|
const electron = getElectron();
|
||||||
if (electron) {
|
if (electron) {
|
||||||
const resp = await electron.invoke(route.replace('/', '-'), args);
|
const resp = await electron.invoke(route.replace('/', '-'), args);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { apiOn } from './api';
|
import { apiOn, transformApiArgsInv } from './api';
|
||||||
import getAsArray from './getAsArray';
|
import getAsArray from './getAsArray';
|
||||||
|
import stableStringify from 'json-stable-stringify';
|
||||||
|
|
||||||
const cachedByKey = {};
|
const cachedByKey = {};
|
||||||
const cachedPromisesByKey = {};
|
const cachedPromisesByKey = {};
|
||||||
@@ -15,10 +16,11 @@ function cacheGet(key) {
|
|||||||
|
|
||||||
function addCacheKeyToReloadTrigger(cacheKey, reloadTrigger) {
|
function addCacheKeyToReloadTrigger(cacheKey, reloadTrigger) {
|
||||||
for (const item of getAsArray(reloadTrigger)) {
|
for (const item of getAsArray(reloadTrigger)) {
|
||||||
if (!(item in cachedKeysByReloadTrigger)) {
|
const itemString = stableStringify(item);
|
||||||
cachedKeysByReloadTrigger[item] = [];
|
if (!(itemString in cachedKeysByReloadTrigger)) {
|
||||||
|
cachedKeysByReloadTrigger[itemString] = [];
|
||||||
}
|
}
|
||||||
cachedKeysByReloadTrigger[item].push(cacheKey);
|
cachedKeysByReloadTrigger[itemString].push(cacheKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +34,8 @@ function cacheSet(cacheKey, value, reloadTrigger, generation) {
|
|||||||
function cacheClean(reloadTrigger) {
|
function cacheClean(reloadTrigger) {
|
||||||
cacheGeneration += 1;
|
cacheGeneration += 1;
|
||||||
for (const item of getAsArray(reloadTrigger)) {
|
for (const item of getAsArray(reloadTrigger)) {
|
||||||
const keys = cachedKeysByReloadTrigger[item];
|
const itemString = stableStringify(transformApiArgsInv(item));
|
||||||
|
const keys = cachedKeysByReloadTrigger[itemString];
|
||||||
if (keys) {
|
if (keys) {
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
delete cachedByKey[key];
|
delete cachedByKey[key];
|
||||||
@@ -40,7 +43,7 @@ function cacheClean(reloadTrigger) {
|
|||||||
cacheGenerationByKey[key] = cacheGeneration;
|
cacheGenerationByKey[key] = cacheGeneration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete cachedKeysByReloadTrigger[item];
|
delete cachedKeysByReloadTrigger[itemString];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +80,8 @@ export async function loadCachedValue(reloadTrigger, cacheKey, func) {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error when using cached promise', err);
|
console.error('Error when using cached promise', err);
|
||||||
cacheClean(cacheKey);
|
// cacheClean(cacheKey);
|
||||||
|
cacheClean(reloadTrigger);
|
||||||
const res = await func();
|
const res = await func();
|
||||||
cacheSet(cacheKey, res, reloadTrigger, generation);
|
cacheSet(cacheKey, res, reloadTrigger, generation);
|
||||||
return res;
|
return res;
|
||||||
@@ -87,35 +91,48 @@ export async function loadCachedValue(reloadTrigger, cacheKey, func) {
|
|||||||
|
|
||||||
export async function subscribeCacheChange(reloadTrigger, cacheKey, reloadHandler) {
|
export async function subscribeCacheChange(reloadTrigger, cacheKey, reloadHandler) {
|
||||||
for (const item of getAsArray(reloadTrigger)) {
|
for (const item of getAsArray(reloadTrigger)) {
|
||||||
if (!subscriptionsByReloadTrigger[item]) {
|
const itemString = stableStringify(item);
|
||||||
subscriptionsByReloadTrigger[item] = [];
|
if (!subscriptionsByReloadTrigger[itemString]) {
|
||||||
|
subscriptionsByReloadTrigger[itemString] = [];
|
||||||
}
|
}
|
||||||
subscriptionsByReloadTrigger[item].push(reloadHandler);
|
subscriptionsByReloadTrigger[itemString].push(reloadHandler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function unsubscribeCacheChange(reloadTrigger, cacheKey, reloadHandler) {
|
export async function unsubscribeCacheChange(reloadTrigger, cacheKey, reloadHandler) {
|
||||||
for (const item of getAsArray(reloadTrigger)) {
|
for (const item of getAsArray(reloadTrigger)) {
|
||||||
if (subscriptionsByReloadTrigger[item]) {
|
const itemString = stableStringify(item);
|
||||||
subscriptionsByReloadTrigger[item] = subscriptionsByReloadTrigger[item].filter(x => x != reloadHandler);
|
if (subscriptionsByReloadTrigger[itemString]) {
|
||||||
|
subscriptionsByReloadTrigger[itemString] = subscriptionsByReloadTrigger[itemString].filter(
|
||||||
|
x => x != reloadHandler
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (subscriptionsByReloadTrigger[item].length == 0) {
|
if (subscriptionsByReloadTrigger[itemString].length == 0) {
|
||||||
delete subscriptionsByReloadTrigger[item];
|
delete subscriptionsByReloadTrigger[itemString];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function dispatchCacheChange(reloadTrigger) {
|
export function dispatchCacheChange(reloadTrigger) {
|
||||||
// console.log('CHANGE', reloadTrigger);
|
|
||||||
cacheClean(reloadTrigger);
|
cacheClean(reloadTrigger);
|
||||||
|
|
||||||
for (const item of getAsArray(reloadTrigger)) {
|
for (const item of getAsArray(reloadTrigger)) {
|
||||||
if (subscriptionsByReloadTrigger[item]) {
|
const itemString = stableStringify(transformApiArgsInv(item));
|
||||||
for (const handler of subscriptionsByReloadTrigger[item]) {
|
if (subscriptionsByReloadTrigger[itemString]) {
|
||||||
|
for (const handler of subscriptionsByReloadTrigger[itemString]) {
|
||||||
handler();
|
handler();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function batchDispatchCacheTriggers(predicate) {
|
||||||
|
for (const key in subscriptionsByReloadTrigger) {
|
||||||
|
const relaodTrigger = JSON.parse(key);
|
||||||
|
if (predicate(relaodTrigger)) {
|
||||||
|
dispatchCacheChange(relaodTrigger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
apiOn('changed-cache', reloadTrigger => dispatchCacheChange(reloadTrigger));
|
apiOn('changed-cache', reloadTrigger => dispatchCacheChange(reloadTrigger));
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { currentDatabase, getCurrentDatabase, getSingleDatabaseMode, openedTabs } from '../stores';
|
import { currentDatabase, getCurrentDatabase, getLockedDatabaseMode, openedTabs } from '../stores';
|
||||||
import { shouldShowTab } from '../widgets/TabsPanel.svelte';
|
import { shouldShowTab } from '../widgets/TabsPanel.svelte';
|
||||||
import { callWhenAppLoaded } from './appLoadManager';
|
import { callWhenAppLoaded } from './appLoadManager';
|
||||||
import { getConnectionInfo } from './metadataLoaders';
|
import { getConnectionInfo } from './metadataLoaders';
|
||||||
@@ -9,7 +9,7 @@ let lastCurrentTab = null;
|
|||||||
openedTabs.subscribe(value => {
|
openedTabs.subscribe(value => {
|
||||||
const newCurrentTab = (value || []).find(x => x.selected);
|
const newCurrentTab = (value || []).find(x => x.selected);
|
||||||
if (newCurrentTab == lastCurrentTab) return;
|
if (newCurrentTab == lastCurrentTab) return;
|
||||||
if (getSingleDatabaseMode() && getCurrentDatabase()) return;
|
if (getLockedDatabaseMode() && getCurrentDatabase()) return;
|
||||||
|
|
||||||
const lastTab = lastCurrentTab;
|
const lastTab = lastCurrentTab;
|
||||||
lastCurrentTab = newCurrentTab;
|
lastCurrentTab = newCurrentTab;
|
||||||
@@ -31,7 +31,7 @@ openedTabs.subscribe(value => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
currentDatabase.subscribe(currentDb => {
|
currentDatabase.subscribe(currentDb => {
|
||||||
if (!getSingleDatabaseMode()) return;
|
if (!getLockedDatabaseMode()) return;
|
||||||
openedTabs.update(tabs => {
|
openedTabs.update(tabs => {
|
||||||
const newTabs = tabs.map(tab => ({
|
const newTabs = tabs.map(tab => ({
|
||||||
...tab,
|
...tab,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { getConnectionList } from './metadataLoaders';
|
|||||||
// };
|
// };
|
||||||
|
|
||||||
const doServerPing = value => {
|
const doServerPing = value => {
|
||||||
apiCall('server-connections/ping', { connections: value });
|
apiCall('server-connections/ping', { conidArray: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
const doDatabasePing = value => {
|
const doDatabasePing = value => {
|
||||||
@@ -29,12 +29,12 @@ export function subscribeConnectionPingers() {
|
|||||||
openedConnections.subscribe(value => {
|
openedConnections.subscribe(value => {
|
||||||
doServerPing(value);
|
doServerPing(value);
|
||||||
if (openedConnectionsHandle) window.clearInterval(openedConnectionsHandle);
|
if (openedConnectionsHandle) window.clearInterval(openedConnectionsHandle);
|
||||||
openedConnectionsHandle = window.setInterval(() => doServerPing(value), 30 * 1000);
|
openedConnectionsHandle = window.setInterval(() => doServerPing(value), 20 * 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
currentDatabase.subscribe(value => {
|
currentDatabase.subscribe(value => {
|
||||||
doDatabasePing(value);
|
doDatabasePing(value);
|
||||||
if (currentDatabaseHandle) window.clearInterval(currentDatabaseHandle);
|
if (currentDatabaseHandle) window.clearInterval(currentDatabaseHandle);
|
||||||
currentDatabaseHandle = window.setInterval(() => doDatabasePing(value), 30 * 1000);
|
currentDatabaseHandle = window.setInterval(() => doDatabasePing(value), 20 * 1000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { apiCall, apiOff, apiOn } from './api';
|
|||||||
const databaseInfoLoader = ({ conid, database }) => ({
|
const databaseInfoLoader = ({ conid, database }) => ({
|
||||||
url: 'database-connections/structure',
|
url: 'database-connections/structure',
|
||||||
params: { conid, database },
|
params: { conid, database },
|
||||||
reloadTrigger: `database-structure-changed-${conid}-${database}`,
|
reloadTrigger: { key: `database-structure-changed`, conid, database },
|
||||||
transform: extendDatabaseInfo,
|
transform: extendDatabaseInfo,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -28,31 +28,31 @@ const databaseInfoLoader = ({ conid, database }) => ({
|
|||||||
const connectionInfoLoader = ({ conid }) => ({
|
const connectionInfoLoader = ({ conid }) => ({
|
||||||
url: 'connections/get',
|
url: 'connections/get',
|
||||||
params: { conid },
|
params: { conid },
|
||||||
reloadTrigger: 'connection-list-changed',
|
reloadTrigger: { key: 'connection-list-changed' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const configLoader = () => ({
|
const configLoader = () => ({
|
||||||
url: 'config/get',
|
url: 'config/get',
|
||||||
params: {},
|
params: {},
|
||||||
reloadTrigger: 'config-changed',
|
reloadTrigger: { key: 'config-changed' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const settingsLoader = () => ({
|
const settingsLoader = () => ({
|
||||||
url: 'config/get-settings',
|
url: 'config/get-settings',
|
||||||
params: {},
|
params: {},
|
||||||
reloadTrigger: 'settings-changed',
|
reloadTrigger: { key: 'settings-changed' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const platformInfoLoader = () => ({
|
const platformInfoLoader = () => ({
|
||||||
url: 'config/platform-info',
|
url: 'config/platform-info',
|
||||||
params: {},
|
params: {},
|
||||||
reloadTrigger: 'platform-info-changed',
|
reloadTrigger: { key: 'platform-info-changed' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const favoritesLoader = () => ({
|
const favoritesLoader = () => ({
|
||||||
url: 'files/favorites',
|
url: 'files/favorites',
|
||||||
params: {},
|
params: {},
|
||||||
reloadTrigger: 'files-changed-favorites',
|
reloadTrigger: { key: 'files-changed-favorites' },
|
||||||
});
|
});
|
||||||
|
|
||||||
// const sqlObjectListLoader = ({ conid, database }) => ({
|
// const sqlObjectListLoader = ({ conid, database }) => ({
|
||||||
@@ -64,13 +64,13 @@ const favoritesLoader = () => ({
|
|||||||
const databaseStatusLoader = ({ conid, database }) => ({
|
const databaseStatusLoader = ({ conid, database }) => ({
|
||||||
url: 'database-connections/status',
|
url: 'database-connections/status',
|
||||||
params: { conid, database },
|
params: { conid, database },
|
||||||
reloadTrigger: `database-status-changed-${conid}-${database}`,
|
reloadTrigger: { key: `database-status-changed`, conid, database },
|
||||||
});
|
});
|
||||||
|
|
||||||
const databaseListLoader = ({ conid }) => ({
|
const databaseListLoader = ({ conid }) => ({
|
||||||
url: 'server-connections/list-databases',
|
url: 'server-connections/list-databases',
|
||||||
params: { conid },
|
params: { conid },
|
||||||
reloadTrigger: `database-list-changed-${conid}`,
|
reloadTrigger: { key: `database-list-changed`, conid },
|
||||||
onLoaded: value => {
|
onLoaded: value => {
|
||||||
if (value?.length > 0) setLocalStorage(`database_list_${conid}`, value);
|
if (value?.length > 0) setLocalStorage(`database_list_${conid}`, value);
|
||||||
},
|
},
|
||||||
@@ -85,37 +85,37 @@ const databaseListLoader = ({ conid }) => ({
|
|||||||
const serverVersionLoader = ({ conid }) => ({
|
const serverVersionLoader = ({ conid }) => ({
|
||||||
url: 'server-connections/version',
|
url: 'server-connections/version',
|
||||||
params: { conid },
|
params: { conid },
|
||||||
reloadTrigger: `server-version-changed-${conid}`,
|
reloadTrigger: { key: `server-version-changed`, conid },
|
||||||
});
|
});
|
||||||
|
|
||||||
const databaseServerVersionLoader = ({ conid, database }) => ({
|
const databaseServerVersionLoader = ({ conid, database }) => ({
|
||||||
url: 'database-connections/server-version',
|
url: 'database-connections/server-version',
|
||||||
params: { conid, database },
|
params: { conid, database },
|
||||||
reloadTrigger: `database-server-version-changed-${conid}-${database}`,
|
reloadTrigger: { key: `database-server-version-changed`, conid, database },
|
||||||
});
|
});
|
||||||
|
|
||||||
const archiveFoldersLoader = () => ({
|
const archiveFoldersLoader = () => ({
|
||||||
url: 'archive/folders',
|
url: 'archive/folders',
|
||||||
params: {},
|
params: {},
|
||||||
reloadTrigger: `archive-folders-changed`,
|
reloadTrigger: { key: `archive-folders-changed` },
|
||||||
});
|
});
|
||||||
|
|
||||||
const archiveFilesLoader = ({ folder }) => ({
|
const archiveFilesLoader = ({ folder }) => ({
|
||||||
url: 'archive/files',
|
url: 'archive/files',
|
||||||
params: { folder },
|
params: { folder },
|
||||||
reloadTrigger: `archive-files-changed-${folder}`,
|
reloadTrigger: { key: `archive-files-changed`, folder },
|
||||||
});
|
});
|
||||||
|
|
||||||
const appFoldersLoader = () => ({
|
const appFoldersLoader = () => ({
|
||||||
url: 'apps/folders',
|
url: 'apps/folders',
|
||||||
params: {},
|
params: {},
|
||||||
reloadTrigger: `app-folders-changed`,
|
reloadTrigger: { key: `app-folders-changed` },
|
||||||
});
|
});
|
||||||
|
|
||||||
const appFilesLoader = ({ folder }) => ({
|
const appFilesLoader = ({ folder }) => ({
|
||||||
url: 'apps/files',
|
url: 'apps/files',
|
||||||
params: { folder },
|
params: { folder },
|
||||||
reloadTrigger: `app-files-changed-${folder}`,
|
reloadTrigger: { key: `app-files-changed`, app: folder },
|
||||||
});
|
});
|
||||||
|
|
||||||
// const dbAppsLoader = ({ conid, database }) => ({
|
// const dbAppsLoader = ({ conid, database }) => ({
|
||||||
@@ -127,41 +127,41 @@ const appFilesLoader = ({ folder }) => ({
|
|||||||
const usedAppsLoader = ({ conid, database }) => ({
|
const usedAppsLoader = ({ conid, database }) => ({
|
||||||
url: 'apps/get-used-apps',
|
url: 'apps/get-used-apps',
|
||||||
params: {},
|
params: {},
|
||||||
reloadTrigger: `used-apps-changed`,
|
reloadTrigger: { key: `used-apps-changed` },
|
||||||
});
|
});
|
||||||
|
|
||||||
const serverStatusLoader = () => ({
|
const serverStatusLoader = () => ({
|
||||||
url: 'server-connections/server-status',
|
url: 'server-connections/server-status',
|
||||||
params: {},
|
params: {},
|
||||||
reloadTrigger: `server-status-changed`,
|
reloadTrigger: { key: `server-status-changed` },
|
||||||
});
|
});
|
||||||
|
|
||||||
const connectionListLoader = () => ({
|
const connectionListLoader = () => ({
|
||||||
url: 'connections/list',
|
url: 'connections/list',
|
||||||
params: {},
|
params: {},
|
||||||
reloadTrigger: `connection-list-changed`,
|
reloadTrigger: { key: `connection-list-changed` },
|
||||||
});
|
});
|
||||||
|
|
||||||
const installedPluginsLoader = () => ({
|
const installedPluginsLoader = () => ({
|
||||||
url: 'plugins/installed',
|
url: 'plugins/installed',
|
||||||
params: {},
|
params: {},
|
||||||
reloadTrigger: `installed-plugins-changed`,
|
reloadTrigger: { key: `installed-plugins-changed` },
|
||||||
});
|
});
|
||||||
|
|
||||||
const filesLoader = ({ folder }) => ({
|
const filesLoader = ({ folder }) => ({
|
||||||
url: 'files/list',
|
url: 'files/list',
|
||||||
params: { folder },
|
params: { folder },
|
||||||
reloadTrigger: `files-changed-${folder}`,
|
reloadTrigger: { key: `files-changed`, folder },
|
||||||
});
|
});
|
||||||
const allFilesLoader = () => ({
|
const allFilesLoader = () => ({
|
||||||
url: 'files/list-all',
|
url: 'files/list-all',
|
||||||
params: {},
|
params: {},
|
||||||
reloadTrigger: `all-files-changed`,
|
reloadTrigger: { key: `all-files-changed` },
|
||||||
});
|
});
|
||||||
const authTypesLoader = ({ engine }) => ({
|
const authTypesLoader = ({ engine }) => ({
|
||||||
url: 'plugins/auth-types',
|
url: 'plugins/auth-types',
|
||||||
params: { engine },
|
params: { engine },
|
||||||
reloadTrigger: `installed-plugins-changed`,
|
reloadTrigger: { key: `installed-plugins-changed` },
|
||||||
errorValue: null,
|
errorValue: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import InlineButton from '../buttons/InlineButton.svelte';
|
import InlineButton from '../buttons/InlineButton.svelte';
|
||||||
import SearchInput from '../elements/SearchInput.svelte';
|
import SearchInput from '../elements/SearchInput.svelte';
|
||||||
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
|
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
|
||||||
import { useConnectionList, useServerStatus } from '../utility/metadataLoaders';
|
import { useConfig, useConnectionList, useServerStatus } from '../utility/metadataLoaders';
|
||||||
import SearchBoxWrapper from '../elements/SearchBoxWrapper.svelte';
|
import SearchBoxWrapper from '../elements/SearchBoxWrapper.svelte';
|
||||||
import AppObjectList from '../appobj/AppObjectList.svelte';
|
import AppObjectList from '../appobj/AppObjectList.svelte';
|
||||||
import * as connectionAppObject from '../appobj/ConnectionAppObject.svelte';
|
import * as connectionAppObject from '../appobj/ConnectionAppObject.svelte';
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
import { useConnectionColorFactory } from '../utility/useConnectionColor';
|
import { useConnectionColorFactory } from '../utility/useConnectionColor';
|
||||||
import FontIcon from '../icons/FontIcon.svelte';
|
import FontIcon from '../icons/FontIcon.svelte';
|
||||||
import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
|
import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
|
||||||
import { apiCall } from '../utility/api';
|
import { apiCall, getVolatileRemapping } from '../utility/api';
|
||||||
import LargeButton from '../buttons/LargeButton.svelte';
|
import LargeButton from '../buttons/LargeButton.svelte';
|
||||||
import { plusExpandIcon, chevronExpandIcon } from '../icons/expandIcons';
|
import { plusExpandIcon, chevronExpandIcon } from '../icons/expandIcons';
|
||||||
import { safeJsonParse } from 'dbgate-tools';
|
import { safeJsonParse } from 'dbgate-tools';
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
|
|
||||||
$: connectionsWithStatus =
|
$: connectionsWithStatus =
|
||||||
$connections && $serverStatus
|
$connections && $serverStatus
|
||||||
? $connections.map(conn => ({ ...conn, status: $serverStatus[conn._id] }))
|
? $connections.map(conn => ({ ...conn, status: $serverStatus[getVolatileRemapping(conn._id)] }))
|
||||||
: $connections;
|
: $connections;
|
||||||
|
|
||||||
$: connectionsWithStatusFiltered = connectionsWithStatus?.filter(
|
$: connectionsWithStatusFiltered = connectionsWithStatus?.filter(
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
import WidgetColumnBarItem from './WidgetColumnBarItem.svelte';
|
import WidgetColumnBarItem from './WidgetColumnBarItem.svelte';
|
||||||
import SqlObjectList from './SqlObjectList.svelte';
|
import SqlObjectList from './SqlObjectList.svelte';
|
||||||
import DbKeysTree from './DbKeysTree.svelte';
|
import DbKeysTree from './DbKeysTree.svelte';
|
||||||
|
import SingleConnectionDatabaseList from './SingleConnectionDatabaseList.svelte';
|
||||||
|
|
||||||
export let hidden = false;
|
export let hidden = false;
|
||||||
|
|
||||||
@@ -24,7 +25,11 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<WidgetColumnBar {hidden}>
|
<WidgetColumnBar {hidden}>
|
||||||
{#if !$config?.singleDatabase}
|
{#if $config?.singleConnection}
|
||||||
|
<WidgetColumnBarItem title="Databases" name="databases" height="35%" storageName="databasesWidget">
|
||||||
|
<SingleConnectionDatabaseList connection={$config?.singleConnection} />
|
||||||
|
</WidgetColumnBarItem>
|
||||||
|
{:else if !$config?.singleDbConnection}
|
||||||
<WidgetColumnBarItem title="Connections" name="connections" height="35%" storageName="connectionsWidget">
|
<WidgetColumnBarItem title="Connections" name="connections" height="35%" storageName="connectionsWidget">
|
||||||
<ConnectionList />
|
<ConnectionList />
|
||||||
</WidgetColumnBarItem>
|
</WidgetColumnBarItem>
|
||||||
|
|||||||
32
packages/web/src/widgets/SingleConnectionDatabaseList.svelte
Normal file
32
packages/web/src/widgets/SingleConnectionDatabaseList.svelte
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import _ from 'lodash';
|
||||||
|
import InlineButton from '../buttons/InlineButton.svelte';
|
||||||
|
import SearchInput from '../elements/SearchInput.svelte';
|
||||||
|
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
|
||||||
|
import SearchBoxWrapper from '../elements/SearchBoxWrapper.svelte';
|
||||||
|
import SubDatabaseList from '../appobj/SubDatabaseList.svelte';
|
||||||
|
import { openedConnections } from '../stores';
|
||||||
|
import { useConnectionColorFactory } from '../utility/useConnectionColor';
|
||||||
|
import FontIcon from '../icons/FontIcon.svelte';
|
||||||
|
import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
|
||||||
|
import { apiCall } from '../utility/api';
|
||||||
|
|
||||||
|
export let connection;
|
||||||
|
|
||||||
|
let filter = '';
|
||||||
|
|
||||||
|
const handleRefreshDatabases = () => {
|
||||||
|
apiCall('server-connections/refresh', { conid: connection._id });
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SearchBoxWrapper>
|
||||||
|
<SearchInput placeholder="Search connection or database" bind:value={filter} />
|
||||||
|
<CloseSearchButton bind:filter />
|
||||||
|
<InlineButton on:click={handleRefreshDatabases} title="Refresh database list">
|
||||||
|
<FontIcon icon="icon refresh" />
|
||||||
|
</InlineButton>
|
||||||
|
</SearchBoxWrapper>
|
||||||
|
<WidgetsInnerContainer>
|
||||||
|
<SubDatabaseList data={connection} {filter} passProps={{}} />
|
||||||
|
</WidgetsInnerContainer>
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
const getCurrentValueMarker: any = {};
|
const getCurrentValueMarker: any = {};
|
||||||
|
|
||||||
export function shouldShowTab(tab, singleDbMode = getCurrentValueMarker, currentDb = getCurrentValueMarker) {
|
export function shouldShowTab(tab, lockedDbMode = getCurrentValueMarker, currentDb = getCurrentValueMarker) {
|
||||||
if (singleDbMode == getCurrentValueMarker) {
|
if (lockedDbMode == getCurrentValueMarker) {
|
||||||
singleDbMode = getSingleDatabaseMode();
|
lockedDbMode = getLockedDatabaseMode();
|
||||||
}
|
}
|
||||||
if (singleDbMode) {
|
if (lockedDbMode) {
|
||||||
if (currentDb == getCurrentValueMarker) {
|
if (currentDb == getCurrentValueMarker) {
|
||||||
currentDb = getCurrentDatabase();
|
currentDb = getCurrentDatabase();
|
||||||
}
|
}
|
||||||
@@ -250,8 +250,8 @@
|
|||||||
activeTabId,
|
activeTabId,
|
||||||
getActiveTabId,
|
getActiveTabId,
|
||||||
getCurrentDatabase,
|
getCurrentDatabase,
|
||||||
singleDatabaseMode,
|
lockedDatabaseMode,
|
||||||
getSingleDatabaseMode,
|
getLockedDatabaseMode,
|
||||||
} from '../stores';
|
} from '../stores';
|
||||||
import tabs from '../tabs';
|
import tabs from '../tabs';
|
||||||
import { setSelectedTab } from '../utility/common';
|
import { setSelectedTab } from '../utility/common';
|
||||||
@@ -264,7 +264,7 @@
|
|||||||
import TabCloseButton from '../elements/TabCloseButton.svelte';
|
import TabCloseButton from '../elements/TabCloseButton.svelte';
|
||||||
import CloseTabModal from '../modals/CloseTabModal.svelte';
|
import CloseTabModal from '../modals/CloseTabModal.svelte';
|
||||||
|
|
||||||
$: showTabFilterFunc = tab => shouldShowTab(tab, $singleDatabaseMode, $currentDatabase);
|
$: showTabFilterFunc = tab => shouldShowTab(tab, $lockedDatabaseMode, $currentDatabase);
|
||||||
$: connectionList = useConnectionList();
|
$: connectionList = useConnectionList();
|
||||||
|
|
||||||
$: currentDbKey =
|
$: currentDbKey =
|
||||||
@@ -443,7 +443,7 @@
|
|||||||
<div class="tabs" on:wheel={handleTabsWheel} bind:this={domTabs}>
|
<div class="tabs" on:wheel={handleTabsWheel} bind:this={domTabs}>
|
||||||
{#each groupedTabs as tabGroup}
|
{#each groupedTabs as tabGroup}
|
||||||
<div class="db-wrapper">
|
<div class="db-wrapper">
|
||||||
{#if !$singleDatabaseMode}
|
{#if !$lockedDatabaseMode}
|
||||||
<div
|
<div
|
||||||
class="db-name"
|
class="db-name"
|
||||||
class:selected={draggingDbGroup
|
class:selected={draggingDbGroup
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
visibleSelectedWidget,
|
visibleSelectedWidget,
|
||||||
visibleWidgetSideBar,
|
visibleWidgetSideBar,
|
||||||
visibleHamburgerMenuWidget,
|
visibleHamburgerMenuWidget,
|
||||||
singleDatabaseMode,
|
lockedDatabaseMode,
|
||||||
} from '../stores';
|
} from '../stores';
|
||||||
import mainMenuDefinition from '../../../../app/src/mainMenuDefinition';
|
import mainMenuDefinition from '../../../../app/src/mainMenuDefinition';
|
||||||
import hasPermission from '../utility/hasPermission';
|
import hasPermission from '../utility/hasPermission';
|
||||||
@@ -112,12 +112,12 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="wrapper"
|
class="wrapper"
|
||||||
title={`Toggle whether tabs from all databases are visible. Currently - ${$singleDatabaseMode ? 'NO' : 'YES'}`}
|
title={`Toggle whether tabs from all databases are visible. Currently - ${$lockedDatabaseMode ? 'NO' : 'YES'}`}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
$singleDatabaseMode = !$singleDatabaseMode;
|
$lockedDatabaseMode = !$lockedDatabaseMode;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FontIcon icon={$singleDatabaseMode ? 'icon single-database-mode' : 'icon multi-database-mode'} />
|
<FontIcon icon={$lockedDatabaseMode ? 'icon locked-database-mode' : 'icon unlocked-database-mode'} />
|
||||||
</div>
|
</div>
|
||||||
<div class="wrapper" on:click={handleSettingsMenu} bind:this={domSettings}>
|
<div class="wrapper" on:click={handleSettingsMenu} bind:this={domSettings}>
|
||||||
<FontIcon icon="icon settings" />
|
<FontIcon icon="icon settings" />
|
||||||
|
|||||||
Reference in New Issue
Block a user