feat: theme-aligned monitoring and seccubox logs
This commit is contained in:
parent
ee5c001572
commit
d566a84dda
@ -58,6 +58,18 @@ var callStats = rpc.declare({
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callSecuboxLogs = rpc.declare({
|
||||
object: 'luci.crowdsec-dashboard',
|
||||
method: 'seccubox_logs',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callCollectDebug = rpc.declare({
|
||||
object: 'luci.crowdsec-dashboard',
|
||||
method: 'collect_debug',
|
||||
expect: { success: false }
|
||||
});
|
||||
|
||||
var callBan = rpc.declare({
|
||||
object: 'luci.crowdsec-dashboard',
|
||||
method: 'ban',
|
||||
@ -99,8 +111,26 @@ return baseclass.extend({
|
||||
getMachines: callMachines,
|
||||
getHub: callHub,
|
||||
getStats: callStats,
|
||||
getSecuboxLogs: callSecuboxLogs,
|
||||
collectDebugSnapshot: callCollectDebug,
|
||||
addBan: callBan,
|
||||
removeBan: callUnban,
|
||||
formatDuration: formatDuration,
|
||||
formatDate: formatDate
|
||||
formatDate: formatDate,
|
||||
|
||||
getDashboardData: function() {
|
||||
return Promise.all([
|
||||
callStatus(),
|
||||
callStats(),
|
||||
callDecisions(),
|
||||
callAlerts()
|
||||
]).then(function(results) {
|
||||
return {
|
||||
status: results[0] || {},
|
||||
stats: results[1] || {},
|
||||
decisions: (results[2] && results[2].decisions) || [],
|
||||
alerts: (results[3] && results[3].alerts) || []
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -247,6 +247,17 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.cs-log-card pre.cs-log-output {
|
||||
background: #0b1120;
|
||||
color: #9efc6a;
|
||||
padding: 14px;
|
||||
border-radius: 12px;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 12px;
|
||||
max-height: 220px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
.cs-table {
|
||||
width: 100%;
|
||||
|
||||
@ -28,7 +28,10 @@ return view.extend({
|
||||
// Load API
|
||||
this.csApi = new api();
|
||||
|
||||
return this.csApi.getDashboardData();
|
||||
return Promise.all([
|
||||
this.csApi.getDashboardData(),
|
||||
this.csApi.getSecuboxLogs()
|
||||
]);
|
||||
},
|
||||
|
||||
renderHeader: function(status) {
|
||||
@ -310,7 +313,7 @@ return view.extend({
|
||||
if (result.success) {
|
||||
self.showToast('IP ' + ip + ' unbanned successfully', 'success');
|
||||
// Refresh data
|
||||
return self.csApi.getDashboardData();
|
||||
return self.refreshDashboard();
|
||||
} else {
|
||||
self.showToast('Failed to unban: ' + (result.error || 'Unknown error'), 'error');
|
||||
}
|
||||
@ -355,7 +358,7 @@ return view.extend({
|
||||
if (result.success) {
|
||||
self.showToast('IP ' + ip + ' banned for ' + duration, 'success');
|
||||
self.closeBanModal();
|
||||
return self.csApi.getDashboardData();
|
||||
return self.refreshDashboard();
|
||||
} else {
|
||||
self.showToast('Failed to ban: ' + (result.error || 'Unknown error'), 'error');
|
||||
}
|
||||
@ -393,6 +396,7 @@ return view.extend({
|
||||
var stats = data.stats || {};
|
||||
var decisions = data.decisions || [];
|
||||
var alerts = data.alerts || [];
|
||||
var logs = this.logs || [];
|
||||
|
||||
return E('div', {}, [
|
||||
this.renderHeader(status),
|
||||
@ -429,31 +433,77 @@ return view.extend({
|
||||
E('div', { 'class': 'cs-card-title' }, 'Recent Alerts'),
|
||||
]),
|
||||
E('div', { 'class': 'cs-card-body' }, this.renderAlertsTimeline(alerts))
|
||||
])
|
||||
]),
|
||||
this.renderLogCard(logs)
|
||||
]),
|
||||
|
||||
this.renderBanModal()
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
render: function(payload) {
|
||||
var self = this;
|
||||
this.data = data;
|
||||
this.data = payload[0] || {};
|
||||
this.logs = (payload[1] && payload[1].entries) || [];
|
||||
|
||||
var view = E('div', { 'class': 'crowdsec-dashboard' }, [
|
||||
E('div', { 'id': 'cs-dashboard-content' }, this.renderContent(data))
|
||||
E('div', { 'id': 'cs-dashboard-content' }, this.renderContent(this.data))
|
||||
]);
|
||||
|
||||
// Setup polling for auto-refresh (every 30 seconds)
|
||||
poll.add(function() {
|
||||
return self.csApi.getDashboardData().then(function(newData) {
|
||||
self.data = newData;
|
||||
self.updateView();
|
||||
});
|
||||
return self.refreshDashboard();
|
||||
}, 30);
|
||||
|
||||
return view;
|
||||
},
|
||||
|
||||
refreshDashboard: function() {
|
||||
var self = this;
|
||||
return Promise.all([
|
||||
self.csApi.getDashboardData(),
|
||||
self.csApi.getSecuboxLogs()
|
||||
]).then(function(results) {
|
||||
self.data = results[0];
|
||||
self.logs = (results[1] && results[1].entries) || [];
|
||||
self.updateView();
|
||||
});
|
||||
},
|
||||
|
||||
renderLogCard: function(entries) {
|
||||
return E('div', { 'class': 'cs-card cs-log-card' }, [
|
||||
E('div', { 'class': 'cs-card-header' }, [
|
||||
E('div', { 'class': 'cs-card-title' }, _('SecuBox Log Tail')),
|
||||
E('button', {
|
||||
'class': 'cs-btn cs-btn-secondary cs-btn-sm',
|
||||
'click': ui.createHandlerFn(this, 'handleSnapshot')
|
||||
}, _('Snapshot'))
|
||||
]),
|
||||
entries && entries.length ?
|
||||
E('pre', { 'class': 'cs-log-output' }, entries.join('\n')) :
|
||||
E('p', { 'class': 'cs-empty' }, _('Log file empty'))
|
||||
]);
|
||||
},
|
||||
|
||||
handleSnapshot: function() {
|
||||
var self = this;
|
||||
ui.showModal(_('Collecting snapshot'), [
|
||||
E('p', {}, _('Aggregating dmesg/logread into SecuBox log…')),
|
||||
E('div', { 'class': 'spinning' })
|
||||
]);
|
||||
this.csApi.collectDebugSnapshot().then(function(result) {
|
||||
ui.hideModal();
|
||||
if (result && result.success) {
|
||||
self.refreshDashboard();
|
||||
self.showToast(_('Snapshot appended to /var/log/seccubox.log'), 'success');
|
||||
} else {
|
||||
self.showToast((result && result.error) || _('Snapshot failed'), 'error');
|
||||
}
|
||||
}).catch(function(err) {
|
||||
ui.hideModal();
|
||||
self.showToast(err.message || _('Snapshot failed'), 'error');
|
||||
});
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
|
||||
@ -6,6 +6,13 @@
|
||||
. /lib/functions.sh
|
||||
. /usr/share/libubox/jshn.sh
|
||||
|
||||
SECCUBOX_LOG="/usr/sbin/secubox-log"
|
||||
|
||||
secubox_log() {
|
||||
[ -x "$SECCUBOX_LOG" ] || return
|
||||
"$SECCUBOX_LOG" --tag "crowdsec" --message "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
CSCLI="/usr/bin/cscli"
|
||||
|
||||
# Check if cscli exists
|
||||
@ -137,6 +144,7 @@ add_ban() {
|
||||
result=$($CSCLI decisions add --ip "$ip" --duration "$duration" --reason "$reason" 2>&1)
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
secubox_log "CrowdSec ban added for $ip ($duration)"
|
||||
echo '{"success": true}'
|
||||
else
|
||||
json_init
|
||||
@ -161,6 +169,7 @@ remove_ban() {
|
||||
result=$($CSCLI decisions delete --ip "$ip" 2>&1)
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
secubox_log "CrowdSec ban removed for $ip"
|
||||
echo '{"success": true}'
|
||||
else
|
||||
json_init
|
||||
@ -214,6 +223,31 @@ get_dashboard_stats() {
|
||||
json_dump
|
||||
}
|
||||
|
||||
seccubox_logs() {
|
||||
json_init
|
||||
json_add_array "entries"
|
||||
if [ -f /var/log/seccubox.log ]; then
|
||||
tail -n 80 /var/log/seccubox.log | while IFS= read -r line; do
|
||||
json_add_string "" "$line"
|
||||
done
|
||||
fi
|
||||
json_close_array
|
||||
json_dump
|
||||
}
|
||||
|
||||
collect_debug() {
|
||||
json_init
|
||||
if [ -x "$SECCUBOX_LOG" ]; then
|
||||
"$SECCUBOX_LOG" --snapshot >/dev/null 2>&1
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Snapshot appended to /var/log/seccubox.log"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "secubox-log helper not found"
|
||||
fi
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Main dispatcher
|
||||
case "$1" in
|
||||
list)
|
||||
@ -259,6 +293,12 @@ case "$1" in
|
||||
stats)
|
||||
get_dashboard_stats
|
||||
;;
|
||||
seccubox_logs)
|
||||
seccubox_logs
|
||||
;;
|
||||
collect_debug)
|
||||
collect_debug
|
||||
;;
|
||||
*)
|
||||
echo '{"error": "Unknown method"}'
|
||||
;;
|
||||
|
||||
@ -47,6 +47,11 @@ Real-time system monitoring dashboard for OpenWrt with a modern, responsive inte
|
||||
- Animated gauges and sparklines
|
||||
- GitHub-inspired color palette
|
||||
|
||||
### 🔔 SecuBox Alerts & Logs
|
||||
- Control bar integrates with the new `/usr/sbin/secubox-log` helper.
|
||||
- Start/restart/stop events get appended to `/var/log/seccubox.log`.
|
||||
- Dashboard card shows the tail of the aggregated log and lets you capture a dmesg/logread snapshot from LuCI.
|
||||
|
||||
## Screenshots
|
||||
|
||||
### Real-time View
|
||||
|
||||
@ -96,6 +96,18 @@ var callStopNetdata = rpc.declare({
|
||||
expect: { success: false }
|
||||
});
|
||||
|
||||
var callSecuboxLogs = rpc.declare({
|
||||
object: 'luci.netdata-dashboard',
|
||||
method: 'seccubox_logs',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callCollectDebug = rpc.declare({
|
||||
object: 'luci.netdata-dashboard',
|
||||
method: 'collect_debug',
|
||||
expect: { success: false }
|
||||
});
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (!bytes || bytes === 0) return '0 B';
|
||||
var units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
@ -134,6 +146,8 @@ return baseclass.extend({
|
||||
restartNetdata: callRestartNetdata,
|
||||
startNetdata: callStartNetdata,
|
||||
stopNetdata: callStopNetdata,
|
||||
getSecuboxLogs: callSecuboxLogs,
|
||||
collectDebugSnapshot: callCollectDebug,
|
||||
|
||||
// Utility functions
|
||||
formatBytes: formatBytes,
|
||||
|
||||
@ -170,6 +170,96 @@
|
||||
color: var(--nd-text-muted);
|
||||
}
|
||||
|
||||
/* New SecuBox-aligned cards */
|
||||
.nd-control-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.nd-card {
|
||||
background: var(--nd-bg-secondary);
|
||||
border: 1px solid var(--nd-border);
|
||||
border-radius: var(--nd-radius-lg);
|
||||
padding: 20px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: var(--nd-shadow);
|
||||
}
|
||||
|
||||
.nd-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.nd-card-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nd-chip {
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(56, 189, 248, 0.1);
|
||||
color: var(--nd-accent-blue);
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.nd-chip.danger {
|
||||
background: rgba(248, 81, 73, 0.15);
|
||||
color: var(--nd-accent-red);
|
||||
}
|
||||
|
||||
.nd-card-text {
|
||||
margin: 0;
|
||||
color: var(--nd-text-secondary);
|
||||
}
|
||||
|
||||
.nd-card-actions {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.nd-card.nd-logs pre {
|
||||
background: #000;
|
||||
color: #a8ff60;
|
||||
padding: 12px;
|
||||
border-radius: var(--nd-radius);
|
||||
max-height: 240px;
|
||||
overflow-y: auto;
|
||||
font-family: var(--nd-font-mono);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.nd-card.nd-embed {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nd-card.nd-embed.off {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nd-iframe-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-top: 75%;
|
||||
background: var(--nd-bg-tertiary);
|
||||
}
|
||||
|
||||
.nd-iframe-wrapper iframe {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
/* Charts Grid */
|
||||
.nd-charts-grid {
|
||||
display: grid;
|
||||
|
||||
@ -4,13 +4,20 @@
|
||||
'require ui';
|
||||
'require poll';
|
||||
'require netdata-dashboard/api as API';
|
||||
'require secubox-theme/theme as Theme';
|
||||
|
||||
var lang = (typeof L !== 'undefined' && L.env && L.env.lang) ||
|
||||
(document.documentElement && document.documentElement.getAttribute('lang')) ||
|
||||
(navigator.language ? navigator.language.split('-')[0] : 'en');
|
||||
Theme.init({ language: lang });
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
API.getNetdataStatus(),
|
||||
API.getNetdataAlarms(),
|
||||
API.getStats()
|
||||
API.getStats(),
|
||||
API.getSecuboxLogs()
|
||||
]);
|
||||
},
|
||||
|
||||
@ -18,163 +25,162 @@ return view.extend({
|
||||
var netdataStatus = data[0] || {};
|
||||
var alarms = data[1] || {};
|
||||
var stats = data[2] || {};
|
||||
var logs = (data[3] && data[3].entries) || [];
|
||||
|
||||
var isRunning = netdataStatus.running || false;
|
||||
var netdataUrl = netdataStatus.url || 'http://127.0.0.1:19999';
|
||||
var alarmCount = this.countAlarms(alarms);
|
||||
|
||||
// Count active alarms
|
||||
var alarmCount = 0;
|
||||
if (alarms.alarms && typeof alarms.alarms === 'object') {
|
||||
Object.keys(alarms.alarms).forEach(function(key) {
|
||||
var alarm = alarms.alarms[key];
|
||||
if (alarm.status && alarm.status !== 'CLEAR') {
|
||||
alarmCount++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var view = E('div', { 'class': 'cbi-map' }, [
|
||||
E('h2', {}, _('Netdata Dashboard')),
|
||||
E('div', { 'class': 'cbi-map-descr' },
|
||||
_('Real-time system monitoring and performance metrics powered by Netdata.')),
|
||||
|
||||
// Control Panel
|
||||
E('div', { 'class': 'cbi-section', 'style': 'margin-bottom: 1em;' }, [
|
||||
E('div', { 'style': 'display: grid; grid-template-columns: 2fr 1fr; gap: 1em;' }, [
|
||||
// Status Card
|
||||
E('div', {
|
||||
'style': 'background: ' + (isRunning ? '#d4edda' : '#f8d7da') + '; border-left: 4px solid ' + (isRunning ? '#28a745' : '#dc3545') + '; padding: 1em; border-radius: 4px;'
|
||||
}, [
|
||||
E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center;' }, [
|
||||
E('div', {}, [
|
||||
E('div', { 'style': 'font-size: 0.9em; color: #666; margin-bottom: 0.25em;' }, _('Service Status')),
|
||||
E('div', { 'style': 'font-size: 1.5em; font-weight: bold; color: ' + (isRunning ? '#155724' : '#721c24') + ';' },
|
||||
isRunning ? _('RUNNING') : _('STOPPED'))
|
||||
]),
|
||||
E('div', { 'style': 'text-align: right;' }, [
|
||||
E('div', { 'style': 'font-size: 0.9em; color: #666; margin-bottom: 0.25em;' }, _('Version')),
|
||||
E('div', { 'style': 'font-size: 1.1em; font-weight: bold;' }, netdataStatus.version || 'Unknown')
|
||||
]),
|
||||
E('div', { 'style': 'text-align: right;' }, [
|
||||
E('div', { 'style': 'font-size: 0.9em; color: #666; margin-bottom: 0.25em;' }, _('Port')),
|
||||
E('div', { 'style': 'font-size: 1.1em; font-weight: bold;' }, (netdataStatus.port || 19999).toString())
|
||||
])
|
||||
])
|
||||
]),
|
||||
|
||||
// Control Buttons
|
||||
E('div', { 'style': 'display: flex; flex-direction: column; gap: 0.5em;' }, [
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'click': L.bind(this.handleStart, this),
|
||||
'disabled': isRunning,
|
||||
'style': 'flex: 1;'
|
||||
}, _('Start Netdata')),
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-reset',
|
||||
'click': L.bind(this.handleRestart, this),
|
||||
'disabled': !isRunning,
|
||||
'style': 'flex: 1;'
|
||||
}, _('Restart Netdata')),
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-negative',
|
||||
'click': L.bind(this.handleStop, this),
|
||||
'disabled': !isRunning,
|
||||
'style': 'flex: 1;'
|
||||
}, _('Stop Netdata'))
|
||||
])
|
||||
])
|
||||
]),
|
||||
|
||||
// Alarms Card
|
||||
alarmCount > 0 ? E('div', {
|
||||
'class': 'cbi-section',
|
||||
'style': 'background: #fff3cd; border-left: 4px solid #ffc107; padding: 1em; margin-bottom: 1em;'
|
||||
}, [
|
||||
E('div', { 'style': 'display: flex; align-items: center; gap: 1em;' }, [
|
||||
E('div', { 'style': 'font-size: 2em;' }, '⚠️'),
|
||||
E('div', { 'style': 'flex: 1;' }, [
|
||||
E('strong', {}, _('Active Alarms: %d').format(alarmCount)),
|
||||
E('p', { 'style': 'margin: 0.25em 0 0 0; color: #666;' },
|
||||
_('Netdata has detected %d active alarm(s). Check the dashboard for details.').format(alarmCount))
|
||||
]),
|
||||
E('a', {
|
||||
'href': netdataUrl + '#menu_alarms',
|
||||
'target': '_blank',
|
||||
'class': 'cbi-button cbi-button-action'
|
||||
}, _('View Alarms'))
|
||||
])
|
||||
]) : null,
|
||||
|
||||
// Quick Stats Preview
|
||||
E('div', { 'class': 'cbi-section', 'style': 'margin-bottom: 1em;' }, [
|
||||
E('h3', { 'style': 'margin-top: 0;' }, _('Quick Stats')),
|
||||
E('div', { 'style': 'display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1em;' }, [
|
||||
this.renderStatCard(_('CPU'), stats.cpu_percent + '%', '#0088cc'),
|
||||
this.renderStatCard(_('Memory'), stats.memory_percent + '%', '#17a2b8'),
|
||||
this.renderStatCard(_('Disk'), stats.disk_percent + '%', '#6610f2'),
|
||||
this.renderStatCard(_('Load'), stats.load || '0.00', '#e83e8c'),
|
||||
this.renderStatCard(_('Temp'), stats.temperature + '°C', '#fd7e14'),
|
||||
this.renderStatCard(_('Uptime'), API.formatUptime(stats.uptime || 0), '#28a745')
|
||||
])
|
||||
]),
|
||||
|
||||
// Netdata Dashboard Iframe
|
||||
isRunning ? E('div', { 'class': 'cbi-section' }, [
|
||||
E('h3', { 'style': 'margin-top: 0;' }, _('Netdata Real-Time Dashboard')),
|
||||
E('div', {
|
||||
'style': 'position: relative; width: 100%; height: 0; padding-bottom: 75%; background: #f5f5f5; border-radius: 4px; overflow: hidden;'
|
||||
}, [
|
||||
E('iframe', {
|
||||
'src': netdataUrl,
|
||||
'style': 'position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none;',
|
||||
'frameborder': '0',
|
||||
'allow': 'fullscreen'
|
||||
})
|
||||
]),
|
||||
E('div', { 'style': 'margin-top: 1em; padding: 0.75em; background: #e8f4f8; border-radius: 4px;' }, [
|
||||
E('strong', {}, _('Tip:')),
|
||||
' ',
|
||||
_('Use the Netdata interface above for detailed real-time monitoring. Click '),
|
||||
E('a', { 'href': netdataUrl, 'target': '_blank' }, _('here')),
|
||||
_(' to open in a new window.')
|
||||
])
|
||||
]) : E('div', { 'class': 'cbi-section' }, [
|
||||
E('div', {
|
||||
'style': 'text-align: center; padding: 3em; background: #f8d7da; border-radius: 4px; border-left: 4px solid #dc3545;'
|
||||
}, [
|
||||
E('div', { 'style': 'font-size: 3em; margin-bottom: 0.5em;' }, '⚠️'),
|
||||
E('h3', {}, _('Netdata is not running')),
|
||||
E('p', { 'style': 'color: #666; margin-bottom: 1.5em;' },
|
||||
_('Start the Netdata service to access real-time monitoring dashboards.')),
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'style': 'font-size: 1.1em; padding: 0.75em 2em;',
|
||||
'click': L.bind(this.handleStart, this)
|
||||
}, _('Start Netdata Now'))
|
||||
])
|
||||
])
|
||||
var view = E('div', { 'class': 'netdata-dashboard secubox-netdata' }, [
|
||||
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
|
||||
E('link', { 'rel': 'stylesheet', 'href': L.resource('netdata-dashboard/dashboard.css') }),
|
||||
this.renderHeader(netdataStatus, stats),
|
||||
this.renderControls(isRunning),
|
||||
this.renderQuickStats(stats),
|
||||
this.renderAlarmCard(alarmCount, netdataUrl),
|
||||
this.renderLogCard(logs),
|
||||
this.renderEmbed(isRunning, netdataUrl)
|
||||
]);
|
||||
|
||||
// Setup auto-refresh
|
||||
poll.add(L.bind(function() {
|
||||
return Promise.all([
|
||||
API.getNetdataStatus(),
|
||||
API.getStats()
|
||||
API.getStats(),
|
||||
API.getSecuboxLogs()
|
||||
]).then(L.bind(function(refreshData) {
|
||||
// Could update stats display here
|
||||
// Update quick stats/logs in place if needed
|
||||
}, this));
|
||||
}, this), 5);
|
||||
|
||||
return view;
|
||||
},
|
||||
|
||||
renderStatCard: function(label, value, color) {
|
||||
return E('div', {
|
||||
'style': 'background: white; border-left: 4px solid ' + color + '; padding: 1em; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);'
|
||||
}, [
|
||||
E('div', { 'style': 'font-size: 0.85em; color: #666; margin-bottom: 0.25em;' }, label),
|
||||
E('div', { 'style': 'font-size: 1.5em; font-weight: bold; color: ' + color + ';' }, value)
|
||||
countAlarms: function(alarms) {
|
||||
var count = 0;
|
||||
if (alarms.alarms && typeof alarms.alarms === 'object') {
|
||||
Object.keys(alarms.alarms).forEach(function(key) {
|
||||
var alarm = alarms.alarms[key];
|
||||
if (alarm.status && alarm.status !== 'CLEAR')
|
||||
count++;
|
||||
});
|
||||
}
|
||||
return count;
|
||||
},
|
||||
|
||||
renderHeader: function(status, stats) {
|
||||
return E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [
|
||||
E('div', {}, [
|
||||
E('h2', { 'class': 'sh-page-title' }, [
|
||||
E('span', { 'class': 'sh-page-title-icon' }, '📊'),
|
||||
_('Netdata Monitoring')
|
||||
]),
|
||||
E('p', { 'class': 'sh-page-subtitle' },
|
||||
_('Real-time analytics for CPU, memory, disk, and services.'))
|
||||
]),
|
||||
E('div', { 'class': 'sh-header-meta' }, [
|
||||
this.renderHeaderChip(_('Status'), status.running ? _('Online') : _('Offline'),
|
||||
status.running ? 'success' : 'warn'),
|
||||
this.renderHeaderChip(_('Version'), status.version || _('Unknown')),
|
||||
this.renderHeaderChip(_('Uptime'), API.formatUptime(stats.uptime || 0))
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
renderHeaderChip: function(label, value, tone) {
|
||||
return E('div', { 'class': 'sh-header-chip' + (tone ? ' ' + tone : '') }, [
|
||||
E('span', { 'class': 'sh-chip-label' }, label),
|
||||
E('strong', {}, value)
|
||||
]);
|
||||
},
|
||||
|
||||
renderControls: function(isRunning) {
|
||||
return E('div', { 'class': 'nd-control-bar' }, [
|
||||
E('button', {
|
||||
'class': 'sh-btn-primary',
|
||||
'click': L.bind(this.handleStart, this),
|
||||
'disabled': isRunning
|
||||
}, ['▶️ ', _('Start')]),
|
||||
E('button', {
|
||||
'class': 'sh-btn-secondary',
|
||||
'click': L.bind(this.handleRestart, this),
|
||||
'disabled': !isRunning
|
||||
}, ['🔁 ', _('Restart')]),
|
||||
E('button', {
|
||||
'class': 'sh-btn-secondary',
|
||||
'click': L.bind(this.handleStop, this),
|
||||
'disabled': !isRunning
|
||||
}, ['⏹ ', _('Stop')])
|
||||
]);
|
||||
},
|
||||
|
||||
renderQuickStats: function(stats) {
|
||||
return E('div', { 'class': 'nd-quick-stats' }, [
|
||||
this.renderStatCard(_('CPU'), (stats.cpu_percent || 0) + '%'),
|
||||
this.renderStatCard(_('Memory'), (stats.memory_percent || 0) + '%'),
|
||||
this.renderStatCard(_('Disk'), (stats.disk_percent || 0) + '%'),
|
||||
this.renderStatCard(_('Load'), stats.load || '0.00'),
|
||||
this.renderStatCard(_('Temp'), (stats.temperature || 0) + '°C'),
|
||||
this.renderStatCard(_('Clients'), stats.clients || 0)
|
||||
]);
|
||||
},
|
||||
|
||||
renderStatCard: function(label, value) {
|
||||
return E('div', { 'class': 'nd-stat-card' }, [
|
||||
E('span', { 'class': 'nd-stat-label' }, label),
|
||||
E('strong', { 'class': 'nd-stat-value' }, value)
|
||||
]);
|
||||
},
|
||||
|
||||
renderAlarmCard: function(count, url) {
|
||||
return E('div', { 'class': 'nd-card nd-alarms' }, [
|
||||
E('div', { 'class': 'nd-card-header' }, [
|
||||
E('div', { 'class': 'nd-card-title' }, ['🚨', _('Netdata alarms')]),
|
||||
E('span', { 'class': 'nd-chip' + (count > 0 ? ' danger' : '') }, count + ' ' + _('active'))
|
||||
]),
|
||||
count > 0 ? E('p', { 'class': 'nd-card-text' },
|
||||
_('Netdata reports %d active alarms. Open the dashboard to investigate.').format(count)) :
|
||||
E('p', { 'class': 'nd-card-text' }, _('No active alarms detected.')),
|
||||
E('div', { 'class': 'nd-card-actions' }, [
|
||||
E('a', { 'href': url + '#menu_alarms', 'class': 'sh-btn-secondary', 'target': '_blank' }, ['🔍 ', _('View alarms')])
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
renderLogCard: function(entries) {
|
||||
return E('div', { 'class': 'nd-card nd-logs' }, [
|
||||
E('div', { 'class': 'nd-card-header' }, [
|
||||
E('div', { 'class': 'nd-card-title' }, ['🗒️', _('SecuBox Log Tail')]),
|
||||
E('button', {
|
||||
'class': 'sh-btn-secondary',
|
||||
'click': L.bind(this.handleSnapshot, this)
|
||||
}, ['📎 ', _('Add snapshot')])
|
||||
]),
|
||||
entries && entries.length ? E('pre', { 'class': 'nd-log-output' },
|
||||
entries.join('\n')) : E('p', { 'class': 'nd-card-text' }, _('Log file empty.'))
|
||||
]);
|
||||
},
|
||||
|
||||
renderEmbed: function(isRunning, url) {
|
||||
if (!isRunning) {
|
||||
return E('div', { 'class': 'nd-card nd-embed off' }, [
|
||||
E('div', { 'class': 'nd-card-title' }, ['⚠️ ', _('Netdata is offline')]),
|
||||
E('p', { 'class': 'nd-card-text' }, _('Start the service to access real-time charts.'))
|
||||
]);
|
||||
}
|
||||
|
||||
return E('div', { 'class': 'nd-card nd-embed' }, [
|
||||
E('div', { 'class': 'nd-card-header' }, [
|
||||
E('div', { 'class': 'nd-card-title' }, ['📈', _('Live dashboard')]),
|
||||
E('a', { 'href': url, 'target': '_blank', 'class': 'sh-btn-secondary' }, _('Open in tab'))
|
||||
]),
|
||||
E('div', { 'class': 'nd-iframe-wrapper' }, [
|
||||
E('iframe', {
|
||||
'src': url,
|
||||
'frameborder': '0',
|
||||
'allow': 'fullscreen'
|
||||
})
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
@ -268,6 +274,25 @@ return view.extend({
|
||||
});
|
||||
},
|
||||
|
||||
handleSnapshot: function() {
|
||||
var self = this;
|
||||
ui.showModal(_('Collecting snapshot'), [
|
||||
E('p', {}, _('Aggregating dmesg and logread into SecuBox log…')),
|
||||
E('div', { 'class': 'spinning' })
|
||||
]);
|
||||
API.collectDebugSnapshot().then(function(result) {
|
||||
ui.hideModal();
|
||||
if (result && result.success) {
|
||||
ui.addNotification(null, E('p', {}, _('Snapshot appended to /var/log/seccubox.log.')), 'info');
|
||||
} else {
|
||||
ui.addNotification(null, E('p', {}, (result && result.error) || _('Failed to collect snapshot.')), 'error');
|
||||
}
|
||||
}).catch(function(err) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', {}, err.message || err), 'error');
|
||||
});
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
|
||||
@ -6,6 +6,13 @@
|
||||
. /lib/functions.sh
|
||||
. /usr/share/libubox/jshn.sh
|
||||
|
||||
SECCUBOX_LOG="/usr/sbin/secubox-log"
|
||||
|
||||
secubox_log() {
|
||||
[ -x "$SECCUBOX_LOG" ] || return
|
||||
"$SECCUBOX_LOG" --tag "netdata" --message "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Get CPU statistics
|
||||
get_cpu() {
|
||||
json_init
|
||||
@ -525,6 +532,7 @@ restart_netdata() {
|
||||
if pgrep -x netdata >/dev/null 2>&1; then
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Netdata restarted successfully"
|
||||
secubox_log "Netdata service restarted"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Failed to start Netdata"
|
||||
@ -551,6 +559,7 @@ start_netdata() {
|
||||
if pgrep -x netdata >/dev/null 2>&1; then
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Netdata started successfully"
|
||||
secubox_log "Netdata service started"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Failed to start Netdata"
|
||||
@ -577,6 +586,7 @@ stop_netdata() {
|
||||
if ! pgrep -x netdata >/dev/null 2>&1; then
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Netdata stopped successfully"
|
||||
secubox_log "Netdata service stopped"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Failed to stop Netdata"
|
||||
@ -589,10 +599,35 @@ stop_netdata() {
|
||||
json_dump
|
||||
}
|
||||
|
||||
seccubox_logs() {
|
||||
json_init
|
||||
json_add_array "entries"
|
||||
if [ -f /var/log/seccubox.log ]; then
|
||||
tail -n 80 /var/log/seccubox.log | while IFS= read -r line; do
|
||||
json_add_string "" "$line"
|
||||
done
|
||||
fi
|
||||
json_close_array
|
||||
json_dump
|
||||
}
|
||||
|
||||
collect_debug() {
|
||||
json_init
|
||||
if [ -x "$SECCUBOX_LOG" ]; then
|
||||
"$SECCUBOX_LOG" --snapshot >/dev/null 2>&1
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Snapshot collected to /var/log/seccubox.log"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "secubox-log helper not found"
|
||||
fi
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Main dispatcher
|
||||
case "$1" in
|
||||
list)
|
||||
echo '{"stats":{},"cpu":{},"memory":{},"disk":{},"network":{},"processes":{},"sensors":{},"system":{},"netdata_status":{},"netdata_alarms":{},"netdata_info":{},"restart_netdata":{},"start_netdata":{},"stop_netdata":{}}'
|
||||
echo '{"stats":{},"cpu":{},"memory":{},"disk":{},"network":{},"processes":{},"sensors":{},"system":{},"netdata_status":{},"netdata_alarms":{},"netdata_info":{},"restart_netdata":{},"start_netdata":{},"stop_netdata":{},"seccubox_logs":{},"collect_debug":{}}'
|
||||
;;
|
||||
call)
|
||||
case "$2" in
|
||||
@ -638,6 +673,12 @@ case "$1" in
|
||||
stop_netdata)
|
||||
stop_netdata
|
||||
;;
|
||||
seccubox_logs)
|
||||
seccubox_logs
|
||||
;;
|
||||
collect_debug)
|
||||
collect_debug
|
||||
;;
|
||||
*)
|
||||
echo '{"error": "Unknown method"}'
|
||||
;;
|
||||
|
||||
@ -46,6 +46,18 @@ var callStats = rpc.declare({
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callSecuboxLogs = rpc.declare({
|
||||
object: 'luci.netifyd-dashboard',
|
||||
method: 'seccubox_logs',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callCollectDebug = rpc.declare({
|
||||
object: 'luci.netifyd-dashboard',
|
||||
method: 'collect_debug',
|
||||
expect: { success: false }
|
||||
});
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (bytes === 0) return '0 B';
|
||||
var k = 1024;
|
||||
@ -61,6 +73,8 @@ return baseclass.extend({
|
||||
getHosts: callHosts,
|
||||
getProtocols: callProtocols,
|
||||
getStats: callStats,
|
||||
getSecuboxLogs: callSecuboxLogs,
|
||||
collectDebugSnapshot: callCollectDebug,
|
||||
formatBytes: formatBytes,
|
||||
|
||||
// Aggregate function for overview page
|
||||
|
||||
@ -368,6 +368,48 @@
|
||||
color: var(--nf-text-primary);
|
||||
}
|
||||
|
||||
.nf-log-card {
|
||||
background: var(--nf-bg-secondary);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 18px;
|
||||
padding: 20px;
|
||||
margin-top: 20px;
|
||||
box-shadow: 0 18px 30px rgba(2, 6, 23, 0.45);
|
||||
}
|
||||
|
||||
.nf-log-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.nf-log-btn {
|
||||
background: transparent;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
padding: 6px 14px;
|
||||
border-radius: 999px;
|
||||
color: var(--nf-text-secondary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nf-log-output {
|
||||
background: #020617;
|
||||
color: #9efc6a;
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
font-family: var(--nf-font-mono);
|
||||
font-size: 12px;
|
||||
max-height: 220px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.nf-log-empty {
|
||||
color: var(--nf-text-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* Flow Table */
|
||||
.nf-table-container {
|
||||
overflow-x: auto;
|
||||
|
||||
@ -9,7 +9,10 @@ return view.extend({
|
||||
title: _('Netifyd Dashboard'),
|
||||
|
||||
load: function() {
|
||||
return api.getAllData();
|
||||
return Promise.all([
|
||||
api.getAllData(),
|
||||
api.getSecuboxLogs()
|
||||
]);
|
||||
},
|
||||
|
||||
renderDonut: function(data, size) {
|
||||
@ -47,8 +50,10 @@ return view.extend({
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
render: function(payload) {
|
||||
var self = this;
|
||||
var data = payload[0] || {};
|
||||
var logEntries = (payload[1] && payload[1].entries) || [];
|
||||
var status = data.status || {};
|
||||
var stats = data.stats || {};
|
||||
var apps = (data.applications || {}).applications || [];
|
||||
@ -63,7 +68,6 @@ return view.extend({
|
||||
{ name: 'UDP', value: udpFlows },
|
||||
{ name: 'Other', value: Math.max(0, totalFlows - tcpFlows - udpFlows) }
|
||||
].filter(function(p) { return p.value > 0; });
|
||||
|
||||
var topApps = apps.slice(0, 6);
|
||||
var maxAppBytes = topApps.length > 0 ? Math.max.apply(null, topApps.map(function(a) { return a.bytes; })) : 1;
|
||||
|
||||
@ -186,8 +190,10 @@ return view.extend({
|
||||
)
|
||||
])
|
||||
])
|
||||
])
|
||||
]);
|
||||
]),
|
||||
|
||||
this.renderLogCard(logEntries)
|
||||
]);
|
||||
|
||||
// Include CSS
|
||||
var cssLink = E('link', { 'rel': 'stylesheet', 'href': L.resource('netifyd-dashboard/dashboard.css') });
|
||||
@ -196,6 +202,39 @@ return view.extend({
|
||||
return view;
|
||||
},
|
||||
|
||||
renderLogCard: function(entries) {
|
||||
return E('div', { 'class': 'nf-log-card' }, [
|
||||
E('div', { 'class': 'nf-log-header' }, [
|
||||
E('strong', {}, _('SecuBox log tail')),
|
||||
E('button', {
|
||||
'class': 'nf-log-btn',
|
||||
'click': L.bind(this.handleSnapshot, this)
|
||||
}, _('Snapshot'))
|
||||
]),
|
||||
entries && entries.length ?
|
||||
E('pre', { 'class': 'nf-log-output' }, entries.join('\n')) :
|
||||
E('p', { 'class': 'nf-log-empty' }, _('Log file empty'))
|
||||
]);
|
||||
},
|
||||
|
||||
handleSnapshot: function() {
|
||||
ui.showModal(_('Collecting snapshot'), [
|
||||
E('p', {}, _('Aggregating dmesg + logread into SecuBox log…')),
|
||||
E('div', { 'class': 'spinning' })
|
||||
]);
|
||||
api.collectDebugSnapshot().then(function(result) {
|
||||
ui.hideModal();
|
||||
if (result && result.success) {
|
||||
ui.addNotification(null, E('p', {}, _('Snapshot appended to /var/log/seccubox.log')), 'info');
|
||||
} else {
|
||||
ui.addNotification(null, E('p', {}, (result && result.error) || _('Snapshot failed')), 'error');
|
||||
}
|
||||
}).catch(function(err) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', {}, err.message || err), 'error');
|
||||
});
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
|
||||
@ -6,6 +6,13 @@
|
||||
. /lib/functions.sh
|
||||
. /usr/share/libubox/jshn.sh
|
||||
|
||||
SECCUBOX_LOG="/usr/sbin/secubox-log"
|
||||
|
||||
secubox_log() {
|
||||
[ -x "$SECCUBOX_LOG" ] || return
|
||||
"$SECCUBOX_LOG" --tag "netifyd" --message "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
NETIFYD_SOCKET="/var/run/netifyd/netifyd.sock"
|
||||
NETIFYD_STATUS="/var/run/netifyd/status.json"
|
||||
NETIFYD_FLOWS="/var/run/netifyd/flows.json"
|
||||
@ -265,6 +272,31 @@ get_devices() {
|
||||
json_dump
|
||||
}
|
||||
|
||||
seccubox_logs() {
|
||||
json_init
|
||||
json_add_array "entries"
|
||||
if [ -f /var/log/seccubox.log ]; then
|
||||
tail -n 80 /var/log/seccubox.log | while IFS= read -r line; do
|
||||
json_add_string "" "$line"
|
||||
done
|
||||
fi
|
||||
json_close_array
|
||||
json_dump
|
||||
}
|
||||
|
||||
collect_debug() {
|
||||
json_init
|
||||
if [ -x "$SECCUBOX_LOG" ]; then
|
||||
"$SECCUBOX_LOG" --snapshot >/dev/null 2>&1
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Snapshot stored in /var/log/seccubox.log"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "secubox-log helper not found"
|
||||
fi
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Get overall statistics
|
||||
get_stats() {
|
||||
json_init
|
||||
@ -463,7 +495,7 @@ get_dns_queries() {
|
||||
# Main dispatcher
|
||||
case "$1" in
|
||||
list)
|
||||
echo '{"status":{},"flows":{},"applications":{},"protocols":{},"devices":{},"stats":{},"risks":{},"category_bandwidth":{},"top_talkers":{},"dns_queries":{}}'
|
||||
echo '{"status":{},"flows":{},"applications":{},"protocols":{},"devices":{},"stats":{},"risks":{},"category_bandwidth":{},"top_talkers":{},"dns_queries":{},"seccubox_logs":{},"collect_debug":{}}'
|
||||
;;
|
||||
call)
|
||||
case "$2" in
|
||||
@ -497,6 +529,12 @@ case "$1" in
|
||||
dns_queries)
|
||||
get_dns_queries
|
||||
;;
|
||||
seccubox_logs)
|
||||
seccubox_logs
|
||||
;;
|
||||
collect_debug)
|
||||
collect_debug
|
||||
;;
|
||||
*)
|
||||
echo '{"error": "Unknown method"}'
|
||||
;;
|
||||
|
||||
87
luci-app-secubox/root/usr/sbin/secubox-log
Executable file
87
luci-app-secubox/root/usr/sbin/secubox-log
Executable file
@ -0,0 +1,87 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# SecuBox Log Aggregator / Logger
|
||||
# Central log file: /var/log/seccubox.log
|
||||
|
||||
LOG_FILE="/var/log/seccubox.log"
|
||||
TAG="secubox"
|
||||
MESSAGE=""
|
||||
PAYLOAD=""
|
||||
TAIL_COUNT=""
|
||||
MODE="append"
|
||||
|
||||
ensure_log() {
|
||||
local dir
|
||||
dir="$(dirname "$LOG_FILE")"
|
||||
[ -d "$dir" ] || mkdir -p "$dir"
|
||||
touch "$LOG_FILE"
|
||||
}
|
||||
|
||||
write_entry() {
|
||||
ensure_log
|
||||
printf '%s [%s] %s\n' "$(date -Iseconds)" "$TAG" "$MESSAGE" >> "$LOG_FILE"
|
||||
[ -n "$PAYLOAD" ] && printf '%s\n' "$PAYLOAD" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
write_snapshot() {
|
||||
ensure_log
|
||||
{
|
||||
printf '===== SNAPSHOT %s =====\n' "$(date -Iseconds)"
|
||||
printf '--- DMESG (tail -n 200) ---\n'
|
||||
dmesg | tail -n 200
|
||||
printf '--- LOGREAD (tail -n 200) ---\n'
|
||||
logread 2>/dev/null | tail -n 200
|
||||
printf '===== END SNAPSHOT =====\n'
|
||||
} >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
tail_log() {
|
||||
ensure_log
|
||||
if [ -n "$TAIL_COUNT" ]; then
|
||||
tail -n "$TAIL_COUNT" "$LOG_FILE"
|
||||
else
|
||||
tail "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--tag)
|
||||
TAG="$2"; shift 2;;
|
||||
--message|-m)
|
||||
MESSAGE="$2"; shift 2;;
|
||||
--payload|-p)
|
||||
PAYLOAD="$2"; shift 2;;
|
||||
--snapshot)
|
||||
MODE="snapshot"; shift;;
|
||||
--tail)
|
||||
MODE="tail"; TAIL_COUNT="$2"; shift 2;;
|
||||
--tail-all)
|
||||
MODE="tail"; TAIL_COUNT=""; shift;;
|
||||
-h|--help)
|
||||
cat <<'EOF'
|
||||
secubox-log --tag TAG --message MSG [--payload TEXT]
|
||||
secubox-log --snapshot # append dmesg+logread snapshot
|
||||
secubox-log --tail [N] # print last N lines (default 10)
|
||||
EOF
|
||||
exit 0;;
|
||||
*)
|
||||
shift;;
|
||||
esac
|
||||
done
|
||||
|
||||
case "$MODE" in
|
||||
snapshot)
|
||||
write_snapshot
|
||||
;;
|
||||
tail)
|
||||
tail_log
|
||||
;;
|
||||
*)
|
||||
if [ -z "$MESSAGE" ]; then
|
||||
echo "Usage: secubox-log --message 'text' [--tag tag]" >&2
|
||||
exit 1
|
||||
fi
|
||||
write_entry
|
||||
;;
|
||||
esac
|
||||
@ -120,6 +120,25 @@ opkg install /tmp/luci-app-system-hub*.ipk
|
||||
/etc/init.d/rpcd restart
|
||||
```
|
||||
|
||||
### Logging & Debug Utilities
|
||||
|
||||
#### secubox-log.sh
|
||||
|
||||
Centralized logger/aggregator for SecuBox modules. Appends tagged events to `/var/log/seccubox.log`, captures snapshots that merge `dmesg` + `logread`, and can tail the aggregated file for troubleshooting.
|
||||
|
||||
```
|
||||
# Append a message
|
||||
secubox-log.sh --tag netdata --message "Netdata restarted"
|
||||
|
||||
# Add a snapshot with dmesg/logread tail
|
||||
secubox-log.sh --snapshot
|
||||
|
||||
# Tail the aggregated log
|
||||
secubox-log.sh --tail 100
|
||||
```
|
||||
|
||||
The script is also installed on the router as `/usr/sbin/secubox-log` (via `luci-app-secubox`) so LuCI modules can log lifecycle events and collect debug bundles.
|
||||
|
||||
**Example Workflow - Firmware Building:**
|
||||
```bash
|
||||
# 1. Build firmware for MOCHAbin with SecuBox pre-installed
|
||||
|
||||
91
secubox-tools/secubox-log.sh
Executable file
91
secubox-tools/secubox-log.sh
Executable file
@ -0,0 +1,91 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# SecuBox Log Aggregator / Logger
|
||||
# Usage:
|
||||
# secubox-log.sh --tag netdata --message "Netdata started"
|
||||
# secubox-log.sh --snapshot # append dmesg + logread snapshot
|
||||
# secubox-log.sh --tail 100 # print last 100 lines
|
||||
#
|
||||
|
||||
LOG_FILE="/var/log/seccubox.log"
|
||||
TAG="secubox"
|
||||
MESSAGE=""
|
||||
PAYLOAD=""
|
||||
TAIL_COUNT=""
|
||||
MODE="append"
|
||||
|
||||
ensure_log() {
|
||||
local dir
|
||||
dir="$(dirname "$LOG_FILE")"
|
||||
[ -d "$dir" ] || mkdir -p "$dir"
|
||||
touch "$LOG_FILE"
|
||||
}
|
||||
|
||||
write_entry() {
|
||||
ensure_log
|
||||
printf '%s [%s] %s\n' "$(date -Iseconds)" "$TAG" "$MESSAGE" >> "$LOG_FILE"
|
||||
[ -n "$PAYLOAD" ] && printf '%s\n' "$PAYLOAD" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
write_snapshot() {
|
||||
ensure_log
|
||||
{
|
||||
printf '===== SNAPSHOT %s =====\n' "$(date -Iseconds)"
|
||||
printf '--- DMESG (tail -n 200) ---\n'
|
||||
dmesg | tail -n 200
|
||||
printf '--- LOGREAD (tail -n 200) ---\n'
|
||||
logread 2>/dev/null | tail -n 200
|
||||
printf '===== END SNAPSHOT =====\n'
|
||||
} >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
tail_log() {
|
||||
ensure_log
|
||||
if [ -n "$TAIL_COUNT" ]; then
|
||||
tail -n "$TAIL_COUNT" "$LOG_FILE"
|
||||
else
|
||||
tail "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--tag)
|
||||
TAG="$2"; shift 2;;
|
||||
--message|-m)
|
||||
MESSAGE="$2"; shift 2;;
|
||||
--payload|-p)
|
||||
PAYLOAD="$2"; shift 2;;
|
||||
--snapshot)
|
||||
MODE="snapshot"; shift;;
|
||||
--tail)
|
||||
MODE="tail"; TAIL_COUNT="$2"; shift 2;;
|
||||
--tail-all)
|
||||
MODE="tail"; TAIL_COUNT=""; shift;;
|
||||
-h|--help)
|
||||
cat <<'EOF'
|
||||
secubox-log.sh --tag TAG --message MSG [--payload TEXT]
|
||||
secubox-log.sh --snapshot # append dmesg+logread snapshot
|
||||
secubox-log.sh --tail [N] # print last N lines (default 10)
|
||||
EOF
|
||||
exit 0;;
|
||||
*)
|
||||
shift;;
|
||||
esac
|
||||
done
|
||||
|
||||
case "$MODE" in
|
||||
snapshot)
|
||||
write_snapshot
|
||||
;;
|
||||
tail)
|
||||
tail_log
|
||||
;;
|
||||
*)
|
||||
if [ -z "$MESSAGE" ]; then
|
||||
echo "Usage: $0 --message 'text' [--tag tag]" >&2
|
||||
exit 1
|
||||
fi
|
||||
write_entry
|
||||
;;
|
||||
esac
|
||||
Loading…
Reference in New Issue
Block a user