forked from baron/baron-sso
devfront UI 일관성 및 정렬 정책 적용
This commit is contained in:
@@ -197,7 +197,20 @@ function AppLayout() {
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
<div className="hidden space-y-2 px-5 pb-6 text-xs text-[var(--color-muted)] md:block">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="px-3 pt-4 border-t border-border/50">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleLogout}
|
||||
className="w-full flex items-center gap-3 rounded-xl px-3 py-3 text-sm transition text-muted-foreground hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<LogOut size={18} />
|
||||
<span>{t("ui.dev.nav.logout", "Logout")}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="hidden space-y-2 px-5 pb-6 pt-2 text-xs text-[var(--color-muted)] md:block">
|
||||
<p>{t("msg.dev.sidebar.notice", "개발자 전용 콘솔입니다.")}</p>
|
||||
<p>
|
||||
{t(
|
||||
@@ -207,17 +220,6 @@ function AppLayout() {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-2 pb-6 md:px-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleLogout}
|
||||
className="flex w-full items-center gap-3 rounded-xl px-3 py-3 text-sm text-muted-foreground transition hover:bg-muted/10 hover:text-foreground"
|
||||
>
|
||||
<LogOut size={18} />
|
||||
<span>{t("ui.dev.nav.logout", "Logout")}</span>
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div className="relative">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import type { AxiosError } from "axios";
|
||||
import { Plus, Shield, Sparkles, Trash2, Upload } from "lucide-react";
|
||||
import { Plus, Save, Shield, Sparkles, Trash2, Upload } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Link, useNavigate, useParams } from "react-router-dom";
|
||||
import { Badge } from "../../components/ui/badge";
|
||||
@@ -16,6 +16,7 @@ import { Input } from "../../components/ui/input";
|
||||
import { Label } from "../../components/ui/label";
|
||||
import { Switch } from "../../components/ui/switch";
|
||||
import { Textarea } from "../../components/ui/textarea";
|
||||
import { toast } from "../../components/ui/use-toast";
|
||||
import {
|
||||
createClient,
|
||||
deleteClient,
|
||||
@@ -128,6 +129,21 @@ function ClientGeneralPage() {
|
||||
setScopes(scopes.filter((s) => s.id !== id));
|
||||
};
|
||||
|
||||
const handleStatusChange = (nextStatus: ClientStatus) => {
|
||||
setStatus(nextStatus);
|
||||
const statusLabel =
|
||||
nextStatus === "active"
|
||||
? t("ui.common.status.active", "Active")
|
||||
: t("ui.common.status.inactive", "Inactive");
|
||||
toast(
|
||||
t(
|
||||
"msg.dev.clients.general.status_changed",
|
||||
"상태가 {{status}}로 변경되었습니다.",
|
||||
{ status: statusLabel },
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
const scopeNames = scopes.map((scope) => scope.name).filter(Boolean);
|
||||
@@ -204,7 +220,7 @@ function ClientGeneralPage() {
|
||||
window.confirm(
|
||||
t(
|
||||
"msg.dev.clients.delete_confirm",
|
||||
"정말로 이 앱을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.",
|
||||
"정말로 이 앱을 삭제하시습니까? 이 작업은 되돌릴 수 없습니다.",
|
||||
),
|
||||
)
|
||||
) {
|
||||
@@ -309,33 +325,6 @@ function ClientGeneralPage() {
|
||||
)}
|
||||
</CardDescription>
|
||||
</div>
|
||||
{!isCreate && (
|
||||
<div className="flex flex-col items-end gap-2">
|
||||
<Label className="text-xs font-bold uppercase tracking-wider text-muted-foreground">
|
||||
{t("ui.dev.clients.table.status", "상태")}
|
||||
</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
checked={status === "active"}
|
||||
onCheckedChange={(checked) =>
|
||||
setStatus(checked ? "active" : "inactive")
|
||||
}
|
||||
/>
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm font-medium",
|
||||
status === "active"
|
||||
? "text-emerald-400"
|
||||
: "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{status === "active"
|
||||
? t("ui.common.status.active", "활성")
|
||||
: t("ui.common.status.inactive", "비활성")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid gap-8 md:grid-cols-2">
|
||||
<div className="space-y-5">
|
||||
@@ -371,40 +360,66 @@ function ClientGeneralPage() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-semibold">
|
||||
{t("ui.dev.clients.general.identity.logo", "App Logo URL")}
|
||||
</Label>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex-1 space-y-2">
|
||||
<Input
|
||||
value={logoUrl}
|
||||
onChange={(e) => setLogoUrl(e.target.value)}
|
||||
placeholder={t(
|
||||
"ui.dev.clients.general.identity.logo_placeholder",
|
||||
"https://example.com/logo.png",
|
||||
)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t(
|
||||
"msg.dev.clients.general.identity.logo_help",
|
||||
"인증 화면에 표시될 PNG/SVG URL입니다.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex h-20 w-20 items-center justify-center rounded-lg border-2 border-dashed border-border bg-muted/40 shrink-0">
|
||||
{logoUrl ? (
|
||||
<img
|
||||
src={logoUrl}
|
||||
alt={t(
|
||||
"ui.dev.clients.general.identity.logo_preview",
|
||||
"Logo Preview",
|
||||
<div className="space-y-5">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-semibold">
|
||||
{t("ui.dev.clients.general.identity.logo", "App Logo URL")}
|
||||
</Label>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex-1 space-y-2">
|
||||
<Input
|
||||
value={logoUrl}
|
||||
onChange={(e) => setLogoUrl(e.target.value)}
|
||||
placeholder={t(
|
||||
"ui.dev.clients.general.identity.logo_placeholder",
|
||||
"https://example.com/logo.png",
|
||||
)}
|
||||
className="h-full w-full object-contain"
|
||||
/>
|
||||
) : (
|
||||
<Upload className="h-5 w-5 text-muted-foreground" />
|
||||
)}
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t(
|
||||
"msg.dev.clients.general.identity.logo_help",
|
||||
"인증 화면에 표시될 PNG/SVG URL입니다.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex h-20 w-20 items-center justify-center rounded-lg border-2 border-dashed border-border bg-muted/40 shrink-0">
|
||||
{logoUrl ? (
|
||||
<img
|
||||
src={logoUrl}
|
||||
alt={t(
|
||||
"ui.dev.clients.general.identity.logo_preview",
|
||||
"Logo Preview",
|
||||
)}
|
||||
className="h-full w-full object-contain"
|
||||
/>
|
||||
) : (
|
||||
<Upload className="h-5 w-5 text-muted-foreground" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-semibold">
|
||||
{t("ui.dev.clients.table.status", "상태")}
|
||||
</Label>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant={status === "active" ? "default" : "outline"}
|
||||
onClick={() => handleStatusChange("active")}
|
||||
>
|
||||
{t("ui.common.status.active", "활성")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant={status === "inactive" ? "default" : "outline"}
|
||||
onClick={() => handleStatusChange("inactive")}
|
||||
>
|
||||
{t("ui.common.status.inactive", "비활성")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -658,20 +673,30 @@ function ClientGeneralPage() {
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex flex-col-reverse sm:flex-row sm:justify-end gap-2">
|
||||
<Button variant="outline" onClick={() => navigate("/clients")}>
|
||||
{t("ui.common.cancel", "취소")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => mutation.mutate()}
|
||||
disabled={mutation.isPending}
|
||||
className="px-8 shadow-lg shadow-primary/20"
|
||||
disabled={
|
||||
mutation.isPending ||
|
||||
isLoading ||
|
||||
name.trim() === "" ||
|
||||
(isCreate && redirectUris.trim() === "")
|
||||
}
|
||||
className="shadow-lg shadow-primary/20"
|
||||
>
|
||||
{mutation.isPending ? (
|
||||
<div className="h-4 w-4 border-2 border-white/30 border-t-white rounded-full animate-spin mr-2" />
|
||||
) : (
|
||||
<Save size={16} className="mr-2" />
|
||||
)}
|
||||
{mutation.isPending
|
||||
? t("msg.common.saving", "저장 중...")
|
||||
: isCreate
|
||||
? t("ui.dev.clients.general.create", "클라이언트 생성")
|
||||
: t("ui.dev.clients.general.save", "설정 저장")}
|
||||
: t("ui.common.save", "저장")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,30 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { Plus, Trash2, Edit, Globe, Save } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "../../../components/ui/card";
|
||||
import { Input } from "../../../components/ui/input";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "../../../components/ui/table";
|
||||
import {
|
||||
createIdpConfigForClient,
|
||||
listIdpConfigsForClient,
|
||||
} from "../../../lib/devApi";
|
||||
import type { IdpConfig, IdpConfigCreateRequest } from "../../../lib/devApi";
|
||||
import { t } from "../../../lib/i18n";
|
||||
|
||||
// Proper Modal Component with Form
|
||||
const CreateIdpModal = ({
|
||||
@@ -37,12 +56,10 @@ const CreateIdpModal = ({
|
||||
onClose();
|
||||
},
|
||||
onError: (error) => {
|
||||
// Basic error handling
|
||||
alert(`Failed to create configuration: ${error.message}`);
|
||||
},
|
||||
});
|
||||
|
||||
// 이 내용으로 교체해주세요
|
||||
const handleChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>,
|
||||
) => {
|
||||
@@ -61,104 +78,89 @@ const CreateIdpModal = ({
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white p-6 rounded-lg shadow-xl w-full max-w-lg">
|
||||
<h2 className="text-xl font-bold mb-4">Add New IdP Configuration</h2>
|
||||
<form onSubmit={handleSubmit}>
|
||||
{/* Display Name */}
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700 text-sm font-bold mb-2">
|
||||
Display Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="display_name"
|
||||
value={formData.display_name}
|
||||
onChange={handleChange}
|
||||
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50 backdrop-blur-sm">
|
||||
<Card className="w-full max-w-lg shadow-2xl animate-in zoom-in-95 duration-200">
|
||||
<CardHeader>
|
||||
<CardTitle>{t("ui.dev.clients.federation.add_title", "Add Identity Provider")}</CardTitle>
|
||||
<CardDescription>
|
||||
{t("msg.dev.clients.federation.add_subtitle", "Connect an external OIDC provider.")}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-semibold">Display Name</label>
|
||||
<Input
|
||||
name="display_name"
|
||||
value={formData.display_name}
|
||||
onChange={handleChange}
|
||||
placeholder="e.g. Google Workspace"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Issuer URL */}
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700 text-sm font-bold mb-2">
|
||||
Issuer URL
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
name="issuer_url"
|
||||
value={formData.issuer_url}
|
||||
onChange={handleChange}
|
||||
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
||||
placeholder="https://accounts.google.com"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-semibold">Issuer URL</label>
|
||||
<Input
|
||||
type="url"
|
||||
name="issuer_url"
|
||||
value={formData.issuer_url}
|
||||
onChange={handleChange}
|
||||
placeholder="https://accounts.google.com"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Client ID */}
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700 text-sm font-bold mb-2">
|
||||
Client ID
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="oidc_client_id"
|
||||
value={formData.oidc_client_id}
|
||||
onChange={handleChange}
|
||||
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-semibold">Client ID</label>
|
||||
<Input
|
||||
name="oidc_client_id"
|
||||
value={formData.oidc_client_id}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-semibold">Client Secret</label>
|
||||
<Input
|
||||
type="password"
|
||||
name="oidc_client_secret"
|
||||
value={formData.oidc_client_secret}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Client Secret */}
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700 text-sm font-bold mb-2">
|
||||
Client Secret
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="oidc_client_secret"
|
||||
value={formData.oidc_client_secret}
|
||||
onChange={handleChange}
|
||||
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-semibold">Scopes</label>
|
||||
<Input
|
||||
name="scopes"
|
||||
value={formData.scopes}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Scopes */}
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700 text-sm font-bold mb-2">
|
||||
Scopes
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="scopes"
|
||||
value={formData.scopes}
|
||||
onChange={handleChange}
|
||||
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex items-center justify-end">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded mr-2"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={mutation.isPending}
|
||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50"
|
||||
>
|
||||
{mutation.isPending ? "Saving..." : "Save Configuration"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div className="flex flex-col-reverse sm:flex-row sm:justify-end gap-2 pt-4">
|
||||
<Button type="button" variant="outline" onClick={onClose}>
|
||||
{t("ui.common.cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={mutation.isPending || formData.display_name.trim() === "" || formData.issuer_url.trim() === ""}
|
||||
>
|
||||
{mutation.isPending ? (
|
||||
<div className="h-4 w-4 border-2 border-white/30 border-t-white rounded-full animate-spin mr-2" />
|
||||
) : (
|
||||
<Save size={16} className="mr-2" />
|
||||
)}
|
||||
{mutation.isPending ? t("msg.common.saving", "Saving...") : t("ui.common.save", "Save Configuration")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -168,7 +170,7 @@ export function ClientFederationPage() {
|
||||
const [isCreateModalOpen, setCreateModalOpen] = useState(false);
|
||||
|
||||
if (!clientId) {
|
||||
return <div>Client ID is missing</div>;
|
||||
return <div className="p-8 text-center text-destructive">Client ID is missing</div>;
|
||||
}
|
||||
|
||||
const { data, isLoading, error } = useQuery({
|
||||
@@ -177,94 +179,92 @@ export function ClientFederationPage() {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h1 className="text-2xl font-bold mb-4">Identity Federation Settings</h1>
|
||||
<p className="mb-4 text-gray-600">
|
||||
Manage external identity providers for this application.
|
||||
</p>
|
||||
<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>
|
||||
|
||||
<div className="mb-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setCreateModalOpen(true)}
|
||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
||||
>
|
||||
+ Add IdP Configuration
|
||||
</button>
|
||||
</div>
|
||||
<Card className="glass-panel">
|
||||
<CardContent className="p-0">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Display Name</TableHead>
|
||||
<TableHead>Provider Type</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{isLoading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} className="h-24 text-center">
|
||||
{t("msg.common.loading", "Loading...")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : error ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} className="h-24 text-center text-destructive">
|
||||
{(error as Error).message}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : data?.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} className="h-24 text-center text-muted-foreground">
|
||||
{t("msg.dev.clients.federation.empty", "No IdP configurations found.")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
data?.map((config: IdpConfig) => (
|
||||
<tr key={config.id} className="border-b transition-colors hover:bg-muted/50">
|
||||
<TableCell className="font-medium">{config.display_name}</TableCell>
|
||||
<TableCell>{config.provider_type.toUpperCase()}</TableCell>
|
||||
<TableCell>
|
||||
<span
|
||||
className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold ${
|
||||
config.status === "active"
|
||||
? "bg-emerald-500/10 text-emerald-500"
|
||||
: "bg-muted text-muted-foreground"
|
||||
}`}
|
||||
>
|
||||
{config.status}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8">
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 text-destructive">
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<CreateIdpModal
|
||||
isOpen={isCreateModalOpen}
|
||||
onClose={() => setCreateModalOpen(false)}
|
||||
clientId={clientId}
|
||||
/>
|
||||
|
||||
{isLoading && <div>Loading configurations...</div>}
|
||||
{error && (
|
||||
<div className="text-red-500">
|
||||
Failed to load configurations: {error.message}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data && (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full bg-white">
|
||||
<thead className="bg-gray-200">
|
||||
<tr>
|
||||
<th className="py-2 px-4 border-b">Display Name</th>
|
||||
<th className="py-2 px-4 border-b">Provider Type</th>
|
||||
<th className="py-2 px-4 border-b">Status</th>
|
||||
<th className="py-2 px-4 border-b">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={4} className="text-center py-4">
|
||||
No IdP configurations found.
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
data.map((config: IdpConfig) => (
|
||||
<tr key={config.id}>
|
||||
<td className="py-2 px-4 border-b">
|
||||
{config.display_name}
|
||||
</td>
|
||||
<td className="py-2 px-4 border-b">
|
||||
{config.provider_type.toUpperCase()}
|
||||
</td>
|
||||
<td className="py-2 px-4 border-b">
|
||||
<span
|
||||
className={`px-2 py-1 text-xs font-semibold rounded-full ${
|
||||
config.status === "active"
|
||||
? "bg-green-200 text-green-800"
|
||||
: "bg-gray-200 text-gray-800"
|
||||
}`}
|
||||
>
|
||||
{config.status}
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-2 px-4 border-b">
|
||||
<button
|
||||
type="button"
|
||||
className="text-blue-500 hover:underline mr-2"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="text-red-500 hover:underline"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -252,6 +252,7 @@ load_error = "Error loading client: {{error}}"
|
||||
loading = "Loading client..."
|
||||
saved = "Saved"
|
||||
save_error = "Failed to save: {{error}}"
|
||||
status_changed = "Status changed to {{status}}."
|
||||
|
||||
[msg.dev.clients.general.identity]
|
||||
logo_help = "Logo Help"
|
||||
|
||||
@@ -252,6 +252,7 @@ load_error = "Error loading client: {{error}}"
|
||||
loading = "Loading client..."
|
||||
saved = "설정이 저장되었습니다."
|
||||
save_error = "저장 실패: {{error}}"
|
||||
status_changed = "상태가 {{status}}로 변경되었습니다."
|
||||
|
||||
[msg.dev.clients.general.identity]
|
||||
logo_help = "인증 화면에 표시될 PNG/SVG URL입니다."
|
||||
|
||||
@@ -252,6 +252,7 @@ load_error = ""
|
||||
loading = ""
|
||||
saved = ""
|
||||
save_error = ""
|
||||
status_changed = ""
|
||||
|
||||
[msg.dev.clients.general.identity]
|
||||
logo_help = ""
|
||||
|
||||
@@ -1168,7 +1168,7 @@ settings = "Settings"
|
||||
[ui.dev.clients.general]
|
||||
create = "Create Application"
|
||||
display_new = "Add Connected Application"
|
||||
save = "Settings Save"
|
||||
save = "Save"
|
||||
title_create = "Create Client"
|
||||
title_edit = "Client Settings"
|
||||
|
||||
|
||||
@@ -1168,7 +1168,7 @@ settings = "Settings"
|
||||
[ui.dev.clients.general]
|
||||
create = "앱 생성"
|
||||
display_new = "연동 앱 추가"
|
||||
save = "설정 저장"
|
||||
save = "저장"
|
||||
title_create = "Create Client"
|
||||
title_edit = "Client Settings"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user