chore: updates in UI due to migration to OAS3.1

Signed-off-by: allanice001 <allanice001@gmail.com>
This commit is contained in:
allanice001
2025-11-17 19:56:16 +00:00
parent 0f0edf1007
commit 07974c1359
20 changed files with 163 additions and 290 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -12,7 +12,7 @@ export async function logoutEverywhere(): Promise<void> {
try { try {
const body: DtoLogoutRequest = { refresh_token: tokens.refresh_token } as DtoLogoutRequest const body: DtoLogoutRequest = { refresh_token: tokens.refresh_token } as DtoLogoutRequest
await makeAuthApi().logout({ body }) await makeAuthApi().logout({ dtoLogoutRequest: body })
} catch (err) { } catch (err) {
console.warn("Logout API failed; clearing local state anyway", err) console.warn("Logout API failed; clearing local state anyway", err)
} finally { } finally {

View File

@@ -1,4 +1,5 @@
; ;
// src/pages/ClustersPage.tsx // src/pages/ClustersPage.tsx
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
@@ -27,6 +28,36 @@ import { Label } from "@/components/ui/label.tsx";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select.tsx"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select.tsx";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table.tsx"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table.tsx";
import { Textarea } from "@/components/ui/textarea.tsx"; import { Textarea } from "@/components/ui/textarea.tsx";
;
@@ -1268,6 +1299,8 @@ export const ClustersPage = () => {
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
<pre>{JSON.stringify(clustersQ.data, null, 2)}</pre>
</div> </div>
) )
} }

View File

@@ -1,8 +1,14 @@
import { useEffect, useState, type FC } from "react" import { type FC, useEffect, useState } from "react"
import { archerAdminApi } from "@/api/archer_admin" import { archerAdminApi } from "@/api/archer_admin"
import type { AdminListArcherJobsRequest } from "@/sdk"
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
import { Loader2, Plus, RefreshCw, Search, X } from "lucide-react" import { Loader2, Plus, RefreshCw, Search, X } from "lucide-react"
import {
type AdminListArcherJobsRequest,
AdminListArcherJobsStatusEnum,
type DtoJob,
type DtoPageJob,
type DtoQueueInfo,
} from "@/sdk"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge"
@@ -19,67 +25,20 @@ import {
} from "@/components/ui/dialog" } from "@/components/ui/dialog"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label" import { Label } from "@/components/ui/label"
import { import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"
Select, import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Textarea } from "@/components/ui/textarea" import { Textarea } from "@/components/ui/textarea"
// Types (align with generated client camelCase) type JobStatus = AdminListArcherJobsStatusEnum
type JobStatus =
| "queued"
| "running"
| "succeeded"
| "failed"
| "canceled"
| "retrying"
| "scheduled"
type DtoJob = {
id: string
type: string
queue: string
status: JobStatus
attempts: number
maxAttempts?: number
createdAt: string
updatedAt?: string
lastError?: string | null
runAt?: string | null
payload?: unknown
}
type DtoPageJob = {
items: DtoJob[]
total: number
page: number
pageSize: number
}
type QueueInfo = {
name: string
pending: number
running: number
failed: number
scheduled: number
}
const STATUS: JobStatus[] = [ const STATUS: JobStatus[] = [
"queued", AdminListArcherJobsStatusEnum.queued,
"running", AdminListArcherJobsStatusEnum.running,
"succeeded", AdminListArcherJobsStatusEnum.succeeded,
"failed", AdminListArcherJobsStatusEnum.failed,
"canceled", AdminListArcherJobsStatusEnum.canceled,
"retrying", AdminListArcherJobsStatusEnum.retrying,
"scheduled", AdminListArcherJobsStatusEnum.scheduled,
] ]
const statusClass: Record<JobStatus, string> = { const statusClass: Record<JobStatus, string> = {
@@ -95,7 +54,10 @@ const statusClass: Record<JobStatus, string> = {
function fmt(dt?: string | null) { function fmt(dt?: string | null) {
if (!dt) return "—" if (!dt) return "—"
const d = new Date(dt) const d = new Date(dt)
return new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short" }).format(d) return new Intl.DateTimeFormat(undefined, {
dateStyle: "medium",
timeStyle: "short",
}).format(d)
} }
// Small debounce hook for search input // Small debounce hook for search input
@@ -126,12 +88,12 @@ export const JobsPage: FC = () => {
queryKey: key, queryKey: key,
queryFn: () => queryFn: () =>
archerAdminApi.listJobs({ archerAdminApi.listJobs({
status: status, status: status ? (status as JobStatus) : undefined,
queue: queue || undefined, queue: queue || undefined,
q: debouncedQ || undefined, q: debouncedQ || undefined,
page, page,
pageSize, pageSize,
} as AdminListArcherJobsRequest) as Promise<DtoPageJob>, } as AdminListArcherJobsRequest),
placeholderData: (prev) => prev, placeholderData: (prev) => prev,
staleTime: 10_000, staleTime: 10_000,
}) })
@@ -139,7 +101,7 @@ export const JobsPage: FC = () => {
// Queues summary (optional header) // Queues summary (optional header)
const queuesQ = useQuery({ const queuesQ = useQuery({
queryKey: ["archer", "queues"], queryKey: ["archer", "queues"],
queryFn: () => archerAdminApi.listQueues() as Promise<QueueInfo[]>, queryFn: () => archerAdminApi.listQueues() as Promise<DtoQueueInfo[]>,
staleTime: 30_000, staleTime: 30_000,
}) })
@@ -153,10 +115,12 @@ export const JobsPage: FC = () => {
}) => archerAdminApi.enqueue(body), }) => archerAdminApi.enqueue(body),
onSuccess: () => qc.invalidateQueries({ queryKey: ["archer", "jobs"] }), onSuccess: () => qc.invalidateQueries({ queryKey: ["archer", "jobs"] }),
}) })
const retryM = useMutation({ const retryM = useMutation({
mutationFn: (id: string) => archerAdminApi.retryJob(id), mutationFn: (id: string) => archerAdminApi.retryJob(id),
onSuccess: () => qc.invalidateQueries({ queryKey: ["archer", "jobs"] }), onSuccess: () => qc.invalidateQueries({ queryKey: ["archer", "jobs"] }),
}) })
const cancelM = useMutation({ const cancelM = useMutation({
mutationFn: (id: string) => archerAdminApi.cancelJob(id), mutationFn: (id: string) => archerAdminApi.cancelJob(id),
onSuccess: () => qc.invalidateQueries({ queryKey: ["archer", "jobs"] }), onSuccess: () => qc.invalidateQueries({ queryKey: ["archer", "jobs"] }),
@@ -164,8 +128,10 @@ export const JobsPage: FC = () => {
const busy = jobsQ.isFetching const busy = jobsQ.isFetching
const data = jobsQ.data as DtoPageJob const data = jobsQ.data as DtoPageJob | undefined
const totalPages = data ? Math.max(1, Math.ceil(data.total / data.pageSize)) : 1 const items: DtoJob[] = data?.items ?? []
const total = data?.total ?? 0
const totalPages = Math.max(1, Math.ceil(total / pageSize))
return ( return (
<div className="container mx-auto space-y-6 p-6"> <div className="container mx-auto space-y-6 p-6">
@@ -204,10 +170,10 @@ export const JobsPage: FC = () => {
<CardTitle className="text-base">{q.name}</CardTitle> <CardTitle className="text-base">{q.name}</CardTitle>
</CardHeader> </CardHeader>
<CardContent className="grid grid-cols-2 gap-2 text-sm"> <CardContent className="grid grid-cols-2 gap-2 text-sm">
<Metric label="Pending" value={q.pending} /> <Metric label="Pending" value={q.pending ?? 0} />
<Metric label="Running" value={q.running} /> <Metric label="Running" value={q.running ?? 0} />
<Metric label="Failed" value={q.failed} /> <Metric label="Failed" value={q.failed ?? 0} />
<Metric label="Scheduled" value={q.scheduled} /> <Metric label="Scheduled" value={q.scheduled ?? 0} />
</CardContent> </CardContent>
</Card> </Card>
))} ))}
@@ -321,67 +287,85 @@ export const JobsPage: FC = () => {
</TableCell> </TableCell>
</TableRow> </TableRow>
)} )}
{!jobsQ.isLoading && data && data.items.length === 0 && ( {!jobsQ.isLoading && items.length === 0 && (
<TableRow> <TableRow>
<TableCell colSpan={7} className="text-muted-foreground py-8 text-center"> <TableCell colSpan={7} className="text-muted-foreground py-8 text-center">
No jobs match your filters. No jobs match your filters.
</TableCell> </TableCell>
</TableRow> </TableRow>
)} )}
{data?.items.map((j) => ( {items.map((j) => {
<TableRow key={j.id}> const jobStatus: JobStatus =
<TableCell> (j.status as JobStatus | undefined) ??
<code className="text-xs">{j.id}</code> AdminListArcherJobsStatusEnum.queued
</TableCell>
<TableCell> return (
<Badge variant="secondary">{j.queue}</Badge> <TableRow key={j.id}>
</TableCell> <TableCell>
<TableCell> <code className="text-xs">{j.id}</code>
<span className={cn("rounded-md px-2 py-0.5 text-xs", statusClass[j.status])}> </TableCell>
{j.status} <TableCell>
</span> <Badge variant="secondary">{j.queue}</Badge>
</TableCell> </TableCell>
<TableCell> <TableCell>
{j.maxAttempts ? `${j.attempts}/${j.maxAttempts}` : j.attempts} <span
</TableCell> className={cn(
<TableCell>{fmt(j.runAt)}</TableCell> "rounded-md px-2 py-0.5 text-xs",
<TableCell>{fmt(j.updatedAt ?? j.createdAt)}</TableCell> statusClass[jobStatus],
<TableCell className="text-right"> )}
<div className="flex justify-end gap-2"> >
{(j.status === "failed" || j.status === "canceled") && ( {jobStatus}
<Button </span>
size="sm" </TableCell>
variant="outline" <TableCell>
disabled={retryM.isPending} {j.max_attempts ? `${j.attempts}/${j.max_attempts}` : j.attempts}
onClick={() => retryM.mutate(j.id)} </TableCell>
> <TableCell>{fmt(j.run_at)}</TableCell>
Retry <TableCell>{fmt(j.updated_at ?? j.created_at)}</TableCell>
</Button> <TableCell className="text-right">
)} <div className="flex justify-end gap-2">
{(j.status === "queued" || {(jobStatus === AdminListArcherJobsStatusEnum.failed ||
j.status === "running" || jobStatus === AdminListArcherJobsStatusEnum.canceled) && (
j.status === "scheduled") && ( <Button
<Button size="sm"
size="sm" variant="outline"
variant="outline" disabled={retryM.isPending || !j.id}
disabled={cancelM.isPending} onClick={() => {
onClick={() => cancelM.mutate(j.id)} if (!j.id) return
> retryM.mutate(j.id)
Cancel }}
</Button> >
)} Retry
<DetailsButton job={j} /> </Button>
</div> )}
</TableCell> {(jobStatus === AdminListArcherJobsStatusEnum.queued ||
</TableRow> jobStatus === AdminListArcherJobsStatusEnum.running ||
))} jobStatus === AdminListArcherJobsStatusEnum.scheduled) && (
<Button
size="sm"
variant="outline"
disabled={cancelM.isPending || !j.id}
onClick={() => {
if (!j.id) return
cancelM.mutate(j.id)
}}
>
Cancel
</Button>
)}
<DetailsButton job={j} />
</div>
</TableCell>
</TableRow>
)
})}
</TableBody> </TableBody>
</Table> </Table>
{/* Pagination */} {/* Pagination */}
<div className="flex items-center justify-between border-t p-3 text-sm"> <div className="flex items-center justify-between border-t p-3 text-sm">
<div> <div>
Page {page} of {totalPages} {data?.total ?? 0} total Page {page} of {totalPages} {total} total
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<Button <Button
@@ -428,13 +412,15 @@ function DetailsButton({ job }: { job: DtoJob }) {
<DialogTitle>Job {job.id}</DialogTitle> <DialogTitle>Job {job.id}</DialogTitle>
</DialogHeader> </DialogHeader>
<div className="grid gap-3"> <div className="grid gap-3">
{job.lastError && ( {job.last_error && (
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle className="text-sm">Last error</CardTitle> <CardTitle className="text-sm">Last error</CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<pre className="overflow-auto text-xs whitespace-pre-wrap">{job.lastError}</pre> <pre className="overflow-auto text-xs whitespace-pre-wrap">
{job.last_error}
</pre>
</CardContent> </CardContent>
</Card> </Card>
)} )}
@@ -460,9 +446,9 @@ function DetailsButton({ job }: { job: DtoJob }) {
} }
function EnqueueDialog({ function EnqueueDialog({
onSubmit, onSubmit,
submitting, submitting,
}: { }: {
onSubmit: (body: { onSubmit: (body: {
queue: string queue: string
type: string type: string
@@ -527,7 +513,11 @@ function EnqueueDialog({
</div> </div>
<div className="grid gap-2"> <div className="grid gap-2">
<Label>Run at (optional)</Label> <Label>Run at (optional)</Label>
<Input type="datetime-local" value={runAt} onChange={(e) => setRunAt(e.target.value)} /> <Input
type="datetime-local"
value={runAt}
onChange={(e) => setRunAt(e.target.value)}
/>
</div> </div>
</div> </div>
<DialogFooter> <DialogFooter>

View File

@@ -31,32 +31,17 @@ import {
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from "@/components/ui/dialog.tsx" } from "@/components/ui/dialog.tsx"
import { import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form.tsx"
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form.tsx"
import { Input } from "@/components/ui/input.tsx" import { Input } from "@/components/ui/input.tsx"
import { Label } from "@/components/ui/label.tsx" import { Label } from "@/components/ui/label.tsx"
import { Separator } from "@/components/ui/separator.tsx" import { Separator } from "@/components/ui/separator.tsx"
import { import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table.tsx"
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table.tsx"
const orgsApi = makeOrgsApi() const orgsApi = makeOrgsApi()
const orgApi = { const orgApi = {
create: (body: { name: string; domain?: string }) => create: (body: { name: string; domain?: string }) =>
withRefresh(async () => orgsApi.createOrg({ body })), // POST /orgs withRefresh(async () => orgsApi.createOrg({ handlersOrgCreateReq: body })), // POST /orgs
} }
const profileSchema = z.object({ const profileSchema = z.object({

View File

@@ -10,30 +10,10 @@ import { z } from "zod"
import { Button } from "@/components/ui/button.tsx" import { Button } from "@/components/ui/button.tsx"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx"
import { import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog.tsx"
Dialog, import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form.tsx"
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog.tsx"
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form.tsx"
import { Input } from "@/components/ui/input.tsx" import { Input } from "@/components/ui/input.tsx"
import { import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table.tsx"
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table.tsx"
const createSchema = z.object({ const createSchema = z.object({
name: z.string(), name: z.string(),
@@ -66,7 +46,7 @@ export const OrgApiKeys = () => {
} | null>(null) } | null>(null)
const createMut = useMutation({ const createMut = useMutation({
mutationFn: (v: CreateValues) => api.createOrgKey({ id: orgId!, body: v }), mutationFn: (v: CreateValues) => api.createOrgKey({ id: orgId!, handlersOrgKeyCreateReq: v }),
onSuccess: (resp) => { onSuccess: (resp) => {
void qc.invalidateQueries({ queryKey: ["org:keys", orgId] }) void qc.invalidateQueries({ queryKey: ["org:keys", orgId] })
setShowSecret({ key: resp.org_key, secret: resp.org_secret }) setShowSecret({ key: resp.org_key, secret: resp.org_secret })

View File

@@ -11,30 +11,10 @@ import { z } from "zod"
import { Button } from "@/components/ui/button.tsx" import { Button } from "@/components/ui/button.tsx"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx"
import { import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form.tsx"
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form.tsx"
import { Input } from "@/components/ui/input.tsx" import { Input } from "@/components/ui/input.tsx"
import { import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select.tsx"
Select, import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table.tsx"
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select.tsx"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table.tsx"
const addSchema = z.object({ const addSchema = z.object({
user_id: z.uuid("Invalid UUID"), user_id: z.uuid("Invalid UUID"),
@@ -68,7 +48,7 @@ export const OrgMembers = () => {
}) })
const addMut = useMutation({ const addMut = useMutation({
mutationFn: (v: AddValues) => api.addOrUpdateMember({ id: orgId!, body: v }), mutationFn: (v: AddValues) => api.addOrUpdateMember({ id: orgId!, handlersMemberUpsertReq: v }),
onSuccess: () => { onSuccess: () => {
toast.success("Member added/updated") toast.success("Member added/updated")
void qc.invalidateQueries({ queryKey: ["org:members", orgId] }) void qc.invalidateQueries({ queryKey: ["org:members", orgId] })
@@ -88,7 +68,7 @@ export const OrgMembers = () => {
const roleMut = useMutation({ const roleMut = useMutation({
mutationFn: ({ userId, role }: { userId: string; role: "owner" | "admin" | "member" }) => mutationFn: ({ userId, role }: { userId: string; role: "owner" | "admin" | "member" }) =>
api.addOrUpdateMember({ id: orgId!, body: { user_id: userId, role } }), api.addOrUpdateMember({ id: orgId!, handlersMemberUpsertReq: { user_id: userId, role } }),
onMutate: async ({ userId, role }) => { onMutate: async ({ userId, role }) => {
setUpdatingId(userId) setUpdatingId(userId)
// cancel queries and snapshot previous // cancel queries and snapshot previous

View File

@@ -10,14 +10,7 @@ import { z } from "zod"
import { Button } from "@/components/ui/button.tsx" import { Button } from "@/components/ui/button.tsx"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx"
import { import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form.tsx"
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form.tsx"
import { Input } from "@/components/ui/input.tsx" import { Input } from "@/components/ui/input.tsx"
/* /*
@@ -80,7 +73,7 @@ export const OrgSettings = () => {
}, [q.data, form]) }, [q.data, form])
const updateMut = useMutation({ const updateMut = useMutation({
mutationFn: (v: Partial<Values>) => api.updateOrg({ id: orgId!, body: v }), mutationFn: (v: Partial<Values>) => api.updateOrg({ id: orgId!, handlersOrgUpdateReq: v }),
onSuccess: () => { onSuccess: () => {
void qc.invalidateQueries({ queryKey: ["org", orgId] }) void qc.invalidateQueries({ queryKey: ["org", orgId] })
toast.success("Organization updated") toast.success("Organization updated")

View File

@@ -28,5 +28,5 @@
"@/*": ["./src/*"] "@/*": ["./src/*"]
} }
}, },
"include": ["src", "src/types"] "include": ["src"]
} }

View File

@@ -11,5 +11,4 @@
}, },
"jsx": "react-jsx" "jsx": "react-jsx"
}, },
"include": ["src", "src/types"]
} }