fix: let users opt into system databases
This commit is contained in:
1 parent
7bdacc52ba
commit
76a0b9f76d
11 files changed
+190
-35
No files matched your search
@@ -5,9 +5,12 @@ import { CheckSquare, Loader2, Search, Square } from "lucide-vue-next";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useDatabaseOptions } from "@/composables/useDatabaseOptions";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { normalizeVisibleDatabaseSelection } from "@/lib/visibleDatabases";
|
||||
import {
|
||||
filterDatabaseNamesForConnection,
|
||||
isSystemDatabaseName,
|
||||
normalizeVisibleDatabaseSelection,
|
||||
} from "@/lib/visibleDatabases";
|
||||
import * as api from "@/lib/api";
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -22,22 +25,29 @@ const emit = defineEmits<{
|
||||
|
||||
const { t } = useI18n();
|
||||
const connectionStore = useConnectionStore();
|
||||
const { getDatabaseOptions } = useDatabaseOptions();
|
||||
|
||||
const databaseNames = ref<string[]>([]);
|
||||
const selectedNames = ref<Set<string>>(new Set());
|
||||
const searchText = ref("");
|
||||
const showSystemDatabases = ref(false);
|
||||
const isLoading = ref(false);
|
||||
const errorMessage = ref("");
|
||||
|
||||
const connection = computed(() => connectionStore.getConfig(props.connectionId));
|
||||
const listedDatabaseNames = computed(() => {
|
||||
if (showSystemDatabases.value) return databaseNames.value;
|
||||
return filterDatabaseNamesForConnection(databaseNames.value, connection.value);
|
||||
});
|
||||
const filteredDatabaseNames = computed(() => {
|
||||
const query = searchText.value.trim().toLowerCase();
|
||||
if (!query) return databaseNames.value;
|
||||
return databaseNames.value.filter((name) => name.toLowerCase().includes(query));
|
||||
if (!query) return listedDatabaseNames.value;
|
||||
return listedDatabaseNames.value.filter((name) => name.toLowerCase().includes(query));
|
||||
});
|
||||
const selectedCount = computed(() => selectedNames.value.size);
|
||||
const totalCount = computed(() => databaseNames.value.length);
|
||||
const totalCount = computed(() => listedDatabaseNames.value.length);
|
||||
const hasSystemDatabases = computed(() =>
|
||||
databaseNames.value.some((database) => isSystemDatabaseName(connection.value?.db_type, database)),
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
@@ -47,6 +57,13 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
watch(showSystemDatabases, (show) => {
|
||||
if (show) return;
|
||||
selectedNames.value = new Set(
|
||||
[...selectedNames.value].filter((database) => !isSystemDatabaseName(connection.value?.db_type, database)),
|
||||
);
|
||||
});
|
||||
|
||||
async function loadDatabases() {
|
||||
isLoading.value = true;
|
||||
errorMessage.value = "";
|
||||
@@ -55,11 +72,17 @@ async function loadDatabases() {
|
||||
const names = await loadDatabaseNames();
|
||||
databaseNames.value = names;
|
||||
const configured = connection.value?.visible_databases;
|
||||
const initialSelection = Array.isArray(configured) ? normalizeVisibleDatabaseSelection(configured, names) : names;
|
||||
const initialSelection = Array.isArray(configured)
|
||||
? normalizeVisibleDatabaseSelection(configured, names)
|
||||
: filterDatabaseNamesForConnection(names, connection.value);
|
||||
selectedNames.value = new Set(initialSelection);
|
||||
showSystemDatabases.value = initialSelection.some((database) =>
|
||||
isSystemDatabaseName(connection.value?.db_type, database),
|
||||
);
|
||||
} catch (e: any) {
|
||||
databaseNames.value = [];
|
||||
selectedNames.value = new Set();
|
||||
showSystemDatabases.value = false;
|
||||
errorMessage.value = String(e?.message || e);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
@@ -72,7 +95,14 @@ async function loadDatabaseNames(): Promise<string[]> {
|
||||
await connectionStore.ensureConnected(props.connectionId);
|
||||
return api.listSchemas(props.connectionId, config.database || "");
|
||||
}
|
||||
return getDatabaseOptions(props.connectionId);
|
||||
await connectionStore.ensureConnected(props.connectionId);
|
||||
if (config?.db_type === "redis") {
|
||||
return (await api.redisListDatabases(props.connectionId)).map((database) => String(database.db));
|
||||
}
|
||||
if (config?.db_type === "mongodb") {
|
||||
return api.mongoListDatabases(props.connectionId);
|
||||
}
|
||||
return (await api.listDatabases(props.connectionId)).map((database) => database.name);
|
||||
}
|
||||
|
||||
function toggleDatabase(database: string) {
|
||||
@@ -83,7 +113,7 @@ function toggleDatabase(database: string) {
|
||||
}
|
||||
|
||||
function selectAll() {
|
||||
selectedNames.value = new Set(databaseNames.value);
|
||||
selectedNames.value = new Set(listedDatabaseNames.value);
|
||||
}
|
||||
|
||||
function clearSelection() {
|
||||
@@ -140,6 +170,19 @@ async function saveSelection() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label
|
||||
v-if="hasSystemDatabases"
|
||||
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>
|
||||
</label>
|
||||
|
||||
<div class="h-72 overflow-y-auto rounded-md border bg-background/50 p-1">
|
||||
<div v-if="isLoading" class="flex h-full items-center justify-center gap-2 text-sm text-muted-foreground">
|
||||
<Loader2 class="h-4 w-4 animate-spin" />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ref } from "vue";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { filterDatabaseNamesForConnection } from "@/lib/visibleDatabases";
|
||||
import * as api from "@/lib/api";
|
||||
|
||||
export function useDatabaseOptions() {
|
||||
@@ -19,10 +20,16 @@ export function useDatabaseOptions() {
|
||||
const dbs = await api.redisListDatabases(connectionId);
|
||||
databaseOptions.value[connectionId] = dbs.map((db) => String(db.db));
|
||||
} else if (connection.db_type === "mongodb") {
|
||||
databaseOptions.value[connectionId] = await api.mongoListDatabases(connectionId);
|
||||
databaseOptions.value[connectionId] = filterDatabaseNamesForConnection(
|
||||
await api.mongoListDatabases(connectionId),
|
||||
connection,
|
||||
);
|
||||
} else {
|
||||
const dbs = await api.listDatabases(connectionId);
|
||||
databaseOptions.value[connectionId] = dbs.map((db) => db.name);
|
||||
databaseOptions.value[connectionId] = filterDatabaseNamesForConnection(
|
||||
dbs.map((db) => db.name),
|
||||
connection,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
loadingDatabaseOptions.value[connectionId] = false;
|
||||
|
||||
@@ -854,6 +854,7 @@ export default {
|
||||
selectAll: "Select all",
|
||||
clear: "Clear",
|
||||
showAll: "Show all",
|
||||
showSystemDatabases: "Show system databases",
|
||||
save: "Save",
|
||||
loadFailed: "Failed to load databases: {message}",
|
||||
},
|
||||
|
||||
@@ -747,6 +747,7 @@ export default {
|
||||
selectAll: "Seleccionar todo",
|
||||
clear: "Limpiar",
|
||||
showAll: "Mostrar todo",
|
||||
showSystemDatabases: "Mostrar bases de datos del sistema",
|
||||
save: "Guardar",
|
||||
loadFailed: "No se pudieron cargar las bases de datos: {message}",
|
||||
},
|
||||
|
||||
@@ -835,6 +835,7 @@ export default {
|
||||
selectAll: "全选",
|
||||
clear: "清空",
|
||||
showAll: "显示全部",
|
||||
showSystemDatabases: "显示系统库",
|
||||
save: "保存",
|
||||
loadFailed: "加载数据库失败:{message}",
|
||||
},
|
||||
|
||||
@@ -1,3 +1,68 @@
|
||||
import type { ConnectionConfig, DatabaseType } from "@/types/database";
|
||||
|
||||
const SYSTEM_DATABASE_RULES: Partial<Record<DatabaseType, ReadonlySet<string>>> = {
|
||||
mysql: new Set(["information_schema", "mysql", "performance_schema", "sys"]),
|
||||
doris: new Set(["information_schema", "mysql", "performance_schema", "sys"]),
|
||||
starrocks: new Set(["information_schema", "mysql", "performance_schema", "sys"]),
|
||||
goldendb: new Set(["information_schema", "mysql", "performance_schema", "sys"]),
|
||||
gbase: new Set(["information_schema", "mysql", "performance_schema", "sys"]),
|
||||
postgres: new Set(["template0", "template1"]),
|
||||
gaussdb: new Set(["template0", "template1"]),
|
||||
opengauss: new Set(["template0", "template1"]),
|
||||
kingbase: new Set(["template0", "template1"]),
|
||||
highgo: new Set(["template0", "template1"]),
|
||||
vastbase: new Set(["template0", "template1"]),
|
||||
redshift: new Set(["template0", "template1"]),
|
||||
clickhouse: new Set(["information_schema", "system"]),
|
||||
sqlserver: new Set(["master", "model", "msdb", "tempdb"]),
|
||||
mongodb: new Set(["admin", "config", "local"]),
|
||||
oracle: new Set([
|
||||
"anonymous",
|
||||
"appqossys",
|
||||
"audsys",
|
||||
"ctxsys",
|
||||
"dbsnmp",
|
||||
"dip",
|
||||
"dvf",
|
||||
"dvsys",
|
||||
"exfsys",
|
||||
"flows_files",
|
||||
"gsmadmin_internal",
|
||||
"mddata",
|
||||
"mdsys",
|
||||
"mgmt_view",
|
||||
"olapsys",
|
||||
"orddata",
|
||||
"ordplugins",
|
||||
"ordsys",
|
||||
"outln",
|
||||
"owbsys",
|
||||
"remote_scheduler_agent",
|
||||
"si_informtn_schema",
|
||||
"sys",
|
||||
"sysback",
|
||||
"sysdg",
|
||||
"syskm",
|
||||
"system",
|
||||
"wmsys",
|
||||
"xdb",
|
||||
"xs$null",
|
||||
]),
|
||||
dameng: new Set(["ctisys", "dba", "sys", "sysauditor", "sysdba", "syssso", "system"]),
|
||||
saphana: new Set(["_sys_afl", "_sys_bi", "_sys_bic", "_sys_repo", "_sys_statistics", "sys"]),
|
||||
cassandra: new Set([
|
||||
"system",
|
||||
"system_auth",
|
||||
"system_distributed",
|
||||
"system_schema",
|
||||
"system_traces",
|
||||
"system_views",
|
||||
"system_virtual_schema",
|
||||
]),
|
||||
neo4j: new Set(["system"]),
|
||||
snowflake: new Set(["snowflake", "snowflake_sample_data"]),
|
||||
};
|
||||
|
||||
export function visibleDatabaseFilterIsEnabled(visibleDatabases: string[] | undefined): boolean {
|
||||
return Array.isArray(visibleDatabases);
|
||||
}
|
||||
@@ -17,3 +82,19 @@ export function normalizeVisibleDatabaseSelection(selectedNames: string[], datab
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
export function isSystemDatabaseName(databaseType: DatabaseType | undefined, databaseName: string): boolean {
|
||||
if (!databaseType) return false;
|
||||
return SYSTEM_DATABASE_RULES[databaseType]?.has(databaseName.toLowerCase()) ?? false;
|
||||
}
|
||||
|
||||
export function filterDatabaseNamesForConnection(
|
||||
databaseNames: string[],
|
||||
connection: Pick<ConnectionConfig, "db_type" | "visible_databases"> | undefined,
|
||||
): string[] {
|
||||
const visibleDatabases = connection?.visible_databases;
|
||||
if (visibleDatabaseFilterIsEnabled(visibleDatabases)) {
|
||||
return filterVisibleDatabaseNames(databaseNames, visibleDatabases);
|
||||
}
|
||||
return databaseNames.filter((name) => !isSystemDatabaseName(connection?.db_type, name));
|
||||
}
|
||||
@@ -25,7 +25,11 @@ import { buildDatabaseTreeNodes } from "@/lib/databaseTree";
|
||||
import { buildSqlServerDatabaseTreeNodes, SQLSERVER_DEFAULT_SCHEMA } from "@/lib/sqlServerTree";
|
||||
import { findDatabaseTreeNode } from "@/lib/treeRefreshTarget";
|
||||
import { shouldMarkDisconnected } from "@/lib/connectionHealth";
|
||||
import { filterVisibleDatabaseNames, normalizeVisibleDatabaseSelection } from "@/lib/visibleDatabases";
|
||||
import {
|
||||
filterDatabaseNamesForConnection,
|
||||
filterVisibleDatabaseNames,
|
||||
normalizeVisibleDatabaseSelection,
|
||||
} from "@/lib/visibleDatabases";
|
||||
import {
|
||||
buildGroupedObjectTreeNodes,
|
||||
buildTableTreeNodes,
|
||||
@@ -695,7 +699,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
}
|
||||
}
|
||||
const schemas = await api.listSchemas(connectionId, effectiveDb);
|
||||
const visibleSchemas = filterVisibleDatabaseNames(schemas, config?.visible_databases);
|
||||
const visibleSchemas = filterDatabaseNamesForConnection(schemas, config);
|
||||
const schemaNodes: TreeNode[] = visibleSchemas.map((s) => ({
|
||||
id: `${connectionId}:${s}:${s}`,
|
||||
label: s,
|
||||
@@ -718,9 +722,9 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
}
|
||||
}
|
||||
const databases = await api.listDatabases(connectionId);
|
||||
const visibleNames = filterVisibleDatabaseNames(
|
||||
const visibleNames = filterDatabaseNamesForConnection(
|
||||
databases.map((database) => database.name),
|
||||
config?.visible_databases,
|
||||
config,
|
||||
);
|
||||
const visibleNameSet = new Set(visibleNames);
|
||||
const visibleDatabases = databases.filter((database) => visibleNameSet.has(database.name));
|
||||
@@ -810,7 +814,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
await ensureConnected(connectionId);
|
||||
const dbs = await api.mongoListDatabases(connectionId);
|
||||
const config = getConfig(connectionId);
|
||||
const visibleDbs = filterVisibleDatabaseNames(dbs, config?.visible_databases);
|
||||
const visibleDbs = filterDatabaseNamesForConnection(dbs, config);
|
||||
setChildren(
|
||||
node,
|
||||
withSavedSqlRoot(
|
||||
|
||||
Reference in new issue
Block a user