perf: move server status pane into browser pane, add server node to the top of database tree list
This commit is contained in:
parent
484cdcf54e
commit
0bc8335eef
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -127,7 +127,6 @@
|
|||
"field_required": "此项不能为空",
|
||||
"spec_field_required": "{key} 不能为空",
|
||||
"no_connections": "空空如也",
|
||||
"empty_tab_content": "可以从左边选择键来查看键的详细内容",
|
||||
"nonexist_tab_content": "所选键不存在,请尝试刷新重试",
|
||||
"empty_server_content": "可以从左边选择并打开连接",
|
||||
"reload_when_succ": "操作成功后立即重新加载",
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
},
|
||||
|
||||
|
|
Loading…
Reference in New Issue