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 ID |
- RSRP |
- RSRQ |
- SINR |
+ RSRP |
+ RSRQ |
+ SINR |
-
+
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 = `
+
+
+
+ Name
+ ${formattedBandNumber || "N/A"}
+
+
+ EARFCN
+ ${earfcn || "N/A"}
+
+
+ Bandwidth
+ ${bandwidth || "N/A"}
+
+
+ Physical ID
+ ${pci || "N/A"}
+
+
+ RSRP
+ ${rsrp ? createSignalTag(rsrp, "RSRP") : "N/A"}
+
+
+ RSRQ
+ ${rsrq ? createSignalTag(rsrq, "RSRQ") : "N/A"}
+
+
+ SINR
+ ${sinr ? createSignalTag(sinr, "SINR") : "N/A"}
+
+
+
+ `;
+
+ // 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 = `
+
+ ${Array.from(
+ { length: rows.length },
+ (_, i) =>
+ ``
+ ).join("")}
+
+ `;
+
+ // 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);