feat: add stream type support
This commit is contained in:
parent
9b63e51300
commit
2d0e8f5445
|
@ -497,6 +497,21 @@ func (c *connectionService) GetKeyValue(connName string, db int, key string) (re
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
value = items
|
value = items
|
||||||
|
case "stream":
|
||||||
|
var msgs []redis.XMessage
|
||||||
|
items := []types.StreamItem{}
|
||||||
|
msgs, err = rdb.XRevRange(ctx, key, "+", "-").Result()
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, msg := range msgs {
|
||||||
|
items = append(items, types.StreamItem{
|
||||||
|
ID: msg.ID,
|
||||||
|
Value: msg.Values,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
value = items
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
|
@ -538,13 +553,10 @@ func (c *connectionService) SetKeyValue(connName string, db int, key, keyType st
|
||||||
resp.Msg = "invalid list value"
|
resp.Msg = "invalid list value"
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
_, err = rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
|
err = rdb.LPush(ctx, key, strs...).Err()
|
||||||
pipe.LPush(ctx, key, strs...)
|
if err == nil && expiration > 0 {
|
||||||
if expiration > 0 {
|
rdb.Expire(ctx, key, expiration)
|
||||||
pipe.Expire(ctx, key, expiration)
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
case "hash":
|
case "hash":
|
||||||
if strs, ok := value.([]any); !ok {
|
if strs, ok := value.([]any); !ok {
|
||||||
|
@ -552,11 +564,7 @@ func (c *connectionService) SetKeyValue(connName string, db int, key, keyType st
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
if len(strs) > 1 {
|
if len(strs) > 1 {
|
||||||
kvs := map[string]any{}
|
err = rdb.HSet(ctx, key, strs).Err()
|
||||||
for i := 0; i < len(strs); i += 2 {
|
|
||||||
kvs[strs[i].(string)] = strs[i+1]
|
|
||||||
}
|
|
||||||
err = rdb.HSet(ctx, key, kvs).Err()
|
|
||||||
if err == nil && expiration > 0 {
|
if err == nil && expiration > 0 {
|
||||||
rdb.Expire(ctx, key, expiration)
|
rdb.Expire(ctx, key, expiration)
|
||||||
}
|
}
|
||||||
|
@ -594,6 +602,22 @@ func (c *connectionService) SetKeyValue(connName string, db int, key, keyType st
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "stream":
|
||||||
|
if strs, ok := value.([]any); !ok {
|
||||||
|
resp.Msg = "invalid stream value"
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
if len(strs) > 2 {
|
||||||
|
err = rdb.XAdd(ctx, &redis.XAddArgs{
|
||||||
|
Stream: key,
|
||||||
|
ID: strs[0].(string),
|
||||||
|
Values: strs[1:],
|
||||||
|
}).Err()
|
||||||
|
if err == nil && expiration > 0 {
|
||||||
|
rdb.Expire(ctx, key, expiration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -886,6 +910,41 @@ func (c *connectionService) AddZSetValue(connName string, db int, key string, ac
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddStreamValue add stream field
|
||||||
|
func (c *connectionService) AddStreamValue(connName string, db int, key, ID string, fieldItems []any) (resp types.JSResp) {
|
||||||
|
rdb, ctx, err := c.getRedisClient(connName, db)
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = rdb.XAdd(ctx, &redis.XAddArgs{
|
||||||
|
Stream: key,
|
||||||
|
ID: ID,
|
||||||
|
Values: fieldItems,
|
||||||
|
}).Result()
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Success = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveStreamValues remove stream values by id
|
||||||
|
func (c *connectionService) RemoveStreamValues(connName string, db int, key string, IDs []string) (resp types.JSResp) {
|
||||||
|
rdb, ctx, err := c.getRedisClient(connName, db)
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = rdb.XDel(ctx, key, IDs...).Result()
|
||||||
|
resp.Success = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// SetKeyTTL set ttl of key
|
// SetKeyTTL set ttl of key
|
||||||
func (c *connectionService) SetKeyTTL(connName string, db int, key string, ttl int64) (resp types.JSResp) {
|
func (c *connectionService) SetKeyTTL(connName string, db int, key string, ttl int64) (resp types.JSResp) {
|
||||||
rdb, ctx, err := c.getRedisClient(connName, db)
|
rdb, ctx, err := c.getRedisClient(connName, db)
|
||||||
|
|
|
@ -4,3 +4,8 @@ type ZSetItem struct {
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
Score float64 `json:"score"`
|
Score float64 `json:"score"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StreamItem struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Value map[string]any `json:"value"`
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import Save from '../icons/Save.vue'
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
bindKey: String,
|
bindKey: String,
|
||||||
editing: Boolean,
|
editing: Boolean,
|
||||||
|
readonly: Boolean,
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['edit', 'delete', 'save', 'cancel'])
|
const emit = defineEmits(['edit', 'delete', 'save', 'cancel'])
|
||||||
|
@ -20,7 +21,7 @@ const emit = defineEmits(['edit', 'delete', 'save', 'cancel'])
|
||||||
<icon-button :icon="Close" @click="emit('cancel')" />
|
<icon-button :icon="Close" @click="emit('cancel')" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex-box-h edit-column-func">
|
<div v-else class="flex-box-h edit-column-func">
|
||||||
<icon-button :icon="Edit" @click="emit('edit')" />
|
<icon-button v-if="!props.readonly" :icon="Edit" @click="emit('edit')" />
|
||||||
<n-popconfirm :negative-text="$t('cancel')" :positive-text="$t('confirm')" @positive-click="emit('delete')">
|
<n-popconfirm :negative-text="$t('cancel')" :positive-text="$t('confirm')" @positive-click="emit('delete')">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<icon-button :icon="Delete" />
|
<icon-button :icon="Delete" />
|
||||||
|
|
|
@ -13,6 +13,7 @@ import useConnectionStore from '../../stores/connections.js'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useConfirmDialog } from '../../utils/confirm_dialog.js'
|
import { useConfirmDialog } from '../../utils/confirm_dialog.js'
|
||||||
import ContentServerStatus from '../content_value/ContentServerStatus.vue'
|
import ContentServerStatus from '../content_value/ContentServerStatus.vue'
|
||||||
|
import ContentValueStream from '../content_value/ContentValueStream.vue'
|
||||||
|
|
||||||
const serverInfo = ref({})
|
const serverInfo = ref({})
|
||||||
const autoRefresh = ref(false)
|
const autoRefresh = ref(false)
|
||||||
|
@ -62,6 +63,7 @@ const valueComponents = {
|
||||||
[types.LIST]: ContentValueList,
|
[types.LIST]: ContentValueList,
|
||||||
[types.SET]: ContentValueSet,
|
[types.SET]: ContentValueSet,
|
||||||
[types.ZSET]: ContentValueZset,
|
[types.ZSET]: ContentValueZset,
|
||||||
|
[types.STREAM]: ContentValueStream,
|
||||||
}
|
}
|
||||||
|
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
|
@ -71,7 +73,7 @@ const tab = computed(() =>
|
||||||
map(tabStore.tabs, (item) => ({
|
map(tabStore.tabs, (item) => ({
|
||||||
key: item.name,
|
key: item.name,
|
||||||
label: item.title,
|
label: item.title,
|
||||||
}))
|
})),
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
@ -80,7 +82,7 @@ watch(
|
||||||
if (nav === 'browser') {
|
if (nav === 'browser') {
|
||||||
refreshInfo()
|
refreshInfo()
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
const tabContent = computed(() => {
|
const tabContent = computed(() => {
|
||||||
|
|
|
@ -11,6 +11,8 @@ import { useMessage } from 'naive-ui'
|
||||||
import IconButton from '../common/IconButton.vue'
|
import IconButton from '../common/IconButton.vue'
|
||||||
import useConnectionStore from '../../stores/connections.js'
|
import useConnectionStore from '../../stores/connections.js'
|
||||||
import { useConfirmDialog } from '../../utils/confirm_dialog.js'
|
import { useConfirmDialog } from '../../utils/confirm_dialog.js'
|
||||||
|
import Copy from '../icons/Copy.vue'
|
||||||
|
import { ClipboardSetText } from '../../../wailsjs/runtime/runtime.js'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
server: String,
|
server: String,
|
||||||
|
@ -38,6 +40,18 @@ const onReloadKey = () => {
|
||||||
connectionStore.loadKeyValue(props.server, props.db, props.keyPath)
|
connectionStore.loadKeyValue(props.server, props.db, props.keyPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onCopyKey = () => {
|
||||||
|
ClipboardSetText(props.keyPath)
|
||||||
|
.then((succ) => {
|
||||||
|
if (succ) {
|
||||||
|
message.success(i18n.t('copy_succ'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
message.error(e.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const confirmDialog = useConfirmDialog()
|
const confirmDialog = useConfirmDialog()
|
||||||
const onDeleteKey = () => {
|
const onDeleteKey = () => {
|
||||||
confirmDialog.warning(i18n.t('remove_tip', { name: props.keyPath }), () => {
|
confirmDialog.warning(i18n.t('remove_tip', { name: props.keyPath }), () => {
|
||||||
|
@ -53,12 +67,13 @@ const onDeleteKey = () => {
|
||||||
<template>
|
<template>
|
||||||
<div class="content-toolbar flex-box-h">
|
<div class="content-toolbar flex-box-h">
|
||||||
<n-input-group>
|
<n-input-group>
|
||||||
<redis-type-tag :type="props.keyType" size="large"></redis-type-tag>
|
<redis-type-tag :type="props.keyType" size="large" />
|
||||||
<n-input v-model:value="props.keyPath">
|
<n-input v-model:value="props.keyPath">
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<icon-button :icon="Refresh" t-tooltip="reload" size="18" @click="onReloadKey" />
|
<icon-button :icon="Refresh" t-tooltip="reload" size="18" @click="onReloadKey" />
|
||||||
</template>
|
</template>
|
||||||
</n-input>
|
</n-input>
|
||||||
|
<icon-button :icon="Copy" t-tooltip="copy_key" size="18" border @click="onCopyKey" />
|
||||||
</n-input-group>
|
</n-input-group>
|
||||||
<n-button-group>
|
<n-button-group>
|
||||||
<n-tooltip>
|
<n-tooltip>
|
||||||
|
|
|
@ -0,0 +1,202 @@
|
||||||
|
<script setup>
|
||||||
|
import { computed, h, reactive, ref } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import ContentToolbar from './ContentToolbar.vue'
|
||||||
|
import AddLink from '../icons/AddLink.vue'
|
||||||
|
import { NButton, NCode, NIcon, NInput, useMessage } from 'naive-ui'
|
||||||
|
import { types, types as redisTypes } from '../../consts/support_redis_type.js'
|
||||||
|
import EditableTableColumn from '../common/EditableTableColumn.vue'
|
||||||
|
import useDialogStore from '../../stores/dialog.js'
|
||||||
|
import useConnectionStore from '../../stores/connections.js'
|
||||||
|
import { includes, keys, some, values } from 'lodash'
|
||||||
|
|
||||||
|
const i18n = useI18n()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
name: String,
|
||||||
|
db: Number,
|
||||||
|
keyPath: String,
|
||||||
|
ttl: {
|
||||||
|
type: Number,
|
||||||
|
default: -1,
|
||||||
|
},
|
||||||
|
value: Object,
|
||||||
|
})
|
||||||
|
|
||||||
|
const filterOption = computed(() => [
|
||||||
|
{
|
||||||
|
value: 1,
|
||||||
|
label: i18n.t('field'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 2,
|
||||||
|
label: i18n.t('value'),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
const filterType = ref(1)
|
||||||
|
|
||||||
|
const connectionStore = useConnectionStore()
|
||||||
|
const dialogStore = useDialogStore()
|
||||||
|
const keyType = redisTypes.STREAM
|
||||||
|
const idColumn = reactive({
|
||||||
|
key: 'id',
|
||||||
|
title: 'ID',
|
||||||
|
align: 'center',
|
||||||
|
titleAlign: 'center',
|
||||||
|
resizable: true,
|
||||||
|
})
|
||||||
|
const valueColumn = reactive({
|
||||||
|
key: 'value',
|
||||||
|
title: i18n.t('value'),
|
||||||
|
align: 'center',
|
||||||
|
titleAlign: 'center',
|
||||||
|
resizable: true,
|
||||||
|
filterOptionValue: null,
|
||||||
|
filter(value, row) {
|
||||||
|
const v = value.toString()
|
||||||
|
if (filterType.value === 1) {
|
||||||
|
// filter key
|
||||||
|
return some(keys(row.value), (key) => includes(key, v))
|
||||||
|
} else {
|
||||||
|
// filter value
|
||||||
|
return some(values(row.value), (val) => includes(val, v))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// sorter: (row1, row2) => row1.value - row2.value,
|
||||||
|
// ellipsis: {
|
||||||
|
// tooltip: true
|
||||||
|
// },
|
||||||
|
render: (row) => {
|
||||||
|
return h(NCode, { language: 'json', wordWrap: true }, { default: () => JSON.stringify(row.value) })
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const actionColumn = {
|
||||||
|
key: 'action',
|
||||||
|
title: i18n.t('action'),
|
||||||
|
width: 60,
|
||||||
|
align: 'center',
|
||||||
|
titleAlign: 'center',
|
||||||
|
fixed: 'right',
|
||||||
|
render: (row) => {
|
||||||
|
return h(EditableTableColumn, {
|
||||||
|
bindKey: row.id,
|
||||||
|
readonly: true,
|
||||||
|
onDelete: async () => {
|
||||||
|
try {
|
||||||
|
const { success, msg } = await connectionStore.removeStreamValues(
|
||||||
|
props.name,
|
||||||
|
props.db,
|
||||||
|
props.keyPath,
|
||||||
|
row.id,
|
||||||
|
)
|
||||||
|
if (success) {
|
||||||
|
connectionStore.loadKeyValue(props.name, props.db, props.keyPath).then((r) => {})
|
||||||
|
message.success(i18n.t('delete_key_succ', { key: row.id }))
|
||||||
|
// update display value
|
||||||
|
// if (!isEmpty(removed)) {
|
||||||
|
// for (const elem of removed) {
|
||||||
|
// delete props.value[elem]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
message.error(msg)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
message.error(e.message)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const columns = reactive([idColumn, valueColumn, actionColumn])
|
||||||
|
|
||||||
|
const tableData = computed(() => {
|
||||||
|
const data = []
|
||||||
|
for (const elem of props.value) {
|
||||||
|
data.push({
|
||||||
|
id: elem.id,
|
||||||
|
value: elem.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
})
|
||||||
|
const message = useMessage()
|
||||||
|
const onAddRow = () => {
|
||||||
|
dialogStore.openAddFieldsDialog(props.name, props.db, props.keyPath, types.STREAM)
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterValue = ref('')
|
||||||
|
const onFilterInput = (val) => {
|
||||||
|
valueColumn.filterOptionValue = val
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChangeFilterType = (type) => {
|
||||||
|
onFilterInput(filterValue.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearFilter = () => {
|
||||||
|
idColumn.filterOptionValue = null
|
||||||
|
valueColumn.filterOptionValue = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const onUpdateFilter = (filters, sourceColumn) => {
|
||||||
|
switch (filterType.value) {
|
||||||
|
case filterOption[0].value:
|
||||||
|
idColumn.filterOptionValue = filters[sourceColumn.key]
|
||||||
|
break
|
||||||
|
case filterOption[1].value:
|
||||||
|
valueColumn.filterOptionValue = filters[sourceColumn.key]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="content-wrapper flex-box-v">
|
||||||
|
<content-toolbar :db="props.db" :key-path="props.keyPath" :key-type="keyType" :server="props.name" :ttl="ttl" />
|
||||||
|
<div class="tb2 flex-box-h">
|
||||||
|
<div class="flex-box-h">
|
||||||
|
<n-input-group>
|
||||||
|
<n-select
|
||||||
|
v-model:value="filterType"
|
||||||
|
:consistent-menu-width="false"
|
||||||
|
:options="filterOption"
|
||||||
|
style="width: 120px"
|
||||||
|
@update:value="onChangeFilterType"
|
||||||
|
/>
|
||||||
|
<n-input
|
||||||
|
v-model:value="filterValue"
|
||||||
|
:placeholder="$t('search')"
|
||||||
|
clearable
|
||||||
|
@clear="clearFilter"
|
||||||
|
@update:value="onFilterInput"
|
||||||
|
/>
|
||||||
|
</n-input-group>
|
||||||
|
</div>
|
||||||
|
<div class="flex-item-expand"></div>
|
||||||
|
<n-button plain @click="onAddRow">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon :component="AddLink" size="18" />
|
||||||
|
</template>
|
||||||
|
{{ $t('add_row') }}
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
<div class="fill-height flex-box-h" style="user-select: text">
|
||||||
|
<n-data-table
|
||||||
|
:key="(row) => row.id"
|
||||||
|
:columns="columns"
|
||||||
|
:data="tableData"
|
||||||
|
:single-column="true"
|
||||||
|
:single-line="false"
|
||||||
|
flex-height
|
||||||
|
max-height="100%"
|
||||||
|
size="small"
|
||||||
|
striped
|
||||||
|
virtual-scroll
|
||||||
|
@update:filters="onUpdateFilter"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
|
@ -10,6 +10,8 @@ import AddListValue from '../new_value/AddListValue.vue'
|
||||||
import AddHashValue from '../new_value/AddHashValue.vue'
|
import AddHashValue from '../new_value/AddHashValue.vue'
|
||||||
import AddZSetValue from '../new_value/AddZSetValue.vue'
|
import AddZSetValue from '../new_value/AddZSetValue.vue'
|
||||||
import useConnectionStore from '../../stores/connections.js'
|
import useConnectionStore from '../../stores/connections.js'
|
||||||
|
import NewStreamValue from '../new_value/NewStreamValue.vue'
|
||||||
|
import { size, slice } from 'lodash'
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const newForm = reactive({
|
const newForm = reactive({
|
||||||
|
@ -29,6 +31,7 @@ const addValueComponent = {
|
||||||
[types.LIST]: AddListValue,
|
[types.LIST]: AddListValue,
|
||||||
[types.SET]: NewSetValue,
|
[types.SET]: NewSetValue,
|
||||||
[types.ZSET]: AddZSetValue,
|
[types.ZSET]: AddZSetValue,
|
||||||
|
[types.STREAM]: NewStreamValue,
|
||||||
}
|
}
|
||||||
const defaultValue = {
|
const defaultValue = {
|
||||||
[types.STRING]: '',
|
[types.STRING]: '',
|
||||||
|
@ -36,6 +39,7 @@ const defaultValue = {
|
||||||
[types.LIST]: [],
|
[types.LIST]: [],
|
||||||
[types.SET]: [],
|
[types.SET]: [],
|
||||||
[types.ZSET]: [],
|
[types.ZSET]: [],
|
||||||
|
[types.STREAM]: ['*'],
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -52,6 +56,8 @@ const title = computed(() => {
|
||||||
return i18n.t('new_field')
|
return i18n.t('new_field')
|
||||||
case types.ZSET:
|
case types.ZSET:
|
||||||
return i18n.t('new_field')
|
return i18n.t('new_field')
|
||||||
|
case types.STREAM:
|
||||||
|
return i18n.t('new_field')
|
||||||
}
|
}
|
||||||
return ''
|
return ''
|
||||||
})
|
})
|
||||||
|
@ -69,7 +75,7 @@ watch(
|
||||||
newForm.opType = 0
|
newForm.opType = 0
|
||||||
newForm.value = null
|
newForm.value = null
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
const connectionStore = useConnectionStore()
|
const connectionStore = useConnectionStore()
|
||||||
|
@ -143,6 +149,28 @@ const onAdd = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case types.STREAM:
|
||||||
|
{
|
||||||
|
if (size(value) > 2) {
|
||||||
|
const { success, msg } = await connectionStore.addStreamValue(
|
||||||
|
server,
|
||||||
|
db,
|
||||||
|
key,
|
||||||
|
value[0],
|
||||||
|
slice(value, 1),
|
||||||
|
)
|
||||||
|
if (success) {
|
||||||
|
if (newForm.reload) {
|
||||||
|
connectionStore.loadKeyValue(server, db, key).then(() => {})
|
||||||
|
}
|
||||||
|
message.success(i18n.t('handle_succ'))
|
||||||
|
} else {
|
||||||
|
message.error(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
dialogStore.closeAddFieldsDialog()
|
dialogStore.closeAddFieldsDialog()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { useI18n } from 'vue-i18n'
|
||||||
import useConnectionStore from '../../stores/connections.js'
|
import useConnectionStore from '../../stores/connections.js'
|
||||||
import { NSpace, useMessage } from 'naive-ui'
|
import { NSpace, useMessage } from 'naive-ui'
|
||||||
import useTabStore from '../../stores/tab.js'
|
import useTabStore from '../../stores/tab.js'
|
||||||
|
import NewStreamValue from '../new_value/NewStreamValue.vue'
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const newForm = reactive({
|
const newForm = reactive({
|
||||||
|
@ -52,6 +53,7 @@ const addValueComponent = {
|
||||||
[types.LIST]: NewListValue,
|
[types.LIST]: NewListValue,
|
||||||
[types.SET]: NewSetValue,
|
[types.SET]: NewSetValue,
|
||||||
[types.ZSET]: NewZSetValue,
|
[types.ZSET]: NewZSetValue,
|
||||||
|
[types.STREAM]: NewStreamValue,
|
||||||
}
|
}
|
||||||
const defaultValue = {
|
const defaultValue = {
|
||||||
[types.STRING]: '',
|
[types.STRING]: '',
|
||||||
|
@ -59,6 +61,7 @@ const defaultValue = {
|
||||||
[types.LIST]: [],
|
[types.LIST]: [],
|
||||||
[types.SET]: [],
|
[types.SET]: [],
|
||||||
[types.ZSET]: [],
|
[types.ZSET]: [],
|
||||||
|
[types.STREAM]: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
const dialogStore = useDialog()
|
const dialogStore = useDialog()
|
||||||
|
@ -165,7 +168,7 @@ const onClose = () => {
|
||||||
<n-input v-model:value="newForm.key" placeholder="" />
|
<n-input v-model:value="newForm.key" placeholder="" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item :label="$t('db_index')" path="db" required>
|
<n-form-item :label="$t('db_index')" path="db" required>
|
||||||
<n-select v-model:value="newForm.db" :options="dbOptions" />
|
<n-select v-model:value="newForm.db" :options="dbOptions" filterable />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item :label="$t('type')" path="type" required>
|
<n-form-item :label="$t('type')" path="type" required>
|
||||||
<n-select v-model:value="newForm.type" :options="options" :render-label="renderTypeLabel" />
|
<n-select v-model:value="newForm.type" :options="options" :render-label="renderTypeLabel" />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { defineOptions, ref } from 'vue'
|
||||||
import { isEmpty, reject } from 'lodash'
|
import { isEmpty, reject } from 'lodash'
|
||||||
import Add from '../icons/Add.vue'
|
import Add from '../icons/Add.vue'
|
||||||
import Delete from '../icons/Delete.vue'
|
import Delete from '../icons/Delete.vue'
|
||||||
|
@ -10,6 +10,9 @@ const props = defineProps({
|
||||||
type: Number,
|
type: Number,
|
||||||
value: Object,
|
value: Object,
|
||||||
})
|
})
|
||||||
|
defineOptions({
|
||||||
|
inheritAttrs: false,
|
||||||
|
})
|
||||||
const emit = defineEmits(['update:value', 'update:type'])
|
const emit = defineEmits(['update:value', 'update:type'])
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
<script setup>
|
||||||
|
import { defineOptions, ref } from 'vue'
|
||||||
|
import { flatMap, isEmpty, reject } from 'lodash'
|
||||||
|
import Add from '../icons/Add.vue'
|
||||||
|
import Delete from '../icons/Delete.vue'
|
||||||
|
import IconButton from '../common/IconButton.vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
value: Array,
|
||||||
|
})
|
||||||
|
defineOptions({
|
||||||
|
inheritAttrs: false,
|
||||||
|
})
|
||||||
|
const id = ref('*')
|
||||||
|
const emit = defineEmits(['update:value'])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef Hash
|
||||||
|
* @property {string} key
|
||||||
|
* @property {string} [value]
|
||||||
|
*/
|
||||||
|
const kvList = ref([{ key: '', value: '' }])
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Hash[]} val
|
||||||
|
*/
|
||||||
|
const onUpdate = (val) => {
|
||||||
|
val = reject(val, { key: '' })
|
||||||
|
const vals = flatMap(val, (item) => [item.key, item.value])
|
||||||
|
vals.splice(0, 0, id.value || '*')
|
||||||
|
emit('update:value', vals)
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
validate: () => {
|
||||||
|
return !isEmpty(props.value)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-form-item label="ID">
|
||||||
|
<n-input v-model:value="id" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item :label="$t('element')" required>
|
||||||
|
<n-dynamic-input
|
||||||
|
v-model:value="kvList"
|
||||||
|
:key-placeholder="$t('enter_field')"
|
||||||
|
:value-placeholder="$t('enter_value')"
|
||||||
|
preset="pair"
|
||||||
|
@update:value="onUpdate"
|
||||||
|
>
|
||||||
|
<template #action="{ index, create, remove, move }">
|
||||||
|
<icon-button v-if="kvList.length > 1" :icon="Delete" size="18" @click="() => remove(index)" />
|
||||||
|
<icon-button :icon="Add" size="18" @click="() => create(index)" />
|
||||||
|
</template>
|
||||||
|
</n-dynamic-input>
|
||||||
|
</n-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
|
@ -4,14 +4,16 @@ export const types = {
|
||||||
LIST: 'LIST',
|
LIST: 'LIST',
|
||||||
SET: 'SET',
|
SET: 'SET',
|
||||||
ZSET: 'ZSET',
|
ZSET: 'ZSET',
|
||||||
|
STREAM: 'STREAM',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const typesColor = {
|
export const typesColor = {
|
||||||
[types.STRING]: '#8256DC',
|
[types.STRING]: '#8256DC',
|
||||||
[types.HASH]: '#2983ED',
|
[types.HASH]: '#0171F5',
|
||||||
[types.LIST]: '#26A15E',
|
[types.LIST]: '#23C338',
|
||||||
[types.SET]: '#EE9F33',
|
[types.SET]: '#F29E33',
|
||||||
[types.ZSET]: '#CE3352',
|
[types.ZSET]: '#F53227',
|
||||||
|
[types.STREAM]: '#F5C201',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const typesBgColor = {
|
export const typesBgColor = {
|
||||||
|
@ -20,6 +22,7 @@ export const typesBgColor = {
|
||||||
[types.LIST]: '#E3F3EB',
|
[types.LIST]: '#E3F3EB',
|
||||||
[types.SET]: '#FDF1DF',
|
[types.SET]: '#FDF1DF',
|
||||||
[types.ZSET]: '#FAEAED',
|
[types.ZSET]: '#FAEAED',
|
||||||
|
[types.STREAM]: '#FFF8DF',
|
||||||
}
|
}
|
||||||
|
|
||||||
// export const typesName = Object.fromEntries(Object.entries(types).map(([key, value]) => [key, value.name]))
|
// export const typesName = Object.fromEntries(Object.entries(types).map(([key, value]) => [key, value.name]))
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { endsWith, get, isEmpty, join, remove, size, slice, sortedIndexBy, split
|
||||||
import {
|
import {
|
||||||
AddHashField,
|
AddHashField,
|
||||||
AddListItem,
|
AddListItem,
|
||||||
|
AddStreamValue,
|
||||||
AddZSetValue,
|
AddZSetValue,
|
||||||
CloseConnection,
|
CloseConnection,
|
||||||
CreateGroup,
|
CreateGroup,
|
||||||
|
@ -15,6 +16,7 @@ import {
|
||||||
ListConnection,
|
ListConnection,
|
||||||
OpenConnection,
|
OpenConnection,
|
||||||
OpenDatabase,
|
OpenDatabase,
|
||||||
|
RemoveStreamValues,
|
||||||
RenameGroup,
|
RenameGroup,
|
||||||
RenameKey,
|
RenameKey,
|
||||||
SaveConnection,
|
SaveConnection,
|
||||||
|
@ -1134,6 +1136,54 @@ const useConnectionStore = defineStore('connections', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* insert new stream field item
|
||||||
|
* @param {string} connName
|
||||||
|
* @param {number} db
|
||||||
|
* @param {string} key
|
||||||
|
* @param {string} id
|
||||||
|
* @param {string[]} values field1, value1, filed2, value2...
|
||||||
|
* @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>}
|
||||||
|
*/
|
||||||
|
async addStreamValue(connName, db, key, id, values) {
|
||||||
|
try {
|
||||||
|
const { data = {}, success, msg } = await AddStreamValue(connName, db, key, id, values)
|
||||||
|
if (success) {
|
||||||
|
const { updated = {} } = data
|
||||||
|
return { success, updated }
|
||||||
|
} else {
|
||||||
|
return { success: false, msg }
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, msg: e.message }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remove stream field
|
||||||
|
* @param {string} connName
|
||||||
|
* @param {number} db
|
||||||
|
* @param {string} key
|
||||||
|
* @param {string[]|string} ids
|
||||||
|
* @returns {Promise<{[msg]: {}, success: boolean, [removed]: string[]}>}
|
||||||
|
*/
|
||||||
|
async removeStreamValues(connName, db, key, ids) {
|
||||||
|
if (typeof ids === 'string') {
|
||||||
|
ids = [ids]
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { data = {}, success, msg } = await RemoveStreamValues(connName, db, key, ids)
|
||||||
|
if (success) {
|
||||||
|
const { removed = [] } = data
|
||||||
|
return { success, removed }
|
||||||
|
} else {
|
||||||
|
return { success, msg }
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, msg: e.message }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* reset key's ttl
|
* reset key's ttl
|
||||||
* @param {string} connName
|
* @param {string} connName
|
||||||
|
|
Loading…
Reference in New Issue