Add Storage, Users, Secrets, Webhooks, Scheduled tasks, Audit log screens

Full management surfaces for the platform-admin tenant, mirroring the
existing Tenants pattern (DataTable + row actions + create/edit dialogs +
ConfirmDialog for destructive ops, all data-action tagged for the
command bus, useRegisterAdminContext publishing for the assistant).

- Storage (/storage): backends + credentials. Write-only secret fields,
  Validate/Activate/Deactivate/Set-default/Mark-degraded/Maintenance.
- Users (/users): tabs for Users, Invitations, Roles. Per-user View
  drawer with profile, role add/remove, API keys (one-time reveal on
  create), usage + quota.
- Secrets (/secrets): /api/v1/admin/secrets — create/rotate/rollback,
  versions dialog, enable/disable, generate-value helper.
- Webhooks (/webhooks): CRUD, pause/resume, regenerate-secret with
  one-time reveal, send test event, deliveries dialog.
- Scheduled tasks (/scheduled-tasks): cron CRUD, run-now trigger,
  enable/disable, expandable run history.
- Audit log (/activity): replaces the empty stub. Filter by severity,
  resource type, date range; click for full JSON detail.

All endpoints are hand-rolled HTTP because most aren't covered by the
generated OpenAPI typed paths yet — switch to arcadia.typed.* when the
backend wires them into OpenApiSpex.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
jules
2026-05-01 22:50:09 +10:00
parent 45fa130951
commit a907e25a7c
19 changed files with 7439 additions and 25 deletions

View File

@@ -0,0 +1,65 @@
// Arcadia invitations API helpers.
// Backed by /api/v1/invitations.
import type { ArcadiaClient } from "@crema/arcadia-client"
export interface InvitationRole {
id: string
name: string
slug: string
}
export interface InvitationInviter {
id: string
email: string
name: string
}
export interface Invitation {
id: string
email: string
role: InvitationRole
invited_by: InvitationInviter | null
expires_at: string | null
accepted_at: string | null
revoked_at: string | null
revocation_reason: string | null
inserted_at: string
}
export type InvitationStatus = "pending" | "accepted" | "revoked" | "expired"
export function invitationStatus(inv: Invitation): InvitationStatus {
if (inv.accepted_at) return "accepted"
if (inv.revoked_at) return "revoked"
if (inv.expires_at && new Date(inv.expires_at).getTime() < Date.now()) return "expired"
return "pending"
}
export interface InvitationInput {
email: string
role_id: string
}
export async function listInvitations(arcadia: ArcadiaClient): Promise<Invitation[]> {
const res = await arcadia.GET<{ data: Invitation[] }>("/api/v1/invitations")
return res.data
}
export async function createInvitation(
arcadia: ArcadiaClient,
input: InvitationInput,
): Promise<Invitation> {
const res = await arcadia.POST<{ data: Invitation }>("/api/v1/invitations", {
body: { invitation: input },
})
return res.data
}
export async function revokeInvitation(arcadia: ArcadiaClient, id: string): Promise<void> {
await arcadia.DELETE(`/api/v1/invitations/${id}`)
}
export async function resendInvitation(arcadia: ArcadiaClient, id: string): Promise<void> {
await arcadia.POST(`/api/v1/invitations/${id}/resend`)
}