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_VERSION:=1.0.0
PKG_RELEASE:=11
PKG_RELEASE:=12
PKG_LICENSE:=MIT
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>

View File

@ -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();
},