feat: 执行查询前提示选择数据库

* Improve:执行查询前提示选择数据库

* Fix:补齐连接配置测试字段

---------

Co-authored-by: staff <staff@qimaos-MacBook-Pro.local>
This commit is contained in:
zipg 2026-06-26 14:03:27 +08:00 committed by GitHub
parent dbfe284ca9
commit 74d57982c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 91 additions and 3 deletions

View File

@ -205,6 +205,16 @@ async function resolveActiveExecutableSql(snapshot?: SqlExecutionSnapshot) {
}
const blockDangerousRedisCommands = ref(true);
const databaseRequiredSignal = ref(0);
const databaseRequiredTabId = ref<string | null>(null);
function promptActiveDatabaseSelection() {
const tab = activeTab.value;
if (!tab) return;
databaseRequiredTabId.value = tab.id;
databaseRequiredSignal.value += 1;
toast(t("editor.selectDatabaseRequired"), 2500);
}
const { dangerSql, pendingDangerSql, showDangerDialog, suppressDangerConfirm, tryExecute, doExecute, cancelActiveExecution, tryExplain, onDangerConfirm, explainMode } = useSqlExecution({
activeTab,
@ -213,6 +223,7 @@ const { dangerSql, pendingDangerSql, showDangerDialog, suppressDangerConfirm, tr
resolveExecutableSql: resolveActiveExecutableSql,
activeOutputView,
blockDangerousRedisCommands,
onMissingDatabase: promptActiveDatabaseSelection,
});
function requestActiveEditorExecute() {
@ -938,7 +949,12 @@ async function changeActiveConnection(connectionId: string) {
function changeActiveDatabase(database: string) {
const tab = activeTab.value;
if (tab) queryStore.updateDatabase(tab.id, database);
if (tab) {
queryStore.updateDatabase(tab.id, database);
if (databaseRequiredTabId.value === tab.id && database) {
databaseRequiredTabId.value = null;
}
}
}
async function setActiveDatabaseAsDefault() {
@ -1486,6 +1502,7 @@ onUnmounted(() => {
:explain-mode="explainMode"
:block-dangerous-redis-commands="blockDangerousRedisCommands"
:sql-keyword-case="settingsStore.editorSettings.sqlFormatter.keywordCase"
:database-required-signal="databaseRequiredTabId === activeTab.id ? databaseRequiredSignal : 0"
@update:explain-mode="(m: 'explain' | 'autotrace') => (explainMode = m)"
@update:block-dangerous-redis-commands="(v: boolean) => (blockDangerousRedisCommands = v)"
@execute="requestActiveEditorExecute()"

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, watchEffect } from "vue";
import { computed, ref, watch, watchEffect } from "vue";
import { useI18n } from "vue-i18n";
import { Play, Loader2, Square, Database, Check, Table2, AlignLeft, GitBranch, Save, FolderOpen, Layers, X, Shield, Upload } from "@lucide/vue";
import { Button } from "@/components/ui/button";
@ -25,6 +25,7 @@ const props = defineProps<{
explainMode?: string;
blockDangerousRedisCommands?: boolean;
sqlKeywordCase: "preserve" | "upper" | "lower";
databaseRequiredSignal?: number;
}>();
const emit = defineEmits<{
@ -81,6 +82,22 @@ const activeSchemaOptions = computed(() => {
if (!connection) return [];
return getSchemaOptionsForDb(connection.id, schemaDatabaseKey.value);
});
const databaseRequiredVisible = ref(false);
watch(
() => props.databaseRequiredSignal,
(signal) => {
if (!signal) return;
databaseRequiredVisible.value = false;
requestAnimationFrame(() => {
databaseRequiredVisible.value = true;
});
},
);
watch(activeDatabaseValue, (database) => {
if (database) databaseRequiredVisible.value = false;
});
watchEffect(() => {
const connection = props.activeConnection;
@ -254,8 +271,8 @@ function connectionById(connectionId: string): ConnectionConfig | undefined {
<div
v-if="activeConnection?.db_type !== 'elasticsearch' && activeConnection?.db_type !== 'qdrant' && activeConnection?.db_type !== 'milvus' && activeConnection?.db_type !== 'weaviate' && activeConnection?.db_type !== 'chromadb' && activeConnection?.db_type !== 'zookeeper' && !isSingleDb"
class="flex items-center gap-1"
:class="{ 'database-required-prompt': databaseRequiredVisible }"
>
<Database class="h-3.5 w-3.5 shrink-0" />
<SearchableSelect
:model-value="activeDatabaseValue"
:options="activeDatabaseOptions.length ? activeDatabaseOptions : activeDatabaseValue ? [activeDatabaseValue] : []"
@ -265,6 +282,7 @@ function connectionById(connectionId: string): ConnectionConfig | undefined {
:loading-text="t('common.loading')"
:loading="loadingDatabaseOptions[activeConnection?.id || '']"
:display-name="databaseDisplayName"
trigger-class="gap-1.5"
@update:model-value="(database) => emit('changeDatabase', database)"
@update:open="
(open: boolean) => {
@ -272,6 +290,10 @@ function connectionById(connectionId: string): ConnectionConfig | undefined {
}
"
>
<template #trigger-label="{ label, loading }">
<Database class="h-3.5 w-3.5 shrink-0" />
<span class="truncate">{{ loading ? t("common.loading") : label }}</span>
</template>
<template #option-label="{ label }">
<TruncatedTextTooltip :text="label" class="min-w-0 flex-1" side="left" :side-offset="8" />
</template>
@ -319,3 +341,33 @@ function connectionById(connectionId: string): ConnectionConfig | undefined {
</div>
</div>
</template>
<style scoped>
.database-required-prompt {
color: var(--destructive);
animation: database-required-shake 420ms ease;
}
.database-required-prompt :deep(button) {
color: var(--destructive);
border-color: color-mix(in oklch, var(--destructive) 55%, transparent);
background: color-mix(in oklch, var(--destructive) 10%, transparent);
}
@keyframes database-required-shake {
0%,
100% {
transform: translateX(0);
}
12%,
36%,
60% {
transform: translateX(-3px);
}
24%,
48%,
72% {
transform: translateX(3px);
}
}
</style>

View File

@ -5,6 +5,7 @@ import { useHistoryStore } from "@/stores/historyStore";
import { useConnectionStore } from "@/stores/connectionStore";
import { useSettingsStore } from "@/stores/settingsStore";
import { useToast } from "@/composables/useToast";
import { isSingleDatabase } from "@/lib/databaseCapabilities";
import { classifySqlActivityKind } from "@/lib/historyActivityKind";
import { sqlMetadataRefreshTarget } from "@/lib/sqlMetadataRefresh";
import { classifyRedisCommandSafety, firstRedisCommandToken } from "@/lib/redisCommandSafety";
@ -41,6 +42,7 @@ export function useSqlExecution(deps: {
resolveExecutableSql?: (snapshot?: SqlExecutionSnapshot) => Promise<string>;
activeOutputView: Ref<"result" | "summary" | "explain" | "chart">;
blockDangerousRedisCommands?: Ref<boolean>;
onMissingDatabase?: () => void;
}) {
const { t } = useI18n();
const queryStore = useQueryStore();
@ -66,6 +68,10 @@ export function useSqlExecution(deps: {
const tab = deps.activeTab.value;
const sql = await resolvedExecutableSql(sqlOverride);
if (!tab || !sql.trim()) return;
if (requiresDatabaseSelection(tab, deps.activeConnection.value)) {
deps.onMissingDatabase?.();
return;
}
// Redis: block dangerous commands when toggle is on (check each line for multi-line input)
if (deps.activeConnection.value?.db_type === "redis" && deps.blockDangerousRedisCommands?.value !== false) {
const commands = sql
@ -94,6 +100,10 @@ export function useSqlExecution(deps: {
sql ??= await resolvedExecutableSql();
const tab = deps.activeTab.value;
if (!tab || !sql.trim()) return;
if (requiresDatabaseSelection(tab, deps.activeConnection.value)) {
deps.onMissingDatabase?.();
return;
}
deps.activeOutputView.value = "result";
const connName = connectionStore.getConfig(tab.connectionId)?.name || "";
const start = Date.now();
@ -181,3 +191,10 @@ export function useSqlExecution(deps: {
explainMode,
};
}
function requiresDatabaseSelection(tab: QueryTab, connection: ConnectionConfig | undefined): boolean {
if (tab.mode !== "query") return false;
if (!connection || tab.database) return false;
if (isSingleDatabase(connection.db_type)) return false;
return !["elasticsearch", "qdrant", "milvus", "weaviate", "chromadb", "zookeeper"].includes(connection.db_type);
}

View File

@ -392,6 +392,7 @@ export default {
selectConnection: "Select connection",
searchConnection: "Search connections...",
selectDatabase: "Select database",
selectDatabaseRequired: "Select a database first",
searchDatabase: "Search databases...",
selectSchema: "Select schema",
setDefaultDatabase: "Set Default",

View File

@ -393,6 +393,7 @@ export default {
selectConnection: "选择连接",
searchConnection: "搜索连接...",
selectDatabase: "选择数据库",
selectDatabaseRequired: "请先选择数据库",
searchDatabase: "搜索数据库...",
selectSchema: "选择模式",
setDefaultDatabase: "设为默认",