secubox-openwrt/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/logs.js
CyberMind-FR 8e53825ad5 release: v0.2.2 - Design System v0.3.0 & Comprehensive Documentation
🎨 Design System v0.3.0 (Demo-inspired)
- New dark palette: #0a0a0f, #6366f1→#8b5cf6 gradients
- Typography: Inter + JetBrains Mono
- Compact stats grid (130px min)
- Gradient text effects with background-clip
- Sticky navigation tabs
- Enhanced card borders and hover effects

📚 Comprehensive Documentation Suite
- DEVELOPMENT-GUIDELINES.md (33KB, 900+ lines)
  - 9 major sections: Design, Architecture, RPCD, ACL, JS, CSS, Errors, Validation, Deployment
  - Complete code templates and best practices
  - Common error diagnostics and solutions
- QUICK-START.md (6.4KB)
  - 8 critical rules for immediate reference
  - Quick code templates
  - Error quick fixes table
- deploy-module-template.sh (8.1KB)
  - Standardized deployment with automatic backup
  - Permission fixes, cache clearing, verification
- Updated CLAUDE.md, README.md with documentation index
- Updated .claude/README.md to v2.0

🔄 Version Updates
- luci-app-secubox: 0.1.2 → 0.2.2
- luci-app-system-hub: 0.1.1 → 0.2.2
- Updated all version strings (api.js, overview.js, CSS files)

🎯 CSS Enhancements
- common.css: Complete rewrite with demo palette
- overview.css: Dashboard header with gradient
- services.css: Updated version to 0.2.2
- components.css: Updated version to 0.2.2

🔧 Critical Rules Documented
1. RPCD naming: file = ubus object (luci. prefix)
2. Menu path = view file location
3. Permissions: 755 (RPCD), 644 (CSS/JS)
4. ALWAYS run validate-modules.sh
5. CSS variables only (no hardcode)
6. Dark mode mandatory
7. Typography: Inter + JetBrains Mono
8. Gradients: --sh-primary → --sh-primary-end

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-26 18:55:19 +01:00

296 lines
8.5 KiB
JavaScript

'use strict';
'require view';
'require ui';
'require dom';
'require system-hub/api as API';
'require system-hub/theme as Theme';
return view.extend({
logs: [],
currentFilter: 'all',
searchQuery: '',
lineCount: 100,
load: function() {
return Promise.all([
API.getLogs(100, ''),
Theme.getTheme()
]);
},
render: function(data) {
this.logs = data[0] || [];
var theme = data[1];
var container = E('div', { 'class': 'system-hub-logs' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/common.css') }),
E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/dashboard.css') }),
// Header
this.renderHeader(),
// Controls
this.renderControls(),
// Filter tabs
this.renderFilterTabs(),
// Log viewer
E('div', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-header' }, [
E('h3', { 'class': 'sh-card-title' }, [
E('span', { 'class': 'sh-card-title-icon' }, '📟'),
'Log Output'
]),
E('div', { 'class': 'sh-card-badge' }, this.getFilteredLogs().length + ' lines')
]),
E('div', { 'class': 'sh-card-body', 'style': 'padding: 0;' }, [
E('div', { 'id': 'log-container' })
])
])
]);
// Initial render
this.updateLogDisplay();
return container;
},
renderHeader: function() {
var stats = this.getLogStats();
return E('div', { 'class': 'sh-page-header' }, [
E('div', {}, [
E('h2', { 'class': 'sh-page-title' }, [
E('span', { 'class': 'sh-page-title-icon' }, '📋'),
'System Logs'
]),
E('p', { 'class': 'sh-page-subtitle' },
'View and filter system logs in real-time')
]),
E('div', { 'class': 'sh-stats-grid' }, [
E('div', { 'class': 'sh-stat-badge' }, [
E('div', { 'class': 'sh-stat-value' }, stats.total),
E('div', { 'class': 'sh-stat-label' }, 'Total Lines')
]),
E('div', { 'class': 'sh-stat-badge' }, [
E('div', { 'class': 'sh-stat-value', 'style': 'color: #ef4444;' }, stats.errors),
E('div', { 'class': 'sh-stat-label' }, 'Errors')
]),
E('div', { 'class': 'sh-stat-badge' }, [
E('div', { 'class': 'sh-stat-value', 'style': 'color: #f59e0b;' }, stats.warnings),
E('div', { 'class': 'sh-stat-label' }, 'Warnings')
])
])
]);
},
renderControls: function() {
var self = this;
return E('div', { 'style': 'display: flex; gap: 12px; margin-bottom: 24px; flex-wrap: wrap;' }, [
// Search box
E('input', {
'type': 'text',
'class': 'cbi-input-text',
'placeholder': '🔍 Search logs...',
'style': 'flex: 1; min-width: 250px; padding: 12px 16px; border-radius: 8px; border: 1px solid var(--sh-border); background: var(--sh-bg-card); color: var(--sh-text-primary); font-size: 14px;',
'input': function(ev) {
self.searchQuery = ev.target.value.toLowerCase();
self.updateLogDisplay();
}
}),
// Line count selector
E('select', {
'class': 'cbi-input-select',
'style': 'padding: 12px 16px; border-radius: 8px; border: 1px solid var(--sh-border); background: var(--sh-bg-card); color: var(--sh-text-primary); font-size: 14px;',
'change': function(ev) {
self.lineCount = parseInt(ev.target.value);
self.refreshLogs();
}
}, [
E('option', { 'value': '50' }, '50 lines'),
E('option', { 'value': '100', 'selected': '' }, '100 lines'),
E('option', { 'value': '200' }, '200 lines'),
E('option', { 'value': '500' }, '500 lines'),
E('option', { 'value': '1000' }, '1000 lines')
]),
// Refresh button
E('button', {
'class': 'sh-btn sh-btn-primary',
'click': L.bind(this.refreshLogs, this)
}, [
E('span', {}, '🔄'),
E('span', {}, 'Refresh')
]),
// Download button
E('button', {
'class': 'sh-btn sh-btn-secondary',
'click': function() {
self.downloadLogs();
}
}, [
E('span', {}, '📥'),
E('span', {}, 'Download')
])
]);
},
renderFilterTabs: function() {
var stats = this.getLogStats();
return E('div', { 'class': 'sh-filter-tabs' }, [
this.createFilterTab('all', '📋', 'All Logs', stats.total),
this.createFilterTab('error', '🔴', 'Errors', stats.errors),
this.createFilterTab('warning', '🟡', 'Warnings', stats.warnings),
this.createFilterTab('info', '🔵', 'Info', stats.info)
]);
},
createFilterTab: function(filter, icon, label, count) {
var self = this;
var isActive = this.currentFilter === filter;
return E('div', {
'class': 'sh-filter-tab' + (isActive ? ' active' : ''),
'click': function() {
self.currentFilter = filter;
self.updateFilterTabs();
self.updateLogDisplay();
}
}, [
E('span', { 'class': 'sh-tab-icon' }, icon),
E('span', { 'class': 'sh-tab-label' }, label + ' (' + count + ')')
]);
},
updateFilterTabs: function() {
var tabs = document.querySelectorAll('.sh-filter-tab');
tabs.forEach(function(tab, index) {
var filters = ['all', 'error', 'warning', 'info'];
if (filters[index] === this.currentFilter) {
tab.classList.add('active');
} else {
tab.classList.remove('active');
}
}.bind(this));
},
updateLogDisplay: function() {
var container = document.getElementById('log-container');
if (!container) return;
var filtered = this.getFilteredLogs();
var logsText = filtered.length > 0 ? filtered.join('\n') : 'No logs available';
dom.content(container, [
E('pre', {
'style': 'background: var(--sh-bg-tertiary); color: var(--sh-text-primary); padding: 20px; overflow: auto; max-height: 600px; font-size: 12px; font-family: "Courier New", monospace; border-radius: 0; margin: 0; line-height: 1.5;'
}, logsText)
]);
// Update badge
var badge = document.querySelector('.sh-card-badge');
if (badge) {
badge.textContent = filtered.length + ' lines';
}
},
getFilteredLogs: function() {
return this.logs.filter(function(line) {
var lineLower = line.toLowerCase();
// Apply filter
var matchesFilter = true;
switch (this.currentFilter) {
case 'error':
matchesFilter = lineLower.includes('error') || lineLower.includes('err') || lineLower.includes('fail');
break;
case 'warning':
matchesFilter = lineLower.includes('warn') || lineLower.includes('warning');
break;
case 'info':
matchesFilter = lineLower.includes('info') || lineLower.includes('notice');
break;
}
// Apply search
var matchesSearch = !this.searchQuery || lineLower.includes(this.searchQuery);
return matchesFilter && matchesSearch;
}.bind(this));
},
getLogStats: function() {
var stats = {
total: this.logs.length,
errors: 0,
warnings: 0,
info: 0
};
this.logs.forEach(function(line) {
var lineLower = line.toLowerCase();
if (lineLower.includes('error') || lineLower.includes('err') || lineLower.includes('fail')) {
stats.errors++;
} else if (lineLower.includes('warn') || lineLower.includes('warning')) {
stats.warnings++;
} else if (lineLower.includes('info') || lineLower.includes('notice')) {
stats.info++;
}
});
return stats;
},
refreshLogs: function() {
ui.showModal(_('Loading Logs'), [
E('p', { 'class': 'spinning' }, _('Fetching logs...'))
]);
API.getLogs(this.lineCount, '').then(L.bind(function(logs) {
ui.hideModal();
this.logs = logs || [];
this.updateLogDisplay();
// Update stats
var stats = this.getLogStats();
var statBadges = document.querySelectorAll('.sh-stat-value');
if (statBadges.length >= 3) {
statBadges[0].textContent = stats.total;
statBadges[1].textContent = stats.errors;
statBadges[2].textContent = stats.warnings;
}
// Update filter tabs counts
var tabs = document.querySelectorAll('.sh-tab-label');
if (tabs.length >= 4) {
tabs[0].textContent = 'All Logs (' + stats.total + ')';
tabs[1].textContent = 'Errors (' + stats.errors + ')';
tabs[2].textContent = 'Warnings (' + stats.warnings + ')';
tabs[3].textContent = 'Info (' + stats.info + ')';
}
}, this)).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', _('Failed to load logs: ') + err.message), 'error');
});
},
downloadLogs: function() {
var filtered = this.getFilteredLogs();
var content = filtered.join('\n');
var blob = new Blob([content], { type: 'text/plain' });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = 'system-logs-' + new Date().toISOString().split('T')[0] + '.txt';
a.click();
URL.revokeObjectURL(url);
ui.addNotification(null, E('p', '✓ Logs downloaded'), 'info');
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});