HBSA000_시스템 관리 (플랫폼 운영 콘솔)
(Hereby) 시스템 관리 > 앱 개요
| 앱 | System Admin (tools/system-admin, Fastify 서버 포트 4100 + Alpine UI) |
| 대상 사용자 | 플랫폼 운영자(벤더 hereby 스태프) — accounts.platformRole = OPERATOR / SUPPORT |
| 프로토타입 | TBU (dev 라우트 AccessDenied 격리) |
| 관련 정책 | [[POL_권한(RBAC)]] · [[POL_AI안전경계]] |
| Status | draft (코드 기반 — 라이브 실사 불가: dev 라우트 AccessDenied 격리, 2026-08-25 확인) |
| Owner | 카나 |
개요
System Admin은 병원 내부 Admin Panel(HBAD)이 아니라, 벤더(hereby) 운영자가 전 고객사(조직)를 횡단 관리하는 독립 운영 콘솔이다. 신규 병원 프로비저닝, 병원별 관리자 계정·역할·권한, 외부 연동 자격증명, 근태 리뷰, 공휴일 동기화, Secom 지문 동기화, 플랫폼 스태프 부여를 다룬다. feat/system-admin-redesign(PR #583, 2026-08-22 develop 머지)으로 콘솔이 DB 클라이언트에서 순수 API 클라이언트로 전환됐다 — 세 축: ①데이터 경로(raw pg → 각 엔드포인트 tRPC platform.system* systemProcedure 경유) ②인증(env 단일 계정 → 운영자 본인 계정, OPERATOR 판정) ③토폴로지(단일 DB → 멀티 엔드포인트 fan-out).
⚠ 라이브 실사 불가: dev 라우트가 AccessDenied로 격리되어 2026-08-25 재확인 시에도 접근 불가. 본 사양서는 전 편 코드 기반 draft이며, 코드에서 추출한 UI 원문(라벨·문구)만 큰따옴표 인용하고 코드 근거(파일 경로)를 표기한다. 화면 인터랙션 결과(토스트 문구·전환 애니메이션 등)의 실측이 불가한 지점은 TBU로 남긴다.
문서 히스토리
| 날짜 | 작성자 | 팀 | 변경 내용 |
|---|---|---|---|
| 2026-07-06 | 카나 | 콸 | 최초 작성 (docs 기반) |
| 2026-07-21 | 카나 | 콸 | tenant config 소관 HBAD로 정정 |
| 2026-08-18 | 카나 | 콸 | 라이브 재검증 — DNS NXDOMAIN·tenant route AccessDenied |
| 2026-08-25 | 카나 | 콸 | 🔴 전면 재작성 — redesign PR #583 코드 census 반영. 개요·접근모델(계정 인증/OPERATOR·SUPPORT/멀티엔드포인트/볼트) 재정의, 화면 채번 HBSA100~280 신설 |
| 2026-10-22 | 카나 | PM | (10/21 머지분 — 신규 화면 보고, 자동 작성 금지 원칙) #1297(641c7810, 14파일 +2,529) 「시트 템플릿 관리」 탭 신설(tools/system-admin tabs/sheet-templates.html) — 시스템 시트 템플릿의 상세 조회/편집/생성/삭제, 조직 간 복사와 엔드포인트 간(dev↔운영) 복사, 조직 시트에서 템플릿 추출 기능(sheet-template-json 직렬화, system-admin-sheet-template.service + capabilities 권한 행). 「법정공제 — 4대보험 (월별)」 등 시드 시트 템플릿의 운영 편집 표면이 생긴 것 — 공제 치명 2건 수정이 코드 배포 없이 템플릿 편집으로도 가능해질 수 있는 경로(PM 함의). 채번 제안: HBSA290 대기 중이므로 후속 번호 HBSA291(Sheet Template Management, 가칭) — 콸 승인 대기, 자동 작성 안 함. 커밋 근거, 운영 콘솔 실사 불가(구조적) |
| 2026-10-21 | 카나 | PM | (10/20 머지분 — 신규 화면 보고, 자동 작성 금지 원칙) ① #1264(e72caee) 콘솔 플랫폼 그룹에 「계정 관리」 탭 신설(tabs/accounts.html) — 회원 계정 검색→연결 조직(직원) 확인, 계정에 조직 추가/조직에 기존 계정 추가(둘 다 합성 계정 직원에 linkEmployeeToAccount 운영자 직접 수행, 연결 가드 3겹: 합성 계정 거부·조직당 1명·타 실계정 연결 직원 가로채기 거부, 재인증+멱등키) · #1266(5ffa868) 같은 탭 조직 연결 모달 「새 직원 생성」 모드(성·이름·사번·입사일, 실계정만·사번 중복 거부·mustChangePassword false) — 채번 제안: HBSA290 (Account Management), 콸 승인 대기 ② #1265(8f50533) 접근 관리 화면(tabs/sysadmin-admins.html — 미채번)에 비밀번호 5회 실패 잠금 노출: 목록 「잠김」 라벨·상세 실패 횟수/자동 해제 시각·admin_user:manage 수동 해제(사유 5자 이상 필수·실패 횟수 리셋·admin_user.unlocked 감사·미잠김 409). 전부 커밋 근거, 운영 콘솔 실사 불가 |
접근 모델 (redesign PR #583 코드 기준)
| 층위 | 정의 | 코드 근거 |
|---|---|---|
| 네트워크 게이트 | IP allowlist(정확 일치, CIDR 없음). 미허용 IP는 403 "Access Denied" + "Connection from {ip} is not permitted.". /healthz만 예외. 심층 방어로 유지(계정 인증 도입 후에도) | server/auth.ts ipAllowlistHook |
| 콘솔 인증 | 운영자 본인 이메일 계정으로 로그인. 대상 엔드포인트의 account.login(scope:"account") 검증 → platform.systemWhoAmI로 OPERATOR 여부 확인 → 콘솔 JWT(12h) 발급. env 단일 계정(구 SYSADMIN_USERNAME/PASSWORD) 폐기 | server/index.ts /api/auth/login |
| OPERATOR | 전 org 전권(owner 동형) 내재. per-org grant 행 불필요 — resolveSystemGrantUser가 platformRole만으로 SUPER_ADMIN 상당 세션 합성. grant 발급/회수 권한 보유 | design §2.1 |
| SUPPORT | 전권 없음. OPERATOR가 벤더-사이드로 발급한 per-org organization_grants로 스코프 지원 진입만 | design §2.1 |
| OPERATOR env bootstrap | 신규 배포 닭-달걀(부여 UI가 OPERATOR 로그인 뒤) 해소. API 부팅 시 OPERATOR_BOOTSTRAP_EMAIL/PASSWORD 존재 + OPERATOR 전무일 때만 계정 생성(멱등, Grafana 패턴). 2번째 스태프부터는 콘솔 Platform Staff 탭에서 생성 | design Phase 3 후속 |
| 멀티 엔드포인트 | 엔드포인트 = 자체 accounts·JWT secret·DB를 가진 독립 인증 realm(엔터프라이즈 배포 단위). 콘솔은 env SYSADMIN_ENDPOINTS(JSON 배열) 레지스트리 + 엔드포인트별 세션 매니저(독립 만료·401 시 해당 엔드포인트만 재로그인) | orchestrator/endpoints.ts |
| 자격증명 볼트(§5-2) | 세션 매니저 자격증명은 인메모리(재시작 시 소실). SYSADMIN_VAULT_KEY(FIELD_ENCRYPTION_KEY와 별개) 설정 시 AES-256-GCM 파일 볼트에 영속(opt-in, 리프레시 토큰은 비저장) | orchestrator/credential-vault.ts |
권한 규칙 상세는 [[POL_권한(RBAC)]] 참조. 본 사양서는 규칙을 정의하지 않고 화면이 어느 게이트 뒤에 있는지만 채록한다.
Data I/O (앱 레벨 — 공통 오케스트레이션)
Client → Server (콘솔 자체 REST) → API systemProcedure
| 액션 | 콘솔 REST | 오케스트레이션 | API systemProcedure |
|---|---|---|---|
| 로그인 | POST /api/auth/login | verifyOperatorCredential(세션 밖 검증) | account.login + platform.systemWhoAmI |
| 크로스-org 목록 | 각 탭 GET / | fan-out(전 엔드포인트 병렬, 부분 실패 허용) | platform.systemOrganizations 등 |
| 단일-org 상세/쓰기 | GET/PUT/POST/DELETE /:id (?endpoint=) | callOrgScoped / callOrgScopedMutation(단일 엔드포인트 라우팅, 4xx 원 상태코드 보존) | platform.systemOrganizationDetail 등 |
Server → Client — fan-out 4-way 엔드포인트 상태
| 상태 | 의미 | UI 배지(Endpoints 탭) |
|---|---|---|
ok | 정상 | "연결됨" (green) |
unauthenticated | 인증 필요(토큰 만료·미등록) | "인증 필요" (yellow) |
unsupported | 구버전 서버에 프로시저 없음 | TBU(집계에는 포함, 배지 매핑 미확인) |
failed | 다운·타임아웃 | "연결 실패" (red) |
부분 실패는 오류가 아니라 1급 상태다(design §6). 코드 근거: orchestrator/fanout.ts, orchestrator/org-scoped.ts.
화면 플로우 (앱 진입)
화면 채번 트리
| 코드 | 화면 | 타입 | 코드 근거 |
|---|---|---|---|
| HBSA000 | 앱 개요 (본 문서) | - | - |
| [[HBSA100]] | Signin (운영자 로그인) | Screen | src/views/signin.html |
| [[HBSA200]] | Dashboard Shell (헤더 + 탭 네비) | Screen | src/views/dashboard.html, src/app.ts |
| [[HBSA210]] | Organizations (목록 + 상세 + admin/role/org-edit 모달) | Tab | tabs/organizations.html |
| [[HBSA211]] | Provision New Organization (모달) | Modal | modals/provision.html |
| [[HBSA220]] | Attendance Review (크로스-org 근태) | Tab | tabs/attendance.html |
| [[HBSA230]] | Secom Sync (지문 동기화 소스) | Tab | tabs/secom-sync.html |
| [[HBSA240]] | External Integrations (연동 자격증명) | Tab | tabs/integrations.html |
| [[HBSA250]] | Sheet Templates (시스템 시트 템플릿) | Tab | tabs/sheet-templates.html |
| [[HBSA260]] | National Holidays (공휴일 동기화) | Tab | tabs/national-holidays.html |
| [[HBSA270]] | Platform Staff (플랫폼 스태프·엔드포인트별) | Tab | tabs/platform-staff.html |
| [[HBSA280]] | Endpoints (엔드포인트 레지스트리·연결) | Tab | tabs/endpoints.html |
⚠ 트리거 지시의 "responsive LNB sidebar"는 코드에서 확인되지 않음 — dashboard.html은 상단 수평 tab-nav(
ds-tab-nav) 구조다. 본 사양은 머지된 코드 기준으로 tab-nav를 채록하며, LNB는 이후 커밋 또는 미머지 산출물일 수 있어 TBU로 남긴다.
System Admin vs Admin Panel 경계
| 구분 | System Admin (본 앱, HBSA) | Admin Panel (HBAD) |
|---|---|---|
| 사용자 | 벤더 플랫폼 운영자 (OPERATOR/SUPPORT) | 병원 내부 관리자/HR admin |
| scope | cross-tenant / 전 병원 (fan-out) | single organization |
| 목적 | 프로비저닝·admin bootstrap·플랫폼 권한·연동·근태 리뷰 | 직원/팀/권한/근태/병원 설정 운영 |
| 접근 | standalone host + IP allowlist + 계정 인증(OPERATOR) | tenant app route |
| 데이터 경로 | 각 엔드포인트 platform.system* systemProcedure(멀티테넌시 의도된 예외) | 조직 스코프 tRPC |
| tenant config | 아님 — 세율/호봉/항목코드/연차기준/근무유형/롤 생성은 HBAD 소관 | HBAD 소관 |
도입 체인 — Provisioning → Import → Admin Panel
| 단계 | System Admin (HBSA) | Importer (HBIM) | Admin Panel (HBAD) |
|---|---|---|---|
| 10 | [[HBSA211]] 조직+admin+역할/권한 one-step provision (provisionWorkspace 재사용) | target organization 필요 | admin login 시작점 |
| 20 | Seed Defaults / Seed Item Codes | imported users role mapping | tenant 권한 운영 |
| 30 | 근태·연동·공휴일 리뷰 | Excel workbook import | 직원/팀/근태 운영 개시 |
AI 접점 요약
최고권한 콘솔이므로 AI 안전경계가 가장 강하다 — org 생성/admin 생성·삭제·password reset/role·permission mutation/IP allowlist·platformRole 부여 모두 AI 자동 실행 금지, 전 mutation은 운영자 confirm + audit ([[POL_AI안전경계]]). 코드상 AI 위젯은 조직별 settings.ai.enabled opt-in(HBSA210 참조)이며 콘솔 자체 AI skill 트리거는 이번 redesign 코드 범위에 없음(TBU).
OQ (미해소)
- "responsive LNB sidebar" 실체 = 코드 미확인(tab-nav만 존재). 후속 커밋 여부 TBU
.env.example에 구SYSADMIN_USERNAME/PASSWORD·DB_*잔존 — design은 Phase 3에서 제거 선언. 문서/코드 불일치(env.example 미갱신 추정) — 실배포 env 확인 필요 TBU- fan-out
unsupported상태의 UI 배지 매핑 = TBU - 로그인·mutation 성공/실패 토스트 문구 실측 = TBU (라이브 격리)
- audit_logs 조회 UI가 콘솔 내부에 있는지 = 코드상 미발견(쓰기 시 기록만) TBU