perf: move server status pane into browser pane, add server node to the top of database tree list

This commit is contained in:
tiny-craft 2023-07-14 16:17:59 +08:00
parent 484cdcf54e
commit 0bc8335eef
9 changed files with 164 additions and 125 deletions

View File

@ -129,7 +129,7 @@ func (c *connectionService) SaveSortedConnection(sortedConns types.Connections)
return
}
// CreateGroup create new group
// CreateGroup create a new group
func (c *connectionService) CreateGroup(name string) (resp types.JSResp) {
err := c.conns.CreateGroup(name)
if err != nil {
@ -151,7 +151,7 @@ func (c *connectionService) RenameGroup(name, newName string) (resp types.JSResp
return
}
// DeleteGroup remove group by name
// DeleteGroup remove a group by name
func (c *connectionService) DeleteGroup(name string, includeConn bool) (resp types.JSResp) {
err := c.conns.DeleteGroup(name, includeConn)
if err != nil {

View File

@ -1,5 +1,5 @@
<script setup>
import { computed } from 'vue'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { types } from '../../consts/support_redis_type.js'
import ContentValueHash from '../content_value/ContentValueHash.vue'
import ContentValueList from '../content_value/ContentValueList.vue'
@ -12,6 +12,35 @@ import { useDialog } from 'naive-ui'
import useConnectionStore from '../../stores/connections.js'
import { useI18n } from 'vue-i18n'
import { useConfirmDialog } from '../../utils/confirm_dialog.js'
import ContentServerStatus from '../content_value/ContentServerStatus.vue'
const serverInfo = ref({})
const autoRefresh = ref(false)
const serverName = computed(() => {
if (tabContent.value != null) {
return tabContent.value.name
}
return ''
})
const refreshInfo = async () => {
if (!isEmpty(serverName.value) && connectionStore.isConnected(serverName.value)) {
serverInfo.value = await connectionStore.getServerInfo(serverName.value)
}
}
let intervalId
onMounted(() => {
refreshInfo()
intervalId = setInterval(() => {
if (autoRefresh.value) {
refreshInfo()
}
}, 5000)
})
onUnmounted(() => {
clearInterval(intervalId)
})
const valueComponents = {
[types.STRING]: ContentValueString,
@ -31,10 +60,6 @@ const tab = computed(() =>
}))
)
/**
*
* @type {ComputedRef<TabItem>}
*/
const tabContent = computed(() => {
const tab = tabStore.currentTab
if (tab == null) {
@ -50,6 +75,14 @@ const tabContent = computed(() => {
}
})
const showServerStatus = computed(() => {
return tabContent.value == null || isEmpty(tabContent.value.keyPath)
})
const showNonexists = computed(() => {
return tabContent.value.value == null
})
const onUpdateValue = (tabIndex) => {
tabStore.switchTab(tabIndex)
}
@ -95,10 +128,11 @@ const onCloseTab = (tabIndex) => {
</n-tabs>
<!-- TODO: add loading status -->
<div v-if="tabContent == null || isEmpty(tabContent.keyPath)" class="flex-item-expand flex-box-v">
<n-empty :description="$t('empty_tab_content')" class="empty-content" />
<div v-if="showServerStatus" class="content-container flex-item-expand flex-box-v">
<!-- select nothing or select server node, display server status -->
<content-server-status v-model:auto-refresh="autoRefresh" :server="serverName" :info="serverInfo" />
</div>
<div v-else-if="tabContent.value == null" class="flex-item-expand flex-box-v">
<div v-else-if="showNonexists" class="content-container flex-item-expand flex-box-v">
<n-empty :description="$t('nonexist_tab_content')" class="empty-content">
<template #extra>
<n-button @click="onReloadKey">{{ $t('reload') }}</n-button>
@ -119,6 +153,12 @@ const onCloseTab = (tabIndex) => {
<style lang="scss" scoped>
@import 'content';
.content-container {
padding: 5px;
box-sizing: border-box;
}
//.tab-item {
// gap: 5px;
// padding: 0 5px 0 10px;

View File

@ -1,45 +1,14 @@
<script setup>
import useDialog from '../../stores/dialog.js'
import AddLink from '../icons/AddLink.vue'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import useConnectionStore from '../../stores/connections.js'
import { isEmpty } from 'lodash'
import ContentServerStatus from '../content_value/ContentServerStatus.vue'
import { useDialog } from 'naive-ui'
const dialogStore = useDialog()
const connectionStore = useConnectionStore()
const serverInfo = ref({})
const autoRefresh = ref(true)
const refreshInfo = async () => {
const server = connectionStore.selectedServer
if (!isEmpty(server) && connectionStore.isConnected(server)) {
serverInfo.value = await connectionStore.getServerInfo(server)
}
}
let intervalId
onMounted(() => {
intervalId = setInterval(() => {
if (autoRefresh.value) {
refreshInfo()
}
}, 5000)
})
onUnmounted(() => {
clearInterval(intervalId)
})
watch(() => connectionStore.selectedServer, refreshInfo)
const hasContent = computed(() => !isEmpty(serverInfo.value))
</script>
<template>
<div class="content-container flex-box-v" :style="{ 'justify-content': hasContent ? 'flex-start' : 'center' }">
<div class="content-container flex-box-v">
<!-- TODO: replace icon to app icon -->
<n-empty v-if="!hasContent" :description="$t('empty_server_content')">
<n-empty :description="$t('empty_server_content')">
<template #extra>
<n-button @click="dialogStore.openNewDialog()">
<template #icon>
@ -49,12 +18,6 @@ const hasContent = computed(() => !isEmpty(serverInfo.value))
</n-button>
</template>
</n-empty>
<content-server-status
v-else
v-model:auto-refresh="autoRefresh"
:server="connectionStore.selectedServer"
:info="serverInfo"
/>
</div>
</template>
@ -62,7 +25,7 @@ const hasContent = computed(() => !isEmpty(serverInfo.value))
@import 'content';
.content-container {
//justify-content: center;
justify-content: center;
padding: 5px;
box-sizing: border-box;
}

View File

@ -123,8 +123,8 @@ const onFilterInfo = (val) => {
</n-gi>
<n-gi :span="6">
<n-statistic :value="totalKeys">
<template #label
>{{ $t('total_keys') }}
<template #label>
{{ $t('total_keys') }}
<n-icon :component="Help" />
</template>
</n-statistic>

View File

@ -1,10 +1,10 @@
<script setup>
import { h, nextTick, onMounted, reactive, ref } from 'vue'
import { computed, h, nextTick, onMounted, reactive, ref } from 'vue'
import { ConnectionType } from '../../consts/connection_type.js'
import { NIcon, useDialog, useMessage } from 'naive-ui'
import Key from '../icons/Key.vue'
import ToggleDb from '../icons/ToggleDb.vue'
import { indexOf, isEmpty } from 'lodash'
import { get, indexOf, isEmpty } from 'lodash'
import { useI18n } from 'vue-i18n'
import Refresh from '../icons/Refresh.vue'
import CopyLink from '../icons/CopyLink.vue'
@ -16,15 +16,33 @@ import useDialogStore from '../../stores/dialog.js'
import { ClipboardSetText } from '../../../wailsjs/runtime/runtime.js'
import useConnectionStore from '../../stores/connections.js'
import { useConfirmDialog } from '../../utils/confirm_dialog.js'
import ToggleServer from '../icons/ToggleServer.vue'
import Unlink from '../icons/Unlink.vue'
const props = defineProps({
server: String,
})
const i18n = useI18n()
const loading = ref(false)
const loadingConnections = ref(false)
const expandedKeys = ref([])
const selectedKeys = ref([])
const expandedKeys = ref([props.server])
const selectedKeys = ref([props.server])
const connectionStore = useConnectionStore()
const dialogStore = useDialogStore()
const data = computed(() => {
const dbs = get(connectionStore.databases, props.server, [])
return [
{
key: props.server,
label: props.server,
type: ConnectionType.Server,
children: dbs,
},
]
})
const contextMenuParam = reactive({
show: false,
x: 0,
@ -40,6 +58,21 @@ const renderIcon = (icon) => {
}
}
const menuOptions = {
[ConnectionType.Server]: () => {
console.log('open server context')
return [
{
key: 'server_reload',
label: i18n.t('reload'),
icon: renderIcon(Refresh),
},
{
key: 'server_close',
label: i18n.t('disconnect'),
icon: renderIcon(Unlink),
},
]
},
[ConnectionType.RedisDB]: ({ opened }) => {
if (opened) {
return [
@ -127,10 +160,6 @@ onMounted(async () => {
}
})
const props = defineProps({
server: String,
})
const expandKey = (key) => {
const idx = indexOf(expandedKeys.value, key)
if (idx === -1) {
@ -159,24 +188,37 @@ const onUpdateExpanded = (value, option, meta) => {
}
const onUpdateSelectedKeys = (keys, options) => {
try {
if (!isEmpty(options)) {
// prevent load duplicate key
for (const node of options) {
if (node.type === ConnectionType.RedisValue) {
const { key, name, db, redisKey } = node
const { key, db, redisKey } = node
if (indexOf(selectedKeys.value, key) === -1) {
connectionStore.loadKeyValue(name, db, redisKey)
}
break
connectionStore.loadKeyValue(props.server, db, redisKey)
}
return
}
}
// default is load blank key to display server status
connectionStore.loadKeyValue(props.server, 0)
}
} finally {
selectedKeys.value = keys
}
}
const renderPrefix = ({ option }) => {
switch (option.type) {
case ConnectionType.Server:
return h(
NIcon,
{ size: 20 },
{
default: () => h(ToggleServer, { modelValue: false }),
}
)
case ConnectionType.RedisDB:
return h(
NIcon,
@ -258,7 +300,7 @@ const onLoadTree = async (node) => {
case ConnectionType.RedisDB:
loading.value = true
try {
await connectionStore.openDatabase(node.name, node.db)
await connectionStore.openDatabase(props.server, node.db)
} catch (e) {
message.error(e.message)
node.isLeaf = undefined
@ -276,30 +318,38 @@ const onLoadTree = async (node) => {
const confirmDialog = useConfirmDialog()
const handleSelectContextMenu = (key) => {
contextMenuParam.show = false
const { name, db, key: nodeKey, redisKey } = contextMenuParam.currentNode
const { db, key: nodeKey, redisKey } = contextMenuParam.currentNode
switch (key) {
case 'server_reload':
connectionStore.openConnection(props.server, true).then(() => {
message.success(i18n.t('reload_succ'))
})
break
case 'server_close':
connectionStore.closeConnection(props.server)
break
case 'db_open':
nextTick().then(() => expandKey(nodeKey))
break
case 'db_reload':
connectionStore.reopenDatabase(name, db)
connectionStore.reopenDatabase(props.server, db)
break
case 'db_newkey':
case 'key_newkey':
dialogStore.openNewKeyDialog(redisKey, name, db)
dialogStore.openNewKeyDialog(redisKey, props.server, db)
break
case 'key_reload':
connectionStore.loadKeys(name, db, redisKey)
connectionStore.loadKeys(props.server, db, redisKey)
break
case 'value_reload':
connectionStore.loadKeyValue(name, db, redisKey)
connectionStore.loadKeyValue(props.server, db, redisKey)
break
case 'key_remove':
dialogStore.openDeleteKeyDialog(name, db, redisKey + ':*')
dialogStore.openDeleteKeyDialog(props.server, db, redisKey + ':*')
break
case 'value_remove':
confirmDialog.warning(i18n.t('remove_tip', { name: redisKey }), () => {
connectionStore.deleteKey(name, db, redisKey).then((success) => {
connectionStore.deleteKey(props.server, db, redisKey).then((success) => {
if (success) {
message.success(i18n.t('delete_key_succ', { key: redisKey }))
}
@ -334,7 +384,7 @@ const handleOutsideContextMenu = () => {
:block-node="true"
:animated="false"
:cancelable="false"
:data="connectionStore.databases[props.server] || []"
:data="data"
:expand-on-click="false"
:expanded-keys="expandedKeys"
:selected-keys="selectedKeys"

View File

@ -27,18 +27,6 @@ const message = useMessage()
const expandedKeys = ref([])
const selectedKeys = ref([])
watch(selectedKeys, () => {
const key = selectedKeys.value[0]
// try to remove group name
const kparts = split(key, '/')
const len = size(kparts)
if (len > 1) {
connectionStore.selectedServer = kparts[len - 1]
} else {
connectionStore.selectedServer = selectedKeys.value[0]
}
})
const props = defineProps({
filterPattern: {
type: String,

View File

@ -124,7 +124,6 @@
"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 detail.",
"nonexist_tab_content": "The selected key does not exist. Please retry",
"empty_server_content": "Connect server from left list",
"reload_when_succ": "Reload immediately after success",

View File

@ -127,7 +127,6 @@
"field_required": "此项不能为空",
"spec_field_required": "{key} 不能为空",
"no_connections": "空空如也",
"empty_tab_content": "可以从左边选择键来查看键的详细内容",
"nonexist_tab_content": "所选键不存在,请尝试刷新重试",
"empty_server_content": "可以从左边选择并打开连接",
"reload_when_succ": "操作成功后立即重新加载",

View File

@ -1,5 +1,5 @@
import { defineStore } from 'pinia'
import { endsWith, findIndex, get, isEmpty, last, size, split, uniq } from 'lodash'
import { endsWith, findIndex, get, isEmpty, size, split, uniq } from 'lodash'
import {
AddHashField,
AddListItem,
@ -71,7 +71,6 @@ const useConnectionStore = defineStore('connections', {
state: () => ({
groups: [], // all group name set
connections: [], // all connections
selectedServer: '', // current selected server
serverStats: {}, // current server status info
databases: {}, // all databases in opened connections group by server name
nodeMap: {}, // all node in opened connections group by server+db and key+type
@ -301,6 +300,7 @@ const useConnectionStore = defineStore('connections', {
}
delete this.databases[name]
delete this.serverStats[name]
const tabStore = useTabStore()
tabStore.removeTabByName(name)
@ -308,7 +308,7 @@ const useConnectionStore = defineStore('connections', {
},
/**
* close all connection
* close all connections
* @returns {Promise<void>}
*/
async closeAllConnection() {
@ -338,7 +338,7 @@ const useConnectionStore = defineStore('connections', {
},
/**
* create connection group
* create a connection group
* @param name
* @returns {Promise<{success: boolean, [msg]: string}>}
*/
@ -441,14 +441,15 @@ const useConnectionStore = defineStore('connections', {
/**
* load redis key
* @param server
* @param db
* @param key
* @param {string} server
* @param {number} db
* @param {string} [key] when key is null or blank, update tab to display normal content (blank content or server status)
*/
async loadKeyValue(server, db, key) {
try {
const { data, success, msg } = await GetKeyValue(server, db, key)
const tab = useTabStore()
if (!isEmpty(key)) {
const { data, success, msg } = await GetKeyValue(server, db, key)
if (success) {
const { type, ttl, value } = data
tab.upsertTab({
@ -459,16 +460,18 @@ const useConnectionStore = defineStore('connections', {
key,
value,
})
} else {
return
}
}
tab.upsertTab({
server,
db,
type: 'none',
ttl: -1,
key,
key: null,
value: null,
})
}
} finally {
}
},
@ -582,7 +585,6 @@ const useConnectionStore = defineStore('connections', {
selectedNode = {
key: `${connName}/db${db}${nodeKey}`,
label: keyPart[i],
name: connName,
db,
keys: 0,
redisKey: handlePath,
@ -601,7 +603,6 @@ const useConnectionStore = defineStore('connections', {
const selectedNode = {
key: `${connName}/db${db}${nodeKey}`,
label: keyPart[i],
name: connName,
db,
keys: 0,
redisKey: handlePath,
@ -612,7 +613,6 @@ const useConnectionStore = defineStore('connections', {
children.push(selectedNode)
}
}
console.log('count:', ++count)
}
},