Skip to content

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류의 모듈 구성(다크 사이드바, 카드, 통계 타일)은 유지하되, 콘텐츠 영역은 최대 폭 제한 없이 화면 전체를 사용하고(AppLayout full-width), 카드/테이블 패딩과 행 높이를 줄여 같은 화면에 더 많은 데이터가 보이도록 했다 (theme.tsMuiTableCell/MuiCardContent override, DataGrid rowHeight 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-schemeuseMediaQuery로 읽어 자동 전환한다(수동 토글 없음, 기존 방식 유지).
  • 로그인은 프론트엔드 목업이다. Collector에 인증 인프라가 전혀 없어(Spring Security, 사용자 저장소, 로그인 엔드포인트 없음), localStorage에 사용자명만 저장하고 비밀번호는 검증하지 않는다. 실제 백엔드 인증은 범위 밖이며 .claude/decisions.md에 Open Issue로 기록되어 있다.

패키지 구조 (console/src)

text
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가 추가되어 실제 화면으로 구현되었다.

화면 구성

  1. 로그인 (/login): 사용자명/비밀번호 입력 폼. 비밀번호는 검증하지 않는 목업이며, 제출하면 즉시 로그인 처리된다.
  2. Overview (/): 서비스 수 + 목록, 최근 Trace 5건을 카드 형태로 보여주는 홈 화면. 기존 listServices()/listTraces() API를 그대로 재사용하며 새 엔드포인트는 없다.
  3. 서비스 목록 (/services): 서비스명 링크 목록.
  4. 서비스 요약 (/services/:serviceName): 요청 수/평균 응답시간/에러 수/에러율 카드 + URI별 통계 표. Trace 목록으로 이동하는 링크 포함.
  5. Trace 목록 (/traces?serviceName=): 페이지네이션(이전/다음, 20건씩) 목록. serviceName 쿼리 파라미터가 없으면 전체 서비스의 Trace를 최신순으로 보여준다. Collector API가 전체 건수를 반환하지 않아 MUI Pagination(총 페이지 수 필요) 대신 이전/다음 버튼을 그대로 유지했다.
  6. Trace 상세 (/traces/:traceId): 기본 필드 + 요청 상세(Query String, 요청/응답 Content-Type·Content-Length) + 요청/응답 헤더(key-value, 값이 있을 때만 표시 - 헤더 수집은 Agent 쪽에서 옵트인이라 없을 수 있음) + @ApmxTrace Span 목록.
  7. Agents (/agents): GET /api/v1/agents로 등록된 Agent를 그룹별로 묶어 표시한다. 활성/비활성 상태(heartbeat 90초 기준), 서비스/호스트/PID/JVM/Agent 버전/시작 시간/마지막 수신 시각을 보여주고, 행의 편집 버튼으로 표시 이름과 그룹을 변경한다 (PATCH /api/v1/agents/{agentId}). 표시 이름을 비우면 Agent가 실행 시 보고한 이름으로 되돌아간다.
  8. 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})을 위해 추가되었다.

예시

로컬 실행

bash
cd console
npm install
cp .env.example .env   # 필요 시 VITE_API_BASE_URL 조정
npm run dev

Collector가 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 commandnpm run build:site
Build output directorysite
환경 변수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.tsbase, docs/.vitepress/config.mjsbase빌드 결과물에만 /console/, /docs/를 붙이도록 조건부로 되어 있다 - 로컬 npm run dev/ npm run docs:dev는 기존처럼 루트(http://localhost:5173)에서 그대로 동작한다 (아래 "로컬 실행" 참고).
  • react-router-domBrowserRouter에 프로덕션 빌드에서만 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.md TODO와 함께 처리
  • console/README.md가 Vite 스캐폴딩 시 생성된 제네릭 템플릿 문구 그대로 남아있다 - 프로젝트 설명(실행 방법 등, 본 문서 및 루트 README.md와 중복되지 않는 선에서)으로 교체 필요
  • App.css/index.css 파일 정리 (MUI 도입으로 내용이 필요 없어졌으나, 파일 삭제 자체는 사용자 확인 후 진행하기로 함)