Merge claude/1

This commit is contained in:
librelad 2026-05-30 21:04:09 +01:00
commit 7a7ef3ebc1
10 changed files with 6 additions and 554 deletions

View File

@ -0,0 +1 @@
{"sessionId":"9cea077c-56da-4223-a765-be38c688106b","pid":1384,"procStart":"1332","acquiredAt":1780168110981}

View File

@ -149,11 +149,6 @@ class SystemLoader {
window.taskEventBus.start(); window.taskEventBus.start();
} }
// Initialize task global functions
if (typeof setupTaskGlobalFunctions === 'function') {
setupTaskGlobalFunctions();
}
// Create TasksManager instance // Create TasksManager instance
if (typeof TasksManager !== 'undefined') { if (typeof TasksManager !== 'undefined') {
window.tasksManager = new TasksManager(); window.tasksManager = new TasksManager();
@ -171,7 +166,6 @@ class SystemLoader {
'/core/lib/task/task-commands.js', '/core/lib/task/task-commands.js',
'/core/lib/task/task-actions.js', '/core/lib/task/task-actions.js',
'/core/lib/task/task-router.js', '/core/lib/task/task-router.js',
'/core/lib/task/task-global-functions.js',
'/core/lib/task/task-manager.js', '/core/lib/task/task-manager.js',
'/core/lib/task/task-parameter-preserve.js', '/core/lib/task/task-parameter-preserve.js',
'/components/tasks/js/tasks-manager.js', // base: class + constructor + init + bus wiring '/components/tasks/js/tasks-manager.js', // base: class + constructor + init + bus wiring

View File

@ -243,7 +243,6 @@ class SystemOrchestrator {
// Now initialize the SPA after all components are ready // Now initialize the SPA after all components are ready
this.initializeOriginalSPA(); this.initializeOriginalSPA();
this._wireLogout();
this.proceedToApplication(); this.proceedToApplication();
} }

View File

@ -290,11 +290,9 @@ class LibrePortalSPAClean {
this.showError('Page not found'); this.showError('Page not found');
} }
// Update navigation highlighting after content loads // Update navigation highlighting after content loads. The live topbar
if (typeof window.topbarNavigationHighlighting === 'function') { // singleton recomputes the active nav from the current path.
//console.log('🔗 SPA: Updating navigation highlighting after navigation'); window.topbar?.setActiveNav?.();
window.topbarNavigationHighlighting();
}
} catch (error) { } catch (error) {
console.error('❌ Navigation error:', error); console.error('❌ Navigation error:', error);

View File

@ -1,28 +0,0 @@
/**
* Global Functions for Individual Task Actions
* Extends the tasks manager with global action functions for individual tasks
*/
// Task global functions initialization is now handled by SystemLoader
// Global task functions will be set up centrally when TasksManager is available
function setupTaskGlobalFunctions() {
if (window.TasksManager || window.tasksManager) {
// Use whichever instance is available
const tasksManager = window.tasksManager || window.TasksManager;
// Add modular action functions to global scope
window.installApp = (appName, config = '') => tasksManager.router.routeAction('install', { appName, config });
window.uninstallApp = (appName) => tasksManager.router.routeAction('uninstall', { appName });
window.restartApp = (appName) => tasksManager.router.routeAction('restart', { appName });
window.startApp = (appName) => tasksManager.router.routeAction('start', { appName });
window.stopApp = (appName) => tasksManager.router.routeAction('stop', { appName });
window.backupApp = (appName) => tasksManager.router.routeAction('backup', { appName });
window.updateConfig = (appName) => tasksManager.router.routeAction('update_config', { appName });
window.systemUpdate = () => tasksManager.router.routeAction('system_update');
//console.log('✅ Task action functions registered globally');
} else {
console.warn('⚠️ TasksManager not found, action functions not registered');
}
}

View File

@ -1,6 +1,5 @@
// DOM Helper Functions - Shared across all pages // DOM Helper Functions - Shared across all pages
// DOM helpers
function safeGetElement(id) { function safeGetElement(id) {
const element = document.getElementById(id); const element = document.getElementById(id);
if (!element) { if (!element) {
@ -24,42 +23,3 @@ function safeQuerySelectorAll(selector) {
} }
return elements; return elements;
} }
// Navigation helpers
function setupActiveNavigation() {
const navItems = document.querySelectorAll('.nav-item');
navItems.forEach(item => {
if (item.textContent.includes('App Center')) {
item.classList.add('active');
}
});
}
// Wait for element to appear
function waitForElement(id, timeout = 5000) {
return new Promise((resolve, reject) => {
const element = document.getElementById(id);
if (element) {
resolve(element);
return;
}
const observer = new MutationObserver(() => {
const element = document.getElementById(id);
if (element) {
observer.disconnect();
resolve(element);
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
setTimeout(() => {
observer.disconnect();
reject(new Error(`Element ${id} not found within ${timeout}ms`));
}, timeout);
});
}

View File

@ -1,287 +0,0 @@
// SPA Router for LibrePortal - Handles navigation without page reloads
class Router {
constructor() {
this.routes = new Map();
this.currentRoute = null;
this.loadingBar = null;
this.contentContainer = null;
this.init();
}
init() {
// Create loading bar
this.createLoadingBar();
// Get content container
this.contentContainer = document.getElementById('main-content') || document.querySelector('.main');
//console.log('Router: Content container found:', !!this.contentContainer, this.contentContainer);
// Handle browser back/forward
window.addEventListener('popstate', (e) => {
if (e.state && e.state.route) {
this.navigate(e.state.route, false);
}
});
// Don't handle initial route here - let SPA handle it after routes are registered
//console.log('Router: Initialized, waiting for SPA to handle initial navigation');
}
createLoadingBar() {
// Create neon loading bar
this.loadingBar = document.createElement('div');
this.loadingBar.className = 'neon-loading-bar';
this.loadingBar.innerHTML = `
<div class="neon-loading-bar-inner">
<div class="neon-loading-bar-progress"></div>
<div class="neon-loading-bar-glow"></div>
</div>
`;
// Insert after header
const header = document.getElementById('topbar-container');
if (header) {
header.insertAdjacentElement('afterend', this.loadingBar);
}
// Add CSS
this.addLoadingBarStyles();
}
addLoadingBarStyles() {
const style = document.createElement('style');
style.textContent = `
.neon-loading-bar {
position: fixed;
top: 60px;
left: 0;
right: 0;
height: 3px;
background: rgba(0, 0, 0, 0.3);
z-index: 1000;
opacity: 0;
transition: opacity 0.3s ease;
}
.neon-loading-bar.active {
opacity: 1;
}
.neon-loading-bar-inner {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
.neon-loading-bar-progress {
height: 100%;
width: 0%;
background: linear-gradient(90deg, #00d4ff, #0099ff, #00d4ff);
transition: width 0.3s ease;
box-shadow: 0 0 10px #00d4ff;
animation: neonPulse 1.5s ease-in-out infinite;
}
.neon-loading-bar-glow {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 100%;
background: linear-gradient(90deg, transparent, #00d4ff, transparent);
opacity: 0.6;
animation: neonGlow 2s ease-in-out infinite;
}
@keyframes neonPulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
@keyframes neonGlow {
0%, 100% {
opacity: 0.3;
transform: scaleX(1);
}
50% {
opacity: 0.8;
transform: scaleX(1.1);
}
}
.neon-loading-bar.complete .neon-loading-bar-progress {
width: 100%;
animation: none;
}
.neon-loading-bar.complete .neon-loading-bar-glow {
animation: none;
opacity: 0;
}
`;
document.head.appendChild(style);
}
// Register a route
register(path, handler) {
this.routes.set(path, handler);
//console.log('Router: Registered route:', path);
}
// Navigate to a route
async navigate(path, addToHistory = true) {
if (this.currentRoute === path) return;
// Show loading bar
this.showLoadingBar();
// Update browser history
if (addToHistory) {
history.pushState({ route: path }, '', path);
}
this.currentRoute = path;
try {
//console.log('Router: Navigating to:', path);
//console.log('Router: Available routes:', Array.from(this.routes.keys()));
// Find matching route - simple wildcard matching
let handler = null;
if (this.routes.has(path)) {
// Exact match
handler = this.routes.get(path);
} else {
// Wildcard matching
for (const [route, routeHandler] of this.routes) {
if (route.includes('*')) {
const routePattern = route.replace('*', '');
if (path.startsWith(routePattern)) {
handler = routeHandler;
break;
}
}
}
}
if (handler) {
//console.log('Router: Found handler for:', path);
await handler();
} else {
console.warn('Router: No handler found for:', path);
console.warn('Router: Available routes were:', Array.from(this.routes.keys()));
this.hideLoadingBar();
}
} catch (error) {
console.error('Navigation error:', error);
this.hideLoadingBar();
}
}
showLoadingBar() {
if (this.loadingBar) {
this.loadingBar.classList.add('active');
// Reset progress
const progress = this.loadingBar.querySelector('.neon-loading-bar-progress');
if (progress) {
progress.style.width = '0%';
}
}
}
updateProgress(percent) {
if (this.loadingBar) {
const progress = this.loadingBar.querySelector('.neon-loading-bar-progress');
if (progress) {
progress.style.width = `${percent}%`;
}
}
}
hideLoadingBar() {
if (this.loadingBar) {
// Complete animation
this.updateProgress(100);
setTimeout(() => {
this.loadingBar.classList.add('complete');
setTimeout(() => {
this.loadingBar.classList.remove('active', 'complete');
const progress = this.loadingBar.querySelector('.neon-loading-bar-progress');
if (progress) {
progress.style.width = '0%';
}
}, 300);
}, 200);
}
}
// Load content dynamically
async loadContent(content, title = null) {
//console.log('Router: Loading content with title:', title);
if (!this.contentContainer) {
console.error('Router: No content container found');
return;
}
// Clear current content
this.contentContainer.innerHTML = '';
// Update page title
if (title) {
document.title = `${title} - LibrePortal`;
}
// Add new content
this.contentContainer.innerHTML = content;
//console.log('Router: Content loaded successfully, container innerHTML length:', this.contentContainer.innerHTML.length);
// Update active nav state
this.updateActiveNav();
}
updateActiveNav() {
// Always clear all navigation first to prevent sticky highlighting
if (typeof window.topbarNavigationHighlighting === 'function') {
window.topbarNavigationHighlighting();
return; // Exit early - let topbar handle it
}
// Fallback to original logic if helper not available
// Remove active class from all nav items
document.querySelectorAll('.nav-item').forEach(item => {
item.classList.remove('active');
});
// Use path-based detection only for consistency
const pathname = window.location.pathname;
let activeNavId;
// PRIMARY: Use path-based detection only (most reliable)
if (pathname.startsWith('/app') || pathname.startsWith('/apps')) {
activeNavId = 'nav-app-center';
} else if (pathname.startsWith('/admin') || pathname.startsWith('/config') || pathname.startsWith('/ssh')) {
activeNavId = 'nav-config';
} else if (pathname.startsWith('/tasks')) {
activeNavId = 'nav-tasks';
} else if (pathname === '/' || pathname === '/dashboard') {
activeNavId = 'nav-dashboard';
} else {
activeNavId = 'nav-dashboard'; // default
}
if (activeNavId) {
const activeNav = document.getElementById(activeNavId);
if (activeNav) {
activeNav.classList.add('nav-active');
}
}
}
}
// Global router instance
const router = new Router();

View File

@ -1,99 +1,8 @@
// UI Helper Functions - Shared across all pages // App metadata helper — resolves an app's icon path, falling back to the
// Simple functions without import/export syntax for compatibility // bundled SVG when the catalog entry doesn't carry an explicit icon URL.
// App icon and name helpers
function getAppIcon(appName, appIconUrl) { function getAppIcon(appName, appIconUrl) {
const cleanAppName = appName.replace('install_', '').replace(' ', ''); const cleanAppName = appName.replace('install_', '').replace(' ', '');
// Use icon URL from JSON if available, otherwise fall back to default // Use icon URL from JSON if available, otherwise fall back to default
return appIconUrl || `/core/icons/apps/${cleanAppName}.svg`; return appIconUrl || `/core/icons/apps/${cleanAppName}.svg`;
} }
function getAppStatus(installed) {
return installed ? 'Installed' : 'Not Installed';
}
function formatAppName(name) {
return name.split(' - ')[0].split(' -')[0].trim();
}
function getAppShortName(appData) {
return appData.name.split(' - ')[0].split(' -')[0].trim();
}
// Mobile menu helpers
function setupMobileMenu() {
const mobileMenuToggle = document.getElementById('mobile-menu-toggle');
const sidebar = document.getElementById('sidebar');
const mobileOverlay = document.getElementById('mobile-overlay');
if (!mobileMenuToggle || !sidebar || !mobileOverlay) {
//console.log('Mobile menu elements not found');
return;
}
function toggleMobileMenu() {
sidebar.classList.toggle('mobile-open');
mobileOverlay.classList.toggle('active');
if (sidebar.classList.contains('mobile-open')) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
}
function closeMobileMenu() {
sidebar.classList.remove('mobile-open');
mobileOverlay.classList.remove('active');
document.body.style.overflow = '';
}
// Mobile menu event listeners
mobileMenuToggle.addEventListener('click', toggleMobileMenu);
mobileOverlay.addEventListener('click', closeMobileMenu);
// Close mobile menu when window is resized to desktop size
window.addEventListener('resize', () => {
if (window.innerWidth > 768) {
closeMobileMenu();
}
});
// Expose closeMobileMenu globally for other scripts
window.closeMobileMenu = closeMobileMenu;
}
// Navigation helpers
function setupActiveNavigation() {
const navItems = document.querySelectorAll('.nav-item');
navItems.forEach(item => {
if (item.textContent.includes('App Center')) {
item.classList.add('active');
}
});
}
// DOM helpers
function safeGetElement(id) {
const element = document.getElementById(id);
if (!element) {
//console.log(`${id} element not found`);
}
return element;
}
function safeQuerySelector(selector) {
const element = document.querySelector(selector);
if (!element) {
//console.log(`${selector} element not found`);
}
return element;
}
function safeQuerySelectorAll(selector) {
const elements = document.querySelectorAll(selector);
if (!elements || elements.length === 0) {
//console.log(`${selector} elements not found`);
}
return elements;
}

View File

@ -481,97 +481,4 @@ class TopbarComponent {
document.body.setAttribute('data-theme', theme); document.body.setAttribute('data-theme', theme);
} }
static clearAllNavigationHighlighting() {
//// // console.log('🧹 Aggressively clearing all navigation highlighting');
// Remove ALL active classes from ALL navigation items
document.querySelectorAll('.nav-item').forEach(item => {
//// // console.log('🧹 Clearing nav item:', item.id, item.textContent.trim());
item.classList.remove('active');
item.classList.remove('nav-active');
// Also remove any other possible active states
item.removeAttribute('aria-current');
item.blur(); // Remove focus
});
// Also clear other active classes that might interfere
document.querySelectorAll('.category.active').forEach(item => {
item.classList.remove('active');
});
document.querySelectorAll('.tab-button.active').forEach(item => {
item.classList.remove('active');
});
//// // console.log('🧹 Cleared all navigation highlighting');
}
static createNavigationHighlighting() {
// Define the single function that handles navigation highlighting
window.topbarNavigationHighlighting = function() {
// Always clear all existing navigation first to prevent sticky highlighting
TopbarComponent.clearAllNavigationHighlighting();
// PRIMARY: Use path-based detection only (most reliable)
// This avoids confusion from query parameters like tab=, app=, etc.
const path = window.location.pathname;
let activeNavId;
if (path.startsWith('/app') || path.startsWith('/apps')) {
activeNavId = 'nav-app-center';
} else if (path.startsWith('/admin') || path.startsWith('/config') || path.startsWith('/ssh')) {
activeNavId = 'nav-config';
} else if (path.startsWith('/tasks')) {
activeNavId = 'nav-tasks';
} else if (path.startsWith('/backup')) {
activeNavId = 'nav-backup';
} else if (path.startsWith('/updater')) {
activeNavId = 'nav-updater';
} else if (path === '/' || path === '/dashboard') {
activeNavId = 'nav-dashboard';
} else {
// Fallback: use currentPage detection
const currentPage = new TopbarComponent().getCurrentPage();
switch (currentPage) {
case 'index.html':
case '':
case 'index':
case 'app':
case 'apps':
activeNavId = 'nav-app-center';
break;
case 'config':
activeNavId = 'nav-config';
break;
case 'tasks':
activeNavId = 'nav-tasks';
break;
case 'backup':
activeNavId = 'nav-backup';
break;
case 'updater':
activeNavId = 'nav-updater';
break;
case 'dashboard':
activeNavId = 'nav-dashboard';
break;
default:
activeNavId = 'nav-dashboard';
}
}
// Add active class to current page nav
if (activeNavId) {
const activeNav = document.getElementById(activeNavId);
if (activeNav) {
activeNav.classList.add('nav-active');
} else {
console.warn(`❌ Topbar: Nav element not found: ${activeNavId}`);
}
} else {
console.warn(`❌ Topbar: Could not determine active nav for path: ${path}`);
}
};
//// // console.log('🌐 Topbar navigation highlighting function created');
}
} }

View File

@ -92,7 +92,6 @@
<script src="/core/lib/util/lp-ui.js"></script> <script src="/core/lib/util/lp-ui.js"></script>
<script src="/core/lib/util/dom-helpers.js"></script> <script src="/core/lib/util/dom-helpers.js"></script>
<script src="/core/lib/util/ui-helpers.js"></script> <script src="/core/lib/util/ui-helpers.js"></script>
<script src="/core/lib/util/router.js"></script>
<script src="/core/lib/util/data-loader.js"></script> <script src="/core/lib/util/data-loader.js"></script>
<script src="/core/lib/util/system-live.js"></script> <script src="/core/lib/util/system-live.js"></script>
<script src="/core/lib/util/dismissible.js"></script> <script src="/core/lib/util/dismissible.js"></script>