forked from baron/baron-sso
Merge branch 'dev' into feat/org-chart-rebac
This commit is contained in:
@@ -69,7 +69,7 @@ function ClientConsentsPage() {
|
||||
</Link>
|
||||
<span>/</span>
|
||||
<Link to="/clients" className="hover:text-primary">
|
||||
{t("ui.dev.clients.consents.breadcrumb.clients", "Clients")}
|
||||
{t("ui.dev.clients.consents.breadcrumb.clients", "Apps")}
|
||||
</Link>
|
||||
<span>/</span>
|
||||
<span>{clientData?.client?.name || clientId}</span>
|
||||
|
||||
@@ -124,7 +124,7 @@ function ClientDetailsPage() {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="p-8 text-center">
|
||||
{t("msg.dev.clients.details.loading", "Loading client...")}
|
||||
{t("msg.dev.clients.details.loading", "Loading app...")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -137,7 +137,7 @@ function ClientDetailsPage() {
|
||||
<div className="p-8 text-center text-red-500">
|
||||
{t(
|
||||
"msg.dev.clients.details.load_error",
|
||||
"Error loading client: {{error}}",
|
||||
"Error loading app: {{error}}",
|
||||
{ error: errMsg || t("msg.common.unknown_error", "unknown error") },
|
||||
)}
|
||||
</div>
|
||||
@@ -185,7 +185,7 @@ function ClientDetailsPage() {
|
||||
<div className="space-y-3">
|
||||
<div className="flex flex-wrap items-center gap-2 text-sm text-muted-foreground">
|
||||
<Link to="/clients" className="text-primary hover:underline">
|
||||
{t("ui.dev.clients.details.breadcrumb.section", "Relying Parties")}
|
||||
{t("ui.dev.clients.details.breadcrumb.section", "Apps")}
|
||||
</Link>
|
||||
<span>/</span>
|
||||
<span className="text-foreground">
|
||||
|
||||
@@ -47,7 +47,7 @@ function ClientGeneralPage() {
|
||||
const [name, setName] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const [logoUrl, setLogoUrl] = useState("");
|
||||
const [clientType, setClientType] = useState<ClientType>("confidential");
|
||||
const [clientType, setClientType] = useState<ClientType>("private");
|
||||
const [status, setStatus] = useState<ClientStatus>("active");
|
||||
const [redirectUris, setRedirectUris] = useState("");
|
||||
const [scopes, setScopes] = useState<ScopeItem[]>(() => [
|
||||
@@ -157,6 +157,21 @@ function ClientGeneralPage() {
|
||||
}
|
||||
alert(t("msg.dev.clients.general.saved", "설정이 저장되었습니다."));
|
||||
},
|
||||
onError: (err) => {
|
||||
const errorMessage =
|
||||
(err as AxiosError<{ error?: string }>).response?.data?.error ??
|
||||
(err as Error)?.message ??
|
||||
t("msg.common.unknown_error", "unknown error");
|
||||
alert(
|
||||
t(
|
||||
"msg.dev.clients.general.save_error",
|
||||
"저장에 실패했습니다: {{error}}",
|
||||
{
|
||||
error: errorMessage,
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
if (!isCreate && isLoading) {
|
||||
@@ -376,7 +391,6 @@ function ClientGeneralPage() {
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-muted/50 border-b border-border text-xs uppercase tracking-wider text-muted-foreground">
|
||||
<tr>
|
||||
<th className="px-4 py-3 text-left font-bold">Scope Name</th>
|
||||
<th className="px-4 py-3 text-left font-bold">
|
||||
{t(
|
||||
"ui.dev.clients.general.scopes.table.name",
|
||||
@@ -395,7 +409,9 @@ function ClientGeneralPage() {
|
||||
"Mandatory",
|
||||
)}
|
||||
</th>
|
||||
<th className="px-4 py-3 text-right" />
|
||||
<th className="px-4 py-3 text-right font-bold">
|
||||
{t("ui.dev.clients.general.scopes.table.delete", "Delete")}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border">
|
||||
@@ -489,7 +505,7 @@ function ClientGeneralPage() {
|
||||
<label
|
||||
className={cn(
|
||||
"relative flex cursor-pointer flex-col gap-1 rounded-xl border-2 p-4 transition",
|
||||
clientType === "confidential"
|
||||
clientType === "private"
|
||||
? "border-primary bg-primary/5"
|
||||
: "border-border bg-card hover:border-muted-foreground/40",
|
||||
)}
|
||||
@@ -498,31 +514,28 @@ function ClientGeneralPage() {
|
||||
className="sr-only"
|
||||
type="radio"
|
||||
name="client-type"
|
||||
checked={clientType === "confidential"}
|
||||
onChange={() => setClientType("confidential")}
|
||||
checked={clientType === "private"}
|
||||
onChange={() => setClientType("private")}
|
||||
/>
|
||||
<span className="flex items-center gap-2 text-sm font-bold uppercase text-foreground">
|
||||
<Shield className="h-4 w-4 text-primary" />
|
||||
{t(
|
||||
"ui.dev.clients.general.security.confidential",
|
||||
"Confidential",
|
||||
)}
|
||||
{t("ui.dev.clients.general.security.private", "Private")}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t(
|
||||
"msg.dev.clients.general.security.confidential_help",
|
||||
"msg.dev.clients.general.security.private_help",
|
||||
"서버 사이드 앱(예: Node.js, Java)처럼 비밀키를 안전하게 보관 가능한 경우.",
|
||||
)}
|
||||
</span>
|
||||
<span className="absolute right-4 top-4 text-primary">
|
||||
{clientType === "confidential" ? "✓" : ""}
|
||||
{clientType === "private" ? "✓" : ""}
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label
|
||||
className={cn(
|
||||
"relative flex cursor-pointer flex-col gap-1 rounded-xl border-2 p-4 transition",
|
||||
clientType === "public"
|
||||
clientType === "pkce"
|
||||
? "border-primary bg-primary/5"
|
||||
: "border-border bg-card hover:border-muted-foreground/40",
|
||||
)}
|
||||
@@ -531,21 +544,21 @@ function ClientGeneralPage() {
|
||||
className="sr-only"
|
||||
type="radio"
|
||||
name="client-type"
|
||||
checked={clientType === "public"}
|
||||
onChange={() => setClientType("public")}
|
||||
checked={clientType === "pkce"}
|
||||
onChange={() => setClientType("pkce")}
|
||||
/>
|
||||
<span className="flex items-center gap-2 text-sm font-bold uppercase text-foreground">
|
||||
<Sparkles className="h-4 w-4" />
|
||||
{t("ui.dev.clients.general.security.public", "Public")}
|
||||
{t("ui.dev.clients.general.security.pkce", "PKCE")}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t(
|
||||
"msg.dev.clients.general.security.public_help",
|
||||
"msg.dev.clients.general.security.pkce_help",
|
||||
"SPA/모바일 앱처럼 비밀키 보관이 어려운 경우. PKCE를 기본 사용합니다.",
|
||||
)}
|
||||
</span>
|
||||
<span className="absolute right-4 top-4 text-primary">
|
||||
{clientType === "public" ? "✓" : ""}
|
||||
{clientType === "pkce" ? "✓" : ""}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -87,7 +87,10 @@ function ClientsPage() {
|
||||
|
||||
const clients = data?.items || [];
|
||||
const totalClients = clients.length;
|
||||
// TODO: Add real stats for active sessions and auth failures
|
||||
const activeClients = clients.filter(
|
||||
(client) => client.status === "active",
|
||||
).length;
|
||||
// TODO: Replace with real session/auth-failure metrics when backend endpoints are available.
|
||||
type StatTone = "up" | "down" | "stable";
|
||||
type StatItem = {
|
||||
labelKey: string;
|
||||
@@ -101,7 +104,7 @@ function ClientsPage() {
|
||||
const stats: StatItem[] = [
|
||||
{
|
||||
labelKey: "ui.dev.clients.stats.total",
|
||||
labelFallback: "총 클라이언트",
|
||||
labelFallback: "총 애플리케이션",
|
||||
value: totalClients.toString(),
|
||||
deltaKey: "ui.dev.clients.stats.realtime",
|
||||
deltaFallback: "Realtime",
|
||||
@@ -110,10 +113,10 @@ function ClientsPage() {
|
||||
{
|
||||
labelKey: "ui.dev.clients.stats.active_sessions",
|
||||
labelFallback: "활성 세션",
|
||||
value: "-",
|
||||
deltaKey: "ui.dev.clients.stats.not_impl",
|
||||
deltaFallback: "Not impl",
|
||||
tone: "stable" as const,
|
||||
value: activeClients.toString(),
|
||||
deltaKey: "ui.dev.clients.stats.realtime",
|
||||
deltaFallback: "Realtime",
|
||||
tone: "up" as const,
|
||||
},
|
||||
{
|
||||
labelKey: "ui.dev.clients.stats.auth_failures",
|
||||
@@ -266,7 +269,7 @@ function ClientsPage() {
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
||||
{client.type === "confidential" ? (
|
||||
{client.type === "private" ? (
|
||||
<ServerCog className="h-4 w-4" />
|
||||
) : (
|
||||
<ShieldHalf className="h-4 w-4" />
|
||||
@@ -309,16 +312,11 @@ function ClientsPage() {
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge
|
||||
variant={
|
||||
client.type === "confidential" ? "success" : "muted"
|
||||
}
|
||||
variant={client.type === "private" ? "success" : "muted"}
|
||||
>
|
||||
{client.type === "confidential"
|
||||
? t(
|
||||
"ui.dev.clients.type.confidential",
|
||||
"기밀(Confidential)",
|
||||
)
|
||||
: t("ui.dev.clients.type.public", "Public")}
|
||||
{client.type === "private"
|
||||
? t("ui.dev.clients.type.private", "Private")
|
||||
: t("ui.dev.clients.type.pkce", "PKCE")}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
|
||||
Reference in New Issue
Block a user