[agent-pipeline] rollback to 577e9c12

This commit is contained in:
DDD1542 2026-03-18 14:18:02 +09:00
parent 27efe672b9
commit 0f15644651
3 changed files with 22 additions and 307 deletions

View File

@ -7,7 +7,6 @@ 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 { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Badge } from "@/components/ui/badge";
import { ArrowLeft, Save, RefreshCw, ArrowRight, Trash2 } from "lucide-react";
import { toast } from "sonner";
@ -19,7 +18,6 @@ import {
ConnectionInfo,
ColumnInfo,
BatchMappingRequest,
NodeFlowItem,
} from "@/lib/api/batch";
export default function BatchCreatePage() {
@ -45,11 +43,6 @@ export default function BatchCreatePage() {
const [selectedFromColumn, setSelectedFromColumn] = useState<ColumnInfo | null>(null);
const [mappings, setMappings] = useState<BatchMapping[]>([]);
// 실행 타입: 데이터 매핑 / 노드 플로우
const [executionType, setExecutionType] = useState<"mapping" | "node_flow">("mapping");
const [selectedFlowId, setSelectedFlowId] = useState<number | null>(null);
const [nodeFlows, setNodeFlows] = useState<NodeFlowItem[]>([]);
// 로딩 상태
const [loading, setLoading] = useState(false);
const [loadingConnections, setLoadingConnections] = useState(false);
@ -59,17 +52,6 @@ export default function BatchCreatePage() {
loadConnections();
}, []);
// 노드 플로우 목록 로드 (실행 타입 노드 플로우 시 사용)
useEffect(() => {
if (executionType !== "node_flow") return;
const load = async () => {
const res = await BatchAPI.getNodeFlows();
if (res.success && res.data) setNodeFlows(res.data);
else setNodeFlows([]);
};
load();
}, [executionType]);
const loadConnections = async () => {
setLoadingConnections(true);
try {
@ -239,28 +221,19 @@ export default function BatchCreatePage() {
return;
}
if (executionType === "mapping") {
if (mappings.length === 0) {
toast.error("최소 하나 이상의 매핑을 추가해주세요.");
return;
}
} else {
if (selectedFlowId == null) {
toast.error("노드 플로우를 선택해주세요.");
return;
}
if (mappings.length === 0) {
toast.error("최소 하나 이상의 매핑을 추가해주세요.");
return;
}
setLoading(true);
try {
const request: BatchMappingRequest = {
const request = {
batchName: batchName,
description: description || undefined,
cronSchedule: cronSchedule,
mappings: executionType === "mapping" ? mappings : [],
isActive: true,
executionType,
nodeFlowId: executionType === "node_flow" ? selectedFlowId ?? undefined : undefined,
mappings: mappings,
isActive: true
};
await BatchAPI.createBatchConfig(request);
@ -332,66 +305,10 @@ export default function BatchCreatePage() {
rows={3}
/>
</div>
{/* 실행 타입 */}
<div className="space-y-3">
<Label> </Label>
<RadioGroup
value={executionType}
onValueChange={(v) => {
setExecutionType(v as "mapping" | "node_flow");
if (v === "mapping") setSelectedFlowId(null);
}}
className="flex flex-col gap-2 sm:flex-row sm:gap-6"
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="mapping" id="exec-mapping" />
<Label htmlFor="exec-mapping" className="font-normal cursor-pointer">
()
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="node_flow" id="exec-node-flow" />
<Label htmlFor="exec-node-flow" className="font-normal cursor-pointer">
</Label>
</div>
</RadioGroup>
{executionType === "node_flow" && (
<div className="space-y-2 pt-2">
<Label> </Label>
<Select
value={selectedFlowId != null ? String(selectedFlowId) : ""}
onValueChange={(v) => setSelectedFlowId(v ? parseInt(v, 10) : null)}
disabled={nodeFlows.length === 0}
>
<SelectTrigger className="w-full max-w-md">
<SelectValue placeholder={nodeFlows.length === 0 ? "로딩 중..." : "플로우를 선택하세요"} />
</SelectTrigger>
<SelectContent>
{nodeFlows.map((flow) => (
<SelectItem key={flow.flow_id} value={String(flow.flow_id)}>
<span className="font-medium">{flow.flow_name}</span>
{flow.description && (
<span className="ml-2 text-muted-foreground text-xs">({flow.description})</span>
)}
</SelectItem>
))}
</SelectContent>
</Select>
{selectedFlowId != null && nodeFlows.find((f) => f.flow_id === selectedFlowId)?.description && (
<p className="text-xs text-muted-foreground">
{nodeFlows.find((f) => f.flow_id === selectedFlowId)?.description}
</p>
)}
</div>
)}
</div>
</CardContent>
</Card>
{/* 매핑 설정 - 데이터 매핑일 때만 표시 */}
{executionType === "mapping" && (
{/* 매핑 설정 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* FROM 섹션 */}
<Card className="border-emerald-200">
@ -557,10 +474,9 @@ export default function BatchCreatePage() {
</CardContent>
</Card>
</div>
)}
{/* 매핑 현황 */}
{executionType === "mapping" && mappings.length > 0 && (
{mappings.length > 0 && (
<Card>
<CardHeader>
<CardTitle> ({mappings.length})</CardTitle>
@ -613,7 +529,7 @@ export default function BatchCreatePage() {
</Button>
<Button
onClick={saveBatchConfig}
disabled={loading || (executionType === "mapping" ? mappings.length === 0 : selectedFlowId == null)}
disabled={loading || mappings.length === 0}
className="flex items-center space-x-2"
>
{loading ? (

View File

@ -14,7 +14,6 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Badge } from "@/components/ui/badge";
import { Checkbox } from "@/components/ui/checkbox";
import { RefreshCw, Save, ArrowLeft, Plus, Trash2 } from "lucide-react";
@ -24,7 +23,6 @@ import {
BatchConfig,
BatchMapping,
ConnectionInfo,
NodeFlowItem,
} from "@/lib/api/batch";
import { BatchManagementAPI } from "@/lib/api/batchManagement";
@ -66,11 +64,6 @@ export default function BatchEditPage() {
const [authServiceNames, setAuthServiceNames] = useState<string[]>([]);
const [dataArrayPath, setDataArrayPath] = useState("");
// 실행 타입: 데이터 매핑 / 노드 플로우
const [executionType, setExecutionType] = useState<"mapping" | "node_flow">("mapping");
const [selectedFlowId, setSelectedFlowId] = useState<number | null>(null);
const [nodeFlows, setNodeFlows] = useState<NodeFlowItem[]>([]);
// 연결 정보
const [connections, setConnections] = useState<ConnectionInfo[]>([]);
const [fromConnection, setFromConnection] = useState<ConnectionInfo | null>(null);
@ -123,17 +116,6 @@ export default function BatchEditPage() {
}
}, [batchId]);
// 노드 플로우 목록 로드 (실행 타입 노드 플로우 시 사용)
useEffect(() => {
if (executionType !== "node_flow") return;
const load = async () => {
const res = await BatchAPI.getNodeFlows();
if (res.success && res.data) setNodeFlows(res.data);
else setNodeFlows([]);
};
load();
}, [executionType]);
// 인증 서비스명 목록 로드
const loadAuthServiceNames = async () => {
try {
@ -242,8 +224,6 @@ export default function BatchEditPage() {
setConflictKey((config as any).conflict_key || "");
setAuthServiceName((config as any).auth_service_name || "");
setDataArrayPath((config as any).data_array_path || "");
setExecutionType((config.execution_type as "mapping" | "node_flow") || "mapping");
setSelectedFlowId(config.node_flow_id ?? null);
// 인증 토큰 모드 설정
if ((config as any).auth_service_name) {
@ -559,33 +539,21 @@ export default function BatchEditPage() {
// 배치 설정 저장
const saveBatchConfig = async () => {
if (!batchName || !cronSchedule) {
// restapi-to-db인 경우 mappingList 사용, 아닌 경우 mappings 사용
const effectiveMappings = batchType === "restapi-to-db" ? mappingList : mappings;
if (!batchName || !cronSchedule || effectiveMappings.length === 0) {
toast.error("필수 항목을 모두 입력해주세요.");
return;
}
if (executionType === "node_flow") {
if (selectedFlowId == null) {
toast.error("노드 플로우를 선택해주세요.");
return;
}
} else {
const effectiveMappings = batchType === "restapi-to-db" ? mappingList : mappings;
if (effectiveMappings.length === 0) {
toast.error("필수 항목을 모두 입력해주세요.");
return;
}
}
const effectiveMappings = batchType === "restapi-to-db" ? mappingList : mappings;
try {
setLoading(true);
// 노드 플로우 타입이면 매핑 없이 전송
let finalMappings: BatchMapping[] =
executionType === "node_flow" ? [] : mappings;
// restapi-to-db인 경우 mappingList를 mappings 형식으로 변환
let finalMappings: BatchMapping[] = mappings;
if (executionType !== "node_flow" && batchType === "restapi-to-db" && batchConfig?.batch_mappings?.[0]) {
if (batchType === "restapi-to-db" && batchConfig?.batch_mappings?.[0]) {
const first = batchConfig.batch_mappings[0] as any;
finalMappings = mappingList
.filter((m) => m.dbColumn) // DB 컬럼이 선택된 것만
@ -618,11 +586,9 @@ export default function BatchEditPage() {
isActive,
mappings: finalMappings,
saveMode,
conflictKey: saveMode === "UPSERT" ? conflictKey : null,
authServiceName: authTokenMode === "db" ? authServiceName : null,
dataArrayPath: dataArrayPath || null,
executionType,
nodeFlowId: executionType === "node_flow" ? selectedFlowId ?? undefined : undefined,
conflictKey: saveMode === "UPSERT" ? conflictKey : null, // INSERT면 null로 명시적 삭제
authServiceName: authTokenMode === "db" ? authServiceName : null, // 직접입력이면 null로 명시적 삭제
dataArrayPath: dataArrayPath || null
});
toast.success("배치 설정이 성공적으로 수정되었습니다.");
@ -717,61 +683,6 @@ export default function BatchEditPage() {
/>
<Label htmlFor="isActive"></Label>
</div>
{/* 실행 타입 */}
<div className="space-y-3">
<Label> </Label>
<RadioGroup
value={executionType}
onValueChange={(v) => {
setExecutionType(v as "mapping" | "node_flow");
if (v === "mapping") setSelectedFlowId(null);
}}
className="flex flex-col gap-2 sm:flex-row sm:gap-6"
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="mapping" id="edit-exec-mapping" />
<Label htmlFor="edit-exec-mapping" className="font-normal cursor-pointer">
()
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="node_flow" id="edit-exec-node-flow" />
<Label htmlFor="edit-exec-node-flow" className="font-normal cursor-pointer">
</Label>
</div>
</RadioGroup>
{executionType === "node_flow" && (
<div className="space-y-2 pt-2">
<Label> </Label>
<Select
value={selectedFlowId != null ? String(selectedFlowId) : ""}
onValueChange={(v) => setSelectedFlowId(v ? parseInt(v, 10) : null)}
disabled={nodeFlows.length === 0}
>
<SelectTrigger className="w-full max-w-md">
<SelectValue placeholder={nodeFlows.length === 0 ? "로딩 중..." : "플로우를 선택하세요"} />
</SelectTrigger>
<SelectContent>
{nodeFlows.map((flow) => (
<SelectItem key={flow.flow_id} value={String(flow.flow_id)}>
<span className="font-medium">{flow.flow_name}</span>
{flow.description != null && flow.description !== "" && (
<span className="ml-2 text-muted-foreground text-xs">({flow.description})</span>
)}
</SelectItem>
))}
</SelectContent>
</Select>
{selectedFlowId != null && nodeFlows.find((f) => f.flow_id === selectedFlowId)?.description && (
<p className="text-xs text-muted-foreground">
{nodeFlows.find((f) => f.flow_id === selectedFlowId)?.description}
</p>
)}
</div>
)}
</div>
</CardContent>
</Card>
@ -1625,14 +1536,7 @@ export default function BatchEditPage() {
</Button>
<Button
onClick={saveBatchConfig}
disabled={
loading ||
(executionType === "node_flow"
? selectedFlowId == null
: batchType === "restapi-to-db"
? mappingList.length === 0
: mappings.length === 0)
}
disabled={loading || (batchType === "restapi-to-db" ? mappingList.length === 0 : mappings.length === 0)}
>
{loading ? (
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />

View File

@ -13,9 +13,6 @@ export interface BatchConfig {
save_mode?: 'INSERT' | 'UPSERT'; // 저장 모드 (기본: INSERT)
conflict_key?: string; // UPSERT 시 충돌 기준 컬럼명
auth_service_name?: string; // REST API 인증에 사용할 토큰 서비스명
execution_type?: 'mapping' | 'node_flow';
node_flow_id?: number;
node_flow_context?: Record<string, unknown>;
created_date?: Date;
created_by?: string;
updated_date?: Date;
@ -98,17 +95,6 @@ export interface BatchMappingRequest {
cronSchedule: string;
mappings: BatchMapping[];
isActive?: boolean;
executionType?: 'mapping' | 'node_flow';
nodeFlowId?: number;
nodeFlowContext?: Record<string, unknown>;
}
/** 노드 플로우 목록 항목 (getNodeFlows 응답) */
export interface NodeFlowItem {
flow_id: number;
flow_name: string;
description?: string;
created_date?: string;
}
export interface ApiResponse<T> {
@ -474,97 +460,6 @@ export class BatchAPI {
return [];
}
}
/**
*
*/
static async getNodeFlows(): Promise<ApiResponse<NodeFlowItem[]>> {
try {
const response = await apiClient.get<ApiResponse<NodeFlowItem[]>>(
"/batch-management/node-flows"
);
return response.data;
} catch (error) {
console.error("노드 플로우 목록 조회 오류:", error);
return {
success: false,
error: error instanceof Error ? error.message : "노드 플로우 목록 조회에 실패했습니다.",
};
}
}
/**
* ()
*/
static async getBatchStats(): Promise<
ApiResponse<{
totalBatches: number;
activeBatches: number;
todayExecutions: number;
todayFailures: number;
}>
> {
try {
const response = await apiClient.get<
ApiResponse<{
totalBatches: number;
activeBatches: number;
todayExecutions: number;
todayFailures: number;
}>
>("/batch-management/stats");
return response.data;
} catch (error) {
console.error("배치 통계 조회 오류:", error);
return {
success: false,
error: error instanceof Error ? error.message : "배치 통계 조회에 실패했습니다.",
};
}
}
/**
* ( 24 / )
*/
static async getBatchSparkline(
batchId: number
): Promise<
ApiResponse<Array<{ hour: number; status: string; count: number }>>
> {
try {
const response = await apiClient.get<
ApiResponse<Array<{ hour: number; status: string; count: number }>>
>(`/batch-management/batch-configs/${batchId}/sparkline`);
return response.data;
} catch (error) {
console.error("배치 스파크라인 조회 오류:", error);
return {
success: false,
error: error instanceof Error ? error.message : "스파크라인 조회에 실패했습니다.",
};
}
}
/**
*
*/
static async getBatchRecentLogs(
batchId: number,
limit: number = 5
): Promise<ApiResponse<unknown[]>> {
try {
const response = await apiClient.get<ApiResponse<unknown[]>>(
`/batch-management/batch-configs/${batchId}/recent-logs?limit=${limit}`
);
return response.data;
} catch (error) {
console.error("배치 최근 로그 조회 오류:", error);
return {
success: false,
error: error instanceof Error ? error.message : "최근 로그 조회에 실패했습니다.",
};
}
}
}
// BatchJob export 추가 (이미 위에서 interface로 정의됨)