fix(dameng): support configurable system schemas

This commit is contained in:
zipg authored and GitHub committed 2026-07-22 17:40:40 +08:00
1 parent 697b7a4b80
commit a81d6da604
15 files changed
+146 -66

No files matched your search

@@ -80,8 +80,8 @@ import {
Square,
Trash2,
} from "@lucide/vue";
import { buildDraftVisibleDatabasesConnectionId, connectionCanChooseVisibleDatabases, initialVisibleDatabaseSelection, visibleDatabaseSelectionIsStale } from "@/lib/connection/connectionVisibleDatabases";
import { canSaveVisibleDatabaseSelection, connectionUsesVisibleSchemaFilter, filterDatabaseNamesForVisiblePicker, isSystemDatabaseName, normalizeVisibleDatabaseSelection, buildDraftVisibleSchemasConnectionId, normalizeVisibleSchemaSelection } from "@/lib/database/visibleDatabases";
import { buildDraftVisibleDatabasesConnectionId, connectionCanChooseVisibleDatabases, initialVisibleDatabaseSelection, visibleObjectFiltersNeedReset } from "@/lib/connection/connectionVisibleDatabases";
import { canSaveVisibleDatabaseSelection, connectionUsesVisibleSchemaFilter, filterDatabaseNamesForVisiblePicker, filterSchemaNamesForVisiblePicker, normalizeVisibleDatabaseSelection, buildDraftVisibleSchemasConnectionId, normalizeVisibleSchemaSelection } from "@/lib/database/visibleDatabases";
import { isSchemaAware, isSingleDatabase } from "@/lib/database/databaseFeatureSupport";
import VisibleSchemasDialog from "@/components/sidebar/VisibleSchemasDialog.vue";
import CloudflareD1ConnectionFields from "@/components/connection/CloudflareD1ConnectionFields.vue";
@@ -1745,6 +1745,7 @@ watch(
is_production: config.is_production || false,
production_databases: config.production_databases || [],
visible_databases: config.visible_databases,
visible_schemas: config.visible_schemas,
};
productionProtectionEnabled.value = !!config.is_production || (config.production_databases?.length ?? 0) > 0;
connectionUrlInput.value = config.db_type === "h2" && config.connection_string ? config.connection_string : "";
@@ -2240,12 +2241,12 @@ const visibleDatabaseSummary = computed(() => {
if (!Array.isArray(configured)) return t("visibleDatabases.showAll");
return t("visibleDatabases.selectedCount", { selected: configured.length, total: visibleDatabaseNames.value.length });
});
const listedVisibleDatabaseNames = computed(() => {
if (visibleFilterUsesSchemas.value) return visibleDatabaseNames.value;
const defaultListedVisibleDatabaseNames = computed(() => {
const connection = connectionConfigSnapshotForVisibleDatabases();
if (visibleDatabaseShowSystem.value) return visibleDatabaseNames.value;
if (visibleFilterUsesSchemas.value) return filterSchemaNamesForVisiblePicker(visibleDatabaseNames.value, connection);
return filterDatabaseNamesForVisiblePicker(visibleDatabaseNames.value, connection);
});
const listedVisibleDatabaseNames = computed(() => (visibleDatabaseShowSystem.value ? visibleDatabaseNames.value : defaultListedVisibleDatabaseNames.value));
const filteredVisibleDatabaseNames = computed(() => {
const query = visibleDatabaseSearchText.value.trim().toLowerCase();
if (!query) return listedVisibleDatabaseNames.value;
@@ -2254,11 +2255,8 @@ const filteredVisibleDatabaseNames = computed(() => {
const visibleDatabaseSelectedCount = computed(() => visibleDatabaseSelection.value.size);
const visibleDatabaseTotalCount = computed(() => listedVisibleDatabaseNames.value.length);
const visibleDatabaseCanSave = computed(() => canSaveVisibleDatabaseSelection([...visibleDatabaseSelection.value]));
const visibleDatabaseHasSystemDatabases = computed(() => {
if (visibleFilterUsesSchemas.value) return false;
const connection = connectionConfigSnapshotForVisibleDatabases();
return visibleDatabaseNames.value.some((database) => isSystemDatabaseName(connection.db_type, database));
});
const visibleDatabaseHasSystemObjects = computed(() => defaultListedVisibleDatabaseNames.value.length < visibleDatabaseNames.value.length);
const visibleSystemObjectsLabelKey = computed(() => (visibleFilterUsesSchemas.value ? "visibleSchemas.showSystemSchemas" : "visibleDatabases.showSystemDatabases"));
const filteredProductionDatabaseNames = computed(() => {
const query = productionDatabaseSearchText.value.trim().toLowerCase();
if (!query) return productionDatabaseNames.value;
@@ -3227,10 +3225,12 @@ async function openVisibleDatabasesPicker() {
await api.connectDb(draftConfig);
const names = await loadVisibleDatabaseNames(draftId, draftConfig);
visibleDatabaseNames.value = names;
visibleDatabaseShowSystem.value = false;
const configuredSchemas = visibleSchemaObjectSelection.value;
const initialSelection = visibleFilterUsesSchemas.value ? (Array.isArray(configuredSchemas) ? normalizeVisibleSchemaSelection(configuredSchemas, names) : names) : initialVisibleDatabaseSelection(names, form.value.visible_databases, draftConfig);
const initialSelection = visibleFilterUsesSchemas.value ? (Array.isArray(configuredSchemas) ? normalizeVisibleSchemaSelection(configuredSchemas, names) : filterSchemaNamesForVisiblePicker(names, draftConfig)) : initialVisibleDatabaseSelection(names, form.value.visible_databases, draftConfig);
visibleDatabaseSelection.value = new Set(initialSelection);
visibleDatabaseShowSystem.value = !visibleFilterUsesSchemas.value && initialSelection.some((database) => isSystemDatabaseName(draftConfig.db_type, database));
const defaultVisible = new Set(defaultListedVisibleDatabaseNames.value);
visibleDatabaseShowSystem.value = initialSelection.some((name) => !defaultVisible.has(name));
showVisibleDatabasesDialog.value = true;
} catch (e: any) {
visibleDatabaseNames.value = [];
@@ -3623,18 +3623,19 @@ watch([() => form.value.db_type, () => form.value.username], () => {
watch(
() => connectionConfigSnapshotForVisibleDatabases(),
(current, previous) => {
if (!previous || !form.value.visible_databases?.length) return;
if (!visibleDatabaseSelectionIsStale(previous, current)) return;
if (!previous || !visibleObjectFiltersNeedReset(previous, current)) return;
form.value.visible_databases = undefined;
visibleDatabaseNames.value = [];
visibleDatabaseSelection.value = new Set();
form.value.visible_schemas = undefined;
resetVisibleDatabaseDraftState();
resetVisibleSchemasState();
},
);
watch(visibleDatabaseShowSystem, (show) => {
if (show) return;
const connection = connectionConfigSnapshotForVisibleDatabases();
visibleDatabaseSelection.value = new Set([...visibleDatabaseSelection.value].filter((database) => !isSystemDatabaseName(connection.db_type, database)));
const visible = new Set(visibleFilterUsesSchemas.value ? filterSchemaNamesForVisiblePicker(visibleDatabaseNames.value, connection) : filterDatabaseNamesForVisiblePicker(visibleDatabaseNames.value, connection));
visibleDatabaseSelection.value = new Set([...visibleDatabaseSelection.value].filter((name) => visible.has(name)));
});
watch(canUseTransportLayers, (value) => {
@@ -6250,9 +6251,9 @@ function openExternalUrl(url: string) {
{{ t(visibleObjectEmptySelectionKey) }}
</p>
<label v-if="visibleDatabaseHasSystemDatabases" class="flex h-8 items-center gap-2 rounded-md px-1 text-xs text-muted-foreground">
<label v-if="visibleDatabaseHasSystemObjects" class="flex h-8 items-center gap-2 rounded-md px-1 text-xs text-muted-foreground">
<input v-model="visibleDatabaseShowSystem" type="checkbox" class="h-3.5 w-3.5 accent-primary" :disabled="isLoadingVisibleDatabases || !!visibleDatabaseError" />
<span>{{ t("visibleDatabases.showSystemDatabases") }}</span>
<span>{{ t(visibleSystemObjectsLabelKey) }}</span>
</label>
<div class="h-72 overflow-y-auto rounded-md border bg-background/50 p-1">
@@ -6364,6 +6365,8 @@ function openExternalUrl(url: string) {
:connection-id="''"
:connection-name="form.name || selectedProfile().label"
:database="visibleSchemasDatabaseKey"
:database-type="form.db_type"
:username="form.username"
:draft-schema-names="visibleSchemaNames"
:draft-initial-selection="visibleSchemaInitialSelection"
:draft-loading="isLoadingVisibleSchemas"
@@ -6,7 +6,7 @@ import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { useConnectionStore } from "@/stores/connectionStore";
import { canSaveVisibleDatabaseSelection, connectionUsesVisibleSchemaFilter, filterDatabaseNamesForVisiblePicker, isSystemDatabaseName, normalizeVisibleDatabaseSelection } from "@/lib/database/visibleDatabases";
import { canSaveVisibleDatabaseSelection, connectionUsesVisibleSchemaFilter, filterDatabaseNamesForVisiblePicker, filterSchemaNamesForVisiblePicker, normalizeVisibleDatabaseSelection } from "@/lib/database/visibleDatabases";
import * as api from "@/lib/backend/api";
const props = defineProps<{
@@ -40,11 +40,12 @@ const descriptionKey = computed(() => (isSchemaFilterMode.value ? "visibleSchema
const searchPlaceholderKey = computed(() => (isSchemaFilterMode.value ? "visibleSchemas.searchPlaceholder" : "visibleDatabases.searchPlaceholder"));
const emptySelectionKey = computed(() => (isSchemaFilterMode.value ? "visibleSchemas.emptySelection" : "visibleDatabases.emptySelection"));
const loadFailedKey = computed(() => (isSchemaFilterMode.value ? "visibleSchemas.loadFailed" : "visibleDatabases.loadFailed"));
const listedObjectNames = computed(() => {
if (isSchemaFilterMode.value) return objectNames.value;
if (showSystemDatabases.value) return objectNames.value;
const showSystemObjectsKey = computed(() => (isSchemaFilterMode.value ? "visibleSchemas.showSystemSchemas" : "visibleDatabases.showSystemDatabases"));
const defaultObjectNames = computed(() => {
if (isSchemaFilterMode.value) return filterSchemaNamesForVisiblePicker(objectNames.value, connection.value);
return filterDatabaseNamesForVisiblePicker(objectNames.value, connection.value);
});
const listedObjectNames = computed(() => (showSystemDatabases.value ? objectNames.value : defaultObjectNames.value));
const filteredObjectNames = computed(() => {
const query = searchText.value.trim().toLowerCase();
if (!query) return listedObjectNames.value;
@@ -53,7 +54,7 @@ const filteredObjectNames = computed(() => {
const selectedCount = computed(() => selectedNames.value.size);
const totalCount = computed(() => listedObjectNames.value.length);
const canSaveSelection = computed(() => canSaveVisibleDatabaseSelection([...selectedNames.value]));
const hasSystemDatabases = computed(() => !isSchemaFilterMode.value && objectNames.value.some((database) => isSystemDatabaseName(connection.value?.db_type, database)));
const hasSystemObjects = computed(() => defaultObjectNames.value.length < objectNames.value.length);
const showAllDisabled = computed(() => {
if (isSchemaFilterMode.value) {
return !connection.value?.visible_schemas?.[databaseKey.value];
@@ -73,7 +74,8 @@ watch(
watch(showSystemDatabases, (show) => {
if (show) return;
selectedNames.value = new Set([...selectedNames.value].filter((database) => !isSystemDatabaseName(connection.value?.db_type, database)));
const visible = new Set(listedObjectNames.value);
selectedNames.value = new Set([...selectedNames.value].filter((name) => visible.has(name)));
});
async function loadDatabases() {
@@ -83,10 +85,12 @@ async function loadDatabases() {
try {
const names = await loadObjectNames();
objectNames.value = names;
showSystemDatabases.value = false;
const configured = isSchemaFilterMode.value ? connection.value?.visible_schemas?.[databaseKey.value] : connection.value?.visible_databases;
const initialSelection = Array.isArray(configured) ? normalizeVisibleDatabaseSelection(configured, names) : listedObjectNames.value;
selectedNames.value = new Set(initialSelection);
showSystemDatabases.value = !isSchemaFilterMode.value && initialSelection.some((database) => isSystemDatabaseName(connection.value?.db_type, database));
const defaultVisible = new Set(defaultObjectNames.value);
showSystemDatabases.value = initialSelection.some((name) => !defaultVisible.has(name));
} catch (e: any) {
objectNames.value = [];
selectedNames.value = new Set();
@@ -190,9 +194,9 @@ async function saveSelection() {
{{ t(emptySelectionKey) }}
</p>
<label v-if="hasSystemDatabases" class="flex h-8 items-center gap-2 rounded-md px-1 text-xs text-muted-foreground">
<label v-if="hasSystemObjects" class="flex h-8 items-center gap-2 rounded-md px-1 text-xs text-muted-foreground">
<input v-model="showSystemDatabases" type="checkbox" class="h-3.5 w-3.5 accent-primary" :disabled="isLoading || !!errorMessage" />
<span>{{ t("visibleDatabases.showSystemDatabases") }}</span>
<span>{{ t(showSystemObjectsKey) }}</span>
</label>
<div class="h-72 overflow-y-auto rounded-md border bg-background/50 p-1">
@@ -7,7 +7,8 @@ import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { useConnectionStore } from "@/stores/connectionStore";
import { normalizeVisibleSchemaSelection } from "@/lib/database/visibleDatabases";
import { filterSchemaNamesForVisiblePicker, normalizeVisibleSchemaSelection } from "@/lib/database/visibleDatabases";
import type { DatabaseType } from "@/types/database";
import * as api from "@/lib/backend/api";
import { addVisibleSchemas, initialVisibleSchemaSelection, resolveVisibleSchemaSaveAction, selectVisibleSchemas } from "./visibleSchemasDialogState";
@@ -16,6 +17,8 @@ const props = defineProps<{
connectionId: string;
connectionName: string;
database: string;
databaseType?: DatabaseType;
username?: string;
/** Draft mode: parent provides schemas/loading/error externally. Component emits events instead of calling store. */
draftMode?: boolean;
draftSchemaNames?: string[];
@@ -39,22 +42,26 @@ const searchText = ref("");
const isLoading = ref(false);
const isSaving = ref(false);
const errorMessage = ref("");
const showSystemSchemas = ref(false);
const isDraftMode = computed(() => props.draftMode);
const isLoadingState = computed(() => (isDraftMode.value ? props.draftLoading : isLoading.value));
const errorMessageState = computed(() => (isDraftMode.value ? props.draftError || "" : errorMessage.value));
const connection = computed(() => (isDraftMode.value ? { db_type: props.databaseType, username: props.username || "" } : connectionStore.getConfig(props.connectionId)));
const allSchemaNames = computed(() => (isDraftMode.value ? props.draftSchemaNames || [] : schemaNames.value));
const listedSchemaNames = computed(() => (showSystemSchemas.value ? allSchemaNames.value : filterSchemaNamesForVisiblePicker(allSchemaNames.value, connection.value)));
const hasSystemSchemas = computed(() => filterSchemaNamesForVisiblePicker(allSchemaNames.value, connection.value).length < allSchemaNames.value.length);
const filteredSchemaNames = computed(() => {
const query = searchText.value.trim().toLowerCase();
const names = isDraftMode.value ? props.draftSchemaNames || [] : schemaNames.value;
const names = listedSchemaNames.value;
if (!query) return names;
return names.filter((name) => name.toLowerCase().includes(query));
});
const filteredSchemaItems = computed(() => filteredSchemaNames.value.map((name) => ({ name })));
const selectedCount = computed(() => selectedNames.value.size);
const totalCount = computed(() => {
const names = isDraftMode.value ? props.draftSchemaNames || [] : schemaNames.value;
return names.length;
return listedSchemaNames.value.length;
});
const canSaveSelection = computed(() => selectedNames.value.size > 0);
@@ -75,8 +82,7 @@ watch(
function initDraftMode() {
searchText.value = "";
const names = props.draftSchemaNames || [];
// Draft callers historically use an empty array for both absent and all-selected state.
selectedNames.value = initialVisibleSchemaSelection(names, props.draftInitialSelection, true);
initializeSelection(names, props.draftInitialSelection, true);
}
async function loadSchemas() {
@@ -90,7 +96,7 @@ async function loadSchemas() {
const names = await api.listSchemas(props.connectionId, database);
schemaNames.value = names;
const configured = config?.visible_schemas?.[database || ""];
selectedNames.value = initialVisibleSchemaSelection(names, configured);
initializeSelection(names, configured);
} catch (e: any) {
schemaNames.value = [];
selectedNames.value = new Set();
@@ -100,6 +106,21 @@ async function loadSchemas() {
}
}
function initializeSelection(names: string[], configured: string[] | undefined, emptySelectionMeansAll = false) {
showSystemSchemas.value = false;
const hasExplicitSelection = configured !== undefined && !(emptySelectionMeansAll && configured.length === 0);
const initial = hasExplicitSelection ? initialVisibleSchemaSelection(names, configured) : selectVisibleSchemas(filterSchemaNamesForVisiblePicker(names, connection.value));
const defaultVisible = new Set(filterSchemaNamesForVisiblePicker(names, connection.value));
selectedNames.value = initial;
showSystemSchemas.value = hasExplicitSelection && [...initial].some((name) => !defaultVisible.has(name));
}
watch(showSystemSchemas, (show) => {
if (show) return;
const visible = new Set(listedSchemaNames.value);
selectedNames.value = new Set([...selectedNames.value].filter((name) => visible.has(name)));
});
function toggleSchema(schema: string) {
const next = new Set(selectedNames.value);
if (next.has(schema)) next.delete(schema);
@@ -110,8 +131,7 @@ function toggleSchema(schema: string) {
const isSearching = computed(() => searchText.value.trim().length > 0);
function selectAll() {
const names = isDraftMode.value ? props.draftSchemaNames || [] : schemaNames.value;
selectedNames.value = selectVisibleSchemas(names);
selectedNames.value = selectVisibleSchemas(listedSchemaNames.value);
}
function selectFiltered() {
@@ -148,7 +168,7 @@ async function showAllSchemas() {
async function saveSelection() {
if (!canSaveSelection.value || isSaving.value) return;
const names = isDraftMode.value ? props.draftSchemaNames || [] : schemaNames.value;
const names = listedSchemaNames.value;
const normalized = normalizeVisibleSchemaSelection([...selectedNames.value], names);
if (isDraftMode.value) {
emit("draft:save", normalized);
@@ -211,6 +231,11 @@ async function saveSelection() {
{{ t("visibleSchemas.emptySelection") }}
</p>
<label v-if="hasSystemSchemas" class="flex h-8 items-center gap-2 rounded-md px-1 text-xs text-muted-foreground">
<input v-model="showSystemSchemas" type="checkbox" class="h-3.5 w-3.5 accent-primary" :disabled="isLoadingState || !!errorMessageState" />
<span>{{ t("visibleSchemas.showSystemSchemas") }}</span>
</label>
<div v-if="isLoadingState || errorMessageState || !filteredSchemaItems.length" class="h-72 overflow-y-auto rounded-md border bg-background/50 p-1">
<div v-if="isLoadingState" class="flex h-full items-center justify-center gap-2 text-sm text-muted-foreground">
<Loader2 class="h-4 w-4 animate-spin" />