redis: set ttl

This commit is contained in:
Jan Prochazka
2022-03-10 17:12:21 +01:00
parent 98a9859216
commit 3d72df424f
8 changed files with 235 additions and 33 deletions

View File

@@ -181,6 +181,16 @@ module.exports = {
return res.result || null; return res.result || null;
}, },
callMethod_meta: true,
async callMethod({ conid, database, method, args }) {
const opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, { msgtype: 'callMethod', method, args });
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);

View File

@@ -205,6 +205,17 @@ async function handleLoadKeyInfo({ msgid, key }) {
} }
} }
async function handleCallMethod({ msgid, method, args }) {
await waitConnected();
const driver = requireEngineDriver(storedConnection);
try {
const result = await driver.callMethod(systemConnection, method, args);
process.send({ msgtype: 'response', msgid, result });
} catch (err) {
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
}
}
async function handleLoadKeyTableRange({ msgid, key, cursor, count }) { async function handleLoadKeyTableRange({ msgid, key, cursor, count }) {
await waitConnected(); await waitConnected();
const driver = requireEngineDriver(storedConnection); const driver = requireEngineDriver(storedConnection);
@@ -283,6 +294,7 @@ const messageHandlers = {
collectionData: handleCollectionData, collectionData: handleCollectionData,
loadKeys: handleLoadKeys, loadKeys: handleLoadKeys,
loadKeyInfo: handleLoadKeyInfo, loadKeyInfo: handleLoadKeyInfo,
callMethod: handleCallMethod,
loadKeyTableRange: handleLoadKeyTableRange, loadKeyTableRange: handleLoadKeyTableRange,
sqlPreview: handleSqlPreview, sqlPreview: handleSqlPreview,
ping: handlePing, ping: handlePing,

View File

@@ -91,6 +91,8 @@ export interface EngineDriver {
getQuerySplitterOptions(usage: 'stream' | 'script'): any; getQuerySplitterOptions(usage: 'stream' | 'script'): any;
script(pool: any, sql: string): Promise; script(pool: any, sql: string): Promise;
getNewObjectTemplates(): NewObjectTemplate[]; getNewObjectTemplates(): NewObjectTemplate[];
// direct call of pool method, only some methods could be supported, on only some drivers
callMethod(pool, method, args);
analyserClass?: any; analyserClass?: any;
dumperClass?: any; dumperClass?: any;

View File

@@ -61,7 +61,7 @@
} }
} }
$: console.log(title, indentLevel); // $: console.log(title, indentLevel);
</script> </script>
<div <div

View File

@@ -1,41 +1,92 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount, tick } from 'svelte';
import ScrollableTableControl from '../elements/ScrollableTableControl.svelte'; import ScrollableTableControl from '../elements/ScrollableTableControl.svelte';
import { apiCall } from '../utility/api'; import { apiCall } from '../utility/api';
import createRef from '../utility/createRef';
export let conid; export let conid;
export let database; export let database;
export let keyInfo; export let keyInfo;
export let onChangeSelected;
let rows = []; let rows = [];
let cursor = 0; let cursor = 0;
let isLoading = false;
let loadNextNeeded = false;
let isLoadedAll = false;
let selectedIndex;
const oldIndexRef = createRef(null);
async function loadRows() { async function loadNextRows() {
const res = await apiCall('database-connections/load-key-table-range', { if (isLoading) {
conid, // console.log('ALREADY LOADING');
database, loadNextNeeded = true;
key: keyInfo.key, return;
cursor, }
count: 10, isLoading = true;
}); try {
rows = res.items; const res = await apiCall('database-connections/load-key-table-range', {
conid,
database,
key: keyInfo.key,
cursor,
count: 10,
});
const newRows = [...rows];
for (const row of res.items) {
if (keyInfo.keyColumn && newRows.find(x => x[keyInfo.keyColumn] == row[keyInfo.keyColumn])) {
continue;
}
newRows.push({ rowNumber: newRows.length + 1, ...row });
}
rows = newRows;
cursor = res.cursor;
isLoadedAll = cursor == 0;
} finally {
isLoading = false;
}
if (loadNextNeeded) {
loadNextNeeded = false;
await tick();
loadNextRows();
}
}
$: {
if (onChangeSelected && rows[selectedIndex]) {
if (oldIndexRef.get() != selectedIndex) {
oldIndexRef.set(selectedIndex);
onChangeSelected(rows[selectedIndex]);
}
}
} }
$: { $: {
keyInfo; keyInfo;
} }
onMount(() => { onMount(() => {
loadRows(); loadNextRows();
}); });
</script> </script>
<ScrollableTableControl <ScrollableTableControl
columns={[ columns={[
keyInfo.tableColumns.map(fieldName => ({ {
fieldName, fieldName: 'rowNumber',
header: fieldName, header: 'num',
width: '60px',
},
...keyInfo.tableColumns.map(column => ({
fieldName: column.name,
header: column.name,
})), })),
]} ]}
{rows} {rows}
onLoadNext={isLoadedAll ? null : loadNextRows}
selectable
singleLineRow
bind:selectedIndex
/> />

View File

@@ -15,7 +15,7 @@
<script lang="ts"> <script lang="ts">
import _ from 'lodash'; import _ from 'lodash';
import { onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
import keycodes from '../utility/keycodes'; import keycodes from '../utility/keycodes';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import resizeObserver from '../utility/resizeObserver'; import resizeObserver from '../utility/resizeObserver';
@@ -27,6 +27,8 @@
export let selectedIndex = 0; export let selectedIndex = 0;
export let clickable = false; export let clickable = false;
export let disableFocusOutline = false; export let disableFocusOutline = false;
export let onLoadNext = null;
export let singleLineRow = false;
export let domTable = undefined; export let domTable = undefined;
@@ -34,6 +36,36 @@
let headerHeight = 0; let headerHeight = 0;
let domBody; let domBody;
let domLoadNext;
let observer;
function startObserver(dom) {
if (observer) {
// console.log('STOP OBSERVE');
observer.disconnect();
observer = null;
}
if (dom) {
// console.log('OBSERVE');
observer = new IntersectionObserver(entries => {
// console.log('ENTRIES', entries);
if (entries.find(x => x.isIntersecting)) {
// console.log('INVOKE LOAD NEXT');
onLoadNext();
}
});
observer.observe(dom);
}
}
$: startObserver(domLoadNext);
onDestroy(() => {
if (observer) {
observer.disconnect();
}
});
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
$: columnList = _.compact(_.flatten(columns)); $: columnList = _.compact(_.flatten(columns));
@@ -86,6 +118,7 @@
bind:this={domTable} bind:this={domTable}
class:selectable class:selectable
class:disableFocusOutline class:disableFocusOutline
class:singleLineRow
on:keydown on:keydown
tabindex={selectable ? -1 : undefined} tabindex={selectable ? -1 : undefined}
on:keydown={handleKeyDown} on:keydown={handleKeyDown}
@@ -163,6 +196,13 @@
{/each} {/each}
</tr> </tr>
{/each} {/each}
{#if onLoadNext}
{#key rows}
<tr>
<td colspan={columnList.length} bind:this={domLoadNext}> Loading next rows... </td>
</tr>
{/key}
{/if}
</tbody> </tbody>
</table> </table>
</div> </div>
@@ -193,6 +233,11 @@
table tbody tr td { table tbody tr td {
overflow: hidden; overflow: hidden;
} }
table.singleLineRow tbody tr td {
white-space: nowrap;
}
table tbody { table tbody {
display: block; display: block;
overflow-y: scroll; overflow-y: scroll;

View File

@@ -19,6 +19,9 @@
import { getIconForRedisType } from 'dbgate-tools'; import { getIconForRedisType } from 'dbgate-tools';
import TextField from '../forms/TextField.svelte'; import TextField from '../forms/TextField.svelte';
import DbKeyTableControl from '../datagrid/DbKeyTableControl.svelte'; import DbKeyTableControl from '../datagrid/DbKeyTableControl.svelte';
import { showModal } from '../modals/modalTools';
import InputTextModal from '../modals/InputTextModal.svelte';
import _ from 'lodash';
export let conid; export let conid;
export let database; export let database;
@@ -27,8 +30,43 @@
export const activator = createActivator('DbKeyDetailTab', true); export const activator = createActivator('DbKeyDetailTab', true);
let currentRow;
$: key = $activeDbKeysStore[`${conid}:${database}`]; $: key = $activeDbKeysStore[`${conid}:${database}`];
let refreshToken = 0; let refreshToken = 0;
function handleChangeTtl(keyInfo) {
showModal(InputTextModal, {
value: keyInfo.ttl,
label: 'New TTL value (-1=key never expires)',
header: `Set TTL for key ${keyInfo.key}`,
onConfirm: async value => {
const ttl = parseInt(value);
if (_.isNumber(ttl)) {
if (ttl < 0) {
await apiCall('database-connections/call-method', {
conid,
database,
method: 'persist',
args: [keyInfo.key],
});
} else {
await apiCall('database-connections/call-method', {
conid,
database,
method: 'expire',
args: [keyInfo.key, ttl],
});
}
refresh();
}
},
});
}
function refresh() {
refreshToken += 1;
}
</script> </script>
{#await apiCall('database-connections/load-key-info', { conid, database, key, refreshToken })} {#await apiCall('database-connections/load-key-info', { conid, database, key, refreshToken })}
@@ -36,28 +74,38 @@
{:then keyInfo} {:then keyInfo}
<div class="container"> <div class="container">
<div class="top-panel"> <div class="top-panel">
<FontIcon icon={getIconForRedisType(keyInfo.type)} padRight />
<div class="type"> <div class="type">
<FontIcon icon={getIconForRedisType(keyInfo.type)} padRight />
{keyInfo.type} {keyInfo.type}
</div> </div>
<TextField value={key} readOnly /> <div class="key-name">
{key} <TextField value={key} readOnly />
TTL:{keyInfo.ttl} </div>
<FormStyledButton <FormStyledButton value={`TTL:${keyInfo.ttl}`} on:click={() => handleChangeTtl(keyInfo)} />
value="Refresh" <FormStyledButton value="Refresh" on:click={refresh} />
on:click={() => {
refreshToken += 1;
}}
/>
</div> </div>
<div class="content"> <div class="content">
{#if keyInfo.tableColumns} {#if keyInfo.tableColumns}
<VerticalSplitter> <VerticalSplitter>
<svelte:fragment slot="1"> <svelte:fragment slot="1">
<DbKeyTableControl {conid} {database} {keyInfo} /> <DbKeyTableControl
{conid}
{database}
{keyInfo}
onChangeSelected={row => {
currentRow = row;
}}
/>
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="2">PROPS</svelte:fragment> <div slot="2" class="props">
{#each keyInfo.tableColumns as column}
<div class="colname">{column.name}</div>
<div class="colvalue">
<AceEditor readOnly value={currentRow && currentRow[column.name]} />
</div>
{/each}
</div>
</VerticalSplitter> </VerticalSplitter>
{:else} {:else}
<AceEditor readOnly value={keyInfo.value} /> <AceEditor readOnly value={keyInfo.value} />
@@ -80,9 +128,36 @@
.top-panel { .top-panel {
display: flex; display: flex;
background: var(--theme-bg-2);
} }
.type { .type {
font-weight: bold; font-weight: bold;
margin-right: 10px;
align-self: center;
}
.props {
flex: 1;
display: flex;
flex-direction: column;
}
.colname {
margin: 10px;
}
.colvalue {
position: relative;
flex: 1;
}
.key-name {
flex-grow: 1;
display: flex;
}
.key-name :global(input) {
flex-grow: 1;
} }
</style> </style>

View File

@@ -59,7 +59,10 @@ const driver = {
async getVersion(pool) { async getVersion(pool) {
const info = await this.info(pool); const info = await this.info(pool);
return { version: info.redis_version }; return {
version: info.redis_version,
versionText: `Redis ${info.redis_version}`,
};
}, },
async listDatabases(pool) { async listDatabases(pool) {
const info = await this.info(pool); const info = await this.info(pool);
@@ -159,18 +162,18 @@ const driver = {
res.value = await pool.get(key); res.value = await pool.get(key);
break; break;
case 'list': case 'list':
res.tableColumns = ['value']; res.tableColumns = [{ name: 'value' }];
break; break;
case 'set': case 'set':
res.tableColumns = ['value']; res.tableColumns = [{ name: 'value' }];
res.keyColumn = 'value'; res.keyColumn = 'value';
break; break;
case 'zset': case 'zset':
res.tableColumns = ['value', 'score']; res.tableColumns = [{ name: 'value' }, { name: 'score' }];
res.keyColumn = 'value'; res.keyColumn = 'value';
break; break;
case 'hash': case 'hash':
res.tableColumns = ['key', 'value']; res.tableColumns = [{ name: 'key' }, { name: 'value' }];
res.keyColumn = 'key'; res.keyColumn = 'key';
break; break;
} }
@@ -178,6 +181,10 @@ const driver = {
return res; return res;
}, },
async callMethod(pool, method, args) {
return await pool[method](...args);
},
async loadKeyTableRange(pool, key, cursor, count) { async loadKeyTableRange(pool, key, cursor, count) {
const type = await pool.type(key); const type = await pool.type(key);
switch (type) { switch (type) {