main
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
/**
|
||||
* Last-heard list (MMDVMHost mode) — last 20 unique transmissions across
|
||||
* every enabled mode (D-Star / DMR / YSF / P25 / NXDN / M17 / POCSAG).
|
||||
*
|
||||
* AJAX-loaded partial; refreshed every 1.5 seconds by /index.php in
|
||||
* MMDVMHost mode. Renders columns: time (UTC → local), mode, callsign,
|
||||
* target, source (RF / Net), duration, loss, BER. Callsign links use
|
||||
* the operator's chosen lookup service (RadioID or QRZ, from
|
||||
* /etc/pistar-css.ini's [Lookup] Service key) plus aprs.fi for D-Star
|
||||
* dPRS data.
|
||||
*
|
||||
* Data flow: relies on `$lastHeard` populated by mmdvmhost/functions.php
|
||||
* (which parses /var/log/pi-star/MMDVM-YYYY-MM-DD.log via shell
|
||||
* pipelines). This file just renders the array — see functions.php
|
||||
* for the parsing logic and log-line offset comments.
|
||||
*/
|
||||
|
||||
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . '/config/security_headers.php');
|
||||
// AJAX-loaded partial; the parent page (index.php) sets the full
|
||||
// security headers. setEmbeddableSecurityHeaders() ships the
|
||||
// non-frame-related security headers without locking frame-
|
||||
// ancestors, so the partial can be loaded into the parent via
|
||||
// $.load(). Calling setSecurityHeaders() before this would emit
|
||||
// X-Frame-Options + frame-ancestors 'self', which makes the
|
||||
// embeddable variant a no-op (headers_sent() === true) — fixes
|
||||
// the historical bug where the wrong variant won.
|
||||
setEmbeddableSecurityHeaders();
|
||||
|
||||
include_once $_SERVER['DOCUMENT_ROOT'].'/config/config.php'; // MMDVMDash Config
|
||||
include_once $_SERVER['DOCUMENT_ROOT'].'/mmdvmhost/tools.php'; // MMDVMDash Tools
|
||||
include_once $_SERVER['DOCUMENT_ROOT'].'/mmdvmhost/functions.php'; // MMDVMDash Functions
|
||||
include_once $_SERVER['DOCUMENT_ROOT'].'/config/language.php'; // Translation Code
|
||||
|
||||
// Check if the config file exists
|
||||
if (file_exists('/etc/pistar-css.ini')) {
|
||||
// Use the values from the file
|
||||
$piStarCssFile = '/etc/pistar-css.ini';
|
||||
if (fopen($piStarCssFile,'r')) { $piStarCss = parse_ini_file($piStarCssFile, true); }
|
||||
|
||||
// Set the Values from the config file
|
||||
if (isset($piStarCss['Lookup']['Service'])) { $callsignLookupSvc = $piStarCss['Lookup']['Service']; } // Lookup Service "QRZ" or "RadioID"
|
||||
else { $callsignLookupSvc = "RadioID"; } // Set the default if its missing // Set the default if its missing
|
||||
} else {
|
||||
// Default values
|
||||
$callsignLookupSvc = "RadioID";
|
||||
}
|
||||
|
||||
// Safety net
|
||||
if (($callsignLookupSvc != "RadioID") && ($callsignLookupSvc != "QRZ")) { $callsignLookupSvc = "RadioID"; }
|
||||
|
||||
// Setup the URL(s)
|
||||
$idLookupUrl = "https://database.radioid.net/database/view?id=";
|
||||
if ($callsignLookupSvc == "RadioID") { $callsignLookupUrl = "https://database.radioid.net/database/view?callsign="; }
|
||||
if ($callsignLookupSvc == "QRZ") { $callsignLookupUrl = "https://www.qrz.com/db/"; }
|
||||
|
||||
?>
|
||||
<b><?php echo $lang['last_heard_list'];?></b>
|
||||
<table>
|
||||
<tr>
|
||||
<th><a class="tooltip" href="#"><?php echo $lang['time'];?> (<?php echo date('T')?>)<span><b>Time in <?php echo date('T')?> time zone</b></span></a></th>
|
||||
<th><a class="tooltip" href="#"><?php echo $lang['mode'];?><span><b>Transmitted Mode</b></span></a></th>
|
||||
<th style="min-width:14ch"><a class="tooltip" href="#"><?php echo $lang['callsign'];?><span><b>Callsign</b></span></a></th>
|
||||
<th><a class="tooltip" href="#"><?php echo $lang['target'];?><span><b>Target, D-Star Reflector, DMR Talk Group etc</b></span></a></th>
|
||||
<th><a class="tooltip" href="#"><?php echo $lang['src'];?><span><b>Received from source</b></span></a></th>
|
||||
<th><a class="tooltip" href="#"><?php echo $lang['dur'];?>(s)<span><b>Duration in Seconds</b></span></a></th>
|
||||
<th><a class="tooltip" href="#"><?php echo $lang['loss'];?><span><b>Packet Loss</b></span></a></th>
|
||||
<th><a class="tooltip" href="#"><?php echo $lang['ber'];?><span><b>Bit Error Rate</b></span></a></th>
|
||||
</tr>
|
||||
<?php
|
||||
$i = 0;
|
||||
for ($i = 0; ($i <= 19); $i++) { //Last 20 calls
|
||||
if (isset($lastHeard[$i])) {
|
||||
$listElem = $lastHeard[$i];
|
||||
if ( $listElem[2] ) {
|
||||
$utc_time = $listElem[0];
|
||||
$utc_tz = new DateTimeZone('UTC');
|
||||
$local_tz = new DateTimeZone(date_default_timezone_get ());
|
||||
$dt = new DateTime($utc_time, $utc_tz);
|
||||
$dt->setTimeZone($local_tz);
|
||||
$local_time = $dt->format('H:i:s M jS');
|
||||
|
||||
// Every value below comes from log-line parsing in
|
||||
// mmdvmhost/functions.php, which in turn parses log lines
|
||||
// produced by RF traffic. A station transmitting on RF
|
||||
// can put almost any byte sequence into the callsign or
|
||||
// target field; without escaping it lands in the
|
||||
// dashboard's HTML refresh every 1.5s. Normalise once
|
||||
// here so every echo below works on safe values.
|
||||
//
|
||||
// $modeHtml — "Slot 1" -> "TS1" cosmetic + escape
|
||||
// $cs — callsign (HTML-safe text)
|
||||
// $csUrl — callsign URL-encoded for href= path
|
||||
// $csSuffix — D-Star station ID after `/` (HTML-safe)
|
||||
// $tgt — target callsign / talkgroup (HTML-safe)
|
||||
// $src — source ("RF"/"Net"/etc.; HTML-safe)
|
||||
// $dur, $loss, $ber — numeric; HTML-safe defensively.
|
||||
//
|
||||
// The downstream str_replace ' '->' ' has to run
|
||||
// AFTER htmlspecialchars; the entity reference ` `
|
||||
// is intentional raw HTML output, not data.
|
||||
$modeHtml = htmlspecialchars(str_replace('Slot ', 'TS', $listElem[1]), ENT_QUOTES, 'UTF-8');
|
||||
$cs = htmlspecialchars((string)$listElem[2], ENT_QUOTES, 'UTF-8');
|
||||
$csUrl = rawurlencode((string)$listElem[2]);
|
||||
$csSuffix = htmlspecialchars((string)$listElem[3], ENT_QUOTES, 'UTF-8');
|
||||
// Target normalisation: if it's a single character left-pad
|
||||
// it to 8 spaces (cosmetic, matches legacy layout). The
|
||||
// visible value is HTML-escaped, then spaces become
|
||||
// non-breaking-space entities AFTER the escape so the
|
||||
// entity isn't double-encoded.
|
||||
$tgtRaw = (string)$listElem[4];
|
||||
// Append this operator's own alias in parentheses when the
|
||||
// target is a plain "TG <id>" and TGList_CN.json has a name for
|
||||
// it — MMDVMHost's log never carries a name, only the numeric
|
||||
// ID, so without this lookup the column can only ever show
|
||||
// digits (see getTGNameMap() in functions.php).
|
||||
if (preg_match('/^TG\s+(\d+)$/', trim($tgtRaw), $tgMatch)) {
|
||||
$tgName = getTGNameMap()[(int)$tgMatch[1]] ?? null;
|
||||
if ($tgName !== null) { $tgtRaw .= " ($tgName)"; }
|
||||
}
|
||||
if (strlen($tgtRaw) === 1) { $tgtRaw = str_pad($tgtRaw, 8, ' ', STR_PAD_LEFT); }
|
||||
$tgtHtml = htmlspecialchars($tgtRaw, ENT_QUOTES, 'UTF-8');
|
||||
$src = htmlspecialchars((string)$listElem[5], ENT_QUOTES, 'UTF-8');
|
||||
$dur = htmlspecialchars((string)$listElem[6], ENT_QUOTES, 'UTF-8');
|
||||
$loss = htmlspecialchars((string)(isset($listElem[7]) ? $listElem[7] : ''), ENT_QUOTES, 'UTF-8');
|
||||
$ber = htmlspecialchars((string)(isset($listElem[8]) ? $listElem[8] : ''), ENT_QUOTES, 'UTF-8');
|
||||
|
||||
echo "<tr>";
|
||||
echo "<td align=\"left\">$local_time</td>";
|
||||
echo "<td align=\"left\">$modeHtml</td>";
|
||||
if (is_numeric($listElem[2])) {
|
||||
if ($listElem[2] > 9999) { echo "<td align=\"left\"><a href=\"".$idLookupUrl.$csUrl."\" target=\"_blank\">$cs</a></td>"; }
|
||||
else { echo "<td align=\"left\">$cs</td>"; }
|
||||
} elseif (!preg_match('/[A-Za-z].*[0-9]|[0-9].*[A-Za-z]/', $listElem[2])) {
|
||||
echo "<td align=\"left\">$cs</td>";
|
||||
} else {
|
||||
// Strip any "-suffix" before linking — re-derive the
|
||||
// url-encoded form from the trimmed value.
|
||||
$csTrim = (strpos($listElem[2], "-") > 0)
|
||||
? substr($listElem[2], 0, strpos($listElem[2], "-"))
|
||||
: (string)$listElem[2];
|
||||
$csTrimHtml = htmlspecialchars($csTrim, ENT_QUOTES, 'UTF-8');
|
||||
$csTrimUrl = rawurlencode($csTrim);
|
||||
if ( $listElem[3] && $listElem[3] != ' ' ) {
|
||||
echo "<td align=\"left\"><div style=\"float:left;\"><a href=\"".$callsignLookupUrl.$csTrimUrl."\" target=\"_blank\">$csTrimHtml</a>/$csSuffix</div> <div style=\"text-align:right;\">(<a href=\"https://aprs.fi/#!call=".$csTrimUrl."*\" target=\"_blank\">GPS</a>)</div></td>";
|
||||
} else {
|
||||
echo "<td align=\"left\"><div style=\"float:left;\"><a href=\"".$callsignLookupUrl.$csTrimUrl."\" target=\"_blank\">$csTrimHtml</a></div> <div style=\"text-align:right;\">(<a href=\"https://aprs.fi/#!call=".$csTrimUrl."*\" target=\"_blank\">GPS</a>)</div></td>";
|
||||
}
|
||||
}
|
||||
|
||||
if ( substr($tgtRaw, 0, 6) === 'CQCQCQ' ) {
|
||||
echo "<td align=\"left\">$tgtHtml</td>";
|
||||
} else {
|
||||
echo "<td align=\"left\">".str_replace(' ', ' ', $tgtHtml)."</td>";
|
||||
}
|
||||
|
||||
|
||||
if ($listElem[5] == "RF"){
|
||||
echo "<td style=\"background:#1d1;\">RF</td>";
|
||||
}else{
|
||||
echo "<td>$src</td>";
|
||||
}
|
||||
if ($listElem[6] == null) {
|
||||
// Live duration
|
||||
$utc_time = $listElem[0];
|
||||
$utc_tz = new DateTimeZone('UTC');
|
||||
$now = new DateTime("now", $utc_tz);
|
||||
$dt = new DateTime($utc_time, $utc_tz);
|
||||
$duration = $now->getTimestamp() - $dt->getTimestamp();
|
||||
$duration_string = $duration<999 ? round($duration) . "+" : "∞";
|
||||
echo "<td colspan =\"3\" style=\"background:#f33;\">TX " . $duration_string . " sec</td>";
|
||||
} else if ($listElem[6] == "DMR Data") {
|
||||
echo "<td colspan =\"3\" style=\"background:#1d1;\">DMR Data</td>";
|
||||
} else if ($listElem[6] == "POCSAG Data") {
|
||||
echo "<td colspan =\"3\" style=\"background:#1d1;\">POCSAG Data</td>";
|
||||
} else {
|
||||
echo "<td>$dur</td>";
|
||||
|
||||
// Colour the Loss Field
|
||||
if (floatval($listElem[7]) < 1) { echo "<td>$loss</td>"; }
|
||||
elseif (floatval($listElem[7]) == 1) { echo "<td style=\"background:#1d1;\">$loss</td>"; }
|
||||
elseif (floatval($listElem[7]) > 1 && floatval($listElem[7]) <= 3) { echo "<td style=\"background:#fa0;\">$loss</td>"; }
|
||||
else { echo "<td style=\"background:#f33;\">$loss</td>"; }
|
||||
|
||||
// Colour the BER Field
|
||||
if (floatval($listElem[8]) == 0) { echo "<td>$ber</td>"; }
|
||||
elseif (floatval($listElem[8]) >= 0.0 && floatval($listElem[8]) <= 1.9) { echo "<td style=\"background:#1d1;\">$ber</td>"; }
|
||||
elseif (floatval($listElem[8]) >= 2.0 && floatval($listElem[8]) <= 4.9) { echo "<td style=\"background:#fa0;\">$ber</td>"; }
|
||||
else { echo "<td style=\"background:#f33;\">$ber</td>"; }
|
||||
}
|
||||
echo "</tr>\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user