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:
@@ -77,12 +77,34 @@
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2><i class="fas fa-desktop"></i> Proxmox VM 관리</h2>
|
||||
<div>
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<!-- 호스트 선택 드롭다운 -->
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" id="hostSelector" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fas fa-server"></i> {{ current_host_name or '호스트 선택' }}
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="hostSelector">
|
||||
{% for host_key, host_info in available_hosts.items() %}
|
||||
<li>
|
||||
<a class="dropdown-item {% if host_key == current_host_key %}active{% endif %}"
|
||||
href="/vms?host={{ host_key }}"
|
||||
onclick="changeHost('{{ host_key }}')">
|
||||
<i class="fas fa-server me-2"></i>
|
||||
<strong>{{ host_info.name }}</strong><br>
|
||||
<small class="text-muted">{{ host_info.host }}{% if host_info.port != 443 %}:{{ host_info.port }}{% endif %}</small>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button id="refresh-btn" class="btn btn-outline-primary">
|
||||
<i class="fas fa-sync-alt"></i> 새로고침
|
||||
</button>
|
||||
<span class="text-muted ms-3">
|
||||
<i class="fas fa-server"></i> {{ host }}
|
||||
|
||||
<span class="text-muted">
|
||||
<i class="fas fa-network-wired"></i>
|
||||
<small>{{ current_host_info.host }}{% if current_host_info.port != 443 %}:{{ current_host_info.port }}{% endif %}</small>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -271,7 +293,8 @@
|
||||
body: JSON.stringify({
|
||||
node: node,
|
||||
vmid: vmid,
|
||||
vm_name: vmName
|
||||
vm_name: vmName,
|
||||
host: new URLSearchParams(window.location.search).get('host') || 'pve7.0bin.in'
|
||||
})
|
||||
});
|
||||
|
||||
@@ -312,7 +335,8 @@
|
||||
},
|
||||
body: JSON.stringify({
|
||||
node: node,
|
||||
vmid: vmid
|
||||
vmid: vmid,
|
||||
host: new URLSearchParams(window.location.search).get('host') || 'pve7.0bin.in'
|
||||
})
|
||||
});
|
||||
|
||||
@@ -345,7 +369,8 @@
|
||||
},
|
||||
body: JSON.stringify({
|
||||
node: node,
|
||||
vmid: vmid
|
||||
vmid: vmid,
|
||||
host: new URLSearchParams(window.location.search).get('host') || 'pve7.0bin.in'
|
||||
})
|
||||
});
|
||||
|
||||
@@ -375,6 +400,13 @@
|
||||
location.reload();
|
||||
};
|
||||
|
||||
// 호스트 변경
|
||||
function changeHost(hostKey) {
|
||||
showSpinner();
|
||||
showToast('호스트 변경', `${hostKey} 호스트로 연결 중...`, 'info');
|
||||
// URL을 통해 페이지 이동 (이미 href에 설정되어 있음)
|
||||
};
|
||||
|
||||
// 스피너 표시/숨김
|
||||
function showSpinner() {
|
||||
document.querySelector('.loading-spinner').style.display = 'block';
|
||||
|
||||
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>
|
||||
@@ -80,7 +80,6 @@
|
||||
status("연결이 정상적으로 종료되었습니다");
|
||||
} else {
|
||||
const reason = e.detail.reason || 'Unknown';
|
||||
status(`연결 실패: ${reason} (Code: ${e.detail.code || 'Unknown'})`);
|
||||
console.error('❌ VNC 연결 실패 상세:', {
|
||||
code: e.detail.code,
|
||||
reason: e.detail.reason,
|
||||
@@ -89,7 +88,7 @@
|
||||
|
||||
// WebSocket 에러 코드별 메시지
|
||||
const errorMessages = {
|
||||
1006: 'WebSocket 서버에 연결할 수 없습니다. VM이 실행중인지 확인하세요.',
|
||||
1006: 'WebSocket 서버에 연결할 수 없습니다. SSL 인증서를 확인하세요.',
|
||||
1000: '정상적으로 연결이 종료되었습니다.',
|
||||
1002: '프로토콜 오류가 발생했습니다.',
|
||||
1003: '지원하지 않는 데이터를 받았습니다.',
|
||||
@@ -99,6 +98,14 @@
|
||||
|
||||
const userFriendlyMessage = errorMessages[e.detail.code] || `알 수 없는 오류 (코드: ${e.detail.code})`;
|
||||
status(`❌ ${userFriendlyMessage}`);
|
||||
|
||||
// SSL 인증서 문제일 가능성이 높은 경우 SSL 도움말 페이지로 이동
|
||||
if (e.detail.code === 1006 || !e.detail.clean) {
|
||||
setTimeout(() => {
|
||||
const sessionId = window.location.pathname.split('/').pop();
|
||||
window.location.href = `/vnc/${sessionId}/ssl-help`;
|
||||
}, 5000); // 5초 후 이동하여 사용자가 메시지를 읽을 시간 제공
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
291
farmq-admin/templates/vnc_ssl_help.html
Normal file
291
farmq-admin/templates/vnc_ssl_help.html
Normal file
@@ -0,0 +1,291 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>SSL 인증서 문제 해결 - {{ vm_name }}</title>
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
|
||||
<style>
|
||||
body {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
.ssl-help-container {
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
||||
margin: 50px auto;
|
||||
max-width: 800px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ssl-help-header {
|
||||
background: linear-gradient(135deg, #ff6b6b, #ffd93d);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ssl-help-body {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.step-card {
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.step-header {
|
||||
background: #f8f9fa;
|
||||
padding: 15px 20px;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 15px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.step-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.url-box {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
font-family: monospace;
|
||||
word-break: break-all;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.btn-copy {
|
||||
margin-left: 10px;
|
||||
padding: 5px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.warning-box {
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffeaa7;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.success-box {
|
||||
background: #d4edda;
|
||||
border: 1px solid #c3e6cb;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="ssl-help-container">
|
||||
<div class="ssl-help-header">
|
||||
<i class="fas fa-shield-alt fa-3x mb-3"></i>
|
||||
<h2>SSL 인증서 신뢰 설정이 필요합니다</h2>
|
||||
<p class="mb-0">{{ vm_name }} VNC 연결을 위해 Proxmox 서버의 SSL 인증서를 신뢰해야 합니다.</p>
|
||||
</div>
|
||||
|
||||
<div class="ssl-help-body">
|
||||
<div class="warning-box">
|
||||
<i class="fas fa-exclamation-triangle text-warning me-2"></i>
|
||||
<strong>연결 실패 원인:</strong> 브라우저가 Proxmox 서버의 자체 서명된 SSL 인증서를 신뢰하지 않아 WebSocket 연결이 차단되고 있습니다.
|
||||
</div>
|
||||
|
||||
<div class="step-card">
|
||||
<div class="step-header">
|
||||
<div class="step-number">1</div>
|
||||
SSL 인증서 신뢰 설정
|
||||
</div>
|
||||
<div class="step-content">
|
||||
<p>아래 링크를 <strong>새 탭</strong>에서 열어 Proxmox 서버의 SSL 인증서를 신뢰하도록 설정하세요:</p>
|
||||
|
||||
<div class="url-box">
|
||||
<a href="https://{{ proxmox_host }}:{{ proxmox_port }}" target="_blank" id="proxmoxUrl">
|
||||
https://{{ proxmox_host }}:{{ proxmox_port }}
|
||||
</a>
|
||||
<button class="btn btn-sm btn-outline-secondary btn-copy" onclick="copyUrl()">
|
||||
<i class="fas fa-copy"></i> 복사
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<h6><i class="fas fa-info-circle text-info me-2"></i>브라우저별 설정 방법:</h6>
|
||||
<ul class="list-unstyled ms-3">
|
||||
<li><strong>Chrome/Edge:</strong> "고급" → "{{ proxmox_host }}(으)로 이동(안전하지 않음)" 클릭</li>
|
||||
<li><strong>Firefox:</strong> "고급" → "위험을 감수하고 계속" 클릭</li>
|
||||
<li><strong>Safari:</strong> "세부 정보 표시" → "웹 사이트 방문" 클릭</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step-card">
|
||||
<div class="step-header">
|
||||
<div class="step-number">2</div>
|
||||
Proxmox 웹 인터페이스 확인
|
||||
</div>
|
||||
<div class="step-content">
|
||||
<p>링크를 클릭하면 Proxmox VE 웹 인터페이스가 표시됩니다. 로그인할 필요는 없으며, 페이지가 정상적으로 로드되면 SSL 인증서 신뢰 설정이 완료된 것입니다.</p>
|
||||
|
||||
<div class="success-box">
|
||||
<i class="fas fa-check-circle text-success me-2"></i>
|
||||
<strong>성공 확인:</strong> Proxmox 로그인 페이지가 보이면 인증서 신뢰 설정이 완료되었습니다.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step-card">
|
||||
<div class="step-header">
|
||||
<div class="step-number">3</div>
|
||||
VNC 연결 재시도
|
||||
</div>
|
||||
<div class="step-content">
|
||||
<p>SSL 인증서 신뢰 설정이 완료되면, 아래 버튼을 클릭하여 VNC 연결을 다시 시도하세요:</p>
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<button class="btn btn-primary btn-lg" onclick="retryVncConnection()">
|
||||
<i class="fas fa-desktop me-2"></i>VNC 연결 재시도
|
||||
</button>
|
||||
|
||||
<button class="btn btn-outline-secondary btn-lg ms-3" onclick="testWebSocket()">
|
||||
<i class="fas fa-wifi me-2"></i>연결 테스트
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="connectionStatus" class="mt-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step-card">
|
||||
<div class="step-header">
|
||||
<div class="step-number">4</div>
|
||||
문제 해결
|
||||
</div>
|
||||
<div class="step-content">
|
||||
<p>위 단계를 완료했는데도 연결이 되지 않는다면:</p>
|
||||
<ul>
|
||||
<li>브라우저를 완전히 닫고 다시 열어보세요</li>
|
||||
<li>시크릿/인코그니토 모드에서 시도해보세요</li>
|
||||
<li>다른 브라우저를 사용해보세요</li>
|
||||
<li>방화벽이나 프록시 설정을 확인하세요</li>
|
||||
</ul>
|
||||
|
||||
<div class="text-center mt-3">
|
||||
<a href="/vms?host={{ host_key }}" class="btn btn-outline-dark">
|
||||
<i class="fas fa-arrow-left me-2"></i>VM 목록으로 돌아가기
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script>
|
||||
// URL 복사 함수
|
||||
function copyUrl() {
|
||||
const url = document.getElementById('proxmoxUrl').href;
|
||||
navigator.clipboard.writeText(url).then(() => {
|
||||
alert('URL이 클립보드에 복사되었습니다.');
|
||||
});
|
||||
}
|
||||
|
||||
// VNC 연결 재시도
|
||||
function retryVncConnection() {
|
||||
const sessionId = '{{ session_id }}';
|
||||
window.location.href = `/vnc/${sessionId}`;
|
||||
}
|
||||
|
||||
// WebSocket 연결 테스트
|
||||
function testWebSocket() {
|
||||
const statusDiv = document.getElementById('connectionStatus');
|
||||
statusDiv.innerHTML = '<div class="text-center"><i class="fas fa-spinner fa-spin"></i> 연결 테스트 중...</div>';
|
||||
|
||||
try {
|
||||
const websocketUrl = '{{ websocket_url }}';
|
||||
const ws = new WebSocket(websocketUrl);
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
ws.close();
|
||||
statusDiv.innerHTML = `
|
||||
<div class="alert alert-warning">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
<strong>연결 시간 초과</strong><br>
|
||||
SSL 인증서 신뢰 설정을 먼저 완료해주세요.
|
||||
</div>`;
|
||||
}, 5000);
|
||||
|
||||
ws.onopen = function() {
|
||||
clearTimeout(timeout);
|
||||
ws.close();
|
||||
statusDiv.innerHTML = `
|
||||
<div class="alert alert-success">
|
||||
<i class="fas fa-check-circle me-2"></i>
|
||||
<strong>연결 성공!</strong><br>
|
||||
VNC 연결 재시도 버튼을 클릭하세요.
|
||||
</div>`;
|
||||
};
|
||||
|
||||
ws.onerror = function() {
|
||||
clearTimeout(timeout);
|
||||
statusDiv.innerHTML = `
|
||||
<div class="alert alert-danger">
|
||||
<i class="fas fa-times-circle me-2"></i>
|
||||
<strong>연결 실패</strong><br>
|
||||
SSL 인증서 신뢰 설정이 필요합니다.
|
||||
</div>`;
|
||||
};
|
||||
|
||||
ws.onclose = function(event) {
|
||||
if (event.code !== 1000) {
|
||||
clearTimeout(timeout);
|
||||
statusDiv.innerHTML = `
|
||||
<div class="alert alert-warning">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
<strong>연결 종료</strong><br>
|
||||
SSL 인증서를 신뢰한 후 다시 시도해주세요.
|
||||
</div>`;
|
||||
}
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
statusDiv.innerHTML = `
|
||||
<div class="alert alert-danger">
|
||||
<i class="fas fa-times-circle me-2"></i>
|
||||
<strong>테스트 오류</strong><br>
|
||||
${error.message}
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user