KCG AI 기반 불법조업 탐지·차단 플랫폼 프론트엔드. React 19 + TypeScript 5.9 + Vite 8 + MapLibre + deck.gl + Zustand + Tailwind CSS. SFR 20개 전체 UI 구현 완료, 백엔드 연동 대기. - npm + Nexus 프록시 레지스트리 설정 - 팀 워크플로우 v1.6.1 부트스트랩 파일 배치 - .githooks (commit-msg, post-checkout) - package.json name: kcg-ai-monitoring v0.1.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
371 lines
18 KiB
Markdown
371 lines
18 KiB
Markdown
# KCG AI Monitoring - 아키텍처 문서
|
|
|
|
> AI 기반 불법조업 탐지 차단 플랫폼 프론트엔드
|
|
|
|
---
|
|
|
|
## 기술스택
|
|
|
|
| 분류 | 라이브러리 | 버전 | 역할 |
|
|
|------|-----------|------|------|
|
|
| UI 프레임워크 | React | 19.2.4 | 함수형 컴포넌트 + Hooks 기반 SPA |
|
|
| 언어 | TypeScript | 5.9 | 정적 타입, strict 모드 |
|
|
| 빌드 도구 | Vite | 8.0.3 | Rolldown 기반 ESM 번들링 (~480ms build), HMR |
|
|
| CSS 프레임워크 | Tailwind CSS | 4.2.2 | `@tailwindcss/vite` 플러그인 통합 |
|
|
| 지도 (베이스맵) | MapLibre GL | 5.22 | CartoDB 래스터 타일 (Dark/Light 자동 전환) |
|
|
| 지도 (벡터) | deck.gl | 9.2 | MapboxOverlay 기반 GPU 벡터 렌더링 (40만척+) |
|
|
| 차트 | ECharts | 6.0 (core) | `lib/charts` 래퍼를 통한 프리셋 차트 |
|
|
| 상태관리 | Zustand | 5.0 | 8개 독립 스토어 (vessel, patrol, event, kpi 등) |
|
|
| 라우팅 | react-router-dom | 7.12.0 | BrowserRouter, 중첩 Route |
|
|
| 다국어 | react-i18next + i18next | 17.0 + 26.0 | 10 NS, ko/en 네임스페이스 기반 |
|
|
| 스타일 변형 | class-variance-authority (CVA) | 0.7 | card/badge/statusDot variants |
|
|
| 아이콘 | lucide-react | 0.487.0 | SVG 아이콘 컴포넌트 |
|
|
| 린트 | ESLint | 10.2 | Flat Config, typescript-eslint + react-hooks + react-refresh |
|
|
|
|
---
|
|
|
|
## 디렉토리 구조
|
|
|
|
```
|
|
src/
|
|
├── lib/
|
|
│ ├── charts/ # BaseChart + 4 프리셋 (Area, Bar, Pie, Line)
|
|
│ │ ├── BaseChart.tsx # 코어 (init, resize, dispose 자동 관리, 'kcg-dark' 테마)
|
|
│ │ ├── theme.ts # ECharts 테마 등록
|
|
│ │ ├── tokens.ts # 차트 색상 토큰
|
|
│ │ └── presets/ # AreaChart, BarChart, PieChart, LineChart
|
|
│ ├── map/
|
|
│ │ ├── BaseMap.tsx # MapLibre + deck.gl (forwardRef, memo, overlay 노출)
|
|
│ │ ├── hooks/ # useMapLayers, useStoreLayerSync (RAF 기반)
|
|
│ │ ├── layers/ # markers, polyline, heatmap, zones, static (STATIC_LAYERS)
|
|
│ │ │ ├── markers.ts # createMarkerLayer (transitions + DataFilterExtension)
|
|
│ │ │ ├── static.ts # EEZ + NLL 싱글턴 (GPU 1회 업로드)
|
|
│ │ │ ├── polyline.ts # 경로/트랙 라인
|
|
│ │ │ ├── heatmap.ts # 히트맵 (위험도 시각화)
|
|
│ │ │ ├── zones.ts # 구역 원
|
|
│ │ │ └── boundaries.ts # 레거시 GeoJSON (하위 호환)
|
|
│ │ ├── controls/ # (예약) 지도 컨트롤 확장
|
|
│ │ ├── constants.ts # EEZ, NLL, 타일 URL, 기본값
|
|
│ │ └── types.ts # MapVessel, MapLayerConfig, HeatPoint
|
|
│ ├── i18n/ # 10 NS (common, dashboard, detection, patrol, enforcement, statistics, ai, fieldOps, admin, auth)
|
|
│ │ ├── config.ts # i18next 초기화 (ko 기본, en 폴백)
|
|
│ │ └── locales/ # ko/*.json, en/*.json (10파일 x 2언어)
|
|
│ └── theme/ # tokens, colors, variants (CVA)
|
|
│ ├── tokens.ts # CSS 변수 매핑 + resolved 색상값
|
|
│ ├── colors.ts # 시맨틱 팔레트 (risk, alert, vessel, status, chartSeries)
|
|
│ └── variants.ts # cardVariants, badgeVariants, statusDotVariants (CVA)
|
|
│
|
|
├── data/
|
|
│ ├── mock/ # 7 공유 mock 모듈
|
|
│ │ ├── vessels.ts # 선박 목록 (한국, 중국, 경비함)
|
|
│ │ ├── events.ts # 탐지/단속 이벤트
|
|
│ │ ├── transfers.ts # 전재(환적) 데이터
|
|
│ │ ├── patrols.ts # 순찰 경로/일정
|
|
│ │ ├── gear.ts # 어구 탐지 데이터
|
|
│ │ ├── kpi.ts # KPI/통계 데이터
|
|
│ │ └── enforcement.ts # 단속 이력 데이터
|
|
│ ├── areasCodes.json # 해역 코드 (52건)
|
|
│ ├── speciesCodes.json # 어종 코드 (578건)
|
|
│ ├── fisheryCodes.json # 어업유형 코드 (59건)
|
|
│ ├── vesselTypeCodes.json # 선박유형 코드 (186건)
|
|
│ └── commonCodes.ts # 코드 유틸리티
|
|
│
|
|
├── stores/ # 8 Zustand 스토어
|
|
│ ├── vesselStore.ts # 선박 목록, 선택, 필터
|
|
│ ├── patrolStore.ts # 순찰 경로/함정
|
|
│ ├── eventStore.ts # 탐지/경보 이벤트
|
|
│ ├── kpiStore.ts # KPI 메트릭, 추세
|
|
│ ├── transferStore.ts # 전재(환적) 데이터
|
|
│ ├── gearStore.ts # 어구 탐지
|
|
│ ├── enforcementStore.ts # 단속 이력
|
|
│ └── settingsStore.ts # theme/language + localStorage 동기화
|
|
│
|
|
├── services/ # 7 API 서비스 (현재 mock 반환)
|
|
│ ├── api.ts # fetch 래퍼 (향후 Axios 교체 예정)
|
|
│ ├── vessel.ts # getVessels, getSuspects, getVesselDetail
|
|
│ ├── event.ts # getEvents, getAlerts
|
|
│ ├── patrol.ts # getPatrolShips
|
|
│ ├── kpi.ts # getKpiMetrics, getMonthlyTrends, getViolationTypes
|
|
│ ├── ws.ts # connectWs (STOMP 스텁, 미구현)
|
|
│ └── index.ts # 배럴 export
|
|
│
|
|
├── shared/components/ # 공유 UI 컴포넌트
|
|
│ ├── ui/
|
|
│ │ ├── card.tsx # Card(CVA variant), CardHeader, CardTitle, CardContent
|
|
│ │ └── badge.tsx # Badge(CVA intent/size)
|
|
│ └── common/
|
|
│ ├── DataTable.tsx # 범용 테이블 (가변너비, 검색, 정렬, 페이징, 엑셀, 출력)
|
|
│ ├── Pagination.tsx # 페이지네이션
|
|
│ ├── SearchInput.tsx # 검색 입력
|
|
│ ├── ExcelExport.tsx # 엑셀 다운로드
|
|
│ ├── FileUpload.tsx # 파일 업로드
|
|
│ ├── PageToolbar.tsx # 페이지 상단 툴바
|
|
│ ├── PrintButton.tsx # 인쇄 버튼
|
|
│ ├── SaveButton.tsx # 저장 버튼
|
|
│ └── NotificationBanner.tsx # 알림 배너
|
|
│
|
|
├── features/ # 13 도메인 그룹 (31 페이지)
|
|
│ ├── dashboard/ # 종합 대시보드 (Dashboard)
|
|
│ ├── monitoring/ # 실시간 모니터링 (MonitoringDashboard)
|
|
│ ├── surveillance/ # 감시 (LiveMapView, MapControl)
|
|
│ ├── detection/ # 탐지 (DarkVessel, Gear, ChinaFishing)
|
|
│ ├── risk-assessment/ # 위험도 평가 (RiskMap, EnforcementPlan)
|
|
│ ├── patrol/ # 순찰 (PatrolRoute, FleetOptimization)
|
|
│ ├── enforcement/ # 단속 (EnforcementHistory, EventList)
|
|
│ ├── statistics/ # 통계 (Statistics, ExternalService, ReportManagement)
|
|
│ ├── ai-operations/ # AI 운영 (AIModelManagement, MLOps, AIAssistant)
|
|
│ ├── field-ops/ # 현장 대응 (MobileService, ShipAgent, AIAlert)
|
|
│ ├── admin/ # 관리 (AccessControl, SystemConfig, Notice, DataHub, AdminPanel)
|
|
│ ├── vessel/ # 선박 (VesselDetail, TransferDetection)
|
|
│ └── auth/ # 인증 (LoginPage)
|
|
│
|
|
├── app/ # 애플리케이션 셸
|
|
│ ├── App.tsx # BrowserRouter + Routes (26 보호 경로 + login)
|
|
│ ├── auth/AuthContext.tsx # 인증 컨텍스트 (ProtectedRoute)
|
|
│ └── layout/MainLayout.tsx # 사이드바 + 콘텐츠 + i18n 메뉴
|
|
│
|
|
└── styles/ # 전역 스타일
|
|
├── theme.css # CSS 커스텀 속성 (dark 기본 + .light 오버라이드)
|
|
├── tailwind.css # Tailwind 기본
|
|
└── fonts.css # 웹폰트
|
|
```
|
|
|
|
---
|
|
|
|
## Path Alias
|
|
|
|
| Alias | 경로 | 용도 |
|
|
|-------|------|------|
|
|
| `@/*` | `src/*` | 프로젝트 전체 절대 임포트 |
|
|
| `@lib/*` | `src/lib/*` | 공통 라이브러리 (charts, map, i18n, theme) |
|
|
| `@shared/*` | `src/shared/*` | 공유 UI 컴포넌트 |
|
|
| `@features/*` | `src/features/*` | 도메인 feature 모듈 |
|
|
| `@data/*` | `src/data/*` | 기준정보 + mock 데이터 |
|
|
| `@stores/*` | `src/stores/*` | Zustand 스토어 |
|
|
|
|
Vite `resolve.alias`와 TypeScript `compilerOptions.paths`에 동일하게 설정되어 있다.
|
|
|
|
---
|
|
|
|
## 의존성
|
|
|
|
### 프로덕션 (11개)
|
|
|
|
| 패키지 | 버전 | 용도 |
|
|
|--------|------|------|
|
|
| react | ^19.2.4 | UI 렌더링 |
|
|
| react-dom | ^19.2.4 | DOM 렌더링 |
|
|
| react-router-dom | ^7.12.0 | SPA 라우팅 |
|
|
| maplibre-gl | ^5.22.0 | 래스터 베이스맵 엔진 |
|
|
| deck.gl | ^9.2.11 | GPU 벡터 렌더링 (ScatterplotLayer, PathLayer 등) |
|
|
| @deck.gl/mapbox | ^9.2.11 | MapboxOverlay (MapLibre 인터리브) |
|
|
| echarts | ^6.0.0 | 차트 라이브러리 |
|
|
| zustand | ^5.0.12 | 경량 상태관리 (8개 스토어) |
|
|
| class-variance-authority | ^0.7.1 | Tailwind 변형 관리 (CVA) |
|
|
| react-i18next | ^17.0.2 | React 다국어 바인딩 |
|
|
| i18next | ^26.0.3 | 다국어 코어 |
|
|
| lucide-react | 0.487.0 | SVG 아이콘 |
|
|
|
|
### 개발 (13개)
|
|
|
|
| 패키지 | 버전 | 용도 |
|
|
|--------|------|------|
|
|
| vite | ^8.0.3 | 빌드/개발 서버 (Rolldown) |
|
|
| @vitejs/plugin-react | ^6.0.1 | React Fast Refresh |
|
|
| tailwindcss | ^4.2.2 | 유틸리티 CSS |
|
|
| @tailwindcss/vite | ^4.2.2 | Tailwind Vite 플러그인 |
|
|
| typescript | 5.9 | 타입 체크 |
|
|
| eslint | ^10.2.0 | 린트 (Flat Config) |
|
|
| @eslint/js | ^10.0.1 | ESLint JS 설정 |
|
|
| typescript-eslint | ^8.58.0 | TS ESLint 파서/규칙 |
|
|
| eslint-plugin-react-hooks | ^7.0.1 | Hooks 규칙 |
|
|
| eslint-plugin-react-refresh | ^0.5.2 | Fast Refresh 규칙 |
|
|
| globals | ^17.4.0 | 전역 변수 정의 |
|
|
| @types/react | ^19.2.14 | React 타입 |
|
|
| @types/react-dom | ^19.2.3 | ReactDOM 타입 |
|
|
|
|
---
|
|
|
|
## 공통 모듈 API 요약
|
|
|
|
### lib/map — BaseMap + deck.gl 최적화
|
|
|
|
**BaseMap** — MapLibre + deck.gl 통합 컴포넌트 (`forwardRef`, `memo`)
|
|
|
|
```typescript
|
|
interface BaseMapProps {
|
|
center?: [number, number]; // [lat, lng] 기본값 [35.5, 127.0]
|
|
zoom?: number; // 기본값 7
|
|
className?: string;
|
|
style?: React.CSSProperties;
|
|
height?: number | string; // 기본값 '100%'
|
|
layers?: Layer[]; // @deprecated — useMapLayers hook 사용 권장
|
|
onMapReady?: (map: Map) => void; // 지도 로드 완료 콜백
|
|
onClick?: (info: unknown) => void;
|
|
interactive?: boolean; // 기본 true
|
|
}
|
|
|
|
// ref를 통해 MapHandle 노출
|
|
interface MapHandle {
|
|
overlay: MapboxOverlay | null; // deck.gl overlay 직접 접근
|
|
}
|
|
```
|
|
|
|
- `useImperativeHandle`로 overlay 외부 노출 → hook에서 직접 `setProps()` 호출
|
|
- CartoDB Dark/Light 래스터 타일 자동 전환 (`settingsStore.theme` 구독)
|
|
- `interactive=false`로 정적 지도 프리뷰 생성 가능
|
|
|
|
**useMapLayers** — RAF 배치 레이어 업데이트 (React 리렌더 0회)
|
|
|
|
```typescript
|
|
useMapLayers(
|
|
handleRef: RefObject<MapHandle>, // BaseMap ref
|
|
buildLayers: () => Layer[], // 레이어 빌드 함수
|
|
deps: unknown[], // 변경 감지 (shallow 비교)
|
|
)
|
|
```
|
|
|
|
**useStoreLayerSync** — Zustand store.subscribe + RAF 기반 레이어 동기화
|
|
|
|
```typescript
|
|
useStoreLayerSync<T>(
|
|
handleRef: RefObject<MapHandle>,
|
|
subscribe: (cb: (state: T) => void) => () => void,
|
|
buildLayers: (state: T) => Layer[],
|
|
)
|
|
```
|
|
|
|
**STATIC_LAYERS** — EEZ + NLL 싱글턴 (모듈 로드 시 1회 생성, GPU 재전송 없음)
|
|
|
|
**createMarkerLayer** — ScatterplotLayer 생성 (transitions 보간 + DataFilterExtension)
|
|
|
|
### lib/charts — ECharts 래퍼
|
|
|
|
**BaseChart** — ECharts 코어 래퍼 컴포넌트
|
|
|
|
```typescript
|
|
interface BaseChartProps {
|
|
option: EChartsOption;
|
|
className?: string;
|
|
style?: React.CSSProperties;
|
|
height?: number; // 기본값 200
|
|
notMerge?: boolean;
|
|
onEvents?: Record<string, (params: unknown) => void>;
|
|
}
|
|
```
|
|
|
|
- `echarts.init(container, 'kcg-dark')` 프로젝트 다크 테마 자동 적용
|
|
- `ResizeObserver` 자동 리사이즈, unmount 시 `dispose` 자동 정리
|
|
|
|
**프리셋 차트**
|
|
|
|
| 컴포넌트 | 주요 Props | 설명 |
|
|
|----------|-----------|------|
|
|
| `AreaChart` | `data`, `xKey`, `series`, `yAxisDomain?` | smooth line + 반투명 영역 |
|
|
| `BarChart` | `data`, `xKey`, `series`, `horizontal?`, `itemColors?` | 수직/수평 막대, 항목별 색상 |
|
|
| `PieChart` | `data: {name, value, color?}[]`, `innerRadius?`, `outerRadius?` | 파이/도넛 (기본 도넛) |
|
|
| `LineChart` | `data`, `xKey`, `series` | smooth 라인, 원형 심볼 |
|
|
|
|
### lib/theme — CVA 변형 + 디자인 토큰
|
|
|
|
| 모듈 | 내용 |
|
|
|------|------|
|
|
| `variants.ts` | `cardVariants` (default/elevated/inner/transparent), `badgeVariants` (8 intent x 4 size), `statusDotVariants` (4 status x 3 size) |
|
|
| `tokens.ts` | `cssVars` (CSS 변수 참조), `resolvedColors` (ECharts/MapLibre용 하드코딩 값) |
|
|
| `colors.ts` | `riskColors` (5단계), `alertStyles`, `vesselColors`, `statusColors`, `chartSeriesColors` (8색 팔레트) |
|
|
|
|
### lib/i18n — 10 네임스페이스 다국어
|
|
|
|
- `i18next` + `react-i18next` 기반
|
|
- 기본 언어: `ko`, 폴백: `ko`, 지원: `ko` / `en`
|
|
- 10 네임스페이스: `common`, `dashboard`, `detection`, `patrol`, `enforcement`, `statistics`, `ai`, `fieldOps`, `admin`, `auth`
|
|
- 사용: `useTranslation('namespace')` → `t('key')`
|
|
- 언어 전환: `settingsStore.toggleLanguage()` + `localStorage` 동기화
|
|
|
|
---
|
|
|
|
## 렌더링 최적화 아키텍처
|
|
|
|
```
|
|
store 변경 → useStoreLayerSync → RAF → overlay.setProps() (React 리렌더 0회)
|
|
deps 변경 → useMapLayers → RAF → overlay.setProps() (React 리렌더 0회)
|
|
정적 레이어 → STATIC_LAYERS 싱글턴 (GPU 1회 업로드, 모든 페이지 공유)
|
|
동적 레이어 → transitions 보간 + DataFilterExtension (GPU 필터링)
|
|
시계/타이머 → useRef + DOM textContent 직접 조작 (setState 0회)
|
|
```
|
|
|
|
핵심 원칙: **React render cycle 완전 우회**. deck.gl overlay에 직접 `setProps()`를 호출하여 40만척+ 실시간 렌더링 시에도 React 리렌더가 발생하지 않는다.
|
|
|
|
---
|
|
|
|
## 테마 시스템
|
|
|
|
- **기본값**: `:root` = dark 테마, `.light` 클래스 오버라이드
|
|
- **시맨틱 CSS 변수**: `surface-raised`, `surface-overlay`, `text-heading`, `text-label`, `text-hint`, `border`
|
|
- **Tailwind 통합**: `@theme inline`으로 CSS 변수를 Tailwind 유틸리티에 매핑
|
|
- **settingsStore**: `theme` / `language` + `localStorage` 자동 동기화
|
|
- **지도 타일**: CartoDB Dark Matter ↔ CartoDB Positron 자동 전환 (`settingsStore.theme` 구독)
|
|
- **CVA 변형**: `cardVariants`, `badgeVariants`, `statusDotVariants`가 CSS 변수 참조 → 테마 자동 반응
|
|
|
|
---
|
|
|
|
## 라우팅 구조 (26 보호 경로 + login)
|
|
|
|
`App.tsx`에서 `BrowserRouter` > `AuthProvider` > `Routes`로 구성된다.
|
|
|
|
- `/login` — 비보호 라우트 (LoginPage)
|
|
- `/` — `ProtectedRoute` > `MainLayout` (사이드바 + Outlet)
|
|
- `/` → `/dashboard` 리다이렉트
|
|
- `/dashboard` — 종합 대시보드 (SFR-12)
|
|
- `/monitoring` — 실시간 모니터링
|
|
- `/risk-map` — 위험도 평가 (SFR-05)
|
|
- `/enforcement-plan` — 단속계획 (SFR-06)
|
|
- `/dark-vessel` — 무등화 선박 탐지 (SFR-09)
|
|
- `/gear-detection` — 어구 탐지 (SFR-10)
|
|
- `/china-fishing` — 중국어선 탐지
|
|
- `/patrol-route` — 순찰경로 (SFR-07)
|
|
- `/fleet-optimization` — 함대 최적화 (SFR-08)
|
|
- `/enforcement-history` — 단속 이력 (SFR-11)
|
|
- `/event-list` — 이벤트 목록
|
|
- `/mobile-service` — 현장 모바일 (SFR-15)
|
|
- `/ship-agent` — 함정 에이전트 (SFR-16)
|
|
- `/ai-alert` — AI 경보 (SFR-17)
|
|
- `/statistics` — 통계 (SFR-13)
|
|
- `/external-service` — 외부연계 (SFR-14)
|
|
- `/reports` — 보고서 관리
|
|
- `/ai-model` — AI 모델 관리 (SFR-04)
|
|
- `/mlops` — MLOps (SFR-18~19)
|
|
- `/ai-assistant` — AI 어시스턴트 (SFR-20)
|
|
- `/data-hub` — 데이터허브 (SFR-03)
|
|
- `/system-config` — 환경설정 (SFR-02)
|
|
- `/notices` — 공지사항
|
|
- `/access-control` — 접근 권한 (SFR-01)
|
|
- `/admin` — 시스템 관리
|
|
- `/events` — 감시 (LiveMapView)
|
|
- `/map-control` — 지도 컨트롤
|
|
- `/vessel/:id` — 선박 상세
|
|
|
|
인증은 `AuthContext`의 `useAuth().user` 존재 여부로 판단하며, 미인증 시 `/login`으로 리다이렉트한다.
|
|
|
|
---
|
|
|
|
## 빌드 설정
|
|
|
|
- **TypeScript**: `target: ES2020`, `module: ESNext`, `moduleResolution: bundler`, `strict: true`
|
|
- **Vite**: `react()` + `tailwindcss()` 플러그인, 6개 path alias (`@`, `@lib`, `@shared`, `@features`, `@data`, `@stores`)
|
|
- **ESLint 10 Flat Config**: `typescript-eslint` + `react-hooks` + `react-refresh` 규칙
|
|
- **빌드 속도**: Rolldown 기반 ~480ms
|
|
|
|
---
|
|
|
|
## 현재 아키텍처 특성
|
|
|
|
1. **Zustand 8개 스토어**: vessel, patrol, event, kpi, transfer, gear, enforcement, settings. `settingsStore`는 theme/language + localStorage 동기화 담당.
|
|
2. **deck.gl GPU 렌더링**: MapLibre(래스터 베이스맵) + deck.gl(벡터). React 리렌더 완전 분리, RAF 기반 `overlay.setProps()` 직접 호출.
|
|
3. **CVA 스타일 시스템**: `cardVariants`, `badgeVariants`, `statusDotVariants`로 Tailwind 패턴 통합. CSS 변수 기반 테마 반응.
|
|
4. **mock 기반 서비스 계층**: 7개 API 서비스가 `data/mock/` 모듈에서 데이터 반환. 향후 Axios + 실제 API로 교체 예정.
|
|
5. **i18n 10 NS 구조 완성**: 리소스 파일 완비, MainLayout 메뉴 + 페이지 제목 + LoginPage 적용 완료. 페이지 내부 텍스트는 대부분 한국어 하드코딩 잔존.
|
|
6. **Dark/Light 테마**: CSS 변수 기반 양방향 테마. 지도 타일 자동 전환. 일부 alert 색상(`red-500/20` 등) 하드코딩 잔존.
|
|
7. **단일 번들**: 코드 스플리팅 미적용 (~3.2MB), React.lazy 미사용. 모든 feature가 단일 번들로 빌드.
|
|
8. **WebSocket 미구현**: `connectWs` 스텁만 존재, STOMP.js + SockJS 미설치.
|