Add device-side log file, remote terminal, and streaming error reporting

IPTV.py now writes logs to a rotating file and forwards critical VLC
playback errors (start timeout, MediaPlayerEncounteredError) to the
server instead of losing everything to unredirected print() calls.
The web UI's device menu gets a working Terminal (to run diagnostic
commands like tail on the log) and a Recent errors view backed by a
capped last_errors list on each device document, keeping DB volume
bounded to the last 5 entries per device.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
ubuntu
2026-08-16 22:09:51 +00:00
co-authored by Claude Sonnet 5
parent 10fd5106f8
commit 3eb0d5e8cd
3 changed files with 230 additions and 60 deletions
+112 -4
View File
@@ -961,6 +961,8 @@
<li><a onclick="openSnapshotIntervalSettings()" class="dropdown-item" href="#">Snapshot interval</a></li>
<li><a onclick="openGeminiKeySettings()" class="dropdown-item" href="#">Gemini API Key</a></li>
<li><a onclick="openTerminal()" class="dropdown-item" href="#">Terminal</a></li>
<li><a onclick="viewDeviceLogs()" class="dropdown-item" href="#">View logs</a></li>
<li><a onclick="openRecentErrors()" class="dropdown-item" href="#">Recent errors</a></li>
<li><a onclick="takeSnapshot()" class="dropdown-item" href="#">Take Snapshot</a></li>
<li><a onclick="getIP()" class="dropdown-item" href="#">Get IP</a></li>
<li class="dropdown-submenu">
@@ -1152,19 +1154,35 @@
<!-- ZONE MODAL TERMINAL -->
<div class="modal fade" id="modalTerminal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-sm" role="document">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Terminal </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="input-group mb-3">
<span class="input-group-text" id="basic-addon1">Command</span>
<input type="text" class="form-control" placeholder="Text Command" aria-label="Command" aria-describedby="basic-addon1" id="txt_command">
</div>
<button type="button" class="btn btn-primary">Send command</button>
<div class="alert alert-secondary" role="alert" id="txt_result_command">
</div>
<button type="button" class="btn btn-primary" id="btn_send_terminal_command">Send command</button>
<button type="button" class="btn btn-outline-secondary" id="btn_view_device_logs">View streaming logs</button>
<pre class="alert alert-secondary mt-3 mb-0" role="alert" id="txt_result_command" style="max-height: 320px; overflow-y: auto; white-space: pre-wrap; text-align: left;"></pre>
</div>
</div>
</div>
</div>
<!-- ZONE MODAL RECENT ERRORS -->
<div class="modal fade" id="modalRecentErrors" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Recent streaming errors</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="recent_errors_list" style="max-height: 320px; overflow-y: auto;"></div>
</div>
</div>
</div>
@@ -1511,6 +1529,7 @@
var playlistImportProgress = 0
var playlistImportInFlight = false
var selectedDeviceSnapshotIntervalSeconds = 10
var currentSelectedDeviceData = null
var socket = io('https://iptv.mrk.ovh', {
secure: true,
@@ -1549,6 +1568,13 @@
updateListDevices()
}
// RECEPTION TERMINAL COMMAND OUTPUT
if (data['typeCommand'] == 'display_command_result') {
var resultBox = document.getElementById('txt_result_command');
$(resultBox).text($(resultBox).text() + data['payload']);
resultBox.scrollTop = resultBox.scrollHeight;
}
// PROGRESS BAR DISPLAY
if (data['typeCommand'] == 'progress_analysis') {
$('#progress-container').show()
@@ -2661,6 +2687,7 @@
if (isCurrentDevice) {
currentDeviceUpdated = true;
selectedDeviceSnapshotData = dataJSON[i];
currentSelectedDeviceData = dataJSON[i];
$('#sidDevice').val( dataJSON[i].sid)
var data_running = dataJSON[i].data_running || {}
@@ -2684,6 +2711,7 @@
}
if (!currentDeviceUpdated) {
currentSelectedDeviceData = null;
updateCurrentDeviceStream({label: 'No data', className: 'bg-secondary'}, false);
updateSelectedDeviceSnapshot(null, false);
updateAutoSnapshotToggle(null);
@@ -3170,6 +3198,86 @@
});
});
// --- TERMINAL / DEVICE LOGS ---
var DEVICE_LOG_FILE_PATH = '/home/pi/Documents/iptv.log';
function openTerminal() {
if ($('#sidDevice').val().length === 0 || $('#id_device').val().length === 0) {
$('#noDeviceModal').modal('show');
return;
}
$('#txt_command').val('');
$('#txt_result_command').text('');
$('#modalTerminal').modal('show');
}
function sendTerminalCommand(command) {
if ($('#sidDevice').val().length === 0 || $('#id_device').val().length === 0) {
$('#modalTerminal').modal('hide');
$('#noDeviceModal').modal('show');
return;
}
var cmd = (typeof command === 'string') ? command : $('#txt_command').val().trim();
if (!cmd) {
return;
}
$('#txt_command').val(cmd);
$('#txt_result_command').text('');
socket.emit('communication', {
fromSidDevice: $('#sidClient').val(),
toSidDevice: $('#sidDevice').val(),
typeCommand: 'command_line',
payload: cmd,
packet: 0
});
}
$('#btn_send_terminal_command').click(function () {
sendTerminalCommand();
});
$('#btn_view_device_logs').click(function () {
sendTerminalCommand('tail -n 200 ' + DEVICE_LOG_FILE_PATH);
});
function viewDeviceLogs() {
if ($('#sidDevice').val().length === 0 || $('#id_device').val().length === 0) {
$('#noDeviceModal').modal('show');
return;
}
$('#txt_result_command').text('');
$('#modalTerminal').modal('show');
sendTerminalCommand('tail -n 200 ' + DEVICE_LOG_FILE_PATH);
}
$('#txt_command').on('keypress', function (e) {
if (e.which === 13) {
sendTerminalCommand();
}
});
function openRecentErrors() {
if ($('#sidDevice').val().length === 0 || $('#id_device').val().length === 0) {
$('#noDeviceModal').modal('show');
return;
}
var errors = (currentSelectedDeviceData && currentSelectedDeviceData.last_errors) || [];
var html = '';
if (errors.length === 0) {
html = '<div class="text-muted">No recent errors.</div>';
} else {
errors.slice().reverse().forEach(function (err) {
var when = err.timestamp ? new Date(err.timestamp * 1000).toLocaleString() : '';
html += '<div class="alert alert-danger py-2 mb-2"><strong>' + when + '</strong><br>' + $('<div>').text(err.message).html() + '</div>';
});
}
$('#recent_errors_list').html(html);
$('#modalRecentErrors').modal('show');
}
// --- AI SEARCH ---
function openAiSearch() {
$('#aiSearchAlert').html('');