feat(mongo): add GridFS manager for MongoDB
* feat(mongo): browse GridFS buckets and files Refs #2342 * feat(mongo): add GridFS manager for MongoDB * chore(i18n): autofill new translations --------- Co-authored-by: dbx-i18n-bot <github-actions[bot]@users.noreply.github.com> Co-authored-by: t8y2 <1156263951@qq.com>
This commit is contained in:
35 files changed
+1959
-39
No files matched your search
@@ -375,6 +375,13 @@ export const documentListDatabases = forward("documentListDatabases");
|
||||
export const mongoListDatabases = forward("mongoListDatabases");
|
||||
export const documentListCollections = forward("documentListCollections");
|
||||
export const mongoListCollections = forward("mongoListCollections");
|
||||
export const documentListGridFsBuckets = forward("documentListGridFsBuckets");
|
||||
export const documentCreateGridFsBucket = forward("documentCreateGridFsBucket");
|
||||
export const documentDeleteGridFsBucket = forward("documentDeleteGridFsBucket");
|
||||
export const documentListGridFsFiles = forward("documentListGridFsFiles");
|
||||
export const documentDownloadGridFsFile = forward("documentDownloadGridFsFile");
|
||||
export const documentUploadGridFsFile = forward("documentUploadGridFsFile");
|
||||
export const documentDeleteGridFsFile = forward("documentDeleteGridFsFile");
|
||||
export const vectorGetCollectionDetail = forward("vectorGetCollectionDetail");
|
||||
export const mongoCreateDatabase = forward("mongoCreateDatabase");
|
||||
export const mongoDropDatabase = forward("mongoDropDatabase");
|
||||
|
||||
@@ -64,6 +64,7 @@ import type {
|
||||
KvPutResponse,
|
||||
KvDeleteResponse,
|
||||
MongoDocumentResult,
|
||||
MongoGridFsBucketInfo,
|
||||
HistoryEntry,
|
||||
SqlFileRequest,
|
||||
SqlFilePreview,
|
||||
@@ -88,6 +89,7 @@ import type {
|
||||
BuildExplainSqlOptions,
|
||||
ExplainSqlBuildResult,
|
||||
DroppedFilePreviewSqlOptions,
|
||||
MongoGridFsFileInfo,
|
||||
} from "./tauri";
|
||||
import type { QueryEditability } from "@/lib/sqlAnalysis";
|
||||
import type {
|
||||
@@ -1800,6 +1802,55 @@ export async function documentFindDocuments(connectionId: string, database: stri
|
||||
return post("/api/document-store/find-documents", { connectionId, database, collection, skip, limit, filter, projection, sort, executionId });
|
||||
}
|
||||
|
||||
export async function documentListGridFsFiles(connectionId: string, database: string, bucket: string): Promise<MongoGridFsFileInfo[]> {
|
||||
return post("/api/document-store/list-gridfs-files", { connectionId, database, bucket });
|
||||
}
|
||||
|
||||
export async function documentListGridFsBuckets(connectionId: string, database: string): Promise<MongoGridFsBucketInfo[]> {
|
||||
return post("/api/document-store/list-gridfs-buckets", { connectionId, database });
|
||||
}
|
||||
|
||||
export async function documentCreateGridFsBucket(connectionId: string, database: string, bucket: string): Promise<void> {
|
||||
return post("/api/document-store/create-gridfs-bucket", { connectionId, database, bucket });
|
||||
}
|
||||
|
||||
export async function documentDeleteGridFsBucket(connectionId: string, database: string, bucket: string): Promise<void> {
|
||||
return post("/api/document-store/delete-gridfs-bucket", { connectionId, database, bucket });
|
||||
}
|
||||
|
||||
export async function documentDownloadGridFsFile(connectionId: string, database: string, bucket: string, fileId: string): Promise<Uint8Array> {
|
||||
const res = await fetch(apiUrl("/api/document-store/download-gridfs-file"), {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ connectionId, database, bucket, fileId }),
|
||||
});
|
||||
if (!res.ok) throw new Error(await res.text());
|
||||
const data = (await res.json()) as number[];
|
||||
return new Uint8Array(data);
|
||||
}
|
||||
|
||||
export async function documentUploadGridFsFile(connectionId: string, database: string, bucket: string, fileName: string, data: Uint8Array, contentType?: string): Promise<string> {
|
||||
const body = new FormData();
|
||||
body.append("connectionId", connectionId);
|
||||
body.append("database", database);
|
||||
body.append("bucket", bucket);
|
||||
body.append("fileName", fileName);
|
||||
if (contentType) body.append("contentType", contentType);
|
||||
const bytes = new Uint8Array(data.byteLength);
|
||||
bytes.set(data);
|
||||
body.append("file", new Blob([bytes], { type: contentType || "application/octet-stream" }), fileName);
|
||||
const res = await fetch(apiUrl("/api/document-store/upload-gridfs-file"), {
|
||||
method: "POST",
|
||||
body,
|
||||
});
|
||||
if (!res.ok) throw new Error(await res.text());
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function documentDeleteGridFsFile(connectionId: string, database: string, bucket: string, fileId: string): Promise<void> {
|
||||
return post("/api/document-store/delete-gridfs-file", { connectionId, database, bucket, fileId });
|
||||
}
|
||||
|
||||
export async function mongoServerVersion(connectionId: string, database: string, executionId?: string): Promise<string> {
|
||||
return post("/api/mongo/server-version", { connectionId, database, executionId });
|
||||
}
|
||||
|
||||
@@ -15,6 +15,11 @@ export type ActiveTabSidebarTarget =
|
||||
database: string;
|
||||
collectionName: string;
|
||||
}
|
||||
| {
|
||||
type: "mongo-gridfs";
|
||||
connectionId: string;
|
||||
database: string;
|
||||
}
|
||||
| {
|
||||
type: "vector-collection";
|
||||
connectionId: string;
|
||||
@@ -75,6 +80,22 @@ export function activeTabSidebarTarget(tab: QueryTab | undefined | null): Active
|
||||
};
|
||||
}
|
||||
|
||||
if (tab.mode === "mongo-bucket") {
|
||||
return {
|
||||
type: "mongo-gridfs",
|
||||
connectionId: tab.connectionId,
|
||||
database: tab.database,
|
||||
};
|
||||
}
|
||||
|
||||
if (tab.mode === "mongo-gridfs") {
|
||||
return {
|
||||
type: "mongo-gridfs",
|
||||
connectionId: tab.connectionId,
|
||||
database: tab.database,
|
||||
};
|
||||
}
|
||||
|
||||
if (tab.mode === "vector") {
|
||||
const collectionName = tab.sql || tab.title;
|
||||
if (!collectionName) return null;
|
||||
@@ -131,6 +152,10 @@ export function matchesTarget(node: TreeNode, target: ActiveTabSidebarTarget): b
|
||||
return node.type === "mongo-collection" && node.connectionId === target.connectionId && node.database === target.database && node.label === target.collectionName;
|
||||
}
|
||||
|
||||
if (target.type === "mongo-gridfs") {
|
||||
return node.type === "mongo-gridfs" && node.connectionId === target.connectionId && node.database === target.database;
|
||||
}
|
||||
|
||||
if (target.type === "vector-collection") {
|
||||
return node.type === "vector-collection" && node.connectionId === target.connectionId && node.label === target.collectionName;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,12 @@ export function sortSidebarTreeChildrenForParent(parent: Pick<TreeNode, "type">,
|
||||
const normalized = children.map((child) => sortRecursive(child, databaseType));
|
||||
|
||||
if (parent.type === "mongo-db") {
|
||||
const gridFsNodes = normalized.filter((child) => child.type === "mongo-gridfs");
|
||||
const collections = normalized.filter((child) => child.type !== "mongo-gridfs");
|
||||
return [...gridFsNodes, ...sortByLabel(collections)];
|
||||
}
|
||||
|
||||
if (parent.type === "mongo-buckets") {
|
||||
return sortByLabel(normalized);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,28 @@
|
||||
import type { TreeNodeType } from "@/types/database";
|
||||
|
||||
const leafTypes: Set<TreeNodeType> = new Set(["column", "index", "fkey", "trigger", "procedure", "function", "package", "package-body", "object-browser", "redis-db", "mq-tenant", "zookeeper-root", "vector-collection", "elasticsearch-index", "user-admin", "saved-sql-file", "load-more"]);
|
||||
const leafTypes: Set<TreeNodeType> = new Set([
|
||||
"column",
|
||||
"index",
|
||||
"fkey",
|
||||
"trigger",
|
||||
"procedure",
|
||||
"function",
|
||||
"package",
|
||||
"package-body",
|
||||
"object-browser",
|
||||
"redis-db",
|
||||
"mq-tenant",
|
||||
"zookeeper-root",
|
||||
"mongo-gridfs",
|
||||
"mongo-bucket",
|
||||
"vector-collection",
|
||||
"elasticsearch-index",
|
||||
"user-admin",
|
||||
"saved-sql-file",
|
||||
"load-more",
|
||||
]);
|
||||
|
||||
const fullWidthLabelTypes: Set<TreeNodeType> = new Set(["table", "view", "materialized_view", "mongo-collection", "vector-collection", "elasticsearch-index"]);
|
||||
const fullWidthLabelTypes: Set<TreeNodeType> = new Set(["table", "view", "materialized_view", "mongo-collection", "mongo-bucket", "vector-collection", "elasticsearch-index"]);
|
||||
|
||||
const emptyContainerTypes: Set<TreeNodeType> = new Set(["saved-sql-root", "saved-sql-folder"]);
|
||||
|
||||
@@ -18,6 +38,8 @@ const pinnableTypes: Set<TreeNodeType> = new Set([
|
||||
"materialized_view",
|
||||
"redis-db",
|
||||
"mongo-db",
|
||||
"mongo-gridfs",
|
||||
"mongo-bucket",
|
||||
"mongo-collection",
|
||||
"vector-collection",
|
||||
"elasticsearch-index",
|
||||
|
||||
@@ -70,6 +70,15 @@ export function tabDisplayTitle(tab: QueryTab, t: Translate): string {
|
||||
if (compact) return tab.sql;
|
||||
return `${tab.sql}@${database}`;
|
||||
}
|
||||
if (tab.mode === "mongo-gridfs") {
|
||||
if (compact) return t("tabs.gridfs");
|
||||
return `${t("tabs.gridfs")}@${database}`;
|
||||
}
|
||||
if (tab.mode === "mongo-bucket") {
|
||||
const bucketName = tab.mongoBucket?.bucketName || tab.sql || tab.title.split(".").pop() || tab.title;
|
||||
if (compact) return bucketName;
|
||||
return `${bucketName}@${database}`;
|
||||
}
|
||||
if (tab.mode === "vector" && tab.sql) {
|
||||
if (compact) return tab.sql;
|
||||
return `${tab.sql}@${database}`;
|
||||
@@ -114,6 +123,12 @@ export function tabTooltipLines(tab: QueryTab, t: Translate): { label: string; v
|
||||
if (tab.mode === "mongo" && tab.sql) {
|
||||
lines.push({ label: t("tabs.tooltipCollection"), value: tab.sql });
|
||||
}
|
||||
if (tab.mode === "mongo-gridfs") {
|
||||
lines.push({ label: t("tabs.gridfs"), value: t("tabs.gridfs") });
|
||||
}
|
||||
if (tab.mode === "mongo-bucket") {
|
||||
lines.push({ label: t("tabs.gridfs"), value: tab.mongoBucket?.bucketName || tab.sql || tab.title });
|
||||
}
|
||||
if (tab.mode === "vector" && tab.sql) {
|
||||
lines.push({ label: t("tabs.tooltipCollection"), value: tab.sql });
|
||||
}
|
||||
@@ -182,6 +197,7 @@ export function tabModeLabel(tab: QueryTab, t: Translate): string {
|
||||
if (tab.mode === "data") return t("tabs.table");
|
||||
if (tab.mode === "query") return t("tabs.sql");
|
||||
if (tab.mode === "mongo") return t("tabs.mongo");
|
||||
if (tab.mode === "mongo-gridfs" || tab.mode === "mongo-bucket") return t("tabs.gridfs");
|
||||
if (tab.mode === "vector") return t("tabs.vector");
|
||||
if (tab.mode === "redis") return t("tabs.redis");
|
||||
if (tab.mode === "etcd") return t("tabs.etcd");
|
||||
|
||||
@@ -1484,6 +1484,24 @@ export interface MongoDocumentResult {
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface MongoGridFsFileInfo {
|
||||
id: string;
|
||||
filename?: string;
|
||||
length: number;
|
||||
chunkSize: number;
|
||||
uploadDate?: string;
|
||||
metadata?: any;
|
||||
md5?: string;
|
||||
contentType?: string;
|
||||
aliases?: string[];
|
||||
}
|
||||
|
||||
export interface MongoGridFsBucketInfo {
|
||||
name: string;
|
||||
fileCount: number;
|
||||
totalBytes: number;
|
||||
}
|
||||
|
||||
export async function documentListDatabases(connectionId: string): Promise<string[]> {
|
||||
return invoke("document_list_databases", { connectionId });
|
||||
}
|
||||
@@ -1533,6 +1551,42 @@ export async function documentFindDocuments(connectionId: string, database: stri
|
||||
return invoke("document_find_documents", { connectionId, database, collection, skip, limit, filter, projection, sort, executionId });
|
||||
}
|
||||
|
||||
export async function documentListGridFsFiles(connectionId: string, database: string, bucket: string): Promise<MongoGridFsFileInfo[]> {
|
||||
return invoke("document_list_gridfs_files", { connectionId, database, bucket });
|
||||
}
|
||||
|
||||
export async function documentListGridFsBuckets(connectionId: string, database: string): Promise<MongoGridFsBucketInfo[]> {
|
||||
return invoke("document_list_gridfs_buckets", { connectionId, database });
|
||||
}
|
||||
|
||||
export async function documentCreateGridFsBucket(connectionId: string, database: string, bucket: string): Promise<void> {
|
||||
return invoke("document_create_gridfs_bucket", { connectionId, database, bucket });
|
||||
}
|
||||
|
||||
export async function documentDeleteGridFsBucket(connectionId: string, database: string, bucket: string): Promise<void> {
|
||||
return invoke("document_delete_gridfs_bucket", { connectionId, database, bucket });
|
||||
}
|
||||
|
||||
export async function documentDownloadGridFsFile(connectionId: string, database: string, bucket: string, fileId: string): Promise<Uint8Array> {
|
||||
const data = await invoke<number[]>("document_download_gridfs_file", { connectionId, database, bucket, fileId });
|
||||
return new Uint8Array(data);
|
||||
}
|
||||
|
||||
export async function documentUploadGridFsFile(connectionId: string, database: string, bucket: string, fileName: string, data: Uint8Array, contentType?: string): Promise<string> {
|
||||
return invoke("document_upload_gridfs_file", {
|
||||
connectionId,
|
||||
database,
|
||||
bucket,
|
||||
fileName,
|
||||
data: Array.from(data),
|
||||
contentType,
|
||||
});
|
||||
}
|
||||
|
||||
export async function documentDeleteGridFsFile(connectionId: string, database: string, bucket: string, fileId: string): Promise<void> {
|
||||
return invoke("document_delete_gridfs_file", { connectionId, database, bucket, fileId });
|
||||
}
|
||||
|
||||
export async function mongoServerVersion(connectionId: string, database: string, executionId?: string): Promise<string> {
|
||||
return invoke("mongo_server_version", { connectionId, database, executionId });
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ export type SidebarSelectionCopyAction = "copy-name" | "none";
|
||||
export type SidebarActivation = "single" | "double";
|
||||
|
||||
const dataNodeTypes = new Set<TreeNodeType>(["table", "view", "materialized_view"]);
|
||||
const toggleLeafNodeTypes = new Set<TreeNodeType>(["redis-db", "mq-tenant", "etcd-root", "zookeeper-root", "mongo-collection", "vector-collection", "elasticsearch-index", "user-admin"]);
|
||||
const documentBrowserNodeTypes = new Set<TreeNodeType>(["mongo-collection", "mongo-bucket"]);
|
||||
const toggleLeafNodeTypes = new Set<TreeNodeType>(["redis-db", "mq-tenant", "etcd-root", "zookeeper-root", "mongo-gridfs", "mongo-collection", "mongo-bucket", "vector-collection", "elasticsearch-index", "user-admin"]);
|
||||
const objectBrowserNodeTypes = new Set<TreeNodeType>(["database", "schema", "object-browser"]);
|
||||
const sourceNodeTypes = new Set<TreeNodeType>(["materialized_view", "procedure", "function", "sequence", "package", "package-body"]);
|
||||
const savedSqlNodeTypes = new Set<TreeNodeType>(["saved-sql-file"]);
|
||||
@@ -25,6 +26,10 @@ export function objectSourceKindForTreeNode(type: TreeNodeType): ObjectSourceKin
|
||||
return null;
|
||||
}
|
||||
|
||||
export function isDocumentBrowserTreeNode(type: TreeNodeType): boolean {
|
||||
return documentBrowserNodeTypes.has(type);
|
||||
}
|
||||
|
||||
export function treeNodeRowAction(type: TreeNodeType, canExpand: boolean, activation: SidebarActivation = "single"): TreeNodeRowAction {
|
||||
if (activation === "double") return "none";
|
||||
if (dataNodeTypes.has(type)) return "open-data";
|
||||
|
||||
Reference in new issue
Block a user