Refactor server and database save structure, support multiple server detail tab
This commit is contained in:
parent
79784fd109
commit
dadde8d090
|
@ -1,21 +1,25 @@
|
|||
<script setup>
|
||||
import ContentPane from './components/ContentPane.vue'
|
||||
import NavigationPane from './components/NavigationPane.vue'
|
||||
import ContentPane from './components/content/ContentPane.vue'
|
||||
import DatabasePane from './components/sidebar/DatabasePane.vue'
|
||||
import { computed, nextTick, onMounted, provide, reactive, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { GetPreferences } from '../wailsjs/go/storage/PreferencesStorage.js'
|
||||
import { get } from 'lodash'
|
||||
import { useThemeVars } from 'naive-ui'
|
||||
import NavMenu from './components/NavMenu.vue'
|
||||
import ConnectionPane from './components/sidebar/ConnectionPane.vue'
|
||||
import ContentServerPane from './components/content/ContentServerPane.vue'
|
||||
import useTabStore from './stores/tab.js'
|
||||
|
||||
const themeVars = useThemeVars()
|
||||
|
||||
const data = reactive({
|
||||
asideWith: 300,
|
||||
navMenuWidth: 60,
|
||||
hoverResize: false,
|
||||
resizing: false,
|
||||
})
|
||||
|
||||
const tabStore = useTabStore()
|
||||
const preferences = ref({})
|
||||
provide('preferences', preferences)
|
||||
const i18n = useI18n()
|
||||
|
@ -34,7 +38,7 @@ const getFontSize = computed(() => {
|
|||
|
||||
const handleResize = (evt) => {
|
||||
if (data.resizing) {
|
||||
data.asideWith = Math.max(evt.clientX, 300)
|
||||
tabStore.asideWidth = Math.max(evt.clientX - data.navMenuWidth, 300)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,7 +56,7 @@ const startResize = () => {
|
|||
}
|
||||
|
||||
const asideWidthVal = computed(() => {
|
||||
return data.asideWith + 'px'
|
||||
return tabStore.asideWidth + 'px'
|
||||
})
|
||||
|
||||
const dragging = computed(() => {
|
||||
|
@ -62,10 +66,17 @@ const dragging = computed(() => {
|
|||
|
||||
<template>
|
||||
<!-- app content-->
|
||||
<div id="app-container" :class="{ dragging: dragging }" class="flex-box-h">
|
||||
<nav-menu />
|
||||
<div id="app-container" :class="{ dragging }" class="flex-box-h">
|
||||
<nav-menu v-model:value="tabStore.nav" :width="data.navMenuWidth" />
|
||||
<!-- structure page-->
|
||||
<div v-show="tabStore.nav === 'structure'" class="flex-box-h flex-item-expand">
|
||||
<div id="app-side" :style="{ width: asideWidthVal }" class="flex-box-h flex-item">
|
||||
<navigation-pane class="flex-item-expand"></navigation-pane>
|
||||
<database-pane
|
||||
v-for="t in tabStore.tabs"
|
||||
v-show="get(tabStore.currentTab, 'name') === t.name"
|
||||
:key="t.name"
|
||||
class="flex-item-expand"
|
||||
/>
|
||||
<div
|
||||
:class="{
|
||||
'resize-divider-hover': data.hoverResize,
|
||||
|
@ -75,10 +86,32 @@ const dragging = computed(() => {
|
|||
@mousedown="startResize"
|
||||
@mouseout="data.hoverResize = false"
|
||||
@mouseover="data.hoverResize = true"
|
||||
></div>
|
||||
/>
|
||||
</div>
|
||||
<content-pane class="flex-item-expand" />
|
||||
</div>
|
||||
|
||||
<!-- server list page -->
|
||||
<div v-show="tabStore.nav === 'server'" class="flex-box-h flex-item-expand">
|
||||
<div id="app-side" :style="{ width: asideWidthVal }" class="flex-box-h flex-item">
|
||||
<connection-pane class="flex-item-expand" />
|
||||
<div
|
||||
:class="{
|
||||
'resize-divider-hover': data.hoverResize,
|
||||
'resize-divider-drag': data.resizing,
|
||||
}"
|
||||
class="resize-divider"
|
||||
@mousedown="startResize"
|
||||
@mouseout="data.hoverResize = false"
|
||||
@mouseover="data.hoverResize = true"
|
||||
/>
|
||||
</div>
|
||||
<content-server-pane class="flex-item-expand" />
|
||||
</div>
|
||||
|
||||
<!-- log page -->
|
||||
<div v-show="tabStore.nav === 'log'">display log</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -5,11 +5,11 @@ import Delete from './icons/Delete.vue'
|
|||
import Edit from './icons/Edit.vue'
|
||||
import Refresh from './icons/Refresh.vue'
|
||||
import Timer from './icons/Timer.vue'
|
||||
import RedisTypeTag from './RedisTypeTag.vue'
|
||||
import useConnectionStore from '../stores/connection.js'
|
||||
import RedisTypeTag from './common/RedisTypeTag.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import IconButton from './IconButton.vue'
|
||||
import IconButton from './common/IconButton.vue'
|
||||
import useConnectionStore from '../stores/connections.js'
|
||||
|
||||
const props = defineProps({
|
||||
server: String,
|
||||
|
|
|
@ -1,23 +1,38 @@
|
|||
<script setup>
|
||||
import { computed, h, ref } from 'vue'
|
||||
import { computed, h } from 'vue'
|
||||
import { NIcon, useThemeVars } from 'naive-ui'
|
||||
import ToggleDb from './icons/ToggleDb.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import ToggleServer from './icons/ToggleServer.vue'
|
||||
import IconButton from './IconButton.vue'
|
||||
import IconButton from './common/IconButton.vue'
|
||||
import Config from './icons/Config.vue'
|
||||
import useDialogStore from '../stores/dialog.js'
|
||||
import Github from './icons/Github.vue'
|
||||
import { BrowserOpenURL } from '../../wailsjs/runtime/runtime.js'
|
||||
import Log from './icons/Log.vue'
|
||||
import useConnectionStore from '../stores/connections.js'
|
||||
|
||||
const themeVars = useThemeVars()
|
||||
|
||||
const iconSize = 26
|
||||
const selectedMenu = ref('server')
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: String,
|
||||
default: 'server',
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 60,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:value'])
|
||||
|
||||
const iconSize = computed(() => Math.floor(props.width * 0.4))
|
||||
const renderIcon = (icon) => {
|
||||
return () => h(NIcon, null, { default: () => h(icon) })
|
||||
}
|
||||
|
||||
const connectionStore = useConnectionStore()
|
||||
const i18n = useI18n()
|
||||
const menuOptions = computed(() => {
|
||||
return [
|
||||
|
@ -25,13 +40,18 @@ const menuOptions = computed(() => {
|
|||
label: i18n.t('structure'),
|
||||
key: 'structure',
|
||||
icon: renderIcon(ToggleDb),
|
||||
show: true,
|
||||
show: connectionStore.anyConnectionOpened,
|
||||
},
|
||||
{
|
||||
label: i18n.t('server'),
|
||||
key: 'server',
|
||||
icon: renderIcon(ToggleServer),
|
||||
},
|
||||
{
|
||||
label: i18n.t('log'),
|
||||
key: 'log',
|
||||
icon: renderIcon(Log),
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
|
@ -74,12 +94,19 @@ const openGithub = () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div id="app-nav-menu" class="flex-box-v">
|
||||
<div
|
||||
id="app-nav-menu"
|
||||
:style="{
|
||||
width: props.width + 'px',
|
||||
}"
|
||||
class="flex-box-v"
|
||||
>
|
||||
<n-menu
|
||||
v-model:value="selectedMenu"
|
||||
:collapsed-width="props.width"
|
||||
:value="props.value"
|
||||
:collapsed="true"
|
||||
:collapsed-icon-size="iconSize"
|
||||
:collapsed-width="60"
|
||||
@update:value="(val) => emit('update:value', val)"
|
||||
:options="menuOptions"
|
||||
></n-menu>
|
||||
<div class="flex-item-expand"></div>
|
||||
|
@ -101,7 +128,7 @@ const openGithub = () => {
|
|||
|
||||
<style lang="scss">
|
||||
#app-nav-menu {
|
||||
width: 60px;
|
||||
//width: 60px;
|
||||
height: 100vh;
|
||||
border-right: var(--border-color) solid 1px;
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<script setup>
|
||||
import IconButton from './IconButton.vue'
|
||||
import Delete from './icons/Delete.vue'
|
||||
import Edit from './icons/Edit.vue'
|
||||
import Close from './icons/Close.vue'
|
||||
import Save from './icons/Save.vue'
|
||||
import Delete from '../icons/Delete.vue'
|
||||
import Edit from '../icons/Edit.vue'
|
||||
import Close from '../icons/Close.vue'
|
||||
import Save from '../icons/Save.vue'
|
||||
|
||||
const props = defineProps({
|
||||
bindKey: String,
|
|
@ -1,6 +1,6 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { types, validType } from '../consts/support_redis_type.js'
|
||||
import { types, validType } from '../../consts/support_redis_type.js'
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
|
@ -1,13 +1,16 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { types } from '../consts/support_redis_type.js'
|
||||
import ContentValueHash from './content_value/ContentValueHash.vue'
|
||||
import ContentValueList from './content_value/ContentValueList.vue'
|
||||
import ContentValueString from './content_value/ContentValueString.vue'
|
||||
import ContentValueSet from './content_value/ContentValueSet.vue'
|
||||
import ContentValueZset from './content_value/ContentValueZset.vue'
|
||||
import { types } from '../../consts/support_redis_type.js'
|
||||
import ContentValueHash from '../content_value/ContentValueHash.vue'
|
||||
import ContentValueList from '../content_value/ContentValueList.vue'
|
||||
import ContentValueString from '../content_value/ContentValueString.vue'
|
||||
import ContentValueSet from '../content_value/ContentValueSet.vue'
|
||||
import ContentValueZset from '../content_value/ContentValueZSet.vue'
|
||||
import { isEmpty, map, toUpper } from 'lodash'
|
||||
import useTabStore from '../stores/tab.js'
|
||||
import useTabStore from '../../stores/tab.js'
|
||||
import { useDialog } from 'naive-ui'
|
||||
import useConnectionStore from '../../stores/connections.js'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const valueComponents = {
|
||||
[types.STRING]: ContentValueString,
|
||||
|
@ -17,6 +20,8 @@ const valueComponents = {
|
|||
[types.ZSET]: ContentValueZset,
|
||||
}
|
||||
|
||||
const dialog = useDialog()
|
||||
const connectionStore = useConnectionStore()
|
||||
const tabStore = useTabStore()
|
||||
const tab = computed(() =>
|
||||
map(tabStore.tabs, (item) => ({
|
||||
|
@ -52,9 +57,24 @@ const onAddTab = () => {
|
|||
tabStore.newBlankTab()
|
||||
}
|
||||
|
||||
const i18n = useI18n()
|
||||
const onCloseTab = (tabIndex) => {
|
||||
tabStore.removeTab(tabIndex)
|
||||
console.log('TODO: close connection also')
|
||||
dialog.warning({
|
||||
title: i18n.t('close_confirm_title'),
|
||||
content: i18n.t('close_confirm'),
|
||||
positiveText: i18n.t('confirm'),
|
||||
negativeText: i18n.t('cancel'),
|
||||
closable: false,
|
||||
closeOnEsc: false,
|
||||
maskClosable: false,
|
||||
transformOrigin: 'center',
|
||||
onPositiveClick: () => {
|
||||
const removedTab = tabStore.removeTab(tabIndex)
|
||||
if (removedTab != null) {
|
||||
connectionStore.closeConnection(removedTab.name)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -63,8 +83,7 @@ const onCloseTab = (tabIndex) => {
|
|||
<!-- <content-tab :model-value="tab"></content-tab>-->
|
||||
<n-tabs
|
||||
v-model:value="tabStore.activatedIndex"
|
||||
:closable="tab.length > 1"
|
||||
addable
|
||||
:closable="true"
|
||||
size="small"
|
||||
type="card"
|
||||
@add="onAddTab"
|
||||
|
@ -75,10 +94,10 @@ const onCloseTab = (tabIndex) => {
|
|||
<n-ellipsis style="max-width: 150px">{{ t.label }}</n-ellipsis>
|
||||
</n-tab>
|
||||
</n-tabs>
|
||||
<!-- add loading status -->
|
||||
<!-- TODO: add loading status -->
|
||||
<component
|
||||
:is="valueComponents[tabContent.type]"
|
||||
v-if="tabContent != null && !isEmpty(tabContent.keyPath)"
|
||||
:is="valueComponents[tabContent.type]"
|
||||
:db="tabContent.db"
|
||||
:key-path="tabContent.keyPath"
|
||||
:name="tabContent.name"
|
||||
|
@ -92,20 +111,5 @@ const onCloseTab = (tabIndex) => {
|
|||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content-container {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background-color: var(--bg-color);
|
||||
padding-top: 2px;
|
||||
padding-bottom: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.empty-content {
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
}
|
||||
@import 'content';
|
||||
</style>
|
|
@ -0,0 +1,43 @@
|
|||
<script setup>
|
||||
import useDialog from '../../stores/dialog.js'
|
||||
import AddLink from '../icons/AddLink.vue'
|
||||
|
||||
const dialogStore = useDialog()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="content-container flex-box-v">
|
||||
<!-- TODO: replace icon to app icon -->
|
||||
<n-empty :description="$t('empty_server_content')">
|
||||
<template #extra>
|
||||
<n-button @click="dialogStore.openNewDialog()">
|
||||
<template #icon>
|
||||
<n-icon :component="AddLink" size="18" />
|
||||
</template>
|
||||
{{ $t('new_conn') }}
|
||||
</n-button>
|
||||
</template>
|
||||
</n-empty>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import 'content';
|
||||
|
||||
.content-container {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.color-preset-item {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 2px;
|
||||
border: white 3px solid;
|
||||
cursor: pointer;
|
||||
|
||||
&_selected,
|
||||
&:hover {
|
||||
border-color: #cdd0d6;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,9 +1,8 @@
|
|||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import useConnectionStore from '../stores/connection.js'
|
||||
import { throttle } from 'lodash'
|
||||
import { ConnectionType } from '../consts/connection_type.js'
|
||||
import Close from './icons/Close.vue'
|
||||
import { ref } from 'vue'
|
||||
import { ConnectionType } from '../../consts/connection_type.js'
|
||||
import Close from '../icons/Close.vue'
|
||||
import useConnectionStore from '../../stores/connections.js'
|
||||
|
||||
const emit = defineEmits(['switchTab', 'closeTab', 'update:modelValue'])
|
||||
|
||||
|
@ -31,7 +30,7 @@ const onCurrentSelectChange = ({ type, group = '', server = '', db = 0, key = ''
|
|||
// load and update content value
|
||||
}
|
||||
}
|
||||
watch(() => connectionStore.currentSelect, throttle(onCurrentSelectChange, 1000))
|
||||
// watch(() => databaseStore.currentSelect, throttle(onCurrentSelectChange, 1000))
|
||||
|
||||
const items = ref(props.modelValue)
|
||||
const selIndex = ref(props.selectedIndex)
|
|
@ -0,0 +1,16 @@
|
|||
.content-container {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background-color: var(--bg-color);
|
||||
padding-top: 2px;
|
||||
padding-bottom: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.empty-content {
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
}
|
|
@ -5,9 +5,9 @@ import ContentToolbar from '../ContentToolbar.vue'
|
|||
import AddLink from '../icons/AddLink.vue'
|
||||
import { NButton, NCode, NIcon, NInput, useMessage } from 'naive-ui'
|
||||
import { types, types as redisTypes } from '../../consts/support_redis_type.js'
|
||||
import EditableTableColumn from '../EditableTableColumn.vue'
|
||||
import useConnectionStore from '../../stores/connection.js'
|
||||
import EditableTableColumn from '../common/EditableTableColumn.vue'
|
||||
import useDialogStore from '../../stores/dialog.js'
|
||||
import useConnectionStore from '../../stores/connections.js'
|
||||
|
||||
const i18n = useI18n()
|
||||
|
||||
|
|
|
@ -6,9 +6,9 @@ import AddLink from '../icons/AddLink.vue'
|
|||
import { NButton, NCode, NIcon, NInput, useMessage } from 'naive-ui'
|
||||
import { size } from 'lodash'
|
||||
import { types, types as redisTypes } from '../../consts/support_redis_type.js'
|
||||
import EditableTableColumn from '../EditableTableColumn.vue'
|
||||
import useConnectionStore from '../../stores/connection.js'
|
||||
import EditableTableColumn from '../common/EditableTableColumn.vue'
|
||||
import useDialogStore from '../../stores/dialog.js'
|
||||
import useConnectionStore from '../../stores/connections.js'
|
||||
|
||||
const i18n = useI18n()
|
||||
|
||||
|
|
|
@ -5,10 +5,10 @@ import ContentToolbar from '../ContentToolbar.vue'
|
|||
import AddLink from '../icons/AddLink.vue'
|
||||
import { NButton, NCode, NIcon, NInput, useMessage } from 'naive-ui'
|
||||
import { size } from 'lodash'
|
||||
import useConnectionStore from '../../stores/connection.js'
|
||||
import useDialogStore from '../../stores/dialog.js'
|
||||
import { types, types as redisTypes } from '../../consts/support_redis_type.js'
|
||||
import EditableTableColumn from '../EditableTableColumn.vue'
|
||||
import EditableTableColumn from '../common/EditableTableColumn.vue'
|
||||
import useConnectionStore from '../../stores/connections.js'
|
||||
|
||||
const i18n = useI18n()
|
||||
const props = defineProps({
|
||||
|
|
|
@ -9,10 +9,10 @@ import { types } from '../../consts/value_view_type.js'
|
|||
import Close from '../icons/Close.vue'
|
||||
import Edit from '../icons/Edit.vue'
|
||||
import { IsJson } from '../../utils/check_string_format.js'
|
||||
import useConnectionStore from '../../stores/connection.js'
|
||||
import { types as redisTypes } from '../../consts/support_redis_type.js'
|
||||
import { ClipboardSetText } from '../../../wailsjs/runtime/runtime.js'
|
||||
import { toLower } from 'lodash'
|
||||
import useConnectionStore from '../../stores/connections.js'
|
||||
|
||||
const props = defineProps({
|
||||
name: String,
|
||||
|
|
|
@ -5,10 +5,10 @@ import ContentToolbar from '../ContentToolbar.vue'
|
|||
import AddLink from '../icons/AddLink.vue'
|
||||
import { NButton, NCode, NIcon, NInput, NInputNumber, useMessage } from 'naive-ui'
|
||||
import { types, types as redisTypes } from '../../consts/support_redis_type.js'
|
||||
import EditableTableColumn from '../EditableTableColumn.vue'
|
||||
import useConnectionStore from '../../stores/connection.js'
|
||||
import EditableTableColumn from '../common/EditableTableColumn.vue'
|
||||
import { isEmpty } from 'lodash'
|
||||
import useDialogStore from '../../stores/dialog.js'
|
||||
import useConnectionStore from '../../stores/connections.js'
|
||||
|
||||
const i18n = useI18n()
|
||||
const props = defineProps({
|
||||
|
|
|
@ -4,12 +4,12 @@ import { types } from '../../consts/support_redis_type'
|
|||
import useDialog from '../../stores/dialog'
|
||||
import NewStringValue from '../new_value/NewStringValue.vue'
|
||||
import NewSetValue from '../new_value/NewSetValue.vue'
|
||||
import useConnectionStore from '../../stores/connection.js'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import AddListValue from '../new_value/AddListValue.vue'
|
||||
import AddHashValue from '../new_value/AddHashValue.vue'
|
||||
import AddZSetValue from '../new_value/AddZSetValue.vue'
|
||||
import useConnectionStore from '../../stores/connections.js'
|
||||
|
||||
const i18n = useI18n()
|
||||
const newForm = reactive({
|
||||
|
|
|
@ -8,8 +8,8 @@ import NewHashValue from '../new_value/NewHashValue.vue'
|
|||
import NewListValue from '../new_value/NewListValue.vue'
|
||||
import NewZSetValue from '../new_value/NewZSetValue.vue'
|
||||
import NewSetValue from '../new_value/NewSetValue.vue'
|
||||
import useConnectionStore from '../../stores/connection.js'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import useConnectionStore from '../../stores/connections.js'
|
||||
|
||||
const i18n = useI18n()
|
||||
const newForm = reactive({
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<script setup>
|
||||
import { reactive, watch } from 'vue'
|
||||
import useDialog from '../../stores/dialog'
|
||||
import useConnectionStore from '../../stores/connection.js'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import useConnectionStore from '../../stores/connections.js'
|
||||
|
||||
const renameForm = reactive({
|
||||
server: '',
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
import { reactive, ref, watch } from 'vue'
|
||||
import useDialog from '../../stores/dialog'
|
||||
import useTabStore from '../../stores/tab.js'
|
||||
import useConnectionStore from '../../stores/connection.js'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import useConnectionStore from '../../stores/connections.js'
|
||||
|
||||
const ttlForm = reactive({
|
||||
ttl: -1,
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
<script setup>
|
||||
const props = defineProps({
|
||||
strokeWidth: {
|
||||
type: [Number, String],
|
||||
default: 3,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect
|
||||
:stroke-width="strokeWidth"
|
||||
fill="none"
|
||||
height="34"
|
||||
stroke="currentColor"
|
||||
stroke-linejoin="round"
|
||||
width="28"
|
||||
x="13"
|
||||
y="10"
|
||||
/>
|
||||
<path
|
||||
:stroke-width="strokeWidth"
|
||||
d="M35 10V4H8C7.44772 4 7 4.44772 7 5V38H13"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
:stroke-width="strokeWidth"
|
||||
d="M21 22H33"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
:stroke-width="strokeWidth"
|
||||
d="M21 30H33"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
|
@ -3,7 +3,7 @@ import { ref } from 'vue'
|
|||
import { flatMap, reject } from 'lodash'
|
||||
import Add from '../icons/Add.vue'
|
||||
import Delete from '../icons/Delete.vue'
|
||||
import IconButton from '../IconButton.vue'
|
||||
import IconButton from '../common/IconButton.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const props = defineProps({
|
||||
|
|
|
@ -3,7 +3,7 @@ import { ref } from 'vue'
|
|||
import { compact } from 'lodash'
|
||||
import Add from '../icons/Add.vue'
|
||||
import Delete from '../icons/Delete.vue'
|
||||
import IconButton from '../IconButton.vue'
|
||||
import IconButton from '../common/IconButton.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const props = defineProps({
|
||||
|
|
|
@ -3,7 +3,7 @@ import { ref } from 'vue'
|
|||
import { isEmpty, reject } from 'lodash'
|
||||
import Add from '../icons/Add.vue'
|
||||
import Delete from '../icons/Delete.vue'
|
||||
import IconButton from '../IconButton.vue'
|
||||
import IconButton from '../common/IconButton.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const props = defineProps({
|
||||
|
|
|
@ -3,7 +3,7 @@ import { ref } from 'vue'
|
|||
import { flatMap, reject } from 'lodash'
|
||||
import Add from '../icons/Add.vue'
|
||||
import Delete from '../icons/Delete.vue'
|
||||
import IconButton from '../IconButton.vue'
|
||||
import IconButton from '../common/IconButton.vue'
|
||||
|
||||
const props = defineProps({
|
||||
value: Array,
|
||||
|
|
|
@ -3,7 +3,7 @@ import { ref } from 'vue'
|
|||
import { compact } from 'lodash'
|
||||
import Add from '../icons/Add.vue'
|
||||
import Delete from '../icons/Delete.vue'
|
||||
import IconButton from '../IconButton.vue'
|
||||
import IconButton from '../common/IconButton.vue'
|
||||
|
||||
const props = defineProps({
|
||||
value: Array,
|
||||
|
|
|
@ -3,7 +3,7 @@ import { ref } from 'vue'
|
|||
import { compact, uniq } from 'lodash'
|
||||
import Add from '../icons/Add.vue'
|
||||
import Delete from '../icons/Delete.vue'
|
||||
import IconButton from '../IconButton.vue'
|
||||
import IconButton from '../common/IconButton.vue'
|
||||
|
||||
const props = defineProps({
|
||||
value: Array,
|
||||
|
|
|
@ -3,7 +3,7 @@ import { ref } from 'vue'
|
|||
import { flatMap, isEmpty, reject } from 'lodash'
|
||||
import Add from '../icons/Add.vue'
|
||||
import Delete from '../icons/Delete.vue'
|
||||
import IconButton from '../IconButton.vue'
|
||||
import IconButton from '../common/IconButton.vue'
|
||||
|
||||
const props = defineProps({
|
||||
value: Array,
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<script setup>
|
||||
import useDialogStore from '../stores/dialog.js'
|
||||
import useDialogStore from '../../stores/dialog.js'
|
||||
import { NIcon } from 'naive-ui'
|
||||
import AddGroup from './icons/AddGroup.vue'
|
||||
import AddLink from './icons/AddLink.vue'
|
||||
import Sort from './icons/Sort.vue'
|
||||
import ConnectionsTree from './ConnectionsTree.vue'
|
||||
import IconButton from './IconButton.vue'
|
||||
import Filter from './icons/Filter.vue'
|
||||
import AddGroup from '../icons/AddGroup.vue'
|
||||
import AddLink from '../icons/AddLink.vue'
|
||||
import Sort from '../icons/Sort.vue'
|
||||
import IconButton from '../common/IconButton.vue'
|
||||
import Filter from '../icons/Filter.vue'
|
||||
import ConnectionTree from './ConnectionTree.vue'
|
||||
|
||||
const dialogStore = useDialogStore()
|
||||
|
||||
|
@ -16,8 +16,8 @@ const onSort = () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="true" class="nav-pane-container flex-box-v">
|
||||
<ConnectionsTree />
|
||||
<div class="nav-pane-container flex-box-v">
|
||||
<connection-tree />
|
||||
|
||||
<!-- bottom function bar -->
|
||||
<div class="nav-pane-bottom flex-box-h">
|
|
@ -0,0 +1,271 @@
|
|||
<script setup>
|
||||
import useDialogStore from '../../stores/dialog.js'
|
||||
import { h, nextTick, onMounted, reactive, ref } from 'vue'
|
||||
import useConnectionStore from '../../stores/connections.js'
|
||||
import { NIcon, useMessage } from 'naive-ui'
|
||||
import { ConnectionType } from '../../consts/connection_type.js'
|
||||
import ToggleFolder from '../icons/ToggleFolder.vue'
|
||||
import ToggleServer from '../icons/ToggleServer.vue'
|
||||
import { indexOf } from 'lodash'
|
||||
import Config from '../icons/Config.vue'
|
||||
import Delete from '../icons/Delete.vue'
|
||||
import Refresh from '../icons/Refresh.vue'
|
||||
import Unlink from '../icons/Unlink.vue'
|
||||
import CopyLink from '../icons/CopyLink.vue'
|
||||
import Connect from '../icons/Connect.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import useTabStore from '../../stores/tab.js'
|
||||
import Edit from '../icons/Edit.vue'
|
||||
|
||||
const i18n = useI18n()
|
||||
const loadingConnection = ref(false)
|
||||
const openingConnection = ref(false)
|
||||
const connectionStore = useConnectionStore()
|
||||
const tabStore = useTabStore()
|
||||
const dialogStore = useDialogStore()
|
||||
const message = useMessage()
|
||||
|
||||
const expandedKeys = ref([])
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
loadingConnection.value = true
|
||||
await nextTick()
|
||||
await connectionStore.initConnections()
|
||||
} finally {
|
||||
loadingConnection.value = false
|
||||
}
|
||||
})
|
||||
|
||||
const contextMenuParam = reactive({
|
||||
show: false,
|
||||
x: 0,
|
||||
y: 0,
|
||||
options: null,
|
||||
currentNode: null,
|
||||
})
|
||||
|
||||
const renderIcon = (icon) => {
|
||||
return () => {
|
||||
return h(NIcon, null, {
|
||||
default: () => h(icon),
|
||||
})
|
||||
}
|
||||
}
|
||||
const menuOptions = {
|
||||
[ConnectionType.Group]: ({ opened }) => [
|
||||
{
|
||||
key: 'group_reload',
|
||||
label: i18n.t('edit_conn_group'),
|
||||
icon: renderIcon(Config),
|
||||
},
|
||||
{
|
||||
key: 'group_delete',
|
||||
label: i18n.t('remove_conn_group'),
|
||||
icon: renderIcon(Delete),
|
||||
},
|
||||
],
|
||||
[ConnectionType.Server]: ({ connected }) => {
|
||||
if (connected) {
|
||||
return [
|
||||
{
|
||||
key: 'server_reload',
|
||||
label: i18n.t('reload'),
|
||||
icon: renderIcon(Refresh),
|
||||
},
|
||||
{
|
||||
key: 'server_close',
|
||||
label: i18n.t('disconnect'),
|
||||
icon: renderIcon(Unlink),
|
||||
},
|
||||
{
|
||||
key: 'server_dup',
|
||||
label: i18n.t('dup_conn'),
|
||||
icon: renderIcon(CopyLink),
|
||||
},
|
||||
{
|
||||
key: 'server_config',
|
||||
label: i18n.t('edit_conn'),
|
||||
icon: renderIcon(Config),
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
key: 'd1',
|
||||
},
|
||||
{
|
||||
key: 'server_remove',
|
||||
label: i18n.t('remove_conn'),
|
||||
icon: renderIcon(Delete),
|
||||
},
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
{
|
||||
key: 'server_open',
|
||||
label: i18n.t('open_connection'),
|
||||
icon: renderIcon(Connect),
|
||||
},
|
||||
{
|
||||
key: 'server_edit',
|
||||
label: i18n.t('edit_conn'),
|
||||
icon: renderIcon(Edit),
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
key: 'd1',
|
||||
},
|
||||
{
|
||||
key: 'server_delete',
|
||||
label: i18n.t('remove_conn'),
|
||||
icon: renderIcon(Delete),
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const renderLabel = ({ option }) => {
|
||||
// switch (option.type) {
|
||||
// case ConnectionType.Server:
|
||||
// return h(ConnectionTreeItem, { title: option.label })
|
||||
// }
|
||||
return option.label
|
||||
}
|
||||
|
||||
const renderPrefix = ({ option }) => {
|
||||
switch (option.type) {
|
||||
case ConnectionType.Group:
|
||||
const opened = indexOf(expandedKeys.value, option.key) !== -1
|
||||
return h(
|
||||
NIcon,
|
||||
{ size: 20 },
|
||||
{
|
||||
default: () => h(ToggleFolder, { modelValue: opened }),
|
||||
}
|
||||
)
|
||||
case ConnectionType.Server:
|
||||
const connected = connectionStore.isConnected(option.name)
|
||||
return h(
|
||||
NIcon,
|
||||
{ size: 20 },
|
||||
{
|
||||
default: () => h(ToggleServer, { modelValue: !!connected }),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const onUpdateExpandedKeys = (value, option, meta) => {
|
||||
expandedKeys.value = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Open connection
|
||||
* @param name
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const openConnection = async (name) => {
|
||||
try {
|
||||
openingConnection.value = true
|
||||
await connectionStore.openConnection(name)
|
||||
tabStore.upsertTab({
|
||||
server: name,
|
||||
})
|
||||
} catch (e) {
|
||||
message.error(e.message)
|
||||
// node.isLeaf = undefined
|
||||
} finally {
|
||||
openingConnection.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const nodeProps = ({ option }) => {
|
||||
return {
|
||||
onDblclick: async () => {
|
||||
if (option.type === ConnectionType.Server) {
|
||||
openConnection(option.name).then(() => {})
|
||||
}
|
||||
},
|
||||
onContextmenu(e) {
|
||||
e.preventDefault()
|
||||
const mop = menuOptions[option.type]
|
||||
if (mop == null) {
|
||||
return
|
||||
}
|
||||
contextMenuParam.show = false
|
||||
nextTick().then(() => {
|
||||
contextMenuParam.options = mop(option)
|
||||
contextMenuParam.currentNode = option
|
||||
contextMenuParam.x = e.clientX
|
||||
contextMenuParam.y = e.clientY
|
||||
contextMenuParam.show = true
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const renderContextLabel = (option) => {
|
||||
return h('div', { class: 'context-menu-item' }, option.label)
|
||||
}
|
||||
|
||||
const handleSelectContextMenu = (key) => {
|
||||
contextMenuParam.show = false
|
||||
const { name, db, key: nodeKey, redisKey } = contextMenuParam.currentNode
|
||||
switch (key) {
|
||||
case 'server_open':
|
||||
openConnection(name).then(() => {})
|
||||
}
|
||||
console.warn('TODO: handle context menu:' + key)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-tree
|
||||
:animated="false"
|
||||
:block-line="true"
|
||||
:block-node="true"
|
||||
:cancelable="false"
|
||||
:data="connectionStore.connections"
|
||||
:expand-on-click="true"
|
||||
:expanded-keys="expandedKeys"
|
||||
:node-props="nodeProps"
|
||||
:on-update:expanded-keys="onUpdateExpandedKeys"
|
||||
:render-label="renderLabel"
|
||||
:render-prefix="renderPrefix"
|
||||
class="fill-height"
|
||||
virtual-scroll
|
||||
/>
|
||||
|
||||
<!-- status display modal -->
|
||||
<n-modal :show="loadingConnection || openingConnection" transform-origin="center">
|
||||
<n-card
|
||||
:bordered="false"
|
||||
:content-style="{ textAlign: 'center' }"
|
||||
aria-model="true"
|
||||
role="dialog"
|
||||
style="width: 400px"
|
||||
>
|
||||
<n-spin>
|
||||
<template #description>
|
||||
{{ openingConnection ? $t('opening_connection') : '' }}
|
||||
</template>
|
||||
</n-spin>
|
||||
</n-card>
|
||||
</n-modal>
|
||||
|
||||
<!-- context menu -->
|
||||
<n-dropdown
|
||||
:animated="false"
|
||||
:options="contextMenuParam.options"
|
||||
:render-label="renderContextLabel"
|
||||
:show="contextMenuParam.show"
|
||||
:x="contextMenuParam.x"
|
||||
:y="contextMenuParam.y"
|
||||
placement="bottom-start"
|
||||
trigger="manual"
|
||||
@clickoutside="contextMenuParam.show = false"
|
||||
@select="handleSelectContextMenu"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,26 @@
|
|||
<script setup>
|
||||
const props = defineProps({
|
||||
title: String,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="db-tree-item flex-box-v">
|
||||
<div class="tree-item-title">{{ title }}</div>
|
||||
<div class="tree-item-addr">localhost:3306</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.db-tree-item {
|
||||
padding: 0 5px;
|
||||
|
||||
.tree-item-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.tree-item-addr {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,44 @@
|
|||
<script setup>
|
||||
import { NIcon } from 'naive-ui'
|
||||
import AddGroup from '../icons/AddGroup.vue'
|
||||
import AddLink from '../icons/AddLink.vue'
|
||||
import DatabaseTree from './DatabaseTree.vue'
|
||||
import IconButton from '../common/IconButton.vue'
|
||||
import Filter from '../icons/Filter.vue'
|
||||
import useTabStore from '../../stores/tab.js'
|
||||
import { computed } from 'vue'
|
||||
import { get } from 'lodash'
|
||||
|
||||
const tabStore = useTabStore()
|
||||
const currentName = computed(() => get(tabStore.currentTab, 'name', ''))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="nav-pane-container flex-box-v">
|
||||
<database-tree :server="currentName" />
|
||||
|
||||
<!-- bottom function bar -->
|
||||
<div class="nav-pane-bottom flex-box-h">
|
||||
<icon-button :icon="AddLink" color="#555" size="20" stroke-width="4" t-tooltip="new_conn" />
|
||||
<icon-button :icon="AddGroup" color="#555" size="20" stroke-width="4" t-tooltip="new_group" />
|
||||
<n-input placeholder="">
|
||||
<template #prefix>
|
||||
<n-icon :component="Filter" color="#aaa" size="20" />
|
||||
</template>
|
||||
</n-input>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.nav-pane-container {
|
||||
overflow: hidden;
|
||||
background-color: var(--bg-color);
|
||||
|
||||
.nav-pane-bottom {
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 3px 3px 5px 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,38 +1,37 @@
|
|||
<script setup>
|
||||
import { h, nextTick, onMounted, reactive, ref } from 'vue'
|
||||
import { ConnectionType } from '../consts/connection_type.js'
|
||||
import useConnection from '../stores/connection.js'
|
||||
import { ConnectionType } from '../../consts/connection_type.js'
|
||||
import { NIcon, useDialog, useMessage } from 'naive-ui'
|
||||
import ToggleFolder from './icons/ToggleFolder.vue'
|
||||
import Key from './icons/Key.vue'
|
||||
import ToggleDb from './icons/ToggleDb.vue'
|
||||
import ToggleServer from './icons/ToggleServer.vue'
|
||||
import Key from '../icons/Key.vue'
|
||||
import ToggleDb from '../icons/ToggleDb.vue'
|
||||
import { indexOf, remove, startsWith } from 'lodash'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import Refresh from './icons/Refresh.vue'
|
||||
import Config from './icons/Config.vue'
|
||||
import CopyLink from './icons/CopyLink.vue'
|
||||
import Unlink from './icons/Unlink.vue'
|
||||
import Add from './icons/Add.vue'
|
||||
import Layer from './icons/Layer.vue'
|
||||
import Delete from './icons/Delete.vue'
|
||||
import Connect from './icons/Connect.vue'
|
||||
import useDialogStore from '../stores/dialog.js'
|
||||
import { ClipboardSetText } from '../../wailsjs/runtime/runtime.js'
|
||||
import useTabStore from '../stores/tab.js'
|
||||
import Refresh from '../icons/Refresh.vue'
|
||||
import CopyLink from '../icons/CopyLink.vue'
|
||||
import Add from '../icons/Add.vue'
|
||||
import Layer from '../icons/Layer.vue'
|
||||
import Delete from '../icons/Delete.vue'
|
||||
import Connect from '../icons/Connect.vue'
|
||||
import useDialogStore from '../../stores/dialog.js'
|
||||
import { ClipboardSetText } from '../../../wailsjs/runtime/runtime.js'
|
||||
import useTabStore from '../../stores/tab.js'
|
||||
import useConnectionStore from '../../stores/connections.js'
|
||||
|
||||
const i18n = useI18n()
|
||||
const loading = ref(false)
|
||||
const loadingConnections = ref(false)
|
||||
const expandedKeys = ref([])
|
||||
const connectionStore = useConnection()
|
||||
const connectionStore = useConnectionStore()
|
||||
const tabStore = useTabStore()
|
||||
const dialogStore = useDialogStore()
|
||||
|
||||
const showContextMenu = ref(false)
|
||||
const contextPos = reactive({ x: 0, y: 0 })
|
||||
const contextMenuOptions = ref(null)
|
||||
const currentContextNode = ref(null)
|
||||
const contextMenuParam = reactive({
|
||||
show: false,
|
||||
x: 0,
|
||||
y: 0,
|
||||
options: null,
|
||||
currentNode: null,
|
||||
})
|
||||
const renderIcon = (icon) => {
|
||||
return () => {
|
||||
return h(NIcon, null, {
|
||||
|
@ -41,61 +40,6 @@ const renderIcon = (icon) => {
|
|||
}
|
||||
}
|
||||
const menuOptions = {
|
||||
[ConnectionType.Group]: ({ opened }) => [
|
||||
{
|
||||
key: 'group_reload',
|
||||
label: i18n.t('config_conn_group'),
|
||||
icon: renderIcon(Config),
|
||||
},
|
||||
{
|
||||
key: 'group_delete',
|
||||
label: i18n.t('remove_conn_group'),
|
||||
icon: renderIcon(Delete),
|
||||
},
|
||||
],
|
||||
[ConnectionType.Server]: ({ connected }) => {
|
||||
if (connected) {
|
||||
return [
|
||||
{
|
||||
key: 'server_reload',
|
||||
label: i18n.t('reload'),
|
||||
icon: renderIcon(Refresh),
|
||||
},
|
||||
{
|
||||
key: 'server_disconnect',
|
||||
label: i18n.t('disconnect'),
|
||||
icon: renderIcon(Unlink),
|
||||
},
|
||||
{
|
||||
key: 'server_dup',
|
||||
label: i18n.t('dup_conn'),
|
||||
icon: renderIcon(CopyLink),
|
||||
},
|
||||
{
|
||||
key: 'server_config',
|
||||
label: i18n.t('config_conn'),
|
||||
icon: renderIcon(Config),
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
key: 'd1',
|
||||
},
|
||||
{
|
||||
key: 'server_remove',
|
||||
label: i18n.t('remove_conn'),
|
||||
icon: renderIcon(Delete),
|
||||
},
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
{
|
||||
key: 'server_open',
|
||||
label: i18n.t('open_connection'),
|
||||
icon: renderIcon(Connect),
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
[ConnectionType.RedisDB]: ({ opened }) => {
|
||||
if (opened) {
|
||||
return [
|
||||
|
@ -177,12 +121,16 @@ onMounted(async () => {
|
|||
try {
|
||||
// TODO: Show loading list status
|
||||
loadingConnections.value = true
|
||||
nextTick(connectionStore.initConnection)
|
||||
// nextTick(connectionStore.initConnection)
|
||||
} finally {
|
||||
loadingConnections.value = false
|
||||
}
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
server: Strig,
|
||||
})
|
||||
|
||||
const expandKey = (key) => {
|
||||
const idx = indexOf(expandedKeys.value, key)
|
||||
if (idx === -1) {
|
||||
|
@ -224,22 +172,6 @@ const onUpdateExpanded = (value, option, meta) => {
|
|||
|
||||
const renderPrefix = ({ option }) => {
|
||||
switch (option.type) {
|
||||
case ConnectionType.Group:
|
||||
return h(
|
||||
NIcon,
|
||||
{ size: 20 },
|
||||
{
|
||||
default: () => h(ToggleFolder, { modelValue: option.expanded === true }),
|
||||
}
|
||||
)
|
||||
case ConnectionType.Server:
|
||||
return h(
|
||||
NIcon,
|
||||
{ size: 20 },
|
||||
{
|
||||
default: () => h(ToggleServer, { modelValue: option.connected === true }),
|
||||
}
|
||||
)
|
||||
case ConnectionType.RedisDB:
|
||||
return h(
|
||||
NIcon,
|
||||
|
@ -302,6 +234,7 @@ const nodeProps = ({ option }) => {
|
|||
|
||||
case ConnectionType.RedisDB:
|
||||
option.isLeaf = false
|
||||
break
|
||||
}
|
||||
|
||||
// default handle is expand current node
|
||||
|
@ -313,13 +246,13 @@ const nodeProps = ({ option }) => {
|
|||
if (mop == null) {
|
||||
return
|
||||
}
|
||||
showContextMenu.value = false
|
||||
contextMenuParam.show = false
|
||||
nextTick().then(() => {
|
||||
contextMenuOptions.value = mop(option)
|
||||
currentContextNode.value = option
|
||||
contextPos.x = e.clientX
|
||||
contextPos.y = e.clientY
|
||||
showContextMenu.value = true
|
||||
contextMenuParam.options = mop(option)
|
||||
contextMenuParam.currentNode = option
|
||||
contextMenuParam.x = e.clientX
|
||||
contextMenuParam.y = e.clientY
|
||||
contextMenuParam.show = true
|
||||
})
|
||||
},
|
||||
// onMouseover() {
|
||||
|
@ -330,17 +263,6 @@ const nodeProps = ({ option }) => {
|
|||
|
||||
const onLoadTree = async (node) => {
|
||||
switch (node.type) {
|
||||
case ConnectionType.Server:
|
||||
loading.value = true
|
||||
try {
|
||||
await connectionStore.openConnection(node.name)
|
||||
} catch (e) {
|
||||
message.error(e.message)
|
||||
node.isLeaf = undefined
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
break
|
||||
case ConnectionType.RedisDB:
|
||||
loading.value = true
|
||||
try {
|
||||
|
@ -356,8 +278,8 @@ const onLoadTree = async (node) => {
|
|||
}
|
||||
|
||||
const handleSelectContextMenu = (key) => {
|
||||
showContextMenu.value = false
|
||||
const { name, db, key: nodeKey, redisKey } = currentContextNode.value
|
||||
contextMenuParam.show = false
|
||||
const { name, db, key: nodeKey, redisKey } = contextMenuParam.currentNode
|
||||
switch (key) {
|
||||
case 'server_disconnect':
|
||||
connectionStore.closeConnection(nodeKey).then((success) => {
|
||||
|
@ -412,7 +334,7 @@ const handleSelectContextMenu = (key) => {
|
|||
}
|
||||
|
||||
const handleOutsideContextMenu = () => {
|
||||
showContextMenu.value = false
|
||||
contextMenuParam.show = false
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -420,7 +342,9 @@ const handleOutsideContextMenu = () => {
|
|||
<n-tree
|
||||
:block-line="true"
|
||||
:block-node="true"
|
||||
:data="connectionStore.connections"
|
||||
:animated="false"
|
||||
:cancelable="false"
|
||||
:data="connectionStore.databases[props.server] || []"
|
||||
:expand-on-click="false"
|
||||
:expanded-keys="expandedKeys"
|
||||
:node-props="nodeProps"
|
||||
|
@ -429,17 +353,16 @@ const handleOutsideContextMenu = () => {
|
|||
:render-label="renderLabel"
|
||||
:render-prefix="renderPrefix"
|
||||
:render-suffix="renderSuffix"
|
||||
block-line
|
||||
class="fill-height"
|
||||
virtual-scroll
|
||||
/>
|
||||
<n-dropdown
|
||||
:animated="false"
|
||||
:options="contextMenuOptions"
|
||||
:options="contextMenuParam.options"
|
||||
:render-label="renderContextLabel"
|
||||
:show="showContextMenu"
|
||||
:x="contextPos.x"
|
||||
:y="contextPos.y"
|
||||
:show="contextMenuParam.show"
|
||||
:x="contextMenuParam.x"
|
||||
:y="contextMenuParam.y"
|
||||
placement="bottom-start"
|
||||
trigger="manual"
|
||||
@clickoutside="handleOutsideContextMenu"
|
|
@ -9,6 +9,9 @@
|
|||
"new_group": "Add New Group",
|
||||
"sort_conn": "Resort Connections",
|
||||
"reload_key": "Reload Current Key",
|
||||
"close_confirm_title": "Confirm Close",
|
||||
"close_confirm": "Confirm close this tab and connection",
|
||||
"opening_connection": "Opening Connection...",
|
||||
"ttl": "TTL",
|
||||
"forever": "Forever",
|
||||
"rename_key": "Rename Key",
|
||||
|
@ -31,8 +34,8 @@
|
|||
"disconnect": "Disconnect",
|
||||
"dup_conn": "Duplicate Connection",
|
||||
"remove_conn": "Delete Connection",
|
||||
"config_conn": "Edit Connection Config",
|
||||
"config_conn_group": "Edit Connection Group",
|
||||
"edit_conn": "Edit Connection Config",
|
||||
"edit_conn_group": "Edit Connection Group",
|
||||
"remove_conn_group": "Delete Connection Group",
|
||||
"copy_path": "Copy Path",
|
||||
"remove_path": "Remove Path",
|
||||
|
@ -102,10 +105,12 @@
|
|||
"field_required": "This item should not be blank",
|
||||
"spec_field_required": "\"{key}\" should not be blank",
|
||||
"no_connections": "No Connection",
|
||||
"empty_tab_content": "Select the key from left list to see the details of the key.",
|
||||
"empty_tab_content": "Select the key from left list to see detail.",
|
||||
"empty_server_content": "Connect server from left list",
|
||||
"reload_when_succ": "Reload immediately after success",
|
||||
"server": "Server",
|
||||
"structure": "Structure",
|
||||
"log": "Log",
|
||||
"about": "About",
|
||||
"check_update": "Check for Updates..."
|
||||
}
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
"new_group": "添加新分组",
|
||||
"sort_conn": "调整连接顺序",
|
||||
"reload_key": "重新载入此键内容",
|
||||
"close_confirm_title": "关闭确认",
|
||||
"close_confirm": "是否关闭当前连接",
|
||||
"opening_connection": "正在打开连接...",
|
||||
"ttl": "TTL",
|
||||
"forever": "永久",
|
||||
"rename_key": "重命名键",
|
||||
|
@ -33,8 +36,8 @@
|
|||
"disconnect": "断开连接",
|
||||
"dup_conn": "复制连接",
|
||||
"remove_conn": "删除连接",
|
||||
"config_conn": "编辑连接配置",
|
||||
"config_conn_group": "编辑连接分组",
|
||||
"edit_conn": "编辑连接配置",
|
||||
"edit_conn_group": "编辑连接分组",
|
||||
"remove_conn_group": "删除连接分组",
|
||||
"copy_path": "复制路径",
|
||||
"remove_path": "删除路径",
|
||||
|
@ -106,9 +109,11 @@
|
|||
"spec_field_required": "{key} 不能为空",
|
||||
"no_connections": "空空如也",
|
||||
"empty_tab_content": "可以从左边选择键来查看键的详细内容",
|
||||
"empty_server_content": "可以从左边选择并打开连接",
|
||||
"reload_when_succ": "操作成功后立即重新加载",
|
||||
"server": "服务器",
|
||||
"structure": "结构",
|
||||
"log": "日志",
|
||||
"about": "关于",
|
||||
"check_update": "检查更新..."
|
||||
}
|
||||
|
|
|
@ -0,0 +1,921 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { get, isEmpty, last, remove, size, sortedIndexBy, split } from 'lodash'
|
||||
import {
|
||||
AddHashField,
|
||||
AddListItem,
|
||||
AddZSetValue,
|
||||
CloseConnection,
|
||||
GetKeyValue,
|
||||
ListConnection,
|
||||
OpenConnection,
|
||||
OpenDatabase,
|
||||
RemoveKey,
|
||||
RenameKey,
|
||||
SetHashValue,
|
||||
SetKeyTTL,
|
||||
SetKeyValue,
|
||||
SetListItem,
|
||||
SetSetItem,
|
||||
UpdateSetItem,
|
||||
UpdateZSetValue,
|
||||
} from '../../wailsjs/go/services/connectionService.js'
|
||||
import { ConnectionType } from '../consts/connection_type.js'
|
||||
import useTabStore from './tab.js'
|
||||
|
||||
const separator = ':'
|
||||
|
||||
const useConnectionStore = defineStore('connections', {
|
||||
/**
|
||||
* @typedef {Object} ConnectionItem
|
||||
* @property {string} key
|
||||
* @property {string} label display label
|
||||
* @property {string} name database name
|
||||
* @property {number} type
|
||||
* @property {ConnectionItem[]} children
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} DatabaseItem
|
||||
* @property {string} key
|
||||
* @property {string} label
|
||||
* @property {string} name - server name, type != ConnectionType.Group only
|
||||
* @property {number} type
|
||||
* @property {number} [db] - database index, type == ConnectionType.RedisDB only
|
||||
* @property {number} keys
|
||||
* @property {boolean} [opened] - redis db is opened, type == ConnectionType.RedisDB only
|
||||
* @property {boolean} [expanded] - current node is expanded
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {{databases: Object<string, DatabaseItem[]>, connections: ConnectionItem[]}}
|
||||
*/
|
||||
state: () => ({
|
||||
connections: [], // all connections
|
||||
databases: {}, // all databases in opened connections group by name
|
||||
}),
|
||||
getters: {
|
||||
anyConnectionOpened() {
|
||||
return !isEmpty(this.databases)
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
/**
|
||||
* Load all store connections struct from local profile
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async initConnections() {
|
||||
if (!isEmpty(this.connections)) {
|
||||
return
|
||||
}
|
||||
const conns = []
|
||||
const { data = [{ groupName: '', connections: [] }] } = await ListConnection()
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const group = data[i]
|
||||
// Top level group
|
||||
if (isEmpty(group.groupName)) {
|
||||
const len = size(group.connections)
|
||||
for (let j = 0; j < len; j++) {
|
||||
const item = group.connections[j]
|
||||
conns.push({
|
||||
key: item.name,
|
||||
label: item.name,
|
||||
name: item.name,
|
||||
type: ConnectionType.Server,
|
||||
// isLeaf: false,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Custom group
|
||||
const children = []
|
||||
const len = size(group.connections)
|
||||
for (let j = 0; j < len; j++) {
|
||||
const item = group.connections[j]
|
||||
const value = group.groupName + '/' + item.name
|
||||
children.push({
|
||||
key: value,
|
||||
label: item.name,
|
||||
name: item.name,
|
||||
type: ConnectionType.Server,
|
||||
children: j === len - 1 ? undefined : [],
|
||||
// isLeaf: false,
|
||||
})
|
||||
}
|
||||
conns.push({
|
||||
key: group.groupName,
|
||||
label: group.groupName,
|
||||
type: ConnectionType.Group,
|
||||
children,
|
||||
})
|
||||
}
|
||||
}
|
||||
this.connections = conns
|
||||
console.debug(JSON.stringify(this.connections))
|
||||
},
|
||||
|
||||
/**
|
||||
* get database server by name
|
||||
* @param name
|
||||
* @returns {ConnectionItem|null}
|
||||
*/
|
||||
getConnection(name) {
|
||||
const conns = this.connections
|
||||
for (let i = 0; i < conns.length; i++) {
|
||||
if (conns[i].type === ConnectionType.Server && conns[i].key === name) {
|
||||
return conns[i]
|
||||
} else if (conns[i].type === ConnectionType.Group) {
|
||||
const children = conns[i].children
|
||||
for (let j = 0; j < children.length; j++) {
|
||||
if (children[j].type === ConnectionType.Server && conns[i].key === name) {
|
||||
return children[j]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if connection is connected
|
||||
* @param name
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isConnected(name) {
|
||||
let dbs = get(this.databases, name, [])
|
||||
return !isEmpty(dbs)
|
||||
},
|
||||
|
||||
/**
|
||||
* Open connection
|
||||
* @param {string} name
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async openConnection(name) {
|
||||
if (this.isConnected(name)) {
|
||||
return
|
||||
}
|
||||
|
||||
const { data, success, msg } = await OpenConnection(name)
|
||||
if (!success) {
|
||||
throw new Error(msg)
|
||||
}
|
||||
// append to db node to current connection
|
||||
// const connNode = this.getConnection(name)
|
||||
// if (connNode == null) {
|
||||
// throw new Error('no such connection')
|
||||
// }
|
||||
const { db } = data
|
||||
if (isEmpty(db)) {
|
||||
throw new Error('no db loaded')
|
||||
}
|
||||
const dbs = []
|
||||
for (let i = 0; i < db.length; i++) {
|
||||
dbs.push({
|
||||
key: `${name}/${db[i].name}`,
|
||||
label: db[i].name,
|
||||
name: name,
|
||||
keys: db[i].keys,
|
||||
db: i,
|
||||
type: ConnectionType.RedisDB,
|
||||
isLeaf: false,
|
||||
})
|
||||
}
|
||||
this.databases[name] = dbs
|
||||
},
|
||||
|
||||
/**
|
||||
* Close connection
|
||||
* @param {string} name
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async closeConnection(name) {
|
||||
const { success, msg } = await CloseConnection(name)
|
||||
if (!success) {
|
||||
// throw new Error(msg)
|
||||
return false
|
||||
}
|
||||
|
||||
delete this.databases[name]
|
||||
return true
|
||||
},
|
||||
|
||||
/**
|
||||
* Open database and load all keys
|
||||
* @param connName
|
||||
* @param db
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async openDatabase(connName, db) {
|
||||
const { data, success, msg } = await OpenDatabase(connName, db)
|
||||
if (!success) {
|
||||
throw new Error(msg)
|
||||
}
|
||||
const { keys = [] } = data
|
||||
if (isEmpty(keys)) {
|
||||
const dbs = this.databases[connName]
|
||||
dbs[db].children = []
|
||||
dbs[db].opened = true
|
||||
return
|
||||
}
|
||||
|
||||
// insert child to children list by order
|
||||
const sortedInsertChild = (childrenList, item) => {
|
||||
const insertIdx = sortedIndexBy(childrenList, item, 'key')
|
||||
childrenList.splice(insertIdx, 0, item)
|
||||
// childrenList.push(item)
|
||||
}
|
||||
// update all node item's children num
|
||||
const updateChildrenNum = (node) => {
|
||||
let count = 0
|
||||
const totalChildren = size(node.children)
|
||||
if (totalChildren > 0) {
|
||||
for (const elem of node.children) {
|
||||
updateChildrenNum(elem)
|
||||
count += elem.keys
|
||||
}
|
||||
} else {
|
||||
count += 1
|
||||
}
|
||||
node.keys = count
|
||||
// node.children = sortBy(node.children, 'label')
|
||||
}
|
||||
|
||||
const keyStruct = []
|
||||
const mark = {}
|
||||
for (const key in keys) {
|
||||
const keyPart = split(key, separator)
|
||||
// const prefixLen = size(keyPart) - 1
|
||||
const len = size(keyPart)
|
||||
let handlePath = ''
|
||||
let ks = keyStruct
|
||||
for (let i = 0; i < len; i++) {
|
||||
handlePath += keyPart[i]
|
||||
if (i !== len - 1) {
|
||||
// layer
|
||||
const treeKey = `${handlePath}@${ConnectionType.RedisKey}`
|
||||
if (!mark.hasOwnProperty(treeKey)) {
|
||||
mark[treeKey] = {
|
||||
key: `${connName}/db${db}/${treeKey}`,
|
||||
label: keyPart[i],
|
||||
name: connName,
|
||||
db,
|
||||
keys: 0,
|
||||
redisKey: handlePath,
|
||||
type: ConnectionType.RedisKey,
|
||||
children: [],
|
||||
}
|
||||
sortedInsertChild(ks, mark[treeKey])
|
||||
}
|
||||
ks = mark[treeKey].children
|
||||
handlePath += separator
|
||||
} else {
|
||||
// key
|
||||
const treeKey = `${handlePath}@${ConnectionType.RedisValue}`
|
||||
mark[treeKey] = {
|
||||
key: `${connName}/db${db}/${treeKey}`,
|
||||
label: keyPart[i],
|
||||
name: connName,
|
||||
db,
|
||||
keys: 0,
|
||||
redisKey: handlePath,
|
||||
type: ConnectionType.RedisValue,
|
||||
}
|
||||
sortedInsertChild(ks, mark[treeKey])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// append db node to current connection's children
|
||||
const dbs = this.databases[connName]
|
||||
dbs[db].children = keyStruct
|
||||
dbs[db].opened = true
|
||||
updateChildrenNum(dbs[db])
|
||||
},
|
||||
|
||||
/**
|
||||
* select node
|
||||
* @param key
|
||||
* @param name
|
||||
* @param db
|
||||
* @param type
|
||||
* @param redisKey
|
||||
*/
|
||||
select({ key, name, db, type, redisKey }) {
|
||||
if (type === ConnectionType.RedisValue) {
|
||||
console.log(`[click]key:${key} db: ${db} redis key: ${redisKey}`)
|
||||
|
||||
// async get value for key
|
||||
this.loadKeyValue(name, db, redisKey).then(() => {})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* load redis key
|
||||
* @param server
|
||||
* @param db
|
||||
* @param key
|
||||
*/
|
||||
async loadKeyValue(server, db, key) {
|
||||
try {
|
||||
const { data, success, msg } = await GetKeyValue(server, db, key)
|
||||
if (success) {
|
||||
const { type, ttl, value } = data
|
||||
const tab = useTabStore()
|
||||
tab.upsertTab({
|
||||
server,
|
||||
db,
|
||||
type,
|
||||
ttl,
|
||||
key,
|
||||
value,
|
||||
})
|
||||
} else {
|
||||
console.warn('TODO: handle get key fail')
|
||||
}
|
||||
} finally {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} connName
|
||||
* @param {number} db
|
||||
* @param {string} key
|
||||
* @private
|
||||
*/
|
||||
_addKey(connName, db, key) {
|
||||
const dbs = this.databases[connName]
|
||||
const dbDetail = get(dbs, db, {})
|
||||
|
||||
if (dbDetail == null) {
|
||||
return
|
||||
}
|
||||
|
||||
const descendantChain = [dbDetail]
|
||||
|
||||
const keyPart = split(key, separator)
|
||||
let redisKey = ''
|
||||
const keyLen = size(keyPart)
|
||||
let added = false
|
||||
for (let i = 0; i < keyLen; i++) {
|
||||
redisKey += keyPart[i]
|
||||
|
||||
const node = last(descendantChain)
|
||||
const nodeList = get(node, 'children', [])
|
||||
const len = size(nodeList)
|
||||
const isLastKeyPart = i === keyLen - 1
|
||||
for (let j = 0; j < len + 1; j++) {
|
||||
const treeKey = get(nodeList[j], 'key')
|
||||
const isLast = j >= len - 1
|
||||
const currentKey = `${connName}/db${db}/${redisKey}@${
|
||||
isLastKeyPart ? ConnectionType.RedisValue : ConnectionType.RedisKey
|
||||
}`
|
||||
if (treeKey > currentKey || isLast) {
|
||||
// out of search range, add new item
|
||||
if (isLastKeyPart) {
|
||||
// key not exists, add new one
|
||||
const item = {
|
||||
key: currentKey,
|
||||
label: keyPart[i],
|
||||
name: connName,
|
||||
db,
|
||||
keys: 1,
|
||||
redisKey,
|
||||
type: ConnectionType.RedisValue,
|
||||
}
|
||||
if (isLast) {
|
||||
nodeList.push(item)
|
||||
} else {
|
||||
nodeList.splice(j, 0, item)
|
||||
}
|
||||
added = true
|
||||
} else {
|
||||
// layer not exists, add new one
|
||||
const item = {
|
||||
key: currentKey,
|
||||
label: keyPart[i],
|
||||
name: connName,
|
||||
db,
|
||||
keys: 0,
|
||||
redisKey,
|
||||
type: ConnectionType.RedisKey,
|
||||
children: [],
|
||||
}
|
||||
if (isLast) {
|
||||
nodeList.push(item)
|
||||
descendantChain.push(last(nodeList))
|
||||
} else {
|
||||
nodeList.splice(j, 0, item)
|
||||
descendantChain.push(nodeList[j])
|
||||
}
|
||||
redisKey += separator
|
||||
added = true
|
||||
}
|
||||
break
|
||||
} else if (treeKey === currentKey) {
|
||||
if (isLastKeyPart) {
|
||||
// same key exists, do nothing
|
||||
console.log('TODO: same key exist, do nothing now, should replace value later')
|
||||
} else {
|
||||
// same group exists, find into it's children
|
||||
descendantChain.push(nodeList[j])
|
||||
redisKey += separator
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update ancestor node's info
|
||||
if (added) {
|
||||
const desLen = size(descendantChain)
|
||||
for (let i = 0; i < desLen; i++) {
|
||||
const children = get(descendantChain[i], 'children', [])
|
||||
let keys = 0
|
||||
for (const child of children) {
|
||||
if (child.type === ConnectionType.RedisKey) {
|
||||
keys += get(child, 'keys', 1)
|
||||
} else if (child.type === ConnectionType.RedisValue) {
|
||||
keys += get(child, 'keys', 0)
|
||||
}
|
||||
}
|
||||
descendantChain[i].keys = keys
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* set redis key
|
||||
* @param {string} connName
|
||||
* @param {number} db
|
||||
* @param {string} key
|
||||
* @param {number} keyType
|
||||
* @param {any} value
|
||||
* @param {number} ttl
|
||||
* @returns {Promise<{[msg]: string, success: boolean}>}
|
||||
*/
|
||||
async setKey(connName, db, key, keyType, value, ttl) {
|
||||
try {
|
||||
const { data, success, msg } = await SetKeyValue(connName, db, key, keyType, value, ttl)
|
||||
if (success) {
|
||||
// update tree view data
|
||||
this._addKey(connName, db, key)
|
||||
return { success }
|
||||
} else {
|
||||
return { success, msg }
|
||||
}
|
||||
} catch (e) {
|
||||
return { success: false, msg: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* update hash field
|
||||
* when field is set, newField is null, delete field
|
||||
* when field is null, newField is set, add new field
|
||||
* when both field and newField are set, and field === newField, update field
|
||||
* when both field and newField are set, and field !== newField, delete field and add newField
|
||||
* @param {string} connName
|
||||
* @param {number} db
|
||||
* @param {string} key
|
||||
* @param {string} field
|
||||
* @param {string} newField
|
||||
* @param {string} value
|
||||
* @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>}
|
||||
*/
|
||||
async setHash(connName, db, key, field, newField, value) {
|
||||
try {
|
||||
const { data, success, msg } = await SetHashValue(connName, db, key, field, newField || '', value || '')
|
||||
if (success) {
|
||||
const { updated = {} } = data
|
||||
return { success, updated }
|
||||
} else {
|
||||
return { success, msg }
|
||||
}
|
||||
} catch (e) {
|
||||
return { success: false, msg: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* insert or update hash field item
|
||||
* @param {string} connName
|
||||
* @param {number} db
|
||||
* @param {string} key
|
||||
* @param {number }action 0:ignore duplicated fields 1:overwrite duplicated fields
|
||||
* @param {string[]} fieldItems field1, value1, filed2, value2...
|
||||
* @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>}
|
||||
*/
|
||||
async addHashField(connName, db, key, action, fieldItems) {
|
||||
try {
|
||||
const { data, success, msg } = await AddHashField(connName, db, key, action, fieldItems)
|
||||
if (success) {
|
||||
const { updated = {} } = data
|
||||
return { success, updated }
|
||||
} else {
|
||||
return { success: false, msg }
|
||||
}
|
||||
} catch (e) {
|
||||
return { success: false, msg: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* remove hash field
|
||||
* @param {string} connName
|
||||
* @param {number} db
|
||||
* @param {string} key
|
||||
* @param {string} field
|
||||
* @returns {Promise<{[msg]: {}, success: boolean, [removed]: string[]}>}
|
||||
*/
|
||||
async removeHashField(connName, db, key, field) {
|
||||
try {
|
||||
const { data, success, msg } = await SetHashValue(connName, db, key, field, '', '')
|
||||
if (success) {
|
||||
const { removed = [] } = data
|
||||
return { success, removed }
|
||||
} else {
|
||||
return { success, msg }
|
||||
}
|
||||
} catch (e) {
|
||||
return { success: false, msg: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* insert list item
|
||||
* @param {string} connName
|
||||
* @param {number} db
|
||||
* @param {string} key
|
||||
* @param {int} action 0: push to head, 1: push to tail
|
||||
* @param {string[]}values
|
||||
* @returns {Promise<*|{msg, success: boolean}>}
|
||||
*/
|
||||
async addListItem(connName, db, key, action, values) {
|
||||
try {
|
||||
return AddListItem(connName, db, key, action, values)
|
||||
} catch (e) {
|
||||
return { success: false, msg: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* prepend item to head of list
|
||||
* @param connName
|
||||
* @param db
|
||||
* @param key
|
||||
* @param values
|
||||
* @returns {Promise<[msg]: string, success: boolean, [item]: []>}
|
||||
*/
|
||||
async prependListItem(connName, db, key, values) {
|
||||
try {
|
||||
const { data, success, msg } = await AddListItem(connName, db, key, 0, values)
|
||||
if (success) {
|
||||
const { left = [] } = data
|
||||
return { success, item: left }
|
||||
} else {
|
||||
return { success: false, msg }
|
||||
}
|
||||
} catch (e) {
|
||||
return { success: false, msg: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* append item to tail of list
|
||||
* @param connName
|
||||
* @param db
|
||||
* @param key
|
||||
* @param values
|
||||
* @returns {Promise<[msg]: string, success: boolean, [item]: any[]>}
|
||||
*/
|
||||
async appendListItem(connName, db, key, values) {
|
||||
try {
|
||||
const { data, success, msg } = await AddListItem(connName, db, key, 1, values)
|
||||
if (success) {
|
||||
const { right = [] } = data
|
||||
return { success, item: right }
|
||||
} else {
|
||||
return { success: false, msg }
|
||||
}
|
||||
} catch (e) {
|
||||
return { success: false, msg: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* update value of list item by index
|
||||
* @param {string} connName
|
||||
* @param {number} db
|
||||
* @param {string} key
|
||||
* @param {number} index
|
||||
* @param {string} value
|
||||
* @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>}
|
||||
*/
|
||||
async updateListItem(connName, db, key, index, value) {
|
||||
try {
|
||||
const { data, success, msg } = await SetListItem(connName, db, key, index, value)
|
||||
if (success) {
|
||||
const { updated = {} } = data
|
||||
return { success, updated }
|
||||
} else {
|
||||
return { success, msg }
|
||||
}
|
||||
} catch (e) {
|
||||
return { success: false, msg: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* remove list item
|
||||
* @param {string} connName
|
||||
* @param {number} db
|
||||
* @param {string} key
|
||||
* @param {number} index
|
||||
* @returns {Promise<{[msg]: string, success: boolean, [removed]: string[]}>}
|
||||
*/
|
||||
async removeListItem(connName, db, key, index) {
|
||||
try {
|
||||
const { data, success, msg } = await SetListItem(connName, db, key, index, '')
|
||||
if (success) {
|
||||
const { removed = [] } = data
|
||||
return { success, removed }
|
||||
} else {
|
||||
return { success, msg }
|
||||
}
|
||||
} catch (e) {
|
||||
return { success: false, msg: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* add item to set
|
||||
* @param {string} connName
|
||||
* @param {number} db
|
||||
* @param {string} key
|
||||
* @param {string} value
|
||||
* @returns {Promise<{[msg]: string, success: boolean}>}
|
||||
*/
|
||||
async addSetItem(connName, db, key, value) {
|
||||
try {
|
||||
const { success, msg } = await SetSetItem(connName, db, key, false, [value])
|
||||
if (success) {
|
||||
return { success }
|
||||
} else {
|
||||
return { success, msg }
|
||||
}
|
||||
} catch (e) {
|
||||
return { success: false, msg: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* update value of set item
|
||||
* @param {string} connName
|
||||
* @param {number} db
|
||||
* @param {string} key
|
||||
* @param {string} value
|
||||
* @param {string} newValue
|
||||
* @returns {Promise<{[msg]: string, success: boolean}>}
|
||||
*/
|
||||
async updateSetItem(connName, db, key, value, newValue) {
|
||||
try {
|
||||
const { success, msg } = await UpdateSetItem(connName, db, key, value, newValue)
|
||||
if (success) {
|
||||
return { success: true }
|
||||
} else {
|
||||
return { success, msg }
|
||||
}
|
||||
} catch (e) {
|
||||
return { success: false, msg: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* remove item from set
|
||||
* @param connName
|
||||
* @param db
|
||||
* @param key
|
||||
* @param value
|
||||
* @returns {Promise<{[msg]: string, success: boolean}>}
|
||||
*/
|
||||
async removeSetItem(connName, db, key, value) {
|
||||
try {
|
||||
const { success, msg } = await SetSetItem(connName, db, key, true, [value])
|
||||
if (success) {
|
||||
return { success }
|
||||
} else {
|
||||
return { success, msg }
|
||||
}
|
||||
} catch (e) {
|
||||
return { success: false, msg: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* add item to sorted set
|
||||
* @param {string} connName
|
||||
* @param {number} db
|
||||
* @param {string} key
|
||||
* @param {number} action
|
||||
* @param {Object.<string, number>} vs value: score
|
||||
* @returns {Promise<{[msg]: string, success: boolean}>}
|
||||
*/
|
||||
async addZSetItem(connName, db, key, action, vs) {
|
||||
try {
|
||||
const { success, msg } = await AddZSetValue(connName, db, key, action, vs)
|
||||
if (success) {
|
||||
return { success }
|
||||
} else {
|
||||
return { success, msg }
|
||||
}
|
||||
} catch (e) {
|
||||
return { success: false, msg: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* update item of sorted set
|
||||
* @param {string} connName
|
||||
* @param {number} db
|
||||
* @param {string} key
|
||||
* @param {string} value
|
||||
* @param {string} newValue
|
||||
* @param {number} score
|
||||
* @returns {Promise<{[msg]: string, success: boolean, [updated]: {}, [removed]: []}>}
|
||||
*/
|
||||
async updateZSetItem(connName, db, key, value, newValue, score) {
|
||||
try {
|
||||
const { data, success, msg } = await UpdateZSetValue(connName, db, key, value, newValue, score)
|
||||
if (success) {
|
||||
const { updated, removed } = data
|
||||
return { success, updated, removed }
|
||||
} else {
|
||||
return { success, msg }
|
||||
}
|
||||
} catch (e) {
|
||||
return { success: false, msg: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* remove item from sorted set
|
||||
* @param {string} connName
|
||||
* @param {number} db
|
||||
* @param key
|
||||
* @param {string} value
|
||||
* @returns {Promise<{[msg]: string, success: boolean, [removed]: []}>}
|
||||
*/
|
||||
async removeZSetItem(connName, db, key, value) {
|
||||
try {
|
||||
const { data, success, msg } = await UpdateZSetValue(connName, db, key, value, '', 0)
|
||||
if (success) {
|
||||
const { removed } = data
|
||||
return { success, removed }
|
||||
} else {
|
||||
return { success, msg }
|
||||
}
|
||||
} catch (e) {
|
||||
return { success: false, msg: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* reset key's ttl
|
||||
* @param {string} connName
|
||||
* @param {number} db
|
||||
* @param {string} key
|
||||
* @param {number} ttl
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async setTTL(connName, db, key, ttl) {
|
||||
try {
|
||||
const { success, msg } = await SetKeyTTL(connName, db, key, ttl)
|
||||
return success === true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} connName
|
||||
* @param {number} db
|
||||
* @param {string} key
|
||||
* @private
|
||||
*/
|
||||
_removeKey(connName, db, key) {
|
||||
const dbs = this.databases[connName]
|
||||
const dbDetail = get(dbs, db, {})
|
||||
|
||||
if (dbDetail == null) {
|
||||
return
|
||||
}
|
||||
|
||||
const descendantChain = [dbDetail]
|
||||
const keyPart = split(key, separator)
|
||||
let redisKey = ''
|
||||
const keyLen = size(keyPart)
|
||||
let deleted = false
|
||||
let forceBreak = false
|
||||
for (let i = 0; i < keyLen && !forceBreak; i++) {
|
||||
redisKey += keyPart[i]
|
||||
|
||||
const node = last(descendantChain)
|
||||
const nodeList = get(node, 'children', [])
|
||||
const len = size(nodeList)
|
||||
const isLastKeyPart = i === keyLen - 1
|
||||
for (let j = 0; j < len; j++) {
|
||||
const treeKey = get(nodeList[j], 'key')
|
||||
const currentKey = `${connName}/db${db}/${redisKey}@${
|
||||
isLastKeyPart ? ConnectionType.RedisValue : ConnectionType.RedisKey
|
||||
}`
|
||||
if (treeKey > currentKey) {
|
||||
// out of search range, target not exists
|
||||
forceBreak = true
|
||||
break
|
||||
} else if (treeKey === currentKey) {
|
||||
if (isLastKeyPart) {
|
||||
// find target
|
||||
nodeList.splice(j, 1)
|
||||
node.keys -= 1
|
||||
deleted = true
|
||||
forceBreak = true
|
||||
} else {
|
||||
// find into it's children
|
||||
descendantChain.push(nodeList[j])
|
||||
redisKey += separator
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (forceBreak) {
|
||||
break
|
||||
}
|
||||
}
|
||||
// console.log(JSON.stringify(descendantChain))
|
||||
|
||||
// update ancestor node's info
|
||||
if (deleted) {
|
||||
const desLen = size(descendantChain)
|
||||
for (let i = desLen - 1; i > 0; i--) {
|
||||
const children = get(descendantChain[i], 'children', [])
|
||||
const parent = descendantChain[i - 1]
|
||||
if (isEmpty(children)) {
|
||||
const parentChildren = get(parent, 'children', [])
|
||||
const k = get(descendantChain[i], 'key')
|
||||
remove(parentChildren, (item) => item.key === k)
|
||||
}
|
||||
parent.keys -= 1
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* remove redis key
|
||||
* @param {string} connName
|
||||
* @param {number} db
|
||||
* @param {string} key
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async removeKey(connName, db, key) {
|
||||
try {
|
||||
const { data, success, msg } = await RemoveKey(connName, db, key)
|
||||
if (success) {
|
||||
// update tree view data
|
||||
this._removeKey(connName, db, key)
|
||||
|
||||
// set tab content empty
|
||||
const tab = useTabStore()
|
||||
tab.emptyTab(connName)
|
||||
return true
|
||||
}
|
||||
} finally {
|
||||
}
|
||||
return false
|
||||
},
|
||||
|
||||
/**
|
||||
* rename key
|
||||
* @param {string} connName
|
||||
* @param {number} db
|
||||
* @param {string} key
|
||||
* @param {string} newKey
|
||||
* @returns {Promise<{[msg]: string, success: boolean}>}
|
||||
*/
|
||||
async renameKey(connName, db, key, newKey) {
|
||||
const { success = false, msg } = await RenameKey(connName, db, key, newKey)
|
||||
if (success) {
|
||||
// delete old key and add new key struct
|
||||
this._removeKey(connName, db, key)
|
||||
this._addKey(connName, db, newKey)
|
||||
return { success: true }
|
||||
} else {
|
||||
return { success: false, msg }
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export default useConnectionStore
|
|
@ -6,7 +6,6 @@ import {
|
|||
AddZSetValue,
|
||||
CloseConnection,
|
||||
GetKeyValue,
|
||||
ListConnection,
|
||||
OpenConnection,
|
||||
OpenDatabase,
|
||||
RemoveKey,
|
||||
|
@ -21,12 +20,13 @@ import {
|
|||
} from '../../wailsjs/go/services/connectionService.js'
|
||||
import { ConnectionType } from '../consts/connection_type.js'
|
||||
import useTabStore from './tab.js'
|
||||
import useConnectionStore from './connections.js'
|
||||
|
||||
const separator = ':'
|
||||
|
||||
const useConnectionStore = defineStore('connection', {
|
||||
const useDatabaseStore = defineStore('database', {
|
||||
/**
|
||||
* @typedef {Object} ConnectionItem
|
||||
* @typedef {Object} DatabaseItem
|
||||
* @property {string} key
|
||||
* @property {string} label
|
||||
* @property {string} name - server name, type != ConnectionType.Group only
|
||||
|
@ -40,62 +40,14 @@ const useConnectionStore = defineStore('connection', {
|
|||
|
||||
/**
|
||||
*
|
||||
* @returns {{connections: ConnectionItem[]}}
|
||||
* @returns {{connections: DatabaseItem[]}}
|
||||
*/
|
||||
state: () => ({
|
||||
connections: [], // all connections list
|
||||
databases: {}, // all database group by opened connections
|
||||
}),
|
||||
getters: {},
|
||||
actions: {
|
||||
/**
|
||||
* Load all store connections struct from local profile
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async initConnection() {
|
||||
if (!isEmpty(this.connections)) {
|
||||
return
|
||||
}
|
||||
const { data = [{ groupName: '', connections: [] }] } = await ListConnection()
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const group = data[i]
|
||||
// Top level group
|
||||
if (isEmpty(group.groupName)) {
|
||||
for (let j = 0; j < group.connections.length; j++) {
|
||||
const item = group.connections[j]
|
||||
this.connections.push({
|
||||
key: item.name,
|
||||
label: item.name,
|
||||
name: item.name,
|
||||
type: ConnectionType.Server,
|
||||
// isLeaf: false,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Custom group
|
||||
const children = []
|
||||
for (let j = 0; j < group.connections.length; j++) {
|
||||
const item = group.connections[j]
|
||||
const value = group.groupName + '/' + item.name
|
||||
children.push({
|
||||
key: value,
|
||||
label: item.name,
|
||||
name: item.name,
|
||||
type: ConnectionType.Server,
|
||||
children: j === group.connections.length - 1 ? undefined : [],
|
||||
// isLeaf: false,
|
||||
})
|
||||
}
|
||||
this.connections.push({
|
||||
key: group.groupName,
|
||||
label: group.groupName,
|
||||
type: ConnectionType.Group,
|
||||
children,
|
||||
})
|
||||
}
|
||||
}
|
||||
console.debug(JSON.stringify(this.connections))
|
||||
},
|
||||
|
||||
/**
|
||||
* Open connection
|
||||
* @param {string} connName
|
||||
|
@ -107,7 +59,8 @@ const useConnectionStore = defineStore('connection', {
|
|||
throw new Error(msg)
|
||||
}
|
||||
// append to db node to current connection
|
||||
const connNode = this.getConnection(connName)
|
||||
const connStore = useConnectionStore()
|
||||
const connNode = connStore.getConnection(connName)
|
||||
if (connNode == null) {
|
||||
throw new Error('no such connection')
|
||||
}
|
||||
|
@ -901,4 +854,4 @@ const useConnectionStore = defineStore('connection', {
|
|||
},
|
||||
})
|
||||
|
||||
export default useConnectionStore
|
||||
export default useDatabaseStore
|
|
@ -1,4 +1,4 @@
|
|||
import { find, findIndex, isEmpty, size } from 'lodash'
|
||||
import { find, findIndex, size } from 'lodash'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
const useTabStore = defineStore('tab', {
|
||||
|
@ -21,6 +21,8 @@ const useTabStore = defineStore('tab', {
|
|||
* @returns {{tabList: TabItem[], activatedTab: string, activatedIndex: number}}
|
||||
*/
|
||||
state: () => ({
|
||||
nav: 'server',
|
||||
asideWidth: 300,
|
||||
tabList: [],
|
||||
activatedTab: '',
|
||||
activatedIndex: 0, // current activated tab index
|
||||
|
@ -31,9 +33,9 @@ const useTabStore = defineStore('tab', {
|
|||
* @returns {TabItem[]}
|
||||
*/
|
||||
tabs() {
|
||||
if (isEmpty(this.tabList)) {
|
||||
this.newBlankTab()
|
||||
}
|
||||
// if (isEmpty(this.tabList)) {
|
||||
// this.newBlankTab()
|
||||
// }
|
||||
return this.tabList
|
||||
},
|
||||
|
||||
|
@ -53,7 +55,7 @@ const useTabStore = defineStore('tab', {
|
|||
currentTabIndex() {
|
||||
const len = size(this.tabs)
|
||||
if (this.activatedIndex < 0 || this.activatedIndex >= len) {
|
||||
this.activatedIndex = 0
|
||||
this._setActivatedIndex(0)
|
||||
}
|
||||
return this.tabs[this.activatedIndex]
|
||||
},
|
||||
|
@ -68,17 +70,22 @@ const useTabStore = defineStore('tab', {
|
|||
title: 'new tab',
|
||||
blank: true,
|
||||
})
|
||||
this.activatedIndex = size(this.tabList) - 1
|
||||
this._setActivatedIndex(size(this.tabList) - 1)
|
||||
},
|
||||
|
||||
_setActivatedIndex(idx) {
|
||||
this.activatedIndex = idx
|
||||
this.nav = idx >= 0 ? 'structure' : 'server'
|
||||
},
|
||||
|
||||
/**
|
||||
* update or insert a new tab if not exists with the same name
|
||||
* @param {string} server
|
||||
* @param {number} db
|
||||
* @param {number} type
|
||||
* @param {number} ttl
|
||||
* @param {string} key
|
||||
* @param {*} value
|
||||
* @param {number} [db]
|
||||
* @param {number} [type]
|
||||
* @param {number} [ttl]
|
||||
* @param {string} [key]
|
||||
* @param {*} [value]
|
||||
*/
|
||||
upsertTab({ server, db, type, ttl, key, value }) {
|
||||
let tabIndex = findIndex(this.tabList, { name: server })
|
||||
|
@ -90,20 +97,21 @@ const useTabStore = defineStore('tab', {
|
|||
type,
|
||||
ttl,
|
||||
key,
|
||||
value,
|
||||
valu,
|
||||
})
|
||||
tabIndex = this.tabList.length - 1
|
||||
}
|
||||
const tab = this.tabList[tabIndex]
|
||||
tab.blank = false
|
||||
tab.title = `${server}/db${db}`
|
||||
// tab.title = db !== undefined ? `${server}/db${db}` : `${server}`
|
||||
tab.title = server
|
||||
tab.server = server
|
||||
tab.db = db
|
||||
tab.type = type
|
||||
tab.ttl = ttl
|
||||
tab.key = key
|
||||
tab.value = value
|
||||
this.activatedIndex = tabIndex
|
||||
this._setActivatedIndex(tabIndex)
|
||||
// this.activatedTab = tab.name
|
||||
},
|
||||
|
||||
|
@ -149,23 +157,27 @@ const useTabStore = defineStore('tab', {
|
|||
const len = size(this.tabs)
|
||||
// ignore remove last blank tab
|
||||
if (len === 1 && this.tabs[0].blank) {
|
||||
return
|
||||
return null
|
||||
}
|
||||
|
||||
if (tabIndex < 0 || tabIndex >= len) {
|
||||
return
|
||||
return null
|
||||
}
|
||||
this.tabList.splice(tabIndex, 1)
|
||||
const removed = this.tabList.splice(tabIndex, 1)
|
||||
|
||||
if (len === 1) {
|
||||
this.newBlankTab()
|
||||
} else {
|
||||
// Update select index if removed index equal current selected
|
||||
// update select index if removed index equal current selected
|
||||
this.activatedIndex -= 1
|
||||
if (this.activatedIndex < 0 && this.tabList.length > 0) {
|
||||
this.activatedIndex = 0
|
||||
if (this.activatedIndex < 0) {
|
||||
if (this.tabList.length > 0) {
|
||||
this._setActivatedIndex(0)
|
||||
} else {
|
||||
this._setActivatedIndex(-1)
|
||||
}
|
||||
} else {
|
||||
this._setActivatedIndex(this.activatedIndex)
|
||||
}
|
||||
|
||||
return size(removed) > 0 ? removed[0] : null
|
||||
},
|
||||
removeTabByName(tabName) {
|
||||
const idx = findIndex(this.tabs, { name: tabName })
|
||||
|
|
|
@ -104,6 +104,6 @@ body {
|
|||
}
|
||||
|
||||
.context-menu-item {
|
||||
min-width: 120px;
|
||||
min-width: 100px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue