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 {
const body: DtoLogoutRequest = { refresh_token: tokens.refresh_token } as DtoLogoutRequest
await makeAuthApi().logout({ body })
await makeAuthApi().logout({ dtoLogoutRequest: body })
} catch (err) {
console.warn("Logout API failed; clearing local state anyway", err)
} finally {

View File

@@ -1,4 +1,5 @@
;
// src/pages/ClustersPage.tsx
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 { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table.tsx";
import { Textarea } from "@/components/ui/textarea.tsx";
;
@@ -1268,6 +1299,8 @@ export const ClustersPage = () => {
</DialogFooter>
</DialogContent>
</Dialog>
<pre>{JSON.stringify(clustersQ.data, null, 2)}</pre>
</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 type { AdminListArcherJobsRequest } from "@/sdk"
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
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 { Badge } from "@/components/ui/badge"
@@ -19,67 +25,20 @@ import {
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Select, 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"
// Types (align with generated client camelCase)
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
}
type JobStatus = AdminListArcherJobsStatusEnum
const STATUS: JobStatus[] = [
"queued",
"running",
"succeeded",
"failed",
"canceled",
"retrying",
"scheduled",
AdminListArcherJobsStatusEnum.queued,
AdminListArcherJobsStatusEnum.running,
AdminListArcherJobsStatusEnum.succeeded,
AdminListArcherJobsStatusEnum.failed,
AdminListArcherJobsStatusEnum.canceled,
AdminListArcherJobsStatusEnum.retrying,
AdminListArcherJobsStatusEnum.scheduled,
]
const statusClass: Record<JobStatus, string> = {
@@ -95,7 +54,10 @@ const statusClass: Record<JobStatus, string> = {
function fmt(dt?: string | null) {
if (!dt) return "—"
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
@@ -126,12 +88,12 @@ export const JobsPage: FC = () => {
queryKey: key,
queryFn: () =>
archerAdminApi.listJobs({
status: status,
status: status ? (status as JobStatus) : undefined,
queue: queue || undefined,
q: debouncedQ || undefined,
page,
pageSize,
} as AdminListArcherJobsRequest) as Promise<DtoPageJob>,
} as AdminListArcherJobsRequest),
placeholderData: (prev) => prev,
staleTime: 10_000,
})
@@ -139,7 +101,7 @@ export const JobsPage: FC = () => {
// Queues summary (optional header)
const queuesQ = useQuery({
queryKey: ["archer", "queues"],
queryFn: () => archerAdminApi.listQueues() as Promise<QueueInfo[]>,
queryFn: () => archerAdminApi.listQueues() as Promise<DtoQueueInfo[]>,
staleTime: 30_000,
})
@@ -153,10 +115,12 @@ export const JobsPage: FC = () => {
}) => archerAdminApi.enqueue(body),
onSuccess: () => qc.invalidateQueries({ queryKey: ["archer", "jobs"] }),
})
const retryM = useMutation({
mutationFn: (id: string) => archerAdminApi.retryJob(id),
onSuccess: () => qc.invalidateQueries({ queryKey: ["archer", "jobs"] }),
})
const cancelM = useMutation({
mutationFn: (id: string) => archerAdminApi.cancelJob(id),
onSuccess: () => qc.invalidateQueries({ queryKey: ["archer", "jobs"] }),
@@ -164,8 +128,10 @@ export const JobsPage: FC = () => {
const busy = jobsQ.isFetching
const data = jobsQ.data as DtoPageJob
const totalPages = data ? Math.max(1, Math.ceil(data.total / data.pageSize)) : 1
const data = jobsQ.data as DtoPageJob | undefined
const items: DtoJob[] = data?.items ?? []
const total = data?.total ?? 0
const totalPages = Math.max(1, Math.ceil(total / pageSize))
return (
<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>
</CardHeader>
<CardContent className="grid grid-cols-2 gap-2 text-sm">
<Metric label="Pending" value={q.pending} />
<Metric label="Running" value={q.running} />
<Metric label="Failed" value={q.failed} />
<Metric label="Scheduled" value={q.scheduled} />
<Metric label="Pending" value={q.pending ?? 0} />
<Metric label="Running" value={q.running ?? 0} />
<Metric label="Failed" value={q.failed ?? 0} />
<Metric label="Scheduled" value={q.scheduled ?? 0} />
</CardContent>
</Card>
))}
@@ -321,14 +287,19 @@ export const JobsPage: FC = () => {
</TableCell>
</TableRow>
)}
{!jobsQ.isLoading && data && data.items.length === 0 && (
{!jobsQ.isLoading && items.length === 0 && (
<TableRow>
<TableCell colSpan={7} className="text-muted-foreground py-8 text-center">
No jobs match your filters.
</TableCell>
</TableRow>
)}
{data?.items.map((j) => (
{items.map((j) => {
const jobStatus: JobStatus =
(j.status as JobStatus | undefined) ??
AdminListArcherJobsStatusEnum.queued
return (
<TableRow key={j.id}>
<TableCell>
<code className="text-xs">{j.id}</code>
@@ -337,35 +308,47 @@ export const JobsPage: FC = () => {
<Badge variant="secondary">{j.queue}</Badge>
</TableCell>
<TableCell>
<span className={cn("rounded-md px-2 py-0.5 text-xs", statusClass[j.status])}>
{j.status}
<span
className={cn(
"rounded-md px-2 py-0.5 text-xs",
statusClass[jobStatus],
)}
>
{jobStatus}
</span>
</TableCell>
<TableCell>
{j.maxAttempts ? `${j.attempts}/${j.maxAttempts}` : j.attempts}
{j.max_attempts ? `${j.attempts}/${j.max_attempts}` : j.attempts}
</TableCell>
<TableCell>{fmt(j.runAt)}</TableCell>
<TableCell>{fmt(j.updatedAt ?? j.createdAt)}</TableCell>
<TableCell>{fmt(j.run_at)}</TableCell>
<TableCell>{fmt(j.updated_at ?? j.created_at)}</TableCell>
<TableCell className="text-right">
<div className="flex justify-end gap-2">
{(j.status === "failed" || j.status === "canceled") && (
{(jobStatus === AdminListArcherJobsStatusEnum.failed ||
jobStatus === AdminListArcherJobsStatusEnum.canceled) && (
<Button
size="sm"
variant="outline"
disabled={retryM.isPending}
onClick={() => retryM.mutate(j.id)}
disabled={retryM.isPending || !j.id}
onClick={() => {
if (!j.id) return
retryM.mutate(j.id)
}}
>
Retry
</Button>
)}
{(j.status === "queued" ||
j.status === "running" ||
j.status === "scheduled") && (
{(jobStatus === AdminListArcherJobsStatusEnum.queued ||
jobStatus === AdminListArcherJobsStatusEnum.running ||
jobStatus === AdminListArcherJobsStatusEnum.scheduled) && (
<Button
size="sm"
variant="outline"
disabled={cancelM.isPending}
onClick={() => cancelM.mutate(j.id)}
disabled={cancelM.isPending || !j.id}
onClick={() => {
if (!j.id) return
cancelM.mutate(j.id)
}}
>
Cancel
</Button>
@@ -374,14 +357,15 @@ export const JobsPage: FC = () => {
</div>
</TableCell>
</TableRow>
))}
)
})}
</TableBody>
</Table>
{/* Pagination */}
<div className="flex items-center justify-between border-t p-3 text-sm">
<div>
Page {page} of {totalPages} {data?.total ?? 0} total
Page {page} of {totalPages} {total} total
</div>
<div className="flex gap-2">
<Button
@@ -428,13 +412,15 @@ function DetailsButton({ job }: { job: DtoJob }) {
<DialogTitle>Job {job.id}</DialogTitle>
</DialogHeader>
<div className="grid gap-3">
{job.lastError && (
{job.last_error && (
<Card>
<CardHeader>
<CardTitle className="text-sm">Last error</CardTitle>
</CardHeader>
<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>
</Card>
)}
@@ -462,7 +448,7 @@ function DetailsButton({ job }: { job: DtoJob }) {
function EnqueueDialog({
onSubmit,
submitting,
}: {
}: {
onSubmit: (body: {
queue: string
type: string
@@ -527,7 +513,11 @@ function EnqueueDialog({
</div>
<div className="grid gap-2">
<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>
<DialogFooter>

View File

@@ -31,32 +31,17 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog.tsx"
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form.tsx"
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form.tsx"
import { Input } from "@/components/ui/input.tsx"
import { Label } from "@/components/ui/label.tsx"
import { Separator } from "@/components/ui/separator.tsx"
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table.tsx"
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table.tsx"
const orgsApi = makeOrgsApi()
const orgApi = {
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({

View File

@@ -10,30 +10,10 @@ import { z } from "zod"
import { Button } from "@/components/ui/button.tsx"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx"
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog.tsx"
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form.tsx"
import { Dialog, 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 {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table.tsx"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table.tsx"
const createSchema = z.object({
name: z.string(),
@@ -66,7 +46,7 @@ export const OrgApiKeys = () => {
} | null>(null)
const createMut = useMutation({
mutationFn: (v: CreateValues) => api.createOrgKey({ id: orgId!, body: v }),
mutationFn: (v: CreateValues) => api.createOrgKey({ id: orgId!, handlersOrgKeyCreateReq: v }),
onSuccess: (resp) => {
void qc.invalidateQueries({ queryKey: ["org:keys", orgId] })
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 { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx"
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form.tsx"
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form.tsx"
import { Input } from "@/components/ui/input.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 { Select, 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({
user_id: z.uuid("Invalid UUID"),
@@ -68,7 +48,7 @@ export const OrgMembers = () => {
})
const addMut = useMutation({
mutationFn: (v: AddValues) => api.addOrUpdateMember({ id: orgId!, body: v }),
mutationFn: (v: AddValues) => api.addOrUpdateMember({ id: orgId!, handlersMemberUpsertReq: v }),
onSuccess: () => {
toast.success("Member added/updated")
void qc.invalidateQueries({ queryKey: ["org:members", orgId] })
@@ -88,7 +68,7 @@ export const OrgMembers = () => {
const roleMut = useMutation({
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 }) => {
setUpdatingId(userId)
// cancel queries and snapshot previous

View File

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

View File

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

View File

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