# 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*