From d18222dec8b3f5c4b7708a197bb82cc65cb8eb26 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Sun, 4 Jan 2026 17:18:07 +0100 Subject: [PATCH] feat: add cyberpunk theme to Catalog Sources with debug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LuCI Admin (v1.0.0-12): - Complete cyberpunk transformation of Catalog Sources view - Debug console.log for all operations - Stats panel with 4 metrics (Total, Enabled, Updates, Active) - Auto-refresh every 30s with polling - Quick actions panel (Sync All, Refresh) - Cyber-list layout with priority sorting - Status indicators with animated dots - Actions: SYNC, TEST, SET ACTIVE, ENABLE/DISABLE ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .../secubox/luci-app-secubox-admin/Makefile | 2 +- .../view/secubox-admin/catalog-sources.js | 336 +++++++++++------- 2 files changed, 208 insertions(+), 130 deletions(-) diff --git a/package/secubox/luci-app-secubox-admin/Makefile b/package/secubox/luci-app-secubox-admin/Makefile index e6215aa3..cae0aac9 100644 --- a/package/secubox/luci-app-secubox-admin/Makefile +++ b/package/secubox/luci-app-secubox-admin/Makefile @@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-secubox-admin PKG_VERSION:=1.0.0 -PKG_RELEASE:=11 +PKG_RELEASE:=12 PKG_LICENSE:=MIT PKG_MAINTAINER:=CyberMind diff --git a/package/secubox/luci-app-secubox-admin/htdocs/luci-static/resources/view/secubox-admin/catalog-sources.js b/package/secubox/luci-app-secubox-admin/htdocs/luci-static/resources/view/secubox-admin/catalog-sources.js index 8f15f4d2..aa42ae4d 100644 --- a/package/secubox/luci-app-secubox-admin/htdocs/luci-static/resources/view/secubox-admin/catalog-sources.js +++ b/package/secubox/luci-app-secubox-admin/htdocs/luci-static/resources/view/secubox-admin/catalog-sources.js @@ -7,83 +7,150 @@ return view.extend({ load: function() { + console.log('[CATALOG-SOURCES] Loading data...'); return Promise.all([ L.resolveDefault(API.getCatalogSources(), { sources: [] }), - L.resolveDefault(API.checkUpdates(), { updates: [] }) - ]); + L.resolveDefault(API.checkUpdates(), { updates: [], total_updates_available: 0 }) + ]).then(function(results) { + console.log('[CATALOG-SOURCES] Data loaded:', { + sources: results[0], + updates: results[1] + }); + return results; + }).catch(function(err) { + console.error('[CATALOG-SOURCES] Load error:', err); + return [{ sources: [] }, { updates: [], total_updates_available: 0 }]; + }); }, render: function(data) { + console.log('[CATALOG-SOURCES] Rendering with data:', data); var sources = data[0].sources || []; var updateInfo = data[1]; var self = this; - var container = E('div', { 'class': 'secubox-catalog-sources' }, [ + console.log('[CATALOG-SOURCES] Sources count:', sources.length); + + var activeSource = sources.filter(function(s) { return s.active; })[0]; + var enabledCount = sources.filter(function(s) { return s.enabled; }).length; + + var container = E('div', { 'class': 'cyberpunk-mode secubox-catalog-sources' }, [ E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-admin/common.css') }), E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-admin/admin.css') }), + E('link', { 'rel': 'stylesheet', + 'href': L.resource('secubox-admin/cyberpunk.css') }), - E('h2', {}, 'Catalog Sources'), - E('p', {}, 'Manage catalog sources with automatic fallback'), + // Cyberpunk header + E('div', { 'class': 'cyber-header' }, [ + E('div', { 'class': 'cyber-header-title' }, '๐Ÿ“ก CATALOG SOURCES'), + E('div', { 'class': 'cyber-header-subtitle' }, + 'Multi-source catalog system ยท ' + sources.length + ' sources configured') + ]), - // Summary stats - E('div', { 'class': 'source-summary' }, [ - E('div', { 'class': 'stat-card' }, [ - E('div', { 'class': 'stat-label' }, 'Total Sources'), - E('div', { 'class': 'stat-value' }, sources.length.toString()) + // Stats panel + E('div', { 'class': 'cyber-panel' }, [ + E('div', { 'class': 'cyber-panel-header' }, [ + E('span', { 'class': 'cyber-panel-title' }, 'SYSTEM STATUS'), + E('span', { 'class': 'cyber-panel-badge' }, enabledCount + '/' + sources.length) ]), - E('div', { 'class': 'stat-card' }, [ - E('div', { 'class': 'stat-label' }, 'Active Source'), - E('div', { 'class': 'stat-value' }, - sources.filter(function(s) { return s.active; })[0]?.name || 'None') - ]), - E('div', { 'class': 'stat-card' }, [ - E('div', { 'class': 'stat-label' }, 'Updates Available'), - E('div', { 'class': 'stat-value' }, - (updateInfo.total_updates_available || 0).toString()) + E('div', { 'class': 'cyber-panel-body' }, [ + E('div', { 'class': 'cyber-stats-grid' }, [ + E('div', { 'class': 'cyber-stat-card' }, [ + E('div', { 'class': 'cyber-stat-icon' }, '๐Ÿ“ก'), + E('div', { 'class': 'cyber-stat-value' }, sources.length), + E('div', { 'class': 'cyber-stat-label' }, 'Total Sources') + ]), + E('div', { 'class': 'cyber-stat-card accent' }, [ + E('div', { 'class': 'cyber-stat-icon' }, 'โœ“'), + E('div', { 'class': 'cyber-stat-value' }, enabledCount), + E('div', { 'class': 'cyber-stat-label' }, 'Enabled') + ]), + E('div', { 'class': 'cyber-stat-card warning' }, [ + E('div', { 'class': 'cyber-stat-icon' }, 'โšก'), + E('div', { 'class': 'cyber-stat-value' }, updateInfo.total_updates_available || 0), + E('div', { 'class': 'cyber-stat-label' }, 'Updates') + ]), + E('div', { 'class': 'cyber-stat-card' + (activeSource ? '' : ' danger') }, [ + E('div', { 'class': 'cyber-stat-icon' }, 'โ–ธ'), + E('div', { 'class': 'cyber-stat-value', 'style': 'font-size: 14px;' }, + activeSource ? activeSource.name.toUpperCase() : 'NONE'), + E('div', { 'class': 'cyber-stat-label' }, 'Active Source') + ]) + ]) ]) ]), - // Sync controls - E('div', { 'class': 'sync-controls' }, [ - E('button', { - 'class': 'btn btn-primary', - 'click': function() { - self.syncAllSources(); - } - }, 'Sync All Sources'), - E('button', { - 'class': 'btn btn-secondary', - 'click': function() { - self.refreshPage(); - } - }, 'Refresh Status') + // Quick actions panel + E('div', { 'class': 'cyber-panel' }, [ + E('div', { 'class': 'cyber-panel-header' }, [ + E('span', { 'class': 'cyber-panel-title' }, 'QUICK ACTIONS') + ]), + E('div', { 'class': 'cyber-panel-body' }, [ + E('div', { 'class': 'cyber-quick-actions' }, [ + E('button', { + 'class': 'cyber-action-btn', + 'click': function() { + console.log('[CATALOG-SOURCES] Sync all sources'); + self.syncAllSources(); + } + }, [ + E('span', { 'class': 'cyber-action-icon' }, '๐Ÿ”„'), + E('span', { 'class': 'cyber-action-label' }, 'SYNC ALL SOURCES'), + E('span', { 'class': 'cyber-action-arrow' }, 'โ†’') + ]), + E('button', { + 'class': 'cyber-action-btn', + 'click': function() { + console.log('[CATALOG-SOURCES] Refresh status'); + self.refreshPage(); + } + }, [ + E('span', { 'class': 'cyber-action-icon' }, 'โ†ป'), + E('span', { 'class': 'cyber-action-label' }, 'REFRESH STATUS'), + E('span', { 'class': 'cyber-action-arrow' }, 'โ†’') + ]) + ]) + ]) ]), // Sources list - E('div', { 'class': 'sources-container', 'id': 'sources-container' }, - sources - .sort(function(a, b) { return a.priority - b.priority; }) - .map(function(source) { - return self.renderSourceCard(source); - }) + E('div', { 'class': 'cyber-list', 'id': 'sources-container' }, + sources.length > 0 ? + sources + .sort(function(a, b) { return a.priority - b.priority; }) + .map(function(source) { + console.log('[CATALOG-SOURCES] Rendering source:', source.name); + return self.renderSourceCard(source); + }) : + [E('div', { 'class': 'cyber-panel' }, [ + E('div', { 'class': 'cyber-panel-body', 'style': 'text-align: center; padding: 40px;' }, [ + E('div', { 'style': 'font-size: 48px; margin-bottom: 20px;' }, '๐Ÿ“ก'), + E('div', { 'style': 'color: var(--cyber-text-dim);' }, 'NO SOURCES CONFIGURED'), + E('div', { 'style': 'color: var(--cyber-text-dim); font-size: 12px; margin-top: 10px;' }, + 'Configure sources in /etc/config/secubox-appstore') + ]) + ])] ) ]); // Auto-refresh every 30 seconds poll.add(function() { + console.log('[CATALOG-SOURCES] Polling for updates...'); return API.getCatalogSources().then(function(result) { var sourcesContainer = document.getElementById('sources-container'); - if (sourcesContainer) { - var sources = result.sources || []; + if (sourcesContainer && result.sources) { + console.log('[CATALOG-SOURCES] Poll update:', result.sources.length, 'sources'); sourcesContainer.innerHTML = ''; - sources + result.sources .sort(function(a, b) { return a.priority - b.priority; }) .forEach(function(source) { sourcesContainer.appendChild(self.renderSourceCard(source)); }); } + }).catch(function(err) { + console.error('[CATALOG-SOURCES] Poll error:', err); }); }, 30); @@ -93,78 +160,93 @@ return view.extend({ renderSourceCard: function(source) { var self = this; var statusClass = this.getStatusClass(source.status); - var statusIcon = this.getStatusIcon(source.status); + var statusDot = source.status === 'online' || source.status === 'available' ? 'online' : 'offline'; + + var itemClass = 'cyber-list-item'; + if (source.active) itemClass += ' active'; + if (!source.enabled) itemClass += ' offline'; return E('div', { - 'class': 'source-card' + (source.active ? ' active-source' : ''), + 'class': itemClass, 'data-source': source.name }, [ - // Source header - E('div', { 'class': 'source-header' }, [ - E('div', { 'class': 'source-title' }, [ - E('h3', {}, source.name), - source.active ? E('span', { 'class': 'badge badge-success' }, 'ACTIVE') : null - ]), - E('div', { 'class': 'source-priority' }, - E('span', { 'class': 'priority-badge' }, 'Priority: ' + source.priority) - ) - ]), + // Icon + E('div', { 'class': 'cyber-list-icon' }, + source.type === 'remote' ? '๐ŸŒ' : + source.type === 'local' ? '๐Ÿ’พ' : + source.type === 'embedded' ? '๐Ÿ“ฆ' : 'โ“' + ), - // Source info - E('div', { 'class': 'source-info' }, [ - E('div', { 'class': 'info-row' }, [ - E('span', { 'class': 'label' }, 'Type:'), - E('span', { 'class': 'value' }, source.type) - ]), - source.url ? E('div', { 'class': 'info-row' }, [ - E('span', { 'class': 'label' }, 'URL:'), - E('span', { 'class': 'value url-text' }, source.url) - ]) : null, - source.path ? E('div', { 'class': 'info-row' }, [ - E('span', { 'class': 'label' }, 'Path:'), - E('span', { 'class': 'value' }, source.path) - ]) : null, - E('div', { 'class': 'info-row' }, [ - E('span', { 'class': 'label' }, 'Status:'), - E('span', { 'class': 'value' }, [ - E('span', { 'class': 'status-indicator ' + statusClass }, statusIcon), - E('span', {}, source.status || 'unknown') + // Content + E('div', { 'class': 'cyber-list-content' }, [ + E('div', { 'class': 'cyber-list-title' }, [ + source.name.toUpperCase(), + source.active ? E('span', { 'class': 'cyber-badge success' }, [ + E('span', { 'class': 'cyber-status-dot online' }), + ' ACTIVE' + ]) : null, + !source.enabled ? E('span', { 'class': 'cyber-badge' }, [ + 'DISABLED' + ]) : E('span', { 'class': 'cyber-badge info' }, [ + E('span', { 'class': 'cyber-status-dot ' + statusDot }), + ' ' + (source.status || 'UNKNOWN').toUpperCase() ]) ]), - source.last_success ? E('div', { 'class': 'info-row' }, [ - E('span', { 'class': 'label' }, 'Last Success:'), - E('span', { 'class': 'value' }, this.formatTimestamp(source.last_success)) - ]) : null + E('div', { 'class': 'cyber-list-meta' }, [ + E('span', { 'class': 'cyber-list-meta-item' }, [ + E('span', {}, '๐Ÿ”ข '), + 'Priority: ' + source.priority + ]), + E('span', { 'class': 'cyber-list-meta-item' }, [ + E('span', {}, '๐Ÿ“‹ '), + source.type + ]), + source.url ? E('span', { 'class': 'cyber-list-meta-item' }, [ + E('span', {}, '๐Ÿ”— '), + E('span', { 'style': 'max-width: 300px; overflow: hidden; text-overflow: ellipsis;' }, + source.url) + ]) : null, + source.path ? E('span', { 'class': 'cyber-list-meta-item' }, [ + E('span', {}, '๐Ÿ“ '), + source.path + ]) : null, + source.last_success ? E('span', { 'class': 'cyber-list-meta-item' }, [ + E('span', {}, 'โฑ๏ธ '), + this.formatTimestamp(source.last_success) + ]) : null + ]) ]), - // Source actions - E('div', { 'class': 'source-actions' }, [ - E('button', { - 'class': 'btn btn-sm btn-primary', + // Actions + E('div', { 'class': 'cyber-list-actions' }, [ + source.enabled ? E('button', { + 'class': 'cyber-btn primary', 'click': function() { + console.log('[CATALOG-SOURCES] Sync source:', source.name); self.syncSource(source.name); - }, - 'disabled': !source.enabled - }, 'Sync'), - E('button', { - 'class': 'btn btn-sm btn-secondary', + } + }, '๐Ÿ”„ SYNC') : null, + source.enabled ? E('button', { + 'class': 'cyber-btn', 'click': function() { + console.log('[CATALOG-SOURCES] Test source:', source.name); self.testSource(source.name); - }, - 'disabled': !source.enabled - }, 'Test'), - !source.active ? E('button', { - 'class': 'btn btn-sm btn-warning', + } + }, '๐Ÿงช TEST') : null, + !source.active && source.enabled ? E('button', { + 'class': 'cyber-btn warning', 'click': function() { + console.log('[CATALOG-SOURCES] Set active:', source.name); self.setActiveSource(source.name); } - }, 'Set Active') : null, + }, 'โ–ธ SET ACTIVE') : null, E('button', { - 'class': 'btn btn-sm ' + (source.enabled ? 'btn-danger' : 'btn-success'), + 'class': 'cyber-btn ' + (source.enabled ? 'danger' : ''), 'click': function() { + console.log('[CATALOG-SOURCES] Toggle source:', source.name, !source.enabled); self.toggleSource(source.name, !source.enabled); } - }, source.enabled ? 'Disable' : 'Enable') + }, source.enabled ? 'โŠ— DISABLE' : 'โŠ• ENABLE') ]) ]); }, @@ -183,20 +265,6 @@ return view.extend({ } }, - getStatusIcon: function(status) { - switch(status) { - case 'online': - case 'success': - case 'available': - return 'โœ“'; - case 'offline': - case 'error': - return 'โœ—'; - default: - return '?'; - } - }, - formatTimestamp: function(timestamp) { if (!timestamp) return 'Never'; var date = new Date(timestamp); @@ -205,71 +273,78 @@ return view.extend({ var diffMins = Math.floor(diffMs / 60000); if (diffMins < 1) return 'Just now'; - if (diffMins < 60) return diffMins + ' minutes ago'; + if (diffMins < 60) return diffMins + 'm ago'; var diffHours = Math.floor(diffMins / 60); - if (diffHours < 24) return diffHours + ' hours ago'; + if (diffHours < 24) return diffHours + 'h ago'; var diffDays = Math.floor(diffHours / 24); - if (diffDays < 7) return diffDays + ' days ago'; + if (diffDays < 7) return diffDays + 'd ago'; return date.toLocaleDateString(); }, syncSource: function(sourceName) { - ui.showModal(_('Syncing Catalog'), [ - E('p', { 'class': 'spinning' }, _('Syncing from source: %s...').format(sourceName)) + console.log('[CATALOG-SOURCES] Syncing source:', sourceName); + ui.showModal('Syncing Catalog', [ + Components.renderLoader('Syncing from source: ' + sourceName + '...') ]); API.syncCatalog(sourceName).then(function(result) { + console.log('[CATALOG-SOURCES] Sync result:', result); ui.hideModal(); if (result.success) { - ui.addNotification(null, E('p', _('Catalog synced successfully from: %s').format(sourceName)), 'success'); + ui.addNotification(null, E('p', 'Catalog synced successfully from: ' + sourceName), 'success'); window.location.reload(); } else { - ui.addNotification(null, E('p', _('Sync failed: %s').format(result.error || 'Unknown error')), 'error'); + ui.addNotification(null, E('p', 'Sync failed: ' + (result.error || 'Unknown error')), 'error'); } }).catch(function(err) { + console.error('[CATALOG-SOURCES] Sync error:', err); ui.hideModal(); - ui.addNotification(null, E('p', _('Sync error: %s').format(err.message)), 'error'); + ui.addNotification(null, E('p', 'Sync error: ' + err.message), 'error'); }); }, syncAllSources: function() { - ui.showModal(_('Syncing Catalogs'), [ - E('p', { 'class': 'spinning' }, _('Syncing from all enabled sources...')) + console.log('[CATALOG-SOURCES] Syncing all sources'); + ui.showModal('Syncing Catalogs', [ + Components.renderLoader('Syncing from all enabled sources...') ]); API.syncCatalog(null).then(function(result) { + console.log('[CATALOG-SOURCES] Sync all result:', result); ui.hideModal(); if (result.success) { - ui.addNotification(null, E('p', _('Catalogs synced successfully')), 'success'); + ui.addNotification(null, E('p', 'Catalogs synced successfully'), 'success'); window.location.reload(); } else { - ui.addNotification(null, E('p', _('Sync failed: %s').format(result.error || 'Unknown error')), 'error'); + ui.addNotification(null, E('p', 'Sync failed: ' + (result.error || 'Unknown error')), 'error'); } }).catch(function(err) { + console.error('[CATALOG-SOURCES] Sync all error:', err); ui.hideModal(); - ui.addNotification(null, E('p', _('Sync error: %s').format(err.message)), 'error'); + ui.addNotification(null, E('p', 'Sync error: ' + err.message), 'error'); }); }, testSource: function(sourceName) { - ui.addNotification(null, E('p', _('Testing source: %s...').format(sourceName)), 'info'); - // Test is done by attempting a sync + console.log('[CATALOG-SOURCES] Testing source:', sourceName); + ui.addNotification(null, E('p', 'Testing source: ' + sourceName + '...'), 'info'); this.syncSource(sourceName); }, setActiveSource: function(sourceName) { - ui.showModal(_('Setting Active Source'), [ - E('p', { 'class': 'spinning' }, _('Setting active source to: %s...').format(sourceName)) + console.log('[CATALOG-SOURCES] Setting active source:', sourceName); + ui.showModal('Setting Active Source', [ + Components.renderLoader('Setting active source to: ' + sourceName + '...') ]); API.setCatalogSource(sourceName).then(function(result) { + console.log('[CATALOG-SOURCES] Set active result:', result); ui.hideModal(); if (result.success) { - ui.addNotification(null, E('p', _('Active source set to: %s').format(sourceName)), 'success'); - // Trigger sync from new source + ui.addNotification(null, E('p', 'Active source set to: ' + sourceName), 'success'); return API.syncCatalog(sourceName); } else { throw new Error(result.error || 'Failed to set source'); @@ -277,20 +352,23 @@ return view.extend({ }).then(function() { window.location.reload(); }).catch(function(err) { + console.error('[CATALOG-SOURCES] Set active error:', err); ui.hideModal(); - ui.addNotification(null, E('p', _('Error: %s').format(err.message)), 'error'); + ui.addNotification(null, E('p', 'Error: ' + err.message), 'error'); }); }, toggleSource: function(sourceName, enable) { + console.log('[CATALOG-SOURCES] Toggle source:', sourceName, enable); ui.addNotification(null, - E('p', _('%s source: %s').format(enable ? 'Enabling' : 'Disabling', sourceName)), + E('p', (enable ? 'Enabling' : 'Disabling') + ' source: ' + sourceName), 'info' ); // TODO: Implement UCI config update to enable/disable source }, refreshPage: function() { + console.log('[CATALOG-SOURCES] Refreshing page'); window.location.reload(); },