부산대학교 클라우드 플랫폼(Pickle)의 웹 콘솔입니다.
랜딩 페이지부터 사용자 콘솔, 기관과 시스템 관리자 콘솔, 웹 터미널까지 사용자가 이 플랫폼을 만나는 모든 화면이 여기 있습니다. nginx가 정적 빌드 산출물을 서빙하는 SPA이고, 데이터는 전부 백엔드 API에서 받아옵니다.
접속: https://pickle.pusan.ac.kr
리소스 신청, 승인 대기, 대시보드, VM별 SSH 개인키 발급, 웹 터미널, 도메인 공개로 이어지는 사용자 여정에 승인 큐와 감사 로그, 드리프트 리포트, 관리자 개입 같은 관리자 콘솔 화면이 더해집니다. 아래는 그중 관리자 쪽 세 화면입니다.
![]() 관리자 대시보드. 승인 대기, VM 현황, 리소스 요약 |
![]() VM 상세. 개요와 이벤트 탭, 정보 카드 |
![]() 관리자 감사 로그. 민감한 동작은 행위자와 IP가 함께 남습니다 |
|
- 알림 보내기: 대상 인원과 조회 가능한 명단을 미리 확인한 뒤 발송합니다. 미리보기는 예상이며 저장 결과는 실제 큐 접수 시의 인원입니다. 발송 상세를 URL로 다시 열고 시스템 계층은 수신자별 결과로 이동할 수 있습니다.
- 공지사항 관리: 등록·수정 전용 페이지에서 본문·이미지·게시 기간과 미리보기를 확인합니다.
등록은
/admin/notices/new, 수정은/admin/notices/:noticeId/edit이며 등록 후 같은 공지에 이미지를 첨부합니다. 목록의 읽기 상세는 드로어로 열고 작성 후 원래 목록·선택 항목으로 돌아갑니다. 평문과 저장된 이미지는 실제 본문·팝업 카드와 같은 형태이며, 게시 예정·게시 중·종료를 구분합니다. 팝업은 전용 조건으로 전체 페이지를 읽고 열린 화면에서도 60초마다 갱신합니다.
플랫폼은 VM 신청·승인·생성, SSH와 웹 터미널 접속, 도메인 공개, 만료와 삭제까지를 다룹니다. 이 레포지토리가 맡는 부분은 아래와 같습니다.
-
랜딩: 가상머신, LLM API 키와 도메인을 소개하고 접속 예시와 사용 가이드로 연결합니다. 모바일에서도 본문 섹션과 가이드를 메뉴에서 탐색합니다.
-
사용자 여정: 회원가입부터 신청, 접속, 도메인 공개, 삭제까지 인프라를 몰라도 따라갈 수 있게 안내합니다.
-
신청서: 리소스 종류를 고르고, 만들 리소스(이름과 OS, 사양)를 정한 뒤, 누구 명의로 왜 언제까지 쓸지를 적습니다. 종류를 아는 자리에서 들어오면 첫 단계는 접힙니다. 사양은 준비된 것을 고르거나 직접 적을 수 있고, 직접 적으면 사유를 받아 관리자가 검토합니다. 접속 이름(SSH 접속명)도 여기서 직접 정할 수 있습니다. 워크스페이스 소유자와 승인 권한자는 구성원과 대기 중인 초대를 대상자로 골라 가상머신과 LLM API 키를 여러 사람에게 한 번에 신청할 수 있고, 초대받은 사람의 리소스는 가입할 때 만들어집니다. 엑셀에서 복사한 출석부 행을 붙여넣어 학번으로 대상자를 넣을 수도 있습니다. 줄마다 확인 결과를 미리 보여 주고, 아직 구성원이 아니거나 가입하지 않은 학생은 제출할 때 구성원으로 추가하거나 초대합니다. 워크스페이스 구성원 초대도 같은 붙여넣기와 미리보기를 씁니다. 관리자 콘솔에서는 같은 신청서로 제출과 동시에 승인하고, 신청 상세에서 대상자별 생성 상태를 확인합니다.
-
워크스페이스 스코프: 사이드바 선택기로 워크스페이스 하나를 고르면 그 워크스페이스가 가진 리소스와 신청을 전부 보고, 접근 권한이 없는 리소스는 이름과 상태, 소유자까지 보입니다. '전체'를 고르면 워크스페이스를 가리지 않고 본인이 접근 권한을 가진 리소스와 본인이 낸 신청만 봅니다.
-
웹 터미널: SSH 클라이언트나 키가 없어도 브라우저에서 바로 VM 셸을 엽니다. 별도 창으로 열려 화면 전체를 쓰고, 창 크기를 조절하면 터미널 크기도 따라갑니다.
-
접속 수단 관리: VM별 SSH 개인키를 발급받아 내려받고, VM 초기 비밀번호를 다시 열람하거나 재발급합니다.
-
도메인 공개: 서브도메인을 직접 정해 VM의 웹 서비스를 공개하고, 필요하면 다시 내립니다.
-
도메인: VM 없이 이름만 받아 A, AAAA, CNAME, TXT 레코드를 직접 편집합니다. 다른 리소스와 같은 신청으로 받고, 고른 루트 도메인에 따라 접수와 동시에 발급되거나 관리자 검토를 거칩니다. 대상은 이 플랫폼 밖 서버여도 됩니다. 레코드 편집기는 표 전체를 저장하므로 표에서 지운 줄은 존에서도 지워집니다. 사용 기한이 있고 연장하지 않으면 이름이 회수됩니다.
-
LLM API 키: 승인받은 키를 목록에서 확인하고 상세에서 직접 발급합니다. 평문은 발급 응답에서 한 번만 보이고 서버에는 해시만 남으므로 화면도 그렇게 다룹니다 — 값을 잃어버렸으면 재발급(이전 값은 그 자리에서 무효), 유출됐으면 폐기입니다. 사용량 탭은 일별 요청·토큰과 한도에 걸려 거부된 요청을 보여 줍니다.
-
알림함: 승인이나 만료 같은 사건이 쌓이고, 읽은 것과 안 읽은 것이 구분됩니다. 관리자 신청 알림은 신청의 기관으로 이동합니다. 기관 정보가 없는 기존 알림은 권한 범위에서 신청을 조회해 기관을 확인하며, 대상을 열지 못하면 알림함에 오류를 표시합니다. 읽음 처리와 대상 이동 실패는 별도로 표시합니다.
-
관리자 콘솔: 가상머신·LLM API 키 신청을 종류별 참고 정보로 검토하고, 승인된 LLM API 키의 6개 한도와 정지·해제·폐기를 역할과 상태에 맞게 관리합니다. 키나 관리 기관, 계정이 바뀌면 열린 편집 창과 입력을 폐기합니다. Gateway·upstream의 관측 상태와 최종 처리 지표를 읽습니다. LLM 사용량 화면은 7·30·90일 수요와 scope-aware 소비처, 실제 한도 소진과 정확한 차단 사유를 보여 주며 집계, 사용량 수신, OpenRouter 사용액, 전송 대기열의 확인 시각을 함께 표시합니다. 기관별 OpenRouter 사업 계정 화면에서는 사업과 담당자, 관리용 키의 등록·교체 상태를 관리하고, 저장된 구매 금액, 누적 사용액, 잔액, 소진 예상과 Pickle 밖에서 쓴 금액을 각 확인 시각과 함께 읽습니다. 이 값은 화면 요청 때 OpenRouter를 직접 부른 값이 아니며, 마지막 성공·시도·실패와 키 대사 신선도를 나누어 표시합니다. 유료 모델을 승인할 때 승인자가 사업 계정을 고르고, 한 번 정해진 연결은 화면에서 바꿀 수 없습니다. 리소스 현황, 감사 로그, 드리프트 리포트와 관리자 개입도 함께 다룹니다.
가상머신 목록의 행과 이름은
/admin/vms/:vmId상세 페이지로 연결합니다. 전원 제어와 기간 연장, 접속 차단, 삭제 예약·취소·강제 삭제는 상세에서 대상 기관의 실제 역할에 따라 표시합니다. 상세의VM 관리링크나 브라우저 뒤로가기로 돌아오면 진입한 관리 범위와 검색·워크스페이스·상태·정렬·페이지, 일괄 변경 대상 선택을 유지합니다. 계정이나 역할이 바뀌면 이전 목록 상태와 대상 선택을 재사용하지 않습니다.워크스페이스 목록은 이름과 종류, 정렬, 페이지와 상세 탭을 URL에 보존합니다. 상세를 닫으면 같은 조회 조건을 유지하고 대상 버튼 또는 검색창으로 포커스를 돌립니다. 신청 목록은 상태와 종류, 검색, 희망 종료일, 무기한 여부, 관련 사용자와 페이지를 URL에 보존하며 실제 신청 기관에서 상세를 읽고 원래 목록으로 복귀합니다. 워크스페이스 상세의 VM 수는 전체 워크스페이스 기준이며 기관 범위의 VM 이동은 별도로 표시합니다.
저장된 신청의 승인과 관리자의 즉시 승인 신청은 같은 최종 확인을 사용합니다. 신청자와 워크스페이스, 실제 대상 명단, 가입 대기와 취소·제외, 희망값과 1명당 부여값, 기간을 비교합니다. 명단 미리보기는 구성원이나 초대를 만들지 않으며 신청을 제출할 때 처리합니다. 한 신청의 대상은 최대 200명이고, 조회에서 확인한 계정도 제출 시 서버에서 다시 검증합니다. 다인 VM의 합계는 취소·제외를 뺀 부여안이며 물리 용량 예약이나 생성 완료를 뜻하지 않습니다. 배치 노드는 이름으로 고르고, 현재 사양으로 VM 한 대를 배치할 수 있는지 조회합니다. 실제 생성 단계의 배치는 달라질 수 있으며 자동 배치의 실제 노드도 결과에서 확인합니다. 대상별 등록 실패는 재시도 접수와 이후 상태를 구분하고, 등록된 VM의 생성·오류·실행 상태는 별도로 조회합니다. 시스템 계층은 해당 VM으로 필터된 운영 작업과 신청 사이를 왕복할 수 있습니다. VM·LLM API 키 결과에서 상세로 이동하면 원래 신청과 필터 목록으로 돌아갈 수 있으며, 상세에서도 연결된 실제 승인 신청을 엽니다. 도메인 결과에 원본 ID가 있으면 기존 도메인 드로어에서 정확한 대상을 조회합니다. 종료된 도메인은 현재 상세와 분리해 기록 상태와 갱신·해제·예약 기한을 읽습니다. 연결 ID가 없는 기존 발급 결과는 이름으로 다른 발급 건을 추정하지 않습니다.
신청 카탈로그는 OS 이미지·사양·기간 탭과 상태·검색을 URL에 보존하고 사양과 기간의 표시 순서를 관리합니다. 노드 이미지가 은퇴해도 같은 논리 이미지의 호환 활성 복제본이 남으면 선택할 수 있습니다. 기존 신청의 저장된 사양과 종료일은 카탈로그 편집으로 바뀌지 않으며, 기간 표시 이름은 현재 이름을 보여 줍니다.
사용자 목록도 검색과 상태, 역할, 조회 기관, 정렬, 페이지와 선택 항목을 URL에 보존합니다. 빠른 조회 드로어에서
/admin/users/:userId지원 상세로 이동해 워크스페이스 구성원과 초대, 신청 대상별 결과를 함께 읽습니다. 워크스페이스 구성원 이름은 현재 역할에서 지원 상세를 열 수 있을 때만 링크로 표시합니다. 프로필 정정과 재활성화 전에 연결된 초대와 승인된 신청의 영향을 확인하며 변경 후 실제 가입, 자원 등록과 생성 진행 상태를 각각 조회합니다. 기관 열람자·운영자·관리자는 기관 연결과 무관하게 일반 사용자의 프로필·워크스페이스·초대· 신청·자원 metadata를 조회합니다. 지원 화면의 기관 선택은 전체 또는 특정 기관을 찾는 필터입니다. DNS 레코드 원문과 연결 정책의 CIDR은 실제 담당 기관의 조회 역할로 읽습니다. 기관 운영자·관리자와 시스템 관리자는 프로필을 정정하며, 계정 활성화·정지와 MFA 초기화는 시스템 관리자만 수행합니다. 기관 운영자·관리자와 시스템 관리자는 워크스페이스 정보와 구성원 초대, 소유자 지정·역할 변경·제거와 개별 자원 접근 목록을 지원합니다. 개인 워크스페이스의 구성원 구조와 마지막 소유자, 남은 자원의 삭제 조건은 서버가 판단합니다. 구성원 이름은 프로필 열람 권한과 별개인 부여 대상이며, 활성 시스템 구성원도 이름과 메일로 선택할 수 있습니다. 승인·예산·자원 직접 조치는 대상 기관에서 실제 보유한 역할을 따릅니다. 관련 신청과 플랫폼 전체 사용자 관계로 다시 이동할 수 있습니다. 접근 조건 조회는 정확한 사용자와 자원 ID로 구성원, 사용자 지정 권한, 워크스페이스 전체 권한과 자원 상태를 보여 줍니다. 기본 조건 충족과 실제 접속 성공은 구분합니다. 허용된 관리자는 사용자 지정 권한의 회수안을 미리보고 현재 상태와 충돌하지 않을 때 회수하며 처리 결과를 다시 조회합니다. 부여·등급 변경·전체 공유나 자기 초대·프로필 정정으로 본인의 실효 권한이 늘어나면 목적과 정확한 대상, 얻는 등급을 확인한 뒤 적용합니다. 생성 대기 중인 자원의 예상 소유자 권한은 현재 권한과 구별합니다. 일괄 접근 변경도 선택한 자원마다 확인과 결과를 보존하며, 회수는 비활성 계정이나 이전 구성원의 기존 개인 권한도 선택할 수 있습니다.일괄 변경은 서버에 저장된 미리보기와 적용 ID로 다시 엽니다. 미리보기의 24시간 유효 기간은 새 적용에만 해당하며, 저장된 적용 결과는 이후에도 조회할 수 있습니다. 응답을 받지 못하면 같은 ID의 결과를 조회하거나 동일한 본문으로 재확인합니다. 새 적용 ID를 자동으로 만들지 않습니다. 다른 창에서 소비한 미리보기는 기존 적용 결과로 연결합니다. 현재 권한으로 볼 수 없는 대상의 이름과 변경값은 가리고, 저장 당시 적용·거절 결과와 현재 조회 상태를 구분합니다. 사용자 지원의 권한 회수 결과도 VM·LLM API 키·도메인·GPU 각각의 적용 ID로 다시 조회합니다. DB 저장, 후속 작업 접수, 현재 작업 상태와 외부 자원의 실제 완료는 별도로 표시합니다. VM은 종료일을 지정하거나 무기한으로 저장하며, 만료된 VM도 삭제 관련 상태가 아니면 연장할 수 있습니다. LLM API 키는 회수된 키의 만료일을 바꿀 수 없고, 이미 발급된 유료 공급자 키의 연장은 제한됩니다. 미발급 키의 기간 변경과 작업 접수는 실제 키 발급 완료를 뜻하지 않습니다.
만료 관리는 VM·LLM API 키·도메인·GPU의 주 기한을 자원당 하나씩 모아 조회합니다. 기관·워크스페이스·자원 종류·기한 종류·기간과 페이지를 주소에 보존하며, 전체 수와 정렬은 서버 조회 결과를 사용합니다. 승인 사용 기한과 실제 GPU 임대 기한, 도메인 갱신과 예약 기한은 서로 구분하고 다른 날짜는 관련 정보로 표시합니다. 관측 시각의 기한 경과는 실제 중지·회수·해제 완료를 뜻하지 않습니다. 허용된 상세 화면에서 기존 기간 변경을 수행하고 목록으로 돌아오면 다시 조회합니다. 전체 조회 권한과 실제 담당 기관의 변경 권한은 별개입니다.
워크스페이스 상세는 구성원에서 사용자 지원으로 연결하고 자원 종류별 개수와 해당 기관의 신청 목록을 제공합니다. 대기 초대는 지원 조회 권한으로 읽습니다.
네트워크 상세는 저장된 연결 가드를 먼저 보여 주고 시스템 관리자만 별도 창에서 편집합니다. 가드 저장과 삭제 접수는 외부 반영 완료와 구분합니다. 릴레이 반영 보고는 실제 접속 성공을 보장하지 않으며 교내 IP는 관리자가 기록한 할당·회수 상태로 읽습니다. 신청 상세의 검토 결과와 대상자 리소스 등록도 VM 생성 완료와 구분하고, 정확한 리소스 상세에서 진행을 확인합니다.
기관 운영은 기관의 실제 역할 명단과 신청 알림 수신자를 함께 보여 줍니다. 기관을 고른 뒤 명단 전체의 역할과 메일 수신 방식을 미리보고 한 번에 저장하며, 다른 화면의 변경과 충돌하면 최신 명단을 다시 비교합니다. 지정 수신자 0명은 경고와 함께 저장할 수 있습니다. 콘솔 알림은 활성 기관 승인자에게 표시하고 이메일은 기관 설정을 따릅니다. 변경 후 접수된 신청부터 새 설정을 적용하며, 이미 발송 큐에 들어간 수신자와 주소는 유지합니다. 기관 변경 이력은 대상 기관을 기준으로 찾고 변경 전후 명단과 사유를 읽습니다.
시스템 계층의 발송 이력은 업무 알림과 계정 메일을 함께 조회합니다. 발송 당시 확정 주소, 현재 계정 주소, 대기 종류와 개별 SMTP 시도 결과를 구분하고 관련 신청, 기관과 사용자로 이동합니다. 신청 접수 당시 명단과 정책 버전은 현재 기관 명단과 별도로 보존합니다. 실패한 업무 메일은 허용된 건만 단건 재발송을 접수하며 계정 메일은 원래 계정 흐름에서 다시 요청합니다. SMTP 인계는 메일함 도착이나 열람을 확인한 결과가 아닙니다.
시각 표기는 자리에 따라 하나만 씁니다. 지금 얼마나 신선한지를 묻는 자리(상태, 잔액, 관측)는 상대 표기만, 언제 있었는지를 기록하는 자리(감사, 활동 내역, 생성·변경일)는 절대 표기만 쓰고 둘을 나란히 두지 않습니다.
API는 항상 자기 오리진의 /api로 호출합니다. 로컬에서는 Vite 프록시가, 배포된 환경에서는
각 환경의 nginx가 같은 경로를 해당 백엔드로 넘깁니다. 빌드는 배포 대상 호스트를 고정하고,
다른 호스트에서 열리면 화면을 시작하지 않습니다. 로컬 미리보기는 loopback 호스트에서 허용합니다.
TypeScript 타입은 백엔드가 생성한 OpenAPI 스펙에서 만들어 레포지토리에 커밋합니다. 명세가 바뀌면 diff에 드러나고, 스펙과 어긋난 코드는 타입 검사가 잡아냅니다.
테스트는 MSW로 API 전체를 목킹해 백엔드 없이 전 화면을 돌립니다. 핸들러 픽스처가 프론트엔드가 기대하는 응답의 문서 역할도 합니다.
웹 터미널의 WebSocket은 /api 밖의 /terminal/ws로 나갑니다. OpenAPI 표면과 API
런타임을 1:1로 유지하기 위해서이고, WS 종단은
pickle-sshgw의 터미널 브리지가 맡습니다.
바로 옆의 /terminal/<vm uuid>는 터미널 창 화면으로 이 앱이 가집니다 — 라우터가
아니라 src/main.tsx가 주소를 보고 문서를 가르며, UUID인 경로만 창으로 인정하므로
브리지가 쓰는 /terminal/ws와 겹치지 않습니다. 이 창은 콘솔의 인증 스택을 얹지
않고 접속 티켓을 콘솔 탭에서 postMessage로 받습니다. 창이 스스로 세션을 복원하면
리프레시 토큰 회전 검사가 콘솔 탭과 충돌해 모든 탭이 로그아웃되기 때문입니다.
로그인 완료 후 계정 정보 조회가 성공하면 콘솔로 이동합니다. 대기 중에는 스피너를 표시하고, 환영 문구는 콘솔 위에 짧게 표시한 뒤 첫 클릭이나 키 입력에서 닫힙니다. Google 로그인에서 2단계 인증을 거쳐도 검증된 콘솔 내부 복귀 주소를 유지합니다. 로그아웃하면 이전 계정 정보와 복귀 주소를 초기화합니다.
/docs에서 서비스 소개, 가입, 신청과 승인, 워크스페이스 공유, 가상머신, 도메인과 네트워크,
LLM API, 계정과 문제 해결을 로그인 없이 읽을 수 있습니다. 콘솔 메뉴의 사용 가이드는
현재 헤더와 선택한 워크스페이스 및 기관을 유지하며 왼쪽 리소스 메뉴를 문서 목차로
바꿉니다. 모바일에서는 상단 메뉴 버튼으로 목차를 엽니다. 사이드바 맨 위에는 제목과
키워드 검색이 고정되고 그 아래로 목차가 스크롤됩니다. 본문은 번호가 있는 절 목차와
이전·다음 문서, 원래 작업 화면으로 돌아가는 링크를 제공합니다.
랜딩의 서비스 소개 버튼도 /docs로 연결되며 로그인 후에도 콘솔 이동 버튼 옆에 표시됩니다.
본문은 src/docs/articles/에서 작성하고 src/docs/catalog.ts에 등록합니다.
각 문서는 GuideArticle의 제목과 검색 키워드, 고정 slug, 절별 id와 본문을 갖습니다.
본문의 문서 링크에는 GuideLink, 사용자 작업으로 가는 링크에는 GuideAction을 사용합니다.
문서 링크와 공개 경로 변환은 src/lib/docs-paths.ts가 맡습니다.
화면 예시는 src/assets/docs/의 PNG를 기사에서 import하고 GuideFigure로 표시합니다.
이미지마다 대체 텍스트와 캡션을 작성하며, 실제 메뉴 위치와 확인할 대상을 설명합니다.
이미지를 선택하면 같은 원본을 새 탭에서 볼 수 있습니다. 원본의 너비와 높이를 함께
지정하면 지연 로딩 중에도 이미지가 차지할 공간을 유지합니다.
메뉴 이름과 화면 배치, 권한 또는 상태 표시가 바뀌면 관련 이미지와 설명을 함께 갱신합니다. 예시 화면은 개인정보와 자격증명이 없는 데이터로 촬영하고, 확인할 영역만 담아 글자를 읽을 수 있는 크기로 유지합니다. 캡처에 나온 사용량과 한도는 현재 정책의 근거로 사용하지 않습니다. 새 이미지가 연결된 문서의 렌더링과 원본 링크, 좁은 화면에서의 표시를 확인하며, 하나의 원본은 가장 관련 있는 절에 배치합니다.
공개 /docs/*, 사용자 /console/docs/*, 워크스페이스별 /console/:workspaceId/docs/*,
관리자 /admin/docs/*는 같은 본문을 공유합니다. 새 문서는 실제 화면의 메뉴와 버튼,
권한, 완료 확인 방법까지 대조하고 문서 링크 검사와 화면 테스트를 함께 실행합니다.
LLM 키 승인, 관리자 한도 변경, OpenRouter 사업 계정의 승인 기본값은 하나의 입력란에서
허용과 차단 규칙을 함께 편집합니다. 한 줄에 한 규칙을 적고 허용에는 +, 차단에는 -를
붙입니다. 전체 내용을 한 번에 복사해 다른 입력란에 붙여 넣을 수 있습니다.
+openai/*
+anthropic/*
+google/*
-*/*-pro
공급자 자리의 *는 모든 공급자와 ~로 시작하는 별칭을 포함합니다. 모델 자리에는
정확한 이름, gpt-5-* 같은 계열, *-pro 같은 끝 이름 패턴을 사용합니다.
차단이 허용보다 우선하며 줄 순서는 결과에 영향을 주지 않습니다. + 규칙이 없으면
금액 한도 안에서 모든 유료 모델을 허용하고, - 규칙이 없으면 차단하지 않습니다.
자체 서빙 모델은 이 규칙의 대상이 아닙니다.
정확한 모델 이름은 :batch 같은 변형도 포함합니다. 특정 공급자를 허용할 때는 ~ 별칭을
따로 적으며, 특정 공급자의 차단은 별칭에도 적용됩니다. 규칙이 하나라도 있으면 모델을
대신 선택하는 openrouter/ 이름은 사용할 수 없습니다. 계정 기본값 변경은 다음 승인에
미리 채울 값만 바꾸며 이미 발급된 키에는 적용되지 않습니다.
GPU 신청·할당·VM 연결·반납과 관리자 검토 화면은 개발 모드에서
VITE_GPU_PREVIEW=1 npm run dev:mock으로 확인합니다. /console/gpus는 사용자
목록이며 /admin/gpus는 관리자 인벤토리·대기열·검토 화면입니다. 실습할 데이터는
src/test/msw/handlers/gpu.ts의 픽스처로 지정합니다. 개발 미리 보기는 할당·대기·연결 오류
예시를 불러오며 테스트 초기 인벤토리는 비어 있습니다. 미리 보기에서 처리하지 못한 API
호출은 오류로 막아 실제 백엔드에 전달하지 않습니다.
GPU 임대 기간은 신청자와 승인자가 시간 또는 일 단위로 직접 입력합니다. 미연결·저사용 검토 기준은 관리자 플랫폼 설정의 현재값을 사용하며 임대 기간과 별개입니다. VM 연결은 대상과 전원 변경을 확인한 뒤 실행하고, 연결 해제 후에도 임대는 유지됩니다.
기본 개발 모드와 production 빌드에서는 GPU 경로와 신청 선택을 열지 않습니다. 사이드바의 GPU는 준비 중으로 유지하며 실제 서비스 개방은 별도로 진행합니다.
도메인과 포트포워딩의 출발지 정책 화면은 VITE_PUBLIC_SOURCE_POLICY_ENABLED=1인
빌드에서만 노출합니다. 변수를 지정하지 않거나 다른 값을 넣으면 화면을 표시하지 않습니다.
이 플래그는 화면 노출만 제어하며 API 권한 검사를 대신하지 않습니다. 정책을 바꿔도 이미
연결된 세션을 강제로 종료하지 않고 새 연결부터 적용합니다.
서비스가 연결된 도메인은 IPv4·IPv6 주소와 CIDR을 받고, 포트포워딩은 relay 집행 범위에
맞춰 IPv4만 받습니다. 플랫폼이 트래픽을 중계하지 않는 DNS 전용 도메인에는 이 화면을
표시하지 않습니다. 상태는 비활성·반영 대기·설정 확인·설정 실패로 구분하며 내부 적용
세대 숫자는 사용자 흐름에 노출하지 않습니다. 교내 preset은 현재 CIDR을 입력 목록에
추가하는 snapshot이며 저장 뒤 자동으로 바뀌지 않습니다.
VM별 수신·송신 규칙 화면은 VITE_VM_NETWORK_POLICY_ENABLED=1인 빌드에서만 노출하고,
기본값은 off입니다. 사용자 VM 상세의 네트워크 탭에서는 VIEWER 이상이 저장된 정책을
읽고 EDITOR 이상이 변경합니다. 관리자 VM 상세에서는 기관 scope 안의 ORG_MANAGER·
ORG_ADMIN과 SYS_MANAGER·SYS_ADMIN이 변경하며 viewer 역할은 읽기만 합니다. 이 UI
플래그와 역할 판정은 화면 제어이고 최종 권한은 API가 검사합니다.
사용자 규칙은 순서가 유지되는 최대 128개의 IPv4 CIDR 규칙입니다. 방향은 수신·송신, 처리는 허용·차단, 프로토콜은 전체·TCP·UDP·ICMP이며 TCP/UDP만 단일 포트나 범위를 가질 수 있습니다. 플랫폼 SSH, 웹 터미널과 공개 경로에 필요한 system rule은 목적과 설명만 읽을 수 있고 편집할 수 없습니다. 저장은 revision CAS를 사용하므로 충돌 시 작성 중인 규칙을 보존하고 명시적으로 최신 정책을 불러와야 합니다.
상태의 설정 확인됨은 서버가 방화벽 설정 반영을 다시 확인한 상태이며 실제 연결 성공을 뜻하지
않습니다. 실패(차단 설정 확인)은 변경 적용은 실패했지만 fail-closed 차단 설정의 반영을
확인한 상태입니다. 준비되지 않았거나 API가 unavailable인 VM은 편집기를 표시하지 않습니다.
React 19, TypeScript, Vite 8, Tailwind 4, TanStack Query 5, react-router 8, openapi-fetch. 웹 터미널은 xterm.js, 랜딩의 3D 히어로는 react-three-fiber, 사용량·용량 추이 차트는 uPlot을 사용합니다. 무거운 세 가지는 모두 해당 화면을 열 때만 내려받도록 코드 분할해 두었습니다. 2단계 인증 등록 화면의 QR 코드는 qrcode.react가 SVG로 그립니다. 테스트는 vitest와 MSW 조합이고, 린트는 oxlint로 경고까지 실패로 취급합니다.
Node.js 24(LTS)가 필요합니다. package.json의 engines에 적혀 있습니다.
npm install
npm run dev # /api·/terminal/ws 를 로컬 pickle-api :8080 으로 프록시
npm run dev:mock # api 없이 목 데이터로 실행
scripts/verify.sh # lint → typecheck → test → build → 취약점 감사dev:mock 은 데이터베이스도 api도 없이 화면만 띄웁니다. 테스트가 쓰는 목 핸들러를 그대로
브라우저에 물리므로 로그인부터 신청, 승인까지 눌러 볼 수 있고, 접속 이름 중복처럼 서버만
아는 실패도 그대로 재현됩니다. 저장되는 것은 없고 새로고침하면 처음 상태로 돌아갑니다.
목 코드는 운영 번들에 들어가지 않습니다(동적 import 와 import.meta.env.DEV 가드).
| 명령 | 내용 |
|---|---|
npm run lint / npm run typecheck |
oxlint / tsc -b |
npm run test / npm run test:watch |
vitest 1회 실행 / 워치 |
npm run build / npm run preview |
프로덕션 빌드 / 미리보기 |
npm run gen:api |
API 타입 재생성 |
verify.sh 안의 npm audit --omit=dev --audit-level=high는 차단 게이트입니다. 런타임
의존성에 high 이상 취약점이 있으면 검증이 실패합니다.
로컬 백엔드를 dev 프로파일로 띄우면 빈 데이터베이스에 개발용 계정 둘이 만들어집니다.
시스템 관리자는 admin@pickle.local, 기관 관리자는 orgadmin@pickle.local입니다.
비밀번호는 백엔드를 기동할 때 환경 변수로 준 값이라 여기에는 적혀 있지 않습니다. 신청과
승인에 필요한 기관과 OS, 사양 프리셋도 같이 들어오므로 화면을 처음부터 따라갈 수
있습니다. 이 계정들은 dev 전용이고, 운영 환경에서는 운영자 부트스트랩 절차가 관리자
계정을 만듭니다.
배포용 빌드는 대상 브랜치와 함께 VITE_DEPLOY_TARGET을 명시합니다.
production은 pickle.pusan.ac.kr, development는 pickle.pnuops.com에서만
화면을 시작합니다. 빌드 결과의 로컬 미리보기는 localhost, 127.0.0.1,
[::1]에서도 시작합니다. 로컬 Vite 개발 서버는 기본적으로 development를 사용하며
호스트 검사는 적용하지 않습니다.
| 변수 | 값 | 역할 |
|---|---|---|
VITE_DEPLOY_TARGET |
production 또는 development |
배포 호스트와 사용 안내의 외부 접속 주소를 선택합니다. 일반 로컬 빌드의 기본값은 production입니다. |
VITE_DEV_SSH_HOST |
개발 전용 *.pnuops.com 호스트, 선택 |
없으면 개발 화면의 SSH 예시를 비활성화합니다. |
VITE_DEV_LLM_BASE_URL |
개발 전용 https://*.pnuops.com/v1, 선택 |
없으면 개발 화면의 LLM 연결 정보와 예시를 비활성화합니다. |
VITE_DEPLOY_TARGET=development npm run build
VITE_DEPLOY_TARGET=production npm run build개발 빌드에 외부 접속 주소를 설정하면 해당 개발 서비스가 실제로 준비된 뒤에만 사용합니다.
운영 서비스 주소가 개발 빌드의 최종 출력 파일에 들어가면 빌드가 실패합니다.
이 검사는 public/에서 복사한 파일과 바이너리 asset도 포함합니다. 개발 API와 DNS, 메일 연동의
대상은 콘솔 번들이 아니라 같은 오리진의 개발 API 설정에서 결정합니다.
npm run gen:api # ../api/contract/openapi.yaml → src/api/schema.d.ts
# 별도 checkout이나 worktree의 명세를 사용할 때
OPENAPI_SCHEMA=/absolute/path/to/openapi.yaml npm run gen:apipickle-api 레포지토리가 형제 디렉터리(../api)로
체크아웃돼 있다고 가정합니다. OPENAPI_SCHEMA를 지정하면 그 파일을 입력으로 사용하며
출력은 그대로 src/api/schema.d.ts입니다. 산출물은 커밋 대상입니다.
src/api/ 생성된 타입(schema.d.ts)과 쿼리 래퍼
src/auth/ 권한과 역할 판정
src/pages/ 화면 (랜딩·사용자·관리자)
src/terminal/ 웹 터미널 창 — 화면·소켓 훅·콘솔 탭과의 티켓 중계
src/components/ src/layouts/ 공용 UI
src/lib/ 상태·라벨 매핑, 포맷, 검증 유틸
src/test/ vitest 설정과 MSW 목
public/ 빌드가 그대로 내보내는 정적 파일
assets/ README 스크린샷
public/pnu-logo.png는 지우거나 이름을 바꾸지 마세요. 콘솔 소스가 참조하지 않아 쓰이지
않는 것처럼 보이지만, favicon이 /pnu-logo.png로 가리키고 발송 메일의 HTML 상단도 같은
파일을 절대 URL로 불러옵니다. 없어져도 nginx가 SPA 껍데기를 200으로 돌려주므로 배포
검사에 걸리지 않고, 이미 발송된 메일의 로고까지 함께 깨집니다. 엠블럼을 교체할 때는 이
파일과 src/assets/pnu-logo.png를 같이 바꿔야 favicon과 메일이 화면 속 로고와 어긋나지
않습니다.
flowchart LR
subgraph ext [외부]
B[콘솔 접속]
V[VM 도메인 접속]
S[VM SSH 접속]
PC[VM 포트 접속]
L[LLM API 호출]
end
subgraph relay [오프캠퍼스 릴레이]
HA[HAProxy :22]
NFT[nftables DNAT]
RA[pickle-relay-agent]
end
subgraph campus [부산대학교 서버팜]
PN[Pickle nginx]
VN[VM nginx]
C[pickle-console]
A[pickle-api]
J[JobRunr]
G[pickle-sshgw]
P[pickle-proxy-agent]
DB[(PostgreSQL)]
PVE[Proxmox VE]
VM[사용자 VM]
IB[pickle-image-builder]
LG[pickle-llm-gateway]
UP[업스트림 모델 서버]
end
B --> PN
V --> VN
S --> HA
PC --> NFT
L --> LG
HA -->|WireGuard| G
NFT -->|WireGuard| VM
NFT -. 규칙 적용 .- RA
RA -->|sync| A
PN -->|/| C
PN -->|/api| A
PN -->|/terminal| G
G -->|인가 질의| A
LG -->|키·모델 동기화| A
LG --> UP
G --> VM
VN --> VM
A --> DB
A -->|작업 등록| J
J -->|Proxmox API| PVE
A -->|도메인 설정| P
P -.->|vhost 적용| VN
PVE -.->|생성/제어| VM
IB -.->|템플릿 빌드| PVE
| 레포지토리 | 역할 |
|---|---|
| pickle-api | REST API와 프로비저닝 워커 (Spring Boot 4, Java 25, PostgreSQL 18, JobRunr) |
| pickle-console | 사용자·관리자 웹 콘솔 (React 19, TypeScript) |
| pickle-sshgw | SSH 게이트웨이와 웹 터미널 브리지 (sshpiperd, Go) |
| pickle-proxy-agent | nginx 리버스 프록시 제어 에이전트 (Go) |
| pickle-relay-agent | 오프캠퍼스 릴레이의 nftables DNAT 에이전트 (Go) |
| pickle-llm-gateway | 교내 LLM API 게이트웨이 (Go) |
| pickle-image-builder | 사용자 VM OS 이미지 빌드 레시피 (shell, virt-customize) |
| pickle-infra (비공개) | 인프라 프로비저닝 스크립트와 운영 런북 (shell) |
| pickle-infra-example | 프로비저닝·배포 스크립트와 런북 샘플 |
| pickle-secrets (비공개) | 호스트 시크릿 볼트 (git-crypt) |
| pickle-secrets-example | 볼트 레이아웃과 git-crypt 운용 절차 |



