forked from baron/baron-sso
페이지 헤더 스타일 통일
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||
import type { AxiosError } from "axios";
|
||||
import { Download, RefreshCw, Search } from "lucide-react";
|
||||
import { Download, NotebookTabs, RefreshCw, Search } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { parseAuditDetails } from "../../../../common/core/audit";
|
||||
import { AuditLogTable } from "../../../../common/core/components/audit";
|
||||
@@ -12,6 +12,7 @@ import { Button } from "../../components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "../../components/ui/card";
|
||||
@@ -120,6 +121,7 @@ function AuditLogsPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
icon={<NotebookTabs size={20} />}
|
||||
title={t("ui.common.audit.title", "Audit Logs")}
|
||||
description={t(
|
||||
"msg.dev.audit.subtitle",
|
||||
@@ -157,6 +159,12 @@ function AuditLogsPage() {
|
||||
<CardTitle>
|
||||
{t("ui.common.audit.registry.title", "Audit registry")}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
{t(
|
||||
"msg.dev.audit.registry_description",
|
||||
"최근 감사 로그를 검색 조건에 맞춰 필터링하고, 작업 이력을 빠르게 확인합니다.",
|
||||
)}
|
||||
</CardDescription>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 pt-0">
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import type { AxiosError } from "axios";
|
||||
import {
|
||||
ArrowLeft,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Download,
|
||||
Filter,
|
||||
Search,
|
||||
ShieldHalf,
|
||||
} from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import { PageHeader } from "../../../../common/core/components/page";
|
||||
import {
|
||||
commonStickyTableHeaderClass,
|
||||
commonTableShellClass,
|
||||
@@ -194,21 +195,13 @@ function ClientConsentsPage() {
|
||||
)}
|
||||
</span>
|
||||
</nav>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="ghost" size="icon" asChild>
|
||||
<Link to={`/clients/${clientId}`}>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
<div>
|
||||
<p className="text-3xl font-black leading-tight">
|
||||
{t(
|
||||
"ui.dev.clients.consents.title",
|
||||
"User Consent Grants",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<PageHeader
|
||||
icon={<ShieldHalf size={20} />}
|
||||
title={t(
|
||||
"ui.dev.clients.consents.title",
|
||||
"User Consent Grants",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ClientDetailTabs activeTab="consents" clientId={clientId} />
|
||||
@@ -242,24 +235,14 @@ function ClientConsentsPage() {
|
||||
)}
|
||||
</span>
|
||||
</nav>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="ghost" size="icon" asChild>
|
||||
<Link to={`/clients/${clientId}`}>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
<div>
|
||||
<p className="text-3xl font-black leading-tight">
|
||||
{t("ui.dev.clients.consents.title", "User Consent Grants")}
|
||||
</p>
|
||||
<p className="text-muted-foreground">
|
||||
{t(
|
||||
"msg.dev.clients.consents.subtitle",
|
||||
"OIDC Relying Party 사용자 권한을 검토·관리합니다.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<PageHeader
|
||||
icon={<ShieldHalf size={20} />}
|
||||
title={t("ui.dev.clients.consents.title", "User Consent Grants")}
|
||||
description={t(
|
||||
"msg.dev.clients.consents.subtitle",
|
||||
"OIDC Relying Party 사용자 권한을 검토·관리합니다.",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge
|
||||
@@ -623,7 +606,7 @@ function ClientConsentsPage() {
|
||||
"Active Grants",
|
||||
)}
|
||||
</p>
|
||||
<CardTitle className="text-2xl font-black">
|
||||
<CardTitle className="text-xl font-semibold">
|
||||
{rows.filter((r) => r.status === "active").length}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
@@ -636,7 +619,7 @@ function ClientConsentsPage() {
|
||||
"Total Scopes Issued",
|
||||
)}
|
||||
</p>
|
||||
<CardTitle className="text-2xl font-black">
|
||||
<CardTitle className="text-xl font-semibold">
|
||||
{rows.reduce((acc, row) => acc + row.grantedScopes.length, 0)}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
@@ -649,7 +632,7 @@ function ClientConsentsPage() {
|
||||
"Avg. Scopes per User",
|
||||
)}
|
||||
</p>
|
||||
<CardTitle className="text-2xl font-black">
|
||||
<CardTitle className="text-xl font-semibold">
|
||||
{rows.length > 0
|
||||
? (
|
||||
rows.reduce(
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import type { AxiosError } from "axios";
|
||||
import {
|
||||
ArrowLeft,
|
||||
Eye,
|
||||
EyeOff,
|
||||
Link2,
|
||||
RefreshCw,
|
||||
Save,
|
||||
Shield,
|
||||
ShieldHalf,
|
||||
} from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import { PageHeader } from "../../../../common/core/components/page";
|
||||
import { Badge } from "../../components/ui/badge";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import {
|
||||
@@ -246,36 +247,26 @@ function ClientDetailsPage() {
|
||||
{t("ui.dev.clients.details.tab.connection", "Federation")}
|
||||
</span>
|
||||
</nav>
|
||||
<div className="flex flex-wrap items-start justify-between gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="ghost" size="icon" asChild>
|
||||
<Link to="/clients">
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-4xl font-black leading-tight tracking-tight">
|
||||
{client?.name || client?.id || clientId}
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
{t(
|
||||
"msg.dev.clients.details.subtitle",
|
||||
"Manage OIDC credentials and endpoints.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Badge
|
||||
variant={client?.status === "active" ? "info" : "muted"}
|
||||
className="px-3 py-1 text-xs uppercase"
|
||||
>
|
||||
{client?.status === "active"
|
||||
? t("ui.common.status.active", "Active")
|
||||
: client?.status === "inactive"
|
||||
? t("ui.common.status.inactive", "Inactive")
|
||||
: t("msg.common.loading", "Loading...")}
|
||||
</Badge>
|
||||
</div>
|
||||
<PageHeader
|
||||
icon={<ShieldHalf size={20} />}
|
||||
title={client?.name || client?.id || clientId}
|
||||
description={t(
|
||||
"msg.dev.clients.details.subtitle",
|
||||
"Manage OIDC credentials and endpoints.",
|
||||
)}
|
||||
actions={
|
||||
<Badge
|
||||
variant={client?.status === "active" ? "info" : "muted"}
|
||||
className="px-3 py-1 text-xs uppercase"
|
||||
>
|
||||
{client?.status === "active"
|
||||
? t("ui.common.status.active", "Active")
|
||||
: client?.status === "inactive"
|
||||
? t("ui.common.status.inactive", "Inactive")
|
||||
: t("msg.common.loading", "Loading...")}
|
||||
</Badge>
|
||||
}
|
||||
/>
|
||||
<ClientDetailTabs activeTab="connection" clientId={clientId} />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import type { AxiosError } from "axios";
|
||||
import {
|
||||
ArrowLeft,
|
||||
Check,
|
||||
ExternalLink,
|
||||
Info,
|
||||
@@ -9,6 +8,7 @@ import {
|
||||
Save,
|
||||
Search,
|
||||
Shield,
|
||||
ShieldHalf,
|
||||
Sparkles,
|
||||
Trash2,
|
||||
Upload,
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAuth } from "react-oidc-context";
|
||||
import { Link, useNavigate, useParams } from "react-router-dom";
|
||||
import { PageHeader } from "../../../../common/core/components/page";
|
||||
import { Badge } from "../../components/ui/badge";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import {
|
||||
@@ -1195,26 +1196,18 @@ function ClientGeneralPage() {
|
||||
</>
|
||||
)}
|
||||
</nav>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="ghost" size="icon" asChild>
|
||||
<Link to={isCreate ? "/clients" : `/clients/${clientId}`}>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-3xl font-black leading-tight">
|
||||
{isCreate
|
||||
? t("ui.dev.clients.general.title_create", "Create Client")
|
||||
: t("ui.dev.clients.general.title_edit", "Client Settings")}
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
{t(
|
||||
"ui.dev.clients.general.subtitle",
|
||||
"앱 정보, 권한 스코프, 보안 설정을 관리합니다.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<PageHeader
|
||||
icon={<ShieldHalf size={20} />}
|
||||
title={
|
||||
isCreate
|
||||
? t("ui.dev.clients.general.title_create", "Create Client")
|
||||
: t("ui.dev.clients.general.title_edit", "Client Settings")
|
||||
}
|
||||
description={t(
|
||||
"ui.dev.clients.general.subtitle",
|
||||
"앱 정보, 권한 스코프, 보안 설정을 관리합니다.",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{!isCreate && (
|
||||
<Badge
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import type { AxiosError } from "axios";
|
||||
import { ArrowLeft, Info, Link2, Plus, Trash2, X } from "lucide-react";
|
||||
import { Info, Link2, Plus, ShieldHalf, Trash2, X } from "lucide-react";
|
||||
import { useDeferredValue, useMemo, useState } from "react";
|
||||
import { useAuth } from "react-oidc-context";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import { PageHeader } from "../../../../common/core/components/page";
|
||||
import { Badge } from "../../components/ui/badge";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import {
|
||||
@@ -352,27 +353,14 @@ function ClientRelationsPage() {
|
||||
{t("ui.dev.clients.details.tab.relationships", "Relationships")}
|
||||
</span>
|
||||
</nav>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="ghost" size="icon" asChild>
|
||||
<Link to={`/clients/${clientId}`}>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
<div>
|
||||
<p className="text-3xl font-black leading-tight">
|
||||
{t(
|
||||
"ui.dev.clients.relationships.title",
|
||||
"Client Relationships",
|
||||
)}
|
||||
</p>
|
||||
<p className="text-muted-foreground">
|
||||
{t(
|
||||
"msg.dev.clients.relationships.subtitle",
|
||||
"RP direct operator relation을 조회하고 User 단위로 추가·삭제합니다.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<PageHeader
|
||||
icon={<ShieldHalf size={20} />}
|
||||
title={t("ui.dev.clients.relationships.title", "Client Relationships")}
|
||||
description={t(
|
||||
"msg.dev.clients.relationships.subtitle",
|
||||
"RP direct operator relation을 조회하고 User 단위로 추가·삭제합니다.",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import type { AxiosError } from "axios";
|
||||
import { BookOpenText, Filter, Plus, Search, X } from "lucide-react";
|
||||
import { BookOpenText, Filter, Plus, Search, ShieldHalf, X } from "lucide-react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useAuth } from "react-oidc-context";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
@@ -262,6 +262,7 @@ function ClientsPage() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<PageHeader
|
||||
icon={<ShieldHalf size={20} />}
|
||||
title={t("ui.dev.clients.registry.subtitle", "연동 앱")}
|
||||
description={t(
|
||||
"msg.dev.clients.registry.description",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { Edit, Globe, Plus, Save, Trash2 } from "lucide-react";
|
||||
import { Edit, Plus, Save, ShieldHalf, Trash2 } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { PageHeader } from "../../../../../common/core/components/page";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
@@ -195,24 +196,20 @@ export function ClientFederationPage() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6 p-1">
|
||||
<header className="flex flex-wrap items-center justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold flex items-center gap-2">
|
||||
<Globe className="h-6 w-6 text-primary" />
|
||||
{t("ui.dev.clients.federation.title", "Identity Federation")}
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
{t(
|
||||
"msg.dev.clients.federation.subtitle",
|
||||
"Manage external identity providers for this application.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={() => setCreateModalOpen(true)} className="gap-2">
|
||||
<Plus className="h-4 w-4" />
|
||||
{t("ui.dev.clients.federation.add_btn", "Add Provider")}
|
||||
</Button>
|
||||
</header>
|
||||
<PageHeader
|
||||
icon={<ShieldHalf size={20} />}
|
||||
title={t("ui.dev.clients.federation.title", "Identity Federation")}
|
||||
description={t(
|
||||
"msg.dev.clients.federation.subtitle",
|
||||
"Manage external identity providers for this application.",
|
||||
)}
|
||||
actions={
|
||||
<Button onClick={() => setCreateModalOpen(true)} className="gap-2">
|
||||
<Plus className="h-4 w-4" />
|
||||
{t("ui.dev.clients.federation.add_btn", "Add Provider")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
<Card className="glass-panel">
|
||||
<CardContent className="p-0">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
ClipboardCheck,
|
||||
CheckCircle2,
|
||||
Clock,
|
||||
Plus,
|
||||
@@ -20,6 +21,7 @@ import { Button } from "../../components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "../../components/ui/card";
|
||||
@@ -152,6 +154,8 @@ export default function DeveloperRequestPage() {
|
||||
const hasActiveRequest = requests?.some(
|
||||
(r) => r.status === "pending" || r.status === "approved",
|
||||
);
|
||||
const approvedRequestCount =
|
||||
requests?.filter((request) => request.status === "approved").length ?? 0;
|
||||
const isActionPending =
|
||||
approveMutation.isPending ||
|
||||
rejectMutation.isPending ||
|
||||
@@ -160,6 +164,7 @@ export default function DeveloperRequestPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
icon={<ClipboardCheck size={20} />}
|
||||
title={t("ui.dev.nav.developer_request", "개발자 권한 신청")}
|
||||
description={
|
||||
isSuperAdmin
|
||||
@@ -187,6 +192,13 @@ export default function DeveloperRequestPage() {
|
||||
<CardTitle className="text-xl">
|
||||
{t("ui.dev.request.list.title", "신청 내역")}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
{t(
|
||||
"msg.dev.request.list.approved_count",
|
||||
"총 {{count}}명의 사용자가 승인되었습니다.",
|
||||
{ count: approvedRequestCount },
|
||||
)}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className={commonTableShellClass}>
|
||||
|
||||
@@ -3,8 +3,8 @@ import type { AxiosError } from "axios";
|
||||
import {
|
||||
Activity,
|
||||
AlertTriangle,
|
||||
BarChart3,
|
||||
CheckCircle2,
|
||||
LayoutDashboard,
|
||||
Layers3,
|
||||
ShieldCheck,
|
||||
} from "lucide-react";
|
||||
@@ -662,17 +662,22 @@ function GlobalOverviewPage() {
|
||||
|
||||
return (
|
||||
<div className="space-y-4 animate-in fade-in duration-500">
|
||||
<div className="flex flex-wrap items-end justify-between gap-4">
|
||||
<div className="space-y-1">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">
|
||||
{t("ui.common.overview.title", "운영 현황")}
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t(
|
||||
"msg.dev.dashboard.description",
|
||||
"연동 앱 구성과 인증 운영 지표를 한 곳에서 확인합니다.",
|
||||
)}
|
||||
</p>
|
||||
<div className="flex flex-wrap items-start justify-between gap-4">
|
||||
<div className="flex min-w-0 items-start gap-3">
|
||||
<div className="mt-1 flex h-10 w-10 shrink-0 items-center justify-center rounded-xl border border-primary/15 bg-primary/10 text-primary">
|
||||
<LayoutDashboard size={20} />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<h2 className="text-3xl font-semibold">
|
||||
{t("ui.common.overview.title", "운영 현황")}
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t(
|
||||
"msg.dev.dashboard.description",
|
||||
"연동 앱 구성과 인증 운영 지표를 한 곳에서 확인합니다.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -704,22 +709,19 @@ function GlobalOverviewPage() {
|
||||
|
||||
<section className="space-y-3">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<BarChart3 size={18} className="text-primary" />
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-base font-semibold">
|
||||
{t(
|
||||
"ui.dev.dashboard.chart.title",
|
||||
"애플리케이션별 로그인요청/기타 요청 현황",
|
||||
)}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t(
|
||||
"msg.dev.dashboard.chart.filter_description",
|
||||
"전체 또는 선택한 애플리케이션만 기준으로 그래프를 확인합니다.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-base font-semibold">
|
||||
{t(
|
||||
"ui.dev.dashboard.chart.title",
|
||||
"애플리케이션별 로그인요청/기타 요청 현황",
|
||||
)}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t(
|
||||
"msg.dev.dashboard.chart.filter_description",
|
||||
"전체 또는 선택한 애플리케이션만 기준으로 그래프를 확인합니다.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex h-8 items-center gap-1" aria-label="집계 단위">
|
||||
{[
|
||||
|
||||
@@ -64,9 +64,14 @@ function ProfilePage() {
|
||||
return (
|
||||
<div className="space-y-6 max-w-4xl mx-auto">
|
||||
<div>
|
||||
<h1 className="text-3xl font-black tracking-tight">
|
||||
{t("ui.dev.profile.title", "내 정보")}
|
||||
</h1>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="mt-1 flex h-10 w-10 shrink-0 items-center justify-center rounded-xl border border-primary/15 bg-primary/10 text-primary">
|
||||
<User className="h-5 w-5" />
|
||||
</div>
|
||||
<h1 className="text-3xl font-semibold tracking-tight">
|
||||
{t("ui.dev.profile.title", "내 정보")}
|
||||
</h1>
|
||||
</div>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
{t(
|
||||
"ui.dev.profile.subtitle",
|
||||
|
||||
Reference in New Issue
Block a user