feat(webui): centralize resource access explanations

This commit is contained in:
2026-07-21 13:18:30 +02:00
parent 9dabd9356d
commit 6526f37aae
8 changed files with 257 additions and 1 deletions

View File

@@ -26,7 +26,8 @@
"test:dialog-focus": "rm -rf .component-test-build && mkdir -p .component-test-build && printf '{\"type\":\"commonjs\"}\\n' > .component-test-build/package.json && tsc -p tsconfig.component-tests.json && node .component-test-build/tests/dialog-focus.test.js && node scripts/test-dialog-focus-structure.mjs", "test:dialog-focus": "rm -rf .component-test-build && mkdir -p .component-test-build && printf '{\"type\":\"commonjs\"}\\n' > .component-test-build/package.json && tsc -p tsconfig.component-tests.json && node .component-test-build/tests/dialog-focus.test.js && node scripts/test-dialog-focus-structure.mjs",
"test:module-capabilities": "rm -rf .module-test-build && mkdir -p .module-test-build && printf '{\"type\":\"commonjs\"}\n' > .module-test-build/package.json && tsc -p tsconfig.module-tests.json && node .module-test-build/tests/module-capabilities.test.js && node .module-test-build/tests/privacy-policy.test.js", "test:module-capabilities": "rm -rf .module-test-build && mkdir -p .module-test-build && printf '{\"type\":\"commonjs\"}\n' > .module-test-build/package.json && tsc -p tsconfig.module-tests.json && node .module-test-build/tests/module-capabilities.test.js && node .module-test-build/tests/privacy-policy.test.js",
"test:module-permutations": "node scripts/test-module-permutations.mjs", "test:module-permutations": "node scripts/test-module-permutations.mjs",
"test:mail-components": "rm -rf .component-test-build && mkdir -p .component-test-build && printf '{\"type\":\"commonjs\"}\\n' > .component-test-build/package.json && tsc -p tsconfig.component-tests.json && node .component-test-build/tests/mail-components.test.js" "test:mail-components": "rm -rf .component-test-build && mkdir -p .component-test-build && printf '{\"type\":\"commonjs\"}\\n' > .component-test-build/package.json && tsc -p tsconfig.component-tests.json && node .component-test-build/tests/mail-components.test.js",
"test:resource-access": "rm -rf .component-test-build && mkdir -p .component-test-build && printf '{\"type\":\"commonjs\"}\\n' > .component-test-build/package.json && tsc -p tsconfig.component-tests.json && node .component-test-build/tests/resource-access-explanation.test.js"
}, },
"dependencies": { "dependencies": {
"@govoplan/access-webui": "file:../../govoplan-access/webui", "@govoplan/access-webui": "file:../../govoplan-access/webui",

View File

@@ -0,0 +1,35 @@
import type { ApiSettings } from "../types";
import { apiFetch, apiPath } from "./client";
import type {
AccessDecisionProvenanceItem,
ResourceAccessExplanationOptions,
ResourceAccessExplanationResponse,
ResourceAccessExplanationUser
} from "./resourceAccessContracts";
export type {
AccessDecisionProvenanceItem,
ResourceAccessExplanationOptions,
ResourceAccessExplanationResponse,
ResourceAccessExplanationUser
} from "./resourceAccessContracts";
/**
* Fetches the platform's optional resource-access explanation endpoint.
* Core owns the transport contract without requiring the Access module that
* contributes the endpoint at runtime.
*/
export function fetchResourceAccessExplanation<
TUser extends ResourceAccessExplanationUser = ResourceAccessExplanationUser,
TProvenance extends AccessDecisionProvenanceItem = AccessDecisionProvenanceItem
>(
settings: ApiSettings,
options: ResourceAccessExplanationOptions
): Promise<ResourceAccessExplanationResponse<TUser, TProvenance>> {
return apiFetch(settings, apiPath("/api/v1/admin/access/resource-explanation", {
user_id: options.userId,
resource_type: options.resourceType,
resource_id: options.resourceId,
action: options.action,
tenant_id: options.tenantId
}));
}

View File

@@ -0,0 +1,34 @@
export type ResourceAccessExplanationUser = {
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<string, unknown>;
};
export type ResourceAccessExplanationResponse<
TUser extends ResourceAccessExplanationUser = ResourceAccessExplanationUser,
TProvenance extends AccessDecisionProvenanceItem = AccessDecisionProvenanceItem
> = {
user: TUser;
resource_type: string;
resource_id: string;
action: string;
provenance: TProvenance[];
};
export type ResourceAccessExplanationOptions = {
userId: string;
resourceType: string;
resourceId: string;
action: string;
tenantId?: string | null;
};

View File

@@ -0,0 +1,84 @@
import type { ResourceAccessExplanationResponse } from "../api/resourceAccessContracts";
export type ResourceAccessExplanationProps = {
loading?: boolean;
explanation?: ResourceAccessExplanationResponse | null;
fallbackResourceLabel?: string;
};
export default function ResourceAccessExplanation({
loading = false,
explanation,
fallbackResourceLabel = ""
}: ResourceAccessExplanationProps) {
if (loading) {
return <p className="muted small-note">i18n:govoplan-core.loading_access_explanation.04a7c934</p>;
}
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 (
<>
<div className="form-grid compact responsive-form-grid">
<div><span className="form-label">i18n:govoplan-core.user.9f8a2389</span><p>{userLabel}</p></div>
<div><span className="form-label">i18n:govoplan-core.resource.d1c626a9</span><p>{resourceLabel}</p></div>
<div><span className="form-label">i18n:govoplan-core.action.97c89a4d</span><p><code>{explanation.action}</code></p></div>
<div><span className="form-label">i18n:govoplan-core.evidence.8487d192</span><p>{explanation.provenance.length}</p></div>
</div>
{explanation.provenance.length === 0 ? (
<p className="muted small-note">i18n:govoplan-core.no_access_evidence_was_returned.84a21e4e</p>
) : (
<div className="admin-assignment-grid">
{explanation.provenance.map((item, index) => (
<div key={`${item.kind}:${item.id ?? index}`}>
<strong>{resourceAccessProvenanceKindLabel(item.kind)}</strong>
<div className="muted small-note">
{item.source || "i18n:govoplan-core.no_source.6dcf9723"}
{item.id && <> · <code>{item.id}</code></>}
</div>
{item.label && <p>{item.label}</p>}
{Object.keys(item.details ?? {}).length > 0 && (
<p className="muted small-note">{formatResourceAccessProvenanceDetails(item.details ?? {})}</p>
)}
</div>
))}
</div>
)}
</>
);
}
export function resourceAccessProvenanceKindLabel(kind: string): string {
switch (kind) {
case "resource":
return "i18n:govoplan-core.resource.d1c626a9";
case "owner":
return "i18n:govoplan-core.owner.89ff3122";
case "share":
return "i18n:govoplan-core.share.09ca55ca";
case "policy":
return "i18n:govoplan-core.policy.0b779a05";
case "role":
return "i18n:govoplan-core.role.b5b4a5a2";
case "right":
return "i18n:govoplan-core.permission.2f81a22d";
default:
return kind;
}
}
export function formatResourceAccessProvenanceDetails(details: Record<string, unknown>): string {
return Object.entries(details)
.map(([key, value]) => `${key}: ${formatResourceAccessProvenanceValue(value)}`)
.join("; ");
}
function formatResourceAccessProvenanceValue(value: unknown): string {
if (value === null || value === undefined) return "i18n:govoplan-core.none.6eef6648";
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return String(value);
return JSON.stringify(value) ?? String(value);
}

View File

@@ -6,12 +6,14 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.about.6b21fb79": "About", "i18n:govoplan-core.about.6b21fb79": "About",
"i18n:govoplan-core.accent_color.e49578ed": "Accent color", "i18n:govoplan-core.accent_color.e49578ed": "Accent color",
"i18n:govoplan-core.access_is_restricted_sign_in_to_open_available_m.2a47a549": "Access is restricted. Sign in to open available modules and administration.", "i18n:govoplan-core.access_is_restricted_sign_in_to_open_available_m.2a47a549": "Access is restricted. Sign in to open available modules and administration.",
"i18n:govoplan-core.access_explanation.75ee7f62": "Access explanation",
"i18n:govoplan-core.accessible_to_you.266eca4e": "Accessible to you", "i18n:govoplan-core.accessible_to_you.266eca4e": "Accessible to you",
"i18n:govoplan-core.account_context.5bb0a918": "Account context", "i18n:govoplan-core.account_context.5bb0a918": "Account context",
"i18n:govoplan-core.account_display_name.03ed8fc5": "Account display name", "i18n:govoplan-core.account_display_name.03ed8fc5": "Account display name",
"i18n:govoplan-core.account_id.c5e0db28": "Account ID", "i18n:govoplan-core.account_id.c5e0db28": "Account ID",
"i18n:govoplan-core.account_settings.82cf8a5f": "Account settings", "i18n:govoplan-core.account_settings.82cf8a5f": "Account settings",
"i18n:govoplan-core.account.f967543b": "ACCOUNT", "i18n:govoplan-core.account.f967543b": "ACCOUNT",
"i18n:govoplan-core.action.97c89a4d": "Action",
"i18n:govoplan-core.action_or_review_severity_when_this_rule_finds_m.2e6863d1": "Action or review severity when this rule finds multiple possible matches.", "i18n:govoplan-core.action_or_review_severity_when_this_rule_finds_m.2e6863d1": "Action or review severity when this rule finds multiple possible matches.",
"i18n:govoplan-core.action_or_review_severity_when_this_rule_finds_n.bc500ee1": "Action or review severity when this rule finds no matching file.", "i18n:govoplan-core.action_or_review_severity_when_this_rule_finds_n.bc500ee1": "Action or review severity when this rule finds no matching file.",
"i18n:govoplan-core.actions.c3cd636a": "Actions", "i18n:govoplan-core.actions.c3cd636a": "Actions",
@@ -167,6 +169,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.entry_did_not_export_a_platformwebmodule.9a157741": "entry did not export a PlatformWebModule", "i18n:govoplan-core.entry_did_not_export_a_platformwebmodule.9a157741": "entry did not export a PlatformWebModule",
"i18n:govoplan-core.entry_integrity_check_failed.1d8f6b89": "entry integrity check failed", "i18n:govoplan-core.entry_integrity_check_failed.1d8f6b89": "entry integrity check failed",
"i18n:govoplan-core.equals.09b6a6dc": "Equals", "i18n:govoplan-core.equals.09b6a6dc": "Equals",
"i18n:govoplan-core.evidence.8487d192": "Evidence",
"i18n:govoplan-core.excel_spreadsheet.7107fea2": "Excel spreadsheet", "i18n:govoplan-core.excel_spreadsheet.7107fea2": "Excel spreadsheet",
"i18n:govoplan-core.expand.9869e506": "Expand", "i18n:govoplan-core.expand.9869e506": "Expand",
"i18n:govoplan-core.explicit_api_url.b117b4b8": "Explicit API URL", "i18n:govoplan-core.explicit_api_url.b117b4b8": "Explicit API URL",
@@ -253,6 +256,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.less_or_equal.2860e695": "Less or equal", "i18n:govoplan-core.less_or_equal.2860e695": "Less or equal",
"i18n:govoplan-core.less_than.1d3d412a": "Less than", "i18n:govoplan-core.less_than.1d3d412a": "Less than",
"i18n:govoplan-core.library_template.c62eb776": "Library template", "i18n:govoplan-core.library_template.c62eb776": "Library template",
"i18n:govoplan-core.loading_access_explanation.04a7c934": "Loading access explanation...",
"i18n:govoplan-core.loading_administration_data.643bd894": "Loading administration data...", "i18n:govoplan-core.loading_administration_data.643bd894": "Loading administration data...",
"i18n:govoplan-core.loading_campaigns.43ea3afa": "Loading campaigns...", "i18n:govoplan-core.loading_campaigns.43ea3afa": "Loading campaigns...",
"i18n:govoplan-core.loading_data.089f19c5": "Loading data…", "i18n:govoplan-core.loading_data.089f19c5": "Loading data…",
@@ -314,6 +318,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.next_month.8abf7cf1": "Next month", "i18n:govoplan-core.next_month.8abf7cf1": "Next month",
"i18n:govoplan-core.next_page.4bfc194b": "Next page", "i18n:govoplan-core.next_page.4bfc194b": "Next page",
"i18n:govoplan-core.next_passes_will_add_functionality_here.c13caade": "Next passes will add functionality here.", "i18n:govoplan-core.next_passes_will_add_functionality_here.c13caade": "Next passes will add functionality here.",
"i18n:govoplan-core.no_access_evidence_was_returned.84a21e4e": "No access evidence was returned.",
"i18n:govoplan-core.no_accessible_campaigns_found.0b74419a": "No accessible campaigns found.", "i18n:govoplan-core.no_accessible_campaigns_found.0b74419a": "No accessible campaigns found.",
"i18n:govoplan-core.no_address_added_yet.809c4247": "No address added yet.", "i18n:govoplan-core.no_address_added_yet.809c4247": "No address added yet.",
"i18n:govoplan-core.no_entries_configured.48e7b97c": "No entries configured.", "i18n:govoplan-core.no_entries_configured.48e7b97c": "No entries configured.",
@@ -323,6 +328,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.no_value_available": "No {value0} available.", "i18n:govoplan-core.no_value_available": "No {value0} available.",
"i18n:govoplan-core.no_readable_body_content.37643a01": "No readable body content.", "i18n:govoplan-core.no_readable_body_content.37643a01": "No readable body content.",
"i18n:govoplan-core.no_rows_found.0c1a8d34": "No rows found.", "i18n:govoplan-core.no_rows_found.0c1a8d34": "No rows found.",
"i18n:govoplan-core.no_source.6dcf9723": "No source",
"i18n:govoplan-core.no_subject.49b20da0": "(no subject)", "i18n:govoplan-core.no_subject.49b20da0": "(no subject)",
"i18n:govoplan-core.no_values_are_configured_for_this_column.16e935e8": "No values are configured for this column.", "i18n:govoplan-core.no_values_are_configured_for_this_column.16e935e8": "No values are configured for this column.",
"i18n:govoplan-core.none.6eef6648": "None", "i18n:govoplan-core.none.6eef6648": "None",
@@ -343,6 +349,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.optional_html_version_of_the_email_body_for_rich.3f01db97": "Optional HTML version of the email body for richer formatting.", "i18n:govoplan-core.optional_html_version_of_the_email_body_for_rich.3f01db97": "Optional HTML version of the email body for richer formatting.",
"i18n:govoplan-core.optional_local_alias_for_the_active_tenant_it_do.edbb7b14": "Optional local alias for the active tenant. It does not replace the global account name in the title bar.", "i18n:govoplan-core.optional_local_alias_for_the_active_tenant_it_do.edbb7b14": "Optional local alias for the active tenant. It does not replace the global account name in the title bar.",
"i18n:govoplan-core.or_click_to_select_files.91b05dc1": "or click to select files", "i18n:govoplan-core.or_click_to_select_files.91b05dc1": "or click to select files",
"i18n:govoplan-core.owner.89ff3122": "Owner",
"i18n:govoplan-core.owner_policy.1e8df143": "Owner policy", "i18n:govoplan-core.owner_policy.1e8df143": "Owner policy",
"i18n:govoplan-core.p_no_html_body_content_p.c305bfc6": "<p>No HTML body content.</p>", "i18n:govoplan-core.p_no_html_body_content_p.c305bfc6": "<p>No HTML body content.</p>",
"i18n:govoplan-core.page.fb06270f": "Page", "i18n:govoplan-core.page.fb06270f": "Page",
@@ -351,6 +358,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.password.8be3c943": "Password", "i18n:govoplan-core.password.8be3c943": "Password",
"i18n:govoplan-core.path_or_identifier_for_an_external_recipient_sou.f7da6e8f": "Path or identifier for an external recipient source such as a CSV file.", "i18n:govoplan-core.path_or_identifier_for_an_external_recipient_sou.f7da6e8f": "Path or identifier for an external recipient source such as a CSV file.",
"i18n:govoplan-core.pdf.d613d88c": "PDF", "i18n:govoplan-core.pdf.d613d88c": "PDF",
"i18n:govoplan-core.permission.2f81a22d": "Permission",
"i18n:govoplan-core.permit_per_recipient_sender_overrides_when_build.62e46d13": "Permit per-recipient sender overrides when building messages.", "i18n:govoplan-core.permit_per_recipient_sender_overrides_when_build.62e46d13": "Permit per-recipient sender overrides when building messages.",
"i18n:govoplan-core.permit_recipient_rows_to_define_their_own_bcc_re.46d737dc": "Permit recipient rows to define their own BCC recipients.", "i18n:govoplan-core.permit_recipient_rows_to_define_their_own_bcc_re.46d737dc": "Permit recipient rows to define their own BCC recipients.",
"i18n:govoplan-core.permit_recipient_rows_to_define_their_own_cc_rec.0247b3cd": "Permit recipient rows to define their own CC recipients.", "i18n:govoplan-core.permit_recipient_rows_to_define_their_own_cc_rec.0247b3cd": "Permit recipient rows to define their own CC recipients.",
@@ -366,6 +374,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.please_wait_while_the_local_session_is_verified.1dbd8fd3": "Please wait while the local session is verified.", "i18n:govoplan-core.please_wait_while_the_local_session_is_verified.1dbd8fd3": "Please wait while the local session is verified.",
"i18n:govoplan-core.png_image.6715d4b8": "PNG image", "i18n:govoplan-core.png_image.6715d4b8": "PNG image",
"i18n:govoplan-core.preferences_saved.c8cd3501": "Preferences saved.", "i18n:govoplan-core.preferences_saved.c8cd3501": "Preferences saved.",
"i18n:govoplan-core.policy.0b779a05": "Policy",
"i18n:govoplan-core.policies.8d611849": "Policies", "i18n:govoplan-core.policies.8d611849": "Policies",
"i18n:govoplan-core.port.fe035157": "Port", "i18n:govoplan-core.port.fe035157": "Port",
"i18n:govoplan-core.powerpoint_presentation.b32b7115": "PowerPoint presentation", "i18n:govoplan-core.powerpoint_presentation.b32b7115": "PowerPoint presentation",
@@ -403,10 +412,12 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.request_failed.9fcda32c": "Request failed", "i18n:govoplan-core.request_failed.9fcda32c": "Request failed",
"i18n:govoplan-core.required.eed6bfb4": "Required", "i18n:govoplan-core.required.eed6bfb4": "Required",
"i18n:govoplan-core.resize.f52dc753": "Resize", "i18n:govoplan-core.resize.f52dc753": "Resize",
"i18n:govoplan-core.resource.d1c626a9": "Resource",
"i18n:govoplan-core.retention_policy_saved.eb577758": "Retention policy saved.", "i18n:govoplan-core.retention_policy_saved.eb577758": "Retention policy saved.",
"i18n:govoplan-core.retention_policy.962fb418": "Retention policy", "i18n:govoplan-core.retention_policy.962fb418": "Retention policy",
"i18n:govoplan-core.reusable_template_record_this_campaign_should_re.5896529b": "Reusable template record this campaign should refer to once the template backend is available.", "i18n:govoplan-core.reusable_template_record_this_campaign_should_re.5896529b": "Reusable template record this campaign should refer to once the template backend is available.",
"i18n:govoplan-core.review_send.1627617d": "Review & Send", "i18n:govoplan-core.review_send.1627617d": "Review & Send",
"i18n:govoplan-core.role.b5b4a5a2": "Role",
"i18n:govoplan-core.rows_per_page.af2f9c1b": "Rows per page", "i18n:govoplan-core.rows_per_page.af2f9c1b": "Rows per page",
"i18n:govoplan-core.sa.50cf95ce": "Sa", "i18n:govoplan-core.sa.50cf95ce": "Sa",
"i18n:govoplan-core.same_origin_proxied.c39e6e2b": "Same-origin / proxied", "i18n:govoplan-core.same_origin_proxied.c39e6e2b": "Same-origin / proxied",
@@ -428,6 +439,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.server.cb0cb170": "Server", "i18n:govoplan-core.server.cb0cb170": "Server",
"i18n:govoplan-core.session_expired.b828190e": "Session expired", "i18n:govoplan-core.session_expired.b828190e": "Session expired",
"i18n:govoplan-core.session_state.b7a3d0f4": "Session state", "i18n:govoplan-core.session_state.b7a3d0f4": "Session state",
"i18n:govoplan-core.share.09ca55ca": "Share",
"i18n:govoplan-core.set_concrete_system_retention_values_blank_day_f.98b9a627": "Set concrete system retention values. Blank day fields mean unlimited retention.", "i18n:govoplan-core.set_concrete_system_retention_values_blank_day_f.98b9a627": "Set concrete system retention values. Blank day fields mean unlimited retention.",
"i18n:govoplan-core.settings.c7f73bb5": "Settings", "i18n:govoplan-core.settings.c7f73bb5": "Settings",
"i18n:govoplan-core.show_content.0528d8d2": "Show content", "i18n:govoplan-core.show_content.0528d8d2": "Show content",
@@ -563,12 +575,14 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.about.6b21fb79": "About", "i18n:govoplan-core.about.6b21fb79": "About",
"i18n:govoplan-core.accent_color.e49578ed": "Accent color", "i18n:govoplan-core.accent_color.e49578ed": "Accent color",
"i18n:govoplan-core.access_is_restricted_sign_in_to_open_available_m.2a47a549": "Access is restricted. Sign in to open available modules and administration.", "i18n:govoplan-core.access_is_restricted_sign_in_to_open_available_m.2a47a549": "Access is restricted. Sign in to open available modules and administration.",
"i18n:govoplan-core.access_explanation.75ee7f62": "Zugriffserklärung",
"i18n:govoplan-core.accessible_to_you.266eca4e": "Accessible to you", "i18n:govoplan-core.accessible_to_you.266eca4e": "Accessible to you",
"i18n:govoplan-core.account_context.5bb0a918": "Account context", "i18n:govoplan-core.account_context.5bb0a918": "Account context",
"i18n:govoplan-core.account_display_name.03ed8fc5": "Account display name", "i18n:govoplan-core.account_display_name.03ed8fc5": "Account display name",
"i18n:govoplan-core.account_id.c5e0db28": "Account ID", "i18n:govoplan-core.account_id.c5e0db28": "Account ID",
"i18n:govoplan-core.account_settings.82cf8a5f": "Account settings", "i18n:govoplan-core.account_settings.82cf8a5f": "Account settings",
"i18n:govoplan-core.account.f967543b": "ACCOUNT", "i18n:govoplan-core.account.f967543b": "ACCOUNT",
"i18n:govoplan-core.action.97c89a4d": "Aktion",
"i18n:govoplan-core.action_or_review_severity_when_this_rule_finds_m.2e6863d1": "Action or review severity when this rule finds multiple possible matches.", "i18n:govoplan-core.action_or_review_severity_when_this_rule_finds_m.2e6863d1": "Action or review severity when this rule finds multiple possible matches.",
"i18n:govoplan-core.action_or_review_severity_when_this_rule_finds_n.bc500ee1": "Action or review severity when this rule finds no matching file.", "i18n:govoplan-core.action_or_review_severity_when_this_rule_finds_n.bc500ee1": "Action or review severity when this rule finds no matching file.",
"i18n:govoplan-core.actions.c3cd636a": "Aktionen", "i18n:govoplan-core.actions.c3cd636a": "Aktionen",
@@ -724,6 +738,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.entry_did_not_export_a_platformwebmodule.9a157741": "entry did not export a PlatformWebModule", "i18n:govoplan-core.entry_did_not_export_a_platformwebmodule.9a157741": "entry did not export a PlatformWebModule",
"i18n:govoplan-core.entry_integrity_check_failed.1d8f6b89": "entry integrity check failed", "i18n:govoplan-core.entry_integrity_check_failed.1d8f6b89": "entry integrity check failed",
"i18n:govoplan-core.equals.09b6a6dc": "Equals", "i18n:govoplan-core.equals.09b6a6dc": "Equals",
"i18n:govoplan-core.evidence.8487d192": "Nachweise",
"i18n:govoplan-core.excel_spreadsheet.7107fea2": "Excel spreadsheet", "i18n:govoplan-core.excel_spreadsheet.7107fea2": "Excel spreadsheet",
"i18n:govoplan-core.expand.9869e506": "Expand", "i18n:govoplan-core.expand.9869e506": "Expand",
"i18n:govoplan-core.explicit_api_url.b117b4b8": "Explicit API URL", "i18n:govoplan-core.explicit_api_url.b117b4b8": "Explicit API URL",
@@ -810,6 +825,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.less_or_equal.2860e695": "Less or equal", "i18n:govoplan-core.less_or_equal.2860e695": "Less or equal",
"i18n:govoplan-core.less_than.1d3d412a": "Less than", "i18n:govoplan-core.less_than.1d3d412a": "Less than",
"i18n:govoplan-core.library_template.c62eb776": "Library template", "i18n:govoplan-core.library_template.c62eb776": "Library template",
"i18n:govoplan-core.loading_access_explanation.04a7c934": "Zugriffserklärung wird geladen...",
"i18n:govoplan-core.loading_administration_data.643bd894": "Loading administration data...", "i18n:govoplan-core.loading_administration_data.643bd894": "Loading administration data...",
"i18n:govoplan-core.loading_campaigns.43ea3afa": "Loading campaigns...", "i18n:govoplan-core.loading_campaigns.43ea3afa": "Loading campaigns...",
"i18n:govoplan-core.loading_data.089f19c5": "Loading data…", "i18n:govoplan-core.loading_data.089f19c5": "Loading data…",
@@ -871,6 +887,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.next_month.8abf7cf1": "Next month", "i18n:govoplan-core.next_month.8abf7cf1": "Next month",
"i18n:govoplan-core.next_page.4bfc194b": "Next page", "i18n:govoplan-core.next_page.4bfc194b": "Next page",
"i18n:govoplan-core.next_passes_will_add_functionality_here.c13caade": "Die nächsten Durchläufe ergänzen hier die Funktionalität.", "i18n:govoplan-core.next_passes_will_add_functionality_here.c13caade": "Die nächsten Durchläufe ergänzen hier die Funktionalität.",
"i18n:govoplan-core.no_access_evidence_was_returned.84a21e4e": "Es wurden keine Zugriffsnachweise zurückgegeben.",
"i18n:govoplan-core.no_accessible_campaigns_found.0b74419a": "No accessible campaigns found.", "i18n:govoplan-core.no_accessible_campaigns_found.0b74419a": "No accessible campaigns found.",
"i18n:govoplan-core.no_address_added_yet.809c4247": "No address added yet.", "i18n:govoplan-core.no_address_added_yet.809c4247": "No address added yet.",
"i18n:govoplan-core.no_entries_configured.48e7b97c": "No entries configured.", "i18n:govoplan-core.no_entries_configured.48e7b97c": "No entries configured.",
@@ -880,6 +897,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.no_value_available": "Keine {value0} verfügbar.", "i18n:govoplan-core.no_value_available": "Keine {value0} verfügbar.",
"i18n:govoplan-core.no_readable_body_content.37643a01": "No readable body content.", "i18n:govoplan-core.no_readable_body_content.37643a01": "No readable body content.",
"i18n:govoplan-core.no_rows_found.0c1a8d34": "No rows found.", "i18n:govoplan-core.no_rows_found.0c1a8d34": "No rows found.",
"i18n:govoplan-core.no_source.6dcf9723": "Keine Quelle",
"i18n:govoplan-core.no_subject.49b20da0": "(no subject)", "i18n:govoplan-core.no_subject.49b20da0": "(no subject)",
"i18n:govoplan-core.no_values_are_configured_for_this_column.16e935e8": "No values are configured for this column.", "i18n:govoplan-core.no_values_are_configured_for_this_column.16e935e8": "No values are configured for this column.",
"i18n:govoplan-core.none.6eef6648": "Keine", "i18n:govoplan-core.none.6eef6648": "Keine",
@@ -900,6 +918,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.optional_html_version_of_the_email_body_for_rich.3f01db97": "Optional HTML version of the email body for richer formatting.", "i18n:govoplan-core.optional_html_version_of_the_email_body_for_rich.3f01db97": "Optional HTML version of the email body for richer formatting.",
"i18n:govoplan-core.optional_local_alias_for_the_active_tenant_it_do.edbb7b14": "Optional local alias for the active tenant. It does not replace the global account name in the title bar.", "i18n:govoplan-core.optional_local_alias_for_the_active_tenant_it_do.edbb7b14": "Optional local alias for the active tenant. It does not replace the global account name in the title bar.",
"i18n:govoplan-core.or_click_to_select_files.91b05dc1": "or click to select files", "i18n:govoplan-core.or_click_to_select_files.91b05dc1": "or click to select files",
"i18n:govoplan-core.owner.89ff3122": "Eigentümer",
"i18n:govoplan-core.owner_policy.1e8df143": "Owner policy", "i18n:govoplan-core.owner_policy.1e8df143": "Owner policy",
"i18n:govoplan-core.p_no_html_body_content_p.c305bfc6": "<p>No HTML body content.</p>", "i18n:govoplan-core.p_no_html_body_content_p.c305bfc6": "<p>No HTML body content.</p>",
"i18n:govoplan-core.page.fb06270f": "Page", "i18n:govoplan-core.page.fb06270f": "Page",
@@ -908,6 +927,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.password.8be3c943": "Passwort", "i18n:govoplan-core.password.8be3c943": "Passwort",
"i18n:govoplan-core.path_or_identifier_for_an_external_recipient_sou.f7da6e8f": "Path or identifier for an external recipient source such as a CSV file.", "i18n:govoplan-core.path_or_identifier_for_an_external_recipient_sou.f7da6e8f": "Path or identifier for an external recipient source such as a CSV file.",
"i18n:govoplan-core.pdf.d613d88c": "PDF", "i18n:govoplan-core.pdf.d613d88c": "PDF",
"i18n:govoplan-core.permission.2f81a22d": "Berechtigung",
"i18n:govoplan-core.permit_per_recipient_sender_overrides_when_build.62e46d13": "Permit per-recipient sender overrides when building messages.", "i18n:govoplan-core.permit_per_recipient_sender_overrides_when_build.62e46d13": "Permit per-recipient sender overrides when building messages.",
"i18n:govoplan-core.permit_recipient_rows_to_define_their_own_bcc_re.46d737dc": "Permit recipient rows to define their own BCC recipients.", "i18n:govoplan-core.permit_recipient_rows_to_define_their_own_bcc_re.46d737dc": "Permit recipient rows to define their own BCC recipients.",
"i18n:govoplan-core.permit_recipient_rows_to_define_their_own_cc_rec.0247b3cd": "Permit recipient rows to define their own CC recipients.", "i18n:govoplan-core.permit_recipient_rows_to_define_their_own_cc_rec.0247b3cd": "Permit recipient rows to define their own CC recipients.",
@@ -923,6 +943,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.please_wait_while_the_local_session_is_verified.1dbd8fd3": "Bitte warten Sie, während die lokale Sitzung geprüft wird.", "i18n:govoplan-core.please_wait_while_the_local_session_is_verified.1dbd8fd3": "Bitte warten Sie, während die lokale Sitzung geprüft wird.",
"i18n:govoplan-core.png_image.6715d4b8": "PNG image", "i18n:govoplan-core.png_image.6715d4b8": "PNG image",
"i18n:govoplan-core.preferences_saved.c8cd3501": "Einstellungen gespeichert.", "i18n:govoplan-core.preferences_saved.c8cd3501": "Einstellungen gespeichert.",
"i18n:govoplan-core.policy.0b779a05": "Richtlinie",
"i18n:govoplan-core.policies.8d611849": "Richtlinien", "i18n:govoplan-core.policies.8d611849": "Richtlinien",
"i18n:govoplan-core.port.fe035157": "Port", "i18n:govoplan-core.port.fe035157": "Port",
"i18n:govoplan-core.powerpoint_presentation.b32b7115": "PowerPoint presentation", "i18n:govoplan-core.powerpoint_presentation.b32b7115": "PowerPoint presentation",
@@ -960,10 +981,12 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.request_failed.9fcda32c": "Request failed", "i18n:govoplan-core.request_failed.9fcda32c": "Request failed",
"i18n:govoplan-core.required.eed6bfb4": "Required", "i18n:govoplan-core.required.eed6bfb4": "Required",
"i18n:govoplan-core.resize.f52dc753": "Resize", "i18n:govoplan-core.resize.f52dc753": "Resize",
"i18n:govoplan-core.resource.d1c626a9": "Ressource",
"i18n:govoplan-core.retention_policy_saved.eb577758": "Retention policy saved.", "i18n:govoplan-core.retention_policy_saved.eb577758": "Retention policy saved.",
"i18n:govoplan-core.retention_policy.962fb418": "Retention policy", "i18n:govoplan-core.retention_policy.962fb418": "Retention policy",
"i18n:govoplan-core.reusable_template_record_this_campaign_should_re.5896529b": "Reusable template record this campaign should refer to once the template backend is available.", "i18n:govoplan-core.reusable_template_record_this_campaign_should_re.5896529b": "Reusable template record this campaign should refer to once the template backend is available.",
"i18n:govoplan-core.review_send.1627617d": "Prüfen & Senden", "i18n:govoplan-core.review_send.1627617d": "Prüfen & Senden",
"i18n:govoplan-core.role.b5b4a5a2": "Rolle",
"i18n:govoplan-core.rows_per_page.af2f9c1b": "Rows per page", "i18n:govoplan-core.rows_per_page.af2f9c1b": "Rows per page",
"i18n:govoplan-core.sa.50cf95ce": "Sa", "i18n:govoplan-core.sa.50cf95ce": "Sa",
"i18n:govoplan-core.same_origin_proxied.c39e6e2b": "Same-origin / proxied", "i18n:govoplan-core.same_origin_proxied.c39e6e2b": "Same-origin / proxied",
@@ -985,6 +1008,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-core.server.cb0cb170": "Server", "i18n:govoplan-core.server.cb0cb170": "Server",
"i18n:govoplan-core.session_expired.b828190e": "Sitzung abgelaufen", "i18n:govoplan-core.session_expired.b828190e": "Sitzung abgelaufen",
"i18n:govoplan-core.session_state.b7a3d0f4": "Session state", "i18n:govoplan-core.session_state.b7a3d0f4": "Session state",
"i18n:govoplan-core.share.09ca55ca": "Freigabe",
"i18n:govoplan-core.set_concrete_system_retention_values_blank_day_f.98b9a627": "Set concrete system retention values. Blank day fields mean unlimited retention.", "i18n:govoplan-core.set_concrete_system_retention_values_blank_day_f.98b9a627": "Set concrete system retention values. Blank day fields mean unlimited retention.",
"i18n:govoplan-core.settings.c7f73bb5": "Einstellungen", "i18n:govoplan-core.settings.c7f73bb5": "Einstellungen",
"i18n:govoplan-core.show_content.0528d8d2": "Show content", "i18n:govoplan-core.show_content.0528d8d2": "Show content",

View File

@@ -24,6 +24,7 @@ export type {
MockMailboxMessageResponse MockMailboxMessageResponse
} from "./api/mailContracts"; } from "./api/mailContracts";
export * from "./api/privacyRetention"; export * from "./api/privacyRetention";
export * from "./api/resourceAccess";
export * from "./platform/modules"; export * from "./platform/modules";
export * from "./platform/ModuleContext"; export * from "./platform/ModuleContext";
export * from "./platform/moduleEvents"; export * from "./platform/moduleEvents";
@@ -80,6 +81,8 @@ export { default as MessageDisplayPanel } from "./components/MessageDisplayPanel
export type { MessageDisplayAttachment, MessageDisplayField } from "./components/MessageDisplayPanel"; export type { MessageDisplayAttachment, MessageDisplayField } from "./components/MessageDisplayPanel";
export { default as PageTitle } from "./components/PageTitle"; export { default as PageTitle } from "./components/PageTitle";
export { default as PasswordField } from "./components/PasswordField"; export { default as PasswordField } from "./components/PasswordField";
export { default as ResourceAccessExplanation, formatResourceAccessProvenanceDetails, resourceAccessProvenanceKindLabel } from "./components/ResourceAccessExplanation";
export type { ResourceAccessExplanationProps } from "./components/ResourceAccessExplanation";
export type { PasswordFieldProps } from "./components/PasswordField"; export type { PasswordFieldProps } from "./components/PasswordField";
export { PolicyRow, PolicySection, PolicyTable } from "./components/PolicyTable"; export { PolicyRow, PolicySection, PolicyTable } from "./components/PolicyTable";
export { default as PolicyPathHelp, normalizePolicySourcePathItems } from "./components/PolicyPathHelp"; export { default as PolicyPathHelp, normalizePolicySourcePathItems } from "./components/PolicyPathHelp";

View File

@@ -0,0 +1,73 @@
function assert(condition: unknown, message = "assertion failed"): void {
if (!condition) throw new Error(message);
}
function assertEqual<T>(actual: T, expected: T, message = "values should be equal"): void {
if (actual !== expected) throw new Error(`${message}: expected ${String(expected)}, got ${String(actual)}`);
}
import { renderToStaticMarkup } from "react-dom/server";
import ResourceAccessExplanation, {
formatResourceAccessProvenanceDetails,
resourceAccessProvenanceKindLabel
} from "../src/components/ResourceAccessExplanation";
const loadingMarkup = renderToStaticMarkup(<ResourceAccessExplanation loading />);
assert(
loadingMarkup.includes("i18n:govoplan-core.loading_access_explanation.04a7c934"),
"loading copy is owned by Core"
);
const explanationMarkup = renderToStaticMarkup(
<ResourceAccessExplanation
fallbackResourceLabel="Fallback resource"
explanation={{
user: { id: "user-1", email: "reader@example.test", display_name: "Reader" },
resource_type: "campaign",
resource_id: "campaign-1",
action: "campaigns:campaign:read",
provenance: [
{
kind: "resource",
id: "campaign-1",
label: "Quarterly notice",
source: "campaigns",
details: { inherited: true, limit: null }
},
{ kind: "right" }
]
}}
/>
);
assert(explanationMarkup.includes("Reader"), "the preferred user label is rendered");
assert(explanationMarkup.includes("Quarterly notice"), "resource provenance overrides the fallback label");
assert(explanationMarkup.includes("campaigns:campaign:read"), "the evaluated action is rendered");
assert(explanationMarkup.includes("i18n:govoplan-core.permission.2f81a22d"), "known provenance kinds use Core wording");
assert(explanationMarkup.includes("i18n:govoplan-core.no_source.6dcf9723"), "missing sources use Core wording");
assert(!explanationMarkup.includes("govoplan-campaign"), "the shared component does not depend on Campaign wording");
assert(!explanationMarkup.includes("govoplan-files"), "the shared component does not depend on Files wording");
const emptyMarkup = renderToStaticMarkup(
<ResourceAccessExplanation
fallbackResourceLabel="Fallback resource"
explanation={{
user: { id: "user-1" },
resource_type: "file",
resource_id: "file-1",
action: "files:file:read",
provenance: []
}}
/>
);
assert(emptyMarkup.includes("Fallback resource"), "the caller fallback labels resources without resource provenance");
assert(
emptyMarkup.includes("i18n:govoplan-core.no_access_evidence_was_returned.84a21e4e"),
"empty evidence uses Core wording"
);
assertEqual(resourceAccessProvenanceKindLabel("custom"), "custom", "unknown provenance kinds remain visible");
assertEqual(
formatResourceAccessProvenanceDetails({ count: 2, inherited: false, source: null, nested: { id: "role-1" } }),
'count: 2; inherited: false; source: i18n:govoplan-core.none.6eef6648; nested: {"id":"role-1"}',
"provenance details retain the established compact format"
);

View File

@@ -21,10 +21,12 @@
"tests/data-grid-actions.test.tsx", "tests/data-grid-actions.test.tsx",
"tests/dialog-focus.test.tsx", "tests/dialog-focus.test.tsx",
"tests/mail-components.test.tsx", "tests/mail-components.test.tsx",
"tests/resource-access-explanation.test.tsx",
"src/components/CredentialPanel.tsx", "src/components/CredentialPanel.tsx",
"src/components/email/EmailAddressInput.tsx", "src/components/email/EmailAddressInput.tsx",
"src/components/PasswordField.tsx", "src/components/PasswordField.tsx",
"src/components/MessageDisplayPanel.tsx", "src/components/MessageDisplayPanel.tsx",
"src/components/ResourceAccessExplanation.tsx",
"src/components/mail/MailServerSettingsPanel.tsx", "src/components/mail/MailServerSettingsPanel.tsx",
"src/components/Button.tsx", "src/components/Button.tsx",
"src/components/DismissibleAlert.tsx", "src/components/DismissibleAlert.tsx",