1
0
forked from baron/baron-sso

샘플 adminfront, devfront 구성. ory-keto-migrate 오류 해결

This commit is contained in:
Lectom C Han
2026-01-28 14:03:42 +09:00
parent b7a0397ef9
commit e573f4ca50
21 changed files with 1293 additions and 213 deletions

View File

@@ -1,3 +1,4 @@
import { useMutation, useQuery } from "@tanstack/react-query";
import {
ArrowLeft,
ChevronLeft,
@@ -5,7 +6,8 @@ import {
Filter,
Search,
} from "lucide-react";
import { Link } from "react-router-dom";
import { useState } from "react";
import { Link, useParams } from "react-router-dom";
import { Badge } from "../../components/ui/badge";
import { Button } from "../../components/ui/button";
import {
@@ -24,39 +26,38 @@ import {
TableHeader,
TableRow,
} from "../../components/ui/table";
const rows = [
{
initials: "JD",
name: "John Doe",
email: "john.doe@example.com",
scopes: ["openid", "profile", "email", "offline_access"],
lastAuth: "Oct 24, 2023 14:22",
},
{
initials: "AS",
name: "Alice Smith",
email: "alice.smith@devmail.com",
scopes: ["openid", "profile"],
lastAuth: "Oct 23, 2023 09:15",
},
{
initials: "RJ",
name: "Robert Johnson",
email: "r.johnson@corporate.org",
scopes: ["openid", "profile", "groups"],
lastAuth: "Oct 21, 2023 18:45",
},
{
initials: "ML",
name: "Maria Lopez",
email: "maria.l@provider.net",
scopes: ["openid", "email"],
lastAuth: "Oct 20, 2023 11:30",
},
];
import { fetchClient, fetchConsents, revokeConsent } from "../../lib/devApi";
function ClientConsentsPage() {
const params = useParams();
const clientId = params.id ?? "";
const [subjectInput, setSubjectInput] = useState("");
const [subject, setSubject] = useState("");
const { data: clientData } = useQuery({
queryKey: ["client", clientId],
queryFn: () => fetchClient(clientId),
enabled: clientId.length > 0,
});
const {
data: consentsData,
isLoading,
error,
refetch,
} = useQuery({
queryKey: ["consents", clientId, subject],
queryFn: () => fetchConsents(subject, clientId),
enabled: subject.length > 0,
});
const revokeMutation = useMutation({
mutationFn: (payload: { subject: string }) =>
revokeConsent(payload.subject, clientId),
onSuccess: () => {
refetch();
},
});
const rows = consentsData?.items ?? [];
return (
<div className="space-y-8">
<header className="space-y-4">
@@ -71,7 +72,7 @@ function ClientConsentsPage() {
Clients
</Link>
<span>/</span>
<span>OIDC Relying Party</span>
<span>{clientData?.client?.name || clientId}</span>
<span>/</span>
<span className="text-foreground font-semibold">
User Consent Grants
@@ -79,7 +80,7 @@ function ClientConsentsPage() {
</nav>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon" asChild>
<Link to="/clients/cli_481...8k2">
<Link to={`/clients/${clientId}`}>
<ArrowLeft className="h-4 w-4" />
</Link>
</Button>
@@ -94,12 +95,18 @@ function ClientConsentsPage() {
</div>
</div>
<div className="flex items-center gap-3">
<Badge variant="success">Active</Badge>
<Badge
variant={
clientData?.client?.status === "active" ? "success" : "muted"
}
>
{clientData?.client?.status === "active" ? "Active" : "Inactive"}
</Badge>
</div>
</div>
<div className="flex gap-6 overflow-x-auto border-b border-border pb-3 text-sm font-bold">
<Link
to="/clients/cli_481...8k2"
to={`/clients/${clientId}`}
className="whitespace-nowrap border-b-2 border-transparent text-muted-foreground hover:text-foreground"
>
Overview
@@ -108,7 +115,7 @@ function ClientConsentsPage() {
Consent &amp; Users
</span>
<Link
to="/clients/cli_481...8k2/settings"
to={`/clients/${clientId}/settings`}
className="whitespace-nowrap border-b-2 border-transparent text-muted-foreground hover:text-foreground"
>
Settings
@@ -124,6 +131,8 @@ function ClientConsentsPage() {
<Input
className="pl-10"
placeholder="사용자 ID, 이름, 이메일로 검색"
value={subjectInput}
onChange={(e) => setSubjectInput(e.target.value)}
/>
</div>
<div className="flex items-center gap-2">
@@ -142,12 +151,28 @@ function ClientConsentsPage() {
<Filter className="h-4 w-4" />
Advanced Filters
</Button>
<Button
className="shadow-sm shadow-primary/30"
onClick={() => setSubject(subjectInput.trim())}
>
</Button>
<Button className="shadow-sm shadow-primary/30">Export CSV</Button>
</div>
</CardContent>
</Card>
<Card className="glass-panel">
{error && (
<CardContent className="text-sm text-red-500">
Error loading consents: {(error as Error).message}
</CardContent>
)}
{isLoading && (
<CardContent className="text-sm text-muted-foreground">
Loading consents...
</CardContent>
)}
<Table>
<TableHeader>
<TableRow>
@@ -160,16 +185,18 @@ function ClientConsentsPage() {
</TableHeader>
<TableBody>
{rows.map((row) => (
<TableRow key={row.email}>
<TableRow key={`${row.subject}-${row.clientId}`}>
<TableCell>
<div className="flex items-center gap-3">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-primary/10 text-xs font-bold text-primary">
{row.initials}
{row.subject.slice(0, 2).toUpperCase()}
</div>
<div className="flex flex-col">
<span className="text-sm font-semibold">{row.name}</span>
<span className="text-sm font-semibold">
{row.clientName || "Subject"}
</span>
<span className="text-xs text-muted-foreground">
{row.email}
{row.subject}
</span>
</div>
</div>
@@ -179,7 +206,7 @@ function ClientConsentsPage() {
</TableCell>
<TableCell>
<div className="flex flex-wrap gap-1">
{row.scopes.map((scope) => (
{row.grantedScopes.map((scope) => (
<Badge
key={scope}
variant="muted"
@@ -191,10 +218,16 @@ function ClientConsentsPage() {
</div>
</TableCell>
<TableCell className="text-sm text-muted-foreground">
{row.lastAuth}
{row.authenticatedAt || "-"}
</TableCell>
<TableCell className="text-right">
<Button variant="ghost" className="text-destructive">
<Button
variant="ghost"
className="text-destructive"
onClick={() =>
revokeMutation.mutate({ subject: row.subject })
}
>
Revoke
</Button>
</TableCell>