diff --git a/apps/attendance/README.md b/apps/attendance/README.md index e2e1af0..7241728 100644 --- a/apps/attendance/README.md +++ b/apps/attendance/README.md @@ -36,17 +36,27 @@ KV에는 트랜잭션이 없고 다른 지역에서는 쓰기가 최대 약 60 | `GET /api/state` | Basic | 현재 인원, 회원별 오늘·이번 주 시간, 최근 기록 | | `POST /api/members` | Basic, 같은 출처 JSON | `{ "uid", "name" }` 회원 등록·이름 수정 | +## 대시보드 + +- 서버가 제공하는 단일 HTML 화면에 [Mobbin의 공개 업무용 대시보드 패턴](https://mobbin.com/explore/web/screens/analytics-dashboard)을 참고한 Material Design 3의 색상·서피스·타이포그래피를 적용합니다. 사이드바 없이 구성하며 외부 폰트나 UI 라이브러리를 불러오지 않습니다. +- 현재 인원과 입실 중인 회원의 체류 시간을 크게 표시하고, 전체 회원의 오늘·이번 주 체류 시간 합계와 퇴근 미기록 건수를 보여 줍니다. 회원별 표와 이번 주 최근 세션 최대 15건을 함께 제공합니다. +- 16:9 데스크톱 화면에서는 본문 스크롤 없이 주요 패널을 한 화면에 배치하고, 긴 목록은 각 패널의 이전·다음 버튼으로 넘깁니다. 화면 크기에 맞춰 표시 개수가 바뀌고 새로고침 후에도 선택한 페이지를 유지합니다. +- 테마 버튼은 자동(시스템 설정) → 라이트 → 다크 순서로 전환하며 선택을 브라우저에 저장합니다. 작은 화면에서는 카드가 재배치되고 표는 가로로 스크롤할 수 있습니다. +- 데이터는 10초마다 갱신하고 입실 중인 회원의 체류 시계는 매초 갱신합니다. 상태 조회·등록 요청은 응답 본문 수신까지 10초로 제한하며, 새로고침 버튼으로 즉시 갱신할 수 있습니다. 연결 실패 시 마지막 데이터임을 표시하고 체류 시계를 마지막 동기화 시각에 멈춥니다. 처음부터 불러오지 못한 경우 수치를 임의로 표시하지 않습니다. +- 미등록 카드는 화면에서 이름을 입력해 등록합니다. 주기적 갱신에도 입력과 포커스를 유지하며 등록 중·완료·오류 상태를 카드별로 표시합니다. 등록 전 태그는 입실 기록이 아니므로 등록 후 카드를 다시 태그해야 합니다. +- 회원 이름과 카드 정보는 `textContent`로만 출력합니다. 상태 조회와 등록은 같은 출처의 기존 인증을 사용하며 등록은 JSON 요청으로만 전송합니다. +- 배포는 BCSDLab 조직 Gateway를 요구하는 접근 정책을 전제로 합니다. 일반 소비자 WARP 접속만 허용하는 정책은 충분하지 않습니다. Worker의 모든 도메인·미리보기 우회 경로를 보호하기 전에는 공개 경로를 활성화하지 않습니다. + ## 배포 ```sh -pnpm --filter @koin/attendance exec wrangler secret put DEVICE_TOKEN -pnpm --filter @koin/attendance exec wrangler secret put DASHBOARD_PASSWORD +# 기존 DEVICE_TOKEN·DASHBOARD_PASSWORD는 유지합니다. 비밀값 설정·회전은 별도 운영 절차입니다. pnpm deploy:attendance ``` -KV 네임스페이스는 첫 배포 때 wrangler가 만들고 `id`를 기록합니다. 기록된 `wrangler.jsonc`를 커밋합니다. 토큰은 충분히 긴 무작위 값을 사용합니다(`openssl rand -hex 32`). +`wrangler.jsonc`의 `ATTENDANCE`는 기존 운영 KV 네임스페이스를 고정합니다. 새 네임스페이스를 만들거나 id를 바꾸면 기존 데이터와 분리되므로 배포 중 치환하지 않습니다. 새 토큰이 필요할 때만 충분히 긴 무작위 값으로 별도 설정합니다(`openssl rand -hex 32`). -벽걸이 화면은 `https://:@/` 로 열면 인증 창 없이 표시됩니다. +벽걸이 화면은 대시보드 경로(`/`)를 열고 Basic 인증 창에서 `DASHBOARD_PASSWORD`로 인증합니다. 비밀번호를 URL에 넣지 않습니다. ## 로컬 실행 @@ -98,12 +108,27 @@ Raspberry Pi 3B 기준입니다. `READER`로 리더 종류를 고릅니다. ### 설치 ```sh -sudo raspi-config nonint do_spi 0 # rc522: SPI 활성화(/dev/spidev0.0) -sudo apt install python3-spidev python3-rpi.gpio # rc522. hid는 python3-evdev -sudo install -D pi/reader.py /opt/attendance/reader.py -sudo install -m 600 /dev/null /etc/attendance-reader.env # ATTENDANCE_URL=…, DEVICE_TOKEN=…, READER=rc522 -sudo install pi/attendance-reader.service /etc/systemd/system/ -sudo systemctl enable --now attendance-reader +sudo raspi-config nonint do_spi 0 # rc522: 부팅 시 SPI 활성화 +sudo dtparam spi=on # rc522: 재부팅 없이 현재 부팅에도 적용 +sudo apt install python3-spidev python3-rpi-lgpio # Raspberry Pi OS Trixie. hid는 python3-evdev +sudo install -D -o root -g root -m 755 pi/reader.py /opt/attendance/reader.py +sudo install -o root -g root -m 644 pi/attendance-reader.service /etc/systemd/system/ +sudo systemctl daemon-reload +``` + +RC522는 `/dev/spidev0.0`과 위 배선을 사용합니다. SPI 장치 파일이 있다는 것만으로 모듈 연결이 확인되지는 않습니다. 시작 시 `RC522 응답 없음`이면 3.3V 전원·GND·CE0·SCLK·MOSI·MISO·RST 배선을 확인합니다. USB HID는 `/dev/input/by-id/*-event-kbd`에 실제 리더가 있어야 합니다. + +실제 Worker 주소와 해당 Worker의 `DEVICE_TOKEN`을 받은 뒤에만 런타임 설정을 작성하고 서비스를 시작합니다. 진단 중이거나 모듈·카드 인식이 확인되지 않았다면 전송 루프를 켜지 않습니다. 네트워크 접근 정책이 있는 배포는 Pi에서도 허용된 경로로 Worker에 접근할 수 있어야 합니다. + +```sh +sudo touch /etc/attendance-reader.env +sudo chown root:root /etc/attendance-reader.env +sudo chmod 600 /etc/attendance-reader.env +sudoedit /etc/attendance-reader.env +# ATTENDANCE_URL=<실제 Worker 기본 주소> +# DEVICE_TOKEN=<해당 Worker의 실제 토큰> +# READER=rc522 (USB HID는 hid; 필요하면 READER_DEVICE도 지정) +sudo systemctl enable --now attendance-reader.service ``` - 오프라인이면 태그를 `/var/lib/attendance/queue.jsonl`에 보관하고 30초마다 순서대로 재전송합니다. 태그 시각(`at`)은 파이 시계 기준이므로 NTP 동기화가 필요합니다. diff --git a/apps/attendance/src/dashboard.ts b/apps/attendance/src/dashboard.ts index 11f6b90..519ce51 100644 --- a/apps/attendance/src/dashboard.ts +++ b/apps/attendance/src/dashboard.ts @@ -8,74 +8,435 @@ export const DASHBOARD_HTML = /* html */ ` + BCSD 출근부 -
-
-

BCSD 출근부

-
+ +
+
+
+
+

CLUBROOM / ATTENDANCE

출입 현황

+
+
+
한국 표준시 · KST
+ + +
- -
-

지금 있는 사람

-
- -
-
-

이번 주 체류 시간 (월요일 시작, KST)

- - - -
이름상태오늘 첫 입실오늘이번 주퇴근 미기록
-
- -
-

최근 기록

- - - -
이름입실퇴실체류
-
-
+
+ +
+
+

지금 동아리방에는

등록 회원 확인 중

+
—명
+

함께 몰입하는 시간, 이곳에서.

+
+
+

지금 있는 사람

입실 현황
+

동아리방 현황을 불러오고 있습니다.

+
+ +
+
+
+

오늘 함께한 시간

—

전체 회원 체류 시간 합계

+

이번 주 함께한 시간

—

월요일부터 지금까지 · KST

+

이번 주 퇴근 미기록

—

미기록 세션은 시간 합계에서 제외

+
+
+

이번 주의 발자취

회원별 입실 현황과 오늘 · 이번 주 체류 시간

확인 중
+

회원별 기록을 불러오고 있습니다.

+ + +

퇴실 태그 없이 6시간이 지난 세션은 ‘퇴근 미기록’으로 표시하며 체류 시간에서 제외합니다.

+
+
+
+

최근 기록

이번 주 기록 중 최근 입실 순 · 최대 15건

확인 중
+

최근 입실 · 퇴실 기록을 불러오고 있습니다.

+ + +
+
+

미등록 카드

카드의 주인을 연결해 주세요.

확인 중
+

새로 태그한 카드를 확인하고 있습니다.

+
+ +

이름은 최대 30자입니다. 등록 후 카드를 다시 태그하면 입실이 기록됩니다. 미등록 카드는 7일 뒤 자동 삭제됩니다.

+
+
+
첫 데이터를 불러오는 중

아직 동기화되지 않음 · 10초마다 갱신

+

+
+

BCSD LAB · 작은 태그 하나로 이어지는 우리의 하루

모든 시각은 KST · 저장소 반영에 최대 약 60초 소요될 수 있습니다.

+ + + diff --git a/apps/attendance/wrangler.jsonc b/apps/attendance/wrangler.jsonc index ad17b78..cbc04d7 100644 --- a/apps/attendance/wrangler.jsonc +++ b/apps/attendance/wrangler.jsonc @@ -3,9 +3,9 @@ "name": "bcsd-attendance", "main": "src/index.ts", "compatibility_date": "2026-10-29", - "workers_dev": true, + "workers_dev": false, "preview_urls": false, "observability": { "enabled": true }, - // id는 첫 배포 때 wrangler가 네임스페이스를 만들고 기록합니다. - "kv_namespaces": [{ "binding": "ATTENDANCE" }], + // 기존 운영 네임스페이스를 재사용합니다. 새 네임스페이스로 치환하지 않습니다. + "kv_namespaces": [{ "binding": "ATTENDANCE", "id": "2cc47a111f334c4ea99deb2435263e00" }], }