[FE-28] 운영 확장 — 웹훅 수신 · 터널 상태
작업 내용 (설계 의도)
근거: 지원 관리 확장 FE 웹 설계 S-11 확장 (1단계) — 웹훅 수신 · 터널 상태 · 상태 표기 규칙(웹훅 검증 결과·터널 도달) · Query 규약(['operations', ...] staleTime 0) · API 연동 > 1단계의 S-11 행, 지원 관리 확장 TDD 1단계 — 운영 조회 확장.
변경 사항
- 외부 노출(터널)과 웹훅 수신이 1단계에 들어오면서, “지금 외부에서 우리 앱에 도달할 수 있는가” 를 확인할 자리가 필요해집니다. 기존 운영 화면(S-11)에 터널 카드와 웹훅 수신 세그먼트를 추가합니다.
api/operations.ts를 확장하고(웹훅 수신 목록·터널 상태)hooks/operations/useWebhookReceipts(useInfiniteQuery)·useTunnelStatus를 신규로 둡니다.- 터널 상태는 화면 최상단 고정 카드입니다. 세그먼트 안에 넣으면 다른 탭을 보는 동안 터널이 끊긴 걸 모릅니다.
- 터널 카드는 3가지 상황을 서로 다르게 표시합니다. 셋을 한 문구로 뭉치면 “고쳐야 할 일”과 “원래 그런 상태”를 구분할 수 없습니다.
- 조회 실패(요청 자체가 실패) — 중립 “확인할 수 없어요”.
ErrorState도 danger도 아닙니다. 조회 실패와 터널 끊김은 다른 사실입니다. reachable=false+publicHostname있음 — danger “끊김”. 실제 장애입니다.publicHostname=null— 중립 “터널이 설정되지 않았어요”. 아직 설정 안 한 상태이지 에러가 아닙니다.
- 조회 실패(요청 자체가 실패) — 중립 “확인할 수 없어요”.
- 웹훅 수신 세그먼트를 기존 세그먼트(수집 이력 / 알림 발송) 옆에 추가하고,
days(기본 7)·result필터와[더 보기]를 둡니다. 검증 결과는OK는 positive, 실패 3종(SIGNATURE_MISMATCH·TIMESTAMP_OUT_OF_RANGE·NONCE_REUSED)은 danger로 표시하되 사유 문자열은 응답 값을 그대로 씁니다. - 웹훅 수신 빈 상태는 에러가 아닙니다 — “최근 7일 수신 이력이 없어요 / Gmail 연동을 아직 켜지 않았다면 정상이에요”. 3단계 이전에는 항상 이 상태이므로, 경고처럼 보이면 매번 오탐을 만듭니다.
['operations', ...]쿼리는 기존 규약을 승계해staleTime: 0입니다 — 운영 이력은 항상 최신을 확인해야 합니다.pages/operations/OperationsPage.tsx는 단계 1에서 이 티켓만 수정합니다(3단계 소스 레지스트리는 FE-44, 다른 단계).POST /api/webhooks/contact-events를 FE가 호출하지 않습니다 — Gmail Apps Script가 호출하는 진입점입니다. FE는 수신 결과만 조회합니다. 백필 운영 명령(POST /api/operations/backfills/**)도 화면을 만들지 않습니다(1회성 배포 절차).
롤백: 터널 카드와 웹훅 세그먼트를 제거하면 기존 운영 화면(수집 이력·알림 발송)이 그대로 동작합니다 — 기존 세그먼트 로직은 수정하지 않습니다.
의존
- FE-20 —
WebhookReceiptResponse·TunnelStatusResponse·PageResponse<T>타입,['operations','webhook-receipts', ...]·['operations','tunnel-status']queryKey, 운영 MSW 목(실패 시나리오 포함). - FE-22 — 운영 화면이 인증 게이트 뒤에 배치된 라우트 골격.
- BE-54 — 운영 조회 확장 API(터널 상태는 앱이 자기 공개 호스트명으로 왕복 확인). 통합 검증은 BE-54 완료 후 수행합니다.
다이어그램
처리 흐름
sequenceDiagram participant User as 사용자 participant Page as OperationsPage participant Tunnel as useTunnelStatus participant Receipts as useWebhookReceipts participant Api as /api/operations User->>Page: 운영 화면 진입 Page->>Tunnel: 터널 상태 조회 Tunnel->>Api: GET tunnel-status Api-->>Tunnel: reachable · publicHostname User->>Page: 웹훅 수신 세그먼트 선택 Page->>Receipts: days=7 조회
컴포넌트 의존
flowchart LR Page[OperationsPage] --> Card[TunnelStatusCard] Page --> Section[WebhookReceiptSection] Card --> TunnelHook[useTunnelStatus] Section --> ReceiptHook[useWebhookReceipts] TunnelHook --> Api[api/operations] ReceiptHook --> Api Section --> Chip[Chip · Segment 기존] Page --> Existing[수집 이력 · 알림 발송 기존]
테스트 케이스
reachable=true이면 터널 카드에 positive “연결됨”과publicHostname·확인 시각이 보인다.- 터널 조회가 실패하면 중립 “확인할 수 없어요”가 보이고 danger 표시나
ErrorState는 보이지 않는다. reachable=false이고publicHostname이 있으면 danger “끊김”이 보이고, 조회 실패 문구와 다르게 표시된다.publicHostname=null이면 중립 “터널이 설정되지 않았어요”가 보이고 danger로 표시되지 않는다.- 웹훅 수신 목록이 있으면 검증 결과·엔드포인트·
providerMessageId·수신 시각이 렌더된다. verificationResult=OK는 positive로, 실패 3종은 danger로 렌더된다.- 웹훅 수신 0건이면 “최근 7일 수신 이력이 없어요”와 정상 안내가 보이고 에러 표현이 없다.
result필터를 바꾸면 해당 검증 결과만 조회된다.hasNext=true일 때만[더 보기]가 보이고, 누르면 기존 목록 아래에 이어 붙는다.- 웹훅 수신 목록 조회가 실패하면
ErrorState와[다시 시도]가 보이고 터널 카드는 정상 렌더된다. - 운영 쿼리는
staleTime: 0이라 화면 재진입 시 재조회가 발생한다. - 기존 수집 이력·알림 발송 세그먼트가 그대로 동작한다.
- 터널 카드와 웹훅 목록을 다크 모드로 렌더하면 시맨틱 토큰 class만 사용하고 하드코딩 색이 0건이다.