Files
govoplan-policy/webui/src/api/viewPolicies.ts
T

186 lines
5.5 KiB
TypeScript

import { apiFetch, apiPath, type ApiSettings } from "@govoplan/core-webui";
export type ViewPolicyScope = "system" | "tenant" | "group" | "user";
export type ViewPolicyItem = {
allow_view?: boolean;
allow_select?: boolean;
allow_assign?: boolean;
allow_edit?: boolean;
allow_derive?: boolean;
allow_workflow_activate?: boolean;
allowed_view_ids?: string[];
visible_surface_ids?: string[];
};
export type EffectiveViewPolicy = {
allow_view: boolean;
allow_select: boolean;
allow_assign: boolean;
allow_edit: boolean;
allow_derive: boolean;
allow_workflow_activate: boolean;
allowed_view_ids?: string[] | null;
visible_surface_ids?: string[] | null;
};
export type ViewPolicyScopeResponse = {
scope_type: ViewPolicyScope;
scope_id?: string | null;
id?: string | null;
revision?: number | null;
policy: ViewPolicyItem;
effective_policy: EffectiveViewPolicy;
parent_policy: EffectiveViewPolicy;
source_path: Array<{
scope_type: string;
scope_id?: string | null;
source_id?: string | null;
fields?: string[];
}>;
diagnostics: Array<Record<string, unknown>>;
};
export type ViewPolicyReferenceData = {
views: Array<{ id: string; name: string; scope_type?: string }>;
surfaces: Array<{ id: string; label: string; module_id: string; kind: string }>;
};
export type PolicyImpactCategory = "newly_allowed" | "newly_denied" | "unchanged" | "indeterminate";
export type PolicyImpactPreviewResponse = {
preview_id: string;
proposal_hash: string;
policy_family: string;
scope_type: string;
scope_id?: string | null;
base_revision?: number | null;
counts: Record<PolicyImpactCategory, number>;
effects: Array<{
category: PolicyImpactCategory;
subject: {
module_id: string;
resource_type: string;
resource_id: string;
action: string;
label?: string | null;
scope_type?: string | null;
scope_id?: string | null;
};
current_allowed?: boolean | null;
proposed_allowed?: boolean | null;
rule: string;
current_sources: Array<{ path: string; label: string }>;
proposed_sources: Array<{ path: string; label: string }>;
explanation?: string | null;
}>;
populations: Array<{
provider_id: string;
state: "complete" | "sampled" | "truncated" | "unavailable";
returned: number;
total_available?: number | null;
explanation?: string | null;
}>;
details_hidden: boolean;
details_explanation?: string | null;
high_impact: boolean;
};
export function fetchViewPolicy(
settings: ApiSettings,
scope: ViewPolicyScope,
scopeId?: string | null
): Promise<ViewPolicyScopeResponse> {
return apiFetch(settings, apiPath(`/api/v1/admin/view-policies/${scope}`, {
scope_id: scopeId || undefined
}));
}
export function updateViewPolicy(
settings: ApiSettings,
scope: ViewPolicyScope,
scopeId: string | null | undefined,
policy: ViewPolicyItem,
impactPreview?: Pick<PolicyImpactPreviewResponse, "preview_id" | "proposal_hash"> | null
): Promise<ViewPolicyScopeResponse> {
return apiFetch(settings, apiPath(`/api/v1/admin/view-policies/${scope}`, {
scope_id: scopeId || undefined
}), {
method: "PUT",
body: JSON.stringify({
policy,
impact_preview_id: impactPreview?.preview_id,
impact_proposal_hash: impactPreview?.proposal_hash
})
});
}
export function deleteViewPolicy(
settings: ApiSettings,
scope: ViewPolicyScope,
scopeId?: string | null,
impactPreview?: Pick<PolicyImpactPreviewResponse, "preview_id" | "proposal_hash"> | null
): Promise<ViewPolicyScopeResponse> {
return apiFetch(settings, apiPath(`/api/v1/admin/view-policies/${scope}`, {
scope_id: scopeId || undefined,
impact_preview_id: impactPreview?.preview_id,
impact_proposal_hash: impactPreview?.proposal_hash
}), { method: "DELETE" });
}
export function previewViewPolicyImpact(
settings: ApiSettings,
scope: ViewPolicyScope,
scopeId: string | null | undefined,
policy: ViewPolicyItem,
population: { viewIds: string[]; surfaceIds: string[] }
): Promise<PolicyImpactPreviewResponse> {
return apiFetch(settings, "/api/v1/admin/policy-impact/preview", {
method: "POST",
body: JSON.stringify({
policy_family: "view",
scope_type: scope,
scope_id: scopeId || null,
proposed_policy: policy,
populations: [
{
provider_id: "views",
selector: {
include_views: true,
include_surfaces: false,
view_ids: population.viewIds.slice(0, 500)
},
limit: 500
},
{
provider_id: "views",
selector: {
include_views: false,
include_surfaces: true,
surface_ids: population.surfaceIds.slice(0, 500)
},
limit: 500
}
],
include_details: true
})
});
}
export async function fetchViewPolicyReferences(settings: ApiSettings): Promise<ViewPolicyReferenceData> {
const [definitionResult, surfaceResult] = await Promise.allSettled([
apiFetch<{ definitions: Array<{ id: string; name: string; scope_type?: string }> }>(
settings,
apiPath("/api/v1/views/definitions", { scope_type: "tenant", include_inherited: true })
),
apiFetch<{ surfaces: Array<{ id: string; label: string; module_id: string; kind: string }> }>(
settings,
"/api/v1/views/surfaces"
)
]);
return {
views: definitionResult.status === "fulfilled" ? definitionResult.value.definitions : [],
surfaces: surfaceResult.status === "fulfilled" ? surfaceResult.value.surfaces : []
};
}