mirror of
				https://github.com/tiny-craft/tiny-rdm.git
				synced 2025-10-31 18:42:33 +08:00 
			
		
		
		
	feat: support import keys from csv file
This commit is contained in:
		| @@ -19,6 +19,7 @@ import { darkThemeOverrides, themeOverrides } from '@/utils/theme.js' | ||||
| import AboutDialog from '@/components/dialogs/AboutDialog.vue' | ||||
| import FlushDbDialog from '@/components/dialogs/FlushDbDialog.vue' | ||||
| import ExportKeyDialog from '@/components/dialogs/ExportKeyDialog.vue' | ||||
| import ImportKeyDialog from '@/components/dialogs/ImportKeyDialog.vue' | ||||
|  | ||||
| const prefStore = usePreferencesStore() | ||||
| const connectionStore = useConnectionStore() | ||||
| @@ -69,6 +70,7 @@ watch( | ||||
|             <rename-key-dialog /> | ||||
|             <delete-key-dialog /> | ||||
|             <export-key-dialog /> | ||||
|             <import-key-dialog /> | ||||
|             <flush-db-dialog /> | ||||
|             <set-ttl-dialog /> | ||||
|             <preferences-dialog /> | ||||
|   | ||||
| @@ -1,17 +1,18 @@ | ||||
| <script setup> | ||||
| import { SelectFile } from 'wailsjs/go/services/systemService.js' | ||||
| import { get } from 'lodash' | ||||
| import { get, isEmpty } from 'lodash' | ||||
|  | ||||
| const props = defineProps({ | ||||
|     value: String, | ||||
|     placeholder: String, | ||||
|     disabled: Boolean, | ||||
|     ext: String, | ||||
| }) | ||||
|  | ||||
| const emit = defineEmits(['update:value']) | ||||
|  | ||||
| const handleSelectFile = async () => { | ||||
|     const { success, data } = await SelectFile() | ||||
|     const { success, data } = await SelectFile('', isEmpty(props.ext) ? null : [props.ext]) | ||||
|     if (success) { | ||||
|         const path = get(data, 'path', '') | ||||
|         emit('update:value', path) | ||||
|   | ||||
| @@ -18,15 +18,14 @@ const exportKeyForm = reactive({ | ||||
| const dialogStore = useDialog() | ||||
| const browserStore = useBrowserStore() | ||||
| const loading = ref(false) | ||||
| const deleting = ref(false) | ||||
| const exporting = ref(false) | ||||
| watchEffect(() => { | ||||
|     if (dialogStore.exportKeyDialogVisible) { | ||||
|         const { server, db, keys } = dialogStore.exportKeyParam | ||||
|         exportKeyForm.server = server | ||||
|         exportKeyForm.db = db | ||||
|         exportKeyForm.keys = keys | ||||
|         // exportKeyForm.async = true | ||||
|         deleting.value = false | ||||
|         exporting.value = false | ||||
|     } | ||||
| }) | ||||
|  | ||||
| @@ -39,16 +38,16 @@ const exportEnable = computed(() => { | ||||
| }) | ||||
|  | ||||
| const i18n = useI18n() | ||||
| const onConfirmDelete = async () => { | ||||
| const onConfirmExport = async () => { | ||||
|     try { | ||||
|         deleting.value = true | ||||
|         exporting.value = true | ||||
|         const { server, db, keys, file } = exportKeyForm | ||||
|         browserStore.exportKeys(server, db, keys, file).catch((e) => {}) | ||||
|     } catch (e) { | ||||
|         $message.error(e.message) | ||||
|         return | ||||
|     } finally { | ||||
|         deleting.value = false | ||||
|         exporting.value = false | ||||
|     } | ||||
|     dialogStore.closeExportKeyDialog() | ||||
| } | ||||
| @@ -101,7 +100,7 @@ const onClose = () => { | ||||
|                     :focusable="false" | ||||
|                     :loading="loading" | ||||
|                     type="error" | ||||
|                     @click="onConfirmDelete"> | ||||
|                     @click="onConfirmExport"> | ||||
|                     {{ $t('dialogue.export.export') }} | ||||
|                 </n-button> | ||||
|             </div> | ||||
|   | ||||
							
								
								
									
										122
									
								
								frontend/src/components/dialogs/ImportKeyDialog.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								frontend/src/components/dialogs/ImportKeyDialog.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | ||||
| <script setup> | ||||
| import { computed, reactive, ref, watchEffect } from 'vue' | ||||
| import useDialog from 'stores/dialog' | ||||
| import { useI18n } from 'vue-i18n' | ||||
| import useBrowserStore from 'stores/browser.js' | ||||
| import { isEmpty } from 'lodash' | ||||
| import FileOpenInput from '@/components/common/FileOpenInput.vue' | ||||
|  | ||||
| const importKeyForm = reactive({ | ||||
|     server: '', | ||||
|     db: 0, | ||||
|     file: '', | ||||
|     type: 0, | ||||
|     conflict: 0, | ||||
| }) | ||||
|  | ||||
| const dialogStore = useDialog() | ||||
| const browserStore = useBrowserStore() | ||||
| const loading = ref(false) | ||||
| const importing = ref(false) | ||||
| watchEffect(() => { | ||||
|     if (dialogStore.importKeyDialogVisible) { | ||||
|         const { server, db } = dialogStore.importKeyParam | ||||
|         importKeyForm.server = server | ||||
|         importKeyForm.db = db | ||||
|         importKeyForm.file = '' | ||||
|         importKeyForm.type = 0 | ||||
|         importKeyForm.conflict = 0 | ||||
|         importing.value = false | ||||
|     } | ||||
| }) | ||||
|  | ||||
| const i18n = useI18n() | ||||
| const conflictOption = [ | ||||
|     { | ||||
|         value: 0, | ||||
|         label: i18n.t('dialogue.import.conflict_overwrite'), | ||||
|     }, | ||||
|     { | ||||
|         value: 1, | ||||
|         label: i18n.t('dialogue.import.conflict_ignore'), | ||||
|     }, | ||||
| ] | ||||
|  | ||||
| const importEnable = computed(() => { | ||||
|     return !isEmpty(importKeyForm.file) | ||||
| }) | ||||
|  | ||||
| const onConfirmImport = async () => { | ||||
|     try { | ||||
|         importing.value = true | ||||
|         const { server, db, file, conflict } = importKeyForm | ||||
|         browserStore.importKeysFromCSVFile(server, db, file, conflict).catch((e) => {}) | ||||
|     } catch (e) { | ||||
|         $message.error(e.message) | ||||
|         return | ||||
|     } finally { | ||||
|         importing.value = false | ||||
|     } | ||||
|     dialogStore.closeImportKeyDialog() | ||||
| } | ||||
|  | ||||
| const onClose = () => { | ||||
|     dialogStore.closeImportKeyDialog() | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <template> | ||||
|     <n-modal | ||||
|         v-model:show="dialogStore.importKeyDialogVisible" | ||||
|         :closable="false" | ||||
|         :close-on-esc="false" | ||||
|         :mask-closable="false" | ||||
|         :show-icon="false" | ||||
|         :title="$t('dialogue.import.name')" | ||||
|         preset="dialog" | ||||
|         transform-origin="center"> | ||||
|         <n-spin :show="loading"> | ||||
|             <n-form :model="importKeyForm" :show-require-mark="false" label-placement="top"> | ||||
|                 <n-grid :x-gap="10"> | ||||
|                     <n-form-item-gi :label="$t('dialogue.key.server')" :span="12"> | ||||
|                         <n-input :autofocus="false" :value="importKeyForm.server" readonly /> | ||||
|                     </n-form-item-gi> | ||||
|                     <n-form-item-gi :label="$t('dialogue.key.db_index')" :span="12"> | ||||
|                         <n-input :autofocus="false" :value="importKeyForm.db.toString()" readonly /> | ||||
|                     </n-form-item-gi> | ||||
|                 </n-grid> | ||||
|                 <n-form-item :label="$t('dialogue.import.open_csv_file')" required> | ||||
|                     <file-open-input | ||||
|                         v-model:value="importKeyForm.file" | ||||
|                         :placeholder="$t('dialogue.import.open_csv_file_tip')" | ||||
|                         ext="csv" /> | ||||
|                 </n-form-item> | ||||
|                 <n-form-item :label="$t('dialogue.import.conflict_handle')"> | ||||
|                     <n-radio-group v-model:value="importKeyForm.conflict"> | ||||
|                         <n-radio-button | ||||
|                             v-for="(op, i) in conflictOption" | ||||
|                             :key="i" | ||||
|                             :label="op.label" | ||||
|                             :value="op.value" /> | ||||
|                     </n-radio-group> | ||||
|                 </n-form-item> | ||||
|             </n-form> | ||||
|         </n-spin> | ||||
|  | ||||
|         <template #action> | ||||
|             <div class="flex-item n-dialog__action"> | ||||
|                 <n-button :disabled="loading" :focusable="false" @click="onClose">{{ $t('common.cancel') }}</n-button> | ||||
|                 <n-button | ||||
|                     :disabled="!importEnable" | ||||
|                     :focusable="false" | ||||
|                     :loading="loading" | ||||
|                     type="error" | ||||
|                     @click="onConfirmImport"> | ||||
|                     {{ $t('dialogue.export.export') }} | ||||
|                 </n-button> | ||||
|             </div> | ||||
|         </template> | ||||
|     </n-modal> | ||||
| </template> | ||||
|  | ||||
| <style lang="scss" scoped></style> | ||||
| @@ -9,36 +9,24 @@ const props = defineProps({ | ||||
|  | ||||
| <template> | ||||
|     <svg fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"> | ||||
|         <mask | ||||
|             id="icon-122098f7f10b972" | ||||
|             height="48" | ||||
|             maskUnits="userSpaceOnUse" | ||||
|             style="mask-type: alpha" | ||||
|             width="48" | ||||
|             x="0" | ||||
|             y="0"> | ||||
|             <path d="M48 0H0V48H48V0Z" fill="currentColor" /> | ||||
|         </mask> | ||||
|         <g mask="url(#icon-122098f7f10b972)"> | ||||
|             <path | ||||
|                 :stroke-width="props.strokeWidth" | ||||
|                 d="M6 24.0083V42H42V24" | ||||
|                 stroke="currentColor" | ||||
|                 stroke-linecap="round" | ||||
|                 stroke-linejoin="round" /> | ||||
|             <path | ||||
|                 :stroke-width="props.strokeWidth" | ||||
|                 d="M33 15L24 6L15 15" | ||||
|                 stroke="currentColor" | ||||
|                 stroke-linecap="round" | ||||
|                 stroke-linejoin="round" /> | ||||
|             <path | ||||
|                 :stroke-width="props.strokeWidth" | ||||
|                 d="M23.9917 32V6" | ||||
|                 stroke="currentColor" | ||||
|                 stroke-linecap="round" | ||||
|                 stroke-linejoin="round" /> | ||||
|         </g> | ||||
|         <path | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             d="M6 24.0083V42H42V24" | ||||
|             stroke="currentColor" | ||||
|             stroke-linecap="round" | ||||
|             stroke-linejoin="round" /> | ||||
|         <path | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             d="M33 23L24 32L15 23" | ||||
|             stroke="currentColor" | ||||
|             stroke-linecap="round" | ||||
|             stroke-linejoin="round" /> | ||||
|         <path | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             d="M23.9917 6V32" | ||||
|             stroke="currentColor" | ||||
|             stroke-linecap="round" | ||||
|             stroke-linejoin="round" /> | ||||
|     </svg> | ||||
| </template> | ||||
|  | ||||
|   | ||||
| @@ -11,19 +11,19 @@ const props = defineProps({ | ||||
|     <svg fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"> | ||||
|         <path | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             d="M6 24.0083V42H42V24" | ||||
|             d="M6 24V42H42V24" | ||||
|             stroke="currentColor" | ||||
|             stroke-linecap="round" | ||||
|             stroke-linejoin="round" /> | ||||
|         <path | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             d="M33 23L24 32L15 23" | ||||
|             d="M33 15L24 6L15 15" | ||||
|             stroke="currentColor" | ||||
|             stroke-linecap="round" | ||||
|             stroke-linejoin="round" /> | ||||
|         <path | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             d="M23.9917 6V32" | ||||
|             d="M24 6V32" | ||||
|             stroke="currentColor" | ||||
|             stroke-linecap="round" | ||||
|             stroke-linejoin="round" /> | ||||
|   | ||||
| @@ -45,7 +45,7 @@ const onUpdate = (val) => { | ||||
| </script> | ||||
|  | ||||
| <template> | ||||
|     <n-form-item :label="$t('interface.type')"> | ||||
|     <n-form-item :label="$t('dialogue.field.conflict_handle')"> | ||||
|         <n-radio-group :value="props.type" @update:value="(val) => emit('update:type', val)"> | ||||
|             <n-radio-button v-for="(op, i) in updateOption" :key="i" :label="op.label" :value="op.value" /> | ||||
|         </n-radio-group> | ||||
|   | ||||
| @@ -41,7 +41,7 @@ defineExpose({ | ||||
| </script> | ||||
|  | ||||
| <template> | ||||
|     <n-form-item :label="$t('dialogue.field.element')" required> | ||||
|     <n-form-item :label="$t('dialogue.field.conflict_handle')" required> | ||||
|         <n-dynamic-input v-model:value="zset" @create="onCreate" @update:value="onUpdate"> | ||||
|             <template #default="{ value }"> | ||||
|                 <n-input | ||||
|   | ||||
| @@ -25,6 +25,7 @@ import Close from '@/components/icons/Close.vue' | ||||
| import More from '@/components/icons/More.vue' | ||||
| import Export from '@/components/icons/Export.vue' | ||||
| import { ConnectionType } from '@/consts/connection_type.js' | ||||
| import Import from '@/components/icons/Import.vue' | ||||
|  | ||||
| const props = defineProps({ | ||||
|     server: String, | ||||
| @@ -64,6 +65,7 @@ const dbSelectOptions = computed(() => { | ||||
|  | ||||
| const moreOptions = computed(() => { | ||||
|     return [ | ||||
|         { key: 'import', label: i18n.t('interface.import_key'), icon: render.renderIcon(Import, { strokeWidth: 3.5 }) }, | ||||
|         { key: 'flush', label: i18n.t('interface.flush_db'), icon: render.renderIcon(Delete, { strokeWidth: 3.5 }) }, | ||||
|         { | ||||
|             key: 'disconnect', | ||||
| @@ -162,6 +164,10 @@ const onExportChecked = () => { | ||||
|     browserTreeRef.value?.exportCheckedItems() | ||||
| } | ||||
|  | ||||
| const onImportData = () => { | ||||
|     dialogStore.openImportKeyDialog(props.server, props.db) | ||||
| } | ||||
|  | ||||
| const onFlush = () => { | ||||
|     dialogStore.openFlushDBDialog(props.server, props.db) | ||||
| } | ||||
| @@ -215,6 +221,9 @@ const onMatchInput = (matchVal, filterVal) => { | ||||
|  | ||||
| const onSelectOptions = (select) => { | ||||
|     switch (select) { | ||||
|         case 'import': | ||||
|             onImportData() | ||||
|             break | ||||
|         case 'flush': | ||||
|             onFlush() | ||||
|             break | ||||
|   | ||||
| @@ -75,6 +75,7 @@ | ||||
|     "rename_key": "Rename Key", | ||||
|     "delete_key": "Delete Key", | ||||
|     "batch_delete_key": "Batch Delete Keys", | ||||
|     "import_key": "Import Key", | ||||
|     "flush_db": "Flush Database", | ||||
|     "check_mode": "Check Mode", | ||||
|     "quit_check_mode": "Quit Check Mode", | ||||
| @@ -225,8 +226,7 @@ | ||||
|       }, | ||||
|       "cluster": { | ||||
|         "title": "Cluster", | ||||
|         "enable": "Serve as Cluster Node", | ||||
|         "readonly": "Enables read-only commands on slave nodes" | ||||
|         "enable": "Serve as Cluster Node" | ||||
|       } | ||||
|     }, | ||||
|     "group": { | ||||
| @@ -252,6 +252,7 @@ | ||||
|     "field": { | ||||
|       "new": "Add New Field", | ||||
|       "new_item": "Add New Item", | ||||
|       "conflict_handle": "When Field Conflict", | ||||
|       "overwrite_field": "Overwrite Existing Field", | ||||
|       "ignore_field": "Ignore Existing Field", | ||||
|       "insert_type": "Insert", | ||||
| @@ -272,12 +273,23 @@ | ||||
|       "filter_pattern_tip": "* : Matches zero or more characters. For example, 'key*' matches all keys starting with 'key'.\n? : Matches a single character. For example, 'key?' matches 'key1', 'key2'.\n[] : Matches a single character within the specified range. For example, 'key[1-3]' matches keys like 'key1', 'key2', 'key3'.\n\\ : Escape character. To match *, ?, [, or ], use the backslash '\\' for escaping." | ||||
|     }, | ||||
|     "export": { | ||||
|       "name": "Export Keys", | ||||
|       "name": "Export Data", | ||||
|       "export": "Export", | ||||
|       "save_file": "Export Path", | ||||
|       "save_file_tip": "Select the export file save path", | ||||
|       "exporting": "Exporting key({index}/{count}): {key}", | ||||
|       "export_completed": "Export process has been completed, {success} successed, {fail} failed" | ||||
|       "save_file_tip": "Select the path to save exported file", | ||||
|       "exporting": "Exporting keys({index}/{count})", | ||||
|       "export_completed": "Export completed, {success} successes, {fail} failed" | ||||
|     }, | ||||
|     "import": { | ||||
|       "name": "Import Data", | ||||
|       "import": "Import", | ||||
|       "open_csv_file": "Import File", | ||||
|       "open_csv_file_tip": "Select the file for import", | ||||
|       "conflict_handle": "Handle Key Conflict", | ||||
|       "conflict_overwrite": "Overwrite", | ||||
|       "conflict_ignore": "Ignore", | ||||
|       "importing": "Importing Keys imported/overwrite:{imported} conflict/fail:{conflict}", | ||||
|       "import_completed": "Import completed, {success} successes, {ignored} failed" | ||||
|     }, | ||||
|     "ttl": { | ||||
|       "title": "Set Key TTL" | ||||
|   | ||||
| @@ -75,6 +75,7 @@ | ||||
|     "rename_key": "重命名键", | ||||
|     "delete_key": "删除键", | ||||
|     "batch_delete_key": "批量删除键", | ||||
|     "import_key": "导入数据", | ||||
|     "flush_db": "清空数据库", | ||||
|     "check_mode": "勾选模式", | ||||
|     "quit_check_mode": "退出勾选模式", | ||||
| @@ -251,8 +252,9 @@ | ||||
|     "field": { | ||||
|       "new": "添加新字段", | ||||
|       "new_item": "添加新元素", | ||||
|       "overwrite_field": "覆盖同名字段", | ||||
|       "ignore_field": "忽略同名字段", | ||||
|       "conflict_handle": "字段冲突处理", | ||||
|       "overwrite_field": "覆盖", | ||||
|       "ignore_field": "忽略", | ||||
|       "insert_type": "插入类型", | ||||
|       "append_item": "尾部追加", | ||||
|       "prepend_item": "插入头部", | ||||
| @@ -271,13 +273,24 @@ | ||||
|       "filter_pattern_tip": "*:匹配零个或多个字符。例如:\"key*\"匹配到以\"key\"开头的所有键\n?:匹配单个字符。例如:\"key?\"匹配\"key1\"、\"key2\"\n[ ]:匹配指定范围内的单个字符。例如:\"key[1-3]\"可以匹配类似于 \"key1\"、\"key2\"、\"key3\" 的键\n\\:转义字符。如果想要匹配 *、?、[、或],需要使用反斜杠\"\\\"进行转义" | ||||
|     }, | ||||
|     "export": { | ||||
|       "name": "导出键", | ||||
|       "name": "导出数据", | ||||
|       "export": "确认导出", | ||||
|       "save_file": "导出路径", | ||||
|       "save_file_tip": "选择保存文件路径", | ||||
|       "exporting": "正在导出键({index}/{count}):{key}", | ||||
|       "save_file_tip": "选择导出文件保存路径", | ||||
|       "exporting": "正在导出键({index}/{count})", | ||||
|       "export_completed": "已完成导出操作,成功{success}个,失败{fail}个" | ||||
|     }, | ||||
|     "import": { | ||||
|       "name": "导入数据", | ||||
|       "import": "确认导入", | ||||
|       "open_csv_file": "导入文件路径", | ||||
|       "open_csv_file_tip": "选择需要导入的文件", | ||||
|       "conflict_handle": "键冲突处理", | ||||
|       "conflict_overwrite": "覆盖", | ||||
|       "conflict_ignore": "忽略", | ||||
|       "importing": "正在导入数据 已导入/覆盖:{imported} 冲突/失败:{conflict}", | ||||
|       "import_completed": "已完成导入操作,成功{success}个,忽略{ignored}个" | ||||
|     }, | ||||
|     "ttl": { | ||||
|       "title": "设置键存活时间" | ||||
|     }, | ||||
|   | ||||
| @@ -17,6 +17,7 @@ import { | ||||
|     GetKeySummary, | ||||
|     GetKeyType, | ||||
|     GetSlowLogs, | ||||
|     ImportCSV, | ||||
|     LoadAllKeys, | ||||
|     LoadNextAllKeys, | ||||
|     LoadNextKeys, | ||||
| @@ -1527,7 +1528,7 @@ const useBrowserStore = defineStore('browser', { | ||||
|          * @return {Promise<void>} | ||||
|          */ | ||||
|         async deleteKeys(server, db, keys) { | ||||
|             const delMsgRef = $message.loading('', { duration: 0, closable: true }) | ||||
|             const msgRef = $message.loading('', { duration: 0, closable: true }) | ||||
|             let deleted = [] | ||||
|             let failCount = 0 | ||||
|             let canceled = false | ||||
| @@ -1542,13 +1543,13 @@ const useBrowserStore = defineStore('browser', { | ||||
|                         maxProgress = progress | ||||
|                     } | ||||
|                     const k = decodeRedisKey(processing) | ||||
|                     delMsgRef.content = i18nGlobal.t('dialogue.deleting_key', { | ||||
|                     msgRef.content = i18nGlobal.t('dialogue.deleting_key', { | ||||
|                         key: k, | ||||
|                         index: maxProgress, | ||||
|                         count: total, | ||||
|                     }) | ||||
|                 }) | ||||
|                 delMsgRef.onClose = () => { | ||||
|                 msgRef.onClose = () => { | ||||
|                     EventsEmit(cancelEvent) | ||||
|                 } | ||||
|                 const { data, success, msg } = await DeleteKeys(server, db, keys, serialNo) | ||||
| @@ -1560,7 +1561,7 @@ const useBrowserStore = defineStore('browser', { | ||||
|                     $message.error(msg) | ||||
|                 } | ||||
|             } finally { | ||||
|                 delMsgRef.destroy() | ||||
|                 msgRef.destroy() | ||||
|                 EventsOff(eventName) | ||||
|                 // clear checked keys | ||||
|                 const tab = useTabStore() | ||||
| @@ -1608,7 +1609,7 @@ const useBrowserStore = defineStore('browser', { | ||||
|          * @returns {Promise<void>} | ||||
|          */ | ||||
|         async exportKeys(server, db, keys, path) { | ||||
|             const delMsgRef = $message.loading('', { duration: 0, closable: true }) | ||||
|             const msgRef = $message.loading('', { duration: 0, closable: true }) | ||||
|             let exported = 0 | ||||
|             let failCount = 0 | ||||
|             let canceled = false | ||||
| @@ -1616,13 +1617,13 @@ const useBrowserStore = defineStore('browser', { | ||||
|             try { | ||||
|                 EventsOn(eventName, ({ total, progress, processing }) => { | ||||
|                     // update export progress | ||||
|                     delMsgRef.content = i18nGlobal.t('dialogue.export.exporting', { | ||||
|                         key: decodeRedisKey(processing), | ||||
|                     msgRef.content = i18nGlobal.t('dialogue.export.exporting', { | ||||
|                         // key: decodeRedisKey(processing), | ||||
|                         index: progress, | ||||
|                         count: total, | ||||
|                     }) | ||||
|                 }) | ||||
|                 delMsgRef.onClose = () => { | ||||
|                 msgRef.onClose = () => { | ||||
|                     EventsEmit('export:stop:' + path) | ||||
|                 } | ||||
|                 const { data, success, msg } = await ExportKey(server, db, keys, path) | ||||
| @@ -1634,7 +1635,7 @@ const useBrowserStore = defineStore('browser', { | ||||
|                     $message.error(msg) | ||||
|                 } | ||||
|             } finally { | ||||
|                 delMsgRef.destroy() | ||||
|                 msgRef.destroy() | ||||
|                 EventsOff(eventName) | ||||
|             } | ||||
|             if (canceled) { | ||||
| @@ -1653,6 +1654,52 @@ const useBrowserStore = defineStore('browser', { | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * import multiple keys from csv file | ||||
|          * @param {string} server | ||||
|          * @param {number} db | ||||
|          * @param {string} path | ||||
|          * @param {int} conflict | ||||
|          * @return {Promise<void>} | ||||
|          */ | ||||
|         async importKeysFromCSVFile(server, db, path, conflict) { | ||||
|             const msgRef = $message.loading('', { duration: 0, closable: true }) | ||||
|             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, | ||||
|                     }) | ||||
|                 }) | ||||
|                 msgRef.onClose = () => { | ||||
|                     EventsEmit('import:stop:' + path) | ||||
|                 } | ||||
|                 const { data, success, msg } = await ImportCSV(server, db, path, conflict) | ||||
|                 if (success) { | ||||
|                     canceled = get(data, 'canceled', false) | ||||
|                     imported = get(data, 'imported', 0) | ||||
|                     ignored = get(data, 'ignored', 0) | ||||
|                 } else { | ||||
|                     $message.error(msg) | ||||
|                 } | ||||
|             } finally { | ||||
|                 msgRef.destroy() | ||||
|                 EventsOff(eventName) | ||||
|             } | ||||
|             if (canceled) { | ||||
|                 $message.info(i18nGlobal.t('dialogue.handle_cancel')) | ||||
|             } else { | ||||
|                 // no fail | ||||
|                 $message.success(i18nGlobal.t('dialogue.import.import_completed', { success: imported, ignored })) | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * flush database | ||||
|          * @param server | ||||
|   | ||||
| @@ -70,6 +70,12 @@ const useDialogStore = defineStore('dialog', { | ||||
|         }, | ||||
|         exportKeyDialogVisible: false, | ||||
|  | ||||
|         importKeyParam: { | ||||
|             server: '', | ||||
|             db: 0, | ||||
|         }, | ||||
|         importKeyDialogVisible: false, | ||||
|  | ||||
|         flushDBParam: { | ||||
|             server: '', | ||||
|             db: 0, | ||||
| @@ -199,6 +205,20 @@ const useDialogStore = defineStore('dialog', { | ||||
|             this.exportKeyDialogVisible = false | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * | ||||
|          * @param {string} server | ||||
|          * @param {number} db | ||||
|          */ | ||||
|         openImportKeyDialog(server, db) { | ||||
|             this.importKeyParam.server = server | ||||
|             this.importKeyParam.db = db | ||||
|             this.importKeyDialogVisible = true | ||||
|         }, | ||||
|         closeImportKeyDialog() { | ||||
|             this.importKeyDialogVisible = false | ||||
|         }, | ||||
|  | ||||
|         openFlushDBDialog(server, db) { | ||||
|             this.flushDBParam.server = server | ||||
|             this.flushDBParam.db = db | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Lykin
					Lykin