|
|
|
|
@@ -1,787 +0,0 @@
|
|
|
|
|
import type { ApiSettings } from "../types";
|
|
|
|
|
import { apiFetch } from "./client";
|
|
|
|
|
|
|
|
|
|
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 TenantOwnerCandidate = {
|
|
|
|
|
account_id: string;
|
|
|
|
|
email: string;
|
|
|
|
|
display_name?: string | null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type RoleSummary = {
|
|
|
|
|
id: string;
|
|
|
|
|
slug: string;
|
|
|
|
|
name: string;
|
|
|
|
|
description?: string | null;
|
|
|
|
|
permissions: string[];
|
|
|
|
|
effective_permission_count: number;
|
|
|
|
|
is_builtin: boolean;
|
|
|
|
|
is_assignable: boolean;
|
|
|
|
|
user_assignments: number;
|
|
|
|
|
group_assignments: number;
|
|
|
|
|
level: "tenant" | "system";
|
|
|
|
|
system_template_id?: string | null;
|
|
|
|
|
system_required?: boolean;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type GroupSummary = {
|
|
|
|
|
id: string;
|
|
|
|
|
slug: string;
|
|
|
|
|
name: string;
|
|
|
|
|
description?: string | null;
|
|
|
|
|
is_active: boolean;
|
|
|
|
|
member_count: number;
|
|
|
|
|
member_ids: string[];
|
|
|
|
|
roles: RoleSummary[];
|
|
|
|
|
created_at: string;
|
|
|
|
|
updated_at: string;
|
|
|
|
|
system_template_id?: string | null;
|
|
|
|
|
system_required?: boolean;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type UserAdminItem = {
|
|
|
|
|
id: string;
|
|
|
|
|
account_id: string;
|
|
|
|
|
tenant_id: string;
|
|
|
|
|
email: string;
|
|
|
|
|
display_name?: string | null;
|
|
|
|
|
is_active: boolean;
|
|
|
|
|
account_is_active: boolean;
|
|
|
|
|
password_reset_required: boolean;
|
|
|
|
|
last_login_at?: string | null;
|
|
|
|
|
groups: GroupSummary[];
|
|
|
|
|
roles: RoleSummary[];
|
|
|
|
|
effective_scopes: string[];
|
|
|
|
|
is_owner: boolean;
|
|
|
|
|
is_last_active_owner: boolean;
|
|
|
|
|
created_at: string;
|
|
|
|
|
updated_at: string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type SystemAccountItem = {
|
|
|
|
|
account_id: string;
|
|
|
|
|
email: string;
|
|
|
|
|
display_name?: string | null;
|
|
|
|
|
is_active: boolean;
|
|
|
|
|
memberships: Array<{ tenant_id: string; tenant_name: string; user_id: string; is_active: boolean; role_ids: string[]; group_ids: string[]; is_owner: boolean; is_last_active_owner: boolean }>;
|
|
|
|
|
roles: RoleSummary[];
|
|
|
|
|
last_login_at?: string | null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export type SystemMembershipDraft = {
|
|
|
|
|
tenant_id: string;
|
|
|
|
|
is_active: boolean;
|
|
|
|
|
role_ids: string[];
|
|
|
|
|
group_ids: string[];
|
|
|
|
|
is_owner?: boolean;
|
|
|
|
|
is_last_active_owner?: boolean;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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 PrivacyRetentionPolicyScope = "system" | "tenant" | "user" | "group" | "campaign";
|
|
|
|
|
|
|
|
|
|
export type PolicySourceStep = {
|
|
|
|
|
scope_type: string;
|
|
|
|
|
scope_id?: string | null;
|
|
|
|
|
path: string;
|
|
|
|
|
label: string;
|
|
|
|
|
applied_fields?: string[];
|
|
|
|
|
policy?: PrivacyRetentionPolicyPatch | PrivacyRetentionPolicy | null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type PolicyDecision = {
|
|
|
|
|
allowed: boolean;
|
|
|
|
|
reason?: string | null;
|
|
|
|
|
source_path: PolicySourceStep[];
|
|
|
|
|
requirements: string[];
|
|
|
|
|
details?: Record<string, unknown>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type PrivacyRetentionPolicyScopeResponse = {
|
|
|
|
|
scope_type: PrivacyRetentionPolicyScope;
|
|
|
|
|
scope_id?: string | null;
|
|
|
|
|
policy: PrivacyRetentionPolicyPatch;
|
|
|
|
|
effective_policy: PrivacyRetentionPolicy;
|
|
|
|
|
parent_policy?: PrivacyRetentionPolicy | null;
|
|
|
|
|
effective_policy_sources?: PolicySourceStep[];
|
|
|
|
|
parent_policy_sources?: PolicySourceStep[];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type PrivacyRetentionPolicyExplainResponse = {
|
|
|
|
|
scope_type: PrivacyRetentionPolicyScope;
|
|
|
|
|
scope_id?: string | null;
|
|
|
|
|
decision: PolicyDecision;
|
|
|
|
|
effective_policy: PrivacyRetentionPolicy;
|
|
|
|
|
parent_policy?: PrivacyRetentionPolicy | null;
|
|
|
|
|
effective_policy_sources?: PolicySourceStep[];
|
|
|
|
|
parent_policy_sources?: PolicySourceStep[];
|
|
|
|
|
blocked_fields: PrivacyRetentionPolicyFieldKey[];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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?: { enabled: boolean; message?: string | null };
|
|
|
|
|
available_languages?: LanguagePackage[];
|
|
|
|
|
enabled_language_codes?: string[];
|
|
|
|
|
settings: Record<string, unknown>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type LanguagePackage = {
|
|
|
|
|
code: string;
|
|
|
|
|
label: string;
|
|
|
|
|
native_label?: string | null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type TenantSettingsItem = {
|
|
|
|
|
id: string;
|
|
|
|
|
slug: string;
|
|
|
|
|
name: string;
|
|
|
|
|
default_locale: string;
|
|
|
|
|
available_languages: LanguagePackage[];
|
|
|
|
|
system_enabled_language_codes: string[];
|
|
|
|
|
enabled_language_codes: string[];
|
|
|
|
|
settings: Record<string, unknown>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type RetentionRunResponse = {
|
|
|
|
|
result: {
|
|
|
|
|
dry_run: boolean;
|
|
|
|
|
policy: PrivacyRetentionPolicy;
|
|
|
|
|
cutoffs: Record<string, string | null>;
|
|
|
|
|
effective_policy_scope?: string;
|
|
|
|
|
counts: Record<string, Record<string, number>>;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type ConfigurationSafetyField = {
|
|
|
|
|
key: string;
|
|
|
|
|
label: string;
|
|
|
|
|
owner_module: string;
|
|
|
|
|
scope: "system" | "tenant" | "user" | "group" | "campaign";
|
|
|
|
|
storage: string;
|
|
|
|
|
ui_managed: boolean;
|
|
|
|
|
risk: "low" | "medium" | "high" | "destructive";
|
|
|
|
|
secret_handling: "none" | "reference_only" | "env_only";
|
|
|
|
|
required_scopes: string[];
|
|
|
|
|
dry_run_required: boolean;
|
|
|
|
|
validation_required: boolean;
|
|
|
|
|
policy_explanation_required: boolean;
|
|
|
|
|
audit_event?: string | null;
|
|
|
|
|
maintenance_required: boolean;
|
|
|
|
|
two_person_approval_required: boolean;
|
|
|
|
|
rollback_history_required: boolean;
|
|
|
|
|
notes?: string | null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type ConfigurationChangeSafetyPlan = {
|
|
|
|
|
key: string;
|
|
|
|
|
allowed: boolean;
|
|
|
|
|
field?: ConfigurationSafetyField | null;
|
|
|
|
|
risk?: ConfigurationSafetyField["risk"] | null;
|
|
|
|
|
missing_scopes: string[];
|
|
|
|
|
dry_run_required: boolean;
|
|
|
|
|
dry_run_satisfied: boolean;
|
|
|
|
|
approval_required: boolean;
|
|
|
|
|
approval_satisfied: boolean;
|
|
|
|
|
maintenance_required: boolean;
|
|
|
|
|
maintenance_satisfied: boolean;
|
|
|
|
|
rollback_history_required: boolean;
|
|
|
|
|
secret_handling: ConfigurationSafetyField["secret_handling"];
|
|
|
|
|
audit_event?: string | null;
|
|
|
|
|
policy_explanation?: string | null;
|
|
|
|
|
blockers: string[];
|
|
|
|
|
warnings: string[];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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: "pending_approval" | "approved" | "applied" | "rejected" | string;
|
|
|
|
|
approvals: Array<Record<string, unknown>>;
|
|
|
|
|
plan: ConfigurationChangeSafetyPlan;
|
|
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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 type ApiKeyAdminItem = {
|
|
|
|
|
id: string;
|
|
|
|
|
user_id: string;
|
|
|
|
|
user_email: string;
|
|
|
|
|
name: string;
|
|
|
|
|
prefix: string;
|
|
|
|
|
scopes: string[];
|
|
|
|
|
expires_at?: string | null;
|
|
|
|
|
last_used_at?: string | null;
|
|
|
|
|
revoked_at?: string | null;
|
|
|
|
|
created_at: string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type AuditAdminItem = {
|
|
|
|
|
id: string;
|
|
|
|
|
scope: "tenant" | "system";
|
|
|
|
|
tenant_id?: string | null;
|
|
|
|
|
actor_email?: string | null;
|
|
|
|
|
action: string;
|
|
|
|
|
object_type?: string | null;
|
|
|
|
|
object_id?: string | null;
|
|
|
|
|
details: Record<string, unknown>;
|
|
|
|
|
created_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 async function fetchTenantOwnerCandidates(settings: ApiSettings): Promise<TenantOwnerCandidate[]> {
|
|
|
|
|
const response = await apiFetch<{ accounts: TenantOwnerCandidate[] }>(settings, "/api/v1/admin/tenants/owner-candidates");
|
|
|
|
|
return response.accounts;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function createTenant(settings: ApiSettings, payload: {
|
|
|
|
|
slug: string;
|
|
|
|
|
name: string;
|
|
|
|
|
owner_account_id?: string | null;
|
|
|
|
|
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;
|
|
|
|
|
}): Promise<TenantAdminItem> {
|
|
|
|
|
return apiFetch(settings, "/api/v1/admin/tenants", { method: "POST", body: JSON.stringify(payload) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function updateTenant(settings: ApiSettings, tenantId: string, payload: Partial<{
|
|
|
|
|
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;
|
|
|
|
|
}>): Promise<TenantAdminItem> {
|
|
|
|
|
return apiFetch(settings, `/api/v1/admin/tenants/${tenantId}`, { method: "PATCH", body: JSON.stringify(payload) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function fetchTenantSettings(settings: ApiSettings): Promise<TenantSettingsItem> {
|
|
|
|
|
return apiFetch(settings, "/api/v1/admin/tenant/settings");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function updateTenantSettings(settings: ApiSettings, payload: { default_locale: string; enabled_language_codes?: string[] | null }): Promise<TenantSettingsItem> {
|
|
|
|
|
return apiFetch(settings, "/api/v1/admin/tenant/settings", { method: "PATCH", body: JSON.stringify(payload) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function fetchUsers(settings: ApiSettings): Promise<UserAdminItem[]> {
|
|
|
|
|
const response = await apiFetch<{ users: UserAdminItem[] }>(settings, "/api/v1/admin/users");
|
|
|
|
|
return response.users;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function createUser(settings: ApiSettings, payload: {
|
|
|
|
|
email: string;
|
|
|
|
|
display_name?: string | null;
|
|
|
|
|
password?: string | null;
|
|
|
|
|
password_reset_required?: boolean;
|
|
|
|
|
is_active?: boolean;
|
|
|
|
|
group_ids?: string[];
|
|
|
|
|
role_ids?: string[];
|
|
|
|
|
}): Promise<{ user: UserAdminItem; account_created: boolean; temporary_password?: string | null }> {
|
|
|
|
|
return apiFetch(settings, "/api/v1/admin/users", { method: "POST", body: JSON.stringify(payload) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function updateUser(settings: ApiSettings, userId: string, payload: Partial<{
|
|
|
|
|
display_name: string | null;
|
|
|
|
|
is_active: boolean;
|
|
|
|
|
group_ids: string[];
|
|
|
|
|
role_ids: string[];
|
|
|
|
|
}>): Promise<UserAdminItem> {
|
|
|
|
|
return apiFetch(settings, `/api/v1/admin/users/${userId}`, { method: "PATCH", body: JSON.stringify(payload) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function fetchGroups(settings: ApiSettings): Promise<GroupSummary[]> {
|
|
|
|
|
const response = await apiFetch<{ groups: GroupSummary[] }>(settings, "/api/v1/admin/groups");
|
|
|
|
|
return response.groups;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function createGroup(settings: ApiSettings, payload: {
|
|
|
|
|
slug: string;
|
|
|
|
|
name: string;
|
|
|
|
|
description?: string | null;
|
|
|
|
|
is_active?: boolean;
|
|
|
|
|
member_ids?: string[];
|
|
|
|
|
role_ids?: string[];
|
|
|
|
|
}): Promise<GroupSummary> {
|
|
|
|
|
return apiFetch(settings, "/api/v1/admin/groups", { method: "POST", body: JSON.stringify(payload) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function updateGroup(settings: ApiSettings, groupId: string, payload: Partial<{
|
|
|
|
|
name: string;
|
|
|
|
|
description: string | null;
|
|
|
|
|
is_active: boolean;
|
|
|
|
|
member_ids: string[];
|
|
|
|
|
role_ids: string[];
|
|
|
|
|
}>): Promise<GroupSummary> {
|
|
|
|
|
return apiFetch(settings, `/api/v1/admin/groups/${groupId}`, { method: "PATCH", body: JSON.stringify(payload) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function fetchRoles(settings: ApiSettings): Promise<RoleSummary[]> {
|
|
|
|
|
const response = await apiFetch<{ roles: RoleSummary[] }>(settings, "/api/v1/admin/roles");
|
|
|
|
|
return response.roles;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function createRole(settings: ApiSettings, payload: {
|
|
|
|
|
slug: string;
|
|
|
|
|
name: string;
|
|
|
|
|
description?: string | null;
|
|
|
|
|
permissions: string[];
|
|
|
|
|
}): Promise<RoleSummary> {
|
|
|
|
|
return apiFetch(settings, "/api/v1/admin/roles", { method: "POST", body: JSON.stringify(payload) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function updateRole(settings: ApiSettings, roleId: string, payload: {
|
|
|
|
|
name: string;
|
|
|
|
|
description?: string | null;
|
|
|
|
|
permissions: string[];
|
|
|
|
|
is_assignable: boolean;
|
|
|
|
|
}): Promise<RoleSummary> {
|
|
|
|
|
return apiFetch(settings, `/api/v1/admin/roles/${roleId}`, { method: "PATCH", body: JSON.stringify(payload) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function deleteRole(settings: ApiSettings, roleId: string): Promise<void> {
|
|
|
|
|
return apiFetch(settings, `/api/v1/admin/roles/${roleId}`, { method: "DELETE" });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function fetchSystemRoles(settings: ApiSettings): Promise<RoleSummary[]> {
|
|
|
|
|
const response = await apiFetch<{ roles: RoleSummary[] }>(settings, "/api/v1/admin/system/roles");
|
|
|
|
|
return response.roles;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function createSystemRole(settings: ApiSettings, payload: {
|
|
|
|
|
slug: string;
|
|
|
|
|
name: string;
|
|
|
|
|
description?: string | null;
|
|
|
|
|
permissions: string[];
|
|
|
|
|
}): Promise<RoleSummary> {
|
|
|
|
|
return apiFetch(settings, "/api/v1/admin/system/roles", { method: "POST", body: JSON.stringify(payload) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function updateSystemRole(settings: ApiSettings, roleId: string, payload: {
|
|
|
|
|
name: string;
|
|
|
|
|
description?: string | null;
|
|
|
|
|
permissions: string[];
|
|
|
|
|
is_assignable: boolean;
|
|
|
|
|
}): Promise<RoleSummary> {
|
|
|
|
|
return apiFetch(settings, `/api/v1/admin/system/roles/${roleId}`, { method: "PATCH", body: JSON.stringify(payload) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function deleteSystemRole(settings: ApiSettings, roleId: string): Promise<void> {
|
|
|
|
|
return apiFetch(settings, `/api/v1/admin/system/roles/${roleId}`, { method: "DELETE" });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function fetchSystemAccounts(settings: ApiSettings): Promise<{ accounts: SystemAccountItem[]; roles: RoleSummary[] }> {
|
|
|
|
|
return apiFetch(settings, "/api/v1/admin/system/accounts");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function updateSystemAccount(settings: ApiSettings, accountId: string, payload: {
|
|
|
|
|
display_name?: string | null;
|
|
|
|
|
is_active?: boolean;
|
|
|
|
|
role_ids?: string[];
|
|
|
|
|
}): Promise<SystemAccountItem> {
|
|
|
|
|
return apiFetch(settings, `/api/v1/admin/system/accounts/${accountId}`, {
|
|
|
|
|
method: "PATCH",
|
|
|
|
|
body: JSON.stringify(payload)
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function updateSystemAccountRoles(settings: ApiSettings, accountId: string, roleIds: string[]): Promise<SystemAccountItem> {
|
|
|
|
|
return apiFetch(settings, `/api/v1/admin/system/accounts/${accountId}/roles`, {
|
|
|
|
|
method: "PUT",
|
|
|
|
|
body: JSON.stringify({ role_ids: roleIds })
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function fetchApiKeys(settings: ApiSettings, includeRevoked = false): Promise<ApiKeyAdminItem[]> {
|
|
|
|
|
const params = new URLSearchParams();
|
|
|
|
|
if (includeRevoked) params.set("include_revoked", "true");
|
|
|
|
|
const suffix = params.toString() ? `?${params.toString()}` : "";
|
|
|
|
|
const response = await apiFetch<{ api_keys: ApiKeyAdminItem[] }>(settings, `/api/v1/admin/api-keys${suffix}`);
|
|
|
|
|
return response.api_keys;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function createApiKey(settings: ApiSettings, payload: {
|
|
|
|
|
name: string;
|
|
|
|
|
user_id?: string | null;
|
|
|
|
|
scopes: string[];
|
|
|
|
|
expires_at?: string | null;
|
|
|
|
|
}): Promise<ApiKeyAdminItem & { secret: string }> {
|
|
|
|
|
return apiFetch(settings, "/api/v1/admin/api-keys", { method: "POST", body: JSON.stringify(payload) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function revokeApiKey(settings: ApiSettings, keyId: string): Promise<ApiKeyAdminItem> {
|
|
|
|
|
return apiFetch(settings, `/api/v1/admin/api-keys/${keyId}/revoke`, { method: "POST" });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type AuditQueryOptions = {
|
|
|
|
|
tenantId?: string | null;
|
|
|
|
|
allTenants?: boolean;
|
|
|
|
|
scope?: "tenant" | "system";
|
|
|
|
|
limit?: number;
|
|
|
|
|
offset?: number;
|
|
|
|
|
page?: number;
|
|
|
|
|
pageSize?: number;
|
|
|
|
|
sortBy?: "time" | "actor" | "action" | "object" | "tenant";
|
|
|
|
|
sortDirection?: "asc" | "desc";
|
|
|
|
|
filters?: Partial<Record<"time" | "actor" | "action" | "object" | "tenant", string>>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export async function fetchAdminAudit(settings: ApiSettings, options: AuditQueryOptions = {}): Promise<{ items: AuditAdminItem[]; total: number; page: number; page_size: number; pages: number }> {
|
|
|
|
|
const params = new URLSearchParams();
|
|
|
|
|
if (options.tenantId) params.set("tenant_id", options.tenantId);
|
|
|
|
|
if (options.allTenants) params.set("all_tenants", "true");
|
|
|
|
|
if (options.scope) params.set("scope", options.scope);
|
|
|
|
|
if (options.limit) params.set("limit", String(options.limit));
|
|
|
|
|
if (options.offset) params.set("offset", String(options.offset));
|
|
|
|
|
if (options.page) params.set("page", String(options.page));
|
|
|
|
|
if (options.pageSize) params.set("page_size", String(options.pageSize));
|
|
|
|
|
if (options.sortBy) params.set("sort_by", options.sortBy);
|
|
|
|
|
if (options.sortDirection) params.set("sort_direction", options.sortDirection);
|
|
|
|
|
for (const [column, value] of Object.entries(options.filters ?? {})) {
|
|
|
|
|
if (value?.trim()) params.set(`filter_${column}`, value);
|
|
|
|
|
}
|
|
|
|
|
const suffix = params.toString() ? `?${params.toString()}` : "";
|
|
|
|
|
return apiFetch(settings, `/api/v1/admin/audit${suffix}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function createSystemAccount(settings: ApiSettings, payload: {
|
|
|
|
|
email: string;
|
|
|
|
|
display_name?: string | null;
|
|
|
|
|
password?: string | null;
|
|
|
|
|
password_reset_required?: boolean;
|
|
|
|
|
is_active?: boolean;
|
|
|
|
|
role_ids?: string[];
|
|
|
|
|
memberships?: SystemMembershipDraft[];
|
|
|
|
|
}): Promise<{ account: SystemAccountItem; temporary_password?: string | null }> {
|
|
|
|
|
return apiFetch(settings, "/api/v1/admin/system/accounts", { method: "POST", body: JSON.stringify(payload) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function updateSystemMemberships(settings: ApiSettings, accountId: string, memberships: SystemMembershipDraft[]): Promise<SystemAccountItem> {
|
|
|
|
|
return apiFetch(settings, `/api/v1/admin/system/accounts/${accountId}/memberships`, {
|
|
|
|
|
method: "PUT", body: JSON.stringify({ memberships })
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function fetchSystemSettings(settings: ApiSettings): Promise<SystemSettingsItem> {
|
|
|
|
|
return apiFetch(settings, "/api/v1/admin/system/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?: { enabled: boolean; message?: string | null } | null;
|
|
|
|
|
available_languages?: LanguagePackage[] | null;
|
|
|
|
|
enabled_language_codes?: string[] | null;
|
|
|
|
|
change_request_id?: string | null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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 getPrivacyRetentionPolicy(settings: ApiSettings, scope: PrivacyRetentionPolicyScope, scopeId?: string | null): Promise<PrivacyRetentionPolicyScopeResponse> {
|
|
|
|
|
const params = new URLSearchParams();
|
|
|
|
|
if (scopeId) params.set("scope_id", scopeId);
|
|
|
|
|
const suffix = params.toString() ? `?${params.toString()}` : "";
|
|
|
|
|
return apiFetch(settings, `/api/v1/admin/privacy-retention/policies/${encodeURIComponent(scope)}${suffix}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function explainPrivacyRetentionPolicy(settings: ApiSettings, scope: PrivacyRetentionPolicyScope, scopeId?: string | null): Promise<PrivacyRetentionPolicyExplainResponse> {
|
|
|
|
|
const params = new URLSearchParams();
|
|
|
|
|
if (scopeId) params.set("scope_id", scopeId);
|
|
|
|
|
const suffix = params.toString() ? `?${params.toString()}` : "";
|
|
|
|
|
return apiFetch(settings, `/api/v1/admin/privacy-retention/policies/${encodeURIComponent(scope)}/explain${suffix}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function updatePrivacyRetentionPolicy(settings: ApiSettings, scope: PrivacyRetentionPolicyScope, policy: PrivacyRetentionPolicyPatch, scopeId?: string | null, changeRequestId?: string | null): Promise<PrivacyRetentionPolicyScopeResponse> {
|
|
|
|
|
const params = new URLSearchParams();
|
|
|
|
|
if (scopeId) params.set("scope_id", scopeId);
|
|
|
|
|
const suffix = params.toString() ? `?${params.toString()}` : "";
|
|
|
|
|
return apiFetch(settings, `/api/v1/admin/privacy-retention/policies/${encodeURIComponent(scope)}${suffix}`, { method: "PUT", body: JSON.stringify({ policy, change_request_id: changeRequestId ?? null }) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function runRetentionPolicy(settings: ApiSettings, dryRun = true): Promise<RetentionRunResponse> {
|
|
|
|
|
return apiFetch(settings, "/api/v1/admin/system/retention/run", { method: "POST", body: JSON.stringify({ dry_run: dryRun }) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function fetchConfigurationSafetyCatalog(settings: ApiSettings, includeEnvOnly = false): Promise<ConfigurationSafetyField[]> {
|
|
|
|
|
const suffix = includeEnvOnly ? "?include_env_only=true" : "";
|
|
|
|
|
const response = await apiFetch<{ fields: ConfigurationSafetyField[] }>(settings, `/api/v1/admin/configuration-safety${suffix}`);
|
|
|
|
|
return response.fields;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function planConfigurationChange(settings: ApiSettings, payload: {
|
|
|
|
|
key: string;
|
|
|
|
|
value?: unknown;
|
|
|
|
|
dry_run?: boolean;
|
|
|
|
|
maintenance_mode?: boolean;
|
|
|
|
|
approval_count?: number;
|
|
|
|
|
}): Promise<ConfigurationChangeSafetyPlan> {
|
|
|
|
|
const response = await apiFetch<{ plan: ConfigurationChangeSafetyPlan }>(settings, "/api/v1/admin/configuration-safety/plan", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: JSON.stringify(payload)
|
|
|
|
|
});
|
|
|
|
|
return response.plan;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function fetchConfigurationChanges(settings: ApiSettings): Promise<{ requests: ConfigurationChangeRequest[]; history: ConfigurationChangeRecord[] }> {
|
|
|
|
|
return apiFetch(settings, "/api/v1/admin/configuration-changes");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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" });
|
|
|
|
|
}
|