X7ROOT File Manager
Current Path:
/home/gfecatvj/4abetter.us/afterhours/assets
home
/
gfecatvj
/
4abetter.us
/
afterhours
/
assets
/
📁
..
📄
script.js
(34.49 KB)
📄
style.css
(17.23 KB)
Editing: script.js
// Global variables let visitorTypeChart = null; let deviceTypeChart = null; let notifications = []; let lastVisitTimestamp = null; // Initialize dashboard when DOM is loaded document.addEventListener('DOMContentLoaded', function() { initializeDashboard(); setupEventListeners(); loadInitialData(); startRealTimeUpdates(); }); /** * Initialize dashboard components */ function initializeDashboard() { // Set up navigation const navItems = document.querySelectorAll('.nav-item'); navItems.forEach(item => { item.addEventListener('click', function(e) { e.preventDefault(); const section = this.dataset.section; showSection(section); // Update active nav item navItems.forEach(nav => nav.classList.remove('active')); this.classList.add('active'); }); }); // Initialize charts initializeCharts(); } /** * Set up event listeners */ function setupEventListeners() { // URL shortening form const shortenForm = document.getElementById('shortenForm'); if (shortenForm) { shortenForm.addEventListener('submit', handleUrlShorten); } // Visit filter const visitFilter = document.getElementById('visitFilter'); if (visitFilter) { visitFilter.addEventListener('change', function() { loadVisits(this.value); }); } // Notification bell const notificationBell = document.getElementById('notificationBell'); if (notificationBell) { notificationBell.addEventListener('click', toggleNotifications); } // Close notifications when clicking outside document.addEventListener('click', function(e) { const notificationPanel = document.getElementById('notificationPanel'); const notificationBell = document.getElementById('notificationBell'); if (!notificationPanel.contains(e.target) && !notificationBell.contains(e.target)) { closeNotifications(); } }); } /** * Show specific section */ function showSection(sectionName) { // Hide all sections const sections = document.querySelectorAll('.content-section'); sections.forEach(section => section.classList.remove('active')); // Show selected section const targetSection = document.getElementById(sectionName + '-section'); if (targetSection) { targetSection.classList.add('active'); // Load section-specific data switch(sectionName) { case 'url-shortener': loadUrls(); break; case 'analytics': loadAnalytics(); break; case 'visits': loadVisits(); break; } } } /** * Load initial dashboard data */ function loadInitialData() { loadStatistics(); loadRecentActivity(); loadUrls(); } /** * Start real-time updates - Enhanced for live notifications */ function startRealTimeUpdates() { // Check for new visits every 5 seconds for instant notifications setInterval(function() { checkForNewVisits(); updateLastUpdatedTime(); }, 5000); // Update statistics every 30 seconds setInterval(function() { loadStatistics(); }, 30000); // Full refresh every 2 minutes setInterval(function() { loadRecentActivity(); }, 120000); } /** * Load statistics */ async function loadStatistics() { try { const response = await fetch('api.php?action=get_statistics'); const stats = await response.json(); updateStatistics(stats); } catch (error) { console.error('Error loading statistics:', error); } } /** * Update statistics display */ function updateStatistics(stats) { const statNumbers = document.querySelectorAll('.stat-number'); if (statNumbers.length >= 4) { statNumbers[0].textContent = stats.total_urls || 0; statNumbers[1].textContent = stats.total_visits || 0; statNumbers[2].textContent = stats.human_visits || 0; statNumbers[3].textContent = stats.bot_visits || 0; } } /** * Load recent activity */ async function loadRecentActivity() { try { const response = await fetch('api.php?action=get_recent_visits&limit=10'); const visits = await response.json(); updateRecentActivity(visits); } catch (error) { console.error('Error loading recent activity:', error); } } /** * Update recent activity display */ function updateRecentActivity(visits) { const activityList = document.getElementById('recentActivity'); if (!activityList) return; if (visits.length === 0) { activityList.innerHTML = '<div class="empty-state"><i class="fas fa-eye-slash"></i><h3>No recent activity</h3><p>Visits will appear here as they happen</p></div>'; return; } activityList.innerHTML = visits.map(visit => ` <div class="activity-item"> <div class="activity-icon"> <i class="fas fa-eye"></i> </div> <div class="activity-content"> <div class="activity-text"> New visit from <span class="country-flag fi fi-${(visit.country_code || 'xx').toLowerCase()}"></span> ${visit.country || 'Unknown'} </div> <div class="activity-time">${timeAgo(visit.timestamp)}</div> </div> <div class="activity-meta"> <span class="visitor-type ${visit.is_human ? 'human' : 'bot'}"> ${visit.is_human ? 'Human' : 'Bot'} </span> </div> </div> `).join(''); } /** * Handle URL shortening */ async function handleUrlShorten(e) { e.preventDefault(); const form = e.target; const formData = new FormData(); formData.append('action', 'shorten_url'); formData.append('url', form.originalUrl.value); formData.append('custom_code', form.shortCode.value); // Disable form const submitBtn = form.querySelector('button[type="submit"]'); const originalText = submitBtn.innerHTML; submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Shortening...'; submitBtn.disabled = true; try { const response = await fetch('api.php', { method: 'POST', body: formData }); const result = await response.json(); if (result.success) { showShortenedResult(result.short_url); form.reset(); loadUrls(); // Refresh URL list } else { showError(result.error || 'Failed to shorten URL'); } } catch (error) { console.error('Error shortening URL:', error); showError('Network error occurred'); } finally { // Re-enable form submitBtn.innerHTML = originalText; submitBtn.disabled = false; } } /** * Show shortened result */ function showShortenedResult(shortUrl) { const resultDiv = document.getElementById('shortenedResult'); const resultInput = document.getElementById('resultUrl'); if (resultDiv && resultInput) { resultInput.value = shortUrl; resultDiv.style.display = 'block'; // Scroll to result resultDiv.scrollIntoView({ behavior: 'smooth' }); } } /** * Load URLs list */ async function loadUrls() { try { const response = await fetch('api.php?action=get_urls'); const urls = await response.json(); updateUrlsList(urls); } catch (error) { console.error('Error loading URLs:', error); } } /** * Update URLs list display */ function updateUrlsList(urls) { const urlList = document.getElementById('urlList'); if (!urlList) return; if (urls.length === 0) { urlList.innerHTML = '<div class="empty-state"><i class="fas fa-link"></i><h3>No URLs created yet</h3><p>Create your first short URL using the form above</p></div>'; return; } urlList.innerHTML = urls.map(url => ` <div class="url-item"> <div class="url-info"> <div class="url-short">${url.short_url}</div> <div class="url-original">${url.original_url}</div> </div> <div class="url-meta"> <span><i class="fas fa-eye"></i> ${url.visits} visits</span> <span><i class="fas fa-calendar"></i> ${formatDate(url.created_at)}</span> </div> <div class="url-actions"> <button class="copy-btn" onclick="copyToClipboard('${url.short_url}')"> <i class="fas fa-copy"></i> Copy </button> <button class="delete-btn" onclick="deleteUrl('${url.short_code}')"> <i class="fas fa-trash"></i> Delete </button> </div> </div> `).join(''); } /** * Load visits */ async function loadVisits(filter = 'all') { try { const response = await fetch(`api.php?action=get_visits&filter=${filter}&limit=100`); const visits = await response.json(); updateVisitsTable(visits); } catch (error) { console.error('Error loading visits:', error); } } /** * Update visits table */ function updateVisitsTable(visits) { const tableBody = document.getElementById('visitsTableBody'); if (!tableBody) return; if (visits.length === 0) { tableBody.innerHTML = '<tr><td colspan="9" style="text-align: center; padding: 40px;">No visits found</td></tr>'; return; } tableBody.innerHTML = visits.map(visit => ` <tr> <td>${formatDateTime(visit.timestamp)}</td> <td> <span class="country-flag fi fi-${(visit.country_code || 'xx').toLowerCase()}"></span> ${visit.country || 'Unknown'} </td> <td title="${visit.ip}">${visit.ip}</td> <td> <i class="fas fa-${getDeviceIcon(visit.device_type)} device-${(visit.device_type || 'unknown').toLowerCase()}"></i> ${visit.device_type || 'Unknown'} </td> <td>${visit.os || 'Unknown'}</td> <td>${visit.connection_type || 'Unknown'}</td> <td title="${visit.org}">${visit.isp || 'Unknown'}</td> <td> <span class="status-badge ${getVisitorTypeClass(visit)}"> ${getVisitorTypeText(visit)} </span> </td> <td>/${visit.short_code}</td> </tr> `).join(''); } /** * Load analytics data */ async function loadAnalytics() { try { const response = await fetch('api.php?action=get_analytics'); const analytics = await response.json(); updateAnalytics(analytics); } catch (error) { console.error('Error loading analytics:', error); } } /** * Update analytics display */ function updateAnalytics(analytics) { updateCountriesList(analytics.countries || {}); updateOSList(analytics.os || {}); updateCharts(analytics); } /** * Update countries list */ function updateCountriesList(countries) { const countriesList = document.getElementById('topCountries'); if (!countriesList) return; const entries = Object.entries(countries).slice(0, 10); if (entries.length === 0) { countriesList.innerHTML = '<div class="empty-state" style="padding: 20px;"><p>No country data available</p></div>'; return; } countriesList.innerHTML = entries.map(([country, count]) => ` <div class="country-item"> <div class="country-info"> <span class="country-flag fi fi-${getCountryCode(country).toLowerCase()}"></span> <span>${country}</span> </div> <span class="country-count">${count}</span> </div> `).join(''); } /** * Update OS list */ function updateOSList(osList) { const osListEl = document.getElementById('topOS'); if (!osListEl) return; const entries = Object.entries(osList).slice(0, 10); if (entries.length === 0) { osListEl.innerHTML = '<div class="empty-state" style="padding: 20px;"><p>No OS data available</p></div>'; return; } osListEl.innerHTML = entries.map(([os, count]) => ` <div class="os-item"> <div class="os-info"> <i class="fab fa-${getOSIcon(os)}"></i> <span>${os}</span> </div> <span class="os-count">${count}</span> </div> `).join(''); } /** * Initialize charts */ function initializeCharts() { // Visitor type chart const visitorCtx = document.getElementById('visitorTypeChart'); if (visitorCtx) { visitorTypeChart = new Chart(visitorCtx, { type: 'doughnut', data: { labels: ['Human', 'Bot'], datasets: [{ data: [0, 0], backgroundColor: ['#10b981', '#f59e0b'], borderWidth: 0 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom' } } } }); } // Device type chart const deviceCtx = document.getElementById('deviceTypeChart'); if (deviceCtx) { deviceTypeChart = new Chart(deviceCtx, { type: 'doughnut', data: { labels: ['Desktop', 'Mobile', 'Tablet'], datasets: [{ data: [0, 0, 0], backgroundColor: ['#3b82f6', '#10b981', '#8b5cf6'], borderWidth: 0 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom' } } } }); } } /** * Update charts with analytics data */ function updateCharts(analytics) { // Update visitor type chart if (visitorTypeChart && analytics.visitors) { const humanCount = analytics.visitors.human || 0; const botCount = analytics.visitors.bot || 0; visitorTypeChart.data.datasets[0].data = [humanCount, botCount]; visitorTypeChart.update(); } // Update device type chart if (deviceTypeChart && analytics.devices) { const desktop = analytics.devices.Desktop || 0; const mobile = analytics.devices.Mobile || 0; const tablet = analytics.devices.Tablet || 0; deviceTypeChart.data.datasets[0].data = [desktop, mobile, tablet]; deviceTypeChart.update(); } } /** * Check for new visits - Enhanced with live popup notifications */ async function checkForNewVisits() { try { const since = lastVisitTimestamp || new Date(Date.now() - 10000).toISOString(); const response = await fetch(`api.php?action=get_recent_visits&limit=20&since=${since}`); const newVisits = await response.json(); if (newVisits.length > 0) { // Update last visit timestamp lastVisitTimestamp = newVisits[0].timestamp; // Process each new visit newVisits.forEach(visit => { // Add to notifications addNotification(visit); // Show live popup notification showLiveVisitPopup(visit); }); // Update displays immediately loadStatistics(); loadRecentActivity(); } } catch (error) { console.error('Error checking for new visits:', error); } } /** * Add notification */ function addNotification(visit) { const notification = { id: 'notif_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9), visit: visit, timestamp: new Date(), read: false }; notifications.unshift(notification); // Keep only last 50 notifications if (notifications.length > 50) { notifications = notifications.slice(0, 50); } updateNotificationCount(); updateNotificationList(); } /** * Toggle notifications panel */ function toggleNotifications() { const panel = document.getElementById('notificationPanel'); if (panel.classList.contains('show')) { closeNotifications(); } else { openNotifications(); } } /** * Open notifications panel */ function openNotifications() { const panel = document.getElementById('notificationPanel'); panel.classList.add('show'); // Mark all as read notifications.forEach(notif => notif.read = true); updateNotificationCount(); updateNotificationList(); } /** * Close notifications panel */ function closeNotifications() { const panel = document.getElementById('notificationPanel'); panel.classList.remove('show'); } /** * Update notification count */ function updateNotificationCount() { const countEl = document.getElementById('notificationCount'); const unreadCount = notifications.filter(notif => !notif.read).length; if (countEl) { countEl.textContent = unreadCount; countEl.classList.toggle('hidden', unreadCount === 0); } } /** * Update notification list */ function updateNotificationList() { const listEl = document.getElementById('notificationList'); if (!listEl) return; if (notifications.length === 0) { listEl.innerHTML = '<div class="empty-state" style="padding: 20px;"><p>No notifications yet</p></div>'; return; } listEl.innerHTML = notifications.map(notif => ` <div class="notification-item ${!notif.read ? 'new' : ''}"> <div class="notification-icon"> <i class="fas fa-eye"></i> </div> <div class="notification-content"> <div class="notification-text"> New visit from <span class="country-flag fi fi-${(notif.visit.country_code || 'xx').toLowerCase()}"></span> ${notif.visit.country || 'Unknown'} </div> <div class="notification-time">${timeAgo(notif.visit.timestamp)}</div> </div> <div class="notification-meta"> <span class="visitor-type ${notif.visit.is_human ? 'human' : 'bot'}"> ${notif.visit.is_human ? 'Human' : 'Bot'} </span> </div> </div> `).join(''); } /** * Copy text to clipboard */ async function copyToClipboard(text) { try { await navigator.clipboard.writeText(text); showSuccess('Copied to clipboard!'); } catch (error) { console.error('Failed to copy:', error); showError('Failed to copy to clipboard'); } } /** * Delete URL */ async function deleteUrl(shortCode) { if (!confirm('Are you sure you want to delete this URL?')) { return; } const formData = new FormData(); formData.append('action', 'delete_url'); formData.append('short_code', shortCode); try { const response = await fetch('api.php', { method: 'POST', body: formData }); const result = await response.json(); if (result.success) { showSuccess('URL deleted successfully'); loadUrls(); // Refresh list } else { showError(result.error || 'Failed to delete URL'); } } catch (error) { console.error('Error deleting URL:', error); showError('Network error occurred'); } } /** * Update last updated time */ function updateLastUpdatedTime() { const lastUpdatedEl = document.getElementById('lastUpdated'); if (lastUpdatedEl) { lastUpdatedEl.textContent = new Date().toLocaleString(); } } /** * Show success message */ function showSuccess(message) { showMessage(message, 'success'); } /** * Show error message */ function showError(message) { showMessage(message, 'error'); } /** * Show live visit popup notification */ function showLiveVisitPopup(visit) { // Create popup overlay const overlay = document.createElement('div'); overlay.className = 'live-visit-overlay'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); z-index: 99999; display: flex; align-items: center; justify-content: center; animation: fadeIn 0.3s ease; `; // Create popup content const popup = document.createElement('div'); popup.className = 'live-visit-popup'; popup.style.cssText = ` background: white; border-radius: 16px; padding: 30px; max-width: 500px; width: 90%; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); transform: scale(0.8); animation: popupIn 0.3s ease forwards; text-align: center; `; // Get visitor type styling const visitorTypeClass = getVisitorTypeClass(visit); const visitorTypeText = getVisitorTypeText(visit); const flagClass = `fi fi-${(visit.country_code || 'xx').toLowerCase()}`; popup.innerHTML = ` <div style="margin-bottom: 20px;"> <div style="width: 80px; height: 80px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 50%; margin: 0 auto 15px; display: flex; align-items: center; justify-content: center; color: white; font-size: 32px;"> <i class="fas fa-eye"></i> </div> <h2 style="margin: 0 0 8px; color: #111827; font-size: 24px;">🔥 New Visit!</h2> <p style="margin: 0; color: #6b7280; font-size: 16px;">Someone just accessed your short URL</p> </div> <div style="background: #f9fafb; border-radius: 12px; padding: 20px; margin-bottom: 25px; text-align: left;"> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; font-size: 14px;"> <div> <strong style="color: #374151;">🌍 Country:</strong><br> <span class="${flagClass}" style="margin-right: 8px;"></span> ${visit.country || 'Unknown'} </div> <div> <strong style="color: #374151;">🏷️ Visitor Type:</strong><br> <span class="status-badge ${visitorTypeClass}">${visitorTypeText}</span> </div> <div> <strong style="color: #374151;">🌐 IP Address:</strong><br> ${visit.ip} </div> <div> <strong style="color: #374151;">💻 Device:</strong><br> ${visit.device_type || 'Unknown'} </div> <div> <strong style="color: #374151;">🖥️ OS:</strong><br> ${visit.os || 'Unknown'} </div> <div> <strong style="color: #374151;">🏢 ISP:</strong><br> ${visit.isp || 'Unknown'} </div> </div> <div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #e5e7eb;"> <strong style="color: #374151;">🔗 Short URL:</strong> /${visit.short_code}<br> <strong style="color: #374151;">⏰ Time:</strong> ${formatDateTime(visit.timestamp)} </div> </div> <button onclick="closeLiveVisitPopup()" style=" background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; padding: 12px 30px; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; transition: transform 0.2s; " onmouseover="this.style.transform='translateY(-2px)'" onmouseout="this.style.transform='translateY(0)'"> ✅ Got it! </button> `; overlay.appendChild(popup); document.body.appendChild(overlay); // Add CSS animations if (!document.getElementById('popup-animations')) { const style = document.createElement('style'); style.id = 'popup-animations'; style.textContent = ` @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes popupIn { from { transform: scale(0.8); opacity: 0; } to { transform: scale(1); opacity: 1; } } @keyframes popupOut { from { transform: scale(1); opacity: 1; } to { transform: scale(0.8); opacity: 0; } } `; document.head.appendChild(style); } // Store reference for closing window.currentVisitPopup = overlay; // Play loud attention-grabbing notification sound playLoudNotificationSound(); } /** * Play loud and attention-grabbing notification sound */ function playLoudNotificationSound() { try { // Create multiple audio instances for layered effect const audioConfigs = [ { // High-pitched alert beep frequency: 1000, duration: 0.3, volume: 1.0, type: 'square' }, { // Medium pitched beep frequency: 800, duration: 0.2, volume: 0.8, type: 'sine', delay: 0.1 }, { // Low pitched attention sound frequency: 600, duration: 0.4, volume: 0.9, type: 'triangle', delay: 0.2 } ]; audioConfigs.forEach((config, index) => { setTimeout(() => { playBeep(config.frequency, config.duration, config.volume, config.type); }, (config.delay || 0) * 1000); }); // Fallback: Try to play traditional alert sound at max volume setTimeout(() => { try { // Traditional notification sound as fallback const fallbackAudio = new Audio('data:audio/wav;base64,UklGRtIBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YW4BAABk6vr/+Pb6/wT0Av8E9AL/BfYC/wb4Av8G+AL/B/gC/wj4Av8I+gL/CfoC/wr6Av8K/AL/C/wC/wv+Av8M/gL/DAAD/w0AA/8NAAP/DgAD/w4AA/8PAAP/DwAD/xAAA/8QAAP/EgAD/xIAA/8TAAP/EwAD/xQAA/8UAAP/FQAD/xUAA/8WAAP/FgAD/xcAA/8XAAP/GAAD/xgAA/8ZAAP/GQAD/xoAA/8aAAP/GwAD/xsAA/8cAAP/HAAD/x0AA/8dAAP/HgAD/x4AA/8fAAP/HwAD/yAAA/8gAAP/IQAD/yEAA/8iAAP/IgAD/yMAA/8jAAP/JAAD/yQAA/8lAAP/JQAD/yYAA/8mAAP/JwAD/ycAA/8oAAP/KAAD/ykAA/8pAAP/KgAD/yoAA/8rAAP/KwAD/ywAA/8sAAP/LQAD/y0AA/8uAAP/LgAD/y8AA/8vAAP/MAAD/zAAA/8xAAP/MQAD/zIAA/8yAAP/MwAD/zMAA/80AAP/NAAD/zUAA/81AAP/NgAD/zYAA/83AAP/NwAD/zgAA/84AAP/OQAD/zkAA/86AAP/OgAD/zsAA/87AAP/PAAD/zwAA/89AAP/PQAD/z4AA/8+AAP/PwAD/z8AA/9AAAP/QAAD/0EAA/9BAAP/QgAD/0IAA/9DAAP/QwAD/0QAA/9EAAP/RQAD/0UAA/9GAAP/RgAD/0cAA/9HAAP/SAAD/0gAA/9JAAP/SQAD/0oAA/9KAAP/SwAD/0sAA/9MAAP/TAAD/00AA/9NAAP/TgAD/04AA/9PAAP/TwAD/1AAA/9QAAP/UQAD/1EAA/9SAAP/UgAD/1MAA/9TAAP/VAAD/1QAA/9VAAP/VQAD/1YAA/9WAAP/VwAD/1cAA/9YAAP/WAAD/1kAA/9ZAAP/WgAD/1oAA/9bAAP/WwAD/1wAA/9cAAP/XQAD/10AA/9eAAP/XgAD/18AA/9fAAP/YAAD/2AAA/9hAAP/YQAD/2IAA/9iAAP/YwAD/2MAA/9kAAP/ZAAD/2UAA/9lAAP/ZgAD/2YAA/9nAAP/ZwAD/2gAA/9oAAP/aQAD/2kAA/9qAAP/agAD/2sAA/9rAAP/bAAD/2wAA/9tAAP/bQAD/24AA/9uAAP/bwAD/28AA/9wAAP/cAAD/3EAA/9xAAP/cgAD/3IAA/9zAAP/cwAD/3QAA/90AAP/dQAD/3UAA/92AAP/dgAD/3cAA/93AAP/eAAD/3gAA/95AAP/eQAD/3oAA/96AAP/ewAD/3sAA/98AAP/fAAD/30AA/99AAP/fgAD/34AA/9/AAP/fwAD/4AAA/+AAAP/gQAD/4EAA/+CAAP/ggAD/4MAA/+DAAP/hAAD/4QAA/+FAAP/hQAD/4YAA/+GAAP/hwAD/4cAA/+IAAP/iAAD/4kAA/+JAAP/igAD/4oAA/+LAAP/iwAD/4wAA/+MAAP/jQAD/40AA/+OAAP/jgAD/48AA/+PAAP/kAAD/5AAA/+RAAP/kQAD/5IAA/+SAAP/kwAD/5MAA/+UAAP/lAAD/5UAA/+VAAP/lgAD/5YAA/+XAAP/lwAD/5gAA/+YAAP/mQAD/5kAA/+aAAP/mgAD/5sAA/+bAAP/nAAD/5wAA/+dAAP/nQAD/54AA/+eAAP/nwAD/58AA/+gAAP/oAAD/6EAA/+hAAP/ogAD/6IAA/+jAAP/owAD/6QAA/+kAAP/pQAD/6UAA/+mAAP/pgAD/6cAA/+nAAP/qAAD/6gAA/+pAAP/qQAD/6oAA/+qAAP/qwAD/6sAA/+sAAP/rAAD/60AA/+tAAP/rgAD/64AA/+vAAP/rwAD/7AAA/+wAAP/sQAD/7EAA/+yAAP/sgAD/7MAA/+zAAP/tAAD/7QAA/+1AAP/tQAD/7YAA/+2AAP/twAD/7cAA/+4AAP/uAAD/7kAA/+5AAP/ugAD/7oAA/+7AAP/uwAD/7wAA/+8AAP/vQAD/70AA/++AAP/vgAD/78AA/+/AAP/wAAD/8AAA//BAAP/wQAD/8IAA//CAAP/wwAD/8MAA//EAAP/xAAD/8UAA//FAAP/xgAD/8YAA//HAAP/xwAD/8gAA//IAAP/yQAD/8kAA//KAAP/ygAD/8sAA//LAAP/zAAD/8wAA//NAAP/zQAD/84AA//OAAP/zwAD/88AA//QAAP/0AAD/9EAA//RAAP/0gAD/9IAA//TAAP/0wAD/9QAA//UAAP/1QAD/9UAA//WAAP/1gAD/9cAA//XAAP/2AAD/9gAA//ZAAP/2QAD/9oAA//aAAP/2wAD/9sAA//cAAP/3AAD/90AA//dAAP/3gAD/94AA//fAAP/3wAD/+AAA//gAAP/4QAD/+EAA//iAAP/4gAD/+MAA//jAAP/5AAD/+QAA//lAAP/5QAD/+YAA//mAAP/5wAD/+cAA//oAAP/6AAD/+kAA//pAAP/6gAD/+oAA//rAAP/6wAD/+wAA//sAAP/7QAD/+0AA//uAAP/7gAD/+8AA//vAAP/8AAD//AAA//xAAP/8QAD//IAA//yAAP/8wAD//MAA//0AAP/9AAD//UAA//1AAP/9gAD//YAA//3AAP/9wAD//gAA//4AAP/+QAD//kAA//6AAP/+gAD//sAA//7AAP//AAD//wAA//9AAP//QAD//4AA//+AAP//wAD//8AA///AAP//wAD//8AA///AAP//wAD//8AA///AAP//wAD//8AA///AAP//wAD//8AA///AAP//wAD//8AA///AAP//wAD//8AA'); fallbackAudio.volume = 1.0; fallbackAudio.play().catch(() => {}); } catch (e) {} }, 800); } catch (e) { console.error('Failed to play notification sound:', e); } } /** * Generate and play a beep sound using Web Audio API */ function playBeep(frequency, duration, volume, type = 'sine') { try { // Create audio context const audioContext = new (window.AudioContext || window.webkitAudioContext)(); // Create oscillator const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); // Connect nodes oscillator.connect(gainNode); gainNode.connect(audioContext.destination); // Configure oscillator oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime); oscillator.type = type; // Configure volume with attack and release gainNode.gain.setValueAtTime(0, audioContext.currentTime); gainNode.gain.linearRampToValueAtTime(volume, audioContext.currentTime + 0.01); gainNode.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + duration); // Start and stop oscillator.start(audioContext.currentTime); oscillator.stop(audioContext.currentTime + duration); } catch (e) { console.error('Failed to generate beep:', e); } } /** * Close live visit popup */ function closeLiveVisitPopup() { if (window.currentVisitPopup) { const popup = window.currentVisitPopup.querySelector('.live-visit-popup'); popup.style.animation = 'popupOut 0.3s ease forwards'; window.currentVisitPopup.style.animation = 'fadeIn 0.3s ease reverse'; setTimeout(() => { if (window.currentVisitPopup && window.currentVisitPopup.parentNode) { document.body.removeChild(window.currentVisitPopup); } window.currentVisitPopup = null; }, 300); } } /** * Show message (toast-like notification) */ function showMessage(message, type) { // Create toast element const toast = document.createElement('div'); toast.className = `toast toast-${type}`; toast.innerHTML = ` <i class="fas fa-${type === 'success' ? 'check' : 'exclamation-triangle'}"></i> <span>${message}</span> `; // Add styles toast.style.cssText = ` position: fixed; top: 20px; right: 20px; padding: 12px 16px; border-radius: 6px; color: white; font-size: 14px; font-weight: 500; z-index: 10000; transform: translateX(100%); transition: transform 0.3s ease; display: flex; align-items: center; gap: 8px; min-width: 250px; background: ${type === 'success' ? '#10b981' : '#ef4444'}; `; document.body.appendChild(toast); // Animate in setTimeout(() => { toast.style.transform = 'translateX(0)'; }, 10); // Remove after 3 seconds setTimeout(() => { toast.style.transform = 'translateX(100%)'; setTimeout(() => { document.body.removeChild(toast); }, 300); }, 3000); } // Utility functions /** * Format date */ function formatDate(dateString) { return new Date(dateString).toLocaleDateString(); } /** * Format date and time */ function formatDateTime(dateString) { return new Date(dateString).toLocaleString(); } /** * Time ago helper */ function timeAgo(dateString) { const now = new Date(); const past = new Date(dateString); const diffInSeconds = Math.floor((now - past) / 1000); if (diffInSeconds < 60) return 'Just now'; if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`; if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`; return `${Math.floor(diffInSeconds / 86400)}d ago`; } /** * Get device icon */ function getDeviceIcon(deviceType) { const icons = { 'Mobile': 'mobile-alt', 'Tablet': 'tablet-alt', 'Desktop': 'desktop' }; return icons[deviceType] || 'question'; } /** * Get OS icon */ function getOSIcon(os) { const icons = { 'Windows': 'windows', 'macOS': 'apple', 'iOS': 'apple', 'Android': 'android', 'Linux': 'linux', 'Ubuntu': 'ubuntu' }; return icons[os] || 'desktop'; } /** * Get visitor type class */ function getVisitorTypeClass(visit) { if (visit.is_proxy || visit.is_vpn) return 'status-proxy'; if (visit.is_hosting) return 'status-hosting'; if (visit.is_bot) return 'status-bot'; return 'status-human'; } /** * Get visitor type text */ function getVisitorTypeText(visit) { if (visit.is_proxy || visit.is_vpn) return 'Proxy'; if (visit.is_hosting) return 'Hosting'; if (visit.is_bot) return 'Bot'; return 'Human'; } /** * Get country code (simplified mapping) */ function getCountryCode(country) { const codes = { 'United States': 'us', 'United Kingdom': 'gb', 'Germany': 'de', 'France': 'fr', 'Italy': 'it', 'Spain': 'es', 'Canada': 'ca', 'Australia': 'au', 'Japan': 'jp', 'China': 'cn', 'India': 'in', 'Brazil': 'br', 'Russia': 'ru' }; return codes[country] || 'xx'; }
Upload File
Create Folder