BLA
BLA는 오픈소스 Blue Defense MCP와 CLI로, 소프트웨어 자산을 자동 AI 리버스 엔지니어링으로부터 보호합니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
작업 승인 → 게이트 안전검증 → 작업이력·자격 관리를 하나로 묶은 산업 현장 출입통제 웹입니다. 2026 미래내일 일경험 지원사업 — AI·로봇 안전제어 솔루션 챌린지 4기 팀 Ctrl+Z · 이 저장소는 웹(프론트엔드 + 백엔드 + DB) 범위입니다. 키오스크는 NVIDIA Jetson Orin에서 별도 구현되며, 이 저장소와는 API 계약으로만 만납니다. 종이 작업허가서와 단순 출입카드는 "확인"에서 멈추고 출입 자체에는 개입하지 않습니다. 이 시스템은 세 가지를 시스템이 강제합니다. 1. 팀장 승인 없이는 키오스크에 작업이 뜨지 않습니다 → 입장 시도 자체가 불가능 2. 자격이 만료되면 검증 단계에 들어가기 전에 차단됩니다 3. 모든 인증·검증·승인이 자동으로 기록되어 사후 추적이 됩니다 /dashboard/sessions/[id] 는 관제에서 작업 제목을 눌러 들어가는 세션 상세입니다. 젯슨 문열림 시뮬레이션이 들어갈 자리를 비워둔 틀입니다.README 전문 보기 ↗
작업 승인 → 게이트 안전검증 → 작업이력·자격 관리를 하나로 묶은 산업 현장 출입통제 웹입니다.
2026 미래내일 일경험 지원사업 — AI·로봇 안전제어 솔루션 챌린지 4기 팀 Ctrl+Z · 이 저장소는 웹(프론트엔드 + 백엔드 + DB) 범위입니다. 키오스크는 NVIDIA Jetson Orin에서 별도 구현되며, 이 저장소와는 API 계약으로만 만납니다.
종이 작업허가서와 단순 출입카드는 "확인"에서 멈추고 출입 자체에는 개입하지 않습니다. 이 시스템은 세 가지를 시스템이 강제합니다.
| 코드 | 경로 | 화면 | 누가 보나 |
|---|---|---|---|
| W1 | /login | 로그인 | 전원 |
| — | /signup | 가입 신청 | 계정 없는 사람 (인증 없이 열림) |
| W2 | /requests/new | 작업 승인 요청 | 작업자 · 팀장 |
| W3 | /approvals | 팀장 승인함 | 팀장 |
| W4 | /dashboard | 관제 대시보드 | 전원 |
| W5 | /me | 마이페이지 | 작업자 · 팀장 |
| — | /notes | 작업장별 특이사항 | 전원 |
| — | /admin | 계정 관리 | 안전관리자 |
/dashboard/sessions/[id] 는 관제에서 작업 제목을 눌러 들어가는 세션 상세입니다.
아래쪽에 게이트 3D 시뮬레이션(Unity WebGL, center-web/public/safety-gate-3d/)이 붙어 있어,
서버가 판정한 검증·문 열림·입장을 그림으로 다시 보여줍니다. 판정은 서버가 하고 3D 는 재생만 합니다.
| 구분 | 선택 | 이유 |
|---|---|---|
| 프레임워크 | Next.js 16 (App Router) | Route Handler가 백엔드 역할. 서버와 화면을 한 프로젝트에 |
| 언어 | TypeScript · React 19 | 권한·상태를 타입으로 좁혀 잘못된 값을 못 만들게 |
| 스타일 | CSS Modules + 디자인 토큰 | 경계선을 border가 아니라 inset box-shadow로 그리는 스펙이라 유틸리티 클래스와 안 맞음 |
| DB | Firebase Firestore | 실시간 구독이 관제 화면에 그대로 맞음 |
| 인증 | Firebase Auth + 커스텀 클레임 | 역할을 토큰에 박아 서버가 검증 |
| 실시간 | Server-Sent Events | 방향이 서버→클라이언트 한쪽뿐이라 WebSocket 불필요 |
클라이언트는 DB를 직접 만지지 않습니다.
Firestore 보안 규칙은 전부 잠겨 있고(center-web/firestore.rules), 서버만 Admin SDK로 접근합니다.
규칙에 로직을 흩어놓으면 나중에 무엇이 무엇을 막는지 추적이 안 됩니다.
권한은 토큰에서 읽습니다.
역할은 Firebase Auth 커스텀 클레임에 있고, 서버가 verifyIdToken으로 확인한 값만 씁니다.
요청 본문의 역할은 믿지 않습니다.
파생값은 저장하지 않습니다.
자격의 유효/임박/만료는 expiresOn에서, 연차는 hiredOn에서 매번 계산합니다.
상태를 컬럼으로 굳히면 배치 없이는 반드시 썩습니다.
젯슨은 관찰만 보고하고 상태를 주장하지 않습니다.
카드 UID를 09:04:12에 읽었다 ✅ / 인원 충족됐으니 문 열어 ❌
상태 전이와 화면 문구는 서버가 계산해 응답으로 내려줍니다.
계약 전문은 center-web/lib/gate-contract.ts 한 파일입니다.
cd center-web
npm install
cp .env.local.example .env.local # 값을 채워야 동작합니다
npm run dev
.env.local은 저장소에 없습니다. Firebase 콘솔에서 직접 받아야 합니다 —
웹 앱 config 6개(프로젝트 설정 → 내 앱)와 서비스 계정 키(프로젝트 설정 → 서비스 계정).
어떤 값이 어디서 오는지는 .env.local.example 주석에 적어뒀습니다.
node --env-file=.env.local scripts/check-firebase.mjs # 연결 확인
node --env-file=.env.local scripts/seed.mjs # 컬렉션 생성 + 시드 + 계정 발급
node --env-file=.env.local scripts/reset-requests.mjs # 시연 전 초기화
seed.mjs는 여러 번 돌려도 안전합니다. 진행중 세션은 실행 시점 기준으로 다시 만들어지므로,
시연 직전에 한 번 돌리면 관제 화면의 경과 시간이 그럴듯해집니다.
seed.mjs를 돌리면 Auth 계정이 다시 만들어져 열어둔 브라우저는 로그아웃됩니다. 다시 로그인하면 됩니다.
아이디 = 사번, 초기 비밀번호 = 사번 + 1234
| 사번 | 이름 | 역할 | 비밀번호 |
|---|---|---|---|
| 202533690 | 김병오 | 팀장 (승인자) | 2025336901234 |
| 202533795 | 윤지윤 | 안전관리자 | 2025337951234 |
| 202533872 | 정천호 | 작업자 | 2025338721234 |
| 202633671 | 박상하 | 작업자 | 2026336711234 |
사번은 scripts/seed-data.mjs의 TEAM 한 곳에서만 정의합니다 —
세션·요청·출입기록이 전부 사번을 참조해서, 흩어놓으면 하나 바꿀 때 참조를 빠뜨립니다.
main에 push하면 Vercel이 자동으로 재배포합니다. 따로 할 일이 없습니다.
ctrlz-2026/Ctrl-Center-web (조직 ctrlz-2026)center-web환경변수는 저장소가 아니라 Vercel 대시보드에 있습니다
(Settings → Environment Variables). 로컬 .env.local과 같은 10개입니다.
저장소가 Public인 이유 — Vercel Hobby 플랜은 조직의 Private 저장소를 배포하지 못합니다. 비밀키는
.env.local에만 있고 그 파일은.gitignore에 있어 올라가지 않으므로 공개로 돌렸습니다.
git clone https://github.com/ctrlz-2026/Ctrl-Center-web.git
cd Ctrl-Center-web/center-web
npm install
그다음 .env.local을 직접 넣어야 합니다 — 저장소에 없습니다.
가장 빠른 방법은 쓰던 컴퓨터의 center-web/.env.local을 그대로 복사하는 것이고,
없으면 Firebase 콘솔에서 다시 받습니다(.env.local.example 주석 참고).
node --env-file=.env.local scripts/check-firebase.mjs # 연결부터 확인
npm run dev
Firestore는 클라우드에 있으므로 시드를 다시 돌릴 필요가 없습니다. 데이터가 이미 들어 있고, 컴퓨터가 바뀌어도 그대로 보입니다.
center-web/
app/
(auth)/login/ W1
(auth)/signup/ 가입 신청 (인증 없이 열림)
(app)/requests/new/ W2
(app)/approvals/ W3
(app)/dashboard/ W4
sessions/[id]/ 세션 상세 — 젯슨 시뮬레이션 자리
(app)/me/ W5
(app)/notes/ 작업장별 특이사항
(app)/admin/ 계정 관리 (안전관리자)
icon.svg 파비콘 (ZC 키캡 로고)
api/
me/ · requests/ 웹용 (Firebase ID 토큰)
notes/ 작업장별 특이사항
signup/ 가입 신청 — 유일하게 인증 없이 열린 경로
admin/accounts/ 계정 목록 · 역할/비번/활성
[empNo]/profile/ 자격 · 사원증 · 얼굴등록 · 작업배정
[empNo]/face-template/ 얼굴 등록 상태 · 벡터 파일 올리기 · 삭제
admin/signups/[id]/ 가입 승인·거절
stream/requests/ SSE 실시간
gate/events/ 젯슨 수신구 (기기 키)
gate/[gateId]/ 번들 · 선택된 작업 · 얼굴 등록(사번 확인 · 완료 알림) · 얼굴 벡터 내려받기 (기기 키)
kiosk/[gateId]/ 키오스크 화면용 (작업 선택 · 사원증 태그 · 상태 · 종료)
components/ Button · Badge · Card · DataTable · Field · Logo · TopNav …
lib/
firebase/ client · admin · queries · dashboard · auth-guard · user
gate-contract.ts 젯슨과 맞추는 계약 (이 파일만 넘기면 됨)
types.ts 권한 함수가 여기 한 곳에만 있음
scripts/ 시드 · 초기화 · 연결확인
docs/
backend-design.md 스키마 · API · 실시간 설계 · 결정 기록
screenshots/ 화면 캡처
고칠 때 어디를 보나
| 바꾸고 싶은 것 | 파일 |
|---|---|
| 누가 무엇을 할 수 있나 | lib/types.ts (권한 함수가 여기에만 있음) |
| 색·모서리·여백 | app/tokens/center.css (개별 화면 CSS 말고 여기) |
| 사번·시드 데이터 | scripts/seed-data.mjs |
| 젯슨과의 약속 | lib/gate-contract.ts |
| 왜 그렇게 했는지 | docs/backend-design.md |
동작하는 것 — 로그인·권한 3역할, 회원가입과 관리자 승인, 계정 관리(비밀번호 초기화·역할 변경·비활성화), 자격/사원증/얼굴등록/작업배정 관리, 승인 워크플로우 (Firestore 영속), 실시간 관제, 작업장별 특이사항, 개인별 출입기록, 젯슨 수신구(중복 제거·형식 검증). 전부 배포돼 있습니다.
결정해서 굳힌 것 (배경은 docs/backend-design.md)
selfApproved로 기록에 남깁니다아직인 것
ppeItems.yoloClass가 helmet 외 전부 null.
모델이 못 잡는 항목을 작업코드 필수 보호구에 넣으면 착용하고 서 있어도 영원히 통과 못 합니다.employeeCards가 TEMP-* UID, pending: true
(관리자 콘솔에서 실물 UID를 넣을 수 있게는 해뒀습니다)A~H, J)를 씁니다. 팀장 「작업 기준 설계」
표는 A-3·B-7 형식이지만 뒤 숫자가 인원·시간·위험도 어느 것과도 무관한
임의값이라, 내용은 표 그대로 두고 표기만 줄였습니다(팀 결정 2026-09-01).
대조표는 scripts/seed-data.mjs 의 workCodes 주석 참고. I 는 숫자 1 로
읽혀 건너뜁니다