feat: add cyberpunk theme to Catalog Sources with debug

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 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-04 17:18:07 +01:00
parent 4b8d72b6f6
commit d18222dec8
2 changed files with 208 additions and 130 deletions

View File

@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-secubox-admin PKG_NAME:=luci-app-secubox-admin
PKG_VERSION:=1.0.0 PKG_VERSION:=1.0.0
PKG_RELEASE:=11 PKG_RELEASE:=12
PKG_LICENSE:=MIT PKG_LICENSE:=MIT
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr> PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>

View File

@ -7,83 +7,150 @@
return view.extend({ return view.extend({
load: function() { load: function() {
console.log('[CATALOG-SOURCES] Loading data...');
return Promise.all([ return Promise.all([
L.resolveDefault(API.getCatalogSources(), { sources: [] }), 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) { render: function(data) {
console.log('[CATALOG-SOURCES] Rendering with data:', data);
var sources = data[0].sources || []; var sources = data[0].sources || [];
var updateInfo = data[1]; var updateInfo = data[1];
var self = this; 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', E('link', { 'rel': 'stylesheet',
'href': L.resource('secubox-admin/common.css') }), 'href': L.resource('secubox-admin/common.css') }),
E('link', { 'rel': 'stylesheet', E('link', { 'rel': 'stylesheet',
'href': L.resource('secubox-admin/admin.css') }), 'href': L.resource('secubox-admin/admin.css') }),
E('link', { 'rel': 'stylesheet',
'href': L.resource('secubox-admin/cyberpunk.css') }),
E('h2', {}, 'Catalog Sources'), // Cyberpunk header
E('p', {}, 'Manage catalog sources with automatic fallback'), 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 // Stats panel
E('div', { 'class': 'source-summary' }, [ E('div', { 'class': 'cyber-panel' }, [
E('div', { 'class': 'stat-card' }, [ E('div', { 'class': 'cyber-panel-header' }, [
E('div', { 'class': 'stat-label' }, 'Total Sources'), E('span', { 'class': 'cyber-panel-title' }, 'SYSTEM STATUS'),
E('div', { 'class': 'stat-value' }, sources.length.toString()) E('span', { 'class': 'cyber-panel-badge' }, enabledCount + '/' + sources.length)
]), ]),
E('div', { 'class': 'stat-card' }, [ E('div', { 'class': 'cyber-panel-body' }, [
E('div', { 'class': 'stat-label' }, 'Active Source'), E('div', { 'class': 'cyber-stats-grid' }, [
E('div', { 'class': 'stat-value' }, E('div', { 'class': 'cyber-stat-card' }, [
sources.filter(function(s) { return s.active; })[0]?.name || 'None') E('div', { 'class': 'cyber-stat-icon' }, '📡'),
]), E('div', { 'class': 'cyber-stat-value' }, sources.length),
E('div', { 'class': 'stat-card' }, [ E('div', { 'class': 'cyber-stat-label' }, 'Total Sources')
E('div', { 'class': 'stat-label' }, 'Updates Available'), ]),
E('div', { 'class': 'stat-value' }, E('div', { 'class': 'cyber-stat-card accent' }, [
(updateInfo.total_updates_available || 0).toString()) 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 // Quick actions panel
E('div', { 'class': 'sync-controls' }, [ E('div', { 'class': 'cyber-panel' }, [
E('button', { E('div', { 'class': 'cyber-panel-header' }, [
'class': 'btn btn-primary', E('span', { 'class': 'cyber-panel-title' }, 'QUICK ACTIONS')
'click': function() { ]),
self.syncAllSources(); E('div', { 'class': 'cyber-panel-body' }, [
} E('div', { 'class': 'cyber-quick-actions' }, [
}, 'Sync All Sources'), E('button', {
E('button', { 'class': 'cyber-action-btn',
'class': 'btn btn-secondary', 'click': function() {
'click': function() { console.log('[CATALOG-SOURCES] Sync all sources');
self.refreshPage(); self.syncAllSources();
} }
}, 'Refresh Status') }, [
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 // Sources list
E('div', { 'class': 'sources-container', 'id': 'sources-container' }, E('div', { 'class': 'cyber-list', 'id': 'sources-container' },
sources sources.length > 0 ?
.sort(function(a, b) { return a.priority - b.priority; }) sources
.map(function(source) { .sort(function(a, b) { return a.priority - b.priority; })
return self.renderSourceCard(source); .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 // Auto-refresh every 30 seconds
poll.add(function() { poll.add(function() {
console.log('[CATALOG-SOURCES] Polling for updates...');
return API.getCatalogSources().then(function(result) { return API.getCatalogSources().then(function(result) {
var sourcesContainer = document.getElementById('sources-container'); var sourcesContainer = document.getElementById('sources-container');
if (sourcesContainer) { if (sourcesContainer && result.sources) {
var sources = result.sources || []; console.log('[CATALOG-SOURCES] Poll update:', result.sources.length, 'sources');
sourcesContainer.innerHTML = ''; sourcesContainer.innerHTML = '';
sources result.sources
.sort(function(a, b) { return a.priority - b.priority; }) .sort(function(a, b) { return a.priority - b.priority; })
.forEach(function(source) { .forEach(function(source) {
sourcesContainer.appendChild(self.renderSourceCard(source)); sourcesContainer.appendChild(self.renderSourceCard(source));
}); });
} }
}).catch(function(err) {
console.error('[CATALOG-SOURCES] Poll error:', err);
}); });
}, 30); }, 30);
@ -93,78 +160,93 @@ return view.extend({
renderSourceCard: function(source) { renderSourceCard: function(source) {
var self = this; var self = this;
var statusClass = this.getStatusClass(source.status); 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', { return E('div', {
'class': 'source-card' + (source.active ? ' active-source' : ''), 'class': itemClass,
'data-source': source.name 'data-source': source.name
}, [ }, [
// Source header // Icon
E('div', { 'class': 'source-header' }, [ E('div', { 'class': 'cyber-list-icon' },
E('div', { 'class': 'source-title' }, [ source.type === 'remote' ? '🌐' :
E('h3', {}, source.name), source.type === 'local' ? '💾' :
source.active ? E('span', { 'class': 'badge badge-success' }, 'ACTIVE') : null source.type === 'embedded' ? '📦' : '❓'
]), ),
E('div', { 'class': 'source-priority' },
E('span', { 'class': 'priority-badge' }, 'Priority: ' + source.priority)
)
]),
// Source info // Content
E('div', { 'class': 'source-info' }, [ E('div', { 'class': 'cyber-list-content' }, [
E('div', { 'class': 'info-row' }, [ E('div', { 'class': 'cyber-list-title' }, [
E('span', { 'class': 'label' }, 'Type:'), source.name.toUpperCase(),
E('span', { 'class': 'value' }, source.type) source.active ? E('span', { 'class': 'cyber-badge success' }, [
]), E('span', { 'class': 'cyber-status-dot online' }),
source.url ? E('div', { 'class': 'info-row' }, [ ' ACTIVE'
E('span', { 'class': 'label' }, 'URL:'), ]) : null,
E('span', { 'class': 'value url-text' }, source.url) !source.enabled ? E('span', { 'class': 'cyber-badge' }, [
]) : null, 'DISABLED'
source.path ? E('div', { 'class': 'info-row' }, [ ]) : E('span', { 'class': 'cyber-badge info' }, [
E('span', { 'class': 'label' }, 'Path:'), E('span', { 'class': 'cyber-status-dot ' + statusDot }),
E('span', { 'class': 'value' }, source.path) ' ' + (source.status || 'UNKNOWN').toUpperCase()
]) : 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')
]) ])
]), ]),
source.last_success ? E('div', { 'class': 'info-row' }, [ E('div', { 'class': 'cyber-list-meta' }, [
E('span', { 'class': 'label' }, 'Last Success:'), E('span', { 'class': 'cyber-list-meta-item' }, [
E('span', { 'class': 'value' }, this.formatTimestamp(source.last_success)) E('span', {}, '🔢 '),
]) : null '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 // Actions
E('div', { 'class': 'source-actions' }, [ E('div', { 'class': 'cyber-list-actions' }, [
E('button', { source.enabled ? E('button', {
'class': 'btn btn-sm btn-primary', 'class': 'cyber-btn primary',
'click': function() { 'click': function() {
console.log('[CATALOG-SOURCES] Sync source:', source.name);
self.syncSource(source.name); self.syncSource(source.name);
}, }
'disabled': !source.enabled }, '🔄 SYNC') : null,
}, 'Sync'), source.enabled ? E('button', {
E('button', { 'class': 'cyber-btn',
'class': 'btn btn-sm btn-secondary',
'click': function() { 'click': function() {
console.log('[CATALOG-SOURCES] Test source:', source.name);
self.testSource(source.name); self.testSource(source.name);
}, }
'disabled': !source.enabled }, '🧪 TEST') : null,
}, 'Test'), !source.active && source.enabled ? E('button', {
!source.active ? E('button', { 'class': 'cyber-btn warning',
'class': 'btn btn-sm btn-warning',
'click': function() { 'click': function() {
console.log('[CATALOG-SOURCES] Set active:', source.name);
self.setActiveSource(source.name); self.setActiveSource(source.name);
} }
}, 'Set Active') : null, }, '▸ SET ACTIVE') : null,
E('button', { E('button', {
'class': 'btn btn-sm ' + (source.enabled ? 'btn-danger' : 'btn-success'), 'class': 'cyber-btn ' + (source.enabled ? 'danger' : ''),
'click': function() { 'click': function() {
console.log('[CATALOG-SOURCES] Toggle source:', source.name, !source.enabled);
self.toggleSource(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) { formatTimestamp: function(timestamp) {
if (!timestamp) return 'Never'; if (!timestamp) return 'Never';
var date = new Date(timestamp); var date = new Date(timestamp);
@ -205,71 +273,78 @@ return view.extend({
var diffMins = Math.floor(diffMs / 60000); var diffMins = Math.floor(diffMs / 60000);
if (diffMins < 1) return 'Just now'; 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); 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); var diffDays = Math.floor(diffHours / 24);
if (diffDays < 7) return diffDays + ' days ago'; if (diffDays < 7) return diffDays + 'd ago';
return date.toLocaleDateString(); return date.toLocaleDateString();
}, },
syncSource: function(sourceName) { syncSource: function(sourceName) {
ui.showModal(_('Syncing Catalog'), [ console.log('[CATALOG-SOURCES] Syncing source:', sourceName);
E('p', { 'class': 'spinning' }, _('Syncing from source: %s...').format(sourceName)) ui.showModal('Syncing Catalog', [
Components.renderLoader('Syncing from source: ' + sourceName + '...')
]); ]);
API.syncCatalog(sourceName).then(function(result) { API.syncCatalog(sourceName).then(function(result) {
console.log('[CATALOG-SOURCES] Sync result:', result);
ui.hideModal(); ui.hideModal();
if (result.success) { 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(); window.location.reload();
} else { } 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) { }).catch(function(err) {
console.error('[CATALOG-SOURCES] Sync error:', err);
ui.hideModal(); 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() { syncAllSources: function() {
ui.showModal(_('Syncing Catalogs'), [ console.log('[CATALOG-SOURCES] Syncing all sources');
E('p', { 'class': 'spinning' }, _('Syncing from all enabled sources...')) ui.showModal('Syncing Catalogs', [
Components.renderLoader('Syncing from all enabled sources...')
]); ]);
API.syncCatalog(null).then(function(result) { API.syncCatalog(null).then(function(result) {
console.log('[CATALOG-SOURCES] Sync all result:', result);
ui.hideModal(); ui.hideModal();
if (result.success) { if (result.success) {
ui.addNotification(null, E('p', _('Catalogs synced successfully')), 'success'); ui.addNotification(null, E('p', 'Catalogs synced successfully'), 'success');
window.location.reload(); window.location.reload();
} else { } 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) { }).catch(function(err) {
console.error('[CATALOG-SOURCES] Sync all error:', err);
ui.hideModal(); 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) { testSource: function(sourceName) {
ui.addNotification(null, E('p', _('Testing source: %s...').format(sourceName)), 'info'); console.log('[CATALOG-SOURCES] Testing source:', sourceName);
// Test is done by attempting a sync ui.addNotification(null, E('p', 'Testing source: ' + sourceName + '...'), 'info');
this.syncSource(sourceName); this.syncSource(sourceName);
}, },
setActiveSource: function(sourceName) { setActiveSource: function(sourceName) {
ui.showModal(_('Setting Active Source'), [ console.log('[CATALOG-SOURCES] Setting active source:', sourceName);
E('p', { 'class': 'spinning' }, _('Setting active source to: %s...').format(sourceName)) ui.showModal('Setting Active Source', [
Components.renderLoader('Setting active source to: ' + sourceName + '...')
]); ]);
API.setCatalogSource(sourceName).then(function(result) { API.setCatalogSource(sourceName).then(function(result) {
console.log('[CATALOG-SOURCES] Set active result:', result);
ui.hideModal(); ui.hideModal();
if (result.success) { if (result.success) {
ui.addNotification(null, E('p', _('Active source set to: %s').format(sourceName)), 'success'); ui.addNotification(null, E('p', 'Active source set to: ' + sourceName), 'success');
// Trigger sync from new source
return API.syncCatalog(sourceName); return API.syncCatalog(sourceName);
} else { } else {
throw new Error(result.error || 'Failed to set source'); throw new Error(result.error || 'Failed to set source');
@ -277,20 +352,23 @@ return view.extend({
}).then(function() { }).then(function() {
window.location.reload(); window.location.reload();
}).catch(function(err) { }).catch(function(err) {
console.error('[CATALOG-SOURCES] Set active error:', err);
ui.hideModal(); 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) { toggleSource: function(sourceName, enable) {
console.log('[CATALOG-SOURCES] Toggle source:', sourceName, enable);
ui.addNotification(null, ui.addNotification(null,
E('p', _('%s source: %s').format(enable ? 'Enabling' : 'Disabling', sourceName)), E('p', (enable ? 'Enabling' : 'Disabling') + ' source: ' + sourceName),
'info' 'info'
); );
// TODO: Implement UCI config update to enable/disable source // TODO: Implement UCI config update to enable/disable source
}, },
refreshPage: function() { refreshPage: function() {
console.log('[CATALOG-SOURCES] Refreshing page');
window.location.reload(); window.location.reload();
}, },