forked from baron/baron-sso
Merge pull request 'feature/ui-refactor' (#339) from feature/ui-refactor into dev
Reviewed-on: baron/baron-sso#339
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,100 @@ 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 +181,11 @@ 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 +194,113 @@ 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,12 @@ 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.federation]
|
||||
subtitle = "Manage external identity providers for this application."
|
||||
add_subtitle = "Connect an external OIDC provider."
|
||||
empty = "No IdP configurations found."
|
||||
|
||||
[msg.dev.clients.general.identity]
|
||||
logo_help = "Logo Help"
|
||||
@@ -1019,10 +1025,14 @@ settings = "Settings"
|
||||
[ui.dev.clients.general]
|
||||
create = "Create Application"
|
||||
display_new = "Add Connected Application"
|
||||
save = "Settings Save"
|
||||
title_create = "Create Client"
|
||||
title_edit = "Client Settings"
|
||||
|
||||
[ui.dev.clients.federation]
|
||||
title = "Identity Federation"
|
||||
add_title = "Add Identity Provider"
|
||||
add_btn = "Add Provider"
|
||||
|
||||
[ui.dev.clients.general.breadcrumb]
|
||||
section = "Applications"
|
||||
|
||||
|
||||
@@ -252,6 +252,12 @@ load_error = "Error loading client: {{error}}"
|
||||
loading = "Loading client..."
|
||||
saved = "설정이 저장되었습니다."
|
||||
save_error = "저장 실패: {{error}}"
|
||||
status_changed = "상태가 {{status}}로 변경되었습니다."
|
||||
|
||||
[msg.dev.clients.federation]
|
||||
subtitle = "이 애플리케이션의 외부 IdP 설정을 관리합니다."
|
||||
add_subtitle = "외부 OIDC 제공자를 연결합니다."
|
||||
empty = "등록된 IdP 설정이 없습니다."
|
||||
|
||||
[msg.dev.clients.general.identity]
|
||||
logo_help = "인증 화면에 표시될 PNG/SVG URL입니다."
|
||||
@@ -1019,10 +1025,14 @@ settings = "Settings"
|
||||
[ui.dev.clients.general]
|
||||
create = "앱 생성"
|
||||
display_new = "연동 앱 추가"
|
||||
save = "설정 저장"
|
||||
title_create = "Create Client"
|
||||
title_edit = "Client Settings"
|
||||
|
||||
[ui.dev.clients.federation]
|
||||
title = "Identity Federation"
|
||||
add_title = "Add Identity Provider"
|
||||
add_btn = "Add Provider"
|
||||
|
||||
[ui.dev.clients.general.breadcrumb]
|
||||
section = "Applications"
|
||||
|
||||
|
||||
@@ -252,6 +252,12 @@ load_error = ""
|
||||
loading = ""
|
||||
saved = ""
|
||||
save_error = ""
|
||||
status_changed = ""
|
||||
|
||||
[msg.dev.clients.federation]
|
||||
subtitle = ""
|
||||
add_subtitle = ""
|
||||
empty = ""
|
||||
|
||||
[msg.dev.clients.general.identity]
|
||||
logo_help = ""
|
||||
@@ -1031,10 +1037,14 @@ settings = ""
|
||||
[ui.dev.clients.general]
|
||||
create = ""
|
||||
display_new = ""
|
||||
save = ""
|
||||
title_create = ""
|
||||
title_edit = ""
|
||||
|
||||
[ui.dev.clients.federation]
|
||||
title = ""
|
||||
add_title = ""
|
||||
add_btn = ""
|
||||
|
||||
[ui.dev.clients.general.breadcrumb]
|
||||
section = ""
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -250,6 +250,12 @@ load_error = ""
|
||||
loading = ""
|
||||
saved = ""
|
||||
save_error = ""
|
||||
status_changed = ""
|
||||
|
||||
[msg.dev.clients.federation]
|
||||
subtitle = ""
|
||||
add_subtitle = ""
|
||||
empty = ""
|
||||
|
||||
[msg.dev.clients.general.identity]
|
||||
logo_help = ""
|
||||
@@ -1030,10 +1036,14 @@ settings = ""
|
||||
[ui.dev.clients.general]
|
||||
create = ""
|
||||
display_new = ""
|
||||
save = ""
|
||||
title_create = ""
|
||||
title_edit = ""
|
||||
|
||||
[ui.dev.clients.federation]
|
||||
title = ""
|
||||
add_title = ""
|
||||
add_btn = ""
|
||||
|
||||
[ui.dev.clients.general.breadcrumb]
|
||||
section = ""
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user