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:
242
farmq-admin/templates/vnc_proxy.html
Normal file
242
farmq-admin/templates/vnc_proxy.html
Normal file
@@ -0,0 +1,242 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ vm_name }} - VNC 콘솔 (프록시)</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
background-color: dimgrey;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#top_bar {
|
||||
background-color: #6e84a3;
|
||||
color: white;
|
||||
font: bold 12px Helvetica;
|
||||
padding: 6px 5px 4px 5px;
|
||||
border-bottom: 1px outset;
|
||||
}
|
||||
#status {
|
||||
text-align: center;
|
||||
}
|
||||
#sendCtrlAltDelButton {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
right: 120px;
|
||||
border: 1px outset;
|
||||
padding: 5px 5px 4px 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#connectButton {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
border: 1px outset;
|
||||
padding: 5px 5px 4px 5px;
|
||||
cursor: pointer;
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#screen {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #ff6b6b;
|
||||
background-color: #ffe6e6;
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ff6b6b;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: #28a745;
|
||||
background-color: #e6ffe6;
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #28a745;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="top_bar">
|
||||
<div id="status">로딩 중...</div>
|
||||
<div id="sendCtrlAltDelButton">Ctrl+Alt+Del 전송</div>
|
||||
<div id="connectButton">VNC 연결</div>
|
||||
</div>
|
||||
<div id="screen">
|
||||
<div id="connectionMessages"></div>
|
||||
</div>
|
||||
|
||||
<!-- Socket.IO 클라이언트 -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.2/socket.io.js"></script>
|
||||
|
||||
<!-- noVNC 라이브러리 -->
|
||||
<script type="module" crossorigin="anonymous">
|
||||
import RFB from '/static/novnc/core/rfb.js';
|
||||
|
||||
let rfb;
|
||||
let desktopName;
|
||||
let socket;
|
||||
|
||||
// 상태 표시 함수
|
||||
function status(text) {
|
||||
document.getElementById('status').textContent = text;
|
||||
}
|
||||
|
||||
function showMessage(message, type = 'info') {
|
||||
const messagesDiv = document.getElementById('connectionMessages');
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = type;
|
||||
messageDiv.textContent = message;
|
||||
messagesDiv.appendChild(messageDiv);
|
||||
|
||||
// 5초 후 메시지 제거
|
||||
setTimeout(() => {
|
||||
if (messageDiv.parentNode) {
|
||||
messageDiv.parentNode.removeChild(messageDiv);
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// noVNC 이벤트 핸들러
|
||||
function connectedToServer(e) {
|
||||
status("연결됨");
|
||||
showMessage("VNC 서버에 성공적으로 연결되었습니다.", 'success');
|
||||
}
|
||||
|
||||
function disconnectedFromServer(e) {
|
||||
if (e.detail.clean) {
|
||||
status("연결 종료");
|
||||
showMessage("VNC 연결이 정상적으로 종료되었습니다.", 'info');
|
||||
} else {
|
||||
status("연결 실패");
|
||||
showMessage("VNC 연결이 예기치 않게 종료되었습니다.", 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function credentialsAreRequired(e) {
|
||||
status("인증 필요");
|
||||
showMessage("VNC 서버에서 추가 인증을 요구합니다.", 'error');
|
||||
}
|
||||
|
||||
function updateDesktopName(e) {
|
||||
desktopName = e.detail.name;
|
||||
status("연결됨: " + desktopName);
|
||||
}
|
||||
|
||||
function onSecurityFailure(e) {
|
||||
status("보안 인증 실패");
|
||||
showMessage("VNC 보안 인증에 실패했습니다: " + e.detail.reason, 'error');
|
||||
}
|
||||
|
||||
// Socket.IO 연결 및 이벤트 핸들러
|
||||
function initSocketIO() {
|
||||
socket = io();
|
||||
|
||||
socket.on('connect', function() {
|
||||
console.log('Socket.IO 연결됨');
|
||||
status("서버 연결됨 - VNC 연결 대기 중");
|
||||
});
|
||||
|
||||
socket.on('disconnect', function() {
|
||||
console.log('Socket.IO 연결 종료');
|
||||
status("서버 연결 종료");
|
||||
});
|
||||
|
||||
socket.on('vnc_ready', function(data) {
|
||||
console.log('VNC 연결 준비 완료:', data);
|
||||
status("VNC 연결 중...");
|
||||
|
||||
// noVNC로 직접 연결 (테스트용)
|
||||
try {
|
||||
const websocketUrl = data.websocket_url;
|
||||
const password = data.password;
|
||||
|
||||
console.log('WebSocket URL:', websocketUrl);
|
||||
console.log('VNC Password:', password);
|
||||
|
||||
rfb = new RFB(document.getElementById('screen'), websocketUrl,
|
||||
{ credentials: { password: password } });
|
||||
|
||||
// 이벤트 리스너 등록
|
||||
rfb.addEventListener("connect", connectedToServer);
|
||||
rfb.addEventListener("disconnect", disconnectedFromServer);
|
||||
rfb.addEventListener("credentialsrequired", credentialsAreRequired);
|
||||
rfb.addEventListener("desktopname", updateDesktopName);
|
||||
rfb.addEventListener("securityfailure", onSecurityFailure);
|
||||
|
||||
showMessage("VNC 연결을 시도 중입니다...", 'info');
|
||||
|
||||
} catch (error) {
|
||||
console.error('VNC 연결 오류:', error);
|
||||
showMessage("VNC 연결 중 오류가 발생했습니다: " + error.message, 'error');
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('vnc_error', function(data) {
|
||||
console.error('VNC 프록시 오류:', data);
|
||||
status("VNC 연결 실패");
|
||||
showMessage("VNC 연결 실패: " + data.error, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// VNC 연결 시작
|
||||
function connectVNC() {
|
||||
if (socket && socket.connected) {
|
||||
showMessage("VNC 연결을 요청 중입니다...", 'info');
|
||||
status("VNC 연결 요청 중...");
|
||||
|
||||
socket.emit('vnc_connect', {
|
||||
vm_id: {{ vmid }},
|
||||
node: '{{ node }}',
|
||||
vm_name: '{{ vm_name }}'
|
||||
});
|
||||
} else {
|
||||
showMessage("서버 연결이 필요합니다. 잠시 후 다시 시도해주세요.", 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Ctrl+Alt+Del 전송
|
||||
function sendCtrlAltDel() {
|
||||
if (rfb) {
|
||||
rfb.sendCtrlAltDel();
|
||||
showMessage("Ctrl+Alt+Del을 전송했습니다.", 'info');
|
||||
}
|
||||
}
|
||||
|
||||
// 이벤트 리스너 설정
|
||||
document.getElementById('connectButton').onclick = connectVNC;
|
||||
document.getElementById('sendCtrlAltDelButton').onclick = sendCtrlAltDel;
|
||||
|
||||
// 페이지 로드 시 Socket.IO 초기화
|
||||
window.addEventListener('load', function() {
|
||||
initSocketIO();
|
||||
});
|
||||
|
||||
// 페이지 언로드 시 연결 정리
|
||||
window.addEventListener('beforeunload', function() {
|
||||
if (rfb) {
|
||||
rfb.disconnect();
|
||||
}
|
||||
if (socket) {
|
||||
socket.disconnect();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user