fix: UX — start.sh 자동 LAN IP 감지 + topics/vote 레이아웃 조정

- start.sh: 호스트 LAN IP 자동 감지 후 PUBLIC_BASE_URL 세팅, 이제 QR이 172.x 컨테이너 IP 대신 실제 LAN IP를 가리킴
- docker-compose.yml: PUBLIC_BASE_URL 환경변수 pass-through 추가
- app.py: topics min-height 480→360, font-size/line-height 상향, vote counter를 QR 위로 이동, pct 계산 단순화
- README: 실행 섹션 교체 (start.sh 권장, raw/최소 방식 병기)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
th-kim0823
2026-04-27 21:07:11 +09:00
parent 02e67baa77
commit 02e186a860
4 changed files with 53 additions and 21 deletions

View File

@@ -12,7 +12,19 @@
5. **Stage 3 — 투표** (큰 화면에 QR, 모바일 → `/?mode=vote`) 5. **Stage 3 — 투표** (큰 화면에 QR, 모바일 → `/?mode=vote`)
6. **시상** (`/?mode=ceremony&token=mlops2026`) 6. **시상** (`/?mode=ceremony&token=mlops2026`)
## 실행 — Docker (한 줄) ## 실행 — Docker
```bash
./start.sh # LAN IP 자동 감지 + PUBLIC_BASE_URL 세팅 + 컨테이너 기동
```
또는 raw 방식 (LAN IP 수동):
```bash
PUBLIC_BASE_URL="http://192.168.0.47:8501" docker compose up -d --build
```
또는 최소 (LAN IP 없이, 어드민에서 나중에 override):
```bash ```bash
docker compose up -d --build docker compose up -d --build
@@ -24,7 +36,7 @@ docker compose up -d --build
docker compose down # 종료 (데이터 보존) docker compose down # 종료 (데이터 보존)
``` ```
**ADMIN_TOKEN**: `mlops2026` (외우기 쉬운 고정값). 변경하려면 `docker-compose.yml``ADMIN_TOKEN:` 값 직접 수정 후 `docker compose up -d --build`. **ADMIN_TOKEN**: `mlops2026` (외우기 쉬운 고정값). 변경하려면 `docker-compose.yml``ADMIN_TOKEN:` 값 직접 수정 후 재기동.
## URL ## URL

29
app.py
View File

@@ -58,16 +58,16 @@ SHOW_CSS = """
.show-cat-card { .show-cat-card {
border-radius: 14px; border-radius: 14px;
padding: 18px; padding: 18px;
min-height: 480px; min-height: 360px;
} }
.show-cat-T1 { background: linear-gradient(135deg, #ffb84d, #ff8c00); color: #222; } .show-cat-T1 { background: linear-gradient(135deg, #ffb84d, #ff8c00); color: #222; }
.show-cat-T2 { background: linear-gradient(135deg, #4dffd2, #2a8e7e); color: #1a1a1a; } .show-cat-T2 { background: linear-gradient(135deg, #4dffd2, #2a8e7e); color: #1a1a1a; }
.show-cat-T3 { background: linear-gradient(135deg, #ff4d6d, #b83a55); color: white; } .show-cat-T3 { background: linear-gradient(135deg, #ff4d6d, #b83a55); color: white; }
.show-cat-T4 { background: linear-gradient(135deg, #a64dff, #6a2eaf); color: white; } .show-cat-T4 { background: linear-gradient(135deg, #a64dff, #6a2eaf); color: white; }
.show-cat-title { font-size: 32px; font-weight: 800; margin-bottom: 4px; } .show-cat-title { font-size: 36px; font-weight: 800; margin-bottom: 4px; }
.show-cat-tagline { font-size: 16px; font-style: italic; margin-bottom: 4px; } .show-cat-tagline { font-size: 18px; font-style: italic; margin-bottom: 4px; }
.show-cat-tone { font-size: 14px; opacity: 0.85; margin-bottom: 12px; } .show-cat-tone { font-size: 14px; opacity: 0.85; margin-bottom: 12px; }
.show-cat-item { font-size: 17px; line-height: 1.45; padding: 4px 0; } .show-cat-item { font-size: 19px; line-height: 1.55; padding: 4px 0; }
.show-vote-counter { font-size: 96px; text-align: center; font-weight: 900; padding: 16px 0; } .show-vote-counter { font-size: 96px; text-align: center; font-weight: 900; padding: 16px 0; }
.show-vote-caption { font-size: 36px; text-align: center; color: #555; padding: 12px 0; } .show-vote-caption { font-size: 36px; text-align: center; color: #555; padding: 12px 0; }
</style> </style>
@@ -468,6 +468,17 @@ def render_stage_vote(data):
unsafe_allow_html=True, unsafe_allow_html=True,
) )
votes = data.get("votes", [])
total = len(data.get("people", []))
voted = len(votes)
pct = voted / total if total else 0
st.markdown(
f'<div class="show-vote-counter">{voted} / {total}</div>',
unsafe_allow_html=True,
)
st.progress(pct)
vote_url = compute_vote_url() vote_url = compute_vote_url()
qr_png = make_qr_png(vote_url) qr_png = make_qr_png(vote_url)
@@ -479,16 +490,6 @@ def render_stage_vote(data):
unsafe_allow_html=True, unsafe_allow_html=True,
) )
votes = data.get("votes", [])
total = len(data.get("people", []))
voted = len(votes)
pct = int(100 * voted / total) if total else 0
st.markdown(
f'<div class="show-vote-counter">{voted} / {total}</div>',
unsafe_allow_html=True,
)
st.progress(pct / 100 if total else 0)
def render_voter(): def render_voter():
if not can_accept_votes(load_data()): if not can_accept_votes(load_data()):

View File

@@ -6,10 +6,13 @@ services:
ports: ports:
- "${PORT:-8501}:8501" - "${PORT:-8501}:8501"
environment: environment:
ADMIN_TOKEN: ${ADMIN_TOKEN:-change-me} # 외우기 쉬운 고정 token. 변경하려면 여기 값만 수정.
DATA_PATH: /app/hackathon.json ADMIN_TOKEN: mlops2026
DATA_PATH: /app/data/hackathon.json
PUBLIC_BASE_URL: ${PUBLIC_BASE_URL:-}
volumes: volumes:
# 단일 데이터 파일. 호스트 ↔ 컨테이너 read-write mount. # 단일 데이터 디렉터리 마운트.
# 호스트에서 jq/vi 편집 가능, 앱이 votes 추가 시 그대로 반영. # 첫 부팅 시 entrypoint.sh가 assign_teams.py 실행하여 hackathon.json 시드.
- ./hackathon.json:/app/hackathon.json # 이후 부팅은 기존 파일 보존.
- ./data:/app/data
restart: unless-stopped restart: unless-stopped

16
start.sh Executable file
View File

@@ -0,0 +1,16 @@
#!/bin/bash
# 호스트 LAN IP 자동 감지 → PUBLIC_BASE_URL 세팅 → 컨테이너 기동
set -euo pipefail
cd "$(dirname "$0")"
LAN_IP=$(ipconfig getifaddr en0 2>/dev/null || ip -4 addr show 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v '^127\.' | head -1 || echo "")
PORT=${PORT:-8501}
if [[ -n "$LAN_IP" ]]; then
export PUBLIC_BASE_URL="http://${LAN_IP}:${PORT}"
echo "[start] PUBLIC_BASE_URL=${PUBLIC_BASE_URL} (자동 감지)"
else
echo "[start] LAN IP 감지 실패. 어드민에서 PUBLIC_BASE_URL 직접 설정 필요."
fi
exec docker compose up -d --build "$@"