add icon(128*128)
This commit is contained in:
@@ -72,7 +72,18 @@ class VNCServerApp {
|
||||
this.executeConfirmAction();
|
||||
});
|
||||
|
||||
// 아이콘 관련 이벤트
|
||||
document.getElementById('selectIconBtn').addEventListener('click', () => {
|
||||
document.getElementById('iconFile').click();
|
||||
});
|
||||
|
||||
document.getElementById('iconFile').addEventListener('change', (e) => {
|
||||
this.handleIconFileSelect(e);
|
||||
});
|
||||
|
||||
document.getElementById('removeIconBtn').addEventListener('click', () => {
|
||||
this.removeIconPreview();
|
||||
});
|
||||
}
|
||||
|
||||
async loadServerList() {
|
||||
@@ -201,35 +212,101 @@ class VNCServerApp {
|
||||
}
|
||||
}
|
||||
|
||||
handleIconFileSelect(event) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
// 파일 크기 체크 (1MB)
|
||||
if (file.size > 1024 * 1024) {
|
||||
this.showError('파일 크기는 1MB를 초과할 수 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 이미지 크기 체크 (클라이언트 측에서 미리 확인)
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
if (img.width > 128 || img.height > 128) {
|
||||
this.showSuccess('이미지가 128x128로 자동 리사이즈됩니다.');
|
||||
}
|
||||
URL.revokeObjectURL(img.src); // 메모리 정리
|
||||
};
|
||||
img.src = URL.createObjectURL(file);
|
||||
|
||||
// 파일 타입 체크
|
||||
if (!file.type.startsWith('image/')) {
|
||||
this.showError('이미지 파일만 선택할 수 있습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const iconPreview = document.getElementById('iconPreview');
|
||||
const iconPlaceholder = document.getElementById('iconPlaceholder');
|
||||
const removeIconBtn = document.getElementById('removeIconBtn');
|
||||
|
||||
iconPreview.src = e.target.result;
|
||||
iconPreview.classList.remove('hidden');
|
||||
iconPlaceholder.classList.add('hidden');
|
||||
removeIconBtn.classList.remove('hidden');
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
removeIconPreview() {
|
||||
const iconPreview = document.getElementById('iconPreview');
|
||||
const iconPlaceholder = document.getElementById('iconPlaceholder');
|
||||
const removeIconBtn = document.getElementById('removeIconBtn');
|
||||
const iconFile = document.getElementById('iconFile');
|
||||
|
||||
iconPreview.src = '';
|
||||
iconPreview.classList.add('hidden');
|
||||
iconPlaceholder.classList.remove('hidden');
|
||||
removeIconBtn.classList.add('hidden');
|
||||
iconFile.value = '';
|
||||
}
|
||||
|
||||
showServerModal(server = null) {
|
||||
const modal = document.getElementById('serverModal');
|
||||
const title = document.getElementById('modalTitle');
|
||||
const modalTitle = document.getElementById('modalTitle');
|
||||
const form = document.getElementById('serverForm');
|
||||
|
||||
console.log('서버 정보2:', server); // 디버깅용
|
||||
|
||||
// 폼 초기화
|
||||
form.reset();
|
||||
this.removeIconPreview();
|
||||
|
||||
if (server) {
|
||||
title.textContent = '서버 편집';
|
||||
// 편집 모드
|
||||
modalTitle.textContent = '서버 편집';
|
||||
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('serverTitle').value = server.Title || '';
|
||||
document.getElementById('serverCategory').value = server.Category || '';
|
||||
document.getElementById('serverDescription').value = server.Description || '';
|
||||
document.getElementById('serverArgument').value = server.Argument || '';
|
||||
// 편집 모드 플래그 설정
|
||||
modal.dataset.editMode = 'true';
|
||||
modal.dataset.editUser = server.User;
|
||||
modal.dataset.editIp = server.IP;
|
||||
|
||||
// 아이콘 미리보기 설정
|
||||
if (server.Icon) {
|
||||
const iconPreview = document.getElementById('iconPreview');
|
||||
const iconPlaceholder = document.getElementById('iconPlaceholder');
|
||||
const removeIconBtn = document.getElementById('removeIconBtn');
|
||||
|
||||
iconPreview.src = `${this.apiBase}/icon/${encodeURIComponent(server.User)}/${encodeURIComponent(server.IP)}`;
|
||||
iconPreview.classList.remove('hidden');
|
||||
iconPlaceholder.classList.add('hidden');
|
||||
removeIconBtn.classList.remove('hidden');
|
||||
}
|
||||
|
||||
form.dataset.editMode = 'true';
|
||||
form.dataset.originalUser = server.User;
|
||||
form.dataset.originalIp = server.IP;
|
||||
} else {
|
||||
title.textContent = '서버 추가';
|
||||
form.reset();
|
||||
// 추가 모드 플래그 설정
|
||||
modal.dataset.editMode = 'false';
|
||||
delete modal.dataset.editUser;
|
||||
delete modal.dataset.editIp;
|
||||
// 추가 모드
|
||||
modalTitle.textContent = '서버 추가';
|
||||
delete form.dataset.editMode;
|
||||
delete form.dataset.originalUser;
|
||||
delete form.dataset.originalIp;
|
||||
}
|
||||
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
}
|
||||
|
||||
@@ -356,53 +433,77 @@ class VNCServerApp {
|
||||
}
|
||||
|
||||
async saveServer() {
|
||||
const serverIp = document.getElementById('serverIp').value;
|
||||
const userName = document.getElementById('userName')?.value || '';
|
||||
const modal = document.getElementById('serverModal');
|
||||
const isEditMode = modal.dataset.editMode === 'true';
|
||||
|
||||
if (!userName) {
|
||||
this.showError('사용자 이름이 설정되지 않았습니다. 먼저 설정에서 사용자 이름을 입력해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
const serverData = {
|
||||
user: userName,
|
||||
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
|
||||
};
|
||||
|
||||
try {
|
||||
let url, method;
|
||||
const form = document.getElementById('serverForm');
|
||||
const userName = document.getElementById('userName')?.value || '';
|
||||
|
||||
if (isEditMode) {
|
||||
// 편집 모드: 원본 사용자명과 IP를 쿼리 파라미터로 전달
|
||||
const originalUser = modal.dataset.editUser;
|
||||
const originalIp = modal.dataset.editIp;
|
||||
url = `${this.apiBase}/update/${encodeURIComponent(originalUser)}/${encodeURIComponent(originalIp)}`;
|
||||
method = 'PUT';
|
||||
} else {
|
||||
// 추가 모드
|
||||
url = `${this.apiBase}/add`;
|
||||
method = 'POST';
|
||||
if (!userName) {
|
||||
this.showError('사용자 이름을 먼저 설정해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(serverData)
|
||||
});
|
||||
const serverData = {
|
||||
User: userName,
|
||||
IP: document.getElementById('serverIp').value,
|
||||
Title: document.getElementById('serverTitle').value,
|
||||
Category: document.getElementById('serverCategory').value,
|
||||
Description: document.getElementById('serverDescription').value,
|
||||
Password: document.getElementById('serverPassword').value,
|
||||
Argument: document.getElementById('serverArgument').value
|
||||
};
|
||||
|
||||
if (!response.ok) throw new Error('서버 저장에 실패했습니다.');
|
||||
|
||||
const result = await response.json();
|
||||
this.showSuccess(result.message);
|
||||
// 아이콘 파일이 선택된 경우 Base64로 변환
|
||||
const iconFile = document.getElementById('iconFile');
|
||||
if (iconFile.files.length > 0) {
|
||||
const file = iconFile.files[0];
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = async (e) => {
|
||||
// Base64 데이터에서 헤더 제거 (data:image/png;base64, 부분)
|
||||
const base64Data = e.target.result.split(',')[1];
|
||||
serverData.IconBase64 = base64Data;
|
||||
|
||||
await this.submitServerData(serverData, form);
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
} else {
|
||||
await this.submitServerData(serverData, form);
|
||||
}
|
||||
} catch (error) {
|
||||
this.showError('서버 저장 중 오류가 발생했습니다: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async submitServerData(serverData, form) {
|
||||
try {
|
||||
let response;
|
||||
const isEditMode = form.dataset.editMode === 'true';
|
||||
|
||||
if (isEditMode) {
|
||||
// 편집 모드
|
||||
const originalUser = form.dataset.originalUser;
|
||||
const originalIp = form.dataset.originalIp;
|
||||
response = await fetch(`${this.apiBase}/update/${encodeURIComponent(originalUser)}/${encodeURIComponent(originalIp)}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(serverData)
|
||||
});
|
||||
} else {
|
||||
// 추가 모드
|
||||
response = await fetch(`${this.apiBase}/add`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(serverData)
|
||||
});
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.Message || '서버 저장에 실패했습니다.');
|
||||
}
|
||||
|
||||
this.showSuccess(isEditMode ? '서버가 성공적으로 수정되었습니다.' : '서버가 성공적으로 추가되었습니다.');
|
||||
this.hideServerModal();
|
||||
this.loadServerList();
|
||||
} catch (error) {
|
||||
@@ -543,54 +644,69 @@ class VNCServerApp {
|
||||
}
|
||||
|
||||
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 indent = ' '.repeat(depth);
|
||||
let html = '';
|
||||
|
||||
const headerData = {
|
||||
categoryName: categoryName,
|
||||
depth: depth,
|
||||
bgClass: depth === 0 ? 'bg-gray-50' : 'bg-gray-25',
|
||||
hasContent: hasSubcategories || hasServers,
|
||||
totalItems: totalItems
|
||||
};
|
||||
// 카테고리 헤더
|
||||
if (depth === 0) {
|
||||
html += `<div class="category-header bg-gray-100 px-4 py-2 border-b border-gray-200">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-lg font-semibold text-gray-800 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path>
|
||||
</svg>
|
||||
${this.escapeHtml(categoryName)}
|
||||
</h3>
|
||||
<span class="text-sm text-gray-500">${categoryData.servers.length}개 서버</span>
|
||||
</div>
|
||||
</div>`;
|
||||
} else {
|
||||
html += `<div class="subcategory-header bg-gray-50 px-4 py-2 border-b border-gray-200 ml-${depth * 4}">
|
||||
<div class="flex items-center justify-between">
|
||||
<h4 class="text-md font-medium text-gray-700 flex items-center">
|
||||
<svg class="w-4 h-4 mr-2 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
|
||||
</svg>
|
||||
${this.escapeHtml(categoryName)}
|
||||
</h4>
|
||||
<span class="text-sm text-gray-500">${categoryData.servers.length}개 서버</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
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) {
|
||||
// 서버 목록
|
||||
if (categoryData.servers.length > 0) {
|
||||
categoryData.servers.forEach(server => {
|
||||
console.log('서버 데이터:', server); // 디버깅용
|
||||
const serverName = server.Title || server.title || `${server.User || server.user} - ${server.IP || server.ip}`;
|
||||
const serverIP = server.IP || server.ip;
|
||||
const userName = server.User || server.user;
|
||||
const serverCategory = server.Category || server.category;
|
||||
const serverDescription = server.Description || server.description;
|
||||
const serverArgument = server.Argument || server.argument;
|
||||
|
||||
// 아이콘 URL 생성
|
||||
const iconUrl = `${this.apiBase}/icon/${encodeURIComponent(userName)}/${encodeURIComponent(serverIP)}`;
|
||||
|
||||
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
|
||||
userName: userName,
|
||||
serverIP: serverIP,
|
||||
serverName: this.escapeHtml(serverName),
|
||||
serverCategory: this.escapeHtml(serverCategory),
|
||||
serverDescription: this.escapeHtml(serverDescription),
|
||||
serverArgument: this.escapeHtml(serverArgument),
|
||||
iconUrl: iconUrl
|
||||
};
|
||||
console.log('렌더링할 서버 데이터:', serverData); // 디버깅용
|
||||
|
||||
html += this.renderTemplate('serverItemTemplate', serverData);
|
||||
});
|
||||
}
|
||||
|
||||
// 하위 카테고리 렌더링
|
||||
const sortedSubcategories = Object.keys(categoryData.subcategories).sort();
|
||||
sortedSubcategories.forEach(subcategoryName => {
|
||||
html += this.renderCategoryNode(subcategoryName, categoryData.subcategories[subcategoryName], depth + 1);
|
||||
// 하위 카테고리
|
||||
const subcategories = Object.keys(categoryData.subcategories).sort();
|
||||
subcategories.forEach(subcategory => {
|
||||
html += this.renderCategoryNode(subcategory, categoryData.subcategories[subcategory], depth + 1);
|
||||
});
|
||||
|
||||
html += `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user