Files
odroid-power-mate/page/src/main.js

295 lines
10 KiB
JavaScript

/**
* @file main.js
* @description The main entry point for the web application.
* This file imports all necessary modules, sets up the application structure,
* initializes components, and establishes the WebSocket connection.
*/
// --- Stylesheets ---
import 'bootstrap/dist/css/bootstrap.min.css';
import 'bootstrap-icons/font/bootstrap-icons.css';
import './style.css';
// --- Module Imports -- -
import {StatusMessage} from './proto.js';
import * as api from './api.js';
import {initWebSocket} from './websocket.js';
import {setupTerminal, term} from './terminal.js';
import {
applyTheme,
initUI,
updateControlStatus,
updateSensorUI,
updateSwitchStatusUI,
updateUptimeUI,
updateVersionUI,
updateWebsocketStatus,
updateWifiStatusUI
} from './ui.js';
import {setupEventListeners} from './events.js';
// --- Globals ---
// StatusMessage is imported directly from the generated proto.js file.
// --- DOM Elements ---
const loginContainer = document.getElementById('login-container');
const mainContent = document.querySelector('main.container');
const loginForm = document.getElementById('login-form');
const usernameInput = document.getElementById('username');
const passwordInput = document.getElementById('password');
const loginAlert = document.getElementById('login-alert');
const logoutButton = document.getElementById('logout-button');
const themeToggleLogin = document.getElementById('theme-toggle-login');
const themeIconLogin = document.getElementById('theme-icon-login');
const themeToggleMain = document.getElementById('theme-toggle');
const themeIconMain = document.getElementById('theme-icon');
// User Settings DOM Elements
const userSettingsForm = document.getElementById('user-settings-form');
const newUsernameInput = document.getElementById('new-username');
const newPasswordInput = document.getElementById('new-password');
const confirmPasswordInput = document.getElementById('confirm-password');
// --- WebSocket Event Handlers ---
function onWsOpen() {
updateWebsocketStatus(true);
console.log('Connected to WebSocket Server');
}
function onWsClose() {
updateWebsocketStatus(false);
console.warn('Connection closed. Reconnecting...');
setTimeout(connect, 2000);
}
/**
* Callback for when a message is received from the WebSocket server.
* @param {MessageEvent} event - The WebSocket message event.
*/
function onWsMessage(event) {
if (!(event.data instanceof ArrayBuffer)) {
console.warn('Message is not an ArrayBuffer, skipping protobuf decoding.');
return;
}
const buffer = new Uint8Array(event.data);
try {
const decodedMessage = StatusMessage.decode(buffer);
const payloadType = decodedMessage.payload;
switch (payloadType) {
case 'sensorData': {
const sensorData = decodedMessage.sensorData;
if (sensorData) {
// Create a payload for the sensor UI (charts and header)
const sensorPayload = {
USB: sensorData.usb,
MAIN: sensorData.main,
VIN: sensorData.vin,
timestamp: sensorData.timestamp
};
updateSensorUI(sensorPayload);
// Update uptime separately from the sensor data payload
if (sensorData.uptimeSec !== undefined) {
updateUptimeUI(sensorData.uptimeSec);
}
}
break;
}
case 'wifiStatus':
updateWifiStatusUI(decodedMessage.wifiStatus);
break;
case 'swStatus':
if (decodedMessage.swStatus) {
updateSwitchStatusUI(decodedMessage.swStatus);
}
break;
case 'uartData':
if (term && decodedMessage.uartData && decodedMessage.uartData.data) {
term.write(decodedMessage.uartData.data);
}
break;
default:
if (payloadType !== undefined) {
console.warn('Received message with unknown or empty payload type:', payloadType);
}
break;
}
} catch (e) {
console.error('Error decoding protobuf message:', e);
}
}
// --- Authentication Functions ---
function checkAuth() {
const token = localStorage.getItem('authToken');
if (token) {
return true;
} else {
return false;
}
}
async function handleLogin(event) {
event.preventDefault();
const username = usernameInput.value;
const password = passwordInput.value;
try {
const response = await api.login(username, password);
if (response && response.token) {
localStorage.setItem('authToken', response.token);
loginAlert.classList.add('d-none');
loginContainer.style.setProperty('display', 'none', 'important');
initializeMainAppContent(); // After successful login, initialize the main app
} else {
loginAlert.textContent = 'Login failed: No token received.';
loginAlert.classList.remove('d-none');
}
} catch (error) {
console.error('Login error:', error);
loginAlert.textContent = `Login failed: ${error.message}`;
loginAlert.classList.remove('d-none');
}
}
function handleLogout() {
localStorage.removeItem('authToken');
// Hide main content and show login form
loginContainer.style.setProperty('display', 'flex', 'important');
mainContent.style.setProperty('display', 'none', 'important');
// Optionally, disconnect WebSocket or perform other cleanup
// For now, just hide the main content.
}
// --- User Settings Functions ---
async function handleUserSettingsSubmit(event) {
event.preventDefault();
const newUsername = newUsernameInput.value;
const newPassword = newPasswordInput.value;
const confirmPassword = confirmPasswordInput.value;
if (!newUsername || !newPassword || !confirmPassword) {
alert('Please fill in all fields for username and password.');
return;
}
if (newPassword !== confirmPassword) {
alert('New password and confirm password do not match.');
return;
}
try {
const response = await api.updateUserSettings(newUsername, newPassword);
if (response && response.status === 'user_credentials_updated') {
alert('Username and password updated successfully. Please log in again with new credentials.');
handleLogout(); // Force logout to re-authenticate with new credentials
} else {
alert(`Failed to update credentials: ${response.message || 'Unknown error'}`);
}
} catch (error) {
console.error('Error updating user settings:', error);
alert(`Error updating user settings: ${error.message}`);
}
}
// --- Theme Toggle Functions ---
function setupThemeToggles() {
// Initialize theme for login page
const savedTheme = localStorage.getItem('theme') || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
applyTheme(savedTheme);
themeToggleLogin.checked = savedTheme === 'dark';
themeIconLogin.className = savedTheme === 'dark' ? 'bi bi-moon-stars-fill' : 'bi bi-sun-fill';
// Sync main theme toggle with login theme toggle (initial state)
themeToggleMain.checked = savedTheme === 'dark';
themeIconMain.className = savedTheme === 'dark' ? 'bi bi-moon-stars-fill' : 'bi bi-sun-fill';
themeToggleLogin.addEventListener('change', () => {
const newTheme = themeToggleLogin.checked ? 'dark' : 'light';
applyTheme(newTheme);
localStorage.setItem('theme', newTheme);
themeIconLogin.className = newTheme === 'dark' ? 'bi bi-moon-stars-fill' : 'bi bi-sun-fill';
themeToggleMain.checked = themeToggleLogin.checked; // Keep main toggle in sync
themeIconMain.className = themeIconLogin.className; // Keep main icon in sync
});
themeToggleMain.addEventListener('change', () => {
const newTheme = themeToggleMain.checked ? 'dark' : 'light';
applyTheme(newTheme);
localStorage.setItem('theme', newTheme);
themeIconMain.className = newTheme === 'dark' ? 'bi bi-moon-stars-fill' : 'bi bi-sun-fill';
themeToggleLogin.checked = themeToggleMain.checked; // Keep login toggle in sync
themeIconLogin.className = themeIconMain.className; // Keep login icon in sync
});
}
// --- Application Initialization ---
async function initializeVersion() {
try {
const versionData = await api.fetchVersion();
if (versionData && versionData.version) {
updateVersionUI(versionData.version);
}
} catch (error) {
console.error('Error fetching version:', error);
updateVersionUI('N/A');
}
}
function connect() {
updateControlStatus();
initWebSocket({ onOpen: onWsOpen, onClose: onWsClose, onMessage: onWsMessage });
}
// New function to initialize main app content after successful login or on initial load if authenticated
function initializeMainAppContent() {
loginContainer.style.setProperty('display', 'none', 'important');
mainContent.style.setProperty('display', 'block', 'important');
initUI();
setupTerminal();
initializeVersion();
setupEventListeners(); // Attach main app event listeners
logoutButton.addEventListener('click', handleLogout); // Attach logout listener
connect();
// Attach user settings form listener
if (userSettingsForm) {
userSettingsForm.addEventListener('submit', handleUserSettingsSubmit);
}
}
function initialize() {
setupThemeToggles(); // Setup theme toggles for both login and main (initial sync)
// Always attach login form listener
loginForm.addEventListener('submit', handleLogin);
if (!checkAuth()) { // If NOT authenticated
// Show login form
loginContainer.style.setProperty('display', 'flex', 'important');
mainContent.style.setProperty('display', 'none', 'important');
console.log('Not authenticated. Login form displayed. Main app content NOT initialized.');
return; // IMPORTANT: Stop execution here if not authenticated
}
// If authenticated, initialize main content
console.log('Authenticated. Initializing main app content.');
initializeMainAppContent();
}
// --- Start Application ---
document.addEventListener('DOMContentLoaded', initialize);