mirror of
https://github.com/opnsense/core.git
synced 2026-05-28 04:34:51 -04:00
network time: status: refactor to MVC (#9361)
This commit is contained in:
parent
30987d973a
commit
02fa9e41da
10 changed files with 501 additions and 291 deletions
6
plist
6
plist
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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']);
|
||||
}
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
157
src/opnsense/mvc/app/views/OPNsense/Ntpd/status.volt
Normal file
157
src/opnsense/mvc/app/views/OPNsense/Ntpd/status.volt
Normal 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}° ${(gps.lat_min * 60).toFixed(5)}${gps.lat_dir})
|
||||
</div>
|
||||
<div class="gps-item">
|
||||
<strong>Longitude:</strong>
|
||||
${gps.lon.toFixed(5)}
|
||||
(${gps.lon_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>
|
||||
168
src/opnsense/scripts/ntpd/ntpd_status.php
Executable file
168
src/opnsense/scripts/ntpd/ntpd_status.php
Executable 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);
|
||||
5
src/opnsense/service/conf/actions.d/actions_ntpd.conf
Normal file
5
src/opnsense/service/conf/actions.d/actions_ntpd.conf
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
[status]
|
||||
command:/usr/local/opnsense/scripts/ntpd/ntpd_status.php
|
||||
parameters:
|
||||
type:script_output
|
||||
message:Show ntpd status
|
||||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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); ?>° <?= 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); ?>° <?= 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"); ?>
|
||||
Loading…
Reference in a new issue