forked from baron/baron-sso
adminfront ui 개편
This commit is contained in:
@@ -39,6 +39,18 @@ function AppLayout() {
|
||||
return stored === "dark" ? "dark" : "light";
|
||||
});
|
||||
const [isProfileOpen, setIsProfileOpen] = useState(false);
|
||||
const [timeLeft, setTimeLeft] = useState<number | null>(null);
|
||||
const expiresAt = auth.user?.expires_at;
|
||||
|
||||
useEffect(() => {
|
||||
if (!expiresAt) return;
|
||||
const updateTimer = () => {
|
||||
setTimeLeft(Math.max(0, Math.floor(expiresAt - Date.now() / 1000)));
|
||||
};
|
||||
updateTimer();
|
||||
const interval = setInterval(updateTimer, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, [expiresAt]);
|
||||
|
||||
const { data: profile } = useQuery({
|
||||
queryKey: ["me"],
|
||||
@@ -156,20 +168,8 @@ function AppLayout() {
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden rounded-full border border-border px-3 py-2 text-xs text-muted-foreground md:inline-flex md:items-center md:gap-2">
|
||||
<BadgeCheck size={14} />
|
||||
{t("msg.admin.scope_admin", "Scoped to /admin")}
|
||||
</div>
|
||||
</div>
|
||||
<nav className="px-2 pb-4 md:px-3 md:pb-8">
|
||||
<div className="flex flex-wrap gap-2 px-3 pb-4 text-[11px] text-muted-foreground md:flex-col md:items-start">
|
||||
<span className="rounded-full border border-border px-3 py-1">
|
||||
{t("msg.admin.idp_env_prod", "IDP env: prod")}
|
||||
</span>
|
||||
<span className="rounded-full border border-border px-3 py-1">
|
||||
{t("msg.admin.tenant_headers", "Tenant-aware headers")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
{navItems.map(({ label, to, icon: Icon }) => (
|
||||
<NavLink
|
||||
@@ -201,36 +201,11 @@ function AppLayout() {
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
<div className="hidden space-y-2 px-5 pb-6 text-xs text-[var(--color-muted)] md:block">
|
||||
<p>
|
||||
{t(
|
||||
"msg.admin.notice.scope",
|
||||
"관리 기능은 /admin 네임스페이스에서만 노출합니다.",
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{t(
|
||||
"msg.admin.notice.idp_policy",
|
||||
"IDP 관리 키는 서버 내부 래핑 API로만 사용하며, 감사·레이트리밋을 기본 적용합니다.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div className="relative">
|
||||
<header className="sticky top-0 z-20 border-b border-border bg-background/90 backdrop-blur">
|
||||
<div className="flex items-center justify-between px-5 py-4 md:px-8">
|
||||
<div className="flex flex-col gap-1">
|
||||
<p className="text-xs uppercase tracking-[0.22em] text-muted-foreground">
|
||||
{t("ui.admin.header.plane", "Admin Plane")}
|
||||
</p>
|
||||
<span className="text-lg font-semibold">
|
||||
{t(
|
||||
"msg.admin.header.subtitle",
|
||||
"Tenant isolation & least privilege by default",
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<header className="sticky top-0 z-50 border-b border-border bg-background/90 backdrop-blur">
|
||||
<div className="flex items-center justify-end px-5 py-4 md:px-8">
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<LanguageSelector />
|
||||
<button
|
||||
@@ -266,7 +241,7 @@ function AppLayout() {
|
||||
{isProfileOpen && (
|
||||
<>
|
||||
<div
|
||||
className="fixed inset-0 z-30"
|
||||
className="fixed inset-0 z-[90]"
|
||||
onClick={() => setIsProfileOpen(false)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") setIsProfileOpen(false);
|
||||
@@ -275,7 +250,7 @@ function AppLayout() {
|
||||
tabIndex={-1}
|
||||
aria-label="Close profile menu"
|
||||
/>
|
||||
<div className="absolute right-0 mt-2 w-56 origin-top-right rounded-xl border border-border bg-card p-2 shadow-xl ring-1 ring-black ring-opacity-5 focus:outline-none z-40 animate-in fade-in zoom-in-95 duration-200">
|
||||
<div className="absolute right-0 mt-2 w-56 origin-top-right rounded-xl border border-border bg-card p-2 shadow-xl ring-1 ring-black ring-opacity-5 focus:outline-none z-[100] animate-in fade-in zoom-in-95 duration-200">
|
||||
<div className="px-3 py-3 border-b border-border/50 mb-1">
|
||||
<p className="text-sm font-semibold truncate">
|
||||
{profile?.name || auth.user?.profile.name}
|
||||
@@ -364,8 +339,10 @@ function AppLayout() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<span className="hidden md:inline-flex rounded-full border border-border px-3 py-2 text-muted-foreground">
|
||||
{t("msg.admin.session_ttl", "Session TTL: 15m admin")}
|
||||
<span className="hidden md:inline-flex rounded-full border border-border px-3 py-2 text-muted-foreground font-mono">
|
||||
{timeLeft !== null
|
||||
? `Session TTL: ${Math.floor(timeLeft / 60)}m ${timeLeft % 60}s`
|
||||
: t("msg.admin.session_ttl", "Session TTL: 15m admin")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user