103 lines
3.4 KiB
TypeScript
103 lines
3.4 KiB
TypeScript
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "@/components/ui/table"
|
|
import { Ban } from "lucide-react"
|
|
import { securityService, type ApiKey } from "@/services"
|
|
import { toast } from "sonner"
|
|
import { format } from "date-fns"
|
|
import type { ApiError } from "@/types/error.types"
|
|
|
|
export default function ApiKeysPage() {
|
|
const queryClient = useQueryClient()
|
|
|
|
const { data: apiKeys, isLoading } = useQuery({
|
|
queryKey: ['admin', 'security', 'api-keys'],
|
|
queryFn: () => securityService.getAllApiKeys(),
|
|
})
|
|
|
|
const revokeMutation = useMutation({
|
|
mutationFn: (id: string) => securityService.revokeApiKey(id),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['admin', 'security', 'api-keys'] })
|
|
toast.success("API key revoked successfully")
|
|
},
|
|
onError: (error) => {
|
|
const apiError = error as ApiError
|
|
toast.error(apiError.response?.data?.message || "Failed to revoke API key")
|
|
},
|
|
})
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<h2 className="text-3xl font-bold">API Keys</h2>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>All API Keys</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{isLoading ? (
|
|
<div className="text-center py-8">Loading API keys...</div>
|
|
) : (
|
|
<>
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Name</TableHead>
|
|
<TableHead>User</TableHead>
|
|
<TableHead>Last Used</TableHead>
|
|
<TableHead>Status</TableHead>
|
|
<TableHead>Actions</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{apiKeys?.map((key: ApiKey) => (
|
|
<TableRow key={key.id}>
|
|
<TableCell className="font-medium">{key.name}</TableCell>
|
|
<TableCell>{key.userId || 'N/A'}</TableCell>
|
|
<TableCell>
|
|
{key.lastUsed ? format(new Date(key.lastUsed), 'MMM dd, yyyy') : 'Never'}
|
|
</TableCell>
|
|
<TableCell>
|
|
<Badge variant={key.isActive ? 'default' : 'destructive'}>
|
|
{key.isActive ? 'Active' : 'Revoked'}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell>
|
|
{key.isActive && (
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
onClick={() => revokeMutation.mutate(key.id)}
|
|
>
|
|
<Ban className="w-4 h-4" />
|
|
</Button>
|
|
)}
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
{apiKeys?.length === 0 && (
|
|
<div className="text-center py-8 text-muted-foreground">
|
|
No API keys found
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
)
|
|
}
|
|
|