Files
govoplan-admin/webui/src/api/admin.ts

641 lines
21 KiB
TypeScript

import type { ApiSettings, DeltaDeletedItem } from "@govoplan/core-webui";
import { apiFetch } from "@govoplan/core-webui";
export type PermissionItem = {
scope: string;
label: string;
description: string;
category: string;
level: "tenant" | "system";
system_template_id?: string | null;
system_required?: boolean;
};
export type AdminOverview = {
active_tenant_id: string;
active_tenant_name: string;
tenant_count?: number | null;
system_account_count?: number | null;
system_group_template_count?: number | null;
system_role_template_count?: number | null;
user_count: number;
active_user_count: number;
group_count: number;
role_count: number;
active_api_key_count: number;
capabilities: string[];
};
export type TenantAdminItem = {
id: string;
slug: string;
name: string;
description?: string | null;
default_locale: string;
settings: Record<string, unknown>;
allow_custom_groups?: boolean | null;
allow_custom_roles?: boolean | null;
allow_api_keys?: boolean | null;
effective_governance: Record<string, boolean>;
is_active: boolean;
counts: Record<string, number>;
created_at: string;
updated_at: string;
};
export type PrivacyRetentionPolicyFieldKey =
| "store_raw_campaign_json"
| "raw_campaign_json_retention_days"
| "generated_eml_retention_days"
| "stored_report_detail_retention_days"
| "mock_mailbox_retention_days"
| "audit_detail_retention_days"
| "audit_detail_level";
export type PrivacyRetentionLimitPermissions = Record<PrivacyRetentionPolicyFieldKey, boolean>;
export type PrivacyRetentionLimitPermissionPatch = Partial<PrivacyRetentionLimitPermissions>;
export type PrivacyRetentionPolicy = {
store_raw_campaign_json: boolean;
raw_campaign_json_retention_days?: number | null;
generated_eml_retention_days?: number | null;
stored_report_detail_retention_days?: number | null;
mock_mailbox_retention_days?: number | null;
audit_detail_retention_days?: number | null;
audit_detail_level: "full" | "redacted" | "minimal";
allow_lower_level_limits: PrivacyRetentionLimitPermissions;
};
export type PrivacyRetentionPolicyPatch = Partial<Omit<PrivacyRetentionPolicy, "allow_lower_level_limits">> & {
allow_lower_level_limits?: PrivacyRetentionLimitPermissionPatch;
};
export type MaintenanceMode = {
enabled: boolean;
message?: string | null;
};
export type LanguagePackage = {
code: string;
label: string;
native_label?: string | null;
};
export type SystemSettingsItem = {
default_locale: string;
allow_tenant_custom_groups: boolean;
allow_tenant_custom_roles: boolean;
allow_tenant_api_keys: boolean;
privacy_retention_policy: PrivacyRetentionPolicy;
maintenance_mode: MaintenanceMode;
available_languages: LanguagePackage[];
enabled_language_codes: string[];
settings: Record<string, unknown>;
};
export type SystemSettingsDeltaSections = Partial<{
defaults: Pick<SystemSettingsItem, "default_locale">;
tenant_capabilities: Pick<SystemSettingsItem, "allow_tenant_custom_groups" | "allow_tenant_custom_roles" | "allow_tenant_api_keys">;
languages: Pick<SystemSettingsItem, "available_languages" | "enabled_language_codes">;
privacy_retention_policy: SystemSettingsItem["privacy_retention_policy"];
maintenance_mode: SystemSettingsItem["maintenance_mode"];
settings: SystemSettingsItem["settings"];
}>;
export type SystemSettingsUpdatePayload = {
default_locale: string;
allow_tenant_custom_groups: boolean;
allow_tenant_custom_roles: boolean;
allow_tenant_api_keys: boolean;
privacy_retention_policy?: PrivacyRetentionPolicy | null;
maintenance_mode?: MaintenanceMode | null;
available_languages?: LanguagePackage[] | null;
enabled_language_codes?: string[] | null;
change_request_id?: string | null;
};
export type ConfigurationChangeRequest = {
id: string;
key: string;
label?: string;
target?: Record<string, unknown>;
dry_run: boolean;
requested_by: string;
requested_at: string;
updated_at: string;
status: string;
approvals: Array<Record<string, unknown>>;
plan: Record<string, unknown>;
value_preview?: unknown;
};
export type ConfigurationChangeRecord = {
id: string;
version: number;
key: string;
target?: Record<string, unknown>;
actor_user_id: string;
approval_request_id?: string | null;
approval_user_ids: string[];
before?: unknown;
after?: unknown;
rollback_value?: unknown;
status: string;
created_at: string;
};
export type ConfigurationPackageDiagnostic = {
severity: "blocker" | "warning" | "info";
code: string;
message: string;
module_id?: string | null;
object_ref?: string | null;
resolution?: string | null;
};
export type ConfigurationPackageRequiredData = {
key: string;
label: string;
data_type: string;
required: boolean;
secret: boolean;
description?: string | null;
};
export type ConfigurationPackagePlanItem = {
action: "create" | "update" | "bind" | "skip" | "blocked" | "noop";
module_id: string;
fragment_type: string;
fragment_id?: string | null;
summary?: string | null;
};
export type ConfigurationPackageFragment = {
module_id: string;
fragment_type: string;
fragment_id?: string | null;
payload: Record<string, unknown>;
};
export type ConfigurationPackageRunPayload = {
package: Record<string, unknown>;
tenant_id?: string | null;
supplied_data?: Record<string, unknown>;
change_request_id?: string | null;
};
type DeltaResponseFields = {
deleted: DeltaDeletedItem[];
watermark?: string | null;
has_more: boolean;
full: boolean;
};
function deltaSuffix(options: { since?: string | null; limit?: number } = {}): string {
const params = new URLSearchParams();
if (options.since) params.set("since", options.since);
if (options.limit) params.set("limit", String(options.limit));
return params.toString() ? `?${params.toString()}` : "";
}
export type SystemSettingsDeltaResponse = {
item?: SystemSettingsItem | null;
sections: SystemSettingsDeltaSections;
changed_sections: string[];
} & DeltaResponseFields;
export type ConfigurationChangesDeltaResponse = {
requests: ConfigurationChangeRequest[];
history: ConfigurationChangeRecord[];
} & DeltaResponseFields;
export type ModuleCatalogItem = {
id: string;
name: string;
version: string;
installed: boolean;
current_enabled: boolean;
desired_enabled: boolean;
protected: boolean;
restart_required: boolean;
dependencies: string[];
optional_dependencies: string[];
dependents: string[];
permissions_count: number;
role_templates_count: number;
route_contributed: boolean;
nav_count: number;
frontend_package?: string | null;
migration_module_id?: string | null;
migration_script_location?: string | null;
runtime_toggle_supported: boolean;
install_uninstall_supported: boolean;
};
export type ModuleCatalogResponse = {
modules: ModuleCatalogItem[];
current_enabled: string[];
desired_enabled: string[];
configured_enabled: string[];
protected_modules: string[];
restart_required: boolean;
runtime_toggle_supported: boolean;
install_uninstall_supported: boolean;
install_plan_supported: boolean;
package_mutation_supported: boolean;
maintenance_mode: MaintenanceMode;
notes: string[];
};
export type ModuleInstallPlanItem = {
module_id: string;
action: "install" | "uninstall";
python_package?: string | null;
python_ref?: string | null;
webui_package?: string | null;
webui_ref?: string | null;
destroy_data: boolean;
status: "planned" | "applied" | "blocked";
notes?: string | null;
};
export type ModuleInstallPreflightIssue = {
severity: "blocker" | "warning" | "info";
code: string;
message: string;
module_id?: string | null;
};
export type ModuleInstallChecklistItem = {
id: string;
label: string;
status: "done" | "pending" | "blocked" | "warning";
detail?: string | null;
};
export type ModuleInstallPreflight = {
allowed: boolean;
maintenance_mode: boolean;
frontend_rebuild_required: boolean;
restart_required: boolean;
commands: string[];
rollback_commands: string[];
issues: ModuleInstallPreflightIssue[];
checklist: ModuleInstallChecklistItem[];
};
export type ModuleInstallPlanResponse = {
items: ModuleInstallPlanItem[];
updated_at?: string | null;
commands: string[];
maintenance_mode: MaintenanceMode;
preflight?: ModuleInstallPreflight | null;
notes: string[];
};
export type ModuleInstallerLockStatus = {
locked: boolean;
path: string;
[key: string]: unknown;
};
export type ModuleInstallerDaemonStatus = {
running: boolean;
status: string;
path: string;
[key: string]: unknown;
};
export type ModuleInstallerRunSummary = {
run_id: string;
status: string;
started_at?: string | null;
finished_at?: string | null;
request_id?: string | null;
requested_by?: string | null;
trace?: Record<string, unknown> | null;
rollback_status?: string | null;
supervisor_status?: string | null;
error?: string | null;
record_path: string;
commands_count: number;
planned_modules: string[];
};
export type ModuleInstallerRunListResponse = {
runs: ModuleInstallerRunSummary[];
lock: ModuleInstallerLockStatus;
cursor?: string | null;
next_cursor?: string | null;
full?: boolean;
};
export type ModuleInstallerRequestOptions = {
build_webui: boolean;
migrate_database: boolean;
webui_root?: string | null;
npm_bin?: string | null;
database_backup_command?: string | null;
database_restore_command?: string | null;
database_restore_check_command?: string | null;
activate_installed_modules: boolean;
remove_uninstalled_modules_from_desired: boolean;
restart_commands: string[];
health_urls: string[];
health_timeout_seconds: number;
health_interval_seconds: number;
};
export type ModuleInstallerRequestItem = {
request_id: string;
status: string;
created_at: string;
requested_by?: string | null;
started_at?: string | null;
finished_at?: string | null;
cancelled_at?: string | null;
cancelled_by?: string | null;
retry_of?: string | null;
trace?: Record<string, unknown> | null;
error?: string | null;
record_path?: string | null;
options: Record<string, unknown>;
};
export type ModuleInstallerRequestListResponse = {
requests: ModuleInstallerRequestItem[];
daemon: ModuleInstallerDaemonStatus;
cursor?: string | null;
next_cursor?: string | null;
full?: boolean;
};
export type ModulePackageCatalogItem = {
module_id: string;
name: string;
description?: string | null;
version?: string | null;
action: "install" | "uninstall";
python_package?: string | null;
python_ref?: string | null;
webui_package?: string | null;
webui_ref?: string | null;
license_features: string[];
license_allowed: boolean;
license_enforced: boolean;
license_missing_features: string[];
license_reason?: string | null;
notes?: string | null;
tags: string[];
};
export type ModuleLicenseDiagnostics = {
configured: boolean;
valid: boolean;
path?: string | null;
license_id?: string | null;
subject?: string | null;
features: string[];
valid_from?: string | null;
valid_until?: string | null;
signed: boolean;
trusted: boolean;
key_id?: string | null;
enforced: boolean;
allowed: boolean;
required_features: string[];
missing_features: string[];
expires_in_days?: number | null;
reason?: string | null;
guidance: string[];
};
export type ModulePackageCatalogResponse = {
modules: ModulePackageCatalogItem[];
configured: boolean;
valid: boolean;
path?: string | null;
source?: string | null;
source_type?: string | null;
cache_used: boolean;
cache_path?: string | null;
channel?: string | null;
sequence?: number | null;
generated_at?: string | null;
not_before?: string | null;
expires_at?: string | null;
signed: boolean;
trusted: boolean;
key_id?: string | null;
license: ModuleLicenseDiagnostics;
warnings: string[];
error?: string | null;
};
export type GovernanceAssignment = {
tenant_id: string;
mode: "available" | "required";
};
export type GovernanceTemplateItem = {
id: string;
kind: "group" | "role";
slug: string;
name: string;
description?: string | null;
permissions: string[];
effective_permission_count: number;
is_active: boolean;
assignments: GovernanceAssignment[];
created_at: string;
updated_at: string;
};
export function fetchAdminOverview(settings: ApiSettings): Promise<AdminOverview> {
return apiFetch(settings, "/api/v1/admin/overview");
}
export async function fetchPermissionCatalog(settings: ApiSettings): Promise<PermissionItem[]> {
const response = await apiFetch<{ permissions: PermissionItem[] }>(settings, "/api/v1/admin/permissions");
return response.permissions;
}
export async function fetchTenants(settings: ApiSettings): Promise<TenantAdminItem[]> {
const response = await apiFetch<{ tenants: TenantAdminItem[] }>(settings, "/api/v1/admin/tenants");
return response.tenants;
}
export function fetchSystemSettings(settings: ApiSettings): Promise<SystemSettingsItem> {
return apiFetch(settings, "/api/v1/admin/system/settings");
}
export function fetchSystemSettingsDelta(settings: ApiSettings, options: { since?: string | null; limit?: number } = {}): Promise<SystemSettingsDeltaResponse> {
const suffix = deltaSuffix(options);
return apiFetch(settings, `/api/v1/admin/system/settings/delta${suffix}`);
}
export function updateSystemSettings(settings: ApiSettings, payload: SystemSettingsUpdatePayload): Promise<SystemSettingsItem> {
return apiFetch(settings, "/api/v1/admin/system/settings", { method: "PATCH", body: JSON.stringify(payload) });
}
export function fetchModuleCatalog(settings: ApiSettings): Promise<ModuleCatalogResponse> {
return apiFetch(settings, "/api/v1/admin/system/modules");
}
export function updateModuleState(settings: ApiSettings, enabledModules: string[], changeRequestId?: string | null): Promise<ModuleCatalogResponse> {
return apiFetch(settings, "/api/v1/admin/system/modules", {
method: "PUT",
body: JSON.stringify({ enabled_modules: enabledModules, change_request_id: changeRequestId ?? null })
});
}
export function fetchModuleInstallPlan(settings: ApiSettings): Promise<ModuleInstallPlanResponse> {
return apiFetch(settings, "/api/v1/admin/system/modules/install-plan");
}
export function fetchModuleInstallerRuns(settings: ApiSettings, options: { page_size?: number; cursor?: string | null } = {}): Promise<ModuleInstallerRunListResponse> {
const params = new URLSearchParams();
if (options.page_size) params.set("page_size", String(options.page_size));
if (options.cursor) params.set("cursor", options.cursor);
const suffix = params.toString() ? `?${params.toString()}` : "";
return apiFetch(settings, `/api/v1/admin/system/modules/install-runs${suffix}`);
}
export function fetchModuleInstallerRequests(settings: ApiSettings, options: { page_size?: number; cursor?: string | null } = {}): Promise<ModuleInstallerRequestListResponse> {
const params = new URLSearchParams();
if (options.page_size) params.set("page_size", String(options.page_size));
if (options.cursor) params.set("cursor", options.cursor);
const suffix = params.toString() ? `?${params.toString()}` : "";
return apiFetch(settings, `/api/v1/admin/system/modules/install-requests${suffix}`);
}
export function createModuleInstallerRequest(settings: ApiSettings, options: ModuleInstallerRequestOptions): Promise<ModuleInstallerRequestItem> {
return apiFetch(settings, "/api/v1/admin/system/modules/install-requests", {
method: "POST",
body: JSON.stringify({ options })
});
}
export function cancelModuleInstallerRequest(settings: ApiSettings, requestId: string): Promise<ModuleInstallerRequestItem> {
return apiFetch(settings, `/api/v1/admin/system/modules/install-requests/${requestId}/cancel`, { method: "POST" });
}
export function retryModuleInstallerRequest(settings: ApiSettings, requestId: string): Promise<ModuleInstallerRequestItem> {
return apiFetch(settings, `/api/v1/admin/system/modules/install-requests/${requestId}/retry`, { method: "POST" });
}
export function fetchModulePackageCatalog(settings: ApiSettings): Promise<ModulePackageCatalogResponse> {
return apiFetch(settings, "/api/v1/admin/system/modules/package-catalog");
}
export function planModuleInstallFromCatalog(settings: ApiSettings, moduleId: string): Promise<ModuleInstallPlanResponse> {
return apiFetch(settings, `/api/v1/admin/system/modules/install-plan/catalog/${encodeURIComponent(moduleId)}`, { method: "POST" });
}
export function planModuleUninstall(settings: ApiSettings, moduleId: string): Promise<ModuleInstallPlanResponse> {
return apiFetch(settings, `/api/v1/admin/system/modules/${encodeURIComponent(moduleId)}/uninstall-plan`, { method: "POST" });
}
export function updateModuleInstallPlan(settings: ApiSettings, items: ModuleInstallPlanItem[], changeRequestId?: string | null): Promise<ModuleInstallPlanResponse> {
return apiFetch(settings, "/api/v1/admin/system/modules/install-plan", {
method: "PUT",
body: JSON.stringify({ items, change_request_id: changeRequestId ?? null })
});
}
export function clearModuleInstallPlan(settings: ApiSettings): Promise<ModuleInstallPlanResponse> {
return apiFetch(settings, "/api/v1/admin/system/modules/install-plan", { method: "DELETE" });
}
export async function fetchGovernanceTemplates(settings: ApiSettings, kind?: "group" | "role"): Promise<GovernanceTemplateItem[]> {
const suffix = kind ? `?kind=${encodeURIComponent(kind)}` : "";
const response = await apiFetch<{ templates: GovernanceTemplateItem[] }>(settings, `/api/v1/admin/system/governance-templates${suffix}`);
return response.templates;
}
export function createGovernanceTemplate(settings: ApiSettings, payload: Omit<GovernanceTemplateItem, "id" | "created_at" | "updated_at" | "effective_permission_count"> & { change_request_id?: string | null }): Promise<GovernanceTemplateItem> {
return apiFetch(settings, "/api/v1/admin/system/governance-templates", { method: "POST", body: JSON.stringify(payload) });
}
export function updateGovernanceTemplate(settings: ApiSettings, templateId: string, payload: Omit<GovernanceTemplateItem, "id" | "kind" | "slug" | "created_at" | "updated_at" | "effective_permission_count"> & { change_request_id?: string | null }): Promise<GovernanceTemplateItem> {
return apiFetch(settings, `/api/v1/admin/system/governance-templates/${templateId}`, { method: "PATCH", body: JSON.stringify(payload) });
}
export function deleteGovernanceTemplate(settings: ApiSettings, templateId: string, changeRequestId?: string | null): Promise<void> {
const suffix = changeRequestId ? `?change_request_id=${encodeURIComponent(changeRequestId)}` : "";
return apiFetch(settings, `/api/v1/admin/system/governance-templates/${templateId}${suffix}`, { method: "DELETE" });
}
export function fetchConfigurationChanges(settings: ApiSettings): Promise<{ requests: ConfigurationChangeRequest[]; history: ConfigurationChangeRecord[] }> {
return apiFetch(settings, "/api/v1/admin/configuration-changes");
}
export function fetchConfigurationChangesDelta(settings: ApiSettings, options: { since?: string | null; limit?: number } = {}): Promise<ConfigurationChangesDeltaResponse> {
const suffix = deltaSuffix(options);
return apiFetch(settings, `/api/v1/admin/configuration-changes/delta${suffix}`);
}
export async function createConfigurationChangeRequest(settings: ApiSettings, payload: {
key: string;
value?: unknown;
dry_run?: boolean;
target?: Record<string, unknown>;
reason?: string | null;
}): Promise<ConfigurationChangeRequest> {
const response = await apiFetch<{ request: ConfigurationChangeRequest }>(settings, "/api/v1/admin/configuration-change-requests", {
method: "POST",
body: JSON.stringify(payload)
});
return response.request;
}
export async function approveConfigurationChangeRequest(settings: ApiSettings, requestId: string, reason?: string | null): Promise<ConfigurationChangeRequest> {
const response = await apiFetch<{ request: ConfigurationChangeRequest }>(settings, `/api/v1/admin/configuration-change-requests/${encodeURIComponent(requestId)}/approve`, {
method: "POST",
body: JSON.stringify({ reason: reason ?? null })
});
return response.request;
}
export function fetchConfigurationPackageCatalogValidation(settings: ApiSettings): Promise<{ validation: Record<string, unknown> }> {
return apiFetch(settings, "/api/v1/admin/configuration-packages/catalog");
}
export function dryRunConfigurationPackage(settings: ApiSettings, payload: ConfigurationPackageRunPayload): Promise<{
diagnostics: ConfigurationPackageDiagnostic[];
required_data: ConfigurationPackageRequiredData[];
plan: ConfigurationPackagePlanItem[];
}> {
return apiFetch(settings, "/api/v1/admin/configuration-packages/dry-run", {
method: "POST",
body: JSON.stringify(payload)
});
}
export function applyConfigurationPackage(settings: ApiSettings, payload: ConfigurationPackageRunPayload): Promise<{
diagnostics: ConfigurationPackageDiagnostic[];
created_refs: Record<string, string>;
updated_refs: Record<string, string>;
}> {
return apiFetch(settings, "/api/v1/admin/configuration-packages/apply", {
method: "POST",
body: JSON.stringify(payload)
});
}
export function exportConfigurationPackage(settings: ApiSettings, payload: {
tenant_id?: string | null;
scopes?: string[];
module_ids?: string[];
object_refs?: string[];
}): Promise<{
fragments: ConfigurationPackageFragment[];
data_requirements: ConfigurationPackageRequiredData[];
diagnostics: ConfigurationPackageDiagnostic[];
}> {
return apiFetch(settings, "/api/v1/admin/configuration-packages/export", {
method: "POST",
body: JSON.stringify(payload)
});
}