* Tue Jun 24 2025 Brian Read <brianr@koozali.org> 11.0.0-95.sme

- Add clock ticker to datetime panel [SME: 13054]
- Add Test Server button for ntp server [SME: 13048]
- Add checking that date is fully valid [SME: 13055]
This commit is contained in:
Brian Read 2025-06-25 10:35:16 +01:00
parent 0341d02608
commit 0f2e2b82aa
8 changed files with 210 additions and 62 deletions

View File

@ -37,7 +37,7 @@ use esmith::NavigationDB; # no UTF8 raw is ok for ASCII only flat file
use SrvMngr_Auth qw(check_admin_access);
#this is overwrittrn with the "release" by the spec file - release can be "99.el8.sme"
our $VERSION = '93.el8.sme';
our $VERSION = '94.el8.sme';
#Extract the release value
if ($VERSION =~ /^(\d+)/) {
$VERSION = $1; # $1 contains the matched numeric digits
@ -328,6 +328,7 @@ sub setup_routing {
$if_admin->get('/datetime')->to('datetime#main')->name('datetime');
$if_admin->post('/datetimeu')->to('datetime#do_update')->name('datetimeu');
$if_admin->get('/datetimed')->to('datetime#do_display')->name('datetimed');
$if_admin->post('/datetimet')->to('datetime#do_testntp')->name('datetimet');
$if_admin->get('/directory')->to('directory#main')->name('directory');

View File

@ -13,6 +13,8 @@ use esmith::NetworksDB::UTF8;
use esmith::HostsDB;
use esmith::DomainsDB::UTF8;
use DateTime;
use constant FALSE => 0;
use constant TRUE => 1;
@ -79,6 +81,7 @@ my $ddb;
my $now_sec = sprintf('%02d', $today_sec);
my $current_year = $today_year;
my $ntpserverurl = $cdb->get_prop('ntpd','NTPServer');
my $now = DateTime->now( time_zone => 'local' );
my %ret = (
# fields from Inputs
'time_mode'=>($ntpserverurl eq '' ? 'dat_manually_set' : 'dat_ntp_server'),
@ -90,6 +93,8 @@ my $ddb;
'minute'=>"$now_min",
'second'=>"$now_sec",
'ntpstatus' => $cdb->get_prop('ntpd','status') || 'disabled',
# and the current time as a full format
'currentdatetime' => $now->strftime('%Y-%m-%dT%H:%M:%S')
);
return %ret;
@ -224,38 +229,12 @@ sub validate_change_datetime {
$timezone = "US/Eastern";
}
my $month = $c->param('month');
if ($month =~ /^(\d{1,2})$/) {
$month = $1;
} else {
$month = "1";
}
if (($month < 1) || ($month > 12)) {
return $c->l('dat_INVALID_MONTH') . " $month. " . $c->l('dat_MONTH_BETWEEN_1_AND_12');
}
my $day = $c->param('day');
if ($day =~ /^(\d{1,2})$/) {
$day = $1;
} else {
$day = "1";
}
if (($day < 1) || ($day > 31)) {
return $c->l('dat_INVALID_DAY') . " $day. " . $c->l('dat_BETWEEN_1_AND_31');
}
my $year = $c->param('year');
if (!is_valid_date($year, $month, $day)){
return $c->l('dat_Invalid_date')
}
if ($year =~ /^(\d{4})$/) {
$year = $1;
} else {
$year = "2000";
}
if (($year < 1900) || ($year > 2200)) {
return $c->l('dat_INVALID_YEAR') . " $year. " . $c->l('dat_FOUR_DIGIT_YEAR');
}
my $hour = $c->param('hour');
if ($hour =~ /^(\d{1,2})$/) {
@ -289,24 +268,6 @@ sub validate_change_datetime {
if (($second < 0) || ($second > 59)) {
return $c->l('dat_INVALID_SECOND') . " $second. " . $c->l('dat_BETWEEN_0_AND_59');
}
#my $ampm = $c->param('Ampm');
#Move to 24 hours clock - not using AM/PM.
#if ($ampm =~ /^(AM|PM)$/) {
#$ampm = $1;
#} else {
#$ampm = "AM";
#}
# force AM so that it actually works on 24hr clock.
#$ampm = "AM";
# convert to 24 hour time
#$hour = $hour % 12;
#if ($ampm eq "PM") {
# $hour = $hour + 12;
#}
#--------------------------------------------------
# Store time zone in configuration database
@ -325,10 +286,27 @@ sub validate_change_datetime {
# and hardware clock
#--------------------------------------------------
my $newdate = sprintf "%02d%02d%02d%02d%04d.%02d", $month, $day, $hour, $minute, $year, $second;
esmith::util::backgroundCommand(2, "/sbin/e-smith/signal-event", "timezone-update", $newdate);
$c->app->log->info("Changing date manually to $newdate");
esmith::util::backgroundCommand(2, "/sbin/e-smith/signal-event", "timezone-update", $newdate); #TEMP!!!
return '';
} ## end sub validate_change_datetime
sub is_valid_date {
my ($year, $month, $day) = @_;
# Check if all parts are defined and integers
return 0 unless defined $year && defined $month && defined $day;
return 0 unless $year =~ /^\d+$/ && $month =~ /^\d+$/ && $day =~ /^\d+$/;
# Try to construct a DateTime object
eval {
DateTime->new(year => $year, month => $month, day => $day);
1;
} or return 0;
return 1;
}
sub update_ntpserver {
my $c = shift;
my $ntpserver = shift;
@ -378,6 +356,4 @@ sub disable_ntp {
return '';
} ## end sub disable_ntp
1;

View File

@ -8,6 +8,9 @@ package SrvMngr::Controller::Datetime;
# heading : System
# description : Date and time
# navigation : 4000 300
#
# ######name : datetimet, method : post, url : /datetimet, ctlact : datetime#testntp
#
# routes : end
#
# Documentation: https://wiki.contribs.org/Datetime
@ -115,7 +118,7 @@ sub do_update {
my $c = shift;
$c->app->log->info($c->log_req);
$c->app->log->info($c->param('month'));
#$c->app->log->info($c->param('month'));
#The most common ones - you might want to delete some of these if they are not used.
@ -173,7 +176,7 @@ sub do_update {
$c->render(template => "datetime");
return
} else {
if ($c->param('time_mode') eq 'data_manually_set') {
if ($c->param('time_mode') eq 'dat_manually_set') {
$c->stash( success => $c->l('dat_UPDATING_CLOCK'));
} else {
$c->stash( success => $c->l('dat_SETTINGS_CHANGED'));
@ -280,4 +283,51 @@ sub do_display {
);
$c->render(template => "datetime");
}
sub do_testntp {
my $c = shift;
my $server = $c->req->json->{ntpserver} // '';
# Strict validation: hostname or IPv4
unless ($server =~ /^(?=.{1,253}$)([a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*|\d{1,3}(?:\.\d{1,3}){3})$/) {
return $c->render(json => { success => 0, error => 'Invalid server name or IP' });
}
my $timeout = 5;
my @cmd = ('timeout', $timeout, 'ntpdate', '-q', $server);
# Run ntpdate and capture output
my $output = qx{@cmd 2>&1};
$c->app->log->info($output);
my $exit_code = $? >> 8;
# Parse for known errors
if ($exit_code == 124) {
return $c->render(json => { success => 0, error => "Timeout: NTP server did not respond within $timeout seconds" });
}
if ($output =~ /no server suitable for synchronization found/i) {
return $c->render(json => { success => 0, error => "No suitable NTP server found or server unreachable" });
}
if ($output =~ /Name or service not known|Temporary failure in name resolution/i) {
return $c->render(json => { success => 0, error => "DNS resolution failed for $server" });
}
if ($output =~ /ntpdig: no eligible servers/i) {
return $c->render(json => { success => 0, error => "Not a an NTP server" });
}
if ($output =~ /permission denied/i) {
return $c->render(json => { success => 0, error => "Permission denied running ntpdate" });
}
if ($exit_code != 0) {
return $c->render(json => { success => 0, error => "ntpdate failed (exit code $exit_code): $output" });
}
# Extract date and time down to seconds from adjust line
my ($datetime) = $output =~ /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/m;
if ($datetime) {
return $c->render(json => { success => 1, time => $datetime });
} else {
return $c->render(json => { success => 0, error => "Could not parse date/time from NTP server response." });}
}
1;

View File

@ -1,5 +1,5 @@
'dat_FORM_TITLE' => 'Date and time configuration',
'dat_The_time_is_currently' => 'The time is currently:',
'dat_INITIAL_DESC' => 'This is where you configure the date and time of this server. You may use an existing network time server or
manually set the date and time for your time zone.',
'dat_SET_DATE_TITLE' => 'Set Date and Time',
@ -59,3 +59,4 @@ clock, and <b>will not</b> try to synchronize from a time server.',
'dat_manually_set' => 'Set manually',
'dat_NTP_Server_URL' =>'NTP Server URL:',
'dat_set_manually' =>'Set Date and Time:',
'dat_Invalid_date' => 'Invalid date',

View File

@ -7,7 +7,7 @@
.datetime-label-col {
background: #e8f3e2; /* light green */
padding: 1em 0em 0em 0em;
min-width: 192px;
min-width: 30%;
display: flex;
align-items: flex-start;
justify-content: flex-end;
@ -16,7 +16,7 @@
}
.datetime-label {
display: block;
display:inline-flex;
}
.datetime-fields-col {
@ -27,3 +27,25 @@
border-left: none;
border-radius: 0 4px 4px 0;
}
.datetime-clock {
min-width: 20em;
display:inline-flex;
border:0px;
padding:5px;
}
.datetime-clock-label {
background-color:#e8f3e2;
display:inline-flex;
width:30%;
font-weight:bold;
text-align:right;
}
.ntp-test-result { font-weight: bold; }
.ntp-test-success { color: green; }
.ntp-test-error { color: red; }
.ntp-test-wait { color: #333; }

View File

@ -16,3 +16,86 @@ document.addEventListener('DOMContentLoaded', function() {
select.addEventListener('change', toggleSections);
toggleSections(); // Set initial state
});
document.addEventListener('DOMContentLoaded', function() {
// Parse the initial server time from the input value
const clockElement = document.getElementById('real-time-clock');
if (!clockElement) return;
// Get the initial server time from the input's value
let serverTime = new Date(clockElement.value.replace(' ', 'T'));
function updateDateTime() {
// Format the date/time string as desired
const daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
const dayOfWeek = daysOfWeek[serverTime.getDay()];
const month = months[serverTime.getMonth()];
const day = serverTime.getDate();
const year = serverTime.getFullYear();
let hours = serverTime.getHours();
const ampm = hours >= 12 ? 'PM' : 'AM';
hours = hours % 12 || 12;
const minutes = serverTime.getMinutes().toString().padStart(2, '0');
const seconds = serverTime.getSeconds().toString().padStart(2, '0');
const dateTimeString = `${dayOfWeek}, ${month} ${day}, ${year} ${hours}:${minutes}:${seconds} ${ampm}`;
clockElement.value = dateTimeString;
// Advance serverTime by one second
serverTime.setSeconds(serverTime.getSeconds() + 1);
}
updateDateTime();
setInterval(updateDateTime, 1000);
});
document.addEventListener('DOMContentLoaded', function() {
const btn = document.getElementById('test-ntp-btn');
const input = document.getElementById('ntpserver');
const result = document.getElementById('ntp-test-result');
btn.addEventListener('click', function() {
const server = input.value.trim();
result.className = 'ntp-test-result'; // reset
if (!server) {
result.textContent = "Please enter a server address.";
result.classList.add('ntp-test-error');
return;
}
result.textContent = "Testing...";
result.classList.add('ntp-test-wait');
fetch('/smanager/datetimet', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ ntpserver: server })
})
.then(response => {
if (!response.ok) {
// HTTP error, e.g., 404, 500
throw new Error(`HTTP error: ${response.status} ${response.statusText}`);
}
return response.json();
})
.then(data => {
result.className = 'ntp-test-result'; // reset
if (data.success) {
result.textContent = `Server time: ${data.time}`;
result.classList.add('ntp-test-success');
} else {
result.textContent = `Error: ${data.error}`;
result.classList.add('ntp-test-error');
}
})
.catch(error => {
// Network error or thrown HTTP error
result.className = 'ntp-test-result ntp-test-error';
result.textContent = `Request failed: ${error.message}`;
});
});
});

View File

@ -27,7 +27,15 @@
<h1><%= $title %></h1><br>
%= $modul
<% my $btn = l('SAVE'); %>
<p>
<br /><br />
<span>
%= label_for 'real-time-clock' => $c->l('dat_The_time_is_currently'), class => 'datetime-clock-label'
</span><span class=data2>
<!--
<div id="real-time-clock"></div>
-->
%= text_field 'clock', id => 'real-time-clock', readonly => 'readonly', class => 'datetime-clock' , value => $dat_data->{currentdatetime}
</span>
% if ($dat_data->{ntpstatus} eq 'disabled') {
<div class='datetime-set-ntp'>
%=l 'dat_NTP_ENABLE_DESC'
@ -68,7 +76,9 @@
<div class=datetime-fields-col>
% my $server_check = '^([a-zA-Z0-9][a-zA-Z0-9\.\-]{0,253}[a-zA-Z0-9]|(\d{1,3}\.){3}\d{1,3})$';
% param 'ntpserver' => $dat_data->{ntpserver} unless param 'ntpserver';
%= text_field ntpserver => placeholder => 'e.g. smeserver.pool.ntp.org',id => 'ntpserver', pattern => $server_check, title => 'Enter a valid hostname or IPv4 address',required => 'required'
%= text_field ntpserver => placeholder => 'e.g. smeserver.pool.ntp.org', id => 'ntpserver', pattern => $server_check, title => 'Enter a valid hostname or IPv4 address', required => 'required'
<button type="button" id="test-ntp-btn" class="btn btn-primary ml-2">Test Server</button>
<span id="ntp-test-result" class="ntp-test-result ml-2"></span>
</div>
</div>
</div>

View File

@ -2,7 +2,7 @@ Summary: Sme server navigation module : manager 2
%define name smeserver-manager
Name: %{name}
%define version 11.0.0
%define release 94
%define release 95
Version: %{version}
Release: %{release}%{?dist}
License: GPL
@ -144,6 +144,11 @@ true
%defattr(-,root,root)
%changelog
* Tue Jun 24 2025 Brian Read <brianr@koozali.org> 11.0.0-95.sme
- Add clock ticker to datetime panel [SME: 13054]
- Add Test Server button for ntp server [SME: 13048]
- Add checking that date is fully valid [SME: 13055]
* Thu Jun 19 2025 Brian Read <brianr@koozali.org> 11.0.0-94.sme
- re-instate datetime routes in SrvMngr.pm - removed by mistake [SME: 13053]