diff --git a/packages/web/src/tabs/DbKeyDetailTab.svelte b/packages/web/src/tabs/DbKeyDetailTab.svelte
new file mode 100644
index 000000000..e1cfa83fd
--- /dev/null
+++ b/packages/web/src/tabs/DbKeyDetailTab.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/packages/web/src/tabs/index.js b/packages/web/src/tabs/index.js
index fa5c843c9..18d6fafc1 100644
--- a/packages/web/src/tabs/index.js
+++ b/packages/web/src/tabs/index.js
@@ -21,6 +21,7 @@ import * as CompareModelTab from './CompareModelTab.svelte';
import * as JsonTab from './JsonTab.svelte';
import * as ChangelogTab from './ChangelogTab.svelte';
import * as DiagramTab from './DiagramTab.svelte';
+import * as DbKeyDetailTab from './DbKeyDetailTab.svelte';
export default {
TableDataTab,
@@ -46,4 +47,5 @@ export default {
JsonTab,
ChangelogTab,
DiagramTab,
+ DbKeyDetailTab,
};
diff --git a/packages/web/src/widgets/DbKeysTreeNode.svelte b/packages/web/src/widgets/DbKeysTreeNode.svelte
index 07e6b0491..2e188e185 100644
--- a/packages/web/src/widgets/DbKeysTreeNode.svelte
+++ b/packages/web/src/widgets/DbKeysTreeNode.svelte
@@ -2,6 +2,7 @@
import AppObjectCore from '../appobj/AppObjectCore.svelte';
import { plusExpandIcon } from '../icons/expandIcons';
import FontIcon from '../icons/FontIcon.svelte';
+ import openNewTab from '../utility/openNewTab';
import DbKeysSubTree from './DbKeysSubTree.svelte';
@@ -55,6 +56,16 @@
on:click={() => {
if (item.type == 'dir') {
isExpanded = !isExpanded;
+ } else {
+ openNewTab({
+ tabComponent: 'DbKeyDetailTab',
+ title: 'Key: ' + database,
+ props: {
+ isDefaultBrowser: true,
+ conid,
+ database,
+ },
+ });
}
}}
extInfo={item.count ? `(${item.count})` : null}
diff --git a/plugins/dbgate-plugin-redis/src/backend/driver.js b/plugins/dbgate-plugin-redis/src/backend/driver.js
index adca9a7a0..dbab004a0 100644
--- a/plugins/dbgate-plugin-redis/src/backend/driver.js
+++ b/plugins/dbgate-plugin-redis/src/backend/driver.js
@@ -117,22 +117,24 @@ const driver = {
return Object.values(res);
},
+ async getKeyCardinality(pool, key, type) {
+ switch (type) {
+ case 'list':
+ return pool.llen(key);
+ case 'set':
+ return pool.scard(key);
+ case 'zset':
+ return pool.zcard(key);
+ case 'stream':
+ return pool.xlen(key);
+ case 'hash':
+ return pool.hlen(key);
+ }
+ },
+
async enrichOneKeyInfo(pool, item) {
item.type = await pool.type(item.key);
- switch (item.type) {
- case 'list':
- item.count = await pool.llen(item.key);
- break;
- case 'set':
- item.count = await pool.scard(item.key);
- break;
- case 'zset':
- item.count = await pool.zcard(item.key);
- break;
- case 'stream':
- item.count = await pool.xlen(item.key);
- break;
- }
+ item.count = await this.getKeyCardinality(pool, item.key, item.type);
},
async enrichKeyInfo(pool, levelInfo) {
@@ -142,6 +144,74 @@ const driver = {
async (item) => await this.enrichOneKeyInfo(pool, item)
);
},
+
+ async loadKeyInfo(pool, key) {
+ const res = {};
+ const type = await pool.type(key);
+
+ res.key = key;
+ res.type = type;
+ res.ttl = await pool.ttl(key);
+ res.count = await this.getKeyCardinality(pool, key, type);
+
+ switch (type) {
+ case 'string':
+ res.value = await pool.get(key);
+ break;
+ case 'list':
+ res.tableColumns = ['value'];
+ break;
+ case 'set':
+ res.tableColumns = ['value'];
+ res.keyColumn = 'value';
+ break;
+ case 'zset':
+ res.tableColumns = ['value', 'score'];
+ res.keyColumn = 'value';
+ break;
+ case 'hash':
+ res.tableColumns = ['key', 'value'];
+ res.keyColumn = 'key';
+ break;
+ }
+
+ return res;
+ },
+
+ async loadKeyTableRange(pool, key, cursor, count) {
+ const type = await pool.type(key);
+ switch (type) {
+ case 'list': {
+ const res = await pool.lrange(key, cursor, start + count);
+ return {
+ cursor: res.length > count ? cursor + count : 0,
+ items: res.map((value) => ({ value })).slice(0, count),
+ };
+ }
+ case 'set': {
+ const res = await pool.sscan(key, cursor, 'COUNT', count);
+ return {
+ cursor: parseInt(res[0]),
+ items: res[1].map((value) => ({ value })),
+ };
+ }
+ case 'zset': {
+ const res = await pool.zscan(key, cursor, 'COUNT', count);
+ return {
+ cursor: parseInt(res[0]),
+ items: _.chunk(res[1], 2).map((item) => ({ value: item[0], score: item[1] })),
+ };
+ }
+ case 'hash': {
+ const res = await pool.hscan(key, cursor, 'COUNT', count);
+ return {
+ cursor: parseInt(res[0]),
+ items: _.chunk(res[1], 2).map((item) => ({ key: item[0], value: item[1] })),
+ };
+ }
+ }
+ return null;
+ },
};
module.exports = driver;