diff --git a/FARMQ_ADMIN_IMPLEMENTATION_GUIDE.md b/FARMQ_ADMIN_IMPLEMENTATION_GUIDE.md new file mode 100644 index 0000000..d58bec5 --- /dev/null +++ b/FARMQ_ADMIN_IMPLEMENTATION_GUIDE.md @@ -0,0 +1,351 @@ +# FARMQ Admin 구현 가이드 및 설계 원칙 + +## 📋 개요 + +FARMQ Admin은 Headscale을 기반으로 한 100개 약국 네트워크 관리 시스템의 웹 인터페이스입니다. Headplane의 기능을 대체하면서 추가적인 약국 관리 기능을 제공하는 통합 관리 플랫폼입니다. + +## 🏗️ 아키텍처 설계 원칙 + +### 핵심 설계 철학 +``` +FARMQ Admin (Frontend/API) → Headscale CLI (Backend Engine) → Network Management +``` + +**FARMQ Admin**은 **프론트엔드 인터페이스**이고, **Headscale**은 **백엔드 엔진**으로 작동합니다. +모든 네트워크 관리 기능은 **Headscale CLI를 통해 제어**되며, FARMQ Admin은 이를 웹 인터페이스로 래핑합니다. + +### 계층 구조 +``` +┌─────────────────────────────────────┐ +│ FARMQ Admin │ ← 웹 UI, 약국 관리, 대시보드 +│ (Flask + Bootstrap + JS) │ +├─────────────────────────────────────┤ +│ API Layer │ ← REST API, CLI 인터페이스 +│ (Python subprocess calls) │ +├─────────────────────────────────────┤ +│ Headscale CLI │ ← 네트워크 관리 엔진 +│ (Docker containerized) │ +├─────────────────────────────────────┤ +│ Database Layer │ ← 이중 데이터베이스 +│ ┌─────────────┬─────────────────┐ │ +│ │ FARMQ DB │ Headscale DB │ │ +│ │ (약국정보) │ (노드정보) │ │ +│ └─────────────┴─────────────────┘ │ +└─────────────────────────────────────┘ +``` + +## 🔧 구현 방법론 + +### 1. CLI 기반 기능 구현 패턴 + +모든 Headscale 관련 기능은 다음 패턴을 따라 구현합니다: + +```python +# 표준 구현 패턴 +def headscale_function(): + try: + # Docker를 통해 Headscale CLI 실행 + result = subprocess.run( + ['docker', 'exec', 'headscale', 'headscale', 'command', 'args'], + capture_output=True, + text=True, + check=True + ) + + # JSON 출력 파싱 (가능한 경우) + if '-o json' in args: + data = json.loads(result.stdout) + return data + + return {'success': True, 'output': result.stdout} + + except subprocess.CalledProcessError as e: + return {'success': False, 'error': e.stderr} +``` + +#### 구현 예시들 + +**1. 실시간 온라인 상태 조회** +```python +def get_headscale_online_status() -> Dict[str, bool]: + """Headscale CLI를 통해 실시간 온라인 상태 조회""" + result = subprocess.run([ + 'docker', 'exec', 'headscale', + 'headscale', 'nodes', 'list', '-o', 'json' + ], capture_output=True, text=True, check=True) + + nodes_data = json.loads(result.stdout) + online_status = {} + + for node in nodes_data: + node_name = node.get('given_name') or node.get('name', '') + is_online = node.get('online', False) == True + online_status[node_name.lower()] = is_online + + return online_status +``` + +**2. 노드 삭제 기능** +```python +@app.route('/api/nodes//delete', methods=['DELETE']) +def api_delete_node(node_id): + """노드 삭제 API""" + result = subprocess.run([ + 'docker', 'exec', 'headscale', + 'headscale', 'nodes', 'delete', + '-i', str(node_id), '--force' + ], capture_output=True, text=True, check=True) + + return jsonify({ + 'success': True, + 'message': f'노드 {node_id}가 성공적으로 삭제되었습니다.' + }) +``` + +### 2. 이중 데이터베이스 전략 + +#### FARMQ Database (자체 관리) +- **목적**: 약국 정보, 관리자 데이터, 커스텀 설정 +- **특징**: 완전한 제어권, 외래키 제약 없음, 능동적 관리 +- **테이블**: `pharmacy_info`, `machine_profiles`, `monitoring_metrics`, `system_alerts` + +```python +# FARMQ DB - 약국 정보 관리 +class PharmacyInfo(FarmqBase): + __tablename__ = 'pharmacy_info' + + id = Column(Integer, primary_key=True) + pharmacy_name = Column(String(100), nullable=False) + business_number = Column(String(20), unique=True) + manager_name = Column(String(50)) + headscale_user_name = Column(String(50)) # Headscale과 연결점 +``` + +#### Headscale Database (읽기 전용) +- **목적**: 네트워크 노드 정보, 실시간 상태 +- **특징**: 읽기 전용 접근, Headscale이 관리 +- **활용**: 실시간 쿼리로 최신 상태 반영 + +```python +# Headscale DB - 읽기 전용 조회 +def get_dashboard_stats(): + headscale_session = get_headscale_session() + + # 실시간 노드 상태 + active_nodes = headscale_session.query(Node).filter( + Node.deleted_at.is_(None) + ).all() + + # CLI로 온라인 상태 확인 + online_status = get_headscale_online_status() + + # 두 데이터 소스 결합 + for node in active_nodes: + node_name = (node.given_name or '').lower() + is_online = online_status.get(node_name, False) +``` + +### 3. 실시간 동기화 전략 + +#### 기존 문제점 +- 타임아웃 기반 온라인 판단 (부정확) +- 캐시된 데이터 사용 (지연) +- Headplane과 상태 불일치 + +#### 해결책: 직접 CLI 조회 +```javascript +// 실시간 업데이트 (프론트엔드) +function updateStats() { + fetch('/api/dashboard/stats') + .then(response => response.json()) + .then(stats => { + // Headplane과 동일한 3/5 온라인 표시 + document.querySelector('[data-stat="online"]').textContent = stats.online_machines; + document.querySelector('[data-stat="offline"]').textContent = stats.offline_machines; + }); +} + +// 10초마다 업데이트 (Headplane보다 빠름) +setInterval(updateStats, 10000); +``` + +## 🚀 확장 가능한 기능 구현 로드맵 + +### Phase 1: 기본 Headplane 기능 대체 ✅ +- [x] 실시간 노드 상태 동기화 +- [x] 노드 삭제 기능 +- [x] 대시보드 통계 +- [x] 머신 목록 관리 + +### Phase 2: 고급 네트워크 관리 기능 +- [ ] **노드 이름 변경** + ```python + @app.route('/api/nodes//rename', methods=['POST']) + def api_rename_node(node_id): + new_name = request.json.get('new_name') + subprocess.run(['docker', 'exec', 'headscale', + 'headscale', 'nodes', 'rename', + '-i', str(node_id), new_name]) + ``` + +- [ ] **노드 만료/로그아웃** + ```python + @app.route('/api/nodes//expire', methods=['POST']) + def api_expire_node(node_id): + subprocess.run(['docker', 'exec', 'headscale', + 'headscale', 'nodes', 'expire', + '-i', str(node_id)]) + ``` + +- [ ] **라우트 관리** + ```python + @app.route('/api/nodes//routes', methods=['GET']) + def api_node_routes(node_id): + result = subprocess.run(['docker', 'exec', 'headscale', + 'headscale', 'nodes', 'list-routes', + '-i', str(node_id), '-o', 'json']) + ``` + +### Phase 3: 약국별 네트워크 관리 +- [ ] **약국별 사용자 그룹 관리** + ```python + @app.route('/api/pharmacy//users', methods=['GET']) + def api_pharmacy_users(pharmacy_id): + # 약국에 속한 Headscale 사용자 조회 + pharmacy = get_pharmacy_by_id(pharmacy_id) + subprocess.run(['docker', 'exec', 'headscale', + 'headscale', 'users', 'list', '-o', 'json']) + ``` + +- [ ] **약국별 PreAuth Key 생성** + ```python + @app.route('/api/pharmacy//preauth-key', methods=['POST']) + def api_create_pharmacy_preauth_key(pharmacy_id): + pharmacy = get_pharmacy_by_id(pharmacy_id) + user_name = pharmacy.headscale_user_name + + subprocess.run(['docker', 'exec', 'headscale', + 'headscale', 'preauthkeys', 'create', + '--user', user_name, '--reusable']) + ``` + +### Phase 4: 고급 모니터링 및 자동화 +- [ ] **실시간 네트워크 토폴로지** +- [ ] **자동 장애 감지 및 알림** +- [ ] **성능 메트릭 수집** +- [ ] **백업 및 복구 자동화** + +## 🎯 개발 가이드라인 + +### 1. 모든 새 기능은 CLI 우선 +```python +# ❌ 잘못된 접근 +def bad_implementation(): + # 직접 DB 조작 시도 + session.execute("UPDATE nodes SET ...") + +# ✅ 올바른 접근 +def good_implementation(): + # Headscale CLI 사용 + subprocess.run(['docker', 'exec', 'headscale', 'headscale', 'command']) +``` + +### 2. 에러 처리 표준화 +```python +def standard_error_handling(): + try: + result = subprocess.run(headscale_command, check=True) + return {'success': True, 'data': result.stdout} + except subprocess.CalledProcessError as e: + return {'success': False, 'error': e.stderr} + except Exception as e: + return {'success': False, 'error': f'서버 오류: {str(e)}'} +``` + +### 3. UI 일관성 유지 +```javascript +// 표준 삭제 확인 패턴 +function confirmDelete(itemType, itemName, deleteFunction) { + if (confirm(`정말로 ${itemType} "${itemName}"를 삭제하시겠습니까?\n\n삭제된 항목은 복구할 수 없습니다.`)) { + deleteFunction(); + } +} + +// 표준 피드백 패턴 +function showFeedback(message, type = 'info') { + showToast(message, type); + if (type === 'success') { + setTimeout(() => location.reload(), 1500); + } +} +``` + +## 🔍 디버깅 및 로깅 + +### CLI 호출 로깅 +```python +def log_cli_call(command, result): + print(f"🔧 Headscale CLI: {' '.join(command)}") + print(f"📤 Output: {result.stdout}") + if result.stderr: + print(f"⚠️ Error: {result.stderr}") +``` + +### 프론트엔드 상태 디버깅 +```javascript +// 개발 모드에서만 활성화 +if (window.location.hostname === 'localhost') { + console.log('🔍 FARMQ Admin Debug Mode'); + window.farmqDebug = { + showNodeStatus: () => console.table(onlineStatus), + refreshStats: updateStats, + testAPI: (endpoint) => fetch(endpoint).then(r => r.json()) + }; +} +``` + +## 📊 성능 최적화 + +### 1. CLI 호출 최적화 +- JSON 출력 사용으로 파싱 효율화 +- 불필요한 CLI 호출 최소화 +- 결과 캐싱 (단기간) + +### 2. 프론트엔드 최적화 +- 실시간 업데이트 주기 조정 (10초) +- 필요한 데이터만 요청 +- 사용자 상호작용 우선순위 + +## 🔐 보안 고려사항 + +### 1. CLI 명령 검증 +```python +def validate_node_id(node_id): + if not isinstance(node_id, int) or node_id <= 0: + raise ValueError("Invalid node ID") + return node_id + +def sanitize_command_args(args): + # 특수문자 및 인젝션 방지 + return [arg for arg in args if is_safe_arg(arg)] +``` + +### 2. 권한 관리 +- API 엔드포인트별 권한 확인 +- 약국별 데이터 접근 제한 +- 관리자/사용자 역할 구분 + +## 📝 결론 + +FARMQ Admin은 **Headscale CLI를 core engine으로 활용**하는 **웹 프론트엔드 래퍼**입니다. +이 접근 방식을 통해: + +1. **Headplane과 100% 호환성** 유지 +2. **실시간 정확한 상태** 반영 +3. **확장 가능한 구조** 제공 +4. **약국 특화 기능** 추가 가능 + +모든 새로운 기능은 이 원칙을 따라 구현하여 **일관성 있고 안정적인 시스템**을 구축합니다. + +--- +*Generated with [Claude Code](https://claude.ai/code) - FARMQ Admin Implementation Guide v1.0* \ No newline at end of file