perf: create "content-pane" component for each tab to maintain data and status easily
This commit is contained in:
parent
54864fe7d5
commit
297286f150
|
@ -208,7 +208,12 @@ onMounted(async () => {
|
||||||
@mouseout="data.hoverResize = false"
|
@mouseout="data.hoverResize = false"
|
||||||
@mouseover="data.hoverResize = true" />
|
@mouseover="data.hoverResize = true" />
|
||||||
</div>
|
</div>
|
||||||
<content-pane class="flex-item-expand" />
|
<content-pane
|
||||||
|
v-for="t in tabStore.tabs"
|
||||||
|
v-show="get(tabStore.currentTab, 'name') === t.name"
|
||||||
|
:key="t.name"
|
||||||
|
:server="t.name"
|
||||||
|
class="flex-item-expand" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- server list page -->
|
<!-- server list page -->
|
||||||
|
|
|
@ -73,10 +73,10 @@ defineExpose({
|
||||||
<template>
|
<template>
|
||||||
<n-card
|
<n-card
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
|
:theme-overrides="{ borderRadius: '0px' }"
|
||||||
:title="$t('log.launch_log')"
|
:title="$t('log.launch_log')"
|
||||||
class="content-container flex-box-v"
|
class="content-container flex-box-v"
|
||||||
content-style="display: flex;flex-direction: column; overflow: hidden; backgroundColor: gray"
|
content-style="display: flex;flex-direction: column; overflow: hidden; backgroundColor: gray">
|
||||||
:theme-overrides="{ borderRadius: '0px' }">
|
|
||||||
<n-form :disabled="data.loading" class="flex-item" inline>
|
<n-form :disabled="data.loading" class="flex-item" inline>
|
||||||
<n-form-item :label="$t('log.filter_server')">
|
<n-form-item :label="$t('log.filter_server')">
|
||||||
<n-select
|
<n-select
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted, onUnmounted, reactive, watch } from 'vue'
|
import { computed, nextTick, ref, watch } from 'vue'
|
||||||
import { get, isEmpty, keyBy, map, size, toUpper } from 'lodash'
|
import { find, map, toUpper } from 'lodash'
|
||||||
import useTabStore from 'stores/tab.js'
|
import useTabStore from 'stores/tab.js'
|
||||||
import useConnectionStore from 'stores/connections.js'
|
|
||||||
import ContentServerStatus from '@/components/content_value/ContentServerStatus.vue'
|
import ContentServerStatus from '@/components/content_value/ContentServerStatus.vue'
|
||||||
import Status from '@/components/icons/Status.vue'
|
import Status from '@/components/icons/Status.vue'
|
||||||
import { useThemeVars } from 'naive-ui'
|
import { useThemeVars } from 'naive-ui'
|
||||||
|
@ -24,98 +23,10 @@ const themeVars = useThemeVars()
|
||||||
* @property {boolean} autoLoading loading status for auto refresh
|
* @property {boolean} autoLoading loading status for auto refresh
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
const props = defineProps({
|
||||||
*
|
server: String,
|
||||||
* @type {UnwrapNestedRefs<Object.<string, ServerStatusItem>>}
|
|
||||||
*/
|
|
||||||
const serverStatusTab = reactive({})
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} serverName
|
|
||||||
* @return {UnwrapRef<ServerStatusItem>}
|
|
||||||
*/
|
|
||||||
const getServerInfo = (serverName) => {
|
|
||||||
if (isEmpty(serverName)) {
|
|
||||||
return {
|
|
||||||
name: serverName,
|
|
||||||
info: {},
|
|
||||||
autoRefresh: false,
|
|
||||||
autoLoading: false,
|
|
||||||
loading: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!serverStatusTab.hasOwnProperty(serverName)) {
|
|
||||||
serverStatusTab[serverName] = {
|
|
||||||
name: serverName,
|
|
||||||
info: {},
|
|
||||||
autoRefresh: false,
|
|
||||||
autoLoading: false,
|
|
||||||
loading: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return serverStatusTab[serverName]
|
|
||||||
}
|
|
||||||
const serverName = computed(() => {
|
|
||||||
if (tabContent.value != null) {
|
|
||||||
return tabContent.value.name
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
})
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {ComputedRef<ServerStatusItem>}
|
|
||||||
*/
|
|
||||||
const currentServer = computed(() => {
|
|
||||||
return getServerInfo(serverName.value)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* refresh server status info
|
|
||||||
* @param {string} serverName
|
|
||||||
* @param {boolean} [force] force refresh will show loading indicator
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
const refreshInfo = async (serverName, force) => {
|
|
||||||
const info = getServerInfo(serverName)
|
|
||||||
if (force) {
|
|
||||||
info.loading = true
|
|
||||||
} else {
|
|
||||||
info.autoLoading = true
|
|
||||||
}
|
|
||||||
if (!isEmpty(serverName) && connectionStore.isConnected(serverName)) {
|
|
||||||
try {
|
|
||||||
info.info = await connectionStore.getServerInfo(serverName)
|
|
||||||
} finally {
|
|
||||||
info.loading = false
|
|
||||||
info.autoLoading = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const refreshAllInfo = async (force) => {
|
|
||||||
for (const serverName in serverStatusTab) {
|
|
||||||
await refreshInfo(serverName, force)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let intervalId
|
|
||||||
onMounted(() => {
|
|
||||||
refreshAllInfo(true)
|
|
||||||
intervalId = setInterval(() => {
|
|
||||||
for (const serverName in serverStatusTab) {
|
|
||||||
if (get(serverStatusTab, [serverName, 'autoRefresh'])) {
|
|
||||||
refreshInfo(serverName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 5000)
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
clearInterval(intervalId)
|
|
||||||
})
|
|
||||||
|
|
||||||
const connectionStore = useConnectionStore()
|
|
||||||
const tabStore = useTabStore()
|
const tabStore = useTabStore()
|
||||||
const tab = computed(() =>
|
const tab = computed(() =>
|
||||||
map(tabStore.tabs, (item) => ({
|
map(tabStore.tabs, (item) => ({
|
||||||
|
@ -124,35 +35,10 @@ const tab = computed(() =>
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(
|
|
||||||
() => tabStore.nav,
|
|
||||||
(nav) => {
|
|
||||||
if (nav === 'browser') {
|
|
||||||
refreshInfo(serverName.value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => tabStore.tabList,
|
|
||||||
(tabs) => {
|
|
||||||
if (size(tabs) < size(serverStatusTab)) {
|
|
||||||
const tabMap = keyBy(tabs, 'name')
|
|
||||||
// remove unused server status tab
|
|
||||||
for (const t in serverStatusTab) {
|
|
||||||
if (!tabMap.hasOwnProperty(t)) {
|
|
||||||
delete serverStatusTab[t]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ deep: true },
|
|
||||||
)
|
|
||||||
|
|
||||||
const tabContent = computed(() => {
|
const tabContent = computed(() => {
|
||||||
const tab = tabStore.currentTab
|
const tab = find(tabStore.tabs, { name: props.server })
|
||||||
if (tab == null) {
|
if (tab == null) {
|
||||||
return null
|
return {}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
name: tab.name,
|
name: tab.name,
|
||||||
|
@ -168,26 +54,10 @@ const tabContent = computed(() => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const showServerStatus = computed(() => {
|
|
||||||
return tabContent.value == null || isEmpty(tabContent.value.keyPath)
|
|
||||||
})
|
|
||||||
|
|
||||||
const isBlankValue = computed(() => {
|
const isBlankValue = computed(() => {
|
||||||
return tabContent.value.value == null
|
return tabContent.value.value == null
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* reload current selection key
|
|
||||||
* @returns {Promise<null>}
|
|
||||||
*/
|
|
||||||
const onReloadKey = async () => {
|
|
||||||
const tab = tabStore.currentTab
|
|
||||||
if (tab == null || isEmpty(tab.key)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
await connectionStore.loadKeyValue(tab.name, tab.db, tab.key, tab.viewAs)
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedSubTab = computed(() => {
|
const selectedSubTab = computed(() => {
|
||||||
const { subTab = 'status' } = tabStore.currentTab || {}
|
const { subTab = 'status' } = tabStore.currentTab || {}
|
||||||
return subTab
|
return subTab
|
||||||
|
@ -196,11 +66,28 @@ const selectedSubTab = computed(() => {
|
||||||
const onSwitchSubTab = (name) => {
|
const onSwitchSubTab = (name) => {
|
||||||
tabStore.switchSubTab(name)
|
tabStore.switchSubTab(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BUG: naive-ui tabs will set the bottom line to '0px' after switch to another page and back again
|
||||||
|
// watch parent tabs' changing and call 'syncBarPosition' manually
|
||||||
|
const tabsRef = ref(null)
|
||||||
|
const cliRef = ref(null)
|
||||||
|
watch(
|
||||||
|
() => tabContent.value?.name,
|
||||||
|
(name) => {
|
||||||
|
if (name === props.server) {
|
||||||
|
nextTick().then(() => {
|
||||||
|
tabsRef.value?.syncBarPosition()
|
||||||
|
cliRef.value?.resizeTerm()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="content-container flex-box-v">
|
<div class="content-container flex-box-v">
|
||||||
<n-tabs
|
<n-tabs
|
||||||
|
ref="tabsRef"
|
||||||
:tabs-padding="5"
|
:tabs-padding="5"
|
||||||
:theme-overrides="{
|
:theme-overrides="{
|
||||||
tabFontWeightActive: 'normal',
|
tabFontWeightActive: 'normal',
|
||||||
|
@ -217,7 +104,7 @@ const onSwitchSubTab = (name) => {
|
||||||
type="line"
|
type="line"
|
||||||
@update:value="onSwitchSubTab">
|
@update:value="onSwitchSubTab">
|
||||||
<!-- server status pane -->
|
<!-- server status pane -->
|
||||||
<n-tab-pane :name="BrowserTabType.Status.toString()">
|
<n-tab-pane :name="BrowserTabType.Status.toString()" display-directive="show:lazy">
|
||||||
<template #tab>
|
<template #tab>
|
||||||
<n-space :size="5" :wrap-item="false" align="center" inline justify="center">
|
<n-space :size="5" :wrap-item="false" align="center" inline justify="center">
|
||||||
<n-icon size="16">
|
<n-icon size="16">
|
||||||
|
@ -226,17 +113,11 @@ const onSwitchSubTab = (name) => {
|
||||||
<span>{{ $t('interface.sub_tab.status') }}</span>
|
<span>{{ $t('interface.sub_tab.status') }}</span>
|
||||||
</n-space>
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
<content-server-status
|
<content-server-status :server="props.server" />
|
||||||
v-model:auto-refresh="currentServer.autoRefresh"
|
|
||||||
:auto-loading="currentServer.autoLoading"
|
|
||||||
:info="currentServer.info"
|
|
||||||
:loading="currentServer.loading"
|
|
||||||
:server="currentServer.name"
|
|
||||||
@refresh="refreshInfo(currentServer.name, true)" />
|
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
|
||||||
<!-- key detail pane -->
|
<!-- key detail pane -->
|
||||||
<n-tab-pane :name="BrowserTabType.KeyDetail.toString()">
|
<n-tab-pane :name="BrowserTabType.KeyDetail.toString()" display-directive="show:lazy">
|
||||||
<template #tab>
|
<template #tab>
|
||||||
<n-space :size="5" :wrap-item="false" align="center" inline justify="center">
|
<n-space :size="5" :wrap-item="false" align="center" inline justify="center">
|
||||||
<n-icon size="16">
|
<n-icon size="16">
|
||||||
|
@ -249,20 +130,19 @@ const onSwitchSubTab = (name) => {
|
||||||
</template>
|
</template>
|
||||||
<content-value-wrapper
|
<content-value-wrapper
|
||||||
:blank="isBlankValue"
|
:blank="isBlankValue"
|
||||||
:type="tabContent.type"
|
|
||||||
:db="tabContent.db"
|
:db="tabContent.db"
|
||||||
:key-code="tabContent.keyCode"
|
:key-code="tabContent.keyCode"
|
||||||
:key-path="tabContent.keyPath"
|
:key-path="tabContent.keyPath"
|
||||||
:name="tabContent.name"
|
:name="tabContent.name"
|
||||||
:size="tabContent.size"
|
:size="tabContent.size"
|
||||||
:ttl="tabContent.ttl"
|
:ttl="tabContent.ttl"
|
||||||
|
:type="tabContent.type"
|
||||||
:value="tabContent.value"
|
:value="tabContent.value"
|
||||||
:view-as="tabContent.viewAs"
|
:view-as="tabContent.viewAs" />
|
||||||
@reload="onReloadKey" />
|
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
|
||||||
<!-- cli pane -->
|
<!-- cli pane -->
|
||||||
<n-tab-pane :name="BrowserTabType.Cli.toString()">
|
<n-tab-pane :name="BrowserTabType.Cli.toString()" display-directive="show:lazy">
|
||||||
<template #tab>
|
<template #tab>
|
||||||
<n-space :size="5" :wrap-item="false" align="center" inline justify="center">
|
<n-space :size="5" :wrap-item="false" align="center" inline justify="center">
|
||||||
<n-icon size="16">
|
<n-icon size="16">
|
||||||
|
@ -271,7 +151,7 @@ const onSwitchSubTab = (name) => {
|
||||||
<span>{{ $t('interface.sub_tab.cli') }}</span>
|
<span>{{ $t('interface.sub_tab.cli') }}</span>
|
||||||
</n-space>
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
<content-cli :name="currentServer.name" />
|
<content-cli ref="cliRef" :name="props.server" />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
|
||||||
<!-- slow log pane -->
|
<!-- slow log pane -->
|
||||||
|
@ -312,4 +192,8 @@ const onSwitchSubTab = (name) => {
|
||||||
background-color: v-bind('themeVars.tabColor');
|
background-color: v-bind('themeVars.tabColor');
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.n-tabs .n-tabs-bar {
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -52,13 +52,13 @@ const tab = computed(() =>
|
||||||
<n-tabs
|
<n-tabs
|
||||||
v-model:value="tabStore.activatedIndex"
|
v-model:value="tabStore.activatedIndex"
|
||||||
:closable="true"
|
:closable="true"
|
||||||
:tabs-padding="0"
|
|
||||||
:tab-style="{
|
:tab-style="{
|
||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
borderWidth: '1px',
|
borderWidth: '1px',
|
||||||
borderLeftColor: themeVars.borderColor,
|
borderLeftColor: themeVars.borderColor,
|
||||||
borderRightColor: themeVars.borderColor,
|
borderRightColor: themeVars.borderColor,
|
||||||
}"
|
}"
|
||||||
|
:tabs-padding="0"
|
||||||
:theme-overrides="{
|
:theme-overrides="{
|
||||||
tabFontWeightActive: 800,
|
tabFontWeightActive: 800,
|
||||||
tabGapSmallCard: 0,
|
tabGapSmallCard: 0,
|
||||||
|
@ -76,10 +76,10 @@ const tab = computed(() =>
|
||||||
<n-tab
|
<n-tab
|
||||||
v-for="(t, i) in tab"
|
v-for="(t, i) in tab"
|
||||||
:key="i"
|
:key="i"
|
||||||
|
:class="tabClass(i)"
|
||||||
:closable="true"
|
:closable="true"
|
||||||
:name="i"
|
:name="i"
|
||||||
:style="tabStore.activatedIndex === i ? activeTabStyle : undefined"
|
:style="tabStore.activatedIndex === i ? activeTabStyle : undefined"
|
||||||
:class="tabClass(i)"
|
|
||||||
@dblclick.stop="() => {}">
|
@dblclick.stop="() => {}">
|
||||||
<n-space :size="5" :wrap-item="false" align="center" inline justify="center">
|
<n-space :size="5" :wrap-item="false" align="center" inline justify="center">
|
||||||
<n-icon size="18">
|
<n-icon size="18">
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Terminal } from 'xterm'
|
import { Terminal } from 'xterm'
|
||||||
import { FitAddon } from 'xterm-addon-fit'
|
import { FitAddon } from 'xterm-addon-fit'
|
||||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
import { computed, defineExpose, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||||
import 'xterm/css/xterm.css'
|
import 'xterm/css/xterm.css'
|
||||||
import { EventsEmit, EventsOff, EventsOn } from 'wailsjs/runtime/runtime.js'
|
import { EventsEmit, EventsOff, EventsOn } from 'wailsjs/runtime/runtime.js'
|
||||||
import { get, isEmpty, set, size } from 'lodash'
|
import { get, isEmpty, set, size } from 'lodash'
|
||||||
|
@ -54,7 +54,6 @@ const newTerm = () => {
|
||||||
return { term, fitAddon }
|
return { term, fitAddon }
|
||||||
}
|
}
|
||||||
|
|
||||||
let intervalID
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const { term, fitAddon } = newTerm()
|
const { term, fitAddon } = newTerm()
|
||||||
termInst = term
|
termInst = term
|
||||||
|
@ -69,16 +68,9 @@ onMounted(() => {
|
||||||
EventsOn(`cmd:output:${props.name}`, receiveTermOutput)
|
EventsOn(`cmd:output:${props.name}`, receiveTermOutput)
|
||||||
fitAddon.fit()
|
fitAddon.fit()
|
||||||
term.focus()
|
term.focus()
|
||||||
|
|
||||||
intervalID = setInterval(() => {
|
|
||||||
if (props.activated) {
|
|
||||||
resizeTerm()
|
|
||||||
}
|
|
||||||
}, 1000)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
clearInterval(intervalID)
|
|
||||||
// window.removeEventListener('resize', resizeTerm)
|
// window.removeEventListener('resize', resizeTerm)
|
||||||
EventsOff(`cmd:output:${props.name}`)
|
EventsOff(`cmd:output:${props.name}`)
|
||||||
termInst.dispose()
|
termInst.dispose()
|
||||||
|
@ -92,6 +84,10 @@ const resizeTerm = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
resizeTerm,
|
||||||
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => prefStore.general.fontSize,
|
() => prefStore.general.fontSize,
|
||||||
(fontSize) => {
|
(fontSize) => {
|
||||||
|
@ -125,7 +121,6 @@ const onTermData = (data) => {
|
||||||
|
|
||||||
case 13: // enter
|
case 13: // enter
|
||||||
// try to process local command first
|
// try to process local command first
|
||||||
console.log('enter con', getCurrentInput())
|
|
||||||
switch (getCurrentInput()) {
|
switch (getCurrentInput()) {
|
||||||
case 'clear':
|
case 'clear':
|
||||||
case 'clr':
|
case 'clr':
|
||||||
|
|
|
@ -1,36 +1,74 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { get, isEmpty, map, mapValues, pickBy, split, sum, toArray, toNumber } from 'lodash'
|
import { get, isEmpty, map, mapValues, pickBy, split, sum, toArray, toNumber } from 'lodash'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||||
import IconButton from '@/components/common/IconButton.vue'
|
import IconButton from '@/components/common/IconButton.vue'
|
||||||
import Filter from '@/components/icons/Filter.vue'
|
import Filter from '@/components/icons/Filter.vue'
|
||||||
import Refresh from '@/components/icons/Refresh.vue'
|
import Refresh from '@/components/icons/Refresh.vue'
|
||||||
|
import useConnectionStore from 'stores/connections.js'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
server: String,
|
server: String,
|
||||||
info: Object,
|
|
||||||
autoRefresh: false,
|
|
||||||
loading: false,
|
|
||||||
autoLoading: false,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['update:autoRefresh', 'refresh'])
|
const emit = defineEmits(['update:autoRefresh', 'refresh'])
|
||||||
|
|
||||||
|
const connectionStore = useConnectionStore()
|
||||||
|
const serverInfo = ref({})
|
||||||
|
const autoRefresh = ref(false)
|
||||||
|
const loading = ref(false) // loading status for refresh
|
||||||
|
const autoLoading = ref(false) // loading status for auto refresh
|
||||||
|
|
||||||
|
/**
|
||||||
|
* refresh server status info
|
||||||
|
* @param {boolean} [force] force refresh will show loading indicator
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
const refreshInfo = async (force) => {
|
||||||
|
if (force) {
|
||||||
|
loading.value = true
|
||||||
|
} else {
|
||||||
|
autoLoading.value = true
|
||||||
|
}
|
||||||
|
if (!isEmpty(props.server) && connectionStore.isConnected(props.server)) {
|
||||||
|
try {
|
||||||
|
serverInfo.value = await connectionStore.getServerInfo(props.server)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
autoLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let intervalID
|
||||||
|
onMounted(() => {
|
||||||
|
refreshInfo()
|
||||||
|
intervalID = setInterval(() => {
|
||||||
|
if (autoRefresh.value === true) {
|
||||||
|
refreshInfo()
|
||||||
|
}
|
||||||
|
}, 5000)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearInterval(intervalID)
|
||||||
|
})
|
||||||
|
|
||||||
const scrollRef = ref(null)
|
const scrollRef = ref(null)
|
||||||
const redisVersion = computed(() => {
|
const redisVersion = computed(() => {
|
||||||
return get(props.info, 'Server.redis_version', '')
|
return get(serverInfo.value, 'Server.redis_version', '')
|
||||||
})
|
})
|
||||||
|
|
||||||
const redisMode = computed(() => {
|
const redisMode = computed(() => {
|
||||||
return get(props.info, 'Server.redis_mode', '')
|
return get(serverInfo.value, 'Server.redis_mode', '')
|
||||||
})
|
})
|
||||||
|
|
||||||
const role = computed(() => {
|
const role = computed(() => {
|
||||||
return get(props.info, 'Replication.role', '')
|
return get(serverInfo.value, 'Replication.role', '')
|
||||||
})
|
})
|
||||||
|
|
||||||
const timeUnit = ['common.unit_minute', 'common.unit_hour', 'common.unit_day']
|
const timeUnit = ['common.unit_minute', 'common.unit_hour', 'common.unit_day']
|
||||||
const uptime = computed(() => {
|
const uptime = computed(() => {
|
||||||
let seconds = get(props.info, 'Server.uptime_in_seconds', 0)
|
let seconds = get(serverInfo.value, 'Server.uptime_in_seconds', 0)
|
||||||
seconds /= 60
|
seconds /= 60
|
||||||
if (seconds < 60) {
|
if (seconds < 60) {
|
||||||
// minutes
|
// minutes
|
||||||
|
@ -46,7 +84,7 @@ const uptime = computed(() => {
|
||||||
|
|
||||||
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||||
const usedMemory = computed(() => {
|
const usedMemory = computed(() => {
|
||||||
let size = get(props.info, 'Memory.used_memory', 0)
|
let size = get(serverInfo.value, 'Memory.used_memory', 0)
|
||||||
let unitIndex = 0
|
let unitIndex = 0
|
||||||
|
|
||||||
while (size >= 1024 && unitIndex < units.length - 1) {
|
while (size >= 1024 && unitIndex < units.length - 1) {
|
||||||
|
@ -59,7 +97,7 @@ const usedMemory = computed(() => {
|
||||||
|
|
||||||
const totalKeys = computed(() => {
|
const totalKeys = computed(() => {
|
||||||
const regex = /^db\d+$/
|
const regex = /^db\d+$/
|
||||||
const result = pickBy(props.info['Keyspace'], (value, key) => {
|
const result = pickBy(serverInfo.value['Keyspace'], (value, key) => {
|
||||||
return regex.test(key)
|
return regex.test(key)
|
||||||
})
|
})
|
||||||
const nums = mapValues(result, (v) => {
|
const nums = mapValues(result, (v) => {
|
||||||
|
@ -75,7 +113,7 @@ const infoFilter = ref('')
|
||||||
<template>
|
<template>
|
||||||
<n-scrollbar ref="scrollRef">
|
<n-scrollbar ref="scrollRef">
|
||||||
<n-back-top :listen-to="scrollRef" />
|
<n-back-top :listen-to="scrollRef" />
|
||||||
<n-space vertical :wrap-item="false" :size="5" style="padding: 5px">
|
<n-space :size="5" :wrap-item="false" style="padding: 5px" vertical>
|
||||||
<n-card>
|
<n-card>
|
||||||
<template #header>
|
<template #header>
|
||||||
<n-space :wrap-item="false" align="center" inline size="small">
|
<n-space :wrap-item="false" align="center" inline size="small">
|
||||||
|
@ -103,19 +141,16 @@ const infoFilter = ref('')
|
||||||
<template #header-extra>
|
<template #header-extra>
|
||||||
<n-space align="center" inline>
|
<n-space align="center" inline>
|
||||||
{{ $t('status.auto_refresh') }}
|
{{ $t('status.auto_refresh') }}
|
||||||
<n-switch
|
<n-switch v-model:value="autoRefresh" :loading="autoLoading" />
|
||||||
:loading="props.autoLoading"
|
|
||||||
:value="props.autoRefresh"
|
|
||||||
@update:value="(v) => emit('update:autoRefresh', v)" />
|
|
||||||
<n-tooltip>
|
<n-tooltip>
|
||||||
{{ $t('status.refresh') }}
|
{{ $t('status.refresh') }}
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<n-button
|
<n-button
|
||||||
:loading="props.autoLoading"
|
:loading="autoLoading"
|
||||||
circle
|
circle
|
||||||
size="small"
|
size="small"
|
||||||
tertiary
|
tertiary
|
||||||
@click="emit('refresh')">
|
@click="refreshInfo(true)">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon :component="Refresh" />
|
<n-icon :component="Refresh" />
|
||||||
</template>
|
</template>
|
||||||
|
@ -124,7 +159,7 @@ const infoFilter = ref('')
|
||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
</n-space>
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
<n-spin :show="props.loading">
|
<n-spin :show="loading">
|
||||||
<n-grid style="min-width: 500px" x-gap="5">
|
<n-grid style="min-width: 500px" x-gap="5">
|
||||||
<n-gi :span="6">
|
<n-gi :span="6">
|
||||||
<n-statistic :label="$t('status.uptime')" :value="uptime[0]">
|
<n-statistic :label="$t('status.uptime')" :value="uptime[0]">
|
||||||
|
@ -134,7 +169,7 @@ const infoFilter = ref('')
|
||||||
<n-gi :span="6">
|
<n-gi :span="6">
|
||||||
<n-statistic
|
<n-statistic
|
||||||
:label="$t('status.connected_clients')"
|
:label="$t('status.connected_clients')"
|
||||||
:value="get(props.info, 'Clients.connected_clients', 0)" />
|
:value="get(serverInfo.value, 'Clients.connected_clients', 0)" />
|
||||||
</n-gi>
|
</n-gi>
|
||||||
<n-gi :span="6">
|
<n-gi :span="6">
|
||||||
<n-statistic :value="totalKeys">
|
<n-statistic :value="totalKeys">
|
||||||
|
@ -159,9 +194,9 @@ const infoFilter = ref('')
|
||||||
</template>
|
</template>
|
||||||
</n-input>
|
</n-input>
|
||||||
</template>
|
</template>
|
||||||
<n-spin :show="props.loading">
|
<n-spin :show="loading">
|
||||||
<n-tabs default-value="CPU" placement="left" type="line">
|
<n-tabs default-value="CPU" placement="left" type="line">
|
||||||
<n-tab-pane v-for="(v, k) in props.info" :key="k" :disabled="isEmpty(v)" :name="k">
|
<n-tab-pane v-for="(v, k) in serverInfo" :key="k" :disabled="isEmpty(v)" :name="k">
|
||||||
<n-data-table
|
<n-data-table
|
||||||
:columns="[
|
:columns="[
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,8 +8,10 @@ import ContentValueSet from '@/components/content_value/ContentValueSet.vue'
|
||||||
import ContentValueZset from '@/components/content_value/ContentValueZSet.vue'
|
import ContentValueZset from '@/components/content_value/ContentValueZSet.vue'
|
||||||
import ContentValueStream from '@/components/content_value/ContentValueStream.vue'
|
import ContentValueStream from '@/components/content_value/ContentValueStream.vue'
|
||||||
import { useThemeVars } from 'naive-ui'
|
import { useThemeVars } from 'naive-ui'
|
||||||
|
import useConnectionStore from 'stores/connections.js'
|
||||||
|
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
const connectionStore = useConnectionStore()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
blank: Boolean,
|
blank: Boolean,
|
||||||
|
@ -33,8 +35,6 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['reload'])
|
|
||||||
|
|
||||||
const valueComponents = {
|
const valueComponents = {
|
||||||
[redisTypes.STRING]: ContentValueString,
|
[redisTypes.STRING]: ContentValueString,
|
||||||
[redisTypes.HASH]: ContentValueHash,
|
[redisTypes.HASH]: ContentValueHash,
|
||||||
|
@ -43,18 +43,25 @@ const valueComponents = {
|
||||||
[redisTypes.ZSET]: ContentValueZset,
|
[redisTypes.ZSET]: ContentValueZset,
|
||||||
[redisTypes.STREAM]: ContentValueStream,
|
[redisTypes.STREAM]: ContentValueStream,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* reload current selection key
|
||||||
|
* @returns {Promise<null>}
|
||||||
|
*/
|
||||||
|
const onReloadKey = async () => {
|
||||||
|
await connectionStore.loadKeyValue(props.name, props.db, props.key, props.viewAs)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-empty v-if="props.blank" :description="$t('interface.nonexist_tab_content')" class="empty-content">
|
<n-empty v-if="props.blank" :description="$t('interface.nonexist_tab_content')" class="empty-content">
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<n-button :focusable="false" @click="emit('reload')">{{ $t('interface.reload') }}</n-button>
|
<n-button :focusable="false" @click="onReloadKey">{{ $t('interface.reload') }}</n-button>
|
||||||
</template>
|
</template>
|
||||||
</n-empty>
|
</n-empty>
|
||||||
|
<keep-alive v-else>
|
||||||
<component
|
<component
|
||||||
class="content-value-wrapper"
|
|
||||||
:is="valueComponents[props.type]"
|
:is="valueComponents[props.type]"
|
||||||
v-else
|
|
||||||
:db="props.db"
|
:db="props.db"
|
||||||
:key-code="props.keyCode"
|
:key-code="props.keyCode"
|
||||||
:key-path="props.keyPath"
|
:key-path="props.keyPath"
|
||||||
|
@ -62,10 +69,12 @@ const valueComponents = {
|
||||||
:size="props.size"
|
:size="props.size"
|
||||||
:ttl="props.ttl"
|
:ttl="props.ttl"
|
||||||
:value="props.value"
|
:value="props.value"
|
||||||
:view-as="props.viewAs" />
|
:view-as="props.viewAs"
|
||||||
|
class="content-value-wrapper" />
|
||||||
|
</keep-alive>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style lang="scss" scoped>
|
||||||
.content-value-wrapper {
|
.content-value-wrapper {
|
||||||
background-color: v-bind('themeVars.bodyColor');
|
background-color: v-bind('themeVars.bodyColor');
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,6 @@ import { types } from '@/consts/support_redis_type.js'
|
||||||
import Search from '@/components/icons/Search.vue'
|
import Search from '@/components/icons/Search.vue'
|
||||||
import Unlink from '@/components/icons/Unlink.vue'
|
import Unlink from '@/components/icons/Unlink.vue'
|
||||||
import Status from '@/components/icons/Status.vue'
|
import Status from '@/components/icons/Status.vue'
|
||||||
import SwitchButton from '@/components/common/SwitchButton.vue'
|
|
||||||
import ListView from '@/components/icons/ListView.vue'
|
|
||||||
import TreeView from '@/components/icons/TreeView.vue'
|
|
||||||
|
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
const dialogStore = useDialogStore()
|
const dialogStore = useDialogStore()
|
||||||
|
|
|
@ -234,7 +234,7 @@ const useTabStore = defineStore('tab', {
|
||||||
/**
|
/**
|
||||||
* set selected keys of current display browser tree
|
* set selected keys of current display browser tree
|
||||||
* @param {string} server
|
* @param {string} server
|
||||||
* @param {string|string[]} keys
|
* @param {string|string[]} [keys]
|
||||||
*/
|
*/
|
||||||
setSelectedKeys(server, keys = null) {
|
setSelectedKeys(server, keys = null) {
|
||||||
let tab = find(this.tabList, { name: server })
|
let tab = find(this.tabList, { name: server })
|
||||||
|
|
Loading…
Reference in New Issue