This commit is contained in:
unknown
2026-03-21 07:26:38 +09:00
parent 166d94fca4
commit 40150cd66d
406 changed files with 34179 additions and 55658 deletions
+155 -18
View File
@@ -98,7 +98,7 @@ def index():
end_page = min(start_page + 9, total_pages)
# 4. 백업 폴더 목록
backup_dirs = [d for d in backup_dir.iterdir() if d.is_dir()]
backup_dirs = [d for d in backup_dir.iterdir() if d.is_dir() and d.name != "archive"]
backup_dirs.sort(key=lambda p: p.stat().st_mtime, reverse=True)
backup_page = int(request.args.get("backup_page", 1))
@@ -119,31 +119,109 @@ def index():
total_pages=total_pages,
start_page=start_page,
end_page=end_page,
backup_files=backup_files,
backup_files={}, # [DISABLED] Use server_info_files instead
total_backup_pages=total_backup_pages,
backup_page=backup_page,
server_info_files=backup_files, # Server Info (Paginated)
scripts=all_scripts, # 기존 리스트 호환
grouped_scripts=grouped_scripts_sorted, # 카테고리별 분류
xml_files=xml_files,
# [NEW] Repositories
mac_files=_get_file_info_list(Config.MAC_FOLDER),
guid_files=_get_file_info_list(Config.GUID_FOLDER),
gpu_files=_get_file_info_list(Config.GPU_FOLDER),
)
def _get_backup_info(folder_path):
"""
폴더 내의 하위 폴더들을 순회하며 파일 목록 정보를 반환 (백업/아카이브 용)
"""
path = Path(folder_path)
if not path.exists():
return {}
dirs = [d for d in path.iterdir() if d.is_dir() and d.name != "archive"]
dirs.sort(key=lambda p: p.stat().st_mtime, reverse=True)
result = {}
for d in dirs:
files = [f.name for f in d.iterdir() if f.is_file()]
result[d.name] = {"files": files, "count": len(files)}
return result
def _get_file_info_list(folder_path_str):
"""
폴더 내 파일들의 상세 정보(이름, 크기, 수정일)를 리스트로 반환
"""
path = Path(folder_path_str)
if not path.exists():
return []
files = []
try:
for f in path.glob("*"):
if f.is_file():
stat = f.stat()
files.append({
"name": f.name,
"size": stat.st_size,
"mtime": stat.st_mtime, # timestamp for sorting
"date": time.strftime('%Y-%m-%d %H:%M', time.localtime(stat.st_mtime))
})
except Exception as e:
logging.error(f"Error reading folder {folder_path_str}: {e}")
return []
# 기본 정렬: 최신순
return sorted(files, key=lambda x: x['mtime'], reverse=True)
@main_bp.route("/process_ips", methods=["POST"])
@login_required
def process_ips():
ips = request.form.get("ips")
selected_script = request.form.get("script")
selected_xml_file = request.form.get("xmlFile")
if not ips or not selected_script:
return jsonify({"error": "IP 주소와 스크립트를 모두 입력하세요."}), 400
job_type = request.form.get("job_type")
# 변수 초기화
selected_script = ""
profile_name = None
xml_file_path = None
if selected_script == "02-set_config.py" and selected_xml_file:
xml_path = Path(Config.XML_FOLDER) / selected_xml_file
if not xml_path.exists():
return jsonify({"error": "선택한 XML 파일이 존재하지 않습니다."}), 400
xml_file_path = str(xml_path)
slot_priority = request.form.get("slot_priority") # [NEW] 슬롯 우선순위
# 작업 유형에 따른 스크립트 및 프로파일 설정
if job_type in ["mac", "server_info", "guid", "gpu"]:
profile_name = request.form.get("profile")
if job_type in ["mac", "guid"] and not profile_name:
return jsonify({"error": "프로파일을 선택하세요."}), 400
if job_type == "mac":
selected_script = "unified/collect_mac.py"
elif job_type == "server_info":
selected_script = "unified/collect_server_info.py"
elif job_type == "guid":
selected_script = "unified/collect_guid.py"
elif job_type == "gpu":
selected_script = "unified/collect_gpu.py"
profile_name = None # GPU는 프로파일 불필요
else:
# Legacy 모드 (기존 스크립트 선택)
selected_script = request.form.get("script")
selected_xml_file = request.form.get("xmlFile")
if not selected_script:
return jsonify({"error": "스크립트를 선택하세요."}), 400
if selected_script == "02-set_config.py" and selected_xml_file:
xml_path = Path(Config.XML_FOLDER) / selected_xml_file
if not xml_path.exists():
return jsonify({"error": "선택한 XML 파일이 존재하지 않습니다."}), 400
xml_file_path = str(xml_path)
if not ips:
return jsonify({"error": "IP 주소를 입력하세요."}), 400
job_id = str(time.time())
session["job_id"] = job_id
@@ -157,11 +235,11 @@ def process_ips():
observer.start()
future = executor.submit(
process_ips_concurrently, ip_files, job_id, observer, selected_script, xml_file_path
process_ips_concurrently, ip_files, job_id, observer, selected_script, xml_file_path, profile_name, slot_priority
)
future.add_done_callback(lambda x: on_complete(job_id))
logging.info(f"[AJAX] 작업 시작: {job_id}, script: {selected_script}")
logging.info(f"[AJAX] 작업 시작: {job_id}, type: {job_type}, script: {selected_script}, profile: {profile_name}")
return jsonify({"job_id": job_id})
@@ -220,11 +298,21 @@ def delete_file(filename: str):
@main_bp.route("/download_zip", methods=["POST"])
@login_required
def download_zip():
zip_filename = request.form.get("zip_filename", "export")
zip_filename = request.form.get("zip_filename", "export").strip()
zip_path = Path(Config.TEMP_ZIP_FOLDER) / f"{zip_filename}.zip"
# 기본 대상은 스테이징 폴더 (IDRAC_INFO_FOLDER)
target_dir = Path(Config.IDRAC_INFO_FOLDER)
# 입력된 이름이 백업 폴더에 존재하는지 확인
if zip_filename:
possible_backup_path = Path(Config.BACKUP_FOLDER) / zip_filename
if possible_backup_path.exists() and possible_backup_path.is_dir():
target_dir = possible_backup_path
logging.info(f"백업 폴더 ZIP 다운로드 요청: {zip_filename}")
with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as zipf:
for file in Path(Config.IDRAC_INFO_FOLDER).glob("*"):
for file in target_dir.glob("*"):
if file.is_file():
zipf.write(file, arcname=file.name)
@@ -280,4 +368,53 @@ def move_backup_files():
except Exception as e:
logging.error(f"파일 이동 실패: {e}")
return jsonify({"success": False, "message": f"이동 중 오류 발생: {str(e)}"}), 500
return jsonify({"success": False, "message": f"이동 중 오류 발생: {str(e)}"}), 500
@main_bp.route("/delete_backup_folder/<folder_name>", methods=["POST"])
@login_required
def delete_backup_folder(folder_name: str):
folder_path = Path(Config.BACKUP_FOLDER) / folder_name
# 기본 폴더 보호
if folder_name == "archive":
flash("이 폴더는 삭제할 수 없습니다.", "warning")
return redirect(url_for("main.index"))
if folder_path.exists() and folder_path.is_dir():
try:
shutil.rmtree(folder_path)
flash(f"백업 폴더 '{folder_name}'가 삭제되었습니다.", "success")
logging.info(f"백업 폴더 삭제됨: {folder_name}")
except Exception as e:
logging.error(f"백업 폴더 삭제 오류: {e}")
flash("폴더 삭제 중 오류가 발생했습니다.", "danger")
else:
flash("폴더가 존재하지 않습니다.", "warning")
return redirect(url_for("main.index"))
@main_bp.route("/archive_backup_folder/<folder_name>", methods=["POST"])
@login_required
def archive_backup_folder(folder_name: str):
src_path = Path(Config.BACKUP_FOLDER) / folder_name
archive_root = Path(Config.ARCHIVE_FOLDER)
archive_root.mkdir(parents=True, exist_ok=True)
dst_path = archive_root / folder_name
if src_path.exists() and src_path.is_dir():
try:
# 이름 중복 시 처리 (timestamp 추가)
if dst_path.exists():
timestamp = int(time.time())
dst_path = archive_root / f"{folder_name}_{timestamp}"
shutil.move(str(src_path), str(dst_path))
flash(f"'{folder_name}' 폴더가 아카이브(archive)로 이동되었습니다.", "success")
logging.info(f"백업 아카이빙 성공: {folder_name} -> {dst_path}")
except Exception as e:
logging.error(f"백업 아카이빙 오류: {e}")
flash("폴더 이동 중 오류가 발생했습니다.", "danger")
else:
flash("폴더가 존재하지 않습니다.", "warning")
return redirect(url_for("main.index"))