토큰 배치 수정 화면에서 API 응답 미리보기 및 access_token 매핑 편집 가능하도록 개선

This commit is contained in:
dohyeons 2025-11-27 12:08:18 +09:00
parent 06c39df3a9
commit 5b98819191
1 changed files with 193 additions and 8 deletions

View File

@ -7,12 +7,24 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Badge } from "@/components/ui/badge";
import { Checkbox } from "@/components/ui/checkbox";
import { RefreshCw, Save, ArrowLeft, Plus, Trash2 } from "lucide-react";
import { toast } from "sonner";
import { BatchAPI, BatchConfig, BatchMapping, ConnectionInfo } from "@/lib/api/batch";
import {
BatchAPI,
BatchConfig,
BatchMapping,
ConnectionInfo,
} from "@/lib/api/batch";
import { BatchManagementAPI } from "@/lib/api/batchManagement";
interface BatchColumnInfo {
column_name: string;
@ -66,6 +78,9 @@ export default function BatchEditPage() {
// 배치 타입 감지
const [batchType, setBatchType] = useState<'db-to-db' | 'restapi-to-db' | 'db-to-restapi' | null>(null);
// REST API 미리보기 상태
const [apiPreviewData, setApiPreviewData] = useState<any[]>([]);
// 페이지 로드 시 배치 정보 조회
useEffect(() => {
@ -335,6 +350,86 @@ export default function BatchEditPage() {
setMappings([...mappings, newMapping]);
};
// REST API → DB 매핑 추가
const addRestapiToDbMapping = () => {
if (!batchConfig || !batchConfig.batch_mappings || batchConfig.batch_mappings.length === 0) {
return;
}
const first = batchConfig.batch_mappings[0] as any;
const newMapping: BatchMapping = {
// FROM: REST API (기존 설정 그대로 복사)
from_connection_type: "restapi" as any,
from_connection_id: first.from_connection_id,
from_table_name: first.from_table_name,
from_column_name: "",
from_column_type: "",
// TO: DB (기존 설정 그대로 복사)
to_connection_type: first.to_connection_type as any,
to_connection_id: first.to_connection_id,
to_table_name: first.to_table_name,
to_column_name: "",
to_column_type: "",
mapping_type: (first.mapping_type as any) || "direct",
mapping_order: mappings.length + 1,
};
setMappings((prev) => [...prev, newMapping]);
};
// REST API 데이터 미리보기 (수정 화면용)
const previewRestApiData = async () => {
if (!mappings || mappings.length === 0) {
toast.error("미리보기할 REST API 매핑이 없습니다.");
return;
}
const first: any = mappings[0];
if (!first.from_api_url || !first.from_table_name) {
toast.error("API URL과 엔드포인트 정보가 없습니다.");
return;
}
try {
const method =
(first.from_api_method as "GET" | "POST" | "PUT" | "DELETE") || "GET";
const paramInfo =
first.from_api_param_type &&
first.from_api_param_name &&
first.from_api_param_value
? {
paramType: first.from_api_param_type as "url" | "query",
paramName: first.from_api_param_name as string,
paramValue: first.from_api_param_value as string,
paramSource:
(first.from_api_param_source as "static" | "dynamic") ||
"static",
}
: undefined;
const result = await BatchManagementAPI.previewRestApiData(
first.from_api_url,
first.from_api_key || "",
first.from_table_name,
method,
paramInfo,
first.from_api_body || undefined
);
setApiPreviewData(result.samples || []);
toast.success(
`API 데이터 미리보기 완료! ${result.fields.length}개 필드, ${result.samples.length}개 레코드`
);
} catch (error) {
console.error("REST API 미리보기 오류:", error);
toast.error("API 데이터 미리보기에 실패했습니다.");
}
};
// 매핑 삭제
const removeMapping = (index: number) => {
const updatedMappings = mappings.filter((_, i) => i !== index);
@ -637,6 +732,37 @@ export default function BatchEditPage() {
.
</p>
</div>
{/* API 데이터 미리보기 */}
<div className="space-y-3">
<Button
variant="outline"
size="sm"
onClick={previewRestApiData}
className="mt-2"
>
<RefreshCw className="w-4 h-4 mr-2" />
API
</Button>
{apiPreviewData.length > 0 && (
<div className="mt-2 rounded-lg border bg-muted p-3">
<p className="text-sm font-medium text-muted-foreground">
( 3)
</p>
<div className="mt-2 space-y-2 max-h-60 overflow-y-auto">
{apiPreviewData.slice(0, 3).map((item, index) => (
<pre
key={index}
className="whitespace-pre-wrap rounded border bg-background p-2 text-xs font-mono"
>
{JSON.stringify(item, null, 2)}
</pre>
))}
</div>
</div>
)}
</div>
</div>
)}
</div>
@ -687,6 +813,12 @@ export default function BatchEditPage() {
</Button>
)}
{batchType === 'restapi-to-db' && (
<Button onClick={addRestapiToDbMapping} size="sm">
<Plus className="w-4 h-4 mr-2" />
</Button>
)}
</CardTitle>
</CardHeader>
<CardContent>
@ -791,20 +923,73 @@ export default function BatchEditPage() {
<div className="flex items-center justify-between mb-4">
<div>
<h4 className="font-medium"> #{index + 1}</h4>
<p className="text-sm text-gray-600">
API : {mapping.from_column_name} DB : {mapping.to_column_name}
</p>
{mapping.from_column_name && mapping.to_column_name && (
<p className="text-sm text-gray-600">
API : {mapping.from_column_name} DB : {mapping.to_column_name}
</p>
)}
</div>
<Button
variant="outline"
size="sm"
onClick={() => removeMapping(index)}
>
<Trash2 className="w-4 h-4" />
</Button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<Label>API </Label>
<Input value={mapping.from_column_name || ''} readOnly />
<Label>API (JSON )</Label>
<Input
value={mapping.from_column_name || ""}
onChange={(e) =>
updateMapping(
index,
"from_column_name",
e.target.value
)
}
placeholder="response.access_token"
/>
</div>
<div>
<Label>DB </Label>
<Input value={mapping.to_column_name || ''} readOnly />
<Select
value={mapping.to_column_name || ""}
onValueChange={(value) => {
updateMapping(index, "to_column_name", value);
const selectedColumn = toColumns.find(
(col) => col.column_name === value
);
if (selectedColumn) {
updateMapping(
index,
"to_column_type",
selectedColumn.data_type
);
}
}}
>
<SelectTrigger>
<SelectValue placeholder="대상 컬럼 선택" />
</SelectTrigger>
<SelectContent>
{toColumns.map((column) => (
<SelectItem
key={column.column_name}
value={column.column_name}
>
{column.column_name} ({column.data_type})
</SelectItem>
))}
</SelectContent>
</Select>
{toColumns.length === 0 && (
<p className="text-xs text-gray-500 mt-1">
.
</p>
)}
</div>
</div>
</div>