perf: add silent deletion option to batch delete dialog
This commit is contained in:
parent
ff2043c0e2
commit
a679858478
|
@ -2073,7 +2073,7 @@ func (b *browserService) DeleteOneKey(server string, db int, k any) (resp types.
|
|||
}
|
||||
|
||||
// DeleteKeys delete keys sync with notification
|
||||
func (b *browserService) DeleteKeys(server string, db int, ks []any, serialNo string) (resp types.JSResp) {
|
||||
func (b *browserService) DeleteKeys(server string, db int, ks []any, notice bool, serialNo string) (resp types.JSResp) {
|
||||
// connect a new connection to export keys
|
||||
conf := Connection().getConnection(server)
|
||||
if conf == nil {
|
||||
|
@ -2093,7 +2093,7 @@ func (b *browserService) DeleteKeys(server string, db int, ks []any, serialNo st
|
|||
defer cancelFunc()
|
||||
|
||||
cancelEvent := "delete:stop:" + serialNo
|
||||
runtime.EventsOnce(ctx, cancelEvent, func(data ...any) {
|
||||
cancelStopEvent := runtime.EventsOnce(ctx, cancelEvent, func(data ...any) {
|
||||
cancelFunc()
|
||||
})
|
||||
processEvent := "deleting:" + serialNo
|
||||
|
@ -2104,29 +2104,41 @@ func (b *browserService) DeleteKeys(server string, db int, ks []any, serialNo st
|
|||
var mutex sync.Mutex
|
||||
del := func(ctx context.Context, cli redis.UniversalClient) error {
|
||||
startTime := time.Now().Add(-10 * time.Second)
|
||||
supportUnlink := true
|
||||
for i, k := range ks {
|
||||
// emit progress per second
|
||||
if i >= total-1 || time.Now().Sub(startTime).Milliseconds() > 100 {
|
||||
if notice && (i >= total-1 || time.Now().Sub(startTime).Milliseconds() > 100) {
|
||||
startTime = time.Now()
|
||||
param := map[string]any{
|
||||
"total": total,
|
||||
"progress": i + 1,
|
||||
"processing": k,
|
||||
}
|
||||
runtime.EventsEmit(b.ctx, processEvent, param)
|
||||
runtime.EventsEmit(ctx, processEvent, param)
|
||||
// do some sleep to prevent blocking the Redis server
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
key := strutil.DecodeRedisKey(k)
|
||||
delErr := cli.Del(ctx, key).Err()
|
||||
if err != nil {
|
||||
failed.Add(1)
|
||||
} else {
|
||||
// save deleted key
|
||||
mutex.Lock()
|
||||
deletedKeys = append(deletedKeys, k)
|
||||
mutex.Unlock()
|
||||
var delErr error
|
||||
if supportUnlink {
|
||||
if delErr = cli.Unlink(ctx, key).Err(); delErr != nil {
|
||||
supportUnlink = false
|
||||
delErr = nil
|
||||
}
|
||||
}
|
||||
if !supportUnlink {
|
||||
delErr = cli.Del(ctx, key).Err()
|
||||
}
|
||||
if notice {
|
||||
if delErr != nil {
|
||||
failed.Add(1)
|
||||
} else {
|
||||
// save deleted key
|
||||
mutex.Lock()
|
||||
deletedKeys = append(deletedKeys, k)
|
||||
mutex.Unlock()
|
||||
}
|
||||
}
|
||||
if errors.Is(delErr, context.Canceled) || canceled {
|
||||
canceled = true
|
||||
|
@ -2145,7 +2157,7 @@ func (b *browserService) DeleteKeys(server string, db int, ks []any, serialNo st
|
|||
err = del(ctx, client)
|
||||
}
|
||||
|
||||
runtime.EventsOff(ctx, cancelEvent)
|
||||
cancelStopEvent()
|
||||
resp.Success = true
|
||||
resp.Data = struct {
|
||||
Canceled bool `json:"canceled"`
|
||||
|
@ -2189,8 +2201,7 @@ func (b *browserService) ExportKey(server string, db int, ks []any, path string,
|
|||
writer := csv.NewWriter(file)
|
||||
defer writer.Flush()
|
||||
|
||||
cancelEvent := "export:stop:" + path
|
||||
runtime.EventsOnce(ctx, cancelEvent, func(data ...any) {
|
||||
cancelStopEvent := runtime.EventsOnce(ctx, "export:stop:"+path, func(data ...any) {
|
||||
cancelFunc()
|
||||
})
|
||||
processEvent := "exporting:" + path
|
||||
|
@ -2206,7 +2217,7 @@ func (b *browserService) ExportKey(server string, db int, ks []any, path string,
|
|||
"progress": i + 1,
|
||||
"processing": k,
|
||||
}
|
||||
runtime.EventsEmit(b.ctx, processEvent, param)
|
||||
runtime.EventsEmit(ctx, processEvent, param)
|
||||
}
|
||||
|
||||
key := strutil.DecodeRedisKey(k)
|
||||
|
@ -2230,7 +2241,7 @@ func (b *browserService) ExportKey(server string, db int, ks []any, path string,
|
|||
}
|
||||
}
|
||||
|
||||
runtime.EventsOff(ctx, cancelEvent)
|
||||
cancelStopEvent()
|
||||
resp.Success = true
|
||||
resp.Data = struct {
|
||||
Canceled bool `json:"canceled"`
|
||||
|
@ -2274,7 +2285,7 @@ func (b *browserService) ImportCSV(server string, db int, path string, conflict
|
|||
reader := csv.NewReader(file)
|
||||
|
||||
cancelEvent := "import:stop:" + path
|
||||
runtime.EventsOnce(ctx, cancelEvent, func(data ...any) {
|
||||
cancelStopEvent := runtime.EventsOnce(ctx, cancelEvent, func(data ...any) {
|
||||
cancelFunc()
|
||||
})
|
||||
processEvent := "importing:" + path
|
||||
|
@ -2349,7 +2360,7 @@ func (b *browserService) ImportCSV(server string, db int, path string, conflict
|
|||
}
|
||||
}
|
||||
|
||||
runtime.EventsOff(ctx, cancelEvent)
|
||||
cancelStopEvent()
|
||||
resp.Success = true
|
||||
resp.Data = struct {
|
||||
Canceled bool `json:"canceled"`
|
||||
|
|
|
@ -74,7 +74,7 @@ const onConfirmDelete = async () => {
|
|||
deleting.value = true
|
||||
const { server, db, key, affectedKeys } = deleteForm
|
||||
await nextTick()
|
||||
browserStore.deleteKeys(server, db, affectedKeys).catch((e) => {})
|
||||
browserStore.deleteKeys(server, db, affectedKeys, !deleteForm.async).catch((e) => {})
|
||||
} catch (e) {
|
||||
$message.error(e.message)
|
||||
return
|
||||
|
@ -115,11 +115,9 @@ const onClose = () => {
|
|||
required>
|
||||
<n-input v-model:value="deleteForm.key" placeholder="" @input="resetAffected" />
|
||||
</n-form-item>
|
||||
<!-- <n-form-item :label="$t('dialogue.key.async_delete')" required>-->
|
||||
<!-- <n-checkbox v-model:checked="deleteForm.async">-->
|
||||
<!-- {{ $t('dialogue.key.async_delete_title') }}-->
|
||||
<!-- </n-checkbox>-->
|
||||
<!-- </n-form-item>-->
|
||||
<n-checkbox v-model:checked="deleteForm.async">
|
||||
{{ $t('dialogue.key.silent') }}
|
||||
</n-checkbox>
|
||||
<n-card
|
||||
v-if="deleteForm.showAffected"
|
||||
:title="$t('dialogue.key.affected_key') + `(${size(deleteForm.affectedKeys)})`"
|
||||
|
|
|
@ -263,13 +263,16 @@
|
|||
"confirm_delete_key": "Confirm Delete {num} Key(s)",
|
||||
"async_delete": "Asynchronously Execute",
|
||||
"async_delete_title": "Do not waiting for the operation's result",
|
||||
"silent": "Do not display deletion status in real time",
|
||||
"confirm_flush": "I know what I'm doing!",
|
||||
"confirm_flush_db": "Confirm Flush Database"
|
||||
},
|
||||
"delete": {
|
||||
"success": "\"{key}\" has been deleted",
|
||||
"deleting": "Deleting",
|
||||
"doing": "Deleting key({index}/{count})",
|
||||
"completed": "Deletion process has been completed, {success} successed, {fail} failed"
|
||||
"completed": "Deletion process has been completed",
|
||||
"completed_status": "{success} successed, {fail} failed"
|
||||
},
|
||||
"field": {
|
||||
"new": "Add New Field",
|
||||
|
|
|
@ -263,13 +263,16 @@
|
|||
"confirm_delete_key": "确认删除{num}个键",
|
||||
"async_delete": "异步执行",
|
||||
"async_delete_title": "不等待操作结果",
|
||||
"silent": "不实时显示删除状态",
|
||||
"confirm_flush": "我知道我正在执行的操作!",
|
||||
"confirm_flush_db": "确认清空数据库"
|
||||
},
|
||||
"delete": {
|
||||
"success": "{key} 已被删除",
|
||||
"deleting": "正在删除",
|
||||
"doing": "正在删除键({index}/{count})",
|
||||
"completed": "已完成删除操作,成功{success}个,失败{fail}个"
|
||||
"completed": "已完成删除操作",
|
||||
"completed_status": "成功{success}个,失败{fail}个"
|
||||
},
|
||||
"field": {
|
||||
"new": "添加新字段",
|
||||
|
|
|
@ -44,7 +44,7 @@ import useConnectionStore from 'stores/connections.js'
|
|||
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
||||
import { isRedisGlob } from '@/utils/glob_pattern.js'
|
||||
import { i18nGlobal } from '@/utils/i18n.js'
|
||||
import { EventsEmit, EventsOff, EventsOn } from 'wailsjs/runtime/runtime.js'
|
||||
import { EventsEmit, EventsOn } from 'wailsjs/runtime/runtime.js'
|
||||
import { RedisNodeItem } from '@/objects/redisNodeItem.js'
|
||||
import { RedisServerState } from '@/objects/redisServerState.js'
|
||||
import { RedisDatabaseItem } from '@/objects/redisDatabaseItem.js'
|
||||
|
@ -1614,34 +1614,33 @@ const useBrowserStore = defineStore('browser', {
|
|||
* @param {string} server
|
||||
* @param {number} db
|
||||
* @param {string[]|number[][]} keys
|
||||
* @param {boolean} notice
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async deleteKeys(server, db, keys) {
|
||||
const msgRef = $message.loading('', { duration: 0, closable: true })
|
||||
async deleteKeys(server, db, keys, notice) {
|
||||
const msgRef = $message.loading(i18nGlobal.t('dialogue.delete.deleting'), { duration: 0, closable: true })
|
||||
let deleted = []
|
||||
let failCount = 0
|
||||
let canceled = false
|
||||
const serialNo = Date.now().valueOf().toString()
|
||||
const eventName = 'deleting:' + serialNo
|
||||
const cancelEvent = 'delete:stop:' + serialNo
|
||||
try {
|
||||
let maxProgress = 0
|
||||
EventsOn(eventName, ({ total, progress, processing }) => {
|
||||
// update delete progress
|
||||
if (progress > maxProgress) {
|
||||
maxProgress = progress
|
||||
}
|
||||
const k = decodeRedisKey(processing)
|
||||
msgRef.content = i18nGlobal.t('dialogue.delete.doing', {
|
||||
key: k,
|
||||
index: maxProgress,
|
||||
count: total,
|
||||
})
|
||||
})
|
||||
msgRef.onClose = () => {
|
||||
EventsEmit(cancelEvent)
|
||||
let maxProgress = 0
|
||||
const cancelEventFn = EventsOn('deleting:' + serialNo, ({ total, progress, processing }) => {
|
||||
// update delete progress
|
||||
if (progress > maxProgress) {
|
||||
maxProgress = progress
|
||||
}
|
||||
const { data, success, msg } = await DeleteKeys(server, db, keys, serialNo)
|
||||
const k = decodeRedisKey(processing)
|
||||
msgRef.content = i18nGlobal.t('dialogue.delete.doing', {
|
||||
key: k,
|
||||
index: maxProgress,
|
||||
count: total,
|
||||
})
|
||||
})
|
||||
msgRef.onClose = () => {
|
||||
EventsEmit('delete:stop:' + serialNo)
|
||||
}
|
||||
try {
|
||||
const { data, success, msg } = await DeleteKeys(server, db, keys, notice, serialNo)
|
||||
if (success) {
|
||||
canceled = get(data, 'canceled', false)
|
||||
deleted = get(data, 'deleted', [])
|
||||
|
@ -1650,8 +1649,8 @@ const useBrowserStore = defineStore('browser', {
|
|||
$message.error(msg)
|
||||
}
|
||||
} finally {
|
||||
cancelEventFn()
|
||||
msgRef.destroy()
|
||||
EventsOff(eventName)
|
||||
// clear checked keys
|
||||
const tab = useTabStore()
|
||||
tab.setCheckedKeys(server)
|
||||
|
@ -1662,13 +1661,40 @@ const useBrowserStore = defineStore('browser', {
|
|||
$message.info(i18nGlobal.t('dialogue.handle_cancel'))
|
||||
} else if (failCount <= 0) {
|
||||
// no fail
|
||||
$message.success(i18nGlobal.t('dialogue.delete.completed', { success: deletedCount, fail: failCount }))
|
||||
let msg = i18nGlobal.t('dialogue.delete.completed')
|
||||
if (notice) {
|
||||
msg +=
|
||||
'\n' +
|
||||
i18nGlobal.t('dialogue.delete.completed_status', {
|
||||
success: deletedCount,
|
||||
fail: failCount,
|
||||
})
|
||||
}
|
||||
$message.success(msg)
|
||||
} else if (failCount >= deletedCount) {
|
||||
// all fail
|
||||
$message.error(i18nGlobal.t('dialogue.delete.completed', { success: deletedCount, fail: failCount }))
|
||||
let msg = i18nGlobal.t('dialogue.delete.completed')
|
||||
if (notice) {
|
||||
msg +=
|
||||
'\n' +
|
||||
i18nGlobal.t('dialogue.delete.completed_status', {
|
||||
success: deletedCount,
|
||||
fail: failCount,
|
||||
})
|
||||
}
|
||||
$message.error(msg)
|
||||
} else {
|
||||
// some fail
|
||||
$message.warn(i18nGlobal.t('dialogue.delete.completed', { success: deletedCount, fail: failCount }))
|
||||
let msg = i18nGlobal.t('dialogue.delete.completed')
|
||||
if (notice) {
|
||||
msg +=
|
||||
'\n' +
|
||||
i18nGlobal.t('dialogue.delete.completed_status', {
|
||||
success: deletedCount,
|
||||
fail: failCount,
|
||||
})
|
||||
}
|
||||
$message.warn(msg)
|
||||
}
|
||||
// update ui
|
||||
timeout(100).then(async () => {
|
||||
|
@ -1703,19 +1729,18 @@ const useBrowserStore = defineStore('browser', {
|
|||
let exported = 0
|
||||
let failCount = 0
|
||||
let canceled = false
|
||||
const eventName = 'exporting:' + path
|
||||
try {
|
||||
EventsOn(eventName, ({ total, progress, processing }) => {
|
||||
// update export progress
|
||||
msgRef.content = i18nGlobal.t('dialogue.export.exporting', {
|
||||
// key: decodeRedisKey(processing),
|
||||
index: progress,
|
||||
count: total,
|
||||
})
|
||||
const cancelEventFn = EventsOn('exporting:' + path, ({ total, progress, processing }) => {
|
||||
// update export progress
|
||||
msgRef.content = i18nGlobal.t('dialogue.export.exporting', {
|
||||
// key: decodeRedisKey(processing),
|
||||
index: progress,
|
||||
count: total,
|
||||
})
|
||||
msgRef.onClose = () => {
|
||||
EventsEmit('export:stop:' + path)
|
||||
}
|
||||
})
|
||||
msgRef.onClose = () => {
|
||||
EventsEmit('export:stop:' + path)
|
||||
}
|
||||
try {
|
||||
const { data, success, msg } = await ExportKey(server, db, keys, path, expire)
|
||||
if (success) {
|
||||
canceled = get(data, 'canceled', false)
|
||||
|
@ -1726,7 +1751,7 @@ const useBrowserStore = defineStore('browser', {
|
|||
}
|
||||
} finally {
|
||||
msgRef.destroy()
|
||||
EventsOff(eventName)
|
||||
cancelEventFn()
|
||||
}
|
||||
if (canceled) {
|
||||
$message.info(i18nGlobal.t('dialogue.handle_cancel'))
|
||||
|
@ -1759,19 +1784,18 @@ const useBrowserStore = defineStore('browser', {
|
|||
let imported = 0
|
||||
let ignored = 0
|
||||
let canceled = false
|
||||
const eventName = 'importing:' + path
|
||||
try {
|
||||
EventsOn(eventName, ({ imported = 0, ignored = 0 }) => {
|
||||
// update export progress
|
||||
msgRef.content = i18nGlobal.t('dialogue.import.importing', {
|
||||
// key: decodeRedisKey(processing),
|
||||
imported,
|
||||
conflict: ignored,
|
||||
})
|
||||
const cancelEventFn = EventsOn('importing:' + path, ({ imported = 0, ignored = 0 }) => {
|
||||
// update export progress
|
||||
msgRef.content = i18nGlobal.t('dialogue.import.importing', {
|
||||
// key: decodeRedisKey(processing),
|
||||
imported,
|
||||
conflict: ignored,
|
||||
})
|
||||
msgRef.onClose = () => {
|
||||
EventsEmit('import:stop:' + path)
|
||||
}
|
||||
})
|
||||
msgRef.onClose = () => {
|
||||
EventsEmit('import:stop:' + path)
|
||||
}
|
||||
try {
|
||||
const { data, success, msg } = await ImportCSV(server, db, path, conflict, ttl)
|
||||
if (success) {
|
||||
canceled = get(data, 'canceled', false)
|
||||
|
@ -1781,8 +1805,8 @@ const useBrowserStore = defineStore('browser', {
|
|||
$message.error(msg)
|
||||
}
|
||||
} finally {
|
||||
cancelEventFn()
|
||||
msgRef.destroy()
|
||||
EventsOff(eventName)
|
||||
}
|
||||
if (canceled) {
|
||||
$message.info(i18nGlobal.t('dialogue.handle_cancel'))
|
||||
|
|
Loading…
Reference in New Issue