feat: 执行查询前提示选择数据库
* Improve:执行查询前提示选择数据库 * Fix:补齐连接配置测试字段 --------- Co-authored-by: staff <staff@qimaos-MacBook-Pro.local>
This commit is contained in:
parent
dbfe284ca9
commit
74d57982c9
|
|
@ -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()"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -393,6 +393,7 @@ export default {
|
|||
selectConnection: "选择连接",
|
||||
searchConnection: "搜索连接...",
|
||||
selectDatabase: "选择数据库",
|
||||
selectDatabaseRequired: "请先选择数据库",
|
||||
searchDatabase: "搜索数据库...",
|
||||
selectSchema: "选择模式",
|
||||
setDefaultDatabase: "设为默认",
|
||||
|
|
|
|||
Loading…
Reference in New Issue