diff --git a/packages/api/src/proc/databaseConnectionProcess.js b/packages/api/src/proc/databaseConnectionProcess.js index aea805911..048fc0909 100644 --- a/packages/api/src/proc/databaseConnectionProcess.js +++ b/packages/api/src/proc/databaseConnectionProcess.js @@ -211,6 +211,10 @@ async function handleLoadKeyInfo({ msgid, key }) { async function handleCallMethod({ msgid, method, args }) { return handleDriverDataCore(msgid, driver => { + if (storedConnection.isReadOnly) { + throw new Error('Connection is read only, cannot call custom methods'); + } + ensureExecuteCustomScript(driver); return driver.callMethod(systemConnection, method, args); }); diff --git a/packages/web/src/widgets/DbKeysTreeNode.svelte b/packages/web/src/widgets/DbKeysTreeNode.svelte index 600f24b90..90e282b77 100644 --- a/packages/web/src/widgets/DbKeysTreeNode.svelte +++ b/packages/web/src/widgets/DbKeysTreeNode.svelte @@ -3,15 +3,15 @@ import AppObjectCore from '../appobj/AppObjectCore.svelte'; import { plusExpandIcon } from '../icons/expandIcons'; - import FontIcon from '../icons/FontIcon.svelte'; import ConfirmModal from '../modals/ConfirmModal.svelte'; import InputTextModal from '../modals/InputTextModal.svelte'; import { showModal } from '../modals/modalTools'; import newQuery from '../query/newQuery'; import { activeDbKeysStore } from '../stores'; import { apiCall } from '../utility/api'; + import { getConnectionInfo } from '../utility/metadataLoaders'; import openNewTab from '../utility/openNewTab'; -import { showSnackbarError } from '../utility/snackbar'; + import { showSnackbarError } from '../utility/snackbar'; import DbKeysSubTree from './DbKeysSubTree.svelte'; @@ -29,56 +29,60 @@ import { showSnackbarError } from '../utility/snackbar'; let reloadToken = 0; // $: console.log(item.text, indentLevel); - function createMenu() { + async function createMenu() { + const connection = await getConnectionInfo({ conid }); return [ - item.key != null && { - label: 'Delete key', - onClick: () => { - showModal(ConfirmModal, { - message: `Really delete key ${item.key}?`, - onConfirm: async () => { - await apiCall('database-connections/call-method', { - conid, - database, - method: 'del', - args: [item.key], - }); + item.key != null && + !connection?.isReadOnly && { + label: 'Delete key', + onClick: () => { + showModal(ConfirmModal, { + message: `Really delete key ${item.key}?`, + onConfirm: async () => { + await apiCall('database-connections/call-method', { + conid, + database, + method: 'del', + args: [item.key], + }); - if (onRefreshParent) { - onRefreshParent(); - } - }, - }); + if (onRefreshParent) { + onRefreshParent(); + } + }, + }); + }, }, - }, - item.key != null && { - label: 'Rename key', - onClick: () => { - showModal(InputTextModal, { - value: item.key, - label: 'New name', - header: 'Rename key', - onConfirm: async newName => { - await apiCall('database-connections/call-method', { - conid, - database, - method: 'rename', - args: [item.key, newName], - }); + item.key != null && + !connection?.isReadOnly && { + label: 'Rename key', + onClick: () => { + showModal(InputTextModal, { + value: item.key, + label: 'New name', + header: 'Rename key', + onConfirm: async newName => { + await apiCall('database-connections/call-method', { + conid, + database, + method: 'rename', + args: [item.key, newName], + }); - if (onRefreshParent) { - onRefreshParent(); - } - }, - }); + if (onRefreshParent) { + onRefreshParent(); + } + }, + }); + }, }, - }, - item.type == 'dir' && { - label: 'Reload', - onClick: () => { - reloadToken += 1; + item.type == 'dir' && + !connection?.isReadOnly && { + label: 'Reload', + onClick: () => { + reloadToken += 1; + }, }, - }, item.type == 'dir' && { label: 'Delete branch', onClick: () => { diff --git a/plugins/dbgate-plugin-redis/src/backend/driver.js b/plugins/dbgate-plugin-redis/src/backend/driver.js index a788cbf53..21310e48b 100644 --- a/plugins/dbgate-plugin-redis/src/backend/driver.js +++ b/plugins/dbgate-plugin-redis/src/backend/driver.js @@ -80,16 +80,22 @@ function splitCommandLine(str) { const driver = { ...driverBase, analyserClass: Analyser, - async connect({ server, port, password, database }) { + async connect({ server, port, password, database, useDatabaseUrl, databaseUrl }) { let db = 0; - if (_.isString(database) && database.startsWith('db')) db = parseInt(database.substring(2)); - if (_.isNumber(database)) db = database; - const pool = new Redis({ - host: server, - port, - password, - db, - }); + let pool; + if (useDatabaseUrl) { + pool = new Redis(databaseUrl); + } else { + if (_.isString(database) && database.startsWith('db')) db = parseInt(database.substring(2)); + if (_.isNumber(database)) db = database; + pool = new Redis({ + host: server, + port, + password, + db, + }); + } + return pool; }, // @ts-ignore diff --git a/plugins/dbgate-plugin-redis/src/frontend/driver.js b/plugins/dbgate-plugin-redis/src/frontend/driver.js index 843b41d10..8dab3bf0d 100644 --- a/plugins/dbgate-plugin-redis/src/frontend/driver.js +++ b/plugins/dbgate-plugin-redis/src/frontend/driver.js @@ -26,6 +26,7 @@ const driver = { databaseEngineTypes: ['keyvalue'], supportedCreateDatabase: false, getQuerySplitterOptions: () => redisSplitterOptions, + databaseUrlPlaceholder: 'e.g. redis://:authpassword@127.0.0.1:6380/4', supportedKeyTypes: [ { name: 'string', @@ -75,7 +76,11 @@ const driver = { ], showConnectionField: (field, values) => { - return ['server', 'port', 'password'].includes(field); + if (field == 'useDatabaseUrl') return true; + if (values.useDatabaseUrl) { + return ['databaseUrl', 'isReadOnly'].includes(field); + } + return ['server', 'port', 'password', 'isReadOnly'].includes(field); }, };