chore(webui): remove dead controllers (app-manager.js, config-router.js)

Both are orphans with zero inbound references (verified by grep across the
whole frontend): app-manager.js only self-assigns window.appManager and is
in no loader/scripts array; config-router.js only self-defines ConfigRouter
and is referenced nowhere (config-manager owns rendering directly). First,
safest step of the feature reorganization.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
librelad 2026-05-30 01:52:43 +01:00
parent 66a48ea8b8
commit a98a241d5e
2 changed files with 0 additions and 654 deletions

View File

@ -1,363 +0,0 @@
// App Manager - Dynamic app loading with beautiful styling
class AppManager {
constructor() {
this.cache = new Map();
}
getRandomLoadingMessage() {
const messages = [
"Preparing your application settings...",
"Gathering application information...",
"Loading application configuration...",
"Setting up your app management panel...",
"Loading the perfect app settings...",
"Crafting your application experience...",
"Preparing your app control panel...",
"Loading application details...",
"Setting up your app workspace...",
"Configuring your application environment..."
];
return messages[Math.floor(Math.random() * messages.length)];
}
async loadApp(appName) {
//console.log(`AppManager: Loading ${appName} app...`);
// Check cache first
if (this.cache.has(appName)) {
//console.log(`AppManager: Using cached ${appName} app`);
return this.cache.get(appName);
}
try {
// Load app data from apps.json
const response = await fetch('/data/apps/generated/apps.json', { cache: 'no-store' });
if (!response.ok) {
throw new Error(`Failed to load apps.json: ${response.status}`);
}
const appsData = await response.json();
// Try multiple ways to find the app
let app = appsData.apps.find(app =>
app.name.toLowerCase().includes(appName.toLowerCase()) ||
app.command.toLowerCase().includes(appName.toLowerCase()) ||
app.name === appName ||
app.name.toLowerCase() === appName.toLowerCase()
);
if (!app) {
// Try case-insensitive exact match
app = appsData.apps.find(app =>
app.name.toLowerCase() === appName.toLowerCase() ||
app.command.toLowerCase().includes(appName.toLowerCase())
);
}
if (!app) {
//console.log(`Available apps:`, appsData.apps.map(a => ({ name: a.name, command: a.command })));
throw new Error(`App ${appName} not found`);
}
//console.log(`AppManager: Loaded ${appName} app:`, app);
// Cache the result
this.cache.set(appName, app);
return app;
} catch (error) {
console.error(`AppManager: Error loading ${appName} app:`, error);
return null;
}
}
async renderApp(appName) {
//console.log(`AppManager: Rendering ${appName} app...`);
const configSection = document.getElementById('config-section');
if (!configSection) {
console.error('AppManager: config-section element not found');
return;
}
// Show loading with enhanced visual
configSection.innerHTML = `
<div class="loading-enhanced" style="padding: 22px;">
<div class="loading-content" style="
text-align: center;
padding: 22px;
background: var(--input-bg);
border: 1px solid var(--border-color);
border-radius: 6px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
">
<div class="loading-spinner" style="
width: 24px;
height: 24px;
border: 3px solid rgba(52, 152, 219, 0.3);
border-top: 3px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 16px auto;
display: inline-block;
"></div>
<div class="loading-message" style="
font-size: 16px;
color: var(--text-primary, #fff);
font-weight: 500;
margin-bottom: 8px;
">
Loading application...
</div>
<div class="loading-subtitle" style="
font-size: 14px;
color: var(--text-color, #fff);
font-style: italic;
">
${this.getRandomLoadingMessage()}
</div>
</div>
<style>
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</div>
`;
// Update loading bar if available
if (typeof router !== 'undefined' && router.updateProgress) {
router.updateProgress(60);
}
try {
// Load app data
const app = await this.loadApp(appName);
// Update loading bar
if (typeof router !== 'undefined' && router.updateProgress) {
router.updateProgress(70);
}
if (!app) {
configSection.innerHTML = '<div class="error">Application not found</div>';
return;
}
// App config comes from apps.json (window.apps), not a separate
// per-app JSON. Pass null — the renderer's config section is gated
// on appConfig?.config keys so it just skips that section.
if (typeof router !== 'undefined' && router.updateProgress) {
router.updateProgress(80);
}
await this.renderWithOriginalStyling(appName, app, null);
// Final progress update
if (typeof router !== 'undefined' && router.updateProgress) {
router.updateProgress(80);
}
//console.log(`AppManager: Successfully rendered ${appName} app`);
} catch (error) {
console.error(`AppManager: Error rendering ${appName} app:`, error);
configSection.innerHTML = `<div class="error">Failed to load ${appName} application: ${error.message}</div>`;
}
}
async renderWithOriginalStyling(appName, app, appConfig) {
const configSection = document.getElementById('config-section');
// Render using the original app-config system
let formHTML = `
<div class="app-title">
<h3>${app.displayName || app.name} Application</h3>
<p>${app.description || 'Manage settings and configuration for ' + (app.displayName || app.name)}</p>
</div>
<div class="app-container">
<form id="app-form-${appName}" class="app-form">
`;
// App information section
formHTML += `
<div class="app-category">
<h3>Application Information</h3>
<p class="category-description">Basic information about this application</p>
<div class="config-group">
<div class="config-field">
<label class="config-label">Application Name</label>
<div class="config-input-wrapper">
<input type="text" value="${app.displayName || app.name}" readonly class="config-input">
</div>
</div>
<div class="config-field">
<label class="config-label">Status</label>
<div class="config-input-wrapper">
<input type="text" value="${app.installed ? 'Installed' : 'Not Installed'}" readonly class="config-input">
</div>
</div>
<div class="config-field">
<label class="config-label">Version</label>
<div class="config-input-wrapper">
<input type="text" value="${app.version || 'N/A'}" readonly class="config-input">
</div>
</div>
</div>
</div>
`;
// Configuration section if available
if (appConfig && appConfig.config && Object.keys(appConfig.config).length > 0) {
// Use ConfigShared if available for beautiful rendering
if (typeof ConfigShared !== 'undefined') {
const groupedConfigs = ConfigShared.groupConfigKeys(appConfig.config);
const categoryOrder = ConfigShared.extractCategoryOrder(appConfig.config);
for (const category of categoryOrder) {
const keys = groupedConfigs[category];
if (keys && keys.length > 0 && category !== 'Hidden/Unused Options') {
const displayCategory = ConfigShared.formatCategoryName(category);
const categoryDescription = await ConfigShared.getCategoryDescription(category);
formHTML += `
<div class="config-category">
<h3>${displayCategory}</h3>
<p class="category-description">${categoryDescription}</p>
<div class="config-group">
${ConfigShared.generateFieldsForCategory(keys, category, appConfig.config, (fieldId, key, value, title, description, options, config) => ConfigShared.generateField(fieldId, key, value, title, description, options, config))}
</div>
</div>
`;
}
}
} else {
// Fallback simple rendering
formHTML += `
<div class="config-category">
<h3>Configuration</h3>
<p class="category-description">Application-specific settings</p>
<div class="config-group">
<div class="config-field">
<label class="config-label">Configuration Available</label>
<div class="config-input-wrapper">
<input type="text" value="Configuration loaded successfully" readonly class="config-input">
</div>
</div>
</div>
</div>
`;
}
} else {
// No configuration available
formHTML += `
<div class="config-category">
<h3>Configuration</h3>
<p class="category-description">No specific configuration available for this application</p>
<div class="config-group">
<div class="config-field">
<label class="config-label">Status</label>
<div class="config-input-wrapper">
<input type="text" value="No custom configuration needed" readonly class="config-input">
</div>
</div>
</div>
</div>
`;
}
formHTML += `
</form>
<div class="config-actions">
<button type="button" class="btn btn-primary" onclick="AppManager.saveAppConfig('${appName}')">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path>
<polyline points="17 21 17 13 7 13 7 21"></polyline>
<polyline points="7 3 7 8 15 8"></polyline>
</svg>
Save Configuration
</button>
<button type="button" class="btn btn-secondary" onclick="AppManager.resetAppConfig('${appName}')">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="1 4 1 10 7 10"></polyline>
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path>
</svg>
Reset to Defaults
</button>
</div>
</div>
`;
configSection.innerHTML = formHTML;
}
static async saveAppConfig(appName) {
//console.log(`AppManager: Saving ${appName} config...`);
const form = document.getElementById(`app-form-${appName}`);
if (!form) {
console.error('AppManager: Form not found');
return;
}
// Show success message
if (typeof ConfigShared !== 'undefined' && ConfigShared.showNotification) {
ConfigShared.showNotification('Application configuration saved successfully!', 'success');
} else {
// Fallback message
const message = document.createElement('div');
message.className = 'config-message success';
message.textContent = 'Application configuration saved successfully!';
const actionsDiv = form.parentElement.querySelector('.config-actions');
actionsDiv.insertBefore(message, actionsDiv.firstChild);
// Remove message after 3 seconds
setTimeout(() => {
if (message.parentNode) {
message.parentNode.removeChild(message);
}
}, 3000);
}
}
static async resetAppConfig(appName) {
//console.log(`AppManager: Resetting ${appName} config...`);
if (confirm('Are you sure you want to reset all settings to their default values?')) {
// Reload the page to reset
window.location.reload();
}
}
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;
}
//console.log(`Loading script: ${src}`);
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.id = scriptId;
script.onload = () => {
//console.log(`Script loaded successfully: ${src}`);
resolve();
};
script.onerror = (error) => {
console.error(`Script failed to load: ${src}`, error);
reject(new Error(`Failed to load script: ${src}`));
};
document.head.appendChild(script);
});
}
}
// Global instance
window.appManager = new AppManager();

View File

@ -1,291 +0,0 @@
// 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;