1
0
forked from baron/baron-sso

클라이언트 대시보드 통계 실지표 연동 및 백엔드 API 구현

This commit is contained in:
2026-03-03 14:06:27 +09:00
parent 8db37c377a
commit 20c97843c3
6 changed files with 530 additions and 101 deletions

View File

@@ -9,6 +9,7 @@ import {
ShieldHalf,
} from "lucide-react";
import { useState } from "react";
import { useAuth } from "react-oidc-context";
import { Link, useNavigate } from "react-router-dom";
import {
Avatar,
@@ -34,15 +35,29 @@ import {
TableHeader,
TableRow,
} from "../../components/ui/table";
import { fetchClients } from "../../lib/devApi";
import { fetchClients, fetchDevStats } from "../../lib/devApi";
import { t } from "../../lib/i18n";
import { cn } from "../../lib/utils";
function ClientsPage() {
const navigate = useNavigate();
const { data, isLoading, error } = useQuery({
const auth = useAuth();
const hasAccessToken = Boolean(auth.user?.access_token);
const {
data,
isLoading: isLoadingClients,
error: clientError,
} = useQuery({
queryKey: ["clients"],
queryFn: fetchClients,
enabled: hasAccessToken,
});
const { data: statsData, isLoading: isLoadingStats } = useQuery({
queryKey: ["dev-stats"],
queryFn: fetchDevStats,
enabled: hasAccessToken,
});
const [searchQuery, setSearchQuery] = useState("");
@@ -63,11 +78,10 @@ function ClientsPage() {
return matchesSearch && matchesType && matchesStatus;
});
const totalClients = clients.length;
const activeClients = clients.filter(
(client) => client.status === "active",
).length;
// TODO: Replace with real session/auth-failure metrics when backend endpoints are available.
const totalClients = statsData?.total_clients ?? clients.length;
const activeSessions = statsData?.active_sessions ?? 0;
const authFailures = statsData?.auth_failures_24h ?? 0;
type StatTone = "up" | "down" | "stable";
type StatItem = {
labelKey: string;
@@ -90,7 +104,7 @@ function ClientsPage() {
{
labelKey: "ui.dev.clients.stats.active_sessions",
labelFallback: "Active Sessions",
value: activeClients.toString(),
value: activeSessions.toString(),
deltaKey: "ui.dev.clients.stats.realtime",
deltaFallback: "Realtime",
tone: "up" as const,
@@ -98,14 +112,16 @@ function ClientsPage() {
{
labelKey: "ui.dev.clients.stats.auth_failures",
labelFallback: "Auth Failures (24h)",
value: "0",
deltaKey: "ui.dev.clients.stats.stable",
deltaFallback: "Stable",
tone: "stable" as const,
value: authFailures.toString(),
deltaKey: authFailures > 0 ? "ui.dev.clients.stats.alert" : "ui.dev.clients.stats.stable",
deltaFallback: authFailures > 0 ? "Check Logs" : "Stable",
tone: authFailures > 0 ? ("down" as const) : ("stable" as const),
},
];
if (isLoading) {
const isLoading = isLoadingClients || isLoadingStats;
if (auth.isLoading || !hasAccessToken || isLoading) {
return (
<div className="p-8 text-center">
{t("msg.dev.clients.loading", "Loading clients...")}
@@ -113,10 +129,10 @@ function ClientsPage() {
);
}
if (error) {
if (clientError) {
const errMsg =
(error as AxiosError<{ error?: string }>).response?.data?.error ??
(error as Error).message;
(clientError as AxiosError<{ error?: string }>).response?.data?.error ??
(clientError as Error).message;
return (
<div className="p-8 text-center text-red-500">
{t("msg.dev.clients.load_error", "Error loading clients: {{error}}", {
@@ -268,7 +284,13 @@ function ClientsPage() {
<div className="mt-1 flex items-baseline gap-2">
<span className="text-3xl font-bold">{item.value}</span>
<Badge
variant={item.tone === "up" ? "success" : "muted"}
variant={
item.tone === "up"
? "success"
: item.tone === "down"
? "destructive"
: "muted"
}
className={cn(
"px-2",
item.tone === "stable" && "bg-muted/40 text-foreground",