from __future__ import annotations import os import sys import shutil import subprocess import logging from pathlib import Path from flask import Blueprint, request, redirect, url_for, flash, jsonify, send_file from flask_login import login_required from config import Config utils_bp = Blueprint("utils", __name__) def register_util_routes(app): app.register_blueprint(utils_bp) @utils_bp.route("/move_mac_files", methods=["POST"]) @login_required def move_mac_files(): src = Path(Config.IDRAC_INFO_FOLDER) dst = Path(Config.MAC_FOLDER) dst.mkdir(parents=True, exist_ok=True) moved = 0 errors = [] try: for file in src.iterdir(): if not file.is_file(): continue try: # 파일이 존재하는지 확인 if not file.exists(): errors.append(f"{file.name}: 파일이 존재하지 않음") continue # 대상 파일이 이미 존재하는 경우 건너뛰기 target = dst / file.name if target.exists(): logging.warning(f"⚠️ 파일이 이미 존재하여 건너뜀: {file.name}") continue shutil.move(str(file), str(target)) moved += 1 except Exception as e: error_msg = f"{file.name}: {str(e)}" errors.append(error_msg) logging.error(f"❌ 파일 이동 실패: {error_msg}") # 결과 로깅 if moved > 0: logging.info(f"✅ MAC 파일 이동 완료 ({moved}개)") if errors: logging.warning(f"⚠️ 일부 파일 이동 실패: {errors}") # 하나라도 성공하면 success: true 반환 return jsonify({ "success": True, "moved": moved, "errors": errors if errors else None }) except Exception as e: logging.error(f"❌ MAC 이동 중 치명적 오류: {e}") return jsonify({"success": False, "error": str(e)}) @utils_bp.route("/move_guid_files", methods=["POST"]) @login_required def move_guid_files(): src = Path(Config.IDRAC_INFO_FOLDER) dst = Path(Config.GUID_FOLDER) dst.mkdir(parents=True, exist_ok=True) moved = 0 errors = [] try: for file in src.iterdir(): if not file.is_file(): continue try: # 파일이 존재하는지 확인 if not file.exists(): errors.append(f"{file.name}: 파일이 존재하지 않음") continue # 대상 파일이 이미 존재하는 경우 건너뛰기 target = dst / file.name if target.exists(): logging.warning(f"⚠️ 파일이 이미 존재하여 건너뜀: {file.name}") continue shutil.move(str(file), str(target)) moved += 1 except Exception as e: error_msg = f"{file.name}: {str(e)}" errors.append(error_msg) logging.error(f"❌ 파일 이동 실패: {error_msg}") # 결과 메시지 if moved > 0: flash(f"GUID 파일이 성공적으로 이동되었습니다. ({moved}개)", "success") logging.info(f"✅ GUID 파일 이동 완료 ({moved}개)") if errors: logging.warning(f"⚠️ 일부 파일 이동 실패: {errors}") flash(f"일부 파일 이동 실패: {len(errors)}개", "warning") except Exception as e: logging.error(f"❌ GUID 이동 오류: {e}") flash(f"오류 발생: {e}", "danger") return redirect(url_for("main.index")) @utils_bp.route("/move_gpu_files", methods=["POST"]) @login_required def move_gpu_files(): """ data/idrac_info → data/gpu_serial 로 GPU 시리얼 텍스트 파일 이동 """ src = Path(Config.IDRAC_INFO_FOLDER) # 예: data/idrac_info dst = Path(Config.GPU_FOLDER) # 예: data/gpu_serial dst.mkdir(parents=True, exist_ok=True) moved = 0 errors = [] try: for file in src.iterdir(): if not file.is_file(): continue # GPU 관련 텍스트 파일만 이동 (GUID 등 제외) if not file.name.lower().endswith(".txt"): continue try: # 파일 존재 확인 if not file.exists(): errors.append(f"{file.name}: 파일이 존재하지 않음") continue # 대상 파일이 이미 존재하면 스킵 target = dst / file.name if target.exists(): logging.warning(f"⚠️ 이미 존재하여 건너뜀: {file.name}") continue # 파일 이동 shutil.move(str(file), str(target)) moved += 1 except Exception as e: error_msg = f"{file.name}: {str(e)}" errors.append(error_msg) logging.error(f"❌ GPU 파일 이동 실패: {error_msg}") # 결과 메시지 if moved > 0: flash(f"GPU 시리얼 파일이 성공적으로 이동되었습니다. ({moved}개)", "success") logging.info(f"✅ GPU 파일 이동 완료 ({moved}개)") if errors: logging.warning(f"⚠️ 일부 파일 이동 실패: {errors}") flash(f"일부 GPU 파일 이동 실패: {len(errors)}개", "warning") except Exception as e: logging.error(f"❌ GPU 이동 오류: {e}") flash(f"오류 발생: {e}", "danger") return redirect(url_for("main.index")) @utils_bp.route("/update_server_list", methods=["POST"]) @login_required def update_server_list(): content = request.form.get("server_list_content") if not content: flash("내용을 입력하세요.", "warning") return redirect(url_for("main.index")) path = Path(Config.SERVER_LIST_FOLDER) / "server_list.txt" try: path.write_text(content, encoding="utf-8") result = subprocess.run( [sys.executable, str(Path(Config.SERVER_LIST_FOLDER) / "excel.py")], capture_output=True, text=True, check=True, cwd=str(Path(Config.SERVER_LIST_FOLDER)), timeout=300, ) logging.info(f"서버 리스트 스크립트 실행 결과: {result.stdout}") flash("서버 리스트가 업데이트되었습니다.", "success") except subprocess.CalledProcessError as e: logging.error(f"서버 리스트 스크립트 오류: {e.stderr}") flash(f"스크립트 실행 실패: {e.stderr}", "danger") except Exception as e: logging.error(f"서버 리스트 처리 오류: {e}") flash(f"서버 리스트 처리 중 오류 발생: {e}", "danger") return redirect(url_for("main.index")) @utils_bp.route("/update_guid_list", methods=["POST"]) @login_required def update_guid_list(): content = request.form.get("server_list_content") if not content: flash("내용을 입력하세요.", "warning") return redirect(url_for("main.index")) path = Path(Config.SERVER_LIST_FOLDER) / "guid_list.txt" try: path.write_text(content, encoding="utf-8") result = subprocess.run( [sys.executable, str(Path(Config.SERVER_LIST_FOLDER) / "GUIDtxtT0Execl.py")], capture_output=True, text=True, check=True, cwd=str(Path(Config.SERVER_LIST_FOLDER)), timeout=300, ) logging.info(f"GUID 리스트 스크립트 실행 결과: {result.stdout}") flash("GUID 리스트가 업데이트되었습니다.", "success") except subprocess.CalledProcessError as e: logging.error(f"GUID 리스트 스크립트 오류: {e.stderr}") flash(f"스크립트 실행 실패: {e.stderr}", "danger") except Exception as e: logging.error(f"GUID 리스트 처리 오류: {e}") flash(f"GUID 리스트 처리 중 오류 발생: {e}", "danger") return redirect(url_for("main.index")) @utils_bp.route("/update_gpu_list", methods=["POST"]) @login_required def update_gpu_list(): """ GPU 시리얼용 리스트(gpu_serial_list.txt)를 갱신하고 Excel을 생성합니다. - form name="gpu_list_content" 로 내용 전달 (S/T 목록 라인별) - txt_to_excel.py --preset gpu --list-file """ content = request.form.get("server_list_content") if not content: flash("내용을 입력하세요.", "warning") return redirect(url_for("main.index")) server_list_dir = Path(Config.SERVER_LIST_FOLDER) list_path = server_list_dir / "gpu_list.txt" # txt_to_excel.py는 server_list 폴더에 둔다고 가정 (위치 다르면 경로만 수정) script_path = server_list_dir / "GPUTOExecl.py" try: # 1) gpu_serial_list.txt 저장 list_path.write_text(content, encoding="utf-8") # 2) 엑셀 생성 실행 (GPU 프리셋) cmd = [ sys.executable, str(script_path), "--preset", "gpu", "--list-file", str(list_path), ] result = subprocess.run( cmd, capture_output=True, text=True, check=True, cwd=str(server_list_dir), # data/server_list 기준 실행 timeout=300, ) logging.info(f"[GPU] 리스트 스크립트 실행 STDOUT:\n{result.stdout}") if result.stderr: logging.warning(f"[GPU] 리스트 스크립트 STDERR:\n{result.stderr}") flash("GPU 리스트가 업데이트되었습니다.", "success") except subprocess.CalledProcessError as e: logging.error(f"[GPU] 스크립트 오류: {e.stderr}") flash(f"스크립트 실행 실패: {e.stderr}", "danger") except Exception as e: logging.error(f"[GPU] 처리 오류: {e}") flash(f"GPU 리스트 처리 중 오류 발생: {e}", "danger") return redirect(url_for("main.index")) logging.info(f"엑셀 파일 다운로드: {path}") return send_file(str(path), as_attachment=True, download_name="mac_info.xlsx") @utils_bp.route("/scan_network", methods=["POST"]) @login_required def scan_network(): """ 지정된 IP 범위(Start ~ End)에 대해 Ping 테스트를 수행하고 응답이 있는 IP 목록을 반환합니다. """ import ipaddress import platform import concurrent.futures data = request.get_json() start_ip_str = data.get('start_ip') end_ip_str = data.get('end_ip') if not start_ip_str or not end_ip_str: return jsonify({"success": False, "error": "시작 IP와 종료 IP를 모두 입력해주세요."}), 400 try: start_ip = ipaddress.IPv4Address(start_ip_str) end_ip = ipaddress.IPv4Address(end_ip_str) if start_ip > end_ip: return jsonify({"success": False, "error": "시작 IP가 종료 IP보다 큽니다."}), 400 # IP 개수 제한 (너무 많은 스캔 방지, 예: C클래스 2개 분량 512개) if int(end_ip) - int(start_ip) > 512: return jsonify({"success": False, "error": "스캔 범위가 너무 넓습니다. (최대 512개)"}), 400 except ValueError: return jsonify({"success": False, "error": "유효하지 않은 IP 주소 형식입니다."}), 400 # Ping 함수 정의 def ping_ip(ip_obj): ip = str(ip_obj) param = '-n' if platform.system().lower() == 'windows' else '-c' timeout_param = '-w' if platform.system().lower() == 'windows' else '-W' # Windows: -w 200 (ms), Linux: -W 1 (s) timeout_val = '200' if platform.system().lower() == 'windows' else '1' command = ['ping', param, '1', timeout_param, timeout_val, ip] try: # shell=False로 보안 강화, stdout/stderr 무시 res = subprocess.run(command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) return ip if res.returncode == 0 else None except Exception: return None active_ips = [] # IP 리스트 생성 # ipaddress 모듈을 사용하여 범위 내 IP 생성 target_ips = [] temp_ip = start_ip while temp_ip <= end_ip: target_ips.append(temp_ip) temp_ip += 1 # 병렬 처리 (최대 50 쓰레드) with concurrent.futures.ThreadPoolExecutor(max_workers=50) as executor: results = executor.map(ping_ip, target_ips) # 결과 수집 (None 제외) active_ips = [ip for ip in results if ip is not None] return jsonify({ "success": True, "active_ips": active_ips, "count": len(active_ips), "message": f"스캔 완료: {len(active_ips)}개의 활성 IP 발견" })