diff --git a/farmq-admin/app.py b/farmq-admin/app.py index 56116e5..b39ca82 100644 --- a/farmq-admin/app.py +++ b/farmq-admin/app.py @@ -9,7 +9,7 @@ import os from datetime import datetime from config import config from utils.database_new import ( - init_database, get_session, close_session, + init_databases, get_farmq_session, get_dashboard_stats, get_all_pharmacies_with_stats, get_all_machines_with_details, get_machine_detail, get_pharmacy_detail, get_active_alerts, sync_machines_from_headscale, sync_users_from_headscale @@ -24,12 +24,14 @@ def create_app(config_name=None): app.config.from_object(config[config_name]) # 데이터베이스 초기화 - init_database(app.config['SQLALCHEMY_DATABASE_URI']) + init_databases( + headscale_db_uri='sqlite:////srv/headscale-setup/data/db.sqlite', + farmq_db_uri='sqlite:///farmq.db' + ) - # 요청 종료 후 세션 정리 - @app.teardown_appcontext - def cleanup(error): - close_session() + # 데이터 동기화 실행 + sync_users_from_headscale() + sync_machines_from_headscale() # 메인 대시보드 @app.route('/') diff --git a/farmq-admin/utils/database_new.py b/farmq-admin/utils/database_new.py index e2ee7e3..a816e40 100644 --- a/farmq-admin/utils/database_new.py +++ b/farmq-admin/utils/database_new.py @@ -206,34 +206,69 @@ def get_pharmacy_detail(pharmacy_id: int) -> Optional[Dict[str, Any]]: # ========================================== def get_all_machines_with_details() -> List[Dict[str, Any]]: - """모든 머신 상세 정보 조회""" + """모든 머신 상세 정보 조회 - Headscale Node 데이터 사용""" + headscale_session = get_headscale_session() farmq_session = get_farmq_session() try: - machines = farmq_session.query(MachineProfile).filter( - MachineProfile.status == 'active' + # Headscale에서 모든 노드 조회 + nodes = headscale_session.query(Node).filter( + Node.deleted_at.is_(None) ).all() result = [] - for machine in machines: - machine_data = machine.to_dict() + for node in nodes: + # 기본 머신 정보 + machine_data = { + 'id': node.id, + 'hostname': node.hostname, + 'machine_name': node.hostname, # 표시용 이름 + 'tailscale_ip': node.ipv4, + 'ipv6': node.ipv6, + 'headscale_user_name': node.user.name if node.user else '미지정', + 'user_id': node.user_id, + 'last_seen': node.last_seen, + 'created_at': node.created_at, + 'updated_at': node.updated_at + } - # 약국 정보 추가 - if machine.pharmacy_id: + # 온라인 상태 확인 (24시간 timeout) + if node.last_seen: + try: + from datetime import timezone + # node.last_seen이 timezone-aware인지 확인 + if node.last_seen.tzinfo is not None: + cutoff_time = datetime.now(timezone.utc) - timedelta(hours=24) + else: + cutoff_time = datetime.now() - timedelta(hours=24) + machine_data['is_online'] = node.last_seen > cutoff_time + except Exception as e: + # 타임존 비교 에러가 발생하면 기본적으로 온라인으로 가정 + print(f"Timezone comparison error for {node.hostname}: {e}") + machine_data['is_online'] = True + else: + machine_data['is_online'] = False + + # 사용자 이름으로 약국 정보 찾기 + machine_data['pharmacy'] = None + if node.user: pharmacy = farmq_session.query(PharmacyInfo).filter( - PharmacyInfo.id == machine.pharmacy_id + PharmacyInfo.headscale_user_name == node.user.name ).first() if pharmacy: - machine_data['pharmacy'] = pharmacy.to_dict() + machine_data['pharmacy'] = { + 'id': pharmacy.id, + 'pharmacy_name': pharmacy.pharmacy_name, + 'manager_name': pharmacy.manager_name, + 'business_number': pharmacy.business_number + } - # 최근 모니터링 데이터 - latest_metrics = farmq_session.query(MonitoringMetrics).filter( - MonitoringMetrics.machine_profile_id == machine.id - ).order_by(desc(MonitoringMetrics.collected_at)).first() + # 마지막 접속 시간을 사람이 읽기 쉬운 형태로 + machine_data['last_seen_humanized'] = humanize_datetime(node.last_seen) - if latest_metrics: - machine_data['latest_metrics'] = latest_metrics.to_dict() - machine_data['alerts'] = latest_metrics.get_alert_status() + # 하드웨어 사양 및 모니터링 데이터는 나중에 추가 예정 + machine_data['specs'] = None + machine_data['latest_monitoring'] = None result.append(machine_data) @@ -241,6 +276,7 @@ def get_all_machines_with_details() -> List[Dict[str, Any]]: finally: close_session(farmq_session) + close_session(headscale_session) def get_machine_detail(machine_id: int) -> Optional[Dict[str, Any]]: """머신 상세 정보 조회""" @@ -498,6 +534,25 @@ def get_average_cpu_temperature() -> float: stats = get_dashboard_stats() return stats['avg_cpu_temp'] +def humanize_datetime(dt) -> str: + """datetime을 사람이 읽기 쉬운 형태로 변환""" + if not dt: + return '알 수 없음' + + try: + import humanize + # 한국어 설정 시도 + try: + humanize.i18n.activate('ko_KR') + except: + pass + return humanize.naturaltime(dt) + except ImportError: + # humanize가 없으면 기본 형식 사용 + if isinstance(dt, str): + return dt + return dt.strftime('%Y-%m-%d %H:%M:%S') + def get_machine_with_details(machine_id: int) -> Optional[Dict[str, Any]]: """머신 상세 정보 조회 (하위 호환성)""" return get_machine_detail(machine_id)