From e9b83341e987bd0964c36fab92b64cccfc2aacc2 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 12:13:19 +0000 Subject: [PATCH] feat: add frontend components and OpenAPI spec for permission history - Add OpenAPI endpoints for folder and group permission history - Create reusable PermissionHistory component with Svelte 5 - Integrate history display into FolderEditor (for folder admins) - Integrate history display into GroupEditor (for workspace admins) - Fix ownership issues in folders.rs and granular_acls.rs - Frontend validation passes (npm run check) Note: SQLx query cache update required (cargo sqlx prepare) Co-authored-by: Ruben Fiszel --- backend/windmill-api/openapi.yaml | 68 +++++++++++++ backend/windmill-api/src/folders.rs | 7 +- backend/windmill-api/src/granular_acls.rs | 2 +- .../src/lib/components/FolderEditor.svelte | 16 +++ .../src/lib/components/GroupEditor.svelte | 16 +++ .../lib/components/PermissionHistory.svelte | 99 +++++++++++++++++++ 6 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 frontend/src/lib/components/PermissionHistory.svelte diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 34e607e664..58eeb240fe 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -11995,6 +11995,40 @@ paths: schema: type: string + /w/{workspace}/groups_history/get/{name}: + get: + summary: get group permission history + operationId: getGroupPermissionHistory + tags: + - group + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Name" + - $ref: "#/components/parameters/Page" + - $ref: "#/components/parameters/PerPage" + responses: + "200": + description: group permission history + content: + application/json: + schema: + type: array + items: + type: object + properties: + id: + type: integer + changed_by: + type: string + changed_at: + type: string + format: date-time + change_type: + type: string + member_affected: + type: string + nullable: true + /w/{workspace}/folders/list: get: summary: list folders @@ -12258,6 +12292,40 @@ paths: schema: type: string + /w/{workspace}/folders_history/get/{name}: + get: + summary: get folder permission history + operationId: getFolderPermissionHistory + tags: + - folder + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Name" + - $ref: "#/components/parameters/Page" + - $ref: "#/components/parameters/PerPage" + responses: + "200": + description: folder permission history + content: + application/json: + schema: + type: array + items: + type: object + properties: + id: + type: integer + changed_by: + type: string + changed_at: + type: string + format: date-time + change_type: + type: string + owner_affected: + type: string + nullable: true + /workers/list: get: summary: list workers diff --git a/backend/windmill-api/src/folders.rs b/backend/windmill-api/src/folders.rs index c72adb05c7..2afb63dec5 100644 --- a/backend/windmill-api/src/folders.rs +++ b/backend/windmill-api/src/folders.rs @@ -329,6 +329,10 @@ async fn update_folder( sqlb.set("edited_at", "now()"); + // Track whether permission-related fields are being updated + let owners_changed = ng.owners.is_some(); + let extra_perms_changed = ng.extra_perms.is_some(); + if !authed.is_admin { let prefixed_username = format!("u/{}", authed.username); if ng.owners.as_ref().is_some_and(|x| { @@ -417,7 +421,8 @@ async fn update_folder( .await?; // Log permission changes if owners or extra_perms were updated - if ng.owners.is_some() || ng.extra_perms.is_some() { + let should_log = owners_changed || extra_perms_changed; + if should_log { log_folder_permission_change( &mut *tx, &w_id, diff --git a/backend/windmill-api/src/granular_acls.rs b/backend/windmill-api/src/granular_acls.rs index 6dd05762a0..27c3e704e2 100644 --- a/backend/windmill-api/src/granular_acls.rs +++ b/backend/windmill-api/src/granular_acls.rs @@ -116,7 +116,7 @@ async fn add_granular_acl( "UPDATE {kind} SET extra_perms = jsonb_set(extra_perms, $1, to_jsonb($2), \ true) WHERE {identifier} = $3 AND workspace_id = $4 RETURNING extra_perms" )) - .bind(vec![owner]) + .bind(vec![owner.clone()]) .bind(write.unwrap_or(false)) .bind(path) .bind(&w_id) diff --git a/frontend/src/lib/components/FolderEditor.svelte b/frontend/src/lib/components/FolderEditor.svelte index a2023240cf..cc1414682d 100644 --- a/frontend/src/lib/components/FolderEditor.svelte +++ b/frontend/src/lib/components/FolderEditor.svelte @@ -20,6 +20,7 @@ import Select from './select/Select.svelte' import { safeSelectItems } from './select/utils.svelte' import TextInput from './text_input/TextInput.svelte' + import PermissionHistory from './PermissionHistory.svelte' interface Props { name: string @@ -441,4 +442,19 @@ {/if} + + {#if can_write} + { + return await FolderService.getFolderPermissionHistory({ + workspace, + name: folderName, + page, + perPage + }) + }} + /> + {/if} diff --git a/frontend/src/lib/components/GroupEditor.svelte b/frontend/src/lib/components/GroupEditor.svelte index bdea0c4533..1f5f7de254 100644 --- a/frontend/src/lib/components/GroupEditor.svelte +++ b/frontend/src/lib/components/GroupEditor.svelte @@ -20,6 +20,7 @@ import { safeSelectItems } from './select/utils.svelte' import TextInput from './text_input/TextInput.svelte' import { Trash } from 'lucide-svelte' + import PermissionHistory from './PermissionHistory.svelte' interface Props { name: string @@ -311,4 +312,19 @@ {/if} + + {#if $userStore?.is_admin} + { + return await GroupService.getGroupPermissionHistory({ + workspace, + name: groupName, + page, + perPage + }) + }} + /> + {/if} diff --git a/frontend/src/lib/components/PermissionHistory.svelte b/frontend/src/lib/components/PermissionHistory.svelte new file mode 100644 index 0000000000..c7cd811eee --- /dev/null +++ b/frontend/src/lib/components/PermissionHistory.svelte @@ -0,0 +1,99 @@ + + +