forked from baron/baron-sso
devfront 설정 탭 섹션 순서 조정
This commit is contained in:
@@ -1892,85 +1892,8 @@ function ClientGeneralPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card className="glass-panel">
|
|
||||||
<CardHeader className="pb-4">
|
|
||||||
<div className="flex flex-col gap-2 md:flex-row md:items-start md:justify-between">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<CardTitle className="text-xl font-bold">
|
|
||||||
{t("ui.dev.clients.general.auto_login.title", "자동 로그인")}
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
{t(
|
|
||||||
"msg.dev.clients.general.auto_login.subtitle",
|
|
||||||
"RP가 자체 로그인 시작 URL에서 OIDC 요청을 만들 수 있으면 userfront에서 바로 로그인 진입을 제공합니다.",
|
|
||||||
)}
|
|
||||||
</CardDescription>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-3 rounded-xl border border-border bg-muted/30 px-4 py-3">
|
|
||||||
<div className="space-y-0.5 text-right">
|
|
||||||
<p className="text-sm font-semibold">
|
|
||||||
{autoLoginSupported
|
|
||||||
? t("ui.common.enabled", "사용")
|
|
||||||
: t("ui.common.disabled", "사용 안 함")}
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
{t(
|
|
||||||
"ui.dev.clients.general.auto_login.supported",
|
|
||||||
"자동 로그인 지원",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
checked={autoLoginSupported}
|
|
||||||
onCheckedChange={setAutoLoginSupported}
|
|
||||||
id="auto-login-supported"
|
|
||||||
aria-label={t(
|
|
||||||
"ui.dev.clients.general.auto_login.supported",
|
|
||||||
"자동 로그인 지원",
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="auto-login-url" className="text-sm font-semibold">
|
|
||||||
{t(
|
|
||||||
"ui.dev.clients.general.auto_login.url",
|
|
||||||
"자동 로그인 시작 URL",
|
|
||||||
)}
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="auto-login-url"
|
|
||||||
value={autoLoginUrl}
|
|
||||||
onChange={(event) => setAutoLoginUrl(event.target.value)}
|
|
||||||
disabled={!autoLoginSupported}
|
|
||||||
aria-invalid={!hasValidAutoLoginUrl}
|
|
||||||
className={!hasValidAutoLoginUrl ? "border-destructive" : ""}
|
|
||||||
placeholder={t(
|
|
||||||
"ui.dev.clients.general.auto_login.url_placeholder",
|
|
||||||
"https://app.example.com/login?auto=1",
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
{t(
|
|
||||||
"msg.dev.clients.general.auto_login.help",
|
|
||||||
"이 URL은 RP가 state, nonce, PKCE 값을 직접 생성한 뒤 Baron OIDC로 리다이렉트해야 합니다.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
{!hasValidAutoLoginUrl ? (
|
|
||||||
<p className="text-xs text-destructive">
|
|
||||||
{t(
|
|
||||||
"msg.dev.clients.general.auto_login.invalid_url",
|
|
||||||
"자동 로그인 URL 형식이 올바르지 않습니다. http 또는 https 주소를 입력하세요.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* 2. Scopes */}
|
{/* 2. Scopes */}
|
||||||
|
{/* 3. Custom Claims */}
|
||||||
<Card className="glass-panel">
|
<Card className="glass-panel">
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4">
|
||||||
<div>
|
<div>
|
||||||
@@ -2389,229 +2312,6 @@ function ClientGeneralPage() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="glass-panel">
|
|
||||||
<CardHeader className="pb-4">
|
|
||||||
<div className="flex flex-col gap-2 md:flex-row md:items-start md:justify-between">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<CardTitle className="text-xl font-bold">
|
|
||||||
{t(
|
|
||||||
"ui.dev.clients.general.tenant_access.title",
|
|
||||||
"테넌트 접근 제한",
|
|
||||||
)}
|
|
||||||
</CardTitle>
|
|
||||||
<div className="text-sm text-muted-foreground">
|
|
||||||
<p className="leading-relaxed">
|
|
||||||
{t(
|
|
||||||
"ui.dev.clients.general.tenant_access.subtitle",
|
|
||||||
"허용된 테넌트만 이 RP에 접근할 수 있도록 제한합니다.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p className="leading-relaxed">
|
|
||||||
{t(
|
|
||||||
"ui.dev.clients.general.tenant_access.hint",
|
|
||||||
"제한을 켜면 tenants 스코프가 자동으로 포함되며, 허용 테넌트를 하나 이상 선택해야 합니다.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-3 rounded-xl border border-border bg-muted/30 px-4 py-3">
|
|
||||||
<div className="space-y-0.5 text-right">
|
|
||||||
<p className="text-sm font-semibold">
|
|
||||||
{tenantAccessRestricted
|
|
||||||
? t(
|
|
||||||
"ui.dev.clients.general.tenant_access.enabled",
|
|
||||||
"제한 있음",
|
|
||||||
)
|
|
||||||
: t(
|
|
||||||
"ui.dev.clients.general.tenant_access.disabled",
|
|
||||||
"제한 없음",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
{t(
|
|
||||||
"ui.dev.clients.general.tenant_access.title",
|
|
||||||
"테넌트 접근 제한",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
checked={tenantAccessRestricted}
|
|
||||||
onCheckedChange={handleTenantAccessToggle}
|
|
||||||
id="tenant-access-toggle"
|
|
||||||
disabled={isGeneralSettingsReadOnly}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-3">
|
|
||||||
{tenantAccessRestricted ? (
|
|
||||||
<div className="grid gap-4 lg:grid-cols-[0.8fr_1.2fr]">
|
|
||||||
<div className="space-y-3">
|
|
||||||
<Label className="text-sm font-semibold">
|
|
||||||
{t(
|
|
||||||
"ui.dev.clients.general.tenant_access.picker_label",
|
|
||||||
"Add allowed tenant",
|
|
||||||
)}{" "}
|
|
||||||
<span className="text-destructive">*</span>
|
|
||||||
</Label>
|
|
||||||
<TenantAccessPicker
|
|
||||||
disabled={isGeneralSettingsReadOnly}
|
|
||||||
selectedCount={allowedTenantIds.length}
|
|
||||||
onSelectTenant={(selection) =>
|
|
||||||
handleSelectAllowedTenant(selection.id)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-3">
|
|
||||||
<Label className="text-sm font-semibold">
|
|
||||||
{t(
|
|
||||||
"ui.dev.clients.general.tenant_access.selected_title",
|
|
||||||
"허용 테넌트",
|
|
||||||
)}
|
|
||||||
</Label>
|
|
||||||
<SettingsTableShell bodyClassName="max-h-80">
|
|
||||||
<SettingsTable>
|
|
||||||
<SettingsTableHeader className="sticky top-0 z-10">
|
|
||||||
<tr>
|
|
||||||
<SettingsTableHead className="w-[28%]">
|
|
||||||
{t(
|
|
||||||
"ui.dev.clients.general.tenant_access.table.name",
|
|
||||||
"테넌트명",
|
|
||||||
)}
|
|
||||||
</SettingsTableHead>
|
|
||||||
<SettingsTableHead className="w-[18%]">
|
|
||||||
{t(
|
|
||||||
"ui.dev.clients.general.tenant_access.table.slug",
|
|
||||||
"슬러그",
|
|
||||||
)}
|
|
||||||
</SettingsTableHead>
|
|
||||||
<SettingsTableHead>
|
|
||||||
{t(
|
|
||||||
"ui.dev.clients.general.tenant_access.table.id",
|
|
||||||
"테넌트 ID",
|
|
||||||
)}
|
|
||||||
</SettingsTableHead>
|
|
||||||
<SettingsTableHead className="w-[112px] text-right">
|
|
||||||
{t(
|
|
||||||
"ui.dev.clients.general.tenant_access.table.actions",
|
|
||||||
"작업",
|
|
||||||
)}
|
|
||||||
</SettingsTableHead>
|
|
||||||
</tr>
|
|
||||||
</SettingsTableHeader>
|
|
||||||
<SettingsTableBody>
|
|
||||||
{selectedAllowedTenants.length > 0 ? (
|
|
||||||
<>
|
|
||||||
{selectedAllowedTenants.map((tenant) => (
|
|
||||||
<SettingsTableRow
|
|
||||||
key={tenant.id}
|
|
||||||
data-testid={`allowed-tenant-${tenant.id}`}
|
|
||||||
>
|
|
||||||
<SettingsTableCell className="align-middle font-medium">
|
|
||||||
<span className="block truncate">
|
|
||||||
{tenant.name}
|
|
||||||
</span>
|
|
||||||
</SettingsTableCell>
|
|
||||||
<SettingsTableCell className="align-middle text-muted-foreground">
|
|
||||||
{tenant.slug || "-"}
|
|
||||||
</SettingsTableCell>
|
|
||||||
<SettingsTableCell className="align-middle font-mono text-sm text-muted-foreground">
|
|
||||||
<span className="break-all">{tenant.id}</span>
|
|
||||||
</SettingsTableCell>
|
|
||||||
<SettingsTableCell className="align-middle text-right">
|
|
||||||
<div className="inline-flex items-center gap-1.5">
|
|
||||||
<CopyButton
|
|
||||||
aria-label="테넌트 UUID 복사"
|
|
||||||
className="h-8 w-8"
|
|
||||||
data-testid={`allowed-tenant-copy-${tenant.id}`}
|
|
||||||
size="icon"
|
|
||||||
value={tenant.id}
|
|
||||||
variant="ghost"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
aria-label={t("ui.common.delete", "삭제")}
|
|
||||||
onClick={() =>
|
|
||||||
toggleAllowedTenant(tenant.id)
|
|
||||||
}
|
|
||||||
className="inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition hover:text-destructive"
|
|
||||||
data-testid={`allowed-tenant-remove-${tenant.id}`}
|
|
||||||
disabled={isGeneralSettingsReadOnly}
|
|
||||||
>
|
|
||||||
<Trash2 className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</SettingsTableCell>
|
|
||||||
</SettingsTableRow>
|
|
||||||
))}
|
|
||||||
{allowedTenantIds
|
|
||||||
.filter(
|
|
||||||
(tenantId) =>
|
|
||||||
!selectedAllowedTenants.some(
|
|
||||||
(tenant) => tenant.id === tenantId,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.map((tenantId) => (
|
|
||||||
<SettingsTableRow
|
|
||||||
key={tenantId}
|
|
||||||
data-testid={`allowed-tenant-${tenantId}`}
|
|
||||||
>
|
|
||||||
<SettingsTableCell className="align-middle font-medium">
|
|
||||||
<span className="block truncate">
|
|
||||||
{tenantId}
|
|
||||||
</span>
|
|
||||||
</SettingsTableCell>
|
|
||||||
<SettingsTableCell className="align-middle text-muted-foreground">
|
|
||||||
-
|
|
||||||
</SettingsTableCell>
|
|
||||||
<SettingsTableCell className="align-middle font-mono text-sm text-muted-foreground">
|
|
||||||
<span className="break-all">{tenantId}</span>
|
|
||||||
</SettingsTableCell>
|
|
||||||
<SettingsTableCell className="align-middle text-right">
|
|
||||||
<div className="inline-flex items-center gap-1.5">
|
|
||||||
<CopyButton
|
|
||||||
aria-label="테넌트 UUID 복사"
|
|
||||||
className="h-8 w-8"
|
|
||||||
data-testid={`allowed-tenant-copy-${tenantId}`}
|
|
||||||
size="icon"
|
|
||||||
value={tenantId}
|
|
||||||
variant="ghost"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
aria-label={t("ui.common.delete", "삭제")}
|
|
||||||
onClick={() =>
|
|
||||||
toggleAllowedTenant(tenantId)
|
|
||||||
}
|
|
||||||
className="inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition hover:text-destructive"
|
|
||||||
data-testid={`allowed-tenant-remove-${tenantId}`}
|
|
||||||
disabled={isGeneralSettingsReadOnly}
|
|
||||||
>
|
|
||||||
<Trash2 className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</SettingsTableCell>
|
|
||||||
</SettingsTableRow>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<SettingsTableEmptyState colSpan={4} className="py-4">
|
|
||||||
{t(
|
|
||||||
"ui.dev.clients.general.tenant_access.selected_empty",
|
|
||||||
"아직 선택된 테넌트가 없습니다.",
|
|
||||||
)}
|
|
||||||
</SettingsTableEmptyState>
|
|
||||||
)}
|
|
||||||
</SettingsTableBody>
|
|
||||||
</SettingsTable>
|
|
||||||
</SettingsTableShell>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="glass-panel">
|
<Card className="glass-panel">
|
||||||
<CardHeader className="pb-4">
|
<CardHeader className="pb-4">
|
||||||
<div className="flex flex-col gap-2 md:flex-row md:items-start md:justify-between">
|
<div className="flex flex-col gap-2 md:flex-row md:items-start md:justify-between">
|
||||||
@@ -3029,7 +2729,310 @@ function ClientGeneralPage() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* 3. Security Settings */}
|
{/* 4. Tenant Access Restriction */}
|
||||||
|
<Card className="glass-panel">
|
||||||
|
<CardHeader className="pb-4">
|
||||||
|
<div className="flex flex-col gap-2 md:flex-row md:items-start md:justify-between">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<CardTitle className="text-xl font-bold">
|
||||||
|
{t(
|
||||||
|
"ui.dev.clients.general.tenant_access.title",
|
||||||
|
"테넌트 접근 제한",
|
||||||
|
)}
|
||||||
|
</CardTitle>
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
<p className="leading-relaxed">
|
||||||
|
{t(
|
||||||
|
"ui.dev.clients.general.tenant_access.subtitle",
|
||||||
|
"허용된 테넌트만 이 RP에 접근할 수 있도록 제한합니다.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p className="leading-relaxed">
|
||||||
|
{t(
|
||||||
|
"ui.dev.clients.general.tenant_access.hint",
|
||||||
|
"제한을 켜면 tenants 스코프가 자동으로 포함되며, 허용 테넌트를 하나 이상 선택해야 합니다.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3 rounded-xl border border-border bg-muted/30 px-4 py-3">
|
||||||
|
<div className="space-y-0.5 text-right">
|
||||||
|
<p className="text-sm font-semibold">
|
||||||
|
{tenantAccessRestricted
|
||||||
|
? t(
|
||||||
|
"ui.dev.clients.general.tenant_access.enabled",
|
||||||
|
"제한 있음",
|
||||||
|
)
|
||||||
|
: t(
|
||||||
|
"ui.dev.clients.general.tenant_access.disabled",
|
||||||
|
"제한 없음",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{t(
|
||||||
|
"ui.dev.clients.general.tenant_access.title",
|
||||||
|
"테넌트 접근 제한",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={tenantAccessRestricted}
|
||||||
|
onCheckedChange={handleTenantAccessToggle}
|
||||||
|
id="tenant-access-toggle"
|
||||||
|
disabled={isGeneralSettingsReadOnly}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-3">
|
||||||
|
{tenantAccessRestricted ? (
|
||||||
|
<div className="grid gap-4 lg:grid-cols-[0.8fr_1.2fr]">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Label className="text-sm font-semibold">
|
||||||
|
{t(
|
||||||
|
"ui.dev.clients.general.tenant_access.picker_label",
|
||||||
|
"Add allowed tenant",
|
||||||
|
)}{" "}
|
||||||
|
<span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<TenantAccessPicker
|
||||||
|
disabled={isGeneralSettingsReadOnly}
|
||||||
|
selectedCount={allowedTenantIds.length}
|
||||||
|
onSelectTenant={(selection) =>
|
||||||
|
handleSelectAllowedTenant(selection.id)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Label className="text-sm font-semibold">
|
||||||
|
{t(
|
||||||
|
"ui.dev.clients.general.tenant_access.selected_title",
|
||||||
|
"허용 테넌트",
|
||||||
|
)}
|
||||||
|
</Label>
|
||||||
|
<SettingsTableShell bodyClassName="max-h-80">
|
||||||
|
<SettingsTable>
|
||||||
|
<SettingsTableHeader className="sticky top-0 z-10">
|
||||||
|
<tr>
|
||||||
|
<SettingsTableHead className="w-[28%]">
|
||||||
|
{t(
|
||||||
|
"ui.dev.clients.general.tenant_access.table.name",
|
||||||
|
"테넌트명",
|
||||||
|
)}
|
||||||
|
</SettingsTableHead>
|
||||||
|
<SettingsTableHead className="w-[18%]">
|
||||||
|
{t(
|
||||||
|
"ui.dev.clients.general.tenant_access.table.slug",
|
||||||
|
"슬러그",
|
||||||
|
)}
|
||||||
|
</SettingsTableHead>
|
||||||
|
<SettingsTableHead>
|
||||||
|
{t(
|
||||||
|
"ui.dev.clients.general.tenant_access.table.id",
|
||||||
|
"테넌트 ID",
|
||||||
|
)}
|
||||||
|
</SettingsTableHead>
|
||||||
|
<SettingsTableHead className="w-[112px] text-right">
|
||||||
|
{t(
|
||||||
|
"ui.dev.clients.general.tenant_access.table.actions",
|
||||||
|
"작업",
|
||||||
|
)}
|
||||||
|
</SettingsTableHead>
|
||||||
|
</tr>
|
||||||
|
</SettingsTableHeader>
|
||||||
|
<SettingsTableBody>
|
||||||
|
{selectedAllowedTenants.length > 0 ? (
|
||||||
|
<>
|
||||||
|
{selectedAllowedTenants.map((tenant) => (
|
||||||
|
<SettingsTableRow
|
||||||
|
key={tenant.id}
|
||||||
|
data-testid={`allowed-tenant-${tenant.id}`}
|
||||||
|
>
|
||||||
|
<SettingsTableCell className="align-middle font-medium">
|
||||||
|
<span className="block truncate">
|
||||||
|
{tenant.name}
|
||||||
|
</span>
|
||||||
|
</SettingsTableCell>
|
||||||
|
<SettingsTableCell className="align-middle text-muted-foreground">
|
||||||
|
{tenant.slug || "-"}
|
||||||
|
</SettingsTableCell>
|
||||||
|
<SettingsTableCell className="align-middle font-mono text-sm text-muted-foreground">
|
||||||
|
<span className="break-all">{tenant.id}</span>
|
||||||
|
</SettingsTableCell>
|
||||||
|
<SettingsTableCell className="align-middle text-right">
|
||||||
|
<div className="inline-flex items-center gap-1.5">
|
||||||
|
<CopyButton
|
||||||
|
aria-label="테넌트 UUID 복사"
|
||||||
|
className="h-8 w-8"
|
||||||
|
data-testid={`allowed-tenant-copy-${tenant.id}`}
|
||||||
|
size="icon"
|
||||||
|
value={tenant.id}
|
||||||
|
variant="ghost"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label={t("ui.common.delete", "삭제")}
|
||||||
|
onClick={() =>
|
||||||
|
toggleAllowedTenant(tenant.id)
|
||||||
|
}
|
||||||
|
className="inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition hover:text-destructive"
|
||||||
|
data-testid={`allowed-tenant-remove-${tenant.id}`}
|
||||||
|
disabled={isGeneralSettingsReadOnly}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</SettingsTableCell>
|
||||||
|
</SettingsTableRow>
|
||||||
|
))}
|
||||||
|
{allowedTenantIds
|
||||||
|
.filter(
|
||||||
|
(tenantId) =>
|
||||||
|
!selectedAllowedTenants.some(
|
||||||
|
(tenant) => tenant.id === tenantId,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.map((tenantId) => (
|
||||||
|
<SettingsTableRow
|
||||||
|
key={tenantId}
|
||||||
|
data-testid={`allowed-tenant-${tenantId}`}
|
||||||
|
>
|
||||||
|
<SettingsTableCell className="align-middle font-medium">
|
||||||
|
<span className="block truncate">
|
||||||
|
{tenantId}
|
||||||
|
</span>
|
||||||
|
</SettingsTableCell>
|
||||||
|
<SettingsTableCell className="align-middle text-muted-foreground">
|
||||||
|
-
|
||||||
|
</SettingsTableCell>
|
||||||
|
<SettingsTableCell className="align-middle font-mono text-sm text-muted-foreground">
|
||||||
|
<span className="break-all">{tenantId}</span>
|
||||||
|
</SettingsTableCell>
|
||||||
|
<SettingsTableCell className="align-middle text-right">
|
||||||
|
<div className="inline-flex items-center gap-1.5">
|
||||||
|
<CopyButton
|
||||||
|
aria-label="테넌트 UUID 복사"
|
||||||
|
className="h-8 w-8"
|
||||||
|
data-testid={`allowed-tenant-copy-${tenantId}`}
|
||||||
|
size="icon"
|
||||||
|
value={tenantId}
|
||||||
|
variant="ghost"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label={t("ui.common.delete", "삭제")}
|
||||||
|
onClick={() =>
|
||||||
|
toggleAllowedTenant(tenantId)
|
||||||
|
}
|
||||||
|
className="inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition hover:text-destructive"
|
||||||
|
data-testid={`allowed-tenant-remove-${tenantId}`}
|
||||||
|
disabled={isGeneralSettingsReadOnly}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</SettingsTableCell>
|
||||||
|
</SettingsTableRow>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<SettingsTableEmptyState colSpan={4} className="py-4">
|
||||||
|
{t(
|
||||||
|
"ui.dev.clients.general.tenant_access.selected_empty",
|
||||||
|
"아직 선택된 테넌트가 없습니다.",
|
||||||
|
)}
|
||||||
|
</SettingsTableEmptyState>
|
||||||
|
)}
|
||||||
|
</SettingsTableBody>
|
||||||
|
</SettingsTable>
|
||||||
|
</SettingsTableShell>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 5. Auto Login */}
|
||||||
|
<Card className="glass-panel">
|
||||||
|
<CardHeader className="pb-4">
|
||||||
|
<div className="flex flex-col gap-2 md:flex-row md:items-start md:justify-between">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<CardTitle className="text-xl font-bold">
|
||||||
|
{t("ui.dev.clients.general.auto_login.title", "자동 로그인")}
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
{t(
|
||||||
|
"msg.dev.clients.general.auto_login.subtitle",
|
||||||
|
"RP가 자체 로그인 시작 URL에서 OIDC 요청을 만들 수 있으면 userfront에서 바로 로그인 진입을 제공합니다.",
|
||||||
|
)}
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3 rounded-xl border border-border bg-muted/30 px-4 py-3">
|
||||||
|
<div className="space-y-0.5 text-right">
|
||||||
|
<p className="text-sm font-semibold">
|
||||||
|
{autoLoginSupported
|
||||||
|
? t("ui.common.enabled", "사용")
|
||||||
|
: t("ui.common.disabled", "사용 안 함")}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{t(
|
||||||
|
"ui.dev.clients.general.auto_login.supported",
|
||||||
|
"자동 로그인 지원",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={autoLoginSupported}
|
||||||
|
onCheckedChange={setAutoLoginSupported}
|
||||||
|
id="auto-login-supported"
|
||||||
|
aria-label={t(
|
||||||
|
"ui.dev.clients.general.auto_login.supported",
|
||||||
|
"자동 로그인 지원",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="auto-login-url" className="text-sm font-semibold">
|
||||||
|
{t(
|
||||||
|
"ui.dev.clients.general.auto_login.url",
|
||||||
|
"자동 로그인 시작 URL",
|
||||||
|
)}
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="auto-login-url"
|
||||||
|
value={autoLoginUrl}
|
||||||
|
onChange={(event) => setAutoLoginUrl(event.target.value)}
|
||||||
|
disabled={!autoLoginSupported}
|
||||||
|
aria-invalid={!hasValidAutoLoginUrl}
|
||||||
|
className={!hasValidAutoLoginUrl ? "border-destructive" : ""}
|
||||||
|
placeholder={t(
|
||||||
|
"ui.dev.clients.general.auto_login.url_placeholder",
|
||||||
|
"https://app.example.com/login?auto=1",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{t(
|
||||||
|
"msg.dev.clients.general.auto_login.help",
|
||||||
|
"이 URL은 RP가 state, nonce, PKCE 값을 직접 생성한 뒤 Baron OIDC로 리다이렉트해야 합니다.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
{!hasValidAutoLoginUrl ? (
|
||||||
|
<p className="text-xs text-destructive">
|
||||||
|
{t(
|
||||||
|
"msg.dev.clients.general.auto_login.invalid_url",
|
||||||
|
"자동 로그인 URL 형식이 올바르지 않습니다. http 또는 https 주소를 입력하세요.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 6. Security Settings */}
|
||||||
<Card className="glass-panel">
|
<Card className="glass-panel">
|
||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
<CardTitle className="text-xl font-bold">
|
<CardTitle className="text-xl font-bold">
|
||||||
|
|||||||
Reference in New Issue
Block a user