feat: implement working system logs viewer in system-hub

Fixed and enhanced the system logs functionality with real-time log viewing
and color-coded log levels.

🔧 Backend Fix (RPCD)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Problem:
- get_logs() function returned empty array
- Logs were piped through while loop creating a subshell
- json_add_string in subshell didn't affect main JSON output

Solution:
- Use temporary file to collect logs
- Read from file outside of subshell
- Properly build JSON array with all log lines

Implementation:
```sh
# Store logs in temporary file
logread | tail -n "$lines" > "$tmpfile"

# Read from file (no subshell issue)
while IFS= read -r line; do
    json_add_string "" "$line"
done < "$tmpfile"
```

Testing:
 ubus call luci.system-hub get_logs '{"lines": 100}' → Returns real logs
 Filter parameter works: get_logs '{"filter": "error"}'
 Temporary files cleaned up after use

🎨 Frontend Enhancement (logs.js)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Added color-coded log level display:

Features:
1. **Color-coded by severity:**
   - 🔴 Errors (red): error, err, fatal, crit
   - 🟡 Warnings (orange): warn, warning
   - 🔵 Info (blue): info, notice
   - 🟣 Debug (purple): debug
   - 🟢 Success (green): success, ok

2. **Visual enhancements:**
   - Colored left border for each log line
   - Semi-transparent background matching log level
   - JetBrains Mono font for better readability
   - Proper line spacing and padding

3. **Empty state:**
   - Friendly message when no logs available
   - Different message for empty search results
   - Large icon for better UX

Implementation:
```javascript
renderLogLine: function(line) {
    // Detect log level from keywords
    if (line.includes('error')) {
        color = '#ef4444';  // Red
        bgColor = 'rgba(239, 68, 68, 0.1)';
    }
    // ... other levels

    return E('div', {
        'style': 'border-left: 3px solid ' + color + '; ...'
    }, line);
}
```

📋 Features Summary
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 Real-time system log viewing (logread integration)
 Filter by log level (all, errors, warnings, info)
 Search functionality (filter text in logs)
 Adjustable line count (50, 100, 200, 500, 1000 lines)
 Color-coded log levels for easy identification
 Refresh button to reload logs
 Download logs as .txt file
 Statistics: total lines, errors count, warnings count
 Beautiful empty state when no logs match

🚀 Deployment
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 Updated RPCD backend on router
 Updated frontend logs.js on router
 Permissions fixed (755 for RPCD, 644 for JS)
 Services restarted (rpcd, uhttpd)
 Tested and working

Test URL: https://192.168.8.191/cgi-bin/luci/admin/secubox/system/system-hub/logs

Files Modified:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Backend:
* luci-app-system-hub/root/usr/libexec/rpcd/luci.system-hub
  - Fixed get_logs() to return real logs (+12 lines refactor)

Frontend:
* luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/logs.js
  - Added renderLogLine() for color-coded display (+27 lines)
  - Enhanced updateLogDisplay() with empty state (+18 lines)
  - JetBrains Mono font integration

🎯 User Experience
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Before:
 Empty log viewer (backend returned [])
 Plain text display
 No visual distinction between log levels

After:
 Real system logs displayed
 Color-coded by severity level
 Easy to spot errors and warnings
 Professional terminal-like appearance
 Fully functional filters and search

🤖 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 2025-12-26 21:07:16 +01:00
parent 68a9f35797
commit e90cf85f69
2 changed files with 68 additions and 15 deletions

View File

@ -181,13 +181,30 @@ return view.extend({
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)
]);
if (filtered.length === 0) {
dom.content(container, [
E('div', {
'style': 'padding: 60px 20px; text-align: center; color: var(--sh-text-secondary);'
}, [
E('div', { 'style': 'font-size: 48px; margin-bottom: 16px;' }, '📋'),
E('div', { 'style': 'font-size: 16px; font-weight: 500;' }, 'No logs available'),
E('div', { 'style': 'font-size: 14px; margin-top: 8px;' },
this.searchQuery ? 'Try a different search query' : 'System logs will appear here')
])
]);
} else {
// Render colored log lines
var logLines = filtered.map(function(line) {
return this.renderLogLine(line);
}.bind(this));
dom.content(container, [
E('div', {
'style': 'background: var(--sh-bg-tertiary); padding: 20px; overflow: auto; max-height: 600px; font-size: 12px; font-family: "JetBrains Mono", "Courier New", monospace; line-height: 1.6;'
}, logLines)
]);
}
// Update badge
var badge = document.querySelector('.sh-card-badge');
@ -196,6 +213,34 @@ return view.extend({
}
},
renderLogLine: function(line) {
var lineLower = line.toLowerCase();
var color = 'var(--sh-text-primary)';
var bgColor = 'transparent';
// Detect log level and apply color
if (lineLower.includes('error') || lineLower.includes('err') || lineLower.includes('fatal') || lineLower.includes('crit')) {
color = '#ef4444'; // Red for errors
bgColor = 'rgba(239, 68, 68, 0.1)';
} else if (lineLower.includes('warn') || lineLower.includes('warning')) {
color = '#f59e0b'; // Orange for warnings
bgColor = 'rgba(245, 158, 11, 0.1)';
} else if (lineLower.includes('info') || lineLower.includes('notice')) {
color = '#3b82f6'; // Blue for info
bgColor = 'rgba(59, 130, 246, 0.1)';
} else if (lineLower.includes('debug')) {
color = '#8b5cf6'; // Purple for debug
bgColor = 'rgba(139, 92, 246, 0.1)';
} else if (lineLower.includes('success') || lineLower.includes('ok')) {
color = '#22c55e'; // Green for success
bgColor = 'rgba(34, 197, 94, 0.1)';
}
return E('div', {
'style': 'padding: 4px 8px; margin: 0; color: ' + color + '; background: ' + bgColor + '; border-left: 3px solid ' + color + '; margin-bottom: 2px; border-radius: 2px;'
}, line);
},
getFilteredLogs: function() {
return this.logs.filter(function(line) {
var lineLower = line.toLowerCase();

View File

@ -388,20 +388,28 @@ get_logs() {
json_get_var filter filter ""
json_cleanup
# Get logs into a temporary file to avoid subshell issues
local tmpfile="/tmp/syslog-$$"
if [ -n "$filter" ]; then
logread | tail -n "$lines" | grep -i "$filter" > "$tmpfile"
else
logread | tail -n "$lines" > "$tmpfile"
fi
json_init
json_add_array "logs"
if [ -n "$filter" ]; then
logread | tail -n "$lines" | grep -i "$filter" | while IFS= read -r line; do
json_add_string "" "$line"
done
else
logread | tail -n "$lines" | while IFS= read -r line; do
json_add_string "" "$line"
done
fi
# Read from file line by line
while IFS= read -r line; do
json_add_string "" "$line"
done < "$tmpfile"
json_close_array
# Cleanup
rm -f "$tmpfile"
json_dump
}