A free, open, self-hosted app platform (GNU AGPLv3): one-click app deploys, Traefik reverse proxy with automatic SSL, rootless Docker support, gluetun VPN routing, and a web dashboard to manage it all. Free & open forever to self-host; optional paid hosted services fund it. See PROMISE.md. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: librelad <librelad@digitalangels.vip>
292 lines
10 KiB
JavaScript
Executable File
292 lines
10 KiB
JavaScript
Executable File
// Configuration Router - Loads appropriate config component based on URL
|
|
class ConfigRouter {
|
|
constructor() {
|
|
this.currentComponent = null;
|
|
}
|
|
|
|
async init() {
|
|
//console.log('ConfigRouter: Initializing...');
|
|
// Get current category from query parameter or global variable
|
|
const searchParams = new URLSearchParams(window.location.search);
|
|
let currentCategory = searchParams.get('config') || window.configCategory || 'general';
|
|
|
|
//console.log(`Initial parsing - searchParams.get('config'): ${searchParams.get('config')}`);
|
|
//console.log(`Window location search: ${window.location.search}`);
|
|
//console.log(`Initial currentCategory: ${currentCategory}`);
|
|
|
|
// Handle the case where URL is config?=backup (malformed but common)
|
|
// Check if we got the category from URL params, not from fallback
|
|
const gotFromUrlParams = searchParams.get('config') !== null;
|
|
|
|
if (!gotFromUrlParams && window.location.search.includes('?=')) {
|
|
//console.log(`URL contains ?= and no valid config param, attempting regex match...`);
|
|
const pathMatch = window.location.search.match(/\?=([^&]+)/);
|
|
//console.log(`Regex match result:`, pathMatch);
|
|
//console.log(`Regex test result:`, /\?=([^&]+)/.test(window.location.search));
|
|
if (pathMatch && pathMatch[1]) {
|
|
currentCategory = pathMatch[1];
|
|
//console.log(`Updated currentCategory from regex: ${currentCategory}`);
|
|
} else {
|
|
//console.log(`Regex failed to match, currentCategory remains: ${currentCategory}`);
|
|
}
|
|
} else {
|
|
//console.log(`Got category from URL params (${gotFromUrlParams}) or URL doesn't contain ?=, keeping: ${currentCategory}`);
|
|
}
|
|
|
|
//console.log(`Config router init: final category=${currentCategory}`);
|
|
|
|
// Backup config moved to /backup — redirect old URL/bookmarks.
|
|
if (currentCategory === 'backup') {
|
|
if (window.librePortalSPA && typeof window.librePortalSPA.navigate === 'function') {
|
|
window.librePortalSPA.navigate('/backup', true);
|
|
} else {
|
|
window.location.href = '/backup';
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Load categories for sidebar
|
|
await this.loadConfigCategories();
|
|
|
|
// Set active category in sidebar
|
|
this.setActiveCategory(currentCategory);
|
|
|
|
// Load appropriate config component
|
|
await this.loadConfigComponent(currentCategory);
|
|
}
|
|
|
|
async loadConfigCategories() {
|
|
try {
|
|
//console.log('Loading config categories from: data/config/generated/configs.json');
|
|
|
|
// Start loading bar
|
|
if (typeof router !== 'undefined' && router.updateProgress) {
|
|
router.updateProgress(20);
|
|
}
|
|
|
|
// Load unified config file
|
|
const response = await fetch('/data/config/generated/configs.json');
|
|
//console.log('Response status:', response.status, response.statusText);
|
|
//console.log('Response ok:', response.ok);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
const text = await response.text();
|
|
//console.log('Raw response text:', text);
|
|
//console.log('Text length:', text.length);
|
|
//console.log('First 100 chars:', text.substring(0, 100));
|
|
|
|
if (!text || text.trim() === '') {
|
|
throw new Error('Empty response from configs.json');
|
|
}
|
|
|
|
const data = JSON.parse(text);
|
|
const categories = data.categories;
|
|
|
|
//console.log('Parsed categories:', categories);
|
|
|
|
if (typeof router !== 'undefined' && router.updateProgress) {
|
|
router.updateProgress(60);
|
|
}
|
|
|
|
const categoriesList = document.getElementById('config-categories-list');
|
|
if (!categoriesList) {
|
|
console.error('config-categories-list element not found');
|
|
return;
|
|
}
|
|
|
|
categoriesList.innerHTML = '';
|
|
|
|
// Convert categories object to array and sort by ORDER
|
|
const categoriesArray = Object.entries(categories).map(([key, value]) => ({
|
|
id: key,
|
|
...value
|
|
}));
|
|
|
|
// Sort by ORDER if available, otherwise by title
|
|
categoriesArray.sort((a, b) => {
|
|
const orderA = parseInt(a.order) || 999;
|
|
const orderB = parseInt(b.order) || 999;
|
|
return orderA - orderB;
|
|
});
|
|
|
|
categoriesArray.forEach(category => {
|
|
const categoryItem = document.createElement('div');
|
|
categoryItem.className = 'category';
|
|
categoryItem.setAttribute('data-category', category.id);
|
|
|
|
// Use correct icon from our new structure
|
|
const iconName = category.icon || category.id;
|
|
const iconPath = `/icons/config/${iconName}.svg`;
|
|
//console.log(`Category: ${category.id}, Icon path: ${iconPath}`);
|
|
categoryItem.innerHTML = `
|
|
<img src="${iconPath}" alt="${category.title}" style="width: 20px; height: 20px; margin-right: 8px;"/>
|
|
${category.title}
|
|
`;
|
|
|
|
categoryItem.addEventListener('click', () => {
|
|
//console.log(`Category clicked: ${category.id}`);
|
|
this.navigateToCategory(category.id);
|
|
});
|
|
|
|
categoriesList.appendChild(categoryItem);
|
|
});
|
|
|
|
if (typeof router !== 'undefined' && router.updateProgress) {
|
|
router.updateProgress(80);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error loading config categories:', error);
|
|
if (typeof router !== 'undefined' && router.hideLoadingBar) {
|
|
router.hideLoadingBar();
|
|
}
|
|
}
|
|
}
|
|
|
|
setActiveCategory(categoryId) {
|
|
// Update active state
|
|
document.querySelectorAll('.category').forEach(item => {
|
|
item.classList.remove('active');
|
|
});
|
|
document.querySelector(`[data-category="${categoryId}"]`)?.classList.add('active');
|
|
}
|
|
|
|
navigateToCategory(categoryId) {
|
|
//console.log(`Config router: navigating to ${categoryId} (SPA mode)`);
|
|
|
|
// Update URL without full page reload using query parameter
|
|
const url = `/config?=${categoryId}`;
|
|
//console.log(`Updating URL to: ${url}`);
|
|
window.history.pushState({}, '', url);
|
|
|
|
// Set active category
|
|
this.setActiveCategory(categoryId);
|
|
|
|
// Load config content dynamically
|
|
this.loadConfigComponent(categoryId);
|
|
}
|
|
|
|
async loadConfigComponent(categoryId) {
|
|
try {
|
|
//console.log(`Config router: Loading component for ${categoryId}`);
|
|
|
|
// Start loading bar
|
|
if (typeof router !== 'undefined' && router.updateProgress) {
|
|
router.updateProgress(10);
|
|
router.showLoadingBar();
|
|
}
|
|
|
|
// Clear current content
|
|
const configSection = document.getElementById('config-section');
|
|
if (configSection) {
|
|
configSection.innerHTML = '<div class="loading">Loading configuration...</div>';
|
|
}
|
|
|
|
// Update progress
|
|
if (typeof router !== 'undefined' && router.updateProgress) {
|
|
router.updateProgress(30);
|
|
}
|
|
|
|
// Use the simple config manager
|
|
if (window.configManager) {
|
|
//console.log(`Using ConfigManager for ${categoryId}`);
|
|
if (typeof router !== 'undefined' && router.updateProgress) {
|
|
router.updateProgress(50);
|
|
}
|
|
|
|
await window.configManager.renderConfig(categoryId);
|
|
|
|
if (typeof router !== 'undefined' && router.updateProgress) {
|
|
router.updateProgress(90);
|
|
}
|
|
} else {
|
|
// Fallback - try to load config manager
|
|
//console.log('ConfigManager not available, loading it...');
|
|
if (typeof router !== 'undefined' && router.updateProgress) {
|
|
router.updateProgress(40);
|
|
}
|
|
|
|
await new Promise((resolve) => {
|
|
const script = document.createElement('script');
|
|
script.src = '/js/components/config/config-manager.js';
|
|
script.onload = async () => {
|
|
if (window.configManager) {
|
|
if (typeof router !== 'undefined' && router.updateProgress) {
|
|
router.updateProgress(60);
|
|
}
|
|
await window.configManager.renderConfig(categoryId);
|
|
if (typeof router !== 'undefined' && router.updateProgress) {
|
|
router.updateProgress(90);
|
|
}
|
|
}
|
|
resolve();
|
|
};
|
|
document.head.appendChild(script);
|
|
});
|
|
}
|
|
|
|
// Complete loading
|
|
if (typeof router !== 'undefined' && router.updateProgress) {
|
|
router.updateProgress(100);
|
|
setTimeout(() => {
|
|
router.hideLoadingBar();
|
|
}, 500); // Small delay to show completion
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error(`Error loading config component for ${categoryId}:`, error);
|
|
const configSection = document.getElementById('config-section');
|
|
if (configSection) {
|
|
configSection.innerHTML = `<div class="error">Failed to load ${categoryId} configuration: ${error.message}</div>`;
|
|
}
|
|
if (typeof router !== 'undefined' && router.hideLoadingBar) {
|
|
router.hideLoadingBar();
|
|
}
|
|
}
|
|
}
|
|
|
|
async loadConfigComponentManual(categoryId) {
|
|
const configSection = document.getElementById('config-section');
|
|
|
|
// This method is no longer needed since we use ConfigManager for all categories
|
|
// The individual config classes have been removed
|
|
//console.log(`ConfigRouter: loadConfigComponentManual called for ${categoryId} - delegating to ConfigManager`);
|
|
|
|
// Use ConfigManager for all categories now
|
|
if (window.configManager) {
|
|
await window.configManager.renderConfig(categoryId);
|
|
} else {
|
|
configSection.innerHTML = '<div class="error">ConfigManager not available</div>';
|
|
}
|
|
|
|
// Hide loading bar
|
|
if (typeof router !== 'undefined' && router.hideLoadingBar) {
|
|
router.hideLoadingBar();
|
|
}
|
|
}
|
|
|
|
async loadScript(src) {
|
|
// Check if script is already loaded
|
|
const scriptId = src.replace(/[^a-zA-Z0-9]/g, '_');
|
|
if (document.getElementById(scriptId)) {
|
|
//console.log(`Script ${src} already loaded, skipping`);
|
|
return;
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const script = document.createElement('script');
|
|
script.src = src;
|
|
script.id = scriptId;
|
|
script.onload = resolve;
|
|
script.onerror = reject;
|
|
document.head.appendChild(script);
|
|
});
|
|
}
|
|
}
|
|
|
|
// Export for global access
|
|
window.ConfigRouter = ConfigRouter;
|