..
This commit is contained in:
@@ -2,10 +2,26 @@ class VNCServerApp {
|
||||
constructor() {
|
||||
this.apiBase = '/api/vncserver';
|
||||
this.init();
|
||||
|
||||
// 개발용: Ctrl+R로 강제 새로고침
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.ctrlKey && e.key === 'r') {
|
||||
e.preventDefault();
|
||||
window.location.reload(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
init() {
|
||||
async init() {
|
||||
this.bindEvents();
|
||||
await this.loadSettings(); // 설정 먼저 로드
|
||||
|
||||
// 사용자 이름이 없으면 설정 모달 자동 열기
|
||||
const userName = document.getElementById('userName')?.value || '';
|
||||
if (!userName) {
|
||||
this.showSettingsModal();
|
||||
}
|
||||
|
||||
this.loadServerList();
|
||||
this.checkVNCStatus();
|
||||
}
|
||||
@@ -55,11 +71,25 @@ class VNCServerApp {
|
||||
document.getElementById('confirmOk').addEventListener('click', () => {
|
||||
this.executeConfirmAction();
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
async loadServerList() {
|
||||
try {
|
||||
const response = await fetch(`${this.apiBase}/list`);
|
||||
// 설정에서 사용자 이름 가져오기
|
||||
const userName = document.getElementById('userName')?.value || '';
|
||||
|
||||
// 사용자 이름이 없으면 서버 목록을 로드하지 않고 설정 안내 메시지 표시
|
||||
if (!userName) {
|
||||
const serverList = document.getElementById('serverList');
|
||||
serverList.innerHTML = this.renderTemplate('userNameRequiredTemplate', {});
|
||||
return;
|
||||
}
|
||||
|
||||
let url = `${this.apiBase}/list?userName=${encodeURIComponent(userName)}`;
|
||||
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error('서버 목록을 불러올 수 없습니다.');
|
||||
|
||||
const servers = await response.json();
|
||||
@@ -72,48 +102,66 @@ class VNCServerApp {
|
||||
renderServerList(servers) {
|
||||
const serverList = document.getElementById('serverList');
|
||||
|
||||
console.log('서버 데이터:', servers); // 디버깅용
|
||||
|
||||
if (servers.length === 0) {
|
||||
serverList.innerHTML = `
|
||||
<div class="px-6 py-8 text-center text-gray-500">
|
||||
<svg class="w-12 h-12 mx-auto mb-4 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
<p>등록된 서버가 없습니다.</p>
|
||||
<p class="text-sm">새 서버를 추가해보세요.</p>
|
||||
</div>
|
||||
`;
|
||||
serverList.innerHTML = this.renderTemplate('emptyServerListTemplate', {});
|
||||
return;
|
||||
}
|
||||
|
||||
serverList.innerHTML = servers.map(server => `
|
||||
<div class="px-6 py-4 hover:bg-gray-50 transition duration-200">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-3 h-3 bg-green-500 rounded-full"></div>
|
||||
<h3 class="text-lg font-medium text-gray-900">${this.escapeHtml(server.user)}@${this.escapeHtml(server.ip)}</h3>
|
||||
</div>
|
||||
<div class="mt-2 text-sm text-gray-600">
|
||||
<p><span class="font-medium">IP:</span> ${this.escapeHtml(server.ip)}</p>
|
||||
${server.category ? `<p class="mt-1"><span class="font-medium">카테고리:</span> ${this.escapeHtml(server.category)}</p>` : ''}
|
||||
${server.description ? `<p class="mt-1"><span class="font-medium">설명:</span> ${this.escapeHtml(server.description)}</p>` : ''}
|
||||
${server.argument ? `<p class="mt-1"><span class="font-medium">인수:</span> ${this.escapeHtml(server.argument)}</p>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<button onclick="app.connectToServer('${this.escapeHtml(server.user)}', '${this.escapeHtml(server.ip)}')" class="bg-primary hover:bg-blue-700 text-white px-3 py-1 rounded text-sm transition duration-200">
|
||||
연결
|
||||
</button>
|
||||
<button onclick="app.editServer('${this.escapeHtml(server.user)}', '${this.escapeHtml(server.ip)}')" class="bg-secondary hover:bg-gray-600 text-white px-3 py-1 rounded text-sm transition duration-200">
|
||||
편집
|
||||
</button>
|
||||
<button onclick="app.deleteServer('${this.escapeHtml(server.user)}', '${this.escapeHtml(server.ip)}', '${this.escapeHtml(server.user)}@${this.escapeHtml(server.ip)}')" class="bg-danger hover:bg-red-700 text-white px-3 py-1 rounded text-sm transition duration-200">
|
||||
삭제
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
// 계층적 카테고리 구조 생성
|
||||
const categoryTree = {};
|
||||
servers.forEach(server => {
|
||||
console.log('서버 카테고리:', server.category, '타입:', typeof server.category); // 디버깅용
|
||||
console.log('서버 Category:', server.Category, '타입:', typeof server.Category); // 디버깅용
|
||||
|
||||
// category와 Category 둘 다 확인
|
||||
const categoryValue = server.category || server.Category || '';
|
||||
|
||||
if (categoryValue && categoryValue.trim()) {
|
||||
console.log('처리할 카테고리 값:', categoryValue); // 디버깅용
|
||||
// | 기호로 구분된 카테고리들을 분리하여 계층 구조 생성
|
||||
const categoryPath = categoryValue.split('|').map(cat => cat.trim()).filter(cat => cat.length > 0);
|
||||
console.log('카테고리 경로:', categoryPath); // 디버깅용
|
||||
|
||||
let currentLevel = categoryTree;
|
||||
categoryPath.forEach((category, index) => {
|
||||
if (!currentLevel[category]) {
|
||||
currentLevel[category] = {
|
||||
servers: [],
|
||||
subcategories: {}
|
||||
};
|
||||
}
|
||||
|
||||
// 마지막 레벨에만 서버 추가
|
||||
if (index === categoryPath.length - 1) {
|
||||
currentLevel[category].servers.push(server);
|
||||
}
|
||||
|
||||
currentLevel = currentLevel[category].subcategories;
|
||||
});
|
||||
} else {
|
||||
// 카테고리가 없으면 '기타'로 분류
|
||||
if (!categoryTree['기타']) {
|
||||
categoryTree['기타'] = {
|
||||
servers: [],
|
||||
subcategories: {}
|
||||
};
|
||||
}
|
||||
categoryTree['기타'].servers.push(server);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('카테고리 트리:', categoryTree); // 디버깅용
|
||||
|
||||
let html = '';
|
||||
const sortedCategories = Object.keys(categoryTree).sort();
|
||||
|
||||
sortedCategories.forEach(category => {
|
||||
html += this.renderCategoryNode(category, categoryTree[category], 0);
|
||||
});
|
||||
|
||||
serverList.innerHTML = html;
|
||||
}
|
||||
|
||||
async checkVNCStatus() {
|
||||
@@ -133,20 +181,9 @@ class VNCServerApp {
|
||||
|
||||
showVNCStatus(status) {
|
||||
const statusDiv = document.getElementById('status');
|
||||
if (status.isInstalled) {
|
||||
statusDiv.innerHTML = `
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
VNC Viewer가 설치되어 있습니다.
|
||||
</div>
|
||||
<button onclick="app.showSettingsModal()" class="text-green-600 hover:text-green-800 text-sm underline">설정 변경</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
if (status.IsInstalled) {
|
||||
// VNC가 설치되어 있으면 상태 메시지를 숨김
|
||||
statusDiv.innerHTML = '';
|
||||
} else {
|
||||
statusDiv.innerHTML = `
|
||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
||||
@@ -155,7 +192,7 @@ class VNCServerApp {
|
||||
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
VNC Viewer를 찾을 수 없습니다. 경로: ${status.path}
|
||||
VNC Viewer를 찾을 수 없습니다. 경로: ${status.Path}
|
||||
</div>
|
||||
<button onclick="app.showSettingsModal()" class="text-red-600 hover:text-red-800 text-sm underline">설정</button>
|
||||
</div>
|
||||
@@ -174,6 +211,7 @@ class VNCServerApp {
|
||||
document.getElementById('serverUser').value = server.user;
|
||||
document.getElementById('serverIp').value = server.ip;
|
||||
document.getElementById('serverCategory').value = server.category || '';
|
||||
document.getElementById('serverTitle').value = server.title || '';
|
||||
document.getElementById('serverDescription').value = server.description || '';
|
||||
document.getElementById('serverPassword').value = server.password || '';
|
||||
document.getElementById('serverArgument').value = server.argument || '';
|
||||
@@ -211,17 +249,23 @@ class VNCServerApp {
|
||||
|
||||
const settings = await response.json();
|
||||
console.log('로드된 설정:', settings); // 디버깅용
|
||||
console.log('VNC 경로:', settings.vncViewerPath); // 디버깅용
|
||||
console.log('웹 포트:', settings.webServerPort); // 디버깅용
|
||||
console.log('VNC 경로:', settings.VNCViewerPath); // 디버깅용
|
||||
console.log('웹 포트:', settings.WebServerPort); // 디버깅용
|
||||
|
||||
const userNameElement = document.getElementById('userName');
|
||||
const vncPathElement = document.getElementById('vncViewerPath');
|
||||
const vncArgumentElement = document.getElementById('vncArgument');
|
||||
const webPortElement = document.getElementById('webServerPort');
|
||||
|
||||
console.log('사용자 이름 요소:', userNameElement); // 디버깅용
|
||||
console.log('VNC 경로 요소:', vncPathElement); // 디버깅용
|
||||
console.log('VNC 인수 요소:', vncArgumentElement); // 디버깅용
|
||||
console.log('웹 포트 요소:', webPortElement); // 디버깅용
|
||||
|
||||
vncPathElement.value = settings.vncViewerPath || '';
|
||||
webPortElement.value = settings.webServerPort || 8080;
|
||||
userNameElement.value = settings.UserName || '';
|
||||
vncPathElement.value = settings.VNCViewerPath || '';
|
||||
vncArgumentElement.value = settings.Argument || '';
|
||||
webPortElement.value = settings.WebServerPort || 8080;
|
||||
|
||||
console.log('설정 로드 완료'); // 디버깅용
|
||||
console.log('설정된 VNC 경로 값:', vncPathElement.value); // 디버깅용
|
||||
@@ -233,12 +277,23 @@ class VNCServerApp {
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
const userName = document.getElementById('userName').value.trim();
|
||||
const vncPath = document.getElementById('vncViewerPath').value;
|
||||
const vncArgument = document.getElementById('vncArgument').value;
|
||||
const webPort = parseInt(document.getElementById('webServerPort').value);
|
||||
|
||||
// 사용자 이름 필수 검증
|
||||
if (!userName) {
|
||||
this.showError('사용자 이름을 입력해주세요.');
|
||||
document.getElementById('userName').focus();
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = {
|
||||
vncViewerPath: vncPath,
|
||||
webServerPort: webPort
|
||||
UserName: userName,
|
||||
VNCViewerPath: vncPath,
|
||||
Argument: vncArgument,
|
||||
WebServerPort: webPort
|
||||
};
|
||||
|
||||
console.log('저장할 설정:', settings); // 디버깅용
|
||||
@@ -266,6 +321,7 @@ class VNCServerApp {
|
||||
this.showSuccess(result.message);
|
||||
this.hideSettingsModal();
|
||||
this.checkVNCStatus(); // VNC 상태 다시 확인
|
||||
this.loadServerList(); // 서버 목록 다시 로드 (사용자 필터 적용)
|
||||
} catch (error) {
|
||||
console.error('설정 저장 오류:', error); // 디버깅용
|
||||
this.showError('설정 저장 중 오류가 발생했습니다: ' + error.message);
|
||||
@@ -296,6 +352,7 @@ class VNCServerApp {
|
||||
user: serverUser,
|
||||
ip: serverIp,
|
||||
category: document.getElementById('serverCategory').value,
|
||||
title: document.getElementById('serverTitle').value,
|
||||
description: document.getElementById('serverDescription').value,
|
||||
password: document.getElementById('serverPassword').value,
|
||||
argument: document.getElementById('serverArgument').value
|
||||
@@ -428,6 +485,95 @@ class VNCServerApp {
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// 간단한 템플릿 엔진
|
||||
renderTemplate(templateId, data) {
|
||||
const template = document.getElementById(templateId);
|
||||
if (!template) {
|
||||
console.error(`Template not found: ${templateId}`);
|
||||
return '';
|
||||
}
|
||||
|
||||
let html = template.innerHTML;
|
||||
|
||||
// 변수 치환
|
||||
Object.keys(data).forEach(key => {
|
||||
const regex = new RegExp(`{{${key}}}`, 'g');
|
||||
html = html.replace(regex, this.escapeHtml(data[key] || ''));
|
||||
});
|
||||
|
||||
// 조건부 렌더링 ({{#if variable}}...{{/if}})
|
||||
html = html.replace(/\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (match, variable, content) => {
|
||||
return data[variable] ? content : '';
|
||||
});
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
renderCategoryNode(categoryName, categoryData, depth) {
|
||||
const indent = depth * 20; // 들여쓰기
|
||||
const hasSubcategories = Object.keys(categoryData.subcategories).length > 0;
|
||||
const hasServers = categoryData.servers.length > 0;
|
||||
const totalItems = categoryData.servers.length + Object.keys(categoryData.subcategories).length;
|
||||
|
||||
const headerData = {
|
||||
categoryName: categoryName,
|
||||
depth: depth,
|
||||
bgClass: depth === 0 ? 'bg-gray-50' : 'bg-gray-25',
|
||||
hasContent: hasSubcategories || hasServers,
|
||||
totalItems: totalItems
|
||||
};
|
||||
|
||||
let html = `
|
||||
<div class="category-group" style="margin-left: ${indent}px;">
|
||||
${this.renderTemplate('categoryHeaderTemplate', headerData)}
|
||||
<div class="category-content" id="category-${this.escapeHtml(categoryName)}-${depth}">
|
||||
`;
|
||||
|
||||
// 서버 목록 렌더링
|
||||
if (hasServers) {
|
||||
categoryData.servers.forEach(server => {
|
||||
const serverData = {
|
||||
serverName: `${server.User || server.user}@${server.Title || server.title}`,
|
||||
userName: server.User || server.user,
|
||||
serverIP: server.IP || server.ip,
|
||||
serverCategory: server.Category || server.category,
|
||||
serverTitle: server.Title || server.title,
|
||||
serverDescription: server.Description || server.description,
|
||||
serverArgument: server.Argument || server.argument
|
||||
};
|
||||
html += this.renderTemplate('serverItemTemplate', serverData);
|
||||
});
|
||||
}
|
||||
|
||||
// 하위 카테고리 렌더링
|
||||
const sortedSubcategories = Object.keys(categoryData.subcategories).sort();
|
||||
sortedSubcategories.forEach(subcategoryName => {
|
||||
html += this.renderCategoryNode(subcategoryName, categoryData.subcategories[subcategoryName], depth + 1);
|
||||
});
|
||||
|
||||
html += `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
toggleCategory(category, depth = 0) {
|
||||
const content = document.getElementById(`category-${category}-${depth}`);
|
||||
const icon = content.previousElementSibling.querySelector('.category-icon');
|
||||
|
||||
if (content.style.display === 'none') {
|
||||
content.style.display = 'block';
|
||||
icon.style.transform = 'rotate(0deg)';
|
||||
} else {
|
||||
content.style.display = 'none';
|
||||
icon.style.transform = 'rotate(-90deg)';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// 앱 초기화
|
||||
|
||||
Reference in New Issue
Block a user