// System Dashboard JavaScript console.log('πŸš€ system-dashboard.js loaded'); import { apiRequest } from './api-helper.js'; import { getCurrentUser } from './auth.js'; console.log('πŸ“¦ modules imported successfully'); // μ „μ—­ λ³€μˆ˜ let systemData = { users: [], logs: [], systemStatus: {} }; // Initialize on page load document.addEventListener('DOMContentLoaded', function() { console.log('πŸ“„ DOM loaded, starting initialization'); initializeSystemDashboard(); setupEventListeners(); }); // Setup event listeners function setupEventListeners() { // Add event listeners to all data-action buttons const actionButtons = document.querySelectorAll('[data-action]'); actionButtons.forEach(button => { const action = button.getAttribute('data-action'); switch(action) { case 'account-management': button.addEventListener('click', openAccountManagement); console.log('βœ… Account management button listener added'); break; case 'system-logs': button.addEventListener('click', openSystemLogs); console.log('βœ… System logs button listener added'); break; case 'database-management': button.addEventListener('click', openDatabaseManagement); console.log('βœ… Database management button listener added'); break; case 'system-settings': button.addEventListener('click', openSystemSettings); console.log('βœ… System settings button listener added'); break; case 'backup-management': button.addEventListener('click', openBackupManagement); console.log('βœ… Backup management button listener added'); break; case 'monitoring': button.addEventListener('click', openMonitoring); console.log('βœ… Monitoring button listener added'); break; case 'close-modal': button.addEventListener('click', () => closeModal('account-modal')); console.log('βœ… Modal close button listener added'); break; } }); console.log(`🎯 Total ${actionButtons.length} event listeners setup completed`); } // Initialize system dashboard async function initializeSystemDashboard() { try { console.log('πŸš€ Starting system dashboard initialization...'); // Load user info await loadUserInfo(); console.log('βœ… User info loaded'); // Load system status await loadSystemStatus(); console.log('βœ… System status loaded'); // Load user statistics await loadUserStats(); console.log('βœ… User statistics loaded'); // Load recent activities await loadRecentActivities(); console.log('βœ… Recent activities loaded'); // Setup auto-refresh (every 30 seconds) setInterval(refreshSystemStatus, 30000); console.log('πŸŽ‰ System dashboard initialization completed'); } catch (error) { console.error('❌ System dashboard initialization error:', error); showNotification('Error loading system dashboard', 'error'); } } // μ‚¬μš©μž 정보 λ‘œλ“œ async function loadUserInfo() { try { const user = getCurrentUser(); if (user && user.name) { document.getElementById('user-name').textContent = user.name; } } catch (error) { console.error('μ‚¬μš©μž 정보 λ‘œλ“œ 였λ₯˜:', error); } } // μ‹œμŠ€ν…œ μƒνƒœ λ‘œλ“œ async function loadSystemStatus() { try { // μ„œλ²„ μƒνƒœ 확인 const serverStatus = await checkServerStatus(); updateServerStatus(serverStatus); // λ°μ΄ν„°λ² μ΄μŠ€ μƒνƒœ 확인 const dbStatus = await checkDatabaseStatus(); updateDatabaseStatus(dbStatus); // μ‹œμŠ€ν…œ μ•Œλ¦Ό 확인 const alerts = await getSystemAlerts(); updateSystemAlerts(alerts); } catch (error) { console.error('μ‹œμŠ€ν…œ μƒνƒœ λ‘œλ“œ 였λ₯˜:', error); } } // μ„œλ²„ μƒνƒœ 확인 async function checkServerStatus() { try { const response = await apiRequest('/api/system/status', 'GET'); return response.success ? 'online' : 'offline'; } catch (error) { return 'offline'; } } // λ°μ΄ν„°λ² μ΄μŠ€ μƒνƒœ 확인 async function checkDatabaseStatus() { try { const response = await apiRequest('/api/system/db-status', 'GET'); return response; } catch (error) { return { status: 'error', connections: 0 }; } } // μ‹œμŠ€ν…œ μ•Œλ¦Ό κ°€μ Έμ˜€κΈ° async function getSystemAlerts() { try { const response = await apiRequest('/api/system/alerts', 'GET'); return response.alerts || []; } catch (error) { return []; } } // μ„œλ²„ μƒνƒœ μ—…λ°μ΄νŠΈ function updateServerStatus(status) { const serverCheckTime = document.getElementById('server-check-time'); const statusElements = document.querySelectorAll('.status-value'); if (serverCheckTime) { serverCheckTime.textContent = new Date().toLocaleTimeString('ko-KR'); } // μ„œλ²„ μƒνƒœ ν‘œμ‹œ μ—…λ°μ΄νŠΈ 둜직 μΆ”κ°€ } // λ°μ΄ν„°λ² μ΄μŠ€ μƒνƒœ μ—…λ°μ΄νŠΈ function updateDatabaseStatus(dbStatus) { const dbConnections = document.getElementById('db-connections'); if (dbConnections && dbStatus.connections !== undefined) { dbConnections.textContent = dbStatus.connections; } } // μ‹œμŠ€ν…œ μ•Œλ¦Ό μ—…λ°μ΄νŠΈ function updateSystemAlerts(alerts) { const systemAlerts = document.getElementById('system-alerts'); if (systemAlerts) { systemAlerts.textContent = alerts.length; systemAlerts.className = `status-value ${alerts.length > 0 ? 'warning' : 'online'}`; } } // μ‚¬μš©μž 톡계 λ‘œλ“œ async function loadUserStats() { try { const response = await apiRequest('/api/system/users/stats', 'GET'); if (response.success) { const activeUsers = document.getElementById('active-users'); const totalUsers = document.getElementById('total-users'); if (activeUsers) activeUsers.textContent = response.data.active || 0; if (totalUsers) totalUsers.textContent = response.data.total || 0; } } catch (error) { console.error('μ‚¬μš©μž 톡계 λ‘œλ“œ 였λ₯˜:', error); } } // 졜근 ν™œλ™ λ‘œλ“œ async function loadRecentActivities() { try { const response = await apiRequest('/api/system/recent-activities', 'GET'); if (response.success && response.data) { displayRecentActivities(response.data); } } catch (error) { console.error('졜근 ν™œλ™ λ‘œλ“œ 였λ₯˜:', error); displayDefaultActivities(); } } // 졜근 ν™œλ™ ν‘œμ‹œ function displayRecentActivities(activities) { const container = document.getElementById('recent-activities'); if (!container) return; if (!activities || activities.length === 0) { container.innerHTML = '

졜근 ν™œλ™μ΄ μ—†μŠ΅λ‹ˆλ‹€.

'; return; } const html = activities.map(activity => `

${activity.title}

${activity.description}

${formatTimeAgo(activity.created_at)}
`).join(''); container.innerHTML = html; } // κΈ°λ³Έ ν™œλ™ ν‘œμ‹œ (데이터 λ‘œλ“œ μ‹€νŒ¨ μ‹œ) function displayDefaultActivities() { const container = document.getElementById('recent-activities'); if (!container) return; const defaultActivities = [ { type: 'system', title: 'μ‹œμŠ€ν…œ μ‹œμž‘', description: 'μ‹œμŠ€ν…œμ΄ μ •μƒμ μœΌλ‘œ μ‹œμž‘λ˜μ—ˆμŠ΅λ‹ˆλ‹€.', created_at: new Date().toISOString() } ]; displayRecentActivities(defaultActivities); } // ν™œλ™ νƒ€μž…μ— λ”°λ₯Έ μ•„μ΄μ½˜ λ°˜ν™˜ function getActivityIcon(type) { const icons = { 'login': 'fa-sign-in-alt', 'user_create': 'fa-user-plus', 'user_update': 'fa-user-edit', 'user_delete': 'fa-user-minus', 'system': 'fa-cog', 'database': 'fa-database', 'backup': 'fa-download', 'error': 'fa-exclamation-triangle' }; return icons[type] || 'fa-info-circle'; } // μ‹œκ°„ ν¬λ§·νŒ… (λͺ‡ λΆ„ μ „, λͺ‡ μ‹œκ°„ μ „ λ“±) function formatTimeAgo(dateString) { const now = new Date(); const date = new Date(dateString); const diffInSeconds = Math.floor((now - date) / 1000); if (diffInSeconds < 60) { return '방금 μ „'; } else if (diffInSeconds < 3600) { return `${Math.floor(diffInSeconds / 60)}λΆ„ μ „`; } else if (diffInSeconds < 86400) { return `${Math.floor(diffInSeconds / 3600)}μ‹œκ°„ μ „`; } else { return `${Math.floor(diffInSeconds / 86400)}일 μ „`; } } // μ‹œμŠ€ν…œ μƒνƒœ μƒˆλ‘œκ³ μΉ¨ async function refreshSystemStatus() { try { await loadSystemStatus(); await loadUserStats(); } catch (error) { console.error('μ‹œμŠ€ν…œ μƒνƒœ μƒˆλ‘œκ³ μΉ¨ 였λ₯˜:', error); } } // Open account management function openAccountManagement() { console.log('🎯 Account management button clicked'); const modal = document.getElementById('account-modal'); const content = document.getElementById('account-management-content'); console.log('Modal element:', modal); console.log('Content element:', content); if (modal && content) { console.log('βœ… Modal and content elements found, loading content...'); // Load account management content loadAccountManagementContent(content); modal.style.display = 'block'; console.log('βœ… Modal displayed'); } else { console.error('❌ Modal or content element not found'); } } // 계정 관리 컨텐츠 λ‘œλ“œ async function loadAccountManagementContent(container) { try { container.innerHTML = `
λ‘œλ”© 쀑...
`; // μ‚¬μš©μž λͺ©λ‘ λ‘œλ“œ const response = await apiRequest('/api/system/users', 'GET'); if (response.success) { displayAccountManagement(container, response.data); } else { throw new Error(response.error || 'μ‚¬μš©μž λͺ©λ‘μ„ 뢈러올 수 μ—†μŠ΅λ‹ˆλ‹€.'); } } catch (error) { console.error('계정 관리 컨텐츠 λ‘œλ“œ 였λ₯˜:', error); container.innerHTML = `

계정 정보λ₯Ό λΆˆλŸ¬μ˜€λŠ” 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.

`; } } // 계정 관리 ν™”λ©΄ ν‘œμ‹œ function displayAccountManagement(container, users) { const html = `

μ‚¬μš©μž 계정 관리

${generateUsersTableRows(users)}
ID μ‚¬μš©μžλͺ… 이름 κΆŒν•œ μƒνƒœ λ§ˆμ§€λ§‰ 둜그인 μž‘μ—…
`; container.innerHTML = html; systemData.users = users; } // μ‚¬μš©μž ν…Œμ΄λΈ” ν–‰ 생성 function generateUsersTableRows(users) { if (!users || users.length === 0) { return 'λ“±λ‘λœ μ‚¬μš©μžκ°€ μ—†μŠ΅λ‹ˆλ‹€.'; } return users.map(user => ` ${user.user_id} ${user.username} ${user.name || '-'} ${getRoleDisplayName(user.role)} ${user.is_active ? 'ν™œμ„±' : 'λΉ„ν™œμ„±'} ${user.last_login_at ? formatDate(user.last_login_at) : 'μ—†μŒ'} `).join(''); } // κΆŒν•œ ν‘œμ‹œλͺ… λ°˜ν™˜ function getRoleDisplayName(role) { const roleNames = { 'system': 'μ‹œμŠ€ν…œ', 'admin': 'κ΄€λ¦¬μž', 'leader': 'κ·Έλ£Ήμž₯', 'user': 'μ‚¬μš©μž' }; return roleNames[role] || role; } // λ‚ μ§œ ν¬λ§·νŒ… function formatDate(dateString) { if (!dateString) return '-'; const date = new Date(dateString); return date.toLocaleString('ko-KR'); } // μ‹œμŠ€ν…œ 둜그 μ—΄κΈ° function openSystemLogs() { console.log('μ‹œμŠ€ν…œ 둜그 λ²„νŠΌ 클릭됨'); const modal = document.getElementById('account-modal'); const content = document.getElementById('account-management-content'); if (modal && content) { loadSystemLogsContent(content); modal.style.display = 'block'; } } // μ‹œμŠ€ν…œ 둜그 컨텐츠 λ‘œλ“œ async function loadSystemLogsContent(container) { try { container.innerHTML = `

μ‹œμŠ€ν…œ 둜그

둜그 λ‘œλ”© 쀑...
`; // 둜그 데이터 λ‘œλ“œ await loadLogsData(); } catch (error) { console.error('μ‹œμŠ€ν…œ 둜그 λ‘œλ“œ 였λ₯˜:', error); container.innerHTML = `

μ‹œμŠ€ν…œ 둜그λ₯Ό λΆˆλŸ¬μ˜€λŠ” 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.

`; } } // 둜그 데이터 λ‘œλ“œ async function loadLogsData() { try { const response = await apiRequest('/api/system/logs/activity', 'GET'); const logsContainer = document.querySelector('.logs-container'); if (response.success && response.data) { displayLogs(response.data, logsContainer); } else { logsContainer.innerHTML = '

둜그 데이터가 μ—†μŠ΅λ‹ˆλ‹€.

'; } } catch (error) { console.error('둜그 데이터 λ‘œλ“œ 였λ₯˜:', error); document.querySelector('.logs-container').innerHTML = '

둜그 데이터λ₯Ό 뢈러올 수 μ—†μŠ΅λ‹ˆλ‹€.

'; } } // 둜그 ν‘œμ‹œ function displayLogs(logs, container) { if (!logs || logs.length === 0) { container.innerHTML = '

ν‘œμ‹œν•  λ‘œκ·Έκ°€ μ—†μŠ΅λ‹ˆλ‹€.

'; return; } const html = ` ${logs.map(log => ` `).join('')}
μ‹œκ°„ μœ ν˜• μ‚¬μš©μž λ‚΄μš©
${formatDate(log.created_at)} ${log.type} ${log.username || '-'} ${log.description}
`; container.innerHTML = html; } // 둜그 필터링 function filterLogs() { console.log('둜그 필터링 μ‹€ν–‰'); // μ‹€μ œ κ΅¬ν˜„μ€ μΆ”ν›„ μΆ”κ°€ showNotification('둜그 필터링 κΈ°λŠ₯은 개발 μ€‘μž…λ‹ˆλ‹€.', 'info'); } // λ°μ΄ν„°λ² μ΄μŠ€ 관리 μ—΄κΈ° function openDatabaseManagement() { console.log('λ°μ΄ν„°λ² μ΄μŠ€ 관리 λ²„νŠΌ 클릭됨'); showNotification('λ°μ΄ν„°λ² μ΄μŠ€ 관리 κΈ°λŠ₯은 개발 μ€‘μž…λ‹ˆλ‹€.', 'info'); } // μ‹œμŠ€ν…œ μ„€μ • μ—΄κΈ° function openSystemSettings() { console.log('μ‹œμŠ€ν…œ μ„€μ • λ²„νŠΌ 클릭됨'); showNotification('μ‹œμŠ€ν…œ μ„€μ • κΈ°λŠ₯은 개발 μ€‘μž…λ‹ˆλ‹€.', 'info'); } // λ°±μ—… 관리 μ—΄κΈ° function openBackupManagement() { console.log('λ°±μ—… 관리 λ²„νŠΌ 클릭됨'); showNotification('λ°±μ—… 관리 κΈ°λŠ₯은 개발 μ€‘μž…λ‹ˆλ‹€.', 'info'); } // λͺ¨λ‹ˆν„°λ§ μ—΄κΈ° function openMonitoring() { console.log('λͺ¨λ‹ˆν„°λ§ λ²„νŠΌ 클릭됨'); showNotification('λͺ¨λ‹ˆν„°λ§ κΈ°λŠ₯은 개발 μ€‘μž…λ‹ˆλ‹€.', 'info'); } // λͺ¨λ‹¬ λ‹«κΈ° function closeModal(modalId) { const modal = document.getElementById(modalId); if (modal) { modal.style.display = 'none'; } } // λ‘œκ·Έμ•„μ›ƒ function logout() { if (confirm('λ‘œκ·Έμ•„μ›ƒ ν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ?')) { localStorage.removeItem('token'); localStorage.removeItem('user'); window.location.href = '/'; } } // μ•Œλ¦Ό ν‘œμ‹œ function showNotification(message, type = 'info') { // κ°„λ‹¨ν•œ μ•Œλ¦Ό ν‘œμ‹œ (λ‚˜μ€‘μ— ν† μŠ€νŠΈ 라이브러리둜 ꡐ체 κ°€λŠ₯) const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.textContent = message; document.body.appendChild(notification); setTimeout(() => { notification.remove(); }, 5000); } // μ‚¬μš©μž νŽΈμ§‘ async function editUser(userId) { try { // μ‚¬μš©μž 정보 κ°€μ Έμ˜€κΈ° const response = await apiRequest(`/api/system/users`, 'GET'); if (!response.success) { throw new Error('μ‚¬μš©μž 정보λ₯Ό κ°€μ Έμ˜¬ 수 μ—†μŠ΅λ‹ˆλ‹€.'); } const user = response.data.find(u => u.user_id === userId); if (!user) { throw new Error('ν•΄λ‹Ή μ‚¬μš©μžλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.'); } // νŽΈμ§‘ 폼 ν‘œμ‹œ showUserEditForm(user); } catch (error) { console.error('μ‚¬μš©μž νŽΈμ§‘ 였λ₯˜:', error); showNotification('μ‚¬μš©μž 정보λ₯Ό λΆˆλŸ¬μ˜€λŠ” 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.', 'error'); } } // μ‚¬μš©μž νŽΈμ§‘ 폼 ν‘œμ‹œ function showUserEditForm(user) { const formHtml = `

μ‚¬μš©μž 정보 μˆ˜μ •

`; const container = document.getElementById('account-management-content'); container.innerHTML = formHtml; // 폼 제좜 이벀트 λ¦¬μŠ€λ„ˆ document.getElementById('edit-user-form').addEventListener('submit', async (e) => { e.preventDefault(); await updateUser(user.user_id); }); } // μ‚¬μš©μž 정보 μ—…λ°μ΄νŠΈ async function updateUser(userId) { try { const formData = { name: document.getElementById('edit-name').value, email: document.getElementById('edit-email').value || null, role: document.getElementById('edit-role').value, access_level: document.getElementById('edit-role').value, is_active: parseInt(document.getElementById('edit-is-active').value), worker_id: document.getElementById('edit-worker-id').value || null }; const response = await apiRequest(`/api/system/users/${userId}`, 'PUT', formData); if (response.success) { showNotification('μ‚¬μš©μž 정보가 μ„±κ³΅μ μœΌλ‘œ μ—…λ°μ΄νŠΈλ˜μ—ˆμŠ΅λ‹ˆλ‹€.', 'success'); closeModal('account-modal'); // 계정 관리 λ‹€μ‹œ λ‘œλ“œ setTimeout(() => openAccountManagement(), 500); } else { throw new Error(response.error || 'μ—…λ°μ΄νŠΈμ— μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.'); } } catch (error) { console.error('μ‚¬μš©μž μ—…λ°μ΄νŠΈ 였λ₯˜:', error); showNotification('μ‚¬μš©μž 정보 μ—…λ°μ΄νŠΈ 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.', 'error'); } } // μ‚¬μš©μž μ‚­μ œ async function deleteUser(userId) { try { // μ‚¬μš©μž 정보 κ°€μ Έμ˜€κΈ° const response = await apiRequest(`/api/system/users`, 'GET'); if (!response.success) { throw new Error('μ‚¬μš©μž 정보λ₯Ό κ°€μ Έμ˜¬ 수 μ—†μŠ΅λ‹ˆλ‹€.'); } const user = response.data.find(u => u.user_id === userId); if (!user) { throw new Error('ν•΄λ‹Ή μ‚¬μš©μžλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.'); } // μ‚­μ œ 확인 if (!confirm(`μ •λ§λ‘œ μ‚¬μš©μž '${user.username}'λ₯Ό μ‚­μ œν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ?\n\n이 μž‘μ—…μ€ 되돌릴 수 μ—†μŠ΅λ‹ˆλ‹€.`)) { return; } // μ‚¬μš©μž μ‚­μ œ μš”μ²­ const deleteResponse = await apiRequest(`/api/system/users/${userId}`, 'DELETE'); if (deleteResponse.success) { showNotification('μ‚¬μš©μžκ°€ μ„±κ³΅μ μœΌλ‘œ μ‚­μ œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.', 'success'); // 계정 관리 λ‹€μ‹œ λ‘œλ“œ setTimeout(() => { const container = document.getElementById('account-management-content'); if (container) { loadAccountManagementContent(container); } }, 500); } else { throw new Error(deleteResponse.error || 'μ‚­μ œμ— μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.'); } } catch (error) { console.error('μ‚¬μš©μž μ‚­μ œ 였λ₯˜:', error); showNotification('μ‚¬μš©μž μ‚­μ œ 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.', 'error'); } } // μƒˆ μ‚¬μš©μž 생성 폼 μ—΄κΈ° function openCreateUserForm() { const formHtml = `

μƒˆ μ‚¬μš©μž 생성

`; const container = document.getElementById('account-management-content'); container.innerHTML = formHtml; // 폼 제좜 이벀트 λ¦¬μŠ€λ„ˆ document.getElementById('create-user-form').addEventListener('submit', async (e) => { e.preventDefault(); await createUser(); }); } // μƒˆ μ‚¬μš©μž 생성 async function createUser() { try { const formData = { username: document.getElementById('create-username').value, password: document.getElementById('create-password').value, name: document.getElementById('create-name').value, email: document.getElementById('create-email').value || null, role: document.getElementById('create-role').value, access_level: document.getElementById('create-role').value, worker_id: document.getElementById('create-worker-id').value || null }; const response = await apiRequest('/api/system/users', 'POST', formData); if (response.success) { showNotification('μƒˆ μ‚¬μš©μžκ°€ μ„±κ³΅μ μœΌλ‘œ μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€.', 'success'); // 계정 관리 λͺ©λ‘μœΌλ‘œ λŒμ•„κ°€κΈ° setTimeout(() => { const container = document.getElementById('account-management-content'); loadAccountManagementContent(container); }, 500); } else { throw new Error(response.error || 'μ‚¬μš©μž 생성에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.'); } } catch (error) { console.error('μ‚¬μš©μž 생성 였λ₯˜:', error); showNotification('μ‚¬μš©μž 생성 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.', 'error'); } } // μ‚¬μš©μž 필터링 function filterUsers() { const searchTerm = document.getElementById('user-search').value.toLowerCase(); const roleFilter = document.getElementById('role-filter').value; const rows = document.querySelectorAll('#users-tbody tr'); rows.forEach(row => { const username = row.cells[1].textContent.toLowerCase(); const name = row.cells[2].textContent.toLowerCase(); const role = row.querySelector('.role-badge').textContent.toLowerCase(); const matchesSearch = username.includes(searchTerm) || name.includes(searchTerm); const matchesRole = !roleFilter || role.includes(roleFilter); row.style.display = matchesSearch && matchesRole ? '' : 'none'; }); } // λͺ¨λ‹¬ κ΄€λ ¨ ν•¨μˆ˜λ“€λ§Œ μ „μ—­μœΌλ‘œ λ…ΈμΆœ (λ™μ μœΌλ‘œ μƒμ„±λ˜λŠ” HTMLμ—μ„œ μ‚¬μš©) window.closeModal = closeModal; window.editUser = editUser; window.deleteUser = deleteUser; window.openCreateUserForm = openCreateUserForm; window.filterUsers = filterUsers; window.filterLogs = filterLogs; // ν…ŒμŠ€νŠΈμš© μ „μ—­ ν•¨μˆ˜ window.testFunction = function() { console.log('πŸ§ͺ ν…ŒμŠ€νŠΈ ν•¨μˆ˜ 호좜됨!'); alert('ν…ŒμŠ€νŠΈ ν•¨μˆ˜κ°€ μ •μƒμ μœΌλ‘œ μž‘λ™ν•©λ‹ˆλ‹€!'); }; console.log('🌐 μ „μ—­ ν•¨μˆ˜λ“€ λ…ΈμΆœ μ™„λ£Œ'); // λͺ¨λ‹¬ μ™ΈλΆ€ 클릭 μ‹œ λ‹«κΈ° window.onclick = function(event) { const modals = document.querySelectorAll('.modal'); modals.forEach(modal => { if (event.target === modal) { modal.style.display = 'none'; } }); };