forked from baron/baron-sso
연동 앱 페이지 UI 정리
This commit is contained in:
@@ -46,7 +46,6 @@ import {
|
|||||||
type ClientSummary,
|
type ClientSummary,
|
||||||
fetchClients,
|
fetchClients,
|
||||||
fetchDeveloperRequestStatus,
|
fetchDeveloperRequestStatus,
|
||||||
fetchDevStats,
|
|
||||||
fetchMyTenants,
|
fetchMyTenants,
|
||||||
requestDeveloperAccess,
|
requestDeveloperAccess,
|
||||||
} from "../../lib/devApi";
|
} from "../../lib/devApi";
|
||||||
@@ -79,12 +78,6 @@ function ClientsPage() {
|
|||||||
enabled: hasAccessToken,
|
enabled: hasAccessToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: statsData, isLoading: isLoadingStats } = useQuery({
|
|
||||||
queryKey: ["dev-stats"],
|
|
||||||
queryFn: fetchDevStats,
|
|
||||||
enabled: hasAccessToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { data: me, isLoading: isLoadingMe } = useQuery({
|
const { data: me, isLoading: isLoadingMe } = useQuery({
|
||||||
queryKey: ["userMe"],
|
queryKey: ["userMe"],
|
||||||
queryFn: fetchMe,
|
queryFn: fetchMe,
|
||||||
@@ -172,9 +165,6 @@ function ClientsPage() {
|
|||||||
typeFilter,
|
typeFilter,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const totalClients = statsData?.total_clients ?? clients.length;
|
|
||||||
const activeSessions = statsData?.active_sessions ?? 0;
|
|
||||||
const authFailures = statsData?.auth_failures_24h ?? 0;
|
|
||||||
const hasFilterResult = filteredClients.length > 0;
|
const hasFilterResult = filteredClients.length > 0;
|
||||||
const isFilteredOut = clients.length > 0 && !hasFilterResult;
|
const isFilteredOut = clients.length > 0 && !hasFilterResult;
|
||||||
const visibleClients = useMemo(() => {
|
const visibleClients = useMemo(() => {
|
||||||
@@ -198,49 +188,8 @@ function ClientsPage() {
|
|||||||
"";
|
"";
|
||||||
const profileRoleLabel = t(`ui.admin.role.${profileRole}`, profileRole);
|
const profileRoleLabel = t(`ui.admin.role.${profileRole}`, profileRole);
|
||||||
|
|
||||||
type StatTone = "up" | "down" | "stable";
|
|
||||||
type StatItem = {
|
|
||||||
labelKey: string;
|
|
||||||
labelFallback: string;
|
|
||||||
value: string;
|
|
||||||
deltaKey: string;
|
|
||||||
deltaFallback: string;
|
|
||||||
tone: StatTone;
|
|
||||||
};
|
|
||||||
|
|
||||||
const stats: StatItem[] = [
|
|
||||||
{
|
|
||||||
labelKey: "ui.dev.clients.stats.total",
|
|
||||||
labelFallback: "Total Applications",
|
|
||||||
value: totalClients.toString(),
|
|
||||||
deltaKey: "ui.dev.clients.stats.realtime",
|
|
||||||
deltaFallback: "Realtime",
|
|
||||||
tone: "up" as const,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
labelKey: "ui.dev.clients.stats.active_sessions",
|
|
||||||
labelFallback: "Active Sessions",
|
|
||||||
value: activeSessions.toString(),
|
|
||||||
deltaKey: "ui.dev.clients.stats.realtime",
|
|
||||||
deltaFallback: "Realtime",
|
|
||||||
tone: "up" as const,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
labelKey: "ui.dev.clients.stats.auth_failures",
|
|
||||||
labelFallback: "Auth Failures (24h)",
|
|
||||||
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),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const isLoading =
|
const isLoading =
|
||||||
isLoadingClients ||
|
isLoadingClients ||
|
||||||
isLoadingStats ||
|
|
||||||
isLoadingRequest ||
|
isLoadingRequest ||
|
||||||
(hasAccessToken && !profileRole && isLoadingMe);
|
(hasAccessToken && !profileRole && isLoadingMe);
|
||||||
|
|
||||||
@@ -285,7 +234,7 @@ function ClientsPage() {
|
|||||||
canCreateClient ? (
|
canCreateClient ? (
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
className="shadow-lg shadow-primary/30"
|
className="mt-1 shadow-lg shadow-primary/30"
|
||||||
onClick={() => navigate("/clients/new")}
|
onClick={() => navigate("/clients/new")}
|
||||||
>
|
>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
@@ -343,7 +292,19 @@ function ClientsPage() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Card className="glass-panel">
|
<Card className="glass-panel">
|
||||||
<CardHeader className="pb-4 pt-6">
|
<CardHeader className="space-y-4 pb-4 pt-6">
|
||||||
|
<div>
|
||||||
|
<CardTitle className="text-xl font-semibold">
|
||||||
|
{t("ui.dev.clients.list.title", "클라이언트 목록")}
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
{t(
|
||||||
|
"msg.dev.clients.showing",
|
||||||
|
"총 {{shown}}개의 애플리케이션이 등록되어 있습니다.",
|
||||||
|
{ shown: clients.length },
|
||||||
|
)}
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
<SearchFilterBar
|
<SearchFilterBar
|
||||||
primary={
|
primary={
|
||||||
<div className="relative flex-1">
|
<div className="relative flex-1">
|
||||||
@@ -360,34 +321,21 @@ function ClientsPage() {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
<>
|
<Button
|
||||||
<Button
|
variant="ghost"
|
||||||
variant="ghost"
|
size="sm"
|
||||||
size="sm"
|
className={cn(
|
||||||
className={cn(
|
"gap-1 text-muted-foreground",
|
||||||
"gap-1 text-muted-foreground",
|
isAdvancedFilterOpen && "text-primary bg-primary/10",
|
||||||
isAdvancedFilterOpen && "text-primary bg-primary/10",
|
)}
|
||||||
)}
|
onClick={() => setIsAdvancedFilterOpen(!isAdvancedFilterOpen)}
|
||||||
onClick={() => setIsAdvancedFilterOpen(!isAdvancedFilterOpen)}
|
>
|
||||||
>
|
<Filter className="h-4 w-4" />
|
||||||
<Filter className="h-4 w-4" />
|
{t(
|
||||||
{t(
|
"ui.dev.clients.consents.filters.advanced",
|
||||||
"ui.dev.clients.consents.filters.advanced",
|
"Advanced Filters",
|
||||||
"Advanced Filters",
|
)}
|
||||||
)}
|
</Button>
|
||||||
</Button>
|
|
||||||
<div className="hidden items-center gap-2 md:flex">
|
|
||||||
<Badge variant="muted">
|
|
||||||
{t(
|
|
||||||
"ui.dev.clients.badge.tenant_selected",
|
|
||||||
"테넌트: 선택됨",
|
|
||||||
)}
|
|
||||||
</Badge>
|
|
||||||
<Badge variant="success">
|
|
||||||
{t("ui.dev.clients.badge.dev_session", "DevFront 세션")}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
}
|
}
|
||||||
advancedOpen={isAdvancedFilterOpen}
|
advancedOpen={isAdvancedFilterOpen}
|
||||||
advanced={
|
advanced={
|
||||||
@@ -447,59 +395,6 @@ function ClientsPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="pt-0">
|
|
||||||
<div className="grid gap-3 md:grid-cols-3">
|
|
||||||
{stats.map((item) => (
|
|
||||||
<Card
|
|
||||||
key={item.labelKey}
|
|
||||||
className="border border-border/60 bg-background/70 shadow-none"
|
|
||||||
>
|
|
||||||
<CardHeader className="space-y-1 p-4">
|
|
||||||
<CardDescription>
|
|
||||||
{t(item.labelKey, item.labelFallback)}
|
|
||||||
</CardDescription>
|
|
||||||
<div className="flex items-baseline gap-2">
|
|
||||||
<span className="text-2xl font-semibold tracking-tight">
|
|
||||||
{item.value}
|
|
||||||
</span>
|
|
||||||
<Badge
|
|
||||||
variant={
|
|
||||||
item.tone === "up"
|
|
||||||
? "success"
|
|
||||||
: item.tone === "down"
|
|
||||||
? "warning"
|
|
||||||
: "muted"
|
|
||||||
}
|
|
||||||
className={cn(
|
|
||||||
"px-2 py-0.5 text-[11px]",
|
|
||||||
item.tone === "stable" && "bg-muted/40 text-foreground",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{t(item.deltaKey, item.deltaFallback)}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="glass-panel">
|
|
||||||
<CardHeader className="flex flex-row items-center justify-between flex-shrink-0">
|
|
||||||
<div>
|
|
||||||
<CardTitle className="text-xl font-semibold">
|
|
||||||
{t("ui.dev.clients.list.title", "클라이언트 목록")}
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
{t(
|
|
||||||
"msg.dev.clients.showing",
|
|
||||||
"총 {{shown}}개의 애플리케이션이 등록되어 있습니다.",
|
|
||||||
{ shown: totalClients },
|
|
||||||
)}
|
|
||||||
</CardDescription>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="flex-1 flex flex-col min-h-0 pt-0">
|
<CardContent className="flex-1 flex flex-col min-h-0 pt-0">
|
||||||
<div className={commonTableShellClass}>
|
<div className={commonTableShellClass}>
|
||||||
<div className={commonTableViewportClass}>
|
<div className={commonTableViewportClass}>
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ test("clients page loads correctly", async ({ page }) => {
|
|||||||
|
|
||||||
// 페이지 내 주요 텍스트 확인
|
// 페이지 내 주요 텍스트 확인
|
||||||
await expect(page.getByText("연동 앱 목록")).toBeVisible();
|
await expect(page.getByText("연동 앱 목록")).toBeVisible();
|
||||||
|
await expect(page.getByText("Total Applications", { exact: true })).toHaveCount(0);
|
||||||
|
|
||||||
// 테이블 헤더 확인
|
// 테이블 헤더 확인
|
||||||
await expect(
|
await expect(
|
||||||
|
|||||||
@@ -595,6 +595,10 @@ description = "Quickly review application types and headless login usage."
|
|||||||
empty = "Review the RPs this account can access."
|
empty = "Review the RPs this account can access."
|
||||||
none = "No linked applications are available."
|
none = "No linked applications are available."
|
||||||
|
|
||||||
|
[msg.dev.dashboard.recent_changes]
|
||||||
|
description = "Review trends for changed or deleted applications on the dashboard."
|
||||||
|
empty = "There are no recent change logs yet."
|
||||||
|
|
||||||
[msg.dev.dashboard.notice]
|
[msg.dev.dashboard.notice]
|
||||||
consent_audit = "Consent Audit"
|
consent_audit = "Consent Audit"
|
||||||
dev_scope = "Dev Scope"
|
dev_scope = "Dev Scope"
|
||||||
@@ -2308,6 +2312,20 @@ title = "Quick links"
|
|||||||
[ui.dev.dashboard.recent]
|
[ui.dev.dashboard.recent]
|
||||||
title = "My Applications"
|
title = "My Applications"
|
||||||
|
|
||||||
|
[ui.dev.dashboard.recent_changes]
|
||||||
|
aria = "Recent changed application status"
|
||||||
|
deleted_group = "Deleted RPs"
|
||||||
|
period = "Recent change aggregation period"
|
||||||
|
series = "Changes {{changes}} / Actors {{actors}}"
|
||||||
|
title = "Recent Changed Apps"
|
||||||
|
y_axis = "Y axis: change count"
|
||||||
|
|
||||||
|
[ui.dev.dashboard.recent_changes.summary]
|
||||||
|
changed_clients = "Changed applications"
|
||||||
|
deleted_clients = "Deleted RPs"
|
||||||
|
latest_change = "Latest change"
|
||||||
|
total_changes = "Recent change count"
|
||||||
|
|
||||||
[ui.dev.dashboard.stack]
|
[ui.dev.dashboard.stack]
|
||||||
notes = "Setup notes"
|
notes = "Setup notes"
|
||||||
subtitle = "Devfront baseline"
|
subtitle = "Devfront baseline"
|
||||||
|
|||||||
@@ -1087,6 +1087,10 @@ description = "애플리케이션 유형과 headless login 사용 현황을 빠
|
|||||||
empty = "현재 계정이 접근할 수 있는 RP를 확인합니다."
|
empty = "현재 계정이 접근할 수 있는 RP를 확인합니다."
|
||||||
none = "표시할 연동 앱이 없습니다."
|
none = "표시할 연동 앱이 없습니다."
|
||||||
|
|
||||||
|
[msg.dev.dashboard.recent_changes]
|
||||||
|
description = "변경 또는 삭제된 애플리케이션을 대시보드에서 추이를 확인합니다."
|
||||||
|
empty = "최근 변경 로그가 아직 없습니다."
|
||||||
|
|
||||||
[msg.dev.dashboard.notice]
|
[msg.dev.dashboard.notice]
|
||||||
consent_audit = "Consent 회수는 감사 로그와 연계"
|
consent_audit = "Consent 회수는 감사 로그와 연계"
|
||||||
dev_scope = "RP 정책은 dev scope에서만 적용"
|
dev_scope = "RP 정책은 dev scope에서만 적용"
|
||||||
@@ -2772,6 +2776,20 @@ title = "빠른 이동"
|
|||||||
[ui.dev.dashboard.recent]
|
[ui.dev.dashboard.recent]
|
||||||
title = "내 애플리케이션"
|
title = "내 애플리케이션"
|
||||||
|
|
||||||
|
[ui.dev.dashboard.recent_changes]
|
||||||
|
aria = "최근 변경된 앱 현황"
|
||||||
|
deleted_group = "삭제된 RP"
|
||||||
|
period = "최근 변경 집계 단위"
|
||||||
|
series = "변경 {{changes}} / 작업자 {{actors}}"
|
||||||
|
title = "최근 변경된 앱"
|
||||||
|
y_axis = "Y축: 변경 수"
|
||||||
|
|
||||||
|
[ui.dev.dashboard.recent_changes.summary]
|
||||||
|
changed_clients = "변경된 앱 수"
|
||||||
|
deleted_clients = "삭제된 RP 수"
|
||||||
|
latest_change = "마지막 변경일"
|
||||||
|
total_changes = "최근 변경 건수"
|
||||||
|
|
||||||
[ui.dev.dashboard.stack]
|
[ui.dev.dashboard.stack]
|
||||||
notes = "Setup notes"
|
notes = "Setup notes"
|
||||||
subtitle = "Devfront baseline"
|
subtitle = "Devfront baseline"
|
||||||
|
|||||||
@@ -947,6 +947,10 @@ description = ""
|
|||||||
empty = ""
|
empty = ""
|
||||||
none = ""
|
none = ""
|
||||||
|
|
||||||
|
[msg.dev.dashboard.recent_changes]
|
||||||
|
description = ""
|
||||||
|
empty = ""
|
||||||
|
|
||||||
[msg.dev.dashboard.notice]
|
[msg.dev.dashboard.notice]
|
||||||
consent_audit = ""
|
consent_audit = ""
|
||||||
dev_scope = ""
|
dev_scope = ""
|
||||||
@@ -2653,6 +2657,20 @@ title = ""
|
|||||||
[ui.dev.dashboard.recent]
|
[ui.dev.dashboard.recent]
|
||||||
title = ""
|
title = ""
|
||||||
|
|
||||||
|
[ui.dev.dashboard.recent_changes]
|
||||||
|
aria = ""
|
||||||
|
deleted_group = ""
|
||||||
|
period = ""
|
||||||
|
series = ""
|
||||||
|
title = ""
|
||||||
|
y_axis = ""
|
||||||
|
|
||||||
|
[ui.dev.dashboard.recent_changes.summary]
|
||||||
|
changed_clients = ""
|
||||||
|
deleted_clients = ""
|
||||||
|
latest_change = ""
|
||||||
|
total_changes = ""
|
||||||
|
|
||||||
[ui.dev.dashboard.stack]
|
[ui.dev.dashboard.stack]
|
||||||
notes = ""
|
notes = ""
|
||||||
subtitle = ""
|
subtitle = ""
|
||||||
|
|||||||
Reference in New Issue
Block a user