diff --git a/apps/desktop/src/components/layout/AppTabBar.vue b/apps/desktop/src/components/layout/AppTabBar.vue index e67a8b57b1..d3e4921ce2 100644 --- a/apps/desktop/src/components/layout/AppTabBar.vue +++ b/apps/desktop/src/components/layout/AppTabBar.vue @@ -2,7 +2,7 @@ import { computed, ref, watch, nextTick, onUnmounted } from "vue"; import type { CSSProperties } from "vue"; import { useI18n } from "vue-i18n"; -import { X, Pin, ChevronDown, Table2, Code2, TableProperties, PencilRuler, KeyRound, Pencil, Package, Lock, Copy, AlertTriangle, Network, Minimize2, Maximize2, Settings, CalendarClock, Activity, Gauge, ShieldCheck } from "@lucide/vue"; +import { X, Pin, ChevronDown, Table2, Code2, TableProperties, PencilRuler, KeyRound, Pencil, Package, Lock, Copy, AlertTriangle, Network, Minimize2, Maximize2, Settings, CalendarClock, Activity, Gauge, ShieldCheck, Database } from "@lucide/vue"; import CustomContextMenu, { type ContextMenuItem } from "@/components/ui/CustomContextMenu.vue"; import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; @@ -494,7 +494,7 @@ function tabColorStyle(tab: QueryTab) { function tabIconClass(tab: QueryTab) { if (tab.externalSqlFileMissing) return "text-amber-600 dark:text-amber-400"; if (tab.mode === "mq") return ""; - if (tab.mode === "objects") return "text-amber-500 dark:text-amber-400"; + if (tab.mode === "databases" || tab.mode === "objects") return "text-amber-500 dark:text-amber-400"; if (tab.mode === "data" || tab.mode === "mongo" || tab.mode === "vector" || tab.mode === "redis" || tab.mode === "hbase" || tab.mode === "structure") return "text-emerald-600 dark:text-emerald-400"; return "text-blue-600 dark:text-blue-400"; } @@ -530,6 +530,7 @@ function tabMenuIcon(tab: QueryTab) { if (tab.mode === "etcd-dashboard") return Gauge; if (tab.mode === "etcd-access-control") return ShieldCheck; if (tab.mode === "nacos") return Network; + if (tab.mode === "databases") return Database; if (tab.mode === "objects") return TableProperties; if (tab.mode === "structure") return PencilRuler; if (tab.mode === "dameng-jobs") return CalendarClock; @@ -677,6 +678,7 @@ function onOverflowItemKeydown(event: KeyboardEvent, tabId: string, kind: "regul + @@ -874,6 +876,7 @@ function onOverflowItemKeydown(event: KeyboardEvent, tabId: string, kind: "regul + diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue index 2001c12d3e..03830b3cc4 100644 --- a/apps/desktop/src/components/layout/ContentArea.vue +++ b/apps/desktop/src/components/layout/ContentArea.vue @@ -66,6 +66,7 @@ const MqAdminConsole = defineAsyncComponent(() => import("@/components/mq/MqAdmi const MqttAdminConsole = defineAsyncComponent(() => import("@/components/mqtt/MqttAdminConsole.vue")); const NacosAdminConsole = defineAsyncComponent(() => import("@/components/nacos/NacosAdminConsole.vue")); const NacosDashboard = defineAsyncComponent(() => import("@/components/nacos/NacosDashboard.vue")); +const DatabaseBrowser = defineAsyncComponent(() => import("@/components/objects/DatabaseBrowser.vue")); const ObjectBrowser = defineAsyncComponent(() => import("@/components/objects/ObjectBrowser.vue")); const TableStructureEditor = defineAsyncComponent(() => import("@/components/structure/TableStructureEditor.vue")); const DatabaseUserAdmin = defineAsyncComponent(() => import("@/components/admin/DatabaseUserAdmin.vue")); @@ -247,6 +248,7 @@ const etcdDashboardRef = ref<{ refresh?: () => boolean }>(); const zookeeperKeyBrowserRef = ref(); const consulOverviewRef = ref<{ refresh?: () => boolean }>(); const consulWorkspaceRef = ref(); +const databaseBrowserRef = ref(); const objectBrowserRef = ref(); const activeTableMeta = computed(() => props.activeTab.tableMeta); const activeDataTabTableMeta = computed(() => tableMetaForDataTab(props.activeTab)); @@ -785,6 +787,7 @@ function focusSearch(): boolean { if (props.activeTab.mode === "etcd") return etcdKeyBrowserRef.value?.focusSearch() ?? false; if (props.activeTab.mode === "zookeeper") return zookeeperKeyBrowserRef.value?.focusSearch() ?? false; if (props.activeTab.mode === "consul") return consulWorkspaceRef.value?.focusSearch() ?? false; + if (props.activeTab.mode === "databases") return databaseBrowserRef.value?.focusSearch() ?? false; if (props.activeTab.mode === "objects") return objectBrowserRef.value?.focusSearch() ?? false; if (props.activeTab.mode === "query") return queryEditorRef.value?.openSearch() ?? false; return dataGridRef.value?.focusSearch() ?? false; @@ -804,6 +807,7 @@ function refreshData(): boolean { if (props.activeTab.mode === "zookeeper") return zookeeperKeyBrowserRef.value?.refresh?.() ?? false; if (props.activeTab.mode === "consul-overview") return consulOverviewRef.value?.refresh?.() ?? false; if (props.activeTab.mode === "consul") return consulWorkspaceRef.value?.refresh?.() ?? false; + if (props.activeTab.mode === "databases") return databaseBrowserRef.value?.refresh?.() ?? false; // Restored data tabs intentionally omit row data, so refresh must work before DataGrid mounts. if (canReloadUnavailableDataTab(props.activeTab)) { emit("reload"); @@ -2041,6 +2045,12 @@ defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, hand + +