redis load key refactor

This commit is contained in:
SPRINX0\prochazka
2025-05-14 12:44:53 +02:00
parent b16b02c3f1
commit bb076cce5d
5 changed files with 97 additions and 58 deletions

View File

@@ -19,6 +19,12 @@ export interface DbKeysLeafNodeModel extends DbKeysNodeModelBase {
export interface DbKeysFolderNodeModel extends DbKeysNodeModelBase { export interface DbKeysFolderNodeModel extends DbKeysNodeModelBase {
// root: string; // root: string;
type: 'dir'; type: 'dir';
// visibleCount?: number;
// isExpanded?: boolean;
}
export interface DbKeysFolderStateMode {
key: string;
visibleCount?: number; visibleCount?: number;
isExpanded?: boolean; isExpanded?: boolean;
} }
@@ -27,6 +33,7 @@ export interface DbKeysTreeModel {
treeKeySeparator: string; treeKeySeparator: string;
root: DbKeysFolderNodeModel; root: DbKeysFolderNodeModel;
dirsByKey: { [key: string]: DbKeysFolderNodeModel }; dirsByKey: { [key: string]: DbKeysFolderNodeModel };
dirStateByKey: { [key: string]: DbKeysFolderStateMode };
childrenByKey: { [key: string]: DbKeysNodeModel[] }; childrenByKey: { [key: string]: DbKeysNodeModel[] };
keyObjectsByKey: { [key: string]: DbKeysNodeModel }; keyObjectsByKey: { [key: string]: DbKeysNodeModel };
scannedKeys: number; scannedKeys: number;
@@ -54,7 +61,10 @@ export interface DbKeysLoadResult {
// export type DbKeysLoadFunction = (root: string, limit: number) => Promise<DbKeysLoadResult>; // export type DbKeysLoadFunction = (root: string, limit: number) => Promise<DbKeysLoadResult>;
export type DbKeysChangeModelFunction = (func: (model: DbKeysTreeModel) => DbKeysTreeModel) => void; export type DbKeysChangeModelFunction = (
func: (model: DbKeysTreeModel) => DbKeysTreeModel,
loadNextPage: boolean
) => void;
// function dbKeys_findFolderNode(node: DbKeysNodeModel, root: string) { // function dbKeys_findFolderNode(node: DbKeysNodeModel, root: string) {
// if (node.type != 'dir') { // if (node.type != 'dir') {
@@ -174,13 +184,11 @@ export function dbKeys_mergeNextPage(tree: DbKeysTreeModel, nextPage: DbKeysLoad
const newDirKey = newDirPath.join(tree.treeKeySeparator); const newDirKey = newDirPath.join(tree.treeKeySeparator);
if (!dirsByKey[newDirKey]) { if (!dirsByKey[newDirKey]) {
dirsByKey[newDirKey] = { dirsByKey[newDirKey] = {
isExpanded: tree.dirsByKey[newDirKey]?.isExpanded ?? false,
level: keyObj.level - 1, level: keyObj.level - 1,
keyPath: newDirPath, keyPath: newDirPath,
parentKey: newDirPath.slice(0, -1).join(tree.treeKeySeparator), parentKey: newDirPath.slice(0, -1).join(tree.treeKeySeparator),
type: 'dir', type: 'dir',
key: newDirKey, key: newDirKey,
visibleCount: tree.dirsByKey[newDirKey]?.visibleCount ?? SHOW_INCREMENT,
text: `${newDirPath[newDirPath.length - 1]}${tree.treeKeySeparator}*`, text: `${newDirPath[newDirPath.length - 1]}${tree.treeKeySeparator}*`,
}; };
} }
@@ -204,6 +212,9 @@ export function dbKeys_mergeNextPage(tree: DbKeysTreeModel, nextPage: DbKeysLoad
childrenByKey[dirObj.parentKey] = []; childrenByKey[dirObj.parentKey] = [];
} }
childrenByKey[dirObj.parentKey].push(dirObj); childrenByKey[dirObj.parentKey].push(dirObj);
// set key count
dirsByKey[dirObj.key].count = childrenByKey[dirObj.key].length;
} }
for (const key in childrenByKey) { for (const key in childrenByKey) {
@@ -223,14 +234,11 @@ export function dbKeys_mergeNextPage(tree: DbKeysTreeModel, nextPage: DbKeysLoad
} }
export function dbKeys_markNodeExpanded(tree: DbKeysTreeModel, root: string, isExpanded: boolean): DbKeysTreeModel { export function dbKeys_markNodeExpanded(tree: DbKeysTreeModel, root: string, isExpanded: boolean): DbKeysTreeModel {
const node = tree.dirsByKey[root]; const node = tree.dirStateByKey[root];
if (!node) {
return tree;
}
return { return {
...tree, ...tree,
dirsByKey: { dirStateByKey: {
...tree.dirsByKey, ...tree.dirStateByKey,
[root]: { [root]: {
...node, ...node,
isExpanded, isExpanded,
@@ -239,24 +247,28 @@ export function dbKeys_markNodeExpanded(tree: DbKeysTreeModel, root: string, isE
}; };
} }
export function dbKeys_refreshAll(treeKeySeparator: string, tree?: DbKeysTreeModel): DbKeysTreeModel { export function dbKeys_createNewModel(treeKeySeparator: string): DbKeysTreeModel {
const root: DbKeysFolderNodeModel = { const root: DbKeysFolderNodeModel = {
isExpanded: true,
level: 0, level: 0,
type: 'dir', type: 'dir',
keyPath: [], keyPath: [],
parentKey: '', parentKey: '',
key: '', key: '',
visibleCount: SHOW_INCREMENT,
}; };
return { return {
...tree,
treeKeySeparator, treeKeySeparator,
childrenByKey: {}, childrenByKey: {},
keyObjectsByKey: {}, keyObjectsByKey: {},
dirsByKey: { dirsByKey: {
'': root, '': root,
}, },
dirStateByKey: {
'': {
key: '',
visibleCount: SHOW_INCREMENT,
isExpanded: true,
},
},
scannedKeys: 0, scannedKeys: 0,
dbsize: 0, dbsize: 0,
loadCount: 2000, loadCount: 2000,
@@ -266,6 +278,21 @@ export function dbKeys_refreshAll(treeKeySeparator: string, tree?: DbKeysTreeMod
}; };
} }
export function dbKeys_clearLoadedData(tree: DbKeysTreeModel): DbKeysTreeModel {
return {
...tree,
childrenByKey: {},
keyObjectsByKey: {},
dirsByKey: {
'': tree.root,
},
scannedKeys: 0,
dbsize: 0,
cursor: '0',
loadedAll: false,
};
}
// export function dbKeys_reloadFolder(tree: DbKeysTreeModel, root: string): DbKeysTreeModel { // export function dbKeys_reloadFolder(tree: DbKeysTreeModel, root: string): DbKeysTreeModel {
// return { // return {
// ...tree, // ...tree,
@@ -282,8 +309,8 @@ export function dbKeys_refreshAll(treeKeySeparator: string, tree?: DbKeysTreeMod
// } // }
function addFlatItems(tree: DbKeysTreeModel, root: string, res: DbKeysNodeModel[], visitedRoots: string[] = []) { function addFlatItems(tree: DbKeysTreeModel, root: string, res: DbKeysNodeModel[], visitedRoots: string[] = []) {
const item = tree.dirsByKey[root]; const item = tree.dirStateByKey[root];
if (!item.isExpanded) { if (!item?.isExpanded) {
return false; return false;
} }
const children = tree.childrenByKey[root] || []; const children = tree.childrenByKey[root] || [];

View File

@@ -46,7 +46,7 @@
icon="icon dots-horizontal" icon="icon dots-horizontal"
expandIcon="icon invisible-box" expandIcon="icon invisible-box"
on:click={() => { on:click={() => {
changeModel(model => dbKeys_markNodeExpanded(model, key, true)); changeModel(model => dbKeys_markNodeExpanded(model, key, true), false);
}} }}
/> />
{/if} {/if}

View File

@@ -1,9 +1,10 @@
<script lang="ts"> <script lang="ts">
import { import {
dbKeys_clearLoadedData,
dbKeys_createNewModel,
dbKeys_getFlatList, dbKeys_getFlatList,
dbKeys_markNodeExpanded, dbKeys_markNodeExpanded,
dbKeys_mergeNextPage, dbKeys_mergeNextPage,
dbKeys_refreshAll,
findEngineDriver, findEngineDriver,
} from 'dbgate-tools'; } from 'dbgate-tools';
@@ -32,7 +33,6 @@
import AppObjectListHandler from './AppObjectListHandler.svelte'; import AppObjectListHandler from './AppObjectListHandler.svelte';
import { getOpenDetailOnArrowsSettings } from '../settings/settingsTools'; import { getOpenDetailOnArrowsSettings } from '../settings/settingsTools';
import openNewTab from '../utility/openNewTab'; import openNewTab from '../utility/openNewTab';
import clickOutside from '../utility/clickOutside';
export let conid; export let conid;
export let database; export let database;
@@ -43,12 +43,9 @@
let domFilter = null; let domFilter = null;
let filter; let filter;
let isLoading = false;
let model = dbKeys_refreshAll(treeKeySeparator); let model = dbKeys_createNewModel(treeKeySeparator);
function handleRefreshDatabase() {
changeModel(model => dbKeys_refreshAll(treeKeySeparator, model));
}
function handleAddKey() { function handleAddKey() {
const connection = $currentDatabase?.connection; const connection = $currentDatabase?.connection;
@@ -69,7 +66,7 @@
args: [item.keyName, ...type.dbKeyFields.map(fld => item[fld.name])], args: [item.keyName, ...type.dbKeyFields.map(fld => item[fld.name])],
}); });
handleRefreshDatabase(); reloadModel();
}, },
}); });
} }
@@ -81,11 +78,13 @@
$: connection = useConnectionInfo({ conid }); $: connection = useConnectionInfo({ conid });
function changeModel(modelUpdate) { function changeModel(modelUpdate, loadNext) {
model = modelUpdate(model); if (modelUpdate) model = modelUpdate(model);
if (loadNext) loadNextPage();
} }
async function loadNextPage() { async function loadNextPage() {
isLoading = true;
const nextScan = await apiCall('database-connections/scan-keys', { const nextScan = await apiCall('database-connections/scan-keys', {
conid, conid,
database, database,
@@ -95,11 +94,11 @@
}); });
model = dbKeys_mergeNextPage(model, nextScan); model = dbKeys_mergeNextPage(model, nextScan);
isLoading = false;
} }
function reloadModel() { function reloadModel() {
changeModel(model => dbKeys_refreshAll(treeKeySeparator, model)); changeModel(model => dbKeys_clearLoadedData(model), true);
loadNextPage();
} }
$: { $: {
@@ -109,7 +108,7 @@
reloadModel(); reloadModel();
} }
$: console.log('DbKeysTree MODEL', model); // $: console.log('DbKeysTree MODEL', model);
</script> </script>
<SearchBoxWrapper noMargin> <SearchBoxWrapper noMargin>
@@ -126,20 +125,32 @@
<InlineButton on:click={handleAddKey} title="Add new key"> <InlineButton on:click={handleAddKey} title="Add new key">
<FontIcon icon="icon plus-thick" /> <FontIcon icon="icon plus-thick" />
</InlineButton> </InlineButton>
<InlineButton on:click={handleRefreshDatabase} title="Refresh key list"> <InlineButton on:click={reloadModel} title="Refresh key list">
<FontIcon icon="icon refresh" /> <FontIcon icon="icon refresh" />
</InlineButton> </InlineButton>
</SearchBoxWrapper> </SearchBoxWrapper>
{#if !model?.loadedAll}
<div class="space-between align-items-center ml-1"> <div class="space-between align-items-center ml-1">
{#if model} {#if model}
<div> <div>
{#if isLoading}
Loading...
{:else}
Scanned {Math.min(model?.scannedKeys, model?.dbsize) ?? '???'}/{model?.dbsize ?? '???'} Scanned {Math.min(model?.scannedKeys, model?.dbsize) ?? '???'}/{model?.dbsize ?? '???'}
{/if}
</div> </div>
{/if} {/if}
{#if isLoading}
<div style="margin: 3px; margin-bottom: 2px">
<FontIcon icon="icon loading" />
</div>
{:else}
<InlineButton on:click={loadNextPage} title="Scan more keys"> <InlineButton on:click={loadNextPage} title="Scan more keys">
<FontIcon icon="icon more" /> Scan more <FontIcon icon="icon more" /> Scan more
</InlineButton> </InlineButton>
{/if}
</div> </div>
{/if}
{#if differentFocusedDb} {#if differentFocusedDb}
<FocusedConnectionInfoWidget {conid} {database} connection={$connection} /> <FocusedConnectionInfoWidget {conid} {database} connection={$connection} />
{/if} {/if}
@@ -172,11 +183,11 @@
}; };
} }
if (data.key && clickAction == 'keyEnter') { if (data.key && clickAction == 'keyEnter') {
changeModel(model => dbKeys_markNodeExpanded(model, data.key, !model.dirsByKey[data.key]?.isExpanded)); changeModel(model => dbKeys_markNodeExpanded(model, data.key, !model.dirsByKey[data.key]?.isExpanded), false);
} }
}} }}
handleExpansion={(data, value) => { handleExpansion={(data, value) => {
changeModel(model => dbKeys_markNodeExpanded(model, data.key, value)); changeModel(model => dbKeys_markNodeExpanded(model, data.key, value), false);
}} }}
onScrollTop={() => { onScrollTop={() => {
domContainer?.scrollTop(); domContainer?.scrollTop();

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { import {
dbKeys_clearLoadedData,
dbKeys_markNodeExpanded, dbKeys_markNodeExpanded,
dbKeys_reloadFolder,
DbKeysChangeModelFunction, DbKeysChangeModelFunction,
DbKeysTreeModel, DbKeysTreeModel,
getIconForRedisType, getIconForRedisType,
@@ -36,7 +36,7 @@
export let model: DbKeysTreeModel; export let model: DbKeysTreeModel;
export let changeModel: DbKeysChangeModelFunction; export let changeModel: DbKeysChangeModelFunction;
$: isExpanded = model.dirsByKey[item.key]?.isExpanded; $: isExpanded = model.dirStateByKey[item.key]?.isExpanded;
// $: console.log(item.text, indentLevel); // $: console.log(item.text, indentLevel);
function createMenu() { function createMenu() {
@@ -55,7 +55,7 @@
args: [item.key], args: [item.key],
}); });
changeModel(m => dbKeys_reloadFolder(m, key)); changeModel(m => dbKeys_clearLoadedData(m), true);
}, },
}); });
}, },
@@ -76,23 +76,23 @@
args: [item.key, newName], args: [item.key, newName],
}); });
changeModel(m => dbKeys_reloadFolder(m, key)); changeModel(m => dbKeys_clearLoadedData(m), true);
}, },
}); });
}, },
}, },
item.type == 'dir' && // item.type == 'dir' &&
!connection?.isReadOnly && { // !connection?.isReadOnly && {
label: 'Reload', // label: 'Reload',
onClick: () => { // onClick: () => {
changeModel(m => dbKeys_reloadFolder(m, key)); // changeModel(m => dbKeys_clearLoadedData(m), true);
}, // },
}, // },
item.type == 'dir' && item.type == 'dir' &&
!connection?.isReadOnly && { !connection?.isReadOnly && {
label: 'Delete branch', label: 'Delete branch',
onClick: () => { onClick: () => {
const branch = `${item.root}:*`; const branch = `${item.key}:*`;
showModal(ConfirmModal, { showModal(ConfirmModal, {
message: `Really delete branch ${branch} with all keys?`, message: `Really delete branch ${branch} with all keys?`,
onConfirm: async () => { onConfirm: async () => {
@@ -103,7 +103,7 @@
args: [branch], args: [branch],
}); });
changeModel(m => dbKeys_reloadFolder(m, key)); changeModel(m => dbKeys_clearLoadedData(m), true);
}, },
}); });
}, },
@@ -141,12 +141,12 @@
expandIcon={item.type == 'dir' ? plusExpandIcon(isExpanded) : 'icon invisible-box'} expandIcon={item.type == 'dir' ? plusExpandIcon(isExpanded) : 'icon invisible-box'}
on:expand={() => { on:expand={() => {
if (item.type == 'dir') { if (item.type == 'dir') {
changeModel(tree => dbKeys_markNodeExpanded(tree, item.key, !isExpanded)); changeModel(tree => dbKeys_markNodeExpanded(tree, item.key, !isExpanded), false);
} }
}} }}
on:click={() => { on:click={() => {
if (item.type == 'dir') { if (item.type == 'dir') {
changeModel(tree => dbKeys_markNodeExpanded(tree, item.key, !isExpanded)); changeModel(tree => dbKeys_markNodeExpanded(tree, item.key, !isExpanded), false);
} else { } else {
openNewTab({ openNewTab({
tabComponent: 'DbKeyDetailTab', tabComponent: 'DbKeyDetailTab',

View File

@@ -202,7 +202,8 @@ const driver = {
}, },
async scanKeys(dbhan, pattern, cursor = 0, count) { async scanKeys(dbhan, pattern, cursor = 0, count) {
const [nextCursor, keys] = await dbhan.client.scan(cursor, 'MATCH', pattern || '*', 'COUNT', count); const match = pattern?.match(/[\?\[\{]/) ? pattern : pattern ? `*${pattern}*` : '*';
const [nextCursor, keys] = await dbhan.client.scan(cursor, 'MATCH', match, 'COUNT', count);
const dbsize = await dbhan.client.dbsize(); const dbsize = await dbhan.client.dbsize();
const keysMapped = keys.map((key) => ({ const keysMapped = keys.map((key) => ({
key, key,