mirror of
				https://github.com/tiny-craft/tiny-rdm.git
				synced 2025-11-01 02:52:33 +08:00 
			
		
		
		
	feat: adjusted the content pane to accommodate more information (add sub content tabs).
This commit is contained in:
		| @@ -1,16 +1,19 @@ | |||||||
| <script setup> | <script setup> | ||||||
| import { computed, onMounted, onUnmounted, reactive, watch } from 'vue' | import { computed, onMounted, onUnmounted, reactive, watch } from 'vue' | ||||||
| import { types } from '@/consts/support_redis_type.js' |  | ||||||
| import ContentValueHash from '@/components/content_value/ContentValueHash.vue' |  | ||||||
| import ContentValueList from '@/components/content_value/ContentValueList.vue' |  | ||||||
| import ContentValueString from '@/components/content_value/ContentValueString.vue' |  | ||||||
| import ContentValueSet from '@/components/content_value/ContentValueSet.vue' |  | ||||||
| import ContentValueZset from '@/components/content_value/ContentValueZSet.vue' |  | ||||||
| import { get, isEmpty, keyBy, map, size, 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 Status from '@/components/icons/Status.vue' | ||||||
|  | import { useThemeVars } from 'naive-ui' | ||||||
|  | import { BrowserTabType } from '@/consts/browser_tab_type.js' | ||||||
|  | import Terminal from '@/components/icons/Terminal.vue' | ||||||
|  | import Log from '@/components/icons/Log.vue' | ||||||
|  | import Detail from '@/components/icons/Detail.vue' | ||||||
|  | import ContentValueWrapper from '@/components/content_value/ContentValueWrapper.vue' | ||||||
|  | import ContentCli from '@/components/content_value/ContentCli.vue' | ||||||
|  |  | ||||||
|  | const themeVars = useThemeVars() | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @typedef {Object} ServerStatusItem |  * @typedef {Object} ServerStatusItem | ||||||
| @@ -112,15 +115,6 @@ onUnmounted(() => { | |||||||
|     clearInterval(intervalId) |     clearInterval(intervalId) | ||||||
| }) | }) | ||||||
|  |  | ||||||
| const valueComponents = { |  | ||||||
|     [types.STRING]: ContentValueString, |  | ||||||
|     [types.HASH]: ContentValueHash, |  | ||||||
|     [types.LIST]: ContentValueList, |  | ||||||
|     [types.SET]: ContentValueSet, |  | ||||||
|     [types.ZSET]: ContentValueZset, |  | ||||||
|     [types.STREAM]: ContentValueStream, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const connectionStore = useConnectionStore() | const connectionStore = useConnectionStore() | ||||||
| const tabStore = useTabStore() | const tabStore = useTabStore() | ||||||
| const tab = computed(() => | const tab = computed(() => | ||||||
| @@ -162,6 +156,7 @@ const tabContent = computed(() => { | |||||||
|     } |     } | ||||||
|     return { |     return { | ||||||
|         name: tab.name, |         name: tab.name, | ||||||
|  |         subTab: tab.subTab, | ||||||
|         type: toUpper(tab.type), |         type: toUpper(tab.type), | ||||||
|         db: tab.db, |         db: tab.db, | ||||||
|         keyPath: tab.key, |         keyPath: tab.key, | ||||||
| @@ -177,7 +172,7 @@ const showServerStatus = computed(() => { | |||||||
|     return tabContent.value == null || isEmpty(tabContent.value.keyPath) |     return tabContent.value == null || isEmpty(tabContent.value.keyPath) | ||||||
| }) | }) | ||||||
|  |  | ||||||
| const showNonexists = computed(() => { | const isBlankValue = computed(() => { | ||||||
|     return tabContent.value.value == null |     return tabContent.value.value == null | ||||||
| }) | }) | ||||||
|  |  | ||||||
| @@ -192,12 +187,44 @@ const onReloadKey = async () => { | |||||||
|     } |     } | ||||||
|     await connectionStore.loadKeyValue(tab.name, tab.db, tab.key, tab.viewAs) |     await connectionStore.loadKeyValue(tab.name, tab.db, tab.key, tab.viewAs) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | const selectedSubTab = computed(() => { | ||||||
|  |     const { subTab = 'status' } = tabStore.currentTab || {} | ||||||
|  |     return subTab | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | const onSwitchSubTab = (name) => { | ||||||
|  |     tabStore.switchSubTab(name) | ||||||
|  | } | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <template> | <template> | ||||||
|     <div class="content-container flex-box-v"> |     <div class="content-container flex-box-v"> | ||||||
|         <div v-if="showServerStatus" class="content-container flex-item-expand flex-box-v"> |         <n-tabs | ||||||
|             <!-- select nothing or select server node, display server status --> |             :tabs-padding="5" | ||||||
|  |             :theme-overrides="{ | ||||||
|  |                 tabGapSmallLine: '10px', | ||||||
|  |                 tabGapMediumLine: '10px', | ||||||
|  |                 tabGapLargeLine: '10px', | ||||||
|  |             }" | ||||||
|  |             :value="selectedSubTab" | ||||||
|  |             class="content-sub-tab" | ||||||
|  |             default-value="status" | ||||||
|  |             pane-class="content-sub-tab-pane" | ||||||
|  |             placement="top" | ||||||
|  |             tab-style="padding-left: 10px; padding-right: 10px;" | ||||||
|  |             type="line" | ||||||
|  |             @update:value="onSwitchSubTab"> | ||||||
|  |             <!-- server status pane --> | ||||||
|  |             <n-tab-pane :name="BrowserTabType.Status.toString()"> | ||||||
|  |                 <template #tab> | ||||||
|  |                     <n-space :size="5" :wrap-item="false" align="center" inline justify="center"> | ||||||
|  |                         <n-icon size="16"> | ||||||
|  |                             <status :inverse="selectedSubTab === BrowserTabType.Status.toString()" stroke-width="4" /> | ||||||
|  |                         </n-icon> | ||||||
|  |                         <span>{{ $t('interface.sub_tab.status') }}</span> | ||||||
|  |                     </n-space> | ||||||
|  |                 </template> | ||||||
|                 <content-server-status |                 <content-server-status | ||||||
|                     v-model:auto-refresh="currentServer.autoRefresh" |                     v-model:auto-refresh="currentServer.autoRefresh" | ||||||
|                     :auto-loading="currentServer.autoLoading" |                     :auto-loading="currentServer.autoLoading" | ||||||
| @@ -205,17 +232,23 @@ const onReloadKey = async () => { | |||||||
|                     :loading="currentServer.loading" |                     :loading="currentServer.loading" | ||||||
|                     :server="currentServer.name" |                     :server="currentServer.name" | ||||||
|                     @refresh="refreshInfo(currentServer.name, true)" /> |                     @refresh="refreshInfo(currentServer.name, true)" /> | ||||||
|         </div> |             </n-tab-pane> | ||||||
|         <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"> |             <!-- key detail pane --> | ||||||
|                 <template #extra> |             <n-tab-pane :name="BrowserTabType.KeyDetail.toString()"> | ||||||
|                     <n-button :focusable="false" @click="onReloadKey">{{ $t('interface.reload') }}</n-button> |                 <template #tab> | ||||||
|  |                     <n-space :size="5" :wrap-item="false" align="center" inline justify="center"> | ||||||
|  |                         <n-icon size="16"> | ||||||
|  |                             <detail | ||||||
|  |                                 :inverse="selectedSubTab === BrowserTabType.KeyDetail.toString()" | ||||||
|  |                                 fill-color="none" /> | ||||||
|  |                         </n-icon> | ||||||
|  |                         <span>{{ $t('interface.sub_tab.key_detail') }}</span> | ||||||
|  |                     </n-space> | ||||||
|                 </template> |                 </template> | ||||||
|             </n-empty> |                 <content-value-wrapper | ||||||
|         </div> |                     :blank="isBlankValue" | ||||||
|         <component |                     :type="tabContent.type" | ||||||
|             :is="valueComponents[tabContent.type]" |  | ||||||
|             v-else |  | ||||||
|                     :db="tabContent.db" |                     :db="tabContent.db" | ||||||
|                     :key-code="tabContent.keyCode" |                     :key-code="tabContent.keyCode" | ||||||
|                     :key-path="tabContent.keyPath" |                     :key-path="tabContent.keyPath" | ||||||
| @@ -223,7 +256,35 @@ const onReloadKey = async () => { | |||||||
|                     :size="tabContent.size" |                     :size="tabContent.size" | ||||||
|                     :ttl="tabContent.ttl" |                     :ttl="tabContent.ttl" | ||||||
|                     :value="tabContent.value" |                     :value="tabContent.value" | ||||||
|             :view-as="tabContent.viewAs" /> |                     :view-as="tabContent.viewAs" | ||||||
|  |                     @reload="onReloadKey" /> | ||||||
|  |             </n-tab-pane> | ||||||
|  |  | ||||||
|  |             <!-- cli pane --> | ||||||
|  |             <n-tab-pane :name="BrowserTabType.Cli.toString()"> | ||||||
|  |                 <template #tab> | ||||||
|  |                     <n-space :size="5" :wrap-item="false" align="center" inline justify="center"> | ||||||
|  |                         <n-icon size="16"> | ||||||
|  |                             <terminal :inverse="selectedSubTab === BrowserTabType.Cli.toString()" /> | ||||||
|  |                         </n-icon> | ||||||
|  |                         <span>{{ $t('interface.sub_tab.cli') }}</span> | ||||||
|  |                     </n-space> | ||||||
|  |                 </template> | ||||||
|  |                 <content-cli /> | ||||||
|  |             </n-tab-pane> | ||||||
|  |  | ||||||
|  |             <!-- slow log pane --> | ||||||
|  |             <n-tab-pane :name="BrowserTabType.SlowLog.toString()"> | ||||||
|  |                 <template #tab> | ||||||
|  |                     <n-space :size="5" :wrap-item="false" align="center" inline justify="center"> | ||||||
|  |                         <n-icon size="16"> | ||||||
|  |                             <log :inverse="selectedSubTab === BrowserTabType.SlowLog.toString()" /> | ||||||
|  |                         </n-icon> | ||||||
|  |                         <span>{{ $t('interface.sub_tab.slow_log') }}</span> | ||||||
|  |                     </n-space> | ||||||
|  |                 </template> | ||||||
|  |             </n-tab-pane> | ||||||
|  |         </n-tabs> | ||||||
|     </div> |     </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| @@ -231,27 +292,24 @@ const onReloadKey = async () => { | |||||||
| @import '@/styles/content'; | @import '@/styles/content'; | ||||||
|  |  | ||||||
| .content-container { | .content-container { | ||||||
|     padding: 5px; |     //padding: 5px 5px 0; | ||||||
|  |     //padding-top: 0; | ||||||
|     box-sizing: border-box; |     box-sizing: border-box; | ||||||
|  |     background-color: v-bind('themeVars.tabColor'); | ||||||
|  | } | ||||||
|  | </style> | ||||||
|  |  | ||||||
|  | <style lang="scss"> | ||||||
|  | .content-sub-tab { | ||||||
|  |     margin-bottom: 5px; | ||||||
|  |     background-color: v-bind('themeVars.bodyColor'); | ||||||
|  |     height: 100%; | ||||||
| } | } | ||||||
|  |  | ||||||
| //.tab-item { | .content-sub-tab-pane { | ||||||
| //    gap: 5px; |     padding: 0 !important; | ||||||
| //    padding: 0 5px 0 10px; |     height: 100%; | ||||||
| //    align-items: center; |     background-color: v-bind('themeVars.tabColor'); | ||||||
| //    max-width: 150px; |     overflow: hidden; | ||||||
| // | } | ||||||
| //    transition: all var(--transition-duration-fast) var(--transition-function-ease-in-out-bezier); |  | ||||||
| // |  | ||||||
| //    &-label { |  | ||||||
| //        font-size: 15px; |  | ||||||
| //        text-align: center; |  | ||||||
| //    } |  | ||||||
| // |  | ||||||
| //    &-close { |  | ||||||
| //        &:hover { |  | ||||||
| //            background-color: rgb(176, 177, 182, 0.4); |  | ||||||
| //        } |  | ||||||
| //    } |  | ||||||
| //} |  | ||||||
| </style> | </style> | ||||||
|   | |||||||
| @@ -83,7 +83,7 @@ const tab = computed(() => | |||||||
|             @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"> | ||||||
|                     <server stroke-width="4" :inverse="tabStore.activatedIndex === i" /> |                     <server stroke-width="4" /> | ||||||
|                 </n-icon> |                 </n-icon> | ||||||
|                 <n-ellipsis style="max-width: 150px">{{ t.label }}</n-ellipsis> |                 <n-ellipsis style="max-width: 150px">{{ t.label }}</n-ellipsis> | ||||||
|             </n-space> |             </n-space> | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								frontend/src/components/content_value/ContentCli.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								frontend/src/components/content_value/ContentCli.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | <script setup></script> | ||||||
|  |  | ||||||
|  | <template> | ||||||
|  |     <n-empty description="coming soon" class="empty-content"></n-empty> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <style scoped lang="scss"></style> | ||||||
| @@ -75,7 +75,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> |         <n-space vertical :wrap-item="false" :size="5" style="padding: 5px"> | ||||||
|             <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"> | ||||||
|   | |||||||
| @@ -0,0 +1,72 @@ | |||||||
|  | <script setup> | ||||||
|  | import { types } from '@/consts/value_view_type.js' | ||||||
|  | import { types as redisTypes } from '@/consts/support_redis_type.js' | ||||||
|  | import ContentValueString from '@/components/content_value/ContentValueString.vue' | ||||||
|  | import ContentValueHash from '@/components/content_value/ContentValueHash.vue' | ||||||
|  | import ContentValueList from '@/components/content_value/ContentValueList.vue' | ||||||
|  | import ContentValueSet from '@/components/content_value/ContentValueSet.vue' | ||||||
|  | import ContentValueZset from '@/components/content_value/ContentValueZSet.vue' | ||||||
|  | import ContentValueStream from '@/components/content_value/ContentValueStream.vue' | ||||||
|  | import { useThemeVars } from 'naive-ui' | ||||||
|  |  | ||||||
|  | const themeVars = useThemeVars() | ||||||
|  |  | ||||||
|  | const props = defineProps({ | ||||||
|  |     blank: Boolean, | ||||||
|  |     type: String, | ||||||
|  |     name: String, | ||||||
|  |     db: Number, | ||||||
|  |     keyPath: String, | ||||||
|  |     keyCode: { | ||||||
|  |         type: Array, | ||||||
|  |         default: null, | ||||||
|  |     }, | ||||||
|  |     ttl: { | ||||||
|  |         type: Number, | ||||||
|  |         default: -1, | ||||||
|  |     }, | ||||||
|  |     value: [String, Object], | ||||||
|  |     size: Number, | ||||||
|  |     viewAs: { | ||||||
|  |         type: String, | ||||||
|  |         default: types.PLAIN_TEXT, | ||||||
|  |     }, | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | const emit = defineEmits(['reload']) | ||||||
|  |  | ||||||
|  | const valueComponents = { | ||||||
|  |     [redisTypes.STRING]: ContentValueString, | ||||||
|  |     [redisTypes.HASH]: ContentValueHash, | ||||||
|  |     [redisTypes.LIST]: ContentValueList, | ||||||
|  |     [redisTypes.SET]: ContentValueSet, | ||||||
|  |     [redisTypes.ZSET]: ContentValueZset, | ||||||
|  |     [redisTypes.STREAM]: ContentValueStream, | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <template> | ||||||
|  |     <n-empty v-if="props.blank" :description="$t('interface.nonexist_tab_content')" class="empty-content"> | ||||||
|  |         <template #extra> | ||||||
|  |             <n-button :focusable="false" @click="emit('reload')">{{ $t('interface.reload') }}</n-button> | ||||||
|  |         </template> | ||||||
|  |     </n-empty> | ||||||
|  |     <component | ||||||
|  |         class="content-value-wrapper" | ||||||
|  |         :is="valueComponents[props.type]" | ||||||
|  |         v-else | ||||||
|  |         :db="props.db" | ||||||
|  |         :key-code="props.keyCode" | ||||||
|  |         :key-path="props.keyPath" | ||||||
|  |         :name="props.name" | ||||||
|  |         :size="props.size" | ||||||
|  |         :ttl="props.ttl" | ||||||
|  |         :value="props.value" | ||||||
|  |         :view-as="props.viewAs" /> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <style scoped lang="scss"> | ||||||
|  | .content-value-wrapper { | ||||||
|  |     background-color: v-bind('themeVars.bodyColor'); | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										109
									
								
								frontend/src/components/icons/Detail.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								frontend/src/components/icons/Detail.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | |||||||
|  | <script setup> | ||||||
|  | const emit = defineEmits(['update:modelValue']) | ||||||
|  |  | ||||||
|  | const props = defineProps({ | ||||||
|  |     inverse: { | ||||||
|  |         type: Boolean, | ||||||
|  |         default: false, | ||||||
|  |     }, | ||||||
|  |     strokeWidth: { | ||||||
|  |         type: [Number, String], | ||||||
|  |         default: 3, | ||||||
|  |     }, | ||||||
|  | }) | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <template> | ||||||
|  |     <svg v-if="props.inverse" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||||
|  |         <rect | ||||||
|  |             x="6" | ||||||
|  |             y="6" | ||||||
|  |             width="36" | ||||||
|  |             height="36" | ||||||
|  |             rx="3" | ||||||
|  |             fill="currentColor" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <rect | ||||||
|  |             x="13" | ||||||
|  |             y="13" | ||||||
|  |             width="8" | ||||||
|  |             height="8" | ||||||
|  |             fill="#FFF" | ||||||
|  |             stroke="#FFF" | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <path | ||||||
|  |             d="M27 13L35 13" | ||||||
|  |             stroke="#FFF" | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <path | ||||||
|  |             d="M27 20L35 20" | ||||||
|  |             stroke="#FFF" | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <path | ||||||
|  |             d="M13 28L35 28" | ||||||
|  |             stroke="#FFF" | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <path | ||||||
|  |             d="M13 35H35" | ||||||
|  |             stroke="#FFF" | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |     </svg> | ||||||
|  |     <svg v-else viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||||
|  |         <rect | ||||||
|  |             x="6" | ||||||
|  |             y="6" | ||||||
|  |             width="36" | ||||||
|  |             height="36" | ||||||
|  |             rx="3" | ||||||
|  |             fill="none" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <rect | ||||||
|  |             x="13" | ||||||
|  |             y="13" | ||||||
|  |             width="8" | ||||||
|  |             height="8" | ||||||
|  |             fill="none" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <path | ||||||
|  |             d="M27 13L35 13" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <path | ||||||
|  |             d="M27 20L35 20" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <path | ||||||
|  |             d="M13 28L35 28" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <path | ||||||
|  |             d="M13 35H35" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |     </svg> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <style lang="scss" scoped></style> | ||||||
							
								
								
									
										67
									
								
								frontend/src/components/icons/Terminal.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								frontend/src/components/icons/Terminal.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | |||||||
|  | <script setup> | ||||||
|  | const emit = defineEmits(['update:modelValue']) | ||||||
|  |  | ||||||
|  | const props = defineProps({ | ||||||
|  |     inverse: { | ||||||
|  |         type: Boolean, | ||||||
|  |         default: false, | ||||||
|  |     }, | ||||||
|  |     strokeWidth: { | ||||||
|  |         type: [Number, String], | ||||||
|  |         default: 3, | ||||||
|  |     }, | ||||||
|  | }) | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <template> | ||||||
|  |     <svg v-if="props.inverse" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||||
|  |         <rect | ||||||
|  |             x="4" | ||||||
|  |             y="8" | ||||||
|  |             width="40" | ||||||
|  |             height="32" | ||||||
|  |             rx="2" | ||||||
|  |             fill="currentColor" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <path | ||||||
|  |             d="M12 18L19 24L12 30" | ||||||
|  |             stroke="#FFF" | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <path | ||||||
|  |             d="M23 32H36" | ||||||
|  |             stroke="#FFF" | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |     </svg> | ||||||
|  |     <svg v-else viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||||
|  |         <rect | ||||||
|  |             x="4" | ||||||
|  |             y="8" | ||||||
|  |             width="40" | ||||||
|  |             height="32" | ||||||
|  |             rx="2" | ||||||
|  |             fill="none" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <path | ||||||
|  |             d="M12 18L19 24L12 30" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <path | ||||||
|  |             d="M23 32H36" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |     </svg> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <style lang="scss" scoped></style> | ||||||
							
								
								
									
										10
									
								
								frontend/src/consts/browser_tab_type.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								frontend/src/consts/browser_tab_type.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | /** | ||||||
|  |  * all types of Browser sub tabs | ||||||
|  |  * @enum {string} | ||||||
|  |  */ | ||||||
|  | export const BrowserTabType = { | ||||||
|  |     Status: 'status', | ||||||
|  |     KeyDetail: 'key_detail', | ||||||
|  |     Cli: 'cli', | ||||||
|  |     SlowLog: 'slow_log', | ||||||
|  | } | ||||||
| @@ -87,13 +87,19 @@ | |||||||
|     "load_more": "Load More Keys", |     "load_more": "Load More Keys", | ||||||
|     "load_all": "Load All Left Keys", |     "load_all": "Load All Left Keys", | ||||||
|     "more_action": "More Action", |     "more_action": "More Action", | ||||||
|     "nonexist_tab_content": "Selected key does not exist. Please retry", |     "nonexist_tab_content": "Selected key does not exist or no key is selected. Please retry", | ||||||
|     "empty_server_content": "Select and open a connection from the left", |     "empty_server_content": "Select and open a connection from the left", | ||||||
|     "empty_server_list": "No redis server", |     "empty_server_list": "No redis server", | ||||||
|     "action": "Action", |     "action": "Action", | ||||||
|     "type": "Type", |     "type": "Type", | ||||||
|     "score": "Score", |     "score": "Score", | ||||||
|     "total": "Length: {size}" |     "total": "Length: {size}", | ||||||
|  |     "sub_tab": { | ||||||
|  |       "status": "Status", | ||||||
|  |       "key_detail": "Key Detail", | ||||||
|  |       "cli": "Command Line", | ||||||
|  |       "slow_log": "Slow Log" | ||||||
|  |     } | ||||||
|   }, |   }, | ||||||
|   "ribbon": { |   "ribbon": { | ||||||
|     "server": "Server", |     "server": "Server", | ||||||
|   | |||||||
| @@ -87,13 +87,19 @@ | |||||||
|     "load_more": "加载更多键", |     "load_more": "加载更多键", | ||||||
|     "load_all": "加载剩余所有键", |     "load_all": "加载剩余所有键", | ||||||
|     "more_action": "更多操作", |     "more_action": "更多操作", | ||||||
|     "nonexist_tab_content": "所选键不存在,请尝试刷新重试", |     "nonexist_tab_content": "所选键不存在或未选中任何键,请尝试刷新重试", | ||||||
|     "empty_server_content": "可以从左边选择并打开连接", |     "empty_server_content": "可以从左边选择并打开连接", | ||||||
|     "empty_server_list": "还没添加Redis服务器", |     "empty_server_list": "还没添加Redis服务器", | ||||||
|     "action": "操作", |     "action": "操作", | ||||||
|     "type": "类型", |     "type": "类型", | ||||||
|     "score": "分值", |     "score": "分值", | ||||||
|     "total": "总数:{size}" |     "total": "总数:{size}", | ||||||
|  |     "sub_tab": { | ||||||
|  |       "status": "状态", | ||||||
|  |       "key_detail": "键详情", | ||||||
|  |       "cli": "命令行", | ||||||
|  |       "slow_log": "慢日志" | ||||||
|  |     } | ||||||
|   }, |   }, | ||||||
|   "ribbon": { |   "ribbon": { | ||||||
|     "server": "服务器", |     "server": "服务器", | ||||||
|   | |||||||
| @@ -52,7 +52,7 @@ import useTabStore from './tab.js' | |||||||
| import { types } from '@/consts/support_redis_type.js' | import { types } from '@/consts/support_redis_type.js' | ||||||
| import { decodeRedisKey, nativeRedisKey } from '@/utils/key_convert.js' | import { decodeRedisKey, nativeRedisKey } from '@/utils/key_convert.js' | ||||||
| import { KeyViewType } from '@/consts/key_view_type.js' | import { KeyViewType } from '@/consts/key_view_type.js' | ||||||
| import { nextTick } from 'vue' | import { BrowserTabType } from '@/consts/browser_tab_type.js' | ||||||
|  |  | ||||||
| const useConnectionStore = defineStore('connections', { | const useConnectionStore = defineStore('connections', { | ||||||
|     /** |     /** | ||||||
| @@ -668,6 +668,7 @@ const useConnectionStore = defineStore('connections', { | |||||||
|                         const k = decodeRedisKey(key) |                         const k = decodeRedisKey(key) | ||||||
|                         const binaryKey = k !== key |                         const binaryKey = k !== key | ||||||
|                         tab.upsertTab({ |                         tab.upsertTab({ | ||||||
|  |                             subTab: BrowserTabType.KeyDetail, | ||||||
|                             server, |                             server, | ||||||
|                             db, |                             db, | ||||||
|                             type, |                             type, | ||||||
| @@ -690,6 +691,7 @@ const useConnectionStore = defineStore('connections', { | |||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 tab.upsertTab({ |                 tab.upsertTab({ | ||||||
|  |                     subTab: BrowserTabType.Status, | ||||||
|                     server, |                     server, | ||||||
|                     db, |                     db, | ||||||
|                     type: 'none', |                     type: 'none', | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { find, findIndex, get, size } from 'lodash' | import { find, findIndex, get, isEmpty, set, size } from 'lodash' | ||||||
| import { defineStore } from 'pinia' | import { defineStore } from 'pinia' | ||||||
|  |  | ||||||
| const useTabStore = defineStore('tab', { | const useTabStore = defineStore('tab', { | ||||||
| @@ -6,6 +6,7 @@ const useTabStore = defineStore('tab', { | |||||||
|      * @typedef {Object} TabItem |      * @typedef {Object} TabItem | ||||||
|      * @property {string} name connection name |      * @property {string} name connection name | ||||||
|      * @property {boolean} blank is blank tab |      * @property {boolean} blank is blank tab | ||||||
|  |      * @property {string} subTab secondary tab value | ||||||
|      * @property {string} [title] tab title |      * @property {string} [title] tab title | ||||||
|      * @property {string} [icon] tab icon |      * @property {string} [icon] tab icon | ||||||
|      * @property {string[]} selectedKeys |      * @property {string[]} selectedKeys | ||||||
| @@ -64,12 +65,16 @@ const useTabStore = defineStore('tab', { | |||||||
|          * |          * | ||||||
|          * @param idx |          * @param idx | ||||||
|          * @param {boolean} [switchNav] |          * @param {boolean} [switchNav] | ||||||
|  |          * @param {string} [subTab] | ||||||
|          * @private |          * @private | ||||||
|          */ |          */ | ||||||
|         _setActivatedIndex(idx, switchNav) { |         _setActivatedIndex(idx, switchNav, subTab) { | ||||||
|             this.activatedIndex = idx |             this.activatedIndex = idx | ||||||
|             if (switchNav === true) { |             if (switchNav === true) { | ||||||
|                 this.nav = idx >= 0 ? 'browser' : 'server' |                 this.nav = idx >= 0 ? 'browser' : 'server' | ||||||
|  |                 if (!isEmpty(subTab)) { | ||||||
|  |                     set(this.tabList, [idx, 'subTab'], subTab) | ||||||
|  |                 } | ||||||
|             } else { |             } else { | ||||||
|                 if (idx < 0) { |                 if (idx < 0) { | ||||||
|                     this.nav = 'server' |                     this.nav = 'server' | ||||||
| @@ -79,6 +84,7 @@ const useTabStore = defineStore('tab', { | |||||||
|  |  | ||||||
|         /** |         /** | ||||||
|          * update or insert a new tab if not exists with the same name |          * update or insert a new tab if not exists with the same name | ||||||
|  |          * @param {string} subTab | ||||||
|          * @param {string} server |          * @param {string} server | ||||||
|          * @param {number} [db] |          * @param {number} [db] | ||||||
|          * @param {number} [type] |          * @param {number} [type] | ||||||
| @@ -89,11 +95,13 @@ const useTabStore = defineStore('tab', { | |||||||
|          * @param {*} [value] |          * @param {*} [value] | ||||||
|          * @param {string} [viewAs] |          * @param {string} [viewAs] | ||||||
|          */ |          */ | ||||||
|         upsertTab({ server, db, type, ttl, key, keyCode, size, value, viewAs }) { |         upsertTab({ subTab, server, db, type, ttl, key, keyCode, size, value, viewAs }) { | ||||||
|             let tabIndex = findIndex(this.tabList, { name: server }) |             let tabIndex = findIndex(this.tabList, { name: server }) | ||||||
|             if (tabIndex === -1) { |             if (tabIndex === -1) { | ||||||
|                 this.tabList.push({ |                 this.tabList.push({ | ||||||
|                     name: server, |                     name: server, | ||||||
|  |                     title: server, | ||||||
|  |                     subTab, | ||||||
|                     server, |                     server, | ||||||
|                     db, |                     db, | ||||||
|                     type, |                     type, | ||||||
| @@ -105,9 +113,10 @@ const useTabStore = defineStore('tab', { | |||||||
|                     viewAs, |                     viewAs, | ||||||
|                 }) |                 }) | ||||||
|                 tabIndex = this.tabList.length - 1 |                 tabIndex = this.tabList.length - 1 | ||||||
|             } |             } else { | ||||||
|                 const tab = this.tabList[tabIndex] |                 const tab = this.tabList[tabIndex] | ||||||
|                 tab.blank = false |                 tab.blank = false | ||||||
|  |                 tab.subTab = subTab | ||||||
|                 // tab.title = db !== undefined ? `${server}/db${db}` : `${server}` |                 // tab.title = db !== undefined ? `${server}/db${db}` : `${server}` | ||||||
|                 tab.title = server |                 tab.title = server | ||||||
|                 tab.server = server |                 tab.server = server | ||||||
| @@ -119,7 +128,8 @@ const useTabStore = defineStore('tab', { | |||||||
|                 tab.size = size |                 tab.size = size | ||||||
|                 tab.value = value |                 tab.value = value | ||||||
|                 tab.viewAs = viewAs |                 tab.viewAs = viewAs | ||||||
|             this._setActivatedIndex(tabIndex, true) |             } | ||||||
|  |             this._setActivatedIndex(tabIndex, true, subTab) | ||||||
|             // this.activatedTab = tab.name |             // this.activatedTab = tab.name | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
| @@ -162,6 +172,14 @@ const useTabStore = defineStore('tab', { | |||||||
|             // this.activatedIndex = tabIndex |             // this.activatedIndex = tabIndex | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
|  |         switchSubTab(name) { | ||||||
|  |             const tab = this.currentTab | ||||||
|  |             if (tab == null) { | ||||||
|  |                 return | ||||||
|  |             } | ||||||
|  |             tab.subTab = name | ||||||
|  |         }, | ||||||
|  |  | ||||||
|         /** |         /** | ||||||
|          * |          * | ||||||
|          * @param {number} tabIndex |          * @param {number} tabIndex | ||||||
|   | |||||||
| @@ -1,8 +1,6 @@ | |||||||
| .content-container { | .content-container { | ||||||
|   height: 100%; |   height: 100%; | ||||||
|   overflow: hidden; |   overflow: hidden; | ||||||
|   padding-top: 2px; |  | ||||||
|   padding-bottom: 5px; |  | ||||||
|   box-sizing: border-box; |   box-sizing: border-box; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -75,7 +75,7 @@ body { | |||||||
| } | } | ||||||
|  |  | ||||||
| .content-wrapper { | .content-wrapper { | ||||||
|   //height: 100%; |   height: 100%; | ||||||
|   flex-grow: 1; |   flex-grow: 1; | ||||||
|   overflow: hidden; |   overflow: hidden; | ||||||
|   gap: 5px; |   gap: 5px; | ||||||
| @@ -95,6 +95,7 @@ body { | |||||||
|   .value-wrapper { |   .value-wrapper { | ||||||
|     //border-top: v-bind('themeVars.borderColor') 1px solid; |     //border-top: v-bind('themeVars.borderColor') 1px solid; | ||||||
|     user-select: text; |     user-select: text; | ||||||
|  |     height: 100%; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 tiny-craft
					tiny-craft