mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-17 21:26:00 +00:00
Merge pull request #1307 from dbgate/feature/redis-gui-refactor
Feature/redis gui refactor
This commit is contained in:
@@ -494,6 +494,20 @@ module.exports = {
|
||||
return res.result || null;
|
||||
},
|
||||
|
||||
saveRedisData_meta: true,
|
||||
async saveRedisData({ conid, database, changeSet }, req) {
|
||||
await testConnectionPermission(conid, req);
|
||||
|
||||
const opened = await this.ensureOpened(conid, database);
|
||||
const res = await this.sendRequest(opened, { msgtype: 'saveRedisData', changeSet });
|
||||
if (res.errorMessage) {
|
||||
return {
|
||||
errorMessage: res.errorMessage,
|
||||
};
|
||||
}
|
||||
return res.result || null;
|
||||
},
|
||||
|
||||
status_meta: true,
|
||||
async status({ conid, database }, req) {
|
||||
if (!conid) {
|
||||
|
||||
@@ -368,6 +368,90 @@ async function handleSaveTableData({ msgid, changeSet }) {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSaveRedisData({ msgid, changeSet }) {
|
||||
try {
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
|
||||
if (!changeSet || !changeSet.changes || !Array.isArray(changeSet.changes)) {
|
||||
throw new Error('Invalid changeSet structure');
|
||||
}
|
||||
|
||||
for (const change of changeSet.changes) {
|
||||
if (change.type === 'string') {
|
||||
await driver.query(dbhan, `SET "${change.key}" "${change.value}"`);
|
||||
} else if (change.type === 'json') {
|
||||
await driver.query(dbhan, `JSON.SET "${change.key}" $ '${change.value.replace(/'/g, "\\'")}'`);
|
||||
} else if (change.type === 'hash') {
|
||||
if (change.updates && Array.isArray(change.updates)) {
|
||||
for (const update of change.updates) {
|
||||
await driver.query(dbhan, `HSET "${change.key}" "${update.key}" "${update.value}"`);
|
||||
|
||||
if (update.ttl !== undefined && update.ttl !== null && update.ttl !== -1) {
|
||||
try {
|
||||
await dbhan.client.call('HEXPIRE', change.key, update.ttl, 'FIELDS', 1, update.key);
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (change.inserts && Array.isArray(change.inserts)) {
|
||||
for (const insert of change.inserts) {
|
||||
await driver.query(dbhan, `HSET "${change.key}" "${insert.key}" "${insert.value}"`);
|
||||
|
||||
if (insert.ttl !== undefined && insert.ttl !== null && insert.ttl !== -1) {
|
||||
try {
|
||||
await dbhan.client.call('HEXPIRE', change.key, insert.ttl, 'FIELDS', 1, insert.key);
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (change.type === 'zset') {
|
||||
if (change.updates && Array.isArray(change.updates)) {
|
||||
for (const update of change.updates) {
|
||||
await driver.query(dbhan, `ZADD "${change.key}" ${update.score} "${update.member}"`);
|
||||
}
|
||||
}
|
||||
if (change.inserts && Array.isArray(change.inserts)) {
|
||||
for (const insert of change.inserts) {
|
||||
await driver.query(dbhan, `ZADD "${change.key}" ${insert.score} "${insert.member}"`);
|
||||
}
|
||||
}
|
||||
} else if (change.type === 'list') {
|
||||
if (change.updates && Array.isArray(change.updates)) {
|
||||
for (const update of change.updates) {
|
||||
await driver.query(dbhan, `LSET "${change.key}" ${update.index} "${update.value}"`);
|
||||
}
|
||||
}
|
||||
if (change.inserts && Array.isArray(change.inserts)) {
|
||||
for (const insert of change.inserts) {
|
||||
await driver.query(dbhan, `RPUSH "${change.key}" "${insert.value}"`);
|
||||
}
|
||||
}
|
||||
} else if (change.type === 'set') {
|
||||
if (change.inserts && Array.isArray(change.inserts)) {
|
||||
for (const insert of change.inserts) {
|
||||
await driver.query(dbhan, `SADD "${change.key}" "${insert.value}"`);
|
||||
}
|
||||
}
|
||||
} else if (change.type === 'stream') {
|
||||
if (change.inserts && Array.isArray(change.inserts)) {
|
||||
for (const insert of change.inserts) {
|
||||
const streamId = insert.id === '*' || !insert.id ? '*' : insert.id;
|
||||
await driver.query(dbhan, `XADD "${change.key}" ${streamId} value "${insert.value}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
process.send({ msgtype: 'response', msgid });
|
||||
} catch (err) {
|
||||
process.send({
|
||||
msgtype: 'response',
|
||||
msgid,
|
||||
errorMessage: extractErrorMessage(err, 'Error saving Redis data'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSqlPreview({ msgid, objects, options }) {
|
||||
await waitStructure();
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
@@ -501,6 +585,7 @@ const messageHandlers = {
|
||||
schemaList: handleSchemaList,
|
||||
executeSessionQuery: handleExecuteSessionQuery,
|
||||
evalJsonScript: handleEvalJsonScript,
|
||||
saveRedisData: handleSaveRedisData,
|
||||
// runCommand: handleRunCommand,
|
||||
};
|
||||
|
||||
|
||||
55
packages/datalib/src/ChangeSetRedis.ts
Normal file
55
packages/datalib/src/ChangeSetRedis.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
export interface ChangeSetRedis_String {
|
||||
key: string;
|
||||
type: 'string';
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface ChangeSetRedis_JSON {
|
||||
key: string;
|
||||
type: 'json';
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface ChangeSetRedis_Hash {
|
||||
key: string;
|
||||
type: 'hash';
|
||||
inserts: { key: string; value: string, ttl: number }[];
|
||||
updates: { key: string; value: string, ttl: number }[];
|
||||
deletes: string[];
|
||||
}
|
||||
|
||||
export interface ChangeSetRedis_List {
|
||||
key: string;
|
||||
type: 'list';
|
||||
inserts: { index: number; value: string }[];
|
||||
updates: { index: number; value: string }[];
|
||||
deletes: number[];
|
||||
}
|
||||
|
||||
export interface ChangeSetRedis_Set {
|
||||
key: string;
|
||||
type: 'set';
|
||||
inserts: string[];
|
||||
deletes: string[];
|
||||
}
|
||||
|
||||
export interface ChangeSetRedis_ZSet {
|
||||
key: string;
|
||||
type: 'zset';
|
||||
inserts: { member: string; score: number }[];
|
||||
updates: { member: string; score: number }[];
|
||||
deletes: string[];
|
||||
}
|
||||
|
||||
export type ChangeSetRedisType =
|
||||
| ChangeSetRedis_String
|
||||
| ChangeSetRedis_JSON
|
||||
| ChangeSetRedis_Hash
|
||||
| ChangeSetRedis_List
|
||||
| ChangeSetRedis_Set
|
||||
| ChangeSetRedis_ZSet;
|
||||
|
||||
|
||||
export interface ChangeSetRedis {
|
||||
changes: ChangeSetRedisType[];
|
||||
}
|
||||
@@ -25,3 +25,4 @@ export * from './CustomGridDisplay';
|
||||
export * from './ScriptDrivedDeployer';
|
||||
export * from './chartDefinitions';
|
||||
export * from './chartProcessor';
|
||||
export * from './ChangeSetRedis';
|
||||
|
||||
@@ -504,6 +504,7 @@ export function getIconForRedisType(type) {
|
||||
case 'binary':
|
||||
return 'img type-binary';
|
||||
case 'ReJSON-RL':
|
||||
case 'JSON':
|
||||
return 'img type-rejson';
|
||||
default:
|
||||
return null;
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
export let database;
|
||||
export let keyInfo;
|
||||
export let onChangeSelected;
|
||||
export let modifyRow = null;
|
||||
export let changeSetRedis = null;
|
||||
|
||||
let rows = [];
|
||||
let cursor = 0;
|
||||
@@ -73,6 +75,12 @@
|
||||
onMount(() => {
|
||||
loadNextRows();
|
||||
});
|
||||
|
||||
$: displayRows = modifyRow ? rows.map(row => modifyRow(row)) : rows;
|
||||
$: {
|
||||
changeSetRedis;
|
||||
displayRows = modifyRow ? rows.map(row => modifyRow(row)) : rows;
|
||||
}
|
||||
</script>
|
||||
|
||||
<ScrollableTableControl
|
||||
@@ -87,7 +95,7 @@
|
||||
header: column.name,
|
||||
})),
|
||||
]}
|
||||
{rows}
|
||||
rows={displayRows}
|
||||
onLoadNext={isLoadedAll ? null : loadNextRows}
|
||||
selectable
|
||||
singleLineRow
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
<div class="props">
|
||||
{#each dbKeyFields as column}
|
||||
<DbKeyValueDetail
|
||||
value={item && item[column.name]}
|
||||
value={item && item[column.name] != null ? String(item[column.name]) : ''}
|
||||
columnTitle={_.startCase(column.name)}
|
||||
onChangeValue={onChangeItem
|
||||
onChangeValue={onChangeItem && !column.readOnly
|
||||
? value => {
|
||||
onChangeItem?.({
|
||||
...item,
|
||||
@@ -29,5 +29,8 @@
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
53
packages/web/src/dbkeyvalue/DbKeyItemEdit.svelte
Normal file
53
packages/web/src/dbkeyvalue/DbKeyItemEdit.svelte
Normal file
@@ -0,0 +1,53 @@
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import DbKeyValueDetail from './DbKeyValueDetail.svelte';
|
||||
|
||||
export let dbKeyFields;
|
||||
export let item;
|
||||
export let onChangeItem = null;
|
||||
export let keyColumn = null;
|
||||
|
||||
$: console.log('DbKeyItemEdit', { item, dbKeyFields, keyColumn, onChangeItem: !!onChangeItem });
|
||||
|
||||
function getValueAsString(value) {
|
||||
if (value === null || value === undefined) return undefined;
|
||||
if (typeof value === 'string') return value;
|
||||
if (typeof value === 'number') return String(value);
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="props">
|
||||
{#each dbKeyFields as column}
|
||||
<div class="field-wrapper">
|
||||
<DbKeyValueDetail
|
||||
value={getValueAsString(item?.[column.name])}
|
||||
columnTitle={_.startCase(column.name)}
|
||||
onChangeValue={onChangeItem && column.name !== keyColumn
|
||||
? value => {
|
||||
onChangeItem?.({
|
||||
...item,
|
||||
[column.name]: value,
|
||||
});
|
||||
}
|
||||
: null}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.props {
|
||||
flex: 1;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.field-wrapper {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
max-height: 100px;
|
||||
}
|
||||
</style>
|
||||
@@ -12,6 +12,7 @@
|
||||
export let columnTitle;
|
||||
export let value;
|
||||
export let onChangeValue = null;
|
||||
export let keyType = null;
|
||||
</script>
|
||||
|
||||
<div class="colnamewrap">
|
||||
@@ -30,13 +31,16 @@
|
||||
</div>
|
||||
<div class="colvalue">
|
||||
{#if display == 'text'}
|
||||
<AceEditor
|
||||
readOnly={!onChangeValue}
|
||||
{value}
|
||||
on:input={e => {
|
||||
onChangeValue?.(e.detail);
|
||||
}}
|
||||
/>
|
||||
<div class="editor-wrapper">
|
||||
<AceEditor
|
||||
readOnly={!onChangeValue}
|
||||
value={value != null ? String(value) : ''}
|
||||
mode={keyType === 'JSON' ? 'json' : undefined}
|
||||
on:input={e => {
|
||||
onChangeValue?.(e.detail);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if display == 'json'}
|
||||
<div class="outer">
|
||||
@@ -64,6 +68,13 @@
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.editor-wrapper {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
min-height: 60px;
|
||||
max-height: 1000px;
|
||||
}
|
||||
|
||||
.outer {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
173
packages/web/src/dbkeyvalue/DbKeyValueHashEdit.svelte
Normal file
173
packages/web/src/dbkeyvalue/DbKeyValueHashEdit.svelte
Normal file
@@ -0,0 +1,173 @@
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import TextField from '../forms/TextField.svelte';
|
||||
import FormFieldTemplateLarge from '../forms/FormFieldTemplateLarge.svelte';
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
|
||||
export let dbKeyFields;
|
||||
export let item;
|
||||
export let onChangeItem = null;
|
||||
export let keyColumn = null;
|
||||
|
||||
let records = [{ key: '', value: '', ttl: '' }];
|
||||
let lastItem = null;
|
||||
|
||||
$: if (item !== lastItem) {
|
||||
if (item?.records && Array.isArray(item.records)) {
|
||||
records = [...item.records];
|
||||
} else if (!item) {
|
||||
records = [{ key: '', value: '', ttl: '' }];
|
||||
}
|
||||
lastItem = item;
|
||||
}
|
||||
|
||||
$: console.log('DbKeyItemEdit', { item, dbKeyFields, keyColumn, onChangeItem: !!onChangeItem });
|
||||
|
||||
function getValueAsString(value) {
|
||||
if (value === null || value === undefined) return '';
|
||||
if (typeof value === 'string') return value;
|
||||
if (typeof value === 'number') return String(value);
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
function handleFieldChange(index, fieldName, value) {
|
||||
records = records.map((record, idx) =>
|
||||
idx === index ? { ...record, [fieldName]: value } : record
|
||||
);
|
||||
|
||||
if (onChangeItem && fieldName !== keyColumn) {
|
||||
onChangeItem?.({
|
||||
...item,
|
||||
records: records,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function addRecord() {
|
||||
records = [...records, { key: '', value: '', ttl: '' }];
|
||||
if (onChangeItem) {
|
||||
onChangeItem({
|
||||
...item,
|
||||
records: records,
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
{#each records as record, index}
|
||||
<div class="props flex">
|
||||
<div class="field-wrapper col-3">
|
||||
<FormFieldTemplateLarge label="Key" type="text" noMargin>
|
||||
<TextField
|
||||
value={record.key}
|
||||
on:change={e => handleFieldChange(index, 'key', e.target.value)}
|
||||
disabled={keyColumn === 'key'}
|
||||
/>
|
||||
</FormFieldTemplateLarge>
|
||||
</div>
|
||||
<div class="field-wrapper col-6">
|
||||
<FormFieldTemplateLarge label="Value" type="text" noMargin>
|
||||
<TextField
|
||||
value={record.value}
|
||||
on:change={e => handleFieldChange(index, 'value', e.target.value)}
|
||||
disabled={keyColumn === 'value'}
|
||||
/>
|
||||
</FormFieldTemplateLarge>
|
||||
</div>
|
||||
<div class="field-wrapper col-2">
|
||||
<FormFieldTemplateLarge label="TTL" type="text" noMargin>
|
||||
<TextField
|
||||
value={record.ttl}
|
||||
on:change={e => handleFieldChange(index, 'ttl', e.target.value)}
|
||||
disabled={keyColumn === 'ttl'}
|
||||
/>
|
||||
</FormFieldTemplateLarge>
|
||||
</div>
|
||||
<div class="delete-wrapper col-1">
|
||||
<button class="delete-button" on:click={() => {
|
||||
records = records.filter((_, idx) => idx !== index);
|
||||
if (onChangeItem) {
|
||||
onChangeItem({
|
||||
...item,
|
||||
records: records,
|
||||
});
|
||||
}
|
||||
}}>
|
||||
<FontIcon icon="icon delete" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<div class="add-button-wrapper">
|
||||
<button class="add-button" on:click={addRecord}>
|
||||
<FontIcon icon="icon add" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.props {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.field-wrapper {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.delete-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
color: var(--theme-font-3);
|
||||
transition: color 0.2s;
|
||||
font-size: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.add-button-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
color: var(--theme-font-3);
|
||||
transition: color 0.2s;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.add-button:hover {
|
||||
color: var(--theme-font-hover);
|
||||
}
|
||||
</style>
|
||||
151
packages/web/src/dbkeyvalue/DbKeyValueListEdit.svelte
Normal file
151
packages/web/src/dbkeyvalue/DbKeyValueListEdit.svelte
Normal file
@@ -0,0 +1,151 @@
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import TextField from '../forms/TextField.svelte';
|
||||
import FormFieldTemplateLarge from '../forms/FormFieldTemplateLarge.svelte';
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
|
||||
export let dbKeyFields;
|
||||
export let item;
|
||||
export let onChangeItem = null;
|
||||
export let keyColumn = null;
|
||||
|
||||
let records = [{ value: '' }];
|
||||
let lastItem = null;
|
||||
|
||||
$: if (item !== lastItem) {
|
||||
if (item?.records && Array.isArray(item.records)) {
|
||||
records = [...item.records];
|
||||
} else if (!item) {
|
||||
records = [{ value: '' }];
|
||||
}
|
||||
lastItem = item;
|
||||
}
|
||||
|
||||
$: console.log('DbKeyValueListEdit', { item, dbKeyFields, keyColumn, onChangeItem: !!onChangeItem });
|
||||
|
||||
function handleFieldChange(index, fieldName, value) {
|
||||
records = records.map((record, idx) =>
|
||||
idx === index ? { ...record, [fieldName]: value } : record
|
||||
);
|
||||
|
||||
if (onChangeItem && fieldName !== keyColumn) {
|
||||
onChangeItem?.({
|
||||
...item,
|
||||
records: records,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function addRecord() {
|
||||
records = [...records, { value: '' }];
|
||||
if (onChangeItem) {
|
||||
onChangeItem({
|
||||
...item,
|
||||
records: records,
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
{#each records as record, index}
|
||||
<div class="props flex">
|
||||
<div class="field-wrapper col-11">
|
||||
<FormFieldTemplateLarge label="Value" type="text" noMargin>
|
||||
<TextField
|
||||
value={record.value}
|
||||
on:change={e => handleFieldChange(index, 'value', e.target.value)}
|
||||
disabled={keyColumn === 'value'}
|
||||
/>
|
||||
</FormFieldTemplateLarge>
|
||||
</div>
|
||||
<div class="delete-wrapper col-1">
|
||||
<button class="delete-button" on:click={() => {
|
||||
records = records.filter((_, idx) => idx !== index);
|
||||
if (onChangeItem) {
|
||||
onChangeItem({
|
||||
...item,
|
||||
records: records,
|
||||
});
|
||||
}
|
||||
}}>
|
||||
<FontIcon icon="icon delete" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<div class="add-button-wrapper">
|
||||
<button class="add-button" on:click={addRecord}>
|
||||
<FontIcon icon="icon add" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.props {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.field-wrapper {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.delete-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
color: var(--theme-font-3);
|
||||
transition: color 0.2s;
|
||||
font-size: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.delete-button:hover {
|
||||
color: var(--theme-font-hover);
|
||||
}
|
||||
|
||||
.add-button-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
color: var(--theme-font-3);
|
||||
transition: color 0.2s;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.add-button:hover {
|
||||
color: var(--theme-font-hover);
|
||||
}
|
||||
</style>
|
||||
151
packages/web/src/dbkeyvalue/DbKeyValueSetEdit.svelte
Normal file
151
packages/web/src/dbkeyvalue/DbKeyValueSetEdit.svelte
Normal file
@@ -0,0 +1,151 @@
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import TextField from '../forms/TextField.svelte';
|
||||
import FormFieldTemplateLarge from '../forms/FormFieldTemplateLarge.svelte';
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
|
||||
export let dbKeyFields;
|
||||
export let item;
|
||||
export let onChangeItem = null;
|
||||
export let keyColumn = null;
|
||||
|
||||
let records = [{ value: '' }];
|
||||
let lastItem = null;
|
||||
|
||||
$: if (item !== lastItem) {
|
||||
if (item?.records && Array.isArray(item.records)) {
|
||||
records = [...item.records];
|
||||
} else if (!item) {
|
||||
records = [{ value: '' }];
|
||||
}
|
||||
lastItem = item;
|
||||
}
|
||||
|
||||
$: console.log('DbKeyValueSetEdit', { item, dbKeyFields, keyColumn, onChangeItem: !!onChangeItem });
|
||||
|
||||
function handleFieldChange(index, fieldName, value) {
|
||||
records = records.map((record, idx) =>
|
||||
idx === index ? { ...record, [fieldName]: value } : record
|
||||
);
|
||||
|
||||
if (onChangeItem && fieldName !== keyColumn) {
|
||||
onChangeItem?.({
|
||||
...item,
|
||||
records: records,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function addRecord() {
|
||||
records = [...records, { value: '' }];
|
||||
if (onChangeItem) {
|
||||
onChangeItem({
|
||||
...item,
|
||||
records: records,
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
{#each records as record, index}
|
||||
<div class="props flex">
|
||||
<div class="field-wrapper col-11">
|
||||
<FormFieldTemplateLarge label="Value" type="text" noMargin>
|
||||
<TextField
|
||||
value={record.value}
|
||||
on:change={e => handleFieldChange(index, 'value', e.target.value)}
|
||||
disabled={keyColumn === 'value'}
|
||||
/>
|
||||
</FormFieldTemplateLarge>
|
||||
</div>
|
||||
<div class="delete-wrapper col-1">
|
||||
<button class="delete-button" on:click={() => {
|
||||
records = records.filter((_, idx) => idx !== index);
|
||||
if (onChangeItem) {
|
||||
onChangeItem({
|
||||
...item,
|
||||
records: records,
|
||||
});
|
||||
}
|
||||
}}>
|
||||
<FontIcon icon="icon delete" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<div class="add-button-wrapper">
|
||||
<button class="add-button" on:click={addRecord}>
|
||||
<FontIcon icon="icon add" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.props {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.field-wrapper {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.delete-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
color: var(--theme-font-3);
|
||||
transition: color 0.2s;
|
||||
font-size: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.delete-button:hover {
|
||||
color: var(--theme-font-hover);
|
||||
}
|
||||
|
||||
.add-button-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
color: var(--theme-font-3);
|
||||
transition: color 0.2s;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.add-button:hover {
|
||||
color: var(--theme-font-hover);
|
||||
}
|
||||
</style>
|
||||
166
packages/web/src/dbkeyvalue/DbKeyValueStreamEdit.svelte
Normal file
166
packages/web/src/dbkeyvalue/DbKeyValueStreamEdit.svelte
Normal file
@@ -0,0 +1,166 @@
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import TextField from '../forms/TextField.svelte';
|
||||
import FormFieldTemplateLarge from '../forms/FormFieldTemplateLarge.svelte';
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
|
||||
export let dbKeyFields;
|
||||
export let item;
|
||||
export let onChangeItem = null;
|
||||
export let keyColumn = null;
|
||||
|
||||
let records = [{ id: '', value: '' }];
|
||||
let lastItem = null;
|
||||
|
||||
$: if (item !== lastItem) {
|
||||
if (item?.records && Array.isArray(item.records)) {
|
||||
records = [...item.records];
|
||||
} else if (!item) {
|
||||
records = [{ id: '', value: '' }];
|
||||
}
|
||||
lastItem = item;
|
||||
}
|
||||
|
||||
$: console.log('DbKeyValueStreamEdit', { item, dbKeyFields, keyColumn, onChangeItem: !!onChangeItem });
|
||||
|
||||
function getValueAsString(value) {
|
||||
if (value === null || value === undefined) return '';
|
||||
if (typeof value === 'string') return value;
|
||||
if (typeof value === 'number') return String(value);
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
function handleFieldChange(index, fieldName, value) {
|
||||
records = records.map((record, idx) => (idx === index ? { ...record, [fieldName]: value } : record));
|
||||
|
||||
if (onChangeItem && fieldName !== keyColumn) {
|
||||
onChangeItem?.({
|
||||
...item,
|
||||
records: records,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function addRecord() {
|
||||
records = [...records, { id: '', value: '' }];
|
||||
if (onChangeItem) {
|
||||
onChangeItem({
|
||||
...item,
|
||||
records: records,
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
{#each records as record, index}
|
||||
<div class="props flex">
|
||||
<div class="field-wrapper col-3">
|
||||
<FormFieldTemplateLarge label="ID" type="text" noMargin>
|
||||
<TextField
|
||||
value={record.id}
|
||||
on:change={e => handleFieldChange(index, 'id', e.target.value)}
|
||||
disabled={keyColumn === 'id'}
|
||||
placeholder="* for auto"
|
||||
/>
|
||||
</FormFieldTemplateLarge>
|
||||
</div>
|
||||
<div class="field-wrapper col-8">
|
||||
<FormFieldTemplateLarge label="Value" type="text" noMargin>
|
||||
<TextField
|
||||
value={record.value}
|
||||
on:change={e => handleFieldChange(index, 'value', e.target.value)}
|
||||
disabled={keyColumn === 'value'}
|
||||
/>
|
||||
</FormFieldTemplateLarge>
|
||||
</div>
|
||||
<div class="delete-wrapper col-1">
|
||||
<button class="delete-button" on:click={() => {
|
||||
records = records.filter((_, idx) => idx !== index);
|
||||
if (onChangeItem) {
|
||||
onChangeItem({
|
||||
...item,
|
||||
records: records,
|
||||
});
|
||||
}
|
||||
}}>
|
||||
<FontIcon icon="icon delete" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<div class="add-button-wrapper">
|
||||
<button class="add-button" on:click={addRecord}>
|
||||
<FontIcon icon="icon add" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.props {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.field-wrapper {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.delete-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
color: var(--theme-font-3);
|
||||
transition: color 0.2s;
|
||||
font-size: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.delete-button:hover {
|
||||
color: var(--theme-font-hover);
|
||||
}
|
||||
|
||||
.add-button-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
color: var(--theme-font-3);
|
||||
transition: color 0.2s;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.add-button:hover {
|
||||
color: var(--theme-font-hover);
|
||||
}
|
||||
</style>
|
||||
165
packages/web/src/dbkeyvalue/DbKeyValueZSetEdit.svelte
Normal file
165
packages/web/src/dbkeyvalue/DbKeyValueZSetEdit.svelte
Normal file
@@ -0,0 +1,165 @@
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import TextField from '../forms/TextField.svelte';
|
||||
import FormFieldTemplateLarge from '../forms/FormFieldTemplateLarge.svelte';
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
|
||||
export let dbKeyFields;
|
||||
export let item;
|
||||
export let onChangeItem = null;
|
||||
export let keyColumn = null;
|
||||
|
||||
let records = [{ member: '', score: '' }];
|
||||
let lastItem = null;
|
||||
|
||||
$: if (item !== lastItem) {
|
||||
if (item?.records && Array.isArray(item.records)) {
|
||||
records = [...item.records];
|
||||
} else if (!item) {
|
||||
records = [{ member: '', score: '' }];
|
||||
}
|
||||
lastItem = item;
|
||||
}
|
||||
|
||||
$: console.log('DbKeyValueZSetEdit', { item, dbKeyFields, keyColumn, onChangeItem: !!onChangeItem });
|
||||
|
||||
function getValueAsString(value) {
|
||||
if (value === null || value === undefined) return '';
|
||||
if (typeof value === 'string') return value;
|
||||
if (typeof value === 'number') return String(value);
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
function handleFieldChange(index, fieldName, value) {
|
||||
records = records.map((record, idx) => (idx === index ? { ...record, [fieldName]: value } : record));
|
||||
|
||||
if (onChangeItem && fieldName !== keyColumn) {
|
||||
onChangeItem?.({
|
||||
...item,
|
||||
records: records,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function addRecord() {
|
||||
records = [...records, { member: '', score: '' }];
|
||||
if (onChangeItem) {
|
||||
onChangeItem({
|
||||
...item,
|
||||
records: records,
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
{#each records as record, index}
|
||||
<div class="props flex">
|
||||
<div class="field-wrapper col-7">
|
||||
<FormFieldTemplateLarge label="Member" type="text" noMargin>
|
||||
<TextField
|
||||
value={record.member}
|
||||
on:change={e => handleFieldChange(index, 'member', e.target.value)}
|
||||
disabled={keyColumn === 'member'}
|
||||
/>
|
||||
</FormFieldTemplateLarge>
|
||||
</div>
|
||||
<div class="field-wrapper col-4">
|
||||
<FormFieldTemplateLarge label="Score" type="text" noMargin>
|
||||
<TextField
|
||||
value={record.score}
|
||||
on:change={e => handleFieldChange(index, 'score', e.target.value)}
|
||||
disabled={keyColumn === 'score'}
|
||||
/>
|
||||
</FormFieldTemplateLarge>
|
||||
</div>
|
||||
<div class="delete-wrapper col-1">
|
||||
<button class="delete-button" on:click={() => {
|
||||
records = records.filter((_, idx) => idx !== index);
|
||||
if (onChangeItem) {
|
||||
onChangeItem({
|
||||
...item,
|
||||
records: records,
|
||||
});
|
||||
}
|
||||
}}>
|
||||
<FontIcon icon="icon delete" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<div class="add-button-wrapper">
|
||||
<button class="add-button" on:click={addRecord}>
|
||||
<FontIcon icon="icon add" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.props {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.field-wrapper {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.delete-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
color: var(--theme-font-3);
|
||||
transition: color 0.2s;
|
||||
font-size: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.delete-button:hover {
|
||||
color: var(--theme-font-hover);
|
||||
}
|
||||
|
||||
.add-button-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
color: var(--theme-font-3);
|
||||
transition: color 0.2s;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.add-button:hover {
|
||||
color: var(--theme-font-hover);
|
||||
}
|
||||
</style>
|
||||
@@ -337,7 +337,7 @@
|
||||
'img type-zset': 'mdi mdi-format-list-checks color-icon-blue',
|
||||
'img type-stream': 'mdi mdi-view-stream color-icon-blue',
|
||||
'img type-binary': 'mdi mdi-file color-icon-blue',
|
||||
'img type-rejson': 'mdi mdi-color-json color-icon-blue',
|
||||
'img type-rejson': 'mdi mdi-code-json color-icon-blue',
|
||||
'img keydb': 'mdi mdi-key color-icon-blue',
|
||||
|
||||
'img replicator': 'mdi mdi-content-duplicate color-icon-green',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import FormStyledButton from '../buttons/FormStyledButton.svelte';
|
||||
import DbKeyItemDetail from '../dbkeyvalue/DbKeyItemDetail.svelte';
|
||||
import DbKeyValueHashEdit from '../dbkeyvalue/DbKeyValueHashEdit.svelte';
|
||||
import FormFieldTemplateLarge from '../forms/FormFieldTemplateLarge.svelte';
|
||||
|
||||
import FormProvider from '../forms/FormProvider.svelte';
|
||||
@@ -53,13 +54,23 @@
|
||||
/>
|
||||
</FormFieldTemplateLarge>
|
||||
|
||||
<DbKeyItemDetail
|
||||
dbKeyFields={driver.supportedKeyTypes.find(x => x.name == type).dbKeyFields}
|
||||
{item}
|
||||
onChangeItem={value => {
|
||||
item = value;
|
||||
}}
|
||||
/>
|
||||
{#if type === 'hash'}
|
||||
<DbKeyValueHashEdit
|
||||
dbKeyFields={driver.supportedKeyTypes.find(x => x.name == type).dbKeyFields}
|
||||
{item}
|
||||
onChangeItem={value => {
|
||||
item = value;
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<DbKeyItemDetail
|
||||
dbKeyFields={driver.supportedKeyTypes.find(x => x.name == type).dbKeyFields}
|
||||
{item}
|
||||
onChangeItem={value => {
|
||||
item = value;
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
@@ -30,12 +30,22 @@
|
||||
import InputTextModal from '../modals/InputTextModal.svelte';
|
||||
import _ from 'lodash';
|
||||
import DbKeyItemDetail from '../dbkeyvalue/DbKeyItemDetail.svelte';
|
||||
import DbKeyValueListEdit from '../dbkeyvalue/DbKeyValueListEdit.svelte';
|
||||
import DbKeyValueHashEdit from '../dbkeyvalue/DbKeyValueHashEdit.svelte';
|
||||
import DbKeyValueZSetEdit from '../dbkeyvalue/DbKeyValueZSetEdit.svelte';
|
||||
import DbKeyValueSetEdit from '../dbkeyvalue/DbKeyValueSetEdit.svelte';
|
||||
import DbKeyValueStreamEdit from '../dbkeyvalue/DbKeyValueStreamEdit.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';
|
||||
import type { ChangeSetRedis, ChangeSetRedisType } from 'dbgate-datalib';
|
||||
import useEditorData from '../query/useEditorData';
|
||||
import { onDestroy } from 'svelte';
|
||||
|
||||
export let tabid;
|
||||
export let conid;
|
||||
@@ -44,18 +54,61 @@
|
||||
export let isDefaultBrowser = false;
|
||||
|
||||
export const activator = createActivator('DbKeyDetailTab', true);
|
||||
|
||||
export function getChangeSetRedis(): ChangeSetRedis {
|
||||
return changeSetRedis;
|
||||
}
|
||||
|
||||
export function resetChangeSet() {
|
||||
changeSetRedis = { changes: [] };
|
||||
}
|
||||
|
||||
let currentRow;
|
||||
let showAddForm = false;
|
||||
let previousKey = null;
|
||||
|
||||
$: key = $activeDbKeysStore[`${conid}:${database}`];
|
||||
let refreshToken = 0;
|
||||
let editedValue = null;
|
||||
|
||||
const { editorState, editorValue, setEditorData } = useEditorData({
|
||||
tabid,
|
||||
onInitialData: value => {
|
||||
if (value && value.changes) {
|
||||
changeSetRedis = value;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
let changeSetRedis: ChangeSetRedis = { changes: [] };
|
||||
|
||||
$: if ($editorValue && $editorValue.changes) {
|
||||
changeSetRedis = $editorValue;
|
||||
}
|
||||
|
||||
$: if (changeSetRedis && changeSetRedis.changes) {
|
||||
setEditorData(changeSetRedis);
|
||||
}
|
||||
|
||||
$: if (key !== previousKey && previousKey !== null && changeSetRedis.changes.length > 0) {
|
||||
setEditorData(changeSetRedis);
|
||||
previousKey = key;
|
||||
} else if (key !== previousKey) {
|
||||
previousKey = key;
|
||||
}
|
||||
|
||||
$: hasChanges = changeSetRedis.changes.length > 0;
|
||||
|
||||
$: changeTab(tabid, tab => ({
|
||||
...tab,
|
||||
title: getKeyText(key),
|
||||
}));
|
||||
|
||||
onDestroy(() => {
|
||||
if (changeSetRedis && changeSetRedis.changes && changeSetRedis.changes.length > 0) {
|
||||
setEditorData(changeSetRedis);
|
||||
}
|
||||
});
|
||||
|
||||
function handleChangeTtl(keyInfo) {
|
||||
showModal(InputTextModal, {
|
||||
value: keyInfo.ttl,
|
||||
@@ -85,98 +138,373 @@
|
||||
});
|
||||
}
|
||||
|
||||
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 => {
|
||||
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: keyInfo.keyType.addMethod,
|
||||
args: [keyInfo.key, ...keyInfo.keyType.dbKeyFields.map(col => row[col.name])],
|
||||
method: 'rename',
|
||||
args: [keyInfo.key, value],
|
||||
});
|
||||
|
||||
if (res.errorMessage) {
|
||||
showModal(ErrorMessageModal, { message: res.errorMessage });
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
refresh();
|
||||
return true;
|
||||
|
||||
activeDbKeysStore.update(store => ({
|
||||
...store,
|
||||
[`${conid}:${database}`]: value,
|
||||
}));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function addOrUpdateChange(change: ChangeSetRedisType) {
|
||||
const existingIndex = changeSetRedis.changes.findIndex(
|
||||
c => c.key === change.key && c.type === change.type
|
||||
);
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
changeSetRedis = {
|
||||
...changeSetRedis,
|
||||
changes: changeSetRedis.changes.map((c, idx) =>
|
||||
idx === existingIndex ? change : c
|
||||
)
|
||||
};
|
||||
} else {
|
||||
changeSetRedis = {
|
||||
...changeSetRedis,
|
||||
changes: [...changeSetRedis.changes, change]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getDisplayRow(row, keyInfo) {
|
||||
if (!row) return row;
|
||||
|
||||
const existingChange = changeSetRedis.changes.find(
|
||||
c => c.key === keyInfo.key && c.type === keyInfo.type
|
||||
);
|
||||
|
||||
if (!existingChange) return row;
|
||||
|
||||
if (keyInfo.type === 'hash') {
|
||||
// @ts-ignore
|
||||
const update = existingChange.updates?.find(u => u.key === row.key);
|
||||
if (update) {
|
||||
return { ...row, value: update.value, TTL: update.ttl !== undefined ? update.ttl : row.TTL };
|
||||
}
|
||||
} else if (keyInfo.type === 'list') {
|
||||
// @ts-ignore
|
||||
const update = existingChange.updates?.find(u => u.index === row.rowNumber);
|
||||
if (update) {
|
||||
return { ...row, value: update.value };
|
||||
}
|
||||
} else if (keyInfo.type === 'zset') {
|
||||
// @ts-ignore
|
||||
const update = existingChange.updates?.find(u => u.member === row.member);
|
||||
if (update) {
|
||||
return { ...row, score: update.score };
|
||||
}
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
function getDisplayValue(keyInfo) {
|
||||
const existingChange = changeSetRedis.changes.find(
|
||||
c => c.key === keyInfo.key && c.type === keyInfo.type
|
||||
);
|
||||
|
||||
if (existingChange && (keyInfo.type === 'string' || keyInfo.type === 'JSON')) {
|
||||
// @ts-ignore
|
||||
return existingChange.value || keyInfo.value;
|
||||
}
|
||||
|
||||
return keyInfo.value;
|
||||
}
|
||||
|
||||
function getExistingInserts(keyInfo) {
|
||||
const existingChange = changeSetRedis.changes.find(
|
||||
c => c.key === keyInfo.key && c.type === keyInfo.type
|
||||
);
|
||||
|
||||
let records = [];
|
||||
|
||||
// Add existing inserts if any
|
||||
if (existingChange && existingChange.inserts) {
|
||||
// @ts-ignore
|
||||
records = existingChange.inserts.map(insert => {
|
||||
if (keyInfo.type === 'hash') {
|
||||
return { key: insert.key || '', value: insert.value || '', ttl: insert.ttl ? String(insert.ttl) : '' };
|
||||
} else if (keyInfo.type === 'list' || keyInfo.type === 'set') {
|
||||
return { value: insert.value || '' };
|
||||
} else if (keyInfo.type === 'zset') {
|
||||
return { member: insert.member || '', score: insert.score ? String(insert.score) : '' };
|
||||
} else if (keyInfo.type === 'stream') {
|
||||
return { id: insert.id || '', value: insert.value || '' };
|
||||
}
|
||||
return insert;
|
||||
});
|
||||
}
|
||||
|
||||
if (records.length === 0) {
|
||||
if (keyInfo.type === 'hash') {
|
||||
records.push({ key: '', value: '', ttl: '' });
|
||||
} else if (keyInfo.type === 'list' || keyInfo.type === 'set') {
|
||||
records.push({ value: '' });
|
||||
} else if (keyInfo.type === 'zset') {
|
||||
records.push({ member: '', score: '' });
|
||||
} else if (keyInfo.type === 'stream') {
|
||||
records.push({ id: '', value: '' });
|
||||
}
|
||||
}
|
||||
|
||||
return { records };
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
changeSetRedis = { changes: [] };
|
||||
setEditorData({ changes: [] });
|
||||
refreshToken += 1;
|
||||
}
|
||||
|
||||
async function saveAll() {
|
||||
await apiCall('database-connections/save-redis-data', {
|
||||
conid,
|
||||
database,
|
||||
changeSet: changeSetRedis,
|
||||
});
|
||||
changeSetRedis = { changes: [] };
|
||||
setEditorData({ changes: [] });
|
||||
refreshToken += 1;
|
||||
}
|
||||
</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">
|
||||
<div class="type">
|
||||
<FontIcon icon={getIconForRedisType(keyInfo.type)} padRight />
|
||||
{keyInfo.type}
|
||||
<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="key-name">
|
||||
<TextField value={key} readOnly />
|
||||
</div>
|
||||
<FormStyledButton value={`TTL:${keyInfo.ttl}`} on:click={() => handleChangeTtl(keyInfo)} />
|
||||
{#if keyInfo.type == 'string'}
|
||||
<FormStyledButton
|
||||
value={_t('common.save', { defaultMessage: 'Save' })}
|
||||
on:click={saveString}
|
||||
disabled={!editedValue}
|
||||
/>
|
||||
{/if}
|
||||
{#if keyInfo.keyType?.addMethod && keyInfo.keyType?.showItemList}
|
||||
<FormStyledButton value="Add item" on:click={() => addItem(keyInfo)} />
|
||||
{/if}
|
||||
<FormStyledButton value={_t('common.refresh', { defaultMessage: 'Refresh' })} on:click={refresh} />
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
{#if keyInfo.keyType?.dbKeyFields && keyInfo.keyType?.showItemList}
|
||||
<VerticalSplitter>
|
||||
<svelte:fragment slot="1">
|
||||
<DbKeyTableControl
|
||||
{conid}
|
||||
{database}
|
||||
{keyInfo}
|
||||
onChangeSelected={row => {
|
||||
currentRow = row;
|
||||
<div class="content">
|
||||
{#if keyInfo.keyType?.dbKeyFields && keyInfo.keyType?.showItemList}
|
||||
<VerticalSplitter>
|
||||
<svelte:fragment slot="1">
|
||||
<DbKeyTableControl
|
||||
{conid}
|
||||
{database}
|
||||
{keyInfo}
|
||||
{changeSetRedis}
|
||||
onChangeSelected={row => {
|
||||
currentRow = row;
|
||||
showAddForm = false;
|
||||
}}
|
||||
modifyRow={row => getDisplayRow(row, keyInfo)}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="2">
|
||||
{#if showAddForm}
|
||||
{#if keyInfo.type === 'list'}
|
||||
<DbKeyValueListEdit
|
||||
dbKeyFields={keyInfo.keyType.dbKeyFields}
|
||||
item={getExistingInserts(keyInfo)}
|
||||
keyColumn={null}
|
||||
onChangeItem={item => {
|
||||
if (item && item.records && item.records.length > 0) {
|
||||
const existingChange = changeSetRedis.changes.find(
|
||||
c => c.key === keyInfo.key && c.type === keyInfo.type
|
||||
);
|
||||
// @ts-ignore
|
||||
const listChange = existingChange || { key: keyInfo.key, type: 'list', inserts: [], updates: [], deletes: [] };
|
||||
// @ts-ignore
|
||||
listChange.inserts = item.records.filter(r => r.value.trim() !== '').map(r => ({ value: r.value }));
|
||||
addOrUpdateChange(listChange);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{:else if keyInfo.type === 'hash'}
|
||||
<DbKeyValueHashEdit
|
||||
dbKeyFields={keyInfo.keyType.dbKeyFields}
|
||||
item={getExistingInserts(keyInfo)}
|
||||
keyColumn={null}
|
||||
onChangeItem={item => {
|
||||
if (item && item.records && item.records.length > 0) {
|
||||
const existingChange = changeSetRedis.changes.find(
|
||||
c => c.key === keyInfo.key && c.type === keyInfo.type
|
||||
);
|
||||
// @ts-ignore
|
||||
const hashChange = existingChange || { key: keyInfo.key, type: 'hash', inserts: [], updates: [], deletes: [] };
|
||||
// @ts-ignore
|
||||
hashChange.inserts = item.records.filter(r => r.key.trim() !== '' && r.value.trim() !== '').map(r => ({ key: r.key, value: r.value, ttl: r.ttl ? parseInt(r.ttl) : undefined }));
|
||||
addOrUpdateChange(hashChange);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{:else if keyInfo.type === 'zset'}
|
||||
<DbKeyValueZSetEdit
|
||||
dbKeyFields={keyInfo.keyType.dbKeyFields}
|
||||
item={getExistingInserts(keyInfo)}
|
||||
keyColumn={null}
|
||||
onChangeItem={item => {
|
||||
if (item && item.records && item.records.length > 0) {
|
||||
const existingChange = changeSetRedis.changes.find(
|
||||
c => c.key === keyInfo.key && c.type === keyInfo.type
|
||||
);
|
||||
// @ts-ignore
|
||||
const zsetChange = existingChange || { key: keyInfo.key, type: 'zset', inserts: [], updates: [], deletes: [] };
|
||||
// @ts-ignore
|
||||
zsetChange.inserts = item.records.filter(r => r.member.trim() !== '' && r.score.trim() !== '').map(r => ({ member: r.member, score: parseFloat(r.score) }));
|
||||
addOrUpdateChange(zsetChange);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{:else if keyInfo.type === 'set'}
|
||||
<DbKeyValueSetEdit
|
||||
dbKeyFields={keyInfo.keyType.dbKeyFields}
|
||||
item={getExistingInserts(keyInfo)}
|
||||
keyColumn={null}
|
||||
onChangeItem={item => {
|
||||
if (item && item.records && item.records.length > 0) {
|
||||
const existingChange = changeSetRedis.changes.find(
|
||||
c => c.key === keyInfo.key && c.type === keyInfo.type
|
||||
);
|
||||
// @ts-ignore
|
||||
const setChange = existingChange || { key: keyInfo.key, type: 'set', inserts: [], updates: [], deletes: [] };
|
||||
// @ts-ignore
|
||||
setChange.inserts = item.records.filter(r => r.value.trim() !== '').map(r => ({ value: r.value }));
|
||||
addOrUpdateChange(setChange);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{:else if keyInfo.type === 'stream'}
|
||||
<DbKeyValueStreamEdit
|
||||
dbKeyFields={keyInfo.keyType.dbKeyFields}
|
||||
item={getExistingInserts(keyInfo)}
|
||||
keyColumn={null}
|
||||
onChangeItem={item => {
|
||||
if (item && item.records && item.records.length > 0) {
|
||||
const existingChange = changeSetRedis.changes.find(
|
||||
c => c.key === keyInfo.key && c.type === keyInfo.type
|
||||
);
|
||||
// @ts-ignore
|
||||
const streamChange = existingChange || { key: keyInfo.key, type: 'stream', inserts: [], updates: [], deletes: [] };
|
||||
// @ts-ignore
|
||||
streamChange.inserts = item.records.filter(r => r.value.trim() !== '').map(r => ({ id: r.id.trim() || '*', value: r.value }));
|
||||
addOrUpdateChange(streamChange);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{:else}
|
||||
<DbKeyItemDetail
|
||||
dbKeyFields={keyInfo.keyType.dbKeyFields}
|
||||
item={getDisplayRow(currentRow, keyInfo)}
|
||||
onChangeItem={item => {
|
||||
const existingChange = changeSetRedis.changes.find(
|
||||
c => c.key === keyInfo.key && c.type === keyInfo.type
|
||||
);
|
||||
|
||||
if (keyInfo.type === 'hash') {
|
||||
// @ts-ignore
|
||||
const hashChange = existingChange || { key: keyInfo.key, type: 'hash', inserts: [], updates: [], deletes: [] };
|
||||
// @ts-ignore
|
||||
const updateIndex = hashChange.updates?.findIndex(u => u.key === item.key) ?? -1;
|
||||
if (updateIndex >= 0) {
|
||||
// @ts-ignore
|
||||
hashChange.updates[updateIndex] = { key: item.key, value: item.value, ttl: item.TTL };
|
||||
} else {
|
||||
// @ts-ignore
|
||||
hashChange.updates = [...(hashChange.updates || []), { key: item.key, value: item.value, ttl: item.TTL }];
|
||||
}
|
||||
addOrUpdateChange(hashChange);
|
||||
} else if (keyInfo.type === 'list') {
|
||||
// @ts-ignore
|
||||
const listChange = existingChange || { key: keyInfo.key, type: 'list', inserts: [], updates: [], deletes: [] };
|
||||
// @ts-ignore
|
||||
const updateIndex = listChange.updates?.findIndex(u => u.index === item.rowNumber) ?? -1;
|
||||
if (updateIndex >= 0) {
|
||||
// @ts-ignore
|
||||
listChange.updates[updateIndex] = { index: item.rowNumber, value: item.value };
|
||||
} else {
|
||||
// @ts-ignore
|
||||
listChange.updates = [...(listChange.updates || []), { index: item.rowNumber, value: item.value }];
|
||||
}
|
||||
addOrUpdateChange(listChange);
|
||||
} else if (keyInfo.type === 'zset') {
|
||||
// @ts-ignore
|
||||
const zsetChange = existingChange || { key: keyInfo.key, type: 'zset', inserts: [], updates: [], deletes: [] };
|
||||
// @ts-ignore
|
||||
const updateIndex = zsetChange.updates?.findIndex(u => u.member === item.member) ?? -1;
|
||||
if (updateIndex >= 0) {
|
||||
// @ts-ignore
|
||||
zsetChange.updates[updateIndex] = { member: item.member, score: item.score };
|
||||
} else {
|
||||
// @ts-ignore
|
||||
zsetChange.updates = [...(zsetChange.updates || []), { member: item.member, score: item.score }];
|
||||
}
|
||||
addOrUpdateChange(zsetChange);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</VerticalSplitter>
|
||||
{:else}
|
||||
<div class="value-holder">
|
||||
<DbKeyValueDetail
|
||||
columnTitle="Value"
|
||||
value={getDisplayValue(keyInfo)}
|
||||
keyType={keyInfo.type}
|
||||
onChangeValue={value => {
|
||||
if (keyInfo.type === 'string') {
|
||||
addOrUpdateChange({
|
||||
key: key,
|
||||
type: 'string',
|
||||
value: value,
|
||||
});
|
||||
} else if (keyInfo.type === 'JSON') {
|
||||
addOrUpdateChange({
|
||||
key: key,
|
||||
type: 'json',
|
||||
value: value,
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="2">
|
||||
<DbKeyItemDetail dbKeyFields={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>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<svelte:fragment slot="toolstrip">
|
||||
<ToolStripButton
|
||||
icon="icon save"
|
||||
on:click={saveAll}
|
||||
disabled={!hasChanges}
|
||||
>{_t('common.save', { defaultMessage: 'Save' })}</ToolStripButton>
|
||||
{#if keyInfo.keyType?.addMethod && keyInfo.keyType?.showItemList}
|
||||
<ToolStripButton icon="icon add" on:click={() => { showAddForm = true; }}>Add field</ToolStripButton>
|
||||
{/if}
|
||||
<ToolStripButton icon="icon refresh" on:click={refresh}>{_t('common.refresh', { defaultMessage: 'Refresh' })}</ToolStripButton>
|
||||
</svelte:fragment>
|
||||
</ToolStripContainer>
|
||||
{/await}
|
||||
|
||||
<style>
|
||||
|
||||
264
packages/web/src/tabs/DbKeyTab.svelte
Normal file
264
packages/web/src/tabs/DbKeyTab.svelte
Normal file
@@ -0,0 +1,264 @@
|
||||
<script lang="ts">
|
||||
import DbKeyValueDetail from '../dbkeyvalue/DbKeyValueDetail.svelte';
|
||||
import DbKeyValueHashEdit from '../dbkeyvalue/DbKeyValueHashEdit.svelte';
|
||||
import DbKeyValueListEdit from '../dbkeyvalue/DbKeyValueListEdit.svelte';
|
||||
import DbKeyValueSetEdit from '../dbkeyvalue/DbKeyValueSetEdit.svelte';
|
||||
import DbKeyValueZSetEdit from '../dbkeyvalue/DbKeyValueZSetEdit.svelte';
|
||||
import DbKeyValueStreamEdit from '../dbkeyvalue/DbKeyValueStreamEdit.svelte';
|
||||
import FormFieldTemplateLarge from "../forms/FormFieldTemplateLarge.svelte";
|
||||
import FormProvider from '../forms/FormProvider.svelte';
|
||||
import SelectField from '../forms/SelectField.svelte';
|
||||
import TextField from "../forms/TextField.svelte";
|
||||
import ToolStripContainer from '../buttons/ToolStripContainer.svelte';
|
||||
import ToolStripButton from '../buttons/ToolStripButton.svelte';
|
||||
import { _t } from '../translations';
|
||||
import { apiCall } from '../utility/api';
|
||||
import { showSnackbarSuccess } from '../utility/snackbar';
|
||||
import { findEngineDriver } from 'dbgate-tools';
|
||||
import { activeDbKeysStore, getExtensions, openedTabs } from '../stores';
|
||||
import { useConnectionInfo } from '../utility/metadataLoaders';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
|
||||
export let conid;
|
||||
export let database;
|
||||
export let tabid;
|
||||
export let initialKeyName = '';
|
||||
|
||||
$: connection = useConnectionInfo({ conid });
|
||||
$: driver = $connection && findEngineDriver($connection, getExtensions());
|
||||
|
||||
let item = {};
|
||||
let keyName = initialKeyName || '';
|
||||
$: type = driver?.supportedKeyTypes?.[0]?.name || '';
|
||||
|
||||
$: console.log('DbKeyTab debug:', { conid, database, connection: $connection, driver, hasTypes: driver?.supportedKeyTypes?.length });
|
||||
|
||||
async function handleSave() {
|
||||
if (!driver) return;
|
||||
|
||||
const typeConfig = driver.supportedKeyTypes.find(x => x.name == type);
|
||||
|
||||
if (type === 'hash' && item.records && Array.isArray(item.records)) {
|
||||
for (const record of item.records) {
|
||||
if (record.key && record.value) {
|
||||
await apiCall('database-connections/call-method', {
|
||||
conid,
|
||||
database,
|
||||
method: typeConfig.addMethod,
|
||||
args: [keyName, record.key, record.value],
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (type === 'list' && item.records && Array.isArray(item.records)) {
|
||||
const values = item.records
|
||||
.map(record => record.value)
|
||||
.filter(value => value);
|
||||
|
||||
if (values.length > 0) {
|
||||
await apiCall('database-connections/call-method', {
|
||||
conid,
|
||||
database,
|
||||
method: typeConfig.addMethod,
|
||||
args: [keyName, ...values],
|
||||
});
|
||||
}
|
||||
} else if (type === 'set' && item.records && Array.isArray(item.records)) {
|
||||
const values = item.records
|
||||
.map(record => record.value)
|
||||
.filter(value => value);
|
||||
|
||||
if (values.length > 0) {
|
||||
await apiCall('database-connections/call-method', {
|
||||
conid,
|
||||
database,
|
||||
method: typeConfig.addMethod,
|
||||
args: [keyName, ...values],
|
||||
});
|
||||
}
|
||||
} else if (type === 'zset' && item.records && Array.isArray(item.records)) {
|
||||
for (const record of item.records) {
|
||||
if (record.member && record.score) {
|
||||
await apiCall('database-connections/call-method', {
|
||||
conid,
|
||||
database,
|
||||
method: typeConfig.addMethod,
|
||||
args: [keyName, record.member, parseFloat(record.score)],
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (type === 'stream' && item.records && Array.isArray(item.records)) {
|
||||
for (const record of item.records) {
|
||||
if (record.value) {
|
||||
const streamId = record.id || '*';
|
||||
await apiCall('database-connections/call-method', {
|
||||
conid,
|
||||
database,
|
||||
method: typeConfig.addMethod,
|
||||
args: [keyName, streamId, record.value],
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await apiCall('database-connections/call-method', {
|
||||
conid,
|
||||
database,
|
||||
method: typeConfig.addMethod,
|
||||
args: [keyName, ...typeConfig.dbKeyFields.map(fld => item[fld.name])],
|
||||
});
|
||||
}
|
||||
|
||||
showSnackbarSuccess('Key created successfully');
|
||||
|
||||
$activeDbKeysStore = {
|
||||
...$activeDbKeysStore,
|
||||
[`${conid}:${database}`]: keyName,
|
||||
};
|
||||
|
||||
openedTabs.update(tabs =>
|
||||
tabs.map(tab =>
|
||||
tab.tabid === tabid
|
||||
? { ...tab, closedTime: new Date().getTime(), selected: false }
|
||||
: tab
|
||||
)
|
||||
);
|
||||
|
||||
openNewTab({
|
||||
tabComponent: 'DbKeyDetailTab',
|
||||
title: keyName || '(no name)',
|
||||
icon: 'img keydb',
|
||||
props: {
|
||||
isDefaultBrowser: true,
|
||||
conid,
|
||||
database,
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if driver && driver.supportedKeyTypes && driver.supportedKeyTypes.length > 0}
|
||||
<FormProvider>
|
||||
<ToolStripContainer>
|
||||
<div class="container">
|
||||
<div class="flex flex-gap">
|
||||
<div class="col-9">
|
||||
<FormFieldTemplateLarge label={_t('addDbKeyModal.key', { defaultMessage: 'Key' })} type="text" noMargin>
|
||||
<TextField
|
||||
value={keyName}
|
||||
on:change={e => {
|
||||
// @ts-ignore
|
||||
keyName = e.target.value;
|
||||
}}
|
||||
/>
|
||||
</FormFieldTemplateLarge>
|
||||
</div>
|
||||
|
||||
<div class="col-3">
|
||||
<FormFieldTemplateLarge label={_t('addDbKeyModal.type', { defaultMessage: 'Type' })} type="combo" noMargin>
|
||||
<SelectField
|
||||
options={driver.supportedKeyTypes.map(t => ({ value: t.name, label: t.label }))}
|
||||
value={type}
|
||||
isNative
|
||||
on:change={e => {
|
||||
type = e.detail;
|
||||
}}
|
||||
/>
|
||||
</FormFieldTemplateLarge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if type === 'hash'}
|
||||
<DbKeyValueHashEdit
|
||||
dbKeyFields={driver.supportedKeyTypes.find(x => x.name == type).dbKeyFields}
|
||||
{item}
|
||||
onChangeItem={value => {
|
||||
item = value;
|
||||
}}
|
||||
/>
|
||||
{:else if type === 'list'}
|
||||
<DbKeyValueListEdit
|
||||
dbKeyFields={driver.supportedKeyTypes.find(x => x.name == type).dbKeyFields}
|
||||
{item}
|
||||
onChangeItem={value => {
|
||||
item = value;
|
||||
}}
|
||||
/>
|
||||
{:else if type === 'set'}
|
||||
<DbKeyValueSetEdit
|
||||
dbKeyFields={driver.supportedKeyTypes.find(x => x.name == type).dbKeyFields}
|
||||
{item}
|
||||
onChangeItem={value => {
|
||||
item = value;
|
||||
}}
|
||||
/>
|
||||
{:else if type === 'zset'}
|
||||
<DbKeyValueZSetEdit
|
||||
dbKeyFields={driver.supportedKeyTypes.find(x => x.name == type).dbKeyFields}
|
||||
{item}
|
||||
onChangeItem={value => {
|
||||
item = value;
|
||||
}}
|
||||
/>
|
||||
{:else if type === 'stream'}
|
||||
<DbKeyValueStreamEdit
|
||||
dbKeyFields={driver.supportedKeyTypes.find(x => x.name == type).dbKeyFields}
|
||||
{item}
|
||||
onChangeItem={value => {
|
||||
item = value;
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<DbKeyValueDetail
|
||||
columnTitle="Value"
|
||||
value={item.value}
|
||||
onChangeValue={value => {
|
||||
item = { ...item, value };
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<svelte:fragment slot="toolstrip">
|
||||
<ToolStripButton
|
||||
icon="icon save"
|
||||
on:click={handleSave}
|
||||
disabled={!keyName || keyName.trim() === ''}
|
||||
>{_t('common.save', { defaultMessage: 'Save' })}</ToolStripButton>
|
||||
</svelte:fragment>
|
||||
</ToolStripContainer>
|
||||
</FormProvider>
|
||||
{:else}
|
||||
<div class="wrapper">
|
||||
<div class="container">
|
||||
<div class="loading">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
overflow: hidden;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.flex-gap {
|
||||
gap: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -18,6 +18,7 @@ import * as JsonTab from './JsonTab.svelte';
|
||||
import * as ChangelogTab from './ChangelogTab.svelte';
|
||||
import * as DiagramTab from './DiagramTab.svelte';
|
||||
import * as DbKeyDetailTab from './DbKeyDetailTab.svelte';
|
||||
import * as DbKeyTab from './DbKeyTab.svelte';
|
||||
import * as QueryDataTab from './QueryDataTab.svelte';
|
||||
import * as ConnectionTab from './ConnectionTab.svelte';
|
||||
import * as MapTab from './MapTab.svelte';
|
||||
@@ -50,6 +51,7 @@ export default {
|
||||
ChangelogTab,
|
||||
DiagramTab,
|
||||
DbKeyDetailTab,
|
||||
DbKeyTab,
|
||||
QueryDataTab,
|
||||
ConnectionTab,
|
||||
MapTab,
|
||||
|
||||
@@ -51,25 +51,48 @@
|
||||
function handleAddKey() {
|
||||
const connection = $currentDatabase?.connection;
|
||||
const database = $currentDatabase?.name;
|
||||
const driver = findEngineDriver(connection, getExtensions());
|
||||
const focusedKey = $focusedTreeDbKey;
|
||||
|
||||
let initialKeyName = '';
|
||||
if (focusedKey) {
|
||||
if (focusedKey.type === 'dir' && focusedKey.key) {
|
||||
initialKeyName = focusedKey.key + treeKeySeparator;
|
||||
} else if (focusedKey.key) {
|
||||
const lastSeparatorIndex = focusedKey.key.lastIndexOf(treeKeySeparator);
|
||||
if (lastSeparatorIndex !== -1) {
|
||||
initialKeyName = focusedKey.key.substring(0, lastSeparatorIndex + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showModal(AddDbKeyModal, {
|
||||
conid: connection._id,
|
||||
database,
|
||||
driver,
|
||||
onConfirm: async item => {
|
||||
const type = driver.supportedKeyTypes.find(x => x.name == item.type);
|
||||
|
||||
await apiCall('database-connections/call-method', {
|
||||
conid: connection._id,
|
||||
database,
|
||||
method: type.addMethod,
|
||||
args: [item.keyName, ...type.dbKeyFields.map(fld => item[fld.name])],
|
||||
});
|
||||
|
||||
reloadModel();
|
||||
openNewTab({
|
||||
tabComponent: 'DbKeyTab',
|
||||
title: 'Add key',
|
||||
icon: 'img keydb',
|
||||
props: {
|
||||
conid: connection?._id,
|
||||
database,
|
||||
initialKeyName,
|
||||
},
|
||||
});
|
||||
|
||||
// showModal(AddDbKeyModal, {
|
||||
// conid: connection._id,
|
||||
// database,
|
||||
// driver,
|
||||
// onConfirm: async item => {
|
||||
// const type = driver.supportedKeyTypes.find(x => x.name == item.type);
|
||||
|
||||
// await apiCall('database-connections/call-method', {
|
||||
// conid: connection._id,
|
||||
// database,
|
||||
// method: type.addMethod,
|
||||
// args: [item.keyName, ...type.dbKeyFields.map(fld => item[fld.name])],
|
||||
// });
|
||||
|
||||
// reloadModel();
|
||||
// },
|
||||
// });
|
||||
}
|
||||
|
||||
$: differentFocusedDb =
|
||||
|
||||
@@ -459,6 +459,15 @@ const driver = {
|
||||
case 'string':
|
||||
res.value = await dbhan.client.get(key);
|
||||
break;
|
||||
case 'ReJSON-RL':
|
||||
res.type = 'JSON';
|
||||
try {
|
||||
const jsonData = await dbhan.client.call('JSON.GET', key);
|
||||
res.value = JSON.stringify(JSON.parse(jsonData), null, 2);
|
||||
} catch (e) {
|
||||
res.value = '';
|
||||
}
|
||||
break;
|
||||
// case 'list':
|
||||
// res.tableColumns = [{ name: 'value' }];
|
||||
// res.addMethod = 'rpush';
|
||||
@@ -495,6 +504,10 @@ const driver = {
|
||||
switch (method) {
|
||||
case 'mdel':
|
||||
return await this.deleteBranch(dbhan, args[0]);
|
||||
case 'zadd':
|
||||
return await dbhan.client.zadd(args[0], args[2], args[1]);
|
||||
case 'json.set':
|
||||
return await dbhan.client.call('JSON.SET', args[0], '$', args[1]);
|
||||
case 'xaddjson':
|
||||
let json;
|
||||
try {
|
||||
@@ -528,14 +541,28 @@ const driver = {
|
||||
const res = await dbhan.client.zscan(key, cursor, 'COUNT', count);
|
||||
return {
|
||||
cursor: parseInt(res[0]),
|
||||
items: _.chunk(res[1], 2).map((item) => ({ value: item[0], score: item[1] })),
|
||||
items: _.chunk(res[1], 2).map((item) => ({ member: item[0], score: item[1] })),
|
||||
};
|
||||
}
|
||||
case 'hash': {
|
||||
const res = await dbhan.client.hscan(key, cursor, 'COUNT', count);
|
||||
const fields = _.chunk(res[1], 2);
|
||||
|
||||
// Get TTL for each hash field (Redis 7.4+)
|
||||
const items = await Promise.all(
|
||||
fields.map(async ([fieldKey, fieldValue]) => {
|
||||
try {
|
||||
const ttl = await dbhan.client.call('HTTL', key, 'FIELDS', 1, fieldKey);
|
||||
return { key: fieldKey, value: fieldValue, TTL: ttl && ttl[0] !== undefined ? ttl[0] : null };
|
||||
} catch (e) {
|
||||
return { key: fieldKey, value: fieldValue };
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
cursor: parseInt(res[0]),
|
||||
items: _.chunk(res[1], 2).map((item) => ({ key: item[0], value: item[1] })),
|
||||
items,
|
||||
};
|
||||
}
|
||||
case 'stream': {
|
||||
|
||||
@@ -49,7 +49,7 @@ const driver = {
|
||||
{
|
||||
name: 'set',
|
||||
label: 'Set',
|
||||
dbKeyFields: [{ name: 'value' }],
|
||||
dbKeyFields: [{ name: 'value', readOnly: true }],
|
||||
keyColumn: 'value',
|
||||
addMethod: 'sadd',
|
||||
showItemList: true,
|
||||
@@ -57,15 +57,15 @@ const driver = {
|
||||
{
|
||||
name: 'zset',
|
||||
label: 'Sorted Set',
|
||||
dbKeyFields: [{ name: 'score' }, { name: 'value' }],
|
||||
keyColumn: 'value',
|
||||
dbKeyFields: [{ name: 'member', readOnly: true }, { name: 'score' }],
|
||||
keyColumn: 'member',
|
||||
addMethod: 'zadd',
|
||||
showItemList: true,
|
||||
},
|
||||
{
|
||||
name: 'hash',
|
||||
label: 'Hash',
|
||||
dbKeyFields: [{ name: 'key' }, { name: 'value' }],
|
||||
dbKeyFields: [{ name: 'key', readOnly: true }, { name: 'value' }, { name: 'TTL' }],
|
||||
keyColumn: 'key',
|
||||
addMethod: 'hset',
|
||||
showItemList: true,
|
||||
@@ -73,11 +73,17 @@ const driver = {
|
||||
{
|
||||
name: 'stream',
|
||||
label: 'Stream',
|
||||
dbKeyFields: [{ name: 'id' }, { name: 'value' }],
|
||||
dbKeyFields: [{ name: 'id', readOnly: true }, { name: 'value', readOnly: true }],
|
||||
keyColumn: 'id',
|
||||
addMethod: 'xaddjson',
|
||||
showItemList: true,
|
||||
},
|
||||
{
|
||||
name: 'json',
|
||||
label: 'JSON',
|
||||
dbKeyFields: [{ name: 'value' }],
|
||||
addMethod: 'json.set',
|
||||
}
|
||||
],
|
||||
|
||||
showConnectionField: (field, values) => {
|
||||
|
||||
Reference in New Issue
Block a user