mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-05-01 14:23:58 +00:00
redis: set ttl
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
2
packages/types/engines.d.ts
vendored
2
packages/types/engines.d.ts
vendored
@@ -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;
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: console.log(title, indentLevel);
|
// $: console.log(title, indentLevel);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -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
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user