diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 90323f6205..b9f57e8175 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -22abd6d4e229f1206a13ebee8a6a9b808cd82a0d +f78df23339e3136e8b6e9148a509508633448dd2 diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index bc6d731756..5d89c8ae04 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -20889,6 +20889,11 @@ paths: in: query schema: type: string + - name: s3_resource_path + in: query + description: When set, test the connection of this object storage resource instead of the workspace storage + schema: + type: string responses: "200": description: Connection settings @@ -20970,6 +20975,11 @@ paths: in: query schema: type: string + - name: s3_resource_path + in: query + description: When set, list the files of this object storage resource instead of the workspace storage + schema: + type: string responses: "200": description: List of file keys @@ -21006,6 +21016,11 @@ paths: in: query schema: type: string + - name: s3_resource_path + in: query + description: When set, load the file metadata from this object storage resource instead of the workspace storage + schema: + type: string responses: "200": description: FileMetadata @@ -21055,6 +21070,11 @@ paths: in: query schema: type: string + - name: s3_resource_path + in: query + description: When set, load the file preview from this object storage resource instead of the workspace storage + schema: + type: string responses: "200": description: FilePreview @@ -21365,6 +21385,11 @@ paths: in: query schema: type: string + - name: s3_resource_path + in: query + description: When set, delete the file from this object storage resource instead of the workspace storage + schema: + type: string responses: "200": description: Confirmation @@ -21394,6 +21419,11 @@ paths: in: query schema: type: string + - name: s3_resource_path + in: query + description: When set, move the file within this object storage resource instead of the workspace storage + schema: + type: string responses: "200": description: Confirmation diff --git a/backend/windmill-api/src/apps.rs b/backend/windmill-api/src/apps.rs index e8ac1340ca..4a6d5134d2 100644 --- a/backend/windmill-api/src/apps.rs +++ b/backend/windmill-api/src/apps.rs @@ -4191,15 +4191,19 @@ async fn app_load_file_metadata( OptAuthed(opt_authed): OptAuthed, Extension(db): Extension, Path((w_id, path)): Path<(String, StripPath)>, - Query(query): Query, + Query(mut query): Query, Query(sig): Query, ) -> Result { let path = path.to_path(); let file_query = app_s3_file_query(query.file_key.clone(), query.storage.clone(), sig); let job_authed = app_s3_on_behalf_and_provenance(&db, &path, &w_id, &opt_authed, &file_query).await?; + // On-behalf app reads are confined to the workspace storage; a + // viewer-supplied custom resource must not be honored. + query.s3_resource_path = None; let resp = - crate::job_helpers_oss::load_file_metadata_internal(job_authed, &db, &w_id, query).await?; + crate::job_helpers_oss::load_file_metadata_internal(job_authed, &db, None, &w_id, query) + .await?; Ok(Json(resp).into_response()) } @@ -4208,15 +4212,19 @@ async fn app_load_file_preview( OptAuthed(opt_authed): OptAuthed, Extension(db): Extension, Path((w_id, path)): Path<(String, StripPath)>, - Query(query): Query, + Query(mut query): Query, Query(sig): Query, ) -> Result { let path = path.to_path(); let file_query = app_s3_file_query(query.file_key.clone(), query.storage.clone(), sig); let job_authed = app_s3_on_behalf_and_provenance(&db, &path, &w_id, &opt_authed, &file_query).await?; + // On-behalf app reads are confined to the workspace storage; a + // viewer-supplied custom resource must not be honored. + query.s3_resource_path = None; let resp = - crate::job_helpers_oss::load_file_preview_internal(job_authed, &db, &w_id, query).await?; + crate::job_helpers_oss::load_file_preview_internal(job_authed, &db, None, &w_id, query) + .await?; Ok(Json(resp).into_response()) } diff --git a/backend/windmill-api/src/job_helpers_oss.rs b/backend/windmill-api/src/job_helpers_oss.rs index 8a50f2e481..6cb7a4c769 100644 --- a/backend/windmill-api/src/job_helpers_oss.rs +++ b/backend/windmill-api/src/job_helpers_oss.rs @@ -200,7 +200,7 @@ pub async fn read_object_streamable( pub async fn delete_s3_file_internal( _authed: OptJobAuthed, _db: &DB, - _token: &str, + _user_db: Option, _w_id: &str, _query: DeleteS3FileQuery, ) -> error::Result<()> { @@ -215,6 +215,7 @@ pub async fn delete_s3_file_internal( pub struct DeleteS3FileQuery { pub file_key: String, pub storage: Option, + pub s3_resource_path: Option, } // Stubs for the app-scoped S3 display ops (mirrors the EE `*_internal` helpers + @@ -231,6 +232,7 @@ mod app_s3_display_stubs { pub struct LoadFileMetadataQuery { pub file_key: String, pub storage: Option, + pub s3_resource_path: Option, } #[derive(Serialize)] @@ -242,6 +244,7 @@ mod app_s3_display_stubs { #[allow(dead_code)] pub struct LoadFilePreviewQuery { pub storage: Option, + pub s3_resource_path: Option, pub file_key: String, pub file_size_in_bytes: Option, pub file_mime_type: Option, @@ -281,6 +284,7 @@ mod app_s3_display_stubs { pub async fn load_file_metadata_internal( _authed: OptJobAuthed, _db: &DB, + _user_db: Option, _w_id: &str, _query: LoadFileMetadataQuery, ) -> error::Result { @@ -292,6 +296,7 @@ mod app_s3_display_stubs { pub async fn load_file_preview_internal( _authed: OptJobAuthed, _db: &DB, + _user_db: Option, _w_id: &str, _query: LoadFilePreviewQuery, ) -> error::Result { diff --git a/frontend/src/lib/components/ExploreAssetButton.svelte b/frontend/src/lib/components/ExploreAssetButton.svelte index 45d378fd86..85ee7f0b98 100644 --- a/frontend/src/lib/components/ExploreAssetButton.svelte +++ b/frontend/src/lib/components/ExploreAssetButton.svelte @@ -1,4 +1,16 @@ @@ -19,7 +33,12 @@ import { Button, ButtonType } from '$lib/components/common' import S3FilePicker from '$lib/components/S3FilePicker.svelte' import { VolumeService } from '$lib/gen' - import { globalDbManagerDrawer, userStore, workspaceStore } from '$lib/stores' + import { + globalDbManagerDrawer, + globalS3FilePickerExplorer, + userStore, + workspaceStore + } from '$lib/stores' import { isS3Uri } from '$lib/utils' import { Database, File, HardDriveIcon } from 'lucide-svelte' import DucklakeIcon from './icons/DucklakeIcon.svelte' @@ -52,6 +71,12 @@ let dbManagerDrawer = $derived(globalDbManagerDrawer.val) let ws = $derived(workspace ?? $workspaceStore) const assetUri = $derived(formatAsset(asset)) + // Contexts with a select/upload flow pass their own picker; everything else + // (e.g. the resources list) falls back to the global read-only explorer. + let effectiveS3FilePicker = $derived(s3FilePicker ?? globalS3FilePickerExplorer.val) + let isStorageResource = $derived( + asset.kind === 'resource' && isObjectStorageResourceType(_resourceMetadata?.resource_type) + )