Merge branch 'askpassword'

This commit is contained in:
Jan Prochazka
2022-12-25 19:33:21 +01:00
35 changed files with 501 additions and 118 deletions

View File

@@ -21,6 +21,7 @@ module.exports = ({ editMenu }) => [
{ divider: true },
{ command: 'file.exit', hideDisabled: true },
{ command: 'app.logout', hideDisabled: true, skipInApp: true },
{ command: 'app.disconnect', hideDisabled: true, skipInApp: true },
],
},
{

View File

@@ -17,6 +17,7 @@
"start:api:portal": "yarn workspace dbgate-api start:portal",
"start:api:singledb": "yarn workspace dbgate-api start:singledb",
"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:sqltree": "yarn workspace dbgate-sqltree start",
"start:tools": "yarn workspace dbgate-tools start",

14
packages/api/env/dblogin/.env vendored Normal file
View 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

View File

@@ -5,8 +5,8 @@ CONNECTIONS=mysql
LABEL_mysql=MySql localhost
SERVER_mysql=localhost
USER_mysql=root
PASSWORD_mysql=test
PORT_mysql=3307
PASSWORD_mysql=Pwd2020Db
PORT_mysql=3306
ENGINE_mysql=mysql@dbgate-plugin-mysql
DBCONFIG_mysql=[{"name":"Chinook","connectionColor":"cyan"}]

View File

@@ -60,6 +60,7 @@
"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: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:singleconn": "env-cmd node src/index.js --server localhost --user root --port 3307 --engine mysql@dbgate-plugin-mysql --password test --listen-api",
"ts": "tsc",

View File

@@ -58,7 +58,7 @@ module.exports = {
refreshFiles_meta: true,
async refreshFiles({ folder }) {
socket.emitChanged(`app-files-changed-${folder}`);
socket.emitChanged('app-files-changed', { app: folder });
},
refreshFolders_meta: true,
@@ -69,7 +69,7 @@ module.exports = {
deleteFile_meta: true,
async deleteFile({ 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);
},
@@ -79,7 +79,7 @@ module.exports = {
path.join(path.join(appdir(), folder), `${file}.${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);
},
@@ -95,7 +95,7 @@ module.exports = {
if (!folder) throw new Error('Missing folder parameter');
await fs.rmdir(path.join(appdir(), folder), { recursive: true });
socket.emitChanged(`app-folders-changed`);
socket.emitChanged(`app-files-changed-${folder}`);
socket.emitChanged('app-files-changed', { app: folder });
socket.emitChanged('used-apps-changed');
},
@@ -219,7 +219,7 @@ module.exports = {
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');
},
@@ -271,7 +271,7 @@ module.exports = {
const file = path.join(appdir(), appFolder, fileName);
if (!(await fs.exists(file))) {
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');
return true;
}

View File

@@ -75,7 +75,7 @@ module.exports = {
refreshFiles_meta: true,
async refreshFiles({ folder }) {
socket.emitChanged(`archive-files-changed-${folder}`);
socket.emitChanged('archive-files-changed', { folder });
},
refreshFolders_meta: true,
@@ -86,7 +86,7 @@ module.exports = {
deleteFile_meta: true,
async deleteFile({ 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,
@@ -95,7 +95,7 @@ module.exports = {
path.join(resolveArchiveFolder(folder), `${file}.${fileType}`),
path.join(resolveArchiveFolder(folder), `${newFile}.${fileType}`)
);
socket.emitChanged(`archive-files-changed-${folder}`);
socket.emitChanged(`archive-files-changed`, { folder });
},
renameFolder_meta: true,
@@ -119,7 +119,7 @@ module.exports = {
saveFreeTable_meta: true,
async saveFreeTable({ folder, file, data }) {
await saveFreeTableData(path.join(resolveArchiveFolder(folder), `${file}.jsonl`), data);
socket.emitChanged(`archive-files-changed-${folder}`);
socket.emitChanged(`archive-files-changed`, { folder });
return true;
},
@@ -147,7 +147,7 @@ module.exports = {
saveText_meta: true,
async saveText({ folder, file, 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;
},
@@ -156,7 +156,7 @@ module.exports = {
const source = getJslFileName(jslid);
const target = path.join(resolveArchiveFolder(folder), `${file}.jsonl`);
await fs.copyFile(source, target);
socket.emitChanged(`archive-files-changed-${folder}`);
socket.emitChanged(`archive-files-changed`, { folder });
return true;
},

View File

@@ -38,7 +38,8 @@ module.exports = {
return {
runAsPortal: !!connections.portalConnections,
singleDatabase: connections.singleDatabase,
singleDbConnection: connections.singleDbConnection,
singleConnection: connections.singleConnection,
// hideAppEditor: !!process.env.HIDE_APP_EDITOR,
allowShellConnection: platformInfo.allowShellConnection,
allowShellScripting: platformInfo.allowShellScripting,

View File

@@ -2,6 +2,7 @@ const path = require('path');
const { fork } = require('child_process');
const _ = require('lodash');
const fs = require('fs-extra');
const crypto = require('crypto');
const { datadir, filesdir } = require('../utility/directories');
const socket = require('../utility/socket');
@@ -15,6 +16,8 @@ const { safeJsonParse } = require('dbgate-tools');
const platformInfo = require('../utility/platformInfo');
const { connectionHasPermission, testConnectionPermission } = require('../utility/hasPermission');
let volatileConnections = {};
function getNamedArgs() {
const res = {};
for (let i = 0; i < process.argv.length; i++) {
@@ -49,6 +52,7 @@ function getPortalCollections() {
server: process.env[`SERVER_${id}`],
user: process.env[`USER_${id}`],
password: process.env[`PASSWORD_${id}`],
passwordMode: process.env[`PASSWORD_MODE_${id}`],
port: process.env[`PORT_${id}`],
databaseUrl: process.env[`URL_${id}`],
useDatabaseUrl: !!process.env[`URL_${id}`],
@@ -126,9 +130,10 @@ function getPortalCollections() {
return null;
}
const portalConnections = getPortalCollections();
function getSingleDatabase() {
function getSingleDbConnection() {
if (process.env.SINGLE_CONNECTION && process.env.SINGLE_DATABASE) {
// @ts-ignore
const connection = portalConnections.find(x => x._id == process.env.SINGLE_CONNECTION);
@@ -152,12 +157,31 @@ function getSingleDatabase() {
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 = {
datastore: null,
opened: [],
singleDatabase,
singleDbConnection,
singleConnection,
portalConnections,
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,
async save(connection) {
if (portalConnections) return;
@@ -258,6 +312,10 @@ module.exports = {
async getCore({ conid, mask = false }) {
if (!conid) return null;
const volatile = volatileConnections[conid];
if (volatile) {
return volatile;
}
if (portalConnections) {
const res = portalConnections.find(x => x._id == conid) || null;
return mask && !platformInfo.allowShellConnection ? maskConnection(res) : res;

View File

@@ -27,6 +27,7 @@ const { createTwoFilesPatch } = require('diff');
const diff2htmlPage = require('../utility/diff2htmlPage');
const processArgs = require('../utility/processArgs');
const { testConnectionPermission } = require('../utility/hasPermission');
const { MissingCredentialsError } = require('../utility/exceptions');
module.exports = {
/** @type {import('dbgate-types').OpenedDatabaseConnection[]} */
@@ -42,19 +43,19 @@ module.exports = {
const existing = this.opened.find(x => x.conid == conid && x.database == database);
if (!existing) return;
existing.structure = structure;
socket.emitChanged(`database-structure-changed-${conid}-${database}`);
socket.emitChanged('database-structure-changed', { conid, database });
},
handle_structureTime(conid, database, { analysedTime }) {
const existing = this.opened.find(x => x.conid == conid && x.database == database);
if (!existing) return;
existing.analysedTime = analysedTime;
socket.emitChanged(`database-status-changed-${conid}-${database}`);
socket.emitChanged(`database-status-changed`, { conid, database });
},
handle_version(conid, database, { version }) {
const existing = this.opened.find(x => x.conid == conid && x.database == database);
if (!existing) return;
existing.serverVersion = version;
socket.emitChanged(`database-server-version-changed-${conid}-${database}`);
socket.emitChanged(`database-server-version-changed`, { conid, database });
},
handle_error(conid, database, props) {
@@ -72,7 +73,7 @@ module.exports = {
if (!existing) return;
if (existing.status && status && existing.status.counter > status.counter) return;
existing.status = status;
socket.emitChanged(`database-status-changed-${conid}-${database}`);
socket.emitChanged(`database-status-changed`, { conid, database });
},
handle_ping() {},
@@ -81,6 +82,9 @@ module.exports = {
const existing = this.opened.find(x => x.conid == conid && x.database == database);
if (existing) return existing;
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], [
'--is-forked-api',
'--start-process',
@@ -313,7 +317,7 @@ module.exports = {
},
structure: existing.structure,
};
socket.emitChanged(`database-status-changed-${conid}-${database}`);
socket.emitChanged(`database-status-changed`, { conid, database });
}
},

View File

@@ -49,7 +49,7 @@ module.exports = {
async delete({ folder, file }, req) {
if (!hasPermission(`files/${folder}/write`, req)) return false;
await fs.unlink(path.join(filesdir(), folder, file));
socket.emitChanged(`files-changed-${folder}`);
socket.emitChanged(`files-changed`, { folder });
socket.emitChanged(`all-files-changed`);
return true;
},
@@ -58,7 +58,7 @@ module.exports = {
async rename({ folder, file, newFile }, req) {
if (!hasPermission(`files/${folder}/write`, req)) return false;
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`);
return true;
},
@@ -66,7 +66,7 @@ module.exports = {
refresh_meta: true,
async refresh({ folders }, req) {
for (const folder of folders) {
socket.emitChanged(`files-changed-${folder}`);
socket.emitChanged(`files-changed`, { folder });
socket.emitChanged(`all-files-changed`);
}
return true;
@@ -76,7 +76,7 @@ module.exports = {
async copy({ folder, file, newFile }, req) {
if (!hasPermission(`files/${folder}/write`, req)) return false;
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`);
return true;
},
@@ -112,13 +112,13 @@ module.exports = {
if (!hasPermission(`archive/write`, req)) return false;
const dir = resolveArchiveFolder(folder.substring('archive:'.length));
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;
} else if (folder.startsWith('app:')) {
if (!hasPermission(`apps/write`, req)) return false;
const app = folder.substring('app:'.length);
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');
apps.emitChangedDbApp(folder);
return true;
@@ -129,7 +129,7 @@ module.exports = {
await fs.mkdir(dir);
}
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`);
if (folder == 'shell') {
scheduler.reload();

View File

@@ -9,6 +9,7 @@ const lock = new AsyncLock();
const config = require('./config');
const processArgs = require('../utility/processArgs');
const { testConnectionPermission } = require('../utility/hasPermission');
const { MissingCredentialsError } = require('../utility/exceptions');
module.exports = {
opened: [],
@@ -20,13 +21,13 @@ module.exports = {
const existing = this.opened.find(x => x.conid == conid);
if (!existing) return;
existing.databases = databases;
socket.emitChanged(`database-list-changed-${conid}`);
socket.emitChanged(`database-list-changed`, { conid });
},
handle_version(conid, { version }) {
const existing = this.opened.find(x => x.conid == conid);
if (!existing) return;
existing.version = version;
socket.emitChanged(`server-version-changed-${conid}`);
socket.emitChanged(`server-version-changed`, { conid });
},
handle_status(conid, { status }) {
const existing = this.opened.find(x => x.conid == conid);
@@ -46,6 +47,9 @@ module.exports = {
const existing = this.opened.find(x => x.conid == conid);
if (existing) return existing;
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], [
'--is-forked-api',
'--start-process',
@@ -127,9 +131,9 @@ module.exports = {
},
ping_meta: true,
async ping({ connections }) {
async ping({ conidArray }) {
await Promise.all(
_.uniq(connections).map(async conid => {
_.uniq(conidArray).map(async conid => {
const last = this.lastPinged[conid];
if (last && new Date().getTime() - last < 30 * 1000) {
return Promise.resolve();

View File

@@ -0,0 +1,9 @@
class MissingCredentialsError {
constructor(detail) {
this.detail = detail;
}
}
module.exports = {
MissingCredentialsError,
};

View File

@@ -1,4 +1,5 @@
const _ = require('lodash');
const stableStringify = require('json-stable-stringify');
const sseResponses = [];
let electronSender = null;
@@ -27,12 +28,12 @@ module.exports = {
electronSender.send(message, data == null ? null : data);
}
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);
this.emit('changed-cache', key);
this.emit('changed-cache', { key, ...params });
// this.emit(key);
},
};

View File

@@ -1,6 +1,7 @@
const _ = require('lodash');
const express = require('express');
const getExpressPath = require('./getExpressPath');
const { MissingCredentialsError } = require('./exceptions');
/**
* @param {string} route
@@ -37,6 +38,13 @@ module.exports = function useController(app, electron, route, controller) {
if (data === undefined) return null;
return data;
} catch (err) {
if (err instanceof MissingCredentialsError) {
return {
missingCredentials: true,
apiErrorMessage: 'Missing credentials',
detail: err.detail,
};
}
return { apiErrorMessage: err.message };
}
});
@@ -69,7 +77,15 @@ module.exports = function useController(app, electron, route, controller) {
res.json(data);
} catch (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 });
}
}
});
}

View File

@@ -52,6 +52,7 @@
const electron = getElectron();
const currentDb = getCurrentDatabase();
openedConnections.update(list => list.filter(x => x != conid));
removeVolatileMapping(conid);
if (electron) {
apiCall('server-connections/disconnect', { conid });
}
@@ -100,7 +101,7 @@
import getConnectionLabel from '../utility/getConnectionLabel';
import { getDatabaseList, useUsedApps } from '../utility/metadataLoaders';
import { getLocalStorage } from '../utility/storageCache';
import { apiCall } from '../utility/api';
import { apiCall, removeVolatileMapping } from '../utility/api';
import ImportDatabaseDumpModal from '../modals/ImportDatabaseDumpModal.svelte';
import { closeMultipleTabs } from '../widgets/TabsPanel.svelte';
import AboutModal from '../modals/AboutModal.svelte';

View File

@@ -40,7 +40,7 @@
for (const connection of connectionList || []) {
const conid = connection._id;
if (connection.singleDatabase) continue;
if (getCurrentConfig()?.singleDatabase) continue;
if (getCurrentConfig()?.singleDbConnection) continue;
const databases = getLocalStorage(`database_list_${conid}`) || [];
for (const db of databases) {
databaseList.push({

View File

@@ -37,6 +37,7 @@ import { openWebLink } from '../utility/exportFileTools';
import { getSettings } from '../utility/metadataLoaders';
import { isMac } from '../utility/common';
import { doLogout, internalRedirectTo } from '../clientAuth';
import { disconnectServerConnection } from '../appobj/ConnectionAppObject.svelte';
// function themeCommand(theme: ThemeDefinition) {
// return {
@@ -552,6 +553,14 @@ registerCommand({
onClick: doLogout,
});
registerCommand({
id: 'app.disconnect',
category: 'App',
name: 'Disconnect',
testEnabled: () => getCurrentConfig()?.singleConnection != null,
onClick: () => disconnectServerConnection(getCurrentConfig()?.singleConnection?._id),
});
export function registerFileCommands({
idPrefix,
category,

View File

@@ -8,6 +8,7 @@
export let name;
export let disabled = false;
export let saveOnInput = false;
const { values, setFieldValue } = getFormContext();
@@ -23,6 +24,11 @@
{disabled}
value={isCrypted ? '' : 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}
type={isCrypted || showPassword ? 'text' : 'password'}
/>

View File

@@ -4,6 +4,7 @@
export let name;
export let defaultValue;
export let saveOnInput = false;
const { values, setFieldValue } = getFormContext();
</script>
@@ -12,4 +13,9 @@
{...$$restProps}
value={$values[name] ?? defaultValue}
on:input={e => setFieldValue(name, e.target['value'])}
on:input={e => {
if (saveOnInput) {
setFieldValue(name, e.target['value']);
}
}}
/>

View File

@@ -40,8 +40,8 @@
'icon columns': 'mdi mdi-view-column',
'icon columns-outline': 'mdi mdi-view-column-outline',
'icon single-database-mode': 'mdi mdi-database-lock',
'icon multi-database-mode': 'mdi mdi-database-eye',
'icon locked-database-mode': 'mdi mdi-database-lock',
'icon unlocked-database-mode': 'mdi mdi-database-eye',
'icon database': 'mdi mdi-database',
'icon server': 'mdi mdi-server',

View 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>

View File

@@ -28,8 +28,12 @@
$: driver = $extensions.drivers.find(x => x.engine == engine);
$: defaultDatabase = $values.defaultDatabase;
$: showUser = driver?.showConnectionField('user', $values);
$: showPassword = driver?.showConnectionField('password', $values);
$: showUser = driver?.showConnectionField('user', $values) && $values.passwordMode != 'askUser';
$: 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);
</script>
@@ -159,7 +163,7 @@
<FormPasswordField label="Password" name="password" disabled={isConnected || disabledFields.includes('password')} />
{/if}
{#if !disabledFields.includes('password') && showPassword}
{#if !disabledFields.includes('password') && showPasswordMode}
<FormSelectField
label="Password mode"
isNative
@@ -169,6 +173,8 @@
options={[
{ value: 'saveEncrypted', label: 'Save and encrypt' },
{ 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}

View File

@@ -24,7 +24,7 @@
currentEditorTheme,
extensions,
selectedWidget,
singleDatabaseMode,
lockedDatabaseMode,
visibleWidgetSideBar,
} from '../stores';
import { isMac } from '../utility/common';
@@ -115,11 +115,11 @@ ORDER BY
type="checkbox"
labelProps={{
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>
<FormCheckboxField

View File

@@ -50,7 +50,7 @@ function subscribeCssVariable(store, transform, cssVariable) {
}
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 visibleSelectedWidget = derived(
[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(leftPanelWidth, x => `${x}px`, '--dim-left-panel-width');
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;
activeTabId.subscribe(value => {
@@ -200,11 +200,11 @@ pinnedDatabases.subscribe(value => {
});
export const getPinnedDatabases = () => _.compact(pinnedDatabasesValue);
let singleDatabaseModeValue = null;
singleDatabaseMode.subscribe(value => {
singleDatabaseModeValue = value;
let lockedDatabaseModeValue = null;
lockedDatabaseMode.subscribe(value => {
lockedDatabaseModeValue = value;
});
export const getSingleDatabaseMode = () => singleDatabaseModeValue;
export const getLockedDatabaseMode = () => lockedDatabaseModeValue;
let currentDatabaseValue = null;
currentDatabase.subscribe(value => {
@@ -246,8 +246,8 @@ export function subscribeApiDependendStores() {
useConfig().subscribe(value => {
currentConfigValue = value;
invalidateCommands();
if (value.singleDatabase) {
currentDatabase.set(value.singleDatabase);
if (value.singleDbConnection) {
currentDatabase.set(value.singleDbConnection);
}
});
}

View File

@@ -5,6 +5,9 @@ import getElectron from './getElectron';
// import socket from './socket';
import { showSnackbarError } from '../utility/snackbar';
import { isOauthCallback, redirectToLogin } from '../clientAuth';
import { showModal } from '../modals/modalTools';
import DatabaseLoginModal, { isDatabaseLoginVisible } from '../modals/DatabaseLoginModal.svelte';
import _ from 'lodash';
let eventSource;
let apiLogging = false;
@@ -12,6 +15,9 @@ let apiLogging = false;
let apiDisabled = false;
const disabledOnOauth = isOauthCallback();
const volatileConnectionMap = {};
const volatileConnectionMapInv = {};
export function disableApi() {
apiDisabled = true;
}
@@ -20,6 +26,27 @@ export function enableApi() {
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() {
if (!eventSource) {
eventSource = new EventSource(`${resolveApi()}/stream`);
@@ -32,7 +59,16 @@ function processApiResponse(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);
return {
errorMessage: resp.apiErrorMessage,
@@ -42,6 +78,22 @@ function processApiResponse(route, args, 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) {
if (apiLogging) {
console.log('>>> API CALL', route, args);
@@ -55,6 +107,8 @@ export async function apiCall(route: string, args: {} = undefined) {
return;
}
args = transformApiArgs(args);
const electron = getElectron();
if (electron) {
const resp = await electron.invoke(route.replace('/', '-'), args);

View File

@@ -1,5 +1,6 @@
import { apiOn } from './api';
import { apiOn, transformApiArgsInv } from './api';
import getAsArray from './getAsArray';
import stableStringify from 'json-stable-stringify';
const cachedByKey = {};
const cachedPromisesByKey = {};
@@ -15,10 +16,11 @@ function cacheGet(key) {
function addCacheKeyToReloadTrigger(cacheKey, reloadTrigger) {
for (const item of getAsArray(reloadTrigger)) {
if (!(item in cachedKeysByReloadTrigger)) {
cachedKeysByReloadTrigger[item] = [];
const itemString = stableStringify(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) {
cacheGeneration += 1;
for (const item of getAsArray(reloadTrigger)) {
const keys = cachedKeysByReloadTrigger[item];
const itemString = stableStringify(transformApiArgsInv(item));
const keys = cachedKeysByReloadTrigger[itemString];
if (keys) {
for (const key of keys) {
delete cachedByKey[key];
@@ -40,7 +43,7 @@ function cacheClean(reloadTrigger) {
cacheGenerationByKey[key] = cacheGeneration;
}
}
delete cachedKeysByReloadTrigger[item];
delete cachedKeysByReloadTrigger[itemString];
}
}
@@ -77,7 +80,8 @@ export async function loadCachedValue(reloadTrigger, cacheKey, func) {
}
} catch (err) {
console.error('Error when using cached promise', err);
cacheClean(cacheKey);
// cacheClean(cacheKey);
cacheClean(reloadTrigger);
const res = await func();
cacheSet(cacheKey, res, reloadTrigger, generation);
return res;
@@ -87,35 +91,48 @@ export async function loadCachedValue(reloadTrigger, cacheKey, func) {
export async function subscribeCacheChange(reloadTrigger, cacheKey, reloadHandler) {
for (const item of getAsArray(reloadTrigger)) {
if (!subscriptionsByReloadTrigger[item]) {
subscriptionsByReloadTrigger[item] = [];
const itemString = stableStringify(item);
if (!subscriptionsByReloadTrigger[itemString]) {
subscriptionsByReloadTrigger[itemString] = [];
}
subscriptionsByReloadTrigger[item].push(reloadHandler);
subscriptionsByReloadTrigger[itemString].push(reloadHandler);
}
}
export async function unsubscribeCacheChange(reloadTrigger, cacheKey, reloadHandler) {
for (const item of getAsArray(reloadTrigger)) {
if (subscriptionsByReloadTrigger[item]) {
subscriptionsByReloadTrigger[item] = subscriptionsByReloadTrigger[item].filter(x => x != reloadHandler);
const itemString = stableStringify(item);
if (subscriptionsByReloadTrigger[itemString]) {
subscriptionsByReloadTrigger[itemString] = subscriptionsByReloadTrigger[itemString].filter(
x => x != reloadHandler
);
}
if (subscriptionsByReloadTrigger[item].length == 0) {
delete subscriptionsByReloadTrigger[item];
if (subscriptionsByReloadTrigger[itemString].length == 0) {
delete subscriptionsByReloadTrigger[itemString];
}
}
}
function dispatchCacheChange(reloadTrigger) {
// console.log('CHANGE', reloadTrigger);
export function dispatchCacheChange(reloadTrigger) {
cacheClean(reloadTrigger);
for (const item of getAsArray(reloadTrigger)) {
if (subscriptionsByReloadTrigger[item]) {
for (const handler of subscriptionsByReloadTrigger[item]) {
const itemString = stableStringify(transformApiArgsInv(item));
if (subscriptionsByReloadTrigger[itemString]) {
for (const handler of subscriptionsByReloadTrigger[itemString]) {
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));

View File

@@ -1,5 +1,5 @@
import _ from 'lodash';
import { currentDatabase, getCurrentDatabase, getSingleDatabaseMode, openedTabs } from '../stores';
import { currentDatabase, getCurrentDatabase, getLockedDatabaseMode, openedTabs } from '../stores';
import { shouldShowTab } from '../widgets/TabsPanel.svelte';
import { callWhenAppLoaded } from './appLoadManager';
import { getConnectionInfo } from './metadataLoaders';
@@ -9,7 +9,7 @@ let lastCurrentTab = null;
openedTabs.subscribe(value => {
const newCurrentTab = (value || []).find(x => x.selected);
if (newCurrentTab == lastCurrentTab) return;
if (getSingleDatabaseMode() && getCurrentDatabase()) return;
if (getLockedDatabaseMode() && getCurrentDatabase()) return;
const lastTab = lastCurrentTab;
lastCurrentTab = newCurrentTab;
@@ -31,7 +31,7 @@ openedTabs.subscribe(value => {
});
currentDatabase.subscribe(currentDb => {
if (!getSingleDatabaseMode()) return;
if (!getLockedDatabaseMode()) return;
openedTabs.update(tabs => {
const newTabs = tabs.map(tab => ({
...tab,

View File

@@ -10,7 +10,7 @@ import { getConnectionList } from './metadataLoaders';
// };
const doServerPing = value => {
apiCall('server-connections/ping', { connections: value });
apiCall('server-connections/ping', { conidArray: value });
};
const doDatabasePing = value => {
@@ -29,12 +29,12 @@ export function subscribeConnectionPingers() {
openedConnections.subscribe(value => {
doServerPing(value);
if (openedConnectionsHandle) window.clearInterval(openedConnectionsHandle);
openedConnectionsHandle = window.setInterval(() => doServerPing(value), 30 * 1000);
openedConnectionsHandle = window.setInterval(() => doServerPing(value), 20 * 1000);
});
currentDatabase.subscribe(value => {
doDatabasePing(value);
if (currentDatabaseHandle) window.clearInterval(currentDatabaseHandle);
currentDatabaseHandle = window.setInterval(() => doDatabasePing(value), 30 * 1000);
currentDatabaseHandle = window.setInterval(() => doDatabasePing(value), 20 * 1000);
});
}

View File

@@ -9,7 +9,7 @@ import { apiCall, apiOff, apiOn } from './api';
const databaseInfoLoader = ({ conid, database }) => ({
url: 'database-connections/structure',
params: { conid, database },
reloadTrigger: `database-structure-changed-${conid}-${database}`,
reloadTrigger: { key: `database-structure-changed`, conid, database },
transform: extendDatabaseInfo,
});
@@ -28,31 +28,31 @@ const databaseInfoLoader = ({ conid, database }) => ({
const connectionInfoLoader = ({ conid }) => ({
url: 'connections/get',
params: { conid },
reloadTrigger: 'connection-list-changed',
reloadTrigger: { key: 'connection-list-changed' },
});
const configLoader = () => ({
url: 'config/get',
params: {},
reloadTrigger: 'config-changed',
reloadTrigger: { key: 'config-changed' },
});
const settingsLoader = () => ({
url: 'config/get-settings',
params: {},
reloadTrigger: 'settings-changed',
reloadTrigger: { key: 'settings-changed' },
});
const platformInfoLoader = () => ({
url: 'config/platform-info',
params: {},
reloadTrigger: 'platform-info-changed',
reloadTrigger: { key: 'platform-info-changed' },
});
const favoritesLoader = () => ({
url: 'files/favorites',
params: {},
reloadTrigger: 'files-changed-favorites',
reloadTrigger: { key: 'files-changed-favorites' },
});
// const sqlObjectListLoader = ({ conid, database }) => ({
@@ -64,13 +64,13 @@ const favoritesLoader = () => ({
const databaseStatusLoader = ({ conid, database }) => ({
url: 'database-connections/status',
params: { conid, database },
reloadTrigger: `database-status-changed-${conid}-${database}`,
reloadTrigger: { key: `database-status-changed`, conid, database },
});
const databaseListLoader = ({ conid }) => ({
url: 'server-connections/list-databases',
params: { conid },
reloadTrigger: `database-list-changed-${conid}`,
reloadTrigger: { key: `database-list-changed`, conid },
onLoaded: value => {
if (value?.length > 0) setLocalStorage(`database_list_${conid}`, value);
},
@@ -85,37 +85,37 @@ const databaseListLoader = ({ conid }) => ({
const serverVersionLoader = ({ conid }) => ({
url: 'server-connections/version',
params: { conid },
reloadTrigger: `server-version-changed-${conid}`,
reloadTrigger: { key: `server-version-changed`, conid },
});
const databaseServerVersionLoader = ({ conid, database }) => ({
url: 'database-connections/server-version',
params: { conid, database },
reloadTrigger: `database-server-version-changed-${conid}-${database}`,
reloadTrigger: { key: `database-server-version-changed`, conid, database },
});
const archiveFoldersLoader = () => ({
url: 'archive/folders',
params: {},
reloadTrigger: `archive-folders-changed`,
reloadTrigger: { key: `archive-folders-changed` },
});
const archiveFilesLoader = ({ folder }) => ({
url: 'archive/files',
params: { folder },
reloadTrigger: `archive-files-changed-${folder}`,
reloadTrigger: { key: `archive-files-changed`, folder },
});
const appFoldersLoader = () => ({
url: 'apps/folders',
params: {},
reloadTrigger: `app-folders-changed`,
reloadTrigger: { key: `app-folders-changed` },
});
const appFilesLoader = ({ folder }) => ({
url: 'apps/files',
params: { folder },
reloadTrigger: `app-files-changed-${folder}`,
reloadTrigger: { key: `app-files-changed`, app: folder },
});
// const dbAppsLoader = ({ conid, database }) => ({
@@ -127,41 +127,41 @@ const appFilesLoader = ({ folder }) => ({
const usedAppsLoader = ({ conid, database }) => ({
url: 'apps/get-used-apps',
params: {},
reloadTrigger: `used-apps-changed`,
reloadTrigger: { key: `used-apps-changed` },
});
const serverStatusLoader = () => ({
url: 'server-connections/server-status',
params: {},
reloadTrigger: `server-status-changed`,
reloadTrigger: { key: `server-status-changed` },
});
const connectionListLoader = () => ({
url: 'connections/list',
params: {},
reloadTrigger: `connection-list-changed`,
reloadTrigger: { key: `connection-list-changed` },
});
const installedPluginsLoader = () => ({
url: 'plugins/installed',
params: {},
reloadTrigger: `installed-plugins-changed`,
reloadTrigger: { key: `installed-plugins-changed` },
});
const filesLoader = ({ folder }) => ({
url: 'files/list',
params: { folder },
reloadTrigger: `files-changed-${folder}`,
reloadTrigger: { key: `files-changed`, folder },
});
const allFilesLoader = () => ({
url: 'files/list-all',
params: {},
reloadTrigger: `all-files-changed`,
reloadTrigger: { key: `all-files-changed` },
});
const authTypesLoader = ({ engine }) => ({
url: 'plugins/auth-types',
params: { engine },
reloadTrigger: `installed-plugins-changed`,
reloadTrigger: { key: `installed-plugins-changed` },
errorValue: null,
});

View File

@@ -3,7 +3,7 @@
import InlineButton from '../buttons/InlineButton.svelte';
import SearchInput from '../elements/SearchInput.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 AppObjectList from '../appobj/AppObjectList.svelte';
import * as connectionAppObject from '../appobj/ConnectionAppObject.svelte';
@@ -21,7 +21,7 @@
import { useConnectionColorFactory } from '../utility/useConnectionColor';
import FontIcon from '../icons/FontIcon.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 { plusExpandIcon, chevronExpandIcon } from '../icons/expandIcons';
import { safeJsonParse } from 'dbgate-tools';
@@ -33,7 +33,7 @@
$: connectionsWithStatus =
$connections && $serverStatus
? $connections.map(conn => ({ ...conn, status: $serverStatus[conn._id] }))
? $connections.map(conn => ({ ...conn, status: $serverStatus[getVolatileRemapping(conn._id)] }))
: $connections;
$: connectionsWithStatusFiltered = connectionsWithStatus?.filter(

View File

@@ -12,6 +12,7 @@
import WidgetColumnBarItem from './WidgetColumnBarItem.svelte';
import SqlObjectList from './SqlObjectList.svelte';
import DbKeysTree from './DbKeysTree.svelte';
import SingleConnectionDatabaseList from './SingleConnectionDatabaseList.svelte';
export let hidden = false;
@@ -24,7 +25,11 @@
</script>
<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">
<ConnectionList />
</WidgetColumnBarItem>

View 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>

View File

@@ -1,11 +1,11 @@
<script lang="ts" context="module">
const getCurrentValueMarker: any = {};
export function shouldShowTab(tab, singleDbMode = getCurrentValueMarker, currentDb = getCurrentValueMarker) {
if (singleDbMode == getCurrentValueMarker) {
singleDbMode = getSingleDatabaseMode();
export function shouldShowTab(tab, lockedDbMode = getCurrentValueMarker, currentDb = getCurrentValueMarker) {
if (lockedDbMode == getCurrentValueMarker) {
lockedDbMode = getLockedDatabaseMode();
}
if (singleDbMode) {
if (lockedDbMode) {
if (currentDb == getCurrentValueMarker) {
currentDb = getCurrentDatabase();
}
@@ -250,8 +250,8 @@
activeTabId,
getActiveTabId,
getCurrentDatabase,
singleDatabaseMode,
getSingleDatabaseMode,
lockedDatabaseMode,
getLockedDatabaseMode,
} from '../stores';
import tabs from '../tabs';
import { setSelectedTab } from '../utility/common';
@@ -264,7 +264,7 @@
import TabCloseButton from '../elements/TabCloseButton.svelte';
import CloseTabModal from '../modals/CloseTabModal.svelte';
$: showTabFilterFunc = tab => shouldShowTab(tab, $singleDatabaseMode, $currentDatabase);
$: showTabFilterFunc = tab => shouldShowTab(tab, $lockedDatabaseMode, $currentDatabase);
$: connectionList = useConnectionList();
$: currentDbKey =
@@ -443,7 +443,7 @@
<div class="tabs" on:wheel={handleTabsWheel} bind:this={domTabs}>
{#each groupedTabs as tabGroup}
<div class="db-wrapper">
{#if !$singleDatabaseMode}
{#if !$lockedDatabaseMode}
<div
class="db-name"
class:selected={draggingDbGroup

View File

@@ -7,7 +7,7 @@
visibleSelectedWidget,
visibleWidgetSideBar,
visibleHamburgerMenuWidget,
singleDatabaseMode,
lockedDatabaseMode,
} from '../stores';
import mainMenuDefinition from '../../../../app/src/mainMenuDefinition';
import hasPermission from '../utility/hasPermission';
@@ -112,12 +112,12 @@
<div
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={() => {
$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 class="wrapper" on:click={handleSettingsMenu} bind:this={domSettings}>
<FontIcon icon="icon settings" />