diff --git a/package.json b/package.json index 18136d8..f9b396f 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "LICENSE" ], "peerDependencies": { - "@govoplan/core-webui": "^0.1.8", + "@govoplan/core-webui": "^0.1.9", "lucide-react": "^1.23.0", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/webui/package.json b/webui/package.json index 533f5ea..a79bc53 100644 --- a/webui/package.json +++ b/webui/package.json @@ -24,7 +24,7 @@ "react-dom": "^19.0.0", "react-router-dom": "^7.1.1", "lucide-react": "^1.23.0", - "@govoplan/core-webui": "^0.1.8" + "@govoplan/core-webui": "^0.1.9" }, "peerDependenciesMeta": { "@govoplan/core-webui": { diff --git a/webui/src/api/files.ts b/webui/src/api/files.ts index e5d9259..ea2b2c4 100644 --- a/webui/src/api/files.ts +++ b/webui/src/api/files.ts @@ -1,4 +1,10 @@ import { ApiError, apiFetch, apiUrl, authHeaders, csrfToken, type ApiSettings, type DeltaDeletedItem, type FilesManagedFileLinkTarget } from "@govoplan/core-webui"; +export { fetchResourceAccessExplanation } from "@govoplan/core-webui"; +export type { + AccessDecisionProvenanceItem, + ResourceAccessExplanationUser as AccessExplanationUser, + ResourceAccessExplanationResponse +} from "@govoplan/core-webui"; export type FileSpace = { id: string; @@ -559,44 +565,6 @@ export function bulkDeleteFiles(settings: ApiSettings, fileIds: string[]): Promi export type FileBulkShareResponse = {shares: FileShare[];shared_count: number;}; -export type AccessExplanationUser = { - id: string; - account_id?: string | null; - email?: string | null; - display_name?: string | null; -}; - -export type AccessDecisionProvenanceItem = { - kind: string; - id?: string | null; - label?: string | null; - tenant_id?: string | null; - source?: string | null; - details?: Record; -}; - -export type ResourceAccessExplanationResponse = { - user: AccessExplanationUser; - resource_type: string; - resource_id: string; - action: string; - provenance: AccessDecisionProvenanceItem[]; -}; - -export function fetchResourceAccessExplanation( -settings: ApiSettings, -options: {userId: string;resourceType: string;resourceId: string;action: string;tenantId?: string | null;}) -: Promise { - const params = new URLSearchParams({ - user_id: options.userId, - resource_type: options.resourceType, - resource_id: options.resourceId, - action: options.action - }); - if (options.tenantId) params.set("tenant_id", options.tenantId); - return apiFetch(settings, `/api/v1/admin/access/resource-explanation?${params.toString()}`); -} - export function virtualFolderResourceId(options: {tenantId: string;ownerType: "user" | "group";ownerId: string;path: string;}): string { return `virtual-folder:v1:${options.tenantId}:${options.ownerType}:${options.ownerId}:${base64Url(options.path.replace(/\\/g, "/").replace(/^\/+|\/+$/g, ""))}`; } diff --git a/webui/src/features/files/FilesPage.tsx b/webui/src/features/files/FilesPage.tsx index 327e1d1..43f8ae2 100644 --- a/webui/src/features/files/FilesPage.tsx +++ b/webui/src/features/files/FilesPage.tsx @@ -8,6 +8,7 @@ import { FileDropZone, FormField, LoadingIndicator, + ResourceAccessExplanation, ToggleSwitch, hasScope, type ApiSettings, @@ -2211,8 +2212,8 @@ export default function FilesPage({ settings, auth }: {settings: ApiSettings;aut } {accessExplanationTarget && - {if (!resourceAccessLoading) {setAccessExplanationTarget(null);setResourceAccessExplanation(null);}}}> - {if (!resourceAccessLoading) {setAccessExplanationTarget(null);setResourceAccessExplanation(null);}}}> + @@ -2508,71 +2509,6 @@ export default function FilesPage({ settings, auth }: {settings: ApiSettings;aut } -function ResourceAccessExplanationContent({ - loading, - explanation, - fallbackResourceLabel -}: {loading: boolean;explanation: ResourceAccessExplanationResponse | null;fallbackResourceLabel: string;}) { - if (loading) return

i18n:govoplan-files.loading_access_explanation.04a7c934

; - if (!explanation) return null; - const userLabel = explanation.user.display_name || explanation.user.email || explanation.user.id; - const resourceLabel = explanation.provenance.find((item) => item.kind === "resource")?.label || fallbackResourceLabel || explanation.resource_id; - return ( - <> -
-
i18n:govoplan-files.user.9f8a2389

{userLabel}

-
i18n:govoplan-files.resource.d1c626a9

{resourceLabel}

-
i18n:govoplan-files.action.97c89a4d

{explanation.action}

-
i18n:govoplan-files.evidence.8487d192

{explanation.provenance.length}

-
- {explanation.provenance.length === 0 ? -

i18n:govoplan-files.no_access_evidence_was_returned.84a21e4e

: -
- {explanation.provenance.map((item, index) => -
- {provenanceKindLabel(item.kind)} -
- {item.source || "i18n:govoplan-files.no_source.6dcf9723"} - {item.id && <> ยท {item.id}} -
- {item.label &&

{item.label}

} - {Object.keys(item.details ?? {}).length > 0 &&

{formatProvenanceDetails(item.details ?? {})}

} -
- )} -
- } - ); -} - -function provenanceKindLabel(kind: string): string { - switch (kind) { - case "resource": - return "i18n:govoplan-files.resource.d1c626a9"; - case "owner": - return "i18n:govoplan-files.owner.89ff3122"; - case "share": - return "i18n:govoplan-files.share.09ca55ca"; - case "policy": - return "i18n:govoplan-files.policy.0b779a05"; - case "role": - return "i18n:govoplan-files.role.b5b4a5a2"; - case "right": - return "i18n:govoplan-files.permission.2f81a22d"; - default: - return kind; - } -} - -function formatProvenanceDetails(details: Record): string { - return Object.entries(details).map(([key, value]) => `${key}: ${formatProvenanceValue(value)}`).join("; "); -} - -function formatProvenanceValue(value: unknown): string { - if (value === null || value === undefined) return "i18n:govoplan-files.none.6eef6648"; - if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return String(value); - return JSON.stringify(value); -} - function FileSearchRow({ value, caseSensitive,