Add multi-host Proxmox support with SSL certificate handling
- Added support for multiple Proxmox hosts (pve7.0bin.in:443, Healthport PVE:8006) - Enhanced VM management APIs to accept host parameter - Fixed WebSocket URL generation bug (dynamic port handling) - Added comprehensive SSL certificate trust help system - Implemented host selection dropdown in UI - Added VNC connection failure detection and automatic SSL help redirection - Updated session management to store host_key information - Enhanced error handling for different Proxmox configurations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -5,11 +5,14 @@ Headscale + Headplane 고도화 관리자 페이지
|
||||
"""
|
||||
|
||||
from flask import Flask, render_template, jsonify, request, redirect, url_for
|
||||
from flask_socketio import SocketIO, emit, disconnect
|
||||
import os
|
||||
import json
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
from config import config
|
||||
import asyncio
|
||||
import threading
|
||||
from utils.database_new import (
|
||||
init_databases, get_farmq_session,
|
||||
get_dashboard_stats, get_all_pharmacies_with_stats, get_all_machines_with_details,
|
||||
@@ -20,6 +23,7 @@ from models.farmq_models import PharmacyInfo
|
||||
from sqlalchemy import or_
|
||||
import subprocess
|
||||
from utils.proxmox_client import ProxmoxClient
|
||||
from utils.vnc_proxy import init_vnc_proxy, get_vnc_proxy
|
||||
|
||||
def create_app(config_name=None):
|
||||
"""Flask 애플리케이션 팩토리"""
|
||||
@@ -29,6 +33,9 @@ def create_app(config_name=None):
|
||||
config_name = config_name or os.environ.get('FLASK_ENV', 'default')
|
||||
app.config.from_object(config[config_name])
|
||||
|
||||
# SocketIO 초기화
|
||||
socketio = SocketIO(app, cors_allowed_origins="*", async_mode='threading')
|
||||
|
||||
# 데이터베이스 초기화
|
||||
init_databases(
|
||||
headscale_db_uri='sqlite:////srv/headscale-setup/data/db.sqlite',
|
||||
@@ -42,10 +49,43 @@ def create_app(config_name=None):
|
||||
# VNC 세션 관리 (메모리 기반)
|
||||
vnc_sessions = {}
|
||||
|
||||
# Proxmox 서버 설정
|
||||
PROXMOX_HOST = "pve7.0bin.in"
|
||||
PROXMOX_USERNAME = "root@pam"
|
||||
PROXMOX_PASSWORD = "trajet6640"
|
||||
# 다중 Proxmox 서버 설정
|
||||
PROXMOX_HOSTS = {
|
||||
'pve7.0bin.in': {
|
||||
'host': 'pve7.0bin.in',
|
||||
'username': 'root@pam',
|
||||
'password': 'trajet6640',
|
||||
'port': 443,
|
||||
'name': 'PVE 7.0 (Main)',
|
||||
'default': True
|
||||
},
|
||||
'healthport_pve': {
|
||||
'host': '100.64.0.6',
|
||||
'username': 'root@pam',
|
||||
'password': 'healthport',
|
||||
'port': 8006,
|
||||
'name': 'Healthport PVE',
|
||||
'default': False
|
||||
}
|
||||
}
|
||||
|
||||
# 기본 호스트 가져오기
|
||||
def get_default_host():
|
||||
for host_key, host_config in PROXMOX_HOSTS.items():
|
||||
if host_config.get('default', False):
|
||||
return host_key, host_config
|
||||
# 기본값이 없으면 첫 번째 호스트 반환
|
||||
return next(iter(PROXMOX_HOSTS.items()))
|
||||
|
||||
# 호스트 설정 가져오기
|
||||
def get_host_config(host_key=None):
|
||||
if not host_key:
|
||||
return get_default_host()
|
||||
return host_key, PROXMOX_HOSTS.get(host_key, get_default_host()[1])
|
||||
|
||||
# VNC 프록시 초기화 (기본 호스트로)
|
||||
default_host_key, default_host_config = get_default_host()
|
||||
init_vnc_proxy(default_host_config['host'], default_host_config['username'], default_host_config['password'])
|
||||
|
||||
# 메인 대시보드
|
||||
@app.route('/')
|
||||
@@ -492,11 +532,20 @@ def create_app(config_name=None):
|
||||
def vm_list():
|
||||
"""VM 목록 페이지"""
|
||||
try:
|
||||
# 요청된 호스트 가져오기
|
||||
requested_host = request.args.get('host')
|
||||
current_host_key, current_host_config = get_host_config(requested_host)
|
||||
|
||||
# Proxmox 클라이언트 생성 및 로그인
|
||||
client = ProxmoxClient(PROXMOX_HOST, PROXMOX_USERNAME, PROXMOX_PASSWORD)
|
||||
client = ProxmoxClient(
|
||||
current_host_config['host'],
|
||||
current_host_config['username'],
|
||||
current_host_config['password'],
|
||||
port=current_host_config['port']
|
||||
)
|
||||
if not client.login():
|
||||
return render_template('error.html',
|
||||
error='Proxmox 서버에 연결할 수 없습니다.'), 500
|
||||
error=f'Proxmox 서버({current_host_config["name"]})에 연결할 수 없습니다.'), 500
|
||||
|
||||
# VM 목록 가져오기
|
||||
vms = client.get_vm_list()
|
||||
@@ -509,7 +558,10 @@ def create_app(config_name=None):
|
||||
|
||||
return render_template('vm_list.html',
|
||||
vms=vms,
|
||||
host=PROXMOX_HOST,
|
||||
available_hosts=PROXMOX_HOSTS,
|
||||
current_host_key=current_host_key,
|
||||
current_host_name=current_host_config['name'],
|
||||
current_host_info=current_host_config,
|
||||
total_vms=total_vms,
|
||||
running_vms=running_vms,
|
||||
stopped_vms=stopped_vms,
|
||||
@@ -527,11 +579,20 @@ def create_app(config_name=None):
|
||||
node = data.get('node')
|
||||
vmid = int(data.get('vmid'))
|
||||
vm_name = data.get('vm_name', f'VM-{vmid}')
|
||||
host_key = data.get('host')
|
||||
|
||||
# 호스트 설정 가져오기
|
||||
current_host_key, current_host_config = get_host_config(host_key)
|
||||
|
||||
# Proxmox 클라이언트 생성 및 로그인
|
||||
client = ProxmoxClient(PROXMOX_HOST, PROXMOX_USERNAME, PROXMOX_PASSWORD)
|
||||
client = ProxmoxClient(
|
||||
current_host_config['host'],
|
||||
current_host_config['username'],
|
||||
current_host_config['password'],
|
||||
port=current_host_config['port']
|
||||
)
|
||||
if not client.login():
|
||||
return jsonify({'error': 'Proxmox 서버 로그인 실패'}), 500
|
||||
return jsonify({'error': f'Proxmox 서버({current_host_config["name"]}) 로그인 실패'}), 500
|
||||
|
||||
# VM 상태 확인
|
||||
vm_status = client.get_vm_status(node, vmid)
|
||||
@@ -553,6 +614,7 @@ def create_app(config_name=None):
|
||||
'websocket_url': vnc_data['websocket_url'],
|
||||
'password': vnc_data.get('password', ''), # VNC 패스워드 추가
|
||||
'vm_status': vm_status.get('status', 'unknown'), # VM 상태 추가
|
||||
'host_key': current_host_key, # 호스트 키 저장
|
||||
'created_at': datetime.now()
|
||||
}
|
||||
|
||||
@@ -635,6 +697,57 @@ def create_app(config_name=None):
|
||||
print(f"❌ VNC 콘솔 오류: {e}")
|
||||
return render_template('error.html', error=str(e)), 500
|
||||
|
||||
@app.route('/vnc/<session_id>/proxy')
|
||||
def vnc_console_proxy(session_id):
|
||||
"""VNC 콘솔 페이지 (Socket.IO 프록시 버전)"""
|
||||
try:
|
||||
# 세션 확인
|
||||
if session_id not in vnc_sessions:
|
||||
return render_template('error.html',
|
||||
error='유효하지 않은 VNC 세션입니다.'), 404
|
||||
|
||||
session_data = vnc_sessions[session_id]
|
||||
|
||||
# Socket.IO 기반 VNC 프록시 사용
|
||||
return render_template('vnc_proxy.html',
|
||||
vm_name=session_data['vm_name'],
|
||||
vmid=session_data['vmid'],
|
||||
node=session_data['node'],
|
||||
session_id=session_id)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ VNC 프록시 콘솔 오류: {e}")
|
||||
return render_template('error.html', error=str(e)), 500
|
||||
|
||||
@app.route('/vnc/<session_id>/ssl-help')
|
||||
def vnc_ssl_help(session_id):
|
||||
"""VNC SSL 인증서 도움말 페이지"""
|
||||
try:
|
||||
# 세션 확인
|
||||
if session_id not in vnc_sessions:
|
||||
return render_template('error.html',
|
||||
error='유효하지 않은 VNC 세션입니다.'), 404
|
||||
|
||||
session_data = vnc_sessions[session_id]
|
||||
host_key = session_data.get('host_key', 'pve7.0bin.in')
|
||||
|
||||
# 호스트 설정 가져오기
|
||||
current_host_key, current_host_config = get_host_config(host_key)
|
||||
|
||||
return render_template('vnc_ssl_help.html',
|
||||
vm_name=session_data['vm_name'],
|
||||
vmid=session_data['vmid'],
|
||||
node=session_data['node'],
|
||||
session_id=session_id,
|
||||
websocket_url=session_data['websocket_url'],
|
||||
proxmox_host=current_host_config['host'],
|
||||
proxmox_port=current_host_config['port'],
|
||||
host_key=current_host_key)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ VNC SSL 도움말 오류: {e}")
|
||||
return render_template('error.html', error=str(e)), 500
|
||||
|
||||
@app.route('/api/vm/start', methods=['POST'])
|
||||
def api_vm_start():
|
||||
"""VM 시작 API"""
|
||||
@@ -642,11 +755,20 @@ def create_app(config_name=None):
|
||||
data = request.get_json()
|
||||
node = data.get('node')
|
||||
vmid = int(data.get('vmid'))
|
||||
host_key = data.get('host')
|
||||
|
||||
# 호스트 설정 가져오기
|
||||
current_host_key, current_host_config = get_host_config(host_key)
|
||||
|
||||
# Proxmox 클라이언트 생성 및 로그인
|
||||
client = ProxmoxClient(PROXMOX_HOST, PROXMOX_USERNAME, PROXMOX_PASSWORD)
|
||||
client = ProxmoxClient(
|
||||
current_host_config['host'],
|
||||
current_host_config['username'],
|
||||
current_host_config['password'],
|
||||
port=current_host_config['port']
|
||||
)
|
||||
if not client.login():
|
||||
return jsonify({'error': 'Proxmox 서버 로그인 실패'}), 500
|
||||
return jsonify({'error': f'Proxmox 서버({current_host_config["name"]}) 로그인 실패'}), 500
|
||||
|
||||
# VM 시작
|
||||
success = client.start_vm(node, vmid)
|
||||
@@ -666,11 +788,20 @@ def create_app(config_name=None):
|
||||
data = request.get_json()
|
||||
node = data.get('node')
|
||||
vmid = int(data.get('vmid'))
|
||||
host_key = data.get('host')
|
||||
|
||||
# 호스트 설정 가져오기
|
||||
current_host_key, current_host_config = get_host_config(host_key)
|
||||
|
||||
# Proxmox 클라이언트 생성 및 로그인
|
||||
client = ProxmoxClient(PROXMOX_HOST, PROXMOX_USERNAME, PROXMOX_PASSWORD)
|
||||
client = ProxmoxClient(
|
||||
current_host_config['host'],
|
||||
current_host_config['username'],
|
||||
current_host_config['password'],
|
||||
port=current_host_config['port']
|
||||
)
|
||||
if not client.login():
|
||||
return jsonify({'error': 'Proxmox 서버 로그인 실패'}), 500
|
||||
return jsonify({'error': f'Proxmox 서버({current_host_config["name"]}) 로그인 실패'}), 500
|
||||
|
||||
# VM 정지
|
||||
success = client.stop_vm(node, vmid)
|
||||
@@ -1430,24 +1561,84 @@ def create_app(config_name=None):
|
||||
error='내부 서버 오류가 발생했습니다.',
|
||||
error_code=500), 500
|
||||
|
||||
return app
|
||||
# VNC WebSocket 프록시 핸들러
|
||||
@socketio.on('vnc_connect')
|
||||
def handle_vnc_connect(data):
|
||||
"""VNC WebSocket 프록시 연결 핸들러"""
|
||||
print(f"🔌 VNC 프록시 연결 요청: {data}")
|
||||
|
||||
try:
|
||||
vm_id = data.get('vm_id')
|
||||
node = data.get('node', 'pve7')
|
||||
|
||||
if not vm_id:
|
||||
emit('vnc_error', {'error': 'VM ID가 필요합니다.'})
|
||||
return
|
||||
|
||||
# VNC 프록시 가져오기
|
||||
vnc_proxy = get_vnc_proxy()
|
||||
if not vnc_proxy:
|
||||
emit('vnc_error', {'error': 'VNC 프록시가 초기화되지 않았습니다.'})
|
||||
return
|
||||
|
||||
# 비동기 VNC 프록시 시작을 별도 스레드에서 실행
|
||||
def run_vnc_proxy():
|
||||
# 간단한 동기 버전으로 시작 - 실제 WebSocket 중계는 나중에 구현
|
||||
try:
|
||||
# VNC 연결 정보 생성 테스트
|
||||
client = ProxmoxClient(PROXMOX_HOST, PROXMOX_USERNAME, PROXMOX_PASSWORD)
|
||||
if not client.login():
|
||||
socketio.emit('vnc_error', {'error': 'Proxmox 로그인 실패'})
|
||||
return
|
||||
|
||||
vnc_data = client.get_vnc_ticket(node, vm_id)
|
||||
if vnc_data:
|
||||
socketio.emit('vnc_ready', {
|
||||
'vm_id': vm_id,
|
||||
'node': node,
|
||||
'websocket_url': vnc_data['websocket_url'],
|
||||
'password': vnc_data['password']
|
||||
})
|
||||
else:
|
||||
socketio.emit('vnc_error', {'error': 'VNC 티켓 생성 실패'})
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ VNC 프록시 오류: {e}")
|
||||
socketio.emit('vnc_error', {'error': str(e)})
|
||||
|
||||
# 별도 스레드에서 실행
|
||||
threading.Thread(target=run_vnc_proxy, daemon=True).start()
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ VNC 연결 핸들러 오류: {e}")
|
||||
emit('vnc_error', {'error': str(e)})
|
||||
|
||||
@socketio.on('disconnect')
|
||||
def handle_disconnect():
|
||||
"""WebSocket 연결 종료 핸들러"""
|
||||
print('🔌 클라이언트 연결 종료')
|
||||
|
||||
return app, socketio
|
||||
|
||||
# 개발 서버 실행
|
||||
if __name__ == '__main__':
|
||||
app = create_app()
|
||||
app, socketio = create_app()
|
||||
|
||||
# 개발 환경에서만 실행
|
||||
if app.config.get('DEBUG'):
|
||||
print("🚀 Starting FARMQ Admin System...")
|
||||
print("🚀 Starting FARMQ Admin System with WebSocket Support...")
|
||||
print(f"📊 Dashboard: http://localhost:5001")
|
||||
print(f"🏥 Pharmacy Management: http://localhost:5001/pharmacy")
|
||||
print(f"💻 Machine Management: http://localhost:5001/machines")
|
||||
print(f"🖥️ VM Management (VNC): http://localhost:5001/vms")
|
||||
print(f"🔌 WebSocket VNC Proxy: ws://localhost:5001/socket.io/")
|
||||
print("─" * 60)
|
||||
|
||||
app.run(
|
||||
socketio.run(
|
||||
app,
|
||||
host='0.0.0.0',
|
||||
port=5001,
|
||||
debug=True,
|
||||
use_reloader=True
|
||||
use_reloader=True,
|
||||
allow_unsafe_werkzeug=True
|
||||
)
|
||||
Reference in New Issue
Block a user