oneshim-web
로컬 웹 대시보드 — Axum REST API + React 프론트엔드
크레이트 유형
라이브러리 크레이트 (lib) — oneshim-app에 통합되어 실행됨
📋 개요
oneshim-web은 데스크톱 에이전트의 로컬 웹 대시보드를 제공합니다. Rust 백엔드(Axum)와 React 프론트엔드를 결합하여 브라우저에서 에이전트 상태를 모니터링하고 설정을 관리할 수 있습니다.
주요 특징
| 기능 | 설명 |
|---|---|
| REST API | 60개+ 엔드포인트 (메트릭, 프레임, 이벤트, 태그, 검색, 리포트, 자동화 등) |
| 실시간 SSE | Server-Sent Events로 실시간 메트릭 스트리밍 |
| React 프론트엔드 | React 18 + Vite + Tailwind CSS (9페이지) |
| 정적 파일 임베드 | rust-embed로 빌드 결과 바이너리에 포함 |
| 다국어 지원 | 한국어/영어 자동 감지 (i18n, 220개+ 키) |
| 자동화 대시보드 | 자동화 상태, 감사 로그, 워크플로우 프리셋, 실행 통계 |
| E2E 테스트 | Playwright 기반 72개 테스트 |
🏗️ 아키텍처
📂 디렉토리 구조
crates/oneshim-web/
├── src/
│ ├── lib.rs # WebServer + AppState (audit_logger 포함)
│ ├── routes.rs # 라우트 정의 (60개+ 엔드포인트)
│ ├── embedded.rs # 정적 파일 서빙 + SPA 라우팅
│ ├── error.rs # ApiError 정의
│ └── handlers/ # REST API 핸들러
│ ├── metrics.rs # 시스템 메트릭
│ ├── processes.rs # 프로세스 정보
│ ├── idle.rs # 유휴 상태
│ ├── sessions.rs # 세션 통계
│ ├── frames.rs # 스크린샷 프레임
│ ├── events.rs # 이벤트 로그
│ ├── stats.rs # 통계 (히트맵 포함)
│ ├── tags.rs # 태그 관리
│ ├── search.rs # 통합 검색
│ ├── reports.rs # 리포트 생성
│ ├── timeline.rs # 통합 타임라인
│ ├── focus.rs # 집중도 분석
│ ├── backup.rs # 백업/복원
│ ├── export.rs # 데이터 내보내기
│ ├── settings.rs # 설정 조회/변경 (자동화/샌드박스/AI 포함)
│ ├── automation.rs # 자동화 API (10개 엔드포인트)
│ └── stream.rs # SSE 실시간 스트림
└── frontend/
├── src/
│ ├── App.tsx # 라우팅 + 레이아웃
│ ├── pages/ # 페이지 컴포넌트 (9개)
│ ├── components/ # 공통 컴포넌트
│ ├── components/ui/ # 디자인 시스템 컴포넌트
│ ├── styles/ # 디자인 토큰 + 변형
│ ├── lib/ # 유틸리티
│ ├── i18n/ # 다국어 번역 (220개+ 키)
│ └── api.ts # API 클라이언트
├── e2e/ # Playwright E2E 테스트
├── playwright.config.ts
└── package.json
🔌 AppState
#[derive(Clone)]
pub struct AppState {
pub storage: Arc<SqliteStorage>,
pub frames_dir: Option<PathBuf>,
pub event_tx: broadcast::Sender<RealtimeEvent>,
pub config_manager: Option<ConfigManager>,
pub audit_logger: Option<Arc<RwLock<AuditLogger>>>,
}
WebServer 빌더
let server = WebServer::new(storage, web_config)
.with_config_manager(config_manager)
.with_audit_logger(audit_logger)
.with_event_tx(event_tx)
.with_frames_dir(frames_dir);
server.run(shutdown_rx).await?;
🌐 REST API 엔드포인트
메트릭 & 모니터링
| 메서드 | 경로 | 설명 |
|---|---|---|
| GET | /api/metrics | 시스템 메트릭 조회 |
| GET | /api/metrics/history | 메트릭 히스토리 |
| GET | /api/processes | 프로세스 목록 |
| GET | /api/idle | 유휴 상태 조회 |
| GET | /api/sessions | 세션 통계 |
| GET | /api/stats/heatmap | 활동 히트맵 |
| GET | /api/stream | SSE 실시간 스트림 |
프레임 & 이벤트
| 메서드 | 경로 | 설명 |
|---|---|---|
| GET | /api/frames | 프레임 목록 (페이지네이션) |
| GET | /api/frames/:id | 프레임 상세 |
| GET | /api/frames/:id/image | 프레임 이미지 |
| GET | /api/events | 이벤트 목록 (페이지네이션) |
태그
| 메서드 | 경로 | 설명 |
|---|---|---|
| GET | /api/tags | 모든 태그 조회 |
| POST | /api/tags | 태그 생성 |
| PUT | /api/tags/:id | 태그 수정 |
| DELETE | /api/tags/:id | 태그 삭제 |
| POST | /api/frames/:id/tags/:tag_id | 프레임에 태그 추가 |
| DELETE | /api/frames/:id/tags/:tag_id | 프레임에서 태그 제거 |
검색 & 리포트
| 메서드 | 경로 | 설명 |
|---|---|---|
| GET | /api/search | 통합 검색 (텍스트 + 태그) |
| GET | /api/reports | 활동 리포트 생성 |
| GET | /api/timeline | 통합 타임라인 |
집중도 분석
| 메서드 | 경로 | 설명 |
|---|---|---|
| GET | /api/focus/metrics | 집중도 메트릭 |
| GET | /api/focus/sessions | 작업 세션 |
| GET | /api/focus/interruptions | 중단 이벤트 |
| GET | /api/focus/suggestions | 로컬 제안 |
| POST | /api/focus/suggestions/:id/feedback | 제안 피드백 |
설정 & 데이터 관리
| 메서드 | 경로 | 설명 |
|---|---|---|
| GET | /api/settings | 설정 조회 (자동화/샌드박스/AI 포함) |
| POST | /api/settings | 설정 변경 |
| GET | /api/backup | 백업 다운로드 |
| POST | /api/backup/restore | 백업 복원 |
| GET | /api/export/:type | 데이터 내보내기 (JSON/CSV) |
자동화 (Automation)
| 메서드 | 경로 | 설명 |
|---|---|---|
| GET | /api/automation/status | 자동화 시스템 상태 |
| GET | /api/automation/audit | 감사 로그 조회 (limit, status 필터) |
| GET | /api/automation/policies | 활성 정책 요약 |
| GET | /api/automation/stats | 실행 통계 (성공/실패/거부/타임아웃) |
| GET | /api/automation/presets | 프리셋 목록 (내장 + 사용자) |
| POST | /api/automation/presets | 사용자 프리셋 생성 |
| PUT | /api/automation/presets/:id | 사용자 프리셋 수정 |
| DELETE | /api/automation/presets/:id | 사용자 프리셋 삭제 |
| POST | /api/automation/presets/:id/run | 프리셋 실행 |
📊 자동화 API DTO
/// 자동화 시스템 상태
pub struct AutomationStatusDto {
pub enabled: bool,
pub sandbox_enabled: bool,
pub sandbox_profile: String,
pub ocr_provider: String,
pub llm_provider: String,
pub external_data_policy: String,
pub pending_audit_entries: usize,
}
/// 감사 로그 엔트리
pub struct AuditEntryDto {
pub entry_id: String,
pub timestamp: String,
pub session_id: String,
pub command_id: String,
pub action_type: String,
pub status: String, // Started | Completed | Failed | Denied | Timeout
pub details: Option<String>,
pub elapsed_ms: Option<u64>,
}
/// 실행 통계
pub struct AutomationStatsDto {
pub total_executions: usize,
pub successful: usize,
pub failed: usize,
pub denied: usize,
pub timeout: usize,
pub avg_elapsed_ms: f64,
}
⚙️ Settings DTO (자동화 관련)
AppSettings에 3개 자동화 섹션 추가:
pub struct AppSettings {
// ... 기존 모니터/비전/알림/프라이버시 설정 ...
pub automation: AutomationSettings,
pub sandbox: SandboxSettings,
pub ai_provider: AiProviderSettings,
}
pub struct AutomationSettings { pub enabled: bool }
pub struct SandboxSettings {
pub enabled: bool,
pub profile: String, // "Permissive" | "Standard" | "Strict"
pub allowed_read_paths: Vec<String>,
pub allowed_write_paths: Vec<String>,
pub allow_network: bool,
pub max_memory_bytes: u64,
pub max_cpu_time_ms: u64,
}
pub struct AiProviderSettings {
pub ocr_provider: String, // "Local" | "Remote"
pub llm_provider: String, // "Local" | "Remote"
pub external_data_policy: String, // "PiiFilterStrict" | "PiiFilterStandard" | "AllowFiltered"
pub fallback_to_local: bool,
pub ocr_api: Option<ExternalApiSettings>,
pub llm_api: Option<ExternalApiSettings>,
}
pub struct ExternalApiSettings {
pub endpoint: String,
pub api_key_masked: String, // GET: 마스킹 / POST: 전체 키
pub model: Option<String>,
pub timeout_secs: u64,
}
API 키 마스킹
- GET:
mask_api_key("sk-1234567890abcdef")→"sk...cdef"(앞 2자 +...+ 뒤 4자) - POST: 전체 키 수신 시 저장, 마스킹된 값(
is_masked_key())이면 기존 키 유지
🎨 프론트엔드 페이지
| 페이지 | 경로 | 단축키 | 기능 |
|---|---|---|---|
| Dashboard | / | D | 실시간 메트릭, CPU/Memory 차트, 앱 사용량, 히트맵, 집중도 |
| Timeline | /timeline | T | 스크린샷 그리드/리스트, 필터링, 태그, 라이트박스 |
| Search | /search | — | 통합 검색, 태그 필터, 결과 하이라이팅 |
| Reports | /reports | R | 주간/월간 리포트, 생산성 점수, 차트 |
| Session Replay | /replay | — | 세션 리플레이 |
| Focus Analytics | /focus | — | 집중도 분석 |
| Automation | /automation | A | 자동화 대시보드 |
| Settings | /settings | S | 설정 (자동화/샌드박스/AI 포함) |
| Privacy | /privacy | P | 개인정보 관리 |
Automation 페이지
5개 패널로 구성 (React Query 기반):
- 상태 카드 — 활성화 여부, 샌드박스 프로필, OCR/LLM 제공자, 대기 감사 항목
- 워크플로우 프리셋 — 카테고리별 탭 (생산성/앱 관리/워크플로우/사용자), 프리셋 카드 그리드, 실행/CRUD
- 실행 통계 — 성공/실패/거부/타임아웃 카운트 + 평균 소요 시간
- 감사 로그 — 테이블 (시각, 명령ID, 액션, 상태 배지, 소요시간), 상태별 필터, 30초 자동 새로고침
- 정책 정보 — 현재 적용된 정책 요약
Settings 페이지 (자동화 섹션)
기존 설정에 3개 섹션 추가:
- 자동화 — 활성화 토글
- 샌드박스 — 활성화, 프로필 드롭다운, 네트워크 허용 토글
- AI 제공자 — OCR/LLM 타입 선택, 데이터 정책, 폴백 토글, 외부 API 설정 (
type="password")
🎯 디자인 시스템
코드 기반 디자인 시스템으로 TypeScript 타입 안전성을 통해 일관성을 보장합니다.
디자인 토큰 (styles/tokens.ts)
export const colors = {
primary: {
DEFAULT: 'bg-teal-600 dark:bg-teal-500',
hover: 'hover:bg-teal-700 dark:hover:bg-teal-400',
text: 'text-teal-600 dark:text-teal-400',
},
surface: { bg: '...', elevated: '...', border: '...' },
text: { primary: '...', secondary: '...', tertiary: '...' },
semantic: { success: '...', warning: '...', error: '...', info: '...' },
status: { online: '...', offline: '...', idle: '...' },
}
UI 컴포넌트 (components/ui/)
| 컴포넌트 | Props | 설명 |
|---|---|---|
Button | variant, size, isLoading | 버튼 (primary/secondary/ghost/danger) |
Card | variant, padding | 카드 (default/elevated/highlight/interactive) |
Input | variant, inputSize, error | 입력 필드 |
Badge | color, size | 배지 (7가지 색상) |
Select | variant, selectSize | 드롭다운 |
Spinner | size | 로딩 스피너 |
🌍 다국어 지원 (i18n)
react-i18next 기반으로 한국어/영어 자동 감지를 지원합니다.
번역 파일
i18n/locales/ko.json— 한국어 (220+ 키)i18n/locales/en.json— 영어 (220+ 키)
주요 번역 영역
automation.*— 자동화 UI 번역 (40개+)settingsAutomation.*— 자동화 설정 번역 (26개+)- 기존 번역 유지 (dashboard, timeline, settings, privacy, search, reports 등)
🧪 테스트
Rust 테스트 (78개)
cargo test -p oneshim-web
- API 핸들러 테스트
- 라우트 테스트
- 에러 핸들링 테스트
- 자동화 DTO 직렬화
- 설정 매핑 (자동화/샌드박스/AI)
E2E 테스트 (72개)
cd crates/oneshim-web/frontend
pnpm test:e2e # 전체 실행
pnpm test:e2e:headed # 브라우저 표시
pnpm test:e2e:ui # Playwright UI 모드
| 파일 | 테스트 수 | 검증 영역 |
|---|---|---|
navigation.spec.ts | 9 | 네비게이션, 키보드 단축키 |
dashboard.spec.ts | 8 | 메트릭 카드, 차트, 연결 상태 |
timeline.spec.ts | 8 | 필터링, 뷰 모드, 키보드 |
settings.spec.ts | 13 | 설정 폼, 저장, 내보내기 |
privacy.spec.ts | 13 | 데이터 삭제, 백업/복원 |
search.spec.ts | 10 | 검색 폼, 태그 필터 |
reports.spec.ts | 11 | 기간 선택, 차트, 통계 |
⚙️ 설정
WebConfig
oneshim-core의 AppConfig에 포함된 웹 서버 설정:
[web]
enabled = true
port = 9090
allow_external = false
| 옵션 | 설명 | 기본값 |
|---|---|---|
enabled | 웹 서버 활성화 | true |
port | 포트 번호 | 9090 |
allow_external | 외부 접속 허용 | false |
자동 포트 찾기
포트 충돌 시 다음 포트를 자동으로 시도합니다 (최대 10개).
9090 → 9091 → 9092 → ... → 9099
🔧 빌드
프론트엔드 빌드
cd crates/oneshim-web/frontend
pnpm install
pnpm build
전체 빌드 (프론트엔드 임베드)
# 프론트엔드 빌드 후 Rust 빌드
./scripts/build-frontend.sh
cargo build --release -p oneshim-app
rust-embed가 frontend/dist/ 디렉토리를 바이너리에 포함합니다.
📦 의존성
Rust
| 크레이트 | 버전 | 용도 |
|---|---|---|
| axum | 0.7 | HTTP 서버 |
| tower-http | 0.6 | CORS, 로깅 |
| rust-embed | 8 | 정적 파일 임베드 |
| mime_guess | 2 | Content-Type 감지 |
| tokio | 1 | 비동기 런타임 |
Frontend
| 패키지 | 버전 | 용도 |
|---|---|---|
| react | 18 | UI 라이브러리 |
| vite | 6 | 번들러 |
| tailwindcss | 3 | CSS 프레임워크 |
| recharts | 2 | 차트 |
| i18next | 24 | 다국어 |
| @playwright/test | 1 | E2E 테스트 |
| clsx + tailwind-merge | - | 클래스 병합 |
관련 문서:
- oneshim-storage — SQLite 저장소
- oneshim-core — 설정 및 모델
- oneshim-automation — 자동화 제어
- oneshim-app — 앱 통합