37cac72085
- Docker/K8s 배포 설정을 pipeline-backend/pipeline-front로 통일 - 네임스페이스, 서비스, PVC 등 k8s 리소스명 pipeline-* 로 변경 - AI 에이전트 관리 기능 추가 (에이전트, 그룹, 프로바이더, 대화, API 키, 지식베이스) - 장비 연결 관리 기능 추가 (PLC/Modbus/OPC-UA/MQTT) - 배치 스케줄러에 AI agent/device collection/crawling 타입 추가 - 배치 편집 UI 개선 (6가지 실행 방식 지원) - 회사별 페이지(COMPANY_*) 제거 및 AdminPageRenderer 최적화 - 메뉴 재구성: 장비 연결 관리 시스템관리로 이동, 에이전트 오케스트레이션으로 개명 - ai-assistant 디렉토리 제거 (backend-node로 통합) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
639 lines
17 KiB
TypeScript
639 lines
17 KiB
TypeScript
// 배치관리 API 클라이언트 (새로운 API로 업데이트)
|
|
// 작성일: 2024-12-24
|
|
|
|
import { apiClient } from "./client";
|
|
|
|
export type BatchExecutionType = "mapping" | "node_flow" | "ai_agent" | "rest_api_sync" | "device_collection" | "crawling";
|
|
|
|
export interface BatchConfig {
|
|
id?: number;
|
|
batch_name: string;
|
|
description?: string;
|
|
cron_schedule: string;
|
|
is_active?: string;
|
|
company_code?: string;
|
|
save_mode?: 'INSERT' | 'UPSERT';
|
|
conflict_key?: string;
|
|
auth_service_name?: string;
|
|
execution_type?: BatchExecutionType;
|
|
node_flow_id?: number;
|
|
node_flow_context?: Record<string, any>;
|
|
created_date?: Date;
|
|
created_by?: string;
|
|
updated_date?: Date;
|
|
updated_by?: string;
|
|
batch_mappings?: BatchMapping[];
|
|
last_status?: string;
|
|
last_executed_at?: string;
|
|
last_total_records?: number;
|
|
}
|
|
|
|
export interface NodeFlowInfo {
|
|
flow_id: number;
|
|
flow_name: string;
|
|
description?: string;
|
|
company_code?: string;
|
|
node_count: number;
|
|
}
|
|
|
|
export interface BatchStats {
|
|
totalBatches: number;
|
|
activeBatches: number;
|
|
todayExecutions: number;
|
|
todayFailures: number;
|
|
prevDayExecutions: number;
|
|
prevDayFailures: number;
|
|
}
|
|
|
|
export interface SparklineData {
|
|
hour: string;
|
|
success: number;
|
|
failed: number;
|
|
}
|
|
|
|
export interface RecentLog {
|
|
id: number;
|
|
started_at: string;
|
|
finished_at: string | null;
|
|
status: string;
|
|
total_records: number;
|
|
success_records: number;
|
|
failed_records: number;
|
|
error_message: string | null;
|
|
duration_ms: number | null;
|
|
}
|
|
|
|
export interface BatchMapping {
|
|
id?: number;
|
|
batch_config_id?: number;
|
|
|
|
// FROM 정보
|
|
from_connection_type: 'internal' | 'external';
|
|
from_connection_id?: number;
|
|
from_table_name: string;
|
|
from_column_name: string;
|
|
from_column_type?: string;
|
|
|
|
// TO 정보
|
|
to_connection_type: 'internal' | 'external';
|
|
to_connection_id?: number;
|
|
to_table_name: string;
|
|
to_column_name: string;
|
|
to_column_type?: string;
|
|
|
|
mapping_order?: number;
|
|
created_date?: Date;
|
|
created_by?: string;
|
|
}
|
|
|
|
export interface BatchConfigFilter {
|
|
batch_name?: string;
|
|
is_active?: string;
|
|
company_code?: string;
|
|
search?: string;
|
|
page?: number;
|
|
limit?: number;
|
|
}
|
|
|
|
export interface BatchJob {
|
|
id: number;
|
|
job_name: string;
|
|
job_type: string;
|
|
description?: string;
|
|
cron_schedule: string;
|
|
schedule_cron?: string;
|
|
is_active: string;
|
|
last_execution?: Date;
|
|
last_executed_at?: string;
|
|
next_execution?: Date;
|
|
execution_count?: number;
|
|
success_count?: number;
|
|
failure_count?: number;
|
|
status?: string;
|
|
created_date?: Date;
|
|
created_by?: string;
|
|
}
|
|
|
|
export interface ConnectionInfo {
|
|
type: 'internal' | 'external';
|
|
id?: number;
|
|
name: string;
|
|
db_type?: string;
|
|
}
|
|
|
|
export interface ColumnInfo {
|
|
column_name: string;
|
|
data_type: string;
|
|
is_nullable?: boolean;
|
|
column_default?: string;
|
|
}
|
|
|
|
export interface TableInfo {
|
|
table_name: string;
|
|
columns: ColumnInfo[];
|
|
description?: string | null;
|
|
}
|
|
|
|
export interface BatchMappingRequest {
|
|
batchName: string;
|
|
description?: string;
|
|
cronSchedule: string;
|
|
mappings: BatchMapping[];
|
|
isActive?: boolean;
|
|
executionType?: BatchExecutionType;
|
|
nodeFlowId?: number;
|
|
nodeFlowContext?: Record<string, any>;
|
|
}
|
|
|
|
export interface ApiResponse<T> {
|
|
success: boolean;
|
|
data?: T;
|
|
message?: string;
|
|
error?: string;
|
|
}
|
|
|
|
export class BatchAPI {
|
|
private static readonly BASE_PATH = "";
|
|
|
|
/**
|
|
* 배치 설정 목록 조회
|
|
*/
|
|
static async getBatchConfigs(filter: BatchConfigFilter = {}): Promise<{
|
|
success: boolean;
|
|
data: BatchConfig[];
|
|
pagination?: {
|
|
page: number;
|
|
limit: number;
|
|
total: number;
|
|
totalPages: number;
|
|
};
|
|
message?: string;
|
|
}> {
|
|
try {
|
|
const params = new URLSearchParams();
|
|
|
|
if (filter.is_active) params.append("is_active", filter.is_active);
|
|
if (filter.company_code) params.append("company_code", filter.company_code);
|
|
if (filter.search) params.append("search", filter.search);
|
|
if (filter.page) params.append("page", filter.page.toString());
|
|
if (filter.limit) params.append("limit", filter.limit.toString());
|
|
|
|
const response = await apiClient.get<{
|
|
success: boolean;
|
|
data: BatchConfig[];
|
|
pagination?: {
|
|
page: number;
|
|
limit: number;
|
|
total: number;
|
|
totalPages: number;
|
|
};
|
|
message?: string;
|
|
}>(`/batch-management/batch-configs?${params.toString()}`);
|
|
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error("배치 설정 목록 조회 오류:", error);
|
|
return {
|
|
success: false,
|
|
data: [],
|
|
message: error instanceof Error ? error.message : "배치 설정 목록 조회에 실패했습니다."
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 특정 배치 설정 조회 (별칭)
|
|
*/
|
|
static async getBatchConfig(id: number): Promise<BatchConfig> {
|
|
return this.getBatchConfigById(id);
|
|
}
|
|
|
|
/**
|
|
* 특정 배치 설정 조회
|
|
*/
|
|
static async getBatchConfigById(id: number): Promise<BatchConfig> {
|
|
try {
|
|
const response = await apiClient.get<ApiResponse<BatchConfig>>(
|
|
`/batch-management/batch-configs/${id}`,
|
|
);
|
|
|
|
if (!response.data.success) {
|
|
throw new Error(response.data.message || "배치 설정 조회에 실패했습니다.");
|
|
}
|
|
|
|
if (!response.data.data) {
|
|
throw new Error("배치 설정을 찾을 수 없습니다.");
|
|
}
|
|
|
|
return response.data.data;
|
|
} catch (error) {
|
|
console.error("배치 설정 조회 오류:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 배치 설정 생성
|
|
*/
|
|
static async createBatchConfig(data: BatchMappingRequest): Promise<BatchConfig> {
|
|
try {
|
|
const response = await apiClient.post<ApiResponse<BatchConfig>>(
|
|
`/batch-management/batch-configs`,
|
|
data,
|
|
);
|
|
|
|
if (!response.data.success) {
|
|
throw new Error(response.data.message || "배치 설정 생성에 실패했습니다.");
|
|
}
|
|
|
|
if (!response.data.data) {
|
|
throw new Error("배치 설정 생성 결과를 받을 수 없습니다.");
|
|
}
|
|
|
|
return response.data.data;
|
|
} catch (error) {
|
|
console.error("배치 설정 생성 오류:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 배치 설정 수정
|
|
*/
|
|
static async updateBatchConfig(
|
|
id: number,
|
|
data: Partial<BatchMappingRequest>
|
|
): Promise<BatchConfig> {
|
|
try {
|
|
const response = await apiClient.put<ApiResponse<BatchConfig>>(
|
|
`/batch-management/batch-configs/${id}`,
|
|
data,
|
|
);
|
|
|
|
if (!response.data.success) {
|
|
throw new Error(response.data.message || "배치 설정 수정에 실패했습니다.");
|
|
}
|
|
|
|
if (!response.data.data) {
|
|
throw new Error("배치 설정 수정 결과를 받을 수 없습니다.");
|
|
}
|
|
|
|
return response.data.data;
|
|
} catch (error) {
|
|
console.error("배치 설정 수정 오류:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 배치 설정 삭제
|
|
*/
|
|
static async deleteBatchConfig(id: number): Promise<void> {
|
|
try {
|
|
const response = await apiClient.delete<ApiResponse<void>>(
|
|
`/batch-management/batch-configs/${id}`,
|
|
);
|
|
|
|
if (!response.data.success) {
|
|
throw new Error(response.data.message || "배치 설정 삭제에 실패했습니다.");
|
|
}
|
|
} catch (error) {
|
|
console.error("배치 설정 삭제 오류:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 사용 가능한 커넥션 목록 조회
|
|
*/
|
|
static async getConnections(): Promise<ConnectionInfo[]> {
|
|
try {
|
|
console.log("[BatchAPI] getAvailableConnections 호출 시작");
|
|
console.log("[BatchAPI] API URL:", `/batch-management/connections`);
|
|
|
|
const response = await apiClient.get<ApiResponse<ConnectionInfo[]>>(
|
|
`/batch-management/connections`,
|
|
);
|
|
|
|
console.log("[BatchAPI] API 응답:", response);
|
|
console.log("[BatchAPI] 응답 데이터:", response.data);
|
|
|
|
if (!response.data.success) {
|
|
console.error("[BatchAPI] API 응답 실패:", response.data);
|
|
throw new Error(response.data.message || "커넥션 목록 조회에 실패했습니다.");
|
|
}
|
|
|
|
const result = response.data.data || [];
|
|
console.log("[BatchAPI] 최종 결과:", result);
|
|
return result;
|
|
} catch (error) {
|
|
console.error("[BatchAPI] 커넥션 목록 조회 오류:", error);
|
|
console.error("[BatchAPI] 오류 상세:", {
|
|
message: error instanceof Error ? error.message : 'Unknown error',
|
|
stack: error instanceof Error ? error.stack : undefined
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 특정 커넥션의 테이블 목록 조회
|
|
*/
|
|
static async getTablesFromConnection(
|
|
connection: ConnectionInfo
|
|
): Promise<string[]> {
|
|
try {
|
|
let url = `/batch-management/connections/${connection.type}`;
|
|
if (connection.type === 'external' && connection.id) {
|
|
url += `/${connection.id}`;
|
|
}
|
|
url += '/tables';
|
|
|
|
const response = await apiClient.get<ApiResponse<TableInfo[]>>(url);
|
|
|
|
if (!response.data.success) {
|
|
throw new Error(response.data.message || "테이블 목록 조회에 실패했습니다.");
|
|
}
|
|
|
|
// TableInfo[]에서 table_name만 추출하여 string[]로 변환
|
|
const tables = response.data.data || [];
|
|
return tables.map(table => table.table_name);
|
|
} catch (error) {
|
|
console.error("테이블 목록 조회 오류:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 특정 테이블의 컬럼 정보 조회
|
|
*/
|
|
static async getTableColumns(
|
|
connection: ConnectionInfo,
|
|
tableName: string
|
|
): Promise<ColumnInfo[]> {
|
|
try {
|
|
let url = `/batch-management/connections/${connection.type}`;
|
|
if (connection.type === 'external' && connection.id) {
|
|
url += `/${connection.id}`;
|
|
}
|
|
url += `/tables/${tableName}/columns`;
|
|
|
|
const response = await apiClient.get<ApiResponse<ColumnInfo[]>>(url);
|
|
|
|
if (!response.data.success) {
|
|
throw new Error(response.data.message || "컬럼 정보 조회에 실패했습니다.");
|
|
}
|
|
|
|
return response.data.data || [];
|
|
} catch (error) {
|
|
console.error("컬럼 정보 조회 오류:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 배치 작업 목록 조회
|
|
*/
|
|
static async getBatchJobs(): Promise<BatchJob[]> {
|
|
try {
|
|
const response = await apiClient.get<ApiResponse<BatchJob[]>>('/batch-management/jobs');
|
|
|
|
if (!response.data.success) {
|
|
throw new Error(response.data.message || "배치 작업 목록 조회에 실패했습니다.");
|
|
}
|
|
|
|
return response.data.data || [];
|
|
} catch (error) {
|
|
console.error("배치 작업 목록 조회 오류:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 배치 수동 실행
|
|
*/
|
|
static async executeBatchConfig(batchId: number): Promise<{
|
|
success: boolean;
|
|
message?: string;
|
|
data?: {
|
|
batchId: string;
|
|
totalRecords: number;
|
|
successRecords: number;
|
|
failedRecords: number;
|
|
duration: number;
|
|
};
|
|
}> {
|
|
try {
|
|
const response = await apiClient.post<{
|
|
success: boolean;
|
|
message?: string;
|
|
data?: {
|
|
batchId: string;
|
|
totalRecords: number;
|
|
successRecords: number;
|
|
failedRecords: number;
|
|
duration: number;
|
|
};
|
|
}>(`/batch-management/batch-configs/${batchId}/execute`);
|
|
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error("배치 실행 오류:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 지원되는 배치 작업 타입 조회
|
|
*/
|
|
static async getSupportedJobTypes(): Promise<string[]> {
|
|
try {
|
|
const response = await apiClient.get<ApiResponse<string[]>>('/batch-management/job-types');
|
|
if (!response.data.success) {
|
|
throw new Error(response.data.message || "작업 타입 조회에 실패했습니다.");
|
|
}
|
|
return response.data.data || [];
|
|
} catch (error) {
|
|
console.error("작업 타입 조회 오류:", error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 배치 작업 삭제
|
|
*/
|
|
static async deleteBatchJob(id: number): Promise<void> {
|
|
try {
|
|
const response = await apiClient.delete<ApiResponse<void>>(`/batch-management/jobs/${id}`);
|
|
if (!response.data.success) {
|
|
throw new Error(response.data.message || "배치 작업 삭제에 실패했습니다.");
|
|
}
|
|
} catch (error) {
|
|
console.error("배치 작업 삭제 오류:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 배치 작업 실행
|
|
*/
|
|
static async executeBatchJob(id: number): Promise<void> {
|
|
try {
|
|
const response = await apiClient.post<ApiResponse<void>>(`/batch-management/jobs/${id}/execute`);
|
|
if (!response.data.success) {
|
|
throw new Error(response.data.message || "배치 작업 실행에 실패했습니다.");
|
|
}
|
|
} catch (error) {
|
|
console.error("배치 작업 실행 오류:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* auth_tokens 테이블의 서비스명 목록 조회
|
|
*/
|
|
static async getAuthServiceNames(): Promise<string[]> {
|
|
try {
|
|
const response = await apiClient.get<{
|
|
success: boolean;
|
|
data: string[];
|
|
}>(`/batch-management/auth-services`);
|
|
|
|
if (response.data.success) {
|
|
return response.data.data || [];
|
|
}
|
|
return [];
|
|
} catch (error) {
|
|
console.error("인증 서비스 목록 조회 오류:", error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 노드 플로우 목록 조회 (배치 설정 시 플로우 선택용)
|
|
*/
|
|
static async getNodeFlows(): Promise<NodeFlowInfo[]> {
|
|
try {
|
|
const response = await apiClient.get<ApiResponse<NodeFlowInfo[]>>(
|
|
`/batch-management/node-flows`
|
|
);
|
|
if (response.data.success) {
|
|
return response.data.data || [];
|
|
}
|
|
return [];
|
|
} catch (error) {
|
|
console.error("노드 플로우 목록 조회 오류:", error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 배치 대시보드 통계 조회
|
|
*/
|
|
static async getBatchStats(): Promise<BatchStats | null> {
|
|
try {
|
|
const response = await apiClient.get<ApiResponse<BatchStats>>(
|
|
`/batch-management/stats`
|
|
);
|
|
if (response.data.success) {
|
|
return response.data.data || null;
|
|
}
|
|
return null;
|
|
} catch (error) {
|
|
console.error("배치 통계 조회 오류:", error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 배치별 최근 24시간 스파크라인 데이터
|
|
*/
|
|
static async getBatchSparkline(batchId: number): Promise<SparklineData[]> {
|
|
try {
|
|
const response = await apiClient.get<ApiResponse<SparklineData[]>>(
|
|
`/batch-management/batch-configs/${batchId}/sparkline`
|
|
);
|
|
if (response.data.success) {
|
|
return response.data.data || [];
|
|
}
|
|
return [];
|
|
} catch (error) {
|
|
console.error("스파크라인 조회 오류:", error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 배치별 최근 실행 로그
|
|
*/
|
|
static async getBatchRecentLogs(batchId: number, limit: number = 5): Promise<RecentLog[]> {
|
|
try {
|
|
const response = await apiClient.get<ApiResponse<RecentLog[]>>(
|
|
`/batch-management/batch-configs/${batchId}/recent-logs?limit=${limit}`
|
|
);
|
|
if (response.data.success) {
|
|
return response.data.data || [];
|
|
}
|
|
return [];
|
|
} catch (error) {
|
|
console.error("최근 실행 로그 조회 오류:", error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// ===== 새 실행 타입용 API =====
|
|
|
|
/** AI 에이전트 그룹 목록 */
|
|
static async getAiAgentGroups(): Promise<any[]> {
|
|
try {
|
|
const res = await apiClient.get("/ai-agent-groups");
|
|
return res.data.data || [];
|
|
} catch { return []; }
|
|
}
|
|
|
|
/** 장비 연결 목록 */
|
|
static async getDeviceConnections(): Promise<any[]> {
|
|
try {
|
|
const res = await apiClient.get("/pipeline-device-connections");
|
|
return res.data.data || [];
|
|
} catch { return []; }
|
|
}
|
|
|
|
/** 장비 태그 목록 */
|
|
static async getDeviceTags(connectionId: number): Promise<any[]> {
|
|
try {
|
|
const res = await apiClient.get(`/pipeline-device-connections/${connectionId}/tags`);
|
|
return res.data.data || [];
|
|
} catch { return []; }
|
|
}
|
|
|
|
/** REST API 연결 목록 */
|
|
static async getRestApiConnections(): Promise<any[]> {
|
|
try {
|
|
const res = await apiClient.get("/external-rest-api-connections");
|
|
return res.data.data || [];
|
|
} catch { return []; }
|
|
}
|
|
|
|
/** 크롤링 설정 목록 */
|
|
static async getCrawlConfigs(): Promise<any[]> {
|
|
try {
|
|
const res = await apiClient.get("/crawl-configs");
|
|
return res.data.data || res.data || [];
|
|
} catch { return []; }
|
|
}
|
|
|
|
/** REST API 미리보기 */
|
|
static async previewRestApi(data: {
|
|
apiUrl: string;
|
|
apiKey?: string;
|
|
endpointPath: string;
|
|
method?: string;
|
|
dataArrayPath?: string;
|
|
}): Promise<{ fields: string[]; samples: any[]; totalCount: number }> {
|
|
const res = await apiClient.post("/batch-management/rest-api/preview", data);
|
|
if (!res.data.success) throw new Error(res.data.message);
|
|
return res.data.data;
|
|
}
|
|
}
|