Files
pipeline/frontend/lib/api/fleet.ts
T
chpark 4c1dc4082e
Build and Push Images / build-and-push (push) Has been cancelled
feat: Fleet/Collector/엣지 배포 관련 누적 작업 일괄 커밋
이전 세션들에서 작업된 아래 범위를 모두 포함:

Fleet 서브시스템 (src/fleet/)
- fleetDeviceService / fleetCommandService / fleetDeploymentService / fleetReleaseService
- fleetMetricsService, fleetScriptService, fleetEdgeConfigService
- Edge 디바이스 관리, 커맨드 발행, 배포/릴리스, 스크립트 동기화

Collector 확장
- centralMqttForwarder / centralForwarderConfigService
- equipmentStateService, pythonHookRunner, scriptCache
- Modbus/OPC-UA/S7/XGT 프로토콜 클라이언트
- targetDbIntrospection (저장 DB 조회)

Routes / API
- automationDashboardRoutes, centralForwarderRoutes, equipmentStateRoutes

DB
- importEdgeConfig (Python cached config → Pipeline DB)
- seedDataSources (external_db_connections 초기 시드)

엣지 배포 리소스
- docker/edge/Dockerfile.backend.prod, Dockerfile.frontend.prod
- docker/edge/docker-compose.edge.yml

프론트엔드
- admin/automaticMng (centralForwarder, dashboard, equipmentState)
- admin/fleet (commands, devices, deployments, releases, scripts, alerts)
- admin/pipeline-device 개선 (저장 DB 드롭다운, 태그 매핑 등)
- ExternalDbConnectionModal, ScriptsManagerDialog 등 신규 컴포넌트
- lib/api: automationDashboard, centralForwarder, equipmentState, fleet

docs/
- EDGE_SERVER_STRUCTURE, FLEET_COMPLETE, FLEET_EDGE_INTEGRATION, FLEET_HOOK_INTEGRATION

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 20:00:06 +09:00

214 lines
8.9 KiB
TypeScript

import { apiClient } from "./client";
const BASE = "/fleet";
export interface FleetDevice {
id?: number;
device_id: string;
company_code?: string;
device_name?: string;
device_type?: string;
ip_address?: string;
mac_address?: string;
hardware_fingerprint?: string;
last_seen_at?: string;
is_online?: boolean;
equipment_id?: number | null;
equipment_name?: string;
equipment_code?: string;
agent_version?: string;
os_info?: Record<string, any>;
hardware_info?: Record<string, any>;
device_group?: string;
tags?: any[];
}
export interface FleetCommand {
id?: number;
device_id: string;
command_type: string;
payload?: Record<string, any>;
status?: string;
result?: Record<string, any>;
error_message?: string;
issued_by?: string;
issued_at?: string;
sent_at?: string;
responded_at?: string;
}
export interface FleetAlert {
id: number;
rule_id: number;
rule_name?: string;
device_id: string;
severity: string;
title: string;
message: string;
metric: string;
value: number;
threshold: number;
status: "open" | "acknowledged" | "resolved";
created_at: string;
}
export const fleetApi = {
// 디바이스
getDevices: (filter?: { is_online?: boolean; search?: string }) =>
apiClient.get(`${BASE}/devices`, { params: filter }).then((r) => r.data),
getDevice: (deviceId: string) =>
apiClient.get(`${BASE}/devices/${deviceId}`).then((r) => r.data),
registerDevice: (data: Partial<FleetDevice>) =>
apiClient.post(`${BASE}/devices/register`, data).then((r) => r.data),
updateDevice: (deviceId: string, data: Partial<FleetDevice>) =>
apiClient.patch(`${BASE}/devices/${deviceId}`, data).then((r) => r.data),
deleteDevice: (deviceId: string) =>
apiClient.delete(`${BASE}/devices/${deviceId}`).then((r) => r.data),
getMetrics: (deviceId: string, limit = 100) =>
apiClient.get(`${BASE}/devices/${deviceId}/metrics`, { params: { limit } }).then((r) => r.data),
// 커맨드
getCommands: (filter?: { device_id?: string; status?: string; limit?: number }) =>
apiClient.get(`${BASE}/commands`, { params: filter }).then((r) => r.data),
getCommandTypes: () =>
apiClient.get(`${BASE}/commands/types`).then((r) => r.data),
issueCommand: (data: { device_id: string; command_type: string; payload?: any; timeout_sec?: number }) =>
apiClient.post(`${BASE}/commands`, data).then((r) => r.data),
// 알림
getAlerts: (status: string = "open") =>
apiClient.get(`${BASE}/alerts`, { params: { status } }).then((r) => r.data),
ackAlert: (id: number) =>
apiClient.post(`${BASE}/alerts/${id}/ack`).then((r) => r.data),
resolveAlert: (id: number) =>
apiClient.post(`${BASE}/alerts/${id}/resolve`).then((r) => r.data),
getAlertRules: () =>
apiClient.get(`${BASE}/alert-rules`).then((r) => r.data),
// 배포
getDeployments: () =>
apiClient.get(`${BASE}/deployments`).then((r) => r.data),
getReleases: () =>
apiClient.get(`${BASE}/releases`).then((r) => r.data),
// 통계
getStats: () =>
apiClient.get(`${BASE}/stats`).then((r) => r.data),
// 실시간 데이터
getLatestValues: (deviceId: string) =>
apiClient.get(`${BASE}/devices/${deviceId}/latest-values`).then((r) => r.data),
getLatestValuesByEquipment: (equipmentId: number) =>
apiClient.get(`${BASE}/equipment/${equipmentId}/latest-values`).then((r) => r.data),
getTagTimeseries: (deviceId: string, tagName: string, limit = 500) =>
apiClient
.get(`${BASE}/devices/${deviceId}/tags/${encodeURIComponent(tagName)}/timeseries`, {
params: { limit },
})
.then((r) => r.data),
getDataStats: (deviceId?: string) =>
apiClient.get(`${BASE}/data/stats`, { params: { device_id: deviceId } }).then((r) => r.data),
// ===== Python Hook 스크립트 =====
getHookTypes: () =>
apiClient.get(`${BASE}/scripts/hook-types`).then((r) => r.data),
listScripts: (filter?: any) =>
apiClient.get(`${BASE}/scripts`, { params: filter }).then((r) => r.data),
getScript: (id: number) =>
apiClient.get(`${BASE}/scripts/${id}`).then((r) => r.data),
createScript: (data: any) =>
apiClient.post(`${BASE}/scripts`, data).then((r) => r.data),
updateScript: (id: number, data: any) =>
apiClient.put(`${BASE}/scripts/${id}`, data).then((r) => r.data),
deleteScript: (id: number) =>
apiClient.delete(`${BASE}/scripts/${id}`).then((r) => r.data),
dryRunScript: (code: string, hook_type: string, test_input: any, timeout_ms?: number) =>
apiClient.post(`${BASE}/scripts/dry-run`, { code, hook_type, test_input, timeout_ms }).then((r) => r.data),
getScriptVersions: (id: number) =>
apiClient.get(`${BASE}/scripts/${id}/versions`).then((r) => r.data),
getScriptVersion: (id: number, version: number) =>
apiClient.get(`${BASE}/scripts/${id}/versions/${version}`).then((r) => r.data),
rollbackScript: (id: number, version: number) =>
apiClient.post(`${BASE}/scripts/${id}/rollback/${version}`).then((r) => r.data),
// ===== 릴리즈 =====
getReleases: (filter?: any) => apiClient.get(`${BASE}/releases`, { params: filter }).then(r => r.data),
getRelease: (id: number) => apiClient.get(`${BASE}/releases/${id}`).then(r => r.data),
createRelease: (data: any) => apiClient.post(`${BASE}/releases`, data).then(r => r.data),
updateRelease: (id: number, data: any) => apiClient.put(`${BASE}/releases/${id}`, data).then(r => r.data),
deleteRelease: (id: number) => apiClient.delete(`${BASE}/releases/${id}`).then(r => r.data),
transitionRelease: (id: number, status: string) =>
apiClient.post(`${BASE}/releases/${id}/transition`, { status }).then(r => r.data),
// ===== 배포 =====
createDeployment: (data: any) => apiClient.post(`${BASE}/deployments`, data).then(r => r.data),
getDeploymentDetail: (id: number) => apiClient.get(`${BASE}/deployments/${id}`).then(r => r.data),
getDeploymentStatus: (id: number) => apiClient.get(`${BASE}/deployments/${id}/status`).then(r => r.data),
startDeployment: (id: number) => apiClient.post(`${BASE}/deployments/${id}/start`).then(r => r.data),
cancelDeployment: (id: number) => apiClient.post(`${BASE}/deployments/${id}/cancel`).then(r => r.data),
rollbackDeployment: (id: number) => apiClient.post(`${BASE}/deployments/${id}/rollback`).then(r => r.data),
// ===== Harbor =====
getHarborProjects: () => apiClient.get(`${BASE}/harbor/projects`).then(r => r.data),
getHarborRepos: (project: string) => apiClient.get(`${BASE}/harbor/projects/${project}/repos`).then(r => r.data),
getHarborTags: (project: string, repo: string) =>
apiClient.get(`${BASE}/harbor/projects/${project}/repos/${repo}/tags`).then(r => r.data),
pingHarbor: () => apiClient.get(`${BASE}/harbor/ping`).then(r => r.data),
// ===== 태그 템플릿 =====
getTagTemplates: (filter?: any) => apiClient.get(`${BASE}/tag-templates`, { params: filter }).then(r => r.data),
getTagTemplate: (id: number) => apiClient.get(`${BASE}/tag-templates/${id}`).then(r => r.data),
createTagTemplate: (data: any) => apiClient.post(`${BASE}/tag-templates`, data).then(r => r.data),
updateTagTemplate: (id: number, data: any) => apiClient.put(`${BASE}/tag-templates/${id}`, data).then(r => r.data),
deleteTagTemplate: (id: number) => apiClient.delete(`${BASE}/tag-templates/${id}`).then(r => r.data),
applyTagTemplate: (templateId: number, connectionId: number, overwrite = false) =>
apiClient.post(`${BASE}/tag-templates/${templateId}/apply/${connectionId}`, { overwrite }).then(r => r.data),
// ===== 알림 규칙 =====
createAlertRule: (data: any) => apiClient.post(`${BASE}/alert-rules`, data).then(r => r.data),
updateAlertRule: (id: number, data: any) => apiClient.put(`${BASE}/alert-rules/${id}`, data).then(r => r.data),
deleteAlertRule: (id: number) => apiClient.delete(`${BASE}/alert-rules/${id}`).then(r => r.data),
toggleAlertRule: (id: number) => apiClient.post(`${BASE}/alert-rules/${id}/toggle`).then(r => r.data),
// ===== V1 매핑 =====
getV1Mappings: (filter?: any) => apiClient.get(`${BASE}/v1-mappings`, { params: filter }).then(r => r.data),
createV1Mapping: (data: any) => apiClient.post(`${BASE}/v1-mappings`, data).then(r => r.data),
updateV1Mapping: (id: number, data: any) => apiClient.put(`${BASE}/v1-mappings/${id}`, data).then(r => r.data),
deleteV1Mapping: (id: number) => apiClient.delete(`${BASE}/v1-mappings/${id}`).then(r => r.data),
// ===== PLC 상태 =====
getPlcStatus: (filter?: any) => apiClient.get(`${BASE}/plc-status`, { params: filter }).then(r => r.data),
getPlcSummary: () => apiClient.get(`${BASE}/plc-status/summary`).then(r => r.data),
// ===== Audit =====
getAuditLogs: (filter?: any) => apiClient.get(`${BASE}/audit-logs`, { params: filter }).then(r => r.data),
getAuditStats: () => apiClient.get(`${BASE}/audit-logs/stats`).then(r => r.data),
// ===== Provisioning =====
getPreRegistered: () => apiClient.get(`${BASE}/provision/pre-registered`).then(r => r.data),
preRegister: (data: any) => apiClient.post(`${BASE}/provision/pre-register`, data).then(r => r.data),
};