import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import type { AxiosError } from "axios"; import { Activity, BookOpenText, Copy, Laptop, Plus, Search, ServerCog, ShieldHalf, } from "lucide-react"; import { Link, useNavigate } from "react-router-dom"; import { Avatar, AvatarFallback, AvatarImage, } from "../../components/ui/avatar"; import { Badge } from "../../components/ui/badge"; import { Button } from "../../components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "../../components/ui/card"; import { Input } from "../../components/ui/input"; import { Separator } from "../../components/ui/separator"; import { Switch } from "../../components/ui/switch"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "../../components/ui/table"; import { deleteClient, fetchClients, updateClientStatus, } from "../../lib/devApi"; import { cn } from "../../lib/utils"; import { CopyButton } from "../../components/ui/copy-button"; import { toast } from "../../components/ui/use-toast"; function ClientsPage() { const navigate = useNavigate(); const queryClient = useQueryClient(); const { data, isLoading, error } = useQuery({ queryKey: ["clients"], queryFn: fetchClients, }); const updateStatusMutation = useMutation({ mutationFn: (payload: { id: string; status: "active" | "inactive" }) => updateClientStatus(payload.id, payload.status), onSuccess: (_, variables) => { const statusText = variables.status === "active" ? "활성화" : "비활성화"; toast(`클라이언트가 ${statusText}되었습니다.`); queryClient.invalidateQueries({ queryKey: ["clients"] }); }, onError: (error: AxiosError<{ error?: string }>) => { const errMsg = error.response?.data?.error ?? error.message ?? "Failed to update client status"; toast(errMsg, "error"); }, }); const deleteMutation = useMutation({ mutationFn: (clientId: string) => deleteClient(clientId), onSuccess: () => queryClient.invalidateQueries({ queryKey: ["clients"] }), }); const clients = data?.items || []; const totalClients = clients.length; // TODO: Add real stats for active sessions and auth failures const stats = [ { label: "총 클라이언트", value: totalClients.toString(), delta: "Realtime", tone: "up" as const, }, { label: "활성 세션", value: "-", delta: "Not impl", tone: "stable" as const, }, { label: "인증 실패 (24h)", value: "0", delta: "Stable", tone: "stable" as const, }, ]; if (isLoading) { return
Loading clients...
; } if (error) { const errMsg = (error as AxiosError<{ error?: string }>).response?.data?.error ?? (error as Error).message; return (
Error loading clients: {errMsg}
); } return (

RP registry

Relying Parties OIDC 클라이언트, 인증 방식, 리다이렉트 URI, 비밀키 재발행을 감사 로그와 함께 관리합니다.
테넌트: 선택됨 관리자 세션
{stats.map((item) => ( {item.label}
{item.value} {item.delta}
))}
클라이언트 목록
애플리케이션 Client ID 유형 상태 생성일 액션 {clients.map((client) => (
{client.type === "confidential" ? ( ) : ( )}

{client.name || "Untitled"}

Tenant-scoped

{client.id} toast("클라이언트 ID가 복사되었습니다.")} />
{client.type === "confidential" ? "기밀(Confidential)" : "Public"}
updateStatusMutation.mutate({ id: client.id, status: checked ? "active" : "inactive", }) } /> {client.status === "active" ? "활성" : "비활성"}
{client.createdAt ? new Date(client.createdAt).toLocaleDateString() : "-"}
))}
Showing {clients.length} of {totalClients} clients
Need help with OIDC configuration? Developer guides for Confidential/Public clients, redirect URIs, and auth methods.

Docs & Examples

Includes PKCE, client_secret_basic, redirect URI validation tips.

Owner Tenant admin on-call
AR

AI Admin Bot

admin@brsw.kr

Role: Tenant Admin Scope: TENANT-12
); } export default ClientsPage;