--- a +++ b/web/server/server.js @@ -0,0 +1,231 @@ +/* + * DNAnalyzer Server Page JavaScript + * Copyright © 2025 Piyush Acharya + */ + +document.addEventListener('DOMContentLoaded', function() { + // Mobile navigation toggle + const mobileToggle = document.getElementById('mobileToggle'); + const navLinks = document.getElementById('navLinks'); + + if (mobileToggle && navLinks) { + mobileToggle.addEventListener('click', function() { + navLinks.classList.toggle('active'); + mobileToggle.querySelector('i').classList.toggle('fa-bars'); + mobileToggle.querySelector('i').classList.toggle('fa-times'); + }); + } + + // Navbar scroll behavior + const navbar = document.getElementById('navbar'); + + window.addEventListener('scroll', function() { + if (window.scrollY > 100) { + navbar.classList.add('scrolled'); + } else { + navbar.classList.remove('scrolled'); + } + }); + + // Server status check + const serverStatusElement = document.getElementById('server-status'); + let statusCheckInterval; + let retryCount = 0; + const MAX_RETRIES = 3; + const SERVER_PORT = 8080; + + // Copy commands functionality + window.copyCloneCommand = function() { + copyToClipboard('git clone https://github.com/VerisimilitudeX/DNAnalyzer.git && cd DNAnalyzer', 0); + }; + + window.copyChmodCommand = function() { + copyToClipboard('chmod +x ./gradlew', 1); + }; + + window.copyBuildCommand = function() { + copyToClipboard('./gradlew clean bootJar', 2); + }; + + window.copyRunCommand = function() { + copyToClipboard('java -jar build/libs/DNAnalyzer.jar', 3); + }; + + function copyToClipboard(text, buttonIndex) { + navigator.clipboard.writeText(text).then(() => { + const buttons = document.querySelectorAll('.copy-button'); + const button = buttons[buttonIndex]; + button.textContent = 'Copied!'; + button.classList.add('success'); + setTimeout(() => { + button.textContent = 'Copy'; + button.classList.remove('success'); + }, 2000); + }).catch(() => { + const buttons = document.querySelectorAll('.copy-button'); + const button = buttons[buttonIndex]; + button.textContent = 'Failed'; + button.classList.add('error'); + setTimeout(() => { + button.textContent = 'Copy'; + button.classList.remove('error'); + }, 2000); + }); + } + + // Server status check with retry logic + async function checkServerStatus(isInitialCheck = false) { + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 5000); + + // Try the API status endpoint first + const response = await fetch(`http://localhost:${SERVER_PORT}/api/v1/status`, { + method: 'GET', + signal: controller.signal, + headers: { + 'Accept': 'application/json', + } + }); + + clearTimeout(timeoutId); + retryCount = 0; // Reset retry count on success + + serverStatusElement.innerHTML = ` + <div class="status-indicator online"> + <div class="status-dot"></div> + <div class="status-details"> + <div class="status-main">Server Online</div> + <div class="status-info"> + <span>Status: Running</span> + <span>Last checked: ${new Date().toLocaleTimeString()}</span> + <span>Ready to process DNA sequences</span> + </div> + </div> + </div> + `; + return true; + } catch (error) { + // Try root URL as fallback + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 5000); + + const response = await fetch(`http://localhost:${SERVER_PORT}/`, { + method: 'GET', + signal: controller.signal + }); + + clearTimeout(timeoutId); + + if (response.ok || response.status === 404) { + // Even a 404 means the server is running + retryCount = 0; + + serverStatusElement.innerHTML = ` + <div class="status-indicator online"> + <div class="status-dot"></div> + <div class="status-details"> + <div class="status-main">Server Online</div> + <div class="status-info"> + <span>Status: Running</span> + <span>Last checked: ${new Date().toLocaleTimeString()}</span> + <span>Ready to process DNA sequences</span> + </div> + </div> + </div> + `; + return true; + } + throw new Error('Server not responding correctly'); + } catch (fallbackError) { + retryCount++; + const isMaxRetries = retryCount >= MAX_RETRIES; + + serverStatusElement.innerHTML = ` + <div class="status-indicator offline"> + <div class="status-dot"></div> + <div class="status-details"> + <div class="status-main">Server Offline</div> + <div class="status-info"> + <span>Last checked: ${new Date().toLocaleTimeString()}</span> + ${isInitialCheck ? '<span>Follow the setup steps above to start the server</span>' : ''} + ${!isInitialCheck && !isMaxRetries ? `<span>Retrying... (${retryCount}/${MAX_RETRIES})</span>` : ''} + ${isMaxRetries ? '<span>Max retries reached. Check server setup instructions above.</span>' : ''} + </div> + </div> + </div> + `; + if (isMaxRetries) { + clearInterval(statusCheckInterval); + showTroubleshooting(); + } + return false; + } + } + } + + function showTroubleshooting() { + const troubleshootingSection = document.getElementById('troubleshooting'); + if (troubleshootingSection) { + troubleshootingSection.classList.add('active'); + troubleshootingSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + } + + // Initialize status checking + async function initializeStatusCheck() { + const isOnline = await checkServerStatus(true); + + if (!isOnline) { + // If offline on first check, start checking more frequently initially + statusCheckInterval = setInterval(() => checkServerStatus(), 5000); + + // After 30 seconds, switch to normal interval if server is still offline + setTimeout(() => { + clearInterval(statusCheckInterval); + statusCheckInterval = setInterval(() => checkServerStatus(), 30000); + }, 30000); + } else { + // If online, check every 30 seconds + statusCheckInterval = setInterval(() => checkServerStatus(), 30000); + } + } + + // Start status checking + initializeStatusCheck(); + + // Cleanup on page unload + window.addEventListener('unload', () => { + if (statusCheckInterval) { + clearInterval(statusCheckInterval); + } + }); + + // Add animated appearance for steps + const animateSteps = () => { + const stepCards = document.querySelectorAll('.step-card'); + const observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + entry.target.style.opacity = 1; + entry.target.style.transform = 'translateY(0)'; + observer.unobserve(entry.target); + } + }); + }, { + threshold: 0.1 + }); + + stepCards.forEach((card, index) => { + card.style.opacity = 0; + card.style.transform = 'translateY(20px)'; + card.style.transition = 'opacity 0.5s ease, transform 0.5s ease'; + card.style.transitionDelay = `${index * 0.1}s`; + observer.observe(card); + }); + }; + + // Initialize animations + setTimeout(animateSteps, 500); +}); \ No newline at end of file