added auth improvements and luci
This commit is contained in:
135
www/js/advance/at-terminal.js
Normal file
135
www/js/advance/at-terminal.js
Normal file
@@ -0,0 +1,135 @@
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const form = document.getElementById("commandForm");
|
||||
const output = document.getElementById("output");
|
||||
const commandInput = document.getElementById("command");
|
||||
const sendButton = document.getElementById("sendButton");
|
||||
const commandHistory = document.getElementById("commandHistory");
|
||||
const noHistory = document.getElementById("noHistory");
|
||||
const clearHistoryButton = document.getElementById("clearHistory");
|
||||
const cooldownTimer = document.getElementById("cooldownTimer");
|
||||
|
||||
const COOLDOWN_DURATION = 1000; // 1 second cooldown
|
||||
let isLoading = false;
|
||||
let cooldownActive = false;
|
||||
|
||||
function setLoading(loading) {
|
||||
isLoading = loading;
|
||||
sendButton.classList.toggle("is-loading", loading);
|
||||
form.classList.toggle("loading", loading);
|
||||
}
|
||||
|
||||
function setCooldown() {
|
||||
cooldownActive = true;
|
||||
sendButton.classList.add("cooldown");
|
||||
let timeLeft = COOLDOWN_DURATION;
|
||||
|
||||
function updateTimer() {
|
||||
timeLeft -= 100;
|
||||
if (timeLeft <= 0) {
|
||||
cooldownActive = false;
|
||||
sendButton.classList.remove("cooldown");
|
||||
cooldownTimer.textContent = "";
|
||||
return;
|
||||
}
|
||||
cooldownTimer.textContent = `${(timeLeft / 1000).toFixed(1)}s`;
|
||||
setTimeout(updateTimer, 100);
|
||||
}
|
||||
|
||||
updateTimer();
|
||||
}
|
||||
|
||||
function updateHistoryVisibility() {
|
||||
const hasHistoryItems =
|
||||
commandHistory.querySelectorAll(".history-item").length > 0;
|
||||
noHistory.style.display = hasHistoryItems ? "none" : "block";
|
||||
clearHistoryButton.style.display = hasHistoryItems ? "block" : "none";
|
||||
}
|
||||
|
||||
function addToHistory(command, response) {
|
||||
const historyItem = document.createElement("div");
|
||||
historyItem.className = "box mb-2 history-item";
|
||||
historyItem.innerHTML = `
|
||||
<button class="delete delete-history" aria-label="delete"></button>
|
||||
<strong class="command-text">${command}</strong>
|
||||
<pre style="margin-top: 0.5rem; font-size: 0.85em; white-space: pre-wrap;">${response}</pre>
|
||||
`;
|
||||
|
||||
historyItem
|
||||
.querySelector(".command-text")
|
||||
.addEventListener("click", () => {
|
||||
commandInput.value = command;
|
||||
commandInput.focus();
|
||||
});
|
||||
|
||||
historyItem
|
||||
.querySelector(".delete-history")
|
||||
.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
historyItem.classList.add("fade-out");
|
||||
setTimeout(() => {
|
||||
historyItem.remove();
|
||||
updateHistoryVisibility();
|
||||
}, 300);
|
||||
});
|
||||
|
||||
commandHistory.insertBefore(historyItem, commandHistory.firstChild);
|
||||
updateHistoryVisibility();
|
||||
}
|
||||
|
||||
clearHistoryButton.addEventListener("click", () => {
|
||||
const historyItems = commandHistory.querySelectorAll(".history-item");
|
||||
historyItems.forEach((item) => {
|
||||
item.classList.add("fade-out");
|
||||
});
|
||||
setTimeout(() => {
|
||||
commandHistory.innerHTML = "";
|
||||
commandHistory.appendChild(noHistory);
|
||||
updateHistoryVisibility();
|
||||
}, 300);
|
||||
});
|
||||
|
||||
async function sendCommand(command) {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await fetch("/cgi-bin/atinout_handler.sh", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: `command=${encodeURIComponent(command)}`,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
output.value = data.output || "No response received";
|
||||
addToHistory(command, data.output || "No response received");
|
||||
setCooldown();
|
||||
} catch (error) {
|
||||
const errorMessage = `Error: ${error.message}\n\nTroubleshooting steps:\n1. Check if the device is connected\n2. Verify AT port settings\n3. Ensure atinout utility is installed`;
|
||||
output.value = errorMessage;
|
||||
addToHistory(command, errorMessage);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
form.addEventListener("submit", async function (e) {
|
||||
e.preventDefault();
|
||||
if (isLoading || cooldownActive) return;
|
||||
|
||||
const command = commandInput.value.trim();
|
||||
if (!command) {
|
||||
output.value = "Please enter a command";
|
||||
return;
|
||||
}
|
||||
|
||||
await sendCommand(command);
|
||||
commandInput.value = "";
|
||||
});
|
||||
|
||||
// Initialize visibility
|
||||
updateHistoryVisibility();
|
||||
});
|
||||
@@ -1,108 +1,205 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// Function to generate a random token
|
||||
function generateAuthToken(length = 32) {
|
||||
const charset =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
let token = "";
|
||||
for (let i = 0; i < length; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * charset.length);
|
||||
token += charset[randomIndex];
|
||||
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';
|
||||
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');
|
||||
if (!sessionStr) return null;
|
||||
|
||||
try {
|
||||
return JSON.parse(sessionStr);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
setSessionData(token) {
|
||||
const session = {
|
||||
token,
|
||||
lastActivity: Date.now(),
|
||||
expiresAt: Date.now() + this.sessionTimeout
|
||||
};
|
||||
localStorage.setItem('session', JSON.stringify(session));
|
||||
}
|
||||
|
||||
isSessionValid() {
|
||||
const session = this.getSessionData();
|
||||
if (!session) return false;
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
// Check if session has expired
|
||||
if (now > session.expiresAt) {
|
||||
this.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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
async login(username, password) {
|
||||
try {
|
||||
const formData = new URLSearchParams();
|
||||
formData.append('username', username);
|
||||
formData.append('password', encodeURIComponent(password));
|
||||
|
||||
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 token = this.generateAuthToken();
|
||||
this.setSessionData(token);
|
||||
window.location.href = '/home.html';
|
||||
return true;
|
||||
}
|
||||
return token;
|
||||
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
throw new Error('An error occurred during login');
|
||||
}
|
||||
|
||||
// Initially hide the body to prevent content from flashing
|
||||
document.body.style.display = "none";
|
||||
|
||||
// Check if the user is already logged in
|
||||
const authToken = localStorage.getItem("authToken");
|
||||
|
||||
// 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", // Add all the protected HTML pages here
|
||||
];
|
||||
|
||||
const currentPage = window.location.pathname;
|
||||
|
||||
// If the user is not logged in and tries to access a protected page, redirect to login
|
||||
if (!authToken && protectedPages.includes(currentPage)) {
|
||||
window.location.href = "index.html";
|
||||
} else {
|
||||
// Show the page if authentication is successful or not required
|
||||
document.body.style.display = "";
|
||||
}
|
||||
|
||||
// If the user is logged in and tries to access the login page, redirect to home
|
||||
if (authToken && currentPage.includes("index.html")) {
|
||||
window.location.href = "home.html";
|
||||
}
|
||||
|
||||
// Login form logic (only for login page)
|
||||
const loginForm = document.getElementById("loginForm");
|
||||
}
|
||||
|
||||
logout() {
|
||||
localStorage.removeItem('session');
|
||||
window.location.href = '/index.html';
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Handle login form
|
||||
const loginForm = document.getElementById('loginForm');
|
||||
if (loginForm) {
|
||||
loginForm.addEventListener("submit", async (e) => {
|
||||
loginForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const username = document.getElementById("username").value;
|
||||
const password = document.getElementById("password").value;
|
||||
const errorElement = document.getElementById("error");
|
||||
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
const errorElement = document.getElementById('error');
|
||||
|
||||
try {
|
||||
const formData = new URLSearchParams();
|
||||
formData.append("username", username);
|
||||
formData.append("password", encodeURIComponent(password)); // URL-encode the password
|
||||
|
||||
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(); // Parse JSON response
|
||||
|
||||
if (result.state === "success") {
|
||||
const newToken = generateAuthToken();
|
||||
localStorage.setItem("authToken", newToken); // Store the token
|
||||
window.location.href = "home.html"; // Redirect on success
|
||||
} else {
|
||||
document.getElementById("error").textContent =
|
||||
"Invalid username or password";
|
||||
console.log("Invalid username or password");
|
||||
const success = await this.login(username, password);
|
||||
if (!success) {
|
||||
errorElement.textContent = 'Invalid username or password';
|
||||
}
|
||||
} catch (error) {
|
||||
// Handle any errors (e.g., network issues)
|
||||
errorElement.textContent = "An error occurred. Please try again later.";
|
||||
errorElement.textContent = error.message;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Logout button logic (only for pages that have the logout button)
|
||||
const logoutButton = document.getElementById("logoutButton");
|
||||
|
||||
// Handle component loading
|
||||
window.addEventListener('componentLoaded', (event) => {
|
||||
if (event.detail.componentId === 'nav-placeholder') {
|
||||
this.setupNavbarHandlers();
|
||||
}
|
||||
});
|
||||
|
||||
// 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", () => {
|
||||
localStorage.removeItem("authToken"); // Remove token
|
||||
window.location.href = "index.html"; // Redirect to login
|
||||
logoutButton.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.logout();
|
||||
});
|
||||
}
|
||||
|
||||
// Fix for the issue of being redirected to login every time the Home button is clicked
|
||||
document.querySelectorAll(".navbar-item").forEach((el) => {
|
||||
if (el.textContent.includes("Home")) {
|
||||
el.addEventListener("click", (e) => {
|
||||
if (localStorage.getItem("authToken")) {
|
||||
e.preventDefault();
|
||||
window.location.href = "home.html";
|
||||
|
||||
// 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';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize auth manager when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
window.authManager = new AuthManager();
|
||||
});
|
||||
42
www/js/styles/modal-trigger.js
Normal file
42
www/js/styles/modal-trigger.js
Normal file
@@ -0,0 +1,42 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Functions to open and close a modal
|
||||
function openModal($el) {
|
||||
$el.classList.add('is-active');
|
||||
}
|
||||
|
||||
function closeModal($el) {
|
||||
$el.classList.remove('is-active');
|
||||
}
|
||||
|
||||
function closeAllModals() {
|
||||
(document.querySelectorAll('.modal') || []).forEach(($modal) => {
|
||||
closeModal($modal);
|
||||
});
|
||||
}
|
||||
|
||||
// Add a click event on buttons to open a specific modal
|
||||
(document.querySelectorAll('.reboot-modal') || []).forEach(($trigger) => {
|
||||
const modal = $trigger.dataset.target;
|
||||
const $target = document.getElementById(modal);
|
||||
|
||||
$trigger.addEventListener('click', () => {
|
||||
openModal($target);
|
||||
});
|
||||
});
|
||||
|
||||
// Add a click event on various child elements to close the parent modal
|
||||
(document.querySelectorAll('.modal-background, .modal-close, .modal-card-head .delete, .modal-card-body .cancel') || []).forEach(($close) => {
|
||||
const $target = $close.closest('.modal');
|
||||
|
||||
$close.addEventListener('click', () => {
|
||||
closeModal($target);
|
||||
});
|
||||
});
|
||||
|
||||
// Add a keyboard event to close all modals
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if(event.key === "Escape") {
|
||||
closeAllModals();
|
||||
}
|
||||
});
|
||||
});
|
||||
132
www/js/utils/reboot.js
Normal file
132
www/js/utils/reboot.js
Normal file
@@ -0,0 +1,132 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const modal = document.getElementById('reboot-modal');
|
||||
const rebootButton = document.getElementById('rebootModem');
|
||||
const cancelButtons = modal.querySelectorAll('.cancel, .modal-background');
|
||||
const restartConnectionBtn = document.querySelector('a.button.is-link.is-outlined');
|
||||
const modalMessage = document.getElementById('modal-message');
|
||||
const loadingContent = document.getElementById('loading-content');
|
||||
const modalButtons = document.getElementById('modal-buttons');
|
||||
const countdownElement = document.getElementById('countdown');
|
||||
|
||||
let countdownInterval;
|
||||
|
||||
function toggleModal(show = true) {
|
||||
modal.classList.toggle('is-active', show);
|
||||
document.documentElement.classList.toggle('is-clipped', show);
|
||||
|
||||
// Reset modal content when closing
|
||||
if (!show) {
|
||||
modalMessage.style.display = 'block';
|
||||
loadingContent.style.display = 'none';
|
||||
modalButtons.style.display = 'flex';
|
||||
if (countdownInterval) {
|
||||
clearInterval(countdownInterval);
|
||||
}
|
||||
countdownElement.textContent = '80';
|
||||
}
|
||||
}
|
||||
|
||||
function startCountdown() {
|
||||
let timeLeft = 80;
|
||||
|
||||
// Update display for countdown
|
||||
modalMessage.style.display = 'none';
|
||||
loadingContent.style.display = 'flex';
|
||||
modalButtons.style.display = 'none';
|
||||
|
||||
countdownInterval = setInterval(() => {
|
||||
timeLeft--;
|
||||
countdownElement.textContent = timeLeft;
|
||||
|
||||
if (timeLeft <= 0) {
|
||||
clearInterval(countdownInterval);
|
||||
window.location.reload();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Show modal when restart connection button is clicked
|
||||
restartConnectionBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
toggleModal(true);
|
||||
});
|
||||
|
||||
// Hide modal when cancel or background is clicked
|
||||
cancelButtons.forEach(button => {
|
||||
button.addEventListener('click', () => toggleModal(false));
|
||||
});
|
||||
|
||||
// Handle ESC key press
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && modal.classList.contains('is-active')) {
|
||||
toggleModal(false);
|
||||
}
|
||||
});
|
||||
|
||||
// Function to send AT command
|
||||
async function sendRebootCommand() {
|
||||
try {
|
||||
// Disable the reboot button and show loading state
|
||||
rebootButton.classList.add('is-loading');
|
||||
rebootButton.disabled = true;
|
||||
|
||||
const response = await fetch('/cgi-bin/atinout_handler.sh', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: 'command=' + encodeURIComponent('AT+CFUN=1,1')
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.output && data.output.includes('OK')) {
|
||||
startCountdown();
|
||||
} else {
|
||||
throw new Error('Reboot command failed');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
toggleModal(false);
|
||||
showNotification('Failed to reboot device. Please try again.', 'is-danger');
|
||||
} finally {
|
||||
// Re-enable the reboot button and remove loading state
|
||||
rebootButton.classList.remove('is-loading');
|
||||
rebootButton.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to show notification (for errors only now)
|
||||
function showNotification(message, type = 'is-info') {
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification ${type} is-light`;
|
||||
notification.style.position = 'fixed';
|
||||
notification.style.top = '1rem';
|
||||
notification.style.right = '1rem';
|
||||
notification.style.zIndex = '9999';
|
||||
notification.style.maxWidth = '300px';
|
||||
|
||||
const deleteButton = document.createElement('button');
|
||||
deleteButton.className = 'delete';
|
||||
deleteButton.addEventListener('click', () => notification.remove());
|
||||
|
||||
notification.appendChild(deleteButton);
|
||||
notification.appendChild(document.createTextNode(message));
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
if (document.body.contains(notification)) {
|
||||
notification.remove();
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Handle reboot button click
|
||||
rebootButton.addEventListener('click', sendRebootCommand);
|
||||
});
|
||||
Reference in New Issue
Block a user