update
This commit is contained in:
@@ -8,6 +8,8 @@ from .xml import register_xml_routes
|
||||
from .utilities import register_util_routes
|
||||
from .file_view import register_file_view
|
||||
from .jobs import register_jobs_routes
|
||||
from .idrac_routes import register_idrac_routes
|
||||
from .catalog_sync import catalog_bp
|
||||
|
||||
def register_routes(app: Flask, socketio=None) -> None:
|
||||
"""블루프린트 일괄 등록. socketio는 main 라우트에서만 사용."""
|
||||
@@ -18,4 +20,6 @@ def register_routes(app: Flask, socketio=None) -> None:
|
||||
register_xml_routes(app)
|
||||
register_util_routes(app)
|
||||
register_file_view(app)
|
||||
register_jobs_routes(app)
|
||||
register_jobs_routes(app)
|
||||
register_idrac_routes(app)
|
||||
app.register_blueprint(catalog_bp)
|
||||
|
||||
BIN
backend/routes/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
backend/routes/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
backend/routes/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/admin.cpython-311.pyc
Normal file
BIN
backend/routes/__pycache__/admin.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/admin.cpython-312.pyc
Normal file
BIN
backend/routes/__pycache__/admin.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/auth.cpython-311.pyc
Normal file
BIN
backend/routes/__pycache__/auth.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/auth.cpython-312.pyc
Normal file
BIN
backend/routes/__pycache__/auth.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/catalog_sync.cpython-311.pyc
Normal file
BIN
backend/routes/__pycache__/catalog_sync.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/catalog_sync.cpython-312.pyc
Normal file
BIN
backend/routes/__pycache__/catalog_sync.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/file_view.cpython-311.pyc
Normal file
BIN
backend/routes/__pycache__/file_view.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/file_view.cpython-312.pyc
Normal file
BIN
backend/routes/__pycache__/file_view.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/home.cpython-311.pyc
Normal file
BIN
backend/routes/__pycache__/home.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/home.cpython-312.pyc
Normal file
BIN
backend/routes/__pycache__/home.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/idrac_routes.cpython-311.pyc
Normal file
BIN
backend/routes/__pycache__/idrac_routes.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/idrac_routes.cpython-312.pyc
Normal file
BIN
backend/routes/__pycache__/idrac_routes.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/idrac_routes.cpython-313.pyc
Normal file
BIN
backend/routes/__pycache__/idrac_routes.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/jobs.cpython-311.pyc
Normal file
BIN
backend/routes/__pycache__/jobs.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/jobs.cpython-312.pyc
Normal file
BIN
backend/routes/__pycache__/jobs.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/jobs.cpython-313.pyc
Normal file
BIN
backend/routes/__pycache__/jobs.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/main.cpython-311.pyc
Normal file
BIN
backend/routes/__pycache__/main.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/main.cpython-312.pyc
Normal file
BIN
backend/routes/__pycache__/main.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/utilities.cpython-311.pyc
Normal file
BIN
backend/routes/__pycache__/utilities.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/utilities.cpython-312.pyc
Normal file
BIN
backend/routes/__pycache__/utilities.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
backend/routes/__pycache__/xml.cpython-311.pyc
Normal file
BIN
backend/routes/__pycache__/xml.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/xml.cpython-312.pyc
Normal file
BIN
backend/routes/__pycache__/xml.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
23
backend/routes/catalog_sync.py
Normal file
23
backend/routes/catalog_sync.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# backend/routes/catalog_sync.py
|
||||
from flask import Blueprint, jsonify, request
|
||||
from backend.services.dell_catalog_sync import sync_dell_catalog
|
||||
|
||||
catalog_bp = Blueprint("catalog", __name__, url_prefix="/catalog")
|
||||
|
||||
@catalog_bp.route("/sync", methods=["POST"])
|
||||
def sync_catalog():
|
||||
"""Dell Catalog 버전 정보를 동기화 (CSRF 보호 유지)"""
|
||||
try:
|
||||
data = request.get_json(silent=True) or {}
|
||||
model = data.get("model", "PowerEdge R750")
|
||||
|
||||
sync_dell_catalog(model)
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": f"{model} 동기화 완료"
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"message": f"오류: {str(e)}"
|
||||
})
|
||||
1094
backend/routes/idrac_routes.py
Normal file
1094
backend/routes/idrac_routes.py
Normal file
File diff suppressed because it is too large
Load Diff
669
backend/routes/idrac_routes_base.py
Normal file
669
backend/routes/idrac_routes_base.py
Normal file
@@ -0,0 +1,669 @@
|
||||
"""
|
||||
Dell iDRAC 멀티 서버 펌웨어 관리 라우트
|
||||
backend/routes/idrac_routes.py
|
||||
- CSRF 보호 제외 추가
|
||||
"""
|
||||
|
||||
from flask import Blueprint, render_template, request, jsonify, current_app
|
||||
from werkzeug.utils import secure_filename
|
||||
import os
|
||||
from datetime import datetime
|
||||
from backend.services.idrac_redfish_client import DellRedfishClient
|
||||
from backend.models.idrac_server import IdracServer, db
|
||||
from flask_socketio import emit
|
||||
import threading
|
||||
|
||||
# Blueprint 생성
|
||||
idrac_bp = Blueprint('idrac', __name__, url_prefix='/idrac')
|
||||
|
||||
# 설정
|
||||
UPLOAD_FOLDER = 'uploads/firmware'
|
||||
ALLOWED_EXTENSIONS = {'exe', 'bin'}
|
||||
MAX_FILE_SIZE = 500 * 1024 * 1024 # 500MB
|
||||
|
||||
def allowed_file(filename):
|
||||
"""허용된 파일 형식 확인"""
|
||||
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
||||
|
||||
# ========================================
|
||||
# 메인 페이지
|
||||
# ========================================
|
||||
|
||||
@idrac_bp.route('/')
|
||||
def index():
|
||||
"""iDRAC 멀티 서버 관리 메인 페이지"""
|
||||
return render_template('idrac_firmware.html')
|
||||
|
||||
# ========================================
|
||||
# 서버 관리 API
|
||||
# ========================================
|
||||
|
||||
@idrac_bp.route('/api/servers', methods=['GET'])
|
||||
def get_servers():
|
||||
"""등록된 서버 목록 조회"""
|
||||
try:
|
||||
group = request.args.get('group') # 그룹 필터
|
||||
|
||||
query = IdracServer.query.filter_by(is_active=True)
|
||||
if group and group != 'all':
|
||||
query = query.filter_by(group_name=group)
|
||||
|
||||
servers = query.order_by(IdracServer.name).all()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'servers': [s.to_dict() for s in servers]
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'오류: {str(e)}'
|
||||
})
|
||||
|
||||
@idrac_bp.route('/api/servers', methods=['POST'])
|
||||
def add_server():
|
||||
"""서버 추가"""
|
||||
try:
|
||||
data = request.json
|
||||
|
||||
# 필수 필드 확인
|
||||
if not all([data.get('name'), data.get('ip_address'), data.get('password')]):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '필수 필드를 모두 입력하세요 (서버명, IP, 비밀번호)'
|
||||
})
|
||||
|
||||
# 중복 IP 확인
|
||||
existing = IdracServer.query.filter_by(ip_address=data['ip_address']).first()
|
||||
if existing:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'이미 등록된 IP입니다: {data["ip_address"]}'
|
||||
})
|
||||
|
||||
# 서버 생성
|
||||
server = IdracServer(
|
||||
name=data['name'],
|
||||
ip_address=data['ip_address'],
|
||||
username=data.get('username', 'root'),
|
||||
password=data['password'],
|
||||
group_name=data.get('group_name'),
|
||||
location=data.get('location'),
|
||||
model=data.get('model'),
|
||||
notes=data.get('notes')
|
||||
)
|
||||
|
||||
db.session.add(server)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'서버 {server.name} 추가 완료',
|
||||
'server': server.to_dict()
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'오류: {str(e)}'
|
||||
})
|
||||
|
||||
@idrac_bp.route('/api/servers/<int:server_id>', methods=['PUT'])
|
||||
def update_server(server_id):
|
||||
"""서버 정보 수정"""
|
||||
try:
|
||||
server = IdracServer.query.get(server_id)
|
||||
if not server:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '서버를 찾을 수 없습니다'
|
||||
})
|
||||
|
||||
data = request.json
|
||||
|
||||
# 업데이트
|
||||
if 'name' in data:
|
||||
server.name = data['name']
|
||||
if 'ip_address' in data:
|
||||
server.ip_address = data['ip_address']
|
||||
if 'username' in data:
|
||||
server.username = data['username']
|
||||
if 'password' in data:
|
||||
server.password = data['password']
|
||||
if 'group_name' in data:
|
||||
server.group_name = data['group_name']
|
||||
if 'location' in data:
|
||||
server.location = data['location']
|
||||
if 'model' in data:
|
||||
server.model = data['model']
|
||||
if 'notes' in data:
|
||||
server.notes = data['notes']
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '서버 정보 수정 완료',
|
||||
'server': server.to_dict()
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'오류: {str(e)}'
|
||||
})
|
||||
|
||||
@idrac_bp.route('/api/servers/<int:server_id>', methods=['DELETE'])
|
||||
def delete_server(server_id):
|
||||
"""서버 삭제 (소프트 삭제)"""
|
||||
try:
|
||||
server = IdracServer.query.get(server_id)
|
||||
if not server:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '서버를 찾을 수 없습니다'
|
||||
})
|
||||
|
||||
server.is_active = False
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'서버 {server.name} 삭제 완료'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'오류: {str(e)}'
|
||||
})
|
||||
|
||||
@idrac_bp.route('/api/groups', methods=['GET'])
|
||||
def get_groups():
|
||||
"""등록된 그룹 목록"""
|
||||
try:
|
||||
groups = db.session.query(IdracServer.group_name)\
|
||||
.filter(IdracServer.is_active == True)\
|
||||
.filter(IdracServer.group_name.isnot(None))\
|
||||
.distinct()\
|
||||
.all()
|
||||
|
||||
group_list = [g[0] for g in groups if g[0]]
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'groups': group_list
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'오류: {str(e)}'
|
||||
})
|
||||
|
||||
# ========================================
|
||||
# 연결 및 상태 확인 API
|
||||
# ========================================
|
||||
|
||||
@idrac_bp.route('/api/servers/<int:server_id>/test', methods=['POST'])
|
||||
def test_connection(server_id):
|
||||
"""단일 서버 연결 테스트"""
|
||||
try:
|
||||
server = IdracServer.query.get(server_id)
|
||||
if not server:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '서버를 찾을 수 없습니다'
|
||||
})
|
||||
|
||||
client = DellRedfishClient(server.ip_address, server.username, server.password)
|
||||
|
||||
if client.check_connection():
|
||||
server.status = 'online'
|
||||
server.last_connected = datetime.utcnow()
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'{server.name} 연결 성공'
|
||||
})
|
||||
else:
|
||||
server.status = 'offline'
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'{server.name} 연결 실패'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'오류: {str(e)}'
|
||||
})
|
||||
|
||||
@idrac_bp.route('/api/servers/test-multi', methods=['POST'])
|
||||
def test_connections_multi():
|
||||
"""다중 서버 일괄 연결 테스트"""
|
||||
try:
|
||||
data = request.json
|
||||
server_ids = data.get('server_ids', [])
|
||||
|
||||
if not server_ids:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '서버를 선택하세요'
|
||||
})
|
||||
|
||||
results = []
|
||||
|
||||
for server_id in server_ids:
|
||||
server = IdracServer.query.get(server_id)
|
||||
if not server:
|
||||
continue
|
||||
|
||||
try:
|
||||
client = DellRedfishClient(server.ip_address, server.username, server.password)
|
||||
|
||||
if client.check_connection():
|
||||
server.status = 'online'
|
||||
server.last_connected = datetime.utcnow()
|
||||
results.append({
|
||||
'server_id': server.id,
|
||||
'server_name': server.name,
|
||||
'success': True,
|
||||
'message': '연결 성공'
|
||||
})
|
||||
else:
|
||||
server.status = 'offline'
|
||||
results.append({
|
||||
'server_id': server.id,
|
||||
'server_name': server.name,
|
||||
'success': False,
|
||||
'message': '연결 실패'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
server.status = 'offline'
|
||||
results.append({
|
||||
'server_id': server.id,
|
||||
'server_name': server.name,
|
||||
'success': False,
|
||||
'message': str(e)
|
||||
})
|
||||
|
||||
db.session.commit()
|
||||
|
||||
success_count = sum(1 for r in results if r['success'])
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'results': results,
|
||||
'summary': {
|
||||
'total': len(results),
|
||||
'success': success_count,
|
||||
'failed': len(results) - success_count
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'오류: {str(e)}'
|
||||
})
|
||||
|
||||
@idrac_bp.route('/api/servers/<int:server_id>/firmware', methods=['GET'])
|
||||
def get_server_firmware(server_id):
|
||||
"""단일 서버 펌웨어 버전 조회"""
|
||||
try:
|
||||
server = IdracServer.query.get(server_id)
|
||||
if not server:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '서버를 찾을 수 없습니다'
|
||||
})
|
||||
|
||||
client = DellRedfishClient(server.ip_address, server.username, server.password)
|
||||
inventory = client.get_firmware_inventory()
|
||||
|
||||
# BIOS 버전 업데이트
|
||||
for item in inventory:
|
||||
if 'BIOS' in item.get('Name', ''):
|
||||
server.current_bios = item.get('Version')
|
||||
break
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': inventory
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'오류: {str(e)}'
|
||||
})
|
||||
|
||||
# ========================================
|
||||
# 멀티 서버 펌웨어 업로드 API
|
||||
# ========================================
|
||||
|
||||
@idrac_bp.route('/api/upload-multi', methods=['POST'])
|
||||
def upload_multi():
|
||||
"""다중 서버에 펌웨어 일괄 업로드"""
|
||||
try:
|
||||
if 'file' not in request.files:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '파일이 없습니다'
|
||||
})
|
||||
|
||||
file = request.files['file']
|
||||
server_ids_str = request.form.get('server_ids')
|
||||
|
||||
if not server_ids_str:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '서버를 선택하세요'
|
||||
})
|
||||
|
||||
server_ids = [int(x) for x in server_ids_str.split(',')]
|
||||
|
||||
if file.filename == '':
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '파일이 선택되지 않았습니다'
|
||||
})
|
||||
|
||||
if not allowed_file(file.filename):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '허용되지 않은 파일 형식입니다 (.exe, .bin만 가능)'
|
||||
})
|
||||
|
||||
# 로컬에 임시 저장
|
||||
filename = secure_filename(file.filename)
|
||||
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
||||
local_path = os.path.join(UPLOAD_FOLDER, filename)
|
||||
file.save(local_path)
|
||||
|
||||
# 백그라운드 스레드로 업로드 시작
|
||||
from backend.services import watchdog_handler
|
||||
socketio = watchdog_handler.socketio
|
||||
|
||||
def upload_to_servers():
|
||||
results = []
|
||||
|
||||
for idx, server_id in enumerate(server_ids):
|
||||
server = IdracServer.query.get(server_id)
|
||||
if not server:
|
||||
continue
|
||||
|
||||
try:
|
||||
# 진행 상황 전송
|
||||
if socketio:
|
||||
socketio.emit('upload_progress', {
|
||||
'server_id': server.id,
|
||||
'server_name': server.name,
|
||||
'status': 'uploading',
|
||||
'progress': 0,
|
||||
'message': '업로드 시작...'
|
||||
})
|
||||
|
||||
server.status = 'updating'
|
||||
db.session.commit()
|
||||
|
||||
client = DellRedfishClient(server.ip_address, server.username, server.password)
|
||||
result = client.upload_firmware_staged(local_path)
|
||||
|
||||
if result['success']:
|
||||
server.status = 'online'
|
||||
server.last_updated = datetime.utcnow()
|
||||
|
||||
results.append({
|
||||
'server_id': server.id,
|
||||
'server_name': server.name,
|
||||
'success': True,
|
||||
'job_id': result['job_id'],
|
||||
'message': '업로드 완료'
|
||||
})
|
||||
|
||||
if socketio:
|
||||
socketio.emit('upload_progress', {
|
||||
'server_id': server.id,
|
||||
'server_name': server.name,
|
||||
'status': 'completed',
|
||||
'progress': 100,
|
||||
'message': '업로드 완료',
|
||||
'job_id': result['job_id']
|
||||
})
|
||||
else:
|
||||
server.status = 'online'
|
||||
|
||||
results.append({
|
||||
'server_id': server.id,
|
||||
'server_name': server.name,
|
||||
'success': False,
|
||||
'message': result.get('message', '업로드 실패')
|
||||
})
|
||||
|
||||
if socketio:
|
||||
socketio.emit('upload_progress', {
|
||||
'server_id': server.id,
|
||||
'server_name': server.name,
|
||||
'status': 'failed',
|
||||
'progress': 0,
|
||||
'message': result.get('message', '업로드 실패')
|
||||
})
|
||||
|
||||
db.session.commit()
|
||||
|
||||
except Exception as e:
|
||||
server.status = 'online'
|
||||
db.session.commit()
|
||||
|
||||
results.append({
|
||||
'server_id': server.id,
|
||||
'server_name': server.name,
|
||||
'success': False,
|
||||
'message': str(e)
|
||||
})
|
||||
|
||||
if socketio:
|
||||
socketio.emit('upload_progress', {
|
||||
'server_id': server.id,
|
||||
'server_name': server.name,
|
||||
'status': 'failed',
|
||||
'progress': 0,
|
||||
'message': str(e)
|
||||
})
|
||||
|
||||
# 최종 결과 전송
|
||||
if socketio:
|
||||
success_count = sum(1 for r in results if r['success'])
|
||||
socketio.emit('upload_complete', {
|
||||
'results': results,
|
||||
'summary': {
|
||||
'total': len(results),
|
||||
'success': success_count,
|
||||
'failed': len(results) - success_count
|
||||
}
|
||||
})
|
||||
|
||||
# 스레드 시작
|
||||
thread = threading.Thread(target=upload_to_servers)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'{len(server_ids)}대 서버에 업로드 시작',
|
||||
'filename': filename
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'업로드 오류: {str(e)}'
|
||||
})
|
||||
|
||||
# ========================================
|
||||
# 기존 단일 서버 API (호환성 유지)
|
||||
# ========================================
|
||||
|
||||
@idrac_bp.route('/api/files/local', methods=['GET'])
|
||||
def list_local_files():
|
||||
"""로컬에 저장된 DUP 파일 목록"""
|
||||
try:
|
||||
files = []
|
||||
|
||||
if os.path.exists(UPLOAD_FOLDER):
|
||||
for filename in os.listdir(UPLOAD_FOLDER):
|
||||
filepath = os.path.join(UPLOAD_FOLDER, filename)
|
||||
if os.path.isfile(filepath):
|
||||
file_size = os.path.getsize(filepath)
|
||||
files.append({
|
||||
'name': filename,
|
||||
'size': file_size,
|
||||
'size_mb': round(file_size / (1024 * 1024), 2),
|
||||
'uploaded_at': datetime.fromtimestamp(
|
||||
os.path.getmtime(filepath)
|
||||
).strftime('%Y-%m-%d %H:%M:%S')
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'files': files
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'오류: {str(e)}'
|
||||
})
|
||||
|
||||
@idrac_bp.route('/api/files/local/<filename>', methods=['DELETE'])
|
||||
def delete_local_file(filename):
|
||||
"""로컬 DUP 파일 삭제"""
|
||||
try:
|
||||
filepath = os.path.join(UPLOAD_FOLDER, secure_filename(filename))
|
||||
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'{filename} 삭제 완료'
|
||||
})
|
||||
else:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '파일을 찾을 수 없습니다'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'삭제 오류: {str(e)}'
|
||||
})
|
||||
|
||||
# ========================================
|
||||
# 서버 재부팅 API (멀티)
|
||||
# ========================================
|
||||
|
||||
@idrac_bp.route('/api/servers/reboot-multi', methods=['POST'])
|
||||
def reboot_servers_multi():
|
||||
"""선택한 서버들 일괄 재부팅"""
|
||||
try:
|
||||
data = request.json
|
||||
server_ids = data.get('server_ids', [])
|
||||
reboot_type = data.get('type', 'GracefulRestart')
|
||||
|
||||
if not server_ids:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '서버를 선택하세요'
|
||||
})
|
||||
|
||||
results = []
|
||||
|
||||
for server_id in server_ids:
|
||||
server = IdracServer.query.get(server_id)
|
||||
if not server:
|
||||
continue
|
||||
|
||||
try:
|
||||
client = DellRedfishClient(server.ip_address, server.username, server.password)
|
||||
result = client.reboot_server(reboot_type)
|
||||
|
||||
if result:
|
||||
results.append({
|
||||
'server_id': server.id,
|
||||
'server_name': server.name,
|
||||
'success': True,
|
||||
'message': '재부팅 시작'
|
||||
})
|
||||
else:
|
||||
results.append({
|
||||
'server_id': server.id,
|
||||
'server_name': server.name,
|
||||
'success': False,
|
||||
'message': '재부팅 실패'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
results.append({
|
||||
'server_id': server.id,
|
||||
'server_name': server.name,
|
||||
'success': False,
|
||||
'message': str(e)
|
||||
})
|
||||
|
||||
success_count = sum(1 for r in results if r['success'])
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'results': results,
|
||||
'summary': {
|
||||
'total': len(results),
|
||||
'success': success_count,
|
||||
'failed': len(results) - success_count
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'오류: {str(e)}'
|
||||
})
|
||||
|
||||
|
||||
# Blueprint 등록 함수
|
||||
def register_idrac_routes(app):
|
||||
"""
|
||||
iDRAC 멀티 서버 관리 Blueprint 등록
|
||||
|
||||
Args:
|
||||
app: Flask 애플리케이션 인스턴스
|
||||
"""
|
||||
# uploads 디렉토리 생성
|
||||
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
||||
|
||||
# Blueprint 등록
|
||||
app.register_blueprint(idrac_bp)
|
||||
|
||||
# CSRF 보호 제외 - iDRAC API 엔드포인트
|
||||
try:
|
||||
from flask_wtf.csrf import CSRFProtect
|
||||
csrf = CSRFProtect()
|
||||
# API 엔드포인트는 CSRF 검증 제외
|
||||
csrf.exempt(idrac_bp)
|
||||
except:
|
||||
pass # CSRF 설정 실패해도 계속 진행
|
||||
|
||||
# DB 테이블 생성
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
399
backend/routes/version_compare_api.py
Normal file
399
backend/routes/version_compare_api.py
Normal file
@@ -0,0 +1,399 @@
|
||||
"""
|
||||
펌웨어 버전 비교 API 코드
|
||||
idrac_routes.py 파일의 register_idrac_routes 함수 위에 추가하세요
|
||||
"""
|
||||
|
||||
# ========================================
|
||||
# 펌웨어 버전 관리 API
|
||||
# ========================================
|
||||
|
||||
@idrac_bp.route('/api/firmware-versions', methods=['GET'])
|
||||
def get_firmware_versions():
|
||||
"""등록된 최신 펌웨어 버전 목록"""
|
||||
try:
|
||||
server_model = request.args.get('model') # 서버 모델 필터
|
||||
|
||||
query = FirmwareVersion.query.filter_by(is_active=True)
|
||||
|
||||
if server_model:
|
||||
# 특정 모델 또는 범용
|
||||
query = query.filter(
|
||||
(FirmwareVersion.server_model == server_model) |
|
||||
(FirmwareVersion.server_model == None)
|
||||
)
|
||||
|
||||
versions = query.order_by(FirmwareVersion.component_name).all()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'versions': [v.to_dict() for v in versions]
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'오류: {str(e)}'
|
||||
})
|
||||
|
||||
@idrac_bp.route('/api/firmware-versions', methods=['POST'])
|
||||
def add_firmware_version():
|
||||
"""최신 펌웨어 버전 등록"""
|
||||
try:
|
||||
data = request.json
|
||||
|
||||
# 필수 필드 확인
|
||||
if not all([data.get('component_name'), data.get('latest_version')]):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '컴포넌트명과 버전을 입력하세요'
|
||||
})
|
||||
|
||||
# 중복 확인 (같은 컴포넌트, 같은 모델)
|
||||
existing = FirmwareVersion.query.filter_by(
|
||||
component_name=data['component_name'],
|
||||
server_model=data.get('server_model')
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'이미 등록된 컴포넌트입니다'
|
||||
})
|
||||
|
||||
# 버전 생성
|
||||
version = FirmwareVersion(
|
||||
component_name=data['component_name'],
|
||||
component_type=data.get('component_type'),
|
||||
vendor=data.get('vendor'),
|
||||
server_model=data.get('server_model'),
|
||||
latest_version=data['latest_version'],
|
||||
release_date=data.get('release_date'),
|
||||
download_url=data.get('download_url'),
|
||||
file_name=data.get('file_name'),
|
||||
file_size_mb=data.get('file_size_mb'),
|
||||
notes=data.get('notes'),
|
||||
is_critical=data.get('is_critical', False)
|
||||
)
|
||||
|
||||
db.session.add(version)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'{version.component_name} 버전 정보 등록 완료',
|
||||
'version': version.to_dict()
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'오류: {str(e)}'
|
||||
})
|
||||
|
||||
@idrac_bp.route('/api/firmware-versions/<int:version_id>', methods=['PUT'])
|
||||
def update_firmware_version(version_id):
|
||||
"""펌웨어 버전 정보 수정"""
|
||||
try:
|
||||
version = FirmwareVersion.query.get(version_id)
|
||||
if not version:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '버전 정보를 찾을 수 없습니다'
|
||||
})
|
||||
|
||||
data = request.json
|
||||
|
||||
# 업데이트
|
||||
if 'component_name' in data:
|
||||
version.component_name = data['component_name']
|
||||
if 'latest_version' in data:
|
||||
version.latest_version = data['latest_version']
|
||||
if 'release_date' in data:
|
||||
version.release_date = data['release_date']
|
||||
if 'download_url' in data:
|
||||
version.download_url = data['download_url']
|
||||
if 'file_name' in data:
|
||||
version.file_name = data['file_name']
|
||||
if 'notes' in data:
|
||||
version.notes = data['notes']
|
||||
if 'is_critical' in data:
|
||||
version.is_critical = data['is_critical']
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '버전 정보 수정 완료',
|
||||
'version': version.to_dict()
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'오류: {str(e)}'
|
||||
})
|
||||
|
||||
@idrac_bp.route('/api/firmware-versions/<int:version_id>', methods=['DELETE'])
|
||||
def delete_firmware_version(version_id):
|
||||
"""펌웨어 버전 정보 삭제"""
|
||||
try:
|
||||
version = FirmwareVersion.query.get(version_id)
|
||||
if not version:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '버전 정보를 찾을 수 없습니다'
|
||||
})
|
||||
|
||||
version.is_active = False
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'{version.component_name} 버전 정보 삭제 완료'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'오류: {str(e)}'
|
||||
})
|
||||
|
||||
@idrac_bp.route('/api/servers/<int:server_id>/firmware/compare', methods=['GET'])
|
||||
def compare_server_firmware(server_id):
|
||||
"""
|
||||
서버 펌웨어 버전 비교
|
||||
현재 버전과 최신 버전을 비교하여 업데이트 필요 여부 확인
|
||||
"""
|
||||
try:
|
||||
server = IdracServer.query.get(server_id)
|
||||
if not server:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '서버를 찾을 수 없습니다'
|
||||
})
|
||||
|
||||
# 현재 펌웨어 조회
|
||||
client = DellRedfishClient(server.ip_address, server.username, server.password)
|
||||
current_inventory = client.get_firmware_inventory()
|
||||
|
||||
# 최신 버전 정보 조회
|
||||
latest_versions = FirmwareVersion.query.filter_by(is_active=True).all()
|
||||
|
||||
# 비교 결과
|
||||
comparisons = []
|
||||
|
||||
for current_fw in current_inventory:
|
||||
component_name = current_fw['Name']
|
||||
current_version = current_fw['Version']
|
||||
|
||||
# 최신 버전 찾기 (컴포넌트명 매칭)
|
||||
latest = None
|
||||
for lv in latest_versions:
|
||||
if lv.component_name.lower() in component_name.lower():
|
||||
# 서버 모델 확인
|
||||
if not lv.server_model or lv.server_model == server.model:
|
||||
latest = lv
|
||||
break
|
||||
|
||||
# 비교
|
||||
comparison = FirmwareComparisonResult(
|
||||
component_name=component_name,
|
||||
current_version=current_version,
|
||||
latest_version=latest.latest_version if latest else None
|
||||
)
|
||||
|
||||
result = comparison.to_dict()
|
||||
|
||||
# 추가 정보
|
||||
if latest:
|
||||
result['latest_info'] = {
|
||||
'release_date': latest.release_date,
|
||||
'download_url': latest.download_url,
|
||||
'file_name': latest.file_name,
|
||||
'is_critical': latest.is_critical,
|
||||
'notes': latest.notes
|
||||
}
|
||||
|
||||
comparisons.append(result)
|
||||
|
||||
# 통계
|
||||
total = len(comparisons)
|
||||
outdated = len([c for c in comparisons if c['status'] == 'outdated'])
|
||||
latest_count = len([c for c in comparisons if c['status'] == 'latest'])
|
||||
unknown = len([c for c in comparisons if c['status'] == 'unknown'])
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'server': {
|
||||
'id': server.id,
|
||||
'name': server.name,
|
||||
'model': server.model
|
||||
},
|
||||
'comparisons': comparisons,
|
||||
'summary': {
|
||||
'total': total,
|
||||
'outdated': outdated,
|
||||
'latest': latest_count,
|
||||
'unknown': unknown
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'오류: {str(e)}'
|
||||
})
|
||||
|
||||
@idrac_bp.route('/api/servers/firmware/compare-multi', methods=['POST'])
|
||||
def compare_multi_servers_firmware():
|
||||
"""
|
||||
여러 서버의 펌웨어 버전 비교
|
||||
"""
|
||||
try:
|
||||
data = request.json
|
||||
server_ids = data.get('server_ids', [])
|
||||
|
||||
if not server_ids:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '서버를 선택하세요'
|
||||
})
|
||||
|
||||
results = []
|
||||
|
||||
for server_id in server_ids:
|
||||
server = IdracServer.query.get(server_id)
|
||||
if not server:
|
||||
continue
|
||||
|
||||
try:
|
||||
# 각 서버 비교
|
||||
client = DellRedfishClient(server.ip_address, server.username, server.password)
|
||||
current_inventory = client.get_firmware_inventory()
|
||||
|
||||
latest_versions = FirmwareVersion.query.filter_by(is_active=True).all()
|
||||
|
||||
outdated_count = 0
|
||||
outdated_items = []
|
||||
|
||||
for current_fw in current_inventory:
|
||||
component_name = current_fw['Name']
|
||||
current_version = current_fw['Version']
|
||||
|
||||
# 최신 버전 찾기
|
||||
for lv in latest_versions:
|
||||
if lv.component_name.lower() in component_name.lower():
|
||||
comparison = FirmwareComparisonResult(
|
||||
component_name=component_name,
|
||||
current_version=current_version,
|
||||
latest_version=lv.latest_version
|
||||
)
|
||||
|
||||
if comparison.status == 'outdated':
|
||||
outdated_count += 1
|
||||
outdated_items.append({
|
||||
'component': component_name,
|
||||
'current': current_version,
|
||||
'latest': lv.latest_version
|
||||
})
|
||||
break
|
||||
|
||||
results.append({
|
||||
'server_id': server.id,
|
||||
'server_name': server.name,
|
||||
'outdated_count': outdated_count,
|
||||
'outdated_items': outdated_items,
|
||||
'status': 'needs_update' if outdated_count > 0 else 'up_to_date'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
results.append({
|
||||
'server_id': server.id,
|
||||
'server_name': server.name,
|
||||
'error': str(e)
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'results': results
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'오류: {str(e)}'
|
||||
})
|
||||
|
||||
|
||||
# ========================================
|
||||
# 초기 데이터 생성 함수
|
||||
# ========================================
|
||||
|
||||
def init_firmware_versions():
|
||||
"""초기 펌웨어 버전 데이터 생성"""
|
||||
initial_versions = [
|
||||
{
|
||||
'component_name': 'BIOS',
|
||||
'component_type': 'Firmware',
|
||||
'vendor': 'Dell',
|
||||
'server_model': 'PowerEdge R750',
|
||||
'latest_version': '2.15.0',
|
||||
'release_date': '2024-01-15',
|
||||
'notes': 'PowerEdge R750 최신 BIOS'
|
||||
},
|
||||
{
|
||||
'component_name': 'iDRAC',
|
||||
'component_type': 'Firmware',
|
||||
'vendor': 'Dell',
|
||||
'latest_version': '6.10.30.00',
|
||||
'release_date': '2024-02-20',
|
||||
'notes': 'iDRAC9 최신 펌웨어 (모든 모델 공용)'
|
||||
},
|
||||
{
|
||||
'component_name': 'PERC H755',
|
||||
'component_type': 'Firmware',
|
||||
'vendor': 'Dell',
|
||||
'server_model': 'PowerEdge R750',
|
||||
'latest_version': '25.5.9.0001',
|
||||
'release_date': '2024-01-10',
|
||||
'notes': 'PERC H755 RAID 컨트롤러'
|
||||
},
|
||||
{
|
||||
'component_name': 'BIOS',
|
||||
'component_type': 'Firmware',
|
||||
'vendor': 'Dell',
|
||||
'server_model': 'PowerEdge R640',
|
||||
'latest_version': '2.19.2',
|
||||
'release_date': '2024-02-01',
|
||||
'notes': 'PowerEdge R640 최신 BIOS'
|
||||
},
|
||||
{
|
||||
'component_name': 'CPLD',
|
||||
'component_type': 'Firmware',
|
||||
'vendor': 'Dell',
|
||||
'latest_version': '1.0.6',
|
||||
'release_date': '2023-12-15',
|
||||
'notes': '시스템 보드 CPLD (14G/15G 공용)'
|
||||
},
|
||||
]
|
||||
|
||||
for data in initial_versions:
|
||||
# 중복 체크
|
||||
existing = FirmwareVersion.query.filter_by(
|
||||
component_name=data['component_name'],
|
||||
server_model=data.get('server_model')
|
||||
).first()
|
||||
|
||||
if not existing:
|
||||
version = FirmwareVersion(**data)
|
||||
db.session.add(version)
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
print("✓ 초기 펌웨어 버전 데이터 생성 완료")
|
||||
except:
|
||||
db.session.rollback()
|
||||
print("⚠ 초기 데이터 생성 중 오류 (이미 있을 수 있음)")
|
||||
Reference in New Issue
Block a user