08. Console Design
목적
Console이 어떤 데이터를 어떻게 보여주는지 정의한다.
범위
console 모듈(React + TypeScript + Vite). Collector REST API 계약은 docs/07-api-design.md를 참고한다.
설계
- MVP read-only 단계는 종료되었다. Console도 이제 계정 관리, 프로필 설정 등 일부 쓰기 작업을 수행할 수 있다. 다만 집계/비율/통계 같은 비즈니스 계산 로직은 여전히 Collector REST API가 담당하며, Console은 API 응답을 조합해 표시하는 역할에 집중한다 (AGENTS.md 3.4). 로그인 화면 추가 이후에도 "계산 로직은 백엔드가 담당한다"는 원칙은 그대로 유지된다 - 인증은 화면 접근 제어일 뿐 비즈니스 로직이 아니다.
- 상태관리 라이브러리(Redux 등) 없이
useState/useEffect+fetch만 사용한다. - MVP 서빙 방식: 독립 dev 서버(
npm run dev, Vite 기본 포트 5173)로 실행하거나, Installer(Docker Compose)로 nginx 정적 빌드를 띄운다 (docs/09-installer-design.md). - 레이아웃: 여백을 최소화한 꽉 찬(dense) 한국형 대시보드. Sentry/Datadog류의 모듈 구성(다크 사이드바, 카드, 통계 타일)은 유지하되, 콘텐츠 영역은 최대 폭 제한 없이 화면 전체를 사용하고(
AppLayoutfull-width), 카드/테이블 패딩과 행 높이를 줄여 같은 화면에 더 많은 데이터가 보이도록 했다 (theme.ts의MuiTableCell/MuiCardContentoverride,DataGridrowHeight 32px). - 디자인 시스템: MUI(Material UI) 기반 MD3(Material Design 3) 스타일. 이전에는 "UI 프레임워크 없음, 순수 CSS"였으나, 로그인/사이드바 작업을 계기로
@mui/material+@emotion/react+@emotion/styled+@mui/icons-material를 도입했다 (.claude/decisions.md참고). 주의: MUI의 기본 테마는 Material 2에 가깝고 MD3를 그대로 구현하지 않는다.console/src/theme.ts에서 팔레트/모서리 반경(shape)/타이포그래피를 MD3에 가깝게 커스터마이징한 것으로, "MD3 컴포넌트 라이브러리를 그대로 씀"이 아니라 "MD3에서 영감을 받은 MUI 테마"다. 라이트/다크는prefers-color-scheme를useMediaQuery로 읽어 자동 전환한다(수동 토글 없음, 기존 방식 유지). - 로그인은 프론트엔드 목업이다. Collector에 인증 인프라가 전혀 없어(Spring Security, 사용자 저장소, 로그인 엔드포인트 없음),
localStorage에 사용자명만 저장하고 비밀번호는 검증하지 않는다. 실제 백엔드 인증은 범위 밖이며.claude/decisions.md에 Open Issue로 기록되어 있다.
패키지 구조 (console/src)
console/src
├── main.tsx, App.tsx 엔트리 + 라우팅 셸 (react-router-dom, ThemeProvider)
├── theme.ts MD3 스타일 MUI 테마 (팔레트/shape/typography)
├── App.css, index.css 현재 비어있음 - MUI ThemeProvider/CssBaseline이 대체
├── format.ts 순수 표시 포맷팅 헬퍼 (timestamp/percent/duration)
├── auth/
│ ├── AuthContext.tsx localStorage 기반 목업 로그인/로그아웃 상태
│ └── ProtectedRoute.tsx 미인증 시 /login으로 리다이렉트하는 라우트 가드
├── layout/
│ ├── AppLayout.tsx 다크 사이드바 + full-width 콘텐츠 영역 + <Outlet/>
│ └── navItems.ts 사이드바 메뉴 항목 정의 (label/path/icon/comingSoon)
├── api/
│ ├── client.ts fetch 래퍼 + 엔드포인트별 함수
│ └── types.ts Collector 응답을 그대로 미러링하는 TS 인터페이스
└── pages/
├── LoginPage.tsx 목업 로그인 화면
├── OverviewPage.tsx 홈 - 서비스/최근 Trace 요약 (기존 API 재사용)
├── ServiceListPage.tsx GET /api/v1/services
├── ServiceSummaryPage.tsx GET /api/v1/services/{name}/summary, /uris
├── TraceListPage.tsx GET /api/v1/traces?serviceName=&page=&size=
├── TraceDetailPage.tsx GET /api/v1/traces/{traceId}
├── AgentsPage.tsx GET /api/v1/agents + PATCH /api/v1/agents/{agentId}
└── LogsPage.tsx GET /api/v1/logs (서비스/레벨 필터, 검색)페이지 수가 적어 제네릭 DataTable 같은 재사용 컴포넌트는 만들지 않고, 페이지마다 MUI Table을 직접 구성했다 (과설계 방지). 데이터 fetching 로직(useEffect/useState + api/client.ts)은 MUI 도입 전후로 변경되지 않았다 - 이번 변경은 마크업/스타일 계층에 한정된다.
내비게이션 구조 (사이드바)
Sentry/Pinpoint류 APM 대시보드를 참고한 IA. 순서대로: Overview(/, 홈) → Services(/services, 서비스 목록 - /services/:serviceName은 사이드바 항목이 아니라 목록에서 이동) → Traces(/traces, /traces/:traceId도 동일) → Agents(/agents) → Logs(/logs) → 계정 관리(/account/users). Agents/Logs는 Collector API가 추가되어 실제 화면으로 구현되었다.
화면 구성
- 로그인 (
/login): 사용자명/비밀번호 입력 폼. 비밀번호는 검증하지 않는 목업이며, 제출하면 즉시 로그인 처리된다. - Overview (
/): 서비스 수 + 목록, 최근 Trace 5건을 카드 형태로 보여주는 홈 화면. 기존listServices()/listTraces()API를 그대로 재사용하며 새 엔드포인트는 없다. - 서비스 목록 (
/services): 서비스명 링크 목록. - 서비스 요약 (
/services/:serviceName): 요청 수/평균 응답시간/에러 수/에러율 카드 + URI별 통계 표. Trace 목록으로 이동하는 링크 포함. - Trace 목록 (
/traces?serviceName=): 페이지네이션(이전/다음, 20건씩) 목록.serviceName쿼리 파라미터가 없으면 전체 서비스의 Trace를 최신순으로 보여준다. Collector API가 전체 건수를 반환하지 않아 MUIPagination(총 페이지 수 필요) 대신 이전/다음 버튼을 그대로 유지했다. - Trace 상세 (
/traces/:traceId): 기본 필드 + 요청 상세(Query String, 요청/응답 Content-Type·Content-Length) + 요청/응답 헤더(key-value, 값이 있을 때만 표시 - 헤더 수집은 Agent 쪽에서 옵트인이라 없을 수 있음) +@ApmxTraceSpan 목록. - Agents (
/agents):GET /api/v1/agents로 등록된 Agent를 그룹별로 묶어 표시한다. 활성/비활성 상태(heartbeat 90초 기준), 서비스/호스트/PID/JVM/Agent 버전/시작 시간/마지막 수신 시각을 보여주고, 행의 편집 버튼으로 표시 이름과 그룹을 변경한다 (PATCH /api/v1/agents/{agentId}). 표시 이름을 비우면 Agent가 실행 시 보고한 이름으로 되돌아간다. - Logs (
/logs):GET /api/v1/logs로 수집된 로그를 최신순으로 표시한다. 서비스 드롭다운(GET /api/v1/logs/services), 레벨 필터, 메시지/호스트 클라이언트 검색, 새로고침 버튼, 이전/다음 페이지네이션(100건씩)을 제공한다.
CORS
Collector가 apmx.console.allowed-origin 프로퍼티(기본값 http://localhost:5173)로 설정된 origin에 대해 /api/** 경로의 GET/POST/PUT/PATCH/DELETE를 허용한다 (io.apmx.collector.config.WebConfig). PATCH는 Console의 Agent 이름/그룹 변경(PATCH /api/v1/agents/{agentId})을 위해 추가되었다.
예시
로컬 실행
cd console
npm install
cp .env.example .env # 필요 시 VITE_API_BASE_URL 조정
npm run devCollector가 http://localhost:8080에서 실행 중이어야 하며, 기본 설정으로는 별도 CORS 설정 없이 바로 동작한다 (Vite 기본 포트가 apmx.console.allowed-origin 기본값과 일치).
Cloudflare Pages 배포 (docs와 하나의 프로젝트로 통합)
커스텀 도메인 없이 Cloudflare 기본 *.pages.dev 도메인만 쓰기로 했다(.claude/decisions.md 2026-07-09 참고). Pages는 프로젝트 1개당 도메인이 하나씩 자동 발급되는 구조라, docs와 Console을 각각 별도 프로젝트로 만들면 서로 다른 무작위 *.pages.dev 도메인을 갖게 된다. 대신 Cloudflare Pages 프로젝트를 1개만 두고, docs는 /docs/, Console은 /console/ 경로로 같은 도메인 아래 배포한다.
루트의 scripts/build-site.sh(= npm run build:site)가 docs/(VitePress)와 console/(Vite)을 각각 빌드한 뒤 site/docs/, site/console/로 합치고, web-root/index.html(두 링크로 이동하는 랜딩 페이지)과 web-root/_redirects (Console SPA fallback 규칙)를 site/에 함께 복사한다. Cloudflare Pages 프로젝트를 새로 연결할 때 다음 값을 지정한다(Root directory는 저장소 루트, 즉 console이 아님).
| 설정 | 값 |
|---|---|
| Root directory | / (저장소 루트) |
| Build command | npm run build:site |
| Build output directory | site |
| 환경 변수 | VITE_API_BASE_URL = 배포된 Collector의 base URL (예: https://api.apmx.example.com) |
- Console은
<project>.pages.dev/console/, docs는<project>.pages.dev/docs/, 루트(/)는 둘로 가는 링크만 있는 랜딩 페이지다. console/vite.config.ts의base,docs/.vitepress/config.mjs의base는 빌드 결과물에만/console/,/docs/를 붙이도록 조건부로 되어 있다 - 로컬npm run dev/npm run docs:dev는 기존처럼 루트(http://localhost:5173)에서 그대로 동작한다 (아래 "로컬 실행" 참고).react-router-dom의BrowserRouter에 프로덕션 빌드에서만basename="/console"을 준다(App.tsx,import.meta.env.PROD로 분기) - 로컬 개발에는 영향 없음.- SPA fallback(
/console/*가 존재하지 않는 경로여도index.html을 200으로 응답)은console/public/_redirects가 아니라 병합된site/루트의_redirects(web-root/_redirects에서 복사됨) 로만 처리한다 - Cloudflare Pages는_redirects를 배포 결과물의 최상위 경로에서만 읽고,site/console/_redirects처럼 하위 폴더에 있으면 무시하기 때문이다. docs(VitePress)는 라우트별로 정적 HTML을 생성하므로 fallback이 따로 필요 없다. VITE_API_BASE_URL은 빌드 시점에 번들에 굳어지는 값이다(Vite의import.meta.env). Collector 주소가 바뀌면 Pages에서 환경 변수를 바꾼 뒤 재배포해야 한다.- Collector 쪽
apmx.console.allowed-origin을 배포된 Console의 실제 origin (https://<project>.pages.dev-/console없이 origin만)으로 맞춰줘야 CORS가 통과한다. - Pages 프로젝트의 Production 브랜치를 지정해 두면, 그 브랜치에 push될 때마다 자동으로 새 빌드가 트리거된다(Cloudflare Pages의 GitHub 연동 기본 동작 - 수동 배포 명령이 필요 없다). 다른 브랜치/PR은 자동으로 Preview 배포(
<hash>.<project>.pages.dev)가 생기며, 이때도 같은build:site가 그대로 실행되어 docs+console이 함께 미리보기된다.
TODO
- 실제 백엔드 인증(Spring Security, 사용자 저장소, 로그인 API) - 현재 로그인은 프론트엔드 목업이다 (
.claude/decisions.md참고) - Agents 화면에 에이전트(그룹)별 트래픽 차트/응답시간 시각화 추가 (현재는 목록/관리만 구현)
- Logs 화면 자동 새로고침(폴링) 및 시간 범위 필터
- 프론트엔드 테스트(Vitest/React Testing Library) 도입 검토 - 현재 저장소에 프론트엔드 테스트 도구가 없어 이번 범위에서는 생략했다
- Span 목록을 waterfall/트리 형태로 시각화하는 방안 검토
- 시간 범위 필터(
from/to) -docs/07-api-design.mdTODO와 함께 처리 console/README.md가 Vite 스캐폴딩 시 생성된 제네릭 템플릿 문구 그대로 남아있다 - 프로젝트 설명(실행 방법 등, 본 문서 및 루트README.md와 중복되지 않는 선에서)으로 교체 필요- 빈
App.css/index.css파일 정리 (MUI 도입으로 내용이 필요 없어졌으나, 파일 삭제 자체는 사용자 확인 후 진행하기로 함)