mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-28 15:16:00 +00:00
redis key browser works
This commit is contained in:
@@ -161,6 +161,26 @@ module.exports = {
|
|||||||
return res.result || null;
|
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,
|
updateCollection_meta: true,
|
||||||
async updateCollection({ conid, database, changeSet }) {
|
async updateCollection({ conid, database, changeSet }) {
|
||||||
const opened = await this.ensureOpened(conid, database);
|
const opened = await this.ensureOpened(conid, database);
|
||||||
|
|||||||
@@ -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 }) {
|
async function handleUpdateCollection({ msgid, changeSet }) {
|
||||||
await waitConnected();
|
await waitConnected();
|
||||||
const driver = requireEngineDriver(storedConnection);
|
const driver = requireEngineDriver(storedConnection);
|
||||||
@@ -260,6 +282,8 @@ const messageHandlers = {
|
|||||||
updateCollection: handleUpdateCollection,
|
updateCollection: handleUpdateCollection,
|
||||||
collectionData: handleCollectionData,
|
collectionData: handleCollectionData,
|
||||||
loadKeys: handleLoadKeys,
|
loadKeys: handleLoadKeys,
|
||||||
|
loadKeyInfo: handleLoadKeyInfo,
|
||||||
|
loadKeyTableRange: handleLoadKeyTableRange,
|
||||||
sqlPreview: handleSqlPreview,
|
sqlPreview: handleSqlPreview,
|
||||||
ping: handlePing,
|
ping: handlePing,
|
||||||
syncModel: handleSyncModel,
|
syncModel: handleSyncModel,
|
||||||
|
|||||||
@@ -59,3 +59,28 @@ export function safeJsonParse(json, defaultValue?, logError = false) {
|
|||||||
export function isJsonLikeLongString(value) {
|
export function isJsonLikeLongString(value) {
|
||||||
return _isString(value) && value.length > 100 && value.match(/^\s*\{.*\}\s*$|^\s*\[.*\]\s*$/);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
2
packages/types/engines.d.ts
vendored
2
packages/types/engines.d.ts
vendored
@@ -76,6 +76,8 @@ export interface EngineDriver {
|
|||||||
}[]
|
}[]
|
||||||
>;
|
>;
|
||||||
loadKeys(pool, root: string): Promise;
|
loadKeys(pool, root: string): Promise;
|
||||||
|
loadKeyInfo(pool, key): Promise;
|
||||||
|
loadKeyTableRange(pool, key, cursor, count): Promise;
|
||||||
analyseFull(pool: any, serverVersion): Promise<DatabaseInfo>;
|
analyseFull(pool: any, serverVersion): Promise<DatabaseInfo>;
|
||||||
analyseIncremental(pool: any, structure: DatabaseInfo, serverVersion): Promise<DatabaseInfo>;
|
analyseIncremental(pool: any, structure: DatabaseInfo, serverVersion): Promise<DatabaseInfo>;
|
||||||
dialect: SqlDialect;
|
dialect: SqlDialect;
|
||||||
|
|||||||
41
packages/web/src/datagrid/DbKeyTableControl.svelte
Normal file
41
packages/web/src/datagrid/DbKeyTableControl.svelte
Normal 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}
|
||||||
|
/>
|
||||||
@@ -2,10 +2,19 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
export let value;
|
export let value;
|
||||||
export let focused;
|
export let focused = false;
|
||||||
export let domEditor;
|
export let domEditor = undefined;
|
||||||
|
|
||||||
if (focused) onMount(() => domEditor.focus());
|
if (focused) onMount(() => domEditor.focus());
|
||||||
</script>
|
</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"
|
||||||
|
/>
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ export const loadingPluginStore = writable({
|
|||||||
loaded: false,
|
loaded: false,
|
||||||
loadingPackageName: null,
|
loadingPackageName: null,
|
||||||
});
|
});
|
||||||
|
export const activeDbKeysStore = writableWithStorage({}, 'activeDbKeysStore');
|
||||||
|
|
||||||
export const currentThemeDefinition = derived([currentTheme, extensions], ([$currentTheme, $extensions]) =>
|
export const currentThemeDefinition = derived([currentTheme, extensions], ([$currentTheme, $extensions]) =>
|
||||||
$extensions.themes.find(x => x.themeClassName == $currentTheme)
|
$extensions.themes.find(x => x.themeClassName == $currentTheme)
|
||||||
|
|||||||
@@ -8,10 +8,81 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 conid;
|
||||||
export let database;
|
export let database;
|
||||||
export let key;
|
export let key;
|
||||||
export let isDefaultBrowser = false;
|
export let isDefaultBrowser = false;
|
||||||
|
|
||||||
export const activator = createActivator('DbKeyDetailTab', true);
|
export const activator = createActivator('DbKeyDetailTab', true);
|
||||||
|
|
||||||
|
$: key = $activeDbKeysStore[`${conid}:${database}`];
|
||||||
|
let refreshToken = 0;
|
||||||
</script>
|
</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>
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { getIconForRedisType } from 'dbgate-tools';
|
||||||
|
|
||||||
import AppObjectCore from '../appobj/AppObjectCore.svelte';
|
import AppObjectCore from '../appobj/AppObjectCore.svelte';
|
||||||
import { plusExpandIcon } from '../icons/expandIcons';
|
import { plusExpandIcon } from '../icons/expandIcons';
|
||||||
import FontIcon from '../icons/FontIcon.svelte';
|
import FontIcon from '../icons/FontIcon.svelte';
|
||||||
|
import { activeDbKeysStore } from '../stores';
|
||||||
import openNewTab from '../utility/openNewTab';
|
import openNewTab from '../utility/openNewTab';
|
||||||
|
|
||||||
import DbKeysSubTree from './DbKeysSubTree.svelte';
|
import DbKeysSubTree from './DbKeysSubTree.svelte';
|
||||||
@@ -16,36 +19,12 @@
|
|||||||
|
|
||||||
let isExpanded;
|
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);
|
// $: console.log(item.text, indentLevel);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AppObjectCore
|
<AppObjectCore
|
||||||
icon={getIconForType(item.type)}
|
icon={getIconForRedisType(item.type)}
|
||||||
title={item.text}
|
title={item.text}
|
||||||
expandIcon={item.type == 'dir' ? plusExpandIcon(isExpanded) : 'icon invisible-box'}
|
expandIcon={item.type == 'dir' ? plusExpandIcon(isExpanded) : 'icon invisible-box'}
|
||||||
on:expand={() => {
|
on:expand={() => {
|
||||||
@@ -66,6 +45,10 @@
|
|||||||
database,
|
database,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
$activeDbKeysStore = {
|
||||||
|
...$activeDbKeysStore,
|
||||||
|
[`${conid}:${database}`]: item.key,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
extInfo={item.count ? `(${item.count})` : null}
|
extInfo={item.count ? `(${item.count})` : null}
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ const driver = {
|
|||||||
const type = await pool.type(key);
|
const type = await pool.type(key);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'list': {
|
case 'list': {
|
||||||
const res = await pool.lrange(key, cursor, start + count);
|
const res = await pool.lrange(key, cursor, cursor + count);
|
||||||
return {
|
return {
|
||||||
cursor: res.length > count ? cursor + count : 0,
|
cursor: res.length > count ? cursor + count : 0,
|
||||||
items: res.map((value) => ({ value })).slice(0, count),
|
items: res.map((value) => ({ value })).slice(0, count),
|
||||||
|
|||||||
Reference in New Issue
Block a user