diff --git a/Web/wwwroot/index.html b/Web/wwwroot/index.html index 8ea6655..0244dff 100644 --- a/Web/wwwroot/index.html +++ b/Web/wwwroot/index.html @@ -37,21 +37,56 @@ - -
- - + + +
+
+ +
+
+ + + + + +
+
+ + +
+ + + + + + +
+
+ + +
@@ -271,6 +306,19 @@ + + \ No newline at end of file diff --git a/Web/wwwroot/js/app.js b/Web/wwwroot/js/app.js index 3e4fac7..d06b247 100644 --- a/Web/wwwroot/js/app.js +++ b/Web/wwwroot/js/app.js @@ -1,6 +1,8 @@ class VNCServerApp { constructor() { this.apiBase = '/api/vncserver'; + this.allServers = []; // 모든 서버 데이터 저장 + this.filteredServers = []; // 필터링된 서버 데이터 this.init(); // 개발용: Ctrl+R로 강제 새로고침 @@ -84,6 +86,24 @@ class VNCServerApp { document.getElementById('removeIconBtn').addEventListener('click', () => { this.removeIconPreview(); }); + + // 검색 관련 이벤트 + document.getElementById('searchInput').addEventListener('input', (e) => { + this.handleSearch(e.target.value); + }); + + document.getElementById('clearSearchBtn').addEventListener('click', () => { + document.getElementById('searchInput').value = ''; + this.handleSearch(''); + }); + + document.getElementById('categoryFilter').addEventListener('change', (e) => { + this.handleCategoryFilter(e.target.value); + }); + + document.getElementById('clearFiltersBtn').addEventListener('click', () => { + this.clearAllFilters(); + }); } async loadServerList() { @@ -104,7 +124,16 @@ class VNCServerApp { if (!response.ok) throw new Error('서버 목록을 불러올 수 없습니다.'); const servers = await response.json(); - this.renderServerList(servers); + + // 모든 서버 데이터 저장 + this.allServers = servers; + this.filteredServers = [...servers]; + + // 카테고리 필터 옵션 업데이트 + this.updateCategoryFilter(); + + // 서버 목록 렌더링 + this.renderServerList(this.filteredServers); } catch (error) { this.showError('서버 목록을 불러오는 중 오류가 발생했습니다: ' + error.message); } @@ -116,7 +145,17 @@ class VNCServerApp { console.log('서버 데이터:', servers); // 디버깅용 if (servers.length === 0) { - serverList.innerHTML = this.renderTemplate('emptyServerListTemplate', {}); + // 검색 필터가 적용된 상태인지 확인 + const searchTerm = document.getElementById('searchInput').value.trim(); + const selectedCategory = document.getElementById('categoryFilter').value; + + if (searchTerm || selectedCategory) { + // 검색 결과가 없는 경우 + serverList.innerHTML = this.renderTemplate('emptySearchResultTemplate', {}); + } else { + // 서버가 아예 없는 경우 + serverList.innerHTML = this.renderTemplate('emptyServerListTemplate', {}); + } return; } @@ -505,7 +544,7 @@ class VNCServerApp { this.showSuccess(isEditMode ? '서버가 성공적으로 수정되었습니다.' : '서버가 성공적으로 추가되었습니다.'); this.hideServerModal(); - this.loadServerList(); + this.loadServerList(); // 서버 목록 새로고침 (검색 필터도 함께 업데이트) } catch (error) { this.showError('서버 저장 중 오류가 발생했습니다: ' + error.message); } @@ -541,9 +580,9 @@ class VNCServerApp { if (!response.ok) throw new Error('서버 삭제에 실패했습니다.'); - const result = await response.json(); - this.showSuccess(result.message); - this.loadServerList(); + this.showSuccess('서버가 성공적으로 삭제되었습니다.'); + this.hideConfirmModal(); + this.loadServerList(); // 서버 목록 새로고침 (검색 필터도 함께 업데이트) } catch (error) { this.showError('서버 삭제 중 오류가 발생했습니다: ' + error.message); } @@ -723,6 +762,113 @@ class VNCServerApp { } } + updateCategoryFilter() { + const categoryFilter = document.getElementById('categoryFilter'); + const categories = new Set(); + + // 모든 서버에서 카테고리 수집 + this.allServers.forEach(server => { + const category = server.Category || server.category; + if (category && category.trim()) { + // | 기호로 구분된 카테고리들을 분리 + const categoryParts = category.split('|').map(cat => cat.trim()).filter(cat => cat.length > 0); + categoryParts.forEach(cat => categories.add(cat)); + } + }); + + // 기존 옵션 제거 (첫 번째 "모든 카테고리" 옵션 제외) + while (categoryFilter.children.length > 1) { + categoryFilter.removeChild(categoryFilter.lastChild); + } + + // 카테고리 옵션 추가 + Array.from(categories).sort().forEach(category => { + const option = document.createElement('option'); + option.value = category; + option.textContent = category; + categoryFilter.appendChild(option); + }); + } + + handleSearch(searchTerm) { + const clearSearchBtn = document.getElementById('clearSearchBtn'); + + // 검색어가 있으면 X 버튼 표시 + if (searchTerm.trim()) { + clearSearchBtn.classList.remove('hidden'); + } else { + clearSearchBtn.classList.add('hidden'); + } + + this.applyFilters(); + } + + handleCategoryFilter(selectedCategory) { + this.applyFilters(); + } + + clearAllFilters() { + document.getElementById('searchInput').value = ''; + document.getElementById('categoryFilter').value = ''; + document.getElementById('clearSearchBtn').classList.add('hidden'); + this.applyFilters(); + } + + applyFilters() { + const searchTerm = document.getElementById('searchInput').value.toLowerCase().trim(); + const selectedCategory = document.getElementById('categoryFilter').value; + + // 필터링 적용 + this.filteredServers = this.allServers.filter(server => { + // 검색어 필터 + if (searchTerm) { + const searchFields = [ + server.Title || server.title || '', + server.IP || server.ip || '', + server.Category || server.category || '', + server.Description || server.description || '' + ].map(field => field.toLowerCase()); + + const matchesSearch = searchFields.some(field => field.includes(searchTerm)); + if (!matchesSearch) return false; + } + + // 카테고리 필터 + if (selectedCategory) { + const serverCategory = server.Category || server.category || ''; + const categoryParts = serverCategory.split('|').map(cat => cat.trim()); + const matchesCategory = categoryParts.includes(selectedCategory); + if (!matchesCategory) return false; + } + + return true; + }); + + // 검색 결과 정보 업데이트 + this.updateSearchInfo(); + + // 필터링된 서버 목록 렌더링 + this.renderServerList(this.filteredServers); + } + + updateSearchInfo() { + const searchInfo = document.getElementById('searchInfo'); + const searchResultCount = document.getElementById('searchResultCount'); + const searchTerm = document.getElementById('searchInput').value.trim(); + const selectedCategory = document.getElementById('categoryFilter').value; + + if (searchTerm || selectedCategory) { + searchResultCount.textContent = this.filteredServers.length; + searchInfo.classList.remove('hidden'); + } else { + searchInfo.classList.add('hidden'); + } + } + + escapeRegex(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } + }