Files
dbgate/packages/web/src/tabs/DbKeyDetailTab.svelte
2025-12-10 13:57:40 +01:00

264 lines
7.5 KiB
Svelte

<script lang="ts" context="module">
import createActivator, { getActiveComponent } from '../utility/createActivator';
const getCurrentEditor = () => getActiveComponent('DbKeyDetailTab');
export const matchingProps = ['conid', 'database', 'isDefaultBrowser'];
export const allowAddToFavorites = props => false;
function getKeyText(key) {
if (!key) return '(no name)';
const keySplit = key.split(':');
if (keySplit.length > 1) return keySplit[keySplit.length - 1];
return key || '(no name)';
}
</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';
import { showModal } from '../modals/modalTools';
import InputTextModal from '../modals/InputTextModal.svelte';
import _ from 'lodash';
import DbKeyItemDetail from '../dbkeyvalue/DbKeyItemDetail.svelte';
import DbKeyAddItemModal from '../modals/DbKeyAddItemModal.svelte';
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
import { changeTab } from '../utility/common';
import SelectField from '../forms/SelectField.svelte';
import DbKeyValueDetail from '../dbkeyvalue/DbKeyValueDetail.svelte';
import { _t } from '../translations';
import ToolStripContainer from '../buttons/ToolStripContainer.svelte';
import ToolStripButton from '../buttons/ToolStripButton.svelte';
export let tabid;
export let conid;
export let database;
export let key;
export let isDefaultBrowser = false;
export const activator = createActivator('DbKeyDetailTab', true);
let currentRow;
$: key = $activeDbKeysStore[`${conid}:${database}`];
let refreshToken = 0;
let editedValue = null;
$: changeTab(tabid, tab => ({
...tab,
title: getKeyText(key),
}));
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 handleKeyRename(keyInfo) {
showModal(InputTextModal, {
value: keyInfo.key,
label: 'New key name',
header: `Rename key ${keyInfo.key}`,
onConfirm: async value => {
const res = await apiCall('database-connections/call-method', {
conid,
database,
method: 'rename',
args: [keyInfo.key, value],
});
if (res.errorMessage) {
showModal(ErrorMessageModal, { message: res.errorMessage });
return;
}
activeDbKeysStore.update(store => ({
...store,
[`${conid}:${database}`]: value,
}));
},
});
}
function refresh() {
editedValue = null;
refreshToken += 1;
}
async function saveString() {
await apiCall('database-connections/call-method', {
conid,
database,
method: 'set',
args: [key, editedValue],
});
refresh();
}
async function addItem(keyInfo) {
showModal(DbKeyAddItemModal, {
keyInfo,
onConfirm: async row => {
const res = await apiCall('database-connections/call-method', {
conid,
database,
method: keyInfo.keyType.addMethod,
args: [keyInfo.key, ...keyInfo.keyType.dbKeyFields.map(col => row[col.name])],
});
if (res.errorMessage) {
showModal(ErrorMessageModal, { message: res.errorMessage });
return false;
}
refresh();
return true;
},
});
}
</script>
{#await apiCall('database-connections/load-key-info', { conid, database, key, refreshToken })}
<LoadingInfo message="Loading key details" wrapper />
{:then keyInfo}
<ToolStripContainer>
<div class="container">
<div class="top-panel">
<div class="type">
<FontIcon icon={getIconForRedisType(keyInfo.type)} padRight />
{keyInfo.keyType?.label || keyInfo.type}
</div>
<div class="key-name">
<TextField value={key} readOnly />
</div>
<FormStyledButton value="Rename Key" on:click={() => handleKeyRename(keyInfo)} />
<FormStyledButton value={`TTL:${keyInfo.ttl}`} on:click={() => handleChangeTtl(keyInfo)} />
</div>
<div class="content">
{#if keyInfo.keyType?.dbKeyFields && keyInfo.keyType?.showItemList}
<VerticalSplitter>
<svelte:fragment slot="1">
<DbKeyTableControl
{conid}
{database}
{keyInfo}
onChangeSelected={row => {
currentRow = row;
}}
/>
</svelte:fragment>
<svelte:fragment slot="2">
<DbKeyItemDetail
dbKeyFields={keyInfo.type === 'hash'
? keyInfo.keyType.dbKeyFields.filter(f => f.name === 'value')
: keyInfo.keyType.dbKeyFields}
item={currentRow}
/>
</svelte:fragment>
</VerticalSplitter>
{:else}
<div class="value-holder">
<DbKeyValueDetail
columnTitle="Value"
value={editedValue || keyInfo.value}
onChangeValue={value => {
editedValue = value;
}}
/>
</div>
{/if}
</div>
</div>
<svelte:fragment slot="toolstrip">
{#if keyInfo.type == 'string'}
<ToolStripButton
icon="icon save"
on:click={saveString}
disabled={!editedValue}
>{_t('common.save', { defaultMessage: 'Save' })}</ToolStripButton>
{/if}
{#if keyInfo.keyType?.addMethod && keyInfo.keyType?.showItemList}
<ToolStripButton icon="icon add" on:click={() => addItem(keyInfo)}>Add field</ToolStripButton>
{/if}
<ToolStripButton icon="icon refresh" on:click={refresh}>{_t('common.refresh', { defaultMessage: 'Refresh' })}</ToolStripButton>
</svelte:fragment>
</ToolStripContainer>
{/await}
<style>
.container {
display: flex;
flex-direction: column;
flex: 1;
}
.content {
flex: 1;
position: relative;
}
.top-panel {
display: flex;
background: var(--theme-bg-2);
}
.type {
font-weight: bold;
margin-right: 10px;
align-self: center;
}
.key-name {
flex-grow: 1;
display: flex;
}
.key-name :global(input) {
flex-grow: 1;
}
.value-holder {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
}
</style>