Compare commits
No commits in common. "ab8077999d08dd6dc4590ab20481e081b80ddab0" and "379bb5e623983695cd52df88b493bff8fb8091de" have entirely different histories.
ab8077999d
...
379bb5e623
|
@ -390,20 +390,7 @@ func (b *browserService) ServerInfo(name string) (resp types.JSResp) {
|
||||||
// @param path contain connection name and db name
|
// @param path contain connection name and db name
|
||||||
func (b *browserService) OpenDatabase(connName string, db int, match string, keyType string) (resp types.JSResp) {
|
func (b *browserService) OpenDatabase(connName string, db int, match string, keyType string) (resp types.JSResp) {
|
||||||
b.setClientCursor(connName, db, 0)
|
b.setClientCursor(connName, db, 0)
|
||||||
|
return b.LoadNextKeys(connName, db, match, keyType)
|
||||||
item, err := b.getRedisClient(connName, db)
|
|
||||||
if err != nil {
|
|
||||||
resp.Msg = err.Error()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
client, ctx := item.client, item.ctx
|
|
||||||
maxKeys := b.loadDBSize(ctx, client)
|
|
||||||
|
|
||||||
resp.Success = true
|
|
||||||
resp.Data = map[string]any{
|
|
||||||
"maxKeys": maxKeys,
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// scan keys
|
// scan keys
|
||||||
|
@ -446,7 +433,6 @@ func (b *browserService) scanKeys(ctx context.Context, client redis.UniversalCli
|
||||||
// cluster mode
|
// cluster mode
|
||||||
var mutex sync.Mutex
|
var mutex sync.Mutex
|
||||||
err = cluster.ForEachMaster(ctx, func(ctx context.Context, cli *redis.Client) error {
|
err = cluster.ForEachMaster(ctx, func(ctx context.Context, cli *redis.Client) error {
|
||||||
// FIXME: BUG? can not fully load in cluster mode? maybe remove the shared "cursor"
|
|
||||||
return scan(ctx, cli, func(k []any) {
|
return scan(ctx, cli, func(k []any) {
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
keys = append(keys, k...)
|
keys = append(keys, k...)
|
||||||
|
@ -896,7 +882,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
|
||||||
|
|
||||||
items = make([]types.ZSetEntryItem, 0, len(loadedVal))
|
items = make([]types.ZSetEntryItem, 0, len(loadedVal))
|
||||||
for _, z := range loadedVal {
|
for _, z := range loadedVal {
|
||||||
val := strutil.AnyToString(z.Member, "", 0)
|
val := strutil.AnyToString(z.Score, "", 0)
|
||||||
if doFilter && !strings.Contains(val, param.MatchPattern) {
|
if doFilter && !strings.Contains(val, param.MatchPattern) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,8 +25,6 @@ const props = defineProps({
|
||||||
border: Boolean,
|
border: Boolean,
|
||||||
disabled: Boolean,
|
disabled: Boolean,
|
||||||
buttonStyle: [String, Object],
|
buttonStyle: [String, Object],
|
||||||
buttonClass: [String, Object],
|
|
||||||
small: Boolean,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const hasTooltip = computed(() => {
|
const hasTooltip = computed(() => {
|
||||||
|
@ -38,12 +36,10 @@ const hasTooltip = computed(() => {
|
||||||
<n-tooltip v-if="hasTooltip" :show-arrow="false">
|
<n-tooltip v-if="hasTooltip" :show-arrow="false">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<n-button
|
<n-button
|
||||||
:class="props.buttonClass"
|
|
||||||
:color="props.color"
|
:color="props.color"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:focusable="false"
|
:focusable="false"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:size="small ? 'small' : ''"
|
|
||||||
:style="props.buttonStyle"
|
:style="props.buttonStyle"
|
||||||
:text="!border"
|
:text="!border"
|
||||||
:type="type"
|
:type="type"
|
||||||
|
@ -61,13 +57,10 @@ const hasTooltip = computed(() => {
|
||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
<n-button
|
<n-button
|
||||||
v-else
|
v-else
|
||||||
:class="props.buttonClass"
|
|
||||||
:color="props.color"
|
:color="props.color"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:focusable="false"
|
:focusable="false"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:size="small ? 'small' : ''"
|
|
||||||
:style="props.buttonStyle"
|
|
||||||
:text="!border"
|
:text="!border"
|
||||||
:type="type"
|
:type="type"
|
||||||
@click.prevent="emit('click')">
|
@click.prevent="emit('click')">
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
<script setup>
|
|
||||||
import { computed, h } from 'vue'
|
|
||||||
import { useThemeVars } from 'naive-ui'
|
|
||||||
import { types, typesBgColor, typesColor } from '@/consts/support_redis_type.js'
|
|
||||||
import { get, map, toUpper } from 'lodash'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
value: {
|
|
||||||
type: String,
|
|
||||||
default: 'ALL',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:value', 'select'])
|
|
||||||
|
|
||||||
const options = computed(() => {
|
|
||||||
const opts = map(types, (v) => ({
|
|
||||||
label: v,
|
|
||||||
key: v,
|
|
||||||
}))
|
|
||||||
return [{ label: 'ALL', key: 'ALL' }, ...opts]
|
|
||||||
})
|
|
||||||
|
|
||||||
const themeVars = useThemeVars()
|
|
||||||
const renderIcon = (option) => {
|
|
||||||
if (option.key === props.value) {
|
|
||||||
const backgroundColor = get(typesColor, option.key, themeVars.value.textColor3)
|
|
||||||
return h('div', { style: { borderRadius: '999px', width: '10px', height: '10px', backgroundColor } }, '')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderLabel = (option) => {
|
|
||||||
const color = get(typesColor, option.key, '')
|
|
||||||
return h('div', { style: { color, fontWeight: '450' } }, option.label)
|
|
||||||
}
|
|
||||||
|
|
||||||
const fontColor = computed(() => {
|
|
||||||
return get(typesColor, props.value, '')
|
|
||||||
})
|
|
||||||
|
|
||||||
const backgroundColor = computed(() => {
|
|
||||||
return get(typesBgColor, props.value, '')
|
|
||||||
})
|
|
||||||
|
|
||||||
const displayValue = computed(() => {
|
|
||||||
return get(types, toUpper(props.value), 'ALL')
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleSelect = (select) => {
|
|
||||||
if (props.value !== select) {
|
|
||||||
emit('update:value', select)
|
|
||||||
emit('select', select)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<n-dropdown
|
|
||||||
:options="options"
|
|
||||||
:render-icon="renderIcon"
|
|
||||||
:render-label="renderLabel"
|
|
||||||
show-arrow
|
|
||||||
@select="handleSelect">
|
|
||||||
<n-tag
|
|
||||||
:bordered="true"
|
|
||||||
:color="{ color: backgroundColor, textColor: fontColor }"
|
|
||||||
class="redis-tag"
|
|
||||||
size="medium"
|
|
||||||
strong>
|
|
||||||
{{ displayValue }}
|
|
||||||
</n-tag>
|
|
||||||
</n-dropdown>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.redis-tag {
|
|
||||||
padding: 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dropdown-type-item) {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -12,6 +12,7 @@ const props = defineProps({
|
||||||
default: 'STRING',
|
default: 'STRING',
|
||||||
},
|
},
|
||||||
binaryKey: Boolean,
|
binaryKey: Boolean,
|
||||||
|
bordered: Boolean,
|
||||||
size: String,
|
size: String,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -26,8 +27,9 @@ const backgroundColor = computed(() => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-tag
|
<n-tag
|
||||||
|
:bordered="props.bordered"
|
||||||
:class="[props.size === 'small' ? 'redis-type-tag-small' : 'redis-type-tag']"
|
:class="[props.size === 'small' ? 'redis-type-tag-small' : 'redis-type-tag']"
|
||||||
:color="{ color: backgroundColor, textColor: fontColor }"
|
:color="{ color: backgroundColor, borderColor: fontColor, textColor: fontColor }"
|
||||||
:size="props.size"
|
:size="props.size"
|
||||||
strong>
|
strong>
|
||||||
{{ props.type }}
|
{{ props.type }}
|
||||||
|
|
|
@ -101,7 +101,6 @@ const handleMouseOver = () => {
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
transition: background-color 0.3s ease-in;
|
transition: background-color 0.3s ease-in;
|
||||||
z-index: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.resize-divider-hide {
|
.resize-divider-hide {
|
||||||
|
|
|
@ -94,13 +94,26 @@ const viewLanguage = computed(() => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const btnStyle = computed(() => ({
|
||||||
|
padding: '3px',
|
||||||
|
border: 'solid 1px #0000',
|
||||||
|
borderRadius: '3px',
|
||||||
|
}))
|
||||||
|
|
||||||
|
const pinBtnStyle = computed(() => ({
|
||||||
|
padding: '3px',
|
||||||
|
border: `solid 1px ${themeVars.value.borderColor}`,
|
||||||
|
borderRadius: '3px',
|
||||||
|
backgroundColor: themeVars.value.borderColor,
|
||||||
|
}))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {decodeTypes|null} decode
|
* @param {decodeTypes} decode
|
||||||
* @param {formatTypes|null} format
|
* @param {formatTypes} format
|
||||||
* @return {Promise<void>}
|
* @return {Promise<void>}
|
||||||
*/
|
*/
|
||||||
const onFormatChanged = async (decode = null, format = null) => {
|
const onFormatChanged = async (decode = '', format = '') => {
|
||||||
try {
|
try {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const {
|
const {
|
||||||
|
@ -183,21 +196,21 @@ const onSave = () => {
|
||||||
<template #header-extra>
|
<template #header-extra>
|
||||||
<n-space :size="5">
|
<n-space :size="5">
|
||||||
<icon-button
|
<icon-button
|
||||||
:button-class="{ 'pinable-btn': true, 'unpin-btn': !isPin, 'pin-btn': isPin }"
|
:button-style="isPin ? pinBtnStyle : btnStyle"
|
||||||
:icon="Pin"
|
:icon="Pin"
|
||||||
:size="19"
|
:size="19"
|
||||||
:t-tooltip="isPin ? 'interface.unpin_edit' : 'interface.pin_edit'"
|
:t-tooltip="isPin ? 'interface.unpin_edit' : 'interface.pin_edit'"
|
||||||
stroke-width="4"
|
stroke-width="4"
|
||||||
@click="isPin = !isPin" />
|
@click="isPin = !isPin" />
|
||||||
<icon-button
|
<icon-button
|
||||||
:button-class="['pinable-btn', 'unpin-btn']"
|
:button-style="btnStyle"
|
||||||
:icon="props.fullscreen ? OffScreen : FullScreen"
|
:icon="props.fullscreen ? OffScreen : FullScreen"
|
||||||
:size="18"
|
:size="18"
|
||||||
stroke-width="5"
|
stroke-width="5"
|
||||||
t-tooltip="interface.fullscreen"
|
t-tooltip="interface.fullscreen"
|
||||||
@click="onToggleFullscreen" />
|
@click="onToggleFullscreen" />
|
||||||
<icon-button
|
<icon-button
|
||||||
:button-class="['pinable-btn', 'unpin-btn']"
|
:button-style="btnStyle"
|
||||||
:icon="WindowClose"
|
:icon="WindowClose"
|
||||||
:size="18"
|
:size="18"
|
||||||
stroke-width="5"
|
stroke-width="5"
|
||||||
|
@ -254,22 +267,6 @@ const onSave = () => {
|
||||||
background-color: unset;
|
background-color: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.pinable-btn) {
|
|
||||||
padding: 3px;
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 1px;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.unpin-btn) {
|
|
||||||
border-color: #0000;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.pin-btn) {
|
|
||||||
border-color: v-bind('themeVars.iconColorDisabled');
|
|
||||||
background-color: v-bind('themeVars.iconColorDisabled');
|
|
||||||
}
|
|
||||||
|
|
||||||
//:deep(.n-card--bordered) {
|
//:deep(.n-card--bordered) {
|
||||||
// border-radius: 0;
|
// border-radius: 0;
|
||||||
//}
|
//}
|
||||||
|
|
|
@ -2,23 +2,6 @@
|
||||||
import { computed, reactive } from 'vue'
|
import { computed, reactive } from 'vue'
|
||||||
import { debounce, isEmpty, trim } from 'lodash'
|
import { debounce, isEmpty, trim } from 'lodash'
|
||||||
import { NButton, NInput } from 'naive-ui'
|
import { NButton, NInput } from 'naive-ui'
|
||||||
import IconButton from '@/components/common/IconButton.vue'
|
|
||||||
import Help from '@/components/icons/Help.vue'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
fullSearchIcon: {
|
|
||||||
type: [String, Object],
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
debounceWait: {
|
|
||||||
type: Number,
|
|
||||||
default: 500,
|
|
||||||
},
|
|
||||||
small: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['filterChanged', 'matchChanged'])
|
const emit = defineEmits(['filterChanged', 'matchChanged'])
|
||||||
|
|
||||||
|
@ -51,13 +34,7 @@ const onFullSearch = () => {
|
||||||
const _onInput = () => {
|
const _onInput = () => {
|
||||||
emit('filterChanged', inputData.filter)
|
emit('filterChanged', inputData.filter)
|
||||||
}
|
}
|
||||||
const onInput = debounce(_onInput, props.debounceWait, { leading: true, trailing: true })
|
const onInput = debounce(_onInput, 500, { leading: true, trailing: true })
|
||||||
|
|
||||||
const onKeyup = (evt) => {
|
|
||||||
if (evt.key === 'Enter') {
|
|
||||||
onFullSearch()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onClearFilter = () => {
|
const onClearFilter = () => {
|
||||||
inputData.filter = ''
|
inputData.filter = ''
|
||||||
|
@ -81,18 +58,13 @@ defineExpose({
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-input-group>
|
<n-input-group>
|
||||||
<slot name="prepend" />
|
|
||||||
|
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="inputData.filter"
|
v-model:value="inputData.filter"
|
||||||
:placeholder="$t('interface.filter')"
|
:placeholder="$t('interface.filter')"
|
||||||
:size="props.small ? 'small' : ''"
|
|
||||||
clearable
|
clearable
|
||||||
@clear="onClearFilter"
|
@clear="onClearFilter"
|
||||||
@input="onInput"
|
@input="onInput">
|
||||||
@keyup.enter="onKeyup">
|
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<slot name="prefix" />
|
|
||||||
<n-tooltip v-if="hasMatch">
|
<n-tooltip v-if="hasMatch">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<n-tag closable size="small" @close="onClearMatch">
|
<n-tag closable size="small" @close="onClearMatch">
|
||||||
|
@ -102,31 +74,10 @@ defineExpose({
|
||||||
{{ $t('interface.full_search_result', { pattern: inputData.match }) }}
|
{{ $t('interface.full_search_result', { pattern: inputData.match }) }}
|
||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<template #suffix>
|
|
||||||
<n-tooltip trigger="hover">
|
|
||||||
<template #trigger>
|
|
||||||
<n-icon :component="Help" />
|
|
||||||
</template>
|
|
||||||
<div class="text-block" style="max-width: 600px">
|
|
||||||
{{ $t('dialogue.filter.filter_pattern_tip') }}
|
|
||||||
</div>
|
|
||||||
</n-tooltip>
|
|
||||||
</template>
|
|
||||||
</n-input>
|
</n-input>
|
||||||
|
<n-button :disabled="hasMatch && !hasFilter" :focusable="false" @click="onFullSearch">
|
||||||
<icon-button
|
|
||||||
v-if="props.fullSearchIcon"
|
|
||||||
:disabled="hasMatch && !hasFilter"
|
|
||||||
:icon="props.fullSearchIcon"
|
|
||||||
:size="small ? 16 : 20"
|
|
||||||
border
|
|
||||||
small
|
|
||||||
t-tooltip="interface.full_search"
|
|
||||||
@click="onFullSearch" />
|
|
||||||
<n-button v-else :disabled="hasMatch && !hasFilter" :focusable="false" @click="onFullSearch">
|
|
||||||
{{ $t('interface.full_search') }}
|
{{ $t('interface.full_search') }}
|
||||||
</n-button>
|
</n-button>
|
||||||
<slot name="append" />
|
|
||||||
</n-input-group>
|
</n-input-group>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,11 @@ watch(
|
||||||
)
|
)
|
||||||
|
|
||||||
const browserStore = useBrowserStore()
|
const browserStore = useBrowserStore()
|
||||||
const onConfirm = () => {}
|
const onConfirm = () => {
|
||||||
|
const { server, db, type, pattern } = filterForm
|
||||||
|
browserStore.setKeyFilter(server, db, pattern, type)
|
||||||
|
browserStore.reopenDatabase(server, db)
|
||||||
|
}
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
dialogStore.closeKeyFilterDialog()
|
dialogStore.closeKeyFilterDialog()
|
||||||
|
|
|
@ -109,16 +109,14 @@ const onAdd = async () => {
|
||||||
$message.error(err)
|
$message.error(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (subFormRef.value?.validate) {
|
await subFormRef.value?.validate((errs) => {
|
||||||
await subFormRef.value?.validate((errs) => {
|
const err = get(errs, '0.0.message')
|
||||||
const err = get(errs, '0.0.message')
|
if (err != null) {
|
||||||
if (err != null) {
|
$message.error(err)
|
||||||
$message.error(err)
|
} else {
|
||||||
} else {
|
$message.error(i18n.t('dialogue.spec_field_required', { key: i18n.t('dialogue.field.element') }))
|
||||||
$message.error(i18n.t('dialogue.spec_field_required', { key: i18n.t('dialogue.field.element') }))
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const { server, db, key, type, ttl } = newForm
|
const { server, db, key, type, ttl } = newForm
|
||||||
let { value } = newForm
|
let { value } = newForm
|
||||||
|
|
|
@ -12,13 +12,13 @@ const props = defineProps({
|
||||||
<path
|
<path
|
||||||
:stroke-width="props.strokeWidth"
|
:stroke-width="props.strokeWidth"
|
||||||
clip-rule="evenodd"
|
clip-rule="evenodd"
|
||||||
d="M23.9999 31L12 19L19.9999 19L19.9999 8L27.9999 8L27.9999 19L35.9999 19L23.9999 31Z"
|
d="M23.9999 29.0001L12 17.0001L19.9999 17.0001L19.9999 6.00011L27.9999 6.00011L27.9999 17.0001L35.9999 17.0001L23.9999 29.0001Z"
|
||||||
fill="none"
|
fill="none"
|
||||||
fill-rule="evenodd"
|
fill-rule="evenodd"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round" />
|
stroke-linejoin="round" />
|
||||||
<path :stroke-width="props.strokeWidth" d="M42 38L6 38" stroke="currentColor" stroke-linecap="round" />
|
<path :stroke-width="props.strokeWidth" d="M42 37L6 37" stroke="currentColor" stroke-linecap="round" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,15 @@ const props = defineProps({
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<svg fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
<svg fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
||||||
<circle :r="props.strokeWidth" cx="12" cy="24" fill="currentColor" />
|
<path
|
||||||
<circle :r="props.strokeWidth" cx="24" cy="24" fill="currentColor" />
|
:stroke-width="strokeWidth"
|
||||||
<circle :r="props.strokeWidth" cx="36" cy="24" fill="currentColor" />
|
d="M24 44C35.0457 44 44 35.0457 44 24C44 12.9543 35.0457 4 24 4C12.9543 4 4 12.9543 4 24C4 35.0457 12.9543 44 24 44Z"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<circle cx="14" cy="24" fill="currentColor" r="3" />
|
||||||
|
<circle cx="24" cy="24" fill="currentColor" r="3" />
|
||||||
|
<circle cx="34" cy="24" fill="currentColor" r="3" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
<script setup>
|
|
||||||
const props = defineProps({
|
|
||||||
strokeWidth: {
|
|
||||||
type: [Number, String],
|
|
||||||
default: 4,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<svg fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path
|
|
||||||
:stroke-width="props.strokeWidth"
|
|
||||||
d="M24.0605 10L24.0239 38"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round" />
|
|
||||||
<path
|
|
||||||
:stroke-width="props.strokeWidth"
|
|
||||||
d="M10 24L38 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round" />
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
|
@ -17,7 +17,13 @@ const props = defineProps({
|
||||||
stroke-linejoin="round" />
|
stroke-linejoin="round" />
|
||||||
<path
|
<path
|
||||||
:stroke-width="props.strokeWidth"
|
:stroke-width="props.strokeWidth"
|
||||||
d="M33 33L42 42"
|
d="M26.657 14.3431C25.2093 12.8954 23.2093 12 21.0001 12C18.791 12 16.791 12.8954 15.3433 14.3431"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<path
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
d="M33.2216 33.2217L41.7069 41.707"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round" />
|
stroke-linejoin="round" />
|
||||||
|
|
|
@ -1,173 +1,54 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useThemeVars } from 'naive-ui'
|
import { NIcon, useThemeVars } from 'naive-ui'
|
||||||
import BrowserTree from './BrowserTree.vue'
|
import BrowserTree from './BrowserTree.vue'
|
||||||
import IconButton from '@/components/common/IconButton.vue'
|
import IconButton from '@/components/common/IconButton.vue'
|
||||||
import useTabStore from 'stores/tab.js'
|
import useTabStore from 'stores/tab.js'
|
||||||
import { computed, onMounted, reactive, ref, unref, watch } from 'vue'
|
import { computed, reactive, ref } from 'vue'
|
||||||
import { get, map } from 'lodash'
|
import { get } from 'lodash'
|
||||||
import Refresh from '@/components/icons/Refresh.vue'
|
import Refresh from '@/components/icons/Refresh.vue'
|
||||||
import useDialogStore from 'stores/dialog.js'
|
import useDialogStore from 'stores/dialog.js'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { types } from '@/consts/support_redis_type.js'
|
||||||
import Search from '@/components/icons/Search.vue'
|
import Search from '@/components/icons/Search.vue'
|
||||||
import Unlink from '@/components/icons/Unlink.vue'
|
import Unlink from '@/components/icons/Unlink.vue'
|
||||||
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
|
|
||||||
import LoadAll from '@/components/icons/LoadAll.vue'
|
|
||||||
import LoadList from '@/components/icons/LoadList.vue'
|
|
||||||
import Delete from '@/components/icons/Delete.vue'
|
|
||||||
import useBrowserStore from 'stores/browser.js'
|
|
||||||
import { useRender } from '@/utils/render.js'
|
|
||||||
import RedisTypeSelector from '@/components/common/RedisTypeSelector.vue'
|
|
||||||
import { types } from '@/consts/support_redis_type.js'
|
|
||||||
import Plus from '@/components/icons/Plus.vue'
|
|
||||||
|
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
const i18n = useI18n()
|
|
||||||
const dialogStore = useDialogStore()
|
const dialogStore = useDialogStore()
|
||||||
// const prefStore = usePreferencesStore()
|
|
||||||
const tabStore = useTabStore()
|
const tabStore = useTabStore()
|
||||||
const browserStore = useBrowserStore()
|
|
||||||
const render = useRender()
|
|
||||||
const currentName = computed(() => get(tabStore.currentTab, 'name', ''))
|
const currentName = computed(() => get(tabStore.currentTab, 'name', ''))
|
||||||
const browserTreeRef = ref(null)
|
const browserTreeRef = ref(null)
|
||||||
const loading = ref(false)
|
|
||||||
const fullyLoaded = ref(false)
|
|
||||||
|
|
||||||
const selectedDB = computed(() => {
|
const onInfo = () => {
|
||||||
return browserStore.selectedDatabases[currentName.value] || 0
|
browserTreeRef.value?.handleSelectContextMenu('server_info')
|
||||||
})
|
|
||||||
|
|
||||||
const dbSelectOptions = computed(() => {
|
|
||||||
const dblist = browserStore.getDBList(currentName.value)
|
|
||||||
return map(dblist, (db) => {
|
|
||||||
if (selectedDB.value === db.db) {
|
|
||||||
return {
|
|
||||||
value: db.db,
|
|
||||||
label: `db${db.db} (${db.keys}/${db.maxKeys})`,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
value: db.db,
|
|
||||||
label: `db${db.db} (${db.maxKeys})`,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const loadProgress = computed(() => {
|
|
||||||
const db = browserStore.getDatabase(currentName.value, selectedDB.value)
|
|
||||||
if (db.maxKeys <= 0) {
|
|
||||||
return 100
|
|
||||||
}
|
|
||||||
return (db.keys * 100) / db.maxKeys
|
|
||||||
})
|
|
||||||
|
|
||||||
const onReload = async () => {
|
|
||||||
try {
|
|
||||||
loading.value = true
|
|
||||||
tabStore.setSelectedKeys(currentName.value)
|
|
||||||
const db = selectedDB.value
|
|
||||||
browserStore.closeDatabase(currentName.value, db)
|
|
||||||
browserTreeRef.value?.resetExpandKey(currentName.value, db)
|
|
||||||
|
|
||||||
let matchType = unref(filterForm.type)
|
|
||||||
if (!types.hasOwnProperty(matchType)) {
|
|
||||||
matchType = ''
|
|
||||||
}
|
|
||||||
browserStore.setKeyFilter(currentName.value, {
|
|
||||||
type: matchType,
|
|
||||||
pattern: unref(filterForm.pattern),
|
|
||||||
})
|
|
||||||
await browserStore.openDatabase(currentName.value, db)
|
|
||||||
fullyLoaded.value = await browserStore.loadMoreKeys(currentName.value, db)
|
|
||||||
// $message.success(i18n.t('dialogue.reload_succ'))
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(e)
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onAddKey = () => {
|
|
||||||
dialogStore.openNewKeyDialog('', currentName.value, selectedDB.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onLoadMore = async () => {
|
|
||||||
try {
|
|
||||||
loading.value = true
|
|
||||||
fullyLoaded.value = await browserStore.loadMoreKeys(currentName.value, selectedDB.value)
|
|
||||||
} catch (e) {
|
|
||||||
$message.error(e.message)
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onLoadAll = async () => {
|
|
||||||
try {
|
|
||||||
loading.value = true
|
|
||||||
await browserStore.loadAllKeys(currentName.value, selectedDB.value)
|
|
||||||
fullyLoaded.value = true
|
|
||||||
} catch (e) {
|
|
||||||
$message.error(e.message)
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onFlush = () => {
|
|
||||||
dialogStore.openFlushDBDialog(currentName.value, selectedDB.value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const i18n = useI18n()
|
||||||
const onDisconnect = () => {
|
const onDisconnect = () => {
|
||||||
browserStore.closeConnection(currentName.value)
|
browserTreeRef.value?.handleSelectContextMenu('server_close')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSelectDB = async (db, prevDB) => {
|
const onRefresh = () => {
|
||||||
// watch 'browserStore.openedDB[currentName.value]' instead
|
browserTreeRef.value?.handleSelectContextMenu('server_reload')
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterForm = reactive({
|
const filterForm = reactive({
|
||||||
|
showFilter: false,
|
||||||
type: '',
|
type: '',
|
||||||
pattern: '',
|
pattern: '',
|
||||||
filter: '',
|
|
||||||
})
|
})
|
||||||
const onSelectFilterType = (select) => {
|
|
||||||
onReload()
|
|
||||||
}
|
|
||||||
|
|
||||||
const onFilterInput = (val) => {
|
const filterTypeOptions = computed(() => {
|
||||||
filterForm.filter = val
|
const options = Object.keys(types).map((t) => ({
|
||||||
}
|
value: t,
|
||||||
|
label: t,
|
||||||
|
}))
|
||||||
|
options.splice(0, 0, {
|
||||||
|
value: '',
|
||||||
|
label: i18n.t('common.all'),
|
||||||
|
})
|
||||||
|
return options
|
||||||
|
})
|
||||||
|
|
||||||
const onMatchInput = (matchVal, filterVal) => {
|
|
||||||
filterForm.pattern = matchVal
|
|
||||||
filterForm.filter = filterVal
|
|
||||||
onReload()
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => browserStore.openedDB[currentName.value],
|
|
||||||
async (db, prevDB) => {
|
|
||||||
if (db === undefined) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
loading.value = true
|
|
||||||
browserStore.closeDatabase(currentName.value, prevDB)
|
|
||||||
browserStore.setKeyFilter(currentName.value, {})
|
|
||||||
await browserStore.openDatabase(currentName.value, db)
|
|
||||||
browserTreeRef.value?.resetExpandKey(currentName.value, db)
|
|
||||||
fullyLoaded.value = await browserStore.loadMoreKeys(currentName.value, db)
|
|
||||||
browserTreeRef.value?.refreshTree()
|
|
||||||
} catch (e) {
|
|
||||||
$message.error(e.message)
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
onMounted(() => onReload())
|
|
||||||
// forbid dynamic switch key view due to performance issues
|
// forbid dynamic switch key view due to performance issues
|
||||||
// const viewType = ref(0)
|
// const viewType = ref(0)
|
||||||
// const onSwitchView = (selectView) => {
|
// const onSwitchView = (selectView) => {
|
||||||
|
@ -178,142 +59,47 @@ onMounted(() => onReload())
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="nav-pane-container flex-box-v">
|
<div class="nav-pane-container flex-box-v">
|
||||||
<!-- top function bar -->
|
<browser-tree ref="browserTreeRef" :server="currentName" />
|
||||||
<div class="flex-box-h nav-pane-func">
|
|
||||||
<content-search-input
|
<div v-if="filterForm.showFilter" class="nav-pane-bottom flex-box-h">
|
||||||
:debounce-wait="1000"
|
<n-input-group>
|
||||||
:full-search-icon="Search"
|
<n-select
|
||||||
small
|
v-model:value="filterForm.type"
|
||||||
@filter-changed="onFilterInput"
|
:consistent-menu-width="false"
|
||||||
@match-changed="onMatchInput">
|
:options="filterTypeOptions"
|
||||||
<template #prepend>
|
style="width: 120px" />
|
||||||
<redis-type-selector v-model:value="filterForm.type" @update:value="onSelectFilterType" />
|
<n-input clearable placeholder="" />
|
||||||
</template>
|
<n-button :focusable="false" ghost>
|
||||||
</content-search-input>
|
<template #icon>
|
||||||
<n-button-group>
|
<n-icon :component="Search" />
|
||||||
<n-tooltip :show-arrow="false">
|
|
||||||
<template #trigger>
|
|
||||||
<n-button :disabled="loading" :focusable="false" bordered size="small" @click="onReload">
|
|
||||||
<template #icon>
|
|
||||||
<n-icon :component="Refresh" size="18" />
|
|
||||||
</template>
|
|
||||||
</n-button>
|
|
||||||
</template>
|
</template>
|
||||||
{{ $t('interface.reload') }}
|
</n-button>
|
||||||
</n-tooltip>
|
</n-input-group>
|
||||||
<n-tooltip :show-arrow="false">
|
|
||||||
<template #trigger>
|
|
||||||
<n-button :disabled="loading" :focusable="false" bordered size="small" @click="onAddKey">
|
|
||||||
<template #icon>
|
|
||||||
<n-icon :component="Plus" size="18" />
|
|
||||||
</template>
|
|
||||||
</n-button>
|
|
||||||
</template>
|
|
||||||
{{ $t('interface.new_key') }}
|
|
||||||
</n-tooltip>
|
|
||||||
</n-button-group>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- loaded progress -->
|
|
||||||
<n-progress
|
|
||||||
:border-radius="0"
|
|
||||||
:color="fullyLoaded ? '#0000' : themeVars.primaryColor"
|
|
||||||
:height="2"
|
|
||||||
:percentage="loadProgress"
|
|
||||||
:processing="loading"
|
|
||||||
:show-indicator="false"
|
|
||||||
status="success"
|
|
||||||
type="line" />
|
|
||||||
|
|
||||||
<!-- tree view -->
|
|
||||||
<browser-tree
|
|
||||||
ref="browserTreeRef"
|
|
||||||
:full-loaded="fullyLoaded"
|
|
||||||
:loading="loading && loadProgress <= 0"
|
|
||||||
:pattern="filterForm.filter"
|
|
||||||
:server="currentName" />
|
|
||||||
<!-- bottom function bar -->
|
<!-- bottom function bar -->
|
||||||
<div class="nav-pane-bottom flex-box-v">
|
<div class="nav-pane-bottom flex-box-h">
|
||||||
<!-- <switch-button-->
|
<!-- <switch-button-->
|
||||||
<!-- v-model:value="viewType"-->
|
<!-- v-model:value="viewType"-->
|
||||||
<!-- :icons="[TreeView, ListView]"-->
|
<!-- :icons="[TreeView, ListView]"-->
|
||||||
<!-- :t-tooltips="['interface.tree_view', 'interface.list_view']"-->
|
<!-- :t-tooltips="['interface.tree_view', 'interface.list_view']"-->
|
||||||
<!-- :stroke-width="3.5"-->
|
<!-- stroke-width="4"-->
|
||||||
<!-- unselect-stroke-width="3"-->
|
<!-- unselect-stroke-width="3"-->
|
||||||
<!-- @update:value="onSwitchView" />-->
|
<!-- @update:value="onSwitchView" />-->
|
||||||
<div class="flex-box-h nav-pane-func">
|
<!-- <icon-button :icon="Status" size="20" stroke-width="4" t-tooltip="interface.status" @click="onInfo" />-->
|
||||||
<n-select
|
<icon-button :icon="Refresh" size="20" stroke-width="4" t-tooltip="interface.reload" @click="onRefresh" />
|
||||||
v-model:value="browserStore.openedDB[currentName]"
|
<div class="flex-item-expand" />
|
||||||
:consistent-menu-width="false"
|
<icon-button
|
||||||
:filter="(pattern, option) => option.value.toString() === pattern"
|
:icon="Unlink"
|
||||||
:options="dbSelectOptions"
|
size="20"
|
||||||
filterable
|
stroke-width="4"
|
||||||
size="small"
|
t-tooltip="interface.disconnect"
|
||||||
style="min-width: 100px; max-width: 200px"
|
@click="onDisconnect" />
|
||||||
@update:value="handleSelectDB" />
|
|
||||||
<icon-button
|
|
||||||
:button-class="['nav-pane-func-btn']"
|
|
||||||
:disabled="fullyLoaded"
|
|
||||||
:icon="LoadList"
|
|
||||||
:loading="loading"
|
|
||||||
:stroke-width="3.5"
|
|
||||||
size="20"
|
|
||||||
t-tooltip="interface.load_more"
|
|
||||||
@click="onLoadMore" />
|
|
||||||
<icon-button
|
|
||||||
:button-class="['nav-pane-func-btn']"
|
|
||||||
:disabled="fullyLoaded"
|
|
||||||
:icon="LoadAll"
|
|
||||||
:loading="loading"
|
|
||||||
:stroke-width="3.5"
|
|
||||||
size="20"
|
|
||||||
t-tooltip="interface.load_all"
|
|
||||||
@click="onLoadAll" />
|
|
||||||
<div class="flex-item-expand" />
|
|
||||||
<icon-button
|
|
||||||
:button-class="['nav-pane-func-btn']"
|
|
||||||
:icon="Delete"
|
|
||||||
:stroke-width="3.5"
|
|
||||||
size="20"
|
|
||||||
t-tooltip="interface.flush_db"
|
|
||||||
@click="onFlush" />
|
|
||||||
<icon-button
|
|
||||||
:button-class="['nav-pane-func-btn']"
|
|
||||||
:icon="Unlink"
|
|
||||||
:stroke-width="3.5"
|
|
||||||
size="20"
|
|
||||||
t-tooltip="interface.disconnect"
|
|
||||||
@click="onDisconnect" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '@/styles/style';
|
|
||||||
|
|
||||||
:deep(.toggle-btn) {
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.filter-on) {
|
|
||||||
border-color: v-bind('themeVars.iconColorDisabled');
|
|
||||||
background-color: v-bind('themeVars.iconColorDisabled');
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.filter-off) {
|
|
||||||
border-color: #0000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-pane-top {
|
|
||||||
//@include bottom-shadow(0.1);
|
|
||||||
color: v-bind('themeVars.iconColor');
|
|
||||||
border-bottom: v-bind('themeVars.borderColor') 1px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-pane-bottom {
|
.nav-pane-bottom {
|
||||||
@include top-shadow(0.1);
|
|
||||||
color: v-bind('themeVars.iconColor');
|
color: v-bind('themeVars.iconColor');
|
||||||
border-top: v-bind('themeVars.borderColor') 1px solid;
|
border-top: v-bind('themeVars.borderColor') 1px solid;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, h, nextTick, reactive, ref } from 'vue'
|
import { computed, h, nextTick, onMounted, reactive, ref } from 'vue'
|
||||||
import { ConnectionType } from '@/consts/connection_type.js'
|
import { ConnectionType } from '@/consts/connection_type.js'
|
||||||
import { NIcon, NSpace, useThemeVars } from 'naive-ui'
|
import { NIcon, NSpace, NTag, useThemeVars } from 'naive-ui'
|
||||||
import Key from '@/components/icons/Key.vue'
|
import Key from '@/components/icons/Key.vue'
|
||||||
import Binary from '@/components/icons/Binary.vue'
|
import Binary from '@/components/icons/Binary.vue'
|
||||||
import Database from '@/components/icons/Database.vue'
|
import Database from '@/components/icons/Database.vue'
|
||||||
|
@ -12,29 +12,30 @@ import CopyLink from '@/components/icons/CopyLink.vue'
|
||||||
import Add from '@/components/icons/Add.vue'
|
import Add from '@/components/icons/Add.vue'
|
||||||
import Layer from '@/components/icons/Layer.vue'
|
import Layer from '@/components/icons/Layer.vue'
|
||||||
import Delete from '@/components/icons/Delete.vue'
|
import Delete from '@/components/icons/Delete.vue'
|
||||||
|
import Connect from '@/components/icons/Connect.vue'
|
||||||
import useDialogStore from 'stores/dialog.js'
|
import useDialogStore from 'stores/dialog.js'
|
||||||
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
||||||
import useConnectionStore from 'stores/connections.js'
|
import useConnectionStore from 'stores/connections.js'
|
||||||
|
import Unlink from '@/components/icons/Unlink.vue'
|
||||||
import Filter from '@/components/icons/Filter.vue'
|
import Filter from '@/components/icons/Filter.vue'
|
||||||
|
import Close from '@/components/icons/Close.vue'
|
||||||
|
import { typesBgColor, typesColor } from '@/consts/support_redis_type.js'
|
||||||
import useTabStore from 'stores/tab.js'
|
import useTabStore from 'stores/tab.js'
|
||||||
import IconButton from '@/components/common/IconButton.vue'
|
import IconButton from '@/components/common/IconButton.vue'
|
||||||
import { parseHexColor } from '@/utils/rgb.js'
|
import { parseHexColor } from '@/utils/rgb.js'
|
||||||
import LoadList from '@/components/icons/LoadList.vue'
|
import LoadList from '@/components/icons/LoadList.vue'
|
||||||
import LoadAll from '@/components/icons/LoadAll.vue'
|
import LoadAll from '@/components/icons/LoadAll.vue'
|
||||||
import useBrowserStore from 'stores/browser.js'
|
import useBrowserStore from 'stores/browser.js'
|
||||||
import { useRender } from '@/utils/render.js'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
server: String,
|
server: String,
|
||||||
keyView: String,
|
keyView: String,
|
||||||
loading: Boolean,
|
|
||||||
pattern: String,
|
|
||||||
fullLoaded: Boolean,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
const render = useRender()
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
|
const loading = ref(false)
|
||||||
|
const loadingConnections = ref(false)
|
||||||
const expandedKeys = ref([props.server])
|
const expandedKeys = ref([props.server])
|
||||||
const connectionStore = useConnectionStore()
|
const connectionStore = useConnectionStore()
|
||||||
const browserStore = useBrowserStore()
|
const browserStore = useBrowserStore()
|
||||||
|
@ -54,9 +55,8 @@ const selectedKeys = computed(() => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const data = computed(() => {
|
const data = computed(() => {
|
||||||
// const dbs = get(browserStore.databases, props.server, [])
|
const dbs = get(browserStore.databases, props.server, [])
|
||||||
// return dbs
|
return dbs
|
||||||
return browserStore.getKeyList(props.server)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const backgroundColor = computed(() => {
|
const backgroundColor = computed(() => {
|
||||||
|
@ -74,44 +74,90 @@ const contextMenuParam = reactive({
|
||||||
y: 0,
|
y: 0,
|
||||||
options: null,
|
options: null,
|
||||||
})
|
})
|
||||||
|
const renderIcon = (icon) => {
|
||||||
|
return () => {
|
||||||
|
return h(NIcon, null, {
|
||||||
|
default: () => h(icon),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
const menuOptions = {
|
const menuOptions = {
|
||||||
|
[ConnectionType.Server]: () => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'server_reload',
|
||||||
|
label: i18n.t('interface.reload'),
|
||||||
|
icon: renderIcon(Refresh),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'server_close',
|
||||||
|
label: i18n.t('interface.disconnect'),
|
||||||
|
icon: renderIcon(Unlink),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
[ConnectionType.RedisDB]: ({ opened }) => {
|
[ConnectionType.RedisDB]: ({ opened }) => {
|
||||||
if (opened) {
|
if (opened) {
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
key: 'db_reload',
|
||||||
|
label: i18n.t('interface.reload'),
|
||||||
|
icon: renderIcon(Refresh),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'db_newkey',
|
||||||
|
label: i18n.t('interface.new_key'),
|
||||||
|
icon: renderIcon(Add),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'db_filter',
|
key: 'db_filter',
|
||||||
label: i18n.t('interface.filter_key'),
|
label: i18n.t('interface.filter_key'),
|
||||||
icon: render.renderIcon(Filter),
|
icon: renderIcon(Filter),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'divider',
|
type: 'divider',
|
||||||
key: 'd1',
|
key: 'd1',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'db_flush',
|
||||||
|
label: i18n.t('interface.flush_db'),
|
||||||
|
icon: renderIcon(Delete),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'divider',
|
type: 'divider',
|
||||||
key: 'd2',
|
key: 'd2',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'db_close',
|
||||||
|
label: i18n.t('interface.close_db'),
|
||||||
|
icon: renderIcon(Close),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
return []
|
return [
|
||||||
|
{
|
||||||
|
key: 'db_open',
|
||||||
|
label: i18n.t('interface.open_db'),
|
||||||
|
icon: renderIcon(Connect),
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[ConnectionType.RedisKey]: () => [
|
[ConnectionType.RedisKey]: () => [
|
||||||
// {
|
// {
|
||||||
// key: 'key_reload',
|
// key: 'key_reload',
|
||||||
// label: i18n.t('interface.reload'),
|
// label: i18n.t('interface.reload'),
|
||||||
// icon: render.renderIcon(Refresh),
|
// icon: renderIcon(Refresh),
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
key: 'key_newkey',
|
key: 'key_newkey',
|
||||||
label: i18n.t('interface.new_key'),
|
label: i18n.t('interface.new_key'),
|
||||||
icon: render.renderIcon(Add),
|
icon: renderIcon(Add),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'key_copy',
|
key: 'key_copy',
|
||||||
label: i18n.t('interface.copy_path'),
|
label: i18n.t('interface.copy_path'),
|
||||||
icon: render.renderIcon(CopyLink),
|
icon: renderIcon(CopyLink),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'divider',
|
type: 'divider',
|
||||||
|
@ -120,19 +166,19 @@ const menuOptions = {
|
||||||
{
|
{
|
||||||
key: 'key_remove',
|
key: 'key_remove',
|
||||||
label: i18n.t('interface.batch_delete_key'),
|
label: i18n.t('interface.batch_delete_key'),
|
||||||
icon: render.renderIcon(Delete),
|
icon: renderIcon(Delete),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[ConnectionType.RedisValue]: () => [
|
[ConnectionType.RedisValue]: () => [
|
||||||
{
|
{
|
||||||
key: 'value_reload',
|
key: 'value_reload',
|
||||||
label: i18n.t('interface.reload'),
|
label: i18n.t('interface.reload'),
|
||||||
icon: render.renderIcon(Refresh),
|
icon: renderIcon(Refresh),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'value_copy',
|
key: 'value_copy',
|
||||||
label: i18n.t('interface.copy_key'),
|
label: i18n.t('interface.copy_key'),
|
||||||
icon: render.renderIcon(CopyLink),
|
icon: renderIcon(CopyLink),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'divider',
|
type: 'divider',
|
||||||
|
@ -141,7 +187,7 @@ const menuOptions = {
|
||||||
{
|
{
|
||||||
key: 'value_remove',
|
key: 'value_remove',
|
||||||
label: i18n.t('interface.remove_key'),
|
label: i18n.t('interface.remove_key'),
|
||||||
icon: render.renderIcon(Delete),
|
icon: renderIcon(Delete),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@ -150,6 +196,15 @@ const renderContextLabel = (option) => {
|
||||||
return h('div', { class: 'context-menu-item' }, option.label)
|
return h('div', { class: 'context-menu-item' }, option.label)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
// TODO: Show loading list status
|
||||||
|
loadingConnections.value = true
|
||||||
|
} finally {
|
||||||
|
loadingConnections.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const expandKey = (key) => {
|
const expandKey = (key) => {
|
||||||
const idx = indexOf(expandedKeys.value, key)
|
const idx = indexOf(expandedKeys.value, key)
|
||||||
if (idx === -1) {
|
if (idx === -1) {
|
||||||
|
@ -181,11 +236,40 @@ const handleSelectContextMenu = (key) => {
|
||||||
const redisKey = rkc || rk
|
const redisKey = rkc || rk
|
||||||
const redisKeyName = !!rkc ? label : redisKey
|
const redisKeyName = !!rkc ? label : redisKey
|
||||||
switch (key) {
|
switch (key) {
|
||||||
|
case 'server_info':
|
||||||
|
tabStore.setSelectedKeys(props.server)
|
||||||
|
onUpdateSelectedKeys()
|
||||||
|
break
|
||||||
|
case 'server_reload':
|
||||||
|
expandedKeys.value = [props.server]
|
||||||
|
tabStore.setSelectedKeys(props.server)
|
||||||
|
browserStore.openConnection(props.server, true).then(() => {
|
||||||
|
$message.success(i18n.t('dialogue.reload_succ'))
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case 'server_close':
|
||||||
|
browserStore.closeConnection(props.server)
|
||||||
|
break
|
||||||
|
case 'db_open':
|
||||||
|
nextTick().then(() => expandKey(nodeKey))
|
||||||
|
break
|
||||||
|
case 'db_reload':
|
||||||
|
resetExpandKey(props.server, db)
|
||||||
|
browserStore.reopenDatabase(props.server, db)
|
||||||
|
break
|
||||||
|
case 'db_close':
|
||||||
|
resetExpandKey(props.server, db, true)
|
||||||
|
browserStore.closeDatabase(props.server, db)
|
||||||
|
break
|
||||||
|
case 'db_flush':
|
||||||
|
dialogStore.openFlushDBDialog(props.server, db)
|
||||||
|
break
|
||||||
|
case 'db_newkey':
|
||||||
case 'key_newkey':
|
case 'key_newkey':
|
||||||
dialogStore.openNewKeyDialog(redisKey, props.server, db)
|
dialogStore.openNewKeyDialog(redisKey, props.server, db)
|
||||||
break
|
break
|
||||||
case 'db_filter':
|
case 'db_filter':
|
||||||
const { match: pattern, type } = browserStore.getKeyFilter(props.server)
|
const { match: pattern, type } = browserStore.getKeyFilter(props.server, db)
|
||||||
dialogStore.openKeyFilterDialog(props.server, db, pattern, type)
|
dialogStore.openKeyFilterDialog(props.server, db, pattern, type)
|
||||||
break
|
break
|
||||||
case 'key_reload':
|
case 'key_reload':
|
||||||
|
@ -227,6 +311,23 @@ const handleSelectContextMenu = (key) => {
|
||||||
$message.error(e.message)
|
$message.error(e.message)
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
|
case 'db_loadmore':
|
||||||
|
if (node != null && !!!node.loading && !!!node.fullLoaded) {
|
||||||
|
node.loading = true
|
||||||
|
browserStore
|
||||||
|
.loadMoreKeys(props.server, db)
|
||||||
|
.then((end) => {
|
||||||
|
// fully loaded
|
||||||
|
node.fullLoaded = end === true
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
$message.error(e.message)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
delete node.loading
|
||||||
|
})
|
||||||
|
}
|
||||||
|
break
|
||||||
case 'db_loadall':
|
case 'db_loadall':
|
||||||
if (node != null && !!!node.loading) {
|
if (node != null && !!!node.loading) {
|
||||||
node.loading = true
|
node.loading = true
|
||||||
|
@ -247,6 +348,10 @@ const handleSelectContextMenu = (key) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
handleSelectContextMenu,
|
||||||
|
})
|
||||||
|
|
||||||
const onUpdateExpanded = (value, option, meta) => {
|
const onUpdateExpanded = (value, option, meta) => {
|
||||||
expandedKeys.value = value
|
expandedKeys.value = value
|
||||||
if (!meta.node) {
|
if (!meta.node) {
|
||||||
|
@ -340,6 +445,61 @@ const renderPrefix = ({ option }) => {
|
||||||
// render tree item label
|
// render tree item label
|
||||||
const renderLabel = ({ option }) => {
|
const renderLabel = ({ option }) => {
|
||||||
switch (option.type) {
|
switch (option.type) {
|
||||||
|
case ConnectionType.Server:
|
||||||
|
return h('b', {}, { default: () => option.label })
|
||||||
|
case ConnectionType.RedisDB:
|
||||||
|
const { name: server, db, opened = false } = option
|
||||||
|
let { match: matchPattern, type: typeFilter } = browserStore.getKeyFilter(server, db)
|
||||||
|
const items = []
|
||||||
|
if (opened) {
|
||||||
|
items.push(`${option.label} (${option.keys || 0}/${Math.max(option.maxKeys || 0, option.keys || 0)})`)
|
||||||
|
} else {
|
||||||
|
items.push(`${option.label} (${Math.max(option.maxKeys || 0, option.keys || 0)})`)
|
||||||
|
}
|
||||||
|
// show filter tag after label
|
||||||
|
// type filter tag
|
||||||
|
if (!isEmpty(typeFilter)) {
|
||||||
|
items.push(
|
||||||
|
h(
|
||||||
|
NTag,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
closable: true,
|
||||||
|
bordered: false,
|
||||||
|
color: {
|
||||||
|
color: typesBgColor[typeFilter],
|
||||||
|
textColor: typesColor[typeFilter],
|
||||||
|
},
|
||||||
|
onClose: () => {
|
||||||
|
// remove type filter
|
||||||
|
browserStore.setKeyFilter(server, db, matchPattern)
|
||||||
|
browserStore.reopenDatabase(server, db)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ default: () => typeFilter },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// match pattern tag
|
||||||
|
if (!isEmpty(matchPattern) && matchPattern !== '*') {
|
||||||
|
items.push(
|
||||||
|
h(
|
||||||
|
NTag,
|
||||||
|
{
|
||||||
|
bordered: false,
|
||||||
|
closable: true,
|
||||||
|
size: 'small',
|
||||||
|
onClose: () => {
|
||||||
|
// remove key match pattern
|
||||||
|
browserStore.setKeyFilter(server, db, '*', typeFilter)
|
||||||
|
browserStore.reopenDatabase(server, db)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ default: () => matchPattern },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return renderIconMenu(items)
|
||||||
case ConnectionType.RedisKey:
|
case ConnectionType.RedisKey:
|
||||||
return `${option.label} (${option.keys || 0})`
|
return `${option.label} (${option.keys || 0})`
|
||||||
// case ConnectionType.RedisValue:
|
// case ConnectionType.RedisValue:
|
||||||
|
@ -368,6 +528,24 @@ const calcDBMenu = (opened, loading, end) => {
|
||||||
const btns = []
|
const btns = []
|
||||||
if (opened) {
|
if (opened) {
|
||||||
btns.push(
|
btns.push(
|
||||||
|
h(IconButton, {
|
||||||
|
tTooltip: 'interface.filter_key',
|
||||||
|
icon: Filter,
|
||||||
|
disabled: loading === true,
|
||||||
|
onClick: () => handleSelectContextMenu('db_filter'),
|
||||||
|
}),
|
||||||
|
h(IconButton, {
|
||||||
|
tTooltip: 'interface.reload',
|
||||||
|
icon: Refresh,
|
||||||
|
disabled: loading === true,
|
||||||
|
onClick: () => handleSelectContextMenu('db_reload'),
|
||||||
|
}),
|
||||||
|
h(IconButton, {
|
||||||
|
tTooltip: 'interface.new_key',
|
||||||
|
icon: Add,
|
||||||
|
disabled: loading === true,
|
||||||
|
onClick: () => handleSelectContextMenu('db_newkey'),
|
||||||
|
}),
|
||||||
h(IconButton, {
|
h(IconButton, {
|
||||||
tTooltip: 'interface.load_more',
|
tTooltip: 'interface.load_more',
|
||||||
icon: LoadList,
|
icon: LoadList,
|
||||||
|
@ -384,24 +562,38 @@ const calcDBMenu = (opened, loading, end) => {
|
||||||
color: loading === true ? themeVars.value.primaryColor : '',
|
color: loading === true ? themeVars.value.primaryColor : '',
|
||||||
onClick: () => handleSelectContextMenu('db_loadall'),
|
onClick: () => handleSelectContextMenu('db_loadall'),
|
||||||
}),
|
}),
|
||||||
|
h(IconButton, {
|
||||||
|
tTooltip: 'interface.flush_db',
|
||||||
|
icon: Delete,
|
||||||
|
disabled: loading === true,
|
||||||
|
onClick: () => handleSelectContextMenu('db_flush'),
|
||||||
|
}),
|
||||||
// h(IconButton, {
|
// h(IconButton, {
|
||||||
// tTooltip: 'interface.more_action',
|
// tTooltip: 'interface.more_action',
|
||||||
// icon: More,
|
// icon: More,
|
||||||
// onClick: () => handleSelectContextMenu('more_action'),
|
// onClick: () => handleSelectContextMenu('more_action'),
|
||||||
// }),
|
// }),
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
btns.push(
|
||||||
|
h(IconButton, {
|
||||||
|
tTooltip: 'interface.open_db',
|
||||||
|
icon: Connect,
|
||||||
|
onClick: () => handleSelectContextMenu('db_open'),
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return btns
|
return btns
|
||||||
}
|
}
|
||||||
|
|
||||||
const calcLayerMenu = (loading) => {
|
const calcLayerMenu = (loading, end) => {
|
||||||
return [
|
return [
|
||||||
// reload layer enable only full loaded
|
// reload layer enable only full loaded
|
||||||
h(IconButton, {
|
h(IconButton, {
|
||||||
tTooltip: props.fullLoaded ? 'interface.reload' : 'interface.reload',
|
tTooltip: end === true ? 'interface.reload' : 'interface.reload',
|
||||||
icon: Refresh,
|
icon: Refresh,
|
||||||
loading: loading === true,
|
loading: loading === true,
|
||||||
disabled: !props.fullLoaded,
|
disabled: end !== true,
|
||||||
onClick: () => handleSelectContextMenu('key_reload'),
|
onClick: () => handleSelectContextMenu('key_reload'),
|
||||||
}),
|
}),
|
||||||
h(IconButton, {
|
h(IconButton, {
|
||||||
|
@ -434,7 +626,8 @@ const renderSuffix = ({ option }) => {
|
||||||
case ConnectionType.RedisDB:
|
case ConnectionType.RedisDB:
|
||||||
return renderIconMenu(calcDBMenu(option.opened, option.loading, option.fullLoaded))
|
return renderIconMenu(calcDBMenu(option.opened, option.loading, option.fullLoaded))
|
||||||
case ConnectionType.RedisKey:
|
case ConnectionType.RedisKey:
|
||||||
return renderIconMenu(calcLayerMenu(option.loading))
|
const fullLoaded = browserStore.isFullLoaded(props.server, option.db)
|
||||||
|
return renderIconMenu(calcLayerMenu(option.loading, fullLoaded))
|
||||||
case ConnectionType.RedisValue:
|
case ConnectionType.RedisValue:
|
||||||
return renderIconMenu(calcValueMenu())
|
return renderIconMenu(calcValueMenu())
|
||||||
}
|
}
|
||||||
|
@ -445,7 +638,7 @@ const renderSuffix = ({ option }) => {
|
||||||
const nodeProps = ({ option }) => {
|
const nodeProps = ({ option }) => {
|
||||||
return {
|
return {
|
||||||
onDblclick: () => {
|
onDblclick: () => {
|
||||||
if (props.loading) {
|
if (loading.value) {
|
||||||
console.warn('TODO: alert to ignore double click when loading')
|
console.warn('TODO: alert to ignore double click when loading')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -472,29 +665,37 @@ const nodeProps = ({ option }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onLoadTree = async (node) => {
|
||||||
|
switch (node.type) {
|
||||||
|
case ConnectionType.RedisDB:
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
await browserStore.openDatabase(props.server, node.db)
|
||||||
|
} catch (e) {
|
||||||
|
$message.error(e.message)
|
||||||
|
node.isLeaf = undefined
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
break
|
||||||
|
// case ConnectionType.RedisKey:
|
||||||
|
// console.warn('load redis key', node.redisKey)
|
||||||
|
// node.keys = sumBy(node.children, 'keys')
|
||||||
|
// break
|
||||||
|
// case ConnectionType.RedisValue:
|
||||||
|
// node.keys = 1
|
||||||
|
// break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleOutsideContextMenu = () => {
|
const handleOutsideContextMenu = () => {
|
||||||
contextMenuParam.show = false
|
contextMenuParam.show = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// the NTree node may get incorrect height after change data
|
|
||||||
// add key property to force refresh the component and then everything back to normal
|
|
||||||
const treeKey = ref(0)
|
|
||||||
defineExpose({
|
|
||||||
handleSelectContextMenu,
|
|
||||||
resetExpandKey,
|
|
||||||
refreshTree: () => {
|
|
||||||
treeKey.value = Date.now()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :style="{ backgroundColor }" class="flex-box-v browser-tree-wrapper">
|
<div :style="{ backgroundColor }" class="browser-tree-wrapper">
|
||||||
<n-spin v-if="props.loading" class="fill-height" />
|
|
||||||
<n-empty v-else-if="!props.loading && isEmpty(data)" class="empty-content" />
|
|
||||||
<n-tree
|
<n-tree
|
||||||
v-show="!props.loading && !isEmpty(data)"
|
|
||||||
:key="treeKey"
|
|
||||||
:animated="false"
|
:animated="false"
|
||||||
:block-line="true"
|
:block-line="true"
|
||||||
:block-node="true"
|
:block-node="true"
|
||||||
|
@ -502,16 +703,14 @@ defineExpose({
|
||||||
:data="data"
|
:data="data"
|
||||||
:expand-on-click="false"
|
:expand-on-click="false"
|
||||||
:expanded-keys="expandedKeys"
|
:expanded-keys="expandedKeys"
|
||||||
:filter="(pattern, node) => includes(node.redisKey, pattern)"
|
|
||||||
:node-props="nodeProps"
|
:node-props="nodeProps"
|
||||||
:pattern="props.pattern"
|
|
||||||
:render-label="renderLabel"
|
:render-label="renderLabel"
|
||||||
:render-prefix="renderPrefix"
|
:render-prefix="renderPrefix"
|
||||||
:render-suffix="renderSuffix"
|
:render-suffix="renderSuffix"
|
||||||
:selected-keys="selectedKeys"
|
:selected-keys="selectedKeys"
|
||||||
:show-irrelevant-nodes="false"
|
|
||||||
class="fill-height"
|
class="fill-height"
|
||||||
virtual-scroll
|
virtual-scroll
|
||||||
|
@load="onLoadTree"
|
||||||
@update:selected-keys="onUpdateSelectedKeys"
|
@update:selected-keys="onUpdateSelectedKeys"
|
||||||
@update:expanded-keys="onUpdateExpanded" />
|
@update:expanded-keys="onUpdateExpanded" />
|
||||||
<n-dropdown
|
<n-dropdown
|
||||||
|
@ -528,8 +727,6 @@ defineExpose({
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '@/styles/content';
|
|
||||||
|
|
||||||
.browser-tree-wrapper {
|
.browser-tree-wrapper {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
|
@ -18,16 +18,14 @@ const filterPattern = ref('')
|
||||||
<connection-tree :filter-pattern="filterPattern" />
|
<connection-tree :filter-pattern="filterPattern" />
|
||||||
|
|
||||||
<!-- bottom function bar -->
|
<!-- bottom function bar -->
|
||||||
<div class="nav-pane-bottom nav-pane-func flex-box-h">
|
<div class="nav-pane-bottom flex-box-h">
|
||||||
<icon-button
|
<icon-button
|
||||||
:button-class="['nav-pane-func-btn']"
|
|
||||||
:icon="AddLink"
|
:icon="AddLink"
|
||||||
size="20"
|
size="20"
|
||||||
stroke-width="4"
|
stroke-width="4"
|
||||||
t-tooltip="interface.new_conn"
|
t-tooltip="interface.new_conn"
|
||||||
@click="dialogStore.openNewDialog()" />
|
@click="dialogStore.openNewDialog()" />
|
||||||
<icon-button
|
<icon-button
|
||||||
:button-class="['nav-pane-func-btn']"
|
|
||||||
:icon="AddGroup"
|
:icon="AddGroup"
|
||||||
size="20"
|
size="20"
|
||||||
stroke-width="4"
|
stroke-width="4"
|
||||||
|
|
|
@ -20,11 +20,9 @@ import { hexGammaCorrection, parseHexColor, toHexColor } from '@/utils/rgb.js'
|
||||||
import IconButton from '@/components/common/IconButton.vue'
|
import IconButton from '@/components/common/IconButton.vue'
|
||||||
import usePreferencesStore from 'stores/preferences.js'
|
import usePreferencesStore from 'stores/preferences.js'
|
||||||
import useBrowserStore from 'stores/browser.js'
|
import useBrowserStore from 'stores/browser.js'
|
||||||
import { useRender } from '@/utils/render.js'
|
|
||||||
|
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const render = useRender()
|
|
||||||
const connectingServer = ref('')
|
const connectingServer = ref('')
|
||||||
const connectionStore = useConnectionStore()
|
const connectionStore = useConnectionStore()
|
||||||
const browserStore = useBrowserStore()
|
const browserStore = useBrowserStore()
|
||||||
|
@ -61,12 +59,12 @@ const menuOptions = {
|
||||||
{
|
{
|
||||||
key: 'group_rename',
|
key: 'group_rename',
|
||||||
label: i18n.t('interface.rename_conn_group'),
|
label: i18n.t('interface.rename_conn_group'),
|
||||||
icon: render.renderIcon(Edit),
|
icon: renderIcon(Edit),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'group_delete',
|
key: 'group_delete',
|
||||||
label: i18n.t('interface.remove_conn_group'),
|
label: i18n.t('interface.remove_conn_group'),
|
||||||
icon: render.renderIcon(Delete),
|
icon: renderIcon(Delete),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[ConnectionType.Server]: ({ name }) => {
|
[ConnectionType.Server]: ({ name }) => {
|
||||||
|
@ -76,17 +74,17 @@ const menuOptions = {
|
||||||
{
|
{
|
||||||
key: 'server_close',
|
key: 'server_close',
|
||||||
label: i18n.t('interface.disconnect'),
|
label: i18n.t('interface.disconnect'),
|
||||||
icon: render.renderIcon(Unlink),
|
icon: renderIcon(Unlink),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'server_edit',
|
key: 'server_edit',
|
||||||
label: i18n.t('interface.edit_conn'),
|
label: i18n.t('interface.edit_conn'),
|
||||||
icon: render.renderIcon(Config),
|
icon: renderIcon(Config),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'server_dup',
|
key: 'server_dup',
|
||||||
label: i18n.t('interface.dup_conn'),
|
label: i18n.t('interface.dup_conn'),
|
||||||
icon: render.renderIcon(CopyLink),
|
icon: renderIcon(CopyLink),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'divider',
|
type: 'divider',
|
||||||
|
@ -95,7 +93,7 @@ const menuOptions = {
|
||||||
{
|
{
|
||||||
key: 'server_remove',
|
key: 'server_remove',
|
||||||
label: i18n.t('interface.remove_conn'),
|
label: i18n.t('interface.remove_conn'),
|
||||||
icon: render.renderIcon(Delete),
|
icon: renderIcon(Delete),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
|
@ -103,17 +101,17 @@ const menuOptions = {
|
||||||
{
|
{
|
||||||
key: 'server_open',
|
key: 'server_open',
|
||||||
label: i18n.t('interface.open_connection'),
|
label: i18n.t('interface.open_connection'),
|
||||||
icon: render.renderIcon(Connect),
|
icon: renderIcon(Connect),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'server_edit',
|
key: 'server_edit',
|
||||||
label: i18n.t('interface.edit_conn'),
|
label: i18n.t('interface.edit_conn'),
|
||||||
icon: render.renderIcon(Config),
|
icon: renderIcon(Config),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'server_dup',
|
key: 'server_dup',
|
||||||
label: i18n.t('interface.dup_conn'),
|
label: i18n.t('interface.dup_conn'),
|
||||||
icon: render.renderIcon(CopyLink),
|
icon: renderIcon(CopyLink),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'divider',
|
type: 'divider',
|
||||||
|
@ -122,7 +120,7 @@ const menuOptions = {
|
||||||
{
|
{
|
||||||
key: 'server_remove',
|
key: 'server_remove',
|
||||||
label: i18n.t('interface.remove_conn'),
|
label: i18n.t('interface.remove_conn'),
|
||||||
icon: render.renderIcon(Delete),
|
icon: renderIcon(Delete),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,10 +13,8 @@ import usePreferencesStore from 'stores/preferences.js'
|
||||||
import Record from '@/components/icons/Record.vue'
|
import Record from '@/components/icons/Record.vue'
|
||||||
import { extraTheme } from '@/utils/extra_theme.js'
|
import { extraTheme } from '@/utils/extra_theme.js'
|
||||||
import useBrowserStore from 'stores/browser.js'
|
import useBrowserStore from 'stores/browser.js'
|
||||||
import { useRender } from '@/utils/render.js'
|
|
||||||
|
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
const render = useRender()
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
value: {
|
value: {
|
||||||
|
@ -32,6 +30,9 @@ const props = defineProps({
|
||||||
const emit = defineEmits(['update:value'])
|
const emit = defineEmits(['update:value'])
|
||||||
|
|
||||||
const iconSize = computed(() => Math.floor(props.width * 0.45))
|
const iconSize = computed(() => Math.floor(props.width * 0.45))
|
||||||
|
const renderIcon = (icon) => {
|
||||||
|
return () => h(NIcon, null, { default: () => h(icon, { strokeWidth: 3 }) })
|
||||||
|
}
|
||||||
|
|
||||||
const browserStore = useBrowserStore()
|
const browserStore = useBrowserStore()
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
|
@ -61,12 +62,12 @@ const preferencesOptions = computed(() => {
|
||||||
{
|
{
|
||||||
label: i18n.t('menu.preferences'),
|
label: i18n.t('menu.preferences'),
|
||||||
key: 'preferences',
|
key: 'preferences',
|
||||||
icon: render.renderIcon(Config, { strokeWidth: 3 }),
|
icon: renderIcon(Config),
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// label: i18n.t('menu.help'),
|
// label: i18n.t('menu.help'),
|
||||||
// key: 'help',
|
// key: 'help',
|
||||||
// icon: render.renderIcon(Help, { strokeWidth: 3 }),
|
// icon: renderIcon(Help),
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
label: i18n.t('menu.check_update'),
|
label: i18n.t('menu.check_update'),
|
||||||
|
@ -121,20 +122,22 @@ const exThemeVars = computed(() => {
|
||||||
}"
|
}"
|
||||||
class="flex-box-v">
|
class="flex-box-v">
|
||||||
<div class="ribbon-wrapper flex-box-v">
|
<div class="ribbon-wrapper flex-box-v">
|
||||||
<n-tooltip v-for="(m, i) in menuOptions" :key="i" :delay="2" :show-arrow="false" placement="right">
|
<div
|
||||||
<template #trigger>
|
v-for="(m, i) in menuOptions"
|
||||||
<div
|
v-show="m.show !== false"
|
||||||
v-show="m.show !== false"
|
:key="i"
|
||||||
:class="{ 'ribbon-item-active': props.value === m.key }"
|
:class="{ 'ribbon-item-active': props.value === m.key }"
|
||||||
class="ribbon-item clickable"
|
class="ribbon-item clickable"
|
||||||
@click="emit('update:value', m.key)">
|
@click="emit('update:value', m.key)">
|
||||||
|
<n-tooltip :delay="2" :show-arrow="false" placement="right">
|
||||||
|
<template #trigger>
|
||||||
<n-icon :size="iconSize">
|
<n-icon :size="iconSize">
|
||||||
<component :is="m.icon" :stroke-width="3.5" />
|
<component :is="m.icon" :stroke-width="3.5" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
{{ m.label }}
|
||||||
{{ m.label }}
|
</n-tooltip>
|
||||||
</n-tooltip>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-item-expand"></div>
|
<div class="flex-item-expand"></div>
|
||||||
<div class="nav-menu-item flex-box-v">
|
<div class="nav-menu-item flex-box-v">
|
||||||
|
|
|
@ -255,7 +255,7 @@
|
||||||
"filter": {
|
"filter": {
|
||||||
"set_key_filter": "Set Key Filter",
|
"set_key_filter": "Set Key Filter",
|
||||||
"filter_pattern": "Pattern",
|
"filter_pattern": "Pattern",
|
||||||
"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."
|
"filter_pattern_tip": "prefix_*: Matches key names starting with \"prefix_\".\n*_suffix: Matches key names ending with \"_suffix\".\n*pattern*: Matches key names containing \"pattern\".\nprefix_??: Matches key names starting with \"prefix_\" followed by any two characters.\n*abc*: Matches key names containing \"abc\" at any position."
|
||||||
},
|
},
|
||||||
"ttl": {
|
"ttl": {
|
||||||
"title": "Set Key TTL"
|
"title": "Set Key TTL"
|
||||||
|
|
|
@ -246,7 +246,8 @@
|
||||||
},
|
},
|
||||||
"filter": {
|
"filter": {
|
||||||
"set_key_filter": "Definir Filtro de Chave",
|
"set_key_filter": "Definir Filtro de Chave",
|
||||||
"filter_pattern": "Padrão"
|
"filter_pattern": "Padrão",
|
||||||
|
"filter_pattern_tip": "prefixo_*: Corresponde a nomes de chaves que começam com \"prefixo_\".\n*_sufixo: Corresponde a nomes de chaves que terminam com \"_sufixo\".\n*padrão*: Corresponde a nomes de chaves que contêm \"padrão\".\nprefixo_??: Corresponde a nomes de chaves que começam com \"prefixo_\" seguido por dois caracteres.\n*abc*: Corresponde a nomes de chaves que contêm \"abc\" em qualquer posição."
|
||||||
},
|
},
|
||||||
"ttl": {
|
"ttl": {
|
||||||
"title": "Definir TTL da Chave"
|
"title": "Definir TTL da Chave"
|
||||||
|
|
|
@ -254,7 +254,7 @@
|
||||||
"filter": {
|
"filter": {
|
||||||
"set_key_filter": "设置键过滤器",
|
"set_key_filter": "设置键过滤器",
|
||||||
"filter_pattern": "过滤表达式",
|
"filter_pattern": "过滤表达式",
|
||||||
"filter_pattern_tip": "*:匹配零个或多个字符。例如:\"key*\"匹配到以\"key\"开头的所有键\n?:匹配单个字符。例如:\"key?\"匹配\"key1\"、\"key2\"\n[ ]:匹配指定范围内的单个字符。例如:\"key[1-3]\"可以匹配类似于 \"key1\"、\"key2\"、\"key3\" 的键\n\\:转义字符。如果想要匹配 *、?、[、或],需要使用反斜杠\"\\\"进行转义"
|
"filter_pattern_tip": "prefix_*:匹配以\"prefix_\"开头的键名\n*_suffix:匹配以\"_suffix\"结尾的键名\n*pattern*:匹配包含\"pattern\"的键名\nprefix_??:匹配以\"prefix_\"开头后跟两个任意字符的键名\n*abc*:匹配包含\"abc\"的任意位置的键名"
|
||||||
},
|
},
|
||||||
"ttl": {
|
"ttl": {
|
||||||
"title": "设置键存活时间"
|
"title": "设置键存活时间"
|
||||||
|
|
|
@ -50,9 +50,9 @@ import { decodeRedisKey, nativeRedisKey } from '@/utils/key_convert.js'
|
||||||
import { BrowserTabType } from '@/consts/browser_tab_type.js'
|
import { BrowserTabType } from '@/consts/browser_tab_type.js'
|
||||||
import { KeyViewType } from '@/consts/key_view_type.js'
|
import { KeyViewType } from '@/consts/key_view_type.js'
|
||||||
import { ConnectionType } from '@/consts/connection_type.js'
|
import { ConnectionType } from '@/consts/connection_type.js'
|
||||||
|
import { types } from '@/consts/support_redis_type.js'
|
||||||
import useConnectionStore from 'stores/connections.js'
|
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'
|
|
||||||
|
|
||||||
const useBrowserStore = defineStore('browser', {
|
const useBrowserStore = defineStore('browser', {
|
||||||
/**
|
/**
|
||||||
|
@ -70,18 +70,8 @@ const useBrowserStore = defineStore('browser', {
|
||||||
* @property {boolean} [opened] - redis db is opened, type == ConnectionType.RedisDB only
|
* @property {boolean} [opened] - redis db is opened, type == ConnectionType.RedisDB only
|
||||||
* @property {boolean} [expanded] - current node is expanded
|
* @property {boolean} [expanded] - current node is expanded
|
||||||
* @property {DatabaseItem[]} [children]
|
* @property {DatabaseItem[]} [children]
|
||||||
*/
|
* @property {boolean} [loading] - indicated that is loading children now
|
||||||
|
* @property {boolean} [fullLoaded] - indicated that all children already loaded
|
||||||
/**
|
|
||||||
* @typedef {Object} FilterItem
|
|
||||||
* @property {string} pattern key pattern filter
|
|
||||||
* @property {string} type type filter
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} LoadingState
|
|
||||||
* @property {string} loading indicated that is loading children now
|
|
||||||
* @property {string} fullLoaded indicated that all children already loaded
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -95,7 +85,8 @@ const useBrowserStore = defineStore('browser', {
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} BrowserState
|
* @typedef {Object} BrowserState
|
||||||
* @property {Object} serverStats
|
* @property {Object} serverStats
|
||||||
* @property {Object.<string, FilterItem>} filter
|
* @property {Object.<string, string>} keyFilter key is 'server#db', 'server#-1' stores default filter pattern
|
||||||
|
* @property {Object.<string, string>} typeFilter key is 'server#db'
|
||||||
* @property {Object.<string, KeyViewType>} viewType
|
* @property {Object.<string, KeyViewType>} viewType
|
||||||
* @property {Object.<string, DatabaseItem[]>} databases
|
* @property {Object.<string, DatabaseItem[]>} databases
|
||||||
* @property {Object.<string, Map<string, DatabaseItem>>} nodeMap key format likes 'server#db', children key format likes 'key#type'
|
* @property {Object.<string, Map<string, DatabaseItem>>} nodeMap key format likes 'server#db', children key format likes 'key#type'
|
||||||
|
@ -107,21 +98,17 @@ const useBrowserStore = defineStore('browser', {
|
||||||
*/
|
*/
|
||||||
state: () => ({
|
state: () => ({
|
||||||
serverStats: {}, // current server status info
|
serverStats: {}, // current server status info
|
||||||
filter: {}, // all filters in opened connections map by server and FilterItem
|
keyFilter: {}, // all key filters in opened connections group by 'server+db'
|
||||||
loadingState: {}, // all loading state in opened connections map by server and LoadingState
|
typeFilter: {}, // all key type filters in opened connections group by 'server+db'
|
||||||
viewType: {}, // view type selection for all opened connections group by 'server'
|
viewType: {}, // view type selection for all opened connections group by 'server'
|
||||||
databases: {}, // all databases in opened connections group by 'server name'
|
databases: {}, // all databases in opened connections group by 'server name'
|
||||||
nodeMap: {}, // all nodes in opened connections group by 'server#db' and 'type/key'
|
nodeMap: {}, // all nodes in opened connections group by 'server#db' and 'type/key'
|
||||||
keySet: {}, // all keys set in opened connections group by 'server#db
|
keySet: {}, // all keys set in opened connections group by 'server#db
|
||||||
openedDB: {}, // opened database map by server and database index
|
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
anyConnectionOpened() {
|
anyConnectionOpened() {
|
||||||
return !isEmpty(this.databases)
|
return !isEmpty(this.databases)
|
||||||
},
|
},
|
||||||
selectedDatabases() {
|
|
||||||
return this.openedDB || {}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
/**
|
/**
|
||||||
|
@ -152,22 +139,13 @@ const useBrowserStore = defineStore('browser', {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get database info list
|
* get database by server name and index
|
||||||
* @param server
|
* @param {string} connName
|
||||||
* @return {DatabaseItem[]}
|
|
||||||
*/
|
|
||||||
getDBList(server) {
|
|
||||||
return this.databases[server] || []
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get database by server name and database index
|
|
||||||
* @param {string} server
|
|
||||||
* @param {number} db
|
* @param {number} db
|
||||||
* @return {DatabaseItem|null}
|
* @return {DatabaseItem|null}
|
||||||
*/
|
*/
|
||||||
getDatabase(server, db) {
|
getDatabase(connName, db) {
|
||||||
const dbs = this.databases[server]
|
const dbs = this.databases[connName]
|
||||||
if (dbs != null) {
|
if (dbs != null) {
|
||||||
const selDB = find(dbs, (item) => item.db === db)
|
const selDB = find(dbs, (item) => item.db === db)
|
||||||
if (selDB != null) {
|
if (selDB != null) {
|
||||||
|
@ -178,24 +156,17 @@ const useBrowserStore = defineStore('browser', {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get current selection database by server
|
* get full loaded status of database
|
||||||
* @param server
|
* @param connName
|
||||||
* @return {number}
|
* @param db
|
||||||
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
getSelectedDB(server) {
|
isFullLoaded(connName, db) {
|
||||||
return this.selectedDatabases[server] || 0
|
const selDB = this.getDatabase(connName, db)
|
||||||
},
|
if (selDB != null) {
|
||||||
|
return selDB.fullLoaded === true
|
||||||
/**
|
}
|
||||||
* get key list in current database
|
return false
|
||||||
* @param server
|
|
||||||
* @return {DatabaseItem[]}
|
|
||||||
*/
|
|
||||||
getKeyList(server) {
|
|
||||||
const db = this.getSelectedDB(server)
|
|
||||||
const dbNodes = this.databases[server]
|
|
||||||
const node = find(dbNodes, (n) => n.db === db)
|
|
||||||
return node.children
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -277,7 +248,6 @@ const useBrowserStore = defineStore('browser', {
|
||||||
}
|
}
|
||||||
this.databases[name] = dbs
|
this.databases[name] = dbs
|
||||||
this.viewType[name] = view
|
this.viewType[name] = view
|
||||||
this.openedDB[name] = get(dbs, '0.db', 0)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -295,11 +265,12 @@ const useBrowserStore = defineStore('browser', {
|
||||||
const dbs = this.databases[name]
|
const dbs = this.databases[name]
|
||||||
if (!isEmpty(dbs)) {
|
if (!isEmpty(dbs)) {
|
||||||
for (const db of dbs) {
|
for (const db of dbs) {
|
||||||
|
this.removeKeyFilter(name, db.db)
|
||||||
this._getNodeMap(name, db.db).clear()
|
this._getNodeMap(name, db.db).clear()
|
||||||
this._getKeySet(name, db.db).clear()
|
this._getKeySet(name, db.db).clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete this.filter[name]
|
this.removeKeyFilter(name, -1)
|
||||||
delete this.databases[name]
|
delete this.databases[name]
|
||||||
delete this.serverStats[name]
|
delete this.serverStats[name]
|
||||||
|
|
||||||
|
@ -310,32 +281,32 @@ const useBrowserStore = defineStore('browser', {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* open database and load all keys
|
* open database and load all keys
|
||||||
* @param server
|
* @param connName
|
||||||
* @param db
|
* @param db
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async openDatabase(server, db) {
|
async openDatabase(connName, db) {
|
||||||
const { match: filterPattern, type: filterType } = this.getKeyFilter(server)
|
const { match: filterPattern, type: filterType } = this.getKeyFilter(connName, db)
|
||||||
const { data, success, msg } = await OpenDatabase(server, db, filterPattern, filterType)
|
const { data, success, msg } = await OpenDatabase(connName, db, filterPattern, filterType)
|
||||||
if (!success) {
|
if (!success) {
|
||||||
throw new Error(msg)
|
throw new Error(msg)
|
||||||
}
|
}
|
||||||
const { keys = [], end = false, maxKeys = 0 } = data
|
const { keys = [], end = false, maxKeys = 0 } = data
|
||||||
const selDB = this.getDatabase(server, db)
|
const selDB = this.getDatabase(connName, db)
|
||||||
if (selDB == null) {
|
if (selDB == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
selDB.opened = true
|
selDB.opened = true
|
||||||
|
selDB.fullLoaded = end
|
||||||
selDB.maxKeys = maxKeys
|
selDB.maxKeys = maxKeys
|
||||||
set(this.loadingState, 'fullLoaded', end)
|
|
||||||
if (isEmpty(keys)) {
|
if (isEmpty(keys)) {
|
||||||
selDB.children = []
|
selDB.children = []
|
||||||
} else {
|
} else {
|
||||||
// append db node to current connection's children
|
// append db node to current connection's children
|
||||||
this._addKeyNodes(server, db, keys)
|
this._addKeyNodes(connName, db, keys)
|
||||||
}
|
}
|
||||||
this._tidyNode(server, db)
|
this._tidyNode(connName, db)
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -354,27 +325,24 @@ const useBrowserStore = defineStore('browser', {
|
||||||
|
|
||||||
this._getNodeMap(connName, db).clear()
|
this._getNodeMap(connName, db).clear()
|
||||||
this._getKeySet(connName, db).clear()
|
this._getKeySet(connName, db).clear()
|
||||||
delete this.filter[connName]
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* close database
|
* close database
|
||||||
* @param server
|
* @param connName
|
||||||
* @param db
|
* @param db
|
||||||
*/
|
*/
|
||||||
closeDatabase(server, db) {
|
closeDatabase(connName, db) {
|
||||||
const selDB = this.getDatabase(server, db)
|
const selDB = this.getDatabase(connName, db)
|
||||||
if (selDB == null) {
|
if (selDB == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
delete selDB.children
|
delete selDB.children
|
||||||
selDB.isLeaf = false
|
selDB.isLeaf = false
|
||||||
selDB.opened = false
|
selDB.opened = false
|
||||||
selDB.keys = 0
|
|
||||||
|
|
||||||
this._getNodeMap(server, db).clear()
|
this._getNodeMap(connName, db).clear()
|
||||||
this._getKeySet(server, db).clear()
|
this._getKeySet(connName, db).clear()
|
||||||
delete this.filter[server]
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -579,7 +547,7 @@ const useBrowserStore = defineStore('browser', {
|
||||||
let match = prefix
|
let match = prefix
|
||||||
if (isEmpty(match)) {
|
if (isEmpty(match)) {
|
||||||
match = '*'
|
match = '*'
|
||||||
} else if (!isRedisGlob(match)) {
|
} else {
|
||||||
const separator = this._getSeparator(connName)
|
const separator = this._getSeparator(connName)
|
||||||
if (!endsWith(prefix, separator + '*')) {
|
if (!endsWith(prefix, separator + '*')) {
|
||||||
match = prefix + separator + '*'
|
match = prefix + separator + '*'
|
||||||
|
@ -595,7 +563,7 @@ const useBrowserStore = defineStore('browser', {
|
||||||
* @return {Promise<boolean>}
|
* @return {Promise<boolean>}
|
||||||
*/
|
*/
|
||||||
async loadMoreKeys(connName, db) {
|
async loadMoreKeys(connName, db) {
|
||||||
const { match, type: keyType } = this.getKeyFilter(connName)
|
const { match, type: keyType } = this.getKeyFilter(connName, db)
|
||||||
const { keys, maxKeys, end } = await this._loadKeys(connName, db, match, keyType, false)
|
const { keys, maxKeys, end } = await this._loadKeys(connName, db, match, keyType, false)
|
||||||
this._setDBMaxKeys(connName, db, maxKeys)
|
this._setDBMaxKeys(connName, db, maxKeys)
|
||||||
// remove current keys below prefix
|
// remove current keys below prefix
|
||||||
|
@ -1968,32 +1936,41 @@ const useBrowserStore = defineStore('browser', {
|
||||||
/**
|
/**
|
||||||
* get key filter pattern and filter type
|
* get key filter pattern and filter type
|
||||||
* @param {string} server
|
* @param {string} server
|
||||||
|
* @param {number} db
|
||||||
* @returns {{match: string, type: string}}
|
* @returns {{match: string, type: string}}
|
||||||
*/
|
*/
|
||||||
getKeyFilter(server) {
|
getKeyFilter(server, db) {
|
||||||
let { pattern = '', type = '' } = this.filter[server] || {}
|
let match, type
|
||||||
if (isEmpty(pattern)) {
|
const key = `${server}#${db}`
|
||||||
// no custom match pattern, use default
|
if (!this.keyFilter.hasOwnProperty(key)) {
|
||||||
|
// get default key filter from connection profile
|
||||||
const conn = useConnectionStore()
|
const conn = useConnectionStore()
|
||||||
pattern = conn.getDefaultKeyFilter(server)
|
match = conn.getDefaultKeyFilter(server)
|
||||||
|
} else {
|
||||||
|
match = this.keyFilter[key] || '*'
|
||||||
}
|
}
|
||||||
|
type = this.typeFilter[`${server}#${db}`] || ''
|
||||||
return {
|
return {
|
||||||
match: pattern,
|
match,
|
||||||
type: toUpper(type),
|
type: toUpper(type),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* set key filter
|
||||||
* @param {string} server
|
* @param {string} server
|
||||||
|
* @param {number} db
|
||||||
|
* @param {string} pattern
|
||||||
* @param {string} [type]
|
* @param {string} [type]
|
||||||
* @param {string} [pattern]
|
|
||||||
*/
|
*/
|
||||||
setKeyFilter(server, { type, pattern }) {
|
setKeyFilter(server, db, pattern, type) {
|
||||||
const filter = this.filter[server] || {}
|
this.keyFilter[`${server}#${db}`] = pattern || '*'
|
||||||
filter.type = type === null ? filter.type : type
|
this.typeFilter[`${server}#${db}`] = types[toUpper(type)] || ''
|
||||||
filter.pattern = type === null ? filter.pattern : pattern
|
},
|
||||||
this.filter[server] = filter
|
|
||||||
|
removeKeyFilter(server, db) {
|
||||||
|
this.keyFilter[`${server}#${db}`] = '*'
|
||||||
|
delete this.typeFilter[`${server}#${db}`]
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -235,7 +235,7 @@ const useTabStore = defineStore('tab', {
|
||||||
tabData.value = assign(value, tabData.value || {})
|
tabData.value = assign(value, tabData.value || {})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tabData.value = value || []
|
tabData.value = value
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -25,14 +25,6 @@ body {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin bottom-shadow($transparent) {
|
|
||||||
box-shadow: 0 5px 5px -5px rgba(0, 0, 0, $transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin top-shadow($transparent) {
|
|
||||||
box-shadow: 0 -5px 5px -5px rgba(0, 0, 0, $transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
@ -69,9 +61,13 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.ellipsis {
|
.ellipsis {
|
||||||
white-space: nowrap;
|
white-space: nowrap; /* 禁止文本换行 */
|
||||||
overflow: hidden;
|
overflow: hidden; /* 隐藏超出容器的文本 */
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis; /* 使用省略号表示被截断的文本 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit-item {
|
||||||
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fill-height {
|
.fill-height {
|
||||||
|
@ -110,7 +106,6 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.value-footer {
|
.value-footer {
|
||||||
@include top-shadow(0.1);
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
padding: 3px 10px 3px 10px;
|
padding: 3px 10px 3px 10px;
|
||||||
|
@ -135,18 +130,12 @@ body {
|
||||||
.nav-pane-container {
|
.nav-pane-container {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.nav-pane-func {
|
.nav-pane-bottom {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
gap: 8px;
|
||||||
gap: 3px;
|
padding: 3px 10px 3px 10px;
|
||||||
padding: 3px 8px;
|
|
||||||
min-height: 30px;
|
min-height: 30px;
|
||||||
|
//border-top: v-bind('themeVars.borderColor') 1px solid;
|
||||||
.nav-pane-func-btn {
|
|
||||||
padding: 3px;
|
|
||||||
border-radius: 3px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
import { includes, isEmpty } from 'lodash'
|
|
||||||
|
|
||||||
const REDIS_GLOB_CHAR = ['?', '*', '[', ']', '{', '}']
|
|
||||||
export const isRedisGlob = (str) => {
|
|
||||||
if (!isEmpty(str)) {
|
|
||||||
for (const c of REDIS_GLOB_CHAR) {
|
|
||||||
if (includes(str, c)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
import { h } from 'vue'
|
|
||||||
import { NIcon } from 'naive-ui'
|
|
||||||
|
|
||||||
export function useRender() {
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string|Object} icon
|
|
||||||
* @param {{}} [props]
|
|
||||||
* @return {*}
|
|
||||||
*/
|
|
||||||
renderIcon: (icon, props = {}) => {
|
|
||||||
return () => {
|
|
||||||
return h(NIcon, null, {
|
|
||||||
default: () => h(icon, props),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue