feat(connection): add visible schema filtering
This commit is contained in:
29 files changed
+602
-22
No files matched your search
@@ -36,7 +36,9 @@ import { prestoSqlBuiltinDriverPaths } from "@/lib/prestoSqlBuiltinDriver";
|
||||
import { SQLITE_DATABASE_FILE_EXTENSIONS } from "@/lib/databaseFileDetection";
|
||||
import { ArrowLeft, ArrowDown, ArrowUp, CheckSquare, ChevronRight, CircleHelp, Copy, ExternalLink, FilePlus2, FolderOpen, GripVertical, Grid3X3, KeyRound, Link2, List, ListFilter, Loader2, Pipette, Plus, Search, ShieldCheck, Square, Trash2 } from "@lucide/vue";
|
||||
import { buildDraftVisibleDatabasesConnectionId, connectionCanChooseVisibleDatabases, initialVisibleDatabaseSelection, visibleDatabaseSelectionIsStale } from "@/lib/connectionVisibleDatabases";
|
||||
import { canSaveVisibleDatabaseSelection, filterDatabaseNamesForConnection, isSystemDatabaseName, normalizeVisibleDatabaseSelection } from "@/lib/visibleDatabases";
|
||||
import { canSaveVisibleDatabaseSelection, filterDatabaseNamesForConnection, isSystemDatabaseName, normalizeVisibleDatabaseSelection, buildDraftVisibleSchemasConnectionId } from "@/lib/visibleDatabases";
|
||||
import { isSchemaAware } from "@/lib/databaseFeatureSupport";
|
||||
import VisibleSchemasDialog from "@/components/sidebar/VisibleSchemasDialog.vue";
|
||||
|
||||
type DbOption = { value: string; label: string };
|
||||
type DbCategory = { key: string; title: string; options: DbOption[] };
|
||||
@@ -106,6 +108,11 @@ const visibleDatabaseSelection = ref<Set<string>>(new Set());
|
||||
const visibleDatabaseSearchText = ref("");
|
||||
const visibleDatabaseError = ref("");
|
||||
const visibleDatabaseShowSystem = ref(false);
|
||||
const showVisibleSchemasDialog = ref(false);
|
||||
const isLoadingVisibleSchemas = ref(false);
|
||||
const visibleSchemaNames = ref<string[]>([]);
|
||||
const visibleSchemaInitialSelection = ref<string[]>([]);
|
||||
const visibleSchemaError = ref("");
|
||||
let testRunId = 0;
|
||||
|
||||
const defaultForm = (): ConnectionForm => ({
|
||||
@@ -980,6 +987,7 @@ function onDbTypeChange(val: string) {
|
||||
customDriverName.value = "";
|
||||
applyProfile(val, !!editingId.value);
|
||||
resetTestState();
|
||||
resetVisibleSchemasState();
|
||||
}
|
||||
|
||||
function switchH2ConnectionMode(mode: H2ConnectionMode) {
|
||||
@@ -1296,6 +1304,18 @@ const visibleDatabaseHasSystemDatabases = computed(() => {
|
||||
const connection = connectionConfigSnapshotForVisibleDatabases();
|
||||
return visibleDatabaseNames.value.some((database) => isSystemDatabaseName(connection.db_type, database));
|
||||
});
|
||||
const canChooseVisibleSchemas = computed(() => isSchemaAware(form.value.db_type));
|
||||
const visibleSchemasDatabaseKey = computed(() => form.value.database || "");
|
||||
const hasVisibleSchemaFilter = computed(() => {
|
||||
const key = visibleSchemasDatabaseKey.value;
|
||||
return Array.isArray(form.value.visible_schemas?.[key]);
|
||||
});
|
||||
const visibleSchemaSummary = computed(() => {
|
||||
const key = visibleSchemasDatabaseKey.value;
|
||||
const configured = form.value.visible_schemas?.[key];
|
||||
if (!configured?.length) return t("visibleSchemas.showAll");
|
||||
return t("visibleSchemas.selectedCount", { selected: configured.length, total: visibleSchemaNames.value.length });
|
||||
});
|
||||
const testResultMessage = computed(() => {
|
||||
if (!testResult.value) return "";
|
||||
return testResult.value.ok ? t("connection.testSuccess") : testResult.value.message;
|
||||
@@ -1607,6 +1627,7 @@ function connectionConfigForSubmit(id: string): ConnectionConfig {
|
||||
delete legacy.proxy_username;
|
||||
delete legacy.proxy_password;
|
||||
config.visible_databases = Array.isArray(config.visible_databases) && config.visible_databases.length > 0 ? config.visible_databases : undefined;
|
||||
if (config.visible_schemas && Object.keys(config.visible_schemas).length === 0) config.visible_schemas = undefined;
|
||||
return config as ConnectionConfig;
|
||||
}
|
||||
|
||||
@@ -1929,6 +1950,57 @@ function saveVisibleDatabaseSelection() {
|
||||
showVisibleDatabasesDialog.value = false;
|
||||
}
|
||||
|
||||
function resetVisibleSchemasState() {
|
||||
showVisibleSchemasDialog.value = false;
|
||||
isLoadingVisibleSchemas.value = false;
|
||||
visibleSchemaNames.value = [];
|
||||
visibleSchemaInitialSelection.value = [];
|
||||
visibleSchemaError.value = "";
|
||||
}
|
||||
|
||||
async function openVisibleSchemasPicker() {
|
||||
if (!ensureConnectionHostResolvedFromUrl()) return;
|
||||
if (!canChooseVisibleSchemas.value || isLoadingVisibleSchemas.value) return;
|
||||
isLoadingVisibleSchemas.value = true;
|
||||
visibleSchemaError.value = "";
|
||||
const draftId = buildDraftVisibleSchemasConnectionId(uuid());
|
||||
try {
|
||||
const draftConfig: ConnectionConfig = {
|
||||
...connectionConfigForSubmit(draftId),
|
||||
id: draftId,
|
||||
};
|
||||
await store.addEphemeralConnection(draftConfig);
|
||||
await store.ensureConnected(draftId);
|
||||
const names = await api.listSchemas(draftId, visibleSchemasDatabaseKey.value);
|
||||
visibleSchemaNames.value = names;
|
||||
const key = visibleSchemasDatabaseKey.value;
|
||||
const configured = form.value.visible_schemas?.[key];
|
||||
visibleSchemaInitialSelection.value = Array.isArray(configured) ? configured : [];
|
||||
showVisibleSchemasDialog.value = true;
|
||||
} catch (e: any) {
|
||||
visibleSchemaNames.value = [];
|
||||
visibleSchemaInitialSelection.value = [];
|
||||
visibleSchemaError.value = String(e?.message || e);
|
||||
} finally {
|
||||
isLoadingVisibleSchemas.value = false;
|
||||
store.removeConnection(draftId).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
function handleDraftSchemasSave(selectedNames: string[]) {
|
||||
const key = visibleSchemasDatabaseKey.value;
|
||||
form.value.visible_schemas = { ...(form.value.visible_schemas || {}), [key]: selectedNames };
|
||||
}
|
||||
|
||||
function handleDraftSchemasShowAll() {
|
||||
const key = visibleSchemasDatabaseKey.value;
|
||||
if (form.value.visible_schemas) {
|
||||
const next = { ...form.value.visible_schemas };
|
||||
delete next[key];
|
||||
form.value.visible_schemas = Object.keys(next).length > 0 ? next : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function applyConnectionUrl() {
|
||||
if (applyConnectionUrlToForm(connectionUrlInput.value)) {
|
||||
toast(t("connection.parseConnectionUrlApplied"), 2000);
|
||||
@@ -1963,6 +2035,7 @@ function resetForm() {
|
||||
dbSearchQuery.value = "";
|
||||
configTab.value = "connection";
|
||||
resetVisibleDatabaseDraftState();
|
||||
resetVisibleSchemasState();
|
||||
resetTestState();
|
||||
}
|
||||
|
||||
@@ -3896,6 +3969,11 @@ function openExternalUrl(url: string) {
|
||||
<ListFilter v-else class="mr-1.5 h-4 w-4" />
|
||||
{{ hasVisibleDatabaseFilter ? visibleDatabaseSummary : t("contextMenu.selectVisibleDatabases") }}
|
||||
</Button>
|
||||
<Button v-if="canChooseVisibleSchemas" variant="outline" class="shrink-0" :disabled="isTesting || isSaving || isLoadingVisibleSchemas || !hasRequiredConnectionTarget" @click="openVisibleSchemasPicker">
|
||||
<Loader2 v-if="isLoadingVisibleSchemas" class="mr-1.5 h-4 w-4 animate-spin" />
|
||||
<ListFilter v-else class="mr-1.5 h-4 w-4" />
|
||||
{{ hasVisibleSchemaFilter ? visibleSchemaSummary : t("visibleSchemas.showAll") }}
|
||||
</Button>
|
||||
<Button variant="outline" class="shrink-0" :disabled="isTesting || isSaving" @click="testConnection">
|
||||
{{ isTesting ? t("connection.testing") : t("connection.test") }}
|
||||
</Button>
|
||||
@@ -3985,4 +4063,18 @@ function openExternalUrl(url: string) {
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<VisibleSchemasDialog
|
||||
v-model:open="showVisibleSchemasDialog"
|
||||
draft-mode
|
||||
:connection-id="''"
|
||||
:connection-name="form.name || selectedProfile().label"
|
||||
:database="visibleSchemasDatabaseKey"
|
||||
:draft-schema-names="visibleSchemaNames"
|
||||
:draft-initial-selection="visibleSchemaInitialSelection"
|
||||
:draft-loading="isLoadingVisibleSchemas"
|
||||
:draft-error="visibleSchemaError"
|
||||
@draft:save="handleDraftSchemasSave"
|
||||
@draft:show-all="handleDraftSchemasShowAll"
|
||||
/>
|
||||
</template>
|
||||
@@ -121,7 +121,9 @@ import { rankSavedSqlHistory, type SavedSqlHistoryScope } from "@/lib/savedSqlHi
|
||||
import { isSqlServerLinkedNode } from "@/lib/sqlServerLinkedServers";
|
||||
import DatabaseIcon from "@/components/icons/DatabaseIcon.vue";
|
||||
import ConnectionErrorIndicator from "@/components/connection/ConnectionErrorIndicator.vue";
|
||||
import { isSchemaAware } from "@/lib/databaseFeatureSupport";
|
||||
import VisibleDatabasesDialog from "@/components/sidebar/VisibleDatabasesDialog.vue";
|
||||
import SchemaFilterDialog from "@/components/sidebar/VisibleSchemasDialog.vue";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@@ -153,6 +155,7 @@ type DuplicateStructureSource = TreeNode & { connectionId: string; database: str
|
||||
const DATA_TAB_METADATA_TTL_MS = 30_000;
|
||||
const { getDatabaseOptions } = useDatabaseOptions();
|
||||
const showVisibleDatabasesDialog = ref(false);
|
||||
const showVisibleSchemasDialog = ref(false);
|
||||
const { addTask: addExportTask } = useExportTracker();
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -3065,11 +3068,25 @@ const nodeIconClass = computed(() => {
|
||||
if (props.node.type !== "database") return infoClass;
|
||||
return canCloseDatabaseConnection.value ? infoClass : "text-muted-foreground/65";
|
||||
});
|
||||
|
||||
const canConfigureVisibleDatabases = computed(() => {
|
||||
if (props.node.type !== "connection" || !props.node.connectionId) return false;
|
||||
const dbType = connectionStore.getConfig(props.node.connectionId)?.db_type;
|
||||
return dbType !== "elasticsearch" && dbType !== "qdrant" && dbType !== "milvus" && dbType !== "weaviate" && dbType !== "etcd" && dbType !== "mq" && dbType !== "nacos";
|
||||
});
|
||||
|
||||
const canConfigureVisibleSchemas = computed(() => {
|
||||
if (props.node.type === "database" && props.node.connectionId && props.node.database != null) {
|
||||
const dbType = connectionStore.getConfig(props.node.connectionId)?.db_type;
|
||||
return isSchemaAware(dbType);
|
||||
}
|
||||
if (props.node.type === "connection" && props.node.connectionId) {
|
||||
const dbType = connectionStore.getConfig(props.node.connectionId)?.db_type;
|
||||
return isSchemaAware(dbType) && !usesTreeSchemaMode(dbType);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const canCopyFinalProxyPort = computed(() => {
|
||||
if (props.node.type !== "connection" || !props.node.connectionId) return false;
|
||||
return hasEnabledTransportLayers(connectionStore.getConfig(props.node.connectionId));
|
||||
@@ -3107,6 +3124,10 @@ function openVisibleDatabasesDialog() {
|
||||
showVisibleDatabasesDialog.value = true;
|
||||
}
|
||||
|
||||
function openVisibleSchemasDialog() {
|
||||
showVisibleSchemasDialog.value = true;
|
||||
}
|
||||
|
||||
// --- Connection Group Management ---
|
||||
const isRenamingGroup = ref(false);
|
||||
const renameInput = ref("");
|
||||
@@ -3521,6 +3542,13 @@ function treeItemMenuItems(): ContextMenuItem[] {
|
||||
icon: ListFilter,
|
||||
});
|
||||
}
|
||||
if (canConfigureVisibleSchemas.value) {
|
||||
items.push({
|
||||
label: t("visibleSchemas.title"),
|
||||
action: openVisibleSchemasDialog,
|
||||
icon: ListFilter,
|
||||
});
|
||||
}
|
||||
items.push({ label: t("contextMenu.editConnection"), action: editConnection, icon: Pencil });
|
||||
if (revealConnectionFilePath.value) {
|
||||
items.push({
|
||||
@@ -3613,6 +3641,13 @@ function treeItemMenuItems(): ContextMenuItem[] {
|
||||
icon: RefreshCw,
|
||||
shortcut: shortcutRefresh,
|
||||
});
|
||||
if (canConfigureVisibleSchemas.value) {
|
||||
items.push({
|
||||
label: t("visibleSchemas.title"),
|
||||
action: openVisibleSchemasDialog,
|
||||
icon: ListFilter,
|
||||
});
|
||||
}
|
||||
items.push({ label: "", separator: true });
|
||||
items.push({ label: t("transfer.dataTransfer"), action: openTransfer, icon: ArrowRightLeft });
|
||||
items.push({ label: t("diff.title"), action: openSchemaDiff, icon: ArrowRightLeft });
|
||||
@@ -4021,8 +4056,13 @@ function treeItemMenuItems(): ContextMenuItem[] {
|
||||
</LightTooltip>
|
||||
</div>
|
||||
</CustomContextMenu>
|
||||
|
||||
<VisibleDatabasesDialog v-if="node.type === 'connection' && node.connectionId" v-model:open="showVisibleDatabasesDialog" :connection-id="node.connectionId" :connection-name="node.label" />
|
||||
|
||||
<SchemaFilterDialog v-if="node.type === 'database' && node.connectionId && node.database != null" v-model:open="showVisibleSchemasDialog" :connection-id="node.connectionId" :connection-name="node.label" :database="node.database ?? ''" />
|
||||
|
||||
<SchemaFilterDialog v-else-if="node.type === 'connection' && node.connectionId && canConfigureVisibleSchemas" v-model:open="showVisibleSchemasDialog" :connection-id="node.connectionId" :connection-name="node.label" :database="connectionStore.getConfig(node.connectionId)?.database || ''" />
|
||||
|
||||
<Dialog v-model:open="showDeleteConfirm">
|
||||
<DialogContent class="sm:max-w-[400px]">
|
||||
<DialogHeader>
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { CheckSquare, Loader2, Search, Square } from "@lucide/vue";
|
||||
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/visibleDatabases";
|
||||
import * as api from "@/lib/api";
|
||||
|
||||
const props = defineProps<{
|
||||
open: boolean;
|
||||
connectionId: string;
|
||||
connectionName: string;
|
||||
database: string;
|
||||
/** Draft mode: parent provides schemas/loading/error externally. Component emits events instead of calling store. */
|
||||
draftMode?: boolean;
|
||||
draftSchemaNames?: string[];
|
||||
draftInitialSelection?: string[];
|
||||
draftLoading?: boolean;
|
||||
draftError?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
"update:open": [value: boolean];
|
||||
"draft:save": [selectedNames: string[]];
|
||||
"draft:showAll": [];
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const connectionStore = useConnectionStore();
|
||||
|
||||
const schemaNames = ref<string[]>([]);
|
||||
const selectedNames = ref<Set<string>>(new Set());
|
||||
const searchText = ref("");
|
||||
const isLoading = ref(false);
|
||||
const errorMessage = ref("");
|
||||
|
||||
const isDraftMode = computed(() => props.draftMode);
|
||||
const isLoadingState = computed(() => (isDraftMode.value ? props.draftLoading : isLoading.value));
|
||||
const errorMessageState = computed(() => (isDraftMode.value ? props.draftError || "" : errorMessage.value));
|
||||
|
||||
const filteredSchemaNames = computed(() => {
|
||||
const query = searchText.value.trim().toLowerCase();
|
||||
const names = isDraftMode.value ? props.draftSchemaNames || [] : schemaNames.value;
|
||||
if (!query) return names;
|
||||
return names.filter((name) => name.toLowerCase().includes(query));
|
||||
});
|
||||
const selectedCount = computed(() => selectedNames.value.size);
|
||||
const totalCount = computed(() => {
|
||||
const names = isDraftMode.value ? props.draftSchemaNames || [] : schemaNames.value;
|
||||
return names.length;
|
||||
});
|
||||
const canSaveSelection = computed(() => selectedNames.value.size > 0);
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
(open) => {
|
||||
if (!open) return;
|
||||
if (isDraftMode.value) {
|
||||
initDraftMode();
|
||||
} else {
|
||||
loadSchemas().catch(() => {});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
function initDraftMode() {
|
||||
searchText.value = "";
|
||||
const names = props.draftSchemaNames || [];
|
||||
const configured = props.draftInitialSelection;
|
||||
if (configured && configured.length > 0) {
|
||||
const visibleSet = new Set(normalizeVisibleSchemaSelection(configured, names));
|
||||
selectedNames.value = new Set(names.filter((name) => visibleSet.has(name)));
|
||||
} else {
|
||||
selectedNames.value = new Set(names);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSchemas() {
|
||||
isLoading.value = true;
|
||||
errorMessage.value = "";
|
||||
searchText.value = "";
|
||||
try {
|
||||
const config = connectionStore.getConfig(props.connectionId);
|
||||
const database = props.database || config?.database || "";
|
||||
await connectionStore.ensureConnected(props.connectionId);
|
||||
const names = await api.listSchemas(props.connectionId, database);
|
||||
schemaNames.value = names;
|
||||
const configured = config?.visible_schemas?.[database || ""];
|
||||
if (Array.isArray(configured)) {
|
||||
const visibleSet = new Set(normalizeVisibleSchemaSelection(configured, names));
|
||||
selectedNames.value = new Set(names.filter((name) => visibleSet.has(name)));
|
||||
} else {
|
||||
selectedNames.value = new Set(names);
|
||||
}
|
||||
} catch (e: any) {
|
||||
schemaNames.value = [];
|
||||
selectedNames.value = new Set();
|
||||
errorMessage.value = String(e?.message || e);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSchema(schema: string) {
|
||||
const next = new Set(selectedNames.value);
|
||||
if (next.has(schema)) next.delete(schema);
|
||||
else next.add(schema);
|
||||
selectedNames.value = next;
|
||||
}
|
||||
|
||||
const isSearching = computed(() => searchText.value.trim().length > 0);
|
||||
|
||||
function selectAll() {
|
||||
const names = isDraftMode.value ? props.draftSchemaNames || [] : schemaNames.value;
|
||||
selectedNames.value = new Set(names);
|
||||
}
|
||||
|
||||
function selectFiltered() {
|
||||
const next = new Set(selectedNames.value);
|
||||
for (const name of filteredSchemaNames.value) {
|
||||
next.add(name);
|
||||
}
|
||||
selectedNames.value = next;
|
||||
}
|
||||
|
||||
function clearSelection() {
|
||||
selectedNames.value = new Set();
|
||||
}
|
||||
|
||||
async function showAllSchemas() {
|
||||
if (isDraftMode.value) {
|
||||
selectedNames.value = new Set(props.draftSchemaNames || []);
|
||||
emit("draft:showAll");
|
||||
emit("update:open", false);
|
||||
return;
|
||||
}
|
||||
const config = connectionStore.getConfig(props.connectionId);
|
||||
const database = props.database || config?.database || "";
|
||||
if (config?.visible_schemas?.[database || ""]) {
|
||||
await connectionStore.clearVisibleSchemas(props.connectionId, database);
|
||||
}
|
||||
selectedNames.value = new Set(schemaNames.value);
|
||||
emit("update:open", false);
|
||||
}
|
||||
|
||||
async function saveSelection() {
|
||||
if (!canSaveSelection.value) return;
|
||||
const names = isDraftMode.value ? props.draftSchemaNames || [] : schemaNames.value;
|
||||
const normalized = normalizeVisibleSchemaSelection([...selectedNames.value], names);
|
||||
if (isDraftMode.value) {
|
||||
emit("draft:save", normalized);
|
||||
emit("update:open", false);
|
||||
return;
|
||||
}
|
||||
const config = connectionStore.getConfig(props.connectionId);
|
||||
const database = props.database || config?.database || "";
|
||||
await connectionStore.setVisibleSchemas(props.connectionId, database, normalized);
|
||||
emit("update:open", false);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog :open="open" @update:open="(value: boolean) => emit('update:open', value)">
|
||||
<DialogContent class="sm:max-w-[460px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t("visibleSchemas.title") }}</DialogTitle>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{{ t("visibleSchemas.description", { connection: connectionName }) }}
|
||||
</p>
|
||||
</DialogHeader>
|
||||
|
||||
<div class="flex items-center gap-2 rounded-md border bg-background px-2">
|
||||
<Search class="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<Input v-model="searchText" :placeholder="t('visibleSchemas.searchPlaceholder')" class="h-8 border-0 px-0 shadow-none focus-visible:ring-0" :disabled="isLoadingState || !!errorMessageState" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between text-xs text-muted-foreground">
|
||||
<span>{{ t("visibleSchemas.selectedCount", { selected: selectedCount, total: totalCount }) }}</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<button class="hover:text-foreground disabled:opacity-50" :disabled="isLoadingState" @click="selectAll">
|
||||
{{ t("visibleSchemas.selectAll") }}
|
||||
</button>
|
||||
<button v-if="isSearching" class="hover:text-foreground disabled:opacity-50" :disabled="isLoadingState" @click="selectFiltered">
|
||||
{{ t("visibleSchemas.selectFiltered") }}
|
||||
</button>
|
||||
<button class="hover:text-foreground disabled:opacity-50" :disabled="isLoadingState" @click="clearSelection">
|
||||
{{ t("visibleSchemas.clear") }}
|
||||
</button>
|
||||
<button class="hover:text-foreground disabled:opacity-50" :disabled="isLoadingState" @click="showAllSchemas">
|
||||
{{ t("visibleSchemas.showAll") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="!isLoadingState && !errorMessageState && !canSaveSelection" class="text-xs text-destructive">
|
||||
{{ t("visibleSchemas.emptySelection") }}
|
||||
</p>
|
||||
|
||||
<div 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" />
|
||||
{{ t("common.loading") }}
|
||||
</div>
|
||||
<div v-else-if="errorMessageState" class="p-3 text-sm text-destructive">
|
||||
{{ t("visibleSchemas.loadFailed", { message: errorMessageState }) }}
|
||||
</div>
|
||||
<div v-else-if="!filteredSchemaNames.length" class="p-3 text-sm text-muted-foreground">
|
||||
{{ t("grid.noSearchResults") }}
|
||||
</div>
|
||||
<template v-else>
|
||||
<button
|
||||
v-for="schema in filteredSchemaNames"
|
||||
:key="schema"
|
||||
type="button"
|
||||
class="flex h-8 w-full min-w-0 items-center gap-2 rounded-sm px-2 text-left text-sm hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground focus-visible:outline-none"
|
||||
@click="toggleSchema(schema)"
|
||||
>
|
||||
<CheckSquare v-if="selectedNames.has(schema)" class="h-4 w-4 shrink-0 text-primary" />
|
||||
<Square v-else class="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<span class="truncate">{{ schema }}</span>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" @click="emit('update:open', false)">{{ t("dangerDialog.cancel") }}</Button>
|
||||
<Button :disabled="isLoadingState || !!errorMessageState || !canSaveSelection" @click="saveSelection">
|
||||
{{ t("visibleSchemas.save") }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
Reference in new issue
Block a user