토큰 배치 수정 화면에서 API 응답 미리보기 및 access_token 매핑 편집 가능하도록 개선
This commit is contained in:
parent
06c39df3a9
commit
5b98819191
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue