all working

This commit is contained in:
HotSwapp
2025-08-10 21:34:11 -05:00
parent 14ee479edc
commit 1512b2d12a
22 changed files with 1453 additions and 489 deletions

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -347,12 +347,7 @@
}
});
// Handle escape key for modal
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeShortcutsModal();
}
});
// Escape handling is centralized in keyboard-shortcuts.js
</script>
<script>
// Global modal helpers for Tailwind-based modals
@@ -367,20 +362,18 @@
</script>
<!-- Custom JavaScript -->
<!-- Load main.js first so global handlers are registered before other scripts -->
<script src="/static/js/main.js"></script>
<script src="/static/js/alerts.js"></script>
<script src="/static/js/keyboard-shortcuts.js"></script>
<script src="/static/js/main.js"></script>
{% block extra_scripts %}{% endblock %}
<script>
// Initialize keyboard shortcuts on page load
// Initialize page meta on load (keyboard shortcuts initialized centrally in main.js)
document.addEventListener('DOMContentLoaded', function() {
initializeKeyboardShortcuts();
updateCurrentPageDisplay();
updateCurrentYear();
initializeAuthManager();
checkUserPermissions();
});
// Update current page display in footer
@@ -414,357 +407,7 @@
}
}
// Check user permissions and show/hide admin menu
async function checkUserPermissions() {
const token = localStorage.getItem('auth_token');
if (!token || isLoginPage()) {
return;
}
try {
const response = await fetch('/api/auth/me', {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
const user = await response.json();
if (user.is_admin) {
// Show admin menu items
document.getElementById('admin-menu-item').classList.remove('hidden');
document.getElementById('admin-menu-divider').classList.remove('hidden');
}
// Update user display name if available
const userDropdown = document.querySelector('#userDropdown button span');
if (user.full_name && userDropdown) {
userDropdown.textContent = user.full_name;
}
}
} catch (error) {
console.error('Error checking user permissions:', error);
}
}
// Authentication Manager
function initializeAuthManager() {
// Check if we have a valid token on page load
const token = localStorage.getItem('auth_token');
if (token && !isLoginPage()) {
// Verify token is still valid
checkTokenValidity();
// Set up periodic token refresh (every hour)
setInterval(refreshTokenIfNeeded, 3600000); // 1 hour
// Set up activity monitoring for auto-refresh
setupActivityMonitoring();
} else if (!isLoginPage() && !token) {
// No token and not on login page - redirect to login
window.location.href = '/login';
}
}
function isLoginPage() {
return window.location.pathname === '/login' || window.location.pathname === '/';
}
async function checkTokenValidity() {
const token = localStorage.getItem('auth_token');
if (!token) return false;
try {
const response = await fetch('/api/auth/me', {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (!response.ok) {
// Token is invalid, remove it and redirect to login
localStorage.removeItem('auth_token');
if (!isLoginPage()) {
window.location.href = '/login';
}
return false;
}
return true;
} catch (error) {
console.error('Error checking token validity:', error);
return false;
}
}
async function refreshTokenIfNeeded() {
const token = localStorage.getItem('auth_token');
if (!token) return;
try {
// Try to get a new token
const response = await fetch('/api/auth/refresh', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (response.ok) {
const data = await response.json();
localStorage.setItem('auth_token', data.access_token);
console.log('Token refreshed successfully');
} else {
// If refresh fails, check if current token is still valid
const isValid = await checkTokenValidity();
if (!isValid) {
localStorage.removeItem('auth_token');
window.location.href = '/login';
}
}
} catch (error) {
console.error('Error refreshing token:', error);
}
}
async function setupActivityMonitoring() {
let lastActivity = Date.now();
let warningShown = false;
let inactivityWarningMinutes = 240; // default 4 hours
const inactivityGraceMinutes = 5; // auto-logout after warning + 5 minutes
let inactivityAlertEl = null;
try {
const minutes = await getInactivityWarningMinutes();
if (Number.isFinite(minutes) && minutes > 0) {
inactivityWarningMinutes = minutes;
}
} catch (e) {
console.debug('Using default inactivity warning minutes');
}
// Track user activity
const activityEvents = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart'];
activityEvents.forEach(event => {
document.addEventListener(event, () => {
lastActivity = Date.now();
warningShown = false; // Reset warning flag on activity
hideInactivityWarning();
});
});
function showInactivityWarning() {
hideInactivityWarning();
const msg = `You've been inactive. Your session may expire due to inactivity.`;
if (window.alerts && typeof window.alerts.show === 'function') {
inactivityAlertEl = window.alerts.show(msg, 'warning', {
title: 'Session Warning',
html: false,
duration: 0,
dismissible: true,
id: 'inactivity-warning',
actions: [
{
label: 'Stay Logged In',
classes: 'bg-warning-600 hover:bg-warning-700 text-white text-xs px-3 py-1 rounded',
onClick: () => extendSession(),
autoClose: true
},
{
label: 'Dismiss',
classes: 'bg-neutral-200 hover:bg-neutral-300 text-neutral-800 dark:bg-neutral-700 dark:hover:bg-neutral-600 dark:text-neutral-200 text-xs px-3 py-1 rounded',
onClick: () => hideInactivityWarning(),
autoClose: true
}
]
});
} else {
// Fallback
alert('Session Warning: ' + msg);
}
// Auto-hide after 2 minutes if no action taken
setTimeout(() => {
hideInactivityWarning();
}, 2 * 60 * 1000);
}
function hideInactivityWarning() {
const el = document.getElementById('inactivity-warning');
if (el && el.remove) el.remove();
inactivityAlertEl = null;
}
function extendSession() {
refreshTokenIfNeeded();
hideInactivityWarning();
showSessionExtendedNotification();
}
// Check every 5 minutes for inactivity
setInterval(() => {
const now = Date.now();
const warningMs = inactivityWarningMinutes * 60 * 1000;
const logoutMs = (inactivityWarningMinutes + inactivityGraceMinutes) * 60 * 1000;
const timeSinceActivity = now - lastActivity;
if (timeSinceActivity > warningMs && !warningShown) {
showInactivityWarning();
warningShown = true;
}
if (timeSinceActivity > logoutMs) {
logout('Session expired due to inactivity');
}
}, 5 * 60 * 1000); // Check every 5 minutes
}
async function getInactivityWarningMinutes() {
const token = localStorage.getItem('auth_token');
if (!token) return 240;
const resp = await fetch('/api/settings/inactivity_warning_minutes', {
headers: { 'Authorization': `Bearer ${token}` }
});
if (!resp.ok) return 240;
const data = await resp.json();
if (typeof data.minutes === 'number') return data.minutes;
const parsed = parseInt(data.setting_value || data.minutes, 10);
return Number.isFinite(parsed) ? parsed : 240;
}
function showSessionExtendedNotification() {
if (window.alerts && typeof window.alerts.success === 'function') {
window.alerts.success('Your session has been refreshed successfully.', {
title: 'Session Extended',
duration: 3000
});
return;
}
// Fallback
const notification = document.createElement('div');
notification.className = 'fixed top-4 right-4 bg-green-100 border-l-4 border-green-500 text-green-700 p-4 rounded-lg shadow-lg z-50 max-w-sm';
notification.innerHTML = `
<div class="flex items-center">
<div class="flex-shrink-0">
<i class="fas fa-check-circle text-green-500"></i>
</div>
<div class="ml-3">
<p class="text-sm font-medium">Session Extended</p>
<p class="text-xs mt-1">Your session has been refreshed successfully.</p>
</div>
</div>
`;
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 3000);
}
// Enhanced logout function
function logout(reason = null) {
localStorage.removeItem('auth_token');
if (reason) {
// Store logout reason to show on login page
sessionStorage.setItem('logout_reason', reason);
}
window.location.href = '/login';
}
// Theme Management
function toggleTheme() {
const html = document.documentElement;
const isDark = html.classList.contains('dark');
if (isDark) {
html.classList.remove('dark');
localStorage.setItem('theme-preference', 'light');
saveThemePreference('light');
} else {
html.classList.add('dark');
localStorage.setItem('theme-preference', 'dark');
saveThemePreference('dark');
}
}
function initializeTheme() {
// Check for saved theme preference
const savedTheme = localStorage.getItem('theme-preference');
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
// Use saved theme, or default to system preference
const theme = savedTheme || (prefersDark ? 'dark' : 'light');
if (theme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
// Load user's theme preference from server if authenticated
loadUserThemePreference();
}
async function saveThemePreference(theme) {
const token = localStorage.getItem('auth_token');
if (!token || isLoginPage()) return;
try {
await fetch('/api/auth/theme-preference', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ theme_preference: theme })
});
} catch (error) {
console.log('Could not save theme preference to server:', error.message);
}
}
async function loadUserThemePreference() {
const token = localStorage.getItem('auth_token');
if (!token || isLoginPage()) return;
try {
const response = await fetch('/api/auth/me', {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
const user = await response.json();
if (user.theme_preference) {
if (user.theme_preference === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
localStorage.setItem('theme-preference', user.theme_preference);
}
}
} catch (error) {
console.log('Could not load theme preference from server:', error.message);
}
}
// Initialize theme before other scripts
initializeTheme();
// Make functions globally available
window.authManager = {
checkTokenValidity,
refreshTokenIfNeeded,
logout
};
window.themeManager = {
toggleTheme,
initializeTheme,
saveThemePreference,
loadUserThemePreference
};
// Auth helpers and theme management are centralized in /static/js/main.js
</script>
</body>
</html>