From d3b978ccba87e4d84f7e457862923984483825d6 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Mon, 6 Jul 2026 22:13:56 +0800 Subject: [PATCH] feat(results): add query result auto refresh menu --- .../src/components/layout/ContentArea.vue | 99 +++++++++++++++++-- 1 file changed, 93 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue index 5912e6f02..04141186f 100644 --- a/apps/desktop/src/components/layout/ContentArea.vue +++ b/apps/desktop/src/components/layout/ContentArea.vue @@ -3,7 +3,7 @@ import { computed, ref, defineAsyncComponent, watch, nextTick, onMounted, onUnmo import { safeLocalStorageGet, safeLocalStorageSet } from "@/lib/backend/safeStorage"; import type { CSSProperties } from "vue"; import { useI18n } from "vue-i18n"; -import { Check, Columns3, EyeOff, Loader2, Search, Bot, GitBranch, BarChart3, TableProperties, ChevronDown, ChevronUp, Inbox, RefreshCcw, Wrench, Toolbox, ListChecks, Database, FileUp, Download, X, Pin, Rows3, SquareDashed, Minus, Plus } from "@lucide/vue"; +import { Check, Columns3, EyeOff, Loader2, Search, Bot, GitBranch, BarChart3, TableProperties, ChevronDown, ChevronUp, Inbox, RefreshCcw, Timer, Wrench, Toolbox, ListChecks, Database, FileUp, Download, X, Pin, Rows3, SquareDashed, Minus, Plus } from "@lucide/vue"; import { Splitpanes, Pane } from "splitpanes"; import "splitpanes/dist/splitpanes.css"; import { Button } from "@/components/ui/button"; @@ -301,6 +301,11 @@ const activeResultSql = computed(() => resultSqlForGrid(props.activeTab)); const resultArchiveExporting = ref(false); const canExportResultArchive = computed(() => props.activeTab.mode === "query" && (!!props.activeTab.result || !!props.activeTab.results?.length || !!props.activeTab.resultRuns?.length)); const resultAutoSave = computed(() => props.activeTab.resultAutoSave === true); +const QUERY_RESULT_AUTO_REFRESH_INTERVAL_OPTIONS = [5, 10, 30, 60, 300]; +const queryResultAutoRefreshIntervalSeconds = ref(10); +const queryResultAutoRefreshEnabled = ref(false); +let queryResultAutoRefreshTimer: ReturnType | undefined; +const queryResultAutoRefreshLabel = computed(() => (queryResultAutoRefreshEnabled.value ? t("tabs.autoRefreshEvery", { seconds: queryResultAutoRefreshIntervalSeconds.value }) : t("tabs.autoRefresh"))); watch( () => visibleResultItems.value.map((item) => item.index).join(","), () => { @@ -372,6 +377,7 @@ const resultsPaneOpen = ref(false); const resultsPaneSize = ref(Number(safeLocalStorageGet("dbx-results-pane-size")) || DEFAULT_QUERY_RESULTS_PANE_SIZE); const editorPaneSize = computed(() => (resultsPaneOpen.value ? 100 - resultsPaneSize.value : 100)); const queryRunningElapsed = ref(0); +const canAutoRefreshQueryResult = computed(() => props.activeTab.mode === "query" && props.activeOutputView === "result" && resultsPaneOpen.value && hasTabularResult.value && !props.activeTab.isExecuting); function onResultsResized(payload: { panes: { size: number }[] }) { const resultsPane = payload.panes[1]; @@ -413,9 +419,29 @@ watch(() => [props.activeTab.id, props.activeTab.isExecuting, props.activeTab.qu onUnmounted(() => { stopQueryRunningElapsedTimer(); + stopQueryResultAutoRefreshTimer(); window.removeEventListener("dbx-refresh-active-kv-browser", onRefreshActiveKvBrowser); }); +watch(() => props.activeTab.id, stopQueryResultAutoRefresh); + +watch( + () => [props.activeOutputView, resultsPaneOpen.value] as const, + ([outputView, paneOpen]) => { + if (outputView !== "result" || !paneOpen) stopQueryResultAutoRefresh(); + }, +); + +watch(canAutoRefreshQueryResult, (canRefresh) => { + if (!queryResultAutoRefreshEnabled.value) return; + if (canRefresh) restartQueryResultAutoRefreshTimer(); + else stopQueryResultAutoRefreshTimer(); +}); + +watch(activeQueryError, (message) => { + if (message && queryResultAutoRefreshEnabled.value) stopQueryResultAutoRefresh(); +}); + watch( hasQueryOutput, (hasOutput) => { @@ -600,6 +626,37 @@ function focusSearch(): boolean { return dataGridRef.value?.focusSearch() ?? false; } +function stopQueryResultAutoRefreshTimer() { + clearInterval(queryResultAutoRefreshTimer); + queryResultAutoRefreshTimer = undefined; +} + +function runQueryResultAutoRefreshTick() { + if (!queryResultAutoRefreshEnabled.value || !canAutoRefreshQueryResult.value) return; + refreshData(); +} + +function restartQueryResultAutoRefreshTimer() { + stopQueryResultAutoRefreshTimer(); + if (!queryResultAutoRefreshEnabled.value || !canAutoRefreshQueryResult.value) return; + queryResultAutoRefreshTimer = setInterval(runQueryResultAutoRefreshTick, queryResultAutoRefreshIntervalSeconds.value * 1000); +} + +function setQueryResultAutoRefreshInterval(seconds: number) { + queryResultAutoRefreshIntervalSeconds.value = seconds; + if (queryResultAutoRefreshEnabled.value) restartQueryResultAutoRefreshTimer(); +} + +function toggleQueryResultAutoRefresh() { + queryResultAutoRefreshEnabled.value = !queryResultAutoRefreshEnabled.value; + restartQueryResultAutoRefreshTimer(); +} + +function stopQueryResultAutoRefresh() { + queryResultAutoRefreshEnabled.value = false; + stopQueryResultAutoRefreshTimer(); +} + function refreshData(): boolean { if (props.activeTab.mode === "etcd") return etcdKeyBrowserRef.value?.refresh?.() ?? false; if (props.activeTab.mode === "zookeeper") return zookeeperKeyBrowserRef.value?.refresh?.() ?? false; @@ -933,11 +990,41 @@ defineExpose({ focusSearch, refreshData, handleModRTarget, requestQueryEditorExe - +
+ + + + + + + + + + {{ queryResultAutoRefreshEnabled ? t("tabs.stopAutoRefresh") : t("tabs.startAutoRefresh") }} + + + + + {{ t("tabs.autoRefreshEvery", { seconds }) }} + + + +