""" RAG 검색 챗봇 웹 애플리케이션 - 장치 관리 API 라우트 정의 (API 경로 수정됨) """ import logging import requests from flask import request, jsonify # 로거 가져오기 logger = logging.getLogger(__name__) def register_device_routes(app, login_required, DEVICE_SERVER_URL): """Flask 애플리케이션에 장치 관리 관련 라우트 등록""" # 사용자 지정 장치 서버 URL 변수 custom_device_url = None # URL 설정 함수 def get_device_url(): # 사용자 지정 URL이 있으면 사용, 없으면 환경변수 값 사용 return custom_device_url or DEVICE_SERVER_URL @app.route('/api/device/connect', methods=['POST']) @login_required def connect_device_server(): """사용자 지정 장치 서버 URL 연결 API""" nonlocal custom_device_url # 상위 스코프의 변수 참조 try: # 요청에서 URL 가져오기 request_data = request.get_json() if not request_data or 'url' not in request_data: logger.error("URL이 제공되지 않았습니다.") return jsonify({ "success": False, "error": "URL이 제공되지 않았습니다." }), 400 # Bad Request new_url = request_data['url'].strip() if not new_url: logger.error("URL이 비어 있습니다.") return jsonify({ "success": False, "error": "URL이 비어 있습니다." }), 400 # Bad Request # URL 설정 logger.info(f"사용자 지정 장치 서버 URL 설정: {new_url}") custom_device_url = new_url # 설정된 URL로 상태 확인 시도 try: api_path = "/api/status" logger.info(f"장치 서버 상태 확인 요청: {custom_device_url}{api_path}") response = requests.get(f"{custom_device_url}{api_path}", timeout=5) if response.status_code == 200: try: data = response.json() logger.info(f"사용자 지정 장치 서버 연결 성공. 응답 데이터: {data}") return jsonify({ "success": True, "message": "장치 서버 연결에 성공했습니다.", "server_status": data.get("status", "정상") }) except requests.exceptions.JSONDecodeError: logger.error("장치 서버 응답 JSON 파싱 실패") return jsonify({ "success": False, "error": "장치 서버로부터 유효하지 않은 JSON 응답을 받았습니다." }), 502 # Bad Gateway else: error_message = f"장치 서버 응답 오류: {response.status_code}" try: error_detail = response.json().get("error", response.text) error_message += f" - {error_detail}" except Exception: error_message += f" - {response.text}" logger.warning(error_message) custom_device_url = None # 연결 실패 시 URL 초기화 return jsonify({ "success": False, "error": error_message }), 502 # Bad Gateway except requests.exceptions.Timeout: logger.error(f"장치 서버 연결 타임아웃 ({custom_device_url})") custom_device_url = None # 연결 실패 시 URL 초기화 return jsonify({ "success": False, "error": "장치 서버 연결 타임아웃. 서버 응답이 너무 느립니다." }), 504 # Gateway Timeout except requests.exceptions.ConnectionError: logger.error(f"장치 관리 서버 연결 실패 ({custom_device_url})") custom_device_url = None # 연결 실패 시 URL 초기화 return jsonify({ "success": False, "error": "장치 관리 서버에 연결할 수 없습니다. 서버가 실행 중인지, URL이 정확한지 확인해주세요." }), 502 # Bad Gateway except Exception as e: logger.error(f"장치 서버 연결 중 예상치 못한 오류 발생: {e}", exc_info=True) custom_device_url = None # 연결 실패 시 URL 초기화 return jsonify({ "success": False, "error": f"장치 서버 연결 중 오류 발생: {str(e)}" }), 500 # Internal Server Error except Exception as e: logger.error(f"/api/device/connect 처리 중 내부 서버 오류: {e}", exc_info=True) return jsonify({ "success": False, "error": f"내부 서버 오류 발생: {str(e)}" }), 500 # Internal Server Error @app.route('/api/device/status', methods=['GET']) @login_required def device_status(): """장치 관리 서버의 상태를 확인하는 API 엔드포인트""" try: # requests 라이브러리는 함수 내에서 임포트할 필요 없음 (상단 임포트 사용) # json, jsonify도 Flask에서 이미 임포트됨 (상단 임포트 사용) # 연결 타임아웃 설정 timeout = 5 # 5초로 타임아웃 설정 try: # 장치 서버 상태 확인 - 경로: /api/status current_device_url = get_device_url() api_path = "/api/status" logger.info(f"장치 서버 상태 확인 요청: {current_device_url}{api_path}") response = requests.get(f"{current_device_url}{api_path}", timeout=timeout) # 응답 상태 코드 및 내용 로깅 (디버깅 강화) logger.debug(f"장치 서버 응답 상태 코드: {response.status_code}") try: logger.debug(f"장치 서버 응답 내용: {response.text[:200]}...") # 너무 길면 잘라서 로깅 except Exception: logger.debug("장치 서버 응답 내용 로깅 실패 (텍스트 형식 아님?)") if response.status_code == 200: # 성공 시 응답 데이터 구조 확인 및 로깅 try: data = response.json() logger.info(f"장치 서버 상태 확인 성공. 응답 데이터: {data}") return jsonify({"success": True, "status": "connected", "data": data}) except requests.exceptions.JSONDecodeError: logger.error("장치 서버 응답 JSON 파싱 실패") return jsonify({ "success": False, "error": "장치 서버로부터 유효하지 않은 JSON 응답을 받았습니다." }), 502 # Bad Gateway (업스트림 서버 오류) else: # 실패 시 오류 메시지 포함 로깅 error_message = f"장치 서버 응답 오류: {response.status_code}" try: # 서버에서 오류 메시지를 json으로 보내는 경우 포함 error_detail = response.json().get("error", response.text) error_message += f" - {error_detail}" except Exception: error_message += f" - {response.text}" # JSON 파싱 실패 시 원본 텍스트 logger.warning(error_message) # 경고 레벨로 로깅 return jsonify({ "success": False, "error": error_message }), 502 # Bad Gateway except requests.exceptions.Timeout: logger.error(f"장치 서버 연결 타임아웃 ({DEVICE_SERVER_URL})") return jsonify({ "success": False, "error": "장치 서버 연결 타임아웃. 서버 응답이 너무 느립니다." }), 504 # Gateway Timeout except requests.exceptions.ConnectionError: current_device_url = get_device_url() logger.error(f"장치 관리 서버 연결 실패 ({current_device_url})") return jsonify({ "success": False, "error": "장치 관리 서버에 연결할 수 없습니다. 서버가 실행 중인지, URL이 정확한지 확인해주세요." }), 502 # Bad Gateway (연결 실패도 업스트림 문제로 간주) except Exception as e: logger.error(f"장치 서버 연결 중 예상치 못한 오류 발생: {e}", exc_info=True) # 상세 스택 트레이스 로깅 return jsonify({ "success": False, "error": f"장치 서버 연결 중 오류 발생: {str(e)}" }), 500 # Internal Server Error (Flask 앱 내부 또는 requests 라이브러리 문제 가능성) except Exception as e: # 이 try-except 블록은 /api/device/status 라우트 자체의 내부 오류 처리 logger.error(f"/api/device/status 처리 중 내부 서버 오류: {e}", exc_info=True) return jsonify({ "success": False, "error": f"내부 서버 오류 발생: {str(e)}" }), 500 # Internal Server Error @app.route('/api/device/list', methods=['GET']) @login_required def device_list(): """장치 목록 조회 API""" logger.info("장치 목록 조회 요청") try: current_device_url = get_device_url() api_path = "/api/devices" logger.info(f"장치 목록 조회 요청: {current_device_url}{api_path}") response = requests.get(f"{current_device_url}{api_path}", timeout=5) logger.debug(f"장치 목록 응답 상태 코드: {response.status_code}") if response.status_code == 200: try: data = response.json() devices = data.get("devices", []) logger.info(f"장치 목록 조회 성공: {len(devices)}개 장치") return jsonify({ "success": True, "devices": devices }) except requests.exceptions.JSONDecodeError: logger.error("장치 목록 응답 JSON 파싱 실패") return jsonify({"success": False, "error": "장치 서버로부터 유효하지 않은 JSON 응답"}), 502 else: error_message = f"장치 목록 조회 실패: {response.status_code}" try: error_message += f" - {response.json().get('error', response.text)}" except Exception: error_message += f" - {response.text}" logger.warning(error_message) return jsonify({ "success": False, "error": error_message }), 502 except requests.exceptions.Timeout: logger.error("장치 목록 조회 시간 초과") return jsonify({ "success": False, "error": "장치 목록 조회 시간이 초과되었습니다." }), 504 except requests.exceptions.ConnectionError: logger.error("장치 관리 서버 연결 실패") return jsonify({ "success": False, "error": "장치 관리 서버에 연결할 수 없습니다. 서버가 실행 중인지 확인해주세요." }), 503 # Service Unavailable except Exception as e: logger.error(f"장치 목록 조회 중 오류 발생: {e}", exc_info=True) return jsonify({ "success": False, "error": f"장치 목록 조회 중 오류 발생: {str(e)}" }), 500 @app.route('/api/device/programs', methods=['GET']) @login_required def device_programs(): """실행 가능한 프로그램 목록 조회 API""" logger.info("프로그램 목록 조회 요청") try: current_device_url = get_device_url() api_path = "/api/programs" logger.info(f"프로그램 목록 조회 요청: {current_device_url}{api_path}") response = requests.get(f"{current_device_url}{api_path}", timeout=5) logger.debug(f"프로그램 목록 응답 상태 코드: {response.status_code}") if response.status_code == 200: try: data = response.json() programs = data.get("programs", []) logger.info(f"프로그램 목록 조회 성공: {len(programs)}개 프로그램") return jsonify({ "success": True, "programs": programs }) except requests.exceptions.JSONDecodeError: logger.error("프로그램 목록 응답 JSON 파싱 실패") return jsonify({"success": False, "error": "장치 서버로부터 유효하지 않은 JSON 응답"}), 502 else: error_message = f"프로그램 목록 조회 실패: {response.status_code}" try: error_message += f" - {response.json().get('error', response.text)}" except Exception: error_message += f" - {response.text}" logger.warning(error_message) return jsonify({ "success": False, "error": error_message }), 502 except requests.exceptions.Timeout: logger.error("프로그램 목록 조회 시간 초과") return jsonify({ "success": False, "error": "프로그램 목록 조회 시간이 초과되었습니다." }), 504 except requests.exceptions.ConnectionError: logger.error("장치 관리 서버 연결 실패") return jsonify({ "success": False, "error": "장치 관리 서버에 연결할 수 없습니다. 서버가 실행 중인지 확인해주세요." }), 503 except Exception as e: logger.error(f"프로그램 목록 조회 중 오류 발생: {e}", exc_info=True) return jsonify({ "success": False, "error": f"프로그램 목록 조회 중 오류 발생: {str(e)}" }), 500 @app.route('/api/device/programs//execute', methods=['POST']) @login_required def execute_program(program_id): """프로그램 실행 API""" logger.info(f"프로그램 실행 요청: {program_id}") try: current_device_url = get_device_url() api_path = f"/api/programs/{program_id}/execute" logger.info(f"프로그램 실행 요청: {current_device_url}{api_path}") response = requests.post( f"{current_device_url}{api_path}", json={}, # 필요시 여기에 파라미터 추가 가능 timeout=10 # 프로그램 실행에는 더 긴 시간 부여 ) logger.debug(f"프로그램 실행 응답 상태 코드: {response.status_code}") if response.status_code == 200: try: data = response.json() success = data.get("success", False) message = data.get("message", "") logger.info(f"프로그램 실행 응답: {success}, {message}") return jsonify(data) # 서버 응답 그대로 반환 except requests.exceptions.JSONDecodeError: logger.error("프로그램 실행 응답 JSON 파싱 실패") # 성공(200)했지만 응답이 비정상적인 경우 return jsonify({ "success": False, # 파싱 실패했으므로 False로 간주 "error": "프로그램 실행 서버로부터 유효하지 않은 JSON 응답" }), 502 else: error_message = f"프로그램 실행 요청 실패: {response.status_code}" try: error_message += f" - {response.json().get('error', response.text)}" except Exception: error_message += f" - {response.text}" logger.warning(error_message) return jsonify({ "success": False, "error": error_message }), 502 # 또는 response.status_code 를 그대로 반환하는 것도 고려 except requests.exceptions.Timeout: logger.error("프로그램 실행 요청 시간 초과") return jsonify({ "success": False, "error": "프로그램 실행 요청 시간이 초과되었습니다." }), 504 except requests.exceptions.ConnectionError: logger.error("장치 관리 서버 연결 실패") return jsonify({ "success": False, "error": "장치 관리 서버에 연결할 수 없습니다. 서버가 실행 중인지 확인해주세요." }), 503 except Exception as e: logger.error(f"프로그램 실행 중 오류 발생: {e}", exc_info=True) return jsonify({ "success": False, "error": f"프로그램 실행 중 오류 발생: {str(e)}" }), 500