diff --git a/devfront/src/features/clients/ClientConsentsPage.tsx b/devfront/src/features/clients/ClientConsentsPage.tsx index e82277ca..113855d7 100644 --- a/devfront/src/features/clients/ClientConsentsPage.tsx +++ b/devfront/src/features/clients/ClientConsentsPage.tsx @@ -33,6 +33,8 @@ function ClientConsentsPage() { const clientId = params.id ?? ""; const [subjectInput, setSubjectInput] = useState(""); const [subject, setSubject] = useState(""); + const [statusFilter, setStatusFilter] = useState("all"); + const { data: clientData } = useQuery({ queryKey: ["client", clientId], queryFn: () => fetchClient(clientId), @@ -44,9 +46,9 @@ function ClientConsentsPage() { error, refetch, } = useQuery({ - queryKey: ["consents", clientId, subject], - queryFn: () => fetchConsents(subject, clientId), - enabled: clientId.length > 0, // Removed subject.length > 0 check + queryKey: ["consents", clientId, subject, statusFilter], + queryFn: () => fetchConsents(subject, clientId, statusFilter), + enabled: clientId.length > 0, }); const revokeMutation = useMutation({ mutationFn: (payload: { subject: string }) => @@ -56,6 +58,19 @@ function ClientConsentsPage() { }, }); + const handleRevoke = (sub: string) => { + if ( + window.confirm( + t( + "msg.dev.clients.consents.revoke_confirm", + "정말로 이 사용자의 권한을 철회하시겠습니까? 철회 시 사용자는 다음 접속 시 다시 동의해야 합니다.", + ), + ) + ) { + revokeMutation.mutate({ subject: sub }); + } + }; + const rows = consentsData?.items ?? []; return ( @@ -150,14 +165,18 @@ function ClientConsentsPage() { {t("ui.dev.clients.consents.status_label", "Status:")} - setStatusFilter(e.target.value)} + > + - - @@ -226,7 +245,7 @@ function ClientConsentsPage() { {t( "ui.dev.clients.consents.table.last_auth", - "Last Authenticated", + "Last Authenticated / Revoked", )} @@ -243,7 +262,10 @@ function ClientConsentsPage() { ) : ( rows.map((row) => ( - +
@@ -273,9 +295,15 @@ function ClientConsentsPage() {
- - {t("ui.common.status.active", "Active")} - + {row.status === "active" ? ( + + {t("ui.common.status.active", "Active")} + + ) : ( + + {t("ui.dev.clients.consents.status_revoked", "Revoked")} + + )}
@@ -294,20 +322,28 @@ function ClientConsentsPage() { {new Date(row.createdAt).toLocaleString()} - {row.authenticatedAt - ? new Date(row.authenticatedAt).toLocaleString() - : "-"} + {row.status === "revoked" && row.deletedAt ? ( + + {t("ui.dev.clients.consents.revoked_at", "Revoked: ")} + {new Date(row.deletedAt).toLocaleString()} + + ) : row.authenticatedAt ? ( + new Date(row.authenticatedAt).toLocaleString() + ) : ( + "-" + )} - + {row.status === "active" && ( + + )} )) @@ -349,7 +385,9 @@ function ClientConsentsPage() { "Active Grants", )}

- {rows.length} + + {rows.filter((r) => r.status === "active").length} + diff --git a/devfront/src/features/clients/routes/ClientFederationPage.tsx b/devfront/src/features/clients/routes/ClientFederationPage.tsx index 4d63eead..ac6c14a8 100644 --- a/devfront/src/features/clients/routes/ClientFederationPage.tsx +++ b/devfront/src/features/clients/routes/ClientFederationPage.tsx @@ -156,7 +156,7 @@ const CreateIdpModal = ({ disabled={ mutation.isPending || formData.display_name.trim() === "" || - formData.issuer_url.trim() === "" + (formData.issuer_url?.trim() ?? "") === "" } > {mutation.isPending ? ( diff --git a/devfront/src/lib/devApi.ts b/devfront/src/lib/devApi.ts index 5aa29992..b20e8cd0 100644 --- a/devfront/src/lib/devApi.ts +++ b/devfront/src/lib/devApi.ts @@ -57,6 +57,8 @@ export type ConsentSummary = { grantedScopes: string[]; authenticatedAt?: string; createdAt: string; + deletedAt?: string; + status: "active" | "revoked"; tenantId?: string; tenantName?: string; }; @@ -148,11 +150,18 @@ export async function deleteClient(clientId: string) { await apiClient.delete(`/dev/clients/${clientId}`); } -export async function fetchConsents(subject: string, clientId?: string) { +export async function fetchConsents( + subject: string, + clientId?: string, + status?: string, +) { const params: Record = { subject }; if (clientId) { params.client_id = clientId; } + if (status && status !== "all") { + params.status = status; + } const { data } = await apiClient.get("/dev/consents", { params, }); diff --git a/devfront/src/locales/ko.toml b/devfront/src/locales/ko.toml index 35a3da00..d832d1e4 100644 --- a/devfront/src/locales/ko.toml +++ b/devfront/src/locales/ko.toml @@ -956,36 +956,37 @@ admin_session = "관리자 세션" tenant_selected = "테넌트: 선택됨" [ui.dev.clients.consents] -export_csv = "Export CSV" -revoke = "Revoke" +export_csv = "CSV 내보내기" +revoke = "권한 철회" +revoked_at = "철회일: " search_placeholder = "사용자 ID, 이름, 이메일로 검색" -status_all = "All Statuses" -status_label = "Status:" -status_revoked = "Revoked" -subject = "Subject" -title = "User Consent Grants" +status_all = "모든 상태" +status_label = "상태:" +status_revoked = "철회됨" +subject = "사용자 ID" +title = "사용자 동의 권한 관리" [ui.dev.clients.consents.breadcrumb] -clients = "Clients" -current = "User Consent Grants" -home = "Home" +clients = "애플리케이션" +current = "사용자 동의 권한" +home = "홈" [ui.dev.clients.consents.filters] -advanced = "Advanced Filters" +advanced = "상세 필터" [ui.dev.clients.consents.stats] -active_grants = "Active Grants" -avg_scopes = "Avg. Scopes per User" -total_scopes = "Total Scopes Issued" +active_grants = "활성 권한" +avg_scopes = "사용자당 평균 권한 수" +total_scopes = "전체 부여된 권한 수" [ui.dev.clients.consents.table] -action = "Action" -first_granted = "First Granted" -last_auth = "Last Authenticated" -scopes = "Granted Scopes" -status = "Status" -tenant = "Tenant" -user = "User" +action = "작업" +first_granted = "최초 동의" +last_auth = "최근 인증 / 철회" +scopes = "부여된 권한 (Scopes)" +status = "상태" +tenant = "테넌트" +user = "사용자" [ui.dev.clients.details]