network time: status: refactor to MVC (#9361)

This commit is contained in:
Stephan de Wit 2025-11-11 14:08:53 +01:00 committed by GitHub
parent 30987d973a
commit 02fa9e41da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 501 additions and 291 deletions

6
plist
View file

@ -423,6 +423,8 @@
/usr/local/opnsense/mvc/app/controllers/OPNsense/Monit/forms/general.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Monit/forms/services.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Monit/forms/tests.xml
/usr/local/opnsense/mvc/app/controllers/OPNsense/Ntpd/Api/ServiceController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/Ntpd/StatusController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/OpenVPN/Api/ClientOverwritesController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/OpenVPN/Api/ExportController.php
/usr/local/opnsense/mvc/app/controllers/OPNsense/OpenVPN/Api/InstancesController.php
@ -974,6 +976,7 @@
/usr/local/opnsense/mvc/app/views/OPNsense/Kea/leases6.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Monit/index.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Monit/status.volt
/usr/local/opnsense/mvc/app/views/OPNsense/Ntpd/status.volt
/usr/local/opnsense/mvc/app/views/OPNsense/OpenVPN/cso.volt
/usr/local/opnsense/mvc/app/views/OPNsense/OpenVPN/export.volt
/usr/local/opnsense/mvc/app/views/OPNsense/OpenVPN/instances.volt
@ -1269,6 +1272,7 @@
/usr/local/opnsense/scripts/netflow/lib/aggregates/source.py
/usr/local/opnsense/scripts/netflow/lib/flowparser.py
/usr/local/opnsense/scripts/netflow/lib/parse.py
/usr/local/opnsense/scripts/ntpd/ntpd_status.php
/usr/local/opnsense/scripts/openssh/ssh_query.py
/usr/local/opnsense/scripts/openvpn/client_connect.php
/usr/local/opnsense/scripts/openvpn/client_disconnect.sh
@ -1397,6 +1401,7 @@
/usr/local/opnsense/service/conf/actions.d/actions_kea.conf
/usr/local/opnsense/service/conf/actions.d/actions_monit.conf
/usr/local/opnsense/service/conf/actions.d/actions_netflow.conf
/usr/local/opnsense/service/conf/actions.d/actions_ntpd.conf
/usr/local/opnsense/service/conf/actions.d/actions_openssh.conf
/usr/local/opnsense/service/conf/actions.d/actions_openvpn.conf
/usr/local/opnsense/service/conf/actions.d/actions_shaper.conf
@ -2505,7 +2510,6 @@
/usr/local/www/services_ntpd_pps.php
/usr/local/www/services_opendns.php
/usr/local/www/services_router_advertisements.php
/usr/local/www/status_ntpd.php
/usr/local/www/status_wireless.php
/usr/local/www/system_advanced_admin.php
/usr/local/www/system_advanced_firewall.php

View file

@ -0,0 +1,118 @@
<?php
/*
* Copyright (C) 2025 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Ntpd\Api;
use OPNsense\Base\ApiControllerBase;
use OPNsense\Core\Backend;
class ServiceController extends ApiControllerBase
{
private function keyMetadata()
{
return [
'server' => gettext('The remote NTP server\'s address or reference (IP or hostname)'),
'refid' => gettext('Reference ID of the server that the remote peer is synchronized to. This may be another server or a hardware device.'),
'stratum' => gettext('Distance from the top of the time hierarchy (16 = unsynchronized, 1 = hardware device that provides true time)'),
'type' => gettext('Connection type'),
'when' => gettext('How many seconds ago the last NTP packet was received from this peer'),
'poll' => gettext('Poll interval (in seconds) between requests to this peer. Typically increases automatically'),
'reach' => gettext('An octal value showing an 8-bit shift register of the last 8 reachability checks'),
'delay' => gettext('Round-trip delay (in milliseconds) to the server. Lower is better'),
'offset' => gettext('The time difference between your system clock and the server clock (in milliseconds)'),
'jitter' => gettext('The variation in offset over time (in milliseconds). Lower means a more stable connection')
];
}
private function symbolMetadata()
{
return [
'status' => [
'*' => [
'descr' => gettext('The current system peer your machine is syncing time from'),
'status' => gettext('Active Peer')
],
'+' => [
'descr' => gettext('Candidates that could be used if the primary fails'),
'status' => gettext('Candidate')
],
'o' => [
'descr' => gettext('Peer synchronized to a Pulse Per Second signal'),
'status' => gettext('PPS Peer')
],
'#' => [
'descr' => gettext('A source that is selected as a "backup" in the pool'),
'status' => gettext('Selected')
],
'.' => [
'descr' => gettext('Peer was considered, but rejected by the intersection algorithm'),
'status' => gettext('Excess Peer')
],
'x' => [
'descr' => gettext('The peer is deemed to be delivering incorrect time, this peer is ignored'),
'status' => gettext('False Ticker')
],
'-' => [
'descr' => gettext('Peer responded, but statistically out of sync with the main cluster'),
'status' => gettext('Outlier')
],
' ' => [
'descr' => gettext('Not currently considered'),
'status' => gettext('Not Considered')
]
],
'connection_type' => [
'u' => gettext('Unicast'),
'b' => gettext('Broadcast'),
'l' => gettext('Local'),
'm' => gettext('Multicast'),
's' => gettext('Symmetric Active, this machine and the peer can provide time to each other'),
'p' => gettext('This peer was discovered via an NTP pool association'),
]
];
}
public function metaAction()
{
return [
'key' => $this->keyMetadata(),
'symbols' => $this->symbolMetadata()
];
}
public function gpsAction()
{
return json_decode((new Backend())->configdRun('ntpd status'), true)['gps'];
}
public function statusAction()
{
$status = json_decode((new Backend())->configdRun('ntpd status'), true);
return $this->searchRecordsetBase($status['ntpq_servers']);
}
}

View file

@ -0,0 +1,43 @@
<?php
/*
* Copyright (C) 2025 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Ntpd;
use OPNsense\Base\IndexController;
/**
* Class StatusController
* @package OPNsense\Ntpd
*/
class StatusController extends IndexController
{
public function indexAction()
{
$this->view->pick('OPNsense/Ntpd/status');
}
}

View file

@ -14,7 +14,8 @@
<page-status-ntp>
<name>Status: NTP</name>
<patterns>
<pattern>status_ntpd.php*</pattern>
<pattern>/ui/ntpd/status/*</pattern>
<pattern>/api/ntpd/service/status</pattern>
</patterns>
</page-status-ntp>
<page-services-ntp-pps>

View file

@ -4,7 +4,7 @@
<General order="10" url="/services_ntpd.php"/>
<GPS order="20" url="/services_ntpd_gps.php"/>
<PPS order="30" url="/services_ntpd_pps.php"/>
<Status order="40" url="/status_ntpd.php"/>
<Status order="40" url="/ui/ntpd/status"/>
<Log order="50" VisibleName="Log File" url="/ui/diagnostics/log/core/ntpd"/>
</NTP>
</Services>

View file

@ -0,0 +1,157 @@
{#
# Copyright (c) 2025 Deciso B.V.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#}
<script>
$( document ).ready(function() {
ajaxGet('/api/ntpd/service/meta', {}, function (meta, status) {
function headerFormatters() {
let result = {};
for (let key in meta['key']) {
result[key] = function (column) {
return `<span class="has-tooltip" data-toggle="tooltip" title="${meta['key'][key]}">${column.title}</span>`;
};
}
return result;
}
const table = $('#ntpd-table').UIBootgrid({
search: '/api/ntpd/service/status',
options: {
static: true,
navigation: 0,
selection: false,
multiSelect: false,
headerFormatters: {
...headerFormatters()
},
formatters: {
status: (col, row) => {
const val = meta.symbols?.status?.[row.status]?.status, metaVal = meta.symbols?.status?.[row.status]?.descr;
return metaVal ? `<span class="has-tooltip" data-toggle="tooltip" title="${metaVal}">${val}</span>` : val;
},
type: (col, row) => {
const val = row[col.id], metaVal = meta.symbols?.connection_type?.[row.type];
return metaVal ? `<span class="has-tooltip" data-toggle="tooltip" title="${metaVal}">${val}</span>` : val;
}
}
}
});
});
ajaxGet('/api/ntpd/service/gps', {}, function (data, status) {
const gps = data.gps || {};
const container = document.getElementById('gps-content');
if (!gps.ok) {
$('#gps-status').remove();
return;
}
let html = '';
if (gps.lat !== undefined && gps.lon !== undefined) {
html += `
<div class="gps-item">
<strong>Latitude:</strong>
${gps.lat.toFixed(5)}
(${gps.lat_deg}&deg; ${(gps.lat_min * 60).toFixed(5)}${gps.lat_dir})
</div>
<div class="gps-item">
<strong>Longitude:</strong>
${gps.lon.toFixed(5)}
(${gps.lon_deg}&deg; ${(gps.lon_min * 60).toFixed(5)}${gps.lon_dir})
</div>
<div class="gps-item">
<a target="_gmaps" href="https://maps.google.com/?q=${gps.lat},${gps.lon}">
View on Google Maps
</a>
</div>
`;
}
if (gps.alt !== undefined) {
html += `
<div class="gps-item">
<strong>Altitude:</strong> ${gps.alt} ${gps.alt_unit || ''}
</div>
`;
}
if (gps.sat !== undefined || gps.gps_satview !== undefined) {
html += `
<div class="gps-item">
<strong>Satellites:</strong>
${gps.gps_satview ? `in view ${gps.gps_satview}` : ''}
${gps.gps_satview && gps.sat ? ', ' : ''}
${gps.sat ? `in use ${gps.sat}` : ''}
</div>
`;
}
container.innerHTML = html || '<p>No valid GPS fields available.</p>';
});
});
</script>
<style>
.has-tooltip {
cursor: help;
}
.has-tooltip::after {
content: " ⓘ";
font-size: 0.8em;
color: currentColor; /* theme-agnostic */
opacity: 0.6;
}
</style>
<div class="tab-content content-box" style="padding: 10px;">
<table id="ntpd-table" class="table table-condensed table-hover table-striped table-responsive">
<thead>
<tr>
<th data-column-id="status" data-formatter="status" data-sortable="false">{{ lang._('Status') }}</th>
<th data-column-id="server" data-formatter="server" data-sortable="false">{{ lang._('Server') }}</th>
<th data-column-id="refid" data-formatter="refid" data-sortable="false">{{ lang._('Ref ID') }}</th>
<th data-column-id="stratum" data-formatter="stratum" data-sortable="false">{{ lang._('Stratum') }}</th>
<th data-column-id="type" data-formatter="type" data-sortable="false">{{ lang._('Type') }}</th>
<th data-column-id="when" data-formatter="when" data-sortable="false">{{ lang._('When') }}</th>
<th data-column-id="poll" data-formatter="poll" data-sortable="false">{{ lang._('Poll') }}</th>
<th data-column-id="reach" data-formatter="reach" data-sortable="false">{{ lang._('Reach') }}</th>
<th data-column-id="delay" data-formatter="delay" data-sortable="false">{{ lang._('Delay') }}</th>
<th data-column-id="offset" data-formatter="offset" data-sortable="false">{{ lang._('Offset') }}</th>
<th data-column-id="jitter" data-formatter="jitter" data-sortable="false">{{ lang._('Jitter') }}</th>
</tr>
</thead>
</table>
<div id="gps-status">
<div>
<h4>GPS Information</h4>
<div id="gps-content"></div>
</div>
</div>
</div>

View file

@ -0,0 +1,168 @@
#!/usr/local/bin/php
<?php
/*
* Copyright (C) 2014-2025 Deciso B.V.
* Copyright (C) 2013 Dagorlad
* Copyright (C) 2012 Jim Pingle <jimp@pfsense.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
require_once('script/load_phalcon.php');
use OPNsense\Core\Config;
$result = [];
exec("/usr/local/sbin/ntpq -pnw", $ntpq_output);
$ntpq_servers = [];
$server = [];
foreach (array_slice($ntpq_output, 3) as $line) {
if (empty($server['status'])) {
$server['status'] = substr($line, 0, 1);
}
$line = substr($line, 1);
$peerinfo = preg_split('/\s+/', $line);
if (empty($server['server'])) {
$server['server'] = $peerinfo[0];
}
if (empty($peerinfo[1])) {
$server = [];
continue;
}
$server['refid'] = $peerinfo[1];
$server['stratum'] = $peerinfo[2];
$server['type'] = $peerinfo[3];
$server['when'] = $peerinfo[4];
$server['poll'] = $peerinfo[5];
$server['reach'] = $peerinfo[6];
$server['delay'] = $peerinfo[7];
$server['offset'] = $peerinfo[8];
$server['jitter'] = $peerinfo[9];
if ($server['type'] === 'p') {
$server['server'] = gettext('DNS Pool');
}
$ntpq_servers[] = $server;
$server = [];
}
$result['ntpq_servers'] = $ntpq_servers;
$result['gps'] = [];
function nmeaGeoParts(?string $val, ?string $dir): ?array {
if ($val === null || $dir === null) return null;
if (!preg_match('/^\d+(\.\d+)?$/', $val)) return null;
[$int, $frac] = array_pad(explode('.', $val, 2), 2, '0');
$degStr = substr($int, 0, max(strlen($int) - 2, 0));
$minStr = substr($int, -2) . '.' . $frac;
$deg = ($degStr === '' ? 0 : (int)$degStr);
$min = (float)$minStr;
$dir = strtoupper($dir);
$sign = ($dir === 'S' || $dir === 'W') ? -1 : 1;
$dec = $sign * ($deg + $min / 60.0);
return ['dec' => $dec, 'deg' => $deg, 'min' => $min, 'dir' => $dir];
}
exec("/usr/local/sbin/ntpq -c clockvar 2>/dev/null", $ntpq_clockvar_output);
foreach ($ntpq_clockvar_output as $line) {
if (strncmp($line, "timecode=", 9) !== 0) continue;
if (!preg_match('/"([^"]+)"/', $line, $m)) continue;
$vars = explode(',', $m[1]);
$type = $vars[0] ?? '';
$gps = ['sentence' => $type];
switch ($type) {
case '$GPRMC': {
$gps['ok'] = (($vars[2] ?? '') === 'A');
$lat = nmeaGeoParts($vars[3] ?? null, $vars[4] ?? null);
$lon = nmeaGeoParts($vars[5] ?? null, $vars[6] ?? null);
break;
}
case '$GPGGA': {
$gps['ok'] = $vars[6] ?? null;
$gps['alt'] = $vars[9] ?? null;
$gps['alt_unit'] = $vars[10] ?? null;
$gps['sat'] = $vars[7] ?? null;
$lat = nmeaGeoParts($vars[2] ?? null, $vars[3] ?? null);
$lon = nmeaGeoParts($vars[4] ?? null, $vars[5] ?? null);
break;
}
case '$GPGLL': {
$gps['ok'] = (($vars[6] ?? '') === 'A');
$lat = nmeaGeoParts($vars[1] ?? null, $vars[2] ?? null);
$lon = nmeaGeoParts($vars[3] ?? null, $vars[4] ?? null);
break;
}
default:
$lat = $lon = null;
}
// Merge parsed lat/lon parts if present
if ($lat) {
$gps += [
'lat' => $lat['dec'],
'lat_deg' => $lat['deg'],
'lat_min' => $lat['min'],
'lat_dir' => $lat['dir'],
];
}
if ($lon) {
$gps += [
'lon' => $lon['dec'],
'lon_deg' => $lon['deg'],
'lon_min' => $lon['min'],
'lon_dir' => $lon['dir'],
];
}
$gps = array_filter($gps, static fn($v) => $v !== null && $v !== '');
$result['gps'] = array_replace($result['gps'], $gps);
}
$cfg = Config::getInstance()->object();
if (!empty($cfg->ntpd->gps->type) && (string)$cfg->ntpd->gps->type == 'SureGPS' && isset($result['gps']['ok'])) {
// GSV message is only enabled by init commands in services_ntpd_gps.php for SureGPS board
$gpsport = fopen("/dev/gps0", "r+");
while ($gpsport) {
$buffer = fgets($gpsport);
if (substr($buffer, 0, 6) == '$GPGSV') {
$gpgsv = explode(',',$buffer);
$result['gps']['gps_satview'] = $gpgsv[3];
break;
}
}
}
echo json_encode($result);

View file

@ -0,0 +1,5 @@
[status]
command:/usr/local/opnsense/scripts/ntpd/ntpd_status.php
parameters:
type:script_output
message:Show ntpd status

View file

@ -378,7 +378,8 @@ class UIBootgrid {
let def = cell.getColumn().getDefinition();
let column = {
id: def.field,
visible: def.visible
visible: def.visible,
title: def.title
};
onRendered(() => {

View file

@ -1,287 +0,0 @@
<?php
/*
* Copyright (C) 2014-2016 Deciso B.V.
* Copyright (C) 2013 Dagorlad
* Copyright (C) 2012 Jim Pingle <jimp@pfsense.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
require_once("guiconfig.inc");
require_once("interfaces.inc");
exec("/usr/local/sbin/ntpq -pnw | /usr/bin/tail +3", $ntpq_output);
$ntpq_servers = array();
$server = array();
foreach ($ntpq_output as $line) {
$status = gettext('Unknown');
switch (substr($line, 0, 1)) {
case ' ':
$status = gettext('Unreach/Pending');
break;
case '*':
$status = gettext('Active Peer');
break;
case '+':
$status = gettext('Candidate');
break;
case 'o':
$status = gettext('PPS Peer');
break;
case '#':
$status = gettext('Selected');
break;
case '.':
$status = gettext('Excess Peer');
break;
case 'x':
$status = gettext('False Ticker');
break;
case '-':
$status = gettext('Outlier');
break;
}
if (empty($server['status'])) {
$server['status'] = $status;
}
$line = substr($line, 1);
$peerinfo = preg_split('/\s+/', $line);
if (empty($server['server'])) {
$server['server'] = $peerinfo[0];
}
if (empty($peerinfo[1])) {
continue;
}
$server['refid'] = $peerinfo[1];
$server['stratum'] = $peerinfo[2];
$server['type'] = $peerinfo[3];
$server['when'] = $peerinfo[4];
$server['poll'] = $peerinfo[5];
$server['reach'] = $peerinfo[6];
$server['delay'] = $peerinfo[7];
$server['offset'] = $peerinfo[8];
$server['jitter'] = $peerinfo[9];
$ntpq_servers[] = $server;
$server = array();
}
exec("/usr/local/sbin/ntpq -c clockvar", $ntpq_clockvar_output);
foreach ($ntpq_clockvar_output as $line) {
if (substr($line, 0, 9) == "timecode=") {
$tmp = explode('"', $line);
$tmp = $tmp[1];
$gps_vars = explode(',', $tmp);
if (substr($tmp, 0, 6) == '$GPRMC') {
if (is_numeric($gps_vars[3]) && is_numeric($gps_vars[5])) {
list ($gps_lat_deg, $gps_lat_min) = explode('.', $gps_vars[3]);
$gps_lat_min = substr($gps_lat_deg, -2) .".". $gps_lat_min;
$gps_lat_deg = substr($gps_lat_deg, 0, strlen($gps_lat_deg) - 2);
$gps_lat_min /= 60.0;
$gps_lat = $gps_lat_deg + $gps_lat_min;
$gps_lat_dir = $gps_vars[4];
$gps_lat = $gps_lat * ($gps_lat_dir == 'N' ? 1 : -1);
list ($gps_lon_deg, $gps_lon_min) = explode('.', $gps_vars[5]);
$gps_lon_min = substr($gps_lon_deg, -2) .".". $gps_lon_min;
$gps_lon_deg = substr($gps_lon_deg, 0, strlen($gps_lon_deg) - 2);
$gps_lon_min /= 60.0;
$gps_lon = $gps_lon_deg + $gps_lon_min;
$gps_lon_dir = $gps_vars[6];
$gps_lon = $gps_lon * ($gps_lon_dir == 'E' ? 1 : -1);
}
$gps_ok = $gps_vars[2] == 'A';
} elseif (substr($tmp, 0, 6) == '$GPGGA') {
if (is_numeric($gps_vars[2]) && is_numeric($gps_vars[4])) {
list ($gps_lat_deg, $gps_lat_min) = explode('.', $gps_vars[2]);
$gps_lat_min = substr($gps_lat_deg, -2) .".". $gps_lat_min;
$gps_lat_deg = substr($gps_lat_deg, 0, strlen($gps_lat_deg) - 2);
$gps_lat_min /= 60.0;
$gps_lat = $gps_lat_deg + $gps_lat_min;
$gps_lat_dir = $gps_vars[3];
$gps_lat = $gps_lat * ($gps_lat_dir == 'N' ? 1 : -1);
list ($gps_lon_deg, $gps_lon_min) = explode('.', $gps_vars[4]);
$gps_lon_min = substr($gps_lon_deg, -2) .".". $gps_lon_min;
$gps_lon_deg = substr($gps_lon_deg, 0, strlen($gps_lon_deg) - 2);
$gps_lon_min /= 60.0;
$gps_lon = $gps_lon_deg + $gps_lon_min;
$gps_lon_dir = $gps_vars[5];
$gps_lon = $gps_lon * ($gps_lon_dir == 'E' ? 1 : -1);
}
$gps_ok = $gps_vars[6];
$gps_alt = $gps_vars[9];
$gps_alt_unit = $gps_vars[10];
$gps_sat = $gps_vars[7];
} elseif (substr($tmp, 0, 6) == '$GPGLL') {
if (is_numeric($gps_vars[1]) && is_numeric($gps_vars[3])) {
list ($gps_lat_deg, $gps_lat_min) = explode('.', $gps_vars[1]);
$gps_lat_min = substr($gps_lat_deg, -2) .".". $gps_lat_min;
$gps_lat_deg = substr($gps_lat_deg, 0, strlen($gps_lat_deg) - 2);
$gps_lat_min /= 60.0;
$gps_lat = $gps_lat_deg + $gps_lat_min;
$gps_lat_dir = $gps_vars[2];
$gps_lat = $gps_lat * ($gps_lat_dir == 'N' ? 1 : -1);
list ($gps_lon_deg, $gps_lon_min) = explode('.', $gps_vars[3]);
$gps_lon_min = substr($gps_lon_deg, -2) .".". $gps_lon_min;
$gps_lon_deg = substr($gps_lon_deg, 0, strlen($gps_lon_deg) - 2);
$gps_lon_min /= 60.0;
$gps_lon = $gps_lon_deg + $gps_lon_min;
$gps_lon_dir = $gps_vars[4];
$gps_lon = $gps_lon * ($gps_lon_dir == 'E' ? 1 : -1);
}
$gps_ok = $gps_vars[6] == 'A';
}
}
}
if (isset($config['ntpd']['gps']['type']) && ($config['ntpd']['gps']['type'] == 'SureGPS') && isset($gps_ok)) {
//GSV message is only enabled by init commands in services_ntpd_gps.php for SureGPS board
$gpsport = fopen("/dev/gps0", "r+");
while ($gpsport) {
$buffer = fgets($gpsport);
if (substr($buffer, 0, 6) == '$GPGSV') {
$gpgsv = explode(',',$buffer);
$gps_satview = $gpgsv[3];
break;
}
}
}
$service_hook = 'ntpd';
include("head.inc");
?>
<body>
<?php include("fbegin.inc"); ?>
<section class="page-content-main">
<div class="container-fluid">
<div class="row">
<section class="col-xs-12">
<div class="content-box">
<header class="content-box-head container-fluid">
<h3><?=gettext("Network Time Protocol Status");?></h3>
</header>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th><?=gettext("Status"); ?></th>
<th><?=gettext("Server"); ?></th>
<th><?=gettext("Ref ID"); ?></th>
<th><?=gettext("Stratum"); ?></th>
<th><?=gettext("Type"); ?></th>
<th><?=gettext("When"); ?></th>
<th><?=gettext("Poll"); ?></th>
<th><?=gettext("Reach"); ?></th>
<th><?=gettext("Delay"); ?></th>
<th><?=gettext("Offset"); ?></th>
<th><?=gettext("Jitter"); ?></th>
</tr>
</thead>
<tbody>
<?php
if (count($ntpq_servers) == 0): ?>
<tr>
<td colspan="11">
<?= gettext('No peers found.') ?>
</td>
</tr>
<?php
else:
$i = 0;
foreach ($ntpq_servers as $server): ?>
<tr>
<td><?=$server['status'];?></td>
<td><?=$server['server'];?></td>
<td><?=$server['refid'];?></td>
<td><?=$server['stratum'];?></td>
<td><?=$server['type'];?></td>
<td><?=$server['when'];?></td>
<td><?=$server['poll'];?></td>
<td><?=$server['reach'];?></td>
<td><?=$server['delay'];?></td>
<td><?=$server['offset'];?></td>
<td><?=$server['jitter'];?></td>
</tr>
<?php
$i++;
endforeach;
endif; ?>
</tbody>
</table>
<?php
if (isset($gps_ok)):
$gps_goo_lnk = 2; ?>
<table class="table table-striped">
<thead>
<tr>
<th><?=gettext("Clock Latitude"); ?></th>
<th><?=gettext("Clock Longitude"); ?></th>
<?php if (isset($gps_alt)) { echo '<th>' . gettext("Clock Altitude") . '</th>'; $gps_goo_lnk++;}?>
<?php if (isset($gps_sat) || isset($gps_satview)) { echo '<th>' . gettext("Satellites") . '</th>'; $gps_goo_lnk++;}?>
</tr>
</thead>
<tbody>
<tr>
<?php if (isset($gps_lat)): ?>
<td><?= sprintf("%.5f", $gps_lat); ?> (<?= sprintf("%d", $gps_lat_deg); ?>&deg; <?= sprintf("%.5f", $gps_lat_min*60); ?><?= $gps_lat_dir ?>)</td>
<?php else: ?>
<td><?= gettext('N/A') ?></td>
<?php endif ?>
<?php if (isset($gps_lon)): ?>
<td><?= sprintf("%.5f", $gps_lon); ?> (<?= sprintf("%d", $gps_lon_deg); ?>&deg; <?= sprintf("%.5f", $gps_lon_min*60); ?><?= $gps_lon_dir ?>)</td>
<?php else: ?>
<td><?= gettext('N/A') ?></td>
<?php endif ?>
<?php if (isset($gps_alt)) { echo '<td>' . $gps_alt . ' ' . $gps_alt_unit . '</td>';}?>
<td>
<?php
if (isset($gps_satview)) {echo 'in view ' . intval($gps_satview);}
if (isset($gps_sat) && isset($gps_satview)) {echo ', ';}
if (isset($gps_sat)) {echo 'in use ' . $gps_sat;}
?>
</td>
</tr>
<?php if (isset($gps_lon) && isset($gps_lat)): ?>
<tr>
<td colspan="<?= html_safe($gps_goo_lnk) ?>"><a target="_gmaps" href="https://maps.google.com/?q=<?= html_safe($gps_lat) ?>,<?= html_safe($gps_lon) ?>">Google Maps Link</a></td>
</tr>
<?php endif ?>
</tbody>
</table>
<?php
endif; ?>
</div>
</div>
</section>
</div>
</div>
</section>
<?php include("foot.inc"); ?>