iran 프로젝트의 gear-parent-flow 패턴을 차용하여 시스템 전체 데이터 흐름을
노드/엣지로 시각화하는 별도 React 앱 추가. 메인 SPA와 완전 분리.
## 인프라
- @xyflow/react 추가
- frontend/system-flow.html (별도 entry HTML)
- frontend/src/systemFlowMain.tsx (React entry)
- vite.config.ts: rollupOptions.input에 systemFlow 추가
- 빌드 산출물: dist/system-flow.html + dist/assets/systemFlow-*.js (231kB, 메인과 분리)
## 매니페스트 (frontend/src/flow/manifest/)
카테고리별 JSON 분할 + 빌드 시 병합:
- 01-ingest.json (6) — snpdb, vessel_store, refresh
- 02-pipeline.json (7) — 7단계 분류 파이프라인
- 03-algorithms.json (12) — zone/dark/spoofing/risk/transship 등
- 04-fleet.json (9) — fleet_tracker, polygon_builder, gear_correlation, parent_inference
- 05-output.json (8) — event/violation/kpi/stats/alert/redis
- 06-storage.json (18) — 핵심 DB 테이블
- 07-backend.json (15) — Spring Boot 컨트롤러 + endpoint
- 08-frontend.json (17) — 프론트 화면 (menu 매핑 포함)
- 09-decision.json (8) — 운영자 의사결정 액션
- 10-external.json (2) — iran, redis
- edges.json (133) — data/trigger/feedback 분류
## 뷰어 컴포넌트
- SystemFlowViewer.tsx — 3단 레이아웃 + React Flow + 상태 관리
- components/FilterBar.tsx — 검색/단계/메뉴/상세필터 + 레이아웃 토글
- components/NodeListSidebar.tsx — 좌측 카테고리별 노드 리스트
- components/NodeDetailPanel.tsx — 우측 선택 정보 + incoming/outgoing 흐름
- components/nodeShapes.ts — kind별 모양/색상 헬퍼
- SystemFlowViewer.css — 전용 다크 테마 스타일
## 기능
- stage(단계) ⇄ menu(메뉴) 두 가지 그룹화 토글
- 통합 검색 (label/file/symbol/tag)
- 다중 필터 (kind/trigger/status)
- 노드 모양: kind별 (algorithm=다이아몬드, decision=마름모, api=6각형 등)
- 엣지 색상: data=회색, trigger=녹색, feedback=노란 점선
- 딥링크: /system-flow.html#node=<id> (산출문서에서 직접 참조)
## /version 스킬 통합
- CLAUDE.md에 "/version 스킬 사후 처리" 섹션 추가
Claude가 /version 호출 후 자동으로 manifest.meta version/updatedAt/releaseDate 갱신
- .gitea/workflows/deploy.yml에 archive 보존 단계 추가
/deploy/kcg-ai-monitoring-archive/system-flow/v{version}_{date}/ 영구 누적
(nginx 노출 X, 서버 로컬 보존)
- docs/system-flow-guide.md 작성 (URL, 노드 ID 명명, 산출문서 참조법, 갱신 절차)
## URL
- 운영: https://kcg-ai-monitoring.gc-si.dev/system-flow.html
- 메인 SPA에 링크 노출 없음 (개발 단계 페이지)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
166 lines
4.7 KiB
TypeScript
166 lines
4.7 KiB
TypeScript
/**
|
|
* System Flow Manifest — 카테고리별 JSON 병합 + 타입 정의
|
|
*
|
|
* 노드/엣지 데이터는 카테고리별 JSON 파일로 분할 관리되며,
|
|
* 이 파일에서 import + concat 후 단일 manifest로 export.
|
|
*/
|
|
|
|
import metaJson from './meta.json';
|
|
import ingest from './01-ingest.json';
|
|
import pipeline from './02-pipeline.json';
|
|
import algorithms from './03-algorithms.json';
|
|
import fleet from './04-fleet.json';
|
|
import output from './05-output.json';
|
|
import storage from './06-storage.json';
|
|
import backend from './07-backend.json';
|
|
import frontend from './08-frontend.json';
|
|
import decision from './09-decision.json';
|
|
import external from './10-external.json';
|
|
import edgesJson from './edges.json';
|
|
|
|
// ─── 타입 정의 ──────────────────────────────────────────────
|
|
|
|
export type NodeKind =
|
|
| 'source' // 원천 데이터 (snpdb 등)
|
|
| 'cache' // 메모리/Redis 캐시
|
|
| 'pipeline' // 7단계 분류 파이프라인 단계
|
|
| 'algorithm' // 분석 알고리즘
|
|
| 'output' // 출력 모듈 (event_generator 등)
|
|
| 'storage' // DB 테이블
|
|
| 'api' // 백엔드 API 엔드포인트
|
|
| 'ui' // 프론트 화면
|
|
| 'decision' // 운영자 의사결정 액션
|
|
| 'external'; // 외부 시스템 (iran, GPKI 등)
|
|
|
|
export type NodeTrigger =
|
|
| 'scheduled' // 5분 주기 등 자동
|
|
| 'event' // 이벤트 체이닝
|
|
| 'user_action' // 운영자 클릭/입력
|
|
| 'on_demand'; // 사용자 조회 시
|
|
|
|
export type NodeStatus = 'implemented' | 'partial' | 'planned' | 'deprecated';
|
|
|
|
export type EdgeKind = 'data' | 'trigger' | 'feedback';
|
|
|
|
export interface FlowNode {
|
|
id: string;
|
|
label: string;
|
|
shortDescription: string;
|
|
stage: string;
|
|
menu?: string;
|
|
kind: NodeKind;
|
|
trigger: NodeTrigger;
|
|
status: NodeStatus;
|
|
file?: string;
|
|
symbol?: string;
|
|
lineRange?: [number, number];
|
|
inputs?: string[];
|
|
outputs?: string[];
|
|
params?: string[];
|
|
rules?: string[];
|
|
actor?: string;
|
|
triggers?: string[];
|
|
tags?: string[];
|
|
notes?: string;
|
|
position?: { x: number; y: number };
|
|
}
|
|
|
|
export interface FlowEdge {
|
|
id: string;
|
|
source: string;
|
|
target: string;
|
|
label?: string;
|
|
detail?: string;
|
|
kind?: EdgeKind;
|
|
}
|
|
|
|
export interface FlowMeta {
|
|
version: string;
|
|
updatedAt: string;
|
|
releaseDate?: string;
|
|
description: string;
|
|
nodeCount?: number;
|
|
edgeCount?: number;
|
|
}
|
|
|
|
export interface FlowManifest {
|
|
meta: FlowMeta;
|
|
nodes: FlowNode[];
|
|
edges: FlowEdge[];
|
|
}
|
|
|
|
// ─── 병합 ──────────────────────────────────────────────────
|
|
|
|
const allNodes: FlowNode[] = [
|
|
...(ingest as FlowNode[]),
|
|
...(pipeline as FlowNode[]),
|
|
...(algorithms as FlowNode[]),
|
|
...(fleet as FlowNode[]),
|
|
...(output as FlowNode[]),
|
|
...(storage as FlowNode[]),
|
|
...(backend as FlowNode[]),
|
|
...(frontend as FlowNode[]),
|
|
...(decision as FlowNode[]),
|
|
...(external as FlowNode[]),
|
|
];
|
|
|
|
const allEdges: FlowEdge[] = edgesJson as FlowEdge[];
|
|
|
|
export const manifest: FlowManifest = {
|
|
meta: {
|
|
...(metaJson as FlowMeta),
|
|
nodeCount: allNodes.length,
|
|
edgeCount: allEdges.length,
|
|
},
|
|
nodes: allNodes,
|
|
edges: allEdges,
|
|
};
|
|
|
|
// ─── stage 색상 매핑 (다크 테마) ──────────────────────────
|
|
|
|
export const STAGE_COLORS: Record<string, string> = {
|
|
수집: '#38bdf8', // sky-400
|
|
캐시: '#a78bfa', // violet-400
|
|
파이프라인: '#60a5fa', // blue-400
|
|
분석: '#c084fc', // purple-400
|
|
선단: '#f472b6', // pink-400
|
|
출력: '#fb923c', // orange-400
|
|
저장소: '#facc15', // yellow-400
|
|
API: '#22c55e', // green-500
|
|
UI: '#14b8a6', // teal-500
|
|
의사결정: '#f59e0b', // amber-500
|
|
외부: '#94a3b8', // slate-400
|
|
};
|
|
|
|
export const KIND_LABELS: Record<NodeKind, string> = {
|
|
source: '원천',
|
|
cache: '캐시',
|
|
pipeline: '파이프라인',
|
|
algorithm: '알고리즘',
|
|
output: '출력 모듈',
|
|
storage: '저장소',
|
|
api: 'API',
|
|
ui: '화면',
|
|
decision: '의사결정',
|
|
external: '외부',
|
|
};
|
|
|
|
export const TRIGGER_LABELS: Record<NodeTrigger, string> = {
|
|
scheduled: '주기 자동',
|
|
event: '이벤트',
|
|
user_action: '사용자 액션',
|
|
on_demand: '조회 시',
|
|
};
|
|
|
|
export const STATUS_LABELS: Record<NodeStatus, string> = {
|
|
implemented: '구현됨',
|
|
partial: '부분 구현',
|
|
planned: '계획',
|
|
deprecated: '폐기 예정',
|
|
};
|
|
|
|
export const ALL_STAGES = Array.from(new Set(allNodes.map((n) => n.stage)));
|
|
export const ALL_MENUS = Array.from(
|
|
new Set(allNodes.map((n) => n.menu).filter(Boolean) as string[]),
|
|
);
|