// 전역 변수 let currentUser = null; let authToken = null; const API_BASE_URL = ''; // DOM 로드 완료 시 초기화 document.addEventListener('DOMContentLoaded', function() { initializeApp(); }); // 앱 초기화 function initializeApp() { hideLoading(); // 저장된 토큰 확인 const savedToken = localStorage.getItem('authToken'); if (savedToken) { authToken = savedToken; validateToken(); } else { showLoginScreen(); } // 이벤트 리스너 설정 setupEventListeners(); } // 이벤트 리스너 설정 function setupEventListeners() { // 로그인 폼 document.getElementById('loginForm').addEventListener('submit', handleLogin); // 회원가입 폼 document.getElementById('registerForm').addEventListener('submit', handleRegister); // 로그아웃 버튼 document.getElementById('logoutBtn').addEventListener('click', handleLogout); // 네비게이션 링크 document.querySelectorAll('.nav-link').forEach(link => { link.addEventListener('click', function(e) { e.preventDefault(); const section = this.getAttribute('data-section'); showSection(section); }); }); // API 키 생성 폼 document.getElementById('createKeyForm').addEventListener('submit', handleCreateApiKey); // 데이터 생성 폼 document.getElementById('createDataForm').addEventListener('submit', handleCreateData); // 모달 외부 클릭 시 닫기 window.addEventListener('click', function(e) { if (e.target.classList.contains('modal')) { e.target.style.display = 'none'; } }); } // 로딩 표시/숨김 function showLoading() { document.getElementById('loading').style.display = 'flex'; } function hideLoading() { document.getElementById('loading').style.display = 'none'; } // 로그인 화면 표시 function showLoginScreen() { document.getElementById('loginScreen').style.display = 'flex'; document.getElementById('dashboard').style.display = 'none'; } // 대시보드 표시 function showDashboard() { document.getElementById('loginScreen').style.display = 'none'; document.getElementById('dashboard').style.display = 'grid'; // 사용자 정보 표시 if (currentUser) { document.getElementById('currentUser').textContent = currentUser.username; const roleElement = document.getElementById('userRole'); roleElement.textContent = currentUser.role; roleElement.className = `role-badge ${currentUser.role}`; // 관리자 권한에 따른 UI 조정 if (currentUser.role === 'admin') { document.body.classList.add('admin'); } } // 초기 데이터 로드 loadDashboardData(); // API 키 사용 횟수 실시간 업데이트를 위한 주기적 새로고침 (30초마다) if (window.apiKeyRefreshInterval) { clearInterval(window.apiKeyRefreshInterval); } window.apiKeyRefreshInterval = setInterval(() => { if (document.getElementById('apiKeysSection').style.display !== 'none') { loadApiKeys(); } }, 30000); } // 토큰 검증 async function validateToken() { try { showLoading(); const response = await fetch('/auth/me', { headers: { 'Authorization': `Bearer ${authToken}` } }); if (response.ok) { const data = await response.json(); currentUser = data.user; showDashboard(); } else { localStorage.removeItem('authToken'); authToken = null; showLoginScreen(); } } catch (error) { console.error('토큰 검증 실패:', error); localStorage.removeItem('authToken'); authToken = null; showLoginScreen(); } finally { hideLoading(); } } // 로그인 처리 async function handleLogin(e) { e.preventDefault(); const username = document.getElementById('loginUsername').value; const password = document.getElementById('loginPassword').value; try { showLoading(); const response = await fetch('/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); const data = await response.json(); if (response.ok) { authToken = data.token; currentUser = data.user; localStorage.setItem('authToken', authToken); showToast('로그인 성공!', 'success'); showDashboard(); } else { showToast(data.message || '로그인 실패', 'error'); } } catch (error) { console.error('로그인 오류:', error); showToast('로그인 중 오류가 발생했습니다', 'error'); } finally { hideLoading(); } } // 회원가입 처리 async function handleRegister(e) { e.preventDefault(); const username = document.getElementById('registerUsername').value; const email = document.getElementById('registerEmail').value; const password = document.getElementById('registerPassword').value; try { showLoading(); const response = await fetch('/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, email, password }) }); const data = await response.json(); if (response.ok) { showToast('회원가입 성공! 로그인해주세요.', 'success'); showLoginTab(); document.getElementById('registerForm').reset(); } else { showToast(data.message || '회원가입 실패', 'error'); } } catch (error) { console.error('회원가입 오류:', error); showToast('회원가입 중 오류가 발생했습니다', 'error'); } finally { hideLoading(); } } // 로그아웃 처리 function handleLogout() { localStorage.removeItem('authToken'); authToken = null; currentUser = null; document.body.classList.remove('admin'); // API 키 새로고침 인터벌 정리 if (window.apiKeyRefreshInterval) { clearInterval(window.apiKeyRefreshInterval); window.apiKeyRefreshInterval = null; } showLoginScreen(); showToast('로그아웃되었습니다', 'info'); } // 탭 전환 function showLoginTab() { document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active')); document.querySelectorAll('.tab-btn')[0].classList.add('active'); document.getElementById('loginForm').style.display = 'block'; document.getElementById('registerForm').style.display = 'none'; } function showRegisterTab() { document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active')); document.querySelectorAll('.tab-btn')[1].classList.add('active'); document.getElementById('loginForm').style.display = 'none'; document.getElementById('registerForm').style.display = 'block'; } // 섹션 표시 function showSection(sectionName) { // 모든 섹션 숨기기 document.querySelectorAll('.content-section').forEach(section => { section.classList.remove('active'); }); // 모든 네비게이션 링크 비활성화 document.querySelectorAll('.nav-link').forEach(link => { link.classList.remove('active'); }); // 선택된 섹션 표시 const targetSection = document.getElementById(`${sectionName}-section`); if (targetSection) { targetSection.classList.add('active'); } // 선택된 네비게이션 링크 활성화 const targetLink = document.querySelector(`[data-section="${sectionName}"]`); if (targetLink) { targetLink.classList.add('active'); } // 섹션별 데이터 로드 loadSectionData(sectionName); } // 대시보드 데이터 로드 async function loadDashboardData() { await Promise.all([ loadApiKeys(), loadStats(), loadData() ]); if (currentUser && currentUser.role === 'admin') { await Promise.all([ loadUsers(), checkSystemStatus() ]); } } // 섹션별 데이터 로드 async function loadSectionData(sectionName) { switch (sectionName) { case 'overview': await loadStats(); break; case 'api-keys': await loadApiKeys(); break; case 'data-management': await loadData(); break; case 'users': if (currentUser && currentUser.role === 'admin') { await loadUsers(); } break; case 'api-logs': if (currentUser && currentUser.role === 'admin') { await loadApiLogs(); await loadApiLogStats(); } break; case 'api-tester': await loadApiTester(); break; case 'monitoring': if (currentUser && currentUser.role === 'admin') { await checkSystemStatus(); } break; } } // API 키 목록 로드 async function loadApiKeys() { try { showLoading(); const response = await fetch('/auth/api-keys', { headers: { 'Authorization': `Bearer ${authToken}` } }); if (response.ok) { const data = await response.json(); displayApiKeys(data.apiKeys || []); } else { console.error('API 키 로드 실패:', response.status, response.statusText); showToast('API 키 목록을 불러올 수 없습니다', 'error'); } } catch (error) { console.error('API 키 로드 실패:', error); showToast('API 키 로드 중 오류가 발생했습니다', 'error'); } finally { hideLoading(); } } // 권한 라벨 반환 function getPermissionLabel(permission) { const labels = { 'read': '읽기', 'write': '쓰기', 'delete': '삭제', 'admin': '관리자' }; return labels[permission] || permission; } // API 키 표시 function displayApiKeys(apiKeys) { const tbody = document.querySelector('#apiKeysTable tbody'); if (!tbody) { console.error('API 키 테이블을 찾을 수 없습니다'); return; } tbody.innerHTML = ''; if (!apiKeys || apiKeys.length === 0) { tbody.innerHTML = ` 생성된 API 키가 없습니다. `; return; } apiKeys.forEach(key => { const row = document.createElement('tr'); row.innerHTML = ` ${key.keyName}
${key.apiKey}
${Array.isArray(key.permissions) && key.permissions.length > 0 ? key.permissions.map(p => `${getPermissionLabel(p)}`).join(' ') : '권한 없음' }
${key.usageCount || 0} ${key.lastUsed ? new Date(key.lastUsed).toLocaleString() : '사용 안함'} ${new Date(key.createdAt).toLocaleString()} ${key.isActive ? '활성' : '비활성'} `; tbody.appendChild(row); }); } // 통계 로드 async function loadStats() { try { if (currentUser && currentUser.role === 'admin') { const response = await fetch('/auth/stats', { headers: { 'Authorization': `Bearer ${authToken}` } }); if (response.ok) { const data = await response.json(); displayStats(data.stats); } } else { // 일반 사용자는 자신의 API 키 통계만 표시 const response = await fetch('/auth/api-keys', { headers: { 'Authorization': `Bearer ${authToken}` } }); if (response.ok) { const data = await response.json(); const userStats = { totalKeys: data.apiKeys.length, totalRequests: data.apiKeys.reduce((sum, key) => sum + key.usageCount, 0), activeKeysToday: data.apiKeys.filter(key => key.isActive).length }; displayStats(userStats); } } } catch (error) { console.error('통계 로드 실패:', error); } } // 통계 표시 function displayStats(stats) { document.getElementById('totalKeys').textContent = stats.totalKeys || 0; document.getElementById('totalRequests').textContent = stats.totalRequests || 0; document.getElementById('activeKeysToday').textContent = stats.activeKeysToday || 0; if (stats.totalUsers !== undefined) { document.getElementById('totalUsers').textContent = stats.totalUsers; } } // 데이터 로드 async function loadData() { try { // API 키가 있는 경우 해당 키로 요청, 없으면 JWT 토큰으로 요청 const headers = { 'Authorization': `Bearer ${authToken}` }; const response = await fetch('/api/data', { headers }); if (response.ok) { const data = await response.json(); displayData(data.data); } } catch (error) { console.error('데이터 로드 실패:', error); } } // 데이터 표시 function displayData(dataList) { const tbody = document.querySelector('#dataTable tbody'); tbody.innerHTML = ''; dataList.forEach(item => { const row = document.createElement('tr'); row.innerHTML = ` ${item[0]} ${item[1]} ${item[2] || ''}
${item[3] ? item[3].substring(0, 100) + '...' : ''}
${item[4] ? new Date(item[4]).toLocaleString() : ''} ${item[5] ? new Date(item[5]).toLocaleString() : ''} `; tbody.appendChild(row); }); } // 사용자 목록 로드 (관리자 전용) async function loadUsers() { try { const response = await fetch('/auth/users', { headers: { 'Authorization': `Bearer ${authToken}` } }); if (response.ok) { const data = await response.json(); displayUsers(data.users); } } catch (error) { console.error('사용자 목록 로드 실패:', error); } } // 사용자 목록 표시 function displayUsers(users) { const tbody = document.querySelector('#usersTable tbody'); tbody.innerHTML = ''; users.forEach(user => { const row = document.createElement('tr'); row.innerHTML = ` ${user.id} ${user.username} ${user.email} ${user.role} ${user.isActive ? '활성' : '비활성'} ${new Date(user.createdAt).toLocaleString()} ${user.lastLogin ? new Date(user.lastLogin).toLocaleString() : '없음'} `; tbody.appendChild(row); }); } // 시스템 상태 확인 async function checkSystemStatus() { try { // 서버 상태 확인 const serverResponse = await fetch('/api/health'); const serverStatus = document.getElementById('serverStatus'); if (serverResponse.ok) { serverStatus.innerHTML = ' 온라인'; serverStatus.className = 'status-indicator online'; } else { serverStatus.innerHTML = ' 오프라인'; serverStatus.className = 'status-indicator offline'; } // 데이터베이스 상태는 서버 응답으로 판단 const dbStatus = document.getElementById('dbStatus'); if (serverResponse.ok) { dbStatus.innerHTML = ' 연결됨'; dbStatus.className = 'status-indicator online'; } else { dbStatus.innerHTML = ' 연결 실패'; dbStatus.className = 'status-indicator offline'; } } catch (error) { console.error('시스템 상태 확인 실패:', error); const serverStatus = document.getElementById('serverStatus'); const dbStatus = document.getElementById('dbStatus'); serverStatus.innerHTML = ' 오프라인'; serverStatus.className = 'status-indicator offline'; dbStatus.innerHTML = ' 연결 실패'; dbStatus.className = 'status-indicator offline'; } } // API 키 생성 모달 표시 function showCreateKeyModal() { document.getElementById('createKeyModal').style.display = 'block'; } // 데이터 생성 모달 표시 function showCreateDataModal() { document.getElementById('createDataModal').style.display = 'block'; } // 모달 닫기 function closeModal(modalId) { document.getElementById(modalId).style.display = 'none'; } // API 키 생성 완료 모달 표시 function showApiKeyCreatedModal(apiKey, keyName) { // 기존 모달이 있다면 제거 const existingModal = document.getElementById('apiKeyCreatedModal'); if (existingModal) { existingModal.remove(); } // 새 모달 생성 const modal = document.createElement('div'); modal.id = 'apiKeyCreatedModal'; modal.className = 'modal'; modal.style.display = 'block'; modal.innerHTML = ` `; document.body.appendChild(modal); // 모달 외부 클릭 시 닫기 modal.addEventListener('click', function(e) { if (e.target === modal) { closeApiKeyCreatedModal(); } }); } // 새 API 키 복사 function copyNewApiKey(apiKey) { if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(apiKey).then(() => { showToast('API 키가 클립보드에 복사되었습니다!', 'success'); }).catch(() => { fallbackCopyText(apiKey); }); } else { fallbackCopyText(apiKey); } } // API 키 생성 완료 모달 닫기 function closeApiKeyCreatedModal() { const modal = document.getElementById('apiKeyCreatedModal'); if (modal) { modal.remove(); } } // API 키 생성 처리 async function handleCreateApiKey(e) { e.preventDefault(); const keyName = document.getElementById('keyName').value; const permissions = Array.from(document.querySelectorAll('#createKeyForm input[type="checkbox"]:checked')) .map(cb => cb.value); try { showLoading(); const response = await fetch('/auth/api-keys', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}` }, body: JSON.stringify({ keyName, permissions }) }); const data = await response.json(); if (response.ok) { showToast('API 키가 생성되었습니다!', 'success'); closeModal('createKeyModal'); document.getElementById('createKeyForm').reset(); // 생성된 API 키를 복사 가능한 모달로 표시 showApiKeyCreatedModal(data.apiKey, data.keyName); await loadApiKeys(); } else { showToast(data.message || 'API 키 생성 실패', 'error'); } } catch (error) { console.error('API 키 생성 오류:', error); showToast('API 키 생성 중 오류가 발생했습니다', 'error'); } finally { hideLoading(); } } // 데이터 생성 처리 async function handleCreateData(e) { e.preventDefault(); const name = document.getElementById('dataName').value; const description = document.getElementById('dataDescription').value; const dataValue = document.getElementById('dataValue').value; try { showLoading(); const response = await fetch('/api/data', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}` }, body: JSON.stringify({ name, description, dataValue }) }); const data = await response.json(); if (response.ok) { showToast('데이터가 생성되었습니다!', 'success'); closeModal('createDataModal'); document.getElementById('createDataForm').reset(); await loadData(); } else { showToast(data.message || '데이터 생성 실패', 'error'); } } catch (error) { console.error('데이터 생성 오류:', error); showToast('데이터 생성 중 오류가 발생했습니다', 'error'); } finally { hideLoading(); } } // API 키 삭제 async function deleteApiKey(keyId, keyName) { if (!confirm(`정말로 "${keyName}" API 키를 삭제하시겠습니까?\n\n⚠️ 이 작업은 되돌릴 수 없습니다!`)) { return; } try { showLoading(); const response = await fetch(`/auth/api-keys/${keyId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${authToken}` } }); const data = await response.json(); if (response.ok) { showToast('API 키가 삭제되었습니다', 'success'); await loadApiKeys(); } else { showToast(data.message || 'API 키 삭제 실패', 'error'); } } catch (error) { console.error('API 키 삭제 오류:', error); showToast('API 키 삭제 중 오류가 발생했습니다', 'error'); } finally { hideLoading(); } } // 데이터 삭제 async function deleteData(id) { if (!confirm('이 데이터를 삭제하시겠습니까?')) { return; } try { showLoading(); const response = await fetch(`/api/data/${id}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${authToken}` } }); const data = await response.json(); if (response.ok) { showToast('데이터가 삭제되었습니다', 'success'); await loadData(); } else { showToast(data.message || '데이터 삭제 실패', 'error'); } } catch (error) { console.error('데이터 삭제 오류:', error); showToast('데이터 삭제 중 오류가 발생했습니다', 'error'); } finally { hideLoading(); } } // API 연결 테스트 async function testApiConnection() { try { showLoading(); const response = await fetch('/api/health'); if (response.ok) { const data = await response.json(); showToast('API 연결이 정상입니다!', 'success'); } else { showToast('API 연결에 문제가 있습니다', 'error'); } } catch (error) { console.error('API 연결 테스트 실패:', error); showToast('API 연결 테스트 실패', 'error'); } finally { hideLoading(); } } // 토스트 알림 표시 function showToast(message, type = 'info') { const toast = document.getElementById('toast'); // 기존 타이머가 있다면 제거 if (toast.hideTimer) { clearTimeout(toast.hideTimer); } toast.textContent = message; toast.className = `toast ${type}`; toast.classList.add('show'); // 새로운 타이머 설정 toast.hideTimer = setTimeout(() => { toast.classList.remove('show'); // 애니메이션이 끝난 후 완전히 숨김 setTimeout(() => { if (!toast.classList.contains('show')) { toast.style.display = 'none'; } }, 300); }, 3000); // 토스트가 보일 때는 display를 block으로 설정 toast.style.display = 'block'; // 클릭하면 즉시 닫기 toast.onclick = () => { if (toast.hideTimer) { clearTimeout(toast.hideTimer); } toast.classList.remove('show'); setTimeout(() => { toast.style.display = 'none'; }, 300); }; } // API 키 복사 기능 function copyApiKey(apiKey, keyName) { // 클립보드에 복사 if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(apiKey).then(() => { showToast(`"${keyName}" API 키가 클립보드에 복사되었습니다!`, 'success'); }).catch(() => { // 폴백: 텍스트 선택 fallbackCopyApiKey(apiKey, keyName); }); } else { // 폴백: 텍스트 선택 fallbackCopyApiKey(apiKey, keyName); } } // 폴백 복사 기능 (구형 브라우저용) function fallbackCopyApiKey(apiKey, keyName) { const textArea = document.createElement('textarea'); textArea.value = apiKey; textArea.style.position = 'fixed'; textArea.style.left = '-999999px'; textArea.style.top = '-999999px'; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { document.execCommand('copy'); showToast(`"${keyName}" API 키가 클립보드에 복사되었습니다!`, 'success'); } catch (err) { console.error('복사 실패:', err); showToast('복사에 실패했습니다. 수동으로 선택해서 복사해주세요.', 'warning'); // API 키를 선택 상태로 만들기 const keyElement = document.querySelector(`#key-${keyId}`); if (keyElement) { const range = document.createRange(); range.selectNodeContents(keyElement); const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range); } } document.body.removeChild(textArea); } // API 키 숨기기/보이기 토글 function toggleApiKeyVisibility(keyId) { const keyElement = document.getElementById(`key-${keyId}`); const toggleBtn = document.getElementById(`toggle-${keyId}`); if (keyElement.classList.contains('api-key-hidden')) { // 보이기 keyElement.classList.remove('api-key-hidden'); toggleBtn.innerHTML = ''; toggleBtn.title = '숨기기'; } else { // 숨기기 keyElement.classList.add('api-key-hidden'); toggleBtn.innerHTML = ''; toggleBtn.title = '보이기'; } } // 모든 API 키 복사 (여러 개가 있을 때) function copyAllApiKeys() { const apiKeyElements = document.querySelectorAll('.api-key-value'); const apiKeys = Array.from(apiKeyElements).map(el => el.textContent).join('\n'); if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(apiKeys).then(() => { showToast(`${apiKeyElements.length}개의 API 키가 모두 복사되었습니다!`, 'success'); }).catch(() => { fallbackCopyText(apiKeys); }); } else { fallbackCopyText(apiKeys); } } // 텍스트 복사 폴백 함수 function fallbackCopyText(text) { const textArea = document.createElement('textarea'); textArea.value = text; textArea.style.position = 'fixed'; textArea.style.left = '-999999px'; textArea.style.top = '-999999px'; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { document.execCommand('copy'); showToast('클립보드에 복사되었습니다!', 'success'); } catch (err) { showToast('복사에 실패했습니다.', 'error'); } document.body.removeChild(textArea); } // 탭 기능 function showTab(tabId) { // 모든 탭 콘텐츠 숨기기 document.querySelectorAll('.tab-content').forEach(tab => { tab.classList.remove('active'); }); // 모든 탭 버튼 비활성화 document.querySelectorAll('.tab-btn').forEach(btn => { btn.classList.remove('active'); }); // 선택된 탭 콘텐츠 보이기 const selectedTab = document.getElementById(tabId); if (selectedTab) { selectedTab.classList.add('active'); } // 클릭된 버튼 활성화 event.target.classList.add('active'); } // 유틸리티 함수들 function formatDate(dateString) { return new Date(dateString).toLocaleString(); } function formatNumber(num) { return new Intl.NumberFormat().format(num); } // API 로그 관련 함수들 let currentLogPage = 1; const logsPerPage = 50; // API 로그 목록 로드 async function loadApiLogs(page = 1) { try { showLoading(); const filters = { startDate: document.getElementById('startDate').value, endDate: document.getElementById('endDate').value, method: document.getElementById('methodFilter').value, endpoint: document.getElementById('endpointFilter').value, username: document.getElementById('usernameFilter').value }; const queryParams = new URLSearchParams({ page: page, limit: logsPerPage, ...Object.fromEntries(Object.entries(filters).filter(([_, v]) => v)) }); const response = await fetch(`/logs/api-logs?${queryParams}`, { headers: { 'Authorization': `Bearer ${authToken}` } }); if (response.ok) { const data = await response.json(); displayApiLogs(data.logs); displayLogPagination(data.page, data.totalPages, data.total); currentLogPage = page; } else { throw new Error('API 로그 로드 실패'); } } catch (error) { console.error('API 로그 로드 오류:', error); showToast('API 로그 로드에 실패했습니다.', 'error'); } finally { hideLoading(); } } // API 로그 통계 로드 async function loadApiLogStats() { try { const response = await fetch('/logs/api-logs/stats', { headers: { 'Authorization': `Bearer ${authToken}` } }); if (response.ok) { const stats = await response.json(); document.getElementById('totalApiRequests').textContent = formatNumber(stats.totalRequests); document.getElementById('todayApiRequests').textContent = formatNumber(stats.todayRequests); } } catch (error) { console.error('API 로그 통계 로드 오류:', error); } } // API 로그 표시 function displayApiLogs(logs) { const tbody = document.querySelector('#apiLogsTable tbody'); tbody.innerHTML = ''; if (logs.length === 0) { tbody.innerHTML = '로그가 없습니다.'; return; } logs.forEach(log => { const row = document.createElement('tr'); const methodClass = `method-${log.method.toLowerCase()}`; const statusClass = getStatusClass(log.responseStatus); row.innerHTML = ` ${formatDate(log.createdAt)} ${log.method} ${log.endpoint} ${log.username || '-'} ${log.apiKeyName || '-'} ${log.responseStatus} ${log.responseTime}ms ${log.ipAddress || '-'} `; tbody.appendChild(row); }); } // 상태 코드에 따른 CSS 클래스 반환 function getStatusClass(status) { if (status >= 200 && status < 300) return 'status-success'; if (status >= 400 && status < 500) return 'status-warning'; if (status >= 500) return 'status-error'; return 'status-success'; } // 로그 페이지네이션 표시 function displayLogPagination(currentPage, totalPages, totalItems) { const pagination = document.getElementById('logPagination'); pagination.innerHTML = ''; if (totalPages <= 1) return; // 이전 버튼 const prevBtn = document.createElement('button'); prevBtn.textContent = '이전'; prevBtn.disabled = currentPage === 1; prevBtn.onclick = () => loadApiLogs(currentPage - 1); pagination.appendChild(prevBtn); // 페이지 번호들 const startPage = Math.max(1, currentPage - 2); const endPage = Math.min(totalPages, currentPage + 2); for (let i = startPage; i <= endPage; i++) { const pageBtn = document.createElement('button'); pageBtn.textContent = i; pageBtn.className = i === currentPage ? 'active' : ''; pageBtn.onclick = () => loadApiLogs(i); pagination.appendChild(pageBtn); } // 다음 버튼 const nextBtn = document.createElement('button'); nextBtn.textContent = '다음'; nextBtn.disabled = currentPage === totalPages; nextBtn.onclick = () => loadApiLogs(currentPage + 1); pagination.appendChild(nextBtn); // 총 개수 표시 const info = document.createElement('span'); info.textContent = ` (총 ${formatNumber(totalItems)}개)`; info.style.marginLeft = '1rem'; info.style.color = '#6c757d'; pagination.appendChild(info); } // 로그 필터 초기화 function clearLogFilters() { document.getElementById('startDate').value = ''; document.getElementById('endDate').value = ''; document.getElementById('methodFilter').value = ''; document.getElementById('endpointFilter').value = ''; document.getElementById('usernameFilter').value = ''; loadApiLogs(1); } // API 테스터 관련 함수들 async function loadApiTester() { // API 키 목록을 셀렉트박스에 로드 try { const response = await fetch('/auth/api-keys', { headers: { 'Authorization': `Bearer ${authToken}` } }); if (response.ok) { const data = await response.json(); const select = document.getElementById('testApiKey'); select.innerHTML = ''; (data.apiKeys || []).forEach(key => { const option = document.createElement('option'); option.value = key.apiKey; option.textContent = `${key.keyName} (${key.apiKey.substring(0, 8)}...)`; select.appendChild(option); }); } } catch (error) { console.error('API 키 로드 실패:', error); } // 메소드 변경 시 요청 본문 표시/숨김 document.getElementById('testMethod').addEventListener('change', function() { const method = this.value; const bodyGroup = document.getElementById('requestBodyGroup'); if (method === 'POST' || method === 'PUT') { bodyGroup.style.display = 'block'; } else { bodyGroup.style.display = 'none'; } }); } // API 요청 전송 async function sendApiRequest() { const method = document.getElementById('testMethod').value; const url = document.getElementById('testUrl').value; const customApiKey = document.getElementById('customApiKey').value; const selectedApiKey = document.getElementById('testApiKey').value; const body = document.getElementById('testBody').value; const apiKey = customApiKey || selectedApiKey; if (!apiKey) { showToast('API 키를 선택하거나 입력해주세요.', 'error'); return; } if (!url) { showToast('엔드포인트를 입력해주세요.', 'error'); return; } const fullUrl = `http://localhost:5577${url}`; const startTime = Date.now(); try { showLoading(); const requestOptions = { method: method, headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' } }; if ((method === 'POST' || method === 'PUT') && body) { try { JSON.parse(body); // JSON 유효성 검사 requestOptions.body = body; } catch (e) { showToast('요청 본문이 유효한 JSON이 아닙니다.', 'error'); return; } } const response = await fetch(fullUrl, requestOptions); const endTime = Date.now(); const responseTime = endTime - startTime; // 응답 헤더 수집 const headers = {}; response.headers.forEach((value, key) => { headers[key] = value; }); // 응답 본문 읽기 let responseBody; const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('application/json')) { responseBody = await response.json(); } else { responseBody = await response.text(); } // 결과 표시 displayApiResponse({ status: response.status, statusText: response.statusText, headers: headers, body: responseBody, responseTime: responseTime }); showToast(`API 호출 완료 (${responseTime}ms)`, 'success'); } catch (error) { console.error('API 요청 실패:', error); showToast(`API 요청 실패: ${error.message}`, 'error'); // 오류 응답 표시 displayApiResponse({ status: 0, statusText: 'Network Error', headers: {}, body: { error: error.message }, responseTime: Date.now() - startTime }); } finally { hideLoading(); } } // API 응답 표시 function displayApiResponse(response) { const responseDiv = document.getElementById('apiResponse'); const statusSpan = document.getElementById('responseStatus'); const timeSpan = document.getElementById('responseTime'); const headersDiv = document.getElementById('responseHeaders'); const bodyDiv = document.getElementById('responseBody'); // 상태 표시 statusSpan.textContent = `${response.status} ${response.statusText}`; statusSpan.className = `response-status ${response.status >= 200 && response.status < 300 ? 'success' : 'error'}`; // 응답 시간 표시 timeSpan.textContent = `${response.responseTime}ms`; // 헤더 표시 headersDiv.textContent = JSON.stringify(response.headers, null, 2); // 본문 표시 if (typeof response.body === 'object') { bodyDiv.textContent = JSON.stringify(response.body, null, 2); } else { bodyDiv.textContent = response.body; } // 응답 영역 표시 responseDiv.style.display = 'block'; responseDiv.scrollIntoView({ behavior: 'smooth' }); } // API 테스트 초기화 function clearApiTest() { document.getElementById('testMethod').value = 'GET'; document.getElementById('testUrl').value = '/api/data'; document.getElementById('customApiKey').value = ''; document.getElementById('testApiKey').value = ''; document.getElementById('testBody').value = ''; document.getElementById('requestBodyGroup').style.display = 'none'; document.getElementById('apiResponse').style.display = 'none'; } // 예제 로드 function loadExample(type) { const methodSelect = document.getElementById('testMethod'); const urlInput = document.getElementById('testUrl'); const bodyTextarea = document.getElementById('testBody'); const bodyGroup = document.getElementById('requestBodyGroup'); switch (type) { case 'getAllUsers': methodSelect.value = 'GET'; urlInput.value = '/api/users'; bodyGroup.style.display = 'none'; break; case 'getUser': methodSelect.value = 'GET'; urlInput.value = '/api/users/user001'; bodyGroup.style.display = 'none'; break; case 'createUser': methodSelect.value = 'POST'; urlInput.value = '/api/users'; bodyTextarea.value = JSON.stringify({ USER_ID: "user999", USER_NAME: "홍길동", DEPT_CODE: "IT001", EMAIL: "hong@example.com", PHONE: "010-1234-5678" }, null, 2); bodyGroup.style.display = 'block'; break; case 'updateUser': methodSelect.value = 'PUT'; urlInput.value = '/api/users/user999'; bodyTextarea.value = JSON.stringify({ USER_NAME: "홍길동(수정)", DEPT_CODE: "IT002", EMAIL: "hong.updated@example.com", PHONE: "010-9876-5432" }, null, 2); bodyGroup.style.display = 'block'; break; case 'deleteUser': methodSelect.value = 'DELETE'; urlInput.value = '/api/users/user999'; bodyGroup.style.display = 'none'; break; } }