feat: add flush db operation

This commit is contained in:
tiny-craft 2023-11-02 18:17:54 +08:00
parent 67666f4edf
commit 9b9d0e3c7c
11 changed files with 199 additions and 16 deletions

View File

@ -1521,6 +1521,45 @@ func (c *connectionService) DeleteKey(connName string, db int, k any, async bool
return
}
// FlushDB flush database
func (c *connectionService) FlushDB(connName string, db int, async bool) (resp types.JSResp) {
item, err := c.getRedisClient(connName, db)
if err != nil {
resp.Msg = err.Error()
return
}
flush := func(ctx context.Context, cli redis.UniversalClient) {
cli.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
pipe.Select(ctx, db)
if async {
pipe.FlushDBAsync(ctx)
} else {
pipe.FlushDB(ctx)
}
return nil
})
}
client, ctx := item.client, item.ctx
if cluster, ok := client.(*redis.ClusterClient); ok {
// cluster mode
err = cluster.ForEachMaster(ctx, func(ctx context.Context, cli *redis.Client) error {
flush(ctx, cli)
return nil
})
} else {
flush(ctx, client)
}
if err != nil {
resp.Msg = err.Error()
return
}
resp.Success = true
return
}
// RenameKey rename key
func (c *connectionService) RenameKey(connName string, db int, key, newKey string) (resp types.JSResp) {
item, err := c.getRedisClient(connName, db)

View File

@ -99,7 +99,7 @@ func (l *LogHook) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.Proc
err := next(ctx, cmds)
cost := time.Since(t).Milliseconds()
b := make([]byte, 0, 64)
for _, cmd := range cmds {
for i, cmd := range cmds {
log.Println("pipeline: ", cmd)
if l.cmdExec != nil {
for i, arg := range cmd.Args() {
@ -108,9 +108,11 @@ func (l *LogHook) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.Proc
}
b = appendArg(b, arg)
}
if i != len(cmds) {
b = append(b, '\n')
}
}
}
if l.cmdExec != nil {
l.cmdExec(string(b), cost)
}

View File

@ -20,6 +20,7 @@ import KeyFilterDialog from './components/dialogs/KeyFilterDialog.vue'
import { WindowSetDarkTheme, WindowSetLightTheme } from 'wailsjs/runtime/runtime.js'
import { darkThemeOverrides, themeOverrides } from '@/utils/theme.js'
import AboutDialog from '@/components/dialogs/AboutDialog.vue'
import FlushDbDialog from '@/components/dialogs/FlushDbDialog.vue'
hljs.registerLanguage('json', json)
hljs.registerLanguage('plaintext', plaintext)
@ -73,6 +74,7 @@ watch(
<add-fields-dialog />
<rename-key-dialog />
<delete-key-dialog />
<flush-db-dialog />
<set-ttl-dialog />
<preferences-dialog />
<about-dialog />

View File

@ -1,9 +1,9 @@
<script setup>
import { computed, nextTick, reactive, ref } from 'vue'
import { computed, h, nextTick, reactive, ref } from 'vue'
import IconButton from '@/components/common/IconButton.vue'
import Refresh from '@/components/icons/Refresh.vue'
import useConnectionStore from 'stores/connections.js'
import { map, uniqBy } from 'lodash'
import { map, size, split, uniqBy } from 'lodash'
import { useI18n } from 'vue-i18n'
import Delete from '@/components/icons/Delete.vue'
import dayjs from 'dayjs'
@ -132,6 +132,17 @@ defineExpose({
filter(value, row) {
return value === '' || !!~row.cmd.indexOf(value.toString())
},
render({ cmd }, index) {
const cmdList = split(cmd, '\n')
if (size(cmdList) > 1) {
return h(
'div',
null,
map(cmdList, (c) => h('div', null, c)),
)
}
return cmd
},
},
{
title: $t('log.cost_time'),

View File

@ -0,0 +1,85 @@
<script setup>
import { reactive, watch } from 'vue'
import useDialog from 'stores/dialog'
import { useI18n } from 'vue-i18n'
import useConnectionStore from 'stores/connections.js'
const flushForm = reactive({
server: '',
db: 0,
key: '',
async: false,
confirm: false,
})
const dialogStore = useDialog()
const connectionStore = useConnectionStore()
watch(
() => dialogStore.flushDBDialogVisible,
(visible) => {
if (visible) {
const { server, db } = dialogStore.flushDBParam
flushForm.server = server
flushForm.db = db
flushForm.async = true
flushForm.confirm = false
}
},
)
const i18n = useI18n()
const onConfirmFlush = async () => {
try {
const { server, db, async } = flushForm
const success = await connectionStore.flushDatabase(server, db, async)
if (success) {
$message.success(i18n.t('dialogue.handle_succ'))
}
} catch (e) {
$message.error(e.message)
}
dialogStore.closeFlushDBDialog()
}
const onClose = () => {
dialogStore.closeFlushDBDialog()
}
</script>
<template>
<n-modal
v-model:show="dialogStore.flushDBDialogVisible"
:closable="false"
:close-on-esc="false"
:mask-closable="false"
:show-icon="false"
:title="$t('interface.flush_db')"
preset="dialog"
transform-origin="center">
<n-form :model="flushForm" :show-require-mark="false" label-placement="top">
<n-form-item :label="$t('dialogue.key.server')">
<n-input :value="flushForm.server" readonly />
</n-form-item>
<n-form-item :label="$t('dialogue.key.db_index')">
<n-input :value="flushForm.db.toString()" readonly />
</n-form-item>
<n-form-item :label="$t('dialogue.key.async_delete')" required>
<n-checkbox v-model:checked="flushForm.async">{{ $t('dialogue.key.async_delete_title') }}</n-checkbox>
</n-form-item>
<n-form-item :label="$t('common.warning')" required>
<n-checkbox v-model:checked="flushForm.confirm">
<span style="color: red; font-weight: bold">{{ $t('dialogue.key.confirm_flush') }}</span>
</n-checkbox>
</n-form-item>
</n-form>
<template #action>
<n-button :focusable="false" @click="onClose">{{ $t('common.cancel') }}</n-button>
<n-button :disabled="!!!flushForm.confirm" :focusable="false" type="primary" @click="onConfirmFlush">
{{ $t('dialogue.key.confirm_flush_db') }}
</n-button>
</template>
</n-modal>
</template>
<style lang="scss" scoped></style>

View File

@ -10,7 +10,7 @@ const props = defineProps({
},
strokeColor: {
type: String,
default: '#FFF',
default: 'currentColor',
},
})
</script>

View File

@ -117,8 +117,8 @@ const menuOptions = {
key: 'd1',
},
{
key: 'key_remove',
label: i18n.t('interface.batch_delete'),
key: 'db_flush',
label: i18n.t('interface.flush_db'),
icon: renderIcon(Delete),
},
{
@ -163,7 +163,7 @@ const menuOptions = {
},
{
key: 'key_remove',
label: i18n.t('interface.batch_delete'),
label: i18n.t('interface.batch_delete_key'),
icon: renderIcon(Delete),
},
],
@ -260,7 +260,7 @@ const handleSelectContextMenu = (key) => {
connectionStore.closeDatabase(props.server, db)
break
case 'db_flush':
dialogStore.openDeleteKeyDialog(props.server, db, '*')
dialogStore.openFlushDBDialog(props.server, db)
break
case 'db_newkey':
case 'key_newkey':
@ -548,7 +548,7 @@ const getDatabaseMenu = (opened, loading, end) => {
onClick: () => handleSelectContextMenu('db_loadall'),
}),
h(IconButton, {
tTooltip: 'interface.batch_delete',
tTooltip: 'interface.flush_db',
icon: Delete,
disabled: loading === true,
onClick: () => handleSelectContextMenu('db_flush'),
@ -585,7 +585,7 @@ const getLayerMenu = () => {
onClick: () => handleSelectContextMenu('key_newkey'),
}),
h(IconButton, {
tTooltip: 'interface.batch_delete',
tTooltip: 'interface.batch_delete_key',
icon: Delete,
onClick: () => handleSelectContextMenu('key_remove'),
}),

View File

@ -65,6 +65,7 @@
"rename_key": "Rename Key",
"delete_key": "Delete Key",
"batch_delete_key": "Batch Delete Keys",
"flush_db": "Flush Database",
"copy_value": "Copy Value",
"edit_value": "Edit Value",
"save_update": "Save Update",
@ -82,7 +83,6 @@
"decode_with": "Decode / Decompression",
"reload": "Reload",
"open_connection": "Open Connection",
"batch_delete": "Batch Delete",
"copy_path": "Copy Path",
"copy_key": "Copy Key",
"binary_key": "Binary Key Name",
@ -214,14 +214,16 @@
"new": "New Key",
"new_name": "New Key Name",
"persist_key": "Persist Key",
"server": "Belong",
"server": "Connection",
"db_index": "Database Index",
"key_expression": "Key Expression",
"affected_key": "Affected Keys",
"show_affected_key": "Show Affected Keys",
"confirm_delete_key": "Confirm Delete {num} Key(s)",
"async_delete": "Asynchronously Execute",
"async_delete_title": "Do not waiting for the operation's result"
"async_delete_title": "Do not waiting for the operation's result",
"confirm_flush": "I know what I'm doing!",
"confirm_flush_db": "Confirm Flush Database"
},
"field": {
"new": "Add New Field",

View File

@ -65,6 +65,7 @@
"rename_key": "重命名键",
"delete_key": "删除键",
"batch_delete_key": "批量删除键",
"flush_db": "清空数据库",
"copy_value": "复制值",
"edit_value": "修改值",
"save_update": "保存修改",
@ -82,7 +83,6 @@
"decode_with": "解码/解压方式",
"reload": "重新载入",
"open_connection": "打开连接",
"batch_delete": "批量删除键",
"copy_path": "复制路径",
"copy_key": "复制键名",
"binary_key": "二进制键名",
@ -220,7 +220,9 @@
"show_affected_key": "查看受影响的键名",
"confirm_delete_key": "确认删除{num}个键",
"async_delete": "异步执行",
"async_delete_title": "不等待操作结果"
"async_delete_title": "不等待操作结果",
"confirm_flush": "我知道我正在执行的操作!",
"confirm_flush_db": "确认清空数据库"
},
"field": {
"new": "添加新字段",

View File

@ -25,6 +25,7 @@ import {
DeleteConnection,
DeleteGroup,
DeleteKey,
FlushDB,
GetCmdHistory,
GetConnection,
GetKeyValue,
@ -1666,6 +1667,30 @@ const useConnectionStore = defineStore('connections', {
return false
},
/**
* flush database
* @param connName
* @param db
* @param async
* @return {Promise<boolean>}
*/
async flushDatabase(connName, db, async) {
try {
const { success = false } = await FlushDB(connName, db, async)
if (success === true) {
// update tree view data
this._deleteKeyNode(connName, db)
// set tab content empty
const tab = useTabStore()
tab.emptyTab(connName)
return true
}
} finally {
}
return true
},
/**
* rename key
* @param {string} connName

View File

@ -63,6 +63,12 @@ const useDialogStore = defineStore('dialog', {
},
deleteKeyDialogVisible: false,
flushDBParam: {
server: '',
db: 0,
},
flushDBDialogVisible: false,
selectTTL: -1,
ttlDialogVisible: false,
@ -164,6 +170,15 @@ const useDialogStore = defineStore('dialog', {
this.deleteKeyDialogVisible = false
},
openFlushDBDialog(server, db) {
this.flushDBParam.server = server
this.flushDBParam.db = db
this.flushDBDialogVisible = true
},
closeFlushDBDialog() {
this.flushDBDialogVisible = false
},
/**
*
* @param {string} prefix