forked from baron/baron-sso
Consent 목록 CSV 내보내기 구현
This commit is contained in:
@@ -80,6 +80,56 @@ function ClientConsentsPage() {
|
|||||||
return scopeFilter === "all" || row.grantedScopes.includes(scopeFilter);
|
return scopeFilter === "all" || row.grantedScopes.includes(scopeFilter);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleExportCSV = () => {
|
||||||
|
if (filteredRows.length === 0) return;
|
||||||
|
|
||||||
|
const headers = [
|
||||||
|
t("ui.dev.clients.consents.table.user", "User"),
|
||||||
|
t("ui.dev.clients.consents.table.tenant", "Tenant"),
|
||||||
|
t("ui.dev.clients.table.status", "Status"),
|
||||||
|
t("ui.dev.clients.consents.table.scopes", "Granted Scopes"),
|
||||||
|
t("ui.dev.clients.consents.table.first_granted", "First Granted"),
|
||||||
|
t(
|
||||||
|
"ui.dev.clients.consents.table.last_auth",
|
||||||
|
"Last Authenticated / Revoked",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
const csvContent = [
|
||||||
|
headers.join(","),
|
||||||
|
...filteredRows.map((row) => {
|
||||||
|
const lastAuthRevoked =
|
||||||
|
row.status === "revoked" && row.deletedAt
|
||||||
|
? `${t("ui.dev.clients.consents.status_revoked", "Revoked")}: ${new Date(row.deletedAt).toLocaleString()}`
|
||||||
|
: row.authenticatedAt
|
||||||
|
? new Date(row.authenticatedAt).toLocaleString()
|
||||||
|
: "-";
|
||||||
|
|
||||||
|
return [
|
||||||
|
`"${row.subject} (${row.userName || ""})"`,
|
||||||
|
`"${row.tenantName || row.tenantId || ""}"`,
|
||||||
|
`"${row.status}"`,
|
||||||
|
`"${row.grantedScopes.join(", ")}"`,
|
||||||
|
`"${new Date(row.createdAt).toLocaleString()}"`,
|
||||||
|
`"${lastAuthRevoked}"`,
|
||||||
|
].join(",");
|
||||||
|
}),
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const blob = new Blob(["\uFEFF" + csvContent], {
|
||||||
|
type: "text/csv;charset=utf-8;",
|
||||||
|
});
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
const date = new Date().toISOString().split("T")[0];
|
||||||
|
link.setAttribute("href", url);
|
||||||
|
link.setAttribute("download", `consents_${clientId}_${date}.csv`);
|
||||||
|
link.style.visibility = "hidden";
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
<header className="space-y-4">
|
<header className="space-y-4">
|
||||||
@@ -191,7 +241,11 @@ function ClientConsentsPage() {
|
|||||||
>
|
>
|
||||||
{t("ui.common.search", "검색")}
|
{t("ui.common.search", "검색")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="shadow-sm shadow-primary/30">
|
<Button
|
||||||
|
className="shadow-sm shadow-primary/30"
|
||||||
|
onClick={handleExportCSV}
|
||||||
|
disabled={filteredRows.length === 0}
|
||||||
|
>
|
||||||
{t("ui.dev.clients.consents.export_csv", "Export CSV")}
|
{t("ui.dev.clients.consents.export_csv", "Export CSV")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -455,6 +509,8 @@ function ClientConsentsPage() {
|
|||||||
{rows.reduce((acc, row) => acc + row.grantedScopes.length, 0)}
|
{rows.reduce((acc, row) => acc + row.grantedScopes.length, 0)}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
</Card>
|
||||||
|
<Card className="glass-panel">
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="pb-2">
|
||||||
<p className="text-xs font-bold uppercase tracking-wider text-muted-foreground">
|
<p className="text-xs font-bold uppercase tracking-wider text-muted-foreground">
|
||||||
{t(
|
{t(
|
||||||
|
|||||||
Reference in New Issue
Block a user