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:
parent
68a9f35797
commit
e90cf85f69
@ -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();
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user