콘솔 UX 표준
본 문서는 콘솔의 모든 화면에서 준수해야 하는 UX 표준과 이를 효율적으로 구현할 수 있는 컴포넌트 구성을 다룹니다. 대부분의 항목은 test/e2e/ux 테스트를 통해 자동으로 검증됩니다 (검증 대상: 페르소나 8개, 뷰포트 4개, 언어 2개, GPU가 없는 제어 플레인 환경).
모든 화면
| 보장 항목 | 구현 방법 | 자동 검사 항목 |
|---|---|---|
| 브라우저 탭 이름에 화면명 표시 | <PageHeader title>이 useDocumentTitle 호출 | page.title.notPerScreen |
문서 구조 내 단일 <h1> 태그 유지 | <PageHeader> 사용 | page.heading.noH1 |
<main>, <nav>, <header> 랜드마크 및 건너뛰기 링크 제공 | Layout 컴포넌트 적용 | page.landmark.*, page.skipLink.missing |
<html lang> 속성이 선택된 언어와 일치 | frontend/src/i18n/index.ts | i18n.htmlLangMismatch |
| 모든 대화형 컨트롤에 시각적 포커스 링 제공 | frontend/src/styles/index.css의 :focus-visible | keyboard.noFocusRing |
| 텍스트와 배경 간 명암비가 WCAG AA 기준 충족 | 테마 토큰 적용 | contrast.belowWcagAA |
| 최소 뷰포트(390px) 환경에서 가로 스크롤 미발생 | .gs-shell, hideOnMobile 열 활용 | responsive.horizontalScroll |
| 모바일 터치 영역 ≥ 44px, 텍스트 크기 ≥ 12px 유지 | frontend/src/styles/index.css 하단 모바일 오버라이드 | responsive.tapTargetTooSmall |
prefers-reduced-motion 설정을 준수하는 애니메이션 적용 | frontend/src/styles/index.css | — |
최상위 하위 화면
- 브레드크럼(Breadcrumb) 및 이전 페이지 이동 경로 제공:
<PageHeader crumbs={[...]}>및<BackLink>를 함께 구성합니다. 네비게이션 경로가 명시되지 않으면 사용자는 브라우저 뒤로 가기 버튼에만 의존하게 되며, 이 과정에서 작성 중이던 폼 데이터가 유실될 수 있습니다. - 제목에 구체적인 대상 이름 명시: 단순 화면 종류뿐만 아니라 식별 가능한 대상 명칭을 함께 표기합니다. (예: "부서 편집 — NLP팀")
목록 (Table)
관련 기능은 frontend/src/components/Table.tsx 및 frontend/src/hooks/useTableState.ts에 구현되어 있으므로, 각 화면에서는 sort, dir, onSort 속성을 전달하여 적용합니다.
- 정렬 가능한 열: 렌더링된 DOM 노드가 아닌 실제 데이터 원본 값을 기준으로 정렬합니다. 정렬 순서가 불필요한 열은
sortable: false로 설정합니다. - 검색, 실시간 일치 항목 수 표시 및 일괄 초기화:
<TableToolbar>컴포넌트를 활용합니다. - URL 기반 상태 관리: 검색어, 정렬 기준, 페이지 번호, 탭 상태 등을 쿼리 파라미터로 유지하여 북마크 생성 및 링크 공유가 가능하도록 하고, 상세 행 이동 후 뒤로 가기 시 기존 상태를 복원합니다.
- 두 가지 빈 상태(Empty State) 구분:
- 데이터가 존재하지 않는 경우: 첫 번째 항목 생성 버튼과 함께
<EmptyState>표시 - 필터링 결과가 없는 경우: 필터 조건 초기화 기능과 함께
<NoResults>표시 - (참고: 별도 안내가 없는 빈 테이블은 시스템 오류로 오인될 수 있습니다.)
- 데이터가 존재하지 않는 경우: 첫 번째 항목 생성 버튼과 함께
- 로딩 스켈레톤 UI: 데이터 로딩 시 빈 패널이나 단독 스피너 대신
<TableSkeleton>을 사용합니다. - 고정 헤더 및 페이지네이션: 15행 초과 시 **고정 헤더(Sticky Header)**를 적용하고, 25행 초과 시 페이지네이션을 적용합니다.
- 다중 선택(행 선택): 일괄 처리가 가능한 액션이 존재하는 테이블에는 행 선택(Checkbox) 기능을 제공합니다.
- 접근성을 위한 테이블 캡션: 단순 "table"이 아닌 테이블의 목적과 내용을 명확히 설명하는 캡션을 제공합니다.
폼 (Form)
- 명시적 라벨 사용:
<Field>컴포넌트가<label for>, 필수 항목 표시, 힌트, 오류 메시지를 입력 컨트롤에 올바르게 연결하고aria-describedby,aria-invalid,aria-required속성을 설정합니다. (플레이스홀더는 입력 시 사라지므로 라벨 대용으로 사용할 수 없습니다.) <form>태그 활용: 폼을<form>요소로 감싸 Enter 키로 제출할 수 있도록 합니다. 이는 키보드 접근성을 높이는 가장 기본적인 방법입니다.- Blur 시점의 유효성 검증: 제출 시점이 아닌 Blur(포커스 해제) 시점에 입력값을 검증합니다. 형식에 맞지 않는 이메일이나 잘못된 수량은 입력 직후 명시적으로 안내합니다.
- 적절한 Input 속성 지정: 가능한 모든 필드에
autocomplete,inputmode,min,max,step속성을 선언합니다. 이를 통해 비밀번호 관리자 연동을 지원하고 모바일 기기에서 최적화된 키보드 레이아웃을 제공합니다. - 비활성화된 주 버튼의 사유 안내: 주 버튼이 비활성화된 경우
<DisabledReason>을 통해 사유를 안내합니다. 설명 없이 비활성화된 버튼은 사용자가 원인을 파악하기 어렵게 만듭니다. - 저장되지 않은 변경 사항 보호:
useUnsavedGuard를 적용하여 앱 내 이동 및 탭 닫기 시 작업 내용 유실을 방지합니다.
숫자 입력
모든 숫자 입력 필드는 min, max, step, inputmode 속성을 명시적으로 선언하고, 포커스를 벗어날 때(Blur) 입력값을 유효 범위 내로 보정(Clamp) 처리합니다. HTML 속성은 브라우저 기본 스피너 및 검증을 제어하지만 직접 타이핑하거나 붙여넣는 유효 범위 밖의 입력을 완벽히 차단하지는 못합니다. 입력값 보정 처리가 누락되면 음수 수량이나 잘못된 자릿수의 값이 API 요청으로 전달되어 422(Unprocessable Entity) 에러가 발생할 수 있습니다.
파괴적 작업 (Destructive Actions)
window.confirm 대신 useConfirm()을 사용합니다. 기본 브라우저 대화 상자는 손실 대상 정보를 명확히 보여줄 수 없고, i18n 번역이 불가능하며, 브라우저 탭 전체를 차단하는 문제가 있습니다.
- 명확한 대상 이름 명시: 모호한 질문("정말 진행하시겠습니까?") 대신 구체적인 대상을 명시합니다. (예: "
vit-base-ft를 종료할까요?") - 작업 결과 명시: 정산될 크레딧, 삭제되는 볼륨 데이터, 고아 상태가 되는 세션 등 수행 결과의 영향을 명확히 안내합니다.
- 불가역적 작업 시 텍스트 입력 검증: 조직·클러스터·공유 데이터셋 삭제, 다수의 세션 동시 종료 등 복구가 불가능한 작업은 대상의 이름을 직접 입력하도록 요구합니다.
- 포커스 트랩 및 키보드 제어: ESC 키 및 오버레이 클릭은 취소로 처리합니다. 대화 상자가 열려 있는 동안 포커스는 모달 내부에 격리(Focus Trap)되며, 닫힐 때 대화 상자를 호출했던 컨트롤로 포커스가 복원됩니다.
- 복구 가능한 작업에 대한 실행 취소(Undo) 지원: 예컨대 접근 권한 해제 등의 작업은 즉시 적용 후 실행 취소 버튼이 포함된 토스트 알림을 제공합니다 (
pushToast(kind, message, { label, run })). 잦은 작업에서 불필요한 확인 모달을 줄일 수 있습니다. 명시적 텍스트 입력 확인은 되돌릴 수 없는 작업에만 제한적으로 적용합니다.
서버 오류 처리
요청 거절 및 저장 실패 오류는 토스트 알림뿐만 아니라 폼 상단에도 명시적으로 표시합니다. 5초 후 자동으로 사라지거나 원인 필드를 알 수 없는 메시지는 적절한 오류 안내가 아닙니다. 사용자가 다음 시도를 할 때까지 실패한 액션 요소 근처에 오류 메시지를 유지해야 합니다.
실시간 데이터
- 최종 조회 시각 표시 및 수동 새로고침 제공:
<PageHeader updatedAt onRefresh>를 구성합니다. 최종 조회 시각이 없으면 데이터가 오래된 것인지 작업이 정지된 것인지 구분할 수 없습니다. - 상대 시간 표시 및 호버 시 절대 시간 제공:
<Timestamp>컴포넌트를 사용합니다. "6시간 전"과 같은 상대 시간을 기본 표기하고,datetime속성 및 툴팁을 통해 정확한 절대 시간을 로그와 대조할 수 있도록 합니다. 상대 시간 텍스트는 타이머를 통해 주기적으로 재렌더링하여 장시간 열려 있는 탭에서도 정확한 시간을 유지합니다.
식별자 및 복사 기능
세션, 클러스터, 디바이스, 지갑 등의 식별자(ID)는 지원 티켓 작성, kubectl 명령어 실행, 메시지 전달 시 자주 참조됩니다. 모든 식별자 옆에는 피드백(시각적 복사 완료 표시)이 명확한 <CopyButton>을 제공합니다. 피드백이 없는 단순 동작은 중복 클릭 및 작업 불확실성을 유발합니다.
다국어 지원 (i18n)
- 영어 번들을 기본(Fallback) 원본으로 관리하며, 한국어 번들은 완전하게 번역된 형태로 제공합니다. 번들 간 i18n 키 일치 여부를 검증합니다.
- 문장을 부분 조각으로 조합하여 구성하지 않습니다. 동적 값은 플레이스홀더를 사용하고, 문장 내 마크업이 포함된 경우
<Trans>컴포넌트를 활용하여 언어별 어순 차이에 대응할 수 있도록 합니다. - 언어 변경 컨트롤은 로그아웃 화면 및 오류 페이지를 포함하여 시스템 전체 모든 화면에서 접근 가능해야 합니다.
- 사용자가 직접 입력한 이름(관리자 정의 명칭)은 언어 변경과 무관하게 입력된 원본 그대로 유지합니다.
한국어 표현 가이드라인
한국어 i18n 번들은 문자열 유형별로 통일된 문체 기준을 적용하여 어조의 일관성을 유지합니다.
- 명사구 형태 (동사 어미 생략): 화면 제목 및 부제, 테이블 헤더, 필드 라벨, 탭, 버튼, 상태 배지, 필터 명칭에 적용합니다. (예:
노드 비우기,예약,차단 / 오프라인 노드) - 완성형 문장 형태: 발생한 사건이나 수행할 작업을 설명하는 위치에 사용합니다 (대화 상자 본문, 오류 메시지, 토스트 알림, 빈 상태 안내, 필드 힌트). 문장은
~합니다또는~하세요어미로 작성하며,~해요나~함은 사용하지 않습니다. 확인 대화 상자의 제목은 질문형으로 작성합니다 (예:{{name}}을(를) 삭제할까요?). - 고유명사 및 단위 표기: 고유 제품명 및 기술 단위는 표준 영문 표기를 유지합니다 (예: GPU, VRAM, CUDA, GiB, DCGM, kubeconfig, CSV). 주요 용어는 시스템 전체에서 동일한 한국어 용어로 통일합니다.
| 영어 (코드 / Kubernetes) | 콘솔 표준 한국어 표기 |
|---|---|
| cordon / uncordon | 차단 / 차단 해제 |
| drain | 비우기 |
| hold (크레딧 예약) | 예약 |
| operator | 오퍼레이터 |
| super_admin | 시스템 관리자 |
| org_admin | 조직 관리자 |
| group_admin / member / guest | 부서 관리자 / 구성원 / 게스트 |
| group (project) | 부서 |
| organization | 조직 |
| offering / preset / policy | 오퍼링 / 프리셋 / 정책 |
| quota | 쿼터 (스토리지) · 한도 (정책) |
| session / volume / wallet / credit | 세션 / 볼륨 / 지갑 / 크레딧 |
| pause / terminate / paused | 일시정지 / 종료 / 일시정지됨 |
| exclusive / fractional | 전용 / 공유 |
화면 신규 추가 절차
PageHeader와 Table/Field 컴포넌트를 조합하여 기본 구조를 작성하면 대부분의 UX 표준 규격을 자동으로 충족할 수 있습니다. 작성 후에는 해당 화면을 사용하는 페르소나를 지정하여 UX 감사 스크립트를 실행합니다.
UX_PERSONA=researcher node test/e2e/ux/audit.js