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