Compare commits

...

2 Commits

Author SHA1 Message Date
tiny-craft 1a49db2450 feat: add cancel operation to the waiting spin for opening connections #9
chore: update common message config
2023-10-07 18:32:42 +08:00
tiny-craft efc09a8745 refactor: separate define for all opened server status info 2023-10-07 15:24:02 +08:00
7 changed files with 124 additions and 40 deletions

View File

@ -302,10 +302,6 @@ func (c *connectionService) OpenConnection(name string) (resp types.JSResp) {
} }
} }
switch selConn.DBFilterType { switch selConn.DBFilterType {
case "none":
for idx := 0; idx < totaldb; idx++ {
dbs = append(dbs, queryDB(idx))
}
case "show": case "show":
for _, idx := range selConn.DBFilterList { for _, idx := range selConn.DBFilterList {
dbs = append(dbs, queryDB(idx)) dbs = append(dbs, queryDB(idx))
@ -316,6 +312,10 @@ func (c *connectionService) OpenConnection(name string) (resp types.JSResp) {
dbs = append(dbs, queryDB(idx)) dbs = append(dbs, queryDB(idx))
} }
} }
default:
for idx := 0; idx < totaldb; idx++ {
dbs = append(dbs, queryDB(idx))
}
} }
resp.Success = true resp.Success = true

View File

@ -1,55 +1,109 @@
<script setup> <script setup>
import { computed, onMounted, onUnmounted, ref, watch } from 'vue' import { computed, onMounted, onUnmounted, reactive, watch } from 'vue'
import { types } from '@/consts/support_redis_type.js' import { types } from '@/consts/support_redis_type.js'
import ContentValueHash from '@/components/content_value/ContentValueHash.vue' import ContentValueHash from '@/components/content_value/ContentValueHash.vue'
import ContentValueList from '@/components/content_value/ContentValueList.vue' import ContentValueList from '@/components/content_value/ContentValueList.vue'
import ContentValueString from '@/components/content_value/ContentValueString.vue' import ContentValueString from '@/components/content_value/ContentValueString.vue'
import ContentValueSet from '@/components/content_value/ContentValueSet.vue' 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 { isEmpty, map, toUpper } from 'lodash' import { get, isEmpty, keyBy, map, size, toUpper } from 'lodash'
import useTabStore from 'stores/tab.js' import useTabStore from 'stores/tab.js'
import useConnectionStore from 'stores/connections.js' import useConnectionStore from 'stores/connections.js'
import ContentServerStatus from '@/components/content_value/ContentServerStatus.vue' import ContentServerStatus from '@/components/content_value/ContentServerStatus.vue'
import ContentValueStream from '@/components/content_value/ContentValueStream.vue' import ContentValueStream from '@/components/content_value/ContentValueStream.vue'
const serverInfo = ref({}) /**
const autoRefresh = ref(false) * @typedef {Object} ServerStatusItem
* @property {string} name
* @property {Object} info
* @property {boolean} autoRefresh
* @property {boolean} loading loading status for refresh
* @property {boolean} autoLoading loading status for auto refresh
*/
/**
*
* @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(() => { const serverName = computed(() => {
if (tabContent.value != null) { if (tabContent.value != null) {
return tabContent.value.name return tabContent.value.name
} }
return '' return ''
}) })
const loadingServerInfo = ref(false) /**
const autoLoadingServerInfo = ref(false) *
* @type {ComputedRef<ServerStatusItem>}
*/
const currentServer = computed(() => {
return getServerInfo(serverName.value)
})
/** /**
* refresh server status info * refresh server status info
* @param {string} serverName
* @param {boolean} [force] force refresh will show loading indicator * @param {boolean} [force] force refresh will show loading indicator
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
const refreshInfo = async (force) => { const refreshInfo = async (serverName, force) => {
const info = getServerInfo(serverName)
if (force) { if (force) {
loadingServerInfo.value = true info.loading = true
} else { } else {
autoLoadingServerInfo.value = true info.autoLoading = true
} }
if (!isEmpty(serverName.value) && connectionStore.isConnected(serverName.value)) { if (!isEmpty(serverName) && connectionStore.isConnected(serverName)) {
try { try {
serverInfo.value = await connectionStore.getServerInfo(serverName.value) info.info = await connectionStore.getServerInfo(serverName)
} finally { } finally {
loadingServerInfo.value = false info.loading = false
autoLoadingServerInfo.value = false info.autoLoading = false
} }
} }
} }
const refreshAllInfo = async (force) => {
for (const serverName in serverStatusTab) {
await refreshInfo(serverName, force)
}
}
let intervalId let intervalId
onMounted(() => { onMounted(() => {
refreshInfo(true) refreshAllInfo(true)
intervalId = setInterval(() => { intervalId = setInterval(() => {
if (autoRefresh.value) { for (const serverName in serverStatusTab) {
refreshInfo() if (get(serverStatusTab, [serverName, 'autoRefresh'])) {
refreshInfo(serverName)
}
} }
}, 5000) }, 5000)
}) })
@ -80,11 +134,27 @@ watch(
() => tabStore.nav, () => tabStore.nav,
(nav) => { (nav) => {
if (nav === 'browser') { if (nav === 'browser') {
refreshInfo() 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 = tabStore.currentTab
if (tab == null) { if (tab == null) {
@ -110,10 +180,6 @@ const showNonexists = computed(() => {
return tabContent.value.value == null return tabContent.value.value == null
}) })
const onUpdateValue = (tabIndex) => {
tabStore.switchTab(tabIndex)
}
/** /**
* reload current selection key * reload current selection key
* @returns {Promise<null>} * @returns {Promise<null>}
@ -132,12 +198,12 @@ const onReloadKey = async () => {
<div v-if="showServerStatus" class="content-container flex-item-expand flex-box-v"> <div v-if="showServerStatus" class="content-container flex-item-expand flex-box-v">
<!-- select nothing or select server node, display server status --> <!-- select nothing or select server node, display server status -->
<content-server-status <content-server-status
v-model:auto-refresh="autoRefresh" v-model:auto-refresh="currentServer.autoRefresh"
:info="serverInfo" :info="currentServer.info"
:loading="loadingServerInfo" :loading="currentServer.loading"
:auto-loading="autoLoadingServerInfo" :auto-loading="currentServer.autoLoading"
:server="serverName" :server="currentServer.name"
@refresh="refreshInfo(true)" /> @refresh="refreshInfo(currentServer.name, true)" />
</div> </div>
<div v-else-if="showNonexists" class="content-container flex-item-expand flex-box-v"> <div v-else-if="showNonexists" class="content-container flex-item-expand flex-box-v">
<n-empty :description="$t('interface.nonexist_tab_content')" class="empty-content"> <n-empty :description="$t('interface.nonexist_tab_content')" class="empty-content">

View File

@ -261,7 +261,7 @@ const onClose = () => {
path="keySeparator"> path="keySeparator">
<n-input <n-input
v-model:value="generalForm.keySeparator" v-model:value="generalForm.keySeparator"
:placeholder="$t('dialogue.connection.advn_separator_tip')" /> :placeholder="$t('dialogue.connection.advn.separator_tip')" />
</n-form-item-gi> </n-form-item-gi>
<n-form-item-gi <n-form-item-gi
:span="12" :span="12"

View File

@ -21,7 +21,7 @@ import usePreferencesStore from 'stores/preferences.js'
const themeVars = useThemeVars() const themeVars = useThemeVars()
const i18n = useI18n() const i18n = useI18n()
const openingConnection = ref(false) const connectingServer = ref('')
const connectionStore = useConnectionStore() const connectionStore = useConnectionStore()
const tabStore = useTabStore() const tabStore = useTabStore()
const prefStore = usePreferencesStore() const prefStore = usePreferencesStore()
@ -287,18 +287,21 @@ const onUpdateSelectedKeys = (keys, option) => {
*/ */
const openConnection = async (name) => { const openConnection = async (name) => {
try { try {
connectingServer.value = name
if (!connectionStore.isConnected(name)) { if (!connectionStore.isConnected(name)) {
openingConnection.value = true
await connectionStore.openConnection(name) await connectionStore.openConnection(name)
} }
// check if connection already canceled before finish open
if (!isEmpty(connectingServer.value)) {
tabStore.upsertTab({ tabStore.upsertTab({
server: name, server: name,
}) })
}
} catch (e) { } catch (e) {
$message.error(e.message) $message.error(e.message)
// node.isLeaf = undefined // node.isLeaf = undefined
} finally { } finally {
openingConnection.value = false connectingServer.value = ''
} }
} }
@ -467,6 +470,13 @@ const handleDrop = ({ node, dragNode, dropPosition }) => {
connectionStore.connections = Array.from(connectionStore.connections) connectionStore.connections = Array.from(connectionStore.connections)
saveSort() saveSort()
} }
const onCancelOpen = () => {
if (!isEmpty(connectingServer.value)) {
connectionStore.closeConnection(connectingServer.value)
connectingServer.value = ''
}
}
</script> </script>
<template> <template>
@ -496,7 +506,7 @@ const handleDrop = ({ node, dragNode, dropPosition }) => {
@update:expanded-keys="onUpdateExpandedKeys" /> @update:expanded-keys="onUpdateExpandedKeys" />
<!-- status display modal --> <!-- status display modal -->
<n-modal :show="openingConnection" transform-origin="center"> <n-modal :show="connectingServer !== ''" transform-origin="center">
<n-card <n-card
:bordered="false" :bordered="false"
:content-style="{ textAlign: 'center' }" :content-style="{ textAlign: 'center' }"
@ -505,7 +515,12 @@ const handleDrop = ({ node, dragNode, dropPosition }) => {
style="width: 400px"> style="width: 400px">
<n-spin> <n-spin>
<template #description> <template #description>
{{ $t('dialogue.opening_connection') }} <n-space vertical>
<n-text strong>{{ $t('dialogue.opening_connection') }}</n-text>
<n-button secondary size="small" :focusable="false" @click="onCancelOpen">
{{ $t('dialogue.interrupt_connection') }}
</n-button>
</n-space>
</template> </template>
</n-spin> </n-spin>
</n-card> </n-card>

View File

@ -99,6 +99,7 @@
"close_confirm": "Confirm close this tab and connection", "close_confirm": "Confirm close this tab and connection",
"edit_close_confirm": "Please close the relevant connections before editing. Do you want to continue?", "edit_close_confirm": "Please close the relevant connections before editing. Do you want to continue?",
"opening_connection": "Opening Connection...", "opening_connection": "Opening Connection...",
"interrupt_connection": "Cancel",
"remove_tip": "{type} \"{name}\" will be deleted", "remove_tip": "{type} \"{name}\" will be deleted",
"remove_group_tip": "Group \"{name}\" and all connections in it will be deleted", "remove_group_tip": "Group \"{name}\" and all connections in it will be deleted",
"delete_key_succ": "\"{key}\" has been deleted", "delete_key_succ": "\"{key}\" has been deleted",

View File

@ -99,6 +99,7 @@
"close_confirm": "是否关闭当前连接", "close_confirm": "是否关闭当前连接",
"edit_close_confirm": "编辑前需要关闭相关连接,是否继续", "edit_close_confirm": "编辑前需要关闭相关连接,是否继续",
"opening_connection": "正在打开连接...", "opening_connection": "正在打开连接...",
"interrupt_connection": "中断连接",
"remove_tip": "{type} \"{name}\" 将会被删除", "remove_tip": "{type} \"{name}\" 将会被删除",
"remove_group_tip": "分组 \"{name}\"及其所有连接将会被删除", "remove_group_tip": "分组 \"{name}\"及其所有连接将会被删除",
"delete_key_succ": "{key} 已被删除", "delete_key_succ": "{key} 已被删除",

View File

@ -105,6 +105,7 @@ export async function setupDiscreteApi() {
configProviderProps, configProviderProps,
messageProviderProps: { messageProviderProps: {
placement: 'bottom-right', placement: 'bottom-right',
keepAliveOnHover: true,
}, },
notificationProviderProps: { notificationProviderProps: {
max: 5, max: 5,