# task-456.1: 활성 프리뷰 동적 감지

## 레벨: Lv.1

## 문제
대시보드 "활성 프리뷰" 섹션이 `preview_manager.py`를 통해 시작된 프리뷰만 표시함.
직접 실행된 Vite/Next 서버(예: InsuRo port 3004)는 `preview-state.json`에 미등록이라 감지 안 됨.

## 수정 파일
- `/home/jay/workspace/dashboard/server.py` — `_get_previews()` 메서드 (line 1268~1303)

## 수정 방법
`_get_previews()` 메서드에서 기존 `PreviewManager.status()` 결과에 추가로:

1. `config/preview-ports.json`의 `port_assignments` 로드
2. 이미 `PreviewManager.status()`에서 감지된 프로젝트는 제외
3. 나머지 포트에 대해 TCP 소켓 연결 시도 (기존 `_check_http_response` 활용)
4. 응답하는 포트가 있으면 활성 프리뷰 목록에 추가

### 구체적 코드 흐름 (line 1268~1303 수정)

```python
def _get_previews(self) -> dict:
    try:
        scripts_dir = str(Path(__file__).parent.parent / "scripts")
        if scripts_dir not in sys.path:
            sys.path.insert(0, scripts_dir)
        from preview_manager import PreviewManager
        manager = PreviewManager()
        status_list = manager.status()

        previews = []
        tracked_ports = set()  # 이미 감지된 포트 추적

        for item in status_list:
            url = item.get("url", "")
            is_responding = self._check_http_response(url, timeout=2.0)
            if is_responding:
                preview = {
                    "project": item.get("project_name"),
                    "port": item.get("port"),
                    "url": url,
                    "pid": item.get("pid"),
                }
                previews.append(preview)
                tracked_ports.add(item.get("port"))
            else:
                print(f"⚠️ 좀비 프리뷰 감지: {item.get('project_name')} (PID {item.get('pid')}, 응답 없음)")

        # --- 동적 감지: preview-ports.json의 할당 포트 중 미감지 포트 확인 ---
        try:
            config_path = Path(__file__).parent.parent / "config" / "preview-ports.json"
            if config_path.exists():
                import json
                with open(config_path) as f:
                    port_config = json.load(f)
                port_assignments = port_config.get("port_assignments", {})
                tailscale_ip = port_config.get("tailscale_ip", "100.76.130.39")

                for project_name, port in port_assignments.items():
                    if port in tracked_ports:
                        continue  # 이미 감지됨
                    url = f"http://{tailscale_ip}:{port}/"
                    if self._check_http_response(url, timeout=2.0):
                        previews.append({
                            "project": project_name,
                            "port": port,
                            "url": url,
                            "pid": None,  # preview_manager 외부에서 실행됨
                        })
        except Exception as e:
            print(f"⚠️ 동적 프리뷰 감지 실패: {e}")

        return {"previews": previews}
    except Exception as e:
        print(f"⚠️ 프리뷰 목록 로드 실패: {e}")
        return {"previews": []}
```

## 검증
- InsuRo가 port 3004에서 실행 중일 때 대시보드 "활성 프리뷰"에 표시되는지 확인
- preview_manager로 시작한 프리뷰도 정상 표시되는지 확인 (기존 동작 유지)
- 포트가 닫혀있으면 표시 안 되는지 확인