diff --git a/backend/services/browser_service.go b/backend/services/browser_service.go
index 35f6fa4..5515bd3 100644
--- a/backend/services/browser_service.go
+++ b/backend/services/browser_service.go
@@ -2234,7 +2234,7 @@ func (b *browserService) ExportKey(server string, db int, ks []any, path string,
}
// ImportCSV import data from csv file
-func (b *browserService) ImportCSV(server string, db int, path string, conflict int, includeExpire bool) (resp types.JSResp) {
+func (b *browserService) ImportCSV(server string, db int, path string, conflict int, ttl int64) (resp types.JSResp) {
// connect a new connection to export keys
conf := Connection().getConnection(server)
if conf == nil {
@@ -2270,14 +2270,14 @@ func (b *browserService) ImportCSV(server string, db int, path string, conflict
var line []string
var readErr error
var key, value []byte
- var ttl time.Duration
+ var ttlValue time.Duration
var imported, ignored int64
var canceled bool
startTime := time.Now().Add(-10 * time.Second)
for {
readErr = nil
- ttl = redis.KeepTTL
+ ttlValue = redis.KeepTTL
line, readErr = reader.Read()
if readErr != nil {
break
@@ -2293,21 +2293,25 @@ func (b *browserService) ImportCSV(server string, db int, path string, conflict
continue
}
// get ttl
- if includeExpire && len(line) > 2 {
+ if ttl < 0 {
+ // use previous
if expire, ttlErr := strconv.ParseInt(line[2], 10, 64); ttlErr == nil && expire > 0 {
- ttl = time.UnixMilli(expire).Sub(time.Now())
+ ttlValue = time.UnixMilli(expire).Sub(time.Now())
}
+ } else if ttl > 0 {
+ // custom ttl
+ ttlValue = time.Duration(ttl) * time.Second
}
if conflict == 0 {
- readErr = client.RestoreReplace(ctx, string(key), ttl, string(value)).Err()
+ readErr = client.RestoreReplace(ctx, string(key), ttlValue, string(value)).Err()
} else {
keyStr := string(key)
// go-redis may crash when batch calling restore
// use "exists" to filter first
if n, _ := client.Exists(ctx, keyStr).Result(); n <= 0 {
- readErr = client.Restore(ctx, keyStr, ttl, string(value)).Err()
+ readErr = client.Restore(ctx, keyStr, ttlValue, string(value)).Err()
} else {
- readErr = errors.New("key existed")
+ readErr = errors.New("key already existed")
}
}
if readErr != nil {
diff --git a/frontend/src/components/common/TtlInput.vue b/frontend/src/components/common/TtlInput.vue
new file mode 100644
index 0000000..283e862
--- /dev/null
+++ b/frontend/src/components/common/TtlInput.vue
@@ -0,0 +1,66 @@
+
+
+
+
+ emit('update:value', val)" />
+ emit('update:unit', val)" />
+
+
+
+
diff --git a/frontend/src/components/dialogs/ImportKeyDialog.vue b/frontend/src/components/dialogs/ImportKeyDialog.vue
index 1387fd9..ab52f72 100644
--- a/frontend/src/components/dialogs/ImportKeyDialog.vue
+++ b/frontend/src/components/dialogs/ImportKeyDialog.vue
@@ -5,15 +5,18 @@ import { useI18n } from 'vue-i18n'
import useBrowserStore from 'stores/browser.js'
import { isEmpty } from 'lodash'
import FileOpenInput from '@/components/common/FileOpenInput.vue'
+import TtlInput from '@/components/common/TtlInput.vue'
const importKeyForm = reactive({
server: '',
db: 0,
- expire: true,
reload: true,
file: '',
type: 0,
conflict: 0,
+ ttlType: 0,
+ ttl: -1,
+ ttlUnit: 1,
})
const dialogStore = useDialog()
@@ -25,11 +28,12 @@ watchEffect(() => {
const { server, db } = dialogStore.importKeyParam
importKeyForm.server = server
importKeyForm.db = db
- importKeyForm.expire = true
importKeyForm.reload = true
importKeyForm.file = ''
importKeyForm.type = 0
importKeyForm.conflict = 0
+ importKeyForm.ttlType = 0
+ importKeyForm.ttl = -1
importing.value = false
}
})
@@ -46,6 +50,21 @@ const conflictOption = computed(() => [
},
])
+const ttlOption = computed(() => [
+ {
+ value: 0,
+ label: i18n.t('dialogue.import.ttl_include'),
+ },
+ {
+ value: 1,
+ label: i18n.t('dialogue.import.ttl_ignore'),
+ },
+ {
+ value: 2,
+ label: i18n.t('dialogue.import.ttl_custom'),
+ },
+])
+
const importEnable = computed(() => {
return !isEmpty(importKeyForm.file)
})
@@ -53,8 +72,19 @@ const importEnable = computed(() => {
const onConfirmImport = async () => {
try {
importing.value = true
- const { server, db, file, conflict, expire, reload } = importKeyForm
- browserStore.importKeysFromCSVFile(server, db, file, conflict, expire, reload).catch((e) => {})
+ const { server, db, file, conflict, ttlType, ttl, ttlUnit, reload } = importKeyForm
+ let ttlVal = 0
+ switch (ttlType) {
+ case 0:
+ ttlVal = -1
+ break
+ case 1:
+ ttlVal = 0
+ break
+ default:
+ ttlVal = ttl * (ttlUnit || 1)
+ }
+ browserStore.importKeysFromCSVFile(server, db, file, conflict, ttlVal, reload).catch((e) => {})
} catch (e) {
$message.error(e.message)
return
@@ -104,16 +134,22 @@ const onClose = () => {
:value="op.value" />
-
+
-
- {{ $t('dialogue.import.import_expire') }}
-
-
- {{ $t('dialogue.import.reload') }}
-
+
+
+
+
+
+
+ {{ $t('dialogue.import.reload') }}
+
+
diff --git a/frontend/src/components/dialogs/SetTtlDialog.vue b/frontend/src/components/dialogs/SetTtlDialog.vue
index af20cf0..b5be274 100644
--- a/frontend/src/components/dialogs/SetTtlDialog.vue
+++ b/frontend/src/components/dialogs/SetTtlDialog.vue
@@ -4,6 +4,7 @@ import useDialog from 'stores/dialog'
import useBrowserStore from 'stores/browser.js'
import { useI18n } from 'vue-i18n'
import { isEmpty, size } from 'lodash'
+import TtlInput from '@/components/common/TtlInput.vue'
const ttlForm = reactive({
server: '',
@@ -42,22 +43,6 @@ const isBatchAction = computed(() => {
})
const i18n = useI18n()
-const unit = computed(() => [
- { value: 1, label: i18n.t('common.second') },
- {
- value: 60,
- label: i18n.t('common.minute'),
- },
- {
- value: 3600,
- label: i18n.t('common.hour'),
- },
- {
- value: 86400,
- label: i18n.t('common.day'),
- },
-])
-
const quickOption = computed(() => [
{ value: -1, unit: 1, label: i18n.t('interface.forever') },
{ value: 10, unit: 1, label: `10 ${i18n.t('common.second')}` },
@@ -119,15 +104,7 @@ const onConfirm = async () => {
-
-
-
-
+
diff --git a/frontend/src/components/sidebar/BrowserTree.vue b/frontend/src/components/sidebar/BrowserTree.vue
index 046b00f..4c8821a 100644
--- a/frontend/src/components/sidebar/BrowserTree.vue
+++ b/frontend/src/components/sidebar/BrowserTree.vue
@@ -565,7 +565,7 @@ watchEffect(
)
// the NTree node may get incorrect height after change data
-// add key property to force refresh the component and then everything back to normal
+// add key property for force refresh the component so that everything back to normal
const treeKey = ref(0)
defineExpose({
handleSelectContextMenu,
diff --git a/frontend/src/langs/en-us.json b/frontend/src/langs/en-us.json
index 5b5ae6a..2f1b3cf 100644
--- a/frontend/src/langs/en-us.json
+++ b/frontend/src/langs/en-us.json
@@ -291,7 +291,6 @@
"import": {
"name": "Import Data",
"import_expire_title": "Expiration",
- "import_expire": "Import Expiration Time",
"import": "Import",
"reload": "Reload Keys After Imported",
"open_csv_file": "Import File",
@@ -299,6 +298,9 @@
"conflict_handle": "Key Conflict Resolution",
"conflict_overwrite": "Overwrite",
"conflict_ignore": "Ignore",
+ "ttl_include": "Import From File",
+ "ttl_ignore": "Do Not Set",
+ "ttl_custom": "Custom",
"importing": "Importing Keys imported/overwrite:{imported} conflict/fail:{conflict}",
"import_completed": "Import completed, {success} successes, {ignored} failed"
},
diff --git a/frontend/src/langs/zh-cn.json b/frontend/src/langs/zh-cn.json
index e040aca..e10b2d3 100644
--- a/frontend/src/langs/zh-cn.json
+++ b/frontend/src/langs/zh-cn.json
@@ -291,7 +291,6 @@
"import": {
"name": "导入数据",
"import_expire_title": "过期时间",
- "import_expire": "包含键过期时间",
"reload": "导入完成后重新载入",
"import": "确认导入",
"open_csv_file": "导入文件路径",
@@ -299,6 +298,9 @@
"conflict_handle": "键冲突处理",
"conflict_overwrite": "覆盖",
"conflict_ignore": "忽略",
+ "ttl_include": "尝试导入",
+ "ttl_ignore": "不设置",
+ "ttl_custom": "自定义",
"importing": "正在导入数据 已导入/覆盖:{imported} 冲突/失败:{conflict}",
"import_completed": "已完成导入操作,成功{success}个,忽略{ignored}个"
},
diff --git a/frontend/src/stores/browser.js b/frontend/src/stores/browser.js
index a7d7031..61da84e 100644
--- a/frontend/src/stores/browser.js
+++ b/frontend/src/stores/browser.js
@@ -1738,11 +1738,11 @@ const useBrowserStore = defineStore('browser', {
* @param {number} db
* @param {string} path
* @param {number} conflict
- * @param {boolean} [expire]
+ * @param {number} [ttl] <0:use previous; ==0: persist; >0: custom ttl
* @param {boolean} [reload]
* @return {Promise}
*/
- async importKeysFromCSVFile(server, db, path, conflict, expire, reload) {
+ async importKeysFromCSVFile(server, db, path, conflict, ttl, reload) {
const msgRef = $message.loading('', { duration: 0, closable: true })
let imported = 0
let ignored = 0
@@ -1760,7 +1760,7 @@ const useBrowserStore = defineStore('browser', {
msgRef.onClose = () => {
EventsEmit('import:stop:' + path)
}
- const { data, success, msg } = await ImportCSV(server, db, path, conflict, expire)
+ const { data, success, msg } = await ImportCSV(server, db, path, conflict, ttl)
if (success) {
canceled = get(data, 'canceled', false)
imported = get(data, 'imported', 0)
diff --git a/frontend/src/stores/tab.js b/frontend/src/stores/tab.js
index 0da647d..19ad166 100644
--- a/frontend/src/stores/tab.js
+++ b/frontend/src/stores/tab.js
@@ -674,7 +674,7 @@ const useTabStore = defineStore('tab', {
* @param {string} server
* @param {CheckedKey[]} [keys]
*/
- setCheckedKeys(server, keys) {
+ setCheckedKeys(server, keys = null) {
let tab = find(this.tabList, { name: server })
if (tab != null) {
if (isEmpty(keys)) {