diff --git a/www/css/custom.css b/www/css/custom.css index 4bf805b..413d12a 100644 --- a/www/css/custom.css +++ b/www/css/custom.css @@ -160,3 +160,104 @@ html.theme-light { animation:s7 1s infinite; } @keyframes s7 {to{transform: rotate(.5turn)}} + +/* table responsiveness */ + /* Custom table styles */ + .cell-table { + width: 100%; + border-collapse: collapse; +} + +.cell-table th, +.cell-table td { + padding: 0.75rem; +} + +/* Mobile styles */ +@media screen and (max-width: 768px) { + .cell-table thead { + display: none; + } + + .cell-table tbody { + display: block; + } + + .cell-carousel { + position: relative; + overflow: hidden; + padding: 1rem 0; + } + + .cell-carousel__container { + display: flex; + transition: transform 0.3s ease; + min-height: 300px; /* Adjust based on your content */ + } + + .cell-carousel__slide { + flex: 0 0 100%; + padding: 1rem; + box-sizing: border-box; + } + + .cell-card { + border: 1px solid #dbdbdb; + border-radius: 4px; + padding: 1rem; + height: 100%; + } + + .cell-card__item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 0; + border-bottom: 1px solid #dbdbdb; + } + + .cell-card__item:last-child { + border-bottom: none; + } + + .cell-card__label { + font-weight: bold; + margin-right: 1rem; + } + + /* Navigation buttons */ + .cell-carousel__nav { + display: flex; + justify-content: center; + margin-top: 1rem; + } + + /* .cell-carousel__btn { + background: #3273dc; + color: white; + border: none; + padding: 0.5rem 1rem; + margin: 0 0.5rem; + border-radius: 4px; + cursor: pointer; + } */ + + .cell-carousel__indicators { + display: flex; + justify-content: center; + margin-top: 0.5rem; + } + + .cell-carousel__dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: #dbdbdb; + margin: 0 4px; + cursor: pointer; + } + + .cell-carousel__dot--active { + background: #3273dc; + } +} \ No newline at end of file diff --git a/www/home.html b/www/home.html index 9847880..4591674 100644 --- a/www/home.html +++ b/www/home.html @@ -407,20 +407,20 @@ - +
- - - + + + - +
Name EARFCN Bandwidth Physical IDRSRPRSRQSINRRSRPRSRQSINR
diff --git a/www/js/auth/auth.js b/www/js/auth/auth.js index cb9e9a3..69640df 100644 --- a/www/js/auth/auth.js +++ b/www/js/auth/auth.js @@ -1,50 +1,15 @@ -class AuthManager { - constructor() { - this.protectedPages = new Set([ - '/home.html', - '/advance-settings.html', - '/bandlock.html', - '/cell-locking.html', - '/cell-scanner.html', - '/cell-settings.html', - '/cell-sms.html', - '/about.html' - ]); - - // Session timeout in milliseconds (e.g., 30 minutes) - this.sessionTimeout = 30 * 60 * 1000; - - this.init(); - } - - init() { - // Initially hide the body to prevent content flashing - document.body.style.display = 'none'; - - // Check authentication state - this.checkAuthState(); - - // Set up event listeners - this.setupEventListeners(); - - // Show body after auth check - document.body.style.display = ''; - } - - generateAuthToken(length = 32) { - const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; +document.addEventListener("DOMContentLoaded", () => { + const SESSION_DURATION = 30 * 60 * 1000; // 30 minutes in milliseconds + + function generateAuthToken(length = 32) { + const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; return Array.from(crypto.getRandomValues(new Uint8Array(length))) .map(x => charset[x % charset.length]) .join(''); } - - isProtectedPage(path) { - return this.protectedPages.has(path) || - Array.from(this.protectedPages).some(page => path.includes(page)); - } - - getSessionData() { - const sessionStr = localStorage.getItem('session'); + + function getSessionData() { + const sessionStr = localStorage.getItem("session"); if (!sessionStr) return null; try { @@ -53,153 +18,133 @@ class AuthManager { return null; } } - - setSessionData(token) { + + function setSessionData(token) { const session = { token, lastActivity: Date.now(), - expiresAt: Date.now() + this.sessionTimeout + expiresAt: Date.now() + SESSION_DURATION }; - localStorage.setItem('session', JSON.stringify(session)); + localStorage.setItem("session", JSON.stringify(session)); } - - isSessionValid() { - const session = this.getSessionData(); + + function isSessionValid() { + const session = getSessionData(); if (!session) return false; - + const now = Date.now(); // Check if session has expired if (now > session.expiresAt) { - this.logout(); + logout(); return false; } - - // Update last activity and extend session if needed - if (now - session.lastActivity > 5 * 60 * 1000) { // Update every 5 minutes - this.setSessionData(session.token); + + // Extend session if it's been more than 5 minutes since last activity + if (now - session.lastActivity > 5 * 60 * 1000) { + setSessionData(session.token); } - + return true; } - - checkAuthState() { - const currentPath = window.location.pathname; - const isAuthenticated = this.isSessionValid(); - - // Redirect logic - if (!isAuthenticated && this.isProtectedPage(currentPath)) { - window.location.href = '/index.html'; - return false; - } - - if (isAuthenticated && currentPath.includes('index.html')) { - window.location.href = '/home.html'; - return false; - } - - return true; + + function logout() { + localStorage.removeItem("session"); + window.location.href = "index.html"; } - async login(username, password) { - try { - const formData = new URLSearchParams(); - formData.append('username', username); - formData.append('password', encodeURIComponent(password)); + // Initially hide the body to prevent content from flashing + document.body.style.display = "none"; - const response = await fetch('/cgi-bin/auth.sh', { - method: 'POST', - body: formData, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - } - }); + // Define which pages should be protected + const protectedPages = [ + "/home.html", + "/advance-settings.html", + "/bandlock.html", + "/cell-locking.html", + "/cell-scanner.html", + "/cell-settings.html", + "/cell-sms.html", + "/about.html", + ]; - const result = await response.json(); + const currentPage = window.location.pathname; - if (result.state === 'success') { - const token = this.generateAuthToken(); - this.setSessionData(token); - window.location.href = '/home.html'; - return true; - } - - return false; - } catch (error) { - console.error('Login error:', error); - throw new Error('An error occurred during login'); - } + // Authentication check + const isAuthenticated = isSessionValid(); + + // Redirect logic + if (!isAuthenticated && protectedPages.includes(currentPage)) { + window.location.href = "index.html"; + return; } - - logout() { - localStorage.removeItem('session'); - window.location.href = '/index.html'; + + if (isAuthenticated && currentPage.includes("index.html")) { + window.location.href = "home.html"; + return; } + + // Show the page if authentication check is complete + document.body.style.display = ""; - setupEventListeners() { - // Handle login form - const loginForm = document.getElementById('loginForm'); - if (loginForm) { - loginForm.addEventListener('submit', async (e) => { - e.preventDefault(); - const username = document.getElementById('username').value; - const password = document.getElementById('password').value; - const errorElement = document.getElementById('error'); + // Login form logic + const loginForm = document.getElementById("loginForm"); + if (loginForm) { + loginForm.addEventListener("submit", async (e) => { + e.preventDefault(); - try { - const success = await this.login(username, password); - if (!success) { - errorElement.textContent = 'Invalid username or password'; - } - } catch (error) { - errorElement.textContent = error.message; - } - }); - } + const username = document.getElementById("username").value; + const password = document.getElementById("password").value; + const errorElement = document.getElementById("error"); - // Handle component loading - window.addEventListener('componentLoaded', (event) => { - if (event.detail.componentId === 'nav-placeholder') { - this.setupNavbarHandlers(); - } - }); + try { + const formData = new URLSearchParams(); + formData.append("username", username); + formData.append("password", encodeURIComponent(password)); - // Set up periodic session check - setInterval(() => { - if (this.isProtectedPage(window.location.pathname)) { - this.isSessionValid(); - } - }, 60000); // Check every minute - } - - setupNavbarHandlers() { - // Handle logout button - const logoutButton = document.getElementById('logoutButton'); - if (logoutButton) { - logoutButton.addEventListener('click', (e) => { - e.preventDefault(); - this.logout(); - }); - } - - // Handle home navigation - const homeLinks = document.querySelectorAll('.navbar-item'); - homeLinks.forEach(link => { - if (link.textContent.trim() === 'Home') { - link.addEventListener('click', (e) => { - e.preventDefault(); - if (this.isSessionValid()) { - window.location.href = '/home.html'; - } else { - window.location.href = '/index.html'; - } + const response = await fetch("/cgi-bin/auth.sh", { + method: "POST", + body: formData, + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, }); + + const result = await response.json(); + + if (result.state === "success") { + const newToken = generateAuthToken(); + setSessionData(newToken); + window.location.href = "home.html"; + } else { + errorElement.textContent = "Invalid username or password"; + } + } catch (error) { + errorElement.textContent = "An error occurred. Please try again later."; + console.error("Login error:", error); } }); } -} -// Initialize auth manager when DOM is loaded -document.addEventListener('DOMContentLoaded', () => { - window.authManager = new AuthManager(); + // Event listeners + const logoutButton = document.getElementById("logoutButton"); + if (logoutButton) { + logoutButton.addEventListener("click", logout); + } + + document.querySelectorAll(".navbar-item").forEach((el) => { + if (el.textContent.includes("Home")) { + el.addEventListener("click", (e) => { + if (isSessionValid()) { + e.preventDefault(); + window.location.href = "home.html"; + } + }); + } + }); + + // Periodic session check + if (protectedPages.includes(currentPage)) { + setInterval(isSessionValid, 60000); // Check every minute + } }); \ No newline at end of file diff --git a/www/js/home/main.js b/www/js/home/main.js index aa26219..7ff2879 100644 --- a/www/js/home/main.js +++ b/www/js/home/main.js @@ -97,7 +97,11 @@ function handleRefreshClick() { } } - Promise.all([fetchATCommandData(), fetchConnectionStatus(), fetchTrafficStats]).finally(() => { + Promise.all([ + fetchATCommandData(), + fetchConnectionStatus(), + fetchTrafficStats, + ]).finally(() => { if (refreshButton) { refreshButton.disabled = false; const icon = refreshButton.querySelector("i"); @@ -184,7 +188,10 @@ function startPeriodicRefresh(refreshRate = DEFAULT_REFRESH_RATE) { // Start new intervals atCommandInterval = setInterval(fetchATCommandData, refreshRate); - trafficStatsInterval = setInterval(fetchTrafficStats, TRAFFIC_STATS_REFRESH_RATE); + trafficStatsInterval = setInterval( + fetchTrafficStats, + TRAFFIC_STATS_REFRESH_RATE + ); connectionStatusInterval = setInterval( fetchConnectionStatus, refreshRate * CONNECTION_CHECK_MULTIPLIER @@ -620,22 +627,56 @@ function createBandTableRow(bandData, networkType, servingCellJSON) { ?.replace("LTE BAND ", "B") .replace("NR5G BAND ", "N"); - // Create row HTML - row.innerHTML = ` + // Create both desktop and mobile versions of the content + const desktopContent = ` ${formattedBandNumber || "N/A"} ${earfcn || "N/A"} ${bandwidth || "N/A"} ${pci || "N/A"} - - ${rsrp ? createSignalTag(rsrp, "RSRP") : "N/A"} - - - ${rsrq ? createSignalTag(rsrq, "RSRQ") : "N/A"} - - - ${sinr ? createSignalTag(sinr, "SINR") : "N/A"} - + ${rsrp ? createSignalTag(rsrp, "RSRP") : "N/A"} + ${rsrq ? createSignalTag(rsrq, "RSRQ") : "N/A"} + ${sinr ? createSignalTag(sinr, "SINR") : "N/A"} `; + + const mobileContent = ` + + `; + + // Store both versions in data attributes + row.setAttribute("data-desktop", desktopContent); + row.setAttribute("data-mobile", mobileContent); + row.innerHTML = desktopContent; } catch (error) { console.error("Error parsing band data:", error); row.innerHTML = 'Error parsing band data'; @@ -930,7 +971,6 @@ async function fetchTrafficStats() { // Update the DOM setText("download", downloadFormatted); setText("upload", uploadFormatted); - } catch (error) { console.error("There was a problem with the fetch operation:", error); } @@ -1045,3 +1085,118 @@ document.addEventListener("DOMContentLoaded", () => { setupRefreshControls(); setupEventListeners(); }); + +// Mobile carousel functions +let touchStartX = 0; +let touchEndX = 0; + +function initMobileCarousel() { + const table = document.getElementById("bandTable"); + const tbody = table.querySelector("tbody"); + const rows = tbody.querySelectorAll("tr"); + + if (window.innerWidth <= 768) { + // Create carousel structure + const carouselWrapper = document.createElement("div"); + carouselWrapper.className = "cell-carousel"; + + const carouselContainer = document.createElement("div"); + carouselContainer.className = "cell-carousel__container"; + + // Move content to carousel + rows.forEach((row) => { + carouselContainer.insertAdjacentHTML( + "beforeend", + row.getAttribute("data-mobile") + ); + }); + + carouselWrapper.appendChild(carouselContainer); + + // Add only indicators + const indicatorsHTML = ` + + `; + + // Replace table with carousel + table.style.display = "none"; + table.parentNode.insertBefore(carouselWrapper, table); + carouselWrapper.insertAdjacentHTML("beforeend", indicatorsHTML); + + // Add touch event listeners + carouselContainer.addEventListener("touchstart", handleTouchStart, false); + carouselContainer.addEventListener("touchmove", handleTouchMove, false); + carouselContainer.addEventListener("touchend", handleTouchEnd, false); + } else { + // Restore desktop view if necessary + const carousel = document.querySelector(".cell-carousel"); + if (carousel) { + carousel.remove(); + table.style.display = ""; + } + rows.forEach((row) => { + row.innerHTML = row.getAttribute("data-desktop"); + }); + } +} + +function handleTouchStart(event) { + touchStartX = event.touches[0].clientX; +} + +function handleTouchMove(event) { + event.preventDefault(); // Prevent scrolling while swiping +} + +function handleTouchEnd(event) { + touchEndX = event.changedTouches[0].clientX; + handleSwipe(); +} + +function handleSwipe() { + const swipeThreshold = 50; // Minimum distance for a swipe + const container = document.querySelector(".cell-carousel__container"); + const slides = container.querySelectorAll(".cell-carousel__slide"); + + const diffX = touchStartX - touchEndX; + + if (Math.abs(diffX) > swipeThreshold) { + if (diffX > 0 && currentSlide < slides.length - 1) { + // Swipe left, go to next slide + currentSlide++; + } else if (diffX < 0 && currentSlide > 0) { + // Swipe right, go to previous slide + currentSlide--; + } + updateCarousel(); + } +} + +function goToSlide(index) { + currentSlide = index; + updateCarousel(); +} + +function updateCarousel() { + const container = document.querySelector(".cell-carousel__container"); + container.style.transform = `translateX(-${currentSlide * 100}%)`; + + // Update indicators + const dots = document.querySelectorAll(".cell-carousel__dot"); + dots.forEach((dot, index) => { + dot.classList.toggle("cell-carousel__dot--active", index === currentSlide); + }); +} + +// Initialize carousel on load and resize +window.addEventListener("load", initMobileCarousel); +window.addEventListener("resize", initMobileCarousel);