redis key browser works

This commit is contained in:
Jan Prochazka
2022-03-07 20:55:29 +01:00
parent 78faa94d77
commit 9b396e7431
10 changed files with 205 additions and 29 deletions

View File

@@ -161,6 +161,26 @@ module.exports = {
return res.result || null;
},
loadKeyInfo_meta: true,
async loadKeyInfo({ conid, database, key }) {
const opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, { msgtype: 'loadKeyInfo', key });
if (res.errorMessage) {
console.error(res.errorMessage);
}
return res.result || null;
},
loadKeyTableRange_meta: true,
async loadKeyTableRange({ conid, database, key, cursor, count }) {
const opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, { msgtype: 'loadKeyTableRange', key, cursor, count });
if (res.errorMessage) {
console.error(res.errorMessage);
}
return res.result || null;
},
updateCollection_meta: true,
async updateCollection({ conid, database, changeSet }) {
const opened = await this.ensureOpened(conid, database);

View File

@@ -194,6 +194,28 @@ async function handleLoadKeys({ msgid, root }) {
}
}
async function handleLoadKeyInfo({ msgid, key }) {
await waitConnected();
const driver = requireEngineDriver(storedConnection);
try {
const result = await driver.loadKeyInfo(systemConnection, key);
process.send({ msgtype: 'response', msgid, result });
} catch (err) {
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
}
}
async function handleLoadKeyTableRange({ msgid, key, cursor, count }) {
await waitConnected();
const driver = requireEngineDriver(storedConnection);
try {
const result = await driver.loadKeyTableRange(systemConnection, key, cursor, count);
process.send({ msgtype: 'response', msgid, result });
} catch (err) {
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
}
}
async function handleUpdateCollection({ msgid, changeSet }) {
await waitConnected();
const driver = requireEngineDriver(storedConnection);
@@ -260,6 +282,8 @@ const messageHandlers = {
updateCollection: handleUpdateCollection,
collectionData: handleCollectionData,
loadKeys: handleLoadKeys,
loadKeyInfo: handleLoadKeyInfo,
loadKeyTableRange: handleLoadKeyTableRange,
sqlPreview: handleSqlPreview,
ping: handlePing,
syncModel: handleSyncModel,

View File

@@ -59,3 +59,28 @@ export function safeJsonParse(json, defaultValue?, logError = false) {
export function isJsonLikeLongString(value) {
return _isString(value) && value.length > 100 && value.match(/^\s*\{.*\}\s*$|^\s*\[.*\]\s*$/);
}
export function getIconForRedisType(type) {
switch (type) {
case 'dir':
return 'img folder';
case 'string':
return 'img type-string';
case 'hash':
return 'img type-hash';
case 'set':
return 'img type-set';
case 'list':
return 'img type-list';
case 'zset':
return 'img type-zset';
case 'stream':
return 'img type-stream';
case 'binary':
return 'img type-binary';
case 'ReJSON-RL':
return 'img type-rejson';
default:
return null;
}
}

View File

@@ -76,6 +76,8 @@ export interface EngineDriver {
}[]
>;
loadKeys(pool, root: string): Promise;
loadKeyInfo(pool, key): Promise;
loadKeyTableRange(pool, key, cursor, count): Promise;
analyseFull(pool: any, serverVersion): Promise<DatabaseInfo>;
analyseIncremental(pool: any, structure: DatabaseInfo, serverVersion): Promise<DatabaseInfo>;
dialect: SqlDialect;

View File

@@ -0,0 +1,41 @@
<script lang="ts">
import { onMount } from 'svelte';
import ScrollableTableControl from '../elements/ScrollableTableControl.svelte';
import { apiCall } from '../utility/api';
export let conid;
export let database;
export let keyInfo;
let rows = [];
let cursor = 0;
async function loadRows() {
const res = await apiCall('database-connections/load-key-table-range', {
conid,
database,
key: keyInfo.key,
cursor,
count: 10,
});
rows = res.items;
}
$: {
keyInfo;
}
onMount(() => {
loadRows();
});
</script>
<ScrollableTableControl
columns={[
keyInfo.tableColumns.map(fieldName => ({
fieldName,
header: fieldName,
})),
]}
{rows}
/>

View File

@@ -2,10 +2,19 @@
import { onMount } from 'svelte';
export let value;
export let focused;
export let domEditor;
export let focused = false;
export let domEditor = undefined;
if (focused) onMount(() => domEditor.focus());
</script>
<input type="text" {...$$restProps} bind:value on:change on:input bind:this={domEditor} on:keydown autocomplete="new-password" />
<input
type="text"
{...$$restProps}
bind:value
on:change
on:input
bind:this={domEditor}
on:keydown
autocomplete="new-password"
/>

View File

@@ -93,6 +93,7 @@ export const loadingPluginStore = writable({
loaded: false,
loadingPackageName: null,
});
export const activeDbKeysStore = writableWithStorage({}, 'activeDbKeysStore');
export const currentThemeDefinition = derived([currentTheme, extensions], ([$currentTheme, $extensions]) =>
$extensions.themes.find(x => x.themeClassName == $currentTheme)

View File

@@ -8,10 +8,81 @@
</script>
<script lang="ts">
import { activeDbKeysStore } from '../stores';
import { apiCall } from '../utility/api';
import LoadingInfo from '../elements/LoadingInfo.svelte';
import ScrollableTableControl from '../elements/ScrollableTableControl.svelte';
import AceEditor from '../query/AceEditor.svelte';
import VerticalSplitter from '../elements/VerticalSplitter.svelte';
import FormStyledButton from '../buttons/FormStyledButton.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import { getIconForRedisType } from 'dbgate-tools';
import TextField from '../forms/TextField.svelte';
import DbKeyTableControl from '../datagrid/DbKeyTableControl.svelte';
export let conid;
export let database;
export let key;
export let isDefaultBrowser = false;
export const activator = createActivator('DbKeyDetailTab', true);
$: key = $activeDbKeysStore[`${conid}:${database}`];
let refreshToken = 0;
</script>
{#await apiCall('database-connections/load-key-info', { conid, database, key, refreshToken })}
<LoadingInfo message="Loading key details" wrapper />
{:then keyInfo}
<div class="container">
<div class="top-panel">
<FontIcon icon={getIconForRedisType(keyInfo.type)} padRight />
<div class="type">
{keyInfo.type}
</div>
<TextField value={key} readOnly />
{key}
TTL:{keyInfo.ttl}
<FormStyledButton
value="Refresh"
on:click={() => {
refreshToken += 1;
}}
/>
</div>
<div class="content">
{#if keyInfo.tableColumns}
<VerticalSplitter>
<svelte:fragment slot="1">
<DbKeyTableControl {conid} {database} {keyInfo} />
</svelte:fragment>
<svelte:fragment slot="2">PROPS</svelte:fragment>
</VerticalSplitter>
{:else}
<AceEditor readOnly value={keyInfo.value} />
{/if}
</div>
</div>
{/await}
<style>
.container {
display: flex;
flex-direction: column;
flex: 1;
}
.content {
flex: 1;
position: relative;
}
.top-panel {
display: flex;
}
.type {
font-weight: bold;
}
</style>

View File

@@ -1,7 +1,10 @@
<script lang="ts">
import { getIconForRedisType } from 'dbgate-tools';
import AppObjectCore from '../appobj/AppObjectCore.svelte';
import { plusExpandIcon } from '../icons/expandIcons';
import FontIcon from '../icons/FontIcon.svelte';
import { activeDbKeysStore } from '../stores';
import openNewTab from '../utility/openNewTab';
import DbKeysSubTree from './DbKeysSubTree.svelte';
@@ -16,36 +19,12 @@
let isExpanded;
function getIconForType(type) {
switch (type) {
case 'dir':
return 'img folder';
case 'string':
return 'img type-string';
case 'hash':
return 'img type-hash';
case 'set':
return 'img type-set';
case 'list':
return 'img type-list';
case 'zset':
return 'img type-zset';
case 'stream':
return 'img type-stream';
case 'binary':
return 'img type-binary';
case 'ReJSON-RL':
return 'img type-rejson';
default:
return null;
}
}
// $: console.log(item.text, indentLevel);
</script>
<AppObjectCore
icon={getIconForType(item.type)}
icon={getIconForRedisType(item.type)}
title={item.text}
expandIcon={item.type == 'dir' ? plusExpandIcon(isExpanded) : 'icon invisible-box'}
on:expand={() => {
@@ -66,6 +45,10 @@
database,
},
});
$activeDbKeysStore = {
...$activeDbKeysStore,
[`${conid}:${database}`]: item.key,
};
}
}}
extInfo={item.count ? `(${item.count})` : null}

View File

@@ -182,7 +182,7 @@ const driver = {
const type = await pool.type(key);
switch (type) {
case 'list': {
const res = await pool.lrange(key, cursor, start + count);
const res = await pool.lrange(key, cursor, cursor + count);
return {
cursor: res.length > count ? cursor + count : 0,
items: res.map((value) => ({ value })).slice(0, count),